itd-api 0.9.0 → 0.9.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 +4 -4
- 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 +198 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -65
- package/dist/index.d.ts +11 -65
- package/dist/index.js +199 -39
- 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-CqtkP65d.d.ts → events-BCn5LRnT.d.cts} +3 -5
- package/dist/shared/{events-C5wnTXPT.js → events-BIBO2Jif.js} +4 -4
- package/dist/shared/{events-C5wnTXPT.js.map → events-BIBO2Jif.js.map} +1 -1
- package/dist/shared/{events-FZfnEez0.cjs → events-BsRm_Fba.cjs} +3 -9
- package/dist/shared/{events-FZfnEez0.cjs.map → events-BsRm_Fba.cjs.map} +1 -1
- package/dist/shared/{events-DGAWlCaI.d.cts → events-t1uGgpkf.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-hiWFh2s4.js → render--ZXmYEf-.js} +131 -100
- package/dist/shared/render--ZXmYEf-.js.map +1 -0
- package/dist/shared/{render-B4qis7Sb.d.ts → render-BdnBV-aT.d.ts} +112 -16
- package/dist/shared/{render-DDwy2Ame.d.cts → render-DA83gS0f.d.cts} +112 -16
- package/dist/shared/{render-Dqi8Owqk.cjs → render-DqVzx68r.cjs} +129 -104
- package/dist/shared/render-DqVzx68r.cjs.map +1 -0
- 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-XBsMdAcv.d.ts → url-Dbj6V6fk.d.cts} +21 -5
- package/dist/shared/{url-XBsMdAcv.d.cts → url-Dbj6V6fk.d.ts} +21 -5
- package/dist/shared/{url-Bsb3xE5U.cjs → url-Doc0XXW_.cjs} +274 -86
- package/dist/shared/url-Doc0XXW_.cjs.map +1 -0
- package/dist/shared/{url-DP33mp3s.js → url-i2f-X1hX.js} +274 -86
- package/dist/shared/url-i2f-X1hX.js.map +1 -0
- package/dist/web/index.cjs +1 -1
- package/dist/web/index.js +1 -1
- package/package.json +4 -4
- 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-Dqi8Owqk.cjs.map +0 -1
- package/dist/shared/render-hiWFh2s4.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-Bsb3xE5U.cjs.map +0 -1
- package/dist/shared/url-DP33mp3s.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Ft as RuntimeOptions, It as QueryParams, Vt as ItdClock, a as NotificationEvent, cn as EventChannelStatus, ct as Listener, hn as NotificationType, lt as Unsubscribe, nt as AuthIdentity, rt as AuthProvider, s as Notification, tt as ClientConnection, xt as Logger } from "./url-Dbj6V6fk.js";
|
|
2
2
|
//#region src/events/transports/transport.d.ts
|
|
3
3
|
/** Идентификаторы операций транспорта уведомлений. */
|
|
4
4
|
type EventOperationId = 'events.notifications.poll.updates' | 'events.notifications.poll.unread';
|
|
@@ -278,8 +278,6 @@ interface EventChannelOptions<C extends EventContext = EventContext> extends Rec
|
|
|
278
278
|
/** Переподключаться при восстановлении сети. По умолчанию `true`. */
|
|
279
279
|
reconnectOnOnline?: boolean | undefined;
|
|
280
280
|
}
|
|
281
|
-
/** Проверяет настройки общей механики потока. */
|
|
282
|
-
declare function resolveEventChannelOptions<C extends EventContext>(options?: EventChannelOptions<C>): Readonly<EventChannelOptions<C>>;
|
|
283
281
|
/**
|
|
284
282
|
* Общая механика потока событий: соединение, переподключение с задержкой, обновление
|
|
285
283
|
* токена, реакция на среду, статусы и очередь обработчиков.
|
|
@@ -694,5 +692,5 @@ declare class WebSocketTransport implements EventTransport {
|
|
|
694
692
|
connect(context: EventTransportContext): Promise<void>;
|
|
695
693
|
}
|
|
696
694
|
//#endregion
|
|
697
|
-
export {
|
|
698
|
-
//# sourceMappingURL=events-
|
|
695
|
+
export { EventTransportContext as $, EventHandler as A, NotificationEventContext as B, EventChannelDeps as C, EventSession as D, EventEnqueueOptions as E, EventSequentializer as F, NotificationOfType as G, NotificationEventOfType as H, EventTypeGuard as I, NotificationUpdateOrigin as J, NotificationUpdate as K, runEventMiddleware as L, EventMiddlewareObject as M, EventNext as N, EventSyncReason as O, EventPredicate as P, EventTransport as Q, EventContext as R, EventChannel as S, EventChannelOptions as T, NotificationEventSelector as U, NotificationEventFilter as V, NotificationEventsUpdate as W, UnknownNotificationUpdate as X, NotificationUpdateType as Y, UnreadCountUpdate as Z, createNotificationEventsClient as _, WebSocketTransportOptions as a, NotificationEventsOptions as b, EventErrorContext as c, EventMiddlewareLike as d, EventTransportFrame as et, EventRouteTable as f, NotificationEventsClientOptions as g, NotificationEventsClient as h, WebSocketTransport as i, EventMiddleware as j, ReconnectOptions as k, EventFilter as l, EventRouter as m, WebSocketLike as n, EventComposer as o, EventRouteSelector as p, NotificationUpdateOfType as q, WebSocketOpenFailureClassifier as r, EventErrorBoundary as s, WebSocketImplementationOptions as t, UnauthorizedStreamError as tt, EventMiddlewareGroup as u, NotificationEvents as v, EventChannelEvents as w, NotificationEventsTransport as x, NotificationEventsMap as y, NotificationContext as z };
|
|
696
|
+
//# sourceMappingURL=events-t1uGgpkf.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as ItdConfigError } from "./errors-GI10kZxk.js";
|
|
2
|
-
import { o as AUTH_FLAG_COOKIE, s as CookieJar } from "./redact-
|
|
2
|
+
import { o as AUTH_FLAG_COOKIE, s as CookieJar } from "./redact-DJV0XM3n.js";
|
|
3
3
|
import { a as isEnumerableKeyValueStore, n as collectKeyValueStoreKeys, r as createKeyValueStore, t as MemoryKeyValueStore } from "./key-value-store-Bt2PSMxY.js";
|
|
4
|
-
import { n as copySession } from "./storage-
|
|
4
|
+
import { n as copySession } from "./storage-DiDe0PZM.js";
|
|
5
5
|
//#region src/session/multi-storage.ts
|
|
6
6
|
/**
|
|
7
7
|
* Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает
|
|
@@ -22,12 +22,17 @@ function scopedTokenStorage(storage, account) {
|
|
|
22
22
|
*
|
|
23
23
|
* @internal
|
|
24
24
|
*/
|
|
25
|
-
function isRestorableSession(session) {
|
|
26
|
-
if (
|
|
27
|
-
|
|
25
|
+
function isRestorableSession(session, baseUrl) {
|
|
26
|
+
if (typeof session !== "object" || session === null || Array.isArray(session)) return false;
|
|
27
|
+
const accessToken = session.accessToken;
|
|
28
|
+
const refreshToken = session.refreshToken;
|
|
29
|
+
if (typeof accessToken === "string" && accessToken.trim() !== "") return true;
|
|
30
|
+
if (typeof refreshToken === "string" && refreshToken.trim() !== "") return true;
|
|
31
|
+
const cookies = session.cookies;
|
|
32
|
+
if (!Array.isArray(cookies)) return false;
|
|
28
33
|
const jar = new CookieJar();
|
|
29
|
-
jar.deserialize(
|
|
30
|
-
return jar.has(AUTH_FLAG_COOKIE);
|
|
34
|
+
jar.deserialize(cookies.filter((entry) => typeof entry === "string"));
|
|
35
|
+
return jar.has(AUTH_FLAG_COOKIE, baseUrl);
|
|
31
36
|
}
|
|
32
37
|
/** @internal */
|
|
33
38
|
function controlledTokenStorage(storage, account) {
|
|
@@ -119,4 +124,4 @@ function createMultiTokenStorage(store, options = {}) {
|
|
|
119
124
|
//#endregion
|
|
120
125
|
export { scopedTokenStorage as a, isRestorableSession as i, controlledTokenStorage as n, createMultiTokenStorage as r, MemoryMultiTokenStorage as t };
|
|
121
126
|
|
|
122
|
-
//# sourceMappingURL=multi-storage-
|
|
127
|
+
//# sourceMappingURL=multi-storage-C3Ngtop1.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-storage-C3Ngtop1.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: unknown, baseUrl: string): session is ItdSession {\n if (typeof session !== 'object' || session === null || Array.isArray(session)) return false;\n const accessToken = (session as { accessToken?: unknown }).accessToken;\n const refreshToken = (session as { refreshToken?: unknown }).refreshToken;\n if (typeof accessToken === 'string' && accessToken.trim() !== '') return true;\n if (typeof refreshToken === 'string' && refreshToken.trim() !== '') return true;\n\n const cookies = (session as { cookies?: unknown }).cookies;\n if (!Array.isArray(cookies)) return false;\n\n const jar = new CookieJar();\n jar.deserialize(cookies.filter((entry): entry is string => typeof entry === 'string'));\n return jar.has(AUTH_FLAG_COOKIE, baseUrl);\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":";;;;;;;;;AAoDA,SAAgB,mBAAmB,SAA4B,SAA+B;CAC5F,OAAO;EACL,WAAW,QAAQ,IAAI,OAAO;EAC9B,MAAM,YAAY,QAAQ,IAAI,SAAS,OAAO;EAC9C,aAAa,QAAQ,MAAM,OAAO;CACpC;AACF;;;;;;;;;AAUA,SAAgB,oBAAoB,SAAkB,SAAwC;CAC5F,IAAI,OAAO,YAAY,YAAY,YAAY,QAAQ,MAAM,QAAQ,OAAO,GAAG,OAAO;CACtF,MAAM,cAAe,QAAsC;CAC3D,MAAM,eAAgB,QAAuC;CAC7D,IAAI,OAAO,gBAAgB,YAAY,YAAY,KAAK,MAAM,IAAI,OAAO;CACzE,IAAI,OAAO,iBAAiB,YAAY,aAAa,KAAK,MAAM,IAAI,OAAO;CAE3E,MAAM,UAAW,QAAkC;CACnD,IAAI,CAAC,MAAM,QAAQ,OAAO,GAAG,OAAO;CAEpC,MAAM,MAAM,IAAI,UAAU;CAC1B,IAAI,YAAY,QAAQ,QAAQ,UAA2B,OAAO,UAAU,QAAQ,CAAC;CACrF,OAAO,IAAI,IAAI,kBAAkB,OAAO;AAC1C;;AAkBA,SAAgB,uBACd,SACA,SACwB;CACxB,IAAI,UAAU;CACd,MAAM,0BAAU,IAAI,IAAmB;CAEvC,MAAM,UAAU,cAAyD;EACvE,IAAI,SAAS,OAAO,QAAQ,QAAQ;EAEpC,MAAM,UAAU,QAAQ,QAAQ,CAAC,CAAC,KAAK,SAAS;EAChD,QAAQ,IAAI,OAAO;EACnB,QAAa,WACL,QAAQ,OAAO,OAAO,SACtB,QAAQ,OAAO,OAAO,CAC9B;EACA,OAAO;CACT;CAEA,OAAO;EACL,SAAS;GACP,WAAY,UAAU,OAAO,QAAQ,IAAI,OAAO;GAChD,MAAM,YAAY,aAAa,QAAQ,IAAI,SAAS,OAAO,CAAC;GAC5D,aAAa,aAAa,QAAQ,MAAM,OAAO,CAAC;EAClD;EACA,SAAS;GACP,UAAU;EACZ;EACA,MAAM,QAAQ;GACZ,MAAM,QAAQ,IAAI,CAAC,GAAG,OAAO,CAAC;EAChC;CACF;AACF;;;;;;;AAQA,IAAa,0BAAb,MAAkE;CAChE,SAAkB,IAAI,oBAAgC;CAEtD,YAAY,SAAuD;EACjE,KAAK,MAAM,CAAC,SAAS,YAAY,OAAO,QAAQ,WAAW,CAAC,CAAC,GAC3D,KAAKA,OAAO,IAAI,WAAW,OAAO,GAAG,YAAY,OAAO,CAAC;CAE7D;CAEA,IAAI,SAAoC;EACtC,MAAM,UAAU,KAAKA,OAAO,IAAI,WAAW,OAAO,CAAC;EACnD,OAAO,UAAU,YAAY,OAAO,IAAI;CAC1C;CAEA,IAAI,SAAiB,SAA2B;EAC9C,KAAKA,OAAO,IAAI,WAAW,OAAO,GAAG,YAAY,OAAO,CAAC;CAC3D;CAEA,MAAM,SAAuB;EAC3B,KAAKA,OAAO,OAAO,WAAW,OAAO,CAAC;CACxC;CAEA,WAAqB;EACnB,OAAO,KAAKA,OACT,KAAK,kBAAkB,CAAC,CACxB,SAAS,QAAQ,gBAAgB,IAAI,MAAM,mBAAmB,MAAM,CAAC,KAAK,CAAC,CAAC;CACjF;AACF;AAEA,MAAM,qBAAqB;AAE3B,SAAS,WAAW,SAAyB;CAC3C,OAAO,GAAG,qBAAqB,mBAAmB,OAAO;AAC3D;;AAGA,SAAS,gBAAgB,SAAqC;CAC5D,IAAI;EACF,OAAO,mBAAmB,OAAO;CACnC,QAAQ;EACN,QAAQ,KACN,8CAA8C,KAAK,UAAU,OAAO,EAAE,iBACxE;EACA;CACF;AACF;;;;;;;AAcA,SAAgB,wBACd,OACA,UAA2C,CAAC,GACzB;CACnB,MAAM,SAAS,QAAQ,UAAU;CACjC,IAAI,OAAO,WAAW,UACpB,MAAM,IAAI,eAAe,8CAA8C;CAEzE,MAAM,UAAU,oBAAoB,KAAK;CACzC,IAAI,CAAC,0BAA0B,OAAO,GACpC,MAAM,IAAI,eAAe,0DAA0D;CAErF,MAAM,OAAO,YAAoB,GAAG,SAAS,mBAAmB,OAAO;CACvE,OAAO;EACL,MAAM,IAAI,SAAS;GACjB,MAAM,UAAU,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC;GAC9C,OAAO,UAAU,YAAY,OAAO,IAAI;EAC1C;EACA,MAAM,SAAS,YAAY,QAAQ,IAAI,IAAI,OAAO,GAAG,YAAY,OAAO,CAAC;EACzE,QAAQ,YAAY,QAAQ,OAAO,IAAI,OAAO,CAAC;EAC/C,MAAM,WAAW;GAEf,QAAO,MADY,yBAAyB,SAAS,MAAM,EAAA,CAC/C,SAAS,UAAU,gBAAgB,MAAM,MAAM,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;EAClF;CACF;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const require_errors = require("./errors-BmP3TKoW.cjs");
|
|
2
|
-
const require_redact = require("./redact-
|
|
2
|
+
const require_redact = require("./redact-lAmck1Z6.cjs");
|
|
3
3
|
const require_key_value_store = require("./key-value-store-B5GYVEYZ.cjs");
|
|
4
|
-
const require_storage = require("./storage-
|
|
4
|
+
const require_storage = require("./storage-CZJZoaz9.cjs");
|
|
5
5
|
//#region src/session/multi-storage.ts
|
|
6
6
|
/**
|
|
7
7
|
* Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает
|
|
@@ -22,12 +22,17 @@ function scopedTokenStorage(storage, account) {
|
|
|
22
22
|
*
|
|
23
23
|
* @internal
|
|
24
24
|
*/
|
|
25
|
-
function isRestorableSession(session) {
|
|
26
|
-
if (
|
|
27
|
-
|
|
25
|
+
function isRestorableSession(session, baseUrl) {
|
|
26
|
+
if (typeof session !== "object" || session === null || Array.isArray(session)) return false;
|
|
27
|
+
const accessToken = session.accessToken;
|
|
28
|
+
const refreshToken = session.refreshToken;
|
|
29
|
+
if (typeof accessToken === "string" && accessToken.trim() !== "") return true;
|
|
30
|
+
if (typeof refreshToken === "string" && refreshToken.trim() !== "") return true;
|
|
31
|
+
const cookies = session.cookies;
|
|
32
|
+
if (!Array.isArray(cookies)) return false;
|
|
28
33
|
const jar = new require_redact.CookieJar();
|
|
29
|
-
jar.deserialize(
|
|
30
|
-
return jar.has(require_redact.AUTH_FLAG_COOKIE);
|
|
34
|
+
jar.deserialize(cookies.filter((entry) => typeof entry === "string"));
|
|
35
|
+
return jar.has(require_redact.AUTH_FLAG_COOKIE, baseUrl);
|
|
31
36
|
}
|
|
32
37
|
/** @internal */
|
|
33
38
|
function controlledTokenStorage(storage, account) {
|
|
@@ -148,4 +153,4 @@ Object.defineProperty(exports, "scopedTokenStorage", {
|
|
|
148
153
|
}
|
|
149
154
|
});
|
|
150
155
|
|
|
151
|
-
//# sourceMappingURL=multi-storage-
|
|
156
|
+
//# sourceMappingURL=multi-storage-s-fPtjoy.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-storage-s-fPtjoy.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: unknown, baseUrl: string): session is ItdSession {\n if (typeof session !== 'object' || session === null || Array.isArray(session)) return false;\n const accessToken = (session as { accessToken?: unknown }).accessToken;\n const refreshToken = (session as { refreshToken?: unknown }).refreshToken;\n if (typeof accessToken === 'string' && accessToken.trim() !== '') return true;\n if (typeof refreshToken === 'string' && refreshToken.trim() !== '') return true;\n\n const cookies = (session as { cookies?: unknown }).cookies;\n if (!Array.isArray(cookies)) return false;\n\n const jar = new CookieJar();\n jar.deserialize(cookies.filter((entry): entry is string => typeof entry === 'string'));\n return jar.has(AUTH_FLAG_COOKIE, baseUrl);\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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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_errors = require("./errors-BmP3TKoW.cjs");
|
|
2
|
-
const require_redact = require("./redact-
|
|
2
|
+
const require_redact = require("./redact-lAmck1Z6.cjs");
|
|
3
3
|
//#region src/core/attachments/contracts.ts
|
|
4
4
|
/** Способ передачи содержимого вложения. */
|
|
5
5
|
const FileTransferMode = Object.freeze({
|
|
@@ -188,4 +188,4 @@ Object.defineProperty(exports, "resolveFileStreamOptions", {
|
|
|
188
188
|
}
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
-
//# sourceMappingURL=options-
|
|
191
|
+
//# sourceMappingURL=options-DjEtbVaf.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options-DOJYtoti.cjs","names":["ItdConfigError","redactUrl","ItdFileError","ItdFileErrorReason","ItdConfigError","ItdFileError","ItdFileErrorReason","redactUrl","redactErrorCause","ItdConfigError"],"sources":["../../src/core/attachments/contracts.ts","../../src/core/attachments/limits.ts","../../src/core/attachments/bounded-stream.ts","../../src/core/attachments/options.ts"],"sourcesContent":["/** Способ передачи содержимого вложения. */\nexport const FileTransferMode = Object.freeze({\n /** Сначала получить файл целиком, затем отправить его как `Blob`. */\n Buffer: 'buffer',\n /** Передавать данные по мере чтения, не собирая файл целиком в памяти. */\n Stream: 'stream',\n} as const);\nexport type FileTransferMode = (typeof FileTransferMode)[keyof typeof FileTransferMode];\n\n/** Размер очереди потокового вложения по умолчанию — 4 МиБ. */\nexport const DEFAULT_FILE_STREAM_BUFFER_BYTES = 4 * 1024 * 1024;\n\n/** Предел размера файла, скачиваемого по адресу, — 100 МиБ. */\nexport const DEFAULT_URL_FILE_MAX_BYTES = 100 * 1024 * 1024;\n\n/** Что доступно источнику при открытии файла. */\nexport interface FileContext {\n /** Общая отмена текущей попытки, включая её таймаут. */\n signal?: AbortSignal | undefined;\n /** `fetch` клиента — с переданными пользователем настройками. */\n fetch: typeof fetch;\n /** Номер попытки отправки, начиная с 1. */\n attempt: number;\n}\n\n/** Готовое содержимое файла. */\nexport interface FileContent {\n file: Blob | ArrayBuffer | Uint8Array;\n /** Имя влияет на определение MIME, если `contentType` не задан. */\n filename?: string | undefined;\n /** MIME без параметров. Если не указан, определяется по имени или `Blob`. */\n contentType?: string | undefined;\n}\n\n/** Открытый поток файла. */\nexport interface FileStreamContent {\n stream: ReadableStream<Uint8Array>;\n filename?: string | undefined;\n contentType?: string | undefined;\n /** Размер, если он известен до чтения. */\n size?: number | undefined;\n /** Освобождает внешний ресурс после завершения попытки. */\n close?: (() => void | Promise<void>) | undefined;\n}\n\n/** Буферный источник, который вычисляется непосредственно перед первой попыткой. */\nexport interface LazyFile {\n load: (context: FileContext) => FileContent | Promise<FileContent>;\n}\n\n/**\n * Повторяемый потоковый источник.\n *\n * `open` вызывается для каждой попытки отправки. Он обязан возвращать новый поток:\n * уже прочитанный `ReadableStream` повторно использовать нельзя.\n */\nexport interface StreamFile {\n open: (context: FileContext) => FileStreamContent | Promise<FileStreamContent>;\n}\n\n/** Общие настройки потоковой передачи. */\nexport interface FileStreamOptions {\n /** Буферный или потоковый режим. По умолчанию `'buffer'`. */\n mode?: FileTransferMode | undefined;\n /** Максимальный размер файла. Без значения размер не ограничивается. */\n maxBytes?: number | undefined;\n /**\n * Верхняя граница очереди, которой управляет библиотека. По умолчанию 4 МиБ.\n *\n * Сетевой рантайм может иметь собственные внутренние буферы сверх этого значения.\n */\n streamBufferBytes?: number | undefined;\n}\n\n/** Файл, который нужно получить по HTTP(S). */\nexport interface UrlFile extends UrlFileOptions {\n url: string;\n}\n\n/** Настройки файла по HTTP(S). */\nexport interface UrlFileOptions extends FileStreamOptions {\n filename?: string | undefined;\n /** По умолчанию берётся из `Content-Type` ответа. */\n contentType?: string | undefined;\n}\n\n/** Файл для загрузки. Голый поток не принимается, потому что его нельзя повторить. */\nexport type FileInput =\n | Blob\n | ArrayBuffer\n | Uint8Array\n | FileContent\n | UrlFile\n | LazyFile\n | StreamFile;\n\n/** Настройки фабрики пользовательского потока. */\nexport interface FromStreamOptions extends Omit<FileStreamOptions, 'mode'> {\n filename?: string | undefined;\n contentType?: string | undefined;\n /** Размер, если он известен заранее. */\n size?: number | undefined;\n}\n","import { ItdConfigError, ItdFileError, ItdFileErrorReason } from '../errors.js';\nimport { redactUrl } from '../redact.js';\n\n/** Проверяет числовую границу до обращения к источнику. */\nexport function optionalBytes(value: number | undefined, name: string): number | undefined {\n if (value === undefined) return undefined;\n if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {\n throw new ItdConfigError(\n `${name} должен быть неотрицательным целым числом, получено: ${value}`,\n );\n }\n return value;\n}\n\n/** Создаёт типизированную ошибку превышения размера вложения. */\nexport function fileTooLarge(url: string | undefined, limit: number, actual: number): ItdFileError {\n const safeUrl = url ? redactUrl(url) : undefined;\n const source = safeUrl ? ` по адресу ${safeUrl}` : '';\n return new ItdFileError(`файл${source} больше предела в ${limit} байт: ${actual}`, {\n reason: ItdFileErrorReason.TooLarge,\n ...(safeUrl ? { url: safeUrl } : {}),\n limit,\n actual,\n });\n}\n","import { ItdConfigError, ItdFileError, ItdFileErrorReason } from '../errors.js';\nimport { redactErrorCause, redactUrl } from '../redact.js';\nimport { DEFAULT_FILE_STREAM_BUFFER_BYTES } from './contracts.js';\nimport { fileTooLarge, optionalBytes } from './limits.js';\n\n/** Потоки, на которые уже наложены счётчик размера и backpressure. */\nconst BOUNDED_FILE_STREAMS = new WeakSet<ReadableStream<Uint8Array>>();\n\n/** Настройки ограничивающей поток обёртки. */\nexport interface BoundedFileStreamOptions {\n maxBytes?: number | undefined;\n streamBufferBytes?: number | undefined;\n signal?: AbortSignal | undefined;\n url?: string | undefined;\n retryableRead?: boolean | undefined;\n}\n\n/** Приводит произвольный чанк потока к байтам. */\nfunction asBytes(value: Uint8Array): Uint8Array {\n return value instanceof Uint8Array ? value : new Uint8Array(value);\n}\n\n/** Проверяет значение без обращения к методам потенциально чужого объекта. */\nexport function isReadableByteStream(value: unknown): value is ReadableStream<Uint8Array> {\n return typeof ReadableStream !== 'undefined' && value instanceof ReadableStream;\n}\n\n/**\n * Ограничивает размер и очередь потока. Отмена результирующего потока отменяет исходный.\n *\n * @internal\n */\nexport function boundedFileStream(\n source: ReadableStream<Uint8Array>,\n options: BoundedFileStreamOptions = {},\n): ReadableStream<Uint8Array> {\n const maxBytes = optionalBytes(options.maxBytes, 'maxBytes');\n const streamBufferBytes =\n optionalBytes(\n options.streamBufferBytes ?? DEFAULT_FILE_STREAM_BUFFER_BYTES,\n 'streamBufferBytes',\n ) ?? DEFAULT_FILE_STREAM_BUFFER_BYTES;\n if (streamBufferBytes === 0) {\n throw new ItdConfigError('streamBufferBytes должен быть больше нуля');\n }\n\n const reader = source.getReader();\n let total = 0;\n let finished = false;\n let abortListener: (() => void) | undefined;\n\n const cleanup = () => {\n if (abortListener) options.signal?.removeEventListener('abort', abortListener);\n abortListener = undefined;\n };\n\n const stream = new ReadableStream<Uint8Array>(\n {\n start(controller) {\n if (!options.signal) return;\n abortListener = () => {\n finished = true;\n void reader.cancel(options.signal?.reason).catch(() => {});\n controller.error(options.signal?.reason ?? new DOMException('Aborted', 'AbortError'));\n cleanup();\n };\n if (options.signal.aborted) abortListener();\n else options.signal.addEventListener('abort', abortListener, { once: true });\n },\n async pull(controller) {\n if (finished) return;\n\n try {\n const next = await reader.read();\n if (finished) return;\n if (next.done) {\n finished = true;\n cleanup();\n controller.close();\n return;\n }\n\n const chunk = asBytes(next.value);\n total += chunk.byteLength;\n if (maxBytes !== undefined && total > maxBytes) {\n finished = true;\n await reader.cancel().catch(() => {});\n cleanup();\n controller.error(fileTooLarge(options.url, maxBytes, total));\n return;\n }\n\n controller.enqueue(chunk);\n } catch (error) {\n finished = true;\n cleanup();\n if (error instanceof ItdFileError) {\n controller.error(error);\n return;\n }\n controller.error(\n new ItdFileError('не удалось прочитать поток вложения', {\n reason: ItdFileErrorReason.Read,\n ...(options.url ? { url: redactUrl(options.url) } : {}),\n retryable: options.retryableRead ?? false,\n cause: redactErrorCause(error),\n }),\n );\n }\n },\n async cancel(reason) {\n finished = true;\n cleanup();\n await reader.cancel(reason).catch(() => {});\n },\n },\n {\n highWaterMark: streamBufferBytes,\n size: (chunk) => chunk.byteLength,\n },\n );\n BOUNDED_FILE_STREAMS.add(stream);\n return stream;\n}\n\n/** Наложены ли на поток ограничения библиотеки. @internal */\nexport function isBoundedFileStream(stream: ReadableStream<Uint8Array>): boolean {\n return BOUNDED_FILE_STREAMS.has(stream);\n}\n","import { ItdConfigError } from '../errors.js';\nimport {\n DEFAULT_FILE_STREAM_BUFFER_BYTES,\n type FileStreamOptions,\n FileTransferMode,\n} from './contracts.js';\nimport { optionalBytes } from './limits.js';\n\n/** Проверяет и дополняет настройки потока. @internal */\nexport function resolveFileStreamOptions(\n options: FileStreamOptions,\n defaultMaxBytes?: number,\n): {\n mode: FileTransferMode;\n maxBytes: number | undefined;\n streamBufferBytes: number;\n} {\n const mode = options.mode ?? FileTransferMode.Buffer;\n if (mode !== FileTransferMode.Buffer && mode !== FileTransferMode.Stream) {\n throw new ItdConfigError(`mode вложения должен быть 'buffer' или 'stream', получено: ${mode}`);\n }\n if (mode === FileTransferMode.Stream && typeof ReadableStream === 'undefined') {\n throw new ItdConfigError(\n \"эта среда не поддерживает ReadableStream; используйте mode: 'buffer'\",\n );\n }\n\n const maxBytes = optionalBytes(options.maxBytes ?? defaultMaxBytes, 'maxBytes');\n const streamBufferBytes =\n optionalBytes(\n options.streamBufferBytes ?? DEFAULT_FILE_STREAM_BUFFER_BYTES,\n 'streamBufferBytes',\n ) ?? DEFAULT_FILE_STREAM_BUFFER_BYTES;\n if (streamBufferBytes === 0) {\n throw new ItdConfigError('streamBufferBytes должен быть больше нуля');\n }\n\n return { mode, maxBytes, streamBufferBytes };\n}\n"],"mappings":";;;;AACA,MAAa,mBAAmB,OAAO,OAAO;;CAE5C,QAAQ;;CAER,QAAQ;AACV,CAAU;;AAOV,MAAa,6BAA6B;;;;ACT1C,SAAgB,cAAc,OAA2B,MAAkC;CACzF,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;CAChC,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,KAAK,CAAC,OAAO,UAAU,KAAK,GACjE,MAAM,IAAIA,eAAAA,eACR,GAAG,KAAK,uDAAuD,OACjE;CAEF,OAAO;AACT;;AAGA,SAAgB,aAAa,KAAyB,OAAe,QAA8B;CACjG,MAAM,UAAU,MAAMC,eAAAA,UAAU,GAAG,IAAI,KAAA;CACvC,MAAM,SAAS,UAAU,cAAc,YAAY;CACnD,OAAO,IAAIC,eAAAA,aAAa,OAAO,OAAO,oBAAoB,MAAM,SAAS,UAAU;EACjF,QAAQC,eAAAA,mBAAmB;EAC3B,GAAI,UAAU,EAAE,KAAK,QAAQ,IAAI,CAAC;EAClC;EACA;CACF,CAAC;AACH;;;;AClBA,MAAM,uCAAuB,IAAI,QAAoC;;AAYrE,SAAS,QAAQ,OAA+B;CAC9C,OAAO,iBAAiB,aAAa,QAAQ,IAAI,WAAW,KAAK;AACnE;;AAGA,SAAgB,qBAAqB,OAAqD;CACxF,OAAO,OAAO,mBAAmB,eAAe,iBAAiB;AACnE;;;;;;AAOA,SAAgB,kBACd,QACA,UAAoC,CAAC,GACT;CAC5B,MAAM,WAAW,cAAc,QAAQ,UAAU,UAAU;CAC3D,MAAM,oBACJ,cACE,QAAQ,qBAAA,SACR,mBACF,KAAA;CACF,IAAI,sBAAsB,GACxB,MAAM,IAAIC,eAAAA,eAAe,2CAA2C;CAGtE,MAAM,SAAS,OAAO,UAAU;CAChC,IAAI,QAAQ;CACZ,IAAI,WAAW;CACf,IAAI;CAEJ,MAAM,gBAAgB;EACpB,IAAI,eAAe,QAAQ,QAAQ,oBAAoB,SAAS,aAAa;EAC7E,gBAAgB,KAAA;CAClB;CAEA,MAAM,SAAS,IAAI,eACjB;EACE,MAAM,YAAY;GAChB,IAAI,CAAC,QAAQ,QAAQ;GACrB,sBAAsB;IACpB,WAAW;IACX,OAAY,OAAO,QAAQ,QAAQ,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IACzD,WAAW,MAAM,QAAQ,QAAQ,UAAU,IAAI,aAAa,WAAW,YAAY,CAAC;IACpF,QAAQ;GACV;GACA,IAAI,QAAQ,OAAO,SAAS,cAAc;QACrC,QAAQ,OAAO,iBAAiB,SAAS,eAAe,EAAE,MAAM,KAAK,CAAC;EAC7E;EACA,MAAM,KAAK,YAAY;GACrB,IAAI,UAAU;GAEd,IAAI;IACF,MAAM,OAAO,MAAM,OAAO,KAAK;IAC/B,IAAI,UAAU;IACd,IAAI,KAAK,MAAM;KACb,WAAW;KACX,QAAQ;KACR,WAAW,MAAM;KACjB;IACF;IAEA,MAAM,QAAQ,QAAQ,KAAK,KAAK;IAChC,SAAS,MAAM;IACf,IAAI,aAAa,KAAA,KAAa,QAAQ,UAAU;KAC9C,WAAW;KACX,MAAM,OAAO,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC;KACpC,QAAQ;KACR,WAAW,MAAM,aAAa,QAAQ,KAAK,UAAU,KAAK,CAAC;KAC3D;IACF;IAEA,WAAW,QAAQ,KAAK;GAC1B,SAAS,OAAO;IACd,WAAW;IACX,QAAQ;IACR,IAAI,iBAAiBC,eAAAA,cAAc;KACjC,WAAW,MAAM,KAAK;KACtB;IACF;IACA,WAAW,MACT,IAAIA,eAAAA,aAAa,uCAAuC;KACtD,QAAQC,eAAAA,mBAAmB;KAC3B,GAAI,QAAQ,MAAM,EAAE,KAAKC,eAAAA,UAAU,QAAQ,GAAG,EAAE,IAAI,CAAC;KACrD,WAAW,QAAQ,iBAAiB;KACpC,OAAOC,eAAAA,iBAAiB,KAAK;IAC/B,CAAC,CACH;GACF;EACF;EACA,MAAM,OAAO,QAAQ;GACnB,WAAW;GACX,QAAQ;GACR,MAAM,OAAO,OAAO,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;EAC5C;CACF,GACA;EACE,eAAe;EACf,OAAO,UAAU,MAAM;CACzB,CACF;CACA,qBAAqB,IAAI,MAAM;CAC/B,OAAO;AACT;;AAGA,SAAgB,oBAAoB,QAA6C;CAC/E,OAAO,qBAAqB,IAAI,MAAM;AACxC;;;;ACvHA,SAAgB,yBACd,SACA,iBAKA;CACA,MAAM,OAAO,QAAQ,QAAQ,iBAAiB;CAC9C,IAAI,SAAS,iBAAiB,UAAU,SAAS,iBAAiB,QAChE,MAAM,IAAIC,eAAAA,eAAe,8DAA8D,MAAM;CAE/F,IAAI,SAAS,iBAAiB,UAAU,OAAO,mBAAmB,aAChE,MAAM,IAAIA,eAAAA,eACR,sEACF;CAGF,MAAM,WAAW,cAAc,QAAQ,YAAY,iBAAiB,UAAU;CAC9E,MAAM,oBACJ,cACE,QAAQ,qBAAA,SACR,mBACF,KAAA;CACF,IAAI,sBAAsB,GACxB,MAAM,IAAIA,eAAAA,eAAe,2CAA2C;CAGtE,OAAO;EAAE;EAAM;EAAU;CAAkB;AAC7C"}
|
|
1
|
+
{"version":3,"file":"options-DjEtbVaf.cjs","names":["ItdConfigError","redactUrl","ItdFileError","ItdFileErrorReason","ItdConfigError","ItdFileError","ItdFileErrorReason","redactUrl","redactErrorCause","ItdConfigError"],"sources":["../../src/core/attachments/contracts.ts","../../src/core/attachments/limits.ts","../../src/core/attachments/bounded-stream.ts","../../src/core/attachments/options.ts"],"sourcesContent":["/** Способ передачи содержимого вложения. */\nexport const FileTransferMode = Object.freeze({\n /** Сначала получить файл целиком, затем отправить его как `Blob`. */\n Buffer: 'buffer',\n /** Передавать данные по мере чтения, не собирая файл целиком в памяти. */\n Stream: 'stream',\n} as const);\nexport type FileTransferMode = (typeof FileTransferMode)[keyof typeof FileTransferMode];\n\n/** Размер очереди потокового вложения по умолчанию — 4 МиБ. */\nexport const DEFAULT_FILE_STREAM_BUFFER_BYTES = 4 * 1024 * 1024;\n\n/** Предел размера файла, скачиваемого по адресу, — 100 МиБ. */\nexport const DEFAULT_URL_FILE_MAX_BYTES = 100 * 1024 * 1024;\n\n/** Что доступно источнику при открытии файла. */\nexport interface FileContext {\n /** Общая отмена текущей попытки, включая её таймаут. */\n signal?: AbortSignal | undefined;\n /** `fetch` клиента — с переданными пользователем настройками. */\n fetch: typeof fetch;\n /** Номер попытки отправки, начиная с 1. */\n attempt: number;\n}\n\n/** Готовое содержимое файла. */\nexport interface FileContent {\n file: Blob | ArrayBuffer | Uint8Array;\n /** Имя влияет на определение MIME, если `contentType` не задан. */\n filename?: string | undefined;\n /** MIME без параметров. Если не указан, определяется по имени или `Blob`. */\n contentType?: string | undefined;\n}\n\n/** Открытый поток файла. */\nexport interface FileStreamContent {\n stream: ReadableStream<Uint8Array>;\n filename?: string | undefined;\n contentType?: string | undefined;\n /** Размер, если он известен до чтения. */\n size?: number | undefined;\n /** Освобождает внешний ресурс после завершения попытки. */\n close?: (() => void | Promise<void>) | undefined;\n}\n\n/** Буферный источник, который вычисляется непосредственно перед первой попыткой. */\nexport interface LazyFile {\n load: (context: FileContext) => FileContent | Promise<FileContent>;\n}\n\n/**\n * Повторяемый потоковый источник.\n *\n * `open` вызывается для каждой попытки отправки. Он обязан возвращать новый поток:\n * уже прочитанный `ReadableStream` повторно использовать нельзя.\n */\nexport interface StreamFile {\n open: (context: FileContext) => FileStreamContent | Promise<FileStreamContent>;\n}\n\n/** Общие настройки потоковой передачи. */\nexport interface FileStreamOptions {\n /** Буферный или потоковый режим. По умолчанию `'buffer'`. */\n mode?: FileTransferMode | undefined;\n /** Максимальный размер файла. Без значения размер не ограничивается. */\n maxBytes?: number | undefined;\n /**\n * Верхняя граница очереди, которой управляет библиотека. По умолчанию 4 МиБ.\n *\n * Сетевой рантайм может иметь собственные внутренние буферы сверх этого значения.\n */\n streamBufferBytes?: number | undefined;\n}\n\n/** Файл, который нужно получить по HTTP(S). */\nexport interface UrlFile extends UrlFileOptions {\n url: string;\n}\n\n/** Настройки файла по HTTP(S). */\nexport interface UrlFileOptions extends FileStreamOptions {\n filename?: string | undefined;\n /** По умолчанию берётся из `Content-Type` ответа. */\n contentType?: string | undefined;\n}\n\n/** Файл для загрузки. Голый поток не принимается, потому что его нельзя повторить. */\nexport type FileInput =\n | Blob\n | ArrayBuffer\n | Uint8Array\n | FileContent\n | UrlFile\n | LazyFile\n | StreamFile;\n\n/** Настройки фабрики пользовательского потока. */\nexport interface FromStreamOptions extends Omit<FileStreamOptions, 'mode'> {\n filename?: string | undefined;\n contentType?: string | undefined;\n /** Размер, если он известен заранее. */\n size?: number | undefined;\n}\n","import { ItdConfigError, ItdFileError, ItdFileErrorReason } from '../errors.js';\nimport { redactUrl } from '../redact.js';\n\n/** Проверяет числовую границу до обращения к источнику. */\nexport function optionalBytes(value: number | undefined, name: string): number | undefined {\n if (value === undefined) return undefined;\n if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {\n throw new ItdConfigError(\n `${name} должен быть неотрицательным целым числом, получено: ${value}`,\n );\n }\n return value;\n}\n\n/** Создаёт типизированную ошибку превышения размера вложения. */\nexport function fileTooLarge(url: string | undefined, limit: number, actual: number): ItdFileError {\n const safeUrl = url ? redactUrl(url) : undefined;\n const source = safeUrl ? ` по адресу ${safeUrl}` : '';\n return new ItdFileError(`файл${source} больше предела в ${limit} байт: ${actual}`, {\n reason: ItdFileErrorReason.TooLarge,\n ...(safeUrl ? { url: safeUrl } : {}),\n limit,\n actual,\n });\n}\n","import { ItdConfigError, ItdFileError, ItdFileErrorReason } from '../errors.js';\nimport { redactErrorCause, redactUrl } from '../redact.js';\nimport { DEFAULT_FILE_STREAM_BUFFER_BYTES } from './contracts.js';\nimport { fileTooLarge, optionalBytes } from './limits.js';\n\n/** Потоки, на которые уже наложены счётчик размера и backpressure. */\nconst BOUNDED_FILE_STREAMS = new WeakSet<ReadableStream<Uint8Array>>();\n\n/** Настройки ограничивающей поток обёртки. */\nexport interface BoundedFileStreamOptions {\n maxBytes?: number | undefined;\n streamBufferBytes?: number | undefined;\n signal?: AbortSignal | undefined;\n url?: string | undefined;\n retryableRead?: boolean | undefined;\n}\n\n/** Приводит произвольный чанк потока к байтам. */\nfunction asBytes(value: Uint8Array): Uint8Array {\n return value instanceof Uint8Array ? value : new Uint8Array(value);\n}\n\n/** Проверяет значение без обращения к методам потенциально чужого объекта. */\nexport function isReadableByteStream(value: unknown): value is ReadableStream<Uint8Array> {\n return typeof ReadableStream !== 'undefined' && value instanceof ReadableStream;\n}\n\n/**\n * Ограничивает размер и очередь потока. Отмена результирующего потока отменяет исходный.\n *\n * @internal\n */\nexport function boundedFileStream(\n source: ReadableStream<Uint8Array>,\n options: BoundedFileStreamOptions = {},\n): ReadableStream<Uint8Array> {\n const maxBytes = optionalBytes(options.maxBytes, 'maxBytes');\n const streamBufferBytes =\n optionalBytes(\n options.streamBufferBytes ?? DEFAULT_FILE_STREAM_BUFFER_BYTES,\n 'streamBufferBytes',\n ) ?? DEFAULT_FILE_STREAM_BUFFER_BYTES;\n if (streamBufferBytes === 0) {\n throw new ItdConfigError('streamBufferBytes должен быть больше нуля');\n }\n\n const reader = source.getReader();\n let total = 0;\n let finished = false;\n let abortListener: (() => void) | undefined;\n\n const cleanup = () => {\n if (abortListener) options.signal?.removeEventListener('abort', abortListener);\n abortListener = undefined;\n };\n\n const stream = new ReadableStream<Uint8Array>(\n {\n start(controller) {\n if (!options.signal) return;\n abortListener = () => {\n finished = true;\n void reader.cancel(options.signal?.reason).catch(() => {});\n controller.error(options.signal?.reason ?? new DOMException('Aborted', 'AbortError'));\n cleanup();\n };\n if (options.signal.aborted) abortListener();\n else options.signal.addEventListener('abort', abortListener, { once: true });\n },\n async pull(controller) {\n if (finished) return;\n\n try {\n const next = await reader.read();\n if (finished) return;\n if (next.done) {\n finished = true;\n cleanup();\n controller.close();\n return;\n }\n\n const chunk = asBytes(next.value);\n total += chunk.byteLength;\n if (maxBytes !== undefined && total > maxBytes) {\n finished = true;\n await reader.cancel().catch(() => {});\n cleanup();\n controller.error(fileTooLarge(options.url, maxBytes, total));\n return;\n }\n\n controller.enqueue(chunk);\n } catch (error) {\n finished = true;\n cleanup();\n if (error instanceof ItdFileError) {\n controller.error(error);\n return;\n }\n controller.error(\n new ItdFileError('не удалось прочитать поток вложения', {\n reason: ItdFileErrorReason.Read,\n ...(options.url ? { url: redactUrl(options.url) } : {}),\n retryable: options.retryableRead ?? false,\n cause: redactErrorCause(error),\n }),\n );\n }\n },\n async cancel(reason) {\n finished = true;\n cleanup();\n await reader.cancel(reason).catch(() => {});\n },\n },\n {\n highWaterMark: streamBufferBytes,\n size: (chunk) => chunk.byteLength,\n },\n );\n BOUNDED_FILE_STREAMS.add(stream);\n return stream;\n}\n\n/** Наложены ли на поток ограничения библиотеки. @internal */\nexport function isBoundedFileStream(stream: ReadableStream<Uint8Array>): boolean {\n return BOUNDED_FILE_STREAMS.has(stream);\n}\n","import { ItdConfigError } from '../errors.js';\nimport {\n DEFAULT_FILE_STREAM_BUFFER_BYTES,\n type FileStreamOptions,\n FileTransferMode,\n} from './contracts.js';\nimport { optionalBytes } from './limits.js';\n\n/** Проверяет и дополняет настройки потока. @internal */\nexport function resolveFileStreamOptions(\n options: FileStreamOptions,\n defaultMaxBytes?: number,\n): {\n mode: FileTransferMode;\n maxBytes: number | undefined;\n streamBufferBytes: number;\n} {\n const mode = options.mode ?? FileTransferMode.Buffer;\n if (mode !== FileTransferMode.Buffer && mode !== FileTransferMode.Stream) {\n throw new ItdConfigError(`mode вложения должен быть 'buffer' или 'stream', получено: ${mode}`);\n }\n if (mode === FileTransferMode.Stream && typeof ReadableStream === 'undefined') {\n throw new ItdConfigError(\n \"эта среда не поддерживает ReadableStream; используйте mode: 'buffer'\",\n );\n }\n\n const maxBytes = optionalBytes(options.maxBytes ?? defaultMaxBytes, 'maxBytes');\n const streamBufferBytes =\n optionalBytes(\n options.streamBufferBytes ?? DEFAULT_FILE_STREAM_BUFFER_BYTES,\n 'streamBufferBytes',\n ) ?? DEFAULT_FILE_STREAM_BUFFER_BYTES;\n if (streamBufferBytes === 0) {\n throw new ItdConfigError('streamBufferBytes должен быть больше нуля');\n }\n\n return { mode, maxBytes, streamBufferBytes };\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as ItdConfigError, l as ItdFileError, u as ItdFileErrorReason } from "./errors-GI10kZxk.js";
|
|
2
|
-
import { a as redactUrl, r as redactErrorCause } from "./redact-
|
|
2
|
+
import { a as redactUrl, r as redactErrorCause } from "./redact-DJV0XM3n.js";
|
|
3
3
|
//#region src/core/attachments/contracts.ts
|
|
4
4
|
/** Способ передачи содержимого вложения. */
|
|
5
5
|
const FileTransferMode = Object.freeze({
|
|
@@ -141,4 +141,4 @@ function resolveFileStreamOptions(options, defaultMaxBytes) {
|
|
|
141
141
|
//#endregion
|
|
142
142
|
export { fileTooLarge as a, FileTransferMode as c, isReadableByteStream as i, boundedFileStream as n, optionalBytes as o, isBoundedFileStream as r, DEFAULT_URL_FILE_MAX_BYTES as s, resolveFileStreamOptions as t };
|
|
143
143
|
|
|
144
|
-
//# sourceMappingURL=options-
|
|
144
|
+
//# sourceMappingURL=options-i4LrKfqx.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options-DhgGg7Ms.js","names":[],"sources":["../../src/core/attachments/contracts.ts","../../src/core/attachments/limits.ts","../../src/core/attachments/bounded-stream.ts","../../src/core/attachments/options.ts"],"sourcesContent":["/** Способ передачи содержимого вложения. */\nexport const FileTransferMode = Object.freeze({\n /** Сначала получить файл целиком, затем отправить его как `Blob`. */\n Buffer: 'buffer',\n /** Передавать данные по мере чтения, не собирая файл целиком в памяти. */\n Stream: 'stream',\n} as const);\nexport type FileTransferMode = (typeof FileTransferMode)[keyof typeof FileTransferMode];\n\n/** Размер очереди потокового вложения по умолчанию — 4 МиБ. */\nexport const DEFAULT_FILE_STREAM_BUFFER_BYTES = 4 * 1024 * 1024;\n\n/** Предел размера файла, скачиваемого по адресу, — 100 МиБ. */\nexport const DEFAULT_URL_FILE_MAX_BYTES = 100 * 1024 * 1024;\n\n/** Что доступно источнику при открытии файла. */\nexport interface FileContext {\n /** Общая отмена текущей попытки, включая её таймаут. */\n signal?: AbortSignal | undefined;\n /** `fetch` клиента — с переданными пользователем настройками. */\n fetch: typeof fetch;\n /** Номер попытки отправки, начиная с 1. */\n attempt: number;\n}\n\n/** Готовое содержимое файла. */\nexport interface FileContent {\n file: Blob | ArrayBuffer | Uint8Array;\n /** Имя влияет на определение MIME, если `contentType` не задан. */\n filename?: string | undefined;\n /** MIME без параметров. Если не указан, определяется по имени или `Blob`. */\n contentType?: string | undefined;\n}\n\n/** Открытый поток файла. */\nexport interface FileStreamContent {\n stream: ReadableStream<Uint8Array>;\n filename?: string | undefined;\n contentType?: string | undefined;\n /** Размер, если он известен до чтения. */\n size?: number | undefined;\n /** Освобождает внешний ресурс после завершения попытки. */\n close?: (() => void | Promise<void>) | undefined;\n}\n\n/** Буферный источник, который вычисляется непосредственно перед первой попыткой. */\nexport interface LazyFile {\n load: (context: FileContext) => FileContent | Promise<FileContent>;\n}\n\n/**\n * Повторяемый потоковый источник.\n *\n * `open` вызывается для каждой попытки отправки. Он обязан возвращать новый поток:\n * уже прочитанный `ReadableStream` повторно использовать нельзя.\n */\nexport interface StreamFile {\n open: (context: FileContext) => FileStreamContent | Promise<FileStreamContent>;\n}\n\n/** Общие настройки потоковой передачи. */\nexport interface FileStreamOptions {\n /** Буферный или потоковый режим. По умолчанию `'buffer'`. */\n mode?: FileTransferMode | undefined;\n /** Максимальный размер файла. Без значения размер не ограничивается. */\n maxBytes?: number | undefined;\n /**\n * Верхняя граница очереди, которой управляет библиотека. По умолчанию 4 МиБ.\n *\n * Сетевой рантайм может иметь собственные внутренние буферы сверх этого значения.\n */\n streamBufferBytes?: number | undefined;\n}\n\n/** Файл, который нужно получить по HTTP(S). */\nexport interface UrlFile extends UrlFileOptions {\n url: string;\n}\n\n/** Настройки файла по HTTP(S). */\nexport interface UrlFileOptions extends FileStreamOptions {\n filename?: string | undefined;\n /** По умолчанию берётся из `Content-Type` ответа. */\n contentType?: string | undefined;\n}\n\n/** Файл для загрузки. Голый поток не принимается, потому что его нельзя повторить. */\nexport type FileInput =\n | Blob\n | ArrayBuffer\n | Uint8Array\n | FileContent\n | UrlFile\n | LazyFile\n | StreamFile;\n\n/** Настройки фабрики пользовательского потока. */\nexport interface FromStreamOptions extends Omit<FileStreamOptions, 'mode'> {\n filename?: string | undefined;\n contentType?: string | undefined;\n /** Размер, если он известен заранее. */\n size?: number | undefined;\n}\n","import { ItdConfigError, ItdFileError, ItdFileErrorReason } from '../errors.js';\nimport { redactUrl } from '../redact.js';\n\n/** Проверяет числовую границу до обращения к источнику. */\nexport function optionalBytes(value: number | undefined, name: string): number | undefined {\n if (value === undefined) return undefined;\n if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {\n throw new ItdConfigError(\n `${name} должен быть неотрицательным целым числом, получено: ${value}`,\n );\n }\n return value;\n}\n\n/** Создаёт типизированную ошибку превышения размера вложения. */\nexport function fileTooLarge(url: string | undefined, limit: number, actual: number): ItdFileError {\n const safeUrl = url ? redactUrl(url) : undefined;\n const source = safeUrl ? ` по адресу ${safeUrl}` : '';\n return new ItdFileError(`файл${source} больше предела в ${limit} байт: ${actual}`, {\n reason: ItdFileErrorReason.TooLarge,\n ...(safeUrl ? { url: safeUrl } : {}),\n limit,\n actual,\n });\n}\n","import { ItdConfigError, ItdFileError, ItdFileErrorReason } from '../errors.js';\nimport { redactErrorCause, redactUrl } from '../redact.js';\nimport { DEFAULT_FILE_STREAM_BUFFER_BYTES } from './contracts.js';\nimport { fileTooLarge, optionalBytes } from './limits.js';\n\n/** Потоки, на которые уже наложены счётчик размера и backpressure. */\nconst BOUNDED_FILE_STREAMS = new WeakSet<ReadableStream<Uint8Array>>();\n\n/** Настройки ограничивающей поток обёртки. */\nexport interface BoundedFileStreamOptions {\n maxBytes?: number | undefined;\n streamBufferBytes?: number | undefined;\n signal?: AbortSignal | undefined;\n url?: string | undefined;\n retryableRead?: boolean | undefined;\n}\n\n/** Приводит произвольный чанк потока к байтам. */\nfunction asBytes(value: Uint8Array): Uint8Array {\n return value instanceof Uint8Array ? value : new Uint8Array(value);\n}\n\n/** Проверяет значение без обращения к методам потенциально чужого объекта. */\nexport function isReadableByteStream(value: unknown): value is ReadableStream<Uint8Array> {\n return typeof ReadableStream !== 'undefined' && value instanceof ReadableStream;\n}\n\n/**\n * Ограничивает размер и очередь потока. Отмена результирующего потока отменяет исходный.\n *\n * @internal\n */\nexport function boundedFileStream(\n source: ReadableStream<Uint8Array>,\n options: BoundedFileStreamOptions = {},\n): ReadableStream<Uint8Array> {\n const maxBytes = optionalBytes(options.maxBytes, 'maxBytes');\n const streamBufferBytes =\n optionalBytes(\n options.streamBufferBytes ?? DEFAULT_FILE_STREAM_BUFFER_BYTES,\n 'streamBufferBytes',\n ) ?? DEFAULT_FILE_STREAM_BUFFER_BYTES;\n if (streamBufferBytes === 0) {\n throw new ItdConfigError('streamBufferBytes должен быть больше нуля');\n }\n\n const reader = source.getReader();\n let total = 0;\n let finished = false;\n let abortListener: (() => void) | undefined;\n\n const cleanup = () => {\n if (abortListener) options.signal?.removeEventListener('abort', abortListener);\n abortListener = undefined;\n };\n\n const stream = new ReadableStream<Uint8Array>(\n {\n start(controller) {\n if (!options.signal) return;\n abortListener = () => {\n finished = true;\n void reader.cancel(options.signal?.reason).catch(() => {});\n controller.error(options.signal?.reason ?? new DOMException('Aborted', 'AbortError'));\n cleanup();\n };\n if (options.signal.aborted) abortListener();\n else options.signal.addEventListener('abort', abortListener, { once: true });\n },\n async pull(controller) {\n if (finished) return;\n\n try {\n const next = await reader.read();\n if (finished) return;\n if (next.done) {\n finished = true;\n cleanup();\n controller.close();\n return;\n }\n\n const chunk = asBytes(next.value);\n total += chunk.byteLength;\n if (maxBytes !== undefined && total > maxBytes) {\n finished = true;\n await reader.cancel().catch(() => {});\n cleanup();\n controller.error(fileTooLarge(options.url, maxBytes, total));\n return;\n }\n\n controller.enqueue(chunk);\n } catch (error) {\n finished = true;\n cleanup();\n if (error instanceof ItdFileError) {\n controller.error(error);\n return;\n }\n controller.error(\n new ItdFileError('не удалось прочитать поток вложения', {\n reason: ItdFileErrorReason.Read,\n ...(options.url ? { url: redactUrl(options.url) } : {}),\n retryable: options.retryableRead ?? false,\n cause: redactErrorCause(error),\n }),\n );\n }\n },\n async cancel(reason) {\n finished = true;\n cleanup();\n await reader.cancel(reason).catch(() => {});\n },\n },\n {\n highWaterMark: streamBufferBytes,\n size: (chunk) => chunk.byteLength,\n },\n );\n BOUNDED_FILE_STREAMS.add(stream);\n return stream;\n}\n\n/** Наложены ли на поток ограничения библиотеки. @internal */\nexport function isBoundedFileStream(stream: ReadableStream<Uint8Array>): boolean {\n return BOUNDED_FILE_STREAMS.has(stream);\n}\n","import { ItdConfigError } from '../errors.js';\nimport {\n DEFAULT_FILE_STREAM_BUFFER_BYTES,\n type FileStreamOptions,\n FileTransferMode,\n} from './contracts.js';\nimport { optionalBytes } from './limits.js';\n\n/** Проверяет и дополняет настройки потока. @internal */\nexport function resolveFileStreamOptions(\n options: FileStreamOptions,\n defaultMaxBytes?: number,\n): {\n mode: FileTransferMode;\n maxBytes: number | undefined;\n streamBufferBytes: number;\n} {\n const mode = options.mode ?? FileTransferMode.Buffer;\n if (mode !== FileTransferMode.Buffer && mode !== FileTransferMode.Stream) {\n throw new ItdConfigError(`mode вложения должен быть 'buffer' или 'stream', получено: ${mode}`);\n }\n if (mode === FileTransferMode.Stream && typeof ReadableStream === 'undefined') {\n throw new ItdConfigError(\n \"эта среда не поддерживает ReadableStream; используйте mode: 'buffer'\",\n );\n }\n\n const maxBytes = optionalBytes(options.maxBytes ?? defaultMaxBytes, 'maxBytes');\n const streamBufferBytes =\n optionalBytes(\n options.streamBufferBytes ?? DEFAULT_FILE_STREAM_BUFFER_BYTES,\n 'streamBufferBytes',\n ) ?? DEFAULT_FILE_STREAM_BUFFER_BYTES;\n if (streamBufferBytes === 0) {\n throw new ItdConfigError('streamBufferBytes должен быть больше нуля');\n }\n\n return { mode, maxBytes, streamBufferBytes };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"options-i4LrKfqx.js","names":[],"sources":["../../src/core/attachments/contracts.ts","../../src/core/attachments/limits.ts","../../src/core/attachments/bounded-stream.ts","../../src/core/attachments/options.ts"],"sourcesContent":["/** Способ передачи содержимого вложения. */\nexport const FileTransferMode = Object.freeze({\n /** Сначала получить файл целиком, затем отправить его как `Blob`. */\n Buffer: 'buffer',\n /** Передавать данные по мере чтения, не собирая файл целиком в памяти. */\n Stream: 'stream',\n} as const);\nexport type FileTransferMode = (typeof FileTransferMode)[keyof typeof FileTransferMode];\n\n/** Размер очереди потокового вложения по умолчанию — 4 МиБ. */\nexport const DEFAULT_FILE_STREAM_BUFFER_BYTES = 4 * 1024 * 1024;\n\n/** Предел размера файла, скачиваемого по адресу, — 100 МиБ. */\nexport const DEFAULT_URL_FILE_MAX_BYTES = 100 * 1024 * 1024;\n\n/** Что доступно источнику при открытии файла. */\nexport interface FileContext {\n /** Общая отмена текущей попытки, включая её таймаут. */\n signal?: AbortSignal | undefined;\n /** `fetch` клиента — с переданными пользователем настройками. */\n fetch: typeof fetch;\n /** Номер попытки отправки, начиная с 1. */\n attempt: number;\n}\n\n/** Готовое содержимое файла. */\nexport interface FileContent {\n file: Blob | ArrayBuffer | Uint8Array;\n /** Имя влияет на определение MIME, если `contentType` не задан. */\n filename?: string | undefined;\n /** MIME без параметров. Если не указан, определяется по имени или `Blob`. */\n contentType?: string | undefined;\n}\n\n/** Открытый поток файла. */\nexport interface FileStreamContent {\n stream: ReadableStream<Uint8Array>;\n filename?: string | undefined;\n contentType?: string | undefined;\n /** Размер, если он известен до чтения. */\n size?: number | undefined;\n /** Освобождает внешний ресурс после завершения попытки. */\n close?: (() => void | Promise<void>) | undefined;\n}\n\n/** Буферный источник, который вычисляется непосредственно перед первой попыткой. */\nexport interface LazyFile {\n load: (context: FileContext) => FileContent | Promise<FileContent>;\n}\n\n/**\n * Повторяемый потоковый источник.\n *\n * `open` вызывается для каждой попытки отправки. Он обязан возвращать новый поток:\n * уже прочитанный `ReadableStream` повторно использовать нельзя.\n */\nexport interface StreamFile {\n open: (context: FileContext) => FileStreamContent | Promise<FileStreamContent>;\n}\n\n/** Общие настройки потоковой передачи. */\nexport interface FileStreamOptions {\n /** Буферный или потоковый режим. По умолчанию `'buffer'`. */\n mode?: FileTransferMode | undefined;\n /** Максимальный размер файла. Без значения размер не ограничивается. */\n maxBytes?: number | undefined;\n /**\n * Верхняя граница очереди, которой управляет библиотека. По умолчанию 4 МиБ.\n *\n * Сетевой рантайм может иметь собственные внутренние буферы сверх этого значения.\n */\n streamBufferBytes?: number | undefined;\n}\n\n/** Файл, который нужно получить по HTTP(S). */\nexport interface UrlFile extends UrlFileOptions {\n url: string;\n}\n\n/** Настройки файла по HTTP(S). */\nexport interface UrlFileOptions extends FileStreamOptions {\n filename?: string | undefined;\n /** По умолчанию берётся из `Content-Type` ответа. */\n contentType?: string | undefined;\n}\n\n/** Файл для загрузки. Голый поток не принимается, потому что его нельзя повторить. */\nexport type FileInput =\n | Blob\n | ArrayBuffer\n | Uint8Array\n | FileContent\n | UrlFile\n | LazyFile\n | StreamFile;\n\n/** Настройки фабрики пользовательского потока. */\nexport interface FromStreamOptions extends Omit<FileStreamOptions, 'mode'> {\n filename?: string | undefined;\n contentType?: string | undefined;\n /** Размер, если он известен заранее. */\n size?: number | undefined;\n}\n","import { ItdConfigError, ItdFileError, ItdFileErrorReason } from '../errors.js';\nimport { redactUrl } from '../redact.js';\n\n/** Проверяет числовую границу до обращения к источнику. */\nexport function optionalBytes(value: number | undefined, name: string): number | undefined {\n if (value === undefined) return undefined;\n if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {\n throw new ItdConfigError(\n `${name} должен быть неотрицательным целым числом, получено: ${value}`,\n );\n }\n return value;\n}\n\n/** Создаёт типизированную ошибку превышения размера вложения. */\nexport function fileTooLarge(url: string | undefined, limit: number, actual: number): ItdFileError {\n const safeUrl = url ? redactUrl(url) : undefined;\n const source = safeUrl ? ` по адресу ${safeUrl}` : '';\n return new ItdFileError(`файл${source} больше предела в ${limit} байт: ${actual}`, {\n reason: ItdFileErrorReason.TooLarge,\n ...(safeUrl ? { url: safeUrl } : {}),\n limit,\n actual,\n });\n}\n","import { ItdConfigError, ItdFileError, ItdFileErrorReason } from '../errors.js';\nimport { redactErrorCause, redactUrl } from '../redact.js';\nimport { DEFAULT_FILE_STREAM_BUFFER_BYTES } from './contracts.js';\nimport { fileTooLarge, optionalBytes } from './limits.js';\n\n/** Потоки, на которые уже наложены счётчик размера и backpressure. */\nconst BOUNDED_FILE_STREAMS = new WeakSet<ReadableStream<Uint8Array>>();\n\n/** Настройки ограничивающей поток обёртки. */\nexport interface BoundedFileStreamOptions {\n maxBytes?: number | undefined;\n streamBufferBytes?: number | undefined;\n signal?: AbortSignal | undefined;\n url?: string | undefined;\n retryableRead?: boolean | undefined;\n}\n\n/** Приводит произвольный чанк потока к байтам. */\nfunction asBytes(value: Uint8Array): Uint8Array {\n return value instanceof Uint8Array ? value : new Uint8Array(value);\n}\n\n/** Проверяет значение без обращения к методам потенциально чужого объекта. */\nexport function isReadableByteStream(value: unknown): value is ReadableStream<Uint8Array> {\n return typeof ReadableStream !== 'undefined' && value instanceof ReadableStream;\n}\n\n/**\n * Ограничивает размер и очередь потока. Отмена результирующего потока отменяет исходный.\n *\n * @internal\n */\nexport function boundedFileStream(\n source: ReadableStream<Uint8Array>,\n options: BoundedFileStreamOptions = {},\n): ReadableStream<Uint8Array> {\n const maxBytes = optionalBytes(options.maxBytes, 'maxBytes');\n const streamBufferBytes =\n optionalBytes(\n options.streamBufferBytes ?? DEFAULT_FILE_STREAM_BUFFER_BYTES,\n 'streamBufferBytes',\n ) ?? DEFAULT_FILE_STREAM_BUFFER_BYTES;\n if (streamBufferBytes === 0) {\n throw new ItdConfigError('streamBufferBytes должен быть больше нуля');\n }\n\n const reader = source.getReader();\n let total = 0;\n let finished = false;\n let abortListener: (() => void) | undefined;\n\n const cleanup = () => {\n if (abortListener) options.signal?.removeEventListener('abort', abortListener);\n abortListener = undefined;\n };\n\n const stream = new ReadableStream<Uint8Array>(\n {\n start(controller) {\n if (!options.signal) return;\n abortListener = () => {\n finished = true;\n void reader.cancel(options.signal?.reason).catch(() => {});\n controller.error(options.signal?.reason ?? new DOMException('Aborted', 'AbortError'));\n cleanup();\n };\n if (options.signal.aborted) abortListener();\n else options.signal.addEventListener('abort', abortListener, { once: true });\n },\n async pull(controller) {\n if (finished) return;\n\n try {\n const next = await reader.read();\n if (finished) return;\n if (next.done) {\n finished = true;\n cleanup();\n controller.close();\n return;\n }\n\n const chunk = asBytes(next.value);\n total += chunk.byteLength;\n if (maxBytes !== undefined && total > maxBytes) {\n finished = true;\n await reader.cancel().catch(() => {});\n cleanup();\n controller.error(fileTooLarge(options.url, maxBytes, total));\n return;\n }\n\n controller.enqueue(chunk);\n } catch (error) {\n finished = true;\n cleanup();\n if (error instanceof ItdFileError) {\n controller.error(error);\n return;\n }\n controller.error(\n new ItdFileError('не удалось прочитать поток вложения', {\n reason: ItdFileErrorReason.Read,\n ...(options.url ? { url: redactUrl(options.url) } : {}),\n retryable: options.retryableRead ?? false,\n cause: redactErrorCause(error),\n }),\n );\n }\n },\n async cancel(reason) {\n finished = true;\n cleanup();\n await reader.cancel(reason).catch(() => {});\n },\n },\n {\n highWaterMark: streamBufferBytes,\n size: (chunk) => chunk.byteLength,\n },\n );\n BOUNDED_FILE_STREAMS.add(stream);\n return stream;\n}\n\n/** Наложены ли на поток ограничения библиотеки. @internal */\nexport function isBoundedFileStream(stream: ReadableStream<Uint8Array>): boolean {\n return BOUNDED_FILE_STREAMS.has(stream);\n}\n","import { ItdConfigError } from '../errors.js';\nimport {\n DEFAULT_FILE_STREAM_BUFFER_BYTES,\n type FileStreamOptions,\n FileTransferMode,\n} from './contracts.js';\nimport { optionalBytes } from './limits.js';\n\n/** Проверяет и дополняет настройки потока. @internal */\nexport function resolveFileStreamOptions(\n options: FileStreamOptions,\n defaultMaxBytes?: number,\n): {\n mode: FileTransferMode;\n maxBytes: number | undefined;\n streamBufferBytes: number;\n} {\n const mode = options.mode ?? FileTransferMode.Buffer;\n if (mode !== FileTransferMode.Buffer && mode !== FileTransferMode.Stream) {\n throw new ItdConfigError(`mode вложения должен быть 'buffer' или 'stream', получено: ${mode}`);\n }\n if (mode === FileTransferMode.Stream && typeof ReadableStream === 'undefined') {\n throw new ItdConfigError(\n \"эта среда не поддерживает ReadableStream; используйте mode: 'buffer'\",\n );\n }\n\n const maxBytes = optionalBytes(options.maxBytes ?? defaultMaxBytes, 'maxBytes');\n const streamBufferBytes =\n optionalBytes(\n options.streamBufferBytes ?? DEFAULT_FILE_STREAM_BUFFER_BYTES,\n 'streamBufferBytes',\n ) ?? DEFAULT_FILE_STREAM_BUFFER_BYTES;\n if (streamBufferBytes === 0) {\n throw new ItdConfigError('streamBufferBytes должен быть больше нуля');\n }\n\n return { mode, maxBytes, streamBufferBytes };\n}\n"],"mappings":"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"}
|
|
@@ -274,16 +274,20 @@ function parseNameValuePair(nameValuePairStr) {
|
|
|
274
274
|
}
|
|
275
275
|
function parseSetCookie(input, options) {
|
|
276
276
|
options = options ? Object.assign({}, defaultParseOptions, options) : defaultParseOptions;
|
|
277
|
-
if (!input)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
277
|
+
if (!input) {
|
|
278
|
+
if (!options.map) return [];
|
|
279
|
+
else return createNullObj();
|
|
280
|
+
}
|
|
281
|
+
if (input.headers) {
|
|
282
|
+
if (typeof input.headers.getSetCookie === "function") input = input.headers.getSetCookie();
|
|
283
|
+
else if (input.headers["set-cookie"]) input = input.headers["set-cookie"];
|
|
284
|
+
else {
|
|
285
|
+
var sch = input.headers[Object.keys(input.headers).find(function(key) {
|
|
286
|
+
return key.toLowerCase() === "set-cookie";
|
|
287
|
+
})];
|
|
288
|
+
if (!sch && input.headers.cookie && !options.silent) console.warn("Warning: set-cookie-parser appears to have been called on a request object. It is designed to parse Set-Cookie headers from responses, not Cookie headers from requests. Set the option {silent: true} to suppress this warning.");
|
|
289
|
+
input = sch;
|
|
290
|
+
}
|
|
287
291
|
}
|
|
288
292
|
var split = options.split;
|
|
289
293
|
var isArray = Array.isArray(input);
|
|
@@ -364,6 +368,16 @@ const REFRESH_COOKIE = "refresh_token";
|
|
|
364
368
|
const REFRESH_COOKIE_PATH = "/api/v1/auth";
|
|
365
369
|
/** Разделитель origin и содержимого cookie при сериализации. В origin пробелов не бывает. */
|
|
366
370
|
const SERIALIZED_SEPARATOR = " ";
|
|
371
|
+
const COOKIE_NAME = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
372
|
+
/** Проверяет одну запись, созданную {@link CookieJar.serialize}. @internal */
|
|
373
|
+
function isSerializedCookieEntry(entry) {
|
|
374
|
+
const separatorAt = entry.indexOf(SERIALIZED_SEPARATOR);
|
|
375
|
+
if (separatorAt <= 0) return false;
|
|
376
|
+
const origin = entry.slice(0, separatorAt);
|
|
377
|
+
if (originOf(origin) !== origin) return false;
|
|
378
|
+
const parsed = parseSetCookie([entry.slice(separatorAt + 1)]);
|
|
379
|
+
return parsed.length === 1 && COOKIE_NAME.test(parsed[0]?.name ?? "");
|
|
380
|
+
}
|
|
367
381
|
/** Дата в миллисекунды. Некорректная дата считается отсутствующей, а не `NaN`. */
|
|
368
382
|
function toTimestamp(date) {
|
|
369
383
|
if (!date) return void 0;
|
|
@@ -398,8 +412,20 @@ function pathMatches(cookiePath, requestPath) {
|
|
|
398
412
|
* Реализована практическая часть RFC 6265: origin, путь, срок жизни, флаг `Secure`.
|
|
399
413
|
* Доменные cookie для поддоменов намеренно не поддерживаются — API работает с одного хоста.
|
|
400
414
|
*/
|
|
401
|
-
var CookieJar = class {
|
|
415
|
+
var CookieJar = class CookieJar {
|
|
402
416
|
#byOrigin = /* @__PURE__ */ new Map();
|
|
417
|
+
/** Независимая копия для auth-flow, который ещё не получил право на commit. */
|
|
418
|
+
clone() {
|
|
419
|
+
const copy = new CookieJar();
|
|
420
|
+
copy.deserialize(this.serialize());
|
|
421
|
+
return copy;
|
|
422
|
+
}
|
|
423
|
+
/** Атомарно с точки зрения синхронных читателей заменяет содержимое снимком другого jar. */
|
|
424
|
+
replaceWith(source) {
|
|
425
|
+
const entries = source.serialize();
|
|
426
|
+
this.clear();
|
|
427
|
+
this.deserialize(entries);
|
|
428
|
+
}
|
|
403
429
|
/**
|
|
404
430
|
* Забирает `Set-Cookie` из ответа.
|
|
405
431
|
*
|
|
@@ -501,11 +527,10 @@ var CookieJar = class {
|
|
|
501
527
|
deserialize(entries) {
|
|
502
528
|
if (!entries) return;
|
|
503
529
|
for (const entry of entries) {
|
|
530
|
+
if (!isSerializedCookieEntry(entry)) continue;
|
|
504
531
|
const separatorAt = entry.indexOf(SERIALIZED_SEPARATOR);
|
|
505
|
-
if (separatorAt <= 0) continue;
|
|
506
532
|
const origin = entry.slice(0, separatorAt);
|
|
507
533
|
const setCookie = entry.slice(separatorAt + 1);
|
|
508
|
-
if (!originOf(origin)) continue;
|
|
509
534
|
this.setFromStrings(origin, [setCookie]);
|
|
510
535
|
}
|
|
511
536
|
}
|
|
@@ -653,6 +678,6 @@ function redactBody(body) {
|
|
|
653
678
|
return body;
|
|
654
679
|
}
|
|
655
680
|
//#endregion
|
|
656
|
-
export {
|
|
681
|
+
export { shouldSendCredentials as C, resolveFetch as S, supportsStreamingBody as T, originOf as _, redactUrl as a, isBlob as b, REFRESH_COOKIE as c, buildQuery as d, encodePathSegment as f, normalizeBaseUrl as g, joinUrl as h, redactHeaders as i, REFRESH_COOKIE_PATH as l, isSameSite as m, redactBody as n, AUTH_FLAG_COOKIE as o, hostOf as p, redactErrorCause as r, CookieJar as s, maskSecret as t, isSerializedCookieEntry as u, RuntimeMode as v, shouldUseCookieJar as w, isFile as x, createDeviceId as y };
|
|
657
682
|
|
|
658
|
-
//# sourceMappingURL=redact-
|
|
683
|
+
//# sourceMappingURL=redact-DJV0XM3n.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact-DJV0XM3n.js","names":["#byOrigin","#matching"],"sources":["../../src/core/runtime.ts","../../src/core/url.ts","../../node_modules/set-cookie-parser/lib/set-cookie.js","../../src/core/cookies.ts","../../src/core/redact.ts"],"sourcesContent":["import { ItdConfigError } from './errors.js';\n\n/**\n * Как библиотека обращается с cookie.\n *\n * - `browser` — cookie ведёт браузер, запросы уходят с `credentials: 'include'`;\n * - `server` — cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную;\n * - `auto` — определяется по среде исполнения (значение по умолчанию).\n */\nexport const RuntimeMode = Object.freeze({\n /** Определяется по среде исполнения. Значение по умолчанию. */\n Auto: 'auto',\n /** Cookie ведёт браузер, запросы уходят с `credentials: 'include'`. */\n Browser: 'browser',\n /** Cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную. */\n Server: 'server',\n} as const);\nexport type RuntimeMode = (typeof RuntimeMode)[keyof typeof RuntimeMode];\n\n/** Распознанная среда исполнения. */\nexport const DetectedRuntime = Object.freeze({\n Browser: 'browser',\n /** Есть `window`, но нет `document`; cookie ведёт нативный сетевой слой. */\n ReactNative: 'react-native',\n Server: 'server',\n} as const);\nexport type DetectedRuntime = (typeof DetectedRuntime)[keyof typeof DetectedRuntime];\n\n/**\n * Определяет среду исполнения.\n *\n * React Native выделен отдельно намеренно: там есть `window`, но нет `document`, а cookie\n * хранит нативный сетевой слой (`NSHTTPCookieStorage` / `OkHttp`). Свой jar в этой среде\n * привёл бы к отправке cookie дважды.\n */\nexport function detectRuntime(): DetectedRuntime {\n const nav = (globalThis as { navigator?: { product?: string } }).navigator;\n if (nav?.product === 'ReactNative') return DetectedRuntime.ReactNative;\n\n if (typeof document !== 'undefined') return DetectedRuntime.Browser;\n\n return DetectedRuntime.Server;\n}\n\n/**\n * Нужно ли вести собственный cookie-jar.\n *\n * Только в серверных средах: в браузере `Set-Cookie` из JS не читается и не нужен,\n * в React Native cookie ведёт нативный слой.\n */\nexport function shouldUseCookieJar(mode: RuntimeMode): boolean {\n if (mode === RuntimeMode.Browser) return false;\n if (mode === RuntimeMode.Server) return true;\n return detectRuntime() === DetectedRuntime.Server;\n}\n\n/**\n * Нужно ли отправлять запросы с `credentials: 'include'`.\n *\n * Только в браузере: именно так refresh-cookie попадает в запрос `POST /api/v1/auth/refresh`.\n * Требует настроенного CORS на стороне итд.com — если его нет, укажите свой прокси в `baseUrl`.\n */\nexport function shouldSendCredentials(mode: RuntimeMode): boolean {\n if (mode === RuntimeMode.Browser) return true;\n if (mode === RuntimeMode.Server) return false;\n return detectRuntime() === DetectedRuntime.Browser;\n}\n\n/**\n * Возвращает реализацию `fetch`.\n *\n * @param custom реализация из конфигурации клиента, если пользователь её передал\n * @throws {ItdConfigError} если `fetch` недоступен — например, на Node ниже 18\n */\nexport function resolveFetch(custom?: typeof fetch): typeof fetch {\n if (custom !== undefined) {\n if (typeof custom !== 'function') {\n throw new ItdConfigError('fetch должен быть функцией');\n }\n return custom;\n }\n\n if (typeof globalThis.fetch === 'function') {\n // Привязка к globalThis обязательна: в некоторых средах несвязанный fetch бросает\n // «Illegal invocation».\n return globalThis.fetch.bind(globalThis);\n }\n\n throw new ItdConfigError(\n 'В этой среде нет глобального fetch. Обновитесь до Node 18+ либо передайте свою ' +\n 'реализацию через опцию fetch.',\n );\n}\n\n/**\n * Проверки бинарных типов, безопасные в любой среде.\n *\n * Обычный `instanceof` здесь недостаточен: конструктора может не быть вовсе, и тогда\n * проверка падает с `ReferenceError`, а не возвращает `false`. `Blob` есть в Node с 18.0,\n * но `File` стал глобальным только в Node 20 — при заявленной поддержке Node 18 голый\n * `instanceof File` ронял бы любую загрузку.\n */\nexport function isBlob(value: unknown): value is Blob {\n return typeof Blob !== 'undefined' && value instanceof Blob;\n}\n\n/** @see {@link isBlob} */\nexport function isFile(value: unknown): value is File {\n return typeof File !== 'undefined' && value instanceof File;\n}\n\n/** Доступно ли потоковое чтение тела ответа — от этого зависит, сработает ли SSE. */\nexport function supportsStreamingBody(): boolean {\n return typeof ReadableStream !== 'undefined' && typeof TextDecoder !== 'undefined';\n}\n\n/**\n * Создаёт идентификатор устройства для заголовка `X-Device-Id` — UUID v4.\n *\n * `crypto.randomUUID` есть в Node 19+, браузерах и Deno, но отсутствует в Node 18 вне\n * защищённого контекста, поэтому предусмотрен запасной путь.\n */\nexport function createDeviceId(): string {\n const webCrypto = (globalThis as { crypto?: Crypto }).crypto;\n\n if (typeof webCrypto?.randomUUID === 'function') return webCrypto.randomUUID();\n\n // Форма UUID v4 собирается вручную: серверу важна стабильность значения, а не его энтропия.\n const bytes = new Uint8Array(16);\n if (typeof webCrypto?.getRandomValues === 'function') webCrypto.getRandomValues(bytes);\n else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);\n\n // biome-ignore lint/style/noNonNullAssertion: длина массива фиксирована выше\n bytes[6] = (bytes[6]! & 0x0f) | 0x40;\n // biome-ignore lint/style/noNonNullAssertion: длина массива фиксирована выше\n bytes[8] = (bytes[8]! & 0x3f) | 0x80;\n\n const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');\n\n return [\n hex.slice(0, 8),\n hex.slice(8, 12),\n hex.slice(12, 16),\n hex.slice(16, 20),\n hex.slice(20, 32),\n ].join('-');\n}\n","import { ItdConfigError } from './errors.js';\n\n/** Значение параметра запроса. `undefined` и `null` в строку не попадают. */\nexport type QueryValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | readonly (string | number | boolean)[];\n\n/** Параметры строки запроса. */\nexport type QueryParams = Record<string, QueryValue>;\n\n/**\n * Собирает строку запроса.\n *\n * Правила:\n * - `undefined` и `null` пропускаются целиком — не нужно чистить объект перед вызовом;\n * - `boolean` превращается в `true` / `false`;\n * - массив повторяет ключ (`ids=1&ids=2`).\n *\n * @returns строка вида `?a=1&b=2` либо пустая строка, если параметров нет\n *\n * @example\n * ```ts\n * buildQuery({ tab: 'popular', limit: 20, cursor: undefined });\n * // '?tab=popular&limit=20'\n * ```\n */\nexport function buildQuery(params?: QueryParams): string {\n if (!params) return '';\n\n const search = new URLSearchParams();\n\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item === undefined || item === null) continue;\n search.append(key, String(item));\n }\n continue;\n }\n\n search.append(key, String(value));\n }\n\n const query = search.toString();\n return query ? `?${query}` : '';\n}\n\n/**\n * Кодирует значение для подстановки в путь.\n *\n * Нужно для сегментов, которые могут содержать что угодно, — прежде всего хэштегов\n * в `/api/hashtags/{tag}/posts`.\n *\n * @throws {ItdConfigError} если значение пустое\n */\nexport function encodePathSegment(value: string, name = 'параметр пути'): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new ItdConfigError(\n `${name} должен быть непустой строкой, получено: ${JSON.stringify(value)}`,\n );\n }\n return encodeURIComponent(value);\n}\n\n/** Хост из URL в нижнем регистре. Пустая строка, если разобрать не удалось. */\nexport function hostOf(url: string): string {\n try {\n return new URL(url).hostname.toLowerCase();\n } catch {\n return '';\n }\n}\n\n/** Origin из URL — схема, хост и порт. Пустая строка, если разобрать не удалось. */\nexport function originOf(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return '';\n }\n}\n\n/** Тот же хост либо его поддомен. Принимает хосты, а не адреса. */\nexport function isSameSite(primaryHost: string, host: string): boolean {\n if (!primaryHost || !host) return false;\n return host === primaryHost || host.endsWith(`.${primaryHost}`);\n}\n\n/**\n * Склеивает базовый URL и путь.\n *\n * Завершающий слэш пути сохраняется: он значим для `/api/notifications/`\n * и `/api/v1/subscription/`, без него сервер отвечает ошибкой.\n *\n * @example\n * ```ts\n * joinUrl('https://xn--d1ah4a.com/', '/api/notifications/');\n * // 'https://xn--d1ah4a.com/api/notifications/'\n * ```\n */\nexport function joinUrl(baseUrl: string, path: string): string {\n const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n const suffix = path.startsWith('/') ? path : `/${path}`;\n return `${base}${suffix}`;\n}\n\n/** Приводит базовый URL к каноничному виду и проверяет, что он вообще похож на URL. */\nexport function normalizeBaseUrl(baseUrl: string): string {\n if (typeof baseUrl !== 'string' || baseUrl.trim() === '') {\n throw new ItdConfigError(\n `baseUrl должен быть непустой строкой с абсолютным URL, получено: ${JSON.stringify(baseUrl)}`,\n );\n }\n\n let parsed: URL;\n try {\n parsed = new URL(baseUrl);\n } catch {\n throw new ItdConfigError(\n `baseUrl должен быть абсолютным URL, получено: ${JSON.stringify(baseUrl)}`,\n );\n }\n\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new ItdConfigError(\n `baseUrl должен использовать http или https, получено: ${parsed.protocol}`,\n );\n }\n\n if (parsed.username || parsed.password || parsed.search || parsed.hash) {\n throw new ItdConfigError(\n 'baseUrl не должен содержать логин, пароль, query-параметры или fragment',\n );\n }\n\n return parsed.origin + (parsed.pathname === '/' ? '' : parsed.pathname.replace(/\\/$/, ''));\n}\n","var defaultParseOptions = {\n decodeValues: true,\n map: false,\n silent: false,\n split: \"auto\", // auto = split strings but not arrays\n};\n\nfunction isForbiddenKey(key) {\n return typeof key !== \"string\" || key in {};\n}\n\nfunction createNullObj() {\n return Object.create(null);\n}\n\nfunction isNonEmptyString(str) {\n return typeof str === \"string\" && !!str.trim();\n}\n\nfunction parseString(setCookieValue, options) {\n var parts = setCookieValue.split(\";\").filter(isNonEmptyString);\n\n var nameValuePairStr = parts.shift();\n if (!nameValuePairStr) {\n return null;\n }\n var parsed = parseNameValuePair(nameValuePairStr);\n var name = parsed.name;\n var value = parsed.value;\n\n options = options\n ? Object.assign({}, defaultParseOptions, options)\n : defaultParseOptions;\n\n if (isForbiddenKey(name)) {\n return null;\n }\n\n try {\n value = options.decodeValues ? decodeURIComponent(value) : value; // decode cookie value\n } catch (e) {\n console.error(\n \"set-cookie-parser: failed to decode cookie value. Set options.decodeValues=false to disable decoding.\",\n e\n );\n }\n\n var cookie = createNullObj();\n cookie.name = name;\n cookie.value = value;\n\n parts.forEach(function (part) {\n var sides = part.split(\"=\");\n var key = sides.shift().trim().toLowerCase();\n if (isForbiddenKey(key)) {\n return;\n }\n var value = sides.join(\"=\").trim();\n if (key === \"expires\") {\n cookie.expires = new Date(value);\n } else if (key === \"max-age\") {\n var n = parseInt(value, 10);\n if (!Number.isNaN(n)) cookie.maxAge = n;\n } else if (key === \"secure\") {\n cookie.secure = true;\n } else if (key === \"httponly\") {\n cookie.httpOnly = true;\n } else if (key === \"samesite\") {\n cookie.sameSite = value;\n } else if (key === \"partitioned\") {\n cookie.partitioned = true;\n } else if (key) {\n cookie[key] = value;\n }\n });\n\n return cookie;\n}\n\nfunction parseNameValuePair(nameValuePairStr) {\n // Parses name-value-pair according to rfc6265bis draft\n\n var name = \"\";\n var value = \"\";\n var nameValueArr = nameValuePairStr.split(\"=\");\n if (nameValueArr.length > 1) {\n name = nameValueArr.shift();\n value = nameValueArr.join(\"=\"); // everything after the first =, joined by a \"=\" if there was more than one part\n } else {\n value = nameValuePairStr;\n }\n\n return { name: name, value: value };\n}\n\nfunction parseSetCookie(input, options) {\n options = options\n ? Object.assign({}, defaultParseOptions, options)\n : defaultParseOptions;\n\n if (!input) {\n if (!options.map) {\n return [];\n } else {\n return createNullObj();\n }\n }\n\n if (input.headers) {\n if (typeof input.headers.getSetCookie === \"function\") {\n // for fetch responses - they combine headers of the same type in the headers array,\n // but getSetCookie returns an uncombined array\n input = input.headers.getSetCookie();\n } else if (input.headers[\"set-cookie\"]) {\n // fast-path for node.js (which automatically normalizes header names to lower-case)\n input = input.headers[\"set-cookie\"];\n } else {\n // slow-path for other environments - see #25\n var sch =\n input.headers[\n Object.keys(input.headers).find(function (key) {\n return key.toLowerCase() === \"set-cookie\";\n })\n ];\n // warn if called on a request-like object with a cookie header rather than a set-cookie header - see #34, 36\n if (!sch && input.headers.cookie && !options.silent) {\n console.warn(\n \"Warning: set-cookie-parser appears to have been called on a request object. It is designed to parse Set-Cookie headers from responses, not Cookie headers from requests. Set the option {silent: true} to suppress this warning.\"\n );\n }\n input = sch;\n }\n }\n\n var split = options.split;\n var isArray = Array.isArray(input);\n\n if (split === \"auto\") {\n split = !isArray;\n }\n\n if (!isArray) {\n input = [input];\n }\n\n input = input.filter(isNonEmptyString);\n\n if (split) {\n input = input.map(splitCookiesString).flat();\n }\n\n if (!options.map) {\n return input\n .map(function (str) {\n return parseString(str, options);\n })\n .filter(Boolean);\n } else {\n var cookies = createNullObj();\n return input.reduce(function (cookies, str) {\n var cookie = parseString(str, options);\n if (cookie && !isForbiddenKey(cookie.name)) {\n cookies[cookie.name] = cookie;\n }\n return cookies;\n }, cookies);\n }\n}\n\n/*\n Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas\n that are within a single set-cookie field-value, such as in the Expires portion.\n\n This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2\n Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128\n React Native's fetch does this for *every* header, including set-cookie.\n\n Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25\n Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation\n*/\nfunction splitCookiesString(cookiesString) {\n if (Array.isArray(cookiesString)) {\n return cookiesString;\n }\n if (typeof cookiesString !== \"string\") {\n return [];\n }\n\n var cookiesStrings = [];\n var pos = 0;\n var start;\n var ch;\n var lastComma;\n var nextStart;\n var cookiesSeparatorFound;\n\n function skipWhitespace() {\n while (pos < cookiesString.length && /\\s/.test(cookiesString.charAt(pos))) {\n pos += 1;\n }\n return pos < cookiesString.length;\n }\n\n function notSpecialChar() {\n ch = cookiesString.charAt(pos);\n\n return ch !== \"=\" && ch !== \";\" && ch !== \",\";\n }\n\n while (pos < cookiesString.length) {\n start = pos;\n cookiesSeparatorFound = false;\n\n while (skipWhitespace()) {\n ch = cookiesString.charAt(pos);\n if (ch === \",\") {\n // ',' is a cookie separator if we have later first '=', not ';' or ','\n lastComma = pos;\n pos += 1;\n\n skipWhitespace();\n nextStart = pos;\n\n while (pos < cookiesString.length && notSpecialChar()) {\n pos += 1;\n }\n\n // currently special character\n if (pos < cookiesString.length && cookiesString.charAt(pos) === \"=\") {\n // we found cookies separator\n cookiesSeparatorFound = true;\n // pos is inside the next cookie, so back up and return it.\n pos = nextStart;\n cookiesStrings.push(cookiesString.substring(start, lastComma));\n start = pos;\n } else {\n // in param ',' or param separator ';',\n // we continue from that comma\n pos = lastComma + 1;\n }\n } else {\n pos += 1;\n }\n }\n\n if (!cookiesSeparatorFound || pos >= cookiesString.length) {\n cookiesStrings.push(cookiesString.substring(start, cookiesString.length));\n }\n }\n\n return cookiesStrings;\n}\n\n// named export for CJS\nparseSetCookie.parseSetCookie = parseSetCookie;\n// for backwards compatibility\nparseSetCookie.parse = parseSetCookie;\nparseSetCookie.parseString = parseString;\nparseSetCookie.splitCookiesString = splitCookiesString;\n\n// EXPORTS\n// (this section is replaced by build-cjs.js)\n\n// named export for ESM\nexport { parseSetCookie };\n// for backwards compatibility\nexport default parseSetCookie;\nexport { parseSetCookie as parse, parseString, splitCookiesString };\n","import { parse as parseSetCookie, splitCookiesString } from 'set-cookie-parser';\nimport { originOf } from './url.js';\n\n/** Cookie, сохранённая в jar. */\ninterface StoredCookie {\n name: string;\n value: string;\n path: string;\n /** Момент истечения, мс с начала эпохи. `undefined` — сессионная cookie. */\n expires: number | undefined;\n secure: boolean;\n}\n\n/** Имя cookie-флага «есть refresh-сессия». Ставится сайтом итд.com рядом с refresh-токеном. */\nexport const AUTH_FLAG_COOKIE = 'is_auth';\n\n/**\n * Имя cookie с refresh-токеном.\n *\n * `POST /api/v1/auth/refresh` читает токен **только отсюда** — тело запроса сервер игнорирует.\n * Cookie помечена `HttpOnly`, поэтому в браузере её не прочитать и не выставить; вне браузера\n * она проходит через jar, и туда же кладётся токен, переданный строкой.\n */\nexport const REFRESH_COOKIE = 'refresh_token';\n\n/** Путь, которым сервер ограничивает {@link REFRESH_COOKIE}. */\nexport const REFRESH_COOKIE_PATH = '/api/v1/auth';\n\n/** Разделитель origin и содержимого cookie при сериализации. В origin пробелов не бывает. */\nconst SERIALIZED_SEPARATOR = ' ';\nconst COOKIE_NAME = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\n\n/** Проверяет одну запись, созданную {@link CookieJar.serialize}. @internal */\nexport function isSerializedCookieEntry(entry: string): boolean {\n const separatorAt = entry.indexOf(SERIALIZED_SEPARATOR);\n if (separatorAt <= 0) return false;\n\n const origin = entry.slice(0, separatorAt);\n if (originOf(origin) !== origin) return false;\n\n const parsed = parseSetCookie([entry.slice(separatorAt + 1)]);\n return parsed.length === 1 && COOKIE_NAME.test(parsed[0]?.name ?? '');\n}\n\n/** Дата в миллисекунды. Некорректная дата считается отсутствующей, а не `NaN`. */\nfunction toTimestamp(date: Date | undefined): number | undefined {\n if (!date) return undefined;\n const time = date.getTime();\n return Number.isFinite(time) ? time : undefined;\n}\n\nfunction pathOf(url: string): string {\n try {\n return new URL(url).pathname || '/';\n } catch {\n return '/';\n }\n}\n\n/**\n * Подходит ли путь cookie запросу.\n *\n * Правило из RFC 6265: путь cookie должен быть префиксом пути запроса, причём граница\n * должна проходить по сегменту — `/api` не подходит запросу `/apiary`.\n */\nfunction pathMatches(cookiePath: string, requestPath: string): boolean {\n if (cookiePath === '/' || cookiePath === requestPath) return true;\n if (!requestPath.startsWith(cookiePath)) return false;\n return cookiePath.endsWith('/') || requestPath[cookiePath.length] === '/';\n}\n\n/**\n * Минимальное хранилище cookie для сред без своего.\n *\n * Refresh-токен итд.com приходит в `Set-Cookie`, а `fetch` вне браузера cookie не хранит —\n * без jar сессию не продлить. В браузере и React Native не используется: там cookie ведёт\n * сама среда.\n *\n * Реализована практическая часть RFC 6265: origin, путь, срок жизни, флаг `Secure`.\n * Доменные cookie для поддоменов намеренно не поддерживаются — API работает с одного хоста.\n */\nexport class CookieJar {\n readonly #byOrigin = new Map<string, Map<string, StoredCookie>>();\n\n /** Независимая копия для auth-flow, который ещё не получил право на commit. */\n clone(): CookieJar {\n const copy = new CookieJar();\n copy.deserialize(this.serialize());\n return copy;\n }\n\n /** Атомарно с точки зрения синхронных читателей заменяет содержимое снимком другого jar. */\n replaceWith(source: CookieJar): void {\n const entries = source.serialize();\n this.clear();\n this.deserialize(entries);\n }\n\n /**\n * Забирает `Set-Cookie` из ответа.\n *\n * Использует `Headers.getSetCookie()`, где он есть (Node 20+, undici). В остальных средах\n * заголовки склеены в одну строку, и её нельзя резать по запятой напрямую: запятая есть\n * внутри `Expires=Wed, 09 Jun 2027 …`. Разделением занимается `set-cookie-parser`.\n */\n setFromResponse(url: string, response: Response): void {\n const headers = response.headers as Headers & { getSetCookie?: () => string[] };\n\n const raw =\n typeof headers.getSetCookie === 'function'\n ? headers.getSetCookie()\n : splitCookiesString(headers.get('set-cookie') ?? '');\n\n if (raw.length > 0) this.setFromStrings(url, raw);\n }\n\n /**\n * Кладёт cookie напрямую, минуя `Set-Cookie`.\n *\n * Нужно ровно в одном случае: пользователь передал refresh-токен строкой, а сервер читает\n * его только из cookie. Значение не кодируется — оно уходит в заголовок как есть.\n */\n set(url: string, name: string, value: string, path = '/'): void {\n this.setFromStrings(url, [`${name}=${value}; Path=${path}`]);\n }\n\n /** Сохраняет cookie из готовых строк `Set-Cookie`. */\n setFromStrings(url: string, setCookieStrings: string[]): void {\n const origin = originOf(url);\n if (!origin) return;\n\n const jar = this.#byOrigin.get(origin) ?? new Map<string, StoredCookie>();\n\n for (const parsed of parseSetCookie(setCookieStrings)) {\n // Битую дату (её легко получить, если заголовки склеены неправильно) считаем\n // отсутствующей: сессионная cookie лучше, чем cookie со сроком NaN, которая\n // не истекает и одновременно нигде не находится.\n const expires = toTimestamp(parsed.expires);\n const maxAgeExpires =\n typeof parsed.maxAge === 'number' && Number.isFinite(parsed.maxAge)\n ? Date.now() + parsed.maxAge * 1000\n : undefined;\n\n // Max-Age приоритетнее Expires — так требует RFC 6265.\n const expiresAt = maxAgeExpires ?? expires;\n\n // Удаление cookie сервером: срок в прошлом либо Max-Age=0.\n if (expiresAt !== undefined && expiresAt <= Date.now()) {\n jar.delete(parsed.name);\n continue;\n }\n\n jar.set(parsed.name, {\n name: parsed.name,\n value: parsed.value,\n path: parsed.path ?? '/',\n expires: expiresAt,\n secure: parsed.secure ?? false,\n });\n }\n\n this.#byOrigin.set(origin, jar);\n }\n\n /**\n * Собирает значение заголовка `Cookie` для запроса.\n *\n * @returns строка вида `a=1; b=2` либо `undefined`, если подходящих cookie нет\n */\n getHeader(url: string): string | undefined {\n const cookies = this.#matching(url);\n if (cookies.length === 0) return undefined;\n return cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ');\n }\n\n /**\n * Значение действующей cookie.\n *\n * Нужно, чтобы забрать обновлённый refresh-токен: сервер ротирует его при каждом\n * продлении сессии, и сохранять надо именно новое значение.\n *\n * @param url если указан, учитываются origin, путь и флаг `Secure`\n */\n getValue(name: string, url?: string): string | undefined {\n if (url) return this.#matching(url).find((cookie) => cookie.name === name)?.value;\n\n const now = Date.now();\n for (const jar of this.#byOrigin.values()) {\n const cookie = jar.get(name);\n if (cookie && (cookie.expires === undefined || cookie.expires > now)) return cookie.value;\n }\n return undefined;\n }\n\n /**\n * Есть ли действующая cookie с таким именем.\n *\n * Используется для проверки флага {@link AUTH_FLAG_COOKIE} перед запросом обновления\n * токена: у анонима refresh-сессии нет, и дёргать API незачем.\n */\n has(name: string, url?: string): boolean {\n if (url) return this.#matching(url).some((cookie) => cookie.name === name);\n\n const now = Date.now();\n for (const jar of this.#byOrigin.values()) {\n const cookie = jar.get(name);\n if (cookie && (cookie.expires === undefined || cookie.expires > now)) return true;\n }\n return false;\n }\n\n /** Сохраняет содержимое jar для записи в {@link TokenStorage}. */\n serialize(): string[] {\n const result: string[] = [];\n const now = Date.now();\n\n for (const [origin, jar] of this.#byOrigin) {\n for (const cookie of jar.values()) {\n if (cookie.expires !== undefined && cookie.expires <= now) continue;\n\n const parts = [`${cookie.name}=${cookie.value}`, `Path=${cookie.path}`];\n if (cookie.expires !== undefined) {\n parts.push(`Expires=${new Date(cookie.expires).toUTCString()}`);\n }\n if (cookie.secure) parts.push('Secure');\n\n result.push(`${origin}${SERIALIZED_SEPARATOR}${parts.join('; ')}`);\n }\n }\n\n return result;\n }\n\n /** Восстанавливает jar из результата {@link serialize}. Некорректные записи молча пропускаются. */\n deserialize(entries: readonly string[] | undefined): void {\n if (!entries) return;\n\n for (const entry of entries) {\n if (!isSerializedCookieEntry(entry)) continue;\n const separatorAt = entry.indexOf(SERIALIZED_SEPARATOR);\n\n const origin = entry.slice(0, separatorAt);\n const setCookie = entry.slice(separatorAt + 1);\n\n this.setFromStrings(origin, [setCookie]);\n }\n }\n\n /** Удаляет все cookie. */\n clear(): void {\n this.#byOrigin.clear();\n }\n\n /** Действующие cookie, подходящие запросу: тот же origin, подходящий путь, не истёкшие. */\n #matching(url: string): StoredCookie[] {\n const origin = originOf(url);\n const jar = this.#byOrigin.get(origin);\n if (!jar) return [];\n\n const isSecureRequest = origin.startsWith('https:');\n const requestPath = pathOf(url);\n const now = Date.now();\n const result: StoredCookie[] = [];\n\n for (const cookie of jar.values()) {\n if (cookie.expires !== undefined && cookie.expires <= now) {\n jar.delete(cookie.name);\n continue;\n }\n if (cookie.secure && !isSecureRequest) continue;\n if (!pathMatches(cookie.path, requestPath)) continue;\n\n result.push(cookie);\n }\n\n return result;\n }\n}\n","import { isBlob } from './runtime.js';\n\n/** Заголовки, значение которых нельзя писать в лог целиком. */\nconst SECRET_HEADERS = new Set([\n 'authorization',\n 'cookie',\n 'set-cookie',\n 'x-api-key',\n 'x-order-token',\n]);\n\n/** Поля тела запроса, значение которых нельзя писать в лог. */\nconst SECRET_FIELDS = new Set([\n 'password',\n 'oldpassword',\n 'newpassword',\n 'accesstoken',\n 'refreshtoken',\n 'currentpassword',\n 'flowtoken',\n 'token',\n 'turnstiletoken',\n 'otp',\n 'pass',\n 'email',\n 'phone',\n 'address',\n 'orderaccesstoken',\n]);\n\n/** Query-параметры, содержащие секреты. */\nconst SECRET_QUERY_PARAMS = new Set(['token', 'access_token', 'c']);\n\n/**\n * Прячет середину секрета, оставляя концы для сопоставления.\n *\n * @example\n * ```ts\n * maskSecret('eyJhbGciOiJIUzI1NiJ9.abc'); // 'eyJh…(24)…abc'\n * ```\n */\nexport function maskSecret(value: string): string {\n if (value.length <= 8) return '…';\n return `${value.slice(0, 4)}…(${value.length})…${value.slice(-3)}`;\n}\n\n/** Маскирует секретные query-параметры URL перед записью в логи и ошибки. */\nexport function redactUrl(url: string): string {\n try {\n const parsed = new URL(url);\n const entries = [...parsed.searchParams.entries()];\n parsed.search = '';\n for (const [name, value] of entries) {\n parsed.searchParams.append(\n name,\n SECRET_QUERY_PARAMS.has(name.toLowerCase()) ? maskSecret(value) : value,\n );\n }\n return parsed.toString();\n } catch {\n return url.replace(/([?&](?:token|access_token|c)=)[^&#]*/gi, '$1[скрыто]');\n }\n}\n\n/** Маскирует подписанные адреса внутри произвольного диагностического текста. */\nexport function redactUrlsInText(text: string): string {\n return text.replace(/https?:\\/\\/[^\\s\"'<>]+/gi, (url) => redactUrl(url));\n}\n\n/**\n * Создаёт безопасную причину ошибки для публичной цепочки `cause`.\n *\n * Чужие объекты не сохраняются: их поля и пользовательские методы нельзя надёжно\n * сериализовать без риска вывести секрет. Для Error и строк остаются тип и сообщение,\n * но все встреченные в сообщении HTTP(S)-адреса проходят маскирование.\n */\nexport function redactErrorCause(error: unknown): Error | undefined {\n const message =\n error instanceof Error ? error.message : typeof error === 'string' ? error : undefined;\n if (message === undefined) return undefined;\n\n const safeMessage = redactUrlsInText(message);\n const safe = error instanceof TypeError ? new TypeError(safeMessage) : new Error(safeMessage);\n if (error instanceof Error && !(error instanceof TypeError)) safe.name = error.name;\n return safe;\n}\n\n/**\n * Готовит заголовки к записи в лог.\n *\n * `Authorization` и `Cookie` маскируются: логи часто уезжают в системы сбора,\n * и токен доступа в них попадать не должен.\n */\nexport function redactHeaders(headers: Headers): Record<string, string> {\n const result: Record<string, string> = {};\n\n headers.forEach((value, name) => {\n if (SECRET_HEADERS.has(name.toLowerCase())) {\n // У Authorization маскируем только сам токен, схему («Bearer») оставляем видимой.\n const spaceAt = value.indexOf(' ');\n result[name] =\n spaceAt > 0\n ? `${value.slice(0, spaceAt)} ${maskSecret(value.slice(spaceAt + 1))}`\n : maskSecret(value);\n return;\n }\n result[name] = value;\n });\n\n return result;\n}\n\n/**\n * Готовит тело запроса к записи в лог: пароли, токены и коды OTP заменяются заглушкой.\n *\n * Обходит вложенные объекты и массивы. `FormData` и бинарные тела не раскрываются вовсе.\n */\nexport function redactBody(body: unknown): unknown {\n if (body === null || body === undefined) return body;\n\n if (typeof FormData !== 'undefined' && body instanceof FormData) return '[FormData]';\n if (isBlob(body)) return '[Blob]';\n if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return '[binary]';\n\n if (Array.isArray(body)) return body.map(redactBody);\n\n if (typeof body === 'object') {\n const result: Record<string, unknown> = {};\n const entries = Object.entries(body);\n const containsEmail = entries.some(([key]) => key.toLowerCase() === 'email');\n for (const [key, value] of entries) {\n const normalized = key.toLowerCase();\n const secret = SECRET_FIELDS.has(normalized) || (normalized === 'code' && containsEmail);\n result[key] = secret ? '[скрыто]' : redactBody(value);\n }\n return result;\n }\n\n return body;\n}\n"],"x_google_ignoreList":[2],"mappings":"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"}
|