oneentry 1.0.151 → 1.0.152
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/changelog.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# SDK Change Log
|
|
2
2
|
|
|
3
|
+
## v.1.0.152
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
- Auth — eliminated the spurious `401` logged on every page load for a valid restored session. `browserResponse` now does a **proactive refresh** when a refresh token is present but there is no access token yet: it fetches the access token *before* the first request (clean `200`) instead of sending an unauthenticated request that is guaranteed to `401` and then reactively refreshing. After the proactive refresh it attaches `Authorization` and drops the `x-guest-id` scoping. The reactive `401 → refresh → retry` path is retained for mid-session token expiry.
|
|
8
|
+
|
|
9
|
+
- Auth — `refreshToken` is now **single-flight**.
|
|
10
|
+
|
|
3
11
|
## v.1.0.151
|
|
4
12
|
|
|
5
13
|
### What's New
|
|
@@ -57,11 +57,17 @@ export default abstract class AsyncModules extends SyncModules {
|
|
|
57
57
|
*/
|
|
58
58
|
protected _fetchDelete<T = unknown>(path: string, body?: unknown): Promise<T>;
|
|
59
59
|
/**
|
|
60
|
-
* Refreshes the authentication token.
|
|
60
|
+
* Refreshes the authentication token (single-flight).
|
|
61
61
|
* @returns {Promise<boolean>} A promise resolving to a boolean indicating success or failure.
|
|
62
|
-
* @description
|
|
62
|
+
* @description De-duplicates concurrent refreshes — the refresh token is single-use, so parallel requests must share one refresh call instead of each burning a rotated token.
|
|
63
63
|
*/
|
|
64
64
|
protected refreshToken(): Promise<boolean>;
|
|
65
|
+
/**
|
|
66
|
+
* Performs the actual token refresh request (without de-duplication).
|
|
67
|
+
* @returns {Promise<boolean>} A promise resolving to a boolean indicating success or failure.
|
|
68
|
+
* @description Sends POST /users/refresh and, on success, stores the rotated access/refresh tokens and calls saveFunction.
|
|
69
|
+
*/
|
|
70
|
+
private _performRefresh;
|
|
65
71
|
/**
|
|
66
72
|
* Creates options for HTTP requests.
|
|
67
73
|
* @param {string} method - The HTTP method (GET, POST, PUT, DELETE, etc.).
|
|
@@ -282,11 +282,33 @@ class AsyncModules extends syncModules_1.default {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
/**
|
|
285
|
-
* Refreshes the authentication token.
|
|
285
|
+
* Refreshes the authentication token (single-flight).
|
|
286
286
|
* @returns {Promise<boolean>} A promise resolving to a boolean indicating success or failure.
|
|
287
|
-
* @description
|
|
287
|
+
* @description De-duplicates concurrent refreshes — the refresh token is single-use, so parallel requests must share one refresh call instead of each burning a rotated token.
|
|
288
288
|
*/
|
|
289
289
|
async refreshToken() {
|
|
290
|
+
// Reuse an in-flight refresh if one is already running (shared via state
|
|
291
|
+
// across all module instances). Prevents parallel requests from each firing
|
|
292
|
+
// their own refresh and invalidating the single-use refresh token.
|
|
293
|
+
if (this.state._refreshPromise) {
|
|
294
|
+
return this.state._refreshPromise;
|
|
295
|
+
}
|
|
296
|
+
const promise = this._performRefresh();
|
|
297
|
+
this.state._refreshPromise = promise;
|
|
298
|
+
try {
|
|
299
|
+
return await promise;
|
|
300
|
+
}
|
|
301
|
+
finally {
|
|
302
|
+
// Clear so the next genuine 401 (e.g. mid-session expiry) can refresh again.
|
|
303
|
+
this.state._refreshPromise = null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Performs the actual token refresh request (without de-duplication).
|
|
308
|
+
* @returns {Promise<boolean>} A promise resolving to a boolean indicating success or failure.
|
|
309
|
+
* @description Sends POST /users/refresh and, on success, stores the rotated access/refresh tokens and calls saveFunction.
|
|
310
|
+
*/
|
|
311
|
+
async _performRefresh() {
|
|
290
312
|
const url = this.state.url +
|
|
291
313
|
`/api/content/users-auth-providers/marker/` +
|
|
292
314
|
this.state.providerMarker +
|
|
@@ -396,7 +418,32 @@ class AsyncModules extends syncModules_1.default {
|
|
|
396
418
|
* @description Define an asynchronous method 'browserResponse' that takes a path and options as parameters
|
|
397
419
|
*/
|
|
398
420
|
async browserResponse(path, options) {
|
|
421
|
+
// Set when a proactive refresh ran and failed (dead/expired refresh token):
|
|
422
|
+
// used below to skip the reactive refresh so we don't fire a second,
|
|
423
|
+
// equally-doomed refresh on the 401 that the request is about to return.
|
|
424
|
+
let proactiveRefreshFailed = false;
|
|
399
425
|
try {
|
|
426
|
+
// Proactive (eager) refresh: a session restored from storage has a refresh
|
|
427
|
+
// token but no access token yet. Sending the first authenticated request
|
|
428
|
+
// without an access token GUARANTEES a 401 (then a reactive refresh + retry)
|
|
429
|
+
// — a spurious 401 logged on every page load even for a perfectly valid
|
|
430
|
+
// session. Obtaining the access token up-front turns that into a clean 200.
|
|
431
|
+
// Fires at most once per session (skipped once accessToken is set), and is
|
|
432
|
+
// off for custom-auth callers who manage tokens themselves.
|
|
433
|
+
if (!this.state.accessToken &&
|
|
434
|
+
this.state.refreshToken &&
|
|
435
|
+
!this.state.customAuth) {
|
|
436
|
+
const refreshed = await this.refreshToken();
|
|
437
|
+
if (refreshed) {
|
|
438
|
+
// Authenticated now — attach the bearer and drop the guest scoping
|
|
439
|
+
// that makeOptions added while no access token was present.
|
|
440
|
+
options.headers['Authorization'] = 'Bearer ' + this.state.accessToken;
|
|
441
|
+
delete options.headers['x-guest-id'];
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
proactiveRefreshFailed = true;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
400
447
|
// Perform a fetch request using the full URL obtained from '_getFullPath' and the provided options
|
|
401
448
|
const response = await fetch(this._getFullPath(path), options);
|
|
402
449
|
// Check if the response status is OK (status code 200-299)
|
|
@@ -415,8 +462,12 @@ class AsyncModules extends syncModules_1.default {
|
|
|
415
462
|
}
|
|
416
463
|
else {
|
|
417
464
|
// Handle non-OK responses
|
|
418
|
-
//
|
|
419
|
-
|
|
465
|
+
// Reactive refresh for mid-session expiry: access token was present but
|
|
466
|
+
// the server rejected it. Skipped when a proactive refresh already ran
|
|
467
|
+
// and failed (the refresh token is dead — retrying would 400 again).
|
|
468
|
+
if (response.status === 401 &&
|
|
469
|
+
!this.state.customAuth &&
|
|
470
|
+
!proactiveRefreshFailed) {
|
|
420
471
|
// Attempt to refresh the access token
|
|
421
472
|
const refresh = await this.refreshToken();
|
|
422
473
|
if (refresh) {
|
package/dist/base/stateModule.js
CHANGED
|
@@ -46,6 +46,10 @@ class StateModule {
|
|
|
46
46
|
*/
|
|
47
47
|
constructor(url, config) {
|
|
48
48
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1;
|
|
49
|
+
// In-flight token refresh, shared across all module instances (they share this
|
|
50
|
+
// state object). De-duplicates concurrent refreshes so the single-use refresh
|
|
51
|
+
// token is not burned by parallel requests. Null when no refresh is running.
|
|
52
|
+
this._refreshPromise = null;
|
|
49
53
|
this.url = url;
|
|
50
54
|
this.lang = (_a = config.langCode) !== null && _a !== void 0 ? _a : 'en_US';
|
|
51
55
|
this.token = config.token;
|