mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 03:02:45 +00:00
fix: add duplex for fetch uploads
This commit is contained in:
@@ -24,6 +24,7 @@ Docs: https://docs.clawd.bot
|
|||||||
- Heartbeat: normalize target identifiers for consistent routing.
|
- Heartbeat: normalize target identifiers for consistent routing.
|
||||||
- TUI: reload history after gateway reconnect to restore session state. (#1663)
|
- TUI: reload history after gateway reconnect to restore session state. (#1663)
|
||||||
- Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639)
|
- Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639)
|
||||||
|
- Telegram: set fetch duplex="half" for uploads on Node 22 to avoid sendPhoto failures. (#1684) Thanks @commdata2338.
|
||||||
- Signal: repair reaction sends (group/UUID targets + CLI author flags). (#1651) Thanks @vilkasdev.
|
- Signal: repair reaction sends (group/UUID targets + CLI author flags). (#1651) Thanks @vilkasdev.
|
||||||
- Exec: keep approvals for elevated ask unless full mode. (#1616) Thanks @ivancasco.
|
- Exec: keep approvals for elevated ask unless full mode. (#1616) Thanks @ivancasco.
|
||||||
- Agents: auto-compact on context overflow prompt errors before failing. (#1627) Thanks @rodrigouroz.
|
- Agents: auto-compact on context overflow prompt errors before failing. (#1627) Thanks @rodrigouroz.
|
||||||
|
|||||||
@@ -3,6 +3,20 @@ import { describe, expect, it, vi } from "vitest";
|
|||||||
import { wrapFetchWithAbortSignal } from "./fetch.js";
|
import { wrapFetchWithAbortSignal } from "./fetch.js";
|
||||||
|
|
||||||
describe("wrapFetchWithAbortSignal", () => {
|
describe("wrapFetchWithAbortSignal", () => {
|
||||||
|
it("adds duplex for requests with a body", async () => {
|
||||||
|
let seenInit: RequestInit | undefined;
|
||||||
|
const fetchImpl = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
|
||||||
|
seenInit = init;
|
||||||
|
return {} as Response;
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapped = wrapFetchWithAbortSignal(fetchImpl);
|
||||||
|
|
||||||
|
await wrapped("https://example.com", { method: "POST", body: "hi" });
|
||||||
|
|
||||||
|
expect(seenInit?.duplex).toBe("half");
|
||||||
|
});
|
||||||
|
|
||||||
it("converts foreign abort signals to native controllers", async () => {
|
it("converts foreign abort signals to native controllers", async () => {
|
||||||
let seenSignal: AbortSignal | undefined;
|
let seenSignal: AbortSignal | undefined;
|
||||||
const fetchImpl = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
|
const fetchImpl = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
|
||||||
|
|||||||
@@ -2,18 +2,38 @@ type FetchWithPreconnect = typeof fetch & {
|
|||||||
preconnect: (url: string, init?: { credentials?: RequestCredentials }) => void;
|
preconnect: (url: string, init?: { credentials?: RequestCredentials }) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type RequestInitWithDuplex = RequestInit & { duplex?: "half" };
|
||||||
|
|
||||||
|
function withDuplex(
|
||||||
|
init: RequestInit | undefined,
|
||||||
|
input: RequestInfo | URL,
|
||||||
|
): RequestInit | undefined {
|
||||||
|
const hasInitBody = init?.body != null;
|
||||||
|
const hasRequestBody =
|
||||||
|
!hasInitBody &&
|
||||||
|
typeof Request !== "undefined" &&
|
||||||
|
input instanceof Request &&
|
||||||
|
input.body != null;
|
||||||
|
if (!hasInitBody && !hasRequestBody) return init;
|
||||||
|
if (init && "duplex" in (init as Record<string, unknown>)) return init;
|
||||||
|
return init
|
||||||
|
? ({ ...init, duplex: "half" as const } as RequestInitWithDuplex)
|
||||||
|
: ({ duplex: "half" as const } as RequestInitWithDuplex);
|
||||||
|
}
|
||||||
|
|
||||||
export function wrapFetchWithAbortSignal(fetchImpl: typeof fetch): typeof fetch {
|
export function wrapFetchWithAbortSignal(fetchImpl: typeof fetch): typeof fetch {
|
||||||
const wrapped = ((input: RequestInfo | URL, init?: RequestInit) => {
|
const wrapped = ((input: RequestInfo | URL, init?: RequestInit) => {
|
||||||
const signal = init?.signal;
|
const patchedInit = withDuplex(init, input);
|
||||||
if (!signal) return fetchImpl(input, init);
|
const signal = patchedInit?.signal;
|
||||||
|
if (!signal) return fetchImpl(input, patchedInit);
|
||||||
if (typeof AbortSignal !== "undefined" && signal instanceof AbortSignal) {
|
if (typeof AbortSignal !== "undefined" && signal instanceof AbortSignal) {
|
||||||
return fetchImpl(input, init);
|
return fetchImpl(input, patchedInit);
|
||||||
}
|
}
|
||||||
if (typeof AbortController === "undefined") {
|
if (typeof AbortController === "undefined") {
|
||||||
return fetchImpl(input, init);
|
return fetchImpl(input, patchedInit);
|
||||||
}
|
}
|
||||||
if (typeof signal.addEventListener !== "function") {
|
if (typeof signal.addEventListener !== "function") {
|
||||||
return fetchImpl(input, init);
|
return fetchImpl(input, patchedInit);
|
||||||
}
|
}
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const onAbort = () => controller.abort();
|
const onAbort = () => controller.abort();
|
||||||
@@ -22,7 +42,7 @@ export function wrapFetchWithAbortSignal(fetchImpl: typeof fetch): typeof fetch
|
|||||||
} else {
|
} else {
|
||||||
signal.addEventListener("abort", onAbort, { once: true });
|
signal.addEventListener("abort", onAbort, { once: true });
|
||||||
}
|
}
|
||||||
const response = fetchImpl(input, { ...init, signal: controller.signal });
|
const response = fetchImpl(input, { ...patchedInit, signal: controller.signal });
|
||||||
if (typeof signal.removeEventListener === "function") {
|
if (typeof signal.removeEventListener === "function") {
|
||||||
void response.finally(() => {
|
void response.finally(() => {
|
||||||
signal.removeEventListener("abort", onAbort);
|
signal.removeEventListener("abort", onAbort);
|
||||||
|
|||||||
Reference in New Issue
Block a user