itd-api 0.8.0 → 0.9.1

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.
Files changed (80) hide show
  1. package/README.md +66 -21
  2. package/dist/events/index.cjs +3 -3
  3. package/dist/events/index.d.cts +2 -2
  4. package/dist/events/index.d.ts +2 -2
  5. package/dist/events/index.js +3 -3
  6. package/dist/index.cjs +26 -13
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +9 -6
  9. package/dist/index.d.ts +9 -6
  10. package/dist/index.js +13 -8
  11. package/dist/index.js.map +1 -1
  12. package/dist/node/index.cjs +5 -4
  13. package/dist/node/index.cjs.map +1 -1
  14. package/dist/node/index.d.cts +2 -2
  15. package/dist/node/index.d.ts +2 -2
  16. package/dist/node/index.js +4 -3
  17. package/dist/node/index.js.map +1 -1
  18. package/dist/rest/index.cjs +16 -4
  19. package/dist/rest/index.cjs.map +1 -1
  20. package/dist/rest/index.d.cts +5 -3
  21. package/dist/rest/index.d.ts +5 -3
  22. package/dist/rest/index.js +9 -5
  23. package/dist/rest/index.js.map +1 -1
  24. package/dist/shared/{events-QyhQZrnC.d.ts → events-BDdJyH2p.d.ts} +2 -2
  25. package/dist/shared/{events-Br2r7coi.d.cts → events-BQyH5-S0.d.cts} +2 -2
  26. package/dist/shared/{events-iTteVsd_.cjs → events-BqFkwwTw.cjs} +3 -3
  27. package/dist/shared/{events-iTteVsd_.cjs.map → events-BqFkwwTw.cjs.map} +1 -1
  28. package/dist/shared/{events-TfhKh26b.js → events-D6y0Hxyz.js} +3 -3
  29. package/dist/shared/{events-TfhKh26b.js.map → events-D6y0Hxyz.js.map} +1 -1
  30. package/dist/shared/{storage-BLmXmzKF.cjs → key-value-store-B5GYVEYZ.cjs} +1 -85
  31. package/dist/shared/key-value-store-B5GYVEYZ.cjs.map +1 -0
  32. package/dist/shared/{storage-CCxn0Kkv.js → key-value-store-Bt2PSMxY.js} +2 -68
  33. package/dist/shared/key-value-store-Bt2PSMxY.js.map +1 -0
  34. package/dist/shared/key-value-store-COZaNHZS.d.cts +69 -0
  35. package/dist/shared/key-value-store-COZaNHZS.d.ts +69 -0
  36. package/dist/shared/{multi-storage-DccjD7Ww.d.ts → multi-storage-BEL7tcKn.d.cts} +3 -2
  37. package/dist/shared/{multi-storage-CCUgkqvI.cjs → multi-storage-CDKLa956.cjs} +8 -7
  38. package/dist/shared/{multi-storage-CCUgkqvI.cjs.map → multi-storage-CDKLa956.cjs.map} +1 -1
  39. package/dist/shared/{multi-storage-CjAPB5Kq.d.cts → multi-storage-LkljOSzA.d.ts} +3 -2
  40. package/dist/shared/{multi-storage-1AEZTczk.js → multi-storage-SoW05uzO.js} +4 -3
  41. package/dist/shared/{multi-storage-1AEZTczk.js.map → multi-storage-SoW05uzO.js.map} +1 -1
  42. package/dist/shared/{options-BUfoHE9G.cjs → options-DOJYtoti.cjs} +2 -2
  43. package/dist/shared/{options-BUfoHE9G.cjs.map → options-DOJYtoti.cjs.map} +1 -1
  44. package/dist/shared/{options-BNP633A5.js → options-DhgGg7Ms.js} +2 -2
  45. package/dist/shared/{options-BNP633A5.js.map → options-DhgGg7Ms.js.map} +1 -1
  46. package/dist/shared/{redact-DB9EMGJx.cjs → redact-BP00URmQ.cjs} +15 -4
  47. package/dist/shared/{redact-DB9EMGJx.cjs.map → redact-BP00URmQ.cjs.map} +1 -1
  48. package/dist/shared/{redact-FNK29Fv-.js → redact-Ba5iSVYZ.js} +15 -4
  49. package/dist/shared/{redact-FNK29Fv-.js.map → redact-Ba5iSVYZ.js.map} +1 -1
  50. package/dist/shared/{render-BszcKF7S.d.cts → render-BU1_wex8.d.cts} +378 -12
  51. package/dist/shared/{render-DVB7T8pm.d.ts → render-CZ_-F2Tl.d.ts} +378 -12
  52. package/dist/shared/{render-DNTESBSl.cjs → render-DAEeCMk9.cjs} +461 -8
  53. package/dist/shared/render-DAEeCMk9.cjs.map +1 -0
  54. package/dist/shared/{render-CiL1VS0b.js → render-sb1VJhUA.js} +415 -10
  55. package/dist/shared/render-sb1VJhUA.js.map +1 -0
  56. package/dist/shared/storage-BQrtKon9.d.cts +86 -0
  57. package/dist/shared/storage-CTRGT5wF.d.ts +86 -0
  58. package/dist/shared/storage-CfHKzVHf.js +71 -0
  59. package/dist/shared/storage-CfHKzVHf.js.map +1 -0
  60. package/dist/shared/storage-QNVxzpDB.cjs +88 -0
  61. package/dist/shared/storage-QNVxzpDB.cjs.map +1 -0
  62. package/dist/shared/{url-Be9jlWRv.d.ts → url-CZZcyPI0.d.cts} +92 -5
  63. package/dist/shared/{url-Be9jlWRv.d.cts → url-CZZcyPI0.d.ts} +92 -5
  64. package/dist/shared/{url-CFN1sT9S.cjs → url-CzHqE-Fg.cjs} +83 -4
  65. package/dist/shared/{url-CFN1sT9S.cjs.map → url-CzHqE-Fg.cjs.map} +1 -1
  66. package/dist/shared/{url-B3ocFlHj.js → url-Dsr4c5r5.js} +78 -5
  67. package/dist/shared/{url-B3ocFlHj.js.map → url-Dsr4c5r5.js.map} +1 -1
  68. package/dist/web/index.cjs +4 -3
  69. package/dist/web/index.cjs.map +1 -1
  70. package/dist/web/index.d.cts +1 -1
  71. package/dist/web/index.d.ts +1 -1
  72. package/dist/web/index.js +2 -1
  73. package/dist/web/index.js.map +1 -1
  74. package/package.json +1 -1
  75. package/dist/shared/render-CiL1VS0b.js.map +0 -1
  76. package/dist/shared/render-DNTESBSl.cjs.map +0 -1
  77. package/dist/shared/storage-BLmXmzKF.cjs.map +0 -1
  78. package/dist/shared/storage-CCxn0Kkv.js.map +0 -1
  79. package/dist/shared/storage-C_eICCep.d.cts +0 -152
  80. package/dist/shared/storage-C_eICCep.d.ts +0 -152
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["#createEvents","#events","#config","#http","#jar","#emitter","#hooks","#invalidateInFlight","#authScope","#loadSession","#identityForToken","#readExternalToken","#session","#sessionFromConfig","#externalIdentity","#authEpoch","#externalToken","#rotateAuthScope","#hasRefreshSession","#deviceId","#deviceIdLoading","#resolveDeviceId","#saveSession","#signInWithCredentials","#refreshDeduplicated","#noRefreshSessionError","#transitionAuth","#seedRefreshCookie","#loading","#performLoad","#refreshing","#performRefresh","#reloginOrNull","#signingIn","#performSignIn","#resolveTurnstileToken","#auth","#runtime","#features","#resources","#auth","#close","#disposePromise","#dispose","#base","#storage","#clients","#removing","#accountRemovals","#storageControls","#eventUnsubscribers","#plugins","#features","#removingPlugins","#queues","#rateLimitScope","#emitter","#logger","#mergeOptions","#reportCleanup","#forwardEvents","#disposePromise","#dispose"],"sources":["../src/notifications-api.ts","../src/operations/auth.ts","../src/session/jwt.ts","../src/session/auth.ts","../src/resources/auth.ts","../src/client.ts","../src/accounts.ts"],"sourcesContent":["import type { HttpClient } from './core/execution/http.js';\nimport type { NotificationEvents } from './events/stream.js';\nimport { NotificationsResource } from './resources/notifications.js';\n\n/** Уведомления полного клиента: REST-методы и стабильный канал событий. */\nexport interface NotificationsApi extends NotificationsResource {\n /** Один канал событий уведомлений на всё время жизни клиента. */\n readonly events: NotificationEvents;\n}\n\nclass ClientNotifications extends NotificationsResource implements NotificationsApi {\n readonly #createEvents: (resource: NotificationsResource) => NotificationEvents;\n #events: NotificationEvents | undefined;\n\n constructor(\n http: HttpClient,\n createEvents: (resource: NotificationsResource) => NotificationEvents,\n ) {\n super(http);\n this.#createEvents = createEvents;\n }\n\n get events(): NotificationEvents {\n this.#events ??= this.#createEvents(this);\n return this.#events;\n }\n}\n\n/** Создаёт API уведомлений полного клиента. @internal */\nexport function createNotificationsApi(\n http: HttpClient,\n createEvents: (resource: NotificationsResource) => NotificationEvents,\n): NotificationsApi {\n return new ClientNotifications(http, createEvents);\n}\n","import { ItdConfigError } from '../core/errors.js';\nimport { pickArray, pickString } from '../core/unwrap.js';\nimport { defineBuiltInOperation } from '../domain/operations.js';\nimport type { Session } from '../models/account.js';\nimport type { AuthState } from '../models/users.js';\nimport { passthroughOperation, voidOperation } from './common.js';\n\n/** Чем закончился запрос входа. */\nexport const SignInStatus = Object.freeze({\n Authenticated: 'authenticated',\n OtpRequired: 'otp_required',\n} as const);\nexport type SignInStatus = (typeof SignInStatus)[keyof typeof SignInStatus];\n\n/** Результат входа: готовый токен либо продолжение через код подтверждения. */\nexport type SignInResult =\n | { status: 'authenticated'; accessToken: string }\n | { status: 'otp_required'; flowToken: string | undefined };\n\nfunction requireString(body: unknown, key: string, message: string): string {\n const value = pickString(body, key);\n if (!value) throw new ItdConfigError(message);\n return value;\n}\n\nexport const AUTH_CHECK = passthroughOperation<AuthState>('auth.check');\nexport const AUTH_SIGN_UP = defineBuiltInOperation<string>('auth.signUp', (body) =>\n requireString(body, 'flowToken', 'Сервер не вернул flowToken при регистрации'),\n);\nexport const AUTH_SIGN_IN = defineBuiltInOperation<SignInResult>('auth.signIn', (body) => {\n const accessToken = pickString(body, 'accessToken');\n return accessToken\n ? { status: SignInStatus.Authenticated, accessToken }\n : { status: SignInStatus.OtpRequired, flowToken: pickString(body, 'flowToken') };\n});\nexport const AUTH_VERIFY_OTP = defineBuiltInOperation<string>('auth.verifyOtp', (body) =>\n requireString(body, 'accessToken', 'Сервер не вернул accessToken после подтверждения кода'),\n);\nexport const AUTH_REFRESH = defineBuiltInOperation<string | undefined>('auth.refresh', (body) =>\n pickString(body, 'accessToken'),\n);\nexport const AUTH_RESEND_OTP = voidOperation('auth.resendOtp');\nexport const AUTH_LOGOUT = voidOperation('auth.logout');\nexport const AUTH_FORGOT_PASSWORD = defineBuiltInOperation<string>('auth.forgotPassword', (body) =>\n requireString(body, 'flowToken', 'Сервер не вернул flowToken при запросе сброса пароля'),\n);\nexport const AUTH_RESET_PASSWORD = voidOperation('auth.resetPassword');\nexport const AUTH_CHANGE_PASSWORD = voidOperation('auth.changePassword');\nexport const AUTH_SESSIONS = defineBuiltInOperation<Session[]>('auth.sessions', (body) =>\n pickArray<Session>(body, 'sessions'),\n);\nexport const AUTH_REVOKE_SESSION = voidOperation('auth.revokeSession');\nexport const AUTH_REVOKE_OTHER_SESSIONS = voidOperation('auth.revokeOtherSessions');\n","/**\n * Разбирает строку base64url.\n *\n * `atob` понимает только обычный base64, поэтому алфавит приводится к нему, а недостающее\n * выравнивание добивается. Результат декодируется как UTF-8: в полезной нагрузке токена\n * может оказаться кириллица, и «бинарная» строка из `atob` сломала бы `JSON.parse`.\n */\nfunction decodeBase64Url(segment: string): string {\n const base64 = segment.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=');\n\n const binary = atob(padded);\n const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));\n\n return new TextDecoder().decode(bytes);\n}\n\n/** Идентификаторы владельца и сессии, прочитанные из JWT без проверки подписи. */\nexport interface TokenIdentity {\n subject?: string | undefined;\n sessionId?: string | undefined;\n}\n\n/**\n * Читает `sub` и `sid` из полезной нагрузки JWT.\n *\n * Подпись не проверяется — ключа для проверки у клиента нет. Прочитанные значения служат\n * лишь метками для разделения локального состояния и не дают доступа: его определяет сервер.\n */\nexport function readTokenIdentity(token: string): TokenIdentity {\n try {\n const payload = token.split('.')[1];\n if (!payload) return {};\n\n const parsed: unknown = JSON.parse(decodeBase64Url(payload));\n if (typeof parsed !== 'object' || parsed === null) return {};\n\n const { sub, sid } = parsed as { sub?: unknown; sid?: unknown };\n return {\n ...(typeof sub === 'string' && sub.length > 0 ? { subject: sub } : {}),\n ...(typeof sid === 'string' && sid.length > 0 ? { sessionId: sid } : {}),\n };\n } catch {\n // Токен другого формата — не повод падать: поля просто останутся пустыми.\n return {};\n }\n}\n\n/**\n * Читает `sub` из полезной нагрузки JWT.\n *\n * @returns идентификатор владельца токена; `undefined`, если токен не JWT, повреждён\n * или поля `sub` в нём нет\n */\nexport function readTokenSubject(token: string): string | undefined {\n return readTokenIdentity(token).subject;\n}\n","import type { AuthIdentity, AuthProvider, AuthProviderDeps } from '../core/auth-provider.js';\nimport type { ItdClock } from '../core/clock.js';\nimport {\n AUTH_FLAG_COOKIE,\n type CookieJar,\n REFRESH_COOKIE,\n REFRESH_COOKIE_PATH,\n} from '../core/cookies.js';\nimport { Emitter, reportListenerError } from '../core/emitter.js';\nimport { ItdApiError, ItdAuthError, ItdConfigError } from '../core/errors.js';\nimport type { HttpClient } from '../core/execution/http.js';\nimport type { Logger } from '../core/options.js';\nimport { createDeviceId } from '../core/runtime.js';\nimport { isRecord, requireOptionalBoolean } from '../core/validate.js';\nimport type { UserId } from '../models/common.js';\nimport { AUTH_REFRESH, AUTH_SIGN_IN, SignInStatus } from '../operations/auth.js';\nimport { readTokenIdentity, readTokenSubject } from './jwt.js';\nimport type { AuthInput, CredentialsAuth, SessionOptions } from './options.js';\nimport { copySession, type ItdSession, MemoryTokenStorage, type TokenStorage } from './storage.js';\n\n/**\n * Настройки сессии после подстановки умолчаний и проверок.\n *\n * Часть полей заимствована у {@link ResolvedRuntimeConfig}: сессия ходит на тот же хост,\n * теми же часами и с тем же логгером, но остальные настройки исполнения ей не нужны.\n */\nexport interface SessionConfig {\n baseUrl: string;\n clock: ItdClock;\n useCookieJar: boolean;\n logger: Logger | undefined;\n auth: AuthInput | undefined;\n storage: TokenStorage;\n deviceId: string | undefined;\n autoRefresh: boolean;\n reloginOnRefreshFailure: boolean;\n}\n\n/**\n * Проверяет форму `auth` и сообщает о типичных ошибках понятным текстом.\n *\n * Молчаливое игнорирование неверной формы приводит к загадочным `401`, поэтому\n * ошибка возникает сразу при создании клиента.\n */\nfunction validateAuth(auth: AuthInput | undefined): AuthInput | undefined {\n if (auth === undefined) return undefined;\n\n if (typeof auth === 'string') {\n if (auth.trim() === '') {\n throw new ItdConfigError('auth: передана пустая строка вместо accessToken');\n }\n return auth;\n }\n\n if (typeof auth !== 'object' || auth === null) {\n throw new ItdConfigError(\n `auth должен быть строкой с токеном или объектом, получено: ${typeof auth}`,\n );\n }\n\n if ('getToken' in auth) {\n if (typeof auth.getToken !== 'function') {\n throw new ItdConfigError('auth.getToken должен быть функцией');\n }\n return { ...auth };\n }\n\n if ('accessToken' in auth) {\n if (typeof auth.accessToken !== 'string' || auth.accessToken.trim() === '') {\n throw new ItdConfigError('auth.accessToken должен быть непустой строкой');\n }\n return { ...auth };\n }\n\n if ('email' in auth || 'password' in auth) {\n const { email, password, turnstileToken, getTurnstileToken } = auth as {\n email?: unknown;\n password?: unknown;\n turnstileToken?: unknown;\n getTurnstileToken?: unknown;\n };\n if (typeof email !== 'string' || email.trim() === '') {\n throw new ItdConfigError('auth.email должен быть непустой строкой');\n }\n if (typeof password !== 'string' || password === '') {\n throw new ItdConfigError('auth.password должен быть непустой строкой');\n }\n if (getTurnstileToken !== undefined && typeof getTurnstileToken !== 'function') {\n throw new ItdConfigError('auth.getTurnstileToken должен быть функцией');\n }\n if (\n turnstileToken !== undefined &&\n (typeof turnstileToken !== 'string' || turnstileToken.trim() === '')\n ) {\n throw new ItdConfigError('auth.turnstileToken должен быть непустой строкой');\n }\n\n // Отсутствие капчи — не ошибка конфигурации: сессия может быть восстановлена из\n // хранилища, и до входа по паролю дело вообще не дойдёт. Ошибка возникнет в момент\n // входа, где её текст может объяснить, что именно нужно сделать.\n return { ...auth };\n }\n\n throw new ItdConfigError(\n 'auth не распознан. Ожидается строка с accessToken либо объект ' +\n '{ accessToken }, { email, password } или { getToken }',\n );\n}\n\nfunction resolveStorage(storage: TokenStorage | undefined): TokenStorage {\n if (storage === undefined) return new MemoryTokenStorage();\n if (!isRecord(storage)) throw new ItdConfigError('storage должен быть объектом TokenStorage');\n\n for (const method of ['get', 'set', 'clear'] as const) {\n if (typeof storage[method] !== 'function') {\n throw new ItdConfigError(`storage.${method} должен быть функцией`);\n }\n }\n return storage;\n}\n\n/**\n * Приводит настройки сессии к полному виду.\n *\n * @param runtime уже разрешённая конфигурация исполнения — из неё берутся общие поля\n * @throws {ItdConfigError} при некорректных значениях\n */\nexport function resolveSessionConfig(\n options: SessionOptions,\n runtime: Pick<SessionConfig, 'baseUrl' | 'clock' | 'useCookieJar' | 'logger'>,\n): SessionConfig {\n if (!isRecord(options)) throw new ItdConfigError('опции клиента должны быть объектом');\n\n requireOptionalBoolean(options.autoRefresh, 'autoRefresh');\n requireOptionalBoolean(options.reloginOnRefreshFailure, 'reloginOnRefreshFailure');\n\n if (\n options.deviceId !== undefined &&\n (typeof options.deviceId !== 'string' || options.deviceId.trim() === '')\n ) {\n throw new ItdConfigError('deviceId должен быть непустой строкой');\n }\n\n return {\n ...runtime,\n auth: validateAuth(options.auth),\n storage: resolveStorage(options.storage),\n deviceId: options.deviceId,\n autoRefresh: options.autoRefresh ?? true,\n reloginOnRefreshFailure: options.reloginOnRefreshFailure ?? true,\n };\n}\n\n/**\n * Собирает менеджер сессии итд.com как реализацию {@link AuthProvider}.\n *\n * Единственная точка, где конвейер запросов встречается с полноценной авторизацией.\n * Ядро её не вызывает — фабрику передаёт фасад клиента, иначе ссылка на сессию оказалась бы\n * достижимой из любой сборки, включая анонимную.\n */\nexport function createItdAuth(options: SessionOptions, deps: AuthProviderDeps): AuthManager {\n return new AuthManager(resolveSessionConfig(options, deps.config), deps.http, deps.cookies, {\n onAccountChange: deps.onAccountChange,\n });\n}\n\n/** Пути эндпоинтов авторизации. */\nexport const AUTH_PATHS = {\n signUp: '/api/v1/auth/sign-up',\n signIn: '/api/v1/auth/sign-in',\n verifyOtp: '/api/v1/auth/verify-otp',\n resendOtp: '/api/v1/auth/resend-otp',\n refresh: '/api/v1/auth/refresh',\n logout: '/api/v1/auth/logout',\n forgotPassword: '/api/v1/auth/forgot-password',\n resetPassword: '/api/v1/auth/reset-password',\n changePassword: '/api/v1/auth/change-password',\n sessions: '/api/v1/auth/sessions',\n} as const;\n\n/**\n * Публичный ключ Cloudflare Turnstile платформы итд.com.\n *\n * Нужен, чтобы отрисовать виджет капчи и получить токен для `signIn`, `signUp`\n * и `forgotPassword`.\n *\n * Ключ привязан к домену: на чужом origin Cloudflare отказывает виджету с кодом `110200`.\n * Поэтому отрисовать его может только код, выполняемый на самом итд.com. Остальным\n * подходит `@itd-api/turnstile`, готовый токен из другого источника или вовсе вход\n * без капчи — по сохранённой сессии либо по токенам, взятым в браузере.\n *\n * @example\n * ```ts\n * turnstile.render('#captcha', {\n * sitekey: TURNSTILE_SITE_KEY,\n * callback: (turnstileToken) => itd.auth.signIn({ email, password, turnstileToken }),\n * });\n * ```\n */\nexport const TURNSTILE_SITE_KEY = '0x4AAAAAACHhxczw6fJGwPBg';\n\n/** Заголовок с идентификатором устройства. Сервер связывает с ним запись в списке сессий. */\nexport const DEVICE_ID_HEADER = 'X-Device-Id';\n\n/** События слоя авторизации. */\nexport interface AuthEvents {\n /** Токен получен или обновлён. */\n tokens: { accessToken: string };\n /** Выполнен вход. */\n signIn: { accessToken: string };\n /** Сессия очищена — вручную или из-за неудачного обновления. */\n signOut: undefined;\n /** Обновить сессию не удалось; дальнейшие запросы будут падать с 401. */\n authError: { error: unknown };\n}\n\n/** Убирает поле, которое сохраняли промежуточные версии с поддержкой `getUserId()`. */\nfunction withoutLegacyUserId(session: ItdSession): ItdSession {\n const clean = { ...session };\n delete (clean as ItdSession & { userId?: unknown }).userId;\n return clean;\n}\n\nlet authScopeSequence = 0;\n\nfunction nextAuthScope(): string {\n authScopeSequence += 1;\n return String(authScopeSequence);\n}\n\ninterface AuthManagerHooks {\n /** Вызывается синхронно перед заменой владельца авторизации. */\n onAccountChange?: (() => void) | undefined;\n}\n\n/**\n * Хранит сессию и продлевает её.\n *\n * Главное здесь — **дедупликация обновления**. Когда десять параллельных запросов\n * одновременно получают `401`, обновление должно произойти один раз, а остальные обязаны\n * дождаться его результата. Иначе сервер увидит десять параллельных `refresh`, и все,\n * кроме первого, скорее всего получат отказ по уже использованному токену.\n */\nexport class AuthManager implements AuthProvider {\n readonly #config: SessionConfig;\n readonly #http: HttpClient;\n readonly #jar: CookieJar;\n readonly #emitter: Emitter<AuthEvents>;\n readonly #hooks: AuthManagerHooks;\n\n /** `undefined` — сессия ещё не читалась из хранилища. */\n #session: ItdSession | null | undefined;\n /**\n * Общий промис чтения сессии из хранилища. Дедупликация: параллельные запросы на холодном\n * старте читают хранилище один раз и не заводят каждый свой `deviceId`.\n */\n #loading: Promise<ItdSession | null> | null = null;\n /** Общий промис обновления: к нему присоединяются все, кто получил 401. */\n #refreshing: Promise<string | null> | null = null;\n /** Общий промис входа по логину и паролю. */\n #signingIn: Promise<string> | null = null;\n /** Fallback-область для изоляции состояния плагинов, когда JWT-идентичность недоступна. */\n #authScope = nextAuthScope();\n /** Последний токен внешнего источника; `undefined` означает, что источник ещё не читался. */\n #externalToken: string | null | undefined;\n /** Идентификаторы последнего токена внешнего источника для синхронных потребителей. */\n #externalIdentity: AuthIdentity | undefined;\n /**\n * Идентификатор устройства.\n *\n * Держится отдельно от сессии намеренно: выход из аккаунта не меняет устройство,\n * поэтому `clear()` его не трогает.\n */\n #deviceId: string | undefined;\n /** Общий промис первичной выдачи `deviceId` — чтобы параллельные запросы получили один. */\n #deviceIdLoading: Promise<string> | null = null;\n /**\n * Счётчик смен владельца авторизации.\n *\n * Растёт при каждой операции, которая заменяет или очищает сессию извне: `clear`,\n * `setSession`, `setAccessToken`, вход. `#performRefresh` снимает его значение перед\n * сетевым запросом и сверяет перед записью — иначе запоздавший ответ обновления мог бы\n * воскресить уже очищенную сессию поверх `signOut`.\n */\n #authEpoch = 0;\n\n constructor(\n config: SessionConfig,\n http: HttpClient,\n jar: CookieJar,\n hooks: AuthManagerHooks = {},\n ) {\n this.#config = config;\n this.#http = http;\n this.#jar = jar;\n this.#hooks = hooks;\n this.#emitter = new Emitter<AuthEvents>((error) =>\n reportListenerError(config.logger, 'авторизации', error),\n );\n }\n\n /** Подписка на события авторизации. */\n get on(): Emitter<AuthEvents>['on'] {\n return this.#emitter.on.bind(this.#emitter);\n }\n\n /** Подписка на одно срабатывание. */\n get once(): Emitter<AuthEvents>['once'] {\n return this.#emitter.once.bind(this.#emitter);\n }\n\n /** Снимает служебные подписки при окончательном освобождении владельца. @internal */\n dispose(): void {\n this.#invalidateInFlight();\n this.#emitter.removeAllListeners();\n }\n\n /**\n * Непрозрачная fallback-область авторизации.\n *\n * Изолирует состояние плагинов, когда идентичность аккаунта из JWT недоступна (непрозрачный\n * токен): значение уникально для владельца и сменяется при замене или очистке сессии.\n */\n getAuthScope(): string {\n return this.#authScope;\n }\n\n /** Загружает сессию и возвращает идентификаторы аккаунта и сессии для плагинов. */\n async getAuthIdentity(): Promise<AuthIdentity> {\n const session = await this.#loadSession();\n if (session?.accessToken) return this.#identityForToken(session.accessToken);\n\n const auth = this.#config.auth;\n if (typeof auth === 'object' && auth !== null && 'getToken' in auth) {\n const token = await this.#readExternalToken(() => auth.getToken());\n return this.#identityForToken(token ?? undefined);\n }\n\n return {};\n }\n\n /** Идентификаторы аккаунта и сессии без чтения хранилища. @internal */\n getCurrentAuthIdentity(): AuthIdentity {\n const session =\n this.#session === undefined ? this.#sessionFromConfig(this.#config.auth) : this.#session;\n return session?.accessToken\n ? this.#identityForToken(session.accessToken)\n : (this.#externalIdentity ?? {});\n }\n\n #rotateAuthScope(): void {\n this.#authScope = nextAuthScope();\n }\n\n /** Отмечает смену владельца авторизации — обесценивает результат идущего обновления. */\n #invalidateInFlight(): void {\n this.#authEpoch += 1;\n }\n\n #identityForToken(accessToken: string | undefined): AuthIdentity {\n const token = accessToken ? readTokenIdentity(accessToken) : {};\n return {\n ...(token.subject ? { userId: token.subject as UserId } : {}),\n ...(token.sessionId ? { sessionId: token.sessionId } : {}),\n };\n }\n\n async #readExternalToken(\n getToken: () => string | null | Promise<string | null>,\n ): Promise<string | null> {\n const token = (await getToken()) ?? null;\n const current = this.#identityForToken(token ?? undefined);\n\n if (this.#externalToken !== undefined && this.#externalToken !== token) {\n this.#rotateAuthScope();\n const previous = this.#externalIdentity ?? {};\n const changed =\n previous.userId !== undefined && current.userId !== undefined\n ? previous.userId !== current.userId\n : true;\n if (changed) this.#hooks.onAccountChange?.();\n }\n\n this.#externalToken = token;\n this.#externalIdentity = current;\n return token;\n }\n\n /** Обновляет резервный идентификатор и закрывает события при смене аккаунта. */\n #transitionAuth(accessToken: string | undefined, rotateFallback = true): void {\n const knownPrevious = this.#session !== undefined;\n const previous = this.#identityForToken(this.#session?.accessToken);\n const current = this.#identityForToken(accessToken);\n const changed =\n previous.userId !== undefined && current.userId !== undefined\n ? previous.userId !== current.userId\n : previous.userId !== current.userId || rotateFallback;\n\n if (rotateFallback || changed) this.#rotateAuthScope();\n if (knownPrevious && changed) this.#hooks.onAccountChange?.();\n }\n\n /**\n * Есть ли признак живой refresh-сессии.\n *\n * Рядом с refresh-токеном сервер ставит незакрытую cookie `is_auth` — по ней видно,\n * что продлевать сессию вообще есть смысл, и API не дёргается у анонимов.\n * В браузере cookie ведёт сама среда, поэтому там ответ всегда `true`.\n *\n * Асинхронный, потому что признак может лежать в {@link TokenStorage}: до чтения оттуда\n * ответ был бы `false` даже при полностью рабочей сохранённой сессии.\n */\n async hasRefreshSession(): Promise<boolean> {\n await this.#loadSession();\n return this.#hasRefreshSession();\n }\n\n /** То же самое, но без чтения хранилища — для вызовов, где сессия уже загружена. */\n #hasRefreshSession(): boolean {\n if (!this.#config.useCookieJar) return true;\n // Флаг должен принадлежать основному API, а не другому сервису.\n if (this.#jar.has(AUTH_FLAG_COOKIE, this.#config.baseUrl)) return true;\n\n // Явно переданный refresh-токен — тоже основание пробовать.\n return Boolean(this.#session?.refreshToken);\n }\n\n /**\n * Готовит состояние авторизации до входа запроса в очередь. Стадия `auth_preparation`.\n *\n * Загрузка хранилища, внешний источник токена и отложенный вход асинхронны, поэтому\n * обязаны завершиться до захвата слота очереди.\n */\n async prepare(): Promise<void> {\n await this.token();\n }\n\n /**\n * Заголовки уже подготовленной авторизации без чтения storage или вызова внешнего источника.\n *\n * Используются после ожидания транспортной очереди: к этому моменту {@link prepare} уже\n * был вызван снаружи неё, но token мог успеть смениться из-за refresh или `setSession()`.\n */\n currentHeaders(): Record<string, string> {\n const session =\n this.#session === undefined ? this.#sessionFromConfig(this.#config.auth) : this.#session;\n const token = session?.accessToken ?? this.#externalToken ?? null;\n return token ? { Authorization: `Bearer ${token}` } : {};\n }\n\n /**\n * Реакция конвейера на `401`. Стадия `auth_recovery`.\n *\n * При выключенном `autoRefresh` ничего не делает: вызывающий код обновляет сессию сам\n * через `itd.auth.refresh()`, а библиотека просто пробрасывает {@link ItdAuthError}.\n */\n recover(): Promise<boolean> {\n return this.#config.autoRefresh ? this.onUnauthorized() : Promise.resolve(false);\n }\n\n /**\n * Идентификатор устройства для заголовка `X-Device-Id`.\n *\n * Заводится один раз и сохраняется в сессии, чтобы пережить перезапуск процесса:\n * сервер связывает с ним запись в списке сессий, и плавающее значение плодило бы\n * по новой сессии на каждый старт.\n */\n deviceId(): Promise<string> {\n if (this.#deviceId) return Promise.resolve(this.#deviceId);\n\n // Дедупликация: параллельные вызовы на холодном клиенте получают один `X-Device-Id`.\n this.#deviceIdLoading ??= this.#resolveDeviceId().finally(() => {\n this.#deviceIdLoading = null;\n });\n\n return this.#deviceIdLoading;\n }\n\n async #resolveDeviceId(): Promise<string> {\n const session = await this.#loadSession();\n const deviceId = this.#config.deviceId ?? session?.deviceId ?? createDeviceId();\n\n this.#deviceId = deviceId;\n\n // Записываем, только если значение действительно новое, — иначе каждый первый запрос\n // дёргал бы хранилище без всякой пользы.\n if (session?.deviceId !== deviceId) {\n await this.#saveSession({ ...(session ?? {}), deviceId });\n }\n\n return deviceId;\n }\n\n /**\n * Текущий токен доступа.\n *\n * При необходимости выполняет отложенный вход: если в конфигурации переданы логин\n * и пароль, первый же запрос сам заведёт сессию.\n */\n async token(): Promise<string | null> {\n const session = await this.#loadSession();\n if (session?.accessToken) return session.accessToken;\n\n const auth = this.#config.auth;\n if (!auth) return null;\n\n // Внешний источник токена спрашиваем каждый раз: он сам решает, когда обновлять.\n if (typeof auth === 'object' && 'getToken' in auth) {\n return this.#readExternalToken(() => auth.getToken());\n }\n\n if (typeof auth === 'object' && 'email' in auth) {\n return this.#signInWithCredentials(auth);\n }\n\n return null;\n }\n\n /**\n * Реакция транспорта на ответ `401`.\n *\n * @returns `true`, если токен обновлён и запрос имеет смысл повторить\n */\n async onUnauthorized(): Promise<boolean> {\n try {\n const token = await this.#refreshDeduplicated();\n if (token !== null) return true;\n\n // Обновлять нечем — это тоже отказ авторизации, и подписчик должен о нём узнать.\n this.#emitter.emit('authError', { error: this.#noRefreshSessionError() });\n return false;\n } catch (error) {\n this.#emitter.emit('authError', { error });\n return false;\n }\n }\n\n /**\n * Ошибка «сессию продлить нечем».\n *\n * Возникает, только когда обновление даже не начиналось: нет ни cookie `is_auth`,\n * ни refresh-токена. Если сервер ответил отказом, наружу уходит **его** ошибка —\n * подменять её этой значило бы прятать причину (`REFRESH_TOKEN_MISSING`,\n * `SESSION_NOT_FOUND`, `SESSION_REVOKED` — разные поводы и разные действия).\n */\n #noRefreshSessionError(): ItdAuthError {\n return new ItdAuthError({\n status: 401,\n code: 'SESSION_EXPIRED',\n message:\n 'Не удалось обновить сессию: нет ни cookie is_auth, ни refresh-токена. ' +\n 'Войдите заново либо передайте refreshToken в auth.',\n method: 'POST',\n path: AUTH_PATHS.refresh,\n raw: undefined,\n });\n }\n\n /**\n * Обновляет токен доступа.\n *\n * Параллельные вызовы объединяются в один сетевой запрос.\n *\n * @throws {ItdAuthError} если обновить сессию не удалось\n */\n async refresh(): Promise<string> {\n const token = await this.#refreshDeduplicated();\n if (token === null) throw this.#noRefreshSessionError();\n return token;\n }\n\n /** Сохраняет токен, полученный извне, — например после подтверждения OTP. */\n async setAccessToken(accessToken: string): Promise<void> {\n await this.#loadSession();\n this.#invalidateInFlight();\n this.#transitionAuth(accessToken);\n await this.#saveSession({\n ...(this.#session ?? {}),\n accessToken,\n obtainedAt: this.#config.clock.now(),\n });\n this.#emitter.emit('tokens', { accessToken });\n }\n\n /** Текущая сессия целиком. Полезно, чтобы сохранить её самому. */\n async getSession(): Promise<ItdSession | null> {\n const session = await this.#loadSession();\n return session ? copySession(session) : null;\n }\n\n /**\n * Идентификатор владельца сессии.\n *\n * Считается непосредственно из текущего токена и отдельно не сохраняется: после замены\n * токена идентификатор прежнего владельца остаться не может.\n */\n async getUserId(): Promise<UserId | undefined> {\n const session = await this.#loadSession();\n return session?.accessToken ? readTokenSubject(session.accessToken) : undefined;\n }\n\n /** Заменяет сессию и связанные с ней cookie целиком. */\n async setSession(session: ItdSession): Promise<void> {\n await this.#loadSession();\n this.#invalidateInFlight();\n this.#transitionAuth(session.accessToken);\n this.#jar.clear();\n this.#jar.deserialize(session.cookies);\n\n if (session.deviceId) this.#deviceId = session.deviceId;\n\n // Refresh-cookie добавляется до сериализации сессии.\n this.#session = session;\n this.#seedRefreshCookie();\n\n await this.#saveSession(session);\n }\n\n /**\n * Забывает сессию и cookie. Сетевой запрос не выполняется.\n *\n * Идентификатор устройства выход переживает: иначе каждая пара «выход — вход» плодила бы\n * новую запись в списке сессий.\n */\n async clear(): Promise<void> {\n await this.#loadSession();\n this.#invalidateInFlight();\n this.#transitionAuth(undefined);\n this.#session = null;\n this.#jar.clear();\n await this.#config.storage.clear();\n\n if (this.#deviceId) await this.#saveSession({ deviceId: this.#deviceId });\n\n this.#emitter.emit('signOut', undefined);\n }\n\n #loadSession(): Promise<ItdSession | null> {\n if (this.#session !== undefined) return Promise.resolve(this.#session);\n\n this.#loading ??= this.#performLoad().finally(() => {\n this.#loading = null;\n });\n\n return this.#loading;\n }\n\n async #performLoad(): Promise<ItdSession | null> {\n const loaded = (await this.#config.storage.get()) ?? null;\n const stored = loaded ? withoutLegacyUserId(loaded) : null;\n\n // Восстанавливаем cookie: без них не выйдет обновить токен после перезапуска процесса.\n if (stored?.cookies) this.#jar.deserialize(stored.cookies);\n\n const fromConfig = this.#sessionFromConfig(this.#config.auth);\n\n // Хранилище отвечает за состояние сессии и всегда важнее, но недостающие поля\n // берутся из конфигурации: типичный случай — сохранён только accessToken,\n // а refresh-токен приходит из настроек приложения.\n this.#session =\n stored && fromConfig\n ? {\n ...stored,\n accessToken: stored.accessToken ?? fromConfig.accessToken,\n refreshToken: stored.refreshToken ?? fromConfig.refreshToken,\n }\n : (stored ?? fromConfig);\n\n this.#seedRefreshCookie();\n\n return this.#session;\n }\n\n /**\n * Кладёт refresh-токен в jar как cookie `refresh_token`.\n *\n * `POST /api/v1/auth/refresh` читает токен только из cookie, поэтому переданный строкой\n * приходится превращать в неё. В браузере это невозможно — cookie помечена `HttpOnly`,\n * и там обновление работает только на той, что поставил сам сервер.\n */\n #seedRefreshCookie(): void {\n if (!this.#config.useCookieJar) return;\n\n const refreshToken = this.#session?.refreshToken;\n if (!refreshToken) return;\n if (this.#jar.has(REFRESH_COOKIE)) return;\n\n this.#jar.set(this.#config.baseUrl, REFRESH_COOKIE, refreshToken, REFRESH_COOKIE_PATH);\n }\n\n #sessionFromConfig(auth: AuthInput | undefined): ItdSession | null {\n if (!auth) return null;\n if (typeof auth === 'string') {\n return { accessToken: auth, obtainedAt: this.#config.clock.now() };\n }\n\n if ('accessToken' in auth) {\n return {\n accessToken: auth.accessToken,\n refreshToken: auth.refreshToken,\n obtainedAt: this.#config.clock.now(),\n };\n }\n\n return null;\n }\n\n async #saveSession(session: ItdSession): Promise<void> {\n const cookies = this.#config.useCookieJar ? this.#jar.serialize() : undefined;\n const deviceId = session.deviceId ?? this.#deviceId;\n\n const next = withoutLegacyUserId({\n ...session,\n ...(cookies?.length ? { cookies } : {}),\n ...(deviceId ? { deviceId } : {}),\n });\n\n this.#session = next;\n await this.#config.storage.set(next);\n }\n\n /**\n * Обновление с дедупликацией.\n *\n * Все, кто пришёл, пока обновление уже идёт, получают его результат, а не запускают своё.\n */\n #refreshDeduplicated(): Promise<string | null> {\n if (this.#refreshing) return this.#refreshing;\n\n const promise = this.#performRefresh().finally(() => {\n this.#refreshing = null;\n });\n\n this.#refreshing = promise;\n return promise;\n }\n\n async #performRefresh(): Promise<string | null> {\n await this.#loadSession();\n\n if (!this.#hasRefreshSession()) {\n // Нет признаков сессии — обновлять нечего. При наличии логина и пароля\n // пробуем войти заново.\n return this.#reloginOrNull();\n }\n\n // Снимок владельца авторизации: если он сменится, пока идёт сетевой запрос,\n // результат обновления записывать нельзя.\n const epoch = this.#authEpoch;\n\n try {\n // Служебный запрос идёт через общий pipeline. Слой авторизации он пропускает, чтобы\n // не отправлять устаревший Bearer и не запускать рекурсивный refresh. Очередь безопасна:\n // исходная неудачная транспортная попытка освободила свой слот до вызова этого метода.\n const accessToken = await this.#http.execute(AUTH_REFRESH, {\n path: AUTH_PATHS.refresh,\n skipAuth: true,\n skipAuthRefresh: true,\n // Тела нет намеренно: сервер читает refresh-токен только из cookie — см.\n // #seedRefreshCookie. По той же причине не нужен и устаревший Bearer.\n });\n if (!accessToken) return this.#reloginOrNull();\n\n if (this.#authEpoch !== epoch) {\n // Пока шёл refresh, владельца сменили (signOut / setSession / вход): его результат\n // устарел. Не воскрешаем сохранённую сессию, отдаём актуальный токен как есть.\n return this.#session?.accessToken ?? null;\n }\n\n // Сервер выдаёт при обновлении **новый** refresh-токен (`Set-Cookie: refresh_token=…;\n // Max-Age=2592000`) и тут же гасит прежний. Забрать его из jar обязательно: иначе\n // сохранённая строка протухнет и восстановление сессии из хранилища перестанет работать.\n const rotated = this.#jar.getValue(\n REFRESH_COOKIE,\n this.#config.baseUrl + REFRESH_COOKIE_PATH,\n );\n\n // Refresh продолжает ту же серверную сессию. Для непрозрачного токена это единственная\n // надёжная возможность сохранить scope; смена пользователя в JWT всё равно будет обнаружена.\n this.#transitionAuth(accessToken, false);\n await this.#saveSession({\n ...(this.#session ?? {}),\n accessToken,\n ...(rotated ? { refreshToken: rotated } : {}),\n obtainedAt: this.#config.clock.now(),\n });\n\n this.#emitter.emit('tokens', { accessToken });\n return accessToken;\n } catch (error) {\n if (error instanceof ItdApiError) {\n if (this.#authEpoch !== epoch) {\n // Владельца сменили, пока шёл refresh: ошибка относится к уже забытой сессии —\n // не трогаем актуальное состояние.\n return this.#session?.accessToken ?? null;\n }\n\n // Сессия недействительна — чистим её, иначе будем биться в стену на каждом запросе.\n this.#transitionAuth(undefined);\n this.#session = null;\n this.#jar.clear();\n await this.#config.storage.clear();\n\n const relogged = await this.#reloginOrNull();\n if (relogged !== null) return relogged;\n\n // Войти заново нечем — отдаём ошибку сервера как есть. Именно она объясняет,\n // что произошло: REFRESH_TOKEN_MISSING, SESSION_NOT_FOUND, SESSION_REVOKED.\n throw error;\n }\n throw error;\n }\n }\n\n /** Повторный вход, если разрешён настройкой и есть логин с паролем. */\n async #reloginOrNull(): Promise<string | null> {\n const auth = this.#config.auth;\n\n if (\n !this.#config.reloginOnRefreshFailure ||\n !auth ||\n typeof auth !== 'object' ||\n !('email' in auth)\n ) {\n return null;\n }\n\n try {\n return await this.#signInWithCredentials(auth);\n } catch {\n return null;\n }\n }\n\n /**\n * Вход по логину и паролю.\n *\n * Параллельные вызовы объединяются: одновременный старт нескольких запросов не должен\n * приводить к нескольким попыткам входа и блокировке аккаунта.\n */\n #signInWithCredentials(credentials: CredentialsAuth): Promise<string> {\n if (this.#signingIn) return this.#signingIn;\n\n const promise = this.#performSignIn(credentials).finally(() => {\n this.#signingIn = null;\n });\n\n this.#signingIn = promise;\n return promise;\n }\n\n /**\n * Берёт токен капчи для входа.\n *\n * `getTurnstileToken` приоритетнее готовой строки: токен Turnstile одноразовый и живёт\n * несколько минут, поэтому при повторном входе через сутки годится только свежий.\n */\n async #resolveTurnstileToken(credentials: CredentialsAuth): Promise<string> {\n if (credentials.getTurnstileToken) {\n const token = await credentials.getTurnstileToken();\n if (token) return token;\n }\n\n if (credentials.turnstileToken) return credentials.turnstileToken;\n\n throw new ItdConfigError(\n 'Вход по email и паролю требует токен капчи Cloudflare Turnstile: без него сервер ' +\n 'отвечает 422. Передайте auth.getTurnstileToken (источник свежего токена) либо ' +\n 'разовый auth.turnstileToken. Ключ виджета — TURNSTILE_SITE_KEY. В Node токен умеет ' +\n 'добывать отдельный пакет: npm i @itd-api/turnstile, затем ' +\n 'getTurnstileToken: createTurnstileSolver().',\n );\n }\n\n async #performSignIn(credentials: CredentialsAuth): Promise<string> {\n const turnstileToken = await this.#resolveTurnstileToken(credentials);\n\n // Через общий pipeline: плагины, повторы и очередь сохраняются, а слой авторизации\n // пропускается, потому что токена ещё нет.\n const result = await this.#http.execute(AUTH_SIGN_IN, {\n path: AUTH_PATHS.signIn,\n body: { email: credentials.email, password: credentials.password, turnstileToken },\n skipAuth: true,\n skipAuthRefresh: true,\n });\n\n if (result.status !== SignInStatus.Authenticated) {\n // Сервер запросил подтверждение по коду — автоматически это не пройти.\n throw new ItdConfigError(\n 'Вход по email и паролю требует подтверждения кодом из письма. Автоматический вход ' +\n 'невозможен: воспользуйтесь itd.auth.signInWithOtp() и передайте полученный ' +\n 'accessToken в конфигурацию клиента.',\n );\n }\n const accessToken = result.accessToken;\n\n // Прежний refresh-токен и cookie намеренно не переносятся: вход выдал новую сессию,\n // и держаться за старую было бы ошибкой. Идентификатор устройства добавит #saveSession.\n this.#invalidateInFlight();\n this.#transitionAuth(accessToken);\n await this.#saveSession({ accessToken, obtainedAt: this.#config.clock.now() });\n this.#emitter.emit('tokens', { accessToken });\n this.#emitter.emit('signIn', { accessToken });\n\n return accessToken;\n }\n}\n","import { ItdConfigError } from '../core/errors.js';\nimport type { HttpClient } from '../core/execution/http.js';\nimport type { RequestOptions } from '../core/options.js';\nimport type { Session } from '../models/account.js';\nimport type { AuthState } from '../models/users.js';\nimport {\n AUTH_CHANGE_PASSWORD,\n AUTH_CHECK,\n AUTH_FORGOT_PASSWORD,\n AUTH_LOGOUT,\n AUTH_RESEND_OTP,\n AUTH_RESET_PASSWORD,\n AUTH_REVOKE_OTHER_SESSIONS,\n AUTH_REVOKE_SESSION,\n AUTH_SESSIONS,\n AUTH_SIGN_IN,\n AUTH_SIGN_UP,\n AUTH_VERIFY_OTP,\n type SignInResult,\n SignInStatus,\n} from '../operations/auth.js';\nimport { AUTH_PATHS, type AuthManager, type TURNSTILE_SITE_KEY } from '../session/auth.js';\nimport { BaseResource } from './base.js';\n\n/** Учётные данные для входа. */\nexport interface Credentials {\n email: string;\n password: string;\n}\n\n/**\n * Учётные данные вместе с токеном капчи.\n *\n * `turnstileToken` обязателен: без него сервер отвечает `VALIDATION_ERROR`, с недействительным —\n * `TURNSTILE_VERIFICATION_FAILED`. Токен даёт виджет Cloudflare Turnstile с ключом\n * {@link TURNSTILE_SITE_KEY}; он одноразовый и живёт несколько минут.\n */\nexport interface CaptchaCredentials extends Credentials {\n turnstileToken: string;\n}\n\n/** Запрос письма для сброса пароля. Капча обязательна, как и при входе. */\nexport interface ForgotPasswordInput {\n email: string;\n turnstileToken: string;\n}\n\n/**\n * Установка нового пароля.\n *\n * Сброс идёт тем же потоком с кодом, что и вход: {@link AuthResource.forgotPassword} выдаёт\n * `flowToken`, письмо приносит `otp`, и всё это вместе с новым паролем уходит сюда.\n */\nexport interface ResetPasswordInput {\n email: string;\n otp: string;\n flowToken: string;\n newPassword: string;\n}\n\nexport type { SignInResult, SignInStatus as SignInStatusValue } from '../operations/auth.js';\n/** Чем закончился вход: сразу токеном или запросом кода подтверждения. */\nexport { SignInStatus } from '../operations/auth.js';\n\n/**\n * Результат входа.\n *\n * Сервер может как сразу выдать токен, так и потребовать код подтверждения — размеченное\n * объединение делает оба случая явными.\n */\n\n/**\n * Авторизация, сессии и пароли.\n *\n * Доступна как `itd.auth`.\n */\nexport class AuthResource extends BaseResource {\n readonly #auth: AuthManager;\n\n constructor(http: HttpClient, deps: { auth: AuthManager }) {\n super(http);\n this.#auth = deps.auth;\n }\n\n /**\n * Проверяет состояние авторизации и возвращает текущего пользователя.\n *\n * Без авторизации возвращает `authenticated: false` и `user: null`.\n */\n check(options: RequestOptions = {}): Promise<AuthState> {\n return this.http.execute(AUTH_CHECK, {\n path: '/api/profile',\n ...options,\n });\n }\n\n /**\n * Регистрирует аккаунт и запускает подтверждение по коду.\n *\n * @returns `flowToken`, который нужно передать в {@link verifyOtp}\n */\n signUp(credentials: CaptchaCredentials, options: RequestOptions = {}): Promise<string> {\n return this.http.execute(AUTH_SIGN_UP, {\n path: AUTH_PATHS.signUp,\n body: credentials,\n skipAuth: true,\n skipAuthRefresh: true,\n ...options,\n });\n }\n\n /**\n * Выполняет вход.\n *\n * Если сервер потребовал код подтверждения, вернётся `status: 'otp_required'` —\n * тогда продолжайте через {@link verifyOtp} либо воспользуйтесь {@link signInWithOtp}.\n *\n * При успешном входе токен сохраняется в клиенте автоматически.\n *\n * @param credentials email, пароль и обязательный токен капчи — см. {@link CaptchaCredentials}\n */\n async signIn(\n credentials: CaptchaCredentials,\n options: RequestOptions = {},\n ): Promise<SignInResult> {\n const result = await this.http.execute(AUTH_SIGN_IN, {\n path: AUTH_PATHS.signIn,\n body: credentials,\n skipAuth: true,\n skipAuthRefresh: true,\n ...options,\n });\n\n if (result.status === SignInStatus.Authenticated) {\n await this.#auth.setAccessToken(result.accessToken);\n }\n return result;\n }\n\n /**\n * Подтверждает вход кодом из письма.\n *\n * Полученный токен сохраняется в клиенте автоматически.\n */\n async verifyOtp(\n input: Credentials & { otp: string; flowToken: string },\n options: RequestOptions = {},\n ): Promise<string> {\n const accessToken = await this.http.execute(AUTH_VERIFY_OTP, {\n path: AUTH_PATHS.verifyOtp,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...options,\n });\n\n await this.#auth.setAccessToken(accessToken);\n return accessToken;\n }\n\n /** Отправляет код подтверждения повторно. */\n resendOtp(\n input: { email: string; flowToken: string },\n options: RequestOptions = {},\n ): Promise<void> {\n return this.voidOperation(AUTH_RESEND_OTP, {\n path: AUTH_PATHS.resendOtp,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...options,\n });\n }\n\n /**\n * Полный вход с подтверждением по коду.\n *\n * Удобно для скриптов и ботов: код запрашивается функцией `getOtp`, а всё остальное\n * библиотека делает сама.\n *\n * @example\n * ```ts\n * import { createInterface } from 'node:readline/promises';\n *\n * const rl = createInterface({ input: process.stdin, output: process.stdout });\n *\n * const token = await itd.auth.signInWithOtp({\n * email, password,\n * getOtp: () => rl.question('Код из письма: '),\n * });\n * ```\n */\n async signInWithOtp(\n input: CaptchaCredentials & { getOtp: () => string | Promise<string> },\n options: RequestOptions = {},\n ): Promise<string> {\n const { getOtp, ...credentials } = input;\n const result = await this.signIn(credentials, options);\n\n if (result.status === SignInStatus.Authenticated) return result.accessToken;\n\n if (!result.flowToken) {\n throw new ItdConfigError(\n 'Сервер запросил код подтверждения, но не вернул flowToken — продолжить вход невозможно',\n );\n }\n\n const otp = await getOtp();\n\n // Токен капчи сюда не передаётся: он одноразовый и уже потрачен на sign-in,\n // а verify-otp капчу не требует.\n return this.verifyOtp(\n {\n email: credentials.email,\n password: credentials.password,\n otp,\n flowToken: result.flowToken,\n },\n options,\n );\n }\n\n /**\n * Обновляет токен доступа.\n *\n * Параллельные вызовы объединяются в один сетевой запрос. При включённом `autoRefresh`\n * вызывать вручную обычно не нужно.\n */\n refresh(): Promise<string> {\n return this.#auth.refresh();\n }\n\n /**\n * Есть ли признак живой сессии обновления.\n *\n * Проверяет cookie `is_auth`, которую сервер ставит рядом с refresh-токеном, а также\n * refresh-токен, переданный строкой. Позволяет не дёргать API у неавторизованного\n * пользователя. В браузере всегда `true`: cookie ведёт сама среда, и прочитать её\n * из JS нельзя.\n *\n * Читает {@link TokenStorage}, поэтому результат верен и до первого запроса.\n *\n * @example\n * ```ts\n * if (await itd.auth.hasRefreshSession()) await itd.auth.refresh();\n * else redirectToLogin();\n * ```\n */\n hasRefreshSession(): Promise<boolean> {\n return this.#auth.hasRefreshSession();\n }\n\n /** Завершает текущую сессию на сервере и очищает локальную. */\n async logout(options: RequestOptions = {}): Promise<void> {\n await this.voidOperation(AUTH_LOGOUT, {\n path: AUTH_PATHS.logout,\n skipAuthRefresh: true,\n ...options,\n });\n\n await this.#auth.clear();\n }\n\n /**\n * Завершает все сессии пользователя и очищает локальную.\n *\n * Собран из двух запросов, потому что единого эндпоинта на сервере нет:\n * `POST /api/v1/auth/logout-all` отвечает `404`. Сначала отзываются все прочие сессии\n * (`DELETE /api/v1/auth/sessions`), затем завершается текущая — в обратном порядке\n * отзывать было бы уже нечем.\n */\n async logoutAll(options: RequestOptions = {}): Promise<void> {\n await this.revokeOtherSessions(options);\n await this.logout(options);\n }\n\n /** Забывает сессию локально, не обращаясь к серверу. */\n signOut(): Promise<void> {\n return this.#auth.clear();\n }\n\n /**\n * Запрашивает письмо с кодом для сброса пароля.\n *\n * @returns `flowToken`, который нужно передать в {@link resetPassword}\n */\n forgotPassword(input: ForgotPasswordInput, options: RequestOptions = {}): Promise<string> {\n return this.http.execute(AUTH_FORGOT_PASSWORD, {\n path: AUTH_PATHS.forgotPassword,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...options,\n });\n }\n\n /**\n * Устанавливает новый пароль по коду из письма.\n *\n * Сервер ждёт все четыре поля сразу — `email`, `otp`, `flowToken` и `newPassword`;\n * при нехватке любого отвечает `422`.\n */\n resetPassword(input: ResetPasswordInput, options: RequestOptions = {}): Promise<void> {\n return this.voidOperation(AUTH_RESET_PASSWORD, {\n path: AUTH_PATHS.resetPassword,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...options,\n });\n }\n\n /**\n * Полный сброс пароля с кодом из письма.\n *\n * Тот же приём, что и {@link signInWithOtp}: код запрашивается функцией `getOtp`,\n * остальное библиотека делает сама.\n *\n * @example\n * ```ts\n * await itd.auth.resetPasswordWithOtp({\n * email,\n * turnstileToken,\n * newPassword,\n * getOtp: () => rl.question('Код из письма: '),\n * });\n * ```\n */\n async resetPasswordWithOtp(\n input: ForgotPasswordInput & {\n newPassword: string;\n getOtp: () => string | Promise<string>;\n },\n options: RequestOptions = {},\n ): Promise<void> {\n const flowToken = await this.forgotPassword(\n { email: input.email, turnstileToken: input.turnstileToken },\n options,\n );\n\n const otp = await input.getOtp();\n\n await this.resetPassword(\n { email: input.email, otp, flowToken, newPassword: input.newPassword },\n options,\n );\n }\n\n /**\n * Меняет пароль. Требует действующей сессии.\n *\n * При неверном текущем пароле сервер отвечает `ACCOUNT_CURRENT_PASSWORD_INCORRECT`.\n *\n * @example\n * ```ts\n * await itd.auth.changePassword({ currentPassword, newPassword });\n * ```\n */\n changePassword(\n input: { currentPassword: string; newPassword: string },\n options: RequestOptions = {},\n ): Promise<void> {\n return this.voidOperation(AUTH_CHANGE_PASSWORD, {\n path: AUTH_PATHS.changePassword,\n body: { currentPassword: input.currentPassword, newPassword: input.newPassword },\n ...options,\n });\n }\n\n /** Загружает список активных сессий. У текущей поле `isCurrent` равно `true`. */\n sessions(options: RequestOptions = {}): Promise<Session[]> {\n return this.http.execute(AUTH_SESSIONS, {\n path: AUTH_PATHS.sessions,\n ...options,\n });\n }\n\n /** Завершает указанную сессию. */\n revokeSession(sessionId: string, options: RequestOptions = {}): Promise<void> {\n return this.voidOperation(AUTH_REVOKE_SESSION, {\n path: `${AUTH_PATHS.sessions}/${encodeURIComponent(sessionId)}`,\n ...options,\n });\n }\n\n /** Завершает все сессии, кроме текущей. */\n revokeOtherSessions(options: RequestOptions = {}): Promise<void> {\n return this.voidOperation(AUTH_REVOKE_OTHER_SESSIONS, {\n path: AUTH_PATHS.sessions,\n ...options,\n });\n }\n}\n","import { installAsyncDisposeFallback } from './core/async-dispose.js';\nimport type { Listener, Unsubscribe } from './core/emitter.js';\nimport { ItdConfigError, ItdStateError } from './core/errors.js';\nimport { type ClientRuntime, createClientRuntime } from './core/execution/client-runtime.js';\nimport { ExtensibleOperationCatalog } from './core/feature-catalog.js';\nimport { type ClientFeatureHost, createFeatureHost } from './core/feature-host.js';\nimport type { ClientFeature } from './core/features.js';\nimport type { RawRequestOptions } from './core/options.js';\nimport type { ClientPlugin } from './core/plugins/contracts.js';\nimport type { PluginRegistry } from './core/plugins/registry.js';\nimport type { RateLimitBucketState, RequestQueuePool } from './core/scheduling/rate-limit.js';\nimport type { ServiceDefinition } from './core/services.js';\nimport { ITD_CATALOG } from './domain/catalog.js';\nimport {\n NotificationEvents,\n resolveNotificationEventsOptions,\n setNotificationEventsConnectGuard,\n} from './events/stream.js';\nimport type { UserId } from './models/common.js';\nimport { createNotificationsApi, type NotificationsApi } from './notifications-api.js';\nimport { EVENT_OPERATIONS } from './operations/events.js';\nimport type { ItdClientOptions } from './options.js';\nimport { AuthResource } from './resources/auth.js';\nimport type { CommentsResource } from './resources/comments.js';\nimport type { FilesResource } from './resources/files.js';\nimport type { HashtagsResource } from './resources/hashtags.js';\nimport type { PlatformResource } from './resources/platform.js';\nimport type { PostsResource } from './resources/posts.js';\nimport type { ReportsResource } from './resources/reports.js';\nimport type { SearchResource } from './resources/search.js';\nimport { createStatusFeature } from './resources/status.js';\nimport type { SubscriptionResource } from './resources/subscription.js';\nimport type { TelemetryResource } from './resources/telemetry.js';\nimport type { UsersResource } from './resources/users.js';\nimport type { VerificationResource } from './resources/verification.js';\nimport { createResources, type RestResources } from './rest/resources.js';\nimport { type AuthEvents, type AuthManager, createItdAuth } from './session/auth.js';\nimport type { ItdSession } from './session/storage.js';\n\nconst CLIENT_PLUGIN_REGISTRIES = new WeakMap<ItdClient, PluginRegistry>();\nconst DISPOSED_CLIENTS = new WeakSet<ItdClient>();\n\nfunction assertClientActive(client: ItdClient, action: string): void {\n if (!DISPOSED_CLIENTS.has(client)) return;\n throw new ItdStateError(\n `ItdClient уже окончательно освобождён через dispose(); нельзя ${action}. Создайте новый клиент`,\n );\n}\n\n/** Проверяет возможность подключить плагин к клиенту без вызова `install()`. @internal */\nexport function assertClientCanUsePlugin(client: ItdClient, plugin: ClientPlugin): void {\n assertClientActive(client, 'подключить плагин');\n CLIENT_PLUGIN_REGISTRIES.get(client)?.assertCanAdd(plugin);\n}\n\n/** Проверяет возможность отключить плагин клиента без изменения реестра. @internal */\nexport function assertClientCanUnusePlugin(client: ItdClient, name: string): void {\n CLIENT_PLUGIN_REGISTRIES.get(client)?.assertCanRemove(name);\n}\n\n/**\n * Скрытые параметры конструктора — не часть публичного API.\n *\n * @internal\n */\ninterface ItdClientInternals {\n /**\n * Готовая очередь запросов — так {@link ItdAccounts} с `rateLimitScope: 'shared'` даёт\n * нескольким клиентам одну на всех. Свою клиент в этом случае не заводит и, что важнее,\n * не гасит при `close()`: чужие ожидающие запросы это отменило бы.\n */\n queues?: RequestQueuePool | undefined;\n}\n\nconst CLIENT_INTERNALS = new WeakMap<ItdClientOptions, ItdClientInternals>();\n\n/** Создаёт клиент с зависимостями внешнего контейнера, не расширяя публичный конструктор. @internal */\nexport function createManagedClient(\n options: ItdClientOptions,\n internals: ItdClientInternals,\n): ItdClient {\n const managedOptions = { ...options };\n CLIENT_INTERNALS.set(managedOptions, internals);\n return new ItdClient(managedOptions);\n}\n\n/**\n * Клиент API итд.com.\n *\n * Методы сгруппированы по разделам: `itd.posts`, `itd.users`, `itd.comments`, `itd.auth`,\n * `itd.files`. Авторизация, обновление токена, повторы и очередь запросов работают сами.\n *\n * @example Готовый токен — для разового вызова\n * ```ts\n * const itd = new ItdClient({ auth: '<accessToken>' });\n * const me = await itd.users.me();\n * ```\n *\n * @example Полноценная сессия для бота\n * ```ts\n * import { ItdClient } from 'itd-api';\n * import { FileTokenStorage } from 'itd-api/node';\n *\n * const itd = new ItdClient({\n * // `auth` не обязателен: когда хранилище уже содержит сессию, токен берётся оттуда,\n * // а истёкший продлевается сам. Здесь он нужен на первый запуск.\n * // Вход по паролю требует токена капчи — см. AuthInput и TURNSTILE_SITE_KEY.\n * auth: { email, password, getTurnstileToken },\n * storage: new FileTokenStorage('./.itd-session.json'),\n * rateLimit: { concurrency: 4, rps: 8 },\n * });\n *\n * for await (const post of itd.posts.iterate({ tab: 'following' })) {\n * if (!post.isLiked) await itd.posts.like(post.id);\n * }\n * ```\n */\nexport class ItdClient {\n readonly #runtime: ClientRuntime<AuthManager>;\n readonly #features: ClientFeatureHost;\n /** Общий результат терминальной очистки для идемпотентных повторных вызовов. */\n #disposePromise: Promise<void> | undefined;\n\n /** Ресурсы конвейера запросов; создаются при первом обращении. */\n readonly #resources: RestResources<NotificationsApi>;\n /** Сессионный ресурс живёт у фасада: он управляет входом и продлением. */\n #auth: AuthResource | undefined;\n\n /** Авторизация, сессии и пароли. */\n get auth(): AuthResource {\n this.#auth ??= new AuthResource(this.#runtime.http, { auth: this.#runtime.auth });\n return this.#auth;\n }\n\n /** Профили, подписки, блокировки, приватность. */\n get users(): UsersResource {\n return this.#resources.users;\n }\n\n /** Лента, публикация, реакции, репосты, комментарии к постам. */\n get posts(): PostsResource {\n return this.#resources.posts;\n }\n\n /** Ответы на комментарии и действия над ними. */\n get comments(): CommentsResource {\n return this.#resources.comments;\n }\n\n /** Загрузка файлов и медиа. */\n get files(): FilesResource {\n return this.#resources.files;\n }\n\n /** Уведомления: список, счётчик, отметки о прочтении, настройки. */\n get notifications(): NotificationsApi {\n return this.#resources.notifications;\n }\n\n /** Хэштеги и посты по ним. */\n get hashtags(): HashtagsResource {\n return this.#resources.hashtags;\n }\n\n /** Глобальный поиск по пользователям и хэштегам. */\n get search(): SearchResource {\n return this.#resources.search;\n }\n\n /** Жалобы на контент и пользователей. */\n get reports(): ReportsResource {\n return this.#resources.reports;\n }\n\n /** Верификация профиля. */\n get verification(): VerificationResource {\n return this.#resources.verification;\n }\n\n /** Подписка и способы оплаты. */\n get subscription(): SubscriptionResource {\n return this.#resources.subscription;\n }\n\n /** Сведения о платформе: версии приложений, изменения, анонсы, баннер события. */\n get platform(): PlatformResource {\n return this.#resources.platform;\n }\n\n /** Телеметрия просмотров. */\n get telemetry(): TelemetryResource {\n return this.#resources.telemetry;\n }\n\n constructor(options: ItdClientOptions = {}) {\n const notificationEventsOptions = resolveNotificationEventsOptions(\n options.events?.notifications,\n );\n const internals = CLIENT_INTERNALS.get(options) ?? {};\n const catalog = new ExtensibleOperationCatalog(ITD_CATALOG);\n let featureHost: ClientFeatureHost | undefined;\n this.#runtime = createClientRuntime(options, {\n catalog,\n queues: internals.queues,\n assertActive: (action) => assertClientActive(this, action),\n // Полноценную сессию подставляет фасад: ядро о ней не знает, и клиент с готовым\n // токеном не потянет за собой ни хранилище, ни вход по паролю.\n auth: (deps) =>\n createItdAuth(options, { ...deps, onAccountChange: () => featureHost?.stop() }),\n });\n this.#features = featureHost = createFeatureHost(this.#runtime, {\n catalog,\n assertActive: (action) => assertClientActive(this, action),\n });\n const status = this.#features.install(createStatusFeature());\n this.#resources = createResources({\n http: this.#runtime.http,\n files: this.#features.files,\n status,\n createNotifications: (http) =>\n createNotificationsApi(http, (notifications) => {\n let events!: NotificationEvents;\n let lifecycleGeneration = 0;\n let lifecycleActive = false;\n let unregister: (() => void) | undefined;\n\n const releaseAfterDrain = (generation: number): void => {\n void events.drain().then(() => {\n if (lifecycleActive || generation !== lifecycleGeneration) return;\n unregister?.();\n unregister = undefined;\n });\n };\n\n events = new NotificationEvents(\n {\n connection: this.#runtime.connection(),\n request: ({ operationId, path, query, signal }) =>\n this.#runtime.http.execute(EVENT_OPERATIONS[operationId], {\n path,\n query,\n signal,\n }),\n getAuthIdentity: () => this.#runtime.auth.getCurrentAuthIdentity(),\n getAuthScope: () => this.#runtime.auth.getAuthScope(),\n fetchUnreadCount: (signal) => notifications.count({ signal }),\n onConnect: () => {\n lifecycleGeneration += 1;\n unregister ??= this.#features.manage({\n kind: `notifications:${events.transport}`,\n stop: () => {\n if (lifecycleActive) events.disconnect();\n },\n drain: () => events.drain(),\n });\n lifecycleActive = true;\n },\n onClose: () => {\n lifecycleActive = false;\n releaseAfterDrain(++lifecycleGeneration);\n },\n logger: this.#runtime.config.logger,\n clock: this.#runtime.config.clock,\n },\n notificationEventsOptions,\n );\n setNotificationEventsConnectGuard(events, () =>\n assertClientActive(this, 'подключить канал событий уведомлений'),\n );\n return events;\n }),\n });\n CLIENT_PLUGIN_REGISTRIES.set(this, this.#runtime.plugins);\n }\n\n /** Базовый URL, к которому обращается клиент. */\n get baseUrl(): string {\n return this.#runtime.config.baseUrl;\n }\n\n /**\n * Выполняет произвольный запрос к API.\n *\n * Запасной путь для случаев, когда нужного метода ещё нет или ответ сервера разошёлся\n * с документацией. Проходит через ту же авторизацию, очередь и обработку ошибок.\n *\n * @example\n * ```ts\n * const raw = await itd.request({ method: 'GET', path: '/api/posts', raw: true });\n * ```\n *\n * @throws {ItdStateError} если клиент уже освобождён через {@link dispose}\n */\n request<T = unknown>(options: RawRequestOptions): Promise<T> {\n return this.#runtime.http.request<T>({\n ...options,\n operationId: options.operationId ?? 'raw',\n });\n }\n\n /**\n * Остаток серверных лимитов по бакетам, через которые уже проходили запросы.\n *\n * Значения берутся из последнего ответа каждого бакета и быстро устаревают: сервер\n * восстанавливает квоту линейно и границу окна не сообщает. Пустой массив при\n * `rateLimit: false`. {@link close} снимок сохраняет, {@link dispose} очищает.\n *\n * @example\n * ```ts\n * const posts = itd.rateLimitState().find((state) => state.bucket === 'posts.create');\n * if ((posts?.remaining ?? Number.POSITIVE_INFINITY) < 3) await sleep(60_000);\n * ```\n */\n rateLimitState(): RateLimitBucketState[] {\n return this.#runtime.rateLimitState();\n }\n\n /**\n * Устанавливает предметный модуль с общей сессией и обработкой запросов клиента.\n *\n * Сервисы, операции и бакеты регистрируются до синхронного `setup()`. Возвращает\n * типизированный API модуля.\n */\n install<TApi>(feature: ClientFeature<TApi>): TApi {\n return this.#features.install(feature);\n }\n\n /**\n * Устанавливает модуль и добавляет его API в свойство клиента только для чтения.\n *\n * Возвращаемое пересечение сохраняет тип уже подключённых свойств, поэтому вызовы можно\n * объединять в цепочку: `new ItdClient().withFeature('pixelBattle', pixelBattleFeature)`.\n */\n withFeature<const K extends string, TApi>(\n key: K,\n feature: ClientFeature<TApi>,\n ): this & { readonly [P in K]: TApi } {\n assertClientActive(this, 'установить feature');\n if (typeof key !== 'string' || key.trim() === '' || key !== key.trim()) {\n throw new ItdConfigError('Свойство feature должно иметь непустое имя без краевых пробелов');\n }\n if (key === 'then' || key in this) {\n throw new ItdConfigError(`Свойство клиента «${key}» уже занято или зарезервировано`);\n }\n if (!Object.isExtensible(this)) {\n throw new ItdConfigError('Нельзя опубликовать feature на нерасширяемом клиенте');\n }\n\n const api = this.install(feature);\n Object.defineProperty(this, key, {\n value: api,\n enumerable: true,\n configurable: false,\n writable: false,\n });\n return this as this & { readonly [P in K]: TApi };\n }\n\n /** Имена установленных модулей в порядке установки. */\n featureNames(): string[] {\n return this.#features.names();\n }\n\n /** Установлен ли модуль с таким именем. */\n hasFeature(name: string): boolean {\n return this.#features.has(name);\n }\n\n /**\n * Подключает плагин.\n *\n * Плагин может регистрировать обёртки операций и перехватчики сетевых попыток.\n *\n * @throws {ItdConfigError} если плагин задан неверно или уже подключён\n * @throws {ItdStateError} если клиент уже освобождён через {@link dispose}\n *\n * @example\n * ```ts\n * import { crypt } from '@itd-api/crypto';\n *\n * itd.use(crypt());\n * await itd.posts.create({\n * content: 'секрет',\n * }, {\n * extensions: { crypto: { encrypt: 'invisible' } },\n * });\n * ```\n */\n use(plugin: ClientPlugin): this {\n assertClientActive(this, 'подключить плагин');\n this.#runtime.plugins.add(plugin, {\n baseUrl: this.#runtime.config.baseUrl,\n logger: this.#runtime.config.logger,\n getAuthScope: () => this.#runtime.auth.getAuthScope(),\n getAuthIdentity: () => this.#runtime.auth.getAuthIdentity(),\n });\n return this;\n }\n\n /** Имена подключённых плагинов в фактическом порядке выполнения обёрток. */\n pluginNames(): string[] {\n return this.#runtime.plugins.names();\n }\n\n /** Подключён ли плагин с таким именем. */\n hasPlugin(name: string): boolean {\n return this.#runtime.plugins.has(name);\n }\n\n /**\n * Отключает плагин и освобождает заведённые им ресурсы.\n *\n * Новые запросы перестают видеть плагин сразу. Очистка дождётся логического запроса,\n * который уже проходил через его обёртку.\n *\n * @returns `false`, если такого плагина не было\n * @throws {ItdConfigError} если от плагина зависит другой подключённый плагин\n */\n unuse(name: string): Promise<boolean> {\n return this.#runtime.plugins.remove(name);\n }\n\n /**\n * Регистрирует сервис платформы — домен, отличный от основного.\n *\n * Запросы с `{ service: 'имя' }` уходят на его хост с его заголовками. То же самое умеет\n * опция `services` конструктора. Занятое имя не переопределяется — ни своё, ни встроенное:\n * разовому запросу хост задаётся полем `baseUrl`.\n *\n * Заголовок авторизации по умолчанию уходит только своим — домену клиента и его\n * поддоменам. Стороннему хосту токен нужно разрешить явно: `auth: true`.\n *\n * @throws {ItdConfigError} если определение неверно или имя уже занято\n * @throws {ItdStateError} если клиент уже освобождён через {@link dispose}\n *\n * @example Сервис платформы на поддомене — токен уходит сам\n * ```ts\n * itd.defineService({\n * name: 'pb',\n * baseUrl: 'https://pbapi.xn--d1ah4a.com',\n * headers: { Referer: 'https://pixel.xn--d1ah4a.com/' },\n * });\n *\n * await itd.request({ method: 'GET', service: 'pb', path: '/api/pixel-info' });\n * ```\n */\n defineService(definition: ServiceDefinition): this {\n assertClientActive(this, 'зарегистрировать сервис');\n this.#runtime.services.define(definition);\n return this;\n }\n\n /**\n * Базовый URL зарегистрированного сервиса.\n *\n * @throws {ItdConfigError} если сервис не зарегистрирован\n */\n serviceBaseUrl(name: string): string {\n return this.#runtime.services.resolveBaseUrl(name);\n }\n\n /**\n * Подписывается на события авторизации.\n *\n * Полезно, чтобы сохранять сессию во внешнее хранилище или узнавать, что вход\n * окончательно потерян.\n *\n * @returns функция отписки\n *\n * @example\n * ```ts\n * itd.on('tokens', ({ accessToken }) => cache.set('itd', accessToken));\n * itd.on('authError', () => notifyUser('Сессия истекла, войдите заново'));\n * ```\n */\n on<K extends keyof AuthEvents>(event: K, listener: Listener<AuthEvents[K]>): Unsubscribe {\n assertClientActive(this, 'подписаться на события');\n return this.#runtime.auth.on(event, listener);\n }\n\n /**\n * Освобождает ресурсы клиента: закрывает все потоки уведомлений, отправляет открытые\n * накопители {@link telemetry}, затем останавливает очередь запросов.\n *\n * Метод дожидается завершения активных обработчиков и освобождения ресурсов остановленных\n * событийных соединений, но не дольше `shutdownTimeout`. После вызова клиентом можно\n * пользоваться снова; ранее созданный поток можно запустить повторным `connect()`.\n *\n * Общая очередь, полученная от {@link ItdAccounts}, не останавливается: её гасит сам\n * контейнер, когда закрывает все аккаунты разом.\n *\n * Терминальное освобождение — это {@link dispose}.\n *\n * @throws {ItdStateError} если остановка событийного соединения не завершилась за отведённый срок\n */\n async close(): Promise<void> {\n return this.#close(false);\n }\n\n async #close(disposeCleanup: boolean): Promise<void> {\n // Terminal cleanup должен пометить batch до первого await: параллельный обычный close()\n // мог уже начать drain потоков, но ещё не дойти до отправки телеметрии.\n if (disposeCleanup) this.#resources.prepareTelemetryClose();\n const errors: unknown[] = [];\n try {\n try {\n await this.#features.close();\n } catch (error) {\n errors.push(error);\n }\n\n // Через геттер закрытие клиента поднимало бы накопитель телеметрии только ради\n // того, чтобы его тут же закрыть.\n try {\n await this.#resources.closeTelemetry(disposeCleanup);\n } catch (error) {\n errors.push(error);\n }\n } finally {\n this.#runtime.close();\n }\n if (errors.length === 1) throw errors[0];\n if (errors.length > 1) throw new AggregateError(errors, 'Не удалось закрыть клиент');\n }\n\n /**\n * Окончательно освобождает клиент: выполняет {@link close}, отменяет незавершённые\n * запросы и отключает все плагины.\n *\n * Терминальное состояние устанавливается сразу при первом вызове. После этого новые\n * запросы, подключение плагинов, регистрация сервисов и создание или повторный запуск\n * событийных каналов завершаются с {@link ItdStateError}. Повторные вызовы возвращают\n * тот же результат очистки.\n *\n * Ожидание остановки событийных соединений и операций плагинов ограничено\n * `shutdownTimeout`.\n *\n * @example\n * ```ts\n * await using itd = new ItdClient({ auth: token });\n * // …работа…\n * // dispose() вызовется сам на выходе из блока\n * ```\n */\n dispose(): Promise<void> {\n if (this.#disposePromise) return this.#disposePromise;\n DISPOSED_CLIENTS.add(this);\n this.#disposePromise = this.#dispose();\n return this.#disposePromise;\n }\n\n async #dispose(): Promise<void> {\n const errors: unknown[] = [];\n try {\n // Сначала завершаем потоки и телеметрию через ещё установленные плагины.\n await this.#close(true);\n } catch (error) {\n errors.push(error);\n }\n try {\n await this.#features.dispose();\n } catch (error) {\n errors.push(error);\n }\n try {\n await this.#runtime.dispose();\n } catch (error) {\n errors.push(error);\n }\n if (errors.length > 0) throw new AggregateError(errors, 'Не удалось освободить клиент');\n }\n\n /** Позволяет использовать клиент с `await using`. */\n [Symbol.asyncDispose](): Promise<void> {\n return this.dispose();\n }\n\n static {\n installAsyncDisposeFallback(ItdClient);\n }\n\n /** Текущая сессия целиком — чтобы сохранить её самостоятельно. */\n getSession(): Promise<ItdSession | null> {\n return this.#runtime.auth.getSession();\n }\n\n /**\n * Идентификатор аккаунта, под которым работает клиент.\n *\n * Читается из токена доступа и не стоит ни одного запроса. `undefined`, когда сессии\n * ещё нет или токен выдан не в формате JWT. Полезен прежде всего с {@link ItdAccounts}:\n * показывает, какому профилю соответствует восстановленная из хранилища запись.\n * Свежий профиль целиком отдаёт `itd.users.me()`.\n *\n * @remarks\n * JWT только декодируется: его подпись не проверяется. Результат подходит для локального\n * разделения состояния, но не доказывает личность пользователя и не заменяет проверку\n * авторизации сервером.\n *\n * @example\n * ```ts\n * for (const [name, itd] of accounts) console.log(name, await itd.getUserId());\n * ```\n */\n getUserId(): Promise<UserId | undefined> {\n return this.#runtime.auth.getUserId();\n }\n\n /**\n * Восстанавливает сохранённую сессию, включая cookie.\n *\n * @throws {ItdStateError} если клиент уже освобождён через {@link dispose}\n */\n setSession(session: ItdSession): Promise<void> {\n try {\n assertClientActive(this, 'изменить сессию');\n } catch (error) {\n return Promise.reject(error);\n }\n return this.#runtime.auth.setSession(session);\n }\n}\n\n/**\n * Создаёт клиент API итд.com.\n *\n * То же, что `new ItdClient(options)`, — для тех, кому привычнее фабрика.\n *\n * @example\n * ```ts\n * const itd = createClient({ auth: token });\n * ```\n */\nexport function createClient(options: ItdClientOptions = {}): ItdClient {\n return new ItdClient(options);\n}\n","import {\n assertClientCanUnusePlugin,\n assertClientCanUsePlugin,\n createManagedClient,\n type ItdClient,\n} from './client.js';\nimport { installAsyncDisposeFallback } from './core/async-dispose.js';\nimport { systemClock } from './core/clock.js';\nimport { resolveRateLimit } from './core/config.js';\nimport { Emitter, type Listener, reportListenerError, type Unsubscribe } from './core/emitter.js';\nimport { ItdConfigError, ItdStateError } from './core/errors.js';\nimport type { ClientFeature } from './core/features.js';\nimport type { Logger } from './core/options.js';\nimport type { ClientPlugin } from './core/plugins/contracts.js';\nimport { assertPluginRemovable, orderPluginDefinitions } from './core/plugins/order.js';\nimport { RequestQueuePool } from './core/scheduling/rate-limit.js';\nimport { ITD_CATALOG } from './domain/catalog.js';\nimport type { ItdClientOptions } from './options.js';\nimport {\n type ControlledTokenStorage,\n controlledTokenStorage,\n isRestorableSession,\n MemoryMultiTokenStorage,\n type MultiTokenStorage,\n} from './session/multi-storage.js';\nimport type { TokenStorage } from './session/storage.js';\n\n/** Как аккаунты делят между собой очередь запросов. */\nexport type RateLimitScope = 'account' | 'shared';\n\n/**\n * Фабрика подключаемого модуля для клиентов {@link ItdAccounts}.\n *\n * `create()` вызывается отдельно для каждого аккаунта, поэтому описание модуля и его API\n * между клиентами не разделяется. `key` публикует API как readonly-свойство клиента.\n */\nexport interface AccountFeature<TApi = unknown> {\n readonly key?: string | undefined;\n create(): ClientFeature<TApi>;\n}\n\n/**\n * Опции конструктора {@link ItdAccounts}.\n *\n * Всё, что понимает `ItdClient`, кроме `auth` и `deviceId`: они у каждого аккаунта свои\n * и задаются в {@link ItdAccounts.addAccount}. Обычный `TokenStorage` клиента здесь заменён\n * общей опцией {@link ItdAccountsOptions.storage} типа {@link MultiTokenStorage}; контейнер\n * сам выдаёт каждому клиенту изолированный срез по имени. Общий `deviceId` особенно вреден —\n * сервер различает по нему записи в списке сессий, и один на всех сложил бы все аккаунты\n * в одну.\n */\nexport interface ItdAccountsOptions\n extends Omit<ItdClientOptions, 'auth' | 'storage' | 'deviceId'> {\n /** Общее хранилище сессий всех аккаунтов. По умолчанию {@link MemoryMultiTokenStorage}. */\n storage?: MultiTokenStorage | undefined;\n /** Плагины, подключаемые каждому аккаунту, в том числе добавленному позже. */\n plugins?: readonly ClientPlugin[] | undefined;\n /** Модули, создаваемые и устанавливаемые отдельно для каждого аккаунта. */\n features?: readonly AccountFeature[] | undefined;\n /**\n * Как делить очередь запросов. По умолчанию `'shared'` — одна на всех.\n *\n * Лимиты итд.com считаются по IP, поэтому аккаунты с одного адреса тратят общую квоту.\n * `'account'` даёт каждому свою очередь и нужен, когда у аккаунтов разные адреса —\n * например, при своём прокси у каждого.\n *\n * В режиме `'shared'` настройки очереди берутся из общей опции `rateLimit`; аккаунту\n * разрешён только `rateLimit: false`, выводящий его из общей очереди.\n */\n rateLimitScope?: RateLimitScope | undefined;\n}\n\n/**\n * Настройки одного аккаунта. Общее мультихранилище задаёт контейнер, а аккаунт получает\n * свой срез автоматически; остальное — как у `ItdClient`.\n *\n * При `rateLimitScope: 'shared'` (умолчание) объект `rateLimit` задаётся только\n * контейнеру; аккаунту разрешено передать `false`, чтобы не ставить его запросы\n * в общую очередь.\n */\nexport type AddAccountOptions = Omit<ItdClientOptions, 'storage'>;\n\n/** Что можно уточнить при удалении аккаунта. */\nexport interface RemoveAccountOptions {\n /**\n * Удалить и сохранённую сессию. По умолчанию `false` — аккаунт убирается только\n * из памяти, а его токены остаются в хранилище и переживут перезапуск.\n */\n forget?: boolean | undefined;\n}\n\n/**\n * События авторизации всех аккаунтов сразу.\n *\n * Те же, что у одиночного клиента, плюс имя аккаунта: подписка на контейнер избавляет\n * от нужды вешать обработчик на каждого.\n */\nexport interface AccountEvents {\n /** Токен получен или обновлён. */\n tokens: { account: string; accessToken: string };\n /** Выполнен вход. */\n signIn: { account: string; accessToken: string };\n /** Сессия очищена — вручную или из-за неудачного обновления. */\n signOut: { account: string };\n /** Обновить сессию не удалось; дальнейшие запросы этого аккаунта будут падать с 401. */\n authError: { account: string; error: unknown };\n}\n\n/** Проверяет имя до создания клиента или частичного восстановления контейнера. */\nfunction validateAccountName(name: string): void {\n if (typeof name !== 'string' || name.trim() === '') {\n throw new ItdConfigError('имя аккаунта должно быть непустой строкой');\n }\n}\n\n/** Проверяет фабрики до создания первого аккаунта. */\nfunction resolveAccountFeatures(features: readonly AccountFeature[] | undefined): AccountFeature[] {\n if (features === undefined) return [];\n if (!Array.isArray(features)) {\n throw new ItdConfigError('features должен быть массивом фабрик подключаемых модулей');\n }\n\n const keys = new Set<string>();\n return features.map((feature, index) => {\n if (typeof feature !== 'object' || feature === null || typeof feature.create !== 'function') {\n throw new ItdConfigError(`features[${index}] должен предоставлять create()`);\n }\n if (feature.key !== undefined) {\n if (\n typeof feature.key !== 'string' ||\n feature.key.trim() === '' ||\n feature.key !== feature.key.trim()\n ) {\n throw new ItdConfigError(\n `features[${index}].key должен быть непустой строкой без пробелов`,\n );\n }\n if (keys.has(feature.key)) {\n throw new ItdConfigError(\n `Свойство подключаемого модуля «${feature.key}» объявлено повторно`,\n );\n }\n keys.add(feature.key);\n }\n const create = feature.create.bind(feature);\n return Object.freeze({\n ...(feature.key === undefined ? {} : { key: feature.key }),\n create,\n });\n });\n}\n\nconst DISPOSED_ACCOUNTS = new WeakSet<ItdAccounts>();\n\nfunction assertAccountsActive(accounts: ItdAccounts, action: string): void {\n if (!DISPOSED_ACCOUNTS.has(accounts)) return;\n throw new ItdStateError(\n `ItdAccounts уже окончательно освобождён через dispose(); нельзя ${action}. Создайте новый контейнер`,\n );\n}\n\n/**\n * Несколько аккаунтов итд.com в одном месте.\n *\n * Контейнер именованных `ItdClient`: каждый аккаунт получает собственный токен, cookie\n * и `deviceId`, а сессии всех складываются в одно хранилище — обычно в один файл, а не\n * в десяток. Имя аккаунта выбираете вы; сервер о нём ничего не знает.\n *\n * @example Бот на нескольких аккаунтах\n * ```ts\n * import { ItdAccounts } from 'itd-api';\n * import { FileMultiTokenStorage } from 'itd-api/node';\n *\n * await using accounts = new ItdAccounts({\n * storage: new FileMultiTokenStorage('./.itd-sessions.json'),\n * rateLimit: { concurrency: 4 },\n * });\n *\n * // Восстанавливаем тех, кто уже входил раньше: токен возьмётся из хранилища.\n * await accounts.restore();\n *\n * if (!accounts.has('kiow')) {\n * accounts.addAccount('kiow', { auth: { email, password, getTurnstileToken } });\n * }\n *\n * await accounts.account('kiow').posts.create({ content: 'привет' });\n *\n * for (const [name, itd] of accounts) {\n * console.log(name, await itd.getUserId());\n * }\n * ```\n */\nexport class ItdAccounts {\n /** Опции, общие для всех аккаунтов, — без полей, которые задаются поаккаунтно. */\n readonly #base: Omit<ItdClientOptions, 'auth' | 'storage' | 'deviceId'>;\n readonly #storage: MultiTokenStorage;\n readonly #clients = new Map<string, ItdClient>();\n /** Имена, чьё удаление ещё не завершилось: повторно занять их пока нельзя. */\n readonly #removing = new Set<string>();\n /** Уже начатые удаления, которых должен дождаться терминальный dispose(). */\n readonly #accountRemovals = new Set<Promise<void>>();\n /** Управляемые срезы хранилища: после удаления аккаунт больше не может писать через свой. */\n readonly #storageControls = new Map<string, ControlledTokenStorage>();\n /** Подписки на события клиентов — снимаются вместе с аккаунтом. */\n readonly #eventUnsubscribers = new Map<string, Unsubscribe[]>();\n /** Плагины для всех: и для уже заведённых аккаунтов, и для будущих. */\n readonly #plugins: ClientPlugin[];\n /** Фабрики модулей для новых и восстанавливаемых аккаунтов. */\n readonly #features: AccountFeature[];\n /** Имена плагинов, чья асинхронная очистка ещё не завершилась. */\n readonly #removingPlugins = new Set<string>();\n /** Общая очередь. `undefined`, когда у каждого аккаунта своя. */\n readonly #queues: RequestQueuePool | undefined;\n readonly #rateLimitScope: RateLimitScope;\n readonly #emitter: Emitter<AccountEvents>;\n readonly #logger: Logger | undefined;\n /** Общий результат терминальной очистки для идемпотентных повторных вызовов. */\n #disposePromise: Promise<void> | undefined;\n\n constructor(options: ItdAccountsOptions = {}) {\n const { storage, plugins, features, rateLimitScope, ...base } = options;\n\n if (\n rateLimitScope !== undefined &&\n rateLimitScope !== 'account' &&\n rateLimitScope !== 'shared'\n ) {\n throw new ItdConfigError(\"rateLimitScope должен быть 'account' или 'shared'\");\n }\n\n this.#base = base;\n this.#storage = storage ?? new MemoryMultiTokenStorage();\n this.#plugins = orderPluginDefinitions(plugins ?? []);\n this.#features = resolveAccountFeatures(features);\n this.#rateLimitScope = rateLimitScope ?? 'shared';\n\n // Общая очередь заводится сразу: проверить опции лучше при создании контейнера,\n // а не при добавлении первого аккаунта.\n const rateLimit =\n this.#rateLimitScope === 'shared' ? resolveRateLimit(base.rateLimit, ITD_CATALOG) : undefined;\n this.#queues = rateLimit\n ? new RequestQueuePool(rateLimit, base.clock ?? systemClock)\n : undefined;\n\n const logger = typeof base.logger === 'object' ? base.logger : undefined;\n this.#logger = logger;\n this.#emitter = new Emitter<AccountEvents>((error) =>\n reportListenerError(logger, 'аккаунтов', error),\n );\n }\n\n /** Общее хранилище сессий — то же, что передано опцией `storage`. */\n get storage(): MultiTokenStorage {\n return this.#storage;\n }\n\n /** Сколько аккаунтов заведено. */\n get size(): number {\n return this.#clients.size;\n }\n\n /** Имена заведённых аккаунтов в порядке добавления. */\n names(): string[] {\n return [...this.#clients.keys()];\n }\n\n /** Заведён ли аккаунт с таким именем. */\n has(name: string): boolean {\n return this.#clients.has(name);\n }\n\n /**\n * Заводит аккаунт.\n *\n * Возвращается обычный `ItdClient` — со всеми ресурсами, плагинами и каналом событий.\n * Хранилище ему подставляется само: срез общего по имени аккаунта.\n *\n * Опция `auth` не обязательна: когда сессия этого аккаунта уже лежит в хранилище,\n * токен возьмётся оттуда, а истёкший продлится сам.\n *\n * @throws {ItdConfigError} если имя пустое или уже занято\n *\n * @example\n * ```ts\n * accounts.addAccount('bot', { auth: { email, password, getTurnstileToken } });\n * accounts.addAccount('reader', { auth: '<accessToken>' });\n * accounts.addAccount('через-прокси', { fetch: proxyFetch('socks5://…') });\n * ```\n */\n addAccount(name: string, options: AddAccountOptions = {}): ItdClient {\n assertAccountsActive(this, 'добавить аккаунт');\n validateAccountName(name);\n if (this.#clients.has(name)) {\n throw new ItdConfigError(\n `аккаунт «${name}» уже добавлен. Возьмите его через accounts.account('${name}')`,\n );\n }\n if (this.#removing.has(name)) {\n throw new ItdConfigError(\n `аккаунт «${name}» ещё удаляется. Дождитесь завершения removeAccount() перед повторным добавлением`,\n );\n }\n if (\n this.#rateLimitScope === 'shared' &&\n options.rateLimit !== undefined &&\n options.rateLimit !== false\n ) {\n throw new ItdConfigError(\n \"при rateLimitScope: 'shared' настройки rateLimit задаются контейнеру; \" +\n 'аккаунту можно передать только rateLimit: false',\n );\n }\n\n const storageControl = controlledTokenStorage(this.#storage, name);\n let client: ItdClient | undefined;\n try {\n client = createManagedClient(\n this.#mergeOptions(options, storageControl.storage),\n this.#queues ? { queues: this.#queues } : {},\n );\n\n for (const plugin of this.#plugins) client.use(plugin);\n for (const feature of this.#features) {\n const manifest = feature.create();\n if (feature.key === undefined) client.install(manifest);\n else client.withFeature(feature.key, manifest);\n }\n } catch (error) {\n storageControl.revoke();\n if (client) {\n void client.dispose().catch((cleanupError: unknown) => {\n this.#reportCleanup('неудачного добавления аккаунта', cleanupError);\n });\n }\n throw error;\n }\n\n const unsubscribers = this.#forwardEvents(name, client);\n this.#storageControls.set(name, storageControl);\n this.#eventUnsubscribers.set(name, unsubscribers);\n this.#clients.set(name, client);\n\n return client;\n }\n\n /**\n * Клиент аккаунта.\n *\n * @throws {ItdConfigError} если такого аккаунта нет\n *\n * @example\n * ```ts\n * await accounts.account('kiow').posts.like(postId);\n * ```\n */\n account(name: string): ItdClient {\n assertAccountsActive(this, 'получить клиент аккаунта');\n const client = this.#clients.get(name);\n if (!client) {\n const known = this.names();\n throw new ItdConfigError(\n `аккаунт «${name}» не заведён. ` +\n (known.length > 0\n ? `Известны: ${known.join(', ')}`\n : 'Ни одного аккаунта нет — добавьте его через addAccount() или restore()'),\n );\n }\n\n return client;\n }\n\n /**\n * Поднимает аккаунты, сессии которых уже лежат в хранилище.\n *\n * То, ради чего мультихранилище знает свой состав: после перезапуска процесса\n * ни `auth`, ни капча не нужны — токен, `deviceId` и cookie берутся из сохранённого.\n * Уже заведённые аккаунты не трогаются. Записи, в которых после выхода остался только\n * `deviceId`, пропускаются: авторизованной сессии в них уже нет.\n *\n * @returns имена добавленных аккаунтов\n *\n * @example\n * ```ts\n * const restored = await accounts.restore();\n * console.log(`подняли ${restored.length} аккаунтов без единого входа`);\n * ```\n */\n async restore(): Promise<string[]> {\n assertAccountsActive(this, 'восстановить аккаунты');\n const saved = await this.#storage.accounts();\n for (const name of saved) validateAccountName(name);\n\n const candidates = [...new Set(saved)].filter((name) => !this.#clients.has(name));\n const sessions = await Promise.all(\n candidates.map(async (name) => ({ name, session: await this.#storage.get(name) })),\n );\n const added: string[] = [];\n\n for (const { name, session } of sessions) {\n if (!isRestorableSession(session)) continue;\n // Пока читали хранилище, аккаунт могли добавить вручную или начать удалять.\n if (this.#clients.has(name) || this.#removing.has(name)) continue;\n this.addAccount(name);\n added.push(name);\n }\n\n return added;\n }\n\n /**\n * Убирает аккаунт: закрывает его клиента и, если попросить, забывает сессию.\n *\n * Сетевого запроса не выполняет. Чтобы завершить сессию на сервере, вызовите\n * `itd.auth.logout()` до удаления.\n *\n * @returns `false`, если такого аккаунта и не было\n */\n async removeAccount(name: string, options: RemoveAccountOptions = {}): Promise<boolean> {\n assertAccountsActive(this, 'удалить аккаунт');\n const client = this.#clients.get(name);\n if (!client) return false;\n\n this.#removing.add(name);\n this.#clients.delete(name);\n const storageControl = this.#storageControls.get(name);\n storageControl?.revoke();\n this.#storageControls.delete(name);\n\n for (const unsubscribe of this.#eventUnsubscribers.get(name) ?? []) unsubscribe();\n this.#eventUnsubscribers.delete(name);\n\n const removal = (async () => {\n const errors: unknown[] = [];\n const closing = await Promise.allSettled([\n client.dispose(),\n storageControl?.drain() ?? Promise.resolve(),\n ]);\n for (const result of closing) {\n if (result.status === 'rejected') errors.push(result.reason);\n }\n\n if (options.forget) {\n await Promise.resolve(this.#storage.clear(name)).catch((error: unknown) => {\n errors.push(error);\n });\n }\n\n if (errors.length > 0) throw errors[0];\n })();\n this.#accountRemovals.add(removal);\n\n try {\n await removal;\n return true;\n } finally {\n this.#accountRemovals.delete(removal);\n this.#removing.delete(name);\n }\n }\n\n /**\n * Подключает плагин всем аккаунтам — и заведённым, и будущим.\n *\n * @throws {ItdConfigError} если плагин задан неверно или уже подключён\n *\n * @example\n * ```ts\n * accounts.use(crypt());\n * ```\n */\n use(plugin: ClientPlugin): this {\n assertAccountsActive(this, 'подключить плагин');\n if (this.#removingPlugins.has(plugin?.name)) {\n throw new ItdConfigError(\n `плагин «${plugin.name}» ещё отключается; дождитесь завершения accounts.unuse()`,\n );\n }\n const ordered = orderPluginDefinitions([...this.#plugins, plugin]);\n const clients = [...this.#clients.values()];\n for (const client of clients) assertClientCanUsePlugin(client, plugin);\n\n const installed: ItdClient[] = [];\n try {\n for (const client of clients) {\n client.use(plugin);\n installed.push(client);\n }\n } catch (error) {\n // remove() исключает запись из реестра до первого await, поэтому видимое состояние\n // откатывается синхронно, а асинхронные teardown завершаются в фоне.\n for (const client of installed.reverse()) {\n void client.unuse(plugin.name).catch((cleanupError: unknown) => {\n this.#reportCleanup(`отката плагина «${plugin.name}»`, cleanupError);\n });\n }\n throw error;\n }\n this.#plugins.splice(0, this.#plugins.length, ...ordered);\n\n return this;\n }\n\n /** Имена общих плагинов в фактическом порядке выполнения обёрток. */\n pluginNames(): string[] {\n return this.#plugins.map((plugin) => plugin.name);\n }\n\n /** Подключён ли общий плагин с таким именем. */\n hasPlugin(name: string): boolean {\n return this.#plugins.some((plugin) => plugin.name === name);\n }\n\n /**\n * Отключает общий плагин у существующих клиентов и не применяет его к будущим.\n *\n * @returns `false`, если такого плагина не было\n * @throws {ItdConfigError} если от плагина зависит другой общий плагин\n */\n async unuse(name: string): Promise<boolean> {\n const index = this.#plugins.findIndex((plugin) => plugin.name === name);\n if (index < 0) return false;\n assertPluginRemovable(this.#plugins, name);\n const clients = [...this.#clients.values()];\n for (const client of clients) assertClientCanUnusePlugin(client, name);\n this.#plugins.splice(index, 1);\n this.#removingPlugins.add(name);\n\n try {\n const results = await Promise.allSettled(clients.map((client) => client.unuse(name)));\n const errors = results\n .filter((result): result is PromiseRejectedResult => result.status === 'rejected')\n .map((result) => result.reason);\n if (errors.length > 0) {\n throw new AggregateError(errors, `Не удалось отключить плагин «${name}» у всех аккаунтов`);\n }\n return true;\n } finally {\n this.#removingPlugins.delete(name);\n }\n }\n\n /**\n * Подписывается на события авторизации всех аккаунтов сразу.\n *\n * @returns функция отписки\n *\n * @example\n * ```ts\n * accounts.on('authError', ({ account }) => console.warn(`${account}: сессия потеряна`));\n * ```\n */\n on<K extends keyof AccountEvents>(event: K, listener: Listener<AccountEvents[K]>): Unsubscribe {\n assertAccountsActive(this, 'подписаться на события');\n return this.#emitter.on(event, listener);\n }\n\n /**\n * Перебор аккаунтов парами «имя — клиент».\n *\n * @example\n * ```ts\n * for (const [name, itd] of accounts) {\n * const me = await itd.users.me();\n * console.log(name, me.nickname);\n * }\n * ```\n */\n [Symbol.iterator](): IterableIterator<[string, ItdClient]> {\n return this.#clients.entries();\n }\n\n /**\n * Закрывает все аккаунты и останавливает общую очередь.\n *\n * Аккаунты остаются в контейнере и работоспособны: новые запросы поднимут всё заново,\n * но уже созданные потоки уведомлений останутся закрытыми.\n *\n * @example\n * ```ts\n * await using accounts = new ItdAccounts({ storage });\n * // …работа…\n * // dispose() вызовется сам на выходе из блока\n * ```\n */\n async close(): Promise<void> {\n try {\n await Promise.all([...this.#clients.values()].map((client) => client.close()));\n } finally {\n this.#queues?.stop();\n }\n }\n\n /**\n * Окончательно освобождает контейнер и отключает общие плагины у всех аккаунтов.\n *\n * Для временной остановки потоков и очереди без отключения плагинов используйте\n * {@link close}. Терминальное состояние устанавливается сразу: контейнер отзывает\n * storage-срезы и подписки, а новые аккаунты, запросы его клиентов и плагины после\n * первого `dispose()` больше не допускаются.\n */\n dispose(): Promise<void> {\n if (this.#disposePromise) return this.#disposePromise;\n DISPOSED_ACCOUNTS.add(this);\n this.#disposePromise = this.#dispose();\n return this.#disposePromise;\n }\n\n async #dispose(): Promise<void> {\n const clients = [...this.#clients.values()];\n const controls = [...this.#storageControls.values()];\n const accountRemovals = [...this.#accountRemovals];\n\n for (const control of controls) control.revoke();\n for (const unsubscribers of this.#eventUnsubscribers.values()) {\n for (const unsubscribe of unsubscribers) unsubscribe();\n }\n this.#eventUnsubscribers.clear();\n this.#storageControls.clear();\n this.#clients.clear();\n this.#emitter.removeAllListeners();\n\n const results = await Promise.allSettled([\n ...clients.map((client) => client.dispose()),\n ...controls.map((control) => control.drain()),\n ...accountRemovals,\n Promise.resolve().then(() => this.#queues?.clear()),\n ]);\n this.#plugins.splice(0);\n this.#features.splice(0);\n this.#removingPlugins.clear();\n\n const errors = results\n .filter((result): result is PromiseRejectedResult => result.status === 'rejected')\n .map((result) => result.reason);\n if (errors.length > 0) throw new AggregateError(errors, 'Не удалось освободить аккаунты');\n }\n\n /** Позволяет использовать контейнер с `await using`. */\n [Symbol.asyncDispose](): Promise<void> {\n return this.dispose();\n }\n\n static {\n installAsyncDisposeFallback(ItdAccounts);\n }\n\n /**\n * Собирает опции клиента: общие, поверх них — аккаунтные, и обязательно свой срез\n * хранилища.\n *\n * `headers` и `services` сливаются по ключам, а не заменяются целиком: иначе один свой\n * заголовок у аккаунта стирал бы весь общий набор.\n */\n #mergeOptions(options: AddAccountOptions, storage: TokenStorage): ItdClientOptions {\n const base = this.#base;\n\n return {\n ...base,\n ...options,\n ...(base.headers || options.headers\n ? { headers: { ...base.headers, ...options.headers } }\n : {}),\n ...(base.services || options.services\n ? { services: { ...base.services, ...options.services } }\n : {}),\n storage,\n };\n }\n\n /** Не теряет ошибку фонового освобождения ресурсов, которого синхронный API не ждёт. */\n #reportCleanup(scope: string, error: unknown): void {\n const message = `Не удалось освободить ресурсы после ${scope}`;\n if (this.#logger) this.#logger.error(message, error);\n else console.error(`[itd-api] ${message}`, error);\n }\n\n /** Ретранслирует события клиента наружу, добавляя к ним имя аккаунта. */\n #forwardEvents(account: string, client: ItdClient): Unsubscribe[] {\n return [\n client.on('tokens', ({ accessToken }) =>\n this.#emitter.emit('tokens', { account, accessToken }),\n ),\n client.on('signIn', ({ accessToken }) =>\n this.#emitter.emit('signIn', { account, accessToken }),\n ),\n client.on('signOut', () => this.#emitter.emit('signOut', { account })),\n client.on('authError', ({ error }) => this.#emitter.emit('authError', { account, error })),\n ];\n }\n}\n\n/**\n * Создаёт контейнер аккаунтов.\n *\n * То же, что `new ItdAccounts(options)`, — для тех, кому привычнее фабрика.\n */\nexport function createAccounts(options: ItdAccountsOptions = {}): ItdAccounts {\n return new ItdAccounts(options);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["#createEvents","#events","#config","#http","#jar","#emitter","#hooks","#invalidateInFlight","#authScope","#loadSession","#identityForToken","#readExternalToken","#session","#sessionFromConfig","#externalIdentity","#authEpoch","#externalToken","#rotateAuthScope","#hasRefreshSession","#deviceId","#deviceIdLoading","#resolveDeviceId","#saveSession","#signInWithCredentials","#refreshDeduplicated","#noRefreshSessionError","#transitionAuth","#seedRefreshCookie","#loading","#performLoad","#refreshing","#performRefresh","#reloginOrNull","#signingIn","#performSignIn","#resolveTurnstileToken","#auth","#runtime","#features","#resources","#auth","#close","#disposePromise","#dispose","#base","#storage","#clients","#removing","#accountRemovals","#storageControls","#eventUnsubscribers","#plugins","#features","#removingPlugins","#queues","#rateLimitScope","#emitter","#logger","#mergeOptions","#reportCleanup","#forwardEvents","#disposePromise","#dispose"],"sources":["../src/notifications-api.ts","../src/operations/auth.ts","../src/session/jwt.ts","../src/session/auth.ts","../src/resources/auth.ts","../src/client.ts","../src/accounts.ts"],"sourcesContent":["import type { HttpClient } from './core/execution/http.js';\nimport type { NotificationEvents } from './events/stream.js';\nimport { NotificationsResource } from './resources/notifications.js';\n\n/** Уведомления полного клиента: REST-методы и стабильный канал событий. */\nexport interface NotificationsApi extends NotificationsResource {\n /** Один канал событий уведомлений на всё время жизни клиента. */\n readonly events: NotificationEvents;\n}\n\nclass ClientNotifications extends NotificationsResource implements NotificationsApi {\n readonly #createEvents: (resource: NotificationsResource) => NotificationEvents;\n #events: NotificationEvents | undefined;\n\n constructor(\n http: HttpClient,\n createEvents: (resource: NotificationsResource) => NotificationEvents,\n ) {\n super(http);\n this.#createEvents = createEvents;\n }\n\n get events(): NotificationEvents {\n this.#events ??= this.#createEvents(this);\n return this.#events;\n }\n}\n\n/** Создаёт API уведомлений полного клиента. @internal */\nexport function createNotificationsApi(\n http: HttpClient,\n createEvents: (resource: NotificationsResource) => NotificationEvents,\n): NotificationsApi {\n return new ClientNotifications(http, createEvents);\n}\n","import { ItdConfigError } from '../core/errors.js';\nimport { pickArray, pickString } from '../core/unwrap.js';\nimport { defineBuiltInOperation } from '../domain/operations.js';\nimport type { Session } from '../models/account.js';\nimport type { AuthState } from '../models/users.js';\nimport { passthroughOperation, voidOperation } from './common.js';\n\n/** Чем закончился запрос входа. */\nexport const SignInStatus = Object.freeze({\n Authenticated: 'authenticated',\n OtpRequired: 'otp_required',\n} as const);\nexport type SignInStatus = (typeof SignInStatus)[keyof typeof SignInStatus];\n\n/** Результат входа: готовый токен либо продолжение через код подтверждения. */\nexport type SignInResult =\n | { status: 'authenticated'; accessToken: string }\n | { status: 'otp_required'; flowToken: string | undefined };\n\nfunction requireString(body: unknown, key: string, message: string): string {\n const value = pickString(body, key);\n if (!value) throw new ItdConfigError(message);\n return value;\n}\n\nexport const AUTH_CHECK = passthroughOperation<AuthState>('auth.check');\nexport const AUTH_SIGN_UP = defineBuiltInOperation<string>('auth.signUp', (body) =>\n requireString(body, 'flowToken', 'Сервер не вернул flowToken при регистрации'),\n);\nexport const AUTH_SIGN_IN = defineBuiltInOperation<SignInResult>('auth.signIn', (body) => {\n const accessToken = pickString(body, 'accessToken');\n return accessToken\n ? { status: SignInStatus.Authenticated, accessToken }\n : { status: SignInStatus.OtpRequired, flowToken: pickString(body, 'flowToken') };\n});\nexport const AUTH_VERIFY_OTP = defineBuiltInOperation<string>('auth.verifyOtp', (body) =>\n requireString(body, 'accessToken', 'Сервер не вернул accessToken после подтверждения кода'),\n);\nexport const AUTH_REFRESH = defineBuiltInOperation<string | undefined>('auth.refresh', (body) =>\n pickString(body, 'accessToken'),\n);\nexport const AUTH_RESEND_OTP = voidOperation('auth.resendOtp');\nexport const AUTH_LOGOUT = voidOperation('auth.logout');\nexport const AUTH_FORGOT_PASSWORD = defineBuiltInOperation<string>('auth.forgotPassword', (body) =>\n requireString(body, 'flowToken', 'Сервер не вернул flowToken при запросе сброса пароля'),\n);\nexport const AUTH_RESET_PASSWORD = voidOperation('auth.resetPassword');\nexport const AUTH_CHANGE_PASSWORD = voidOperation('auth.changePassword');\nexport const AUTH_SESSIONS = defineBuiltInOperation<Session[]>('auth.sessions', (body) =>\n pickArray<Session>(body, 'sessions'),\n);\nexport const AUTH_REVOKE_SESSION = voidOperation('auth.revokeSession');\nexport const AUTH_REVOKE_OTHER_SESSIONS = voidOperation('auth.revokeOtherSessions');\n","/**\n * Разбирает строку base64url.\n *\n * `atob` понимает только обычный base64, поэтому алфавит приводится к нему, а недостающее\n * выравнивание добивается. Результат декодируется как UTF-8: в полезной нагрузке токена\n * может оказаться кириллица, и «бинарная» строка из `atob` сломала бы `JSON.parse`.\n */\nfunction decodeBase64Url(segment: string): string {\n const base64 = segment.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=');\n\n const binary = atob(padded);\n const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));\n\n return new TextDecoder().decode(bytes);\n}\n\n/** Идентификаторы владельца и сессии, прочитанные из JWT без проверки подписи. */\nexport interface TokenIdentity {\n subject?: string | undefined;\n sessionId?: string | undefined;\n}\n\n/**\n * Читает `sub` и `sid` из полезной нагрузки JWT.\n *\n * Подпись не проверяется — ключа для проверки у клиента нет. Прочитанные значения служат\n * лишь метками для разделения локального состояния и не дают доступа: его определяет сервер.\n */\nexport function readTokenIdentity(token: string): TokenIdentity {\n try {\n const payload = token.split('.')[1];\n if (!payload) return {};\n\n const parsed: unknown = JSON.parse(decodeBase64Url(payload));\n if (typeof parsed !== 'object' || parsed === null) return {};\n\n const { sub, sid } = parsed as { sub?: unknown; sid?: unknown };\n return {\n ...(typeof sub === 'string' && sub.length > 0 ? { subject: sub } : {}),\n ...(typeof sid === 'string' && sid.length > 0 ? { sessionId: sid } : {}),\n };\n } catch {\n // Токен другого формата — не повод падать: поля просто останутся пустыми.\n return {};\n }\n}\n\n/**\n * Читает `sub` из полезной нагрузки JWT.\n *\n * @returns идентификатор владельца токена; `undefined`, если токен не JWT, повреждён\n * или поля `sub` в нём нет\n */\nexport function readTokenSubject(token: string): string | undefined {\n return readTokenIdentity(token).subject;\n}\n","import type { AuthIdentity, AuthProvider, AuthProviderDeps } from '../core/auth-provider.js';\nimport type { ItdClock } from '../core/clock.js';\nimport {\n AUTH_FLAG_COOKIE,\n type CookieJar,\n REFRESH_COOKIE,\n REFRESH_COOKIE_PATH,\n} from '../core/cookies.js';\nimport { Emitter, reportListenerError } from '../core/emitter.js';\nimport { ItdApiError, ItdAuthError, ItdConfigError } from '../core/errors.js';\nimport type { HttpClient } from '../core/execution/http.js';\nimport type { Logger } from '../core/options.js';\nimport { createDeviceId } from '../core/runtime.js';\nimport { isRecord, requireOptionalBoolean } from '../core/validate.js';\nimport type { UserId } from '../models/common.js';\nimport { AUTH_REFRESH, AUTH_SIGN_IN, SignInStatus } from '../operations/auth.js';\nimport { readTokenIdentity, readTokenSubject } from './jwt.js';\nimport type { AuthInput, CredentialsAuth, SessionOptions } from './options.js';\nimport { copySession, type ItdSession, MemoryTokenStorage, type TokenStorage } from './storage.js';\n\n/**\n * Настройки сессии после подстановки умолчаний и проверок.\n *\n * Часть полей заимствована у {@link ResolvedRuntimeConfig}: сессия ходит на тот же хост,\n * теми же часами и с тем же логгером, но остальные настройки исполнения ей не нужны.\n */\nexport interface SessionConfig {\n baseUrl: string;\n clock: ItdClock;\n useCookieJar: boolean;\n logger: Logger | undefined;\n auth: AuthInput | undefined;\n storage: TokenStorage;\n deviceId: string | undefined;\n autoRefresh: boolean;\n reloginOnRefreshFailure: boolean;\n}\n\n/**\n * Проверяет форму `auth` и сообщает о типичных ошибках понятным текстом.\n *\n * Молчаливое игнорирование неверной формы приводит к загадочным `401`, поэтому\n * ошибка возникает сразу при создании клиента.\n */\nfunction validateAuth(auth: AuthInput | undefined): AuthInput | undefined {\n if (auth === undefined) return undefined;\n\n if (typeof auth === 'string') {\n if (auth.trim() === '') {\n throw new ItdConfigError('auth: передана пустая строка вместо accessToken');\n }\n return auth;\n }\n\n if (typeof auth !== 'object' || auth === null) {\n throw new ItdConfigError(\n `auth должен быть строкой с токеном или объектом, получено: ${typeof auth}`,\n );\n }\n\n if ('getToken' in auth) {\n if (typeof auth.getToken !== 'function') {\n throw new ItdConfigError('auth.getToken должен быть функцией');\n }\n return { ...auth };\n }\n\n if ('accessToken' in auth) {\n if (typeof auth.accessToken !== 'string' || auth.accessToken.trim() === '') {\n throw new ItdConfigError('auth.accessToken должен быть непустой строкой');\n }\n return { ...auth };\n }\n\n if ('email' in auth || 'password' in auth) {\n const { email, password, turnstileToken, getTurnstileToken } = auth as {\n email?: unknown;\n password?: unknown;\n turnstileToken?: unknown;\n getTurnstileToken?: unknown;\n };\n if (typeof email !== 'string' || email.trim() === '') {\n throw new ItdConfigError('auth.email должен быть непустой строкой');\n }\n if (typeof password !== 'string' || password === '') {\n throw new ItdConfigError('auth.password должен быть непустой строкой');\n }\n if (getTurnstileToken !== undefined && typeof getTurnstileToken !== 'function') {\n throw new ItdConfigError('auth.getTurnstileToken должен быть функцией');\n }\n if (\n turnstileToken !== undefined &&\n (typeof turnstileToken !== 'string' || turnstileToken.trim() === '')\n ) {\n throw new ItdConfigError('auth.turnstileToken должен быть непустой строкой');\n }\n\n // Отсутствие капчи — не ошибка конфигурации: сессия может быть восстановлена из\n // хранилища, и до входа по паролю дело вообще не дойдёт. Ошибка возникнет в момент\n // входа, где её текст может объяснить, что именно нужно сделать.\n return { ...auth };\n }\n\n throw new ItdConfigError(\n 'auth не распознан. Ожидается строка с accessToken либо объект ' +\n '{ accessToken }, { email, password } или { getToken }',\n );\n}\n\nfunction resolveStorage(storage: TokenStorage | undefined): TokenStorage {\n if (storage === undefined) return new MemoryTokenStorage();\n if (!isRecord(storage)) throw new ItdConfigError('storage должен быть объектом TokenStorage');\n\n for (const method of ['get', 'set', 'clear'] as const) {\n if (typeof storage[method] !== 'function') {\n throw new ItdConfigError(`storage.${method} должен быть функцией`);\n }\n }\n return storage;\n}\n\n/**\n * Приводит настройки сессии к полному виду.\n *\n * @param runtime уже разрешённая конфигурация исполнения — из неё берутся общие поля\n * @throws {ItdConfigError} при некорректных значениях\n */\nexport function resolveSessionConfig(\n options: SessionOptions,\n runtime: Pick<SessionConfig, 'baseUrl' | 'clock' | 'useCookieJar' | 'logger'>,\n): SessionConfig {\n if (!isRecord(options)) throw new ItdConfigError('опции клиента должны быть объектом');\n\n requireOptionalBoolean(options.autoRefresh, 'autoRefresh');\n requireOptionalBoolean(options.reloginOnRefreshFailure, 'reloginOnRefreshFailure');\n\n if (\n options.deviceId !== undefined &&\n (typeof options.deviceId !== 'string' || options.deviceId.trim() === '')\n ) {\n throw new ItdConfigError('deviceId должен быть непустой строкой');\n }\n\n return {\n ...runtime,\n auth: validateAuth(options.auth),\n storage: resolveStorage(options.storage),\n deviceId: options.deviceId,\n autoRefresh: options.autoRefresh ?? true,\n reloginOnRefreshFailure: options.reloginOnRefreshFailure ?? true,\n };\n}\n\n/**\n * Собирает менеджер сессии итд.com как реализацию {@link AuthProvider}.\n *\n * Единственная точка, где конвейер запросов встречается с полноценной авторизацией.\n * Ядро её не вызывает — фабрику передаёт фасад клиента, иначе ссылка на сессию оказалась бы\n * достижимой из любой сборки, включая анонимную.\n */\nexport function createItdAuth(options: SessionOptions, deps: AuthProviderDeps): AuthManager {\n return new AuthManager(resolveSessionConfig(options, deps.config), deps.http, deps.cookies, {\n onAccountChange: deps.onAccountChange,\n });\n}\n\n/** Пути эндпоинтов авторизации. */\nexport const AUTH_PATHS = {\n signUp: '/api/v1/auth/sign-up',\n signIn: '/api/v1/auth/sign-in',\n verifyOtp: '/api/v1/auth/verify-otp',\n resendOtp: '/api/v1/auth/resend-otp',\n refresh: '/api/v1/auth/refresh',\n logout: '/api/v1/auth/logout',\n forgotPassword: '/api/v1/auth/forgot-password',\n resetPassword: '/api/v1/auth/reset-password',\n changePassword: '/api/v1/auth/change-password',\n sessions: '/api/v1/auth/sessions',\n} as const;\n\n/**\n * Публичный ключ Cloudflare Turnstile платформы итд.com.\n *\n * Нужен, чтобы отрисовать виджет капчи и получить токен для `signIn`, `signUp`\n * и `forgotPassword`.\n *\n * Ключ привязан к домену: на чужом origin Cloudflare отказывает виджету с кодом `110200`.\n * Поэтому отрисовать его может только код, выполняемый на самом итд.com. Остальным\n * подходит `@itd-api/turnstile`, готовый токен из другого источника или вовсе вход\n * без капчи — по сохранённой сессии либо по токенам, взятым в браузере.\n *\n * @example\n * ```ts\n * turnstile.render('#captcha', {\n * sitekey: TURNSTILE_SITE_KEY,\n * callback: (turnstileToken) => itd.auth.signIn({ email, password, turnstileToken }),\n * });\n * ```\n */\nexport const TURNSTILE_SITE_KEY = '0x4AAAAAACHhxczw6fJGwPBg';\n\n/** Заголовок с идентификатором устройства. Сервер связывает с ним запись в списке сессий. */\nexport const DEVICE_ID_HEADER = 'X-Device-Id';\n\n/** События слоя авторизации. */\nexport interface AuthEvents {\n /** Токен получен или обновлён. */\n tokens: { accessToken: string };\n /** Выполнен вход. */\n signIn: { accessToken: string };\n /** Сессия очищена — вручную или из-за неудачного обновления. */\n signOut: undefined;\n /** Обновить сессию не удалось; дальнейшие запросы будут падать с 401. */\n authError: { error: unknown };\n}\n\n/** Убирает поле, которое сохраняли промежуточные версии с поддержкой `getUserId()`. */\nfunction withoutLegacyUserId(session: ItdSession): ItdSession {\n const clean = { ...session };\n delete (clean as ItdSession & { userId?: unknown }).userId;\n return clean;\n}\n\nlet authScopeSequence = 0;\n\nfunction nextAuthScope(): string {\n authScopeSequence += 1;\n return String(authScopeSequence);\n}\n\ninterface AuthManagerHooks {\n /** Вызывается синхронно перед заменой владельца авторизации. */\n onAccountChange?: (() => void) | undefined;\n}\n\n/**\n * Хранит сессию и продлевает её.\n *\n * Главное здесь — **дедупликация обновления**. Когда десять параллельных запросов\n * одновременно получают `401`, обновление должно произойти один раз, а остальные обязаны\n * дождаться его результата. Иначе сервер увидит десять параллельных `refresh`, и все,\n * кроме первого, скорее всего получат отказ по уже использованному токену.\n */\nexport class AuthManager implements AuthProvider {\n readonly #config: SessionConfig;\n readonly #http: HttpClient;\n readonly #jar: CookieJar;\n readonly #emitter: Emitter<AuthEvents>;\n readonly #hooks: AuthManagerHooks;\n\n /** `undefined` — сессия ещё не читалась из хранилища. */\n #session: ItdSession | null | undefined;\n /**\n * Общий промис чтения сессии из хранилища. Дедупликация: параллельные запросы на холодном\n * старте читают хранилище один раз и не заводят каждый свой `deviceId`.\n */\n #loading: Promise<ItdSession | null> | null = null;\n /** Общий промис обновления: к нему присоединяются все, кто получил 401. */\n #refreshing: Promise<string | null> | null = null;\n /** Общий промис входа по логину и паролю. */\n #signingIn: Promise<string> | null = null;\n /** Fallback-область для изоляции состояния плагинов, когда JWT-идентичность недоступна. */\n #authScope = nextAuthScope();\n /** Последний токен внешнего источника; `undefined` означает, что источник ещё не читался. */\n #externalToken: string | null | undefined;\n /** Идентификаторы последнего токена внешнего источника для синхронных потребителей. */\n #externalIdentity: AuthIdentity | undefined;\n /**\n * Идентификатор устройства.\n *\n * Держится отдельно от сессии намеренно: выход из аккаунта не меняет устройство,\n * поэтому `clear()` его не трогает.\n */\n #deviceId: string | undefined;\n /** Общий промис первичной выдачи `deviceId` — чтобы параллельные запросы получили один. */\n #deviceIdLoading: Promise<string> | null = null;\n /**\n * Счётчик смен владельца авторизации.\n *\n * Растёт при каждой операции, которая заменяет или очищает сессию извне: `clear`,\n * `setSession`, `setAccessToken`, вход. `#performRefresh` снимает его значение перед\n * сетевым запросом и сверяет перед записью — иначе запоздавший ответ обновления мог бы\n * воскресить уже очищенную сессию поверх `signOut`.\n */\n #authEpoch = 0;\n\n constructor(\n config: SessionConfig,\n http: HttpClient,\n jar: CookieJar,\n hooks: AuthManagerHooks = {},\n ) {\n this.#config = config;\n this.#http = http;\n this.#jar = jar;\n this.#hooks = hooks;\n this.#emitter = new Emitter<AuthEvents>((error) =>\n reportListenerError(config.logger, 'авторизации', error),\n );\n }\n\n /** Подписка на события авторизации. */\n get on(): Emitter<AuthEvents>['on'] {\n return this.#emitter.on.bind(this.#emitter);\n }\n\n /** Подписка на одно срабатывание. */\n get once(): Emitter<AuthEvents>['once'] {\n return this.#emitter.once.bind(this.#emitter);\n }\n\n /** Снимает служебные подписки при окончательном освобождении владельца. @internal */\n dispose(): void {\n this.#invalidateInFlight();\n this.#emitter.removeAllListeners();\n }\n\n /**\n * Непрозрачная fallback-область авторизации.\n *\n * Изолирует состояние плагинов, когда идентичность аккаунта из JWT недоступна (непрозрачный\n * токен): значение уникально для владельца и сменяется при замене или очистке сессии.\n */\n getAuthScope(): string {\n return this.#authScope;\n }\n\n /** Загружает сессию и возвращает идентификаторы аккаунта и сессии для плагинов. */\n async getAuthIdentity(): Promise<AuthIdentity> {\n const session = await this.#loadSession();\n if (session?.accessToken) return this.#identityForToken(session.accessToken);\n\n const auth = this.#config.auth;\n if (typeof auth === 'object' && auth !== null && 'getToken' in auth) {\n const token = await this.#readExternalToken(() => auth.getToken());\n return this.#identityForToken(token ?? undefined);\n }\n\n return {};\n }\n\n /** Идентификаторы аккаунта и сессии без чтения хранилища. @internal */\n getCurrentAuthIdentity(): AuthIdentity {\n const session =\n this.#session === undefined ? this.#sessionFromConfig(this.#config.auth) : this.#session;\n return session?.accessToken\n ? this.#identityForToken(session.accessToken)\n : (this.#externalIdentity ?? {});\n }\n\n #rotateAuthScope(): void {\n this.#authScope = nextAuthScope();\n }\n\n /** Отмечает смену владельца авторизации — обесценивает результат идущего обновления. */\n #invalidateInFlight(): void {\n this.#authEpoch += 1;\n }\n\n #identityForToken(accessToken: string | undefined): AuthIdentity {\n const token = accessToken ? readTokenIdentity(accessToken) : {};\n return {\n ...(token.subject ? { userId: token.subject as UserId } : {}),\n ...(token.sessionId ? { sessionId: token.sessionId } : {}),\n };\n }\n\n async #readExternalToken(\n getToken: () => string | null | Promise<string | null>,\n ): Promise<string | null> {\n const token = (await getToken()) ?? null;\n const current = this.#identityForToken(token ?? undefined);\n\n if (this.#externalToken !== undefined && this.#externalToken !== token) {\n this.#rotateAuthScope();\n const previous = this.#externalIdentity ?? {};\n const changed =\n previous.userId !== undefined && current.userId !== undefined\n ? previous.userId !== current.userId\n : true;\n if (changed) this.#hooks.onAccountChange?.();\n }\n\n this.#externalToken = token;\n this.#externalIdentity = current;\n return token;\n }\n\n /** Обновляет резервный идентификатор и закрывает события при смене аккаунта. */\n #transitionAuth(accessToken: string | undefined, rotateFallback = true): void {\n const knownPrevious = this.#session !== undefined;\n const previous = this.#identityForToken(this.#session?.accessToken);\n const current = this.#identityForToken(accessToken);\n const changed =\n previous.userId !== undefined && current.userId !== undefined\n ? previous.userId !== current.userId\n : previous.userId !== current.userId || rotateFallback;\n\n if (rotateFallback || changed) this.#rotateAuthScope();\n if (knownPrevious && changed) this.#hooks.onAccountChange?.();\n }\n\n /**\n * Есть ли признак живой refresh-сессии.\n *\n * Рядом с refresh-токеном сервер ставит незакрытую cookie `is_auth` — по ней видно,\n * что продлевать сессию вообще есть смысл, и API не дёргается у анонимов.\n * В браузере cookie ведёт сама среда, поэтому там ответ всегда `true`.\n *\n * Асинхронный, потому что признак может лежать в {@link TokenStorage}: до чтения оттуда\n * ответ был бы `false` даже при полностью рабочей сохранённой сессии.\n */\n async hasRefreshSession(): Promise<boolean> {\n await this.#loadSession();\n return this.#hasRefreshSession();\n }\n\n /** То же самое, но без чтения хранилища — для вызовов, где сессия уже загружена. */\n #hasRefreshSession(): boolean {\n if (!this.#config.useCookieJar) return true;\n // Флаг должен принадлежать основному API, а не другому сервису.\n if (this.#jar.has(AUTH_FLAG_COOKIE, this.#config.baseUrl)) return true;\n\n // Явно переданный refresh-токен — тоже основание пробовать.\n return Boolean(this.#session?.refreshToken);\n }\n\n /**\n * Готовит состояние авторизации до входа запроса в очередь. Стадия `auth_preparation`.\n *\n * Загрузка хранилища, внешний источник токена и отложенный вход асинхронны, поэтому\n * обязаны завершиться до захвата слота очереди.\n */\n async prepare(): Promise<void> {\n await this.token();\n }\n\n /**\n * Заголовки уже подготовленной авторизации без чтения storage или вызова внешнего источника.\n *\n * Используются после ожидания транспортной очереди: к этому моменту {@link prepare} уже\n * был вызван снаружи неё, но token мог успеть смениться из-за refresh или `setSession()`.\n */\n currentHeaders(): Record<string, string> {\n const session =\n this.#session === undefined ? this.#sessionFromConfig(this.#config.auth) : this.#session;\n const token = session?.accessToken ?? this.#externalToken ?? null;\n return token ? { Authorization: `Bearer ${token}` } : {};\n }\n\n /**\n * Реакция конвейера на `401`. Стадия `auth_recovery`.\n *\n * При выключенном `autoRefresh` ничего не делает: вызывающий код обновляет сессию сам\n * через `itd.auth.refresh()`, а библиотека просто пробрасывает {@link ItdAuthError}.\n */\n recover(): Promise<boolean> {\n return this.#config.autoRefresh ? this.onUnauthorized() : Promise.resolve(false);\n }\n\n /**\n * Идентификатор устройства для заголовка `X-Device-Id`.\n *\n * Заводится один раз и сохраняется в сессии, чтобы пережить перезапуск процесса:\n * сервер связывает с ним запись в списке сессий, и плавающее значение плодило бы\n * по новой сессии на каждый старт.\n */\n deviceId(): Promise<string> {\n if (this.#deviceId) return Promise.resolve(this.#deviceId);\n\n // Дедупликация: параллельные вызовы на холодном клиенте получают один `X-Device-Id`.\n this.#deviceIdLoading ??= this.#resolveDeviceId().finally(() => {\n this.#deviceIdLoading = null;\n });\n\n return this.#deviceIdLoading;\n }\n\n async #resolveDeviceId(): Promise<string> {\n const session = await this.#loadSession();\n const deviceId = this.#config.deviceId ?? session?.deviceId ?? createDeviceId();\n\n this.#deviceId = deviceId;\n\n // Записываем, только если значение действительно новое, — иначе каждый первый запрос\n // дёргал бы хранилище без всякой пользы.\n if (session?.deviceId !== deviceId) {\n await this.#saveSession({ ...(session ?? {}), deviceId });\n }\n\n return deviceId;\n }\n\n /**\n * Текущий токен доступа.\n *\n * При необходимости выполняет отложенный вход: если в конфигурации переданы логин\n * и пароль, первый же запрос сам заведёт сессию.\n */\n async token(): Promise<string | null> {\n const session = await this.#loadSession();\n if (session?.accessToken) return session.accessToken;\n\n const auth = this.#config.auth;\n if (!auth) return null;\n\n // Внешний источник токена спрашиваем каждый раз: он сам решает, когда обновлять.\n if (typeof auth === 'object' && 'getToken' in auth) {\n return this.#readExternalToken(() => auth.getToken());\n }\n\n if (typeof auth === 'object' && 'email' in auth) {\n return this.#signInWithCredentials(auth);\n }\n\n return null;\n }\n\n /**\n * Реакция транспорта на ответ `401`.\n *\n * @returns `true`, если токен обновлён и запрос имеет смысл повторить\n */\n async onUnauthorized(): Promise<boolean> {\n try {\n const token = await this.#refreshDeduplicated();\n if (token !== null) return true;\n\n // Обновлять нечем — это тоже отказ авторизации, и подписчик должен о нём узнать.\n this.#emitter.emit('authError', { error: this.#noRefreshSessionError() });\n return false;\n } catch (error) {\n this.#emitter.emit('authError', { error });\n return false;\n }\n }\n\n /**\n * Ошибка «сессию продлить нечем».\n *\n * Возникает, только когда обновление даже не начиналось: нет ни cookie `is_auth`,\n * ни refresh-токена. Если сервер ответил отказом, наружу уходит **его** ошибка —\n * подменять её этой значило бы прятать причину (`REFRESH_TOKEN_MISSING`,\n * `SESSION_NOT_FOUND`, `SESSION_REVOKED` — разные поводы и разные действия).\n */\n #noRefreshSessionError(): ItdAuthError {\n return new ItdAuthError({\n status: 401,\n code: 'SESSION_EXPIRED',\n message:\n 'Не удалось обновить сессию: нет ни cookie is_auth, ни refresh-токена. ' +\n 'Войдите заново либо передайте refreshToken в auth.',\n method: 'POST',\n path: AUTH_PATHS.refresh,\n raw: undefined,\n });\n }\n\n /**\n * Обновляет токен доступа.\n *\n * Параллельные вызовы объединяются в один сетевой запрос.\n *\n * @throws {ItdAuthError} если обновить сессию не удалось\n */\n async refresh(): Promise<string> {\n const token = await this.#refreshDeduplicated();\n if (token === null) throw this.#noRefreshSessionError();\n return token;\n }\n\n /** Сохраняет токен, полученный извне, — например после подтверждения OTP. */\n async setAccessToken(accessToken: string): Promise<void> {\n await this.#loadSession();\n this.#invalidateInFlight();\n this.#transitionAuth(accessToken);\n await this.#saveSession({\n ...(this.#session ?? {}),\n accessToken,\n obtainedAt: this.#config.clock.now(),\n });\n this.#emitter.emit('tokens', { accessToken });\n }\n\n /** Текущая сессия целиком. Полезно, чтобы сохранить её самому. */\n async getSession(): Promise<ItdSession | null> {\n const session = await this.#loadSession();\n return session ? copySession(session) : null;\n }\n\n /**\n * Идентификатор владельца сессии.\n *\n * Считается непосредственно из текущего токена и отдельно не сохраняется: после замены\n * токена идентификатор прежнего владельца остаться не может.\n */\n async getUserId(): Promise<UserId | undefined> {\n const session = await this.#loadSession();\n return session?.accessToken ? readTokenSubject(session.accessToken) : undefined;\n }\n\n /** Заменяет сессию и связанные с ней cookie целиком. */\n async setSession(session: ItdSession): Promise<void> {\n await this.#loadSession();\n this.#invalidateInFlight();\n this.#transitionAuth(session.accessToken);\n this.#jar.clear();\n this.#jar.deserialize(session.cookies);\n\n if (session.deviceId) this.#deviceId = session.deviceId;\n\n // Refresh-cookie добавляется до сериализации сессии.\n this.#session = session;\n this.#seedRefreshCookie();\n\n await this.#saveSession(session);\n }\n\n /**\n * Забывает сессию и cookie. Сетевой запрос не выполняется.\n *\n * Идентификатор устройства выход переживает: иначе каждая пара «выход — вход» плодила бы\n * новую запись в списке сессий.\n */\n async clear(): Promise<void> {\n await this.#loadSession();\n this.#invalidateInFlight();\n this.#transitionAuth(undefined);\n this.#session = null;\n this.#jar.clear();\n await this.#config.storage.clear();\n\n if (this.#deviceId) await this.#saveSession({ deviceId: this.#deviceId });\n\n this.#emitter.emit('signOut', undefined);\n }\n\n #loadSession(): Promise<ItdSession | null> {\n if (this.#session !== undefined) return Promise.resolve(this.#session);\n\n this.#loading ??= this.#performLoad().finally(() => {\n this.#loading = null;\n });\n\n return this.#loading;\n }\n\n async #performLoad(): Promise<ItdSession | null> {\n const loaded = (await this.#config.storage.get()) ?? null;\n const stored = loaded ? withoutLegacyUserId(loaded) : null;\n\n // Восстанавливаем cookie: без них не выйдет обновить токен после перезапуска процесса.\n if (stored?.cookies) this.#jar.deserialize(stored.cookies);\n\n const fromConfig = this.#sessionFromConfig(this.#config.auth);\n\n // Хранилище отвечает за состояние сессии и всегда важнее, но недостающие поля\n // берутся из конфигурации: типичный случай — сохранён только accessToken,\n // а refresh-токен приходит из настроек приложения.\n this.#session =\n stored && fromConfig\n ? {\n ...stored,\n accessToken: stored.accessToken ?? fromConfig.accessToken,\n refreshToken: stored.refreshToken ?? fromConfig.refreshToken,\n }\n : (stored ?? fromConfig);\n\n this.#seedRefreshCookie();\n\n return this.#session;\n }\n\n /**\n * Кладёт refresh-токен в jar как cookie `refresh_token`.\n *\n * `POST /api/v1/auth/refresh` читает токен только из cookie, поэтому переданный строкой\n * приходится превращать в неё. В браузере это невозможно — cookie помечена `HttpOnly`,\n * и там обновление работает только на той, что поставил сам сервер.\n */\n #seedRefreshCookie(): void {\n if (!this.#config.useCookieJar) return;\n\n const refreshToken = this.#session?.refreshToken;\n if (!refreshToken) return;\n if (this.#jar.has(REFRESH_COOKIE)) return;\n\n this.#jar.set(this.#config.baseUrl, REFRESH_COOKIE, refreshToken, REFRESH_COOKIE_PATH);\n }\n\n #sessionFromConfig(auth: AuthInput | undefined): ItdSession | null {\n if (!auth) return null;\n if (typeof auth === 'string') {\n return { accessToken: auth, obtainedAt: this.#config.clock.now() };\n }\n\n if ('accessToken' in auth) {\n return {\n accessToken: auth.accessToken,\n refreshToken: auth.refreshToken,\n obtainedAt: this.#config.clock.now(),\n };\n }\n\n return null;\n }\n\n async #saveSession(session: ItdSession): Promise<void> {\n const cookies = this.#config.useCookieJar ? this.#jar.serialize() : undefined;\n const deviceId = session.deviceId ?? this.#deviceId;\n\n const next = withoutLegacyUserId({\n ...session,\n ...(cookies?.length ? { cookies } : {}),\n ...(deviceId ? { deviceId } : {}),\n });\n\n this.#session = next;\n await this.#config.storage.set(next);\n }\n\n /**\n * Обновление с дедупликацией.\n *\n * Все, кто пришёл, пока обновление уже идёт, получают его результат, а не запускают своё.\n */\n #refreshDeduplicated(): Promise<string | null> {\n if (this.#refreshing) return this.#refreshing;\n\n const promise = this.#performRefresh().finally(() => {\n this.#refreshing = null;\n });\n\n this.#refreshing = promise;\n return promise;\n }\n\n async #performRefresh(): Promise<string | null> {\n await this.#loadSession();\n\n if (!this.#hasRefreshSession()) {\n // Нет признаков сессии — обновлять нечего. При наличии логина и пароля\n // пробуем войти заново.\n return this.#reloginOrNull();\n }\n\n // Снимок владельца авторизации: если он сменится, пока идёт сетевой запрос,\n // результат обновления записывать нельзя.\n const epoch = this.#authEpoch;\n\n try {\n // Служебный запрос идёт через общий pipeline. Слой авторизации он пропускает, чтобы\n // не отправлять устаревший Bearer и не запускать рекурсивный refresh. Очередь безопасна:\n // исходная неудачная транспортная попытка освободила свой слот до вызова этого метода.\n const accessToken = await this.#http.execute(AUTH_REFRESH, {\n path: AUTH_PATHS.refresh,\n skipAuth: true,\n skipAuthRefresh: true,\n // Тела нет намеренно: сервер читает refresh-токен только из cookie — см.\n // #seedRefreshCookie. По той же причине не нужен и устаревший Bearer.\n });\n if (!accessToken) return this.#reloginOrNull();\n\n if (this.#authEpoch !== epoch) {\n // Пока шёл refresh, владельца сменили (signOut / setSession / вход): его результат\n // устарел. Не воскрешаем сохранённую сессию, отдаём актуальный токен как есть.\n return this.#session?.accessToken ?? null;\n }\n\n // Сервер выдаёт при обновлении **новый** refresh-токен (`Set-Cookie: refresh_token=…;\n // Max-Age=2592000`) и тут же гасит прежний. Забрать его из jar обязательно: иначе\n // сохранённая строка протухнет и восстановление сессии из хранилища перестанет работать.\n const rotated = this.#jar.getValue(\n REFRESH_COOKIE,\n this.#config.baseUrl + REFRESH_COOKIE_PATH,\n );\n\n // Refresh продолжает ту же серверную сессию. Для непрозрачного токена это единственная\n // надёжная возможность сохранить scope; смена пользователя в JWT всё равно будет обнаружена.\n this.#transitionAuth(accessToken, false);\n await this.#saveSession({\n ...(this.#session ?? {}),\n accessToken,\n ...(rotated ? { refreshToken: rotated } : {}),\n obtainedAt: this.#config.clock.now(),\n });\n\n this.#emitter.emit('tokens', { accessToken });\n return accessToken;\n } catch (error) {\n if (error instanceof ItdApiError) {\n if (this.#authEpoch !== epoch) {\n // Владельца сменили, пока шёл refresh: ошибка относится к уже забытой сессии —\n // не трогаем актуальное состояние.\n return this.#session?.accessToken ?? null;\n }\n\n // Сессия недействительна — чистим её, иначе будем биться в стену на каждом запросе.\n this.#transitionAuth(undefined);\n this.#session = null;\n this.#jar.clear();\n await this.#config.storage.clear();\n\n const relogged = await this.#reloginOrNull();\n if (relogged !== null) return relogged;\n\n // Войти заново нечем — отдаём ошибку сервера как есть. Именно она объясняет,\n // что произошло: REFRESH_TOKEN_MISSING, SESSION_NOT_FOUND, SESSION_REVOKED.\n throw error;\n }\n throw error;\n }\n }\n\n /** Повторный вход, если разрешён настройкой и есть логин с паролем. */\n async #reloginOrNull(): Promise<string | null> {\n const auth = this.#config.auth;\n\n if (\n !this.#config.reloginOnRefreshFailure ||\n !auth ||\n typeof auth !== 'object' ||\n !('email' in auth)\n ) {\n return null;\n }\n\n try {\n return await this.#signInWithCredentials(auth);\n } catch {\n return null;\n }\n }\n\n /**\n * Вход по логину и паролю.\n *\n * Параллельные вызовы объединяются: одновременный старт нескольких запросов не должен\n * приводить к нескольким попыткам входа и блокировке аккаунта.\n */\n #signInWithCredentials(credentials: CredentialsAuth): Promise<string> {\n if (this.#signingIn) return this.#signingIn;\n\n const promise = this.#performSignIn(credentials).finally(() => {\n this.#signingIn = null;\n });\n\n this.#signingIn = promise;\n return promise;\n }\n\n /**\n * Берёт токен капчи для входа.\n *\n * `getTurnstileToken` приоритетнее готовой строки: токен Turnstile одноразовый и живёт\n * несколько минут, поэтому при повторном входе через сутки годится только свежий.\n */\n async #resolveTurnstileToken(credentials: CredentialsAuth): Promise<string> {\n if (credentials.getTurnstileToken) {\n const token = await credentials.getTurnstileToken();\n if (token) return token;\n }\n\n if (credentials.turnstileToken) return credentials.turnstileToken;\n\n throw new ItdConfigError(\n 'Вход по email и паролю требует токен капчи Cloudflare Turnstile: без него сервер ' +\n 'отвечает 422. Передайте auth.getTurnstileToken (источник свежего токена) либо ' +\n 'разовый auth.turnstileToken. Ключ виджета — TURNSTILE_SITE_KEY. В Node токен умеет ' +\n 'добывать отдельный пакет: npm i @itd-api/turnstile, затем ' +\n 'getTurnstileToken: createTurnstileSolver().',\n );\n }\n\n async #performSignIn(credentials: CredentialsAuth): Promise<string> {\n const turnstileToken = await this.#resolveTurnstileToken(credentials);\n\n // Через общий pipeline: плагины, повторы и очередь сохраняются, а слой авторизации\n // пропускается, потому что токена ещё нет.\n const result = await this.#http.execute(AUTH_SIGN_IN, {\n path: AUTH_PATHS.signIn,\n body: { email: credentials.email, password: credentials.password, turnstileToken },\n skipAuth: true,\n skipAuthRefresh: true,\n });\n\n if (result.status !== SignInStatus.Authenticated) {\n // Сервер запросил подтверждение по коду — автоматически это не пройти.\n throw new ItdConfigError(\n 'Вход по email и паролю требует подтверждения кодом из письма. Автоматический вход ' +\n 'невозможен: воспользуйтесь itd.auth.signInWithOtp() и передайте полученный ' +\n 'accessToken в конфигурацию клиента.',\n );\n }\n const accessToken = result.accessToken;\n\n // Прежний refresh-токен и cookie намеренно не переносятся: вход выдал новую сессию,\n // и держаться за старую было бы ошибкой. Идентификатор устройства добавит #saveSession.\n this.#invalidateInFlight();\n this.#transitionAuth(accessToken);\n await this.#saveSession({ accessToken, obtainedAt: this.#config.clock.now() });\n this.#emitter.emit('tokens', { accessToken });\n this.#emitter.emit('signIn', { accessToken });\n\n return accessToken;\n }\n}\n","import { ItdConfigError } from '../core/errors.js';\nimport type { HttpClient } from '../core/execution/http.js';\nimport type { RequestOptions } from '../core/options.js';\nimport type { Session } from '../models/account.js';\nimport type { AuthState } from '../models/users.js';\nimport {\n AUTH_CHANGE_PASSWORD,\n AUTH_CHECK,\n AUTH_FORGOT_PASSWORD,\n AUTH_LOGOUT,\n AUTH_RESEND_OTP,\n AUTH_RESET_PASSWORD,\n AUTH_REVOKE_OTHER_SESSIONS,\n AUTH_REVOKE_SESSION,\n AUTH_SESSIONS,\n AUTH_SIGN_IN,\n AUTH_SIGN_UP,\n AUTH_VERIFY_OTP,\n type SignInResult,\n SignInStatus,\n} from '../operations/auth.js';\nimport { AUTH_PATHS, type AuthManager, type TURNSTILE_SITE_KEY } from '../session/auth.js';\nimport { BaseResource } from './base.js';\n\n/** Учётные данные для входа. */\nexport interface Credentials {\n email: string;\n password: string;\n}\n\n/**\n * Учётные данные вместе с токеном капчи.\n *\n * `turnstileToken` обязателен: без него сервер отвечает `VALIDATION_ERROR`, с недействительным —\n * `TURNSTILE_VERIFICATION_FAILED`. Токен даёт виджет Cloudflare Turnstile с ключом\n * {@link TURNSTILE_SITE_KEY}; он одноразовый и живёт несколько минут.\n */\nexport interface CaptchaCredentials extends Credentials {\n turnstileToken: string;\n}\n\n/** Запрос письма для сброса пароля. Капча обязательна, как и при входе. */\nexport interface ForgotPasswordInput {\n email: string;\n turnstileToken: string;\n}\n\n/**\n * Установка нового пароля.\n *\n * Сброс идёт тем же потоком с кодом, что и вход: {@link AuthResource.forgotPassword} выдаёт\n * `flowToken`, письмо приносит `otp`, и всё это вместе с новым паролем уходит сюда.\n */\nexport interface ResetPasswordInput {\n email: string;\n otp: string;\n flowToken: string;\n newPassword: string;\n}\n\nexport type { SignInResult, SignInStatus as SignInStatusValue } from '../operations/auth.js';\n/** Чем закончился вход: сразу токеном или запросом кода подтверждения. */\nexport { SignInStatus } from '../operations/auth.js';\n\n/**\n * Результат входа.\n *\n * Сервер может как сразу выдать токен, так и потребовать код подтверждения — размеченное\n * объединение делает оба случая явными.\n */\n\n/**\n * Авторизация, сессии и пароли.\n *\n * Доступна как `itd.auth`.\n */\nexport class AuthResource extends BaseResource {\n readonly #auth: AuthManager;\n\n constructor(http: HttpClient, deps: { auth: AuthManager }) {\n super(http);\n this.#auth = deps.auth;\n }\n\n /**\n * Проверяет состояние авторизации и возвращает текущего пользователя.\n *\n * Без авторизации возвращает `authenticated: false` и `user: null`.\n */\n check(options: RequestOptions = {}): Promise<AuthState> {\n return this.http.execute(AUTH_CHECK, {\n path: '/api/profile',\n ...options,\n });\n }\n\n /**\n * Регистрирует аккаунт и запускает подтверждение по коду.\n *\n * @returns `flowToken`, который нужно передать в {@link verifyOtp}\n */\n signUp(credentials: CaptchaCredentials, options: RequestOptions = {}): Promise<string> {\n return this.http.execute(AUTH_SIGN_UP, {\n path: AUTH_PATHS.signUp,\n body: credentials,\n skipAuth: true,\n skipAuthRefresh: true,\n ...options,\n });\n }\n\n /**\n * Выполняет вход.\n *\n * Если сервер потребовал код подтверждения, вернётся `status: 'otp_required'` —\n * тогда продолжайте через {@link verifyOtp} либо воспользуйтесь {@link signInWithOtp}.\n *\n * При успешном входе токен сохраняется в клиенте автоматически.\n *\n * @param credentials email, пароль и обязательный токен капчи — см. {@link CaptchaCredentials}\n */\n async signIn(\n credentials: CaptchaCredentials,\n options: RequestOptions = {},\n ): Promise<SignInResult> {\n const result = await this.http.execute(AUTH_SIGN_IN, {\n path: AUTH_PATHS.signIn,\n body: credentials,\n skipAuth: true,\n skipAuthRefresh: true,\n ...options,\n });\n\n if (result.status === SignInStatus.Authenticated) {\n await this.#auth.setAccessToken(result.accessToken);\n }\n return result;\n }\n\n /**\n * Подтверждает вход кодом из письма.\n *\n * Полученный токен сохраняется в клиенте автоматически.\n */\n async verifyOtp(\n input: Credentials & { otp: string; flowToken: string },\n options: RequestOptions = {},\n ): Promise<string> {\n const accessToken = await this.http.execute(AUTH_VERIFY_OTP, {\n path: AUTH_PATHS.verifyOtp,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...options,\n });\n\n await this.#auth.setAccessToken(accessToken);\n return accessToken;\n }\n\n /** Отправляет код подтверждения повторно. */\n resendOtp(\n input: { email: string; flowToken: string },\n options: RequestOptions = {},\n ): Promise<void> {\n return this.voidOperation(AUTH_RESEND_OTP, {\n path: AUTH_PATHS.resendOtp,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...options,\n });\n }\n\n /**\n * Полный вход с подтверждением по коду.\n *\n * Удобно для скриптов и ботов: код запрашивается функцией `getOtp`, а всё остальное\n * библиотека делает сама.\n *\n * @example\n * ```ts\n * import { createInterface } from 'node:readline/promises';\n *\n * const rl = createInterface({ input: process.stdin, output: process.stdout });\n *\n * const token = await itd.auth.signInWithOtp({\n * email, password,\n * getOtp: () => rl.question('Код из письма: '),\n * });\n * ```\n */\n async signInWithOtp(\n input: CaptchaCredentials & { getOtp: () => string | Promise<string> },\n options: RequestOptions = {},\n ): Promise<string> {\n const { getOtp, ...credentials } = input;\n const result = await this.signIn(credentials, options);\n\n if (result.status === SignInStatus.Authenticated) return result.accessToken;\n\n if (!result.flowToken) {\n throw new ItdConfigError(\n 'Сервер запросил код подтверждения, но не вернул flowToken — продолжить вход невозможно',\n );\n }\n\n const otp = await getOtp();\n\n // Токен капчи сюда не передаётся: он одноразовый и уже потрачен на sign-in,\n // а verify-otp капчу не требует.\n return this.verifyOtp(\n {\n email: credentials.email,\n password: credentials.password,\n otp,\n flowToken: result.flowToken,\n },\n options,\n );\n }\n\n /**\n * Обновляет токен доступа.\n *\n * Параллельные вызовы объединяются в один сетевой запрос. При включённом `autoRefresh`\n * вызывать вручную обычно не нужно.\n */\n refresh(): Promise<string> {\n return this.#auth.refresh();\n }\n\n /**\n * Есть ли признак живой сессии обновления.\n *\n * Проверяет cookie `is_auth`, которую сервер ставит рядом с refresh-токеном, а также\n * refresh-токен, переданный строкой. Позволяет не дёргать API у неавторизованного\n * пользователя. В браузере всегда `true`: cookie ведёт сама среда, и прочитать её\n * из JS нельзя.\n *\n * Читает {@link TokenStorage}, поэтому результат верен и до первого запроса.\n *\n * @example\n * ```ts\n * if (await itd.auth.hasRefreshSession()) await itd.auth.refresh();\n * else redirectToLogin();\n * ```\n */\n hasRefreshSession(): Promise<boolean> {\n return this.#auth.hasRefreshSession();\n }\n\n /** Завершает текущую сессию на сервере и очищает локальную. */\n async logout(options: RequestOptions = {}): Promise<void> {\n await this.voidOperation(AUTH_LOGOUT, {\n path: AUTH_PATHS.logout,\n skipAuthRefresh: true,\n ...options,\n });\n\n await this.#auth.clear();\n }\n\n /**\n * Завершает все сессии пользователя и очищает локальную.\n *\n * Собран из двух запросов, потому что единого эндпоинта на сервере нет:\n * `POST /api/v1/auth/logout-all` отвечает `404`. Сначала отзываются все прочие сессии\n * (`DELETE /api/v1/auth/sessions`), затем завершается текущая — в обратном порядке\n * отзывать было бы уже нечем.\n */\n async logoutAll(options: RequestOptions = {}): Promise<void> {\n await this.revokeOtherSessions(options);\n await this.logout(options);\n }\n\n /** Забывает сессию локально, не обращаясь к серверу. */\n signOut(): Promise<void> {\n return this.#auth.clear();\n }\n\n /**\n * Запрашивает письмо с кодом для сброса пароля.\n *\n * @returns `flowToken`, который нужно передать в {@link resetPassword}\n */\n forgotPassword(input: ForgotPasswordInput, options: RequestOptions = {}): Promise<string> {\n return this.http.execute(AUTH_FORGOT_PASSWORD, {\n path: AUTH_PATHS.forgotPassword,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...options,\n });\n }\n\n /**\n * Устанавливает новый пароль по коду из письма.\n *\n * Сервер ждёт все четыре поля сразу — `email`, `otp`, `flowToken` и `newPassword`;\n * при нехватке любого отвечает `422`.\n */\n resetPassword(input: ResetPasswordInput, options: RequestOptions = {}): Promise<void> {\n return this.voidOperation(AUTH_RESET_PASSWORD, {\n path: AUTH_PATHS.resetPassword,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...options,\n });\n }\n\n /**\n * Полный сброс пароля с кодом из письма.\n *\n * Тот же приём, что и {@link signInWithOtp}: код запрашивается функцией `getOtp`,\n * остальное библиотека делает сама.\n *\n * @example\n * ```ts\n * await itd.auth.resetPasswordWithOtp({\n * email,\n * turnstileToken,\n * newPassword,\n * getOtp: () => rl.question('Код из письма: '),\n * });\n * ```\n */\n async resetPasswordWithOtp(\n input: ForgotPasswordInput & {\n newPassword: string;\n getOtp: () => string | Promise<string>;\n },\n options: RequestOptions = {},\n ): Promise<void> {\n const flowToken = await this.forgotPassword(\n { email: input.email, turnstileToken: input.turnstileToken },\n options,\n );\n\n const otp = await input.getOtp();\n\n await this.resetPassword(\n { email: input.email, otp, flowToken, newPassword: input.newPassword },\n options,\n );\n }\n\n /**\n * Меняет пароль. Требует действующей сессии.\n *\n * При неверном текущем пароле сервер отвечает `ACCOUNT_CURRENT_PASSWORD_INCORRECT`.\n *\n * @example\n * ```ts\n * await itd.auth.changePassword({ currentPassword, newPassword });\n * ```\n */\n changePassword(\n input: { currentPassword: string; newPassword: string },\n options: RequestOptions = {},\n ): Promise<void> {\n return this.voidOperation(AUTH_CHANGE_PASSWORD, {\n path: AUTH_PATHS.changePassword,\n body: { currentPassword: input.currentPassword, newPassword: input.newPassword },\n ...options,\n });\n }\n\n /** Загружает список активных сессий. У текущей поле `isCurrent` равно `true`. */\n sessions(options: RequestOptions = {}): Promise<Session[]> {\n return this.http.execute(AUTH_SESSIONS, {\n path: AUTH_PATHS.sessions,\n ...options,\n });\n }\n\n /** Завершает указанную сессию. */\n revokeSession(sessionId: string, options: RequestOptions = {}): Promise<void> {\n return this.voidOperation(AUTH_REVOKE_SESSION, {\n path: `${AUTH_PATHS.sessions}/${encodeURIComponent(sessionId)}`,\n ...options,\n });\n }\n\n /** Завершает все сессии, кроме текущей. */\n revokeOtherSessions(options: RequestOptions = {}): Promise<void> {\n return this.voidOperation(AUTH_REVOKE_OTHER_SESSIONS, {\n path: AUTH_PATHS.sessions,\n ...options,\n });\n }\n}\n","import { installAsyncDisposeFallback } from './core/async-dispose.js';\nimport type { Listener, Unsubscribe } from './core/emitter.js';\nimport { ItdConfigError, ItdStateError } from './core/errors.js';\nimport { type ClientRuntime, createClientRuntime } from './core/execution/client-runtime.js';\nimport { ExtensibleOperationCatalog } from './core/feature-catalog.js';\nimport { type ClientFeatureHost, createFeatureHost } from './core/feature-host.js';\nimport type { ClientFeature } from './core/features.js';\nimport type { RawRequestOptions } from './core/options.js';\nimport type { ClientPlugin } from './core/plugins/contracts.js';\nimport type { PluginRegistry } from './core/plugins/registry.js';\nimport type { RateLimitBucketState, RequestQueuePool } from './core/scheduling/rate-limit.js';\nimport type { ServiceDefinition } from './core/services.js';\nimport { ITD_CATALOG } from './domain/catalog.js';\nimport {\n NotificationEvents,\n resolveNotificationEventsOptions,\n setNotificationEventsConnectGuard,\n} from './events/stream.js';\nimport type { UserId } from './models/common.js';\nimport { createNotificationsApi, type NotificationsApi } from './notifications-api.js';\nimport { EVENT_OPERATIONS } from './operations/events.js';\nimport type { ItdClientOptions } from './options.js';\nimport { AuthResource } from './resources/auth.js';\nimport type { CommentsResource } from './resources/comments.js';\nimport type { FilesResource } from './resources/files.js';\nimport type { HashtagsResource } from './resources/hashtags.js';\nimport type { PlatformResource } from './resources/platform.js';\nimport type { PostsResource } from './resources/posts.js';\nimport type { ReportsResource } from './resources/reports.js';\nimport type { SearchResource } from './resources/search.js';\nimport type { ShopResource } from './resources/shop.js';\nimport { createStatusFeature } from './resources/status.js';\nimport type { SubscriptionResource } from './resources/subscription.js';\nimport type { TelemetryResource } from './resources/telemetry.js';\nimport type { UsersResource } from './resources/users.js';\nimport type { VerificationResource } from './resources/verification.js';\nimport { createResources, type RestResources } from './rest/resources.js';\nimport { type AuthEvents, type AuthManager, createItdAuth } from './session/auth.js';\nimport type { ItdSession } from './session/storage.js';\n\nconst CLIENT_PLUGIN_REGISTRIES = new WeakMap<ItdClient, PluginRegistry>();\nconst DISPOSED_CLIENTS = new WeakSet<ItdClient>();\n\nfunction assertClientActive(client: ItdClient, action: string): void {\n if (!DISPOSED_CLIENTS.has(client)) return;\n throw new ItdStateError(\n `ItdClient уже окончательно освобождён через dispose(); нельзя ${action}. Создайте новый клиент`,\n );\n}\n\n/** Проверяет возможность подключить плагин к клиенту без вызова `install()`. @internal */\nexport function assertClientCanUsePlugin(client: ItdClient, plugin: ClientPlugin): void {\n assertClientActive(client, 'подключить плагин');\n CLIENT_PLUGIN_REGISTRIES.get(client)?.assertCanAdd(plugin);\n}\n\n/** Проверяет возможность отключить плагин клиента без изменения реестра. @internal */\nexport function assertClientCanUnusePlugin(client: ItdClient, name: string): void {\n CLIENT_PLUGIN_REGISTRIES.get(client)?.assertCanRemove(name);\n}\n\n/**\n * Скрытые параметры конструктора — не часть публичного API.\n *\n * @internal\n */\ninterface ItdClientInternals {\n /**\n * Готовая очередь запросов — так {@link ItdAccounts} с `rateLimitScope: 'shared'` даёт\n * нескольким клиентам одну на всех. Свою клиент в этом случае не заводит и, что важнее,\n * не гасит при `close()`: чужие ожидающие запросы это отменило бы.\n */\n queues?: RequestQueuePool | undefined;\n}\n\nconst CLIENT_INTERNALS = new WeakMap<ItdClientOptions, ItdClientInternals>();\n\n/** Создаёт клиент с зависимостями внешнего контейнера, не расширяя публичный конструктор. @internal */\nexport function createManagedClient(\n options: ItdClientOptions,\n internals: ItdClientInternals,\n): ItdClient {\n const managedOptions = { ...options };\n CLIENT_INTERNALS.set(managedOptions, internals);\n return new ItdClient(managedOptions);\n}\n\n/**\n * Клиент API итд.com.\n *\n * Методы сгруппированы по разделам: `itd.posts`, `itd.users`, `itd.comments`, `itd.auth`,\n * `itd.files`. Авторизация, обновление токена, повторы и очередь запросов работают сами.\n *\n * @example Готовый токен — для разового вызова\n * ```ts\n * const itd = new ItdClient({ auth: '<accessToken>' });\n * const me = await itd.users.me();\n * ```\n *\n * @example Полноценная сессия для бота\n * ```ts\n * import { ItdClient } from 'itd-api';\n * import { FileTokenStorage } from 'itd-api/node';\n *\n * const itd = new ItdClient({\n * // `auth` не обязателен: когда хранилище уже содержит сессию, токен берётся оттуда,\n * // а истёкший продлевается сам. Здесь он нужен на первый запуск.\n * // Вход по паролю требует токена капчи — см. AuthInput и TURNSTILE_SITE_KEY.\n * auth: { email, password, getTurnstileToken },\n * storage: new FileTokenStorage('./.itd-session.json'),\n * rateLimit: { concurrency: 4, rps: 8 },\n * });\n *\n * for await (const post of itd.posts.iterate({ tab: 'following' })) {\n * if (!post.isLiked) await itd.posts.like(post.id);\n * }\n * ```\n */\nexport class ItdClient {\n readonly #runtime: ClientRuntime<AuthManager>;\n readonly #features: ClientFeatureHost;\n /** Общий результат терминальной очистки для идемпотентных повторных вызовов. */\n #disposePromise: Promise<void> | undefined;\n\n /** Ресурсы конвейера запросов; создаются при первом обращении. */\n readonly #resources: RestResources<NotificationsApi>;\n /** Сессионный ресурс живёт у фасада: он управляет входом и продлением. */\n #auth: AuthResource | undefined;\n\n /** Авторизация, сессии и пароли. */\n get auth(): AuthResource {\n this.#auth ??= new AuthResource(this.#runtime.http, { auth: this.#runtime.auth });\n return this.#auth;\n }\n\n /** Профили, подписки, блокировки, приватность. */\n get users(): UsersResource {\n return this.#resources.users;\n }\n\n /** Лента, публикация, реакции, репосты, комментарии к постам. */\n get posts(): PostsResource {\n return this.#resources.posts;\n }\n\n /** Ответы на комментарии и действия над ними. */\n get comments(): CommentsResource {\n return this.#resources.comments;\n }\n\n /** Загрузка файлов и медиа. */\n get files(): FilesResource {\n return this.#resources.files;\n }\n\n /** Уведомления: список, счётчик, отметки о прочтении, настройки. */\n get notifications(): NotificationsApi {\n return this.#resources.notifications;\n }\n\n /** Хэштеги и посты по ним. */\n get hashtags(): HashtagsResource {\n return this.#resources.hashtags;\n }\n\n /** Глобальный поиск по пользователям и хэштегам. */\n get search(): SearchResource {\n return this.#resources.search;\n }\n\n /** Жалобы на контент и пользователей. */\n get reports(): ReportsResource {\n return this.#resources.reports;\n }\n\n /** Верификация профиля. */\n get verification(): VerificationResource {\n return this.#resources.verification;\n }\n\n /** Подписка и способы оплаты. */\n get subscription(): SubscriptionResource {\n return this.#resources.subscription;\n }\n\n /** Сведения о платформе: версии приложений, изменения, анонсы, баннер события. */\n get platform(): PlatformResource {\n return this.#resources.platform;\n }\n\n /** Телеметрия просмотров. */\n get telemetry(): TelemetryResource {\n return this.#resources.telemetry;\n }\n\n /** Каталог, доставка и заказы магазина ИТД. */\n get shop(): ShopResource {\n return this.#resources.shop;\n }\n\n constructor(options: ItdClientOptions = {}) {\n const notificationEventsOptions = resolveNotificationEventsOptions(\n options.events?.notifications,\n );\n const internals = CLIENT_INTERNALS.get(options) ?? {};\n const catalog = new ExtensibleOperationCatalog(ITD_CATALOG);\n let featureHost: ClientFeatureHost | undefined;\n this.#runtime = createClientRuntime(options, {\n catalog,\n queues: internals.queues,\n assertActive: (action) => assertClientActive(this, action),\n // Полноценную сессию подставляет фасад: ядро о ней не знает, и клиент с готовым\n // токеном не потянет за собой ни хранилище, ни вход по паролю.\n auth: (deps) =>\n createItdAuth(options, { ...deps, onAccountChange: () => featureHost?.stop() }),\n });\n this.#features = featureHost = createFeatureHost(this.#runtime, {\n catalog,\n assertActive: (action) => assertClientActive(this, action),\n });\n const status = this.#features.install(createStatusFeature());\n this.#resources = createResources({\n http: this.#runtime.http,\n files: this.#features.files,\n status,\n createNotifications: (http) =>\n createNotificationsApi(http, (notifications) => {\n let events!: NotificationEvents;\n let lifecycleGeneration = 0;\n let lifecycleActive = false;\n let unregister: (() => void) | undefined;\n\n const releaseAfterDrain = (generation: number): void => {\n void events.drain().then(() => {\n if (lifecycleActive || generation !== lifecycleGeneration) return;\n unregister?.();\n unregister = undefined;\n });\n };\n\n events = new NotificationEvents(\n {\n connection: this.#runtime.connection(),\n request: ({ operationId, path, query, signal }) =>\n this.#runtime.http.execute(EVENT_OPERATIONS[operationId], {\n path,\n query,\n signal,\n }),\n getAuthIdentity: () => this.#runtime.auth.getCurrentAuthIdentity(),\n getAuthScope: () => this.#runtime.auth.getAuthScope(),\n fetchUnreadCount: (signal) => notifications.count({ signal }),\n onConnect: () => {\n lifecycleGeneration += 1;\n unregister ??= this.#features.manage({\n kind: `notifications:${events.transport}`,\n stop: () => {\n if (lifecycleActive) events.disconnect();\n },\n drain: () => events.drain(),\n });\n lifecycleActive = true;\n },\n onClose: () => {\n lifecycleActive = false;\n releaseAfterDrain(++lifecycleGeneration);\n },\n logger: this.#runtime.config.logger,\n clock: this.#runtime.config.clock,\n },\n notificationEventsOptions,\n );\n setNotificationEventsConnectGuard(events, () =>\n assertClientActive(this, 'подключить канал событий уведомлений'),\n );\n return events;\n }),\n });\n CLIENT_PLUGIN_REGISTRIES.set(this, this.#runtime.plugins);\n }\n\n /** Базовый URL, к которому обращается клиент. */\n get baseUrl(): string {\n return this.#runtime.config.baseUrl;\n }\n\n /**\n * Выполняет произвольный запрос к API.\n *\n * Запасной путь для случаев, когда нужного метода ещё нет или ответ сервера разошёлся\n * с документацией. Проходит через ту же авторизацию, очередь и обработку ошибок.\n *\n * @example\n * ```ts\n * const raw = await itd.request({ method: 'GET', path: '/api/posts', raw: true });\n * ```\n *\n * @throws {ItdStateError} если клиент уже освобождён через {@link dispose}\n */\n request<T = unknown>(options: RawRequestOptions): Promise<T> {\n return this.#runtime.http.request<T>({\n ...options,\n operationId: options.operationId ?? 'raw',\n });\n }\n\n /**\n * Остаток серверных лимитов по бакетам, через которые уже проходили запросы.\n *\n * Значения берутся из последнего ответа каждого бакета и быстро устаревают: сервер\n * восстанавливает квоту линейно и границу окна не сообщает. Пустой массив при\n * `rateLimit: false`. {@link close} снимок сохраняет, {@link dispose} очищает.\n *\n * @example\n * ```ts\n * const posts = itd.rateLimitState().find((state) => state.bucket === 'posts.create');\n * if ((posts?.remaining ?? Number.POSITIVE_INFINITY) < 3) await sleep(60_000);\n * ```\n */\n rateLimitState(): RateLimitBucketState[] {\n return this.#runtime.rateLimitState();\n }\n\n /**\n * Устанавливает предметный модуль с общей сессией и обработкой запросов клиента.\n *\n * Сервисы, операции и бакеты регистрируются до синхронного `setup()`. Возвращает\n * типизированный API модуля.\n */\n install<TApi>(feature: ClientFeature<TApi>): TApi {\n return this.#features.install(feature);\n }\n\n /**\n * Устанавливает модуль и добавляет его API в свойство клиента только для чтения.\n *\n * Возвращаемое пересечение сохраняет тип уже подключённых свойств, поэтому вызовы можно\n * объединять в цепочку: `new ItdClient().withFeature('pixelBattle', pixelBattleFeature)`.\n */\n withFeature<const K extends string, TApi>(\n key: K,\n feature: ClientFeature<TApi>,\n ): this & { readonly [P in K]: TApi } {\n assertClientActive(this, 'установить feature');\n if (typeof key !== 'string' || key.trim() === '' || key !== key.trim()) {\n throw new ItdConfigError('Свойство feature должно иметь непустое имя без краевых пробелов');\n }\n if (key === 'then' || key in this) {\n throw new ItdConfigError(`Свойство клиента «${key}» уже занято или зарезервировано`);\n }\n if (!Object.isExtensible(this)) {\n throw new ItdConfigError('Нельзя опубликовать feature на нерасширяемом клиенте');\n }\n\n const api = this.install(feature);\n Object.defineProperty(this, key, {\n value: api,\n enumerable: true,\n configurable: false,\n writable: false,\n });\n return this as this & { readonly [P in K]: TApi };\n }\n\n /** Имена установленных модулей в порядке установки. */\n featureNames(): string[] {\n return this.#features.names();\n }\n\n /** Установлен ли модуль с таким именем. */\n hasFeature(name: string): boolean {\n return this.#features.has(name);\n }\n\n /**\n * Подключает плагин.\n *\n * Плагин может регистрировать обёртки операций и перехватчики сетевых попыток.\n *\n * @throws {ItdConfigError} если плагин задан неверно или уже подключён\n * @throws {ItdStateError} если клиент уже освобождён через {@link dispose}\n *\n * @example\n * ```ts\n * import { crypt } from '@itd-api/crypto';\n *\n * itd.use(crypt());\n * await itd.posts.create({\n * content: 'секрет',\n * }, {\n * extensions: { crypto: { encrypt: 'invisible' } },\n * });\n * ```\n */\n use(plugin: ClientPlugin): this {\n assertClientActive(this, 'подключить плагин');\n this.#runtime.plugins.add(plugin, {\n baseUrl: this.#runtime.config.baseUrl,\n logger: this.#runtime.config.logger,\n getAuthScope: () => this.#runtime.auth.getAuthScope(),\n getAuthIdentity: () => this.#runtime.auth.getAuthIdentity(),\n });\n return this;\n }\n\n /** Имена подключённых плагинов в фактическом порядке выполнения обёрток. */\n pluginNames(): string[] {\n return this.#runtime.plugins.names();\n }\n\n /** Подключён ли плагин с таким именем. */\n hasPlugin(name: string): boolean {\n return this.#runtime.plugins.has(name);\n }\n\n /**\n * Отключает плагин и освобождает заведённые им ресурсы.\n *\n * Новые запросы перестают видеть плагин сразу. Очистка дождётся логического запроса,\n * который уже проходил через его обёртку.\n *\n * @returns `false`, если такого плагина не было\n * @throws {ItdConfigError} если от плагина зависит другой подключённый плагин\n */\n unuse(name: string): Promise<boolean> {\n return this.#runtime.plugins.remove(name);\n }\n\n /**\n * Регистрирует сервис платформы — домен, отличный от основного.\n *\n * Запросы с `{ service: 'имя' }` уходят на его хост с его заголовками. То же самое умеет\n * опция `services` конструктора. Занятое имя не переопределяется — ни своё, ни встроенное:\n * разовому запросу хост задаётся полем `baseUrl`.\n *\n * Заголовок авторизации по умолчанию уходит только своим — домену клиента и его\n * поддоменам. Стороннему хосту токен нужно разрешить явно: `auth: true`.\n *\n * @throws {ItdConfigError} если определение неверно или имя уже занято\n * @throws {ItdStateError} если клиент уже освобождён через {@link dispose}\n *\n * @example Сервис платформы на поддомене — токен уходит сам\n * ```ts\n * itd.defineService({\n * name: 'pb',\n * baseUrl: 'https://pbapi.xn--d1ah4a.com',\n * headers: { Referer: 'https://pixel.xn--d1ah4a.com/' },\n * });\n *\n * await itd.request({ method: 'GET', service: 'pb', path: '/api/pixel-info' });\n * ```\n */\n defineService(definition: ServiceDefinition): this {\n assertClientActive(this, 'зарегистрировать сервис');\n this.#runtime.services.define(definition);\n return this;\n }\n\n /**\n * Базовый URL зарегистрированного сервиса.\n *\n * @throws {ItdConfigError} если сервис не зарегистрирован\n */\n serviceBaseUrl(name: string): string {\n return this.#runtime.services.resolveBaseUrl(name);\n }\n\n /**\n * Подписывается на события авторизации.\n *\n * Полезно, чтобы сохранять сессию во внешнее хранилище или узнавать, что вход\n * окончательно потерян.\n *\n * @returns функция отписки\n *\n * @example\n * ```ts\n * itd.on('tokens', ({ accessToken }) => cache.set('itd', accessToken));\n * itd.on('authError', () => notifyUser('Сессия истекла, войдите заново'));\n * ```\n */\n on<K extends keyof AuthEvents>(event: K, listener: Listener<AuthEvents[K]>): Unsubscribe {\n assertClientActive(this, 'подписаться на события');\n return this.#runtime.auth.on(event, listener);\n }\n\n /**\n * Освобождает ресурсы клиента: закрывает все потоки уведомлений, отправляет открытые\n * накопители {@link telemetry}, затем останавливает очередь запросов.\n *\n * Метод дожидается завершения активных обработчиков и освобождения ресурсов остановленных\n * событийных соединений, но не дольше `shutdownTimeout`. После вызова клиентом можно\n * пользоваться снова; ранее созданный поток можно запустить повторным `connect()`.\n *\n * Общая очередь, полученная от {@link ItdAccounts}, не останавливается: её гасит сам\n * контейнер, когда закрывает все аккаунты разом.\n *\n * Терминальное освобождение — это {@link dispose}.\n *\n * @throws {ItdStateError} если остановка событийного соединения не завершилась за отведённый срок\n */\n async close(): Promise<void> {\n return this.#close(false);\n }\n\n async #close(disposeCleanup: boolean): Promise<void> {\n // Terminal cleanup должен пометить batch до первого await: параллельный обычный close()\n // мог уже начать drain потоков, но ещё не дойти до отправки телеметрии.\n if (disposeCleanup) this.#resources.prepareTelemetryClose();\n const errors: unknown[] = [];\n try {\n try {\n await this.#features.close();\n } catch (error) {\n errors.push(error);\n }\n\n // Через геттер закрытие клиента поднимало бы накопитель телеметрии только ради\n // того, чтобы его тут же закрыть.\n try {\n await this.#resources.closeTelemetry(disposeCleanup);\n } catch (error) {\n errors.push(error);\n }\n } finally {\n this.#runtime.close();\n }\n if (errors.length === 1) throw errors[0];\n if (errors.length > 1) throw new AggregateError(errors, 'Не удалось закрыть клиент');\n }\n\n /**\n * Окончательно освобождает клиент: выполняет {@link close}, отменяет незавершённые\n * запросы и отключает все плагины.\n *\n * Терминальное состояние устанавливается сразу при первом вызове. После этого новые\n * запросы, подключение плагинов, регистрация сервисов и создание или повторный запуск\n * событийных каналов завершаются с {@link ItdStateError}. Повторные вызовы возвращают\n * тот же результат очистки.\n *\n * Ожидание остановки событийных соединений и операций плагинов ограничено\n * `shutdownTimeout`.\n *\n * @example\n * ```ts\n * await using itd = new ItdClient({ auth: token });\n * // …работа…\n * // dispose() вызовется сам на выходе из блока\n * ```\n */\n dispose(): Promise<void> {\n if (this.#disposePromise) return this.#disposePromise;\n DISPOSED_CLIENTS.add(this);\n this.#disposePromise = this.#dispose();\n return this.#disposePromise;\n }\n\n async #dispose(): Promise<void> {\n const errors: unknown[] = [];\n try {\n // Сначала завершаем потоки и телеметрию через ещё установленные плагины.\n await this.#close(true);\n } catch (error) {\n errors.push(error);\n }\n try {\n await this.#features.dispose();\n } catch (error) {\n errors.push(error);\n }\n try {\n await this.#runtime.dispose();\n } catch (error) {\n errors.push(error);\n }\n if (errors.length > 0) throw new AggregateError(errors, 'Не удалось освободить клиент');\n }\n\n /** Позволяет использовать клиент с `await using`. */\n [Symbol.asyncDispose](): Promise<void> {\n return this.dispose();\n }\n\n static {\n installAsyncDisposeFallback(ItdClient);\n }\n\n /** Текущая сессия целиком — чтобы сохранить её самостоятельно. */\n getSession(): Promise<ItdSession | null> {\n return this.#runtime.auth.getSession();\n }\n\n /**\n * Идентификатор аккаунта, под которым работает клиент.\n *\n * Читается из токена доступа и не стоит ни одного запроса. `undefined`, когда сессии\n * ещё нет или токен выдан не в формате JWT. Полезен прежде всего с {@link ItdAccounts}:\n * показывает, какому профилю соответствует восстановленная из хранилища запись.\n * Свежий профиль целиком отдаёт `itd.users.me()`.\n *\n * @remarks\n * JWT только декодируется: его подпись не проверяется. Результат подходит для локального\n * разделения состояния, но не доказывает личность пользователя и не заменяет проверку\n * авторизации сервером.\n *\n * @example\n * ```ts\n * for (const [name, itd] of accounts) console.log(name, await itd.getUserId());\n * ```\n */\n getUserId(): Promise<UserId | undefined> {\n return this.#runtime.auth.getUserId();\n }\n\n /**\n * Восстанавливает сохранённую сессию, включая cookie.\n *\n * @throws {ItdStateError} если клиент уже освобождён через {@link dispose}\n */\n setSession(session: ItdSession): Promise<void> {\n try {\n assertClientActive(this, 'изменить сессию');\n } catch (error) {\n return Promise.reject(error);\n }\n return this.#runtime.auth.setSession(session);\n }\n}\n\n/**\n * Создаёт клиент API итд.com.\n *\n * То же, что `new ItdClient(options)`, — для тех, кому привычнее фабрика.\n *\n * @example\n * ```ts\n * const itd = createClient({ auth: token });\n * ```\n */\nexport function createClient(options: ItdClientOptions = {}): ItdClient {\n return new ItdClient(options);\n}\n","import {\n assertClientCanUnusePlugin,\n assertClientCanUsePlugin,\n createManagedClient,\n type ItdClient,\n} from './client.js';\nimport { installAsyncDisposeFallback } from './core/async-dispose.js';\nimport { systemClock } from './core/clock.js';\nimport { resolveRateLimit } from './core/config.js';\nimport { Emitter, type Listener, reportListenerError, type Unsubscribe } from './core/emitter.js';\nimport { ItdConfigError, ItdStateError } from './core/errors.js';\nimport type { ClientFeature } from './core/features.js';\nimport type { Logger } from './core/options.js';\nimport type { ClientPlugin } from './core/plugins/contracts.js';\nimport { assertPluginRemovable, orderPluginDefinitions } from './core/plugins/order.js';\nimport { RequestQueuePool } from './core/scheduling/rate-limit.js';\nimport { ITD_CATALOG } from './domain/catalog.js';\nimport type { ItdClientOptions } from './options.js';\nimport {\n type ControlledTokenStorage,\n controlledTokenStorage,\n isRestorableSession,\n MemoryMultiTokenStorage,\n type MultiTokenStorage,\n} from './session/multi-storage.js';\nimport type { TokenStorage } from './session/storage.js';\n\n/** Как аккаунты делят между собой очередь запросов. */\nexport type RateLimitScope = 'account' | 'shared';\n\n/**\n * Фабрика подключаемого модуля для клиентов {@link ItdAccounts}.\n *\n * `create()` вызывается отдельно для каждого аккаунта, поэтому описание модуля и его API\n * между клиентами не разделяется. `key` публикует API как readonly-свойство клиента.\n */\nexport interface AccountFeature<TApi = unknown> {\n readonly key?: string | undefined;\n create(): ClientFeature<TApi>;\n}\n\n/**\n * Опции конструктора {@link ItdAccounts}.\n *\n * Всё, что понимает `ItdClient`, кроме `auth` и `deviceId`: они у каждого аккаунта свои\n * и задаются в {@link ItdAccounts.addAccount}. Обычный `TokenStorage` клиента здесь заменён\n * общей опцией {@link ItdAccountsOptions.storage} типа {@link MultiTokenStorage}; контейнер\n * сам выдаёт каждому клиенту изолированный срез по имени. Общий `deviceId` особенно вреден —\n * сервер различает по нему записи в списке сессий, и один на всех сложил бы все аккаунты\n * в одну.\n */\nexport interface ItdAccountsOptions\n extends Omit<ItdClientOptions, 'auth' | 'storage' | 'deviceId'> {\n /** Общее хранилище сессий всех аккаунтов. По умолчанию {@link MemoryMultiTokenStorage}. */\n storage?: MultiTokenStorage | undefined;\n /** Плагины, подключаемые каждому аккаунту, в том числе добавленному позже. */\n plugins?: readonly ClientPlugin[] | undefined;\n /** Модули, создаваемые и устанавливаемые отдельно для каждого аккаунта. */\n features?: readonly AccountFeature[] | undefined;\n /**\n * Как делить очередь запросов. По умолчанию `'shared'` — одна на всех.\n *\n * Лимиты итд.com считаются по IP, поэтому аккаунты с одного адреса тратят общую квоту.\n * `'account'` даёт каждому свою очередь и нужен, когда у аккаунтов разные адреса —\n * например, при своём прокси у каждого.\n *\n * В режиме `'shared'` настройки очереди берутся из общей опции `rateLimit`; аккаунту\n * разрешён только `rateLimit: false`, выводящий его из общей очереди.\n */\n rateLimitScope?: RateLimitScope | undefined;\n}\n\n/**\n * Настройки одного аккаунта. Общее мультихранилище задаёт контейнер, а аккаунт получает\n * свой срез автоматически; остальное — как у `ItdClient`.\n *\n * При `rateLimitScope: 'shared'` (умолчание) объект `rateLimit` задаётся только\n * контейнеру; аккаунту разрешено передать `false`, чтобы не ставить его запросы\n * в общую очередь.\n */\nexport type AddAccountOptions = Omit<ItdClientOptions, 'storage'>;\n\n/** Что можно уточнить при удалении аккаунта. */\nexport interface RemoveAccountOptions {\n /**\n * Удалить и сохранённую сессию. По умолчанию `false` — аккаунт убирается только\n * из памяти, а его токены остаются в хранилище и переживут перезапуск.\n */\n forget?: boolean | undefined;\n}\n\n/**\n * События авторизации всех аккаунтов сразу.\n *\n * Те же, что у одиночного клиента, плюс имя аккаунта: подписка на контейнер избавляет\n * от нужды вешать обработчик на каждого.\n */\nexport interface AccountEvents {\n /** Токен получен или обновлён. */\n tokens: { account: string; accessToken: string };\n /** Выполнен вход. */\n signIn: { account: string; accessToken: string };\n /** Сессия очищена — вручную или из-за неудачного обновления. */\n signOut: { account: string };\n /** Обновить сессию не удалось; дальнейшие запросы этого аккаунта будут падать с 401. */\n authError: { account: string; error: unknown };\n}\n\n/** Проверяет имя до создания клиента или частичного восстановления контейнера. */\nfunction validateAccountName(name: string): void {\n if (typeof name !== 'string' || name.trim() === '') {\n throw new ItdConfigError('имя аккаунта должно быть непустой строкой');\n }\n}\n\n/** Проверяет фабрики до создания первого аккаунта. */\nfunction resolveAccountFeatures(features: readonly AccountFeature[] | undefined): AccountFeature[] {\n if (features === undefined) return [];\n if (!Array.isArray(features)) {\n throw new ItdConfigError('features должен быть массивом фабрик подключаемых модулей');\n }\n\n const keys = new Set<string>();\n return features.map((feature, index) => {\n if (typeof feature !== 'object' || feature === null || typeof feature.create !== 'function') {\n throw new ItdConfigError(`features[${index}] должен предоставлять create()`);\n }\n if (feature.key !== undefined) {\n if (\n typeof feature.key !== 'string' ||\n feature.key.trim() === '' ||\n feature.key !== feature.key.trim()\n ) {\n throw new ItdConfigError(\n `features[${index}].key должен быть непустой строкой без пробелов`,\n );\n }\n if (keys.has(feature.key)) {\n throw new ItdConfigError(\n `Свойство подключаемого модуля «${feature.key}» объявлено повторно`,\n );\n }\n keys.add(feature.key);\n }\n const create = feature.create.bind(feature);\n return Object.freeze({\n ...(feature.key === undefined ? {} : { key: feature.key }),\n create,\n });\n });\n}\n\nconst DISPOSED_ACCOUNTS = new WeakSet<ItdAccounts>();\n\nfunction assertAccountsActive(accounts: ItdAccounts, action: string): void {\n if (!DISPOSED_ACCOUNTS.has(accounts)) return;\n throw new ItdStateError(\n `ItdAccounts уже окончательно освобождён через dispose(); нельзя ${action}. Создайте новый контейнер`,\n );\n}\n\n/**\n * Несколько аккаунтов итд.com в одном месте.\n *\n * Контейнер именованных `ItdClient`: каждый аккаунт получает собственный токен, cookie\n * и `deviceId`, а сессии всех складываются в одно хранилище — обычно в один файл, а не\n * в десяток. Имя аккаунта выбираете вы; сервер о нём ничего не знает.\n *\n * @example Бот на нескольких аккаунтах\n * ```ts\n * import { ItdAccounts } from 'itd-api';\n * import { FileMultiTokenStorage } from 'itd-api/node';\n *\n * await using accounts = new ItdAccounts({\n * storage: new FileMultiTokenStorage('./.itd-sessions.json'),\n * rateLimit: { concurrency: 4 },\n * });\n *\n * // Восстанавливаем тех, кто уже входил раньше: токен возьмётся из хранилища.\n * await accounts.restore();\n *\n * if (!accounts.has('kiow')) {\n * accounts.addAccount('kiow', { auth: { email, password, getTurnstileToken } });\n * }\n *\n * await accounts.account('kiow').posts.create({ content: 'привет' });\n *\n * for (const [name, itd] of accounts) {\n * console.log(name, await itd.getUserId());\n * }\n * ```\n */\nexport class ItdAccounts {\n /** Опции, общие для всех аккаунтов, — без полей, которые задаются поаккаунтно. */\n readonly #base: Omit<ItdClientOptions, 'auth' | 'storage' | 'deviceId'>;\n readonly #storage: MultiTokenStorage;\n readonly #clients = new Map<string, ItdClient>();\n /** Имена, чьё удаление ещё не завершилось: повторно занять их пока нельзя. */\n readonly #removing = new Set<string>();\n /** Уже начатые удаления, которых должен дождаться терминальный dispose(). */\n readonly #accountRemovals = new Set<Promise<void>>();\n /** Управляемые срезы хранилища: после удаления аккаунт больше не может писать через свой. */\n readonly #storageControls = new Map<string, ControlledTokenStorage>();\n /** Подписки на события клиентов — снимаются вместе с аккаунтом. */\n readonly #eventUnsubscribers = new Map<string, Unsubscribe[]>();\n /** Плагины для всех: и для уже заведённых аккаунтов, и для будущих. */\n readonly #plugins: ClientPlugin[];\n /** Фабрики модулей для новых и восстанавливаемых аккаунтов. */\n readonly #features: AccountFeature[];\n /** Имена плагинов, чья асинхронная очистка ещё не завершилась. */\n readonly #removingPlugins = new Set<string>();\n /** Общая очередь. `undefined`, когда у каждого аккаунта своя. */\n readonly #queues: RequestQueuePool | undefined;\n readonly #rateLimitScope: RateLimitScope;\n readonly #emitter: Emitter<AccountEvents>;\n readonly #logger: Logger | undefined;\n /** Общий результат терминальной очистки для идемпотентных повторных вызовов. */\n #disposePromise: Promise<void> | undefined;\n\n constructor(options: ItdAccountsOptions = {}) {\n const { storage, plugins, features, rateLimitScope, ...base } = options;\n\n if (\n rateLimitScope !== undefined &&\n rateLimitScope !== 'account' &&\n rateLimitScope !== 'shared'\n ) {\n throw new ItdConfigError(\"rateLimitScope должен быть 'account' или 'shared'\");\n }\n\n this.#base = base;\n this.#storage = storage ?? new MemoryMultiTokenStorage();\n this.#plugins = orderPluginDefinitions(plugins ?? []);\n this.#features = resolveAccountFeatures(features);\n this.#rateLimitScope = rateLimitScope ?? 'shared';\n\n // Общая очередь заводится сразу: проверить опции лучше при создании контейнера,\n // а не при добавлении первого аккаунта.\n const rateLimit =\n this.#rateLimitScope === 'shared' ? resolveRateLimit(base.rateLimit, ITD_CATALOG) : undefined;\n this.#queues = rateLimit\n ? new RequestQueuePool(rateLimit, base.clock ?? systemClock)\n : undefined;\n\n const logger = typeof base.logger === 'object' ? base.logger : undefined;\n this.#logger = logger;\n this.#emitter = new Emitter<AccountEvents>((error) =>\n reportListenerError(logger, 'аккаунтов', error),\n );\n }\n\n /** Общее хранилище сессий — то же, что передано опцией `storage`. */\n get storage(): MultiTokenStorage {\n return this.#storage;\n }\n\n /** Сколько аккаунтов заведено. */\n get size(): number {\n return this.#clients.size;\n }\n\n /** Имена заведённых аккаунтов в порядке добавления. */\n names(): string[] {\n return [...this.#clients.keys()];\n }\n\n /** Заведён ли аккаунт с таким именем. */\n has(name: string): boolean {\n return this.#clients.has(name);\n }\n\n /**\n * Заводит аккаунт.\n *\n * Возвращается обычный `ItdClient` — со всеми ресурсами, плагинами и каналом событий.\n * Хранилище ему подставляется само: срез общего по имени аккаунта.\n *\n * Опция `auth` не обязательна: когда сессия этого аккаунта уже лежит в хранилище,\n * токен возьмётся оттуда, а истёкший продлится сам.\n *\n * @throws {ItdConfigError} если имя пустое или уже занято\n *\n * @example\n * ```ts\n * accounts.addAccount('bot', { auth: { email, password, getTurnstileToken } });\n * accounts.addAccount('reader', { auth: '<accessToken>' });\n * accounts.addAccount('через-прокси', { fetch: proxyFetch('socks5://…') });\n * ```\n */\n addAccount(name: string, options: AddAccountOptions = {}): ItdClient {\n assertAccountsActive(this, 'добавить аккаунт');\n validateAccountName(name);\n if (this.#clients.has(name)) {\n throw new ItdConfigError(\n `аккаунт «${name}» уже добавлен. Возьмите его через accounts.account('${name}')`,\n );\n }\n if (this.#removing.has(name)) {\n throw new ItdConfigError(\n `аккаунт «${name}» ещё удаляется. Дождитесь завершения removeAccount() перед повторным добавлением`,\n );\n }\n if (\n this.#rateLimitScope === 'shared' &&\n options.rateLimit !== undefined &&\n options.rateLimit !== false\n ) {\n throw new ItdConfigError(\n \"при rateLimitScope: 'shared' настройки rateLimit задаются контейнеру; \" +\n 'аккаунту можно передать только rateLimit: false',\n );\n }\n\n const storageControl = controlledTokenStorage(this.#storage, name);\n let client: ItdClient | undefined;\n try {\n client = createManagedClient(\n this.#mergeOptions(options, storageControl.storage),\n this.#queues ? { queues: this.#queues } : {},\n );\n\n for (const plugin of this.#plugins) client.use(plugin);\n for (const feature of this.#features) {\n const manifest = feature.create();\n if (feature.key === undefined) client.install(manifest);\n else client.withFeature(feature.key, manifest);\n }\n } catch (error) {\n storageControl.revoke();\n if (client) {\n void client.dispose().catch((cleanupError: unknown) => {\n this.#reportCleanup('неудачного добавления аккаунта', cleanupError);\n });\n }\n throw error;\n }\n\n const unsubscribers = this.#forwardEvents(name, client);\n this.#storageControls.set(name, storageControl);\n this.#eventUnsubscribers.set(name, unsubscribers);\n this.#clients.set(name, client);\n\n return client;\n }\n\n /**\n * Клиент аккаунта.\n *\n * @throws {ItdConfigError} если такого аккаунта нет\n *\n * @example\n * ```ts\n * await accounts.account('kiow').posts.like(postId);\n * ```\n */\n account(name: string): ItdClient {\n assertAccountsActive(this, 'получить клиент аккаунта');\n const client = this.#clients.get(name);\n if (!client) {\n const known = this.names();\n throw new ItdConfigError(\n `аккаунт «${name}» не заведён. ` +\n (known.length > 0\n ? `Известны: ${known.join(', ')}`\n : 'Ни одного аккаунта нет — добавьте его через addAccount() или restore()'),\n );\n }\n\n return client;\n }\n\n /**\n * Поднимает аккаунты, сессии которых уже лежат в хранилище.\n *\n * То, ради чего мультихранилище знает свой состав: после перезапуска процесса\n * ни `auth`, ни капча не нужны — токен, `deviceId` и cookie берутся из сохранённого.\n * Уже заведённые аккаунты не трогаются. Записи, в которых после выхода остался только\n * `deviceId`, пропускаются: авторизованной сессии в них уже нет.\n *\n * @returns имена добавленных аккаунтов\n *\n * @example\n * ```ts\n * const restored = await accounts.restore();\n * console.log(`подняли ${restored.length} аккаунтов без единого входа`);\n * ```\n */\n async restore(): Promise<string[]> {\n assertAccountsActive(this, 'восстановить аккаунты');\n const saved = await this.#storage.accounts();\n for (const name of saved) validateAccountName(name);\n\n const candidates = [...new Set(saved)].filter((name) => !this.#clients.has(name));\n const sessions = await Promise.all(\n candidates.map(async (name) => ({ name, session: await this.#storage.get(name) })),\n );\n const added: string[] = [];\n\n for (const { name, session } of sessions) {\n if (!isRestorableSession(session)) continue;\n // Пока читали хранилище, аккаунт могли добавить вручную или начать удалять.\n if (this.#clients.has(name) || this.#removing.has(name)) continue;\n this.addAccount(name);\n added.push(name);\n }\n\n return added;\n }\n\n /**\n * Убирает аккаунт: закрывает его клиента и, если попросить, забывает сессию.\n *\n * Сетевого запроса не выполняет. Чтобы завершить сессию на сервере, вызовите\n * `itd.auth.logout()` до удаления.\n *\n * @returns `false`, если такого аккаунта и не было\n */\n async removeAccount(name: string, options: RemoveAccountOptions = {}): Promise<boolean> {\n assertAccountsActive(this, 'удалить аккаунт');\n const client = this.#clients.get(name);\n if (!client) return false;\n\n this.#removing.add(name);\n this.#clients.delete(name);\n const storageControl = this.#storageControls.get(name);\n storageControl?.revoke();\n this.#storageControls.delete(name);\n\n for (const unsubscribe of this.#eventUnsubscribers.get(name) ?? []) unsubscribe();\n this.#eventUnsubscribers.delete(name);\n\n const removal = (async () => {\n const errors: unknown[] = [];\n const closing = await Promise.allSettled([\n client.dispose(),\n storageControl?.drain() ?? Promise.resolve(),\n ]);\n for (const result of closing) {\n if (result.status === 'rejected') errors.push(result.reason);\n }\n\n if (options.forget) {\n await Promise.resolve(this.#storage.clear(name)).catch((error: unknown) => {\n errors.push(error);\n });\n }\n\n if (errors.length > 0) throw errors[0];\n })();\n this.#accountRemovals.add(removal);\n\n try {\n await removal;\n return true;\n } finally {\n this.#accountRemovals.delete(removal);\n this.#removing.delete(name);\n }\n }\n\n /**\n * Подключает плагин всем аккаунтам — и заведённым, и будущим.\n *\n * @throws {ItdConfigError} если плагин задан неверно или уже подключён\n *\n * @example\n * ```ts\n * accounts.use(crypt());\n * ```\n */\n use(plugin: ClientPlugin): this {\n assertAccountsActive(this, 'подключить плагин');\n if (this.#removingPlugins.has(plugin?.name)) {\n throw new ItdConfigError(\n `плагин «${plugin.name}» ещё отключается; дождитесь завершения accounts.unuse()`,\n );\n }\n const ordered = orderPluginDefinitions([...this.#plugins, plugin]);\n const clients = [...this.#clients.values()];\n for (const client of clients) assertClientCanUsePlugin(client, plugin);\n\n const installed: ItdClient[] = [];\n try {\n for (const client of clients) {\n client.use(plugin);\n installed.push(client);\n }\n } catch (error) {\n // remove() исключает запись из реестра до первого await, поэтому видимое состояние\n // откатывается синхронно, а асинхронные teardown завершаются в фоне.\n for (const client of installed.reverse()) {\n void client.unuse(plugin.name).catch((cleanupError: unknown) => {\n this.#reportCleanup(`отката плагина «${plugin.name}»`, cleanupError);\n });\n }\n throw error;\n }\n this.#plugins.splice(0, this.#plugins.length, ...ordered);\n\n return this;\n }\n\n /** Имена общих плагинов в фактическом порядке выполнения обёрток. */\n pluginNames(): string[] {\n return this.#plugins.map((plugin) => plugin.name);\n }\n\n /** Подключён ли общий плагин с таким именем. */\n hasPlugin(name: string): boolean {\n return this.#plugins.some((plugin) => plugin.name === name);\n }\n\n /**\n * Отключает общий плагин у существующих клиентов и не применяет его к будущим.\n *\n * @returns `false`, если такого плагина не было\n * @throws {ItdConfigError} если от плагина зависит другой общий плагин\n */\n async unuse(name: string): Promise<boolean> {\n const index = this.#plugins.findIndex((plugin) => plugin.name === name);\n if (index < 0) return false;\n assertPluginRemovable(this.#plugins, name);\n const clients = [...this.#clients.values()];\n for (const client of clients) assertClientCanUnusePlugin(client, name);\n this.#plugins.splice(index, 1);\n this.#removingPlugins.add(name);\n\n try {\n const results = await Promise.allSettled(clients.map((client) => client.unuse(name)));\n const errors = results\n .filter((result): result is PromiseRejectedResult => result.status === 'rejected')\n .map((result) => result.reason);\n if (errors.length > 0) {\n throw new AggregateError(errors, `Не удалось отключить плагин «${name}» у всех аккаунтов`);\n }\n return true;\n } finally {\n this.#removingPlugins.delete(name);\n }\n }\n\n /**\n * Подписывается на события авторизации всех аккаунтов сразу.\n *\n * @returns функция отписки\n *\n * @example\n * ```ts\n * accounts.on('authError', ({ account }) => console.warn(`${account}: сессия потеряна`));\n * ```\n */\n on<K extends keyof AccountEvents>(event: K, listener: Listener<AccountEvents[K]>): Unsubscribe {\n assertAccountsActive(this, 'подписаться на события');\n return this.#emitter.on(event, listener);\n }\n\n /**\n * Перебор аккаунтов парами «имя — клиент».\n *\n * @example\n * ```ts\n * for (const [name, itd] of accounts) {\n * const me = await itd.users.me();\n * console.log(name, me.nickname);\n * }\n * ```\n */\n [Symbol.iterator](): IterableIterator<[string, ItdClient]> {\n return this.#clients.entries();\n }\n\n /**\n * Закрывает все аккаунты и останавливает общую очередь.\n *\n * Аккаунты остаются в контейнере и работоспособны: новые запросы поднимут всё заново,\n * но уже созданные потоки уведомлений останутся закрытыми.\n *\n * @example\n * ```ts\n * await using accounts = new ItdAccounts({ storage });\n * // …работа…\n * // dispose() вызовется сам на выходе из блока\n * ```\n */\n async close(): Promise<void> {\n try {\n await Promise.all([...this.#clients.values()].map((client) => client.close()));\n } finally {\n this.#queues?.stop();\n }\n }\n\n /**\n * Окончательно освобождает контейнер и отключает общие плагины у всех аккаунтов.\n *\n * Для временной остановки потоков и очереди без отключения плагинов используйте\n * {@link close}. Терминальное состояние устанавливается сразу: контейнер отзывает\n * storage-срезы и подписки, а новые аккаунты, запросы его клиентов и плагины после\n * первого `dispose()` больше не допускаются.\n */\n dispose(): Promise<void> {\n if (this.#disposePromise) return this.#disposePromise;\n DISPOSED_ACCOUNTS.add(this);\n this.#disposePromise = this.#dispose();\n return this.#disposePromise;\n }\n\n async #dispose(): Promise<void> {\n const clients = [...this.#clients.values()];\n const controls = [...this.#storageControls.values()];\n const accountRemovals = [...this.#accountRemovals];\n\n for (const control of controls) control.revoke();\n for (const unsubscribers of this.#eventUnsubscribers.values()) {\n for (const unsubscribe of unsubscribers) unsubscribe();\n }\n this.#eventUnsubscribers.clear();\n this.#storageControls.clear();\n this.#clients.clear();\n this.#emitter.removeAllListeners();\n\n const results = await Promise.allSettled([\n ...clients.map((client) => client.dispose()),\n ...controls.map((control) => control.drain()),\n ...accountRemovals,\n Promise.resolve().then(() => this.#queues?.clear()),\n ]);\n this.#plugins.splice(0);\n this.#features.splice(0);\n this.#removingPlugins.clear();\n\n const errors = results\n .filter((result): result is PromiseRejectedResult => result.status === 'rejected')\n .map((result) => result.reason);\n if (errors.length > 0) throw new AggregateError(errors, 'Не удалось освободить аккаунты');\n }\n\n /** Позволяет использовать контейнер с `await using`. */\n [Symbol.asyncDispose](): Promise<void> {\n return this.dispose();\n }\n\n static {\n installAsyncDisposeFallback(ItdAccounts);\n }\n\n /**\n * Собирает опции клиента: общие, поверх них — аккаунтные, и обязательно свой срез\n * хранилища.\n *\n * `headers` и `services` сливаются по ключам, а не заменяются целиком: иначе один свой\n * заголовок у аккаунта стирал бы весь общий набор.\n */\n #mergeOptions(options: AddAccountOptions, storage: TokenStorage): ItdClientOptions {\n const base = this.#base;\n\n return {\n ...base,\n ...options,\n ...(base.headers || options.headers\n ? { headers: { ...base.headers, ...options.headers } }\n : {}),\n ...(base.services || options.services\n ? { services: { ...base.services, ...options.services } }\n : {}),\n storage,\n };\n }\n\n /** Не теряет ошибку фонового освобождения ресурсов, которого синхронный API не ждёт. */\n #reportCleanup(scope: string, error: unknown): void {\n const message = `Не удалось освободить ресурсы после ${scope}`;\n if (this.#logger) this.#logger.error(message, error);\n else console.error(`[itd-api] ${message}`, error);\n }\n\n /** Ретранслирует события клиента наружу, добавляя к ним имя аккаунта. */\n #forwardEvents(account: string, client: ItdClient): Unsubscribe[] {\n return [\n client.on('tokens', ({ accessToken }) =>\n this.#emitter.emit('tokens', { account, accessToken }),\n ),\n client.on('signIn', ({ accessToken }) =>\n this.#emitter.emit('signIn', { account, accessToken }),\n ),\n client.on('signOut', () => this.#emitter.emit('signOut', { account })),\n client.on('authError', ({ error }) => this.#emitter.emit('authError', { account, error })),\n ];\n }\n}\n\n/**\n * Создаёт контейнер аккаунтов.\n *\n * То же, что `new ItdAccounts(options)`, — для тех, кому привычнее фабрика.\n */\nexport function createAccounts(options: ItdAccountsOptions = {}): ItdAccounts {\n return new ItdAccounts(options);\n}\n"],"mappings":"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"}