@swype-org/react-sdk 0.1.48 → 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
@@ -377,7 +377,60 @@ interface SwypePaymentProps {
377
377
  /** Seconds to count down on the success screen before auto-dismissing. */
378
378
  autoCloseSeconds?: number;
379
379
  }
380
- declare function SwypePayment({ destination, onComplete, onError, useWalletConnector, idempotencyKey, merchantAuthorization, merchantName, onBack, onDismiss, autoCloseSeconds, }: SwypePaymentProps): react_jsx_runtime.JSX.Element | null;
380
+ declare function SwypePayment(props: SwypePaymentProps): react_jsx_runtime.JSX.Element;
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
+ }>;
381
434
 
382
435
  type AccessTokenGetter = () => Promise<string | null | undefined>;
383
436
  /**
@@ -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
@@ -377,7 +377,60 @@ interface SwypePaymentProps {
377
377
  /** Seconds to count down on the success screen before auto-dismissing. */
378
378
  autoCloseSeconds?: number;
379
379
  }
380
- declare function SwypePayment({ destination, onComplete, onError, useWalletConnector, idempotencyKey, merchantAuthorization, merchantName, onBack, onDismiss, autoCloseSeconds, }: SwypePaymentProps): react_jsx_runtime.JSX.Element | null;
380
+ declare function SwypePayment(props: SwypePaymentProps): react_jsx_runtime.JSX.Element;
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
+ }>;
381
434
 
382
435
  type AccessTokenGetter = () => Promise<string | null | undefined>;
383
436
  /**
@@ -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
@@ -1,4 +1,4 @@
1
- import { createContext, useRef, useState, useCallback, useMemo, useContext, useEffect } from 'react';
1
+ import { createContext, useRef, useState, useCallback, useMemo, useContext, useEffect, Component } from 'react';
2
2
  import { PrivyProvider, usePrivy, useLoginWithEmail, useLoginWithSms, useLoginWithOAuth } from '@privy-io/react-auth';
3
3
  import { createConfig, http, WagmiProvider, useConfig, useConnect, useSwitchChain } from 'wagmi';
4
4
  import { mainnet, arbitrum, base } from 'wagmi/chains';
@@ -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,44 +672,73 @@ function isInCrossOriginIframe() {
666
672
  }
667
673
  }
668
674
  var delegationCounter = 0;
669
- function delegatePasskeyCreate(options) {
675
+ var DELEGATION_GET_TIMEOUT_MS = 3e4;
676
+ function delegatePasskeyGet(options) {
670
677
  return new Promise((resolve, reject) => {
671
- const id = `pc-${++delegationCounter}-${Date.now()}`;
678
+ const id = `pg-${++delegationCounter}-${Date.now()}`;
679
+ const timer = setTimeout(() => {
680
+ window.removeEventListener("message", handler);
681
+ reject(new Error("Passkey verification timed out. Please try again."));
682
+ }, DELEGATION_GET_TIMEOUT_MS);
672
683
  const handler = (event) => {
673
684
  const data = event.data;
674
685
  if (!data || typeof data !== "object") return;
675
- if (data.type !== "swype:passkey-create-response" || data.id !== id) return;
686
+ if (data.type !== "swype:passkey-get-response" || data.id !== id) return;
687
+ clearTimeout(timer);
676
688
  window.removeEventListener("message", handler);
677
689
  if (data.error) {
678
690
  reject(new Error(data.error));
679
691
  } else if (data.result) {
680
692
  resolve(data.result);
681
693
  } else {
682
- reject(new Error("Invalid passkey create response."));
694
+ reject(new Error("Invalid passkey get response."));
683
695
  }
684
696
  };
685
697
  window.addEventListener("message", handler);
686
- window.parent.postMessage({ type: "swype:passkey-create-request", id, options }, "*");
698
+ window.parent.postMessage({ type: "swype:passkey-get-request", id, options }, "*");
687
699
  });
688
700
  }
689
- function delegatePasskeyGet(options) {
701
+ var POPUP_RESULT_TIMEOUT_MS = 12e4;
702
+ var POPUP_CLOSED_POLL_MS = 500;
703
+ function createPasskeyViaPopup(options) {
690
704
  return new Promise((resolve, reject) => {
691
- 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
+ }
712
+ const timer = setTimeout(() => {
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);
692
722
  const handler = (event) => {
723
+ if (event.source !== popup) return;
693
724
  const data = event.data;
694
725
  if (!data || typeof data !== "object") return;
695
- if (data.type !== "swype:passkey-get-response" || data.id !== id) return;
696
- window.removeEventListener("message", handler);
726
+ if (data.type !== "swype:passkey-popup-result") return;
727
+ cleanup();
697
728
  if (data.error) {
698
729
  reject(new Error(data.error));
699
730
  } else if (data.result) {
700
731
  resolve(data.result);
701
732
  } else {
702
- reject(new Error("Invalid passkey get response."));
733
+ reject(new Error("Invalid passkey popup response."));
703
734
  }
704
735
  };
736
+ function cleanup() {
737
+ clearTimeout(timer);
738
+ clearInterval(closedPoll);
739
+ window.removeEventListener("message", handler);
740
+ }
705
741
  window.addEventListener("message", handler);
706
- window.parent.postMessage({ type: "swype:passkey-get-request", id, options }, "*");
707
742
  });
708
743
  }
709
744
 
@@ -828,53 +863,51 @@ async function createPasskeyCredential(params) {
828
863
  const challenge = new Uint8Array(32);
829
864
  crypto.getRandomValues(challenge);
830
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
+ };
831
885
  if (isInCrossOriginIframe()) {
832
- return delegatePasskeyCreate({
833
- challenge: toBase64(challenge),
834
- rpId,
835
- rpName: "Swype",
836
- userId: toBase64(new TextEncoder().encode(params.userId)),
837
- userName: params.displayName,
838
- userDisplayName: params.displayName,
839
- pubKeyCredParams: [
840
- { alg: -7, type: "public-key" },
841
- { alg: -257, type: "public-key" }
842
- ],
843
- authenticatorSelection: {
844
- authenticatorAttachment: "platform",
845
- residentKey: "preferred",
846
- userVerification: "required"
847
- },
848
- timeout: 6e4
849
- });
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
+ }
850
900
  }
851
901
  await waitForDocumentFocus();
852
902
  const credential = await navigator.credentials.create({
853
- publicKey: {
854
- challenge,
855
- rp: { name: "Swype", id: rpId },
856
- user: {
857
- id: new TextEncoder().encode(params.userId),
858
- name: params.displayName,
859
- displayName: params.displayName
860
- },
861
- pubKeyCredParams: [
862
- { alg: -7, type: "public-key" },
863
- // ES256 (P-256)
864
- { alg: -257, type: "public-key" }
865
- // RS256
866
- ],
867
- authenticatorSelection: {
868
- authenticatorAttachment: "platform",
869
- residentKey: "preferred",
870
- userVerification: "required"
871
- },
872
- timeout: 6e4
873
- }
903
+ publicKey: publicKeyOptions
874
904
  });
875
905
  if (!credential) {
876
906
  throw new Error("Passkey creation was cancelled.");
877
907
  }
908
+ return extractPasskeyResult(credential);
909
+ }
910
+ function extractPasskeyResult(credential) {
878
911
  const response = credential.response;
879
912
  const publicKeyBytes = response.getPublicKey?.();
880
913
  return {
@@ -882,6 +915,29 @@ async function createPasskeyCredential(params) {
882
915
  publicKey: publicKeyBytes ? toBase64(publicKeyBytes) : ""
883
916
  };
884
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
+ }
885
941
  async function deviceHasPasskey(credentialId) {
886
942
  const found = await findDevicePasskey([credentialId]);
887
943
  return found != null;
@@ -2644,18 +2700,20 @@ var hintStyle = (color) => ({
2644
2700
  });
2645
2701
  function CreatePasskeyScreen({
2646
2702
  onCreatePasskey,
2647
- onSkip,
2648
2703
  onBack,
2649
2704
  creating,
2650
- error
2705
+ error,
2706
+ popupFallback = false,
2707
+ onCreatePasskeyViaPopup
2651
2708
  }) {
2652
2709
  const { tokens } = useSwypeConfig();
2710
+ const handleCreate = popupFallback && onCreatePasskeyViaPopup ? onCreatePasskeyViaPopup : onCreatePasskey;
2711
+ const buttonLabel = popupFallback ? "Open passkey setup" : "Create passkey";
2653
2712
  return /* @__PURE__ */ jsxs(
2654
2713
  ScreenLayout,
2655
2714
  {
2656
2715
  footer: /* @__PURE__ */ jsxs(Fragment, { children: [
2657
- /* @__PURE__ */ jsx(PrimaryButton, { onClick: onCreatePasskey, disabled: creating, loading: creating, children: "Create passkey" }),
2658
- /* @__PURE__ */ jsx("button", { type: "button", onClick: onSkip, style: skipStyle(tokens.textMuted), disabled: creating, children: "Skip for now" }),
2716
+ /* @__PURE__ */ jsx(PrimaryButton, { onClick: handleCreate, disabled: creating, loading: creating, children: buttonLabel }),
2659
2717
  /* @__PURE__ */ jsx(PoweredByFooter, {})
2660
2718
  ] }),
2661
2719
  children: [
@@ -2668,7 +2726,7 @@ function CreatePasskeyScreen({
2668
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" })
2669
2727
  ] }) }),
2670
2728
  /* @__PURE__ */ jsx("h2", { style: headingStyle3(tokens.text), children: "Create your passkey" }),
2671
- /* @__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." }),
2672
2730
  error && /* @__PURE__ */ jsx("div", { style: errorBannerStyle2(tokens), children: error }),
2673
2731
  /* @__PURE__ */ jsx(InfoBanner, { children: "Your passkey is stored securely on your device. Swype never sees your biometric data." })
2674
2732
  ] })
@@ -2710,19 +2768,6 @@ var errorBannerStyle2 = (tokens) => ({
2710
2768
  width: "100%",
2711
2769
  textAlign: "left"
2712
2770
  });
2713
- var skipStyle = (color) => ({
2714
- background: "transparent",
2715
- border: "none",
2716
- color,
2717
- cursor: "pointer",
2718
- fontFamily: "inherit",
2719
- fontSize: "0.88rem",
2720
- fontWeight: 500,
2721
- display: "block",
2722
- width: "100%",
2723
- textAlign: "center",
2724
- padding: "12px 0 0"
2725
- });
2726
2771
  var WALLET_EMOJIS = {
2727
2772
  rabby: "\u{1F430}",
2728
2773
  ora: "\u2666\uFE0F",
@@ -3941,6 +3986,74 @@ var hintStyle3 = (color) => ({
3941
3986
  color,
3942
3987
  margin: "8px 0 0"
3943
3988
  });
3989
+ var PaymentErrorBoundary = class extends Component {
3990
+ constructor(props) {
3991
+ super(props);
3992
+ this.state = { hasError: false };
3993
+ }
3994
+ static getDerivedStateFromError() {
3995
+ return { hasError: true };
3996
+ }
3997
+ componentDidCatch(error, info) {
3998
+ console.error("[SwypePayment] Uncaught error:", error, info.componentStack);
3999
+ }
4000
+ handleReset = () => {
4001
+ this.setState({ hasError: false });
4002
+ this.props.onReset();
4003
+ };
4004
+ render() {
4005
+ if (!this.state.hasError) {
4006
+ return this.props.children;
4007
+ }
4008
+ return /* @__PURE__ */ jsxs("div", { style: containerStyle8, children: [
4009
+ /* @__PURE__ */ jsx("div", { style: iconStyle4, children: /* @__PURE__ */ jsxs("svg", { width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", children: [
4010
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10", stroke: "#ef4444", strokeWidth: "1.5" }),
4011
+ /* @__PURE__ */ jsx("path", { d: "M12 8v5", stroke: "#ef4444", strokeWidth: "1.5", strokeLinecap: "round" }),
4012
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "16", r: "0.75", fill: "#ef4444" })
4013
+ ] }) }),
4014
+ /* @__PURE__ */ jsx("h2", { style: headingStyle9, children: "Something went wrong" }),
4015
+ /* @__PURE__ */ jsx("p", { style: messageStyle, children: "An unexpected error occurred. Please try again." }),
4016
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: this.handleReset, style: buttonStyle3, children: "Try again" })
4017
+ ] });
4018
+ }
4019
+ };
4020
+ var containerStyle8 = {
4021
+ display: "flex",
4022
+ flexDirection: "column",
4023
+ alignItems: "center",
4024
+ justifyContent: "center",
4025
+ textAlign: "center",
4026
+ padding: "48px 24px",
4027
+ minHeight: "100%",
4028
+ maxWidth: 420,
4029
+ margin: "0 auto"
4030
+ };
4031
+ var iconStyle4 = {
4032
+ marginBottom: 20
4033
+ };
4034
+ var headingStyle9 = {
4035
+ fontSize: "1.25rem",
4036
+ fontWeight: 700,
4037
+ color: "#1a1a1a",
4038
+ margin: "0 0 8px"
4039
+ };
4040
+ var messageStyle = {
4041
+ fontSize: "0.9rem",
4042
+ color: "#666",
4043
+ margin: "0 0 28px",
4044
+ lineHeight: 1.5
4045
+ };
4046
+ var buttonStyle3 = {
4047
+ background: "#1a1a1a",
4048
+ color: "#fff",
4049
+ border: "none",
4050
+ borderRadius: 12,
4051
+ padding: "12px 32px",
4052
+ fontSize: "0.95rem",
4053
+ fontWeight: 600,
4054
+ fontFamily: "inherit",
4055
+ cursor: "pointer"
4056
+ };
3944
4057
  function isInIframe() {
3945
4058
  try {
3946
4059
  return typeof window !== "undefined" && window !== window.top;
@@ -4018,7 +4131,14 @@ function buildSelectSourceChoices(options) {
4018
4131
  }
4019
4132
  return chainChoices;
4020
4133
  }
4021
- function SwypePayment({
4134
+ function SwypePayment(props) {
4135
+ const resetKey = useRef(0);
4136
+ const handleBoundaryReset = useCallback(() => {
4137
+ resetKey.current += 1;
4138
+ }, []);
4139
+ return /* @__PURE__ */ jsx(PaymentErrorBoundary, { onReset: handleBoundaryReset, children: /* @__PURE__ */ jsx(SwypePaymentInner, { ...props }) }, resetKey.current);
4140
+ }
4141
+ function SwypePaymentInner({
4022
4142
  destination,
4023
4143
  onComplete,
4024
4144
  onError,
@@ -4059,6 +4179,7 @@ function SwypePayment({
4059
4179
  const [transfer, setTransfer] = useState(null);
4060
4180
  const [creatingTransfer, setCreatingTransfer] = useState(false);
4061
4181
  const [registeringPasskey, setRegisteringPasskey] = useState(false);
4182
+ const [passkeyPopupNeeded, setPasskeyPopupNeeded] = useState(false);
4062
4183
  const [activeCredentialId, setActiveCredentialId] = useState(() => {
4063
4184
  if (typeof window === "undefined") return null;
4064
4185
  return window.localStorage.getItem(ACTIVE_CREDENTIAL_STORAGE_KEY);
@@ -4209,7 +4330,7 @@ function SwypePayment({
4209
4330
  }
4210
4331
  setStep("create-passkey");
4211
4332
  } catch {
4212
- if (!cancelled) setStep("deposit");
4333
+ if (!cancelled) setStep("create-passkey");
4213
4334
  }
4214
4335
  };
4215
4336
  checkPasskey();
@@ -4561,44 +4682,59 @@ function SwypePayment({
4561
4682
  merchantAuthorization,
4562
4683
  transfer
4563
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]);
4564
4701
  const handleRegisterPasskey = useCallback(async () => {
4565
4702
  setRegisteringPasskey(true);
4566
4703
  setError(null);
4567
4704
  try {
4568
- const token = await getAccessToken();
4569
- if (!token) throw new Error("Not authenticated");
4570
4705
  const passkeyDisplayName = user?.email?.address ?? user?.google?.name ?? user?.id ?? "Swype User";
4571
4706
  const { credentialId, publicKey } = await createPasskeyCredential({
4572
4707
  userId: user?.id ?? "unknown",
4573
4708
  displayName: passkeyDisplayName
4574
4709
  });
4575
- await registerPasskey(apiBaseUrl, token, credentialId, publicKey);
4576
- setActiveCredentialId(credentialId);
4577
- window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
4578
- const hasActiveWallet = accounts.some(
4579
- (a) => a.wallets.some((w) => w.status === "ACTIVE")
4580
- );
4581
- if (accounts.length === 0 || !hasActiveWallet) {
4582
- setStep("wallet-picker");
4710
+ await completePasskeyRegistration(credentialId, publicKey);
4711
+ } catch (err) {
4712
+ if (err instanceof PasskeyIframeBlockedError) {
4713
+ setPasskeyPopupNeeded(true);
4583
4714
  } else {
4584
- setStep("deposit");
4715
+ setError(err instanceof Error ? err.message : "Failed to register passkey");
4585
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);
4586
4732
  } catch (err) {
4587
4733
  setError(err instanceof Error ? err.message : "Failed to register passkey");
4588
4734
  } finally {
4589
4735
  setRegisteringPasskey(false);
4590
4736
  }
4591
- }, [getAccessToken, user, apiBaseUrl, accounts]);
4592
- const handleSkipPasskey = useCallback(() => {
4593
- const hasActiveWallet = accounts.some(
4594
- (a) => a.wallets.some((w) => w.status === "ACTIVE")
4595
- );
4596
- if (accounts.length === 0 || !hasActiveWallet) {
4597
- setStep("wallet-picker");
4598
- } else {
4599
- setStep("deposit");
4600
- }
4601
- }, [accounts]);
4737
+ }, [user, completePasskeyRegistration]);
4602
4738
  const handleSelectProvider = useCallback((providerId) => {
4603
4739
  setSelectedProviderId(providerId);
4604
4740
  setSelectedAccountId(null);
@@ -4714,10 +4850,11 @@ function SwypePayment({
4714
4850
  CreatePasskeyScreen,
4715
4851
  {
4716
4852
  onCreatePasskey: handleRegisterPasskey,
4717
- onSkip: handleSkipPasskey,
4718
- onBack: () => setStep("login"),
4853
+ onBack: handleLogout,
4719
4854
  creating: registeringPasskey,
4720
- error
4855
+ error,
4856
+ popupFallback: passkeyPopupNeeded,
4857
+ onCreatePasskeyViaPopup: handleCreatePasskeyViaPopup
4721
4858
  }
4722
4859
  );
4723
4860
  }
@@ -4730,7 +4867,7 @@ function SwypePayment({
4730
4867
  loading: creatingTransfer,
4731
4868
  onSelectProvider: handleSelectProvider,
4732
4869
  onContinueConnection: handleContinueConnection,
4733
- onBack: () => setStep("create-passkey")
4870
+ onBack: () => setStep(activeCredentialId ? "deposit" : "create-passkey")
4734
4871
  }
4735
4872
  );
4736
4873
  }
@@ -4850,6 +4987,6 @@ function SwypePayment({
4850
4987
  return null;
4851
4988
  }
4852
4989
 
4853
- 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 };
4854
4991
  //# sourceMappingURL=index.js.map
4855
4992
  //# sourceMappingURL=index.js.map