@lumiapassport/ui-kit 1.16.0 → 1.16.2

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
@@ -462,7 +462,7 @@ declare class IframeManager {
462
462
  private iframeUrl;
463
463
  private projectId;
464
464
  private debug;
465
- private sessionToken;
465
+ private channelToken;
466
466
  private isReady;
467
467
  private readyPromise;
468
468
  private readyResolve;
@@ -478,6 +478,9 @@ declare class IframeManager {
478
478
  private providerConnections;
479
479
  private readonly REQUEST_TIMEOUT;
480
480
  private readonly NONCE_EXPIRY;
481
+ private readonly HEARTBEAT_INTERVAL;
482
+ private heartbeatInterval?;
483
+ private isReconnecting;
481
484
  constructor(config: IframeManagerConfig);
482
485
  /**
483
486
  * Initialize iframe and wait for it to be ready
@@ -488,9 +491,25 @@ declare class IframeManager {
488
491
  */
489
492
  setOnWalletReady(callback: (status: WalletReadyStatus) => void): void;
490
493
  /**
491
- * Authenticate SDK with iframe to establish session
494
+ * Authenticate SDK with iframe to establish secure channel
492
495
  */
493
496
  private authenticateSDK;
497
+ /**
498
+ * Start periodic heartbeat to check SDK channel validity
499
+ */
500
+ private startHeartbeat;
501
+ /**
502
+ * Stop heartbeat
503
+ */
504
+ private stopHeartbeat;
505
+ /**
506
+ * Reconnect SDK channel after it becomes invalid
507
+ */
508
+ reconnect(): Promise<void>;
509
+ /**
510
+ * Setup visibility change handler to check channel when tab becomes visible
511
+ */
512
+ private setupVisibilityHandler;
494
513
  /**
495
514
  * Handle incoming postMessage events
496
515
  */
@@ -504,7 +523,7 @@ declare class IframeManager {
504
523
  */
505
524
  sendMessage(type: string, data: Record<string, any>): Promise<any>;
506
525
  /**
507
- * Compute HMAC for message authentication
526
+ * Compute HMAC for message authentication using SDK channel token
508
527
  */
509
528
  private computeHMAC;
510
529
  /**
package/dist/index.d.ts CHANGED
@@ -462,7 +462,7 @@ declare class IframeManager {
462
462
  private iframeUrl;
463
463
  private projectId;
464
464
  private debug;
465
- private sessionToken;
465
+ private channelToken;
466
466
  private isReady;
467
467
  private readyPromise;
468
468
  private readyResolve;
@@ -478,6 +478,9 @@ declare class IframeManager {
478
478
  private providerConnections;
479
479
  private readonly REQUEST_TIMEOUT;
480
480
  private readonly NONCE_EXPIRY;
481
+ private readonly HEARTBEAT_INTERVAL;
482
+ private heartbeatInterval?;
483
+ private isReconnecting;
481
484
  constructor(config: IframeManagerConfig);
482
485
  /**
483
486
  * Initialize iframe and wait for it to be ready
@@ -488,9 +491,25 @@ declare class IframeManager {
488
491
  */
489
492
  setOnWalletReady(callback: (status: WalletReadyStatus) => void): void;
490
493
  /**
491
- * Authenticate SDK with iframe to establish session
494
+ * Authenticate SDK with iframe to establish secure channel
492
495
  */
493
496
  private authenticateSDK;
497
+ /**
498
+ * Start periodic heartbeat to check SDK channel validity
499
+ */
500
+ private startHeartbeat;
501
+ /**
502
+ * Stop heartbeat
503
+ */
504
+ private stopHeartbeat;
505
+ /**
506
+ * Reconnect SDK channel after it becomes invalid
507
+ */
508
+ reconnect(): Promise<void>;
509
+ /**
510
+ * Setup visibility change handler to check channel when tab becomes visible
511
+ */
512
+ private setupVisibilityHandler;
494
513
  /**
495
514
  * Handle incoming postMessage events
496
515
  */
@@ -504,7 +523,7 @@ declare class IframeManager {
504
523
  */
505
524
  sendMessage(type: string, data: Record<string, any>): Promise<any>;
506
525
  /**
507
- * Compute HMAC for message authentication
526
+ * Compute HMAC for message authentication using SDK channel token
508
527
  */
509
528
  private computeHMAC;
510
529
  /**
package/dist/index.js CHANGED
@@ -693,52 +693,72 @@ async function ensureDkgAndGetOwner(userId, _clientSeedHex) {
693
693
  throw error;
694
694
  }
695
695
  }
696
+ function isChannelError(error) {
697
+ const message = error.message.toLowerCase();
698
+ return message.includes("invalid sdk channel") || message.includes("sdk channel not found") || message.includes("sdk channel expired") || // Backward compatibility
699
+ message === "invalid session";
700
+ }
696
701
  async function signDigestWithMpc(userId, digest32, userOpDetails) {
702
+ const MAX_RETRIES = 1;
697
703
  const startTime = performance.now();
698
704
  currentSigningStats = {
699
705
  startTime,
700
706
  rounds: []
701
707
  };
702
- try {
703
- const iframeManager = getIframeManager();
704
- const { jwtTokenManager: jwtTokenManager4 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
705
- const accessToken = jwtTokenManager4.getAccessToken();
706
- if (!accessToken) {
707
- throw new Error("No access token available for signing");
708
- }
709
- const transaction = {
710
- to: userOpDetails?.callTarget || "0x0000000000000000000000000000000000000000",
711
- value: userOpDetails?.value || "0",
712
- data: userOpDetails?.callData || "0x",
713
- digest32,
714
- // Pre-computed digest - DO NOT recompute!
715
- // Additional UserOp fields for display in confirmation modal
716
- userOpDetails
717
- };
718
- const signature = await iframeManager.signTransaction(userId, transaction, accessToken);
719
- const endTime = performance.now();
720
- currentSigningStats.endTime = endTime;
721
- currentSigningStats.totalDurationMs = endTime - startTime;
722
- return signature;
723
- } catch (error) {
724
- logSdkError(
725
- error instanceof Error ? error : new Error("MPC signing failed"),
726
- { userId, hasUserOpDetails: !!userOpDetails },
727
- "iframe-mpc"
728
- );
729
- const endTime = performance.now();
730
- if (currentSigningStats) {
708
+ let lastError;
709
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
710
+ try {
711
+ const iframeManager = getIframeManager();
712
+ const { jwtTokenManager: jwtTokenManager4 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
713
+ const accessToken = jwtTokenManager4.getAccessToken();
714
+ if (!accessToken) {
715
+ throw new Error("No access token available for signing");
716
+ }
717
+ const transaction = {
718
+ to: userOpDetails?.callTarget || "0x0000000000000000000000000000000000000000",
719
+ value: userOpDetails?.value || "0",
720
+ data: userOpDetails?.callData || "0x",
721
+ digest32,
722
+ // Pre-computed digest - DO NOT recompute!
723
+ // Additional UserOp fields for display in confirmation modal
724
+ userOpDetails
725
+ };
726
+ const signature = await iframeManager.signTransaction(userId, transaction, accessToken);
727
+ const endTime = performance.now();
731
728
  currentSigningStats.endTime = endTime;
732
729
  currentSigningStats.totalDurationMs = endTime - startTime;
730
+ return signature;
731
+ } catch (error) {
732
+ lastError = error instanceof Error ? error : new Error("MPC signing failed");
733
+ if (isChannelError(lastError) && attempt < MAX_RETRIES) {
734
+ console.warn(`[MPC] SDK channel error, reconnecting and retrying (attempt ${attempt + 1})...`);
735
+ try {
736
+ await getIframeManager().reconnect();
737
+ continue;
738
+ } catch (reconnectError) {
739
+ console.error("[MPC] Reconnect failed:", reconnectError);
740
+ }
741
+ }
742
+ logSdkError(
743
+ lastError,
744
+ { userId, hasUserOpDetails: !!userOpDetails, attempt },
745
+ "iframe-mpc"
746
+ );
747
+ const endTime = performance.now();
748
+ if (currentSigningStats) {
749
+ currentSigningStats.endTime = endTime;
750
+ currentSigningStats.totalDurationMs = endTime - startTime;
751
+ }
752
+ if (lastError instanceof LumiaPassportError) {
753
+ throw lastError;
754
+ }
755
+ throw new LumiaPassportError(
756
+ lastError.message,
757
+ ErrorCodes.MPC_SIGNING_ERROR
758
+ );
733
759
  }
734
- if (error instanceof LumiaPassportError) {
735
- throw error;
736
- }
737
- throw new LumiaPassportError(
738
- error instanceof Error ? error.message : "MPC signing failed",
739
- ErrorCodes.MPC_SIGNING_ERROR
740
- );
741
760
  }
761
+ throw lastError || new Error("MPC signing failed after retries");
742
762
  }
743
763
  async function signTypedDataWithMpc(userId, digest32, typedData) {
744
764
  const startTime = performance.now();
@@ -1369,7 +1389,7 @@ async function getShareVaultToken(scopes) {
1369
1389
  }
1370
1390
  return {
1371
1391
  token: data.resourceToken,
1372
- expiresAt: Date.now() + data.expiresIn * 1e3
1392
+ expiresAt: Date.now() + (data.expiresIn || 300) * 1e3
1373
1393
  };
1374
1394
  }
1375
1395
  async function getShareRecoveryStats() {
@@ -2623,10 +2643,9 @@ var init_iframe_manager = __esm({
2623
2643
  "src/internal/lib/iframe-manager.ts"() {
2624
2644
  init_errors();
2625
2645
  IframeManager = class {
2626
- // 5 minutes
2627
2646
  constructor(config) {
2628
2647
  this.iframe = null;
2629
- this.sessionToken = null;
2648
+ this.channelToken = null;
2630
2649
  this.isReady = false;
2631
2650
  // Message handling
2632
2651
  this.pendingRequests = /* @__PURE__ */ new Map();
@@ -2643,6 +2662,9 @@ var init_iframe_manager = __esm({
2643
2662
  this.REQUEST_TIMEOUT = 3e5;
2644
2663
  // 5 minutes (for user interactions like consent)
2645
2664
  this.NONCE_EXPIRY = 3e5;
2665
+ // 5 minutes
2666
+ this.HEARTBEAT_INTERVAL = 5 * 60 * 1e3;
2667
+ this.isReconnecting = false;
2646
2668
  this.iframeUrl = config.iframeUrl;
2647
2669
  this.projectId = config.projectId;
2648
2670
  this.debug = config.debug || false;
@@ -2683,6 +2705,8 @@ var init_iframe_manager = __esm({
2683
2705
  await this.readyPromise;
2684
2706
  await this.authenticateSDK();
2685
2707
  await this.primeProviderSessions();
2708
+ this.startHeartbeat();
2709
+ this.setupVisibilityHandler();
2686
2710
  this.log("[IframeManager] \u2705 Iframe ready and authenticated");
2687
2711
  }
2688
2712
  /**
@@ -2692,18 +2716,93 @@ var init_iframe_manager = __esm({
2692
2716
  this.onWalletReadyCallback = callback;
2693
2717
  }
2694
2718
  /**
2695
- * Authenticate SDK with iframe to establish session
2719
+ * Authenticate SDK with iframe to establish secure channel
2696
2720
  */
2697
2721
  async authenticateSDK() {
2698
2722
  const response = await this.sendMessage("SDK_AUTH", {
2699
2723
  projectId: this.projectId
2700
2724
  });
2701
2725
  if (response.type === "LUMIA_PASSPORT_SDK_AUTH_SUCCESS") {
2702
- this.sessionToken = response.sessionToken;
2726
+ this.channelToken = response.sessionToken;
2703
2727
  } else {
2704
- throw new Error("SDK authentication failed");
2728
+ throw new Error("SDK channel authentication failed");
2705
2729
  }
2706
2730
  }
2731
+ /**
2732
+ * Start periodic heartbeat to check SDK channel validity
2733
+ */
2734
+ startHeartbeat() {
2735
+ if (this.heartbeatInterval) {
2736
+ clearInterval(this.heartbeatInterval);
2737
+ }
2738
+ this.heartbeatInterval = setInterval(async () => {
2739
+ if (!this.channelToken) return;
2740
+ try {
2741
+ const response = await this.sendMessage("SDK_CHANNEL_HEARTBEAT", {});
2742
+ if (!response.valid) {
2743
+ this.log("[IframeManager] SDK channel invalid, reconnecting...");
2744
+ await this.reconnect();
2745
+ }
2746
+ } catch (error) {
2747
+ this.log("[IframeManager] Heartbeat failed:", error);
2748
+ await this.reconnect();
2749
+ }
2750
+ }, this.HEARTBEAT_INTERVAL);
2751
+ this.log("[IframeManager] Heartbeat started (interval: 5 min)");
2752
+ }
2753
+ /**
2754
+ * Stop heartbeat
2755
+ */
2756
+ stopHeartbeat() {
2757
+ if (this.heartbeatInterval) {
2758
+ clearInterval(this.heartbeatInterval);
2759
+ this.heartbeatInterval = void 0;
2760
+ this.log("[IframeManager] Heartbeat stopped");
2761
+ }
2762
+ }
2763
+ /**
2764
+ * Reconnect SDK channel after it becomes invalid
2765
+ */
2766
+ async reconnect() {
2767
+ if (this.isReconnecting) {
2768
+ this.log("[IframeManager] Already reconnecting, skipping...");
2769
+ return;
2770
+ }
2771
+ this.isReconnecting = true;
2772
+ this.log("[IframeManager] Reconnecting SDK channel...");
2773
+ try {
2774
+ this.channelToken = null;
2775
+ await this.authenticateSDK();
2776
+ this.log("[IframeManager] \u2705 SDK channel reconnected");
2777
+ } catch (error) {
2778
+ this.log("[IframeManager] \u274C Reconnect failed:", error);
2779
+ throw error;
2780
+ } finally {
2781
+ this.isReconnecting = false;
2782
+ }
2783
+ }
2784
+ /**
2785
+ * Setup visibility change handler to check channel when tab becomes visible
2786
+ */
2787
+ setupVisibilityHandler() {
2788
+ if (typeof document === "undefined") return;
2789
+ document.addEventListener("visibilitychange", async () => {
2790
+ if (document.visibilityState === "visible" && this.channelToken) {
2791
+ this.log("[IframeManager] Tab visible, checking SDK channel...");
2792
+ try {
2793
+ const response = await this.sendMessage("SDK_CHANNEL_HEARTBEAT", {});
2794
+ if (!response.valid) {
2795
+ this.log("[IframeManager] SDK channel expired while tab was hidden");
2796
+ await this.reconnect();
2797
+ }
2798
+ } catch (error) {
2799
+ this.log("[IframeManager] Channel check failed, reconnecting...");
2800
+ await this.reconnect();
2801
+ }
2802
+ }
2803
+ });
2804
+ this.log("[IframeManager] Visibility handler setup");
2805
+ }
2707
2806
  /**
2708
2807
  * Handle incoming postMessage events
2709
2808
  */
@@ -2727,6 +2826,7 @@ var init_iframe_manager = __esm({
2727
2826
  "LUMIA_PASSPORT_TRUSTED_APP_REMOVED",
2728
2827
  "LUMIA_PASSPORT_REQUEST_NEW_TOKEN",
2729
2828
  "LUMIA_PASSPORT_TOKEN_REFRESHED",
2829
+ "LUMIA_PASSPORT_HEARTBEAT_RESPONSE",
2730
2830
  "LUMIA_PASSPORT_RESPONSE",
2731
2831
  "LUMIA_PASSPORT_ERROR"
2732
2832
  ];
@@ -2855,10 +2955,11 @@ var init_iframe_manager = __esm({
2855
2955
  projectId: this.projectId,
2856
2956
  data: {
2857
2957
  ...data,
2858
- sessionToken: this.sessionToken
2958
+ sessionToken: this.channelToken
2959
+ // named sessionToken for backward compatibility with iframe
2859
2960
  }
2860
2961
  };
2861
- if (this.sessionToken) {
2962
+ if (this.channelToken) {
2862
2963
  message.hmac = await this.computeHMAC(message);
2863
2964
  }
2864
2965
  const responsePromise = new Promise((resolve, reject) => {
@@ -2880,7 +2981,7 @@ var init_iframe_manager = __esm({
2880
2981
  return responsePromise;
2881
2982
  }
2882
2983
  /**
2883
- * Compute HMAC for message authentication
2984
+ * Compute HMAC for message authentication using SDK channel token
2884
2985
  */
2885
2986
  async computeHMAC(message) {
2886
2987
  const encoder = new TextEncoder();
@@ -2892,7 +2993,7 @@ var init_iframe_manager = __esm({
2892
2993
  data: JSON.stringify(message.data)
2893
2994
  });
2894
2995
  const data = encoder.encode(payload);
2895
- const key = encoder.encode(this.sessionToken);
2996
+ const key = encoder.encode(this.channelToken);
2896
2997
  const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
2897
2998
  const signature = await crypto.subtle.sign("HMAC", cryptoKey, data);
2898
2999
  return Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
@@ -3282,6 +3383,24 @@ var init_iframe_manager = __esm({
3282
3383
  });
3283
3384
  } else if (event.data.type === "X_AUTH_SUCCESS" && eventProvider === "x" && providerKey === "x") {
3284
3385
  this.log("[IframeManager] X auth successful from popup:", event.data);
3386
+ if (event.data.tokens) {
3387
+ this.log("[IframeManager] Tokens received in postMessage, storing via jwtTokenManager");
3388
+ Promise.resolve().then(() => (init_auth(), auth_exports)).then(({ jwtTokenManager: jwtTokenManager4 }) => {
3389
+ return jwtTokenManager4.setTokens({
3390
+ accessToken: event.data.tokens.accessToken,
3391
+ refreshToken: event.data.tokens.refreshToken,
3392
+ userId: event.data.user.userId,
3393
+ expiresIn: event.data.user.expiresIn || 3600,
3394
+ hasKeyshare: event.data.user.hasKeyshare || false,
3395
+ displayName: event.data.user.displayName || null,
3396
+ providers: event.data.user.providers || ["x"]
3397
+ });
3398
+ }).then(() => {
3399
+ this.log("[IframeManager] Tokens stored successfully");
3400
+ }).catch((tokenError) => {
3401
+ this.log("[IframeManager] Warning: Failed to store tokens:", tokenError);
3402
+ });
3403
+ }
3285
3404
  finalize({
3286
3405
  success: true,
3287
3406
  user: event.data.user,
@@ -3637,6 +3756,7 @@ var init_iframe_manager = __esm({
3637
3756
  */
3638
3757
  destroy() {
3639
3758
  this.log("[IframeManager] Destroying iframe...");
3759
+ this.stopHeartbeat();
3640
3760
  this.pendingRequests.forEach((pending) => {
3641
3761
  pending.reject(new Error("Iframe manager destroyed"));
3642
3762
  });
@@ -3650,7 +3770,7 @@ var init_iframe_manager = __esm({
3650
3770
  }
3651
3771
  this.iframe = null;
3652
3772
  this.isReady = false;
3653
- this.sessionToken = null;
3773
+ this.channelToken = null;
3654
3774
  this.log("[IframeManager] \u2705 Destroyed");
3655
3775
  }
3656
3776
  /**
@@ -5518,7 +5638,7 @@ function Header() {
5518
5638
  // package.json
5519
5639
  var package_default = {
5520
5640
  name: "@lumiapassport/ui-kit",
5521
- version: "1.16.0",
5641
+ version: "1.16.2",
5522
5642
  description: "React UI components and hooks for Lumia Passport authentication and Account Abstraction",
5523
5643
  type: "module",
5524
5644
  main: "./dist/index.cjs",
@@ -9939,6 +10059,7 @@ var useManageWalletStore = create5((set) => ({
9939
10059
  emailCode: "",
9940
10060
  emailCodeExpiresIn: 0,
9941
10061
  linkIsLoading: false,
10062
+ linkError: null,
9942
10063
  providerType: null,
9943
10064
  confirmUnlink: null,
9944
10065
  setAlert: (alert2) => set({ alert: alert2 }),
@@ -9947,6 +10068,7 @@ var useManageWalletStore = create5((set) => ({
9947
10068
  setEmailCode: (emailCode) => set({ emailCode }),
9948
10069
  setEmailCodeExpiresIn: (emailCodeExpiresIn) => set({ emailCodeExpiresIn }),
9949
10070
  setLinkIsLoading: (linkIsLoading) => set({ linkIsLoading }),
10071
+ setLinkError: (linkError) => set({ linkError }),
9950
10072
  setProviderType: (providerType) => set({ providerType }),
9951
10073
  setConfirmUnlink: (confirmUnlink) => set({ confirmUnlink })
9952
10074
  }));