refactor(infra): make fetch wrapping idempotent

This commit is contained in:
sebslight
2026-02-16 08:24:26 -05:00
parent 7b8cce0910
commit b4fa10ae67
2 changed files with 48 additions and 3 deletions

View File

@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import { wrapFetchWithAbortSignal } from "./fetch.js";
import { resolveFetch, wrapFetchWithAbortSignal } from "./fetch.js";
describe("wrapFetchWithAbortSignal", () => {
it("adds duplex for requests with a body", async () => {
@@ -182,4 +182,31 @@ describe("wrapFetchWithAbortSignal", () => {
expect(addEventListener).not.toHaveBeenCalled();
expect(removeEventListener).not.toHaveBeenCalled();
});
it("returns the same function when called with an already wrapped fetch", () => {
const fetchImpl = vi.fn(async () => ({ ok: true }) as Response);
const wrapped = wrapFetchWithAbortSignal(fetchImpl);
expect(wrapFetchWithAbortSignal(wrapped)).toBe(wrapped);
expect(resolveFetch(wrapped)).toBe(wrapped);
});
it("keeps preconnect bound to the original fetch implementation", () => {
const preconnectSpy = vi.fn(function (this: unknown) {
return this;
});
const fetchImpl = vi.fn(async () => ({ ok: true }) as Response) as typeof fetch & {
preconnect: (url: string, init?: { credentials?: RequestCredentials }) => unknown;
};
fetchImpl.preconnect = preconnectSpy;
const wrapped = wrapFetchWithAbortSignal(fetchImpl) as typeof fetch & {
preconnect: (url: string, init?: { credentials?: RequestCredentials }) => unknown;
};
const seenThis = wrapped.preconnect("https://example.com");
expect(preconnectSpy).toHaveBeenCalledOnce();
expect(seenThis).toBe(fetchImpl);
});
});

View File

@@ -6,6 +6,12 @@ type FetchWithPreconnect = typeof fetch & {
type RequestInitWithDuplex = RequestInit & { duplex?: "half" };
const wrapFetchWithAbortSignalMarker = Symbol.for("openclaw.fetch.abort-signal-wrapped");
type FetchWithAbortSignalMarker = typeof fetch & {
[wrapFetchWithAbortSignalMarker]?: true;
};
function withDuplex(
init: RequestInit | undefined,
input: RequestInfo | URL,
@@ -28,6 +34,10 @@ function withDuplex(
}
export function wrapFetchWithAbortSignal(fetchImpl: typeof fetch): typeof fetch {
if ((fetchImpl as FetchWithAbortSignalMarker)[wrapFetchWithAbortSignalMarker]) {
return fetchImpl;
}
const wrapped = ((input: RequestInfo | URL, init?: RequestInit) => {
const patchedInit = withDuplex(init, input);
const signal = patchedInit?.signal;
@@ -73,13 +83,21 @@ export function wrapFetchWithAbortSignal(fetchImpl: typeof fetch): typeof fetch
}
}) as FetchWithPreconnect;
const wrappedFetch = Object.assign(wrapped, fetchImpl) as FetchWithPreconnect;
const fetchWithPreconnect = fetchImpl as FetchWithPreconnect;
wrapped.preconnect =
wrappedFetch.preconnect =
typeof fetchWithPreconnect.preconnect === "function"
? fetchWithPreconnect.preconnect.bind(fetchWithPreconnect)
: () => {};
return Object.assign(wrapped, fetchImpl);
Object.defineProperty(wrappedFetch, wrapFetchWithAbortSignalMarker, {
value: true,
enumerable: false,
configurable: false,
writable: false,
});
return wrappedFetch;
}
export function resolveFetch(fetchImpl?: typeof fetch): typeof fetch | undefined {