@calimero-network/mero-js 25.0.0 → 25.0.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.cjs CHANGED
@@ -12336,6 +12336,13 @@ var MeroJs = class {
12336
12336
  constructor(config) {
12337
12337
  this.tokenData = null;
12338
12338
  this.refreshPromise = null;
12339
+ // True while the store is known to hold our tokens; a store that cannot persist never sets it.
12340
+ this.storeHoldsTokens = false;
12341
+ // Bumped when local tokens are dropped, so a refresh already in flight cannot restore them.
12342
+ this.tokenGeneration = 0;
12343
+ this.onStorage = () => {
12344
+ if (this.signedOutElsewhere()) this.dropLocalTokens();
12345
+ };
12339
12346
  this.rpcClient = null;
12340
12347
  this.sseClient = null;
12341
12348
  this.wsClient = null;
@@ -12347,6 +12354,8 @@ var MeroJs = class {
12347
12354
  this.tokenStore = config.tokenStore ?? null;
12348
12355
  if (this.tokenStore) {
12349
12356
  this.tokenData = this.tokenStore.getTokens();
12357
+ this.storeHoldsTokens = this.tokenData !== null;
12358
+ if (typeof window !== "undefined") window.addEventListener("storage", this.onStorage);
12350
12359
  }
12351
12360
  const isTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
12352
12361
  const getProof = resolveProofSigner(this.config.requestProof);
@@ -12365,7 +12374,7 @@ var MeroJs = class {
12365
12374
  onTokenRefresh: async (newToken) => {
12366
12375
  if (this.tokenData) {
12367
12376
  this.tokenData.access_token = newToken;
12368
- this.tokenStore?.setTokens(this.tokenData);
12377
+ this.persistTokens(this.tokenData);
12369
12378
  }
12370
12379
  },
12371
12380
  onAuthRevoked: async () => {
@@ -12511,7 +12520,7 @@ var MeroJs = class {
12511
12520
  refresh_token: response.data.refresh_token,
12512
12521
  expires_at: expiresAtFromJwt(accessToken, Date.now() + 36e5)
12513
12522
  };
12514
- this.tokenStore?.setTokens(this.tokenData);
12523
+ this.persistTokens(this.tokenData);
12515
12524
  return this.tokenData;
12516
12525
  } catch (error) {
12517
12526
  throw new Error(
@@ -12519,6 +12528,21 @@ var MeroJs = class {
12519
12528
  );
12520
12529
  }
12521
12530
  }
12531
+ persistTokens(data) {
12532
+ if (!this.tokenStore) return;
12533
+ this.tokenStore.setTokens(data);
12534
+ this.storeHoldsTokens = this.tokenStore.getTokens() !== null;
12535
+ }
12536
+ // Raw store read: an empty store is a logout, another node's bundle is not.
12537
+ signedOutElsewhere() {
12538
+ return this.storeHoldsTokens && !this.tokenStore?.getTokens();
12539
+ }
12540
+ dropLocalTokens() {
12541
+ this.tokenGeneration++;
12542
+ this.refreshPromise = null;
12543
+ this.tokenData = null;
12544
+ this.storeHoldsTokens = false;
12545
+ }
12522
12546
  /**
12523
12547
  * Get a valid token. Returns the current token as-is.
12524
12548
  * The server rejects refresh attempts while the access token is still valid,
@@ -12572,8 +12596,13 @@ var MeroJs = class {
12572
12596
  * Perform the actual token refresh. Runs with the refresh lock held.
12573
12597
  */
12574
12598
  async performTokenRefreshLocked(triggeringAccessToken) {
12599
+ if (this.signedOutElsewhere()) {
12600
+ this.dropLocalTokens();
12601
+ throw new Error("Signed out in another tab");
12602
+ }
12575
12603
  const stored = this.tokenStore?.getTokens() ?? null;
12576
12604
  if (stored?.refresh_token) {
12605
+ this.storeHoldsTokens = true;
12577
12606
  this.tokenData = stored;
12578
12607
  if (triggeringAccessToken && stored.access_token !== triggeringAccessToken) {
12579
12608
  return stored;
@@ -12582,11 +12611,20 @@ var MeroJs = class {
12582
12611
  if (!this.tokenData?.refresh_token) {
12583
12612
  throw new Error("No refresh token available");
12584
12613
  }
12614
+ const generation = this.tokenGeneration;
12615
+ const presented = this.tokenData.access_token;
12616
+ const storeHeldPresented = this.tokenStore?.getTokens()?.access_token === presented;
12585
12617
  try {
12586
12618
  const response = await this.authClient.refreshToken({
12587
12619
  access_token: this.tokenData.access_token,
12588
12620
  refresh_token: this.tokenData.refresh_token
12589
12621
  });
12622
+ if (generation !== this.tokenGeneration) throw new Error("Signed out during token refresh");
12623
+ if (storeHeldPresented && this.tokenStore?.getTokens()?.access_token !== presented) {
12624
+ this.tokenData = this.tokenStore?.getTokens() ?? null;
12625
+ this.storeHoldsTokens = this.tokenData !== null;
12626
+ throw new Error("Signed out during token refresh");
12627
+ }
12590
12628
  const accessToken = response.data.access_token;
12591
12629
  this.tokenData = {
12592
12630
  access_token: accessToken,
@@ -12595,7 +12633,7 @@ var MeroJs = class {
12595
12633
  refresh_token: response.data.refresh_token,
12596
12634
  expires_at: expiresAtFromJwt(accessToken, Date.now() + 36e5)
12597
12635
  };
12598
- this.tokenStore?.setTokens(this.tokenData);
12636
+ this.persistTokens(this.tokenData);
12599
12637
  return this.tokenData;
12600
12638
  } catch (error) {
12601
12639
  throw new Error(
@@ -12607,8 +12645,7 @@ var MeroJs = class {
12607
12645
  * Clear the current token
12608
12646
  */
12609
12647
  clearToken() {
12610
- this.refreshPromise = null;
12611
- this.tokenData = null;
12648
+ this.dropLocalTokens();
12612
12649
  this.tokenStore?.clear();
12613
12650
  }
12614
12651
  /**
@@ -12625,7 +12662,7 @@ var MeroJs = class {
12625
12662
  setTokenData(data) {
12626
12663
  const expiresAt = data.expires_at || expiresAtFromJwt(data.access_token, Date.now() + 36e5);
12627
12664
  this.tokenData = { ...data, expires_at: expiresAt };
12628
- this.tokenStore?.setTokens(this.tokenData);
12665
+ this.persistTokens(this.tokenData);
12629
12666
  }
12630
12667
  /**
12631
12668
  * Get the current token data (for debugging)
@@ -12637,6 +12674,7 @@ var MeroJs = class {
12637
12674
  * Close all event connections and clean up resources
12638
12675
  */
12639
12676
  close() {
12677
+ if (typeof window !== "undefined") window.removeEventListener("storage", this.onStorage);
12640
12678
  this.sseClient?.close();
12641
12679
  this.wsClient?.close();
12642
12680
  }