@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 +26 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/client.cjs +252 -0
- package/dist/client.d.cts +96 -0
- package/dist/client.d.cts.map +1 -0
- package/dist/client.d.mts +96 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +253 -0
- package/dist/client.mjs.map +1 -0
- package/dist/errors.cjs +37 -0
- package/dist/errors.d.cts +10 -0
- package/dist/errors.d.cts.map +1 -0
- package/dist/errors.d.mts +10 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/errors.mjs +37 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/formatters.cjs +139 -0
- package/dist/formatters.mjs +128 -0
- package/dist/formatters.mjs.map +1 -0
- package/dist/index.cjs +15 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +6 -0
- package/dist/synology-afs.cjs +1686 -0
- package/dist/synology-afs.d.cts +232 -0
- package/dist/synology-afs.d.cts.map +1 -0
- package/dist/synology-afs.d.mts +232 -0
- package/dist/synology-afs.d.mts.map +1 -0
- package/dist/synology-afs.mjs +1687 -0
- package/dist/synology-afs.mjs.map +1 -0
- package/dist/types.cjs +44 -0
- package/dist/types.d.cts +174 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +174 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +42 -0
- package/dist/types.mjs.map +1 -0
- package/package.json +57 -0
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 };
|
package/dist/client.cjs
ADDED
|
@@ -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"}
|