@swype-org/react-sdk 0.1.49 → 0.1.52

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.d.cts CHANGED
@@ -379,6 +379,59 @@ interface SwypePaymentProps {
379
379
  }
380
380
  declare function SwypePayment(props: SwypePaymentProps): react_jsx_runtime.JSX.Element;
381
381
 
382
+ /**
383
+ * Cross-origin iframe passkey delegation via postMessage.
384
+ *
385
+ * When the webview-app runs inside a cross-origin iframe, WebAuthn
386
+ * ceremonies cannot execute from within the iframe on Safari. For passkey
387
+ * creation, we first attempt a direct `navigator.credentials.create()`
388
+ * call (works in Chrome/Firefox with the iframe permissions policy). If
389
+ * that fails (Safari), we open a same-origin pop-up window on the Swype
390
+ * domain to perform the ceremony, then relay the result back via
391
+ * `postMessage`.
392
+ *
393
+ * Passkey *assertion* (`navigator.credentials.get`) still delegates to
394
+ * the parent page via postMessage as before.
395
+ */
396
+ /**
397
+ * Thrown when `navigator.credentials.create()` fails inside a
398
+ * cross-origin iframe (Safari). The UI layer should catch this and
399
+ * offer the user a button that opens the Swype passkey pop-up.
400
+ */
401
+ declare class PasskeyIframeBlockedError extends Error {
402
+ constructor(message?: string);
403
+ }
404
+ interface PasskeyPopupOptions {
405
+ challenge: string;
406
+ rpId: string;
407
+ rpName: string;
408
+ userId: string;
409
+ userName: string;
410
+ userDisplayName: string;
411
+ pubKeyCredParams: Array<{
412
+ alg: number;
413
+ type: string;
414
+ }>;
415
+ authenticatorSelection?: {
416
+ authenticatorAttachment?: string;
417
+ residentKey?: string;
418
+ userVerification?: string;
419
+ };
420
+ timeout?: number;
421
+ }
422
+ /**
423
+ * Opens a same-origin pop-up window on the Swype domain to perform
424
+ * passkey creation. Used as a fallback when Safari blocks
425
+ * `navigator.credentials.create()` inside a cross-origin iframe.
426
+ *
427
+ * Must be called from a user-gesture handler (e.g. button click) to
428
+ * avoid the browser's pop-up blocker.
429
+ */
430
+ declare function createPasskeyViaPopup(options: PasskeyPopupOptions): Promise<{
431
+ credentialId: string;
432
+ publicKey: string;
433
+ }>;
434
+
382
435
  type AccessTokenGetter = () => Promise<string | null | undefined>;
383
436
  /**
384
437
  * Creates a WebAuthn passkey credential.
@@ -398,6 +451,15 @@ declare function createPasskeyCredential(params: {
398
451
  credentialId: string;
399
452
  publicKey: string;
400
453
  }>;
454
+ /**
455
+ * Builds the {@link PasskeyPopupOptions} for a user. Called by the UI
456
+ * layer when it needs to open the passkey creation pop-up after
457
+ * `createPasskeyCredential` throws {@link PasskeyIframeBlockedError}.
458
+ */
459
+ declare function buildPasskeyPopupOptions(params: {
460
+ userId: string;
461
+ displayName: string;
462
+ }): PasskeyPopupOptions;
401
463
  /**
402
464
  * @deprecated Use {@link findDevicePasskey} instead, which checks all
403
465
  * credentials in a single WebAuthn call.
@@ -596,4 +658,4 @@ interface SelectSourceScreenProps {
596
658
  }
597
659
  declare function SelectSourceScreen({ choices, selectedChainName, selectedTokenSymbol, recommended, onChainChange, onTokenChange, onConfirm, onLogout, }: SelectSourceScreenProps): react_jsx_runtime.JSX.Element;
598
660
 
599
- export { type Account, type ActionExecutionResult, type AdvancedSettings, type Amount, type AuthorizationAction, type AuthorizationSession, type AuthorizationSessionDetail, type Chain, type Destination, type ErrorResponse, IconCircle, type ListResponse, type MerchantAuthorization, type MerchantPublicKey, OutlineButton, type PaymentStep, PoweredByFooter, PrimaryButton, type Provider, ScreenHeader, ScreenLayout, SelectSourceScreen, SettingsMenu, SetupScreen, type SourceOption, type SourceSelection, type SourceType, Spinner, type StepItem, StepList, SwypePayment, type SwypePaymentProps, SwypeProvider, type SwypeProviderProps, type ThemeMode, type ThemeTokens, type TokenBalance, type Transfer, type TransferDestination, type UserConfig, type Wallet, type WalletSource, type WalletToken, createPasskeyCredential, darkTheme, deviceHasPasskey, findDevicePasskey, getTheme, lightTheme, api as swypeApi, useAuthorizationExecutor, useSwypeConfig, useSwypeDepositAmount, useTransferPolling, useTransferSigning };
661
+ export { type Account, type ActionExecutionResult, type AdvancedSettings, type Amount, type AuthorizationAction, type AuthorizationSession, type AuthorizationSessionDetail, type Chain, type Destination, type ErrorResponse, IconCircle, type ListResponse, type MerchantAuthorization, type MerchantPublicKey, OutlineButton, PasskeyIframeBlockedError, type PaymentStep, PoweredByFooter, PrimaryButton, type Provider, ScreenHeader, ScreenLayout, SelectSourceScreen, SettingsMenu, SetupScreen, type SourceOption, type SourceSelection, type SourceType, Spinner, type StepItem, StepList, SwypePayment, type SwypePaymentProps, SwypeProvider, type SwypeProviderProps, type ThemeMode, type ThemeTokens, type TokenBalance, type Transfer, type TransferDestination, type UserConfig, type Wallet, type WalletSource, type WalletToken, buildPasskeyPopupOptions, createPasskeyCredential, createPasskeyViaPopup, darkTheme, deviceHasPasskey, findDevicePasskey, getTheme, lightTheme, api as swypeApi, useAuthorizationExecutor, useSwypeConfig, useSwypeDepositAmount, useTransferPolling, useTransferSigning };
package/dist/index.d.ts CHANGED
@@ -379,6 +379,59 @@ interface SwypePaymentProps {
379
379
  }
380
380
  declare function SwypePayment(props: SwypePaymentProps): react_jsx_runtime.JSX.Element;
381
381
 
382
+ /**
383
+ * Cross-origin iframe passkey delegation via postMessage.
384
+ *
385
+ * When the webview-app runs inside a cross-origin iframe, WebAuthn
386
+ * ceremonies cannot execute from within the iframe on Safari. For passkey
387
+ * creation, we first attempt a direct `navigator.credentials.create()`
388
+ * call (works in Chrome/Firefox with the iframe permissions policy). If
389
+ * that fails (Safari), we open a same-origin pop-up window on the Swype
390
+ * domain to perform the ceremony, then relay the result back via
391
+ * `postMessage`.
392
+ *
393
+ * Passkey *assertion* (`navigator.credentials.get`) still delegates to
394
+ * the parent page via postMessage as before.
395
+ */
396
+ /**
397
+ * Thrown when `navigator.credentials.create()` fails inside a
398
+ * cross-origin iframe (Safari). The UI layer should catch this and
399
+ * offer the user a button that opens the Swype passkey pop-up.
400
+ */
401
+ declare class PasskeyIframeBlockedError extends Error {
402
+ constructor(message?: string);
403
+ }
404
+ interface PasskeyPopupOptions {
405
+ challenge: string;
406
+ rpId: string;
407
+ rpName: string;
408
+ userId: string;
409
+ userName: string;
410
+ userDisplayName: string;
411
+ pubKeyCredParams: Array<{
412
+ alg: number;
413
+ type: string;
414
+ }>;
415
+ authenticatorSelection?: {
416
+ authenticatorAttachment?: string;
417
+ residentKey?: string;
418
+ userVerification?: string;
419
+ };
420
+ timeout?: number;
421
+ }
422
+ /**
423
+ * Opens a same-origin pop-up window on the Swype domain to perform
424
+ * passkey creation. Used as a fallback when Safari blocks
425
+ * `navigator.credentials.create()` inside a cross-origin iframe.
426
+ *
427
+ * Must be called from a user-gesture handler (e.g. button click) to
428
+ * avoid the browser's pop-up blocker.
429
+ */
430
+ declare function createPasskeyViaPopup(options: PasskeyPopupOptions): Promise<{
431
+ credentialId: string;
432
+ publicKey: string;
433
+ }>;
434
+
382
435
  type AccessTokenGetter = () => Promise<string | null | undefined>;
383
436
  /**
384
437
  * Creates a WebAuthn passkey credential.
@@ -398,6 +451,15 @@ declare function createPasskeyCredential(params: {
398
451
  credentialId: string;
399
452
  publicKey: string;
400
453
  }>;
454
+ /**
455
+ * Builds the {@link PasskeyPopupOptions} for a user. Called by the UI
456
+ * layer when it needs to open the passkey creation pop-up after
457
+ * `createPasskeyCredential` throws {@link PasskeyIframeBlockedError}.
458
+ */
459
+ declare function buildPasskeyPopupOptions(params: {
460
+ userId: string;
461
+ displayName: string;
462
+ }): PasskeyPopupOptions;
401
463
  /**
402
464
  * @deprecated Use {@link findDevicePasskey} instead, which checks all
403
465
  * credentials in a single WebAuthn call.
@@ -596,4 +658,4 @@ interface SelectSourceScreenProps {
596
658
  }
597
659
  declare function SelectSourceScreen({ choices, selectedChainName, selectedTokenSymbol, recommended, onChainChange, onTokenChange, onConfirm, onLogout, }: SelectSourceScreenProps): react_jsx_runtime.JSX.Element;
598
660
 
599
- export { type Account, type ActionExecutionResult, type AdvancedSettings, type Amount, type AuthorizationAction, type AuthorizationSession, type AuthorizationSessionDetail, type Chain, type Destination, type ErrorResponse, IconCircle, type ListResponse, type MerchantAuthorization, type MerchantPublicKey, OutlineButton, type PaymentStep, PoweredByFooter, PrimaryButton, type Provider, ScreenHeader, ScreenLayout, SelectSourceScreen, SettingsMenu, SetupScreen, type SourceOption, type SourceSelection, type SourceType, Spinner, type StepItem, StepList, SwypePayment, type SwypePaymentProps, SwypeProvider, type SwypeProviderProps, type ThemeMode, type ThemeTokens, type TokenBalance, type Transfer, type TransferDestination, type UserConfig, type Wallet, type WalletSource, type WalletToken, createPasskeyCredential, darkTheme, deviceHasPasskey, findDevicePasskey, getTheme, lightTheme, api as swypeApi, useAuthorizationExecutor, useSwypeConfig, useSwypeDepositAmount, useTransferPolling, useTransferSigning };
661
+ export { type Account, type ActionExecutionResult, type AdvancedSettings, type Amount, type AuthorizationAction, type AuthorizationSession, type AuthorizationSessionDetail, type Chain, type Destination, type ErrorResponse, IconCircle, type ListResponse, type MerchantAuthorization, type MerchantPublicKey, OutlineButton, PasskeyIframeBlockedError, type PaymentStep, PoweredByFooter, PrimaryButton, type Provider, ScreenHeader, ScreenLayout, SelectSourceScreen, SettingsMenu, SetupScreen, type SourceOption, type SourceSelection, type SourceType, Spinner, type StepItem, StepList, SwypePayment, type SwypePaymentProps, SwypeProvider, type SwypeProviderProps, type ThemeMode, type ThemeTokens, type TokenBalance, type Transfer, type TransferDestination, type UserConfig, type Wallet, type WalletSource, type WalletToken, buildPasskeyPopupOptions, createPasskeyCredential, createPasskeyViaPopup, darkTheme, deviceHasPasskey, findDevicePasskey, getTheme, lightTheme, api as swypeApi, useAuthorizationExecutor, useSwypeConfig, useSwypeDepositAmount, useTransferPolling, useTransferSigning };
package/dist/index.js CHANGED
@@ -655,6 +655,12 @@ function normalizeSignature(sig) {
655
655
  }
656
656
 
657
657
  // src/passkey-delegation.ts
658
+ var PasskeyIframeBlockedError = class extends Error {
659
+ constructor(message = "Passkey creation is not supported in this browser context.") {
660
+ super(message);
661
+ this.name = "PasskeyIframeBlockedError";
662
+ }
663
+ };
658
664
  function isInCrossOriginIframe() {
659
665
  if (typeof window === "undefined") return false;
660
666
  if (window.parent === window) return false;
@@ -665,57 +671,47 @@ function isInCrossOriginIframe() {
665
671
  return true;
666
672
  }
667
673
  }
668
- var delegationCounter = 0;
669
- var DELEGATION_CREATE_TIMEOUT_MS = 6e4;
670
- var DELEGATION_GET_TIMEOUT_MS = 3e4;
671
- function delegatePasskeyCreate(options) {
674
+ var POPUP_RESULT_TIMEOUT_MS = 12e4;
675
+ var POPUP_CLOSED_POLL_MS = 500;
676
+ function createPasskeyViaPopup(options) {
672
677
  return new Promise((resolve, reject) => {
673
- const id = `pc-${++delegationCounter}-${Date.now()}`;
678
+ const encoded = btoa(JSON.stringify(options));
679
+ const popupUrl = `${window.location.origin}/passkey-register#${encoded}`;
680
+ const popup = window.open(popupUrl, "swype-passkey", "width=460,height=600");
681
+ if (!popup) {
682
+ reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
683
+ return;
684
+ }
674
685
  const timer = setTimeout(() => {
675
- window.removeEventListener("message", handler);
686
+ cleanup();
676
687
  reject(new Error("Passkey creation timed out. Please try again."));
677
- }, DELEGATION_CREATE_TIMEOUT_MS);
688
+ }, POPUP_RESULT_TIMEOUT_MS);
689
+ const closedPoll = setInterval(() => {
690
+ if (popup.closed) {
691
+ cleanup();
692
+ reject(new Error("Passkey setup window was closed before completing."));
693
+ }
694
+ }, POPUP_CLOSED_POLL_MS);
678
695
  const handler = (event) => {
696
+ if (event.source !== popup) return;
679
697
  const data = event.data;
680
698
  if (!data || typeof data !== "object") return;
681
- if (data.type !== "swype:passkey-create-response" || data.id !== id) return;
682
- clearTimeout(timer);
683
- window.removeEventListener("message", handler);
699
+ if (data.type !== "swype:passkey-popup-result") return;
700
+ cleanup();
684
701
  if (data.error) {
685
702
  reject(new Error(data.error));
686
703
  } else if (data.result) {
687
704
  resolve(data.result);
688
705
  } else {
689
- reject(new Error("Invalid passkey create response."));
706
+ reject(new Error("Invalid passkey popup response."));
690
707
  }
691
708
  };
692
- window.addEventListener("message", handler);
693
- window.parent.postMessage({ type: "swype:passkey-create-request", id, options }, "*");
694
- });
695
- }
696
- function delegatePasskeyGet(options) {
697
- return new Promise((resolve, reject) => {
698
- const id = `pg-${++delegationCounter}-${Date.now()}`;
699
- const timer = setTimeout(() => {
700
- window.removeEventListener("message", handler);
701
- reject(new Error("Passkey verification timed out. Please try again."));
702
- }, DELEGATION_GET_TIMEOUT_MS);
703
- const handler = (event) => {
704
- const data = event.data;
705
- if (!data || typeof data !== "object") return;
706
- if (data.type !== "swype:passkey-get-response" || data.id !== id) return;
709
+ function cleanup() {
707
710
  clearTimeout(timer);
711
+ clearInterval(closedPoll);
708
712
  window.removeEventListener("message", handler);
709
- if (data.error) {
710
- reject(new Error(data.error));
711
- } else if (data.result) {
712
- resolve(data.result);
713
- } else {
714
- reject(new Error("Invalid passkey get response."));
715
- }
716
- };
713
+ }
717
714
  window.addEventListener("message", handler);
718
- window.parent.postMessage({ type: "swype:passkey-get-request", id, options }, "*");
719
715
  });
720
716
  }
721
717
 
@@ -840,53 +836,51 @@ async function createPasskeyCredential(params) {
840
836
  const challenge = new Uint8Array(32);
841
837
  crypto.getRandomValues(challenge);
842
838
  const rpId = resolvePasskeyRpId();
839
+ const publicKeyOptions = {
840
+ challenge,
841
+ rp: { name: "Swype", id: rpId },
842
+ user: {
843
+ id: new TextEncoder().encode(params.userId),
844
+ name: params.displayName,
845
+ displayName: params.displayName
846
+ },
847
+ pubKeyCredParams: [
848
+ { alg: -7, type: "public-key" },
849
+ { alg: -257, type: "public-key" }
850
+ ],
851
+ authenticatorSelection: {
852
+ authenticatorAttachment: "platform",
853
+ residentKey: "preferred",
854
+ userVerification: "required"
855
+ },
856
+ timeout: 6e4
857
+ };
843
858
  if (isInCrossOriginIframe()) {
844
- return delegatePasskeyCreate({
845
- challenge: toBase64(challenge),
846
- rpId,
847
- rpName: "Swype",
848
- userId: toBase64(new TextEncoder().encode(params.userId)),
849
- userName: params.displayName,
850
- userDisplayName: params.displayName,
851
- pubKeyCredParams: [
852
- { alg: -7, type: "public-key" },
853
- { alg: -257, type: "public-key" }
854
- ],
855
- authenticatorSelection: {
856
- authenticatorAttachment: "platform",
857
- residentKey: "preferred",
858
- userVerification: "required"
859
- },
860
- timeout: 6e4
861
- });
859
+ try {
860
+ await waitForDocumentFocus();
861
+ const credential2 = await navigator.credentials.create({
862
+ publicKey: publicKeyOptions
863
+ });
864
+ if (!credential2) {
865
+ throw new Error("Passkey creation was cancelled.");
866
+ }
867
+ return extractPasskeyResult(credential2);
868
+ } catch (err) {
869
+ if (err instanceof PasskeyIframeBlockedError) throw err;
870
+ if (err instanceof Error && err.message === "Passkey creation was cancelled.") throw err;
871
+ throw new PasskeyIframeBlockedError();
872
+ }
862
873
  }
863
874
  await waitForDocumentFocus();
864
875
  const credential = await navigator.credentials.create({
865
- publicKey: {
866
- challenge,
867
- rp: { name: "Swype", id: rpId },
868
- user: {
869
- id: new TextEncoder().encode(params.userId),
870
- name: params.displayName,
871
- displayName: params.displayName
872
- },
873
- pubKeyCredParams: [
874
- { alg: -7, type: "public-key" },
875
- // ES256 (P-256)
876
- { alg: -257, type: "public-key" }
877
- // RS256
878
- ],
879
- authenticatorSelection: {
880
- authenticatorAttachment: "platform",
881
- residentKey: "preferred",
882
- userVerification: "required"
883
- },
884
- timeout: 6e4
885
- }
876
+ publicKey: publicKeyOptions
886
877
  });
887
878
  if (!credential) {
888
879
  throw new Error("Passkey creation was cancelled.");
889
880
  }
881
+ return extractPasskeyResult(credential);
882
+ }
883
+ function extractPasskeyResult(credential) {
890
884
  const response = credential.response;
891
885
  const publicKeyBytes = response.getPublicKey?.();
892
886
  return {
@@ -894,6 +888,29 @@ async function createPasskeyCredential(params) {
894
888
  publicKey: publicKeyBytes ? toBase64(publicKeyBytes) : ""
895
889
  };
896
890
  }
891
+ function buildPasskeyPopupOptions(params) {
892
+ const challenge = new Uint8Array(32);
893
+ crypto.getRandomValues(challenge);
894
+ const rpId = resolvePasskeyRpId();
895
+ return {
896
+ challenge: toBase64(challenge),
897
+ rpId,
898
+ rpName: "Swype",
899
+ userId: toBase64(new TextEncoder().encode(params.userId)),
900
+ userName: params.displayName,
901
+ userDisplayName: params.displayName,
902
+ pubKeyCredParams: [
903
+ { alg: -7, type: "public-key" },
904
+ { alg: -257, type: "public-key" }
905
+ ],
906
+ authenticatorSelection: {
907
+ authenticatorAttachment: "platform",
908
+ residentKey: "preferred",
909
+ userVerification: "required"
910
+ },
911
+ timeout: 6e4
912
+ };
913
+ }
897
914
  async function deviceHasPasskey(credentialId) {
898
915
  const found = await findDevicePasskey([credentialId]);
899
916
  return found != null;
@@ -903,16 +920,6 @@ async function findDevicePasskey(credentialIds) {
903
920
  try {
904
921
  const challenge = new Uint8Array(32);
905
922
  crypto.getRandomValues(challenge);
906
- if (isInCrossOriginIframe()) {
907
- const result = await delegatePasskeyGet({
908
- challenge: toBase64(challenge),
909
- rpId: resolvePasskeyRpId(),
910
- allowCredentials: credentialIds.map((id) => ({ type: "public-key", id })),
911
- userVerification: "discouraged",
912
- timeout: 3e4
913
- });
914
- return result.credentialId;
915
- }
916
923
  await waitForDocumentFocus();
917
924
  const assertion = await navigator.credentials.get({
918
925
  publicKey: {
@@ -1411,48 +1418,31 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
1411
1418
  }
1412
1419
  const hashBytes = hexToBytes(payload.userOpHash);
1413
1420
  let signedUserOp;
1414
- if (isInCrossOriginIframe()) {
1415
- const delegatedResult = await delegatePasskeyGet({
1416
- challenge: toBase64(hashBytes),
1421
+ const allowCredentials = payload.passkeyCredentialId ? [{
1422
+ type: "public-key",
1423
+ id: base64ToBytes(payload.passkeyCredentialId)
1424
+ }] : void 0;
1425
+ await waitForDocumentFocus();
1426
+ const assertion = await navigator.credentials.get({
1427
+ publicKey: {
1428
+ challenge: hashBytes,
1417
1429
  rpId: resolvePasskeyRpId(),
1418
- allowCredentials: payload.passkeyCredentialId ? [{ type: "public-key", id: payload.passkeyCredentialId }] : void 0,
1430
+ allowCredentials,
1419
1431
  userVerification: "required",
1420
1432
  timeout: 6e4
1421
- });
1422
- signedUserOp = {
1423
- ...payload.userOp,
1424
- credentialId: delegatedResult.credentialId,
1425
- signature: delegatedResult.signature,
1426
- authenticatorData: delegatedResult.authenticatorData,
1427
- clientDataJSON: delegatedResult.clientDataJSON
1428
- };
1429
- } else {
1430
- const allowCredentials = payload.passkeyCredentialId ? [{
1431
- type: "public-key",
1432
- id: base64ToBytes(payload.passkeyCredentialId)
1433
- }] : void 0;
1434
- await waitForDocumentFocus();
1435
- const assertion = await navigator.credentials.get({
1436
- publicKey: {
1437
- challenge: hashBytes,
1438
- rpId: resolvePasskeyRpId(),
1439
- allowCredentials,
1440
- userVerification: "required",
1441
- timeout: 6e4
1442
- }
1443
- });
1444
- if (!assertion) {
1445
- throw new Error("Passkey authentication was cancelled.");
1446
1433
  }
1447
- const response = assertion.response;
1448
- signedUserOp = {
1449
- ...payload.userOp,
1450
- credentialId: toBase64(assertion.rawId),
1451
- signature: toBase64(response.signature),
1452
- authenticatorData: toBase64(response.authenticatorData),
1453
- clientDataJSON: toBase64(response.clientDataJSON)
1454
- };
1434
+ });
1435
+ if (!assertion) {
1436
+ throw new Error("Passkey authentication was cancelled.");
1455
1437
  }
1438
+ const response = assertion.response;
1439
+ signedUserOp = {
1440
+ ...payload.userOp,
1441
+ credentialId: toBase64(assertion.rawId),
1442
+ signature: toBase64(response.signature),
1443
+ authenticatorData: toBase64(response.authenticatorData),
1444
+ clientDataJSON: toBase64(response.clientDataJSON)
1445
+ };
1456
1446
  return await signTransfer(
1457
1447
  apiBaseUrl,
1458
1448
  token ?? "",
@@ -2658,14 +2648,18 @@ function CreatePasskeyScreen({
2658
2648
  onCreatePasskey,
2659
2649
  onBack,
2660
2650
  creating,
2661
- error
2651
+ error,
2652
+ popupFallback = false,
2653
+ onCreatePasskeyViaPopup
2662
2654
  }) {
2663
2655
  const { tokens } = useSwypeConfig();
2656
+ const handleCreate = popupFallback && onCreatePasskeyViaPopup ? onCreatePasskeyViaPopup : onCreatePasskey;
2657
+ const buttonLabel = popupFallback ? "Open passkey setup" : "Create passkey";
2664
2658
  return /* @__PURE__ */ jsxs(
2665
2659
  ScreenLayout,
2666
2660
  {
2667
2661
  footer: /* @__PURE__ */ jsxs(Fragment, { children: [
2668
- /* @__PURE__ */ jsx(PrimaryButton, { onClick: onCreatePasskey, disabled: creating, loading: creating, children: "Create passkey" }),
2662
+ /* @__PURE__ */ jsx(PrimaryButton, { onClick: handleCreate, disabled: creating, loading: creating, children: buttonLabel }),
2669
2663
  /* @__PURE__ */ jsx(PoweredByFooter, {})
2670
2664
  ] }),
2671
2665
  children: [
@@ -2678,7 +2672,7 @@ function CreatePasskeyScreen({
2678
2672
  /* @__PURE__ */ jsx("path", { d: "M9 14c0 1.5 1.34 2.5 3 2.5s3-1 3-2.5", stroke: tokens.accent, strokeWidth: "1.2", strokeLinecap: "round" })
2679
2673
  ] }) }),
2680
2674
  /* @__PURE__ */ jsx("h2", { style: headingStyle3(tokens.text), children: "Create your passkey" }),
2681
- /* @__PURE__ */ jsx("p", { style: subtitleStyle3(tokens.textSecondary), children: "Use Face ID to sign in instantly \u2014 no passwords, no codes." }),
2675
+ /* @__PURE__ */ jsx("p", { style: subtitleStyle3(tokens.textSecondary), children: popupFallback ? "Your browser requires a separate window for passkey setup. Tap the button below to continue." : "Use Face ID to sign in instantly \u2014 no passwords, no codes." }),
2682
2676
  error && /* @__PURE__ */ jsx("div", { style: errorBannerStyle2(tokens), children: error }),
2683
2677
  /* @__PURE__ */ jsx(InfoBanner, { children: "Your passkey is stored securely on your device. Swype never sees your biometric data." })
2684
2678
  ] })
@@ -3863,7 +3857,7 @@ function OpenWalletScreen({
3863
3857
  const logoSrc = walletName ? KNOWN_LOGOS[walletName.toLowerCase()] : void 0;
3864
3858
  const handleOpen = useCallback(() => {
3865
3859
  const opened = window.open(deeplinkUri, "_blank");
3866
- if (!opened) {
3860
+ if (!opened && window === window.parent) {
3867
3861
  window.location.href = deeplinkUri;
3868
3862
  }
3869
3863
  }, [deeplinkUri]);
@@ -4131,6 +4125,7 @@ function SwypePaymentInner({
4131
4125
  const [transfer, setTransfer] = useState(null);
4132
4126
  const [creatingTransfer, setCreatingTransfer] = useState(false);
4133
4127
  const [registeringPasskey, setRegisteringPasskey] = useState(false);
4128
+ const [passkeyPopupNeeded, setPasskeyPopupNeeded] = useState(false);
4134
4129
  const [activeCredentialId, setActiveCredentialId] = useState(() => {
4135
4130
  if (typeof window === "undefined") return null;
4136
4131
  return window.localStorage.getItem(ACTIVE_CREDENTIAL_STORAGE_KEY);
@@ -4633,34 +4628,59 @@ function SwypePaymentInner({
4633
4628
  merchantAuthorization,
4634
4629
  transfer
4635
4630
  ]);
4631
+ const completePasskeyRegistration = useCallback(async (credentialId, publicKey) => {
4632
+ const token = await getAccessToken();
4633
+ if (!token) throw new Error("Not authenticated");
4634
+ await registerPasskey(apiBaseUrl, token, credentialId, publicKey);
4635
+ setActiveCredentialId(credentialId);
4636
+ window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
4637
+ setPasskeyPopupNeeded(false);
4638
+ const hasActiveWallet = accounts.some(
4639
+ (a) => a.wallets.some((w) => w.status === "ACTIVE")
4640
+ );
4641
+ if (accounts.length === 0 || !hasActiveWallet) {
4642
+ setStep("wallet-picker");
4643
+ } else {
4644
+ setStep("deposit");
4645
+ }
4646
+ }, [getAccessToken, apiBaseUrl, accounts]);
4636
4647
  const handleRegisterPasskey = useCallback(async () => {
4637
4648
  setRegisteringPasskey(true);
4638
4649
  setError(null);
4639
4650
  try {
4640
- const token = await getAccessToken();
4641
- if (!token) throw new Error("Not authenticated");
4642
4651
  const passkeyDisplayName = user?.email?.address ?? user?.google?.name ?? user?.id ?? "Swype User";
4643
4652
  const { credentialId, publicKey } = await createPasskeyCredential({
4644
4653
  userId: user?.id ?? "unknown",
4645
4654
  displayName: passkeyDisplayName
4646
4655
  });
4647
- await registerPasskey(apiBaseUrl, token, credentialId, publicKey);
4648
- setActiveCredentialId(credentialId);
4649
- window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
4650
- const hasActiveWallet = accounts.some(
4651
- (a) => a.wallets.some((w) => w.status === "ACTIVE")
4652
- );
4653
- if (accounts.length === 0 || !hasActiveWallet) {
4654
- setStep("wallet-picker");
4656
+ await completePasskeyRegistration(credentialId, publicKey);
4657
+ } catch (err) {
4658
+ if (err instanceof PasskeyIframeBlockedError) {
4659
+ setPasskeyPopupNeeded(true);
4655
4660
  } else {
4656
- setStep("deposit");
4661
+ setError(err instanceof Error ? err.message : "Failed to register passkey");
4657
4662
  }
4663
+ } finally {
4664
+ setRegisteringPasskey(false);
4665
+ }
4666
+ }, [user, completePasskeyRegistration]);
4667
+ const handleCreatePasskeyViaPopup = useCallback(async () => {
4668
+ setRegisteringPasskey(true);
4669
+ setError(null);
4670
+ try {
4671
+ const passkeyDisplayName = user?.email?.address ?? user?.google?.name ?? user?.id ?? "Swype User";
4672
+ const popupOptions = buildPasskeyPopupOptions({
4673
+ userId: user?.id ?? "unknown",
4674
+ displayName: passkeyDisplayName
4675
+ });
4676
+ const { credentialId, publicKey } = await createPasskeyViaPopup(popupOptions);
4677
+ await completePasskeyRegistration(credentialId, publicKey);
4658
4678
  } catch (err) {
4659
4679
  setError(err instanceof Error ? err.message : "Failed to register passkey");
4660
4680
  } finally {
4661
4681
  setRegisteringPasskey(false);
4662
4682
  }
4663
- }, [getAccessToken, user, apiBaseUrl, accounts]);
4683
+ }, [user, completePasskeyRegistration]);
4664
4684
  const handleSelectProvider = useCallback((providerId) => {
4665
4685
  setSelectedProviderId(providerId);
4666
4686
  setSelectedAccountId(null);
@@ -4778,7 +4798,9 @@ function SwypePaymentInner({
4778
4798
  onCreatePasskey: handleRegisterPasskey,
4779
4799
  onBack: handleLogout,
4780
4800
  creating: registeringPasskey,
4781
- error
4801
+ error,
4802
+ popupFallback: passkeyPopupNeeded,
4803
+ onCreatePasskeyViaPopup: handleCreatePasskeyViaPopup
4782
4804
  }
4783
4805
  );
4784
4806
  }
@@ -4911,6 +4933,6 @@ function SwypePaymentInner({
4911
4933
  return null;
4912
4934
  }
4913
4935
 
4914
- export { IconCircle, OutlineButton, PoweredByFooter, PrimaryButton, ScreenHeader, ScreenLayout, SelectSourceScreen, SettingsMenu, SetupScreen, Spinner, StepList, SwypePayment, SwypeProvider, createPasskeyCredential, darkTheme, deviceHasPasskey, findDevicePasskey, getTheme, lightTheme, api_exports as swypeApi, useAuthorizationExecutor, useSwypeConfig, useSwypeDepositAmount, useTransferPolling, useTransferSigning };
4936
+ export { IconCircle, OutlineButton, PasskeyIframeBlockedError, PoweredByFooter, PrimaryButton, ScreenHeader, ScreenLayout, SelectSourceScreen, SettingsMenu, SetupScreen, Spinner, StepList, SwypePayment, SwypeProvider, buildPasskeyPopupOptions, createPasskeyCredential, createPasskeyViaPopup, darkTheme, deviceHasPasskey, findDevicePasskey, getTheme, lightTheme, api_exports as swypeApi, useAuthorizationExecutor, useSwypeConfig, useSwypeDepositAmount, useTransferPolling, useTransferSigning };
4915
4937
  //# sourceMappingURL=index.js.map
4916
4938
  //# sourceMappingURL=index.js.map