mirror of
https://github.com/rookiestar28/ComfyUI-OpenClaw.git
synced 2026-08-14 00:48:07 +00:00
124 lines
4.0 KiB
JavaScript
124 lines
4.0 KiB
JavaScript
/**
|
|
* R96: Fetch wrapper composition helpers with idempotence guard.
|
|
*
|
|
* Prevents duplicate wrapper stacking when bootstrap code runs multiple times.
|
|
*/
|
|
|
|
const WRAP_META = Symbol.for("openclaw.fetch_wrapper_meta");
|
|
const PRECONNECTED_ORIGINS = new Set();
|
|
|
|
function getDecoratorId(decorator, index) {
|
|
if (typeof decorator?.id === "string" && decorator.id) return decorator.id;
|
|
if (typeof decorator?.name === "string" && decorator.name) return decorator.name;
|
|
return `decorator_${index}`;
|
|
}
|
|
|
|
function arraysEqual(a = [], b = []) {
|
|
if (a.length !== b.length) return false;
|
|
for (let i = 0; i < a.length; i += 1) {
|
|
if (a[i] !== b[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export function getFetchWrapperMeta(fetchFn) {
|
|
return fetchFn?.[WRAP_META] || null;
|
|
}
|
|
|
|
export function composeFetchWrappersOnce(fetchFn, decorators = []) {
|
|
if (typeof fetchFn !== "function") {
|
|
throw new TypeError("fetchFn must be a function");
|
|
}
|
|
if (!Array.isArray(decorators)) {
|
|
throw new TypeError("decorators must be an array");
|
|
}
|
|
|
|
const chainIds = decorators.map(getDecoratorId);
|
|
const existingMeta = getFetchWrapperMeta(fetchFn);
|
|
if (existingMeta && arraysEqual(existingMeta.chainIds, chainIds)) {
|
|
return fetchFn;
|
|
}
|
|
|
|
let wrapped = fetchFn;
|
|
decorators.forEach((decorator, index) => {
|
|
if (typeof decorator !== "function") {
|
|
throw new TypeError(`decorator at index ${index} must be a function`);
|
|
}
|
|
wrapped = decorator(wrapped);
|
|
});
|
|
|
|
const baseMeta = existingMeta || { baseFetch: fetchFn, appliedCount: 0, chainIds: [] };
|
|
Object.defineProperty(wrapped, WRAP_META, {
|
|
value: {
|
|
baseFetch: baseMeta.baseFetch || fetchFn,
|
|
appliedCount: (baseMeta.appliedCount || 0) + 1,
|
|
chainIds,
|
|
},
|
|
enumerable: false,
|
|
configurable: false,
|
|
writable: false,
|
|
});
|
|
return wrapped;
|
|
}
|
|
|
|
/**
|
|
* Decorator: ensures signal is passed through when options contain one.
|
|
* (Useful when other decorators clone/normalize init objects.)
|
|
*/
|
|
export function withAbortPassthrough() {
|
|
const decorator = async (next, input, init = {}) => next(input, { ...init });
|
|
const wrapper = (next) => (input, init) => decorator(next, input, init);
|
|
wrapper.id = "abort_passthrough";
|
|
return wrapper;
|
|
}
|
|
|
|
/**
|
|
* Decorator: best-effort browser preconnect hint for HTTP(S) origins.
|
|
*/
|
|
export function withPreconnectHint() {
|
|
const wrapper = (next) => (input, init) => {
|
|
try {
|
|
const url = typeof input === "string" ? input : (input?.url || "");
|
|
if (url && typeof document !== "undefined" && /^https?:\/\//i.test(url)) {
|
|
const origin = new URL(url, window.location.href).origin;
|
|
if (!PRECONNECTED_ORIGINS.has(origin)) {
|
|
PRECONNECTED_ORIGINS.add(origin);
|
|
const link = document.createElement("link");
|
|
link.rel = "preconnect";
|
|
link.href = origin;
|
|
document.head.appendChild(link);
|
|
}
|
|
}
|
|
} catch {
|
|
// best-effort only
|
|
}
|
|
return next(input, init);
|
|
};
|
|
wrapper.id = "preconnect_hint";
|
|
return wrapper;
|
|
}
|
|
|
|
/**
|
|
* Decorator: single retry for idempotent GET requests on network failures.
|
|
*/
|
|
export function withGetRetry({ retries = 1 } = {}) {
|
|
const wrapper = (next) => async (input, init = {}) => {
|
|
const method = String(init?.method || "GET").toUpperCase();
|
|
const shouldRetry = method === "GET" && retries > 0;
|
|
let lastErr;
|
|
const attempts = shouldRetry ? retries + 1 : 1;
|
|
for (let i = 0; i < attempts; i += 1) {
|
|
try {
|
|
return await next(input, init);
|
|
} catch (err) {
|
|
const isAbort = err?.name === "AbortError";
|
|
if (isAbort || i >= attempts - 1) throw err;
|
|
lastErr = err;
|
|
}
|
|
}
|
|
throw lastErr || new Error("fetch_retry_exhausted");
|
|
};
|
|
wrapper.id = `retry_get_${retries}`;
|
|
return wrapper;
|
|
}
|