Skip to content
This repository was archived by the owner on Jul 17, 2026. It is now read-only.

Commit 2b9b575

Browse files
committed
fix: enhance AbortSignal compatibility and fix listener leaks
- Improve AbortSignal.any polyfill with proper listener cleanup - Add AbortSignal.timeout guard and fallback for older Node.js versions - Implement mergeAbortSignals helper for safe signal combination - Add exhaustive listener cleanup on signal abortion
1 parent 506698b commit 2b9b575

1 file changed

Lines changed: 58 additions & 11 deletions

File tree

src/plugin.ts

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,23 +89,62 @@ const log = createLogger("plugin");
8989
if (typeof (AbortSignal as any).any !== "function") {
9090
(AbortSignal as any).any = function (signals: AbortSignal[]): AbortSignal {
9191
const controller = new AbortController();
92+
const onAbort = () => {
93+
const firstAborted = signals.find(s => s.aborted);
94+
controller.abort(firstAborted?.reason);
95+
cleanup();
96+
};
97+
const cleanup = () => {
98+
for (const signal of signals) {
99+
signal.removeEventListener("abort", onAbort);
100+
}
101+
};
92102
for (const signal of signals) {
93103
if (signal.aborted) {
94104
controller.abort(signal.reason);
95105
return controller.signal;
96106
}
97-
signal.addEventListener(
98-
"abort",
99-
() => {
100-
controller.abort(signal.reason);
101-
},
102-
{ once: true }
103-
);
107+
signal.addEventListener("abort", onAbort, { once: true });
104108
}
109+
controller.signal.addEventListener("abort", cleanup, { once: true });
105110
return controller.signal;
106111
};
107112
}
108113

114+
/**
115+
* Simple combinator for multiple AbortSignals for environments where AbortSignal.any is missing.
116+
*/
117+
function mergeAbortSignals(...signals: (AbortSignal | undefined)[]): AbortSignal {
118+
const activeSignals = signals.filter((s): s is AbortSignal => s !== undefined);
119+
if (activeSignals.length === 0) return new AbortController().signal;
120+
if (activeSignals.length === 1) return activeSignals[0] as AbortSignal;
121+
122+
if (typeof (AbortSignal as any).any === 'function') {
123+
return (AbortSignal as any).any(activeSignals);
124+
}
125+
126+
const controller = new AbortController();
127+
const onAbort = () => {
128+
const firstAborted = activeSignals.find(s => s.aborted);
129+
controller.abort(firstAborted?.reason);
130+
cleanup();
131+
};
132+
const cleanup = () => {
133+
for (const signal of activeSignals) {
134+
signal.removeEventListener("abort", onAbort);
135+
}
136+
};
137+
for (const signal of activeSignals) {
138+
if (signal.aborted) {
139+
controller.abort(signal.reason);
140+
return controller.signal;
141+
}
142+
signal.addEventListener("abort", onAbort, { once: true });
143+
}
144+
controller.signal.addEventListener("abort", cleanup, { once: true });
145+
return controller.signal;
146+
}
147+
109148
// Module-level toast debounce to persist across requests (fixes toast spam)
110149
const rateLimitToastCooldowns = new Map<string, number>();
111150
const RATE_LIMIT_TOAST_COOLDOWN_MS = 5000;
@@ -2063,10 +2102,18 @@ export const createAntigravityPlugin = (providerId: string) => async (
20632102
? Math.min(timeoutMs * 3, 1800000)
20642103
: timeoutMs;
20652104

2066-
const timeoutSignal = AbortSignal.timeout(effectiveTimeoutMs);
2067-
const combinedSignal = abortSignal
2068-
? (AbortSignal as any).any([abortSignal, timeoutSignal])
2069-
: timeoutSignal;
2105+
// Safely create timeout signal with fallback for older Node.js versions
2106+
let timeoutSignal: AbortSignal;
2107+
if (typeof AbortSignal.timeout === 'function') {
2108+
timeoutSignal = AbortSignal.timeout(effectiveTimeoutMs);
2109+
} else {
2110+
const controller = new AbortController();
2111+
setTimeout(() => controller.abort(new Error('Timeout')), effectiveTimeoutMs);
2112+
timeoutSignal = controller.signal;
2113+
}
2114+
2115+
// Safely create combined signal with polyfill/fallback
2116+
const combinedSignal = mergeAbortSignals(abortSignal, timeoutSignal);
20702117

20712118
const response = await fetch(prepared.request, {
20722119
...prepared.init,

0 commit comments

Comments
 (0)