@aigne/afs-synology 1.11.0-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,26 @@
1
+ # Proprietary License
2
+
3
+ Copyright (c) 2024-2025 ArcBlock, Inc. All Rights Reserved.
4
+
5
+ This software and associated documentation files (the "Software") are proprietary
6
+ and confidential. Unauthorized copying, modification, distribution, or use of
7
+ this Software, via any medium, is strictly prohibited.
8
+
9
+ The Software is provided for internal use only within ArcBlock, Inc. and its
10
+ authorized affiliates.
11
+
12
+ ## No License Granted
13
+
14
+ No license, express or implied, is granted to any party for any purpose.
15
+ All rights are reserved by ArcBlock, Inc.
16
+
17
+ ## Public Artifact Distribution
18
+
19
+ Portions of this Software may be released publicly under separate open-source
20
+ licenses (such as MIT License) through designated public repositories. Such
21
+ public releases are governed by their respective licenses and do not affect
22
+ the proprietary nature of this repository.
23
+
24
+ ## Contact
25
+
26
+ For licensing inquiries, contact: legal@arcblock.io
@@ -0,0 +1,11 @@
1
+
2
+ //#region \0@oxc-project+runtime@0.108.0/helpers/decorate.js
3
+ function __decorate(decorators, target, key, desc) {
4
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
5
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
6
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
7
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
8
+ }
9
+
10
+ //#endregion
11
+ exports.__decorate = __decorate;
@@ -0,0 +1,10 @@
1
+ //#region \0@oxc-project+runtime@0.108.0/helpers/decorate.js
2
+ function __decorate(decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ }
8
+
9
+ //#endregion
10
+ export { __decorate };
@@ -0,0 +1,252 @@
1
+ const require_errors = require('./errors.cjs');
2
+ const require_types = require('./types.cjs');
3
+ let _aigne_afs = require("@aigne/afs");
4
+
5
+ //#region src/client.ts
6
+ /**
7
+ * Synology HTTP Client
8
+ *
9
+ * Handles authentication, session management, and API dispatch.
10
+ * All Synology DSM 7 APIs go through /webapi/entry.cgi with
11
+ * api=SYNO.XXX&version=N&method=yyy parameters.
12
+ *
13
+ * Features:
14
+ * - Auto-login on first request
15
+ * - Session auto-renewal on error 105/106/107/119
16
+ * - Thundering herd protection (mutex via promise lock)
17
+ * - 2FA with device_id caching
18
+ * - API version discovery via SYNO.API.Info
19
+ */
20
+ /**
21
+ * Synology HTTP Client with session management and API discovery.
22
+ */
23
+ var SynologyClient = class {
24
+ config;
25
+ sid = null;
26
+ apiInfo = /* @__PURE__ */ new Map();
27
+ apiInfoLoaded = false;
28
+ apiInfoPromise = null;
29
+ reAuthPromise = null;
30
+ destroyed = false;
31
+ /** Cached container name -> ID mapping */
32
+ containerNameToId = /* @__PURE__ */ new Map();
33
+ constructor(config) {
34
+ this.config = config;
35
+ }
36
+ /**
37
+ * Make an authenticated API request.
38
+ * Handles auto-login and session renewal.
39
+ */
40
+ async request(params) {
41
+ if (this.destroyed) throw new _aigne_afs.AFSError("Client has been destroyed", "AFS_PROVIDER_ERROR");
42
+ await this.ensureApiInfo();
43
+ await this.ensureSession();
44
+ return this.doRequest(params, true);
45
+ }
46
+ /**
47
+ * Perform the actual API request with optional re-auth retry.
48
+ */
49
+ async doRequest(params, allowRetry) {
50
+ const { api, method, extra } = params;
51
+ const apiEntry = this.apiInfo.get(api);
52
+ const requestedVersion = params.version ?? (apiEntry ? apiEntry.maxVersion : 1);
53
+ const version = apiEntry ? Math.min(requestedVersion, apiEntry.maxVersion) : requestedVersion;
54
+ const body = new URLSearchParams();
55
+ body.set("api", api);
56
+ body.set("version", String(version));
57
+ body.set("method", method);
58
+ if (this.sid) body.set("_sid", this.sid);
59
+ if (extra) {
60
+ for (const [key, value] of Object.entries(extra)) if (value !== void 0 && value !== null) body.set(key, typeof value === "object" ? JSON.stringify(value) : String(value));
61
+ }
62
+ const url = `${this.config.url}${require_types.API_BASE_PATH}`;
63
+ let response;
64
+ try {
65
+ response = await fetch(url, {
66
+ method: "POST",
67
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
68
+ body: body.toString(),
69
+ tls: this.config.verifySsl === false ? { rejectUnauthorized: false } : void 0
70
+ });
71
+ } catch (error) {
72
+ throw new _aigne_afs.AFSError(`Connection failed: ${error.message}`, "AFS_PROVIDER_ERROR");
73
+ }
74
+ if (!response.ok) throw new _aigne_afs.AFSError(`HTTP error ${response.status}`, "AFS_PROVIDER_ERROR");
75
+ let json;
76
+ try {
77
+ json = await response.json();
78
+ } catch {
79
+ throw new _aigne_afs.AFSError("Malformed JSON response from Synology API", "AFS_PROVIDER_ERROR");
80
+ }
81
+ if (!json.success) {
82
+ const errorCode = json.error?.code ?? 0;
83
+ if (allowRetry && require_errors.SESSION_EXPIRED_CODES.has(errorCode)) {
84
+ await this.reAuth();
85
+ return this.doRequest(params, false);
86
+ }
87
+ throw require_errors.mapSynoError(errorCode, api);
88
+ }
89
+ return json.data;
90
+ }
91
+ /**
92
+ * Load API info from SYNO.API.Info query.
93
+ * Uses promise lock to prevent concurrent discovery.
94
+ */
95
+ async ensureApiInfo() {
96
+ if (this.apiInfoLoaded) return;
97
+ if (this.apiInfoPromise) {
98
+ await this.apiInfoPromise;
99
+ return;
100
+ }
101
+ this.apiInfoPromise = this.loadApiInfo().finally(() => {
102
+ this.apiInfoPromise = null;
103
+ });
104
+ await this.apiInfoPromise;
105
+ }
106
+ async loadApiInfo() {
107
+ if (this.apiInfoLoaded) return;
108
+ const url = `${this.config.url}${require_types.API_BASE_PATH}`;
109
+ const body = new URLSearchParams();
110
+ body.set("api", "SYNO.API.Info");
111
+ body.set("version", "1");
112
+ body.set("method", "query");
113
+ body.set("query", "all");
114
+ let response;
115
+ try {
116
+ response = await fetch(url, {
117
+ method: "POST",
118
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
119
+ body: body.toString(),
120
+ tls: this.config.verifySsl === false ? { rejectUnauthorized: false } : void 0
121
+ });
122
+ } catch (error) {
123
+ throw new _aigne_afs.AFSError(`Connection failed during API discovery: ${error.message}`, "AFS_PROVIDER_ERROR");
124
+ }
125
+ if (!response.ok) throw new _aigne_afs.AFSError(`API discovery failed: HTTP ${response.status}`, "AFS_PROVIDER_ERROR");
126
+ let json;
127
+ try {
128
+ json = await response.json();
129
+ } catch {
130
+ throw new _aigne_afs.AFSError("Malformed JSON during API discovery", "AFS_PROVIDER_ERROR");
131
+ }
132
+ if (json.success && json.data) for (const [apiName, info] of Object.entries(json.data)) this.apiInfo.set(apiName, info);
133
+ this.apiInfoLoaded = true;
134
+ }
135
+ /**
136
+ * Ensure we have a valid session. Login if needed.
137
+ */
138
+ async ensureSession() {
139
+ if (this.sid) return;
140
+ await this.login();
141
+ }
142
+ /**
143
+ * Re-authenticate (session expired). Uses promise lock for thundering herd protection.
144
+ * Only one re-auth fires at a time; concurrent callers await the same promise.
145
+ */
146
+ async reAuth() {
147
+ if (this.reAuthPromise) {
148
+ await this.reAuthPromise;
149
+ return;
150
+ }
151
+ this.sid = null;
152
+ this.reAuthPromise = this.login().finally(() => {
153
+ this.reAuthPromise = null;
154
+ });
155
+ await this.reAuthPromise;
156
+ }
157
+ /**
158
+ * Login to Synology DSM.
159
+ * Supports 2FA with OTP code and device_id caching.
160
+ */
161
+ async login() {
162
+ const url = `${this.config.url}${require_types.API_BASE_PATH}`;
163
+ const body = new URLSearchParams();
164
+ body.set("api", "SYNO.API.Auth");
165
+ body.set("version", "6");
166
+ body.set("method", "login");
167
+ body.set("account", this.config.account);
168
+ body.set("passwd", this.config.password);
169
+ body.set("format", "sid");
170
+ if (this.config.otpCode) body.set("otp_code", this.config.otpCode);
171
+ if (this.config.deviceId) body.set("device_id", this.config.deviceId);
172
+ let response;
173
+ try {
174
+ response = await fetch(url, {
175
+ method: "POST",
176
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
177
+ body: body.toString(),
178
+ tls: this.config.verifySsl === false ? { rejectUnauthorized: false } : void 0
179
+ });
180
+ } catch (error) {
181
+ throw new _aigne_afs.AFSError(`Login connection failed: ${error.message}`, "AFS_PROVIDER_ERROR");
182
+ }
183
+ if (!response.ok) throw new _aigne_afs.AFSError(`Login HTTP error: ${response.status}`, "AFS_PROVIDER_ERROR");
184
+ let json;
185
+ try {
186
+ json = await response.json();
187
+ } catch {
188
+ throw new _aigne_afs.AFSError("Malformed JSON during login", "AFS_PROVIDER_ERROR");
189
+ }
190
+ if (!json.success) throw require_errors.mapSynoError(json.error?.code ?? 0, "login");
191
+ if (!json.data?.sid) throw new _aigne_afs.AFSError("Login succeeded but no session ID returned", "AFS_PROVIDER_ERROR");
192
+ this.sid = json.data.sid;
193
+ if (json.data.device_id) this.config.deviceId = json.data.device_id;
194
+ }
195
+ /**
196
+ * Logout and invalidate session.
197
+ */
198
+ async logout() {
199
+ if (!this.sid) return;
200
+ try {
201
+ const url = `${this.config.url}${require_types.API_BASE_PATH}`;
202
+ const body = new URLSearchParams();
203
+ body.set("api", "SYNO.API.Auth");
204
+ body.set("version", "6");
205
+ body.set("method", "logout");
206
+ body.set("_sid", this.sid);
207
+ await fetch(url, {
208
+ method: "POST",
209
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
210
+ body: body.toString(),
211
+ tls: this.config.verifySsl === false ? { rejectUnauthorized: false } : void 0
212
+ });
213
+ } catch {} finally {
214
+ this.sid = null;
215
+ }
216
+ }
217
+ /**
218
+ * Mark client as destroyed. Idempotent.
219
+ */
220
+ async destroy() {
221
+ if (this.destroyed) return;
222
+ this.destroyed = true;
223
+ await this.logout();
224
+ }
225
+ /** Check if API is available */
226
+ isApiAvailable(apiName) {
227
+ return this.apiInfo.has(apiName);
228
+ }
229
+ /** Get the currently cached device ID */
230
+ getDeviceId() {
231
+ return this.config.deviceId;
232
+ }
233
+ /** Set device ID (for loading from persistent storage) */
234
+ setDeviceId(deviceId) {
235
+ this.config.deviceId = deviceId;
236
+ }
237
+ /** Invalidate cached data to force refresh */
238
+ invalidateCache() {
239
+ this.containerNameToId.clear();
240
+ }
241
+ /** Get container name-to-ID mapping */
242
+ getContainerNameToId() {
243
+ return this.containerNameToId;
244
+ }
245
+ /** Update container name-to-ID mapping */
246
+ setContainerNameToId(mapping) {
247
+ this.containerNameToId = mapping;
248
+ }
249
+ };
250
+
251
+ //#endregion
252
+ exports.SynologyClient = SynologyClient;
@@ -0,0 +1,96 @@
1
+ //#region src/client.d.ts
2
+ /**
3
+ * Synology HTTP Client
4
+ *
5
+ * Handles authentication, session management, and API dispatch.
6
+ * All Synology DSM 7 APIs go through /webapi/entry.cgi with
7
+ * api=SYNO.XXX&version=N&method=yyy parameters.
8
+ *
9
+ * Features:
10
+ * - Auto-login on first request
11
+ * - Session auto-renewal on error 105/106/107/119
12
+ * - Thundering herd protection (mutex via promise lock)
13
+ * - 2FA with device_id caching
14
+ * - API version discovery via SYNO.API.Info
15
+ */
16
+ interface SynoClientConfig {
17
+ url: string;
18
+ account: string;
19
+ password: string;
20
+ otpCode?: string;
21
+ deviceId?: string;
22
+ verifySsl?: boolean;
23
+ }
24
+ interface SynoRequestParams {
25
+ api: string;
26
+ version?: number;
27
+ method: string;
28
+ extra?: Record<string, unknown>;
29
+ }
30
+ /**
31
+ * Synology HTTP Client with session management and API discovery.
32
+ */
33
+ declare class SynologyClient {
34
+ private config;
35
+ private sid;
36
+ private apiInfo;
37
+ private apiInfoLoaded;
38
+ private apiInfoPromise;
39
+ private reAuthPromise;
40
+ private destroyed;
41
+ /** Cached container name -> ID mapping */
42
+ private containerNameToId;
43
+ constructor(config: SynoClientConfig);
44
+ /**
45
+ * Make an authenticated API request.
46
+ * Handles auto-login and session renewal.
47
+ */
48
+ request<T = unknown>(params: SynoRequestParams): Promise<T>;
49
+ /**
50
+ * Perform the actual API request with optional re-auth retry.
51
+ */
52
+ private doRequest;
53
+ /**
54
+ * Load API info from SYNO.API.Info query.
55
+ * Uses promise lock to prevent concurrent discovery.
56
+ */
57
+ private ensureApiInfo;
58
+ private loadApiInfo;
59
+ /**
60
+ * Ensure we have a valid session. Login if needed.
61
+ */
62
+ private ensureSession;
63
+ /**
64
+ * Re-authenticate (session expired). Uses promise lock for thundering herd protection.
65
+ * Only one re-auth fires at a time; concurrent callers await the same promise.
66
+ */
67
+ private reAuth;
68
+ /**
69
+ * Login to Synology DSM.
70
+ * Supports 2FA with OTP code and device_id caching.
71
+ */
72
+ private login;
73
+ /**
74
+ * Logout and invalidate session.
75
+ */
76
+ logout(): Promise<void>;
77
+ /**
78
+ * Mark client as destroyed. Idempotent.
79
+ */
80
+ destroy(): Promise<void>;
81
+ /** Check if API is available */
82
+ isApiAvailable(apiName: string): boolean;
83
+ /** Get the currently cached device ID */
84
+ getDeviceId(): string | undefined;
85
+ /** Set device ID (for loading from persistent storage) */
86
+ setDeviceId(deviceId: string): void;
87
+ /** Invalidate cached data to force refresh */
88
+ invalidateCache(): void;
89
+ /** Get container name-to-ID mapping */
90
+ getContainerNameToId(): Map<string, string>;
91
+ /** Update container name-to-ID mapping */
92
+ setContainerNameToId(mapping: Map<string, string>): void;
93
+ }
94
+ //#endregion
95
+ export { SynologyClient };
96
+ //# sourceMappingURL=client.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.cts","names":[],"sources":["../src/client.ts"],"mappings":";;AAmBA;;;;;;;;;;;;AASA;UATiB,gBAAA;EACf,GAAA;EACA,OAAA;EACA,QAAA;EACA,OAAA;EACA,QAAA;EACA,SAAA;AAAA;AAAA,UAGe,iBAAA;EACf,GAAA;EACA,OAAA;EACA,MAAA;EACA,KAAA,GAAQ,MAAA;AAAA;;;;cAMG,cAAA;EAAA,QACH,MAAA;EAAA,QACA,GAAA;EAAA,QACA,OAAA;EAAA,QACA,aAAA;EAAA,QACA,cAAA;EAAA,QACA,aAAA;EAAA,QACA,SAAA;EAJA;EAAA,QAMA,iBAAA;cAEI,MAAA,EAAQ,gBAAA;EALZ;;;;EAaF,OAAA,aAAA,CAAqB,MAAA,EAAQ,iBAAA,GAAoB,OAAA,CAAQ,CAAA;EARnD;;;EAAA,QA0BE,SAAA;EAlBa;;;;EAAA,QAyFb,aAAA;EAAA,QAgBA,WAAA;EAgDA;;;EAAA,QAAA,aAAA;EA0FE;;;;EAAA,QAjFF,MAAA;EAwHd;;;;EAAA,QAtGc,KAAA;EAqHU;;;EAtDlB,MAAA,CAAA,GAAU,OAAA;EA2DiC;;;EAhC3C,OAAA,CAAA,GAAW,OAAA;;EAOjB,cAAA,CAAe,OAAA;;EAKf,WAAA,CAAA;;EAKA,WAAA,CAAY,QAAA;;EAKZ,eAAA,CAAA;;EAKA,oBAAA,CAAA,GAAwB,GAAA;;EAKxB,oBAAA,CAAqB,OAAA,EAAS,GAAA;AAAA"}
@@ -0,0 +1,96 @@
1
+ //#region src/client.d.ts
2
+ /**
3
+ * Synology HTTP Client
4
+ *
5
+ * Handles authentication, session management, and API dispatch.
6
+ * All Synology DSM 7 APIs go through /webapi/entry.cgi with
7
+ * api=SYNO.XXX&version=N&method=yyy parameters.
8
+ *
9
+ * Features:
10
+ * - Auto-login on first request
11
+ * - Session auto-renewal on error 105/106/107/119
12
+ * - Thundering herd protection (mutex via promise lock)
13
+ * - 2FA with device_id caching
14
+ * - API version discovery via SYNO.API.Info
15
+ */
16
+ interface SynoClientConfig {
17
+ url: string;
18
+ account: string;
19
+ password: string;
20
+ otpCode?: string;
21
+ deviceId?: string;
22
+ verifySsl?: boolean;
23
+ }
24
+ interface SynoRequestParams {
25
+ api: string;
26
+ version?: number;
27
+ method: string;
28
+ extra?: Record<string, unknown>;
29
+ }
30
+ /**
31
+ * Synology HTTP Client with session management and API discovery.
32
+ */
33
+ declare class SynologyClient {
34
+ private config;
35
+ private sid;
36
+ private apiInfo;
37
+ private apiInfoLoaded;
38
+ private apiInfoPromise;
39
+ private reAuthPromise;
40
+ private destroyed;
41
+ /** Cached container name -> ID mapping */
42
+ private containerNameToId;
43
+ constructor(config: SynoClientConfig);
44
+ /**
45
+ * Make an authenticated API request.
46
+ * Handles auto-login and session renewal.
47
+ */
48
+ request<T = unknown>(params: SynoRequestParams): Promise<T>;
49
+ /**
50
+ * Perform the actual API request with optional re-auth retry.
51
+ */
52
+ private doRequest;
53
+ /**
54
+ * Load API info from SYNO.API.Info query.
55
+ * Uses promise lock to prevent concurrent discovery.
56
+ */
57
+ private ensureApiInfo;
58
+ private loadApiInfo;
59
+ /**
60
+ * Ensure we have a valid session. Login if needed.
61
+ */
62
+ private ensureSession;
63
+ /**
64
+ * Re-authenticate (session expired). Uses promise lock for thundering herd protection.
65
+ * Only one re-auth fires at a time; concurrent callers await the same promise.
66
+ */
67
+ private reAuth;
68
+ /**
69
+ * Login to Synology DSM.
70
+ * Supports 2FA with OTP code and device_id caching.
71
+ */
72
+ private login;
73
+ /**
74
+ * Logout and invalidate session.
75
+ */
76
+ logout(): Promise<void>;
77
+ /**
78
+ * Mark client as destroyed. Idempotent.
79
+ */
80
+ destroy(): Promise<void>;
81
+ /** Check if API is available */
82
+ isApiAvailable(apiName: string): boolean;
83
+ /** Get the currently cached device ID */
84
+ getDeviceId(): string | undefined;
85
+ /** Set device ID (for loading from persistent storage) */
86
+ setDeviceId(deviceId: string): void;
87
+ /** Invalidate cached data to force refresh */
88
+ invalidateCache(): void;
89
+ /** Get container name-to-ID mapping */
90
+ getContainerNameToId(): Map<string, string>;
91
+ /** Update container name-to-ID mapping */
92
+ setContainerNameToId(mapping: Map<string, string>): void;
93
+ }
94
+ //#endregion
95
+ export { SynologyClient };
96
+ //# sourceMappingURL=client.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":";;AAmBA;;;;;;;;;;;;AASA;UATiB,gBAAA;EACf,GAAA;EACA,OAAA;EACA,QAAA;EACA,OAAA;EACA,QAAA;EACA,SAAA;AAAA;AAAA,UAGe,iBAAA;EACf,GAAA;EACA,OAAA;EACA,MAAA;EACA,KAAA,GAAQ,MAAA;AAAA;;;;cAMG,cAAA;EAAA,QACH,MAAA;EAAA,QACA,GAAA;EAAA,QACA,OAAA;EAAA,QACA,aAAA;EAAA,QACA,cAAA;EAAA,QACA,aAAA;EAAA,QACA,SAAA;EAJA;EAAA,QAMA,iBAAA;cAEI,MAAA,EAAQ,gBAAA;EALZ;;;;EAaF,OAAA,aAAA,CAAqB,MAAA,EAAQ,iBAAA,GAAoB,OAAA,CAAQ,CAAA;EARnD;;;EAAA,QA0BE,SAAA;EAlBa;;;;EAAA,QAyFb,aAAA;EAAA,QAgBA,WAAA;EAgDA;;;EAAA,QAAA,aAAA;EA0FE;;;;EAAA,QAjFF,MAAA;EAwHd;;;;EAAA,QAtGc,KAAA;EAqHU;;;EAtDlB,MAAA,CAAA,GAAU,OAAA;EA2DiC;;;EAhC3C,OAAA,CAAA,GAAW,OAAA;;EAOjB,cAAA,CAAe,OAAA;;EAKf,WAAA,CAAA;;EAKA,WAAA,CAAY,QAAA;;EAKZ,eAAA,CAAA;;EAKA,oBAAA,CAAA,GAAwB,GAAA;;EAKxB,oBAAA,CAAqB,OAAA,EAAS,GAAA;AAAA"}