astro-tokenkit 1.0.18 → 1.0.20
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 +14 -0
- package/dist/auth/manager.d.ts +1 -1
- package/dist/auth/manager.js +45 -18
- package/dist/client/client.js +12 -6
- package/dist/index.cjs +77 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +77 -33
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +65 -27
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.js +65 -27
- package/dist/middleware.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/utils/fetch.js +20 -9
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -307,3 +307,17 @@ Run on a standard development machine using `npm run bench`:
|
|
|
307
307
|
## License
|
|
308
308
|
|
|
309
309
|
MIT © [oamm](https://github.com/oamm)
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Playground
|
|
314
|
+
|
|
315
|
+
We've included a [playground](./playground) project to quickly test the integration.
|
|
316
|
+
|
|
317
|
+
To run the playground:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
npm run playground
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
This will install the dependencies and start the Astro dev server for the playground.
|
package/dist/auth/manager.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare class TokenManager {
|
|
|
23
23
|
/**
|
|
24
24
|
* Ensure valid tokens (with automatic refresh)
|
|
25
25
|
*/
|
|
26
|
-
ensure(ctx: TokenKitContext, options?: AuthOptions, headers?: Record<string, string
|
|
26
|
+
ensure(ctx: TokenKitContext, options?: AuthOptions, headers?: Record<string, string>, force?: boolean): Promise<Session | null>;
|
|
27
27
|
/**
|
|
28
28
|
* Logout (clear tokens)
|
|
29
29
|
*/
|
package/dist/auth/manager.js
CHANGED
|
@@ -26,21 +26,18 @@ class SingleFlight {
|
|
|
26
26
|
const existing = this.inFlight.get(key);
|
|
27
27
|
if (existing)
|
|
28
28
|
return existing;
|
|
29
|
-
const promise = this
|
|
29
|
+
const promise = (() => __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
try {
|
|
31
|
+
return yield fn();
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
this.inFlight.delete(key);
|
|
35
|
+
}
|
|
36
|
+
}))();
|
|
30
37
|
this.inFlight.set(key, promise);
|
|
31
38
|
return promise;
|
|
32
39
|
});
|
|
33
40
|
}
|
|
34
|
-
doExecute(key, fn) {
|
|
35
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
36
|
-
try {
|
|
37
|
-
return yield fn();
|
|
38
|
-
}
|
|
39
|
-
finally {
|
|
40
|
-
this.inFlight.delete(key);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
41
|
}
|
|
45
42
|
/**
|
|
46
43
|
* Token Manager handles all token operations
|
|
@@ -56,6 +53,7 @@ export class TokenManager {
|
|
|
56
53
|
*/
|
|
57
54
|
login(ctx, credentials, options) {
|
|
58
55
|
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
var _a, _b;
|
|
59
57
|
const url = this.joinURL(this.baseURL, this.config.login);
|
|
60
58
|
const contentType = this.config.contentType || 'application/json';
|
|
61
59
|
const headers = Object.assign(Object.assign({ 'Content-Type': contentType }, this.config.headers), options === null || options === void 0 ? void 0 : options.headers);
|
|
@@ -67,12 +65,16 @@ export class TokenManager {
|
|
|
67
65
|
else {
|
|
68
66
|
requestBody = JSON.stringify(data);
|
|
69
67
|
}
|
|
68
|
+
const timeout = (_b = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : this.config.timeout) !== null && _b !== void 0 ? _b : 30000;
|
|
69
|
+
const controller = new AbortController();
|
|
70
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
70
71
|
let response;
|
|
71
72
|
try {
|
|
72
73
|
response = yield safeFetch(url, {
|
|
73
74
|
method: 'POST',
|
|
74
75
|
headers,
|
|
75
76
|
body: requestBody,
|
|
77
|
+
signal: controller.signal,
|
|
76
78
|
}, this.config);
|
|
77
79
|
}
|
|
78
80
|
catch (error) {
|
|
@@ -81,6 +83,9 @@ export class TokenManager {
|
|
|
81
83
|
yield options.onError(authError, ctx);
|
|
82
84
|
throw authError;
|
|
83
85
|
}
|
|
86
|
+
finally {
|
|
87
|
+
clearTimeout(timeoutId);
|
|
88
|
+
}
|
|
84
89
|
if (!response.ok) {
|
|
85
90
|
const authError = new AuthError(`Login failed: ${response.status} ${response.statusText}`, response.status, response);
|
|
86
91
|
if (options === null || options === void 0 ? void 0 : options.onError)
|
|
@@ -136,6 +141,7 @@ export class TokenManager {
|
|
|
136
141
|
*/
|
|
137
142
|
performRefresh(ctx, refreshToken, options, extraHeaders) {
|
|
138
143
|
return __awaiter(this, void 0, void 0, function* () {
|
|
144
|
+
var _a, _b;
|
|
139
145
|
const url = this.joinURL(this.baseURL, this.config.refresh);
|
|
140
146
|
const contentType = this.config.contentType || 'application/json';
|
|
141
147
|
const headers = Object.assign(Object.assign({ 'Content-Type': contentType }, this.config.headers), extraHeaders);
|
|
@@ -148,17 +154,24 @@ export class TokenManager {
|
|
|
148
154
|
else {
|
|
149
155
|
requestBody = JSON.stringify(data);
|
|
150
156
|
}
|
|
157
|
+
const timeout = (_b = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : this.config.timeout) !== null && _b !== void 0 ? _b : 30000;
|
|
158
|
+
const controller = new AbortController();
|
|
159
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
151
160
|
let response;
|
|
152
161
|
try {
|
|
153
162
|
response = yield safeFetch(url, {
|
|
154
163
|
method: 'POST',
|
|
155
164
|
headers,
|
|
156
165
|
body: requestBody,
|
|
166
|
+
signal: controller.signal,
|
|
157
167
|
}, this.config);
|
|
158
168
|
}
|
|
159
169
|
catch (error) {
|
|
160
170
|
throw new AuthError(`Refresh request failed: ${error.message}`, undefined, undefined, undefined, error);
|
|
161
171
|
}
|
|
172
|
+
finally {
|
|
173
|
+
clearTimeout(timeoutId);
|
|
174
|
+
}
|
|
162
175
|
if (!response.ok) {
|
|
163
176
|
// 401/403 = invalid refresh token
|
|
164
177
|
if (response.status === 401 || response.status === 403) {
|
|
@@ -190,8 +203,8 @@ export class TokenManager {
|
|
|
190
203
|
/**
|
|
191
204
|
* Ensure valid tokens (with automatic refresh)
|
|
192
205
|
*/
|
|
193
|
-
ensure(
|
|
194
|
-
return __awaiter(this,
|
|
206
|
+
ensure(ctx_1, options_1, headers_1) {
|
|
207
|
+
return __awaiter(this, arguments, void 0, function* (ctx, options, headers, force = false) {
|
|
195
208
|
var _a, _b, _c, _d, _e, _f;
|
|
196
209
|
const now = Math.floor(Date.now() / 1000);
|
|
197
210
|
const tokens = retrieveTokens(ctx, this.config.cookies);
|
|
@@ -199,12 +212,14 @@ export class TokenManager {
|
|
|
199
212
|
if (!tokens.accessToken || !tokens.refreshToken || !tokens.expiresAt) {
|
|
200
213
|
return null;
|
|
201
214
|
}
|
|
202
|
-
// Token expired
|
|
203
|
-
if (isExpired(tokens.expiresAt, now, this.config.policy)) {
|
|
215
|
+
// Token expired or force refresh
|
|
216
|
+
if (force || isExpired(tokens.expiresAt, now, this.config.policy)) {
|
|
204
217
|
const flightKey = this.createFlightKey(tokens.refreshToken);
|
|
205
218
|
const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
|
|
206
219
|
if (!bundle)
|
|
207
220
|
return null;
|
|
221
|
+
// Ensure tokens are stored in the current context (in case of shared flight)
|
|
222
|
+
storeTokens(ctx, bundle, this.config.cookies);
|
|
208
223
|
return {
|
|
209
224
|
accessToken: bundle.accessToken,
|
|
210
225
|
expiresAt: bundle.accessExpiresAt,
|
|
@@ -217,6 +232,8 @@ export class TokenManager {
|
|
|
217
232
|
const flightKey = this.createFlightKey(tokens.refreshToken);
|
|
218
233
|
const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
|
|
219
234
|
if (bundle) {
|
|
235
|
+
// Ensure tokens are stored in the current context (in case of shared flight)
|
|
236
|
+
storeTokens(ctx, bundle, this.config.cookies);
|
|
220
237
|
return {
|
|
221
238
|
accessToken: bundle.accessToken,
|
|
222
239
|
expiresAt: bundle.accessExpiresAt,
|
|
@@ -244,23 +261,33 @@ export class TokenManager {
|
|
|
244
261
|
*/
|
|
245
262
|
logout(ctx) {
|
|
246
263
|
return __awaiter(this, void 0, void 0, function* () {
|
|
247
|
-
var _a;
|
|
264
|
+
var _a, _b;
|
|
248
265
|
// Optionally call logout endpoint
|
|
249
266
|
if (this.config.logout) {
|
|
267
|
+
const timeout = (_a = this.config.timeout) !== null && _a !== void 0 ? _a : 10000;
|
|
268
|
+
const controller = new AbortController();
|
|
269
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
250
270
|
try {
|
|
251
271
|
const url = this.joinURL(this.baseURL, this.config.logout);
|
|
252
272
|
const session = this.getSession(ctx);
|
|
253
273
|
const headers = {};
|
|
254
274
|
if (session === null || session === void 0 ? void 0 : session.accessToken) {
|
|
255
|
-
const injectFn = (
|
|
275
|
+
const injectFn = (_b = this.config.injectToken) !== null && _b !== void 0 ? _b : ((token, type) => `${type !== null && type !== void 0 ? type : 'Bearer'} ${token}`);
|
|
256
276
|
headers['Authorization'] = injectFn(session.accessToken, session.tokenType);
|
|
257
277
|
}
|
|
258
|
-
yield safeFetch(url, {
|
|
278
|
+
yield safeFetch(url, {
|
|
279
|
+
method: 'POST',
|
|
280
|
+
headers,
|
|
281
|
+
signal: controller.signal,
|
|
282
|
+
}, this.config);
|
|
259
283
|
}
|
|
260
284
|
catch (error) {
|
|
261
285
|
// Ignore logout endpoint errors
|
|
262
286
|
logger.debug('[TokenKit] Logout endpoint failed:', error);
|
|
263
287
|
}
|
|
288
|
+
finally {
|
|
289
|
+
clearTimeout(timeoutId);
|
|
290
|
+
}
|
|
264
291
|
}
|
|
265
292
|
clearTokens(ctx, this.config.cookies);
|
|
266
293
|
});
|
package/dist/client/client.js
CHANGED
|
@@ -143,6 +143,7 @@ export class APIClient {
|
|
|
143
143
|
executeRequest(config, ctx, attempt) {
|
|
144
144
|
return __awaiter(this, void 0, void 0, function* () {
|
|
145
145
|
var _a, _b, _c, _d, _e;
|
|
146
|
+
const method = config.method.toUpperCase();
|
|
146
147
|
// Ensure valid session (if auth is enabled)
|
|
147
148
|
if (this.tokenManager && !config.skipAuth) {
|
|
148
149
|
yield this.tokenManager.ensure(ctx, config.auth, config.headers);
|
|
@@ -153,13 +154,18 @@ export class APIClient {
|
|
|
153
154
|
const headers = this.buildHeaders(config, ctx, fullURL);
|
|
154
155
|
// Build request init
|
|
155
156
|
const init = {
|
|
156
|
-
method
|
|
157
|
+
method,
|
|
157
158
|
headers,
|
|
158
159
|
signal: config.signal,
|
|
159
160
|
};
|
|
160
|
-
// Add body for
|
|
161
|
-
|
|
161
|
+
// Add body for appropriate methods
|
|
162
|
+
const methodsWithNoBody = ['GET', 'HEAD', 'DELETE'];
|
|
163
|
+
if (config.data && !methodsWithNoBody.includes(method)) {
|
|
162
164
|
init.body = JSON.stringify(config.data);
|
|
165
|
+
// Add Content-Type if not already present
|
|
166
|
+
if (!headers['Content-Type'] && !headers['content-type']) {
|
|
167
|
+
headers['Content-Type'] = 'application/json';
|
|
168
|
+
}
|
|
163
169
|
}
|
|
164
170
|
// Apply request interceptors
|
|
165
171
|
let requestConfig = Object.assign({}, config);
|
|
@@ -177,8 +183,8 @@ export class APIClient {
|
|
|
177
183
|
clearTimeout(timeoutId);
|
|
178
184
|
// Handle 401 (try refresh and retry once)
|
|
179
185
|
if (response.status === 401 && this.tokenManager && !config.skipAuth && attempt === 1) {
|
|
180
|
-
// Clear and try fresh session
|
|
181
|
-
const session = yield this.tokenManager.ensure(ctx, config.auth, config.headers);
|
|
186
|
+
// Clear and try fresh session (force refresh)
|
|
187
|
+
const session = yield this.tokenManager.ensure(ctx, config.auth, config.headers, true);
|
|
182
188
|
if (session) {
|
|
183
189
|
// Retry with new token
|
|
184
190
|
return this.executeRequest(config, ctx, attempt + 1);
|
|
@@ -271,7 +277,7 @@ export class APIClient {
|
|
|
271
277
|
*/
|
|
272
278
|
buildHeaders(config, ctx, targetURL) {
|
|
273
279
|
var _a, _b;
|
|
274
|
-
const headers = Object.assign(Object.assign({
|
|
280
|
+
const headers = Object.assign(Object.assign({}, this.config.headers), config.headers);
|
|
275
281
|
// Add auth token if available (only for safe URLs)
|
|
276
282
|
if (this.tokenManager && !config.skipAuth && this.isSafeURL(targetURL)) {
|
|
277
283
|
const session = this.tokenManager.getSession(ctx);
|
package/dist/index.cjs
CHANGED
|
@@ -400,6 +400,7 @@ function isExpired(expiresAt, now, policy = {}) {
|
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
// packages/astro-tokenkit/src/utils/fetch.ts
|
|
403
|
+
let sharedInsecureAgent = null;
|
|
403
404
|
/**
|
|
404
405
|
* Perform a fetch request with optional certificate validation bypass
|
|
405
406
|
*/
|
|
@@ -408,16 +409,26 @@ function safeFetch(url, init, config) {
|
|
|
408
409
|
const fetchFn = config.fetch || fetch;
|
|
409
410
|
const fetchOptions = Object.assign({}, init);
|
|
410
411
|
if (config.dangerouslyIgnoreCertificateErrors && typeof process !== 'undefined') {
|
|
411
|
-
// In Node.js environment
|
|
412
412
|
try {
|
|
413
|
-
// Try to use undici Agent if available
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
413
|
+
// Try to use undici Agent if available to avoid global process.env changes
|
|
414
|
+
if (!sharedInsecureAgent) {
|
|
415
|
+
// @ts-ignore
|
|
416
|
+
const undici = yield import('undici').catch(() => null);
|
|
417
|
+
if (undici && undici.Agent) {
|
|
418
|
+
sharedInsecureAgent = new undici.Agent({
|
|
419
|
+
connect: { rejectUnauthorized: false }
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (sharedInsecureAgent) {
|
|
424
|
+
fetchOptions.dispatcher = sharedInsecureAgent;
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
// Fallback to global setting (less secure, but only way without undici)
|
|
428
|
+
if (process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0') {
|
|
429
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
430
|
+
}
|
|
431
|
+
}
|
|
421
432
|
}
|
|
422
433
|
catch (e) {
|
|
423
434
|
// Ignore
|
|
@@ -528,21 +539,18 @@ class SingleFlight {
|
|
|
528
539
|
const existing = this.inFlight.get(key);
|
|
529
540
|
if (existing)
|
|
530
541
|
return existing;
|
|
531
|
-
const promise = this
|
|
542
|
+
const promise = (() => __awaiter(this, void 0, void 0, function* () {
|
|
543
|
+
try {
|
|
544
|
+
return yield fn();
|
|
545
|
+
}
|
|
546
|
+
finally {
|
|
547
|
+
this.inFlight.delete(key);
|
|
548
|
+
}
|
|
549
|
+
}))();
|
|
532
550
|
this.inFlight.set(key, promise);
|
|
533
551
|
return promise;
|
|
534
552
|
});
|
|
535
553
|
}
|
|
536
|
-
doExecute(key, fn) {
|
|
537
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
538
|
-
try {
|
|
539
|
-
return yield fn();
|
|
540
|
-
}
|
|
541
|
-
finally {
|
|
542
|
-
this.inFlight.delete(key);
|
|
543
|
-
}
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
554
|
}
|
|
547
555
|
/**
|
|
548
556
|
* Token Manager handles all token operations
|
|
@@ -558,6 +566,7 @@ class TokenManager {
|
|
|
558
566
|
*/
|
|
559
567
|
login(ctx, credentials, options) {
|
|
560
568
|
return __awaiter(this, void 0, void 0, function* () {
|
|
569
|
+
var _a, _b;
|
|
561
570
|
const url = this.joinURL(this.baseURL, this.config.login);
|
|
562
571
|
const contentType = this.config.contentType || 'application/json';
|
|
563
572
|
const headers = Object.assign(Object.assign({ 'Content-Type': contentType }, this.config.headers), options === null || options === void 0 ? void 0 : options.headers);
|
|
@@ -569,12 +578,16 @@ class TokenManager {
|
|
|
569
578
|
else {
|
|
570
579
|
requestBody = JSON.stringify(data);
|
|
571
580
|
}
|
|
581
|
+
const timeout = (_b = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : this.config.timeout) !== null && _b !== void 0 ? _b : 30000;
|
|
582
|
+
const controller = new AbortController();
|
|
583
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
572
584
|
let response;
|
|
573
585
|
try {
|
|
574
586
|
response = yield safeFetch(url, {
|
|
575
587
|
method: 'POST',
|
|
576
588
|
headers,
|
|
577
589
|
body: requestBody,
|
|
590
|
+
signal: controller.signal,
|
|
578
591
|
}, this.config);
|
|
579
592
|
}
|
|
580
593
|
catch (error) {
|
|
@@ -583,6 +596,9 @@ class TokenManager {
|
|
|
583
596
|
yield options.onError(authError, ctx);
|
|
584
597
|
throw authError;
|
|
585
598
|
}
|
|
599
|
+
finally {
|
|
600
|
+
clearTimeout(timeoutId);
|
|
601
|
+
}
|
|
586
602
|
if (!response.ok) {
|
|
587
603
|
const authError = new AuthError(`Login failed: ${response.status} ${response.statusText}`, response.status, response);
|
|
588
604
|
if (options === null || options === void 0 ? void 0 : options.onError)
|
|
@@ -638,6 +654,7 @@ class TokenManager {
|
|
|
638
654
|
*/
|
|
639
655
|
performRefresh(ctx, refreshToken, options, extraHeaders) {
|
|
640
656
|
return __awaiter(this, void 0, void 0, function* () {
|
|
657
|
+
var _a, _b;
|
|
641
658
|
const url = this.joinURL(this.baseURL, this.config.refresh);
|
|
642
659
|
const contentType = this.config.contentType || 'application/json';
|
|
643
660
|
const headers = Object.assign(Object.assign({ 'Content-Type': contentType }, this.config.headers), extraHeaders);
|
|
@@ -650,17 +667,24 @@ class TokenManager {
|
|
|
650
667
|
else {
|
|
651
668
|
requestBody = JSON.stringify(data);
|
|
652
669
|
}
|
|
670
|
+
const timeout = (_b = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : this.config.timeout) !== null && _b !== void 0 ? _b : 30000;
|
|
671
|
+
const controller = new AbortController();
|
|
672
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
653
673
|
let response;
|
|
654
674
|
try {
|
|
655
675
|
response = yield safeFetch(url, {
|
|
656
676
|
method: 'POST',
|
|
657
677
|
headers,
|
|
658
678
|
body: requestBody,
|
|
679
|
+
signal: controller.signal,
|
|
659
680
|
}, this.config);
|
|
660
681
|
}
|
|
661
682
|
catch (error) {
|
|
662
683
|
throw new AuthError(`Refresh request failed: ${error.message}`, undefined, undefined, undefined, error);
|
|
663
684
|
}
|
|
685
|
+
finally {
|
|
686
|
+
clearTimeout(timeoutId);
|
|
687
|
+
}
|
|
664
688
|
if (!response.ok) {
|
|
665
689
|
// 401/403 = invalid refresh token
|
|
666
690
|
if (response.status === 401 || response.status === 403) {
|
|
@@ -692,8 +716,8 @@ class TokenManager {
|
|
|
692
716
|
/**
|
|
693
717
|
* Ensure valid tokens (with automatic refresh)
|
|
694
718
|
*/
|
|
695
|
-
ensure(
|
|
696
|
-
return __awaiter(this,
|
|
719
|
+
ensure(ctx_1, options_1, headers_1) {
|
|
720
|
+
return __awaiter(this, arguments, void 0, function* (ctx, options, headers, force = false) {
|
|
697
721
|
var _a, _b, _c, _d, _e, _f;
|
|
698
722
|
const now = Math.floor(Date.now() / 1000);
|
|
699
723
|
const tokens = retrieveTokens(ctx, this.config.cookies);
|
|
@@ -701,12 +725,14 @@ class TokenManager {
|
|
|
701
725
|
if (!tokens.accessToken || !tokens.refreshToken || !tokens.expiresAt) {
|
|
702
726
|
return null;
|
|
703
727
|
}
|
|
704
|
-
// Token expired
|
|
705
|
-
if (isExpired(tokens.expiresAt, now, this.config.policy)) {
|
|
728
|
+
// Token expired or force refresh
|
|
729
|
+
if (force || isExpired(tokens.expiresAt, now, this.config.policy)) {
|
|
706
730
|
const flightKey = this.createFlightKey(tokens.refreshToken);
|
|
707
731
|
const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
|
|
708
732
|
if (!bundle)
|
|
709
733
|
return null;
|
|
734
|
+
// Ensure tokens are stored in the current context (in case of shared flight)
|
|
735
|
+
storeTokens(ctx, bundle, this.config.cookies);
|
|
710
736
|
return {
|
|
711
737
|
accessToken: bundle.accessToken,
|
|
712
738
|
expiresAt: bundle.accessExpiresAt,
|
|
@@ -719,6 +745,8 @@ class TokenManager {
|
|
|
719
745
|
const flightKey = this.createFlightKey(tokens.refreshToken);
|
|
720
746
|
const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
|
|
721
747
|
if (bundle) {
|
|
748
|
+
// Ensure tokens are stored in the current context (in case of shared flight)
|
|
749
|
+
storeTokens(ctx, bundle, this.config.cookies);
|
|
722
750
|
return {
|
|
723
751
|
accessToken: bundle.accessToken,
|
|
724
752
|
expiresAt: bundle.accessExpiresAt,
|
|
@@ -746,23 +774,33 @@ class TokenManager {
|
|
|
746
774
|
*/
|
|
747
775
|
logout(ctx) {
|
|
748
776
|
return __awaiter(this, void 0, void 0, function* () {
|
|
749
|
-
var _a;
|
|
777
|
+
var _a, _b;
|
|
750
778
|
// Optionally call logout endpoint
|
|
751
779
|
if (this.config.logout) {
|
|
780
|
+
const timeout = (_a = this.config.timeout) !== null && _a !== void 0 ? _a : 10000;
|
|
781
|
+
const controller = new AbortController();
|
|
782
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
752
783
|
try {
|
|
753
784
|
const url = this.joinURL(this.baseURL, this.config.logout);
|
|
754
785
|
const session = this.getSession(ctx);
|
|
755
786
|
const headers = {};
|
|
756
787
|
if (session === null || session === void 0 ? void 0 : session.accessToken) {
|
|
757
|
-
const injectFn = (
|
|
788
|
+
const injectFn = (_b = this.config.injectToken) !== null && _b !== void 0 ? _b : ((token, type) => `${type !== null && type !== void 0 ? type : 'Bearer'} ${token}`);
|
|
758
789
|
headers['Authorization'] = injectFn(session.accessToken, session.tokenType);
|
|
759
790
|
}
|
|
760
|
-
yield safeFetch(url, {
|
|
791
|
+
yield safeFetch(url, {
|
|
792
|
+
method: 'POST',
|
|
793
|
+
headers,
|
|
794
|
+
signal: controller.signal,
|
|
795
|
+
}, this.config);
|
|
761
796
|
}
|
|
762
797
|
catch (error) {
|
|
763
798
|
// Ignore logout endpoint errors
|
|
764
799
|
logger.debug('[TokenKit] Logout endpoint failed:', error);
|
|
765
800
|
}
|
|
801
|
+
finally {
|
|
802
|
+
clearTimeout(timeoutId);
|
|
803
|
+
}
|
|
766
804
|
}
|
|
767
805
|
clearTokens(ctx, this.config.cookies);
|
|
768
806
|
});
|
|
@@ -1065,6 +1103,7 @@ class APIClient {
|
|
|
1065
1103
|
executeRequest(config, ctx, attempt) {
|
|
1066
1104
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1067
1105
|
var _a, _b, _c, _d, _e;
|
|
1106
|
+
const method = config.method.toUpperCase();
|
|
1068
1107
|
// Ensure valid session (if auth is enabled)
|
|
1069
1108
|
if (this.tokenManager && !config.skipAuth) {
|
|
1070
1109
|
yield this.tokenManager.ensure(ctx, config.auth, config.headers);
|
|
@@ -1075,13 +1114,18 @@ class APIClient {
|
|
|
1075
1114
|
const headers = this.buildHeaders(config, ctx, fullURL);
|
|
1076
1115
|
// Build request init
|
|
1077
1116
|
const init = {
|
|
1078
|
-
method
|
|
1117
|
+
method,
|
|
1079
1118
|
headers,
|
|
1080
1119
|
signal: config.signal,
|
|
1081
1120
|
};
|
|
1082
|
-
// Add body for
|
|
1083
|
-
|
|
1121
|
+
// Add body for appropriate methods
|
|
1122
|
+
const methodsWithNoBody = ['GET', 'HEAD', 'DELETE'];
|
|
1123
|
+
if (config.data && !methodsWithNoBody.includes(method)) {
|
|
1084
1124
|
init.body = JSON.stringify(config.data);
|
|
1125
|
+
// Add Content-Type if not already present
|
|
1126
|
+
if (!headers['Content-Type'] && !headers['content-type']) {
|
|
1127
|
+
headers['Content-Type'] = 'application/json';
|
|
1128
|
+
}
|
|
1085
1129
|
}
|
|
1086
1130
|
// Apply request interceptors
|
|
1087
1131
|
let requestConfig = Object.assign({}, config);
|
|
@@ -1099,8 +1143,8 @@ class APIClient {
|
|
|
1099
1143
|
clearTimeout(timeoutId);
|
|
1100
1144
|
// Handle 401 (try refresh and retry once)
|
|
1101
1145
|
if (response.status === 401 && this.tokenManager && !config.skipAuth && attempt === 1) {
|
|
1102
|
-
// Clear and try fresh session
|
|
1103
|
-
const session = yield this.tokenManager.ensure(ctx, config.auth, config.headers);
|
|
1146
|
+
// Clear and try fresh session (force refresh)
|
|
1147
|
+
const session = yield this.tokenManager.ensure(ctx, config.auth, config.headers, true);
|
|
1104
1148
|
if (session) {
|
|
1105
1149
|
// Retry with new token
|
|
1106
1150
|
return this.executeRequest(config, ctx, attempt + 1);
|
|
@@ -1193,7 +1237,7 @@ class APIClient {
|
|
|
1193
1237
|
*/
|
|
1194
1238
|
buildHeaders(config, ctx, targetURL) {
|
|
1195
1239
|
var _a, _b;
|
|
1196
|
-
const headers = Object.assign(Object.assign({
|
|
1240
|
+
const headers = Object.assign(Object.assign({}, this.config.headers), config.headers);
|
|
1197
1241
|
// Add auth token if available (only for safe URLs)
|
|
1198
1242
|
if (this.tokenManager && !config.skipAuth && this.isSafeURL(targetURL)) {
|
|
1199
1243
|
const session = this.tokenManager.getSession(ctx);
|