From 49df95fbbaa75f3b72b60055a1ffe8e7339edbee Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 25 Sep 2025 15:01:35 -0500 Subject: [PATCH 1/5] Improve error handling for when clerk fails to load. --- packages/shared/src/errors/runtimeError.ts | 8 +++++++- packages/shared/src/loadClerkJsScript.ts | 15 ++++++++++----- packages/shared/src/loadScript.ts | 9 ++++++--- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/packages/shared/src/errors/runtimeError.ts b/packages/shared/src/errors/runtimeError.ts index 643f3f63df9..3c341c78edf 100644 --- a/packages/shared/src/errors/runtimeError.ts +++ b/packages/shared/src/errors/runtimeError.ts @@ -18,7 +18,12 @@ export class ClerkRuntimeError extends Error { */ code: string; - constructor(message: string, { code }: { code: string }) { + /** + * The original error that was caught to throw an instance of ClerkRuntimeError. + */ + cause?: Error; + + constructor(message: string, { code, cause }: { code: string; cause?: Error }) { const prefix = '🔒 Clerk:'; const regex = new RegExp(prefix.replace(' ', '\\s*'), 'i'); const sanitized = message.replace(regex, ''); @@ -27,6 +32,7 @@ export class ClerkRuntimeError extends Error { Object.setPrototypeOf(this, ClerkRuntimeError.prototype); + this.cause = cause; this.code = code; this.message = _message; this.clerkRuntimeError = true; diff --git a/packages/shared/src/loadClerkJsScript.ts b/packages/shared/src/loadClerkJsScript.ts index 68f81af1778..1cb78a1e68c 100644 --- a/packages/shared/src/loadClerkJsScript.ts +++ b/packages/shared/src/loadClerkJsScript.ts @@ -1,13 +1,15 @@ import type { ClerkOptions, SDKMetadata, Without } from '@clerk/types'; -import { buildErrorThrower } from './error'; +import { buildErrorThrower, ClerkRuntimeError } from './error'; import { createDevOrStagingUrlCache, parsePublishableKey } from './keys'; import { loadScript } from './loadScript'; import { isValidProxyUrl, proxyUrlToAbsoluteURL } from './proxy'; import { addClerkPrefix } from './url'; import { versionSelector } from './versionSelector'; -const FAILED_TO_LOAD_ERROR = 'Clerk: Failed to load Clerk'; +const ERROR_CODE = 'failed_to_load_clerk_js'; +const ERROR_CODE_TIMEOUT = 'failed_to_load_clerk_js_timeout'; +const FAILED_TO_LOAD_ERROR = 'Failed to load Clerk'; const { isDevOrStagingUrl } = createDevOrStagingUrlCache(); @@ -96,7 +98,7 @@ function waitForClerkWithTimeout(timeoutMs: number): Promise { - throw new Error(FAILED_TO_LOAD_ERROR); + }).catch(error => { + throw new ClerkRuntimeError(FAILED_TO_LOAD_ERROR + (error.message ? `, ${error.message}` : ''), { + code: ERROR_CODE, + cause: error, + }); }); return loadPromise; diff --git a/packages/shared/src/loadScript.ts b/packages/shared/src/loadScript.ts index ae5ea5859b0..804a58a7330 100644 --- a/packages/shared/src/loadScript.ts +++ b/packages/shared/src/loadScript.ts @@ -11,6 +11,9 @@ type LoadScriptOptions = { beforeLoad?: (script: HTMLScriptElement) => void; }; +/** + * + */ export async function loadScript(src = '', opts: LoadScriptOptions): Promise { const { async, defer, beforeLoad, crossOrigin, nonce } = opts || {}; @@ -21,7 +24,7 @@ export async function loadScript(src = '', opts: LoadScriptOptions): Promise { + script.addEventListener('error', event => { script.remove(); - reject(); + reject(event.error); }); script.src = src; From 151c46c8144f1998a6dc83b921ea160123ba40ea Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 25 Sep 2025 15:29:25 -0500 Subject: [PATCH 2/5] Adds changeset --- .changeset/tangy-bees-follow.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tangy-bees-follow.md diff --git a/.changeset/tangy-bees-follow.md b/.changeset/tangy-bees-follow.md new file mode 100644 index 00000000000..2f0b945d942 --- /dev/null +++ b/.changeset/tangy-bees-follow.md @@ -0,0 +1,5 @@ +--- +'@clerk/shared': minor +--- + +Improve error handling when loading clerk-js. From 1d2bd366c9673b03238db57bb665ac7a189a4ac6 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 25 Sep 2025 16:05:42 -0500 Subject: [PATCH 3/5] Update packages/shared/src/loadScript.ts --- packages/shared/src/loadScript.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/shared/src/loadScript.ts b/packages/shared/src/loadScript.ts index 804a58a7330..b6b36c3d35b 100644 --- a/packages/shared/src/loadScript.ts +++ b/packages/shared/src/loadScript.ts @@ -11,9 +11,6 @@ type LoadScriptOptions = { beforeLoad?: (script: HTMLScriptElement) => void; }; -/** - * - */ export async function loadScript(src = '', opts: LoadScriptOptions): Promise { const { async, defer, beforeLoad, crossOrigin, nonce } = opts || {}; From 81d9f0d93a33b0c4caa112f2711804594580a087 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 25 Sep 2025 16:09:11 -0500 Subject: [PATCH 4/5] Improve error handler listener --- packages/shared/src/loadScript.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/loadScript.ts b/packages/shared/src/loadScript.ts index b6b36c3d35b..e81f8730ef2 100644 --- a/packages/shared/src/loadScript.ts +++ b/packages/shared/src/loadScript.ts @@ -39,7 +39,7 @@ export async function loadScript(src = '', opts: LoadScriptOptions): Promise { script.remove(); - reject(event.error); + reject(event.error ?? new Error(`failed to load script: ${src}`)); }); script.src = src; From be1530fba1a9dd204db956e98b3105ba983fc703 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 25 Sep 2025 16:15:02 -0500 Subject: [PATCH 5/5] updates tests --- packages/shared/src/__tests__/loadClerkJsScript.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/shared/src/__tests__/loadClerkJsScript.test.ts b/packages/shared/src/__tests__/loadClerkJsScript.test.ts index bc8b58824ae..d135708634d 100644 --- a/packages/shared/src/__tests__/loadClerkJsScript.test.ts +++ b/packages/shared/src/__tests__/loadClerkJsScript.test.ts @@ -1,3 +1,4 @@ +import { ClerkRuntimeError } from '../error'; import { buildClerkJsScriptAttributes, clerkJsScriptUrl, @@ -86,8 +87,8 @@ describe('loadClerkJsScript(options)', () => { rejectedWith = error; } - expect(rejectedWith).toBeInstanceOf(Error); - expect(rejectedWith.message).toBe('Clerk: Failed to load Clerk'); + expect(rejectedWith).toBeInstanceOf(ClerkRuntimeError); + expect(rejectedWith.message).toContain('Clerk: Failed to load Clerk'); expect((window as any).Clerk).toBeUndefined(); }); @@ -137,8 +138,8 @@ describe('loadClerkJsScript(options)', () => { await loadPromise; fail('Should have thrown error'); } catch (error) { - expect(error).toBeInstanceOf(Error); - expect((error as Error).message).toBe('Clerk: Failed to load Clerk'); + expect(error).toBeInstanceOf(ClerkRuntimeError); + expect((error as Error).message).toContain('Clerk: Failed to load Clerk'); // The malformed Clerk object should still be there since it was set expect((window as any).Clerk).toEqual({ status: 'ready' }); }