fix: normalize abort signals for telegram fetch
This commit is contained in:
36
src/infra/fetch.test.ts
Normal file
36
src/infra/fetch.test.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { wrapFetchWithAbortSignal } from "./fetch.js";
|
||||||
|
|
||||||
|
describe("wrapFetchWithAbortSignal", () => {
|
||||||
|
it("converts foreign abort signals to native controllers", async () => {
|
||||||
|
let seenSignal: AbortSignal | undefined;
|
||||||
|
const fetchImpl = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
|
||||||
|
seenSignal = init?.signal as AbortSignal | undefined;
|
||||||
|
return {} as Response;
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapped = wrapFetchWithAbortSignal(fetchImpl);
|
||||||
|
|
||||||
|
let abortHandler: (() => void) | null = null;
|
||||||
|
const fakeSignal = {
|
||||||
|
aborted: false,
|
||||||
|
addEventListener: (event: string, handler: () => void) => {
|
||||||
|
if (event === "abort") abortHandler = handler;
|
||||||
|
},
|
||||||
|
removeEventListener: (event: string, handler: () => void) => {
|
||||||
|
if (event === "abort" && abortHandler === handler) abortHandler = null;
|
||||||
|
},
|
||||||
|
} as AbortSignal;
|
||||||
|
|
||||||
|
const promise = wrapped("https://example.com", { signal: fakeSignal });
|
||||||
|
expect(fetchImpl).toHaveBeenCalledOnce();
|
||||||
|
expect(seenSignal).toBeInstanceOf(AbortSignal);
|
||||||
|
expect(seenSignal).not.toBe(fakeSignal);
|
||||||
|
|
||||||
|
abortHandler?.();
|
||||||
|
expect(seenSignal?.aborted).toBe(true);
|
||||||
|
|
||||||
|
await promise;
|
||||||
|
});
|
||||||
|
});
|
||||||
29
src/infra/fetch.ts
Normal file
29
src/infra/fetch.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
export function wrapFetchWithAbortSignal(fetchImpl: typeof fetch): typeof fetch {
|
||||||
|
return (input: RequestInfo | URL, init?: RequestInit) => {
|
||||||
|
const signal = init?.signal;
|
||||||
|
if (!signal) return fetchImpl(input, init);
|
||||||
|
if (typeof AbortSignal !== "undefined" && signal instanceof AbortSignal) {
|
||||||
|
return fetchImpl(input, init);
|
||||||
|
}
|
||||||
|
if (typeof AbortController === "undefined") {
|
||||||
|
return fetchImpl(input, init);
|
||||||
|
}
|
||||||
|
if (typeof signal.addEventListener !== "function") {
|
||||||
|
return fetchImpl(input, init);
|
||||||
|
}
|
||||||
|
const controller = new AbortController();
|
||||||
|
const onAbort = () => controller.abort();
|
||||||
|
if (signal.aborted) {
|
||||||
|
controller.abort();
|
||||||
|
} else {
|
||||||
|
signal.addEventListener("abort", onAbort, { once: true });
|
||||||
|
}
|
||||||
|
const response = fetchImpl(input, { ...init, signal: controller.signal });
|
||||||
|
if (typeof signal.removeEventListener === "function") {
|
||||||
|
void response.finally(() => {
|
||||||
|
signal.removeEventListener("abort", onAbort);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -157,13 +157,12 @@ describe("createTelegramBot", () => {
|
|||||||
(globalThis as { Bun?: unknown }).Bun = {};
|
(globalThis as { Bun?: unknown }).Bun = {};
|
||||||
createTelegramBot({ token: "tok" });
|
createTelegramBot({ token: "tok" });
|
||||||
const fetchImpl = resolveTelegramFetch();
|
const fetchImpl = resolveTelegramFetch();
|
||||||
expect(fetchImpl).toBe(fetchSpy);
|
expect(fetchImpl).toBeTypeOf("function");
|
||||||
expect(botCtorSpy).toHaveBeenCalledWith(
|
expect(fetchImpl).not.toBe(fetchSpy);
|
||||||
"tok",
|
const clientFetch = (botCtorSpy.mock.calls[0]?.[1] as { client?: { fetch?: unknown } })
|
||||||
expect.objectContaining({
|
?.client?.fetch;
|
||||||
client: expect.objectContaining({ fetch: fetchSpy }),
|
expect(clientFetch).toBeTypeOf("function");
|
||||||
}),
|
expect(clientFetch).not.toBe(fetchSpy);
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
globalThis.fetch = originalFetch;
|
globalThis.fetch = originalFetch;
|
||||||
if (originalBun === undefined) {
|
if (originalBun === undefined) {
|
||||||
|
|||||||
@@ -284,13 +284,12 @@ describe("createTelegramBot", () => {
|
|||||||
(globalThis as { Bun?: unknown }).Bun = {};
|
(globalThis as { Bun?: unknown }).Bun = {};
|
||||||
createTelegramBot({ token: "tok" });
|
createTelegramBot({ token: "tok" });
|
||||||
const fetchImpl = resolveTelegramFetch();
|
const fetchImpl = resolveTelegramFetch();
|
||||||
expect(fetchImpl).toBe(fetchSpy);
|
expect(fetchImpl).toBeTypeOf("function");
|
||||||
expect(botCtorSpy).toHaveBeenCalledWith(
|
expect(fetchImpl).not.toBe(fetchSpy);
|
||||||
"tok",
|
const clientFetch = (botCtorSpy.mock.calls[0]?.[1] as { client?: { fetch?: unknown } })
|
||||||
expect.objectContaining({
|
?.client?.fetch;
|
||||||
client: expect.objectContaining({ fetch: fetchSpy }),
|
expect(clientFetch).toBeTypeOf("function");
|
||||||
}),
|
expect(clientFetch).not.toBe(fetchSpy);
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
globalThis.fetch = originalFetch;
|
globalThis.fetch = originalFetch;
|
||||||
if (originalBun === undefined) {
|
if (originalBun === undefined) {
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
import { wrapFetchWithAbortSignal } from "../infra/fetch.js";
|
||||||
|
|
||||||
// Bun-only: force native fetch to avoid grammY's Node shim under Bun.
|
// Bun-only: force native fetch to avoid grammY's Node shim under Bun.
|
||||||
export function resolveTelegramFetch(proxyFetch?: typeof fetch): typeof fetch | undefined {
|
export function resolveTelegramFetch(proxyFetch?: typeof fetch): typeof fetch | undefined {
|
||||||
if (proxyFetch) return proxyFetch;
|
if (proxyFetch) return wrapFetchWithAbortSignal(proxyFetch);
|
||||||
const fetchImpl = globalThis.fetch;
|
const fetchImpl = globalThis.fetch;
|
||||||
const isBun = "Bun" in globalThis || Boolean(process?.versions?.bun);
|
const isBun = "Bun" in globalThis || Boolean(process?.versions?.bun);
|
||||||
if (!isBun) return undefined;
|
if (!isBun) return undefined;
|
||||||
if (!fetchImpl) {
|
if (!fetchImpl) {
|
||||||
throw new Error("fetch is not available; set channels.telegram.proxy in config");
|
throw new Error("fetch is not available; set channels.telegram.proxy in config");
|
||||||
}
|
}
|
||||||
return fetchImpl;
|
return wrapFetchWithAbortSignal(fetchImpl);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { ProxyAgent } from "undici";
|
import { ProxyAgent } from "undici";
|
||||||
|
import { wrapFetchWithAbortSignal } from "../infra/fetch.js";
|
||||||
|
|
||||||
export function makeProxyFetch(proxyUrl: string): typeof fetch {
|
export function makeProxyFetch(proxyUrl: string): typeof fetch {
|
||||||
const agent = new ProxyAgent(proxyUrl);
|
const agent = new ProxyAgent(proxyUrl);
|
||||||
return (input: RequestInfo | URL, init?: RequestInit) => {
|
return wrapFetchWithAbortSignal((input: RequestInfo | URL, init?: RequestInit) => {
|
||||||
const base = init ? { ...init } : {};
|
const base = init ? { ...init } : {};
|
||||||
return fetch(input, { ...base, dispatcher: agent });
|
return fetch(input, { ...base, dispatcher: agent });
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,12 +164,10 @@ describe("sendMessageTelegram", () => {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await sendMessageTelegram("123", "hi", { token: "tok" });
|
await sendMessageTelegram("123", "hi", { token: "tok" });
|
||||||
expect(botCtorSpy).toHaveBeenCalledWith(
|
const clientFetch = (botCtorSpy.mock.calls[0]?.[1] as { client?: { fetch?: unknown } })
|
||||||
"tok",
|
?.client?.fetch;
|
||||||
expect.objectContaining({
|
expect(clientFetch).toBeTypeOf("function");
|
||||||
client: expect.objectContaining({ fetch: fetchSpy }),
|
expect(clientFetch).not.toBe(fetchSpy);
|
||||||
}),
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
globalThis.fetch = originalFetch;
|
globalThis.fetch = originalFetch;
|
||||||
if (originalBun === undefined) {
|
if (originalBun === undefined) {
|
||||||
|
|||||||
Reference in New Issue
Block a user