From d58b638277ace882317425a321424630d62824d9 Mon Sep 17 00:00:00 2001 From: JonnyBurger Date: Tue, 30 Sep 2025 14:11:17 +0200 Subject: [PATCH] Stop retrying UrlSource if CORS error is suspected --- docs/guide/reading-media-files.md | 2 +- src/source.ts | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/guide/reading-media-files.md b/docs/guide/reading-media-files.md index 1cdfacd..3e2433e 100644 --- a/docs/guide/reading-media-files.md +++ b/docs/guide/reading-media-files.md @@ -517,7 +517,7 @@ const source = new UrlSource('https://example.com/bigbuckbunny.mp4', { }); ``` -Not setting `getRetryDelay` will default to an infinite, capped exponential backoff pattern. +Not setting `getRetryDelay` will default to an infinite, capped exponential backoff pattern. If a CORS error is suspected (`fetch()` did reject even though `navigator.onLine` is true), no further retries will be made. --- diff --git a/src/source.ts b/src/source.ts index 21ee13f..f9d75ae 100644 --- a/src/source.ts +++ b/src/source.ts @@ -254,8 +254,25 @@ export class BlobSource extends Source { } const URL_SOURCE_MIN_LOAD_AMOUNT = 0.5 * 2 ** 20; // 0.5 MiB + const DEFAULT_RETRY_DELAY - = (previousAttempts => Math.min(2 ** (previousAttempts - 2), 16)) satisfies UrlSourceOptions['getRetryDelay']; + = ((previousAttempts, error) => { + // Check if this could be a CORS error. If so, we cannot recover from it and + // should not attempt to retry. + // CORS errors are intentionally not opaque, so we need to rely on heuristics. + const couldBeCorsError = error instanceof Error && ( + error.message.includes('Failed to fetch') // Chrome + || error.message.includes('Load failed') // Safari + || error.message.includes('NetworkError when attempting to fetch resource') // Firefox + ); + + // Being offline would lead to the same error, in that case it would not be a CORS error + if (couldBeCorsError && navigator.onLine) { + return null; + } + + return Math.min(2 ** (previousAttempts - 2), 16); + }) satisfies UrlSourceOptions['getRetryDelay']; /** * Options for {@link UrlSource}. @@ -274,7 +291,8 @@ export type UrlSourceOptions = { * with the number of previous, unsuccessful attempts, as well as with the error with which the previous request * failed. If the function returns `null`, no more retries will be made. * - * By default, it uses an exponential backoff algorithm that never fully gives up. + * By default, it uses an exponential backoff algorithm that never gives up unless + * a CORS error is suspected (`fetch()` did reject even though `navigator.onLine` is true) */ getRetryDelay?: (previousAttempts: number, error: unknown) => number | null;