@rhinestone/1auth 0.6.9 → 0.7.0

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/index.js CHANGED
@@ -344,25 +344,19 @@ var SPINNER_RADIUS = (SPINNER_SIZE - SPINNER_STROKE) / 2;
344
344
  var SPINNER_CIRCUMFERENCE = 2 * Math.PI * SPINNER_RADIUS;
345
345
  var SPINNER_ARC_FRACTION = 0.17;
346
346
  var SPINNER_ARC = SPINNER_CIRCUMFERENCE * SPINNER_ARC_FRACTION;
347
+ var SPINNER_SPIN_DURATION_MS = 800;
347
348
  var LOADING_MODAL_WIDTH = 380;
348
349
  var LOADING_MODAL_PADDING = 12;
349
350
  var LOADING_MODAL_RADIUS = 16;
350
- var LOADING_MOBILE_BREAKPOINT = 640;
351
- var LOADING_CARD_GAP = 16;
352
351
  var LOADING_BODY_GAP = 12;
353
352
  var LOADING_ICON_PADDING = 12;
354
353
  var LOADING_TITLE_BLOCK_GAP = 4;
355
- var LOADING_FOOTER_GAP = 4;
356
354
  var LOADING_TITLE_FONT_SIZE = 20;
357
355
  var LOADING_TITLE_FONT_WEIGHT = 700;
358
356
  var LOADING_SUBTITLE_FONT_SIZE = 12;
359
357
  var LOADING_SUBTITLE_FONT_WEIGHT = 500;
360
- var LOADING_FOOTER_FONT_SIZE = 11;
361
- var LOADING_FOOTER_FONT_WEIGHT = 500;
362
358
  var LOADING_LINE_HEIGHT = "normal";
363
359
  var LOADING_FONT_FAMILY = "'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'";
364
- var LOGO_WIDTH = 72;
365
- var LOGO_HEIGHT = 16;
366
360
  var BRAND_PURPLE = "#685bff";
367
361
  var LIGHT_TOKENS = {
368
362
  bgPrimary: "#ffffff",
@@ -376,7 +370,6 @@ var DARK_TOKENS = {
376
370
  textPrimary: "#e4e4e7",
377
371
  textSecondary: "#9f9fa9"
378
372
  };
379
- var FOOTER_TEXT_COLOR = "#71717b";
380
373
  var LOADING_TITLE = "Loading...";
381
374
  var LOADING_SUBTITLE = "This will only take a few moments, do not close the window.";
382
375
 
@@ -386,7 +379,36 @@ var POPUP_HEIGHT = 600;
386
379
  var DEFAULT_EMBED_WIDTH = `${LOADING_MODAL_WIDTH}px`;
387
380
  var DEFAULT_EMBED_HEIGHT = "500px";
388
381
  var DEFAULT_PROVIDER_URL = "https://passkey.1auth.box";
382
+ function currentWindowOrigin() {
383
+ if (typeof window === "undefined") return null;
384
+ const origin = window.location?.origin;
385
+ return typeof origin === "string" && origin.length > 0 ? origin : null;
386
+ }
387
+ function appendParentOriginParam(params) {
388
+ const origin = currentWindowOrigin();
389
+ if (origin) params.set("parentOrigin", origin);
390
+ }
389
391
  var MAX_LABEL_LEN = 20;
392
+ var DEFAULT_BACKDROP_COLOR = "#000000";
393
+ var DEFAULT_BACKDROP_OPACITY = 0.4;
394
+ var DEFAULT_BACKDROP_BLUR = 8;
395
+ var HEX_COLOR_RE = /^#[0-9a-fA-F]{6}$/;
396
+ function resolveBackdropStyle(params) {
397
+ const rawColor = params.get("backdropColor");
398
+ const hex = (rawColor && HEX_COLOR_RE.test(rawColor) ? rawColor : DEFAULT_BACKDROP_COLOR).slice(1);
399
+ const r = parseInt(hex.slice(0, 2), 16);
400
+ const g = parseInt(hex.slice(2, 4), 16);
401
+ const b = parseInt(hex.slice(4, 6), 16);
402
+ const clampNumber = (key, min, max, fallback) => {
403
+ const raw = params.get(key);
404
+ if (!raw) return fallback;
405
+ const n = Number(raw);
406
+ return Number.isFinite(n) ? Math.min(max, Math.max(min, n)) : fallback;
407
+ };
408
+ const opacity = clampNumber("backdropOpacity", 0, 1, DEFAULT_BACKDROP_OPACITY);
409
+ const blur = clampNumber("backdropBlur", 0, 40, DEFAULT_BACKDROP_BLUR);
410
+ return { background: `rgba(${r}, ${g}, ${b}, ${opacity})`, blur };
411
+ }
390
412
  function generateModalNonce() {
391
413
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
392
414
  return crypto.randomUUID();
@@ -510,6 +532,15 @@ var OneAuthClient = class {
510
532
  // "Restore will override" warnings and "emitting session_request:N
511
533
  // without any listeners" errors on the second and later txs.
512
534
  this.eoaDialogState = null;
535
+ // Hidden iframe that warms the dialog ahead of the first real open (see
536
+ // `prewarm()`). The passkey app runs on a long-lived server (not serverless),
537
+ // so there is no backend cold-start to hide — what stays expensive is the
538
+ // *browser-side* cost of a freshly created cross-origin iframe: downloading
539
+ // the dialog HTML + JS bundle, parsing it, hydrating React, and loading the
540
+ // font. Loading that bundle once into a parked hidden iframe lets the real
541
+ // open hit the HTTP cache (Next.js chunks/fonts are immutable-cached) over an
542
+ // already-warm connection, so the SDK preload overlay is shown only briefly.
543
+ this.prewarmState = null;
513
544
  // Serialises concurrent `requestWithWallet` calls — they would otherwise
514
545
  // both try to drive the same iframe and cross-resolve each other.
515
546
  this.eoaSerialQueue = Promise.resolve();
@@ -523,6 +554,15 @@ var OneAuthClient = class {
523
554
  if (dialogUrl !== providerUrl) {
524
555
  this.injectPreconnect(dialogUrl);
525
556
  }
557
+ if (this.config.prewarm) {
558
+ const warm = () => void this.prewarm();
559
+ const ric = window.requestIdleCallback;
560
+ if (typeof ric === "function") {
561
+ ric(warm, { timeout: 2e3 });
562
+ } else {
563
+ setTimeout(warm, 200);
564
+ }
565
+ }
526
566
  }
527
567
  const initOperation = this.createTelemetryOperation("client", {
528
568
  hasClientId: !!this.config.clientId,
@@ -756,6 +796,18 @@ var OneAuthClient = class {
756
796
  if (primary) {
757
797
  params.set("accent", primary);
758
798
  }
799
+ const backdrop = theme.backdrop;
800
+ if (backdrop) {
801
+ if (backdrop.color) {
802
+ params.set("backdropColor", backdrop.color);
803
+ }
804
+ if (backdrop.opacity !== void 0) {
805
+ params.set("backdropOpacity", String(backdrop.opacity));
806
+ }
807
+ if (backdrop.blur !== void 0) {
808
+ params.set("backdropBlur", String(backdrop.blur));
809
+ }
810
+ }
759
811
  return params.toString();
760
812
  }
761
813
  /**
@@ -994,9 +1046,7 @@ var OneAuthClient = class {
994
1046
  if (options?.flow === "create-account") {
995
1047
  params.set("switch", "1");
996
1048
  }
997
- if (typeof window !== "undefined" && window.location?.origin) {
998
- params.set("parentOrigin", window.location.origin);
999
- }
1049
+ appendParentOriginParam(params);
1000
1050
  this.appendTelemetryParams(params, telemetry);
1001
1051
  const themeParams = this.getThemeParams(options?.theme);
1002
1052
  if (themeParams) {
@@ -1077,9 +1127,7 @@ var OneAuthClient = class {
1077
1127
  if (this.config.clientId) {
1078
1128
  params.set("clientId", this.config.clientId);
1079
1129
  }
1080
- if (typeof window !== "undefined" && window.location?.origin) {
1081
- params.set("parentOrigin", window.location.origin);
1082
- }
1130
+ appendParentOriginParam(params);
1083
1131
  this.appendTelemetryParams(params, telemetry);
1084
1132
  const themeParams = this.getThemeParams(options?.theme);
1085
1133
  if (themeParams) {
@@ -1210,10 +1258,13 @@ var OneAuthClient = class {
1210
1258
  themeParsed.forEach((value, key) => params.set(key, value));
1211
1259
  }
1212
1260
  const url = `${dialogUrl}/dialog/account?${params.toString()}`;
1261
+ const accessTokenResult = await this.fetchAccessToken();
1262
+ const initMessage = { mode: "iframe" };
1263
+ if (accessTokenResult.ok) {
1264
+ initMessage.auth = { accessToken: accessTokenResult.accessToken };
1265
+ }
1213
1266
  const { dialog, iframe, cleanup } = this.createModalDialog(url);
1214
- const ready = await this.waitForDialogReady(dialog, iframe, cleanup, {
1215
- mode: "iframe"
1216
- });
1267
+ const ready = await this.waitForDialogReady(dialog, iframe, cleanup, initMessage);
1217
1268
  if (!ready) return;
1218
1269
  const dialogOrigin = this.getDialogOrigin();
1219
1270
  return new Promise((resolve) => {
@@ -1398,6 +1449,7 @@ var OneAuthClient = class {
1398
1449
  }
1399
1450
  const dialogUrl = this.getDialogUrl();
1400
1451
  const params = new URLSearchParams({ mode: "iframe" });
1452
+ appendParentOriginParam(params);
1401
1453
  this.appendTelemetryParams(params, telemetry);
1402
1454
  const themeParams = this.getThemeParams(options.theme);
1403
1455
  if (themeParams) {
@@ -1444,6 +1496,7 @@ var OneAuthClient = class {
1444
1496
  targetChains,
1445
1497
  status: result.status ? String(result.status) : void 0
1446
1498
  });
1499
+ await this.waitForDialogClose(dialog, cleanup);
1447
1500
  } else {
1448
1501
  this.emitTelemetry(
1449
1502
  result.error?.code === "USER_CANCELLED" ? "dialog.cancelled" : "grant_permission.failed",
@@ -1625,6 +1678,7 @@ var OneAuthClient = class {
1625
1678
  });
1626
1679
  const dialogUrl = this.getDialogUrl();
1627
1680
  const params = new URLSearchParams({ mode: "iframe" });
1681
+ appendParentOriginParam(params);
1628
1682
  this.appendTelemetryParams(params, telemetry);
1629
1683
  const themeParams = this.getThemeParams(options?.theme);
1630
1684
  if (themeParams) {
@@ -1795,6 +1849,7 @@ var OneAuthClient = class {
1795
1849
  let prepareResponse;
1796
1850
  const dialogUrl = this.getDialogUrl();
1797
1851
  const params = new URLSearchParams({ mode: "iframe" });
1852
+ appendParentOriginParam(params);
1798
1853
  this.appendTelemetryParams(params, telemetry);
1799
1854
  const themeParams = this.getThemeParams();
1800
1855
  if (themeParams) {
@@ -2071,6 +2126,10 @@ var OneAuthClient = class {
2071
2126
  expiresAt: refreshedData.expiresAt,
2072
2127
  intentOp: refreshedData.intentOp,
2073
2128
  digestResult: refreshedData.digestResult,
2129
+ // A refreshed quote mints a fresh binding — it MUST replace the old
2130
+ // one, or execute would verify the new intentOp against a stale
2131
+ // binding and reject (once enforce mode is on).
2132
+ binding: refreshedData.binding,
2074
2133
  auth: refreshedAuth
2075
2134
  };
2076
2135
  return {
@@ -2080,6 +2139,7 @@ var OneAuthClient = class {
2080
2139
  originMessages: refreshedData.originMessages,
2081
2140
  transaction: refreshedData.transaction,
2082
2141
  digestResult: refreshedData.digestResult,
2142
+ binding: refreshedData.binding,
2083
2143
  // Dialog-executed intents read auth from dialog state — ship the
2084
2144
  // rebound bundle with the refresh so PASSKEY_REFRESH_COMPLETE
2085
2145
  // updates it alongside the quote.
@@ -2144,6 +2204,7 @@ var OneAuthClient = class {
2144
2204
  userId: prepareResponse.userId,
2145
2205
  intentOp: prepareResponse.intentOp,
2146
2206
  digestResult: prepareResponse.digestResult,
2207
+ binding: prepareResponse.binding,
2147
2208
  tier: prepareResult.tier,
2148
2209
  auth: sponsorshipTokens,
2149
2210
  telemetry: this.telemetryPayload(telemetry)
@@ -2274,6 +2335,7 @@ var OneAuthClient = class {
2274
2335
  userId: prepareResponse.userId,
2275
2336
  intentOp: prepareResponse.intentOp,
2276
2337
  digestResult: prepareResponse.digestResult,
2338
+ binding: prepareResponse.binding,
2277
2339
  tier: prepareResult.tier,
2278
2340
  ...authReady && { auth: authReady },
2279
2341
  telemetry: this.telemetryPayload(telemetry)
@@ -2447,6 +2509,8 @@ var OneAuthClient = class {
2447
2509
  calls: prepareResponse.calls,
2448
2510
  expiresAt: prepareResponse.expiresAt,
2449
2511
  digestResult: prepareResponse.digestResult,
2512
+ // Opaque prepare->execute binding, forwarded unchanged.
2513
+ binding: prepareResponse.binding,
2450
2514
  // Signature from dialog
2451
2515
  signature: signingResult.signature,
2452
2516
  passkey: signingResult.passkey,
@@ -2772,6 +2836,7 @@ var OneAuthClient = class {
2772
2836
  };
2773
2837
  const dialogUrl = this.getDialogUrl();
2774
2838
  const params = new URLSearchParams({ mode: "iframe" });
2839
+ appendParentOriginParam(params);
2775
2840
  this.appendTelemetryParams(params, telemetry);
2776
2841
  const themeParams = this.getThemeParams();
2777
2842
  if (themeParams) {
@@ -2888,6 +2953,7 @@ var OneAuthClient = class {
2888
2953
  batchIntents: prepareResponse.intents,
2889
2954
  batchFailedIntents: prepareResponse.failedIntents,
2890
2955
  challenge: prepareResponse.challenge,
2956
+ binding: prepareResponse.binding,
2891
2957
  username: options.username,
2892
2958
  accountAddress: prepareResponse.accountAddress,
2893
2959
  userId: prepareResponse.userId,
@@ -2931,6 +2997,7 @@ var OneAuthClient = class {
2931
2997
  batchIntents: prepareResponse.intents,
2932
2998
  batchFailedIntents: prepareResponse.failedIntents,
2933
2999
  challenge: prepareResponse.challenge,
3000
+ binding: prepareResponse.binding,
2934
3001
  username: options.username,
2935
3002
  accountAddress: prepareResponse.accountAddress,
2936
3003
  userId: prepareResponse.userId,
@@ -2998,6 +3065,7 @@ var OneAuthClient = class {
2998
3065
  batchIntents: refreshed.intents,
2999
3066
  batchFailedIntents: refreshed.failedIntents,
3000
3067
  challenge: refreshed.challenge,
3068
+ binding: refreshed.binding,
3001
3069
  expiresAt: refreshed.expiresAt,
3002
3070
  auth: refreshedAuth
3003
3071
  };
@@ -3005,6 +3073,7 @@ var OneAuthClient = class {
3005
3073
  type: "PASSKEY_REFRESH_COMPLETE",
3006
3074
  batchIntents: refreshed.intents,
3007
3075
  challenge: refreshed.challenge,
3076
+ binding: refreshed.binding,
3008
3077
  expiresAt: refreshed.expiresAt,
3009
3078
  auth: refreshedAuth
3010
3079
  }, dialogOrigin);
@@ -3433,6 +3502,82 @@ var OneAuthClient = class {
3433
3502
  } catch {
3434
3503
  }
3435
3504
  }
3505
+ /**
3506
+ * Warm the dialog ahead of the first user interaction.
3507
+ *
3508
+ * `preconnect` (run in the constructor) only warms DNS/TLS. This goes
3509
+ * further: it loads the dialog into a hidden, off-screen iframe so the
3510
+ * browser downloads and parses the dialog HTML + JS bundle and the font, and
3511
+ * keeps the cross-origin connection alive. When the user later opens a real
3512
+ * auth/sign dialog, the fresh iframe serves those bytes from cache over the
3513
+ * warm connection and paints far sooner — so the SDK preload overlay is shown
3514
+ * only briefly (or imperceptibly) instead of covering a full cold load.
3515
+ *
3516
+ * It loads a dedicated `/dialog/warm` route, NOT a real flow route. That
3517
+ * matters for two reasons:
3518
+ * - **No side effects.** The real `/dialog/auth` route runs the signup
3519
+ * flow on mount (identity probe → `POST /api/auth/register?step=start`),
3520
+ * which writes an `AuthChallenge` row and counts against the register
3521
+ * rate limiter. Warming must never create backend auth state for a user
3522
+ * who hasn't acted, so the warm route renders nothing and runs no flow.
3523
+ * - **No message cross-talk.** The warm route posts no `PASSKEY_READY` /
3524
+ * `PASSKEY_RENDERED`, so a slow warm can never satisfy a visible modal's
3525
+ * readiness listener and drive it to `PASSKEY_INIT` at the wrong moment.
3526
+ * The warm route still pulls the shared Next.js framework + main + dialog
3527
+ * layout chunks (the bulk of every flow's bundle), so the real open is fast.
3528
+ *
3529
+ * Best called on a *likely-intent* signal — `pointerenter`/`focus` of your
3530
+ * "Sign in" / "Pay" button — rather than on page load, so visitors who never
3531
+ * authenticate don't pay for a cross-origin iframe. Pass `prewarm: true` in
3532
+ * the client config to have the SDK schedule this once on an idle callback.
3533
+ *
3534
+ * Idempotent: repeated calls return the same in-flight/settled promise.
3535
+ * Resolves `true` once the hidden iframe's document has loaded, `false` on
3536
+ * timeout or failure. Never throws and never blocks a real dialog open — a
3537
+ * failed prewarm just means the next open does a normal (cold) load.
3538
+ *
3539
+ * @returns Whether the dialog became warm.
3540
+ */
3541
+ async prewarm() {
3542
+ if (typeof document === "undefined") return false;
3543
+ if (this.prewarmState) return this.prewarmState.ready;
3544
+ const warmUrl = `${this.getDialogUrl()}/dialog/warm`;
3545
+ const iframe = document.createElement("iframe");
3546
+ iframe.setAttribute("aria-hidden", "true");
3547
+ iframe.setAttribute("tabindex", "-1");
3548
+ iframe.title = "1auth dialog prewarm";
3549
+ iframe.style.cssText = "position:fixed;left:-9999px;top:0;width:1px;height:1px;opacity:0;border:0;pointer-events:none;";
3550
+ const ready = new Promise((resolve) => {
3551
+ let settled = false;
3552
+ const finish = (value) => {
3553
+ if (settled) return;
3554
+ settled = true;
3555
+ clearTimeout(timer);
3556
+ resolve(value);
3557
+ };
3558
+ iframe.onload = () => finish(true);
3559
+ iframe.onerror = () => finish(false);
3560
+ const timer = setTimeout(() => finish(false), 1e4);
3561
+ });
3562
+ iframe.src = warmUrl;
3563
+ document.body.appendChild(iframe);
3564
+ this.prewarmState = { iframe, ready };
3565
+ return ready;
3566
+ }
3567
+ /**
3568
+ * Tear down the hidden prewarm iframe created by {@link prewarm}.
3569
+ *
3570
+ * The HTTP cache that prewarm populated survives teardown, so a subsequent
3571
+ * open is still bundle-warm; this only releases the parked frame (and its
3572
+ * keep-alive connection). Call it when auth is no longer likely on the
3573
+ * current view. Safe to call when nothing is warmed.
3574
+ */
3575
+ destroyPrewarm() {
3576
+ const state = this.prewarmState;
3577
+ if (!state) return;
3578
+ this.prewarmState = null;
3579
+ state.iframe.remove();
3580
+ }
3436
3581
  /**
3437
3582
  * Wait for the dialog iframe to signal ready, but defer sending `PASSKEY_INIT`
3438
3583
  * until the caller decides the time is right.
@@ -3815,10 +3960,15 @@ var OneAuthClient = class {
3815
3960
  return { ok: true, dialog: dialog2, iframe: iframe2 };
3816
3961
  }
3817
3962
  const dialogUrl = this.getDialogUrl();
3963
+ const params = new URLSearchParams({ mode: "iframe" });
3964
+ appendParentOriginParam(params);
3818
3965
  const themeParams = this.getThemeParams(theme);
3819
- const parentOrigin = typeof window !== "undefined" ? window.location.origin : "";
3820
- const parentOriginParam = parentOrigin ? `&parentOrigin=${encodeURIComponent(parentOrigin)}` : "";
3821
- const signingUrl = `${dialogUrl}/dialog/sign-eoa?mode=iframe${parentOriginParam}${themeParams ? `&${themeParams}` : ""}`;
3966
+ if (themeParams) {
3967
+ new URLSearchParams(themeParams).forEach((value, key) => {
3968
+ params.set(key, value);
3969
+ });
3970
+ }
3971
+ const signingUrl = `${dialogUrl}/dialog/sign-eoa?${params.toString()}`;
3822
3972
  const { dialog, iframe, destroy } = this.createModalDialog(signingUrl, {
3823
3973
  persistent: true
3824
3974
  });
@@ -3923,6 +4073,7 @@ var OneAuthClient = class {
3923
4073
  });
3924
4074
  const dialogUrl = this.getDialogUrl();
3925
4075
  const params = new URLSearchParams({ mode: "iframe" });
4076
+ appendParentOriginParam(params);
3926
4077
  this.appendTelemetryParams(params, telemetry);
3927
4078
  const themeParams = this.getThemeParams(options?.theme);
3928
4079
  if (themeParams) {
@@ -4065,6 +4216,7 @@ var OneAuthClient = class {
4065
4216
  });
4066
4217
  const dialogUrl = this.getDialogUrl();
4067
4218
  const params = new URLSearchParams({ mode: "iframe" });
4219
+ appendParentOriginParam(params);
4068
4220
  this.appendTelemetryParams(params, telemetry);
4069
4221
  const themeParams = this.getThemeParams(options?.theme);
4070
4222
  if (themeParams) {
@@ -4518,17 +4670,17 @@ var OneAuthClient = class {
4518
4670
  /**
4519
4671
  * Create and open a full-viewport `<dialog>` containing a passkey iframe.
4520
4672
  *
4521
- * The SDK owns only the hidden outer shell; all visible UI (backdrop,
4522
- * card, animations, close button) is rendered by the passkey app inside the
4523
- * iframe. This approach means design changes can be shipped server-side
4524
- * without updating SDK consumers.
4673
+ * The SDK owns the browser `<dialog>` plus a minimal generic preload shell
4674
+ * that appears immediately on click. The passkey iframe owns all branded and
4675
+ * origin-specific UI (TitleBar, trust icon, action cards); the shell is
4676
+ * removed as soon as the iframe reports that its centered card has painted.
4525
4677
  *
4526
4678
  * Lifecycle:
4527
- * 1. A themed "Loading…" overlay is injected and shown immediately while the
4528
- * iframe loads, matching the exact visual structure of the passkey app's
4529
- * TitleBar + IndeterminateLoader so the transition is seamless.
4530
- * 2. When the iframe sends `PASSKEY_RENDERED`, the overlay fades out and
4531
- * the iframe becomes fully visible.
4679
+ * 1. A themed generic preload shell is injected and shown immediately
4680
+ * while the iframe loads. It intentionally contains no TitleBar or
4681
+ * origin chrome so that UI has a single implementation in apps/passkey.
4682
+ * 2. When the iframe sends `PASSKEY_RENDERED`, the overlay is removed and
4683
+ * the iframe becomes fully visible in the same frame.
4532
4684
  * 3. The returned `cleanup` function tears down all event listeners,
4533
4685
  * disconnects the MutationObserver, closes the `<dialog>`, and removes
4534
4686
  * it from the DOM. It is idempotent — safe to call multiple times.
@@ -4550,12 +4702,10 @@ var OneAuthClient = class {
4550
4702
  const dialogUrl = this.getDialogUrl();
4551
4703
  const hostUrl = new URL(dialogUrl);
4552
4704
  const urlParams = new URL(url, window.location.href).searchParams;
4705
+ const { background: backdropBackground, blur: backdropBlur } = resolveBackdropStyle(urlParams);
4553
4706
  const themeMode = urlParams.get("theme") || "light";
4554
4707
  const isDark = themeMode === "dark" || themeMode !== "light" && window.matchMedia("(prefers-color-scheme: dark)").matches;
4555
4708
  const theme = isDark ? DARK_TOKENS : LIGHT_TOKENS;
4556
- const { bgPrimary, borderColor, textPrimary, textSecondary } = theme;
4557
- const textFooter = FOOTER_TEXT_COLOR;
4558
- const spinnerColor = BRAND_PURPLE;
4559
4709
  const dialog = document.createElement("dialog");
4560
4710
  dialog.dataset.passkey = "";
4561
4711
  if (options?.hidden) {
@@ -4566,16 +4716,6 @@ var OneAuthClient = class {
4566
4716
  document.body.appendChild(dialog);
4567
4717
  const style = document.createElement("style");
4568
4718
  style.textContent = `
4569
- /* Load Inter so the overlay's title/subtitle widths match the
4570
- iframe (which loads Inter via next/font). Without this, host
4571
- pages on Arial / system-ui produce wider text that wraps to two
4572
- lines and breaks the visual match with the Figma. font-display:
4573
- swap renders immediately with a fallback and swaps when Inter
4574
- is ready \u2014 usually well before PASSKEY_RENDERED fires, but the
4575
- overlay is short-lived either way so a single-frame swap is
4576
- acceptable. */
4577
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@500;700&display=swap');
4578
-
4579
4719
  dialog[data-passkey] {
4580
4720
  position: fixed;
4581
4721
  top: 0;
@@ -4613,103 +4753,143 @@ var OneAuthClient = class {
4613
4753
  backdrop-filter: none;
4614
4754
  -webkit-backdrop-filter: none;
4615
4755
  }
4616
- dialog[data-passkey] iframe {
4617
- position: fixed;
4618
- top: 0;
4619
- left: 0;
4620
- width: 100%;
4621
- height: 100%;
4622
- border: 0;
4623
- background-color: transparent;
4624
- color-scheme: normal;
4625
- pointer-events: auto;
4626
- backdrop-filter: blur(8px);
4627
- -webkit-backdrop-filter: blur(8px);
4628
- }
4629
4756
  dialog[data-passkey] [data-passkey-overlay] {
4630
4757
  position: fixed;
4631
- top: 0;
4632
- left: 0;
4633
- width: 100%;
4634
- height: 100%;
4758
+ inset: 0;
4635
4759
  display: flex;
4636
4760
  align-items: center;
4637
4761
  justify-content: center;
4638
- background: rgba(0, 0, 0, 0.4);
4639
- backdrop-filter: blur(8px);
4640
- -webkit-backdrop-filter: blur(8px);
4762
+ background: ${backdropBackground};
4763
+ backdrop-filter: blur(${backdropBlur}px);
4764
+ -webkit-backdrop-filter: blur(${backdropBlur}px);
4641
4765
  z-index: 1;
4642
- /* Purely a visual loading screen \u2014 never intercept clicks.
4643
- If the iframe's X button or backdrop-dismiss area happens to
4644
- be reachable while the overlay is still painting (slow CI
4645
- runners, redirect remounts), the click goes straight through
4646
- instead of being trapped by the SDK preload. The previous
4647
- rAF-deferred display:none lost this race in GitHub Actions
4648
- \u2014 dialog-close.spec.ts repeatedly hit
4649
- "<div data-passkey-overlay> intercepts pointer events". */
4650
4766
  pointer-events: none;
4651
- animation: _1auth-backdrop-in 0.2s ease-out;
4652
4767
  }
4653
4768
  dialog[data-passkey][data-passkey-hidden] [data-passkey-overlay] {
4654
4769
  display: none;
4655
4770
  }
4656
- dialog[data-passkey] [data-passkey-card] {
4771
+ dialog[data-passkey] [data-passkey-preload-card] {
4657
4772
  width: ${LOADING_MODAL_WIDTH}px;
4773
+ max-width: 100%;
4774
+ overflow: hidden;
4775
+ border-radius: ${LOADING_MODAL_RADIUS}px;
4776
+ background: ${theme.bgPrimary};
4777
+ box-shadow: 0 24px 80px rgba(0, 0, 0, 0.24), 0 2px 12px rgba(0, 0, 0, 0.12);
4778
+ font-family: ${LOADING_FONT_FAMILY};
4779
+ line-height: ${LOADING_LINE_HEIGHT};
4780
+ -webkit-font-smoothing: antialiased;
4781
+ -moz-osx-font-smoothing: grayscale;
4782
+ }
4783
+ dialog[data-passkey] [data-passkey-preload-body] {
4658
4784
  box-sizing: border-box;
4785
+ display: flex;
4786
+ width: 100%;
4787
+ flex-direction: column;
4788
+ align-items: center;
4789
+ gap: ${LOADING_BODY_GAP}px;
4659
4790
  padding: ${LOADING_MODAL_PADDING}px;
4660
4791
  border-radius: ${LOADING_MODAL_RADIUS}px;
4661
- box-shadow: inset 0 0 0 1px ${borderColor};
4662
- animation: _1auth-card-in 0.2s cubic-bezier(0.32, 0.72, 0, 1);
4663
- max-height: 100dvh;
4664
- overflow-y: auto;
4792
+ background: ${theme.bgPrimary};
4793
+ }
4794
+ dialog[data-passkey] [data-passkey-preload-spinner-wrap] {
4795
+ display: flex;
4796
+ align-items: center;
4797
+ padding: ${LOADING_ICON_PADDING}px;
4798
+ }
4799
+ dialog[data-passkey] [data-passkey-preload-spinner] {
4800
+ position: relative;
4801
+ width: ${SPINNER_SIZE}px;
4802
+ height: ${SPINNER_SIZE}px;
4803
+ }
4804
+ dialog[data-passkey] [data-passkey-preload-spinner] svg {
4805
+ position: absolute;
4806
+ inset: 0;
4807
+ width: 100%;
4808
+ height: 100%;
4809
+ color: ${BRAND_PURPLE};
4810
+ animation: _1auth-spin ${SPINNER_SPIN_DURATION_MS}ms linear infinite;
4811
+ }
4812
+ dialog[data-passkey] [data-passkey-preload-copy] {
4665
4813
  display: flex;
4814
+ width: 100%;
4666
4815
  flex-direction: column;
4667
- gap: ${LOADING_CARD_GAP}px;
4668
- font-family: ${LOADING_FONT_FAMILY};
4669
- /* line-height: normal must be set on the card so the host page's
4670
- Tailwind preflight (line-height: 1.5 on <body>) doesn't bleed
4671
- into our card via inheritance \u2014 that's what made the SDK preload
4672
- render taller than the iframe screen. */
4816
+ align-items: center;
4817
+ gap: ${LOADING_TITLE_BLOCK_GAP}px;
4818
+ text-align: center;
4819
+ }
4820
+ dialog[data-passkey] [data-passkey-preload-title] {
4821
+ margin: 0;
4822
+ color: ${theme.textPrimary};
4823
+ font-size: ${LOADING_TITLE_FONT_SIZE}px;
4824
+ font-weight: ${LOADING_TITLE_FONT_WEIGHT};
4673
4825
  line-height: ${LOADING_LINE_HEIGHT};
4674
- -webkit-font-smoothing: antialiased;
4675
- -moz-osx-font-smoothing: grayscale;
4676
4826
  }
4677
- @media (max-width: ${LOADING_MOBILE_BREAKPOINT - 1}px) {
4827
+ dialog[data-passkey] [data-passkey-preload-subtitle] {
4828
+ margin: 0;
4829
+ color: ${theme.textSecondary};
4830
+ font-size: ${LOADING_SUBTITLE_FONT_SIZE}px;
4831
+ font-weight: ${LOADING_SUBTITLE_FONT_WEIGHT};
4832
+ line-height: ${LOADING_LINE_HEIGHT};
4833
+ }
4834
+ @media (max-width: 639px) {
4678
4835
  dialog[data-passkey] [data-passkey-overlay] {
4679
4836
  align-items: flex-end;
4680
4837
  }
4681
- dialog[data-passkey] [data-passkey-card] {
4838
+ dialog[data-passkey] [data-passkey-preload-card] {
4682
4839
  width: 100%;
4683
- border-bottom-left-radius: 0;
4684
4840
  border-bottom-right-radius: 0;
4841
+ border-bottom-left-radius: 0;
4842
+ }
4843
+ dialog[data-passkey] [data-passkey-preload-body] {
4685
4844
  padding-bottom: calc(${LOADING_MODAL_PADDING}px + env(safe-area-inset-bottom));
4686
- animation: _1auth-card-slide 0.3s cubic-bezier(0.32, 0.72, 0, 1);
4687
4845
  }
4688
4846
  }
4689
- @keyframes _1auth-backdrop-in {
4690
- from { opacity: 0; } to { opacity: 1; }
4691
- }
4692
- @keyframes _1auth-card-in {
4693
- from { opacity: 0; transform: scale(0.96) translateY(8px); }
4694
- to { opacity: 1; transform: scale(1) translateY(0); }
4695
- }
4696
- @keyframes _1auth-card-slide {
4697
- from { transform: translate3d(0, 100%, 0); }
4698
- to { transform: translate3d(0, 0, 0); }
4699
- }
4700
4847
  @keyframes _1auth-spin {
4701
4848
  from { transform: rotate(0deg); }
4702
4849
  to { transform: rotate(360deg); }
4703
4850
  }
4851
+ dialog[data-passkey] iframe {
4852
+ position: fixed;
4853
+ top: 0;
4854
+ left: 0;
4855
+ width: 100%;
4856
+ height: 100%;
4857
+ border: 0;
4858
+ background-color: transparent;
4859
+ color-scheme: normal;
4860
+ pointer-events: auto;
4861
+ backdrop-filter: blur(${backdropBlur}px);
4862
+ -webkit-backdrop-filter: blur(${backdropBlur}px);
4863
+ transition: none;
4864
+ }
4704
4865
  `;
4705
4866
  dialog.appendChild(style);
4706
4867
  const overlay = document.createElement("div");
4707
4868
  overlay.dataset.passkeyOverlay = "";
4869
+ const preloadCard = document.createElement("div");
4870
+ preloadCard.dataset.passkeyPreloadCard = "";
4871
+ const preloadBody = document.createElement("div");
4872
+ preloadBody.dataset.passkeyPreloadBody = "";
4873
+ const spinnerWrap = document.createElement("div");
4874
+ spinnerWrap.dataset.passkeyPreloadSpinnerWrap = "";
4875
+ const spinner = document.createElement("div");
4876
+ spinner.dataset.passkeyPreloadSpinner = "";
4708
4877
  const spinnerCenter = SPINNER_SIZE / 2;
4709
4878
  const spinnerGap = SPINNER_CIRCUMFERENCE - SPINNER_ARC;
4710
- const spinnerSvg = `<svg style="position:absolute;inset:0;width:100%;height:100%;animation:_1auth-spin 0.8s linear infinite;color:${spinnerColor}" viewBox="0 0 ${SPINNER_SIZE} ${SPINNER_SIZE}" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="${spinnerCenter}" cy="${spinnerCenter}" r="${SPINNER_RADIUS}" stroke="currentColor" opacity="0.3" stroke-width="${SPINNER_STROKE}"/><circle cx="${spinnerCenter}" cy="${spinnerCenter}" r="${SPINNER_RADIUS}" stroke="currentColor" stroke-width="${SPINNER_STROKE}" stroke-linecap="round" stroke-dasharray="${SPINNER_ARC} ${spinnerGap}" transform="rotate(-90 ${spinnerCenter} ${spinnerCenter})"/></svg>`;
4711
- const rhinestoneLogo = `<svg width="${LOGO_WIDTH}" height="${LOGO_HEIGHT}" viewBox="0 0 72 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M10.4767 14.815C9.80706 15.3364 9.0433 15.7628 8.08753 15.8207C7.91561 15.8312 7.74321 15.8312 7.5713 15.8207C6.616 15.7628 5.85177 15.336 5.18212 14.815C4.56047 14.3303 3.87765 13.648 3.10071 12.871L2.9713 12.7416C2.19483 11.9647 1.51153 11.2818 1.02777 10.6602C0.505414 9.99055 0.0790608 9.22679 0.0211784 8.27102C0.0106814 8.0991 0.0106814 7.9267 0.0211784 7.75479C0.0790608 6.79949 0.505884 6.03526 1.02683 5.36561C1.51153 4.74349 2.19388 4.06114 2.97083 3.2842L3.03577 3.21926L3.10024 3.15479C3.87718 2.37784 4.56 1.69502 5.18165 1.21079C5.85177 0.689374 6.61647 0.26255 7.57177 0.205138C7.74368 0.194641 7.91608 0.194641 8.088 0.205138C9.04377 0.26255 9.80753 0.689374 10.4772 1.21079C11.0993 1.69502 11.7816 2.37784 12.5586 3.15431L12.6885 3.2842C13.4649 4.06067 14.1478 4.74349 14.632 5.36561C15.1534 6.03526 15.5802 6.79902 15.6381 7.75479C15.6485 7.92671 15.6485 8.09879 15.6381 8.27102C15.5802 9.22632 15.1534 9.99055 14.632 10.6602C14.1478 11.2818 13.4649 11.9647 12.6885 12.7416L12.6235 12.8061L12.5586 12.871C11.7821 13.6475 11.0993 14.3308 10.4772 14.8146M1.58306 8.17643C1.64518 9.20326 2.47718 10.0353 4.14212 11.7002C5.80706 13.3647 6.64 14.1971 7.66588 14.2588C7.77506 14.2654 7.88424 14.2654 7.99341 14.2588C9.0193 14.1967 9.85224 13.3647 11.5167 11.6997C13.1812 10.0348 14.0136 9.20279 14.0758 8.17596C14.0821 8.06688 14.0821 7.95752 14.0758 7.84843C14.0136 6.82255 13.1816 5.98961 11.5167 4.32514C9.85177 2.66067 9.01977 1.8282 7.99341 1.76608C7.88433 1.75974 7.77497 1.75974 7.66588 1.76608C6.63953 1.8282 5.80706 2.6602 4.14212 4.32514C2.47765 5.98961 1.64518 6.82208 1.58306 7.84843C1.57672 7.95752 1.57672 8.06735 1.58306 8.17643Z" fill="${textFooter}"/><path d="M3.65796 8.01173C3.65796 7.84138 3.7789 7.69549 3.94455 7.65597C4.78502 7.45502 5.44714 7.1402 6.0029 6.64797C6.16666 6.50271 6.32102 6.34836 6.46596 6.18491C6.95819 5.62914 7.27302 4.96702 7.47396 4.12655C7.49245 4.04577 7.53761 3.97356 7.60215 3.92157C7.66669 3.86958 7.74685 3.84084 7.82972 3.83997C8.00008 3.83997 8.14596 3.96044 8.18549 4.12655C8.3869 4.96702 8.70125 5.62914 9.19349 6.18491C9.33874 6.34836 9.4931 6.50271 9.65655 6.64797C10.2123 7.1402 10.8744 7.45502 11.7154 7.65597C11.7961 7.67455 11.8682 7.71975 11.9201 7.78428C11.972 7.84881 12.0006 7.92892 12.0015 8.01173C12.0006 8.09454 11.972 8.17465 11.9201 8.23918C11.8682 8.30371 11.7961 8.34891 11.7154 8.3675C10.8744 8.56844 10.2128 8.88326 9.65655 9.37549C9.4931 9.52075 9.33874 9.6751 9.19349 9.83855C8.70125 10.3943 8.38643 11.0564 8.18549 11.8969C8.16699 11.9777 8.12184 12.0499 8.0573 12.1019C7.99276 12.1539 7.91259 12.1826 7.82972 12.1835C7.74685 12.1826 7.66669 12.1539 7.60215 12.1019C7.53761 12.0499 7.49245 11.9777 7.47396 11.8969C7.27302 11.0564 6.95819 10.3943 6.46596 9.83855C6.32069 9.67537 6.16608 9.52076 6.0029 9.37549C5.44714 8.88326 4.78502 8.56844 3.94455 8.3675C3.86376 8.349 3.79156 8.30384 3.73957 8.23931C3.68758 8.17477 3.65883 8.0946 3.65796 8.01173Z" fill="${textFooter}"/><path d="M30.1186 4.56946C30.1186 5.08193 30.5031 5.44476 31.08 5.44476C31.6569 5.44476 32.0306 5.08193 32.0306 4.56946C32.0306 4.04711 31.6569 3.70593 31.08 3.70593C30.5031 3.70593 30.1186 4.04711 30.1186 4.56993M30.3106 6.00946V11.3422H31.8494V6.00946H30.3106ZM25.9115 11.3422H24.3732V3.87676H25.9115V6.86311C26.1572 6.35111 26.7129 5.84946 27.5035 5.84946C28.7958 5.84946 29.3835 6.68146 29.3835 8.13182V11.3422H27.8447V8.30264C27.8447 7.53464 27.5031 7.14029 26.9158 7.14029C26.2532 7.14029 25.9115 7.67346 25.9115 8.44146V11.3422ZM21.4682 11.3422H19.9304V6.00946H21.4687L21.1977 7.23299C21.1939 7.25002 21.1941 7.26767 21.1981 7.28464C21.202 7.30161 21.2098 7.31747 21.2207 7.33106C21.2316 7.34465 21.2455 7.35561 21.2612 7.36315C21.2769 7.3707 21.2941 7.37462 21.3115 7.37464C21.3657 7.37464 21.4127 7.33699 21.4268 7.28429C21.656 6.44193 22.3482 5.92429 23.0602 5.92429C23.3379 5.92429 23.5087 5.94546 23.6264 5.96664V7.38546C23.4061 7.36702 23.1853 7.35635 22.9642 7.35346C22.2913 7.35346 21.4687 7.66264 21.4687 9.34782L21.4682 11.3422ZM34.4626 11.3422H32.9242V6.00946H34.4631L34.4414 6.90546C34.6871 6.38311 35.264 5.84946 36.0546 5.84946C37.3473 5.84946 37.9346 6.68146 37.9346 8.13182V11.3422H36.3962V8.30264C36.3962 7.53464 36.0546 7.14029 35.4668 7.14029C34.8047 7.14029 34.4626 7.67346 34.4626 8.44146V11.3422Z" fill="${textFooter}"/><path fill-rule="evenodd" clip-rule="evenodd" d="M38.7355 8.68664C38.7355 10.3186 39.7506 11.513 41.4489 11.513C42.9548 11.513 43.7567 10.6918 43.9915 9.78499L42.56 9.51864C42.464 9.89182 42.0899 10.3398 41.4381 10.3398C40.7652 10.3398 40.2099 9.74217 40.1991 9.02782H44.0024C44.0344 8.89982 44.0555 8.71864 44.0555 8.47346C44.0555 6.92711 42.9016 5.83911 41.4598 5.83911C39.9534 5.83911 38.7355 7.04382 38.7355 8.68664ZM42.56 8.11064H40.2527C40.267 7.80585 40.3982 7.51829 40.6191 7.30777C40.8399 7.09724 41.1335 6.97995 41.4386 6.98029C42.0362 6.98029 42.5812 7.42829 42.56 8.11064Z" fill="${textFooter}"/><path d="M47.1016 11.513C45.8946 11.513 44.7732 11.097 44.5487 9.85982L45.9802 9.64664C46.1511 10.201 46.5355 10.4358 47.0805 10.4358C47.5826 10.4358 47.8494 10.201 47.8494 9.89182C47.8494 9.62499 47.6466 9.44382 47.1016 9.33746L46.5995 9.23064C45.5101 9.00664 44.8264 8.50546 44.8264 7.62029C44.8264 6.56429 45.7449 5.83911 47.0805 5.83911C48.3412 5.83911 49.2706 6.42546 49.3986 7.52382L47.9887 7.73746C47.9242 7.21511 47.5609 6.91629 47.0805 6.91629C46.5892 6.91629 46.3322 7.18264 46.3322 7.49229C46.3322 7.73699 46.5035 7.90829 46.9628 8.00429L47.4649 8.09982C48.7365 8.35582 49.3986 8.86782 49.3986 9.84899C49.3986 10.8626 48.3732 11.513 47.1016 11.513Z" fill="${textFooter}"/><path fill-rule="evenodd" clip-rule="evenodd" d="M54.4423 8.67582C54.4423 10.2118 55.5214 11.513 57.2197 11.513C58.9181 11.513 60.008 10.2118 60.008 8.67582C60.008 7.12946 58.9186 5.83911 57.2197 5.83911C55.5209 5.83911 54.4423 7.12946 54.4423 8.67582ZM58.4588 8.67582C58.4588 9.65699 57.8715 10.1266 57.2197 10.1266C56.5788 10.1266 55.991 9.65746 55.991 8.67582C55.991 7.70546 56.5788 7.21464 57.2197 7.21464C57.8715 7.21464 58.4588 7.69464 58.4588 8.67582Z" fill="${textFooter}"/><path d="M62.4071 11.3421H60.8692V6.00937H62.4071L62.3859 6.90536C62.6316 6.38301 63.2085 5.84937 63.9991 5.84937C65.2918 5.84937 65.8791 6.68137 65.8791 8.13172V11.3421H64.3407V8.30254C64.3407 7.53454 63.9991 7.14019 63.4113 7.14019C62.7492 7.14019 62.4071 7.67336 62.4071 8.44137V11.3421Z" fill="${textFooter}"/><path fill-rule="evenodd" clip-rule="evenodd" d="M66.68 8.68664C66.68 10.3186 67.6951 11.513 69.3934 11.513C70.8998 11.513 71.7012 10.6918 71.936 9.78499L70.5045 9.51864C70.4085 9.89182 70.0344 10.3398 69.3826 10.3398C68.7097 10.3398 68.1544 9.74217 68.1435 9.02782H71.9468C71.9788 8.89982 72 8.71864 72 8.47346C72 6.92711 70.8461 5.83911 69.4043 5.83911C67.8979 5.83911 66.68 7.04382 66.68 8.68664ZM70.5045 8.11064H68.1972C68.2111 7.80581 68.3422 7.51812 68.563 7.30754C68.7839 7.09696 69.0775 6.97973 69.3826 6.98029C69.9812 6.98029 70.5257 7.42829 70.5045 8.11064Z" fill="${textFooter}"/><path d="M52.4418 9.04852V7.26217H53.968V6.00005H52.4418V4.42029H50.9124V6.00005H49.8042V7.26217H50.9124V9.10923C50.9124 9.25511 50.9044 9.43394 50.9266 9.59958C50.9647 9.88194 51.0753 10.3384 51.4691 10.7313C51.8626 11.1243 52.32 11.2349 52.6023 11.2725C52.8216 11.3017 53.0654 11.3012 53.2197 11.3008L53.8743 11.3026V9.86923H53.256C52.8687 9.86923 52.6748 9.86923 52.5543 9.74876C52.4414 9.63582 52.4414 9.45793 52.4418 9.11488V9.04852Z" fill="${textFooter}"/></svg>`;
4712
- overlay.innerHTML = `<div data-passkey-card style="background:${bgPrimary}"><div style="display:flex;width:100%;flex-direction:column;align-items:flex-start;gap:${LOADING_CARD_GAP}px"><div style="display:flex;width:100%;flex-direction:column;align-items:center;gap:${LOADING_BODY_GAP}px"><div style="display:flex;align-items:center;padding:${LOADING_ICON_PADDING}px"><div style="position:relative;width:${SPINNER_SIZE}px;height:${SPINNER_SIZE}px">${spinnerSvg}</div></div><div style="display:flex;width:100%;flex-direction:column;align-items:center;gap:${LOADING_TITLE_BLOCK_GAP}px;text-align:center"><p style="margin:0;font-size:${LOADING_TITLE_FONT_SIZE}px;font-weight:${LOADING_TITLE_FONT_WEIGHT};color:${textPrimary};line-height:${LOADING_LINE_HEIGHT}">${LOADING_TITLE}</p><p style="margin:0;font-size:${LOADING_SUBTITLE_FONT_SIZE}px;font-weight:${LOADING_SUBTITLE_FONT_WEIGHT};color:${textSecondary};line-height:${LOADING_LINE_HEIGHT}">${LOADING_SUBTITLE}</p></div></div><div style="display:flex;width:100%;align-items:center;justify-content:center;gap:${LOADING_FOOTER_GAP}px"><span style="font-size:${LOADING_FOOTER_FONT_SIZE}px;font-weight:${LOADING_FOOTER_FONT_WEIGHT};color:${textFooter};line-height:${LOADING_LINE_HEIGHT}">Powered by</span>` + rhinestoneLogo + `</div></div></div>`;
4879
+ spinner.innerHTML = `<svg viewBox="0 0 ${SPINNER_SIZE} ${SPINNER_SIZE}" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><circle cx="${spinnerCenter}" cy="${spinnerCenter}" r="${SPINNER_RADIUS}" stroke="currentColor" opacity="0.3" stroke-width="${SPINNER_STROKE}"/><circle cx="${spinnerCenter}" cy="${spinnerCenter}" r="${SPINNER_RADIUS}" stroke="currentColor" stroke-width="${SPINNER_STROKE}" stroke-linecap="round" stroke-dasharray="${SPINNER_ARC} ${spinnerGap}" transform="rotate(-90 ${spinnerCenter} ${spinnerCenter})"/></svg>`;
4880
+ spinnerWrap.appendChild(spinner);
4881
+ const preloadCopy = document.createElement("div");
4882
+ preloadCopy.dataset.passkeyPreloadCopy = "";
4883
+ const preloadTitle = document.createElement("p");
4884
+ preloadTitle.dataset.passkeyPreloadTitle = "";
4885
+ preloadTitle.textContent = LOADING_TITLE;
4886
+ const preloadSubtitle = document.createElement("p");
4887
+ preloadSubtitle.dataset.passkeyPreloadSubtitle = "";
4888
+ preloadSubtitle.textContent = LOADING_SUBTITLE;
4889
+ preloadCopy.append(preloadTitle, preloadSubtitle);
4890
+ preloadBody.append(spinnerWrap, preloadCopy);
4891
+ preloadCard.appendChild(preloadBody);
4892
+ overlay.appendChild(preloadCard);
4713
4893
  dialog.appendChild(overlay);
4714
4894
  const iframe = document.createElement("iframe");
4715
4895
  iframe.setAttribute(
@@ -4729,21 +4909,23 @@ var OneAuthClient = class {
4729
4909
  );
4730
4910
  iframe.setAttribute("title", "Passkey");
4731
4911
  iframe.style.opacity = "0";
4732
- let overlayHidden = false;
4733
- const hideOverlay = () => {
4734
- if (overlayHidden) return;
4735
- overlayHidden = true;
4912
+ let revealed = false;
4913
+ let readyFailsafe;
4914
+ const revealIframe = () => {
4915
+ if (revealed) return;
4916
+ revealed = true;
4917
+ clearTimeout(readyFailsafe);
4736
4918
  iframe.style.opacity = "1";
4737
- overlay.style.pointerEvents = "none";
4738
- requestAnimationFrame(() => {
4739
- overlay.style.display = "none";
4740
- });
4919
+ overlay.style.display = "none";
4741
4920
  };
4742
- const overlayFailsafe = setTimeout(hideOverlay, 8e3);
4921
+ const revealFailsafe = setTimeout(revealIframe, 8e3);
4743
4922
  const handleMessage = (event) => {
4744
4923
  if (event.origin !== hostUrl.origin) return;
4745
- if (event.data?.type === "PASSKEY_RENDERED" || event.data?.type === "PASSKEY_READY") {
4746
- hideOverlay();
4924
+ if (event.source !== iframe.contentWindow) return;
4925
+ if (event.data?.type === "PASSKEY_RENDERED") {
4926
+ revealIframe();
4927
+ } else if (event.data?.type === "PASSKEY_READY" && !revealed && readyFailsafe === void 0) {
4928
+ readyFailsafe = setTimeout(revealIframe, 1e3);
4747
4929
  }
4748
4930
  if (event.data?.type === "PASSKEY_DISCONNECT") {
4749
4931
  localStorage.removeItem("1auth-user");
@@ -4771,7 +4953,13 @@ var OneAuthClient = class {
4771
4953
  document.addEventListener("keydown", handleEscape);
4772
4954
  const reveal = () => {
4773
4955
  delete dialog.dataset.passkeyHidden;
4956
+ if (!revealed) {
4957
+ revealed = true;
4958
+ clearTimeout(readyFailsafe);
4959
+ clearTimeout(revealFailsafe);
4960
+ }
4774
4961
  iframe.style.opacity = "1";
4962
+ overlay.style.display = "none";
4775
4963
  };
4776
4964
  if (options?.hidden) {
4777
4965
  dialog.show();
@@ -4782,7 +4970,8 @@ var OneAuthClient = class {
4782
4970
  const destroy = () => {
4783
4971
  if (cleanedUp) return;
4784
4972
  cleanedUp = true;
4785
- clearTimeout(overlayFailsafe);
4973
+ clearTimeout(revealFailsafe);
4974
+ clearTimeout(readyFailsafe);
4786
4975
  inertObserver.disconnect();
4787
4976
  viewportInfoCleanup();
4788
4977
  window.removeEventListener("message", handleMessage);
@@ -5088,19 +5277,20 @@ var OneAuthClient = class {
5088
5277
  return;
5089
5278
  }
5090
5279
  if (data?.type === "PASSKEY_GRANT_PERMISSION_RESULT") {
5091
- teardown();
5092
- cleanup();
5093
5280
  if (data.success) {
5281
+ teardown();
5094
5282
  resolve({ success: true, ...data.data ?? {} });
5095
- } else {
5096
- resolve({
5097
- success: false,
5098
- error: data.error ?? data.data?.error ?? {
5099
- code: "GRANT_PERMISSION_FAILED",
5100
- message: "Permission grant failed"
5101
- }
5102
- });
5283
+ return;
5103
5284
  }
5285
+ teardown();
5286
+ cleanup();
5287
+ resolve({
5288
+ success: false,
5289
+ error: data.error ?? data.data?.error ?? {
5290
+ code: "GRANT_PERMISSION_FAILED",
5291
+ message: "Permission grant failed"
5292
+ }
5293
+ });
5104
5294
  } else if (data?.type === "PASSKEY_CLOSE") {
5105
5295
  teardown();
5106
5296
  cleanup();