umbot
    Preparing search index...

    Кастомизация HTTP-слоя

    Вы можете заменить встроенный fetch на любой совместимый HTTP-клиент через AppContext.httpClient. Это позволяет добавлять retry-логику, тайм-ауты, tracing, моки в тестах и т.д.

    • Тайм-ауты — ограничение времени запросов
    • Retry-логика — повторные попытки при ошибках
    • Tracing — трассировка запросов
    • Моки — подмена в тестах
    import { Bot } from 'umbot';
    import { fullPlatforms } from 'umbot/plugins';

    const bot = new Bot();
    bot.use(fullPlatforms);
    const ctx = bot.getAppContext();

    ctx.httpClient = async (input, init) => {
    const controller = new AbortController();
    const id = setTimeout(() => controller.abort(), 5000);
    try {
    const res = await fetch(input, { ...init, signal: controller.signal });
    clearTimeout(id);
    return res;
    } catch (e) {
    clearTimeout(id);
    throw e;
    }
    };

    bot.start('localhost', 3000);
    import { Bot } from 'umbot';
    import { fullPlatforms } from 'umbot/plugins';

    const bot = new Bot();
    bot.use(fullPlatforms);
    const ctx = bot.getAppContext();

    ctx.httpClient = async (input, init) => {
    const maxRetries = 3;
    let lastError: Error | undefined;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
    const res = await fetch(input, init);
    if (res.ok) return res;
    lastError = new Error(`HTTP ${res.status}`);
    } catch (e) {
    lastError = e as Error;
    }
    // Экспоненциальная задержка перед повтором
    if (attempt < maxRetries) {
    await new Promise((r) => setTimeout(r, 1000 * 2 ** (attempt - 1)));
    }
    }
    throw lastError;
    };

    bot.start('localhost', 3000);
    import { Bot } from 'umbot';
    import { fullPlatforms } from 'umbot/plugins';

    const bot = new Bot();
    bot.use(fullPlatforms);
    const ctx = bot.getAppContext();

    ctx.httpClient = async (input, init) => {
    const start = performance.now();
    // input может быть string | URL | Request — сужаем тип
    const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
    console.log(`[HTTP] → ${init?.method ?? 'GET'} ${url}`);

    try {
    const res = await fetch(input, init);
    const ms = (performance.now() - start).toFixed(1);
    console.log(`[HTTP] ← ${res.status} ${url} (${ms}ms)`);
    return res;
    } catch (e) {
    const ms = (performance.now() - start).toFixed(1);
    console.log(`[HTTP] ✗ ${url} ERROR (${ms}ms): ${e}`);
    throw e;
    }
    };

    bot.start('localhost', 3000);
    import { BotTest } from 'umbot/test';
    import { fullPlatforms } from 'umbot/plugins';

    const bot = new BotTest();
    bot.use(fullPlatforms);
    const ctx = bot.getAppContext();

    // Подменяем fetch на мок
    ctx.httpClient = async (input, init) => {
    // input может быть string | URL | Request — сужаем тип
    const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;

    if (url.includes('api.weather.com')) {
    return new Response(JSON.stringify({ temp: 25 }), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
    });
    }

    return new Response('Not Found', { status: 404 });
    };

    // Запускаем через simulate() — в отличие от интерактивного bot.test()
    // он не блокируется в ожидании ввода из консоли (реальных запросов к API
    // платформ нет и там, и там — оба выставляют skipAutoReply)
    await bot.simulate('какая сегодня погода?');

    httpClient должен соответствовать сигнатуре:

    type THttpClient = (url: URL | RequestInfo, init?: RequestInit) => Promise<Response>;
    

    Это совместимо с глобальным fetch в Node.js 20.19+, а также с библиотеками типа node-fetch, undici, got (через обёртку).