astro-tokenkit 1.0.19 → 1.0.21

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 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.
@@ -127,11 +127,27 @@ export class TokenManager {
127
127
  */
128
128
  refresh(ctx, refreshToken, options, headers) {
129
129
  return __awaiter(this, void 0, void 0, function* () {
130
+ logger.debug('[TokenKit] Starting token refresh', !!this.config.debug);
130
131
  try {
131
- return yield this.performRefresh(ctx, refreshToken, options, headers);
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);
136
+ }
137
+ }
138
+ else {
139
+ logger.debug('[TokenKit] Token refresh returned no bundle (invalid or expired)', !!this.config.debug);
140
+ if (this.config.onRefreshError) {
141
+ yield this.config.onRefreshError(new AuthError('Refresh token invalid or expired', 401), ctx);
142
+ }
143
+ }
144
+ return bundle;
132
145
  }
133
146
  catch (error) {
134
- clearTokens(ctx, this.config.cookies);
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
+ }
135
151
  throw error;
136
152
  }
137
153
  });
@@ -210,14 +226,19 @@ export class TokenManager {
210
226
  const tokens = retrieveTokens(ctx, this.config.cookies);
211
227
  // No tokens
212
228
  if (!tokens.accessToken || !tokens.refreshToken || !tokens.expiresAt) {
229
+ logger.debug('[TokenKit] No valid session found, refresh impossible', !!this.config.debug);
213
230
  return null;
214
231
  }
215
232
  // Token expired or force refresh
216
- if (force || isExpired(tokens.expiresAt, now, this.config.policy)) {
233
+ const expired = isExpired(tokens.expiresAt, now, this.config.policy);
234
+ if (force || expired) {
235
+ logger.debug(`[TokenKit] Token ${force ? 'force refresh' : 'expired'}, refreshing...`, !!this.config.debug);
217
236
  const flightKey = this.createFlightKey(tokens.refreshToken);
218
237
  const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
219
- if (!bundle)
238
+ if (!bundle) {
239
+ logger.debug('[TokenKit] Refresh returned no bundle, session lost', !!this.config.debug);
220
240
  return null;
241
+ }
221
242
  // Ensure tokens are stored in the current context (in case of shared flight)
222
243
  storeTokens(ctx, bundle, this.config.cookies);
223
244
  return {
@@ -229,19 +250,26 @@ export class TokenManager {
229
250
  }
230
251
  // Proactive refresh
231
252
  if (shouldRefresh(tokens.expiresAt, now, tokens.lastRefreshAt, this.config.policy)) {
253
+ logger.debug('[TokenKit] Token near expiration, performing proactive refresh', !!this.config.debug);
232
254
  const flightKey = this.createFlightKey(tokens.refreshToken);
233
- const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
234
- if (bundle) {
235
- // Ensure tokens are stored in the current context (in case of shared flight)
236
- storeTokens(ctx, bundle, this.config.cookies);
237
- return {
238
- accessToken: bundle.accessToken,
239
- expiresAt: bundle.accessExpiresAt,
240
- tokenType: bundle.tokenType,
241
- payload: (_d = (_c = bundle.sessionPayload) !== null && _c !== void 0 ? _c : parseJWTPayload(bundle.accessToken)) !== null && _d !== void 0 ? _d : undefined,
242
- };
255
+ try {
256
+ const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
257
+ if (bundle) {
258
+ logger.debug('[TokenKit] Proactive refresh successful', !!this.config.debug);
259
+ // Ensure tokens are stored in the current context (in case of shared flight)
260
+ storeTokens(ctx, bundle, this.config.cookies);
261
+ return {
262
+ accessToken: bundle.accessToken,
263
+ expiresAt: bundle.accessExpiresAt,
264
+ tokenType: bundle.tokenType,
265
+ payload: (_d = (_c = bundle.sessionPayload) !== null && _c !== void 0 ? _c : parseJWTPayload(bundle.accessToken)) !== null && _d !== void 0 ? _d : undefined,
266
+ };
267
+ }
268
+ }
269
+ catch (error) {
270
+ logger.debug(`[TokenKit] Proactive refresh failed: ${error.message}. Continuing with current token.`, !!this.config.debug);
243
271
  }
244
- // Refresh failed, check if tokens still exist
272
+ // Refresh failed or returned no bundle, check if tokens still exist
245
273
  const currentTokens = retrieveTokens(ctx, this.config.cookies);
246
274
  if (!currentTokens.accessToken) {
247
275
  return null;
@@ -283,7 +311,7 @@ export class TokenManager {
283
311
  }
284
312
  catch (error) {
285
313
  // Ignore logout endpoint errors
286
- logger.debug('[TokenKit] Logout endpoint failed:', error);
314
+ logger.debug('[TokenKit] Logout endpoint failed:', !!this.config.debug, error);
287
315
  }
288
316
  finally {
289
317
  clearTimeout(timeoutId);
@@ -15,6 +15,7 @@ import { calculateDelay, shouldRetry, sleep } from '../utils/retry';
15
15
  import { getConfig, getTokenManager } from '../config';
16
16
  import { createMiddleware } from '../middleware';
17
17
  import { safeFetch } from '../utils/fetch';
18
+ import { logger } from '../utils/logger';
18
19
  /**
19
20
  * API Client
20
21
  */
@@ -38,7 +39,7 @@ export class APIClient {
38
39
  * Get token manager
39
40
  */
40
41
  get tokenManager() {
41
- var _a, _b;
42
+ var _a, _b, _c;
42
43
  const config = this.config;
43
44
  if (!config.auth)
44
45
  return undefined;
@@ -54,8 +55,8 @@ export class APIClient {
54
55
  if (!this._localTokenManager ||
55
56
  this._lastUsedAuth !== config.auth ||
56
57
  this._lastUsedBaseURL !== config.baseURL) {
57
- // Merge client-level fetch and SSL settings into auth config
58
- const authConfig = Object.assign(Object.assign({}, config.auth), { fetch: (_a = config.auth.fetch) !== null && _a !== void 0 ? _a : config.fetch, dangerouslyIgnoreCertificateErrors: (_b = config.auth.dangerouslyIgnoreCertificateErrors) !== null && _b !== void 0 ? _b : config.dangerouslyIgnoreCertificateErrors });
58
+ // Merge client-level fetch, SSL and debug settings into auth config
59
+ const authConfig = Object.assign(Object.assign({}, config.auth), { fetch: (_a = config.auth.fetch) !== null && _a !== void 0 ? _a : config.fetch, dangerouslyIgnoreCertificateErrors: (_b = config.auth.dangerouslyIgnoreCertificateErrors) !== null && _b !== void 0 ? _b : config.dangerouslyIgnoreCertificateErrors, debug: (_c = config.auth.debug) !== null && _c !== void 0 ? _c : config.debug });
59
60
  this._localTokenManager = new TokenManager(authConfig, config.baseURL);
60
61
  this._lastUsedAuth = config.auth;
61
62
  this._lastUsedBaseURL = config.baseURL;
@@ -143,8 +144,11 @@ export class APIClient {
143
144
  executeRequest(config, ctx, attempt) {
144
145
  return __awaiter(this, void 0, void 0, function* () {
145
146
  var _a, _b, _c, _d, _e;
147
+ const method = config.method.toUpperCase();
148
+ const debug = this.config.debug;
146
149
  // Ensure valid session (if auth is enabled)
147
150
  if (this.tokenManager && !config.skipAuth) {
151
+ logger.debug(`[TokenKit] Ensuring valid session for ${method} ${config.url}`, !!debug);
148
152
  yield this.tokenManager.ensure(ctx, config.auth, config.headers);
149
153
  }
150
154
  // Build full URL
@@ -153,13 +157,18 @@ export class APIClient {
153
157
  const headers = this.buildHeaders(config, ctx, fullURL);
154
158
  // Build request init
155
159
  const init = {
156
- method: config.method,
160
+ method,
157
161
  headers,
158
162
  signal: config.signal,
159
163
  };
160
- // Add body for non-GET requests
161
- if (config.data && config.method !== 'GET') {
164
+ // Add body for appropriate methods
165
+ const methodsWithNoBody = ['GET', 'HEAD', 'DELETE'];
166
+ if (config.data && !methodsWithNoBody.includes(method)) {
162
167
  init.body = JSON.stringify(config.data);
168
+ // Add Content-Type if not already present
169
+ if (!headers['Content-Type'] && !headers['content-type']) {
170
+ headers['Content-Type'] = 'application/json';
171
+ }
163
172
  }
164
173
  // Apply request interceptors
165
174
  let requestConfig = Object.assign({}, config);
@@ -177,12 +186,15 @@ export class APIClient {
177
186
  clearTimeout(timeoutId);
178
187
  // Handle 401 (try refresh and retry once)
179
188
  if (response.status === 401 && this.tokenManager && !config.skipAuth && attempt === 1) {
189
+ logger.debug('[TokenKit] Received 401, attempting force refresh and retry...', !!debug);
180
190
  // Clear and try fresh session (force refresh)
181
191
  const session = yield this.tokenManager.ensure(ctx, config.auth, config.headers, true);
182
192
  if (session) {
193
+ logger.debug('[TokenKit] Force refresh successful, retrying request...', !!debug);
183
194
  // Retry with new token
184
195
  return this.executeRequest(config, ctx, attempt + 1);
185
196
  }
197
+ logger.debug('[TokenKit] Force refresh failed or returned no session', !!debug);
186
198
  }
187
199
  // Parse response
188
200
  const apiResponse = yield this.parseResponse(response, fullURL);
@@ -271,7 +283,7 @@ export class APIClient {
271
283
  */
272
284
  buildHeaders(config, ctx, targetURL) {
273
285
  var _a, _b;
274
- const headers = Object.assign(Object.assign({ 'Content-Type': 'application/json' }, this.config.headers), config.headers);
286
+ const headers = Object.assign(Object.assign({}, this.config.headers), config.headers);
275
287
  // Add auth token if available (only for safe URLs)
276
288
  if (this.tokenManager && !config.skipAuth && this.isSafeURL(targetURL)) {
277
289
  const session = this.tokenManager.getSession(ctx);
package/dist/index.cjs CHANGED
@@ -508,13 +508,13 @@ catch (e) {
508
508
  * Logger utility that respects the debug flag in the configuration
509
509
  */
510
510
  const logger = {
511
- debug: (message, ...args) => {
512
- if (getConfig().debug) {
511
+ debug: (message, force, ...args) => {
512
+ if (force || getConfig().debug) {
513
513
  console.debug(message, ...args);
514
514
  }
515
515
  },
516
- info: (message, ...args) => {
517
- if (getConfig().debug) {
516
+ info: (message, force, ...args) => {
517
+ if (force || getConfig().debug) {
518
518
  console.log(message, ...args);
519
519
  }
520
520
  },
@@ -640,11 +640,27 @@ class TokenManager {
640
640
  */
641
641
  refresh(ctx, refreshToken, options, headers) {
642
642
  return __awaiter(this, void 0, void 0, function* () {
643
+ logger.debug('[TokenKit] Starting token refresh', !!this.config.debug);
643
644
  try {
644
- return yield this.performRefresh(ctx, refreshToken, options, headers);
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);
649
+ }
650
+ }
651
+ else {
652
+ logger.debug('[TokenKit] Token refresh returned no bundle (invalid or expired)', !!this.config.debug);
653
+ if (this.config.onRefreshError) {
654
+ yield this.config.onRefreshError(new AuthError('Refresh token invalid or expired', 401), ctx);
655
+ }
656
+ }
657
+ return bundle;
645
658
  }
646
659
  catch (error) {
647
- clearTokens(ctx, this.config.cookies);
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
+ }
648
664
  throw error;
649
665
  }
650
666
  });
@@ -723,14 +739,19 @@ class TokenManager {
723
739
  const tokens = retrieveTokens(ctx, this.config.cookies);
724
740
  // No tokens
725
741
  if (!tokens.accessToken || !tokens.refreshToken || !tokens.expiresAt) {
742
+ logger.debug('[TokenKit] No valid session found, refresh impossible', !!this.config.debug);
726
743
  return null;
727
744
  }
728
745
  // Token expired or force refresh
729
- if (force || isExpired(tokens.expiresAt, now, this.config.policy)) {
746
+ const expired = isExpired(tokens.expiresAt, now, this.config.policy);
747
+ if (force || expired) {
748
+ logger.debug(`[TokenKit] Token ${force ? 'force refresh' : 'expired'}, refreshing...`, !!this.config.debug);
730
749
  const flightKey = this.createFlightKey(tokens.refreshToken);
731
750
  const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
732
- if (!bundle)
751
+ if (!bundle) {
752
+ logger.debug('[TokenKit] Refresh returned no bundle, session lost', !!this.config.debug);
733
753
  return null;
754
+ }
734
755
  // Ensure tokens are stored in the current context (in case of shared flight)
735
756
  storeTokens(ctx, bundle, this.config.cookies);
736
757
  return {
@@ -742,19 +763,26 @@ class TokenManager {
742
763
  }
743
764
  // Proactive refresh
744
765
  if (shouldRefresh(tokens.expiresAt, now, tokens.lastRefreshAt, this.config.policy)) {
766
+ logger.debug('[TokenKit] Token near expiration, performing proactive refresh', !!this.config.debug);
745
767
  const flightKey = this.createFlightKey(tokens.refreshToken);
746
- const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
747
- if (bundle) {
748
- // Ensure tokens are stored in the current context (in case of shared flight)
749
- storeTokens(ctx, bundle, this.config.cookies);
750
- return {
751
- accessToken: bundle.accessToken,
752
- expiresAt: bundle.accessExpiresAt,
753
- tokenType: bundle.tokenType,
754
- payload: (_d = (_c = bundle.sessionPayload) !== null && _c !== void 0 ? _c : parseJWTPayload(bundle.accessToken)) !== null && _d !== void 0 ? _d : undefined,
755
- };
768
+ try {
769
+ const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
770
+ if (bundle) {
771
+ logger.debug('[TokenKit] Proactive refresh successful', !!this.config.debug);
772
+ // Ensure tokens are stored in the current context (in case of shared flight)
773
+ storeTokens(ctx, bundle, this.config.cookies);
774
+ return {
775
+ accessToken: bundle.accessToken,
776
+ expiresAt: bundle.accessExpiresAt,
777
+ tokenType: bundle.tokenType,
778
+ payload: (_d = (_c = bundle.sessionPayload) !== null && _c !== void 0 ? _c : parseJWTPayload(bundle.accessToken)) !== null && _d !== void 0 ? _d : undefined,
779
+ };
780
+ }
756
781
  }
757
- // Refresh failed, check if tokens still exist
782
+ catch (error) {
783
+ logger.debug(`[TokenKit] Proactive refresh failed: ${error.message}. Continuing with current token.`, !!this.config.debug);
784
+ }
785
+ // Refresh failed or returned no bundle, check if tokens still exist
758
786
  const currentTokens = retrieveTokens(ctx, this.config.cookies);
759
787
  if (!currentTokens.accessToken) {
760
788
  return null;
@@ -796,7 +824,7 @@ class TokenManager {
796
824
  }
797
825
  catch (error) {
798
826
  // Ignore logout endpoint errors
799
- logger.debug('[TokenKit] Logout endpoint failed:', error);
827
+ logger.debug('[TokenKit] Logout endpoint failed:', !!this.config.debug, error);
800
828
  }
801
829
  finally {
802
830
  clearTimeout(timeoutId);
@@ -941,7 +969,7 @@ function createMiddleware() {
941
969
  else if (config.context) {
942
970
  contextStrategy = 'custom (external AsyncLocalStorage)';
943
971
  }
944
- logger.debug(`[TokenKit] Middleware initialized (auth: ${authStatus}, context: ${contextStrategy})`);
972
+ logger.debug(`[TokenKit] Middleware initialized (auth: ${authStatus}, context: ${contextStrategy})`, !!config.debug);
945
973
  globalStorage[LOGGED_KEY] = true;
946
974
  }
947
975
  const runLogic = () => __awaiter(this, void 0, void 0, function* () {
@@ -953,7 +981,7 @@ function createMiddleware() {
953
981
  }
954
982
  catch (error) {
955
983
  // Log only the message to avoid leaking sensitive data in the error object
956
- logger.debug('[TokenKit] Automatic token rotation failed:', error.message || error);
984
+ logger.debug('[TokenKit] Automatic token rotation failed:', !!config.debug, error.message || error);
957
985
  }
958
986
  }
959
987
  return next();
@@ -998,7 +1026,7 @@ class APIClient {
998
1026
  * Get token manager
999
1027
  */
1000
1028
  get tokenManager() {
1001
- var _a, _b;
1029
+ var _a, _b, _c;
1002
1030
  const config = this.config;
1003
1031
  if (!config.auth)
1004
1032
  return undefined;
@@ -1014,8 +1042,8 @@ class APIClient {
1014
1042
  if (!this._localTokenManager ||
1015
1043
  this._lastUsedAuth !== config.auth ||
1016
1044
  this._lastUsedBaseURL !== config.baseURL) {
1017
- // Merge client-level fetch and SSL settings into auth config
1018
- const authConfig = Object.assign(Object.assign({}, config.auth), { fetch: (_a = config.auth.fetch) !== null && _a !== void 0 ? _a : config.fetch, dangerouslyIgnoreCertificateErrors: (_b = config.auth.dangerouslyIgnoreCertificateErrors) !== null && _b !== void 0 ? _b : config.dangerouslyIgnoreCertificateErrors });
1045
+ // Merge client-level fetch, SSL and debug settings into auth config
1046
+ const authConfig = Object.assign(Object.assign({}, config.auth), { fetch: (_a = config.auth.fetch) !== null && _a !== void 0 ? _a : config.fetch, dangerouslyIgnoreCertificateErrors: (_b = config.auth.dangerouslyIgnoreCertificateErrors) !== null && _b !== void 0 ? _b : config.dangerouslyIgnoreCertificateErrors, debug: (_c = config.auth.debug) !== null && _c !== void 0 ? _c : config.debug });
1019
1047
  this._localTokenManager = new TokenManager(authConfig, config.baseURL);
1020
1048
  this._lastUsedAuth = config.auth;
1021
1049
  this._lastUsedBaseURL = config.baseURL;
@@ -1103,8 +1131,11 @@ class APIClient {
1103
1131
  executeRequest(config, ctx, attempt) {
1104
1132
  return __awaiter(this, void 0, void 0, function* () {
1105
1133
  var _a, _b, _c, _d, _e;
1134
+ const method = config.method.toUpperCase();
1135
+ const debug = this.config.debug;
1106
1136
  // Ensure valid session (if auth is enabled)
1107
1137
  if (this.tokenManager && !config.skipAuth) {
1138
+ logger.debug(`[TokenKit] Ensuring valid session for ${method} ${config.url}`, !!debug);
1108
1139
  yield this.tokenManager.ensure(ctx, config.auth, config.headers);
1109
1140
  }
1110
1141
  // Build full URL
@@ -1113,13 +1144,18 @@ class APIClient {
1113
1144
  const headers = this.buildHeaders(config, ctx, fullURL);
1114
1145
  // Build request init
1115
1146
  const init = {
1116
- method: config.method,
1147
+ method,
1117
1148
  headers,
1118
1149
  signal: config.signal,
1119
1150
  };
1120
- // Add body for non-GET requests
1121
- if (config.data && config.method !== 'GET') {
1151
+ // Add body for appropriate methods
1152
+ const methodsWithNoBody = ['GET', 'HEAD', 'DELETE'];
1153
+ if (config.data && !methodsWithNoBody.includes(method)) {
1122
1154
  init.body = JSON.stringify(config.data);
1155
+ // Add Content-Type if not already present
1156
+ if (!headers['Content-Type'] && !headers['content-type']) {
1157
+ headers['Content-Type'] = 'application/json';
1158
+ }
1123
1159
  }
1124
1160
  // Apply request interceptors
1125
1161
  let requestConfig = Object.assign({}, config);
@@ -1137,12 +1173,15 @@ class APIClient {
1137
1173
  clearTimeout(timeoutId);
1138
1174
  // Handle 401 (try refresh and retry once)
1139
1175
  if (response.status === 401 && this.tokenManager && !config.skipAuth && attempt === 1) {
1176
+ logger.debug('[TokenKit] Received 401, attempting force refresh and retry...', !!debug);
1140
1177
  // Clear and try fresh session (force refresh)
1141
1178
  const session = yield this.tokenManager.ensure(ctx, config.auth, config.headers, true);
1142
1179
  if (session) {
1180
+ logger.debug('[TokenKit] Force refresh successful, retrying request...', !!debug);
1143
1181
  // Retry with new token
1144
1182
  return this.executeRequest(config, ctx, attempt + 1);
1145
1183
  }
1184
+ logger.debug('[TokenKit] Force refresh failed or returned no session', !!debug);
1146
1185
  }
1147
1186
  // Parse response
1148
1187
  const apiResponse = yield this.parseResponse(response, fullURL);
@@ -1231,7 +1270,7 @@ class APIClient {
1231
1270
  */
1232
1271
  buildHeaders(config, ctx, targetURL) {
1233
1272
  var _a, _b;
1234
- const headers = Object.assign(Object.assign({ 'Content-Type': 'application/json' }, this.config.headers), config.headers);
1273
+ const headers = Object.assign(Object.assign({}, this.config.headers), config.headers);
1235
1274
  // Add auth token if available (only for safe URLs)
1236
1275
  if (this.tokenManager && !config.skipAuth && this.isSafeURL(targetURL)) {
1237
1276
  const session = this.tokenManager.getSession(ctx);
@@ -1380,7 +1419,7 @@ function tokenKit(config) {
1380
1419
  }
1381
1420
  // Always inject the client-side script for idle monitoring
1382
1421
  injectScript('page', `import 'astro-tokenkit/client-init';`);
1383
- logger.debug('[TokenKit] Integration initialized');
1422
+ logger.debug('[TokenKit] Integration initialized', !!config.debug);
1384
1423
  },
1385
1424
  },
1386
1425
  };