From 1e179b439604246ec27764ea1da55baf302c7aae Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 15 Jun 2025 12:35:47 -0500 Subject: [PATCH] Add AbortSignal polyfills --- src/lib/polyfills.ts | 78 ++++++++++++++++++++++++++++++++++++++++++++ src/main.tsx | 9 +++-- 2 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 src/lib/polyfills.ts diff --git a/src/lib/polyfills.ts b/src/lib/polyfills.ts new file mode 100644 index 0000000..edb726e --- /dev/null +++ b/src/lib/polyfills.ts @@ -0,0 +1,78 @@ +/** + * Polyfill for AbortSignal.any() + * + * AbortSignal.any() creates an AbortSignal that will be aborted when any of the + * provided signals are aborted. This is useful for combining multiple abort signals. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static + */ + +// Check if AbortSignal.any is already available +if (!AbortSignal.any) { + AbortSignal.any = function(signals: AbortSignal[]): AbortSignal { + // If no signals provided, return a signal that never aborts + if (signals.length === 0) { + return new AbortController().signal; + } + + // If only one signal, return it directly for efficiency + if (signals.length === 1) { + return signals[0]; + } + + // Check if any signal is already aborted + for (const signal of signals) { + if (signal.aborted) { + // Create an already-aborted signal with the same reason + const controller = new AbortController(); + controller.abort(signal.reason); + return controller.signal; + } + } + + // Create a new controller for the combined signal + const controller = new AbortController(); + + // Function to abort the combined signal + const onAbort = (event: Event) => { + const target = event.target as AbortSignal; + controller.abort(target.reason); + }; + + // Listen for abort events on all input signals + for (const signal of signals) { + signal.addEventListener('abort', onAbort, { once: true }); + } + + // Clean up listeners when the combined signal is aborted + controller.signal.addEventListener('abort', () => { + for (const signal of signals) { + signal.removeEventListener('abort', onAbort); + } + }, { once: true }); + + return controller.signal; + }; +} + +/** + * Polyfill for AbortSignal.timeout() + * + * AbortSignal.timeout() creates an AbortSignal that will be aborted after a + * specified number of milliseconds. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static + */ + +// Check if AbortSignal.timeout is already available +if (!AbortSignal.timeout) { + AbortSignal.timeout = function(milliseconds: number): AbortSignal { + const controller = new AbortController(); + + setTimeout(() => { + controller.abort(new DOMException('The operation was aborted due to timeout', 'TimeoutError')); + }, milliseconds); + + return controller.signal; + }; +} \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx index bf51fc7..f322051 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,7 +1,10 @@ -import { createRoot } from 'react-dom/client' +import { createRoot } from 'react-dom/client'; -import App from './App.tsx' -import './index.css' +// Import polyfills first +import './lib/polyfills.ts'; + +import App from './App.tsx'; +import './index.css'; // FIXME: a custom font should be used. Eg: // import '@fontsource-variable/';