formreader-session-timeout 0.2.3 → 0.2.5

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.js CHANGED
@@ -24,12 +24,17 @@ __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,
31
+ initializeAuth: () => initializeAuth,
32
+ isAuthenticated: () => isAuthenticated,
30
33
  isTokenExpired: () => isTokenExpired,
31
34
  resetSessionManager: () => resetSessionManager,
35
+ storeRefreshToken: () => storeRefreshToken,
32
36
  storeToken: () => storeToken,
37
+ triggerAuthChange: () => triggerAuthChange,
33
38
  useSessionTimeout: () => useSessionTimeout,
34
39
  validateToken: () => validateToken
35
40
  });
@@ -47,11 +52,18 @@ var DEFAULT_SESSION_CONFIG = {
47
52
  // 8 hours max
48
53
  refreshEndpoint: "/auth/refresh/",
49
54
  logoutEndpoint: "/auth/logout/",
55
+ trackIdleTime: true,
50
56
  showIdleWarning: true,
51
57
  idleWarningThresholdMs: 2 * 60 * 1e3,
52
58
  // 2 minutes before idle timeout
53
59
  autoRefresh: true,
54
- debug: false
60
+ debug: false,
61
+ accessTokenField: "access",
62
+ refreshTokenField: "refresh",
63
+ refreshAccessTokenField: "access",
64
+ refreshRefreshTokenField: "refresh",
65
+ storeRefreshToken: true,
66
+ validateOnInit: true
55
67
  };
56
68
 
57
69
  // services/tokenService.ts
@@ -101,34 +113,25 @@ function getTimeUntilExpiry(token) {
101
113
  function getStoredToken() {
102
114
  return sessionStorage.getItem("authToken") || localStorage.getItem("authToken");
103
115
  }
116
+ function getStoredRefreshToken() {
117
+ return sessionStorage.getItem("refreshToken") || localStorage.getItem("refreshToken");
118
+ }
104
119
  function storeToken(token, persistent = false) {
105
- if (persistent) {
106
- localStorage.setItem("authToken", token);
107
- sessionStorage.removeItem("authToken");
108
- } else {
109
- sessionStorage.setItem("authToken", token);
110
- localStorage.removeItem("authToken");
111
- }
120
+ const storage = persistent ? localStorage : sessionStorage;
121
+ storage.setItem("authToken", token);
122
+ }
123
+ function storeRefreshToken(token, persistent = false) {
124
+ const storage = persistent ? localStorage : sessionStorage;
125
+ storage.setItem("refreshToken", token);
112
126
  }
113
127
  function clearToken() {
114
128
  sessionStorage.removeItem("authToken");
115
129
  localStorage.removeItem("authToken");
130
+ sessionStorage.removeItem("refreshToken");
131
+ localStorage.removeItem("refreshToken");
116
132
  }
117
133
  function validateToken(token) {
118
- if (!token || typeof token !== "string") {
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 };
134
+ return decodeJWT(token) !== null;
132
135
  }
133
136
 
134
137
  // services/sessionManager.ts
@@ -149,6 +152,20 @@ function defaultFetchClient() {
149
152
  }
150
153
  };
151
154
  }
155
+ function extractTokenFromResponse(response, fieldName = "access") {
156
+ if (!response)
157
+ return null;
158
+ const parts = fieldName.split(".");
159
+ let value = response;
160
+ for (const part of parts) {
161
+ if (value && typeof value === "object") {
162
+ value = value[part];
163
+ } else {
164
+ return null;
165
+ }
166
+ }
167
+ return typeof value === "string" ? value : null;
168
+ }
152
169
  var SessionManager = class {
153
170
  constructor(config) {
154
171
  this.refreshTimer = null;
@@ -171,20 +188,50 @@ var SessionManager = class {
171
188
  this.log("SessionManager initialized with config:", this.config);
172
189
  }
173
190
  /**
174
- * Initialize session management
191
+ * Initialize session management with optional login response
192
+ * Extracts and stores tokens from login response using configured field names
175
193
  */
176
- init() {
177
- const token = getStoredToken();
194
+ init(loginResponse) {
195
+ const token = loginResponse ? extractTokenFromResponse(loginResponse, this.config.accessTokenField || "access") : getStoredToken();
178
196
  if (!token) {
179
197
  this.log("No token found, session not initialized");
198
+ this.emit("noToken");
180
199
  return;
181
200
  }
201
+ storeToken(token);
202
+ if (this.config.storeRefreshToken && loginResponse) {
203
+ const refreshToken = extractTokenFromResponse(
204
+ loginResponse,
205
+ this.config.refreshTokenField || "refresh"
206
+ );
207
+ if (refreshToken) {
208
+ storeRefreshToken(refreshToken);
209
+ this.log("Refresh token stored");
210
+ }
211
+ }
182
212
  const validation = validateToken(token);
183
- if (!validation.valid) {
184
- this.log("Invalid token:", validation.error);
213
+ if (!validation) {
214
+ this.log("Invalid token format");
215
+ this.emit("invalidToken");
185
216
  this.logout();
186
217
  return;
187
218
  }
219
+ if (isTokenExpired(token)) {
220
+ this.log("Token is expired, attempting refresh");
221
+ this.emit("tokenExpired");
222
+ const refreshToken = getStoredRefreshToken();
223
+ if (refreshToken && this.config.autoRefresh) {
224
+ this.refreshToken().then((success) => {
225
+ if (!success) {
226
+ this.logout();
227
+ }
228
+ });
229
+ return;
230
+ } else {
231
+ this.logout();
232
+ return;
233
+ }
234
+ }
188
235
  this.state.isActive = true;
189
236
  this.setupTokenRefresh();
190
237
  this.setupIdleTracking();
@@ -201,43 +248,33 @@ var SessionManager = class {
201
248
  return;
202
249
  const timeUntilExpiry = getTimeUntilExpiry(token);
203
250
  const refreshAt = Math.max(0, timeUntilExpiry - this.config.refreshThresholdMs);
204
- this.log(`Token expires in ${timeUntilExpiry}ms, will refresh in ${refreshAt}ms`);
205
251
  if (this.refreshTimer) {
206
252
  clearTimeout(this.refreshTimer);
207
253
  }
208
- if (this.config.autoRefresh && refreshAt > 0) {
209
- this.refreshTimer = setTimeout(() => {
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");
254
+ this.refreshTimer = setTimeout(() => {
255
+ this.log("Token refresh threshold reached, triggering refresh");
215
256
  this.refreshToken();
216
- }
257
+ }, refreshAt);
258
+ this.log(`Token refresh scheduled in ${refreshAt}ms`);
217
259
  }
218
260
  /**
219
- * Setup idle activity tracking
261
+ * Setup idle tracking with activity listeners
220
262
  */
221
263
  setupIdleTracking() {
222
- const events = ["mousedown", "keydown", "scroll", "touchstart", "click"];
264
+ if (!this.config.trackIdleTime)
265
+ return;
223
266
  const handleActivity = () => {
224
267
  this.state.lastActivityTime = Date.now();
225
268
  this.state.isIdle = false;
226
269
  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
270
  };
271
+ const events = ["mousedown", "keydown", "scroll", "touchstart", "click"];
238
272
  events.forEach((event) => {
239
- window.addEventListener(event, handleActivity, { passive: true });
273
+ window.addEventListener(event, handleActivity, true);
240
274
  });
275
+ if (this.idleTimer) {
276
+ clearTimeout(this.idleTimer);
277
+ }
241
278
  this.idleTimer = setTimeout(() => {
242
279
  var _a, _b;
243
280
  this.state.isIdle = true;
@@ -269,7 +306,7 @@ var SessionManager = class {
269
306
  }, this.config.maxSessionDurationMs);
270
307
  }
271
308
  /**
272
- * Refresh token
309
+ * Refresh token using stored refresh token
273
310
  */
274
311
  async refreshToken() {
275
312
  var _a, _b, _c, _d;
@@ -279,23 +316,49 @@ var SessionManager = class {
279
316
  }
280
317
  this.state.isRefreshing = true;
281
318
  this.state.refreshAttempts++;
282
- const token = getStoredToken();
283
- if (!token) {
319
+ const accessToken = getStoredToken();
320
+ const refreshToken = getStoredRefreshToken();
321
+ if (!accessToken && !refreshToken) {
284
322
  this.state.isRefreshing = false;
285
323
  return false;
286
324
  }
287
325
  try {
288
326
  this.log(`Refreshing token (attempt ${this.state.refreshAttempts})`);
289
327
  const client = this.config.httpClient || defaultFetchClient();
290
- const refreshPayload = this.config.refreshPayloadFormatter ? this.config.refreshPayloadFormatter(token) : { token };
291
- const refreshPromise = client.post(this.config.refreshEndpoint, refreshPayload, { headers: { Authorization: `Bearer ${token}` } });
328
+ const refreshPayload = this.config.refreshPayloadFormatter ? this.config.refreshPayloadFormatter(accessToken || "", refreshToken) : {
329
+ refresh: refreshToken || accessToken,
330
+ token: accessToken
331
+ };
332
+ const refreshPromise = client.post(
333
+ this.config.refreshEndpoint,
334
+ refreshPayload,
335
+ {
336
+ headers: {
337
+ Authorization: `Bearer ${accessToken || refreshToken}`
338
+ }
339
+ }
340
+ );
292
341
  this.requestDeduplication.set("refresh", refreshPromise);
293
342
  const response = await refreshPromise;
294
- const newToken = response.data.token || response.data.access_token;
295
- if (!newToken) {
296
- throw new Error("No token in refresh response");
343
+ const responseData = response.data;
344
+ const newAccessToken = extractTokenFromResponse(
345
+ responseData,
346
+ this.config.refreshAccessTokenField || "access"
347
+ );
348
+ if (!newAccessToken) {
349
+ throw new Error("No access token in refresh response");
350
+ }
351
+ storeToken(newAccessToken);
352
+ if (this.config.storeRefreshToken) {
353
+ const newRefreshToken = extractTokenFromResponse(
354
+ responseData,
355
+ this.config.refreshRefreshTokenField || "refresh"
356
+ );
357
+ if (newRefreshToken) {
358
+ storeRefreshToken(newRefreshToken);
359
+ this.log("Refresh token updated");
360
+ }
297
361
  }
298
- storeToken(newToken);
299
362
  this.state.isRefreshing = false;
300
363
  this.state.refreshAttempts = 0;
301
364
  this.requestDeduplication.delete("refresh");
@@ -317,41 +380,55 @@ var SessionManager = class {
317
380
  }
318
381
  }
319
382
  /**
320
- * Logout and cleanup
383
+ * Manual logout
321
384
  */
322
385
  async logout() {
323
386
  var _a, _b;
387
+ this.log("Logging out");
324
388
  try {
325
389
  const token = getStoredToken();
326
- if (token) {
390
+ if (token && this.config.logoutEndpoint) {
327
391
  const client = this.config.httpClient || defaultFetchClient();
328
- const logoutPayload = this.config.logoutPayloadFormatter ? this.config.logoutPayloadFormatter(token) : { token };
329
- await client.post(this.config.logoutEndpoint, logoutPayload, { headers: { Authorization: `Bearer ${token}` } });
392
+ const logoutPayload = this.config.logoutPayloadFormatter ? this.config.logoutPayloadFormatter(token, getStoredRefreshToken()) : { token };
393
+ await client.post(this.config.logoutEndpoint, logoutPayload, {
394
+ headers: { Authorization: `Bearer ${token}` }
395
+ });
330
396
  }
331
397
  } catch (error) {
332
- this.log("Logout API call failed:", error);
398
+ this.log("Logout request failed:", error);
333
399
  }
334
- this.cleanup();
400
+ clearToken();
335
401
  this.state.isActive = false;
336
402
  this.emit("loggedOut");
337
- (_b = (_a = this.config).onSessionExpired) == null ? void 0 : _b.call(_a);
338
- clearToken();
403
+ (_b = (_a = this.config).onLogout) == null ? void 0 : _b.call(_a);
404
+ this.cleanup();
339
405
  }
340
406
  /**
341
- * Extend session (reset idle timer)
407
+ * Check if session is active and token is valid
342
408
  */
343
- extendSession() {
344
- this.state.lastActivityTime = Date.now();
345
- this.state.isIdle = false;
346
- if (this.idleTimer) {
347
- clearTimeout(this.idleTimer);
409
+ isActive() {
410
+ if (!this.state.isActive)
411
+ return false;
412
+ const token = getStoredToken();
413
+ if (!token)
414
+ return false;
415
+ return !isTokenExpired(token);
416
+ }
417
+ /**
418
+ * Validate current session without initializing
419
+ */
420
+ validateSession() {
421
+ const token = getStoredToken();
422
+ if (!token) {
423
+ return { isValid: false, reason: "no_token" };
348
424
  }
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");
425
+ if (!validateToken(token)) {
426
+ return { isValid: false, reason: "invalid_token" };
427
+ }
428
+ if (isTokenExpired(token)) {
429
+ return { isValid: false, reason: "token_expired" };
430
+ }
431
+ return { isValid: true };
355
432
  }
356
433
  /**
357
434
  * Get current state
@@ -360,13 +437,7 @@ var SessionManager = class {
360
437
  return { ...this.state };
361
438
  }
362
439
  /**
363
- * Get current config
364
- */
365
- getConfig() {
366
- return { ...this.config };
367
- }
368
- /**
369
- * Update config
440
+ * Update config at runtime
370
441
  */
371
442
  updateConfig(newConfig) {
372
443
  this.config = { ...this.config, ...newConfig };
@@ -456,122 +527,151 @@ function resetSessionManager() {
456
527
  }
457
528
  }
458
529
 
530
+ // utils/authUtils.ts
531
+ function isAuthenticated() {
532
+ const token = getStoredToken();
533
+ if (!token) {
534
+ return false;
535
+ }
536
+ if (!validateToken(token)) {
537
+ return false;
538
+ }
539
+ if (isTokenExpired(token)) {
540
+ return false;
541
+ }
542
+ return true;
543
+ }
544
+ async function initializeAuth(config) {
545
+ const manager = getSessionManager(config);
546
+ return new Promise((resolve) => {
547
+ const cleanup = [];
548
+ cleanup.push(manager.on("initialized", () => {
549
+ cleanup.forEach((fn) => fn());
550
+ resolve(true);
551
+ }));
552
+ cleanup.push(manager.on("noToken", () => {
553
+ cleanup.forEach((fn) => fn());
554
+ resolve(false);
555
+ }));
556
+ cleanup.push(manager.on("invalidToken", () => {
557
+ cleanup.forEach((fn) => fn());
558
+ resolve(false);
559
+ }));
560
+ cleanup.push(manager.on("tokenExpired", () => {
561
+ setTimeout(() => {
562
+ cleanup.forEach((fn) => fn());
563
+ resolve(manager.isActive());
564
+ }, 1e3);
565
+ }));
566
+ cleanup.push(manager.on("loggedOut", () => {
567
+ cleanup.forEach((fn) => fn());
568
+ resolve(false);
569
+ }));
570
+ manager.init();
571
+ setTimeout(() => {
572
+ cleanup.forEach((fn) => fn());
573
+ resolve(manager.isActive());
574
+ }, 3e3);
575
+ });
576
+ }
577
+ function triggerAuthChange() {
578
+ window.dispatchEvent(new CustomEvent("authChange"));
579
+ }
580
+
459
581
  // hooks/useSessionTimeout.ts
460
582
  var import_react = require("react");
461
583
  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)({
584
+ const [state, setState] = (0, import_react.useState)({
471
585
  isActive: false,
472
586
  isIdle: false,
473
587
  lastActivityTime: Date.now(),
474
588
  refreshAttempts: 0,
475
589
  isRefreshing: false
476
590
  });
477
- const [timeUntilIdle, setTimeUntilIdle] = (0, import_react.useState)(null);
478
- const [idleWarningVisible, setIdleWarningVisible] = (0, import_react.useState)(false);
591
+ const [error, setError] = (0, import_react.useState)(null);
592
+ const [timeUntilTimeout, setTimeUntilTimeout] = (0, import_react.useState)(0);
593
+ const [timeUntilIdle, setTimeUntilIdle] = (0, import_react.useState)(0);
479
594
  const managerRef = (0, import_react.useRef)(null);
480
- const unsubscribeRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
595
+ const updateTimerRef = (0, import_react.useRef)(null);
481
596
  (0, import_react.useEffect)(() => {
482
- if (!enabled)
483
- return;
484
- const manager = getSessionManager({
485
- ...config,
486
- onSessionExpiring,
487
- onSessionExpired,
488
- onIdle,
489
- onRefreshSuccess
490
- });
597
+ const { loginResponse, autoInit, ...config } = options;
598
+ const manager = getSessionManager(config);
491
599
  managerRef.current = manager;
492
- const subscriptions = /* @__PURE__ */ new Map();
493
- subscriptions.set(
494
- "initialized",
495
- manager.on("initialized", () => {
496
- setSessionState(manager.getState());
497
- })
498
- );
499
- subscriptions.set(
500
- "tokenRefreshed",
501
- manager.on("tokenRefreshed", () => {
502
- setSessionState(manager.getState());
503
- })
504
- );
505
- subscriptions.set(
506
- "idle",
507
- manager.on("idle", () => {
508
- setSessionState(manager.getState());
509
- })
510
- );
511
- subscriptions.set(
512
- "activity",
513
- manager.on("activity", () => {
514
- setSessionState(manager.getState());
515
- setIdleWarningVisible(false);
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();
600
+ const unsubscribeInitialized = manager.on("initialized", () => {
601
+ setState(manager.getState());
602
+ });
603
+ const unsubscribeTokenRefreshed = manager.on("tokenRefreshed", () => {
604
+ setState(manager.getState());
605
+ });
606
+ const unsubscribeRefreshFailed = manager.on("refreshFailed", (error2) => {
607
+ setError(error2);
608
+ setState(manager.getState());
609
+ });
610
+ const unsubscribeLogout = manager.on("logout", () => {
611
+ setState(manager.getState());
612
+ });
613
+ const unsubscribeIdle = manager.on("idle", () => {
614
+ setState(manager.getState());
615
+ });
616
+ const unsubscribeIdleWarning = manager.on("idleWarning", (data) => {
617
+ setTimeUntilIdle(data.timeRemaining);
618
+ });
619
+ const unsubscribeActivity = manager.on("activity", () => {
620
+ setTimeUntilIdle(0);
621
+ });
622
+ if (autoInit !== false && (autoInit === true || loginResponse)) {
623
+ manager.init(loginResponse);
624
+ }
533
625
  return () => {
534
- subscriptions.forEach((unsub) => unsub());
535
- unsubscribeRef.current.clear();
626
+ unsubscribeInitialized();
627
+ unsubscribeTokenRefreshed();
628
+ unsubscribeRefreshFailed();
629
+ unsubscribeLogout();
630
+ unsubscribeIdle();
631
+ unsubscribeIdleWarning();
632
+ unsubscribeActivity();
536
633
  };
537
- }, [enabled, config, onSessionExpiring, onSessionExpired, onIdle, onRefreshSuccess]);
634
+ }, [options]);
538
635
  (0, import_react.useEffect)(() => {
539
- if (!enabled || !sessionState.isActive)
636
+ var _a;
637
+ if (!((_a = managerRef.current) == null ? void 0 : _a.isActive())) {
540
638
  return;
541
- const interval = setInterval(() => {
542
- const manager = managerRef.current;
543
- if (manager) {
544
- setSessionState(manager.getState());
639
+ }
640
+ const updateCountdown = () => {
641
+ setState(managerRef.current.getState());
642
+ };
643
+ updateTimerRef.current = setInterval(updateCountdown, 1e3);
644
+ return () => {
645
+ if (updateTimerRef.current) {
646
+ clearInterval(updateTimerRef.current);
545
647
  }
546
- }, 5e3);
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();
648
+ };
552
649
  }, []);
553
650
  const refreshToken = (0, import_react.useCallback)(async () => {
554
- var _a;
555
- return (_a = managerRef.current) == null ? void 0 : _a.refreshToken();
651
+ if (managerRef.current) {
652
+ return managerRef.current.refreshToken();
653
+ }
654
+ return false;
556
655
  }, []);
557
656
  const logout = (0, import_react.useCallback)(async () => {
558
- var _a;
559
- return (_a = managerRef.current) == null ? void 0 : _a.logout();
657
+ if (managerRef.current) {
658
+ return managerRef.current.logout();
659
+ }
560
660
  }, []);
561
- const updateConfig = (0, import_react.useCallback)((newConfig) => {
562
- var _a;
563
- (_a = managerRef.current) == null ? void 0 : _a.updateConfig(newConfig);
661
+ const dismissIdleWarning = (0, import_react.useCallback)(() => {
662
+ setTimeUntilIdle(0);
564
663
  }, []);
565
664
  return {
566
- sessionState,
567
- timeUntilExpiry: null,
665
+ isActive: state.isActive,
666
+ isIdle: state.isIdle,
667
+ isRefreshing: state.isRefreshing,
668
+ refreshAttempts: state.refreshAttempts,
669
+ timeUntilTimeout,
568
670
  timeUntilIdle,
569
- idleWarningVisible,
570
- extendSession,
671
+ error,
571
672
  refreshToken,
572
673
  logout,
573
- updateConfig,
574
- manager: managerRef.current
674
+ dismissIdleWarning
575
675
  };
576
676
  }
577
677
 
@@ -623,12 +723,17 @@ function SessionStatus() {
623
723
  SessionStatus,
624
724
  clearToken,
625
725
  getSessionManager,
726
+ getStoredRefreshToken,
626
727
  getStoredToken,
627
728
  getTimeUntilExpiry,
628
729
  getTokenInfo,
730
+ initializeAuth,
731
+ isAuthenticated,
629
732
  isTokenExpired,
630
733
  resetSessionManager,
734
+ storeRefreshToken,
631
735
  storeToken,
736
+ triggerAuthChange,
632
737
  useSessionTimeout,
633
738
  validateToken
634
739
  });