decane-connect-kit 2.25.0 → 2.26.1

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
@@ -308,7 +308,13 @@ interface SignTypedDataRequest {
308
308
  message: Record<string, unknown>;
309
309
  primaryType?: string;
310
310
  }
311
- type SdkEvent = 'connected' | 'disconnected' | 'session-expired' | 'session-started' | 'wallet-creating' | 'wallet-unlocking'
311
+ type SdkEvent = 'connected' | 'disconnected' | 'session-expired'
312
+ /**
313
+ * The access token was renewed while the user was active, so the session
314
+ * continued instead of ending. Informational: apps that cache the token need
315
+ * to re-read it, most do not.
316
+ */
317
+ | 'session-renewed' | 'session-started' | 'wallet-creating' | 'wallet-unlocking'
312
318
  /**
313
319
  * Emitted when this wallet could not be enrolled for server-assisted
314
320
  * recovery. Not fatal — the wallet works — but until it is enrolled the user
@@ -737,6 +743,16 @@ declare class ApiClient {
737
743
  token: string;
738
744
  expiresInDays: number;
739
745
  }>;
746
+ /**
747
+ * Trade a still-valid token for a fresh one, so an active user is never
748
+ * signed out mid-use. The backend caps the whole chain by session age, so
749
+ * this cannot renew forever; a 401 means "sign in again", not "retry".
750
+ */
751
+ refreshAccessToken(jwt: string): Promise<{
752
+ jwt: string;
753
+ expiresIn: number;
754
+ sessionExpiresIn: number;
755
+ }>;
740
756
  /** Redeem a decrypted unlock token for a session. */
741
757
  exchangeUnlockToken(token: string): Promise<{
742
758
  jwt: string;
@@ -916,6 +932,13 @@ interface SocialConfig {
916
932
  * see it for what a device gives up until then. Default false.
917
933
  */
918
934
  deferDeviceProtection?: boolean;
935
+ /**
936
+ * Resume a live session in a new tab, not only after a reload. Default false.
937
+ * See the same option on the headless config for the security trade: the
938
+ * session handle is the signing credential, so this widens where it can be
939
+ * read from one tab to the whole origin.
940
+ */
941
+ resumeSessionAcrossTabs?: boolean;
919
942
  /**
920
943
  * Offer to save a recovery-share file at signup. **Default false** since
921
944
  * 2.7.4 — opt in explicitly with `true`.
package/dist/index.d.ts CHANGED
@@ -308,7 +308,13 @@ interface SignTypedDataRequest {
308
308
  message: Record<string, unknown>;
309
309
  primaryType?: string;
310
310
  }
311
- type SdkEvent = 'connected' | 'disconnected' | 'session-expired' | 'session-started' | 'wallet-creating' | 'wallet-unlocking'
311
+ type SdkEvent = 'connected' | 'disconnected' | 'session-expired'
312
+ /**
313
+ * The access token was renewed while the user was active, so the session
314
+ * continued instead of ending. Informational: apps that cache the token need
315
+ * to re-read it, most do not.
316
+ */
317
+ | 'session-renewed' | 'session-started' | 'wallet-creating' | 'wallet-unlocking'
312
318
  /**
313
319
  * Emitted when this wallet could not be enrolled for server-assisted
314
320
  * recovery. Not fatal — the wallet works — but until it is enrolled the user
@@ -737,6 +743,16 @@ declare class ApiClient {
737
743
  token: string;
738
744
  expiresInDays: number;
739
745
  }>;
746
+ /**
747
+ * Trade a still-valid token for a fresh one, so an active user is never
748
+ * signed out mid-use. The backend caps the whole chain by session age, so
749
+ * this cannot renew forever; a 401 means "sign in again", not "retry".
750
+ */
751
+ refreshAccessToken(jwt: string): Promise<{
752
+ jwt: string;
753
+ expiresIn: number;
754
+ sessionExpiresIn: number;
755
+ }>;
740
756
  /** Redeem a decrypted unlock token for a session. */
741
757
  exchangeUnlockToken(token: string): Promise<{
742
758
  jwt: string;
@@ -916,6 +932,13 @@ interface SocialConfig {
916
932
  * see it for what a device gives up until then. Default false.
917
933
  */
918
934
  deferDeviceProtection?: boolean;
935
+ /**
936
+ * Resume a live session in a new tab, not only after a reload. Default false.
937
+ * See the same option on the headless config for the security trade: the
938
+ * session handle is the signing credential, so this widens where it can be
939
+ * read from one tab to the whole origin.
940
+ */
941
+ resumeSessionAcrossTabs?: boolean;
919
942
  /**
920
943
  * Offer to save a recovery-share file at signup. **Default false** since
921
944
  * 2.7.4 — opt in explicitly with `true`.
package/dist/index.js CHANGED
@@ -2193,6 +2193,17 @@ var ApiClient = class {
2193
2193
  headers: { Authorization: `Bearer ${jwt}` }
2194
2194
  });
2195
2195
  }
2196
+ /**
2197
+ * Trade a still-valid token for a fresh one, so an active user is never
2198
+ * signed out mid-use. The backend caps the whole chain by session age, so
2199
+ * this cannot renew forever; a 401 means "sign in again", not "retry".
2200
+ */
2201
+ async refreshAccessToken(jwt) {
2202
+ return this._request("/auth/refresh", {
2203
+ method: "POST",
2204
+ headers: { Authorization: `Bearer ${jwt}` }
2205
+ });
2206
+ }
2196
2207
  /** Redeem a decrypted unlock token for a session. */
2197
2208
  async exchangeUnlockToken(token) {
2198
2209
  return this._request("/auth/unlock-token/exchange", {
@@ -3952,6 +3963,7 @@ async function createDecaneConnect(config) {
3952
3963
  clearSessionPersist(SESSION_KEY);
3953
3964
  }
3954
3965
  let accessTokenExpiry = null;
3966
+ let tokenRefresh = null;
3955
3967
  function scheduleAccessTokenExpiry(jwt, expiresAt) {
3956
3968
  if (accessTokenExpiry) clearTimeout(accessTokenExpiry);
3957
3969
  accessTokenExpiry = setTimeout(() => {
@@ -3961,6 +3973,40 @@ async function createDecaneConnect(config) {
3961
3973
  dropLiveSession();
3962
3974
  events.emit("session-expired");
3963
3975
  }, Math.max(0, expiresAt - Date.now()));
3976
+ scheduleTokenRefresh(jwt, expiresAt);
3977
+ }
3978
+ function scheduleTokenRefresh(jwt, expiresAt) {
3979
+ if (tokenRefresh) clearTimeout(tokenRefresh);
3980
+ const lifetime = expiresAt - Date.now();
3981
+ if (lifetime <= 0) return;
3982
+ const at = Math.max(5e3, lifetime * 0.75);
3983
+ tokenRefresh = setTimeout(() => {
3984
+ tokenRefresh = null;
3985
+ void renewAccessToken(jwt);
3986
+ }, at);
3987
+ }
3988
+ async function renewAccessToken(expected) {
3989
+ if (auth?.jwt !== expected) return;
3990
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") {
3991
+ const onVisible = () => {
3992
+ document.removeEventListener("visibilitychange", onVisible);
3993
+ if (auth?.jwt === expected) void renewAccessToken(expected);
3994
+ };
3995
+ document.addEventListener("visibilitychange", onVisible);
3996
+ return;
3997
+ }
3998
+ try {
3999
+ const { jwt: fresh } = await api.refreshAccessToken(expected);
4000
+ if (auth?.jwt !== expected) return;
4001
+ auth = { ...auth, jwt: fresh };
4002
+ const freshExpiry = decodeAccessToken(fresh)?.expiresAt;
4003
+ if (freshExpiry) {
4004
+ scheduleAccessTokenExpiry(fresh, freshExpiry);
4005
+ persistLiveSession();
4006
+ }
4007
+ events.emit("session-renewed");
4008
+ } catch {
4009
+ }
3964
4010
  }
3965
4011
  if (persistSession) {
3966
4012
  const saved = loadSession(SESSION_KEY) ?? (LEGACY_SESSION_KEY !== SESSION_KEY ? loadSession(LEGACY_SESSION_KEY) : null);
@@ -7550,6 +7596,7 @@ function DecaneKit({ config, children }) {
7550
7596
  rpcUrls,
7551
7597
  requireAssertionPerSignature,
7552
7598
  deferDeviceProtection,
7599
+ resumeSessionAcrossTabs,
7553
7600
  // Recovery callbacks. Forwarding these is what makes recovery
7554
7601
  // configurable at all from <DecaneKit>; before, they were silently
7555
7602
  // dropped and the headless client was constructed without them.
@@ -7571,6 +7618,7 @@ function DecaneKit({ config, children }) {
7571
7618
  promptUnlockPassword,
7572
7619
  requireAssertionPerSignature,
7573
7620
  deferDeviceProtection,
7621
+ resumeSessionAcrossTabs,
7574
7622
  offerRecoveryAtSignup,
7575
7623
  onRecoveryShareOffer,
7576
7624
  onRecoveryFileReady,