astro-tokenkit 1.0.18 → 1.0.19

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.
@@ -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>): Promise<Session | null>;
26
+ ensure(ctx: TokenKitContext, options?: AuthOptions, headers?: Record<string, string>, force?: boolean): Promise<Session | null>;
27
27
  /**
28
28
  * Logout (clear tokens)
29
29
  */
@@ -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.doExecute(key, fn);
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(ctx, options, headers) {
194
- return __awaiter(this, void 0, void 0, function* () {
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 = (_a = this.config.injectToken) !== null && _a !== void 0 ? _a : ((token, type) => `${type !== null && type !== void 0 ? type : 'Bearer'} ${token}`);
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, { method: 'POST', headers }, this.config);
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
  });
@@ -177,8 +177,8 @@ export class APIClient {
177
177
  clearTimeout(timeoutId);
178
178
  // Handle 401 (try refresh and retry once)
179
179
  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);
180
+ // Clear and try fresh session (force refresh)
181
+ const session = yield this.tokenManager.ensure(ctx, config.auth, config.headers, true);
182
182
  if (session) {
183
183
  // Retry with new token
184
184
  return this.executeRequest(config, ctx, attempt + 1);
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 (it is built-in in Node 18+)
414
- // However, we might need to import it if we want to create an Agent.
415
- // Since we don't want to depend on undici in package.json, we use dynamic import.
416
- // But wait, undici's Agent is what we need.
417
- // As a fallback and most reliable way for self-signed certs in Node without extra deps:
418
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
419
- // NOTE: This affects the whole process. We should ideally only do this if it's not already 0.
420
- // But for a dev tool / specialized library, it's often what's needed.
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.doExecute(key, fn);
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(ctx, options, headers) {
696
- return __awaiter(this, void 0, void 0, function* () {
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 = (_a = this.config.injectToken) !== null && _a !== void 0 ? _a : ((token, type) => `${type !== null && type !== void 0 ? type : 'Bearer'} ${token}`);
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, { method: 'POST', headers }, this.config);
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
  });
@@ -1099,8 +1137,8 @@ class APIClient {
1099
1137
  clearTimeout(timeoutId);
1100
1138
  // Handle 401 (try refresh and retry once)
1101
1139
  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);
1140
+ // Clear and try fresh session (force refresh)
1141
+ const session = yield this.tokenManager.ensure(ctx, config.auth, config.headers, true);
1104
1142
  if (session) {
1105
1143
  // Retry with new token
1106
1144
  return this.executeRequest(config, ctx, attempt + 1);