itd-api 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/index.cjs +652 -218
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +228 -42
- package/dist/index.d.ts +228 -42
- package/dist/index.js +649 -219
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["#listeners","#onError","#config","#send","#jar","#emitter","#hooks","#authScope","#loadSession","#identityForToken","#readExternalToken","#session","#sessionFromConfig","#externalIdentity","#authEpoch","#externalToken","#rotateAuthScope","#hasRefreshSession","#deviceId","#deviceIdLoading","#resolveDeviceId","createDeviceId","#saveSession","#signInWithCredentials","#refreshDeduplicated","#noRefreshSessionError","ItdAuthError","#invalidateInFlight","#transitionAuth","copySession","#seedRefreshCookie","#loading","#performLoad","REFRESH_COOKIE","REFRESH_COOKIE_PATH","#refreshing","#performRefresh","#reloginOrNull","ItdApiError","#signingIn","#performSignIn","#resolveTurnstileToken","ItdConfigError","ItdConfigError","ItdConfigError","isRecord","MemoryTokenStorage","RuntimeMode","resolveFetch","shouldUseCookieJar","shouldSendCredentials","#handler","#plugins","#baseUrl","ItdConfigError","#entries","#optionKeys","#removing","#cleanups","#ordered","#rebuildOptionKeys","#trackCleanup","#waitForDrain","#runHook","ItdAbortError","ItdApiError","ItdNetworkError","ItdTimeoutError","ItdFileError","ItdAbortError","isItdApiError","isItdRateLimitError","ItdAbortError","#concurrency","#minGap","#waiting","#active","#drain","#timer","#nextSlot","#options","#main","#byService","#services","#primaryHost","ItdConfigError","isBlob","ItdValidationError","ItdRateLimitError","ItdAuthError","ItdForbiddenError","ItdNotFoundError","ItdConflictError","ItdServerError","ItdApiErrorClass","ItdPhoneVerificationError","ItdConfigError","isBlob","#config","#deps","#buildHeaders","#prepareBody","#toTransportError","#readBodyOrFail","ItdTimeoutError","ItdAbortError","ItdError","ItdNetworkError","#interval","#limit","#readCount","#wait","#idleTimeout","#handshakeTimeout","#lastEventId","#handshake","#read","ItdConfigError","#deps","#options","#emitter","#transport","#maxAttempts","#createTransport","#status","#wanted","#attachEnvironmentListeners","#run","#timer","#detachEnvironment","#controller","#attempt","#setStatus","supportsStreamingBody","#handleEvent","#handleFailure","#refreshAndReconnect","#scheduleReconnect","#giveUp","#options","#maxPages","ItdConfigError","#state","#finished","#pagesLoaded","#advance","#auth","ItdConfigError","ItdConfigError","#state","#uploadFiles","#replies","ItdConfigError","#fetch","#createBodyFactory","#isStreamInput","#prepareStream","#prepareBuffer","ItdError","ItdFileError","ItdFileErrorReason","resolveFileStreamOptions","FileTransferMode","#resolveBuffer","#nameFromMime","normalizeMimeType","isBlob","openUrlFile","ItdConfigError","isBoundedFileStream","boundedFileStream","isFile","downloadFile","#posts","#list","ItdConfigError","ItdConfigError","#content","#spans","#formatted","ItdConfigError","#state","ItdConfigError","#state","#input","#uploadFiles","#feed","#wall","#liked","#comments","#collectAttachments","ItdConfigError","#state","ItdConfigError","#clock","#input","#submit","#result","#resource","#options","#maxBatchSize","#onClose","#dwell","#interactions","#state","#assertOpen","#flushPromise","#flushAll","#closePromise","#batches","#sessionId","createDeviceId","#uploadFile","#userList","#userPage","#userPaginator","#config","#http","#transport","#authManager","#jar","#queues","#ownsQueues","#plugins","#services","#streams","CookieJar","#throttleByHeaders","#disconnectStreams","ItdConfigError","#base","#storage","#clients","#removing","#storageControls","#eventUnsubscribers","#plugins","#removingPlugins","#queues","#rateLimitScope","#emitter","#logger","MemoryMultiTokenStorage","controlledTokenStorage","#mergeOptions","#reportPluginCleanup","#forwardEvents","isRestorableSession"],"sources":["../src/core/emitter.ts","../src/core/jwt.ts","../src/core/auth.ts","../src/core/url.ts","../src/core/version.ts","../src/core/config.ts","../src/core/http.ts","../src/core/pipeline.ts","../src/core/plugins.ts","../src/core/retry.ts","../src/core/middleware.ts","../src/core/rate-limit.ts","../src/core/services.ts","../src/core/redact.ts","../src/core/unwrap.ts","../src/core/error-factory.ts","../src/core/transport.ts","../src/types/enums.ts","../src/notifications/type-map.ts","../src/notifications/normalize.ts","../src/realtime/transport.ts","../src/realtime/poll.ts","../src/realtime/reconnect.ts","../node_modules/eventsource-parser/dist/index.js","../src/realtime/sse.ts","../src/realtime/stream.ts","../src/core/pagination.ts","../src/types/options.ts","../src/resources/base.ts","../src/resources/auth.ts","../src/builders/base.ts","../src/builders/comment.ts","../src/resources/comments.ts","../src/core/mime.ts","../src/core/multipart.ts","../src/resources/files.ts","../src/resources/hashtags.ts","../src/resources/notifications.ts","../src/core/time.ts","../src/resources/platform.ts","../src/spans/validate.ts","../src/spans/parse.ts","../src/builders/markup.ts","../src/builders/poll.ts","../src/builders/post.ts","../src/resources/posts.ts","../src/builders/report.ts","../src/resources/reports.ts","../src/resources/search.ts","../src/resources/subscription.ts","../src/resources/telemetry.ts","../src/resources/users.ts","../src/resources/verification.ts","../src/client.ts","../src/accounts.ts","../src/notifications/text.ts","../src/notifications/url.ts","../src/spans/render.ts","../src/types/models.ts"],"sourcesContent":["import type { Logger } from '../types/options.js';\n\n/** Обработчик события. */\nexport type Listener<T> = (payload: T) => void;\n\n/** Функция отписки, которую возвращает подписка на событие. */\nexport type Unsubscribe = () => void;\n\n/**\n * Минимальный типизированный источник событий.\n *\n * Своя реализация вместо `EventTarget` и `EventEmitter`: первый есть не везде и требует\n * обёрток `CustomEvent`, второй существует только в Node. Нужны ровно подписка и рассылка.\n *\n * Исключение в обработчике не прерывает рассылку остальным и не роняет библиотеку.\n *\n * @typeParam Events карта «имя события → тип полезной нагрузки». Задаётся интерфейсом,\n * поэтому ограничение на индексную сигнатуру намеренно не накладывается.\n */\nexport class Emitter<Events> {\n readonly #listeners = new Map<keyof Events, Set<Listener<never>>>();\n readonly #onError: ((error: unknown) => void) | undefined;\n\n constructor(onListenerError?: (error: unknown) => void) {\n this.#onError = onListenerError;\n }\n\n /**\n * Подписывается на событие.\n *\n * @returns функция отписки\n *\n * @example\n * ```ts\n * const off = realtime.on('notification', (event) => console.log(event));\n * off();\n * ```\n */\n on<K extends keyof Events>(event: K, listener: Listener<Events[K]>): Unsubscribe {\n const set = this.#listeners.get(event) ?? new Set();\n set.add(listener as Listener<never>);\n this.#listeners.set(event, set);\n\n return () => this.off(event, listener);\n }\n\n /** Подписывается на одно срабатывание. */\n once<K extends keyof Events>(event: K, listener: Listener<Events[K]>): Unsubscribe {\n const off = this.on(event, (payload) => {\n off();\n listener(payload);\n });\n return off;\n }\n\n /** Отписывается от события. */\n off<K extends keyof Events>(event: K, listener: Listener<Events[K]>): void {\n this.#listeners.get(event)?.delete(listener as Listener<never>);\n }\n\n /** Рассылает событие подписчикам. */\n emit<K extends keyof Events>(event: K, payload: Events[K]): void {\n const set = this.#listeners.get(event);\n if (!set) return;\n\n // Копия нужна: обработчик вправе отписаться прямо во время рассылки.\n for (const listener of [...set]) {\n try {\n (listener as Listener<Events[K]>)(payload);\n } catch (error) {\n this.#onError?.(error);\n }\n }\n }\n\n /** Сколько подписчиков у события. */\n listenerCount(event: keyof Events): number {\n return this.#listeners.get(event)?.size ?? 0;\n }\n\n /** Снимает все подписки. */\n removeAllListeners(): void {\n this.#listeners.clear();\n }\n}\n\n/**\n * Сообщает об исключении из пользовательского обработчика события: пишет в логгер,\n * при его отсутствии — в консоль.\n *\n * Ошибка чужого обработчика не должна ронять библиотеку, но и пропадать молча ей нельзя:\n * иначе подписка, падающая на каждом событии, выглядит как её отсутствие.\n *\n * @param scope о чьих событиях речь — попадает в текст сообщения\n *\n * @internal\n */\nexport function reportListenerError(\n logger: Logger | undefined,\n scope: string,\n error: unknown,\n): void {\n const message = `Ошибка в обработчике события ${scope}`;\n if (logger) logger.error(message, error);\n else console.error(`[itd-api] ${message}`, error);\n}\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 { UserId } from '../types/models.js';\nimport type { AuthInput, CredentialsAuth } from '../types/options.js';\nimport type { AuthConfig } from './config.js';\nimport {\n AUTH_FLAG_COOKIE,\n type CookieJar,\n REFRESH_COOKIE,\n REFRESH_COOKIE_PATH,\n} from './cookies.js';\nimport { Emitter, reportListenerError } from './emitter.js';\nimport { ItdApiError, ItdAuthError, ItdConfigError } from './errors.js';\nimport { readTokenIdentity, readTokenSubject } from './jwt.js';\nimport type { RequestHandler } from './pipeline.js';\nimport { createDeviceId } from './runtime.js';\nimport { copySession, type ItdSession } from './storage.js';\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 * @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/** Ответ эндпоинтов, выдающих токен. */\ninterface TokenResponse {\n accessToken?: unknown;\n}\n\nfunction readAccessToken(payload: unknown): string | undefined {\n if (typeof payload !== 'object' || payload === null) return undefined;\n const token = (payload as TokenResponse).accessToken;\n return typeof token === 'string' && token.length > 0 ? token : undefined;\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\n/** Области аккаунта и конкретной сессии для локального состояния плагинов. */\nexport interface AuthIdentity {\n /** Идентификатор пользователя; отсутствует у непрозрачного или повреждённого токена. */\n userId?: UserId | undefined;\n /** Идентификатор серверной сессии; отсутствует у непрозрачного или повреждённого токена. */\n sessionId?: string | undefined;\n}\n\ninterface AuthManagerHooks {\n /** Вызывается синхронно перед заменой владельца авторизации. */\n onAccountChange?: (() => void) | undefined;\n}\n\n/**\n * Хранит сессию и продлевает её.\n *\n * Главное здесь — **дедупликация обновления**. Когда десять параллельных запросов\n * одновременно получают `401`, обновление должно произойти один раз, а остальные обязаны\n * дождаться его результата. Иначе сервер увидит десять параллельных `refresh`, и все,\n * кроме первого, скорее всего получат отказ по уже использованному токену.\n */\nexport class AuthManager {\n readonly #config: AuthConfig;\n readonly #send: RequestHandler;\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: AuthConfig,\n send: RequestHandler,\n jar: CookieJar,\n hooks: AuthManagerHooks = {},\n ) {\n this.#config = config;\n this.#send = send;\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 /**\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 /** Меняет fallback и завершает realtime, только если фактически сменился аккаунт. */\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 async getAuthHeaders(): Promise<Record<string, string>> {\n const token = await this.getAccessToken();\n return token ? { Authorization: `Bearer ${token}` } : {};\n }\n\n /**\n * Идентификатор устройства для заголовка `X-Device-Id`.\n *\n * Заводится один раз и сохраняется в сессии, чтобы пережить перезапуск процесса:\n * сервер связывает с ним запись в списке сессий, и плавающее значение плодило бы\n * по новой сессии на каждый старт.\n */\n getDeviceId(): 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 getAccessToken(): 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({ ...(this.#session ?? {}), accessToken, obtainedAt: Date.now() });\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') return { accessToken: auth, obtainedAt: Date.now() };\n\n if ('accessToken' in auth) {\n return {\n accessToken: auth.accessToken,\n refreshToken: auth.refreshToken,\n obtainedAt: Date.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 // Служебный запрос идёт через плагины и повторы, но мимо очереди и слоя авторизации:\n // обновление порождается изнутри запроса, который держит слот в очереди и ждёт его результата.\n const payload = await this.#send({\n method: 'POST',\n path: AUTH_PATHS.refresh,\n skipQueue: true,\n skipAuth: true,\n skipAuthRefresh: true,\n // Тела нет намеренно: сервер читает refresh-токен только из cookie — см.\n // #seedRefreshCookie. По той же причине не нужен и устаревший Bearer.\n });\n\n const accessToken = readAccessToken(payload);\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: Date.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 // Через служебный auth-конвейер: плагины и повторы сохраняются, очередь и слой\n // авторизации обходятся, чтобы не зависнуть на запросе, который ждёт токен.\n const payload = await this.#send({\n method: 'POST',\n path: AUTH_PATHS.signIn,\n body: { email: credentials.email, password: credentials.password, turnstileToken },\n skipQueue: true,\n skipAuth: true,\n skipAuthRefresh: true,\n });\n\n const accessToken = readAccessToken(payload);\n\n if (!accessToken) {\n // Сервер запросил подтверждение по коду — автоматически это не пройти.\n throw new ItdConfigError(\n 'Вход по email и паролю требует подтверждения кодом из письма. Автоматический вход ' +\n 'невозможен: воспользуйтесь itd.auth.signInWithOtp() и передайте полученный ' +\n 'accessToken в конфигурацию клиента.',\n );\n }\n\n // Прежний refresh-токен и cookie намеренно не переносятся: вход выдал новую сессию,\n // и держаться за старую было бы ошибкой. Идентификатор устройства добавит #saveSession.\n this.#invalidateInFlight();\n this.#transitionAuth(accessToken);\n await this.#saveSession({ accessToken, obtainedAt: Date.now() });\n this.#emitter.emit('tokens', { accessToken });\n this.#emitter.emit('signIn', { accessToken });\n\n return accessToken;\n }\n}\n","import { ItdConfigError } from './errors.js';\n\n/** Значение параметра запроса. `undefined` и `null` в строку не попадают. */\nexport type QueryValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | readonly (string | number | boolean)[];\n\n/** Параметры строки запроса. */\nexport type QueryParams = Record<string, QueryValue>;\n\n/**\n * Собирает строку запроса.\n *\n * Правила:\n * - `undefined` и `null` пропускаются целиком — не нужно чистить объект перед вызовом;\n * - `boolean` превращается в `true` / `false`;\n * - массив повторяет ключ (`ids=1&ids=2`).\n *\n * @returns строка вида `?a=1&b=2` либо пустая строка, если параметров нет\n *\n * @example\n * ```ts\n * buildQuery({ tab: 'popular', limit: 20, cursor: undefined });\n * // '?tab=popular&limit=20'\n * ```\n */\nexport function buildQuery(params?: QueryParams): string {\n if (!params) return '';\n\n const search = new URLSearchParams();\n\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item === undefined || item === null) continue;\n search.append(key, String(item));\n }\n continue;\n }\n\n search.append(key, String(value));\n }\n\n const query = search.toString();\n return query ? `?${query}` : '';\n}\n\n/**\n * Кодирует значение для подстановки в путь.\n *\n * Нужно для сегментов, которые могут содержать что угодно, — прежде всего хэштегов\n * в `/api/hashtags/{tag}/posts`.\n *\n * @throws {ItdConfigError} если значение пустое\n */\nexport function encodePathSegment(value: string, name = 'параметр пути'): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new ItdConfigError(\n `${name} должен быть непустой строкой, получено: ${JSON.stringify(value)}`,\n );\n }\n return encodeURIComponent(value);\n}\n\n/**\n * Склеивает базовый URL и путь.\n *\n * Завершающий слэш пути сохраняется: он значим для `/api/notifications/`\n * и `/api/v1/subscription/`, без него сервер отвечает ошибкой.\n *\n * @example\n * ```ts\n * joinUrl('https://xn--d1ah4a.com/', '/api/notifications/');\n * // 'https://xn--d1ah4a.com/api/notifications/'\n * ```\n */\nexport function joinUrl(baseUrl: string, path: string): string {\n const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n const suffix = path.startsWith('/') ? path : `/${path}`;\n return `${base}${suffix}`;\n}\n\n/** Приводит базовый URL к каноничному виду и проверяет, что он вообще похож на URL. */\nexport function normalizeBaseUrl(baseUrl: string): string {\n if (typeof baseUrl !== 'string' || baseUrl.trim() === '') {\n throw new ItdConfigError(\n `baseUrl должен быть непустой строкой с абсолютным URL, получено: ${JSON.stringify(baseUrl)}`,\n );\n }\n\n let parsed: URL;\n try {\n parsed = new URL(baseUrl);\n } catch {\n throw new ItdConfigError(\n `baseUrl должен быть абсолютным URL, получено: ${JSON.stringify(baseUrl)}`,\n );\n }\n\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new ItdConfigError(\n `baseUrl должен использовать http или https, получено: ${parsed.protocol}`,\n );\n }\n\n if (parsed.username || parsed.password || parsed.search || parsed.hash) {\n throw new ItdConfigError(\n 'baseUrl не должен содержать логин, пароль, query-параметры или fragment',\n );\n }\n\n return parsed.origin + (parsed.pathname === '/' ? '' : parsed.pathname.replace(/\\/$/, ''));\n}\n","// Этот файл создаётся автоматически: scripts/sync-version.mjs.\n// Менять вручную не нужно — значение берётся из package.json при `npm version` и сборке.\n\n/** Версия библиотеки. Попадает в `User-Agent`. */\nexport const LIBRARY_VERSION = '0.2.0';\n","import type { AuthInput, ClientHooks, ItdClientOptions, Logger } from '../types/options.js';\nimport { ItdConfigError } from './errors.js';\nimport { RuntimeMode, resolveFetch, shouldSendCredentials, shouldUseCookieJar } from './runtime.js';\nimport type { ServiceDefinition } from './services.js';\nimport { MemoryTokenStorage, type TokenStorage } from './storage.js';\nimport { normalizeBaseUrl } from './url.js';\nimport { LIBRARY_VERSION } from './version.js';\n\n/** Базовый URL API итд.com. Домен записан в punycode: `итд.com`. */\nexport const DEFAULT_BASE_URL = 'https://xn--d1ah4a.com';\n\n/** Базовый URL страницы статуса. Домен записан в punycode: `статус.итд.com`. */\nexport const DEFAULT_STATUS_BASE_URL = 'https://xn--80a7abcbg.xn--d1ah4a.com';\n\n/** Имя встроенного сервиса статуса. */\nexport const STATUS_SERVICE = 'status';\n\n/** Сервисы, зарегистрированные у любого клиента. */\nexport const BUILT_IN_SERVICES: readonly ServiceDefinition[] = Object.freeze([\n Object.freeze({ name: STATUS_SERVICE, baseUrl: DEFAULT_STATUS_BASE_URL, auth: false }),\n]);\n\n/** Таймаут запроса по умолчанию — 30 секунд. */\nexport const DEFAULT_TIMEOUT = 30_000;\n\n// Значение живёт в отдельном модуле, который порождается из package.json скриптом\n// scripts/sync-version.mjs: две записанные вручную версии рано или поздно разъезжаются.\nexport { LIBRARY_VERSION } from './version.js';\n\n/**\n * `User-Agent` по умолчанию.\n *\n * Сайт стоит за DDoS-Guard, и запросы вовсе без `User-Agent` (так делает `fetch` в Node)\n * имеют шанс не пройти фильтр. Префикс `Mozilla/5.0` — дань традиции таких фильтров,\n * дальше идёт честное имя библиотеки: подделываться под браузер она не должна.\n *\n * В браузере заголовок не выставляется — `User-Agent` там запрещён к изменению, и среда\n * молча его игнорирует.\n */\nexport const DEFAULT_USER_AGENT = `Mozilla/5.0 (compatible; itd-api/${LIBRARY_VERSION}; +https://github.com/KiowDev/itd-api)`;\n\n/**\n * Настройки повторов со всеми значениями по умолчанию.\n *\n * Поля перечислены явно, а не через `Required<RetryOptions>`: тот снимает необязательность,\n * но оставляет `| undefined` в типе значения, раз оно указано в исходном интерфейсе.\n */\nexport interface ResolvedRetryOptions {\n attempts: number;\n baseDelay: number;\n maxDelay: number;\n jitter: number;\n retryWrites: boolean;\n shouldRetry: ((error: unknown, attempt: number) => boolean) | undefined;\n}\n\n/**\n * Паузы перед повторами при ответе `429`.\n *\n * Сервер итд.com не присылает `Retry-After` и не сообщает время сброса окна, поэтому\n * паузу приходится подбирать лестницей: от секунды, если окно почти истекло,\n * до полутора минут, если лимит исчерпан всерьёз.\n */\nexport const DEFAULT_RATE_LIMIT_DELAYS = Object.freeze([1000, 5000, 30_000, 60_000, 90_000]);\n\n/** Настройки очереди со всеми значениями по умолчанию. */\nexport interface ResolvedRateLimitOptions {\n concurrency: number;\n rps: number | undefined;\n retryDelays: readonly number[];\n respectHeaders: boolean;\n}\n\n/**\n * Срез конфигурации, нужный слою авторизации.\n *\n * Выделен явно, чтобы {@link AuthManager} не получал целиком `ResolvedConfig` со всеми\n * настройками транспорта, повторов и очереди, к которым он отношения не имеет.\n * `ResolvedConfig` содержит все эти поля, поэтому подходит везде, где ждут `AuthConfig`.\n */\nexport interface AuthConfig {\n baseUrl: string;\n auth: AuthInput | undefined;\n storage: TokenStorage;\n useCookieJar: boolean;\n deviceId: string | undefined;\n reloginOnRefreshFailure: boolean;\n logger: Logger | undefined;\n}\n\n/** Конфигурация клиента после подстановки значений по умолчанию и проверок. */\nexport interface ResolvedConfig extends AuthConfig {\n /** Сервисы из опций клиента. Встроенные сюда не входят. */\n services: ServiceDefinition[];\n autoRefresh: boolean;\n fetch: typeof fetch;\n timeout: number;\n retry: ResolvedRetryOptions | undefined;\n rateLimit: ResolvedRateLimitOptions | undefined;\n hooks: ClientHooks;\n headers: Record<string, string>;\n /** Значение заголовка `User-Agent`. `undefined` — заголовок не выставляется. */\n userAgent: string | undefined;\n mode: RuntimeMode;\n /** Отправлять ли `credentials: 'include'` (в браузере). */\n sendCredentials: boolean;\n}\n\nfunction requirePositive(value: number, name: string): number {\n if (!Number.isFinite(value) || value < 0) {\n throw new ItdConfigError(`${name} должен быть неотрицательным числом, получено: ${value}`);\n }\n return value;\n}\n\nfunction isRecord(value: unknown): boolean {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction requireOptionalBoolean(value: unknown, name: string): void {\n if (value !== undefined && typeof value !== 'boolean') {\n throw new ItdConfigError(`${name} должен быть boolean`);\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\nfunction resolveHeaders(headers: Record<string, string> | undefined): Record<string, string> {\n if (headers === undefined) return {};\n if (!isRecord(headers)) throw new ItdConfigError('headers должен быть объектом строк');\n\n for (const [name, value] of Object.entries(headers)) {\n if (typeof value !== 'string') {\n throw new ItdConfigError(`headers.${name} должен быть строкой`);\n }\n }\n return { ...headers };\n}\n\nfunction resolveHooks(hooks: ClientHooks | undefined): ClientHooks {\n if (hooks === undefined) return {};\n if (!isRecord(hooks)) throw new ItdConfigError('hooks должен быть объектом');\n\n for (const name of ['onRequest', 'onResponse', 'onError', 'onRetry'] as const) {\n if (hooks[name] !== undefined && typeof hooks[name] !== 'function') {\n throw new ItdConfigError(`hooks.${name} должен быть функцией`);\n }\n }\n return { ...hooks };\n}\n\nfunction resolveLogger(logger: ItdClientOptions['logger']): Logger | undefined {\n if (logger === undefined || logger === false) return undefined;\n if (logger === true) return consoleLogger();\n if (!isRecord(logger)) throw new ItdConfigError('logger должен быть boolean или объектом Logger');\n\n for (const method of ['debug', 'info', 'warn', 'error'] as const) {\n if (typeof logger[method] !== 'function') {\n throw new ItdConfigError(`logger.${method} должен быть функцией`);\n }\n }\n return logger;\n}\n\n/** Логгер поверх `console` — включается опцией `logger: true`. */\nfunction consoleLogger(): Logger {\n return {\n debug: (message, ...args) => console.debug(`[itd-api] ${message}`, ...args),\n info: (message, ...args) => console.info(`[itd-api] ${message}`, ...args),\n warn: (message, ...args) => console.warn(`[itd-api] ${message}`, ...args),\n error: (message, ...args) => console.error(`[itd-api] ${message}`, ...args),\n };\n}\n\n/**\n * Приводит настройки повторов к полному виду.\n *\n * Вызывается и при создании клиента (для глобальной настройки), и на каждый запрос,\n * у которого задан свой `retry`. Возвращает `undefined`, когда повторять не нужно\n * (`retry: false` или единственная попытка).\n *\n * @throws {ItdConfigError} при некорректных значениях\n */\nexport function resolveRetry(retry: ItdClientOptions['retry']): ResolvedRetryOptions | undefined {\n if (retry === false) return undefined;\n if (retry !== undefined && !isRecord(retry)) {\n throw new ItdConfigError('retry должен быть объектом или false');\n }\n\n const options = retry ?? {};\n const attempts = options.attempts ?? 3;\n\n if (!Number.isInteger(attempts) || attempts < 1) {\n throw new ItdConfigError(`retry.attempts должен быть целым числом от 1, получено: ${attempts}`);\n }\n\n const jitter = options.jitter ?? 0.3;\n if (!Number.isFinite(jitter) || jitter < 0 || jitter > 1) {\n throw new ItdConfigError(`retry.jitter должен быть в диапазоне 0…1, получено: ${jitter}`);\n }\n requireOptionalBoolean(options.retryWrites, 'retry.retryWrites');\n if (options.shouldRetry !== undefined && typeof options.shouldRetry !== 'function') {\n throw new ItdConfigError('retry.shouldRetry должен быть функцией');\n }\n\n const baseDelay = requirePositive(options.baseDelay ?? 500, 'retry.baseDelay');\n const maxDelay = requirePositive(options.maxDelay ?? 30_000, 'retry.maxDelay');\n\n // Одна попытка означает отсутствие повторов — очередь ретраев можно не поднимать.\n if (attempts === 1) return undefined;\n\n return {\n attempts,\n baseDelay,\n maxDelay,\n jitter,\n retryWrites: options.retryWrites ?? false,\n shouldRetry: options.shouldRetry,\n };\n}\n\n/**\n * Приводит настройки очереди к полному виду. `undefined` — очередь не нужна.\n *\n * Кроме создания клиента вызывается ещё из {@link ItdAccounts}: общая на всех аккаунтов\n * очередь заводится из тех же опций и с теми же проверками.\n *\n * @throws {ItdConfigError} при некорректных значениях\n *\n * @internal\n */\nexport function resolveRateLimit(\n rateLimit: ItdClientOptions['rateLimit'],\n): ResolvedRateLimitOptions | undefined {\n if (rateLimit === false) return undefined;\n if (rateLimit !== undefined && !isRecord(rateLimit)) {\n throw new ItdConfigError('rateLimit должен быть объектом или false');\n }\n\n const defaults = {\n concurrency: 6,\n rps: undefined,\n retryDelays: DEFAULT_RATE_LIMIT_DELAYS,\n respectHeaders: true,\n };\n if (!rateLimit) return defaults;\n\n const concurrency = rateLimit.concurrency ?? 6;\n if (!Number.isInteger(concurrency) || concurrency < 1) {\n throw new ItdConfigError(\n `rateLimit.concurrency должен быть целым числом от 1, получено: ${concurrency}`,\n );\n }\n\n if (rateLimit.rps !== undefined && (!Number.isFinite(rateLimit.rps) || rateLimit.rps <= 0)) {\n throw new ItdConfigError(\n `rateLimit.rps должен быть положительным числом, получено: ${rateLimit.rps}`,\n );\n }\n\n const retryDelays = rateLimit.retryDelays ?? defaults.retryDelays;\n if (!Array.isArray(retryDelays)) {\n throw new ItdConfigError('rateLimit.retryDelays должен быть массивом чисел');\n }\n for (const delay of retryDelays) requirePositive(delay, 'rateLimit.retryDelays');\n requireOptionalBoolean(rateLimit.respectHeaders, 'rateLimit.respectHeaders');\n\n return {\n concurrency,\n rps: rateLimit.rps,\n retryDelays: [...retryDelays],\n respectHeaders: rateLimit.respectHeaders ?? true,\n };\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\n/**\n * Разворачивает запись сервисов из опций в определения: имя берётся из ключа, строка\n * означает один только базовый URL. URL проверяется при регистрации сервиса.\n */\nfunction resolveServices(services: ItdClientOptions['services']): ServiceDefinition[] {\n if (services === undefined) return [];\n if (!isRecord(services)) throw new ItdConfigError('services должен быть объектом');\n\n return Object.entries(services).map(([name, value]) => {\n if (typeof value === 'string') return { name, baseUrl: value };\n if (!isRecord(value)) {\n throw new ItdConfigError(`services.${name} должен быть URL или объектом сервиса`);\n }\n return { ...value, name } as ServiceDefinition;\n });\n}\n\n/**\n * Приводит пользовательские опции к полной конфигурации.\n *\n * Все проверки выполняются здесь, до единого сетевого запроса: неверная настройка должна\n * проявляться при создании клиента, а не через полчаса работы бота.\n *\n * @throws {ItdConfigError} при некорректных значениях\n */\nexport function resolveConfig(options: ItdClientOptions = {}): ResolvedConfig {\n if (!isRecord(options)) throw new ItdConfigError('опции клиента должны быть объектом');\n\n const mode: RuntimeMode = options.mode ?? RuntimeMode.Auto;\n\n if (!Object.values(RuntimeMode).includes(mode)) {\n throw new ItdConfigError(\n `mode должен быть одним из ${Object.values(RuntimeMode).join(', ')}, получено: ${mode}`,\n );\n }\n\n const timeout = requirePositive(options.timeout ?? DEFAULT_TIMEOUT, 'timeout');\n requireOptionalBoolean(options.autoRefresh, 'autoRefresh');\n requireOptionalBoolean(options.reloginOnRefreshFailure, 'reloginOnRefreshFailure');\n\n if (\n options.userAgent !== undefined &&\n options.userAgent !== false &&\n typeof options.userAgent !== 'string'\n ) {\n throw new ItdConfigError('userAgent должен быть строкой или false');\n }\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 baseUrl: normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL),\n services: resolveServices(options.services),\n auth: validateAuth(options.auth),\n storage: resolveStorage(options.storage),\n autoRefresh: options.autoRefresh ?? true,\n reloginOnRefreshFailure: options.reloginOnRefreshFailure ?? true,\n fetch: resolveFetch(options.fetch),\n timeout,\n retry: resolveRetry(options.retry),\n rateLimit: resolveRateLimit(options.rateLimit),\n hooks: resolveHooks(options.hooks),\n logger: resolveLogger(options.logger),\n headers: resolveHeaders(options.headers),\n deviceId: options.deviceId,\n // `false` — способ не слать заголовок вовсе; строка заменяет умолчание.\n userAgent: options.userAgent === false ? undefined : (options.userAgent ?? DEFAULT_USER_AGENT),\n mode,\n useCookieJar: shouldUseCookieJar(mode),\n sendCredentials: shouldSendCredentials(mode),\n };\n}\n","import type { PipelineRequest, RequestHandler } from './pipeline.js';\nimport type { PluginRegistry } from './plugins.js';\n\n/** Что нужно фасаду для работы. */\nexport interface HttpClientDeps {\n /** Готовый обработчик — вся цепочка слоёв поверх транспорта. */\n handler: RequestHandler;\n /** Реестр плагинов: у него ресурсы спрашивают имена заявленных опций. */\n plugins: PluginRegistry;\n baseUrl: string;\n}\n\n/**\n * Точка входа ресурсов в конвейер запросов.\n *\n * Принимает готовый обработчик — цепочку слоёв поверх транспорта, собранную\n * в {@link ItdClient}, — и отдаёт ресурсам метод `request` и имена опций плагинов.\n * О слоях и их порядке ресурсы не знают.\n */\nexport class HttpClient {\n readonly #handler: RequestHandler;\n readonly #plugins: PluginRegistry;\n readonly #baseUrl: string;\n\n constructor(deps: HttpClientDeps) {\n this.#handler = deps.handler;\n this.#plugins = deps.plugins;\n this.#baseUrl = deps.baseUrl;\n }\n\n /** Базовый URL, к которому обращается клиент. */\n get baseUrl(): string {\n return this.#baseUrl;\n }\n\n /**\n * Имена опций запроса, заявленные плагинами.\n *\n * Читается ресурсами: они переносят в транспорт только известные поля, а чужие,\n * если их никто не заявил, отсеивают.\n */\n get pluginOptionKeys(): ReadonlySet<string> {\n return this.#plugins.optionKeys;\n }\n\n /**\n * Выполняет запрос к API через собранный конвейер.\n *\n * @typeParam T ожидаемая форма ответа после снятия обёртки `{ data: … }`\n * @throws {ItdApiError} если сервер ответил статусом ≥ 400\n * @throws {ItdTimeoutError} если истёк таймаут\n * @throws {ItdAbortError} если запрос отменён через `signal`\n * @throws {ItdNetworkError} если запрос не дошёл до сервера\n */\n request<T = unknown>(options: PipelineRequest): Promise<T> {\n return this.#handler(options) as Promise<T>;\n }\n}\n","import type { RawRequestOptions } from '../types/options.js';\n\n/** Тело, заново подготовленное для одной транспортной попытки. */\nexport interface PreparedRequestBody {\n body: BodyInit;\n /** Заголовки тела, например multipart boundary. Пользовательские заголовки важнее. */\n headers?: Record<string, string> | undefined;\n /** Освобождает открытый файл или входящий HTTP-поток. */\n cleanup?: (() => void | Promise<void>) | undefined;\n}\n\n/** Контекст подготовки повторяемого тела. */\nexport interface RequestBodyContext {\n signal: AbortSignal;\n attempt: number;\n}\n\n/** Создаёт новое тело для каждой транспортной попытки. */\nexport type RequestBodyFactory = (\n context: RequestBodyContext,\n) => PreparedRequestBody | Promise<PreparedRequestBody>;\n\n/**\n * Описание запроса внутри конвейера.\n *\n * Отличается от публичного {@link RawRequestOptions} одним служебным полем: слои конвейера\n * должны уметь дописать заголовки так, чтобы пользовательские `headers` всё равно остались\n * важнее. Смешивать их в одном объекте нельзя — тогда слой авторизации перебивал бы\n * `Authorization`, заданный вызывающим кодом вручную.\n */\nexport interface PipelineRequest extends RawRequestOptions {\n /** Повторяемое тело. Используется внутренними ресурсами вместо `body`. @internal */\n bodyFactory?: RequestBodyFactory | undefined;\n /**\n * Разрешает повтор записи после сетевого сбоя. Тело должно быть повторяемым.\n *\n * @internal\n */\n retryNetworkWrite?: boolean | undefined;\n /**\n * Заголовки, добавленные слоями конвейера.\n *\n * Ставятся до пользовательских `headers` и потому могут быть ими переопределены.\n *\n * @internal\n */\n layerHeaders?: Record<string, string> | undefined;\n /**\n * Номер попытки, начиная с 1. Проставляет слой повторов, читают хуки.\n *\n * @internal\n */\n attempt?: number | undefined;\n}\n\n/** Обработчик запроса. Самый внутренний в цепочке — транспорт. */\nexport type RequestHandler = (request: PipelineRequest) => Promise<unknown>;\n\n/**\n * Слой конвейера запросов.\n *\n * Получает запрос и продолжение цепочки. Может изменить запрос, обработать ошибку\n * продолжения или вовсе не вызывать `next`.\n */\nexport type RequestMiddleware = (\n request: PipelineRequest,\n next: RequestHandler,\n) => Promise<unknown>;\n\n/**\n * Собирает слои в один обработчик.\n *\n * Первый слой оказывается самым внешним. Порядок задаётся в {@link ItdClient}.\n *\n * @example\n * ```ts\n * const handler = composePipeline([queue, plugins, retries, auth], transport.send);\n * ```\n */\nexport function composePipeline(\n middlewares: readonly RequestMiddleware[],\n final: RequestHandler,\n): RequestHandler {\n return middlewares.reduceRight<RequestHandler>(\n (next, middleware) => (request) => middleware(request, next),\n final,\n );\n}\n\n/** Добавляет заголовки слоя, не трогая пользовательские. */\nexport function withLayerHeaders(\n request: PipelineRequest,\n headers: Record<string, string>,\n): PipelineRequest {\n return { ...request, layerHeaders: { ...request.layerHeaders, ...headers } };\n}\n","import type { ClientHooks, Logger, RawRequestOptions } from '../types/options.js';\nimport type { AuthIdentity } from './auth.js';\nimport { ItdConfigError } from './errors.js';\n\n/**\n * Обёртка вокруг запроса.\n *\n * Получает описание запроса и продолжение цепочки. Может изменить запрос перед отправкой,\n * посмотреть и подменить разобранный ответ или вовсе не вызывать `next` и вернуть своё.\n *\n * @param request что уходит на сервер; изменять сам объект не нужно — передайте копию в `next`\n * @param next продолжение: либо следующая обёртка, либо настоящий запрос\n * @returns тело ответа в том виде, в каком его получит вызывающий код\n *\n * @example Дописать заголовок ко всем запросам\n * ```ts\n * const transformer: Transformer = (request, next) =>\n * next({ ...request, headers: { ...request.headers, 'X-Trace': trace() } });\n * ```\n */\nexport type Transformer = (\n request: RawRequestOptions,\n next: (request: RawRequestOptions) => Promise<unknown>,\n) => Promise<unknown>;\n\n/** Освобождение ресурсов, заведённых плагином при установке. */\nexport type PluginTeardown = () => void | Promise<void>;\n\n/** Что плагин получает при подключении. */\nexport interface PluginContext {\n /** Базовый URL клиента — например чтобы разобрать абсолютные ссылки из ответа. */\n baseUrl: string;\n /** Отладочный вывод клиента, если он включён. */\n logger: Logger | undefined;\n /**\n * Непрозрачная fallback-область текущей авторизации.\n *\n * Нужна плагинам, которые обязаны безопасно изолировать непрозрачный токен. Для объединения\n * состояния копий одного аккаунта используйте {@link getAuthIdentity}.\n */\n getAuthScope?: (() => string) | undefined;\n /**\n * Загружает сессию и возвращает идентификаторы аккаунта и конкретной сессии из JWT.\n *\n * Предпочтительнее {@link getAuthScope} для состояния, которое должно объединяться между\n * несколькими экземплярами клиента одного аккаунта.\n */\n getAuthIdentity?: (() => Promise<AuthIdentity>) | undefined;\n /** Добавляет обёртку запроса. Подключённые раньше оказываются снаружи. */\n use(transformer: Transformer): void;\n /**\n * Добавляет перехватчики отдельных сетевых попыток.\n *\n * В отличие от {@link use}, они видят каждый retry и сырой `Response` до чтения тела.\n * Несколько наборов хуков одного плагина вызываются в порядке регистрации.\n */\n useHooks(hooks: ClientHooks): void;\n}\n\n/**\n * Плагин клиента.\n *\n * Подключается через `itd.use(plugin)` и работает на уровне транспорта: видит запрос\n * до отправки и разобранный ответ. Библиотека не знает, что именно делает плагин, —\n * ей достаточно списка обёрток и имён опций, которые он читает.\n *\n * @example\n * ```ts\n * const logging: ItdPlugin = {\n * name: 'logging',\n * install({ use, logger }) {\n * use(async (request, next) => {\n * logger?.info(`${request.method} ${request.path}`);\n * return next(request);\n * });\n * },\n * };\n *\n * itd.use(logging);\n * ```\n */\nexport interface ItdPlugin {\n /** Имя плагина. Должно быть уникальным: повторное подключение — ошибка. */\n name: string;\n /**\n * Имена опций запроса, которые плагин читает у методов ресурсов.\n *\n * Библиотека этих опций не понимает и ничего с ними не делает — только доносит\n * от вызова метода до обёртки нетронутыми. Без такого списка чужие поля отсеиваются,\n * чтобы случайная опечатка в параметрах не уезжала на сервер.\n *\n * Имена полей самого запроса (`path`, `body`, `headers`, `signal` и прочие из\n * `RawRequestOptions`) заявить нельзя: подключение такого плагина завершится ошибкой.\n *\n * Типы для них плагин объявляет сам, дополняя `RequestOptions`:\n * ```ts\n * declare module 'itd-api' {\n * interface RequestOptions { encrypt?: string | undefined }\n * }\n * ```\n */\n optionKeys?: readonly string[];\n /** Плагины, которые обязаны быть подключены раньше этого. */\n requires?: readonly string[];\n /** Несовместимые плагины. Достаточно объявить конфликт с одной стороны. */\n conflicts?: readonly string[];\n /** Имена плагинов, снаружи которых должна стоять эта обёртка. */\n before?: readonly string[];\n /** Имена плагинов, внутри которых должна стоять эта обёртка. */\n after?: readonly string[];\n /**\n * Устанавливает плагин.\n *\n * Может вернуть функцию освобождения ресурсов. Она вызывается при `unuse()` или\n * окончательном `dispose()` клиента и может быть асинхронной.\n */\n install(context: PluginContext): unknown;\n}\n\n/** Пустой набор — отдаётся, пока плагинов нет, чтобы не заводить объект на каждый запрос. */\nconst NO_KEYS: ReadonlySet<string> = new Set<string>();\n\n/**\n * Имена, которые плагин заявить не может.\n *\n * Заявленные опции ресурсы переносят в описание запроса поверх собранных полей, поэтому\n * имя из {@link RawRequestOptions} подменило бы путь, тело или заголовки любого вызова.\n */\nconst RESERVED_OPTION_KEYS: ReadonlySet<string> = new Set([\n 'signal',\n 'timeout',\n 'headers',\n 'retry',\n 'method',\n 'path',\n 'service',\n 'baseUrl',\n 'query',\n 'body',\n 'skipAuth',\n 'skipAuthRefresh',\n 'skipQueue',\n 'raw',\n]);\n\nconst RELATION_FIELDS = ['requires', 'conflicts', 'before', 'after'] as const;\nconst HOOK_FIELDS = ['onRequest', 'onResponse', 'onError', 'onRetry'] as const;\n\nfunction validateNameList(plugin: ItdPlugin, field: (typeof RELATION_FIELDS)[number]): void {\n const values = plugin[field];\n if (values === undefined) return;\n if (!Array.isArray(values)) {\n throw new ItdConfigError(`плагин «${plugin.name}»: ${field} должен быть массивом`);\n }\n\n const seen = new Set<string>();\n for (const value of values as readonly unknown[]) {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new ItdConfigError(`плагин «${plugin.name}»: ${field} содержит пустое имя плагина`);\n }\n if (value === plugin.name) {\n throw new ItdConfigError(`плагин «${plugin.name}» не может указать себя в ${field}`);\n }\n if (seen.has(value)) {\n throw new ItdConfigError(`плагин «${plugin.name}»: ${field} повторяет имя «${value}»`);\n }\n seen.add(value);\n }\n}\n\nfunction validateHooks(plugin: string, hooks: ClientHooks): void {\n if (typeof hooks !== 'object' || hooks === null) {\n throw new ItdConfigError(`плагин «${plugin}» передал в useHooks() не объект`);\n }\n for (const field of HOOK_FIELDS) {\n if (hooks[field] !== undefined && typeof hooks[field] !== 'function') {\n throw new ItdConfigError(`плагин «${plugin}»: useHooks().${field} должен быть функцией`);\n }\n }\n}\n\n/**\n * Проверяет описание плагина без его установки.\n *\n * Нужна не только {@link PluginRegistry}: контейнер аккаунтов обязан отклонять сломанный\n * плагин сразу, даже когда внутри ещё нет ни одного клиента, которому можно поручить\n * полноценную установку.\n *\n * @internal\n */\nexport function validatePluginDefinition(plugin: ItdPlugin): void {\n if (typeof plugin?.install !== 'function') {\n throw new ItdConfigError('Плагин должен быть объектом с методом install()');\n }\n\n const name = plugin.name;\n if (typeof name !== 'string' || name.trim() === '') {\n throw new ItdConfigError('У плагина должно быть непустое имя');\n }\n\n const keys = plugin.optionKeys ?? [];\n if (!Array.isArray(keys)) {\n throw new ItdConfigError(`плагин «${name}»: optionKeys должен быть массивом`);\n }\n for (const key of keys as readonly unknown[]) {\n if (typeof key !== 'string' || key.trim() === '') {\n throw new ItdConfigError(`Плагин «${name}» заявил пустое имя опции`);\n }\n if (RESERVED_OPTION_KEYS.has(key)) {\n throw new ItdConfigError(\n `Плагин «${name}» заявил опцию «${key}»: это поле запроса, имя занято. ` +\n `Занятые имена: ${[...RESERVED_OPTION_KEYS].join(', ')}`,\n );\n }\n }\n\n for (const field of RELATION_FIELDS) validateNameList(plugin, field);\n}\n\ninterface OrderedPlugin {\n plugin: ItdPlugin;\n sequence: number;\n}\n\nfunction addEdge(\n from: string,\n to: string,\n edges: Map<string, Set<string>>,\n indegree: Map<string, number>,\n): void {\n const targets = edges.get(from);\n if (!targets || targets.has(to)) return;\n targets.add(to);\n indegree.set(to, (indegree.get(to) ?? 0) + 1);\n}\n\n/**\n * Проверяет зависимости и возвращает плагины в порядке обёрток: внешний идёт раньше.\n *\n * @internal\n */\nexport function orderPluginDefinitions(plugins: readonly ItdPlugin[]): ItdPlugin[] {\n const entries: OrderedPlugin[] = [];\n const byName = new Map<string, OrderedPlugin>();\n\n for (const [sequence, plugin] of plugins.entries()) {\n validatePluginDefinition(plugin);\n if (byName.has(plugin.name)) {\n throw new ItdConfigError(`плагин «${plugin.name}» уже подключён`);\n }\n const entry = { plugin, sequence };\n entries.push(entry);\n byName.set(plugin.name, entry);\n }\n\n for (const { plugin } of entries) {\n for (const required of plugin.requires ?? []) {\n if (!byName.has(required)) {\n throw new ItdConfigError(`плагину «${plugin.name}» требуется плагин «${required}»`);\n }\n }\n\n for (const conflict of plugin.conflicts ?? []) {\n if (byName.has(conflict)) {\n throw new ItdConfigError(`плагин «${plugin.name}» несовместим с плагином «${conflict}»`);\n }\n }\n for (const { plugin: other } of entries) {\n if (other.conflicts?.includes(plugin.name)) {\n throw new ItdConfigError(`плагин «${plugin.name}» несовместим с плагином «${other.name}»`);\n }\n }\n }\n\n const edges = new Map<string, Set<string>>();\n const indegree = new Map<string, number>();\n for (const { plugin } of entries) {\n edges.set(plugin.name, new Set());\n indegree.set(plugin.name, 0);\n }\n\n for (const { plugin } of entries) {\n for (const required of plugin.requires ?? []) {\n addEdge(required, plugin.name, edges, indegree);\n }\n for (const target of plugin.before ?? []) {\n if (byName.has(target)) addEdge(plugin.name, target, edges, indegree);\n }\n for (const target of plugin.after ?? []) {\n if (byName.has(target)) addEdge(target, plugin.name, edges, indegree);\n }\n }\n\n const ready = entries.filter(({ plugin }) => indegree.get(plugin.name) === 0);\n ready.sort((a, b) => a.sequence - b.sequence);\n const ordered: ItdPlugin[] = [];\n\n while (ready.length > 0) {\n const current = ready.shift();\n if (!current) break;\n ordered.push(current.plugin);\n\n for (const target of edges.get(current.plugin.name) ?? []) {\n const next = (indegree.get(target) ?? 0) - 1;\n indegree.set(target, next);\n if (next === 0) {\n const entry = byName.get(target);\n if (entry) {\n ready.push(entry);\n ready.sort((a, b) => a.sequence - b.sequence);\n }\n }\n }\n }\n\n if (ordered.length !== entries.length) {\n const cycle = entries\n .filter(({ plugin }) => (indegree.get(plugin.name) ?? 0) > 0)\n .map(({ plugin }) => plugin.name);\n throw new ItdConfigError(`циклический порядок плагинов: ${cycle.join(' → ')}`);\n }\n\n return ordered;\n}\n\n/** Проверяет, можно ли удалить плагин, не нарушив обязательные зависимости. @internal */\nexport function assertPluginRemovable(plugins: readonly ItdPlugin[], name: string): void {\n const dependent = plugins.find((plugin) => plugin.requires?.includes(name));\n if (dependent) {\n throw new ItdConfigError(\n `нельзя отключить плагин «${name}»: от него зависит «${dependent.name}»`,\n );\n }\n}\n\ninterface InstalledPlugin {\n plugin: ItdPlugin;\n transformers: Transformer[];\n hooks: ClientHooks[];\n teardown: PluginTeardown | undefined;\n activeRequests: number;\n drain: Promise<void> | undefined;\n finishDrain: (() => void) | undefined;\n}\n\ntype HookName = keyof ClientHooks;\ntype HookContext<K extends HookName> = Parameters<NonNullable<ClientHooks[K]>>[0];\ntype HookDispatcher = <K extends HookName>(\n field: K,\n context: HookContext<K>,\n request: RawRequestOptions,\n) => Promise<void>;\n\nconst REQUEST_HOOK_DISPATCHERS = new WeakMap<ClientHooks, HookDispatcher>();\nconst PLUGIN_HOOK_SCOPE: unique symbol = Symbol('itd-api.plugin-hooks');\ntype ScopedRequest = RawRequestOptions & {\n [PLUGIN_HOOK_SCOPE]?: readonly ClientHooks[];\n};\n\n/**\n * Вызывает публичный хук, сохраняя привязанный к логическому запросу снимок плагинов.\n *\n * Обычные наборы хуков по-прежнему получают только публичный контекст. Дополнительный\n * аргумент используется исключительно внутренним составным набором PluginRegistry.\n *\n * @internal\n */\nexport async function dispatchRequestHook<K extends HookName>(\n hooks: ClientHooks,\n field: K,\n context: HookContext<K>,\n request: RawRequestOptions,\n): Promise<void> {\n const dispatcher = REQUEST_HOOK_DISPATCHERS.get(hooks);\n if (dispatcher) {\n await dispatcher(field, context, request);\n return;\n }\n\n const hook = hooks[field] as ((value: HookContext<K>) => void | Promise<void>) | undefined;\n await hook?.(context);\n}\n\n/**\n * Список подключённых плагинов и собранная из них цепочка обёрток.\n *\n * Живёт в клиенте, а работает в транспорте: {@link HttpClient} прогоняет через `run`\n * каждый запрос, если плагины есть.\n */\nexport class PluginRegistry {\n readonly #entries = new Map<string, InstalledPlugin>();\n readonly #optionKeys = new Set<string>();\n readonly #removing = new Set<string>();\n readonly #cleanups = new Set<Promise<void>>();\n #ordered: InstalledPlugin[] = [];\n\n /** Сколько плагинов подключено. */\n get size(): number {\n return this.#entries.size;\n }\n\n /** Имена опций активных плагинов. */\n get optionKeys(): ReadonlySet<string> {\n return this.#optionKeys.size === 0 ? NO_KEYS : this.#optionKeys;\n }\n\n /** Имена плагинов в фактическом порядке выполнения. */\n names(): string[] {\n return this.#ordered.map(({ plugin }) => plugin.name);\n }\n\n /** Подключён ли плагин с таким именем. */\n has(name: string): boolean {\n return this.#entries.has(name);\n }\n\n /** Проверяет добавление без вызова `install()`. @internal */\n assertCanAdd(plugin: ItdPlugin): void {\n validatePluginDefinition(plugin);\n if (this.#removing.has(plugin.name)) {\n throw new ItdConfigError(\n `плагин «${plugin.name}» ещё отключается; дождитесь завершения unuse() или dispose()`,\n );\n }\n orderPluginDefinitions([...this.#ordered.map((entry) => entry.plugin), plugin]);\n }\n\n /** Проверяет удаление без изменения реестра. @internal */\n assertCanRemove(name: string): void {\n const entry = this.#entries.get(name);\n if (!entry) return;\n assertPluginRemovable(\n this.#ordered.map((current) => current.plugin),\n name,\n );\n }\n\n /**\n * Подключает плагин.\n *\n * @throws {ItdConfigError} если плагин задан неверно, уже подключён, нарушает зависимости\n * или заявил занятое имя опции\n */\n add(plugin: ItdPlugin, context: Omit<PluginContext, 'use' | 'useHooks'>): void {\n this.assertCanAdd(plugin);\n const ordered = orderPluginDefinitions([...this.#ordered.map((entry) => entry.plugin), plugin]);\n\n const transformers: Transformer[] = [];\n const hooks: ClientHooks[] = [];\n const installed = plugin.install({\n ...context,\n use: (transformer) => {\n if (typeof transformer !== 'function') {\n throw new ItdConfigError(`плагин «${plugin.name}» передал в use() не функцию`);\n }\n transformers.push(transformer);\n },\n useHooks: (value) => {\n validateHooks(plugin.name, value);\n hooks.push({ ...value });\n },\n });\n\n // До Plugin API 2.0 возвращаемое значение игнорировалось, поэтому постороннее значение\n // остаётся допустимым ради совместимости. Функция теперь получает определённый смысл:\n // это teardown.\n const teardown = typeof installed === 'function' ? (installed as PluginTeardown) : undefined;\n\n this.#entries.set(plugin.name, {\n plugin,\n transformers,\n hooks,\n teardown,\n activeRequests: 0,\n drain: undefined,\n finishDrain: undefined,\n });\n this.#ordered = ordered.map((definition) => {\n const entry = this.#entries.get(definition.name);\n if (!entry) throw new ItdConfigError(`плагин «${definition.name}» не установлен`);\n return entry;\n });\n this.#rebuildOptionKeys();\n }\n\n /**\n * Отключает плагин и вызывает его функцию очистки.\n *\n * Новые запросы перестают видеть плагин сразу. Если его обёртка уже выполняется,\n * очистка дождётся завершения этого логического запроса.\n *\n * @returns `false`, если такого плагина не было\n */\n async remove(name: string): Promise<boolean> {\n const entry = this.#entries.get(name);\n if (!entry) return false;\n\n this.assertCanRemove(name);\n this.#entries.delete(name);\n this.#ordered = this.#ordered.filter((current) => current !== entry);\n this.#rebuildOptionKeys();\n this.#removing.add(name);\n\n const cleanup = this.#trackCleanup(\n (async () => {\n await this.#waitForDrain(entry);\n await entry.teardown?.();\n })(),\n );\n\n try {\n await cleanup;\n return true;\n } finally {\n this.#removing.delete(name);\n }\n }\n\n /**\n * Отключает все плагины окончательно.\n *\n * Очистка идёт изнутри наружу — в порядке, обратном выполнению обёрток.\n */\n async dispose(): Promise<void> {\n const entries = [...this.#ordered].reverse();\n const previousCleanups = [...this.#cleanups];\n this.#entries.clear();\n this.#ordered = [];\n this.#optionKeys.clear();\n for (const { plugin } of entries) this.#removing.add(plugin.name);\n\n const cleanup = this.#trackCleanup(\n (async () => {\n const errors: unknown[] = [];\n const previous = await Promise.allSettled(previousCleanups);\n for (const result of previous) {\n if (result.status === 'rejected') errors.push(result.reason);\n }\n\n for (const entry of entries) {\n try {\n await this.#waitForDrain(entry);\n await entry.teardown?.();\n } catch (error) {\n errors.push(error);\n } finally {\n this.#removing.delete(entry.plugin.name);\n }\n }\n if (errors.length > 0) {\n throw new AggregateError(errors, 'Не удалось освободить ресурсы плагинов');\n }\n })(),\n );\n\n await cleanup;\n }\n\n /**\n * Объединяет конструкторские хуки с хуками подключаемых плагинов.\n *\n * Возвращённый объект динамический: подключение и отключение плагина начинает действовать\n * со следующего логического запроса без пересоздания транспорта.\n */\n hooks(base: ClientHooks): ClientHooks {\n const hooks: ClientHooks = {};\n REQUEST_HOOK_DISPATCHERS.set(hooks, ((field, context, request) =>\n this.#runHook(field, context, request, base)) as HookDispatcher);\n return hooks;\n }\n\n /**\n * Прогоняет запрос через цепочку обёрток.\n *\n * Снимок цепочки берётся в начале: `unuse()` влияет на новые запросы, но не обрывает\n * уже выполняющийся посередине.\n *\n * @param execute настоящий запрос, вызывается самой внутренней обёрткой\n */\n async run(\n request: RawRequestOptions,\n execute: (request: RawRequestOptions) => Promise<unknown>,\n ): Promise<unknown> {\n const entries = [...this.#ordered];\n for (const entry of entries) entry.activeRequests += 1;\n const hookScope = entries.flatMap((entry) => entry.hooks);\n const scoped = (current: RawRequestOptions): ScopedRequest =>\n (current as ScopedRequest)[PLUGIN_HOOK_SCOPE] === hookScope\n ? (current as ScopedRequest)\n : { ...current, [PLUGIN_HOOK_SCOPE]: hookScope };\n\n const chain = entries\n .flatMap((entry) => entry.transformers)\n .reduceRight<(r: RawRequestOptions) => Promise<unknown>>(\n (next, transformer) => (current) =>\n transformer(scoped(current), (prepared) => next(scoped(prepared))),\n (current) => execute(scoped(current)),\n );\n\n try {\n return await chain(scoped(request));\n } finally {\n for (const entry of entries) {\n entry.activeRequests -= 1;\n if (entry.activeRequests === 0) {\n entry.finishDrain?.();\n entry.finishDrain = undefined;\n entry.drain = undefined;\n }\n }\n }\n }\n\n #rebuildOptionKeys(): void {\n this.#optionKeys.clear();\n for (const { plugin } of this.#ordered) {\n for (const key of plugin.optionKeys ?? []) this.#optionKeys.add(key);\n }\n }\n\n #waitForDrain(entry: InstalledPlugin): Promise<void> {\n if (entry.activeRequests === 0) return Promise.resolve();\n entry.drain ??= new Promise<void>((resolve) => {\n entry.finishDrain = resolve;\n });\n return entry.drain;\n }\n\n #trackCleanup(cleanup: Promise<void>): Promise<void> {\n this.#cleanups.add(cleanup);\n void cleanup.then(\n () => this.#cleanups.delete(cleanup),\n () => this.#cleanups.delete(cleanup),\n );\n return cleanup;\n }\n\n async #runHook<K extends HookName>(\n field: K,\n context: HookContext<K>,\n request: RawRequestOptions,\n base: ClientHooks,\n ): Promise<void> {\n const baseHook = base[field] as ((value: HookContext<K>) => void | Promise<void>) | undefined;\n await baseHook?.(context);\n\n const scope = (request as ScopedRequest)[PLUGIN_HOOK_SCOPE] ?? [];\n for (const hooks of scope) {\n const hook = hooks[field] as ((value: HookContext<K>) => void | Promise<void>) | undefined;\n await hook?.(context);\n }\n }\n}\n","import type { ResolvedRetryOptions } from './config.js';\nimport {\n ItdAbortError,\n ItdApiError,\n ItdFileError,\n ItdNetworkError,\n ItdTimeoutError,\n} from './errors.js';\n\n/**\n * Решает, повторять ли запрос.\n *\n * @returns пауза в миллисекундах перед следующей попыткой либо `undefined`, если повторять не нужно\n */\nexport type RetryScheduler = (\n error: unknown,\n attempt: number,\n method: string,\n retryNetworkWrite?: boolean,\n) => number | undefined;\n\n/**\n * Методы, повтор которых безопасен по определению.\n *\n * `DELETE` формально тоже идемпотентен, но его повтор после успеха вернёт `404`\n * и собьёт с толку — в список он не входит.\n */\nconst IDEMPOTENT_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);\n\n/**\n * Стоит ли повторять запрос после этой ошибки.\n *\n * Отдельно разобран `429`: он повторяется даже для запросов на запись, потому что\n * гарантирует, что запрос **не был обработан**. Обрыв сети и `5xx` такой гарантии не дают —\n * сервер мог успеть создать пост, — поэтому запись по умолчанию не повторяется.\n */\nfunction isRetryable(\n error: unknown,\n method: string,\n retryWrites: boolean,\n retryNetworkWrite: boolean,\n): boolean {\n // Отмену повторять нельзя ни при каких условиях: её попросил пользователь.\n if (error instanceof ItdAbortError) return false;\n\n const safeToRepeat = retryWrites || IDEMPOTENT_METHODS.has(method);\n\n if (error instanceof ItdApiError) {\n if (error.status === 429) return true;\n if (error.status >= 500) return safeToRepeat;\n return false;\n }\n\n if (error instanceof ItdNetworkError || error instanceof ItdTimeoutError) {\n return safeToRepeat || retryNetworkWrite;\n }\n\n if (error instanceof ItdFileError) {\n return error.retryable && (safeToRepeat || retryNetworkWrite);\n }\n\n return false;\n}\n\n/** Экспоненциальная пауза со случайным разбросом. */\nfunction backoffDelay(\n attempt: number,\n options: ResolvedRetryOptions,\n random: () => number,\n): number {\n const exponential = options.baseDelay * 2 ** (attempt - 1);\n const capped = Math.min(exponential, options.maxDelay);\n const spread = capped * options.jitter * (random() * 2 - 1);\n\n return Math.max(0, Math.round(capped + spread));\n}\n\n/**\n * Собирает планировщик повторов для транспорта.\n *\n * Поведение при `Retry-After`: пауза, названная сервером, соблюдается точно — она\n * авторитетнее нашего расчёта. Но если сервер просит ждать дольше, чем `maxDelay`,\n * повтор **не выполняется вовсе**: молча спать десять минут внутри вызова библиотека\n * не должна, лучше отдать {@link ItdRateLimitError} и дать решить вызывающему коду.\n *\n * @param options настройки повторов после подстановки значений по умолчанию\n * @param random источник случайности; подменяется в тестах ради предсказуемости\n *\n * @example\n * ```ts\n * const scheduler = createRetryScheduler(config.retry);\n * const delay = scheduler(error, 1, 'GET'); // 500 мс ± 30%\n * ```\n */\nexport function createRetryScheduler(\n options: ResolvedRetryOptions,\n random: () => number = Math.random,\n): RetryScheduler {\n return (error, attempt, method, retryNetworkWrite = false) => {\n if (attempt >= options.attempts) return undefined;\n\n if (options.shouldRetry) {\n return options.shouldRetry(error, attempt)\n ? backoffDelay(attempt, options, random)\n : undefined;\n }\n\n if (!isRetryable(error, method, options.retryWrites, retryNetworkWrite)) return undefined;\n\n if (error instanceof ItdApiError && error.retryAfter !== undefined) {\n return error.retryAfter > options.maxDelay ? undefined : error.retryAfter;\n }\n\n return backoffDelay(attempt, options, random);\n };\n}\n","import type { ClientHooks, Logger, RawRequestOptions, RequestOptions } from '../types/options.js';\nimport { type ResolvedRetryOptions, resolveRetry } from './config.js';\nimport { ItdAbortError, isItdApiError, isItdRateLimitError } from './errors.js';\nimport {\n type PipelineRequest,\n type RequestHandler,\n type RequestMiddleware,\n withLayerHeaders,\n} from './pipeline.js';\nimport { dispatchRequestHook, type PluginRegistry } from './plugins.js';\nimport { createRetryScheduler, type RetryScheduler } from './retry.js';\nimport type { ServiceRegistry } from './services.js';\nimport { normalizeBaseUrl } from './url.js';\n\n/** Ожидание повтора, которое уважает отмену запроса. */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));\n if (signal.aborted) {\n return Promise.reject(new ItdAbortError('Запрос отменён во время ожидания повтора'));\n }\n\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(new ItdAbortError('Запрос отменён во время ожидания повтора'));\n };\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/**\n * Слой очереди: ограничение конкурентности и частоты.\n *\n * `skipQueue` пропускает запрос мимо очереди — так поступают служебные запросы, которые\n * порождены изнутри другого запроса и не могут ждать освободившегося слота.\n *\n * Очередь выбирается по запросу: у каждого сервиса платформы свой хост и свой лимит.\n */\nexport function createQueueMiddleware(\n schedule: <T>(request: PipelineRequest, task: () => Promise<T>) => Promise<T>,\n): RequestMiddleware {\n return (request, next) =>\n request.skipQueue ? next(request) : schedule(request, () => next(request));\n}\n\n/**\n * Слой плагинов.\n *\n * Стоит снаружи повторов и внутри очереди: плагин должен увидеть запрос и ответ по одному\n * разу, независимо от числа попыток, — иначе, например, текст поста зашифруется дважды.\n */\nexport function createPluginsMiddleware(plugins: PluginRegistry): RequestMiddleware {\n return (request, next) =>\n plugins.run(request, next as (request: RawRequestOptions) => Promise<unknown>);\n}\n\n/**\n * Слой сервисов.\n *\n * Запросу с полем `service` подставляет хост сервиса, его заголовки и `skipAuth`, если\n * сервис объявлен публичным. Заданный у запроса `baseUrl` не трогает.\n *\n * Стоит снаружи повторов и авторизации, чтобы выставленный здесь `skipAuth` был ей виден.\n */\nexport function createServicesMiddleware(registry: ServiceRegistry): RequestMiddleware {\n return async (request, next) => {\n const service = request.service === undefined ? undefined : registry.require(request.service);\n let prepared = request;\n\n if (request.baseUrl !== undefined) {\n const baseUrl = normalizeBaseUrl(request.baseUrl);\n if (baseUrl !== request.baseUrl) prepared = { ...prepared, baseUrl };\n\n // Разовый хост не наследует разрешение авторизации от сервиса с другим URL.\n // Явное `skipAuth: false` остаётся способом осознанно отправить токен наружу.\n const matchesService = service?.baseUrl === baseUrl;\n const mayAuthorize = matchesService\n ? service.auth !== false\n : registry.isPrimarySite(baseUrl);\n if (!mayAuthorize && prepared.skipAuth === undefined) {\n prepared = { ...prepared, skipAuth: true };\n }\n }\n\n if (!service) return next(prepared);\n\n if (prepared.baseUrl === undefined) prepared = { ...prepared, baseUrl: service.baseUrl };\n\n if (service.headers) prepared = withLayerHeaders(prepared, service.headers);\n if (service.auth === false && prepared.skipAuth === undefined) {\n prepared = { ...prepared, skipAuth: true };\n }\n\n return next(prepared);\n };\n}\n\n/** Что нужно слою авторизации. */\nexport interface AuthMiddlewareDeps {\n /** Заголовки авторизации для очередного запроса. Пустой объект, если токена нет. */\n getAuthHeaders: () => Promise<Record<string, string>> | Record<string, string>;\n /** Реакция на `401`. Возвращает `true`, если токен обновлён и повтор имеет смысл. */\n onUnauthorized: () => Promise<boolean>;\n /** Обновлять ли токен при `401` автоматически. */\n autoRefresh: boolean;\n}\n\nasync function applyAuth(\n request: PipelineRequest,\n deps: AuthMiddlewareDeps,\n): Promise<PipelineRequest> {\n if (request.skipAuth) return request;\n\n const headers = await deps.getAuthHeaders();\n return Object.keys(headers).length > 0 ? withLayerHeaders(request, headers) : request;\n}\n\n/**\n * Слой авторизации.\n *\n * Подставляет заголовок `Authorization` и обрабатывает `401`: обновляет токен и повторяет\n * запрос ровно один раз. Стоит внутри повторов, поэтому обычным попыткам он не виден —\n * они уже работают со свежим токеном.\n */\nexport function createAuthMiddleware(deps: AuthMiddlewareDeps): RequestMiddleware {\n return async (request, next) => {\n const authorized = await applyAuth(request, deps);\n\n try {\n return await next(authorized);\n } catch (error) {\n // Обновляем и повторяем ровно один раз, чтобы не зациклиться, если сервер\n // отдаёт 401 и на свежем токене.\n if (\n request.skipAuthRefresh ||\n !deps.autoRefresh ||\n !isItdApiError(error) ||\n error.status !== 401\n ) {\n throw error;\n }\n\n const refreshed = await deps.onUnauthorized();\n if (!refreshed) throw error;\n\n const retried = await applyAuth({ ...request, skipAuthRefresh: true }, deps);\n return next(retried);\n }\n };\n}\n\n/** Что нужно слою повторов. */\nexport interface RetryMiddlewareDeps {\n /** Глобальные настройки повторов. `undefined` — по умолчанию не повторять. */\n retry: ResolvedRetryOptions | undefined;\n /**\n * Паузы перед повторами при ответе `429`.\n *\n * Живут отдельно от `retry`: сервер не присылает `Retry-After`, и экспоненциальный откат\n * тут бесполезен — окно измеряется десятками секунд. Не зависят от `retry.attempts`.\n */\n rateLimitDelays: readonly number[];\n /**\n * Придерживает очередь запроса на паузу `429`. `undefined`, если очереди нет.\n *\n * Тормозится очередь того хоста, который ответил отказом: лимит у каждого свой.\n */\n pauseQueue: ((ms: number, request: PipelineRequest) => void) | undefined;\n hooks: ClientHooks;\n logger: Logger | undefined;\n buildUrl: (request: PipelineRequest) => string;\n}\n\n/**\n * Выбирает планировщик отката для конкретного запроса.\n *\n * `retry` у запроса переопределяет глобальную настройку: `false` выключает повторы,\n * объект задаёт свои. Обработка `429` от этого не зависит — она общая.\n */\nfunction resolveBackoff(\n retry: RequestOptions['retry'],\n global: RetryScheduler | undefined,\n): RetryScheduler | undefined {\n if (retry === undefined) return global;\n if (retry === false) return undefined;\n\n const resolved = resolveRetry(retry);\n return resolved ? createRetryScheduler(resolved) : undefined;\n}\n\n/**\n * Слой повторов.\n *\n * Ответ `429` обрабатывается отдельно от прочих ошибок лестницей пауз и с придержанием\n * всей очереди; сетевые сбои и `5xx` — экспоненциальным откатом. Настройка `retry`\n * у отдельного запроса имеет приоритет над глобальной.\n */\nexport function createRetryMiddleware(deps: RetryMiddlewareDeps): RequestMiddleware {\n const globalScheduler = deps.retry ? createRetryScheduler(deps.retry) : undefined;\n\n const nextDelay = (\n error: unknown,\n attempt: number,\n request: PipelineRequest,\n method: string,\n backoff: RetryScheduler | undefined,\n ): number | undefined => {\n if (isItdRateLimitError(error)) {\n // Пауза, названную сервером, соблюдаем точно; иначе берём очередной шаг лестницы.\n const wait = error.retryAfter ?? deps.rateLimitDelays[attempt - 1];\n if (wait === undefined) return undefined;\n\n deps.pauseQueue?.(wait, request);\n deps.logger?.debug(`лимит частоты, попытка ${attempt + 1} через ${wait} мс`);\n return wait;\n }\n\n return backoff?.(error, attempt, method, request.retryNetworkWrite ?? false);\n };\n\n return async (request, next) => {\n const method = request.method.toUpperCase();\n const backoff = resolveBackoff(request.retry, globalScheduler);\n\n for (let attempt = 1; ; attempt++) {\n try {\n return await next({ ...request, attempt });\n } catch (error) {\n const delay = nextDelay(error, attempt, request, method, backoff);\n if (delay === undefined) throw error;\n\n await dispatchRequestHook(\n deps.hooks,\n 'onRetry',\n {\n method,\n path: request.path,\n url: deps.buildUrl(request),\n // Умолчания транспорта добавляются после слоя повторов и сюда не входят.\n headers: new Headers({ ...request.layerHeaders, ...request.headers }),\n attempt,\n error,\n delay,\n },\n request,\n );\n\n deps.logger?.debug(\n `повтор ${method} ${request.path}, попытка ${attempt + 1} через ${delay} мс`,\n );\n\n await sleep(delay, request.signal);\n }\n }\n };\n}\n\n/** Собирает обработчик из слоёв. Реэкспорт для удобства сборки в одном месте. */\nexport { composePipeline } from './pipeline.js';\nexport type { RequestHandler, RequestMiddleware };\n","import type { ResolvedRateLimitOptions } from './config.js';\nimport { ItdAbortError } from './errors.js';\n\n/** Задача, ожидающая своей очереди. */\ninterface QueuedTask {\n run: () => void;\n /** Снимает задачу, ещё не начавшую выполняться, — используется при остановке очереди. */\n cancel: (reason: unknown) => void;\n}\n\n/** Ошибка отмены запроса, который ещё не дошёл до транспорта. */\nfunction queueAbortError(): ItdAbortError {\n return new ItdAbortError('Запрос отменён во время ожидания очереди');\n}\n\n/**\n * Очередь запросов: ограничивает одновременность и частоту.\n *\n * Нужна прежде всего ботам: без неё цикл по сотне постов уходит в API одним залпом\n * и упирается в `RATE_LIMIT_EXCEEDED`.\n *\n * Частота выдерживается равномерным разносом стартов (`1000 / rps` между запросами),\n * а не окном со счётчиком: так нагрузка ровная, без всплеска в начале каждой секунды.\n *\n * @internal\n */\nexport class RequestQueue {\n readonly #concurrency: number;\n /** Минимальный промежуток между стартами, мс. `0` — без ограничения частоты. */\n readonly #minGap: number;\n\n readonly #waiting: QueuedTask[] = [];\n #active = 0;\n /** Момент, раньше которого следующий запрос стартовать не должен. */\n #nextSlot = 0;\n #timer: ReturnType<typeof setTimeout> | undefined;\n\n constructor(options: ResolvedRateLimitOptions) {\n this.#concurrency = options.concurrency;\n this.#minGap = options.rps ? 1000 / options.rps : 0;\n }\n\n /** Сколько задач выполняется прямо сейчас. */\n get active(): number {\n return this.#active;\n }\n\n /** Сколько задач ждёт очереди. */\n get pending(): number {\n return this.#waiting.length;\n }\n\n /**\n * Ставит задачу в очередь.\n *\n * @returns результат задачи; ошибка задачи пробрасывается без изменений\n */\n schedule<T>(task: () => Promise<T>, signal?: AbortSignal): Promise<T> {\n if (signal?.aborted) return Promise.reject(queueAbortError());\n\n return new Promise<T>((resolve, reject) => {\n let queued: QueuedTask;\n\n const detach = () => signal?.removeEventListener('abort', onAbort);\n const onAbort = () => {\n const index = this.#waiting.indexOf(queued);\n if (index < 0) return;\n\n this.#waiting.splice(index, 1);\n queued.cancel(queueAbortError());\n this.#drain();\n };\n\n queued = {\n run: () => {\n detach();\n this.#active += 1;\n\n // `task` обычно возвращает промис, но пользовательский middleware может бросить\n // синхронно. Нормализуем оба пути, чтобы слот всегда освободился.\n Promise.resolve()\n .then(task)\n .then(resolve, reject)\n .finally(() => {\n this.#active -= 1;\n this.#drain();\n });\n },\n cancel: (reason) => {\n detach();\n reject(reason);\n },\n };\n\n this.#waiting.push(queued);\n signal?.addEventListener('abort', onAbort, { once: true });\n\n // Защищает и от нестандартной реализации AbortSignal, которая могла перейти\n // в aborted между первой проверкой и установкой обработчика.\n if (signal?.aborted) onAbort();\n else this.#drain();\n });\n }\n\n /**\n * Останавливает очередь: снимает отложенную паузу и отклоняет ещё не начатые задачи\n * ошибкой `ItdAbortError`. Уже выполняющиеся задачи доводятся до конца.\n */\n stop(): void {\n if (this.#timer !== undefined) {\n clearTimeout(this.#timer);\n this.#timer = undefined;\n }\n this.#nextSlot = 0;\n\n const pending = this.#waiting.splice(0, this.#waiting.length);\n for (const task of pending) {\n task.cancel(new ItdAbortError('Клиент закрыт, запрос отменён'));\n }\n }\n\n /**\n * Придерживает всю очередь на заданное время.\n *\n * Вызывается при получении `429` с заголовком `Retry-After`: тормозить нужно все запросы,\n * а не только тот, который наткнулся на лимит, — иначе остальные продолжат добивать API.\n */\n pause(ms: number): void {\n if (ms <= 0) return;\n this.#nextSlot = Math.max(this.#nextSlot, Date.now() + ms);\n }\n\n /** Запускает столько ожидающих задач, сколько позволяют ограничения. */\n #drain(): void {\n if (this.#waiting.length === 0) {\n // Последний ожидающий запрос мог быть отменён во время длинной паузы. Таймер больше\n // не нужен и не должен удерживать event loop процесса.\n if (this.#timer !== undefined) {\n clearTimeout(this.#timer);\n this.#timer = undefined;\n }\n return;\n }\n if (this.#active >= this.#concurrency) return;\n // Ждём уже запланированного пробуждения, чтобы не плодить таймеры.\n if (this.#timer !== undefined) return;\n\n const now = Date.now();\n\n if (this.#nextSlot > now) {\n this.#timer = setTimeout(() => {\n this.#timer = undefined;\n this.#drain();\n }, this.#nextSlot - now);\n return;\n }\n\n const next = this.#waiting.shift();\n if (!next) return;\n\n if (this.#minGap > 0) this.#nextSlot = now + this.#minGap;\n\n next.run();\n\n // Следующая задача может стартовать сразу, если позволяет конкурентность.\n this.#drain();\n }\n}\n\n/**\n * Очереди по хостам: основная и по одной на каждый сервис платформы.\n *\n * @internal\n */\nexport class RequestQueuePool {\n readonly #options: ResolvedRateLimitOptions;\n readonly #main: RequestQueue;\n /** Очереди сервисов заводятся при первом запросе — обычно не нужна ни одна. */\n readonly #byService = new Map<string, RequestQueue>();\n\n constructor(options: ResolvedRateLimitOptions) {\n this.#options = options;\n this.#main = new RequestQueue(options);\n }\n\n /** Очередь хоста. */\n for(service: string | undefined): RequestQueue {\n if (service === undefined) return this.#main;\n\n let queue = this.#byService.get(service);\n if (!queue) {\n queue = new RequestQueue(this.#options);\n this.#byService.set(service, queue);\n }\n return queue;\n }\n\n /** Останавливает все очереди. */\n stop(): void {\n this.#main.stop();\n for (const queue of this.#byService.values()) queue.stop();\n }\n}\n","import { ItdConfigError } from './errors.js';\nimport { normalizeBaseUrl } from './url.js';\n\n/** Сервис платформы на отдельном домене. */\nexport interface ServiceDefinition {\n /** Имя, по которому запрос выбирает сервис: `{ service: 'status' }`. */\n name: string;\n /** Базовый URL сервиса. */\n baseUrl: string;\n /** Заголовки, добавляемые к каждому запросу сервиса. Заголовки вызова важнее. */\n headers?: Record<string, string> | undefined;\n /**\n * Слать ли заголовок авторизации.\n *\n * По умолчанию включено для основного хоста и его поддоменов. Для остальных хостов\n * авторизацию нужно разрешить явно: `auth: true`.\n */\n auth?: boolean | undefined;\n}\n\n/** Хост из URL. Пустая строка, если разобрать не удалось. */\nfunction hostOf(url: string): string {\n try {\n return new URL(url).hostname.toLowerCase();\n } catch {\n return '';\n }\n}\n\n/** Тот же хост либо его поддомен. */\nfunction isSameSite(primaryHost: string, host: string): boolean {\n if (!primaryHost || !host) return false;\n return host === primaryHost || host.endsWith(`.${primaryHost}`);\n}\n\n/**\n * Именованные сервисы клиента.\n *\n * @internal\n */\nexport class ServiceRegistry {\n readonly #services = new Map<string, ServiceDefinition>();\n /** Хост основного API. */\n readonly #primaryHost: string;\n\n /** @param primaryBaseUrl базовый URL клиента */\n constructor(primaryBaseUrl?: string) {\n this.#primaryHost = primaryBaseUrl ? hostOf(primaryBaseUrl) : '';\n }\n\n /**\n * Регистрирует сервис. Имя очищается от краевых пробелов, базовый URL приводится\n * к каноничному виду, а незаданный `auth` выводится из хоста.\n *\n * @throws {ItdConfigError} если имя пустое, имя занято или `baseUrl` не абсолютный URL\n */\n define(definition: ServiceDefinition): void {\n const raw = definition?.name;\n if (typeof raw !== 'string' || raw.trim() === '') {\n throw new ItdConfigError('У сервиса должно быть непустое имя');\n }\n\n const name = raw.trim();\n if (this.#services.has(name)) {\n throw new ItdConfigError(`Сервис «${name}» уже зарегистрирован и не может быть заменён`);\n }\n\n const baseUrl = normalizeBaseUrl(definition.baseUrl);\n if (definition.auth !== undefined && typeof definition.auth !== 'boolean') {\n throw new ItdConfigError(`services.${name}.auth должен быть boolean`);\n }\n\n let headers: Readonly<Record<string, string>> | undefined;\n if (definition.headers !== undefined) {\n if (\n typeof definition.headers !== 'object' ||\n definition.headers === null ||\n Array.isArray(definition.headers)\n ) {\n throw new ItdConfigError(`services.${name}.headers должен быть объектом строк`);\n }\n for (const [header, value] of Object.entries(definition.headers)) {\n if (typeof value !== 'string') {\n throw new ItdConfigError(`services.${name}.headers.${header} должен быть строкой`);\n }\n }\n headers = Object.freeze({ ...definition.headers });\n }\n\n const normalized = {\n ...definition,\n name,\n baseUrl,\n auth: definition.auth ?? isSameSite(this.#primaryHost, hostOf(baseUrl)),\n ...(headers ? { headers } : {}),\n } satisfies ServiceDefinition;\n\n this.#services.set(name, Object.freeze(normalized));\n }\n\n /** Определение сервиса либо `undefined`, если такого нет. */\n get(name: string): ServiceDefinition | undefined {\n return this.#services.get(name);\n }\n\n /** Зарегистрирован ли сервис с таким именем. */\n has(name: string): boolean {\n return this.#services.has(name);\n }\n\n /**\n * Определение сервиса.\n *\n * @throws {ItdConfigError} если сервис не зарегистрирован\n */\n require(name: string): ServiceDefinition {\n const service = this.#services.get(name);\n if (!service) {\n const known = [...this.#services.keys()];\n throw new ItdConfigError(\n `Сервис «${name}» не зарегистрирован. ` +\n (known.length > 0\n ? `Известны: ${known.join(', ')}`\n : 'Зарегистрируйте его через itd.defineService() или опцию services'),\n );\n }\n return service;\n }\n\n /**\n * Базовый URL сервиса.\n *\n * @throws {ItdConfigError} если сервис не зарегистрирован\n */\n resolveBaseUrl(name: string): string {\n return this.require(name).baseUrl;\n }\n\n /**\n * Принадлежит ли URL основному хосту клиента или его поддомену.\n *\n * Используется для безопасного значения по умолчанию у разового `baseUrl`: Bearer-токен\n * не должен уходить на посторонний хост без явного `skipAuth: false`.\n *\n * @internal\n */\n isPrimarySite(baseUrl: string): boolean {\n return isSameSite(this.#primaryHost, hostOf(baseUrl));\n }\n}\n","import { isBlob } from './runtime.js';\n\n/** Заголовки, значение которых нельзя писать в лог целиком. */\nconst SECRET_HEADERS = new Set(['authorization', 'cookie', 'set-cookie', 'x-api-key']);\n\n/** Поля тела запроса, значение которых нельзя писать в лог. */\nconst SECRET_FIELDS = new Set([\n 'password',\n 'oldpassword',\n 'newpassword',\n 'accesstoken',\n 'refreshtoken',\n 'currentpassword',\n 'flowtoken',\n 'token',\n 'turnstiletoken',\n 'otp',\n]);\n\n/**\n * Прячет середину секрета, оставляя концы для сопоставления.\n *\n * @example\n * ```ts\n * maskSecret('eyJhbGciOiJIUzI1NiJ9.abc'); // 'eyJh…(24)…abc'\n * ```\n */\nexport function maskSecret(value: string): string {\n if (value.length <= 8) return '…';\n return `${value.slice(0, 4)}…(${value.length})…${value.slice(-3)}`;\n}\n\n/**\n * Готовит заголовки к записи в лог.\n *\n * `Authorization` и `Cookie` маскируются: логи часто уезжают в системы сбора,\n * и токен доступа в них попадать не должен.\n */\nexport function redactHeaders(headers: Headers): Record<string, string> {\n const result: Record<string, string> = {};\n\n headers.forEach((value, name) => {\n if (SECRET_HEADERS.has(name.toLowerCase())) {\n // У Authorization маскируем только сам токен, схему («Bearer») оставляем видимой.\n const spaceAt = value.indexOf(' ');\n result[name] =\n spaceAt > 0\n ? `${value.slice(0, spaceAt)} ${maskSecret(value.slice(spaceAt + 1))}`\n : maskSecret(value);\n return;\n }\n result[name] = value;\n });\n\n return result;\n}\n\n/**\n * Готовит тело запроса к записи в лог: пароли, токены и коды OTP заменяются заглушкой.\n *\n * Обходит вложенные объекты и массивы. `FormData` и бинарные тела не раскрываются вовсе.\n */\nexport function redactBody(body: unknown): unknown {\n if (body === null || body === undefined) return body;\n\n if (typeof FormData !== 'undefined' && body instanceof FormData) return '[FormData]';\n if (isBlob(body)) return '[Blob]';\n if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return '[binary]';\n\n if (Array.isArray(body)) return body.map(redactBody);\n\n if (typeof body === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(body)) {\n result[key] = SECRET_FIELDS.has(key.toLowerCase()) ? '[скрыто]' : redactBody(value);\n }\n return result;\n }\n\n return body;\n}\n","/**\n * Снимает внешнюю обёртку `{ data: … }`.\n *\n * Это единственное преобразование ответа, которое делает транспортный слой. Формы ответов у\n * итд.com непоследовательны: одни эндпоинты заворачивают полезную нагрузку в `data`, другие\n * отдают её напрямую (`GET /api/notifications/` → `{ notifications, hasMore }`), третьи кладут\n * список в поле по имени сущности. Угадывать имя поля опасно, поэтому его разбирает каждый\n * метод ресурса явно, а здесь снимается только однозначная обёртка.\n *\n * Обёртка считается обёрткой, лишь когда `data` — **единственный** ключ объекта. Ответ\n * `{ data: …, meta: … }` вернётся как есть.\n *\n * @example\n * ```ts\n * unwrapData({ data: { posts: [] } }); // { posts: [] }\n * unwrapData({ notifications: [], hasMore }); // без изменений\n * unwrapData({ data: [], hasMore: true }); // без изменений — ключей больше одного\n * ```\n */\nexport function unwrapData(body: unknown): unknown {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return body;\n\n const keys = Object.keys(body);\n if (keys.length !== 1 || keys[0] !== 'data') return body;\n\n return (body as { data: unknown }).data;\n}\n\n/**\n * Обычный объект — не `null`, не массив.\n *\n * Живёт здесь, а не в каждом разборщике ответа: одна и та же проверка нужна и фабрике\n * ошибок, и приведению уведомлений.\n */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Непустая строка либо `undefined`. Отличается от {@link pickString} тем, что берёт значение, а не поле. */\nexport function asString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/**\n * Достаёт поле-список из ответа, уже прошедшего {@link unwrapData}.\n *\n * Если поля нет или оно не массив, возвращается пустой массив: сервер иногда опускает\n * пустые коллекции, и падать из-за этого библиотека не должна.\n */\nexport function pickArray<T>(source: unknown, field: string): T[] {\n if (typeof source !== 'object' || source === null) return [];\n const value = (source as Record<string, unknown>)[field];\n return Array.isArray(value) ? (value as T[]) : [];\n}\n\n/** Достаёт объект по имени поля. Возвращает `undefined`, если это не объект. */\nexport function pickObject(source: unknown, field: string): Record<string, unknown> | undefined {\n if (typeof source !== 'object' || source === null) return undefined;\n const value = (source as Record<string, unknown>)[field];\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n return value as Record<string, unknown>;\n}\n\n/** Достаёт булево поле. Возвращает `fallback`, если поля нет или тип не тот. */\nexport function pickBoolean(source: unknown, field: string, fallback = false): boolean {\n if (typeof source !== 'object' || source === null) return fallback;\n const value = (source as Record<string, unknown>)[field];\n return typeof value === 'boolean' ? value : fallback;\n}\n\n/** Достаёт числовое поле. Возвращает `fallback`, если поля нет или значение не конечное число. */\nexport function pickNumber(source: unknown, field: string, fallback: number): number {\n if (typeof source !== 'object' || source === null) return fallback;\n const value = (source as Record<string, unknown>)[field];\n return typeof value === 'number' && Number.isFinite(value) ? value : fallback;\n}\n\n/** Достаёт строковое поле. Возвращает `undefined`, если поля нет, оно пустое или не строка. */\nexport function pickString(source: unknown, field: string): string | undefined {\n if (typeof source !== 'object' || source === null) return undefined;\n const value = (source as Record<string, unknown>)[field];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n","import type { ItdErrorCode } from '../types/enums.js';\nimport {\n type ItdApiError,\n ItdApiError as ItdApiErrorClass,\n ItdAuthError,\n ItdConflictError,\n type ItdFieldErrors,\n ItdForbiddenError,\n ItdNotFoundError,\n ItdPhoneVerificationError,\n ItdRateLimitError,\n ItdServerError,\n ItdValidationError,\n} from './errors.js';\nimport { redactBody } from './redact.js';\nimport { asString, isRecord } from './unwrap.js';\n\n/** Разобранное тело ошибки, приведённое к одной форме. */\nexport interface ParsedErrorBody {\n code: ItdErrorCode;\n message: string;\n detail: string | undefined;\n title: string | undefined;\n fieldErrors: ItdFieldErrors;\n /** Идентификатор пользователя, если сервер его вернул (нужен для ссылки подтверждения телефона). */\n userId: string | undefined;\n}\n\n/**\n * Сводит ошибки по полям к единой форме `{ поле: [сообщения] }`.\n *\n * Понимает две документированные формы:\n * - `errors: { email: ['…'] }` либо `errors: { email: '…' }`\n * - `violations: [{ field: 'email', message: '…' }]`\n */\nfunction collectFieldErrors(source: Record<string, unknown>): ItdFieldErrors {\n const result: ItdFieldErrors = {};\n\n const errors = source.errors;\n if (isRecord(errors)) {\n for (const [field, value] of Object.entries(errors)) {\n if (Array.isArray(value)) {\n const messages = value.filter((item): item is string => typeof item === 'string');\n if (messages.length > 0) result[field] = messages;\n } else if (typeof value === 'string') {\n result[field] = [value];\n }\n }\n }\n\n const violations = source.violations;\n if (Array.isArray(violations)) {\n for (const violation of violations) {\n if (!isRecord(violation)) continue;\n const field = asString(violation.field) ?? asString(violation.property);\n const message = asString(violation.message);\n if (!field || !message) continue;\n const existing = result[field];\n if (existing) existing.push(message);\n else result[field] = [message];\n }\n }\n\n return result;\n}\n\n/**\n * Разбирает тело ответа с ошибкой.\n *\n * API отдаёт ошибки в двух формах — с обёрткой `{ error: … }` и без неё, — а при сбое на\n * уровне прокси может вернуть вообще не JSON. Все три случая сводятся к одной структуре.\n *\n * @param body тело ответа: разобранный JSON, строка или `undefined`\n * @param status HTTP-статус, используется для сообщения по умолчанию\n * @param statusText текст статуса из ответа\n */\nexport function parseErrorBody(body: unknown, status: number, statusText = ''): ParsedErrorBody {\n const fallbackMessage = statusText ? `HTTP ${status} ${statusText}` : `HTTP ${status}`;\n\n // Не-JSON: HTML от прокси, простой текст, пустое тело.\n if (typeof body === 'string') {\n return {\n code: 'UNKNOWN_ERROR',\n message: asString(body.trim()) ?? fallbackMessage,\n detail: undefined,\n title: undefined,\n fieldErrors: {},\n userId: undefined,\n };\n }\n\n if (!isRecord(body)) {\n return {\n code: 'UNKNOWN_ERROR',\n message: fallbackMessage,\n detail: undefined,\n title: undefined,\n fieldErrors: {},\n userId: undefined,\n };\n }\n\n // Ответ вида `{ type: \"validation\", on: \"query\", found: {…} }` — так сервер сообщает\n // о непрошедшей проверке параметров. Ни кода, ни текста в нём нет.\n if (body.type === 'validation') {\n const target = asString(body.on);\n\n return {\n code: 'VALIDATION_ERROR',\n message: target\n ? `Проверка не пройдена: некорректные данные в «${target}»`\n : 'Проверка входных данных не пройдена',\n detail: undefined,\n title: undefined,\n fieldErrors: {},\n userId: undefined,\n };\n }\n\n // Форма `{ error: { code, message, … } }` — работаем с вложенным объектом,\n // но поля верхнего уровня остаются запасным источником.\n const inner = isRecord(body.error) ? body.error : body;\n\n const message =\n asString(inner.message) ??\n asString(inner.detail) ??\n asString(inner.title) ??\n // `{ \"error\": \"Invalid token\" }` — так отвечает сервер на недействительный токен.\n asString(body.error) ??\n fallbackMessage;\n\n return {\n code: (asString(inner.code) ?? asString(body.code) ?? 'UNKNOWN_ERROR') as ItdErrorCode,\n message,\n detail: asString(inner.detail),\n title: asString(inner.title),\n fieldErrors: { ...collectFieldErrors(body), ...collectFieldErrors(inner) },\n userId: asString(inner.userId) ?? asString(body.userId),\n };\n}\n\n/**\n * Переводит заголовок `Retry-After` в миллисекунды.\n *\n * Поддерживает обе формы из спецификации HTTP: число секунд и дату.\n * Возвращает `undefined`, если заголовка нет или он не разбирается.\n */\nexport function parseRetryAfter(\n header: string | null | undefined,\n now = Date.now(),\n): number | undefined {\n if (!header) return undefined;\n\n const seconds = Number(header);\n if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);\n\n const date = Date.parse(header);\n if (Number.isFinite(date)) return Math.max(0, date - now);\n\n return undefined;\n}\n\n/** Читает целое число из заголовка. */\nfunction readIntHeader(headers: Headers | undefined, name: string): number | undefined {\n const raw = headers?.get(name);\n if (raw === null || raw === undefined) return undefined;\n\n const value = Number.parseInt(raw, 10);\n return Number.isFinite(value) ? value : undefined;\n}\n\n/**\n * Читает сведения об ограничении частоты.\n *\n * Сервер сообщает размер окна и остаток, но **не сообщает время сброса** — поэтому\n * точный момент, когда можно повторить, из ответа не вывести.\n */\nexport function readRateLimit(headers: Headers | undefined): {\n limit: number | undefined;\n remaining: number | undefined;\n} {\n return {\n limit: readIntHeader(headers, 'x-ratelimit-limit'),\n remaining: readIntHeader(headers, 'x-ratelimit-remaining'),\n };\n}\n\nconst REQUEST_ID_HEADERS = ['x-request-id', 'x-requestid', 'request-id', 'x-correlation-id'];\n\n/** Достаёт идентификатор запроса из заголовков ответа — пригодится при обращении в поддержку. */\nexport function getRequestId(headers: Headers | undefined): string | undefined {\n if (!headers) return undefined;\n for (const name of REQUEST_ID_HEADERS) {\n const value = headers.get(name);\n if (value) return value;\n }\n return undefined;\n}\n\n/** Соответствие кода ошибки конкретному классу. Приоритетнее, чем HTTP-статус. */\nconst CODE_TO_CLASS: Record<string, new (init: never) => ItdApiError> = {\n VALIDATION_ERROR: ItdValidationError,\n RATE_LIMIT_EXCEEDED: ItdRateLimitError,\n UNAUTHORIZED: ItdAuthError,\n SESSION_EXPIRED: ItdAuthError,\n SESSION_REVOKED: ItdAuthError,\n SESSION_INVALID_REFRESH_TOKEN: ItdAuthError,\n // Оба приходят с `/auth/refresh`: первый — когда cookie refresh_token не долетела,\n // второй — когда она есть, но сессия за ней уже мертва.\n REFRESH_TOKEN_MISSING: ItdAuthError,\n SESSION_NOT_FOUND: ItdAuthError,\n ACCOUNT_INVALID_CREDENTIALS: ItdAuthError,\n ACCESS_DENIED: ItdForbiddenError,\n ENTITY_NOT_FOUND: ItdNotFoundError,\n // Сервер отвечает именно так: `{ error: { code: 'NOT_FOUND', message: 'Post not found' } }`.\n NOT_FOUND: ItdNotFoundError,\n ENTITY_ALREADY_EXISTS: ItdConflictError,\n};\n\n/** Соответствие HTTP-статуса классу — запасной вариант, когда код ничего не говорит. */\nfunction classByStatus(status: number): new (init: never) => ItdApiError {\n if (status === 401) return ItdAuthError;\n if (status === 403) return ItdForbiddenError;\n if (status === 404) return ItdNotFoundError;\n if (status === 409) return ItdConflictError;\n if (status === 422) return ItdValidationError;\n if (status === 429) return ItdRateLimitError;\n if (status >= 500) return ItdServerError;\n return ItdApiErrorClass;\n}\n\n/**\n * Готовит тело ответа к сохранению в `ItdApiError.raw`.\n *\n * При `422` сервер возвращает присланное тело эхом: `{ type: 'validation', on: 'body',\n * found: { email: '…', password: '…' } }`. Ошибка обычно доезжает до логов и систем сбора\n * вроде Sentry — пароль в ней оказаться не должен. Имена полей для диагностики остаются,\n * значения секретов заменяются заглушкой.\n */\nfunction safeRawBody(body: unknown): unknown {\n if (!isRecord(body) || body.type !== 'validation' || !isRecord(body.found)) return body;\n\n return { ...body, found: redactBody(body.found) };\n}\n\n/** Что нужно знать о запросе, чтобы построить ошибку. */\nexport interface ErrorContext {\n method: string;\n path: string;\n status: number;\n statusText?: string | undefined;\n headers?: Headers | undefined;\n response?: Response | undefined;\n /** Тело ответа: разобранный JSON либо строка. */\n body: unknown;\n}\n\n/**\n * Строит типизированную ошибку из ответа сервера.\n *\n * Класс выбирается сначала по коду ошибки, затем по HTTP-статусу — так `VALIDATION_ERROR`\n * со статусом `400` всё равно станет {@link ItdValidationError}.\n */\nexport function createApiError(context: ErrorContext): ItdApiError {\n const parsed = parseErrorBody(context.body, context.status, context.statusText);\n const rateLimit = readRateLimit(context.headers);\n\n const init = {\n rateLimit: rateLimit.limit,\n rateLimitRemaining: rateLimit.remaining,\n status: context.status,\n code: parsed.code,\n message: parsed.message,\n detail: parsed.detail,\n title: parsed.title,\n fieldErrors: parsed.fieldErrors,\n requestId: getRequestId(context.headers),\n method: context.method,\n path: context.path,\n raw: safeRawBody(context.body),\n response: context.response,\n retryAfter: parseRetryAfter(context.headers?.get('retry-after')),\n };\n\n if (parsed.code === 'PHONE_VERIFICATION_REQUIRED') {\n return new ItdPhoneVerificationError({ ...init, userId: parsed.userId });\n }\n\n const Ctor = CODE_TO_CLASS[parsed.code] ?? classByStatus(context.status);\n return new Ctor(init as never);\n}\n","import type { ClientHooks, Logger } from '../types/options.js';\nimport type { CookieJar } from './cookies.js';\nimport { createApiError, readRateLimit } from './error-factory.js';\nimport {\n ItdAbortError,\n ItdConfigError,\n ItdError,\n ItdNetworkError,\n ItdTimeoutError,\n} from './errors.js';\nimport type { PipelineRequest, PreparedRequestBody } from './pipeline.js';\nimport { dispatchRequestHook } from './plugins.js';\nimport { redactBody, redactHeaders } from './redact.js';\nimport { isBlob } from './runtime.js';\nimport { unwrapData } from './unwrap.js';\nimport { buildQuery, joinUrl } from './url.js';\n\n/**\n * Настройки, нужные транспорту.\n *\n * Узкий срез общей конфигурации: авторизация, хранилище, повторы и очередь транспорта\n * не касаются, и он не должен иметь к ним доступ.\n */\nexport interface TransportConfig {\n baseUrl: string;\n fetch: typeof fetch;\n timeout: number;\n headers: Record<string, string>;\n /** Значение заголовка `User-Agent`. `undefined` — заголовок не выставляется. */\n userAgent: string | undefined;\n useCookieJar: boolean;\n sendCredentials: boolean;\n hooks: ClientHooks;\n logger: Logger | undefined;\n}\n\n/**\n * Внешние части, которыми пользуется транспорт.\n *\n * Все три обязательны к передаче и все три могут отсутствовать по существу: cookie-jar\n * не нужен в браузере, идентификатор устройства — при выключенной авторизации, а сведения\n * об ограничении частоты интересны только тогда, когда есть очередь.\n */\nexport interface TransportDeps {\n /** Хранилище cookie. `undefined` — cookie ведёт сама среда. */\n cookies: CookieJar | undefined;\n /**\n * Идентификатор устройства для заголовка `X-Device-Id`. Отправляется на всех запросах,\n * включая анонимные (`sign-in`).\n */\n getDeviceId: (() => Promise<string>) | undefined;\n /**\n * Сообщает об остатке лимита из заголовков ответа.\n *\n * Вызывается после **каждого** ответа, включая ошибочные, — так очередь узнаёт\n * об исчерпании лимита заранее и успевает притормозить до отказа сервера.\n */\n onRateLimit:\n | ((limit: number | undefined, remaining: number | undefined, request: PipelineRequest) => void)\n | undefined;\n}\n\n/**\n * Ставит заголовок, превращая ограничение HTTP в понятное сообщение.\n *\n * Значения заголовков ограничены latin1, поэтому кириллица в них невозможна. Среда\n * сообщает об этом невнятным `Cannot convert argument to a ByteString`, что при русских\n * названиях приложений сбивает с толку.\n */\nfunction setHeader(headers: Headers, name: string, value: string): void {\n try {\n headers.set(name, value);\n } catch {\n throw new ItdConfigError(\n `Значение заголовка ${name} содержит символы вне latin1. HTTP-заголовки не могут ` +\n 'содержать кириллицу — закодируйте значение, например через encodeURIComponent.',\n );\n }\n}\n\n/** Тело, которое отправляется как есть, без сериализации в JSON. */\nfunction isRawBody(body: unknown): body is BodyInit {\n if (typeof body !== 'object' || body === null) return typeof body === 'string';\n return (\n (typeof FormData !== 'undefined' && body instanceof FormData) ||\n isBlob(body) ||\n (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) ||\n (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) ||\n body instanceof ArrayBuffer ||\n ArrayBuffer.isView(body)\n );\n}\n\n/**\n * Читает тело ответа один раз.\n *\n * Ответ можно прочитать только однократно, а тело нужно и при успехе, и при ошибке,\n * поэтому чтение происходит здесь, до ветвления по статусу.\n */\nasync function readBody(response: Response): Promise<unknown> {\n if (response.status === 204 || response.status === 205) return undefined;\n if (response.headers.get('content-length') === '0') return undefined;\n\n const contentType = response.headers.get('content-type') ?? '';\n\n if (contentType.includes('json')) {\n const text = await response.text();\n if (text === '') return undefined;\n try {\n return JSON.parse(text) as unknown;\n } catch {\n // Заголовок обещал JSON, но тело им не является — отдаём как текст,\n // чтобы сообщение об ошибке осталось информативным.\n return text;\n }\n }\n\n const text = await response.text();\n return text === '' ? undefined : text;\n}\n\n/** Ошибка отмены в формате `fetch`. */\nfunction createAbortError(): Error {\n const error = new Error('Операция прервана');\n error.name = 'AbortError';\n return error;\n}\n\n/** Прерывает ожидание промиса при срабатывании сигнала. */\nfunction abortable<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {\n if (signal.aborted) {\n // Готовый промис может завершиться до отложенной ошибки отмены.\n return Promise.race([\n promise,\n new Promise<never>((_resolve, reject) => {\n setTimeout(() => reject(createAbortError()), 0);\n }),\n ]);\n }\n\n let onAbort: (() => void) | undefined;\n\n const interrupted = new Promise<never>((_resolve, reject) => {\n onAbort = () => reject(createAbortError());\n signal.addEventListener('abort', onAbort, { once: true });\n });\n\n return Promise.race([promise, interrupted]).finally(() => {\n if (onAbort) signal.removeEventListener('abort', onAbort);\n });\n}\n\n/** Результат объединения пользовательской отмены и таймаута. */\ninterface AbortBundle {\n signal: AbortSignal;\n /** Сработал ли именно таймаут — от этого зависит класс ошибки. */\n timedOut: () => boolean;\n cleanup: () => void;\n}\n\n/**\n * Объединяет пользовательский `AbortSignal` с таймаутом.\n *\n * Реализовано вручную, а не через `AbortSignal.any`: последний появился только в Node 20,\n * а библиотека поддерживает Node 18.\n */\nfunction createAbortBundle(userSignal: AbortSignal | undefined, timeout: number): AbortBundle {\n const controller = new AbortController();\n let timedOut = false;\n\n const onUserAbort = () => controller.abort(userSignal?.reason);\n\n if (userSignal) {\n if (userSignal.aborted) controller.abort(userSignal.reason);\n else userSignal.addEventListener('abort', onUserAbort, { once: true });\n }\n\n const timer =\n timeout > 0\n ? setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, timeout)\n : undefined;\n\n return {\n signal: controller.signal,\n timedOut: () => timedOut,\n cleanup: () => {\n if (timer !== undefined) clearTimeout(timer);\n userSignal?.removeEventListener('abort', onUserAbort);\n },\n };\n}\n\n/**\n * Единственное место, откуда библиотека ходит в сеть.\n *\n * Отвечает за сборку URL, заголовки, cookie, таймауты, разбор ответа и превращение любой\n * неудачи в типизированную ошибку. Авторизация, повторы, очередь и плагины — отдельные\n * слои конвейера, и транспорт о них не знает.\n */\nexport class Transport {\n readonly #config: TransportConfig;\n readonly #deps: TransportDeps;\n\n constructor(config: TransportConfig, deps: TransportDeps) {\n this.#config = config;\n this.#deps = deps;\n }\n\n /** Базовый URL, к которому обращается транспорт. */\n get baseUrl(): string {\n return this.#config.baseUrl;\n }\n\n /**\n * Выполняет один сетевой запрос.\n *\n * @throws {ItdApiError} если сервер ответил статусом ≥ 400\n * @throws {ItdTimeoutError} если истёк таймаут\n * @throws {ItdAbortError} если запрос отменён через `signal`\n * @throws {ItdNetworkError} если запрос не дошёл до сервера\n */\n send = async (request: PipelineRequest): Promise<unknown> => {\n const method = request.method.toUpperCase();\n const url = this.buildUrl(request);\n const headers = await this.#buildHeaders(request, url);\n const attempt = request.attempt ?? 1;\n\n const timeout = request.timeout ?? this.#config.timeout;\n const abort = createAbortBundle(request.signal, timeout);\n const startedAt = Date.now();\n let cleanupBody: (() => void | Promise<void>) | undefined;\n\n try {\n let body: BodyInit | undefined;\n try {\n const prepared = await this.#prepareBody(request, headers, abort.signal, attempt);\n body = prepared.body;\n cleanupBody = prepared.cleanup;\n } catch (error) {\n const failure = this.#toTransportError(error, abort, request, method, timeout);\n const context = { method, path: request.path, url, headers, attempt };\n await dispatchRequestHook(\n this.#config.hooks,\n 'onError',\n { ...context, duration: Date.now() - startedAt, error: failure },\n request,\n );\n throw failure;\n }\n\n const context = { method, path: request.path, url, headers, attempt };\n await dispatchRequestHook(this.#config.hooks, 'onRequest', context, request);\n\n this.#config.logger?.debug(`→ ${method} ${request.path}`, {\n headers: redactHeaders(headers),\n body: request.bodyFactory ? '[повторяемое тело]' : redactBody(request.body),\n });\n\n let response: Response;\n try {\n const init: RequestInit & { duplex?: 'half' } = {\n method,\n headers,\n signal: abort.signal,\n ...(body !== undefined ? { body } : {}),\n ...(this.#config.sendCredentials ? { credentials: 'include' as const } : {}),\n };\n if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) {\n init.duplex = 'half';\n }\n response = await this.#config.fetch(url, init);\n } catch (error) {\n const duration = Date.now() - startedAt;\n const failure = this.#toTransportError(error, abort, request, method, timeout);\n\n await dispatchRequestHook(\n this.#config.hooks,\n 'onError',\n { ...context, duration, error: failure },\n request,\n );\n this.#config.logger?.warn(\n `× ${method} ${request.path} (${duration} мс): ${failure.message}`,\n );\n\n throw failure;\n }\n\n if (this.#deps.onRateLimit) {\n const { limit, remaining } = readRateLimit(response.headers);\n this.#deps.onRateLimit(limit, remaining, request);\n }\n\n if (this.#config.useCookieJar) this.#deps.cookies?.setFromResponse(url, response);\n\n // Хук получает непрочитанный ответ.\n if (response.ok) {\n await dispatchRequestHook(\n this.#config.hooks,\n 'onResponse',\n {\n ...context,\n status: response.status,\n duration: Date.now() - startedAt,\n response,\n },\n request,\n );\n }\n\n const payload = await this.#readBodyOrFail(\n response,\n context,\n request,\n method,\n abort,\n timeout,\n );\n const duration = Date.now() - startedAt;\n\n if (!response.ok) {\n const error = createApiError({\n method,\n path: request.path,\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n response,\n body: payload,\n });\n\n await dispatchRequestHook(\n this.#config.hooks,\n 'onError',\n { ...context, duration, error },\n request,\n );\n this.#config.logger?.warn(\n `← ${response.status} ${method} ${request.path} (${duration} мс): ${error.message}`,\n );\n\n throw error;\n }\n\n this.#config.logger?.debug(`← ${response.status} ${method} ${request.path} (${duration} мс)`);\n\n return request.raw ? payload : unwrapData(payload);\n } finally {\n try {\n await cleanupBody?.();\n } catch (error) {\n this.#config.logger?.warn(`не удалось закрыть тело ${method} ${request.path}`, error);\n } finally {\n abort.cleanup();\n }\n }\n };\n\n /** Подготавливает тело внутри попытки, чтобы поток можно было открыть заново при retry. */\n async #prepareBody(\n request: PipelineRequest,\n headers: Headers,\n signal: AbortSignal,\n attempt: number,\n ): Promise<{ body: BodyInit | undefined; cleanup: (() => void | Promise<void>) | undefined }> {\n if (request.bodyFactory) {\n if (request.body !== undefined && request.body !== null) {\n throw new ItdConfigError('body и bodyFactory нельзя задавать одновременно');\n }\n\n const pending = Promise.resolve(request.bodyFactory({ signal, attempt }));\n let prepared: PreparedRequestBody;\n try {\n prepared = await abortable(pending, signal);\n } catch (error) {\n if (signal.aborted) {\n void pending\n .then(async (late) => {\n try {\n await late.cleanup?.();\n } catch (cleanupError) {\n this.#config.logger?.warn(\n `не удалось закрыть отложенное тело ${request.method} ${request.path}`,\n cleanupError,\n );\n }\n })\n .catch(() => {});\n }\n throw error;\n }\n for (const [name, value] of Object.entries(prepared.headers ?? {})) {\n if (!headers.has(name)) setHeader(headers, name, value);\n }\n return { body: prepared.body, cleanup: prepared.cleanup };\n }\n\n if (request.body === undefined || request.body === null) {\n return { body: undefined, cleanup: undefined };\n }\n if (isRawBody(request.body)) {\n return { body: request.body, cleanup: undefined };\n }\n\n if (!headers.has('Content-Type')) headers.set('Content-Type', 'application/json');\n return { body: JSON.stringify(request.body), cleanup: undefined };\n }\n\n /** Читает тело и преобразует ошибку чтения в транспортную ошибку библиотеки. */\n async #readBodyOrFail(\n response: Response,\n context: { method: string; path: string; url: string; headers: Headers; attempt: number },\n request: PipelineRequest,\n method: string,\n abort: AbortBundle,\n timeout: number,\n ): Promise<unknown> {\n const startedAt = Date.now();\n\n try {\n return await abortable(readBody(response), abort.signal);\n } catch (error) {\n await response.body?.cancel().catch(() => {});\n\n const failure = this.#toTransportError(error, abort, request, method, timeout);\n\n await dispatchRequestHook(\n this.#config.hooks,\n 'onError',\n {\n ...context,\n duration: Date.now() - startedAt,\n error: failure,\n },\n request,\n );\n this.#config.logger?.warn(\n `× ${method} ${request.path}: не удалось прочитать тело ответа — ${failure.message}`,\n );\n\n throw failure;\n }\n }\n\n /**\n * Итоговый URL со строкой запроса. Нужен и слою повторов — для хука `onRetry`.\n *\n * Хост берётся из самого запроса, если он там задан: у сервисов платформы свои домены.\n */\n buildUrl(request: PipelineRequest): string {\n const base = request.baseUrl ?? this.#config.baseUrl;\n return joinUrl(base, request.path) + buildQuery(request.query);\n }\n\n /**\n * Собирает общие заголовки клиента: `User-Agent`, идентификатор устройства,\n * заголовки конфигурации и cookie для указанного адреса.\n */\n async platformHeaders(url: string): Promise<Headers> {\n const headers = new Headers();\n\n // Сервер этот заголовок не требует, но ожидает: дешевле отправить, чем разбираться,\n // почему часть запросов не проходит фильтры.\n headers.set('X-Requested-With', 'XMLHttpRequest');\n\n // В браузере это запрещённый заголовок: среда молча его игнорирует, ошибки не будет.\n if (this.#config.userAgent) setHeader(headers, 'User-Agent', this.#config.userAgent);\n\n if (this.#deps.getDeviceId) {\n setHeader(headers, 'X-Device-Id', await this.#deps.getDeviceId());\n }\n\n for (const [name, value] of Object.entries(this.#config.headers))\n setHeader(headers, name, value);\n\n if (this.#config.useCookieJar && this.#deps.cookies) {\n const cookie = this.#deps.cookies.getHeader(url);\n if (cookie) setHeader(headers, 'Cookie', cookie);\n }\n\n return headers;\n }\n\n /**\n * Дополняет общие заголовки значением `Accept`, заголовками конвейера и вызова.\n * Заголовки вызова применяются последними.\n */\n async #buildHeaders(request: PipelineRequest, url: string): Promise<Headers> {\n const headers = await this.platformHeaders(url);\n\n // Заголовок из конфигурации имеет приоритет над значением по умолчанию.\n if (!headers.has('Accept')) headers.set('Accept', 'application/json');\n\n for (const [name, value] of Object.entries(request.layerHeaders ?? {}))\n setHeader(headers, name, value);\n\n for (const [name, value] of Object.entries(request.headers ?? {})) {\n setHeader(headers, name, value);\n }\n\n return headers;\n }\n\n /** Превращает исключение `fetch` в понятную ошибку библиотеки. */\n #toTransportError(\n error: unknown,\n abort: AbortBundle,\n request: PipelineRequest,\n method: string,\n timeout: number,\n ): ItdError {\n // Пользовательская отмена с собственным `reason` реджектит `fetch` этим значением, а не\n // `AbortError`, — поэтому опираемся на состояние сигнала, а не только на имя ошибки.\n const aborted = abort.signal.aborted || (error instanceof Error && error.name === 'AbortError');\n\n if (aborted && abort.timedOut()) {\n return new ItdTimeoutError({ timeout, method, path: request.path });\n }\n\n if (aborted) {\n // Сохраняем причину отмены: пользователь мог передать её в `abort(reason)`.\n const reason = request.signal?.reason;\n return new ItdAbortError(\n `Запрос ${method} ${request.path} отменён`,\n reason !== undefined ? { cause: reason } : undefined,\n );\n }\n\n if (error instanceof ItdError) return error;\n\n return new ItdNetworkError(\n `Не удалось выполнить ${method} ${request.path}: ${error instanceof Error ? error.message : String(error)}`,\n { method, path: request.path, cause: error },\n );\n }\n}\n","/**\n * Перечисления API итд.com.\n *\n * Здесь намеренно не используется `enum` из TypeScript. Вместо него — пара «замороженный\n * объект + одноимённый тип». Такой приём даёт всё, ради чего берут `enum`\n * (`FeedTab.Popular`, перебор значений в рантайме), и при этом:\n *\n * - **стирается без остатка** — `enum` порождает рантайм-код и отвергается средами,\n * которые просто срезают типы (`node --experimental-strip-types`);\n * - **не запрещает обычные строки** — `itd.posts.list({ tab: 'popular' })` остаётся валидным,\n * тогда как строковый `enum` считает это ошибкой типа и вынуждает всех импортировать себя;\n * - **позволяет открытые множества** — там, где документация перечисляет значения не полностью,\n * тип расширяется через {@link Loose}, а объект остаётся справочником известных значений.\n *\n * @example\n * ```ts\n * import { FeedTab } from 'itd-api';\n *\n * await itd.posts.list({ tab: FeedTab.Popular }); // без магических строк\n * await itd.posts.list({ tab: 'popular' }); // и так тоже можно\n *\n * Object.values(FeedTab); // ['popular', 'following', 'clan']\n * ```\n *\n * @packageDocumentation\n */\n\n/**\n * Открытое строковое перечисление.\n *\n * Даёт автодополнение известных значений, но не ломается, если сервер пришлёт новое.\n * Используется там, где документация API перечисляет значения не полностью («`everyone` и др.»).\n */\nexport type Loose<T extends string> = T | (string & {});\n\n/**\n * Вкладка ленты `GET /api/posts`.\n *\n * Множество закрытое: неизвестное значение сервер отвергнет.\n */\nexport const FeedTab = Object.freeze({\n /** Популярное. Курсор здесь — номер страницы в виде строки (`\"2\"`, `\"6\"`…). */\n Popular: 'popular',\n /** Записи тех, на кого вы подписаны. Курсор — отметка времени последнего поста. */\n Following: 'following',\n /** Лента клана. Курсор, как и в подписках, — отметка времени. */\n Clan: 'clan',\n} as const);\nexport type FeedTab = (typeof FeedTab)[keyof typeof FeedTab];\n\n/** Порядок комментариев к посту. */\nexport const CommentSort = Object.freeze({\n /** Сначала новые. */\n Newest: 'newest',\n /** Сначала старые. */\n Oldest: 'oldest',\n /** Сначала популярные. */\n Popular: 'popular',\n} as const);\nexport type CommentSort = (typeof CommentSort)[keyof typeof CommentSort];\n\n/** Тип вложения. */\nexport const AttachmentType = Object.freeze({\n Image: 'image',\n Video: 'video',\n /** Голосовые комментарии: `audio/ogg`, с полем `duration`. */\n Audio: 'audio',\n} as const);\nexport type AttachmentType = (typeof AttachmentType)[keyof typeof AttachmentType];\n\n/**\n * Тип фрагмента разметки в тексте поста или комментария.\n *\n * Первые два сервер расставляет сам при разборе текста, остальные приходят от редактора.\n * Тип открытый: набор может пополниться.\n *\n * @example\n * ```ts\n * await itd.posts.update(postId, {\n * content: 'жирное слово',\n * spans: [{ type: SpanType.Bold, offset: 0, length: 6 }],\n * });\n * ```\n */\nexport const SpanType = Object.freeze({\n /** Хэштег. Название без решётки лежит в `tag`. */\n Hashtag: 'hashtag',\n /** Упоминание. Имя пользователя лежит в `tag`. */\n Mention: 'mention',\n /** Ссылка. Адрес лежит в `url`, а не в `tag`. */\n Link: 'link',\n Bold: 'bold',\n Italic: 'italic',\n Underline: 'underline',\n /** Зачёркнутый. */\n Strike: 'strike',\n /** Спойлер: текст скрыт до нажатия. */\n Spoiler: 'spoiler',\n /** Моноширинный. */\n Monospace: 'monospace',\n Quote: 'quote',\n} as const);\nexport type SpanType = Loose<(typeof SpanType)[keyof typeof SpanType]>;\n\n/** На что подаётся жалоба. */\nexport const ReportTargetType = Object.freeze({\n Post: 'post',\n Comment: 'comment',\n User: 'user',\n} as const);\nexport type ReportTargetType = (typeof ReportTargetType)[keyof typeof ReportTargetType];\n\n/** Причина жалобы. Множество закрытое. */\nexport const ReportReason = Object.freeze({\n Spam: 'spam',\n Violence: 'violence',\n Hate: 'hate',\n Adult: 'adult',\n Fraud: 'fraud',\n Other: 'other',\n} as const);\nexport type ReportReason = (typeof ReportReason)[keyof typeof ReportReason];\n\n/** Состояние realtime-соединения. */\nexport const RealtimeStatus = Object.freeze({\n Connecting: 'connecting',\n Connected: 'connected',\n Error: 'error',\n Disconnected: 'disconnected',\n} as const);\nexport type RealtimeStatus = (typeof RealtimeStatus)[keyof typeof RealtimeStatus];\n\n/** Состояние сервиса платформы. Тип открытый. */\nexport const ServiceState = Object.freeze({\n /** Работает штатно. */\n Operational: 'operational',\n /** Работает с деградацией. */\n Degraded: 'degraded',\n /** Недоступен. */\n Downtime: 'downtime',\n} as const);\nexport type ServiceState = Loose<(typeof ServiceState)[keyof typeof ServiceState]>;\n\n/** Вид происшествия в истории сервиса. Тип открытый. */\nexport const IncidentKind = Object.freeze({\n /** Недоступен. */\n Down: 'down',\n /** Деградация. */\n Degraded: 'deg',\n} as const);\nexport type IncidentKind = Loose<(typeof IncidentKind)[keyof typeof IncidentKind]>;\n\n/**\n * Уровень доступа к разделу профиля.\n *\n * Общий набор значений для полей `wallAccess` и `likesVisibility` настроек приватности.\n * Тип открытый: сервер может прислать значение вне этого перечня.\n */\nexport const AccessType = Object.freeze({\n /** Никто. */\n Nobody: 'nobody',\n /** Только взаимные подписки. */\n Mutual: 'mutual',\n /** Подписчики. */\n Followers: 'followers',\n /** Все. */\n Everyone: 'everyone',\n} as const);\nexport type AccessType = Loose<(typeof AccessType)[keyof typeof AccessType]>;\n\n/** Кто может писать на стену профиля. Псевдоним {@link AccessType}. */\nexport const WallAccess = AccessType;\nexport type WallAccess = AccessType;\n\n/** Кто видит реакции пользователя. Псевдоним {@link AccessType}. */\nexport const LikesVisibility = AccessType;\nexport type LikesVisibility = AccessType;\n\n/**\n * Канонический тип уведомления (новое поколение имён).\n *\n * REST-эндпоинт `/api/notifications/` отдаёт старые имена (`like`, `comment`, `reply`,\n * `repost`, `mention`), SSE-поток — новые. Библиотека приводит их к этому набору,\n * сохраняя исходное значение в поле `rawType`.\n */\nexport const NotificationType = Object.freeze({\n /** Реакция на пост. Старое имя — `like`. */\n PostReaction: 'post_reaction',\n /** Комментарий к посту. Старое имя — `comment`. */\n PostComment: 'post_comment',\n /** Ответ на комментарий. Старое имя — `reply`. */\n CommentReply: 'comment_reply',\n /** Репост. Старое имя — `repost`. */\n PostRepost: 'post_repost',\n /** Упоминание в посте. Старое имя — `mention`. */\n PostMention: 'post_mention',\n /** Реакция на комментарий. */\n CommentReaction: 'comment_reaction',\n /** Упоминание в комментарии. */\n CommentMention: 'comment_mention',\n /** Запись на вашей стене. */\n WallPost: 'wall_post',\n /** На вас подписались. */\n Follow: 'follow',\n /** Заявка на подписку (закрытый профиль). */\n FollowRequest: 'follow_request',\n /** Заявка на подписку принята. */\n FollowAccepted: 'follow_accepted',\n /** Верификация одобрена. Приходит только по REST. */\n VerificationApproved: 'verification_approved',\n /** Верификация отклонена. Приходит только по REST. */\n VerificationRejected: 'verification_rejected',\n} as const);\nexport type NotificationType = Loose<(typeof NotificationType)[keyof typeof NotificationType]>;\n\n/**\n * Тип взаимодействия с контентом в телеметрии (`POST /api/v1/x`, поле `t`).\n *\n * Кодируется числом.\n */\nexport const InteractionType = Object.freeze({\n /** Открытие фотографии. */\n PhotoOpen: 1,\n /** Прогресс просмотра видео. Несёт поля `pm`/`dm`. */\n VideoProgress: 2,\n} as const);\nexport type InteractionType = (typeof InteractionType)[keyof typeof InteractionType];\n\n/**\n * Источник показа поста в телеметрии (поле `s`).\n *\n * Кодируется числом. Поле применимо к источникам `PostPage` и `Link`; для лент источник\n * передаётся контекстом `sc`.\n */\nexport const ViewSource = Object.freeze({\n FeedGlobal: 1,\n FeedFollowing: 2,\n FeedClan: 3,\n Profile: 4,\n Hashtag: 5,\n PostPage: 6,\n Link: 7,\n Search: 8,\n} as const);\nexport type ViewSource = (typeof ViewSource)[keyof typeof ViewSource];\n\n/**\n * Причина завершения просмотра поста в телеметрии (`POST /api/v1/i`, поле `r`).\n *\n * Кодируется числом.\n */\nexport const ViewReason = Object.freeze({\n /** Пост ушёл из зоны видимости при обычной прокрутке. */\n Normal: 0,\n /** Потеря фокуса окна. */\n Blur: 1,\n /** Вкладка скрыта. */\n Hidden: 2,\n /** Уход со страницы (`pagehide`). */\n PageHide: 3,\n /** Элемент перестал наблюдаться. */\n Unobserve: 4,\n /** Достигнут порог времени просмотра. */\n ThresholdMet: 5,\n} as const);\nexport type ViewReason = (typeof ViewReason)[keyof typeof ViewReason];\n\n/**\n * Строковые коды ошибок из поля `code`.\n *\n * Ключи намеренно повторяют написание сервера: код из ответа API можно найти здесь\n * поиском один в один, без мысленного перевода регистра.\n *\n * Список открыт — сервер может добавить новый код, и это не должно ломать типизацию.\n *\n * @example\n * ```ts\n * if (err.hasCode(ItdErrorCode.OTP_INVALID)) await restartOtpFlow();\n * ```\n */\nexport const ItdErrorCode = Object.freeze({\n BAD_REQUEST: 'BAD_REQUEST',\n UNAUTHORIZED: 'UNAUTHORIZED',\n ACCESS_DENIED: 'ACCESS_DENIED',\n ENTITY_NOT_FOUND: 'ENTITY_NOT_FOUND',\n ENTITY_ALREADY_EXISTS: 'ENTITY_ALREADY_EXISTS',\n VALIDATION_ERROR: 'VALIDATION_ERROR',\n BUSINESS_RULE_VIOLATION: 'BUSINESS_RULE_VIOLATION',\n RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',\n UNKNOWN_ERROR: 'UNKNOWN_ERROR',\n /** Сервер отвечает так на `404`, `ENTITY_NOT_FOUND` в этом случае не приходит. */\n NOT_FOUND: 'NOT_FOUND',\n /** На практике не приходит: вместо него сервер шлёт `TURNSTILE_VERIFICATION_FAILED`. */\n CAPTCHA_FAILED: 'CAPTCHA_FAILED',\n /** Капча не пройдена: токен Turnstile недействителен, просрочен или уже использован. */\n TURNSTILE_VERIFICATION_FAILED: 'TURNSTILE_VERIFICATION_FAILED',\n OTP_INVALID: 'OTP_INVALID',\n /** `flowToken` неизвестен или просрочен — поток подтверждения нужно начинать заново. */\n INVALID_FLOW_TOKEN: 'INVALID_FLOW_TOKEN',\n ACCOUNT_DEACTIVATED: 'ACCOUNT_DEACTIVATED',\n ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED: 'ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED',\n ACCOUNT_INVALID_CREDENTIALS: 'ACCOUNT_INVALID_CREDENTIALS',\n ACCOUNT_TEMPORARILY_LOCKED: 'ACCOUNT_TEMPORARILY_LOCKED',\n ACCOUNT_CURRENT_PASSWORD_INCORRECT: 'ACCOUNT_CURRENT_PASSWORD_INCORRECT',\n SESSION_EXPIRED: 'SESSION_EXPIRED',\n SESSION_REVOKED: 'SESSION_REVOKED',\n SESSION_INVALID_REFRESH_TOKEN: 'SESSION_INVALID_REFRESH_TOKEN',\n /** Запрос обновления пришёл без cookie `refresh_token` — продлевать нечего. */\n REFRESH_TOKEN_MISSING: 'REFRESH_TOKEN_MISSING',\n /** Cookie `refresh_token` есть, но сессии за ней уже нет: отозвана или истекла. */\n SESSION_NOT_FOUND: 'SESSION_NOT_FOUND',\n MISSING_FLOW_TOKEN: 'MISSING_FLOW_TOKEN',\n PROFILE_USERNAME_TAKEN: 'PROFILE_USERNAME_TAKEN',\n PROFILE_RESTRICTION_ACTIVE: 'PROFILE_RESTRICTION_ACTIVE',\n PROFILE_MODIFICATION_RESTRICTED: 'PROFILE_MODIFICATION_RESTRICTED',\n CONTENT_MODERATION_FAILED: 'CONTENT_MODERATION_FAILED',\n FILE_TOO_LARGE: 'FILE_TOO_LARGE',\n UNSUPPORTED_FILE_TYPE: 'UNSUPPORTED_FILE_TYPE',\n UPLOAD_FAILED: 'UPLOAD_FAILED',\n VIDEO_REQUIRES_VERIFICATION: 'VIDEO_REQUIRES_VERIFICATION',\n PHONE_VERIFICATION_REQUIRED: 'PHONE_VERIFICATION_REQUIRED',\n WRITE_ACCESS_RESTRICTED: 'WRITE_ACCESS_RESTRICTED',\n} as const);\nexport type ItdErrorCode = Loose<(typeof ItdErrorCode)[keyof typeof ItdErrorCode]>;\n","import { NotificationType } from '../types/enums.js';\n\n/**\n * Соответствие коротких имён типов уведомлений развёрнутым.\n *\n * Сервер — и в списке, и в потоке событий — присылает короткие имена: `like`, `comment`,\n * `reply`, `repost`, `comment_like`. Развёрнутые (`post_reaction`, `post_comment`)\n * встречаются в оформлении интерфейса, поэтому библиотека приводит типы к ним:\n * они однозначно называют и объект, и действие.\n *\n * Пришедшее значение всегда остаётся в поле `rawType`.\n */\nexport const NOTIFICATION_TYPE_ALIASES: Readonly<Record<string, NotificationType>> = Object.freeze({\n like: NotificationType.PostReaction,\n comment: NotificationType.PostComment,\n comment_like: NotificationType.CommentReaction,\n reply: NotificationType.CommentReply,\n repost: NotificationType.PostRepost,\n mention: NotificationType.PostMention,\n});\n\nconst KNOWN_TYPES = new Set<string>(Object.values(NotificationType));\n\n/**\n * Приводит имя типа к каноническому.\n *\n * Неизвестное значение возвращается без изменений, чтобы не менять смысл нового типа\n * уведомления на другой.\n *\n * @example\n * ```ts\n * canonicalNotificationType('like'); // 'post_reaction'\n * canonicalNotificationType('post_reaction'); // 'post_reaction'\n * canonicalNotificationType('новое_событие'); // 'новое_событие'\n * ```\n */\nexport function canonicalNotificationType(rawType: string): NotificationType {\n return NOTIFICATION_TYPE_ALIASES[rawType] ?? rawType;\n}\n\n/**\n * Известен ли библиотеке этот тип уведомления.\n *\n * Полезно, чтобы решить, показывать ли уведомление, для которого нет своего оформления.\n */\nexport function isKnownNotificationType(type: string): boolean {\n return KNOWN_TYPES.has(canonicalNotificationType(type));\n}\n","import { asString, isRecord } from '../core/unwrap.js';\nimport type { Actor, Notification } from '../types/models.js';\nimport { canonicalNotificationType } from './type-map.js';\n\n/** Событие потока уведомлений после разбора. */\nexport interface NotificationEvent {\n /** Само уведомление в единой форме. */\n notification: Notification;\n /**\n * Актуальное число непрочитанных, если сервер его сообщил.\n *\n * Клиент не увеличивает счётчик сам: значение приходит с сервера.\n */\n unreadCount: number | undefined;\n /** Нужно ли проиграть звук. */\n sound: boolean;\n}\n\nfunction asActor(value: unknown): Actor | undefined {\n if (!isRecord(value)) return undefined;\n const id = asString(value.id);\n if (!id) return undefined;\n\n return {\n id,\n username: asString(value.username) ?? '',\n displayName: asString(value.displayName) ?? '',\n avatar: asString(value.avatar) ?? '',\n ...(typeof value.isFollowing === 'boolean' ? { isFollowing: value.isFollowing } : {}),\n ...(typeof value.isFollowedBy === 'boolean' ? { isFollowedBy: value.isFollowedBy } : {}),\n };\n}\n\n/** Собирает участников: сервер присылает либо одного `actor`, либо массив `actors`. */\nfunction readActors(source: Record<string, unknown>): Actor[] {\n if (Array.isArray(source.actors)) {\n return source.actors.map(asActor).filter((actor): actor is Actor => actor !== undefined);\n }\n\n const single = asActor(source.actor);\n return single ? [single] : [];\n}\n\n/**\n * Приводит уведомление к единой форме.\n *\n * Нужна потому, что REST-список и поток событий описывают одно и то же событие по-разному:\n * различаются имена типов (`like` против `post_reaction`), имена полей\n * (`targetId`/`entityId`, `read`/`isRead`, `preview`/`entityPreview`) и число участников\n * (`actor` против массива `actors`). После приведения объекты из обоих источников\n * можно складывать в один список.\n *\n * Исходные данные не теряются: имя типа с сервера остаётся в `rawType`,\n * весь объект целиком — в `raw`.\n *\n * @param input уведомление из REST-ответа либо полезная нагрузка события потока\n *\n * @example\n * ```ts\n * const fromRest = normalizeNotification(restItem);\n * const fromStream = normalizeNotification(event.payload);\n * // одинаковая форма — можно объединять\n * ```\n */\nexport function normalizeNotification(input: unknown): Notification {\n const source = isRecord(input) ? input : {};\n\n // Поток кладёт полезную нагрузку то в payload, то прямо в корень события.\n const payload = isRecord(source.payload) ? source.payload : source;\n\n const rawType = asString(payload.type) ?? asString(source.type) ?? '';\n const createdAt = asString(payload.createdAt) ?? asString(source.createdAt) ?? '';\n const readAt = asString(payload.readAt) ?? asString(source.readAt);\n\n const isRead =\n typeof payload.isRead === 'boolean'\n ? payload.isRead\n : typeof payload.read === 'boolean'\n ? payload.read\n : Boolean(readAt);\n\n // Сервер описывает событие парой «цель — предмет», и смысл пары зависит от типа:\n //\n // комментарий, реакция на него — цель = пост, предмет = комментарий\n // репост — цель = сам репост, предмет = исходный пост\n // реакция на пост, подписка — предмета нет\n //\n // Событие относится к предмету, только когда тот является комментарием; иначе объект\n // события — цель. Без этого различия ссылка на репост вела бы на чужой исходный пост.\n const subjectId = asString(payload.subjectId);\n const targetId = asString(payload.targetId);\n const subjectIsComment = payload.subjectType === 'comment';\n const clickUrl = asString(payload.clickUrl);\n\n return {\n id: asString(payload.id) ?? asString(source.id) ?? '',\n type: canonicalNotificationType(rawType),\n rawType,\n entityId:\n asString(payload.entityId) ?? (subjectIsComment ? (subjectId ?? targetId) : targetId) ?? null,\n parentEntityId:\n asString(payload.parentEntityId) ?? (subjectIsComment ? (targetId ?? null) : null),\n isRead,\n actors: readActors(payload),\n count: typeof payload.count === 'number' && payload.count > 0 ? payload.count : 1,\n preview: asString(payload.entityPreview) ?? asString(payload.preview) ?? null,\n ...(clickUrl ? { clickUrl } : {}),\n createdAt,\n updatedAt: asString(payload.updatedAt) ?? readAt ?? createdAt,\n raw: input,\n };\n}\n\n/**\n * Разбирает событие `notification` из потока.\n *\n * Кроме самого уведомления событие несёт служебные поля уровня конверта: актуальный\n * счётчик непрочитанных и признак звука.\n */\nexport function readNotificationEvent(data: unknown): NotificationEvent {\n const source = isRecord(data) ? data : {};\n\n return {\n notification: normalizeNotification(data),\n unreadCount: typeof source.unreadCount === 'number' ? source.unreadCount : undefined,\n sound: source.sound === true,\n };\n}\n\n/**\n * Разбирает событие `unread_count` из потока.\n *\n * Возвращает `undefined`, если сервер прислал событие без вложенного `payload`.\n */\nexport function readUnreadCountEvent(data: unknown): number | undefined {\n if (!isRecord(data)) return undefined;\n\n const payload = isRecord(data.payload) ? data.payload : undefined;\n if (!payload) return undefined;\n\n return typeof payload.count === 'number' ? payload.count : undefined;\n}\n","/** Событие, пришедшее по каналу реального времени. */\nexport interface TransportEvent {\n /** Имя события: `notification`, `unread_count` и другие. */\n name: string;\n /** Полезная нагрузка, уже разобранная из JSON. */\n data: unknown;\n}\n\n/** Что транспорт получает от клиента при подключении. */\nexport interface TransportContext {\n /** Базовый URL API. */\n baseUrl: string;\n /** Реализация `fetch`. */\n fetch: typeof fetch;\n /**\n * Общие заголовки клиента: `User-Agent`, `X-Device-Id`, заголовки конфигурации\n * и cookie для указанного адреса.\n */\n baseHeaders: (url: string) => Promise<Headers>;\n /** Текущий токен доступа. */\n getToken: () => Promise<string | null>;\n /** Отмена подключения. */\n signal: AbortSignal;\n /** Сообщает о полученном событии. */\n onEvent: (event: TransportEvent) => void;\n /** Сообщает о разобранном, но некорректном сообщении. Соединение при этом живёт. */\n onParseError: (error: unknown, raw: string) => void;\n /** Вызывается, когда соединение установлено. */\n onOpen: () => void;\n}\n\n/**\n * Канал получения событий в реальном времени.\n *\n * Сейчас у платформы один такой канал — поток `text/event-stream`. Абстракция нужна\n * на будущее: политика безопасности сайта уже разрешает `wss://*.xn--d1ah4a.com`,\n * и когда появится WebSocket, достаточно будет добавить ещё одну реализацию этого\n * интерфейса. Переподключение, обновление токена и разбор уведомлений от транспорта\n * не зависят.\n */\nexport interface RealtimeTransport {\n /** Понятное имя для логов и диагностики. */\n readonly name: string;\n\n /**\n * Держит соединение, пока оно живо.\n *\n * Должен завершиться, когда поток закрылся, и бросить исключение при ошибке.\n * Отмена через `context.signal` должна приводить к `AbortError`.\n */\n connect(context: TransportContext): Promise<void>;\n}\n\n/** Ошибка, по которой видно, что сервер отверг авторизацию потока. */\nexport class UnauthorizedStreamError extends Error {\n constructor() {\n super('Поток уведомлений отверг токен доступа');\n this.name = 'UnauthorizedStreamError';\n }\n}\n","import { pickArray, pickNumber } from '../core/unwrap.js';\nimport { joinUrl } from '../core/url.js';\nimport type { RealtimeTransport, TransportContext } from './transport.js';\nimport { UnauthorizedStreamError } from './transport.js';\n\n/** Настройки опроса. */\nexport interface PollTransportOptions {\n /** Как часто опрашивать сервер, мс. По умолчанию 15 000. */\n interval?: number;\n /** Сколько уведомлений запрашивать за раз. По умолчанию 20. */\n limit?: number;\n}\n\n/**\n * Запасной транспорт: обычный опрос REST вместо потока.\n *\n * Нужен там, где `fetch` не умеет отдавать тело по частям, — например в части сборок\n * React Native. Наружу выдаёт те же события, что и {@link SseTransport}, поэтому\n * вызывающий код разницы не замечает.\n *\n * Новыми считаются уведомления, которых не было в предыдущем ответе; чтобы список\n * известных не рос бесконечно, он ограничен последними двумя страницами.\n */\nexport class PollTransport implements RealtimeTransport {\n readonly name = 'poll';\n\n readonly #interval: number;\n readonly #limit: number;\n\n constructor(options: PollTransportOptions = {}) {\n this.#interval = options.interval ?? 15_000;\n this.#limit = options.limit ?? 20;\n }\n\n async connect(context: TransportContext): Promise<void> {\n const seen = new Set<string>();\n let firstRun = true;\n let lastUnreadCount: number | undefined;\n\n while (!context.signal.aborted) {\n const token = await context.getToken();\n if (!token) throw new UnauthorizedStreamError();\n\n const url = `${joinUrl(context.baseUrl, '/api/notifications/')}?limit=${this.#limit}&offset=0`;\n\n const headers = await context.baseHeaders(url);\n headers.set('Accept', 'application/json');\n headers.set('Authorization', `Bearer ${token}`);\n\n const response = await context.fetch(url, {\n method: 'GET',\n headers,\n signal: context.signal,\n });\n\n if (response.status === 401) throw new UnauthorizedStreamError();\n if (!response.ok) throw new Error(`Опрос уведомлений вернул статус ${response.status}`);\n\n // Соединение считается установленным только после первого успешного ответа: иначе\n // при постоянно недоступной сети каждая попытка обнуляла бы счётчик и maxAttempts\n // не срабатывал бы никогда.\n context.onOpen();\n\n const body: unknown = await response.json();\n const payload =\n typeof body === 'object' && body !== null && 'data' in body\n ? (body as { data: unknown }).data\n : body;\n\n const items = pickArray<Record<string, unknown>>(payload, 'notifications');\n\n for (const item of [...items].reverse()) {\n const id = typeof item.id === 'string' ? item.id : undefined;\n if (!id || seen.has(id)) continue;\n\n seen.add(id);\n // На первом проходе список уже существующих уведомлений не считается новыми\n // событиями: иначе подключение сразу выдало бы всю историю.\n if (!firstRun) context.onEvent({ name: 'notification', data: { payload: item } });\n }\n\n // Ограничиваем память: держим только последние две страницы идентификаторов.\n if (seen.size > this.#limit * 2) {\n const excess = [...seen].slice(0, seen.size - this.#limit * 2);\n for (const id of excess) seen.delete(id);\n }\n\n const count = await this.#readCount(context, headers);\n if (count !== undefined && count !== lastUnreadCount) {\n lastUnreadCount = count;\n context.onEvent({ name: 'unread_count', data: { payload: { count } } });\n }\n\n firstRun = false;\n await this.#wait(context.signal);\n }\n }\n\n async #readCount(context: TransportContext, headers: Headers): Promise<number | undefined> {\n try {\n const response = await context.fetch(joinUrl(context.baseUrl, '/api/notifications/count'), {\n method: 'GET',\n headers,\n signal: context.signal,\n });\n\n if (!response.ok) return undefined;\n\n const body: unknown = await response.json();\n const payload =\n typeof body === 'object' && body !== null && 'data' in body\n ? (body as { data: unknown }).data\n : body;\n\n return pickNumber(payload, 'count', 0);\n } catch {\n // Счётчик — вспомогательная величина: его недоступность не должна рвать опрос.\n return undefined;\n }\n }\n\n /** Ждёт следующего опроса, прерываясь при отмене. */\n #wait(signal: AbortSignal): Promise<void> {\n // Подписка на уже сработавший сигнал не вызывается никогда — без этой проверки\n // отмена, пришедшая перед самым ожиданием, стоила бы лишнего интервала.\n if (signal.aborted) return Promise.resolve();\n\n return new Promise<void>((resolve) => {\n const timer = setTimeout(finish, this.#interval);\n\n function finish() {\n clearTimeout(timer);\n signal.removeEventListener('abort', finish);\n resolve();\n }\n\n signal.addEventListener('abort', finish, { once: true });\n });\n }\n}\n","/**\n * Паузы перед попытками переподключения, мс.\n *\n * Значения совпадают с теми, что использует сайт итд.com, — поведение библиотеки\n * не отличается от привычного пользователю.\n */\nexport const RECONNECT_BACKOFF = Object.freeze([1000, 2000, 4000, 8000, 16000, 30000]);\n\n/** Доля случайного разброса паузы. */\nexport const RECONNECT_JITTER = 0.3;\n\n/**\n * Сколько раз пытаться переподключиться подряд.\n *\n * После исчерпания поток сообщает `giveup` и ждёт ручного `connect()`.\n */\nexport const MAX_RECONNECT_ATTEMPTS = 15;\n\n/** Настройки переподключения. */\nexport interface ReconnectOptions {\n /** Таблица пауз. Последнее значение действует для всех дальнейших попыток. */\n backoff?: readonly number[];\n /** Доля разброса, 0…1. */\n jitter?: number;\n /** Предел числа попыток. */\n maxAttempts?: number;\n}\n\n/**\n * Вычисляет паузу перед попыткой переподключения.\n *\n * Разброс нужен, чтобы клиенты, отключившиеся одновременно из-за перезапуска сервера,\n * не вернулись тоже одновременно.\n *\n * @param attempt номер попытки, начиная с нуля\n * @param random источник случайности; подменяется в тестах\n */\nexport function reconnectDelay(\n attempt: number,\n options: ReconnectOptions = {},\n random: () => number = Math.random,\n): number {\n const backoff = options.backoff ?? RECONNECT_BACKOFF;\n const jitter = options.jitter ?? RECONNECT_JITTER;\n\n const base = backoff[Math.min(attempt, backoff.length - 1)] ?? 30_000;\n const spread = base * jitter * (random() * 2 - 1);\n\n return Math.max(0, Math.round(base + spread));\n}\n","class ParseError extends Error {\n constructor(message, options) {\n super(message), this.name = \"ParseError\", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line;\n }\n}\nconst LF = 10, CR = 13, SPACE = 32;\nfunction noop(_arg) {\n}\nfunction createParser(config) {\n if (typeof config == \"function\")\n throw new TypeError(\n \"`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?\"\n );\n const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config, pendingFragments = [];\n let pendingFragmentsLength = 0, isFirstChunk = !0, id, data = \"\", dataLines = 0, eventType, terminated = !1;\n function feed(chunk) {\n if (terminated)\n throw new Error(\n \"Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing.\"\n );\n if (isFirstChunk && (isFirstChunk = !1, chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191 && (chunk = chunk.slice(3))), pendingFragments.length === 0) {\n const trailing2 = processLines(chunk);\n trailing2 !== \"\" && (pendingFragments.push(trailing2), pendingFragmentsLength = trailing2.length), checkBufferSize();\n return;\n }\n if (chunk.indexOf(`\n`) === -1 && chunk.indexOf(\"\\r\") === -1) {\n pendingFragments.push(chunk), pendingFragmentsLength += chunk.length, checkBufferSize();\n return;\n }\n pendingFragments.push(chunk);\n const input = pendingFragments.join(\"\");\n pendingFragments.length = 0, pendingFragmentsLength = 0;\n const trailing = processLines(input);\n trailing !== \"\" && (pendingFragments.push(trailing), pendingFragmentsLength = trailing.length), checkBufferSize();\n }\n function checkBufferSize() {\n maxBufferSize !== void 0 && (pendingFragmentsLength + data.length <= maxBufferSize || (terminated = !0, pendingFragments.length = 0, pendingFragmentsLength = 0, id = void 0, data = \"\", dataLines = 0, eventType = void 0, onError(\n new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {\n type: \"max-buffer-size-exceeded\"\n })\n )));\n }\n function processLines(chunk) {\n let searchIndex = 0;\n if (chunk.indexOf(\"\\r\") === -1) {\n let lfIndex = chunk.indexOf(`\n`, searchIndex);\n for (; lfIndex !== -1; ) {\n if (searchIndex === lfIndex) {\n dataLines > 0 && onEvent({ id, event: eventType, data }), id = void 0, data = \"\", dataLines = 0, eventType = void 0, searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`\n`, searchIndex);\n continue;\n }\n const firstCharCode = chunk.charCodeAt(searchIndex);\n if (isDataPrefix(chunk, searchIndex, firstCharCode)) {\n const valueStart = chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5, value = chunk.slice(valueStart, lfIndex);\n if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {\n onEvent({ id, event: eventType, data: value }), id = void 0, data = \"\", eventType = void 0, searchIndex = lfIndex + 2, lfIndex = chunk.indexOf(`\n`, searchIndex);\n continue;\n }\n data = dataLines === 0 ? value : `${data}\n${value}`, dataLines++;\n } else isEventPrefix(chunk, searchIndex, firstCharCode) ? eventType = chunk.slice(\n chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,\n lfIndex\n ) || void 0 : parseLine(chunk, searchIndex, lfIndex);\n searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`\n`, searchIndex);\n }\n return chunk.slice(searchIndex);\n }\n for (; searchIndex < chunk.length; ) {\n const crIndex = chunk.indexOf(\"\\r\", searchIndex), lfIndex = chunk.indexOf(`\n`, searchIndex);\n let lineEnd = -1;\n if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = crIndex < lfIndex ? crIndex : lfIndex : crIndex !== -1 ? crIndex === chunk.length - 1 ? lineEnd = -1 : lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1)\n break;\n parseLine(chunk, searchIndex, lineEnd), searchIndex = lineEnd + 1, chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF && searchIndex++;\n }\n return chunk.slice(searchIndex);\n }\n function parseLine(chunk, start, end) {\n if (start === end) {\n dispatchEvent();\n return;\n }\n const firstCharCode = chunk.charCodeAt(start);\n if (isDataPrefix(chunk, start, firstCharCode)) {\n const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5, value2 = chunk.slice(valueStart, end);\n data = dataLines === 0 ? value2 : `${data}\n${value2}`, dataLines++;\n return;\n }\n if (isEventPrefix(chunk, start, firstCharCode)) {\n eventType = chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || void 0;\n return;\n }\n if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {\n const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);\n id = value2.includes(\"\\0\") ? void 0 : value2;\n return;\n }\n if (firstCharCode === 58) {\n if (onComment) {\n const line2 = chunk.slice(start, end);\n onComment(line2.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1));\n }\n return;\n }\n const line = chunk.slice(start, end), fieldSeparatorIndex = line.indexOf(\":\");\n if (fieldSeparatorIndex === -1) {\n processField(line, \"\", line);\n return;\n }\n const field = line.slice(0, fieldSeparatorIndex), offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1, value = line.slice(fieldSeparatorIndex + offset);\n processField(field, value, line);\n }\n function processField(field, value, line) {\n switch (field) {\n case \"event\":\n eventType = value || void 0;\n break;\n case \"data\":\n data = dataLines === 0 ? value : `${data}\n${value}`, dataLines++;\n break;\n case \"id\":\n id = value.includes(\"\\0\") ? void 0 : value;\n break;\n case \"retry\":\n /^\\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(\n new ParseError(`Invalid \\`retry\\` value: \"${value}\"`, {\n type: \"invalid-retry\",\n value,\n line\n })\n );\n break;\n default:\n onError(\n new ParseError(\n `Unknown field \"${field.length > 20 ? `${field.slice(0, 20)}\\u2026` : field}\"`,\n { type: \"unknown-field\", field, value, line }\n )\n );\n break;\n }\n }\n function dispatchEvent() {\n dataLines > 0 && onEvent({\n id,\n event: eventType,\n data\n }), id = void 0, data = \"\", dataLines = 0, eventType = void 0;\n }\n function reset(options = {}) {\n if (options.consume && pendingFragments.length > 0) {\n const incompleteLine = pendingFragments.join(\"\");\n parseLine(incompleteLine, 0, incompleteLine.length);\n }\n isFirstChunk = !0, id = void 0, data = \"\", dataLines = 0, eventType = void 0, pendingFragments.length = 0, pendingFragmentsLength = 0, terminated = !1;\n }\n return { feed, reset };\n}\nfunction isDataPrefix(chunk, i, firstCharCode) {\n return firstCharCode === 100 && chunk.charCodeAt(i + 1) === 97 && chunk.charCodeAt(i + 2) === 116 && chunk.charCodeAt(i + 3) === 97 && chunk.charCodeAt(i + 4) === 58;\n}\nfunction isEventPrefix(chunk, i, firstCharCode) {\n return firstCharCode === 101 && chunk.charCodeAt(i + 1) === 118 && chunk.charCodeAt(i + 2) === 101 && chunk.charCodeAt(i + 3) === 110 && chunk.charCodeAt(i + 4) === 116 && chunk.charCodeAt(i + 5) === 58;\n}\nexport {\n ParseError,\n createParser\n};\n//# sourceMappingURL=index.js.map\n","import { createParser } from 'eventsource-parser';\nimport { joinUrl } from '../core/url.js';\nimport {\n type RealtimeTransport,\n type TransportContext,\n UnauthorizedStreamError,\n} from './transport.js';\n\n/** Путь потока уведомлений. */\nexport const STREAM_PATH = '/api/notifications/stream';\n\n/** Настройки SSE-транспорта. */\nexport interface SseTransportOptions {\n /**\n * Сколько миллисекунд ждать данных, прежде чем считать соединение мёртвым.\n *\n * Сервер не присылает keep-alive, а оборванное TCP-соединение может не закрыться\n * само — без этой проверки поток «тихо умирает» и новых уведомлений не приходит.\n * По умолчанию 90 000. `0` отключает проверку.\n */\n idleTimeout?: number;\n /**\n * Сколько миллисекунд ждать ответа на запрос потока, прежде чем оборвать попытку.\n *\n * Проверка молчания ({@link idleTimeout}) начинается только после получения тела ответа.\n * Если `fetch` завис на установке соединения, без этого таймаута переподключение не\n * запустится до системного сетевого таймаута. По умолчанию 20 000. `0` отключает проверку.\n */\n handshakeTimeout?: number;\n}\n\n/**\n * Транспорт поверх Server-Sent Events.\n *\n * Нативный `EventSource` не подходит: он не умеет отправлять заголовок `Authorization`,\n * а поток требует Bearer-токен. Поэтому используется `fetch` с чтением тела по частям.\n *\n * Разбор кадров делегирован `eventsource-parser`, который корректно обрабатывает\n * многострочный `data:`, перевод строки `\\r\\n`, `data:` без пробела и кадр, разорванный\n * между сетевыми чанками. В разборе на сайте итд.com каждый из этих случаев обрабатывается\n * неверно.\n */\nexport class SseTransport implements RealtimeTransport {\n readonly name = 'sse';\n\n readonly #idleTimeout: number;\n readonly #handshakeTimeout: number;\n /** Идентификатор последнего события — отправляется при переподключении. */\n #lastEventId: string | undefined;\n\n constructor(options: SseTransportOptions = {}) {\n this.#idleTimeout = options.idleTimeout ?? 90_000;\n this.#handshakeTimeout = options.handshakeTimeout ?? 20_000;\n }\n\n async connect(context: TransportContext): Promise<void> {\n const token = await context.getToken();\n if (!token) throw new UnauthorizedStreamError();\n\n const url = joinUrl(context.baseUrl, STREAM_PATH);\n\n const headers = await context.baseHeaders(url);\n headers.set('Accept', 'text/event-stream');\n headers.set('Authorization', `Bearer ${token}`);\n headers.set('Cache-Control', 'no-cache');\n\n // Сервер пока не присылает id, но поддержка не мешает и пригодится, если начнёт.\n if (this.#lastEventId) headers.set('Last-Event-ID', this.#lastEventId);\n\n // Отдельный контроллер, связанный с сигналом потока: помимо внешней отмены он позволяет\n // оборвать зависшее рукопожатие своим таймаутом. Живёт до конца чтения тела.\n const controller = new AbortController();\n const relayAbort = () => controller.abort(context.signal.reason);\n if (context.signal.aborted) controller.abort(context.signal.reason);\n else context.signal.addEventListener('abort', relayAbort, { once: true });\n\n try {\n const response = await this.#handshake(url, headers, context, controller);\n\n if (response.status === 401) throw new UnauthorizedStreamError();\n if (!response.ok) throw new Error(`Поток уведомлений вернул статус ${response.status}`);\n if (!response.body) throw new Error('Ответ потока уведомлений пуст');\n\n context.onOpen();\n\n await this.#read(response.body, context);\n } finally {\n context.signal.removeEventListener('abort', relayAbort);\n }\n }\n\n /** Выполняет запрос потока, обрывая его, если ответ не пришёл за отведённое время. */\n async #handshake(\n url: string,\n headers: Headers,\n context: TransportContext,\n controller: AbortController,\n ): Promise<Response> {\n let expired = false;\n const timer =\n this.#handshakeTimeout > 0\n ? setTimeout(() => {\n expired = true;\n // Абортим связанный контроллер: `fetch` реджектит, и внешний код переподключится.\n controller.abort(new Error('Истёк таймаут рукопожатия SSE'));\n }, this.#handshakeTimeout)\n : undefined;\n\n try {\n return await context.fetch(url, { method: 'GET', headers, signal: controller.signal });\n } catch (error) {\n if (expired) throw new Error('Поток уведомлений не ответил: истёк таймаут рукопожатия');\n throw error;\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n }\n\n async #read(body: ReadableStream<Uint8Array>, context: TransportContext): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n\n const parser = createParser({\n onEvent: (message) => {\n if (message.id) this.#lastEventId = message.id;\n\n let data: unknown;\n try {\n data = JSON.parse(message.data);\n } catch (error) {\n // Одно битое сообщение не повод рвать соединение.\n context.onParseError(error, message.data);\n return;\n }\n\n // Имя события может отсутствовать — тогда тип лежит внутри полезной нагрузки.\n const name =\n message.event ??\n (typeof data === 'object' && data !== null && 'type' in data\n ? String((data as { type: unknown }).type)\n : 'message');\n\n context.onEvent({ name, data });\n },\n });\n\n let idleTimer: ReturnType<typeof setTimeout> | undefined;\n\n const armIdleTimer = () => {\n if (this.#idleTimeout <= 0) return;\n if (idleTimer !== undefined) clearTimeout(idleTimer);\n\n idleTimer = setTimeout(() => {\n // Отмена чтения завершит цикл, и внешний код переподключится.\n void reader.cancel(new Error('Поток молчит дольше допустимого')).catch(() => {});\n }, this.#idleTimeout);\n };\n\n armIdleTimer();\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n\n armIdleTimer();\n parser.feed(decoder.decode(value, { stream: true }));\n }\n } finally {\n if (idleTimer !== undefined) clearTimeout(idleTimer);\n reader.releaseLock?.();\n }\n }\n}\n","import type { AuthIdentity } from '../core/auth.js';\nimport { Emitter, type Listener, type Unsubscribe } from '../core/emitter.js';\nimport { ItdConfigError } from '../core/errors.js';\nimport { supportsStreamingBody } from '../core/runtime.js';\nimport { pickString } from '../core/unwrap.js';\nimport {\n type NotificationEvent,\n readNotificationEvent,\n readUnreadCountEvent,\n} from '../notifications/normalize.js';\nimport { RealtimeStatus } from '../types/enums.js';\nimport type { Logger } from '../types/options.js';\nimport { PollTransport } from './poll.js';\nimport { MAX_RECONNECT_ATTEMPTS, type ReconnectOptions, reconnectDelay } from './reconnect.js';\nimport { SseTransport } from './sse.js';\nimport {\n type RealtimeTransport,\n type TransportContext,\n UnauthorizedStreamError,\n} from './transport.js';\n\n/** События потока уведомлений. */\nexport interface RealtimeEvents {\n /** Пришло новое уведомление. */\n notification: NotificationEvent;\n /**\n * Сервер подтвердил подключение и назвал получателя событий.\n *\n * Приходит первым кадром сразу после установки соединения.\n */\n ready: { userId: string | undefined };\n /**\n * Получено актуальное число непрочитанных.\n *\n * При подключении клиент может запросить начальное значение через REST. Затем событие\n * возникает, только если счётчик пришёл в потоке. В остальных случаях обновляйте его\n * в приложении либо запрашивайте `itd.notifications.count()`.\n */\n unreadCount: number;\n /** Изменилось состояние соединения. */\n status: RealtimeStatus;\n /** Соединение оборвалось; будет предпринята попытка переподключения. */\n error: { error: unknown; willReconnect: boolean };\n /** Сообщение не удалось разобрать. Соединение при этом продолжает работать. */\n parseError: { error: unknown; raw: string };\n /** Запланировано переподключение. */\n reconnect: { attempt: number; delay: number };\n /** Попытки исчерпаны — соединение восстановится только ручным `connect()`. */\n giveup: undefined;\n /** Любое событие потока в необработанном виде, включая неизвестные библиотеке. */\n message: { name: string; data: unknown };\n}\n\n/** Способ получения событий. */\nexport const RealtimeTransportKind = Object.freeze({\n /** Поток событий, если среда умеет читать тело по частям, иначе опрос. */\n Auto: 'auto',\n /** Поток `text/event-stream`. */\n Sse: 'sse',\n /** Периодический опрос REST. */\n Poll: 'poll',\n} as const);\nexport type RealtimeTransportKind =\n (typeof RealtimeTransportKind)[keyof typeof RealtimeTransportKind];\n\n/** Настройки потока уведомлений. */\nexport interface RealtimeOptions extends ReconnectOptions {\n /**\n * Транспорт. По умолчанию `auto`: поток событий, если среда умеет читать тело ответа\n * по частям, иначе опрос.\n *\n * Можно передать и свою реализацию {@link RealtimeTransport} — это пригодится, если\n * у платформы появится WebSocket либо нужен нестандартный способ доставки.\n */\n transport?: RealtimeTransportKind | RealtimeTransport;\n /**\n * Молчание сервера, после которого соединение считается мёртвым, мс. По умолчанию 90 000.\n *\n * Сервер не присылает keep-alive, поэтому без этой проверки оборванное соединение\n * может незаметно «зависнуть».\n */\n idleTimeout?: number;\n /**\n * Сколько ждать ответа на запрос потока, прежде чем оборвать попытку, мс. По умолчанию\n * 20 000. Защищает от зависания на установке соединения, когда {@link idleTimeout} ещё\n * не действует. `0` отключает проверку. Только для потокового транспорта.\n */\n handshakeTimeout?: number;\n /** Как часто опрашивать сервер, если используется запасной транспорт. */\n pollInterval?: number;\n /**\n * Запрашивать число непрочитанных при подключении. По умолчанию `true`.\n *\n * Так поступает сайт итд.com: поток присылает только новые события, а начальное\n * значение счётчика нужно получить отдельно.\n */\n syncCount?: boolean;\n /**\n * Переподключаться, когда вкладка снова становится видимой. По умолчанию `true`.\n *\n * Только в браузере. У сайта итд.com такой обработки нет, из-за чего вкладка,\n * пролежавшая в фоне, может остаться без соединения.\n */\n reconnectOnVisible?: boolean;\n /** Переподключаться при восстановлении сети. По умолчанию `true`. Только в браузере. */\n reconnectOnOnline?: boolean;\n}\n\n/** Проверяет настройки потока. @throws {ItdConfigError} при некорректных значениях */\nfunction validateRealtimeOptions(options: RealtimeOptions): void {\n const positiveInteger = (value: number | undefined, name: string): void => {\n if (value === undefined) return;\n if (!Number.isInteger(value) || value < 0) {\n throw new ItdConfigError(\n `realtime.${name} должен быть целым неотрицательным числом, получено: ${value}`,\n );\n }\n };\n\n const duration = (value: number | undefined, name: string, min: number): void => {\n if (value === undefined) return;\n if (!Number.isFinite(value) || value < min) {\n throw new ItdConfigError(\n `realtime.${name} должен быть числом не меньше ${min}, получено: ${value}`,\n );\n }\n };\n\n positiveInteger(options.maxAttempts, 'maxAttempts');\n duration(options.pollInterval, 'pollInterval', 1);\n duration(options.idleTimeout, 'idleTimeout', 0);\n duration(options.handshakeTimeout, 'handshakeTimeout', 0);\n\n if (options.jitter !== undefined && !(options.jitter >= 0 && options.jitter <= 1)) {\n throw new ItdConfigError(\n `realtime.jitter должен быть в диапазоне 0…1, получено: ${options.jitter}`,\n );\n }\n\n if (options.backoff !== undefined) {\n if (!Array.isArray(options.backoff) || options.backoff.length === 0) {\n throw new ItdConfigError('realtime.backoff должен быть непустым списком пауз');\n }\n for (const delay of options.backoff) duration(delay, 'backoff', 0);\n }\n}\n\n/** Что поток получает от клиента. */\nexport interface RealtimeDeps {\n baseUrl: string;\n fetch: typeof fetch;\n /** Общие заголовки клиента для адреса — см. {@link TransportContext.baseHeaders}. */\n baseHeaders: (url: string) => Promise<Headers>;\n /** Идентификаторы аккаунта и сессии создавшего поток клиента. */\n getAuthIdentity?: (() => AuthIdentity) | undefined;\n /** Непрозрачная область авторизации создавшего поток клиента. */\n getAuthScope?: (() => string) | undefined;\n getToken: () => Promise<string | null>;\n /** Обновляет токен после отказа авторизации. Возвращает `true`, если удалось. */\n refresh: () => Promise<boolean>;\n /** Загружает начальное число непрочитанных. */\n fetchUnreadCount: () => Promise<number>;\n /** Вызывается при явном закрытии потока. */\n onClose?: (() => void) | undefined;\n logger?: Logger | undefined;\n}\n\n/**\n * Поток уведомлений в реальном времени.\n *\n * Получается вызовом `itd.realtime()`. Соединение поднимается методом {@link connect}\n * и держится само: обрывы, обновление токена и повторные попытки библиотека берёт на себя.\n *\n * @example\n * ```ts\n * const stream = itd.realtime();\n *\n * stream.on('notification', ({ notification, unreadCount }) => {\n * console.log(formatNotificationText(notification), unreadCount);\n * });\n * stream.on('status', (status) => console.log('соединение:', status));\n *\n * await stream.connect();\n * // …позже\n * stream.disconnect();\n * ```\n */\nexport class ItdRealtime {\n readonly #deps: RealtimeDeps;\n readonly #options: RealtimeOptions;\n readonly #emitter: Emitter<RealtimeEvents>;\n readonly #transport: RealtimeTransport;\n readonly #maxAttempts: number;\n\n #controller: AbortController | undefined;\n /**\n * Хочет ли вызывающий код, чтобы соединение было живо.\n *\n * Отдельно от `#controller`, потому что тот появляется только после `await` внутри\n * {@link connect}. Без этого флага два вызова подряд проскочили бы проверку оба\n * и подняли два соединения, а `disconnect()` во время ожидания счётчика не был бы\n * замечен и соединение поднялось бы уже после отмены.\n */\n #wanted = false;\n #status: RealtimeStatus = RealtimeStatus.Disconnected;\n #attempt = 0;\n #timer: ReturnType<typeof setTimeout> | undefined;\n #detachEnvironment: (() => void) | undefined;\n\n constructor(deps: RealtimeDeps, options: RealtimeOptions = {}) {\n validateRealtimeOptions(options);\n\n this.#deps = deps;\n this.#options = options;\n this.#maxAttempts = options.maxAttempts ?? MAX_RECONNECT_ATTEMPTS;\n this.#transport = this.#createTransport();\n // Исключение из пользовательского обработчика — в логгер, при его отсутствии в консоль.\n this.#emitter = new Emitter<RealtimeEvents>((error) => {\n const message = 'Ошибка в обработчике события realtime';\n if (deps.logger) deps.logger.error(message, error);\n else console.error(`[itd-api] ${message}`, error);\n });\n }\n\n /** Текущее состояние соединения. */\n get status(): RealtimeStatus {\n return this.#status;\n }\n\n /** Какой транспорт используется: `sse` или `poll`. */\n get transport(): string {\n return this.#transport.name;\n }\n\n /** Базовый URL клиента, создавшего поток. @internal */\n get baseUrl(): string {\n return this.#deps.baseUrl;\n }\n\n /** Идентификаторы аккаунта и сессии клиента, создавшего поток. @internal */\n getAuthIdentity(): AuthIdentity | undefined {\n return this.#deps.getAuthIdentity?.();\n }\n\n /** Непрозрачная область авторизации создавшего поток клиента. @internal */\n getAuthScope(): string | undefined {\n return this.#deps.getAuthScope?.();\n }\n\n /** Подписывается на событие потока. @returns функция отписки */\n on<K extends keyof RealtimeEvents>(event: K, listener: Listener<RealtimeEvents[K]>): Unsubscribe {\n return this.#emitter.on(event, listener);\n }\n\n /** Подписывается на одно срабатывание. */\n once<K extends keyof RealtimeEvents>(\n event: K,\n listener: Listener<RealtimeEvents[K]>,\n ): Unsubscribe {\n return this.#emitter.once(event, listener);\n }\n\n /**\n * Поднимает соединение.\n *\n * Повторный вызов при уже живом соединении ничего не делает — это защита от двойного\n * подключения при перерисовке интерфейса.\n *\n * Возвращает управление сразу после запуска: соединение живёт в фоне.\n */\n async connect(): Promise<void> {\n if (this.#wanted) return;\n this.#wanted = true;\n\n this.#attachEnvironmentListeners();\n\n if (this.#options.syncCount !== false) {\n try {\n this.#emitter.emit('unreadCount', await this.#deps.fetchUnreadCount());\n } catch (error) {\n // Начальный счётчик — вспомогательная величина, из-за неё поток не отменяется.\n this.#deps.logger?.debug('не удалось получить число непрочитанных', error);\n }\n }\n\n // Пока ждали счётчик, могли успеть вызвать disconnect().\n if (this.#wanted) this.#run();\n }\n\n /** Закрывает соединение и отменяет запланированные попытки. */\n disconnect(): void {\n this.#wanted = false;\n\n if (this.#timer !== undefined) {\n clearTimeout(this.#timer);\n this.#timer = undefined;\n }\n\n this.#detachEnvironment?.();\n this.#detachEnvironment = undefined;\n\n this.#controller?.abort();\n this.#controller = undefined;\n this.#attempt = 0;\n\n this.#setStatus(RealtimeStatus.Disconnected);\n this.#deps.onClose?.();\n }\n\n /** Снимает все подписки. Соединение при этом не закрывается. */\n removeAllListeners(): void {\n this.#emitter.removeAllListeners();\n }\n\n #createTransport(): RealtimeTransport {\n const kind = this.#options.transport ?? RealtimeTransportKind.Auto;\n\n if (typeof kind === 'object') return kind;\n\n if (\n kind === RealtimeTransportKind.Poll ||\n (kind === RealtimeTransportKind.Auto && !supportsStreamingBody())\n ) {\n return new PollTransport({\n ...(this.#options.pollInterval !== undefined\n ? { interval: this.#options.pollInterval }\n : {}),\n });\n }\n\n return new SseTransport({\n ...(this.#options.idleTimeout !== undefined\n ? { idleTimeout: this.#options.idleTimeout }\n : {}),\n ...(this.#options.handshakeTimeout !== undefined\n ? { handshakeTimeout: this.#options.handshakeTimeout }\n : {}),\n });\n }\n\n /** Запускает попытку подключения; повторы планирует сам. */\n #run(): void {\n if (!this.#wanted) return;\n\n // Страховка от потерянного соединения: если предыдущее ещё живо, закрываем его,\n // иначе его AbortController остался бы недостижимым и поток — незакрытым.\n this.#controller?.abort();\n\n const controller = new AbortController();\n this.#controller = controller;\n this.#setStatus(RealtimeStatus.Connecting);\n\n void this.#transport\n .connect({\n baseUrl: this.#deps.baseUrl,\n fetch: this.#deps.fetch,\n baseHeaders: this.#deps.baseHeaders,\n getToken: this.#deps.getToken,\n signal: controller.signal,\n onOpen: () => {\n this.#attempt = 0;\n this.#setStatus(RealtimeStatus.Connected);\n },\n onEvent: (event) => this.#handleEvent(event.name, event.data),\n onParseError: (error, raw) => this.#emitter.emit('parseError', { error, raw }),\n })\n .then(\n () => {\n // Штатное закрытие потока — тоже повод переподключиться.\n if (!controller.signal.aborted) {\n this.#handleFailure(new Error('Соединение с потоком уведомлений закрыто'));\n }\n },\n (error: unknown) => {\n if (controller.signal.aborted) return;\n this.#handleFailure(error);\n },\n );\n }\n\n #handleEvent(name: string, data: unknown): void {\n this.#emitter.emit('message', { name, data });\n\n if (name === 'connected') {\n // Строго строка: `String(null)` дал бы подписчику осмысленно выглядящее «null».\n this.#emitter.emit('ready', { userId: pickString(data, 'userId') });\n return;\n }\n\n if (name === 'notification') {\n const event = readNotificationEvent(data);\n this.#emitter.emit('notification', event);\n\n // Счётчик обновляется только если сервер его прислал: сам библиотека не считает.\n if (event.unreadCount !== undefined) this.#emitter.emit('unreadCount', event.unreadCount);\n return;\n }\n\n if (name === 'unread_count') {\n const count = readUnreadCountEvent(data);\n // Событие без вложенного payload не содержит актуального счётчика и игнорируется.\n if (count !== undefined) this.#emitter.emit('unreadCount', count);\n }\n }\n\n #handleFailure(error: unknown): void {\n this.#controller = undefined;\n\n if (error instanceof UnauthorizedStreamError) {\n void this.#refreshAndReconnect(error);\n return;\n }\n\n this.#setStatus(RealtimeStatus.Error);\n this.#scheduleReconnect(error);\n }\n\n /** Обновляет токен и переподключается; при неудаче прекращает попытки. */\n async #refreshAndReconnect(error: unknown): Promise<void> {\n this.#setStatus(RealtimeStatus.Error);\n\n const refreshed = await this.#deps.refresh().catch(() => false);\n\n if (!this.#wanted) return;\n\n if (!refreshed) {\n this.#giveUp(error);\n return;\n }\n\n this.#scheduleReconnect(error);\n }\n\n #scheduleReconnect(error: unknown): void {\n if (!this.#wanted) return;\n\n if (this.#attempt >= this.#maxAttempts) {\n this.#giveUp(error);\n return;\n }\n\n const delay = reconnectDelay(this.#attempt, this.#options);\n this.#attempt += 1;\n\n this.#emitter.emit('error', { error, willReconnect: true });\n this.#emitter.emit('reconnect', { attempt: this.#attempt, delay });\n\n this.#timer = setTimeout(() => {\n this.#timer = undefined;\n this.#run();\n }, delay);\n }\n\n /** Завершает автоматические попытки переподключения. */\n #giveUp(error: unknown): void {\n this.#wanted = false;\n this.#attempt = 0;\n\n this.#detachEnvironment?.();\n this.#detachEnvironment = undefined;\n\n this.#emitter.emit('error', { error, willReconnect: false });\n this.#emitter.emit('giveup', undefined);\n }\n\n /**\n * Подписывается на события среды.\n *\n * Возврат вкладки из фона и восстановление сети — самые частые причины «мёртвого»\n * соединения. У сайта итд.com такой обработки нет.\n */\n #attachEnvironmentListeners(): void {\n if (this.#detachEnvironment) return;\n\n const target = globalThis as unknown as {\n addEventListener?: (type: string, listener: () => void) => void;\n removeEventListener?: (type: string, listener: () => void) => void;\n document?: { visibilityState?: string };\n };\n\n if (typeof target.addEventListener !== 'function') return;\n\n const wake = () => {\n // Реагируем, только если соединения сейчас нет и попытка не запланирована.\n if (this.#controller || this.#timer !== undefined) return;\n if (this.#status === RealtimeStatus.Disconnected) return;\n\n this.#attempt = 0;\n this.#run();\n };\n\n const onVisibility = () => {\n if (target.document?.visibilityState === 'visible') wake();\n };\n\n const listeners: [string, () => void][] = [];\n\n if (this.#options.reconnectOnVisible !== false && target.document) {\n listeners.push(['visibilitychange', onVisibility]);\n }\n if (this.#options.reconnectOnOnline !== false) {\n listeners.push(['online', wake]);\n }\n\n for (const [type, listener] of listeners) target.addEventListener(type, listener);\n\n this.#detachEnvironment = () => {\n for (const [type, listener] of listeners) target.removeEventListener?.(type, listener);\n };\n }\n\n #setStatus(status: RealtimeStatus): void {\n if (this.#status === status) return;\n this.#status = status;\n this.#emitter.emit('status', status);\n }\n}\n","import { ItdConfigError } from './errors.js';\nimport { pickArray, pickBoolean, pickNumber, pickObject, pickString } from './unwrap.js';\n\n/**\n * Страница списка — единая форма для всех трёх схем пагинации API.\n *\n * Какие необязательные поля заполнены, зависит от эндпоинта: у ленты это `nextCursor`,\n * у подписчиков — `page` и `total`, у уведомлений — `nextOffset`. Обычно они не нужны:\n * перебор берёт на себя {@link Paginator}.\n */\nexport interface Page<T> {\n /** Элементы страницы. */\n items: T[];\n /** Есть ли следующая страница. */\n hasMore: boolean;\n /**\n * Курсор следующей страницы.\n *\n * Непрозрачен: у вкладки `popular` это номер страницы, у `following` — отметка времени.\n * Передавайте его обратно как есть и не пытайтесь разобрать.\n */\n nextCursor?: string | null | undefined;\n /** Номер текущей страницы при постраничной схеме. */\n page?: number | undefined;\n /** Запрошенный размер страницы. */\n limit?: number | undefined;\n /** Общее число элементов, если сервер его сообщил. */\n total?: number | undefined;\n /** Смещение для следующего запроса при схеме со смещением. */\n nextOffset?: number | undefined;\n /** Исходный ответ — на случай, если документация разошлась с реальностью. */\n raw: unknown;\n}\n\n/** Схема пагинации эндпоинта. */\nexport const PaginationMode = Object.freeze({\n /** Следующая страница запрашивается непрозрачным курсором. */\n Cursor: 'cursor',\n /** Следующая страница запрашивается номером. */\n Page: 'page',\n /** Следующая страница запрашивается смещением от начала списка. */\n Offset: 'offset',\n} as const);\nexport type PaginationMode = (typeof PaginationMode)[keyof typeof PaginationMode];\n\n/** Применяет преобразование к элементам страницы, сохраняя сведения о пагинации. */\nexport function mapPage<T, R>(page: Page<T>, map: (item: T) => R): Page<R> {\n return { ...page, items: page.items.map(map) };\n}\n\n/** Позиция, с которой запрашивается очередная страница. */\nexport interface PageState {\n cursor?: string | undefined;\n page?: number | undefined;\n offset?: number | undefined;\n}\n\n/** Настройки перебора страниц. */\nexport interface PaginatorOptions<T> {\n mode: PaginationMode;\n /** Загружает одну страницу для указанной позиции. */\n load: (state: PageState) => Promise<Page<T>>;\n /**\n * С какой позиции начать. По умолчанию с начала списка.\n *\n * Нужно, чтобы перебор можно было продолжить с сохранённого курсора, а не только\n * начать заново.\n */\n start?: PageState | undefined;\n /**\n * Предохранитель от бесконечного перебора. По умолчанию 1000.\n *\n * Сработает, только если сервер бесконечно сообщает `hasMore` — при нормальной работе\n * перебор останавливается сам.\n */\n maxPages?: number | undefined;\n /** Отмена перебора. */\n signal?: AbortSignal | undefined;\n}\n\n/**\n * Достаёт список из ответа, перебирая все формы, которые встречаются у сервера.\n *\n * Список может прийти под именем сущности, под её альтернативным именем\n * (`followers` вместо `users`) либо голым массивом.\n *\n * @param fields имена полей в порядке предпочтения\n */\nfunction readItems<T>(body: unknown, fields: readonly string[]): T[] {\n if (Array.isArray(body)) return body as T[];\n\n for (const field of fields) {\n const items = pickArray<T>(body, field);\n if (items.length > 0) return items;\n }\n\n // Пустой список тоже валиден: возвращаем по первому известному имени.\n const primary = fields[0];\n return primary === undefined ? [] : pickArray<T>(body, primary);\n}\n\n/**\n * Достаёт курсор следующей страницы.\n *\n * Проверяются все известные места: внутри `pagination`, рядом со списком и во вложенном\n * объекте `meta.cursor.next`.\n */\nfunction readCursor(body: unknown): string | null {\n const pagination = pickObject(body, 'pagination');\n const meta = pickObject(body, 'meta');\n const metaCursor = pickObject(meta, 'cursor');\n\n return (\n pickString(pagination, 'nextCursor') ??\n pickString(body, 'nextCursor') ??\n pickString(body, 'cursor') ??\n pickString(metaCursor, 'next') ??\n null\n );\n}\n\n/**\n * Читает страницу курсорной схемы.\n *\n * Используется лентой, постами пользователя и постами по хэштегу.\n *\n * @param fields имена возможных полей со списком; первое — основное\n */\nexport function readCursorPage<T>(body: unknown, ...fields: string[]): Page<T> {\n const pagination = pickObject(body, 'pagination');\n const items = readItems<T>(body, fields);\n const nextCursor = readCursor(body);\n\n return {\n items,\n // Если признака продолжения нет, ориентируемся на наличие курсора.\n hasMore: pickBoolean(pagination, 'hasMore', pickBoolean(body, 'hasMore', nextCursor !== null)),\n nextCursor,\n limit: pickNumber(pagination, 'limit', 0) || undefined,\n raw: body,\n };\n}\n\n/**\n * Читает страницу курсорной схемы с курсором рядом со списком.\n *\n * Так устроены комментарии к посту и ответы на комментарий: `nextCursor` и `hasMore`\n * лежат на одном уровне со списком, а не внутри объекта `pagination`.\n */\nexport function readFlatCursorPage<T>(body: unknown, ...fields: string[]): Page<T> {\n const items = readItems<T>(body, fields);\n const nextCursor = readCursor(body);\n const total = pickNumber(body, 'total', -1);\n\n return {\n items,\n hasMore: pickBoolean(body, 'hasMore', nextCursor !== null),\n nextCursor,\n ...(total >= 0 ? { total } : {}),\n raw: body,\n };\n}\n\n/**\n * Читает страницу постраничной схемы: `data.pagination.{page,limit,total,hasMore}`.\n *\n * Если сервер вместо этого прислал курсор, он тоже подхватывается — некоторые списки\n * отвечают в курсорной форме, хотя документация описывает постраничную.\n */\nexport function readPagedPage<T>(body: unknown, ...fields: string[]): Page<T> {\n const pagination = pickObject(body, 'pagination');\n const items = readItems<T>(body, fields);\n const nextCursor = readCursor(body);\n\n return {\n items,\n hasMore: pickBoolean(pagination, 'hasMore', pickBoolean(body, 'hasMore', nextCursor !== null)),\n page: pickNumber(pagination, 'page', 1),\n limit: pickNumber(pagination, 'limit', 0) || undefined,\n total: pickNumber(pagination, 'total', 0),\n ...(nextCursor !== null ? { nextCursor } : {}),\n raw: body,\n };\n}\n\n/**\n * Читает страницу схемы со смещением.\n *\n * Используется для списков со смещением, включая уведомления.\n */\nexport function readOffsetPage<T>(body: unknown, field: string, offset: number): Page<T> {\n const items = pickArray<T>(body, field);\n\n return {\n items,\n hasMore: pickBoolean(body, 'hasMore'),\n nextOffset: offset + items.length,\n raw: body,\n };\n}\n\n/**\n * Перебор страниц списка.\n *\n * Скрывает различия трёх схем пагинации: перебор элементов, страниц и сбор в массив\n * выглядят одинаково независимо от эндпоинта.\n *\n * **Одноразовый.** Позиция хранится внутри, поэтому повторный перебор того же объекта\n * ничего не вернёт: он продолжится с места, где закончился прошлый. Нужен второй проход —\n * возьмите новый перебор у того же метода ресурса.\n *\n * @example Перебор элементов\n * ```ts\n * for await (const post of itd.posts.iterate({ tab: 'following' })) {\n * console.log(post.content);\n * }\n * ```\n *\n * @example Первые сто элементов\n * ```ts\n * const posts = await itd.posts.iterate({ tab: 'popular' }).collect(100);\n * ```\n *\n * @example Постранично\n * ```ts\n * for await (const page of itd.users.followers('nowkie').pages()) {\n * console.log(page.items.length, 'из', page.total);\n * }\n * ```\n */\nexport class Paginator<T> implements AsyncIterable<T> {\n readonly #options: PaginatorOptions<T>;\n readonly #maxPages: number;\n\n #state: PageState;\n #finished = false;\n #pagesLoaded = 0;\n\n constructor(options: PaginatorOptions<T>) {\n if (\n options.maxPages !== undefined &&\n (!Number.isInteger(options.maxPages) || options.maxPages < 1)\n ) {\n throw new ItdConfigError(\n `maxPages должен быть целым числом не меньше 1, получено: ${options.maxPages}`,\n );\n }\n\n this.#options = options;\n this.#maxPages = options.maxPages ?? 1000;\n this.#state = options.start ?? {};\n }\n\n /**\n * Загружает следующую страницу.\n *\n * @returns страница либо `null`, если перебор закончен\n */\n async next(): Promise<Page<T> | null> {\n if (this.#finished) return null;\n if (this.#options.signal?.aborted) return null;\n\n if (this.#pagesLoaded >= this.#maxPages) {\n this.#finished = true;\n return null;\n }\n\n const previous = this.#state;\n const page = await this.#options.load(previous);\n this.#pagesLoaded += 1;\n\n this.#state = this.#advance(previous, page);\n return page;\n }\n\n /**\n * Перебирает страницы целиком.\n *\n * Полезно, когда нужны сведения о самой странице — например `total`.\n */\n async *pages(): AsyncGenerator<Page<T>, void, undefined> {\n for (;;) {\n const page = await this.next();\n if (!page) return;\n yield page;\n }\n }\n\n /** Перебирает элементы всех страниц подряд. */\n async *[Symbol.asyncIterator](): AsyncGenerator<T, void, undefined> {\n for await (const page of this.pages()) {\n for (const item of page.items) {\n if (this.#options.signal?.aborted) return;\n yield item;\n }\n }\n }\n\n /**\n * Собирает элементы в массив.\n *\n * @param max сколько элементов достаточно; без него перебираются все страницы\n */\n async collect(max?: number): Promise<T[]> {\n // Ноль и отрицательное число — «ничего не нужно»: не делаем ни одного запроса.\n if (max !== undefined && max <= 0) return [];\n\n const result: T[] = [];\n\n for await (const item of this) {\n result.push(item);\n if (max !== undefined && result.length >= max) break;\n }\n\n return result;\n }\n\n /**\n * Вычисляет позицию следующей страницы и решает, продолжать ли.\n *\n * Здесь же стоят предохранители: пустая страница при `hasMore`, неизменившийся курсор\n * и отсутствие курсора останавливают перебор. Без них ошибка на сервере превратилась бы\n * в бесконечный цикл запросов.\n */\n #advance(previous: PageState, page: Page<T>): PageState {\n if (!page.hasMore || page.items.length === 0) {\n this.#finished = true;\n return previous;\n }\n\n if (this.#options.mode === PaginationMode.Cursor) {\n const cursor = page.nextCursor ?? undefined;\n\n if (!cursor || cursor === previous.cursor) {\n this.#finished = true;\n return previous;\n }\n\n return { cursor };\n }\n\n if (this.#options.mode === PaginationMode.Page) {\n return { page: (previous.page ?? 1) + 1 };\n }\n\n return { offset: page.nextOffset ?? (previous.offset ?? 0) + page.items.length };\n }\n}\n","import type { RuntimeMode } from '../core/runtime.js';\nimport type { ServiceDefinition } from '../core/services.js';\nimport type { TokenStorage } from '../core/storage.js';\nimport type { QueryParams } from '../core/url.js';\n\n/**\n * Вход по логину и паролю.\n *\n * Вход требует токен капчи Cloudflare Turnstile, поэтому полностью автоматическим он быть\n * не может: капчу должен решить кто-то снаружи. Токен одноразовый и живёт несколько минут,\n * так что долгоживущему клиенту нужен `getTurnstileToken` — он спрашивается заново перед\n * каждой попыткой входа. Одиночный `turnstileToken` годится для разового скрипта.\n */\nexport interface CredentialsAuth {\n email: string;\n password: string;\n /** Разовый токен капчи. Для повторного входа после истечения сессии не подойдёт. */\n turnstileToken?: string | undefined;\n /** Источник свежего токена капчи. Спрашивается перед каждым входом. */\n getTurnstileToken?: (() => string | Promise<string>) | undefined;\n}\n\n/**\n * Как клиент получает доступ к API.\n *\n * Четыре формы — от разового вызова с готовым токеном до полноценной сессии, которую\n * библиотека заводит и продлевает сама.\n *\n * Опция необязательна: если {@link ItdClientOptions.storage} уже содержит сессию, доступ\n * берётся оттуда. Когда заданы обе, хранилище главнее — оно отражает текущее состояние\n * сессии, — а недостающие поля берутся отсюда.\n *\n * @example\n * ```ts\n * new ItdClient({ auth: '<accessToken>' }); // разовый вызов\n * new ItdClient({ auth: { accessToken, refreshToken } }); // восстановить сессию\n * new ItdClient({ auth: { email, password, getTurnstileToken } }); // залогиниться самому\n * new ItdClient({ auth: { getToken: () => vault.read() } }); // токен из внешнего источника\n * ```\n */\nexport type AuthInput =\n | string\n | { accessToken: string; refreshToken?: string | undefined }\n | CredentialsAuth\n | { getToken: () => string | null | Promise<string | null> };\n\n/** Куда библиотека пишет отладочные сообщения. Совместим с `console`. */\nexport interface Logger {\n debug(message: string, ...args: unknown[]): void;\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n\n/** Настройки повторных попыток. */\nexport interface RetryOptions {\n /** Сколько всего попыток, включая первую. По умолчанию 3. */\n attempts?: number | undefined;\n /** Базовая пауза в мс, удваивается с каждой попыткой. По умолчанию 500. */\n baseDelay?: number | undefined;\n /** Верхняя граница паузы в мс. По умолчанию 30000. */\n maxDelay?: number | undefined;\n /** Доля случайного разброса паузы, 0…1. По умолчанию 0.3. */\n jitter?: number | undefined;\n /**\n * Повторять ли запись (`POST`, `PUT`, `PATCH`, `DELETE`) при сетевых сбоях и `5xx`.\n *\n * По умолчанию `false`: сервер мог успеть выполнить операцию до обрыва, и повтор\n * создаст дубль поста или лишнюю жалобу. Ответ `429` повторяется всегда — он гарантирует,\n * что запрос не был обработан.\n */\n retryWrites?: boolean | undefined;\n /** Своя логика: вернуть `true`, чтобы повторить. Заменяет правила по умолчанию. */\n shouldRetry?: ((error: unknown, attempt: number) => boolean) | undefined;\n}\n\n/** Настройки ограничения нагрузки на API. */\nexport interface RateLimitOptions {\n /** Сколько запросов выполняется одновременно. По умолчанию 6. */\n concurrency?: number | undefined;\n /** Верхняя граница запросов в секунду. По умолчанию без ограничения. */\n rps?: number | undefined;\n /**\n * Паузы перед повторами при ответе `429`, мс.\n * По умолчанию `[1000, 5000, 30000, 60000, 90000]`.\n *\n * Сервер не сообщает, когда сбросится окно лимита, поэтому паузу приходится подбирать.\n * Лестница начинается с секунды: если окно почти истекло, работа продолжится почти\n * сразу, а если лимит исчерпан всерьёз — паузы дорастут до полутора минут.\n * Когда лестница закончилась, {@link ItdRateLimitError} пробрасывается вызывающему коду.\n *\n * Этот список не зависит от `retry.attempts`: тот управляет повторами при обрывах\n * сети и ошибках сервера, где уместен совсем другой темп.\n */\n retryDelays?: readonly number[] | undefined;\n /**\n * Тормозить ли очередь по заголовкам ответа. По умолчанию `true`.\n *\n * Выключите, если управляете темпом сами.\n */\n respectHeaders?: boolean | undefined;\n}\n\n/** Данные о запросе, доступные хукам. */\nexport interface RequestContext {\n method: string;\n /** Путь без базового URL, например `/api/posts`. */\n path: string;\n /** Итоговый URL со строкой запроса. */\n url: string;\n headers: Headers;\n /** Номер попытки, начиная с 1. */\n attempt: number;\n}\n\n/** Данные об успешном ответе. */\nexport interface ResponseContext extends RequestContext {\n status: number;\n /** Длительность запроса в мс. */\n duration: number;\n response: Response;\n}\n\n/** Данные об ошибке запроса. */\nexport interface ErrorContextHook extends RequestContext {\n duration: number;\n error: unknown;\n}\n\n/** Данные о предстоящем повторе. */\nexport interface RetryContext extends RequestContext {\n error: unknown;\n /** Пауза перед следующей попыткой в мс. */\n delay: number;\n}\n\n/**\n * Перехватчики жизненного цикла запроса.\n *\n * Вызываются последовательно; исключение внутри хука прервёт запрос, поэтому свою логику\n * лучше оборачивать в `try`.\n */\nexport interface ClientHooks {\n /** Перед отправкой. Можно дописать заголовки — объект `headers` изменяемый. */\n onRequest?(context: RequestContext): void | Promise<void>;\n /** После успешного ответа, до разбора тела. */\n onResponse?(context: ResponseContext): void | Promise<void>;\n /** При любой ошибке запроса, включая те, что будут повторены. */\n onError?(context: ErrorContextHook): void | Promise<void>;\n /** Перед паузой между попытками. */\n onRetry?(context: RetryContext): void | Promise<void>;\n}\n\n/**\n * Опции конструктора `ItdClient`.\n *\n * Все поля допускают явный `undefined`, чтобы можно было передавать значения, которых\n * может не быть, — например `new ItdClient({ auth: process.env.ITD_TOKEN })`.\n */\nexport interface ItdClientOptions {\n /**\n * Базовый URL API. По умолчанию `https://xn--d1ah4a.com`.\n *\n * Укажите здесь адрес своего прокси, если работаете из браузера: CORS для сторонних\n * источников на итд.com, скорее всего, не настроен.\n */\n baseUrl?: string | undefined;\n /**\n * Сервисы платформы на отдельных доменах.\n *\n * Ключ — имя сервиса, значение — базовый URL или определение целиком. Имя встроенного\n * сервиса задаёт его хост. Встроен один: `status` — хост `itd.platform.status()`.\n *\n * @example\n * ```ts\n * const itd = new ItdClient({\n * services: {\n * status: 'https://my-proxy.example/status',\n * pb: { baseUrl: 'https://pbapi.xn--d1ah4a.com', headers: { Referer: 'https://pixel.xn--d1ah4a.com/' } },\n * },\n * });\n * ```\n */\n services?: Record<string, string | Omit<ServiceDefinition, 'name'>> | undefined;\n /** Авторизация. Без неё доступны только публичные эндпоинты. */\n auth?: AuthInput | undefined;\n /** Где хранить сессию. По умолчанию {@link MemoryTokenStorage}. */\n storage?: TokenStorage | undefined;\n /**\n * Обновлять токен автоматически при ответе `401`. По умолчанию `true`.\n *\n * При `false` библиотека просто пробросит {@link ItdAuthError}, а обновлением\n * вы управляете сами через `itd.auth.refresh()`.\n */\n autoRefresh?: boolean | undefined;\n /**\n * Пытаться ли войти заново, если обновление токена не удалось.\n *\n * Работает, только когда в `auth` переданы email и пароль. По умолчанию `true`.\n */\n reloginOnRefreshFailure?: boolean | undefined;\n /** Своя реализация `fetch`: для Deno, React Native, тестов или прокси. */\n fetch?: typeof fetch | undefined;\n /** Таймаут запроса в мс. По умолчанию 30000 — столько же использует сайт итд.com. `0` снимает ограничение. */\n timeout?: number | undefined;\n /** Повторные попытки. `false` отключает их полностью. */\n retry?: RetryOptions | false | undefined;\n /** Ограничение нагрузки. `false` отключает очередь. */\n rateLimit?: RateLimitOptions | false | undefined;\n /** Перехватчики запросов. */\n hooks?: ClientHooks | undefined;\n /** Отладочный вывод. `true` — писать в `console`. */\n logger?: Logger | boolean | undefined;\n /** Заголовки, добавляемые ко всем запросам, — например `User-Agent` для бота. */\n headers?: Record<string, string> | undefined;\n /**\n * Значение заголовка `X-Device-Id`, который уходит с каждым запросом.\n *\n * Сервер различает по нему записи в списке сессий, поэтому значение должно быть стабильным.\n * Если не задать, библиотека заведёт идентификатор сама и сохранит его в {@link ItdSession},\n * так что при постоянном хранилище он переживёт перезапуск процесса.\n */\n deviceId?: string | undefined;\n /**\n * Значение заголовка `User-Agent`. `false` — не отправлять его вовсе.\n *\n * По умолчанию `Mozilla/5.0 (compatible; itd-api/<версия>; …)`: `fetch` в Node не шлёт\n * `User-Agent` сам, а сайт стоит за DDoS-Guard, который такие запросы может не пропустить.\n * В браузере опция не действует — там заголовок менять запрещено.\n */\n userAgent?: string | false | undefined;\n /** Как обращаться с cookie. По умолчанию определяется по среде исполнения. */\n mode?: RuntimeMode | undefined;\n}\n\n/** Опции отдельного запроса. Доступны в каждом методе ресурсов. */\nexport interface RequestOptions {\n /** Отмена запроса извне. */\n signal?: AbortSignal | undefined;\n /** Таймаут только для этого запроса, мс. */\n timeout?: number | undefined;\n /** Дополнительные заголовки. */\n headers?: Record<string, string> | undefined;\n /** Повторы только для этого запроса. Переопределяют глобальную настройку `retry`. */\n retry?: RetryOptions | false | undefined;\n}\n\n/**\n * Имена полей {@link RequestOptions} — единственный источник истины.\n *\n * Ресурсы переносят в описание запроса только эти поля (плюс заявленные плагинами),\n * потому что параметры методов подмешивают к ним `limit`, `cursor` и прочее, чему\n * в транспорте делать нечего. Список стоит рядом с интерфейсом, чтобы новое поле нельзя\n * было забыть.\n *\n * `satisfies` гарантирует, что каждое имя в списке — действительно поле `RequestOptions`.\n * Обратную полноту (не забыто ли новое поле) проверяет тип {@link RequestOptionKeysComplete}\n * в тесте: здесь её проверять нельзя — плагины расширяют `RequestOptions` своими опциями\n * (`encrypt`, `decrypt` и подобными), которых в этом списке быть и не должно.\n */\nexport const REQUEST_OPTION_KEYS = [\n 'signal',\n 'timeout',\n 'headers',\n 'retry',\n] as const satisfies readonly (keyof RequestOptions)[];\n\n/**\n * Тип-страж полноты {@link REQUEST_OPTION_KEYS}: `true`, только когда список покрывает\n * все ключи `RequestOptions`. Проверяется в тесте, а не здесь, — в сборке плагина\n * интерфейс расширен, и strict-проверка на месте давала бы ложную ошибку.\n */\nexport type RequestOptionKeysComplete =\n keyof RequestOptions extends (typeof REQUEST_OPTION_KEYS)[number] ? true : never;\n\n/** Полное описание запроса для низкоуровневого `itd.request()`. */\nexport interface RawRequestOptions extends RequestOptions {\n method: string;\n /** Путь с ведущим слэшем, например `/api/posts`. Завершающий слэш значим. */\n path: string;\n /**\n * Имя сервиса, на хост которого уйдёт запрос. Без него запрос идёт на основной `baseUrl`\n * клиента. Сервисы задаются опцией {@link ItdClientOptions.services}.\n */\n service?: string | undefined;\n /**\n * Хост этого запроса. Важнее, чем {@link RawRequestOptions.service}.\n *\n * На посторонний основному API хост Bearer-токен по умолчанию не отправляется.\n * Для осознанного разрешения укажите `skipAuth: false`.\n */\n baseUrl?: string | undefined;\n query?: QueryParams | undefined;\n /** Тело: будет отправлено как JSON. Для загрузки файлов передайте `FormData`. */\n body?: unknown;\n /**\n * Не подставлять заголовок авторизации.\n *\n * Явное `false` разрешает авторизацию и для разового внешнего `baseUrl`; без него\n * токен автоматически отправляется только основному хосту и его поддоменам.\n */\n skipAuth?: boolean | undefined;\n /** Не пытаться обновить токен при `401` — используется самими эндпоинтами авторизации. */\n skipAuthRefresh?: boolean | undefined;\n /**\n * Выполнить запрос мимо очереди.\n *\n * Нужно запросам, которые слой авторизации делает **изнутри** другого запроса: продление\n * токена и отложенный вход. Такой запрос обязан стартовать, пока исходный держит место\n * в очереди и ждёт его результата, — иначе оба ждут друг друга и не завершатся никогда.\n */\n skipQueue?: boolean | undefined;\n /** Вернуть тело ответа без снятия обёртки `{ data: … }`. */\n raw?: boolean | undefined;\n}\n","import type { HttpClient } from '../core/http.js';\nimport type { Page, PageState, PaginationMode } from '../core/pagination.js';\nimport { Paginator } from '../core/pagination.js';\nimport type { QueryParams } from '../core/url.js';\nimport { REQUEST_OPTION_KEYS, type RequestOptions } from '../types/options.js';\n\n/** Параметры перебираемого списка: опции запроса плюс предел числа страниц. */\nexport interface ListParams extends RequestOptions {\n /** Ограничение числа страниц при переборе. */\n maxPages?: number | undefined;\n}\n\n/**\n * Описание перебираемого эндпоинта.\n *\n * Одно место, где заданы путь, параметры запроса, чтение страницы и схема пагинации.\n * {@link BaseResource.paginated} строит из него и разовую загрузку, и перебор.\n *\n * @typeParam T тип элемента списка\n * @typeParam P тип параметров метода\n */\nexport interface ListingSpec<T, P extends ListParams> {\n /** Путь эндпоинта. */\n path: (params: P) => string;\n /** Параметры запроса без полей пагинации — их добавит перебор. */\n query: (params: P) => QueryParams;\n /** Читает страницу из ответа. Получает позицию — она нужна схеме со смещением. */\n read: (body: unknown, state: PageState) => Page<T>;\n /** Схема пагинации эндпоинта. */\n mode: PaginationMode;\n /** Начальная позиция, вычисленная из параметров (курсор, номер или смещение). */\n start: (params: P) => PageState;\n}\n\n/** Пара методов, собранная из {@link ListingSpec}: разовая загрузка и перебор. */\nexport interface Listing<T, P extends ListParams> {\n /** Загружает одну страницу с позиции, заданной параметрами. */\n list(params: P): Promise<Page<T>>;\n /** Перебирает страницы, сама подставляя позиции. */\n iterate(params: P): Paginator<T>;\n}\n\n/** Общая основа всех групп методов клиента. */\nexport class BaseResource {\n /** @internal */\n protected readonly http: HttpClient;\n\n constructor(http: HttpClient) {\n this.http = http;\n }\n\n /**\n * Переносит опции запроса в описание транспорта.\n *\n * Копируются только поля {@link REQUEST_OPTION_KEYS} и опции, заявленные плагинами:\n * параметры методов наследуют {@link RequestOptions} и приносят с собой `limit`, `cursor`\n * и прочее, чему в описании запроса делать нечего. Чужие опции плагинов библиотека\n * не понимает, но обязана донести до обёрток нетронутыми.\n */\n protected requestOptions(options: RequestOptions | undefined): Partial<RequestOptions> {\n if (!options) return {};\n\n const source = options as Record<string, unknown>;\n const result: Record<string, unknown> = {};\n\n for (const key of REQUEST_OPTION_KEYS) {\n const value = source[key];\n if (value !== undefined) result[key] = value;\n }\n\n const pluginKeys = this.http.pluginOptionKeys;\n for (const key of pluginKeys) {\n const value = source[key];\n if (value !== undefined) result[key] = value;\n }\n\n return result as Partial<RequestOptions>;\n }\n\n /**\n * Собирает перебор страниц.\n *\n * @param mode схема пагинации эндпоинта\n * @param load загружает одну страницу для указанной позиции\n * @param options `maxPages` и `signal`, а также `start` — позиция, с которой продолжить\n */\n protected paginate<T>(\n mode: PaginationMode,\n load: (state: PageState) => Promise<Page<T>>,\n options?: RequestOptions & { maxPages?: number; start?: PageState },\n ): Paginator<T> {\n return new Paginator<T>({\n mode,\n load,\n ...(options?.maxPages !== undefined ? { maxPages: options.maxPages } : {}),\n ...(options?.signal !== undefined ? { signal: options.signal } : {}),\n ...(options?.start !== undefined ? { start: options.start } : {}),\n });\n }\n\n /**\n * Собирает пару «загрузка страницы + перебор» из одного описания.\n *\n * Путь, параметры запроса и разбор ответа задаются один раз; `list` и `iterate`\n * строятся из них.\n *\n * @example\n * ```ts\n * #feed = this.paginated<Post, FeedParams>({\n * path: () => '/api/posts',\n * query: (p) => ({ tab: p.tab, limit: p.limit }),\n * start: (p) => (p.cursor ? { cursor: p.cursor } : {}),\n * read: (body) => readCursorPage<Post>(body, 'posts'),\n * mode: PaginationMode.Cursor,\n * });\n * ```\n */\n protected paginated<T, P extends ListParams>(spec: ListingSpec<T, P>): Listing<T, P> {\n const load = async (params: P, state: PageState): Promise<Page<T>> => {\n const body = await this.http.request({\n method: 'GET',\n path: spec.path(params),\n query: withPageState(spec.query(params), state),\n ...this.requestOptions(params),\n });\n return spec.read(body, state);\n };\n\n return {\n list: (params) => load(params, spec.start(params)),\n iterate: (params) =>\n this.paginate<T>(spec.mode, (state) => load(params, state), {\n ...(params.maxPages !== undefined ? { maxPages: params.maxPages } : {}),\n ...(params.signal !== undefined ? { signal: params.signal } : {}),\n start: spec.start(params),\n }),\n };\n }\n}\n\n/** Добавляет позицию страницы в параметры запроса. */\nexport function withPageState(query: QueryParams, state: PageState): QueryParams {\n return {\n ...query,\n ...(state.cursor !== undefined ? { cursor: state.cursor } : {}),\n ...(state.page !== undefined ? { page: state.page } : {}),\n ...(state.offset !== undefined ? { offset: state.offset } : {}),\n };\n}\n","import { AUTH_PATHS, type AuthManager, type TURNSTILE_SITE_KEY } from '../core/auth.js';\nimport { ItdConfigError } from '../core/errors.js';\nimport type { HttpClient } from '../core/http.js';\nimport { pickArray, pickString } from '../core/unwrap.js';\nimport type { AuthState, Session } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.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\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/**\n * Результат входа.\n *\n * Сервер может как сразу выдать токен, так и потребовать код подтверждения — размеченное\n * объединение делает оба случая явными.\n */\nexport type SignInResult =\n | { status: 'authenticated'; accessToken: string }\n | { status: 'otp_required'; flowToken: string | undefined };\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.request<AuthState>({\n method: 'GET',\n path: '/api/profile',\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Регистрирует аккаунт и запускает подтверждение по коду.\n *\n * @returns `flowToken`, который нужно передать в {@link verifyOtp}\n */\n async signUp(credentials: CaptchaCredentials, options: RequestOptions = {}): Promise<string> {\n const body = await this.http.request({\n method: 'POST',\n path: AUTH_PATHS.signUp,\n body: credentials,\n skipAuth: true,\n skipAuthRefresh: true,\n ...this.requestOptions(options),\n });\n\n const flowToken = pickString(body, 'flowToken');\n if (!flowToken) {\n throw new ItdConfigError('Сервер не вернул flowToken при регистрации');\n }\n\n return flowToken;\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 body = await this.http.request({\n method: 'POST',\n path: AUTH_PATHS.signIn,\n body: credentials,\n skipAuth: true,\n skipAuthRefresh: true,\n ...this.requestOptions(options),\n });\n\n const accessToken = pickString(body, 'accessToken');\n\n if (accessToken) {\n await this.#auth.setAccessToken(accessToken);\n return { status: SignInStatus.Authenticated, accessToken };\n }\n\n return { status: SignInStatus.OtpRequired, flowToken: pickString(body, 'flowToken') };\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 body = await this.http.request({\n method: 'POST',\n path: AUTH_PATHS.verifyOtp,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...this.requestOptions(options),\n });\n\n const accessToken = pickString(body, 'accessToken');\n if (!accessToken) {\n throw new ItdConfigError('Сервер не вернул accessToken после подтверждения кода');\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.http.request<void>({\n method: 'POST',\n path: AUTH_PATHS.resendOtp,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...this.requestOptions(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.http.request<void>({\n method: 'POST',\n path: AUTH_PATHS.logout,\n skipAuthRefresh: true,\n ...this.requestOptions(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 async forgotPassword(input: ForgotPasswordInput, options: RequestOptions = {}): Promise<string> {\n const body = await this.http.request({\n method: 'POST',\n path: AUTH_PATHS.forgotPassword,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...this.requestOptions(options),\n });\n\n const flowToken = pickString(body, 'flowToken');\n if (!flowToken) {\n throw new ItdConfigError('Сервер не вернул flowToken при запросе сброса пароля');\n }\n\n return flowToken;\n }\n\n /**\n * Устанавливает новый пароль по коду из письма.\n *\n * Сервер ждёт все четыре поля сразу — `email`, `otp`, `flowToken` и `newPassword`;\n * при нехватке любого отвечает `422`.\n */\n resetPassword(input: ResetPasswordInput, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'POST',\n path: AUTH_PATHS.resetPassword,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...this.requestOptions(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.http.request<void>({\n method: 'POST',\n path: AUTH_PATHS.changePassword,\n body: { currentPassword: input.currentPassword, newPassword: input.newPassword },\n ...this.requestOptions(options),\n });\n }\n\n /** Загружает список активных сессий. У текущей поле `isCurrent` равно `true`. */\n async sessions(options: RequestOptions = {}): Promise<Session[]> {\n const body = await this.http.request({\n method: 'GET',\n path: AUTH_PATHS.sessions,\n ...this.requestOptions(options),\n });\n\n return pickArray<Session>(body, 'sessions');\n }\n\n /** Завершает указанную сессию. */\n revokeSession(sessionId: string, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `${AUTH_PATHS.sessions}/${encodeURIComponent(sessionId)}`,\n ...this.requestOptions(options),\n });\n }\n\n /** Завершает все сессии, кроме текущей. */\n revokeOtherSessions(options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: AUTH_PATHS.sessions,\n ...this.requestOptions(options),\n });\n }\n}\n","/** Метка билдера. Через `Symbol.for` — чтобы распознавание переживало смешивание ESM и CJS. */\nexport const BUILDER = Symbol.for('itd.builder');\n\n/**\n * Билдер входных данных.\n *\n * Билдеры необязательны: любой метод, принимающий билдер, принимает и обычный объект.\n * Проверки одинаковы в обоих случаях.\n */\nexport interface ItdBuilder<T> {\n /** @internal */\n readonly [BUILDER]: true;\n /**\n * Собирает и проверяет результат.\n *\n * @throws {ItdConfigError} если нарушены требования к данным\n */\n build(): T;\n /** Чтобы билдер корректно вёл себя внутри `JSON.stringify`. */\n toJSON(): T;\n}\n\n/**\n * Три равноправные формы входа: обычный объект, готовый билдер или функция-настройщик.\n *\n * @example\n * ```ts\n * itd.posts.create({ content: 'привет' }); // объект\n * itd.posts.create(post().content('привет')); // билдер\n * itd.posts.create((p) => p.content('привет')); // функция\n * ```\n */\nexport type BuilderInput<T, B extends ItdBuilder<T>> = T | B | ((builder: B) => B | T);\n\n/** Является ли значение билдером. */\nexport function isBuilder<T>(value: unknown): value is ItdBuilder<T> {\n return typeof value === 'object' && value !== null && BUILDER in value;\n}\n\n/**\n * Приводит любую из трёх форм входа к готовому объекту.\n *\n * Проверка выполняется всегда, независимо от формы: обычный объект проходит ровно те же\n * правила, что и результат билдера.\n *\n * @param input то, что передал пользователь\n * @param factory создаёт пустой билдер для формы с функцией\n * @param validate проверяет собранный объект\n */\nexport function resolveInput<T, B extends ItdBuilder<T>>(\n input: BuilderInput<T, B>,\n factory: () => B,\n validate: (value: T) => T,\n): T {\n if (typeof input === 'function') {\n const result = (input as (builder: B) => B | T)(factory());\n return isBuilder<T>(result) ? result.build() : validate(result as T);\n }\n\n if (isBuilder<T>(input)) return input.build();\n\n return validate(input as T);\n}\n","import type { FileInput } from '../core/attachments.js';\nimport { ItdConfigError } from '../core/errors.js';\nimport type { UserId } from '../types/models.js';\nimport type { CreateCommentInput } from '../types/params.js';\nimport { BUILDER, type BuilderInput, type ItdBuilder, resolveInput } from './base.js';\n\n/** Внутреннее состояние {@link CommentBuilder}. */\ninterface CommentState extends CreateCommentInput {\n content: string;\n attachmentIds: string[];\n files: FileInput[];\n /** Голосовой комментарий: текста быть не должно, вложение ровно одно. */\n voice: boolean;\n}\n\n/**\n * Проверяет данные комментария.\n *\n * @throws {ItdConfigError} если комментарий пуст или голосовой собран неверно\n */\nexport function validateComment(input: CreateCommentInput): CreateCommentInput {\n const content = typeof input?.content === 'string' ? input.content : '';\n const attachmentIds = input?.attachmentIds ?? [];\n const files = input?.files ?? [];\n\n const hasContent = content.trim() !== '';\n const hasAttachments = attachmentIds.length > 0 || files.length > 0;\n\n if (!hasContent && !hasAttachments) {\n throw new ItdConfigError('Комментарий пуст: нужен текст или вложение');\n }\n\n return input;\n}\n\n/** Дополнительная проверка голосового комментария. */\nfunction validateVoice(state: CommentState): void {\n if (state.content.trim() !== '') {\n throw new ItdConfigError(\n 'У голосового комментария не может быть текста: API принимает либо текст, либо аудио',\n );\n }\n\n const total = state.files.length + state.attachmentIds.length;\n if (total !== 1) {\n throw new ItdConfigError(\n `Голосовой комментарий требует ровно одно аудиовложение, передано: ${total}`,\n );\n }\n}\n\n/**\n * Билдер комментария и ответа на комментарий.\n *\n * Неизменяемый: каждый вызов возвращает новый экземпляр. Создаётся функцией {@link comment}.\n */\nexport class CommentBuilder implements ItdBuilder<CreateCommentInput> {\n /** @internal */\n readonly [BUILDER] = true as const;\n\n readonly #state: CommentState;\n\n /** @internal Создавайте билдер функцией {@link comment}. */\n constructor(state: CommentState) {\n this.#state = state;\n }\n\n /** Задаёт текст комментария. */\n content(text: string): CommentBuilder {\n return new CommentBuilder({ ...this.#state, content: text });\n }\n\n /** Прикладывает файл — он будет загружен перед отправкой. */\n attach(file: FileInput): CommentBuilder {\n return new CommentBuilder({ ...this.#state, files: [...this.#state.files, file] });\n }\n\n /** Прикладывает уже загруженное вложение. */\n attachId(attachmentId: string): CommentBuilder {\n return new CommentBuilder({\n ...this.#state,\n attachmentIds: [...this.#state.attachmentIds, attachmentId],\n });\n }\n\n /**\n * Делает комментарий голосовым.\n *\n * Текста у такого комментария быть не должно, а вложение ровно одно — аудио в формате\n * `audio/ogg`. Так его принимает API.\n *\n * @example\n * ```ts\n * import { fromPath } from 'itd-api/node';\n *\n * await itd.posts.comment(postId, (c) => c.voice(fromPath('./answer.ogg')));\n * ```\n */\n voice(audio: FileInput): CommentBuilder {\n return new CommentBuilder({ ...this.#state, files: [audio], voice: true });\n }\n\n /**\n * Кому адресован ответ.\n *\n * Имеет смысл только в `itd.comments.reply()`; при отправке комментария к посту\n * это поле вызовет ошибку.\n */\n replyTo(userId: UserId): CommentBuilder {\n return new CommentBuilder({ ...this.#state, replyToUserId: userId });\n }\n\n build(): CreateCommentInput {\n const { content, attachmentIds, files, voice, ...rest } = this.#state;\n\n if (voice) validateVoice(this.#state);\n\n return validateComment({\n ...rest,\n content,\n ...(attachmentIds.length > 0 ? { attachmentIds } : {}),\n ...(files.length > 0 ? { files } : {}),\n });\n }\n\n toJSON(): CreateCommentInput {\n return this.build();\n }\n}\n\n/**\n * Начинает сборку комментария.\n *\n * @param content текст; можно задать позже методом {@link CommentBuilder.content}\n *\n * @example\n * ```ts\n * import { comment } from 'itd-api';\n *\n * await itd.posts.comment(postId, comment('согласен').attach({ url: memeUrl }));\n * ```\n */\nexport function comment(content = ''): CommentBuilder {\n return new CommentBuilder({ content, attachmentIds: [], files: [], voice: false });\n}\n\n/** Что принимает параметр комментария: объект, билдер или функция-настройщик. */\nexport type CommentInput = BuilderInput<CreateCommentInput, CommentBuilder>;\n\n/**\n * Приводит любую форму входа к готовым данным комментария.\n *\n * @param allowReplyTo разрешено ли поле `replyToUserId`; в комментарии к посту — нет\n */\nexport function resolveComment(input: CommentInput, allowReplyTo = false): CreateCommentInput {\n const resolved = resolveInput(input, () => comment(), validateComment);\n\n if (!allowReplyTo && resolved.replyToUserId !== undefined) {\n throw new ItdConfigError(\n 'replyTo применим только к ответу на комментарий (itd.comments.reply). ' +\n 'В комментарии к посту адресат не указывается.',\n );\n }\n\n return resolved;\n}\n","import { type CommentInput, resolveComment } from '../builders/comment.js';\nimport type { FileInput } from '../core/attachments.js';\nimport type { HttpClient } from '../core/http.js';\nimport { type Page, PaginationMode, type Paginator, readPagedPage } from '../core/pagination.js';\nimport { encodePathSegment } from '../core/url.js';\nimport type { Comment, LikeResult } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/** Параметры запроса ответов на комментарий. */\nexport interface RepliesParams extends RequestOptions {\n limit?: number;\n page?: number;\n maxPages?: number;\n}\n\n/**\n * Комментарии и ответы на них.\n *\n * Доступна как `itd.comments`. Комментарии **к посту** живут в `itd.posts`:\n * `itd.posts.comments()` и `itd.posts.comment()`.\n */\nexport class CommentsResource extends BaseResource {\n readonly #uploadFiles: (files: FileInput[], options?: RequestOptions) => Promise<string[]>;\n\n /** Ответы на комментарий: `/api/comments/{id}/replies`, постраничная пагинация. */\n readonly #replies = this.paginated<Comment, RepliesParams & { commentId: string }>({\n path: (p) => `/api/comments/${encodePathSegment(p.commentId, 'commentId')}/replies`,\n query: (p) => ({ limit: p.limit }),\n start: (p) => (p.page !== undefined ? { page: p.page } : {}),\n read: (body) => readPagedPage<Comment>(body, 'replies'),\n mode: PaginationMode.Page,\n });\n\n constructor(\n http: HttpClient,\n deps: { uploadFiles: (files: FileInput[], options?: RequestOptions) => Promise<string[]> },\n ) {\n super(http);\n this.#uploadFiles = deps.uploadFiles;\n }\n\n /**\n * Загружает страницу ответов на комментарий.\n *\n * Здесь пагинация **постраничная**, в отличие от комментариев к посту, где курсорная.\n */\n replies(commentId: string, params: RepliesParams = {}): Promise<Page<Comment>> {\n return this.#replies.list({ ...params, commentId });\n }\n\n /** Перебирает ответы на комментарий. */\n iterateReplies(commentId: string, params: RepliesParams = {}): Paginator<Comment> {\n return this.#replies.iterate({ ...params, commentId });\n }\n\n /**\n * Отвечает на комментарий.\n *\n * @example\n * ```ts\n * await itd.comments.reply(commentId, 'согласен');\n * await itd.comments.reply(commentId, (c) => c.content('и вот почему').replyTo(userId));\n * ```\n */\n async reply(\n commentId: string,\n input: CommentInput | string,\n options: RequestOptions = {},\n ): Promise<Comment> {\n const data = resolveComment(typeof input === 'string' ? { content: input } : input, true);\n\n const existing = data.attachmentIds ?? [];\n const files = data.files ?? [];\n const attachmentIds =\n files.length > 0 ? [...existing, ...(await this.#uploadFiles(files, options))] : existing;\n\n return this.http.request<Comment>({\n method: 'POST',\n path: `/api/comments/${encodePathSegment(commentId, 'commentId')}/replies`,\n body: {\n content: data.content ?? '',\n attachmentIds,\n ...(data.replyToUserId ? { replyToUserId: data.replyToUserId } : {}),\n },\n ...this.requestOptions(options),\n });\n }\n\n /** Редактирует текст комментария. */\n update(commentId: string, content: string, options: RequestOptions = {}): Promise<Comment> {\n return this.http.request<Comment>({\n method: 'PATCH',\n path: `/api/comments/${encodePathSegment(commentId, 'commentId')}`,\n body: { content },\n ...this.requestOptions(options),\n });\n }\n\n /** Удаляет комментарий. Восстановить его можно через {@link restore}. */\n remove(commentId: string, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `/api/comments/${encodePathSegment(commentId, 'commentId')}`,\n ...this.requestOptions(options),\n });\n }\n\n /** Восстанавливает удалённый комментарий. */\n restore(commentId: string, options: RequestOptions = {}): Promise<Comment> {\n return this.http.request<Comment>({\n method: 'POST',\n path: `/api/comments/${encodePathSegment(commentId, 'commentId')}/restore`,\n ...this.requestOptions(options),\n });\n }\n\n /** Ставит реакцию на комментарий. */\n like(commentId: string, options: RequestOptions = {}): Promise<LikeResult> {\n return this.http.request<LikeResult>({\n method: 'POST',\n path: `/api/comments/${encodePathSegment(commentId, 'commentId')}/like`,\n ...this.requestOptions(options),\n });\n }\n\n /** Убирает реакцию с комментария. */\n unlike(commentId: string, options: RequestOptions = {}): Promise<LikeResult> {\n return this.http.request<LikeResult>({\n method: 'DELETE',\n path: `/api/comments/${encodePathSegment(commentId, 'commentId')}/like`,\n ...this.requestOptions(options),\n });\n }\n}\n","import { ItdConfigError } from './errors.js';\n\n/** Изображения, которые принимает `POST /api/files/upload`. */\nexport const IMAGE_MIME_TYPES = Object.freeze([\n 'image/jpeg',\n 'image/png',\n 'image/gif',\n 'image/webp',\n 'image/avif',\n 'image/heic',\n 'image/heif',\n] as const);\nexport type ImageMimeType = (typeof IMAGE_MIME_TYPES)[number];\n\n/** Видео, которые принимает `POST /api/files/upload`. */\nexport const VIDEO_MIME_TYPES = Object.freeze([\n 'video/mp4',\n 'video/webm',\n 'video/quicktime',\n] as const);\nexport type VideoMimeType = (typeof VIDEO_MIME_TYPES)[number];\n\n/** Аудио для голосовых комментариев. */\nexport const AUDIO_MIME_TYPES = Object.freeze(['audio/ogg'] as const);\nexport type AudioMimeType = (typeof AUDIO_MIME_TYPES)[number];\n\n/** Все типы, которые принимает загрузка. */\nexport const ALLOWED_MIME_TYPES = Object.freeze([\n ...IMAGE_MIME_TYPES,\n ...VIDEO_MIME_TYPES,\n ...AUDIO_MIME_TYPES,\n] as const);\nexport type AllowedMimeType = (typeof ALLOWED_MIME_TYPES)[number];\n\n/**\n * Соответствие расширения и MIME-типа.\n *\n * Своя маленькая таблица вместо зависимости: библиотеке нужны ровно те типы,\n * которые принимает API.\n */\nconst EXTENSION_TO_MIME: Readonly<Record<string, string>> = Object.freeze({\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n jfif: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n avif: 'image/avif',\n heic: 'image/heic',\n heif: 'image/heif',\n mp4: 'video/mp4',\n m4v: 'video/mp4',\n webm: 'video/webm',\n mov: 'video/quicktime',\n qt: 'video/quicktime',\n ogg: 'audio/ogg',\n oga: 'audio/ogg',\n opus: 'audio/ogg',\n});\n\n/** Определяет MIME-тип по расширению имени файла. */\nexport function mimeFromFilename(filename: string): string | undefined {\n const dot = filename.lastIndexOf('.');\n if (dot < 0) return undefined;\n\n return EXTENSION_TO_MIME[filename.slice(dot + 1).toLowerCase()];\n}\n\n/** Разрешён ли тип к загрузке. */\nexport function isAllowedMime(mimeType: string): mimeType is AllowedMimeType {\n return (ALLOWED_MIME_TYPES as readonly string[]).includes(mimeType.toLowerCase());\n}\n\n/**\n * Проверяет тип файла до отправки.\n *\n * Отказ на стороне клиента экономит и время, и трафик: тот же `UNSUPPORTED_FILE_TYPE`\n * пришёл бы с сервера, но уже после загрузки всего файла.\n *\n * @throws {ItdConfigError} если тип не поддерживается\n */\nexport function assertAllowedMime(mimeType: string | undefined, filename?: string): void {\n if (!mimeType) {\n throw new ItdConfigError(\n `Не удалось определить тип файла${filename ? ` «${filename}»` : ''}. ` +\n 'Укажите его явно: { file, filename, contentType }.',\n );\n }\n\n if (!isAllowedMime(mimeType)) {\n throw new ItdConfigError(\n `Тип «${mimeType}» не поддерживается загрузкой. Допустимые: ${ALLOWED_MIME_TYPES.join(', ')}.`,\n );\n }\n}\n","/** Один потоковый файл в multipart/form-data. */\nexport interface MultipartFile {\n stream: ReadableStream<Uint8Array>;\n filename: string;\n contentType: string;\n}\n\n/** Результат сборки потокового multipart-тела. */\nexport interface MultipartBody {\n body: ReadableStream<Uint8Array>;\n contentType: string;\n cancel: (reason?: unknown) => Promise<void>;\n}\n\nconst encoder = new TextEncoder();\n\n/** Убирает возможность внедрить дополнительный multipart-заголовок через имя файла. */\nfunction quotedFilename(filename: string): string {\n return filename\n .replace(/[\\r\\n]/g, '')\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"');\n}\n\n/** Удаляет разделители строк, чтобы значение не создало новый multipart-заголовок. */\nfunction headerValue(value: string): string {\n return value.replace(/[\\r\\n]/g, '');\n}\n\n/** Создаёт уникальную границу без зависимости от Web Crypto. */\nfunction boundary(): string {\n return `----itd-api-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Кодирует один файл в multipart без полной буферизации.\n *\n * Читает следующий чанк только когда потребитель запросил данные.\n */\nexport function createMultipartFileBody(file: MultipartFile): MultipartBody {\n const marker = boundary();\n const prefix = encoder.encode(\n `--${marker}\\r\\n` +\n `Content-Disposition: form-data; name=\"file\"; filename=\"${quotedFilename(file.filename)}\"\\r\\n` +\n `Content-Type: ${headerValue(file.contentType)}\\r\\n\\r\\n`,\n );\n const suffix = encoder.encode(`\\r\\n--${marker}--\\r\\n`);\n const reader = file.stream.getReader();\n let stage: 'prefix' | 'file' | 'suffix' | 'done' = 'prefix';\n let cancelled = false;\n\n const cancel = async (reason?: unknown) => {\n if (cancelled) return;\n cancelled = true;\n await reader.cancel(reason).catch(() => {});\n };\n\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n if (stage === 'prefix') {\n stage = 'file';\n controller.enqueue(prefix);\n return;\n }\n\n if (stage === 'file') {\n try {\n const next = await reader.read();\n if (!next.done) {\n controller.enqueue(next.value);\n return;\n }\n stage = 'suffix';\n } catch (error) {\n stage = 'done';\n controller.error(error);\n return;\n }\n }\n\n if (stage === 'suffix') {\n stage = 'done';\n controller.enqueue(suffix);\n controller.close();\n }\n },\n cancel,\n });\n\n return {\n body,\n contentType: `multipart/form-data; boundary=${marker}`,\n cancel,\n };\n}\n","import {\n boundedFileStream,\n downloadFile,\n type FileContent,\n type FileContext,\n type FileInput,\n type FileStreamContent,\n FileTransferMode,\n isBoundedFileStream,\n type LazyFile,\n normalizeMimeType,\n openUrlFile,\n resolveFileStreamOptions,\n type StreamFile,\n type UrlFile,\n} from '../core/attachments.js';\nimport { ItdConfigError, ItdError, ItdFileError, ItdFileErrorReason } from '../core/errors.js';\nimport type { HttpClient } from '../core/http.js';\nimport { assertAllowedMime, mimeFromFilename } from '../core/mime.js';\nimport { createMultipartFileBody } from '../core/multipart.js';\nimport type { RequestBodyFactory } from '../core/pipeline.js';\nimport { isBlob, isFile } from '../core/runtime.js';\nimport { encodePathSegment } from '../core/url.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/** Ответ загрузки файла. */\nexport interface UploadedFile {\n /** Идентификатор вложения — его передают в `attachmentIds`. */\n id: string;\n /** Адрес файла на CDN. */\n url: string;\n}\n\n/** Настройки загрузки. */\nexport interface UploadOptions extends RequestOptions {\n /** Имя файла. Используется для определения MIME, если тип не задан. */\n filename?: string;\n /** MIME-тип. По умолчанию определяется по имени или `Blob`. */\n contentType?: string;\n /** Проверять тип до отправки. По умолчанию `true`. */\n validateMime?: boolean;\n /** Дополнительный предел размера для любого вида источника. */\n maxBytes?: number | undefined;\n /** Размер очереди библиотеки при потоковой передаче. */\n streamBufferBytes?: number | undefined;\n}\n\n/** Таймаут одной попытки загрузки файла — 5 минут. */\nexport const DEFAULT_UPLOAD_TIMEOUT = 300_000;\n\ninterface PreparedBuffer {\n mode: 'buffer';\n blob: Blob;\n filename: string;\n}\n\ninterface PreparedStream {\n mode: 'stream';\n stream: ReadableStream<Uint8Array>;\n filename: string;\n contentType: string;\n close?: (() => void | Promise<void>) | undefined;\n}\n\ntype PreparedFile = PreparedBuffer | PreparedStream;\n\n/** Файлы и медиа. */\nexport class FilesResource extends BaseResource {\n readonly #fetch: typeof fetch;\n\n constructor(http: HttpClient, deps: { fetch: typeof fetch }) {\n super(http);\n this.#fetch = deps.fetch;\n }\n\n /**\n * Загружает файл и возвращает его идентификатор.\n *\n * Потоковый источник открывается заново при каждой повторной попытке. Буферный источник\n * после успешного чтения переиспользуется.\n */\n upload(input: FileInput, options: UploadOptions = {}): Promise<UploadedFile> {\n const bodyFactory = this.#createBodyFactory(input, options);\n\n return this.http.request<UploadedFile>({\n method: 'POST',\n path: '/api/files/upload',\n bodyFactory,\n retryNetworkWrite: true,\n timeout: options.timeout ?? DEFAULT_UPLOAD_TIMEOUT,\n ...this.requestOptions(options),\n });\n }\n\n /** Загружает несколько файлов последовательно, сохраняя порядок. */\n async uploadMany(files: FileInput[], options: UploadOptions = {}): Promise<string[]> {\n const ids: string[] = [];\n for (const file of files) ids.push((await this.upload(file, options)).id);\n return ids;\n }\n\n /**\n * Загружает сведения о файле.\n *\n * Для ещё не прикреплённого файла сервер может ответить `404`.\n */\n get(fileId: string, options: RequestOptions = {}): Promise<unknown> {\n return this.http.request({\n method: 'GET',\n path: `/api/files/${encodePathSegment(fileId, 'fileId')}`,\n ...this.requestOptions(options),\n });\n }\n\n /** Удаляет загруженный файл. */\n remove(fileId: string, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `/api/files/${encodePathSegment(fileId, 'fileId')}`,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Создаёт фабрику тела. Успешно подготовленный буфер кешируется, а поток открывается\n * заново, поэтому каждая транспортная попытка получает непрочитанное тело.\n */\n #createBodyFactory(input: FileInput, options: UploadOptions): RequestBodyFactory {\n const streamMode = this.#isStreamInput(input);\n let buffered: Promise<PreparedBuffer> | undefined;\n\n return async ({ signal, attempt }) => {\n const context: FileContext = { fetch: this.#fetch, signal, attempt };\n let prepared: PreparedFile;\n\n try {\n if (streamMode) {\n prepared = await this.#prepareStream(input, options, context);\n } else {\n buffered ??= this.#prepareBuffer(input, options, context).catch((error: unknown) => {\n buffered = undefined;\n throw error;\n });\n prepared = await buffered;\n }\n } catch (error) {\n if (error instanceof ItdError || signal.aborted) throw error;\n throw new ItdFileError('не удалось получить содержимое вложения', {\n reason: ItdFileErrorReason.Read,\n retryable: true,\n cause: error,\n });\n }\n\n if (prepared.mode === 'buffer') {\n const form = new FormData();\n form.set('file', prepared.blob, prepared.filename);\n return { body: form };\n }\n\n const multipart = createMultipartFileBody(prepared);\n return {\n body: multipart.body,\n headers: { 'Content-Type': multipart.contentType },\n cleanup: async () => {\n await multipart.cancel();\n await prepared.close?.();\n },\n };\n };\n }\n\n /** Определяет режим без чтения источника. */\n #isStreamInput(input: FileInput): boolean {\n if (typeof input !== 'object' || input === null) return false;\n if ('open' in input) return true;\n if ('url' in input) {\n return resolveFileStreamOptions(input).mode === FileTransferMode.Stream;\n }\n return false;\n }\n\n /** Получает и проверяет буферный источник. */\n async #prepareBuffer(\n input: FileInput,\n options: UploadOptions,\n context: FileContext,\n ): Promise<PreparedBuffer> {\n const content = await this.#resolveBuffer(input, context);\n const filename =\n options.filename ??\n content.filename ??\n this.#nameFromMime(options.contentType ?? content.contentType);\n const contentType =\n normalizeMimeType(options.contentType ?? content.contentType) ??\n normalizeMimeType(isBlob(content.file) ? content.file.type : undefined) ??\n mimeFromFilename(filename);\n\n if (options.validateMime !== false) assertAllowedMime(contentType, filename);\n\n const blob =\n isBlob(content.file) && (!contentType || content.file.type === contentType)\n ? content.file\n : new Blob([content.file as BlobPart], { type: contentType ?? '' });\n\n const limits = resolveFileStreamOptions(options);\n if (limits.maxBytes !== undefined && blob.size > limits.maxBytes) {\n throw new ItdFileError(`файл больше предела в ${limits.maxBytes} байт: ${blob.size}`, {\n reason: ItdFileErrorReason.TooLarge,\n limit: limits.maxBytes,\n actual: blob.size,\n });\n }\n\n return { mode: 'buffer', blob, filename };\n }\n\n /** Открывает и проверяет потоковый источник. */\n async #prepareStream(\n input: FileInput,\n options: UploadOptions,\n context: FileContext,\n ): Promise<PreparedStream> {\n let opened: FileStreamContent;\n if (typeof input === 'object' && input !== null && 'open' in input) {\n opened = await (input as StreamFile).open(context);\n } else if (typeof input === 'object' && input !== null && 'url' in input) {\n const { url, ...urlOptions } = input as UrlFile;\n opened = await openUrlFile(url, urlOptions, context);\n } else {\n throw new ItdConfigError('потоковое вложение должно иметь форму { open } или { url, mode }');\n }\n\n if (\n !opened ||\n typeof ReadableStream === 'undefined' ||\n !(opened.stream instanceof ReadableStream)\n ) {\n await opened?.close?.();\n throw new ItdConfigError('потоковый источник должен вернуть { stream: ReadableStream }');\n }\n\n try {\n const filename =\n options.filename ??\n opened.filename ??\n this.#nameFromMime(options.contentType ?? opened.contentType);\n const contentType =\n normalizeMimeType(options.contentType ?? opened.contentType) ?? mimeFromFilename(filename);\n if (options.validateMime !== false) assertAllowedMime(contentType, filename);\n\n const limits = resolveFileStreamOptions({\n mode: FileTransferMode.Stream,\n ...(options.maxBytes !== undefined ? { maxBytes: options.maxBytes } : {}),\n ...(options.streamBufferBytes !== undefined\n ? { streamBufferBytes: options.streamBufferBytes }\n : {}),\n });\n if (\n limits.maxBytes !== undefined &&\n opened.size !== undefined &&\n opened.size > limits.maxBytes\n ) {\n throw new ItdFileError(`файл больше предела в ${limits.maxBytes} байт: ${opened.size}`, {\n reason: ItdFileErrorReason.TooLarge,\n limit: limits.maxBytes,\n actual: opened.size,\n });\n }\n\n const keepExistingLimits =\n isBoundedFileStream(opened.stream) &&\n options.maxBytes === undefined &&\n options.streamBufferBytes === undefined;\n\n return {\n mode: 'stream',\n stream: keepExistingLimits\n ? opened.stream\n : boundedFileStream(opened.stream, {\n ...(limits.maxBytes !== undefined ? { maxBytes: limits.maxBytes } : {}),\n streamBufferBytes: limits.streamBufferBytes,\n signal: context.signal,\n }),\n filename,\n contentType: contentType ?? '',\n ...(opened.close ? { close: opened.close } : {}),\n };\n } catch (error) {\n await opened.close?.();\n throw error;\n }\n }\n\n /** Разрешает только буферные формы. */\n #resolveBuffer(input: FileInput, context: FileContext): FileContent | Promise<FileContent> {\n if (input instanceof ArrayBuffer || ArrayBuffer.isView(input) || isBlob(input)) {\n return {\n file: input as Blob | ArrayBuffer | Uint8Array,\n ...(isFile(input) ? { filename: input.name } : {}),\n };\n }\n\n if (typeof input !== 'object' || input === null) {\n throw new ItdConfigError(\n `вложение задано значением типа ${typeof input}; ожидается бинарное значение ` +\n 'или объект { file }, { url }, { load }, { open }. ' +\n \"Для файла на диске используйте fromPath('./photo.jpg') из itd-api/node\",\n );\n }\n\n if ('load' in input) return (input as LazyFile).load(context);\n if ('url' in input) {\n const { url, ...urlOptions } = input as UrlFile;\n return downloadFile(url, urlOptions, context);\n }\n if ('file' in input) return input as FileContent;\n if ('open' in input) {\n throw new ItdConfigError('потоковый источник нельзя использовать как буферный');\n }\n\n throw new ItdConfigError(\n 'вложение не распознано: ожидается { file }, { url }, { load } или { open }',\n );\n }\n\n /** Подбирает непустое имя, обязательное для multipart. */\n #nameFromMime(contentType: string | undefined): string {\n const extension = normalizeMimeType(contentType)?.split('/')[1];\n return extension ? `file.${extension}` : 'file';\n }\n}\n","import { type Page, PaginationMode, type Paginator, readCursorPage } from '../core/pagination.js';\nimport { pickArray } from '../core/unwrap.js';\nimport { encodePathSegment } from '../core/url.js';\nimport type { Hashtag, Post } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/** Параметры запроса постов по хэштегу. */\nexport interface HashtagPostsParams extends RequestOptions {\n limit?: number;\n cursor?: string;\n maxPages?: number;\n}\n\n/**\n * Хэштеги.\n *\n * Доступна как `itd.hashtags`.\n */\nexport class HashtagsResource extends BaseResource {\n /** Посты по хэштегу: `/api/hashtags/{tag}/posts`, курсорная пагинация. */\n readonly #posts = this.paginated<Post, HashtagPostsParams & { tag: string }>({\n path: (p) => `/api/hashtags/${encodePathSegment(p.tag, 'tag')}/posts`,\n query: (p) => ({ limit: p.limit }),\n start: (p) => (p.cursor ? { cursor: p.cursor } : {}),\n read: (body) => readCursorPage<Post>(body, 'posts'),\n mode: PaginationMode.Cursor,\n });\n\n /**\n * Ищет хэштеги.\n *\n * Без строки запроса возвращает общий список.\n */\n async search(\n query?: string,\n params: { limit?: number } & RequestOptions = {},\n ): Promise<Hashtag[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/hashtags',\n query: { q: query, limit: params.limit },\n ...this.requestOptions(params),\n });\n\n return pickArray<Hashtag>(body, 'hashtags');\n }\n\n /** Загружает трендовые хэштеги. */\n async trending(params: { limit?: number } & RequestOptions = {}): Promise<Hashtag[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/hashtags/trending',\n query: { limit: params.limit },\n ...this.requestOptions(params),\n });\n\n return pickArray<Hashtag>(body, 'hashtags');\n }\n\n /**\n * Загружает страницу постов по хэштегу.\n *\n * @param tag название без решётки; кодируется автоматически, поэтому кириллица\n * и пробелы допустимы\n */\n posts(tag: string, params: HashtagPostsParams = {}): Promise<Page<Post>> {\n return this.#posts.list({ ...params, tag });\n }\n\n /** Перебирает посты по хэштегу. */\n iteratePosts(tag: string, params: HashtagPostsParams = {}): Paginator<Post> {\n return this.#posts.iterate({ ...params, tag });\n }\n}\n","import { type Page, PaginationMode, type Paginator, readOffsetPage } from '../core/pagination.js';\nimport { pickBoolean, pickNumber } from '../core/unwrap.js';\nimport { encodePathSegment } from '../core/url.js';\nimport { normalizeNotification } from '../notifications/normalize.js';\nimport type { Notification, NotificationSettings } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\nconst NOTIFICATION_SETTING_KEYS = [\n 'enabled',\n 'sound',\n 'follows',\n 'wallPosts',\n 'likes',\n 'comments',\n 'mentions',\n] as const;\n\n/**\n * Сколько идентификаторов уходит в одном запросе на отметку прочтения.\n *\n * Столько же отправляет сайт итд.com — значит на сервере, скорее всего, есть ограничение.\n */\nconst READ_BATCH_SIZE = 20;\n\n/** Параметры запроса списка уведомлений. */\nexport interface NotificationListParams extends RequestOptions {\n limit?: number;\n /** Смещение от начала списка. */\n offset?: number;\n maxPages?: number;\n}\n\n/** Изменяемые настройки уведомлений. */\nexport type UpdateNotificationSettingsInput = Partial<NotificationSettings>;\n\n/**\n * Читает настройки уведомлений.\n *\n * Сервер отдаёт плоский объект: `enabled`, `sound`, `follows`, `wallPosts`, `likes`,\n * `comments`, `mentions`. Отсутствующая настройка считается включённой — так же\n * ведёт себя сайт итд.com.\n */\nfunction readSettings(body: unknown): NotificationSettings {\n const settings = {} as NotificationSettings;\n for (const key of NOTIFICATION_SETTING_KEYS) settings[key] = pickBoolean(body, key, true);\n return settings;\n}\n\n/**\n * Уведомления: список, счётчик, отметки о прочтении, настройки.\n *\n * Доступна как `itd.notifications`. Все уведомления приведены к единой форме, поэтому\n * объекты отсюда и из потока событий можно складывать в один список.\n */\nexport class NotificationsResource extends BaseResource {\n /** Уведомления: `/api/notifications/`, пагинация по смещению. */\n readonly #list = this.paginated<Notification, NotificationListParams>({\n // Завершающий слэш обязателен: без него сервер отвечает ошибкой.\n path: () => '/api/notifications/',\n query: (p) => ({ limit: p.limit }),\n start: (p) => ({ offset: p.offset ?? 0 }),\n read: (body, state) => {\n const page = readOffsetPage<unknown>(body, 'notifications', state.offset ?? 0);\n // Сайт итд.com оборачивает смещение в строку и притворяется, что это курсор;\n // библиотека отдаёт честное число, а сами уведомления — в единой форме.\n return { ...page, items: page.items.map(normalizeNotification) };\n },\n mode: PaginationMode.Offset,\n });\n\n /**\n * Загружает страницу уведомлений.\n *\n * Пагинация здесь основана на смещении.\n *\n * @example\n * ```ts\n * const page = await itd.notifications.list({ limit: 20 });\n * const next = await itd.notifications.list({ limit: 20, offset: page.nextOffset });\n * ```\n */\n list(params: NotificationListParams = {}): Promise<Page<Notification>> {\n return this.#list.list(params);\n }\n\n /**\n * Перебирает уведомления.\n *\n * @example\n * ```ts\n * for await (const notification of itd.notifications.iterate()) {\n * console.log(formatNotificationText(notification));\n * }\n * ```\n */\n iterate(params: NotificationListParams = {}): Paginator<Notification> {\n return this.#list.iterate(params);\n }\n\n /** Загружает число непрочитанных уведомлений. */\n async count(options: RequestOptions = {}): Promise<number> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/notifications/count',\n ...this.requestOptions(options),\n });\n\n return pickNumber(body, 'count', 0);\n }\n\n /**\n * Отмечает уведомление прочитанным.\n *\n * @returns сколько записей отметил сервер\n */\n async markRead(notificationId: string, options: RequestOptions = {}): Promise<number> {\n const body = await this.http.request({\n method: 'POST',\n path: `/api/notifications/${encodePathSegment(notificationId, 'notificationId')}/read`,\n ...this.requestOptions(options),\n });\n\n return pickNumber(body, 'markedCount', 0);\n }\n\n /**\n * Отмечает прочитанными сразу несколько уведомлений.\n *\n * Список автоматически режется на части по 20 идентификаторов — столько же отправляет\n * сайт итд.com, поэтому на сервере вероятен предел. Части уходят последовательно,\n * результат суммируется.\n *\n * @returns сколько записей отметил сервер суммарно\n */\n async markReadBatch(ids: string[], options: RequestOptions = {}): Promise<number> {\n let marked = 0;\n\n for (let index = 0; index < ids.length; index += READ_BATCH_SIZE) {\n const chunk = ids.slice(index, index + READ_BATCH_SIZE);\n\n const body = await this.http.request({\n method: 'POST',\n path: '/api/notifications/read-batch',\n body: { ids: chunk },\n ...this.requestOptions(options),\n });\n\n marked += pickNumber(body, 'markedCount', 0);\n }\n\n return marked;\n }\n\n /** Отмечает прочитанными все уведомления. */\n async markAllRead(options: RequestOptions = {}): Promise<number> {\n const body = await this.http.request({\n method: 'POST',\n path: '/api/notifications/read-all',\n ...this.requestOptions(options),\n });\n\n return pickNumber(body, 'markedCount', 0);\n }\n\n /** Загружает настройки уведомлений. */\n async getSettings(options: RequestOptions = {}): Promise<NotificationSettings> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/notifications/settings',\n ...this.requestOptions(options),\n });\n\n return readSettings(body);\n }\n\n /**\n * Обновляет настройки уведомлений.\n *\n * Отправляются только изменяемые поля, в том же виде, в каком сервер их возвращает.\n */\n async updateSettings(\n input: UpdateNotificationSettingsInput,\n options: RequestOptions = {},\n ): Promise<NotificationSettings> {\n const payload: Record<string, boolean> = {};\n\n for (const key of NOTIFICATION_SETTING_KEYS) {\n const value = input[key];\n if (value !== undefined) payload[key] = value;\n }\n\n const body = await this.http.request({\n method: 'PUT',\n path: '/api/notifications/settings',\n body: payload,\n ...this.requestOptions(options),\n });\n\n return readSettings(body);\n }\n}\n","/** Отметка времени без часового пояса: `2026-07-23 23:14:25`, возможно с долями секунды. */\nconst NAIVE_STAMP = /^(\\d{4}-\\d{2}-\\d{2}) (\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?)$/;\n\n/**\n * Приводит отметку времени без часового пояса к ISO-8601, считая её временем UTC.\n *\n * Строку другого вида возвращает нетронутой.\n *\n * @example\n * ```ts\n * utcStampToIso('2026-07-23 23:14:25'); // '2026-07-23T23:14:25Z'\n * utcStampToIso('2026-07-23T23:14:25Z'); // без изменений\n * ```\n */\nexport function utcStampToIso(value: string): string {\n const match = typeof value === 'string' ? NAIVE_STAMP.exec(value) : null;\n if (!match) return value;\n\n const iso = `${match[1]}T${match[2]}Z`;\n return Number.isFinite(Date.parse(iso)) ? iso : value;\n}\n","import { STATUS_SERVICE } from '../core/config.js';\nimport { utcStampToIso } from '../core/time.js';\nimport { isRecord, pickArray } from '../core/unwrap.js';\nimport type {\n Announcement,\n ChangelogEntry,\n PlatformStatus,\n Portal,\n ServiceStatus,\n} from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/** Требования к версии одного приложения платформы. */\nexport interface PlatformClientVersion {\n /** Минимальная поддерживаемая версия приложения. */\n minVersion: string;\n /** Последняя доступная версия приложения. */\n latestVersion: string;\n /** Адрес страницы обновления приложения. */\n updateUrl: string;\n}\n\n/** Версии клиентских приложений платформы. */\nexport interface PlatformVersions {\n android: PlatformClientVersion;\n ios: PlatformClientVersion;\n [client: string]: PlatformClientVersion;\n}\n\n/** Приводит `last_checked` каждого сервиса к ISO. Остальное остаётся как прислал сервер. */\nfunction normalizeStatus(body: PlatformStatus): PlatformStatus {\n if (!isRecord(body) || !Array.isArray(body.services)) return body;\n\n return {\n ...body,\n services: body.services.map((service: ServiceStatus) =>\n typeof service?.last_checked === 'string'\n ? { ...service, last_checked: utcStampToIso(service.last_checked) }\n : service,\n ),\n };\n}\n\n/**\n * Сведения о платформе: версии приложений, изменения, анонсы, баннер события.\n *\n * Доступна как `itd.platform`.\n */\nexport class PlatformResource extends BaseResource {\n /**\n * Загружает минимальные и актуальные версии клиентских приложений.\n *\n * Endpoint публичный: автоматическая авторизация в запрос не добавляется.\n *\n * @example\n * ```ts\n * const versions = await itd.platform.version();\n * console.log(versions.android.latestVersion);\n * ```\n */\n version(options: RequestOptions = {}): Promise<PlatformVersions> {\n return this.http.request<PlatformVersions>({\n method: 'GET',\n path: '/api/platform/version',\n skipAuth: true,\n ...this.requestOptions(options),\n });\n }\n\n /** Загружает журнал изменений. */\n async changelog(options: RequestOptions = {}): Promise<ChangelogEntry[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/platform/changelog',\n ...this.requestOptions(options),\n });\n\n return Array.isArray(body) ? (body as ChangelogEntry[]) : [];\n }\n\n /** Загружает анонсы платформы. */\n async announcements(options: RequestOptions = {}): Promise<Announcement[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/platform/announcements',\n ...this.requestOptions(options),\n });\n\n return pickArray<Announcement>(body, 'announcements');\n }\n\n /** Загружает баннер текущего события — виджет «портал». */\n portal(options: RequestOptions = {}): Promise<Portal> {\n return this.http.request<Portal>({\n method: 'GET',\n path: '/api/v1/portal',\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Загружает состояние сервисов платформы за последние 90 суток.\n *\n * Идёт на хост `статус.итд.com` без авторизации. Ответ кэшируется сервером на минуту.\n * История по суткам приходит разреженной, ровный массив даёт `statusDays`.\n *\n * @example\n * ```ts\n * const status = await itd.platform.status();\n *\n * if (status.overall_status !== 'operational') {\n * const broken = status.services.filter((s) => s.current_status !== 'operational');\n * console.log('лежит:', broken.map((s) => s.name).join(', '));\n * }\n * ```\n */\n async status(options: RequestOptions = {}): Promise<PlatformStatus> {\n const body = await this.http.request<PlatformStatus>({\n method: 'GET',\n service: STATUS_SERVICE,\n path: '/api/status',\n ...this.requestOptions(options),\n });\n\n return normalizeStatus(body);\n }\n}\n","import { ItdConfigError } from '../core/errors.js';\nimport type { Span } from '../types/models.js';\n\n/**\n * Проверяет, что spans целиком лежат внутри текста.\n *\n * Возвращает копии, чтобы последующая мутация входного массива не меняла уже собранные данные.\n */\nexport function validateSpans(content: string, spans: unknown): Span[] {\n if (!Array.isArray(spans)) {\n throw new ItdConfigError('spans должен быть массивом');\n }\n if (content.length === 0 && spans.length > 0) {\n throw new ItdConfigError('Нельзя задать spans без текста: сначала укажите непустой content');\n }\n\n return spans.map((span, index) => {\n if (!span || typeof span !== 'object') {\n throw new ItdConfigError(`Некорректный span №${index + 1}: ожидался объект`);\n }\n\n const candidate = span as Partial<Span>;\n if (\n typeof candidate.type !== 'string' ||\n !Number.isInteger(candidate.offset) ||\n !Number.isInteger(candidate.length) ||\n (candidate.offset ?? -1) < 0 ||\n (candidate.length ?? 0) <= 0 ||\n (candidate.offset ?? 0) + (candidate.length ?? 0) > content.length\n ) {\n throw new ItdConfigError(\n `Некорректный span №${index + 1}: offset и length должны указывать на непустой фрагмент content`,\n );\n }\n\n return { ...candidate } as Span;\n });\n}\n","import type { TextMarkup } from '../builders/markup.js';\nimport { SpanType } from '../types/enums.js';\nimport type { Span } from '../types/models.js';\nimport { validateSpans } from './validate.js';\n\n/** Настройки безопасного импорта разметки. */\nexport interface ParseMarkupOptions {\n /**\n * Разрешённые схемы ссылок без завершающего двоеточия.\n *\n * По умолчанию разрешены только `http` и `https`. Относительные ссылки не превращаются\n * в spans: wire-формату нужен самостоятельный адрес.\n */\n allowedLinkProtocols?: readonly string[];\n}\n\ninterface MutableMarkup {\n content: string;\n spans: Span[];\n}\n\ninterface OpenHtmlSpan {\n tag: string;\n start: number;\n span: Omit<Span, 'offset' | 'length'>;\n}\n\nconst HTML_BLOCK_TAGS = new Set([\n 'address',\n 'article',\n 'aside',\n 'blockquote',\n 'div',\n 'footer',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'header',\n 'li',\n 'main',\n 'nav',\n 'ol',\n 'p',\n 'pre',\n 'section',\n 'ul',\n]);\n\nconst HTML_DROPPED_TAGS = new Set([\n 'applet',\n 'canvas',\n 'embed',\n 'iframe',\n 'math',\n 'noscript',\n 'object',\n 'script',\n 'style',\n 'svg',\n 'template',\n]);\n\nconst HTML_ENTITIES: Readonly<Record<string, string>> = Object.freeze({\n amp: '&',\n apos: \"'\",\n gt: '>',\n lt: '<',\n nbsp: '\\u00a0',\n quot: '\"',\n});\n\nfunction append(output: MutableMarkup, value: string): void {\n output.content += value;\n}\n\nfunction addSpan(\n output: MutableMarkup,\n start: number,\n span: Omit<Span, 'offset' | 'length'>,\n): void {\n const length = output.content.length - start;\n if (length > 0) output.spans.push({ ...span, offset: start, length });\n}\n\nfunction finish(output: MutableMarkup): TextMarkup {\n const unique = new Map<string, Span>();\n for (const span of output.spans) {\n const key = [\n span.type,\n span.offset,\n span.length,\n span.url ?? '',\n span.tag ?? '',\n span.username ?? '',\n span.id ?? '',\n ].join('\\u0000');\n if (!unique.has(key)) unique.set(key, span);\n }\n\n return {\n content: output.content,\n spans: validateSpans(output.content, [...unique.values()]).sort(\n (left, right) => left.offset - right.offset || right.length - left.length,\n ),\n };\n}\n\nfunction allowedProtocols(options: ParseMarkupOptions): Set<string> {\n const values = options.allowedLinkProtocols ?? ['http', 'https'];\n return new Set(\n values\n .map((value) => `${value.toLowerCase().replace(/:$/u, '')}:`)\n .filter((value) => value !== ':'),\n );\n}\n\nfunction safeLink(value: string, protocols: ReadonlySet<string>): string | null {\n try {\n const url = new URL(value);\n return url.hostname !== '' && protocols.has(url.protocol.toLowerCase()) ? value : null;\n } catch {\n return null;\n }\n}\n\nfunction isEscaped(value: string, index: number): boolean {\n let slashes = 0;\n for (let cursor = index - 1; cursor >= 0 && value[cursor] === '\\\\'; cursor -= 1) slashes += 1;\n return slashes % 2 === 1;\n}\n\nfunction findClosing(value: string, marker: string, from: number): number {\n let cursor = from;\n while (cursor < value.length) {\n const found = value.indexOf(marker, cursor);\n if (found < 0) return -1;\n if (!isEscaped(value, found)) return found;\n cursor = found + marker.length;\n }\n return -1;\n}\n\nfunction findBracketEnd(value: string, from: number, open: string, close: string): number {\n let depth = 1;\n for (let cursor = from; cursor < value.length; cursor += 1) {\n const character = value[cursor];\n if (character === '\\\\') {\n cursor += 1;\n continue;\n }\n if (character === open) depth += 1;\n if (character === close) {\n depth -= 1;\n if (depth === 0) return cursor;\n }\n }\n return -1;\n}\n\nfunction decodeEntities(value: string): string {\n return value.replace(/&(#(?:x[\\da-f]+|\\d+)|[a-z][a-z\\d]+);/giu, (raw, entity: string) => {\n if (entity.startsWith('#x') || entity.startsWith('#X')) {\n const code = Number.parseInt(entity.slice(2), 16);\n return Number.isFinite(code) && code <= 0x10ffff ? String.fromCodePoint(code) : raw;\n }\n if (entity.startsWith('#')) {\n const code = Number.parseInt(entity.slice(1), 10);\n return Number.isFinite(code) && code <= 0x10ffff ? String.fromCodePoint(code) : raw;\n }\n return HTML_ENTITIES[entity.toLowerCase()] ?? raw;\n });\n}\n\nfunction normalizeCodeSpan(value: string): string {\n const normalized = value.replace(/\\r\\n?|\\n/gu, ' ');\n if (\n normalized.length >= 2 &&\n normalized.startsWith(' ') &&\n normalized.endsWith(' ') &&\n !/^ +$/u.test(normalized)\n ) {\n return normalized.slice(1, -1);\n }\n return normalized;\n}\n\nfunction markdownStyle(\n source: string,\n index: number,\n): { marker: string; type: Span['type'] } | undefined {\n const styles = [\n { marker: '**', type: SpanType.Bold },\n { marker: '__', type: SpanType.Bold },\n { marker: '~~', type: SpanType.Strike },\n { marker: '||', type: SpanType.Spoiler },\n { marker: '*', type: SpanType.Italic },\n { marker: '_', type: SpanType.Italic },\n ] as const;\n\n for (const style of styles) {\n if (!source.startsWith(style.marker, index)) continue;\n if (style.marker.includes('_')) {\n const before = source[index - 1] ?? '';\n const after = source[index + style.marker.length] ?? '';\n if (/[\\p{L}\\p{N}]/u.test(before) && /[\\p{L}\\p{N}]/u.test(after)) continue;\n }\n return style;\n }\n return undefined;\n}\n\nfunction parseMarkdownInline(\n source: string,\n output: MutableMarkup,\n protocols: ReadonlySet<string>,\n): void {\n let index = 0;\n while (index < source.length) {\n const character = source[index] ?? '';\n\n if (character === '\\\\' && index + 1 < source.length) {\n append(output, source[index + 1] ?? '');\n index += 2;\n continue;\n }\n\n if (character === '`') {\n const run = source.slice(index).match(/^`+/u)?.[0] ?? '`';\n const close = findClosing(source, run, index + run.length);\n if (close > index + run.length) {\n const start = output.content.length;\n append(output, normalizeCodeSpan(source.slice(index + run.length, close)));\n addSpan(output, start, { type: SpanType.Monospace });\n index = close + run.length;\n continue;\n }\n }\n\n const image = source.startsWith('![', index);\n if ((image || character === '[') && !isEscaped(source, index)) {\n const labelStart = index + (image ? 2 : 1);\n const labelEnd = findBracketEnd(source, labelStart, '[', ']');\n if (labelEnd >= 0 && source[labelEnd + 1] === '(') {\n const destinationEnd = findBracketEnd(source, labelEnd + 2, '(', ')');\n if (destinationEnd >= 0) {\n const label = source.slice(labelStart, labelEnd);\n const rawDestination = source.slice(labelEnd + 2, destinationEnd).trim();\n const destination = rawDestination.replace(/\\\\([\\\\)])/gu, '$1');\n const start = output.content.length;\n parseMarkdownInline(label, output, protocols);\n if (!image) {\n const url = safeLink(destination, protocols);\n if (url) addSpan(output, start, { type: SpanType.Link, url });\n }\n index = destinationEnd + 1;\n continue;\n }\n }\n }\n\n if (source.slice(index, index + 3).toLowerCase() === '<u>') {\n const close = source.toLowerCase().indexOf('</u>', index + 3);\n if (close > index + 3) {\n const start = output.content.length;\n parseMarkdownInline(source.slice(index + 3, close), output, protocols);\n addSpan(output, start, { type: SpanType.Underline });\n index = close + 4;\n continue;\n }\n }\n\n if (character === '<') {\n const close = source.indexOf('>', index + 1);\n if (close > index + 1) {\n const candidate = decodeEntities(source.slice(index + 1, close));\n const url = safeLink(candidate, protocols);\n if (url) {\n const start = output.content.length;\n append(output, candidate);\n addSpan(output, start, { type: SpanType.Link, url });\n index = close + 1;\n continue;\n }\n }\n }\n\n const style = markdownStyle(source, index);\n if (style) {\n const close = findClosing(source, style.marker, index + style.marker.length);\n if (close > index + style.marker.length) {\n const start = output.content.length;\n parseMarkdownInline(source.slice(index + style.marker.length, close), output, protocols);\n addSpan(output, start, { type: style.type });\n index = close + style.marker.length;\n continue;\n }\n }\n\n if (character === '&') {\n const entity = source.slice(index).match(/^&(?:#(?:x[\\da-f]+|\\d+)|[a-z][a-z\\d]+);/iu)?.[0];\n if (entity) {\n append(output, decodeEntities(entity));\n index += entity.length;\n continue;\n }\n }\n\n append(output, character);\n index += 1;\n }\n}\n\nfunction lineEnd(source: string, from: number): number {\n const found = source.indexOf('\\n', from);\n return found < 0 ? source.length : found;\n}\n\n/**\n * Преобразует безопасное подмножество Markdown в текст и wire-spans.\n *\n * Поддерживаются bold, italic, strike, spoiler, inline/fenced code, ссылки, underline\n * через `<u>` и цитаты `>`. Неподдерживаемая и незакрытая разметка остаётся текстом.\n * У изображений сохраняется alt-текст без URL.\n */\nexport function parseMarkdown(source: string, options: ParseMarkupOptions = {}): TextMarkup {\n const output: MutableMarkup = { content: '', spans: [] };\n const protocols = allowedProtocols(options);\n let position = 0;\n\n while (position < source.length) {\n const end = lineEnd(source, position);\n const line = source.slice(position, end);\n const hasNewline = end < source.length;\n const fence = line.match(/^ {0,3}(`{3,}|~{3,})[^\\n]*$/u)?.[1];\n\n if (fence) {\n let closeStart = hasNewline ? end + 1 : source.length;\n let closeEnd = closeStart;\n let found = false;\n while (closeStart < source.length) {\n closeEnd = lineEnd(source, closeStart);\n const closeLine = source.slice(closeStart, closeEnd);\n const marker = closeLine.match(/^ {0,3}(`{3,}|~{3,})\\s*$/u)?.[1];\n if (marker !== undefined && marker[0] === fence[0] && marker.length >= fence.length) {\n found = true;\n break;\n }\n closeStart = closeEnd < source.length ? closeEnd + 1 : source.length;\n }\n\n if (found) {\n const bodyStart = end + 1;\n let body = source.slice(bodyStart, closeStart);\n if (body.endsWith('\\n')) body = body.slice(0, -1);\n const start = output.content.length;\n append(output, body);\n addSpan(output, start, { type: SpanType.Monospace });\n if (closeEnd < source.length) append(output, '\\n');\n position = closeEnd < source.length ? closeEnd + 1 : source.length;\n continue;\n }\n }\n\n const quote = line.match(/^ {0,3}> ?(.*)$/u);\n if (quote) {\n const start = output.content.length;\n parseMarkdownInline(quote[1] ?? '', output, protocols);\n addSpan(output, start, { type: SpanType.Quote });\n } else {\n parseMarkdownInline(line, output, protocols);\n }\n\n if (hasNewline) append(output, '\\n');\n position = hasNewline ? end + 1 : source.length;\n }\n\n return finish(output);\n}\n\nfunction readAttribute(source: string, name: string): string | undefined {\n const pattern = new RegExp(\n `(?:^|\\\\s)${name}\\\\s*=\\\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\\\s\"'=<>\\\\x60]+))`,\n 'iu',\n );\n const match = source.match(pattern);\n const value = match?.[1] ?? match?.[2] ?? match?.[3];\n return value === undefined ? undefined : decodeEntities(value);\n}\n\nfunction htmlSpan(\n tag: string,\n attributes: string,\n protocols: ReadonlySet<string>,\n): Omit<Span, 'offset' | 'length'> | undefined {\n switch (tag) {\n case 'b':\n case 'strong':\n return { type: SpanType.Bold };\n case 'em':\n case 'i':\n return { type: SpanType.Italic };\n case 'u':\n return { type: SpanType.Underline };\n case 'del':\n case 's':\n case 'strike':\n return { type: SpanType.Strike };\n case 'code':\n case 'pre':\n return { type: SpanType.Monospace };\n case 'blockquote':\n return { type: SpanType.Quote };\n case 'a': {\n const href = readAttribute(attributes, 'href');\n const url = href ? safeLink(href, protocols) : null;\n return url ? { type: SpanType.Link, url } : undefined;\n }\n case 'span': {\n const classes = readAttribute(attributes, 'class')?.split(/\\s+/u) ?? [];\n if (\n readAttribute(attributes, 'data-spoiler') === 'true' ||\n classes.some((value) => value.endsWith('-spoiler'))\n ) {\n return { type: SpanType.Spoiler };\n }\n if (classes.some((value) => value.endsWith('-quote'))) {\n return { type: SpanType.Quote };\n }\n return undefined;\n }\n default:\n return undefined;\n }\n}\n\nfunction ensureBlockBreak(output: MutableMarkup): void {\n if (output.content !== '' && !output.content.endsWith('\\n')) append(output, '\\n');\n}\n\nfunction findHtmlTagEnd(source: string, from: number): number {\n let quote: '\"' | \"'\" | undefined;\n\n for (let index = from; index < source.length; index += 1) {\n const character = source[index];\n if (quote) {\n if (character === quote) quote = undefined;\n continue;\n }\n if (character === '\"' || character === \"'\") {\n quote = character;\n continue;\n }\n if (character === '>') return index;\n }\n\n return -1;\n}\n\n/**\n * Преобразует ограниченный HTML в обычный текст и wire-spans.\n *\n * Разрешены только семантические теги форматирования. Атрибуты событий игнорируются,\n * содержимое `script`, `style`, `iframe`, `object`, SVG и подобных активных элементов\n * удаляется. Опасный `href` сохраняет текст ссылки, но не создаёт span.\n */\nexport function parseHtml(source: string, options: ParseMarkupOptions = {}): TextMarkup {\n const output: MutableMarkup = { content: '', spans: [] };\n const protocols = allowedProtocols(options);\n const stack: OpenHtmlSpan[] = [];\n let position = 0;\n\n const closeTag = (tag: string): void => {\n for (let index = stack.length - 1; index >= 0; index -= 1) {\n const open = stack[index];\n if (open?.tag !== tag) continue;\n stack.splice(index, 1);\n addSpan(output, open.start, open.span);\n return;\n }\n };\n\n while (position < source.length) {\n if (source.startsWith('<!--', position)) {\n const end = source.indexOf('-->', position + 4);\n position = end < 0 ? source.length : end + 3;\n continue;\n }\n\n if (source[position] === '<') {\n const end = findHtmlTagEnd(source, position + 1);\n if (end >= 0) {\n const raw = source.slice(position, end + 1);\n const parsed = raw.match(/^<\\s*(\\/?)\\s*([a-z][\\w-]*)([\\s\\S]*?)\\/?\\s*>$/iu);\n if (parsed) {\n const closing = parsed[1] === '/';\n const tag = (parsed[2] ?? '').toLowerCase();\n const attributes = parsed[3] ?? '';\n\n if (!closing && HTML_DROPPED_TAGS.has(tag)) {\n const closingPattern = new RegExp(`<\\\\s*\\\\/\\\\s*${tag}\\\\s*>`, 'igu');\n closingPattern.lastIndex = end + 1;\n const match = closingPattern.exec(source);\n position = match ? match.index + match[0].length : source.length;\n continue;\n }\n\n if (tag === 'br' && !closing) {\n append(output, '\\n');\n position = end + 1;\n continue;\n }\n\n if (tag === 'img' && !closing) {\n append(output, readAttribute(attributes, 'alt') ?? '');\n position = end + 1;\n continue;\n }\n\n if (closing) {\n closeTag(tag);\n if (HTML_BLOCK_TAGS.has(tag)) ensureBlockBreak(output);\n } else {\n if (HTML_BLOCK_TAGS.has(tag)) ensureBlockBreak(output);\n const span = htmlSpan(tag, attributes, protocols);\n if (span) stack.push({ tag, start: output.content.length, span });\n }\n\n position = end + 1;\n continue;\n }\n }\n }\n\n const next = source.indexOf('<', position + 1);\n const end = next < 0 ? source.length : next;\n append(output, decodeEntities(source.slice(position, end)));\n position = end;\n }\n\n while (stack.length > 0) {\n const open = stack.pop();\n if (open) addSpan(output, open.start, open.span);\n }\n\n while (output.content.endsWith('\\n')) output.content = output.content.slice(0, -1);\n return finish(output);\n}\n","import { ItdConfigError } from '../core/errors.js';\nimport { validateSpans } from '../spans/validate.js';\nimport { SpanType } from '../types/enums.js';\nimport type { Span } from '../types/models.js';\nimport { BUILDER, type BuilderInput, type ItdBuilder, resolveInput } from './base.js';\n\n/** Текст вместе с рассчитанной разметкой. */\nexport interface TextMarkup {\n content: string;\n spans: Span[];\n}\n\n/** Описание фрагмента без смещения: его вычисляет {@link MarkupBuilder}. */\nexport type MarkupSpan = Omit<Span, 'offset' | 'length'>;\n\n/** Что принимает метод разметки: результат, билдер или функция-настройщик. */\nexport type MarkupInput = BuilderInput<TextMarkup, MarkupBuilder>;\n\n/**\n * Содержимое форматированного фрагмента.\n *\n * Строка создаёт простой фрагмент. Билдер, готовая разметка или функция позволяют вложить\n * одни spans в другие, как при последовательном форматировании выделения в редакторе сайта.\n */\nexport type MarkupContent = string | MarkupInput;\n\n/** Какие сущности искать в {@link autoSpans}. */\nexport interface AutoSpansOptions {\n /** Находить `#хэштеги`. По умолчанию `true`. */\n hashtags?: boolean;\n /** Находить `@упоминания`. По умолчанию `true`. */\n mentions?: boolean;\n /** Находить абсолютные HTTP(S)-ссылки. По умолчанию `true`. */\n links?: boolean;\n}\n\nfunction validateMarkup(value: TextMarkup): TextMarkup {\n if (!value || typeof value.content !== 'string' || !Array.isArray(value.spans)) {\n throw new ItdConfigError('Разметка должна содержать строку content и массив spans');\n }\n\n return {\n content: value.content,\n spans: validateSpans(value.content, value.spans).sort(\n (left, right) => left.offset - right.offset || right.length - left.length,\n ),\n };\n}\n\nfunction entityName(value: string, prefix: '#' | '@', label: string): string {\n const name = value.startsWith(prefix) ? value.slice(prefix.length) : value;\n if (name === '' || /\\s/u.test(name)) {\n throw new ItdConfigError(`${label} должен быть непустым и не содержать пробелов`);\n }\n return name;\n}\n\n/**\n * Неизменяемый билдер текста с разметкой.\n *\n * Каждый метод дописывает фрагмент и сам считает `offset` и `length` в единицах UTF-16 —\n * именно такие индексы использует JavaScript-редактор сайта.\n */\nexport class MarkupBuilder implements ItdBuilder<TextMarkup> {\n /** @internal */\n readonly [BUILDER] = true as const;\n\n readonly #content: string;\n readonly #spans: Span[];\n\n /** @internal Создавайте билдер функцией {@link markup}. */\n constructor(content: string, spans: Span[]) {\n this.#content = content;\n this.#spans = spans;\n }\n\n /** Дописывает обычный текст без разметки. */\n text(value: string): MarkupBuilder {\n return new MarkupBuilder(this.#content + value, this.#spans);\n }\n\n /** Дописывает переводы строк. */\n newline(count = 1): MarkupBuilder {\n if (!Number.isInteger(count) || count < 0) {\n throw new ItdConfigError('Количество переводов строк должно быть целым неотрицательным');\n }\n return this.text('\\n'.repeat(count));\n }\n\n /** @internal Добавляет готовый фрагмент и сдвигает его spans к текущему концу текста. */\n #formatted(value: MarkupContent, outerSpans: readonly MarkupSpan[]): MarkupBuilder {\n const fragment =\n typeof value === 'string' ? { content: value, spans: [] } : resolveMarkup(value);\n if (fragment.content.length === 0) {\n throw new ItdConfigError('Размечаемый фрагмент не может быть пустым');\n }\n\n const offset = this.#content.length;\n const shifted = fragment.spans.map((span) => ({\n ...span,\n offset: span.offset + offset,\n }));\n const outer = outerSpans.map((span) => ({\n ...span,\n offset,\n length: fragment.content.length,\n }));\n\n return new MarkupBuilder(this.#content + fragment.content, [\n ...this.#spans,\n ...shifted,\n ...outer,\n ]);\n }\n\n /**\n * Дописывает фрагмент с произвольным типом разметки.\n *\n * Вложенный билдер позволяет форматировать часть фрагмента дополнительным стилем.\n * Для нескольких стилей на всём фрагменте используйте {@link styled}.\n */\n span(value: MarkupContent, span: MarkupSpan): MarkupBuilder {\n return this.#formatted(value, [span]);\n }\n\n /**\n * Дописывает фрагмент с несколькими стилями на одном диапазоне.\n *\n * Для `link`, которому нужен `url`, используйте {@link link}; произвольные spans с\n * метаданными можно объединять через вложенные вызовы {@link span}.\n */\n styled(value: MarkupContent, ...types: Span['type'][]): MarkupBuilder {\n if (types.length === 0) return this.#formatted(value, []);\n return this.#formatted(\n value,\n types.map((type) => ({ type })),\n );\n }\n\n /** Дописывает `#хэштег` и сохраняет имя без решётки в `tag`. */\n hashtag(tag: string): MarkupBuilder {\n const name = entityName(tag, '#', 'Хэштег');\n return this.span(`#${name}`, { type: SpanType.Hashtag, tag: name });\n }\n\n /** Дописывает `@username` и сохраняет имя пользователя в `username`. */\n mention(username: string): MarkupBuilder {\n const name = entityName(username, '@', 'Упоминание');\n return this.span(`@${name}`, { type: SpanType.Mention, username: name });\n }\n\n /**\n * Дописывает ссылку.\n *\n * Для строки адрес по умолчанию становится и текстом ссылки. Вложенному форматированному\n * фрагменту URL нужно передать явно.\n */\n link(content: MarkupContent, url?: string): MarkupBuilder {\n const resolvedUrl = url ?? (typeof content === 'string' ? content : undefined);\n if (resolvedUrl === undefined) {\n throw new ItdConfigError('Для форматированного текста ссылки нужно явно передать URL');\n }\n return this.span(content, { type: SpanType.Link, url: resolvedUrl });\n }\n\n bold(content: MarkupContent): MarkupBuilder {\n return this.span(content, { type: SpanType.Bold });\n }\n\n italic(content: MarkupContent): MarkupBuilder {\n return this.span(content, { type: SpanType.Italic });\n }\n\n underline(content: MarkupContent): MarkupBuilder {\n return this.span(content, { type: SpanType.Underline });\n }\n\n strike(content: MarkupContent): MarkupBuilder {\n return this.span(content, { type: SpanType.Strike });\n }\n\n spoiler(content: MarkupContent): MarkupBuilder {\n return this.span(content, { type: SpanType.Spoiler });\n }\n\n monospace(content: MarkupContent): MarkupBuilder {\n return this.span(content, { type: SpanType.Monospace });\n }\n\n quote(content: MarkupContent): MarkupBuilder {\n return this.span(content, { type: SpanType.Quote });\n }\n\n build(): TextMarkup {\n return validateMarkup({ content: this.#content, spans: this.#spans });\n }\n\n toJSON(): TextMarkup {\n return this.build();\n }\n}\n\n/** Начинает сборку текста с автоматически вычисляемыми смещениями. */\nexport function markup(content = ''): MarkupBuilder {\n return new MarkupBuilder(content, []);\n}\n\n/** Приводит любую форму разметки к тексту и массиву spans. */\nexport function resolveMarkup(input: MarkupInput): TextMarkup {\n return resolveInput(input, () => markup(), validateMarkup);\n}\n\nfunction trimUrlEnd(value: string): string {\n let result = value;\n const pairs = [\n ['(', ')'],\n ['[', ']'],\n ['{', '}'],\n ] as const;\n\n let changed: boolean;\n do {\n const before = result;\n while (/[.,!?;:…'\"»”]$/u.test(result)) result = result.slice(0, -1);\n\n for (const [open, close] of pairs) {\n while (result.endsWith(close) && result.split(close).length > result.split(open).length) {\n result = result.slice(0, -1);\n }\n }\n changed = result !== before;\n } while (changed);\n\n return result;\n}\n\nfunction overlaps(span: Span, start: number, end: number): boolean {\n return span.offset < end && start < span.offset + span.length;\n}\n\nfunction isHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return (url.protocol === 'http:' || url.protocol === 'https:') && url.hostname !== '';\n } catch {\n return false;\n }\n}\n\nfunction previousCodePoint(text: string, index: number): string {\n if (index <= 0) return '';\n const last = text.charCodeAt(index - 1);\n if (last >= 0xdc00 && last <= 0xdfff && index >= 2) {\n const first = text.charCodeAt(index - 2);\n if (first >= 0xd800 && first <= 0xdbff) return text.slice(index - 2, index);\n }\n return text[index - 1] ?? '';\n}\n\n/**\n * Находит в тексте те сущности, которые сайт получает после серверного разбора:\n * HTTP(S)-ссылки, `#хэштеги` и `@упоминания`.\n *\n * Смещения выражены в UTF-16 code units, поэтому совпадают с `String#slice`,\n * `substring`, DOM Selection и wire-форматом сайта даже при наличии эмодзи.\n */\nexport function autoSpans(text: string, options: AutoSpansOptions = {}): Span[] {\n const includeLinks = options.links ?? true;\n const includeHashtags = options.hashtags ?? true;\n const includeMentions = options.mentions ?? true;\n const spans: Span[] = [];\n const links: Span[] = [];\n\n const urlPattern = /https?:\\/\\/[^\\s<>\"'`]+/giu;\n for (const match of text.matchAll(urlPattern)) {\n const raw = trimUrlEnd(match[0]);\n if (raw.length === 0 || match.index === undefined || !isHttpUrl(raw)) continue;\n links.push({\n type: SpanType.Link,\n offset: match.index,\n length: raw.length,\n url: raw,\n });\n }\n if (includeLinks) spans.push(...links);\n\n if (includeHashtags || includeMentions) {\n const entityPattern = /#([\\p{L}\\p{M}\\p{N}_]+)|@([a-z\\d][a-z\\d._-]*)/giu;\n\n for (const match of text.matchAll(entityPattern)) {\n if (match.index === undefined) continue;\n const offset = match.index;\n const previous = previousCodePoint(text, offset);\n if (/[\\p{L}\\p{M}\\p{N}_#@.]/u.test(previous)) continue;\n\n const end = offset + match[0].length;\n if (links.some((span) => overlaps(span, offset, end))) continue;\n\n const hashtag = match[1];\n const username = match[2]?.replace(/[._-]+$/u, '');\n if (hashtag !== undefined && includeHashtags) {\n spans.push({\n type: SpanType.Hashtag,\n offset,\n length: match[0].length,\n tag: hashtag,\n });\n } else if (username !== undefined && includeMentions) {\n spans.push({\n type: SpanType.Mention,\n offset,\n length: username.length + 1,\n username,\n });\n }\n }\n }\n\n return spans.sort((left, right) => left.offset - right.offset || right.length - left.length);\n}\n","import { ItdConfigError } from '../core/errors.js';\nimport type { CreatePollInput } from '../types/params.js';\nimport { BUILDER, type BuilderInput, type ItdBuilder, resolveInput } from './base.js';\n\n/** Минимальное число вариантов ответа. */\nconst MIN_OPTIONS = 2;\n\n/** Ограничения опроса — те же, что действуют в интерфейсе итд.com. */\nconst MAX_OPTIONS = 10;\nconst MAX_QUESTION_LENGTH = 200;\nconst MAX_OPTION_LENGTH = 100;\n\n/**\n * Проверяет данные опроса.\n *\n * Применяется и к билдеру, и к обычному объекту — правила одни и те же.\n *\n * @throws {ItdConfigError} если вопрос пуст, вариантов меньше двух или есть дубли\n */\nexport function validatePoll(input: CreatePollInput): CreatePollInput {\n const question = input?.question;\n\n if (typeof question !== 'string' || question.trim() === '') {\n throw new ItdConfigError('Опрос требует непустого вопроса');\n }\n\n if (question.trim().length > MAX_QUESTION_LENGTH) {\n throw new ItdConfigError(\n `Вопрос длиннее ${MAX_QUESTION_LENGTH} символов (передано ${question.trim().length})`,\n );\n }\n\n const options = Array.isArray(input.options) ? input.options : [];\n\n const texts = options.map((option, index) => {\n const text = typeof option?.text === 'string' ? option.text.trim() : '';\n if (text === '') {\n throw new ItdConfigError(`Вариант ответа №${index + 1} пуст — у каждого должен быть текст`);\n }\n if (text.length > MAX_OPTION_LENGTH) {\n throw new ItdConfigError(\n `Вариант ответа №${index + 1} длиннее ${MAX_OPTION_LENGTH} символов (передано ${text.length})`,\n );\n }\n return text;\n });\n\n if (texts.length < MIN_OPTIONS) {\n throw new ItdConfigError(\n `Опрос требует минимум ${MIN_OPTIONS} варианта, передано: ${texts.length}`,\n );\n }\n\n if (texts.length > MAX_OPTIONS) {\n throw new ItdConfigError(\n `Опрос допускает не больше ${MAX_OPTIONS} вариантов, передано: ${texts.length}`,\n );\n }\n\n const seen = new Set<string>();\n for (const text of texts) {\n if (seen.has(text)) {\n throw new ItdConfigError(`Вариант «${text}» повторяется — варианты должны различаться`);\n }\n seen.add(text);\n }\n\n return {\n question: question.trim(),\n options: texts.map((text) => ({ text })),\n // Поле обязательно: без него сервер отвергает создание поста с опросом,\n // даже когда выбор одиночный.\n multipleChoice: input.multipleChoice ?? false,\n };\n}\n\n/**\n * Билдер опроса.\n *\n * Неизменяемый: каждый вызов возвращает новый экземпляр, поэтому заготовку можно\n * переиспользовать, не боясь её испортить. Создаётся функцией {@link poll}.\n */\nexport class PollBuilder implements ItdBuilder<CreatePollInput> {\n /** @internal */\n readonly [BUILDER] = true as const;\n\n readonly #state: CreatePollInput;\n\n /** @internal Создавайте билдер функцией {@link poll}. */\n constructor(state: CreatePollInput) {\n this.#state = state;\n }\n\n /** Задаёт вопрос. */\n question(text: string): PollBuilder {\n return new PollBuilder({ ...this.#state, question: text });\n }\n\n /** Добавляет один вариант ответа. */\n option(text: string): PollBuilder {\n return new PollBuilder({ ...this.#state, options: [...this.#state.options, { text }] });\n }\n\n /**\n * Добавляет несколько вариантов сразу.\n *\n * @example\n * ```ts\n * poll('ну как?').options('да', 'нет', 'не знаю');\n * ```\n */\n options(...texts: string[]): PollBuilder {\n return new PollBuilder({\n ...this.#state,\n options: [...this.#state.options, ...texts.map((text) => ({ text }))],\n });\n }\n\n /** Разрешает выбор нескольких вариантов. */\n multipleChoice(enabled = true): PollBuilder {\n return new PollBuilder({ ...this.#state, multipleChoice: enabled });\n }\n\n build(): CreatePollInput {\n return validatePoll(this.#state);\n }\n\n toJSON(): CreatePollInput {\n return this.build();\n }\n}\n\n/**\n * Начинает сборку опроса.\n *\n * @param question вопрос; можно задать позже методом {@link PollBuilder.question}\n *\n * @example\n * ```ts\n * import { poll } from 'itd-api';\n *\n * const q = poll('Какой язык лучше?')\n * .options('TypeScript', 'JavaScript')\n * .multipleChoice();\n *\n * await itd.posts.create({ content: 'голосуем', poll: q });\n * ```\n */\nexport function poll(question = ''): PollBuilder {\n return new PollBuilder({ question, options: [] });\n}\n\n/** Что принимает параметр опроса: объект, билдер или функция-настройщик. */\nexport type PollInput = BuilderInput<CreatePollInput, PollBuilder>;\n\n/** Приводит любую форму входа к готовым данным опроса. */\nexport function resolvePoll(input: PollInput): CreatePollInput {\n return resolveInput(input, () => poll(), validatePoll);\n}\n","import type { FileInput } from '../core/attachments.js';\nimport { ItdConfigError } from '../core/errors.js';\nimport { type ParseMarkupOptions, parseHtml, parseMarkdown } from '../spans/parse.js';\nimport { validateSpans } from '../spans/validate.js';\nimport { SpanType } from '../types/enums.js';\nimport type { Span, UserId } from '../types/models.js';\nimport type { CreatePostInput, UpdatePostInput } from '../types/params.js';\nimport { BUILDER, type BuilderInput, type ItdBuilder, isBuilder, resolveInput } from './base.js';\nimport { type AutoSpansOptions, autoSpans, type MarkupInput, resolveMarkup } from './markup.js';\nimport { type PollInput, resolvePoll } from './poll.js';\n\n/** UUID любой версии. */\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst BUILD_UPDATE = Symbol.for('itd.postBuilder.update');\n\n/**\n * Проверяет данные поста.\n *\n * Отдельного внимания заслуживает `wallRecipientId`: API принимает там **только UUID**,\n * а имя пользователя молча приводит к ошибке на сервере. Проверка здесь превращает\n * это в понятное сообщение до отправки запроса.\n *\n * @throws {ItdConfigError} если пост пуст или получатель стены задан именем пользователя\n */\nexport function validatePost(input: CreatePostInput): CreatePostInput {\n const content = typeof input?.content === 'string' ? input.content : '';\n const attachmentIds = input?.attachmentIds ?? [];\n const files = input?.files ?? [];\n\n const hasContent = content.trim() !== '';\n const hasAttachments = attachmentIds.length > 0 || files.length > 0;\n const hasPoll = Boolean(input?.poll);\n\n if (!hasContent && !hasAttachments && !hasPoll) {\n throw new ItdConfigError('Пост пуст: нужен текст, вложение или опрос');\n }\n\n const wallRecipientId = input.wallRecipientId;\n if (wallRecipientId !== undefined && wallRecipientId !== null) {\n if (!UUID_PATTERN.test(wallRecipientId)) {\n throw new ItdConfigError(\n `wallRecipientId должен быть UUID, а не именем пользователя (получено: «${wallRecipientId}»). ` +\n 'Идентификатор можно взять из профиля: (await itd.users.get(username)).id',\n );\n }\n }\n\n // Опрос внутри обычного объекта тоже может быть билдером или функцией — приводим его\n // здесь, чтобы форма записи не влияла на результат и на проверки.\n return {\n ...input,\n ...(input.spans !== undefined ? { spans: validateSpans(content, input.spans) } : {}),\n ...(input.poll !== undefined ? { poll: resolvePoll(input.poll) } : {}),\n };\n}\n\n/** Внутреннее состояние {@link PostBuilder}. */\ninterface PostState extends CreatePostInput {\n content: string;\n contentSet: boolean;\n attachmentIds: string[];\n files: FileInput[];\n}\n\n/**\n * Билдер поста.\n *\n * Неизменяемый: каждый вызов возвращает новый экземпляр, поэтому заготовку можно\n * переиспользовать. Создаётся функцией {@link post}.\n *\n * @example Заготовка для нескольких постов\n * ```ts\n * const onWall = post().onWall(userId);\n *\n * await itd.posts.create(onWall.content('первый'));\n * await itd.posts.create(onWall.content('второй')); // заготовка не испорчена\n * ```\n */\nexport class PostBuilder implements ItdBuilder<CreatePostInput> {\n /** @internal */\n readonly [BUILDER] = true as const;\n\n readonly #state: PostState;\n\n /** @internal Создавайте билдер функцией {@link post}. */\n constructor(state: PostState) {\n this.#state = state;\n }\n\n /**\n * Задаёт текст поста, заменяя прежний вместе с его разметкой.\n *\n * Spans привязаны к конкретному тексту, поэтому после замены их нужно задать заново\n * через {@link spans}, {@link markup} или {@link autoSpans}.\n */\n content(text: string): PostBuilder {\n const state: PostState = { ...this.#state, content: text, contentSet: true };\n delete state.spans;\n return new PostBuilder(state);\n }\n\n /** Дописывает текст к уже заданному. */\n append(text: string): PostBuilder {\n const separator = this.#state.content === '' ? '' : '\\n';\n return new PostBuilder({\n ...this.#state,\n content: this.#state.content + separator + text,\n contentSet: true,\n });\n }\n\n /**\n * Задаёт готовую разметку текста. Смещения проверяются при {@link build}.\n *\n * Для автоматического поиска сущностей есть {@link autoSpans}, а для вычисления смещений\n * при сборке текста — {@link markup}.\n */\n spans(spans: Span[]): PostBuilder {\n return new PostBuilder({ ...this.#state, spans });\n }\n\n /**\n * Заменяет текст и разметку результатом {@link MarkupBuilder}.\n *\n * @example\n * ```ts\n * post().markup((m) => m.text('смотрите ').hashtag('котики').text(' от ').mention('nowkie'));\n * ```\n */\n markup(input: MarkupInput): PostBuilder {\n const result = resolveMarkup(input);\n return new PostBuilder({\n ...this.#state,\n content: result.content,\n contentSet: true,\n spans: result.spans,\n });\n }\n\n /** Заменяет текст и spans результатом безопасного разбора Markdown. */\n markdown(source: string, options: ParseMarkupOptions = {}): PostBuilder {\n return this.markup(parseMarkdown(source, options));\n }\n\n /** Заменяет текст и spans результатом безопасного разбора ограниченного HTML. */\n html(source: string, options: ParseMarkupOptions = {}): PostBuilder {\n return this.markup(parseHtml(source, options));\n }\n\n /**\n * Находит HTTP(S)-ссылки, хэштеги и упоминания в уже заданном тексте.\n *\n * Ручные стили сохраняются. Повторный вызов не дублирует уже найденные сущности.\n */\n autoSpans(options: AutoSpansOptions = {}): PostBuilder {\n const current = this.#state.spans ?? [];\n const entityTypes = new Set<string>([SpanType.Hashtag, SpanType.Mention, SpanType.Link]);\n const detected = autoSpans(this.#state.content, options).filter((candidate) => {\n return !current.some(\n (span) =>\n entityTypes.has(span.type) &&\n span.offset < candidate.offset + candidate.length &&\n candidate.offset < span.offset + span.length,\n );\n });\n\n return new PostBuilder({\n ...this.#state,\n spans: [...current, ...detected].sort(\n (left, right) => left.offset - right.offset || right.length - left.length,\n ),\n });\n }\n\n /**\n * Публикует пост на стене другого пользователя.\n *\n * @param userId **UUID** пользователя; имя пользователя не подойдёт\n */\n onWall(userId: UserId): PostBuilder {\n return new PostBuilder({ ...this.#state, wallRecipientId: userId });\n }\n\n /**\n * Прикладывает файл — он будет загружен перед публикацией.\n *\n * Порядок вызовов сохраняется в порядке вложений.\n */\n attach(file: FileInput): PostBuilder {\n return new PostBuilder({ ...this.#state, files: [...this.#state.files, file] });\n }\n\n /** Прикладывает уже загруженное вложение по его идентификатору. */\n attachId(attachmentId: string): PostBuilder {\n return new PostBuilder({\n ...this.#state,\n attachmentIds: [...this.#state.attachmentIds, attachmentId],\n });\n }\n\n /**\n * Добавляет опрос.\n *\n * Принимает объект, {@link PollBuilder} или функцию-настройщик.\n *\n * @example\n * ```ts\n * post('голосуем').poll((q) => q.question('ну как?').options('да', 'нет'));\n * ```\n */\n poll(input: PollInput): PostBuilder {\n return new PostBuilder({ ...this.#state, poll: resolvePoll(input) });\n }\n\n #input(includeEmptyContent = false): CreatePostInput {\n const { content, contentSet: _contentSet, attachmentIds, files, ...rest } = this.#state;\n return {\n ...rest,\n ...(content !== '' || includeEmptyContent ? { content } : {}),\n ...(attachmentIds.length > 0 ? { attachmentIds } : {}),\n ...(files.length > 0 ? { files } : {}),\n };\n }\n\n build(): CreatePostInput {\n return validatePost(this.#input());\n }\n\n /** @internal Собирает данные по правилам `posts.update`, не применяя правила создания. */\n [BUILD_UPDATE](): UpdatePostInput {\n return validatePostUpdate(this.#input(this.#state.contentSet));\n }\n\n toJSON(): CreatePostInput {\n return this.build();\n }\n}\n\n/**\n * Начинает сборку поста.\n *\n * @param content текст; можно задать позже методом {@link PostBuilder.content}\n *\n * @example\n * ```ts\n * import { post } from 'itd-api';\n *\n * await itd.posts.create(\n * post('смотрите что нашёл')\n * .attach({ url: 'https://example.com/photo.jpg' })\n * .poll((q) => q.question('нравится?').options('да', 'нет')),\n * );\n * ```\n */\nexport function post(content?: string): PostBuilder {\n return new PostBuilder({\n content: content ?? '',\n contentSet: content !== undefined,\n attachmentIds: [],\n files: [],\n });\n}\n\n/** Что принимает параметр поста: объект, билдер или функция-настройщик. */\nexport type PostInput = BuilderInput<CreatePostInput, PostBuilder>;\n\n/** Приводит любую форму входа к готовым данным поста. */\nexport function resolvePost(input: PostInput): CreatePostInput {\n return resolveInput(input, () => post(), validatePost);\n}\n\n/** Что принимает `posts.update`: объект, билдер поста или функция-настройщик. */\nexport type PostUpdateInput =\n | UpdatePostInput\n | PostBuilder\n | ((builder: PostBuilder) => PostBuilder | UpdatePostInput);\n\nfunction validatePostUpdate(input: CreatePostInput | UpdatePostInput): UpdatePostInput {\n if (!input || typeof input !== 'object') {\n throw new ItdConfigError('Для обновления поста нужен объект с явно заданным content');\n }\n\n const unsupported = ['wallRecipientId', 'attachmentIds', 'files', 'poll'].filter(\n (key) => (input as CreatePostInput)[key as keyof CreatePostInput] !== undefined,\n );\n if (unsupported.length > 0) {\n throw new ItdConfigError(\n `posts.update изменяет только content и spans; не поддерживаются: ${unsupported.join(', ')}`,\n );\n }\n\n if (input.content === undefined) {\n throw new ItdConfigError(\n 'posts.update требует явно заданный content; обновление одних spans могло бы стереть текст поста',\n );\n }\n if (typeof input.content !== 'string') {\n throw new ItdConfigError('content обновляемого поста должен быть строкой');\n }\n if (input.spans !== undefined && !Array.isArray(input.spans)) {\n throw new ItdConfigError('spans обновляемого поста должен быть массивом');\n }\n\n return {\n content: input.content,\n ...(input.spans !== undefined ? { spans: validateSpans(input.content, input.spans) } : {}),\n };\n}\n\n/** Приводит вход `posts.update` к поддерживаемым данным поста. */\nexport function resolvePostUpdate(input: PostUpdateInput): UpdatePostInput {\n if (typeof input === 'function') {\n const result = input(post());\n return isBuilder<CreatePostInput>(result)\n ? (result as PostBuilder)[BUILD_UPDATE]()\n : validatePostUpdate(result);\n }\n\n return isBuilder<CreatePostInput>(input)\n ? (input as PostBuilder)[BUILD_UPDATE]()\n : validatePostUpdate(input);\n}\n","import { type CommentInput, resolveComment } from '../builders/comment.js';\nimport {\n type PostInput,\n type PostUpdateInput,\n resolvePost,\n resolvePostUpdate,\n} from '../builders/post.js';\nimport type { FileInput } from '../core/attachments.js';\nimport type { HttpClient } from '../core/http.js';\nimport {\n type Page,\n PaginationMode,\n type Paginator,\n readCursorPage,\n readFlatCursorPage,\n} from '../core/pagination.js';\nimport { pickArray } from '../core/unwrap.js';\nimport { encodePathSegment } from '../core/url.js';\nimport type { CommentSort, FeedTab } from '../types/enums.js';\nimport type {\n Comment,\n LikeResult,\n PinPostResult,\n Poll,\n Post,\n PostStats,\n UserRef,\n} from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport type { CreateCommentInput, CreatePostInput } from '../types/params.js';\nimport { BaseResource } from './base.js';\n\n/** Курсорная позиция из параметров: если курсор задан — с него, иначе с начала. */\nfunction cursorStart(params: { cursor?: string | undefined }): { cursor?: string } {\n return params.cursor ? { cursor: params.cursor } : {};\n}\n\n/** Параметры запроса ленты. */\nexport interface FeedParams extends RequestOptions {\n /** Вкладка ленты. По умолчанию сервер отдаёт популярное. */\n tab?: FeedTab;\n /** Сколько постов на страницу. */\n limit?: number;\n /**\n * Курсор следующей страницы из предыдущего ответа.\n *\n * Передавайте значение как есть: его формат зависит от вкладки и может измениться.\n */\n cursor?: string;\n /** Ограничение числа страниц при переборе. */\n maxPages?: number;\n}\n\n/** Параметры запроса постов пользователя. */\nexport interface UserPostsParams extends RequestOptions {\n limit?: number;\n cursor?: string;\n /** Порядок сортировки. */\n sort?: string;\n /** Закреплённый пост, чтобы сервер поднял его наверх. */\n pinnedPostId?: string;\n maxPages?: number;\n}\n\n/** Параметры запроса комментариев к посту. */\nexport interface CommentsParams extends RequestOptions {\n limit?: number;\n /**\n * Курсор следующей страницы: идентификатор последнего полученного комментария.\n *\n * Передавайте значение из `nextCursor` предыдущего ответа как есть.\n */\n cursor?: string;\n sort?: CommentSort;\n maxPages?: number;\n}\n\n/**\n * Посты: лента, публикация, реакции, репосты, комментарии.\n *\n * Доступна как `itd.posts`.\n */\nexport class PostsResource extends BaseResource {\n readonly #uploadFiles: (files: FileInput[], options?: RequestOptions) => Promise<string[]>;\n\n /** Лента: `/api/posts`, курсорная пагинация. */\n readonly #feed = this.paginated<Post, FeedParams>({\n path: () => '/api/posts',\n query: (p) => ({ tab: p.tab, limit: p.limit }),\n start: cursorStart,\n read: (body) => readCursorPage<Post>(body, 'posts'),\n mode: PaginationMode.Cursor,\n });\n\n /** Стена пользователя: `/api/posts/user/{user}`. */\n readonly #wall = this.paginated<Post, UserPostsParams & { user: UserRef }>({\n path: (p) => `/api/posts/user/${encodePathSegment(p.user, 'user')}`,\n query: (p) => ({ limit: p.limit, sort: p.sort, pinnedPostId: p.pinnedPostId }),\n start: cursorStart,\n read: (body) => readCursorPage<Post>(body, 'posts'),\n mode: PaginationMode.Cursor,\n });\n\n /** Понравившиеся посты пользователя: `/api/posts/user/{user}/liked`. */\n readonly #liked = this.paginated<Post, UserPostsParams & { user: UserRef }>({\n path: (p) => `/api/posts/user/${encodePathSegment(p.user, 'user')}/liked`,\n query: (p) => ({ limit: p.limit }),\n start: cursorStart,\n read: (body) => readCursorPage<Post>(body, 'posts'),\n mode: PaginationMode.Cursor,\n });\n\n /** Комментарии к посту: курсор лежит рядом со списком, поэтому свой reader. */\n readonly #comments = this.paginated<Comment, CommentsParams & { postId: string }>({\n path: (p) => `/api/posts/${encodePathSegment(p.postId, 'postId')}/comments`,\n query: (p) => ({ limit: p.limit, sort: p.sort }),\n start: cursorStart,\n read: (body) => readFlatCursorPage<Comment>(body, 'comments'),\n mode: PaginationMode.Cursor,\n });\n\n constructor(\n http: HttpClient,\n deps: { uploadFiles: (files: FileInput[], options?: RequestOptions) => Promise<string[]> },\n ) {\n super(http);\n this.#uploadFiles = deps.uploadFiles;\n }\n\n /**\n * Загружает страницу ленты.\n *\n * @example\n * ```ts\n * const page = await itd.posts.list({ tab: FeedTab.Following, limit: 20 });\n * const next = await itd.posts.list({ tab: FeedTab.Following, cursor: page.nextCursor ?? undefined });\n * ```\n */\n list(params: FeedParams = {}): Promise<Page<Post>> {\n return this.#feed.list(params);\n }\n\n /**\n * Перебирает ленту, сама подставляя курсоры.\n *\n * @example\n * ```ts\n * for await (const post of itd.posts.iterate({ tab: 'following' })) {\n * console.log(post.author.username, post.content);\n * }\n * ```\n */\n iterate(params: FeedParams = {}): Paginator<Post> {\n return this.#feed.iterate(params);\n }\n\n /**\n * Публикует пост.\n *\n * Принимает обычный объект, {@link PostBuilder} или функцию-настройщик. Файлы из поля\n * `files` загружаются автоматически, порядок вложений сохраняется.\n *\n * @example\n * ```ts\n * await itd.posts.create({ content: 'привет' });\n * await itd.posts.create((p) => p.content('привет').attach({ url: 'https://example.com/photo.jpg' }));\n * ```\n */\n async create(input: PostInput, options: RequestOptions = {}): Promise<Post> {\n const data = resolvePost(input);\n const attachmentIds = await this.#collectAttachments(data, options);\n\n return this.http.request<Post>({\n method: 'POST',\n path: '/api/posts',\n body: {\n content: data.content ?? '',\n ...(data.spans ? { spans: data.spans } : {}),\n ...(data.wallRecipientId ? { wallRecipientId: data.wallRecipientId } : {}),\n ...(attachmentIds.length > 0 ? { attachmentIds } : {}),\n ...(data.poll ? { poll: data.poll } : {}),\n },\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Загружает один пост вместе с топовыми комментариями.\n *\n * В отличие от списков, здесь у поста заполнено поле `comments`.\n */\n get(postId: string, options: RequestOptions = {}): Promise<Post> {\n return this.http.request<Post>({\n method: 'GET',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}`,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Редактирует текст и разметку поста.\n *\n * Как и {@link create}, принимает объект, готовый {@link PostBuilder} или\n * функцию-настройщик. Поля создания поста, которые update endpoint не поддерживает\n * (вложения, опрос и стена), отвергаются до запроса.\n */\n update(postId: string, input: PostUpdateInput, options: RequestOptions = {}): Promise<Post> {\n const data = resolvePostUpdate(input);\n return this.http.request<Post>({\n method: 'PUT',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}`,\n body: { content: data.content, ...(data.spans ? { spans: data.spans } : {}) },\n ...this.requestOptions(options),\n });\n }\n\n /** Удаляет пост. Восстановить его можно через {@link restore}. */\n remove(postId: string, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}`,\n ...this.requestOptions(options),\n });\n }\n\n /** Восстанавливает удалённый пост. */\n restore(postId: string, options: RequestOptions = {}): Promise<Post> {\n return this.http.request<Post>({\n method: 'POST',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/restore`,\n ...this.requestOptions(options),\n });\n }\n\n /** Ставит реакцию на пост. */\n like(postId: string, options: RequestOptions = {}): Promise<LikeResult> {\n return this.http.request<LikeResult>({\n method: 'POST',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/like`,\n ...this.requestOptions(options),\n });\n }\n\n /** Убирает реакцию с поста. */\n unlike(postId: string, options: RequestOptions = {}): Promise<LikeResult> {\n return this.http.request<LikeResult>({\n method: 'DELETE',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/like`,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Делает репост с необязательным комментарием.\n *\n * Вложения к репосту не поддерживаются: сервер их игнорирует, поэтому параметров\n * для файлов здесь нет.\n */\n repost(postId: string, content = '', options: RequestOptions = {}): Promise<Post> {\n return this.http.request<Post>({\n method: 'POST',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/repost`,\n body: { content },\n ...this.requestOptions(options),\n });\n }\n\n /** Отменяет репост. */\n unrepost(postId: string, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/repost`,\n ...this.requestOptions(options),\n });\n }\n\n /** Закрепляет пост в профиле. */\n pin(postId: string, options: RequestOptions = {}): Promise<PinPostResult> {\n return this.http.request<PinPostResult>({\n method: 'POST',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/pin`,\n ...this.requestOptions(options),\n });\n }\n\n /** Открепляет пост. */\n unpin(postId: string, options: RequestOptions = {}): Promise<PinPostResult> {\n return this.http.request<PinPostResult>({\n method: 'DELETE',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/pin`,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Голосует в опросе.\n *\n * @param optionIds выбранные варианты; несколько допустимы только при `multipleChoice`\n */\n vote(postId: string, optionIds: string[], options: RequestOptions = {}): Promise<Poll> {\n return this.http.request<Poll>({\n method: 'POST',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/poll/vote`,\n body: { optionIds },\n ...this.requestOptions(options),\n });\n }\n\n /** Запрашивает счётчики сразу для нескольких постов. */\n async stats(ids: string[], options: RequestOptions = {}): Promise<PostStats[]> {\n const body = await this.http.request({\n method: 'POST',\n path: '/api/posts/stats',\n body: { ids },\n ...this.requestOptions(options),\n });\n\n return pickArray<PostStats>(body, 'posts');\n }\n\n /**\n * Загружает страницу стены пользователя.\n *\n * Это **не только его собственные посты**: сюда попадают и записи, которые другие\n * оставили на его стене — у них `author` чужой, а `wallRecipient` указывает на владельца\n * стены. Поэтому число записей обычно больше, чем `postsCount` из профиля; чтобы\n * получить только авторские посты, отфильтруйте по `post.author.id`.\n *\n * Принимает и UUID, и имя пользователя.\n */\n byUser(user: UserRef, params: UserPostsParams = {}): Promise<Page<Post>> {\n return this.#wall.list({ ...params, user });\n }\n\n /** Перебирает стену пользователя. Что именно в неё входит — см. {@link byUser}. */\n iterateByUser(user: UserRef, params: UserPostsParams = {}): Paginator<Post> {\n return this.#wall.iterate({ ...params, user });\n }\n\n /** Загружает страницу постов, которые пользователь отметил реакцией. */\n likedByUser(user: UserRef, params: UserPostsParams = {}): Promise<Page<Post>> {\n return this.#liked.list({ ...params, user });\n }\n\n /** Перебирает посты, которые пользователь отметил реакцией. */\n iterateLikedByUser(user: UserRef, params: UserPostsParams = {}): Paginator<Post> {\n return this.#liked.iterate({ ...params, user });\n }\n\n /**\n * Загружает страницу комментариев к посту.\n *\n * У этого эндпоинта курсор и признак продолжения лежат рядом со списком, а не внутри\n * объекта `pagination`, как у остальных, — разница скрыта внутри.\n */\n comments(postId: string, params: CommentsParams = {}): Promise<Page<Comment>> {\n return this.#comments.list({ ...params, postId });\n }\n\n /** Перебирает комментарии к посту. */\n iterateComments(postId: string, params: CommentsParams = {}): Paginator<Comment> {\n return this.#comments.iterate({ ...params, postId });\n }\n\n /**\n * Комментирует пост.\n *\n * @example\n * ```ts\n * await itd.posts.comment(postId, 'согласен');\n * await itd.posts.comment(postId, (c) => c.content('смотри').attach(blob));\n * ```\n */\n async comment(\n postId: string,\n input: CommentInput | string,\n options: RequestOptions = {},\n ): Promise<Comment> {\n const data = resolveComment(typeof input === 'string' ? { content: input } : input);\n const attachmentIds = await this.#collectAttachments(data, options);\n\n return this.http.request<Comment>({\n method: 'POST',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/comments`,\n body: { content: data.content ?? '', attachmentIds },\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Отправляет голосовой комментарий.\n *\n * Текста у такого комментария нет: сервер ждёт пустой `content` и одно аудиовложение\n * в формате `audio/ogg`.\n *\n * @example\n * ```ts\n * import { fromPath } from 'itd-api/node';\n *\n * await itd.posts.voiceComment(postId, fromPath('./answer.ogg'));\n * ```\n */\n voiceComment(postId: string, audio: FileInput, options: RequestOptions = {}): Promise<Comment> {\n return this.comment(postId, { content: '', files: [audio] }, options);\n }\n\n /** Загружает файлы из входных данных и объединяет их с уже готовыми идентификаторами. */\n async #collectAttachments(\n data: CreatePostInput | CreateCommentInput,\n options: RequestOptions,\n ): Promise<string[]> {\n const existing = data.attachmentIds ?? [];\n const files = data.files ?? [];\n\n if (files.length === 0) return existing;\n\n const uploaded = await this.#uploadFiles(files, options);\n return [...existing, ...uploaded];\n }\n}\n","import { ItdConfigError } from '../core/errors.js';\nimport { ReportReason, ReportTargetType } from '../types/enums.js';\nimport type { CreateReportInput } from '../types/params.js';\nimport { BUILDER, type BuilderInput, type ItdBuilder, resolveInput } from './base.js';\n\nconst REASONS = new Set<string>(Object.values(ReportReason));\nconst TARGET_TYPES = new Set<string>(Object.values(ReportTargetType));\n\n/**\n * Проверяет данные жалобы.\n *\n * @throws {ItdConfigError} если объект жалобы или причина заданы неверно\n */\nexport function validateReport(input: CreateReportInput): CreateReportInput {\n if (!input?.targetId || typeof input.targetId !== 'string') {\n throw new ItdConfigError('Жалоба требует идентификатор объекта (targetId)');\n }\n\n if (!TARGET_TYPES.has(input.targetType)) {\n throw new ItdConfigError(\n `targetType должен быть одним из ${[...TARGET_TYPES].join(', ')}, ` +\n `получено: ${String(input.targetType)}`,\n );\n }\n\n if (!REASONS.has(input.reason)) {\n throw new ItdConfigError(\n `Неизвестная причина жалобы «${String(input.reason)}». Допустимые: ${[...REASONS].join(', ')}`,\n );\n }\n\n return input;\n}\n\n/**\n * Билдер жалобы.\n *\n * Точка входа задаёт объект жалобы и его тип одновременно, поэтому рассогласовать\n * `targetType` и `targetId` невозможно. Создаётся объектом {@link report}.\n */\nexport class ReportBuilder implements ItdBuilder<CreateReportInput> {\n /** @internal */\n readonly [BUILDER] = true as const;\n\n readonly #state: Partial<CreateReportInput>;\n\n /** @internal Создавайте билдер через {@link report}. */\n constructor(state: Partial<CreateReportInput>) {\n this.#state = state;\n }\n\n /** Указывает причину жалобы. */\n reason(reason: ReportReason): ReportBuilder {\n return new ReportBuilder({ ...this.#state, reason });\n }\n\n /** Добавляет пояснение в свободной форме. */\n description(text: string): ReportBuilder {\n return new ReportBuilder({ ...this.#state, description: text });\n }\n\n build(): CreateReportInput {\n return validateReport(this.#state as CreateReportInput);\n }\n\n toJSON(): CreateReportInput {\n return this.build();\n }\n}\n\nfunction start(targetType: ReportTargetType, targetId: string): ReportBuilder {\n return new ReportBuilder({ targetType, targetId });\n}\n\n/**\n * Начинает сборку жалобы.\n *\n * Тип объекта выбирается точкой входа, так что указать идентификатор комментария\n * с типом «пост» нельзя в принципе.\n *\n * @example\n * ```ts\n * import { report, ReportReason } from 'itd-api';\n *\n * await itd.reports.create(report.post(postId).reason(ReportReason.Spam));\n * await itd.reports.create(report.user(userId).reason('fraud').description('пишет в личку'));\n * ```\n */\nexport const report = Object.freeze({\n /** Жалоба на пост. */\n post: (postId: string) => start(ReportTargetType.Post, postId),\n /** Жалоба на комментарий. */\n comment: (commentId: string) => start(ReportTargetType.Comment, commentId),\n /** Жалоба на пользователя. */\n user: (userId: string) => start(ReportTargetType.User, userId),\n});\n\n/** Что принимает параметр жалобы: объект, билдер или функция-настройщик. */\nexport type ReportInput = BuilderInput<CreateReportInput, ReportBuilder>;\n\n/** Приводит любую форму входа к готовым данным жалобы. */\nexport function resolveReport(input: ReportInput): CreateReportInput {\n return resolveInput(input, () => new ReportBuilder({}), validateReport);\n}\n","import { type ReportInput, resolveReport } from '../builders/report.js';\nimport type { Report } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/**\n * Жалобы на контент и пользователей.\n *\n * Доступна как `itd.reports`.\n */\nexport class ReportsResource extends BaseResource {\n /**\n * Отправляет жалобу.\n *\n * Повторная жалоба на тот же объект отклоняется сервером с сообщением\n * «Вы уже отправляли жалобу на этот контент».\n *\n * @example\n * ```ts\n * await itd.reports.create(report.post(postId).reason('spam'));\n * await itd.reports.create({ targetType: 'user', targetId, reason: 'fraud' });\n * ```\n */\n create(input: ReportInput, options: RequestOptions = {}): Promise<Report> {\n const data = resolveReport(input);\n\n return this.http.request<Report>({\n method: 'POST',\n path: '/api/reports',\n body: data,\n ...this.requestOptions(options),\n });\n }\n}\n","import { pickArray } from '../core/unwrap.js';\nimport type { Hashtag, UserSummary } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/** Результат глобального поиска. */\nexport interface SearchResult {\n users: UserSummary[];\n hashtags: Hashtag[];\n}\n\n/**\n * Глобальный поиск.\n *\n * Доступна как `itd.search`.\n */\nexport class SearchResource extends BaseResource {\n /**\n * Ищет пользователей и хэштеги одним запросом.\n *\n * @example\n * ```ts\n * const { users, hashtags } = await itd.search.all('арт');\n * ```\n */\n async all(query: string, options: RequestOptions = {}): Promise<SearchResult> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/search',\n query: { q: query },\n ...this.requestOptions(options),\n });\n\n return {\n users: pickArray<UserSummary>(body, 'users'),\n hashtags: pickArray<Hashtag>(body, 'hashtags'),\n };\n }\n}\n","import { encodePathSegment } from '../core/url.js';\nimport type { PaymentMethod, Subscription } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/**\n * Подписка и способы оплаты.\n *\n * Доступна как `itd.subscription`.\n */\nexport class SubscriptionResource extends BaseResource {\n /** Загружает состояние подписки и её цену. */\n status(options: RequestOptions = {}): Promise<Subscription> {\n return this.http.request<Subscription>({\n method: 'GET',\n // Завершающий слэш обязателен.\n path: '/api/v1/subscription/',\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Запускает оплату подписки.\n *\n * Форма ответа в документации API не описана, поэтому тип результата не уточняется.\n */\n pay(options: RequestOptions = {}): Promise<unknown> {\n return this.http.request({\n method: 'POST',\n path: '/api/v1/subscription/pay',\n ...this.requestOptions(options),\n });\n }\n\n /** Включает или отключает автопродление. */\n setAutoRenewal(enabled: boolean, options: RequestOptions = {}): Promise<unknown> {\n return this.http.request({\n method: 'POST',\n path: '/api/v1/subscription/auto-renewal',\n body: { enabled },\n ...this.requestOptions(options),\n });\n }\n\n /** Запускает привязку карты. */\n bindCard(options: RequestOptions = {}): Promise<unknown> {\n return this.http.request({\n method: 'POST',\n path: '/api/v1/subscription/bind-card',\n ...this.requestOptions(options),\n });\n }\n\n /** Загружает список способов оплаты. Пустой массив, если карт нет. */\n async methods(options: RequestOptions = {}): Promise<PaymentMethod[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/v1/subscription/methods',\n ...this.requestOptions(options),\n });\n\n return Array.isArray(body) ? (body as PaymentMethod[]) : [];\n }\n\n /** Делает способ оплаты основным. */\n setDefaultMethod(methodId: string, options: RequestOptions = {}): Promise<unknown> {\n return this.http.request({\n method: 'POST',\n path: `/api/v1/subscription/methods/${encodePathSegment(methodId, 'methodId')}/default`,\n ...this.requestOptions(options),\n });\n }\n\n /** Удаляет способ оплаты. */\n removeMethod(methodId: string, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `/api/v1/subscription/methods/${encodePathSegment(methodId, 'methodId')}`,\n ...this.requestOptions(options),\n });\n }\n}\n","import { ItdConfigError } from '../core/errors.js';\nimport { createDeviceId } from '../core/runtime.js';\nimport { InteractionType, type ViewReason, type ViewSource } from '../types/enums.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/** Общие опции запроса телеметрии. */\nexport interface TelemetryOptions extends RequestOptions {\n /** Переопределяет идентификатор сессии телеметрии (`sid`) для этого запроса. */\n sid?: string;\n}\n\n/** Часы, используемые tracker-ом для измерения времени просмотра. */\nexport interface TelemetryClock {\n /** Возвращает текущее время в миллисекундах. */\n now(): number;\n}\n\n/** Опции tracker-а просмотра. */\nexport interface ViewTrackerOptions extends TelemetryOptions {\n /** Часы для измерения времени. По умолчанию используется `Date.now()`. */\n clock?: TelemetryClock;\n}\n\n/** Опции накопителя телеметрии. */\nexport interface TelemetryBatchOptions extends TelemetryOptions {\n /**\n * Максимальное число событий в одном запросе.\n *\n * Значение по умолчанию — 50. Это размер клиентской пачки, а не заявленный лимит API.\n */\n maxBatchSize?: number;\n /** Часы для tracker-ов, созданных через накопитель. */\n clock?: TelemetryClock;\n}\n\n/** Событие просмотра поста для {@link TelemetryResource.dwell}. */\nexport interface DwellEntry {\n /** Метка показа — поле `vs` объекта поста. */\n vs: string;\n /** Время появления поста в зоне видимости, epoch-мс. */\n enterAt: number;\n /** Время ухода из зоны видимости, epoch-мс. */\n exitAt: number;\n /** Причина завершения просмотра. */\n reason: ViewReason;\n /** Длительность просмотра в мс. По умолчанию `exitAt - enterAt`. */\n durationMs?: number;\n /** Контекст источника показа. */\n sourceContext?: string;\n /** Источник показа. */\n source?: ViewSource;\n /** Пост уже встречался в этой сессии. */\n repeat?: boolean;\n}\n\n/** Событие взаимодействия с контентом для {@link TelemetryResource.interaction}. */\nexport interface InteractionEntry {\n /** Тип взаимодействия. */\n type: InteractionType;\n /** Метка показа — поле `vs` объекта поста. */\n vs: string;\n /** Идентификатор поста. */\n postId: string;\n /** Индекс вложения, начиная с нуля. */\n mediaIndex?: number;\n /** Источник показа. */\n source?: ViewSource;\n /** Просмотренная позиция видео в мс. */\n positionMs?: number;\n /** Длительность видео в мс. */\n durationMs?: number;\n}\n\n/** Данные для {@link TelemetryResource.startView}. */\nexport interface ViewTrackerInput {\n /** Метка показа — поле `vs` объекта поста. */\n vs: string;\n /** Контекст источника показа. */\n sourceContext?: string;\n /** Источник показа. */\n source?: ViewSource;\n /** Пост уже встречался в этой сессии. */\n repeat?: boolean;\n}\n\n/** Данные открытия фотографии. */\nexport interface PhotoOpenInput {\n /** Метка показа — поле `vs` объекта поста. */\n vs: string;\n /** Идентификатор поста. */\n postId: string;\n /** Индекс открытого вложения, начиная с нуля. */\n mediaIndex: number;\n /** Источник показа. */\n source?: ViewSource;\n}\n\n/** Данные о прогрессе просмотра видео. */\nexport interface VideoProgressInput {\n /** Метка показа — поле `vs` объекта поста. */\n vs: string;\n /** Идентификатор поста. */\n postId: string;\n /** Просмотренная позиция видео в мс. */\n positionMs: number;\n /** Длительность видео в мс. */\n durationMs: number;\n /** Источник показа. */\n source?: ViewSource;\n}\n\n/** Активное измерение времени просмотра. */\nexport interface ViewTracker {\n /** Время начала измерения в миллисекундах. */\n readonly enteredAt: number;\n /** Был ли уже вызван {@link finish}. */\n readonly finished: boolean;\n /**\n * Завершает измерение и передаёт событие.\n *\n * Повторный вызов возвращает тот же Promise и не создаёт второе событие.\n */\n finish(reason: ViewReason): Promise<void>;\n}\n\n/** Явно управляемый накопитель событий телеметрии. */\nexport interface TelemetryBatch {\n /** Число ожидающих событий просмотра. */\n readonly pendingDwell: number;\n /** Число ожидающих событий взаимодействия. */\n readonly pendingInteractions: number;\n /** Закрыт ли накопитель. */\n readonly closed: boolean;\n /** Добавляет одно или несколько событий просмотра. */\n dwell(entry: DwellEntry | readonly DwellEntry[]): this;\n /** Добавляет одно или несколько событий взаимодействия. */\n interaction(entry: InteractionEntry | readonly InteractionEntry[]): this;\n /** Добавляет событие открытия фотографии. */\n photoOpen(input: PhotoOpenInput): this;\n /** Добавляет событие прогресса просмотра видео. */\n videoProgress(input: VideoProgressInput): this;\n /** Начинает измерение просмотра, которое после `finish()` попадёт в этот накопитель. */\n startView(input: ViewTrackerInput): ViewTracker;\n /**\n * Отправляет накопленные события.\n *\n * Опции позволяют заменить, например, отменённый `signal` при повторной попытке.\n */\n flush(options?: TelemetryOptions): Promise<void>;\n /** Отправляет накопленные события и закрывает накопитель. */\n close(): Promise<void>;\n}\n\nconst DEFAULT_BATCH_SIZE = 50;\nconst SYSTEM_CLOCK: TelemetryClock = Object.freeze({ now: () => Date.now() });\n\nfunction assertNonEmpty(value: string, name: string): void {\n if (value.trim().length === 0) throw new ItdConfigError(`${name} должен быть непустой строкой`);\n}\n\nfunction assertFinite(value: number, name: string): void {\n if (!Number.isFinite(value)) throw new ItdConfigError(`${name} должен быть конечным числом`);\n}\n\nfunction assertNonNegative(value: number, name: string): void {\n assertFinite(value, name);\n if (value < 0) throw new ItdConfigError(`${name} должен быть неотрицательным числом`);\n}\n\nfunction validateDwell(entry: DwellEntry): DwellEntry {\n assertNonEmpty(entry.vs, 'vs');\n assertFinite(entry.enterAt, 'enterAt');\n assertFinite(entry.exitAt, 'exitAt');\n if (entry.exitAt < entry.enterAt) {\n throw new ItdConfigError('exitAt не может быть раньше enterAt');\n }\n if (entry.durationMs !== undefined) assertNonNegative(entry.durationMs, 'durationMs');\n return { ...entry };\n}\n\nfunction validateInteraction(entry: InteractionEntry): InteractionEntry {\n assertNonEmpty(entry.vs, 'vs');\n assertNonEmpty(entry.postId, 'postId');\n if (entry.mediaIndex !== undefined) {\n assertNonNegative(entry.mediaIndex, 'mediaIndex');\n if (!Number.isInteger(entry.mediaIndex)) {\n throw new ItdConfigError('mediaIndex должен быть целым числом');\n }\n }\n if (entry.positionMs !== undefined) assertNonNegative(entry.positionMs, 'positionMs');\n if (entry.durationMs !== undefined) assertNonNegative(entry.durationMs, 'durationMs');\n return { ...entry };\n}\n\nfunction photoOpenEntry(input: PhotoOpenInput): InteractionEntry {\n return validateInteraction({ type: InteractionType.PhotoOpen, ...input });\n}\n\nfunction videoProgressEntry(input: VideoProgressInput): InteractionEntry {\n return validateInteraction({ type: InteractionType.VideoProgress, ...input });\n}\n\nclass ViewTrackerImpl implements ViewTracker {\n readonly enteredAt: number;\n readonly #clock: TelemetryClock;\n readonly #input: ViewTrackerInput;\n readonly #submit: (entry: DwellEntry) => Promise<void> | void;\n #result: Promise<void> | undefined;\n\n constructor(\n input: ViewTrackerInput,\n clock: TelemetryClock,\n submit: (entry: DwellEntry) => Promise<void> | void,\n ) {\n assertNonEmpty(input.vs, 'vs');\n this.enteredAt = clock.now();\n assertFinite(this.enteredAt, 'clock.now()');\n this.#input = { ...input };\n this.#clock = clock;\n this.#submit = submit;\n }\n\n get finished(): boolean {\n return this.#result !== undefined;\n }\n\n finish(reason: ViewReason): Promise<void> {\n if (this.#result) return this.#result;\n\n try {\n const exitAt = this.#clock.now();\n assertFinite(exitAt, 'clock.now()');\n if (exitAt < this.enteredAt) {\n throw new ItdConfigError('часы не могут вернуться назад во время измерения просмотра');\n }\n\n this.#result = Promise.resolve(\n this.#submit({\n ...this.#input,\n enterAt: this.enteredAt,\n exitAt,\n reason,\n }),\n );\n } catch (error) {\n this.#result = Promise.reject(error);\n }\n\n return this.#result;\n }\n}\n\nclass TelemetryBatchImpl implements TelemetryBatch {\n readonly #resource: TelemetryResource;\n readonly #options: TelemetryOptions;\n readonly #clock: TelemetryClock;\n readonly #maxBatchSize: number;\n readonly #onClose: () => void;\n readonly #dwell: DwellEntry[] = [];\n readonly #interactions: InteractionEntry[] = [];\n #state: 'open' | 'closing' | 'closed' = 'open';\n #flushPromise: Promise<void> | undefined;\n #closePromise: Promise<void> | undefined;\n\n constructor(resource: TelemetryResource, options: TelemetryBatchOptions, onClose: () => void) {\n const { maxBatchSize = DEFAULT_BATCH_SIZE, clock = SYSTEM_CLOCK, ...requestOptions } = options;\n if (!Number.isInteger(maxBatchSize) || maxBatchSize < 1) {\n throw new ItdConfigError('maxBatchSize должен быть целым числом от 1');\n }\n this.#resource = resource;\n this.#options = requestOptions;\n this.#clock = clock;\n this.#maxBatchSize = maxBatchSize;\n this.#onClose = onClose;\n }\n\n get pendingDwell(): number {\n return this.#dwell.length;\n }\n\n get pendingInteractions(): number {\n return this.#interactions.length;\n }\n\n get closed(): boolean {\n return this.#state === 'closed';\n }\n\n dwell(entry: DwellEntry | readonly DwellEntry[]): this {\n this.#assertOpen();\n const entries = Array.isArray(entry) ? entry : [entry];\n this.#dwell.push(...entries.map(validateDwell));\n return this;\n }\n\n interaction(entry: InteractionEntry | readonly InteractionEntry[]): this {\n this.#assertOpen();\n const entries = Array.isArray(entry) ? entry : [entry];\n this.#interactions.push(...entries.map(validateInteraction));\n return this;\n }\n\n photoOpen(input: PhotoOpenInput): this {\n return this.interaction(photoOpenEntry(input));\n }\n\n videoProgress(input: VideoProgressInput): this {\n return this.interaction(videoProgressEntry(input));\n }\n\n startView(input: ViewTrackerInput): ViewTracker {\n this.#assertOpen();\n return new ViewTrackerImpl(input, this.#clock, (entry) => {\n this.dwell(entry);\n });\n }\n\n flush(options: TelemetryOptions = {}): Promise<void> {\n if (this.#state === 'closed') return Promise.resolve();\n if (this.#flushPromise) return this.#flushPromise;\n\n const running = this.#flushAll({ ...this.#options, ...options });\n const tracked = running.finally(() => {\n if (this.#flushPromise === tracked) this.#flushPromise = undefined;\n });\n this.#flushPromise = tracked;\n return tracked;\n }\n\n close(): Promise<void> {\n if (this.#state === 'closed') return Promise.resolve();\n if (this.#closePromise) return this.#closePromise;\n\n this.#state = 'closing';\n const running = this.flush()\n .then(() => {\n this.#state = 'closed';\n this.#onClose();\n })\n .catch((error: unknown) => {\n this.#state = 'open';\n throw error;\n });\n const tracked = running.finally(() => {\n if (this.#closePromise === tracked) this.#closePromise = undefined;\n });\n this.#closePromise = tracked;\n return tracked;\n }\n\n async #flushAll(options: TelemetryOptions): Promise<void> {\n while (this.#dwell.length > 0) {\n const chunk = this.#dwell.splice(0, this.#maxBatchSize);\n try {\n await this.#resource.dwell(chunk, options);\n } catch (error) {\n this.#dwell.unshift(...chunk);\n throw error;\n }\n }\n\n while (this.#interactions.length > 0) {\n const chunk = this.#interactions.splice(0, this.#maxBatchSize);\n try {\n await this.#resource.interaction(chunk, options);\n } catch (error) {\n this.#interactions.unshift(...chunk);\n throw error;\n }\n }\n }\n\n #assertOpen(): void {\n if (this.#state !== 'open') {\n throw new ItdConfigError('накопитель телеметрии уже закрывается или закрыт');\n }\n }\n}\n\n/**\n * Телеметрия просмотров и взаимодействий.\n *\n * Ничего не отправляет автоматически: каждый запрос, tracker или накопитель создаётся\n * явным вызовом пользователя. Доступна как `itd.telemetry`.\n */\nexport class TelemetryResource extends BaseResource {\n #sessionId: string | undefined;\n readonly #batches = new Set<TelemetryBatchImpl>();\n\n /** Идентификатор сессии телеметрии, общий для всех событий этого ресурса. */\n get sessionId(): string {\n this.#sessionId ??= createDeviceId();\n return this.#sessionId;\n }\n\n /** Отправляет события просмотра постов (`POST /api/v1/i`). */\n dwell(entries: readonly DwellEntry[], options: TelemetryOptions = {}): Promise<unknown> {\n const validated = entries.map(validateDwell);\n return this.http.request({\n method: 'POST',\n path: '/api/v1/i',\n body: {\n sid: options.sid ?? this.sessionId,\n e: validated.map((entry) => ({\n md: entry.durationMs ?? entry.exitAt - entry.enterAt,\n et: entry.enterAt,\n xt: entry.exitAt,\n r: entry.reason,\n v: entry.vs,\n ...(entry.sourceContext !== undefined ? { sc: entry.sourceContext } : {}),\n ...(entry.source !== undefined ? { s: entry.source } : {}),\n ...(entry.repeat ? { b: 1 } : {}),\n })),\n },\n ...this.requestOptions(options),\n });\n }\n\n /** Отправляет события взаимодействия с контентом (`POST /api/v1/x`). */\n interaction(\n entries: readonly InteractionEntry[],\n options: TelemetryOptions = {},\n ): Promise<unknown> {\n const validated = entries.map(validateInteraction);\n return this.http.request({\n method: 'POST',\n path: '/api/v1/x',\n body: {\n sid: options.sid ?? this.sessionId,\n e: validated.map((entry) => ({\n t: entry.type,\n v: entry.vs,\n ai: entry.postId,\n ...(entry.mediaIndex !== undefined ? { mi: entry.mediaIndex } : {}),\n ...(entry.source !== undefined ? { s: entry.source } : {}),\n ...(entry.positionMs !== undefined ? { pm: Math.round(entry.positionMs) } : {}),\n ...(entry.durationMs !== undefined ? { dm: Math.round(entry.durationMs) } : {}),\n })),\n },\n ...this.requestOptions(options),\n });\n }\n\n /** Начинает измерять время просмотра и отправляет результат после `finish()`. */\n startView(input: ViewTrackerInput, options: ViewTrackerOptions = {}): ViewTracker {\n const { clock = SYSTEM_CLOCK, ...requestOptions } = options;\n return new ViewTrackerImpl(input, clock, async (entry) => {\n await this.dwell([entry], requestOptions);\n });\n }\n\n /** Отправляет событие открытия фотографии. */\n photoOpen(input: PhotoOpenInput, options: TelemetryOptions = {}): Promise<unknown> {\n return this.interaction([photoOpenEntry(input)], options);\n }\n\n /** Отправляет событие прогресса просмотра видео. */\n videoProgress(input: VideoProgressInput, options: TelemetryOptions = {}): Promise<unknown> {\n return this.interaction([videoProgressEntry(input)], options);\n }\n\n /**\n * Создаёт накопитель с явными `flush()` и `close()`.\n *\n * Создание и добавление записей не выполняют сетевых запросов.\n */\n batch(options: TelemetryBatchOptions = {}): TelemetryBatch {\n let batch: TelemetryBatchImpl;\n batch = new TelemetryBatchImpl(this, options, () => {\n this.#batches.delete(batch);\n });\n this.#batches.add(batch);\n return batch;\n }\n\n /** Закрывает все созданные накопители, отправляя оставшиеся записи. */\n async close(): Promise<void> {\n const results = await Promise.allSettled([...this.#batches].map((batch) => batch.close()));\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, 'Не удалось отправить накопленную телеметрию');\n }\n }\n}\n","import type { FileInput } from '../core/attachments.js';\nimport type { HttpClient } from '../core/http.js';\nimport { type Page, PaginationMode, type Paginator, readPagedPage } from '../core/pagination.js';\nimport { pickArray, pickBoolean, pickString } from '../core/unwrap.js';\nimport { encodePathSegment } from '../core/url.js';\nimport type {\n Clan,\n FollowResult,\n MyProfile,\n PinsResult,\n PrivacySettings,\n PublicProfile,\n UserId,\n UserRef,\n UserSummary,\n} from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\nimport type { UploadedFile, UploadOptions } from './files.js';\n\n/**\n * Параметры списков пользователей.\n *\n * ⚠️ Списки подписчиков, подписок и заблокированных на сервере **не листаются**:\n * `page` он игнорирует, а `limit` зажимает на 20. Подробности — в {@link UsersResource.followers}.\n */\nexport interface UserListParams extends RequestOptions {\n /** Сколько записей вернуть. Значения больше 20 сервер молча уменьшает до 20. */\n limit?: number;\n /** Номер страницы. Сервер его игнорирует — оставлен на случай, если пагинацию починят. */\n page?: number;\n maxPages?: number;\n}\n\n/** Изменяемые поля своего профиля. */\nexport interface UpdateProfileInput {\n displayName?: string;\n username?: string;\n /** Эмодзи-аватар: символ клана, а не адрес картинки. */\n avatar?: string;\n bio?: string;\n /** Идентификатор загруженного файла баннера. `null` удаляет текущий баннер. */\n bannerId?: string | null;\n}\n\n/** Изменяемые настройки приватности. */\nexport type UpdatePrivacyInput = Partial<PrivacySettings>;\n\n/**\n * Пользователи: профили, подписки, блокировки, приватность.\n *\n * Доступна как `itd.users`.\n */\nexport class UsersResource extends BaseResource {\n readonly #uploadFile: (file: FileInput, options?: UploadOptions) => Promise<UploadedFile>;\n\n constructor(\n http: HttpClient,\n deps: {\n uploadFile: (file: FileInput, options?: UploadOptions) => Promise<UploadedFile>;\n },\n ) {\n super(http);\n this.#uploadFile = deps.uploadFile;\n }\n\n /**\n * Списки пользователей: подписчики, подписки, заблокированные.\n *\n * Путь приходит в параметрах — так один описатель обслуживает все три эндпоинта. Имена\n * полей перечислены с запасом: списки приходят под `users`, но альтернативное имя ничего\n * не стоит и спасает, если эндпоинт назовёт список по-своему. `page` уходит в запрос, хотя\n * сервер его сейчас не читает (см. {@link followers}): когда починят — заработает само.\n */\n readonly #userList = this.paginated<UserSummary, UserListParams & { path: string }>({\n path: (p) => p.path,\n query: (p) => ({ limit: p.limit }),\n start: (p) => (p.page !== undefined ? { page: p.page } : {}),\n read: (body) => readPagedPage<UserSummary>(body, 'users', 'followers', 'following', 'blocked'),\n mode: PaginationMode.Page,\n });\n\n /** Загружает свой профиль — с подпиской и признаком подтверждённого телефона. */\n me(options: RequestOptions = {}): Promise<MyProfile> {\n return this.http.request<MyProfile>({\n method: 'GET',\n path: '/api/users/me',\n ...this.requestOptions(options),\n });\n }\n\n /** Обновляет свой профиль. Передавайте только изменяемые поля. */\n updateMe(input: UpdateProfileInput, options: RequestOptions = {}): Promise<MyProfile> {\n return this.http.request<MyProfile>({\n method: 'PUT',\n path: '/api/users/me',\n body: input,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Загружает изображение и устанавливает его баннером профиля.\n *\n * Для установки используется идентификатор, полученный от `/api/files/upload`.\n * Если файл уже загружен, используйте {@link updateMe}: `{ bannerId: file.id }`.\n *\n * @example\n * ```ts\n * await itd.users.setBanner(file, { filename: 'banner.webp' });\n * ```\n */\n async setBanner(file: FileInput, options: UploadOptions = {}): Promise<MyProfile> {\n const uploaded = await this.#uploadFile(file, options);\n return this.updateMe({ bannerId: uploaded.id }, options);\n }\n\n /** Удаляет баннер профиля, устанавливая `bannerId` в `null`. */\n removeBanner(options: RequestOptions = {}): Promise<MyProfile> {\n return this.updateMe({ bannerId: null }, options);\n }\n\n /** Деактивирует аккаунт. Вернуть его можно через {@link restore}. */\n deactivate(options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: '/api/users/me',\n ...this.requestOptions(options),\n });\n }\n\n /** Восстанавливает деактивированный аккаунт. */\n restore(options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'POST',\n path: '/api/users/me/restore',\n ...this.requestOptions(options),\n });\n }\n\n /** Создаёт профиль после регистрации. */\n createProfile(\n input: { username: string; displayName: string; avatar?: string },\n options: RequestOptions = {},\n ): Promise<MyProfile> {\n return this.http.request<MyProfile>({\n method: 'POST',\n path: '/api/users/profile',\n body: input,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Загружает профиль пользователя.\n *\n * @param user UUID **или** имя пользователя — подходит и то, и другое\n *\n * @example\n * ```ts\n * const profile = await itd.users.get('nowkie');\n * await itd.posts.create({ content: 'привет', wallRecipientId: profile.id });\n * ```\n */\n get(user: UserRef, options: RequestOptions = {}): Promise<PublicProfile> {\n return this.http.request<PublicProfile>({\n method: 'GET',\n path: `/api/users/${encodePathSegment(user, 'user')}`,\n ...this.requestOptions(options),\n });\n }\n\n /** Проверяет, свободно ли имя пользователя. */\n async checkUsername(username: string, options: RequestOptions = {}): Promise<boolean> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/users/check-username',\n query: { username },\n ...this.requestOptions(options),\n });\n\n return pickBoolean(body, 'available');\n }\n\n /** Ищет пользователей по строке запроса. */\n async search(\n query: string,\n params: { limit?: number } & RequestOptions = {},\n ): Promise<UserSummary[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/users/search',\n query: { q: query, limit: params.limit },\n ...this.requestOptions(params),\n });\n\n return pickArray<UserSummary>(body, 'users');\n }\n\n /** Загружает рекомендации, на кого подписаться. */\n async whoToFollow(options: RequestOptions = {}): Promise<UserSummary[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/users/suggestions/who-to-follow',\n ...this.requestOptions(options),\n });\n\n return pickArray<UserSummary>(body, 'users');\n }\n\n /** Загружает рейтинг кланов. */\n async topClans(options: RequestOptions = {}): Promise<Clan[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/users/stats/top-clans',\n ...this.requestOptions(options),\n });\n\n return pickArray<Clan>(body, 'clans');\n }\n\n /**\n * Подписывается на пользователя.\n *\n * У закрытого профиля вместо подписки отправляется заявка — это видно по полю `status`.\n */\n follow(user: UserRef, options: RequestOptions = {}): Promise<FollowResult> {\n return this.http.request<FollowResult>({\n method: 'POST',\n path: `/api/users/${encodePathSegment(user, 'user')}/follow`,\n body: {},\n ...this.requestOptions(options),\n });\n }\n\n /** Отписывается от пользователя. */\n unfollow(user: UserRef, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `/api/users/${encodePathSegment(user, 'user')}/follow`,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Загружает подписчиков пользователя.\n *\n * ⚠️ **Сервер этот список не листает.** Возвращаются первые 20 записей и только они:\n * параметр `page` игнорируется (любая страница отдаёт те же записи и `pagination.page: 1`),\n * `limit` больше 20 молча уменьшается, а `hasMore` всегда `false`. Последнее честно —\n * получить продолжение нечем.\n *\n * Числу `total` доверять тоже не стоит: оно расходится с `followersCount` из профиля —\n * на проверенных аккаунтах занижено примерно на 1–4%.\n */\n followers(user: UserRef, params: UserListParams = {}): Promise<Page<UserSummary>> {\n return this.#userPage(`/api/users/${encodePathSegment(user, 'user')}/followers`, params);\n }\n\n /**\n * Перебирает подписчиков.\n *\n * ⚠️ Перебор закончится после первых 20 записей: сервер список не листает —\n * см. {@link followers}. Метод оставлен на случай, если пагинацию починят.\n */\n iterateFollowers(user: UserRef, params: UserListParams = {}): Paginator<UserSummary> {\n return this.#userPaginator(`/api/users/${encodePathSegment(user, 'user')}/followers`, params);\n }\n\n /** Загружает подписки пользователя. Ограничения те же, что у {@link followers}. */\n following(user: UserRef, params: UserListParams = {}): Promise<Page<UserSummary>> {\n return this.#userPage(`/api/users/${encodePathSegment(user, 'user')}/following`, params);\n }\n\n /** Перебирает подписки. Закончится после первых 20 записей — см. {@link followers}. */\n iterateFollowing(user: UserRef, params: UserListParams = {}): Paginator<UserSummary> {\n return this.#userPaginator(`/api/users/${encodePathSegment(user, 'user')}/following`, params);\n }\n\n /**\n * Проверяет, подписаны ли вы, сразу для нескольких пользователей.\n *\n * @returns объект «идентификатор пользователя → подписаны ли вы»\n *\n * @example\n * ```ts\n * const statuses = await itd.users.followStatus([userA, userB]);\n * // { 'b89dee4f-…': true, '35ea3059-…': false }\n * ```\n */\n followStatus(userIds: UserId[], options: RequestOptions = {}): Promise<Record<string, boolean>> {\n return this.http.request<Record<string, boolean>>({\n method: 'POST',\n path: '/api/users/follow-status',\n body: { userIds },\n ...this.requestOptions(options),\n });\n }\n\n /** Блокирует пользователя. */\n block(user: UserRef, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'POST',\n path: `/api/users/${encodePathSegment(user, 'user')}/block`,\n body: {},\n ...this.requestOptions(options),\n });\n }\n\n /** Снимает блокировку. */\n unblock(user: UserRef, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `/api/users/${encodePathSegment(user, 'user')}/block`,\n ...this.requestOptions(options),\n });\n }\n\n /** Загружает заблокированных пользователей. Ограничения те же, что у {@link followers}. */\n blocked(params: UserListParams = {}): Promise<Page<UserSummary>> {\n return this.#userPage('/api/users/me/blocked', params);\n }\n\n /** Перебирает заблокированных. Закончится после первых 20 записей — см. {@link followers}. */\n iterateBlocked(params: UserListParams = {}): Paginator<UserSummary> {\n return this.#userPaginator('/api/users/me/blocked', params);\n }\n\n /** Загружает настройки приватности. */\n getPrivacy(options: RequestOptions = {}): Promise<PrivacySettings> {\n return this.http.request<PrivacySettings>({\n method: 'GET',\n path: '/api/users/me/privacy',\n ...this.requestOptions(options),\n });\n }\n\n /** Обновляет настройки приватности. Передавайте только изменяемые поля. */\n updatePrivacy(input: UpdatePrivacyInput, options: RequestOptions = {}): Promise<PrivacySettings> {\n return this.http.request<PrivacySettings>({\n method: 'PUT',\n path: '/api/users/me/privacy',\n body: input,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Загружает значки профиля и выбранный из них.\n *\n * `activePin` — строка-идентификатор, а не объект.\n */\n async pins(options: RequestOptions = {}): Promise<PinsResult> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/users/me/pins',\n ...this.requestOptions(options),\n });\n\n return {\n pins: pickArray(body, 'pins'),\n // Сервер отдаёт здесь строку-идентификатор, а не объект значка.\n activePin: pickString(body, 'activePin') ?? null,\n };\n }\n\n /** Выбирает активный значок профиля. */\n setPin(slug: string, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'PUT',\n path: '/api/users/me/pin',\n body: { slug },\n ...this.requestOptions(options),\n });\n }\n\n /** Снимает активный значок. */\n removePin(options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: '/api/users/me/pin',\n ...this.requestOptions(options),\n });\n }\n\n #userPage(path: string, params: UserListParams): Promise<Page<UserSummary>> {\n return this.#userList.list({ ...params, path });\n }\n\n #userPaginator(path: string, params: UserListParams): Paginator<UserSummary> {\n return this.#userList.iterate({ ...params, path });\n }\n}\n","import type { VerificationStatus } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/**\n * Верификация профиля.\n *\n * Доступна как `itd.verification`.\n */\nexport class VerificationResource extends BaseResource {\n /** Загружает статус заявки. Значение `none` означает, что заявка не подавалась. */\n status(options: RequestOptions = {}): Promise<VerificationStatus> {\n return this.http.request<VerificationStatus>({\n method: 'GET',\n path: '/api/verification/status',\n ...this.requestOptions(options),\n });\n }\n\n /** Подаёт заявку на верификацию с видео. */\n submit(videoUrl: string, options: RequestOptions = {}): Promise<unknown> {\n return this.http.request({\n method: 'POST',\n path: '/api/verification/submit',\n body: { videoUrl },\n ...this.requestOptions(options),\n });\n }\n}\n","import type { FileInput } from './core/attachments.js';\nimport { type AuthEvents, AuthManager } from './core/auth.js';\nimport { BUILT_IN_SERVICES, type ResolvedConfig, resolveConfig } from './core/config.js';\nimport { CookieJar } from './core/cookies.js';\nimport type { Listener, Unsubscribe } from './core/emitter.js';\nimport { HttpClient } from './core/http.js';\nimport {\n composePipeline,\n createAuthMiddleware,\n createPluginsMiddleware,\n createQueueMiddleware,\n createRetryMiddleware,\n createServicesMiddleware,\n type RequestMiddleware,\n} from './core/middleware.js';\nimport type { PipelineRequest, RequestHandler } from './core/pipeline.js';\nimport { type ItdPlugin, PluginRegistry } from './core/plugins.js';\nimport { RequestQueuePool } from './core/rate-limit.js';\nimport { type ServiceDefinition, ServiceRegistry } from './core/services.js';\nimport type { ItdSession } from './core/storage.js';\nimport { Transport } from './core/transport.js';\nimport { ItdRealtime, type RealtimeOptions } from './realtime/stream.js';\nimport { AuthResource } from './resources/auth.js';\nimport { CommentsResource } from './resources/comments.js';\nimport { FilesResource } from './resources/files.js';\nimport { HashtagsResource } from './resources/hashtags.js';\nimport { NotificationsResource } from './resources/notifications.js';\nimport { PlatformResource } from './resources/platform.js';\nimport { PostsResource } from './resources/posts.js';\nimport { ReportsResource } from './resources/reports.js';\nimport { SearchResource } from './resources/search.js';\nimport { SubscriptionResource } from './resources/subscription.js';\nimport { TelemetryResource } from './resources/telemetry.js';\nimport { UsersResource } from './resources/users.js';\nimport { VerificationResource } from './resources/verification.js';\nimport type { UserId } from './types/models.js';\nimport type {\n ItdClientOptions,\n RawRequestOptions,\n RequestOptions,\n RetryOptions,\n} from './types/options.js';\n\ndeclare global {\n interface SymbolConstructor {\n readonly asyncDispose: unique symbol;\n }\n}\n\nconst CLIENT_PLUGIN_REGISTRIES = new WeakMap<ItdClient, PluginRegistry>();\n\n/** Проверяет возможность подключить плагин к клиенту без вызова `install()`. @internal */\nexport function assertClientCanUsePlugin(client: ItdClient, plugin: ItdPlugin): void {\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 */\nexport interface ItdClientInternals {\n /**\n * Готовая очередь запросов — так {@link ItdAccounts} с `rateLimitScope: 'shared'` даёт\n * нескольким клиентам одну на всех. Свою клиент в этом случае не заводит и, что важнее,\n * не гасит при `close()`: чужие ожидающие запросы это отменило бы.\n */\n queues?: RequestQueuePool | undefined;\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 #config: ResolvedConfig;\n readonly #http: HttpClient;\n readonly #transport: Transport;\n readonly #authManager: AuthManager;\n readonly #jar: CookieJar;\n readonly #queues: RequestQueuePool | undefined;\n /** Заведена ли очередь этим клиентом. Чужую он останавливать не вправе. */\n readonly #ownsQueues: boolean;\n readonly #plugins = new PluginRegistry();\n readonly #services: ServiceRegistry;\n /** Порождённые потоки уведомлений — чтобы `close()` мог закрыть их разом. */\n readonly #streams = new Set<ItdRealtime>();\n\n /** Авторизация, сессии и пароли. */\n readonly auth: AuthResource;\n /** Профили, подписки, блокировки, приватность. */\n readonly users: UsersResource;\n /** Лента, публикация, реакции, репосты, комментарии к постам. */\n readonly posts: PostsResource;\n /** Ответы на комментарии и действия над ними. */\n readonly comments: CommentsResource;\n /** Загрузка файлов и медиа. */\n readonly files: FilesResource;\n /** Уведомления: список, счётчик, отметки о прочтении, настройки. */\n readonly notifications: NotificationsResource;\n /** Хэштеги и посты по ним. */\n readonly hashtags: HashtagsResource;\n /** Глобальный поиск по пользователям и хэштегам. */\n readonly search: SearchResource;\n /** Жалобы на контент и пользователей. */\n readonly reports: ReportsResource;\n /** Верификация профиля. */\n readonly verification: VerificationResource;\n /** Подписка и способы оплаты. */\n readonly subscription: SubscriptionResource;\n /** Сведения о платформе: версии приложений, изменения, анонсы, баннер события. */\n readonly platform: PlatformResource;\n /** Телеметрия просмотров. */\n readonly telemetry: TelemetryResource;\n\n constructor(options: ItdClientOptions = {}, internals: ItdClientInternals = {}) {\n CLIENT_PLUGIN_REGISTRIES.set(this, this.#plugins);\n const config = resolveConfig(options);\n this.#config = config;\n this.#jar = new CookieJar();\n this.#services = new ServiceRegistry(config.baseUrl);\n\n // Встроенные сервисы регистрируются первыми и заменить их нельзя\n for (const service of BUILT_IN_SERVICES) this.#services.define(service);\n for (const service of config.services) this.#services.define(service);\n\n // Очередь может прийти извне — общая на несколько аккаунтов. `rateLimit: false` отключает\n // её и в этом случае: отдельный аккаунт вправе не вставать в общую очередь.\n const shared = config.rateLimit ? internals.queues : undefined;\n const queues =\n shared ?? (config.rateLimit ? new RequestQueuePool(config.rateLimit) : undefined);\n this.#queues = queues;\n // Гасить в `close()` можно только свою: остановка чужой отменила бы ожидающие запросы\n // соседних аккаунтов.\n this.#ownsQueues = shared === undefined;\n\n // Заполняется ниже. Транспорту нужен `getDeviceId` авторизации, а авторизации —\n // транспорт; взаимная ссылка замыкается через отложенный вызов.\n let authManager!: AuthManager;\n\n // Конструкторские хуки идут первыми, за ними — динамически подключаемые хуки плагинов.\n // Один и тот же объект передаётся транспорту и retry-слою, поэтому плагин видит каждую\n // сетевую попытку, в том числе onRetry между ними.\n const hooks = this.#plugins.hooks(config.hooks);\n const transport = new Transport(\n { ...config, hooks },\n {\n cookies: config.useCookieJar ? this.#jar : undefined,\n getDeviceId: () => authManager.getDeviceId(),\n onRateLimit:\n queues && config.rateLimit?.respectHeaders\n ? (limit, remaining, request) => this.#throttleByHeaders(limit, remaining, request)\n : undefined,\n },\n );\n this.#transport = transport;\n\n const pluginsLayer = createPluginsMiddleware(this.#plugins);\n const retriesLayer = createRetryMiddleware({\n retry: config.retry,\n rateLimitDelays: config.rateLimit?.retryDelays ?? [],\n pauseQueue: queues ? (ms, request) => queues.for(request.service).pause(ms) : undefined,\n hooks,\n logger: config.logger,\n buildUrl: (request) => transport.buildUrl(request),\n });\n\n const authRetry: RetryOptions | undefined = config.retry\n ? {\n attempts: config.retry.attempts,\n baseDelay: config.retry.baseDelay,\n maxDelay: config.retry.maxDelay,\n jitter: config.retry.jitter,\n retryWrites: true,\n ...(config.retry.shouldRetry ? { shouldRetry: config.retry.shouldRetry } : {}),\n }\n : undefined;\n const authPipeline = composePipeline([pluginsLayer, retriesLayer], transport.send);\n // Служебные запросы авторизации проходят через плагины и повторы, но не через очередь\n // и не через сам слой авторизации: они часто запускаются изнутри запроса, который уже ждёт токен.\n // Для них POST безопасен к повтору: refresh/sign-in не создают пользовательский контент.\n const authHandler: RequestHandler = (request) =>\n authRetry && request.retry === undefined\n ? authPipeline({ ...request, retry: authRetry })\n : authPipeline(request);\n authManager = new AuthManager(config, authHandler, this.#jar, {\n onAccountChange: () => this.#disconnectStreams(),\n });\n this.#authManager = authManager;\n\n // Порядок слоёв: очередь снаружи, за ней плагины, сервисы, повторы и авторизация,\n // в сердцевине — транспорт.\n const middlewares: RequestMiddleware[] = [];\n if (queues) {\n middlewares.push(\n createQueueMiddleware((request, task) =>\n queues.for(request.service).schedule(task, request.signal),\n ),\n );\n }\n middlewares.push(pluginsLayer);\n middlewares.push(createServicesMiddleware(this.#services));\n middlewares.push(retriesLayer);\n middlewares.push(\n createAuthMiddleware({\n getAuthHeaders: () => authManager.getAuthHeaders(),\n onUnauthorized: () => authManager.onUnauthorized(),\n autoRefresh: config.autoRefresh,\n }),\n );\n\n const handler = composePipeline(middlewares, transport.send);\n this.#http = new HttpClient({ handler, plugins: this.#plugins, baseUrl: config.baseUrl });\n\n this.files = new FilesResource(this.#http, { fetch: config.fetch });\n\n const uploadFiles = (files: FileInput[], requestOptions?: RequestOptions) =>\n this.files.uploadMany(files, requestOptions ?? {});\n const uploadFile = (file: FileInput, requestOptions?: RequestOptions) =>\n this.files.upload(file, requestOptions ?? {});\n\n this.auth = new AuthResource(this.#http, { auth: this.#authManager });\n this.users = new UsersResource(this.#http, { uploadFile });\n this.posts = new PostsResource(this.#http, { uploadFiles });\n this.comments = new CommentsResource(this.#http, { uploadFiles });\n this.notifications = new NotificationsResource(this.#http);\n this.hashtags = new HashtagsResource(this.#http);\n this.search = new SearchResource(this.#http);\n this.reports = new ReportsResource(this.#http);\n this.verification = new VerificationResource(this.#http);\n this.subscription = new SubscriptionResource(this.#http);\n this.platform = new PlatformResource(this.#http);\n this.telemetry = new TelemetryResource(this.#http);\n }\n\n /** Базовый URL, к которому обращается клиент. */\n get baseUrl(): string {\n return this.#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 request<T = unknown>(options: RawRequestOptions): Promise<T> {\n return this.#http.request<T>(options);\n }\n\n /**\n * Подключает плагин.\n *\n * Плагин работает на уровне транспорта: видит запрос до отправки и разобранный ответ,\n * поэтому одна обёртка охватывает сразу все методы клиента. Подключать можно в любой\n * момент, но обычно это делают сразу после создания клиента.\n *\n * @throws {ItdConfigError} если плагин задан неверно или уже подключён\n *\n * @example\n * ```ts\n * import { crypt } from '@itd-api/crypto';\n *\n * itd.use(crypt());\n * await itd.posts.create({ content: 'секрет' }, { encrypt: 'invis' });\n * ```\n */\n use(plugin: ItdPlugin): this {\n this.#plugins.add(plugin, {\n baseUrl: this.#config.baseUrl,\n logger: this.#config.logger,\n getAuthScope: () => this.#authManager.getAuthScope(),\n getAuthIdentity: () => this.#authManager.getAuthIdentity(),\n });\n return this;\n }\n\n /** Имена подключённых плагинов в фактическом порядке выполнения обёрток. */\n pluginNames(): string[] {\n return this.#plugins.names();\n }\n\n /** Подключён ли плагин с таким именем. */\n hasPlugin(name: string): boolean {\n return this.#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.#plugins.remove(name);\n }\n\n /**\n * Регистрирует сервис платформы — домен, отличный от основного.\n *\n * Запросы с `{ service: 'имя' }` уходят на его хост с его заголовками. То же самое умеет\n * опция `services` конструктора. Занятое имя не переопределяется — ни своё, ни встроенное:\n * разовому запросу хост задаётся полем `baseUrl`.\n *\n * Заголовок авторизации по умолчанию уходит только своим — домену клиента и его\n * поддоменам. Стороннему хосту токен нужно разрешить явно: `auth: true`.\n *\n * @throws {ItdConfigError} если определение неверно или имя уже занято\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 this.#services.define(definition);\n return this;\n }\n\n /**\n * Базовый URL зарегистрированного сервиса.\n *\n * @throws {ItdConfigError} если сервис не зарегистрирован\n */\n serviceBaseUrl(name: string): string {\n return this.#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 return this.#authManager.on(event, listener);\n }\n\n /**\n * Создаёт поток уведомлений в реальном времени.\n *\n * Каждый вызов даёт новый независимый поток; обычно он нужен один на приложение.\n * Соединение поднимается методом `connect()` и держится само. Замена авторизации на токен\n * другого пользователя завершает все потоки клиента; смена только сессии их не затрагивает.\n *\n * @example\n * ```ts\n * const stream = itd.realtime();\n *\n * stream.on('notification', ({ notification }) => {\n * console.log(formatNotificationText(notification));\n * });\n * stream.on('unreadCount', (count) => setBadge(count));\n *\n * await stream.connect();\n * ```\n */\n realtime(options: RealtimeOptions = {}): ItdRealtime {\n let stream!: ItdRealtime;\n stream = new ItdRealtime(\n {\n baseUrl: this.#config.baseUrl,\n fetch: this.#config.fetch,\n baseHeaders: (url) => this.#transport.platformHeaders(url),\n getAuthIdentity: () => this.#authManager.getCurrentAuthIdentity(),\n getAuthScope: () => this.#authManager.getAuthScope(),\n getToken: () => this.#authManager.getAccessToken(),\n refresh: () => this.#authManager.onUnauthorized(),\n fetchUnreadCount: () => this.notifications.count(),\n onClose: () => this.#streams.delete(stream),\n logger: this.#config.logger,\n },\n options,\n );\n\n // Регистрируем для `close()`: поток держит открытое соединение и таймер переподключения.\n this.#streams.add(stream);\n return stream;\n }\n\n /**\n * Освобождает ресурсы клиента: закрывает все потоки уведомлений, отправляет открытые\n * накопители {@link telemetry}, затем останавливает очередь запросов.\n *\n * После вызова клиентом можно пользоваться снова — новые запросы поднимут всё заново,\n * но уже созданные потоки и успешно закрытые накопители останутся закрытыми.\n *\n * Общая очередь, полученная от {@link ItdAccounts}, не останавливается: её гасит сам\n * контейнер, когда закрывает все аккаунты разом.\n *\n * @example\n * ```ts\n * await using itd = new ItdClient({ auth: token });\n * // …работа…\n * // dispose() вызовется сам на выходе из блока\n * ```\n */\n async close(): Promise<void> {\n this.#disconnectStreams();\n try {\n await this.telemetry.close();\n } finally {\n if (this.#ownsQueues) this.#queues?.stop();\n }\n }\n\n /**\n * Окончательно освобождает клиент: выполняет {@link close} и отключает все плагины.\n *\n * В отличие от `close()`, после `dispose()` плагины не восстанавливаются автоматически.\n * Сам клиент остаётся пригоден для обычных запросов; при необходимости плагины можно\n * подключить заново через {@link use}.\n */\n async dispose(): Promise<void> {\n const results = await Promise.allSettled([this.close(), this.#plugins.dispose()]);\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 /** Завершает потоки до того, как запросы начнут использовать другой аккаунт. */\n #disconnectStreams(): void {\n for (const stream of [...this.#streams]) stream.disconnect();\n this.#streams.clear();\n }\n\n /** Позволяет использовать клиент с `await using`. */\n [Symbol.asyncDispose](): Promise<void> {\n return this.dispose();\n }\n\n // Fallback для `await using` в Node 18, где `Symbol.asyncDispose` отсутствует.\n static {\n if (\n typeof (Symbol as SymbolConstructor & { asyncDispose?: symbol }).asyncDispose !== 'symbol'\n ) {\n const prototype = ItdClient.prototype as unknown as Record<PropertyKey, unknown>;\n prototype[Symbol.for('Symbol.asyncDispose')] = prototype.undefined;\n delete prototype.undefined;\n }\n }\n\n /** Текущая сессия целиком — чтобы сохранить её самостоятельно. */\n getSession(): Promise<ItdSession | null> {\n return this.#authManager.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.#authManager.getUserId();\n }\n\n /** Восстанавливает сохранённую сессию, включая cookie. */\n setSession(session: ItdSession): Promise<void> {\n return this.#authManager.setSession(session);\n }\n\n /**\n * Придерживает очередь, когда лимит сервера исчерпан.\n *\n * Сервер сообщает остаток в заголовке `x-ratelimit-remaining`. Как только тот доходит\n * до нуля, очередь встаёт на первую паузу лестницы — короткую, потому что окно могло\n * почти истечь. Если оно ещё действует, следующий запрос получит `429`, и дальше\n * лестницу отработает планировщик повторов.\n *\n * Смысл этой паузы прежде всего в том, чтобы при работе в несколько потоков остальные\n * запросы не улетели в стену все разом.\n */\n #throttleByHeaders(\n limit: number | undefined,\n remaining: number | undefined,\n request: PipelineRequest,\n ): void {\n if (remaining === undefined || remaining > 0) return;\n\n const first = this.#config.rateLimit?.retryDelays[0];\n if (first === undefined) return;\n\n this.#queues?.for(request.service).pause(first);\n this.#config.logger?.debug(\n `лимит сервера исчерпан (${remaining} из ${limit ?? '?'}), очередь ждёт ${first} мс`,\n );\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 { assertClientCanUnusePlugin, assertClientCanUsePlugin, ItdClient } from './client.js';\nimport { resolveRateLimit } from './core/config.js';\nimport { Emitter, type Listener, reportListenerError, type Unsubscribe } from './core/emitter.js';\nimport { ItdConfigError } from './core/errors.js';\nimport {\n type ControlledTokenStorage,\n controlledTokenStorage,\n isRestorableSession,\n MemoryMultiTokenStorage,\n type MultiTokenStorage,\n} from './core/multi-storage.js';\nimport { assertPluginRemovable, type ItdPlugin, orderPluginDefinitions } from './core/plugins.js';\nimport { RequestQueuePool } from './core/rate-limit.js';\nimport type { TokenStorage } from './core/storage.js';\nimport type { ItdClientOptions, Logger } from './types/options.js';\n\n/** Как аккаунты делят между собой очередь запросов. */\nexport type RateLimitScope = 'account' | 'shared';\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 ItdPlugin[] | undefined;\n /**\n * Как делить очередь запросов. По умолчанию `'account'` — своя у каждого.\n *\n * Лимиты итд.com считаются по аккаунту, а при работе через разные прокси общая очередь\n * только мешает. Она нужна в другом случае: когда все аккаунты сидят на одном IP\n * и упираются в ограничение по адресу, — тогда `'shared'` разводит их запросы во времени\n * все разом, а не поаккаунтно.\n *\n * Настройки самой очереди берутся из общей опции `rateLimit`. Личный объект `rateLimit`\n * в этом режиме запрещён, потому что не может изменить уже созданную очередь;\n * `rateLimit: false` у отдельного аккаунта выводит его из неё.\n */\n rateLimitScope?: RateLimitScope | undefined;\n}\n\n/**\n * Настройки одного аккаунта. Общее мультихранилище задаёт контейнер, а аккаунт получает\n * свой срез автоматически; остальное — как у `ItdClient`.\n *\n * При `rateLimitScope: 'shared'` объект `rateLimit` задаётся только контейнеру; аккаунту\n * разрешено передать `false`, чтобы не ставить его запросы в общую очередь.\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/**\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 /** Управляемые срезы хранилища: после удаления аккаунт больше не может писать через свой. */\n readonly #storageControls = new Map<string, ControlledTokenStorage>();\n /** Подписки на события клиентов — снимаются вместе с аккаунтом. */\n readonly #eventUnsubscribers = new Map<string, Unsubscribe[]>();\n /** Плагины для всех: и для уже заведённых аккаунтов, и для будущих. */\n readonly #plugins: ItdPlugin[];\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 constructor(options: ItdAccountsOptions = {}) {\n const { storage, plugins, 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.#rateLimitScope = rateLimitScope ?? 'account';\n\n // Общая очередь заводится сразу: проверить опции лучше при создании контейнера,\n // а не при добавлении первого аккаунта.\n const rateLimit =\n this.#rateLimitScope === 'shared' ? resolveRateLimit(base.rateLimit) : undefined;\n this.#queues = rateLimit ? new RequestQueuePool(rateLimit) : 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` — со всеми ресурсами, плагинами и `realtime()`.\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 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 = new ItdClient(\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 } catch (error) {\n storageControl.revoke();\n if (client) {\n void client.dispose().catch((cleanupError: unknown) => {\n this.#reportPluginCleanup('неудачного добавления аккаунта', 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 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 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 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 try {\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 return true;\n } finally {\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: ItdPlugin): 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.#reportPluginCleanup(`отката плагина «${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 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 await Promise.all([...this.#clients.values()].map((client) => client.close()));\n this.#queues?.stop();\n }\n\n /**\n * Окончательно освобождает контейнер и отключает общие плагины у всех аккаунтов.\n *\n * Для временной остановки потоков и очереди без отключения плагинов используйте\n * {@link close}.\n */\n async dispose(): Promise<void> {\n const results = await Promise.allSettled([\n ...[...this.#clients.values()].map((client) => client.dispose()),\n Promise.resolve().then(() => this.#queues?.stop()),\n ]);\n this.#plugins.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 // Fallback для `await using` в Node 18, где `Symbol.asyncDispose` отсутствует, —\n // тот же приём, что в ItdClient.\n static {\n if (\n typeof (Symbol as SymbolConstructor & { asyncDispose?: symbol }).asyncDispose !== 'symbol'\n ) {\n const prototype = ItdAccounts.prototype as unknown as Record<PropertyKey, unknown>;\n prototype[Symbol.for('Symbol.asyncDispose')] = prototype.undefined;\n delete prototype.undefined;\n }\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 /** Не теряет ошибку фонового teardown, который синхронный API не может await-нуть. */\n #reportPluginCleanup(scope: string, error: unknown): void {\n const message = `Не удалось завершить teardown после ${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","import { NotificationType } from '../types/enums.js';\nimport type { Notification } from '../types/models.js';\n\n/** Имя, которое подставляется, если участник неизвестен. */\nconst UNKNOWN_ACTOR = 'Пользователь';\n\n/** Текст, который подставляется для неизвестного типа уведомления. */\nconst FALLBACK_TEXT = 'Новое уведомление';\n\n/**\n * Шаблоны текстов уведомлений.\n *\n * Для каждого типа задано две формы: для одного участника и для схлопнутого уведомления,\n * где действие совершили несколько человек.\n */\nconst TEMPLATES: Readonly<\n Record<string, { one: (name: string) => string; many: (name: string, others: number) => string }>\n> = Object.freeze({\n [NotificationType.Follow]: {\n one: (name) => `${name} подписался(-ась) на вас`,\n many: (name, others) => `${name} и ещё ${others} подписались на вас`,\n },\n [NotificationType.FollowRequest]: {\n one: (name) => `${name} хочет подписаться на вас`,\n many: (name, others) => `${name} и ещё ${others} хотят подписаться на вас`,\n },\n [NotificationType.FollowAccepted]: {\n one: (name) => `${name} принял(а) вашу заявку`,\n many: (name, others) => `${name} и ещё ${others} приняли вашу заявку`,\n },\n [NotificationType.PostReaction]: {\n one: (name) => `${name} оценил(а) ваш пост`,\n many: (name, others) => `${name} и ещё ${others} оценили ваш пост`,\n },\n [NotificationType.PostComment]: {\n one: (name) => `${name} прокомментировал(а) ваш пост`,\n many: (name, others) => `${name} и ещё ${others} прокомментировали ваш пост`,\n },\n [NotificationType.PostRepost]: {\n one: (name) => `${name} сделал(а) репост`,\n many: (name, others) => `${name} и ещё ${others} сделали репост`,\n },\n [NotificationType.CommentReaction]: {\n one: (name) => `${name} оценил(а) ваш комментарий`,\n many: (name, others) => `${name} и ещё ${others} оценили ваш комментарий`,\n },\n [NotificationType.CommentReply]: {\n one: (name) => `${name} ответил(а) на ваш комментарий`,\n many: (name, others) => `${name} и ещё ${others} ответили на ваш комментарий`,\n },\n [NotificationType.PostMention]: {\n one: (name) => `${name} упомянул(а) вас в посте`,\n many: (name, others) => `${name} и ещё ${others} упомянули вас в посте`,\n },\n [NotificationType.CommentMention]: {\n one: (name) => `${name} упомянул(а) вас в комментарии`,\n many: (name, others) => `${name} и ещё ${others} упомянули вас в комментарии`,\n },\n [NotificationType.WallPost]: {\n one: (name) => `${name} написал(а) на вашей стене`,\n many: (name, others) => `${name} и ещё ${others} написали на вашей стене`,\n },\n [NotificationType.VerificationApproved]: {\n one: () => 'Ваша заявка на верификацию одобрена',\n many: () => 'Ваша заявка на верификацию одобрена',\n },\n [NotificationType.VerificationRejected]: {\n one: () => 'Ваша заявка на верификацию отклонена',\n many: () => 'Ваша заявка на верификацию отклонена',\n },\n});\n\n/**\n * Собирает текст уведомления на русском.\n *\n * Повторяет формулировки сайта итд.com. Для неизвестного типа возвращает\n * «Новое уведомление» — библиотека не выдумывает текст, которого нет.\n *\n * @example\n * ```ts\n * formatNotificationText(notification);\n * // 'Аня и ещё 2 оценили ваш пост'\n * ```\n */\nexport function formatNotificationText(notification: Notification): string {\n const template = TEMPLATES[notification.type];\n if (!template) return FALLBACK_TEXT;\n\n const first = notification.actors[0];\n const name = first?.displayName || first?.username || UNKNOWN_ACTOR;\n\n const others = notification.count - 1;\n return others > 0 ? template.many(name, others) : template.one(name);\n}\n","import { NotificationType } from '../types/enums.js';\nimport type { Notification } from '../types/models.js';\n\n/** Типы, ведущие на пост. */\nconst POST_TYPES = new Set<string>([\n NotificationType.PostReaction,\n NotificationType.PostRepost,\n NotificationType.PostMention,\n NotificationType.WallPost,\n]);\n\n/** Типы, ведущие на комментарий внутри поста. */\nconst COMMENT_TYPES = new Set<string>([\n NotificationType.PostComment,\n NotificationType.CommentReaction,\n NotificationType.CommentReply,\n NotificationType.CommentMention,\n]);\n\n/** Типы, ведущие на профиль. */\nconst FOLLOW_TYPES = new Set<string>([\n NotificationType.Follow,\n NotificationType.FollowRequest,\n NotificationType.FollowAccepted,\n]);\n\n/**\n * Вычисляет адрес, на который ведёт уведомление.\n *\n * Возвращает путь внутри сайта — без домена, чтобы его можно было передать роутеру\n * приложения. Поле `clickUrl` от сервера используется только как запасной вариант:\n * вычисленный путь точнее, поскольку учитывает родительский пост у комментариев.\n *\n * @example\n * ```ts\n * const url = resolveNotificationUrl(notification);\n * // '/@nowkie/post/9f1c…?comment=2b7e…'\n * ```\n */\nexport function resolveNotificationUrl(notification: Notification): string {\n const { type, entityId, parentEntityId, clickUrl } = notification;\n const username = notification.actors[0]?.username;\n\n if (username && entityId) {\n if (POST_TYPES.has(type)) return `/@${username}/post/${entityId}`;\n\n if (COMMENT_TYPES.has(type)) {\n return parentEntityId\n ? `/@${username}/post/${parentEntityId}?comment=${entityId}`\n : `/@${username}/post/${entityId}`;\n }\n }\n\n if (username && FOLLOW_TYPES.has(type)) return `/@${username}`;\n\n return clickUrl || '/notifications';\n}\n","import { SpanType } from '../types/enums.js';\nimport type { Span } from '../types/models.js';\n\n/** Формат результата {@link renderSpans}. */\nexport const SpanRenderFormat = Object.freeze({\n Html: 'html',\n Markdown: 'markdown',\n Ansi: 'ansi',\n} as const);\nexport type SpanRenderFormat = (typeof SpanRenderFormat)[keyof typeof SpanRenderFormat];\n\nexport interface RenderSpansOptions {\n /** Формат результата. По умолчанию `html`. */\n format?: SpanRenderFormat;\n /** Строит адрес упоминания. `null` или `undefined` отключает ссылку. */\n mentionUrl?: (username: string) => string | null | undefined;\n /** Строит адрес хэштега. `null` или `undefined` отключает ссылку. */\n hashtagUrl?: (tag: string) => string | null | undefined;\n /**\n * Префикс HTML-классов. По умолчанию `itd`; пустая строка или `null` отключает классы.\n *\n * Например, `app` создаёт `app-mention`, `app-hashtag`, `app-quote` и `app-spoiler`.\n */\n classPrefix?: string | null;\n}\n\ninterface NormalizedSpan {\n span: Span;\n start: number;\n end: number;\n}\n\nfunction normalizedSpans(\n content: string,\n spans: readonly Span[] | null | undefined,\n): NormalizedSpan[] {\n const result: NormalizedSpan[] = [];\n for (const span of spans ?? []) {\n if (\n !span ||\n !Number.isFinite(span.offset) ||\n !Number.isFinite(span.length) ||\n span.length <= 0\n ) {\n continue;\n }\n\n const start = Math.max(0, Math.trunc(span.offset));\n const end = Math.min(content.length, Math.trunc(span.offset + span.length));\n if (start < end && start < content.length) result.push({ span, start, end });\n }\n return result;\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction safeHttpUrl(value: string | undefined): string | null {\n if (!value) return null;\n try {\n const url = new URL(value);\n return url.protocol === 'http:' || url.protocol === 'https:' ? url.href : null;\n } catch {\n return null;\n }\n}\n\nfunction safeNavigationUrl(value: string | null | undefined): string | null {\n if (!value) return null;\n try {\n const url = new URL(value, 'https://itd.invalid');\n return url.protocol === 'http:' || url.protocol === 'https:' ? value : null;\n } catch {\n return null;\n }\n}\n\nfunction entityValue(span: Span, content: string, kind: 'mention' | 'hashtag'): string {\n const offset = Number.isFinite(span.offset) ? Math.max(0, Math.trunc(span.offset)) : 0;\n const end = Number.isFinite(span.length)\n ? Math.min(content.length, offset + Math.max(0, Math.trunc(span.length)))\n : offset;\n const text = content.slice(offset, end);\n if (kind === 'mention') {\n return span.username ?? span.tag ?? text.replace(/^@/u, '');\n }\n return span.tag ?? text.replace(/^#/u, '');\n}\n\nfunction mentionUrl(username: string, options: RenderSpansOptions): string | null | undefined {\n return options.mentionUrl ? options.mentionUrl(username) : `/@${encodeURIComponent(username)}`;\n}\n\nfunction hashtagUrl(tag: string, options: RenderSpansOptions): string | null | undefined {\n return options.hashtagUrl ? options.hashtagUrl(tag) : `/hashtag/${encodeURIComponent(tag)}`;\n}\n\nfunction classAttribute(name: string, options: RenderSpansOptions): string {\n const prefix = options.classPrefix === undefined ? 'itd' : options.classPrefix;\n return prefix ? ` class=\"${escapeHtml(`${prefix}-${name}`)}\"` : '';\n}\n\nfunction has(active: readonly Span[], type: string): boolean {\n return active.some((span) => span.type === type);\n}\n\nfunction last(active: readonly Span[], type: string): Span | undefined {\n for (let index = active.length - 1; index >= 0; index -= 1) {\n const span = active[index];\n if (span?.type === type) return span;\n }\n return undefined;\n}\n\nfunction renderHtmlSegment(\n text: string,\n active: readonly Span[],\n content: string,\n options: RenderSpansOptions,\n): string {\n let rendered = escapeHtml(text).replace(/\\n/g, '<br>');\n\n if (has(active, SpanType.Bold)) rendered = `<strong>${rendered}</strong>`;\n if (has(active, SpanType.Italic)) rendered = `<em>${rendered}</em>`;\n if (has(active, SpanType.Underline)) rendered = `<u>${rendered}</u>`;\n if (has(active, SpanType.Strike)) rendered = `<s>${rendered}</s>`;\n if (has(active, SpanType.Monospace)) rendered = `<code>${rendered}</code>`;\n if (has(active, SpanType.Quote)) {\n rendered = `<span${classAttribute('quote', options)}>${rendered}</span>`;\n }\n if (has(active, SpanType.Spoiler)) {\n rendered = `<span${classAttribute('spoiler', options)} data-spoiler=\"true\">${rendered}</span>`;\n }\n\n const link = last(active, SpanType.Link);\n const url = safeHttpUrl(link?.url);\n if (url) {\n rendered = `<a href=\"${escapeHtml(url)}\" rel=\"noopener noreferrer\">${rendered}</a>`;\n }\n\n const mention = last(active, SpanType.Mention);\n if (mention) {\n const username = entityValue(mention, content, 'mention');\n const href = safeNavigationUrl(mentionUrl(username, options));\n if (href) {\n rendered = `<a href=\"${escapeHtml(href)}\"${classAttribute('mention', options)}>${rendered}</a>`;\n }\n }\n\n const hashtag = last(active, SpanType.Hashtag);\n if (hashtag) {\n const tag = entityValue(hashtag, content, 'hashtag');\n const href = safeNavigationUrl(hashtagUrl(tag, options));\n if (href) {\n rendered = `<a href=\"${escapeHtml(href)}\"${classAttribute('hashtag', options)}>${rendered}</a>`;\n }\n }\n\n return rendered;\n}\n\nfunction escapeMarkdown(value: string, startsAtLineStart: boolean): string {\n const lines = value.replace(/([\\\\`*_[\\]<>~|&])/g, '\\\\$1').split('\\n');\n return lines\n .map((line, index) => {\n if (index === 0 && !startsAtLineStart) return line;\n return line\n .replace(/^ {0,3}(?=#{1,6}(?:[ \\t]|$))/u, '$&\\\\')\n .replace(/^ {0,3}(?=[+-][ \\t])/u, '$&\\\\')\n .replace(/^(\\s{0,3}\\d{1,9})([.)])(?=[ \\t])/u, '$1\\\\$2')\n .replace(/^(\\s{0,3})(?=-(?:[ \\t]*-){2,}[ \\t]*$)/u, '$1\\\\');\n })\n .join('\\n');\n}\n\nfunction markdownCodeSpan(text: string): string {\n const normalized = text.replace(/\\r\\n?|\\n/g, ' ');\n const longestRun = Math.max(\n 0,\n ...Array.from(normalized.matchAll(/`+/g), (match) => match[0].length),\n );\n const fence = '`'.repeat(longestRun + 1);\n const allSpaces = /^ +$/u.test(normalized);\n const needsPadding =\n normalized.startsWith('`') ||\n normalized.endsWith('`') ||\n (!allSpaces && normalized.startsWith(' ') && normalized.endsWith(' '));\n const body = needsPadding ? ` ${normalized} ` : normalized;\n return `${fence}${body}${fence}`;\n}\n\ninterface RenderSegment {\n start: number;\n text: string;\n active: Span[];\n}\n\nfunction markdownUrl(value: string): string {\n return value.replace(/([\\\\)])/g, '\\\\$1');\n}\n\nfunction renderMarkdownSegment(\n segment: RenderSegment,\n content: string,\n options: RenderSpansOptions,\n): string {\n const { text, active } = segment;\n const startsAtLineStart = segment.start === 0 || content[segment.start - 1] === '\\n';\n let rendered = has(active, SpanType.Monospace)\n ? markdownCodeSpan(text)\n : escapeMarkdown(text, startsAtLineStart);\n if (has(active, SpanType.Bold)) rendered = `**${rendered}**`;\n if (has(active, SpanType.Italic)) rendered = `*${rendered}*`;\n if (has(active, SpanType.Underline)) rendered = `<u>${rendered}</u>`;\n if (has(active, SpanType.Strike)) rendered = `~~${rendered}~~`;\n if (has(active, SpanType.Spoiler)) rendered = `||${rendered}||`;\n\n const link = last(active, SpanType.Link);\n const url = safeHttpUrl(link?.url);\n if (url) rendered = `[${rendered}](${markdownUrl(url)})`;\n\n const mention = last(active, SpanType.Mention);\n if (mention) {\n const username = entityValue(mention, content, 'mention');\n const href = safeNavigationUrl(mentionUrl(username, options));\n if (href) rendered = `[${rendered}](${markdownUrl(href)})`;\n }\n\n const hashtag = last(active, SpanType.Hashtag);\n if (hashtag) {\n const tag = entityValue(hashtag, content, 'hashtag');\n const href = safeNavigationUrl(hashtagUrl(tag, options));\n if (href) rendered = `[${rendered}](${markdownUrl(href)})`;\n }\n\n return rendered;\n}\n\nfunction renderMarkdown(\n segments: readonly RenderSegment[],\n content: string,\n options: RenderSpansOptions,\n): string {\n const groups: { quoted: boolean; text: string }[] = [];\n for (const segment of segments) {\n const quoted = has(segment.active, SpanType.Quote);\n const rendered = renderMarkdownSegment(segment, content, options);\n const previous = groups[groups.length - 1];\n if (previous?.quoted === quoted) previous.text += rendered;\n else groups.push({ quoted, text: rendered });\n }\n\n let result = '';\n for (const [index, group] of groups.entries()) {\n const rendered = group.quoted\n ? group.text\n .split('\\n')\n .map((line) => `> ${line}`)\n .join('\\n')\n : group.text;\n const previous = groups[index - 1];\n if (\n result !== '' &&\n (group.quoted || previous?.quoted) &&\n !result.endsWith('\\n') &&\n !rendered.startsWith('\\n')\n ) {\n result += '\\n';\n }\n result += rendered;\n }\n return result;\n}\n\nfunction renderAnsiSegment(text: string, active: readonly Span[]): string {\n const codes: number[] = [];\n if (has(active, SpanType.Bold)) codes.push(1);\n if (has(active, SpanType.Italic)) codes.push(3);\n if (has(active, SpanType.Underline) || has(active, SpanType.Link)) codes.push(4);\n if (has(active, SpanType.Strike)) codes.push(9);\n if (has(active, SpanType.Spoiler)) codes.push(8);\n if (has(active, SpanType.Quote)) codes.push(2);\n if (has(active, SpanType.Mention) || has(active, SpanType.Hashtag)) codes.push(36);\n\n return codes.length === 0 ? text : `\\u001b[${codes.join(';')}m${text}\\u001b[0m`;\n}\n\n/**\n * Преобразует текст и wire-разметку API в безопасный HTML, Markdown или ANSI.\n *\n * Некорректные серверные spans игнорируются либо обрезаются по границам строки. Пересекающиеся\n * spans разбиваются на независимые сегменты, поэтому HTML остаётся корректно вложенным.\n * Отсутствующий массив считается пустым; формат по умолчанию — HTML.\n */\nexport function renderSpans(\n content: string,\n spans: readonly Span[] | null | undefined = [],\n options: RenderSpansOptions = {},\n): string {\n const normalized = normalizedSpans(content, spans);\n const boundaries = new Set<number>([0, content.length]);\n for (const item of normalized) {\n boundaries.add(item.start);\n boundaries.add(item.end);\n }\n\n const points = [...boundaries].sort((left, right) => left - right);\n const segments: RenderSegment[] = [];\n for (let index = 0; index < points.length - 1; index += 1) {\n const start = points[index];\n const end = points[index + 1];\n if (start === undefined || end === undefined || start === end) continue;\n\n const text = content.slice(start, end);\n const active = normalized\n .filter((item) => item.start <= start && item.end >= end)\n .map((item) => item.span);\n segments.push({ start, text, active });\n }\n\n const format = options.format ?? SpanRenderFormat.Html;\n if (format === SpanRenderFormat.Markdown) return renderMarkdown(segments, content, options);\n\n let result = '';\n for (const segment of segments) {\n switch (format) {\n case SpanRenderFormat.Html:\n result += renderHtmlSegment(segment.text, segment.active, content, options);\n break;\n case SpanRenderFormat.Ansi:\n result += renderAnsiSegment(segment.text, segment.active);\n break;\n }\n }\n\n return result;\n}\n","import type {\n AttachmentType,\n IncidentKind,\n LikesVisibility,\n Loose,\n NotificationType,\n ServiceState,\n SpanType,\n WallAccess,\n} from './enums.js';\n\n/**\n * Дата и время в формате ISO-8601, например `2026-07-21T14:30:00.000Z`.\n *\n * Библиотека не превращает такие поля в `Date`: строку проще сравнивать, логировать\n * и передавать дальше без потерь. Для разбора есть {@link toDate}.\n */\nexport type IsoDate = string;\n\n/**\n * Идентификатор пользователя — **строго UUID**.\n *\n * Отличается от {@link UserRef} тем, что имя пользователя здесь не подойдёт. Так помечены\n * места, где API принимает только UUID: например `wallRecipientId` при постинге на чужую стену.\n */\nexport type UserId = string;\n\n/**\n * Ссылка на пользователя: **UUID либо имя пользователя**.\n *\n * Пути вида `/api/users/{id}` принимают оба варианта, поэтому `itd.users.get('nowkie')`\n * работает так же, как `itd.users.get('9f1c…')`.\n */\nexport type UserRef = string;\n\n/**\n * Разметка в тексте поста или комментария.\n *\n * `offset` и `length` измеряются в UTF-16 code units: это те же индексы, которые используют\n * `String#slice`, `substring` и DOM Selection в JavaScript. Эмодзи вне BMP обычно занимают\n * две единицы.\n */\nexport interface Span {\n /** Тип фрагмента — см. {@link SpanType}. */\n type: SpanType;\n /** Смещение от начала текста. */\n offset: number;\n /** Длина фрагмента. */\n length: number;\n /** Имя хэштега без решётки. У старых mention-объектов может содержать username. */\n tag?: string;\n /** Адрес ссылки. Только у `link`: у него вместо `tag` отдельное поле. */\n url?: string;\n /** Имя пользователя у `mention`. */\n username?: string;\n /** Идентификатор пользователя у некоторых ответов API с `mention`. */\n id?: string;\n}\n\n/**\n * Значок-«пин» в профиле — награда или отметка платформы.\n */\nexport interface Pin {\n /** Постоянный идентификатор, например `epepuy_202605_59`. */\n slug: string;\n /** Отображаемое название. */\n name: string;\n /** Описание, за что выдан. */\n description: string;\n /** Адрес изображения. */\n url: string;\n /** Когда выдан. Приходит только в списке своих пинов. */\n grantedAt?: IsoDate;\n}\n\n/**\n * Автор поста или комментария.\n *\n * Встречается внутри `post.author` и `comment.author`.\n */\nexport interface Author {\n id: UserId;\n username: string;\n displayName: string;\n /**\n * **Эмодзи, а не картинка.**\n *\n * На итд.com аватар — это символ клана (`🩵`, `🦎`), а не адрес изображения.\n * Отрисовывать его нужно как текст.\n */\n avatar: string;\n /** Пройдена ли верификация. */\n verified: boolean;\n /** Активный значок профиля. Может отсутствовать. */\n pin?: Pin | null;\n /** Есть ли премиум-подписка (значок NUKSTA). */\n hasNuksta?: boolean;\n}\n\n/**\n * Участник события в уведомлении.\n *\n * Отличается от {@link Author} набором полей: вместо значков приходит связь с вами.\n */\nexport interface Actor {\n id: UserId;\n username: string;\n displayName: string;\n /** Эмодзи-аватар, см. {@link Author.avatar}. */\n avatar: string;\n /** Подписаны ли вы на этого пользователя. */\n isFollowing?: boolean;\n /** Подписан ли он на вас. */\n isFollowedBy?: boolean;\n}\n\n/**\n * Пользователь в списках.\n *\n * Набор полей зависит от эндпоинта: подписчики и подписки приносят `isFollowing`,\n * поиск и рекомендации — `followersCount` и `hasNuksta`. Необязательные поля отражают\n * это различие.\n */\nexport interface UserSummary {\n id: UserId;\n username: string;\n displayName: string;\n /** Эмодзи-аватар, см. {@link Author.avatar}. */\n avatar: string;\n verified: boolean;\n /** Подписаны ли вы. Приходит в списках подписчиков и подписок. */\n isFollowing?: boolean;\n /** Есть ли премиум. Приходит в поиске и рекомендациях. */\n hasNuksta?: boolean;\n /** Число подписчиков. Приходит в поиске и рекомендациях. */\n followersCount?: number;\n}\n\n/** Поля профиля, общие для своего и чужого. */\ninterface ProfileBase {\n id: UserId;\n username: string;\n displayName: string;\n /** Эмодзи-аватар, см. {@link Author.avatar}. */\n avatar: string;\n /** URL изображения баннера либо `null`. */\n banner: string | null;\n /** Описание профиля. */\n bio: string;\n verified: boolean;\n pin?: Pin | null;\n /** Кто может писать на стену. */\n wallAccess: WallAccess;\n /** Кто видит реакции. */\n likesVisibility: LikesVisibility;\n followersCount: number;\n followingCount: number;\n postsCount: number;\n createdAt: IsoDate;\n}\n\n/** Состояние подписки на премиум. */\nexport interface SubscriptionState {\n isActive: boolean;\n expiresAt: IsoDate | null;\n autoRenewal: boolean;\n}\n\n/**\n * Свой профиль — ответ `GET /api/users/me`.\n *\n * Отличается от чужого наличием {@link subscription} и {@link isPhoneVerified}\n * и отсутствием полей связи (`isFollowing`, `online`).\n */\nexport interface MyProfile extends ProfileBase {\n /** Закрыт ли профиль. */\n isPrivate: boolean;\n /** Подтверждён ли телефон. Без него часть действий недоступна. */\n isPhoneVerified: boolean;\n /** Своя премиум-подписка. */\n subscription: SubscriptionState;\n}\n\n/**\n * Состояние авторизации — ответ `GET /api/profile`.\n *\n * Endpoint доступен без сессии: в этом случае `authenticated` равен `false`,\n * а `user` — `null`.\n */\nexport interface AuthState {\n /** Есть ли действующая сессия. */\n authenticated: boolean;\n /** Заблокирован ли текущий аккаунт. */\n banned: boolean;\n /** Текущий пользователь либо `null` без действующей сессии. */\n user: MyProfile | null;\n}\n\n/**\n * Чужой профиль — ответ `GET /api/users/{id|username}`.\n *\n * Вместо своей подписки содержит связь с вами и присутствие.\n */\nexport interface PublicProfile extends ProfileBase {\n hasNuksta?: boolean;\n /** Закреплённый пост, если он есть. */\n pinnedPostId: string | null;\n /** Подписаны ли вы на него. */\n isFollowing: boolean;\n /** Подписан ли он на вас. */\n isFollowedBy: boolean;\n /** Сейчас ли пользователь в сети. */\n online: boolean;\n /** Когда был в сети. `null`, если скрыто настройками приватности. */\n lastSeen: IsoDate | null;\n}\n\n/** Профиль: свой либо чужой. Различаются функцией {@link isMyProfile}. */\nexport type Profile = MyProfile | PublicProfile;\n\n/**\n * Свой ли это профиль.\n *\n * @example\n * ```ts\n * if (isMyProfile(profile)) console.log(profile.subscription.isActive);\n * ```\n */\nexport function isMyProfile(profile: Profile): profile is MyProfile {\n return 'subscription' in profile;\n}\n\n/** Вложение поста или комментария. */\nexport interface Attachment {\n id: string;\n type: AttachmentType;\n /** Адрес файла на CDN. */\n url: string;\n /** Ширина изображения или видео в пикселях. */\n width?: number;\n /** Высота изображения или видео в пикселях. */\n height?: number;\n mimeType: string;\n /** Исходное имя файла. Приходит не всегда. */\n filename?: string;\n /** Размер в байтах. Приходит не всегда. */\n size?: number;\n /** Длительность аудио или видео в секундах. */\n duration?: number | null;\n /** Порядковый номер во вложениях поста. */\n order?: number;\n}\n\n/** Вариант ответа в опросе. */\nexport interface PollOption {\n id: string;\n text: string;\n /** Сколько голосов отдано за этот вариант. */\n votesCount: number;\n /** Порядковый номер варианта, начиная с нуля. */\n position: number;\n}\n\n/** Опрос внутри поста. */\nexport interface Poll {\n id: string;\n /** Пост, которому принадлежит опрос. */\n postId: string;\n question: string;\n /** Можно ли выбрать несколько вариантов. */\n multipleChoice: boolean;\n options: PollOption[];\n totalVotes: number;\n /** Голосовали ли вы. */\n hasVoted: boolean;\n /** За что проголосовали вы. Пустой массив, если голоса не было. */\n votedOptionIds: string[];\n createdAt: IsoDate;\n}\n\n/** Пост ленты, стены или профиля. */\nexport interface Post {\n id: string;\n content: string;\n /** Разметка текста. Передаётся без изменений, см. {@link Span}. */\n spans: Span[];\n author: Author;\n attachments: Attachment[];\n likesCount: number;\n commentsCount: number;\n repostsCount: number;\n viewsCount: number;\n /** Чья это стена, если пост опубликован не у себя. */\n wallRecipientId: UserId | null;\n /** Владелец стены. Приходит не во всех ответах. */\n wallRecipient?: Author | null;\n /** Поставили ли вы реакцию. */\n isLiked: boolean;\n /** Делали ли вы репост. */\n isReposted: boolean;\n /** Засчитан ли просмотр. */\n isViewed: boolean;\n /** Ваш ли это пост. */\n isOwner: boolean;\n /** Исходный пост, если это репост. */\n originalPost?: Post | null;\n poll?: Poll | null;\n /** Преобладающая реакция — эмодзи либо `null`. */\n dominantEmoji?: string | null;\n /** Когда пост отредактировали. `null`, если не редактировали. */\n editedAt: IsoDate | null;\n createdAt: IsoDate;\n /**\n * Служебная метка показа для телеметрии.\n *\n * Нужна только эндпоинтам `itd.telemetry.*`. В остальных случаях игнорируйте.\n */\n vs?: string;\n /**\n * Топовые комментарии. Приходят только в ответе `GET /api/posts/{id}`.\n *\n * В списках постов поле отсутствует.\n */\n comments?: Comment[];\n}\n\n/** На чей комментарий дан ответ. */\nexport interface CommentReplyTo {\n id: string;\n username: string;\n displayName: string;\n}\n\n/** Комментарий к посту или ответ на комментарий. */\nexport interface Comment {\n id: string;\n /** Текст. У голосового комментария пустой. */\n content: string;\n /**\n * Разметка текста, включая автоматически найденные сервером хэштеги и упоминания.\n *\n * Методы создания и редактирования комментария принимают только `content`, поэтому\n * библиотека не отправляет ручные spans в этих операциях.\n * Поле необязательно: отдельные ответы сервера могут его не содержать.\n */\n spans?: Span[];\n author: Author;\n likesCount: number;\n repliesCount: number;\n isLiked: boolean;\n createdAt: IsoDate;\n /** Вложения. У голосового — одно аудио с `mimeType: 'audio/ogg'`. */\n attachments?: Attachment[];\n /** Вложенные ответы. В списках приходит превью, полный список — через `itd.comments.replies()`. */\n replies?: Comment[];\n /** Заполнено только у ответов. */\n replyTo?: CommentReplyTo;\n}\n\n/**\n * Уведомление в единой форме.\n *\n * REST-список и SSE-поток отдают уведомления по-разному — разные имена типов, разные имена\n * полей, один участник против массива. Библиотека приводит оба вида к этой структуре,\n * поэтому объекты из `itd.notifications.list()` и из потока можно складывать в один список.\n *\n * Исходные данные не теряются: сервeрное имя типа остаётся в {@link rawType},\n * а весь необработанный объект — в {@link raw}.\n */\nexport interface Notification {\n id: string;\n /** Канонический тип. Старые имена (`like`, `comment`) приведены к новым. */\n type: NotificationType;\n /** Имя типа в том виде, в каком его прислал сервер. */\n rawType: string;\n /** Объект события: пост, комментарий, пользователь. */\n entityId: string | null;\n /** Пост, которому принадлежит комментарий, если событие о комментарии. */\n parentEntityId: string | null;\n /** Прочитано ли уведомление. */\n isRead: boolean;\n /** Кто совершил действие. Для схлопнутых уведомлений — несколько человек. */\n actors: Actor[];\n /** Сколько участников схлопнуто в одно уведомление. Минимум 1. */\n count: number;\n /** Текст или заголовок объекта события. */\n preview: string | null;\n /** Ссылка перехода, предложенная сервером. Обычно точнее её `resolveNotificationUrl()`. */\n clickUrl?: string;\n createdAt: IsoDate;\n /** Когда уведомление изменилось — например было прочитано. */\n updatedAt: IsoDate;\n /** Исходный объект как он пришёл от сервера. */\n raw: unknown;\n}\n\n/** Настройки приватности профиля. */\nexport interface PrivacySettings {\n /** Закрыт ли профиль: подписка требует одобрения. */\n isPrivate: boolean;\n wallAccess: WallAccess;\n likesVisibility: LikesVisibility;\n /** Показывать ли время последнего посещения. */\n showLastSeen: boolean;\n}\n\n/**\n * Настройки уведомлений.\n *\n * Сервер отдаёт плоский объект, но исторически знает два набора имён для одних и тех же\n * настроек (`likes` и `reactions`, `comments` и `replies`). При сохранении библиотека\n * отправляет оба, при чтении принимает любой.\n */\nexport interface NotificationSettings {\n /** Общий выключатель доставки. */\n enabled: boolean;\n /** Звук уведомления. */\n sound: boolean;\n /** Новые подписчики. */\n follows: boolean;\n /** Записи на вашей стене. */\n wallPosts: boolean;\n /** Реакции на ваши записи. */\n likes: boolean;\n /** Комментарии и ответы. */\n comments: boolean;\n /** Упоминания. */\n mentions: boolean;\n}\n\n/** Активная сессия входа. */\nexport interface Session {\n id: string;\n /** Та ли это сессия, из которой выполнен запрос. */\n isCurrent: boolean;\n createdAt: IsoDate;\n lastUsedAt: IsoDate;\n expiresAt: IsoDate;\n ipAddress: string;\n /** Код страны по IP, например `RU`. */\n ipCountry: string | null;\n ipCity: string | null;\n deviceType: Loose<'desktop' | 'mobile'>;\n osName: string | null;\n osVersion: string | null;\n /** Название браузера или приложения. */\n clientName: string | null;\n clientVersion: string | null;\n deviceModel: string | null;\n}\n\n/** Состояние платной подписки и её цена. */\nexport interface Subscription {\n /** Активна ли подписка сейчас. */\n active: boolean;\n /** Включено ли автопродление. */\n recurringEnabled: boolean;\n /** Цена в рублях. */\n price: number;\n}\n\n/** Сохранённый способ оплаты. */\nexport interface PaymentMethod {\n id: string;\n /** Последние четыре цифры карты. */\n last4?: string;\n /** Платёжная система: `visa`, `mastercard`, `mir`. */\n brand?: string;\n /** Основной ли это способ оплаты. */\n isDefault?: boolean;\n expiresAt?: IsoDate | null;\n}\n\n/** Хэштег. */\nexport interface Hashtag {\n id: string;\n /** Название без решётки. */\n name: string;\n /** Сколько постов с этим хэштегом. */\n postsCount: number;\n}\n\n/** Клан в рейтинге. */\nexport interface Clan {\n /** Эмодзи клана — оно же аватар его участников. */\n avatar: string;\n memberCount: number;\n}\n\n/**\n * Результат подписки на пользователя.\n *\n * @example\n * ```ts\n * const result = await itd.users.follow('nowkie');\n * // { following: true, followersCount: 11 }\n * ```\n */\nexport interface FollowResult {\n /** Подписка оформлена. У закрытого профиля отправляется заявка, и здесь будет `false`. */\n following: boolean;\n /** Сколько подписчиков стало у пользователя после действия. */\n followersCount?: number;\n /** Статус заявки, если профиль закрыт. */\n status?: Loose<'following' | 'requested'>;\n}\n\n/** Запись журнала изменений платформы. */\nexport interface ChangelogEntry {\n version: string;\n date: string;\n changes: string[];\n}\n\n/** Кнопка в анонсе платформы. */\nexport interface AnnouncementButton {\n title: string;\n /** Оформление: `primary`, `secondary` и другие. */\n style: string;\n action: { type: string; [key: string]: unknown };\n}\n\n/** Анонс на главной странице платформы. */\nexport interface Announcement {\n id: string;\n image: { url: string; width: number; height: number };\n title: string;\n description: string;\n /** Дополнительный текст мелким шрифтом. */\n additional_text?: string;\n buttons: AnnouncementButton[];\n}\n\n/** Баннер текущего события — виджет «портал». */\nexport interface Portal {\n active: boolean;\n title: string;\n url: string;\n}\n\n/** Глубина истории статуса в сутках. Столько элементов отдаёт {@link statusDays}. */\nconst STATUS_WINDOW_DAYS = 90;\n\n/** Происшествие в истории сервиса. */\nexport interface StatusIncidentLine {\n /** Вид происшествия. */\n t: IncidentKind;\n /**\n * Готовая строка для показа: `недоступен 6 мин (12:00–12:06)`. Время московское.\n * Длительность и границы интервала отдельными полями не приходят.\n */\n text: string;\n}\n\n/** Одни сутки в истории сервиса. */\nexport interface StatusDay {\n /** Худшее состояние за сутки. */\n type: ServiceState;\n /** Дата суток, `YYYY-MM-DD`. Сутки нарезаны по UTC. */\n date_key: string;\n /** Доступность за сутки в процентах. */\n uptime: number;\n /** Происшествия за сутки. */\n lines: StatusIncidentLine[];\n}\n\n/** Сервис платформы и его история доступности. */\nexport interface ServiceStatus {\n /** Идентификатор: `auth`, `main`, `media` и прочие. */\n id: string;\n /** Отображаемое название. */\n name: string;\n current_status: ServiceState;\n /** Пояснение к текущему состоянию, например `No downtime`. */\n current_message: string;\n /** Задержка последней проверки в миллисекундах. */\n latency_ms: number;\n /**\n * Момент последней проверки. Сервер отдаёт `YYYY-MM-DD HH:mm:ss` в UTC, библиотека\n * приводит значение к ISO.\n */\n last_checked: IsoDate;\n /** Доступность за 90 суток в процентах. */\n uptime_90d: number;\n /**\n * История по суткам. Ключ — сколько суток назад, `'0'` — сегодня.\n *\n * Объект разреженный: сутки без данных сервер пропускает. Ровный массив даёт\n * {@link statusDays}.\n */\n days: Record<string, StatusDay | undefined>;\n}\n\n/** Состояние платформы — ответ `itd.platform.status()`. */\nexport interface PlatformStatus {\n /** Худшее состояние среди сервисов. */\n overall_status: ServiceState;\n /** Когда данные последний раз пересчитаны. */\n updated_at: IsoDate;\n services: ServiceStatus[];\n}\n\n/** Статус заявки на верификацию. `none` означает, что заявка не подавалась. */\nexport interface VerificationStatus {\n status: Loose<'none' | 'pending' | 'approved' | 'rejected'>;\n}\n\n/** Созданная жалоба. */\nexport interface Report {\n id: string;\n createdAt: IsoDate;\n}\n\n/** Счётчики поста из `itd.posts.stats()`. */\nexport interface PostStats {\n id: string;\n likesCount: number;\n commentsCount: number;\n repostsCount: number;\n viewsCount: number;\n /** Преобладающая реакция — эмодзи либо `null`. */\n dominantEmoji: string | null;\n}\n\n/** Результат реакции на пост. */\nexport interface LikeResult {\n liked: boolean;\n likesCount: number;\n}\n\n/** Результат закрепления поста в профиле. */\nexport interface PinPostResult {\n success: boolean;\n pinnedPostId: string | null;\n}\n\n/** Закреплённые значки профиля и выбранный из них. */\nexport interface PinsResult {\n pins: Pin[];\n /** Идентификатор активного значка — строка, а не объект. */\n activePin: string | null;\n}\n\n/**\n * Разбирает дату API в объект `Date`.\n *\n * @returns `null`, если строки нет или она не разбирается\n *\n * @example\n * ```ts\n * const created = toDate(post.createdAt);\n * ```\n */\nexport function toDate(value: IsoDate | null | undefined): Date | null {\n if (!value) return null;\n const date = new Date(value);\n return Number.isFinite(date.getTime()) ? date : null;\n}\n\n/**\n * Разворачивает историю сервиса в массив на 90 суток.\n * Сутки без данных становятся `null`.\n *\n * @returns массив, где индекс — сколько суток назад: `[0]` — сегодня\n *\n * @example\n * ```ts\n * const status = await itd.platform.status();\n * const days = statusDays(status.services[0]);\n *\n * days[0]?.uptime; // доступность за сегодня\n * days.filter((day) => day === null).length; // за сколько суток данных нет\n * ```\n */\nexport function statusDays(service: ServiceStatus): (StatusDay | null)[] {\n return Array.from(\n { length: STATUS_WINDOW_DAYS },\n (_, index) => service.days[String(index)] ?? null,\n );\n}\n"],"x_google_ignoreList":[23],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["#listeners","#onError","#config","#send","#jar","#emitter","#hooks","#authScope","#loadSession","#identityForToken","#readExternalToken","#session","#sessionFromConfig","#externalIdentity","#authEpoch","#externalToken","#rotateAuthScope","#hasRefreshSession","#deviceId","#deviceIdLoading","#resolveDeviceId","createDeviceId","#saveSession","#signInWithCredentials","#refreshDeduplicated","#noRefreshSessionError","ItdAuthError","#invalidateInFlight","#transitionAuth","copySession","#seedRefreshCookie","#loading","#performLoad","REFRESH_COOKIE","REFRESH_COOKIE_PATH","#refreshing","#performRefresh","#reloginOrNull","ItdApiError","#signingIn","#performSignIn","#resolveTurnstileToken","ItdConfigError","ItdConfigError","ItdConfigError","isRecord","MemoryTokenStorage","RuntimeMode","resolveFetch","shouldUseCookieJar","shouldSendCredentials","#handler","#plugins","#baseUrl","ItdConfigError","#entries","#optionKeys","#removing","#cleanups","#ordered","#rebuildOptionKeys","#trackCleanup","#waitForDrain","#runHook","ItdAbortError","ItdApiError","ItdNetworkError","ItdTimeoutError","ItdFileError","ItdAbortError","isItdApiError","isItdRateLimitError","ItdAbortError","#concurrency","#minGap","#waiting","#clock","#active","#drain","#cancelTimer","#nextSlot","#options","#main","#byService","#services","#primaryHost","ItdConfigError","isBlob","ItdValidationError","ItdRateLimitError","ItdAuthError","ItdForbiddenError","ItdNotFoundError","ItdConflictError","ItdServerError","ItdApiErrorClass","ItdPhoneVerificationError","ItdConfigError","isBlob","#config","#deps","#buildHeaders","#prepareBody","#toTransportError","#readBodyOrFail","ItdTimeoutError","ItdAbortError","ItdError","ItdNetworkError","#options","#hooks","#middleware","#handlers","#queue","#activeKeys","#drainWaiters","#keysFor","#pump","#resolveDrain","#active","#run","#interval","#limit","#clock","#readCount","#wait","#idleTimeout","#handshakeTimeout","#clock","#lastEventId","#handshake","#read","ItdConfigError","ItdConfigError","#deps","#options","#emitter","#dispatcher","#transport","#maxAttempts","#createTransport","#deliver","#reportDispatchError","#status","#wanted","#attachEnvironmentListeners","#dispatch","#run","#cancelTimer","#detachEnvironment","#controller","#attempt","#setStatus","supportsStreamingBody","#handleEvent","#handleFailure","#refreshAndReconnect","#scheduleReconnect","#giveUp","#options","#maxPages","ItdConfigError","#state","#finished","#pagesLoaded","#advance","#auth","ItdConfigError","ItdConfigError","#state","#uploadFiles","#replies","ItdConfigError","#fetch","#createBodyFactory","#isStreamInput","#prepareStream","#prepareBuffer","ItdError","ItdFileError","ItdFileErrorReason","resolveFileStreamOptions","FileTransferMode","#resolveBuffer","#nameFromMime","normalizeMimeType","isBlob","openUrlFile","ItdConfigError","isBoundedFileStream","boundedFileStream","isFile","downloadFile","#posts","#list","ItdConfigError","ItdConfigError","#content","#spans","#formatted","ItdConfigError","#state","ItdConfigError","#state","#input","#uploadFiles","#feed","#wall","#liked","#comments","#collectAttachments","ItdConfigError","#state","ItdConfigError","#clock","#input","#submit","#result","#resource","#options","#maxBatchSize","#onClose","#dwell","#interactions","#state","#assertOpen","#flushPromise","#flushAll","#closePromise","#batches","#sessionId","createDeviceId","#uploadFile","#userList","#userPage","#userPaginator","#config","#http","#transport","#authManager","#jar","#queues","#ownsQueues","#plugins","#services","#streams","CookieJar","#throttleByHeaders","#disconnectStreams","ItdConfigError","#base","#storage","#clients","#removing","#storageControls","#eventUnsubscribers","#plugins","#removingPlugins","#queues","#rateLimitScope","#emitter","#logger","MemoryMultiTokenStorage","controlledTokenStorage","#mergeOptions","#reportPluginCleanup","#forwardEvents","isRestorableSession","#selector","#routes","#fallback","ItdConfigError","#registration","#captureMiddleware"],"sources":["../src/core/emitter.ts","../src/core/jwt.ts","../src/core/auth.ts","../src/core/clock.ts","../src/core/url.ts","../src/core/version.ts","../src/core/config.ts","../src/core/http.ts","../src/core/pipeline.ts","../src/core/plugins.ts","../src/core/retry.ts","../src/core/middleware.ts","../src/core/rate-limit.ts","../src/core/services.ts","../src/core/redact.ts","../src/core/unwrap.ts","../src/core/error-factory.ts","../src/core/transport.ts","../src/types/enums.ts","../src/realtime/middleware.ts","../src/realtime/transport.ts","../src/realtime/poll.ts","../src/realtime/reconnect.ts","../node_modules/eventsource-parser/dist/index.js","../src/realtime/sse.ts","../src/notifications/type-map.ts","../src/notifications/normalize.ts","../src/realtime/updates.ts","../src/realtime/stream.ts","../src/core/pagination.ts","../src/types/options.ts","../src/resources/base.ts","../src/resources/auth.ts","../src/builders/base.ts","../src/builders/comment.ts","../src/resources/comments.ts","../src/core/mime.ts","../src/core/multipart.ts","../src/resources/files.ts","../src/resources/hashtags.ts","../src/resources/notifications.ts","../src/core/time.ts","../src/resources/platform.ts","../src/spans/validate.ts","../src/spans/parse.ts","../src/builders/markup.ts","../src/builders/poll.ts","../src/builders/post.ts","../src/resources/posts.ts","../src/builders/report.ts","../src/resources/reports.ts","../src/resources/search.ts","../src/resources/subscription.ts","../src/resources/telemetry.ts","../src/resources/users.ts","../src/resources/verification.ts","../src/client.ts","../src/accounts.ts","../src/notifications/text.ts","../src/notifications/url.ts","../src/realtime/router.ts","../src/spans/render.ts","../src/types/models.ts"],"sourcesContent":["import type { Logger } from '../types/options.js';\n\n/** Обработчик события. */\nexport type Listener<T> = (payload: T) => void;\n\n/** Функция отписки, которую возвращает подписка на событие. */\nexport type Unsubscribe = () => void;\n\n/**\n * Минимальный типизированный источник событий.\n *\n * Своя реализация вместо `EventTarget` и `EventEmitter`: первый есть не везде и требует\n * обёрток `CustomEvent`, второй существует только в Node. Нужны ровно подписка и рассылка.\n *\n * Исключение в обработчике не прерывает рассылку остальным и не роняет библиотеку.\n *\n * @typeParam Events карта «имя события → тип полезной нагрузки». Задаётся интерфейсом,\n * поэтому ограничение на индексную сигнатуру намеренно не накладывается.\n */\nexport class Emitter<Events> {\n readonly #listeners = new Map<keyof Events, Set<Listener<never>>>();\n readonly #onError: ((error: unknown) => void) | undefined;\n\n constructor(onListenerError?: (error: unknown) => void) {\n this.#onError = onListenerError;\n }\n\n /**\n * Подписывается на событие.\n *\n * @returns функция отписки\n *\n * @example\n * ```ts\n * const off = realtime.on('notification', (event) => console.log(event));\n * off();\n * ```\n */\n on<K extends keyof Events>(event: K, listener: Listener<Events[K]>): Unsubscribe {\n const set = this.#listeners.get(event) ?? new Set();\n set.add(listener as Listener<never>);\n this.#listeners.set(event, set);\n\n return () => this.off(event, listener);\n }\n\n /** Подписывается на одно срабатывание. */\n once<K extends keyof Events>(event: K, listener: Listener<Events[K]>): Unsubscribe {\n const off = this.on(event, (payload) => {\n off();\n listener(payload);\n });\n return off;\n }\n\n /** Отписывается от события. */\n off<K extends keyof Events>(event: K, listener: Listener<Events[K]>): void {\n this.#listeners.get(event)?.delete(listener as Listener<never>);\n }\n\n /** Рассылает событие подписчикам. */\n emit<K extends keyof Events>(event: K, payload: Events[K]): void {\n const set = this.#listeners.get(event);\n if (!set) return;\n\n // Копия нужна: обработчик вправе отписаться прямо во время рассылки.\n for (const listener of [...set]) {\n try {\n (listener as Listener<Events[K]>)(payload);\n } catch (error) {\n this.#onError?.(error);\n }\n }\n }\n\n /** Сколько подписчиков у события. */\n listenerCount(event: keyof Events): number {\n return this.#listeners.get(event)?.size ?? 0;\n }\n\n /** Снимает все подписки. */\n removeAllListeners(): void {\n this.#listeners.clear();\n }\n}\n\n/**\n * Сообщает об исключении из пользовательского обработчика события: пишет в логгер,\n * при его отсутствии — в консоль.\n *\n * Ошибка чужого обработчика не должна ронять библиотеку, но и пропадать молча ей нельзя:\n * иначе подписка, падающая на каждом событии, выглядит как её отсутствие.\n *\n * @param scope о чьих событиях речь — попадает в текст сообщения\n *\n * @internal\n */\nexport function reportListenerError(\n logger: Logger | undefined,\n scope: string,\n error: unknown,\n): void {\n const message = `Ошибка в обработчике события ${scope}`;\n if (logger) logger.error(message, error);\n else console.error(`[itd-api] ${message}`, error);\n}\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 { UserId } from '../types/models.js';\nimport type { AuthInput, CredentialsAuth } from '../types/options.js';\nimport type { AuthConfig } from './config.js';\nimport {\n AUTH_FLAG_COOKIE,\n type CookieJar,\n REFRESH_COOKIE,\n REFRESH_COOKIE_PATH,\n} from './cookies.js';\nimport { Emitter, reportListenerError } from './emitter.js';\nimport { ItdApiError, ItdAuthError, ItdConfigError } from './errors.js';\nimport { readTokenIdentity, readTokenSubject } from './jwt.js';\nimport type { RequestHandler } from './pipeline.js';\nimport { createDeviceId } from './runtime.js';\nimport { copySession, type ItdSession } from './storage.js';\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 * @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/** Ответ эндпоинтов, выдающих токен. */\ninterface TokenResponse {\n accessToken?: unknown;\n}\n\nfunction readAccessToken(payload: unknown): string | undefined {\n if (typeof payload !== 'object' || payload === null) return undefined;\n const token = (payload as TokenResponse).accessToken;\n return typeof token === 'string' && token.length > 0 ? token : undefined;\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\n/** Области аккаунта и конкретной сессии для локального состояния плагинов. */\nexport interface AuthIdentity {\n /** Идентификатор пользователя; отсутствует у непрозрачного или повреждённого токена. */\n userId?: UserId | undefined;\n /** Идентификатор серверной сессии; отсутствует у непрозрачного или повреждённого токена. */\n sessionId?: string | undefined;\n}\n\ninterface AuthManagerHooks {\n /** Вызывается синхронно перед заменой владельца авторизации. */\n onAccountChange?: (() => void) | undefined;\n}\n\n/**\n * Хранит сессию и продлевает её.\n *\n * Главное здесь — **дедупликация обновления**. Когда десять параллельных запросов\n * одновременно получают `401`, обновление должно произойти один раз, а остальные обязаны\n * дождаться его результата. Иначе сервер увидит десять параллельных `refresh`, и все,\n * кроме первого, скорее всего получат отказ по уже использованному токену.\n */\nexport class AuthManager {\n readonly #config: AuthConfig;\n readonly #send: RequestHandler;\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: AuthConfig,\n send: RequestHandler,\n jar: CookieJar,\n hooks: AuthManagerHooks = {},\n ) {\n this.#config = config;\n this.#send = send;\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 /**\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 /** Меняет fallback и завершает realtime, только если фактически сменился аккаунт. */\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 async getAuthHeaders(): Promise<Record<string, string>> {\n const token = await this.getAccessToken();\n return token ? { Authorization: `Bearer ${token}` } : {};\n }\n\n /**\n * Идентификатор устройства для заголовка `X-Device-Id`.\n *\n * Заводится один раз и сохраняется в сессии, чтобы пережить перезапуск процесса:\n * сервер связывает с ним запись в списке сессий, и плавающее значение плодило бы\n * по новой сессии на каждый старт.\n */\n getDeviceId(): 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 getAccessToken(): 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 // Служебный запрос идёт через плагины и повторы, но мимо очереди и слоя авторизации:\n // обновление порождается изнутри запроса, который держит слот в очереди и ждёт его результата.\n const payload = await this.#send({\n method: 'POST',\n path: AUTH_PATHS.refresh,\n skipQueue: true,\n skipAuth: true,\n skipAuthRefresh: true,\n // Тела нет намеренно: сервер читает refresh-токен только из cookie — см.\n // #seedRefreshCookie. По той же причине не нужен и устаревший Bearer.\n });\n\n const accessToken = readAccessToken(payload);\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 // Через служебный auth-конвейер: плагины и повторы сохраняются, очередь и слой\n // авторизации обходятся, чтобы не зависнуть на запросе, который ждёт токен.\n const payload = await this.#send({\n method: 'POST',\n path: AUTH_PATHS.signIn,\n body: { email: credentials.email, password: credentials.password, turnstileToken },\n skipQueue: true,\n skipAuth: true,\n skipAuthRefresh: true,\n });\n\n const accessToken = readAccessToken(payload);\n\n if (!accessToken) {\n // Сервер запросил подтверждение по коду — автоматически это не пройти.\n throw new ItdConfigError(\n 'Вход по email и паролю требует подтверждения кодом из письма. Автоматический вход ' +\n 'невозможен: воспользуйтесь itd.auth.signInWithOtp() и передайте полученный ' +\n 'accessToken в конфигурацию клиента.',\n );\n }\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","/**\n * Часы, которыми клиент измеряет время и планирует отложенную работу.\n *\n * Своя реализация нужна прежде всего в тестах: она позволяет проверять тайм-ауты,\n * повторы и переподключение без ожидания в реальном времени.\n */\nexport interface ItdClock {\n /** Текущее время в миллисекундах с начала эпохи Unix. */\n now(): number;\n /** Планирует вызов после завершения текущего стека и возвращает функцию отмены. */\n schedule(callback: () => void, delay: number): () => void;\n}\n\n/** Системные часы, используемые клиентом по умолчанию. */\nexport const systemClock: ItdClock = Object.freeze({\n now: () => Date.now(),\n schedule(callback: () => void, delay: number): () => void {\n const timer = setTimeout(callback, delay);\n return () => clearTimeout(timer);\n },\n});\n","import { ItdConfigError } from './errors.js';\n\n/** Значение параметра запроса. `undefined` и `null` в строку не попадают. */\nexport type QueryValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | readonly (string | number | boolean)[];\n\n/** Параметры строки запроса. */\nexport type QueryParams = Record<string, QueryValue>;\n\n/**\n * Собирает строку запроса.\n *\n * Правила:\n * - `undefined` и `null` пропускаются целиком — не нужно чистить объект перед вызовом;\n * - `boolean` превращается в `true` / `false`;\n * - массив повторяет ключ (`ids=1&ids=2`).\n *\n * @returns строка вида `?a=1&b=2` либо пустая строка, если параметров нет\n *\n * @example\n * ```ts\n * buildQuery({ tab: 'popular', limit: 20, cursor: undefined });\n * // '?tab=popular&limit=20'\n * ```\n */\nexport function buildQuery(params?: QueryParams): string {\n if (!params) return '';\n\n const search = new URLSearchParams();\n\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item === undefined || item === null) continue;\n search.append(key, String(item));\n }\n continue;\n }\n\n search.append(key, String(value));\n }\n\n const query = search.toString();\n return query ? `?${query}` : '';\n}\n\n/**\n * Кодирует значение для подстановки в путь.\n *\n * Нужно для сегментов, которые могут содержать что угодно, — прежде всего хэштегов\n * в `/api/hashtags/{tag}/posts`.\n *\n * @throws {ItdConfigError} если значение пустое\n */\nexport function encodePathSegment(value: string, name = 'параметр пути'): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new ItdConfigError(\n `${name} должен быть непустой строкой, получено: ${JSON.stringify(value)}`,\n );\n }\n return encodeURIComponent(value);\n}\n\n/**\n * Склеивает базовый URL и путь.\n *\n * Завершающий слэш пути сохраняется: он значим для `/api/notifications/`\n * и `/api/v1/subscription/`, без него сервер отвечает ошибкой.\n *\n * @example\n * ```ts\n * joinUrl('https://xn--d1ah4a.com/', '/api/notifications/');\n * // 'https://xn--d1ah4a.com/api/notifications/'\n * ```\n */\nexport function joinUrl(baseUrl: string, path: string): string {\n const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n const suffix = path.startsWith('/') ? path : `/${path}`;\n return `${base}${suffix}`;\n}\n\n/** Приводит базовый URL к каноничному виду и проверяет, что он вообще похож на URL. */\nexport function normalizeBaseUrl(baseUrl: string): string {\n if (typeof baseUrl !== 'string' || baseUrl.trim() === '') {\n throw new ItdConfigError(\n `baseUrl должен быть непустой строкой с абсолютным URL, получено: ${JSON.stringify(baseUrl)}`,\n );\n }\n\n let parsed: URL;\n try {\n parsed = new URL(baseUrl);\n } catch {\n throw new ItdConfigError(\n `baseUrl должен быть абсолютным URL, получено: ${JSON.stringify(baseUrl)}`,\n );\n }\n\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new ItdConfigError(\n `baseUrl должен использовать http или https, получено: ${parsed.protocol}`,\n );\n }\n\n if (parsed.username || parsed.password || parsed.search || parsed.hash) {\n throw new ItdConfigError(\n 'baseUrl не должен содержать логин, пароль, query-параметры или fragment',\n );\n }\n\n return parsed.origin + (parsed.pathname === '/' ? '' : parsed.pathname.replace(/\\/$/, ''));\n}\n","// Этот файл создаётся автоматически: scripts/sync-version.mjs.\n// Менять вручную не нужно — значение берётся из package.json при `npm version` и сборке.\n\n/** Версия библиотеки. Попадает в `User-Agent`. */\nexport const LIBRARY_VERSION = '0.3.0';\n","import type { AuthInput, ClientHooks, ItdClientOptions, Logger } from '../types/options.js';\nimport { type ItdClock, systemClock } from './clock.js';\nimport { ItdConfigError } from './errors.js';\nimport { RuntimeMode, resolveFetch, shouldSendCredentials, shouldUseCookieJar } from './runtime.js';\nimport type { ServiceDefinition } from './services.js';\nimport { MemoryTokenStorage, type TokenStorage } from './storage.js';\nimport { normalizeBaseUrl } from './url.js';\nimport { LIBRARY_VERSION } from './version.js';\n\n/** Базовый URL API итд.com. Домен записан в punycode: `итд.com`. */\nexport const DEFAULT_BASE_URL = 'https://xn--d1ah4a.com';\n\n/** Базовый URL страницы статуса. Домен записан в punycode: `статус.итд.com`. */\nexport const DEFAULT_STATUS_BASE_URL = 'https://xn--80a7abcbg.xn--d1ah4a.com';\n\n/** Имя встроенного сервиса статуса. */\nexport const STATUS_SERVICE = 'status';\n\n/** Сервисы, зарегистрированные у любого клиента. */\nexport const BUILT_IN_SERVICES: readonly ServiceDefinition[] = Object.freeze([\n Object.freeze({ name: STATUS_SERVICE, baseUrl: DEFAULT_STATUS_BASE_URL, auth: false }),\n]);\n\n/** Таймаут запроса по умолчанию — 30 секунд. */\nexport const DEFAULT_TIMEOUT = 30_000;\n\n// Значение живёт в отдельном модуле, который порождается из package.json скриптом\n// scripts/sync-version.mjs: две записанные вручную версии рано или поздно разъезжаются.\nexport { LIBRARY_VERSION } from './version.js';\n\n/**\n * `User-Agent` по умолчанию.\n *\n * Сайт стоит за DDoS-Guard, и запросы вовсе без `User-Agent` (так делает `fetch` в Node)\n * имеют шанс не пройти фильтр. Префикс `Mozilla/5.0` — дань традиции таких фильтров,\n * дальше идёт честное имя библиотеки: подделываться под браузер она не должна.\n *\n * В браузере заголовок не выставляется — `User-Agent` там запрещён к изменению, и среда\n * молча его игнорирует.\n */\nexport const DEFAULT_USER_AGENT = `Mozilla/5.0 (compatible; itd-api/${LIBRARY_VERSION}; +https://github.com/KiowDev/itd-api)`;\n\n/**\n * Настройки повторов со всеми значениями по умолчанию.\n *\n * Поля перечислены явно, а не через `Required<RetryOptions>`: тот снимает необязательность,\n * но оставляет `| undefined` в типе значения, раз оно указано в исходном интерфейсе.\n */\nexport interface ResolvedRetryOptions {\n attempts: number;\n baseDelay: number;\n maxDelay: number;\n jitter: number;\n retryWrites: boolean;\n shouldRetry: ((error: unknown, attempt: number) => boolean) | undefined;\n}\n\n/**\n * Паузы перед повторами при ответе `429`.\n *\n * Сервер итд.com не присылает `Retry-After` и не сообщает время сброса окна, поэтому\n * паузу приходится подбирать лестницей: от секунды, если окно почти истекло,\n * до полутора минут, если лимит исчерпан всерьёз.\n */\nexport const DEFAULT_RATE_LIMIT_DELAYS = Object.freeze([1000, 5000, 30_000, 60_000, 90_000]);\n\n/** Настройки очереди со всеми значениями по умолчанию. */\nexport interface ResolvedRateLimitOptions {\n concurrency: number;\n rps: number | undefined;\n retryDelays: readonly number[];\n respectHeaders: boolean;\n}\n\n/**\n * Срез конфигурации, нужный слою авторизации.\n *\n * Выделен явно, чтобы {@link AuthManager} не получал целиком `ResolvedConfig` со всеми\n * настройками транспорта, повторов и очереди, к которым он отношения не имеет.\n * `ResolvedConfig` содержит все эти поля, поэтому подходит везде, где ждут `AuthConfig`.\n */\nexport interface AuthConfig {\n baseUrl: string;\n clock: ItdClock;\n auth: AuthInput | undefined;\n storage: TokenStorage;\n useCookieJar: boolean;\n deviceId: string | undefined;\n reloginOnRefreshFailure: boolean;\n logger: Logger | undefined;\n}\n\n/** Конфигурация клиента после подстановки значений по умолчанию и проверок. */\nexport interface ResolvedConfig extends AuthConfig {\n /** Сервисы из опций клиента. Встроенные сюда не входят. */\n services: ServiceDefinition[];\n autoRefresh: boolean;\n fetch: typeof fetch;\n timeout: number;\n retry: ResolvedRetryOptions | undefined;\n rateLimit: ResolvedRateLimitOptions | undefined;\n hooks: ClientHooks;\n headers: Record<string, string>;\n /** Значение заголовка `User-Agent`. `undefined` — заголовок не выставляется. */\n userAgent: string | undefined;\n mode: RuntimeMode;\n /** Отправлять ли `credentials: 'include'` (в браузере). */\n sendCredentials: boolean;\n}\n\nfunction requirePositive(value: number, name: string): number {\n if (!Number.isFinite(value) || value < 0) {\n throw new ItdConfigError(`${name} должен быть неотрицательным числом, получено: ${value}`);\n }\n return value;\n}\n\nfunction isRecord(value: unknown): boolean {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction requireOptionalBoolean(value: unknown, name: string): void {\n if (value !== undefined && typeof value !== 'boolean') {\n throw new ItdConfigError(`${name} должен быть boolean`);\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\nfunction resolveHeaders(headers: Record<string, string> | undefined): Record<string, string> {\n if (headers === undefined) return {};\n if (!isRecord(headers)) throw new ItdConfigError('headers должен быть объектом строк');\n\n for (const [name, value] of Object.entries(headers)) {\n if (typeof value !== 'string') {\n throw new ItdConfigError(`headers.${name} должен быть строкой`);\n }\n }\n return { ...headers };\n}\n\nfunction resolveHooks(hooks: ClientHooks | undefined): ClientHooks {\n if (hooks === undefined) return {};\n if (!isRecord(hooks)) throw new ItdConfigError('hooks должен быть объектом');\n\n for (const name of ['onRequest', 'onResponse', 'onError', 'onRetry'] as const) {\n if (hooks[name] !== undefined && typeof hooks[name] !== 'function') {\n throw new ItdConfigError(`hooks.${name} должен быть функцией`);\n }\n }\n return { ...hooks };\n}\n\nfunction resolveLogger(logger: ItdClientOptions['logger']): Logger | undefined {\n if (logger === undefined || logger === false) return undefined;\n if (logger === true) return consoleLogger();\n if (!isRecord(logger)) throw new ItdConfigError('logger должен быть boolean или объектом Logger');\n\n for (const method of ['debug', 'info', 'warn', 'error'] as const) {\n if (typeof logger[method] !== 'function') {\n throw new ItdConfigError(`logger.${method} должен быть функцией`);\n }\n }\n return logger;\n}\n\n/** Логгер поверх `console` — включается опцией `logger: true`. */\nfunction consoleLogger(): Logger {\n return {\n debug: (message, ...args) => console.debug(`[itd-api] ${message}`, ...args),\n info: (message, ...args) => console.info(`[itd-api] ${message}`, ...args),\n warn: (message, ...args) => console.warn(`[itd-api] ${message}`, ...args),\n error: (message, ...args) => console.error(`[itd-api] ${message}`, ...args),\n };\n}\n\n/**\n * Приводит настройки повторов к полному виду.\n *\n * Вызывается и при создании клиента (для глобальной настройки), и на каждый запрос,\n * у которого задан свой `retry`. Возвращает `undefined`, когда повторять не нужно\n * (`retry: false` или единственная попытка).\n *\n * @throws {ItdConfigError} при некорректных значениях\n */\nexport function resolveRetry(retry: ItdClientOptions['retry']): ResolvedRetryOptions | undefined {\n if (retry === false) return undefined;\n if (retry !== undefined && !isRecord(retry)) {\n throw new ItdConfigError('retry должен быть объектом или false');\n }\n\n const options = retry ?? {};\n const attempts = options.attempts ?? 3;\n\n if (!Number.isInteger(attempts) || attempts < 1) {\n throw new ItdConfigError(`retry.attempts должен быть целым числом от 1, получено: ${attempts}`);\n }\n\n const jitter = options.jitter ?? 0.3;\n if (!Number.isFinite(jitter) || jitter < 0 || jitter > 1) {\n throw new ItdConfigError(`retry.jitter должен быть в диапазоне 0…1, получено: ${jitter}`);\n }\n requireOptionalBoolean(options.retryWrites, 'retry.retryWrites');\n if (options.shouldRetry !== undefined && typeof options.shouldRetry !== 'function') {\n throw new ItdConfigError('retry.shouldRetry должен быть функцией');\n }\n\n const baseDelay = requirePositive(options.baseDelay ?? 500, 'retry.baseDelay');\n const maxDelay = requirePositive(options.maxDelay ?? 30_000, 'retry.maxDelay');\n\n // Одна попытка означает отсутствие повторов — очередь ретраев можно не поднимать.\n if (attempts === 1) return undefined;\n\n return {\n attempts,\n baseDelay,\n maxDelay,\n jitter,\n retryWrites: options.retryWrites ?? false,\n shouldRetry: options.shouldRetry,\n };\n}\n\n/**\n * Приводит настройки очереди к полному виду. `undefined` — очередь не нужна.\n *\n * Кроме создания клиента вызывается ещё из {@link ItdAccounts}: общая на всех аккаунтов\n * очередь заводится из тех же опций и с теми же проверками.\n *\n * @throws {ItdConfigError} при некорректных значениях\n *\n * @internal\n */\nexport function resolveRateLimit(\n rateLimit: ItdClientOptions['rateLimit'],\n): ResolvedRateLimitOptions | undefined {\n if (rateLimit === false) return undefined;\n if (rateLimit !== undefined && !isRecord(rateLimit)) {\n throw new ItdConfigError('rateLimit должен быть объектом или false');\n }\n\n const defaults = {\n concurrency: 6,\n rps: undefined,\n retryDelays: DEFAULT_RATE_LIMIT_DELAYS,\n respectHeaders: true,\n };\n if (!rateLimit) return defaults;\n\n const concurrency = rateLimit.concurrency ?? 6;\n if (!Number.isInteger(concurrency) || concurrency < 1) {\n throw new ItdConfigError(\n `rateLimit.concurrency должен быть целым числом от 1, получено: ${concurrency}`,\n );\n }\n\n if (rateLimit.rps !== undefined && (!Number.isFinite(rateLimit.rps) || rateLimit.rps <= 0)) {\n throw new ItdConfigError(\n `rateLimit.rps должен быть положительным числом, получено: ${rateLimit.rps}`,\n );\n }\n\n const retryDelays = rateLimit.retryDelays ?? defaults.retryDelays;\n if (!Array.isArray(retryDelays)) {\n throw new ItdConfigError('rateLimit.retryDelays должен быть массивом чисел');\n }\n for (const delay of retryDelays) requirePositive(delay, 'rateLimit.retryDelays');\n requireOptionalBoolean(rateLimit.respectHeaders, 'rateLimit.respectHeaders');\n\n return {\n concurrency,\n rps: rateLimit.rps,\n retryDelays: [...retryDelays],\n respectHeaders: rateLimit.respectHeaders ?? true,\n };\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\n/**\n * Разворачивает запись сервисов из опций в определения: имя берётся из ключа, строка\n * означает один только базовый URL. URL проверяется при регистрации сервиса.\n */\nfunction resolveServices(services: ItdClientOptions['services']): ServiceDefinition[] {\n if (services === undefined) return [];\n if (!isRecord(services)) throw new ItdConfigError('services должен быть объектом');\n\n return Object.entries(services).map(([name, value]) => {\n if (typeof value === 'string') return { name, baseUrl: value };\n if (!isRecord(value)) {\n throw new ItdConfigError(`services.${name} должен быть URL или объектом сервиса`);\n }\n return { ...value, name } as ServiceDefinition;\n });\n}\n\n/**\n * Приводит пользовательские опции к полной конфигурации.\n *\n * Все проверки выполняются здесь, до единого сетевого запроса: неверная настройка должна\n * проявляться при создании клиента, а не через полчаса работы бота.\n *\n * @throws {ItdConfigError} при некорректных значениях\n */\nexport function resolveConfig(options: ItdClientOptions = {}): ResolvedConfig {\n if (!isRecord(options)) throw new ItdConfigError('опции клиента должны быть объектом');\n\n const mode: RuntimeMode = options.mode ?? RuntimeMode.Auto;\n\n if (!Object.values(RuntimeMode).includes(mode)) {\n throw new ItdConfigError(\n `mode должен быть одним из ${Object.values(RuntimeMode).join(', ')}, получено: ${mode}`,\n );\n }\n\n const timeout = requirePositive(options.timeout ?? DEFAULT_TIMEOUT, 'timeout');\n\n if (\n options.clock !== undefined &&\n (typeof options.clock !== 'object' ||\n options.clock === null ||\n typeof options.clock.now !== 'function' ||\n typeof options.clock.schedule !== 'function')\n ) {\n throw new ItdConfigError('clock должен предоставлять методы now() и schedule()');\n }\n requireOptionalBoolean(options.autoRefresh, 'autoRefresh');\n requireOptionalBoolean(options.reloginOnRefreshFailure, 'reloginOnRefreshFailure');\n\n if (\n options.userAgent !== undefined &&\n options.userAgent !== false &&\n typeof options.userAgent !== 'string'\n ) {\n throw new ItdConfigError('userAgent должен быть строкой или false');\n }\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 baseUrl: normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL),\n services: resolveServices(options.services),\n auth: validateAuth(options.auth),\n storage: resolveStorage(options.storage),\n autoRefresh: options.autoRefresh ?? true,\n reloginOnRefreshFailure: options.reloginOnRefreshFailure ?? true,\n fetch: resolveFetch(options.fetch),\n clock: options.clock ?? systemClock,\n timeout,\n retry: resolveRetry(options.retry),\n rateLimit: resolveRateLimit(options.rateLimit),\n hooks: resolveHooks(options.hooks),\n logger: resolveLogger(options.logger),\n headers: resolveHeaders(options.headers),\n deviceId: options.deviceId,\n // `false` — способ не слать заголовок вовсе; строка заменяет умолчание.\n userAgent: options.userAgent === false ? undefined : (options.userAgent ?? DEFAULT_USER_AGENT),\n mode,\n useCookieJar: shouldUseCookieJar(mode),\n sendCredentials: shouldSendCredentials(mode),\n };\n}\n","import type { PipelineRequest, RequestHandler } from './pipeline.js';\nimport type { PluginRegistry } from './plugins.js';\n\n/** Что нужно фасаду для работы. */\nexport interface HttpClientDeps {\n /** Готовый обработчик — вся цепочка слоёв поверх транспорта. */\n handler: RequestHandler;\n /** Реестр плагинов: у него ресурсы спрашивают имена заявленных опций. */\n plugins: PluginRegistry;\n baseUrl: string;\n}\n\n/**\n * Точка входа ресурсов в конвейер запросов.\n *\n * Принимает готовый обработчик — цепочку слоёв поверх транспорта, собранную\n * в {@link ItdClient}, — и отдаёт ресурсам метод `request` и имена опций плагинов.\n * О слоях и их порядке ресурсы не знают.\n */\nexport class HttpClient {\n readonly #handler: RequestHandler;\n readonly #plugins: PluginRegistry;\n readonly #baseUrl: string;\n\n constructor(deps: HttpClientDeps) {\n this.#handler = deps.handler;\n this.#plugins = deps.plugins;\n this.#baseUrl = deps.baseUrl;\n }\n\n /** Базовый URL, к которому обращается клиент. */\n get baseUrl(): string {\n return this.#baseUrl;\n }\n\n /**\n * Имена опций запроса, заявленные плагинами.\n *\n * Читается ресурсами: они переносят в транспорт только известные поля, а чужие,\n * если их никто не заявил, отсеивают.\n */\n get pluginOptionKeys(): ReadonlySet<string> {\n return this.#plugins.optionKeys;\n }\n\n /**\n * Выполняет запрос к API через собранный конвейер.\n *\n * @typeParam T ожидаемая форма ответа после снятия обёртки `{ data: … }`\n * @throws {ItdApiError} если сервер ответил статусом ≥ 400\n * @throws {ItdTimeoutError} если истёк таймаут\n * @throws {ItdAbortError} если запрос отменён через `signal`\n * @throws {ItdNetworkError} если запрос не дошёл до сервера\n */\n request<T = unknown>(options: PipelineRequest): Promise<T> {\n return this.#handler(options) as Promise<T>;\n }\n}\n","import type { RawRequestOptions } from '../types/options.js';\n\n/** Тело, заново подготовленное для одной транспортной попытки. */\nexport interface PreparedRequestBody {\n body: BodyInit;\n /** Заголовки тела, например multipart boundary. Пользовательские заголовки важнее. */\n headers?: Record<string, string> | undefined;\n /** Освобождает открытый файл или входящий HTTP-поток. */\n cleanup?: (() => void | Promise<void>) | undefined;\n}\n\n/** Контекст подготовки повторяемого тела. */\nexport interface RequestBodyContext {\n signal: AbortSignal;\n attempt: number;\n}\n\n/** Создаёт новое тело для каждой транспортной попытки. */\nexport type RequestBodyFactory = (\n context: RequestBodyContext,\n) => PreparedRequestBody | Promise<PreparedRequestBody>;\n\n/**\n * Описание запроса внутри конвейера.\n *\n * Отличается от публичного {@link RawRequestOptions} одним служебным полем: слои конвейера\n * должны уметь дописать заголовки так, чтобы пользовательские `headers` всё равно остались\n * важнее. Смешивать их в одном объекте нельзя — тогда слой авторизации перебивал бы\n * `Authorization`, заданный вызывающим кодом вручную.\n */\nexport interface PipelineRequest extends RawRequestOptions {\n /** Повторяемое тело. Используется внутренними ресурсами вместо `body`. @internal */\n bodyFactory?: RequestBodyFactory | undefined;\n /**\n * Разрешает повтор записи после сетевого сбоя. Тело должно быть повторяемым.\n *\n * @internal\n */\n retryNetworkWrite?: boolean | undefined;\n /**\n * Заголовки, добавленные слоями конвейера.\n *\n * Ставятся до пользовательских `headers` и потому могут быть ими переопределены.\n *\n * @internal\n */\n layerHeaders?: Record<string, string> | undefined;\n /**\n * Номер попытки, начиная с 1. Проставляет слой повторов, читают хуки.\n *\n * @internal\n */\n attempt?: number | undefined;\n}\n\n/** Обработчик запроса. Самый внутренний в цепочке — транспорт. */\nexport type RequestHandler = (request: PipelineRequest) => Promise<unknown>;\n\n/**\n * Слой конвейера запросов.\n *\n * Получает запрос и продолжение цепочки. Может изменить запрос, обработать ошибку\n * продолжения или вовсе не вызывать `next`.\n */\nexport type RequestMiddleware = (\n request: PipelineRequest,\n next: RequestHandler,\n) => Promise<unknown>;\n\n/**\n * Собирает слои в один обработчик.\n *\n * Первый слой оказывается самым внешним. Порядок задаётся в {@link ItdClient}.\n *\n * @example\n * ```ts\n * const handler = composePipeline([queue, plugins, retries, auth], transport.send);\n * ```\n */\nexport function composePipeline(\n middlewares: readonly RequestMiddleware[],\n final: RequestHandler,\n): RequestHandler {\n return middlewares.reduceRight<RequestHandler>(\n (next, middleware) => (request) => middleware(request, next),\n final,\n );\n}\n\n/** Добавляет заголовки слоя, не трогая пользовательские. */\nexport function withLayerHeaders(\n request: PipelineRequest,\n headers: Record<string, string>,\n): PipelineRequest {\n return { ...request, layerHeaders: { ...request.layerHeaders, ...headers } };\n}\n","import type { ClientHooks, Logger, RawRequestOptions } from '../types/options.js';\nimport type { AuthIdentity } from './auth.js';\nimport { ItdConfigError } from './errors.js';\n\n/**\n * Обёртка вокруг запроса.\n *\n * Получает описание запроса и продолжение цепочки. Может изменить запрос перед отправкой,\n * посмотреть и подменить разобранный ответ или вовсе не вызывать `next` и вернуть своё.\n *\n * @param request что уходит на сервер; изменять сам объект не нужно — передайте копию в `next`\n * @param next продолжение: либо следующая обёртка, либо настоящий запрос\n * @returns тело ответа в том виде, в каком его получит вызывающий код\n *\n * @example Дописать заголовок ко всем запросам\n * ```ts\n * const transformer: Transformer = (request, next) =>\n * next({ ...request, headers: { ...request.headers, 'X-Trace': trace() } });\n * ```\n */\nexport type Transformer = (\n request: RawRequestOptions,\n next: (request: RawRequestOptions) => Promise<unknown>,\n) => Promise<unknown>;\n\n/** Освобождение ресурсов, заведённых плагином при установке. */\nexport type PluginTeardown = () => void | Promise<void>;\n\n/** Что плагин получает при подключении. */\nexport interface PluginContext {\n /** Базовый URL клиента — например чтобы разобрать абсолютные ссылки из ответа. */\n baseUrl: string;\n /** Отладочный вывод клиента, если он включён. */\n logger: Logger | undefined;\n /**\n * Непрозрачная fallback-область текущей авторизации.\n *\n * Нужна плагинам, которые обязаны безопасно изолировать непрозрачный токен. Для объединения\n * состояния копий одного аккаунта используйте {@link getAuthIdentity}.\n */\n getAuthScope?: (() => string) | undefined;\n /**\n * Загружает сессию и возвращает идентификаторы аккаунта и конкретной сессии из JWT.\n *\n * Предпочтительнее {@link getAuthScope} для состояния, которое должно объединяться между\n * несколькими экземплярами клиента одного аккаунта.\n */\n getAuthIdentity?: (() => Promise<AuthIdentity>) | undefined;\n /** Добавляет обёртку запроса. Подключённые раньше оказываются снаружи. */\n use(transformer: Transformer): void;\n /**\n * Добавляет перехватчики отдельных сетевых попыток.\n *\n * В отличие от {@link use}, они видят каждый retry и сырой `Response` до чтения тела.\n * Несколько наборов хуков одного плагина вызываются в порядке регистрации.\n */\n useHooks(hooks: ClientHooks): void;\n}\n\n/**\n * Плагин клиента.\n *\n * Подключается через `itd.use(plugin)` и работает на уровне транспорта: видит запрос\n * до отправки и разобранный ответ. Библиотека не знает, что именно делает плагин, —\n * ей достаточно списка обёрток и имён опций, которые он читает.\n *\n * @example\n * ```ts\n * const logging: ItdPlugin = {\n * name: 'logging',\n * install({ use, logger }) {\n * use(async (request, next) => {\n * logger?.info(`${request.method} ${request.path}`);\n * return next(request);\n * });\n * },\n * };\n *\n * itd.use(logging);\n * ```\n */\nexport interface ItdPlugin {\n /** Имя плагина. Должно быть уникальным: повторное подключение — ошибка. */\n name: string;\n /**\n * Имена опций запроса, которые плагин читает у методов ресурсов.\n *\n * Библиотека этих опций не понимает и ничего с ними не делает — только доносит\n * от вызова метода до обёртки нетронутыми. Без такого списка чужие поля отсеиваются,\n * чтобы случайная опечатка в параметрах не уезжала на сервер.\n *\n * Имена полей самого запроса (`path`, `body`, `headers`, `signal` и прочие из\n * `RawRequestOptions`) заявить нельзя: подключение такого плагина завершится ошибкой.\n *\n * Типы для них плагин объявляет сам, дополняя `RequestOptions`:\n * ```ts\n * declare module 'itd-api' {\n * interface RequestOptions { encrypt?: string | undefined }\n * }\n * ```\n */\n optionKeys?: readonly string[];\n /** Плагины, которые обязаны быть подключены раньше этого. */\n requires?: readonly string[];\n /** Несовместимые плагины. Достаточно объявить конфликт с одной стороны. */\n conflicts?: readonly string[];\n /** Имена плагинов, снаружи которых должна стоять эта обёртка. */\n before?: readonly string[];\n /** Имена плагинов, внутри которых должна стоять эта обёртка. */\n after?: readonly string[];\n /**\n * Устанавливает плагин.\n *\n * Может вернуть функцию освобождения ресурсов. Она вызывается при `unuse()` или\n * окончательном `dispose()` клиента и может быть асинхронной.\n */\n install(context: PluginContext): unknown;\n}\n\n/** Пустой набор — отдаётся, пока плагинов нет, чтобы не заводить объект на каждый запрос. */\nconst NO_KEYS: ReadonlySet<string> = new Set<string>();\n\n/**\n * Имена, которые плагин заявить не может.\n *\n * Заявленные опции ресурсы переносят в описание запроса поверх собранных полей, поэтому\n * имя из {@link RawRequestOptions} подменило бы путь, тело или заголовки любого вызова.\n */\nconst RESERVED_OPTION_KEYS: ReadonlySet<string> = new Set([\n 'signal',\n 'timeout',\n 'headers',\n 'retry',\n 'method',\n 'path',\n 'service',\n 'baseUrl',\n 'query',\n 'body',\n 'skipAuth',\n 'skipAuthRefresh',\n 'skipQueue',\n 'raw',\n]);\n\nconst RELATION_FIELDS = ['requires', 'conflicts', 'before', 'after'] as const;\nconst HOOK_FIELDS = ['onRequest', 'onResponse', 'onError', 'onRetry'] as const;\n\nfunction validateNameList(plugin: ItdPlugin, field: (typeof RELATION_FIELDS)[number]): void {\n const values = plugin[field];\n if (values === undefined) return;\n if (!Array.isArray(values)) {\n throw new ItdConfigError(`плагин «${plugin.name}»: ${field} должен быть массивом`);\n }\n\n const seen = new Set<string>();\n for (const value of values as readonly unknown[]) {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new ItdConfigError(`плагин «${plugin.name}»: ${field} содержит пустое имя плагина`);\n }\n if (value === plugin.name) {\n throw new ItdConfigError(`плагин «${plugin.name}» не может указать себя в ${field}`);\n }\n if (seen.has(value)) {\n throw new ItdConfigError(`плагин «${plugin.name}»: ${field} повторяет имя «${value}»`);\n }\n seen.add(value);\n }\n}\n\nfunction validateHooks(plugin: string, hooks: ClientHooks): void {\n if (typeof hooks !== 'object' || hooks === null) {\n throw new ItdConfigError(`плагин «${plugin}» передал в useHooks() не объект`);\n }\n for (const field of HOOK_FIELDS) {\n if (hooks[field] !== undefined && typeof hooks[field] !== 'function') {\n throw new ItdConfigError(`плагин «${plugin}»: useHooks().${field} должен быть функцией`);\n }\n }\n}\n\n/**\n * Проверяет описание плагина без его установки.\n *\n * Нужна не только {@link PluginRegistry}: контейнер аккаунтов обязан отклонять сломанный\n * плагин сразу, даже когда внутри ещё нет ни одного клиента, которому можно поручить\n * полноценную установку.\n *\n * @internal\n */\nexport function validatePluginDefinition(plugin: ItdPlugin): void {\n if (typeof plugin?.install !== 'function') {\n throw new ItdConfigError('Плагин должен быть объектом с методом install()');\n }\n\n const name = plugin.name;\n if (typeof name !== 'string' || name.trim() === '') {\n throw new ItdConfigError('У плагина должно быть непустое имя');\n }\n\n const keys = plugin.optionKeys ?? [];\n if (!Array.isArray(keys)) {\n throw new ItdConfigError(`плагин «${name}»: optionKeys должен быть массивом`);\n }\n for (const key of keys as readonly unknown[]) {\n if (typeof key !== 'string' || key.trim() === '') {\n throw new ItdConfigError(`Плагин «${name}» заявил пустое имя опции`);\n }\n if (RESERVED_OPTION_KEYS.has(key)) {\n throw new ItdConfigError(\n `Плагин «${name}» заявил опцию «${key}»: это поле запроса, имя занято. ` +\n `Занятые имена: ${[...RESERVED_OPTION_KEYS].join(', ')}`,\n );\n }\n }\n\n for (const field of RELATION_FIELDS) validateNameList(plugin, field);\n}\n\ninterface OrderedPlugin {\n plugin: ItdPlugin;\n sequence: number;\n}\n\nfunction addEdge(\n from: string,\n to: string,\n edges: Map<string, Set<string>>,\n indegree: Map<string, number>,\n): void {\n const targets = edges.get(from);\n if (!targets || targets.has(to)) return;\n targets.add(to);\n indegree.set(to, (indegree.get(to) ?? 0) + 1);\n}\n\n/**\n * Проверяет зависимости и возвращает плагины в порядке обёрток: внешний идёт раньше.\n *\n * @internal\n */\nexport function orderPluginDefinitions(plugins: readonly ItdPlugin[]): ItdPlugin[] {\n const entries: OrderedPlugin[] = [];\n const byName = new Map<string, OrderedPlugin>();\n\n for (const [sequence, plugin] of plugins.entries()) {\n validatePluginDefinition(plugin);\n if (byName.has(plugin.name)) {\n throw new ItdConfigError(`плагин «${plugin.name}» уже подключён`);\n }\n const entry = { plugin, sequence };\n entries.push(entry);\n byName.set(plugin.name, entry);\n }\n\n for (const { plugin } of entries) {\n for (const required of plugin.requires ?? []) {\n if (!byName.has(required)) {\n throw new ItdConfigError(`плагину «${plugin.name}» требуется плагин «${required}»`);\n }\n }\n\n for (const conflict of plugin.conflicts ?? []) {\n if (byName.has(conflict)) {\n throw new ItdConfigError(`плагин «${plugin.name}» несовместим с плагином «${conflict}»`);\n }\n }\n for (const { plugin: other } of entries) {\n if (other.conflicts?.includes(plugin.name)) {\n throw new ItdConfigError(`плагин «${plugin.name}» несовместим с плагином «${other.name}»`);\n }\n }\n }\n\n const edges = new Map<string, Set<string>>();\n const indegree = new Map<string, number>();\n for (const { plugin } of entries) {\n edges.set(plugin.name, new Set());\n indegree.set(plugin.name, 0);\n }\n\n for (const { plugin } of entries) {\n for (const required of plugin.requires ?? []) {\n addEdge(required, plugin.name, edges, indegree);\n }\n for (const target of plugin.before ?? []) {\n if (byName.has(target)) addEdge(plugin.name, target, edges, indegree);\n }\n for (const target of plugin.after ?? []) {\n if (byName.has(target)) addEdge(target, plugin.name, edges, indegree);\n }\n }\n\n const ready = entries.filter(({ plugin }) => indegree.get(plugin.name) === 0);\n ready.sort((a, b) => a.sequence - b.sequence);\n const ordered: ItdPlugin[] = [];\n\n while (ready.length > 0) {\n const current = ready.shift();\n if (!current) break;\n ordered.push(current.plugin);\n\n for (const target of edges.get(current.plugin.name) ?? []) {\n const next = (indegree.get(target) ?? 0) - 1;\n indegree.set(target, next);\n if (next === 0) {\n const entry = byName.get(target);\n if (entry) {\n ready.push(entry);\n ready.sort((a, b) => a.sequence - b.sequence);\n }\n }\n }\n }\n\n if (ordered.length !== entries.length) {\n const cycle = entries\n .filter(({ plugin }) => (indegree.get(plugin.name) ?? 0) > 0)\n .map(({ plugin }) => plugin.name);\n throw new ItdConfigError(`циклический порядок плагинов: ${cycle.join(' → ')}`);\n }\n\n return ordered;\n}\n\n/** Проверяет, можно ли удалить плагин, не нарушив обязательные зависимости. @internal */\nexport function assertPluginRemovable(plugins: readonly ItdPlugin[], name: string): void {\n const dependent = plugins.find((plugin) => plugin.requires?.includes(name));\n if (dependent) {\n throw new ItdConfigError(\n `нельзя отключить плагин «${name}»: от него зависит «${dependent.name}»`,\n );\n }\n}\n\ninterface InstalledPlugin {\n plugin: ItdPlugin;\n transformers: Transformer[];\n hooks: ClientHooks[];\n teardown: PluginTeardown | undefined;\n activeRequests: number;\n drain: Promise<void> | undefined;\n finishDrain: (() => void) | undefined;\n}\n\ntype HookName = keyof ClientHooks;\ntype HookContext<K extends HookName> = Parameters<NonNullable<ClientHooks[K]>>[0];\ntype HookDispatcher = <K extends HookName>(\n field: K,\n context: HookContext<K>,\n request: RawRequestOptions,\n) => Promise<void>;\n\nconst REQUEST_HOOK_DISPATCHERS = new WeakMap<ClientHooks, HookDispatcher>();\nconst PLUGIN_HOOK_SCOPE: unique symbol = Symbol('itd-api.plugin-hooks');\ntype ScopedRequest = RawRequestOptions & {\n [PLUGIN_HOOK_SCOPE]?: readonly ClientHooks[];\n};\n\n/**\n * Вызывает публичный хук, сохраняя привязанный к логическому запросу снимок плагинов.\n *\n * Обычные наборы хуков по-прежнему получают только публичный контекст. Дополнительный\n * аргумент используется исключительно внутренним составным набором PluginRegistry.\n *\n * @internal\n */\nexport async function dispatchRequestHook<K extends HookName>(\n hooks: ClientHooks,\n field: K,\n context: HookContext<K>,\n request: RawRequestOptions,\n): Promise<void> {\n const dispatcher = REQUEST_HOOK_DISPATCHERS.get(hooks);\n if (dispatcher) {\n await dispatcher(field, context, request);\n return;\n }\n\n const hook = hooks[field] as ((value: HookContext<K>) => void | Promise<void>) | undefined;\n await hook?.(context);\n}\n\n/**\n * Список подключённых плагинов и собранная из них цепочка обёрток.\n *\n * Живёт в клиенте, а работает в транспорте: {@link HttpClient} прогоняет через `run`\n * каждый запрос, если плагины есть.\n */\nexport class PluginRegistry {\n readonly #entries = new Map<string, InstalledPlugin>();\n readonly #optionKeys = new Set<string>();\n readonly #removing = new Set<string>();\n readonly #cleanups = new Set<Promise<void>>();\n #ordered: InstalledPlugin[] = [];\n\n /** Сколько плагинов подключено. */\n get size(): number {\n return this.#entries.size;\n }\n\n /** Имена опций активных плагинов. */\n get optionKeys(): ReadonlySet<string> {\n return this.#optionKeys.size === 0 ? NO_KEYS : this.#optionKeys;\n }\n\n /** Имена плагинов в фактическом порядке выполнения. */\n names(): string[] {\n return this.#ordered.map(({ plugin }) => plugin.name);\n }\n\n /** Подключён ли плагин с таким именем. */\n has(name: string): boolean {\n return this.#entries.has(name);\n }\n\n /** Проверяет добавление без вызова `install()`. @internal */\n assertCanAdd(plugin: ItdPlugin): void {\n validatePluginDefinition(plugin);\n if (this.#removing.has(plugin.name)) {\n throw new ItdConfigError(\n `плагин «${plugin.name}» ещё отключается; дождитесь завершения unuse() или dispose()`,\n );\n }\n orderPluginDefinitions([...this.#ordered.map((entry) => entry.plugin), plugin]);\n }\n\n /** Проверяет удаление без изменения реестра. @internal */\n assertCanRemove(name: string): void {\n const entry = this.#entries.get(name);\n if (!entry) return;\n assertPluginRemovable(\n this.#ordered.map((current) => current.plugin),\n name,\n );\n }\n\n /**\n * Подключает плагин.\n *\n * @throws {ItdConfigError} если плагин задан неверно, уже подключён, нарушает зависимости\n * или заявил занятое имя опции\n */\n add(plugin: ItdPlugin, context: Omit<PluginContext, 'use' | 'useHooks'>): void {\n this.assertCanAdd(plugin);\n const ordered = orderPluginDefinitions([...this.#ordered.map((entry) => entry.plugin), plugin]);\n\n const transformers: Transformer[] = [];\n const hooks: ClientHooks[] = [];\n const installed = plugin.install({\n ...context,\n use: (transformer) => {\n if (typeof transformer !== 'function') {\n throw new ItdConfigError(`плагин «${plugin.name}» передал в use() не функцию`);\n }\n transformers.push(transformer);\n },\n useHooks: (value) => {\n validateHooks(plugin.name, value);\n hooks.push({ ...value });\n },\n });\n\n // До Plugin API 2.0 возвращаемое значение игнорировалось, поэтому постороннее значение\n // остаётся допустимым ради совместимости. Функция теперь получает определённый смысл:\n // это teardown.\n const teardown = typeof installed === 'function' ? (installed as PluginTeardown) : undefined;\n\n this.#entries.set(plugin.name, {\n plugin,\n transformers,\n hooks,\n teardown,\n activeRequests: 0,\n drain: undefined,\n finishDrain: undefined,\n });\n this.#ordered = ordered.map((definition) => {\n const entry = this.#entries.get(definition.name);\n if (!entry) throw new ItdConfigError(`плагин «${definition.name}» не установлен`);\n return entry;\n });\n this.#rebuildOptionKeys();\n }\n\n /**\n * Отключает плагин и вызывает его функцию очистки.\n *\n * Новые запросы перестают видеть плагин сразу. Если его обёртка уже выполняется,\n * очистка дождётся завершения этого логического запроса.\n *\n * @returns `false`, если такого плагина не было\n */\n async remove(name: string): Promise<boolean> {\n const entry = this.#entries.get(name);\n if (!entry) return false;\n\n this.assertCanRemove(name);\n this.#entries.delete(name);\n this.#ordered = this.#ordered.filter((current) => current !== entry);\n this.#rebuildOptionKeys();\n this.#removing.add(name);\n\n const cleanup = this.#trackCleanup(\n (async () => {\n await this.#waitForDrain(entry);\n await entry.teardown?.();\n })(),\n );\n\n try {\n await cleanup;\n return true;\n } finally {\n this.#removing.delete(name);\n }\n }\n\n /**\n * Отключает все плагины окончательно.\n *\n * Очистка идёт изнутри наружу — в порядке, обратном выполнению обёрток.\n */\n async dispose(): Promise<void> {\n const entries = [...this.#ordered].reverse();\n const previousCleanups = [...this.#cleanups];\n this.#entries.clear();\n this.#ordered = [];\n this.#optionKeys.clear();\n for (const { plugin } of entries) this.#removing.add(plugin.name);\n\n const cleanup = this.#trackCleanup(\n (async () => {\n const errors: unknown[] = [];\n const previous = await Promise.allSettled(previousCleanups);\n for (const result of previous) {\n if (result.status === 'rejected') errors.push(result.reason);\n }\n\n for (const entry of entries) {\n try {\n await this.#waitForDrain(entry);\n await entry.teardown?.();\n } catch (error) {\n errors.push(error);\n } finally {\n this.#removing.delete(entry.plugin.name);\n }\n }\n if (errors.length > 0) {\n throw new AggregateError(errors, 'Не удалось освободить ресурсы плагинов');\n }\n })(),\n );\n\n await cleanup;\n }\n\n /**\n * Объединяет конструкторские хуки с хуками подключаемых плагинов.\n *\n * Возвращённый объект динамический: подключение и отключение плагина начинает действовать\n * со следующего логического запроса без пересоздания транспорта.\n */\n hooks(base: ClientHooks): ClientHooks {\n const hooks: ClientHooks = {};\n REQUEST_HOOK_DISPATCHERS.set(hooks, ((field, context, request) =>\n this.#runHook(field, context, request, base)) as HookDispatcher);\n return hooks;\n }\n\n /**\n * Прогоняет запрос через цепочку обёрток.\n *\n * Снимок цепочки берётся в начале: `unuse()` влияет на новые запросы, но не обрывает\n * уже выполняющийся посередине.\n *\n * @param execute настоящий запрос, вызывается самой внутренней обёрткой\n */\n async run(\n request: RawRequestOptions,\n execute: (request: RawRequestOptions) => Promise<unknown>,\n ): Promise<unknown> {\n const entries = [...this.#ordered];\n for (const entry of entries) entry.activeRequests += 1;\n const hookScope = entries.flatMap((entry) => entry.hooks);\n const scoped = (current: RawRequestOptions): ScopedRequest =>\n (current as ScopedRequest)[PLUGIN_HOOK_SCOPE] === hookScope\n ? (current as ScopedRequest)\n : { ...current, [PLUGIN_HOOK_SCOPE]: hookScope };\n\n const chain = entries\n .flatMap((entry) => entry.transformers)\n .reduceRight<(r: RawRequestOptions) => Promise<unknown>>(\n (next, transformer) => (current) =>\n transformer(scoped(current), (prepared) => next(scoped(prepared))),\n (current) => execute(scoped(current)),\n );\n\n try {\n return await chain(scoped(request));\n } finally {\n for (const entry of entries) {\n entry.activeRequests -= 1;\n if (entry.activeRequests === 0) {\n entry.finishDrain?.();\n entry.finishDrain = undefined;\n entry.drain = undefined;\n }\n }\n }\n }\n\n #rebuildOptionKeys(): void {\n this.#optionKeys.clear();\n for (const { plugin } of this.#ordered) {\n for (const key of plugin.optionKeys ?? []) this.#optionKeys.add(key);\n }\n }\n\n #waitForDrain(entry: InstalledPlugin): Promise<void> {\n if (entry.activeRequests === 0) return Promise.resolve();\n entry.drain ??= new Promise<void>((resolve) => {\n entry.finishDrain = resolve;\n });\n return entry.drain;\n }\n\n #trackCleanup(cleanup: Promise<void>): Promise<void> {\n this.#cleanups.add(cleanup);\n void cleanup.then(\n () => this.#cleanups.delete(cleanup),\n () => this.#cleanups.delete(cleanup),\n );\n return cleanup;\n }\n\n async #runHook<K extends HookName>(\n field: K,\n context: HookContext<K>,\n request: RawRequestOptions,\n base: ClientHooks,\n ): Promise<void> {\n const baseHook = base[field] as ((value: HookContext<K>) => void | Promise<void>) | undefined;\n await baseHook?.(context);\n\n const scope = (request as ScopedRequest)[PLUGIN_HOOK_SCOPE] ?? [];\n for (const hooks of scope) {\n const hook = hooks[field] as ((value: HookContext<K>) => void | Promise<void>) | undefined;\n await hook?.(context);\n }\n }\n}\n","import type { ResolvedRetryOptions } from './config.js';\nimport {\n ItdAbortError,\n ItdApiError,\n ItdFileError,\n ItdNetworkError,\n ItdTimeoutError,\n} from './errors.js';\n\n/**\n * Решает, повторять ли запрос.\n *\n * @returns пауза в миллисекундах перед следующей попыткой либо `undefined`, если повторять не нужно\n */\nexport type RetryScheduler = (\n error: unknown,\n attempt: number,\n method: string,\n retryNetworkWrite?: boolean,\n) => number | undefined;\n\n/**\n * Методы, повтор которых безопасен по определению.\n *\n * `DELETE` формально тоже идемпотентен, но его повтор после успеха вернёт `404`\n * и собьёт с толку — в список он не входит.\n */\nconst IDEMPOTENT_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);\n\n/**\n * Стоит ли повторять запрос после этой ошибки.\n *\n * Отдельно разобран `429`: он повторяется даже для запросов на запись, потому что\n * гарантирует, что запрос **не был обработан**. Обрыв сети и `5xx` такой гарантии не дают —\n * сервер мог успеть создать пост, — поэтому запись по умолчанию не повторяется.\n */\nfunction isRetryable(\n error: unknown,\n method: string,\n retryWrites: boolean,\n retryNetworkWrite: boolean,\n): boolean {\n // Отмену повторять нельзя ни при каких условиях: её попросил пользователь.\n if (error instanceof ItdAbortError) return false;\n\n const safeToRepeat = retryWrites || IDEMPOTENT_METHODS.has(method);\n\n if (error instanceof ItdApiError) {\n if (error.status === 429) return true;\n if (error.status >= 500) return safeToRepeat;\n return false;\n }\n\n if (error instanceof ItdNetworkError || error instanceof ItdTimeoutError) {\n return safeToRepeat || retryNetworkWrite;\n }\n\n if (error instanceof ItdFileError) {\n return error.retryable && (safeToRepeat || retryNetworkWrite);\n }\n\n return false;\n}\n\n/** Экспоненциальная пауза со случайным разбросом. */\nfunction backoffDelay(\n attempt: number,\n options: ResolvedRetryOptions,\n random: () => number,\n): number {\n const exponential = options.baseDelay * 2 ** (attempt - 1);\n const capped = Math.min(exponential, options.maxDelay);\n const spread = capped * options.jitter * (random() * 2 - 1);\n\n return Math.max(0, Math.round(capped + spread));\n}\n\n/**\n * Собирает планировщик повторов для транспорта.\n *\n * Поведение при `Retry-After`: пауза, названная сервером, соблюдается точно — она\n * авторитетнее нашего расчёта. Но если сервер просит ждать дольше, чем `maxDelay`,\n * повтор **не выполняется вовсе**: молча спать десять минут внутри вызова библиотека\n * не должна, лучше отдать {@link ItdRateLimitError} и дать решить вызывающему коду.\n *\n * @param options настройки повторов после подстановки значений по умолчанию\n * @param random источник случайности; подменяется в тестах ради предсказуемости\n *\n * @example\n * ```ts\n * const scheduler = createRetryScheduler(config.retry);\n * const delay = scheduler(error, 1, 'GET'); // 500 мс ± 30%\n * ```\n */\nexport function createRetryScheduler(\n options: ResolvedRetryOptions,\n random: () => number = Math.random,\n): RetryScheduler {\n return (error, attempt, method, retryNetworkWrite = false) => {\n if (attempt >= options.attempts) return undefined;\n\n if (options.shouldRetry) {\n return options.shouldRetry(error, attempt)\n ? backoffDelay(attempt, options, random)\n : undefined;\n }\n\n if (!isRetryable(error, method, options.retryWrites, retryNetworkWrite)) return undefined;\n\n if (error instanceof ItdApiError && error.retryAfter !== undefined) {\n return error.retryAfter > options.maxDelay ? undefined : error.retryAfter;\n }\n\n return backoffDelay(attempt, options, random);\n };\n}\n","import type { ClientHooks, Logger, RawRequestOptions, RequestOptions } from '../types/options.js';\nimport { type ItdClock, systemClock } from './clock.js';\nimport { type ResolvedRetryOptions, resolveRetry } from './config.js';\nimport { ItdAbortError, isItdApiError, isItdRateLimitError } from './errors.js';\nimport {\n type PipelineRequest,\n type RequestHandler,\n type RequestMiddleware,\n withLayerHeaders,\n} from './pipeline.js';\nimport { dispatchRequestHook, type PluginRegistry } from './plugins.js';\nimport { createRetryScheduler, type RetryScheduler } from './retry.js';\nimport type { ServiceRegistry } from './services.js';\nimport { normalizeBaseUrl } from './url.js';\n\n/** Ожидание повтора, которое уважает отмену запроса. */\nfunction sleep(clock: ItdClock, ms: number, signal?: AbortSignal): Promise<void> {\n if (!signal) return new Promise((resolve) => clock.schedule(resolve, ms));\n if (signal.aborted) {\n return Promise.reject(new ItdAbortError('Запрос отменён во время ожидания повтора'));\n }\n\n return new Promise((resolve, reject) => {\n const cancel = clock.schedule(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n cancel();\n reject(new ItdAbortError('Запрос отменён во время ожидания повтора'));\n };\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/**\n * Слой очереди: ограничение конкурентности и частоты.\n *\n * `skipQueue` пропускает запрос мимо очереди — так поступают служебные запросы, которые\n * порождены изнутри другого запроса и не могут ждать освободившегося слота.\n *\n * Очередь выбирается по запросу: у каждого сервиса платформы свой хост и свой лимит.\n */\nexport function createQueueMiddleware(\n schedule: <T>(request: PipelineRequest, task: () => Promise<T>) => Promise<T>,\n): RequestMiddleware {\n return (request, next) =>\n request.skipQueue ? next(request) : schedule(request, () => next(request));\n}\n\n/**\n * Слой плагинов.\n *\n * Стоит снаружи повторов и внутри очереди: плагин должен увидеть запрос и ответ по одному\n * разу, независимо от числа попыток, — иначе, например, текст поста зашифруется дважды.\n */\nexport function createPluginsMiddleware(plugins: PluginRegistry): RequestMiddleware {\n return (request, next) =>\n plugins.run(request, next as (request: RawRequestOptions) => Promise<unknown>);\n}\n\n/**\n * Слой сервисов.\n *\n * Запросу с полем `service` подставляет хост сервиса, его заголовки и `skipAuth`, если\n * сервис объявлен публичным. Заданный у запроса `baseUrl` не трогает.\n *\n * Стоит снаружи повторов и авторизации, чтобы выставленный здесь `skipAuth` был ей виден.\n */\nexport function createServicesMiddleware(registry: ServiceRegistry): RequestMiddleware {\n return async (request, next) => {\n const service = request.service === undefined ? undefined : registry.require(request.service);\n let prepared = request;\n\n if (request.baseUrl !== undefined) {\n const baseUrl = normalizeBaseUrl(request.baseUrl);\n if (baseUrl !== request.baseUrl) prepared = { ...prepared, baseUrl };\n\n // Разовый хост не наследует разрешение авторизации от сервиса с другим URL.\n // Явное `skipAuth: false` остаётся способом осознанно отправить токен наружу.\n const matchesService = service?.baseUrl === baseUrl;\n const mayAuthorize = matchesService\n ? service.auth !== false\n : registry.isPrimarySite(baseUrl);\n if (!mayAuthorize && prepared.skipAuth === undefined) {\n prepared = { ...prepared, skipAuth: true };\n }\n }\n\n if (!service) return next(prepared);\n\n if (prepared.baseUrl === undefined) prepared = { ...prepared, baseUrl: service.baseUrl };\n\n if (service.headers) prepared = withLayerHeaders(prepared, service.headers);\n if (service.auth === false && prepared.skipAuth === undefined) {\n prepared = { ...prepared, skipAuth: true };\n }\n\n return next(prepared);\n };\n}\n\n/** Что нужно слою авторизации. */\nexport interface AuthMiddlewareDeps {\n /** Заголовки авторизации для очередного запроса. Пустой объект, если токена нет. */\n getAuthHeaders: () => Promise<Record<string, string>> | Record<string, string>;\n /** Реакция на `401`. Возвращает `true`, если токен обновлён и повтор имеет смысл. */\n onUnauthorized: () => Promise<boolean>;\n /** Обновлять ли токен при `401` автоматически. */\n autoRefresh: boolean;\n}\n\nasync function applyAuth(\n request: PipelineRequest,\n deps: AuthMiddlewareDeps,\n): Promise<PipelineRequest> {\n if (request.skipAuth) return request;\n\n const headers = await deps.getAuthHeaders();\n return Object.keys(headers).length > 0 ? withLayerHeaders(request, headers) : request;\n}\n\n/**\n * Слой авторизации.\n *\n * Подставляет заголовок `Authorization` и обрабатывает `401`: обновляет токен и повторяет\n * запрос ровно один раз. Стоит внутри повторов, поэтому обычным попыткам он не виден —\n * они уже работают со свежим токеном.\n */\nexport function createAuthMiddleware(deps: AuthMiddlewareDeps): RequestMiddleware {\n return async (request, next) => {\n const authorized = await applyAuth(request, deps);\n\n try {\n return await next(authorized);\n } catch (error) {\n // Обновляем и повторяем ровно один раз, чтобы не зациклиться, если сервер\n // отдаёт 401 и на свежем токене.\n if (\n request.skipAuthRefresh ||\n !deps.autoRefresh ||\n !isItdApiError(error) ||\n error.status !== 401\n ) {\n throw error;\n }\n\n const refreshed = await deps.onUnauthorized();\n if (!refreshed) throw error;\n\n const retried = await applyAuth({ ...request, skipAuthRefresh: true }, deps);\n return next(retried);\n }\n };\n}\n\n/** Что нужно слою повторов. */\nexport interface RetryMiddlewareDeps {\n clock?: ItdClock;\n /** Глобальные настройки повторов. `undefined` — по умолчанию не повторять. */\n retry: ResolvedRetryOptions | undefined;\n /**\n * Паузы перед повторами при ответе `429`.\n *\n * Живут отдельно от `retry`: сервер не присылает `Retry-After`, и экспоненциальный откат\n * тут бесполезен — окно измеряется десятками секунд. Не зависят от `retry.attempts`.\n */\n rateLimitDelays: readonly number[];\n /**\n * Придерживает очередь запроса на паузу `429`. `undefined`, если очереди нет.\n *\n * Тормозится очередь того хоста, который ответил отказом: лимит у каждого свой.\n */\n pauseQueue: ((ms: number, request: PipelineRequest) => void) | undefined;\n hooks: ClientHooks;\n logger: Logger | undefined;\n buildUrl: (request: PipelineRequest) => string;\n}\n\n/**\n * Выбирает планировщик отката для конкретного запроса.\n *\n * `retry` у запроса переопределяет глобальную настройку: `false` выключает повторы,\n * объект задаёт свои. Обработка `429` от этого не зависит — она общая.\n */\nfunction resolveBackoff(\n retry: RequestOptions['retry'],\n global: RetryScheduler | undefined,\n): RetryScheduler | undefined {\n if (retry === undefined) return global;\n if (retry === false) return undefined;\n\n const resolved = resolveRetry(retry);\n return resolved ? createRetryScheduler(resolved) : undefined;\n}\n\n/**\n * Слой повторов.\n *\n * Ответ `429` обрабатывается отдельно от прочих ошибок лестницей пауз и с придержанием\n * всей очереди; сетевые сбои и `5xx` — экспоненциальным откатом. Настройка `retry`\n * у отдельного запроса имеет приоритет над глобальной.\n */\nexport function createRetryMiddleware(deps: RetryMiddlewareDeps): RequestMiddleware {\n const globalScheduler = deps.retry ? createRetryScheduler(deps.retry) : undefined;\n\n const nextDelay = (\n error: unknown,\n attempt: number,\n request: PipelineRequest,\n method: string,\n backoff: RetryScheduler | undefined,\n ): number | undefined => {\n if (isItdRateLimitError(error)) {\n // Пауза, названную сервером, соблюдаем точно; иначе берём очередной шаг лестницы.\n const wait = error.retryAfter ?? deps.rateLimitDelays[attempt - 1];\n if (wait === undefined) return undefined;\n\n deps.pauseQueue?.(wait, request);\n deps.logger?.debug(`лимит частоты, попытка ${attempt + 1} через ${wait} мс`);\n return wait;\n }\n\n return backoff?.(error, attempt, method, request.retryNetworkWrite ?? false);\n };\n\n return async (request, next) => {\n const method = request.method.toUpperCase();\n const backoff = resolveBackoff(request.retry, globalScheduler);\n\n for (let attempt = 1; ; attempt++) {\n try {\n return await next({ ...request, attempt });\n } catch (error) {\n const delay = nextDelay(error, attempt, request, method, backoff);\n if (delay === undefined) throw error;\n\n await dispatchRequestHook(\n deps.hooks,\n 'onRetry',\n {\n method,\n path: request.path,\n url: deps.buildUrl(request),\n // Умолчания транспорта добавляются после слоя повторов и сюда не входят.\n headers: new Headers({ ...request.layerHeaders, ...request.headers }),\n attempt,\n error,\n delay,\n },\n request,\n );\n\n deps.logger?.debug(\n `повтор ${method} ${request.path}, попытка ${attempt + 1} через ${delay} мс`,\n );\n\n await sleep(deps.clock ?? systemClock, delay, request.signal);\n }\n }\n };\n}\n\n/** Собирает обработчик из слоёв. Реэкспорт для удобства сборки в одном месте. */\nexport { composePipeline } from './pipeline.js';\nexport type { RequestHandler, RequestMiddleware };\n","import { type ItdClock, systemClock } from './clock.js';\nimport type { ResolvedRateLimitOptions } from './config.js';\nimport { ItdAbortError } from './errors.js';\n\n/** Задача, ожидающая своей очереди. */\ninterface QueuedTask {\n run: () => void;\n /** Снимает задачу, ещё не начавшую выполняться, — используется при остановке очереди. */\n cancel: (reason: unknown) => void;\n}\n\n/** Ошибка отмены запроса, который ещё не дошёл до транспорта. */\nfunction queueAbortError(): ItdAbortError {\n return new ItdAbortError('Запрос отменён во время ожидания очереди');\n}\n\n/**\n * Очередь запросов: ограничивает одновременность и частоту.\n *\n * Нужна прежде всего ботам: без неё цикл по сотне постов уходит в API одним залпом\n * и упирается в `RATE_LIMIT_EXCEEDED`.\n *\n * Частота выдерживается равномерным разносом стартов (`1000 / rps` между запросами),\n * а не окном со счётчиком: так нагрузка ровная, без всплеска в начале каждой секунды.\n *\n * @internal\n */\nexport class RequestQueue {\n readonly #concurrency: number;\n /** Минимальный промежуток между стартами, мс. `0` — без ограничения частоты. */\n readonly #minGap: number;\n\n readonly #waiting: QueuedTask[] = [];\n #active = 0;\n /** Момент, раньше которого следующий запрос стартовать не должен. */\n #nextSlot = 0;\n readonly #clock: ItdClock;\n #cancelTimer: (() => void) | undefined;\n\n constructor(options: ResolvedRateLimitOptions, clock: ItdClock = systemClock) {\n this.#concurrency = options.concurrency;\n this.#minGap = options.rps ? 1000 / options.rps : 0;\n this.#clock = clock;\n }\n\n /** Сколько задач выполняется прямо сейчас. */\n get active(): number {\n return this.#active;\n }\n\n /** Сколько задач ждёт очереди. */\n get pending(): number {\n return this.#waiting.length;\n }\n\n /**\n * Ставит задачу в очередь.\n *\n * @returns результат задачи; ошибка задачи пробрасывается без изменений\n */\n schedule<T>(task: () => Promise<T>, signal?: AbortSignal): Promise<T> {\n if (signal?.aborted) return Promise.reject(queueAbortError());\n\n return new Promise<T>((resolve, reject) => {\n let queued: QueuedTask;\n\n const detach = () => signal?.removeEventListener('abort', onAbort);\n const onAbort = () => {\n const index = this.#waiting.indexOf(queued);\n if (index < 0) return;\n\n this.#waiting.splice(index, 1);\n queued.cancel(queueAbortError());\n this.#drain();\n };\n\n queued = {\n run: () => {\n detach();\n this.#active += 1;\n\n // `task` обычно возвращает промис, но пользовательский middleware может бросить\n // синхронно. Нормализуем оба пути, чтобы слот всегда освободился.\n Promise.resolve()\n .then(task)\n .then(resolve, reject)\n .finally(() => {\n this.#active -= 1;\n this.#drain();\n });\n },\n cancel: (reason) => {\n detach();\n reject(reason);\n },\n };\n\n this.#waiting.push(queued);\n signal?.addEventListener('abort', onAbort, { once: true });\n\n // Защищает и от нестандартной реализации AbortSignal, которая могла перейти\n // в aborted между первой проверкой и установкой обработчика.\n if (signal?.aborted) onAbort();\n else this.#drain();\n });\n }\n\n /**\n * Останавливает очередь: снимает отложенную паузу и отклоняет ещё не начатые задачи\n * ошибкой `ItdAbortError`. Уже выполняющиеся задачи доводятся до конца.\n */\n stop(): void {\n if (this.#cancelTimer) {\n this.#cancelTimer();\n this.#cancelTimer = undefined;\n }\n this.#nextSlot = 0;\n\n const pending = this.#waiting.splice(0, this.#waiting.length);\n for (const task of pending) {\n task.cancel(new ItdAbortError('Клиент закрыт, запрос отменён'));\n }\n }\n\n /**\n * Придерживает всю очередь на заданное время.\n *\n * Вызывается при получении `429` с заголовком `Retry-After`: тормозить нужно все запросы,\n * а не только тот, который наткнулся на лимит, — иначе остальные продолжат добивать API.\n */\n pause(ms: number): void {\n if (ms <= 0) return;\n this.#nextSlot = Math.max(this.#nextSlot, this.#clock.now() + ms);\n }\n\n /** Запускает столько ожидающих задач, сколько позволяют ограничения. */\n #drain(): void {\n if (this.#waiting.length === 0) {\n // Последний ожидающий запрос мог быть отменён во время длинной паузы. Таймер больше\n // не нужен и не должен удерживать event loop процесса.\n if (this.#cancelTimer) {\n this.#cancelTimer();\n this.#cancelTimer = undefined;\n }\n return;\n }\n if (this.#active >= this.#concurrency) return;\n // Ждём уже запланированного пробуждения, чтобы не плодить таймеры.\n if (this.#cancelTimer) return;\n\n const now = this.#clock.now();\n\n if (this.#nextSlot > now) {\n this.#cancelTimer = this.#clock.schedule(() => {\n this.#cancelTimer = undefined;\n this.#drain();\n }, this.#nextSlot - now);\n return;\n }\n\n const next = this.#waiting.shift();\n if (!next) return;\n\n if (this.#minGap > 0) this.#nextSlot = now + this.#minGap;\n\n next.run();\n\n // Следующая задача может стартовать сразу, если позволяет конкурентность.\n this.#drain();\n }\n}\n\n/**\n * Очереди по хостам: основная и по одной на каждый сервис платформы.\n *\n * @internal\n */\nexport class RequestQueuePool {\n readonly #options: ResolvedRateLimitOptions;\n readonly #clock: ItdClock;\n readonly #main: RequestQueue;\n /** Очереди сервисов заводятся при первом запросе — обычно не нужна ни одна. */\n readonly #byService = new Map<string, RequestQueue>();\n\n constructor(options: ResolvedRateLimitOptions, clock: ItdClock = systemClock) {\n this.#options = options;\n this.#clock = clock;\n this.#main = new RequestQueue(options, clock);\n }\n\n /** Очередь хоста. */\n for(service: string | undefined): RequestQueue {\n if (service === undefined) return this.#main;\n\n let queue = this.#byService.get(service);\n if (!queue) {\n queue = new RequestQueue(this.#options, this.#clock);\n this.#byService.set(service, queue);\n }\n return queue;\n }\n\n /** Останавливает все очереди. */\n stop(): void {\n this.#main.stop();\n for (const queue of this.#byService.values()) queue.stop();\n }\n}\n","import { ItdConfigError } from './errors.js';\nimport { normalizeBaseUrl } from './url.js';\n\n/** Сервис платформы на отдельном домене. */\nexport interface ServiceDefinition {\n /** Имя, по которому запрос выбирает сервис: `{ service: 'status' }`. */\n name: string;\n /** Базовый URL сервиса. */\n baseUrl: string;\n /** Заголовки, добавляемые к каждому запросу сервиса. Заголовки вызова важнее. */\n headers?: Record<string, string> | undefined;\n /**\n * Слать ли заголовок авторизации.\n *\n * По умолчанию включено для основного хоста и его поддоменов. Для остальных хостов\n * авторизацию нужно разрешить явно: `auth: true`.\n */\n auth?: boolean | undefined;\n}\n\n/** Хост из URL. Пустая строка, если разобрать не удалось. */\nfunction hostOf(url: string): string {\n try {\n return new URL(url).hostname.toLowerCase();\n } catch {\n return '';\n }\n}\n\n/** Тот же хост либо его поддомен. */\nfunction isSameSite(primaryHost: string, host: string): boolean {\n if (!primaryHost || !host) return false;\n return host === primaryHost || host.endsWith(`.${primaryHost}`);\n}\n\n/**\n * Именованные сервисы клиента.\n *\n * @internal\n */\nexport class ServiceRegistry {\n readonly #services = new Map<string, ServiceDefinition>();\n /** Хост основного API. */\n readonly #primaryHost: string;\n\n /** @param primaryBaseUrl базовый URL клиента */\n constructor(primaryBaseUrl?: string) {\n this.#primaryHost = primaryBaseUrl ? hostOf(primaryBaseUrl) : '';\n }\n\n /**\n * Регистрирует сервис. Имя очищается от краевых пробелов, базовый URL приводится\n * к каноничному виду, а незаданный `auth` выводится из хоста.\n *\n * @throws {ItdConfigError} если имя пустое, имя занято или `baseUrl` не абсолютный URL\n */\n define(definition: ServiceDefinition): void {\n const raw = definition?.name;\n if (typeof raw !== 'string' || raw.trim() === '') {\n throw new ItdConfigError('У сервиса должно быть непустое имя');\n }\n\n const name = raw.trim();\n if (this.#services.has(name)) {\n throw new ItdConfigError(`Сервис «${name}» уже зарегистрирован и не может быть заменён`);\n }\n\n const baseUrl = normalizeBaseUrl(definition.baseUrl);\n if (definition.auth !== undefined && typeof definition.auth !== 'boolean') {\n throw new ItdConfigError(`services.${name}.auth должен быть boolean`);\n }\n\n let headers: Readonly<Record<string, string>> | undefined;\n if (definition.headers !== undefined) {\n if (\n typeof definition.headers !== 'object' ||\n definition.headers === null ||\n Array.isArray(definition.headers)\n ) {\n throw new ItdConfigError(`services.${name}.headers должен быть объектом строк`);\n }\n for (const [header, value] of Object.entries(definition.headers)) {\n if (typeof value !== 'string') {\n throw new ItdConfigError(`services.${name}.headers.${header} должен быть строкой`);\n }\n }\n headers = Object.freeze({ ...definition.headers });\n }\n\n const normalized = {\n ...definition,\n name,\n baseUrl,\n auth: definition.auth ?? isSameSite(this.#primaryHost, hostOf(baseUrl)),\n ...(headers ? { headers } : {}),\n } satisfies ServiceDefinition;\n\n this.#services.set(name, Object.freeze(normalized));\n }\n\n /** Определение сервиса либо `undefined`, если такого нет. */\n get(name: string): ServiceDefinition | undefined {\n return this.#services.get(name);\n }\n\n /** Зарегистрирован ли сервис с таким именем. */\n has(name: string): boolean {\n return this.#services.has(name);\n }\n\n /**\n * Определение сервиса.\n *\n * @throws {ItdConfigError} если сервис не зарегистрирован\n */\n require(name: string): ServiceDefinition {\n const service = this.#services.get(name);\n if (!service) {\n const known = [...this.#services.keys()];\n throw new ItdConfigError(\n `Сервис «${name}» не зарегистрирован. ` +\n (known.length > 0\n ? `Известны: ${known.join(', ')}`\n : 'Зарегистрируйте его через itd.defineService() или опцию services'),\n );\n }\n return service;\n }\n\n /**\n * Базовый URL сервиса.\n *\n * @throws {ItdConfigError} если сервис не зарегистрирован\n */\n resolveBaseUrl(name: string): string {\n return this.require(name).baseUrl;\n }\n\n /**\n * Принадлежит ли URL основному хосту клиента или его поддомену.\n *\n * Используется для безопасного значения по умолчанию у разового `baseUrl`: Bearer-токен\n * не должен уходить на посторонний хост без явного `skipAuth: false`.\n *\n * @internal\n */\n isPrimarySite(baseUrl: string): boolean {\n return isSameSite(this.#primaryHost, hostOf(baseUrl));\n }\n}\n","import { isBlob } from './runtime.js';\n\n/** Заголовки, значение которых нельзя писать в лог целиком. */\nconst SECRET_HEADERS = new Set(['authorization', 'cookie', 'set-cookie', 'x-api-key']);\n\n/** Поля тела запроса, значение которых нельзя писать в лог. */\nconst SECRET_FIELDS = new Set([\n 'password',\n 'oldpassword',\n 'newpassword',\n 'accesstoken',\n 'refreshtoken',\n 'currentpassword',\n 'flowtoken',\n 'token',\n 'turnstiletoken',\n 'otp',\n]);\n\n/**\n * Прячет середину секрета, оставляя концы для сопоставления.\n *\n * @example\n * ```ts\n * maskSecret('eyJhbGciOiJIUzI1NiJ9.abc'); // 'eyJh…(24)…abc'\n * ```\n */\nexport function maskSecret(value: string): string {\n if (value.length <= 8) return '…';\n return `${value.slice(0, 4)}…(${value.length})…${value.slice(-3)}`;\n}\n\n/**\n * Готовит заголовки к записи в лог.\n *\n * `Authorization` и `Cookie` маскируются: логи часто уезжают в системы сбора,\n * и токен доступа в них попадать не должен.\n */\nexport function redactHeaders(headers: Headers): Record<string, string> {\n const result: Record<string, string> = {};\n\n headers.forEach((value, name) => {\n if (SECRET_HEADERS.has(name.toLowerCase())) {\n // У Authorization маскируем только сам токен, схему («Bearer») оставляем видимой.\n const spaceAt = value.indexOf(' ');\n result[name] =\n spaceAt > 0\n ? `${value.slice(0, spaceAt)} ${maskSecret(value.slice(spaceAt + 1))}`\n : maskSecret(value);\n return;\n }\n result[name] = value;\n });\n\n return result;\n}\n\n/**\n * Готовит тело запроса к записи в лог: пароли, токены и коды OTP заменяются заглушкой.\n *\n * Обходит вложенные объекты и массивы. `FormData` и бинарные тела не раскрываются вовсе.\n */\nexport function redactBody(body: unknown): unknown {\n if (body === null || body === undefined) return body;\n\n if (typeof FormData !== 'undefined' && body instanceof FormData) return '[FormData]';\n if (isBlob(body)) return '[Blob]';\n if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return '[binary]';\n\n if (Array.isArray(body)) return body.map(redactBody);\n\n if (typeof body === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(body)) {\n result[key] = SECRET_FIELDS.has(key.toLowerCase()) ? '[скрыто]' : redactBody(value);\n }\n return result;\n }\n\n return body;\n}\n","/**\n * Снимает внешнюю обёртку `{ data: … }`.\n *\n * Это единственное преобразование ответа, которое делает транспортный слой. Формы ответов у\n * итд.com непоследовательны: одни эндпоинты заворачивают полезную нагрузку в `data`, другие\n * отдают её напрямую (`GET /api/notifications/` → `{ notifications, hasMore }`), третьи кладут\n * список в поле по имени сущности. Угадывать имя поля опасно, поэтому его разбирает каждый\n * метод ресурса явно, а здесь снимается только однозначная обёртка.\n *\n * Обёртка считается обёрткой, лишь когда `data` — **единственный** ключ объекта. Ответ\n * `{ data: …, meta: … }` вернётся как есть.\n *\n * @example\n * ```ts\n * unwrapData({ data: { posts: [] } }); // { posts: [] }\n * unwrapData({ notifications: [], hasMore }); // без изменений\n * unwrapData({ data: [], hasMore: true }); // без изменений — ключей больше одного\n * ```\n */\nexport function unwrapData(body: unknown): unknown {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return body;\n\n const keys = Object.keys(body);\n if (keys.length !== 1 || keys[0] !== 'data') return body;\n\n return (body as { data: unknown }).data;\n}\n\n/**\n * Обычный объект — не `null`, не массив.\n *\n * Живёт здесь, а не в каждом разборщике ответа: одна и та же проверка нужна и фабрике\n * ошибок, и приведению уведомлений.\n */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Непустая строка либо `undefined`. Отличается от {@link pickString} тем, что берёт значение, а не поле. */\nexport function asString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/**\n * Достаёт поле-список из ответа, уже прошедшего {@link unwrapData}.\n *\n * Если поля нет или оно не массив, возвращается пустой массив: сервер иногда опускает\n * пустые коллекции, и падать из-за этого библиотека не должна.\n */\nexport function pickArray<T>(source: unknown, field: string): T[] {\n if (typeof source !== 'object' || source === null) return [];\n const value = (source as Record<string, unknown>)[field];\n return Array.isArray(value) ? (value as T[]) : [];\n}\n\n/** Достаёт объект по имени поля. Возвращает `undefined`, если это не объект. */\nexport function pickObject(source: unknown, field: string): Record<string, unknown> | undefined {\n if (typeof source !== 'object' || source === null) return undefined;\n const value = (source as Record<string, unknown>)[field];\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n return value as Record<string, unknown>;\n}\n\n/** Достаёт булево поле. Возвращает `fallback`, если поля нет или тип не тот. */\nexport function pickBoolean(source: unknown, field: string, fallback = false): boolean {\n if (typeof source !== 'object' || source === null) return fallback;\n const value = (source as Record<string, unknown>)[field];\n return typeof value === 'boolean' ? value : fallback;\n}\n\n/** Достаёт числовое поле. Возвращает `fallback`, если поля нет или значение не конечное число. */\nexport function pickNumber(source: unknown, field: string, fallback: number): number {\n if (typeof source !== 'object' || source === null) return fallback;\n const value = (source as Record<string, unknown>)[field];\n return typeof value === 'number' && Number.isFinite(value) ? value : fallback;\n}\n\n/** Достаёт строковое поле. Возвращает `undefined`, если поля нет, оно пустое или не строка. */\nexport function pickString(source: unknown, field: string): string | undefined {\n if (typeof source !== 'object' || source === null) return undefined;\n const value = (source as Record<string, unknown>)[field];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n","import type { ItdErrorCode } from '../types/enums.js';\nimport {\n type ItdApiError,\n ItdApiError as ItdApiErrorClass,\n ItdAuthError,\n ItdConflictError,\n type ItdFieldErrors,\n ItdForbiddenError,\n ItdNotFoundError,\n ItdPhoneVerificationError,\n ItdRateLimitError,\n ItdServerError,\n ItdValidationError,\n} from './errors.js';\nimport { redactBody } from './redact.js';\nimport { asString, isRecord } from './unwrap.js';\n\n/** Разобранное тело ошибки, приведённое к одной форме. */\nexport interface ParsedErrorBody {\n code: ItdErrorCode;\n message: string;\n detail: string | undefined;\n title: string | undefined;\n fieldErrors: ItdFieldErrors;\n /** Идентификатор пользователя, если сервер его вернул (нужен для ссылки подтверждения телефона). */\n userId: string | undefined;\n}\n\n/**\n * Сводит ошибки по полям к единой форме `{ поле: [сообщения] }`.\n *\n * Понимает две документированные формы:\n * - `errors: { email: ['…'] }` либо `errors: { email: '…' }`\n * - `violations: [{ field: 'email', message: '…' }]`\n */\nfunction collectFieldErrors(source: Record<string, unknown>): ItdFieldErrors {\n const result: ItdFieldErrors = {};\n\n const errors = source.errors;\n if (isRecord(errors)) {\n for (const [field, value] of Object.entries(errors)) {\n if (Array.isArray(value)) {\n const messages = value.filter((item): item is string => typeof item === 'string');\n if (messages.length > 0) result[field] = messages;\n } else if (typeof value === 'string') {\n result[field] = [value];\n }\n }\n }\n\n const violations = source.violations;\n if (Array.isArray(violations)) {\n for (const violation of violations) {\n if (!isRecord(violation)) continue;\n const field = asString(violation.field) ?? asString(violation.property);\n const message = asString(violation.message);\n if (!field || !message) continue;\n const existing = result[field];\n if (existing) existing.push(message);\n else result[field] = [message];\n }\n }\n\n return result;\n}\n\n/**\n * Разбирает тело ответа с ошибкой.\n *\n * API отдаёт ошибки в двух формах — с обёрткой `{ error: … }` и без неё, — а при сбое на\n * уровне прокси может вернуть вообще не JSON. Все три случая сводятся к одной структуре.\n *\n * @param body тело ответа: разобранный JSON, строка или `undefined`\n * @param status HTTP-статус, используется для сообщения по умолчанию\n * @param statusText текст статуса из ответа\n */\nexport function parseErrorBody(body: unknown, status: number, statusText = ''): ParsedErrorBody {\n const fallbackMessage = statusText ? `HTTP ${status} ${statusText}` : `HTTP ${status}`;\n\n // Не-JSON: HTML от прокси, простой текст, пустое тело.\n if (typeof body === 'string') {\n return {\n code: 'UNKNOWN_ERROR',\n message: asString(body.trim()) ?? fallbackMessage,\n detail: undefined,\n title: undefined,\n fieldErrors: {},\n userId: undefined,\n };\n }\n\n if (!isRecord(body)) {\n return {\n code: 'UNKNOWN_ERROR',\n message: fallbackMessage,\n detail: undefined,\n title: undefined,\n fieldErrors: {},\n userId: undefined,\n };\n }\n\n // Ответ вида `{ type: \"validation\", on: \"query\", found: {…} }` — так сервер сообщает\n // о непрошедшей проверке параметров. Ни кода, ни текста в нём нет.\n if (body.type === 'validation') {\n const target = asString(body.on);\n\n return {\n code: 'VALIDATION_ERROR',\n message: target\n ? `Проверка не пройдена: некорректные данные в «${target}»`\n : 'Проверка входных данных не пройдена',\n detail: undefined,\n title: undefined,\n fieldErrors: {},\n userId: undefined,\n };\n }\n\n // Форма `{ error: { code, message, … } }` — работаем с вложенным объектом,\n // но поля верхнего уровня остаются запасным источником.\n const inner = isRecord(body.error) ? body.error : body;\n\n const message =\n asString(inner.message) ??\n asString(inner.detail) ??\n asString(inner.title) ??\n // `{ \"error\": \"Invalid token\" }` — так отвечает сервер на недействительный токен.\n asString(body.error) ??\n fallbackMessage;\n\n return {\n code: (asString(inner.code) ?? asString(body.code) ?? 'UNKNOWN_ERROR') as ItdErrorCode,\n message,\n detail: asString(inner.detail),\n title: asString(inner.title),\n fieldErrors: { ...collectFieldErrors(body), ...collectFieldErrors(inner) },\n userId: asString(inner.userId) ?? asString(body.userId),\n };\n}\n\n/**\n * Переводит заголовок `Retry-After` в миллисекунды.\n *\n * Поддерживает обе формы из спецификации HTTP: число секунд и дату.\n * Возвращает `undefined`, если заголовка нет или он не разбирается.\n */\nexport function parseRetryAfter(\n header: string | null | undefined,\n now = Date.now(),\n): number | undefined {\n if (!header) return undefined;\n\n const seconds = Number(header);\n if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);\n\n const date = Date.parse(header);\n if (Number.isFinite(date)) return Math.max(0, date - now);\n\n return undefined;\n}\n\n/** Читает целое число из заголовка. */\nfunction readIntHeader(headers: Headers | undefined, name: string): number | undefined {\n const raw = headers?.get(name);\n if (raw === null || raw === undefined) return undefined;\n\n const value = Number.parseInt(raw, 10);\n return Number.isFinite(value) ? value : undefined;\n}\n\n/**\n * Читает сведения об ограничении частоты.\n *\n * Сервер сообщает размер окна и остаток, но **не сообщает время сброса** — поэтому\n * точный момент, когда можно повторить, из ответа не вывести.\n */\nexport function readRateLimit(headers: Headers | undefined): {\n limit: number | undefined;\n remaining: number | undefined;\n} {\n return {\n limit: readIntHeader(headers, 'x-ratelimit-limit'),\n remaining: readIntHeader(headers, 'x-ratelimit-remaining'),\n };\n}\n\nconst REQUEST_ID_HEADERS = ['x-request-id', 'x-requestid', 'request-id', 'x-correlation-id'];\n\n/** Достаёт идентификатор запроса из заголовков ответа — пригодится при обращении в поддержку. */\nexport function getRequestId(headers: Headers | undefined): string | undefined {\n if (!headers) return undefined;\n for (const name of REQUEST_ID_HEADERS) {\n const value = headers.get(name);\n if (value) return value;\n }\n return undefined;\n}\n\n/** Соответствие кода ошибки конкретному классу. Приоритетнее, чем HTTP-статус. */\nconst CODE_TO_CLASS: Record<string, new (init: never) => ItdApiError> = {\n VALIDATION_ERROR: ItdValidationError,\n RATE_LIMIT_EXCEEDED: ItdRateLimitError,\n UNAUTHORIZED: ItdAuthError,\n SESSION_EXPIRED: ItdAuthError,\n SESSION_REVOKED: ItdAuthError,\n SESSION_INVALID_REFRESH_TOKEN: ItdAuthError,\n // Оба приходят с `/auth/refresh`: первый — когда cookie refresh_token не долетела,\n // второй — когда она есть, но сессия за ней уже мертва.\n REFRESH_TOKEN_MISSING: ItdAuthError,\n SESSION_NOT_FOUND: ItdAuthError,\n ACCOUNT_INVALID_CREDENTIALS: ItdAuthError,\n ACCESS_DENIED: ItdForbiddenError,\n ENTITY_NOT_FOUND: ItdNotFoundError,\n // Сервер отвечает именно так: `{ error: { code: 'NOT_FOUND', message: 'Post not found' } }`.\n NOT_FOUND: ItdNotFoundError,\n ENTITY_ALREADY_EXISTS: ItdConflictError,\n};\n\n/** Соответствие HTTP-статуса классу — запасной вариант, когда код ничего не говорит. */\nfunction classByStatus(status: number): new (init: never) => ItdApiError {\n if (status === 401) return ItdAuthError;\n if (status === 403) return ItdForbiddenError;\n if (status === 404) return ItdNotFoundError;\n if (status === 409) return ItdConflictError;\n if (status === 422) return ItdValidationError;\n if (status === 429) return ItdRateLimitError;\n if (status >= 500) return ItdServerError;\n return ItdApiErrorClass;\n}\n\n/**\n * Готовит тело ответа к сохранению в `ItdApiError.raw`.\n *\n * При `422` сервер возвращает присланное тело эхом: `{ type: 'validation', on: 'body',\n * found: { email: '…', password: '…' } }`. Ошибка обычно доезжает до логов и систем сбора\n * вроде Sentry — пароль в ней оказаться не должен. Имена полей для диагностики остаются,\n * значения секретов заменяются заглушкой.\n */\nfunction safeRawBody(body: unknown): unknown {\n if (!isRecord(body) || body.type !== 'validation' || !isRecord(body.found)) return body;\n\n return { ...body, found: redactBody(body.found) };\n}\n\n/** Что нужно знать о запросе, чтобы построить ошибку. */\nexport interface ErrorContext {\n method: string;\n path: string;\n status: number;\n /** Текущее время в миллисекундах с начала эпохи Unix. */\n now?: number | undefined;\n statusText?: string | undefined;\n headers?: Headers | undefined;\n response?: Response | undefined;\n /** Тело ответа: разобранный JSON либо строка. */\n body: unknown;\n}\n\n/**\n * Строит типизированную ошибку из ответа сервера.\n *\n * Класс выбирается сначала по коду ошибки, затем по HTTP-статусу — так `VALIDATION_ERROR`\n * со статусом `400` всё равно станет {@link ItdValidationError}.\n */\nexport function createApiError(context: ErrorContext): ItdApiError {\n const parsed = parseErrorBody(context.body, context.status, context.statusText);\n const rateLimit = readRateLimit(context.headers);\n\n const init = {\n rateLimit: rateLimit.limit,\n rateLimitRemaining: rateLimit.remaining,\n status: context.status,\n code: parsed.code,\n message: parsed.message,\n detail: parsed.detail,\n title: parsed.title,\n fieldErrors: parsed.fieldErrors,\n requestId: getRequestId(context.headers),\n method: context.method,\n path: context.path,\n raw: safeRawBody(context.body),\n response: context.response,\n retryAfter: parseRetryAfter(context.headers?.get('retry-after'), context.now),\n };\n\n if (parsed.code === 'PHONE_VERIFICATION_REQUIRED') {\n return new ItdPhoneVerificationError({ ...init, userId: parsed.userId });\n }\n\n const Ctor = CODE_TO_CLASS[parsed.code] ?? classByStatus(context.status);\n return new Ctor(init as never);\n}\n","import type { ClientHooks, Logger } from '../types/options.js';\nimport type { ItdClock } from './clock.js';\nimport type { CookieJar } from './cookies.js';\nimport { createApiError, readRateLimit } from './error-factory.js';\nimport {\n ItdAbortError,\n ItdConfigError,\n ItdError,\n ItdNetworkError,\n ItdTimeoutError,\n} from './errors.js';\nimport type { PipelineRequest, PreparedRequestBody } from './pipeline.js';\nimport { dispatchRequestHook } from './plugins.js';\nimport { redactBody, redactHeaders } from './redact.js';\nimport { isBlob } from './runtime.js';\nimport { unwrapData } from './unwrap.js';\nimport { buildQuery, joinUrl } from './url.js';\n\n/**\n * Настройки, нужные транспорту.\n *\n * Узкий срез общей конфигурации: авторизация, хранилище, повторы и очередь транспорта\n * не касаются, и он не должен иметь к ним доступ.\n */\nexport interface TransportConfig {\n baseUrl: string;\n fetch: typeof fetch;\n timeout: number;\n clock: ItdClock;\n headers: Record<string, string>;\n /** Значение заголовка `User-Agent`. `undefined` — заголовок не выставляется. */\n userAgent: string | undefined;\n useCookieJar: boolean;\n sendCredentials: boolean;\n hooks: ClientHooks;\n logger: Logger | undefined;\n}\n\n/**\n * Внешние части, которыми пользуется транспорт.\n *\n * Все три обязательны к передаче и все три могут отсутствовать по существу: cookie-jar\n * не нужен в браузере, идентификатор устройства — при выключенной авторизации, а сведения\n * об ограничении частоты интересны только тогда, когда есть очередь.\n */\nexport interface TransportDeps {\n /** Хранилище cookie. `undefined` — cookie ведёт сама среда. */\n cookies: CookieJar | undefined;\n /**\n * Идентификатор устройства для заголовка `X-Device-Id`. Отправляется на всех запросах,\n * включая анонимные (`sign-in`).\n */\n getDeviceId: (() => Promise<string>) | undefined;\n /**\n * Сообщает об остатке лимита из заголовков ответа.\n *\n * Вызывается после **каждого** ответа, включая ошибочные, — так очередь узнаёт\n * об исчерпании лимита заранее и успевает притормозить до отказа сервера.\n */\n onRateLimit:\n | ((limit: number | undefined, remaining: number | undefined, request: PipelineRequest) => void)\n | undefined;\n}\n\n/**\n * Ставит заголовок, превращая ограничение HTTP в понятное сообщение.\n *\n * Значения заголовков ограничены latin1, поэтому кириллица в них невозможна. Среда\n * сообщает об этом невнятным `Cannot convert argument to a ByteString`, что при русских\n * названиях приложений сбивает с толку.\n */\nfunction setHeader(headers: Headers, name: string, value: string): void {\n try {\n headers.set(name, value);\n } catch {\n throw new ItdConfigError(\n `Значение заголовка ${name} содержит символы вне latin1. HTTP-заголовки не могут ` +\n 'содержать кириллицу — закодируйте значение, например через encodeURIComponent.',\n );\n }\n}\n\n/** Тело, которое отправляется как есть, без сериализации в JSON. */\nfunction isRawBody(body: unknown): body is BodyInit {\n if (typeof body !== 'object' || body === null) return typeof body === 'string';\n return (\n (typeof FormData !== 'undefined' && body instanceof FormData) ||\n isBlob(body) ||\n (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) ||\n (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) ||\n body instanceof ArrayBuffer ||\n ArrayBuffer.isView(body)\n );\n}\n\n/**\n * Читает тело ответа один раз.\n *\n * Ответ можно прочитать только однократно, а тело нужно и при успехе, и при ошибке,\n * поэтому чтение происходит здесь, до ветвления по статусу.\n */\nasync function readBody(response: Response): Promise<unknown> {\n if (response.status === 204 || response.status === 205) return undefined;\n if (response.headers.get('content-length') === '0') return undefined;\n\n const contentType = response.headers.get('content-type') ?? '';\n\n if (contentType.includes('json')) {\n const text = await response.text();\n if (text === '') return undefined;\n try {\n return JSON.parse(text) as unknown;\n } catch {\n // Заголовок обещал JSON, но тело им не является — отдаём как текст,\n // чтобы сообщение об ошибке осталось информативным.\n return text;\n }\n }\n\n const text = await response.text();\n return text === '' ? undefined : text;\n}\n\n/** Ошибка отмены в формате `fetch`. */\nfunction createAbortError(): Error {\n const error = new Error('Операция прервана');\n error.name = 'AbortError';\n return error;\n}\n\n/** Прерывает ожидание промиса при срабатывании сигнала. */\nfunction abortable<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {\n if (signal.aborted) {\n // Готовый промис может завершиться до отложенной ошибки отмены.\n return Promise.race([\n promise,\n new Promise<never>((_resolve, reject) => {\n setTimeout(() => reject(createAbortError()), 0);\n }),\n ]);\n }\n\n let onAbort: (() => void) | undefined;\n\n const interrupted = new Promise<never>((_resolve, reject) => {\n onAbort = () => reject(createAbortError());\n signal.addEventListener('abort', onAbort, { once: true });\n });\n\n return Promise.race([promise, interrupted]).finally(() => {\n if (onAbort) signal.removeEventListener('abort', onAbort);\n });\n}\n\n/** Результат объединения пользовательской отмены и таймаута. */\ninterface AbortBundle {\n signal: AbortSignal;\n /** Сработал ли именно таймаут — от этого зависит класс ошибки. */\n timedOut: () => boolean;\n cleanup: () => void;\n}\n\n/**\n * Объединяет пользовательский `AbortSignal` с таймаутом.\n *\n * Реализовано вручную, а не через `AbortSignal.any`: последний появился только в Node 20,\n * а библиотека поддерживает Node 18.\n */\nfunction createAbortBundle(\n userSignal: AbortSignal | undefined,\n timeout: number,\n clock: ItdClock,\n): AbortBundle {\n const controller = new AbortController();\n let timedOut = false;\n\n const onUserAbort = () => controller.abort(userSignal?.reason);\n\n if (userSignal) {\n if (userSignal.aborted) controller.abort(userSignal.reason);\n else userSignal.addEventListener('abort', onUserAbort, { once: true });\n }\n\n const cancelTimer =\n timeout > 0\n ? clock.schedule(() => {\n timedOut = true;\n controller.abort();\n }, timeout)\n : undefined;\n\n return {\n signal: controller.signal,\n timedOut: () => timedOut,\n cleanup: () => {\n cancelTimer?.();\n userSignal?.removeEventListener('abort', onUserAbort);\n },\n };\n}\n\n/**\n * Единственное место, откуда библиотека ходит в сеть.\n *\n * Отвечает за сборку URL, заголовки, cookie, таймауты, разбор ответа и превращение любой\n * неудачи в типизированную ошибку. Авторизация, повторы, очередь и плагины — отдельные\n * слои конвейера, и транспорт о них не знает.\n */\nexport class Transport {\n readonly #config: TransportConfig;\n readonly #deps: TransportDeps;\n\n constructor(config: TransportConfig, deps: TransportDeps) {\n this.#config = config;\n this.#deps = deps;\n }\n\n /** Базовый URL, к которому обращается транспорт. */\n get baseUrl(): string {\n return this.#config.baseUrl;\n }\n\n /**\n * Выполняет один сетевой запрос.\n *\n * @throws {ItdApiError} если сервер ответил статусом ≥ 400\n * @throws {ItdTimeoutError} если истёк таймаут\n * @throws {ItdAbortError} если запрос отменён через `signal`\n * @throws {ItdNetworkError} если запрос не дошёл до сервера\n */\n send = async (request: PipelineRequest): Promise<unknown> => {\n const method = request.method.toUpperCase();\n const url = this.buildUrl(request);\n const headers = await this.#buildHeaders(request, url);\n const attempt = request.attempt ?? 1;\n\n const timeout = request.timeout ?? this.#config.timeout;\n const abort = createAbortBundle(request.signal, timeout, this.#config.clock);\n const startedAt = this.#config.clock.now();\n let cleanupBody: (() => void | Promise<void>) | undefined;\n\n try {\n let body: BodyInit | undefined;\n try {\n const prepared = await this.#prepareBody(request, headers, abort.signal, attempt);\n body = prepared.body;\n cleanupBody = prepared.cleanup;\n } catch (error) {\n const failure = this.#toTransportError(error, abort, request, method, timeout);\n const context = { method, path: request.path, url, headers, attempt };\n await dispatchRequestHook(\n this.#config.hooks,\n 'onError',\n { ...context, duration: this.#config.clock.now() - startedAt, error: failure },\n request,\n );\n throw failure;\n }\n\n const context = { method, path: request.path, url, headers, attempt };\n await dispatchRequestHook(this.#config.hooks, 'onRequest', context, request);\n\n this.#config.logger?.debug(`→ ${method} ${request.path}`, {\n headers: redactHeaders(headers),\n body: request.bodyFactory ? '[повторяемое тело]' : redactBody(request.body),\n });\n\n let response: Response;\n try {\n const init: RequestInit & { duplex?: 'half' } = {\n method,\n headers,\n signal: abort.signal,\n ...(body !== undefined ? { body } : {}),\n ...(this.#config.sendCredentials ? { credentials: 'include' as const } : {}),\n };\n if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) {\n init.duplex = 'half';\n }\n response = await this.#config.fetch(url, init);\n } catch (error) {\n const duration = this.#config.clock.now() - startedAt;\n const failure = this.#toTransportError(error, abort, request, method, timeout);\n\n await dispatchRequestHook(\n this.#config.hooks,\n 'onError',\n { ...context, duration, error: failure },\n request,\n );\n this.#config.logger?.warn(\n `× ${method} ${request.path} (${duration} мс): ${failure.message}`,\n );\n\n throw failure;\n }\n\n if (this.#deps.onRateLimit) {\n const { limit, remaining } = readRateLimit(response.headers);\n this.#deps.onRateLimit(limit, remaining, request);\n }\n\n if (this.#config.useCookieJar) this.#deps.cookies?.setFromResponse(url, response);\n\n // Хук получает непрочитанный ответ.\n if (response.ok) {\n await dispatchRequestHook(\n this.#config.hooks,\n 'onResponse',\n {\n ...context,\n status: response.status,\n duration: this.#config.clock.now() - startedAt,\n response,\n },\n request,\n );\n }\n\n const payload = await this.#readBodyOrFail(\n response,\n context,\n request,\n method,\n abort,\n timeout,\n );\n const duration = this.#config.clock.now() - startedAt;\n\n if (!response.ok) {\n const error = createApiError({\n method,\n path: request.path,\n status: response.status,\n now: this.#config.clock.now(),\n statusText: response.statusText,\n headers: response.headers,\n response,\n body: payload,\n });\n\n await dispatchRequestHook(\n this.#config.hooks,\n 'onError',\n { ...context, duration, error },\n request,\n );\n this.#config.logger?.warn(\n `← ${response.status} ${method} ${request.path} (${duration} мс): ${error.message}`,\n );\n\n throw error;\n }\n\n this.#config.logger?.debug(`← ${response.status} ${method} ${request.path} (${duration} мс)`);\n\n return request.raw ? payload : unwrapData(payload);\n } finally {\n try {\n await cleanupBody?.();\n } catch (error) {\n this.#config.logger?.warn(`не удалось закрыть тело ${method} ${request.path}`, error);\n } finally {\n abort.cleanup();\n }\n }\n };\n\n /** Подготавливает тело внутри попытки, чтобы поток можно было открыть заново при retry. */\n async #prepareBody(\n request: PipelineRequest,\n headers: Headers,\n signal: AbortSignal,\n attempt: number,\n ): Promise<{ body: BodyInit | undefined; cleanup: (() => void | Promise<void>) | undefined }> {\n if (request.bodyFactory) {\n if (request.body !== undefined && request.body !== null) {\n throw new ItdConfigError('body и bodyFactory нельзя задавать одновременно');\n }\n\n const pending = Promise.resolve(request.bodyFactory({ signal, attempt }));\n let prepared: PreparedRequestBody;\n try {\n prepared = await abortable(pending, signal);\n } catch (error) {\n if (signal.aborted) {\n void pending\n .then(async (late) => {\n try {\n await late.cleanup?.();\n } catch (cleanupError) {\n this.#config.logger?.warn(\n `не удалось закрыть отложенное тело ${request.method} ${request.path}`,\n cleanupError,\n );\n }\n })\n .catch(() => {});\n }\n throw error;\n }\n for (const [name, value] of Object.entries(prepared.headers ?? {})) {\n if (!headers.has(name)) setHeader(headers, name, value);\n }\n return { body: prepared.body, cleanup: prepared.cleanup };\n }\n\n if (request.body === undefined || request.body === null) {\n return { body: undefined, cleanup: undefined };\n }\n if (isRawBody(request.body)) {\n return { body: request.body, cleanup: undefined };\n }\n\n if (!headers.has('Content-Type')) headers.set('Content-Type', 'application/json');\n return { body: JSON.stringify(request.body), cleanup: undefined };\n }\n\n /** Читает тело и преобразует ошибку чтения в транспортную ошибку библиотеки. */\n async #readBodyOrFail(\n response: Response,\n context: { method: string; path: string; url: string; headers: Headers; attempt: number },\n request: PipelineRequest,\n method: string,\n abort: AbortBundle,\n timeout: number,\n ): Promise<unknown> {\n const startedAt = this.#config.clock.now();\n\n try {\n return await abortable(readBody(response), abort.signal);\n } catch (error) {\n await response.body?.cancel().catch(() => {});\n\n const failure = this.#toTransportError(error, abort, request, method, timeout);\n\n await dispatchRequestHook(\n this.#config.hooks,\n 'onError',\n {\n ...context,\n duration: this.#config.clock.now() - startedAt,\n error: failure,\n },\n request,\n );\n this.#config.logger?.warn(\n `× ${method} ${request.path}: не удалось прочитать тело ответа — ${failure.message}`,\n );\n\n throw failure;\n }\n }\n\n /**\n * Итоговый URL со строкой запроса. Нужен и слою повторов — для хука `onRetry`.\n *\n * Хост берётся из самого запроса, если он там задан: у сервисов платформы свои домены.\n */\n buildUrl(request: PipelineRequest): string {\n const base = request.baseUrl ?? this.#config.baseUrl;\n return joinUrl(base, request.path) + buildQuery(request.query);\n }\n\n /**\n * Собирает общие заголовки клиента: `User-Agent`, идентификатор устройства,\n * заголовки конфигурации и cookie для указанного адреса.\n */\n async platformHeaders(url: string): Promise<Headers> {\n const headers = new Headers();\n\n // Сервер этот заголовок не требует, но ожидает: дешевле отправить, чем разбираться,\n // почему часть запросов не проходит фильтры.\n headers.set('X-Requested-With', 'XMLHttpRequest');\n\n // В браузере это запрещённый заголовок: среда молча его игнорирует, ошибки не будет.\n if (this.#config.userAgent) setHeader(headers, 'User-Agent', this.#config.userAgent);\n\n if (this.#deps.getDeviceId) {\n setHeader(headers, 'X-Device-Id', await this.#deps.getDeviceId());\n }\n\n for (const [name, value] of Object.entries(this.#config.headers))\n setHeader(headers, name, value);\n\n if (this.#config.useCookieJar && this.#deps.cookies) {\n const cookie = this.#deps.cookies.getHeader(url);\n if (cookie) setHeader(headers, 'Cookie', cookie);\n }\n\n return headers;\n }\n\n /**\n * Дополняет общие заголовки значением `Accept`, заголовками конвейера и вызова.\n * Заголовки вызова применяются последними.\n */\n async #buildHeaders(request: PipelineRequest, url: string): Promise<Headers> {\n const headers = await this.platformHeaders(url);\n\n // Заголовок из конфигурации имеет приоритет над значением по умолчанию.\n if (!headers.has('Accept')) headers.set('Accept', 'application/json');\n\n for (const [name, value] of Object.entries(request.layerHeaders ?? {}))\n setHeader(headers, name, value);\n\n for (const [name, value] of Object.entries(request.headers ?? {})) {\n setHeader(headers, name, value);\n }\n\n return headers;\n }\n\n /** Превращает исключение `fetch` в понятную ошибку библиотеки. */\n #toTransportError(\n error: unknown,\n abort: AbortBundle,\n request: PipelineRequest,\n method: string,\n timeout: number,\n ): ItdError {\n // Пользовательская отмена с собственным `reason` реджектит `fetch` этим значением, а не\n // `AbortError`, — поэтому опираемся на состояние сигнала, а не только на имя ошибки.\n const aborted = abort.signal.aborted || (error instanceof Error && error.name === 'AbortError');\n\n if (aborted && abort.timedOut()) {\n return new ItdTimeoutError({ timeout, method, path: request.path });\n }\n\n if (aborted) {\n // Сохраняем причину отмены: пользователь мог передать её в `abort(reason)`.\n const reason = request.signal?.reason;\n return new ItdAbortError(\n `Запрос ${method} ${request.path} отменён`,\n reason !== undefined ? { cause: reason } : undefined,\n );\n }\n\n if (error instanceof ItdError) return error;\n\n return new ItdNetworkError(\n `Не удалось выполнить ${method} ${request.path}: ${error instanceof Error ? error.message : String(error)}`,\n { method, path: request.path, cause: error },\n );\n }\n}\n","/**\n * Перечисления API итд.com.\n *\n * Здесь намеренно не используется `enum` из TypeScript. Вместо него — пара «замороженный\n * объект + одноимённый тип». Такой приём даёт всё, ради чего берут `enum`\n * (`FeedTab.Popular`, перебор значений в рантайме), и при этом:\n *\n * - **стирается без остатка** — `enum` порождает рантайм-код и отвергается средами,\n * которые просто срезают типы (`node --experimental-strip-types`);\n * - **не запрещает обычные строки** — `itd.posts.list({ tab: 'popular' })` остаётся валидным,\n * тогда как строковый `enum` считает это ошибкой типа и вынуждает всех импортировать себя;\n * - **позволяет открытые множества** — там, где документация перечисляет значения не полностью,\n * тип расширяется через {@link Loose}, а объект остаётся справочником известных значений.\n *\n * @example\n * ```ts\n * import { FeedTab } from 'itd-api';\n *\n * await itd.posts.list({ tab: FeedTab.Popular }); // без магических строк\n * await itd.posts.list({ tab: 'popular' }); // и так тоже можно\n *\n * Object.values(FeedTab); // ['popular', 'following', 'clan']\n * ```\n *\n * @packageDocumentation\n */\n\n/**\n * Открытое строковое перечисление.\n *\n * Даёт автодополнение известных значений, но не ломается, если сервер пришлёт новое.\n * Используется там, где документация API перечисляет значения не полностью («`everyone` и др.»).\n */\nexport type Loose<T extends string> = T | (string & {});\n\n/**\n * Вкладка ленты `GET /api/posts`.\n *\n * Множество закрытое: неизвестное значение сервер отвергнет.\n */\nexport const FeedTab = Object.freeze({\n /** Популярное. Курсор здесь — номер страницы в виде строки (`\"2\"`, `\"6\"`…). */\n Popular: 'popular',\n /** Записи тех, на кого вы подписаны. Курсор — отметка времени последнего поста. */\n Following: 'following',\n /** Лента клана. Курсор, как и в подписках, — отметка времени. */\n Clan: 'clan',\n} as const);\nexport type FeedTab = (typeof FeedTab)[keyof typeof FeedTab];\n\n/** Порядок комментариев к посту. */\nexport const CommentSort = Object.freeze({\n /** Сначала новые. */\n Newest: 'newest',\n /** Сначала старые. */\n Oldest: 'oldest',\n /** Сначала популярные. */\n Popular: 'popular',\n} as const);\nexport type CommentSort = (typeof CommentSort)[keyof typeof CommentSort];\n\n/** Тип вложения. */\nexport const AttachmentType = Object.freeze({\n Image: 'image',\n Video: 'video',\n /** Голосовые комментарии: `audio/ogg`, с полем `duration`. */\n Audio: 'audio',\n} as const);\nexport type AttachmentType = (typeof AttachmentType)[keyof typeof AttachmentType];\n\n/**\n * Тип фрагмента разметки в тексте поста или комментария.\n *\n * Первые два сервер расставляет сам при разборе текста, остальные приходят от редактора.\n * Тип открытый: набор может пополниться.\n *\n * @example\n * ```ts\n * await itd.posts.update(postId, {\n * content: 'жирное слово',\n * spans: [{ type: SpanType.Bold, offset: 0, length: 6 }],\n * });\n * ```\n */\nexport const SpanType = Object.freeze({\n /** Хэштег. Название без решётки лежит в `tag`. */\n Hashtag: 'hashtag',\n /** Упоминание. Имя пользователя лежит в `tag`. */\n Mention: 'mention',\n /** Ссылка. Адрес лежит в `url`, а не в `tag`. */\n Link: 'link',\n Bold: 'bold',\n Italic: 'italic',\n Underline: 'underline',\n /** Зачёркнутый. */\n Strike: 'strike',\n /** Спойлер: текст скрыт до нажатия. */\n Spoiler: 'spoiler',\n /** Моноширинный. */\n Monospace: 'monospace',\n Quote: 'quote',\n} as const);\nexport type SpanType = Loose<(typeof SpanType)[keyof typeof SpanType]>;\n\n/** На что подаётся жалоба. */\nexport const ReportTargetType = Object.freeze({\n Post: 'post',\n Comment: 'comment',\n User: 'user',\n} as const);\nexport type ReportTargetType = (typeof ReportTargetType)[keyof typeof ReportTargetType];\n\n/** Причина жалобы. Множество закрытое. */\nexport const ReportReason = Object.freeze({\n Spam: 'spam',\n Violence: 'violence',\n Hate: 'hate',\n Adult: 'adult',\n Fraud: 'fraud',\n Other: 'other',\n} as const);\nexport type ReportReason = (typeof ReportReason)[keyof typeof ReportReason];\n\n/** Состояние realtime-соединения. */\nexport const RealtimeStatus = Object.freeze({\n Connecting: 'connecting',\n Connected: 'connected',\n Error: 'error',\n Disconnected: 'disconnected',\n} as const);\nexport type RealtimeStatus = (typeof RealtimeStatus)[keyof typeof RealtimeStatus];\n\n/** Состояние сервиса платформы. Тип открытый. */\nexport const ServiceState = Object.freeze({\n /** Работает штатно. */\n Operational: 'operational',\n /** Работает с деградацией. */\n Degraded: 'degraded',\n /** Недоступен. */\n Downtime: 'downtime',\n} as const);\nexport type ServiceState = Loose<(typeof ServiceState)[keyof typeof ServiceState]>;\n\n/** Вид происшествия в истории сервиса. Тип открытый. */\nexport const IncidentKind = Object.freeze({\n /** Недоступен. */\n Down: 'down',\n /** Деградация. */\n Degraded: 'deg',\n} as const);\nexport type IncidentKind = Loose<(typeof IncidentKind)[keyof typeof IncidentKind]>;\n\n/**\n * Уровень доступа к разделу профиля.\n *\n * Общий набор значений для полей `wallAccess` и `likesVisibility` настроек приватности.\n * Тип открытый: сервер может прислать значение вне этого перечня.\n */\nexport const AccessType = Object.freeze({\n /** Никто. */\n Nobody: 'nobody',\n /** Только взаимные подписки. */\n Mutual: 'mutual',\n /** Подписчики. */\n Followers: 'followers',\n /** Все. */\n Everyone: 'everyone',\n} as const);\nexport type AccessType = Loose<(typeof AccessType)[keyof typeof AccessType]>;\n\n/** Кто может писать на стену профиля. Псевдоним {@link AccessType}. */\nexport const WallAccess = AccessType;\nexport type WallAccess = AccessType;\n\n/** Кто видит реакции пользователя. Псевдоним {@link AccessType}. */\nexport const LikesVisibility = AccessType;\nexport type LikesVisibility = AccessType;\n\n/**\n * Канонический тип уведомления (новое поколение имён).\n *\n * REST-эндпоинт `/api/notifications/` отдаёт старые имена (`like`, `comment`, `reply`,\n * `repost`, `mention`), SSE-поток — новые. Библиотека приводит их к этому набору,\n * сохраняя исходное значение в поле `rawType`.\n */\nexport const NotificationType = Object.freeze({\n /** Реакция на пост. Старое имя — `like`. */\n PostReaction: 'post_reaction',\n /** Комментарий к посту. Старое имя — `comment`. */\n PostComment: 'post_comment',\n /** Ответ на комментарий. Старое имя — `reply`. */\n CommentReply: 'comment_reply',\n /** Репост. Старое имя — `repost`. */\n PostRepost: 'post_repost',\n /** Упоминание в посте. Старое имя — `mention`. */\n PostMention: 'post_mention',\n /** Реакция на комментарий. */\n CommentReaction: 'comment_reaction',\n /** Упоминание в комментарии. */\n CommentMention: 'comment_mention',\n /** Запись на вашей стене. */\n WallPost: 'wall_post',\n /** На вас подписались. */\n Follow: 'follow',\n /** Заявка на подписку (закрытый профиль). */\n FollowRequest: 'follow_request',\n /** Заявка на подписку принята. */\n FollowAccepted: 'follow_accepted',\n /** Верификация одобрена. Приходит только по REST. */\n VerificationApproved: 'verification_approved',\n /** Верификация отклонена. Приходит только по REST. */\n VerificationRejected: 'verification_rejected',\n} as const);\nexport type NotificationType = Loose<(typeof NotificationType)[keyof typeof NotificationType]>;\n\n/**\n * Тип взаимодействия с контентом в телеметрии (`POST /api/v1/x`, поле `t`).\n *\n * Кодируется числом.\n */\nexport const InteractionType = Object.freeze({\n /** Открытие фотографии. */\n PhotoOpen: 1,\n /** Прогресс просмотра видео. Несёт поля `pm`/`dm`. */\n VideoProgress: 2,\n} as const);\nexport type InteractionType = (typeof InteractionType)[keyof typeof InteractionType];\n\n/**\n * Источник показа поста в телеметрии (поле `s`).\n *\n * Кодируется числом. Поле применимо к источникам `PostPage` и `Link`; для лент источник\n * передаётся контекстом `sc`.\n */\nexport const ViewSource = Object.freeze({\n FeedGlobal: 1,\n FeedFollowing: 2,\n FeedClan: 3,\n Profile: 4,\n Hashtag: 5,\n PostPage: 6,\n Link: 7,\n Search: 8,\n} as const);\nexport type ViewSource = (typeof ViewSource)[keyof typeof ViewSource];\n\n/**\n * Причина завершения просмотра поста в телеметрии (`POST /api/v1/i`, поле `r`).\n *\n * Кодируется числом.\n */\nexport const ViewReason = Object.freeze({\n /** Пост ушёл из зоны видимости при обычной прокрутке. */\n Normal: 0,\n /** Потеря фокуса окна. */\n Blur: 1,\n /** Вкладка скрыта. */\n Hidden: 2,\n /** Уход со страницы (`pagehide`). */\n PageHide: 3,\n /** Элемент перестал наблюдаться. */\n Unobserve: 4,\n /** Достигнут порог времени просмотра. */\n ThresholdMet: 5,\n} as const);\nexport type ViewReason = (typeof ViewReason)[keyof typeof ViewReason];\n\n/**\n * Строковые коды ошибок из поля `code`.\n *\n * Ключи намеренно повторяют написание сервера: код из ответа API можно найти здесь\n * поиском один в один, без мысленного перевода регистра.\n *\n * Список открыт — сервер может добавить новый код, и это не должно ломать типизацию.\n *\n * @example\n * ```ts\n * if (err.hasCode(ItdErrorCode.OTP_INVALID)) await restartOtpFlow();\n * ```\n */\nexport const ItdErrorCode = Object.freeze({\n BAD_REQUEST: 'BAD_REQUEST',\n UNAUTHORIZED: 'UNAUTHORIZED',\n ACCESS_DENIED: 'ACCESS_DENIED',\n ENTITY_NOT_FOUND: 'ENTITY_NOT_FOUND',\n ENTITY_ALREADY_EXISTS: 'ENTITY_ALREADY_EXISTS',\n VALIDATION_ERROR: 'VALIDATION_ERROR',\n BUSINESS_RULE_VIOLATION: 'BUSINESS_RULE_VIOLATION',\n RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',\n UNKNOWN_ERROR: 'UNKNOWN_ERROR',\n /** Сервер отвечает так на `404`, `ENTITY_NOT_FOUND` в этом случае не приходит. */\n NOT_FOUND: 'NOT_FOUND',\n /** На практике не приходит: вместо него сервер шлёт `TURNSTILE_VERIFICATION_FAILED`. */\n CAPTCHA_FAILED: 'CAPTCHA_FAILED',\n /** Капча не пройдена: токен Turnstile недействителен, просрочен или уже использован. */\n TURNSTILE_VERIFICATION_FAILED: 'TURNSTILE_VERIFICATION_FAILED',\n OTP_INVALID: 'OTP_INVALID',\n /** `flowToken` неизвестен или просрочен — поток подтверждения нужно начинать заново. */\n INVALID_FLOW_TOKEN: 'INVALID_FLOW_TOKEN',\n ACCOUNT_DEACTIVATED: 'ACCOUNT_DEACTIVATED',\n ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED: 'ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED',\n ACCOUNT_INVALID_CREDENTIALS: 'ACCOUNT_INVALID_CREDENTIALS',\n ACCOUNT_TEMPORARILY_LOCKED: 'ACCOUNT_TEMPORARILY_LOCKED',\n ACCOUNT_CURRENT_PASSWORD_INCORRECT: 'ACCOUNT_CURRENT_PASSWORD_INCORRECT',\n SESSION_EXPIRED: 'SESSION_EXPIRED',\n SESSION_REVOKED: 'SESSION_REVOKED',\n SESSION_INVALID_REFRESH_TOKEN: 'SESSION_INVALID_REFRESH_TOKEN',\n /** Запрос обновления пришёл без cookie `refresh_token` — продлевать нечего. */\n REFRESH_TOKEN_MISSING: 'REFRESH_TOKEN_MISSING',\n /** Cookie `refresh_token` есть, но сессии за ней уже нет: отозвана или истекла. */\n SESSION_NOT_FOUND: 'SESSION_NOT_FOUND',\n MISSING_FLOW_TOKEN: 'MISSING_FLOW_TOKEN',\n PROFILE_USERNAME_TAKEN: 'PROFILE_USERNAME_TAKEN',\n PROFILE_RESTRICTION_ACTIVE: 'PROFILE_RESTRICTION_ACTIVE',\n PROFILE_MODIFICATION_RESTRICTED: 'PROFILE_MODIFICATION_RESTRICTED',\n CONTENT_MODERATION_FAILED: 'CONTENT_MODERATION_FAILED',\n FILE_TOO_LARGE: 'FILE_TOO_LARGE',\n UNSUPPORTED_FILE_TYPE: 'UNSUPPORTED_FILE_TYPE',\n UPLOAD_FAILED: 'UPLOAD_FAILED',\n VIDEO_REQUIRES_VERIFICATION: 'VIDEO_REQUIRES_VERIFICATION',\n PHONE_VERIFICATION_REQUIRED: 'PHONE_VERIFICATION_REQUIRED',\n WRITE_ACCESS_RESTRICTED: 'WRITE_ACCESS_RESTRICTED',\n} as const);\nexport type ItdErrorCode = Loose<(typeof ItdErrorCode)[keyof typeof ItdErrorCode]>;\n","import type { Unsubscribe } from '../core/emitter.js';\nimport type { RealtimeContext } from './updates.js';\n\n/** Продолжает цепочку промежуточных обработчиков потока. */\nexport type RealtimeNext = () => Promise<void>;\n\n/** Обрабатывает обновление потока до его передачи подписчикам. */\nexport type RealtimeMiddleware<C extends RealtimeContext = RealtimeContext> = (\n context: C,\n next: RealtimeNext,\n) => void | Promise<void>;\n\n/** Асинхронный обработчик нормализованного обновления потока. */\nexport type RealtimeHandler<C extends RealtimeContext = RealtimeContext> = (\n context: C,\n) => unknown | Promise<unknown>;\n\n/** Условие отбора контекста потока. */\nexport type RealtimePredicate = (context: RealtimeContext) => boolean;\n\n/** Проверка, сужающая тип контекста потока. */\nexport type RealtimeTypeGuard<C extends RealtimeContext> = (\n context: RealtimeContext,\n) => context is C;\n\n/** Ключи, по которым обновления нельзя обрабатывать одновременно. */\nexport type RealtimeSequentializer = (\n context: RealtimeContext,\n) => PropertyKey | readonly PropertyKey[] | undefined;\n\nconst REALTIME_MIDDLEWARE_SNAPSHOT = Symbol('itd-api.realtime.middlewareSnapshot');\n\ntype SnapshottingRealtimeMiddleware = RealtimeMiddleware & {\n [REALTIME_MIDDLEWARE_SNAPSHOT]?: () => RealtimeMiddleware;\n};\n\n/** @internal */\nexport function captureRealtimeMiddleware(middleware: RealtimeMiddleware): RealtimeMiddleware {\n const capture = (middleware as SnapshottingRealtimeMiddleware)[REALTIME_MIDDLEWARE_SNAPSHOT];\n if (!capture) return middleware;\n\n const snapshot = capture();\n if (typeof snapshot !== 'function') {\n throw new TypeError('Снимок промежуточного обработчика должен быть функцией');\n }\n return snapshot;\n}\n\n/** @internal */\nexport function withRealtimeMiddlewareSnapshot(\n middleware: RealtimeMiddleware,\n capture: () => RealtimeMiddleware,\n): RealtimeMiddleware {\n Object.defineProperty(middleware, REALTIME_MIDDLEWARE_SNAPSHOT, { value: capture });\n return middleware;\n}\n\ninterface HandlerRegistration {\n readonly predicate: RealtimePredicate;\n readonly handler: RealtimeHandler;\n}\n\ninterface DispatchWork {\n readonly context: RealtimeContext;\n readonly middleware: readonly RealtimeMiddleware[];\n readonly handlers: readonly HandlerRegistration[];\n readonly keys: readonly PropertyKey[];\n}\n\nexport interface RealtimeDispatcherOptions {\n concurrency: number;\n sequentialize?: RealtimeSequentializer | undefined;\n}\n\nexport interface RealtimeDispatcherHooks {\n deliver: (context: RealtimeContext) => void;\n middlewareError: (error: unknown, context: RealtimeContext) => void;\n handlerError: (error: unknown, context: RealtimeContext) => void;\n}\n\n/** Выполняет промежуточные обработчики по порядку и запрещает повторный вызов `next()`. */\nexport async function runRealtimeMiddleware(\n middleware: readonly RealtimeMiddleware[],\n context: RealtimeContext,\n terminal: RealtimeNext,\n): Promise<void> {\n let lastIndex = -1;\n\n const dispatch = async (index: number): Promise<void> => {\n if (index <= lastIndex) throw new Error('next() в промежуточном обработчике вызван повторно');\n lastIndex = index;\n\n const current = middleware[index];\n if (!current) {\n await terminal();\n return;\n }\n\n let downstream: Promise<void> | undefined;\n let duplicateCalls: Promise<void> | undefined;\n let failure: { error: unknown } | undefined;\n\n const next: RealtimeNext = () => {\n if (!downstream) {\n downstream = dispatch(index + 1);\n return downstream;\n }\n\n const duplicate = Promise.reject(\n new Error('next() в одном промежуточном обработчике вызван повторно'),\n );\n duplicateCalls = Promise.all(duplicateCalls ? [duplicateCalls, duplicate] : [duplicate]).then(\n () => undefined,\n );\n return duplicate;\n };\n\n try {\n await current(context, next);\n } catch (error) {\n failure = { error };\n }\n\n try {\n await downstream;\n await duplicateCalls;\n } catch (error) {\n failure ??= { error };\n }\n\n if (failure) throw failure.error;\n };\n\n await dispatch(0);\n}\n\n/** Планирует нормализованные обновления и отслеживает незавершённые обработчики. */\nexport class RealtimeDispatcher {\n readonly #options: RealtimeDispatcherOptions;\n readonly #hooks: RealtimeDispatcherHooks;\n readonly #middleware: RealtimeMiddleware[] = [];\n readonly #handlers: HandlerRegistration[] = [];\n readonly #queue: DispatchWork[] = [];\n readonly #activeKeys = new Set<PropertyKey>();\n readonly #drainWaiters = new Set<() => void>();\n\n #active = 0;\n\n constructor(options: RealtimeDispatcherOptions, hooks: RealtimeDispatcherHooks) {\n this.#options = options;\n this.#hooks = hooks;\n }\n\n use(middleware: RealtimeMiddleware): Unsubscribe {\n this.#middleware.push(middleware);\n return () => {\n const index = this.#middleware.indexOf(middleware);\n if (index >= 0) this.#middleware.splice(index, 1);\n };\n }\n\n on(predicate: RealtimePredicate, handler: RealtimeHandler): Unsubscribe {\n const registration = { predicate, handler };\n this.#handlers.push(registration);\n\n return () => {\n const index = this.#handlers.indexOf(registration);\n if (index >= 0) this.#handlers.splice(index, 1);\n };\n }\n\n dispatch(context: RealtimeContext): void {\n let keys: readonly PropertyKey[];\n let middleware: readonly RealtimeMiddleware[];\n try {\n keys = this.#keysFor(context);\n middleware = this.#middleware.map(captureRealtimeMiddleware);\n } catch (error) {\n this.#hooks.middlewareError(error, context);\n return;\n }\n\n this.#queue.push({\n context,\n middleware,\n handlers: [...this.#handlers],\n keys,\n });\n this.#pump();\n }\n\n /** Отбрасывает обновления, обработка которых ещё не началась. */\n clearPending(): void {\n this.#queue.length = 0;\n this.#resolveDrain();\n }\n\n /** Ждёт завершения активных и поставленных в очередь обновлений. */\n drain(): Promise<void> {\n if (this.#active === 0 && this.#queue.length === 0) return Promise.resolve();\n return new Promise<void>((resolve) => this.#drainWaiters.add(resolve));\n }\n\n #keysFor(context: RealtimeContext): readonly PropertyKey[] {\n const value = this.#options.sequentialize?.(context);\n if (value === undefined) return [];\n\n const values = Array.isArray(value) ? value : [value];\n for (const key of values) {\n if (typeof key !== 'string' && typeof key !== 'number' && typeof key !== 'symbol') {\n throw new TypeError('sequentialize() должен возвращать PropertyKey или их список');\n }\n }\n return [...new Set(values)];\n }\n\n #pump(): void {\n while (this.#active < this.#options.concurrency) {\n const blockedKeys = new Set(this.#activeKeys);\n const index = this.#queue.findIndex(({ keys }) => {\n const runnable = keys.every((key) => !blockedKeys.has(key));\n if (!runnable) {\n for (const key of keys) blockedKeys.add(key);\n }\n return runnable;\n });\n if (index < 0) break;\n\n const [work] = this.#queue.splice(index, 1);\n if (!work) break;\n\n this.#active += 1;\n for (const key of work.keys) this.#activeKeys.add(key);\n void this.#run(work);\n }\n\n this.#resolveDrain();\n }\n\n async #run(work: DispatchWork): Promise<void> {\n try {\n await runRealtimeMiddleware(work.middleware, work.context, async () => {\n for (const { predicate, handler } of work.handlers) {\n try {\n if (predicate(work.context)) await handler(work.context);\n } catch (error) {\n this.#hooks.handlerError(error, work.context);\n }\n }\n\n this.#hooks.deliver(work.context);\n });\n } catch (error) {\n this.#hooks.middlewareError(error, work.context);\n } finally {\n this.#active -= 1;\n for (const key of work.keys) this.#activeKeys.delete(key);\n this.#pump();\n }\n }\n\n #resolveDrain(): void {\n if (this.#active !== 0 || this.#queue.length !== 0) return;\n for (const resolve of this.#drainWaiters) resolve();\n this.#drainWaiters.clear();\n }\n}\n","/** Событие, пришедшее по каналу реального времени. */\nexport interface TransportEvent {\n /** Имя события: `notification`, `unread_count` и другие. */\n name: string;\n /** Полезная нагрузка, уже разобранная из JSON. */\n data: unknown;\n}\n\n/** Что транспорт получает от клиента при подключении. */\nexport interface TransportContext {\n /** Базовый URL API. */\n baseUrl: string;\n /** Реализация `fetch`. */\n fetch: typeof fetch;\n /**\n * Общие заголовки клиента: `User-Agent`, `X-Device-Id`, заголовки конфигурации\n * и cookie для указанного адреса.\n */\n baseHeaders: (url: string) => Promise<Headers>;\n /** Текущий токен доступа. */\n getToken: () => Promise<string | null>;\n /** Отмена подключения. */\n signal: AbortSignal;\n /** Сообщает о полученном событии. */\n onEvent: (event: TransportEvent) => void;\n /** Сообщает о разобранном, но некорректном сообщении. Соединение при этом живёт. */\n onParseError: (error: unknown, raw: string) => void;\n /** Вызывается, когда соединение установлено. */\n onOpen: () => void;\n}\n\n/**\n * Канал получения событий в реальном времени.\n *\n * Сейчас у платформы один такой канал — поток `text/event-stream`. Абстракция нужна\n * на будущее: политика безопасности сайта уже разрешает `wss://*.xn--d1ah4a.com`,\n * и когда появится WebSocket, достаточно будет добавить ещё одну реализацию этого\n * интерфейса. Переподключение, обновление токена и разбор уведомлений от транспорта\n * не зависят.\n */\nexport interface RealtimeTransport {\n /** Понятное имя для логов и диагностики. */\n readonly name: string;\n\n /**\n * Держит соединение, пока оно живо.\n *\n * Должен завершиться, когда поток закрылся, и бросить исключение при ошибке.\n * Отмена через `context.signal` должна приводить к `AbortError`.\n */\n connect(context: TransportContext): Promise<void>;\n}\n\n/** Ошибка, по которой видно, что сервер отверг авторизацию потока. */\nexport class UnauthorizedStreamError extends Error {\n constructor() {\n super('Поток уведомлений отверг токен доступа');\n this.name = 'UnauthorizedStreamError';\n }\n}\n","import { type ItdClock, systemClock } from '../core/clock.js';\nimport { pickArray, pickNumber } from '../core/unwrap.js';\nimport { joinUrl } from '../core/url.js';\nimport type { RealtimeTransport, TransportContext } from './transport.js';\nimport { UnauthorizedStreamError } from './transport.js';\n\n/** Настройки опроса. */\nexport interface PollTransportOptions {\n /** Часы опроса. Обычно подменяются только в тестах. */\n clock?: ItdClock;\n /** Как часто опрашивать сервер, мс. По умолчанию 15 000. */\n interval?: number;\n /** Сколько уведомлений запрашивать за раз. По умолчанию 20. */\n limit?: number;\n}\n\n/**\n * Запасной транспорт: обычный опрос REST вместо потока.\n *\n * Нужен там, где `fetch` не умеет отдавать тело по частям, — например в части сборок\n * React Native. Наружу выдаёт те же события, что и {@link SseTransport}, поэтому\n * вызывающий код разницы не замечает.\n *\n * Новыми считаются уведомления, которых не было в предыдущем ответе; чтобы список\n * известных не рос бесконечно, он ограничен последними двумя страницами.\n */\nexport class PollTransport implements RealtimeTransport {\n readonly name = 'poll';\n\n readonly #interval: number;\n readonly #limit: number;\n readonly #clock: ItdClock;\n\n constructor(options: PollTransportOptions = {}) {\n this.#clock = options.clock ?? systemClock;\n this.#interval = options.interval ?? 15_000;\n this.#limit = options.limit ?? 20;\n }\n\n async connect(context: TransportContext): Promise<void> {\n const seen = new Set<string>();\n let firstRun = true;\n let lastUnreadCount: number | undefined;\n\n while (!context.signal.aborted) {\n const token = await context.getToken();\n if (!token) throw new UnauthorizedStreamError();\n\n const url = `${joinUrl(context.baseUrl, '/api/notifications/')}?limit=${this.#limit}&offset=0`;\n\n const headers = await context.baseHeaders(url);\n headers.set('Accept', 'application/json');\n headers.set('Authorization', `Bearer ${token}`);\n\n const response = await context.fetch(url, {\n method: 'GET',\n headers,\n signal: context.signal,\n });\n\n if (response.status === 401) throw new UnauthorizedStreamError();\n if (!response.ok) throw new Error(`Опрос уведомлений вернул статус ${response.status}`);\n\n // Соединение считается установленным только после первого успешного ответа: иначе\n // при постоянно недоступной сети каждая попытка обнуляла бы счётчик и maxAttempts\n // не срабатывал бы никогда.\n context.onOpen();\n\n const body: unknown = await response.json();\n const payload =\n typeof body === 'object' && body !== null && 'data' in body\n ? (body as { data: unknown }).data\n : body;\n\n const items = pickArray<Record<string, unknown>>(payload, 'notifications');\n\n for (const item of [...items].reverse()) {\n const id = typeof item.id === 'string' ? item.id : undefined;\n if (!id || seen.has(id)) continue;\n\n seen.add(id);\n // На первом проходе список уже существующих уведомлений не считается новыми\n // событиями: иначе подключение сразу выдало бы всю историю.\n if (!firstRun) context.onEvent({ name: 'notification', data: { payload: item } });\n }\n\n // Ограничиваем память: держим только последние две страницы идентификаторов.\n if (seen.size > this.#limit * 2) {\n const excess = [...seen].slice(0, seen.size - this.#limit * 2);\n for (const id of excess) seen.delete(id);\n }\n\n const count = await this.#readCount(context, headers);\n if (count !== undefined && count !== lastUnreadCount) {\n lastUnreadCount = count;\n context.onEvent({ name: 'unread_count', data: { payload: { count } } });\n }\n\n firstRun = false;\n await this.#wait(context.signal);\n }\n }\n\n async #readCount(context: TransportContext, headers: Headers): Promise<number | undefined> {\n try {\n const response = await context.fetch(joinUrl(context.baseUrl, '/api/notifications/count'), {\n method: 'GET',\n headers,\n signal: context.signal,\n });\n\n if (!response.ok) return undefined;\n\n const body: unknown = await response.json();\n const payload =\n typeof body === 'object' && body !== null && 'data' in body\n ? (body as { data: unknown }).data\n : body;\n\n return pickNumber(payload, 'count', 0);\n } catch {\n // Счётчик — вспомогательная величина: его недоступность не должна рвать опрос.\n return undefined;\n }\n }\n\n /** Ждёт следующего опроса, прерываясь при отмене. */\n #wait(signal: AbortSignal): Promise<void> {\n // Подписка на уже сработавший сигнал не вызывается никогда — без этой проверки\n // отмена, пришедшая перед самым ожиданием, стоила бы лишнего интервала.\n if (signal.aborted) return Promise.resolve();\n\n return new Promise<void>((resolve) => {\n const cancel = this.#clock.schedule(finish, this.#interval);\n\n function finish() {\n cancel();\n signal.removeEventListener('abort', finish);\n resolve();\n }\n\n signal.addEventListener('abort', finish, { once: true });\n });\n }\n}\n","/**\n * Паузы перед попытками переподключения, мс.\n *\n * Значения совпадают с теми, что использует сайт итд.com, — поведение библиотеки\n * не отличается от привычного пользователю.\n */\nexport const RECONNECT_BACKOFF = Object.freeze([1000, 2000, 4000, 8000, 16000, 30000]);\n\n/** Доля случайного разброса паузы. */\nexport const RECONNECT_JITTER = 0.3;\n\n/**\n * Сколько раз пытаться переподключиться подряд.\n *\n * После исчерпания поток сообщает `giveup` и ждёт ручного `connect()`.\n */\nexport const MAX_RECONNECT_ATTEMPTS = 15;\n\n/** Настройки переподключения. */\nexport interface ReconnectOptions {\n /** Таблица пауз. Последнее значение действует для всех дальнейших попыток. */\n backoff?: readonly number[];\n /** Доля разброса, 0…1. */\n jitter?: number;\n /** Предел числа попыток. */\n maxAttempts?: number;\n}\n\n/**\n * Вычисляет паузу перед попыткой переподключения.\n *\n * Разброс нужен, чтобы клиенты, отключившиеся одновременно из-за перезапуска сервера,\n * не вернулись тоже одновременно.\n *\n * @param attempt номер попытки, начиная с нуля\n * @param random источник случайности; подменяется в тестах\n */\nexport function reconnectDelay(\n attempt: number,\n options: ReconnectOptions = {},\n random: () => number = Math.random,\n): number {\n const backoff = options.backoff ?? RECONNECT_BACKOFF;\n const jitter = options.jitter ?? RECONNECT_JITTER;\n\n const base = backoff[Math.min(attempt, backoff.length - 1)] ?? 30_000;\n const spread = base * jitter * (random() * 2 - 1);\n\n return Math.max(0, Math.round(base + spread));\n}\n","class ParseError extends Error {\n constructor(message, options) {\n super(message), this.name = \"ParseError\", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line;\n }\n}\nconst LF = 10, CR = 13, SPACE = 32;\nfunction noop(_arg) {\n}\nfunction createParser(config) {\n if (typeof config == \"function\")\n throw new TypeError(\n \"`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?\"\n );\n const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config, pendingFragments = [];\n let pendingFragmentsLength = 0, isFirstChunk = !0, id, data = \"\", dataLines = 0, eventType, terminated = !1;\n function feed(chunk) {\n if (terminated)\n throw new Error(\n \"Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing.\"\n );\n if (isFirstChunk && (isFirstChunk = !1, chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191 && (chunk = chunk.slice(3))), pendingFragments.length === 0) {\n const trailing2 = processLines(chunk);\n trailing2 !== \"\" && (pendingFragments.push(trailing2), pendingFragmentsLength = trailing2.length), checkBufferSize();\n return;\n }\n if (chunk.indexOf(`\n`) === -1 && chunk.indexOf(\"\\r\") === -1) {\n pendingFragments.push(chunk), pendingFragmentsLength += chunk.length, checkBufferSize();\n return;\n }\n pendingFragments.push(chunk);\n const input = pendingFragments.join(\"\");\n pendingFragments.length = 0, pendingFragmentsLength = 0;\n const trailing = processLines(input);\n trailing !== \"\" && (pendingFragments.push(trailing), pendingFragmentsLength = trailing.length), checkBufferSize();\n }\n function checkBufferSize() {\n maxBufferSize !== void 0 && (pendingFragmentsLength + data.length <= maxBufferSize || (terminated = !0, pendingFragments.length = 0, pendingFragmentsLength = 0, id = void 0, data = \"\", dataLines = 0, eventType = void 0, onError(\n new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {\n type: \"max-buffer-size-exceeded\"\n })\n )));\n }\n function processLines(chunk) {\n let searchIndex = 0;\n if (chunk.indexOf(\"\\r\") === -1) {\n let lfIndex = chunk.indexOf(`\n`, searchIndex);\n for (; lfIndex !== -1; ) {\n if (searchIndex === lfIndex) {\n dataLines > 0 && onEvent({ id, event: eventType, data }), id = void 0, data = \"\", dataLines = 0, eventType = void 0, searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`\n`, searchIndex);\n continue;\n }\n const firstCharCode = chunk.charCodeAt(searchIndex);\n if (isDataPrefix(chunk, searchIndex, firstCharCode)) {\n const valueStart = chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5, value = chunk.slice(valueStart, lfIndex);\n if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {\n onEvent({ id, event: eventType, data: value }), id = void 0, data = \"\", eventType = void 0, searchIndex = lfIndex + 2, lfIndex = chunk.indexOf(`\n`, searchIndex);\n continue;\n }\n data = dataLines === 0 ? value : `${data}\n${value}`, dataLines++;\n } else isEventPrefix(chunk, searchIndex, firstCharCode) ? eventType = chunk.slice(\n chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,\n lfIndex\n ) || void 0 : parseLine(chunk, searchIndex, lfIndex);\n searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`\n`, searchIndex);\n }\n return chunk.slice(searchIndex);\n }\n for (; searchIndex < chunk.length; ) {\n const crIndex = chunk.indexOf(\"\\r\", searchIndex), lfIndex = chunk.indexOf(`\n`, searchIndex);\n let lineEnd = -1;\n if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = crIndex < lfIndex ? crIndex : lfIndex : crIndex !== -1 ? crIndex === chunk.length - 1 ? lineEnd = -1 : lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1)\n break;\n parseLine(chunk, searchIndex, lineEnd), searchIndex = lineEnd + 1, chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF && searchIndex++;\n }\n return chunk.slice(searchIndex);\n }\n function parseLine(chunk, start, end) {\n if (start === end) {\n dispatchEvent();\n return;\n }\n const firstCharCode = chunk.charCodeAt(start);\n if (isDataPrefix(chunk, start, firstCharCode)) {\n const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5, value2 = chunk.slice(valueStart, end);\n data = dataLines === 0 ? value2 : `${data}\n${value2}`, dataLines++;\n return;\n }\n if (isEventPrefix(chunk, start, firstCharCode)) {\n eventType = chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || void 0;\n return;\n }\n if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {\n const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);\n id = value2.includes(\"\\0\") ? void 0 : value2;\n return;\n }\n if (firstCharCode === 58) {\n if (onComment) {\n const line2 = chunk.slice(start, end);\n onComment(line2.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1));\n }\n return;\n }\n const line = chunk.slice(start, end), fieldSeparatorIndex = line.indexOf(\":\");\n if (fieldSeparatorIndex === -1) {\n processField(line, \"\", line);\n return;\n }\n const field = line.slice(0, fieldSeparatorIndex), offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1, value = line.slice(fieldSeparatorIndex + offset);\n processField(field, value, line);\n }\n function processField(field, value, line) {\n switch (field) {\n case \"event\":\n eventType = value || void 0;\n break;\n case \"data\":\n data = dataLines === 0 ? value : `${data}\n${value}`, dataLines++;\n break;\n case \"id\":\n id = value.includes(\"\\0\") ? void 0 : value;\n break;\n case \"retry\":\n /^\\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(\n new ParseError(`Invalid \\`retry\\` value: \"${value}\"`, {\n type: \"invalid-retry\",\n value,\n line\n })\n );\n break;\n default:\n onError(\n new ParseError(\n `Unknown field \"${field.length > 20 ? `${field.slice(0, 20)}\\u2026` : field}\"`,\n { type: \"unknown-field\", field, value, line }\n )\n );\n break;\n }\n }\n function dispatchEvent() {\n dataLines > 0 && onEvent({\n id,\n event: eventType,\n data\n }), id = void 0, data = \"\", dataLines = 0, eventType = void 0;\n }\n function reset(options = {}) {\n if (options.consume && pendingFragments.length > 0) {\n const incompleteLine = pendingFragments.join(\"\");\n parseLine(incompleteLine, 0, incompleteLine.length);\n }\n isFirstChunk = !0, id = void 0, data = \"\", dataLines = 0, eventType = void 0, pendingFragments.length = 0, pendingFragmentsLength = 0, terminated = !1;\n }\n return { feed, reset };\n}\nfunction isDataPrefix(chunk, i, firstCharCode) {\n return firstCharCode === 100 && chunk.charCodeAt(i + 1) === 97 && chunk.charCodeAt(i + 2) === 116 && chunk.charCodeAt(i + 3) === 97 && chunk.charCodeAt(i + 4) === 58;\n}\nfunction isEventPrefix(chunk, i, firstCharCode) {\n return firstCharCode === 101 && chunk.charCodeAt(i + 1) === 118 && chunk.charCodeAt(i + 2) === 101 && chunk.charCodeAt(i + 3) === 110 && chunk.charCodeAt(i + 4) === 116 && chunk.charCodeAt(i + 5) === 58;\n}\nexport {\n ParseError,\n createParser\n};\n//# sourceMappingURL=index.js.map\n","import { createParser } from 'eventsource-parser';\nimport { type ItdClock, systemClock } from '../core/clock.js';\nimport { joinUrl } from '../core/url.js';\nimport {\n type RealtimeTransport,\n type TransportContext,\n UnauthorizedStreamError,\n} from './transport.js';\n\n/** Путь потока уведомлений. */\nexport const STREAM_PATH = '/api/notifications/stream';\n\n/** Настройки SSE-транспорта. */\nexport interface SseTransportOptions {\n /** Часы потока. Обычно подменяются только в тестах. */\n clock?: ItdClock;\n /**\n * Сколько миллисекунд ждать данных, прежде чем считать соединение мёртвым.\n *\n * Сервер не присылает keep-alive, а оборванное TCP-соединение может не закрыться\n * само — без этой проверки поток «тихо умирает» и новых уведомлений не приходит.\n * По умолчанию 90 000. `0` отключает проверку.\n */\n idleTimeout?: number;\n /**\n * Сколько миллисекунд ждать ответа на запрос потока, прежде чем оборвать попытку.\n *\n * Проверка молчания ({@link idleTimeout}) начинается только после получения тела ответа.\n * Если `fetch` завис на установке соединения, без этого таймаута переподключение не\n * запустится до системного сетевого таймаута. По умолчанию 20 000. `0` отключает проверку.\n */\n handshakeTimeout?: number;\n}\n\n/**\n * Транспорт поверх Server-Sent Events.\n *\n * Нативный `EventSource` не подходит: он не умеет отправлять заголовок `Authorization`,\n * а поток требует Bearer-токен. Поэтому используется `fetch` с чтением тела по частям.\n *\n * Разбор кадров делегирован `eventsource-parser`, который корректно обрабатывает\n * многострочный `data:`, перевод строки `\\r\\n`, `data:` без пробела и кадр, разорванный\n * между сетевыми чанками. В разборе на сайте итд.com каждый из этих случаев обрабатывается\n * неверно.\n */\nexport class SseTransport implements RealtimeTransport {\n readonly name = 'sse';\n\n readonly #idleTimeout: number;\n readonly #handshakeTimeout: number;\n readonly #clock: ItdClock;\n /** Идентификатор последнего события — отправляется при переподключении. */\n #lastEventId: string | undefined;\n\n constructor(options: SseTransportOptions = {}) {\n this.#clock = options.clock ?? systemClock;\n this.#idleTimeout = options.idleTimeout ?? 90_000;\n this.#handshakeTimeout = options.handshakeTimeout ?? 20_000;\n }\n\n async connect(context: TransportContext): Promise<void> {\n const token = await context.getToken();\n if (!token) throw new UnauthorizedStreamError();\n\n const url = joinUrl(context.baseUrl, STREAM_PATH);\n\n const headers = await context.baseHeaders(url);\n headers.set('Accept', 'text/event-stream');\n headers.set('Authorization', `Bearer ${token}`);\n headers.set('Cache-Control', 'no-cache');\n\n // Сервер пока не присылает id, но поддержка не мешает и пригодится, если начнёт.\n if (this.#lastEventId) headers.set('Last-Event-ID', this.#lastEventId);\n\n // Отдельный контроллер, связанный с сигналом потока: помимо внешней отмены он позволяет\n // оборвать зависшее рукопожатие своим таймаутом. Живёт до конца чтения тела.\n const controller = new AbortController();\n const relayAbort = () => controller.abort(context.signal.reason);\n if (context.signal.aborted) controller.abort(context.signal.reason);\n else context.signal.addEventListener('abort', relayAbort, { once: true });\n\n try {\n const response = await this.#handshake(url, headers, context, controller);\n\n if (response.status === 401) throw new UnauthorizedStreamError();\n if (!response.ok) throw new Error(`Поток уведомлений вернул статус ${response.status}`);\n if (!response.body) throw new Error('Ответ потока уведомлений пуст');\n\n context.onOpen();\n\n await this.#read(response.body, context);\n } finally {\n context.signal.removeEventListener('abort', relayAbort);\n }\n }\n\n /** Выполняет запрос потока, обрывая его, если ответ не пришёл за отведённое время. */\n async #handshake(\n url: string,\n headers: Headers,\n context: TransportContext,\n controller: AbortController,\n ): Promise<Response> {\n let expired = false;\n const cancelTimer =\n this.#handshakeTimeout > 0\n ? this.#clock.schedule(() => {\n expired = true;\n // Абортим связанный контроллер: `fetch` реджектит, и внешний код переподключится.\n controller.abort(new Error('Истёк таймаут рукопожатия SSE'));\n }, this.#handshakeTimeout)\n : undefined;\n\n try {\n return await context.fetch(url, { method: 'GET', headers, signal: controller.signal });\n } catch (error) {\n if (expired) throw new Error('Поток уведомлений не ответил: истёк таймаут рукопожатия');\n throw error;\n } finally {\n cancelTimer?.();\n }\n }\n\n async #read(body: ReadableStream<Uint8Array>, context: TransportContext): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n\n const parser = createParser({\n onEvent: (message) => {\n if (message.id) this.#lastEventId = message.id;\n\n let data: unknown;\n try {\n data = JSON.parse(message.data);\n } catch (error) {\n // Одно битое сообщение не повод рвать соединение.\n context.onParseError(error, message.data);\n return;\n }\n\n // Имя события может отсутствовать — тогда тип лежит внутри полезной нагрузки.\n const name =\n message.event ??\n (typeof data === 'object' && data !== null && 'type' in data\n ? String((data as { type: unknown }).type)\n : 'message');\n\n context.onEvent({ name, data });\n },\n });\n\n let cancelIdleTimer: (() => void) | undefined;\n\n const armIdleTimer = () => {\n if (this.#idleTimeout <= 0) return;\n cancelIdleTimer?.();\n\n cancelIdleTimer = this.#clock.schedule(() => {\n // Отмена чтения завершит цикл, и внешний код переподключится.\n void reader.cancel(new Error('Поток молчит дольше допустимого')).catch(() => {});\n }, this.#idleTimeout);\n };\n\n armIdleTimer();\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n\n armIdleTimer();\n parser.feed(decoder.decode(value, { stream: true }));\n }\n } finally {\n cancelIdleTimer?.();\n reader.releaseLock?.();\n }\n }\n}\n","import { NotificationType } from '../types/enums.js';\n\n/**\n * Соответствие коротких имён типов уведомлений развёрнутым.\n *\n * Сервер — и в списке, и в потоке событий — присылает короткие имена: `like`, `comment`,\n * `reply`, `repost`, `comment_like`. Развёрнутые (`post_reaction`, `post_comment`)\n * встречаются в оформлении интерфейса, поэтому библиотека приводит типы к ним:\n * они однозначно называют и объект, и действие.\n *\n * Пришедшее значение всегда остаётся в поле `rawType`.\n */\nexport const NOTIFICATION_TYPE_ALIASES: Readonly<Record<string, NotificationType>> = Object.freeze({\n like: NotificationType.PostReaction,\n comment: NotificationType.PostComment,\n comment_like: NotificationType.CommentReaction,\n reply: NotificationType.CommentReply,\n repost: NotificationType.PostRepost,\n mention: NotificationType.PostMention,\n});\n\nconst KNOWN_TYPES = new Set<string>(Object.values(NotificationType));\n\n/**\n * Приводит имя типа к каноническому.\n *\n * Неизвестное значение возвращается без изменений, чтобы не менять смысл нового типа\n * уведомления на другой.\n *\n * @example\n * ```ts\n * canonicalNotificationType('like'); // 'post_reaction'\n * canonicalNotificationType('post_reaction'); // 'post_reaction'\n * canonicalNotificationType('новое_событие'); // 'новое_событие'\n * ```\n */\nexport function canonicalNotificationType(rawType: string): NotificationType {\n return NOTIFICATION_TYPE_ALIASES[rawType] ?? rawType;\n}\n\n/**\n * Известен ли библиотеке этот тип уведомления.\n *\n * Полезно, чтобы решить, показывать ли уведомление, для которого нет своего оформления.\n */\nexport function isKnownNotificationType(type: string): boolean {\n return KNOWN_TYPES.has(canonicalNotificationType(type));\n}\n","import { asString, isRecord } from '../core/unwrap.js';\nimport type { Actor, Notification } from '../types/models.js';\nimport { canonicalNotificationType } from './type-map.js';\n\n/** Событие потока уведомлений после разбора. */\nexport interface NotificationEvent {\n /** Само уведомление в единой форме. */\n notification: Notification;\n /**\n * Актуальное число непрочитанных, если сервер его сообщил.\n *\n * Клиент не увеличивает счётчик сам: значение приходит с сервера.\n */\n unreadCount: number | undefined;\n /** Нужно ли проиграть звук. */\n sound: boolean;\n}\n\nfunction asActor(value: unknown): Actor | undefined {\n if (!isRecord(value)) return undefined;\n const id = asString(value.id);\n if (!id) return undefined;\n\n return {\n id,\n username: asString(value.username) ?? '',\n displayName: asString(value.displayName) ?? '',\n avatar: asString(value.avatar) ?? '',\n ...(typeof value.isFollowing === 'boolean' ? { isFollowing: value.isFollowing } : {}),\n ...(typeof value.isFollowedBy === 'boolean' ? { isFollowedBy: value.isFollowedBy } : {}),\n };\n}\n\n/** Собирает участников: сервер присылает либо одного `actor`, либо массив `actors`. */\nfunction readActors(source: Record<string, unknown>): Actor[] {\n if (Array.isArray(source.actors)) {\n return source.actors.map(asActor).filter((actor): actor is Actor => actor !== undefined);\n }\n\n const single = asActor(source.actor);\n return single ? [single] : [];\n}\n\n/**\n * Приводит уведомление к единой форме.\n *\n * Нужна потому, что REST-список и поток событий описывают одно и то же событие по-разному:\n * различаются имена типов (`like` против `post_reaction`), имена полей\n * (`targetId`/`entityId`, `read`/`isRead`, `preview`/`entityPreview`) и число участников\n * (`actor` против массива `actors`). После приведения объекты из обоих источников\n * можно складывать в один список.\n *\n * Исходные данные не теряются: имя типа с сервера остаётся в `rawType`,\n * весь объект целиком — в `raw`.\n *\n * @param input уведомление из REST-ответа либо полезная нагрузка события потока\n *\n * @example\n * ```ts\n * const fromRest = normalizeNotification(restItem);\n * const fromStream = normalizeNotification(event.payload);\n * // одинаковая форма — можно объединять\n * ```\n */\nexport function normalizeNotification(input: unknown): Notification {\n const source = isRecord(input) ? input : {};\n\n // Поток кладёт полезную нагрузку то в payload, то прямо в корень события.\n const payload = isRecord(source.payload) ? source.payload : source;\n\n const rawType = asString(payload.type) ?? asString(source.type) ?? '';\n const createdAt = asString(payload.createdAt) ?? asString(source.createdAt) ?? '';\n const readAt = asString(payload.readAt) ?? asString(source.readAt);\n\n const isRead =\n typeof payload.isRead === 'boolean'\n ? payload.isRead\n : typeof payload.read === 'boolean'\n ? payload.read\n : Boolean(readAt);\n\n // Сервер описывает событие парой «цель — предмет», и смысл пары зависит от типа:\n //\n // комментарий, реакция на него — цель = пост, предмет = комментарий\n // репост — цель = сам репост, предмет = исходный пост\n // реакция на пост, подписка — предмета нет\n //\n // Событие относится к предмету, только когда тот является комментарием; иначе объект\n // события — цель. Без этого различия ссылка на репост вела бы на чужой исходный пост.\n const subjectId = asString(payload.subjectId);\n const targetId = asString(payload.targetId);\n const subjectIsComment = payload.subjectType === 'comment';\n const clickUrl = asString(payload.clickUrl);\n\n return {\n id: asString(payload.id) ?? asString(source.id) ?? '',\n type: canonicalNotificationType(rawType),\n rawType,\n entityId:\n asString(payload.entityId) ?? (subjectIsComment ? (subjectId ?? targetId) : targetId) ?? null,\n parentEntityId:\n asString(payload.parentEntityId) ?? (subjectIsComment ? (targetId ?? null) : null),\n isRead,\n actors: readActors(payload),\n count: typeof payload.count === 'number' && payload.count > 0 ? payload.count : 1,\n preview: asString(payload.entityPreview) ?? asString(payload.preview) ?? null,\n ...(clickUrl ? { clickUrl } : {}),\n createdAt,\n updatedAt: asString(payload.updatedAt) ?? readAt ?? createdAt,\n raw: input,\n };\n}\n\n/**\n * Разбирает событие `notification` из потока.\n *\n * Кроме самого уведомления событие несёт служебные поля уровня конверта: актуальный\n * счётчик непрочитанных и признак звука.\n */\nexport function readNotificationEvent(data: unknown): NotificationEvent {\n const source = isRecord(data) ? data : {};\n\n return {\n notification: normalizeNotification(data),\n unreadCount: typeof source.unreadCount === 'number' ? source.unreadCount : undefined,\n sound: source.sound === true,\n };\n}\n\n/**\n * Разбирает событие `unread_count` из потока.\n *\n * Возвращает `undefined`, если сервер прислал событие без вложенного `payload`.\n */\nexport function readUnreadCountEvent(data: unknown): number | undefined {\n if (!isRecord(data)) return undefined;\n\n const payload = isRecord(data.payload) ? data.payload : undefined;\n if (!payload) return undefined;\n\n return typeof payload.count === 'number' ? payload.count : undefined;\n}\n","import { ItdConfigError } from '../core/errors.js';\nimport type { NotificationEvent } from '../notifications/normalize.js';\nimport { readNotificationEvent, readUnreadCountEvent } from '../notifications/normalize.js';\nimport type { NotificationType } from '../types/enums.js';\nimport type { Notification } from '../types/models.js';\nimport type { ItdRealtime } from './stream.js';\nimport type { TransportEvent } from './transport.js';\n\n/** Типы нормализованных обновлений потока. */\nexport const RealtimeUpdateType = Object.freeze({\n Notification: 'notification',\n UnreadCount: 'unreadCount',\n Unknown: 'unknown',\n} as const);\n\n/** Источники нормализованных обновлений потока. */\nexport const RealtimeUpdateOrigin = Object.freeze({\n Stream: 'stream',\n Sync: 'sync',\n} as const);\nexport type RealtimeUpdateOrigin = (typeof RealtimeUpdateOrigin)[keyof typeof RealtimeUpdateOrigin];\n\n/** Уведомление с типом, суженным фильтром потока. */\nexport type NotificationOfType<T extends NotificationType> = Omit<Notification, 'type'> & {\n type: T;\n};\n\n/** Конверт уведомления с типом, суженным фильтром потока. */\nexport type NotificationEventOfType<T extends NotificationType> = Omit<\n NotificationEvent,\n 'notification'\n> & {\n notification: NotificationOfType<T>;\n};\n\n/** Нормализованное уведомление из потока. */\nexport interface RealtimeNotificationUpdate<T extends NotificationType = NotificationType> {\n readonly type: typeof RealtimeUpdateType.Notification;\n readonly data: NotificationEventOfType<T>;\n}\n\n/** Актуальное число непрочитанных уведомлений. */\nexport interface RealtimeUnreadCountUpdate {\n readonly type: typeof RealtimeUpdateType.UnreadCount;\n readonly data: number;\n}\n\n/** Неизвестное библиотеке событие потока. */\nexport interface RealtimeUnknownUpdate {\n readonly type: typeof RealtimeUpdateType.Unknown;\n readonly name: string;\n readonly data: unknown;\n}\n\n/** Данные, проходящие через промежуточные обработчики потока. */\nexport type RealtimeUpdate =\n | RealtimeNotificationUpdate\n | RealtimeUnreadCountUpdate\n | RealtimeUnknownUpdate;\n\n/** Тип нормализованного обновления потока. */\nexport type RealtimeUpdateType = RealtimeUpdate['type'];\n\n/** Обновление потока указанного типа. */\nexport type RealtimeUpdateOfType<T extends RealtimeUpdateType> = Extract<\n RealtimeUpdate,\n { type: T }\n>;\n\n/** Контекст обработки одного обновления потока. */\nexport interface RealtimeContext<U extends RealtimeUpdate = RealtimeUpdate> {\n /** Нормализованные данные обновления. */\n readonly update: U;\n /** Поток, который получил обновление. */\n readonly stream: ItdRealtime;\n /** Исходный кадр транспорта. Для начальной REST-синхронизации равен `undefined`. */\n readonly raw: TransportEvent | undefined;\n /** Откуда получены данные. */\n readonly origin: RealtimeUpdateOrigin;\n}\n\n/** Контекст уведомления с типом, суженным фильтром. */\nexport type RealtimeNotificationContext<T extends NotificationType = NotificationType> =\n RealtimeContext<RealtimeNotificationUpdate<T>>;\n\n/** Условия отбора уведомлений. Все указанные поля объединяются через логическое И. */\nexport interface RealtimeNotificationFilter<T extends NotificationType = NotificationType> {\n /** Один или несколько канонических типов уведомления. */\n type?: T | readonly T[];\n /** Идентификатор хотя бы одного участника уведомления. */\n actorId?: string;\n /** Идентификатор объекта события. */\n entityId?: string | null;\n /** Идентификатор родительского объекта. */\n parentEntityId?: string | null;\n /** Дополнительная проверка после сопоставления полей. */\n predicate?: (context: RealtimeNotificationContext<T>) => boolean;\n}\n\n/** Краткая или объектная форма фильтра уведомлений. */\nexport type RealtimeNotificationSelector<T extends NotificationType = NotificationType> =\n | T\n | readonly T[]\n | RealtimeNotificationFilter<T>;\n\n/** Проверяет форму фильтра уведомлений. */\nexport function validateNotificationSelector(selector: unknown): void {\n if (typeof selector === 'string') {\n if (selector.length === 0) throw new ItdConfigError('Тип уведомления не должен быть пустым');\n return;\n }\n\n if (Array.isArray(selector)) {\n if (selector.length === 0 || selector.some((type) => typeof type !== 'string' || !type)) {\n throw new ItdConfigError('Список типов уведомлений должен содержать непустые строки');\n }\n return;\n }\n\n if (typeof selector !== 'object' || selector === null) {\n throw new ItdConfigError('Фильтр уведомлений должен быть строкой, списком или объектом');\n }\n\n const filter = selector as Record<string, unknown>;\n if (filter.type !== undefined) validateNotificationSelector(filter.type);\n if (filter.actorId !== undefined && typeof filter.actorId !== 'string') {\n throw new ItdConfigError('Фильтр уведомлений: actorId должен быть строкой');\n }\n for (const field of ['entityId', 'parentEntityId'] as const) {\n const value = filter[field];\n if (value !== undefined && value !== null && typeof value !== 'string') {\n throw new ItdConfigError(`Фильтр уведомлений: ${field} должен быть строкой или null`);\n }\n }\n if (filter.predicate !== undefined && typeof filter.predicate !== 'function') {\n throw new ItdConfigError('Фильтр уведомлений: predicate должен быть функцией');\n }\n}\n\n/** Преобразует транспортный кадр в одно логическое обновление. */\nexport function readRealtimeUpdate(event: TransportEvent): RealtimeUpdate | undefined {\n if (event.name === 'notification') {\n return {\n type: RealtimeUpdateType.Notification,\n data: readNotificationEvent(event.data),\n };\n }\n\n if (event.name === 'unread_count') {\n const count = readUnreadCountEvent(event.data);\n return count === undefined ? undefined : { type: RealtimeUpdateType.UnreadCount, data: count };\n }\n\n return { type: RealtimeUpdateType.Unknown, name: event.name, data: event.data };\n}\n\n/** Проверяет объектный или краткий фильтр уведомления. */\nexport function matchesNotification<T extends NotificationType>(\n context: RealtimeNotificationContext,\n selector: RealtimeNotificationSelector<T>,\n): context is RealtimeNotificationContext<T> {\n const notification = context.update.data.notification;\n\n if (typeof selector === 'string') return notification.type === selector;\n if (Array.isArray(selector)) return selector.includes(notification.type as T);\n\n const filter = selector as RealtimeNotificationFilter<T>;\n const types = filter.type === undefined ? undefined : [filter.type].flat();\n\n if (types && !types.includes(notification.type as T)) return false;\n if (\n filter.actorId !== undefined &&\n !notification.actors.some(({ id }) => id === filter.actorId)\n ) {\n return false;\n }\n if (filter.entityId !== undefined && notification.entityId !== filter.entityId) return false;\n if (\n filter.parentEntityId !== undefined &&\n notification.parentEntityId !== filter.parentEntityId\n ) {\n return false;\n }\n\n return filter.predicate?.(context as RealtimeNotificationContext<T>) ?? true;\n}\n\n/** Сужает произвольный контекст потока до контекста уведомления. */\nexport function isNotificationContext(\n context: RealtimeContext,\n): context is RealtimeNotificationContext {\n return context.update.type === RealtimeUpdateType.Notification;\n}\n","import type { AuthIdentity } from '../core/auth.js';\nimport { type ItdClock, systemClock } from '../core/clock.js';\nimport { Emitter, type Listener, type Unsubscribe } from '../core/emitter.js';\nimport { ItdConfigError } from '../core/errors.js';\nimport { supportsStreamingBody } from '../core/runtime.js';\nimport { pickString } from '../core/unwrap.js';\nimport type { NotificationEvent } from '../notifications/normalize.js';\nimport { type NotificationType, RealtimeStatus } from '../types/enums.js';\nimport type { Logger } from '../types/options.js';\nimport {\n RealtimeDispatcher,\n type RealtimeHandler,\n type RealtimeMiddleware,\n type RealtimePredicate,\n type RealtimeSequentializer,\n type RealtimeTypeGuard,\n} from './middleware.js';\nimport { PollTransport } from './poll.js';\nimport { MAX_RECONNECT_ATTEMPTS, type ReconnectOptions, reconnectDelay } from './reconnect.js';\nimport { SseTransport } from './sse.js';\nimport {\n type RealtimeTransport,\n type TransportContext,\n type TransportEvent,\n UnauthorizedStreamError,\n} from './transport.js';\nimport {\n isNotificationContext,\n matchesNotification,\n type RealtimeContext,\n type RealtimeNotificationContext,\n type RealtimeNotificationSelector,\n type RealtimeUpdate,\n type RealtimeUpdateOfType,\n RealtimeUpdateOrigin,\n RealtimeUpdateType,\n readRealtimeUpdate,\n validateNotificationSelector,\n} from './updates.js';\n\n/** События потока уведомлений. */\nexport interface RealtimeEvents {\n /** Пришло новое уведомление. */\n notification: NotificationEvent;\n /**\n * Сервер подтвердил подключение и назвал получателя событий.\n *\n * Приходит первым кадром сразу после установки соединения.\n */\n ready: { userId: string | undefined };\n /**\n * Получено актуальное число непрочитанных.\n *\n * При подключении клиент может запросить начальное значение через REST. Затем событие\n * возникает, только если счётчик пришёл в потоке. В остальных случаях обновляйте его\n * в приложении либо запрашивайте `itd.notifications.count()`.\n */\n unreadCount: number;\n /** Изменилось состояние соединения. */\n status: RealtimeStatus;\n /** Соединение оборвалось; будет предпринята попытка переподключения. */\n error: { error: unknown; willReconnect: boolean };\n /** Сообщение не удалось разобрать. Соединение при этом продолжает работать. */\n parseError: { error: unknown; raw: string };\n /** Запланировано переподключение. */\n reconnect: { attempt: number; delay: number };\n /** Попытки исчерпаны — соединение восстановится только ручным `connect()`. */\n giveup: undefined;\n /** Любой исходный кадр транспорта. Отправляется до нормализации и промежуточных обработчиков. */\n message: TransportEvent;\n /** Промежуточный обработчик потока завершился исключением. */\n middlewareError: { error: unknown; context: RealtimeContext };\n /** Обработчик `onUpdate` завершился исключением. */\n handlerError: { error: unknown; context: RealtimeContext };\n}\n\n/** Способ получения событий. */\nexport const RealtimeTransportKind = Object.freeze({\n /** Поток событий, если среда умеет читать тело по частям, иначе опрос. */\n Auto: 'auto',\n /** Поток `text/event-stream`. */\n Sse: 'sse',\n /** Периодический опрос REST. */\n Poll: 'poll',\n} as const);\nexport type RealtimeTransportKind =\n (typeof RealtimeTransportKind)[keyof typeof RealtimeTransportKind];\n\n/** Настройки потока уведомлений. */\nexport interface RealtimeOptions extends ReconnectOptions {\n /**\n * Транспорт. По умолчанию `auto`: поток событий, если среда умеет читать тело ответа\n * по частям, иначе опрос.\n *\n * Можно передать и свою реализацию {@link RealtimeTransport} — это пригодится, если\n * у платформы появится WebSocket либо нужен нестандартный способ доставки.\n */\n transport?: RealtimeTransportKind | RealtimeTransport;\n /**\n * Молчание сервера, после которого соединение считается мёртвым, мс. По умолчанию 90 000.\n *\n * Сервер не присылает keep-alive, поэтому без этой проверки оборванное соединение\n * может незаметно «зависнуть».\n */\n idleTimeout?: number;\n /**\n * Сколько ждать ответа на запрос потока, прежде чем оборвать попытку, мс. По умолчанию\n * 20 000. Защищает от зависания на установке соединения, когда {@link idleTimeout} ещё\n * не действует. `0` отключает проверку. Только для потокового транспорта.\n */\n handshakeTimeout?: number;\n /** Как часто опрашивать сервер, если используется запасной транспорт. */\n pollInterval?: number;\n /**\n * Запрашивать число непрочитанных при подключении. По умолчанию `true`.\n *\n * Так поступает сайт итд.com: поток присылает только новые события, а начальное\n * значение счётчика нужно получить отдельно.\n */\n syncCount?: boolean;\n /**\n * Переподключаться, когда вкладка снова становится видимой. По умолчанию `true`.\n *\n * Только в браузере. У сайта итд.com такой обработки нет, из-за чего вкладка,\n * пролежавшая в фоне, может остаться без соединения.\n */\n reconnectOnVisible?: boolean;\n /** Переподключаться при восстановлении сети. По умолчанию `true`. Только в браузере. */\n reconnectOnOnline?: boolean;\n /** Максимальное число одновременно обрабатываемых обновлений. По умолчанию 1. */\n concurrency?: number;\n /** Возвращает ключи обновлений, которые нельзя обрабатывать одновременно. */\n sequentialize?: RealtimeSequentializer;\n}\n\n/** Проверяет настройки потока. @throws {ItdConfigError} при некорректных значениях */\nfunction validateRealtimeOptions(options: RealtimeOptions): void {\n const positiveInteger = (value: number | undefined, name: string): void => {\n if (value === undefined) return;\n if (!Number.isInteger(value) || value < 0) {\n throw new ItdConfigError(\n `realtime.${name} должен быть целым неотрицательным числом, получено: ${value}`,\n );\n }\n };\n\n const duration = (value: number | undefined, name: string, min: number): void => {\n if (value === undefined) return;\n if (!Number.isFinite(value) || value < min) {\n throw new ItdConfigError(\n `realtime.${name} должен быть числом не меньше ${min}, получено: ${value}`,\n );\n }\n };\n\n positiveInteger(options.maxAttempts, 'maxAttempts');\n positiveInteger(options.concurrency, 'concurrency');\n if (options.concurrency === 0) {\n throw new ItdConfigError('realtime.concurrency должен быть больше нуля');\n }\n duration(options.pollInterval, 'pollInterval', 1);\n duration(options.idleTimeout, 'idleTimeout', 0);\n duration(options.handshakeTimeout, 'handshakeTimeout', 0);\n\n if (options.jitter !== undefined && !(options.jitter >= 0 && options.jitter <= 1)) {\n throw new ItdConfigError(\n `realtime.jitter должен быть в диапазоне 0…1, получено: ${options.jitter}`,\n );\n }\n\n if (options.backoff !== undefined) {\n if (!Array.isArray(options.backoff) || options.backoff.length === 0) {\n throw new ItdConfigError('realtime.backoff должен быть непустым списком пауз');\n }\n for (const delay of options.backoff) duration(delay, 'backoff', 0);\n }\n\n if (options.sequentialize !== undefined && typeof options.sequentialize !== 'function') {\n throw new ItdConfigError('realtime.sequentialize должен быть функцией');\n }\n}\n\n/** Что поток получает от клиента. */\nexport interface RealtimeDeps {\n baseUrl: string;\n fetch: typeof fetch;\n clock?: ItdClock;\n /** Общие заголовки клиента для адреса — см. {@link TransportContext.baseHeaders}. */\n baseHeaders: (url: string) => Promise<Headers>;\n /** Идентификаторы аккаунта и сессии создавшего поток клиента. */\n getAuthIdentity?: (() => AuthIdentity) | undefined;\n /** Непрозрачная область авторизации создавшего поток клиента. */\n getAuthScope?: (() => string) | undefined;\n getToken: () => Promise<string | null>;\n /** Обновляет токен после отказа авторизации. Возвращает `true`, если удалось. */\n refresh: () => Promise<boolean>;\n /** Загружает начальное число непрочитанных. */\n fetchUnreadCount: () => Promise<number>;\n /** Вызывается при явном закрытии потока. */\n onClose?: (() => void) | undefined;\n /** Вызывается при запуске ранее закрытого потока. */\n onConnect?: (() => void) | undefined;\n logger?: Logger | undefined;\n}\n\n/**\n * Поток уведомлений в реальном времени.\n *\n * Получается вызовом `itd.realtime()`. Соединение поднимается методом {@link connect}\n * и держится само: обрывы, обновление токена и повторные попытки библиотека берёт на себя.\n *\n * @example\n * ```ts\n * import { NotificationType } from 'itd-api';\n *\n * const stream = itd.realtime();\n *\n * stream.onNotification(NotificationType.PostComment, async ({ update }) => {\n * await saveCommentNotification(update.data.notification);\n * });\n * stream.on('status', (status) => console.log('соединение:', status));\n *\n * await stream.connect();\n * // …позже\n * stream.disconnect();\n * await stream.drain();\n * ```\n */\nexport class ItdRealtime {\n readonly #deps: RealtimeDeps;\n readonly #options: RealtimeOptions;\n readonly #emitter: Emitter<RealtimeEvents>;\n readonly #dispatcher: RealtimeDispatcher;\n readonly #transport: RealtimeTransport;\n readonly #maxAttempts: number;\n\n #controller: AbortController | undefined;\n /**\n * Хочет ли вызывающий код, чтобы соединение было живо.\n *\n * Отдельно от `#controller`, потому что тот появляется только после `await` внутри\n * {@link connect}. Без этого флага два вызова подряд проскочили бы проверку оба\n * и подняли два соединения, а `disconnect()` во время ожидания счётчика не был бы\n * замечен и соединение поднялось бы уже после отмены.\n */\n #wanted = false;\n #status: RealtimeStatus = RealtimeStatus.Disconnected;\n #attempt = 0;\n #cancelTimer: (() => void) | undefined;\n #detachEnvironment: (() => void) | undefined;\n\n constructor(deps: RealtimeDeps, options: RealtimeOptions = {}) {\n validateRealtimeOptions(options);\n\n this.#deps = deps;\n this.#options = options;\n this.#maxAttempts = options.maxAttempts ?? MAX_RECONNECT_ATTEMPTS;\n this.#transport = this.#createTransport();\n // Исключение из пользовательского обработчика — в логгер, при его отсутствии в консоль.\n this.#emitter = new Emitter<RealtimeEvents>((error) => {\n const message = 'Ошибка в обработчике события realtime';\n if (deps.logger) deps.logger.error(message, error);\n else console.error(`[itd-api] ${message}`, error);\n });\n this.#dispatcher = new RealtimeDispatcher(\n {\n concurrency: options.concurrency ?? 1,\n ...(options.sequentialize ? { sequentialize: options.sequentialize } : {}),\n },\n {\n deliver: (context) => this.#deliver(context.update),\n middlewareError: (error, context) =>\n this.#reportDispatchError('middlewareError', error, context),\n handlerError: (error, context) => this.#reportDispatchError('handlerError', error, context),\n },\n );\n }\n\n /** Текущее состояние соединения. */\n get status(): RealtimeStatus {\n return this.#status;\n }\n\n /** Какой транспорт используется: `sse` или `poll`. */\n get transport(): string {\n return this.#transport.name;\n }\n\n /** Базовый URL клиента, создавшего поток. @internal */\n get baseUrl(): string {\n return this.#deps.baseUrl;\n }\n\n /** Идентификаторы аккаунта и сессии клиента, создавшего поток. @internal */\n getAuthIdentity(): AuthIdentity | undefined {\n return this.#deps.getAuthIdentity?.();\n }\n\n /** Непрозрачная область авторизации создавшего поток клиента. @internal */\n getAuthScope(): string | undefined {\n return this.#deps.getAuthScope?.();\n }\n\n /** Подписывается на событие потока. @returns функция отписки */\n on<K extends keyof RealtimeEvents>(event: K, listener: Listener<RealtimeEvents[K]>): Unsubscribe {\n return this.#emitter.on(event, listener);\n }\n\n /** Подписывается на одно срабатывание. */\n once<K extends keyof RealtimeEvents>(\n event: K,\n listener: Listener<RealtimeEvents[K]>,\n ): Unsubscribe {\n return this.#emitter.once(event, listener);\n }\n\n /**\n * Добавляет промежуточный обработчик нормализованных обновлений.\n *\n * Обработчики выполняются в порядке регистрации. Если `next()` не вызван, обновление не\n * передаётся дальше по цепочке, асинхронным обработчикам и слушателям событий.\n *\n * @returns функция удаления обработчика\n */\n use(middleware: RealtimeMiddleware): Unsubscribe {\n if (typeof middleware !== 'function') {\n throw new ItdConfigError('realtime.use() принимает функцию обработки');\n }\n return this.#dispatcher.use(middleware);\n }\n\n /** Подписывает асинхронный обработчик на все нормализованные обновления. */\n onUpdate(handler: RealtimeHandler): Unsubscribe;\n /** Подписывает асинхронный обработчик на обновление указанного типа. */\n onUpdate<T extends RealtimeUpdateType>(\n type: T,\n handler: RealtimeHandler<RealtimeContext<RealtimeUpdateOfType<T>>>,\n ): Unsubscribe;\n /** Подписывает асинхронный обработчик по функции сужения типа. */\n onUpdate<C extends RealtimeContext>(\n guard: RealtimeTypeGuard<C>,\n handler: RealtimeHandler<C>,\n ): Unsubscribe;\n /** Подписывает асинхронный обработчик по пользовательскому условию. */\n onUpdate(predicate: RealtimePredicate, handler: RealtimeHandler): Unsubscribe;\n onUpdate(\n selectorOrHandler: RealtimeUpdateType | RealtimePredicate | RealtimeHandler,\n selectedHandler?: RealtimeHandler,\n ): Unsubscribe {\n const selectAll = selectedHandler === undefined;\n const handler = selectAll ? (selectorOrHandler as RealtimeHandler) : selectedHandler;\n if (typeof handler !== 'function') {\n throw new ItdConfigError('realtime.onUpdate() принимает функцию обработчика');\n }\n if (\n !selectAll &&\n typeof selectorOrHandler !== 'function' &&\n !Object.values(RealtimeUpdateType).includes(selectorOrHandler)\n ) {\n throw new ItdConfigError(`Неизвестный тип обновления потока: ${String(selectorOrHandler)}`);\n }\n\n let predicate: RealtimePredicate;\n if (selectAll) {\n predicate = () => true;\n } else {\n const selector = selectorOrHandler as RealtimeUpdateType | RealtimePredicate;\n predicate =\n typeof selector === 'function' ? selector : (context) => context.update.type === selector;\n }\n\n return this.#dispatcher.on(predicate, handler);\n }\n\n /** Подписывает асинхронный обработчик на уведомления, подходящие под фильтр. */\n onNotification<T extends NotificationType>(\n selector: RealtimeNotificationSelector<T>,\n handler: RealtimeHandler<RealtimeNotificationContext<T>>,\n ): Unsubscribe;\n /** Подписывает асинхронный обработчик по функции сужения типа уведомления. */\n onNotification<C extends RealtimeNotificationContext>(\n guard: (context: RealtimeNotificationContext) => context is C,\n handler: RealtimeHandler<C>,\n ): Unsubscribe;\n /** Подписывает асинхронный обработчик по пользовательскому условию. */\n onNotification(\n predicate: (context: RealtimeNotificationContext) => boolean,\n handler: RealtimeHandler<RealtimeNotificationContext>,\n ): Unsubscribe;\n onNotification(\n selector: RealtimeNotificationSelector | ((context: RealtimeNotificationContext) => boolean),\n handler: (context: never) => unknown | Promise<unknown>,\n ): Unsubscribe {\n if (typeof handler !== 'function') {\n throw new ItdConfigError('realtime.onNotification() принимает функцию обработчика');\n }\n if (typeof selector !== 'function') validateNotificationSelector(selector);\n\n const predicate: RealtimePredicate = (context) => {\n if (!isNotificationContext(context)) return false;\n return typeof selector === 'function'\n ? selector(context)\n : matchesNotification(context, selector);\n };\n\n return this.#dispatcher.on(predicate, handler as RealtimeHandler);\n }\n\n /**\n * Поднимает соединение.\n *\n * Повторный вызов при уже живом соединении ничего не делает — это защита от двойного\n * подключения при перерисовке интерфейса.\n *\n * Возвращает управление сразу после запуска: соединение живёт в фоне.\n */\n async connect(): Promise<void> {\n if (this.#wanted) return;\n this.#wanted = true;\n this.#deps.onConnect?.();\n\n this.#attachEnvironmentListeners();\n\n if (this.#options.syncCount !== false) {\n try {\n const count = await this.#deps.fetchUnreadCount();\n if (this.#wanted) {\n this.#dispatch(\n { type: RealtimeUpdateType.UnreadCount, data: count },\n undefined,\n RealtimeUpdateOrigin.Sync,\n );\n }\n } catch (error) {\n // Начальный счётчик — вспомогательная величина, из-за неё поток не отменяется.\n this.#deps.logger?.debug('не удалось получить число непрочитанных', error);\n }\n }\n\n // Пока ждали счётчик, могли успеть вызвать disconnect().\n if (this.#wanted) this.#run();\n }\n\n /** Закрывает соединение и отменяет запланированные попытки. */\n disconnect(): void {\n this.#wanted = false;\n\n if (this.#cancelTimer) {\n this.#cancelTimer();\n this.#cancelTimer = undefined;\n }\n\n this.#detachEnvironment?.();\n this.#detachEnvironment = undefined;\n\n this.#controller?.abort();\n this.#controller = undefined;\n this.#attempt = 0;\n this.#dispatcher.clearPending();\n\n this.#setStatus(RealtimeStatus.Disconnected);\n this.#deps.onClose?.();\n }\n\n /** Ждёт завершения всех принятых обновлений. */\n drain(): Promise<void> {\n return this.#dispatcher.drain();\n }\n\n /** Снимает подписки `on()` и `once()`. Остальные обработчики остаются. */\n removeAllListeners(): void {\n this.#emitter.removeAllListeners();\n }\n\n #createTransport(): RealtimeTransport {\n const kind = this.#options.transport ?? RealtimeTransportKind.Auto;\n\n if (typeof kind === 'object') return kind;\n\n if (\n kind === RealtimeTransportKind.Poll ||\n (kind === RealtimeTransportKind.Auto && !supportsStreamingBody())\n ) {\n return new PollTransport({\n clock: this.#deps.clock ?? systemClock,\n ...(this.#options.pollInterval !== undefined\n ? { interval: this.#options.pollInterval }\n : {}),\n });\n }\n\n return new SseTransport({\n clock: this.#deps.clock ?? systemClock,\n ...(this.#options.idleTimeout !== undefined\n ? { idleTimeout: this.#options.idleTimeout }\n : {}),\n ...(this.#options.handshakeTimeout !== undefined\n ? { handshakeTimeout: this.#options.handshakeTimeout }\n : {}),\n });\n }\n\n /** Запускает попытку подключения; повторы планирует сам. */\n #run(): void {\n if (!this.#wanted) return;\n\n // Страховка от потерянного соединения: если предыдущее ещё живо, закрываем его,\n // иначе его AbortController остался бы недостижимым и поток — незакрытым.\n this.#controller?.abort();\n\n const controller = new AbortController();\n this.#controller = controller;\n this.#setStatus(RealtimeStatus.Connecting);\n\n void this.#transport\n .connect({\n baseUrl: this.#deps.baseUrl,\n fetch: this.#deps.fetch,\n baseHeaders: this.#deps.baseHeaders,\n getToken: this.#deps.getToken,\n signal: controller.signal,\n onOpen: () => {\n this.#attempt = 0;\n this.#setStatus(RealtimeStatus.Connected);\n },\n onEvent: (event) => this.#handleEvent(event),\n onParseError: (error, raw) => this.#emitter.emit('parseError', { error, raw }),\n })\n .then(\n () => {\n // Штатное закрытие потока — тоже повод переподключиться.\n if (!controller.signal.aborted) {\n this.#handleFailure(new Error('Соединение с потоком уведомлений закрыто'));\n }\n },\n (error: unknown) => {\n if (controller.signal.aborted) return;\n this.#handleFailure(error);\n },\n );\n }\n\n #handleEvent(event: TransportEvent): void {\n if (!this.#wanted) return;\n this.#emitter.emit('message', event);\n\n if (event.name === 'connected') {\n // Строго строка: `String(null)` дал бы подписчику осмысленно выглядящее «null».\n this.#emitter.emit('ready', { userId: pickString(event.data, 'userId') });\n return;\n }\n\n const update = readRealtimeUpdate(event);\n if (update) this.#dispatch(update, event, RealtimeUpdateOrigin.Stream);\n }\n\n #dispatch(\n update: RealtimeUpdate,\n raw: TransportEvent | undefined,\n origin: RealtimeUpdateOrigin,\n ): void {\n this.#dispatcher.dispatch({ update, stream: this, raw, origin });\n }\n\n #deliver(update: RealtimeUpdate): void {\n if (update.type === RealtimeUpdateType.Notification) {\n this.#emitter.emit('notification', update.data);\n if (update.data.unreadCount !== undefined) {\n this.#emitter.emit('unreadCount', update.data.unreadCount);\n }\n return;\n }\n\n if (update.type === RealtimeUpdateType.UnreadCount) {\n this.#emitter.emit('unreadCount', update.data);\n return;\n }\n\n if (update.type === RealtimeUpdateType.Unknown) return;\n\n assertNeverUpdate(update);\n }\n\n #reportDispatchError(\n event: 'middlewareError' | 'handlerError',\n error: unknown,\n context: RealtimeContext,\n ): void {\n if (this.#emitter.listenerCount(event) > 0) {\n this.#emitter.emit(event, { error, context });\n return;\n }\n\n const message =\n event === 'middlewareError'\n ? 'Ошибка в промежуточном обработчике потока'\n : 'Ошибка в обработчике обновления потока';\n if (this.#deps.logger) this.#deps.logger.error(message, error);\n else console.error(`[itd-api] ${message}`, error);\n }\n\n #handleFailure(error: unknown): void {\n this.#controller = undefined;\n\n if (error instanceof UnauthorizedStreamError) {\n void this.#refreshAndReconnect(error);\n return;\n }\n\n this.#setStatus(RealtimeStatus.Error);\n this.#scheduleReconnect(error);\n }\n\n /** Обновляет токен и переподключается; при неудаче прекращает попытки. */\n async #refreshAndReconnect(error: unknown): Promise<void> {\n this.#setStatus(RealtimeStatus.Error);\n\n const refreshed = await this.#deps.refresh().catch(() => false);\n\n if (!this.#wanted) return;\n\n if (!refreshed) {\n this.#giveUp(error);\n return;\n }\n\n this.#scheduleReconnect(error);\n }\n\n #scheduleReconnect(error: unknown): void {\n if (!this.#wanted) return;\n\n if (this.#attempt >= this.#maxAttempts) {\n this.#giveUp(error);\n return;\n }\n\n const delay = reconnectDelay(this.#attempt, this.#options);\n this.#attempt += 1;\n\n this.#emitter.emit('error', { error, willReconnect: true });\n this.#emitter.emit('reconnect', { attempt: this.#attempt, delay });\n\n this.#cancelTimer = (this.#deps.clock ?? systemClock).schedule(() => {\n this.#cancelTimer = undefined;\n this.#run();\n }, delay);\n }\n\n /** Завершает автоматические попытки переподключения. */\n #giveUp(error: unknown): void {\n this.#wanted = false;\n this.#attempt = 0;\n\n this.#detachEnvironment?.();\n this.#detachEnvironment = undefined;\n\n this.#emitter.emit('error', { error, willReconnect: false });\n this.#emitter.emit('giveup', undefined);\n }\n\n /**\n * Подписывается на события среды.\n *\n * Возврат вкладки из фона и восстановление сети — самые частые причины «мёртвого»\n * соединения. У сайта итд.com такой обработки нет.\n */\n #attachEnvironmentListeners(): void {\n if (this.#detachEnvironment) return;\n\n const target = globalThis as unknown as {\n addEventListener?: (type: string, listener: () => void) => void;\n removeEventListener?: (type: string, listener: () => void) => void;\n document?: { visibilityState?: string };\n };\n\n if (typeof target.addEventListener !== 'function') return;\n\n const wake = () => {\n // Реагируем, только если соединения сейчас нет и попытка не запланирована.\n if (this.#controller || this.#cancelTimer) return;\n if (this.#status === RealtimeStatus.Disconnected) return;\n\n this.#attempt = 0;\n this.#run();\n };\n\n const onVisibility = () => {\n if (target.document?.visibilityState === 'visible') wake();\n };\n\n const listeners: [string, () => void][] = [];\n\n if (this.#options.reconnectOnVisible !== false && target.document) {\n listeners.push(['visibilitychange', onVisibility]);\n }\n if (this.#options.reconnectOnOnline !== false) {\n listeners.push(['online', wake]);\n }\n\n for (const [type, listener] of listeners) target.addEventListener(type, listener);\n\n this.#detachEnvironment = () => {\n for (const [type, listener] of listeners) target.removeEventListener?.(type, listener);\n };\n }\n\n #setStatus(status: RealtimeStatus): void {\n if (this.#status === status) return;\n this.#status = status;\n this.#emitter.emit('status', status);\n }\n}\n\nfunction assertNeverUpdate(update: never): never {\n throw new TypeError(`Необработанное обновление потока: ${String(update)}`);\n}\n","import { ItdConfigError } from './errors.js';\nimport { pickArray, pickBoolean, pickNumber, pickObject, pickString } from './unwrap.js';\n\n/**\n * Страница списка — единая форма для всех трёх схем пагинации API.\n *\n * Какие необязательные поля заполнены, зависит от эндпоинта: у ленты это `nextCursor`,\n * у подписчиков — `page` и `total`, у уведомлений — `nextOffset`. Обычно они не нужны:\n * перебор берёт на себя {@link Paginator}.\n */\nexport interface Page<T> {\n /** Элементы страницы. */\n items: T[];\n /** Есть ли следующая страница. */\n hasMore: boolean;\n /**\n * Курсор следующей страницы.\n *\n * Непрозрачен: у вкладки `popular` это номер страницы, у `following` — отметка времени.\n * Передавайте его обратно как есть и не пытайтесь разобрать.\n */\n nextCursor?: string | null | undefined;\n /** Номер текущей страницы при постраничной схеме. */\n page?: number | undefined;\n /** Запрошенный размер страницы. */\n limit?: number | undefined;\n /** Общее число элементов, если сервер его сообщил. */\n total?: number | undefined;\n /** Смещение для следующего запроса при схеме со смещением. */\n nextOffset?: number | undefined;\n /** Исходный ответ — на случай, если документация разошлась с реальностью. */\n raw: unknown;\n}\n\n/** Схема пагинации эндпоинта. */\nexport const PaginationMode = Object.freeze({\n /** Следующая страница запрашивается непрозрачным курсором. */\n Cursor: 'cursor',\n /** Следующая страница запрашивается номером. */\n Page: 'page',\n /** Следующая страница запрашивается смещением от начала списка. */\n Offset: 'offset',\n} as const);\nexport type PaginationMode = (typeof PaginationMode)[keyof typeof PaginationMode];\n\n/** Применяет преобразование к элементам страницы, сохраняя сведения о пагинации. */\nexport function mapPage<T, R>(page: Page<T>, map: (item: T) => R): Page<R> {\n return { ...page, items: page.items.map(map) };\n}\n\n/** Позиция, с которой запрашивается очередная страница. */\nexport interface PageState {\n cursor?: string | undefined;\n page?: number | undefined;\n offset?: number | undefined;\n}\n\n/** Настройки перебора страниц. */\nexport interface PaginatorOptions<T> {\n mode: PaginationMode;\n /** Загружает одну страницу для указанной позиции. */\n load: (state: PageState) => Promise<Page<T>>;\n /**\n * С какой позиции начать. По умолчанию с начала списка.\n *\n * Нужно, чтобы перебор можно было продолжить с сохранённого курсора, а не только\n * начать заново.\n */\n start?: PageState | undefined;\n /**\n * Предохранитель от бесконечного перебора. По умолчанию 1000.\n *\n * Сработает, только если сервер бесконечно сообщает `hasMore` — при нормальной работе\n * перебор останавливается сам.\n */\n maxPages?: number | undefined;\n /** Отмена перебора. */\n signal?: AbortSignal | undefined;\n}\n\n/**\n * Достаёт список из ответа, перебирая все формы, которые встречаются у сервера.\n *\n * Список может прийти под именем сущности, под её альтернативным именем\n * (`followers` вместо `users`) либо голым массивом.\n *\n * @param fields имена полей в порядке предпочтения\n */\nfunction readItems<T>(body: unknown, fields: readonly string[]): T[] {\n if (Array.isArray(body)) return body as T[];\n\n for (const field of fields) {\n const items = pickArray<T>(body, field);\n if (items.length > 0) return items;\n }\n\n // Пустой список тоже валиден: возвращаем по первому известному имени.\n const primary = fields[0];\n return primary === undefined ? [] : pickArray<T>(body, primary);\n}\n\n/**\n * Достаёт курсор следующей страницы.\n *\n * Проверяются все известные места: внутри `pagination`, рядом со списком и во вложенном\n * объекте `meta.cursor.next`.\n */\nfunction readCursor(body: unknown): string | null {\n const pagination = pickObject(body, 'pagination');\n const meta = pickObject(body, 'meta');\n const metaCursor = pickObject(meta, 'cursor');\n\n return (\n pickString(pagination, 'nextCursor') ??\n pickString(body, 'nextCursor') ??\n pickString(body, 'cursor') ??\n pickString(metaCursor, 'next') ??\n null\n );\n}\n\n/**\n * Читает страницу курсорной схемы.\n *\n * Используется лентой, постами пользователя и постами по хэштегу.\n *\n * @param fields имена возможных полей со списком; первое — основное\n */\nexport function readCursorPage<T>(body: unknown, ...fields: string[]): Page<T> {\n const pagination = pickObject(body, 'pagination');\n const items = readItems<T>(body, fields);\n const nextCursor = readCursor(body);\n\n return {\n items,\n // Если признака продолжения нет, ориентируемся на наличие курсора.\n hasMore: pickBoolean(pagination, 'hasMore', pickBoolean(body, 'hasMore', nextCursor !== null)),\n nextCursor,\n limit: pickNumber(pagination, 'limit', 0) || undefined,\n raw: body,\n };\n}\n\n/**\n * Читает страницу курсорной схемы с курсором рядом со списком.\n *\n * Так устроены комментарии к посту и ответы на комментарий: `nextCursor` и `hasMore`\n * лежат на одном уровне со списком, а не внутри объекта `pagination`.\n */\nexport function readFlatCursorPage<T>(body: unknown, ...fields: string[]): Page<T> {\n const items = readItems<T>(body, fields);\n const nextCursor = readCursor(body);\n const total = pickNumber(body, 'total', -1);\n\n return {\n items,\n hasMore: pickBoolean(body, 'hasMore', nextCursor !== null),\n nextCursor,\n ...(total >= 0 ? { total } : {}),\n raw: body,\n };\n}\n\n/**\n * Читает страницу постраничной схемы: `data.pagination.{page,limit,total,hasMore}`.\n *\n * Если сервер вместо этого прислал курсор, он тоже подхватывается — некоторые списки\n * отвечают в курсорной форме, хотя документация описывает постраничную.\n */\nexport function readPagedPage<T>(body: unknown, ...fields: string[]): Page<T> {\n const pagination = pickObject(body, 'pagination');\n const items = readItems<T>(body, fields);\n const nextCursor = readCursor(body);\n\n return {\n items,\n hasMore: pickBoolean(pagination, 'hasMore', pickBoolean(body, 'hasMore', nextCursor !== null)),\n page: pickNumber(pagination, 'page', 1),\n limit: pickNumber(pagination, 'limit', 0) || undefined,\n total: pickNumber(pagination, 'total', 0),\n ...(nextCursor !== null ? { nextCursor } : {}),\n raw: body,\n };\n}\n\n/**\n * Читает страницу схемы со смещением.\n *\n * Используется для списков со смещением, включая уведомления.\n */\nexport function readOffsetPage<T>(body: unknown, field: string, offset: number): Page<T> {\n const items = pickArray<T>(body, field);\n\n return {\n items,\n hasMore: pickBoolean(body, 'hasMore'),\n nextOffset: offset + items.length,\n raw: body,\n };\n}\n\n/**\n * Перебор страниц списка.\n *\n * Скрывает различия трёх схем пагинации: перебор элементов, страниц и сбор в массив\n * выглядят одинаково независимо от эндпоинта.\n *\n * **Одноразовый.** Позиция хранится внутри, поэтому повторный перебор того же объекта\n * ничего не вернёт: он продолжится с места, где закончился прошлый. Нужен второй проход —\n * возьмите новый перебор у того же метода ресурса.\n *\n * @example Перебор элементов\n * ```ts\n * for await (const post of itd.posts.iterate({ tab: 'following' })) {\n * console.log(post.content);\n * }\n * ```\n *\n * @example Первые сто элементов\n * ```ts\n * const posts = await itd.posts.iterate({ tab: 'popular' }).collect(100);\n * ```\n *\n * @example Постранично\n * ```ts\n * for await (const page of itd.users.followers('nowkie').pages()) {\n * console.log(page.items.length, 'из', page.total);\n * }\n * ```\n */\nexport class Paginator<T> implements AsyncIterable<T> {\n readonly #options: PaginatorOptions<T>;\n readonly #maxPages: number;\n\n #state: PageState;\n #finished = false;\n #pagesLoaded = 0;\n\n constructor(options: PaginatorOptions<T>) {\n if (\n options.maxPages !== undefined &&\n (!Number.isInteger(options.maxPages) || options.maxPages < 1)\n ) {\n throw new ItdConfigError(\n `maxPages должен быть целым числом не меньше 1, получено: ${options.maxPages}`,\n );\n }\n\n this.#options = options;\n this.#maxPages = options.maxPages ?? 1000;\n this.#state = options.start ?? {};\n }\n\n /**\n * Загружает следующую страницу.\n *\n * @returns страница либо `null`, если перебор закончен\n */\n async next(): Promise<Page<T> | null> {\n if (this.#finished) return null;\n if (this.#options.signal?.aborted) return null;\n\n if (this.#pagesLoaded >= this.#maxPages) {\n this.#finished = true;\n return null;\n }\n\n const previous = this.#state;\n const page = await this.#options.load(previous);\n this.#pagesLoaded += 1;\n\n this.#state = this.#advance(previous, page);\n return page;\n }\n\n /**\n * Перебирает страницы целиком.\n *\n * Полезно, когда нужны сведения о самой странице — например `total`.\n */\n async *pages(): AsyncGenerator<Page<T>, void, undefined> {\n for (;;) {\n const page = await this.next();\n if (!page) return;\n yield page;\n }\n }\n\n /** Перебирает элементы всех страниц подряд. */\n async *[Symbol.asyncIterator](): AsyncGenerator<T, void, undefined> {\n for await (const page of this.pages()) {\n for (const item of page.items) {\n if (this.#options.signal?.aborted) return;\n yield item;\n }\n }\n }\n\n /**\n * Собирает элементы в массив.\n *\n * @param max сколько элементов достаточно; без него перебираются все страницы\n */\n async collect(max?: number): Promise<T[]> {\n // Ноль и отрицательное число — «ничего не нужно»: не делаем ни одного запроса.\n if (max !== undefined && max <= 0) return [];\n\n const result: T[] = [];\n\n for await (const item of this) {\n result.push(item);\n if (max !== undefined && result.length >= max) break;\n }\n\n return result;\n }\n\n /**\n * Вычисляет позицию следующей страницы и решает, продолжать ли.\n *\n * Здесь же стоят предохранители: пустая страница при `hasMore`, неизменившийся курсор\n * и отсутствие курсора останавливают перебор. Без них ошибка на сервере превратилась бы\n * в бесконечный цикл запросов.\n */\n #advance(previous: PageState, page: Page<T>): PageState {\n if (!page.hasMore || page.items.length === 0) {\n this.#finished = true;\n return previous;\n }\n\n if (this.#options.mode === PaginationMode.Cursor) {\n const cursor = page.nextCursor ?? undefined;\n\n if (!cursor || cursor === previous.cursor) {\n this.#finished = true;\n return previous;\n }\n\n return { cursor };\n }\n\n if (this.#options.mode === PaginationMode.Page) {\n return { page: (previous.page ?? 1) + 1 };\n }\n\n return { offset: page.nextOffset ?? (previous.offset ?? 0) + page.items.length };\n }\n}\n","import type { ItdClock } from '../core/clock.js';\nimport type { RuntimeMode } from '../core/runtime.js';\nimport type { ServiceDefinition } from '../core/services.js';\nimport type { TokenStorage } from '../core/storage.js';\nimport type { QueryParams } from '../core/url.js';\n\n/**\n * Вход по логину и паролю.\n *\n * Вход требует токен капчи Cloudflare Turnstile, поэтому полностью автоматическим он быть\n * не может: капчу должен решить кто-то снаружи. Токен одноразовый и живёт несколько минут,\n * так что долгоживущему клиенту нужен `getTurnstileToken` — он спрашивается заново перед\n * каждой попыткой входа. Одиночный `turnstileToken` годится для разового скрипта.\n */\nexport interface CredentialsAuth {\n email: string;\n password: string;\n /** Разовый токен капчи. Для повторного входа после истечения сессии не подойдёт. */\n turnstileToken?: string | undefined;\n /** Источник свежего токена капчи. Спрашивается перед каждым входом. */\n getTurnstileToken?: (() => string | Promise<string>) | undefined;\n}\n\n/**\n * Как клиент получает доступ к API.\n *\n * Четыре формы — от разового вызова с готовым токеном до полноценной сессии, которую\n * библиотека заводит и продлевает сама.\n *\n * Опция необязательна: если {@link ItdClientOptions.storage} уже содержит сессию, доступ\n * берётся оттуда. Когда заданы обе, хранилище главнее — оно отражает текущее состояние\n * сессии, — а недостающие поля берутся отсюда.\n *\n * @example\n * ```ts\n * new ItdClient({ auth: '<accessToken>' }); // разовый вызов\n * new ItdClient({ auth: { accessToken, refreshToken } }); // восстановить сессию\n * new ItdClient({ auth: { email, password, getTurnstileToken } }); // залогиниться самому\n * new ItdClient({ auth: { getToken: () => vault.read() } }); // токен из внешнего источника\n * ```\n */\nexport type AuthInput =\n | string\n | { accessToken: string; refreshToken?: string | undefined }\n | CredentialsAuth\n | { getToken: () => string | null | Promise<string | null> };\n\n/** Куда библиотека пишет отладочные сообщения. Совместим с `console`. */\nexport interface Logger {\n debug(message: string, ...args: unknown[]): void;\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n\n/** Настройки повторных попыток. */\nexport interface RetryOptions {\n /** Сколько всего попыток, включая первую. По умолчанию 3. */\n attempts?: number | undefined;\n /** Базовая пауза в мс, удваивается с каждой попыткой. По умолчанию 500. */\n baseDelay?: number | undefined;\n /** Верхняя граница паузы в мс. По умолчанию 30000. */\n maxDelay?: number | undefined;\n /** Доля случайного разброса паузы, 0…1. По умолчанию 0.3. */\n jitter?: number | undefined;\n /**\n * Повторять ли запись (`POST`, `PUT`, `PATCH`, `DELETE`) при сетевых сбоях и `5xx`.\n *\n * По умолчанию `false`: сервер мог успеть выполнить операцию до обрыва, и повтор\n * создаст дубль поста или лишнюю жалобу. Ответ `429` повторяется всегда — он гарантирует,\n * что запрос не был обработан.\n */\n retryWrites?: boolean | undefined;\n /** Своя логика: вернуть `true`, чтобы повторить. Заменяет правила по умолчанию. */\n shouldRetry?: ((error: unknown, attempt: number) => boolean) | undefined;\n}\n\n/** Настройки ограничения нагрузки на API. */\nexport interface RateLimitOptions {\n /** Сколько запросов выполняется одновременно. По умолчанию 6. */\n concurrency?: number | undefined;\n /** Верхняя граница запросов в секунду. По умолчанию без ограничения. */\n rps?: number | undefined;\n /**\n * Паузы перед повторами при ответе `429`, мс.\n * По умолчанию `[1000, 5000, 30000, 60000, 90000]`.\n *\n * Сервер не сообщает, когда сбросится окно лимита, поэтому паузу приходится подбирать.\n * Лестница начинается с секунды: если окно почти истекло, работа продолжится почти\n * сразу, а если лимит исчерпан всерьёз — паузы дорастут до полутора минут.\n * Когда лестница закончилась, {@link ItdRateLimitError} пробрасывается вызывающему коду.\n *\n * Этот список не зависит от `retry.attempts`: тот управляет повторами при обрывах\n * сети и ошибках сервера, где уместен совсем другой темп.\n */\n retryDelays?: readonly number[] | undefined;\n /**\n * Тормозить ли очередь по заголовкам ответа. По умолчанию `true`.\n *\n * Выключите, если управляете темпом сами.\n */\n respectHeaders?: boolean | undefined;\n}\n\n/** Данные о запросе, доступные хукам. */\nexport interface RequestContext {\n method: string;\n /** Путь без базового URL, например `/api/posts`. */\n path: string;\n /** Итоговый URL со строкой запроса. */\n url: string;\n headers: Headers;\n /** Номер попытки, начиная с 1. */\n attempt: number;\n}\n\n/** Данные об успешном ответе. */\nexport interface ResponseContext extends RequestContext {\n status: number;\n /** Длительность запроса в мс. */\n duration: number;\n response: Response;\n}\n\n/** Данные об ошибке запроса. */\nexport interface ErrorContextHook extends RequestContext {\n duration: number;\n error: unknown;\n}\n\n/** Данные о предстоящем повторе. */\nexport interface RetryContext extends RequestContext {\n error: unknown;\n /** Пауза перед следующей попыткой в мс. */\n delay: number;\n}\n\n/**\n * Перехватчики жизненного цикла запроса.\n *\n * Вызываются последовательно; исключение внутри хука прервёт запрос, поэтому свою логику\n * лучше оборачивать в `try`.\n */\nexport interface ClientHooks {\n /** Перед отправкой. Можно дописать заголовки — объект `headers` изменяемый. */\n onRequest?(context: RequestContext): void | Promise<void>;\n /** После успешного ответа, до разбора тела. */\n onResponse?(context: ResponseContext): void | Promise<void>;\n /** При любой ошибке запроса, включая те, что будут повторены. */\n onError?(context: ErrorContextHook): void | Promise<void>;\n /** Перед паузой между попытками. */\n onRetry?(context: RetryContext): void | Promise<void>;\n}\n\n/**\n * Опции конструктора `ItdClient`.\n *\n * Все поля допускают явный `undefined`, чтобы можно было передавать значения, которых\n * может не быть, — например `new ItdClient({ auth: process.env.ITD_TOKEN })`.\n */\nexport interface ItdClientOptions {\n /**\n * Базовый URL API. По умолчанию `https://xn--d1ah4a.com`.\n *\n * Укажите здесь адрес своего прокси, если работаете из браузера: CORS для сторонних\n * источников на итд.com, скорее всего, не настроен.\n */\n baseUrl?: string | undefined;\n /**\n * Сервисы платформы на отдельных доменах.\n *\n * Ключ — имя сервиса, значение — базовый URL или определение целиком. Имя встроенного\n * сервиса задаёт его хост. Встроен один: `status` — хост `itd.platform.status()`.\n *\n * @example\n * ```ts\n * const itd = new ItdClient({\n * services: {\n * status: 'https://my-proxy.example/status',\n * pb: { baseUrl: 'https://pbapi.xn--d1ah4a.com', headers: { Referer: 'https://pixel.xn--d1ah4a.com/' } },\n * },\n * });\n * ```\n */\n services?: Record<string, string | Omit<ServiceDefinition, 'name'>> | undefined;\n /** Авторизация. Без неё доступны только публичные эндпоинты. */\n auth?: AuthInput | undefined;\n /** Где хранить сессию. По умолчанию {@link MemoryTokenStorage}. */\n storage?: TokenStorage | undefined;\n /**\n * Обновлять токен автоматически при ответе `401`. По умолчанию `true`.\n *\n * При `false` библиотека просто пробросит {@link ItdAuthError}, а обновлением\n * вы управляете сами через `itd.auth.refresh()`.\n */\n autoRefresh?: boolean | undefined;\n /**\n * Пытаться ли войти заново, если обновление токена не удалось.\n *\n * Работает, только когда в `auth` переданы email и пароль. По умолчанию `true`.\n */\n reloginOnRefreshFailure?: boolean | undefined;\n /** Своя реализация `fetch`: для Deno, React Native, тестов или прокси. */\n fetch?: typeof fetch | undefined;\n /** Часы для тайм-аутов, повторов и очередей. Обычно подменяются только в тестах. */\n clock?: ItdClock | undefined;\n /** Таймаут запроса в мс. По умолчанию 30000 — столько же использует сайт итд.com. `0` снимает ограничение. */\n timeout?: number | undefined;\n /** Повторные попытки. `false` отключает их полностью. */\n retry?: RetryOptions | false | undefined;\n /** Ограничение нагрузки. `false` отключает очередь. */\n rateLimit?: RateLimitOptions | false | undefined;\n /** Перехватчики запросов. */\n hooks?: ClientHooks | undefined;\n /** Отладочный вывод. `true` — писать в `console`. */\n logger?: Logger | boolean | undefined;\n /** Заголовки, добавляемые ко всем запросам, — например `User-Agent` для бота. */\n headers?: Record<string, string> | undefined;\n /**\n * Значение заголовка `X-Device-Id`, который уходит с каждым запросом.\n *\n * Сервер различает по нему записи в списке сессий, поэтому значение должно быть стабильным.\n * Если не задать, библиотека заведёт идентификатор сама и сохранит его в {@link ItdSession},\n * так что при постоянном хранилище он переживёт перезапуск процесса.\n */\n deviceId?: string | undefined;\n /**\n * Значение заголовка `User-Agent`. `false` — не отправлять его вовсе.\n *\n * По умолчанию `Mozilla/5.0 (compatible; itd-api/<версия>; …)`: `fetch` в Node не шлёт\n * `User-Agent` сам, а сайт стоит за DDoS-Guard, который такие запросы может не пропустить.\n * В браузере опция не действует — там заголовок менять запрещено.\n */\n userAgent?: string | false | undefined;\n /** Как обращаться с cookie. По умолчанию определяется по среде исполнения. */\n mode?: RuntimeMode | undefined;\n}\n\n/** Опции отдельного запроса. Доступны в каждом методе ресурсов. */\nexport interface RequestOptions {\n /** Отмена запроса извне. */\n signal?: AbortSignal | undefined;\n /** Таймаут только для этого запроса, мс. */\n timeout?: number | undefined;\n /** Дополнительные заголовки. */\n headers?: Record<string, string> | undefined;\n /** Повторы только для этого запроса. Переопределяют глобальную настройку `retry`. */\n retry?: RetryOptions | false | undefined;\n}\n\n/**\n * Имена полей {@link RequestOptions} — единственный источник истины.\n *\n * Ресурсы переносят в описание запроса только эти поля (плюс заявленные плагинами),\n * потому что параметры методов подмешивают к ним `limit`, `cursor` и прочее, чему\n * в транспорте делать нечего. Список стоит рядом с интерфейсом, чтобы новое поле нельзя\n * было забыть.\n *\n * `satisfies` гарантирует, что каждое имя в списке — действительно поле `RequestOptions`.\n * Обратную полноту (не забыто ли новое поле) проверяет тип {@link RequestOptionKeysComplete}\n * в тесте: здесь её проверять нельзя — плагины расширяют `RequestOptions` своими опциями\n * (`encrypt`, `decrypt` и подобными), которых в этом списке быть и не должно.\n */\nexport const REQUEST_OPTION_KEYS = [\n 'signal',\n 'timeout',\n 'headers',\n 'retry',\n] as const satisfies readonly (keyof RequestOptions)[];\n\n/**\n * Тип-страж полноты {@link REQUEST_OPTION_KEYS}: `true`, только когда список покрывает\n * все ключи `RequestOptions`. Проверяется в тесте, а не здесь, — в сборке плагина\n * интерфейс расширен, и strict-проверка на месте давала бы ложную ошибку.\n */\nexport type RequestOptionKeysComplete =\n keyof RequestOptions extends (typeof REQUEST_OPTION_KEYS)[number] ? true : never;\n\n/** Полное описание запроса для низкоуровневого `itd.request()`. */\nexport interface RawRequestOptions extends RequestOptions {\n method: string;\n /** Путь с ведущим слэшем, например `/api/posts`. Завершающий слэш значим. */\n path: string;\n /**\n * Имя сервиса, на хост которого уйдёт запрос. Без него запрос идёт на основной `baseUrl`\n * клиента. Сервисы задаются опцией {@link ItdClientOptions.services}.\n */\n service?: string | undefined;\n /**\n * Хост этого запроса. Важнее, чем {@link RawRequestOptions.service}.\n *\n * На посторонний основному API хост Bearer-токен по умолчанию не отправляется.\n * Для осознанного разрешения укажите `skipAuth: false`.\n */\n baseUrl?: string | undefined;\n query?: QueryParams | undefined;\n /** Тело: будет отправлено как JSON. Для загрузки файлов передайте `FormData`. */\n body?: unknown;\n /**\n * Не подставлять заголовок авторизации.\n *\n * Явное `false` разрешает авторизацию и для разового внешнего `baseUrl`; без него\n * токен автоматически отправляется только основному хосту и его поддоменам.\n */\n skipAuth?: boolean | undefined;\n /** Не пытаться обновить токен при `401` — используется самими эндпоинтами авторизации. */\n skipAuthRefresh?: boolean | undefined;\n /**\n * Выполнить запрос мимо очереди.\n *\n * Нужно запросам, которые слой авторизации делает **изнутри** другого запроса: продление\n * токена и отложенный вход. Такой запрос обязан стартовать, пока исходный держит место\n * в очереди и ждёт его результата, — иначе оба ждут друг друга и не завершатся никогда.\n */\n skipQueue?: boolean | undefined;\n /** Вернуть тело ответа без снятия обёртки `{ data: … }`. */\n raw?: boolean | undefined;\n}\n","import type { HttpClient } from '../core/http.js';\nimport type { Page, PageState, PaginationMode } from '../core/pagination.js';\nimport { Paginator } from '../core/pagination.js';\nimport type { QueryParams } from '../core/url.js';\nimport { REQUEST_OPTION_KEYS, type RequestOptions } from '../types/options.js';\n\n/** Параметры перебираемого списка: опции запроса плюс предел числа страниц. */\nexport interface ListParams extends RequestOptions {\n /** Ограничение числа страниц при переборе. */\n maxPages?: number | undefined;\n}\n\n/**\n * Описание перебираемого эндпоинта.\n *\n * Одно место, где заданы путь, параметры запроса, чтение страницы и схема пагинации.\n * {@link BaseResource.paginated} строит из него и разовую загрузку, и перебор.\n *\n * @typeParam T тип элемента списка\n * @typeParam P тип параметров метода\n */\nexport interface ListingSpec<T, P extends ListParams> {\n /** Путь эндпоинта. */\n path: (params: P) => string;\n /** Параметры запроса без полей пагинации — их добавит перебор. */\n query: (params: P) => QueryParams;\n /** Читает страницу из ответа. Получает позицию — она нужна схеме со смещением. */\n read: (body: unknown, state: PageState) => Page<T>;\n /** Схема пагинации эндпоинта. */\n mode: PaginationMode;\n /** Начальная позиция, вычисленная из параметров (курсор, номер или смещение). */\n start: (params: P) => PageState;\n}\n\n/** Пара методов, собранная из {@link ListingSpec}: разовая загрузка и перебор. */\nexport interface Listing<T, P extends ListParams> {\n /** Загружает одну страницу с позиции, заданной параметрами. */\n list(params: P): Promise<Page<T>>;\n /** Перебирает страницы, сама подставляя позиции. */\n iterate(params: P): Paginator<T>;\n}\n\n/** Общая основа всех групп методов клиента. */\nexport class BaseResource {\n /** @internal */\n protected readonly http: HttpClient;\n\n constructor(http: HttpClient) {\n this.http = http;\n }\n\n /**\n * Переносит опции запроса в описание транспорта.\n *\n * Копируются только поля {@link REQUEST_OPTION_KEYS} и опции, заявленные плагинами:\n * параметры методов наследуют {@link RequestOptions} и приносят с собой `limit`, `cursor`\n * и прочее, чему в описании запроса делать нечего. Чужие опции плагинов библиотека\n * не понимает, но обязана донести до обёрток нетронутыми.\n */\n protected requestOptions(options: RequestOptions | undefined): Partial<RequestOptions> {\n if (!options) return {};\n\n const source = options as Record<string, unknown>;\n const result: Record<string, unknown> = {};\n\n for (const key of REQUEST_OPTION_KEYS) {\n const value = source[key];\n if (value !== undefined) result[key] = value;\n }\n\n const pluginKeys = this.http.pluginOptionKeys;\n for (const key of pluginKeys) {\n const value = source[key];\n if (value !== undefined) result[key] = value;\n }\n\n return result as Partial<RequestOptions>;\n }\n\n /**\n * Собирает перебор страниц.\n *\n * @param mode схема пагинации эндпоинта\n * @param load загружает одну страницу для указанной позиции\n * @param options `maxPages` и `signal`, а также `start` — позиция, с которой продолжить\n */\n protected paginate<T>(\n mode: PaginationMode,\n load: (state: PageState) => Promise<Page<T>>,\n options?: RequestOptions & { maxPages?: number; start?: PageState },\n ): Paginator<T> {\n return new Paginator<T>({\n mode,\n load,\n ...(options?.maxPages !== undefined ? { maxPages: options.maxPages } : {}),\n ...(options?.signal !== undefined ? { signal: options.signal } : {}),\n ...(options?.start !== undefined ? { start: options.start } : {}),\n });\n }\n\n /**\n * Собирает пару «загрузка страницы + перебор» из одного описания.\n *\n * Путь, параметры запроса и разбор ответа задаются один раз; `list` и `iterate`\n * строятся из них.\n *\n * @example\n * ```ts\n * #feed = this.paginated<Post, FeedParams>({\n * path: () => '/api/posts',\n * query: (p) => ({ tab: p.tab, limit: p.limit }),\n * start: (p) => (p.cursor ? { cursor: p.cursor } : {}),\n * read: (body) => readCursorPage<Post>(body, 'posts'),\n * mode: PaginationMode.Cursor,\n * });\n * ```\n */\n protected paginated<T, P extends ListParams>(spec: ListingSpec<T, P>): Listing<T, P> {\n const load = async (params: P, state: PageState): Promise<Page<T>> => {\n const body = await this.http.request({\n method: 'GET',\n path: spec.path(params),\n query: withPageState(spec.query(params), state),\n ...this.requestOptions(params),\n });\n return spec.read(body, state);\n };\n\n return {\n list: (params) => load(params, spec.start(params)),\n iterate: (params) =>\n this.paginate<T>(spec.mode, (state) => load(params, state), {\n ...(params.maxPages !== undefined ? { maxPages: params.maxPages } : {}),\n ...(params.signal !== undefined ? { signal: params.signal } : {}),\n start: spec.start(params),\n }),\n };\n }\n}\n\n/** Добавляет позицию страницы в параметры запроса. */\nexport function withPageState(query: QueryParams, state: PageState): QueryParams {\n return {\n ...query,\n ...(state.cursor !== undefined ? { cursor: state.cursor } : {}),\n ...(state.page !== undefined ? { page: state.page } : {}),\n ...(state.offset !== undefined ? { offset: state.offset } : {}),\n };\n}\n","import { AUTH_PATHS, type AuthManager, type TURNSTILE_SITE_KEY } from '../core/auth.js';\nimport { ItdConfigError } from '../core/errors.js';\nimport type { HttpClient } from '../core/http.js';\nimport { pickArray, pickString } from '../core/unwrap.js';\nimport type { AuthState, Session } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.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\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/**\n * Результат входа.\n *\n * Сервер может как сразу выдать токен, так и потребовать код подтверждения — размеченное\n * объединение делает оба случая явными.\n */\nexport type SignInResult =\n | { status: 'authenticated'; accessToken: string }\n | { status: 'otp_required'; flowToken: string | undefined };\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.request<AuthState>({\n method: 'GET',\n path: '/api/profile',\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Регистрирует аккаунт и запускает подтверждение по коду.\n *\n * @returns `flowToken`, который нужно передать в {@link verifyOtp}\n */\n async signUp(credentials: CaptchaCredentials, options: RequestOptions = {}): Promise<string> {\n const body = await this.http.request({\n method: 'POST',\n path: AUTH_PATHS.signUp,\n body: credentials,\n skipAuth: true,\n skipAuthRefresh: true,\n ...this.requestOptions(options),\n });\n\n const flowToken = pickString(body, 'flowToken');\n if (!flowToken) {\n throw new ItdConfigError('Сервер не вернул flowToken при регистрации');\n }\n\n return flowToken;\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 body = await this.http.request({\n method: 'POST',\n path: AUTH_PATHS.signIn,\n body: credentials,\n skipAuth: true,\n skipAuthRefresh: true,\n ...this.requestOptions(options),\n });\n\n const accessToken = pickString(body, 'accessToken');\n\n if (accessToken) {\n await this.#auth.setAccessToken(accessToken);\n return { status: SignInStatus.Authenticated, accessToken };\n }\n\n return { status: SignInStatus.OtpRequired, flowToken: pickString(body, 'flowToken') };\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 body = await this.http.request({\n method: 'POST',\n path: AUTH_PATHS.verifyOtp,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...this.requestOptions(options),\n });\n\n const accessToken = pickString(body, 'accessToken');\n if (!accessToken) {\n throw new ItdConfigError('Сервер не вернул accessToken после подтверждения кода');\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.http.request<void>({\n method: 'POST',\n path: AUTH_PATHS.resendOtp,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...this.requestOptions(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.http.request<void>({\n method: 'POST',\n path: AUTH_PATHS.logout,\n skipAuthRefresh: true,\n ...this.requestOptions(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 async forgotPassword(input: ForgotPasswordInput, options: RequestOptions = {}): Promise<string> {\n const body = await this.http.request({\n method: 'POST',\n path: AUTH_PATHS.forgotPassword,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...this.requestOptions(options),\n });\n\n const flowToken = pickString(body, 'flowToken');\n if (!flowToken) {\n throw new ItdConfigError('Сервер не вернул flowToken при запросе сброса пароля');\n }\n\n return flowToken;\n }\n\n /**\n * Устанавливает новый пароль по коду из письма.\n *\n * Сервер ждёт все четыре поля сразу — `email`, `otp`, `flowToken` и `newPassword`;\n * при нехватке любого отвечает `422`.\n */\n resetPassword(input: ResetPasswordInput, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'POST',\n path: AUTH_PATHS.resetPassword,\n body: input,\n skipAuth: true,\n skipAuthRefresh: true,\n ...this.requestOptions(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.http.request<void>({\n method: 'POST',\n path: AUTH_PATHS.changePassword,\n body: { currentPassword: input.currentPassword, newPassword: input.newPassword },\n ...this.requestOptions(options),\n });\n }\n\n /** Загружает список активных сессий. У текущей поле `isCurrent` равно `true`. */\n async sessions(options: RequestOptions = {}): Promise<Session[]> {\n const body = await this.http.request({\n method: 'GET',\n path: AUTH_PATHS.sessions,\n ...this.requestOptions(options),\n });\n\n return pickArray<Session>(body, 'sessions');\n }\n\n /** Завершает указанную сессию. */\n revokeSession(sessionId: string, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `${AUTH_PATHS.sessions}/${encodeURIComponent(sessionId)}`,\n ...this.requestOptions(options),\n });\n }\n\n /** Завершает все сессии, кроме текущей. */\n revokeOtherSessions(options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: AUTH_PATHS.sessions,\n ...this.requestOptions(options),\n });\n }\n}\n","/** Метка билдера. Через `Symbol.for` — чтобы распознавание переживало смешивание ESM и CJS. */\nexport const BUILDER = Symbol.for('itd.builder');\n\n/**\n * Билдер входных данных.\n *\n * Билдеры необязательны: любой метод, принимающий билдер, принимает и обычный объект.\n * Проверки одинаковы в обоих случаях.\n */\nexport interface ItdBuilder<T> {\n /** @internal */\n readonly [BUILDER]: true;\n /**\n * Собирает и проверяет результат.\n *\n * @throws {ItdConfigError} если нарушены требования к данным\n */\n build(): T;\n /** Чтобы билдер корректно вёл себя внутри `JSON.stringify`. */\n toJSON(): T;\n}\n\n/**\n * Три равноправные формы входа: обычный объект, готовый билдер или функция-настройщик.\n *\n * @example\n * ```ts\n * itd.posts.create({ content: 'привет' }); // объект\n * itd.posts.create(post().content('привет')); // билдер\n * itd.posts.create((p) => p.content('привет')); // функция\n * ```\n */\nexport type BuilderInput<T, B extends ItdBuilder<T>> = T | B | ((builder: B) => B | T);\n\n/** Является ли значение билдером. */\nexport function isBuilder<T>(value: unknown): value is ItdBuilder<T> {\n return typeof value === 'object' && value !== null && BUILDER in value;\n}\n\n/**\n * Приводит любую из трёх форм входа к готовому объекту.\n *\n * Проверка выполняется всегда, независимо от формы: обычный объект проходит ровно те же\n * правила, что и результат билдера.\n *\n * @param input то, что передал пользователь\n * @param factory создаёт пустой билдер для формы с функцией\n * @param validate проверяет собранный объект\n */\nexport function resolveInput<T, B extends ItdBuilder<T>>(\n input: BuilderInput<T, B>,\n factory: () => B,\n validate: (value: T) => T,\n): T {\n if (typeof input === 'function') {\n const result = (input as (builder: B) => B | T)(factory());\n return isBuilder<T>(result) ? result.build() : validate(result as T);\n }\n\n if (isBuilder<T>(input)) return input.build();\n\n return validate(input as T);\n}\n","import type { FileInput } from '../core/attachments.js';\nimport { ItdConfigError } from '../core/errors.js';\nimport type { UserId } from '../types/models.js';\nimport type { CreateCommentInput } from '../types/params.js';\nimport { BUILDER, type BuilderInput, type ItdBuilder, resolveInput } from './base.js';\n\n/** Внутреннее состояние {@link CommentBuilder}. */\ninterface CommentState extends CreateCommentInput {\n content: string;\n attachmentIds: string[];\n files: FileInput[];\n /** Голосовой комментарий: текста быть не должно, вложение ровно одно. */\n voice: boolean;\n}\n\n/**\n * Проверяет данные комментария.\n *\n * @throws {ItdConfigError} если комментарий пуст или голосовой собран неверно\n */\nexport function validateComment(input: CreateCommentInput): CreateCommentInput {\n const content = typeof input?.content === 'string' ? input.content : '';\n const attachmentIds = input?.attachmentIds ?? [];\n const files = input?.files ?? [];\n\n const hasContent = content.trim() !== '';\n const hasAttachments = attachmentIds.length > 0 || files.length > 0;\n\n if (!hasContent && !hasAttachments) {\n throw new ItdConfigError('Комментарий пуст: нужен текст или вложение');\n }\n\n return input;\n}\n\n/** Дополнительная проверка голосового комментария. */\nfunction validateVoice(state: CommentState): void {\n if (state.content.trim() !== '') {\n throw new ItdConfigError(\n 'У голосового комментария не может быть текста: API принимает либо текст, либо аудио',\n );\n }\n\n const total = state.files.length + state.attachmentIds.length;\n if (total !== 1) {\n throw new ItdConfigError(\n `Голосовой комментарий требует ровно одно аудиовложение, передано: ${total}`,\n );\n }\n}\n\n/**\n * Билдер комментария и ответа на комментарий.\n *\n * Неизменяемый: каждый вызов возвращает новый экземпляр. Создаётся функцией {@link comment}.\n */\nexport class CommentBuilder implements ItdBuilder<CreateCommentInput> {\n /** @internal */\n readonly [BUILDER] = true as const;\n\n readonly #state: CommentState;\n\n /** @internal Создавайте билдер функцией {@link comment}. */\n constructor(state: CommentState) {\n this.#state = state;\n }\n\n /** Задаёт текст комментария. */\n content(text: string): CommentBuilder {\n return new CommentBuilder({ ...this.#state, content: text });\n }\n\n /** Прикладывает файл — он будет загружен перед отправкой. */\n attach(file: FileInput): CommentBuilder {\n return new CommentBuilder({ ...this.#state, files: [...this.#state.files, file] });\n }\n\n /** Прикладывает уже загруженное вложение. */\n attachId(attachmentId: string): CommentBuilder {\n return new CommentBuilder({\n ...this.#state,\n attachmentIds: [...this.#state.attachmentIds, attachmentId],\n });\n }\n\n /**\n * Делает комментарий голосовым.\n *\n * Текста у такого комментария быть не должно, а вложение ровно одно — аудио в формате\n * `audio/ogg`. Так его принимает API.\n *\n * @example\n * ```ts\n * import { fromPath } from 'itd-api/node';\n *\n * await itd.posts.comment(postId, (c) => c.voice(fromPath('./answer.ogg')));\n * ```\n */\n voice(audio: FileInput): CommentBuilder {\n return new CommentBuilder({ ...this.#state, files: [audio], voice: true });\n }\n\n /**\n * Кому адресован ответ.\n *\n * Имеет смысл только в `itd.comments.reply()`; при отправке комментария к посту\n * это поле вызовет ошибку.\n */\n replyTo(userId: UserId): CommentBuilder {\n return new CommentBuilder({ ...this.#state, replyToUserId: userId });\n }\n\n build(): CreateCommentInput {\n const { content, attachmentIds, files, voice, ...rest } = this.#state;\n\n if (voice) validateVoice(this.#state);\n\n return validateComment({\n ...rest,\n content,\n ...(attachmentIds.length > 0 ? { attachmentIds } : {}),\n ...(files.length > 0 ? { files } : {}),\n });\n }\n\n toJSON(): CreateCommentInput {\n return this.build();\n }\n}\n\n/**\n * Начинает сборку комментария.\n *\n * @param content текст; можно задать позже методом {@link CommentBuilder.content}\n *\n * @example\n * ```ts\n * import { comment } from 'itd-api';\n *\n * await itd.posts.comment(postId, comment('согласен').attach({ url: memeUrl }));\n * ```\n */\nexport function comment(content = ''): CommentBuilder {\n return new CommentBuilder({ content, attachmentIds: [], files: [], voice: false });\n}\n\n/** Что принимает параметр комментария: объект, билдер или функция-настройщик. */\nexport type CommentInput = BuilderInput<CreateCommentInput, CommentBuilder>;\n\n/**\n * Приводит любую форму входа к готовым данным комментария.\n *\n * @param allowReplyTo разрешено ли поле `replyToUserId`; в комментарии к посту — нет\n */\nexport function resolveComment(input: CommentInput, allowReplyTo = false): CreateCommentInput {\n const resolved = resolveInput(input, () => comment(), validateComment);\n\n if (!allowReplyTo && resolved.replyToUserId !== undefined) {\n throw new ItdConfigError(\n 'replyTo применим только к ответу на комментарий (itd.comments.reply). ' +\n 'В комментарии к посту адресат не указывается.',\n );\n }\n\n return resolved;\n}\n","import { type CommentInput, resolveComment } from '../builders/comment.js';\nimport type { FileInput } from '../core/attachments.js';\nimport type { HttpClient } from '../core/http.js';\nimport { type Page, PaginationMode, type Paginator, readPagedPage } from '../core/pagination.js';\nimport { encodePathSegment } from '../core/url.js';\nimport type { Comment, LikeResult } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/** Параметры запроса ответов на комментарий. */\nexport interface RepliesParams extends RequestOptions {\n limit?: number;\n page?: number;\n maxPages?: number;\n}\n\n/**\n * Комментарии и ответы на них.\n *\n * Доступна как `itd.comments`. Комментарии **к посту** живут в `itd.posts`:\n * `itd.posts.comments()` и `itd.posts.comment()`.\n */\nexport class CommentsResource extends BaseResource {\n readonly #uploadFiles: (files: FileInput[], options?: RequestOptions) => Promise<string[]>;\n\n /** Ответы на комментарий: `/api/comments/{id}/replies`, постраничная пагинация. */\n readonly #replies = this.paginated<Comment, RepliesParams & { commentId: string }>({\n path: (p) => `/api/comments/${encodePathSegment(p.commentId, 'commentId')}/replies`,\n query: (p) => ({ limit: p.limit }),\n start: (p) => (p.page !== undefined ? { page: p.page } : {}),\n read: (body) => readPagedPage<Comment>(body, 'replies'),\n mode: PaginationMode.Page,\n });\n\n constructor(\n http: HttpClient,\n deps: { uploadFiles: (files: FileInput[], options?: RequestOptions) => Promise<string[]> },\n ) {\n super(http);\n this.#uploadFiles = deps.uploadFiles;\n }\n\n /**\n * Загружает страницу ответов на комментарий.\n *\n * Здесь пагинация **постраничная**, в отличие от комментариев к посту, где курсорная.\n */\n replies(commentId: string, params: RepliesParams = {}): Promise<Page<Comment>> {\n return this.#replies.list({ ...params, commentId });\n }\n\n /** Перебирает ответы на комментарий. */\n iterateReplies(commentId: string, params: RepliesParams = {}): Paginator<Comment> {\n return this.#replies.iterate({ ...params, commentId });\n }\n\n /**\n * Отвечает на комментарий.\n *\n * @example\n * ```ts\n * await itd.comments.reply(commentId, 'согласен');\n * await itd.comments.reply(commentId, (c) => c.content('и вот почему').replyTo(userId));\n * ```\n */\n async reply(\n commentId: string,\n input: CommentInput | string,\n options: RequestOptions = {},\n ): Promise<Comment> {\n const data = resolveComment(typeof input === 'string' ? { content: input } : input, true);\n\n const existing = data.attachmentIds ?? [];\n const files = data.files ?? [];\n const attachmentIds =\n files.length > 0 ? [...existing, ...(await this.#uploadFiles(files, options))] : existing;\n\n return this.http.request<Comment>({\n method: 'POST',\n path: `/api/comments/${encodePathSegment(commentId, 'commentId')}/replies`,\n body: {\n content: data.content ?? '',\n attachmentIds,\n ...(data.replyToUserId ? { replyToUserId: data.replyToUserId } : {}),\n },\n ...this.requestOptions(options),\n });\n }\n\n /** Редактирует текст комментария. */\n update(commentId: string, content: string, options: RequestOptions = {}): Promise<Comment> {\n return this.http.request<Comment>({\n method: 'PATCH',\n path: `/api/comments/${encodePathSegment(commentId, 'commentId')}`,\n body: { content },\n ...this.requestOptions(options),\n });\n }\n\n /** Удаляет комментарий. Восстановить его можно через {@link restore}. */\n remove(commentId: string, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `/api/comments/${encodePathSegment(commentId, 'commentId')}`,\n ...this.requestOptions(options),\n });\n }\n\n /** Восстанавливает удалённый комментарий. */\n restore(commentId: string, options: RequestOptions = {}): Promise<Comment> {\n return this.http.request<Comment>({\n method: 'POST',\n path: `/api/comments/${encodePathSegment(commentId, 'commentId')}/restore`,\n ...this.requestOptions(options),\n });\n }\n\n /** Ставит реакцию на комментарий. */\n like(commentId: string, options: RequestOptions = {}): Promise<LikeResult> {\n return this.http.request<LikeResult>({\n method: 'POST',\n path: `/api/comments/${encodePathSegment(commentId, 'commentId')}/like`,\n ...this.requestOptions(options),\n });\n }\n\n /** Убирает реакцию с комментария. */\n unlike(commentId: string, options: RequestOptions = {}): Promise<LikeResult> {\n return this.http.request<LikeResult>({\n method: 'DELETE',\n path: `/api/comments/${encodePathSegment(commentId, 'commentId')}/like`,\n ...this.requestOptions(options),\n });\n }\n}\n","import { ItdConfigError } from './errors.js';\n\n/** Изображения, которые принимает `POST /api/files/upload`. */\nexport const IMAGE_MIME_TYPES = Object.freeze([\n 'image/jpeg',\n 'image/png',\n 'image/gif',\n 'image/webp',\n 'image/avif',\n 'image/heic',\n 'image/heif',\n] as const);\nexport type ImageMimeType = (typeof IMAGE_MIME_TYPES)[number];\n\n/** Видео, которые принимает `POST /api/files/upload`. */\nexport const VIDEO_MIME_TYPES = Object.freeze([\n 'video/mp4',\n 'video/webm',\n 'video/quicktime',\n] as const);\nexport type VideoMimeType = (typeof VIDEO_MIME_TYPES)[number];\n\n/** Аудио для голосовых комментариев. */\nexport const AUDIO_MIME_TYPES = Object.freeze(['audio/ogg'] as const);\nexport type AudioMimeType = (typeof AUDIO_MIME_TYPES)[number];\n\n/** Все типы, которые принимает загрузка. */\nexport const ALLOWED_MIME_TYPES = Object.freeze([\n ...IMAGE_MIME_TYPES,\n ...VIDEO_MIME_TYPES,\n ...AUDIO_MIME_TYPES,\n] as const);\nexport type AllowedMimeType = (typeof ALLOWED_MIME_TYPES)[number];\n\n/**\n * Соответствие расширения и MIME-типа.\n *\n * Своя маленькая таблица вместо зависимости: библиотеке нужны ровно те типы,\n * которые принимает API.\n */\nconst EXTENSION_TO_MIME: Readonly<Record<string, string>> = Object.freeze({\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n jfif: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n avif: 'image/avif',\n heic: 'image/heic',\n heif: 'image/heif',\n mp4: 'video/mp4',\n m4v: 'video/mp4',\n webm: 'video/webm',\n mov: 'video/quicktime',\n qt: 'video/quicktime',\n ogg: 'audio/ogg',\n oga: 'audio/ogg',\n opus: 'audio/ogg',\n});\n\n/** Определяет MIME-тип по расширению имени файла. */\nexport function mimeFromFilename(filename: string): string | undefined {\n const dot = filename.lastIndexOf('.');\n if (dot < 0) return undefined;\n\n return EXTENSION_TO_MIME[filename.slice(dot + 1).toLowerCase()];\n}\n\n/** Разрешён ли тип к загрузке. */\nexport function isAllowedMime(mimeType: string): mimeType is AllowedMimeType {\n return (ALLOWED_MIME_TYPES as readonly string[]).includes(mimeType.toLowerCase());\n}\n\n/**\n * Проверяет тип файла до отправки.\n *\n * Отказ на стороне клиента экономит и время, и трафик: тот же `UNSUPPORTED_FILE_TYPE`\n * пришёл бы с сервера, но уже после загрузки всего файла.\n *\n * @throws {ItdConfigError} если тип не поддерживается\n */\nexport function assertAllowedMime(mimeType: string | undefined, filename?: string): void {\n if (!mimeType) {\n throw new ItdConfigError(\n `Не удалось определить тип файла${filename ? ` «${filename}»` : ''}. ` +\n 'Укажите его явно: { file, filename, contentType }.',\n );\n }\n\n if (!isAllowedMime(mimeType)) {\n throw new ItdConfigError(\n `Тип «${mimeType}» не поддерживается загрузкой. Допустимые: ${ALLOWED_MIME_TYPES.join(', ')}.`,\n );\n }\n}\n","/** Один потоковый файл в multipart/form-data. */\nexport interface MultipartFile {\n stream: ReadableStream<Uint8Array>;\n filename: string;\n contentType: string;\n}\n\n/** Результат сборки потокового multipart-тела. */\nexport interface MultipartBody {\n body: ReadableStream<Uint8Array>;\n contentType: string;\n cancel: (reason?: unknown) => Promise<void>;\n}\n\nconst encoder = new TextEncoder();\n\n/** Убирает возможность внедрить дополнительный multipart-заголовок через имя файла. */\nfunction quotedFilename(filename: string): string {\n return filename\n .replace(/[\\r\\n]/g, '')\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"');\n}\n\n/** Удаляет разделители строк, чтобы значение не создало новый multipart-заголовок. */\nfunction headerValue(value: string): string {\n return value.replace(/[\\r\\n]/g, '');\n}\n\n/** Создаёт уникальную границу без зависимости от Web Crypto. */\nfunction boundary(): string {\n return `----itd-api-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Кодирует один файл в multipart без полной буферизации.\n *\n * Читает следующий чанк только когда потребитель запросил данные.\n */\nexport function createMultipartFileBody(file: MultipartFile): MultipartBody {\n const marker = boundary();\n const prefix = encoder.encode(\n `--${marker}\\r\\n` +\n `Content-Disposition: form-data; name=\"file\"; filename=\"${quotedFilename(file.filename)}\"\\r\\n` +\n `Content-Type: ${headerValue(file.contentType)}\\r\\n\\r\\n`,\n );\n const suffix = encoder.encode(`\\r\\n--${marker}--\\r\\n`);\n const reader = file.stream.getReader();\n let stage: 'prefix' | 'file' | 'suffix' | 'done' = 'prefix';\n let cancelled = false;\n\n const cancel = async (reason?: unknown) => {\n if (cancelled) return;\n cancelled = true;\n await reader.cancel(reason).catch(() => {});\n };\n\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n if (stage === 'prefix') {\n stage = 'file';\n controller.enqueue(prefix);\n return;\n }\n\n if (stage === 'file') {\n try {\n const next = await reader.read();\n if (!next.done) {\n controller.enqueue(next.value);\n return;\n }\n stage = 'suffix';\n } catch (error) {\n stage = 'done';\n controller.error(error);\n return;\n }\n }\n\n if (stage === 'suffix') {\n stage = 'done';\n controller.enqueue(suffix);\n controller.close();\n }\n },\n cancel,\n });\n\n return {\n body,\n contentType: `multipart/form-data; boundary=${marker}`,\n cancel,\n };\n}\n","import {\n boundedFileStream,\n downloadFile,\n type FileContent,\n type FileContext,\n type FileInput,\n type FileStreamContent,\n FileTransferMode,\n isBoundedFileStream,\n type LazyFile,\n normalizeMimeType,\n openUrlFile,\n resolveFileStreamOptions,\n type StreamFile,\n type UrlFile,\n} from '../core/attachments.js';\nimport { ItdConfigError, ItdError, ItdFileError, ItdFileErrorReason } from '../core/errors.js';\nimport type { HttpClient } from '../core/http.js';\nimport { assertAllowedMime, mimeFromFilename } from '../core/mime.js';\nimport { createMultipartFileBody } from '../core/multipart.js';\nimport type { RequestBodyFactory } from '../core/pipeline.js';\nimport { isBlob, isFile } from '../core/runtime.js';\nimport { encodePathSegment } from '../core/url.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/** Ответ загрузки файла. */\nexport interface UploadedFile {\n /** Идентификатор вложения — его передают в `attachmentIds`. */\n id: string;\n /** Адрес файла на CDN. */\n url: string;\n}\n\n/** Настройки загрузки. */\nexport interface UploadOptions extends RequestOptions {\n /** Имя файла. Используется для определения MIME, если тип не задан. */\n filename?: string;\n /** MIME-тип. По умолчанию определяется по имени или `Blob`. */\n contentType?: string;\n /** Проверять тип до отправки. По умолчанию `true`. */\n validateMime?: boolean;\n /** Дополнительный предел размера для любого вида источника. */\n maxBytes?: number | undefined;\n /** Размер очереди библиотеки при потоковой передаче. */\n streamBufferBytes?: number | undefined;\n}\n\n/** Таймаут одной попытки загрузки файла — 5 минут. */\nexport const DEFAULT_UPLOAD_TIMEOUT = 300_000;\n\ninterface PreparedBuffer {\n mode: 'buffer';\n blob: Blob;\n filename: string;\n}\n\ninterface PreparedStream {\n mode: 'stream';\n stream: ReadableStream<Uint8Array>;\n filename: string;\n contentType: string;\n close?: (() => void | Promise<void>) | undefined;\n}\n\ntype PreparedFile = PreparedBuffer | PreparedStream;\n\n/** Файлы и медиа. */\nexport class FilesResource extends BaseResource {\n readonly #fetch: typeof fetch;\n\n constructor(http: HttpClient, deps: { fetch: typeof fetch }) {\n super(http);\n this.#fetch = deps.fetch;\n }\n\n /**\n * Загружает файл и возвращает его идентификатор.\n *\n * Потоковый источник открывается заново при каждой повторной попытке. Буферный источник\n * после успешного чтения переиспользуется.\n */\n upload(input: FileInput, options: UploadOptions = {}): Promise<UploadedFile> {\n const bodyFactory = this.#createBodyFactory(input, options);\n\n return this.http.request<UploadedFile>({\n method: 'POST',\n path: '/api/files/upload',\n bodyFactory,\n retryNetworkWrite: true,\n timeout: options.timeout ?? DEFAULT_UPLOAD_TIMEOUT,\n ...this.requestOptions(options),\n });\n }\n\n /** Загружает несколько файлов последовательно, сохраняя порядок. */\n async uploadMany(files: FileInput[], options: UploadOptions = {}): Promise<string[]> {\n const ids: string[] = [];\n for (const file of files) ids.push((await this.upload(file, options)).id);\n return ids;\n }\n\n /**\n * Загружает сведения о файле.\n *\n * Для ещё не прикреплённого файла сервер может ответить `404`.\n */\n get(fileId: string, options: RequestOptions = {}): Promise<unknown> {\n return this.http.request({\n method: 'GET',\n path: `/api/files/${encodePathSegment(fileId, 'fileId')}`,\n ...this.requestOptions(options),\n });\n }\n\n /** Удаляет загруженный файл. */\n remove(fileId: string, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `/api/files/${encodePathSegment(fileId, 'fileId')}`,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Создаёт фабрику тела. Успешно подготовленный буфер кешируется, а поток открывается\n * заново, поэтому каждая транспортная попытка получает непрочитанное тело.\n */\n #createBodyFactory(input: FileInput, options: UploadOptions): RequestBodyFactory {\n const streamMode = this.#isStreamInput(input);\n let buffered: Promise<PreparedBuffer> | undefined;\n\n return async ({ signal, attempt }) => {\n const context: FileContext = { fetch: this.#fetch, signal, attempt };\n let prepared: PreparedFile;\n\n try {\n if (streamMode) {\n prepared = await this.#prepareStream(input, options, context);\n } else {\n buffered ??= this.#prepareBuffer(input, options, context).catch((error: unknown) => {\n buffered = undefined;\n throw error;\n });\n prepared = await buffered;\n }\n } catch (error) {\n if (error instanceof ItdError || signal.aborted) throw error;\n throw new ItdFileError('не удалось получить содержимое вложения', {\n reason: ItdFileErrorReason.Read,\n retryable: true,\n cause: error,\n });\n }\n\n if (prepared.mode === 'buffer') {\n const form = new FormData();\n form.set('file', prepared.blob, prepared.filename);\n return { body: form };\n }\n\n const multipart = createMultipartFileBody(prepared);\n return {\n body: multipart.body,\n headers: { 'Content-Type': multipart.contentType },\n cleanup: async () => {\n await multipart.cancel();\n await prepared.close?.();\n },\n };\n };\n }\n\n /** Определяет режим без чтения источника. */\n #isStreamInput(input: FileInput): boolean {\n if (typeof input !== 'object' || input === null) return false;\n if ('open' in input) return true;\n if ('url' in input) {\n return resolveFileStreamOptions(input).mode === FileTransferMode.Stream;\n }\n return false;\n }\n\n /** Получает и проверяет буферный источник. */\n async #prepareBuffer(\n input: FileInput,\n options: UploadOptions,\n context: FileContext,\n ): Promise<PreparedBuffer> {\n const content = await this.#resolveBuffer(input, context);\n const filename =\n options.filename ??\n content.filename ??\n this.#nameFromMime(options.contentType ?? content.contentType);\n const contentType =\n normalizeMimeType(options.contentType ?? content.contentType) ??\n normalizeMimeType(isBlob(content.file) ? content.file.type : undefined) ??\n mimeFromFilename(filename);\n\n if (options.validateMime !== false) assertAllowedMime(contentType, filename);\n\n const blob =\n isBlob(content.file) && (!contentType || content.file.type === contentType)\n ? content.file\n : new Blob([content.file as BlobPart], { type: contentType ?? '' });\n\n const limits = resolveFileStreamOptions(options);\n if (limits.maxBytes !== undefined && blob.size > limits.maxBytes) {\n throw new ItdFileError(`файл больше предела в ${limits.maxBytes} байт: ${blob.size}`, {\n reason: ItdFileErrorReason.TooLarge,\n limit: limits.maxBytes,\n actual: blob.size,\n });\n }\n\n return { mode: 'buffer', blob, filename };\n }\n\n /** Открывает и проверяет потоковый источник. */\n async #prepareStream(\n input: FileInput,\n options: UploadOptions,\n context: FileContext,\n ): Promise<PreparedStream> {\n let opened: FileStreamContent;\n if (typeof input === 'object' && input !== null && 'open' in input) {\n opened = await (input as StreamFile).open(context);\n } else if (typeof input === 'object' && input !== null && 'url' in input) {\n const { url, ...urlOptions } = input as UrlFile;\n opened = await openUrlFile(url, urlOptions, context);\n } else {\n throw new ItdConfigError('потоковое вложение должно иметь форму { open } или { url, mode }');\n }\n\n if (\n !opened ||\n typeof ReadableStream === 'undefined' ||\n !(opened.stream instanceof ReadableStream)\n ) {\n await opened?.close?.();\n throw new ItdConfigError('потоковый источник должен вернуть { stream: ReadableStream }');\n }\n\n try {\n const filename =\n options.filename ??\n opened.filename ??\n this.#nameFromMime(options.contentType ?? opened.contentType);\n const contentType =\n normalizeMimeType(options.contentType ?? opened.contentType) ?? mimeFromFilename(filename);\n if (options.validateMime !== false) assertAllowedMime(contentType, filename);\n\n const limits = resolveFileStreamOptions({\n mode: FileTransferMode.Stream,\n ...(options.maxBytes !== undefined ? { maxBytes: options.maxBytes } : {}),\n ...(options.streamBufferBytes !== undefined\n ? { streamBufferBytes: options.streamBufferBytes }\n : {}),\n });\n if (\n limits.maxBytes !== undefined &&\n opened.size !== undefined &&\n opened.size > limits.maxBytes\n ) {\n throw new ItdFileError(`файл больше предела в ${limits.maxBytes} байт: ${opened.size}`, {\n reason: ItdFileErrorReason.TooLarge,\n limit: limits.maxBytes,\n actual: opened.size,\n });\n }\n\n const keepExistingLimits =\n isBoundedFileStream(opened.stream) &&\n options.maxBytes === undefined &&\n options.streamBufferBytes === undefined;\n\n return {\n mode: 'stream',\n stream: keepExistingLimits\n ? opened.stream\n : boundedFileStream(opened.stream, {\n ...(limits.maxBytes !== undefined ? { maxBytes: limits.maxBytes } : {}),\n streamBufferBytes: limits.streamBufferBytes,\n signal: context.signal,\n }),\n filename,\n contentType: contentType ?? '',\n ...(opened.close ? { close: opened.close } : {}),\n };\n } catch (error) {\n await opened.close?.();\n throw error;\n }\n }\n\n /** Разрешает только буферные формы. */\n #resolveBuffer(input: FileInput, context: FileContext): FileContent | Promise<FileContent> {\n if (input instanceof ArrayBuffer || ArrayBuffer.isView(input) || isBlob(input)) {\n return {\n file: input as Blob | ArrayBuffer | Uint8Array,\n ...(isFile(input) ? { filename: input.name } : {}),\n };\n }\n\n if (typeof input !== 'object' || input === null) {\n throw new ItdConfigError(\n `вложение задано значением типа ${typeof input}; ожидается бинарное значение ` +\n 'или объект { file }, { url }, { load }, { open }. ' +\n \"Для файла на диске используйте fromPath('./photo.jpg') из itd-api/node\",\n );\n }\n\n if ('load' in input) return (input as LazyFile).load(context);\n if ('url' in input) {\n const { url, ...urlOptions } = input as UrlFile;\n return downloadFile(url, urlOptions, context);\n }\n if ('file' in input) return input as FileContent;\n if ('open' in input) {\n throw new ItdConfigError('потоковый источник нельзя использовать как буферный');\n }\n\n throw new ItdConfigError(\n 'вложение не распознано: ожидается { file }, { url }, { load } или { open }',\n );\n }\n\n /** Подбирает непустое имя, обязательное для multipart. */\n #nameFromMime(contentType: string | undefined): string {\n const extension = normalizeMimeType(contentType)?.split('/')[1];\n return extension ? `file.${extension}` : 'file';\n }\n}\n","import { type Page, PaginationMode, type Paginator, readCursorPage } from '../core/pagination.js';\nimport { pickArray } from '../core/unwrap.js';\nimport { encodePathSegment } from '../core/url.js';\nimport type { Hashtag, Post } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/** Параметры запроса постов по хэштегу. */\nexport interface HashtagPostsParams extends RequestOptions {\n limit?: number;\n cursor?: string;\n maxPages?: number;\n}\n\n/**\n * Хэштеги.\n *\n * Доступна как `itd.hashtags`.\n */\nexport class HashtagsResource extends BaseResource {\n /** Посты по хэштегу: `/api/hashtags/{tag}/posts`, курсорная пагинация. */\n readonly #posts = this.paginated<Post, HashtagPostsParams & { tag: string }>({\n path: (p) => `/api/hashtags/${encodePathSegment(p.tag, 'tag')}/posts`,\n query: (p) => ({ limit: p.limit }),\n start: (p) => (p.cursor ? { cursor: p.cursor } : {}),\n read: (body) => readCursorPage<Post>(body, 'posts'),\n mode: PaginationMode.Cursor,\n });\n\n /**\n * Ищет хэштеги.\n *\n * Без строки запроса возвращает общий список.\n */\n async search(\n query?: string,\n params: { limit?: number } & RequestOptions = {},\n ): Promise<Hashtag[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/hashtags',\n query: { q: query, limit: params.limit },\n ...this.requestOptions(params),\n });\n\n return pickArray<Hashtag>(body, 'hashtags');\n }\n\n /** Загружает трендовые хэштеги. */\n async trending(params: { limit?: number } & RequestOptions = {}): Promise<Hashtag[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/hashtags/trending',\n query: { limit: params.limit },\n ...this.requestOptions(params),\n });\n\n return pickArray<Hashtag>(body, 'hashtags');\n }\n\n /**\n * Загружает страницу постов по хэштегу.\n *\n * @param tag название без решётки; кодируется автоматически, поэтому кириллица\n * и пробелы допустимы\n */\n posts(tag: string, params: HashtagPostsParams = {}): Promise<Page<Post>> {\n return this.#posts.list({ ...params, tag });\n }\n\n /** Перебирает посты по хэштегу. */\n iteratePosts(tag: string, params: HashtagPostsParams = {}): Paginator<Post> {\n return this.#posts.iterate({ ...params, tag });\n }\n}\n","import { type Page, PaginationMode, type Paginator, readOffsetPage } from '../core/pagination.js';\nimport { pickBoolean, pickNumber } from '../core/unwrap.js';\nimport { encodePathSegment } from '../core/url.js';\nimport { normalizeNotification } from '../notifications/normalize.js';\nimport type { Notification, NotificationSettings } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\nconst NOTIFICATION_SETTING_KEYS = [\n 'enabled',\n 'sound',\n 'follows',\n 'wallPosts',\n 'likes',\n 'comments',\n 'mentions',\n] as const;\n\n/**\n * Сколько идентификаторов уходит в одном запросе на отметку прочтения.\n *\n * Столько же отправляет сайт итд.com — значит на сервере, скорее всего, есть ограничение.\n */\nconst READ_BATCH_SIZE = 20;\n\n/** Параметры запроса списка уведомлений. */\nexport interface NotificationListParams extends RequestOptions {\n limit?: number;\n /** Смещение от начала списка. */\n offset?: number;\n maxPages?: number;\n}\n\n/** Изменяемые настройки уведомлений. */\nexport type UpdateNotificationSettingsInput = Partial<NotificationSettings>;\n\n/**\n * Читает настройки уведомлений.\n *\n * Сервер отдаёт плоский объект: `enabled`, `sound`, `follows`, `wallPosts`, `likes`,\n * `comments`, `mentions`. Отсутствующая настройка считается включённой — так же\n * ведёт себя сайт итд.com.\n */\nfunction readSettings(body: unknown): NotificationSettings {\n const settings = {} as NotificationSettings;\n for (const key of NOTIFICATION_SETTING_KEYS) settings[key] = pickBoolean(body, key, true);\n return settings;\n}\n\n/**\n * Уведомления: список, счётчик, отметки о прочтении, настройки.\n *\n * Доступна как `itd.notifications`. Все уведомления приведены к единой форме, поэтому\n * объекты отсюда и из потока событий можно складывать в один список.\n */\nexport class NotificationsResource extends BaseResource {\n /** Уведомления: `/api/notifications/`, пагинация по смещению. */\n readonly #list = this.paginated<Notification, NotificationListParams>({\n // Завершающий слэш обязателен: без него сервер отвечает ошибкой.\n path: () => '/api/notifications/',\n query: (p) => ({ limit: p.limit }),\n start: (p) => ({ offset: p.offset ?? 0 }),\n read: (body, state) => {\n const page = readOffsetPage<unknown>(body, 'notifications', state.offset ?? 0);\n // Сайт итд.com оборачивает смещение в строку и притворяется, что это курсор;\n // библиотека отдаёт честное число, а сами уведомления — в единой форме.\n return { ...page, items: page.items.map(normalizeNotification) };\n },\n mode: PaginationMode.Offset,\n });\n\n /**\n * Загружает страницу уведомлений.\n *\n * Пагинация здесь основана на смещении.\n *\n * @example\n * ```ts\n * const page = await itd.notifications.list({ limit: 20 });\n * const next = await itd.notifications.list({ limit: 20, offset: page.nextOffset });\n * ```\n */\n list(params: NotificationListParams = {}): Promise<Page<Notification>> {\n return this.#list.list(params);\n }\n\n /**\n * Перебирает уведомления.\n *\n * @example\n * ```ts\n * for await (const notification of itd.notifications.iterate()) {\n * console.log(formatNotificationText(notification));\n * }\n * ```\n */\n iterate(params: NotificationListParams = {}): Paginator<Notification> {\n return this.#list.iterate(params);\n }\n\n /** Загружает число непрочитанных уведомлений. */\n async count(options: RequestOptions = {}): Promise<number> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/notifications/count',\n ...this.requestOptions(options),\n });\n\n return pickNumber(body, 'count', 0);\n }\n\n /**\n * Отмечает уведомление прочитанным.\n *\n * @returns сколько записей отметил сервер\n */\n async markRead(notificationId: string, options: RequestOptions = {}): Promise<number> {\n const body = await this.http.request({\n method: 'POST',\n path: `/api/notifications/${encodePathSegment(notificationId, 'notificationId')}/read`,\n ...this.requestOptions(options),\n });\n\n return pickNumber(body, 'markedCount', 0);\n }\n\n /**\n * Отмечает прочитанными сразу несколько уведомлений.\n *\n * Список автоматически режется на части по 20 идентификаторов — столько же отправляет\n * сайт итд.com, поэтому на сервере вероятен предел. Части уходят последовательно,\n * результат суммируется.\n *\n * @returns сколько записей отметил сервер суммарно\n */\n async markReadBatch(ids: string[], options: RequestOptions = {}): Promise<number> {\n let marked = 0;\n\n for (let index = 0; index < ids.length; index += READ_BATCH_SIZE) {\n const chunk = ids.slice(index, index + READ_BATCH_SIZE);\n\n const body = await this.http.request({\n method: 'POST',\n path: '/api/notifications/read-batch',\n body: { ids: chunk },\n ...this.requestOptions(options),\n });\n\n marked += pickNumber(body, 'markedCount', 0);\n }\n\n return marked;\n }\n\n /** Отмечает прочитанными все уведомления. */\n async markAllRead(options: RequestOptions = {}): Promise<number> {\n const body = await this.http.request({\n method: 'POST',\n path: '/api/notifications/read-all',\n ...this.requestOptions(options),\n });\n\n return pickNumber(body, 'markedCount', 0);\n }\n\n /** Загружает настройки уведомлений. */\n async getSettings(options: RequestOptions = {}): Promise<NotificationSettings> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/notifications/settings',\n ...this.requestOptions(options),\n });\n\n return readSettings(body);\n }\n\n /**\n * Обновляет настройки уведомлений.\n *\n * Отправляются только изменяемые поля, в том же виде, в каком сервер их возвращает.\n */\n async updateSettings(\n input: UpdateNotificationSettingsInput,\n options: RequestOptions = {},\n ): Promise<NotificationSettings> {\n const payload: Record<string, boolean> = {};\n\n for (const key of NOTIFICATION_SETTING_KEYS) {\n const value = input[key];\n if (value !== undefined) payload[key] = value;\n }\n\n const body = await this.http.request({\n method: 'PUT',\n path: '/api/notifications/settings',\n body: payload,\n ...this.requestOptions(options),\n });\n\n return readSettings(body);\n }\n}\n","/** Отметка времени без часового пояса: `2026-07-23 23:14:25`, возможно с долями секунды. */\nconst NAIVE_STAMP = /^(\\d{4}-\\d{2}-\\d{2}) (\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?)$/;\n\n/**\n * Приводит отметку времени без часового пояса к ISO-8601, считая её временем UTC.\n *\n * Строку другого вида возвращает нетронутой.\n *\n * @example\n * ```ts\n * utcStampToIso('2026-07-23 23:14:25'); // '2026-07-23T23:14:25Z'\n * utcStampToIso('2026-07-23T23:14:25Z'); // без изменений\n * ```\n */\nexport function utcStampToIso(value: string): string {\n const match = typeof value === 'string' ? NAIVE_STAMP.exec(value) : null;\n if (!match) return value;\n\n const iso = `${match[1]}T${match[2]}Z`;\n return Number.isFinite(Date.parse(iso)) ? iso : value;\n}\n","import { STATUS_SERVICE } from '../core/config.js';\nimport { utcStampToIso } from '../core/time.js';\nimport { isRecord, pickArray } from '../core/unwrap.js';\nimport type {\n Announcement,\n ChangelogEntry,\n PlatformStatus,\n Portal,\n ServiceStatus,\n} from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/** Требования к версии одного приложения платформы. */\nexport interface PlatformClientVersion {\n /** Минимальная поддерживаемая версия приложения. */\n minVersion: string;\n /** Последняя доступная версия приложения. */\n latestVersion: string;\n /** Адрес страницы обновления приложения. */\n updateUrl: string;\n}\n\n/** Версии клиентских приложений платформы. */\nexport interface PlatformVersions {\n android: PlatformClientVersion;\n ios: PlatformClientVersion;\n [client: string]: PlatformClientVersion;\n}\n\n/** Приводит `last_checked` каждого сервиса к ISO. Остальное остаётся как прислал сервер. */\nfunction normalizeStatus(body: PlatformStatus): PlatformStatus {\n if (!isRecord(body) || !Array.isArray(body.services)) return body;\n\n return {\n ...body,\n services: body.services.map((service: ServiceStatus) =>\n typeof service?.last_checked === 'string'\n ? { ...service, last_checked: utcStampToIso(service.last_checked) }\n : service,\n ),\n };\n}\n\n/**\n * Сведения о платформе: версии приложений, изменения, анонсы, баннер события.\n *\n * Доступна как `itd.platform`.\n */\nexport class PlatformResource extends BaseResource {\n /**\n * Загружает минимальные и актуальные версии клиентских приложений.\n *\n * Endpoint публичный: автоматическая авторизация в запрос не добавляется.\n *\n * @example\n * ```ts\n * const versions = await itd.platform.version();\n * console.log(versions.android.latestVersion);\n * ```\n */\n version(options: RequestOptions = {}): Promise<PlatformVersions> {\n return this.http.request<PlatformVersions>({\n method: 'GET',\n path: '/api/platform/version',\n skipAuth: true,\n ...this.requestOptions(options),\n });\n }\n\n /** Загружает журнал изменений. */\n async changelog(options: RequestOptions = {}): Promise<ChangelogEntry[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/platform/changelog',\n ...this.requestOptions(options),\n });\n\n return Array.isArray(body) ? (body as ChangelogEntry[]) : [];\n }\n\n /** Загружает анонсы платформы. */\n async announcements(options: RequestOptions = {}): Promise<Announcement[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/platform/announcements',\n ...this.requestOptions(options),\n });\n\n return pickArray<Announcement>(body, 'announcements');\n }\n\n /** Загружает баннер текущего события — виджет «портал». */\n portal(options: RequestOptions = {}): Promise<Portal> {\n return this.http.request<Portal>({\n method: 'GET',\n path: '/api/v1/portal',\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Загружает состояние сервисов платформы за последние 90 суток.\n *\n * Идёт на хост `статус.итд.com` без авторизации. Ответ кэшируется сервером на минуту.\n * История по суткам приходит разреженной, ровный массив даёт `statusDays`.\n *\n * @example\n * ```ts\n * const status = await itd.platform.status();\n *\n * if (status.overall_status !== 'operational') {\n * const broken = status.services.filter((s) => s.current_status !== 'operational');\n * console.log('лежит:', broken.map((s) => s.name).join(', '));\n * }\n * ```\n */\n async status(options: RequestOptions = {}): Promise<PlatformStatus> {\n const body = await this.http.request<PlatformStatus>({\n method: 'GET',\n service: STATUS_SERVICE,\n path: '/api/status',\n ...this.requestOptions(options),\n });\n\n return normalizeStatus(body);\n }\n}\n","import { ItdConfigError } from '../core/errors.js';\nimport type { Span } from '../types/models.js';\n\n/**\n * Проверяет, что spans целиком лежат внутри текста.\n *\n * Возвращает копии, чтобы последующая мутация входного массива не меняла уже собранные данные.\n */\nexport function validateSpans(content: string, spans: unknown): Span[] {\n if (!Array.isArray(spans)) {\n throw new ItdConfigError('spans должен быть массивом');\n }\n if (content.length === 0 && spans.length > 0) {\n throw new ItdConfigError('Нельзя задать spans без текста: сначала укажите непустой content');\n }\n\n return spans.map((span, index) => {\n if (!span || typeof span !== 'object') {\n throw new ItdConfigError(`Некорректный span №${index + 1}: ожидался объект`);\n }\n\n const candidate = span as Partial<Span>;\n if (\n typeof candidate.type !== 'string' ||\n !Number.isInteger(candidate.offset) ||\n !Number.isInteger(candidate.length) ||\n (candidate.offset ?? -1) < 0 ||\n (candidate.length ?? 0) <= 0 ||\n (candidate.offset ?? 0) + (candidate.length ?? 0) > content.length\n ) {\n throw new ItdConfigError(\n `Некорректный span №${index + 1}: offset и length должны указывать на непустой фрагмент content`,\n );\n }\n\n return { ...candidate } as Span;\n });\n}\n","import type { TextMarkup } from '../builders/markup.js';\nimport { SpanType } from '../types/enums.js';\nimport type { Span } from '../types/models.js';\nimport { validateSpans } from './validate.js';\n\n/** Настройки безопасного импорта разметки. */\nexport interface ParseMarkupOptions {\n /**\n * Разрешённые схемы ссылок без завершающего двоеточия.\n *\n * По умолчанию разрешены только `http` и `https`. Относительные ссылки не превращаются\n * в spans: wire-формату нужен самостоятельный адрес.\n */\n allowedLinkProtocols?: readonly string[];\n}\n\ninterface MutableMarkup {\n content: string;\n spans: Span[];\n}\n\ninterface OpenHtmlSpan {\n tag: string;\n start: number;\n span: Omit<Span, 'offset' | 'length'>;\n}\n\nconst HTML_BLOCK_TAGS = new Set([\n 'address',\n 'article',\n 'aside',\n 'blockquote',\n 'div',\n 'footer',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'header',\n 'li',\n 'main',\n 'nav',\n 'ol',\n 'p',\n 'pre',\n 'section',\n 'ul',\n]);\n\nconst HTML_DROPPED_TAGS = new Set([\n 'applet',\n 'canvas',\n 'embed',\n 'iframe',\n 'math',\n 'noscript',\n 'object',\n 'script',\n 'style',\n 'svg',\n 'template',\n]);\n\nconst HTML_ENTITIES: Readonly<Record<string, string>> = Object.freeze({\n amp: '&',\n apos: \"'\",\n gt: '>',\n lt: '<',\n nbsp: '\\u00a0',\n quot: '\"',\n});\n\nfunction append(output: MutableMarkup, value: string): void {\n output.content += value;\n}\n\nfunction addSpan(\n output: MutableMarkup,\n start: number,\n span: Omit<Span, 'offset' | 'length'>,\n): void {\n const length = output.content.length - start;\n if (length > 0) output.spans.push({ ...span, offset: start, length });\n}\n\nfunction finish(output: MutableMarkup): TextMarkup {\n const unique = new Map<string, Span>();\n for (const span of output.spans) {\n const key = [\n span.type,\n span.offset,\n span.length,\n span.url ?? '',\n span.tag ?? '',\n span.username ?? '',\n span.id ?? '',\n ].join('\\u0000');\n if (!unique.has(key)) unique.set(key, span);\n }\n\n return {\n content: output.content,\n spans: validateSpans(output.content, [...unique.values()]).sort(\n (left, right) => left.offset - right.offset || right.length - left.length,\n ),\n };\n}\n\nfunction allowedProtocols(options: ParseMarkupOptions): Set<string> {\n const values = options.allowedLinkProtocols ?? ['http', 'https'];\n return new Set(\n values\n .map((value) => `${value.toLowerCase().replace(/:$/u, '')}:`)\n .filter((value) => value !== ':'),\n );\n}\n\nfunction safeLink(value: string, protocols: ReadonlySet<string>): string | null {\n try {\n const url = new URL(value);\n return url.hostname !== '' && protocols.has(url.protocol.toLowerCase()) ? value : null;\n } catch {\n return null;\n }\n}\n\nfunction isEscaped(value: string, index: number): boolean {\n let slashes = 0;\n for (let cursor = index - 1; cursor >= 0 && value[cursor] === '\\\\'; cursor -= 1) slashes += 1;\n return slashes % 2 === 1;\n}\n\nfunction findClosing(value: string, marker: string, from: number): number {\n let cursor = from;\n while (cursor < value.length) {\n const found = value.indexOf(marker, cursor);\n if (found < 0) return -1;\n if (!isEscaped(value, found)) return found;\n cursor = found + marker.length;\n }\n return -1;\n}\n\nfunction findBracketEnd(value: string, from: number, open: string, close: string): number {\n let depth = 1;\n for (let cursor = from; cursor < value.length; cursor += 1) {\n const character = value[cursor];\n if (character === '\\\\') {\n cursor += 1;\n continue;\n }\n if (character === open) depth += 1;\n if (character === close) {\n depth -= 1;\n if (depth === 0) return cursor;\n }\n }\n return -1;\n}\n\nfunction decodeEntities(value: string): string {\n return value.replace(/&(#(?:x[\\da-f]+|\\d+)|[a-z][a-z\\d]+);/giu, (raw, entity: string) => {\n if (entity.startsWith('#x') || entity.startsWith('#X')) {\n const code = Number.parseInt(entity.slice(2), 16);\n return Number.isFinite(code) && code <= 0x10ffff ? String.fromCodePoint(code) : raw;\n }\n if (entity.startsWith('#')) {\n const code = Number.parseInt(entity.slice(1), 10);\n return Number.isFinite(code) && code <= 0x10ffff ? String.fromCodePoint(code) : raw;\n }\n return HTML_ENTITIES[entity.toLowerCase()] ?? raw;\n });\n}\n\nfunction normalizeCodeSpan(value: string): string {\n const normalized = value.replace(/\\r\\n?|\\n/gu, ' ');\n if (\n normalized.length >= 2 &&\n normalized.startsWith(' ') &&\n normalized.endsWith(' ') &&\n !/^ +$/u.test(normalized)\n ) {\n return normalized.slice(1, -1);\n }\n return normalized;\n}\n\nfunction markdownStyle(\n source: string,\n index: number,\n): { marker: string; type: Span['type'] } | undefined {\n const styles = [\n { marker: '**', type: SpanType.Bold },\n { marker: '__', type: SpanType.Bold },\n { marker: '~~', type: SpanType.Strike },\n { marker: '||', type: SpanType.Spoiler },\n { marker: '*', type: SpanType.Italic },\n { marker: '_', type: SpanType.Italic },\n ] as const;\n\n for (const style of styles) {\n if (!source.startsWith(style.marker, index)) continue;\n if (style.marker.includes('_')) {\n const before = source[index - 1] ?? '';\n const after = source[index + style.marker.length] ?? '';\n if (/[\\p{L}\\p{N}]/u.test(before) && /[\\p{L}\\p{N}]/u.test(after)) continue;\n }\n return style;\n }\n return undefined;\n}\n\nfunction parseMarkdownInline(\n source: string,\n output: MutableMarkup,\n protocols: ReadonlySet<string>,\n): void {\n let index = 0;\n while (index < source.length) {\n const character = source[index] ?? '';\n\n if (character === '\\\\' && index + 1 < source.length) {\n append(output, source[index + 1] ?? '');\n index += 2;\n continue;\n }\n\n if (character === '`') {\n const run = source.slice(index).match(/^`+/u)?.[0] ?? '`';\n const close = findClosing(source, run, index + run.length);\n if (close > index + run.length) {\n const start = output.content.length;\n append(output, normalizeCodeSpan(source.slice(index + run.length, close)));\n addSpan(output, start, { type: SpanType.Monospace });\n index = close + run.length;\n continue;\n }\n }\n\n const image = source.startsWith('![', index);\n if ((image || character === '[') && !isEscaped(source, index)) {\n const labelStart = index + (image ? 2 : 1);\n const labelEnd = findBracketEnd(source, labelStart, '[', ']');\n if (labelEnd >= 0 && source[labelEnd + 1] === '(') {\n const destinationEnd = findBracketEnd(source, labelEnd + 2, '(', ')');\n if (destinationEnd >= 0) {\n const label = source.slice(labelStart, labelEnd);\n const rawDestination = source.slice(labelEnd + 2, destinationEnd).trim();\n const destination = rawDestination.replace(/\\\\([\\\\)])/gu, '$1');\n const start = output.content.length;\n parseMarkdownInline(label, output, protocols);\n if (!image) {\n const url = safeLink(destination, protocols);\n if (url) addSpan(output, start, { type: SpanType.Link, url });\n }\n index = destinationEnd + 1;\n continue;\n }\n }\n }\n\n if (source.slice(index, index + 3).toLowerCase() === '<u>') {\n const close = source.toLowerCase().indexOf('</u>', index + 3);\n if (close > index + 3) {\n const start = output.content.length;\n parseMarkdownInline(source.slice(index + 3, close), output, protocols);\n addSpan(output, start, { type: SpanType.Underline });\n index = close + 4;\n continue;\n }\n }\n\n if (character === '<') {\n const close = source.indexOf('>', index + 1);\n if (close > index + 1) {\n const candidate = decodeEntities(source.slice(index + 1, close));\n const url = safeLink(candidate, protocols);\n if (url) {\n const start = output.content.length;\n append(output, candidate);\n addSpan(output, start, { type: SpanType.Link, url });\n index = close + 1;\n continue;\n }\n }\n }\n\n const style = markdownStyle(source, index);\n if (style) {\n const close = findClosing(source, style.marker, index + style.marker.length);\n if (close > index + style.marker.length) {\n const start = output.content.length;\n parseMarkdownInline(source.slice(index + style.marker.length, close), output, protocols);\n addSpan(output, start, { type: style.type });\n index = close + style.marker.length;\n continue;\n }\n }\n\n if (character === '&') {\n const entity = source.slice(index).match(/^&(?:#(?:x[\\da-f]+|\\d+)|[a-z][a-z\\d]+);/iu)?.[0];\n if (entity) {\n append(output, decodeEntities(entity));\n index += entity.length;\n continue;\n }\n }\n\n append(output, character);\n index += 1;\n }\n}\n\nfunction lineEnd(source: string, from: number): number {\n const found = source.indexOf('\\n', from);\n return found < 0 ? source.length : found;\n}\n\n/**\n * Преобразует безопасное подмножество Markdown в текст и wire-spans.\n *\n * Поддерживаются bold, italic, strike, spoiler, inline/fenced code, ссылки, underline\n * через `<u>` и цитаты `>`. Неподдерживаемая и незакрытая разметка остаётся текстом.\n * У изображений сохраняется alt-текст без URL.\n */\nexport function parseMarkdown(source: string, options: ParseMarkupOptions = {}): TextMarkup {\n const output: MutableMarkup = { content: '', spans: [] };\n const protocols = allowedProtocols(options);\n let position = 0;\n\n while (position < source.length) {\n const end = lineEnd(source, position);\n const line = source.slice(position, end);\n const hasNewline = end < source.length;\n const fence = line.match(/^ {0,3}(`{3,}|~{3,})[^\\n]*$/u)?.[1];\n\n if (fence) {\n let closeStart = hasNewline ? end + 1 : source.length;\n let closeEnd = closeStart;\n let found = false;\n while (closeStart < source.length) {\n closeEnd = lineEnd(source, closeStart);\n const closeLine = source.slice(closeStart, closeEnd);\n const marker = closeLine.match(/^ {0,3}(`{3,}|~{3,})\\s*$/u)?.[1];\n if (marker !== undefined && marker[0] === fence[0] && marker.length >= fence.length) {\n found = true;\n break;\n }\n closeStart = closeEnd < source.length ? closeEnd + 1 : source.length;\n }\n\n if (found) {\n const bodyStart = end + 1;\n let body = source.slice(bodyStart, closeStart);\n if (body.endsWith('\\n')) body = body.slice(0, -1);\n const start = output.content.length;\n append(output, body);\n addSpan(output, start, { type: SpanType.Monospace });\n if (closeEnd < source.length) append(output, '\\n');\n position = closeEnd < source.length ? closeEnd + 1 : source.length;\n continue;\n }\n }\n\n const quote = line.match(/^ {0,3}> ?(.*)$/u);\n if (quote) {\n const start = output.content.length;\n parseMarkdownInline(quote[1] ?? '', output, protocols);\n addSpan(output, start, { type: SpanType.Quote });\n } else {\n parseMarkdownInline(line, output, protocols);\n }\n\n if (hasNewline) append(output, '\\n');\n position = hasNewline ? end + 1 : source.length;\n }\n\n return finish(output);\n}\n\nfunction readAttribute(source: string, name: string): string | undefined {\n const pattern = new RegExp(\n `(?:^|\\\\s)${name}\\\\s*=\\\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\\\s\"'=<>\\\\x60]+))`,\n 'iu',\n );\n const match = source.match(pattern);\n const value = match?.[1] ?? match?.[2] ?? match?.[3];\n return value === undefined ? undefined : decodeEntities(value);\n}\n\nfunction htmlSpan(\n tag: string,\n attributes: string,\n protocols: ReadonlySet<string>,\n): Omit<Span, 'offset' | 'length'> | undefined {\n switch (tag) {\n case 'b':\n case 'strong':\n return { type: SpanType.Bold };\n case 'em':\n case 'i':\n return { type: SpanType.Italic };\n case 'u':\n return { type: SpanType.Underline };\n case 'del':\n case 's':\n case 'strike':\n return { type: SpanType.Strike };\n case 'code':\n case 'pre':\n return { type: SpanType.Monospace };\n case 'blockquote':\n return { type: SpanType.Quote };\n case 'a': {\n const href = readAttribute(attributes, 'href');\n const url = href ? safeLink(href, protocols) : null;\n return url ? { type: SpanType.Link, url } : undefined;\n }\n case 'span': {\n const classes = readAttribute(attributes, 'class')?.split(/\\s+/u) ?? [];\n if (\n readAttribute(attributes, 'data-spoiler') === 'true' ||\n classes.some((value) => value.endsWith('-spoiler'))\n ) {\n return { type: SpanType.Spoiler };\n }\n if (classes.some((value) => value.endsWith('-quote'))) {\n return { type: SpanType.Quote };\n }\n return undefined;\n }\n default:\n return undefined;\n }\n}\n\nfunction ensureBlockBreak(output: MutableMarkup): void {\n if (output.content !== '' && !output.content.endsWith('\\n')) append(output, '\\n');\n}\n\nfunction findHtmlTagEnd(source: string, from: number): number {\n let quote: '\"' | \"'\" | undefined;\n\n for (let index = from; index < source.length; index += 1) {\n const character = source[index];\n if (quote) {\n if (character === quote) quote = undefined;\n continue;\n }\n if (character === '\"' || character === \"'\") {\n quote = character;\n continue;\n }\n if (character === '>') return index;\n }\n\n return -1;\n}\n\n/**\n * Преобразует ограниченный HTML в обычный текст и wire-spans.\n *\n * Разрешены только семантические теги форматирования. Атрибуты событий игнорируются,\n * содержимое `script`, `style`, `iframe`, `object`, SVG и подобных активных элементов\n * удаляется. Опасный `href` сохраняет текст ссылки, но не создаёт span.\n */\nexport function parseHtml(source: string, options: ParseMarkupOptions = {}): TextMarkup {\n const output: MutableMarkup = { content: '', spans: [] };\n const protocols = allowedProtocols(options);\n const stack: OpenHtmlSpan[] = [];\n let position = 0;\n\n const closeTag = (tag: string): void => {\n for (let index = stack.length - 1; index >= 0; index -= 1) {\n const open = stack[index];\n if (open?.tag !== tag) continue;\n stack.splice(index, 1);\n addSpan(output, open.start, open.span);\n return;\n }\n };\n\n while (position < source.length) {\n if (source.startsWith('<!--', position)) {\n const end = source.indexOf('-->', position + 4);\n position = end < 0 ? source.length : end + 3;\n continue;\n }\n\n if (source[position] === '<') {\n const end = findHtmlTagEnd(source, position + 1);\n if (end >= 0) {\n const raw = source.slice(position, end + 1);\n const parsed = raw.match(/^<\\s*(\\/?)\\s*([a-z][\\w-]*)([\\s\\S]*?)\\/?\\s*>$/iu);\n if (parsed) {\n const closing = parsed[1] === '/';\n const tag = (parsed[2] ?? '').toLowerCase();\n const attributes = parsed[3] ?? '';\n\n if (!closing && HTML_DROPPED_TAGS.has(tag)) {\n const closingPattern = new RegExp(`<\\\\s*\\\\/\\\\s*${tag}\\\\s*>`, 'igu');\n closingPattern.lastIndex = end + 1;\n const match = closingPattern.exec(source);\n position = match ? match.index + match[0].length : source.length;\n continue;\n }\n\n if (tag === 'br' && !closing) {\n append(output, '\\n');\n position = end + 1;\n continue;\n }\n\n if (tag === 'img' && !closing) {\n append(output, readAttribute(attributes, 'alt') ?? '');\n position = end + 1;\n continue;\n }\n\n if (closing) {\n closeTag(tag);\n if (HTML_BLOCK_TAGS.has(tag)) ensureBlockBreak(output);\n } else {\n if (HTML_BLOCK_TAGS.has(tag)) ensureBlockBreak(output);\n const span = htmlSpan(tag, attributes, protocols);\n if (span) stack.push({ tag, start: output.content.length, span });\n }\n\n position = end + 1;\n continue;\n }\n }\n }\n\n const next = source.indexOf('<', position + 1);\n const end = next < 0 ? source.length : next;\n append(output, decodeEntities(source.slice(position, end)));\n position = end;\n }\n\n while (stack.length > 0) {\n const open = stack.pop();\n if (open) addSpan(output, open.start, open.span);\n }\n\n while (output.content.endsWith('\\n')) output.content = output.content.slice(0, -1);\n return finish(output);\n}\n","import { ItdConfigError } from '../core/errors.js';\nimport { validateSpans } from '../spans/validate.js';\nimport { SpanType } from '../types/enums.js';\nimport type { Span } from '../types/models.js';\nimport { BUILDER, type BuilderInput, type ItdBuilder, resolveInput } from './base.js';\n\n/** Текст вместе с рассчитанной разметкой. */\nexport interface TextMarkup {\n content: string;\n spans: Span[];\n}\n\n/** Описание фрагмента без смещения: его вычисляет {@link MarkupBuilder}. */\nexport type MarkupSpan = Omit<Span, 'offset' | 'length'>;\n\n/** Что принимает метод разметки: результат, билдер или функция-настройщик. */\nexport type MarkupInput = BuilderInput<TextMarkup, MarkupBuilder>;\n\n/**\n * Содержимое форматированного фрагмента.\n *\n * Строка создаёт простой фрагмент. Билдер, готовая разметка или функция позволяют вложить\n * одни spans в другие, как при последовательном форматировании выделения в редакторе сайта.\n */\nexport type MarkupContent = string | MarkupInput;\n\n/** Какие сущности искать в {@link autoSpans}. */\nexport interface AutoSpansOptions {\n /** Находить `#хэштеги`. По умолчанию `true`. */\n hashtags?: boolean;\n /** Находить `@упоминания`. По умолчанию `true`. */\n mentions?: boolean;\n /** Находить абсолютные HTTP(S)-ссылки. По умолчанию `true`. */\n links?: boolean;\n}\n\nfunction validateMarkup(value: TextMarkup): TextMarkup {\n if (!value || typeof value.content !== 'string' || !Array.isArray(value.spans)) {\n throw new ItdConfigError('Разметка должна содержать строку content и массив spans');\n }\n\n return {\n content: value.content,\n spans: validateSpans(value.content, value.spans).sort(\n (left, right) => left.offset - right.offset || right.length - left.length,\n ),\n };\n}\n\nfunction entityName(value: string, prefix: '#' | '@', label: string): string {\n const name = value.startsWith(prefix) ? value.slice(prefix.length) : value;\n if (name === '' || /\\s/u.test(name)) {\n throw new ItdConfigError(`${label} должен быть непустым и не содержать пробелов`);\n }\n return name;\n}\n\n/**\n * Неизменяемый билдер текста с разметкой.\n *\n * Каждый метод дописывает фрагмент и сам считает `offset` и `length` в единицах UTF-16 —\n * именно такие индексы использует JavaScript-редактор сайта.\n */\nexport class MarkupBuilder implements ItdBuilder<TextMarkup> {\n /** @internal */\n readonly [BUILDER] = true as const;\n\n readonly #content: string;\n readonly #spans: Span[];\n\n /** @internal Создавайте билдер функцией {@link markup}. */\n constructor(content: string, spans: Span[]) {\n this.#content = content;\n this.#spans = spans;\n }\n\n /** Дописывает обычный текст без разметки. */\n text(value: string): MarkupBuilder {\n return new MarkupBuilder(this.#content + value, this.#spans);\n }\n\n /** Дописывает переводы строк. */\n newline(count = 1): MarkupBuilder {\n if (!Number.isInteger(count) || count < 0) {\n throw new ItdConfigError('Количество переводов строк должно быть целым неотрицательным');\n }\n return this.text('\\n'.repeat(count));\n }\n\n /** @internal Добавляет готовый фрагмент и сдвигает его spans к текущему концу текста. */\n #formatted(value: MarkupContent, outerSpans: readonly MarkupSpan[]): MarkupBuilder {\n const fragment =\n typeof value === 'string' ? { content: value, spans: [] } : resolveMarkup(value);\n if (fragment.content.length === 0) {\n throw new ItdConfigError('Размечаемый фрагмент не может быть пустым');\n }\n\n const offset = this.#content.length;\n const shifted = fragment.spans.map((span) => ({\n ...span,\n offset: span.offset + offset,\n }));\n const outer = outerSpans.map((span) => ({\n ...span,\n offset,\n length: fragment.content.length,\n }));\n\n return new MarkupBuilder(this.#content + fragment.content, [\n ...this.#spans,\n ...shifted,\n ...outer,\n ]);\n }\n\n /**\n * Дописывает фрагмент с произвольным типом разметки.\n *\n * Вложенный билдер позволяет форматировать часть фрагмента дополнительным стилем.\n * Для нескольких стилей на всём фрагменте используйте {@link styled}.\n */\n span(value: MarkupContent, span: MarkupSpan): MarkupBuilder {\n return this.#formatted(value, [span]);\n }\n\n /**\n * Дописывает фрагмент с несколькими стилями на одном диапазоне.\n *\n * Для `link`, которому нужен `url`, используйте {@link link}; произвольные spans с\n * метаданными можно объединять через вложенные вызовы {@link span}.\n */\n styled(value: MarkupContent, ...types: Span['type'][]): MarkupBuilder {\n if (types.length === 0) return this.#formatted(value, []);\n return this.#formatted(\n value,\n types.map((type) => ({ type })),\n );\n }\n\n /** Дописывает `#хэштег` и сохраняет имя без решётки в `tag`. */\n hashtag(tag: string): MarkupBuilder {\n const name = entityName(tag, '#', 'Хэштег');\n return this.span(`#${name}`, { type: SpanType.Hashtag, tag: name });\n }\n\n /** Дописывает `@username` и сохраняет имя пользователя в `username`. */\n mention(username: string): MarkupBuilder {\n const name = entityName(username, '@', 'Упоминание');\n return this.span(`@${name}`, { type: SpanType.Mention, username: name });\n }\n\n /**\n * Дописывает ссылку.\n *\n * Для строки адрес по умолчанию становится и текстом ссылки. Вложенному форматированному\n * фрагменту URL нужно передать явно.\n */\n link(content: MarkupContent, url?: string): MarkupBuilder {\n const resolvedUrl = url ?? (typeof content === 'string' ? content : undefined);\n if (resolvedUrl === undefined) {\n throw new ItdConfigError('Для форматированного текста ссылки нужно явно передать URL');\n }\n return this.span(content, { type: SpanType.Link, url: resolvedUrl });\n }\n\n bold(content: MarkupContent): MarkupBuilder {\n return this.span(content, { type: SpanType.Bold });\n }\n\n italic(content: MarkupContent): MarkupBuilder {\n return this.span(content, { type: SpanType.Italic });\n }\n\n underline(content: MarkupContent): MarkupBuilder {\n return this.span(content, { type: SpanType.Underline });\n }\n\n strike(content: MarkupContent): MarkupBuilder {\n return this.span(content, { type: SpanType.Strike });\n }\n\n spoiler(content: MarkupContent): MarkupBuilder {\n return this.span(content, { type: SpanType.Spoiler });\n }\n\n monospace(content: MarkupContent): MarkupBuilder {\n return this.span(content, { type: SpanType.Monospace });\n }\n\n quote(content: MarkupContent): MarkupBuilder {\n return this.span(content, { type: SpanType.Quote });\n }\n\n build(): TextMarkup {\n return validateMarkup({ content: this.#content, spans: this.#spans });\n }\n\n toJSON(): TextMarkup {\n return this.build();\n }\n}\n\n/** Начинает сборку текста с автоматически вычисляемыми смещениями. */\nexport function markup(content = ''): MarkupBuilder {\n return new MarkupBuilder(content, []);\n}\n\n/** Приводит любую форму разметки к тексту и массиву spans. */\nexport function resolveMarkup(input: MarkupInput): TextMarkup {\n return resolveInput(input, () => markup(), validateMarkup);\n}\n\nfunction trimUrlEnd(value: string): string {\n let result = value;\n const pairs = [\n ['(', ')'],\n ['[', ']'],\n ['{', '}'],\n ] as const;\n\n let changed: boolean;\n do {\n const before = result;\n while (/[.,!?;:…'\"»”]$/u.test(result)) result = result.slice(0, -1);\n\n for (const [open, close] of pairs) {\n while (result.endsWith(close) && result.split(close).length > result.split(open).length) {\n result = result.slice(0, -1);\n }\n }\n changed = result !== before;\n } while (changed);\n\n return result;\n}\n\nfunction overlaps(span: Span, start: number, end: number): boolean {\n return span.offset < end && start < span.offset + span.length;\n}\n\nfunction isHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return (url.protocol === 'http:' || url.protocol === 'https:') && url.hostname !== '';\n } catch {\n return false;\n }\n}\n\nfunction previousCodePoint(text: string, index: number): string {\n if (index <= 0) return '';\n const last = text.charCodeAt(index - 1);\n if (last >= 0xdc00 && last <= 0xdfff && index >= 2) {\n const first = text.charCodeAt(index - 2);\n if (first >= 0xd800 && first <= 0xdbff) return text.slice(index - 2, index);\n }\n return text[index - 1] ?? '';\n}\n\n/**\n * Находит в тексте те сущности, которые сайт получает после серверного разбора:\n * HTTP(S)-ссылки, `#хэштеги` и `@упоминания`.\n *\n * Смещения выражены в UTF-16 code units, поэтому совпадают с `String#slice`,\n * `substring`, DOM Selection и wire-форматом сайта даже при наличии эмодзи.\n */\nexport function autoSpans(text: string, options: AutoSpansOptions = {}): Span[] {\n const includeLinks = options.links ?? true;\n const includeHashtags = options.hashtags ?? true;\n const includeMentions = options.mentions ?? true;\n const spans: Span[] = [];\n const links: Span[] = [];\n\n const urlPattern = /https?:\\/\\/[^\\s<>\"'`]+/giu;\n for (const match of text.matchAll(urlPattern)) {\n const raw = trimUrlEnd(match[0]);\n if (raw.length === 0 || match.index === undefined || !isHttpUrl(raw)) continue;\n links.push({\n type: SpanType.Link,\n offset: match.index,\n length: raw.length,\n url: raw,\n });\n }\n if (includeLinks) spans.push(...links);\n\n if (includeHashtags || includeMentions) {\n const entityPattern = /#([\\p{L}\\p{M}\\p{N}_]+)|@([a-z\\d][a-z\\d._-]*)/giu;\n\n for (const match of text.matchAll(entityPattern)) {\n if (match.index === undefined) continue;\n const offset = match.index;\n const previous = previousCodePoint(text, offset);\n if (/[\\p{L}\\p{M}\\p{N}_#@.]/u.test(previous)) continue;\n\n const end = offset + match[0].length;\n if (links.some((span) => overlaps(span, offset, end))) continue;\n\n const hashtag = match[1];\n const username = match[2]?.replace(/[._-]+$/u, '');\n if (hashtag !== undefined && includeHashtags) {\n spans.push({\n type: SpanType.Hashtag,\n offset,\n length: match[0].length,\n tag: hashtag,\n });\n } else if (username !== undefined && includeMentions) {\n spans.push({\n type: SpanType.Mention,\n offset,\n length: username.length + 1,\n username,\n });\n }\n }\n }\n\n return spans.sort((left, right) => left.offset - right.offset || right.length - left.length);\n}\n","import { ItdConfigError } from '../core/errors.js';\nimport type { CreatePollInput } from '../types/params.js';\nimport { BUILDER, type BuilderInput, type ItdBuilder, resolveInput } from './base.js';\n\n/** Минимальное число вариантов ответа. */\nconst MIN_OPTIONS = 2;\n\n/** Ограничения опроса — те же, что действуют в интерфейсе итд.com. */\nconst MAX_OPTIONS = 10;\nconst MAX_QUESTION_LENGTH = 200;\nconst MAX_OPTION_LENGTH = 100;\n\n/**\n * Проверяет данные опроса.\n *\n * Применяется и к билдеру, и к обычному объекту — правила одни и те же.\n *\n * @throws {ItdConfigError} если вопрос пуст, вариантов меньше двух или есть дубли\n */\nexport function validatePoll(input: CreatePollInput): CreatePollInput {\n const question = input?.question;\n\n if (typeof question !== 'string' || question.trim() === '') {\n throw new ItdConfigError('Опрос требует непустого вопроса');\n }\n\n if (question.trim().length > MAX_QUESTION_LENGTH) {\n throw new ItdConfigError(\n `Вопрос длиннее ${MAX_QUESTION_LENGTH} символов (передано ${question.trim().length})`,\n );\n }\n\n const options = Array.isArray(input.options) ? input.options : [];\n\n const texts = options.map((option, index) => {\n const text = typeof option?.text === 'string' ? option.text.trim() : '';\n if (text === '') {\n throw new ItdConfigError(`Вариант ответа №${index + 1} пуст — у каждого должен быть текст`);\n }\n if (text.length > MAX_OPTION_LENGTH) {\n throw new ItdConfigError(\n `Вариант ответа №${index + 1} длиннее ${MAX_OPTION_LENGTH} символов (передано ${text.length})`,\n );\n }\n return text;\n });\n\n if (texts.length < MIN_OPTIONS) {\n throw new ItdConfigError(\n `Опрос требует минимум ${MIN_OPTIONS} варианта, передано: ${texts.length}`,\n );\n }\n\n if (texts.length > MAX_OPTIONS) {\n throw new ItdConfigError(\n `Опрос допускает не больше ${MAX_OPTIONS} вариантов, передано: ${texts.length}`,\n );\n }\n\n const seen = new Set<string>();\n for (const text of texts) {\n if (seen.has(text)) {\n throw new ItdConfigError(`Вариант «${text}» повторяется — варианты должны различаться`);\n }\n seen.add(text);\n }\n\n return {\n question: question.trim(),\n options: texts.map((text) => ({ text })),\n // Поле обязательно: без него сервер отвергает создание поста с опросом,\n // даже когда выбор одиночный.\n multipleChoice: input.multipleChoice ?? false,\n };\n}\n\n/**\n * Билдер опроса.\n *\n * Неизменяемый: каждый вызов возвращает новый экземпляр, поэтому заготовку можно\n * переиспользовать, не боясь её испортить. Создаётся функцией {@link poll}.\n */\nexport class PollBuilder implements ItdBuilder<CreatePollInput> {\n /** @internal */\n readonly [BUILDER] = true as const;\n\n readonly #state: CreatePollInput;\n\n /** @internal Создавайте билдер функцией {@link poll}. */\n constructor(state: CreatePollInput) {\n this.#state = state;\n }\n\n /** Задаёт вопрос. */\n question(text: string): PollBuilder {\n return new PollBuilder({ ...this.#state, question: text });\n }\n\n /** Добавляет один вариант ответа. */\n option(text: string): PollBuilder {\n return new PollBuilder({ ...this.#state, options: [...this.#state.options, { text }] });\n }\n\n /**\n * Добавляет несколько вариантов сразу.\n *\n * @example\n * ```ts\n * poll('ну как?').options('да', 'нет', 'не знаю');\n * ```\n */\n options(...texts: string[]): PollBuilder {\n return new PollBuilder({\n ...this.#state,\n options: [...this.#state.options, ...texts.map((text) => ({ text }))],\n });\n }\n\n /** Разрешает выбор нескольких вариантов. */\n multipleChoice(enabled = true): PollBuilder {\n return new PollBuilder({ ...this.#state, multipleChoice: enabled });\n }\n\n build(): CreatePollInput {\n return validatePoll(this.#state);\n }\n\n toJSON(): CreatePollInput {\n return this.build();\n }\n}\n\n/**\n * Начинает сборку опроса.\n *\n * @param question вопрос; можно задать позже методом {@link PollBuilder.question}\n *\n * @example\n * ```ts\n * import { poll } from 'itd-api';\n *\n * const q = poll('Какой язык лучше?')\n * .options('TypeScript', 'JavaScript')\n * .multipleChoice();\n *\n * await itd.posts.create({ content: 'голосуем', poll: q });\n * ```\n */\nexport function poll(question = ''): PollBuilder {\n return new PollBuilder({ question, options: [] });\n}\n\n/** Что принимает параметр опроса: объект, билдер или функция-настройщик. */\nexport type PollInput = BuilderInput<CreatePollInput, PollBuilder>;\n\n/** Приводит любую форму входа к готовым данным опроса. */\nexport function resolvePoll(input: PollInput): CreatePollInput {\n return resolveInput(input, () => poll(), validatePoll);\n}\n","import type { FileInput } from '../core/attachments.js';\nimport { ItdConfigError } from '../core/errors.js';\nimport { type ParseMarkupOptions, parseHtml, parseMarkdown } from '../spans/parse.js';\nimport { validateSpans } from '../spans/validate.js';\nimport { SpanType } from '../types/enums.js';\nimport type { Span, UserId } from '../types/models.js';\nimport type { CreatePostInput, UpdatePostInput } from '../types/params.js';\nimport { BUILDER, type BuilderInput, type ItdBuilder, isBuilder, resolveInput } from './base.js';\nimport { type AutoSpansOptions, autoSpans, type MarkupInput, resolveMarkup } from './markup.js';\nimport { type PollInput, resolvePoll } from './poll.js';\n\n/** UUID любой версии. */\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst BUILD_UPDATE = Symbol.for('itd.postBuilder.update');\n\n/**\n * Проверяет данные поста.\n *\n * Отдельного внимания заслуживает `wallRecipientId`: API принимает там **только UUID**,\n * а имя пользователя молча приводит к ошибке на сервере. Проверка здесь превращает\n * это в понятное сообщение до отправки запроса.\n *\n * @throws {ItdConfigError} если пост пуст или получатель стены задан именем пользователя\n */\nexport function validatePost(input: CreatePostInput): CreatePostInput {\n const content = typeof input?.content === 'string' ? input.content : '';\n const attachmentIds = input?.attachmentIds ?? [];\n const files = input?.files ?? [];\n\n const hasContent = content.trim() !== '';\n const hasAttachments = attachmentIds.length > 0 || files.length > 0;\n const hasPoll = Boolean(input?.poll);\n\n if (!hasContent && !hasAttachments && !hasPoll) {\n throw new ItdConfigError('Пост пуст: нужен текст, вложение или опрос');\n }\n\n const wallRecipientId = input.wallRecipientId;\n if (wallRecipientId !== undefined && wallRecipientId !== null) {\n if (!UUID_PATTERN.test(wallRecipientId)) {\n throw new ItdConfigError(\n `wallRecipientId должен быть UUID, а не именем пользователя (получено: «${wallRecipientId}»). ` +\n 'Идентификатор можно взять из профиля: (await itd.users.get(username)).id',\n );\n }\n }\n\n // Опрос внутри обычного объекта тоже может быть билдером или функцией — приводим его\n // здесь, чтобы форма записи не влияла на результат и на проверки.\n return {\n ...input,\n ...(input.spans !== undefined ? { spans: validateSpans(content, input.spans) } : {}),\n ...(input.poll !== undefined ? { poll: resolvePoll(input.poll) } : {}),\n };\n}\n\n/** Внутреннее состояние {@link PostBuilder}. */\ninterface PostState extends CreatePostInput {\n content: string;\n contentSet: boolean;\n attachmentIds: string[];\n files: FileInput[];\n}\n\n/**\n * Билдер поста.\n *\n * Неизменяемый: каждый вызов возвращает новый экземпляр, поэтому заготовку можно\n * переиспользовать. Создаётся функцией {@link post}.\n *\n * @example Заготовка для нескольких постов\n * ```ts\n * const onWall = post().onWall(userId);\n *\n * await itd.posts.create(onWall.content('первый'));\n * await itd.posts.create(onWall.content('второй')); // заготовка не испорчена\n * ```\n */\nexport class PostBuilder implements ItdBuilder<CreatePostInput> {\n /** @internal */\n readonly [BUILDER] = true as const;\n\n readonly #state: PostState;\n\n /** @internal Создавайте билдер функцией {@link post}. */\n constructor(state: PostState) {\n this.#state = state;\n }\n\n /**\n * Задаёт текст поста, заменяя прежний вместе с его разметкой.\n *\n * Spans привязаны к конкретному тексту, поэтому после замены их нужно задать заново\n * через {@link spans}, {@link markup} или {@link autoSpans}.\n */\n content(text: string): PostBuilder {\n const state: PostState = { ...this.#state, content: text, contentSet: true };\n delete state.spans;\n return new PostBuilder(state);\n }\n\n /** Дописывает текст к уже заданному. */\n append(text: string): PostBuilder {\n const separator = this.#state.content === '' ? '' : '\\n';\n return new PostBuilder({\n ...this.#state,\n content: this.#state.content + separator + text,\n contentSet: true,\n });\n }\n\n /**\n * Задаёт готовую разметку текста. Смещения проверяются при {@link build}.\n *\n * Для автоматического поиска сущностей есть {@link autoSpans}, а для вычисления смещений\n * при сборке текста — {@link markup}.\n */\n spans(spans: Span[]): PostBuilder {\n return new PostBuilder({ ...this.#state, spans });\n }\n\n /**\n * Заменяет текст и разметку результатом {@link MarkupBuilder}.\n *\n * @example\n * ```ts\n * post().markup((m) => m.text('смотрите ').hashtag('котики').text(' от ').mention('nowkie'));\n * ```\n */\n markup(input: MarkupInput): PostBuilder {\n const result = resolveMarkup(input);\n return new PostBuilder({\n ...this.#state,\n content: result.content,\n contentSet: true,\n spans: result.spans,\n });\n }\n\n /** Заменяет текст и spans результатом безопасного разбора Markdown. */\n markdown(source: string, options: ParseMarkupOptions = {}): PostBuilder {\n return this.markup(parseMarkdown(source, options));\n }\n\n /** Заменяет текст и spans результатом безопасного разбора ограниченного HTML. */\n html(source: string, options: ParseMarkupOptions = {}): PostBuilder {\n return this.markup(parseHtml(source, options));\n }\n\n /**\n * Находит HTTP(S)-ссылки, хэштеги и упоминания в уже заданном тексте.\n *\n * Ручные стили сохраняются. Повторный вызов не дублирует уже найденные сущности.\n */\n autoSpans(options: AutoSpansOptions = {}): PostBuilder {\n const current = this.#state.spans ?? [];\n const entityTypes = new Set<string>([SpanType.Hashtag, SpanType.Mention, SpanType.Link]);\n const detected = autoSpans(this.#state.content, options).filter((candidate) => {\n return !current.some(\n (span) =>\n entityTypes.has(span.type) &&\n span.offset < candidate.offset + candidate.length &&\n candidate.offset < span.offset + span.length,\n );\n });\n\n return new PostBuilder({\n ...this.#state,\n spans: [...current, ...detected].sort(\n (left, right) => left.offset - right.offset || right.length - left.length,\n ),\n });\n }\n\n /**\n * Публикует пост на стене другого пользователя.\n *\n * @param userId **UUID** пользователя; имя пользователя не подойдёт\n */\n onWall(userId: UserId): PostBuilder {\n return new PostBuilder({ ...this.#state, wallRecipientId: userId });\n }\n\n /**\n * Прикладывает файл — он будет загружен перед публикацией.\n *\n * Порядок вызовов сохраняется в порядке вложений.\n */\n attach(file: FileInput): PostBuilder {\n return new PostBuilder({ ...this.#state, files: [...this.#state.files, file] });\n }\n\n /** Прикладывает уже загруженное вложение по его идентификатору. */\n attachId(attachmentId: string): PostBuilder {\n return new PostBuilder({\n ...this.#state,\n attachmentIds: [...this.#state.attachmentIds, attachmentId],\n });\n }\n\n /**\n * Добавляет опрос.\n *\n * Принимает объект, {@link PollBuilder} или функцию-настройщик.\n *\n * @example\n * ```ts\n * post('голосуем').poll((q) => q.question('ну как?').options('да', 'нет'));\n * ```\n */\n poll(input: PollInput): PostBuilder {\n return new PostBuilder({ ...this.#state, poll: resolvePoll(input) });\n }\n\n #input(includeEmptyContent = false): CreatePostInput {\n const { content, contentSet: _contentSet, attachmentIds, files, ...rest } = this.#state;\n return {\n ...rest,\n ...(content !== '' || includeEmptyContent ? { content } : {}),\n ...(attachmentIds.length > 0 ? { attachmentIds } : {}),\n ...(files.length > 0 ? { files } : {}),\n };\n }\n\n build(): CreatePostInput {\n return validatePost(this.#input());\n }\n\n /** @internal Собирает данные по правилам `posts.update`, не применяя правила создания. */\n [BUILD_UPDATE](): UpdatePostInput {\n return validatePostUpdate(this.#input(this.#state.contentSet));\n }\n\n toJSON(): CreatePostInput {\n return this.build();\n }\n}\n\n/**\n * Начинает сборку поста.\n *\n * @param content текст; можно задать позже методом {@link PostBuilder.content}\n *\n * @example\n * ```ts\n * import { post } from 'itd-api';\n *\n * await itd.posts.create(\n * post('смотрите что нашёл')\n * .attach({ url: 'https://example.com/photo.jpg' })\n * .poll((q) => q.question('нравится?').options('да', 'нет')),\n * );\n * ```\n */\nexport function post(content?: string): PostBuilder {\n return new PostBuilder({\n content: content ?? '',\n contentSet: content !== undefined,\n attachmentIds: [],\n files: [],\n });\n}\n\n/** Что принимает параметр поста: объект, билдер или функция-настройщик. */\nexport type PostInput = BuilderInput<CreatePostInput, PostBuilder>;\n\n/** Приводит любую форму входа к готовым данным поста. */\nexport function resolvePost(input: PostInput): CreatePostInput {\n return resolveInput(input, () => post(), validatePost);\n}\n\n/** Что принимает `posts.update`: объект, билдер поста или функция-настройщик. */\nexport type PostUpdateInput =\n | UpdatePostInput\n | PostBuilder\n | ((builder: PostBuilder) => PostBuilder | UpdatePostInput);\n\nfunction validatePostUpdate(input: CreatePostInput | UpdatePostInput): UpdatePostInput {\n if (!input || typeof input !== 'object') {\n throw new ItdConfigError('Для обновления поста нужен объект с явно заданным content');\n }\n\n const unsupported = ['wallRecipientId', 'attachmentIds', 'files', 'poll'].filter(\n (key) => (input as CreatePostInput)[key as keyof CreatePostInput] !== undefined,\n );\n if (unsupported.length > 0) {\n throw new ItdConfigError(\n `posts.update изменяет только content и spans; не поддерживаются: ${unsupported.join(', ')}`,\n );\n }\n\n if (input.content === undefined) {\n throw new ItdConfigError(\n 'posts.update требует явно заданный content; обновление одних spans могло бы стереть текст поста',\n );\n }\n if (typeof input.content !== 'string') {\n throw new ItdConfigError('content обновляемого поста должен быть строкой');\n }\n if (input.spans !== undefined && !Array.isArray(input.spans)) {\n throw new ItdConfigError('spans обновляемого поста должен быть массивом');\n }\n\n return {\n content: input.content,\n ...(input.spans !== undefined ? { spans: validateSpans(input.content, input.spans) } : {}),\n };\n}\n\n/** Приводит вход `posts.update` к поддерживаемым данным поста. */\nexport function resolvePostUpdate(input: PostUpdateInput): UpdatePostInput {\n if (typeof input === 'function') {\n const result = input(post());\n return isBuilder<CreatePostInput>(result)\n ? (result as PostBuilder)[BUILD_UPDATE]()\n : validatePostUpdate(result);\n }\n\n return isBuilder<CreatePostInput>(input)\n ? (input as PostBuilder)[BUILD_UPDATE]()\n : validatePostUpdate(input);\n}\n","import { type CommentInput, resolveComment } from '../builders/comment.js';\nimport {\n type PostInput,\n type PostUpdateInput,\n resolvePost,\n resolvePostUpdate,\n} from '../builders/post.js';\nimport type { FileInput } from '../core/attachments.js';\nimport type { HttpClient } from '../core/http.js';\nimport {\n type Page,\n PaginationMode,\n type Paginator,\n readCursorPage,\n readFlatCursorPage,\n} from '../core/pagination.js';\nimport { pickArray } from '../core/unwrap.js';\nimport { encodePathSegment } from '../core/url.js';\nimport type { CommentSort, FeedTab } from '../types/enums.js';\nimport type {\n Comment,\n LikeResult,\n PinPostResult,\n Poll,\n Post,\n PostStats,\n UserRef,\n} from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport type { CreateCommentInput, CreatePostInput } from '../types/params.js';\nimport { BaseResource } from './base.js';\n\n/** Курсорная позиция из параметров: если курсор задан — с него, иначе с начала. */\nfunction cursorStart(params: { cursor?: string | undefined }): { cursor?: string } {\n return params.cursor ? { cursor: params.cursor } : {};\n}\n\n/** Параметры запроса ленты. */\nexport interface FeedParams extends RequestOptions {\n /** Вкладка ленты. По умолчанию сервер отдаёт популярное. */\n tab?: FeedTab;\n /** Сколько постов на страницу. */\n limit?: number;\n /**\n * Курсор следующей страницы из предыдущего ответа.\n *\n * Передавайте значение как есть: его формат зависит от вкладки и может измениться.\n */\n cursor?: string;\n /** Ограничение числа страниц при переборе. */\n maxPages?: number;\n}\n\n/** Параметры запроса постов пользователя. */\nexport interface UserPostsParams extends RequestOptions {\n limit?: number;\n cursor?: string;\n /** Порядок сортировки. */\n sort?: string;\n /** Закреплённый пост, чтобы сервер поднял его наверх. */\n pinnedPostId?: string;\n maxPages?: number;\n}\n\n/** Параметры запроса комментариев к посту. */\nexport interface CommentsParams extends RequestOptions {\n limit?: number;\n /**\n * Курсор следующей страницы: идентификатор последнего полученного комментария.\n *\n * Передавайте значение из `nextCursor` предыдущего ответа как есть.\n */\n cursor?: string;\n sort?: CommentSort;\n maxPages?: number;\n}\n\n/**\n * Посты: лента, публикация, реакции, репосты, комментарии.\n *\n * Доступна как `itd.posts`.\n */\nexport class PostsResource extends BaseResource {\n readonly #uploadFiles: (files: FileInput[], options?: RequestOptions) => Promise<string[]>;\n\n /** Лента: `/api/posts`, курсорная пагинация. */\n readonly #feed = this.paginated<Post, FeedParams>({\n path: () => '/api/posts',\n query: (p) => ({ tab: p.tab, limit: p.limit }),\n start: cursorStart,\n read: (body) => readCursorPage<Post>(body, 'posts'),\n mode: PaginationMode.Cursor,\n });\n\n /** Стена пользователя: `/api/posts/user/{user}`. */\n readonly #wall = this.paginated<Post, UserPostsParams & { user: UserRef }>({\n path: (p) => `/api/posts/user/${encodePathSegment(p.user, 'user')}`,\n query: (p) => ({ limit: p.limit, sort: p.sort, pinnedPostId: p.pinnedPostId }),\n start: cursorStart,\n read: (body) => readCursorPage<Post>(body, 'posts'),\n mode: PaginationMode.Cursor,\n });\n\n /** Понравившиеся посты пользователя: `/api/posts/user/{user}/liked`. */\n readonly #liked = this.paginated<Post, UserPostsParams & { user: UserRef }>({\n path: (p) => `/api/posts/user/${encodePathSegment(p.user, 'user')}/liked`,\n query: (p) => ({ limit: p.limit }),\n start: cursorStart,\n read: (body) => readCursorPage<Post>(body, 'posts'),\n mode: PaginationMode.Cursor,\n });\n\n /** Комментарии к посту: курсор лежит рядом со списком, поэтому свой reader. */\n readonly #comments = this.paginated<Comment, CommentsParams & { postId: string }>({\n path: (p) => `/api/posts/${encodePathSegment(p.postId, 'postId')}/comments`,\n query: (p) => ({ limit: p.limit, sort: p.sort }),\n start: cursorStart,\n read: (body) => readFlatCursorPage<Comment>(body, 'comments'),\n mode: PaginationMode.Cursor,\n });\n\n constructor(\n http: HttpClient,\n deps: { uploadFiles: (files: FileInput[], options?: RequestOptions) => Promise<string[]> },\n ) {\n super(http);\n this.#uploadFiles = deps.uploadFiles;\n }\n\n /**\n * Загружает страницу ленты.\n *\n * @example\n * ```ts\n * const page = await itd.posts.list({ tab: FeedTab.Following, limit: 20 });\n * const next = await itd.posts.list({ tab: FeedTab.Following, cursor: page.nextCursor ?? undefined });\n * ```\n */\n list(params: FeedParams = {}): Promise<Page<Post>> {\n return this.#feed.list(params);\n }\n\n /**\n * Перебирает ленту, сама подставляя курсоры.\n *\n * @example\n * ```ts\n * for await (const post of itd.posts.iterate({ tab: 'following' })) {\n * console.log(post.author.username, post.content);\n * }\n * ```\n */\n iterate(params: FeedParams = {}): Paginator<Post> {\n return this.#feed.iterate(params);\n }\n\n /**\n * Публикует пост.\n *\n * Принимает обычный объект, {@link PostBuilder} или функцию-настройщик. Файлы из поля\n * `files` загружаются автоматически, порядок вложений сохраняется.\n *\n * @example\n * ```ts\n * await itd.posts.create({ content: 'привет' });\n * await itd.posts.create((p) => p.content('привет').attach({ url: 'https://example.com/photo.jpg' }));\n * ```\n */\n async create(input: PostInput, options: RequestOptions = {}): Promise<Post> {\n const data = resolvePost(input);\n const attachmentIds = await this.#collectAttachments(data, options);\n\n return this.http.request<Post>({\n method: 'POST',\n path: '/api/posts',\n body: {\n content: data.content ?? '',\n ...(data.spans ? { spans: data.spans } : {}),\n ...(data.wallRecipientId ? { wallRecipientId: data.wallRecipientId } : {}),\n ...(attachmentIds.length > 0 ? { attachmentIds } : {}),\n ...(data.poll ? { poll: data.poll } : {}),\n },\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Загружает один пост вместе с топовыми комментариями.\n *\n * В отличие от списков, здесь у поста заполнено поле `comments`.\n */\n get(postId: string, options: RequestOptions = {}): Promise<Post> {\n return this.http.request<Post>({\n method: 'GET',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}`,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Редактирует текст и разметку поста.\n *\n * Как и {@link create}, принимает объект, готовый {@link PostBuilder} или\n * функцию-настройщик. Поля создания поста, которые update endpoint не поддерживает\n * (вложения, опрос и стена), отвергаются до запроса.\n */\n update(postId: string, input: PostUpdateInput, options: RequestOptions = {}): Promise<Post> {\n const data = resolvePostUpdate(input);\n return this.http.request<Post>({\n method: 'PUT',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}`,\n body: { content: data.content, ...(data.spans ? { spans: data.spans } : {}) },\n ...this.requestOptions(options),\n });\n }\n\n /** Удаляет пост. Восстановить его можно через {@link restore}. */\n remove(postId: string, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}`,\n ...this.requestOptions(options),\n });\n }\n\n /** Восстанавливает удалённый пост. */\n restore(postId: string, options: RequestOptions = {}): Promise<Post> {\n return this.http.request<Post>({\n method: 'POST',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/restore`,\n ...this.requestOptions(options),\n });\n }\n\n /** Ставит реакцию на пост. */\n like(postId: string, options: RequestOptions = {}): Promise<LikeResult> {\n return this.http.request<LikeResult>({\n method: 'POST',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/like`,\n ...this.requestOptions(options),\n });\n }\n\n /** Убирает реакцию с поста. */\n unlike(postId: string, options: RequestOptions = {}): Promise<LikeResult> {\n return this.http.request<LikeResult>({\n method: 'DELETE',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/like`,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Делает репост с необязательным комментарием.\n *\n * Вложения к репосту не поддерживаются: сервер их игнорирует, поэтому параметров\n * для файлов здесь нет.\n */\n repost(postId: string, content = '', options: RequestOptions = {}): Promise<Post> {\n return this.http.request<Post>({\n method: 'POST',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/repost`,\n body: { content },\n ...this.requestOptions(options),\n });\n }\n\n /** Отменяет репост. */\n unrepost(postId: string, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/repost`,\n ...this.requestOptions(options),\n });\n }\n\n /** Закрепляет пост в профиле. */\n pin(postId: string, options: RequestOptions = {}): Promise<PinPostResult> {\n return this.http.request<PinPostResult>({\n method: 'POST',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/pin`,\n ...this.requestOptions(options),\n });\n }\n\n /** Открепляет пост. */\n unpin(postId: string, options: RequestOptions = {}): Promise<PinPostResult> {\n return this.http.request<PinPostResult>({\n method: 'DELETE',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/pin`,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Голосует в опросе.\n *\n * @param optionIds выбранные варианты; несколько допустимы только при `multipleChoice`\n */\n vote(postId: string, optionIds: string[], options: RequestOptions = {}): Promise<Poll> {\n return this.http.request<Poll>({\n method: 'POST',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/poll/vote`,\n body: { optionIds },\n ...this.requestOptions(options),\n });\n }\n\n /** Запрашивает счётчики сразу для нескольких постов. */\n async stats(ids: string[], options: RequestOptions = {}): Promise<PostStats[]> {\n const body = await this.http.request({\n method: 'POST',\n path: '/api/posts/stats',\n body: { ids },\n ...this.requestOptions(options),\n });\n\n return pickArray<PostStats>(body, 'posts');\n }\n\n /**\n * Загружает страницу стены пользователя.\n *\n * Это **не только его собственные посты**: сюда попадают и записи, которые другие\n * оставили на его стене — у них `author` чужой, а `wallRecipient` указывает на владельца\n * стены. Поэтому число записей обычно больше, чем `postsCount` из профиля; чтобы\n * получить только авторские посты, отфильтруйте по `post.author.id`.\n *\n * Принимает и UUID, и имя пользователя.\n */\n byUser(user: UserRef, params: UserPostsParams = {}): Promise<Page<Post>> {\n return this.#wall.list({ ...params, user });\n }\n\n /** Перебирает стену пользователя. Что именно в неё входит — см. {@link byUser}. */\n iterateByUser(user: UserRef, params: UserPostsParams = {}): Paginator<Post> {\n return this.#wall.iterate({ ...params, user });\n }\n\n /** Загружает страницу постов, которые пользователь отметил реакцией. */\n likedByUser(user: UserRef, params: UserPostsParams = {}): Promise<Page<Post>> {\n return this.#liked.list({ ...params, user });\n }\n\n /** Перебирает посты, которые пользователь отметил реакцией. */\n iterateLikedByUser(user: UserRef, params: UserPostsParams = {}): Paginator<Post> {\n return this.#liked.iterate({ ...params, user });\n }\n\n /**\n * Загружает страницу комментариев к посту.\n *\n * У этого эндпоинта курсор и признак продолжения лежат рядом со списком, а не внутри\n * объекта `pagination`, как у остальных, — разница скрыта внутри.\n */\n comments(postId: string, params: CommentsParams = {}): Promise<Page<Comment>> {\n return this.#comments.list({ ...params, postId });\n }\n\n /** Перебирает комментарии к посту. */\n iterateComments(postId: string, params: CommentsParams = {}): Paginator<Comment> {\n return this.#comments.iterate({ ...params, postId });\n }\n\n /**\n * Комментирует пост.\n *\n * @example\n * ```ts\n * await itd.posts.comment(postId, 'согласен');\n * await itd.posts.comment(postId, (c) => c.content('смотри').attach(blob));\n * ```\n */\n async comment(\n postId: string,\n input: CommentInput | string,\n options: RequestOptions = {},\n ): Promise<Comment> {\n const data = resolveComment(typeof input === 'string' ? { content: input } : input);\n const attachmentIds = await this.#collectAttachments(data, options);\n\n return this.http.request<Comment>({\n method: 'POST',\n path: `/api/posts/${encodePathSegment(postId, 'postId')}/comments`,\n body: { content: data.content ?? '', attachmentIds },\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Отправляет голосовой комментарий.\n *\n * Текста у такого комментария нет: сервер ждёт пустой `content` и одно аудиовложение\n * в формате `audio/ogg`.\n *\n * @example\n * ```ts\n * import { fromPath } from 'itd-api/node';\n *\n * await itd.posts.voiceComment(postId, fromPath('./answer.ogg'));\n * ```\n */\n voiceComment(postId: string, audio: FileInput, options: RequestOptions = {}): Promise<Comment> {\n return this.comment(postId, { content: '', files: [audio] }, options);\n }\n\n /** Загружает файлы из входных данных и объединяет их с уже готовыми идентификаторами. */\n async #collectAttachments(\n data: CreatePostInput | CreateCommentInput,\n options: RequestOptions,\n ): Promise<string[]> {\n const existing = data.attachmentIds ?? [];\n const files = data.files ?? [];\n\n if (files.length === 0) return existing;\n\n const uploaded = await this.#uploadFiles(files, options);\n return [...existing, ...uploaded];\n }\n}\n","import { ItdConfigError } from '../core/errors.js';\nimport { ReportReason, ReportTargetType } from '../types/enums.js';\nimport type { CreateReportInput } from '../types/params.js';\nimport { BUILDER, type BuilderInput, type ItdBuilder, resolveInput } from './base.js';\n\nconst REASONS = new Set<string>(Object.values(ReportReason));\nconst TARGET_TYPES = new Set<string>(Object.values(ReportTargetType));\n\n/**\n * Проверяет данные жалобы.\n *\n * @throws {ItdConfigError} если объект жалобы или причина заданы неверно\n */\nexport function validateReport(input: CreateReportInput): CreateReportInput {\n if (!input?.targetId || typeof input.targetId !== 'string') {\n throw new ItdConfigError('Жалоба требует идентификатор объекта (targetId)');\n }\n\n if (!TARGET_TYPES.has(input.targetType)) {\n throw new ItdConfigError(\n `targetType должен быть одним из ${[...TARGET_TYPES].join(', ')}, ` +\n `получено: ${String(input.targetType)}`,\n );\n }\n\n if (!REASONS.has(input.reason)) {\n throw new ItdConfigError(\n `Неизвестная причина жалобы «${String(input.reason)}». Допустимые: ${[...REASONS].join(', ')}`,\n );\n }\n\n return input;\n}\n\n/**\n * Билдер жалобы.\n *\n * Точка входа задаёт объект жалобы и его тип одновременно, поэтому рассогласовать\n * `targetType` и `targetId` невозможно. Создаётся объектом {@link report}.\n */\nexport class ReportBuilder implements ItdBuilder<CreateReportInput> {\n /** @internal */\n readonly [BUILDER] = true as const;\n\n readonly #state: Partial<CreateReportInput>;\n\n /** @internal Создавайте билдер через {@link report}. */\n constructor(state: Partial<CreateReportInput>) {\n this.#state = state;\n }\n\n /** Указывает причину жалобы. */\n reason(reason: ReportReason): ReportBuilder {\n return new ReportBuilder({ ...this.#state, reason });\n }\n\n /** Добавляет пояснение в свободной форме. */\n description(text: string): ReportBuilder {\n return new ReportBuilder({ ...this.#state, description: text });\n }\n\n build(): CreateReportInput {\n return validateReport(this.#state as CreateReportInput);\n }\n\n toJSON(): CreateReportInput {\n return this.build();\n }\n}\n\nfunction start(targetType: ReportTargetType, targetId: string): ReportBuilder {\n return new ReportBuilder({ targetType, targetId });\n}\n\n/**\n * Начинает сборку жалобы.\n *\n * Тип объекта выбирается точкой входа, так что указать идентификатор комментария\n * с типом «пост» нельзя в принципе.\n *\n * @example\n * ```ts\n * import { report, ReportReason } from 'itd-api';\n *\n * await itd.reports.create(report.post(postId).reason(ReportReason.Spam));\n * await itd.reports.create(report.user(userId).reason('fraud').description('пишет в личку'));\n * ```\n */\nexport const report = Object.freeze({\n /** Жалоба на пост. */\n post: (postId: string) => start(ReportTargetType.Post, postId),\n /** Жалоба на комментарий. */\n comment: (commentId: string) => start(ReportTargetType.Comment, commentId),\n /** Жалоба на пользователя. */\n user: (userId: string) => start(ReportTargetType.User, userId),\n});\n\n/** Что принимает параметр жалобы: объект, билдер или функция-настройщик. */\nexport type ReportInput = BuilderInput<CreateReportInput, ReportBuilder>;\n\n/** Приводит любую форму входа к готовым данным жалобы. */\nexport function resolveReport(input: ReportInput): CreateReportInput {\n return resolveInput(input, () => new ReportBuilder({}), validateReport);\n}\n","import { type ReportInput, resolveReport } from '../builders/report.js';\nimport type { Report } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/**\n * Жалобы на контент и пользователей.\n *\n * Доступна как `itd.reports`.\n */\nexport class ReportsResource extends BaseResource {\n /**\n * Отправляет жалобу.\n *\n * Повторная жалоба на тот же объект отклоняется сервером с сообщением\n * «Вы уже отправляли жалобу на этот контент».\n *\n * @example\n * ```ts\n * await itd.reports.create(report.post(postId).reason('spam'));\n * await itd.reports.create({ targetType: 'user', targetId, reason: 'fraud' });\n * ```\n */\n create(input: ReportInput, options: RequestOptions = {}): Promise<Report> {\n const data = resolveReport(input);\n\n return this.http.request<Report>({\n method: 'POST',\n path: '/api/reports',\n body: data,\n ...this.requestOptions(options),\n });\n }\n}\n","import { pickArray } from '../core/unwrap.js';\nimport type { Hashtag, UserSummary } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/** Результат глобального поиска. */\nexport interface SearchResult {\n users: UserSummary[];\n hashtags: Hashtag[];\n}\n\n/**\n * Глобальный поиск.\n *\n * Доступна как `itd.search`.\n */\nexport class SearchResource extends BaseResource {\n /**\n * Ищет пользователей и хэштеги одним запросом.\n *\n * @example\n * ```ts\n * const { users, hashtags } = await itd.search.all('арт');\n * ```\n */\n async all(query: string, options: RequestOptions = {}): Promise<SearchResult> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/search',\n query: { q: query },\n ...this.requestOptions(options),\n });\n\n return {\n users: pickArray<UserSummary>(body, 'users'),\n hashtags: pickArray<Hashtag>(body, 'hashtags'),\n };\n }\n}\n","import { encodePathSegment } from '../core/url.js';\nimport type { PaymentMethod, Subscription } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/**\n * Подписка и способы оплаты.\n *\n * Доступна как `itd.subscription`.\n */\nexport class SubscriptionResource extends BaseResource {\n /** Загружает состояние подписки и её цену. */\n status(options: RequestOptions = {}): Promise<Subscription> {\n return this.http.request<Subscription>({\n method: 'GET',\n // Завершающий слэш обязателен.\n path: '/api/v1/subscription/',\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Запускает оплату подписки.\n *\n * Форма ответа в документации API не описана, поэтому тип результата не уточняется.\n */\n pay(options: RequestOptions = {}): Promise<unknown> {\n return this.http.request({\n method: 'POST',\n path: '/api/v1/subscription/pay',\n ...this.requestOptions(options),\n });\n }\n\n /** Включает или отключает автопродление. */\n setAutoRenewal(enabled: boolean, options: RequestOptions = {}): Promise<unknown> {\n return this.http.request({\n method: 'POST',\n path: '/api/v1/subscription/auto-renewal',\n body: { enabled },\n ...this.requestOptions(options),\n });\n }\n\n /** Запускает привязку карты. */\n bindCard(options: RequestOptions = {}): Promise<unknown> {\n return this.http.request({\n method: 'POST',\n path: '/api/v1/subscription/bind-card',\n ...this.requestOptions(options),\n });\n }\n\n /** Загружает список способов оплаты. Пустой массив, если карт нет. */\n async methods(options: RequestOptions = {}): Promise<PaymentMethod[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/v1/subscription/methods',\n ...this.requestOptions(options),\n });\n\n return Array.isArray(body) ? (body as PaymentMethod[]) : [];\n }\n\n /** Делает способ оплаты основным. */\n setDefaultMethod(methodId: string, options: RequestOptions = {}): Promise<unknown> {\n return this.http.request({\n method: 'POST',\n path: `/api/v1/subscription/methods/${encodePathSegment(methodId, 'methodId')}/default`,\n ...this.requestOptions(options),\n });\n }\n\n /** Удаляет способ оплаты. */\n removeMethod(methodId: string, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `/api/v1/subscription/methods/${encodePathSegment(methodId, 'methodId')}`,\n ...this.requestOptions(options),\n });\n }\n}\n","import { ItdConfigError } from '../core/errors.js';\nimport { createDeviceId } from '../core/runtime.js';\nimport { InteractionType, type ViewReason, type ViewSource } from '../types/enums.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/** Общие опции запроса телеметрии. */\nexport interface TelemetryOptions extends RequestOptions {\n /** Переопределяет идентификатор сессии телеметрии (`sid`) для этого запроса. */\n sid?: string;\n}\n\n/** Часы, используемые tracker-ом для измерения времени просмотра. */\nexport interface TelemetryClock {\n /** Возвращает текущее время в миллисекундах. */\n now(): number;\n}\n\n/** Опции tracker-а просмотра. */\nexport interface ViewTrackerOptions extends TelemetryOptions {\n /** Часы для измерения времени. По умолчанию используется `Date.now()`. */\n clock?: TelemetryClock;\n}\n\n/** Опции накопителя телеметрии. */\nexport interface TelemetryBatchOptions extends TelemetryOptions {\n /**\n * Максимальное число событий в одном запросе.\n *\n * Значение по умолчанию — 50. Это размер клиентской пачки, а не заявленный лимит API.\n */\n maxBatchSize?: number;\n /** Часы для tracker-ов, созданных через накопитель. */\n clock?: TelemetryClock;\n}\n\n/** Событие просмотра поста для {@link TelemetryResource.dwell}. */\nexport interface DwellEntry {\n /** Метка показа — поле `vs` объекта поста. */\n vs: string;\n /** Время появления поста в зоне видимости, epoch-мс. */\n enterAt: number;\n /** Время ухода из зоны видимости, epoch-мс. */\n exitAt: number;\n /** Причина завершения просмотра. */\n reason: ViewReason;\n /** Длительность просмотра в мс. По умолчанию `exitAt - enterAt`. */\n durationMs?: number;\n /** Контекст источника показа. */\n sourceContext?: string;\n /** Источник показа. */\n source?: ViewSource;\n /** Пост уже встречался в этой сессии. */\n repeat?: boolean;\n}\n\n/** Событие взаимодействия с контентом для {@link TelemetryResource.interaction}. */\nexport interface InteractionEntry {\n /** Тип взаимодействия. */\n type: InteractionType;\n /** Метка показа — поле `vs` объекта поста. */\n vs: string;\n /** Идентификатор поста. */\n postId: string;\n /** Индекс вложения, начиная с нуля. */\n mediaIndex?: number;\n /** Источник показа. */\n source?: ViewSource;\n /** Просмотренная позиция видео в мс. */\n positionMs?: number;\n /** Длительность видео в мс. */\n durationMs?: number;\n}\n\n/** Данные для {@link TelemetryResource.startView}. */\nexport interface ViewTrackerInput {\n /** Метка показа — поле `vs` объекта поста. */\n vs: string;\n /** Контекст источника показа. */\n sourceContext?: string;\n /** Источник показа. */\n source?: ViewSource;\n /** Пост уже встречался в этой сессии. */\n repeat?: boolean;\n}\n\n/** Данные открытия фотографии. */\nexport interface PhotoOpenInput {\n /** Метка показа — поле `vs` объекта поста. */\n vs: string;\n /** Идентификатор поста. */\n postId: string;\n /** Индекс открытого вложения, начиная с нуля. */\n mediaIndex: number;\n /** Источник показа. */\n source?: ViewSource;\n}\n\n/** Данные о прогрессе просмотра видео. */\nexport interface VideoProgressInput {\n /** Метка показа — поле `vs` объекта поста. */\n vs: string;\n /** Идентификатор поста. */\n postId: string;\n /** Просмотренная позиция видео в мс. */\n positionMs: number;\n /** Длительность видео в мс. */\n durationMs: number;\n /** Источник показа. */\n source?: ViewSource;\n}\n\n/** Активное измерение времени просмотра. */\nexport interface ViewTracker {\n /** Время начала измерения в миллисекундах. */\n readonly enteredAt: number;\n /** Был ли уже вызван {@link finish}. */\n readonly finished: boolean;\n /**\n * Завершает измерение и передаёт событие.\n *\n * Повторный вызов возвращает тот же Promise и не создаёт второе событие.\n */\n finish(reason: ViewReason): Promise<void>;\n}\n\n/** Явно управляемый накопитель событий телеметрии. */\nexport interface TelemetryBatch {\n /** Число ожидающих событий просмотра. */\n readonly pendingDwell: number;\n /** Число ожидающих событий взаимодействия. */\n readonly pendingInteractions: number;\n /** Закрыт ли накопитель. */\n readonly closed: boolean;\n /** Добавляет одно или несколько событий просмотра. */\n dwell(entry: DwellEntry | readonly DwellEntry[]): this;\n /** Добавляет одно или несколько событий взаимодействия. */\n interaction(entry: InteractionEntry | readonly InteractionEntry[]): this;\n /** Добавляет событие открытия фотографии. */\n photoOpen(input: PhotoOpenInput): this;\n /** Добавляет событие прогресса просмотра видео. */\n videoProgress(input: VideoProgressInput): this;\n /** Начинает измерение просмотра, которое после `finish()` попадёт в этот накопитель. */\n startView(input: ViewTrackerInput): ViewTracker;\n /**\n * Отправляет накопленные события.\n *\n * Опции позволяют заменить, например, отменённый `signal` при повторной попытке.\n */\n flush(options?: TelemetryOptions): Promise<void>;\n /** Отправляет накопленные события и закрывает накопитель. */\n close(): Promise<void>;\n}\n\nconst DEFAULT_BATCH_SIZE = 50;\nconst SYSTEM_CLOCK: TelemetryClock = Object.freeze({ now: () => Date.now() });\n\nfunction assertNonEmpty(value: string, name: string): void {\n if (value.trim().length === 0) throw new ItdConfigError(`${name} должен быть непустой строкой`);\n}\n\nfunction assertFinite(value: number, name: string): void {\n if (!Number.isFinite(value)) throw new ItdConfigError(`${name} должен быть конечным числом`);\n}\n\nfunction assertNonNegative(value: number, name: string): void {\n assertFinite(value, name);\n if (value < 0) throw new ItdConfigError(`${name} должен быть неотрицательным числом`);\n}\n\nfunction validateDwell(entry: DwellEntry): DwellEntry {\n assertNonEmpty(entry.vs, 'vs');\n assertFinite(entry.enterAt, 'enterAt');\n assertFinite(entry.exitAt, 'exitAt');\n if (entry.exitAt < entry.enterAt) {\n throw new ItdConfigError('exitAt не может быть раньше enterAt');\n }\n if (entry.durationMs !== undefined) assertNonNegative(entry.durationMs, 'durationMs');\n return { ...entry };\n}\n\nfunction validateInteraction(entry: InteractionEntry): InteractionEntry {\n assertNonEmpty(entry.vs, 'vs');\n assertNonEmpty(entry.postId, 'postId');\n if (entry.mediaIndex !== undefined) {\n assertNonNegative(entry.mediaIndex, 'mediaIndex');\n if (!Number.isInteger(entry.mediaIndex)) {\n throw new ItdConfigError('mediaIndex должен быть целым числом');\n }\n }\n if (entry.positionMs !== undefined) assertNonNegative(entry.positionMs, 'positionMs');\n if (entry.durationMs !== undefined) assertNonNegative(entry.durationMs, 'durationMs');\n return { ...entry };\n}\n\nfunction photoOpenEntry(input: PhotoOpenInput): InteractionEntry {\n return validateInteraction({ type: InteractionType.PhotoOpen, ...input });\n}\n\nfunction videoProgressEntry(input: VideoProgressInput): InteractionEntry {\n return validateInteraction({ type: InteractionType.VideoProgress, ...input });\n}\n\nclass ViewTrackerImpl implements ViewTracker {\n readonly enteredAt: number;\n readonly #clock: TelemetryClock;\n readonly #input: ViewTrackerInput;\n readonly #submit: (entry: DwellEntry) => Promise<void> | void;\n #result: Promise<void> | undefined;\n\n constructor(\n input: ViewTrackerInput,\n clock: TelemetryClock,\n submit: (entry: DwellEntry) => Promise<void> | void,\n ) {\n assertNonEmpty(input.vs, 'vs');\n this.enteredAt = clock.now();\n assertFinite(this.enteredAt, 'clock.now()');\n this.#input = { ...input };\n this.#clock = clock;\n this.#submit = submit;\n }\n\n get finished(): boolean {\n return this.#result !== undefined;\n }\n\n finish(reason: ViewReason): Promise<void> {\n if (this.#result) return this.#result;\n\n try {\n const exitAt = this.#clock.now();\n assertFinite(exitAt, 'clock.now()');\n if (exitAt < this.enteredAt) {\n throw new ItdConfigError('часы не могут вернуться назад во время измерения просмотра');\n }\n\n this.#result = Promise.resolve(\n this.#submit({\n ...this.#input,\n enterAt: this.enteredAt,\n exitAt,\n reason,\n }),\n );\n } catch (error) {\n this.#result = Promise.reject(error);\n }\n\n return this.#result;\n }\n}\n\nclass TelemetryBatchImpl implements TelemetryBatch {\n readonly #resource: TelemetryResource;\n readonly #options: TelemetryOptions;\n readonly #clock: TelemetryClock;\n readonly #maxBatchSize: number;\n readonly #onClose: () => void;\n readonly #dwell: DwellEntry[] = [];\n readonly #interactions: InteractionEntry[] = [];\n #state: 'open' | 'closing' | 'closed' = 'open';\n #flushPromise: Promise<void> | undefined;\n #closePromise: Promise<void> | undefined;\n\n constructor(resource: TelemetryResource, options: TelemetryBatchOptions, onClose: () => void) {\n const { maxBatchSize = DEFAULT_BATCH_SIZE, clock = SYSTEM_CLOCK, ...requestOptions } = options;\n if (!Number.isInteger(maxBatchSize) || maxBatchSize < 1) {\n throw new ItdConfigError('maxBatchSize должен быть целым числом от 1');\n }\n this.#resource = resource;\n this.#options = requestOptions;\n this.#clock = clock;\n this.#maxBatchSize = maxBatchSize;\n this.#onClose = onClose;\n }\n\n get pendingDwell(): number {\n return this.#dwell.length;\n }\n\n get pendingInteractions(): number {\n return this.#interactions.length;\n }\n\n get closed(): boolean {\n return this.#state === 'closed';\n }\n\n dwell(entry: DwellEntry | readonly DwellEntry[]): this {\n this.#assertOpen();\n const entries = Array.isArray(entry) ? entry : [entry];\n this.#dwell.push(...entries.map(validateDwell));\n return this;\n }\n\n interaction(entry: InteractionEntry | readonly InteractionEntry[]): this {\n this.#assertOpen();\n const entries = Array.isArray(entry) ? entry : [entry];\n this.#interactions.push(...entries.map(validateInteraction));\n return this;\n }\n\n photoOpen(input: PhotoOpenInput): this {\n return this.interaction(photoOpenEntry(input));\n }\n\n videoProgress(input: VideoProgressInput): this {\n return this.interaction(videoProgressEntry(input));\n }\n\n startView(input: ViewTrackerInput): ViewTracker {\n this.#assertOpen();\n return new ViewTrackerImpl(input, this.#clock, (entry) => {\n this.dwell(entry);\n });\n }\n\n flush(options: TelemetryOptions = {}): Promise<void> {\n if (this.#state === 'closed') return Promise.resolve();\n if (this.#flushPromise) return this.#flushPromise;\n\n const running = this.#flushAll({ ...this.#options, ...options });\n const tracked = running.finally(() => {\n if (this.#flushPromise === tracked) this.#flushPromise = undefined;\n });\n this.#flushPromise = tracked;\n return tracked;\n }\n\n close(): Promise<void> {\n if (this.#state === 'closed') return Promise.resolve();\n if (this.#closePromise) return this.#closePromise;\n\n this.#state = 'closing';\n const running = this.flush()\n .then(() => {\n this.#state = 'closed';\n this.#onClose();\n })\n .catch((error: unknown) => {\n this.#state = 'open';\n throw error;\n });\n const tracked = running.finally(() => {\n if (this.#closePromise === tracked) this.#closePromise = undefined;\n });\n this.#closePromise = tracked;\n return tracked;\n }\n\n async #flushAll(options: TelemetryOptions): Promise<void> {\n while (this.#dwell.length > 0) {\n const chunk = this.#dwell.splice(0, this.#maxBatchSize);\n try {\n await this.#resource.dwell(chunk, options);\n } catch (error) {\n this.#dwell.unshift(...chunk);\n throw error;\n }\n }\n\n while (this.#interactions.length > 0) {\n const chunk = this.#interactions.splice(0, this.#maxBatchSize);\n try {\n await this.#resource.interaction(chunk, options);\n } catch (error) {\n this.#interactions.unshift(...chunk);\n throw error;\n }\n }\n }\n\n #assertOpen(): void {\n if (this.#state !== 'open') {\n throw new ItdConfigError('накопитель телеметрии уже закрывается или закрыт');\n }\n }\n}\n\n/**\n * Телеметрия просмотров и взаимодействий.\n *\n * Ничего не отправляет автоматически: каждый запрос, tracker или накопитель создаётся\n * явным вызовом пользователя. Доступна как `itd.telemetry`.\n */\nexport class TelemetryResource extends BaseResource {\n #sessionId: string | undefined;\n readonly #batches = new Set<TelemetryBatchImpl>();\n\n /** Идентификатор сессии телеметрии, общий для всех событий этого ресурса. */\n get sessionId(): string {\n this.#sessionId ??= createDeviceId();\n return this.#sessionId;\n }\n\n /** Отправляет события просмотра постов (`POST /api/v1/i`). */\n dwell(entries: readonly DwellEntry[], options: TelemetryOptions = {}): Promise<unknown> {\n const validated = entries.map(validateDwell);\n return this.http.request({\n method: 'POST',\n path: '/api/v1/i',\n body: {\n sid: options.sid ?? this.sessionId,\n e: validated.map((entry) => ({\n md: entry.durationMs ?? entry.exitAt - entry.enterAt,\n et: entry.enterAt,\n xt: entry.exitAt,\n r: entry.reason,\n v: entry.vs,\n ...(entry.sourceContext !== undefined ? { sc: entry.sourceContext } : {}),\n ...(entry.source !== undefined ? { s: entry.source } : {}),\n ...(entry.repeat ? { b: 1 } : {}),\n })),\n },\n ...this.requestOptions(options),\n });\n }\n\n /** Отправляет события взаимодействия с контентом (`POST /api/v1/x`). */\n interaction(\n entries: readonly InteractionEntry[],\n options: TelemetryOptions = {},\n ): Promise<unknown> {\n const validated = entries.map(validateInteraction);\n return this.http.request({\n method: 'POST',\n path: '/api/v1/x',\n body: {\n sid: options.sid ?? this.sessionId,\n e: validated.map((entry) => ({\n t: entry.type,\n v: entry.vs,\n ai: entry.postId,\n ...(entry.mediaIndex !== undefined ? { mi: entry.mediaIndex } : {}),\n ...(entry.source !== undefined ? { s: entry.source } : {}),\n ...(entry.positionMs !== undefined ? { pm: Math.round(entry.positionMs) } : {}),\n ...(entry.durationMs !== undefined ? { dm: Math.round(entry.durationMs) } : {}),\n })),\n },\n ...this.requestOptions(options),\n });\n }\n\n /** Начинает измерять время просмотра и отправляет результат после `finish()`. */\n startView(input: ViewTrackerInput, options: ViewTrackerOptions = {}): ViewTracker {\n const { clock = SYSTEM_CLOCK, ...requestOptions } = options;\n return new ViewTrackerImpl(input, clock, async (entry) => {\n await this.dwell([entry], requestOptions);\n });\n }\n\n /** Отправляет событие открытия фотографии. */\n photoOpen(input: PhotoOpenInput, options: TelemetryOptions = {}): Promise<unknown> {\n return this.interaction([photoOpenEntry(input)], options);\n }\n\n /** Отправляет событие прогресса просмотра видео. */\n videoProgress(input: VideoProgressInput, options: TelemetryOptions = {}): Promise<unknown> {\n return this.interaction([videoProgressEntry(input)], options);\n }\n\n /**\n * Создаёт накопитель с явными `flush()` и `close()`.\n *\n * Создание и добавление записей не выполняют сетевых запросов.\n */\n batch(options: TelemetryBatchOptions = {}): TelemetryBatch {\n let batch: TelemetryBatchImpl;\n batch = new TelemetryBatchImpl(this, options, () => {\n this.#batches.delete(batch);\n });\n this.#batches.add(batch);\n return batch;\n }\n\n /** Закрывает все созданные накопители, отправляя оставшиеся записи. */\n async close(): Promise<void> {\n const results = await Promise.allSettled([...this.#batches].map((batch) => batch.close()));\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, 'Не удалось отправить накопленную телеметрию');\n }\n }\n}\n","import type { FileInput } from '../core/attachments.js';\nimport type { HttpClient } from '../core/http.js';\nimport { type Page, PaginationMode, type Paginator, readPagedPage } from '../core/pagination.js';\nimport { pickArray, pickBoolean, pickString } from '../core/unwrap.js';\nimport { encodePathSegment } from '../core/url.js';\nimport type {\n Clan,\n FollowResult,\n MyProfile,\n PinsResult,\n PrivacySettings,\n PublicProfile,\n UserId,\n UserRef,\n UserSummary,\n} from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\nimport type { UploadedFile, UploadOptions } from './files.js';\n\n/**\n * Параметры списков пользователей.\n *\n * ⚠️ Списки подписчиков, подписок и заблокированных на сервере **не листаются**:\n * `page` он игнорирует, а `limit` зажимает на 20. Подробности — в {@link UsersResource.followers}.\n */\nexport interface UserListParams extends RequestOptions {\n /** Сколько записей вернуть. Значения больше 20 сервер молча уменьшает до 20. */\n limit?: number;\n /** Номер страницы. Сервер его игнорирует — оставлен на случай, если пагинацию починят. */\n page?: number;\n maxPages?: number;\n}\n\n/** Изменяемые поля своего профиля. */\nexport interface UpdateProfileInput {\n displayName?: string;\n username?: string;\n /** Эмодзи-аватар: символ клана, а не адрес картинки. */\n avatar?: string;\n bio?: string;\n /** Идентификатор загруженного файла баннера. `null` удаляет текущий баннер. */\n bannerId?: string | null;\n}\n\n/** Изменяемые настройки приватности. */\nexport type UpdatePrivacyInput = Partial<PrivacySettings>;\n\n/**\n * Пользователи: профили, подписки, блокировки, приватность.\n *\n * Доступна как `itd.users`.\n */\nexport class UsersResource extends BaseResource {\n readonly #uploadFile: (file: FileInput, options?: UploadOptions) => Promise<UploadedFile>;\n\n constructor(\n http: HttpClient,\n deps: {\n uploadFile: (file: FileInput, options?: UploadOptions) => Promise<UploadedFile>;\n },\n ) {\n super(http);\n this.#uploadFile = deps.uploadFile;\n }\n\n /**\n * Списки пользователей: подписчики, подписки, заблокированные.\n *\n * Путь приходит в параметрах — так один описатель обслуживает все три эндпоинта. Имена\n * полей перечислены с запасом: списки приходят под `users`, но альтернативное имя ничего\n * не стоит и спасает, если эндпоинт назовёт список по-своему. `page` уходит в запрос, хотя\n * сервер его сейчас не читает (см. {@link followers}): когда починят — заработает само.\n */\n readonly #userList = this.paginated<UserSummary, UserListParams & { path: string }>({\n path: (p) => p.path,\n query: (p) => ({ limit: p.limit }),\n start: (p) => (p.page !== undefined ? { page: p.page } : {}),\n read: (body) => readPagedPage<UserSummary>(body, 'users', 'followers', 'following', 'blocked'),\n mode: PaginationMode.Page,\n });\n\n /** Загружает свой профиль — с подпиской и признаком подтверждённого телефона. */\n me(options: RequestOptions = {}): Promise<MyProfile> {\n return this.http.request<MyProfile>({\n method: 'GET',\n path: '/api/users/me',\n ...this.requestOptions(options),\n });\n }\n\n /** Обновляет свой профиль. Передавайте только изменяемые поля. */\n updateMe(input: UpdateProfileInput, options: RequestOptions = {}): Promise<MyProfile> {\n return this.http.request<MyProfile>({\n method: 'PUT',\n path: '/api/users/me',\n body: input,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Загружает изображение и устанавливает его баннером профиля.\n *\n * Для установки используется идентификатор, полученный от `/api/files/upload`.\n * Если файл уже загружен, используйте {@link updateMe}: `{ bannerId: file.id }`.\n *\n * @example\n * ```ts\n * await itd.users.setBanner(file, { filename: 'banner.webp' });\n * ```\n */\n async setBanner(file: FileInput, options: UploadOptions = {}): Promise<MyProfile> {\n const uploaded = await this.#uploadFile(file, options);\n return this.updateMe({ bannerId: uploaded.id }, options);\n }\n\n /** Удаляет баннер профиля, устанавливая `bannerId` в `null`. */\n removeBanner(options: RequestOptions = {}): Promise<MyProfile> {\n return this.updateMe({ bannerId: null }, options);\n }\n\n /** Деактивирует аккаунт. Вернуть его можно через {@link restore}. */\n deactivate(options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: '/api/users/me',\n ...this.requestOptions(options),\n });\n }\n\n /** Восстанавливает деактивированный аккаунт. */\n restore(options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'POST',\n path: '/api/users/me/restore',\n ...this.requestOptions(options),\n });\n }\n\n /** Создаёт профиль после регистрации. */\n createProfile(\n input: { username: string; displayName: string; avatar?: string },\n options: RequestOptions = {},\n ): Promise<MyProfile> {\n return this.http.request<MyProfile>({\n method: 'POST',\n path: '/api/users/profile',\n body: input,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Загружает профиль пользователя.\n *\n * @param user UUID **или** имя пользователя — подходит и то, и другое\n *\n * @example\n * ```ts\n * const profile = await itd.users.get('nowkie');\n * await itd.posts.create({ content: 'привет', wallRecipientId: profile.id });\n * ```\n */\n get(user: UserRef, options: RequestOptions = {}): Promise<PublicProfile> {\n return this.http.request<PublicProfile>({\n method: 'GET',\n path: `/api/users/${encodePathSegment(user, 'user')}`,\n ...this.requestOptions(options),\n });\n }\n\n /** Проверяет, свободно ли имя пользователя. */\n async checkUsername(username: string, options: RequestOptions = {}): Promise<boolean> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/users/check-username',\n query: { username },\n ...this.requestOptions(options),\n });\n\n return pickBoolean(body, 'available');\n }\n\n /** Ищет пользователей по строке запроса. */\n async search(\n query: string,\n params: { limit?: number } & RequestOptions = {},\n ): Promise<UserSummary[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/users/search',\n query: { q: query, limit: params.limit },\n ...this.requestOptions(params),\n });\n\n return pickArray<UserSummary>(body, 'users');\n }\n\n /** Загружает рекомендации, на кого подписаться. */\n async whoToFollow(options: RequestOptions = {}): Promise<UserSummary[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/users/suggestions/who-to-follow',\n ...this.requestOptions(options),\n });\n\n return pickArray<UserSummary>(body, 'users');\n }\n\n /** Загружает рейтинг кланов. */\n async topClans(options: RequestOptions = {}): Promise<Clan[]> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/users/stats/top-clans',\n ...this.requestOptions(options),\n });\n\n return pickArray<Clan>(body, 'clans');\n }\n\n /**\n * Подписывается на пользователя.\n *\n * У закрытого профиля вместо подписки отправляется заявка — это видно по полю `status`.\n */\n follow(user: UserRef, options: RequestOptions = {}): Promise<FollowResult> {\n return this.http.request<FollowResult>({\n method: 'POST',\n path: `/api/users/${encodePathSegment(user, 'user')}/follow`,\n body: {},\n ...this.requestOptions(options),\n });\n }\n\n /** Отписывается от пользователя. */\n unfollow(user: UserRef, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `/api/users/${encodePathSegment(user, 'user')}/follow`,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Загружает подписчиков пользователя.\n *\n * ⚠️ **Сервер этот список не листает.** Возвращаются первые 20 записей и только они:\n * параметр `page` игнорируется (любая страница отдаёт те же записи и `pagination.page: 1`),\n * `limit` больше 20 молча уменьшается, а `hasMore` всегда `false`. Последнее честно —\n * получить продолжение нечем.\n *\n * Числу `total` доверять тоже не стоит: оно расходится с `followersCount` из профиля —\n * на проверенных аккаунтах занижено примерно на 1–4%.\n */\n followers(user: UserRef, params: UserListParams = {}): Promise<Page<UserSummary>> {\n return this.#userPage(`/api/users/${encodePathSegment(user, 'user')}/followers`, params);\n }\n\n /**\n * Перебирает подписчиков.\n *\n * ⚠️ Перебор закончится после первых 20 записей: сервер список не листает —\n * см. {@link followers}. Метод оставлен на случай, если пагинацию починят.\n */\n iterateFollowers(user: UserRef, params: UserListParams = {}): Paginator<UserSummary> {\n return this.#userPaginator(`/api/users/${encodePathSegment(user, 'user')}/followers`, params);\n }\n\n /** Загружает подписки пользователя. Ограничения те же, что у {@link followers}. */\n following(user: UserRef, params: UserListParams = {}): Promise<Page<UserSummary>> {\n return this.#userPage(`/api/users/${encodePathSegment(user, 'user')}/following`, params);\n }\n\n /** Перебирает подписки. Закончится после первых 20 записей — см. {@link followers}. */\n iterateFollowing(user: UserRef, params: UserListParams = {}): Paginator<UserSummary> {\n return this.#userPaginator(`/api/users/${encodePathSegment(user, 'user')}/following`, params);\n }\n\n /**\n * Проверяет, подписаны ли вы, сразу для нескольких пользователей.\n *\n * @returns объект «идентификатор пользователя → подписаны ли вы»\n *\n * @example\n * ```ts\n * const statuses = await itd.users.followStatus([userA, userB]);\n * // { 'b89dee4f-…': true, '35ea3059-…': false }\n * ```\n */\n followStatus(userIds: UserId[], options: RequestOptions = {}): Promise<Record<string, boolean>> {\n return this.http.request<Record<string, boolean>>({\n method: 'POST',\n path: '/api/users/follow-status',\n body: { userIds },\n ...this.requestOptions(options),\n });\n }\n\n /** Блокирует пользователя. */\n block(user: UserRef, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'POST',\n path: `/api/users/${encodePathSegment(user, 'user')}/block`,\n body: {},\n ...this.requestOptions(options),\n });\n }\n\n /** Снимает блокировку. */\n unblock(user: UserRef, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: `/api/users/${encodePathSegment(user, 'user')}/block`,\n ...this.requestOptions(options),\n });\n }\n\n /** Загружает заблокированных пользователей. Ограничения те же, что у {@link followers}. */\n blocked(params: UserListParams = {}): Promise<Page<UserSummary>> {\n return this.#userPage('/api/users/me/blocked', params);\n }\n\n /** Перебирает заблокированных. Закончится после первых 20 записей — см. {@link followers}. */\n iterateBlocked(params: UserListParams = {}): Paginator<UserSummary> {\n return this.#userPaginator('/api/users/me/blocked', params);\n }\n\n /** Загружает настройки приватности. */\n getPrivacy(options: RequestOptions = {}): Promise<PrivacySettings> {\n return this.http.request<PrivacySettings>({\n method: 'GET',\n path: '/api/users/me/privacy',\n ...this.requestOptions(options),\n });\n }\n\n /** Обновляет настройки приватности. Передавайте только изменяемые поля. */\n updatePrivacy(input: UpdatePrivacyInput, options: RequestOptions = {}): Promise<PrivacySettings> {\n return this.http.request<PrivacySettings>({\n method: 'PUT',\n path: '/api/users/me/privacy',\n body: input,\n ...this.requestOptions(options),\n });\n }\n\n /**\n * Загружает значки профиля и выбранный из них.\n *\n * `activePin` — строка-идентификатор, а не объект.\n */\n async pins(options: RequestOptions = {}): Promise<PinsResult> {\n const body = await this.http.request({\n method: 'GET',\n path: '/api/users/me/pins',\n ...this.requestOptions(options),\n });\n\n return {\n pins: pickArray(body, 'pins'),\n // Сервер отдаёт здесь строку-идентификатор, а не объект значка.\n activePin: pickString(body, 'activePin') ?? null,\n };\n }\n\n /** Выбирает активный значок профиля. */\n setPin(slug: string, options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'PUT',\n path: '/api/users/me/pin',\n body: { slug },\n ...this.requestOptions(options),\n });\n }\n\n /** Снимает активный значок. */\n removePin(options: RequestOptions = {}): Promise<void> {\n return this.http.request<void>({\n method: 'DELETE',\n path: '/api/users/me/pin',\n ...this.requestOptions(options),\n });\n }\n\n #userPage(path: string, params: UserListParams): Promise<Page<UserSummary>> {\n return this.#userList.list({ ...params, path });\n }\n\n #userPaginator(path: string, params: UserListParams): Paginator<UserSummary> {\n return this.#userList.iterate({ ...params, path });\n }\n}\n","import type { VerificationStatus } from '../types/models.js';\nimport type { RequestOptions } from '../types/options.js';\nimport { BaseResource } from './base.js';\n\n/**\n * Верификация профиля.\n *\n * Доступна как `itd.verification`.\n */\nexport class VerificationResource extends BaseResource {\n /** Загружает статус заявки. Значение `none` означает, что заявка не подавалась. */\n status(options: RequestOptions = {}): Promise<VerificationStatus> {\n return this.http.request<VerificationStatus>({\n method: 'GET',\n path: '/api/verification/status',\n ...this.requestOptions(options),\n });\n }\n\n /** Подаёт заявку на верификацию с видео. */\n submit(videoUrl: string, options: RequestOptions = {}): Promise<unknown> {\n return this.http.request({\n method: 'POST',\n path: '/api/verification/submit',\n body: { videoUrl },\n ...this.requestOptions(options),\n });\n }\n}\n","import type { FileInput } from './core/attachments.js';\nimport { type AuthEvents, AuthManager } from './core/auth.js';\nimport { BUILT_IN_SERVICES, type ResolvedConfig, resolveConfig } from './core/config.js';\nimport { CookieJar } from './core/cookies.js';\nimport type { Listener, Unsubscribe } from './core/emitter.js';\nimport { HttpClient } from './core/http.js';\nimport {\n composePipeline,\n createAuthMiddleware,\n createPluginsMiddleware,\n createQueueMiddleware,\n createRetryMiddleware,\n createServicesMiddleware,\n type RequestMiddleware,\n} from './core/middleware.js';\nimport type { PipelineRequest, RequestHandler } from './core/pipeline.js';\nimport { type ItdPlugin, PluginRegistry } from './core/plugins.js';\nimport { RequestQueuePool } from './core/rate-limit.js';\nimport { type ServiceDefinition, ServiceRegistry } from './core/services.js';\nimport type { ItdSession } from './core/storage.js';\nimport { Transport } from './core/transport.js';\nimport { ItdRealtime, type RealtimeOptions } from './realtime/stream.js';\nimport { AuthResource } from './resources/auth.js';\nimport { CommentsResource } from './resources/comments.js';\nimport { FilesResource } from './resources/files.js';\nimport { HashtagsResource } from './resources/hashtags.js';\nimport { NotificationsResource } from './resources/notifications.js';\nimport { PlatformResource } from './resources/platform.js';\nimport { PostsResource } from './resources/posts.js';\nimport { ReportsResource } from './resources/reports.js';\nimport { SearchResource } from './resources/search.js';\nimport { SubscriptionResource } from './resources/subscription.js';\nimport { TelemetryResource } from './resources/telemetry.js';\nimport { UsersResource } from './resources/users.js';\nimport { VerificationResource } from './resources/verification.js';\nimport type { UserId } from './types/models.js';\nimport type {\n ItdClientOptions,\n RawRequestOptions,\n RequestOptions,\n RetryOptions,\n} from './types/options.js';\n\ndeclare global {\n interface SymbolConstructor {\n readonly asyncDispose: unique symbol;\n }\n}\n\nconst CLIENT_PLUGIN_REGISTRIES = new WeakMap<ItdClient, PluginRegistry>();\n\n/** Проверяет возможность подключить плагин к клиенту без вызова `install()`. @internal */\nexport function assertClientCanUsePlugin(client: ItdClient, plugin: ItdPlugin): void {\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 */\nexport interface ItdClientInternals {\n /**\n * Готовая очередь запросов — так {@link ItdAccounts} с `rateLimitScope: 'shared'` даёт\n * нескольким клиентам одну на всех. Свою клиент в этом случае не заводит и, что важнее,\n * не гасит при `close()`: чужие ожидающие запросы это отменило бы.\n */\n queues?: RequestQueuePool | undefined;\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 #config: ResolvedConfig;\n readonly #http: HttpClient;\n readonly #transport: Transport;\n readonly #authManager: AuthManager;\n readonly #jar: CookieJar;\n readonly #queues: RequestQueuePool | undefined;\n /** Заведена ли очередь этим клиентом. Чужую он останавливать не вправе. */\n readonly #ownsQueues: boolean;\n readonly #plugins = new PluginRegistry();\n readonly #services: ServiceRegistry;\n /** Порождённые потоки уведомлений — чтобы `close()` мог закрыть их разом. */\n readonly #streams = new Set<ItdRealtime>();\n\n /** Авторизация, сессии и пароли. */\n readonly auth: AuthResource;\n /** Профили, подписки, блокировки, приватность. */\n readonly users: UsersResource;\n /** Лента, публикация, реакции, репосты, комментарии к постам. */\n readonly posts: PostsResource;\n /** Ответы на комментарии и действия над ними. */\n readonly comments: CommentsResource;\n /** Загрузка файлов и медиа. */\n readonly files: FilesResource;\n /** Уведомления: список, счётчик, отметки о прочтении, настройки. */\n readonly notifications: NotificationsResource;\n /** Хэштеги и посты по ним. */\n readonly hashtags: HashtagsResource;\n /** Глобальный поиск по пользователям и хэштегам. */\n readonly search: SearchResource;\n /** Жалобы на контент и пользователей. */\n readonly reports: ReportsResource;\n /** Верификация профиля. */\n readonly verification: VerificationResource;\n /** Подписка и способы оплаты. */\n readonly subscription: SubscriptionResource;\n /** Сведения о платформе: версии приложений, изменения, анонсы, баннер события. */\n readonly platform: PlatformResource;\n /** Телеметрия просмотров. */\n readonly telemetry: TelemetryResource;\n\n constructor(options: ItdClientOptions = {}, internals: ItdClientInternals = {}) {\n CLIENT_PLUGIN_REGISTRIES.set(this, this.#plugins);\n const config = resolveConfig(options);\n this.#config = config;\n this.#jar = new CookieJar();\n this.#services = new ServiceRegistry(config.baseUrl);\n\n // Встроенные сервисы регистрируются первыми и заменить их нельзя\n for (const service of BUILT_IN_SERVICES) this.#services.define(service);\n for (const service of config.services) this.#services.define(service);\n\n // Очередь может прийти извне — общая на несколько аккаунтов. `rateLimit: false` отключает\n // её и в этом случае: отдельный аккаунт вправе не вставать в общую очередь.\n const shared = config.rateLimit ? internals.queues : undefined;\n const queues =\n shared ??\n (config.rateLimit ? new RequestQueuePool(config.rateLimit, config.clock) : undefined);\n this.#queues = queues;\n // Гасить в `close()` можно только свою: остановка чужой отменила бы ожидающие запросы\n // соседних аккаунтов.\n this.#ownsQueues = shared === undefined;\n\n // Заполняется ниже. Транспорту нужен `getDeviceId` авторизации, а авторизации —\n // транспорт; взаимная ссылка замыкается через отложенный вызов.\n let authManager!: AuthManager;\n\n // Конструкторские хуки идут первыми, за ними — динамически подключаемые хуки плагинов.\n // Один и тот же объект передаётся транспорту и retry-слою, поэтому плагин видит каждую\n // сетевую попытку, в том числе onRetry между ними.\n const hooks = this.#plugins.hooks(config.hooks);\n const transport = new Transport(\n { ...config, hooks },\n {\n cookies: config.useCookieJar ? this.#jar : undefined,\n getDeviceId: () => authManager.getDeviceId(),\n onRateLimit:\n queues && config.rateLimit?.respectHeaders\n ? (limit, remaining, request) => this.#throttleByHeaders(limit, remaining, request)\n : undefined,\n },\n );\n this.#transport = transport;\n\n const pluginsLayer = createPluginsMiddleware(this.#plugins);\n const retriesLayer = createRetryMiddleware({\n clock: config.clock,\n retry: config.retry,\n rateLimitDelays: config.rateLimit?.retryDelays ?? [],\n pauseQueue: queues ? (ms, request) => queues.for(request.service).pause(ms) : undefined,\n hooks,\n logger: config.logger,\n buildUrl: (request) => transport.buildUrl(request),\n });\n\n const authRetry: RetryOptions | undefined = config.retry\n ? {\n attempts: config.retry.attempts,\n baseDelay: config.retry.baseDelay,\n maxDelay: config.retry.maxDelay,\n jitter: config.retry.jitter,\n retryWrites: true,\n ...(config.retry.shouldRetry ? { shouldRetry: config.retry.shouldRetry } : {}),\n }\n : undefined;\n const authPipeline = composePipeline([pluginsLayer, retriesLayer], transport.send);\n // Служебные запросы авторизации проходят через плагины и повторы, но не через очередь\n // и не через сам слой авторизации: они часто запускаются изнутри запроса, который уже ждёт токен.\n // Для них POST безопасен к повтору: refresh/sign-in не создают пользовательский контент.\n const authHandler: RequestHandler = (request) =>\n authRetry && request.retry === undefined\n ? authPipeline({ ...request, retry: authRetry })\n : authPipeline(request);\n authManager = new AuthManager(config, authHandler, this.#jar, {\n onAccountChange: () => this.#disconnectStreams(),\n });\n this.#authManager = authManager;\n\n // Порядок слоёв: очередь снаружи, за ней плагины, сервисы, повторы и авторизация,\n // в сердцевине — транспорт.\n const middlewares: RequestMiddleware[] = [];\n if (queues) {\n middlewares.push(\n createQueueMiddleware((request, task) =>\n queues.for(request.service).schedule(task, request.signal),\n ),\n );\n }\n middlewares.push(pluginsLayer);\n middlewares.push(createServicesMiddleware(this.#services));\n middlewares.push(retriesLayer);\n middlewares.push(\n createAuthMiddleware({\n getAuthHeaders: () => authManager.getAuthHeaders(),\n onUnauthorized: () => authManager.onUnauthorized(),\n autoRefresh: config.autoRefresh,\n }),\n );\n\n const handler = composePipeline(middlewares, transport.send);\n this.#http = new HttpClient({ handler, plugins: this.#plugins, baseUrl: config.baseUrl });\n\n this.files = new FilesResource(this.#http, { fetch: config.fetch });\n\n const uploadFiles = (files: FileInput[], requestOptions?: RequestOptions) =>\n this.files.uploadMany(files, requestOptions ?? {});\n const uploadFile = (file: FileInput, requestOptions?: RequestOptions) =>\n this.files.upload(file, requestOptions ?? {});\n\n this.auth = new AuthResource(this.#http, { auth: this.#authManager });\n this.users = new UsersResource(this.#http, { uploadFile });\n this.posts = new PostsResource(this.#http, { uploadFiles });\n this.comments = new CommentsResource(this.#http, { uploadFiles });\n this.notifications = new NotificationsResource(this.#http);\n this.hashtags = new HashtagsResource(this.#http);\n this.search = new SearchResource(this.#http);\n this.reports = new ReportsResource(this.#http);\n this.verification = new VerificationResource(this.#http);\n this.subscription = new SubscriptionResource(this.#http);\n this.platform = new PlatformResource(this.#http);\n this.telemetry = new TelemetryResource(this.#http);\n }\n\n /** Базовый URL, к которому обращается клиент. */\n get baseUrl(): string {\n return this.#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 request<T = unknown>(options: RawRequestOptions): Promise<T> {\n return this.#http.request<T>(options);\n }\n\n /**\n * Подключает плагин.\n *\n * Плагин работает на уровне транспорта: видит запрос до отправки и разобранный ответ,\n * поэтому одна обёртка охватывает сразу все методы клиента. Подключать можно в любой\n * момент, но обычно это делают сразу после создания клиента.\n *\n * @throws {ItdConfigError} если плагин задан неверно или уже подключён\n *\n * @example\n * ```ts\n * import { crypt } from '@itd-api/crypto';\n *\n * itd.use(crypt());\n * await itd.posts.create({ content: 'секрет' }, { encrypt: 'invis' });\n * ```\n */\n use(plugin: ItdPlugin): this {\n this.#plugins.add(plugin, {\n baseUrl: this.#config.baseUrl,\n logger: this.#config.logger,\n getAuthScope: () => this.#authManager.getAuthScope(),\n getAuthIdentity: () => this.#authManager.getAuthIdentity(),\n });\n return this;\n }\n\n /** Имена подключённых плагинов в фактическом порядке выполнения обёрток. */\n pluginNames(): string[] {\n return this.#plugins.names();\n }\n\n /** Подключён ли плагин с таким именем. */\n hasPlugin(name: string): boolean {\n return this.#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.#plugins.remove(name);\n }\n\n /**\n * Регистрирует сервис платформы — домен, отличный от основного.\n *\n * Запросы с `{ service: 'имя' }` уходят на его хост с его заголовками. То же самое умеет\n * опция `services` конструктора. Занятое имя не переопределяется — ни своё, ни встроенное:\n * разовому запросу хост задаётся полем `baseUrl`.\n *\n * Заголовок авторизации по умолчанию уходит только своим — домену клиента и его\n * поддоменам. Стороннему хосту токен нужно разрешить явно: `auth: true`.\n *\n * @throws {ItdConfigError} если определение неверно или имя уже занято\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 this.#services.define(definition);\n return this;\n }\n\n /**\n * Базовый URL зарегистрированного сервиса.\n *\n * @throws {ItdConfigError} если сервис не зарегистрирован\n */\n serviceBaseUrl(name: string): string {\n return this.#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 return this.#authManager.on(event, listener);\n }\n\n /**\n * Создаёт поток уведомлений в реальном времени.\n *\n * Каждый вызов даёт новый независимый поток; обычно он нужен один на приложение.\n * Соединение поднимается методом `connect()` и держится само. Замена авторизации на токен\n * другого пользователя завершает все потоки клиента; смена только сессии их не затрагивает.\n *\n * @example\n * ```ts\n * import { NotificationType } from 'itd-api';\n *\n * const stream = itd.realtime();\n *\n * stream.onNotification(NotificationType.PostComment, async ({ update }) => {\n * await handleComment(update.data.notification);\n * });\n * stream.on('unreadCount', (count) => setBadge(count));\n *\n * await stream.connect();\n * ```\n */\n realtime(options: RealtimeOptions = {}): ItdRealtime {\n let stream!: ItdRealtime;\n stream = new ItdRealtime(\n {\n baseUrl: this.#config.baseUrl,\n fetch: this.#config.fetch,\n baseHeaders: (url) => this.#transport.platformHeaders(url),\n getAuthIdentity: () => this.#authManager.getCurrentAuthIdentity(),\n getAuthScope: () => this.#authManager.getAuthScope(),\n getToken: () => this.#authManager.getAccessToken(),\n refresh: () => this.#authManager.onUnauthorized(),\n fetchUnreadCount: () => this.notifications.count(),\n onConnect: () => this.#streams.add(stream),\n onClose: () => this.#streams.delete(stream),\n logger: this.#config.logger,\n clock: this.#config.clock,\n },\n options,\n );\n\n // Регистрируем для `close()`: поток держит открытое соединение и таймер переподключения.\n this.#streams.add(stream);\n return stream;\n }\n\n /**\n * Освобождает ресурсы клиента: закрывает все потоки уведомлений, отправляет открытые\n * накопители {@link telemetry}, затем останавливает очередь запросов.\n *\n * Метод дожидается активных обработчиков потока. После вызова клиентом можно пользоваться\n * снова; ранее созданный поток можно запустить повторным `connect()`.\n *\n * Общая очередь, полученная от {@link ItdAccounts}, не останавливается: её гасит сам\n * контейнер, когда закрывает все аккаунты разом.\n *\n * @example\n * ```ts\n * await using itd = new ItdClient({ auth: token });\n * // …работа…\n * // dispose() вызовется сам на выходе из блока\n * ```\n */\n async close(): Promise<void> {\n const streams = this.#disconnectStreams();\n try {\n await Promise.all(streams.map((stream) => stream.drain()));\n await this.telemetry.close();\n } finally {\n if (this.#ownsQueues) this.#queues?.stop();\n }\n }\n\n /**\n * Окончательно освобождает клиент: выполняет {@link close} и отключает все плагины.\n *\n * В отличие от `close()`, после `dispose()` плагины не восстанавливаются автоматически.\n * Сам клиент остаётся пригоден для обычных запросов; при необходимости плагины можно\n * подключить заново через {@link use}.\n */\n async dispose(): Promise<void> {\n const results = await Promise.allSettled([this.close(), this.#plugins.dispose()]);\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 /** Завершает потоки до того, как запросы начнут использовать другой аккаунт. */\n #disconnectStreams(): ItdRealtime[] {\n const streams = [...this.#streams];\n for (const stream of streams) stream.disconnect();\n this.#streams.clear();\n return streams;\n }\n\n /** Позволяет использовать клиент с `await using`. */\n [Symbol.asyncDispose](): Promise<void> {\n return this.dispose();\n }\n\n // Fallback для `await using` в Node 18, где `Symbol.asyncDispose` отсутствует.\n static {\n if (\n typeof (Symbol as SymbolConstructor & { asyncDispose?: symbol }).asyncDispose !== 'symbol'\n ) {\n const prototype = ItdClient.prototype as unknown as Record<PropertyKey, unknown>;\n prototype[Symbol.for('Symbol.asyncDispose')] = prototype.undefined;\n delete prototype.undefined;\n }\n }\n\n /** Текущая сессия целиком — чтобы сохранить её самостоятельно. */\n getSession(): Promise<ItdSession | null> {\n return this.#authManager.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.#authManager.getUserId();\n }\n\n /** Восстанавливает сохранённую сессию, включая cookie. */\n setSession(session: ItdSession): Promise<void> {\n return this.#authManager.setSession(session);\n }\n\n /**\n * Придерживает очередь, когда лимит сервера исчерпан.\n *\n * Сервер сообщает остаток в заголовке `x-ratelimit-remaining`. Как только тот доходит\n * до нуля, очередь встаёт на первую паузу лестницы — короткую, потому что окно могло\n * почти истечь. Если оно ещё действует, следующий запрос получит `429`, и дальше\n * лестницу отработает планировщик повторов.\n *\n * Смысл этой паузы прежде всего в том, чтобы при работе в несколько потоков остальные\n * запросы не улетели в стену все разом.\n */\n #throttleByHeaders(\n limit: number | undefined,\n remaining: number | undefined,\n request: PipelineRequest,\n ): void {\n if (remaining === undefined || remaining > 0) return;\n\n const first = this.#config.rateLimit?.retryDelays[0];\n if (first === undefined) return;\n\n this.#queues?.for(request.service).pause(first);\n this.#config.logger?.debug(\n `лимит сервера исчерпан (${remaining} из ${limit ?? '?'}), очередь ждёт ${first} мс`,\n );\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 { assertClientCanUnusePlugin, assertClientCanUsePlugin, ItdClient } from './client.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 } from './core/errors.js';\nimport {\n type ControlledTokenStorage,\n controlledTokenStorage,\n isRestorableSession,\n MemoryMultiTokenStorage,\n type MultiTokenStorage,\n} from './core/multi-storage.js';\nimport { assertPluginRemovable, type ItdPlugin, orderPluginDefinitions } from './core/plugins.js';\nimport { RequestQueuePool } from './core/rate-limit.js';\nimport type { TokenStorage } from './core/storage.js';\nimport type { ItdClientOptions, Logger } from './types/options.js';\n\n/** Как аккаунты делят между собой очередь запросов. */\nexport type RateLimitScope = 'account' | 'shared';\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 ItdPlugin[] | undefined;\n /**\n * Как делить очередь запросов. По умолчанию `'account'` — своя у каждого.\n *\n * Лимиты итд.com считаются по аккаунту, а при работе через разные прокси общая очередь\n * только мешает. Она нужна в другом случае: когда все аккаунты сидят на одном IP\n * и упираются в ограничение по адресу, — тогда `'shared'` разводит их запросы во времени\n * все разом, а не поаккаунтно.\n *\n * Настройки самой очереди берутся из общей опции `rateLimit`. Личный объект `rateLimit`\n * в этом режиме запрещён, потому что не может изменить уже созданную очередь;\n * `rateLimit: false` у отдельного аккаунта выводит его из неё.\n */\n rateLimitScope?: RateLimitScope | undefined;\n}\n\n/**\n * Настройки одного аккаунта. Общее мультихранилище задаёт контейнер, а аккаунт получает\n * свой срез автоматически; остальное — как у `ItdClient`.\n *\n * При `rateLimitScope: 'shared'` объект `rateLimit` задаётся только контейнеру; аккаунту\n * разрешено передать `false`, чтобы не ставить его запросы в общую очередь.\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/**\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 /** Управляемые срезы хранилища: после удаления аккаунт больше не может писать через свой. */\n readonly #storageControls = new Map<string, ControlledTokenStorage>();\n /** Подписки на события клиентов — снимаются вместе с аккаунтом. */\n readonly #eventUnsubscribers = new Map<string, Unsubscribe[]>();\n /** Плагины для всех: и для уже заведённых аккаунтов, и для будущих. */\n readonly #plugins: ItdPlugin[];\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 constructor(options: ItdAccountsOptions = {}) {\n const { storage, plugins, 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.#rateLimitScope = rateLimitScope ?? 'account';\n\n // Общая очередь заводится сразу: проверить опции лучше при создании контейнера,\n // а не при добавлении первого аккаунта.\n const rateLimit =\n this.#rateLimitScope === 'shared' ? resolveRateLimit(base.rateLimit) : 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` — со всеми ресурсами, плагинами и `realtime()`.\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 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 = new ItdClient(\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 } catch (error) {\n storageControl.revoke();\n if (client) {\n void client.dispose().catch((cleanupError: unknown) => {\n this.#reportPluginCleanup('неудачного добавления аккаунта', 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 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 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 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 try {\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 return true;\n } finally {\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: ItdPlugin): 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.#reportPluginCleanup(`отката плагина «${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 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 await Promise.all([...this.#clients.values()].map((client) => client.close()));\n this.#queues?.stop();\n }\n\n /**\n * Окончательно освобождает контейнер и отключает общие плагины у всех аккаунтов.\n *\n * Для временной остановки потоков и очереди без отключения плагинов используйте\n * {@link close}.\n */\n async dispose(): Promise<void> {\n const results = await Promise.allSettled([\n ...[...this.#clients.values()].map((client) => client.dispose()),\n Promise.resolve().then(() => this.#queues?.stop()),\n ]);\n this.#plugins.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 // Fallback для `await using` в Node 18, где `Symbol.asyncDispose` отсутствует, —\n // тот же приём, что в ItdClient.\n static {\n if (\n typeof (Symbol as SymbolConstructor & { asyncDispose?: symbol }).asyncDispose !== 'symbol'\n ) {\n const prototype = ItdAccounts.prototype as unknown as Record<PropertyKey, unknown>;\n prototype[Symbol.for('Symbol.asyncDispose')] = prototype.undefined;\n delete prototype.undefined;\n }\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 /** Не теряет ошибку фонового teardown, который синхронный API не может await-нуть. */\n #reportPluginCleanup(scope: string, error: unknown): void {\n const message = `Не удалось завершить teardown после ${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","import { NotificationType } from '../types/enums.js';\nimport type { Notification } from '../types/models.js';\n\n/** Имя, которое подставляется, если участник неизвестен. */\nconst UNKNOWN_ACTOR = 'Пользователь';\n\n/** Текст, который подставляется для неизвестного типа уведомления. */\nconst FALLBACK_TEXT = 'Новое уведомление';\n\n/**\n * Шаблоны текстов уведомлений.\n *\n * Для каждого типа задано две формы: для одного участника и для схлопнутого уведомления,\n * где действие совершили несколько человек.\n */\nconst TEMPLATES: Readonly<\n Record<string, { one: (name: string) => string; many: (name: string, others: number) => string }>\n> = Object.freeze({\n [NotificationType.Follow]: {\n one: (name) => `${name} подписался(-ась) на вас`,\n many: (name, others) => `${name} и ещё ${others} подписались на вас`,\n },\n [NotificationType.FollowRequest]: {\n one: (name) => `${name} хочет подписаться на вас`,\n many: (name, others) => `${name} и ещё ${others} хотят подписаться на вас`,\n },\n [NotificationType.FollowAccepted]: {\n one: (name) => `${name} принял(а) вашу заявку`,\n many: (name, others) => `${name} и ещё ${others} приняли вашу заявку`,\n },\n [NotificationType.PostReaction]: {\n one: (name) => `${name} оценил(а) ваш пост`,\n many: (name, others) => `${name} и ещё ${others} оценили ваш пост`,\n },\n [NotificationType.PostComment]: {\n one: (name) => `${name} прокомментировал(а) ваш пост`,\n many: (name, others) => `${name} и ещё ${others} прокомментировали ваш пост`,\n },\n [NotificationType.PostRepost]: {\n one: (name) => `${name} сделал(а) репост`,\n many: (name, others) => `${name} и ещё ${others} сделали репост`,\n },\n [NotificationType.CommentReaction]: {\n one: (name) => `${name} оценил(а) ваш комментарий`,\n many: (name, others) => `${name} и ещё ${others} оценили ваш комментарий`,\n },\n [NotificationType.CommentReply]: {\n one: (name) => `${name} ответил(а) на ваш комментарий`,\n many: (name, others) => `${name} и ещё ${others} ответили на ваш комментарий`,\n },\n [NotificationType.PostMention]: {\n one: (name) => `${name} упомянул(а) вас в посте`,\n many: (name, others) => `${name} и ещё ${others} упомянули вас в посте`,\n },\n [NotificationType.CommentMention]: {\n one: (name) => `${name} упомянул(а) вас в комментарии`,\n many: (name, others) => `${name} и ещё ${others} упомянули вас в комментарии`,\n },\n [NotificationType.WallPost]: {\n one: (name) => `${name} написал(а) на вашей стене`,\n many: (name, others) => `${name} и ещё ${others} написали на вашей стене`,\n },\n [NotificationType.VerificationApproved]: {\n one: () => 'Ваша заявка на верификацию одобрена',\n many: () => 'Ваша заявка на верификацию одобрена',\n },\n [NotificationType.VerificationRejected]: {\n one: () => 'Ваша заявка на верификацию отклонена',\n many: () => 'Ваша заявка на верификацию отклонена',\n },\n});\n\n/**\n * Собирает текст уведомления на русском.\n *\n * Повторяет формулировки сайта итд.com. Для неизвестного типа возвращает\n * «Новое уведомление» — библиотека не выдумывает текст, которого нет.\n *\n * @example\n * ```ts\n * formatNotificationText(notification);\n * // 'Аня и ещё 2 оценили ваш пост'\n * ```\n */\nexport function formatNotificationText(notification: Notification): string {\n const template = TEMPLATES[notification.type];\n if (!template) return FALLBACK_TEXT;\n\n const first = notification.actors[0];\n const name = first?.displayName || first?.username || UNKNOWN_ACTOR;\n\n const others = notification.count - 1;\n return others > 0 ? template.many(name, others) : template.one(name);\n}\n","import { NotificationType } from '../types/enums.js';\nimport type { Notification } from '../types/models.js';\n\n/** Типы, ведущие на пост. */\nconst POST_TYPES = new Set<string>([\n NotificationType.PostReaction,\n NotificationType.PostRepost,\n NotificationType.PostMention,\n NotificationType.WallPost,\n]);\n\n/** Типы, ведущие на комментарий внутри поста. */\nconst COMMENT_TYPES = new Set<string>([\n NotificationType.PostComment,\n NotificationType.CommentReaction,\n NotificationType.CommentReply,\n NotificationType.CommentMention,\n]);\n\n/** Типы, ведущие на профиль. */\nconst FOLLOW_TYPES = new Set<string>([\n NotificationType.Follow,\n NotificationType.FollowRequest,\n NotificationType.FollowAccepted,\n]);\n\n/**\n * Вычисляет адрес, на который ведёт уведомление.\n *\n * Возвращает путь внутри сайта — без домена, чтобы его можно было передать роутеру\n * приложения. Поле `clickUrl` от сервера используется только как запасной вариант:\n * вычисленный путь точнее, поскольку учитывает родительский пост у комментариев.\n *\n * @example\n * ```ts\n * const url = resolveNotificationUrl(notification);\n * // '/@nowkie/post/9f1c…?comment=2b7e…'\n * ```\n */\nexport function resolveNotificationUrl(notification: Notification): string {\n const { type, entityId, parentEntityId, clickUrl } = notification;\n const username = notification.actors[0]?.username;\n\n if (username && entityId) {\n if (POST_TYPES.has(type)) return `/@${username}/post/${entityId}`;\n\n if (COMMENT_TYPES.has(type)) {\n return parentEntityId\n ? `/@${username}/post/${parentEntityId}?comment=${entityId}`\n : `/@${username}/post/${entityId}`;\n }\n }\n\n if (username && FOLLOW_TYPES.has(type)) return `/@${username}`;\n\n return clickUrl || '/notifications';\n}\n","import type { Unsubscribe } from '../core/emitter.js';\nimport { ItdConfigError } from '../core/errors.js';\nimport {\n captureRealtimeMiddleware,\n type RealtimeMiddleware,\n runRealtimeMiddleware,\n withRealtimeMiddlewareSnapshot,\n} from './middleware.js';\nimport type { RealtimeContext } from './updates.js';\n\n/** Выбирает маршрут обновления. `undefined` и `null` означают отсутствие маршрута. */\nexport type RealtimeRouteSelector<K extends PropertyKey> = (\n context: RealtimeContext,\n) => K | null | undefined | Promise<K | null | undefined>;\n\ninterface RouteRegistration {\n readonly middleware: readonly RealtimeMiddleware[];\n}\n\n/**\n * Направляет обновления потока в именованные цепочки промежуточных обработчиков.\n *\n * @example\n * ```ts\n * import { RealtimeRouter, RealtimeUpdateType } from 'itd-api';\n *\n * const router = new RealtimeRouter((context) => context.update.type);\n * router.route(RealtimeUpdateType.Notification, async (context, next) => {\n * if (context.update.type === RealtimeUpdateType.Notification) {\n * await handleNotification(context.update.data.notification);\n * }\n * await next();\n * });\n * stream.use(router.middleware());\n * ```\n */\nexport class RealtimeRouter<K extends PropertyKey = PropertyKey> {\n readonly #selector: RealtimeRouteSelector<K>;\n readonly #routes = new Map<K, RouteRegistration[]>();\n readonly #fallback: RouteRegistration[] = [];\n\n constructor(selector: RealtimeRouteSelector<K>) {\n if (typeof selector !== 'function') {\n throw new ItdConfigError('RealtimeRouter принимает функцию выбора маршрута');\n }\n this.#selector = selector;\n }\n\n /** Добавляет промежуточные обработчики к маршруту и возвращает функцию их удаления. */\n route(key: K, ...middleware: readonly RealtimeMiddleware[]): Unsubscribe {\n if (!isPropertyKey(key))\n throw new ItdConfigError('Ключ realtime route должен быть PropertyKey');\n const registration = this.#registration(middleware);\n const registrations = this.#routes.get(key) ?? [];\n registrations.push(registration);\n this.#routes.set(key, registrations);\n\n return () => {\n const current = this.#routes.get(key);\n if (!current) return;\n const index = current.indexOf(registration);\n if (index >= 0) current.splice(index, 1);\n if (current.length === 0) this.#routes.delete(key);\n };\n }\n\n /** Добавляет промежуточные обработчики для обновлений без зарегистрированного маршрута. */\n otherwise(...middleware: readonly RealtimeMiddleware[]): Unsubscribe {\n const registration = this.#registration(middleware);\n this.#fallback.push(registration);\n\n return () => {\n const index = this.#fallback.indexOf(registration);\n if (index >= 0) this.#fallback.splice(index, 1);\n };\n }\n\n /** Возвращает промежуточный обработчик для `stream.use()`. */\n middleware(): RealtimeMiddleware {\n const middleware: RealtimeMiddleware = (context, next) =>\n this.#captureMiddleware()(context, next);\n return withRealtimeMiddlewareSnapshot(middleware, () => this.#captureMiddleware());\n }\n\n #captureMiddleware(): RealtimeMiddleware {\n const routes = new Map<K, readonly RealtimeMiddleware[]>();\n for (const [key, registrations] of this.#routes) {\n routes.set(\n key,\n registrations.flatMap(({ middleware }) => middleware).map(captureRealtimeMiddleware),\n );\n }\n const fallback = this.#fallback\n .flatMap(({ middleware }) => middleware)\n .map(captureRealtimeMiddleware);\n\n return async (context, next) => {\n const key = await this.#selector(context);\n if (key != null && !isPropertyKey(key)) {\n throw new ItdConfigError(\n 'Функция выбора маршрута должна возвращать PropertyKey, null или undefined',\n );\n }\n const route = key == null ? undefined : routes.get(key);\n const chain = route && route.length > 0 ? route : fallback;\n\n if (chain.length === 0) {\n await next();\n return;\n }\n\n await runRealtimeMiddleware(chain, context, next);\n };\n }\n\n #registration(middleware: readonly RealtimeMiddleware[]): RouteRegistration {\n if (middleware.length === 0) {\n throw new ItdConfigError('Маршрут должен содержать хотя бы один обработчик');\n }\n for (const item of middleware) {\n if (typeof item !== 'function') {\n throw new ItdConfigError('Маршрут принимает только функции обработки');\n }\n }\n return { middleware: [...middleware] };\n }\n}\n\nfunction isPropertyKey(value: unknown): value is PropertyKey {\n return typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol';\n}\n","import { SpanType } from '../types/enums.js';\nimport type { Span } from '../types/models.js';\n\n/** Формат результата {@link renderSpans}. */\nexport const SpanRenderFormat = Object.freeze({\n Html: 'html',\n Markdown: 'markdown',\n Ansi: 'ansi',\n} as const);\nexport type SpanRenderFormat = (typeof SpanRenderFormat)[keyof typeof SpanRenderFormat];\n\nexport interface RenderSpansOptions {\n /** Формат результата. По умолчанию `html`. */\n format?: SpanRenderFormat;\n /** Строит адрес упоминания. `null` или `undefined` отключает ссылку. */\n mentionUrl?: (username: string) => string | null | undefined;\n /** Строит адрес хэштега. `null` или `undefined` отключает ссылку. */\n hashtagUrl?: (tag: string) => string | null | undefined;\n /**\n * Префикс HTML-классов. По умолчанию `itd`; пустая строка или `null` отключает классы.\n *\n * Например, `app` создаёт `app-mention`, `app-hashtag`, `app-quote` и `app-spoiler`.\n */\n classPrefix?: string | null;\n}\n\ninterface NormalizedSpan {\n span: Span;\n start: number;\n end: number;\n}\n\nfunction normalizedSpans(\n content: string,\n spans: readonly Span[] | null | undefined,\n): NormalizedSpan[] {\n const result: NormalizedSpan[] = [];\n for (const span of spans ?? []) {\n if (\n !span ||\n !Number.isFinite(span.offset) ||\n !Number.isFinite(span.length) ||\n span.length <= 0\n ) {\n continue;\n }\n\n const start = Math.max(0, Math.trunc(span.offset));\n const end = Math.min(content.length, Math.trunc(span.offset + span.length));\n if (start < end && start < content.length) result.push({ span, start, end });\n }\n return result;\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction safeHttpUrl(value: string | undefined): string | null {\n if (!value) return null;\n try {\n const url = new URL(value);\n return url.protocol === 'http:' || url.protocol === 'https:' ? url.href : null;\n } catch {\n return null;\n }\n}\n\nfunction safeNavigationUrl(value: string | null | undefined): string | null {\n if (!value) return null;\n try {\n const url = new URL(value, 'https://itd.invalid');\n return url.protocol === 'http:' || url.protocol === 'https:' ? value : null;\n } catch {\n return null;\n }\n}\n\nfunction entityValue(span: Span, content: string, kind: 'mention' | 'hashtag'): string {\n const offset = Number.isFinite(span.offset) ? Math.max(0, Math.trunc(span.offset)) : 0;\n const end = Number.isFinite(span.length)\n ? Math.min(content.length, offset + Math.max(0, Math.trunc(span.length)))\n : offset;\n const text = content.slice(offset, end);\n if (kind === 'mention') {\n return span.username ?? span.tag ?? text.replace(/^@/u, '');\n }\n return span.tag ?? text.replace(/^#/u, '');\n}\n\nfunction mentionUrl(username: string, options: RenderSpansOptions): string | null | undefined {\n return options.mentionUrl ? options.mentionUrl(username) : `/@${encodeURIComponent(username)}`;\n}\n\nfunction hashtagUrl(tag: string, options: RenderSpansOptions): string | null | undefined {\n return options.hashtagUrl ? options.hashtagUrl(tag) : `/hashtag/${encodeURIComponent(tag)}`;\n}\n\nfunction classAttribute(name: string, options: RenderSpansOptions): string {\n const prefix = options.classPrefix === undefined ? 'itd' : options.classPrefix;\n return prefix ? ` class=\"${escapeHtml(`${prefix}-${name}`)}\"` : '';\n}\n\nfunction has(active: readonly Span[], type: string): boolean {\n return active.some((span) => span.type === type);\n}\n\nfunction last(active: readonly Span[], type: string): Span | undefined {\n for (let index = active.length - 1; index >= 0; index -= 1) {\n const span = active[index];\n if (span?.type === type) return span;\n }\n return undefined;\n}\n\nfunction renderHtmlSegment(\n text: string,\n active: readonly Span[],\n content: string,\n options: RenderSpansOptions,\n): string {\n let rendered = escapeHtml(text).replace(/\\n/g, '<br>');\n\n if (has(active, SpanType.Bold)) rendered = `<strong>${rendered}</strong>`;\n if (has(active, SpanType.Italic)) rendered = `<em>${rendered}</em>`;\n if (has(active, SpanType.Underline)) rendered = `<u>${rendered}</u>`;\n if (has(active, SpanType.Strike)) rendered = `<s>${rendered}</s>`;\n if (has(active, SpanType.Monospace)) rendered = `<code>${rendered}</code>`;\n if (has(active, SpanType.Quote)) {\n rendered = `<span${classAttribute('quote', options)}>${rendered}</span>`;\n }\n if (has(active, SpanType.Spoiler)) {\n rendered = `<span${classAttribute('spoiler', options)} data-spoiler=\"true\">${rendered}</span>`;\n }\n\n const link = last(active, SpanType.Link);\n const url = safeHttpUrl(link?.url);\n if (url) {\n rendered = `<a href=\"${escapeHtml(url)}\" rel=\"noopener noreferrer\">${rendered}</a>`;\n }\n\n const mention = last(active, SpanType.Mention);\n if (mention) {\n const username = entityValue(mention, content, 'mention');\n const href = safeNavigationUrl(mentionUrl(username, options));\n if (href) {\n rendered = `<a href=\"${escapeHtml(href)}\"${classAttribute('mention', options)}>${rendered}</a>`;\n }\n }\n\n const hashtag = last(active, SpanType.Hashtag);\n if (hashtag) {\n const tag = entityValue(hashtag, content, 'hashtag');\n const href = safeNavigationUrl(hashtagUrl(tag, options));\n if (href) {\n rendered = `<a href=\"${escapeHtml(href)}\"${classAttribute('hashtag', options)}>${rendered}</a>`;\n }\n }\n\n return rendered;\n}\n\nfunction escapeMarkdown(value: string, startsAtLineStart: boolean): string {\n const lines = value.replace(/([\\\\`*_[\\]<>~|&])/g, '\\\\$1').split('\\n');\n return lines\n .map((line, index) => {\n if (index === 0 && !startsAtLineStart) return line;\n return line\n .replace(/^ {0,3}(?=#{1,6}(?:[ \\t]|$))/u, '$&\\\\')\n .replace(/^ {0,3}(?=[+-][ \\t])/u, '$&\\\\')\n .replace(/^(\\s{0,3}\\d{1,9})([.)])(?=[ \\t])/u, '$1\\\\$2')\n .replace(/^(\\s{0,3})(?=-(?:[ \\t]*-){2,}[ \\t]*$)/u, '$1\\\\');\n })\n .join('\\n');\n}\n\nfunction markdownCodeSpan(text: string): string {\n const normalized = text.replace(/\\r\\n?|\\n/g, ' ');\n const longestRun = Math.max(\n 0,\n ...Array.from(normalized.matchAll(/`+/g), (match) => match[0].length),\n );\n const fence = '`'.repeat(longestRun + 1);\n const allSpaces = /^ +$/u.test(normalized);\n const needsPadding =\n normalized.startsWith('`') ||\n normalized.endsWith('`') ||\n (!allSpaces && normalized.startsWith(' ') && normalized.endsWith(' '));\n const body = needsPadding ? ` ${normalized} ` : normalized;\n return `${fence}${body}${fence}`;\n}\n\ninterface RenderSegment {\n start: number;\n text: string;\n active: Span[];\n}\n\nfunction markdownUrl(value: string): string {\n return value.replace(/([\\\\)])/g, '\\\\$1');\n}\n\nfunction renderMarkdownSegment(\n segment: RenderSegment,\n content: string,\n options: RenderSpansOptions,\n): string {\n const { text, active } = segment;\n const startsAtLineStart = segment.start === 0 || content[segment.start - 1] === '\\n';\n let rendered = has(active, SpanType.Monospace)\n ? markdownCodeSpan(text)\n : escapeMarkdown(text, startsAtLineStart);\n if (has(active, SpanType.Bold)) rendered = `**${rendered}**`;\n if (has(active, SpanType.Italic)) rendered = `*${rendered}*`;\n if (has(active, SpanType.Underline)) rendered = `<u>${rendered}</u>`;\n if (has(active, SpanType.Strike)) rendered = `~~${rendered}~~`;\n if (has(active, SpanType.Spoiler)) rendered = `||${rendered}||`;\n\n const link = last(active, SpanType.Link);\n const url = safeHttpUrl(link?.url);\n if (url) rendered = `[${rendered}](${markdownUrl(url)})`;\n\n const mention = last(active, SpanType.Mention);\n if (mention) {\n const username = entityValue(mention, content, 'mention');\n const href = safeNavigationUrl(mentionUrl(username, options));\n if (href) rendered = `[${rendered}](${markdownUrl(href)})`;\n }\n\n const hashtag = last(active, SpanType.Hashtag);\n if (hashtag) {\n const tag = entityValue(hashtag, content, 'hashtag');\n const href = safeNavigationUrl(hashtagUrl(tag, options));\n if (href) rendered = `[${rendered}](${markdownUrl(href)})`;\n }\n\n return rendered;\n}\n\nfunction renderMarkdown(\n segments: readonly RenderSegment[],\n content: string,\n options: RenderSpansOptions,\n): string {\n const groups: { quoted: boolean; text: string }[] = [];\n for (const segment of segments) {\n const quoted = has(segment.active, SpanType.Quote);\n const rendered = renderMarkdownSegment(segment, content, options);\n const previous = groups[groups.length - 1];\n if (previous?.quoted === quoted) previous.text += rendered;\n else groups.push({ quoted, text: rendered });\n }\n\n let result = '';\n for (const [index, group] of groups.entries()) {\n const rendered = group.quoted\n ? group.text\n .split('\\n')\n .map((line) => `> ${line}`)\n .join('\\n')\n : group.text;\n const previous = groups[index - 1];\n if (\n result !== '' &&\n (group.quoted || previous?.quoted) &&\n !result.endsWith('\\n') &&\n !rendered.startsWith('\\n')\n ) {\n result += '\\n';\n }\n result += rendered;\n }\n return result;\n}\n\nfunction renderAnsiSegment(text: string, active: readonly Span[]): string {\n const codes: number[] = [];\n if (has(active, SpanType.Bold)) codes.push(1);\n if (has(active, SpanType.Italic)) codes.push(3);\n if (has(active, SpanType.Underline) || has(active, SpanType.Link)) codes.push(4);\n if (has(active, SpanType.Strike)) codes.push(9);\n if (has(active, SpanType.Spoiler)) codes.push(8);\n if (has(active, SpanType.Quote)) codes.push(2);\n if (has(active, SpanType.Mention) || has(active, SpanType.Hashtag)) codes.push(36);\n\n return codes.length === 0 ? text : `\\u001b[${codes.join(';')}m${text}\\u001b[0m`;\n}\n\n/**\n * Преобразует текст и wire-разметку API в безопасный HTML, Markdown или ANSI.\n *\n * Некорректные серверные spans игнорируются либо обрезаются по границам строки. Пересекающиеся\n * spans разбиваются на независимые сегменты, поэтому HTML остаётся корректно вложенным.\n * Отсутствующий массив считается пустым; формат по умолчанию — HTML.\n */\nexport function renderSpans(\n content: string,\n spans: readonly Span[] | null | undefined = [],\n options: RenderSpansOptions = {},\n): string {\n const normalized = normalizedSpans(content, spans);\n const boundaries = new Set<number>([0, content.length]);\n for (const item of normalized) {\n boundaries.add(item.start);\n boundaries.add(item.end);\n }\n\n const points = [...boundaries].sort((left, right) => left - right);\n const segments: RenderSegment[] = [];\n for (let index = 0; index < points.length - 1; index += 1) {\n const start = points[index];\n const end = points[index + 1];\n if (start === undefined || end === undefined || start === end) continue;\n\n const text = content.slice(start, end);\n const active = normalized\n .filter((item) => item.start <= start && item.end >= end)\n .map((item) => item.span);\n segments.push({ start, text, active });\n }\n\n const format = options.format ?? SpanRenderFormat.Html;\n if (format === SpanRenderFormat.Markdown) return renderMarkdown(segments, content, options);\n\n let result = '';\n for (const segment of segments) {\n switch (format) {\n case SpanRenderFormat.Html:\n result += renderHtmlSegment(segment.text, segment.active, content, options);\n break;\n case SpanRenderFormat.Ansi:\n result += renderAnsiSegment(segment.text, segment.active);\n break;\n }\n }\n\n return result;\n}\n","import type {\n AttachmentType,\n IncidentKind,\n LikesVisibility,\n Loose,\n NotificationType,\n ServiceState,\n SpanType,\n WallAccess,\n} from './enums.js';\n\n/**\n * Дата и время в формате ISO-8601, например `2026-07-21T14:30:00.000Z`.\n *\n * Библиотека не превращает такие поля в `Date`: строку проще сравнивать, логировать\n * и передавать дальше без потерь. Для разбора есть {@link toDate}.\n */\nexport type IsoDate = string;\n\n/**\n * Идентификатор пользователя — **строго UUID**.\n *\n * Отличается от {@link UserRef} тем, что имя пользователя здесь не подойдёт. Так помечены\n * места, где API принимает только UUID: например `wallRecipientId` при постинге на чужую стену.\n */\nexport type UserId = string;\n\n/**\n * Ссылка на пользователя: **UUID либо имя пользователя**.\n *\n * Пути вида `/api/users/{id}` принимают оба варианта, поэтому `itd.users.get('nowkie')`\n * работает так же, как `itd.users.get('9f1c…')`.\n */\nexport type UserRef = string;\n\n/**\n * Разметка в тексте поста или комментария.\n *\n * `offset` и `length` измеряются в UTF-16 code units: это те же индексы, которые используют\n * `String#slice`, `substring` и DOM Selection в JavaScript. Эмодзи вне BMP обычно занимают\n * две единицы.\n */\nexport interface Span {\n /** Тип фрагмента — см. {@link SpanType}. */\n type: SpanType;\n /** Смещение от начала текста. */\n offset: number;\n /** Длина фрагмента. */\n length: number;\n /** Имя хэштега без решётки. У старых mention-объектов может содержать username. */\n tag?: string;\n /** Адрес ссылки. Только у `link`: у него вместо `tag` отдельное поле. */\n url?: string;\n /** Имя пользователя у `mention`. */\n username?: string;\n /** Идентификатор пользователя у некоторых ответов API с `mention`. */\n id?: string;\n}\n\n/**\n * Значок-«пин» в профиле — награда или отметка платформы.\n */\nexport interface Pin {\n /** Постоянный идентификатор, например `epepuy_202605_59`. */\n slug: string;\n /** Отображаемое название. */\n name: string;\n /** Описание, за что выдан. */\n description: string;\n /** Адрес изображения. */\n url: string;\n /** Когда выдан. Приходит только в списке своих пинов. */\n grantedAt?: IsoDate;\n}\n\n/**\n * Автор поста или комментария.\n *\n * Встречается внутри `post.author` и `comment.author`.\n */\nexport interface Author {\n id: UserId;\n username: string;\n displayName: string;\n /**\n * **Эмодзи, а не картинка.**\n *\n * На итд.com аватар — это символ клана (`🩵`, `🦎`), а не адрес изображения.\n * Отрисовывать его нужно как текст.\n */\n avatar: string;\n /** Пройдена ли верификация. */\n verified: boolean;\n /** Активный значок профиля. Может отсутствовать. */\n pin?: Pin | null;\n /** Есть ли премиум-подписка (значок NUKSTA). */\n hasNuksta?: boolean;\n}\n\n/**\n * Участник события в уведомлении.\n *\n * Отличается от {@link Author} набором полей: вместо значков приходит связь с вами.\n */\nexport interface Actor {\n id: UserId;\n username: string;\n displayName: string;\n /** Эмодзи-аватар, см. {@link Author.avatar}. */\n avatar: string;\n /** Подписаны ли вы на этого пользователя. */\n isFollowing?: boolean;\n /** Подписан ли он на вас. */\n isFollowedBy?: boolean;\n}\n\n/**\n * Пользователь в списках.\n *\n * Набор полей зависит от эндпоинта: подписчики и подписки приносят `isFollowing`,\n * поиск и рекомендации — `followersCount` и `hasNuksta`. Необязательные поля отражают\n * это различие.\n */\nexport interface UserSummary {\n id: UserId;\n username: string;\n displayName: string;\n /** Эмодзи-аватар, см. {@link Author.avatar}. */\n avatar: string;\n verified: boolean;\n /** Подписаны ли вы. Приходит в списках подписчиков и подписок. */\n isFollowing?: boolean;\n /** Есть ли премиум. Приходит в поиске и рекомендациях. */\n hasNuksta?: boolean;\n /** Число подписчиков. Приходит в поиске и рекомендациях. */\n followersCount?: number;\n}\n\n/** Поля профиля, общие для своего и чужого. */\ninterface ProfileBase {\n id: UserId;\n username: string;\n displayName: string;\n /** Эмодзи-аватар, см. {@link Author.avatar}. */\n avatar: string;\n /** URL изображения баннера либо `null`. */\n banner: string | null;\n /** Описание профиля. */\n bio: string;\n verified: boolean;\n pin?: Pin | null;\n /** Кто может писать на стену. */\n wallAccess: WallAccess;\n /** Кто видит реакции. */\n likesVisibility: LikesVisibility;\n followersCount: number;\n followingCount: number;\n postsCount: number;\n createdAt: IsoDate;\n}\n\n/** Состояние подписки на премиум. */\nexport interface SubscriptionState {\n isActive: boolean;\n expiresAt: IsoDate | null;\n autoRenewal: boolean;\n}\n\n/**\n * Свой профиль — ответ `GET /api/users/me`.\n *\n * Отличается от чужого наличием {@link subscription} и {@link isPhoneVerified}\n * и отсутствием полей связи (`isFollowing`, `online`).\n */\nexport interface MyProfile extends ProfileBase {\n /** Закрыт ли профиль. */\n isPrivate: boolean;\n /** Подтверждён ли телефон. Без него часть действий недоступна. */\n isPhoneVerified: boolean;\n /** Своя премиум-подписка. */\n subscription: SubscriptionState;\n}\n\n/**\n * Состояние авторизации — ответ `GET /api/profile`.\n *\n * Endpoint доступен без сессии: в этом случае `authenticated` равен `false`,\n * а `user` — `null`.\n */\nexport interface AuthState {\n /** Есть ли действующая сессия. */\n authenticated: boolean;\n /** Заблокирован ли текущий аккаунт. */\n banned: boolean;\n /** Текущий пользователь либо `null` без действующей сессии. */\n user: MyProfile | null;\n}\n\n/**\n * Чужой профиль — ответ `GET /api/users/{id|username}`.\n *\n * Вместо своей подписки содержит связь с вами и присутствие.\n */\nexport interface PublicProfile extends ProfileBase {\n hasNuksta?: boolean;\n /** Закреплённый пост, если он есть. */\n pinnedPostId: string | null;\n /** Подписаны ли вы на него. */\n isFollowing: boolean;\n /** Подписан ли он на вас. */\n isFollowedBy: boolean;\n /** Сейчас ли пользователь в сети. */\n online: boolean;\n /** Когда был в сети. `null`, если скрыто настройками приватности. */\n lastSeen: IsoDate | null;\n}\n\n/** Профиль: свой либо чужой. Различаются функцией {@link isMyProfile}. */\nexport type Profile = MyProfile | PublicProfile;\n\n/**\n * Свой ли это профиль.\n *\n * @example\n * ```ts\n * if (isMyProfile(profile)) console.log(profile.subscription.isActive);\n * ```\n */\nexport function isMyProfile(profile: Profile): profile is MyProfile {\n return 'subscription' in profile;\n}\n\n/** Вложение поста или комментария. */\nexport interface Attachment {\n id: string;\n type: AttachmentType;\n /** Адрес файла на CDN. */\n url: string;\n /** Ширина изображения или видео в пикселях. */\n width?: number;\n /** Высота изображения или видео в пикселях. */\n height?: number;\n mimeType: string;\n /** Исходное имя файла. Приходит не всегда. */\n filename?: string;\n /** Размер в байтах. Приходит не всегда. */\n size?: number;\n /** Длительность аудио или видео в секундах. */\n duration?: number | null;\n /** Порядковый номер во вложениях поста. */\n order?: number;\n}\n\n/** Вариант ответа в опросе. */\nexport interface PollOption {\n id: string;\n text: string;\n /** Сколько голосов отдано за этот вариант. */\n votesCount: number;\n /** Порядковый номер варианта, начиная с нуля. */\n position: number;\n}\n\n/** Опрос внутри поста. */\nexport interface Poll {\n id: string;\n /** Пост, которому принадлежит опрос. */\n postId: string;\n question: string;\n /** Можно ли выбрать несколько вариантов. */\n multipleChoice: boolean;\n options: PollOption[];\n totalVotes: number;\n /** Голосовали ли вы. */\n hasVoted: boolean;\n /** За что проголосовали вы. Пустой массив, если голоса не было. */\n votedOptionIds: string[];\n createdAt: IsoDate;\n}\n\n/** Пост ленты, стены или профиля. */\nexport interface Post {\n id: string;\n content: string;\n /** Разметка текста. Передаётся без изменений, см. {@link Span}. */\n spans: Span[];\n author: Author;\n attachments: Attachment[];\n likesCount: number;\n commentsCount: number;\n repostsCount: number;\n viewsCount: number;\n /** Чья это стена, если пост опубликован не у себя. */\n wallRecipientId: UserId | null;\n /** Владелец стены. Приходит не во всех ответах. */\n wallRecipient?: Author | null;\n /** Поставили ли вы реакцию. */\n isLiked: boolean;\n /** Делали ли вы репост. */\n isReposted: boolean;\n /** Засчитан ли просмотр. */\n isViewed: boolean;\n /** Ваш ли это пост. */\n isOwner: boolean;\n /** Исходный пост, если это репост. */\n originalPost?: Post | null;\n poll?: Poll | null;\n /** Преобладающая реакция — эмодзи либо `null`. */\n dominantEmoji?: string | null;\n /** Когда пост отредактировали. `null`, если не редактировали. */\n editedAt: IsoDate | null;\n createdAt: IsoDate;\n /**\n * Служебная метка показа для телеметрии.\n *\n * Нужна только эндпоинтам `itd.telemetry.*`. В остальных случаях игнорируйте.\n */\n vs?: string;\n /**\n * Топовые комментарии. Приходят только в ответе `GET /api/posts/{id}`.\n *\n * В списках постов поле отсутствует.\n */\n comments?: Comment[];\n}\n\n/** На чей комментарий дан ответ. */\nexport interface CommentReplyTo {\n id: string;\n username: string;\n displayName: string;\n}\n\n/** Комментарий к посту или ответ на комментарий. */\nexport interface Comment {\n id: string;\n /** Текст. У голосового комментария пустой. */\n content: string;\n /**\n * Разметка текста, включая автоматически найденные сервером хэштеги и упоминания.\n *\n * Методы создания и редактирования комментария принимают только `content`, поэтому\n * библиотека не отправляет ручные spans в этих операциях.\n * Поле необязательно: отдельные ответы сервера могут его не содержать.\n */\n spans?: Span[];\n author: Author;\n likesCount: number;\n repliesCount: number;\n isLiked: boolean;\n createdAt: IsoDate;\n /** Вложения. У голосового — одно аудио с `mimeType: 'audio/ogg'`. */\n attachments?: Attachment[];\n /** Вложенные ответы. В списках приходит превью, полный список — через `itd.comments.replies()`. */\n replies?: Comment[];\n /** Заполнено только у ответов. */\n replyTo?: CommentReplyTo;\n}\n\n/**\n * Уведомление в единой форме.\n *\n * REST-список и SSE-поток отдают уведомления по-разному — разные имена типов, разные имена\n * полей, один участник против массива. Библиотека приводит оба вида к этой структуре,\n * поэтому объекты из `itd.notifications.list()` и из потока можно складывать в один список.\n *\n * Исходные данные не теряются: сервeрное имя типа остаётся в {@link rawType},\n * а весь необработанный объект — в {@link raw}.\n */\nexport interface Notification {\n id: string;\n /** Канонический тип. Старые имена (`like`, `comment`) приведены к новым. */\n type: NotificationType;\n /** Имя типа в том виде, в каком его прислал сервер. */\n rawType: string;\n /** Объект события: пост, комментарий, пользователь. */\n entityId: string | null;\n /** Пост, которому принадлежит комментарий, если событие о комментарии. */\n parentEntityId: string | null;\n /** Прочитано ли уведомление. */\n isRead: boolean;\n /** Кто совершил действие. Для схлопнутых уведомлений — несколько человек. */\n actors: Actor[];\n /** Сколько участников схлопнуто в одно уведомление. Минимум 1. */\n count: number;\n /** Текст или заголовок объекта события. */\n preview: string | null;\n /** Ссылка перехода, предложенная сервером. Обычно точнее её `resolveNotificationUrl()`. */\n clickUrl?: string;\n createdAt: IsoDate;\n /** Когда уведомление изменилось — например было прочитано. */\n updatedAt: IsoDate;\n /** Исходный объект как он пришёл от сервера. */\n raw: unknown;\n}\n\n/** Настройки приватности профиля. */\nexport interface PrivacySettings {\n /** Закрыт ли профиль: подписка требует одобрения. */\n isPrivate: boolean;\n wallAccess: WallAccess;\n likesVisibility: LikesVisibility;\n /** Показывать ли время последнего посещения. */\n showLastSeen: boolean;\n}\n\n/**\n * Настройки уведомлений.\n *\n * Сервер отдаёт плоский объект, но исторически знает два набора имён для одних и тех же\n * настроек (`likes` и `reactions`, `comments` и `replies`). При сохранении библиотека\n * отправляет оба, при чтении принимает любой.\n */\nexport interface NotificationSettings {\n /** Общий выключатель доставки. */\n enabled: boolean;\n /** Звук уведомления. */\n sound: boolean;\n /** Новые подписчики. */\n follows: boolean;\n /** Записи на вашей стене. */\n wallPosts: boolean;\n /** Реакции на ваши записи. */\n likes: boolean;\n /** Комментарии и ответы. */\n comments: boolean;\n /** Упоминания. */\n mentions: boolean;\n}\n\n/** Активная сессия входа. */\nexport interface Session {\n id: string;\n /** Та ли это сессия, из которой выполнен запрос. */\n isCurrent: boolean;\n createdAt: IsoDate;\n lastUsedAt: IsoDate;\n expiresAt: IsoDate;\n ipAddress: string;\n /** Код страны по IP, например `RU`. */\n ipCountry: string | null;\n ipCity: string | null;\n deviceType: Loose<'desktop' | 'mobile'>;\n osName: string | null;\n osVersion: string | null;\n /** Название браузера или приложения. */\n clientName: string | null;\n clientVersion: string | null;\n deviceModel: string | null;\n}\n\n/** Состояние платной подписки и её цена. */\nexport interface Subscription {\n /** Активна ли подписка сейчас. */\n active: boolean;\n /** Включено ли автопродление. */\n recurringEnabled: boolean;\n /** Цена в рублях. */\n price: number;\n}\n\n/** Сохранённый способ оплаты. */\nexport interface PaymentMethod {\n id: string;\n /** Последние четыре цифры карты. */\n last4?: string;\n /** Платёжная система: `visa`, `mastercard`, `mir`. */\n brand?: string;\n /** Основной ли это способ оплаты. */\n isDefault?: boolean;\n expiresAt?: IsoDate | null;\n}\n\n/** Хэштег. */\nexport interface Hashtag {\n id: string;\n /** Название без решётки. */\n name: string;\n /** Сколько постов с этим хэштегом. */\n postsCount: number;\n}\n\n/** Клан в рейтинге. */\nexport interface Clan {\n /** Эмодзи клана — оно же аватар его участников. */\n avatar: string;\n memberCount: number;\n}\n\n/**\n * Результат подписки на пользователя.\n *\n * @example\n * ```ts\n * const result = await itd.users.follow('nowkie');\n * // { following: true, followersCount: 11 }\n * ```\n */\nexport interface FollowResult {\n /** Подписка оформлена. У закрытого профиля отправляется заявка, и здесь будет `false`. */\n following: boolean;\n /** Сколько подписчиков стало у пользователя после действия. */\n followersCount?: number;\n /** Статус заявки, если профиль закрыт. */\n status?: Loose<'following' | 'requested'>;\n}\n\n/** Запись журнала изменений платформы. */\nexport interface ChangelogEntry {\n version: string;\n date: string;\n changes: string[];\n}\n\n/** Кнопка в анонсе платформы. */\nexport interface AnnouncementButton {\n title: string;\n /** Оформление: `primary`, `secondary` и другие. */\n style: string;\n action: { type: string; [key: string]: unknown };\n}\n\n/** Анонс на главной странице платформы. */\nexport interface Announcement {\n id: string;\n image: { url: string; width: number; height: number };\n title: string;\n description: string;\n /** Дополнительный текст мелким шрифтом. */\n additional_text?: string;\n buttons: AnnouncementButton[];\n}\n\n/** Баннер текущего события — виджет «портал». */\nexport interface Portal {\n active: boolean;\n title: string;\n url: string;\n}\n\n/** Глубина истории статуса в сутках. Столько элементов отдаёт {@link statusDays}. */\nconst STATUS_WINDOW_DAYS = 90;\n\n/** Происшествие в истории сервиса. */\nexport interface StatusIncidentLine {\n /** Вид происшествия. */\n t: IncidentKind;\n /**\n * Готовая строка для показа: `недоступен 6 мин (12:00–12:06)`. Время московское.\n * Длительность и границы интервала отдельными полями не приходят.\n */\n text: string;\n}\n\n/** Одни сутки в истории сервиса. */\nexport interface StatusDay {\n /** Худшее состояние за сутки. */\n type: ServiceState;\n /** Дата суток, `YYYY-MM-DD`. Сутки нарезаны по UTC. */\n date_key: string;\n /** Доступность за сутки в процентах. */\n uptime: number;\n /** Происшествия за сутки. */\n lines: StatusIncidentLine[];\n}\n\n/** Сервис платформы и его история доступности. */\nexport interface ServiceStatus {\n /** Идентификатор: `auth`, `main`, `media` и прочие. */\n id: string;\n /** Отображаемое название. */\n name: string;\n current_status: ServiceState;\n /** Пояснение к текущему состоянию, например `No downtime`. */\n current_message: string;\n /** Задержка последней проверки в миллисекундах. */\n latency_ms: number;\n /**\n * Момент последней проверки. Сервер отдаёт `YYYY-MM-DD HH:mm:ss` в UTC, библиотека\n * приводит значение к ISO.\n */\n last_checked: IsoDate;\n /** Доступность за 90 суток в процентах. */\n uptime_90d: number;\n /**\n * История по суткам. Ключ — сколько суток назад, `'0'` — сегодня.\n *\n * Объект разреженный: сутки без данных сервер пропускает. Ровный массив даёт\n * {@link statusDays}.\n */\n days: Record<string, StatusDay | undefined>;\n}\n\n/** Состояние платформы — ответ `itd.platform.status()`. */\nexport interface PlatformStatus {\n /** Худшее состояние среди сервисов. */\n overall_status: ServiceState;\n /** Когда данные последний раз пересчитаны. */\n updated_at: IsoDate;\n services: ServiceStatus[];\n}\n\n/** Статус заявки на верификацию. `none` означает, что заявка не подавалась. */\nexport interface VerificationStatus {\n status: Loose<'none' | 'pending' | 'approved' | 'rejected'>;\n}\n\n/** Созданная жалоба. */\nexport interface Report {\n id: string;\n createdAt: IsoDate;\n}\n\n/** Счётчики поста из `itd.posts.stats()`. */\nexport interface PostStats {\n id: string;\n likesCount: number;\n commentsCount: number;\n repostsCount: number;\n viewsCount: number;\n /** Преобладающая реакция — эмодзи либо `null`. */\n dominantEmoji: string | null;\n}\n\n/** Результат реакции на пост. */\nexport interface LikeResult {\n liked: boolean;\n likesCount: number;\n}\n\n/** Результат закрепления поста в профиле. */\nexport interface PinPostResult {\n success: boolean;\n pinnedPostId: string | null;\n}\n\n/** Закреплённые значки профиля и выбранный из них. */\nexport interface PinsResult {\n pins: Pin[];\n /** Идентификатор активного значка — строка, а не объект. */\n activePin: string | null;\n}\n\n/**\n * Разбирает дату API в объект `Date`.\n *\n * @returns `null`, если строки нет или она не разбирается\n *\n * @example\n * ```ts\n * const created = toDate(post.createdAt);\n * ```\n */\nexport function toDate(value: IsoDate | null | undefined): Date | null {\n if (!value) return null;\n const date = new Date(value);\n return Number.isFinite(date.getTime()) ? date : null;\n}\n\n/**\n * Разворачивает историю сервиса в массив на 90 суток.\n * Сутки без данных становятся `null`.\n *\n * @returns массив, где индекс — сколько суток назад: `[0]` — сегодня\n *\n * @example\n * ```ts\n * const status = await itd.platform.status();\n * const days = statusDays(status.services[0]);\n *\n * days[0]?.uptime; // доступность за сегодня\n * days.filter((day) => day === null).length; // за сколько суток данных нет\n * ```\n */\nexport function statusDays(service: ServiceStatus): (StatusDay | null)[] {\n return Array.from(\n { length: STATUS_WINDOW_DAYS },\n (_, index) => service.days[String(index)] ?? null,\n );\n}\n"],"x_google_ignoreList":[23],"mappings":"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"}
|