astro-tokenkit 1.0.17 → 1.0.18
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 +62 -0
- package/dist/client/client.d.ts +1 -1
- package/dist/client/client.js +1 -1
- package/dist/client/idle-manager.d.ts +30 -0
- package/dist/client/idle-manager.js +138 -0
- package/dist/client/tk-client.d.ts +1 -0
- package/dist/client/tk-client.js +22 -0
- package/dist/index.cjs +27 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +57 -2
- package/dist/index.js +27 -3
- package/dist/index.js.map +1 -1
- package/dist/integration.d.ts +23 -1
- package/dist/integration.js +26 -2
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.js.map +1 -1
- package/dist/types.d.ts +35 -2
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -114,6 +114,7 @@ const specializedClient = createClient({
|
|
|
114
114
|
| `timeout` | `number` | Request timeout in milliseconds (default: 30000). |
|
|
115
115
|
| `retry` | `RetryConfig` | Retry strategy for failed requests. |
|
|
116
116
|
| `interceptors`| `InterceptorsConfig` | Request/Response/Error interceptors. |
|
|
117
|
+
| `idle` | `IdleConfig` | Inactivity session timeout configuration. |
|
|
117
118
|
| `context` | `AsyncLocalStorage` | External AsyncLocalStorage instance. |
|
|
118
119
|
| `getContextStore`| `() => TokenKitContext`| Custom method to retrieve the context store. |
|
|
119
120
|
| `setContextStore`| `(ctx) => void`| Custom method to set the context store. |
|
|
@@ -138,6 +139,47 @@ const specializedClient = createClient({
|
|
|
138
139
|
| `cookies` | `CookieConfig` | Configuration for auth cookies. |
|
|
139
140
|
| `policy` | `RefreshPolicy` | Strategy for when to trigger token refresh. |
|
|
140
141
|
|
|
142
|
+
### Idle Session Timeout
|
|
143
|
+
|
|
144
|
+
Astro TokenKit automatically monitors user inactivity and closes the session across all open tabs. This feature uses `BroadcastChannel` to synchronize activity and logout events.
|
|
145
|
+
|
|
146
|
+
**Important:** When using the Astro integration, the `onIdle` function cannot be passed in `astro.config.mjs` because it is not serializable. Instead, listen for the `tk:idle` event on the client.
|
|
147
|
+
|
|
148
|
+
| Property | Type | Description |
|
|
149
|
+
| :--- | :--- | :--- |
|
|
150
|
+
| `timeout` | `number` | **Required.** Inactivity timeout in seconds. |
|
|
151
|
+
| `autoLogout`| `boolean` | Whether to automatically trigger logout by calling the configured logout endpoint (default: `true`). |
|
|
152
|
+
| `activeTabOnly` | `boolean` | Whether to track activity only on the active tab to save CPU/memory (default: `true`). |
|
|
153
|
+
| `alert` | `any` | Custom data to be passed to the `tk:idle` event. Ideal for configuring SweetAlert options. |
|
|
154
|
+
|
|
155
|
+
#### Handling Idle Events (e.g. SweetAlert)
|
|
156
|
+
|
|
157
|
+
On the client (browser), you can listen for the `tk:idle` event to show a notification. You can use the `alert` property from your configuration to pass options to your alert plugin.
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
// astro.config.mjs
|
|
161
|
+
tokenKit({
|
|
162
|
+
idle: {
|
|
163
|
+
timeout: 300,
|
|
164
|
+
alert: {
|
|
165
|
+
title: "Session Expired",
|
|
166
|
+
text: "You have been logged out due to inactivity.",
|
|
167
|
+
icon: "warning"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```html
|
|
174
|
+
<script>
|
|
175
|
+
window.addEventListener('tk:idle', (event) => {
|
|
176
|
+
const options = event.detail.alert;
|
|
177
|
+
// Use SweetAlert or any other plugin
|
|
178
|
+
swal(options);
|
|
179
|
+
});
|
|
180
|
+
</script>
|
|
181
|
+
```
|
|
182
|
+
|
|
141
183
|
### Login Options
|
|
142
184
|
|
|
143
185
|
| Property | Type | Description |
|
|
@@ -242,6 +284,26 @@ api.login(credentials)
|
|
|
242
284
|
|
|
243
285
|
> **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 }) => ... )`.
|
|
244
286
|
|
|
287
|
+
## Performance
|
|
288
|
+
|
|
289
|
+
Astro TokenKit is designed with a "low impact" philosophy. It introduces negligible overhead to your requests while providing powerful features like automatic token rotation.
|
|
290
|
+
|
|
291
|
+
### Benchmark Results
|
|
292
|
+
|
|
293
|
+
Run on a standard development machine using `npm run bench`:
|
|
294
|
+
|
|
295
|
+
| Scenario | Operations/sec | Latency (Overhead) |
|
|
296
|
+
| :--- | :--- | :--- |
|
|
297
|
+
| **Native fetch (Baseline)** | ~720,000 | 0µs |
|
|
298
|
+
| **Middleware overhead** | ~1,680,000 | <1µs |
|
|
299
|
+
| **APIClient (No Auth)** | ~200,000 | ~3.5µs |
|
|
300
|
+
| **APIClient (With Auth)** | ~150,000 | ~5.3µs |
|
|
301
|
+
|
|
302
|
+
**Key Takeaways:**
|
|
303
|
+
- **Zero-impact Middleware:** The middleware adds less than 1 microsecond to each Astro request.
|
|
304
|
+
- **Ultra-low Client Overhead:** Using the `APIClient` adds about 3-5 microseconds per request compared to native `fetch`.
|
|
305
|
+
- **Negligible in Real World:** In a typical scenario where a network request takes 10ms (10,000µs), Astro TokenKit adds less than **0.05%** latency.
|
|
306
|
+
|
|
245
307
|
## License
|
|
246
308
|
|
|
247
309
|
MIT © [oamm](https://github.com/oamm)
|
package/dist/client/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { APIResponse, ClientConfig, RequestConfig, RequestOptions, Session,
|
|
1
|
+
import type { APIResponse, ClientConfig, LoginOptions, RequestConfig, RequestOptions, Session, TokenBundle, TokenKitConfig } from '../types';
|
|
2
2
|
import { TokenManager } from '../auth/manager';
|
|
3
3
|
/**
|
|
4
4
|
* API Client
|
package/dist/client/client.js
CHANGED
|
@@ -305,7 +305,7 @@ export class APIClient {
|
|
|
305
305
|
throw new Error('Auth is not configured for this client');
|
|
306
306
|
}
|
|
307
307
|
const context = getContextStore();
|
|
308
|
-
return this.tokenManager.login(context, credentials, options);
|
|
308
|
+
return yield this.tokenManager.login(context, credentials, options);
|
|
309
309
|
});
|
|
310
310
|
}
|
|
311
311
|
/**
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { IdleConfig } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* IdleManager handles user inactivity across multiple tabs.
|
|
4
|
+
* It uses BroadcastChannel to synchronize activity and logout events.
|
|
5
|
+
*/
|
|
6
|
+
export declare class IdleManager {
|
|
7
|
+
private channel;
|
|
8
|
+
private timeout;
|
|
9
|
+
private onIdle;
|
|
10
|
+
private activeTabOnly;
|
|
11
|
+
private rafId;
|
|
12
|
+
private lastCheck;
|
|
13
|
+
private isIdle;
|
|
14
|
+
private eventHandler;
|
|
15
|
+
private config;
|
|
16
|
+
private isMonitoring;
|
|
17
|
+
private lastActivity;
|
|
18
|
+
private expiredTimeKey;
|
|
19
|
+
constructor(config: IdleConfig);
|
|
20
|
+
private start;
|
|
21
|
+
private loop;
|
|
22
|
+
private setupEventListeners;
|
|
23
|
+
private addTrackers;
|
|
24
|
+
private removeTrackers;
|
|
25
|
+
private reportActivity;
|
|
26
|
+
private updateExpiredTimeLocal;
|
|
27
|
+
private handleTimeout;
|
|
28
|
+
private triggerIdle;
|
|
29
|
+
cleanup(): void;
|
|
30
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IdleManager handles user inactivity across multiple tabs.
|
|
3
|
+
* It uses BroadcastChannel to synchronize activity and logout events.
|
|
4
|
+
*/
|
|
5
|
+
export class IdleManager {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
var _a;
|
|
8
|
+
this.channel = null;
|
|
9
|
+
this.rafId = null;
|
|
10
|
+
this.lastCheck = 0;
|
|
11
|
+
this.isIdle = false;
|
|
12
|
+
this.isMonitoring = false;
|
|
13
|
+
this.lastActivity = 0;
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.timeout = config.timeout;
|
|
16
|
+
this.onIdle = config.onIdle || (() => { });
|
|
17
|
+
this.activeTabOnly = (_a = config.activeTabOnly) !== null && _a !== void 0 ? _a : true;
|
|
18
|
+
this.expiredTimeKey = '_tk_idle_expires';
|
|
19
|
+
this.eventHandler = this.reportActivity.bind(this);
|
|
20
|
+
this.isIdle = false;
|
|
21
|
+
if (typeof window === 'undefined')
|
|
22
|
+
return;
|
|
23
|
+
try {
|
|
24
|
+
this.channel = new BroadcastChannel('tk_idle_channel');
|
|
25
|
+
this.channel.onmessage = (event) => {
|
|
26
|
+
if (event.data === 'activity') {
|
|
27
|
+
this.updateExpiredTimeLocal();
|
|
28
|
+
}
|
|
29
|
+
else if (event.data === 'logout') {
|
|
30
|
+
this.triggerIdle();
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
// BroadcastChannel might fail in some environments (e.g. private mode)
|
|
36
|
+
}
|
|
37
|
+
this.start();
|
|
38
|
+
}
|
|
39
|
+
start() {
|
|
40
|
+
if (typeof window === 'undefined')
|
|
41
|
+
return;
|
|
42
|
+
this.updateExpiredTimeLocal();
|
|
43
|
+
this.setupEventListeners();
|
|
44
|
+
this.loop();
|
|
45
|
+
}
|
|
46
|
+
loop() {
|
|
47
|
+
if (this.isIdle)
|
|
48
|
+
return;
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
// Check every 1 second
|
|
51
|
+
if (now - this.lastCheck >= 1000) {
|
|
52
|
+
const expiredTime = parseInt(localStorage.getItem(this.expiredTimeKey) || '0', 10);
|
|
53
|
+
if (expiredTime > 0 && now > expiredTime) {
|
|
54
|
+
this.handleTimeout();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.lastCheck = now;
|
|
58
|
+
}
|
|
59
|
+
this.rafId = requestAnimationFrame(() => this.loop());
|
|
60
|
+
}
|
|
61
|
+
setupEventListeners() {
|
|
62
|
+
if (this.activeTabOnly) {
|
|
63
|
+
document.addEventListener('visibilitychange', () => {
|
|
64
|
+
if (document.visibilityState === 'visible') {
|
|
65
|
+
this.addTrackers();
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
this.removeTrackers();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
if (document.visibilityState === 'visible') {
|
|
72
|
+
this.addTrackers();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
this.addTrackers();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
addTrackers() {
|
|
80
|
+
if (this.isMonitoring)
|
|
81
|
+
return;
|
|
82
|
+
const events = ['mousemove', 'keydown', 'scroll', 'click', 'touchstart'];
|
|
83
|
+
events.forEach(e => window.addEventListener(e, this.eventHandler, { passive: true }));
|
|
84
|
+
this.isMonitoring = true;
|
|
85
|
+
}
|
|
86
|
+
removeTrackers() {
|
|
87
|
+
if (!this.isMonitoring)
|
|
88
|
+
return;
|
|
89
|
+
const events = ['mousemove', 'keydown', 'scroll', 'click', 'touchstart'];
|
|
90
|
+
events.forEach(e => window.removeEventListener(e, this.eventHandler));
|
|
91
|
+
this.isMonitoring = false;
|
|
92
|
+
}
|
|
93
|
+
reportActivity() {
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
// Throttle reporting to every 1 second to reduce overhead
|
|
96
|
+
if (now - this.lastActivity < 1000)
|
|
97
|
+
return;
|
|
98
|
+
this.lastActivity = now;
|
|
99
|
+
this.updateExpiredTimeLocal();
|
|
100
|
+
if (this.channel) {
|
|
101
|
+
this.channel.postMessage('activity');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
updateExpiredTimeLocal() {
|
|
105
|
+
const expires = Date.now() + (this.timeout * 1000);
|
|
106
|
+
localStorage.setItem(this.expiredTimeKey, expires.toString());
|
|
107
|
+
}
|
|
108
|
+
handleTimeout() {
|
|
109
|
+
if (this.isIdle)
|
|
110
|
+
return;
|
|
111
|
+
if (this.channel) {
|
|
112
|
+
this.channel.postMessage('logout');
|
|
113
|
+
}
|
|
114
|
+
this.triggerIdle();
|
|
115
|
+
}
|
|
116
|
+
triggerIdle() {
|
|
117
|
+
if (this.isIdle)
|
|
118
|
+
return;
|
|
119
|
+
this.isIdle = true;
|
|
120
|
+
if (typeof window !== 'undefined') {
|
|
121
|
+
window.dispatchEvent(new CustomEvent('tk:idle', { detail: this.config }));
|
|
122
|
+
}
|
|
123
|
+
this.cleanup();
|
|
124
|
+
this.onIdle();
|
|
125
|
+
}
|
|
126
|
+
cleanup() {
|
|
127
|
+
if (this.rafId) {
|
|
128
|
+
cancelAnimationFrame(this.rafId);
|
|
129
|
+
this.rafId = null;
|
|
130
|
+
}
|
|
131
|
+
this.removeTrackers();
|
|
132
|
+
if (this.channel) {
|
|
133
|
+
this.channel.close();
|
|
134
|
+
this.channel = null;
|
|
135
|
+
}
|
|
136
|
+
localStorage.removeItem(this.expiredTimeKey);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { IdleManager } from './idle-manager';
|
|
2
|
+
if (typeof window !== 'undefined') {
|
|
3
|
+
const config = typeof __TOKENKIT_CONFIG__ !== 'undefined' ? __TOKENKIT_CONFIG__ : {};
|
|
4
|
+
// Initialize Idle Monitoring if configured
|
|
5
|
+
if (config.idle && config.idle.timeout > 0) {
|
|
6
|
+
new IdleManager(Object.assign(Object.assign({}, config.idle), { onIdle: () => {
|
|
7
|
+
var _a;
|
|
8
|
+
// Note: IdleManager dispatches 'tk:idle' automatically
|
|
9
|
+
if (config.idle.autoLogout !== false && ((_a = config.auth) === null || _a === void 0 ? void 0 : _a.logout)) {
|
|
10
|
+
const logoutURL = config.auth.logout.startsWith('http')
|
|
11
|
+
? config.auth.logout
|
|
12
|
+
: (config.baseURL || '') + config.auth.logout;
|
|
13
|
+
fetch(logoutURL, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
credentials: 'include'
|
|
16
|
+
}).finally(() => {
|
|
17
|
+
window.location.reload();
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
} }));
|
|
21
|
+
}
|
|
22
|
+
}
|
package/dist/index.cjs
CHANGED
|
@@ -1227,7 +1227,7 @@ class APIClient {
|
|
|
1227
1227
|
throw new Error('Auth is not configured for this client');
|
|
1228
1228
|
}
|
|
1229
1229
|
const context = getContextStore();
|
|
1230
|
-
return this.tokenManager.login(context, credentials, options);
|
|
1230
|
+
return yield this.tokenManager.login(context, credentials, options);
|
|
1231
1231
|
});
|
|
1232
1232
|
}
|
|
1233
1233
|
/**
|
|
@@ -1284,6 +1284,13 @@ function createClient(config) {
|
|
|
1284
1284
|
* Astro integration for TokenKit
|
|
1285
1285
|
*
|
|
1286
1286
|
* This integration facilitates the setup of TokenKit in an Astro project.
|
|
1287
|
+
* It performs the following:
|
|
1288
|
+
* - Sets the global configuration for the API client.
|
|
1289
|
+
* - Injects the configuration into the client-side via Vite's `define`.
|
|
1290
|
+
* - Automatically registers the TokenKit middleware (unless `autoMiddleware` is set to `false`).
|
|
1291
|
+
* - Injects a client-side script (`astro-tokenkit/client-init`) to handle idle session monitoring and automatic logout.
|
|
1292
|
+
*
|
|
1293
|
+
* @param config - TokenKit configuration options.
|
|
1287
1294
|
*
|
|
1288
1295
|
* @example
|
|
1289
1296
|
* ```ts
|
|
@@ -1297,6 +1304,10 @@ function createClient(config) {
|
|
|
1297
1304
|
* auth: {
|
|
1298
1305
|
* login: '/auth/login',
|
|
1299
1306
|
* refresh: '/auth/refresh',
|
|
1307
|
+
* },
|
|
1308
|
+
* idle: {
|
|
1309
|
+
* timeout: 3600, // 1 hour
|
|
1310
|
+
* alert: { title: 'Session Expired' }
|
|
1300
1311
|
* }
|
|
1301
1312
|
* })
|
|
1302
1313
|
* ]
|
|
@@ -1314,7 +1325,7 @@ function tokenKit(config) {
|
|
|
1314
1325
|
return {
|
|
1315
1326
|
name: 'astro-tokenkit',
|
|
1316
1327
|
hooks: {
|
|
1317
|
-
'astro:config:setup': ({ updateConfig, addMiddleware }) => {
|
|
1328
|
+
'astro:config:setup': ({ updateConfig, addMiddleware, injectScript }) => {
|
|
1318
1329
|
updateConfig({
|
|
1319
1330
|
vite: {
|
|
1320
1331
|
define: {
|
|
@@ -1329,13 +1340,26 @@ function tokenKit(config) {
|
|
|
1329
1340
|
order: 'pre'
|
|
1330
1341
|
});
|
|
1331
1342
|
}
|
|
1343
|
+
// Always inject the client-side script for idle monitoring
|
|
1344
|
+
injectScript('page', `import 'astro-tokenkit/client-init';`);
|
|
1332
1345
|
logger.debug('[TokenKit] Integration initialized');
|
|
1333
1346
|
},
|
|
1334
1347
|
},
|
|
1335
1348
|
};
|
|
1336
1349
|
}
|
|
1337
1350
|
/**
|
|
1338
|
-
* Helper to
|
|
1351
|
+
* Helper to create the TokenKit middleware.
|
|
1352
|
+
*
|
|
1353
|
+
* Use this if you have `autoMiddleware: false` in your integration configuration
|
|
1354
|
+
* and want to manually register the middleware in your `src/middleware.ts` file.
|
|
1355
|
+
*
|
|
1356
|
+
* @example
|
|
1357
|
+
* ```ts
|
|
1358
|
+
* // src/middleware.ts
|
|
1359
|
+
* import { defineMiddleware } from 'astro-tokenkit';
|
|
1360
|
+
*
|
|
1361
|
+
* export const onRequest = defineMiddleware();
|
|
1362
|
+
* ```
|
|
1339
1363
|
*/
|
|
1340
1364
|
const defineMiddleware = () => createMiddleware();
|
|
1341
1365
|
|