@rhinestone/1auth 0.6.9 → 0.6.10

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.mjs CHANGED
@@ -7,7 +7,9 @@ import {
7
7
  verifyMessageHash
8
8
  } from "./chunk-IHBVEU33.mjs";
9
9
  import {
10
- createOneAuthProvider,
10
+ createOneAuthProvider
11
+ } from "./chunk-THKG3FAG.mjs";
12
+ import {
11
13
  getAllSupportedChainsAndTokens,
12
14
  getChainById,
13
15
  getChainExplorerUrl,
@@ -23,7 +25,7 @@ import {
23
25
  isTestnet,
24
26
  isTokenAddressSupported,
25
27
  resolveTokenAddress
26
- } from "./chunk-VZYHCFEH.mjs";
28
+ } from "./chunk-GUAI55LL.mjs";
27
29
  import {
28
30
  buildTransactionReview,
29
31
  encodeWebAuthnSignature,
@@ -127,25 +129,19 @@ var SPINNER_RADIUS = (SPINNER_SIZE - SPINNER_STROKE) / 2;
127
129
  var SPINNER_CIRCUMFERENCE = 2 * Math.PI * SPINNER_RADIUS;
128
130
  var SPINNER_ARC_FRACTION = 0.17;
129
131
  var SPINNER_ARC = SPINNER_CIRCUMFERENCE * SPINNER_ARC_FRACTION;
132
+ var SPINNER_SPIN_DURATION_MS = 800;
130
133
  var LOADING_MODAL_WIDTH = 380;
131
134
  var LOADING_MODAL_PADDING = 12;
132
135
  var LOADING_MODAL_RADIUS = 16;
133
- var LOADING_MOBILE_BREAKPOINT = 640;
134
- var LOADING_CARD_GAP = 16;
135
136
  var LOADING_BODY_GAP = 12;
136
137
  var LOADING_ICON_PADDING = 12;
137
138
  var LOADING_TITLE_BLOCK_GAP = 4;
138
- var LOADING_FOOTER_GAP = 4;
139
139
  var LOADING_TITLE_FONT_SIZE = 20;
140
140
  var LOADING_TITLE_FONT_WEIGHT = 700;
141
141
  var LOADING_SUBTITLE_FONT_SIZE = 12;
142
142
  var LOADING_SUBTITLE_FONT_WEIGHT = 500;
143
- var LOADING_FOOTER_FONT_SIZE = 11;
144
- var LOADING_FOOTER_FONT_WEIGHT = 500;
145
143
  var LOADING_LINE_HEIGHT = "normal";
146
144
  var LOADING_FONT_FAMILY = "'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'";
147
- var LOGO_WIDTH = 72;
148
- var LOGO_HEIGHT = 16;
149
145
  var BRAND_PURPLE = "#685bff";
150
146
  var LIGHT_TOKENS = {
151
147
  bgPrimary: "#ffffff",
@@ -159,7 +155,6 @@ var DARK_TOKENS = {
159
155
  textPrimary: "#e4e4e7",
160
156
  textSecondary: "#9f9fa9"
161
157
  };
162
- var FOOTER_TEXT_COLOR = "#71717b";
163
158
  var LOADING_TITLE = "Loading...";
164
159
  var LOADING_SUBTITLE = "This will only take a few moments, do not close the window.";
165
160
 
@@ -169,7 +164,36 @@ var POPUP_HEIGHT = 600;
169
164
  var DEFAULT_EMBED_WIDTH = `${LOADING_MODAL_WIDTH}px`;
170
165
  var DEFAULT_EMBED_HEIGHT = "500px";
171
166
  var DEFAULT_PROVIDER_URL = "https://passkey.1auth.box";
167
+ function currentWindowOrigin() {
168
+ if (typeof window === "undefined") return null;
169
+ const origin = window.location?.origin;
170
+ return typeof origin === "string" && origin.length > 0 ? origin : null;
171
+ }
172
+ function appendParentOriginParam(params) {
173
+ const origin = currentWindowOrigin();
174
+ if (origin) params.set("parentOrigin", origin);
175
+ }
172
176
  var MAX_LABEL_LEN = 20;
177
+ var DEFAULT_BACKDROP_COLOR = "#000000";
178
+ var DEFAULT_BACKDROP_OPACITY = 0.4;
179
+ var DEFAULT_BACKDROP_BLUR = 8;
180
+ var HEX_COLOR_RE = /^#[0-9a-fA-F]{6}$/;
181
+ function resolveBackdropStyle(params) {
182
+ const rawColor = params.get("backdropColor");
183
+ const hex = (rawColor && HEX_COLOR_RE.test(rawColor) ? rawColor : DEFAULT_BACKDROP_COLOR).slice(1);
184
+ const r = parseInt(hex.slice(0, 2), 16);
185
+ const g = parseInt(hex.slice(2, 4), 16);
186
+ const b = parseInt(hex.slice(4, 6), 16);
187
+ const clampNumber = (key, min, max, fallback) => {
188
+ const raw = params.get(key);
189
+ if (!raw) return fallback;
190
+ const n = Number(raw);
191
+ return Number.isFinite(n) ? Math.min(max, Math.max(min, n)) : fallback;
192
+ };
193
+ const opacity = clampNumber("backdropOpacity", 0, 1, DEFAULT_BACKDROP_OPACITY);
194
+ const blur = clampNumber("backdropBlur", 0, 40, DEFAULT_BACKDROP_BLUR);
195
+ return { background: `rgba(${r}, ${g}, ${b}, ${opacity})`, blur };
196
+ }
173
197
  function generateModalNonce() {
174
198
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
175
199
  return crypto.randomUUID();
@@ -293,6 +317,15 @@ var OneAuthClient = class {
293
317
  // "Restore will override" warnings and "emitting session_request:N
294
318
  // without any listeners" errors on the second and later txs.
295
319
  this.eoaDialogState = null;
320
+ // Hidden iframe that warms the dialog ahead of the first real open (see
321
+ // `prewarm()`). The passkey app runs on a long-lived server (not serverless),
322
+ // so there is no backend cold-start to hide — what stays expensive is the
323
+ // *browser-side* cost of a freshly created cross-origin iframe: downloading
324
+ // the dialog HTML + JS bundle, parsing it, hydrating React, and loading the
325
+ // font. Loading that bundle once into a parked hidden iframe lets the real
326
+ // open hit the HTTP cache (Next.js chunks/fonts are immutable-cached) over an
327
+ // already-warm connection, so the SDK preload overlay is shown only briefly.
328
+ this.prewarmState = null;
296
329
  // Serialises concurrent `requestWithWallet` calls — they would otherwise
297
330
  // both try to drive the same iframe and cross-resolve each other.
298
331
  this.eoaSerialQueue = Promise.resolve();
@@ -306,6 +339,15 @@ var OneAuthClient = class {
306
339
  if (dialogUrl !== providerUrl) {
307
340
  this.injectPreconnect(dialogUrl);
308
341
  }
342
+ if (this.config.prewarm) {
343
+ const warm = () => void this.prewarm();
344
+ const ric = window.requestIdleCallback;
345
+ if (typeof ric === "function") {
346
+ ric(warm, { timeout: 2e3 });
347
+ } else {
348
+ setTimeout(warm, 200);
349
+ }
350
+ }
309
351
  }
310
352
  const initOperation = this.createTelemetryOperation("client", {
311
353
  hasClientId: !!this.config.clientId,
@@ -539,6 +581,18 @@ var OneAuthClient = class {
539
581
  if (primary) {
540
582
  params.set("accent", primary);
541
583
  }
584
+ const backdrop = theme.backdrop;
585
+ if (backdrop) {
586
+ if (backdrop.color) {
587
+ params.set("backdropColor", backdrop.color);
588
+ }
589
+ if (backdrop.opacity !== void 0) {
590
+ params.set("backdropOpacity", String(backdrop.opacity));
591
+ }
592
+ if (backdrop.blur !== void 0) {
593
+ params.set("backdropBlur", String(backdrop.blur));
594
+ }
595
+ }
542
596
  return params.toString();
543
597
  }
544
598
  /**
@@ -777,9 +831,7 @@ var OneAuthClient = class {
777
831
  if (options?.flow === "create-account") {
778
832
  params.set("switch", "1");
779
833
  }
780
- if (typeof window !== "undefined" && window.location?.origin) {
781
- params.set("parentOrigin", window.location.origin);
782
- }
834
+ appendParentOriginParam(params);
783
835
  this.appendTelemetryParams(params, telemetry);
784
836
  const themeParams = this.getThemeParams(options?.theme);
785
837
  if (themeParams) {
@@ -860,9 +912,7 @@ var OneAuthClient = class {
860
912
  if (this.config.clientId) {
861
913
  params.set("clientId", this.config.clientId);
862
914
  }
863
- if (typeof window !== "undefined" && window.location?.origin) {
864
- params.set("parentOrigin", window.location.origin);
865
- }
915
+ appendParentOriginParam(params);
866
916
  this.appendTelemetryParams(params, telemetry);
867
917
  const themeParams = this.getThemeParams(options?.theme);
868
918
  if (themeParams) {
@@ -993,10 +1043,13 @@ var OneAuthClient = class {
993
1043
  themeParsed.forEach((value, key) => params.set(key, value));
994
1044
  }
995
1045
  const url = `${dialogUrl}/dialog/account?${params.toString()}`;
1046
+ const accessTokenResult = await this.fetchAccessToken();
1047
+ const initMessage = { mode: "iframe" };
1048
+ if (accessTokenResult.ok) {
1049
+ initMessage.auth = { accessToken: accessTokenResult.accessToken };
1050
+ }
996
1051
  const { dialog, iframe, cleanup } = this.createModalDialog(url);
997
- const ready = await this.waitForDialogReady(dialog, iframe, cleanup, {
998
- mode: "iframe"
999
- });
1052
+ const ready = await this.waitForDialogReady(dialog, iframe, cleanup, initMessage);
1000
1053
  if (!ready) return;
1001
1054
  const dialogOrigin = this.getDialogOrigin();
1002
1055
  return new Promise((resolve) => {
@@ -1181,6 +1234,7 @@ var OneAuthClient = class {
1181
1234
  }
1182
1235
  const dialogUrl = this.getDialogUrl();
1183
1236
  const params = new URLSearchParams({ mode: "iframe" });
1237
+ appendParentOriginParam(params);
1184
1238
  this.appendTelemetryParams(params, telemetry);
1185
1239
  const themeParams = this.getThemeParams(options.theme);
1186
1240
  if (themeParams) {
@@ -1227,6 +1281,7 @@ var OneAuthClient = class {
1227
1281
  targetChains,
1228
1282
  status: result.status ? String(result.status) : void 0
1229
1283
  });
1284
+ await this.waitForDialogClose(dialog, cleanup);
1230
1285
  } else {
1231
1286
  this.emitTelemetry(
1232
1287
  result.error?.code === "USER_CANCELLED" ? "dialog.cancelled" : "grant_permission.failed",
@@ -1408,6 +1463,7 @@ var OneAuthClient = class {
1408
1463
  });
1409
1464
  const dialogUrl = this.getDialogUrl();
1410
1465
  const params = new URLSearchParams({ mode: "iframe" });
1466
+ appendParentOriginParam(params);
1411
1467
  this.appendTelemetryParams(params, telemetry);
1412
1468
  const themeParams = this.getThemeParams(options?.theme);
1413
1469
  if (themeParams) {
@@ -1578,6 +1634,7 @@ var OneAuthClient = class {
1578
1634
  let prepareResponse;
1579
1635
  const dialogUrl = this.getDialogUrl();
1580
1636
  const params = new URLSearchParams({ mode: "iframe" });
1637
+ appendParentOriginParam(params);
1581
1638
  this.appendTelemetryParams(params, telemetry);
1582
1639
  const themeParams = this.getThemeParams();
1583
1640
  if (themeParams) {
@@ -1854,6 +1911,10 @@ var OneAuthClient = class {
1854
1911
  expiresAt: refreshedData.expiresAt,
1855
1912
  intentOp: refreshedData.intentOp,
1856
1913
  digestResult: refreshedData.digestResult,
1914
+ // A refreshed quote mints a fresh binding — it MUST replace the old
1915
+ // one, or execute would verify the new intentOp against a stale
1916
+ // binding and reject (once enforce mode is on).
1917
+ binding: refreshedData.binding,
1857
1918
  auth: refreshedAuth
1858
1919
  };
1859
1920
  return {
@@ -1863,6 +1924,7 @@ var OneAuthClient = class {
1863
1924
  originMessages: refreshedData.originMessages,
1864
1925
  transaction: refreshedData.transaction,
1865
1926
  digestResult: refreshedData.digestResult,
1927
+ binding: refreshedData.binding,
1866
1928
  // Dialog-executed intents read auth from dialog state — ship the
1867
1929
  // rebound bundle with the refresh so PASSKEY_REFRESH_COMPLETE
1868
1930
  // updates it alongside the quote.
@@ -1927,6 +1989,7 @@ var OneAuthClient = class {
1927
1989
  userId: prepareResponse.userId,
1928
1990
  intentOp: prepareResponse.intentOp,
1929
1991
  digestResult: prepareResponse.digestResult,
1992
+ binding: prepareResponse.binding,
1930
1993
  tier: prepareResult.tier,
1931
1994
  auth: sponsorshipTokens,
1932
1995
  telemetry: this.telemetryPayload(telemetry)
@@ -2057,6 +2120,7 @@ var OneAuthClient = class {
2057
2120
  userId: prepareResponse.userId,
2058
2121
  intentOp: prepareResponse.intentOp,
2059
2122
  digestResult: prepareResponse.digestResult,
2123
+ binding: prepareResponse.binding,
2060
2124
  tier: prepareResult.tier,
2061
2125
  ...authReady && { auth: authReady },
2062
2126
  telemetry: this.telemetryPayload(telemetry)
@@ -2230,6 +2294,8 @@ var OneAuthClient = class {
2230
2294
  calls: prepareResponse.calls,
2231
2295
  expiresAt: prepareResponse.expiresAt,
2232
2296
  digestResult: prepareResponse.digestResult,
2297
+ // Opaque prepare->execute binding, forwarded unchanged.
2298
+ binding: prepareResponse.binding,
2233
2299
  // Signature from dialog
2234
2300
  signature: signingResult.signature,
2235
2301
  passkey: signingResult.passkey,
@@ -2555,6 +2621,7 @@ var OneAuthClient = class {
2555
2621
  };
2556
2622
  const dialogUrl = this.getDialogUrl();
2557
2623
  const params = new URLSearchParams({ mode: "iframe" });
2624
+ appendParentOriginParam(params);
2558
2625
  this.appendTelemetryParams(params, telemetry);
2559
2626
  const themeParams = this.getThemeParams();
2560
2627
  if (themeParams) {
@@ -2671,6 +2738,7 @@ var OneAuthClient = class {
2671
2738
  batchIntents: prepareResponse.intents,
2672
2739
  batchFailedIntents: prepareResponse.failedIntents,
2673
2740
  challenge: prepareResponse.challenge,
2741
+ binding: prepareResponse.binding,
2674
2742
  username: options.username,
2675
2743
  accountAddress: prepareResponse.accountAddress,
2676
2744
  userId: prepareResponse.userId,
@@ -2714,6 +2782,7 @@ var OneAuthClient = class {
2714
2782
  batchIntents: prepareResponse.intents,
2715
2783
  batchFailedIntents: prepareResponse.failedIntents,
2716
2784
  challenge: prepareResponse.challenge,
2785
+ binding: prepareResponse.binding,
2717
2786
  username: options.username,
2718
2787
  accountAddress: prepareResponse.accountAddress,
2719
2788
  userId: prepareResponse.userId,
@@ -2781,6 +2850,7 @@ var OneAuthClient = class {
2781
2850
  batchIntents: refreshed.intents,
2782
2851
  batchFailedIntents: refreshed.failedIntents,
2783
2852
  challenge: refreshed.challenge,
2853
+ binding: refreshed.binding,
2784
2854
  expiresAt: refreshed.expiresAt,
2785
2855
  auth: refreshedAuth
2786
2856
  };
@@ -2788,6 +2858,7 @@ var OneAuthClient = class {
2788
2858
  type: "PASSKEY_REFRESH_COMPLETE",
2789
2859
  batchIntents: refreshed.intents,
2790
2860
  challenge: refreshed.challenge,
2861
+ binding: refreshed.binding,
2791
2862
  expiresAt: refreshed.expiresAt,
2792
2863
  auth: refreshedAuth
2793
2864
  }, dialogOrigin);
@@ -3216,6 +3287,82 @@ var OneAuthClient = class {
3216
3287
  } catch {
3217
3288
  }
3218
3289
  }
3290
+ /**
3291
+ * Warm the dialog ahead of the first user interaction.
3292
+ *
3293
+ * `preconnect` (run in the constructor) only warms DNS/TLS. This goes
3294
+ * further: it loads the dialog into a hidden, off-screen iframe so the
3295
+ * browser downloads and parses the dialog HTML + JS bundle and the font, and
3296
+ * keeps the cross-origin connection alive. When the user later opens a real
3297
+ * auth/sign dialog, the fresh iframe serves those bytes from cache over the
3298
+ * warm connection and paints far sooner — so the SDK preload overlay is shown
3299
+ * only briefly (or imperceptibly) instead of covering a full cold load.
3300
+ *
3301
+ * It loads a dedicated `/dialog/warm` route, NOT a real flow route. That
3302
+ * matters for two reasons:
3303
+ * - **No side effects.** The real `/dialog/auth` route runs the signup
3304
+ * flow on mount (identity probe → `POST /api/auth/register?step=start`),
3305
+ * which writes an `AuthChallenge` row and counts against the register
3306
+ * rate limiter. Warming must never create backend auth state for a user
3307
+ * who hasn't acted, so the warm route renders nothing and runs no flow.
3308
+ * - **No message cross-talk.** The warm route posts no `PASSKEY_READY` /
3309
+ * `PASSKEY_RENDERED`, so a slow warm can never satisfy a visible modal's
3310
+ * readiness listener and drive it to `PASSKEY_INIT` at the wrong moment.
3311
+ * The warm route still pulls the shared Next.js framework + main + dialog
3312
+ * layout chunks (the bulk of every flow's bundle), so the real open is fast.
3313
+ *
3314
+ * Best called on a *likely-intent* signal — `pointerenter`/`focus` of your
3315
+ * "Sign in" / "Pay" button — rather than on page load, so visitors who never
3316
+ * authenticate don't pay for a cross-origin iframe. Pass `prewarm: true` in
3317
+ * the client config to have the SDK schedule this once on an idle callback.
3318
+ *
3319
+ * Idempotent: repeated calls return the same in-flight/settled promise.
3320
+ * Resolves `true` once the hidden iframe's document has loaded, `false` on
3321
+ * timeout or failure. Never throws and never blocks a real dialog open — a
3322
+ * failed prewarm just means the next open does a normal (cold) load.
3323
+ *
3324
+ * @returns Whether the dialog became warm.
3325
+ */
3326
+ async prewarm() {
3327
+ if (typeof document === "undefined") return false;
3328
+ if (this.prewarmState) return this.prewarmState.ready;
3329
+ const warmUrl = `${this.getDialogUrl()}/dialog/warm`;
3330
+ const iframe = document.createElement("iframe");
3331
+ iframe.setAttribute("aria-hidden", "true");
3332
+ iframe.setAttribute("tabindex", "-1");
3333
+ iframe.title = "1auth dialog prewarm";
3334
+ iframe.style.cssText = "position:fixed;left:-9999px;top:0;width:1px;height:1px;opacity:0;border:0;pointer-events:none;";
3335
+ const ready = new Promise((resolve) => {
3336
+ let settled = false;
3337
+ const finish = (value) => {
3338
+ if (settled) return;
3339
+ settled = true;
3340
+ clearTimeout(timer);
3341
+ resolve(value);
3342
+ };
3343
+ iframe.onload = () => finish(true);
3344
+ iframe.onerror = () => finish(false);
3345
+ const timer = setTimeout(() => finish(false), 1e4);
3346
+ });
3347
+ iframe.src = warmUrl;
3348
+ document.body.appendChild(iframe);
3349
+ this.prewarmState = { iframe, ready };
3350
+ return ready;
3351
+ }
3352
+ /**
3353
+ * Tear down the hidden prewarm iframe created by {@link prewarm}.
3354
+ *
3355
+ * The HTTP cache that prewarm populated survives teardown, so a subsequent
3356
+ * open is still bundle-warm; this only releases the parked frame (and its
3357
+ * keep-alive connection). Call it when auth is no longer likely on the
3358
+ * current view. Safe to call when nothing is warmed.
3359
+ */
3360
+ destroyPrewarm() {
3361
+ const state = this.prewarmState;
3362
+ if (!state) return;
3363
+ this.prewarmState = null;
3364
+ state.iframe.remove();
3365
+ }
3219
3366
  /**
3220
3367
  * Wait for the dialog iframe to signal ready, but defer sending `PASSKEY_INIT`
3221
3368
  * until the caller decides the time is right.
@@ -3598,10 +3745,15 @@ var OneAuthClient = class {
3598
3745
  return { ok: true, dialog: dialog2, iframe: iframe2 };
3599
3746
  }
3600
3747
  const dialogUrl = this.getDialogUrl();
3748
+ const params = new URLSearchParams({ mode: "iframe" });
3749
+ appendParentOriginParam(params);
3601
3750
  const themeParams = this.getThemeParams(theme);
3602
- const parentOrigin = typeof window !== "undefined" ? window.location.origin : "";
3603
- const parentOriginParam = parentOrigin ? `&parentOrigin=${encodeURIComponent(parentOrigin)}` : "";
3604
- const signingUrl = `${dialogUrl}/dialog/sign-eoa?mode=iframe${parentOriginParam}${themeParams ? `&${themeParams}` : ""}`;
3751
+ if (themeParams) {
3752
+ new URLSearchParams(themeParams).forEach((value, key) => {
3753
+ params.set(key, value);
3754
+ });
3755
+ }
3756
+ const signingUrl = `${dialogUrl}/dialog/sign-eoa?${params.toString()}`;
3605
3757
  const { dialog, iframe, destroy } = this.createModalDialog(signingUrl, {
3606
3758
  persistent: true
3607
3759
  });
@@ -3706,6 +3858,7 @@ var OneAuthClient = class {
3706
3858
  });
3707
3859
  const dialogUrl = this.getDialogUrl();
3708
3860
  const params = new URLSearchParams({ mode: "iframe" });
3861
+ appendParentOriginParam(params);
3709
3862
  this.appendTelemetryParams(params, telemetry);
3710
3863
  const themeParams = this.getThemeParams(options?.theme);
3711
3864
  if (themeParams) {
@@ -3848,6 +4001,7 @@ var OneAuthClient = class {
3848
4001
  });
3849
4002
  const dialogUrl = this.getDialogUrl();
3850
4003
  const params = new URLSearchParams({ mode: "iframe" });
4004
+ appendParentOriginParam(params);
3851
4005
  this.appendTelemetryParams(params, telemetry);
3852
4006
  const themeParams = this.getThemeParams(options?.theme);
3853
4007
  if (themeParams) {
@@ -4301,17 +4455,17 @@ var OneAuthClient = class {
4301
4455
  /**
4302
4456
  * Create and open a full-viewport `<dialog>` containing a passkey iframe.
4303
4457
  *
4304
- * The SDK owns only the hidden outer shell; all visible UI (backdrop,
4305
- * card, animations, close button) is rendered by the passkey app inside the
4306
- * iframe. This approach means design changes can be shipped server-side
4307
- * without updating SDK consumers.
4458
+ * The SDK owns the browser `<dialog>` plus a minimal generic preload shell
4459
+ * that appears immediately on click. The passkey iframe owns all branded and
4460
+ * origin-specific UI (TitleBar, trust icon, action cards); the shell is
4461
+ * removed as soon as the iframe reports that its centered card has painted.
4308
4462
  *
4309
4463
  * Lifecycle:
4310
- * 1. A themed "Loading…" overlay is injected and shown immediately while the
4311
- * iframe loads, matching the exact visual structure of the passkey app's
4312
- * TitleBar + IndeterminateLoader so the transition is seamless.
4313
- * 2. When the iframe sends `PASSKEY_RENDERED`, the overlay fades out and
4314
- * the iframe becomes fully visible.
4464
+ * 1. A themed generic preload shell is injected and shown immediately
4465
+ * while the iframe loads. It intentionally contains no TitleBar or
4466
+ * origin chrome so that UI has a single implementation in apps/passkey.
4467
+ * 2. When the iframe sends `PASSKEY_RENDERED`, the overlay is removed and
4468
+ * the iframe becomes fully visible in the same frame.
4315
4469
  * 3. The returned `cleanup` function tears down all event listeners,
4316
4470
  * disconnects the MutationObserver, closes the `<dialog>`, and removes
4317
4471
  * it from the DOM. It is idempotent — safe to call multiple times.
@@ -4333,12 +4487,10 @@ var OneAuthClient = class {
4333
4487
  const dialogUrl = this.getDialogUrl();
4334
4488
  const hostUrl = new URL(dialogUrl);
4335
4489
  const urlParams = new URL(url, window.location.href).searchParams;
4490
+ const { background: backdropBackground, blur: backdropBlur } = resolveBackdropStyle(urlParams);
4336
4491
  const themeMode = urlParams.get("theme") || "light";
4337
4492
  const isDark = themeMode === "dark" || themeMode !== "light" && window.matchMedia("(prefers-color-scheme: dark)").matches;
4338
4493
  const theme = isDark ? DARK_TOKENS : LIGHT_TOKENS;
4339
- const { bgPrimary, borderColor, textPrimary, textSecondary } = theme;
4340
- const textFooter = FOOTER_TEXT_COLOR;
4341
- const spinnerColor = BRAND_PURPLE;
4342
4494
  const dialog = document.createElement("dialog");
4343
4495
  dialog.dataset.passkey = "";
4344
4496
  if (options?.hidden) {
@@ -4349,16 +4501,6 @@ var OneAuthClient = class {
4349
4501
  document.body.appendChild(dialog);
4350
4502
  const style = document.createElement("style");
4351
4503
  style.textContent = `
4352
- /* Load Inter so the overlay's title/subtitle widths match the
4353
- iframe (which loads Inter via next/font). Without this, host
4354
- pages on Arial / system-ui produce wider text that wraps to two
4355
- lines and breaks the visual match with the Figma. font-display:
4356
- swap renders immediately with a fallback and swaps when Inter
4357
- is ready \u2014 usually well before PASSKEY_RENDERED fires, but the
4358
- overlay is short-lived either way so a single-frame swap is
4359
- acceptable. */
4360
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@500;700&display=swap');
4361
-
4362
4504
  dialog[data-passkey] {
4363
4505
  position: fixed;
4364
4506
  top: 0;
@@ -4396,103 +4538,143 @@ var OneAuthClient = class {
4396
4538
  backdrop-filter: none;
4397
4539
  -webkit-backdrop-filter: none;
4398
4540
  }
4399
- dialog[data-passkey] iframe {
4400
- position: fixed;
4401
- top: 0;
4402
- left: 0;
4403
- width: 100%;
4404
- height: 100%;
4405
- border: 0;
4406
- background-color: transparent;
4407
- color-scheme: normal;
4408
- pointer-events: auto;
4409
- backdrop-filter: blur(8px);
4410
- -webkit-backdrop-filter: blur(8px);
4411
- }
4412
4541
  dialog[data-passkey] [data-passkey-overlay] {
4413
4542
  position: fixed;
4414
- top: 0;
4415
- left: 0;
4416
- width: 100%;
4417
- height: 100%;
4543
+ inset: 0;
4418
4544
  display: flex;
4419
4545
  align-items: center;
4420
4546
  justify-content: center;
4421
- background: rgba(0, 0, 0, 0.4);
4422
- backdrop-filter: blur(8px);
4423
- -webkit-backdrop-filter: blur(8px);
4547
+ background: ${backdropBackground};
4548
+ backdrop-filter: blur(${backdropBlur}px);
4549
+ -webkit-backdrop-filter: blur(${backdropBlur}px);
4424
4550
  z-index: 1;
4425
- /* Purely a visual loading screen \u2014 never intercept clicks.
4426
- If the iframe's X button or backdrop-dismiss area happens to
4427
- be reachable while the overlay is still painting (slow CI
4428
- runners, redirect remounts), the click goes straight through
4429
- instead of being trapped by the SDK preload. The previous
4430
- rAF-deferred display:none lost this race in GitHub Actions
4431
- \u2014 dialog-close.spec.ts repeatedly hit
4432
- "<div data-passkey-overlay> intercepts pointer events". */
4433
4551
  pointer-events: none;
4434
- animation: _1auth-backdrop-in 0.2s ease-out;
4435
4552
  }
4436
4553
  dialog[data-passkey][data-passkey-hidden] [data-passkey-overlay] {
4437
4554
  display: none;
4438
4555
  }
4439
- dialog[data-passkey] [data-passkey-card] {
4556
+ dialog[data-passkey] [data-passkey-preload-card] {
4440
4557
  width: ${LOADING_MODAL_WIDTH}px;
4558
+ max-width: 100%;
4559
+ overflow: hidden;
4560
+ border-radius: ${LOADING_MODAL_RADIUS}px;
4561
+ background: ${theme.bgPrimary};
4562
+ box-shadow: 0 24px 80px rgba(0, 0, 0, 0.24), 0 2px 12px rgba(0, 0, 0, 0.12);
4563
+ font-family: ${LOADING_FONT_FAMILY};
4564
+ line-height: ${LOADING_LINE_HEIGHT};
4565
+ -webkit-font-smoothing: antialiased;
4566
+ -moz-osx-font-smoothing: grayscale;
4567
+ }
4568
+ dialog[data-passkey] [data-passkey-preload-body] {
4441
4569
  box-sizing: border-box;
4570
+ display: flex;
4571
+ width: 100%;
4572
+ flex-direction: column;
4573
+ align-items: center;
4574
+ gap: ${LOADING_BODY_GAP}px;
4442
4575
  padding: ${LOADING_MODAL_PADDING}px;
4443
4576
  border-radius: ${LOADING_MODAL_RADIUS}px;
4444
- box-shadow: inset 0 0 0 1px ${borderColor};
4445
- animation: _1auth-card-in 0.2s cubic-bezier(0.32, 0.72, 0, 1);
4446
- max-height: 100dvh;
4447
- overflow-y: auto;
4577
+ background: ${theme.bgPrimary};
4578
+ }
4579
+ dialog[data-passkey] [data-passkey-preload-spinner-wrap] {
4580
+ display: flex;
4581
+ align-items: center;
4582
+ padding: ${LOADING_ICON_PADDING}px;
4583
+ }
4584
+ dialog[data-passkey] [data-passkey-preload-spinner] {
4585
+ position: relative;
4586
+ width: ${SPINNER_SIZE}px;
4587
+ height: ${SPINNER_SIZE}px;
4588
+ }
4589
+ dialog[data-passkey] [data-passkey-preload-spinner] svg {
4590
+ position: absolute;
4591
+ inset: 0;
4592
+ width: 100%;
4593
+ height: 100%;
4594
+ color: ${BRAND_PURPLE};
4595
+ animation: _1auth-spin ${SPINNER_SPIN_DURATION_MS}ms linear infinite;
4596
+ }
4597
+ dialog[data-passkey] [data-passkey-preload-copy] {
4448
4598
  display: flex;
4599
+ width: 100%;
4449
4600
  flex-direction: column;
4450
- gap: ${LOADING_CARD_GAP}px;
4451
- font-family: ${LOADING_FONT_FAMILY};
4452
- /* line-height: normal must be set on the card so the host page's
4453
- Tailwind preflight (line-height: 1.5 on <body>) doesn't bleed
4454
- into our card via inheritance \u2014 that's what made the SDK preload
4455
- render taller than the iframe screen. */
4601
+ align-items: center;
4602
+ gap: ${LOADING_TITLE_BLOCK_GAP}px;
4603
+ text-align: center;
4604
+ }
4605
+ dialog[data-passkey] [data-passkey-preload-title] {
4606
+ margin: 0;
4607
+ color: ${theme.textPrimary};
4608
+ font-size: ${LOADING_TITLE_FONT_SIZE}px;
4609
+ font-weight: ${LOADING_TITLE_FONT_WEIGHT};
4456
4610
  line-height: ${LOADING_LINE_HEIGHT};
4457
- -webkit-font-smoothing: antialiased;
4458
- -moz-osx-font-smoothing: grayscale;
4459
4611
  }
4460
- @media (max-width: ${LOADING_MOBILE_BREAKPOINT - 1}px) {
4612
+ dialog[data-passkey] [data-passkey-preload-subtitle] {
4613
+ margin: 0;
4614
+ color: ${theme.textSecondary};
4615
+ font-size: ${LOADING_SUBTITLE_FONT_SIZE}px;
4616
+ font-weight: ${LOADING_SUBTITLE_FONT_WEIGHT};
4617
+ line-height: ${LOADING_LINE_HEIGHT};
4618
+ }
4619
+ @media (max-width: 639px) {
4461
4620
  dialog[data-passkey] [data-passkey-overlay] {
4462
4621
  align-items: flex-end;
4463
4622
  }
4464
- dialog[data-passkey] [data-passkey-card] {
4623
+ dialog[data-passkey] [data-passkey-preload-card] {
4465
4624
  width: 100%;
4466
- border-bottom-left-radius: 0;
4467
4625
  border-bottom-right-radius: 0;
4626
+ border-bottom-left-radius: 0;
4627
+ }
4628
+ dialog[data-passkey] [data-passkey-preload-body] {
4468
4629
  padding-bottom: calc(${LOADING_MODAL_PADDING}px + env(safe-area-inset-bottom));
4469
- animation: _1auth-card-slide 0.3s cubic-bezier(0.32, 0.72, 0, 1);
4470
4630
  }
4471
4631
  }
4472
- @keyframes _1auth-backdrop-in {
4473
- from { opacity: 0; } to { opacity: 1; }
4474
- }
4475
- @keyframes _1auth-card-in {
4476
- from { opacity: 0; transform: scale(0.96) translateY(8px); }
4477
- to { opacity: 1; transform: scale(1) translateY(0); }
4478
- }
4479
- @keyframes _1auth-card-slide {
4480
- from { transform: translate3d(0, 100%, 0); }
4481
- to { transform: translate3d(0, 0, 0); }
4482
- }
4483
4632
  @keyframes _1auth-spin {
4484
4633
  from { transform: rotate(0deg); }
4485
4634
  to { transform: rotate(360deg); }
4486
4635
  }
4636
+ dialog[data-passkey] iframe {
4637
+ position: fixed;
4638
+ top: 0;
4639
+ left: 0;
4640
+ width: 100%;
4641
+ height: 100%;
4642
+ border: 0;
4643
+ background-color: transparent;
4644
+ color-scheme: normal;
4645
+ pointer-events: auto;
4646
+ backdrop-filter: blur(${backdropBlur}px);
4647
+ -webkit-backdrop-filter: blur(${backdropBlur}px);
4648
+ transition: none;
4649
+ }
4487
4650
  `;
4488
4651
  dialog.appendChild(style);
4489
4652
  const overlay = document.createElement("div");
4490
4653
  overlay.dataset.passkeyOverlay = "";
4654
+ const preloadCard = document.createElement("div");
4655
+ preloadCard.dataset.passkeyPreloadCard = "";
4656
+ const preloadBody = document.createElement("div");
4657
+ preloadBody.dataset.passkeyPreloadBody = "";
4658
+ const spinnerWrap = document.createElement("div");
4659
+ spinnerWrap.dataset.passkeyPreloadSpinnerWrap = "";
4660
+ const spinner = document.createElement("div");
4661
+ spinner.dataset.passkeyPreloadSpinner = "";
4491
4662
  const spinnerCenter = SPINNER_SIZE / 2;
4492
4663
  const spinnerGap = SPINNER_CIRCUMFERENCE - SPINNER_ARC;
4493
- 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>`;
4494
- 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>`;
4495
- 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>`;
4664
+ 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>`;
4665
+ spinnerWrap.appendChild(spinner);
4666
+ const preloadCopy = document.createElement("div");
4667
+ preloadCopy.dataset.passkeyPreloadCopy = "";
4668
+ const preloadTitle = document.createElement("p");
4669
+ preloadTitle.dataset.passkeyPreloadTitle = "";
4670
+ preloadTitle.textContent = LOADING_TITLE;
4671
+ const preloadSubtitle = document.createElement("p");
4672
+ preloadSubtitle.dataset.passkeyPreloadSubtitle = "";
4673
+ preloadSubtitle.textContent = LOADING_SUBTITLE;
4674
+ preloadCopy.append(preloadTitle, preloadSubtitle);
4675
+ preloadBody.append(spinnerWrap, preloadCopy);
4676
+ preloadCard.appendChild(preloadBody);
4677
+ overlay.appendChild(preloadCard);
4496
4678
  dialog.appendChild(overlay);
4497
4679
  const iframe = document.createElement("iframe");
4498
4680
  iframe.setAttribute(
@@ -4512,21 +4694,23 @@ var OneAuthClient = class {
4512
4694
  );
4513
4695
  iframe.setAttribute("title", "Passkey");
4514
4696
  iframe.style.opacity = "0";
4515
- let overlayHidden = false;
4516
- const hideOverlay = () => {
4517
- if (overlayHidden) return;
4518
- overlayHidden = true;
4697
+ let revealed = false;
4698
+ let readyFailsafe;
4699
+ const revealIframe = () => {
4700
+ if (revealed) return;
4701
+ revealed = true;
4702
+ clearTimeout(readyFailsafe);
4519
4703
  iframe.style.opacity = "1";
4520
- overlay.style.pointerEvents = "none";
4521
- requestAnimationFrame(() => {
4522
- overlay.style.display = "none";
4523
- });
4704
+ overlay.style.display = "none";
4524
4705
  };
4525
- const overlayFailsafe = setTimeout(hideOverlay, 8e3);
4706
+ const revealFailsafe = setTimeout(revealIframe, 8e3);
4526
4707
  const handleMessage = (event) => {
4527
4708
  if (event.origin !== hostUrl.origin) return;
4528
- if (event.data?.type === "PASSKEY_RENDERED" || event.data?.type === "PASSKEY_READY") {
4529
- hideOverlay();
4709
+ if (event.source !== iframe.contentWindow) return;
4710
+ if (event.data?.type === "PASSKEY_RENDERED") {
4711
+ revealIframe();
4712
+ } else if (event.data?.type === "PASSKEY_READY" && !revealed && readyFailsafe === void 0) {
4713
+ readyFailsafe = setTimeout(revealIframe, 1e3);
4530
4714
  }
4531
4715
  if (event.data?.type === "PASSKEY_DISCONNECT") {
4532
4716
  localStorage.removeItem("1auth-user");
@@ -4554,7 +4738,13 @@ var OneAuthClient = class {
4554
4738
  document.addEventListener("keydown", handleEscape);
4555
4739
  const reveal = () => {
4556
4740
  delete dialog.dataset.passkeyHidden;
4741
+ if (!revealed) {
4742
+ revealed = true;
4743
+ clearTimeout(readyFailsafe);
4744
+ clearTimeout(revealFailsafe);
4745
+ }
4557
4746
  iframe.style.opacity = "1";
4747
+ overlay.style.display = "none";
4558
4748
  };
4559
4749
  if (options?.hidden) {
4560
4750
  dialog.show();
@@ -4565,7 +4755,8 @@ var OneAuthClient = class {
4565
4755
  const destroy = () => {
4566
4756
  if (cleanedUp) return;
4567
4757
  cleanedUp = true;
4568
- clearTimeout(overlayFailsafe);
4758
+ clearTimeout(revealFailsafe);
4759
+ clearTimeout(readyFailsafe);
4569
4760
  inertObserver.disconnect();
4570
4761
  viewportInfoCleanup();
4571
4762
  window.removeEventListener("message", handleMessage);
@@ -4871,19 +5062,20 @@ var OneAuthClient = class {
4871
5062
  return;
4872
5063
  }
4873
5064
  if (data?.type === "PASSKEY_GRANT_PERMISSION_RESULT") {
4874
- teardown();
4875
- cleanup();
4876
5065
  if (data.success) {
5066
+ teardown();
4877
5067
  resolve({ success: true, ...data.data ?? {} });
4878
- } else {
4879
- resolve({
4880
- success: false,
4881
- error: data.error ?? data.data?.error ?? {
4882
- code: "GRANT_PERMISSION_FAILED",
4883
- message: "Permission grant failed"
4884
- }
4885
- });
5068
+ return;
4886
5069
  }
5070
+ teardown();
5071
+ cleanup();
5072
+ resolve({
5073
+ success: false,
5074
+ error: data.error ?? data.data?.error ?? {
5075
+ code: "GRANT_PERMISSION_FAILED",
5076
+ message: "Permission grant failed"
5077
+ }
5078
+ });
4887
5079
  } else if (data?.type === "PASSKEY_CLOSE") {
4888
5080
  teardown();
4889
5081
  cleanup();