@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.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
|
|
676
|
+
function delegatePasskeyGet(options) {
|
|
672
677
|
return new Promise((resolve, reject) => {
|
|
673
|
-
const id = `
|
|
678
|
+
const id = `pg-${++delegationCounter}-${Date.now()}`;
|
|
674
679
|
const timer = setTimeout(() => {
|
|
675
680
|
window.removeEventListener("message", handler);
|
|
676
|
-
reject(new Error("Passkey
|
|
677
|
-
},
|
|
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-
|
|
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
|
|
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-
|
|
698
|
+
window.parent.postMessage({ type: "swype:passkey-get-request", id, options }, "*");
|
|
694
699
|
});
|
|
695
700
|
}
|
|
696
|
-
|
|
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
|
|
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
|
-
|
|
701
|
-
reject(new Error("Passkey
|
|
702
|
-
},
|
|
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-
|
|
707
|
-
|
|
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
|
|
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
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
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:
|
|
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
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|