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 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)
@@ -1,4 +1,4 @@
1
- import type { APIResponse, ClientConfig, RequestConfig, RequestOptions, Session, TokenKitConfig, LoginOptions, TokenBundle } from '../types';
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
@@ -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 define middleware in a separate file if needed
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