@swype-org/react-sdk 0.1.82 → 0.1.84

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.d.cts CHANGED
@@ -422,6 +422,10 @@ interface PasskeyPopupOptions {
422
422
  timeout?: number;
423
423
  /** Populated by `createPasskeyViaPopup`; not set by callers. */
424
424
  channelId?: string;
425
+ /** Privy JWT so the popup can register the passkey server-side. */
426
+ authToken?: string;
427
+ /** Core API base URL for server-side passkey registration. */
428
+ apiBaseUrl?: string;
425
429
  }
426
430
  /**
427
431
  * Opens a same-origin pop-up window on the Swype domain to perform
@@ -433,10 +437,19 @@ interface PasskeyPopupOptions {
433
437
  * Falls back to `window.postMessage` for browsers that preserve the
434
438
  * opener reference.
435
439
  *
440
+ * When both client-side channels are blocked (Safari ITP partitions
441
+ * BroadcastChannel by top-level origin), the popup registers the
442
+ * passkey directly with the backend. On popup close, this function
443
+ * checks the server for newly registered passkeys as a final fallback.
444
+ *
436
445
  * Must be called from a user-gesture handler (e.g. button click) to
437
446
  * avoid the browser's pop-up blocker.
447
+ *
448
+ * @param existingCredentialIds - Credential IDs known before the popup
449
+ * opens. Used to diff against server state when the popup closes
450
+ * without delivering a client-side result.
438
451
  */
439
- declare function createPasskeyViaPopup(options: PasskeyPopupOptions): Promise<{
452
+ declare function createPasskeyViaPopup(options: PasskeyPopupOptions, existingCredentialIds?: string[]): Promise<{
440
453
  credentialId: string;
441
454
  publicKey: string;
442
455
  }>;
@@ -464,10 +477,16 @@ declare function createPasskeyCredential(params: {
464
477
  * Builds the {@link PasskeyPopupOptions} for a user. Called by the UI
465
478
  * layer when it needs to open the passkey creation pop-up after
466
479
  * `createPasskeyCredential` throws {@link PasskeyIframeBlockedError}.
480
+ *
481
+ * @param params.authToken - Privy JWT so the popup can register the
482
+ * passkey server-side (Safari fallback when BroadcastChannel is blocked).
483
+ * @param params.apiBaseUrl - Core API base URL for server-side registration.
467
484
  */
468
485
  declare function buildPasskeyPopupOptions(params: {
469
486
  userId: string;
470
487
  displayName: string;
488
+ authToken?: string;
489
+ apiBaseUrl?: string;
471
490
  }): PasskeyPopupOptions;
472
491
  /**
473
492
  * @deprecated Use {@link findDevicePasskey} instead, which checks all
package/dist/index.d.ts CHANGED
@@ -422,6 +422,10 @@ interface PasskeyPopupOptions {
422
422
  timeout?: number;
423
423
  /** Populated by `createPasskeyViaPopup`; not set by callers. */
424
424
  channelId?: string;
425
+ /** Privy JWT so the popup can register the passkey server-side. */
426
+ authToken?: string;
427
+ /** Core API base URL for server-side passkey registration. */
428
+ apiBaseUrl?: string;
425
429
  }
426
430
  /**
427
431
  * Opens a same-origin pop-up window on the Swype domain to perform
@@ -433,10 +437,19 @@ interface PasskeyPopupOptions {
433
437
  * Falls back to `window.postMessage` for browsers that preserve the
434
438
  * opener reference.
435
439
  *
440
+ * When both client-side channels are blocked (Safari ITP partitions
441
+ * BroadcastChannel by top-level origin), the popup registers the
442
+ * passkey directly with the backend. On popup close, this function
443
+ * checks the server for newly registered passkeys as a final fallback.
444
+ *
436
445
  * Must be called from a user-gesture handler (e.g. button click) to
437
446
  * avoid the browser's pop-up blocker.
447
+ *
448
+ * @param existingCredentialIds - Credential IDs known before the popup
449
+ * opens. Used to diff against server state when the popup closes
450
+ * without delivering a client-side result.
438
451
  */
439
- declare function createPasskeyViaPopup(options: PasskeyPopupOptions): Promise<{
452
+ declare function createPasskeyViaPopup(options: PasskeyPopupOptions, existingCredentialIds?: string[]): Promise<{
440
453
  credentialId: string;
441
454
  publicKey: string;
442
455
  }>;
@@ -464,10 +477,16 @@ declare function createPasskeyCredential(params: {
464
477
  * Builds the {@link PasskeyPopupOptions} for a user. Called by the UI
465
478
  * layer when it needs to open the passkey creation pop-up after
466
479
  * `createPasskeyCredential` throws {@link PasskeyIframeBlockedError}.
480
+ *
481
+ * @param params.authToken - Privy JWT so the popup can register the
482
+ * passkey server-side (Safari fallback when BroadcastChannel is blocked).
483
+ * @param params.apiBaseUrl - Core API base URL for server-side registration.
467
484
  */
468
485
  declare function buildPasskeyPopupOptions(params: {
469
486
  userId: string;
470
487
  displayName: string;
488
+ authToken?: string;
489
+ apiBaseUrl?: string;
471
490
  }): PasskeyPopupOptions;
472
491
  /**
473
492
  * @deprecated Use {@link findDevicePasskey} instead, which checks all
package/dist/index.js CHANGED
@@ -712,7 +712,8 @@ function isSafari() {
712
712
  }
713
713
  var POPUP_RESULT_TIMEOUT_MS = 12e4;
714
714
  var POPUP_CLOSED_POLL_MS = 500;
715
- function createPasskeyViaPopup(options) {
715
+ var POPUP_CLOSED_GRACE_MS = 1e3;
716
+ function createPasskeyViaPopup(options, existingCredentialIds = []) {
716
717
  return new Promise((resolve, reject) => {
717
718
  const channelId = `swype-pk-${Date.now()}-${Math.random().toString(36).slice(2)}`;
718
719
  const payload = { ...options, channelId };
@@ -729,10 +730,28 @@ function createPasskeyViaPopup(options) {
729
730
  cleanup();
730
731
  reject(new Error("Passkey creation timed out. Please try again."));
731
732
  }, POPUP_RESULT_TIMEOUT_MS);
733
+ let closedGraceTimer = null;
732
734
  const closedPoll = setInterval(() => {
733
735
  if (popup.closed) {
734
- cleanup();
735
- reject(new Error("Passkey setup window was closed before completing."));
736
+ clearInterval(closedPoll);
737
+ closedGraceTimer = setTimeout(() => {
738
+ if (!settled) {
739
+ cleanup();
740
+ checkServerForNewPasskey(
741
+ options.authToken,
742
+ options.apiBaseUrl,
743
+ existingCredentialIds
744
+ ).then((result) => {
745
+ if (result) {
746
+ resolve(result);
747
+ } else {
748
+ reject(new Error("Passkey setup window was closed before completing."));
749
+ }
750
+ }).catch(() => {
751
+ reject(new Error("Passkey setup window was closed before completing."));
752
+ });
753
+ }
754
+ }, POPUP_CLOSED_GRACE_MS);
736
755
  }
737
756
  }, POPUP_CLOSED_POLL_MS);
738
757
  function handleResult(data) {
@@ -760,11 +779,24 @@ function createPasskeyViaPopup(options) {
760
779
  function cleanup() {
761
780
  clearTimeout(timer);
762
781
  clearInterval(closedPoll);
782
+ if (closedGraceTimer) clearTimeout(closedGraceTimer);
763
783
  window.removeEventListener("message", postMessageHandler);
764
784
  channel?.close();
765
785
  }
766
786
  });
767
787
  }
788
+ async function checkServerForNewPasskey(authToken, apiBaseUrl, existingCredentialIds) {
789
+ if (!authToken || !apiBaseUrl) return null;
790
+ const res = await fetch(`${apiBaseUrl}/v1/users/config`, {
791
+ headers: { Authorization: `Bearer ${authToken}` }
792
+ });
793
+ if (!res.ok) return null;
794
+ const body = await res.json();
795
+ const passkeys = body.config.passkeys ?? [];
796
+ const existingSet = new Set(existingCredentialIds);
797
+ const newPasskey = passkeys.find((p) => !existingSet.has(p.credentialId));
798
+ return newPasskey ?? null;
799
+ }
768
800
 
769
801
  // src/hooks.ts
770
802
  var WALLET_CLIENT_MAX_ATTEMPTS = 15;
@@ -959,7 +991,9 @@ function buildPasskeyPopupOptions(params) {
959
991
  residentKey: "preferred",
960
992
  userVerification: "required"
961
993
  },
962
- timeout: 6e4
994
+ timeout: 6e4,
995
+ authToken: params.authToken,
996
+ apiBaseUrl: params.apiBaseUrl
963
997
  };
964
998
  }
965
999
  async function deviceHasPasskey(credentialId) {
@@ -4578,6 +4612,7 @@ function SwypePaymentInner({
4578
4612
  if (typeof window === "undefined") return null;
4579
4613
  return window.localStorage.getItem(ACTIVE_CREDENTIAL_STORAGE_KEY);
4580
4614
  });
4615
+ const [knownCredentialIds, setKnownCredentialIds] = useState([]);
4581
4616
  const [authInput, setAuthInput] = useState("");
4582
4617
  const [verificationTarget, setVerificationTarget] = useState(null);
4583
4618
  const [otpCode, setOtpCode] = useState("");
@@ -4867,6 +4902,7 @@ function SwypePaymentInner({
4867
4902
  setOneTapLimit(config.defaultAllowance);
4868
4903
  }
4869
4904
  const allPasskeys = config.passkeys ?? (config.passkey ? [config.passkey] : []);
4905
+ setKnownCredentialIds(allPasskeys.map((p) => p.credentialId));
4870
4906
  if (allPasskeys.length === 0) {
4871
4907
  setStep("create-passkey");
4872
4908
  return;
@@ -5383,12 +5419,18 @@ function SwypePaymentInner({
5383
5419
  setRegisteringPasskey(true);
5384
5420
  setError(null);
5385
5421
  try {
5422
+ const token = await getAccessToken();
5386
5423
  const passkeyDisplayName = user?.email?.address ?? user?.google?.name ?? user?.id ?? "Swype User";
5387
5424
  const popupOptions = buildPasskeyPopupOptions({
5388
5425
  userId: user?.id ?? "unknown",
5389
- displayName: passkeyDisplayName
5426
+ displayName: passkeyDisplayName,
5427
+ authToken: token ?? void 0,
5428
+ apiBaseUrl
5390
5429
  });
5391
- const { credentialId, publicKey } = await createPasskeyViaPopup(popupOptions);
5430
+ const { credentialId, publicKey } = await createPasskeyViaPopup(
5431
+ popupOptions,
5432
+ knownCredentialIds
5433
+ );
5392
5434
  await completePasskeyRegistration(credentialId, publicKey);
5393
5435
  } catch (err) {
5394
5436
  captureException(err);
@@ -5396,7 +5438,7 @@ function SwypePaymentInner({
5396
5438
  } finally {
5397
5439
  setRegisteringPasskey(false);
5398
5440
  }
5399
- }, [user, completePasskeyRegistration]);
5441
+ }, [user, completePasskeyRegistration, getAccessToken, apiBaseUrl, knownCredentialIds]);
5400
5442
  const handleSelectProvider = useCallback((providerId) => {
5401
5443
  setSelectedProviderId(providerId);
5402
5444
  setSelectedAccountId(null);