@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.cjs CHANGED
@@ -658,6 +658,12 @@ function normalizeSignature(sig) {
658
658
  }
659
659
 
660
660
  // src/passkey-delegation.ts
661
+ var PasskeyIframeBlockedError = class extends Error {
662
+ constructor(message = "Passkey creation is not supported in this browser context.") {
663
+ super(message);
664
+ this.name = "PasskeyIframeBlockedError";
665
+ }
666
+ };
661
667
  function isInCrossOriginIframe() {
662
668
  if (typeof window === "undefined") return false;
663
669
  if (window.parent === window) return false;
@@ -668,57 +674,47 @@ function isInCrossOriginIframe() {
668
674
  return true;
669
675
  }
670
676
  }
671
- var delegationCounter = 0;
672
- var DELEGATION_CREATE_TIMEOUT_MS = 6e4;
673
- var DELEGATION_GET_TIMEOUT_MS = 3e4;
674
- function delegatePasskeyCreate(options) {
677
+ var POPUP_RESULT_TIMEOUT_MS = 12e4;
678
+ var POPUP_CLOSED_POLL_MS = 500;
679
+ function createPasskeyViaPopup(options) {
675
680
  return new Promise((resolve, reject) => {
676
- const id = `pc-${++delegationCounter}-${Date.now()}`;
681
+ const encoded = btoa(JSON.stringify(options));
682
+ const popupUrl = `${window.location.origin}/passkey-register#${encoded}`;
683
+ const popup = window.open(popupUrl, "swype-passkey", "width=460,height=600");
684
+ if (!popup) {
685
+ reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
686
+ return;
687
+ }
677
688
  const timer = setTimeout(() => {
678
- window.removeEventListener("message", handler);
689
+ cleanup();
679
690
  reject(new Error("Passkey creation timed out. Please try again."));
680
- }, DELEGATION_CREATE_TIMEOUT_MS);
691
+ }, POPUP_RESULT_TIMEOUT_MS);
692
+ const closedPoll = setInterval(() => {
693
+ if (popup.closed) {
694
+ cleanup();
695
+ reject(new Error("Passkey setup window was closed before completing."));
696
+ }
697
+ }, POPUP_CLOSED_POLL_MS);
681
698
  const handler = (event) => {
699
+ if (event.source !== popup) return;
682
700
  const data = event.data;
683
701
  if (!data || typeof data !== "object") return;
684
- if (data.type !== "swype:passkey-create-response" || data.id !== id) return;
685
- clearTimeout(timer);
686
- window.removeEventListener("message", handler);
702
+ if (data.type !== "swype:passkey-popup-result") return;
703
+ cleanup();
687
704
  if (data.error) {
688
705
  reject(new Error(data.error));
689
706
  } else if (data.result) {
690
707
  resolve(data.result);
691
708
  } else {
692
- reject(new Error("Invalid passkey create response."));
709
+ reject(new Error("Invalid passkey popup response."));
693
710
  }
694
711
  };
695
- window.addEventListener("message", handler);
696
- window.parent.postMessage({ type: "swype:passkey-create-request", id, options }, "*");
697
- });
698
- }
699
- function delegatePasskeyGet(options) {
700
- return new Promise((resolve, reject) => {
701
- const id = `pg-${++delegationCounter}-${Date.now()}`;
702
- const timer = setTimeout(() => {
703
- window.removeEventListener("message", handler);
704
- reject(new Error("Passkey verification timed out. Please try again."));
705
- }, DELEGATION_GET_TIMEOUT_MS);
706
- const handler = (event) => {
707
- const data = event.data;
708
- if (!data || typeof data !== "object") return;
709
- if (data.type !== "swype:passkey-get-response" || data.id !== id) return;
712
+ function cleanup() {
710
713
  clearTimeout(timer);
714
+ clearInterval(closedPoll);
711
715
  window.removeEventListener("message", handler);
712
- if (data.error) {
713
- reject(new Error(data.error));
714
- } else if (data.result) {
715
- resolve(data.result);
716
- } else {
717
- reject(new Error("Invalid passkey get response."));
718
- }
719
- };
716
+ }
720
717
  window.addEventListener("message", handler);
721
- window.parent.postMessage({ type: "swype:passkey-get-request", id, options }, "*");
722
718
  });
723
719
  }
724
720
 
@@ -843,53 +839,51 @@ async function createPasskeyCredential(params) {
843
839
  const challenge = new Uint8Array(32);
844
840
  crypto.getRandomValues(challenge);
845
841
  const rpId = resolvePasskeyRpId();
842
+ const publicKeyOptions = {
843
+ challenge,
844
+ rp: { name: "Swype", id: rpId },
845
+ user: {
846
+ id: new TextEncoder().encode(params.userId),
847
+ name: params.displayName,
848
+ displayName: params.displayName
849
+ },
850
+ pubKeyCredParams: [
851
+ { alg: -7, type: "public-key" },
852
+ { alg: -257, type: "public-key" }
853
+ ],
854
+ authenticatorSelection: {
855
+ authenticatorAttachment: "platform",
856
+ residentKey: "preferred",
857
+ userVerification: "required"
858
+ },
859
+ timeout: 6e4
860
+ };
846
861
  if (isInCrossOriginIframe()) {
847
- return delegatePasskeyCreate({
848
- challenge: toBase64(challenge),
849
- rpId,
850
- rpName: "Swype",
851
- userId: toBase64(new TextEncoder().encode(params.userId)),
852
- userName: params.displayName,
853
- userDisplayName: params.displayName,
854
- pubKeyCredParams: [
855
- { alg: -7, type: "public-key" },
856
- { alg: -257, type: "public-key" }
857
- ],
858
- authenticatorSelection: {
859
- authenticatorAttachment: "platform",
860
- residentKey: "preferred",
861
- userVerification: "required"
862
- },
863
- timeout: 6e4
864
- });
862
+ try {
863
+ await waitForDocumentFocus();
864
+ const credential2 = await navigator.credentials.create({
865
+ publicKey: publicKeyOptions
866
+ });
867
+ if (!credential2) {
868
+ throw new Error("Passkey creation was cancelled.");
869
+ }
870
+ return extractPasskeyResult(credential2);
871
+ } catch (err) {
872
+ if (err instanceof PasskeyIframeBlockedError) throw err;
873
+ if (err instanceof Error && err.message === "Passkey creation was cancelled.") throw err;
874
+ throw new PasskeyIframeBlockedError();
875
+ }
865
876
  }
866
877
  await waitForDocumentFocus();
867
878
  const credential = await navigator.credentials.create({
868
- publicKey: {
869
- challenge,
870
- rp: { name: "Swype", id: rpId },
871
- user: {
872
- id: new TextEncoder().encode(params.userId),
873
- name: params.displayName,
874
- displayName: params.displayName
875
- },
876
- pubKeyCredParams: [
877
- { alg: -7, type: "public-key" },
878
- // ES256 (P-256)
879
- { alg: -257, type: "public-key" }
880
- // RS256
881
- ],
882
- authenticatorSelection: {
883
- authenticatorAttachment: "platform",
884
- residentKey: "preferred",
885
- userVerification: "required"
886
- },
887
- timeout: 6e4
888
- }
879
+ publicKey: publicKeyOptions
889
880
  });
890
881
  if (!credential) {
891
882
  throw new Error("Passkey creation was cancelled.");
892
883
  }
884
+ return extractPasskeyResult(credential);
885
+ }
886
+ function extractPasskeyResult(credential) {
893
887
  const response = credential.response;
894
888
  const publicKeyBytes = response.getPublicKey?.();
895
889
  return {
@@ -897,6 +891,29 @@ async function createPasskeyCredential(params) {
897
891
  publicKey: publicKeyBytes ? toBase64(publicKeyBytes) : ""
898
892
  };
899
893
  }
894
+ function buildPasskeyPopupOptions(params) {
895
+ const challenge = new Uint8Array(32);
896
+ crypto.getRandomValues(challenge);
897
+ const rpId = resolvePasskeyRpId();
898
+ return {
899
+ challenge: toBase64(challenge),
900
+ rpId,
901
+ rpName: "Swype",
902
+ userId: toBase64(new TextEncoder().encode(params.userId)),
903
+ userName: params.displayName,
904
+ userDisplayName: params.displayName,
905
+ pubKeyCredParams: [
906
+ { alg: -7, type: "public-key" },
907
+ { alg: -257, type: "public-key" }
908
+ ],
909
+ authenticatorSelection: {
910
+ authenticatorAttachment: "platform",
911
+ residentKey: "preferred",
912
+ userVerification: "required"
913
+ },
914
+ timeout: 6e4
915
+ };
916
+ }
900
917
  async function deviceHasPasskey(credentialId) {
901
918
  const found = await findDevicePasskey([credentialId]);
902
919
  return found != null;
@@ -906,16 +923,6 @@ async function findDevicePasskey(credentialIds) {
906
923
  try {
907
924
  const challenge = new Uint8Array(32);
908
925
  crypto.getRandomValues(challenge);
909
- if (isInCrossOriginIframe()) {
910
- const result = await delegatePasskeyGet({
911
- challenge: toBase64(challenge),
912
- rpId: resolvePasskeyRpId(),
913
- allowCredentials: credentialIds.map((id) => ({ type: "public-key", id })),
914
- userVerification: "discouraged",
915
- timeout: 3e4
916
- });
917
- return result.credentialId;
918
- }
919
926
  await waitForDocumentFocus();
920
927
  const assertion = await navigator.credentials.get({
921
928
  publicKey: {
@@ -1414,48 +1421,31 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
1414
1421
  }
1415
1422
  const hashBytes = hexToBytes(payload.userOpHash);
1416
1423
  let signedUserOp;
1417
- if (isInCrossOriginIframe()) {
1418
- const delegatedResult = await delegatePasskeyGet({
1419
- challenge: toBase64(hashBytes),
1424
+ const allowCredentials = payload.passkeyCredentialId ? [{
1425
+ type: "public-key",
1426
+ id: base64ToBytes(payload.passkeyCredentialId)
1427
+ }] : void 0;
1428
+ await waitForDocumentFocus();
1429
+ const assertion = await navigator.credentials.get({
1430
+ publicKey: {
1431
+ challenge: hashBytes,
1420
1432
  rpId: resolvePasskeyRpId(),
1421
- allowCredentials: payload.passkeyCredentialId ? [{ type: "public-key", id: payload.passkeyCredentialId }] : void 0,
1433
+ allowCredentials,
1422
1434
  userVerification: "required",
1423
1435
  timeout: 6e4
1424
- });
1425
- signedUserOp = {
1426
- ...payload.userOp,
1427
- credentialId: delegatedResult.credentialId,
1428
- signature: delegatedResult.signature,
1429
- authenticatorData: delegatedResult.authenticatorData,
1430
- clientDataJSON: delegatedResult.clientDataJSON
1431
- };
1432
- } else {
1433
- const allowCredentials = payload.passkeyCredentialId ? [{
1434
- type: "public-key",
1435
- id: base64ToBytes(payload.passkeyCredentialId)
1436
- }] : void 0;
1437
- await waitForDocumentFocus();
1438
- const assertion = await navigator.credentials.get({
1439
- publicKey: {
1440
- challenge: hashBytes,
1441
- rpId: resolvePasskeyRpId(),
1442
- allowCredentials,
1443
- userVerification: "required",
1444
- timeout: 6e4
1445
- }
1446
- });
1447
- if (!assertion) {
1448
- throw new Error("Passkey authentication was cancelled.");
1449
1436
  }
1450
- const response = assertion.response;
1451
- signedUserOp = {
1452
- ...payload.userOp,
1453
- credentialId: toBase64(assertion.rawId),
1454
- signature: toBase64(response.signature),
1455
- authenticatorData: toBase64(response.authenticatorData),
1456
- clientDataJSON: toBase64(response.clientDataJSON)
1457
- };
1437
+ });
1438
+ if (!assertion) {
1439
+ throw new Error("Passkey authentication was cancelled.");
1458
1440
  }
1441
+ const response = assertion.response;
1442
+ signedUserOp = {
1443
+ ...payload.userOp,
1444
+ credentialId: toBase64(assertion.rawId),
1445
+ signature: toBase64(response.signature),
1446
+ authenticatorData: toBase64(response.authenticatorData),
1447
+ clientDataJSON: toBase64(response.clientDataJSON)
1448
+ };
1459
1449
  return await signTransfer(
1460
1450
  apiBaseUrl,
1461
1451
  token ?? "",
@@ -2661,14 +2651,18 @@ function CreatePasskeyScreen({
2661
2651
  onCreatePasskey,
2662
2652
  onBack,
2663
2653
  creating,
2664
- error
2654
+ error,
2655
+ popupFallback = false,
2656
+ onCreatePasskeyViaPopup
2665
2657
  }) {
2666
2658
  const { tokens } = useSwypeConfig();
2659
+ const handleCreate = popupFallback && onCreatePasskeyViaPopup ? onCreatePasskeyViaPopup : onCreatePasskey;
2660
+ const buttonLabel = popupFallback ? "Open passkey setup" : "Create passkey";
2667
2661
  return /* @__PURE__ */ jsxRuntime.jsxs(
2668
2662
  ScreenLayout,
2669
2663
  {
2670
2664
  footer: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2671
- /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { onClick: onCreatePasskey, disabled: creating, loading: creating, children: "Create passkey" }),
2665
+ /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { onClick: handleCreate, disabled: creating, loading: creating, children: buttonLabel }),
2672
2666
  /* @__PURE__ */ jsxRuntime.jsx(PoweredByFooter, {})
2673
2667
  ] }),
2674
2668
  children: [
@@ -2681,7 +2675,7 @@ function CreatePasskeyScreen({
2681
2675
  /* @__PURE__ */ jsxRuntime.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" })
2682
2676
  ] }) }),
2683
2677
  /* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle3(tokens.text), children: "Create your passkey" }),
2684
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: subtitleStyle3(tokens.textSecondary), children: "Use Face ID to sign in instantly \u2014 no passwords, no codes." }),
2678
+ /* @__PURE__ */ jsxRuntime.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." }),
2685
2679
  error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: errorBannerStyle2(tokens), children: error }),
2686
2680
  /* @__PURE__ */ jsxRuntime.jsx(InfoBanner, { children: "Your passkey is stored securely on your device. Swype never sees your biometric data." })
2687
2681
  ] })
@@ -3866,7 +3860,7 @@ function OpenWalletScreen({
3866
3860
  const logoSrc = walletName ? KNOWN_LOGOS[walletName.toLowerCase()] : void 0;
3867
3861
  const handleOpen = react.useCallback(() => {
3868
3862
  const opened = window.open(deeplinkUri, "_blank");
3869
- if (!opened) {
3863
+ if (!opened && window === window.parent) {
3870
3864
  window.location.href = deeplinkUri;
3871
3865
  }
3872
3866
  }, [deeplinkUri]);
@@ -4134,6 +4128,7 @@ function SwypePaymentInner({
4134
4128
  const [transfer, setTransfer] = react.useState(null);
4135
4129
  const [creatingTransfer, setCreatingTransfer] = react.useState(false);
4136
4130
  const [registeringPasskey, setRegisteringPasskey] = react.useState(false);
4131
+ const [passkeyPopupNeeded, setPasskeyPopupNeeded] = react.useState(false);
4137
4132
  const [activeCredentialId, setActiveCredentialId] = react.useState(() => {
4138
4133
  if (typeof window === "undefined") return null;
4139
4134
  return window.localStorage.getItem(ACTIVE_CREDENTIAL_STORAGE_KEY);
@@ -4636,34 +4631,59 @@ function SwypePaymentInner({
4636
4631
  merchantAuthorization,
4637
4632
  transfer
4638
4633
  ]);
4634
+ const completePasskeyRegistration = react.useCallback(async (credentialId, publicKey) => {
4635
+ const token = await getAccessToken();
4636
+ if (!token) throw new Error("Not authenticated");
4637
+ await registerPasskey(apiBaseUrl, token, credentialId, publicKey);
4638
+ setActiveCredentialId(credentialId);
4639
+ window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
4640
+ setPasskeyPopupNeeded(false);
4641
+ const hasActiveWallet = accounts.some(
4642
+ (a) => a.wallets.some((w) => w.status === "ACTIVE")
4643
+ );
4644
+ if (accounts.length === 0 || !hasActiveWallet) {
4645
+ setStep("wallet-picker");
4646
+ } else {
4647
+ setStep("deposit");
4648
+ }
4649
+ }, [getAccessToken, apiBaseUrl, accounts]);
4639
4650
  const handleRegisterPasskey = react.useCallback(async () => {
4640
4651
  setRegisteringPasskey(true);
4641
4652
  setError(null);
4642
4653
  try {
4643
- const token = await getAccessToken();
4644
- if (!token) throw new Error("Not authenticated");
4645
4654
  const passkeyDisplayName = user?.email?.address ?? user?.google?.name ?? user?.id ?? "Swype User";
4646
4655
  const { credentialId, publicKey } = await createPasskeyCredential({
4647
4656
  userId: user?.id ?? "unknown",
4648
4657
  displayName: passkeyDisplayName
4649
4658
  });
4650
- await registerPasskey(apiBaseUrl, token, credentialId, publicKey);
4651
- setActiveCredentialId(credentialId);
4652
- window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
4653
- const hasActiveWallet = accounts.some(
4654
- (a) => a.wallets.some((w) => w.status === "ACTIVE")
4655
- );
4656
- if (accounts.length === 0 || !hasActiveWallet) {
4657
- setStep("wallet-picker");
4659
+ await completePasskeyRegistration(credentialId, publicKey);
4660
+ } catch (err) {
4661
+ if (err instanceof PasskeyIframeBlockedError) {
4662
+ setPasskeyPopupNeeded(true);
4658
4663
  } else {
4659
- setStep("deposit");
4664
+ setError(err instanceof Error ? err.message : "Failed to register passkey");
4660
4665
  }
4666
+ } finally {
4667
+ setRegisteringPasskey(false);
4668
+ }
4669
+ }, [user, completePasskeyRegistration]);
4670
+ const handleCreatePasskeyViaPopup = react.useCallback(async () => {
4671
+ setRegisteringPasskey(true);
4672
+ setError(null);
4673
+ try {
4674
+ const passkeyDisplayName = user?.email?.address ?? user?.google?.name ?? user?.id ?? "Swype User";
4675
+ const popupOptions = buildPasskeyPopupOptions({
4676
+ userId: user?.id ?? "unknown",
4677
+ displayName: passkeyDisplayName
4678
+ });
4679
+ const { credentialId, publicKey } = await createPasskeyViaPopup(popupOptions);
4680
+ await completePasskeyRegistration(credentialId, publicKey);
4661
4681
  } catch (err) {
4662
4682
  setError(err instanceof Error ? err.message : "Failed to register passkey");
4663
4683
  } finally {
4664
4684
  setRegisteringPasskey(false);
4665
4685
  }
4666
- }, [getAccessToken, user, apiBaseUrl, accounts]);
4686
+ }, [user, completePasskeyRegistration]);
4667
4687
  const handleSelectProvider = react.useCallback((providerId) => {
4668
4688
  setSelectedProviderId(providerId);
4669
4689
  setSelectedAccountId(null);
@@ -4781,7 +4801,9 @@ function SwypePaymentInner({
4781
4801
  onCreatePasskey: handleRegisterPasskey,
4782
4802
  onBack: handleLogout,
4783
4803
  creating: registeringPasskey,
4784
- error
4804
+ error,
4805
+ popupFallback: passkeyPopupNeeded,
4806
+ onCreatePasskeyViaPopup: handleCreatePasskeyViaPopup
4785
4807
  }
4786
4808
  );
4787
4809
  }
@@ -4916,6 +4938,7 @@ function SwypePaymentInner({
4916
4938
 
4917
4939
  exports.IconCircle = IconCircle;
4918
4940
  exports.OutlineButton = OutlineButton;
4941
+ exports.PasskeyIframeBlockedError = PasskeyIframeBlockedError;
4919
4942
  exports.PoweredByFooter = PoweredByFooter;
4920
4943
  exports.PrimaryButton = PrimaryButton;
4921
4944
  exports.ScreenHeader = ScreenHeader;
@@ -4927,7 +4950,9 @@ exports.Spinner = Spinner;
4927
4950
  exports.StepList = StepList;
4928
4951
  exports.SwypePayment = SwypePayment;
4929
4952
  exports.SwypeProvider = SwypeProvider;
4953
+ exports.buildPasskeyPopupOptions = buildPasskeyPopupOptions;
4930
4954
  exports.createPasskeyCredential = createPasskeyCredential;
4955
+ exports.createPasskeyViaPopup = createPasskeyViaPopup;
4931
4956
  exports.darkTheme = darkTheme;
4932
4957
  exports.deviceHasPasskey = deviceHasPasskey;
4933
4958
  exports.findDevicePasskey = findDevicePasskey;