astro-tokenkit 1.0.24 → 1.0.26

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.
@@ -20,18 +20,39 @@ import { logger } from '../utils/logger';
20
20
  class SingleFlight {
21
21
  constructor() {
22
22
  this.inFlight = new Map();
23
+ this.recent = new Map();
24
+ this.GRACE_PERIOD = 5000; // 5 seconds grace period for race conditions
23
25
  }
24
26
  execute(key, fn) {
25
27
  return __awaiter(this, void 0, void 0, function* () {
28
+ // 1. Check in-flight
26
29
  const existing = this.inFlight.get(key);
27
30
  if (existing)
28
31
  return existing;
32
+ // 2. Check recent (grace period)
33
+ const cached = this.recent.get(key);
34
+ if (cached && (Date.now() - cached.time < this.GRACE_PERIOD)) {
35
+ return cached.bundle;
36
+ }
37
+ // 3. Execute new flight
29
38
  const promise = (() => __awaiter(this, void 0, void 0, function* () {
30
39
  try {
31
- return yield fn();
40
+ const bundle = yield fn();
41
+ // Store in recent on success
42
+ if (bundle) {
43
+ this.recent.set(key, { bundle, time: Date.now() });
44
+ }
45
+ return bundle;
32
46
  }
33
47
  finally {
34
48
  this.inFlight.delete(key);
49
+ // Cleanup old entries
50
+ const now = Date.now();
51
+ for (const [k, v] of this.recent.entries()) {
52
+ if (now - v.time > this.GRACE_PERIOD) {
53
+ this.recent.delete(k);
54
+ }
55
+ }
35
56
  }
36
57
  }))();
37
58
  this.inFlight.set(key, promise);
@@ -127,29 +148,32 @@ export class TokenManager {
127
148
  */
128
149
  refresh(ctx, refreshToken, options, headers) {
129
150
  return __awaiter(this, void 0, void 0, function* () {
130
- logger.debug('[TokenKit] Starting token refresh', !!this.config.debug);
131
- try {
132
- const bundle = yield this.performRefresh(ctx, refreshToken, options, headers);
133
- if (bundle) {
134
- if (this.config.onRefresh) {
135
- yield this.config.onRefresh(bundle, ctx);
151
+ const flightKey = this.createFlightKey(refreshToken);
152
+ return this.singleFlight.execute(flightKey, () => __awaiter(this, void 0, void 0, function* () {
153
+ logger.debug('[TokenKit] Starting token refresh', !!this.config.debug);
154
+ try {
155
+ const bundle = yield this.performRefresh(ctx, refreshToken, options, headers);
156
+ if (bundle) {
157
+ if (this.config.onRefresh) {
158
+ yield this.config.onRefresh(bundle, ctx);
159
+ }
136
160
  }
161
+ else {
162
+ logger.debug('[TokenKit] Token refresh returned no bundle (invalid or expired)', !!this.config.debug);
163
+ if (this.config.onRefreshError) {
164
+ yield this.config.onRefreshError(new AuthError('Refresh token invalid or expired', 401), ctx);
165
+ }
166
+ }
167
+ return bundle;
137
168
  }
138
- else {
139
- logger.debug('[TokenKit] Token refresh returned no bundle (invalid or expired)', !!this.config.debug);
169
+ catch (error) {
170
+ logger.debug(`[TokenKit] Token refresh failed: ${error.message}`, !!this.config.debug);
140
171
  if (this.config.onRefreshError) {
141
- yield this.config.onRefreshError(new AuthError('Refresh token invalid or expired', 401), ctx);
172
+ yield this.config.onRefreshError(error, ctx);
142
173
  }
174
+ throw error;
143
175
  }
144
- return bundle;
145
- }
146
- catch (error) {
147
- logger.debug(`[TokenKit] Token refresh failed: ${error.message}`, !!this.config.debug);
148
- if (this.config.onRefreshError) {
149
- yield this.config.onRefreshError(error, ctx);
150
- }
151
- throw error;
152
- }
176
+ }));
153
177
  });
154
178
  }
155
179
  /**
@@ -189,8 +213,8 @@ export class TokenManager {
189
213
  clearTimeout(timeoutId);
190
214
  }
191
215
  if (!response.ok) {
192
- // 401/403 = invalid refresh token
193
- if (response.status === 401 || response.status === 403) {
216
+ // 400 (Bad Request), 401 (Unauthorized) or 403 (Forbidden) = invalid refresh token
217
+ if (response.status === 400 || response.status === 401 || response.status === 403) {
194
218
  clearTokens(ctx, this.config.cookies);
195
219
  return null;
196
220
  }
@@ -233,8 +257,7 @@ export class TokenManager {
233
257
  const expired = isExpired(tokens.expiresAt, now, this.config.policy);
234
258
  if (force || expired) {
235
259
  logger.debug(`[TokenKit] Token ${force ? 'force refresh' : 'expired'}, refreshing...`, !!this.config.debug);
236
- const flightKey = this.createFlightKey(tokens.refreshToken);
237
- const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
260
+ const bundle = yield this.refresh(ctx, tokens.refreshToken, options, headers);
238
261
  if (!bundle) {
239
262
  logger.debug('[TokenKit] Refresh returned no bundle, session lost', !!this.config.debug);
240
263
  return null;
@@ -251,9 +274,8 @@ export class TokenManager {
251
274
  // Proactive refresh
252
275
  if (shouldRefresh(tokens.expiresAt, now, tokens.lastRefreshAt, this.config.policy)) {
253
276
  logger.debug('[TokenKit] Token near expiration, performing proactive refresh', !!this.config.debug);
254
- const flightKey = this.createFlightKey(tokens.refreshToken);
255
277
  try {
256
- const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
278
+ const bundle = yield this.refresh(ctx, tokens.refreshToken, options, headers);
257
279
  if (bundle) {
258
280
  logger.debug('[TokenKit] Proactive refresh successful', !!this.config.debug);
259
281
  // Ensure tokens are stored in the current context (in case of shared flight)
@@ -26,5 +26,6 @@ export declare class IdleManager {
26
26
  private updateExpiredTimeLocal;
27
27
  private handleTimeout;
28
28
  private triggerIdle;
29
+ private isExcluded;
29
30
  cleanup(): void;
30
31
  }
@@ -52,6 +52,9 @@ export class IdleManager {
52
52
  start() {
53
53
  if (typeof window === 'undefined')
54
54
  return;
55
+ if (this.isExcluded()) {
56
+ return;
57
+ }
55
58
  this.updateExpiredTimeLocal();
56
59
  this.setupEventListeners();
57
60
  this.loop();
@@ -129,6 +132,10 @@ export class IdleManager {
129
132
  triggerIdle() {
130
133
  if (this.isIdle)
131
134
  return;
135
+ if (this.isExcluded()) {
136
+ this.updateExpiredTimeLocal();
137
+ return;
138
+ }
132
139
  this.isIdle = true;
133
140
  if (typeof window !== 'undefined') {
134
141
  window.dispatchEvent(new CustomEvent('tk:idle', { detail: this.config }));
@@ -136,6 +143,15 @@ export class IdleManager {
136
143
  this.cleanup();
137
144
  this.onIdle();
138
145
  }
146
+ isExcluded() {
147
+ if (this.config.excludePaths && this.config.excludePaths.length > 0) {
148
+ if (typeof window !== 'undefined') {
149
+ const currentPath = window.location.pathname;
150
+ return this.config.excludePaths.some(path => currentPath.startsWith(path));
151
+ }
152
+ }
153
+ return false;
154
+ }
139
155
  cleanup() {
140
156
  if (this.rafId) {
141
157
  cancelAnimationFrame(this.rafId);
package/dist/index.cjs CHANGED
@@ -533,18 +533,39 @@ const logger = {
533
533
  class SingleFlight {
534
534
  constructor() {
535
535
  this.inFlight = new Map();
536
+ this.recent = new Map();
537
+ this.GRACE_PERIOD = 5000; // 5 seconds grace period for race conditions
536
538
  }
537
539
  execute(key, fn) {
538
540
  return __awaiter(this, void 0, void 0, function* () {
541
+ // 1. Check in-flight
539
542
  const existing = this.inFlight.get(key);
540
543
  if (existing)
541
544
  return existing;
545
+ // 2. Check recent (grace period)
546
+ const cached = this.recent.get(key);
547
+ if (cached && (Date.now() - cached.time < this.GRACE_PERIOD)) {
548
+ return cached.bundle;
549
+ }
550
+ // 3. Execute new flight
542
551
  const promise = (() => __awaiter(this, void 0, void 0, function* () {
543
552
  try {
544
- return yield fn();
553
+ const bundle = yield fn();
554
+ // Store in recent on success
555
+ if (bundle) {
556
+ this.recent.set(key, { bundle, time: Date.now() });
557
+ }
558
+ return bundle;
545
559
  }
546
560
  finally {
547
561
  this.inFlight.delete(key);
562
+ // Cleanup old entries
563
+ const now = Date.now();
564
+ for (const [k, v] of this.recent.entries()) {
565
+ if (now - v.time > this.GRACE_PERIOD) {
566
+ this.recent.delete(k);
567
+ }
568
+ }
548
569
  }
549
570
  }))();
550
571
  this.inFlight.set(key, promise);
@@ -640,29 +661,32 @@ class TokenManager {
640
661
  */
641
662
  refresh(ctx, refreshToken, options, headers) {
642
663
  return __awaiter(this, void 0, void 0, function* () {
643
- logger.debug('[TokenKit] Starting token refresh', !!this.config.debug);
644
- try {
645
- const bundle = yield this.performRefresh(ctx, refreshToken, options, headers);
646
- if (bundle) {
647
- if (this.config.onRefresh) {
648
- yield this.config.onRefresh(bundle, ctx);
664
+ const flightKey = this.createFlightKey(refreshToken);
665
+ return this.singleFlight.execute(flightKey, () => __awaiter(this, void 0, void 0, function* () {
666
+ logger.debug('[TokenKit] Starting token refresh', !!this.config.debug);
667
+ try {
668
+ const bundle = yield this.performRefresh(ctx, refreshToken, options, headers);
669
+ if (bundle) {
670
+ if (this.config.onRefresh) {
671
+ yield this.config.onRefresh(bundle, ctx);
672
+ }
649
673
  }
674
+ else {
675
+ logger.debug('[TokenKit] Token refresh returned no bundle (invalid or expired)', !!this.config.debug);
676
+ if (this.config.onRefreshError) {
677
+ yield this.config.onRefreshError(new AuthError('Refresh token invalid or expired', 401), ctx);
678
+ }
679
+ }
680
+ return bundle;
650
681
  }
651
- else {
652
- logger.debug('[TokenKit] Token refresh returned no bundle (invalid or expired)', !!this.config.debug);
682
+ catch (error) {
683
+ logger.debug(`[TokenKit] Token refresh failed: ${error.message}`, !!this.config.debug);
653
684
  if (this.config.onRefreshError) {
654
- yield this.config.onRefreshError(new AuthError('Refresh token invalid or expired', 401), ctx);
685
+ yield this.config.onRefreshError(error, ctx);
655
686
  }
687
+ throw error;
656
688
  }
657
- return bundle;
658
- }
659
- catch (error) {
660
- logger.debug(`[TokenKit] Token refresh failed: ${error.message}`, !!this.config.debug);
661
- if (this.config.onRefreshError) {
662
- yield this.config.onRefreshError(error, ctx);
663
- }
664
- throw error;
665
- }
689
+ }));
666
690
  });
667
691
  }
668
692
  /**
@@ -702,8 +726,8 @@ class TokenManager {
702
726
  clearTimeout(timeoutId);
703
727
  }
704
728
  if (!response.ok) {
705
- // 401/403 = invalid refresh token
706
- if (response.status === 401 || response.status === 403) {
729
+ // 400 (Bad Request), 401 (Unauthorized) or 403 (Forbidden) = invalid refresh token
730
+ if (response.status === 400 || response.status === 401 || response.status === 403) {
707
731
  clearTokens(ctx, this.config.cookies);
708
732
  return null;
709
733
  }
@@ -746,8 +770,7 @@ class TokenManager {
746
770
  const expired = isExpired(tokens.expiresAt, now, this.config.policy);
747
771
  if (force || expired) {
748
772
  logger.debug(`[TokenKit] Token ${force ? 'force refresh' : 'expired'}, refreshing...`, !!this.config.debug);
749
- const flightKey = this.createFlightKey(tokens.refreshToken);
750
- const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
773
+ const bundle = yield this.refresh(ctx, tokens.refreshToken, options, headers);
751
774
  if (!bundle) {
752
775
  logger.debug('[TokenKit] Refresh returned no bundle, session lost', !!this.config.debug);
753
776
  return null;
@@ -764,9 +787,8 @@ class TokenManager {
764
787
  // Proactive refresh
765
788
  if (shouldRefresh(tokens.expiresAt, now, tokens.lastRefreshAt, this.config.policy)) {
766
789
  logger.debug('[TokenKit] Token near expiration, performing proactive refresh', !!this.config.debug);
767
- const flightKey = this.createFlightKey(tokens.refreshToken);
768
790
  try {
769
- const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
791
+ const bundle = yield this.refresh(ctx, tokens.refreshToken, options, headers);
770
792
  if (bundle) {
771
793
  logger.debug('[TokenKit] Proactive refresh successful', !!this.config.debug);
772
794
  // Ensure tokens are stored in the current context (in case of shared flight)