formreader-session-timeout 0.2.0
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 +459 -0
- package/dist/index.d.ts +216 -0
- package/dist/index.js +590 -0
- package/dist/index.mjs +553 -0
- package/package.json +32 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
// types/index.ts
|
|
2
|
+
var DEFAULT_SESSION_CONFIG = {
|
|
3
|
+
refreshThresholdMs: 2 * 60 * 1e3,
|
|
4
|
+
// 2 minutes before expiry
|
|
5
|
+
idleCheckIntervalMs: 10 * 1e3,
|
|
6
|
+
// Check every 10 seconds
|
|
7
|
+
idleTimeoutMs: 15 * 60 * 1e3,
|
|
8
|
+
// 15 minutes of inactivity
|
|
9
|
+
maxSessionDurationMs: 8 * 60 * 60 * 1e3,
|
|
10
|
+
// 8 hours max
|
|
11
|
+
refreshEndpoint: "/auth/refresh/",
|
|
12
|
+
logoutEndpoint: "/auth/logout/",
|
|
13
|
+
showIdleWarning: true,
|
|
14
|
+
idleWarningThresholdMs: 2 * 60 * 1e3,
|
|
15
|
+
// 2 minutes before idle timeout
|
|
16
|
+
autoRefresh: true,
|
|
17
|
+
debug: false
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// services/tokenService.ts
|
|
21
|
+
function decodeJWT(token) {
|
|
22
|
+
try {
|
|
23
|
+
const parts = token.split(".");
|
|
24
|
+
if (parts.length !== 3) {
|
|
25
|
+
throw new Error("Invalid JWT format");
|
|
26
|
+
}
|
|
27
|
+
const decodedPayload = JSON.parse(
|
|
28
|
+
atob(parts[1].replace(/-/g, "+").replace(/_/g, "/"))
|
|
29
|
+
);
|
|
30
|
+
return decodedPayload;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error("Failed to decode JWT:", error);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function getTokenInfo(token) {
|
|
37
|
+
const payload = decodeJWT(token);
|
|
38
|
+
if (!payload || !payload.exp) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const expiresAt = payload.exp * 1e3;
|
|
42
|
+
const expiresIn = expiresAt - Date.now();
|
|
43
|
+
return {
|
|
44
|
+
token,
|
|
45
|
+
expiresAt,
|
|
46
|
+
expiresIn,
|
|
47
|
+
payload
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function isTokenExpired(token, bufferMs = 0) {
|
|
51
|
+
const tokenInfo = getTokenInfo(token);
|
|
52
|
+
if (!tokenInfo) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return tokenInfo.expiresIn - bufferMs <= 0;
|
|
56
|
+
}
|
|
57
|
+
function getTimeUntilExpiry(token) {
|
|
58
|
+
const tokenInfo = getTokenInfo(token);
|
|
59
|
+
if (!tokenInfo) {
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
return Math.max(0, tokenInfo.expiresIn);
|
|
63
|
+
}
|
|
64
|
+
function getStoredToken() {
|
|
65
|
+
return sessionStorage.getItem("authToken") || localStorage.getItem("authToken");
|
|
66
|
+
}
|
|
67
|
+
function storeToken(token, persistent = false) {
|
|
68
|
+
if (persistent) {
|
|
69
|
+
localStorage.setItem("authToken", token);
|
|
70
|
+
sessionStorage.removeItem("authToken");
|
|
71
|
+
} else {
|
|
72
|
+
sessionStorage.setItem("authToken", token);
|
|
73
|
+
localStorage.removeItem("authToken");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function clearToken() {
|
|
77
|
+
sessionStorage.removeItem("authToken");
|
|
78
|
+
localStorage.removeItem("authToken");
|
|
79
|
+
}
|
|
80
|
+
function validateToken(token) {
|
|
81
|
+
if (!token || typeof token !== "string") {
|
|
82
|
+
return { valid: false, error: "Token is missing or invalid" };
|
|
83
|
+
}
|
|
84
|
+
const payload = decodeJWT(token);
|
|
85
|
+
if (!payload) {
|
|
86
|
+
return { valid: false, error: "Failed to decode token" };
|
|
87
|
+
}
|
|
88
|
+
if (!payload.exp) {
|
|
89
|
+
return { valid: false, error: "Token missing expiry time" };
|
|
90
|
+
}
|
|
91
|
+
if (isTokenExpired(token)) {
|
|
92
|
+
return { valid: false, error: "Token has expired" };
|
|
93
|
+
}
|
|
94
|
+
return { valid: true };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// services/sessionManager.ts
|
|
98
|
+
function defaultFetchClient() {
|
|
99
|
+
return {
|
|
100
|
+
post: async (url, body, options) => {
|
|
101
|
+
const headers = {
|
|
102
|
+
"Content-Type": "application/json",
|
|
103
|
+
...options && options.headers ? options.headers : {}
|
|
104
|
+
};
|
|
105
|
+
const resp = await fetch(url, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers,
|
|
108
|
+
body: body ? JSON.stringify(body) : void 0
|
|
109
|
+
});
|
|
110
|
+
const data = await resp.json().catch(() => ({}));
|
|
111
|
+
return { data };
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
var SessionManager = class {
|
|
116
|
+
constructor(config) {
|
|
117
|
+
this.refreshTimer = null;
|
|
118
|
+
this.idleTimer = null;
|
|
119
|
+
this.idleCheckTimer = null;
|
|
120
|
+
this.maxSessionTimer = null;
|
|
121
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
122
|
+
this.requestDeduplication = /* @__PURE__ */ new Map();
|
|
123
|
+
this.config = {
|
|
124
|
+
...DEFAULT_SESSION_CONFIG,
|
|
125
|
+
...config
|
|
126
|
+
};
|
|
127
|
+
this.state = {
|
|
128
|
+
isActive: false,
|
|
129
|
+
isIdle: false,
|
|
130
|
+
lastActivityTime: Date.now(),
|
|
131
|
+
refreshAttempts: 0,
|
|
132
|
+
isRefreshing: false
|
|
133
|
+
};
|
|
134
|
+
this.log("SessionManager initialized with config:", this.config);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Initialize session management
|
|
138
|
+
*/
|
|
139
|
+
init() {
|
|
140
|
+
const token = getStoredToken();
|
|
141
|
+
if (!token) {
|
|
142
|
+
this.log("No token found, session not initialized");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const validation = validateToken(token);
|
|
146
|
+
if (!validation.valid) {
|
|
147
|
+
this.log("Invalid token:", validation.error);
|
|
148
|
+
this.logout();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
this.state.isActive = true;
|
|
152
|
+
this.setupTokenRefresh();
|
|
153
|
+
this.setupIdleTracking();
|
|
154
|
+
this.setupMaxSessionDuration();
|
|
155
|
+
this.emit("initialized");
|
|
156
|
+
this.log("Session initialized successfully");
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Setup token refresh before expiry
|
|
160
|
+
*/
|
|
161
|
+
setupTokenRefresh() {
|
|
162
|
+
const token = getStoredToken();
|
|
163
|
+
if (!token)
|
|
164
|
+
return;
|
|
165
|
+
const timeUntilExpiry = getTimeUntilExpiry(token);
|
|
166
|
+
const refreshAt = Math.max(0, timeUntilExpiry - this.config.refreshThresholdMs);
|
|
167
|
+
this.log(`Token expires in ${timeUntilExpiry}ms, will refresh in ${refreshAt}ms`);
|
|
168
|
+
if (this.refreshTimer) {
|
|
169
|
+
clearTimeout(this.refreshTimer);
|
|
170
|
+
}
|
|
171
|
+
if (this.config.autoRefresh && refreshAt > 0) {
|
|
172
|
+
this.refreshTimer = setTimeout(() => {
|
|
173
|
+
this.log("Scheduled token refresh triggered");
|
|
174
|
+
this.refreshToken();
|
|
175
|
+
}, refreshAt);
|
|
176
|
+
} else if (refreshAt <= 0 && this.config.autoRefresh) {
|
|
177
|
+
this.log("Token near expiry, refreshing immediately");
|
|
178
|
+
this.refreshToken();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Setup idle activity tracking
|
|
183
|
+
*/
|
|
184
|
+
setupIdleTracking() {
|
|
185
|
+
const events = ["mousedown", "keydown", "scroll", "touchstart", "click"];
|
|
186
|
+
const handleActivity = () => {
|
|
187
|
+
this.state.lastActivityTime = Date.now();
|
|
188
|
+
this.state.isIdle = false;
|
|
189
|
+
this.emit("activity");
|
|
190
|
+
if (this.idleTimer) {
|
|
191
|
+
clearTimeout(this.idleTimer);
|
|
192
|
+
}
|
|
193
|
+
this.idleTimer = setTimeout(() => {
|
|
194
|
+
var _a, _b;
|
|
195
|
+
this.state.isIdle = true;
|
|
196
|
+
this.emit("idle");
|
|
197
|
+
this.log("User idle timeout triggered");
|
|
198
|
+
(_b = (_a = this.config).onIdle) == null ? void 0 : _b.call(_a);
|
|
199
|
+
}, this.config.idleTimeoutMs);
|
|
200
|
+
};
|
|
201
|
+
events.forEach((event) => {
|
|
202
|
+
window.addEventListener(event, handleActivity, { passive: true });
|
|
203
|
+
});
|
|
204
|
+
this.idleTimer = setTimeout(() => {
|
|
205
|
+
var _a, _b;
|
|
206
|
+
this.state.isIdle = true;
|
|
207
|
+
this.emit("idle");
|
|
208
|
+
this.log("User idle timeout triggered");
|
|
209
|
+
(_b = (_a = this.config).onIdle) == null ? void 0 : _b.call(_a);
|
|
210
|
+
}, this.config.idleTimeoutMs);
|
|
211
|
+
this.idleCheckTimer = setInterval(() => {
|
|
212
|
+
const timeSinceActivity = Date.now() - this.state.lastActivityTime;
|
|
213
|
+
const timeUntilIdle = this.config.idleTimeoutMs - timeSinceActivity;
|
|
214
|
+
if (this.config.showIdleWarning && timeUntilIdle <= this.config.idleWarningThresholdMs && !this.state.isIdle) {
|
|
215
|
+
this.emit("idleWarning", { timeRemaining: timeUntilIdle });
|
|
216
|
+
}
|
|
217
|
+
}, this.config.idleCheckIntervalMs);
|
|
218
|
+
this.listeners.set("cleanup-events", events.map((event) => () => {
|
|
219
|
+
window.removeEventListener(event, handleActivity);
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Setup max session duration timer
|
|
224
|
+
*/
|
|
225
|
+
setupMaxSessionDuration() {
|
|
226
|
+
if (this.maxSessionTimer) {
|
|
227
|
+
clearTimeout(this.maxSessionTimer);
|
|
228
|
+
}
|
|
229
|
+
this.maxSessionTimer = setTimeout(() => {
|
|
230
|
+
this.log("Max session duration reached, logging out");
|
|
231
|
+
this.logout();
|
|
232
|
+
}, this.config.maxSessionDurationMs);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Refresh token
|
|
236
|
+
*/
|
|
237
|
+
async refreshToken() {
|
|
238
|
+
var _a, _b, _c, _d;
|
|
239
|
+
if (this.state.isRefreshing) {
|
|
240
|
+
this.log("Refresh already in progress, returning pending promise");
|
|
241
|
+
return this.requestDeduplication.get("refresh");
|
|
242
|
+
}
|
|
243
|
+
this.state.isRefreshing = true;
|
|
244
|
+
this.state.refreshAttempts++;
|
|
245
|
+
const token = getStoredToken();
|
|
246
|
+
if (!token) {
|
|
247
|
+
this.state.isRefreshing = false;
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
this.log(`Refreshing token (attempt ${this.state.refreshAttempts})`);
|
|
252
|
+
const client = this.config.httpClient || defaultFetchClient();
|
|
253
|
+
const refreshPayload = this.config.refreshPayloadFormatter ? this.config.refreshPayloadFormatter(token) : { token };
|
|
254
|
+
const refreshPromise = client.post(this.config.refreshEndpoint, refreshPayload, { headers: { Authorization: `Bearer ${token}` } });
|
|
255
|
+
this.requestDeduplication.set("refresh", refreshPromise);
|
|
256
|
+
const response = await refreshPromise;
|
|
257
|
+
const newToken = response.data.token || response.data.access_token;
|
|
258
|
+
if (!newToken) {
|
|
259
|
+
throw new Error("No token in refresh response");
|
|
260
|
+
}
|
|
261
|
+
storeToken(newToken);
|
|
262
|
+
this.state.isRefreshing = false;
|
|
263
|
+
this.state.refreshAttempts = 0;
|
|
264
|
+
this.requestDeduplication.delete("refresh");
|
|
265
|
+
this.log("Token refreshed successfully");
|
|
266
|
+
this.emit("tokenRefreshed");
|
|
267
|
+
(_b = (_a = this.config).onRefreshSuccess) == null ? void 0 : _b.call(_a);
|
|
268
|
+
this.setupTokenRefresh();
|
|
269
|
+
return true;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
this.state.isRefreshing = false;
|
|
272
|
+
this.log("Token refresh failed:", error);
|
|
273
|
+
if (this.state.refreshAttempts >= 3) {
|
|
274
|
+
this.log("Max refresh attempts reached, logging out");
|
|
275
|
+
this.logout();
|
|
276
|
+
}
|
|
277
|
+
(_d = (_c = this.config).onRefreshFailure) == null ? void 0 : _d.call(_c, error);
|
|
278
|
+
this.emit("refreshFailed", error);
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Logout and cleanup
|
|
284
|
+
*/
|
|
285
|
+
async logout() {
|
|
286
|
+
var _a, _b;
|
|
287
|
+
try {
|
|
288
|
+
const token = getStoredToken();
|
|
289
|
+
if (token) {
|
|
290
|
+
const client = this.config.httpClient || defaultFetchClient();
|
|
291
|
+
const logoutPayload = this.config.logoutPayloadFormatter ? this.config.logoutPayloadFormatter(token) : { token };
|
|
292
|
+
await client.post(this.config.logoutEndpoint, logoutPayload, { headers: { Authorization: `Bearer ${token}` } });
|
|
293
|
+
}
|
|
294
|
+
} catch (error) {
|
|
295
|
+
this.log("Logout API call failed:", error);
|
|
296
|
+
}
|
|
297
|
+
this.cleanup();
|
|
298
|
+
this.state.isActive = false;
|
|
299
|
+
this.emit("loggedOut");
|
|
300
|
+
(_b = (_a = this.config).onSessionExpired) == null ? void 0 : _b.call(_a);
|
|
301
|
+
clearToken();
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Extend session (reset idle timer)
|
|
305
|
+
*/
|
|
306
|
+
extendSession() {
|
|
307
|
+
this.state.lastActivityTime = Date.now();
|
|
308
|
+
this.state.isIdle = false;
|
|
309
|
+
if (this.idleTimer) {
|
|
310
|
+
clearTimeout(this.idleTimer);
|
|
311
|
+
}
|
|
312
|
+
this.idleTimer = setTimeout(() => {
|
|
313
|
+
this.state.isIdle = true;
|
|
314
|
+
this.emit("idle");
|
|
315
|
+
}, this.config.idleTimeoutMs);
|
|
316
|
+
this.log("Session extended");
|
|
317
|
+
this.emit("sessionExtended");
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Get current state
|
|
321
|
+
*/
|
|
322
|
+
getState() {
|
|
323
|
+
return { ...this.state };
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Get current config
|
|
327
|
+
*/
|
|
328
|
+
getConfig() {
|
|
329
|
+
return { ...this.config };
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Update config
|
|
333
|
+
*/
|
|
334
|
+
updateConfig(newConfig) {
|
|
335
|
+
this.config = { ...this.config, ...newConfig };
|
|
336
|
+
this.log("Config updated:", this.config);
|
|
337
|
+
if (this.state.isActive) {
|
|
338
|
+
this.cleanup();
|
|
339
|
+
this.init();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Event listeners
|
|
344
|
+
*/
|
|
345
|
+
on(event, callback) {
|
|
346
|
+
if (!this.listeners.has(event)) {
|
|
347
|
+
this.listeners.set(event, []);
|
|
348
|
+
}
|
|
349
|
+
this.listeners.get(event).push(callback);
|
|
350
|
+
return () => {
|
|
351
|
+
const callbacks = this.listeners.get(event);
|
|
352
|
+
if (callbacks) {
|
|
353
|
+
const index = callbacks.indexOf(callback);
|
|
354
|
+
if (index > -1) {
|
|
355
|
+
callbacks.splice(index, 1);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Emit event
|
|
362
|
+
*/
|
|
363
|
+
emit(event, data) {
|
|
364
|
+
const callbacks = this.listeners.get(event);
|
|
365
|
+
if (callbacks) {
|
|
366
|
+
callbacks.forEach((callback) => callback(data));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Cleanup timers and listeners
|
|
371
|
+
*/
|
|
372
|
+
cleanup() {
|
|
373
|
+
if (this.refreshTimer)
|
|
374
|
+
clearTimeout(this.refreshTimer);
|
|
375
|
+
if (this.idleTimer)
|
|
376
|
+
clearTimeout(this.idleTimer);
|
|
377
|
+
if (this.idleCheckTimer)
|
|
378
|
+
clearInterval(this.idleCheckTimer);
|
|
379
|
+
if (this.maxSessionTimer)
|
|
380
|
+
clearTimeout(this.maxSessionTimer);
|
|
381
|
+
this.refreshTimer = null;
|
|
382
|
+
this.idleTimer = null;
|
|
383
|
+
this.idleCheckTimer = null;
|
|
384
|
+
this.maxSessionTimer = null;
|
|
385
|
+
const cleanupFunctions = this.listeners.get("cleanup-events");
|
|
386
|
+
if (cleanupFunctions) {
|
|
387
|
+
cleanupFunctions.forEach((fn) => fn());
|
|
388
|
+
}
|
|
389
|
+
this.listeners.clear();
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Destroy session manager
|
|
393
|
+
*/
|
|
394
|
+
destroy() {
|
|
395
|
+
this.cleanup();
|
|
396
|
+
this.listeners.clear();
|
|
397
|
+
this.requestDeduplication.clear();
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Logging utility
|
|
401
|
+
*/
|
|
402
|
+
log(...args) {
|
|
403
|
+
if (this.config.debug) {
|
|
404
|
+
console.log("[SessionManager]", ...args);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
var sessionManagerInstance = null;
|
|
409
|
+
function getSessionManager(config) {
|
|
410
|
+
if (!sessionManagerInstance) {
|
|
411
|
+
sessionManagerInstance = new SessionManager(config || {});
|
|
412
|
+
}
|
|
413
|
+
return sessionManagerInstance;
|
|
414
|
+
}
|
|
415
|
+
function resetSessionManager() {
|
|
416
|
+
if (sessionManagerInstance) {
|
|
417
|
+
sessionManagerInstance.destroy();
|
|
418
|
+
sessionManagerInstance = null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// hooks/useSessionTimeout.ts
|
|
423
|
+
import { useEffect, useState, useCallback, useRef } from "react";
|
|
424
|
+
function useSessionTimeout(options = {}) {
|
|
425
|
+
const {
|
|
426
|
+
config,
|
|
427
|
+
enabled = true,
|
|
428
|
+
onSessionExpiring,
|
|
429
|
+
onSessionExpired,
|
|
430
|
+
onIdle,
|
|
431
|
+
onRefreshSuccess
|
|
432
|
+
} = options;
|
|
433
|
+
const [sessionState, setSessionState] = useState({
|
|
434
|
+
isActive: false,
|
|
435
|
+
isIdle: false,
|
|
436
|
+
lastActivityTime: Date.now(),
|
|
437
|
+
refreshAttempts: 0,
|
|
438
|
+
isRefreshing: false
|
|
439
|
+
});
|
|
440
|
+
const [timeUntilIdle, setTimeUntilIdle] = useState(null);
|
|
441
|
+
const [idleWarningVisible, setIdleWarningVisible] = useState(false);
|
|
442
|
+
const managerRef = useRef(null);
|
|
443
|
+
const unsubscribeRef = useRef(/* @__PURE__ */ new Map());
|
|
444
|
+
useEffect(() => {
|
|
445
|
+
if (!enabled)
|
|
446
|
+
return;
|
|
447
|
+
const manager = getSessionManager({
|
|
448
|
+
...config,
|
|
449
|
+
onSessionExpiring,
|
|
450
|
+
onSessionExpired,
|
|
451
|
+
onIdle,
|
|
452
|
+
onRefreshSuccess
|
|
453
|
+
});
|
|
454
|
+
managerRef.current = manager;
|
|
455
|
+
const subscriptions = /* @__PURE__ */ new Map();
|
|
456
|
+
subscriptions.set(
|
|
457
|
+
"initialized",
|
|
458
|
+
manager.on("initialized", () => {
|
|
459
|
+
setSessionState(manager.getState());
|
|
460
|
+
})
|
|
461
|
+
);
|
|
462
|
+
subscriptions.set(
|
|
463
|
+
"tokenRefreshed",
|
|
464
|
+
manager.on("tokenRefreshed", () => {
|
|
465
|
+
setSessionState(manager.getState());
|
|
466
|
+
})
|
|
467
|
+
);
|
|
468
|
+
subscriptions.set(
|
|
469
|
+
"idle",
|
|
470
|
+
manager.on("idle", () => {
|
|
471
|
+
setSessionState(manager.getState());
|
|
472
|
+
})
|
|
473
|
+
);
|
|
474
|
+
subscriptions.set(
|
|
475
|
+
"activity",
|
|
476
|
+
manager.on("activity", () => {
|
|
477
|
+
setSessionState(manager.getState());
|
|
478
|
+
setIdleWarningVisible(false);
|
|
479
|
+
})
|
|
480
|
+
);
|
|
481
|
+
subscriptions.set(
|
|
482
|
+
"idleWarning",
|
|
483
|
+
manager.on("idleWarning", (data) => {
|
|
484
|
+
setIdleWarningVisible(true);
|
|
485
|
+
setTimeUntilIdle(data.timeRemaining);
|
|
486
|
+
})
|
|
487
|
+
);
|
|
488
|
+
subscriptions.set(
|
|
489
|
+
"loggedOut",
|
|
490
|
+
manager.on("loggedOut", () => {
|
|
491
|
+
setSessionState(manager.getState());
|
|
492
|
+
})
|
|
493
|
+
);
|
|
494
|
+
unsubscribeRef.current = subscriptions;
|
|
495
|
+
manager.init();
|
|
496
|
+
return () => {
|
|
497
|
+
subscriptions.forEach((unsub) => unsub());
|
|
498
|
+
unsubscribeRef.current.clear();
|
|
499
|
+
};
|
|
500
|
+
}, [enabled, config, onSessionExpiring, onSessionExpired, onIdle, onRefreshSuccess]);
|
|
501
|
+
useEffect(() => {
|
|
502
|
+
if (!enabled || !sessionState.isActive)
|
|
503
|
+
return;
|
|
504
|
+
const interval = setInterval(() => {
|
|
505
|
+
const manager = managerRef.current;
|
|
506
|
+
if (manager) {
|
|
507
|
+
setSessionState(manager.getState());
|
|
508
|
+
}
|
|
509
|
+
}, 5e3);
|
|
510
|
+
return () => clearInterval(interval);
|
|
511
|
+
}, [enabled, sessionState.isActive]);
|
|
512
|
+
const extendSession = useCallback(() => {
|
|
513
|
+
var _a;
|
|
514
|
+
(_a = managerRef.current) == null ? void 0 : _a.extendSession();
|
|
515
|
+
}, []);
|
|
516
|
+
const refreshToken = useCallback(async () => {
|
|
517
|
+
var _a;
|
|
518
|
+
return (_a = managerRef.current) == null ? void 0 : _a.refreshToken();
|
|
519
|
+
}, []);
|
|
520
|
+
const logout = useCallback(async () => {
|
|
521
|
+
var _a;
|
|
522
|
+
return (_a = managerRef.current) == null ? void 0 : _a.logout();
|
|
523
|
+
}, []);
|
|
524
|
+
const updateConfig = useCallback((newConfig) => {
|
|
525
|
+
var _a;
|
|
526
|
+
(_a = managerRef.current) == null ? void 0 : _a.updateConfig(newConfig);
|
|
527
|
+
}, []);
|
|
528
|
+
return {
|
|
529
|
+
sessionState,
|
|
530
|
+
timeUntilExpiry: null,
|
|
531
|
+
timeUntilIdle,
|
|
532
|
+
idleWarningVisible,
|
|
533
|
+
extendSession,
|
|
534
|
+
refreshToken,
|
|
535
|
+
logout,
|
|
536
|
+
updateConfig,
|
|
537
|
+
manager: managerRef.current
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
export {
|
|
541
|
+
DEFAULT_SESSION_CONFIG,
|
|
542
|
+
SessionManager,
|
|
543
|
+
clearToken,
|
|
544
|
+
getSessionManager,
|
|
545
|
+
getStoredToken,
|
|
546
|
+
getTimeUntilExpiry,
|
|
547
|
+
getTokenInfo,
|
|
548
|
+
isTokenExpired,
|
|
549
|
+
resetSessionManager,
|
|
550
|
+
storeToken,
|
|
551
|
+
useSessionTimeout,
|
|
552
|
+
validateToken
|
|
553
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "formreader-session-timeout",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Session timeout microfrontend: configurable JWT expiry decoding and refresh with idle tracking",
|
|
5
|
+
"main": "dist/index.cjs.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup index.ts --format cjs,esm --dts --out-dir dist",
|
|
13
|
+
"clean": "rm -rf dist"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"react": "^16 || ^17 || ^18"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"whatwg-fetch": "^3.6.2"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^25.0.3",
|
|
23
|
+
"@types/react": "^19.2.7",
|
|
24
|
+
"tsup": "^6.5.0",
|
|
25
|
+
"typescript": "^4.9.5"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": ""
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT"
|
|
32
|
+
}
|