astro-tokenkit 1.0.17 → 1.0.19
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/auth/manager.d.ts +1 -1
- package/dist/auth/manager.js +45 -18
- package/dist/client/client.d.ts +1 -1
- package/dist/client/client.js +3 -3
- 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 +94 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +64 -3
- package/dist/index.js +94 -32
- package/dist/index.js.map +1 -1
- package/dist/integration.d.ts +23 -1
- package/dist/integration.js +26 -2
- package/dist/middleware.cjs +65 -27
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.js +65 -27
- package/dist/middleware.js.map +1 -1
- package/dist/types.d.ts +41 -2
- package/dist/utils/fetch.js +20 -9
- 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/auth/manager.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare class TokenManager {
|
|
|
23
23
|
/**
|
|
24
24
|
* Ensure valid tokens (with automatic refresh)
|
|
25
25
|
*/
|
|
26
|
-
ensure(ctx: TokenKitContext, options?: AuthOptions, headers?: Record<string, string
|
|
26
|
+
ensure(ctx: TokenKitContext, options?: AuthOptions, headers?: Record<string, string>, force?: boolean): Promise<Session | null>;
|
|
27
27
|
/**
|
|
28
28
|
* Logout (clear tokens)
|
|
29
29
|
*/
|
package/dist/auth/manager.js
CHANGED
|
@@ -26,21 +26,18 @@ class SingleFlight {
|
|
|
26
26
|
const existing = this.inFlight.get(key);
|
|
27
27
|
if (existing)
|
|
28
28
|
return existing;
|
|
29
|
-
const promise = this
|
|
29
|
+
const promise = (() => __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
try {
|
|
31
|
+
return yield fn();
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
this.inFlight.delete(key);
|
|
35
|
+
}
|
|
36
|
+
}))();
|
|
30
37
|
this.inFlight.set(key, promise);
|
|
31
38
|
return promise;
|
|
32
39
|
});
|
|
33
40
|
}
|
|
34
|
-
doExecute(key, fn) {
|
|
35
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
36
|
-
try {
|
|
37
|
-
return yield fn();
|
|
38
|
-
}
|
|
39
|
-
finally {
|
|
40
|
-
this.inFlight.delete(key);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
41
|
}
|
|
45
42
|
/**
|
|
46
43
|
* Token Manager handles all token operations
|
|
@@ -56,6 +53,7 @@ export class TokenManager {
|
|
|
56
53
|
*/
|
|
57
54
|
login(ctx, credentials, options) {
|
|
58
55
|
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
var _a, _b;
|
|
59
57
|
const url = this.joinURL(this.baseURL, this.config.login);
|
|
60
58
|
const contentType = this.config.contentType || 'application/json';
|
|
61
59
|
const headers = Object.assign(Object.assign({ 'Content-Type': contentType }, this.config.headers), options === null || options === void 0 ? void 0 : options.headers);
|
|
@@ -67,12 +65,16 @@ export class TokenManager {
|
|
|
67
65
|
else {
|
|
68
66
|
requestBody = JSON.stringify(data);
|
|
69
67
|
}
|
|
68
|
+
const timeout = (_b = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : this.config.timeout) !== null && _b !== void 0 ? _b : 30000;
|
|
69
|
+
const controller = new AbortController();
|
|
70
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
70
71
|
let response;
|
|
71
72
|
try {
|
|
72
73
|
response = yield safeFetch(url, {
|
|
73
74
|
method: 'POST',
|
|
74
75
|
headers,
|
|
75
76
|
body: requestBody,
|
|
77
|
+
signal: controller.signal,
|
|
76
78
|
}, this.config);
|
|
77
79
|
}
|
|
78
80
|
catch (error) {
|
|
@@ -81,6 +83,9 @@ export class TokenManager {
|
|
|
81
83
|
yield options.onError(authError, ctx);
|
|
82
84
|
throw authError;
|
|
83
85
|
}
|
|
86
|
+
finally {
|
|
87
|
+
clearTimeout(timeoutId);
|
|
88
|
+
}
|
|
84
89
|
if (!response.ok) {
|
|
85
90
|
const authError = new AuthError(`Login failed: ${response.status} ${response.statusText}`, response.status, response);
|
|
86
91
|
if (options === null || options === void 0 ? void 0 : options.onError)
|
|
@@ -136,6 +141,7 @@ export class TokenManager {
|
|
|
136
141
|
*/
|
|
137
142
|
performRefresh(ctx, refreshToken, options, extraHeaders) {
|
|
138
143
|
return __awaiter(this, void 0, void 0, function* () {
|
|
144
|
+
var _a, _b;
|
|
139
145
|
const url = this.joinURL(this.baseURL, this.config.refresh);
|
|
140
146
|
const contentType = this.config.contentType || 'application/json';
|
|
141
147
|
const headers = Object.assign(Object.assign({ 'Content-Type': contentType }, this.config.headers), extraHeaders);
|
|
@@ -148,17 +154,24 @@ export class TokenManager {
|
|
|
148
154
|
else {
|
|
149
155
|
requestBody = JSON.stringify(data);
|
|
150
156
|
}
|
|
157
|
+
const timeout = (_b = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : this.config.timeout) !== null && _b !== void 0 ? _b : 30000;
|
|
158
|
+
const controller = new AbortController();
|
|
159
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
151
160
|
let response;
|
|
152
161
|
try {
|
|
153
162
|
response = yield safeFetch(url, {
|
|
154
163
|
method: 'POST',
|
|
155
164
|
headers,
|
|
156
165
|
body: requestBody,
|
|
166
|
+
signal: controller.signal,
|
|
157
167
|
}, this.config);
|
|
158
168
|
}
|
|
159
169
|
catch (error) {
|
|
160
170
|
throw new AuthError(`Refresh request failed: ${error.message}`, undefined, undefined, undefined, error);
|
|
161
171
|
}
|
|
172
|
+
finally {
|
|
173
|
+
clearTimeout(timeoutId);
|
|
174
|
+
}
|
|
162
175
|
if (!response.ok) {
|
|
163
176
|
// 401/403 = invalid refresh token
|
|
164
177
|
if (response.status === 401 || response.status === 403) {
|
|
@@ -190,8 +203,8 @@ export class TokenManager {
|
|
|
190
203
|
/**
|
|
191
204
|
* Ensure valid tokens (with automatic refresh)
|
|
192
205
|
*/
|
|
193
|
-
ensure(
|
|
194
|
-
return __awaiter(this,
|
|
206
|
+
ensure(ctx_1, options_1, headers_1) {
|
|
207
|
+
return __awaiter(this, arguments, void 0, function* (ctx, options, headers, force = false) {
|
|
195
208
|
var _a, _b, _c, _d, _e, _f;
|
|
196
209
|
const now = Math.floor(Date.now() / 1000);
|
|
197
210
|
const tokens = retrieveTokens(ctx, this.config.cookies);
|
|
@@ -199,12 +212,14 @@ export class TokenManager {
|
|
|
199
212
|
if (!tokens.accessToken || !tokens.refreshToken || !tokens.expiresAt) {
|
|
200
213
|
return null;
|
|
201
214
|
}
|
|
202
|
-
// Token expired
|
|
203
|
-
if (isExpired(tokens.expiresAt, now, this.config.policy)) {
|
|
215
|
+
// Token expired or force refresh
|
|
216
|
+
if (force || isExpired(tokens.expiresAt, now, this.config.policy)) {
|
|
204
217
|
const flightKey = this.createFlightKey(tokens.refreshToken);
|
|
205
218
|
const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
|
|
206
219
|
if (!bundle)
|
|
207
220
|
return null;
|
|
221
|
+
// Ensure tokens are stored in the current context (in case of shared flight)
|
|
222
|
+
storeTokens(ctx, bundle, this.config.cookies);
|
|
208
223
|
return {
|
|
209
224
|
accessToken: bundle.accessToken,
|
|
210
225
|
expiresAt: bundle.accessExpiresAt,
|
|
@@ -217,6 +232,8 @@ export class TokenManager {
|
|
|
217
232
|
const flightKey = this.createFlightKey(tokens.refreshToken);
|
|
218
233
|
const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
|
|
219
234
|
if (bundle) {
|
|
235
|
+
// Ensure tokens are stored in the current context (in case of shared flight)
|
|
236
|
+
storeTokens(ctx, bundle, this.config.cookies);
|
|
220
237
|
return {
|
|
221
238
|
accessToken: bundle.accessToken,
|
|
222
239
|
expiresAt: bundle.accessExpiresAt,
|
|
@@ -244,23 +261,33 @@ export class TokenManager {
|
|
|
244
261
|
*/
|
|
245
262
|
logout(ctx) {
|
|
246
263
|
return __awaiter(this, void 0, void 0, function* () {
|
|
247
|
-
var _a;
|
|
264
|
+
var _a, _b;
|
|
248
265
|
// Optionally call logout endpoint
|
|
249
266
|
if (this.config.logout) {
|
|
267
|
+
const timeout = (_a = this.config.timeout) !== null && _a !== void 0 ? _a : 10000;
|
|
268
|
+
const controller = new AbortController();
|
|
269
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
250
270
|
try {
|
|
251
271
|
const url = this.joinURL(this.baseURL, this.config.logout);
|
|
252
272
|
const session = this.getSession(ctx);
|
|
253
273
|
const headers = {};
|
|
254
274
|
if (session === null || session === void 0 ? void 0 : session.accessToken) {
|
|
255
|
-
const injectFn = (
|
|
275
|
+
const injectFn = (_b = this.config.injectToken) !== null && _b !== void 0 ? _b : ((token, type) => `${type !== null && type !== void 0 ? type : 'Bearer'} ${token}`);
|
|
256
276
|
headers['Authorization'] = injectFn(session.accessToken, session.tokenType);
|
|
257
277
|
}
|
|
258
|
-
yield safeFetch(url, {
|
|
278
|
+
yield safeFetch(url, {
|
|
279
|
+
method: 'POST',
|
|
280
|
+
headers,
|
|
281
|
+
signal: controller.signal,
|
|
282
|
+
}, this.config);
|
|
259
283
|
}
|
|
260
284
|
catch (error) {
|
|
261
285
|
// Ignore logout endpoint errors
|
|
262
286
|
logger.debug('[TokenKit] Logout endpoint failed:', error);
|
|
263
287
|
}
|
|
288
|
+
finally {
|
|
289
|
+
clearTimeout(timeoutId);
|
|
290
|
+
}
|
|
264
291
|
}
|
|
265
292
|
clearTokens(ctx, this.config.cookies);
|
|
266
293
|
});
|
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
|
@@ -177,8 +177,8 @@ export class APIClient {
|
|
|
177
177
|
clearTimeout(timeoutId);
|
|
178
178
|
// Handle 401 (try refresh and retry once)
|
|
179
179
|
if (response.status === 401 && this.tokenManager && !config.skipAuth && attempt === 1) {
|
|
180
|
-
// Clear and try fresh session
|
|
181
|
-
const session = yield this.tokenManager.ensure(ctx, config.auth, config.headers);
|
|
180
|
+
// Clear and try fresh session (force refresh)
|
|
181
|
+
const session = yield this.tokenManager.ensure(ctx, config.auth, config.headers, true);
|
|
182
182
|
if (session) {
|
|
183
183
|
// Retry with new token
|
|
184
184
|
return this.executeRequest(config, ctx, attempt + 1);
|
|
@@ -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
|
+
}
|