@oxyhq/core 1.11.22 → 1.11.23

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.
Files changed (35) hide show
  1. package/README.md +2 -2
  2. package/dist/cjs/.tsbuildinfo +1 -1
  3. package/dist/cjs/HttpService.js +52 -0
  4. package/dist/cjs/OxyServices.base.js +16 -0
  5. package/dist/esm/.tsbuildinfo +1 -1
  6. package/dist/esm/HttpService.js +52 -0
  7. package/dist/esm/OxyServices.base.js +16 -0
  8. package/dist/types/.tsbuildinfo +1 -1
  9. package/dist/types/HttpService.d.ts +31 -0
  10. package/dist/types/OxyServices.base.d.ts +14 -0
  11. package/dist/types/mixins/OxyServices.analytics.d.ts +1 -0
  12. package/dist/types/mixins/OxyServices.appData.d.ts +1 -0
  13. package/dist/types/mixins/OxyServices.assets.d.ts +1 -0
  14. package/dist/types/mixins/OxyServices.auth.d.ts +1 -0
  15. package/dist/types/mixins/OxyServices.contacts.d.ts +1 -0
  16. package/dist/types/mixins/OxyServices.developer.d.ts +1 -0
  17. package/dist/types/mixins/OxyServices.devices.d.ts +1 -0
  18. package/dist/types/mixins/OxyServices.features.d.ts +6 -1
  19. package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -0
  20. package/dist/types/mixins/OxyServices.karma.d.ts +1 -0
  21. package/dist/types/mixins/OxyServices.language.d.ts +1 -0
  22. package/dist/types/mixins/OxyServices.location.d.ts +1 -0
  23. package/dist/types/mixins/OxyServices.managedAccounts.d.ts +1 -0
  24. package/dist/types/mixins/OxyServices.payment.d.ts +1 -0
  25. package/dist/types/mixins/OxyServices.popup.d.ts +1 -0
  26. package/dist/types/mixins/OxyServices.privacy.d.ts +1 -0
  27. package/dist/types/mixins/OxyServices.redirect.d.ts +1 -0
  28. package/dist/types/mixins/OxyServices.security.d.ts +1 -0
  29. package/dist/types/mixins/OxyServices.topics.d.ts +1 -0
  30. package/dist/types/mixins/OxyServices.user.d.ts +1 -0
  31. package/dist/types/mixins/OxyServices.utility.d.ts +1 -0
  32. package/package.json +1 -1
  33. package/src/HttpService.ts +53 -0
  34. package/src/OxyServices.base.ts +17 -0
  35. package/src/mixins/__tests__/onTokensChanged.test.ts +130 -0
@@ -106,6 +106,16 @@ export class HttpService {
106
106
  this.tokenRefreshPromise = null;
107
107
  this.tokenRefreshCooldownUntil = 0;
108
108
  this._onTokenRefreshed = null;
109
+ /**
110
+ * Fan-out listeners notified on EVERY access-token change on this instance:
111
+ * explicit `setTokens`, `clearTokens`, a successful silent refresh, and the
112
+ * internal 401-driven clear. Unlike the single-slot `_onTokenRefreshed`
113
+ * (owned by AuthManager for the refresh path only), this is a Set so multiple
114
+ * independent observers can mirror token state without clobbering each other.
115
+ *
116
+ * Each listener receives the resulting access token, or `null` when cleared.
117
+ */
118
+ this._tokenChangeListeners = new Set();
109
119
  // Acting-as identity for managed accounts
110
120
  this._actingAsUserId = null;
111
121
  // Performance monitoring
@@ -309,6 +319,7 @@ export class HttpService {
309
319
  // Refresh failed or no token — clear tokens and stale CSRF
310
320
  this.tokenStore.clearTokens();
311
321
  this.tokenStore.clearCsrfToken();
322
+ this.notifyTokenChange();
312
323
  }
313
324
  // On 403 with CSRF error, clear cached token and retry once
314
325
  if (response.status === 403 && !config._isCsrfRetry) {
@@ -688,6 +699,7 @@ export class HttpService {
688
699
  const { accessToken: newToken } = await response.json();
689
700
  this.tokenStore.setTokens(newToken);
690
701
  this._onTokenRefreshed?.(newToken);
702
+ this.notifyTokenChange();
691
703
  this.logger.debug('Token refreshed');
692
704
  return `Bearer ${newToken}`;
693
705
  }
@@ -753,6 +765,7 @@ export class HttpService {
753
765
  // Token management
754
766
  setTokens(accessToken, refreshToken = '') {
755
767
  this.tokenStore.setTokens(accessToken, refreshToken);
768
+ this.notifyTokenChange();
756
769
  }
757
770
  set onTokenRefreshed(callback) {
758
771
  this._onTokenRefreshed = callback;
@@ -760,6 +773,45 @@ export class HttpService {
760
773
  clearTokens() {
761
774
  this.tokenStore.clearTokens();
762
775
  this.tokenStore.clearCsrfToken();
776
+ this.notifyTokenChange();
777
+ }
778
+ /**
779
+ * Subscribe to access-token changes on this instance.
780
+ *
781
+ * Fires on every mutation of the access token — `setTokens`, `clearTokens`,
782
+ * a successful silent refresh, and the internal 401-driven clear — passing
783
+ * the resulting token (or `null` when cleared). Returns an unsubscribe
784
+ * function; call it on teardown to avoid leaks.
785
+ *
786
+ * This is the single hook downstream code (e.g. @oxyhq/services' OxyProvider)
787
+ * uses to keep an external token sink — such as the shared `oxyClient`
788
+ * singleton — in lockstep with the active session, regardless of which code
789
+ * path mutated the token.
790
+ */
791
+ addTokenChangeListener(listener) {
792
+ this._tokenChangeListeners.add(listener);
793
+ return () => {
794
+ this._tokenChangeListeners.delete(listener);
795
+ };
796
+ }
797
+ /**
798
+ * Notify all token-change listeners with the current access token.
799
+ * Listener exceptions are isolated so one bad subscriber cannot break token
800
+ * propagation to the others or to the calling auth flow.
801
+ * @internal
802
+ */
803
+ notifyTokenChange() {
804
+ if (this._tokenChangeListeners.size === 0)
805
+ return;
806
+ const accessToken = this.tokenStore.getAccessToken();
807
+ for (const listener of this._tokenChangeListeners) {
808
+ try {
809
+ listener(accessToken);
810
+ }
811
+ catch (error) {
812
+ this.logger.error('Token change listener threw:', error);
813
+ }
814
+ }
763
815
  }
764
816
  getAccessToken() {
765
817
  return this.tokenStore.getAccessToken();
@@ -110,6 +110,22 @@ export class OxyServicesBase {
110
110
  this._cachedUserId = undefined;
111
111
  this._cachedAccessToken = null;
112
112
  }
113
+ /**
114
+ * Subscribe to access-token changes on this client.
115
+ *
116
+ * The listener fires on every access-token mutation — explicit
117
+ * `setTokens`/`clearTokens`, a successful silent refresh, and the internal
118
+ * 401-driven clear — receiving the resulting token, or `null` when cleared.
119
+ * Returns an unsubscribe function.
120
+ *
121
+ * Primary use: keeping an external token sink (e.g. the shared `oxyClient`
122
+ * singleton) in lockstep with whichever `OxyServices` instance actually owns
123
+ * the session, so imperative consumers reading the singleton always observe
124
+ * the live token regardless of the code path that changed it.
125
+ */
126
+ onTokensChanged(listener) {
127
+ return this.httpService.addTokenChangeListener(listener);
128
+ }
113
129
  /**
114
130
  * Get the current user ID from the access token.
115
131
  * Caches the decoded value and invalidates when the token changes.