Клиент — ItdClient
Точка входа. Группирует ресурсы (itd.posts, itd.users, …) и берёт на себя авторизацию, обновление токена, повторы и очередь запросов. Достаточно одного экземпляра на приложение. Практические рекомендации — в руководстве по конфигурации.
new ItdClient(options?: ItdClientOptions)
createClient(options?: ItdClientOptions): ItdClient // то же самое, фабрикаКласс один на всех платформах и не требует платформенной настройки: хранилище задаётся опцией storage, а вложения объявляют источник сами — см. Формы вложений и Точки входа.
Ресурсы
| Свойство | Ресурс |
|---|---|
itd.auth | Авторизация |
itd.users | Пользователи |
itd.posts | Посты |
itd.comments | Комментарии |
itd.files | Файлы |
itd.notifications | Уведомления |
itd.hashtags, itd.search | Поиск и обнаружение |
itd.reports | Жалобы |
itd.verification | Верификация |
itd.subscription | Подписка |
itd.platform | Платформа |
itd.telemetry | телеметрия просмотров |
Методы
get baseUrl: stringБазовый URL, к которому обращается клиент.
request<T = unknown>(options: RawRequestOptions): Promise<T>Произвольный запрос к API — запасной путь, когда нужного метода нет. Проходит через ту же авторизацию, очередь и обработку ошибок. С raw: true возвращает тело без снятия обёртки { data: … }.
use(plugin: ClientPlugin): this
pluginNames(): string[]
hasPlugin(name: string): boolean
unuse(name: string): Promise<boolean>Подключает плагин — обёртку вокруг запроса и нормализованного результата публичного метода сразу для всех ресурсов. Пакеты, поддерживаемые проектом itd-api: @itd-api/cache, @itd-api/crypto. Остальные методы показывают фактический порядок плагинов, проверяют наличие и отключают плагин с запуском его функции очистки.
defineService(definition: ServiceDefinition): this
serviceBaseUrl(name: string): stringРегистрирует / читает домен платформы, отличный от основного (запросы с { service: 'имя' }). Bearer-токен по умолчанию уходит только на основной хост и его поддомены.
interface ServiceDefinition {
name: string;
baseUrl: string;
headers?: Record<string, string>;
auth?: boolean;
}itd.defineService({
name: 'example',
baseUrl: 'https://api.example.com',
headers: { 'X-Client': 'my-app' },
auth: false,
});
await itd.request({ method: 'GET', service: 'example', path: '/health' });Для внешнего хоста Bearer разрешается только явным auth: true. Зарегистрированное имя нельзя заменить другим определением. Очередь выбирается по итоговому URL.origin: два имени одного хоста используют общий лимитер, а разные хосты — разные.
install<TApi>(feature: ClientFeature<TApi>): TApi
withFeature<K extends string, TApi>(key: K, feature: ClientFeature<TApi>): this & Readonly<Record<K, TApi>>
featureNames(): string[]
hasFeature(name: string): booleanУстанавливает предметный модуль поверх общей сессии и конвейера запросов. install() возвращает его типизированный API, а withFeature() — тот же клиент со свойством модуля, например itd.withFeature('pixelBattle', pixelBattleFeature).pixelBattle. Остальные методы перечисляют установленные модули и проверяют имя; встроенный status также виден в реестре.
readonly notifications: NotificationsApi // notifications.events: NotificationEventsREST-методы и один ленивый стабильный канал уведомлений. Настройки канала задаются через new ItdClient({ events: { notifications: options } }). См. события уведомлений.
on<K>(event: K, listener): UnsubscribeПодписывается на события авторизации. Возвращает функцию отписки.
close(): Promise<void>
dispose(): Promise<void>close() закрывает потоки уведомлений, отправляет и закрывает открытые накопители телеметрии, затем временно останавливает очередь; после него клиентом можно пользоваться снова. Ошибка отправки телеметрии отклоняет close(), а неотправленные записи остаются доступны для повторного flush(). dispose() дополнительно отключает плагины, освобождает их ресурсы и терминально закрывает клиент. После него новые запросы, use(), defineService() и повторный connect() существующего канала событий завершаются с ItdStateError. Уже накопленную телеметрию dispose() всё же отправляет: эта отправка проходит через ещё установленные плагины до их отключения. Повторный dispose() возвращает тот же результат очистки. await using вызывает dispose().
Оба метода ждут чужой код не дольше shutdownTimeout; по истечении срока ресурсы всё равно освобождаются, а метод отклоняется ItdStateError с именем плагина или транспорта потока, который удерживал остановку. dispose() вдобавок отменяет незавершённые запросы — они получают ItdAbortError.
getSession(): Promise<ItdSession | null>
setSession(session: ItdSession): Promise<void>
getUserId(): Promise<UserId | undefined>Читает / восстанавливает текущую сессию целиком и идентификатор аккаунта из токена (без запроса).
⚠️
getUserId()только декодирует JWT и не проверяет его подпись. Используйте результат для локального разделения состояния, но не как доказательство аутентификации.
События
Метод itd.on(event, listener) — ключи AuthEvents:
| Событие | Данные | Когда |
|---|---|---|
tokens | { accessToken } | токен получен или обновлён |
signIn | { accessToken } | выполнен вход |
signOut | — | сессия очищена |
authError | { error } | обновить сессию не удалось; запросы будут падать с 401 |
Опции конструктора
interface ItdClientOptions {
baseUrl?: string; // по умолчанию https://xn--d1ah4a.com
services?: Record<string, string | Omit<ServiceDefinition, 'name'>>;
auth?: AuthInput; // см. ниже
captcha?: CaptchaSolverInput; // откуда брать токен капчи, когда сервер её требует
storage?: TokenStorage; // по умолчанию MemoryTokenStorage
autoRefresh?: boolean; // обновлять токен заранее и при 401; по умолчанию true
reloginOnRefreshFailure?: boolean; // повторный вход при неудаче refresh; по умолчанию true
fetch?: typeof fetch; // своя реализация: Deno, RN, тесты, прокси
timeout?: number; // по умолчанию 30000; 0 — без ограничения
shutdownTimeout?: number; // сколько close()/dispose() ждут чужой код; 10000
retry?: RetryOptions | false;
rateLimit?: RateLimitOptions | false;
hooks?: ClientHooks;
logger?: Logger | boolean; // true — писать в консоль (токены маскируются)
headers?: Record<string, string>;
deviceId?: string; // X-Device-Id; стабильный; иначе заведётся сам
userAgent?: string | false; // false — не слать; в браузере не действует
mode?: RuntimeMode; // как обращаться с файлами cookie
}Авторизация (AuthInput)
interface CredentialsAuth {
email: string;
password: string;
captcha?: CaptchaToken; // разовый токен: тратится на один вход
}
// Опция клиента `captcha` — источник токенов, а не готовый токен.
type CaptchaSolverInput = CaptchaSolver | ((type: CaptchaType) => string | Promise<string>);
interface CaptchaSolver {
getToken(type: CaptchaType): string | Promise<string>;
type?: CaptchaChoice; // 'auto' (по умолчанию) | 'itd' | 'cloudflare'
field?: CaptchaField; // только при явном type
}
type AuthInput =
| string // готовый accessToken
| { accessToken: string; refreshToken?: string } // восстановить сессию
| CredentialsAuth // залогиниться самому
| { getToken: () => string | null | Promise<string | null> }; // токен извнеПовторы (RetryOptions)
interface RetryOptions {
attempts?: number; // всего попыток, включая первую; по умолчанию 3
baseDelay?: number; // базовая пауза, удваивается; по умолчанию 500
maxDelay?: number; // верхняя граница; по умолчанию 30000
jitter?: number; // разброс 0…1; по умолчанию 0.3
shouldRetry?: (
error: unknown,
attempt: number,
context: RetryDecisionContext,
) => boolean;
}Каждая встроенная операция имеет явную RetrySafety: Safe, Idempotent или Unsafe. Первые две повторяются после сетевого сбоя, таймаута или 5xx; Unsafe — нет, потому что сервер мог выполнить действие до обрыва. Политика определяется смыслом операции, а не HTTP-методом: безопасный POST повторяется, тогда как создающий POST — нет.
429 обрабатывается отдельно и повторяется даже для записи, поскольку операция при таком ответе не выполнена. Любой повтор требует заново создаваемого тела запроса. shouldRetry заменяет стандартное семантическое решение для обычных ошибок и получает operationId, retrySafety, bodyReplayable, method и path; отмену и одноразовое тело он переопределить не может.
Очередь и лимиты (RateLimitOptions)
interface RateLimitOptions {
// Пропускная способность
concurrency?: number; // одновременных запросов; по умолчанию 6
rps?: number; // верхняя граница запросов в секунду
// Раздельные счётчики
buckets?: boolean; // очередь на бакет; по умолчанию true
bucketConcurrency?: number; // предел внутри бакета; по умолчанию как concurrency
bucketOverrides?: Record<string, { concurrency?: number; rps?: number; limit?: number }>;
bucket?: (request: RateLimitBucketContext) => string | undefined;
// Реакция на исчерпанный лимит
pacing?: RateLimitPacing; // 'react' | 'smooth' | 'off'; по умолчанию 'react'
retryDelays?: readonly number[]; // паузы при 429; [1000, 5000, 30000, 60000, 90000]
}concurrency ограничивает число одновременных запросов, а rps — их темп. Для общего лимита всего приложения используйте один экземпляр ItdClient.
Очередь выбирается по паре «URL.origin — бакет», уже после разрешения service и baseUrl. Разные имена одного адреса делят ограничения одновременности, частоты и паузы; разовый baseUrl использует очередь своего адреса, а не основного API.
bucketConcurrency по умолчанию равен concurrency. Единственное встроенное исключение — files.upload с пределом 1.
bucketOverrides правит лимит, темп и одновременность отдельных бакетов; неизвестное имя — ошибка конфигурации, как и в rateLimitBucket у отдельного запроса. bucket заменяет само правило выбора: верните undefined, чтобы отдать запрос встроенной карте. Своё правило заводит собственное пространство имён и снимает проверку имён в обеих опциях.
pacing выбирает реакцию на остаток. react не задерживает ничего, пока в бакете есть запас, и придерживает исчерпанный бакет на 60000 / limit миллисекунд. smooth держит ровный темп в пределах лимита бакета. off оставляет только паузу после 429.
buckets: false оставляет одну очередь на origin. Ёмкость отдельного счётчика в этом режиме неизвестна, поэтому bucketConcurrency, все поля bucketOverrides и pacing: 'smooth' не действуют, а исчерпанный остаток встречается первой ступенью retryDelays.
Время сброса окна сервер не сообщает, поэтому при 429 клиент идёт по лестнице пауз: 1, 5, 30, 60 и 90 секунд. После последней ступени выбрасывается ItdRateLimitError; значения заголовков доступны в его rateLimit и rateLimitRemaining.
Таблица лимитов — в «Ограничениях частоты».
Снимок лимитов (rateLimitState())
itd.rateLimitState(): RateLimitBucketState[]Отдаёт по записи { destination, bucket, limit, remaining, active, pending } на каждый бакет, через который уже проходили запросы. limit и remaining берутся из последнего ответа и быстро устаревают. active считает фактически запущенные запросы бакета; pending — ещё не запущенные запросы, ожидающие общего или локального ограничения. Пустой массив, если rateLimit: false.
Хуки (ClientHooks)
interface ClientHooks {
onRequest?(ctx: RequestContext): void | Promise<void>; // до отправки, headers изменяемы
onResponse?(ctx: ResponseContext): void | Promise<void>; // после успеха, до разбора тела
onError?(ctx: ErrorContextHook): void | Promise<void>; // при любой ошибке запроса
onRetry?(ctx: RetryContext): void | Promise<void>; // перед паузой между попытками
}Произвольный запрос (RawRequestOptions)
interface RawRequestOptions extends RequestOptions {
operationId?: OperationId; // `raw` по умолчанию; свои operationId — `custom:*`
method: string;
path: string; // с ведущим слэшем; завершающий слэш значим
service?: string; // хост зарегистрированного сервиса
baseUrl?: string; // хост этого запроса; важнее service
query?: QueryParams;
body?: unknown; // JSON; для файлов — FormData
skipAuth?: boolean; // не подставлять токен; false — разрешить внешнему хосту
skipAuthRefresh?: boolean; // не обновлять токен заранее и при 401
skipQueue?: boolean; // мимо очереди
raw?: boolean; // вернуть тело без снятия обёртки { data }
}Встроенные ресурсы передают постоянный operationId автоматически; плагины и хуки могут использовать его вместо разбора URL. Произвольный itd.request() без идентификатора операции получает raw, поэтому встроенные плагины не принимают совпавший путь за известную операцию. Для интеграций используйте пространство имён custom:*; встроенный идентификатор операции означает, что запрос соответствует этой операции.
raw-запросы с GET, HEAD и OPTIONS считаются безопасными для совместимости. custom:* по умолчанию всегда Unsafe, даже при GET: задайте retrySafety: RetrySafety.Safe или RetrySafety.Idempotent, если это гарантирует контракт интеграции. Глобального переключателя для повтора всех записей намеренно нет.
Каталог встроенных операций доступен программно — это тот же источник, которым пользуются ресурсы, повторы и плагины:
import { isBuiltInOperationId, OPERATIONS, operationMethod, operationRetrySafety } from 'itd-api';
Object.keys(OPERATIONS).length; // все встроенные operationId
operationMethod('posts.create'); // 'POST'
operationRetrySafety('posts.stats'); // 'safe'
isBuiltInOperationId('posts.create'); // trueservice выбирает зарегистрированный хост. baseUrl имеет более высокий приоритет и задаёт хост только этому запросу. Для внешнего baseUrl авторизация выключена автоматически; skipAuth: false явно разрешает отправить текущий Bearer-токен.
type QueryValue =
| string
| number
| boolean
| null
| undefined
| readonly (string | number | boolean)[];
type QueryParams = Record<string, QueryValue>;null и undefined пропускаются, массив записывается повторяющимися параметрами.