diff --git a/packages/shared/src/loadClerkJsScript.ts b/packages/shared/src/loadClerkJsScript.ts index 68f81af1778..fff2d8b6b0b 100644 --- a/packages/shared/src/loadClerkJsScript.ts +++ b/packages/shared/src/loadClerkJsScript.ts @@ -59,6 +59,44 @@ function isClerkProperlyLoaded(): boolean { return typeof clerk === 'object' && typeof clerk.load === 'function'; } +/** + * Checks if an existing script has a request error using Performance API. + * + * @param scriptUrl - The URL of the script to check. + * @returns True if the script has failed to load due to a network/HTTP error. + */ +function hasScriptRequestError(scriptUrl: string): boolean { + if (typeof window === 'undefined' || !window.performance) { + return false; + } + + const entries = performance.getEntries() as PerformanceResourceTiming[]; + const scriptEntry = entries.find(entry => entry.name === scriptUrl); + + if (!scriptEntry) { + return false; + } + + // transferSize === 0 with responseEnd === 0 indicates network failure + // transferSize === 0 with responseEnd > 0 might be a 4xx/5xx error or blocked request + if (scriptEntry.transferSize === 0 && scriptEntry.decodedBodySize === 0) { + // If there was no response at all, it's definitely an error + if (scriptEntry.responseEnd === 0) { + return true; + } + // If we got a response but no content, likely an HTTP error (4xx/5xx) + if (scriptEntry.responseEnd > 0 && scriptEntry.responseStart > 0) { + return true; + } + + if (scriptEntry.responseStatus === 0) { + return true; + } + } + + return false; +} + /** * Waits for Clerk to be properly loaded with a timeout mechanism. * Uses polling to check if Clerk becomes available within the specified timeout. @@ -117,11 +155,13 @@ function waitForClerkWithTimeout(timeoutMs: number): Promise('script[data-clerk-js-script]'); - - if (existingScript) { - return waitForClerkWithTimeout(timeout); - } - if (!opts?.publishableKey) { errorThrower.throwMissingPublishableKeyError(); return null; } + const scriptUrl = clerkJsScriptUrl(opts); + const existingScript = document.querySelector('script[data-clerk-js-script]'); + + if (existingScript) { + if (hasScriptRequestError(scriptUrl)) { + existingScript.remove(); + } else { + try { + return await waitForClerkWithTimeout(timeout); + } catch { + existingScript.remove(); + } + } + } + const loadPromise = waitForClerkWithTimeout(timeout); - loadScript(clerkJsScriptUrl(opts), { + loadScript(scriptUrl, { async: true, crossOrigin: 'anonymous', nonce: opts.nonce,