@spacelr/sdk 0.9.1 → 0.9.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.mts CHANGED
@@ -372,12 +372,13 @@ type AuthLostListener = (reason: AuthLostReason) => void;
372
372
  declare class TokenManager {
373
373
  private storage;
374
374
  private refreshBufferSeconds;
375
+ private refreshLockName;
375
376
  private refreshCallback;
376
377
  private refreshPromise;
377
378
  private tokenRefreshedListeners;
378
379
  private authLostListeners;
379
380
  private authLostEmitted;
380
- constructor(storage?: TokenStorage, refreshBufferSeconds?: number);
381
+ constructor(storage?: TokenStorage, refreshBufferSeconds?: number, refreshLockName?: string);
381
382
  setRefreshCallback(callback: RefreshCallback): void;
382
383
  getAccessToken(): Promise<string | null>;
383
384
  setTokens(tokens: StoredTokens): Promise<void>;
@@ -398,6 +399,9 @@ declare class TokenManager {
398
399
  private shouldRefresh;
399
400
  private tryRefresh;
400
401
  private executeRefresh;
402
+ private doRefresh;
403
+ private safeGetTokens;
404
+ private withCrossTabLock;
401
405
  private emitTokenRefreshed;
402
406
  }
403
407
 
@@ -1702,6 +1706,14 @@ interface SpacelrClient {
1702
1706
  setTokens(tokens: StoredTokens): Promise<void>;
1703
1707
  /** Clear stored tokens and reset auth-loss state. */
1704
1708
  clearTokens(): Promise<void>;
1709
+ /**
1710
+ * Whether a session (a stored token set) currently exists in the configured
1711
+ * TokenStorage. Use this to distinguish "genuinely logged out" from
1712
+ * "maybe-restorable" without reading the storage backend directly — so it
1713
+ * works with any TokenStorage (memory, IndexedDB, …), not just the default
1714
+ * localStorage one.
1715
+ */
1716
+ hasStoredSession(): Promise<boolean>;
1705
1717
  /** Disconnect realtime WebSocket (if connected) */
1706
1718
  disconnect(): void;
1707
1719
  /**
package/dist/index.d.ts CHANGED
@@ -372,12 +372,13 @@ type AuthLostListener = (reason: AuthLostReason) => void;
372
372
  declare class TokenManager {
373
373
  private storage;
374
374
  private refreshBufferSeconds;
375
+ private refreshLockName;
375
376
  private refreshCallback;
376
377
  private refreshPromise;
377
378
  private tokenRefreshedListeners;
378
379
  private authLostListeners;
379
380
  private authLostEmitted;
380
- constructor(storage?: TokenStorage, refreshBufferSeconds?: number);
381
+ constructor(storage?: TokenStorage, refreshBufferSeconds?: number, refreshLockName?: string);
381
382
  setRefreshCallback(callback: RefreshCallback): void;
382
383
  getAccessToken(): Promise<string | null>;
383
384
  setTokens(tokens: StoredTokens): Promise<void>;
@@ -398,6 +399,9 @@ declare class TokenManager {
398
399
  private shouldRefresh;
399
400
  private tryRefresh;
400
401
  private executeRefresh;
402
+ private doRefresh;
403
+ private safeGetTokens;
404
+ private withCrossTabLock;
401
405
  private emitTokenRefreshed;
402
406
  }
403
407
 
@@ -1702,6 +1706,14 @@ interface SpacelrClient {
1702
1706
  setTokens(tokens: StoredTokens): Promise<void>;
1703
1707
  /** Clear stored tokens and reset auth-loss state. */
1704
1708
  clearTokens(): Promise<void>;
1709
+ /**
1710
+ * Whether a session (a stored token set) currently exists in the configured
1711
+ * TokenStorage. Use this to distinguish "genuinely logged out" from
1712
+ * "maybe-restorable" without reading the storage backend directly — so it
1713
+ * works with any TokenStorage (memory, IndexedDB, …), not just the default
1714
+ * localStorage one.
1715
+ */
1716
+ hasStoredSession(): Promise<boolean>;
1705
1717
  /** Disconnect realtime WebSocket (if connected) */
1706
1718
  disconnect(): void;
1707
1719
  /**
package/dist/index.js CHANGED
@@ -428,7 +428,7 @@ var BrowserTokenStorage = class {
428
428
 
429
429
  // libs/sdk/src/core/token-manager.ts
430
430
  var TokenManager = class {
431
- constructor(storage, refreshBufferSeconds = 60) {
431
+ constructor(storage, refreshBufferSeconds = 60, refreshLockName = "spacelr_token_refresh") {
432
432
  this.refreshCallback = null;
433
433
  this.refreshPromise = null;
434
434
  this.tokenRefreshedListeners = /* @__PURE__ */ new Set();
@@ -439,6 +439,7 @@ var TokenManager = class {
439
439
  this.authLostEmitted = false;
440
440
  this.storage = storage ?? new MemoryTokenStorage();
441
441
  this.refreshBufferSeconds = refreshBufferSeconds;
442
+ this.refreshLockName = refreshLockName;
442
443
  }
443
444
  setRefreshCallback(callback) {
444
445
  this.refreshCallback = callback;
@@ -468,7 +469,7 @@ var TokenManager = class {
468
469
  this.authLostEmitted = false;
469
470
  }
470
471
  async getStoredTokens() {
471
- return this.storage.getTokens();
472
+ return this.safeGetTokens();
472
473
  }
473
474
  /**
474
475
  * Force a refresh using the current stored refresh token.
@@ -535,9 +536,18 @@ var TokenManager = class {
535
536
  }
536
537
  }
537
538
  async executeRefresh(refreshToken) {
539
+ return this.withCrossTabLock(() => this.doRefresh(refreshToken));
540
+ }
541
+ async doRefresh(refreshToken) {
538
542
  const callback = this.refreshCallback;
543
+ const current = await this.safeGetTokens();
544
+ if (current && current.refreshToken && current.refreshToken !== refreshToken && !this.isTokenExpired(current)) {
545
+ this.emitTokenRefreshed(current);
546
+ return current;
547
+ }
548
+ const activeRefreshToken = current?.refreshToken || refreshToken;
539
549
  try {
540
- const newTokens = await callback(refreshToken);
550
+ const newTokens = await callback(activeRefreshToken);
541
551
  await this.storage.setTokens(newTokens);
542
552
  this.emitTokenRefreshed(newTokens);
543
553
  return newTokens;
@@ -546,6 +556,22 @@ var TokenManager = class {
546
556
  throw error;
547
557
  }
548
558
  }
559
+ async safeGetTokens() {
560
+ try {
561
+ return await this.storage.getTokens();
562
+ } catch {
563
+ return null;
564
+ }
565
+ }
566
+ withCrossTabLock(fn) {
567
+ const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
568
+ if (!locks) return fn();
569
+ try {
570
+ return locks.request(this.refreshLockName, fn);
571
+ } catch {
572
+ return fn();
573
+ }
574
+ }
549
575
  emitTokenRefreshed(tokens) {
550
576
  for (const listener of this.tokenRefreshedListeners) {
551
577
  try {
@@ -3074,7 +3100,8 @@ function createClient(config) {
3074
3100
  const tokenStorage = config.tokenStorage ?? (typeof window !== "undefined" && typeof window.localStorage !== "undefined" ? new BrowserTokenStorage() : new MemoryTokenStorage());
3075
3101
  const tokenManager = new TokenManager(
3076
3102
  tokenStorage,
3077
- config.refreshBufferSeconds ?? 60
3103
+ config.refreshBufferSeconds ?? 60,
3104
+ `spacelr_token_refresh:${config.projectId}`
3078
3105
  );
3079
3106
  const httpClient = new HttpClient(config, tokenManager);
3080
3107
  const realtime = new RealtimeClient({
@@ -3101,6 +3128,9 @@ function createClient(config) {
3101
3128
  clearTokens() {
3102
3129
  return tokenManager.clearTokens();
3103
3130
  },
3131
+ hasStoredSession() {
3132
+ return tokenManager.getStoredTokens().then((tokens) => !!tokens);
3133
+ },
3104
3134
  disconnect() {
3105
3135
  realtime.disconnect();
3106
3136
  },