itd-api 0.9.1 → 0.9.3
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/dist/events/index.cjs +3 -4
- package/dist/events/index.d.cts +3 -3
- package/dist/events/index.d.ts +3 -3
- package/dist/events/index.js +4 -4
- package/dist/index.cjs +283 -90
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -72
- package/dist/index.d.ts +16 -72
- package/dist/index.js +284 -90
- package/dist/index.js.map +1 -1
- package/dist/node/index.cjs +3 -3
- package/dist/node/index.js +3 -3
- package/dist/rest/index.cjs +4 -5
- package/dist/rest/index.d.cts +3 -3
- package/dist/rest/index.d.ts +3 -3
- package/dist/rest/index.js +5 -5
- package/dist/shared/{events-BDdJyH2p.d.ts → events-BPxBF0gQ.d.cts} +3 -5
- package/dist/shared/{events-D6y0Hxyz.js → events-BzjI6pn5.js} +4 -4
- package/dist/shared/{events-D6y0Hxyz.js.map → events-BzjI6pn5.js.map} +1 -1
- package/dist/shared/{events-BqFkwwTw.cjs → events-C1FudmSe.cjs} +3 -9
- package/dist/shared/{events-BqFkwwTw.cjs.map → events-C1FudmSe.cjs.map} +1 -1
- package/dist/shared/{events-BQyH5-S0.d.cts → events-DnwfcLxS.d.ts} +3 -5
- package/dist/shared/{multi-storage-SoW05uzO.js → multi-storage-C3Ngtop1.js} +13 -8
- package/dist/shared/multi-storage-C3Ngtop1.js.map +1 -0
- package/dist/shared/{multi-storage-CDKLa956.cjs → multi-storage-s-fPtjoy.cjs} +13 -8
- package/dist/shared/multi-storage-s-fPtjoy.cjs.map +1 -0
- package/dist/shared/{options-DOJYtoti.cjs → options-DjEtbVaf.cjs} +2 -2
- package/dist/shared/{options-DOJYtoti.cjs.map → options-DjEtbVaf.cjs.map} +1 -1
- package/dist/shared/{options-DhgGg7Ms.js → options-i4LrKfqx.js} +2 -2
- package/dist/shared/{options-DhgGg7Ms.js.map → options-i4LrKfqx.js.map} +1 -1
- package/dist/shared/{redact-Ba5iSVYZ.js → redact-DJV0XM3n.js} +40 -15
- package/dist/shared/redact-DJV0XM3n.js.map +1 -0
- package/dist/shared/{redact-BP00URmQ.cjs → redact-lAmck1Z6.cjs} +45 -14
- package/dist/shared/redact-lAmck1Z6.cjs.map +1 -0
- package/dist/shared/{render-BU1_wex8.d.cts → render-C1F_QWYD.d.cts} +82 -19
- package/dist/shared/{render-sb1VJhUA.js → render-CtOpp9Pm.js} +127 -124
- package/dist/shared/render-CtOpp9Pm.js.map +1 -0
- package/dist/shared/{render-DAEeCMk9.cjs → render-Omy1drH1.cjs} +125 -128
- package/dist/shared/render-Omy1drH1.cjs.map +1 -0
- package/dist/shared/{render-CZ_-F2Tl.d.ts → render-uFW2kDxp.d.ts} +82 -19
- package/dist/shared/{storage-QNVxzpDB.cjs → storage-CZJZoaz9.cjs} +4 -2
- package/dist/shared/storage-CZJZoaz9.cjs.map +1 -0
- package/dist/shared/{storage-CfHKzVHf.js → storage-DiDe0PZM.js} +4 -2
- package/dist/shared/storage-DiDe0PZM.js.map +1 -0
- package/dist/shared/{url-Dsr4c5r5.js → url-3Ts6EEHQ.js} +301 -96
- package/dist/shared/url-3Ts6EEHQ.js.map +1 -0
- package/dist/shared/{url-CzHqE-Fg.cjs → url-D6wsXm1T.cjs} +301 -96
- package/dist/shared/url-D6wsXm1T.cjs.map +1 -0
- package/dist/shared/{url-CZZcyPI0.d.cts → url-DubjkRR9.d.cts} +10 -16
- package/dist/shared/{url-CZZcyPI0.d.ts → url-DubjkRR9.d.ts} +10 -16
- package/dist/web/index.cjs +1 -1
- package/dist/web/index.js +1 -1
- package/package.json +5 -5
- package/dist/shared/multi-storage-CDKLa956.cjs.map +0 -1
- package/dist/shared/multi-storage-SoW05uzO.js.map +0 -1
- package/dist/shared/redact-BP00URmQ.cjs.map +0 -1
- package/dist/shared/redact-Ba5iSVYZ.js.map +0 -1
- package/dist/shared/render-DAEeCMk9.cjs.map +0 -1
- package/dist/shared/render-sb1VJhUA.js.map +0 -1
- package/dist/shared/storage-CfHKzVHf.js.map +0 -1
- package/dist/shared/storage-QNVxzpDB.cjs.map +0 -1
- package/dist/shared/url-CzHqE-Fg.cjs.map +0 -1
- package/dist/shared/url-Dsr4c5r5.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_url = require("./shared/url-
|
|
2
|
+
const require_url = require("./shared/url-D6wsXm1T.cjs");
|
|
3
3
|
const require_errors = require("./shared/errors-BmP3TKoW.cjs");
|
|
4
|
-
const require_redact = require("./shared/redact-
|
|
5
|
-
const require_render = require("./shared/render-
|
|
6
|
-
const require_options = require("./shared/options-
|
|
7
|
-
const require_events = require("./shared/events-
|
|
4
|
+
const require_redact = require("./shared/redact-lAmck1Z6.cjs");
|
|
5
|
+
const require_render = require("./shared/render-Omy1drH1.cjs");
|
|
6
|
+
const require_options = require("./shared/options-DjEtbVaf.cjs");
|
|
7
|
+
const require_events = require("./shared/events-C1FudmSe.cjs");
|
|
8
8
|
const require_key_value_store = require("./shared/key-value-store-B5GYVEYZ.cjs");
|
|
9
|
-
const require_storage = require("./shared/storage-
|
|
10
|
-
const require_multi_storage = require("./shared/multi-storage-
|
|
9
|
+
const require_storage = require("./shared/storage-CZJZoaz9.cjs");
|
|
10
|
+
const require_multi_storage = require("./shared/multi-storage-s-fPtjoy.cjs");
|
|
11
11
|
//#region src/notifications-api.ts
|
|
12
12
|
var ClientNotifications = class extends require_render.NotificationsResource {
|
|
13
13
|
#createEvents;
|
|
@@ -50,7 +50,7 @@ const AUTH_SIGN_IN = require_url.defineBuiltInOperation("auth.signIn", (body) =>
|
|
|
50
50
|
};
|
|
51
51
|
});
|
|
52
52
|
const AUTH_VERIFY_OTP = require_url.defineBuiltInOperation("auth.verifyOtp", (body) => requireString(body, "accessToken", "Сервер не вернул accessToken после подтверждения кода"));
|
|
53
|
-
const AUTH_REFRESH = require_url.defineBuiltInOperation("auth.refresh", (body) =>
|
|
53
|
+
const AUTH_REFRESH = require_url.defineBuiltInOperation("auth.refresh", (body) => requireString(body, "accessToken", "Сервер не вернул accessToken при обновлении сессии"));
|
|
54
54
|
const AUTH_RESEND_OTP = require_url.voidOperation("auth.resendOtp");
|
|
55
55
|
const AUTH_LOGOUT = require_url.voidOperation("auth.logout");
|
|
56
56
|
const AUTH_FORGOT_PASSWORD = require_url.defineBuiltInOperation("auth.forgotPassword", (body) => requireString(body, "flowToken", "Сервер не вернул flowToken при запросе сброса пароля"));
|
|
@@ -75,38 +75,34 @@ function decodeBase64Url(segment) {
|
|
|
75
75
|
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
76
76
|
return new TextDecoder().decode(bytes);
|
|
77
77
|
}
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
*
|
|
81
|
-
* Подпись не проверяется — ключа для проверки у клиента нет. Прочитанные значения служат
|
|
82
|
-
* лишь метками для разделения локального состояния и не дают доступа: его определяет сервер.
|
|
83
|
-
*/
|
|
84
|
-
function readTokenIdentity(token) {
|
|
78
|
+
/** Читает полезную нагрузку JWT без проверки подписи. */
|
|
79
|
+
function readTokenPayload(token) {
|
|
85
80
|
try {
|
|
86
81
|
const payload = token.split(".")[1];
|
|
87
|
-
if (!payload) return
|
|
82
|
+
if (!payload) return void 0;
|
|
88
83
|
const parsed = JSON.parse(decodeBase64Url(payload));
|
|
89
|
-
if (typeof parsed !== "object" || parsed === null) return
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
...typeof sub === "string" && sub.length > 0 ? { subject: sub } : {},
|
|
93
|
-
...typeof sid === "string" && sid.length > 0 ? { sessionId: sid } : {}
|
|
94
|
-
};
|
|
84
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
|
|
85
|
+
return parsed;
|
|
95
86
|
} catch {
|
|
96
|
-
return
|
|
87
|
+
return;
|
|
97
88
|
}
|
|
98
89
|
}
|
|
99
|
-
/**
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
90
|
+
/** Читает используемые клиентом поля JWT за один раз. */
|
|
91
|
+
function readTokenMetadata(token) {
|
|
92
|
+
const payload = readTokenPayload(token);
|
|
93
|
+
if (!payload) return {};
|
|
94
|
+
const { sub, sid, exp } = payload;
|
|
95
|
+
const expiresAt = typeof exp === "number" && Number.isFinite(exp) ? exp * 1e3 : void 0;
|
|
96
|
+
return {
|
|
97
|
+
...typeof sub === "string" && sub.length > 0 ? { subject: sub } : {},
|
|
98
|
+
...typeof sid === "string" && sid.length > 0 ? { sessionId: sid } : {},
|
|
99
|
+
...expiresAt !== void 0 && Number.isFinite(expiresAt) ? { expiresAt } : {}
|
|
100
|
+
};
|
|
107
101
|
}
|
|
108
102
|
//#endregion
|
|
109
103
|
//#region src/session/auth.ts
|
|
104
|
+
/** Запас до истечения токена на очередь и выполнение запроса. */
|
|
105
|
+
const ACCESS_TOKEN_REFRESH_LEEWAY_MS = 3e4;
|
|
110
106
|
/**
|
|
111
107
|
* Проверяет форму `auth` и сообщает о типичных ошибках понятным текстом.
|
|
112
108
|
*
|
|
@@ -120,12 +116,18 @@ function validateAuth(auth) {
|
|
|
120
116
|
return auth;
|
|
121
117
|
}
|
|
122
118
|
if (typeof auth !== "object" || auth === null) throw new require_errors.ItdConfigError(`auth должен быть строкой с токеном или объектом, получено: ${typeof auth}`);
|
|
119
|
+
if ([
|
|
120
|
+
"getToken" in auth,
|
|
121
|
+
"accessToken" in auth || "refreshToken" in auth,
|
|
122
|
+
"email" in auth || "password" in auth
|
|
123
|
+
].filter(Boolean).length > 1) throw new require_errors.ItdConfigError("auth должен описывать ровно один способ авторизации: getToken, accessToken или email/password");
|
|
123
124
|
if ("getToken" in auth) {
|
|
124
125
|
if (typeof auth.getToken !== "function") throw new require_errors.ItdConfigError("auth.getToken должен быть функцией");
|
|
125
126
|
return { ...auth };
|
|
126
127
|
}
|
|
127
128
|
if ("accessToken" in auth) {
|
|
128
129
|
if (typeof auth.accessToken !== "string" || auth.accessToken.trim() === "") throw new require_errors.ItdConfigError("auth.accessToken должен быть непустой строкой");
|
|
130
|
+
if (auth.refreshToken !== void 0 && (typeof auth.refreshToken !== "string" || auth.refreshToken.trim() === "")) throw new require_errors.ItdConfigError("auth.refreshToken должен быть непустой строкой");
|
|
129
131
|
return { ...auth };
|
|
130
132
|
}
|
|
131
133
|
if ("email" in auth || "password" in auth) {
|
|
@@ -213,10 +215,67 @@ const AUTH_PATHS = {
|
|
|
213
215
|
const TURNSTILE_SITE_KEY = "0x4AAAAAACHhxczw6fJGwPBg";
|
|
214
216
|
/** Убирает поле, которое сохраняли промежуточные версии с поддержкой `getUserId()`. */
|
|
215
217
|
function withoutLegacyUserId(session) {
|
|
216
|
-
const clean =
|
|
218
|
+
const clean = require_storage.copySession(session);
|
|
217
219
|
delete clean.userId;
|
|
218
220
|
return clean;
|
|
219
221
|
}
|
|
222
|
+
function normalizeSessionValue(value, source, mode = "strict", logger) {
|
|
223
|
+
const tolerant = mode === "stored";
|
|
224
|
+
if (!require_url.isRecord$1(value)) {
|
|
225
|
+
if (tolerant) {
|
|
226
|
+
logger?.warn(`${source} вернула не объект сессии; сохранённая запись пропущена`);
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
throw new require_errors.ItdConfigError(`${source} должна быть объектом сессии`);
|
|
230
|
+
}
|
|
231
|
+
const record = value;
|
|
232
|
+
const normalized = { ...record };
|
|
233
|
+
delete normalized.userId;
|
|
234
|
+
const normalizeString = (field) => {
|
|
235
|
+
const item = record[field];
|
|
236
|
+
if (item === void 0) {
|
|
237
|
+
delete normalized[field];
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (item !== void 0 && (typeof item !== "string" || item.trim() === "")) {
|
|
241
|
+
if (tolerant) {
|
|
242
|
+
logger?.warn(`${source}.${field} повреждена; поле сохранённой сессии пропущено`);
|
|
243
|
+
delete normalized[field];
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
throw new require_errors.ItdConfigError(`${source}.${field} должна быть непустой строкой`);
|
|
247
|
+
}
|
|
248
|
+
normalized[field] = item;
|
|
249
|
+
};
|
|
250
|
+
normalizeString("accessToken");
|
|
251
|
+
normalizeString("refreshToken");
|
|
252
|
+
normalizeString("deviceId");
|
|
253
|
+
if (record.obtainedAt === void 0) delete normalized.obtainedAt;
|
|
254
|
+
else if (typeof record.obtainedAt !== "number" || !Number.isFinite(record.obtainedAt)) {
|
|
255
|
+
if (!tolerant) throw new require_errors.ItdConfigError(`${source}.obtainedAt должна быть конечным числом`);
|
|
256
|
+
else {
|
|
257
|
+
logger?.warn(`${source}.obtainedAt повреждена; поле сохранённой сессии пропущено`);
|
|
258
|
+
delete normalized.obtainedAt;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (record.cookies === void 0) delete normalized.cookies;
|
|
262
|
+
else if (!Array.isArray(record.cookies)) {
|
|
263
|
+
if (tolerant) {
|
|
264
|
+
logger?.warn(`${source}.cookies повреждена; поле сохранённой сессии пропущено`);
|
|
265
|
+
delete normalized.cookies;
|
|
266
|
+
} else throw new require_errors.ItdConfigError(`${source}.cookies должна быть массивом`);
|
|
267
|
+
} else {
|
|
268
|
+
const valid = record.cookies.filter((cookie) => typeof cookie === "string" && require_redact.isSerializedCookieEntry(cookie));
|
|
269
|
+
if (valid.length > 0) normalized.cookies = valid;
|
|
270
|
+
else delete normalized.cookies;
|
|
271
|
+
}
|
|
272
|
+
return normalized;
|
|
273
|
+
}
|
|
274
|
+
function normalizeSession(value, source) {
|
|
275
|
+
const normalized = normalizeSessionValue(value, source);
|
|
276
|
+
if (!normalized) throw new require_errors.ItdConfigError(`${source} должна быть объектом сессии`);
|
|
277
|
+
return normalized;
|
|
278
|
+
}
|
|
220
279
|
let authScopeSequence = 0;
|
|
221
280
|
function nextAuthScope() {
|
|
222
281
|
authScopeSequence += 1;
|
|
@@ -243,7 +302,7 @@ var AuthManager = class {
|
|
|
243
302
|
* старте читают хранилище один раз и не заводят каждый свой `deviceId`.
|
|
244
303
|
*/
|
|
245
304
|
#loading = null;
|
|
246
|
-
/**
|
|
305
|
+
/** Текущее обновление: к нему присоединяются запросы той же ревизии. */
|
|
247
306
|
#refreshing = null;
|
|
248
307
|
/** Общий промис входа по логину и паролю. */
|
|
249
308
|
#signingIn = null;
|
|
@@ -253,6 +312,7 @@ var AuthManager = class {
|
|
|
253
312
|
#externalToken;
|
|
254
313
|
/** Идентификаторы последнего токена внешнего источника для синхронных потребителей. */
|
|
255
314
|
#externalIdentity;
|
|
315
|
+
#tokenMetadata;
|
|
256
316
|
/**
|
|
257
317
|
* Идентификатор устройства.
|
|
258
318
|
*
|
|
@@ -266,11 +326,12 @@ var AuthManager = class {
|
|
|
266
326
|
* Счётчик смен владельца авторизации.
|
|
267
327
|
*
|
|
268
328
|
* Растёт при каждой операции, которая заменяет или очищает сессию извне: `clear`,
|
|
269
|
-
* `setSession`, `setAccessToken`, вход.
|
|
270
|
-
* сетевым запросом и сверяет перед записью — иначе запоздавший ответ обновления мог бы
|
|
271
|
-
* воскресить уже очищенную сессию поверх `signOut`.
|
|
329
|
+
* `setSession`, `setAccessToken`, вход. Refresh сверяет снимок до и после сетевого запроса.
|
|
272
330
|
*/
|
|
273
331
|
#authEpoch = 0;
|
|
332
|
+
#externalReadGeneration = 0;
|
|
333
|
+
#disposed = false;
|
|
334
|
+
#persistence = Promise.resolve();
|
|
274
335
|
constructor(config, http, jar, hooks = {}) {
|
|
275
336
|
this.#config = config;
|
|
276
337
|
this.#http = http;
|
|
@@ -280,15 +341,25 @@ var AuthManager = class {
|
|
|
280
341
|
}
|
|
281
342
|
/** Подписка на события авторизации. */
|
|
282
343
|
get on() {
|
|
283
|
-
return
|
|
344
|
+
return ((...args) => {
|
|
345
|
+
this.#assertActive();
|
|
346
|
+
return this.#emitter.on(...args);
|
|
347
|
+
});
|
|
284
348
|
}
|
|
285
349
|
/** Подписка на одно срабатывание. */
|
|
286
350
|
get once() {
|
|
287
|
-
return
|
|
351
|
+
return ((...args) => {
|
|
352
|
+
this.#assertActive();
|
|
353
|
+
return this.#emitter.once(...args);
|
|
354
|
+
});
|
|
288
355
|
}
|
|
289
356
|
/** Снимает служебные подписки при окончательном освобождении владельца. @internal */
|
|
290
357
|
dispose() {
|
|
358
|
+
if (this.#disposed) return;
|
|
359
|
+
this.#disposed = true;
|
|
360
|
+
this.#externalReadGeneration += 1;
|
|
291
361
|
this.#invalidateInFlight();
|
|
362
|
+
this.#tokenMetadata = void 0;
|
|
292
363
|
this.#emitter.removeAllListeners();
|
|
293
364
|
}
|
|
294
365
|
/**
|
|
@@ -302,6 +373,7 @@ var AuthManager = class {
|
|
|
302
373
|
}
|
|
303
374
|
/** Загружает сессию и возвращает идентификаторы аккаунта и сессии для плагинов. */
|
|
304
375
|
async getAuthIdentity() {
|
|
376
|
+
this.#assertActive();
|
|
305
377
|
const session = await this.#loadSession();
|
|
306
378
|
if (session?.accessToken) return this.#identityForToken(session.accessToken);
|
|
307
379
|
const auth = this.#config.auth;
|
|
@@ -322,16 +394,57 @@ var AuthManager = class {
|
|
|
322
394
|
/** Отмечает смену владельца авторизации — обесценивает результат идущего обновления. */
|
|
323
395
|
#invalidateInFlight() {
|
|
324
396
|
this.#authEpoch += 1;
|
|
397
|
+
this.#externalReadGeneration += 1;
|
|
398
|
+
}
|
|
399
|
+
/** Ревизия владельца сессии для условного commit внешнего auth-flow. @internal */
|
|
400
|
+
revision() {
|
|
401
|
+
this.#assertActive();
|
|
402
|
+
return this.#authEpoch;
|
|
403
|
+
}
|
|
404
|
+
/** Создаёт изолированный jar для сетевого auth-flow. @internal */
|
|
405
|
+
createCookieFlow(inherit = true) {
|
|
406
|
+
this.#assertActive();
|
|
407
|
+
return inherit ? this.#jar.clone() : new require_redact.CookieJar();
|
|
408
|
+
}
|
|
409
|
+
/** Применяет cookies только если владелец сессии не сменился. @internal */
|
|
410
|
+
commitCookieFlow(cookies, expectedRevision) {
|
|
411
|
+
this.#assertActive();
|
|
412
|
+
if (this.#authEpoch !== expectedRevision) return false;
|
|
413
|
+
this.#jar.replaceWith(cookies);
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
#assertActive() {
|
|
417
|
+
if (this.#disposed) throw new require_errors.ItdStateError("Менеджер авторизации уже освобождён");
|
|
418
|
+
}
|
|
419
|
+
#currentAccessToken() {
|
|
420
|
+
return this.#session?.accessToken ?? null;
|
|
325
421
|
}
|
|
326
422
|
#identityForToken(accessToken) {
|
|
327
|
-
const token =
|
|
423
|
+
const token = this.#metadataForToken(accessToken);
|
|
328
424
|
return {
|
|
329
425
|
...token.subject ? { userId: token.subject } : {},
|
|
330
426
|
...token.sessionId ? { sessionId: token.sessionId } : {}
|
|
331
427
|
};
|
|
332
428
|
}
|
|
429
|
+
#metadataForToken(accessToken) {
|
|
430
|
+
if (!accessToken) {
|
|
431
|
+
this.#tokenMetadata = void 0;
|
|
432
|
+
return {};
|
|
433
|
+
}
|
|
434
|
+
if (this.#tokenMetadata?.accessToken === accessToken) return this.#tokenMetadata.value;
|
|
435
|
+
const value = readTokenMetadata(accessToken);
|
|
436
|
+
this.#tokenMetadata = {
|
|
437
|
+
accessToken,
|
|
438
|
+
value
|
|
439
|
+
};
|
|
440
|
+
return value;
|
|
441
|
+
}
|
|
333
442
|
async #readExternalToken(getToken) {
|
|
443
|
+
this.#assertActive();
|
|
444
|
+
const generation = ++this.#externalReadGeneration;
|
|
334
445
|
const token = await getToken() ?? null;
|
|
446
|
+
this.#assertActive();
|
|
447
|
+
if (generation !== this.#externalReadGeneration) return token;
|
|
335
448
|
const current = this.#identityForToken(token ?? void 0);
|
|
336
449
|
if (this.#externalToken !== void 0 && this.#externalToken !== token) {
|
|
337
450
|
this.#rotateAuthScope();
|
|
@@ -362,6 +475,7 @@ var AuthManager = class {
|
|
|
362
475
|
* ответ был бы `false` даже при полностью рабочей сохранённой сессии.
|
|
363
476
|
*/
|
|
364
477
|
async hasRefreshSession() {
|
|
478
|
+
this.#assertActive();
|
|
365
479
|
await this.#loadSession();
|
|
366
480
|
return this.#hasRefreshSession();
|
|
367
481
|
}
|
|
@@ -371,14 +485,33 @@ var AuthManager = class {
|
|
|
371
485
|
if (this.#jar.has("is_auth", this.#config.baseUrl)) return true;
|
|
372
486
|
return Boolean(this.#session?.refreshToken);
|
|
373
487
|
}
|
|
374
|
-
/**
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
488
|
+
/** Готовит внутреннюю сессию до retry исходного запроса. */
|
|
489
|
+
async preflight(allowRefresh) {
|
|
490
|
+
this.#assertActive();
|
|
491
|
+
let accessToken = (await this.#loadSession())?.accessToken;
|
|
492
|
+
if (!accessToken) {
|
|
493
|
+
const auth = this.#config.auth;
|
|
494
|
+
if (typeof auth === "object" && auth !== null && "email" in auth) accessToken = await this.#signInWithCredentials(auth) ?? void 0;
|
|
495
|
+
}
|
|
496
|
+
if (!accessToken || !allowRefresh || !this.#config.autoRefresh) return;
|
|
497
|
+
const expiresAt = this.#metadataForToken(accessToken).expiresAt;
|
|
498
|
+
if (expiresAt === void 0 || expiresAt > this.#config.clock.now() + ACCESS_TOKEN_REFRESH_LEEWAY_MS || !this.#hasRefreshSession()) return;
|
|
499
|
+
try {
|
|
500
|
+
await this.#refreshDeduplicated({
|
|
501
|
+
epoch: this.#authEpoch,
|
|
502
|
+
accessToken
|
|
503
|
+
});
|
|
504
|
+
} catch (error) {
|
|
505
|
+
this.#emitter.emit("authError", { error });
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
/** Перед каждой попыткой перечитывает внешний источник токена. */
|
|
380
510
|
async prepare() {
|
|
381
|
-
|
|
511
|
+
this.#assertActive();
|
|
512
|
+
if ((await this.#loadSession())?.accessToken) return;
|
|
513
|
+
const auth = this.#config.auth;
|
|
514
|
+
if (typeof auth === "object" && auth !== null && "getToken" in auth) await this.#readExternalToken(() => auth.getToken());
|
|
382
515
|
}
|
|
383
516
|
/**
|
|
384
517
|
* Заголовки уже подготовленной авторизации без чтения storage или вызова внешнего источника.
|
|
@@ -397,6 +530,7 @@ var AuthManager = class {
|
|
|
397
530
|
* через `itd.auth.refresh()`, а библиотека просто пробрасывает {@link ItdAuthError}.
|
|
398
531
|
*/
|
|
399
532
|
recover() {
|
|
533
|
+
this.#assertActive();
|
|
400
534
|
return this.#config.autoRefresh ? this.onUnauthorized() : Promise.resolve(false);
|
|
401
535
|
}
|
|
402
536
|
/**
|
|
@@ -407,6 +541,7 @@ var AuthManager = class {
|
|
|
407
541
|
* по новой сессии на каждый старт.
|
|
408
542
|
*/
|
|
409
543
|
deviceId() {
|
|
544
|
+
this.#assertActive();
|
|
410
545
|
if (this.#deviceId) return Promise.resolve(this.#deviceId);
|
|
411
546
|
this.#deviceIdLoading ??= this.#resolveDeviceId().finally(() => {
|
|
412
547
|
this.#deviceIdLoading = null;
|
|
@@ -414,9 +549,12 @@ var AuthManager = class {
|
|
|
414
549
|
return this.#deviceIdLoading;
|
|
415
550
|
}
|
|
416
551
|
async #resolveDeviceId() {
|
|
552
|
+
const revision = this.#authEpoch;
|
|
417
553
|
const session = await this.#loadSession();
|
|
554
|
+
this.#assertActive();
|
|
418
555
|
const deviceId = this.#config.deviceId ?? session?.deviceId ?? require_redact.createDeviceId();
|
|
419
556
|
this.#deviceId = deviceId;
|
|
557
|
+
if (this.#authEpoch !== revision) return deviceId;
|
|
420
558
|
if (session?.deviceId !== deviceId) await this.#saveSession({
|
|
421
559
|
...session ?? {},
|
|
422
560
|
deviceId
|
|
@@ -430,6 +568,7 @@ var AuthManager = class {
|
|
|
430
568
|
* и пароль, первый же запрос сам заведёт сессию.
|
|
431
569
|
*/
|
|
432
570
|
async token() {
|
|
571
|
+
this.#assertActive();
|
|
433
572
|
const session = await this.#loadSession();
|
|
434
573
|
if (session?.accessToken) return session.accessToken;
|
|
435
574
|
const auth = this.#config.auth;
|
|
@@ -444,6 +583,7 @@ var AuthManager = class {
|
|
|
444
583
|
* @returns `true`, если токен обновлён и запрос имеет смысл повторить
|
|
445
584
|
*/
|
|
446
585
|
async onUnauthorized() {
|
|
586
|
+
this.#assertActive();
|
|
447
587
|
try {
|
|
448
588
|
if (await this.#refreshDeduplicated() !== null) return true;
|
|
449
589
|
this.#emitter.emit("authError", { error: this.#noRefreshSessionError() });
|
|
@@ -479,24 +619,33 @@ var AuthManager = class {
|
|
|
479
619
|
* @throws {ItdAuthError} если обновить сессию не удалось
|
|
480
620
|
*/
|
|
481
621
|
async refresh() {
|
|
622
|
+
this.#assertActive();
|
|
482
623
|
const token = await this.#refreshDeduplicated();
|
|
483
624
|
if (token === null) throw this.#noRefreshSessionError();
|
|
484
625
|
return token;
|
|
485
626
|
}
|
|
486
627
|
/** Сохраняет токен, полученный извне, — например после подтверждения OTP. */
|
|
487
|
-
async setAccessToken(accessToken) {
|
|
628
|
+
async setAccessToken(accessToken, expectedRevision = this.#authEpoch, cookies) {
|
|
629
|
+
this.#assertActive();
|
|
630
|
+
if (typeof accessToken !== "string" || accessToken.trim() === "") throw new require_errors.ItdConfigError("accessToken должен быть непустой строкой");
|
|
488
631
|
await this.#loadSession();
|
|
632
|
+
this.#assertActive();
|
|
633
|
+
if (this.#authEpoch !== expectedRevision) return false;
|
|
634
|
+
if (cookies) this.#jar.replaceWith(cookies);
|
|
489
635
|
this.#invalidateInFlight();
|
|
490
636
|
this.#transitionAuth(accessToken);
|
|
491
|
-
|
|
637
|
+
const saved = this.#saveSession({
|
|
492
638
|
...this.#session ?? {},
|
|
493
639
|
accessToken,
|
|
494
640
|
obtainedAt: this.#config.clock.now()
|
|
495
641
|
});
|
|
496
642
|
this.#emitter.emit("tokens", { accessToken });
|
|
643
|
+
await saved;
|
|
644
|
+
return true;
|
|
497
645
|
}
|
|
498
646
|
/** Текущая сессия целиком. Полезно, чтобы сохранить её самому. */
|
|
499
647
|
async getSession() {
|
|
648
|
+
this.#assertActive();
|
|
500
649
|
const session = await this.#loadSession();
|
|
501
650
|
return session ? require_storage.copySession(session) : null;
|
|
502
651
|
}
|
|
@@ -507,20 +656,24 @@ var AuthManager = class {
|
|
|
507
656
|
* токена идентификатор прежнего владельца остаться не может.
|
|
508
657
|
*/
|
|
509
658
|
async getUserId() {
|
|
659
|
+
this.#assertActive();
|
|
510
660
|
const session = await this.#loadSession();
|
|
511
|
-
return session?.accessToken ?
|
|
661
|
+
return session?.accessToken ? this.#identityForToken(session.accessToken).userId : void 0;
|
|
512
662
|
}
|
|
513
663
|
/** Заменяет сессию и связанные с ней cookie целиком. */
|
|
514
664
|
async setSession(session) {
|
|
665
|
+
this.#assertActive();
|
|
666
|
+
const normalized = normalizeSession(session, "session");
|
|
515
667
|
await this.#loadSession();
|
|
668
|
+
this.#assertActive();
|
|
516
669
|
this.#invalidateInFlight();
|
|
517
|
-
this.#transitionAuth(
|
|
670
|
+
this.#transitionAuth(normalized.accessToken);
|
|
518
671
|
this.#jar.clear();
|
|
519
|
-
this.#jar.deserialize(
|
|
520
|
-
if (
|
|
521
|
-
this.#session =
|
|
672
|
+
this.#jar.deserialize(normalized.cookies);
|
|
673
|
+
if (normalized.deviceId) this.#deviceId = normalized.deviceId;
|
|
674
|
+
this.#session = normalized;
|
|
522
675
|
this.#seedRefreshCookie();
|
|
523
|
-
await this.#saveSession(
|
|
676
|
+
await this.#saveSession(normalized);
|
|
524
677
|
}
|
|
525
678
|
/**
|
|
526
679
|
* Забывает сессию и cookie. Сетевой запрос не выполняется.
|
|
@@ -529,14 +682,19 @@ var AuthManager = class {
|
|
|
529
682
|
* новую запись в списке сессий.
|
|
530
683
|
*/
|
|
531
684
|
async clear() {
|
|
685
|
+
this.#assertActive();
|
|
532
686
|
await this.#loadSession();
|
|
687
|
+
this.#assertActive();
|
|
533
688
|
this.#invalidateInFlight();
|
|
534
689
|
this.#transitionAuth(void 0);
|
|
535
|
-
this.#session = null;
|
|
536
690
|
this.#jar.clear();
|
|
537
|
-
|
|
538
|
-
|
|
691
|
+
const retained = this.#deviceId ? { deviceId: this.#deviceId } : null;
|
|
692
|
+
this.#session = retained;
|
|
539
693
|
this.#emitter.emit("signOut", void 0);
|
|
694
|
+
await this.#enqueuePersistence(async () => {
|
|
695
|
+
await this.#config.storage.clear();
|
|
696
|
+
if (retained) await this.#config.storage.set(require_storage.copySession(retained));
|
|
697
|
+
});
|
|
540
698
|
}
|
|
541
699
|
#loadSession() {
|
|
542
700
|
if (this.#session !== void 0) return Promise.resolve(this.#session);
|
|
@@ -547,7 +705,8 @@ var AuthManager = class {
|
|
|
547
705
|
}
|
|
548
706
|
async #performLoad() {
|
|
549
707
|
const loaded = await this.#config.storage.get() ?? null;
|
|
550
|
-
|
|
708
|
+
this.#assertActive();
|
|
709
|
+
const stored = loaded === null ? null : normalizeSessionValue(loaded, "storage.get()", "stored", this.#config.logger);
|
|
551
710
|
if (stored?.cookies) this.#jar.deserialize(stored.cookies);
|
|
552
711
|
const fromConfig = this.#sessionFromConfig(this.#config.auth);
|
|
553
712
|
this.#session = stored && fromConfig ? {
|
|
@@ -569,7 +728,7 @@ var AuthManager = class {
|
|
|
569
728
|
if (!this.#config.useCookieJar) return;
|
|
570
729
|
const refreshToken = this.#session?.refreshToken;
|
|
571
730
|
if (!refreshToken) return;
|
|
572
|
-
if (this.#jar.has("refresh_token")) return;
|
|
731
|
+
if (this.#jar.has("refresh_token", this.#config.baseUrl + "/api/v1/auth")) return;
|
|
573
732
|
this.#jar.set(this.#config.baseUrl, require_redact.REFRESH_COOKIE, refreshToken, require_redact.REFRESH_COOKIE_PATH);
|
|
574
733
|
}
|
|
575
734
|
#sessionFromConfig(auth) {
|
|
@@ -588,56 +747,76 @@ var AuthManager = class {
|
|
|
588
747
|
async #saveSession(session) {
|
|
589
748
|
const cookies = this.#config.useCookieJar ? this.#jar.serialize() : void 0;
|
|
590
749
|
const deviceId = session.deviceId ?? this.#deviceId;
|
|
750
|
+
const { cookies: _staleCookies, ...withoutCookies } = require_storage.copySession(session);
|
|
591
751
|
const next = withoutLegacyUserId({
|
|
592
|
-
...
|
|
752
|
+
...withoutCookies,
|
|
593
753
|
...cookies?.length ? { cookies } : {},
|
|
594
754
|
...deviceId ? { deviceId } : {}
|
|
595
755
|
});
|
|
596
756
|
this.#session = next;
|
|
597
|
-
await this.#config.storage.set(next);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
const
|
|
607
|
-
|
|
757
|
+
await this.#enqueuePersistence(() => this.#config.storage.set(require_storage.copySession(next)));
|
|
758
|
+
}
|
|
759
|
+
#enqueuePersistence(operation) {
|
|
760
|
+
const pending = this.#persistence.then(operation, operation);
|
|
761
|
+
this.#persistence = pending.catch(() => {});
|
|
762
|
+
return pending;
|
|
763
|
+
}
|
|
764
|
+
/** Дедуплицирует refresh одной ревизии; следующая ждёт завершения предыдущей. */
|
|
765
|
+
#refreshDeduplicated(expectation = { epoch: this.#authEpoch }) {
|
|
766
|
+
const current = this.#refreshing;
|
|
767
|
+
if (current) {
|
|
768
|
+
if (current.epoch === expectation.epoch) return current.promise;
|
|
769
|
+
return current.promise.then(() => this.#refreshDeduplicated(expectation), () => this.#refreshDeduplicated(expectation));
|
|
770
|
+
}
|
|
771
|
+
const promise = this.#performRefresh(expectation).finally(() => {
|
|
772
|
+
if (this.#refreshing?.promise === promise) this.#refreshing = null;
|
|
608
773
|
});
|
|
609
|
-
this.#refreshing =
|
|
774
|
+
this.#refreshing = {
|
|
775
|
+
epoch: expectation.epoch,
|
|
776
|
+
promise
|
|
777
|
+
};
|
|
610
778
|
return promise;
|
|
611
779
|
}
|
|
612
|
-
|
|
780
|
+
#matchesRefreshExpectation(expectation) {
|
|
781
|
+
return this.#authEpoch === expectation.epoch && (expectation.accessToken === void 0 || this.#session?.accessToken === expectation.accessToken);
|
|
782
|
+
}
|
|
783
|
+
async #performRefresh(expectation) {
|
|
784
|
+
this.#assertActive();
|
|
613
785
|
await this.#loadSession();
|
|
786
|
+
if (!this.#matchesRefreshExpectation(expectation)) return this.#currentAccessToken();
|
|
614
787
|
if (!this.#hasRefreshSession()) return this.#reloginOrNull();
|
|
615
|
-
const
|
|
788
|
+
const flowCookies = this.#jar.clone();
|
|
616
789
|
try {
|
|
617
790
|
const accessToken = await this.#http.execute(AUTH_REFRESH, {
|
|
618
791
|
path: AUTH_PATHS.refresh,
|
|
619
792
|
skipAuth: true,
|
|
620
|
-
skipAuthRefresh: true
|
|
793
|
+
skipAuthRefresh: true,
|
|
794
|
+
cookieJar: flowCookies
|
|
621
795
|
});
|
|
622
|
-
if (!
|
|
623
|
-
|
|
796
|
+
if (!this.#matchesRefreshExpectation(expectation)) return this.#session?.accessToken ?? null;
|
|
797
|
+
this.#jar.replaceWith(flowCookies);
|
|
624
798
|
const rotated = this.#jar.getValue(require_redact.REFRESH_COOKIE, this.#config.baseUrl + require_redact.REFRESH_COOKIE_PATH);
|
|
625
799
|
this.#transitionAuth(accessToken, false);
|
|
626
|
-
|
|
800
|
+
const saved = this.#saveSession({
|
|
627
801
|
...this.#session ?? {},
|
|
628
802
|
accessToken,
|
|
629
803
|
...rotated ? { refreshToken: rotated } : {},
|
|
630
804
|
obtainedAt: this.#config.clock.now()
|
|
631
805
|
});
|
|
632
806
|
this.#emitter.emit("tokens", { accessToken });
|
|
807
|
+
await saved;
|
|
808
|
+
if (this.#authEpoch !== expectation.epoch) return this.#currentAccessToken();
|
|
633
809
|
return accessToken;
|
|
634
810
|
} catch (error) {
|
|
635
|
-
if (
|
|
636
|
-
|
|
811
|
+
if (this.#authEpoch !== expectation.epoch) return this.#currentAccessToken();
|
|
812
|
+
if (error instanceof require_errors.ItdAuthError) {
|
|
813
|
+
this.#invalidateInFlight();
|
|
814
|
+
const clearedRevision = this.#authEpoch;
|
|
637
815
|
this.#transitionAuth(void 0);
|
|
638
816
|
this.#session = null;
|
|
639
817
|
this.#jar.clear();
|
|
640
|
-
await this.#config.storage.clear();
|
|
818
|
+
await this.#enqueuePersistence(() => this.#config.storage.clear());
|
|
819
|
+
if (this.#authEpoch !== clearedRevision) return this.#currentAccessToken();
|
|
641
820
|
const relogged = await this.#reloginOrNull();
|
|
642
821
|
if (relogged !== null) return relogged;
|
|
643
822
|
throw error;
|
|
@@ -651,7 +830,8 @@ var AuthManager = class {
|
|
|
651
830
|
if (!this.#config.reloginOnRefreshFailure || !auth || typeof auth !== "object" || !("email" in auth)) return null;
|
|
652
831
|
try {
|
|
653
832
|
return await this.#signInWithCredentials(auth);
|
|
654
|
-
} catch {
|
|
833
|
+
} catch (error) {
|
|
834
|
+
this.#config.logger?.warn("Автоматический повторный вход не удался", error);
|
|
655
835
|
return null;
|
|
656
836
|
}
|
|
657
837
|
}
|
|
@@ -663,7 +843,8 @@ var AuthManager = class {
|
|
|
663
843
|
*/
|
|
664
844
|
#signInWithCredentials(credentials) {
|
|
665
845
|
if (this.#signingIn) return this.#signingIn;
|
|
666
|
-
const
|
|
846
|
+
const revision = this.#authEpoch;
|
|
847
|
+
const promise = this.#performSignIn(credentials, revision).finally(() => {
|
|
667
848
|
this.#signingIn = null;
|
|
668
849
|
});
|
|
669
850
|
this.#signingIn = promise;
|
|
@@ -683,8 +864,9 @@ var AuthManager = class {
|
|
|
683
864
|
if (credentials.turnstileToken) return credentials.turnstileToken;
|
|
684
865
|
throw new require_errors.ItdConfigError("Вход по email и паролю требует токен капчи Cloudflare Turnstile: без него сервер отвечает 422. Передайте auth.getTurnstileToken (источник свежего токена) либо разовый auth.turnstileToken. Ключ виджета — TURNSTILE_SITE_KEY. В Node токен умеет добывать отдельный пакет: npm i @itd-api/turnstile, затем getTurnstileToken: createTurnstileSolver().");
|
|
685
866
|
}
|
|
686
|
-
async #performSignIn(credentials) {
|
|
867
|
+
async #performSignIn(credentials, revision) {
|
|
687
868
|
const turnstileToken = await this.#resolveTurnstileToken(credentials);
|
|
869
|
+
const flowCookies = new require_redact.CookieJar();
|
|
688
870
|
const result = await this.#http.execute(AUTH_SIGN_IN, {
|
|
689
871
|
path: AUTH_PATHS.signIn,
|
|
690
872
|
body: {
|
|
@@ -693,18 +875,23 @@ var AuthManager = class {
|
|
|
693
875
|
turnstileToken
|
|
694
876
|
},
|
|
695
877
|
skipAuth: true,
|
|
696
|
-
skipAuthRefresh: true
|
|
878
|
+
skipAuthRefresh: true,
|
|
879
|
+
cookieJar: flowCookies
|
|
697
880
|
});
|
|
698
881
|
if (result.status !== SignInStatus.Authenticated) throw new require_errors.ItdConfigError("Вход по email и паролю требует подтверждения кодом из письма. Автоматический вход невозможен: воспользуйтесь itd.auth.signInWithOtp() и передайте полученный accessToken в конфигурацию клиента.");
|
|
699
882
|
const accessToken = result.accessToken;
|
|
883
|
+
this.#assertActive();
|
|
884
|
+
if (this.#authEpoch !== revision) return this.#session?.accessToken ?? null;
|
|
885
|
+
this.#jar.replaceWith(flowCookies);
|
|
700
886
|
this.#invalidateInFlight();
|
|
701
887
|
this.#transitionAuth(accessToken);
|
|
702
|
-
|
|
888
|
+
const saved = this.#saveSession({
|
|
703
889
|
accessToken,
|
|
704
890
|
obtainedAt: this.#config.clock.now()
|
|
705
891
|
});
|
|
706
892
|
this.#emitter.emit("tokens", { accessToken });
|
|
707
893
|
this.#emitter.emit("signIn", { accessToken });
|
|
894
|
+
await saved;
|
|
708
895
|
return accessToken;
|
|
709
896
|
}
|
|
710
897
|
};
|
|
@@ -763,14 +950,18 @@ var AuthResource = class extends require_render.BaseResource {
|
|
|
763
950
|
* @param credentials email, пароль и обязательный токен капчи — см. {@link CaptchaCredentials}
|
|
764
951
|
*/
|
|
765
952
|
async signIn(credentials, options = {}) {
|
|
953
|
+
const revision = this.#auth.revision();
|
|
954
|
+
const cookies = this.#auth.createCookieFlow(false);
|
|
766
955
|
const result = await this.http.execute(AUTH_SIGN_IN, {
|
|
767
956
|
path: AUTH_PATHS.signIn,
|
|
768
957
|
body: credentials,
|
|
769
958
|
skipAuth: true,
|
|
770
959
|
skipAuthRefresh: true,
|
|
960
|
+
cookieJar: cookies,
|
|
771
961
|
...options
|
|
772
962
|
});
|
|
773
|
-
if (result.status === SignInStatus.Authenticated) await this.#auth.setAccessToken(result.accessToken);
|
|
963
|
+
if (result.status === SignInStatus.Authenticated) await this.#auth.setAccessToken(result.accessToken, revision, cookies);
|
|
964
|
+
else this.#auth.commitCookieFlow(cookies, revision);
|
|
774
965
|
return result;
|
|
775
966
|
}
|
|
776
967
|
/**
|
|
@@ -779,14 +970,17 @@ var AuthResource = class extends require_render.BaseResource {
|
|
|
779
970
|
* Полученный токен сохраняется в клиенте автоматически.
|
|
780
971
|
*/
|
|
781
972
|
async verifyOtp(input, options = {}) {
|
|
973
|
+
const revision = this.#auth.revision();
|
|
974
|
+
const cookies = this.#auth.createCookieFlow();
|
|
782
975
|
const accessToken = await this.http.execute(AUTH_VERIFY_OTP, {
|
|
783
976
|
path: AUTH_PATHS.verifyOtp,
|
|
784
977
|
body: input,
|
|
785
978
|
skipAuth: true,
|
|
786
979
|
skipAuthRefresh: true,
|
|
980
|
+
cookieJar: cookies,
|
|
787
981
|
...options
|
|
788
982
|
});
|
|
789
|
-
await this.#auth.setAccessToken(accessToken);
|
|
983
|
+
await this.#auth.setAccessToken(accessToken, revision, cookies);
|
|
790
984
|
return accessToken;
|
|
791
985
|
}
|
|
792
986
|
/** Отправляет код подтверждения повторно. */
|
|
@@ -1705,7 +1899,7 @@ var ItdAccounts = class ItdAccounts {
|
|
|
1705
1899
|
})));
|
|
1706
1900
|
const added = [];
|
|
1707
1901
|
for (const { name, session } of sessions) {
|
|
1708
|
-
if (!require_multi_storage.isRestorableSession(session)) continue;
|
|
1902
|
+
if (!require_multi_storage.isRestorableSession(session, this.#base.baseUrl ?? "https://xn--d1ah4a.com")) continue;
|
|
1709
1903
|
if (this.#clients.has(name) || this.#removing.has(name)) continue;
|
|
1710
1904
|
this.addAccount(name);
|
|
1711
1905
|
added.push(name);
|
|
@@ -2039,7 +2233,6 @@ exports.comment = require_render.comment;
|
|
|
2039
2233
|
exports.createAccounts = createAccounts;
|
|
2040
2234
|
exports.createClient = createClient;
|
|
2041
2235
|
exports.createKeyValueStore = require_key_value_store.createKeyValueStore;
|
|
2042
|
-
exports.createMemoryShopOrderAccessStorage = require_render.createMemoryShopOrderAccessStorage;
|
|
2043
2236
|
exports.createMultiTokenStorage = require_multi_storage.createMultiTokenStorage;
|
|
2044
2237
|
exports.createNotificationEventsClient = require_events.createNotificationEventsClient;
|
|
2045
2238
|
exports.createRecordKeyValueStore = require_key_value_store.createRecordKeyValueStore;
|