Конфигурация клиента
Практическое руководство по таймаутам, повторам, очередям, дополнительным сервисам, собственному fetch и жизненному циклу ItdClient. Полный список полей находится в справочнике клиента.
Один клиент на приложение
Обычно приложению достаточно одного экземпляра:
import { ItdClient } from 'itd-api';
export const itd = new ItdClient({
auth: process.env.ITD_TOKEN,
timeout: 30_000,
retry: { attempts: 3 },
rateLimit: { concurrency: 4, rps: 8 },
});Так авторизация, файлы cookie, обновление токена и темп запросов управляются в одном месте. Для нескольких учётных записей используйте ItdAccounts.
Таймаут и отмена
Глобальный таймаут по умолчанию — 30 секунд. 0 отключает его. Загрузка файлов использует отдельное значение — 5 минут.
import { ItdAbortError, ItdClient } from 'itd-api';
const itd = new ItdClient({ timeout: 20_000 });
const controller = new AbortController();
const request = itd.posts.list(
{ tab: 'popular' },
{ signal: controller.signal, timeout: 5_000 },
);
controller.abort();
try {
await request;
} catch (error) {
if (!(error instanceof ItdAbortError)) throw error;
}signal, timeout, дополнительные заголовки и локальная настройка повторов retry доступны последним аргументом почти каждого метода. Отмена возвращается как ItdAbortError, таймаут — как ItdTimeoutError.
Повторные попытки
По умолчанию клиент делает до трёх попыток с экспоненциальной паузой и случайным разбросом. Встроенные операции явно помечены как safe, idempotent или unsafe в каталоге операций. Первые две категории повторяются после сетевого сбоя, таймаута или ответа 5xx. Правило задаётся смыслом операции, а не HTTP-методом.
unsafe-операции в этих ситуациях не повторяются. Для произвольного запроса политику можно указать отдельно, если это допускает API:
import { RetrySafety } from 'itd-api';
await itd.request({
operationId: 'custom:post-stats',
method: 'POST',
path: '/api/custom/post-stats',
body: { ids: ['post-id'] },
retrySafety: RetrySafety.Safe,
});Ответ 429 обрабатывается отдельно и может повторяться для любого метода: такой ответ означает, что операция не была выполнена. При любом виде повтора тело должно быть возможно создать заново: голый ReadableStream не повторяется. retry: false отключает обычные повторы конкретного вызова, но не лестницу ожиданий после 429.
Очередь и ограничения частоты
concurrency задаёт число одновременно выполняющихся запросов, а rps — равномерный темп их запуска:
const itd = new ItdClient({
rateLimit: {
concurrency: 2,
rps: 0.5, // не чаще одного запроса в две секунды
},
});Уменьшение одной только concurrency не ограничивает частоту запуска. Не ставьте concurrency: 1 без необходимости: долгая загрузка видео займёт единственный слот и задержит остальные операции.
Бакеты
Маршруты итд.com разбиты на бакеты — группы маршрутов с общим счётчиком запросов в минуту. Планировщик выполнения запросов выбирает готовый бакет и одновременно проверяет общие и локальные ограничения: пауза одного бакета не задерживает остальные, а суммарная одновременность остаётся равной concurrency.
запрос
└─ планировщик:
concurrency + rps + bucketConcurrency + bucket rps + паузы → транспортОчереди разделяются по итоговому URL.origin, а не по имени сервиса: разные имена одного адреса используют общий ограничитель, а другой адрес и разовый baseUrl получают свои. buckets: false оставляет одну очередь на адрес; в этом режиме bucketConcurrency и все поля bucketOverrides не действуют, а исчерпанный остаток встречается первой ступенью retryDelays.
Реакция на остаток
pacing решает, что делать с остатком, который сервер сообщает в каждом ответе:
import { ItdClient, RateLimitPacing } from 'itd-api';
const itd = new ItdClient({
rateLimit: { pacing: RateLimitPacing.Smooth },
});React— по умолчанию. Задержек нет, пока в бакете есть остаток; приremaining: 0бакет ждёт60000 / limitмиллисекунд.Smooth— ровный темп в пределах лимита бакета: задержки идут с первого запроса.Off— остаток на темп не влияет, остаётся только пауза после429.
Ни один режим не отменяет 429 полностью: квота считается по IP и делится с другими процессами на том же адресе, а границу минутного окна сервер не сообщает. После 429 работает лестница пауз retryDelays — по умолчанию 1, 5, 30, 60 и 90 секунд.
Таблица бакетов, поправки bucketOverrides, своё правило выбора бакета и снимок itd.rateLimitState() — в справочнике, «Ограничения частоты».
Дополнительные сервисы
Сервис — именованный API-хост со своими заголовками и политикой авторизации:
const itd = new ItdClient({
services: {
pb: {
baseUrl: 'https://pbapi.xn--d1ah4a.com',
headers: { Referer: 'https://pixel.xn--d1ah4a.com/' },
},
},
});
await itd.request({
method: 'GET',
service: 'pb',
path: '/api/pixel-info',
query: { x: 1, y: 2 },
});Сервис можно добавить и после создания клиента:
itd.defineService({
name: 'example',
baseUrl: 'https://api.example.com',
auth: false,
});Bearer-токен включён по умолчанию только для основного хоста и его поддоменов. Для другого хоста его передача требует явного auth: true. После регистрации имя занято: повторный defineService() с тем же именем бросает ItdConfigError.
Переопределение встроенных сервисов
Встроен один сервис — status, его имя доступно как STATUS_SERVICE. Указав такое имя в services, вы задаёте сервису другой хост — например собственный прокси, когда браузеру мешает CORS:
const itd = new ItdClient({
services: {
status: 'https://my-proxy.example/status',
},
});auth и заголовки наследуются от встроенного сервиса: авторизация относится к сервису, а не к адресу. Как и baseUrl, адрес переопределения должен быть доверенным — токен уйдёт на него.
| Что указали | Итоговый auth |
|---|---|
| только хост | как у встроенного сервиса |
явный auth | то, что написано |
явный auth: undefined | вывод по хосту |
Для нового имени правило прежнее: токен уходит только на основной хост и его поддомены, остальным нужен явный auth: true.
baseUrl, fetch и прокси
Пользовательский baseUrl становится основным API-хостом. На него пойдут авторизация, защищённые запросы и событийные соединения, поэтому адрес должен быть доверенным.
Собственный fetch видит URL, заголовки и тело всех запросов:
const itd = new ItdClient({
fetch: async (input, init) => {
console.log(init?.method, input);
return fetch(input, init);
},
});Для HTTP/SOCKS5-прокси используйте @itd-api/proxy. Браузерному приложению из-за CORS нужен собственный серверный прокси — см. интеграции.
Разовый itd.request({ baseUrl: 'https://external.example' }) не получает Bearer автоматически. Если внешнему хосту действительно нужна текущая авторизация, разрешите её через skipAuth: false.
Хуки и логирование
Хуки подходят для метрик, трассировки и дополнительных заголовков:
const itd = new ItdClient({
logger: true,
hooks: {
onRequest: ({ headers }) => {
headers.set('X-Trace-Id', crypto.randomUUID());
},
onResponse: ({ method, path, duration }) => {
console.log(method, path, `${duration} мс`);
},
onRetry: ({ attempt, delay }) => {
console.warn(`попытка ${attempt + 1} через ${delay} мс`);
},
},
});Хуки выполняются последовательно. Исключение из хука прерывает запрос. Встроенный logger маскирует известные поля с токенами и паролями.
Завершение работы
await itd.close();close() закрывает событийные соединения, отправляет открытые накопители телеметрии и затем отменяет запросы, которые ещё ждут очереди. Уже начавшиеся запросы завершаются. Если отправка накопителя не удалась, close() отклоняется, а записи остаются доступны для повторного flush(). Клиент после этого можно использовать снова.
await itd.dispose();dispose() дополнительно отменяет незавершённые запросы, отключает плагины и запускает их функции очистки. После этого новые запросы, use(), defineService() и повторный connect() ранее созданного канала событий завершаются с ItdStateError. Накопленные записи телеметрии отправляются до отключения плагинов. Повторный dispose() безопасно возвращает тот же результат очистки. Для автоматического освобождения ресурсов доступен await using.
Оба метода ждут обработчики потока и активные обёртки плагинов не дольше shutdownTimeout (по умолчанию 10 секунд). После истечения срока ресурсы освобождаются, а метод отклоняется с ItdStateError, в котором указан задержавший остановку компонент. shutdownTimeout: 0 возвращает ожидание без срока.