@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.cjs +236 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -2
- package/dist/index.d.ts +64 -2
- package/dist/index.js +235 -98
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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(
|
|
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(
|
|
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
|
-
|
|
675
|
+
var DELEGATION_GET_TIMEOUT_MS = 3e4;
|
|
676
|
+
function delegatePasskeyGet(options) {
|
|
670
677
|
return new Promise((resolve, reject) => {
|
|
671
|
-
const id = `
|
|
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-
|
|
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
|
|
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-
|
|
698
|
+
window.parent.postMessage({ type: "swype:passkey-get-request", id, options }, "*");
|
|
687
699
|
});
|
|
688
700
|
}
|
|
689
|
-
|
|
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
|
|
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-
|
|
696
|
-
|
|
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
|
|
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
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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:
|
|
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("
|
|
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
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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
|