@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.cjs +152 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -1
- package/dist/index.d.ts +63 -1
- package/dist/index.js +150 -74
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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;
|
|
@@ -669,19 +675,18 @@ function isInCrossOriginIframe() {
|
|
|
669
675
|
}
|
|
670
676
|
}
|
|
671
677
|
var delegationCounter = 0;
|
|
672
|
-
var DELEGATION_CREATE_TIMEOUT_MS = 6e4;
|
|
673
678
|
var DELEGATION_GET_TIMEOUT_MS = 3e4;
|
|
674
|
-
function
|
|
679
|
+
function delegatePasskeyGet(options) {
|
|
675
680
|
return new Promise((resolve, reject) => {
|
|
676
|
-
const id = `
|
|
681
|
+
const id = `pg-${++delegationCounter}-${Date.now()}`;
|
|
677
682
|
const timer = setTimeout(() => {
|
|
678
683
|
window.removeEventListener("message", handler);
|
|
679
|
-
reject(new Error("Passkey
|
|
680
|
-
},
|
|
684
|
+
reject(new Error("Passkey verification timed out. Please try again."));
|
|
685
|
+
}, DELEGATION_GET_TIMEOUT_MS);
|
|
681
686
|
const handler = (event) => {
|
|
682
687
|
const data = event.data;
|
|
683
688
|
if (!data || typeof data !== "object") return;
|
|
684
|
-
if (data.type !== "swype:passkey-
|
|
689
|
+
if (data.type !== "swype:passkey-get-response" || data.id !== id) return;
|
|
685
690
|
clearTimeout(timer);
|
|
686
691
|
window.removeEventListener("message", handler);
|
|
687
692
|
if (data.error) {
|
|
@@ -689,36 +694,54 @@ function delegatePasskeyCreate(options) {
|
|
|
689
694
|
} else if (data.result) {
|
|
690
695
|
resolve(data.result);
|
|
691
696
|
} else {
|
|
692
|
-
reject(new Error("Invalid passkey
|
|
697
|
+
reject(new Error("Invalid passkey get response."));
|
|
693
698
|
}
|
|
694
699
|
};
|
|
695
700
|
window.addEventListener("message", handler);
|
|
696
|
-
window.parent.postMessage({ type: "swype:passkey-
|
|
701
|
+
window.parent.postMessage({ type: "swype:passkey-get-request", id, options }, "*");
|
|
697
702
|
});
|
|
698
703
|
}
|
|
699
|
-
|
|
704
|
+
var POPUP_RESULT_TIMEOUT_MS = 12e4;
|
|
705
|
+
var POPUP_CLOSED_POLL_MS = 500;
|
|
706
|
+
function createPasskeyViaPopup(options) {
|
|
700
707
|
return new Promise((resolve, reject) => {
|
|
701
|
-
const
|
|
708
|
+
const encoded = btoa(JSON.stringify(options));
|
|
709
|
+
const popupUrl = `${window.location.origin}/passkey-register#${encoded}`;
|
|
710
|
+
const popup = window.open(popupUrl, "swype-passkey", "width=460,height=600");
|
|
711
|
+
if (!popup) {
|
|
712
|
+
reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
702
715
|
const timer = setTimeout(() => {
|
|
703
|
-
|
|
704
|
-
reject(new Error("Passkey
|
|
705
|
-
},
|
|
716
|
+
cleanup();
|
|
717
|
+
reject(new Error("Passkey creation timed out. Please try again."));
|
|
718
|
+
}, POPUP_RESULT_TIMEOUT_MS);
|
|
719
|
+
const closedPoll = setInterval(() => {
|
|
720
|
+
if (popup.closed) {
|
|
721
|
+
cleanup();
|
|
722
|
+
reject(new Error("Passkey setup window was closed before completing."));
|
|
723
|
+
}
|
|
724
|
+
}, POPUP_CLOSED_POLL_MS);
|
|
706
725
|
const handler = (event) => {
|
|
726
|
+
if (event.source !== popup) return;
|
|
707
727
|
const data = event.data;
|
|
708
728
|
if (!data || typeof data !== "object") return;
|
|
709
|
-
if (data.type !== "swype:passkey-
|
|
710
|
-
|
|
711
|
-
window.removeEventListener("message", handler);
|
|
729
|
+
if (data.type !== "swype:passkey-popup-result") return;
|
|
730
|
+
cleanup();
|
|
712
731
|
if (data.error) {
|
|
713
732
|
reject(new Error(data.error));
|
|
714
733
|
} else if (data.result) {
|
|
715
734
|
resolve(data.result);
|
|
716
735
|
} else {
|
|
717
|
-
reject(new Error("Invalid passkey
|
|
736
|
+
reject(new Error("Invalid passkey popup response."));
|
|
718
737
|
}
|
|
719
738
|
};
|
|
739
|
+
function cleanup() {
|
|
740
|
+
clearTimeout(timer);
|
|
741
|
+
clearInterval(closedPoll);
|
|
742
|
+
window.removeEventListener("message", handler);
|
|
743
|
+
}
|
|
720
744
|
window.addEventListener("message", handler);
|
|
721
|
-
window.parent.postMessage({ type: "swype:passkey-get-request", id, options }, "*");
|
|
722
745
|
});
|
|
723
746
|
}
|
|
724
747
|
|
|
@@ -843,53 +866,51 @@ async function createPasskeyCredential(params) {
|
|
|
843
866
|
const challenge = new Uint8Array(32);
|
|
844
867
|
crypto.getRandomValues(challenge);
|
|
845
868
|
const rpId = resolvePasskeyRpId();
|
|
869
|
+
const publicKeyOptions = {
|
|
870
|
+
challenge,
|
|
871
|
+
rp: { name: "Swype", id: rpId },
|
|
872
|
+
user: {
|
|
873
|
+
id: new TextEncoder().encode(params.userId),
|
|
874
|
+
name: params.displayName,
|
|
875
|
+
displayName: params.displayName
|
|
876
|
+
},
|
|
877
|
+
pubKeyCredParams: [
|
|
878
|
+
{ alg: -7, type: "public-key" },
|
|
879
|
+
{ alg: -257, type: "public-key" }
|
|
880
|
+
],
|
|
881
|
+
authenticatorSelection: {
|
|
882
|
+
authenticatorAttachment: "platform",
|
|
883
|
+
residentKey: "preferred",
|
|
884
|
+
userVerification: "required"
|
|
885
|
+
},
|
|
886
|
+
timeout: 6e4
|
|
887
|
+
};
|
|
846
888
|
if (isInCrossOriginIframe()) {
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
userVerification: "required"
|
|
862
|
-
},
|
|
863
|
-
timeout: 6e4
|
|
864
|
-
});
|
|
889
|
+
try {
|
|
890
|
+
await waitForDocumentFocus();
|
|
891
|
+
const credential2 = await navigator.credentials.create({
|
|
892
|
+
publicKey: publicKeyOptions
|
|
893
|
+
});
|
|
894
|
+
if (!credential2) {
|
|
895
|
+
throw new Error("Passkey creation was cancelled.");
|
|
896
|
+
}
|
|
897
|
+
return extractPasskeyResult(credential2);
|
|
898
|
+
} catch (err) {
|
|
899
|
+
if (err instanceof PasskeyIframeBlockedError) throw err;
|
|
900
|
+
if (err instanceof Error && err.message === "Passkey creation was cancelled.") throw err;
|
|
901
|
+
throw new PasskeyIframeBlockedError();
|
|
902
|
+
}
|
|
865
903
|
}
|
|
866
904
|
await waitForDocumentFocus();
|
|
867
905
|
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
|
-
}
|
|
906
|
+
publicKey: publicKeyOptions
|
|
889
907
|
});
|
|
890
908
|
if (!credential) {
|
|
891
909
|
throw new Error("Passkey creation was cancelled.");
|
|
892
910
|
}
|
|
911
|
+
return extractPasskeyResult(credential);
|
|
912
|
+
}
|
|
913
|
+
function extractPasskeyResult(credential) {
|
|
893
914
|
const response = credential.response;
|
|
894
915
|
const publicKeyBytes = response.getPublicKey?.();
|
|
895
916
|
return {
|
|
@@ -897,6 +918,29 @@ async function createPasskeyCredential(params) {
|
|
|
897
918
|
publicKey: publicKeyBytes ? toBase64(publicKeyBytes) : ""
|
|
898
919
|
};
|
|
899
920
|
}
|
|
921
|
+
function buildPasskeyPopupOptions(params) {
|
|
922
|
+
const challenge = new Uint8Array(32);
|
|
923
|
+
crypto.getRandomValues(challenge);
|
|
924
|
+
const rpId = resolvePasskeyRpId();
|
|
925
|
+
return {
|
|
926
|
+
challenge: toBase64(challenge),
|
|
927
|
+
rpId,
|
|
928
|
+
rpName: "Swype",
|
|
929
|
+
userId: toBase64(new TextEncoder().encode(params.userId)),
|
|
930
|
+
userName: params.displayName,
|
|
931
|
+
userDisplayName: params.displayName,
|
|
932
|
+
pubKeyCredParams: [
|
|
933
|
+
{ alg: -7, type: "public-key" },
|
|
934
|
+
{ alg: -257, type: "public-key" }
|
|
935
|
+
],
|
|
936
|
+
authenticatorSelection: {
|
|
937
|
+
authenticatorAttachment: "platform",
|
|
938
|
+
residentKey: "preferred",
|
|
939
|
+
userVerification: "required"
|
|
940
|
+
},
|
|
941
|
+
timeout: 6e4
|
|
942
|
+
};
|
|
943
|
+
}
|
|
900
944
|
async function deviceHasPasskey(credentialId) {
|
|
901
945
|
const found = await findDevicePasskey([credentialId]);
|
|
902
946
|
return found != null;
|
|
@@ -2661,14 +2705,18 @@ function CreatePasskeyScreen({
|
|
|
2661
2705
|
onCreatePasskey,
|
|
2662
2706
|
onBack,
|
|
2663
2707
|
creating,
|
|
2664
|
-
error
|
|
2708
|
+
error,
|
|
2709
|
+
popupFallback = false,
|
|
2710
|
+
onCreatePasskeyViaPopup
|
|
2665
2711
|
}) {
|
|
2666
2712
|
const { tokens } = useSwypeConfig();
|
|
2713
|
+
const handleCreate = popupFallback && onCreatePasskeyViaPopup ? onCreatePasskeyViaPopup : onCreatePasskey;
|
|
2714
|
+
const buttonLabel = popupFallback ? "Open passkey setup" : "Create passkey";
|
|
2667
2715
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2668
2716
|
ScreenLayout,
|
|
2669
2717
|
{
|
|
2670
2718
|
footer: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2671
|
-
/* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { onClick:
|
|
2719
|
+
/* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { onClick: handleCreate, disabled: creating, loading: creating, children: buttonLabel }),
|
|
2672
2720
|
/* @__PURE__ */ jsxRuntime.jsx(PoweredByFooter, {})
|
|
2673
2721
|
] }),
|
|
2674
2722
|
children: [
|
|
@@ -2681,7 +2729,7 @@ function CreatePasskeyScreen({
|
|
|
2681
2729
|
/* @__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
2730
|
] }) }),
|
|
2683
2731
|
/* @__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." }),
|
|
2732
|
+
/* @__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
2733
|
error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: errorBannerStyle2(tokens), children: error }),
|
|
2686
2734
|
/* @__PURE__ */ jsxRuntime.jsx(InfoBanner, { children: "Your passkey is stored securely on your device. Swype never sees your biometric data." })
|
|
2687
2735
|
] })
|
|
@@ -4134,6 +4182,7 @@ function SwypePaymentInner({
|
|
|
4134
4182
|
const [transfer, setTransfer] = react.useState(null);
|
|
4135
4183
|
const [creatingTransfer, setCreatingTransfer] = react.useState(false);
|
|
4136
4184
|
const [registeringPasskey, setRegisteringPasskey] = react.useState(false);
|
|
4185
|
+
const [passkeyPopupNeeded, setPasskeyPopupNeeded] = react.useState(false);
|
|
4137
4186
|
const [activeCredentialId, setActiveCredentialId] = react.useState(() => {
|
|
4138
4187
|
if (typeof window === "undefined") return null;
|
|
4139
4188
|
return window.localStorage.getItem(ACTIVE_CREDENTIAL_STORAGE_KEY);
|
|
@@ -4636,34 +4685,59 @@ function SwypePaymentInner({
|
|
|
4636
4685
|
merchantAuthorization,
|
|
4637
4686
|
transfer
|
|
4638
4687
|
]);
|
|
4688
|
+
const completePasskeyRegistration = react.useCallback(async (credentialId, publicKey) => {
|
|
4689
|
+
const token = await getAccessToken();
|
|
4690
|
+
if (!token) throw new Error("Not authenticated");
|
|
4691
|
+
await registerPasskey(apiBaseUrl, token, credentialId, publicKey);
|
|
4692
|
+
setActiveCredentialId(credentialId);
|
|
4693
|
+
window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
|
|
4694
|
+
setPasskeyPopupNeeded(false);
|
|
4695
|
+
const hasActiveWallet = accounts.some(
|
|
4696
|
+
(a) => a.wallets.some((w) => w.status === "ACTIVE")
|
|
4697
|
+
);
|
|
4698
|
+
if (accounts.length === 0 || !hasActiveWallet) {
|
|
4699
|
+
setStep("wallet-picker");
|
|
4700
|
+
} else {
|
|
4701
|
+
setStep("deposit");
|
|
4702
|
+
}
|
|
4703
|
+
}, [getAccessToken, apiBaseUrl, accounts]);
|
|
4639
4704
|
const handleRegisterPasskey = react.useCallback(async () => {
|
|
4640
4705
|
setRegisteringPasskey(true);
|
|
4641
4706
|
setError(null);
|
|
4642
4707
|
try {
|
|
4643
|
-
const token = await getAccessToken();
|
|
4644
|
-
if (!token) throw new Error("Not authenticated");
|
|
4645
4708
|
const passkeyDisplayName = user?.email?.address ?? user?.google?.name ?? user?.id ?? "Swype User";
|
|
4646
4709
|
const { credentialId, publicKey } = await createPasskeyCredential({
|
|
4647
4710
|
userId: user?.id ?? "unknown",
|
|
4648
4711
|
displayName: passkeyDisplayName
|
|
4649
4712
|
});
|
|
4650
|
-
await
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
(a) => a.wallets.some((w) => w.status === "ACTIVE")
|
|
4655
|
-
);
|
|
4656
|
-
if (accounts.length === 0 || !hasActiveWallet) {
|
|
4657
|
-
setStep("wallet-picker");
|
|
4713
|
+
await completePasskeyRegistration(credentialId, publicKey);
|
|
4714
|
+
} catch (err) {
|
|
4715
|
+
if (err instanceof PasskeyIframeBlockedError) {
|
|
4716
|
+
setPasskeyPopupNeeded(true);
|
|
4658
4717
|
} else {
|
|
4659
|
-
|
|
4718
|
+
setError(err instanceof Error ? err.message : "Failed to register passkey");
|
|
4660
4719
|
}
|
|
4720
|
+
} finally {
|
|
4721
|
+
setRegisteringPasskey(false);
|
|
4722
|
+
}
|
|
4723
|
+
}, [user, completePasskeyRegistration]);
|
|
4724
|
+
const handleCreatePasskeyViaPopup = react.useCallback(async () => {
|
|
4725
|
+
setRegisteringPasskey(true);
|
|
4726
|
+
setError(null);
|
|
4727
|
+
try {
|
|
4728
|
+
const passkeyDisplayName = user?.email?.address ?? user?.google?.name ?? user?.id ?? "Swype User";
|
|
4729
|
+
const popupOptions = buildPasskeyPopupOptions({
|
|
4730
|
+
userId: user?.id ?? "unknown",
|
|
4731
|
+
displayName: passkeyDisplayName
|
|
4732
|
+
});
|
|
4733
|
+
const { credentialId, publicKey } = await createPasskeyViaPopup(popupOptions);
|
|
4734
|
+
await completePasskeyRegistration(credentialId, publicKey);
|
|
4661
4735
|
} catch (err) {
|
|
4662
4736
|
setError(err instanceof Error ? err.message : "Failed to register passkey");
|
|
4663
4737
|
} finally {
|
|
4664
4738
|
setRegisteringPasskey(false);
|
|
4665
4739
|
}
|
|
4666
|
-
}, [
|
|
4740
|
+
}, [user, completePasskeyRegistration]);
|
|
4667
4741
|
const handleSelectProvider = react.useCallback((providerId) => {
|
|
4668
4742
|
setSelectedProviderId(providerId);
|
|
4669
4743
|
setSelectedAccountId(null);
|
|
@@ -4781,7 +4855,9 @@ function SwypePaymentInner({
|
|
|
4781
4855
|
onCreatePasskey: handleRegisterPasskey,
|
|
4782
4856
|
onBack: handleLogout,
|
|
4783
4857
|
creating: registeringPasskey,
|
|
4784
|
-
error
|
|
4858
|
+
error,
|
|
4859
|
+
popupFallback: passkeyPopupNeeded,
|
|
4860
|
+
onCreatePasskeyViaPopup: handleCreatePasskeyViaPopup
|
|
4785
4861
|
}
|
|
4786
4862
|
);
|
|
4787
4863
|
}
|
|
@@ -4916,6 +4992,7 @@ function SwypePaymentInner({
|
|
|
4916
4992
|
|
|
4917
4993
|
exports.IconCircle = IconCircle;
|
|
4918
4994
|
exports.OutlineButton = OutlineButton;
|
|
4995
|
+
exports.PasskeyIframeBlockedError = PasskeyIframeBlockedError;
|
|
4919
4996
|
exports.PoweredByFooter = PoweredByFooter;
|
|
4920
4997
|
exports.PrimaryButton = PrimaryButton;
|
|
4921
4998
|
exports.ScreenHeader = ScreenHeader;
|
|
@@ -4927,7 +5004,9 @@ exports.Spinner = Spinner;
|
|
|
4927
5004
|
exports.StepList = StepList;
|
|
4928
5005
|
exports.SwypePayment = SwypePayment;
|
|
4929
5006
|
exports.SwypeProvider = SwypeProvider;
|
|
5007
|
+
exports.buildPasskeyPopupOptions = buildPasskeyPopupOptions;
|
|
4930
5008
|
exports.createPasskeyCredential = createPasskeyCredential;
|
|
5009
|
+
exports.createPasskeyViaPopup = createPasskeyViaPopup;
|
|
4931
5010
|
exports.darkTheme = darkTheme;
|
|
4932
5011
|
exports.deviceHasPasskey = deviceHasPasskey;
|
|
4933
5012
|
exports.findDevicePasskey = findDevicePasskey;
|