astro-tokenkit 1.0.9 → 1.0.11

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
@@ -125,6 +125,11 @@ const specializedClient = createClient({
125
125
  | `login` | `string` | Endpoint path for login (POST). |
126
126
  | `refresh` | `string` | Endpoint path for token refresh (POST). |
127
127
  | `logout` | `string` | Endpoint path for logout (POST). |
128
+ | `contentType` | `'application/json' \| 'application/x-www-form-urlencoded'` | Content type for auth requests (default: `application/json`). |
129
+ | `headers` | `Record<string, string>` | Extra headers for login/refresh requests. |
130
+ | `loginData` | `Record<string, any>` | Extra data to be sent with login request. |
131
+ | `refreshData` | `Record<string, any>` | Extra data to be sent with refresh request. |
132
+ | `refreshRequestField` | `string` | Field name for the refresh token in the refresh request (default: `refreshToken`). |
128
133
  | `fields` | `FieldMapping` | Custom mapping for token fields in API responses. |
129
134
  | `parseLogin` | `Function` | Custom parser for login response: `(body: any) => TokenBundle`. |
130
135
  | `parseRefresh`| `Function` | Custom parser for refresh response: `(body: any) => TokenBundle`. |
@@ -136,17 +141,33 @@ const specializedClient = createClient({
136
141
 
137
142
  | Property | Type | Description |
138
143
  | :--- | :--- | :--- |
139
- | `ctx` | `TokenKitContext` | Optional Astro context. |
140
144
  | `onLogin` | `Function` | Callback after successful login: `(bundle, body, ctx) => void`. |
145
+ | `headers` | `Record<string, string>` | Extra headers for this specific login request. |
146
+ | `data` | `Record<string, any>` | Extra data for this specific login request. |
147
+
148
+ ### Request Auth Overrides
149
+
150
+ When calling `api.get()`, `api.post()`, etc., you can override auth configuration (e.g., for multi-tenancy). Headers provided in the request options are automatically propagated to any automatic token refresh operations:
151
+
152
+ ```typescript
153
+ await api.get('/data', {
154
+ headers: { 'x-tenant-name': 'lynx' },
155
+ auth: {
156
+ data: { extra_refresh_param: 'value' }
157
+ }
158
+ });
159
+ ```
141
160
 
142
161
  ## Advanced Usage
143
162
 
144
163
  ### Manual Context
145
164
 
146
- If you prefer not to use middleware, you can pass the Astro context explicitly to any request:
165
+ If you prefer not to use middleware, you can bind the Astro context manually for a specific scope:
147
166
 
148
167
  ```typescript
149
- const data = await api.get('/data', { ctx: Astro });
168
+ import { runWithContext } from 'astro-tokenkit';
169
+
170
+ const data = await runWithContext(Astro, () => api.get('/data'));
150
171
  ```
151
172
 
152
173
  ### Interceptors
@@ -1,4 +1,4 @@
1
- import type { TokenBundle, Session, AuthConfig, TokenKitContext, OnLoginCallback } from '../types';
1
+ import type { TokenBundle, Session, AuthConfig, TokenKitContext, AuthOptions, LoginOptions } from '../types';
2
2
  /**
3
3
  * Token Manager handles all token operations
4
4
  */
@@ -10,11 +10,11 @@ export declare class TokenManager {
10
10
  /**
11
11
  * Perform login
12
12
  */
13
- login(ctx: TokenKitContext, credentials: any, onLogin?: OnLoginCallback): Promise<TokenBundle>;
13
+ login(ctx: TokenKitContext, credentials: any, options?: LoginOptions): Promise<TokenBundle>;
14
14
  /**
15
15
  * Perform token refresh
16
16
  */
17
- refresh(ctx: TokenKitContext, refreshToken: string): Promise<TokenBundle | null>;
17
+ refresh(ctx: TokenKitContext, refreshToken: string, options?: AuthOptions, headers?: Record<string, string>): Promise<TokenBundle | null>;
18
18
  /**
19
19
  * Internal refresh implementation
20
20
  */
@@ -22,7 +22,7 @@ export declare class TokenManager {
22
22
  /**
23
23
  * Ensure valid tokens (with automatic refresh)
24
24
  */
25
- ensure(ctx: TokenKitContext): Promise<Session | null>;
25
+ ensure(ctx: TokenKitContext, options?: AuthOptions, headers?: Record<string, string>): Promise<Session | null>;
26
26
  /**
27
27
  * Logout (clear tokens)
28
28
  */
@@ -52,13 +52,23 @@ export class TokenManager {
52
52
  /**
53
53
  * Perform login
54
54
  */
55
- login(ctx, credentials, onLogin) {
55
+ login(ctx, credentials, options) {
56
56
  return __awaiter(this, void 0, void 0, function* () {
57
57
  const url = this.baseURL + this.config.login;
58
+ const contentType = this.config.contentType || 'application/json';
59
+ const headers = Object.assign(Object.assign({ 'Content-Type': contentType }, this.config.headers), options === null || options === void 0 ? void 0 : options.headers);
60
+ const data = Object.assign(Object.assign(Object.assign({}, this.config.loginData), options === null || options === void 0 ? void 0 : options.data), credentials);
61
+ let requestBody;
62
+ if (contentType === 'application/x-www-form-urlencoded') {
63
+ requestBody = new URLSearchParams(data).toString();
64
+ }
65
+ else {
66
+ requestBody = JSON.stringify(data);
67
+ }
58
68
  const response = yield fetch(url, {
59
69
  method: 'POST',
60
- headers: { 'Content-Type': 'application/json' },
61
- body: JSON.stringify(credentials),
70
+ headers,
71
+ body: requestBody,
62
72
  }).catch(error => {
63
73
  throw new AuthError(`Login request failed: ${error.message}`);
64
74
  });
@@ -79,8 +89,8 @@ export class TokenManager {
79
89
  // Store in cookies
80
90
  storeTokens(ctx, bundle, this.config.cookies);
81
91
  // Call onLogin callback if provided
82
- if (onLogin) {
83
- yield onLogin(bundle, body, ctx);
92
+ if (options === null || options === void 0 ? void 0 : options.onLogin) {
93
+ yield options.onLogin(bundle, body, ctx);
84
94
  }
85
95
  return bundle;
86
96
  });
@@ -88,10 +98,10 @@ export class TokenManager {
88
98
  /**
89
99
  * Perform token refresh
90
100
  */
91
- refresh(ctx, refreshToken) {
101
+ refresh(ctx, refreshToken, options, headers) {
92
102
  return __awaiter(this, void 0, void 0, function* () {
93
103
  try {
94
- return yield this.performRefresh(ctx, refreshToken);
104
+ return yield this.performRefresh(ctx, refreshToken, options, headers);
95
105
  }
96
106
  catch (error) {
97
107
  clearTokens(ctx, this.config.cookies);
@@ -102,13 +112,24 @@ export class TokenManager {
102
112
  /**
103
113
  * Internal refresh implementation
104
114
  */
105
- performRefresh(ctx, refreshToken) {
115
+ performRefresh(ctx, refreshToken, options, extraHeaders) {
106
116
  return __awaiter(this, void 0, void 0, function* () {
107
117
  const url = this.baseURL + this.config.refresh;
118
+ const contentType = this.config.contentType || 'application/json';
119
+ const headers = Object.assign(Object.assign({ 'Content-Type': contentType }, this.config.headers), extraHeaders);
120
+ const refreshField = this.config.refreshRequestField || 'refreshToken';
121
+ const data = Object.assign(Object.assign(Object.assign({}, this.config.refreshData), options === null || options === void 0 ? void 0 : options.data), { [refreshField]: refreshToken });
122
+ let requestBody;
123
+ if (contentType === 'application/x-www-form-urlencoded') {
124
+ requestBody = new URLSearchParams(data).toString();
125
+ }
126
+ else {
127
+ requestBody = JSON.stringify(data);
128
+ }
108
129
  const response = yield fetch(url, {
109
130
  method: 'POST',
110
- headers: { 'Content-Type': 'application/json' },
111
- body: JSON.stringify({ refreshToken }),
131
+ headers,
132
+ body: requestBody,
112
133
  }).catch(error => {
113
134
  throw new AuthError(`Refresh request failed: ${error.message}`);
114
135
  });
@@ -143,7 +164,7 @@ export class TokenManager {
143
164
  /**
144
165
  * Ensure valid tokens (with automatic refresh)
145
166
  */
146
- ensure(ctx) {
167
+ ensure(ctx, options, headers) {
147
168
  return __awaiter(this, void 0, void 0, function* () {
148
169
  var _a, _b, _c, _d, _e;
149
170
  const now = Math.floor(Date.now() / 1000);
@@ -155,7 +176,7 @@ export class TokenManager {
155
176
  // Token expired
156
177
  if (isExpired(tokens.expiresAt, now, this.config.policy)) {
157
178
  const flightKey = this.createFlightKey(tokens.refreshToken);
158
- const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken));
179
+ const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
159
180
  if (!bundle)
160
181
  return null;
161
182
  return {
@@ -167,7 +188,7 @@ export class TokenManager {
167
188
  // Proactive refresh
168
189
  if (shouldRefresh(tokens.expiresAt, now, tokens.lastRefreshAt, this.config.policy)) {
169
190
  const flightKey = this.createFlightKey(tokens.refreshToken);
170
- const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken));
191
+ const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
171
192
  if (bundle) {
172
193
  return {
173
194
  accessToken: bundle.accessToken,
@@ -1,4 +1,4 @@
1
- import type { ClientConfig, RequestConfig, RequestOptions, Session, TokenKitContext, TokenKitConfig, LoginOptions } from '../types';
1
+ import type { ClientConfig, RequestConfig, RequestOptions, Session, TokenKitConfig, LoginOptions } from '../types';
2
2
  import { TokenManager } from '../auth/manager';
3
3
  /**
4
4
  * API Client
@@ -65,19 +65,19 @@ export declare class APIClient {
65
65
  /**
66
66
  * Login
67
67
  */
68
- login(credentials: any, options?: LoginOptions | TokenKitContext): Promise<void>;
68
+ login(credentials: any, options?: LoginOptions): Promise<void>;
69
69
  /**
70
70
  * Logout
71
71
  */
72
- logout(ctx?: TokenKitContext): Promise<void>;
72
+ logout(): Promise<void>;
73
73
  /**
74
74
  * Check if authenticated
75
75
  */
76
- isAuthenticated(ctx?: TokenKitContext): boolean;
76
+ isAuthenticated(): boolean;
77
77
  /**
78
78
  * Get current session
79
79
  */
80
- getSession(ctx?: TokenKitContext): Session | null;
80
+ getSession(): Session | null;
81
81
  }
82
82
  /**
83
83
  * Global API client instance.
@@ -113,7 +113,7 @@ export class APIClient {
113
113
  */
114
114
  request(config) {
115
115
  return __awaiter(this, void 0, void 0, function* () {
116
- const ctx = getContextStore(config.ctx);
116
+ const ctx = getContextStore();
117
117
  let attempt = 0;
118
118
  let lastError;
119
119
  while (true) {
@@ -144,7 +144,7 @@ export class APIClient {
144
144
  var _a, _b, _c, _d, _e;
145
145
  // Ensure valid session (if auth is enabled)
146
146
  if (this.tokenManager && !config.skipAuth) {
147
- yield this.tokenManager.ensure(ctx);
147
+ yield this.tokenManager.ensure(ctx, config.auth, config.headers);
148
148
  }
149
149
  // Build full URL
150
150
  const fullURL = this.buildURL(config.url, config.params);
@@ -177,7 +177,7 @@ export class APIClient {
177
177
  // Handle 401 (try refresh and retry once)
178
178
  if (response.status === 401 && this.tokenManager && !config.skipAuth && attempt === 1) {
179
179
  // Clear and try fresh session
180
- const session = yield this.tokenManager.ensure(ctx);
180
+ const session = yield this.tokenManager.ensure(ctx, config.auth, config.headers);
181
181
  if (session) {
182
182
  // Retry with new token
183
183
  return this.executeRequest(config, ctx, attempt + 1);
@@ -288,48 +288,38 @@ export class APIClient {
288
288
  if (!this.tokenManager) {
289
289
  throw new Error('Auth is not configured for this client');
290
290
  }
291
- let ctx;
292
- let onLogin;
293
- if (options && 'cookies' in options) {
294
- ctx = options;
295
- }
296
- else if (options) {
297
- const opt = options;
298
- ctx = opt.ctx;
299
- onLogin = opt.onLogin;
300
- }
301
- const context = getContextStore(ctx);
302
- yield this.tokenManager.login(context, credentials, onLogin);
291
+ const context = getContextStore();
292
+ yield this.tokenManager.login(context, credentials, options);
303
293
  });
304
294
  }
305
295
  /**
306
296
  * Logout
307
297
  */
308
- logout(ctx) {
298
+ logout() {
309
299
  return __awaiter(this, void 0, void 0, function* () {
310
300
  if (!this.tokenManager) {
311
301
  throw new Error('Auth is not configured for this client');
312
302
  }
313
- const context = getContextStore(ctx);
303
+ const context = getContextStore();
314
304
  yield this.tokenManager.logout(context);
315
305
  });
316
306
  }
317
307
  /**
318
308
  * Check if authenticated
319
309
  */
320
- isAuthenticated(ctx) {
310
+ isAuthenticated() {
321
311
  if (!this.tokenManager)
322
312
  return false;
323
- const context = getContextStore(ctx);
313
+ const context = getContextStore();
324
314
  return this.tokenManager.isAuthenticated(context);
325
315
  }
326
316
  /**
327
317
  * Get current session
328
318
  */
329
- getSession(ctx) {
319
+ getSession() {
330
320
  if (!this.tokenManager)
331
321
  return null;
332
- const context = getContextStore(ctx);
322
+ const context = getContextStore();
333
323
  return this.tokenManager.getSession(context);
334
324
  }
335
325
  }
@@ -10,7 +10,7 @@ export declare function setSharedContextStorage(storage: AsyncLocalStorage<any>,
10
10
  /**
11
11
  * Get context from shared storage
12
12
  */
13
- export declare function getContextStore(explicitCtx?: TokenKitContext): TokenKitContext;
13
+ export declare function getContextStore(): TokenKitContext;
14
14
  /**
15
15
  * Bind context (only needed if not using shared storage)
16
16
  */
@@ -33,10 +33,7 @@ export function setSharedContextStorage(storage, key = 'astro') {
33
33
  /**
34
34
  * Get context from shared storage
35
35
  */
36
- export function getContextStore(explicitCtx) {
37
- if (explicitCtx) {
38
- return explicitCtx;
39
- }
36
+ export function getContextStore() {
40
37
  const config = getConfig();
41
38
  const getStore = config.getContextStore;
42
39
  if (getStore) {
@@ -51,10 +48,7 @@ export function getContextStore(explicitCtx) {
51
48
  return ctx;
52
49
  }
53
50
  }
54
- throw new Error('Astro context not found. Either:\n' +
55
- '1. Use api.middleware() to bind context automatically, or\n' +
56
- '2. Pass context explicitly: api.get("/path", { ctx: Astro })\n' +
57
- '3. Configure shared storage: setSharedContextStorage(storage, "key")');
51
+ throw new Error('Astro context not found. Make sure to use api.middleware() to bind context automatically.');
58
52
  }
59
53
  /**
60
54
  * Bind context (only needed if not using shared storage)
@@ -6,8 +6,8 @@ export declare function runWithContext<T>(ctx: TokenKitContext, fn: () => T): T;
6
6
  /**
7
7
  * Get current Astro context (from middleware binding or explicit)
8
8
  */
9
- export declare function getContextStore(explicitCtx?: TokenKitContext): TokenKitContext;
9
+ export declare function getContextStore(): TokenKitContext;
10
10
  /**
11
11
  * Check if context is available
12
12
  */
13
- export declare function hasContext(explicitCtx?: TokenKitContext): boolean;
13
+ export declare function hasContext(): boolean;
@@ -19,30 +19,27 @@ export function runWithContext(ctx, fn) {
19
19
  /**
20
20
  * Get current Astro context (from middleware binding or explicit)
21
21
  */
22
- export function getContextStore(explicitCtx) {
22
+ export function getContextStore() {
23
23
  const config = getConfig();
24
24
  const getStore = config.getContextStore;
25
25
  const context = config.context || als;
26
26
  const store = getStore
27
27
  ? getStore()
28
28
  : context.getStore();
29
- const ctx = explicitCtx || store;
30
- if (!ctx) {
31
- throw new Error('Astro context not found. Either:\n' +
32
- '1. Use api.middleware() to bind context automatically, or\n' +
33
- '2. Pass context explicitly: api.get("/path", { ctx: Astro })');
29
+ if (!store) {
30
+ throw new Error('Astro context not found. Make sure to use api.middleware() to bind context automatically.');
34
31
  }
35
- return ctx;
32
+ return store;
36
33
  }
37
34
  /**
38
35
  * Check if context is available
39
36
  */
40
- export function hasContext(explicitCtx) {
37
+ export function hasContext() {
41
38
  const config = getConfig();
42
39
  const getStore = config.getContextStore;
43
40
  const context = config.context || als;
44
41
  const store = getStore
45
42
  ? getStore()
46
43
  : context.getStore();
47
- return !!(explicitCtx || store);
44
+ return !!store;
48
45
  }
package/dist/config.js CHANGED
@@ -3,6 +3,7 @@ import { TokenManager } from "./auth/manager";
3
3
  let config = {
4
4
  runWithContext: undefined,
5
5
  getContextStore: undefined,
6
+ setContextStore: undefined,
6
7
  baseURL: "",
7
8
  };
8
9
  let tokenManager;
@@ -10,8 +11,13 @@ let tokenManager;
10
11
  * Set configuration
11
12
  */
12
13
  export function setConfig(userConfig) {
13
- // Store validated config
14
- config = Object.assign(Object.assign({}, config), userConfig);
14
+ const finalConfig = Object.assign(Object.assign({}, config), userConfig);
15
+ // Validate that getter and setter are defined together
16
+ if ((finalConfig.getContextStore && !finalConfig.setContextStore) ||
17
+ (!finalConfig.getContextStore && finalConfig.setContextStore)) {
18
+ throw new Error("[TokenKit] getContextStore and setContextStore must be defined together.");
19
+ }
20
+ config = finalConfig;
15
21
  // Re-initialize global token manager if auth changed
16
22
  if (config.auth) {
17
23
  tokenManager = new TokenManager(config.auth, config.baseURL);