@swype-org/react-sdk 0.1.87 → 0.1.88
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 +29 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -28
- package/dist/index.d.ts +12 -28
- package/dist/index.js +29 -84
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -391,19 +391,16 @@ interface SwypePaymentProps {
|
|
|
391
391
|
declare function SwypePayment(props: SwypePaymentProps): react_jsx_runtime.JSX.Element;
|
|
392
392
|
|
|
393
393
|
/**
|
|
394
|
-
* Cross-origin iframe passkey
|
|
394
|
+
* Cross-origin iframe passkey helpers.
|
|
395
395
|
*
|
|
396
396
|
* When the webview-app runs inside a cross-origin iframe, WebAuthn
|
|
397
397
|
* ceremonies cannot execute from within the iframe on Safari. For passkey
|
|
398
398
|
* creation, we first attempt a direct `navigator.credentials.create()`
|
|
399
399
|
* call (works in Chrome/Firefox with the iframe permissions policy). If
|
|
400
400
|
* that fails (Safari), we open a same-origin pop-up window on the Swype
|
|
401
|
-
* domain to perform the ceremony
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
* Passkey *assertion* (`navigator.credentials.get`) still delegates to
|
|
406
|
-
* the parent page via postMessage as before.
|
|
401
|
+
* domain to perform the ceremony. The popup writes the result to the
|
|
402
|
+
* server with a verification token, and the opener reads from the server
|
|
403
|
+
* after the popup closes.
|
|
407
404
|
*/
|
|
408
405
|
/**
|
|
409
406
|
* Thrown when `navigator.credentials.create()` fails inside a
|
|
@@ -431,7 +428,7 @@ interface PasskeyPopupOptions {
|
|
|
431
428
|
};
|
|
432
429
|
timeout?: number;
|
|
433
430
|
/** Populated by `createPasskeyViaPopup`; not set by callers. */
|
|
434
|
-
|
|
431
|
+
verificationToken?: string;
|
|
435
432
|
/** Privy JWT so the popup can register the passkey server-side. */
|
|
436
433
|
authToken?: string;
|
|
437
434
|
/** Core API base URL for server-side passkey registration. */
|
|
@@ -442,24 +439,14 @@ interface PasskeyPopupOptions {
|
|
|
442
439
|
* passkey creation. Used as a fallback when Safari blocks
|
|
443
440
|
* `navigator.credentials.create()` inside a cross-origin iframe.
|
|
444
441
|
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
* opener reference.
|
|
449
|
-
*
|
|
450
|
-
* When both client-side channels are blocked (Safari ITP partitions
|
|
451
|
-
* BroadcastChannel by top-level origin), the popup registers the
|
|
452
|
-
* passkey directly with the backend. On popup close, this function
|
|
453
|
-
* checks the server for newly registered passkeys as a final fallback.
|
|
442
|
+
* The popup registers the passkey with the server using a verification
|
|
443
|
+
* token. After the popup closes, this function checks the server for
|
|
444
|
+
* the passkey matching the token.
|
|
454
445
|
*
|
|
455
446
|
* Must be called from a user-gesture handler (e.g. button click) to
|
|
456
447
|
* avoid the browser's pop-up blocker.
|
|
457
|
-
*
|
|
458
|
-
* @param existingCredentialIds - Credential IDs known before the popup
|
|
459
|
-
* opens. Used to diff against server state when the popup closes
|
|
460
|
-
* without delivering a client-side result.
|
|
461
448
|
*/
|
|
462
|
-
declare function createPasskeyViaPopup(options: PasskeyPopupOptions
|
|
449
|
+
declare function createPasskeyViaPopup(options: PasskeyPopupOptions): Promise<{
|
|
463
450
|
credentialId: string;
|
|
464
451
|
publicKey: string;
|
|
465
452
|
}>;
|
|
@@ -467,8 +454,6 @@ interface PasskeyVerifyPopupOptions {
|
|
|
467
454
|
credentialIds: string[];
|
|
468
455
|
rpId: string;
|
|
469
456
|
/** Populated by `findDevicePasskeyViaPopup`; not set by callers. */
|
|
470
|
-
channelId?: string;
|
|
471
|
-
/** Populated by `findDevicePasskeyViaPopup`; not set by callers. */
|
|
472
457
|
verificationToken?: string;
|
|
473
458
|
/** Privy JWT so the popup can report verification server-side. */
|
|
474
459
|
authToken?: string;
|
|
@@ -483,10 +468,9 @@ interface PasskeyVerifyPopupOptions {
|
|
|
483
468
|
* inside a cross-origin iframe. The popup runs on the Swype domain where
|
|
484
469
|
* the rpId matches, so WebAuthn works.
|
|
485
470
|
*
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
489
|
-
* checking the server for the matching token after the popup closes.
|
|
471
|
+
* The popup writes a verification token to the server. After the popup
|
|
472
|
+
* closes, the opener checks the server for the matching token to determine
|
|
473
|
+
* which credential was verified.
|
|
490
474
|
*
|
|
491
475
|
* Must be called from a user-gesture handler (e.g. button click) to
|
|
492
476
|
* avoid the browser's pop-up blocker.
|
package/dist/index.d.ts
CHANGED
|
@@ -391,19 +391,16 @@ interface SwypePaymentProps {
|
|
|
391
391
|
declare function SwypePayment(props: SwypePaymentProps): react_jsx_runtime.JSX.Element;
|
|
392
392
|
|
|
393
393
|
/**
|
|
394
|
-
* Cross-origin iframe passkey
|
|
394
|
+
* Cross-origin iframe passkey helpers.
|
|
395
395
|
*
|
|
396
396
|
* When the webview-app runs inside a cross-origin iframe, WebAuthn
|
|
397
397
|
* ceremonies cannot execute from within the iframe on Safari. For passkey
|
|
398
398
|
* creation, we first attempt a direct `navigator.credentials.create()`
|
|
399
399
|
* call (works in Chrome/Firefox with the iframe permissions policy). If
|
|
400
400
|
* that fails (Safari), we open a same-origin pop-up window on the Swype
|
|
401
|
-
* domain to perform the ceremony
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
* Passkey *assertion* (`navigator.credentials.get`) still delegates to
|
|
406
|
-
* the parent page via postMessage as before.
|
|
401
|
+
* domain to perform the ceremony. The popup writes the result to the
|
|
402
|
+
* server with a verification token, and the opener reads from the server
|
|
403
|
+
* after the popup closes.
|
|
407
404
|
*/
|
|
408
405
|
/**
|
|
409
406
|
* Thrown when `navigator.credentials.create()` fails inside a
|
|
@@ -431,7 +428,7 @@ interface PasskeyPopupOptions {
|
|
|
431
428
|
};
|
|
432
429
|
timeout?: number;
|
|
433
430
|
/** Populated by `createPasskeyViaPopup`; not set by callers. */
|
|
434
|
-
|
|
431
|
+
verificationToken?: string;
|
|
435
432
|
/** Privy JWT so the popup can register the passkey server-side. */
|
|
436
433
|
authToken?: string;
|
|
437
434
|
/** Core API base URL for server-side passkey registration. */
|
|
@@ -442,24 +439,14 @@ interface PasskeyPopupOptions {
|
|
|
442
439
|
* passkey creation. Used as a fallback when Safari blocks
|
|
443
440
|
* `navigator.credentials.create()` inside a cross-origin iframe.
|
|
444
441
|
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
* opener reference.
|
|
449
|
-
*
|
|
450
|
-
* When both client-side channels are blocked (Safari ITP partitions
|
|
451
|
-
* BroadcastChannel by top-level origin), the popup registers the
|
|
452
|
-
* passkey directly with the backend. On popup close, this function
|
|
453
|
-
* checks the server for newly registered passkeys as a final fallback.
|
|
442
|
+
* The popup registers the passkey with the server using a verification
|
|
443
|
+
* token. After the popup closes, this function checks the server for
|
|
444
|
+
* the passkey matching the token.
|
|
454
445
|
*
|
|
455
446
|
* Must be called from a user-gesture handler (e.g. button click) to
|
|
456
447
|
* avoid the browser's pop-up blocker.
|
|
457
|
-
*
|
|
458
|
-
* @param existingCredentialIds - Credential IDs known before the popup
|
|
459
|
-
* opens. Used to diff against server state when the popup closes
|
|
460
|
-
* without delivering a client-side result.
|
|
461
448
|
*/
|
|
462
|
-
declare function createPasskeyViaPopup(options: PasskeyPopupOptions
|
|
449
|
+
declare function createPasskeyViaPopup(options: PasskeyPopupOptions): Promise<{
|
|
463
450
|
credentialId: string;
|
|
464
451
|
publicKey: string;
|
|
465
452
|
}>;
|
|
@@ -467,8 +454,6 @@ interface PasskeyVerifyPopupOptions {
|
|
|
467
454
|
credentialIds: string[];
|
|
468
455
|
rpId: string;
|
|
469
456
|
/** Populated by `findDevicePasskeyViaPopup`; not set by callers. */
|
|
470
|
-
channelId?: string;
|
|
471
|
-
/** Populated by `findDevicePasskeyViaPopup`; not set by callers. */
|
|
472
457
|
verificationToken?: string;
|
|
473
458
|
/** Privy JWT so the popup can report verification server-side. */
|
|
474
459
|
authToken?: string;
|
|
@@ -483,10 +468,9 @@ interface PasskeyVerifyPopupOptions {
|
|
|
483
468
|
* inside a cross-origin iframe. The popup runs on the Swype domain where
|
|
484
469
|
* the rpId matches, so WebAuthn works.
|
|
485
470
|
*
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
489
|
-
* checking the server for the matching token after the popup closes.
|
|
471
|
+
* The popup writes a verification token to the server. After the popup
|
|
472
|
+
* closes, the opener checks the server for the matching token to determine
|
|
473
|
+
* which credential was verified.
|
|
490
474
|
*
|
|
491
475
|
* Must be called from a user-gesture handler (e.g. button click) to
|
|
492
476
|
* avoid the browser's pop-up blocker.
|
package/dist/index.js
CHANGED
|
@@ -725,10 +725,10 @@ function isSafari() {
|
|
|
725
725
|
var POPUP_RESULT_TIMEOUT_MS = 12e4;
|
|
726
726
|
var POPUP_CLOSED_POLL_MS = 500;
|
|
727
727
|
var POPUP_CLOSED_GRACE_MS = 1e3;
|
|
728
|
-
function createPasskeyViaPopup(options
|
|
728
|
+
function createPasskeyViaPopup(options) {
|
|
729
729
|
return new Promise((resolve, reject) => {
|
|
730
|
-
const
|
|
731
|
-
const payload = { ...options,
|
|
730
|
+
const verificationToken = crypto.randomUUID();
|
|
731
|
+
const payload = { ...options, verificationToken };
|
|
732
732
|
const encoded = btoa(JSON.stringify(payload));
|
|
733
733
|
const popupUrl = `${window.location.origin}/passkey-register#${encoded}`;
|
|
734
734
|
const popup = window.open(popupUrl, "swype-passkey");
|
|
@@ -737,22 +737,21 @@ function createPasskeyViaPopup(options, existingCredentialIds = []) {
|
|
|
737
737
|
return;
|
|
738
738
|
}
|
|
739
739
|
let settled = false;
|
|
740
|
-
const channel = typeof BroadcastChannel !== "undefined" ? new BroadcastChannel(channelId) : null;
|
|
741
740
|
const timer = setTimeout(() => {
|
|
742
741
|
cleanup();
|
|
743
742
|
reject(new Error("Passkey creation timed out. Please try again."));
|
|
744
743
|
}, POPUP_RESULT_TIMEOUT_MS);
|
|
745
|
-
let closedGraceTimer = null;
|
|
746
744
|
const closedPoll = setInterval(() => {
|
|
747
745
|
if (popup.closed) {
|
|
748
746
|
clearInterval(closedPoll);
|
|
749
|
-
|
|
747
|
+
setTimeout(() => {
|
|
750
748
|
if (!settled) {
|
|
749
|
+
settled = true;
|
|
751
750
|
cleanup();
|
|
752
|
-
|
|
751
|
+
checkServerForPasskeyByToken(
|
|
753
752
|
options.authToken,
|
|
754
753
|
options.apiBaseUrl,
|
|
755
|
-
|
|
754
|
+
verificationToken
|
|
756
755
|
).then((result) => {
|
|
757
756
|
if (result) {
|
|
758
757
|
resolve(result);
|
|
@@ -766,45 +765,18 @@ function createPasskeyViaPopup(options, existingCredentialIds = []) {
|
|
|
766
765
|
}, POPUP_CLOSED_GRACE_MS);
|
|
767
766
|
}
|
|
768
767
|
}, POPUP_CLOSED_POLL_MS);
|
|
769
|
-
function handleResult(data) {
|
|
770
|
-
if (settled) return;
|
|
771
|
-
if (!data || typeof data !== "object") return;
|
|
772
|
-
if (data.type !== "swype:passkey-popup-result") return;
|
|
773
|
-
settled = true;
|
|
774
|
-
cleanup();
|
|
775
|
-
if (data.error) {
|
|
776
|
-
reject(new Error(data.error));
|
|
777
|
-
} else if (data.result) {
|
|
778
|
-
resolve(data.result);
|
|
779
|
-
} else {
|
|
780
|
-
reject(new Error("Invalid passkey popup response."));
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
if (channel) {
|
|
784
|
-
channel.onmessage = (event) => handleResult(event.data);
|
|
785
|
-
}
|
|
786
|
-
const postMessageHandler = (event) => {
|
|
787
|
-
if (event.source !== popup) return;
|
|
788
|
-
handleResult(event.data);
|
|
789
|
-
};
|
|
790
|
-
window.addEventListener("message", postMessageHandler);
|
|
791
768
|
function cleanup() {
|
|
792
769
|
clearTimeout(timer);
|
|
793
770
|
clearInterval(closedPoll);
|
|
794
|
-
if (closedGraceTimer) clearTimeout(closedGraceTimer);
|
|
795
|
-
window.removeEventListener("message", postMessageHandler);
|
|
796
|
-
channel?.close();
|
|
797
771
|
}
|
|
798
772
|
});
|
|
799
773
|
}
|
|
800
774
|
var VERIFY_POPUP_TIMEOUT_MS = 6e4;
|
|
801
775
|
function findDevicePasskeyViaPopup(options) {
|
|
802
776
|
return new Promise((resolve, reject) => {
|
|
803
|
-
const channelId = `swype-pv-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
804
777
|
const verificationToken = crypto.randomUUID();
|
|
805
778
|
const payload = {
|
|
806
779
|
...options,
|
|
807
|
-
channelId,
|
|
808
780
|
verificationToken
|
|
809
781
|
};
|
|
810
782
|
const encoded = btoa(JSON.stringify(payload));
|
|
@@ -815,7 +787,6 @@ function findDevicePasskeyViaPopup(options) {
|
|
|
815
787
|
return;
|
|
816
788
|
}
|
|
817
789
|
let settled = false;
|
|
818
|
-
const channel = typeof BroadcastChannel !== "undefined" ? new BroadcastChannel(channelId) : null;
|
|
819
790
|
const timer = setTimeout(() => {
|
|
820
791
|
cleanup();
|
|
821
792
|
resolve(null);
|
|
@@ -825,13 +796,14 @@ function findDevicePasskeyViaPopup(options) {
|
|
|
825
796
|
clearInterval(closedPoll);
|
|
826
797
|
setTimeout(() => {
|
|
827
798
|
if (!settled) {
|
|
799
|
+
settled = true;
|
|
828
800
|
cleanup();
|
|
829
|
-
|
|
801
|
+
checkServerForPasskeyByToken(
|
|
830
802
|
options.authToken,
|
|
831
803
|
options.apiBaseUrl,
|
|
832
804
|
verificationToken
|
|
833
|
-
).then((
|
|
834
|
-
resolve(credentialId);
|
|
805
|
+
).then((result) => {
|
|
806
|
+
resolve(result?.credentialId ?? null);
|
|
835
807
|
}).catch(() => {
|
|
836
808
|
resolve(null);
|
|
837
809
|
});
|
|
@@ -839,38 +811,13 @@ function findDevicePasskeyViaPopup(options) {
|
|
|
839
811
|
}, POPUP_CLOSED_GRACE_MS);
|
|
840
812
|
}
|
|
841
813
|
}, POPUP_CLOSED_POLL_MS);
|
|
842
|
-
function handleResult(data) {
|
|
843
|
-
if (settled) return;
|
|
844
|
-
if (!data || typeof data !== "object") return;
|
|
845
|
-
if (data.type !== "swype:passkey-verify-result") return;
|
|
846
|
-
settled = true;
|
|
847
|
-
cleanup();
|
|
848
|
-
if (data.error) {
|
|
849
|
-
resolve(null);
|
|
850
|
-
} else if (data.result && typeof data.result === "object") {
|
|
851
|
-
const result = data.result;
|
|
852
|
-
resolve(result.credentialId ?? null);
|
|
853
|
-
} else {
|
|
854
|
-
resolve(null);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
if (channel) {
|
|
858
|
-
channel.onmessage = (event) => handleResult(event.data);
|
|
859
|
-
}
|
|
860
|
-
const postMessageHandler = (event) => {
|
|
861
|
-
if (event.source !== popup) return;
|
|
862
|
-
handleResult(event.data);
|
|
863
|
-
};
|
|
864
|
-
window.addEventListener("message", postMessageHandler);
|
|
865
814
|
function cleanup() {
|
|
866
815
|
clearTimeout(timer);
|
|
867
816
|
clearInterval(closedPoll);
|
|
868
|
-
window.removeEventListener("message", postMessageHandler);
|
|
869
|
-
channel?.close();
|
|
870
817
|
}
|
|
871
818
|
});
|
|
872
819
|
}
|
|
873
|
-
async function
|
|
820
|
+
async function checkServerForPasskeyByToken(authToken, apiBaseUrl, verificationToken) {
|
|
874
821
|
if (!authToken || !apiBaseUrl) return null;
|
|
875
822
|
const res = await fetch(`${apiBaseUrl}/v1/users/config`, {
|
|
876
823
|
headers: { Authorization: `Bearer ${authToken}` }
|
|
@@ -879,19 +826,7 @@ async function checkServerForVerifiedPasskey(authToken, apiBaseUrl, verification
|
|
|
879
826
|
const body = await res.json();
|
|
880
827
|
const passkeys = body.config.passkeys ?? [];
|
|
881
828
|
const matched = passkeys.find((p) => p.lastVerificationToken === verificationToken);
|
|
882
|
-
return matched
|
|
883
|
-
}
|
|
884
|
-
async function checkServerForNewPasskey(authToken, apiBaseUrl, existingCredentialIds) {
|
|
885
|
-
if (!authToken || !apiBaseUrl) return null;
|
|
886
|
-
const res = await fetch(`${apiBaseUrl}/v1/users/config`, {
|
|
887
|
-
headers: { Authorization: `Bearer ${authToken}` }
|
|
888
|
-
});
|
|
889
|
-
if (!res.ok) return null;
|
|
890
|
-
const body = await res.json();
|
|
891
|
-
const passkeys = body.config.passkeys ?? [];
|
|
892
|
-
const existingSet = new Set(existingCredentialIds);
|
|
893
|
-
const newPasskey = passkeys.find((p) => !existingSet.has(p.credentialId));
|
|
894
|
-
return newPasskey ?? null;
|
|
829
|
+
return matched ? { credentialId: matched.credentialId, publicKey: matched.publicKey } : null;
|
|
895
830
|
}
|
|
896
831
|
|
|
897
832
|
// src/hooks.ts
|
|
@@ -5596,18 +5531,28 @@ function SwypePaymentInner({
|
|
|
5596
5531
|
authToken: token ?? void 0,
|
|
5597
5532
|
apiBaseUrl
|
|
5598
5533
|
});
|
|
5599
|
-
const { credentialId
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
);
|
|
5603
|
-
|
|
5534
|
+
const { credentialId } = await createPasskeyViaPopup(popupOptions);
|
|
5535
|
+
setActiveCredentialId(credentialId);
|
|
5536
|
+
localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
|
|
5537
|
+
setPasskeyPopupNeeded(false);
|
|
5538
|
+
const resolved = resolvePostAuthStep({
|
|
5539
|
+
hasPasskey: true,
|
|
5540
|
+
accounts,
|
|
5541
|
+
persistedMobileFlow: loadMobileFlowState(),
|
|
5542
|
+
mobileSetupInProgress: mobileSetupFlowRef.current,
|
|
5543
|
+
connectingNewAccount
|
|
5544
|
+
});
|
|
5545
|
+
if (resolved.clearPersistedFlow) {
|
|
5546
|
+
clearMobileFlowState();
|
|
5547
|
+
}
|
|
5548
|
+
setStep(resolved.step);
|
|
5604
5549
|
} catch (err) {
|
|
5605
5550
|
captureException(err);
|
|
5606
5551
|
setError(err instanceof Error ? err.message : "Failed to register passkey");
|
|
5607
5552
|
} finally {
|
|
5608
5553
|
setRegisteringPasskey(false);
|
|
5609
5554
|
}
|
|
5610
|
-
}, [user,
|
|
5555
|
+
}, [user, getAccessToken, apiBaseUrl, accounts, connectingNewAccount]);
|
|
5611
5556
|
const handleVerifyPasskeyViaPopup = useCallback(async () => {
|
|
5612
5557
|
setVerifyingPasskeyPopup(true);
|
|
5613
5558
|
setError(null);
|