astro-tokenkit 1.0.10 → 1.0.12

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
@@ -61,8 +61,8 @@ Now you can use the `api` client anywhere in your Astro pages or components with
61
61
  // src/pages/profile.astro
62
62
  import { api } from 'astro-tokenkit';
63
63
 
64
- // No need to pass context, it's handled by middleware!
65
- const user = await api.get('/me');
64
+ // Request methods return an APIResponse object
65
+ const { data: user } = await api.get('/me');
66
66
  ---
67
67
 
68
68
  <h1>Welcome, {user.name}</h1>
@@ -125,10 +125,15 @@ 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
- | `fields` | `FieldMapping` | Custom mapping for token fields in API responses. |
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`). |
133
+ | `fields` | `FieldMapping` | Custom mapping for token fields in API responses (`accessToken`, `refreshToken`, `expiresAt`, `expiresIn`, `tokenType`, `sessionPayload`). |
129
134
  | `parseLogin` | `Function` | Custom parser for login response: `(body: any) => TokenBundle`. |
130
135
  | `parseRefresh`| `Function` | Custom parser for refresh response: `(body: any) => TokenBundle`. |
131
- | `injectToken` | `Function` | Custom token injection: `(token: string) => string` (default: Bearer). |
136
+ | `injectToken` | `Function` | Custom token injection: `(token: string, type?: string) => string` (default: Bearer). |
132
137
  | `cookies` | `CookieConfig` | Configuration for auth cookies. |
133
138
  | `policy` | `RefreshPolicy` | Strategy for when to trigger token refresh. |
134
139
 
@@ -136,17 +141,34 @@ 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
+ | `onError` | `Function` | Callback after failed login: `(error, ctx) => void`. |
146
+ | `headers` | `Record<string, string>` | Extra headers for this specific login request. |
147
+ | `data` | `Record<string, any>` | Extra data for this specific login request. |
148
+
149
+ ### Request Auth Overrides
150
+
151
+ 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:
152
+
153
+ ```typescript
154
+ await api.get('/data', {
155
+ headers: { 'x-tenant-name': 'lynx' },
156
+ auth: {
157
+ data: { extra_refresh_param: 'value' }
158
+ }
159
+ });
160
+ ```
141
161
 
142
162
  ## Advanced Usage
143
163
 
144
164
  ### Manual Context
145
165
 
146
- If you prefer not to use middleware, you can pass the Astro context explicitly to any request:
166
+ If you prefer not to use middleware, you can bind the Astro context manually for a specific scope:
147
167
 
148
168
  ```typescript
149
- const data = await api.get('/data', { ctx: Astro });
169
+ import { runWithContext } from 'astro-tokenkit';
170
+
171
+ const { data } = await runWithContext(Astro, () => api.get('/data'));
150
172
  ```
151
173
 
152
174
  ### Interceptors
@@ -169,16 +191,56 @@ const api = createClient({
169
191
 
170
192
  ```typescript
171
193
  // In an API route or server-side component
172
- await api.login({ username, password }, {
194
+ const { data: bundle } = await api.login({ username, password }, {
173
195
  onLogin: (bundle, body, ctx) => {
174
196
  // Post-login logic (e.g., sync session to another store)
175
197
  console.log('User logged in!', bundle.sessionPayload);
198
+ },
199
+ onError: (error, ctx) => {
200
+ // Handle error (e.g., log it or perform cleanup)
201
+ console.error('Login failed:', error.message);
176
202
  }
177
203
  });
178
204
 
179
205
  await api.logout();
180
206
  ```
181
207
 
208
+ ### Using Promises (.then, .catch, .finally)
209
+
210
+ All API methods return a Promise that resolves to an `APIResponse` object. You can use traditional promise chaining:
211
+
212
+ ```typescript
213
+ // Example with GET request
214
+ api.get('/me')
215
+ .then(({ data: user, status }) => {
216
+ console.log(`User ${user.name} fetched with status ${status}`);
217
+ })
218
+ .catch(err => {
219
+ console.error('Failed to fetch user:', err.message);
220
+ })
221
+ .finally(() => {
222
+ console.log('Request finished');
223
+ });
224
+
225
+ // Example with login
226
+ api.login(credentials)
227
+ .then(({ data: token }) => {
228
+ console.log('Successfully logged in!', token.accessToken);
229
+ })
230
+ .catch(err => {
231
+ if (err instanceof AuthError) {
232
+ console.error('Authentication failed:', err.message);
233
+ } else {
234
+ console.error('An unexpected error occurred:', err.message);
235
+ }
236
+ })
237
+ .finally(() => {
238
+ // E.g. stop loading state
239
+ });
240
+ ```
241
+
242
+ > **Note:** Since all methods return an `APIResponse` object, you can use destructuring in `.then()` to access the data directly, which allows for clean syntax like `.then(({ data: token }) => ... )`.
243
+
182
244
  ## License
183
245
 
184
246
  MIT © [oamm](https://github.com/oamm)
@@ -35,6 +35,13 @@ const EXPIRES_IN_FIELDS = [
35
35
  'expiresIn',
36
36
  'ttl',
37
37
  ];
38
+ /**
39
+ * Common field names for token type
40
+ */
41
+ const TOKEN_TYPE_FIELDS = [
42
+ 'token_type',
43
+ 'tokenType',
44
+ ];
38
45
  /**
39
46
  * Common field names for session payload
40
47
  */
@@ -97,10 +104,13 @@ export function autoDetectFields(body, fieldMapping) {
97
104
  }
98
105
  // Detect session payload (optional)
99
106
  const sessionPayload = findField(SESSION_PAYLOAD_FIELDS, fieldMapping === null || fieldMapping === void 0 ? void 0 : fieldMapping.sessionPayload);
107
+ // Detect token type (optional)
108
+ const tokenType = findField(TOKEN_TYPE_FIELDS, fieldMapping === null || fieldMapping === void 0 ? void 0 : fieldMapping.tokenType);
100
109
  return {
101
110
  accessToken,
102
111
  refreshToken,
103
112
  accessExpiresAt,
113
+ tokenType: tokenType || undefined,
104
114
  sessionPayload: sessionPayload || undefined,
105
115
  };
106
116
  }
@@ -114,6 +124,10 @@ export function parseJWTPayload(token) {
114
124
  return null;
115
125
  }
116
126
  const payload = parts[1];
127
+ // Better UTF-8 support for environments with Buffer (like Node.js/Astro)
128
+ if (typeof Buffer !== 'undefined') {
129
+ return JSON.parse(Buffer.from(payload, 'base64').toString('utf8'));
130
+ }
117
131
  const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
118
132
  return JSON.parse(decoded);
119
133
  }
@@ -1,4 +1,5 @@
1
- import type { TokenBundle, Session, AuthConfig, TokenKitContext, OnLoginCallback } from '../types';
1
+ import { APIResponse } from '../types';
2
+ import type { TokenBundle, Session, AuthConfig, TokenKitContext, AuthOptions, LoginOptions } from '../types';
2
3
  /**
3
4
  * Token Manager handles all token operations
4
5
  */
@@ -10,11 +11,11 @@ export declare class TokenManager {
10
11
  /**
11
12
  * Perform login
12
13
  */
13
- login(ctx: TokenKitContext, credentials: any, onLogin?: OnLoginCallback): Promise<TokenBundle>;
14
+ login(ctx: TokenKitContext, credentials: any, options?: LoginOptions): Promise<APIResponse<TokenBundle>>;
14
15
  /**
15
16
  * Perform token refresh
16
17
  */
17
- refresh(ctx: TokenKitContext, refreshToken: string): Promise<TokenBundle | null>;
18
+ refresh(ctx: TokenKitContext, refreshToken: string, options?: AuthOptions, headers?: Record<string, string>): Promise<TokenBundle | null>;
18
19
  /**
19
20
  * Internal refresh implementation
20
21
  */
@@ -22,7 +23,7 @@ export declare class TokenManager {
22
23
  /**
23
24
  * Ensure valid tokens (with automatic refresh)
24
25
  */
25
- ensure(ctx: TokenKitContext): Promise<Session | null>;
26
+ ensure(ctx: TokenKitContext, options?: AuthOptions, headers?: Record<string, string>): Promise<Session | null>;
26
27
  /**
27
28
  * Logout (clear tokens)
28
29
  */
@@ -39,4 +40,8 @@ export declare class TokenManager {
39
40
  * Create flight key for single-flight deduplication
40
41
  */
41
42
  private createFlightKey;
43
+ /**
44
+ * Join base URL and path safely
45
+ */
46
+ private joinURL;
42
47
  }
@@ -52,18 +52,38 @@ 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
- const url = this.baseURL + this.config.login;
58
- const response = yield fetch(url, {
59
- method: 'POST',
60
- headers: { 'Content-Type': 'application/json' },
61
- body: JSON.stringify(credentials),
62
- }).catch(error => {
63
- throw new AuthError(`Login request failed: ${error.message}`);
64
- });
57
+ const url = this.joinURL(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
+ }
68
+ let response;
69
+ try {
70
+ response = yield fetch(url, {
71
+ method: 'POST',
72
+ headers,
73
+ body: requestBody,
74
+ });
75
+ }
76
+ catch (error) {
77
+ const authError = new AuthError(`Login request failed: ${error.message}`);
78
+ if (options === null || options === void 0 ? void 0 : options.onError)
79
+ yield options.onError(authError, ctx);
80
+ throw authError;
81
+ }
65
82
  if (!response.ok) {
66
- throw new AuthError(`Login failed: ${response.status} ${response.statusText}`, response.status, response);
83
+ const authError = new AuthError(`Login failed: ${response.status} ${response.statusText}`, response.status, response);
84
+ if (options === null || options === void 0 ? void 0 : options.onError)
85
+ yield options.onError(authError, ctx);
86
+ throw authError;
67
87
  }
68
88
  const body = yield response.json().catch(() => ({}));
69
89
  // Parse response
@@ -74,24 +94,34 @@ export class TokenManager {
74
94
  : autoDetectFields(body, this.config.fields);
75
95
  }
76
96
  catch (error) {
77
- throw new AuthError(`Invalid login response: ${error.message}`, response.status, response);
97
+ const authError = new AuthError(`Invalid login response: ${error.message}`, response.status, response);
98
+ if (options === null || options === void 0 ? void 0 : options.onError)
99
+ yield options.onError(authError, ctx);
100
+ throw authError;
78
101
  }
79
102
  // Store in cookies
80
103
  storeTokens(ctx, bundle, this.config.cookies);
81
104
  // Call onLogin callback if provided
82
- if (onLogin) {
83
- yield onLogin(bundle, body, ctx);
105
+ if (options === null || options === void 0 ? void 0 : options.onLogin) {
106
+ yield options.onLogin(bundle, body, ctx);
84
107
  }
85
- return bundle;
108
+ return {
109
+ data: bundle,
110
+ status: response.status,
111
+ statusText: response.statusText,
112
+ headers: response.headers,
113
+ url: response.url,
114
+ ok: response.ok,
115
+ };
86
116
  });
87
117
  }
88
118
  /**
89
119
  * Perform token refresh
90
120
  */
91
- refresh(ctx, refreshToken) {
121
+ refresh(ctx, refreshToken, options, headers) {
92
122
  return __awaiter(this, void 0, void 0, function* () {
93
123
  try {
94
- return yield this.performRefresh(ctx, refreshToken);
124
+ return yield this.performRefresh(ctx, refreshToken, options, headers);
95
125
  }
96
126
  catch (error) {
97
127
  clearTokens(ctx, this.config.cookies);
@@ -102,16 +132,31 @@ export class TokenManager {
102
132
  /**
103
133
  * Internal refresh implementation
104
134
  */
105
- performRefresh(ctx, refreshToken) {
135
+ performRefresh(ctx, refreshToken, options, extraHeaders) {
106
136
  return __awaiter(this, void 0, void 0, function* () {
107
- const url = this.baseURL + this.config.refresh;
108
- const response = yield fetch(url, {
109
- method: 'POST',
110
- headers: { 'Content-Type': 'application/json' },
111
- body: JSON.stringify({ refreshToken }),
112
- }).catch(error => {
137
+ const url = this.joinURL(this.baseURL, this.config.refresh);
138
+ const contentType = this.config.contentType || 'application/json';
139
+ const headers = Object.assign(Object.assign({ 'Content-Type': contentType }, this.config.headers), extraHeaders);
140
+ const refreshField = this.config.refreshRequestField || 'refreshToken';
141
+ const data = Object.assign(Object.assign(Object.assign({}, this.config.refreshData), options === null || options === void 0 ? void 0 : options.data), { [refreshField]: refreshToken });
142
+ let requestBody;
143
+ if (contentType === 'application/x-www-form-urlencoded') {
144
+ requestBody = new URLSearchParams(data).toString();
145
+ }
146
+ else {
147
+ requestBody = JSON.stringify(data);
148
+ }
149
+ let response;
150
+ try {
151
+ response = yield fetch(url, {
152
+ method: 'POST',
153
+ headers,
154
+ body: requestBody,
155
+ });
156
+ }
157
+ catch (error) {
113
158
  throw new AuthError(`Refresh request failed: ${error.message}`);
114
- });
159
+ }
115
160
  if (!response.ok) {
116
161
  // 401/403 = invalid refresh token
117
162
  if (response.status === 401 || response.status === 403) {
@@ -143,9 +188,9 @@ export class TokenManager {
143
188
  /**
144
189
  * Ensure valid tokens (with automatic refresh)
145
190
  */
146
- ensure(ctx) {
191
+ ensure(ctx, options, headers) {
147
192
  return __awaiter(this, void 0, void 0, function* () {
148
- var _a, _b, _c, _d, _e;
193
+ var _a, _b, _c, _d, _e, _f;
149
194
  const now = Math.floor(Date.now() / 1000);
150
195
  const tokens = retrieveTokens(ctx, this.config.cookies);
151
196
  // No tokens
@@ -155,23 +200,25 @@ export class TokenManager {
155
200
  // Token expired
156
201
  if (isExpired(tokens.expiresAt, now, this.config.policy)) {
157
202
  const flightKey = this.createFlightKey(tokens.refreshToken);
158
- const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken));
203
+ const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
159
204
  if (!bundle)
160
205
  return null;
161
206
  return {
162
207
  accessToken: bundle.accessToken,
163
208
  expiresAt: bundle.accessExpiresAt,
209
+ tokenType: bundle.tokenType,
164
210
  payload: (_b = (_a = bundle.sessionPayload) !== null && _a !== void 0 ? _a : parseJWTPayload(bundle.accessToken)) !== null && _b !== void 0 ? _b : undefined,
165
211
  };
166
212
  }
167
213
  // Proactive refresh
168
214
  if (shouldRefresh(tokens.expiresAt, now, tokens.lastRefreshAt, this.config.policy)) {
169
215
  const flightKey = this.createFlightKey(tokens.refreshToken);
170
- const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken));
216
+ const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
171
217
  if (bundle) {
172
218
  return {
173
219
  accessToken: bundle.accessToken,
174
220
  expiresAt: bundle.accessExpiresAt,
221
+ tokenType: bundle.tokenType,
175
222
  payload: (_d = (_c = bundle.sessionPayload) !== null && _c !== void 0 ? _c : parseJWTPayload(bundle.accessToken)) !== null && _d !== void 0 ? _d : undefined,
176
223
  };
177
224
  }
@@ -185,7 +232,8 @@ export class TokenManager {
185
232
  return {
186
233
  accessToken: tokens.accessToken,
187
234
  expiresAt: tokens.expiresAt,
188
- payload: (_e = parseJWTPayload(tokens.accessToken)) !== null && _e !== void 0 ? _e : undefined,
235
+ tokenType: (_e = tokens.tokenType) !== null && _e !== void 0 ? _e : undefined,
236
+ payload: (_f = parseJWTPayload(tokens.accessToken)) !== null && _f !== void 0 ? _f : undefined,
189
237
  };
190
238
  });
191
239
  }
@@ -194,15 +242,22 @@ export class TokenManager {
194
242
  */
195
243
  logout(ctx) {
196
244
  return __awaiter(this, void 0, void 0, function* () {
245
+ var _a;
197
246
  // Optionally call logout endpoint
198
247
  if (this.config.logout) {
199
248
  try {
200
- const url = this.baseURL + this.config.logout;
201
- yield fetch(url, { method: 'POST' });
249
+ const url = this.joinURL(this.baseURL, this.config.logout);
250
+ const session = this.getSession(ctx);
251
+ const headers = {};
252
+ if (session === null || session === void 0 ? void 0 : session.accessToken) {
253
+ const injectFn = (_a = this.config.injectToken) !== null && _a !== void 0 ? _a : ((token, type) => `${type !== null && type !== void 0 ? type : 'Bearer'} ${token}`);
254
+ headers['Authorization'] = injectFn(session.accessToken, session.tokenType);
255
+ }
256
+ yield fetch(url, { method: 'POST', headers });
202
257
  }
203
258
  catch (error) {
204
259
  // Ignore logout endpoint errors
205
- console.warn('Logout endpoint failed:', error);
260
+ console.warn('[TokenKit] Logout endpoint failed:', error);
206
261
  }
207
262
  }
208
263
  clearTokens(ctx, this.config.cookies);
@@ -212,7 +267,7 @@ export class TokenManager {
212
267
  * Get current session (no refresh)
213
268
  */
214
269
  getSession(ctx) {
215
- var _a;
270
+ var _a, _b;
216
271
  const tokens = retrieveTokens(ctx, this.config.cookies);
217
272
  if (!tokens.accessToken || !tokens.expiresAt) {
218
273
  return null;
@@ -220,7 +275,8 @@ export class TokenManager {
220
275
  return {
221
276
  accessToken: tokens.accessToken,
222
277
  expiresAt: tokens.expiresAt,
223
- payload: (_a = parseJWTPayload(tokens.accessToken)) !== null && _a !== void 0 ? _a : undefined,
278
+ tokenType: (_a = tokens.tokenType) !== null && _a !== void 0 ? _a : undefined,
279
+ payload: (_b = parseJWTPayload(tokens.accessToken)) !== null && _b !== void 0 ? _b : undefined,
224
280
  };
225
281
  }
226
282
  /**
@@ -234,12 +290,15 @@ export class TokenManager {
234
290
  * Create flight key for single-flight deduplication
235
291
  */
236
292
  createFlightKey(token) {
237
- let hash = 0;
238
- for (let i = 0; i < token.length; i++) {
239
- const char = token.charCodeAt(i);
240
- hash = ((hash << 5) - hash) + char;
241
- hash = hash & hash;
242
- }
243
- return `flight_${Math.abs(hash).toString(36)}`;
293
+ // Avoid weak hashing of sensitive tokens
294
+ return `refresh_${token}`;
295
+ }
296
+ /**
297
+ * Join base URL and path safely
298
+ */
299
+ joinURL(base, path) {
300
+ const b = base.endsWith('/') ? base : base + '/';
301
+ const p = path.startsWith('/') ? path.slice(1) : path;
302
+ return b + p;
244
303
  }
245
304
  }
@@ -62,5 +62,6 @@ export function isExpired(expiresAt, now, policy = {}) {
62
62
  const clockSkew = typeof normalized.clockSkew === 'number'
63
63
  ? normalized.clockSkew
64
64
  : parseTime(normalized.clockSkew);
65
- return now > expiresAt + clockSkew;
65
+ // Pessimistic: consider it expired if current time + skew is past expiration
66
+ return now + clockSkew > expiresAt;
66
67
  }
@@ -7,6 +7,7 @@ export interface CookieNames {
7
7
  refreshToken: string;
8
8
  expiresAt: string;
9
9
  lastRefreshAt: string;
10
+ tokenType: string;
10
11
  }
11
12
  /**
12
13
  * Get cookie names with optional prefix
@@ -33,6 +34,7 @@ export declare function retrieveTokens(ctx: TokenKitContext, cookieConfig?: Cook
33
34
  refreshToken: string | null;
34
35
  expiresAt: number | null;
35
36
  lastRefreshAt: number | null;
37
+ tokenType: string | null;
36
38
  };
37
39
  /**
38
40
  * Clear all auth cookies
@@ -9,6 +9,7 @@ export function getCookieNames(prefix) {
9
9
  refreshToken: `${p}refresh_token`,
10
10
  expiresAt: `${p}access_expires_at`,
11
11
  lastRefreshAt: `${p}last_refresh_at`,
12
+ tokenType: `${p}token_type`,
12
13
  };
13
14
  }
14
15
  /**
@@ -44,20 +45,25 @@ export function storeTokens(ctx, bundle, cookieConfig = {}) {
44
45
  ctx.cookies.set(names.expiresAt, bundle.accessExpiresAt.toString(), Object.assign(Object.assign({}, options), { maxAge: accessMaxAge, path: '/' }));
45
46
  // Set last refresh timestamp
46
47
  ctx.cookies.set(names.lastRefreshAt, now.toString(), Object.assign(Object.assign({}, options), { maxAge: accessMaxAge, path: '/' }));
48
+ // Set token type if available
49
+ if (bundle.tokenType) {
50
+ ctx.cookies.set(names.tokenType, bundle.tokenType, Object.assign(Object.assign({}, options), { maxAge: accessMaxAge, path: '/' }));
51
+ }
47
52
  }
48
53
  /**
49
54
  * Retrieve tokens from cookies
50
55
  */
51
56
  export function retrieveTokens(ctx, cookieConfig = {}) {
52
- var _a, _b, _c, _d;
57
+ var _a, _b, _c, _d, _e;
53
58
  const names = getCookieNames(cookieConfig.prefix);
54
59
  const accessToken = ((_a = ctx.cookies.get(names.accessToken)) === null || _a === void 0 ? void 0 : _a.value) || null;
55
60
  const refreshToken = ((_b = ctx.cookies.get(names.refreshToken)) === null || _b === void 0 ? void 0 : _b.value) || null;
56
- const expiresAtStr = (_c = ctx.cookies.get(names.expiresAt)) === null || _c === void 0 ? void 0 : _c.value;
61
+ const tokenType = ((_c = ctx.cookies.get(names.tokenType)) === null || _c === void 0 ? void 0 : _c.value) || null;
62
+ const expiresAtStr = (_d = ctx.cookies.get(names.expiresAt)) === null || _d === void 0 ? void 0 : _d.value;
57
63
  const expiresAt = expiresAtStr ? parseInt(expiresAtStr, 10) : null;
58
- const lastRefreshAtStr = (_d = ctx.cookies.get(names.lastRefreshAt)) === null || _d === void 0 ? void 0 : _d.value;
64
+ const lastRefreshAtStr = (_e = ctx.cookies.get(names.lastRefreshAt)) === null || _e === void 0 ? void 0 : _e.value;
59
65
  const lastRefreshAt = lastRefreshAtStr ? parseInt(lastRefreshAtStr, 10) : null;
60
- return { accessToken, refreshToken, expiresAt, lastRefreshAt };
66
+ return { accessToken, refreshToken, expiresAt, lastRefreshAt, tokenType };
61
67
  }
62
68
  /**
63
69
  * Clear all auth cookies
@@ -69,4 +75,5 @@ export function clearTokens(ctx, cookieConfig = {}) {
69
75
  ctx.cookies.delete(names.refreshToken, Object.assign(Object.assign({}, options), { path: '/' }));
70
76
  ctx.cookies.delete(names.expiresAt, Object.assign(Object.assign({}, options), { path: '/' }));
71
77
  ctx.cookies.delete(names.lastRefreshAt, Object.assign(Object.assign({}, options), { path: '/' }));
78
+ ctx.cookies.delete(names.tokenType, Object.assign(Object.assign({}, options), { path: '/' }));
72
79
  }
@@ -1,4 +1,4 @@
1
- import type { ClientConfig, RequestConfig, RequestOptions, Session, TokenKitContext, TokenKitConfig, LoginOptions } from '../types';
1
+ import type { APIResponse, ClientConfig, RequestConfig, RequestOptions, Session, TokenKitConfig, LoginOptions, TokenBundle } from '../types';
2
2
  import { TokenManager } from '../auth/manager';
3
3
  /**
4
4
  * API Client
@@ -25,27 +25,27 @@ export declare class APIClient {
25
25
  /**
26
26
  * GET request
27
27
  */
28
- get<T = any>(url: string, options?: RequestOptions): Promise<T>;
28
+ get<T = any>(url: string, options?: RequestOptions): Promise<APIResponse<T>>;
29
29
  /**
30
30
  * POST request
31
31
  */
32
- post<T = any>(url: string, data?: any, options?: RequestOptions): Promise<T>;
32
+ post<T = any>(url: string, data?: any, options?: RequestOptions): Promise<APIResponse<T>>;
33
33
  /**
34
34
  * PUT request
35
35
  */
36
- put<T = any>(url: string, data?: any, options?: RequestOptions): Promise<T>;
36
+ put<T = any>(url: string, data?: any, options?: RequestOptions): Promise<APIResponse<T>>;
37
37
  /**
38
38
  * PATCH request
39
39
  */
40
- patch<T = any>(url: string, data?: any, options?: RequestOptions): Promise<T>;
40
+ patch<T = any>(url: string, data?: any, options?: RequestOptions): Promise<APIResponse<T>>;
41
41
  /**
42
42
  * DELETE request
43
43
  */
44
- delete<T = any>(url: string, options?: RequestOptions): Promise<T>;
44
+ delete<T = any>(url: string, options?: RequestOptions): Promise<APIResponse<T>>;
45
45
  /**
46
46
  * Generic request method
47
47
  */
48
- request<T = any>(config: RequestConfig): Promise<T>;
48
+ request<T = any>(config: RequestConfig): Promise<APIResponse<T>>;
49
49
  /**
50
50
  * Execute single request
51
51
  */
@@ -62,22 +62,26 @@ export declare class APIClient {
62
62
  * Build request headers
63
63
  */
64
64
  private buildHeaders;
65
+ /**
66
+ * Check if a URL is safe for token injection (same origin as baseURL)
67
+ */
68
+ private isSafeURL;
65
69
  /**
66
70
  * Login
67
71
  */
68
- login(credentials: any, options?: LoginOptions | TokenKitContext): Promise<void>;
72
+ login(credentials: any, options?: LoginOptions): Promise<APIResponse<TokenBundle>>;
69
73
  /**
70
74
  * Logout
71
75
  */
72
- logout(ctx?: TokenKitContext): Promise<void>;
76
+ logout(): Promise<void>;
73
77
  /**
74
78
  * Check if authenticated
75
79
  */
76
- isAuthenticated(ctx?: TokenKitContext): boolean;
80
+ isAuthenticated(): boolean;
77
81
  /**
78
82
  * Get current session
79
83
  */
80
- getSession(ctx?: TokenKitContext): Session | null;
84
+ getSession(): Session | null;
81
85
  }
82
86
  /**
83
87
  * Global API client instance.