@swype-org/react-sdk 0.1.49 → 0.1.50

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;
@@ -666,19 +672,18 @@ function isInCrossOriginIframe() {
666
672
  }
667
673
  }
668
674
  var delegationCounter = 0;
669
- var DELEGATION_CREATE_TIMEOUT_MS = 6e4;
670
675
  var DELEGATION_GET_TIMEOUT_MS = 3e4;
671
- function delegatePasskeyCreate(options) {
676
+ function delegatePasskeyGet(options) {
672
677
  return new Promise((resolve, reject) => {
673
- const id = `pc-${++delegationCounter}-${Date.now()}`;
678
+ const id = `pg-${++delegationCounter}-${Date.now()}`;
674
679
  const timer = setTimeout(() => {
675
680
  window.removeEventListener("message", handler);
676
- reject(new Error("Passkey creation timed out. Please try again."));
677
- }, DELEGATION_CREATE_TIMEOUT_MS);
681
+ reject(new Error("Passkey verification timed out. Please try again."));
682
+ }, DELEGATION_GET_TIMEOUT_MS);
678
683
  const handler = (event) => {
679
684
  const data = event.data;
680
685
  if (!data || typeof data !== "object") return;
681
- if (data.type !== "swype:passkey-create-response" || data.id !== id) return;
686
+ if (data.type !== "swype:passkey-get-response" || data.id !== id) return;
682
687
  clearTimeout(timer);
683
688
  window.removeEventListener("message", handler);
684
689
  if (data.error) {
@@ -686,36 +691,54 @@ function delegatePasskeyCreate(options) {
686
691
  } else if (data.result) {
687
692
  resolve(data.result);
688
693
  } else {
689
- reject(new Error("Invalid passkey create response."));
694
+ reject(new Error("Invalid passkey get response."));
690
695
  }
691
696
  };
692
697
  window.addEventListener("message", handler);
693
- window.parent.postMessage({ type: "swype:passkey-create-request", id, options }, "*");
698
+ window.parent.postMessage({ type: "swype:passkey-get-request", id, options }, "*");
694
699
  });
695
700
  }
696
- function delegatePasskeyGet(options) {
701
+ var POPUP_RESULT_TIMEOUT_MS = 12e4;
702
+ var POPUP_CLOSED_POLL_MS = 500;
703
+ function createPasskeyViaPopup(options) {
697
704
  return new Promise((resolve, reject) => {
698
- const id = `pg-${++delegationCounter}-${Date.now()}`;
705
+ const encoded = btoa(JSON.stringify(options));
706
+ const popupUrl = `${window.location.origin}/passkey-register#${encoded}`;
707
+ const popup = window.open(popupUrl, "swype-passkey", "width=460,height=600");
708
+ if (!popup) {
709
+ reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
710
+ return;
711
+ }
699
712
  const timer = setTimeout(() => {
700
- window.removeEventListener("message", handler);
701
- reject(new Error("Passkey verification timed out. Please try again."));
702
- }, DELEGATION_GET_TIMEOUT_MS);
713
+ cleanup();
714
+ reject(new Error("Passkey creation timed out. Please try again."));
715
+ }, POPUP_RESULT_TIMEOUT_MS);
716
+ const closedPoll = setInterval(() => {
717
+ if (popup.closed) {
718
+ cleanup();
719
+ reject(new Error("Passkey setup window was closed before completing."));
720
+ }
721
+ }, POPUP_CLOSED_POLL_MS);
703
722
  const handler = (event) => {
723
+ if (event.source !== popup) return;
704
724
  const data = event.data;
705
725
  if (!data || typeof data !== "object") return;
706
- if (data.type !== "swype:passkey-get-response" || data.id !== id) return;
707
- clearTimeout(timer);
708
- window.removeEventListener("message", handler);
726
+ if (data.type !== "swype:passkey-popup-result") return;
727
+ cleanup();
709
728
  if (data.error) {
710
729
  reject(new Error(data.error));
711
730
  } else if (data.result) {
712
731
  resolve(data.result);
713
732
  } else {
714
- reject(new Error("Invalid passkey get response."));
733
+ reject(new Error("Invalid passkey popup response."));
715
734
  }
716
735
  };
736
+ function cleanup() {
737
+ clearTimeout(timer);
738
+ clearInterval(closedPoll);
739
+ window.removeEventListener("message", handler);
740
+ }
717
741
  window.addEventListener("message", handler);
718
- window.parent.postMessage({ type: "swype:passkey-get-request", id, options }, "*");
719
742
  });
720
743
  }
721
744
 
@@ -840,53 +863,51 @@ async function createPasskeyCredential(params) {
840
863
  const challenge = new Uint8Array(32);
841
864
  crypto.getRandomValues(challenge);
842
865
  const rpId = resolvePasskeyRpId();
866
+ const publicKeyOptions = {
867
+ challenge,
868
+ rp: { name: "Swype", id: rpId },
869
+ user: {
870
+ id: new TextEncoder().encode(params.userId),
871
+ name: params.displayName,
872
+ displayName: params.displayName
873
+ },
874
+ pubKeyCredParams: [
875
+ { alg: -7, type: "public-key" },
876
+ { alg: -257, type: "public-key" }
877
+ ],
878
+ authenticatorSelection: {
879
+ authenticatorAttachment: "platform",
880
+ residentKey: "preferred",
881
+ userVerification: "required"
882
+ },
883
+ timeout: 6e4
884
+ };
843
885
  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
- });
886
+ try {
887
+ await waitForDocumentFocus();
888
+ const credential2 = await navigator.credentials.create({
889
+ publicKey: publicKeyOptions
890
+ });
891
+ if (!credential2) {
892
+ throw new Error("Passkey creation was cancelled.");
893
+ }
894
+ return extractPasskeyResult(credential2);
895
+ } catch (err) {
896
+ if (err instanceof PasskeyIframeBlockedError) throw err;
897
+ if (err instanceof Error && err.message === "Passkey creation was cancelled.") throw err;
898
+ throw new PasskeyIframeBlockedError();
899
+ }
862
900
  }
863
901
  await waitForDocumentFocus();
864
902
  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
- }
903
+ publicKey: publicKeyOptions
886
904
  });
887
905
  if (!credential) {
888
906
  throw new Error("Passkey creation was cancelled.");
889
907
  }
908
+ return extractPasskeyResult(credential);
909
+ }
910
+ function extractPasskeyResult(credential) {
890
911
  const response = credential.response;
891
912
  const publicKeyBytes = response.getPublicKey?.();
892
913
  return {
@@ -894,6 +915,29 @@ async function createPasskeyCredential(params) {
894
915
  publicKey: publicKeyBytes ? toBase64(publicKeyBytes) : ""
895
916
  };
896
917
  }
918
+ function buildPasskeyPopupOptions(params) {
919
+ const challenge = new Uint8Array(32);
920
+ crypto.getRandomValues(challenge);
921
+ const rpId = resolvePasskeyRpId();
922
+ return {
923
+ challenge: toBase64(challenge),
924
+ rpId,
925
+ rpName: "Swype",
926
+ userId: toBase64(new TextEncoder().encode(params.userId)),
927
+ userName: params.displayName,
928
+ userDisplayName: params.displayName,
929
+ pubKeyCredParams: [
930
+ { alg: -7, type: "public-key" },
931
+ { alg: -257, type: "public-key" }
932
+ ],
933
+ authenticatorSelection: {
934
+ authenticatorAttachment: "platform",
935
+ residentKey: "preferred",
936
+ userVerification: "required"
937
+ },
938
+ timeout: 6e4
939
+ };
940
+ }
897
941
  async function deviceHasPasskey(credentialId) {
898
942
  const found = await findDevicePasskey([credentialId]);
899
943
  return found != null;
@@ -2658,14 +2702,18 @@ function CreatePasskeyScreen({
2658
2702
  onCreatePasskey,
2659
2703
  onBack,
2660
2704
  creating,
2661
- error
2705
+ error,
2706
+ popupFallback = false,
2707
+ onCreatePasskeyViaPopup
2662
2708
  }) {
2663
2709
  const { tokens } = useSwypeConfig();
2710
+ const handleCreate = popupFallback && onCreatePasskeyViaPopup ? onCreatePasskeyViaPopup : onCreatePasskey;
2711
+ const buttonLabel = popupFallback ? "Open passkey setup" : "Create passkey";
2664
2712
  return /* @__PURE__ */ jsxs(
2665
2713
  ScreenLayout,
2666
2714
  {
2667
2715
  footer: /* @__PURE__ */ jsxs(Fragment, { children: [
2668
- /* @__PURE__ */ jsx(PrimaryButton, { onClick: onCreatePasskey, disabled: creating, loading: creating, children: "Create passkey" }),
2716
+ /* @__PURE__ */ jsx(PrimaryButton, { onClick: handleCreate, disabled: creating, loading: creating, children: buttonLabel }),
2669
2717
  /* @__PURE__ */ jsx(PoweredByFooter, {})
2670
2718
  ] }),
2671
2719
  children: [
@@ -2678,7 +2726,7 @@ function CreatePasskeyScreen({
2678
2726
  /* @__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
2727
  ] }) }),
2680
2728
  /* @__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." }),
2729
+ /* @__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
2730
  error && /* @__PURE__ */ jsx("div", { style: errorBannerStyle2(tokens), children: error }),
2683
2731
  /* @__PURE__ */ jsx(InfoBanner, { children: "Your passkey is stored securely on your device. Swype never sees your biometric data." })
2684
2732
  ] })
@@ -4131,6 +4179,7 @@ function SwypePaymentInner({
4131
4179
  const [transfer, setTransfer] = useState(null);
4132
4180
  const [creatingTransfer, setCreatingTransfer] = useState(false);
4133
4181
  const [registeringPasskey, setRegisteringPasskey] = useState(false);
4182
+ const [passkeyPopupNeeded, setPasskeyPopupNeeded] = useState(false);
4134
4183
  const [activeCredentialId, setActiveCredentialId] = useState(() => {
4135
4184
  if (typeof window === "undefined") return null;
4136
4185
  return window.localStorage.getItem(ACTIVE_CREDENTIAL_STORAGE_KEY);
@@ -4633,34 +4682,59 @@ function SwypePaymentInner({
4633
4682
  merchantAuthorization,
4634
4683
  transfer
4635
4684
  ]);
4685
+ const completePasskeyRegistration = useCallback(async (credentialId, publicKey) => {
4686
+ const token = await getAccessToken();
4687
+ if (!token) throw new Error("Not authenticated");
4688
+ await registerPasskey(apiBaseUrl, token, credentialId, publicKey);
4689
+ setActiveCredentialId(credentialId);
4690
+ window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
4691
+ setPasskeyPopupNeeded(false);
4692
+ const hasActiveWallet = accounts.some(
4693
+ (a) => a.wallets.some((w) => w.status === "ACTIVE")
4694
+ );
4695
+ if (accounts.length === 0 || !hasActiveWallet) {
4696
+ setStep("wallet-picker");
4697
+ } else {
4698
+ setStep("deposit");
4699
+ }
4700
+ }, [getAccessToken, apiBaseUrl, accounts]);
4636
4701
  const handleRegisterPasskey = useCallback(async () => {
4637
4702
  setRegisteringPasskey(true);
4638
4703
  setError(null);
4639
4704
  try {
4640
- const token = await getAccessToken();
4641
- if (!token) throw new Error("Not authenticated");
4642
4705
  const passkeyDisplayName = user?.email?.address ?? user?.google?.name ?? user?.id ?? "Swype User";
4643
4706
  const { credentialId, publicKey } = await createPasskeyCredential({
4644
4707
  userId: user?.id ?? "unknown",
4645
4708
  displayName: passkeyDisplayName
4646
4709
  });
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");
4710
+ await completePasskeyRegistration(credentialId, publicKey);
4711
+ } catch (err) {
4712
+ if (err instanceof PasskeyIframeBlockedError) {
4713
+ setPasskeyPopupNeeded(true);
4655
4714
  } else {
4656
- setStep("deposit");
4715
+ setError(err instanceof Error ? err.message : "Failed to register passkey");
4657
4716
  }
4717
+ } finally {
4718
+ setRegisteringPasskey(false);
4719
+ }
4720
+ }, [user, completePasskeyRegistration]);
4721
+ const handleCreatePasskeyViaPopup = useCallback(async () => {
4722
+ setRegisteringPasskey(true);
4723
+ setError(null);
4724
+ try {
4725
+ const passkeyDisplayName = user?.email?.address ?? user?.google?.name ?? user?.id ?? "Swype User";
4726
+ const popupOptions = buildPasskeyPopupOptions({
4727
+ userId: user?.id ?? "unknown",
4728
+ displayName: passkeyDisplayName
4729
+ });
4730
+ const { credentialId, publicKey } = await createPasskeyViaPopup(popupOptions);
4731
+ await completePasskeyRegistration(credentialId, publicKey);
4658
4732
  } catch (err) {
4659
4733
  setError(err instanceof Error ? err.message : "Failed to register passkey");
4660
4734
  } finally {
4661
4735
  setRegisteringPasskey(false);
4662
4736
  }
4663
- }, [getAccessToken, user, apiBaseUrl, accounts]);
4737
+ }, [user, completePasskeyRegistration]);
4664
4738
  const handleSelectProvider = useCallback((providerId) => {
4665
4739
  setSelectedProviderId(providerId);
4666
4740
  setSelectedAccountId(null);
@@ -4778,7 +4852,9 @@ function SwypePaymentInner({
4778
4852
  onCreatePasskey: handleRegisterPasskey,
4779
4853
  onBack: handleLogout,
4780
4854
  creating: registeringPasskey,
4781
- error
4855
+ error,
4856
+ popupFallback: passkeyPopupNeeded,
4857
+ onCreatePasskeyViaPopup: handleCreatePasskeyViaPopup
4782
4858
  }
4783
4859
  );
4784
4860
  }
@@ -4911,6 +4987,6 @@ function SwypePaymentInner({
4911
4987
  return null;
4912
4988
  }
4913
4989
 
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 };
4990
+ 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
4991
  //# sourceMappingURL=index.js.map
4916
4992
  //# sourceMappingURL=index.js.map