@oxyhq/core 1.11.22 → 1.11.24
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/README.md +2 -2
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +52 -0
- package/dist/cjs/OxyServices.base.js +16 -0
- package/dist/cjs/mixins/OxyServices.fedcm.js +169 -73
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +52 -0
- package/dist/esm/OxyServices.base.js +16 -0
- package/dist/esm/mixins/OxyServices.fedcm.js +169 -73
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +31 -0
- package/dist/types/OxyServices.base.d.ts +14 -0
- package/dist/types/OxyServices.d.ts +9 -0
- package/dist/types/mixins/OxyServices.analytics.d.ts +1 -0
- package/dist/types/mixins/OxyServices.appData.d.ts +1 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +1 -0
- package/dist/types/mixins/OxyServices.auth.d.ts +1 -0
- package/dist/types/mixins/OxyServices.contacts.d.ts +1 -0
- package/dist/types/mixins/OxyServices.developer.d.ts +1 -0
- package/dist/types/mixins/OxyServices.devices.d.ts +1 -0
- package/dist/types/mixins/OxyServices.features.d.ts +6 -1
- package/dist/types/mixins/OxyServices.fedcm.d.ts +20 -1
- package/dist/types/mixins/OxyServices.karma.d.ts +1 -0
- package/dist/types/mixins/OxyServices.language.d.ts +1 -0
- package/dist/types/mixins/OxyServices.location.d.ts +1 -0
- package/dist/types/mixins/OxyServices.managedAccounts.d.ts +1 -0
- package/dist/types/mixins/OxyServices.payment.d.ts +1 -0
- package/dist/types/mixins/OxyServices.popup.d.ts +1 -0
- package/dist/types/mixins/OxyServices.privacy.d.ts +1 -0
- package/dist/types/mixins/OxyServices.redirect.d.ts +1 -0
- package/dist/types/mixins/OxyServices.security.d.ts +1 -0
- package/dist/types/mixins/OxyServices.topics.d.ts +1 -0
- package/dist/types/mixins/OxyServices.user.d.ts +1 -0
- package/dist/types/mixins/OxyServices.utility.d.ts +1 -0
- package/package.json +1 -1
- package/src/HttpService.ts +53 -0
- package/src/OxyServices.base.ts +17 -0
- package/src/OxyServices.ts +10 -0
- package/src/mixins/OxyServices.fedcm.ts +187 -78
- package/src/mixins/__tests__/fedcm.test.ts +231 -0
- package/src/mixins/__tests__/onTokensChanged.test.ts +130 -0
|
@@ -50,6 +50,16 @@ export declare class HttpService {
|
|
|
50
50
|
private tokenRefreshPromise;
|
|
51
51
|
private tokenRefreshCooldownUntil;
|
|
52
52
|
private _onTokenRefreshed;
|
|
53
|
+
/**
|
|
54
|
+
* Fan-out listeners notified on EVERY access-token change on this instance:
|
|
55
|
+
* explicit `setTokens`, `clearTokens`, a successful silent refresh, and the
|
|
56
|
+
* internal 401-driven clear. Unlike the single-slot `_onTokenRefreshed`
|
|
57
|
+
* (owned by AuthManager for the refresh path only), this is a Set so multiple
|
|
58
|
+
* independent observers can mirror token state without clobbering each other.
|
|
59
|
+
*
|
|
60
|
+
* Each listener receives the resulting access token, or `null` when cleared.
|
|
61
|
+
*/
|
|
62
|
+
private _tokenChangeListeners;
|
|
53
63
|
private _actingAsUserId;
|
|
54
64
|
private requestMetrics;
|
|
55
65
|
constructor(config: OxyConfig);
|
|
@@ -134,6 +144,27 @@ export declare class HttpService {
|
|
|
134
144
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
135
145
|
set onTokenRefreshed(callback: ((accessToken: string) => void) | null);
|
|
136
146
|
clearTokens(): void;
|
|
147
|
+
/**
|
|
148
|
+
* Subscribe to access-token changes on this instance.
|
|
149
|
+
*
|
|
150
|
+
* Fires on every mutation of the access token — `setTokens`, `clearTokens`,
|
|
151
|
+
* a successful silent refresh, and the internal 401-driven clear — passing
|
|
152
|
+
* the resulting token (or `null` when cleared). Returns an unsubscribe
|
|
153
|
+
* function; call it on teardown to avoid leaks.
|
|
154
|
+
*
|
|
155
|
+
* This is the single hook downstream code (e.g. @oxyhq/services' OxyProvider)
|
|
156
|
+
* uses to keep an external token sink — such as the shared `oxyClient`
|
|
157
|
+
* singleton — in lockstep with the active session, regardless of which code
|
|
158
|
+
* path mutated the token.
|
|
159
|
+
*/
|
|
160
|
+
addTokenChangeListener(listener: (accessToken: string | null) => void): () => void;
|
|
161
|
+
/**
|
|
162
|
+
* Notify all token-change listeners with the current access token.
|
|
163
|
+
* Listener exceptions are isolated so one bad subscriber cannot break token
|
|
164
|
+
* propagation to the others or to the calling auth flow.
|
|
165
|
+
* @internal
|
|
166
|
+
*/
|
|
167
|
+
private notifyTokenChange;
|
|
137
168
|
getAccessToken(): string | null;
|
|
138
169
|
hasAccessToken(): boolean;
|
|
139
170
|
getBaseURL(): string;
|
|
@@ -73,6 +73,20 @@ export declare class OxyServicesBase {
|
|
|
73
73
|
* Clear stored authentication tokens
|
|
74
74
|
*/
|
|
75
75
|
clearTokens(): void;
|
|
76
|
+
/**
|
|
77
|
+
* Subscribe to access-token changes on this client.
|
|
78
|
+
*
|
|
79
|
+
* The listener fires on every access-token mutation — explicit
|
|
80
|
+
* `setTokens`/`clearTokens`, a successful silent refresh, and the internal
|
|
81
|
+
* 401-driven clear — receiving the resulting token, or `null` when cleared.
|
|
82
|
+
* Returns an unsubscribe function.
|
|
83
|
+
*
|
|
84
|
+
* Primary use: keeping an external token sink (e.g. the shared `oxyClient`
|
|
85
|
+
* singleton) in lockstep with whichever `OxyServices` instance actually owns
|
|
86
|
+
* the session, so imperative consumers reading the singleton always observe
|
|
87
|
+
* the live token regardless of the code path that changed it.
|
|
88
|
+
*/
|
|
89
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
76
90
|
/** @internal */ _cachedUserId: string | null | undefined;
|
|
77
91
|
/** @internal */ _cachedAccessToken: string | null;
|
|
78
92
|
/**
|
|
@@ -98,6 +98,15 @@ import { composeOxyServices } from './mixins';
|
|
|
98
98
|
*/
|
|
99
99
|
declare const OxyServicesComposed: import("./mixins").ComposedOxyServicesConstructor;
|
|
100
100
|
export declare class OxyServices extends OxyServicesComposed {
|
|
101
|
+
/**
|
|
102
|
+
* FedCM credential-request timeouts (ms). The runtime values are defined on
|
|
103
|
+
* the FedCM mixin and inherited here via `extends`; these `declare` members
|
|
104
|
+
* surface their types to TypeScript without re-emitting (or duplicating) the
|
|
105
|
+
* literals, so consumers/tests can reference `OxyServices.FEDCM_SILENT_TIMEOUT`
|
|
106
|
+
* with full typing.
|
|
107
|
+
*/
|
|
108
|
+
static readonly FEDCM_TIMEOUT: number;
|
|
109
|
+
static readonly FEDCM_SILENT_TIMEOUT: number;
|
|
101
110
|
constructor(config: OxyConfig);
|
|
102
111
|
}
|
|
103
112
|
export interface OxyServices extends InstanceType<ReturnType<typeof composeOxyServices>> {
|
|
@@ -46,6 +46,7 @@ export declare function OxyServicesAnalyticsMixin<T extends typeof OxyServicesBa
|
|
|
46
46
|
getCloudURL(): string;
|
|
47
47
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
48
48
|
clearTokens(): void;
|
|
49
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
49
50
|
_cachedUserId: string | null | undefined;
|
|
50
51
|
_cachedAccessToken: string | null;
|
|
51
52
|
getCurrentUserId(): string | null;
|
|
@@ -82,6 +82,7 @@ export declare function OxyServicesAppDataMixin<T extends typeof OxyServicesBase
|
|
|
82
82
|
getCloudURL(): string;
|
|
83
83
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
84
84
|
clearTokens(): void;
|
|
85
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
85
86
|
_cachedUserId: string | null | undefined;
|
|
86
87
|
_cachedAccessToken: string | null;
|
|
87
88
|
getCurrentUserId(): string | null;
|
|
@@ -119,6 +119,7 @@ export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>
|
|
|
119
119
|
getCloudURL(): string;
|
|
120
120
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
121
121
|
clearTokens(): void;
|
|
122
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
122
123
|
_cachedUserId: string | null | undefined;
|
|
123
124
|
_cachedAccessToken: string | null;
|
|
124
125
|
getCurrentUserId(): string | null;
|
|
@@ -313,6 +313,7 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
|
|
|
313
313
|
getCloudURL(): string;
|
|
314
314
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
315
315
|
clearTokens(): void;
|
|
316
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
316
317
|
_cachedUserId: string | null | undefined;
|
|
317
318
|
_cachedAccessToken: string | null;
|
|
318
319
|
getCurrentUserId(): string | null;
|
|
@@ -74,6 +74,7 @@ export declare function OxyServicesContactsMixin<T extends typeof OxyServicesBas
|
|
|
74
74
|
getCloudURL(): string;
|
|
75
75
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
76
76
|
clearTokens(): void;
|
|
77
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
77
78
|
_cachedUserId: string | null | undefined;
|
|
78
79
|
_cachedAccessToken: string | null;
|
|
79
80
|
getCurrentUserId(): string | null;
|
|
@@ -79,6 +79,7 @@ export declare function OxyServicesDeveloperMixin<T extends typeof OxyServicesBa
|
|
|
79
79
|
getCloudURL(): string;
|
|
80
80
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
81
81
|
clearTokens(): void;
|
|
82
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
82
83
|
_cachedUserId: string | null | undefined;
|
|
83
84
|
_cachedAccessToken: string | null;
|
|
84
85
|
getCurrentUserId(): string | null;
|
|
@@ -76,6 +76,7 @@ export declare function OxyServicesDevicesMixin<T extends typeof OxyServicesBase
|
|
|
76
76
|
getCloudURL(): string;
|
|
77
77
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
78
78
|
clearTokens(): void;
|
|
79
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
79
80
|
_cachedUserId: string | null | undefined;
|
|
80
81
|
_cachedAccessToken: string | null;
|
|
81
82
|
getCurrentUserId(): string | null;
|
|
@@ -204,6 +204,7 @@ export declare function OxyServicesFeaturesMixin<T extends typeof OxyServicesBas
|
|
|
204
204
|
getCloudURL(): string;
|
|
205
205
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
206
206
|
clearTokens(): void;
|
|
207
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
207
208
|
_cachedUserId: string | null | undefined;
|
|
208
209
|
_cachedAccessToken: string | null;
|
|
209
210
|
getCurrentUserId(): string | null;
|
|
@@ -215,7 +216,11 @@ export declare function OxyServicesFeaturesMixin<T extends typeof OxyServicesBas
|
|
|
215
216
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
216
217
|
maxRetries?: number;
|
|
217
218
|
retryDelay?: number;
|
|
218
|
-
authTimeoutMs
|
|
219
|
+
authTimeoutMs
|
|
220
|
+
/**
|
|
221
|
+
* Get user statistics
|
|
222
|
+
*/
|
|
223
|
+
?: number;
|
|
219
224
|
}): Promise<T_1>;
|
|
220
225
|
validate(): Promise<boolean>;
|
|
221
226
|
handleError(error: unknown): Error;
|
|
@@ -82,6 +82,24 @@ export declare function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(
|
|
|
82
82
|
* ```
|
|
83
83
|
*/
|
|
84
84
|
signInWithFedCM(options?: FedCMAuthOptions): Promise<SessionLoginResponse>;
|
|
85
|
+
/**
|
|
86
|
+
* Run a single interactive FedCM credential request + token exchange for the
|
|
87
|
+
* given (possibly undefined) loginHint. A successful exchange plants the
|
|
88
|
+
* access token and persists the user id as the future loginHint — the hint is
|
|
89
|
+
* therefore only ever stored after a GENUINELY successful sign-in, never
|
|
90
|
+
* speculatively.
|
|
91
|
+
*
|
|
92
|
+
* @private
|
|
93
|
+
*/
|
|
94
|
+
attemptInteractiveSignIn(options: FedCMAuthOptions, loginHint: string | undefined): Promise<SessionLoginResponse>;
|
|
95
|
+
/**
|
|
96
|
+
* Map a raw FedCM/exchange failure to a user-facing {@link OxyAuthenticationError}
|
|
97
|
+
* (or pass it through). Extracted so the clear-and-retry path can reuse the
|
|
98
|
+
* exact same error normalisation as the first attempt.
|
|
99
|
+
*
|
|
100
|
+
* @private
|
|
101
|
+
*/
|
|
102
|
+
normalizeInteractiveSignInError(error: unknown): unknown;
|
|
85
103
|
/**
|
|
86
104
|
* Silent sign-in using FedCM
|
|
87
105
|
*
|
|
@@ -243,6 +261,7 @@ export declare function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(
|
|
|
243
261
|
getCloudURL(): string;
|
|
244
262
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
245
263
|
clearTokens(): void;
|
|
264
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
246
265
|
_cachedUserId: string | null | undefined;
|
|
247
266
|
_cachedAccessToken: string | null;
|
|
248
267
|
getCurrentUserId(): string | null;
|
|
@@ -267,7 +286,7 @@ export declare function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(
|
|
|
267
286
|
};
|
|
268
287
|
readonly DEFAULT_CONFIG_URL: "https://auth.oxy.so/fedcm.json";
|
|
269
288
|
readonly FEDCM_TIMEOUT: 15000;
|
|
270
|
-
readonly FEDCM_SILENT_TIMEOUT:
|
|
289
|
+
readonly FEDCM_SILENT_TIMEOUT: 10000;
|
|
271
290
|
/**
|
|
272
291
|
* Check if FedCM is supported in the current browser
|
|
273
292
|
*/
|
|
@@ -65,6 +65,7 @@ export declare function OxyServicesKarmaMixin<T extends typeof OxyServicesBase>(
|
|
|
65
65
|
getCloudURL(): string;
|
|
66
66
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
67
67
|
clearTokens(): void;
|
|
68
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
68
69
|
_cachedUserId: string | null | undefined;
|
|
69
70
|
_cachedAccessToken: string | null;
|
|
70
71
|
getCurrentUserId(): string | null;
|
|
@@ -61,6 +61,7 @@ export declare function OxyServicesLanguageMixin<T extends typeof OxyServicesBas
|
|
|
61
61
|
getCloudURL(): string;
|
|
62
62
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
63
63
|
clearTokens(): void;
|
|
64
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
64
65
|
_cachedUserId: string | null | undefined;
|
|
65
66
|
_cachedAccessToken: string | null;
|
|
66
67
|
getCurrentUserId(): string | null;
|
|
@@ -44,6 +44,7 @@ export declare function OxyServicesLocationMixin<T extends typeof OxyServicesBas
|
|
|
44
44
|
getCloudURL(): string;
|
|
45
45
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
46
46
|
clearTokens(): void;
|
|
47
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
47
48
|
_cachedUserId: string | null | undefined;
|
|
48
49
|
_cachedAccessToken: string | null;
|
|
49
50
|
getCurrentUserId(): string | null;
|
|
@@ -101,6 +101,7 @@ export declare function OxyServicesManagedAccountsMixin<T extends typeof OxyServ
|
|
|
101
101
|
getCloudURL(): string;
|
|
102
102
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
103
103
|
clearTokens(): void;
|
|
104
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
104
105
|
_cachedUserId: string | null | undefined;
|
|
105
106
|
_cachedAccessToken: string | null;
|
|
106
107
|
getCurrentUserId(): string | null;
|
|
@@ -91,6 +91,7 @@ export declare function OxyServicesPaymentMixin<T extends typeof OxyServicesBase
|
|
|
91
91
|
getCloudURL(): string;
|
|
92
92
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
93
93
|
clearTokens(): void;
|
|
94
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
94
95
|
_cachedUserId: string | null | undefined;
|
|
95
96
|
_cachedAccessToken: string | null;
|
|
96
97
|
getCurrentUserId(): string | null;
|
|
@@ -181,6 +181,7 @@ export declare function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBa
|
|
|
181
181
|
getCloudURL(): string;
|
|
182
182
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
183
183
|
clearTokens(): void;
|
|
184
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
184
185
|
_cachedUserId: string | null | undefined;
|
|
185
186
|
_cachedAccessToken: string | null;
|
|
186
187
|
getCurrentUserId(): string | null;
|
|
@@ -102,6 +102,7 @@ export declare function OxyServicesPrivacyMixin<T extends typeof OxyServicesBase
|
|
|
102
102
|
getCloudURL(): string;
|
|
103
103
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
104
104
|
clearTokens(): void;
|
|
105
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
105
106
|
_cachedUserId: string | null | undefined;
|
|
106
107
|
_cachedAccessToken: string | null;
|
|
107
108
|
getCurrentUserId(): string | null;
|
|
@@ -218,6 +218,7 @@ export declare function OxyServicesRedirectAuthMixin<T extends typeof OxyService
|
|
|
218
218
|
getCloudURL(): string;
|
|
219
219
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
220
220
|
clearTokens(): void;
|
|
221
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
221
222
|
_cachedUserId: string | null | undefined;
|
|
222
223
|
_cachedAccessToken: string | null;
|
|
223
224
|
getCurrentUserId(): string | null;
|
|
@@ -58,6 +58,7 @@ export declare function OxyServicesSecurityMixin<T extends typeof OxyServicesBas
|
|
|
58
58
|
getCloudURL(): string;
|
|
59
59
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
60
60
|
clearTokens(): void;
|
|
61
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
61
62
|
_cachedUserId: string | null | undefined;
|
|
62
63
|
_cachedAccessToken: string | null;
|
|
63
64
|
getCurrentUserId(): string | null;
|
|
@@ -84,6 +84,7 @@ export declare function OxyServicesTopicsMixin<T extends typeof OxyServicesBase>
|
|
|
84
84
|
getCloudURL(): string;
|
|
85
85
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
86
86
|
clearTokens(): void;
|
|
87
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
87
88
|
_cachedUserId: string | null | undefined;
|
|
88
89
|
_cachedAccessToken: string | null;
|
|
89
90
|
getCurrentUserId(): string | null;
|
|
@@ -229,6 +229,7 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
|
|
|
229
229
|
getCloudURL(): string;
|
|
230
230
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
231
231
|
clearTokens(): void;
|
|
232
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
232
233
|
_cachedUserId: string | null | undefined;
|
|
233
234
|
_cachedAccessToken: string | null;
|
|
234
235
|
getCurrentUserId(): string | null;
|
|
@@ -267,6 +267,7 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
267
267
|
getCloudURL(): string;
|
|
268
268
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
269
269
|
clearTokens(): void;
|
|
270
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
270
271
|
_cachedUserId: string | null | undefined;
|
|
271
272
|
_cachedAccessToken: string | null;
|
|
272
273
|
getCurrentUserId(): string | null;
|
package/package.json
CHANGED
package/src/HttpService.ts
CHANGED
|
@@ -176,6 +176,17 @@ export class HttpService {
|
|
|
176
176
|
private tokenRefreshCooldownUntil: number = 0;
|
|
177
177
|
private _onTokenRefreshed: ((accessToken: string) => void) | null = null;
|
|
178
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Fan-out listeners notified on EVERY access-token change on this instance:
|
|
181
|
+
* explicit `setTokens`, `clearTokens`, a successful silent refresh, and the
|
|
182
|
+
* internal 401-driven clear. Unlike the single-slot `_onTokenRefreshed`
|
|
183
|
+
* (owned by AuthManager for the refresh path only), this is a Set so multiple
|
|
184
|
+
* independent observers can mirror token state without clobbering each other.
|
|
185
|
+
*
|
|
186
|
+
* Each listener receives the resulting access token, or `null` when cleared.
|
|
187
|
+
*/
|
|
188
|
+
private _tokenChangeListeners = new Set<(accessToken: string | null) => void>();
|
|
189
|
+
|
|
179
190
|
// Acting-as identity for managed accounts
|
|
180
191
|
private _actingAsUserId: string | null = null;
|
|
181
192
|
|
|
@@ -430,6 +441,7 @@ export class HttpService {
|
|
|
430
441
|
// Refresh failed or no token — clear tokens and stale CSRF
|
|
431
442
|
this.tokenStore.clearTokens();
|
|
432
443
|
this.tokenStore.clearCsrfToken();
|
|
444
|
+
this.notifyTokenChange();
|
|
433
445
|
}
|
|
434
446
|
|
|
435
447
|
// On 403 with CSRF error, clear cached token and retry once
|
|
@@ -844,6 +856,7 @@ export class HttpService {
|
|
|
844
856
|
const { accessToken: newToken } = await response.json();
|
|
845
857
|
this.tokenStore.setTokens(newToken);
|
|
846
858
|
this._onTokenRefreshed?.(newToken);
|
|
859
|
+
this.notifyTokenChange();
|
|
847
860
|
this.logger.debug('Token refreshed');
|
|
848
861
|
return `Bearer ${newToken}`;
|
|
849
862
|
}
|
|
@@ -920,6 +933,7 @@ export class HttpService {
|
|
|
920
933
|
// Token management
|
|
921
934
|
setTokens(accessToken: string, refreshToken = ''): void {
|
|
922
935
|
this.tokenStore.setTokens(accessToken, refreshToken);
|
|
936
|
+
this.notifyTokenChange();
|
|
923
937
|
}
|
|
924
938
|
|
|
925
939
|
set onTokenRefreshed(callback: ((accessToken: string) => void) | null) {
|
|
@@ -929,6 +943,45 @@ export class HttpService {
|
|
|
929
943
|
clearTokens(): void {
|
|
930
944
|
this.tokenStore.clearTokens();
|
|
931
945
|
this.tokenStore.clearCsrfToken();
|
|
946
|
+
this.notifyTokenChange();
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* Subscribe to access-token changes on this instance.
|
|
951
|
+
*
|
|
952
|
+
* Fires on every mutation of the access token — `setTokens`, `clearTokens`,
|
|
953
|
+
* a successful silent refresh, and the internal 401-driven clear — passing
|
|
954
|
+
* the resulting token (or `null` when cleared). Returns an unsubscribe
|
|
955
|
+
* function; call it on teardown to avoid leaks.
|
|
956
|
+
*
|
|
957
|
+
* This is the single hook downstream code (e.g. @oxyhq/services' OxyProvider)
|
|
958
|
+
* uses to keep an external token sink — such as the shared `oxyClient`
|
|
959
|
+
* singleton — in lockstep with the active session, regardless of which code
|
|
960
|
+
* path mutated the token.
|
|
961
|
+
*/
|
|
962
|
+
addTokenChangeListener(listener: (accessToken: string | null) => void): () => void {
|
|
963
|
+
this._tokenChangeListeners.add(listener);
|
|
964
|
+
return () => {
|
|
965
|
+
this._tokenChangeListeners.delete(listener);
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Notify all token-change listeners with the current access token.
|
|
971
|
+
* Listener exceptions are isolated so one bad subscriber cannot break token
|
|
972
|
+
* propagation to the others or to the calling auth flow.
|
|
973
|
+
* @internal
|
|
974
|
+
*/
|
|
975
|
+
private notifyTokenChange(): void {
|
|
976
|
+
if (this._tokenChangeListeners.size === 0) return;
|
|
977
|
+
const accessToken = this.tokenStore.getAccessToken();
|
|
978
|
+
for (const listener of this._tokenChangeListeners) {
|
|
979
|
+
try {
|
|
980
|
+
listener(accessToken);
|
|
981
|
+
} catch (error) {
|
|
982
|
+
this.logger.error('Token change listener threw:', error);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
932
985
|
}
|
|
933
986
|
|
|
934
987
|
getAccessToken(): string | null {
|
package/src/OxyServices.base.ts
CHANGED
|
@@ -146,6 +146,23 @@ export class OxyServicesBase {
|
|
|
146
146
|
this._cachedAccessToken = null;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Subscribe to access-token changes on this client.
|
|
151
|
+
*
|
|
152
|
+
* The listener fires on every access-token mutation — explicit
|
|
153
|
+
* `setTokens`/`clearTokens`, a successful silent refresh, and the internal
|
|
154
|
+
* 401-driven clear — receiving the resulting token, or `null` when cleared.
|
|
155
|
+
* Returns an unsubscribe function.
|
|
156
|
+
*
|
|
157
|
+
* Primary use: keeping an external token sink (e.g. the shared `oxyClient`
|
|
158
|
+
* singleton) in lockstep with whichever `OxyServices` instance actually owns
|
|
159
|
+
* the session, so imperative consumers reading the singleton always observe
|
|
160
|
+
* the live token regardless of the code path that changed it.
|
|
161
|
+
*/
|
|
162
|
+
public onTokensChanged(listener: (accessToken: string | null) => void): () => void {
|
|
163
|
+
return this.httpService.addTokenChangeListener(listener);
|
|
164
|
+
}
|
|
165
|
+
|
|
149
166
|
/** @internal */ _cachedUserId: string | null | undefined = undefined;
|
|
150
167
|
/** @internal */ _cachedAccessToken: string | null = null;
|
|
151
168
|
|
package/src/OxyServices.ts
CHANGED
|
@@ -109,6 +109,16 @@ const OxyServicesComposed = composeOxyServices();
|
|
|
109
109
|
// We extend the composed constructor directly — its public surface is broadened
|
|
110
110
|
// to the full mixin set via the interface declaration that follows.
|
|
111
111
|
export class OxyServices extends OxyServicesComposed {
|
|
112
|
+
/**
|
|
113
|
+
* FedCM credential-request timeouts (ms). The runtime values are defined on
|
|
114
|
+
* the FedCM mixin and inherited here via `extends`; these `declare` members
|
|
115
|
+
* surface their types to TypeScript without re-emitting (or duplicating) the
|
|
116
|
+
* literals, so consumers/tests can reference `OxyServices.FEDCM_SILENT_TIMEOUT`
|
|
117
|
+
* with full typing.
|
|
118
|
+
*/
|
|
119
|
+
declare static readonly FEDCM_TIMEOUT: number;
|
|
120
|
+
declare static readonly FEDCM_SILENT_TIMEOUT: number;
|
|
121
|
+
|
|
112
122
|
constructor(config: OxyConfig) {
|
|
113
123
|
super(config);
|
|
114
124
|
}
|