fix: abort telegram getupdates on shutdown (#23950) (thanks @Gkinthecodeland)

This commit is contained in:
Peter Steinberger
2026-03-09 06:03:28 +00:00
committed by Vincent Koc
parent e383257552
commit 2220a58ff7
4 changed files with 36 additions and 23 deletions

View File

@@ -75,27 +75,6 @@ describe("createTelegramBot", () => {
globalThis.fetch = originalFetch;
}
});
it("aborts wrapped client fetch when fetchAbortSignal aborts", async () => {
const originalFetch = globalThis.fetch;
const fetchSpy = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => init?.signal);
const shutdown = new AbortController();
globalThis.fetch = fetchSpy as unknown as typeof fetch;
try {
createTelegramBot({ token: "tok", fetchAbortSignal: shutdown.signal });
const clientFetch = (botCtorSpy.mock.calls[0]?.[1] as { client?: { fetch?: unknown } })
?.client?.fetch as ((input: RequestInfo | URL, init?: RequestInit) => Promise<unknown>);
expect(clientFetch).toBeTypeOf("function");
const observedSignal = (await clientFetch("https://example.test")) as AbortSignal;
expect(observedSignal).toBeInstanceOf(AbortSignal);
expect(observedSignal.aborted).toBe(false);
shutdown.abort(new Error("shutdown"));
expect(observedSignal.aborted).toBe(true);
} finally {
globalThis.fetch = originalFetch;
}
});
it("applies global and per-account timeoutSeconds", () => {
loadConfig.mockReturnValue({
channels: {

View File

@@ -0,0 +1,34 @@
import { describe, expect, it, vi } from "vitest";
import { botCtorSpy } from "./bot.create-telegram-bot.test-harness.js";
import { createTelegramBot } from "./bot.js";
describe("createTelegramBot fetch abort", () => {
it("aborts wrapped client fetch when fetchAbortSignal aborts", async () => {
const originalFetch = globalThis.fetch;
const shutdown = new AbortController();
const fetchSpy = vi.fn(
(_input: RequestInfo | URL, init?: RequestInit) =>
new Promise<AbortSignal>((resolve) => {
const signal = init?.signal as AbortSignal;
signal.addEventListener("abort", () => resolve(signal), { once: true });
}),
);
globalThis.fetch = fetchSpy as unknown as typeof fetch;
try {
botCtorSpy.mockClear();
createTelegramBot({ token: "tok", fetchAbortSignal: shutdown.signal });
const clientFetch = (botCtorSpy.mock.calls.at(-1)?.[1] as { client?: { fetch?: unknown } })
?.client?.fetch as (input: RequestInfo | URL, init?: RequestInit) => Promise<unknown>;
expect(clientFetch).toBeTypeOf("function");
const observedSignalPromise = clientFetch("https://example.test");
shutdown.abort(new Error("shutdown"));
const observedSignal = (await observedSignalPromise) as AbortSignal;
expect(observedSignal).toBeInstanceOf(AbortSignal);
expect(observedSignal.aborted).toBe(true);
} finally {
globalThis.fetch = originalFetch;
}
});
});

View File

@@ -110,8 +110,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
// (especially long-polling getUpdates) aborts immediately on shutdown. Without this,
// the in-flight getUpdates hangs for up to 30s, and a new gateway instance starting
// its own poll triggers a 409 Conflict from Telegram.
let finalFetch: NonNullable<ApiClientOptions["fetch"]> | undefined =
shouldProvideFetch && fetchImpl ? fetchForClient : undefined;
let finalFetch = shouldProvideFetch && fetchImpl ? fetchForClient : undefined;
if (opts.fetchAbortSignal) {
const baseFetch =
finalFetch ?? (globalThis.fetch as unknown as NonNullable<ApiClientOptions["fetch"]>);