itd-api 0.7.0 → 0.7.2
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/README.md +2 -2
- package/dist/index.cjs +540 -9806
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +280 -4981
- package/dist/index.d.ts +280 -4981
- package/dist/index.js +407 -9673
- package/dist/index.js.map +1 -1
- package/dist/{node.cjs → node/index.cjs} +16 -14
- package/dist/node/index.cjs.map +1 -0
- package/dist/{node.d.cts → node/index.d.cts} +4 -3
- package/dist/{node.d.ts → node/index.d.ts} +4 -3
- package/dist/{node.js → node/index.js} +5 -3
- package/dist/node/index.js.map +1 -0
- package/dist/realtime/index.cjs +165 -0
- package/dist/realtime/index.cjs.map +1 -0
- package/dist/realtime/index.d.cts +51 -0
- package/dist/realtime/index.d.ts +51 -0
- package/dist/realtime/index.js +120 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/rest/index.cjs +398 -0
- package/dist/rest/index.cjs.map +1 -0
- package/dist/rest/index.d.cts +146 -0
- package/dist/rest/index.d.ts +146 -0
- package/dist/rest/index.js +302 -0
- package/dist/rest/index.js.map +1 -0
- package/dist/shared/auth-provider-CG8oCQ9F.cjs +108 -0
- package/dist/shared/auth-provider-CG8oCQ9F.cjs.map +1 -0
- package/dist/shared/auth-provider-mYqxsSVa.js +91 -0
- package/dist/shared/auth-provider-mYqxsSVa.js.map +1 -0
- package/dist/shared/contracts-BoT7msmq.d.cts +84 -0
- package/dist/shared/contracts-BoT7msmq.d.ts +84 -0
- package/dist/{multi-storage-Bf84xiO8.cjs → shared/cookies-DZwFq6kr.cjs} +98 -429
- package/dist/shared/cookies-DZwFq6kr.cjs.map +1 -0
- package/dist/{multi-storage-BUZaLAPO.js → shared/cookies-tX2sNwxb.js} +99 -352
- package/dist/shared/cookies-tX2sNwxb.js.map +1 -0
- package/dist/{storage-IHdXw52v.js → shared/errors-Bhrd2fJd.js} +2 -229
- package/dist/shared/errors-Bhrd2fJd.js.map +1 -0
- package/dist/{storage-DnzZPS_9.cjs → shared/errors-DfU8M5eS.cjs} +1 -288
- package/dist/shared/errors-DfU8M5eS.cjs.map +1 -0
- package/dist/shared/multi-storage--yTEqiod.cjs +150 -0
- package/dist/shared/multi-storage--yTEqiod.cjs.map +1 -0
- package/dist/shared/multi-storage-CjAPB5Kq.d.cts +72 -0
- package/dist/shared/multi-storage-CkvTUC5m.js +121 -0
- package/dist/shared/multi-storage-CkvTUC5m.js.map +1 -0
- package/dist/shared/multi-storage-DccjD7Ww.d.ts +72 -0
- package/dist/shared/options-Dg5N3r1V.cjs +189 -0
- package/dist/shared/options-Dg5N3r1V.cjs.map +1 -0
- package/dist/shared/options-DtATYdLr.js +142 -0
- package/dist/shared/options-DtATYdLr.js.map +1 -0
- package/dist/shared/render-C6HRPs10.js +4289 -0
- package/dist/shared/render-C6HRPs10.js.map +1 -0
- package/dist/shared/render-CgwKdOzu.d.ts +2311 -0
- package/dist/shared/render-DO0F5YSm.d.cts +2311 -0
- package/dist/shared/render-mcuELiYi.cjs +4462 -0
- package/dist/shared/render-mcuELiYi.cjs.map +1 -0
- package/dist/shared/storage-BPJR_k4-.cjs +290 -0
- package/dist/shared/storage-BPJR_k4-.cjs.map +1 -0
- package/dist/{storage-BqMxs76Y.d.ts → shared/storage-C_eICCep.d.cts} +2 -2
- package/dist/{storage-BqMxs76Y.d.cts → shared/storage-C_eICCep.d.ts} +2 -2
- package/dist/shared/storage-D86edNCB.js +231 -0
- package/dist/shared/storage-D86edNCB.js.map +1 -0
- package/dist/shared/url-BaMCQpYH.cjs +4084 -0
- package/dist/shared/url-BaMCQpYH.cjs.map +1 -0
- package/dist/shared/url-DTfZ2toq.d.cts +2081 -0
- package/dist/shared/url-DTfZ2toq.d.ts +2081 -0
- package/dist/shared/url-IU0xN9wX.js +3713 -0
- package/dist/shared/url-IU0xN9wX.js.map +1 -0
- package/dist/shared/websocket-BLR8eVJV.js +1874 -0
- package/dist/shared/websocket-BLR8eVJV.js.map +1 -0
- package/dist/shared/websocket-C_eI4H2o.cjs +1945 -0
- package/dist/shared/websocket-C_eI4H2o.cjs.map +1 -0
- package/dist/shared/websocket-DF7XIMiX.d.cts +562 -0
- package/dist/shared/websocket-DYKBr8HF.d.ts +562 -0
- package/dist/{web.cjs → web/index.cjs} +4 -3
- package/dist/web/index.cjs.map +1 -0
- package/dist/{web.d.cts → web/index.d.cts} +2 -2
- package/dist/{web.d.ts → web/index.d.ts} +2 -2
- package/dist/{web.js → web/index.js} +3 -2
- package/dist/web/index.js.map +1 -0
- package/package.json +40 -12
- package/dist/multi-storage-BUZaLAPO.js.map +0 -1
- package/dist/multi-storage-BUoEoW8f.d.cts +0 -154
- package/dist/multi-storage-Bf84xiO8.cjs.map +0 -1
- package/dist/multi-storage-CQSlI_kn.d.ts +0 -154
- package/dist/node.cjs.map +0 -1
- package/dist/node.js.map +0 -1
- package/dist/storage-DnzZPS_9.cjs.map +0 -1
- package/dist/storage-IHdXw52v.js.map +0 -1
- package/dist/web.cjs.map +0 -1
- package/dist/web.js.map +0 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
const require_errors = require("./errors-DfU8M5eS.cjs");
|
|
2
|
+
const require_cookies = require("./cookies-DZwFq6kr.cjs");
|
|
3
|
+
const require_storage = require("./storage-BPJR_k4-.cjs");
|
|
4
|
+
//#region src/session/multi-storage.ts
|
|
5
|
+
/**
|
|
6
|
+
* Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает
|
|
7
|
+
* отдельный `ItdClient`, который про соседние аккаунты ничего не знает.
|
|
8
|
+
*/
|
|
9
|
+
function scopedTokenStorage(storage, account) {
|
|
10
|
+
return {
|
|
11
|
+
get: () => storage.get(account),
|
|
12
|
+
set: (session) => storage.set(account, session),
|
|
13
|
+
clear: () => storage.clear(account)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Можно ли восстановить авторизованный аккаунт из записи.
|
|
18
|
+
*
|
|
19
|
+
* Один `deviceId` не считается сессией: он намеренно переживает выход, но не позволяет
|
|
20
|
+
* выполнить ни одного авторизованного запроса.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
function isRestorableSession(session) {
|
|
25
|
+
if (!session) return false;
|
|
26
|
+
if (session.accessToken || session.refreshToken) return true;
|
|
27
|
+
const jar = new require_cookies.CookieJar();
|
|
28
|
+
jar.deserialize(session.cookies);
|
|
29
|
+
return jar.has(require_cookies.AUTH_FLAG_COOKIE);
|
|
30
|
+
}
|
|
31
|
+
/** @internal */
|
|
32
|
+
function controlledTokenStorage(storage, account) {
|
|
33
|
+
let revoked = false;
|
|
34
|
+
const pending = /* @__PURE__ */ new Set();
|
|
35
|
+
const mutate = (operation) => {
|
|
36
|
+
if (revoked) return Promise.resolve();
|
|
37
|
+
const promise = Promise.resolve().then(operation);
|
|
38
|
+
pending.add(promise);
|
|
39
|
+
promise.then(() => pending.delete(promise), () => pending.delete(promise));
|
|
40
|
+
return promise;
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
storage: {
|
|
44
|
+
get: () => revoked ? null : storage.get(account),
|
|
45
|
+
set: (session) => mutate(() => storage.set(account, session)),
|
|
46
|
+
clear: () => mutate(() => storage.clear(account))
|
|
47
|
+
},
|
|
48
|
+
revoke() {
|
|
49
|
+
revoked = true;
|
|
50
|
+
},
|
|
51
|
+
async drain() {
|
|
52
|
+
await Promise.all([...pending]);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Мультихранилище в памяти процесса — вариант по умолчанию.
|
|
58
|
+
*
|
|
59
|
+
* Сессии теряются при перезапуске. Для долгоживущих ботов возьмите `FileMultiTokenStorage`
|
|
60
|
+
* из `itd-api/node` либо соберите своё через {@link createMultiTokenStorage}.
|
|
61
|
+
*/
|
|
62
|
+
var MemoryMultiTokenStorage = class {
|
|
63
|
+
#store = new require_storage.MemoryKeyValueStore();
|
|
64
|
+
constructor(initial) {
|
|
65
|
+
for (const [account, session] of Object.entries(initial ?? {})) this.#store.set(accountKey(account), require_storage.copySession(session));
|
|
66
|
+
}
|
|
67
|
+
get(account) {
|
|
68
|
+
const session = this.#store.get(accountKey(account));
|
|
69
|
+
return session ? require_storage.copySession(session) : null;
|
|
70
|
+
}
|
|
71
|
+
set(account, session) {
|
|
72
|
+
this.#store.set(accountKey(account), require_storage.copySession(session));
|
|
73
|
+
}
|
|
74
|
+
clear(account) {
|
|
75
|
+
this.#store.delete(accountKey(account));
|
|
76
|
+
}
|
|
77
|
+
accounts() {
|
|
78
|
+
return this.#store.keys(ACCOUNT_KEY_PREFIX).flatMap((key) => readAccountName(key.slice(ACCOUNT_KEY_PREFIX.length)) ?? []);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const ACCOUNT_KEY_PREFIX = "accounts/";
|
|
82
|
+
function accountKey(account) {
|
|
83
|
+
return `${ACCOUNT_KEY_PREFIX}${encodeURIComponent(account)}`;
|
|
84
|
+
}
|
|
85
|
+
/** Читает имя аккаунта из ключа. `undefined` — ключ не декодируется и запись пропускается. */
|
|
86
|
+
function readAccountName(encoded) {
|
|
87
|
+
try {
|
|
88
|
+
return decodeURIComponent(encoded);
|
|
89
|
+
} catch {
|
|
90
|
+
console.warn(`[itd-api] запись хранилища пропущена: ключ ${JSON.stringify(encoded)} не декодируется`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Создаёт доменное хранилище нескольких сессий поверх enumerable key-value backend.
|
|
96
|
+
*
|
|
97
|
+
* Отдельный индекс аккаунтов не используется: `accounts()` перечисляет ключи backend. Поэтому
|
|
98
|
+
* запись сессии не может разойтись с индексом, но backend обязан эффективно поддерживать `keys`.
|
|
99
|
+
*/
|
|
100
|
+
function createMultiTokenStorage(store, options = {}) {
|
|
101
|
+
const prefix = options.prefix ?? ACCOUNT_KEY_PREFIX;
|
|
102
|
+
if (typeof prefix !== "string") throw new require_errors.ItdConfigError("prefix MultiTokenStorage должен быть строкой");
|
|
103
|
+
const backend = require_storage.createKeyValueStore(store);
|
|
104
|
+
if (!require_storage.isEnumerableKeyValueStore(backend)) throw new require_errors.ItdConfigError("MultiTokenStorage требует KeyValueStore с методом keys()");
|
|
105
|
+
const key = (account) => `${prefix}${encodeURIComponent(account)}`;
|
|
106
|
+
return {
|
|
107
|
+
async get(account) {
|
|
108
|
+
const session = await backend.get(key(account));
|
|
109
|
+
return session ? require_storage.copySession(session) : null;
|
|
110
|
+
},
|
|
111
|
+
set: (account, session) => backend.set(key(account), require_storage.copySession(session)),
|
|
112
|
+
clear: (account) => backend.delete(key(account)),
|
|
113
|
+
async accounts() {
|
|
114
|
+
return (await require_storage.collectKeyValueStoreKeys(backend, prefix)).flatMap((value) => readAccountName(value.slice(prefix.length)) ?? []);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
//#endregion
|
|
119
|
+
Object.defineProperty(exports, "MemoryMultiTokenStorage", {
|
|
120
|
+
enumerable: true,
|
|
121
|
+
get: function() {
|
|
122
|
+
return MemoryMultiTokenStorage;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
Object.defineProperty(exports, "controlledTokenStorage", {
|
|
126
|
+
enumerable: true,
|
|
127
|
+
get: function() {
|
|
128
|
+
return controlledTokenStorage;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
Object.defineProperty(exports, "createMultiTokenStorage", {
|
|
132
|
+
enumerable: true,
|
|
133
|
+
get: function() {
|
|
134
|
+
return createMultiTokenStorage;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
Object.defineProperty(exports, "isRestorableSession", {
|
|
138
|
+
enumerable: true,
|
|
139
|
+
get: function() {
|
|
140
|
+
return isRestorableSession;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
Object.defineProperty(exports, "scopedTokenStorage", {
|
|
144
|
+
enumerable: true,
|
|
145
|
+
get: function() {
|
|
146
|
+
return scopedTokenStorage;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
//# sourceMappingURL=multi-storage--yTEqiod.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-storage--yTEqiod.cjs","names":["CookieJar","AUTH_FLAG_COOKIE","#store","MemoryKeyValueStore","copySession","ItdConfigError","createKeyValueStore","isEnumerableKeyValueStore","collectKeyValueStoreKeys"],"sources":["../../src/session/multi-storage.ts"],"sourcesContent":["import { AUTH_FLAG_COOKIE, CookieJar } from '../core/cookies.js';\nimport { ItdConfigError } from '../core/errors.js';\nimport {\n collectKeyValueStoreKeys,\n createKeyValueStore,\n type EnumerableKeyValueStore,\n isEnumerableKeyValueStore,\n MemoryKeyValueStore,\n} from '../core/key-value-store.js';\nimport { copySession, type ItdSession, type TokenStorage } from './storage.js';\n\n/**\n * Хранилище сессий нескольких аккаунтов.\n *\n * Отличается от {@link TokenStorage} тем, что каждый метод получает **имя аккаунта**:\n * так адаптер сам решает, как строить ключ, и Redis, БД или связка ключей получают то,\n * что им нужно. Имя приходит ровно тем, под которым аккаунт заведён в {@link ItdAccounts}:\n * библиотека его не нормализует и не экранирует — префиксы, экранирование и ограничения\n * на длину ключа остаются за адаптером.\n *\n * Все методы могут быть как синхронными, так и асинхронными.\n *\n * @example Хранилище поверх enumerable Redis backend\n * ```ts\n * const storage = createMultiTokenStorage(withNamespace(redisStore, 'itd'));\n * ```\n */\nexport interface MultiTokenStorage {\n /** Прочитать сессию аккаунта. `null`, если её нет. */\n get(account: string): ItdSession | null | Promise<ItdSession | null>;\n /** Сохранить сессию аккаунта целиком. */\n set(account: string, session: ItdSession): void | Promise<void>;\n /** Удалить сессию аккаунта. Вызывается при выходе и при неудачном обновлении токена. */\n clear(account: string): void | Promise<void>;\n /**\n * Имена сохранённых записей — по ним `ItdAccounts.restore()` находит кандидатов\n * после перезапуска процесса.\n *\n * Список ведёт сам адаптер: у файлового и памятного он виден из самой записи,\n * а хранилищу «ключ — значение» придётся держать множество имён рядом с сессиями.\n * Перед восстановлением контейнер читает каждую запись и пропускает оставшийся после\n * выхода одинокий `deviceId`: без токена или refresh-сессии авторизоваться невозможно.\n * Пустой список означает лишь то, что кандидатов нет, — сами записи при этом могут быть\n * доступны по имени.\n */\n accounts(): readonly string[] | Promise<readonly string[]>;\n}\n\n/**\n * Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает\n * отдельный `ItdClient`, который про соседние аккаунты ничего не знает.\n */\nexport function scopedTokenStorage(storage: MultiTokenStorage, account: string): TokenStorage {\n return {\n get: () => storage.get(account),\n set: (session) => storage.set(account, session),\n clear: () => storage.clear(account),\n };\n}\n\n/**\n * Можно ли восстановить авторизованный аккаунт из записи.\n *\n * Один `deviceId` не считается сессией: он намеренно переживает выход, но не позволяет\n * выполнить ни одного авторизованного запроса.\n *\n * @internal\n */\nexport function isRestorableSession(session: ItdSession | null): session is ItdSession {\n if (!session) return false;\n if (session.accessToken || session.refreshToken) return true;\n\n const jar = new CookieJar();\n jar.deserialize(session.cookies);\n return jar.has(AUTH_FLAG_COOKIE);\n}\n\n/**\n * Управляемый срез хранилища для клиента аккаунта.\n *\n * После `revoke()` новые записи не доходят до общего хранилища, а `drain()` позволяет\n * дождаться уже начатых. Так удалённый клиент или завершившийся с опозданием refresh\n * не воскресят сессию после `removeAccount(..., { forget: true })`.\n *\n * @internal\n */\nexport interface ControlledTokenStorage {\n storage: TokenStorage;\n revoke(): void;\n drain(): Promise<void>;\n}\n\n/** @internal */\nexport function controlledTokenStorage(\n storage: MultiTokenStorage,\n account: string,\n): ControlledTokenStorage {\n let revoked = false;\n const pending = new Set<Promise<void>>();\n\n const mutate = (operation: () => void | Promise<void>): Promise<void> => {\n if (revoked) return Promise.resolve();\n\n const promise = Promise.resolve().then(operation);\n pending.add(promise);\n void promise.then(\n () => pending.delete(promise),\n () => pending.delete(promise),\n );\n return promise;\n };\n\n return {\n storage: {\n get: () => (revoked ? null : storage.get(account)),\n set: (session) => mutate(() => storage.set(account, session)),\n clear: () => mutate(() => storage.clear(account)),\n },\n revoke() {\n revoked = true;\n },\n async drain() {\n await Promise.all([...pending]);\n },\n };\n}\n\n/**\n * Мультихранилище в памяти процесса — вариант по умолчанию.\n *\n * Сессии теряются при перезапуске. Для долгоживущих ботов возьмите `FileMultiTokenStorage`\n * из `itd-api/node` либо соберите своё через {@link createMultiTokenStorage}.\n */\nexport class MemoryMultiTokenStorage implements MultiTokenStorage {\n readonly #store = new MemoryKeyValueStore<ItdSession>();\n\n constructor(initial?: Readonly<Record<string, ItdSession>> | null) {\n for (const [account, session] of Object.entries(initial ?? {})) {\n this.#store.set(accountKey(account), copySession(session));\n }\n }\n\n get(account: string): ItdSession | null {\n const session = this.#store.get(accountKey(account));\n return session ? copySession(session) : null;\n }\n\n set(account: string, session: ItdSession): void {\n this.#store.set(accountKey(account), copySession(session));\n }\n\n clear(account: string): void {\n this.#store.delete(accountKey(account));\n }\n\n accounts(): string[] {\n return this.#store\n .keys(ACCOUNT_KEY_PREFIX)\n .flatMap((key) => readAccountName(key.slice(ACCOUNT_KEY_PREFIX.length)) ?? []);\n }\n}\n\nconst ACCOUNT_KEY_PREFIX = 'accounts/';\n\nfunction accountKey(account: string): string {\n return `${ACCOUNT_KEY_PREFIX}${encodeURIComponent(account)}`;\n}\n\n/** Читает имя аккаунта из ключа. `undefined` — ключ не декодируется и запись пропускается. */\nfunction readAccountName(encoded: string): string | undefined {\n try {\n return decodeURIComponent(encoded);\n } catch {\n console.warn(\n `[itd-api] запись хранилища пропущена: ключ ${JSON.stringify(encoded)} не декодируется`,\n );\n return undefined;\n }\n}\n\n/** Настройки доменного адаптера нескольких сессий. */\nexport interface MultiTokenStorageAdapterOptions {\n /** Префикс ключей аккаунтов. По умолчанию `accounts/`. */\n prefix?: string | undefined;\n}\n\n/**\n * Создаёт доменное хранилище нескольких сессий поверх enumerable key-value backend.\n *\n * Отдельный индекс аккаунтов не используется: `accounts()` перечисляет ключи backend. Поэтому\n * запись сессии не может разойтись с индексом, но backend обязан эффективно поддерживать `keys`.\n */\nexport function createMultiTokenStorage(\n store: EnumerableKeyValueStore<ItdSession>,\n options: MultiTokenStorageAdapterOptions = {},\n): MultiTokenStorage {\n const prefix = options.prefix ?? ACCOUNT_KEY_PREFIX;\n if (typeof prefix !== 'string') {\n throw new ItdConfigError('prefix MultiTokenStorage должен быть строкой');\n }\n const backend = createKeyValueStore(store);\n if (!isEnumerableKeyValueStore(backend)) {\n throw new ItdConfigError('MultiTokenStorage требует KeyValueStore с методом keys()');\n }\n const key = (account: string) => `${prefix}${encodeURIComponent(account)}`;\n return {\n async get(account) {\n const session = await backend.get(key(account));\n return session ? copySession(session) : null;\n },\n set: (account, session) => backend.set(key(account), copySession(session)),\n clear: (account) => backend.delete(key(account)),\n async accounts() {\n const keys = await collectKeyValueStoreKeys(backend, prefix);\n return keys.flatMap((value) => readAccountName(value.slice(prefix.length)) ?? []);\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { o as EnumerableKeyValueStore, r as TokenStorage, t as ItdSession } from "./storage-C_eICCep.cjs";
|
|
2
|
+
//#region src/session/multi-storage.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Хранилище сессий нескольких аккаунтов.
|
|
5
|
+
*
|
|
6
|
+
* Отличается от {@link TokenStorage} тем, что каждый метод получает **имя аккаунта**:
|
|
7
|
+
* так адаптер сам решает, как строить ключ, и Redis, БД или связка ключей получают то,
|
|
8
|
+
* что им нужно. Имя приходит ровно тем, под которым аккаунт заведён в {@link ItdAccounts}:
|
|
9
|
+
* библиотека его не нормализует и не экранирует — префиксы, экранирование и ограничения
|
|
10
|
+
* на длину ключа остаются за адаптером.
|
|
11
|
+
*
|
|
12
|
+
* Все методы могут быть как синхронными, так и асинхронными.
|
|
13
|
+
*
|
|
14
|
+
* @example Хранилище поверх enumerable Redis backend
|
|
15
|
+
* ```ts
|
|
16
|
+
* const storage = createMultiTokenStorage(withNamespace(redisStore, 'itd'));
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
interface MultiTokenStorage {
|
|
20
|
+
/** Прочитать сессию аккаунта. `null`, если её нет. */
|
|
21
|
+
get(account: string): ItdSession | null | Promise<ItdSession | null>;
|
|
22
|
+
/** Сохранить сессию аккаунта целиком. */
|
|
23
|
+
set(account: string, session: ItdSession): void | Promise<void>;
|
|
24
|
+
/** Удалить сессию аккаунта. Вызывается при выходе и при неудачном обновлении токена. */
|
|
25
|
+
clear(account: string): void | Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Имена сохранённых записей — по ним `ItdAccounts.restore()` находит кандидатов
|
|
28
|
+
* после перезапуска процесса.
|
|
29
|
+
*
|
|
30
|
+
* Список ведёт сам адаптер: у файлового и памятного он виден из самой записи,
|
|
31
|
+
* а хранилищу «ключ — значение» придётся держать множество имён рядом с сессиями.
|
|
32
|
+
* Перед восстановлением контейнер читает каждую запись и пропускает оставшийся после
|
|
33
|
+
* выхода одинокий `deviceId`: без токена или refresh-сессии авторизоваться невозможно.
|
|
34
|
+
* Пустой список означает лишь то, что кандидатов нет, — сами записи при этом могут быть
|
|
35
|
+
* доступны по имени.
|
|
36
|
+
*/
|
|
37
|
+
accounts(): readonly string[] | Promise<readonly string[]>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает
|
|
41
|
+
* отдельный `ItdClient`, который про соседние аккаунты ничего не знает.
|
|
42
|
+
*/
|
|
43
|
+
declare function scopedTokenStorage(storage: MultiTokenStorage, account: string): TokenStorage;
|
|
44
|
+
/**
|
|
45
|
+
* Мультихранилище в памяти процесса — вариант по умолчанию.
|
|
46
|
+
*
|
|
47
|
+
* Сессии теряются при перезапуске. Для долгоживущих ботов возьмите `FileMultiTokenStorage`
|
|
48
|
+
* из `itd-api/node` либо соберите своё через {@link createMultiTokenStorage}.
|
|
49
|
+
*/
|
|
50
|
+
declare class MemoryMultiTokenStorage implements MultiTokenStorage {
|
|
51
|
+
#private;
|
|
52
|
+
constructor(initial?: Readonly<Record<string, ItdSession>> | null);
|
|
53
|
+
get(account: string): ItdSession | null;
|
|
54
|
+
set(account: string, session: ItdSession): void;
|
|
55
|
+
clear(account: string): void;
|
|
56
|
+
accounts(): string[];
|
|
57
|
+
}
|
|
58
|
+
/** Настройки доменного адаптера нескольких сессий. */
|
|
59
|
+
interface MultiTokenStorageAdapterOptions {
|
|
60
|
+
/** Префикс ключей аккаунтов. По умолчанию `accounts/`. */
|
|
61
|
+
prefix?: string | undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Создаёт доменное хранилище нескольких сессий поверх enumerable key-value backend.
|
|
65
|
+
*
|
|
66
|
+
* Отдельный индекс аккаунтов не используется: `accounts()` перечисляет ключи backend. Поэтому
|
|
67
|
+
* запись сессии не может разойтись с индексом, но backend обязан эффективно поддерживать `keys`.
|
|
68
|
+
*/
|
|
69
|
+
declare function createMultiTokenStorage(store: EnumerableKeyValueStore<ItdSession>, options?: MultiTokenStorageAdapterOptions): MultiTokenStorage;
|
|
70
|
+
//#endregion
|
|
71
|
+
export { scopedTokenStorage as a, createMultiTokenStorage as i, MultiTokenStorage as n, MultiTokenStorageAdapterOptions as r, MemoryMultiTokenStorage as t };
|
|
72
|
+
//# sourceMappingURL=multi-storage-CjAPB5Kq.d.cts.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { a as ItdConfigError } from "./errors-Bhrd2fJd.js";
|
|
2
|
+
import { n as CookieJar, t as AUTH_FLAG_COOKIE } from "./cookies-tX2sNwxb.js";
|
|
3
|
+
import { a as collectKeyValueStoreKeys, c as isEnumerableKeyValueStore, i as MemoryKeyValueStore, n as copySession, o as createKeyValueStore } from "./storage-D86edNCB.js";
|
|
4
|
+
//#region src/session/multi-storage.ts
|
|
5
|
+
/**
|
|
6
|
+
* Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает
|
|
7
|
+
* отдельный `ItdClient`, который про соседние аккаунты ничего не знает.
|
|
8
|
+
*/
|
|
9
|
+
function scopedTokenStorage(storage, account) {
|
|
10
|
+
return {
|
|
11
|
+
get: () => storage.get(account),
|
|
12
|
+
set: (session) => storage.set(account, session),
|
|
13
|
+
clear: () => storage.clear(account)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Можно ли восстановить авторизованный аккаунт из записи.
|
|
18
|
+
*
|
|
19
|
+
* Один `deviceId` не считается сессией: он намеренно переживает выход, но не позволяет
|
|
20
|
+
* выполнить ни одного авторизованного запроса.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
function isRestorableSession(session) {
|
|
25
|
+
if (!session) return false;
|
|
26
|
+
if (session.accessToken || session.refreshToken) return true;
|
|
27
|
+
const jar = new CookieJar();
|
|
28
|
+
jar.deserialize(session.cookies);
|
|
29
|
+
return jar.has(AUTH_FLAG_COOKIE);
|
|
30
|
+
}
|
|
31
|
+
/** @internal */
|
|
32
|
+
function controlledTokenStorage(storage, account) {
|
|
33
|
+
let revoked = false;
|
|
34
|
+
const pending = /* @__PURE__ */ new Set();
|
|
35
|
+
const mutate = (operation) => {
|
|
36
|
+
if (revoked) return Promise.resolve();
|
|
37
|
+
const promise = Promise.resolve().then(operation);
|
|
38
|
+
pending.add(promise);
|
|
39
|
+
promise.then(() => pending.delete(promise), () => pending.delete(promise));
|
|
40
|
+
return promise;
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
storage: {
|
|
44
|
+
get: () => revoked ? null : storage.get(account),
|
|
45
|
+
set: (session) => mutate(() => storage.set(account, session)),
|
|
46
|
+
clear: () => mutate(() => storage.clear(account))
|
|
47
|
+
},
|
|
48
|
+
revoke() {
|
|
49
|
+
revoked = true;
|
|
50
|
+
},
|
|
51
|
+
async drain() {
|
|
52
|
+
await Promise.all([...pending]);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Мультихранилище в памяти процесса — вариант по умолчанию.
|
|
58
|
+
*
|
|
59
|
+
* Сессии теряются при перезапуске. Для долгоживущих ботов возьмите `FileMultiTokenStorage`
|
|
60
|
+
* из `itd-api/node` либо соберите своё через {@link createMultiTokenStorage}.
|
|
61
|
+
*/
|
|
62
|
+
var MemoryMultiTokenStorage = class {
|
|
63
|
+
#store = new MemoryKeyValueStore();
|
|
64
|
+
constructor(initial) {
|
|
65
|
+
for (const [account, session] of Object.entries(initial ?? {})) this.#store.set(accountKey(account), copySession(session));
|
|
66
|
+
}
|
|
67
|
+
get(account) {
|
|
68
|
+
const session = this.#store.get(accountKey(account));
|
|
69
|
+
return session ? copySession(session) : null;
|
|
70
|
+
}
|
|
71
|
+
set(account, session) {
|
|
72
|
+
this.#store.set(accountKey(account), copySession(session));
|
|
73
|
+
}
|
|
74
|
+
clear(account) {
|
|
75
|
+
this.#store.delete(accountKey(account));
|
|
76
|
+
}
|
|
77
|
+
accounts() {
|
|
78
|
+
return this.#store.keys(ACCOUNT_KEY_PREFIX).flatMap((key) => readAccountName(key.slice(ACCOUNT_KEY_PREFIX.length)) ?? []);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const ACCOUNT_KEY_PREFIX = "accounts/";
|
|
82
|
+
function accountKey(account) {
|
|
83
|
+
return `${ACCOUNT_KEY_PREFIX}${encodeURIComponent(account)}`;
|
|
84
|
+
}
|
|
85
|
+
/** Читает имя аккаунта из ключа. `undefined` — ключ не декодируется и запись пропускается. */
|
|
86
|
+
function readAccountName(encoded) {
|
|
87
|
+
try {
|
|
88
|
+
return decodeURIComponent(encoded);
|
|
89
|
+
} catch {
|
|
90
|
+
console.warn(`[itd-api] запись хранилища пропущена: ключ ${JSON.stringify(encoded)} не декодируется`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Создаёт доменное хранилище нескольких сессий поверх enumerable key-value backend.
|
|
96
|
+
*
|
|
97
|
+
* Отдельный индекс аккаунтов не используется: `accounts()` перечисляет ключи backend. Поэтому
|
|
98
|
+
* запись сессии не может разойтись с индексом, но backend обязан эффективно поддерживать `keys`.
|
|
99
|
+
*/
|
|
100
|
+
function createMultiTokenStorage(store, options = {}) {
|
|
101
|
+
const prefix = options.prefix ?? ACCOUNT_KEY_PREFIX;
|
|
102
|
+
if (typeof prefix !== "string") throw new ItdConfigError("prefix MultiTokenStorage должен быть строкой");
|
|
103
|
+
const backend = createKeyValueStore(store);
|
|
104
|
+
if (!isEnumerableKeyValueStore(backend)) throw new ItdConfigError("MultiTokenStorage требует KeyValueStore с методом keys()");
|
|
105
|
+
const key = (account) => `${prefix}${encodeURIComponent(account)}`;
|
|
106
|
+
return {
|
|
107
|
+
async get(account) {
|
|
108
|
+
const session = await backend.get(key(account));
|
|
109
|
+
return session ? copySession(session) : null;
|
|
110
|
+
},
|
|
111
|
+
set: (account, session) => backend.set(key(account), copySession(session)),
|
|
112
|
+
clear: (account) => backend.delete(key(account)),
|
|
113
|
+
async accounts() {
|
|
114
|
+
return (await collectKeyValueStoreKeys(backend, prefix)).flatMap((value) => readAccountName(value.slice(prefix.length)) ?? []);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
//#endregion
|
|
119
|
+
export { scopedTokenStorage as a, isRestorableSession as i, controlledTokenStorage as n, createMultiTokenStorage as r, MemoryMultiTokenStorage as t };
|
|
120
|
+
|
|
121
|
+
//# sourceMappingURL=multi-storage-CkvTUC5m.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-storage-CkvTUC5m.js","names":["#store"],"sources":["../../src/session/multi-storage.ts"],"sourcesContent":["import { AUTH_FLAG_COOKIE, CookieJar } from '../core/cookies.js';\nimport { ItdConfigError } from '../core/errors.js';\nimport {\n collectKeyValueStoreKeys,\n createKeyValueStore,\n type EnumerableKeyValueStore,\n isEnumerableKeyValueStore,\n MemoryKeyValueStore,\n} from '../core/key-value-store.js';\nimport { copySession, type ItdSession, type TokenStorage } from './storage.js';\n\n/**\n * Хранилище сессий нескольких аккаунтов.\n *\n * Отличается от {@link TokenStorage} тем, что каждый метод получает **имя аккаунта**:\n * так адаптер сам решает, как строить ключ, и Redis, БД или связка ключей получают то,\n * что им нужно. Имя приходит ровно тем, под которым аккаунт заведён в {@link ItdAccounts}:\n * библиотека его не нормализует и не экранирует — префиксы, экранирование и ограничения\n * на длину ключа остаются за адаптером.\n *\n * Все методы могут быть как синхронными, так и асинхронными.\n *\n * @example Хранилище поверх enumerable Redis backend\n * ```ts\n * const storage = createMultiTokenStorage(withNamespace(redisStore, 'itd'));\n * ```\n */\nexport interface MultiTokenStorage {\n /** Прочитать сессию аккаунта. `null`, если её нет. */\n get(account: string): ItdSession | null | Promise<ItdSession | null>;\n /** Сохранить сессию аккаунта целиком. */\n set(account: string, session: ItdSession): void | Promise<void>;\n /** Удалить сессию аккаунта. Вызывается при выходе и при неудачном обновлении токена. */\n clear(account: string): void | Promise<void>;\n /**\n * Имена сохранённых записей — по ним `ItdAccounts.restore()` находит кандидатов\n * после перезапуска процесса.\n *\n * Список ведёт сам адаптер: у файлового и памятного он виден из самой записи,\n * а хранилищу «ключ — значение» придётся держать множество имён рядом с сессиями.\n * Перед восстановлением контейнер читает каждую запись и пропускает оставшийся после\n * выхода одинокий `deviceId`: без токена или refresh-сессии авторизоваться невозможно.\n * Пустой список означает лишь то, что кандидатов нет, — сами записи при этом могут быть\n * доступны по имени.\n */\n accounts(): readonly string[] | Promise<readonly string[]>;\n}\n\n/**\n * Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает\n * отдельный `ItdClient`, который про соседние аккаунты ничего не знает.\n */\nexport function scopedTokenStorage(storage: MultiTokenStorage, account: string): TokenStorage {\n return {\n get: () => storage.get(account),\n set: (session) => storage.set(account, session),\n clear: () => storage.clear(account),\n };\n}\n\n/**\n * Можно ли восстановить авторизованный аккаунт из записи.\n *\n * Один `deviceId` не считается сессией: он намеренно переживает выход, но не позволяет\n * выполнить ни одного авторизованного запроса.\n *\n * @internal\n */\nexport function isRestorableSession(session: ItdSession | null): session is ItdSession {\n if (!session) return false;\n if (session.accessToken || session.refreshToken) return true;\n\n const jar = new CookieJar();\n jar.deserialize(session.cookies);\n return jar.has(AUTH_FLAG_COOKIE);\n}\n\n/**\n * Управляемый срез хранилища для клиента аккаунта.\n *\n * После `revoke()` новые записи не доходят до общего хранилища, а `drain()` позволяет\n * дождаться уже начатых. Так удалённый клиент или завершившийся с опозданием refresh\n * не воскресят сессию после `removeAccount(..., { forget: true })`.\n *\n * @internal\n */\nexport interface ControlledTokenStorage {\n storage: TokenStorage;\n revoke(): void;\n drain(): Promise<void>;\n}\n\n/** @internal */\nexport function controlledTokenStorage(\n storage: MultiTokenStorage,\n account: string,\n): ControlledTokenStorage {\n let revoked = false;\n const pending = new Set<Promise<void>>();\n\n const mutate = (operation: () => void | Promise<void>): Promise<void> => {\n if (revoked) return Promise.resolve();\n\n const promise = Promise.resolve().then(operation);\n pending.add(promise);\n void promise.then(\n () => pending.delete(promise),\n () => pending.delete(promise),\n );\n return promise;\n };\n\n return {\n storage: {\n get: () => (revoked ? null : storage.get(account)),\n set: (session) => mutate(() => storage.set(account, session)),\n clear: () => mutate(() => storage.clear(account)),\n },\n revoke() {\n revoked = true;\n },\n async drain() {\n await Promise.all([...pending]);\n },\n };\n}\n\n/**\n * Мультихранилище в памяти процесса — вариант по умолчанию.\n *\n * Сессии теряются при перезапуске. Для долгоживущих ботов возьмите `FileMultiTokenStorage`\n * из `itd-api/node` либо соберите своё через {@link createMultiTokenStorage}.\n */\nexport class MemoryMultiTokenStorage implements MultiTokenStorage {\n readonly #store = new MemoryKeyValueStore<ItdSession>();\n\n constructor(initial?: Readonly<Record<string, ItdSession>> | null) {\n for (const [account, session] of Object.entries(initial ?? {})) {\n this.#store.set(accountKey(account), copySession(session));\n }\n }\n\n get(account: string): ItdSession | null {\n const session = this.#store.get(accountKey(account));\n return session ? copySession(session) : null;\n }\n\n set(account: string, session: ItdSession): void {\n this.#store.set(accountKey(account), copySession(session));\n }\n\n clear(account: string): void {\n this.#store.delete(accountKey(account));\n }\n\n accounts(): string[] {\n return this.#store\n .keys(ACCOUNT_KEY_PREFIX)\n .flatMap((key) => readAccountName(key.slice(ACCOUNT_KEY_PREFIX.length)) ?? []);\n }\n}\n\nconst ACCOUNT_KEY_PREFIX = 'accounts/';\n\nfunction accountKey(account: string): string {\n return `${ACCOUNT_KEY_PREFIX}${encodeURIComponent(account)}`;\n}\n\n/** Читает имя аккаунта из ключа. `undefined` — ключ не декодируется и запись пропускается. */\nfunction readAccountName(encoded: string): string | undefined {\n try {\n return decodeURIComponent(encoded);\n } catch {\n console.warn(\n `[itd-api] запись хранилища пропущена: ключ ${JSON.stringify(encoded)} не декодируется`,\n );\n return undefined;\n }\n}\n\n/** Настройки доменного адаптера нескольких сессий. */\nexport interface MultiTokenStorageAdapterOptions {\n /** Префикс ключей аккаунтов. По умолчанию `accounts/`. */\n prefix?: string | undefined;\n}\n\n/**\n * Создаёт доменное хранилище нескольких сессий поверх enumerable key-value backend.\n *\n * Отдельный индекс аккаунтов не используется: `accounts()` перечисляет ключи backend. Поэтому\n * запись сессии не может разойтись с индексом, но backend обязан эффективно поддерживать `keys`.\n */\nexport function createMultiTokenStorage(\n store: EnumerableKeyValueStore<ItdSession>,\n options: MultiTokenStorageAdapterOptions = {},\n): MultiTokenStorage {\n const prefix = options.prefix ?? ACCOUNT_KEY_PREFIX;\n if (typeof prefix !== 'string') {\n throw new ItdConfigError('prefix MultiTokenStorage должен быть строкой');\n }\n const backend = createKeyValueStore(store);\n if (!isEnumerableKeyValueStore(backend)) {\n throw new ItdConfigError('MultiTokenStorage требует KeyValueStore с методом keys()');\n }\n const key = (account: string) => `${prefix}${encodeURIComponent(account)}`;\n return {\n async get(account) {\n const session = await backend.get(key(account));\n return session ? copySession(session) : null;\n },\n set: (account, session) => backend.set(key(account), copySession(session)),\n clear: (account) => backend.delete(key(account)),\n async accounts() {\n const keys = await collectKeyValueStoreKeys(backend, prefix);\n return keys.flatMap((value) => readAccountName(value.slice(prefix.length)) ?? []);\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { o as EnumerableKeyValueStore, r as TokenStorage, t as ItdSession } from "./storage-C_eICCep.js";
|
|
2
|
+
//#region src/session/multi-storage.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Хранилище сессий нескольких аккаунтов.
|
|
5
|
+
*
|
|
6
|
+
* Отличается от {@link TokenStorage} тем, что каждый метод получает **имя аккаунта**:
|
|
7
|
+
* так адаптер сам решает, как строить ключ, и Redis, БД или связка ключей получают то,
|
|
8
|
+
* что им нужно. Имя приходит ровно тем, под которым аккаунт заведён в {@link ItdAccounts}:
|
|
9
|
+
* библиотека его не нормализует и не экранирует — префиксы, экранирование и ограничения
|
|
10
|
+
* на длину ключа остаются за адаптером.
|
|
11
|
+
*
|
|
12
|
+
* Все методы могут быть как синхронными, так и асинхронными.
|
|
13
|
+
*
|
|
14
|
+
* @example Хранилище поверх enumerable Redis backend
|
|
15
|
+
* ```ts
|
|
16
|
+
* const storage = createMultiTokenStorage(withNamespace(redisStore, 'itd'));
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
interface MultiTokenStorage {
|
|
20
|
+
/** Прочитать сессию аккаунта. `null`, если её нет. */
|
|
21
|
+
get(account: string): ItdSession | null | Promise<ItdSession | null>;
|
|
22
|
+
/** Сохранить сессию аккаунта целиком. */
|
|
23
|
+
set(account: string, session: ItdSession): void | Promise<void>;
|
|
24
|
+
/** Удалить сессию аккаунта. Вызывается при выходе и при неудачном обновлении токена. */
|
|
25
|
+
clear(account: string): void | Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Имена сохранённых записей — по ним `ItdAccounts.restore()` находит кандидатов
|
|
28
|
+
* после перезапуска процесса.
|
|
29
|
+
*
|
|
30
|
+
* Список ведёт сам адаптер: у файлового и памятного он виден из самой записи,
|
|
31
|
+
* а хранилищу «ключ — значение» придётся держать множество имён рядом с сессиями.
|
|
32
|
+
* Перед восстановлением контейнер читает каждую запись и пропускает оставшийся после
|
|
33
|
+
* выхода одинокий `deviceId`: без токена или refresh-сессии авторизоваться невозможно.
|
|
34
|
+
* Пустой список означает лишь то, что кандидатов нет, — сами записи при этом могут быть
|
|
35
|
+
* доступны по имени.
|
|
36
|
+
*/
|
|
37
|
+
accounts(): readonly string[] | Promise<readonly string[]>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает
|
|
41
|
+
* отдельный `ItdClient`, который про соседние аккаунты ничего не знает.
|
|
42
|
+
*/
|
|
43
|
+
declare function scopedTokenStorage(storage: MultiTokenStorage, account: string): TokenStorage;
|
|
44
|
+
/**
|
|
45
|
+
* Мультихранилище в памяти процесса — вариант по умолчанию.
|
|
46
|
+
*
|
|
47
|
+
* Сессии теряются при перезапуске. Для долгоживущих ботов возьмите `FileMultiTokenStorage`
|
|
48
|
+
* из `itd-api/node` либо соберите своё через {@link createMultiTokenStorage}.
|
|
49
|
+
*/
|
|
50
|
+
declare class MemoryMultiTokenStorage implements MultiTokenStorage {
|
|
51
|
+
#private;
|
|
52
|
+
constructor(initial?: Readonly<Record<string, ItdSession>> | null);
|
|
53
|
+
get(account: string): ItdSession | null;
|
|
54
|
+
set(account: string, session: ItdSession): void;
|
|
55
|
+
clear(account: string): void;
|
|
56
|
+
accounts(): string[];
|
|
57
|
+
}
|
|
58
|
+
/** Настройки доменного адаптера нескольких сессий. */
|
|
59
|
+
interface MultiTokenStorageAdapterOptions {
|
|
60
|
+
/** Префикс ключей аккаунтов. По умолчанию `accounts/`. */
|
|
61
|
+
prefix?: string | undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Создаёт доменное хранилище нескольких сессий поверх enumerable key-value backend.
|
|
65
|
+
*
|
|
66
|
+
* Отдельный индекс аккаунтов не используется: `accounts()` перечисляет ключи backend. Поэтому
|
|
67
|
+
* запись сессии не может разойтись с индексом, но backend обязан эффективно поддерживать `keys`.
|
|
68
|
+
*/
|
|
69
|
+
declare function createMultiTokenStorage(store: EnumerableKeyValueStore<ItdSession>, options?: MultiTokenStorageAdapterOptions): MultiTokenStorage;
|
|
70
|
+
//#endregion
|
|
71
|
+
export { scopedTokenStorage as a, createMultiTokenStorage as i, MultiTokenStorage as n, MultiTokenStorageAdapterOptions as r, MemoryMultiTokenStorage as t };
|
|
72
|
+
//# sourceMappingURL=multi-storage-DccjD7Ww.d.ts.map
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
const require_errors = require("./errors-DfU8M5eS.cjs");
|
|
2
|
+
//#region src/core/attachments/contracts.ts
|
|
3
|
+
/** Способ передачи содержимого вложения. */
|
|
4
|
+
const FileTransferMode = Object.freeze({
|
|
5
|
+
/** Сначала получить файл целиком, затем отправить его как `Blob`. */
|
|
6
|
+
Buffer: "buffer",
|
|
7
|
+
/** Передавать данные по мере чтения, не собирая файл целиком в памяти. */
|
|
8
|
+
Stream: "stream"
|
|
9
|
+
});
|
|
10
|
+
/** Предел размера файла, скачиваемого по адресу, — 100 МиБ. */
|
|
11
|
+
const DEFAULT_URL_FILE_MAX_BYTES = 104857600;
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/core/attachments/limits.ts
|
|
14
|
+
/** Проверяет числовую границу до обращения к источнику. */
|
|
15
|
+
function optionalBytes(value, name) {
|
|
16
|
+
if (value === void 0) return void 0;
|
|
17
|
+
if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) throw new require_errors.ItdConfigError(`${name} должен быть неотрицательным целым числом, получено: ${value}`);
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
/** Создаёт типизированную ошибку превышения размера вложения. */
|
|
21
|
+
function fileTooLarge(url, limit, actual) {
|
|
22
|
+
const source = url ? ` по адресу ${url}` : "";
|
|
23
|
+
return new require_errors.ItdFileError(`файл${source} больше предела в ${limit} байт: ${actual}`, {
|
|
24
|
+
reason: require_errors.ItdFileErrorReason.TooLarge,
|
|
25
|
+
...url ? { url } : {},
|
|
26
|
+
limit,
|
|
27
|
+
actual
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/core/attachments/bounded-stream.ts
|
|
32
|
+
/** Потоки, на которые уже наложены счётчик размера и backpressure. */
|
|
33
|
+
const BOUNDED_FILE_STREAMS = /* @__PURE__ */ new WeakSet();
|
|
34
|
+
/** Приводит произвольный чанк потока к байтам. */
|
|
35
|
+
function asBytes(value) {
|
|
36
|
+
return value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
37
|
+
}
|
|
38
|
+
/** Проверяет значение без обращения к методам потенциально чужого объекта. */
|
|
39
|
+
function isReadableByteStream(value) {
|
|
40
|
+
return typeof ReadableStream !== "undefined" && value instanceof ReadableStream;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Ограничивает размер и очередь потока. Отмена результирующего потока отменяет исходный.
|
|
44
|
+
*
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
function boundedFileStream(source, options = {}) {
|
|
48
|
+
const maxBytes = optionalBytes(options.maxBytes, "maxBytes");
|
|
49
|
+
const streamBufferBytes = optionalBytes(options.streamBufferBytes ?? 4194304, "streamBufferBytes") ?? 4194304;
|
|
50
|
+
if (streamBufferBytes === 0) throw new require_errors.ItdConfigError("streamBufferBytes должен быть больше нуля");
|
|
51
|
+
const reader = source.getReader();
|
|
52
|
+
let total = 0;
|
|
53
|
+
let finished = false;
|
|
54
|
+
let abortListener;
|
|
55
|
+
const cleanup = () => {
|
|
56
|
+
if (abortListener) options.signal?.removeEventListener("abort", abortListener);
|
|
57
|
+
abortListener = void 0;
|
|
58
|
+
};
|
|
59
|
+
const stream = new ReadableStream({
|
|
60
|
+
start(controller) {
|
|
61
|
+
if (!options.signal) return;
|
|
62
|
+
abortListener = () => {
|
|
63
|
+
finished = true;
|
|
64
|
+
reader.cancel(options.signal?.reason).catch(() => {});
|
|
65
|
+
controller.error(options.signal?.reason ?? new DOMException("Aborted", "AbortError"));
|
|
66
|
+
cleanup();
|
|
67
|
+
};
|
|
68
|
+
if (options.signal.aborted) abortListener();
|
|
69
|
+
else options.signal.addEventListener("abort", abortListener, { once: true });
|
|
70
|
+
},
|
|
71
|
+
async pull(controller) {
|
|
72
|
+
if (finished) return;
|
|
73
|
+
try {
|
|
74
|
+
const next = await reader.read();
|
|
75
|
+
if (finished) return;
|
|
76
|
+
if (next.done) {
|
|
77
|
+
finished = true;
|
|
78
|
+
cleanup();
|
|
79
|
+
controller.close();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const chunk = asBytes(next.value);
|
|
83
|
+
total += chunk.byteLength;
|
|
84
|
+
if (maxBytes !== void 0 && total > maxBytes) {
|
|
85
|
+
finished = true;
|
|
86
|
+
await reader.cancel().catch(() => {});
|
|
87
|
+
cleanup();
|
|
88
|
+
controller.error(fileTooLarge(options.url, maxBytes, total));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
controller.enqueue(chunk);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
finished = true;
|
|
94
|
+
cleanup();
|
|
95
|
+
if (error instanceof require_errors.ItdFileError) {
|
|
96
|
+
controller.error(error);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
controller.error(new require_errors.ItdFileError("не удалось прочитать поток вложения", {
|
|
100
|
+
reason: require_errors.ItdFileErrorReason.Read,
|
|
101
|
+
...options.url ? { url: options.url } : {},
|
|
102
|
+
retryable: options.retryableRead ?? false,
|
|
103
|
+
cause: error
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
async cancel(reason) {
|
|
108
|
+
finished = true;
|
|
109
|
+
cleanup();
|
|
110
|
+
await reader.cancel(reason).catch(() => {});
|
|
111
|
+
}
|
|
112
|
+
}, {
|
|
113
|
+
highWaterMark: streamBufferBytes,
|
|
114
|
+
size: (chunk) => chunk.byteLength
|
|
115
|
+
});
|
|
116
|
+
BOUNDED_FILE_STREAMS.add(stream);
|
|
117
|
+
return stream;
|
|
118
|
+
}
|
|
119
|
+
/** Наложены ли на поток ограничения библиотеки. @internal */
|
|
120
|
+
function isBoundedFileStream(stream) {
|
|
121
|
+
return BOUNDED_FILE_STREAMS.has(stream);
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/core/attachments/options.ts
|
|
125
|
+
/** Проверяет и дополняет настройки потока. @internal */
|
|
126
|
+
function resolveFileStreamOptions(options, defaultMaxBytes) {
|
|
127
|
+
const mode = options.mode ?? FileTransferMode.Buffer;
|
|
128
|
+
if (mode !== FileTransferMode.Buffer && mode !== FileTransferMode.Stream) throw new require_errors.ItdConfigError(`mode вложения должен быть 'buffer' или 'stream', получено: ${mode}`);
|
|
129
|
+
if (mode === FileTransferMode.Stream && typeof ReadableStream === "undefined") throw new require_errors.ItdConfigError("эта среда не поддерживает ReadableStream; используйте mode: 'buffer'");
|
|
130
|
+
const maxBytes = optionalBytes(options.maxBytes ?? defaultMaxBytes, "maxBytes");
|
|
131
|
+
const streamBufferBytes = optionalBytes(options.streamBufferBytes ?? 4194304, "streamBufferBytes") ?? 4194304;
|
|
132
|
+
if (streamBufferBytes === 0) throw new require_errors.ItdConfigError("streamBufferBytes должен быть больше нуля");
|
|
133
|
+
return {
|
|
134
|
+
mode,
|
|
135
|
+
maxBytes,
|
|
136
|
+
streamBufferBytes
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
Object.defineProperty(exports, "DEFAULT_URL_FILE_MAX_BYTES", {
|
|
141
|
+
enumerable: true,
|
|
142
|
+
get: function() {
|
|
143
|
+
return DEFAULT_URL_FILE_MAX_BYTES;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
Object.defineProperty(exports, "FileTransferMode", {
|
|
147
|
+
enumerable: true,
|
|
148
|
+
get: function() {
|
|
149
|
+
return FileTransferMode;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
Object.defineProperty(exports, "boundedFileStream", {
|
|
153
|
+
enumerable: true,
|
|
154
|
+
get: function() {
|
|
155
|
+
return boundedFileStream;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
Object.defineProperty(exports, "fileTooLarge", {
|
|
159
|
+
enumerable: true,
|
|
160
|
+
get: function() {
|
|
161
|
+
return fileTooLarge;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
Object.defineProperty(exports, "isBoundedFileStream", {
|
|
165
|
+
enumerable: true,
|
|
166
|
+
get: function() {
|
|
167
|
+
return isBoundedFileStream;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
Object.defineProperty(exports, "isReadableByteStream", {
|
|
171
|
+
enumerable: true,
|
|
172
|
+
get: function() {
|
|
173
|
+
return isReadableByteStream;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
Object.defineProperty(exports, "optionalBytes", {
|
|
177
|
+
enumerable: true,
|
|
178
|
+
get: function() {
|
|
179
|
+
return optionalBytes;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
Object.defineProperty(exports, "resolveFileStreamOptions", {
|
|
183
|
+
enumerable: true,
|
|
184
|
+
get: function() {
|
|
185
|
+
return resolveFileStreamOptions;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
//# sourceMappingURL=options-Dg5N3r1V.cjs.map
|