Конфигурация клиента
Практическое руководство по таймаутам, повторам, очередям, дополнительным сервисам, собственному 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.
Таймаут и отмена
timeout ограничивает одну попытку: сборку заголовков, хуки onRequest и onResponse, перехватчики попытки, запрос и чтение тела ответа — в том числе когда fetch или перехватчик не слушают signal. По умолчанию 30 секунд, 0 отключает. Загрузка файлов использует отдельное значение — 5 минут. Ожидание очереди, паузы между повторами и хук onError в срок не входят. Истёкший timeout повторяется как сетевой сбой: только для safe- и idempotent-операций.
deadline ограничивает операцию целиком: очередь, повторы, паузы после 429, обновление токена, плагины и хуки. По умолчанию не ограничен. Истёкший deadline не повторяется. Без deadline зависший хук или плагин держит операцию бесконечно.
Истечение любого срока даёт ItdTimeoutError; поле budget называет истёкший срок: 'attempt' или 'deadline'.
import { ItdAbortError, ItdClient } from 'itd-api';
const itd = new ItdClient({ timeout: 20_000, deadline: 120_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, deadline, дополнительные заголовки и локальная настройка повторов 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.
Логирование
По умолчанию клиент молчит. logger: true включает встроенный логгер поверх console с уровня info:
import { ItdClient, LogLevel } from 'itd-api';
new ItdClient({ logger: true }); // info и выше
new ItdClient({ logger: LogLevel.Debug }); // плюс каждый запрос и ответ
new ItdClient({ logger: LogLevel.Warn }); // только проблемы| Уровень | Что попадает |
|---|---|
debug | каждый запрос и ответ, ожидание очереди |
info | вход, обновление токена, повторы, подключение потока событий, ход решения капчи |
warn | сетевые ошибки и ответы ≥ 400, обрыв потока событий, отвергнутая сервером сессия |
error | исключения в ваших обработчиках событий |
Токены и пароли маскируются до записи. Исключения из ваших обработчиков событий пишутся в консоль даже без logger.
Свой логгер
Подходит любой объект с методами debug, info, warn и error — например winston или consola; уровни он фильтрует сам. Если у него есть isLevelEnabled(level), как у winston и pino, детали для выключенных уровней не готовятся. К сообщению прилагается не больше одного аргумента: объект с деталями либо ошибка. Pino ждёт объект первым аргументом, поэтому ему нужен адаптер:
import pino from 'pino';
const log = pino();
new ItdClient({
logger: {
debug: (message, details) => log.debug(details, message),
info: (message, details) => log.info(details, message),
warn: (message, details) => log.warn(details, message),
error: (message, details) => log.error(details, message),
},
});Тот же логгер получают плагины через PluginApi.logger и источник капчи из @itd-api/captcha.
Хуки
Хуки подходят для метрик, трассировки и дополнительных заголовков:
const itd = new ItdClient({
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} мс`);
},
},
});Хуки выполняются последовательно. Исключение из хука прерывает запрос.
Завершение работы
await itd.close();close() закрывает событийные соединения, отправляет открытые накопители телеметрии и затем отменяет запросы, которые ещё ждут очереди. Уже начавшиеся запросы завершаются. Если отправка накопителя не удалась, close() отклоняется, а записи остаются доступны для повторного flush(). Клиент после этого можно использовать снова.
await itd.dispose();dispose() дополнительно отменяет незавершённые запросы, отключает плагины и запускает их функции очистки. После этого новые запросы, use(), defineService() и повторный connect() ранее созданного канала событий завершаются с ItdStateError. Накопленные записи телеметрии отправляются до отключения плагинов. Повторный dispose() безопасно возвращает тот же результат очистки. Для автоматического освобождения ресурсов доступен await using.
Оба метода ждут обработчики потока и активные обёртки плагинов не дольше shutdownTimeout (по умолчанию 10 секунд). После истечения срока ресурсы освобождаются, а метод отклоняется с ItdStateError, в котором указан задержавший остановку компонент. shutdownTimeout: 0 возвращает ожидание без срока.