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 ADDED
@@ -0,0 +1,459 @@
1
+ # @formreader/session-timeout
2
+
3
+ A lightweight, fully configurable React microfrontend for **JWT token refresh management**, **idle session tracking**, and **auto-logout** with comprehensive event callbacks.
4
+
5
+ ## Features
6
+
7
+ - ✅ **JWT Expiry Decoding** — Reads token expiry from JWT `exp` claim, no server roundtrip needed
8
+ - ✅ **Configurable Refresh Timing** — Schedule token refresh at any point before expiry
9
+ - ✅ **Idle Detection** — Automatic idle timeout with configurable warning dialogs
10
+ - ✅ **Custom Payload Formatting** — Tailor refresh/logout request payloads to your API
11
+ - ✅ **Flexible HTTP Integration** — Use your own axios/fetch client or rely on built-in fetch
12
+ - ✅ **Event-Driven** — Full control via callbacks: `onRefreshSuccess`, `onIdle`, `onSessionExpired`, etc.
13
+ - ✅ **Zero External Dependencies** — Works standalone; React is a peer dependency only
14
+ - ✅ **Fully Typed** — TypeScript support out of the box
15
+ - ✅ **Singleton Pattern** — Global session manager or per-component hooks
16
+
17
+ ---
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @formreader/session-timeout
23
+ ```
24
+
25
+ Or using yarn:
26
+ ```bash
27
+ yarn add @formreader/session-timeout
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Quick Start
33
+
34
+ ### 1. Hook-Based Usage (Recommended)
35
+
36
+ Import and use the `useSessionTimeout` hook in any React component:
37
+
38
+ ```typescript
39
+ import React from 'react';
40
+ import { useSessionTimeout } from '@formreader/session-timeout';
41
+
42
+ export function MyComponent() {
43
+ const {
44
+ sessionState,
45
+ timeUntilIdle,
46
+ idleWarningVisible,
47
+ extendSession,
48
+ refreshToken,
49
+ logout,
50
+ } = useSessionTimeout({
51
+ enabled: true,
52
+ config: {
53
+ refreshThresholdMs: 2 * 60 * 1000, // Refresh 2 min before expiry
54
+ idleTimeoutMs: 15 * 60 * 1000, // Logout after 15 min idle
55
+ idleCheckIntervalMs: 10 * 1000, // Check idle every 10 sec
56
+ idleWarningThresholdMs: 2 * 60 * 1000, // Show warning 2 min before idle
57
+ autoRefresh: true,
58
+ showIdleWarning: true,
59
+ debug: true,
60
+ // Your API endpoints
61
+ refreshEndpoint: '/api/auth/refresh/',
62
+ logoutEndpoint: '/api/auth/logout/',
63
+ // Custom payload format (if needed)
64
+ refreshPayloadFormatter: (token) => ({ refresh: token }),
65
+ },
66
+ onRefreshSuccess: () => console.log('Token refreshed'),
67
+ onIdle: () => console.log('User idle'),
68
+ onSessionExpired: () => console.log('Session expired'),
69
+ });
70
+
71
+ return (
72
+ <div>
73
+ <p>Session: {sessionState.isActive ? '🟢 Active' : '🔴 Inactive'}</p>
74
+ {idleWarningVisible && (
75
+ <div className="warning">
76
+ <p>Your session is about to expire in {timeUntilIdle}ms</p>
77
+ <button onClick={extendSession}>Stay Logged In</button>
78
+ </div>
79
+ )}
80
+ </div>
81
+ );
82
+ }
83
+ ```
84
+
85
+ ### 2. Service-Based Usage
86
+
87
+ Use `SessionManager` directly for more control:
88
+
89
+ ```typescript
90
+ import {
91
+ getSessionManager,
92
+ resetSessionManager,
93
+ getStoredToken,
94
+ getTokenInfo,
95
+ } from '@formreader/session-timeout';
96
+
97
+ // Initialize on app startup
98
+ const manager = getSessionManager({
99
+ refreshThresholdMs: 2 * 60 * 1000,
100
+ idleTimeoutMs: 15 * 60 * 1000,
101
+ autoRefresh: true,
102
+ debug: true,
103
+ refreshEndpoint: '/api/auth/refresh/',
104
+ logoutEndpoint: '/api/auth/logout/',
105
+ refreshPayloadFormatter: (token) => ({ refresh: token }),
106
+ });
107
+
108
+ manager.init();
109
+
110
+ // Listen to events
111
+ manager.on('tokenRefreshed', () => console.log('Token refreshed'));
112
+ manager.on('idle', () => console.log('User idle'));
113
+
114
+ // Manual refresh if needed
115
+ await manager.refreshToken();
116
+
117
+ // Cleanup on logout or unmount
118
+ manager.destroy();
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Configuration
124
+
125
+ All settings are optional and inherit sensible defaults. Pass them via the `config` object:
126
+
127
+ ### Session Timing
128
+
129
+ | Option | Type | Default | Description |
130
+ |--------|------|---------|-------------|
131
+ | `refreshThresholdMs` | number | `2 * 60 * 1000` | Milliseconds before token expiry to trigger refresh |
132
+ | `idleTimeoutMs` | number | `15 * 60 * 1000` | Milliseconds of inactivity before session ends |
133
+ | `idleCheckIntervalMs` | number | `10 * 1000` | How often to check for idle activity |
134
+ | `idleWarningThresholdMs` | number | `2 * 60 * 1000` | Milliseconds before idle timeout to show warning |
135
+ | `maxSessionDurationMs` | number | `8 * 60 * 60 * 1000` | Absolute max session duration (8 hours) |
136
+
137
+ ### API Configuration
138
+
139
+ | Option | Type | Default | Description |
140
+ |--------|------|---------|-------------|
141
+ | `refreshEndpoint` | string | `/auth/refresh/` | URL for token refresh |
142
+ | `logoutEndpoint` | string | `/auth/logout/` | URL for logout |
143
+ | `autoRefresh` | boolean | `true` | Auto-refresh tokens before expiry |
144
+
145
+ ### UI Configuration
146
+
147
+ | Option | Type | Default | Description |
148
+ |--------|------|---------|-------------|
149
+ | `showIdleWarning` | boolean | `true` | Show idle warning dialog |
150
+ | `debug` | boolean | `false` | Log debug messages to console |
151
+
152
+ ### Custom Integration
153
+
154
+ | Option | Type | Description |
155
+ |--------|------|-------------|
156
+ | `httpClient?` | `{ post(...): Promise }` | Custom HTTP client (defaults to `fetch`) |
157
+ | `refreshPayloadFormatter?` | `(token) => object` | Format refresh request payload |
158
+ | `logoutPayloadFormatter?` | `(token) => object` | Format logout request payload |
159
+
160
+ ---
161
+
162
+ ## Custom HTTP Client
163
+
164
+ If you have an existing axios instance or custom HTTP client, pass it to avoid dependencies:
165
+
166
+ ```typescript
167
+ import axios from 'axios';
168
+
169
+ const { useSessionTimeout } = require('@formreader/session-timeout');
170
+
171
+ useSessionTimeout({
172
+ config: {
173
+ httpClient: {
174
+ post: (url, body, options) => axios.post(url, body, options),
175
+ },
176
+ refreshThresholdMs: 2 * 60 * 1000,
177
+ // ... other config
178
+ },
179
+ });
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Custom Payload Formatting
185
+
186
+ Your API may expect a different payload structure. Use the formatters to customize:
187
+
188
+ ### Example 1: API requires `refresh` field
189
+
190
+ ```typescript
191
+ useSessionTimeout({
192
+ config: {
193
+ refreshPayloadFormatter: (token) => ({
194
+ refresh: token, // API expects "refresh", not "token"
195
+ }),
196
+ logoutPayloadFormatter: (token) => ({
197
+ refresh: token,
198
+ }),
199
+ },
200
+ });
201
+ ```
202
+
203
+ **Request sent:**
204
+ ```json
205
+ POST /api/auth/refresh/
206
+ { "refresh": "eyJhbGc..." }
207
+ ```
208
+
209
+ ### Example 2: Complex nested structure
210
+
211
+ ```typescript
212
+ useSessionTimeout({
213
+ config: {
214
+ refreshPayloadFormatter: (token) => ({
215
+ action: 'refresh',
216
+ credentials: {
217
+ refresh_token: token,
218
+ client_id: 'my-client',
219
+ },
220
+ }),
221
+ },
222
+ });
223
+ ```
224
+
225
+ **Request sent:**
226
+ ```json
227
+ POST /api/auth/refresh/
228
+ {
229
+ "action": "refresh",
230
+ "credentials": {
231
+ "refresh_token": "eyJhbGc...",
232
+ "client_id": "my-client"
233
+ }
234
+ }
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Event Callbacks
240
+
241
+ Full control over session lifecycle:
242
+
243
+ ```typescript
244
+ useSessionTimeout({
245
+ config: { /* ... */ },
246
+
247
+ // Called when token is about to expire
248
+ onSessionExpiring: () => {
249
+ console.warn('Token expiring soon');
250
+ },
251
+
252
+ // Called when user becomes idle
253
+ onIdle: () => {
254
+ console.log('User idle – logging out');
255
+ },
256
+
257
+ // Called when session has completely expired
258
+ onSessionExpired: () => {
259
+ console.log('Session expired – redirecting to login');
260
+ window.location.href = '/login';
261
+ },
262
+
263
+ // Called after successful token refresh
264
+ onRefreshSuccess: () => {
265
+ console.log('Token refreshed silently');
266
+ },
267
+
268
+ // Called on refresh failure
269
+ onRefreshFailure: (error) => {
270
+ console.error('Refresh failed:', error.message);
271
+ },
272
+ });
273
+ ```
274
+
275
+ ---
276
+
277
+ ## Token Storage
278
+
279
+ Tokens are stored in `sessionStorage` by default (cleared on browser close). To persist across tabs, use `localStorage`:
280
+
281
+ ```typescript
282
+ import { storeToken, getStoredToken } from '@formreader/session-timeout';
283
+
284
+ // Store persistently
285
+ storeToken(token, true); // true = use localStorage
286
+
287
+ // Retrieve (checks both storage types)
288
+ const token = getStoredToken();
289
+ ```
290
+
291
+ ---
292
+
293
+ ## Token Inspection
294
+
295
+ Decode and inspect JWT payloads without verification (client-side only):
296
+
297
+ ```typescript
298
+ import { getTokenInfo, isTokenExpired, getTimeUntilExpiry } from '@formreader/session-timeout';
299
+
300
+ const token = getStoredToken();
301
+
302
+ // Get full token info
303
+ const info = getTokenInfo(token);
304
+ console.log(info.expiresAt); // Unix timestamp (ms)
305
+ console.log(info.expiresIn); // Milliseconds until expiry
306
+ console.log(info.payload); // JWT payload object
307
+
308
+ // Quick checks
309
+ const expired = isTokenExpired(token);
310
+ const timeLeft = getTimeUntilExpiry(token);
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Global Session Manager
316
+
317
+ For app-wide state, initialize the singleton once at startup:
318
+
319
+ ```typescript
320
+ import { getSessionManager, resetSessionManager } from '@formreader/session-timeout';
321
+
322
+ // App startup
323
+ const manager = getSessionManager({
324
+ refreshThresholdMs: 2 * 60 * 1000,
325
+ idleTimeoutMs: 15 * 60 * 1000,
326
+ autoRefresh: true,
327
+ refreshEndpoint: '/api/auth/refresh/',
328
+ logoutEndpoint: '/api/auth/logout/',
329
+ });
330
+
331
+ manager.init();
332
+
333
+ // Listen to events
334
+ manager.on('tokenRefreshed', () => console.log('Token refreshed'));
335
+
336
+ // Later, reset if needed (e.g., on logout)
337
+ resetSessionManager();
338
+ ```
339
+
340
+ ---
341
+
342
+ ## TypeScript Support
343
+
344
+ Full TypeScript definitions included:
345
+
346
+ ```typescript
347
+ import {
348
+ SessionConfig,
349
+ SessionState,
350
+ TokenInfo,
351
+ JWTPayload,
352
+ RefreshTokenResponse,
353
+ UseSessionTimeoutOptions,
354
+ } from '@formreader/session-timeout';
355
+
356
+ const config: SessionConfig = {
357
+ refreshThresholdMs: 2 * 60 * 1000,
358
+ idleTimeoutMs: 15 * 60 * 1000,
359
+ // ... rest of config
360
+ };
361
+ ```
362
+
363
+ ---
364
+
365
+ ## API Reference
366
+
367
+ ### `useSessionTimeout(options?)`
368
+
369
+ React hook for session management in components.
370
+
371
+ **Parameters:**
372
+ - `options.enabled?: boolean` — Enable/disable the hook (default: `true`)
373
+ - `options.config?: Partial<SessionConfig>` — Configuration (merged with defaults)
374
+ - `options.onSessionExpiring?: () => void` — Callback before expiry
375
+ - `options.onSessionExpired?: () => void` — Callback on expiry
376
+ - `options.onIdle?: () => void` — Callback on idle
377
+ - `options.onRefreshSuccess?: () => void` — Callback on refresh success
378
+ - `options.onRefreshFailure?: (error: Error) => void` — Callback on refresh failure
379
+
380
+ **Returns:**
381
+ ```typescript
382
+ {
383
+ sessionState: SessionState; // Current session state
384
+ timeUntilIdle: number | null; // Milliseconds until idle
385
+ idleWarningVisible: boolean; // Show idle warning
386
+ extendSession: () => void; // Reset idle timer
387
+ refreshToken: () => Promise<boolean>; // Manually refresh
388
+ logout: () => Promise<void>; // Logout
389
+ updateConfig: (newConfig) => void; // Update config
390
+ manager: SessionManager; // Underlying manager
391
+ }
392
+ ```
393
+
394
+ ### `SessionManager`
395
+
396
+ Direct access to session management service.
397
+
398
+ **Methods:**
399
+ - `init()` — Initialize session management
400
+ - `refreshToken(): Promise<boolean>` — Refresh token
401
+ - `logout(): Promise<void>` — Logout and cleanup
402
+ - `extendSession()` — Reset idle timer
403
+ - `getState(): SessionState` — Current state
404
+ - `getConfig(): SessionConfig` — Current config
405
+ - `updateConfig(newConfig)` — Update config
406
+ - `on(event, callback): () => void` — Subscribe to events
407
+ - `destroy()` — Cleanup and destroy manager
408
+
409
+ **Events:**
410
+ - `'initialized'` — Manager initialized
411
+ - `'tokenRefreshed'` — Token successfully refreshed
412
+ - `'idle'` — User became idle
413
+ - `'activity'` — User activity detected
414
+ - `'idleWarning'` — Idle warning (includes `timeRemaining`)
415
+ - `'sessionExtended'` — Session extended
416
+ - `'refreshFailed'` — Token refresh failed
417
+ - `'loggedOut'` — Logged out
418
+
419
+ ### Token Service Functions
420
+
421
+ - `getTokenInfo(token): TokenInfo | null` — Get token expiry and payload
422
+ - `isTokenExpired(token, bufferMs?): boolean` — Check if token expired
423
+ - `getTimeUntilExpiry(token): number` — Get milliseconds until expiry
424
+ - `getStoredToken(): string | null` — Get token from storage
425
+ - `storeToken(token, persistent?)` — Store token
426
+ - `clearToken()` — Clear stored token
427
+ - `validateToken(token)` — Validate token structure
428
+
429
+ ---
430
+
431
+ ## Production Checklist
432
+
433
+ - [ ] Configure `refreshThresholdMs` for your token lifetime
434
+ - [ ] Set `refreshEndpoint` to your backend refresh URL
435
+ - [ ] Customize `refreshPayloadFormatter` if your API requires custom payload
436
+ - [ ] Set appropriate `idleTimeoutMs` based on security requirements
437
+ - [ ] Pass your app's HTTP client via `httpClient` option
438
+ - [ ] Test token refresh before token expires
439
+ - [ ] Test idle timeout and warning
440
+ - [ ] Handle `onSessionExpired` to redirect to login
441
+ - [ ] Disable `debug: true` in production
442
+ - [ ] Monitor `onRefreshFailure` for refresh failures
443
+
444
+ ---
445
+
446
+ ## Browser Support
447
+
448
+ - Chrome/Edge: ✅ All versions
449
+ - Firefox: ✅ All versions
450
+ - Safari: ✅ 12+
451
+ - Mobile: ✅ iOS 12+, Android 5+
452
+
453
+ Requires `fetch` API or polyfill.
454
+
455
+ ---
456
+
457
+ ## License
458
+
459
+ MIT
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Session Timeout Micro Frontend Types
3
+ * Configurable, reusable types for multi-application token refresh management
4
+ */
5
+ interface SessionConfig {
6
+ /** Time before token expiry to trigger refresh (milliseconds) */
7
+ refreshThresholdMs: number;
8
+ /** Interval to check for idle activity (milliseconds) */
9
+ idleCheckIntervalMs: number;
10
+ /** Time of inactivity before session expires (milliseconds) */
11
+ idleTimeoutMs: number;
12
+ /** Maximum session duration regardless of activity (milliseconds) */
13
+ maxSessionDurationMs: number;
14
+ /** Endpoint to call for token refresh */
15
+ refreshEndpoint: string;
16
+ /** Endpoint to call for logout */
17
+ logoutEndpoint: string;
18
+ /** Whether to show idle warning dialog */
19
+ showIdleWarning: boolean;
20
+ /** Time before idle timeout to show warning (milliseconds) */
21
+ idleWarningThresholdMs: number;
22
+ /** Callback when session is about to expire */
23
+ onSessionExpiring?: () => void;
24
+ /** Callback when user is idle */
25
+ onIdle?: () => void;
26
+ /** Callback when session has expired */
27
+ onSessionExpired?: () => void;
28
+ /** Callback on refresh success */
29
+ onRefreshSuccess?: () => void;
30
+ /** Callback on refresh failure */
31
+ onRefreshFailure?: (error: Error) => void;
32
+ /** Whether to auto-refresh before expiry */
33
+ autoRefresh: boolean;
34
+ /** Debug mode for logging */
35
+ debug: boolean;
36
+ /** Optional HTTP client to use for refresh/logout calls. If not provided, will use fetch. */
37
+ httpClient?: {
38
+ post: (url: string, body?: any, options?: any) => Promise<{
39
+ data: any;
40
+ }>;
41
+ };
42
+ /** Custom function to format the refresh request payload. Allows you to define exactly what data is sent to your API. */
43
+ refreshPayloadFormatter?: (token: string) => Record<string, any>;
44
+ /** Custom function to format the logout request payload. Allows you to define exactly what data is sent to your API. */
45
+ logoutPayloadFormatter?: (token: string) => Record<string, any>;
46
+ }
47
+ interface JWTPayload {
48
+ exp: number;
49
+ iat: number;
50
+ sub?: string;
51
+ [key: string]: any;
52
+ }
53
+ interface TokenInfo {
54
+ token: string;
55
+ expiresAt: number;
56
+ expiresIn: number;
57
+ payload: JWTPayload;
58
+ }
59
+ interface SessionState {
60
+ isActive: boolean;
61
+ isIdle: boolean;
62
+ tokenExpiry?: number;
63
+ lastActivityTime: number;
64
+ refreshAttempts: number;
65
+ isRefreshing: boolean;
66
+ }
67
+ interface RefreshTokenResponse {
68
+ token: string;
69
+ access_token?: string;
70
+ expiresIn?: number;
71
+ [key: string]: any;
72
+ }
73
+ declare const DEFAULT_SESSION_CONFIG: SessionConfig;
74
+
75
+ /**
76
+ * Session Manager Service
77
+ * Manages token refresh, idle tracking, and session lifecycle
78
+ */
79
+
80
+ declare class SessionManager {
81
+ private config;
82
+ private state;
83
+ private refreshTimer;
84
+ private idleTimer;
85
+ private idleCheckTimer;
86
+ private maxSessionTimer;
87
+ private listeners;
88
+ private requestDeduplication;
89
+ constructor(config: Partial<SessionConfig>);
90
+ /**
91
+ * Initialize session management
92
+ */
93
+ init(): void;
94
+ /**
95
+ * Setup token refresh before expiry
96
+ */
97
+ private setupTokenRefresh;
98
+ /**
99
+ * Setup idle activity tracking
100
+ */
101
+ private setupIdleTracking;
102
+ /**
103
+ * Setup max session duration timer
104
+ */
105
+ private setupMaxSessionDuration;
106
+ /**
107
+ * Refresh token
108
+ */
109
+ refreshToken(): Promise<boolean>;
110
+ /**
111
+ * Logout and cleanup
112
+ */
113
+ logout(): Promise<void>;
114
+ /**
115
+ * Extend session (reset idle timer)
116
+ */
117
+ extendSession(): void;
118
+ /**
119
+ * Get current state
120
+ */
121
+ getState(): SessionState;
122
+ /**
123
+ * Get current config
124
+ */
125
+ getConfig(): SessionConfig;
126
+ /**
127
+ * Update config
128
+ */
129
+ updateConfig(newConfig: Partial<SessionConfig>): void;
130
+ /**
131
+ * Event listeners
132
+ */
133
+ on(event: string, callback: Function): () => void;
134
+ /**
135
+ * Emit event
136
+ */
137
+ private emit;
138
+ /**
139
+ * Cleanup timers and listeners
140
+ */
141
+ private cleanup;
142
+ /**
143
+ * Destroy session manager
144
+ */
145
+ destroy(): void;
146
+ /**
147
+ * Logging utility
148
+ */
149
+ private log;
150
+ }
151
+ declare function getSessionManager(config?: Partial<SessionConfig>): SessionManager;
152
+ declare function resetSessionManager(): void;
153
+
154
+ /**
155
+ * Token Service
156
+ * Handles JWT token parsing, decoding, and expiry calculations
157
+ */
158
+
159
+ /**
160
+ * Get token info including expiry time
161
+ */
162
+ declare function getTokenInfo(token: string): TokenInfo | null;
163
+ /**
164
+ * Check if token is expired
165
+ */
166
+ declare function isTokenExpired(token: string, bufferMs?: number): boolean;
167
+ /**
168
+ * Get remaining time until token expiry
169
+ */
170
+ declare function getTimeUntilExpiry(token: string): number;
171
+ /**
172
+ * Extract token from storage
173
+ */
174
+ declare function getStoredToken(): string | null;
175
+ /**
176
+ * Store token in appropriate storage
177
+ */
178
+ declare function storeToken(token: string, persistent?: boolean): void;
179
+ /**
180
+ * Clear stored token
181
+ */
182
+ declare function clearToken(): void;
183
+ /**
184
+ * Validate token structure and expiry
185
+ */
186
+ declare function validateToken(token: string): {
187
+ valid: boolean;
188
+ error?: string;
189
+ };
190
+
191
+ /**
192
+ * useSessionTimeout Hook
193
+ * React hook for session timeout management in components
194
+ */
195
+
196
+ interface UseSessionTimeoutOptions {
197
+ config?: Partial<SessionConfig>;
198
+ enabled?: boolean;
199
+ onSessionExpiring?: () => void;
200
+ onSessionExpired?: () => void;
201
+ onIdle?: () => void;
202
+ onRefreshSuccess?: () => void;
203
+ }
204
+ declare function useSessionTimeout(options?: UseSessionTimeoutOptions): {
205
+ sessionState: SessionState;
206
+ timeUntilExpiry: any;
207
+ timeUntilIdle: number;
208
+ idleWarningVisible: boolean;
209
+ extendSession: () => void;
210
+ refreshToken: () => Promise<boolean>;
211
+ logout: () => Promise<void>;
212
+ updateConfig: (newConfig: Partial<SessionConfig>) => void;
213
+ manager: SessionManager;
214
+ };
215
+
216
+ export { DEFAULT_SESSION_CONFIG, JWTPayload, RefreshTokenResponse, SessionConfig, SessionManager, SessionState, TokenInfo, UseSessionTimeoutOptions, clearToken, getSessionManager, getStoredToken, getTimeUntilExpiry, getTokenInfo, isTokenExpired, resetSessionManager, storeToken, useSessionTimeout, validateToken };