formreader-session-timeout 0.2.3 → 0.2.4
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/dist/index.d.ts +58 -37
- package/dist/index.js +183 -175
- package/dist/index.mjs +181 -175
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ interface SessionConfig {
|
|
|
17
17
|
refreshEndpoint: string;
|
|
18
18
|
/** Endpoint to call for logout */
|
|
19
19
|
logoutEndpoint: string;
|
|
20
|
+
/** Whether to track idle time */
|
|
21
|
+
trackIdleTime?: boolean;
|
|
20
22
|
/** Whether to show idle warning dialog */
|
|
21
23
|
showIdleWarning: boolean;
|
|
22
24
|
/** Time before idle timeout to show warning (milliseconds) */
|
|
@@ -27,6 +29,8 @@ interface SessionConfig {
|
|
|
27
29
|
onIdle?: () => void;
|
|
28
30
|
/** Callback when session has expired */
|
|
29
31
|
onSessionExpired?: () => void;
|
|
32
|
+
/** Callback when user logs out */
|
|
33
|
+
onLogout?: () => void;
|
|
30
34
|
/** Callback on refresh success */
|
|
31
35
|
onRefreshSuccess?: () => void;
|
|
32
36
|
/** Callback on refresh failure */
|
|
@@ -41,10 +45,20 @@ interface SessionConfig {
|
|
|
41
45
|
data: any;
|
|
42
46
|
}>;
|
|
43
47
|
};
|
|
44
|
-
/** Custom function to format the refresh request payload
|
|
45
|
-
refreshPayloadFormatter?: (token: string) => Record<string, any>;
|
|
46
|
-
/** Custom function to format the logout request payload
|
|
48
|
+
/** Custom function to format the refresh request payload */
|
|
49
|
+
refreshPayloadFormatter?: (token: string, refreshToken: string) => Record<string, any>;
|
|
50
|
+
/** Custom function to format the logout request payload */
|
|
47
51
|
logoutPayloadFormatter?: (token: string) => Record<string, any>;
|
|
52
|
+
/** Which field in login response contains access token (default: 'access') */
|
|
53
|
+
accessTokenField?: string;
|
|
54
|
+
/** Which field in login response contains refresh token (default: 'refresh') */
|
|
55
|
+
refreshTokenField?: string;
|
|
56
|
+
/** Which field in refresh response contains new access token (default: 'access') */
|
|
57
|
+
refreshAccessTokenField?: string;
|
|
58
|
+
/** Which field in refresh response contains new refresh token (default: 'refresh') */
|
|
59
|
+
refreshRefreshTokenField?: string;
|
|
60
|
+
/** Whether to store refresh token separately (default: true) */
|
|
61
|
+
storeRefreshToken?: boolean;
|
|
48
62
|
}
|
|
49
63
|
interface JWTPayload {
|
|
50
64
|
exp: number;
|
|
@@ -77,6 +91,7 @@ declare const DEFAULT_SESSION_CONFIG: SessionConfig;
|
|
|
77
91
|
/**
|
|
78
92
|
* Session Manager Service
|
|
79
93
|
* Manages token refresh, idle tracking, and session lifecycle
|
|
94
|
+
* Supports configurable token response field mapping
|
|
80
95
|
*/
|
|
81
96
|
|
|
82
97
|
declare class SessionManager {
|
|
@@ -90,15 +105,16 @@ declare class SessionManager {
|
|
|
90
105
|
private requestDeduplication;
|
|
91
106
|
constructor(config: Partial<SessionConfig>);
|
|
92
107
|
/**
|
|
93
|
-
* Initialize session management
|
|
108
|
+
* Initialize session management with optional login response
|
|
109
|
+
* Extracts and stores tokens from login response using configured field names
|
|
94
110
|
*/
|
|
95
|
-
init(): void;
|
|
111
|
+
init(loginResponse?: any): void;
|
|
96
112
|
/**
|
|
97
113
|
* Setup token refresh before expiry
|
|
98
114
|
*/
|
|
99
115
|
private setupTokenRefresh;
|
|
100
116
|
/**
|
|
101
|
-
* Setup idle activity
|
|
117
|
+
* Setup idle tracking with activity listeners
|
|
102
118
|
*/
|
|
103
119
|
private setupIdleTracking;
|
|
104
120
|
/**
|
|
@@ -106,27 +122,23 @@ declare class SessionManager {
|
|
|
106
122
|
*/
|
|
107
123
|
private setupMaxSessionDuration;
|
|
108
124
|
/**
|
|
109
|
-
* Refresh token
|
|
125
|
+
* Refresh token using stored refresh token
|
|
110
126
|
*/
|
|
111
127
|
refreshToken(): Promise<boolean>;
|
|
112
128
|
/**
|
|
113
|
-
*
|
|
129
|
+
* Manual logout
|
|
114
130
|
*/
|
|
115
131
|
logout(): Promise<void>;
|
|
116
132
|
/**
|
|
117
|
-
*
|
|
133
|
+
* Check if session is active
|
|
118
134
|
*/
|
|
119
|
-
|
|
135
|
+
isActive(): boolean;
|
|
120
136
|
/**
|
|
121
137
|
* Get current state
|
|
122
138
|
*/
|
|
123
139
|
getState(): SessionState;
|
|
124
140
|
/**
|
|
125
|
-
*
|
|
126
|
-
*/
|
|
127
|
-
getConfig(): SessionConfig;
|
|
128
|
-
/**
|
|
129
|
-
* Update config
|
|
141
|
+
* Update config at runtime
|
|
130
142
|
*/
|
|
131
143
|
updateConfig(newConfig: Partial<SessionConfig>): void;
|
|
132
144
|
/**
|
|
@@ -171,48 +183,57 @@ declare function isTokenExpired(token: string, bufferMs?: number): boolean;
|
|
|
171
183
|
*/
|
|
172
184
|
declare function getTimeUntilExpiry(token: string): number;
|
|
173
185
|
/**
|
|
174
|
-
* Extract token from storage
|
|
186
|
+
* Extract token from storage (access token)
|
|
175
187
|
*/
|
|
176
188
|
declare function getStoredToken(): string | null;
|
|
189
|
+
/**
|
|
190
|
+
* Extract refresh token from storage
|
|
191
|
+
*/
|
|
192
|
+
declare function getStoredRefreshToken(): string | null;
|
|
177
193
|
/**
|
|
178
194
|
* Store token in appropriate storage
|
|
179
195
|
*/
|
|
180
196
|
declare function storeToken(token: string, persistent?: boolean): void;
|
|
181
197
|
/**
|
|
182
|
-
*
|
|
198
|
+
* Store refresh token in appropriate storage
|
|
199
|
+
*/
|
|
200
|
+
declare function storeRefreshToken(token: string, persistent?: boolean): void;
|
|
201
|
+
/**
|
|
202
|
+
* Clear all tokens from storage
|
|
183
203
|
*/
|
|
184
204
|
declare function clearToken(): void;
|
|
185
205
|
/**
|
|
186
|
-
* Validate token structure
|
|
206
|
+
* Validate token structure
|
|
187
207
|
*/
|
|
188
|
-
declare function validateToken(token: string):
|
|
189
|
-
valid: boolean;
|
|
190
|
-
error?: string;
|
|
191
|
-
};
|
|
208
|
+
declare function validateToken(token: string): boolean;
|
|
192
209
|
|
|
193
210
|
/**
|
|
194
211
|
* useSessionTimeout Hook
|
|
195
|
-
* React hook for session timeout
|
|
212
|
+
* React hook for managing session timeout
|
|
196
213
|
*/
|
|
197
214
|
|
|
198
|
-
interface UseSessionTimeoutOptions {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
215
|
+
interface UseSessionTimeoutOptions extends Partial<SessionConfig> {
|
|
216
|
+
/**
|
|
217
|
+
* Optional login response from your authentication endpoint
|
|
218
|
+
* Used to extract tokens from response if provided
|
|
219
|
+
*/
|
|
220
|
+
loginResponse?: any;
|
|
221
|
+
/**
|
|
222
|
+
* Whether to automatically initialize on mount
|
|
223
|
+
*/
|
|
224
|
+
autoInit?: boolean;
|
|
205
225
|
}
|
|
206
226
|
declare function useSessionTimeout(options?: UseSessionTimeoutOptions): {
|
|
207
|
-
|
|
208
|
-
|
|
227
|
+
isActive: boolean;
|
|
228
|
+
isIdle: boolean;
|
|
229
|
+
isRefreshing: boolean;
|
|
230
|
+
refreshAttempts: number;
|
|
231
|
+
timeUntilTimeout: number;
|
|
209
232
|
timeUntilIdle: number;
|
|
210
|
-
|
|
211
|
-
extendSession: () => void;
|
|
233
|
+
error: Error;
|
|
212
234
|
refreshToken: () => Promise<boolean>;
|
|
213
235
|
logout: () => Promise<void>;
|
|
214
|
-
|
|
215
|
-
manager: SessionManager;
|
|
236
|
+
dismissIdleWarning: () => void;
|
|
216
237
|
};
|
|
217
238
|
|
|
218
239
|
/**
|
|
@@ -221,4 +242,4 @@ declare function useSessionTimeout(options?: UseSessionTimeoutOptions): {
|
|
|
221
242
|
*/
|
|
222
243
|
declare function SessionStatus(): react_jsx_runtime.JSX.Element;
|
|
223
244
|
|
|
224
|
-
export { DEFAULT_SESSION_CONFIG, JWTPayload, RefreshTokenResponse, SessionConfig, SessionManager, SessionState, SessionStatus, TokenInfo, UseSessionTimeoutOptions, clearToken, getSessionManager, getStoredToken, getTimeUntilExpiry, getTokenInfo, isTokenExpired, resetSessionManager, storeToken, useSessionTimeout, validateToken };
|
|
245
|
+
export { DEFAULT_SESSION_CONFIG, JWTPayload, RefreshTokenResponse, SessionConfig, SessionManager, SessionState, SessionStatus, TokenInfo, UseSessionTimeoutOptions, clearToken, getSessionManager, getStoredRefreshToken, getStoredToken, getTimeUntilExpiry, getTokenInfo, isTokenExpired, resetSessionManager, storeRefreshToken, storeToken, useSessionTimeout, validateToken };
|
package/dist/index.js
CHANGED
|
@@ -24,11 +24,13 @@ __export(session_timeout_exports, {
|
|
|
24
24
|
SessionStatus: () => SessionStatus,
|
|
25
25
|
clearToken: () => clearToken,
|
|
26
26
|
getSessionManager: () => getSessionManager,
|
|
27
|
+
getStoredRefreshToken: () => getStoredRefreshToken,
|
|
27
28
|
getStoredToken: () => getStoredToken,
|
|
28
29
|
getTimeUntilExpiry: () => getTimeUntilExpiry,
|
|
29
30
|
getTokenInfo: () => getTokenInfo,
|
|
30
31
|
isTokenExpired: () => isTokenExpired,
|
|
31
32
|
resetSessionManager: () => resetSessionManager,
|
|
33
|
+
storeRefreshToken: () => storeRefreshToken,
|
|
32
34
|
storeToken: () => storeToken,
|
|
33
35
|
useSessionTimeout: () => useSessionTimeout,
|
|
34
36
|
validateToken: () => validateToken
|
|
@@ -47,11 +49,17 @@ var DEFAULT_SESSION_CONFIG = {
|
|
|
47
49
|
// 8 hours max
|
|
48
50
|
refreshEndpoint: "/auth/refresh/",
|
|
49
51
|
logoutEndpoint: "/auth/logout/",
|
|
52
|
+
trackIdleTime: true,
|
|
50
53
|
showIdleWarning: true,
|
|
51
54
|
idleWarningThresholdMs: 2 * 60 * 1e3,
|
|
52
55
|
// 2 minutes before idle timeout
|
|
53
56
|
autoRefresh: true,
|
|
54
|
-
debug: false
|
|
57
|
+
debug: false,
|
|
58
|
+
accessTokenField: "access",
|
|
59
|
+
refreshTokenField: "refresh",
|
|
60
|
+
refreshAccessTokenField: "access",
|
|
61
|
+
refreshRefreshTokenField: "refresh",
|
|
62
|
+
storeRefreshToken: true
|
|
55
63
|
};
|
|
56
64
|
|
|
57
65
|
// services/tokenService.ts
|
|
@@ -101,34 +109,25 @@ function getTimeUntilExpiry(token) {
|
|
|
101
109
|
function getStoredToken() {
|
|
102
110
|
return sessionStorage.getItem("authToken") || localStorage.getItem("authToken");
|
|
103
111
|
}
|
|
112
|
+
function getStoredRefreshToken() {
|
|
113
|
+
return sessionStorage.getItem("refreshToken") || localStorage.getItem("refreshToken");
|
|
114
|
+
}
|
|
104
115
|
function storeToken(token, persistent = false) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
116
|
+
const storage = persistent ? localStorage : sessionStorage;
|
|
117
|
+
storage.setItem("authToken", token);
|
|
118
|
+
}
|
|
119
|
+
function storeRefreshToken(token, persistent = false) {
|
|
120
|
+
const storage = persistent ? localStorage : sessionStorage;
|
|
121
|
+
storage.setItem("refreshToken", token);
|
|
112
122
|
}
|
|
113
123
|
function clearToken() {
|
|
114
124
|
sessionStorage.removeItem("authToken");
|
|
115
125
|
localStorage.removeItem("authToken");
|
|
126
|
+
sessionStorage.removeItem("refreshToken");
|
|
127
|
+
localStorage.removeItem("refreshToken");
|
|
116
128
|
}
|
|
117
129
|
function validateToken(token) {
|
|
118
|
-
|
|
119
|
-
return { valid: false, error: "Token is missing or invalid" };
|
|
120
|
-
}
|
|
121
|
-
const payload = decodeJWT(token);
|
|
122
|
-
if (!payload) {
|
|
123
|
-
return { valid: false, error: "Failed to decode token" };
|
|
124
|
-
}
|
|
125
|
-
if (!payload.exp) {
|
|
126
|
-
return { valid: false, error: "Token missing expiry time" };
|
|
127
|
-
}
|
|
128
|
-
if (isTokenExpired(token)) {
|
|
129
|
-
return { valid: false, error: "Token has expired" };
|
|
130
|
-
}
|
|
131
|
-
return { valid: true };
|
|
130
|
+
return decodeJWT(token) !== null;
|
|
132
131
|
}
|
|
133
132
|
|
|
134
133
|
// services/sessionManager.ts
|
|
@@ -149,6 +148,20 @@ function defaultFetchClient() {
|
|
|
149
148
|
}
|
|
150
149
|
};
|
|
151
150
|
}
|
|
151
|
+
function extractTokenFromResponse(response, fieldName = "access") {
|
|
152
|
+
if (!response)
|
|
153
|
+
return null;
|
|
154
|
+
const parts = fieldName.split(".");
|
|
155
|
+
let value = response;
|
|
156
|
+
for (const part of parts) {
|
|
157
|
+
if (value && typeof value === "object") {
|
|
158
|
+
value = value[part];
|
|
159
|
+
} else {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return typeof value === "string" ? value : null;
|
|
164
|
+
}
|
|
152
165
|
var SessionManager = class {
|
|
153
166
|
constructor(config) {
|
|
154
167
|
this.refreshTimer = null;
|
|
@@ -171,17 +184,29 @@ var SessionManager = class {
|
|
|
171
184
|
this.log("SessionManager initialized with config:", this.config);
|
|
172
185
|
}
|
|
173
186
|
/**
|
|
174
|
-
* Initialize session management
|
|
187
|
+
* Initialize session management with optional login response
|
|
188
|
+
* Extracts and stores tokens from login response using configured field names
|
|
175
189
|
*/
|
|
176
|
-
init() {
|
|
177
|
-
const token = getStoredToken();
|
|
190
|
+
init(loginResponse) {
|
|
191
|
+
const token = loginResponse ? extractTokenFromResponse(loginResponse, this.config.accessTokenField || "access") : getStoredToken();
|
|
178
192
|
if (!token) {
|
|
179
193
|
this.log("No token found, session not initialized");
|
|
180
194
|
return;
|
|
181
195
|
}
|
|
196
|
+
storeToken(token);
|
|
197
|
+
if (this.config.storeRefreshToken && loginResponse) {
|
|
198
|
+
const refreshToken = extractTokenFromResponse(
|
|
199
|
+
loginResponse,
|
|
200
|
+
this.config.refreshTokenField || "refresh"
|
|
201
|
+
);
|
|
202
|
+
if (refreshToken) {
|
|
203
|
+
storeRefreshToken(refreshToken);
|
|
204
|
+
this.log("Refresh token stored");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
182
207
|
const validation = validateToken(token);
|
|
183
|
-
if (!validation
|
|
184
|
-
this.log("Invalid token
|
|
208
|
+
if (!validation) {
|
|
209
|
+
this.log("Invalid token");
|
|
185
210
|
this.logout();
|
|
186
211
|
return;
|
|
187
212
|
}
|
|
@@ -201,43 +226,33 @@ var SessionManager = class {
|
|
|
201
226
|
return;
|
|
202
227
|
const timeUntilExpiry = getTimeUntilExpiry(token);
|
|
203
228
|
const refreshAt = Math.max(0, timeUntilExpiry - this.config.refreshThresholdMs);
|
|
204
|
-
this.log(`Token expires in ${timeUntilExpiry}ms, will refresh in ${refreshAt}ms`);
|
|
205
229
|
if (this.refreshTimer) {
|
|
206
230
|
clearTimeout(this.refreshTimer);
|
|
207
231
|
}
|
|
208
|
-
|
|
209
|
-
this.
|
|
210
|
-
this.log("Scheduled token refresh triggered");
|
|
211
|
-
this.refreshToken();
|
|
212
|
-
}, refreshAt);
|
|
213
|
-
} else if (refreshAt <= 0 && this.config.autoRefresh) {
|
|
214
|
-
this.log("Token near expiry, refreshing immediately");
|
|
232
|
+
this.refreshTimer = setTimeout(() => {
|
|
233
|
+
this.log("Token refresh threshold reached, triggering refresh");
|
|
215
234
|
this.refreshToken();
|
|
216
|
-
}
|
|
235
|
+
}, refreshAt);
|
|
236
|
+
this.log(`Token refresh scheduled in ${refreshAt}ms`);
|
|
217
237
|
}
|
|
218
238
|
/**
|
|
219
|
-
* Setup idle activity
|
|
239
|
+
* Setup idle tracking with activity listeners
|
|
220
240
|
*/
|
|
221
241
|
setupIdleTracking() {
|
|
222
|
-
|
|
242
|
+
if (!this.config.trackIdleTime)
|
|
243
|
+
return;
|
|
223
244
|
const handleActivity = () => {
|
|
224
245
|
this.state.lastActivityTime = Date.now();
|
|
225
246
|
this.state.isIdle = false;
|
|
226
247
|
this.emit("activity");
|
|
227
|
-
if (this.idleTimer) {
|
|
228
|
-
clearTimeout(this.idleTimer);
|
|
229
|
-
}
|
|
230
|
-
this.idleTimer = setTimeout(() => {
|
|
231
|
-
var _a, _b;
|
|
232
|
-
this.state.isIdle = true;
|
|
233
|
-
this.emit("idle");
|
|
234
|
-
this.log("User idle timeout triggered");
|
|
235
|
-
(_b = (_a = this.config).onIdle) == null ? void 0 : _b.call(_a);
|
|
236
|
-
}, this.config.idleTimeoutMs);
|
|
237
248
|
};
|
|
249
|
+
const events = ["mousedown", "keydown", "scroll", "touchstart", "click"];
|
|
238
250
|
events.forEach((event) => {
|
|
239
|
-
window.addEventListener(event, handleActivity,
|
|
251
|
+
window.addEventListener(event, handleActivity, true);
|
|
240
252
|
});
|
|
253
|
+
if (this.idleTimer) {
|
|
254
|
+
clearTimeout(this.idleTimer);
|
|
255
|
+
}
|
|
241
256
|
this.idleTimer = setTimeout(() => {
|
|
242
257
|
var _a, _b;
|
|
243
258
|
this.state.isIdle = true;
|
|
@@ -269,7 +284,7 @@ var SessionManager = class {
|
|
|
269
284
|
}, this.config.maxSessionDurationMs);
|
|
270
285
|
}
|
|
271
286
|
/**
|
|
272
|
-
* Refresh token
|
|
287
|
+
* Refresh token using stored refresh token
|
|
273
288
|
*/
|
|
274
289
|
async refreshToken() {
|
|
275
290
|
var _a, _b, _c, _d;
|
|
@@ -279,23 +294,49 @@ var SessionManager = class {
|
|
|
279
294
|
}
|
|
280
295
|
this.state.isRefreshing = true;
|
|
281
296
|
this.state.refreshAttempts++;
|
|
282
|
-
const
|
|
283
|
-
|
|
297
|
+
const accessToken = getStoredToken();
|
|
298
|
+
const refreshToken = getStoredRefreshToken();
|
|
299
|
+
if (!accessToken && !refreshToken) {
|
|
284
300
|
this.state.isRefreshing = false;
|
|
285
301
|
return false;
|
|
286
302
|
}
|
|
287
303
|
try {
|
|
288
304
|
this.log(`Refreshing token (attempt ${this.state.refreshAttempts})`);
|
|
289
305
|
const client = this.config.httpClient || defaultFetchClient();
|
|
290
|
-
const refreshPayload = this.config.refreshPayloadFormatter ? this.config.refreshPayloadFormatter(
|
|
291
|
-
|
|
306
|
+
const refreshPayload = this.config.refreshPayloadFormatter ? this.config.refreshPayloadFormatter(accessToken || "", refreshToken || "") : {
|
|
307
|
+
refresh: refreshToken || accessToken,
|
|
308
|
+
token: accessToken
|
|
309
|
+
};
|
|
310
|
+
const refreshPromise = client.post(
|
|
311
|
+
this.config.refreshEndpoint,
|
|
312
|
+
refreshPayload,
|
|
313
|
+
{
|
|
314
|
+
headers: {
|
|
315
|
+
Authorization: `Bearer ${accessToken || refreshToken}`
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
);
|
|
292
319
|
this.requestDeduplication.set("refresh", refreshPromise);
|
|
293
320
|
const response = await refreshPromise;
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
321
|
+
const responseData = response.data;
|
|
322
|
+
const newAccessToken = extractTokenFromResponse(
|
|
323
|
+
responseData,
|
|
324
|
+
this.config.refreshAccessTokenField || "access"
|
|
325
|
+
);
|
|
326
|
+
if (!newAccessToken) {
|
|
327
|
+
throw new Error("No access token in refresh response");
|
|
328
|
+
}
|
|
329
|
+
storeToken(newAccessToken);
|
|
330
|
+
if (this.config.storeRefreshToken) {
|
|
331
|
+
const newRefreshToken = extractTokenFromResponse(
|
|
332
|
+
responseData,
|
|
333
|
+
this.config.refreshRefreshTokenField || "refresh"
|
|
334
|
+
);
|
|
335
|
+
if (newRefreshToken) {
|
|
336
|
+
storeRefreshToken(newRefreshToken);
|
|
337
|
+
this.log("Refresh token updated");
|
|
338
|
+
}
|
|
297
339
|
}
|
|
298
|
-
storeToken(newToken);
|
|
299
340
|
this.state.isRefreshing = false;
|
|
300
341
|
this.state.refreshAttempts = 0;
|
|
301
342
|
this.requestDeduplication.delete("refresh");
|
|
@@ -317,41 +358,34 @@ var SessionManager = class {
|
|
|
317
358
|
}
|
|
318
359
|
}
|
|
319
360
|
/**
|
|
320
|
-
*
|
|
361
|
+
* Manual logout
|
|
321
362
|
*/
|
|
322
363
|
async logout() {
|
|
323
364
|
var _a, _b;
|
|
365
|
+
this.log("Logging out");
|
|
324
366
|
try {
|
|
325
367
|
const token = getStoredToken();
|
|
326
|
-
if (token) {
|
|
368
|
+
if (token && this.config.logoutEndpoint) {
|
|
327
369
|
const client = this.config.httpClient || defaultFetchClient();
|
|
328
370
|
const logoutPayload = this.config.logoutPayloadFormatter ? this.config.logoutPayloadFormatter(token) : { token };
|
|
329
|
-
await client.post(this.config.logoutEndpoint, logoutPayload, {
|
|
371
|
+
await client.post(this.config.logoutEndpoint, logoutPayload, {
|
|
372
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
373
|
+
});
|
|
330
374
|
}
|
|
331
375
|
} catch (error) {
|
|
332
|
-
this.log("Logout
|
|
376
|
+
this.log("Logout request failed:", error);
|
|
333
377
|
}
|
|
334
|
-
this.cleanup();
|
|
335
|
-
this.state.isActive = false;
|
|
336
|
-
this.emit("loggedOut");
|
|
337
|
-
(_b = (_a = this.config).onSessionExpired) == null ? void 0 : _b.call(_a);
|
|
338
378
|
clearToken();
|
|
379
|
+
this.state.isActive = false;
|
|
380
|
+
this.emit("logout");
|
|
381
|
+
(_b = (_a = this.config).onLogout) == null ? void 0 : _b.call(_a);
|
|
382
|
+
this.cleanup();
|
|
339
383
|
}
|
|
340
384
|
/**
|
|
341
|
-
*
|
|
385
|
+
* Check if session is active
|
|
342
386
|
*/
|
|
343
|
-
|
|
344
|
-
this.state.
|
|
345
|
-
this.state.isIdle = false;
|
|
346
|
-
if (this.idleTimer) {
|
|
347
|
-
clearTimeout(this.idleTimer);
|
|
348
|
-
}
|
|
349
|
-
this.idleTimer = setTimeout(() => {
|
|
350
|
-
this.state.isIdle = true;
|
|
351
|
-
this.emit("idle");
|
|
352
|
-
}, this.config.idleTimeoutMs);
|
|
353
|
-
this.log("Session extended");
|
|
354
|
-
this.emit("sessionExtended");
|
|
387
|
+
isActive() {
|
|
388
|
+
return this.state.isActive;
|
|
355
389
|
}
|
|
356
390
|
/**
|
|
357
391
|
* Get current state
|
|
@@ -360,13 +394,7 @@ var SessionManager = class {
|
|
|
360
394
|
return { ...this.state };
|
|
361
395
|
}
|
|
362
396
|
/**
|
|
363
|
-
*
|
|
364
|
-
*/
|
|
365
|
-
getConfig() {
|
|
366
|
-
return { ...this.config };
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Update config
|
|
397
|
+
* Update config at runtime
|
|
370
398
|
*/
|
|
371
399
|
updateConfig(newConfig) {
|
|
372
400
|
this.config = { ...this.config, ...newConfig };
|
|
@@ -459,119 +487,97 @@ function resetSessionManager() {
|
|
|
459
487
|
// hooks/useSessionTimeout.ts
|
|
460
488
|
var import_react = require("react");
|
|
461
489
|
function useSessionTimeout(options = {}) {
|
|
462
|
-
const {
|
|
463
|
-
config,
|
|
464
|
-
enabled = true,
|
|
465
|
-
onSessionExpiring,
|
|
466
|
-
onSessionExpired,
|
|
467
|
-
onIdle,
|
|
468
|
-
onRefreshSuccess
|
|
469
|
-
} = options;
|
|
470
|
-
const [sessionState, setSessionState] = (0, import_react.useState)({
|
|
490
|
+
const [state, setState] = (0, import_react.useState)({
|
|
471
491
|
isActive: false,
|
|
472
492
|
isIdle: false,
|
|
473
493
|
lastActivityTime: Date.now(),
|
|
474
494
|
refreshAttempts: 0,
|
|
475
495
|
isRefreshing: false
|
|
476
496
|
});
|
|
477
|
-
const [
|
|
478
|
-
const [
|
|
497
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
498
|
+
const [timeUntilTimeout, setTimeUntilTimeout] = (0, import_react.useState)(0);
|
|
499
|
+
const [timeUntilIdle, setTimeUntilIdle] = (0, import_react.useState)(0);
|
|
479
500
|
const managerRef = (0, import_react.useRef)(null);
|
|
480
|
-
const
|
|
501
|
+
const updateTimerRef = (0, import_react.useRef)(null);
|
|
481
502
|
(0, import_react.useEffect)(() => {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
const manager = getSessionManager({
|
|
485
|
-
...config,
|
|
486
|
-
onSessionExpiring,
|
|
487
|
-
onSessionExpired,
|
|
488
|
-
onIdle,
|
|
489
|
-
onRefreshSuccess
|
|
490
|
-
});
|
|
503
|
+
const { loginResponse, autoInit, ...config } = options;
|
|
504
|
+
const manager = getSessionManager(config);
|
|
491
505
|
managerRef.current = manager;
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
);
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
);
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
);
|
|
518
|
-
subscriptions.set(
|
|
519
|
-
"idleWarning",
|
|
520
|
-
manager.on("idleWarning", (data) => {
|
|
521
|
-
setIdleWarningVisible(true);
|
|
522
|
-
setTimeUntilIdle(data.timeRemaining);
|
|
523
|
-
})
|
|
524
|
-
);
|
|
525
|
-
subscriptions.set(
|
|
526
|
-
"loggedOut",
|
|
527
|
-
manager.on("loggedOut", () => {
|
|
528
|
-
setSessionState(manager.getState());
|
|
529
|
-
})
|
|
530
|
-
);
|
|
531
|
-
unsubscribeRef.current = subscriptions;
|
|
532
|
-
manager.init();
|
|
506
|
+
const unsubscribeInitialized = manager.on("initialized", () => {
|
|
507
|
+
setState(manager.getState());
|
|
508
|
+
});
|
|
509
|
+
const unsubscribeTokenRefreshed = manager.on("tokenRefreshed", () => {
|
|
510
|
+
setState(manager.getState());
|
|
511
|
+
});
|
|
512
|
+
const unsubscribeRefreshFailed = manager.on("refreshFailed", (error2) => {
|
|
513
|
+
setError(error2);
|
|
514
|
+
setState(manager.getState());
|
|
515
|
+
});
|
|
516
|
+
const unsubscribeLogout = manager.on("logout", () => {
|
|
517
|
+
setState(manager.getState());
|
|
518
|
+
});
|
|
519
|
+
const unsubscribeIdle = manager.on("idle", () => {
|
|
520
|
+
setState(manager.getState());
|
|
521
|
+
});
|
|
522
|
+
const unsubscribeIdleWarning = manager.on("idleWarning", (data) => {
|
|
523
|
+
setTimeUntilIdle(data.timeRemaining);
|
|
524
|
+
});
|
|
525
|
+
const unsubscribeActivity = manager.on("activity", () => {
|
|
526
|
+
setTimeUntilIdle(0);
|
|
527
|
+
});
|
|
528
|
+
if (autoInit !== false && (autoInit === true || loginResponse)) {
|
|
529
|
+
manager.init(loginResponse);
|
|
530
|
+
}
|
|
533
531
|
return () => {
|
|
534
|
-
|
|
535
|
-
|
|
532
|
+
unsubscribeInitialized();
|
|
533
|
+
unsubscribeTokenRefreshed();
|
|
534
|
+
unsubscribeRefreshFailed();
|
|
535
|
+
unsubscribeLogout();
|
|
536
|
+
unsubscribeIdle();
|
|
537
|
+
unsubscribeIdleWarning();
|
|
538
|
+
unsubscribeActivity();
|
|
536
539
|
};
|
|
537
|
-
}, [
|
|
540
|
+
}, [options]);
|
|
538
541
|
(0, import_react.useEffect)(() => {
|
|
539
|
-
|
|
542
|
+
var _a;
|
|
543
|
+
if (!((_a = managerRef.current) == null ? void 0 : _a.isActive())) {
|
|
540
544
|
return;
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
+
}
|
|
546
|
+
const updateCountdown = () => {
|
|
547
|
+
setState(managerRef.current.getState());
|
|
548
|
+
};
|
|
549
|
+
updateTimerRef.current = setInterval(updateCountdown, 1e3);
|
|
550
|
+
return () => {
|
|
551
|
+
if (updateTimerRef.current) {
|
|
552
|
+
clearInterval(updateTimerRef.current);
|
|
545
553
|
}
|
|
546
|
-
}
|
|
547
|
-
return () => clearInterval(interval);
|
|
548
|
-
}, [enabled, sessionState.isActive]);
|
|
549
|
-
const extendSession = (0, import_react.useCallback)(() => {
|
|
550
|
-
var _a;
|
|
551
|
-
(_a = managerRef.current) == null ? void 0 : _a.extendSession();
|
|
554
|
+
};
|
|
552
555
|
}, []);
|
|
553
556
|
const refreshToken = (0, import_react.useCallback)(async () => {
|
|
554
|
-
|
|
555
|
-
|
|
557
|
+
if (managerRef.current) {
|
|
558
|
+
return managerRef.current.refreshToken();
|
|
559
|
+
}
|
|
560
|
+
return false;
|
|
556
561
|
}, []);
|
|
557
562
|
const logout = (0, import_react.useCallback)(async () => {
|
|
558
|
-
|
|
559
|
-
|
|
563
|
+
if (managerRef.current) {
|
|
564
|
+
return managerRef.current.logout();
|
|
565
|
+
}
|
|
560
566
|
}, []);
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
(_a = managerRef.current) == null ? void 0 : _a.updateConfig(newConfig);
|
|
567
|
+
const dismissIdleWarning = (0, import_react.useCallback)(() => {
|
|
568
|
+
setTimeUntilIdle(0);
|
|
564
569
|
}, []);
|
|
565
570
|
return {
|
|
566
|
-
|
|
567
|
-
|
|
571
|
+
isActive: state.isActive,
|
|
572
|
+
isIdle: state.isIdle,
|
|
573
|
+
isRefreshing: state.isRefreshing,
|
|
574
|
+
refreshAttempts: state.refreshAttempts,
|
|
575
|
+
timeUntilTimeout,
|
|
568
576
|
timeUntilIdle,
|
|
569
|
-
|
|
570
|
-
extendSession,
|
|
577
|
+
error,
|
|
571
578
|
refreshToken,
|
|
572
579
|
logout,
|
|
573
|
-
|
|
574
|
-
manager: managerRef.current
|
|
580
|
+
dismissIdleWarning
|
|
575
581
|
};
|
|
576
582
|
}
|
|
577
583
|
|
|
@@ -623,11 +629,13 @@ function SessionStatus() {
|
|
|
623
629
|
SessionStatus,
|
|
624
630
|
clearToken,
|
|
625
631
|
getSessionManager,
|
|
632
|
+
getStoredRefreshToken,
|
|
626
633
|
getStoredToken,
|
|
627
634
|
getTimeUntilExpiry,
|
|
628
635
|
getTokenInfo,
|
|
629
636
|
isTokenExpired,
|
|
630
637
|
resetSessionManager,
|
|
638
|
+
storeRefreshToken,
|
|
631
639
|
storeToken,
|
|
632
640
|
useSessionTimeout,
|
|
633
641
|
validateToken
|
package/dist/index.mjs
CHANGED
|
@@ -10,11 +10,17 @@ var DEFAULT_SESSION_CONFIG = {
|
|
|
10
10
|
// 8 hours max
|
|
11
11
|
refreshEndpoint: "/auth/refresh/",
|
|
12
12
|
logoutEndpoint: "/auth/logout/",
|
|
13
|
+
trackIdleTime: true,
|
|
13
14
|
showIdleWarning: true,
|
|
14
15
|
idleWarningThresholdMs: 2 * 60 * 1e3,
|
|
15
16
|
// 2 minutes before idle timeout
|
|
16
17
|
autoRefresh: true,
|
|
17
|
-
debug: false
|
|
18
|
+
debug: false,
|
|
19
|
+
accessTokenField: "access",
|
|
20
|
+
refreshTokenField: "refresh",
|
|
21
|
+
refreshAccessTokenField: "access",
|
|
22
|
+
refreshRefreshTokenField: "refresh",
|
|
23
|
+
storeRefreshToken: true
|
|
18
24
|
};
|
|
19
25
|
|
|
20
26
|
// services/tokenService.ts
|
|
@@ -64,34 +70,25 @@ function getTimeUntilExpiry(token) {
|
|
|
64
70
|
function getStoredToken() {
|
|
65
71
|
return sessionStorage.getItem("authToken") || localStorage.getItem("authToken");
|
|
66
72
|
}
|
|
73
|
+
function getStoredRefreshToken() {
|
|
74
|
+
return sessionStorage.getItem("refreshToken") || localStorage.getItem("refreshToken");
|
|
75
|
+
}
|
|
67
76
|
function storeToken(token, persistent = false) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
77
|
+
const storage = persistent ? localStorage : sessionStorage;
|
|
78
|
+
storage.setItem("authToken", token);
|
|
79
|
+
}
|
|
80
|
+
function storeRefreshToken(token, persistent = false) {
|
|
81
|
+
const storage = persistent ? localStorage : sessionStorage;
|
|
82
|
+
storage.setItem("refreshToken", token);
|
|
75
83
|
}
|
|
76
84
|
function clearToken() {
|
|
77
85
|
sessionStorage.removeItem("authToken");
|
|
78
86
|
localStorage.removeItem("authToken");
|
|
87
|
+
sessionStorage.removeItem("refreshToken");
|
|
88
|
+
localStorage.removeItem("refreshToken");
|
|
79
89
|
}
|
|
80
90
|
function validateToken(token) {
|
|
81
|
-
|
|
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 };
|
|
91
|
+
return decodeJWT(token) !== null;
|
|
95
92
|
}
|
|
96
93
|
|
|
97
94
|
// services/sessionManager.ts
|
|
@@ -112,6 +109,20 @@ function defaultFetchClient() {
|
|
|
112
109
|
}
|
|
113
110
|
};
|
|
114
111
|
}
|
|
112
|
+
function extractTokenFromResponse(response, fieldName = "access") {
|
|
113
|
+
if (!response)
|
|
114
|
+
return null;
|
|
115
|
+
const parts = fieldName.split(".");
|
|
116
|
+
let value = response;
|
|
117
|
+
for (const part of parts) {
|
|
118
|
+
if (value && typeof value === "object") {
|
|
119
|
+
value = value[part];
|
|
120
|
+
} else {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return typeof value === "string" ? value : null;
|
|
125
|
+
}
|
|
115
126
|
var SessionManager = class {
|
|
116
127
|
constructor(config) {
|
|
117
128
|
this.refreshTimer = null;
|
|
@@ -134,17 +145,29 @@ var SessionManager = class {
|
|
|
134
145
|
this.log("SessionManager initialized with config:", this.config);
|
|
135
146
|
}
|
|
136
147
|
/**
|
|
137
|
-
* Initialize session management
|
|
148
|
+
* Initialize session management with optional login response
|
|
149
|
+
* Extracts and stores tokens from login response using configured field names
|
|
138
150
|
*/
|
|
139
|
-
init() {
|
|
140
|
-
const token = getStoredToken();
|
|
151
|
+
init(loginResponse) {
|
|
152
|
+
const token = loginResponse ? extractTokenFromResponse(loginResponse, this.config.accessTokenField || "access") : getStoredToken();
|
|
141
153
|
if (!token) {
|
|
142
154
|
this.log("No token found, session not initialized");
|
|
143
155
|
return;
|
|
144
156
|
}
|
|
157
|
+
storeToken(token);
|
|
158
|
+
if (this.config.storeRefreshToken && loginResponse) {
|
|
159
|
+
const refreshToken = extractTokenFromResponse(
|
|
160
|
+
loginResponse,
|
|
161
|
+
this.config.refreshTokenField || "refresh"
|
|
162
|
+
);
|
|
163
|
+
if (refreshToken) {
|
|
164
|
+
storeRefreshToken(refreshToken);
|
|
165
|
+
this.log("Refresh token stored");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
145
168
|
const validation = validateToken(token);
|
|
146
|
-
if (!validation
|
|
147
|
-
this.log("Invalid token
|
|
169
|
+
if (!validation) {
|
|
170
|
+
this.log("Invalid token");
|
|
148
171
|
this.logout();
|
|
149
172
|
return;
|
|
150
173
|
}
|
|
@@ -164,43 +187,33 @@ var SessionManager = class {
|
|
|
164
187
|
return;
|
|
165
188
|
const timeUntilExpiry = getTimeUntilExpiry(token);
|
|
166
189
|
const refreshAt = Math.max(0, timeUntilExpiry - this.config.refreshThresholdMs);
|
|
167
|
-
this.log(`Token expires in ${timeUntilExpiry}ms, will refresh in ${refreshAt}ms`);
|
|
168
190
|
if (this.refreshTimer) {
|
|
169
191
|
clearTimeout(this.refreshTimer);
|
|
170
192
|
}
|
|
171
|
-
|
|
172
|
-
this.
|
|
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");
|
|
193
|
+
this.refreshTimer = setTimeout(() => {
|
|
194
|
+
this.log("Token refresh threshold reached, triggering refresh");
|
|
178
195
|
this.refreshToken();
|
|
179
|
-
}
|
|
196
|
+
}, refreshAt);
|
|
197
|
+
this.log(`Token refresh scheduled in ${refreshAt}ms`);
|
|
180
198
|
}
|
|
181
199
|
/**
|
|
182
|
-
* Setup idle activity
|
|
200
|
+
* Setup idle tracking with activity listeners
|
|
183
201
|
*/
|
|
184
202
|
setupIdleTracking() {
|
|
185
|
-
|
|
203
|
+
if (!this.config.trackIdleTime)
|
|
204
|
+
return;
|
|
186
205
|
const handleActivity = () => {
|
|
187
206
|
this.state.lastActivityTime = Date.now();
|
|
188
207
|
this.state.isIdle = false;
|
|
189
208
|
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
209
|
};
|
|
210
|
+
const events = ["mousedown", "keydown", "scroll", "touchstart", "click"];
|
|
201
211
|
events.forEach((event) => {
|
|
202
|
-
window.addEventListener(event, handleActivity,
|
|
212
|
+
window.addEventListener(event, handleActivity, true);
|
|
203
213
|
});
|
|
214
|
+
if (this.idleTimer) {
|
|
215
|
+
clearTimeout(this.idleTimer);
|
|
216
|
+
}
|
|
204
217
|
this.idleTimer = setTimeout(() => {
|
|
205
218
|
var _a, _b;
|
|
206
219
|
this.state.isIdle = true;
|
|
@@ -232,7 +245,7 @@ var SessionManager = class {
|
|
|
232
245
|
}, this.config.maxSessionDurationMs);
|
|
233
246
|
}
|
|
234
247
|
/**
|
|
235
|
-
* Refresh token
|
|
248
|
+
* Refresh token using stored refresh token
|
|
236
249
|
*/
|
|
237
250
|
async refreshToken() {
|
|
238
251
|
var _a, _b, _c, _d;
|
|
@@ -242,23 +255,49 @@ var SessionManager = class {
|
|
|
242
255
|
}
|
|
243
256
|
this.state.isRefreshing = true;
|
|
244
257
|
this.state.refreshAttempts++;
|
|
245
|
-
const
|
|
246
|
-
|
|
258
|
+
const accessToken = getStoredToken();
|
|
259
|
+
const refreshToken = getStoredRefreshToken();
|
|
260
|
+
if (!accessToken && !refreshToken) {
|
|
247
261
|
this.state.isRefreshing = false;
|
|
248
262
|
return false;
|
|
249
263
|
}
|
|
250
264
|
try {
|
|
251
265
|
this.log(`Refreshing token (attempt ${this.state.refreshAttempts})`);
|
|
252
266
|
const client = this.config.httpClient || defaultFetchClient();
|
|
253
|
-
const refreshPayload = this.config.refreshPayloadFormatter ? this.config.refreshPayloadFormatter(
|
|
254
|
-
|
|
267
|
+
const refreshPayload = this.config.refreshPayloadFormatter ? this.config.refreshPayloadFormatter(accessToken || "", refreshToken || "") : {
|
|
268
|
+
refresh: refreshToken || accessToken,
|
|
269
|
+
token: accessToken
|
|
270
|
+
};
|
|
271
|
+
const refreshPromise = client.post(
|
|
272
|
+
this.config.refreshEndpoint,
|
|
273
|
+
refreshPayload,
|
|
274
|
+
{
|
|
275
|
+
headers: {
|
|
276
|
+
Authorization: `Bearer ${accessToken || refreshToken}`
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
);
|
|
255
280
|
this.requestDeduplication.set("refresh", refreshPromise);
|
|
256
281
|
const response = await refreshPromise;
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
282
|
+
const responseData = response.data;
|
|
283
|
+
const newAccessToken = extractTokenFromResponse(
|
|
284
|
+
responseData,
|
|
285
|
+
this.config.refreshAccessTokenField || "access"
|
|
286
|
+
);
|
|
287
|
+
if (!newAccessToken) {
|
|
288
|
+
throw new Error("No access token in refresh response");
|
|
289
|
+
}
|
|
290
|
+
storeToken(newAccessToken);
|
|
291
|
+
if (this.config.storeRefreshToken) {
|
|
292
|
+
const newRefreshToken = extractTokenFromResponse(
|
|
293
|
+
responseData,
|
|
294
|
+
this.config.refreshRefreshTokenField || "refresh"
|
|
295
|
+
);
|
|
296
|
+
if (newRefreshToken) {
|
|
297
|
+
storeRefreshToken(newRefreshToken);
|
|
298
|
+
this.log("Refresh token updated");
|
|
299
|
+
}
|
|
260
300
|
}
|
|
261
|
-
storeToken(newToken);
|
|
262
301
|
this.state.isRefreshing = false;
|
|
263
302
|
this.state.refreshAttempts = 0;
|
|
264
303
|
this.requestDeduplication.delete("refresh");
|
|
@@ -280,41 +319,34 @@ var SessionManager = class {
|
|
|
280
319
|
}
|
|
281
320
|
}
|
|
282
321
|
/**
|
|
283
|
-
*
|
|
322
|
+
* Manual logout
|
|
284
323
|
*/
|
|
285
324
|
async logout() {
|
|
286
325
|
var _a, _b;
|
|
326
|
+
this.log("Logging out");
|
|
287
327
|
try {
|
|
288
328
|
const token = getStoredToken();
|
|
289
|
-
if (token) {
|
|
329
|
+
if (token && this.config.logoutEndpoint) {
|
|
290
330
|
const client = this.config.httpClient || defaultFetchClient();
|
|
291
331
|
const logoutPayload = this.config.logoutPayloadFormatter ? this.config.logoutPayloadFormatter(token) : { token };
|
|
292
|
-
await client.post(this.config.logoutEndpoint, logoutPayload, {
|
|
332
|
+
await client.post(this.config.logoutEndpoint, logoutPayload, {
|
|
333
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
334
|
+
});
|
|
293
335
|
}
|
|
294
336
|
} catch (error) {
|
|
295
|
-
this.log("Logout
|
|
337
|
+
this.log("Logout request failed:", error);
|
|
296
338
|
}
|
|
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
339
|
clearToken();
|
|
340
|
+
this.state.isActive = false;
|
|
341
|
+
this.emit("logout");
|
|
342
|
+
(_b = (_a = this.config).onLogout) == null ? void 0 : _b.call(_a);
|
|
343
|
+
this.cleanup();
|
|
302
344
|
}
|
|
303
345
|
/**
|
|
304
|
-
*
|
|
346
|
+
* Check if session is active
|
|
305
347
|
*/
|
|
306
|
-
|
|
307
|
-
this.state.
|
|
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");
|
|
348
|
+
isActive() {
|
|
349
|
+
return this.state.isActive;
|
|
318
350
|
}
|
|
319
351
|
/**
|
|
320
352
|
* Get current state
|
|
@@ -323,13 +355,7 @@ var SessionManager = class {
|
|
|
323
355
|
return { ...this.state };
|
|
324
356
|
}
|
|
325
357
|
/**
|
|
326
|
-
*
|
|
327
|
-
*/
|
|
328
|
-
getConfig() {
|
|
329
|
-
return { ...this.config };
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
|
-
* Update config
|
|
358
|
+
* Update config at runtime
|
|
333
359
|
*/
|
|
334
360
|
updateConfig(newConfig) {
|
|
335
361
|
this.config = { ...this.config, ...newConfig };
|
|
@@ -422,119 +448,97 @@ function resetSessionManager() {
|
|
|
422
448
|
// hooks/useSessionTimeout.ts
|
|
423
449
|
import { useEffect, useState, useCallback, useRef } from "react";
|
|
424
450
|
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({
|
|
451
|
+
const [state, setState] = useState({
|
|
434
452
|
isActive: false,
|
|
435
453
|
isIdle: false,
|
|
436
454
|
lastActivityTime: Date.now(),
|
|
437
455
|
refreshAttempts: 0,
|
|
438
456
|
isRefreshing: false
|
|
439
457
|
});
|
|
440
|
-
const [
|
|
441
|
-
const [
|
|
458
|
+
const [error, setError] = useState(null);
|
|
459
|
+
const [timeUntilTimeout, setTimeUntilTimeout] = useState(0);
|
|
460
|
+
const [timeUntilIdle, setTimeUntilIdle] = useState(0);
|
|
442
461
|
const managerRef = useRef(null);
|
|
443
|
-
const
|
|
462
|
+
const updateTimerRef = useRef(null);
|
|
444
463
|
useEffect(() => {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const manager = getSessionManager({
|
|
448
|
-
...config,
|
|
449
|
-
onSessionExpiring,
|
|
450
|
-
onSessionExpired,
|
|
451
|
-
onIdle,
|
|
452
|
-
onRefreshSuccess
|
|
453
|
-
});
|
|
464
|
+
const { loginResponse, autoInit, ...config } = options;
|
|
465
|
+
const manager = getSessionManager(config);
|
|
454
466
|
managerRef.current = manager;
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
)
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
);
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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();
|
|
467
|
+
const unsubscribeInitialized = manager.on("initialized", () => {
|
|
468
|
+
setState(manager.getState());
|
|
469
|
+
});
|
|
470
|
+
const unsubscribeTokenRefreshed = manager.on("tokenRefreshed", () => {
|
|
471
|
+
setState(manager.getState());
|
|
472
|
+
});
|
|
473
|
+
const unsubscribeRefreshFailed = manager.on("refreshFailed", (error2) => {
|
|
474
|
+
setError(error2);
|
|
475
|
+
setState(manager.getState());
|
|
476
|
+
});
|
|
477
|
+
const unsubscribeLogout = manager.on("logout", () => {
|
|
478
|
+
setState(manager.getState());
|
|
479
|
+
});
|
|
480
|
+
const unsubscribeIdle = manager.on("idle", () => {
|
|
481
|
+
setState(manager.getState());
|
|
482
|
+
});
|
|
483
|
+
const unsubscribeIdleWarning = manager.on("idleWarning", (data) => {
|
|
484
|
+
setTimeUntilIdle(data.timeRemaining);
|
|
485
|
+
});
|
|
486
|
+
const unsubscribeActivity = manager.on("activity", () => {
|
|
487
|
+
setTimeUntilIdle(0);
|
|
488
|
+
});
|
|
489
|
+
if (autoInit !== false && (autoInit === true || loginResponse)) {
|
|
490
|
+
manager.init(loginResponse);
|
|
491
|
+
}
|
|
496
492
|
return () => {
|
|
497
|
-
|
|
498
|
-
|
|
493
|
+
unsubscribeInitialized();
|
|
494
|
+
unsubscribeTokenRefreshed();
|
|
495
|
+
unsubscribeRefreshFailed();
|
|
496
|
+
unsubscribeLogout();
|
|
497
|
+
unsubscribeIdle();
|
|
498
|
+
unsubscribeIdleWarning();
|
|
499
|
+
unsubscribeActivity();
|
|
499
500
|
};
|
|
500
|
-
}, [
|
|
501
|
+
}, [options]);
|
|
501
502
|
useEffect(() => {
|
|
502
|
-
|
|
503
|
+
var _a;
|
|
504
|
+
if (!((_a = managerRef.current) == null ? void 0 : _a.isActive())) {
|
|
503
505
|
return;
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
506
|
+
}
|
|
507
|
+
const updateCountdown = () => {
|
|
508
|
+
setState(managerRef.current.getState());
|
|
509
|
+
};
|
|
510
|
+
updateTimerRef.current = setInterval(updateCountdown, 1e3);
|
|
511
|
+
return () => {
|
|
512
|
+
if (updateTimerRef.current) {
|
|
513
|
+
clearInterval(updateTimerRef.current);
|
|
508
514
|
}
|
|
509
|
-
}
|
|
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
|
+
};
|
|
515
516
|
}, []);
|
|
516
517
|
const refreshToken = useCallback(async () => {
|
|
517
|
-
|
|
518
|
-
|
|
518
|
+
if (managerRef.current) {
|
|
519
|
+
return managerRef.current.refreshToken();
|
|
520
|
+
}
|
|
521
|
+
return false;
|
|
519
522
|
}, []);
|
|
520
523
|
const logout = useCallback(async () => {
|
|
521
|
-
|
|
522
|
-
|
|
524
|
+
if (managerRef.current) {
|
|
525
|
+
return managerRef.current.logout();
|
|
526
|
+
}
|
|
523
527
|
}, []);
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
(_a = managerRef.current) == null ? void 0 : _a.updateConfig(newConfig);
|
|
528
|
+
const dismissIdleWarning = useCallback(() => {
|
|
529
|
+
setTimeUntilIdle(0);
|
|
527
530
|
}, []);
|
|
528
531
|
return {
|
|
529
|
-
|
|
530
|
-
|
|
532
|
+
isActive: state.isActive,
|
|
533
|
+
isIdle: state.isIdle,
|
|
534
|
+
isRefreshing: state.isRefreshing,
|
|
535
|
+
refreshAttempts: state.refreshAttempts,
|
|
536
|
+
timeUntilTimeout,
|
|
531
537
|
timeUntilIdle,
|
|
532
|
-
|
|
533
|
-
extendSession,
|
|
538
|
+
error,
|
|
534
539
|
refreshToken,
|
|
535
540
|
logout,
|
|
536
|
-
|
|
537
|
-
manager: managerRef.current
|
|
541
|
+
dismissIdleWarning
|
|
538
542
|
};
|
|
539
543
|
}
|
|
540
544
|
|
|
@@ -585,11 +589,13 @@ export {
|
|
|
585
589
|
SessionStatus,
|
|
586
590
|
clearToken,
|
|
587
591
|
getSessionManager,
|
|
592
|
+
getStoredRefreshToken,
|
|
588
593
|
getStoredToken,
|
|
589
594
|
getTimeUntilExpiry,
|
|
590
595
|
getTokenInfo,
|
|
591
596
|
isTokenExpired,
|
|
592
597
|
resetSessionManager,
|
|
598
|
+
storeRefreshToken,
|
|
593
599
|
storeToken,
|
|
594
600
|
useSessionTimeout,
|
|
595
601
|
validateToken
|
package/package.json
CHANGED