@clerk/shared 4.30.1-canary.v20260824165645 → 4.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/internal/clerk-js/protectCheck.d.mts +19 -1
- package/dist/internal/clerk-js/protectCheck.d.ts +19 -1
- package/dist/internal/clerk-js/protectCheck.js +64 -5
- package/dist/internal/clerk-js/protectCheck.js.map +1 -1
- package/dist/internal/clerk-js/protectCheck.mjs +64 -6
- package/dist/internal/clerk-js/protectCheck.mjs.map +1 -1
- package/dist/loadClerkJsScript.js +1 -1
- package/dist/loadClerkJsScript.js.map +1 -1
- package/dist/loadClerkJsScript.mjs +1 -1
- package/dist/loadClerkJsScript.mjs.map +1 -1
- package/dist/types/clerk.d.mts +9 -0
- package/dist/types/clerk.d.ts +9 -0
- package/dist/types/protectConfig.d.mts +25 -0
- package/dist/types/protectConfig.d.ts +25 -0
- package/dist/versionSelector.js +1 -1
- package/dist/versionSelector.js.map +1 -1
- package/dist/versionSelector.mjs +1 -1
- package/dist/versionSelector.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -25,7 +25,25 @@ interface ExecuteProtectCheckOptions {
|
|
|
25
25
|
* Scripts that don't honor the signal will continue to run; this is best-effort by design.
|
|
26
26
|
*/
|
|
27
27
|
signal?: AbortSignal;
|
|
28
|
+
/**
|
|
29
|
+
* Overrides how long to wait for the challenge module to LOAD. Per-instance and per-loader
|
|
30
|
+
* config, since the right value depends on the population an instance serves; a non-positive
|
|
31
|
+
* or non-numeric value falls back to {@link DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS}.
|
|
32
|
+
*
|
|
33
|
+
* Bounds the handoff only — never the challenge. See the note on the constant.
|
|
34
|
+
*/
|
|
35
|
+
loadTimeoutMs?: number;
|
|
28
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Default bound on LOADING the challenge module.
|
|
39
|
+
*
|
|
40
|
+
* Its only job is a network that accepts a connection and then never answers, because every
|
|
41
|
+
* other load failure — a CSP block, DNS, a 404, a body that isn't a valid module — rejects the
|
|
42
|
+
* dynamic import on its own and needs no timer to notice. That makes a generous value the safe
|
|
43
|
+
* one: nothing legitimate is waiting on this timer, while a value tight enough to fire on a
|
|
44
|
+
* genuinely slow connection would fail a load that was going to succeed.
|
|
45
|
+
*/
|
|
46
|
+
declare const DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS = 60000;
|
|
29
47
|
/**
|
|
30
48
|
* Loads the Protect challenge SDK from `protectCheck.sdkUrl`, hands it the container element
|
|
31
49
|
* and the spec-defined init payload (`token`, `uiHints`, `signal`), and returns the proof
|
|
@@ -49,4 +67,4 @@ interface ExecuteProtectCheckOptions {
|
|
|
49
67
|
*/
|
|
50
68
|
declare function executeProtectCheck(protectCheck: Pick<ProtectCheckResource, 'sdkUrl' | 'token' | 'uiHints'>, container: HTMLDivElement, options?: ExecuteProtectCheckOptions): Promise<string>;
|
|
51
69
|
//#endregion
|
|
52
|
-
export { ExecuteProtectCheckOptions, executeProtectCheck };
|
|
70
|
+
export { DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS, ExecuteProtectCheckOptions, executeProtectCheck };
|
|
@@ -25,7 +25,25 @@ interface ExecuteProtectCheckOptions {
|
|
|
25
25
|
* Scripts that don't honor the signal will continue to run; this is best-effort by design.
|
|
26
26
|
*/
|
|
27
27
|
signal?: AbortSignal;
|
|
28
|
+
/**
|
|
29
|
+
* Overrides how long to wait for the challenge module to LOAD. Per-instance and per-loader
|
|
30
|
+
* config, since the right value depends on the population an instance serves; a non-positive
|
|
31
|
+
* or non-numeric value falls back to {@link DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS}.
|
|
32
|
+
*
|
|
33
|
+
* Bounds the handoff only — never the challenge. See the note on the constant.
|
|
34
|
+
*/
|
|
35
|
+
loadTimeoutMs?: number;
|
|
28
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Default bound on LOADING the challenge module.
|
|
39
|
+
*
|
|
40
|
+
* Its only job is a network that accepts a connection and then never answers, because every
|
|
41
|
+
* other load failure — a CSP block, DNS, a 404, a body that isn't a valid module — rejects the
|
|
42
|
+
* dynamic import on its own and needs no timer to notice. That makes a generous value the safe
|
|
43
|
+
* one: nothing legitimate is waiting on this timer, while a value tight enough to fire on a
|
|
44
|
+
* genuinely slow connection would fail a load that was going to succeed.
|
|
45
|
+
*/
|
|
46
|
+
declare const DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS = 60000;
|
|
29
47
|
/**
|
|
30
48
|
* Loads the Protect challenge SDK from `protectCheck.sdkUrl`, hands it the container element
|
|
31
49
|
* and the spec-defined init payload (`token`, `uiHints`, `signal`), and returns the proof
|
|
@@ -49,4 +67,4 @@ interface ExecuteProtectCheckOptions {
|
|
|
49
67
|
*/
|
|
50
68
|
declare function executeProtectCheck(protectCheck: Pick<ProtectCheckResource, 'sdkUrl' | 'token' | 'uiHints'>, container: HTMLDivElement, options?: ExecuteProtectCheckOptions): Promise<string>;
|
|
51
69
|
//#endregion
|
|
52
|
-
export { ExecuteProtectCheckOptions, executeProtectCheck };
|
|
70
|
+
export { DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS, ExecuteProtectCheckOptions, executeProtectCheck };
|
|
@@ -4,6 +4,66 @@ require('../../_chunks/error-C-B61RHn.js');
|
|
|
4
4
|
|
|
5
5
|
//#region src/internal/clerk-js/protectCheck.ts
|
|
6
6
|
/**
|
|
7
|
+
* Default bound on LOADING the challenge module.
|
|
8
|
+
*
|
|
9
|
+
* Its only job is a network that accepts a connection and then never answers, because every
|
|
10
|
+
* other load failure — a CSP block, DNS, a 404, a body that isn't a valid module — rejects the
|
|
11
|
+
* dynamic import on its own and needs no timer to notice. That makes a generous value the safe
|
|
12
|
+
* one: nothing legitimate is waiting on this timer, while a value tight enough to fire on a
|
|
13
|
+
* genuinely slow connection would fail a load that was going to succeed.
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS = 6e4;
|
|
16
|
+
/**
|
|
17
|
+
* Ceiling on the configured load bound. `setTimeout` stores its delay in a signed 32-bit int, so a
|
|
18
|
+
* larger value overflows and fires immediately — which would make every load fail instantly, the
|
|
19
|
+
* exact opposite of what an operator asking for a long timeout wanted. Clamping rather than
|
|
20
|
+
* rejecting keeps a fat-fingered config from breaking sign-in.
|
|
21
|
+
*/
|
|
22
|
+
const MAX_PROTECT_CHECK_LOAD_TIMEOUT_MS = 6e5;
|
|
23
|
+
function resolveLoadTimeoutMs(configured) {
|
|
24
|
+
if (typeof configured !== "number" || !Number.isFinite(configured) || configured <= 0) return DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS;
|
|
25
|
+
return Math.min(configured, MAX_PROTECT_CHECK_LOAD_TIMEOUT_MS);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Races the dynamic import against `timeoutMs`, always clearing the timer so a fast load does not
|
|
29
|
+
* leave one pending.
|
|
30
|
+
*
|
|
31
|
+
* The bound stops at the import on purpose. Once `mod.default` is called the challenge owns its
|
|
32
|
+
* own deadline and the host imposes none: the host cannot know an honest duration for a challenge
|
|
33
|
+
* whose type is chosen server-side, per decision, and whose work it deliberately knows nothing
|
|
34
|
+
* about — waiting on a person, or moving a server-chosen number of bytes over an unknown link. A
|
|
35
|
+
* host-side wall over execution aborts legitimate challenges and reports them as timeouts, and
|
|
36
|
+
* since a re-run restarts the work, retrying cannot win on any connection slow enough to trip it.
|
|
37
|
+
*
|
|
38
|
+
* The abort signal is raced too. A stalled import cannot itself be cancelled, but without this the
|
|
39
|
+
* caller's abort would not settle anything: an unmounted component would keep this promise, its
|
|
40
|
+
* closures and its timer alive for the whole load bound, and then report a load failure for what
|
|
41
|
+
* was really a cancellation.
|
|
42
|
+
*/
|
|
43
|
+
function importWithTimeout(url, timeoutMs, signal) {
|
|
44
|
+
let timeoutId;
|
|
45
|
+
let onAbort;
|
|
46
|
+
const expiry = new Promise((_, reject) => {
|
|
47
|
+
timeoutId = setTimeout(() => reject(/* @__PURE__ */ new Error("Protect check script load timed out")), timeoutMs);
|
|
48
|
+
});
|
|
49
|
+
const aborted = new Promise((_, reject) => {
|
|
50
|
+
if (!signal) return;
|
|
51
|
+
onAbort = () => reject(/* @__PURE__ */ new Error("Protect check aborted during load"));
|
|
52
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
53
|
+
});
|
|
54
|
+
return Promise.race([
|
|
55
|
+
import(
|
|
56
|
+
/* webpackIgnore: true */
|
|
57
|
+
url
|
|
58
|
+
),
|
|
59
|
+
expiry,
|
|
60
|
+
aborted
|
|
61
|
+
]).finally(() => {
|
|
62
|
+
clearTimeout(timeoutId);
|
|
63
|
+
if (signal && onAbort) signal.removeEventListener("abort", onAbort);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
7
67
|
* Validates the `sdk_url` returned by the server before passing it to dynamic `import()`.
|
|
8
68
|
*
|
|
9
69
|
* Rejects:
|
|
@@ -50,17 +110,15 @@ function assertValidSdkUrl(sdkUrl) {
|
|
|
50
110
|
* - `protect_check_execution_failed` — the script's default export threw
|
|
51
111
|
*/
|
|
52
112
|
async function executeProtectCheck(protectCheck, container, options = {}) {
|
|
53
|
-
const { signal, setWidgetVisible } = options;
|
|
113
|
+
const { signal, setWidgetVisible, loadTimeoutMs } = options;
|
|
54
114
|
const { sdkUrl, token, uiHints } = protectCheck;
|
|
55
115
|
const validated = assertValidSdkUrl(sdkUrl);
|
|
56
116
|
if (signal?.aborted) throw new require_clerkRuntimeError.ClerkRuntimeError("Protect check aborted by caller", { code: "protect_check_aborted" });
|
|
57
117
|
let mod;
|
|
58
118
|
try {
|
|
59
|
-
mod = await
|
|
60
|
-
/* webpackIgnore: true */
|
|
61
|
-
validated.toString()
|
|
62
|
-
);
|
|
119
|
+
mod = await importWithTimeout(validated.toString(), resolveLoadTimeoutMs(loadTimeoutMs), signal);
|
|
63
120
|
} catch {
|
|
121
|
+
if (signal?.aborted) throw new require_clerkRuntimeError.ClerkRuntimeError("Protect check aborted by caller", { code: "protect_check_aborted" });
|
|
64
122
|
throw new require_clerkRuntimeError.ClerkRuntimeError("Protect check script failed to load. This is commonly caused by a Content Security Policy that blocks the script origin (add it to your script-src directive), a network error, or an invalid module.", { code: "protect_check_script_load_failed" });
|
|
65
123
|
}
|
|
66
124
|
if (signal?.aborted) throw new require_clerkRuntimeError.ClerkRuntimeError("Protect check aborted by caller", { code: "protect_check_aborted" });
|
|
@@ -83,5 +141,6 @@ async function executeProtectCheck(protectCheck, container, options = {}) {
|
|
|
83
141
|
}
|
|
84
142
|
|
|
85
143
|
//#endregion
|
|
144
|
+
exports.DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS = DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS;
|
|
86
145
|
exports.executeProtectCheck = executeProtectCheck;
|
|
87
146
|
//# sourceMappingURL=protectCheck.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protectCheck.js","names":["ClerkRuntimeError"],"sources":["../../../src/internal/clerk-js/protectCheck.ts"],"sourcesContent":["import { ClerkRuntimeError } from '../../error';\nimport type { ProtectCheckResource } from '../../types';\n\nexport interface ExecuteProtectCheckOptions {\n /**\n * Host-provided visibility handshake, forwarded to the script verbatim as\n * `setWidgetVisible` in the init payload. The script calls it right before revealing UI in\n * the container (and with `false` once its widget is done); the returned promise resolves\n * only after the host has applied the change to the DOM (e.g. removed its own loading\n * spinner), so the script can sequence its reveal without a frame of overlap. A script that\n * knows its widget is imminent may call it immediately to avoid a spinner flash. Scripts\n * must treat the field as optional — older hosts don't provide it.\n */\n setWidgetVisible?: (visible: boolean) => Promise<void>;\n /**\n * Signals that the caller no longer needs the proof token (component unmounted, user\n * navigated away, etc.). When the signal aborts:\n * - If the script has not yet been imported, `executeProtectCheck` rejects with\n * `protect_check_aborted` without loading the script.\n * - The signal is forwarded to the script as `{ signal }` in the second argument so\n * cooperating SDKs can cancel any in-flight UI / network work.\n * - Even if the script ignores the signal and resolves with a token, the helper\n * re-checks `signal.aborted` after the await and rejects with `protect_check_aborted`\n * so the caller never observes a \"successful\" abort.\n *\n * Scripts that don't honor the signal will continue to run; this is best-effort by design.\n */\n signal?: AbortSignal;\n}\n\ninterface ScriptInitOptions {\n token: string;\n uiHints?: Record<string, string>;\n signal?: AbortSignal;\n setWidgetVisible?: (visible: boolean) => Promise<void>;\n}\n\ntype ScriptDefault = (container: HTMLDivElement, init: ScriptInitOptions) => Promise<string>;\n\n/**\n * Validates the `sdk_url` returned by the server before passing it to dynamic `import()`.\n *\n * Rejects:\n * - Anything that fails URL parsing (relative paths, garbage strings)\n * - Non-`https:` schemes — including `http:`, `data:`, `blob:`, `javascript:`. The server\n * always returns an HTTPS URL, but the dynamic-import primitive accepts `data:`/`blob:`\n * modules which would let a tampered response inject arbitrary code into the host page.\n * - URLs containing credentials (`user:pass@host`) — phishing surface, no legitimate use.\n *\n * Throws `ClerkRuntimeError` with code `protect_check_invalid_sdk_url`. We deliberately do\n * NOT silently strip an invalid `protect_check` from the resource: the gate must remain\n * present so the user can't bypass it by manipulating the response. Fail-closed.\n */\nfunction assertValidSdkUrl(sdkUrl: string): URL {\n let parsed: URL;\n try {\n parsed = new URL(sdkUrl);\n } catch {\n throw new ClerkRuntimeError('Protect check sdk_url is not a valid URL', {\n code: 'protect_check_invalid_sdk_url',\n });\n }\n if (parsed.protocol !== 'https:') {\n throw new ClerkRuntimeError('Protect check sdk_url must use HTTPS', {\n code: 'protect_check_invalid_sdk_url',\n });\n }\n if (parsed.username || parsed.password) {\n throw new ClerkRuntimeError('Protect check sdk_url must not contain credentials', {\n code: 'protect_check_invalid_sdk_url',\n });\n }\n return parsed;\n}\n\n/**\n * Loads the Protect challenge SDK from `protectCheck.sdkUrl`, hands it the container element\n * and the spec-defined init payload (`token`, `uiHints`, `signal`), and returns the proof\n * token the SDK produces.\n *\n * The SDK script must:\n * - Be a valid ES module served over HTTPS\n * - Have a default export of the shape `(container, { token, uiHints, signal }) => Promise<string>`\n * - Honor the `signal` to abort any pending work (best-effort)\n *\n * Only the minimal fields (`token`, optional `ui_hints`) are surfaced to the script — the\n * full sign-up/sign-in resource is intentionally NOT passed, to minimize the trust surface\n * granted to third-party Protect scripts.\n *\n * Failure modes are surfaced as `ClerkRuntimeError` with one of:\n * - `protect_check_invalid_sdk_url` — URL fails the safety checks above\n * - `protect_check_aborted` — caller aborted before or during execution\n * - `protect_check_script_load_failed` — network error, CSP block, or invalid module\n * - `protect_check_invalid_script` — module loaded but no callable default export\n * - `protect_check_execution_failed` — the script's default export threw\n */\nexport async function executeProtectCheck(\n protectCheck: Pick<ProtectCheckResource, 'sdkUrl' | 'token' | 'uiHints'>,\n container: HTMLDivElement,\n options: ExecuteProtectCheckOptions = {},\n): Promise<string> {\n const { signal, setWidgetVisible } = options;\n const { sdkUrl, token, uiHints } = protectCheck;\n\n const validated = assertValidSdkUrl(sdkUrl);\n\n if (signal?.aborted) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n\n let mod: Record<string, unknown>;\n try {\n mod = await import(/* webpackIgnore: true */ validated.toString());\n } catch {\n // Surface a generic message and deliberately omit the original error: Chromium/Firefox embed\n // the sdk_url in the dynamic-import failure text, which a tampered response could plant in the UI.\n throw new ClerkRuntimeError(\n 'Protect check script failed to load. This is commonly caused by a Content Security ' +\n 'Policy that blocks the script origin (add it to your script-src directive), a ' +\n 'network error, or an invalid module.',\n { code: 'protect_check_script_load_failed' },\n );\n }\n\n if (signal?.aborted) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n\n if (typeof mod.default !== 'function') {\n throw new ClerkRuntimeError('Protect check script does not export a default function', {\n code: 'protect_check_invalid_script',\n });\n }\n\n let proofToken: string;\n try {\n proofToken = await (mod.default as ScriptDefault)(container, { token, uiHints, signal, setWidgetVisible });\n } catch (err) {\n // Distinguish abort-induced rejections from genuine script errors: only relabel as\n // `protect_check_aborted` when the error looks like an abort (`AbortError`), otherwise\n // surface the script's actual failure so production diagnostics aren't masked.\n const looksLikeAbort = err instanceof Error && err.name === 'AbortError';\n if (signal?.aborted && looksLikeAbort) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n const original = err instanceof Error ? err.message : String(err);\n throw new ClerkRuntimeError(`Protect check script execution failed: ${original}`, {\n code: 'protect_check_execution_failed',\n });\n }\n\n // The script may have ignored the signal and resolved with a token after the abort fired.\n // Re-check here so callers get a consistent contract: if you aborted, you never see a token.\n if (signal?.aborted) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n\n return proofToken;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqDA,SAAS,kBAAkB,QAAqB;CAC9C,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,IAAI,MAAM;CACzB,QAAQ;EACN,MAAM,IAAIA,4CAAkB,4CAA4C,EACtE,MAAM,gCACR,CAAC;CACH;CACA,IAAI,OAAO,aAAa,UACtB,MAAM,IAAIA,4CAAkB,wCAAwC,EAClE,MAAM,gCACR,CAAC;CAEH,IAAI,OAAO,YAAY,OAAO,UAC5B,MAAM,IAAIA,4CAAkB,sDAAsD,EAChF,MAAM,gCACR,CAAC;CAEH,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;AAuBA,eAAsB,oBACpB,cACA,WACA,UAAsC,CAAC,GACtB;CACjB,MAAM,EAAE,QAAQ,qBAAqB;CACrC,MAAM,EAAE,QAAQ,OAAO,YAAY;CAEnC,MAAM,YAAY,kBAAkB,MAAM;CAE1C,IAAI,QAAQ,SACV,MAAM,IAAIA,4CAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;CAGlG,IAAI;CACJ,IAAI;EACF,MAAM,MAAM;;GAAiC,UAAU,SAAS;;CAClE,QAAQ;EAGN,MAAM,IAAIA,4CACR,yMAGA,EAAE,MAAM,mCAAmC,CAC7C;CACF;CAEA,IAAI,QAAQ,SACV,MAAM,IAAIA,4CAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;CAGlG,IAAI,OAAO,IAAI,YAAY,YACzB,MAAM,IAAIA,4CAAkB,2DAA2D,EACrF,MAAM,+BACR,CAAC;CAGH,IAAI;CACJ,IAAI;EACF,aAAa,MAAO,IAAI,QAA0B,WAAW;GAAE;GAAO;GAAS;GAAQ;EAAiB,CAAC;CAC3G,SAAS,KAAK;EAIZ,MAAM,iBAAiB,eAAe,SAAS,IAAI,SAAS;EAC5D,IAAI,QAAQ,WAAW,gBACrB,MAAM,IAAIA,4CAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;EAGlG,MAAM,IAAIA,4CAAkB,0CADX,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,KACkB,EAChF,MAAM,iCACR,CAAC;CACH;CAIA,IAAI,QAAQ,SACV,MAAM,IAAIA,4CAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;CAGlG,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"protectCheck.js","names":["ClerkRuntimeError"],"sources":["../../../src/internal/clerk-js/protectCheck.ts"],"sourcesContent":["import { ClerkRuntimeError } from '../../error';\nimport type { ProtectCheckResource } from '../../types';\n\nexport interface ExecuteProtectCheckOptions {\n /**\n * Host-provided visibility handshake, forwarded to the script verbatim as\n * `setWidgetVisible` in the init payload. The script calls it right before revealing UI in\n * the container (and with `false` once its widget is done); the returned promise resolves\n * only after the host has applied the change to the DOM (e.g. removed its own loading\n * spinner), so the script can sequence its reveal without a frame of overlap. A script that\n * knows its widget is imminent may call it immediately to avoid a spinner flash. Scripts\n * must treat the field as optional — older hosts don't provide it.\n */\n setWidgetVisible?: (visible: boolean) => Promise<void>;\n /**\n * Signals that the caller no longer needs the proof token (component unmounted, user\n * navigated away, etc.). When the signal aborts:\n * - If the script has not yet been imported, `executeProtectCheck` rejects with\n * `protect_check_aborted` without loading the script.\n * - The signal is forwarded to the script as `{ signal }` in the second argument so\n * cooperating SDKs can cancel any in-flight UI / network work.\n * - Even if the script ignores the signal and resolves with a token, the helper\n * re-checks `signal.aborted` after the await and rejects with `protect_check_aborted`\n * so the caller never observes a \"successful\" abort.\n *\n * Scripts that don't honor the signal will continue to run; this is best-effort by design.\n */\n signal?: AbortSignal;\n /**\n * Overrides how long to wait for the challenge module to LOAD. Per-instance and per-loader\n * config, since the right value depends on the population an instance serves; a non-positive\n * or non-numeric value falls back to {@link DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS}.\n *\n * Bounds the handoff only — never the challenge. See the note on the constant.\n */\n loadTimeoutMs?: number;\n}\n\n/**\n * Default bound on LOADING the challenge module.\n *\n * Its only job is a network that accepts a connection and then never answers, because every\n * other load failure — a CSP block, DNS, a 404, a body that isn't a valid module — rejects the\n * dynamic import on its own and needs no timer to notice. That makes a generous value the safe\n * one: nothing legitimate is waiting on this timer, while a value tight enough to fire on a\n * genuinely slow connection would fail a load that was going to succeed.\n */\nexport const DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS = 60_000;\n\n/**\n * Ceiling on the configured load bound. `setTimeout` stores its delay in a signed 32-bit int, so a\n * larger value overflows and fires immediately — which would make every load fail instantly, the\n * exact opposite of what an operator asking for a long timeout wanted. Clamping rather than\n * rejecting keeps a fat-fingered config from breaking sign-in.\n */\nconst MAX_PROTECT_CHECK_LOAD_TIMEOUT_MS = 600_000;\n\nfunction resolveLoadTimeoutMs(configured: number | undefined): number {\n if (typeof configured !== 'number' || !Number.isFinite(configured) || configured <= 0) {\n return DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS;\n }\n return Math.min(configured, MAX_PROTECT_CHECK_LOAD_TIMEOUT_MS);\n}\n\n/**\n * Races the dynamic import against `timeoutMs`, always clearing the timer so a fast load does not\n * leave one pending.\n *\n * The bound stops at the import on purpose. Once `mod.default` is called the challenge owns its\n * own deadline and the host imposes none: the host cannot know an honest duration for a challenge\n * whose type is chosen server-side, per decision, and whose work it deliberately knows nothing\n * about — waiting on a person, or moving a server-chosen number of bytes over an unknown link. A\n * host-side wall over execution aborts legitimate challenges and reports them as timeouts, and\n * since a re-run restarts the work, retrying cannot win on any connection slow enough to trip it.\n *\n * The abort signal is raced too. A stalled import cannot itself be cancelled, but without this the\n * caller's abort would not settle anything: an unmounted component would keep this promise, its\n * closures and its timer alive for the whole load bound, and then report a load failure for what\n * was really a cancellation.\n */\nfunction importWithTimeout(\n url: string,\n timeoutMs: number,\n signal: AbortSignal | undefined,\n): Promise<Record<string, unknown>> {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n let onAbort: (() => void) | undefined;\n\n const expiry = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => reject(new Error('Protect check script load timed out')), timeoutMs);\n });\n const aborted = new Promise<never>((_, reject) => {\n if (!signal) {\n return;\n }\n onAbort = () => reject(new Error('Protect check aborted during load'));\n signal.addEventListener('abort', onAbort, { once: true });\n });\n\n return Promise.race([import(/* webpackIgnore: true */ url), expiry, aborted]).finally(() => {\n clearTimeout(timeoutId);\n if (signal && onAbort) {\n signal.removeEventListener('abort', onAbort);\n }\n }) as Promise<Record<string, unknown>>;\n}\n\ninterface ScriptInitOptions {\n token: string;\n uiHints?: Record<string, string>;\n signal?: AbortSignal;\n setWidgetVisible?: (visible: boolean) => Promise<void>;\n}\n\ntype ScriptDefault = (container: HTMLDivElement, init: ScriptInitOptions) => Promise<string>;\n\n/**\n * Validates the `sdk_url` returned by the server before passing it to dynamic `import()`.\n *\n * Rejects:\n * - Anything that fails URL parsing (relative paths, garbage strings)\n * - Non-`https:` schemes — including `http:`, `data:`, `blob:`, `javascript:`. The server\n * always returns an HTTPS URL, but the dynamic-import primitive accepts `data:`/`blob:`\n * modules which would let a tampered response inject arbitrary code into the host page.\n * - URLs containing credentials (`user:pass@host`) — phishing surface, no legitimate use.\n *\n * Throws `ClerkRuntimeError` with code `protect_check_invalid_sdk_url`. We deliberately do\n * NOT silently strip an invalid `protect_check` from the resource: the gate must remain\n * present so the user can't bypass it by manipulating the response. Fail-closed.\n */\nfunction assertValidSdkUrl(sdkUrl: string): URL {\n let parsed: URL;\n try {\n parsed = new URL(sdkUrl);\n } catch {\n throw new ClerkRuntimeError('Protect check sdk_url is not a valid URL', {\n code: 'protect_check_invalid_sdk_url',\n });\n }\n if (parsed.protocol !== 'https:') {\n throw new ClerkRuntimeError('Protect check sdk_url must use HTTPS', {\n code: 'protect_check_invalid_sdk_url',\n });\n }\n if (parsed.username || parsed.password) {\n throw new ClerkRuntimeError('Protect check sdk_url must not contain credentials', {\n code: 'protect_check_invalid_sdk_url',\n });\n }\n return parsed;\n}\n\n/**\n * Loads the Protect challenge SDK from `protectCheck.sdkUrl`, hands it the container element\n * and the spec-defined init payload (`token`, `uiHints`, `signal`), and returns the proof\n * token the SDK produces.\n *\n * The SDK script must:\n * - Be a valid ES module served over HTTPS\n * - Have a default export of the shape `(container, { token, uiHints, signal }) => Promise<string>`\n * - Honor the `signal` to abort any pending work (best-effort)\n *\n * Only the minimal fields (`token`, optional `ui_hints`) are surfaced to the script — the\n * full sign-up/sign-in resource is intentionally NOT passed, to minimize the trust surface\n * granted to third-party Protect scripts.\n *\n * Failure modes are surfaced as `ClerkRuntimeError` with one of:\n * - `protect_check_invalid_sdk_url` — URL fails the safety checks above\n * - `protect_check_aborted` — caller aborted before or during execution\n * - `protect_check_script_load_failed` — network error, CSP block, or invalid module\n * - `protect_check_invalid_script` — module loaded but no callable default export\n * - `protect_check_execution_failed` — the script's default export threw\n */\nexport async function executeProtectCheck(\n protectCheck: Pick<ProtectCheckResource, 'sdkUrl' | 'token' | 'uiHints'>,\n container: HTMLDivElement,\n options: ExecuteProtectCheckOptions = {},\n): Promise<string> {\n const { signal, setWidgetVisible, loadTimeoutMs } = options;\n const { sdkUrl, token, uiHints } = protectCheck;\n\n const validated = assertValidSdkUrl(sdkUrl);\n\n if (signal?.aborted) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n\n let mod: Record<string, unknown>;\n try {\n mod = await importWithTimeout(validated.toString(), resolveLoadTimeoutMs(loadTimeoutMs), signal);\n } catch {\n // An abort that landed mid-load is a cancellation, not a load failure. Checked first so the\n // caller gets the same contract it does everywhere else: if you aborted, you never see\n // anything but `protect_check_aborted`.\n if (signal?.aborted) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n // Surface a generic message and deliberately omit the original error: Chromium/Firefox embed\n // the sdk_url in the dynamic-import failure text, which a tampered response could plant in the UI.\n throw new ClerkRuntimeError(\n 'Protect check script failed to load. This is commonly caused by a Content Security ' +\n 'Policy that blocks the script origin (add it to your script-src directive), a ' +\n 'network error, or an invalid module.',\n { code: 'protect_check_script_load_failed' },\n );\n }\n\n if (signal?.aborted) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n\n if (typeof mod.default !== 'function') {\n throw new ClerkRuntimeError('Protect check script does not export a default function', {\n code: 'protect_check_invalid_script',\n });\n }\n\n let proofToken: string;\n try {\n proofToken = await (mod.default as ScriptDefault)(container, { token, uiHints, signal, setWidgetVisible });\n } catch (err) {\n // Distinguish abort-induced rejections from genuine script errors: only relabel as\n // `protect_check_aborted` when the error looks like an abort (`AbortError`), otherwise\n // surface the script's actual failure so production diagnostics aren't masked.\n const looksLikeAbort = err instanceof Error && err.name === 'AbortError';\n if (signal?.aborted && looksLikeAbort) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n const original = err instanceof Error ? err.message : String(err);\n throw new ClerkRuntimeError(`Protect check script execution failed: ${original}`, {\n code: 'protect_check_execution_failed',\n });\n }\n\n // The script may have ignored the signal and resolved with a token after the abort fired.\n // Re-check here so callers get a consistent contract: if you aborted, you never see a token.\n if (signal?.aborted) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n\n return proofToken;\n}\n"],"mappings":";;;;;;;;;;;;;;AA+CA,MAAa,wCAAwC;;;;;;;AAQrD,MAAM,oCAAoC;AAE1C,SAAS,qBAAqB,YAAwC;CACpE,IAAI,OAAO,eAAe,YAAY,CAAC,OAAO,SAAS,UAAU,KAAK,cAAc,GAClF,OAAO;CAET,OAAO,KAAK,IAAI,YAAY,iCAAiC;AAC/D;;;;;;;;;;;;;;;;;AAkBA,SAAS,kBACP,KACA,WACA,QACkC;CAClC,IAAI;CACJ,IAAI;CAEJ,MAAM,SAAS,IAAI,SAAgB,GAAG,WAAW;EAC/C,YAAY,iBAAiB,uBAAO,IAAI,MAAM,qCAAqC,CAAC,GAAG,SAAS;CAClG,CAAC;CACD,MAAM,UAAU,IAAI,SAAgB,GAAG,WAAW;EAChD,IAAI,CAAC,QACH;EAEF,gBAAgB,uBAAO,IAAI,MAAM,mCAAmC,CAAC;EACrE,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAC1D,CAAC;CAED,OAAO,QAAQ,KAAK;EAAC;;GAAiC;;EAAM;EAAQ;CAAO,CAAC,CAAC,CAAC,cAAc;EAC1F,aAAa,SAAS;EACtB,IAAI,UAAU,SACZ,OAAO,oBAAoB,SAAS,OAAO;CAE/C,CAAC;AACH;;;;;;;;;;;;;;;AAyBA,SAAS,kBAAkB,QAAqB;CAC9C,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,IAAI,MAAM;CACzB,QAAQ;EACN,MAAM,IAAIA,4CAAkB,4CAA4C,EACtE,MAAM,gCACR,CAAC;CACH;CACA,IAAI,OAAO,aAAa,UACtB,MAAM,IAAIA,4CAAkB,wCAAwC,EAClE,MAAM,gCACR,CAAC;CAEH,IAAI,OAAO,YAAY,OAAO,UAC5B,MAAM,IAAIA,4CAAkB,sDAAsD,EAChF,MAAM,gCACR,CAAC;CAEH,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;AAuBA,eAAsB,oBACpB,cACA,WACA,UAAsC,CAAC,GACtB;CACjB,MAAM,EAAE,QAAQ,kBAAkB,kBAAkB;CACpD,MAAM,EAAE,QAAQ,OAAO,YAAY;CAEnC,MAAM,YAAY,kBAAkB,MAAM;CAE1C,IAAI,QAAQ,SACV,MAAM,IAAIA,4CAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;CAGlG,IAAI;CACJ,IAAI;EACF,MAAM,MAAM,kBAAkB,UAAU,SAAS,GAAG,qBAAqB,aAAa,GAAG,MAAM;CACjG,QAAQ;EAIN,IAAI,QAAQ,SACV,MAAM,IAAIA,4CAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;EAIlG,MAAM,IAAIA,4CACR,yMAGA,EAAE,MAAM,mCAAmC,CAC7C;CACF;CAEA,IAAI,QAAQ,SACV,MAAM,IAAIA,4CAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;CAGlG,IAAI,OAAO,IAAI,YAAY,YACzB,MAAM,IAAIA,4CAAkB,2DAA2D,EACrF,MAAM,+BACR,CAAC;CAGH,IAAI;CACJ,IAAI;EACF,aAAa,MAAO,IAAI,QAA0B,WAAW;GAAE;GAAO;GAAS;GAAQ;EAAiB,CAAC;CAC3G,SAAS,KAAK;EAIZ,MAAM,iBAAiB,eAAe,SAAS,IAAI,SAAS;EAC5D,IAAI,QAAQ,WAAW,gBACrB,MAAM,IAAIA,4CAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;EAGlG,MAAM,IAAIA,4CAAkB,0CADX,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,KACkB,EAChF,MAAM,iCACR,CAAC;CACH;CAIA,IAAI,QAAQ,SACV,MAAM,IAAIA,4CAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;CAGlG,OAAO;AACT"}
|
|
@@ -3,6 +3,66 @@ import "../../_chunks/error-CYyD2kei.mjs";
|
|
|
3
3
|
|
|
4
4
|
//#region src/internal/clerk-js/protectCheck.ts
|
|
5
5
|
/**
|
|
6
|
+
* Default bound on LOADING the challenge module.
|
|
7
|
+
*
|
|
8
|
+
* Its only job is a network that accepts a connection and then never answers, because every
|
|
9
|
+
* other load failure — a CSP block, DNS, a 404, a body that isn't a valid module — rejects the
|
|
10
|
+
* dynamic import on its own and needs no timer to notice. That makes a generous value the safe
|
|
11
|
+
* one: nothing legitimate is waiting on this timer, while a value tight enough to fire on a
|
|
12
|
+
* genuinely slow connection would fail a load that was going to succeed.
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS = 6e4;
|
|
15
|
+
/**
|
|
16
|
+
* Ceiling on the configured load bound. `setTimeout` stores its delay in a signed 32-bit int, so a
|
|
17
|
+
* larger value overflows and fires immediately — which would make every load fail instantly, the
|
|
18
|
+
* exact opposite of what an operator asking for a long timeout wanted. Clamping rather than
|
|
19
|
+
* rejecting keeps a fat-fingered config from breaking sign-in.
|
|
20
|
+
*/
|
|
21
|
+
const MAX_PROTECT_CHECK_LOAD_TIMEOUT_MS = 6e5;
|
|
22
|
+
function resolveLoadTimeoutMs(configured) {
|
|
23
|
+
if (typeof configured !== "number" || !Number.isFinite(configured) || configured <= 0) return DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS;
|
|
24
|
+
return Math.min(configured, MAX_PROTECT_CHECK_LOAD_TIMEOUT_MS);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Races the dynamic import against `timeoutMs`, always clearing the timer so a fast load does not
|
|
28
|
+
* leave one pending.
|
|
29
|
+
*
|
|
30
|
+
* The bound stops at the import on purpose. Once `mod.default` is called the challenge owns its
|
|
31
|
+
* own deadline and the host imposes none: the host cannot know an honest duration for a challenge
|
|
32
|
+
* whose type is chosen server-side, per decision, and whose work it deliberately knows nothing
|
|
33
|
+
* about — waiting on a person, or moving a server-chosen number of bytes over an unknown link. A
|
|
34
|
+
* host-side wall over execution aborts legitimate challenges and reports them as timeouts, and
|
|
35
|
+
* since a re-run restarts the work, retrying cannot win on any connection slow enough to trip it.
|
|
36
|
+
*
|
|
37
|
+
* The abort signal is raced too. A stalled import cannot itself be cancelled, but without this the
|
|
38
|
+
* caller's abort would not settle anything: an unmounted component would keep this promise, its
|
|
39
|
+
* closures and its timer alive for the whole load bound, and then report a load failure for what
|
|
40
|
+
* was really a cancellation.
|
|
41
|
+
*/
|
|
42
|
+
function importWithTimeout(url, timeoutMs, signal) {
|
|
43
|
+
let timeoutId;
|
|
44
|
+
let onAbort;
|
|
45
|
+
const expiry = new Promise((_, reject) => {
|
|
46
|
+
timeoutId = setTimeout(() => reject(/* @__PURE__ */ new Error("Protect check script load timed out")), timeoutMs);
|
|
47
|
+
});
|
|
48
|
+
const aborted = new Promise((_, reject) => {
|
|
49
|
+
if (!signal) return;
|
|
50
|
+
onAbort = () => reject(/* @__PURE__ */ new Error("Protect check aborted during load"));
|
|
51
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
52
|
+
});
|
|
53
|
+
return Promise.race([
|
|
54
|
+
import(
|
|
55
|
+
/* webpackIgnore: true */
|
|
56
|
+
url
|
|
57
|
+
),
|
|
58
|
+
expiry,
|
|
59
|
+
aborted
|
|
60
|
+
]).finally(() => {
|
|
61
|
+
clearTimeout(timeoutId);
|
|
62
|
+
if (signal && onAbort) signal.removeEventListener("abort", onAbort);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
6
66
|
* Validates the `sdk_url` returned by the server before passing it to dynamic `import()`.
|
|
7
67
|
*
|
|
8
68
|
* Rejects:
|
|
@@ -49,17 +109,15 @@ function assertValidSdkUrl(sdkUrl) {
|
|
|
49
109
|
* - `protect_check_execution_failed` — the script's default export threw
|
|
50
110
|
*/
|
|
51
111
|
async function executeProtectCheck(protectCheck, container, options = {}) {
|
|
52
|
-
const { signal, setWidgetVisible } = options;
|
|
112
|
+
const { signal, setWidgetVisible, loadTimeoutMs } = options;
|
|
53
113
|
const { sdkUrl, token, uiHints } = protectCheck;
|
|
54
114
|
const validated = assertValidSdkUrl(sdkUrl);
|
|
55
115
|
if (signal?.aborted) throw new ClerkRuntimeError("Protect check aborted by caller", { code: "protect_check_aborted" });
|
|
56
116
|
let mod;
|
|
57
117
|
try {
|
|
58
|
-
mod = await
|
|
59
|
-
/* webpackIgnore: true */
|
|
60
|
-
validated.toString()
|
|
61
|
-
);
|
|
118
|
+
mod = await importWithTimeout(validated.toString(), resolveLoadTimeoutMs(loadTimeoutMs), signal);
|
|
62
119
|
} catch {
|
|
120
|
+
if (signal?.aborted) throw new ClerkRuntimeError("Protect check aborted by caller", { code: "protect_check_aborted" });
|
|
63
121
|
throw new ClerkRuntimeError("Protect check script failed to load. This is commonly caused by a Content Security Policy that blocks the script origin (add it to your script-src directive), a network error, or an invalid module.", { code: "protect_check_script_load_failed" });
|
|
64
122
|
}
|
|
65
123
|
if (signal?.aborted) throw new ClerkRuntimeError("Protect check aborted by caller", { code: "protect_check_aborted" });
|
|
@@ -82,5 +140,5 @@ async function executeProtectCheck(protectCheck, container, options = {}) {
|
|
|
82
140
|
}
|
|
83
141
|
|
|
84
142
|
//#endregion
|
|
85
|
-
export { executeProtectCheck };
|
|
143
|
+
export { DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS, executeProtectCheck };
|
|
86
144
|
//# sourceMappingURL=protectCheck.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protectCheck.mjs","names":[],"sources":["../../../src/internal/clerk-js/protectCheck.ts"],"sourcesContent":["import { ClerkRuntimeError } from '../../error';\nimport type { ProtectCheckResource } from '../../types';\n\nexport interface ExecuteProtectCheckOptions {\n /**\n * Host-provided visibility handshake, forwarded to the script verbatim as\n * `setWidgetVisible` in the init payload. The script calls it right before revealing UI in\n * the container (and with `false` once its widget is done); the returned promise resolves\n * only after the host has applied the change to the DOM (e.g. removed its own loading\n * spinner), so the script can sequence its reveal without a frame of overlap. A script that\n * knows its widget is imminent may call it immediately to avoid a spinner flash. Scripts\n * must treat the field as optional — older hosts don't provide it.\n */\n setWidgetVisible?: (visible: boolean) => Promise<void>;\n /**\n * Signals that the caller no longer needs the proof token (component unmounted, user\n * navigated away, etc.). When the signal aborts:\n * - If the script has not yet been imported, `executeProtectCheck` rejects with\n * `protect_check_aborted` without loading the script.\n * - The signal is forwarded to the script as `{ signal }` in the second argument so\n * cooperating SDKs can cancel any in-flight UI / network work.\n * - Even if the script ignores the signal and resolves with a token, the helper\n * re-checks `signal.aborted` after the await and rejects with `protect_check_aborted`\n * so the caller never observes a \"successful\" abort.\n *\n * Scripts that don't honor the signal will continue to run; this is best-effort by design.\n */\n signal?: AbortSignal;\n}\n\ninterface ScriptInitOptions {\n token: string;\n uiHints?: Record<string, string>;\n signal?: AbortSignal;\n setWidgetVisible?: (visible: boolean) => Promise<void>;\n}\n\ntype ScriptDefault = (container: HTMLDivElement, init: ScriptInitOptions) => Promise<string>;\n\n/**\n * Validates the `sdk_url` returned by the server before passing it to dynamic `import()`.\n *\n * Rejects:\n * - Anything that fails URL parsing (relative paths, garbage strings)\n * - Non-`https:` schemes — including `http:`, `data:`, `blob:`, `javascript:`. The server\n * always returns an HTTPS URL, but the dynamic-import primitive accepts `data:`/`blob:`\n * modules which would let a tampered response inject arbitrary code into the host page.\n * - URLs containing credentials (`user:pass@host`) — phishing surface, no legitimate use.\n *\n * Throws `ClerkRuntimeError` with code `protect_check_invalid_sdk_url`. We deliberately do\n * NOT silently strip an invalid `protect_check` from the resource: the gate must remain\n * present so the user can't bypass it by manipulating the response. Fail-closed.\n */\nfunction assertValidSdkUrl(sdkUrl: string): URL {\n let parsed: URL;\n try {\n parsed = new URL(sdkUrl);\n } catch {\n throw new ClerkRuntimeError('Protect check sdk_url is not a valid URL', {\n code: 'protect_check_invalid_sdk_url',\n });\n }\n if (parsed.protocol !== 'https:') {\n throw new ClerkRuntimeError('Protect check sdk_url must use HTTPS', {\n code: 'protect_check_invalid_sdk_url',\n });\n }\n if (parsed.username || parsed.password) {\n throw new ClerkRuntimeError('Protect check sdk_url must not contain credentials', {\n code: 'protect_check_invalid_sdk_url',\n });\n }\n return parsed;\n}\n\n/**\n * Loads the Protect challenge SDK from `protectCheck.sdkUrl`, hands it the container element\n * and the spec-defined init payload (`token`, `uiHints`, `signal`), and returns the proof\n * token the SDK produces.\n *\n * The SDK script must:\n * - Be a valid ES module served over HTTPS\n * - Have a default export of the shape `(container, { token, uiHints, signal }) => Promise<string>`\n * - Honor the `signal` to abort any pending work (best-effort)\n *\n * Only the minimal fields (`token`, optional `ui_hints`) are surfaced to the script — the\n * full sign-up/sign-in resource is intentionally NOT passed, to minimize the trust surface\n * granted to third-party Protect scripts.\n *\n * Failure modes are surfaced as `ClerkRuntimeError` with one of:\n * - `protect_check_invalid_sdk_url` — URL fails the safety checks above\n * - `protect_check_aborted` — caller aborted before or during execution\n * - `protect_check_script_load_failed` — network error, CSP block, or invalid module\n * - `protect_check_invalid_script` — module loaded but no callable default export\n * - `protect_check_execution_failed` — the script's default export threw\n */\nexport async function executeProtectCheck(\n protectCheck: Pick<ProtectCheckResource, 'sdkUrl' | 'token' | 'uiHints'>,\n container: HTMLDivElement,\n options: ExecuteProtectCheckOptions = {},\n): Promise<string> {\n const { signal, setWidgetVisible } = options;\n const { sdkUrl, token, uiHints } = protectCheck;\n\n const validated = assertValidSdkUrl(sdkUrl);\n\n if (signal?.aborted) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n\n let mod: Record<string, unknown>;\n try {\n mod = await import(/* webpackIgnore: true */ validated.toString());\n } catch {\n // Surface a generic message and deliberately omit the original error: Chromium/Firefox embed\n // the sdk_url in the dynamic-import failure text, which a tampered response could plant in the UI.\n throw new ClerkRuntimeError(\n 'Protect check script failed to load. This is commonly caused by a Content Security ' +\n 'Policy that blocks the script origin (add it to your script-src directive), a ' +\n 'network error, or an invalid module.',\n { code: 'protect_check_script_load_failed' },\n );\n }\n\n if (signal?.aborted) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n\n if (typeof mod.default !== 'function') {\n throw new ClerkRuntimeError('Protect check script does not export a default function', {\n code: 'protect_check_invalid_script',\n });\n }\n\n let proofToken: string;\n try {\n proofToken = await (mod.default as ScriptDefault)(container, { token, uiHints, signal, setWidgetVisible });\n } catch (err) {\n // Distinguish abort-induced rejections from genuine script errors: only relabel as\n // `protect_check_aborted` when the error looks like an abort (`AbortError`), otherwise\n // surface the script's actual failure so production diagnostics aren't masked.\n const looksLikeAbort = err instanceof Error && err.name === 'AbortError';\n if (signal?.aborted && looksLikeAbort) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n const original = err instanceof Error ? err.message : String(err);\n throw new ClerkRuntimeError(`Protect check script execution failed: ${original}`, {\n code: 'protect_check_execution_failed',\n });\n }\n\n // The script may have ignored the signal and resolved with a token after the abort fired.\n // Re-check here so callers get a consistent contract: if you aborted, you never see a token.\n if (signal?.aborted) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n\n return proofToken;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqDA,SAAS,kBAAkB,QAAqB;CAC9C,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,IAAI,MAAM;CACzB,QAAQ;EACN,MAAM,IAAI,kBAAkB,4CAA4C,EACtE,MAAM,gCACR,CAAC;CACH;CACA,IAAI,OAAO,aAAa,UACtB,MAAM,IAAI,kBAAkB,wCAAwC,EAClE,MAAM,gCACR,CAAC;CAEH,IAAI,OAAO,YAAY,OAAO,UAC5B,MAAM,IAAI,kBAAkB,sDAAsD,EAChF,MAAM,gCACR,CAAC;CAEH,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;AAuBA,eAAsB,oBACpB,cACA,WACA,UAAsC,CAAC,GACtB;CACjB,MAAM,EAAE,QAAQ,qBAAqB;CACrC,MAAM,EAAE,QAAQ,OAAO,YAAY;CAEnC,MAAM,YAAY,kBAAkB,MAAM;CAE1C,IAAI,QAAQ,SACV,MAAM,IAAI,kBAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;CAGlG,IAAI;CACJ,IAAI;EACF,MAAM,MAAM;;GAAiC,UAAU,SAAS;;CAClE,QAAQ;EAGN,MAAM,IAAI,kBACR,yMAGA,EAAE,MAAM,mCAAmC,CAC7C;CACF;CAEA,IAAI,QAAQ,SACV,MAAM,IAAI,kBAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;CAGlG,IAAI,OAAO,IAAI,YAAY,YACzB,MAAM,IAAI,kBAAkB,2DAA2D,EACrF,MAAM,+BACR,CAAC;CAGH,IAAI;CACJ,IAAI;EACF,aAAa,MAAO,IAAI,QAA0B,WAAW;GAAE;GAAO;GAAS;GAAQ;EAAiB,CAAC;CAC3G,SAAS,KAAK;EAIZ,MAAM,iBAAiB,eAAe,SAAS,IAAI,SAAS;EAC5D,IAAI,QAAQ,WAAW,gBACrB,MAAM,IAAI,kBAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;EAGlG,MAAM,IAAI,kBAAkB,0CADX,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,KACkB,EAChF,MAAM,iCACR,CAAC;CACH;CAIA,IAAI,QAAQ,SACV,MAAM,IAAI,kBAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;CAGlG,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"protectCheck.mjs","names":[],"sources":["../../../src/internal/clerk-js/protectCheck.ts"],"sourcesContent":["import { ClerkRuntimeError } from '../../error';\nimport type { ProtectCheckResource } from '../../types';\n\nexport interface ExecuteProtectCheckOptions {\n /**\n * Host-provided visibility handshake, forwarded to the script verbatim as\n * `setWidgetVisible` in the init payload. The script calls it right before revealing UI in\n * the container (and with `false` once its widget is done); the returned promise resolves\n * only after the host has applied the change to the DOM (e.g. removed its own loading\n * spinner), so the script can sequence its reveal without a frame of overlap. A script that\n * knows its widget is imminent may call it immediately to avoid a spinner flash. Scripts\n * must treat the field as optional — older hosts don't provide it.\n */\n setWidgetVisible?: (visible: boolean) => Promise<void>;\n /**\n * Signals that the caller no longer needs the proof token (component unmounted, user\n * navigated away, etc.). When the signal aborts:\n * - If the script has not yet been imported, `executeProtectCheck` rejects with\n * `protect_check_aborted` without loading the script.\n * - The signal is forwarded to the script as `{ signal }` in the second argument so\n * cooperating SDKs can cancel any in-flight UI / network work.\n * - Even if the script ignores the signal and resolves with a token, the helper\n * re-checks `signal.aborted` after the await and rejects with `protect_check_aborted`\n * so the caller never observes a \"successful\" abort.\n *\n * Scripts that don't honor the signal will continue to run; this is best-effort by design.\n */\n signal?: AbortSignal;\n /**\n * Overrides how long to wait for the challenge module to LOAD. Per-instance and per-loader\n * config, since the right value depends on the population an instance serves; a non-positive\n * or non-numeric value falls back to {@link DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS}.\n *\n * Bounds the handoff only — never the challenge. See the note on the constant.\n */\n loadTimeoutMs?: number;\n}\n\n/**\n * Default bound on LOADING the challenge module.\n *\n * Its only job is a network that accepts a connection and then never answers, because every\n * other load failure — a CSP block, DNS, a 404, a body that isn't a valid module — rejects the\n * dynamic import on its own and needs no timer to notice. That makes a generous value the safe\n * one: nothing legitimate is waiting on this timer, while a value tight enough to fire on a\n * genuinely slow connection would fail a load that was going to succeed.\n */\nexport const DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS = 60_000;\n\n/**\n * Ceiling on the configured load bound. `setTimeout` stores its delay in a signed 32-bit int, so a\n * larger value overflows and fires immediately — which would make every load fail instantly, the\n * exact opposite of what an operator asking for a long timeout wanted. Clamping rather than\n * rejecting keeps a fat-fingered config from breaking sign-in.\n */\nconst MAX_PROTECT_CHECK_LOAD_TIMEOUT_MS = 600_000;\n\nfunction resolveLoadTimeoutMs(configured: number | undefined): number {\n if (typeof configured !== 'number' || !Number.isFinite(configured) || configured <= 0) {\n return DEFAULT_PROTECT_CHECK_LOAD_TIMEOUT_MS;\n }\n return Math.min(configured, MAX_PROTECT_CHECK_LOAD_TIMEOUT_MS);\n}\n\n/**\n * Races the dynamic import against `timeoutMs`, always clearing the timer so a fast load does not\n * leave one pending.\n *\n * The bound stops at the import on purpose. Once `mod.default` is called the challenge owns its\n * own deadline and the host imposes none: the host cannot know an honest duration for a challenge\n * whose type is chosen server-side, per decision, and whose work it deliberately knows nothing\n * about — waiting on a person, or moving a server-chosen number of bytes over an unknown link. A\n * host-side wall over execution aborts legitimate challenges and reports them as timeouts, and\n * since a re-run restarts the work, retrying cannot win on any connection slow enough to trip it.\n *\n * The abort signal is raced too. A stalled import cannot itself be cancelled, but without this the\n * caller's abort would not settle anything: an unmounted component would keep this promise, its\n * closures and its timer alive for the whole load bound, and then report a load failure for what\n * was really a cancellation.\n */\nfunction importWithTimeout(\n url: string,\n timeoutMs: number,\n signal: AbortSignal | undefined,\n): Promise<Record<string, unknown>> {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n let onAbort: (() => void) | undefined;\n\n const expiry = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => reject(new Error('Protect check script load timed out')), timeoutMs);\n });\n const aborted = new Promise<never>((_, reject) => {\n if (!signal) {\n return;\n }\n onAbort = () => reject(new Error('Protect check aborted during load'));\n signal.addEventListener('abort', onAbort, { once: true });\n });\n\n return Promise.race([import(/* webpackIgnore: true */ url), expiry, aborted]).finally(() => {\n clearTimeout(timeoutId);\n if (signal && onAbort) {\n signal.removeEventListener('abort', onAbort);\n }\n }) as Promise<Record<string, unknown>>;\n}\n\ninterface ScriptInitOptions {\n token: string;\n uiHints?: Record<string, string>;\n signal?: AbortSignal;\n setWidgetVisible?: (visible: boolean) => Promise<void>;\n}\n\ntype ScriptDefault = (container: HTMLDivElement, init: ScriptInitOptions) => Promise<string>;\n\n/**\n * Validates the `sdk_url` returned by the server before passing it to dynamic `import()`.\n *\n * Rejects:\n * - Anything that fails URL parsing (relative paths, garbage strings)\n * - Non-`https:` schemes — including `http:`, `data:`, `blob:`, `javascript:`. The server\n * always returns an HTTPS URL, but the dynamic-import primitive accepts `data:`/`blob:`\n * modules which would let a tampered response inject arbitrary code into the host page.\n * - URLs containing credentials (`user:pass@host`) — phishing surface, no legitimate use.\n *\n * Throws `ClerkRuntimeError` with code `protect_check_invalid_sdk_url`. We deliberately do\n * NOT silently strip an invalid `protect_check` from the resource: the gate must remain\n * present so the user can't bypass it by manipulating the response. Fail-closed.\n */\nfunction assertValidSdkUrl(sdkUrl: string): URL {\n let parsed: URL;\n try {\n parsed = new URL(sdkUrl);\n } catch {\n throw new ClerkRuntimeError('Protect check sdk_url is not a valid URL', {\n code: 'protect_check_invalid_sdk_url',\n });\n }\n if (parsed.protocol !== 'https:') {\n throw new ClerkRuntimeError('Protect check sdk_url must use HTTPS', {\n code: 'protect_check_invalid_sdk_url',\n });\n }\n if (parsed.username || parsed.password) {\n throw new ClerkRuntimeError('Protect check sdk_url must not contain credentials', {\n code: 'protect_check_invalid_sdk_url',\n });\n }\n return parsed;\n}\n\n/**\n * Loads the Protect challenge SDK from `protectCheck.sdkUrl`, hands it the container element\n * and the spec-defined init payload (`token`, `uiHints`, `signal`), and returns the proof\n * token the SDK produces.\n *\n * The SDK script must:\n * - Be a valid ES module served over HTTPS\n * - Have a default export of the shape `(container, { token, uiHints, signal }) => Promise<string>`\n * - Honor the `signal` to abort any pending work (best-effort)\n *\n * Only the minimal fields (`token`, optional `ui_hints`) are surfaced to the script — the\n * full sign-up/sign-in resource is intentionally NOT passed, to minimize the trust surface\n * granted to third-party Protect scripts.\n *\n * Failure modes are surfaced as `ClerkRuntimeError` with one of:\n * - `protect_check_invalid_sdk_url` — URL fails the safety checks above\n * - `protect_check_aborted` — caller aborted before or during execution\n * - `protect_check_script_load_failed` — network error, CSP block, or invalid module\n * - `protect_check_invalid_script` — module loaded but no callable default export\n * - `protect_check_execution_failed` — the script's default export threw\n */\nexport async function executeProtectCheck(\n protectCheck: Pick<ProtectCheckResource, 'sdkUrl' | 'token' | 'uiHints'>,\n container: HTMLDivElement,\n options: ExecuteProtectCheckOptions = {},\n): Promise<string> {\n const { signal, setWidgetVisible, loadTimeoutMs } = options;\n const { sdkUrl, token, uiHints } = protectCheck;\n\n const validated = assertValidSdkUrl(sdkUrl);\n\n if (signal?.aborted) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n\n let mod: Record<string, unknown>;\n try {\n mod = await importWithTimeout(validated.toString(), resolveLoadTimeoutMs(loadTimeoutMs), signal);\n } catch {\n // An abort that landed mid-load is a cancellation, not a load failure. Checked first so the\n // caller gets the same contract it does everywhere else: if you aborted, you never see\n // anything but `protect_check_aborted`.\n if (signal?.aborted) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n // Surface a generic message and deliberately omit the original error: Chromium/Firefox embed\n // the sdk_url in the dynamic-import failure text, which a tampered response could plant in the UI.\n throw new ClerkRuntimeError(\n 'Protect check script failed to load. This is commonly caused by a Content Security ' +\n 'Policy that blocks the script origin (add it to your script-src directive), a ' +\n 'network error, or an invalid module.',\n { code: 'protect_check_script_load_failed' },\n );\n }\n\n if (signal?.aborted) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n\n if (typeof mod.default !== 'function') {\n throw new ClerkRuntimeError('Protect check script does not export a default function', {\n code: 'protect_check_invalid_script',\n });\n }\n\n let proofToken: string;\n try {\n proofToken = await (mod.default as ScriptDefault)(container, { token, uiHints, signal, setWidgetVisible });\n } catch (err) {\n // Distinguish abort-induced rejections from genuine script errors: only relabel as\n // `protect_check_aborted` when the error looks like an abort (`AbortError`), otherwise\n // surface the script's actual failure so production diagnostics aren't masked.\n const looksLikeAbort = err instanceof Error && err.name === 'AbortError';\n if (signal?.aborted && looksLikeAbort) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n const original = err instanceof Error ? err.message : String(err);\n throw new ClerkRuntimeError(`Protect check script execution failed: ${original}`, {\n code: 'protect_check_execution_failed',\n });\n }\n\n // The script may have ignored the signal and resolved with a token after the abort fired.\n // Re-check here so callers get a consistent contract: if you aborted, you never see a token.\n if (signal?.aborted) {\n throw new ClerkRuntimeError('Protect check aborted by caller', { code: 'protect_check_aborted' });\n }\n\n return proofToken;\n}\n"],"mappings":";;;;;;;;;;;;;AA+CA,MAAa,wCAAwC;;;;;;;AAQrD,MAAM,oCAAoC;AAE1C,SAAS,qBAAqB,YAAwC;CACpE,IAAI,OAAO,eAAe,YAAY,CAAC,OAAO,SAAS,UAAU,KAAK,cAAc,GAClF,OAAO;CAET,OAAO,KAAK,IAAI,YAAY,iCAAiC;AAC/D;;;;;;;;;;;;;;;;;AAkBA,SAAS,kBACP,KACA,WACA,QACkC;CAClC,IAAI;CACJ,IAAI;CAEJ,MAAM,SAAS,IAAI,SAAgB,GAAG,WAAW;EAC/C,YAAY,iBAAiB,uBAAO,IAAI,MAAM,qCAAqC,CAAC,GAAG,SAAS;CAClG,CAAC;CACD,MAAM,UAAU,IAAI,SAAgB,GAAG,WAAW;EAChD,IAAI,CAAC,QACH;EAEF,gBAAgB,uBAAO,IAAI,MAAM,mCAAmC,CAAC;EACrE,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAC1D,CAAC;CAED,OAAO,QAAQ,KAAK;EAAC;;GAAiC;;EAAM;EAAQ;CAAO,CAAC,CAAC,CAAC,cAAc;EAC1F,aAAa,SAAS;EACtB,IAAI,UAAU,SACZ,OAAO,oBAAoB,SAAS,OAAO;CAE/C,CAAC;AACH;;;;;;;;;;;;;;;AAyBA,SAAS,kBAAkB,QAAqB;CAC9C,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,IAAI,MAAM;CACzB,QAAQ;EACN,MAAM,IAAI,kBAAkB,4CAA4C,EACtE,MAAM,gCACR,CAAC;CACH;CACA,IAAI,OAAO,aAAa,UACtB,MAAM,IAAI,kBAAkB,wCAAwC,EAClE,MAAM,gCACR,CAAC;CAEH,IAAI,OAAO,YAAY,OAAO,UAC5B,MAAM,IAAI,kBAAkB,sDAAsD,EAChF,MAAM,gCACR,CAAC;CAEH,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;AAuBA,eAAsB,oBACpB,cACA,WACA,UAAsC,CAAC,GACtB;CACjB,MAAM,EAAE,QAAQ,kBAAkB,kBAAkB;CACpD,MAAM,EAAE,QAAQ,OAAO,YAAY;CAEnC,MAAM,YAAY,kBAAkB,MAAM;CAE1C,IAAI,QAAQ,SACV,MAAM,IAAI,kBAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;CAGlG,IAAI;CACJ,IAAI;EACF,MAAM,MAAM,kBAAkB,UAAU,SAAS,GAAG,qBAAqB,aAAa,GAAG,MAAM;CACjG,QAAQ;EAIN,IAAI,QAAQ,SACV,MAAM,IAAI,kBAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;EAIlG,MAAM,IAAI,kBACR,yMAGA,EAAE,MAAM,mCAAmC,CAC7C;CACF;CAEA,IAAI,QAAQ,SACV,MAAM,IAAI,kBAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;CAGlG,IAAI,OAAO,IAAI,YAAY,YACzB,MAAM,IAAI,kBAAkB,2DAA2D,EACrF,MAAM,+BACR,CAAC;CAGH,IAAI;CACJ,IAAI;EACF,aAAa,MAAO,IAAI,QAA0B,WAAW;GAAE;GAAO;GAAS;GAAQ;EAAiB,CAAC;CAC3G,SAAS,KAAK;EAIZ,MAAM,iBAAiB,eAAe,SAAS,IAAI,SAAS;EAC5D,IAAI,QAAQ,WAAW,gBACrB,MAAM,IAAI,kBAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;EAGlG,MAAM,IAAI,kBAAkB,0CADX,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,KACkB,EAChF,MAAM,iCACR,CAAC;CACH;CAIA,IAAI,QAAQ,SACV,MAAM,IAAI,kBAAkB,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;CAGlG,OAAO;AACT"}
|
|
@@ -140,7 +140,7 @@ const clerkJSScriptUrl = (opts) => {
|
|
|
140
140
|
const clerkUIScriptUrl = (opts) => {
|
|
141
141
|
const { __internal_clerkUIUrl, __internal_clerkUIVersion, proxyUrl, domain, publishableKey } = opts;
|
|
142
142
|
if (__internal_clerkUIUrl) return __internal_clerkUIUrl;
|
|
143
|
-
const version = require_versionSelector.versionSelector(__internal_clerkUIVersion, "1.30.8
|
|
143
|
+
const version = require_versionSelector.versionSelector(__internal_clerkUIVersion, "1.30.8");
|
|
144
144
|
if (proxyUrl && require_proxy.isProxyUrlRelative(proxyUrl)) return buildRelativeProxyScriptUrl(proxyUrl, "ui", version, "ui.browser.js");
|
|
145
145
|
return `https://${buildScriptHost({
|
|
146
146
|
publishableKey,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadClerkJsScript.js","names":["createDevOrStagingUrlCache","buildErrorThrower","ClerkRuntimeError","versionSelector","isProxyUrlRelative","isValidProxyUrl","proxyUrlToAbsoluteURL","parsePublishableKey","addClerkPrefix"],"sources":["../src/loadClerkJsScript.ts"],"sourcesContent":["import { buildErrorThrower, ClerkRuntimeError } from './error';\nimport { createDevOrStagingUrlCache, parsePublishableKey } from './keys';\nimport { loadScript } from './loadScript';\nimport { isProxyUrlRelative, isValidProxyUrl, proxyUrlToAbsoluteURL } from './proxy';\nimport type { SDKMetadata } from './types';\nimport { addClerkPrefix } from './url';\nimport { versionSelector } from './versionSelector';\n\nconst { isDevOrStagingUrl } = createDevOrStagingUrlCache();\n\nconst errorThrower = buildErrorThrower({ packageName: '@clerk/shared' });\n\nexport type LoadClerkJSScriptOptions = {\n publishableKey: string;\n /** @internal */\n __internal_clerkJSUrl?: string;\n /** @internal */\n __internal_clerkJSVersion?: string;\n sdkMetadata?: SDKMetadata;\n proxyUrl?: string;\n domain?: string;\n nonce?: string;\n /**\n * Timeout in milliseconds to wait for clerk-js to load before considering it failed.\n *\n * @default 15000 (15 seconds)\n */\n scriptLoadTimeout?: number;\n};\n\n/**\n * @deprecated Use `LoadClerkJSScriptOptions` instead. This alias will be removed in a future major version.\n */\nexport type LoadClerkJsScriptOptions = LoadClerkJSScriptOptions;\n\nexport type LoadClerkUIScriptOptions = {\n publishableKey: string;\n /** @internal */\n __internal_clerkUIUrl?: string;\n /** @internal */\n __internal_clerkUIVersion?: string;\n proxyUrl?: string;\n domain?: string;\n nonce?: string;\n scriptLoadTimeout?: number;\n};\n\n/**\n * Validates that window.Clerk exists and is properly initialized.\n * This ensures we don't have false positives where the script loads but Clerk is malformed.\n *\n * @returns `true` if window.Clerk exists and has the expected structure with a load method.\n */\nfunction isClerkGlobalProperlyLoaded(prop: 'Clerk' | '__internal_ClerkUICtor'): boolean {\n if (typeof window === 'undefined' || !(window as any)[prop]) {\n return false;\n }\n\n // Basic validation that window.Clerk has the expected structure\n const val = (window as any)[prop];\n return !!val;\n}\nconst isClerkProperlyLoaded = () => isClerkGlobalProperlyLoaded('Clerk');\nconst isClerkUIProperlyLoaded = () => isClerkGlobalProperlyLoaded('__internal_ClerkUICtor');\n\n/**\n * Checks if an existing script has a request error using Performance API.\n *\n * @param scriptUrl - The URL of the script to check.\n * @returns True if the script has failed to load due to a network/HTTP error.\n */\nfunction hasScriptRequestError(scriptUrl: string): boolean {\n if (typeof window === 'undefined' || !window.performance) {\n return false;\n }\n\n const entries = performance.getEntriesByName(scriptUrl, 'resource') as PerformanceResourceTiming[];\n\n if (entries.length === 0) {\n return false;\n }\n\n const scriptEntry = entries[entries.length - 1];\n\n // transferSize === 0 with responseEnd === 0 indicates network failure\n // transferSize === 0 with responseEnd > 0 might be a 4xx/5xx error or blocked request\n if (scriptEntry.transferSize === 0 && scriptEntry.decodedBodySize === 0) {\n // If there was no response at all, it's definitely an error\n if (scriptEntry.responseEnd === 0) {\n return true;\n }\n // If we got a response but no content, likely an HTTP error (4xx/5xx)\n if (scriptEntry.responseEnd > 0 && scriptEntry.responseStart > 0) {\n return true;\n }\n\n if ('responseStatus' in scriptEntry) {\n const status = (scriptEntry as any).responseStatus;\n if (status >= 400) {\n return true;\n }\n if (scriptEntry.responseStatus === 0) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Hotloads the Clerk JS script with robust failure detection.\n *\n * Uses a timeout-based approach to ensure absolute certainty about load success/failure.\n * If the script fails to load within the timeout period, or loads but doesn't create\n * a proper Clerk instance, the promise rejects with an error.\n *\n * @param opts - The options used to build the Clerk JS script URL and load the script.\n * Must include a `publishableKey` if no existing script is found.\n * @returns Promise that resolves with null if Clerk loads successfully, or rejects with an error.\n *\n * @example\n * ```typescript\n * try {\n * await loadClerkJsScript({ publishableKey: 'pk_test_...' });\n * console.log('Clerk loaded successfully');\n * } catch (error) {\n * console.error('Failed to load Clerk:', error.message);\n * }\n * ```\n */\nexport const loadClerkJSScript = async (opts?: LoadClerkJSScriptOptions): Promise<HTMLScriptElement | null> => {\n const timeout = opts?.scriptLoadTimeout ?? 15000;\n const rejectWith = (error?: Error) =>\n new ClerkRuntimeError('Failed to load Clerk JS' + (error?.message ? `, ${error.message}` : ''), {\n code: 'failed_to_load_clerk_js',\n cause: error,\n });\n\n if (isClerkProperlyLoaded()) {\n return null;\n }\n\n if (!opts?.publishableKey) {\n errorThrower.throwMissingPublishableKeyError();\n return null;\n }\n\n const scriptUrl = clerkJSScriptUrl(opts);\n const existingScript = document.querySelector<HTMLScriptElement>('script[data-clerk-js-script]');\n\n if (existingScript) {\n if (hasScriptRequestError(scriptUrl)) {\n existingScript.remove();\n } else {\n try {\n await waitForPredicateWithTimeout(timeout, isClerkProperlyLoaded, rejectWith(), existingScript);\n return null;\n } catch {\n existingScript.remove();\n }\n }\n }\n\n const loadPromise = waitForPredicateWithTimeout(timeout, isClerkProperlyLoaded, rejectWith());\n\n loadScript(scriptUrl, {\n async: true,\n crossOrigin: 'anonymous',\n nonce: opts.nonce,\n beforeLoad: applyAttributesToScript(buildClerkJSScriptAttributes(opts)),\n }).catch(error => {\n throw rejectWith(error);\n });\n\n return loadPromise;\n};\n\nexport const loadClerkUIScript = async (opts?: LoadClerkUIScriptOptions): Promise<HTMLScriptElement | null> => {\n const timeout = opts?.scriptLoadTimeout ?? 15000;\n const rejectWith = (error?: Error) =>\n new ClerkRuntimeError('Failed to load Clerk UI' + (error?.message ? `, ${error.message}` : ''), {\n code: 'failed_to_load_clerk_ui',\n cause: error,\n });\n\n if (isClerkUIProperlyLoaded()) {\n return null;\n }\n\n if (!opts?.publishableKey) {\n errorThrower.throwMissingPublishableKeyError();\n return null;\n }\n\n const scriptUrl = clerkUIScriptUrl(opts);\n const existingScript = document.querySelector<HTMLScriptElement>('script[data-clerk-ui-script]');\n\n if (existingScript) {\n if (hasScriptRequestError(scriptUrl)) {\n existingScript.remove();\n } else {\n try {\n await waitForPredicateWithTimeout(timeout, isClerkUIProperlyLoaded, rejectWith(), existingScript);\n return null;\n } catch {\n existingScript.remove();\n }\n }\n }\n\n const loadPromise = waitForPredicateWithTimeout(timeout, isClerkUIProperlyLoaded, rejectWith());\n\n loadScript(scriptUrl, {\n async: true,\n crossOrigin: 'anonymous',\n nonce: opts.nonce,\n beforeLoad: applyAttributesToScript(buildClerkUIScriptAttributes(opts)),\n }).catch(error => {\n throw rejectWith(error);\n });\n\n return loadPromise;\n};\n\nexport const clerkJSScriptUrl = (opts: LoadClerkJSScriptOptions) => {\n const { __internal_clerkJSUrl, __internal_clerkJSVersion, proxyUrl, domain, publishableKey } = opts;\n\n if (__internal_clerkJSUrl) {\n return __internal_clerkJSUrl;\n }\n\n const version = versionSelector(__internal_clerkJSVersion);\n\n if (proxyUrl && isProxyUrlRelative(proxyUrl)) {\n return buildRelativeProxyScriptUrl(proxyUrl, 'clerk-js', version, 'clerk.browser.js');\n }\n\n const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain });\n return `https://${scriptHost}/npm/@clerk/clerk-js@${version}/dist/clerk.browser.js`;\n};\n\nexport const clerkUIScriptUrl = (opts: LoadClerkUIScriptOptions) => {\n const { __internal_clerkUIUrl, __internal_clerkUIVersion, proxyUrl, domain, publishableKey } = opts;\n\n if (__internal_clerkUIUrl) {\n return __internal_clerkUIUrl;\n }\n\n const version = versionSelector(__internal_clerkUIVersion, UI_PACKAGE_VERSION);\n\n if (proxyUrl && isProxyUrlRelative(proxyUrl)) {\n return buildRelativeProxyScriptUrl(proxyUrl, 'ui', version, 'ui.browser.js');\n }\n\n const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain });\n return `https://${scriptHost}/npm/@clerk/ui@${version}/dist/ui.browser.js`;\n};\n\nexport const buildClerkJSScriptAttributes = (options: LoadClerkJSScriptOptions) => {\n const obj: Record<string, string> = {};\n\n if (options.publishableKey) {\n obj['data-clerk-publishable-key'] = options.publishableKey;\n }\n\n if (options.proxyUrl) {\n obj['data-clerk-proxy-url'] = options.proxyUrl;\n }\n\n if (options.domain) {\n obj['data-clerk-domain'] = options.domain;\n }\n\n if (options.nonce) {\n obj.nonce = options.nonce;\n }\n\n return obj;\n};\n\nexport const buildClerkUIScriptAttributes = (options: LoadClerkUIScriptOptions) => {\n // TODO @nikos do we need this?\n return buildClerkJSScriptAttributes(options);\n};\n\nconst applyAttributesToScript = (attributes: Record<string, string>) => (script: HTMLScriptElement) => {\n for (const attribute in attributes) {\n script.setAttribute(attribute, attributes[attribute]);\n }\n};\n\nconst stripTrailingSlashes = (value: string) => {\n while (value.endsWith('/')) {\n value = value.slice(0, -1);\n }\n\n return value;\n};\n\nconst buildRelativeProxyScriptUrl = (proxyUrl: string, packageName: string, version: string, fileName: string) => {\n return `${stripTrailingSlashes(proxyUrl)}/npm/@clerk/${packageName}@${version}/dist/${fileName}`;\n};\n\nexport const buildScriptHost = (opts: { publishableKey: string; proxyUrl?: string; domain?: string }) => {\n const { proxyUrl, domain, publishableKey } = opts;\n\n if (!!proxyUrl && isValidProxyUrl(proxyUrl)) {\n const resolvedProxyUrl = proxyUrlToAbsoluteURL(proxyUrl);\n\n if (isProxyUrlRelative(resolvedProxyUrl)) {\n return parsePublishableKey(publishableKey)?.frontendApi || '';\n }\n\n return resolvedProxyUrl.replace(/http(s)?:\\/\\//, '');\n } else if (domain && !isDevOrStagingUrl(parsePublishableKey(publishableKey)?.frontendApi || '')) {\n return addClerkPrefix(domain);\n } else {\n return parsePublishableKey(publishableKey)?.frontendApi || '';\n }\n};\n\nfunction waitForPredicateWithTimeout(\n timeoutMs: number,\n predicate: () => boolean,\n rejectWith: Error,\n existingScript?: HTMLScriptElement,\n): Promise<HTMLScriptElement | null> {\n return new Promise((resolve, reject) => {\n let resolved = false;\n\n const cleanup = (timeoutId: ReturnType<typeof setTimeout>, pollInterval: ReturnType<typeof setInterval>) => {\n clearTimeout(timeoutId);\n clearInterval(pollInterval);\n };\n\n // Bail out early if the script fails to load, instead of waiting for the entire timeout\n existingScript?.addEventListener('error', () => {\n cleanup(timeoutId, pollInterval);\n reject(rejectWith);\n });\n\n const checkAndResolve = () => {\n if (resolved) {\n return;\n }\n\n if (predicate()) {\n resolved = true;\n cleanup(timeoutId, pollInterval);\n resolve(null);\n }\n };\n\n const handleTimeout = () => {\n if (resolved) {\n return;\n }\n\n resolved = true;\n cleanup(timeoutId, pollInterval);\n\n if (!predicate()) {\n reject(rejectWith);\n } else {\n resolve(null);\n }\n };\n\n const timeoutId = setTimeout(handleTimeout, timeoutMs);\n\n checkAndResolve();\n\n const pollInterval = setInterval(() => {\n if (resolved) {\n clearInterval(pollInterval);\n return;\n }\n checkAndResolve();\n }, 100);\n });\n}\n\nexport function setClerkJSLoadingErrorPackageName(packageName: string) {\n errorThrower.setPackageName({ packageName });\n}\n\n/**\n * @deprecated Use `loadClerkJSScript` instead. This alias will be removed in a future major version.\n */\nexport const loadClerkJsScript = loadClerkJSScript;\n\n/**\n * @deprecated Use `clerkJSScriptUrl` instead. This alias will be removed in a future major version.\n */\nexport const clerkJsScriptUrl = clerkJSScriptUrl;\n\n/**\n * @deprecated Use `buildClerkJSScriptAttributes` instead. This alias will be removed in a future major version.\n */\nexport const buildClerkJsScriptAttributes = buildClerkJSScriptAttributes;\n\n/**\n * @deprecated Use `setClerkJSLoadingErrorPackageName` instead. This alias will be removed in a future major version.\n */\nexport const setClerkJsLoadingErrorPackageName = setClerkJSLoadingErrorPackageName;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"loadClerkJsScript.js","names":["createDevOrStagingUrlCache","buildErrorThrower","ClerkRuntimeError","versionSelector","isProxyUrlRelative","isValidProxyUrl","proxyUrlToAbsoluteURL","parsePublishableKey","addClerkPrefix"],"sources":["../src/loadClerkJsScript.ts"],"sourcesContent":["import { buildErrorThrower, ClerkRuntimeError } from './error';\nimport { createDevOrStagingUrlCache, parsePublishableKey } from './keys';\nimport { loadScript } from './loadScript';\nimport { isProxyUrlRelative, isValidProxyUrl, proxyUrlToAbsoluteURL } from './proxy';\nimport type { SDKMetadata } from './types';\nimport { addClerkPrefix } from './url';\nimport { versionSelector } from './versionSelector';\n\nconst { isDevOrStagingUrl } = createDevOrStagingUrlCache();\n\nconst errorThrower = buildErrorThrower({ packageName: '@clerk/shared' });\n\nexport type LoadClerkJSScriptOptions = {\n publishableKey: string;\n /** @internal */\n __internal_clerkJSUrl?: string;\n /** @internal */\n __internal_clerkJSVersion?: string;\n sdkMetadata?: SDKMetadata;\n proxyUrl?: string;\n domain?: string;\n nonce?: string;\n /**\n * Timeout in milliseconds to wait for clerk-js to load before considering it failed.\n *\n * @default 15000 (15 seconds)\n */\n scriptLoadTimeout?: number;\n};\n\n/**\n * @deprecated Use `LoadClerkJSScriptOptions` instead. This alias will be removed in a future major version.\n */\nexport type LoadClerkJsScriptOptions = LoadClerkJSScriptOptions;\n\nexport type LoadClerkUIScriptOptions = {\n publishableKey: string;\n /** @internal */\n __internal_clerkUIUrl?: string;\n /** @internal */\n __internal_clerkUIVersion?: string;\n proxyUrl?: string;\n domain?: string;\n nonce?: string;\n scriptLoadTimeout?: number;\n};\n\n/**\n * Validates that window.Clerk exists and is properly initialized.\n * This ensures we don't have false positives where the script loads but Clerk is malformed.\n *\n * @returns `true` if window.Clerk exists and has the expected structure with a load method.\n */\nfunction isClerkGlobalProperlyLoaded(prop: 'Clerk' | '__internal_ClerkUICtor'): boolean {\n if (typeof window === 'undefined' || !(window as any)[prop]) {\n return false;\n }\n\n // Basic validation that window.Clerk has the expected structure\n const val = (window as any)[prop];\n return !!val;\n}\nconst isClerkProperlyLoaded = () => isClerkGlobalProperlyLoaded('Clerk');\nconst isClerkUIProperlyLoaded = () => isClerkGlobalProperlyLoaded('__internal_ClerkUICtor');\n\n/**\n * Checks if an existing script has a request error using Performance API.\n *\n * @param scriptUrl - The URL of the script to check.\n * @returns True if the script has failed to load due to a network/HTTP error.\n */\nfunction hasScriptRequestError(scriptUrl: string): boolean {\n if (typeof window === 'undefined' || !window.performance) {\n return false;\n }\n\n const entries = performance.getEntriesByName(scriptUrl, 'resource') as PerformanceResourceTiming[];\n\n if (entries.length === 0) {\n return false;\n }\n\n const scriptEntry = entries[entries.length - 1];\n\n // transferSize === 0 with responseEnd === 0 indicates network failure\n // transferSize === 0 with responseEnd > 0 might be a 4xx/5xx error or blocked request\n if (scriptEntry.transferSize === 0 && scriptEntry.decodedBodySize === 0) {\n // If there was no response at all, it's definitely an error\n if (scriptEntry.responseEnd === 0) {\n return true;\n }\n // If we got a response but no content, likely an HTTP error (4xx/5xx)\n if (scriptEntry.responseEnd > 0 && scriptEntry.responseStart > 0) {\n return true;\n }\n\n if ('responseStatus' in scriptEntry) {\n const status = (scriptEntry as any).responseStatus;\n if (status >= 400) {\n return true;\n }\n if (scriptEntry.responseStatus === 0) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Hotloads the Clerk JS script with robust failure detection.\n *\n * Uses a timeout-based approach to ensure absolute certainty about load success/failure.\n * If the script fails to load within the timeout period, or loads but doesn't create\n * a proper Clerk instance, the promise rejects with an error.\n *\n * @param opts - The options used to build the Clerk JS script URL and load the script.\n * Must include a `publishableKey` if no existing script is found.\n * @returns Promise that resolves with null if Clerk loads successfully, or rejects with an error.\n *\n * @example\n * ```typescript\n * try {\n * await loadClerkJsScript({ publishableKey: 'pk_test_...' });\n * console.log('Clerk loaded successfully');\n * } catch (error) {\n * console.error('Failed to load Clerk:', error.message);\n * }\n * ```\n */\nexport const loadClerkJSScript = async (opts?: LoadClerkJSScriptOptions): Promise<HTMLScriptElement | null> => {\n const timeout = opts?.scriptLoadTimeout ?? 15000;\n const rejectWith = (error?: Error) =>\n new ClerkRuntimeError('Failed to load Clerk JS' + (error?.message ? `, ${error.message}` : ''), {\n code: 'failed_to_load_clerk_js',\n cause: error,\n });\n\n if (isClerkProperlyLoaded()) {\n return null;\n }\n\n if (!opts?.publishableKey) {\n errorThrower.throwMissingPublishableKeyError();\n return null;\n }\n\n const scriptUrl = clerkJSScriptUrl(opts);\n const existingScript = document.querySelector<HTMLScriptElement>('script[data-clerk-js-script]');\n\n if (existingScript) {\n if (hasScriptRequestError(scriptUrl)) {\n existingScript.remove();\n } else {\n try {\n await waitForPredicateWithTimeout(timeout, isClerkProperlyLoaded, rejectWith(), existingScript);\n return null;\n } catch {\n existingScript.remove();\n }\n }\n }\n\n const loadPromise = waitForPredicateWithTimeout(timeout, isClerkProperlyLoaded, rejectWith());\n\n loadScript(scriptUrl, {\n async: true,\n crossOrigin: 'anonymous',\n nonce: opts.nonce,\n beforeLoad: applyAttributesToScript(buildClerkJSScriptAttributes(opts)),\n }).catch(error => {\n throw rejectWith(error);\n });\n\n return loadPromise;\n};\n\nexport const loadClerkUIScript = async (opts?: LoadClerkUIScriptOptions): Promise<HTMLScriptElement | null> => {\n const timeout = opts?.scriptLoadTimeout ?? 15000;\n const rejectWith = (error?: Error) =>\n new ClerkRuntimeError('Failed to load Clerk UI' + (error?.message ? `, ${error.message}` : ''), {\n code: 'failed_to_load_clerk_ui',\n cause: error,\n });\n\n if (isClerkUIProperlyLoaded()) {\n return null;\n }\n\n if (!opts?.publishableKey) {\n errorThrower.throwMissingPublishableKeyError();\n return null;\n }\n\n const scriptUrl = clerkUIScriptUrl(opts);\n const existingScript = document.querySelector<HTMLScriptElement>('script[data-clerk-ui-script]');\n\n if (existingScript) {\n if (hasScriptRequestError(scriptUrl)) {\n existingScript.remove();\n } else {\n try {\n await waitForPredicateWithTimeout(timeout, isClerkUIProperlyLoaded, rejectWith(), existingScript);\n return null;\n } catch {\n existingScript.remove();\n }\n }\n }\n\n const loadPromise = waitForPredicateWithTimeout(timeout, isClerkUIProperlyLoaded, rejectWith());\n\n loadScript(scriptUrl, {\n async: true,\n crossOrigin: 'anonymous',\n nonce: opts.nonce,\n beforeLoad: applyAttributesToScript(buildClerkUIScriptAttributes(opts)),\n }).catch(error => {\n throw rejectWith(error);\n });\n\n return loadPromise;\n};\n\nexport const clerkJSScriptUrl = (opts: LoadClerkJSScriptOptions) => {\n const { __internal_clerkJSUrl, __internal_clerkJSVersion, proxyUrl, domain, publishableKey } = opts;\n\n if (__internal_clerkJSUrl) {\n return __internal_clerkJSUrl;\n }\n\n const version = versionSelector(__internal_clerkJSVersion);\n\n if (proxyUrl && isProxyUrlRelative(proxyUrl)) {\n return buildRelativeProxyScriptUrl(proxyUrl, 'clerk-js', version, 'clerk.browser.js');\n }\n\n const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain });\n return `https://${scriptHost}/npm/@clerk/clerk-js@${version}/dist/clerk.browser.js`;\n};\n\nexport const clerkUIScriptUrl = (opts: LoadClerkUIScriptOptions) => {\n const { __internal_clerkUIUrl, __internal_clerkUIVersion, proxyUrl, domain, publishableKey } = opts;\n\n if (__internal_clerkUIUrl) {\n return __internal_clerkUIUrl;\n }\n\n const version = versionSelector(__internal_clerkUIVersion, UI_PACKAGE_VERSION);\n\n if (proxyUrl && isProxyUrlRelative(proxyUrl)) {\n return buildRelativeProxyScriptUrl(proxyUrl, 'ui', version, 'ui.browser.js');\n }\n\n const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain });\n return `https://${scriptHost}/npm/@clerk/ui@${version}/dist/ui.browser.js`;\n};\n\nexport const buildClerkJSScriptAttributes = (options: LoadClerkJSScriptOptions) => {\n const obj: Record<string, string> = {};\n\n if (options.publishableKey) {\n obj['data-clerk-publishable-key'] = options.publishableKey;\n }\n\n if (options.proxyUrl) {\n obj['data-clerk-proxy-url'] = options.proxyUrl;\n }\n\n if (options.domain) {\n obj['data-clerk-domain'] = options.domain;\n }\n\n if (options.nonce) {\n obj.nonce = options.nonce;\n }\n\n return obj;\n};\n\nexport const buildClerkUIScriptAttributes = (options: LoadClerkUIScriptOptions) => {\n // TODO @nikos do we need this?\n return buildClerkJSScriptAttributes(options);\n};\n\nconst applyAttributesToScript = (attributes: Record<string, string>) => (script: HTMLScriptElement) => {\n for (const attribute in attributes) {\n script.setAttribute(attribute, attributes[attribute]);\n }\n};\n\nconst stripTrailingSlashes = (value: string) => {\n while (value.endsWith('/')) {\n value = value.slice(0, -1);\n }\n\n return value;\n};\n\nconst buildRelativeProxyScriptUrl = (proxyUrl: string, packageName: string, version: string, fileName: string) => {\n return `${stripTrailingSlashes(proxyUrl)}/npm/@clerk/${packageName}@${version}/dist/${fileName}`;\n};\n\nexport const buildScriptHost = (opts: { publishableKey: string; proxyUrl?: string; domain?: string }) => {\n const { proxyUrl, domain, publishableKey } = opts;\n\n if (!!proxyUrl && isValidProxyUrl(proxyUrl)) {\n const resolvedProxyUrl = proxyUrlToAbsoluteURL(proxyUrl);\n\n if (isProxyUrlRelative(resolvedProxyUrl)) {\n return parsePublishableKey(publishableKey)?.frontendApi || '';\n }\n\n return resolvedProxyUrl.replace(/http(s)?:\\/\\//, '');\n } else if (domain && !isDevOrStagingUrl(parsePublishableKey(publishableKey)?.frontendApi || '')) {\n return addClerkPrefix(domain);\n } else {\n return parsePublishableKey(publishableKey)?.frontendApi || '';\n }\n};\n\nfunction waitForPredicateWithTimeout(\n timeoutMs: number,\n predicate: () => boolean,\n rejectWith: Error,\n existingScript?: HTMLScriptElement,\n): Promise<HTMLScriptElement | null> {\n return new Promise((resolve, reject) => {\n let resolved = false;\n\n const cleanup = (timeoutId: ReturnType<typeof setTimeout>, pollInterval: ReturnType<typeof setInterval>) => {\n clearTimeout(timeoutId);\n clearInterval(pollInterval);\n };\n\n // Bail out early if the script fails to load, instead of waiting for the entire timeout\n existingScript?.addEventListener('error', () => {\n cleanup(timeoutId, pollInterval);\n reject(rejectWith);\n });\n\n const checkAndResolve = () => {\n if (resolved) {\n return;\n }\n\n if (predicate()) {\n resolved = true;\n cleanup(timeoutId, pollInterval);\n resolve(null);\n }\n };\n\n const handleTimeout = () => {\n if (resolved) {\n return;\n }\n\n resolved = true;\n cleanup(timeoutId, pollInterval);\n\n if (!predicate()) {\n reject(rejectWith);\n } else {\n resolve(null);\n }\n };\n\n const timeoutId = setTimeout(handleTimeout, timeoutMs);\n\n checkAndResolve();\n\n const pollInterval = setInterval(() => {\n if (resolved) {\n clearInterval(pollInterval);\n return;\n }\n checkAndResolve();\n }, 100);\n });\n}\n\nexport function setClerkJSLoadingErrorPackageName(packageName: string) {\n errorThrower.setPackageName({ packageName });\n}\n\n/**\n * @deprecated Use `loadClerkJSScript` instead. This alias will be removed in a future major version.\n */\nexport const loadClerkJsScript = loadClerkJSScript;\n\n/**\n * @deprecated Use `clerkJSScriptUrl` instead. This alias will be removed in a future major version.\n */\nexport const clerkJsScriptUrl = clerkJSScriptUrl;\n\n/**\n * @deprecated Use `buildClerkJSScriptAttributes` instead. This alias will be removed in a future major version.\n */\nexport const buildClerkJsScriptAttributes = buildClerkJSScriptAttributes;\n\n/**\n * @deprecated Use `setClerkJSLoadingErrorPackageName` instead. This alias will be removed in a future major version.\n */\nexport const setClerkJsLoadingErrorPackageName = setClerkJSLoadingErrorPackageName;\n"],"mappings":"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"}
|
|
@@ -139,7 +139,7 @@ const clerkJSScriptUrl = (opts) => {
|
|
|
139
139
|
const clerkUIScriptUrl = (opts) => {
|
|
140
140
|
const { __internal_clerkUIUrl, __internal_clerkUIVersion, proxyUrl, domain, publishableKey } = opts;
|
|
141
141
|
if (__internal_clerkUIUrl) return __internal_clerkUIUrl;
|
|
142
|
-
const version = versionSelector(__internal_clerkUIVersion, "1.30.8
|
|
142
|
+
const version = versionSelector(__internal_clerkUIVersion, "1.30.8");
|
|
143
143
|
if (proxyUrl && isProxyUrlRelative(proxyUrl)) return buildRelativeProxyScriptUrl(proxyUrl, "ui", version, "ui.browser.js");
|
|
144
144
|
return `https://${buildScriptHost({
|
|
145
145
|
publishableKey,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadClerkJsScript.mjs","names":[],"sources":["../src/loadClerkJsScript.ts"],"sourcesContent":["import { buildErrorThrower, ClerkRuntimeError } from './error';\nimport { createDevOrStagingUrlCache, parsePublishableKey } from './keys';\nimport { loadScript } from './loadScript';\nimport { isProxyUrlRelative, isValidProxyUrl, proxyUrlToAbsoluteURL } from './proxy';\nimport type { SDKMetadata } from './types';\nimport { addClerkPrefix } from './url';\nimport { versionSelector } from './versionSelector';\n\nconst { isDevOrStagingUrl } = createDevOrStagingUrlCache();\n\nconst errorThrower = buildErrorThrower({ packageName: '@clerk/shared' });\n\nexport type LoadClerkJSScriptOptions = {\n publishableKey: string;\n /** @internal */\n __internal_clerkJSUrl?: string;\n /** @internal */\n __internal_clerkJSVersion?: string;\n sdkMetadata?: SDKMetadata;\n proxyUrl?: string;\n domain?: string;\n nonce?: string;\n /**\n * Timeout in milliseconds to wait for clerk-js to load before considering it failed.\n *\n * @default 15000 (15 seconds)\n */\n scriptLoadTimeout?: number;\n};\n\n/**\n * @deprecated Use `LoadClerkJSScriptOptions` instead. This alias will be removed in a future major version.\n */\nexport type LoadClerkJsScriptOptions = LoadClerkJSScriptOptions;\n\nexport type LoadClerkUIScriptOptions = {\n publishableKey: string;\n /** @internal */\n __internal_clerkUIUrl?: string;\n /** @internal */\n __internal_clerkUIVersion?: string;\n proxyUrl?: string;\n domain?: string;\n nonce?: string;\n scriptLoadTimeout?: number;\n};\n\n/**\n * Validates that window.Clerk exists and is properly initialized.\n * This ensures we don't have false positives where the script loads but Clerk is malformed.\n *\n * @returns `true` if window.Clerk exists and has the expected structure with a load method.\n */\nfunction isClerkGlobalProperlyLoaded(prop: 'Clerk' | '__internal_ClerkUICtor'): boolean {\n if (typeof window === 'undefined' || !(window as any)[prop]) {\n return false;\n }\n\n // Basic validation that window.Clerk has the expected structure\n const val = (window as any)[prop];\n return !!val;\n}\nconst isClerkProperlyLoaded = () => isClerkGlobalProperlyLoaded('Clerk');\nconst isClerkUIProperlyLoaded = () => isClerkGlobalProperlyLoaded('__internal_ClerkUICtor');\n\n/**\n * Checks if an existing script has a request error using Performance API.\n *\n * @param scriptUrl - The URL of the script to check.\n * @returns True if the script has failed to load due to a network/HTTP error.\n */\nfunction hasScriptRequestError(scriptUrl: string): boolean {\n if (typeof window === 'undefined' || !window.performance) {\n return false;\n }\n\n const entries = performance.getEntriesByName(scriptUrl, 'resource') as PerformanceResourceTiming[];\n\n if (entries.length === 0) {\n return false;\n }\n\n const scriptEntry = entries[entries.length - 1];\n\n // transferSize === 0 with responseEnd === 0 indicates network failure\n // transferSize === 0 with responseEnd > 0 might be a 4xx/5xx error or blocked request\n if (scriptEntry.transferSize === 0 && scriptEntry.decodedBodySize === 0) {\n // If there was no response at all, it's definitely an error\n if (scriptEntry.responseEnd === 0) {\n return true;\n }\n // If we got a response but no content, likely an HTTP error (4xx/5xx)\n if (scriptEntry.responseEnd > 0 && scriptEntry.responseStart > 0) {\n return true;\n }\n\n if ('responseStatus' in scriptEntry) {\n const status = (scriptEntry as any).responseStatus;\n if (status >= 400) {\n return true;\n }\n if (scriptEntry.responseStatus === 0) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Hotloads the Clerk JS script with robust failure detection.\n *\n * Uses a timeout-based approach to ensure absolute certainty about load success/failure.\n * If the script fails to load within the timeout period, or loads but doesn't create\n * a proper Clerk instance, the promise rejects with an error.\n *\n * @param opts - The options used to build the Clerk JS script URL and load the script.\n * Must include a `publishableKey` if no existing script is found.\n * @returns Promise that resolves with null if Clerk loads successfully, or rejects with an error.\n *\n * @example\n * ```typescript\n * try {\n * await loadClerkJsScript({ publishableKey: 'pk_test_...' });\n * console.log('Clerk loaded successfully');\n * } catch (error) {\n * console.error('Failed to load Clerk:', error.message);\n * }\n * ```\n */\nexport const loadClerkJSScript = async (opts?: LoadClerkJSScriptOptions): Promise<HTMLScriptElement | null> => {\n const timeout = opts?.scriptLoadTimeout ?? 15000;\n const rejectWith = (error?: Error) =>\n new ClerkRuntimeError('Failed to load Clerk JS' + (error?.message ? `, ${error.message}` : ''), {\n code: 'failed_to_load_clerk_js',\n cause: error,\n });\n\n if (isClerkProperlyLoaded()) {\n return null;\n }\n\n if (!opts?.publishableKey) {\n errorThrower.throwMissingPublishableKeyError();\n return null;\n }\n\n const scriptUrl = clerkJSScriptUrl(opts);\n const existingScript = document.querySelector<HTMLScriptElement>('script[data-clerk-js-script]');\n\n if (existingScript) {\n if (hasScriptRequestError(scriptUrl)) {\n existingScript.remove();\n } else {\n try {\n await waitForPredicateWithTimeout(timeout, isClerkProperlyLoaded, rejectWith(), existingScript);\n return null;\n } catch {\n existingScript.remove();\n }\n }\n }\n\n const loadPromise = waitForPredicateWithTimeout(timeout, isClerkProperlyLoaded, rejectWith());\n\n loadScript(scriptUrl, {\n async: true,\n crossOrigin: 'anonymous',\n nonce: opts.nonce,\n beforeLoad: applyAttributesToScript(buildClerkJSScriptAttributes(opts)),\n }).catch(error => {\n throw rejectWith(error);\n });\n\n return loadPromise;\n};\n\nexport const loadClerkUIScript = async (opts?: LoadClerkUIScriptOptions): Promise<HTMLScriptElement | null> => {\n const timeout = opts?.scriptLoadTimeout ?? 15000;\n const rejectWith = (error?: Error) =>\n new ClerkRuntimeError('Failed to load Clerk UI' + (error?.message ? `, ${error.message}` : ''), {\n code: 'failed_to_load_clerk_ui',\n cause: error,\n });\n\n if (isClerkUIProperlyLoaded()) {\n return null;\n }\n\n if (!opts?.publishableKey) {\n errorThrower.throwMissingPublishableKeyError();\n return null;\n }\n\n const scriptUrl = clerkUIScriptUrl(opts);\n const existingScript = document.querySelector<HTMLScriptElement>('script[data-clerk-ui-script]');\n\n if (existingScript) {\n if (hasScriptRequestError(scriptUrl)) {\n existingScript.remove();\n } else {\n try {\n await waitForPredicateWithTimeout(timeout, isClerkUIProperlyLoaded, rejectWith(), existingScript);\n return null;\n } catch {\n existingScript.remove();\n }\n }\n }\n\n const loadPromise = waitForPredicateWithTimeout(timeout, isClerkUIProperlyLoaded, rejectWith());\n\n loadScript(scriptUrl, {\n async: true,\n crossOrigin: 'anonymous',\n nonce: opts.nonce,\n beforeLoad: applyAttributesToScript(buildClerkUIScriptAttributes(opts)),\n }).catch(error => {\n throw rejectWith(error);\n });\n\n return loadPromise;\n};\n\nexport const clerkJSScriptUrl = (opts: LoadClerkJSScriptOptions) => {\n const { __internal_clerkJSUrl, __internal_clerkJSVersion, proxyUrl, domain, publishableKey } = opts;\n\n if (__internal_clerkJSUrl) {\n return __internal_clerkJSUrl;\n }\n\n const version = versionSelector(__internal_clerkJSVersion);\n\n if (proxyUrl && isProxyUrlRelative(proxyUrl)) {\n return buildRelativeProxyScriptUrl(proxyUrl, 'clerk-js', version, 'clerk.browser.js');\n }\n\n const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain });\n return `https://${scriptHost}/npm/@clerk/clerk-js@${version}/dist/clerk.browser.js`;\n};\n\nexport const clerkUIScriptUrl = (opts: LoadClerkUIScriptOptions) => {\n const { __internal_clerkUIUrl, __internal_clerkUIVersion, proxyUrl, domain, publishableKey } = opts;\n\n if (__internal_clerkUIUrl) {\n return __internal_clerkUIUrl;\n }\n\n const version = versionSelector(__internal_clerkUIVersion, UI_PACKAGE_VERSION);\n\n if (proxyUrl && isProxyUrlRelative(proxyUrl)) {\n return buildRelativeProxyScriptUrl(proxyUrl, 'ui', version, 'ui.browser.js');\n }\n\n const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain });\n return `https://${scriptHost}/npm/@clerk/ui@${version}/dist/ui.browser.js`;\n};\n\nexport const buildClerkJSScriptAttributes = (options: LoadClerkJSScriptOptions) => {\n const obj: Record<string, string> = {};\n\n if (options.publishableKey) {\n obj['data-clerk-publishable-key'] = options.publishableKey;\n }\n\n if (options.proxyUrl) {\n obj['data-clerk-proxy-url'] = options.proxyUrl;\n }\n\n if (options.domain) {\n obj['data-clerk-domain'] = options.domain;\n }\n\n if (options.nonce) {\n obj.nonce = options.nonce;\n }\n\n return obj;\n};\n\nexport const buildClerkUIScriptAttributes = (options: LoadClerkUIScriptOptions) => {\n // TODO @nikos do we need this?\n return buildClerkJSScriptAttributes(options);\n};\n\nconst applyAttributesToScript = (attributes: Record<string, string>) => (script: HTMLScriptElement) => {\n for (const attribute in attributes) {\n script.setAttribute(attribute, attributes[attribute]);\n }\n};\n\nconst stripTrailingSlashes = (value: string) => {\n while (value.endsWith('/')) {\n value = value.slice(0, -1);\n }\n\n return value;\n};\n\nconst buildRelativeProxyScriptUrl = (proxyUrl: string, packageName: string, version: string, fileName: string) => {\n return `${stripTrailingSlashes(proxyUrl)}/npm/@clerk/${packageName}@${version}/dist/${fileName}`;\n};\n\nexport const buildScriptHost = (opts: { publishableKey: string; proxyUrl?: string; domain?: string }) => {\n const { proxyUrl, domain, publishableKey } = opts;\n\n if (!!proxyUrl && isValidProxyUrl(proxyUrl)) {\n const resolvedProxyUrl = proxyUrlToAbsoluteURL(proxyUrl);\n\n if (isProxyUrlRelative(resolvedProxyUrl)) {\n return parsePublishableKey(publishableKey)?.frontendApi || '';\n }\n\n return resolvedProxyUrl.replace(/http(s)?:\\/\\//, '');\n } else if (domain && !isDevOrStagingUrl(parsePublishableKey(publishableKey)?.frontendApi || '')) {\n return addClerkPrefix(domain);\n } else {\n return parsePublishableKey(publishableKey)?.frontendApi || '';\n }\n};\n\nfunction waitForPredicateWithTimeout(\n timeoutMs: number,\n predicate: () => boolean,\n rejectWith: Error,\n existingScript?: HTMLScriptElement,\n): Promise<HTMLScriptElement | null> {\n return new Promise((resolve, reject) => {\n let resolved = false;\n\n const cleanup = (timeoutId: ReturnType<typeof setTimeout>, pollInterval: ReturnType<typeof setInterval>) => {\n clearTimeout(timeoutId);\n clearInterval(pollInterval);\n };\n\n // Bail out early if the script fails to load, instead of waiting for the entire timeout\n existingScript?.addEventListener('error', () => {\n cleanup(timeoutId, pollInterval);\n reject(rejectWith);\n });\n\n const checkAndResolve = () => {\n if (resolved) {\n return;\n }\n\n if (predicate()) {\n resolved = true;\n cleanup(timeoutId, pollInterval);\n resolve(null);\n }\n };\n\n const handleTimeout = () => {\n if (resolved) {\n return;\n }\n\n resolved = true;\n cleanup(timeoutId, pollInterval);\n\n if (!predicate()) {\n reject(rejectWith);\n } else {\n resolve(null);\n }\n };\n\n const timeoutId = setTimeout(handleTimeout, timeoutMs);\n\n checkAndResolve();\n\n const pollInterval = setInterval(() => {\n if (resolved) {\n clearInterval(pollInterval);\n return;\n }\n checkAndResolve();\n }, 100);\n });\n}\n\nexport function setClerkJSLoadingErrorPackageName(packageName: string) {\n errorThrower.setPackageName({ packageName });\n}\n\n/**\n * @deprecated Use `loadClerkJSScript` instead. This alias will be removed in a future major version.\n */\nexport const loadClerkJsScript = loadClerkJSScript;\n\n/**\n * @deprecated Use `clerkJSScriptUrl` instead. This alias will be removed in a future major version.\n */\nexport const clerkJsScriptUrl = clerkJSScriptUrl;\n\n/**\n * @deprecated Use `buildClerkJSScriptAttributes` instead. This alias will be removed in a future major version.\n */\nexport const buildClerkJsScriptAttributes = buildClerkJSScriptAttributes;\n\n/**\n * @deprecated Use `setClerkJSLoadingErrorPackageName` instead. This alias will be removed in a future major version.\n */\nexport const setClerkJsLoadingErrorPackageName = setClerkJSLoadingErrorPackageName;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"loadClerkJsScript.mjs","names":[],"sources":["../src/loadClerkJsScript.ts"],"sourcesContent":["import { buildErrorThrower, ClerkRuntimeError } from './error';\nimport { createDevOrStagingUrlCache, parsePublishableKey } from './keys';\nimport { loadScript } from './loadScript';\nimport { isProxyUrlRelative, isValidProxyUrl, proxyUrlToAbsoluteURL } from './proxy';\nimport type { SDKMetadata } from './types';\nimport { addClerkPrefix } from './url';\nimport { versionSelector } from './versionSelector';\n\nconst { isDevOrStagingUrl } = createDevOrStagingUrlCache();\n\nconst errorThrower = buildErrorThrower({ packageName: '@clerk/shared' });\n\nexport type LoadClerkJSScriptOptions = {\n publishableKey: string;\n /** @internal */\n __internal_clerkJSUrl?: string;\n /** @internal */\n __internal_clerkJSVersion?: string;\n sdkMetadata?: SDKMetadata;\n proxyUrl?: string;\n domain?: string;\n nonce?: string;\n /**\n * Timeout in milliseconds to wait for clerk-js to load before considering it failed.\n *\n * @default 15000 (15 seconds)\n */\n scriptLoadTimeout?: number;\n};\n\n/**\n * @deprecated Use `LoadClerkJSScriptOptions` instead. This alias will be removed in a future major version.\n */\nexport type LoadClerkJsScriptOptions = LoadClerkJSScriptOptions;\n\nexport type LoadClerkUIScriptOptions = {\n publishableKey: string;\n /** @internal */\n __internal_clerkUIUrl?: string;\n /** @internal */\n __internal_clerkUIVersion?: string;\n proxyUrl?: string;\n domain?: string;\n nonce?: string;\n scriptLoadTimeout?: number;\n};\n\n/**\n * Validates that window.Clerk exists and is properly initialized.\n * This ensures we don't have false positives where the script loads but Clerk is malformed.\n *\n * @returns `true` if window.Clerk exists and has the expected structure with a load method.\n */\nfunction isClerkGlobalProperlyLoaded(prop: 'Clerk' | '__internal_ClerkUICtor'): boolean {\n if (typeof window === 'undefined' || !(window as any)[prop]) {\n return false;\n }\n\n // Basic validation that window.Clerk has the expected structure\n const val = (window as any)[prop];\n return !!val;\n}\nconst isClerkProperlyLoaded = () => isClerkGlobalProperlyLoaded('Clerk');\nconst isClerkUIProperlyLoaded = () => isClerkGlobalProperlyLoaded('__internal_ClerkUICtor');\n\n/**\n * Checks if an existing script has a request error using Performance API.\n *\n * @param scriptUrl - The URL of the script to check.\n * @returns True if the script has failed to load due to a network/HTTP error.\n */\nfunction hasScriptRequestError(scriptUrl: string): boolean {\n if (typeof window === 'undefined' || !window.performance) {\n return false;\n }\n\n const entries = performance.getEntriesByName(scriptUrl, 'resource') as PerformanceResourceTiming[];\n\n if (entries.length === 0) {\n return false;\n }\n\n const scriptEntry = entries[entries.length - 1];\n\n // transferSize === 0 with responseEnd === 0 indicates network failure\n // transferSize === 0 with responseEnd > 0 might be a 4xx/5xx error or blocked request\n if (scriptEntry.transferSize === 0 && scriptEntry.decodedBodySize === 0) {\n // If there was no response at all, it's definitely an error\n if (scriptEntry.responseEnd === 0) {\n return true;\n }\n // If we got a response but no content, likely an HTTP error (4xx/5xx)\n if (scriptEntry.responseEnd > 0 && scriptEntry.responseStart > 0) {\n return true;\n }\n\n if ('responseStatus' in scriptEntry) {\n const status = (scriptEntry as any).responseStatus;\n if (status >= 400) {\n return true;\n }\n if (scriptEntry.responseStatus === 0) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Hotloads the Clerk JS script with robust failure detection.\n *\n * Uses a timeout-based approach to ensure absolute certainty about load success/failure.\n * If the script fails to load within the timeout period, or loads but doesn't create\n * a proper Clerk instance, the promise rejects with an error.\n *\n * @param opts - The options used to build the Clerk JS script URL and load the script.\n * Must include a `publishableKey` if no existing script is found.\n * @returns Promise that resolves with null if Clerk loads successfully, or rejects with an error.\n *\n * @example\n * ```typescript\n * try {\n * await loadClerkJsScript({ publishableKey: 'pk_test_...' });\n * console.log('Clerk loaded successfully');\n * } catch (error) {\n * console.error('Failed to load Clerk:', error.message);\n * }\n * ```\n */\nexport const loadClerkJSScript = async (opts?: LoadClerkJSScriptOptions): Promise<HTMLScriptElement | null> => {\n const timeout = opts?.scriptLoadTimeout ?? 15000;\n const rejectWith = (error?: Error) =>\n new ClerkRuntimeError('Failed to load Clerk JS' + (error?.message ? `, ${error.message}` : ''), {\n code: 'failed_to_load_clerk_js',\n cause: error,\n });\n\n if (isClerkProperlyLoaded()) {\n return null;\n }\n\n if (!opts?.publishableKey) {\n errorThrower.throwMissingPublishableKeyError();\n return null;\n }\n\n const scriptUrl = clerkJSScriptUrl(opts);\n const existingScript = document.querySelector<HTMLScriptElement>('script[data-clerk-js-script]');\n\n if (existingScript) {\n if (hasScriptRequestError(scriptUrl)) {\n existingScript.remove();\n } else {\n try {\n await waitForPredicateWithTimeout(timeout, isClerkProperlyLoaded, rejectWith(), existingScript);\n return null;\n } catch {\n existingScript.remove();\n }\n }\n }\n\n const loadPromise = waitForPredicateWithTimeout(timeout, isClerkProperlyLoaded, rejectWith());\n\n loadScript(scriptUrl, {\n async: true,\n crossOrigin: 'anonymous',\n nonce: opts.nonce,\n beforeLoad: applyAttributesToScript(buildClerkJSScriptAttributes(opts)),\n }).catch(error => {\n throw rejectWith(error);\n });\n\n return loadPromise;\n};\n\nexport const loadClerkUIScript = async (opts?: LoadClerkUIScriptOptions): Promise<HTMLScriptElement | null> => {\n const timeout = opts?.scriptLoadTimeout ?? 15000;\n const rejectWith = (error?: Error) =>\n new ClerkRuntimeError('Failed to load Clerk UI' + (error?.message ? `, ${error.message}` : ''), {\n code: 'failed_to_load_clerk_ui',\n cause: error,\n });\n\n if (isClerkUIProperlyLoaded()) {\n return null;\n }\n\n if (!opts?.publishableKey) {\n errorThrower.throwMissingPublishableKeyError();\n return null;\n }\n\n const scriptUrl = clerkUIScriptUrl(opts);\n const existingScript = document.querySelector<HTMLScriptElement>('script[data-clerk-ui-script]');\n\n if (existingScript) {\n if (hasScriptRequestError(scriptUrl)) {\n existingScript.remove();\n } else {\n try {\n await waitForPredicateWithTimeout(timeout, isClerkUIProperlyLoaded, rejectWith(), existingScript);\n return null;\n } catch {\n existingScript.remove();\n }\n }\n }\n\n const loadPromise = waitForPredicateWithTimeout(timeout, isClerkUIProperlyLoaded, rejectWith());\n\n loadScript(scriptUrl, {\n async: true,\n crossOrigin: 'anonymous',\n nonce: opts.nonce,\n beforeLoad: applyAttributesToScript(buildClerkUIScriptAttributes(opts)),\n }).catch(error => {\n throw rejectWith(error);\n });\n\n return loadPromise;\n};\n\nexport const clerkJSScriptUrl = (opts: LoadClerkJSScriptOptions) => {\n const { __internal_clerkJSUrl, __internal_clerkJSVersion, proxyUrl, domain, publishableKey } = opts;\n\n if (__internal_clerkJSUrl) {\n return __internal_clerkJSUrl;\n }\n\n const version = versionSelector(__internal_clerkJSVersion);\n\n if (proxyUrl && isProxyUrlRelative(proxyUrl)) {\n return buildRelativeProxyScriptUrl(proxyUrl, 'clerk-js', version, 'clerk.browser.js');\n }\n\n const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain });\n return `https://${scriptHost}/npm/@clerk/clerk-js@${version}/dist/clerk.browser.js`;\n};\n\nexport const clerkUIScriptUrl = (opts: LoadClerkUIScriptOptions) => {\n const { __internal_clerkUIUrl, __internal_clerkUIVersion, proxyUrl, domain, publishableKey } = opts;\n\n if (__internal_clerkUIUrl) {\n return __internal_clerkUIUrl;\n }\n\n const version = versionSelector(__internal_clerkUIVersion, UI_PACKAGE_VERSION);\n\n if (proxyUrl && isProxyUrlRelative(proxyUrl)) {\n return buildRelativeProxyScriptUrl(proxyUrl, 'ui', version, 'ui.browser.js');\n }\n\n const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain });\n return `https://${scriptHost}/npm/@clerk/ui@${version}/dist/ui.browser.js`;\n};\n\nexport const buildClerkJSScriptAttributes = (options: LoadClerkJSScriptOptions) => {\n const obj: Record<string, string> = {};\n\n if (options.publishableKey) {\n obj['data-clerk-publishable-key'] = options.publishableKey;\n }\n\n if (options.proxyUrl) {\n obj['data-clerk-proxy-url'] = options.proxyUrl;\n }\n\n if (options.domain) {\n obj['data-clerk-domain'] = options.domain;\n }\n\n if (options.nonce) {\n obj.nonce = options.nonce;\n }\n\n return obj;\n};\n\nexport const buildClerkUIScriptAttributes = (options: LoadClerkUIScriptOptions) => {\n // TODO @nikos do we need this?\n return buildClerkJSScriptAttributes(options);\n};\n\nconst applyAttributesToScript = (attributes: Record<string, string>) => (script: HTMLScriptElement) => {\n for (const attribute in attributes) {\n script.setAttribute(attribute, attributes[attribute]);\n }\n};\n\nconst stripTrailingSlashes = (value: string) => {\n while (value.endsWith('/')) {\n value = value.slice(0, -1);\n }\n\n return value;\n};\n\nconst buildRelativeProxyScriptUrl = (proxyUrl: string, packageName: string, version: string, fileName: string) => {\n return `${stripTrailingSlashes(proxyUrl)}/npm/@clerk/${packageName}@${version}/dist/${fileName}`;\n};\n\nexport const buildScriptHost = (opts: { publishableKey: string; proxyUrl?: string; domain?: string }) => {\n const { proxyUrl, domain, publishableKey } = opts;\n\n if (!!proxyUrl && isValidProxyUrl(proxyUrl)) {\n const resolvedProxyUrl = proxyUrlToAbsoluteURL(proxyUrl);\n\n if (isProxyUrlRelative(resolvedProxyUrl)) {\n return parsePublishableKey(publishableKey)?.frontendApi || '';\n }\n\n return resolvedProxyUrl.replace(/http(s)?:\\/\\//, '');\n } else if (domain && !isDevOrStagingUrl(parsePublishableKey(publishableKey)?.frontendApi || '')) {\n return addClerkPrefix(domain);\n } else {\n return parsePublishableKey(publishableKey)?.frontendApi || '';\n }\n};\n\nfunction waitForPredicateWithTimeout(\n timeoutMs: number,\n predicate: () => boolean,\n rejectWith: Error,\n existingScript?: HTMLScriptElement,\n): Promise<HTMLScriptElement | null> {\n return new Promise((resolve, reject) => {\n let resolved = false;\n\n const cleanup = (timeoutId: ReturnType<typeof setTimeout>, pollInterval: ReturnType<typeof setInterval>) => {\n clearTimeout(timeoutId);\n clearInterval(pollInterval);\n };\n\n // Bail out early if the script fails to load, instead of waiting for the entire timeout\n existingScript?.addEventListener('error', () => {\n cleanup(timeoutId, pollInterval);\n reject(rejectWith);\n });\n\n const checkAndResolve = () => {\n if (resolved) {\n return;\n }\n\n if (predicate()) {\n resolved = true;\n cleanup(timeoutId, pollInterval);\n resolve(null);\n }\n };\n\n const handleTimeout = () => {\n if (resolved) {\n return;\n }\n\n resolved = true;\n cleanup(timeoutId, pollInterval);\n\n if (!predicate()) {\n reject(rejectWith);\n } else {\n resolve(null);\n }\n };\n\n const timeoutId = setTimeout(handleTimeout, timeoutMs);\n\n checkAndResolve();\n\n const pollInterval = setInterval(() => {\n if (resolved) {\n clearInterval(pollInterval);\n return;\n }\n checkAndResolve();\n }, 100);\n });\n}\n\nexport function setClerkJSLoadingErrorPackageName(packageName: string) {\n errorThrower.setPackageName({ packageName });\n}\n\n/**\n * @deprecated Use `loadClerkJSScript` instead. This alias will be removed in a future major version.\n */\nexport const loadClerkJsScript = loadClerkJSScript;\n\n/**\n * @deprecated Use `clerkJSScriptUrl` instead. This alias will be removed in a future major version.\n */\nexport const clerkJsScriptUrl = clerkJSScriptUrl;\n\n/**\n * @deprecated Use `buildClerkJSScriptAttributes` instead. This alias will be removed in a future major version.\n */\nexport const buildClerkJsScriptAttributes = buildClerkJSScriptAttributes;\n\n/**\n * @deprecated Use `setClerkJSLoadingErrorPackageName` instead. This alias will be removed in a future major version.\n */\nexport const setClerkJsLoadingErrorPackageName = setClerkJSLoadingErrorPackageName;\n"],"mappings":"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"}
|
package/dist/types/clerk.d.mts
CHANGED
|
@@ -285,6 +285,15 @@ interface Clerk {
|
|
|
285
285
|
* @internal
|
|
286
286
|
*/
|
|
287
287
|
__internal_moduleManager: ModuleManager | undefined;
|
|
288
|
+
/**
|
|
289
|
+
* The verification-module load timeout asked for by the loader this browser was assigned, or
|
|
290
|
+
* undefined when it asked for nothing. The assignment is randomized per page load, so it cannot
|
|
291
|
+
* be recomputed from the environment config; callers fall back to the instance-wide value on
|
|
292
|
+
* that config, and then to the SDK default.
|
|
293
|
+
*
|
|
294
|
+
* @internal
|
|
295
|
+
*/
|
|
296
|
+
__internal_protectChallengeLoadTimeoutMs?: number;
|
|
288
297
|
frontendApi: string;
|
|
289
298
|
/** Your Clerk [Publishable Key](!publishable-key). */
|
|
290
299
|
publishableKey: string;
|
package/dist/types/clerk.d.ts
CHANGED
|
@@ -285,6 +285,15 @@ interface Clerk {
|
|
|
285
285
|
* @internal
|
|
286
286
|
*/
|
|
287
287
|
__internal_moduleManager: ModuleManager | undefined;
|
|
288
|
+
/**
|
|
289
|
+
* The verification-module load timeout asked for by the loader this browser was assigned, or
|
|
290
|
+
* undefined when it asked for nothing. The assignment is randomized per page load, so it cannot
|
|
291
|
+
* be recomputed from the environment config; callers fall back to the instance-wide value on
|
|
292
|
+
* that config, and then to the SDK default.
|
|
293
|
+
*
|
|
294
|
+
* @internal
|
|
295
|
+
*/
|
|
296
|
+
__internal_protectChallengeLoadTimeoutMs?: number;
|
|
288
297
|
frontendApi: string;
|
|
289
298
|
/** Your Clerk [Publishable Key](!publishable-key). */
|
|
290
299
|
publishableKey: string;
|
|
@@ -38,11 +38,34 @@ interface ProtectLoader {
|
|
|
38
38
|
* 5000 and is capped at 10000 by the SDK, so this cannot stall a sign-in.
|
|
39
39
|
*/
|
|
40
40
|
token_timeout_ms?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Overrides {@link ProtectConfigJSON.challenge_load_timeout_ms} for browsers that got this
|
|
43
|
+
* loader.
|
|
44
|
+
*
|
|
45
|
+
* Per loader because loaders roll out gradually: while a new one ramps, two are live for the
|
|
46
|
+
* same instance at once, and the new one may need a different value from the one it replaces.
|
|
47
|
+
*
|
|
48
|
+
* Precedence is across the APPLIED SET, not per loader: the first applied loader with a finite,
|
|
49
|
+
* positive value wins, and the instance-wide value applies only when no applied loader specifies
|
|
50
|
+
* such a value. An instance running two loaders at once should therefore either set this on both
|
|
51
|
+
* or on neither — setting it on one makes it apply to browsers that got the other, which is rarely
|
|
52
|
+
* what is meant.
|
|
53
|
+
*/
|
|
54
|
+
challenge_load_timeout_ms?: number;
|
|
41
55
|
}
|
|
42
56
|
interface ProtectConfigJSON {
|
|
43
57
|
object: 'protect_config';
|
|
44
58
|
id: string;
|
|
45
59
|
loaders?: ProtectLoader[];
|
|
60
|
+
/**
|
|
61
|
+
* How long to wait for a verification module to LOAD before giving up, in milliseconds. Absent
|
|
62
|
+
* means "use the SDK default".
|
|
63
|
+
*
|
|
64
|
+
* It bounds only the load. Once a verification module is running it governs its own duration,
|
|
65
|
+
* so this is not a bound on how long verification may take — the two are deliberately separate
|
|
66
|
+
* budgets, and only the first is the SDK's to set.
|
|
67
|
+
*/
|
|
68
|
+
challenge_load_timeout_ms?: number;
|
|
46
69
|
/**
|
|
47
70
|
* Unix seconds. A session token acquired while an older value was configured is discarded and
|
|
48
71
|
* re-acquired, so raising this makes every browser fetch a fresh one as its environment
|
|
@@ -60,6 +83,8 @@ interface ProtectConfigResource extends ClerkResource {
|
|
|
60
83
|
loaders?: ProtectLoader[];
|
|
61
84
|
/** See {@link ProtectConfigJSON.tokens_invalid_before}. */
|
|
62
85
|
tokens_invalid_before?: number;
|
|
86
|
+
/** See {@link ProtectConfigJSON.challenge_load_timeout_ms}. */
|
|
87
|
+
challenge_load_timeout_ms?: number;
|
|
63
88
|
__internal_toSnapshot: () => ProtectConfigJSONSnapshot;
|
|
64
89
|
}
|
|
65
90
|
/**
|
|
@@ -38,11 +38,34 @@ interface ProtectLoader {
|
|
|
38
38
|
* 5000 and is capped at 10000 by the SDK, so this cannot stall a sign-in.
|
|
39
39
|
*/
|
|
40
40
|
token_timeout_ms?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Overrides {@link ProtectConfigJSON.challenge_load_timeout_ms} for browsers that got this
|
|
43
|
+
* loader.
|
|
44
|
+
*
|
|
45
|
+
* Per loader because loaders roll out gradually: while a new one ramps, two are live for the
|
|
46
|
+
* same instance at once, and the new one may need a different value from the one it replaces.
|
|
47
|
+
*
|
|
48
|
+
* Precedence is across the APPLIED SET, not per loader: the first applied loader with a finite,
|
|
49
|
+
* positive value wins, and the instance-wide value applies only when no applied loader specifies
|
|
50
|
+
* such a value. An instance running two loaders at once should therefore either set this on both
|
|
51
|
+
* or on neither — setting it on one makes it apply to browsers that got the other, which is rarely
|
|
52
|
+
* what is meant.
|
|
53
|
+
*/
|
|
54
|
+
challenge_load_timeout_ms?: number;
|
|
41
55
|
}
|
|
42
56
|
interface ProtectConfigJSON {
|
|
43
57
|
object: 'protect_config';
|
|
44
58
|
id: string;
|
|
45
59
|
loaders?: ProtectLoader[];
|
|
60
|
+
/**
|
|
61
|
+
* How long to wait for a verification module to LOAD before giving up, in milliseconds. Absent
|
|
62
|
+
* means "use the SDK default".
|
|
63
|
+
*
|
|
64
|
+
* It bounds only the load. Once a verification module is running it governs its own duration,
|
|
65
|
+
* so this is not a bound on how long verification may take — the two are deliberately separate
|
|
66
|
+
* budgets, and only the first is the SDK's to set.
|
|
67
|
+
*/
|
|
68
|
+
challenge_load_timeout_ms?: number;
|
|
46
69
|
/**
|
|
47
70
|
* Unix seconds. A session token acquired while an older value was configured is discarded and
|
|
48
71
|
* re-acquired, so raising this makes every browser fetch a fresh one as its environment
|
|
@@ -60,6 +83,8 @@ interface ProtectConfigResource extends ClerkResource {
|
|
|
60
83
|
loaders?: ProtectLoader[];
|
|
61
84
|
/** See {@link ProtectConfigJSON.tokens_invalid_before}. */
|
|
62
85
|
tokens_invalid_before?: number;
|
|
86
|
+
/** See {@link ProtectConfigJSON.challenge_load_timeout_ms}. */
|
|
87
|
+
challenge_load_timeout_ms?: number;
|
|
63
88
|
__internal_toSnapshot: () => ProtectConfigJSONSnapshot;
|
|
64
89
|
}
|
|
65
90
|
/**
|
package/dist/versionSelector.js
CHANGED
|
@@ -12,7 +12,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
12
12
|
* @param packageVersion - The version of `@clerk/clerk-js` that will be used if an explicit version is not provided
|
|
13
13
|
* @returns The npm tag, version or major version to use
|
|
14
14
|
*/
|
|
15
|
-
const versionSelector = (clerkJSVersion, packageVersion = "6.30.1
|
|
15
|
+
const versionSelector = (clerkJSVersion, packageVersion = "6.30.1") => {
|
|
16
16
|
if (clerkJSVersion) return clerkJSVersion;
|
|
17
17
|
const prereleaseTag = getPrereleaseTag(packageVersion);
|
|
18
18
|
if (prereleaseTag) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"versionSelector.js","names":[],"sources":["../src/versionSelector.ts"],"sourcesContent":["/**\n * This version selector is a bit complicated, so here is the flow:\n * 1. Use the clerkJSVersion prop on the provider\n * 2. Use the exact `@clerk/clerk-js` version if it is a `@snapshot` prerelease\n * 3. Use the prerelease tag of `@clerk/clerk-js` or the packageVersion provided\n * 4. Fallback to the major version of `@clerk/clerk-js` or the packageVersion provided\n *\n * @param clerkJSVersion - The optional clerkJSVersion prop on the provider\n * @param packageVersion - The version of `@clerk/clerk-js` that will be used if an explicit version is not provided\n * @returns The npm tag, version or major version to use\n */\nexport const versionSelector = (clerkJSVersion: string | undefined, packageVersion = JS_PACKAGE_VERSION) => {\n if (clerkJSVersion) {\n return clerkJSVersion;\n }\n\n const prereleaseTag = getPrereleaseTag(packageVersion);\n if (prereleaseTag) {\n if (prereleaseTag === 'snapshot') {\n return packageVersion;\n }\n\n return prereleaseTag;\n }\n\n return getMajorVersion(packageVersion);\n};\n\nconst getPrereleaseTag = (packageVersion: string) =>\n packageVersion\n .trim()\n .replace(/^v/, '')\n .match(/-(.+?)(\\.|$)/)?.[1];\n\nexport const getMajorVersion = (packageVersion: string) => packageVersion.trim().replace(/^v/, '').split('.')[0];\n"],"mappings":";;;;;;;;;;;;;;AAWA,MAAa,mBAAmB,gBAAoC,
|
|
1
|
+
{"version":3,"file":"versionSelector.js","names":[],"sources":["../src/versionSelector.ts"],"sourcesContent":["/**\n * This version selector is a bit complicated, so here is the flow:\n * 1. Use the clerkJSVersion prop on the provider\n * 2. Use the exact `@clerk/clerk-js` version if it is a `@snapshot` prerelease\n * 3. Use the prerelease tag of `@clerk/clerk-js` or the packageVersion provided\n * 4. Fallback to the major version of `@clerk/clerk-js` or the packageVersion provided\n *\n * @param clerkJSVersion - The optional clerkJSVersion prop on the provider\n * @param packageVersion - The version of `@clerk/clerk-js` that will be used if an explicit version is not provided\n * @returns The npm tag, version or major version to use\n */\nexport const versionSelector = (clerkJSVersion: string | undefined, packageVersion = JS_PACKAGE_VERSION) => {\n if (clerkJSVersion) {\n return clerkJSVersion;\n }\n\n const prereleaseTag = getPrereleaseTag(packageVersion);\n if (prereleaseTag) {\n if (prereleaseTag === 'snapshot') {\n return packageVersion;\n }\n\n return prereleaseTag;\n }\n\n return getMajorVersion(packageVersion);\n};\n\nconst getPrereleaseTag = (packageVersion: string) =>\n packageVersion\n .trim()\n .replace(/^v/, '')\n .match(/-(.+?)(\\.|$)/)?.[1];\n\nexport const getMajorVersion = (packageVersion: string) => packageVersion.trim().replace(/^v/, '').split('.')[0];\n"],"mappings":";;;;;;;;;;;;;;AAWA,MAAa,mBAAmB,gBAAoC,8BAAwC;CAC1G,IAAI,gBACF,OAAO;CAGT,MAAM,gBAAgB,iBAAiB,cAAc;CACrD,IAAI,eAAe;EACjB,IAAI,kBAAkB,YACpB,OAAO;EAGT,OAAO;CACT;CAEA,OAAO,gBAAgB,cAAc;AACvC;AAEA,MAAM,oBAAoB,mBACxB,eACG,KAAK,CAAC,CACN,QAAQ,MAAM,EAAE,CAAC,CACjB,MAAM,cAAc,CAAC,GAAG;AAE7B,MAAa,mBAAmB,mBAA2B,eAAe,KAAK,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC"}
|
package/dist/versionSelector.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @param packageVersion - The version of `@clerk/clerk-js` that will be used if an explicit version is not provided
|
|
11
11
|
* @returns The npm tag, version or major version to use
|
|
12
12
|
*/
|
|
13
|
-
const versionSelector = (clerkJSVersion, packageVersion = "6.30.1
|
|
13
|
+
const versionSelector = (clerkJSVersion, packageVersion = "6.30.1") => {
|
|
14
14
|
if (clerkJSVersion) return clerkJSVersion;
|
|
15
15
|
const prereleaseTag = getPrereleaseTag(packageVersion);
|
|
16
16
|
if (prereleaseTag) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"versionSelector.mjs","names":[],"sources":["../src/versionSelector.ts"],"sourcesContent":["/**\n * This version selector is a bit complicated, so here is the flow:\n * 1. Use the clerkJSVersion prop on the provider\n * 2. Use the exact `@clerk/clerk-js` version if it is a `@snapshot` prerelease\n * 3. Use the prerelease tag of `@clerk/clerk-js` or the packageVersion provided\n * 4. Fallback to the major version of `@clerk/clerk-js` or the packageVersion provided\n *\n * @param clerkJSVersion - The optional clerkJSVersion prop on the provider\n * @param packageVersion - The version of `@clerk/clerk-js` that will be used if an explicit version is not provided\n * @returns The npm tag, version or major version to use\n */\nexport const versionSelector = (clerkJSVersion: string | undefined, packageVersion = JS_PACKAGE_VERSION) => {\n if (clerkJSVersion) {\n return clerkJSVersion;\n }\n\n const prereleaseTag = getPrereleaseTag(packageVersion);\n if (prereleaseTag) {\n if (prereleaseTag === 'snapshot') {\n return packageVersion;\n }\n\n return prereleaseTag;\n }\n\n return getMajorVersion(packageVersion);\n};\n\nconst getPrereleaseTag = (packageVersion: string) =>\n packageVersion\n .trim()\n .replace(/^v/, '')\n .match(/-(.+?)(\\.|$)/)?.[1];\n\nexport const getMajorVersion = (packageVersion: string) => packageVersion.trim().replace(/^v/, '').split('.')[0];\n"],"mappings":";;;;;;;;;;;;AAWA,MAAa,mBAAmB,gBAAoC,
|
|
1
|
+
{"version":3,"file":"versionSelector.mjs","names":[],"sources":["../src/versionSelector.ts"],"sourcesContent":["/**\n * This version selector is a bit complicated, so here is the flow:\n * 1. Use the clerkJSVersion prop on the provider\n * 2. Use the exact `@clerk/clerk-js` version if it is a `@snapshot` prerelease\n * 3. Use the prerelease tag of `@clerk/clerk-js` or the packageVersion provided\n * 4. Fallback to the major version of `@clerk/clerk-js` or the packageVersion provided\n *\n * @param clerkJSVersion - The optional clerkJSVersion prop on the provider\n * @param packageVersion - The version of `@clerk/clerk-js` that will be used if an explicit version is not provided\n * @returns The npm tag, version or major version to use\n */\nexport const versionSelector = (clerkJSVersion: string | undefined, packageVersion = JS_PACKAGE_VERSION) => {\n if (clerkJSVersion) {\n return clerkJSVersion;\n }\n\n const prereleaseTag = getPrereleaseTag(packageVersion);\n if (prereleaseTag) {\n if (prereleaseTag === 'snapshot') {\n return packageVersion;\n }\n\n return prereleaseTag;\n }\n\n return getMajorVersion(packageVersion);\n};\n\nconst getPrereleaseTag = (packageVersion: string) =>\n packageVersion\n .trim()\n .replace(/^v/, '')\n .match(/-(.+?)(\\.|$)/)?.[1];\n\nexport const getMajorVersion = (packageVersion: string) => packageVersion.trim().replace(/^v/, '').split('.')[0];\n"],"mappings":";;;;;;;;;;;;AAWA,MAAa,mBAAmB,gBAAoC,8BAAwC;CAC1G,IAAI,gBACF,OAAO;CAGT,MAAM,gBAAgB,iBAAiB,cAAc;CACrD,IAAI,eAAe;EACjB,IAAI,kBAAkB,YACpB,OAAO;EAGT,OAAO;CACT;CAEA,OAAO,gBAAgB,cAAc;AACvC;AAEA,MAAM,oBAAoB,mBACxB,eACG,KAAK,CAAC,CACN,QAAQ,MAAM,EAAE,CAAC,CACjB,MAAM,cAAc,CAAC,GAAG;AAE7B,MAAa,mBAAmB,mBAA2B,eAAe,KAAK,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC"}
|