@payloadcms/ui 3.90.2 → 3.91.0-internal.c1569dc

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.
Files changed (76) hide show
  1. package/dist/elements/FolderView/FolderFileTable/index.d.ts.map +1 -1
  2. package/dist/elements/FolderView/FolderFileTable/index.js +12 -2
  3. package/dist/elements/FolderView/FolderFileTable/index.js.map +1 -1
  4. package/dist/elements/FolderView/FolderFileTable/index.scss +14 -0
  5. package/dist/elements/Thumbnail/index.d.ts +1 -0
  6. package/dist/elements/Thumbnail/index.d.ts.map +1 -1
  7. package/dist/elements/Thumbnail/index.js +2 -0
  8. package/dist/elements/Thumbnail/index.js.map +1 -1
  9. package/dist/exports/client/index.d.ts +2 -1
  10. package/dist/exports/client/index.d.ts.map +1 -1
  11. package/dist/exports/client/index.js +24 -24
  12. package/dist/exports/client/index.js.map +4 -4
  13. package/dist/forms/fieldSchemasToFormState/addFieldStatePromise.d.ts.map +1 -1
  14. package/dist/forms/fieldSchemasToFormState/addFieldStatePromise.js +12 -4
  15. package/dist/forms/fieldSchemasToFormState/addFieldStatePromise.js.map +1 -1
  16. package/dist/providers/Auth/AuthSessionDebug/index.d.ts +13 -0
  17. package/dist/providers/Auth/AuthSessionDebug/index.d.ts.map +1 -0
  18. package/dist/providers/Auth/AuthSessionDebug/index.js +93 -0
  19. package/dist/providers/Auth/AuthSessionDebug/index.js.map +1 -0
  20. package/dist/providers/Auth/AuthSessionDebug/index.scss +49 -0
  21. package/dist/providers/Auth/AuthSessionDebug/utilities.d.ts +21 -0
  22. package/dist/providers/Auth/AuthSessionDebug/utilities.d.ts.map +1 -0
  23. package/dist/providers/Auth/AuthSessionDebug/utilities.js +53 -0
  24. package/dist/providers/Auth/AuthSessionDebug/utilities.js.map +1 -0
  25. package/dist/providers/Auth/authSessionRequests.d.ts +27 -0
  26. package/dist/providers/Auth/authSessionRequests.d.ts.map +1 -0
  27. package/dist/providers/Auth/authSessionRequests.js +119 -0
  28. package/dist/providers/Auth/authSessionRequests.js.map +1 -0
  29. package/dist/providers/Auth/authSessionRequests.spec.js +121 -0
  30. package/dist/providers/Auth/authSessionRequests.spec.js.map +1 -0
  31. package/dist/providers/Auth/index.d.ts +3 -57
  32. package/dist/providers/Auth/index.d.ts.map +1 -1
  33. package/dist/providers/Auth/index.js +290 -135
  34. package/dist/providers/Auth/index.js.map +1 -1
  35. package/dist/providers/Auth/sessionActivity.d.ts +23 -0
  36. package/dist/providers/Auth/sessionActivity.d.ts.map +1 -0
  37. package/dist/providers/Auth/sessionActivity.js +49 -0
  38. package/dist/providers/Auth/sessionActivity.js.map +1 -0
  39. package/dist/providers/Auth/sessionActivity.spec.js +44 -0
  40. package/dist/providers/Auth/sessionActivity.spec.js.map +1 -0
  41. package/dist/providers/Auth/sessionTiming.d.ts +11 -0
  42. package/dist/providers/Auth/sessionTiming.d.ts.map +1 -0
  43. package/dist/providers/Auth/sessionTiming.js +18 -0
  44. package/dist/providers/Auth/sessionTiming.js.map +1 -0
  45. package/dist/providers/Auth/tabSessionSync/index.d.ts +17 -0
  46. package/dist/providers/Auth/tabSessionSync/index.d.ts.map +1 -0
  47. package/dist/providers/Auth/tabSessionSync/index.js +263 -0
  48. package/dist/providers/Auth/tabSessionSync/index.js.map +1 -0
  49. package/dist/providers/Auth/tabSessionSync/index.spec.js +463 -0
  50. package/dist/providers/Auth/tabSessionSync/index.spec.js.map +1 -0
  51. package/dist/providers/Auth/tabSessionSync/types.d.ts +107 -0
  52. package/dist/providers/Auth/tabSessionSync/types.d.ts.map +1 -0
  53. package/dist/providers/Auth/tabSessionSync/types.js +6 -0
  54. package/dist/providers/Auth/tabSessionSync/types.js.map +1 -0
  55. package/dist/providers/Auth/tabSessionSync/useTabSessionSync.d.ts +11 -0
  56. package/dist/providers/Auth/tabSessionSync/useTabSessionSync.d.ts.map +1 -0
  57. package/dist/providers/Auth/tabSessionSync/useTabSessionSync.js +90 -0
  58. package/dist/providers/Auth/tabSessionSync/useTabSessionSync.js.map +1 -0
  59. package/dist/providers/Auth/tabSessionSync/utilities.d.ts +26 -0
  60. package/dist/providers/Auth/tabSessionSync/utilities.d.ts.map +1 -0
  61. package/dist/providers/Auth/tabSessionSync/utilities.js +159 -0
  62. package/dist/providers/Auth/tabSessionSync/utilities.js.map +1 -0
  63. package/dist/providers/Auth/types.d.ts +70 -0
  64. package/dist/providers/Auth/types.d.ts.map +1 -0
  65. package/dist/providers/Auth/types.js +2 -0
  66. package/dist/providers/Auth/types.js.map +1 -0
  67. package/dist/providers/Auth/types.spec.js +8 -0
  68. package/dist/providers/Auth/types.spec.js.map +1 -0
  69. package/dist/providers/Auth/useAuthSessionTimers.d.ts +26 -0
  70. package/dist/providers/Auth/useAuthSessionTimers.d.ts.map +1 -0
  71. package/dist/providers/Auth/useAuthSessionTimers.js +203 -0
  72. package/dist/providers/Auth/useAuthSessionTimers.js.map +1 -0
  73. package/dist/providers/Auth/useAuthSessionTimers.spec.js +311 -0
  74. package/dist/providers/Auth/useAuthSessionTimers.spec.js.map +1 -0
  75. package/dist/styles.css +1 -1
  76. package/package.json +5 -5
@@ -0,0 +1,203 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
4
+ import { createSessionActivityTracker, registerSessionActivityListeners } from './sessionActivity.js';
5
+ import { getAuthSessionTiming } from './sessionTiming.js';
6
+ const maxTimeoutMs = 2_147_483_647 // ~24.8 days
7
+ ;
8
+ /**
9
+ * Manages activity-aware refresh, expiration reminders, and logout timing for the current session.
10
+ *
11
+ * Activity inside the refresh window schedules a refresh. Earlier activity is remembered until the
12
+ * refresh-window checkpoint so the session can still be extended without requiring another
13
+ * activity event.
14
+ */
15
+ export function useAuthSessionTimers({
16
+ isAuthenticated,
17
+ onActivity,
18
+ onActivityRefresh,
19
+ onExpire,
20
+ onReminder
21
+ }) {
22
+ const activityListenerCleanupRef = useRef(undefined);
23
+ const activityCheckpointTimeoutRef = useRef(null);
24
+ const forceLogOutTimeoutRef = useRef(null);
25
+ const hasActiveSessionRef = useRef(isAuthenticated);
26
+ const lastSessionActivityAtRef = useRef(undefined);
27
+ const scheduledRefreshTimeoutRef = useRef(null);
28
+ const reminderTimeoutRef = useRef(null);
29
+ const sessionTimingRef = useRef(undefined);
30
+ const tokenExpirationMsRef = useRef(undefined);
31
+ const callbacksRef = useRef({
32
+ onActivity,
33
+ onActivityRefresh,
34
+ onExpire,
35
+ onReminder
36
+ });
37
+ const scheduleRefresh = useCallback(forceRefresh => {
38
+ if (!hasActiveSessionRef.current) {
39
+ return;
40
+ }
41
+ const sessionTiming = sessionTimingRef.current;
42
+ if (forceRefresh || sessionTiming && Date.now() >= sessionTiming.refreshStartsAt) {
43
+ clearTimeout(scheduledRefreshTimeoutRef.current);
44
+ scheduledRefreshTimeoutRef.current = setTimeout(() => {
45
+ callbacksRef.current.onActivityRefresh();
46
+ }, 1000);
47
+ }
48
+ }, []);
49
+ const activityTracker = useMemo(() => createSessionActivityTracker({
50
+ onActivity: (_source, occurredAt) => {
51
+ const isFirstActivity = lastSessionActivityAtRef.current === undefined;
52
+ lastSessionActivityAtRef.current = occurredAt;
53
+ if (isFirstActivity) {
54
+ callbacksRef.current.onActivity();
55
+ }
56
+ scheduleRefresh();
57
+ }
58
+ }), [scheduleRefresh]);
59
+ const recordActivity = useCallback(source => {
60
+ const occurredAt_0 = Date.now();
61
+ const sessionTiming_0 = sessionTimingRef.current;
62
+ const isActivityWindowOpen = sessionTiming_0 && occurredAt_0 >= sessionTiming_0.activityTrackingStartsAt && occurredAt_0 < sessionTiming_0.reminderStartsAt;
63
+ if (!activityListenerCleanupRef.current || !isActivityWindowOpen) {
64
+ return false;
65
+ }
66
+ return activityTracker.record(source);
67
+ }, [activityTracker]);
68
+ const addActivityListeners = useCallback(() => {
69
+ if (activityListenerCleanupRef.current) {
70
+ return;
71
+ }
72
+ activityListenerCleanupRef.current = registerSessionActivityListeners({
73
+ markActivity: recordActivity,
74
+ window
75
+ });
76
+ }, [recordActivity]);
77
+ const removeActivityListeners = useCallback(() => {
78
+ const cleanup = activityListenerCleanupRef.current;
79
+ if (!cleanup) {
80
+ return;
81
+ }
82
+ activityListenerCleanupRef.current = undefined;
83
+ cleanup();
84
+ }, []);
85
+ const resetSessionLifecycle = useCallback(() => {
86
+ clearTimeout(activityCheckpointTimeoutRef.current);
87
+ clearTimeout(forceLogOutTimeoutRef.current);
88
+ clearTimeout(reminderTimeoutRef.current);
89
+ clearTimeout(scheduledRefreshTimeoutRef.current);
90
+ removeActivityListeners();
91
+ activityTracker.reset();
92
+ lastSessionActivityAtRef.current = undefined;
93
+ }, [activityTracker, removeActivityListeners]);
94
+ const clear = useCallback(() => {
95
+ resetSessionLifecycle();
96
+ hasActiveSessionRef.current = false;
97
+ sessionTimingRef.current = undefined;
98
+ tokenExpirationMsRef.current = undefined;
99
+ }, [resetSessionLifecycle]);
100
+ const setExpiration = useCallback(expirationMs => {
101
+ if (!Number.isFinite(expirationMs)) {
102
+ return;
103
+ }
104
+ resetSessionLifecycle();
105
+ tokenExpirationMsRef.current = expirationMs;
106
+ const sessionTiming_1 = getAuthSessionTiming({
107
+ expirationMs
108
+ });
109
+ sessionTimingRef.current = sessionTiming_1;
110
+ const scheduleReminder = () => {
111
+ if (tokenExpirationMsRef.current !== expirationMs) {
112
+ return;
113
+ }
114
+ const reminderStartsInMs = Math.max(sessionTiming_1.reminderStartsAt - Date.now(), 0);
115
+ reminderTimeoutRef.current = setTimeout(() => {
116
+ if (tokenExpirationMsRef.current !== expirationMs) {
117
+ return;
118
+ }
119
+ if (Date.now() < sessionTiming_1.reminderStartsAt) {
120
+ scheduleReminder();
121
+ return;
122
+ }
123
+ clearTimeout(scheduledRefreshTimeoutRef.current);
124
+ removeActivityListeners();
125
+ if (Date.now() < sessionTiming_1.expiresAt) {
126
+ callbacksRef.current.onReminder();
127
+ }
128
+ }, Math.min(reminderStartsInMs, maxTimeoutMs));
129
+ };
130
+ const scheduleActivityCheckpoint = () => {
131
+ if (tokenExpirationMsRef.current !== expirationMs) {
132
+ return;
133
+ }
134
+ const refreshStartsInMs = Math.max(sessionTiming_1.refreshStartsAt - Date.now(), 0);
135
+ activityCheckpointTimeoutRef.current = setTimeout(() => {
136
+ if (tokenExpirationMsRef.current !== expirationMs) {
137
+ return;
138
+ }
139
+ const checkpointAt = Date.now();
140
+ if (checkpointAt < sessionTiming_1.refreshStartsAt) {
141
+ scheduleActivityCheckpoint();
142
+ return;
143
+ }
144
+ const lastActivityAt = lastSessionActivityAtRef.current;
145
+ const hasRecentActivity = lastActivityAt !== undefined && checkpointAt < sessionTiming_1.reminderStartsAt && lastActivityAt >= sessionTiming_1.activityTrackingStartsAt;
146
+ if (hasRecentActivity) {
147
+ scheduleRefresh(true);
148
+ }
149
+ }, Math.min(refreshStartsInMs, maxTimeoutMs));
150
+ };
151
+ const scheduleExpiration = () => {
152
+ if (tokenExpirationMsRef.current !== expirationMs) {
153
+ return;
154
+ }
155
+ const expiresInMs = Math.max(sessionTiming_1.expiresAt - Date.now(), 0);
156
+ forceLogOutTimeoutRef.current = setTimeout(() => {
157
+ if (tokenExpirationMsRef.current === expirationMs) {
158
+ if (Date.now() < sessionTiming_1.expiresAt) {
159
+ scheduleExpiration();
160
+ return;
161
+ }
162
+ hasActiveSessionRef.current = false;
163
+ removeActivityListeners();
164
+ callbacksRef.current.onExpire(expirationMs);
165
+ }
166
+ }, Math.min(expiresInMs, maxTimeoutMs));
167
+ };
168
+ hasActiveSessionRef.current = expirationMs > Date.now();
169
+ if (hasActiveSessionRef.current) {
170
+ addActivityListeners();
171
+ scheduleReminder();
172
+ scheduleActivityCheckpoint();
173
+ }
174
+ scheduleExpiration();
175
+ return sessionTiming_1;
176
+ }, [addActivityListeners, removeActivityListeners, resetSessionLifecycle, scheduleRefresh]);
177
+ const getCurrentExpirationMs = useCallback(() => tokenExpirationMsRef.current, []);
178
+ useEffect(() => {
179
+ callbacksRef.current = {
180
+ onActivity,
181
+ onActivityRefresh,
182
+ onExpire,
183
+ onReminder
184
+ };
185
+ }, [onActivity, onActivityRefresh, onExpire, onReminder]);
186
+ useEffect(() => {
187
+ hasActiveSessionRef.current = isAuthenticated;
188
+ if (!isAuthenticated) {
189
+ removeActivityListeners();
190
+ }
191
+ }, [isAuthenticated, removeActivityListeners]);
192
+ useEffect(() => {
193
+ return clear;
194
+ }, [clear]);
195
+ return useMemo(() => ({
196
+ clear,
197
+ getCurrentExpirationMs,
198
+ recordActivity,
199
+ scheduleRefresh,
200
+ setExpiration
201
+ }), [clear, getCurrentExpirationMs, recordActivity, scheduleRefresh, setExpiration]);
202
+ }
203
+ //# sourceMappingURL=useAuthSessionTimers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAuthSessionTimers.js","names":["useCallback","useEffect","useMemo","useRef","createSessionActivityTracker","registerSessionActivityListeners","getAuthSessionTiming","maxTimeoutMs","useAuthSessionTimers","isAuthenticated","onActivity","onActivityRefresh","onExpire","onReminder","activityListenerCleanupRef","undefined","activityCheckpointTimeoutRef","forceLogOutTimeoutRef","hasActiveSessionRef","lastSessionActivityAtRef","scheduledRefreshTimeoutRef","reminderTimeoutRef","sessionTimingRef","tokenExpirationMsRef","callbacksRef","scheduleRefresh","forceRefresh","current","sessionTiming","Date","now","refreshStartsAt","clearTimeout","setTimeout","activityTracker","_source","occurredAt","isFirstActivity","recordActivity","source","isActivityWindowOpen","activityTrackingStartsAt","reminderStartsAt","record","addActivityListeners","markActivity","window","removeActivityListeners","cleanup","resetSessionLifecycle","reset","clear","setExpiration","expirationMs","Number","isFinite","scheduleReminder","reminderStartsInMs","Math","max","expiresAt","min","scheduleActivityCheckpoint","refreshStartsInMs","checkpointAt","lastActivityAt","hasRecentActivity","scheduleExpiration","expiresInMs","getCurrentExpirationMs"],"sources":["../../../src/providers/Auth/useAuthSessionTimers.ts"],"sourcesContent":["'use client'\nimport { useCallback, useEffect, useMemo, useRef } from 'react'\n\nimport type { SessionActivitySource } from './sessionActivity.js'\nimport type { AuthSessionTiming } from './sessionTiming.js'\n\nimport {\n createSessionActivityTracker,\n registerSessionActivityListeners,\n} from './sessionActivity.js'\nimport { getAuthSessionTiming } from './sessionTiming.js'\n\nconst maxTimeoutMs = 2_147_483_647 // ~24.8 days\n\nexport type AuthSessionTimers = {\n clear: () => void\n getCurrentExpirationMs: () => number | undefined\n /** Records activity only while the activity tracking window is open. */\n recordActivity: (source: SessionActivitySource) => boolean\n scheduleRefresh: (forceRefresh?: boolean) => void\n setExpiration: (expirationMs: number) => AuthSessionTiming | undefined\n}\n\nexport type UseAuthSessionTimersArgs = {\n isAuthenticated: boolean\n onActivity: () => void\n onActivityRefresh: () => void\n onExpire: (expirationMs: number) => void\n onReminder: () => void\n}\n\n/**\n * Manages activity-aware refresh, expiration reminders, and logout timing for the current session.\n *\n * Activity inside the refresh window schedules a refresh. Earlier activity is remembered until the\n * refresh-window checkpoint so the session can still be extended without requiring another\n * activity event.\n */\nexport function useAuthSessionTimers({\n isAuthenticated,\n onActivity,\n onActivityRefresh,\n onExpire,\n onReminder,\n}: UseAuthSessionTimersArgs): AuthSessionTimers {\n const activityListenerCleanupRef = useRef<(() => void) | undefined>(undefined)\n const activityCheckpointTimeoutRef = useRef<ReturnType<typeof setTimeout>>(null)\n const forceLogOutTimeoutRef = useRef<ReturnType<typeof setTimeout>>(null)\n const hasActiveSessionRef = useRef(isAuthenticated)\n const lastSessionActivityAtRef = useRef<number>(undefined)\n const scheduledRefreshTimeoutRef = useRef<ReturnType<typeof setTimeout>>(null)\n const reminderTimeoutRef = useRef<ReturnType<typeof setTimeout>>(null)\n const sessionTimingRef = useRef<AuthSessionTiming>(undefined)\n const tokenExpirationMsRef = useRef<number>(undefined)\n const callbacksRef = useRef({ onActivity, onActivityRefresh, onExpire, onReminder })\n\n const scheduleRefresh = useCallback((forceRefresh?: boolean) => {\n if (!hasActiveSessionRef.current) {\n return\n }\n\n const sessionTiming = sessionTimingRef.current\n\n if (forceRefresh || (sessionTiming && Date.now() >= sessionTiming.refreshStartsAt)) {\n clearTimeout(scheduledRefreshTimeoutRef.current)\n scheduledRefreshTimeoutRef.current = setTimeout(() => {\n callbacksRef.current.onActivityRefresh()\n }, 1000)\n }\n }, [])\n\n const activityTracker = useMemo(\n () =>\n createSessionActivityTracker({\n onActivity: (_source, occurredAt) => {\n const isFirstActivity = lastSessionActivityAtRef.current === undefined\n\n lastSessionActivityAtRef.current = occurredAt\n\n if (isFirstActivity) {\n callbacksRef.current.onActivity()\n }\n\n scheduleRefresh()\n },\n }),\n [scheduleRefresh],\n )\n\n const recordActivity = useCallback(\n (source: SessionActivitySource): boolean => {\n const occurredAt = Date.now()\n const sessionTiming = sessionTimingRef.current\n const isActivityWindowOpen =\n sessionTiming &&\n occurredAt >= sessionTiming.activityTrackingStartsAt &&\n occurredAt < sessionTiming.reminderStartsAt\n\n if (!activityListenerCleanupRef.current || !isActivityWindowOpen) {\n return false\n }\n\n return activityTracker.record(source)\n },\n [activityTracker],\n )\n\n const addActivityListeners = useCallback(() => {\n if (activityListenerCleanupRef.current) {\n return\n }\n\n activityListenerCleanupRef.current = registerSessionActivityListeners({\n markActivity: recordActivity,\n window,\n })\n }, [recordActivity])\n\n const removeActivityListeners = useCallback(() => {\n const cleanup = activityListenerCleanupRef.current\n\n if (!cleanup) {\n return\n }\n\n activityListenerCleanupRef.current = undefined\n cleanup()\n }, [])\n\n const resetSessionLifecycle = useCallback(() => {\n clearTimeout(activityCheckpointTimeoutRef.current)\n clearTimeout(forceLogOutTimeoutRef.current)\n clearTimeout(reminderTimeoutRef.current)\n clearTimeout(scheduledRefreshTimeoutRef.current)\n removeActivityListeners()\n activityTracker.reset()\n lastSessionActivityAtRef.current = undefined\n }, [activityTracker, removeActivityListeners])\n\n const clear = useCallback(() => {\n resetSessionLifecycle()\n hasActiveSessionRef.current = false\n sessionTimingRef.current = undefined\n tokenExpirationMsRef.current = undefined\n }, [resetSessionLifecycle])\n\n const setExpiration = useCallback(\n (expirationMs: number) => {\n if (!Number.isFinite(expirationMs)) {\n return\n }\n\n resetSessionLifecycle()\n tokenExpirationMsRef.current = expirationMs\n\n const sessionTiming = getAuthSessionTiming({ expirationMs })\n\n sessionTimingRef.current = sessionTiming\n\n const scheduleReminder = () => {\n if (tokenExpirationMsRef.current !== expirationMs) {\n return\n }\n\n const reminderStartsInMs = Math.max(sessionTiming.reminderStartsAt - Date.now(), 0)\n\n reminderTimeoutRef.current = setTimeout(\n () => {\n if (tokenExpirationMsRef.current !== expirationMs) {\n return\n }\n\n if (Date.now() < sessionTiming.reminderStartsAt) {\n scheduleReminder()\n return\n }\n\n clearTimeout(scheduledRefreshTimeoutRef.current)\n removeActivityListeners()\n\n if (Date.now() < sessionTiming.expiresAt) {\n callbacksRef.current.onReminder()\n }\n },\n Math.min(reminderStartsInMs, maxTimeoutMs),\n )\n }\n\n const scheduleActivityCheckpoint = () => {\n if (tokenExpirationMsRef.current !== expirationMs) {\n return\n }\n\n const refreshStartsInMs = Math.max(sessionTiming.refreshStartsAt - Date.now(), 0)\n\n activityCheckpointTimeoutRef.current = setTimeout(\n () => {\n if (tokenExpirationMsRef.current !== expirationMs) {\n return\n }\n\n const checkpointAt = Date.now()\n\n if (checkpointAt < sessionTiming.refreshStartsAt) {\n scheduleActivityCheckpoint()\n return\n }\n\n const lastActivityAt = lastSessionActivityAtRef.current\n const hasRecentActivity =\n lastActivityAt !== undefined &&\n checkpointAt < sessionTiming.reminderStartsAt &&\n lastActivityAt >= sessionTiming.activityTrackingStartsAt\n\n if (hasRecentActivity) {\n scheduleRefresh(true)\n }\n },\n Math.min(refreshStartsInMs, maxTimeoutMs),\n )\n }\n\n const scheduleExpiration = () => {\n if (tokenExpirationMsRef.current !== expirationMs) {\n return\n }\n\n const expiresInMs = Math.max(sessionTiming.expiresAt - Date.now(), 0)\n\n forceLogOutTimeoutRef.current = setTimeout(\n () => {\n if (tokenExpirationMsRef.current === expirationMs) {\n if (Date.now() < sessionTiming.expiresAt) {\n scheduleExpiration()\n return\n }\n\n hasActiveSessionRef.current = false\n removeActivityListeners()\n callbacksRef.current.onExpire(expirationMs)\n }\n },\n Math.min(expiresInMs, maxTimeoutMs),\n )\n }\n\n hasActiveSessionRef.current = expirationMs > Date.now()\n\n if (hasActiveSessionRef.current) {\n addActivityListeners()\n scheduleReminder()\n scheduleActivityCheckpoint()\n }\n\n scheduleExpiration()\n\n return sessionTiming\n },\n [addActivityListeners, removeActivityListeners, resetSessionLifecycle, scheduleRefresh],\n )\n\n const getCurrentExpirationMs = useCallback(() => tokenExpirationMsRef.current, [])\n\n useEffect(() => {\n callbacksRef.current = { onActivity, onActivityRefresh, onExpire, onReminder }\n }, [onActivity, onActivityRefresh, onExpire, onReminder])\n\n useEffect(() => {\n hasActiveSessionRef.current = isAuthenticated\n\n if (!isAuthenticated) {\n removeActivityListeners()\n }\n }, [isAuthenticated, removeActivityListeners])\n\n useEffect(() => {\n return clear\n }, [clear])\n\n return useMemo(\n () => ({\n clear,\n getCurrentExpirationMs,\n recordActivity,\n scheduleRefresh,\n setExpiration,\n }),\n [clear, getCurrentExpirationMs, recordActivity, scheduleRefresh, setExpiration],\n )\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,311 @@
1
+ // @vitest-environment jsdom
2
+ import React, { act } from 'react';
3
+ import { createRoot } from 'react-dom/client';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import { getAuthSessionTiming } from './sessionTiming.js';
6
+ import { useAuthSessionTimers } from './useAuthSessionTimers.js';
7
+ const timerCleanups = [];
8
+ beforeEach(() => {
9
+ vi.useFakeTimers();
10
+ vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z'));
11
+ });
12
+ afterEach(() => {
13
+ for (const cleanup of timerCleanups.splice(0)) {
14
+ cleanup();
15
+ }
16
+ vi.useRealTimers();
17
+ });
18
+ describe('useAuthSessionTimers', () => {
19
+ it('should scale session timing for tokens shorter than two minutes', () => {
20
+ const nowMs = Date.now();
21
+ const expirationMs = nowMs + 30_000;
22
+ expect(getAuthSessionTiming({
23
+ expirationMs,
24
+ nowMs
25
+ })).toEqual({
26
+ activityTrackingStartsAt: nowMs,
27
+ expiresAt: expirationMs,
28
+ refreshStartsAt: nowMs,
29
+ reminderStartsAt: nowMs + 15_000
30
+ });
31
+ });
32
+ it('should return the lifecycle timing when setting an expiration', () => {
33
+ const timers = renderAuthSessionTimers();
34
+ const expirationMs = Date.now() + 300_000;
35
+ expect(timers.setExpiration(expirationMs)).toEqual({
36
+ activityTrackingStartsAt: expirationMs - 240_000,
37
+ expiresAt: expirationMs,
38
+ refreshStartsAt: expirationMs - 120_000,
39
+ reminderStartsAt: expirationMs - 60_000
40
+ });
41
+ });
42
+ it('should refresh at the checkpoint when activity occurred in the tracking window', () => {
43
+ const onActivity = vi.fn();
44
+ const onActivityRefresh = vi.fn();
45
+ const timers = renderAuthSessionTimers({
46
+ onActivity,
47
+ onActivityRefresh
48
+ });
49
+ timers.setExpiration(Date.now() + 300_000);
50
+ act(() => {
51
+ vi.advanceTimersByTime(120_000);
52
+ window.dispatchEvent(new MouseEvent('mousemove'));
53
+ vi.advanceTimersByTime(1_000);
54
+ window.dispatchEvent(new KeyboardEvent('keydown'));
55
+ vi.advanceTimersByTime(59_000);
56
+ vi.advanceTimersByTime(1_000);
57
+ });
58
+ expect(onActivity).toHaveBeenCalledOnce();
59
+ expect(onActivityRefresh).toHaveBeenCalledTimes(1);
60
+ });
61
+ it('should ignore activity once the expiration reminder opens', () => {
62
+ const onActivityRefresh = vi.fn();
63
+ const timers = renderAuthSessionTimers({
64
+ onActivityRefresh
65
+ });
66
+ const sessionStartedAt = Date.now();
67
+ timers.setExpiration(sessionStartedAt + 300_000);
68
+ act(() => {
69
+ vi.advanceTimersByTime(60_000);
70
+ });
71
+ vi.setSystemTime(sessionStartedAt + 240_000);
72
+ expect(timers.recordActivity('route')).toBe(false);
73
+ expect(onActivityRefresh).not.toHaveBeenCalled();
74
+ });
75
+ it('should clear timers and listeners on logout', () => {
76
+ const onActivityRefresh = vi.fn();
77
+ const onExpire = vi.fn();
78
+ const timers = renderAuthSessionTimers({
79
+ onActivityRefresh,
80
+ onExpire
81
+ });
82
+ timers.setExpiration(Date.now() + 300_000);
83
+ timers.clear();
84
+ act(() => {
85
+ window.dispatchEvent(new MouseEvent('mousemove'));
86
+ vi.advanceTimersByTime(300_000);
87
+ });
88
+ expect(onActivityRefresh).not.toHaveBeenCalled();
89
+ expect(onExpire).not.toHaveBeenCalled();
90
+ });
91
+ it('should schedule the reminder and expiration from the current expiration', () => {
92
+ const onExpire = vi.fn();
93
+ const onReminder = vi.fn();
94
+ const timers = renderAuthSessionTimers({
95
+ onExpire,
96
+ onReminder
97
+ });
98
+ const expirationMs = Date.now() + 300_000;
99
+ timers.setExpiration(expirationMs);
100
+ act(() => {
101
+ vi.advanceTimersByTime(240_000);
102
+ });
103
+ expect(onReminder).toHaveBeenCalledTimes(1);
104
+ expect(onExpire).not.toHaveBeenCalled();
105
+ act(() => {
106
+ vi.advanceTimersByTime(60_000);
107
+ });
108
+ expect(onExpire).toHaveBeenCalledWith(expirationMs);
109
+ });
110
+ it('should respect expirations beyond the maximum browser timeout', () => {
111
+ const onExpire = vi.fn();
112
+ const timers = renderAuthSessionTimers({
113
+ onExpire
114
+ });
115
+ const thirtyDaysMs = 2_592_000_000;
116
+ const maximumBrowserTimeoutMs = 2_147_483_647;
117
+ const expirationMs = Date.now() + thirtyDaysMs;
118
+ timers.setExpiration(expirationMs);
119
+ act(() => {
120
+ vi.advanceTimersByTime(maximumBrowserTimeoutMs);
121
+ });
122
+ expect(onExpire).not.toHaveBeenCalled();
123
+ act(() => {
124
+ vi.advanceTimersByTime(thirtyDaysMs - maximumBrowserTimeoutMs - 1);
125
+ });
126
+ expect(onExpire).not.toHaveBeenCalled();
127
+ act(() => {
128
+ vi.advanceTimersByTime(1);
129
+ });
130
+ expect(onExpire).toHaveBeenCalledOnce();
131
+ expect(onExpire).toHaveBeenCalledWith(expirationMs);
132
+ });
133
+ it('should schedule expiration when the token expires exactly now', () => {
134
+ const onActivityRefresh = vi.fn();
135
+ const onExpire = vi.fn();
136
+ const onReminder = vi.fn();
137
+ const timers = renderAuthSessionTimers({
138
+ isAuthenticated: false,
139
+ onActivityRefresh,
140
+ onExpire,
141
+ onReminder
142
+ });
143
+ const expirationMs = Date.now();
144
+ timers.setExpiration(expirationMs);
145
+ expect(onExpire).not.toHaveBeenCalled();
146
+ act(() => {
147
+ vi.advanceTimersByTime(0);
148
+ });
149
+ expect(onExpire).toHaveBeenCalledOnce();
150
+ expect(onExpire).toHaveBeenCalledWith(expirationMs);
151
+ expect(onActivityRefresh).not.toHaveBeenCalled();
152
+ expect(onReminder).not.toHaveBeenCalled();
153
+ });
154
+ it('should schedule expiration when the token is already past', () => {
155
+ const onActivityRefresh = vi.fn();
156
+ const onExpire = vi.fn();
157
+ const onReminder = vi.fn();
158
+ const timers = renderAuthSessionTimers({
159
+ isAuthenticated: false,
160
+ onActivityRefresh,
161
+ onExpire,
162
+ onReminder
163
+ });
164
+ const expirationMs = Date.now() - 1;
165
+ timers.setExpiration(expirationMs);
166
+ act(() => {
167
+ vi.advanceTimersByTime(0);
168
+ });
169
+ expect(onExpire).toHaveBeenCalledOnce();
170
+ expect(onExpire).toHaveBeenCalledWith(expirationMs);
171
+ expect(onActivityRefresh).not.toHaveBeenCalled();
172
+ expect(onReminder).not.toHaveBeenCalled();
173
+ });
174
+ it('should reject a non-finite expiration without activating the session', () => {
175
+ const onActivityRefresh = vi.fn();
176
+ const onExpire = vi.fn();
177
+ const timers = renderAuthSessionTimers({
178
+ isAuthenticated: false,
179
+ onActivityRefresh,
180
+ onExpire
181
+ });
182
+ timers.setExpiration(Number.NaN);
183
+ timers.scheduleRefresh(true);
184
+ act(() => {
185
+ vi.advanceTimersByTime(1_000);
186
+ });
187
+ expect(timers.getCurrentExpirationMs()).toBeUndefined();
188
+ expect(onActivityRefresh).not.toHaveBeenCalled();
189
+ expect(onExpire).not.toHaveBeenCalled();
190
+ });
191
+ it('should synchronously activate on apply and deactivate on clear', () => {
192
+ const onActivityRefresh = vi.fn();
193
+ const timers = renderAuthSessionTimers({
194
+ isAuthenticated: false,
195
+ onActivityRefresh
196
+ });
197
+ timers.clear();
198
+ timers.setExpiration(Date.now() + 300_000);
199
+ timers.scheduleRefresh(true);
200
+ act(() => {
201
+ vi.advanceTimersByTime(1_000);
202
+ });
203
+ expect(onActivityRefresh).toHaveBeenCalledOnce();
204
+ timers.clear();
205
+ timers.scheduleRefresh(true);
206
+ act(() => {
207
+ vi.advanceTimersByTime(1_000);
208
+ });
209
+ expect(onActivityRefresh).toHaveBeenCalledOnce();
210
+ });
211
+ it('should reset tracked activity when replacing the expiration', () => {
212
+ const onActivity = vi.fn();
213
+ const onActivityRefresh = vi.fn();
214
+ const timers = renderAuthSessionTimers({
215
+ onActivity,
216
+ onActivityRefresh
217
+ });
218
+ const expirationMs = Date.now() + 300_000;
219
+ timers.setExpiration(expirationMs);
220
+ act(() => {
221
+ vi.advanceTimersByTime(60_000);
222
+ });
223
+ expect(timers.recordActivity('route')).toBe(true);
224
+ expect(onActivity).toHaveBeenCalledOnce();
225
+ timers.setExpiration(expirationMs);
226
+ expect(timers.recordActivity('route')).toBe(true);
227
+ expect(onActivity).toHaveBeenCalledTimes(2);
228
+ act(() => {
229
+ vi.advanceTimersByTime(120_000);
230
+ vi.advanceTimersByTime(1_000);
231
+ });
232
+ expect(onActivityRefresh).toHaveBeenCalledOnce();
233
+ });
234
+ it('should keep its controller and activity throttle across rerenders', () => {
235
+ const onActivityRefresh = vi.fn();
236
+ const timers = renderAuthSessionTimers({
237
+ onActivityRefresh
238
+ });
239
+ const initialController = timers.getController();
240
+ timers.setExpiration(Date.now() + 300_000);
241
+ act(() => {
242
+ vi.advanceTimersByTime(238_000);
243
+ window.dispatchEvent(new MouseEvent('mousemove'));
244
+ });
245
+ timers.rerender();
246
+ expect(timers.getController()).toBe(initialController);
247
+ act(() => {
248
+ vi.advanceTimersByTime(1);
249
+ window.dispatchEvent(new MouseEvent('mousemove'));
250
+ vi.advanceTimersByTime(999);
251
+ });
252
+ expect(onActivityRefresh).toHaveBeenCalledTimes(1);
253
+ });
254
+ it('should synchronously replace the current expiration', () => {
255
+ const timers = renderAuthSessionTimers();
256
+ const firstExpirationMs = Date.now() + 300_000;
257
+ const replacementExpirationMs = Date.now() + 600_000;
258
+ timers.setExpiration(firstExpirationMs);
259
+ timers.setExpiration(replacementExpirationMs);
260
+ expect(timers.getCurrentExpirationMs()).toBe(replacementExpirationMs);
261
+ });
262
+ });
263
+ function renderAuthSessionTimers({
264
+ isAuthenticated = true,
265
+ onActivity = vi.fn(),
266
+ onActivityRefresh = vi.fn(),
267
+ onExpire = vi.fn(),
268
+ onReminder = vi.fn()
269
+ } = {}) {
270
+ let controller;
271
+ const container = document.createElement('div');
272
+ const root = createRoot(container);
273
+ function SessionTimers() {
274
+ controller = useAuthSessionTimers({
275
+ isAuthenticated,
276
+ onActivity,
277
+ onActivityRefresh,
278
+ onExpire,
279
+ onReminder
280
+ });
281
+ return null;
282
+ }
283
+ act(() => {
284
+ root.render(React.createElement(SessionTimers));
285
+ });
286
+ timerCleanups.push(() => {
287
+ act(() => {
288
+ root.unmount();
289
+ });
290
+ });
291
+ const getController = () => {
292
+ if (!controller) {
293
+ throw new Error('Expected auth session timers.');
294
+ }
295
+ return controller;
296
+ };
297
+ return {
298
+ clear: () => getController().clear(),
299
+ getController,
300
+ getCurrentExpirationMs: () => getController().getCurrentExpirationMs(),
301
+ rerender: () => {
302
+ act(() => {
303
+ root.render(React.createElement(SessionTimers));
304
+ });
305
+ },
306
+ recordActivity: source => getController().recordActivity(source),
307
+ scheduleRefresh: forceRefresh => getController().scheduleRefresh(forceRefresh),
308
+ setExpiration: expirationMs => getController().setExpiration(expirationMs)
309
+ };
310
+ }
311
+ //# sourceMappingURL=useAuthSessionTimers.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAuthSessionTimers.spec.js","names":["React","act","createRoot","afterEach","beforeEach","describe","expect","it","vi","getAuthSessionTiming","useAuthSessionTimers","timerCleanups","useFakeTimers","setSystemTime","Date","cleanup","splice","useRealTimers","nowMs","now","expirationMs","toEqual","activityTrackingStartsAt","expiresAt","refreshStartsAt","reminderStartsAt","timers","renderAuthSessionTimers","setExpiration","onActivity","fn","onActivityRefresh","advanceTimersByTime","window","dispatchEvent","MouseEvent","KeyboardEvent","toHaveBeenCalledOnce","toHaveBeenCalledTimes","sessionStartedAt","recordActivity","toBe","not","toHaveBeenCalled","onExpire","clear","onReminder","toHaveBeenCalledWith","thirtyDaysMs","maximumBrowserTimeoutMs","isAuthenticated","Number","NaN","scheduleRefresh","getCurrentExpirationMs","toBeUndefined","initialController","getController","rerender","firstExpirationMs","replacementExpirationMs","controller","container","document","createElement","root","SessionTimers","render","push","unmount","Error","source","forceRefresh"],"sources":["../../../src/providers/Auth/useAuthSessionTimers.spec.ts"],"sourcesContent":["// @vitest-environment jsdom\nimport React, { act } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'\n\nimport type { AuthSessionTimers } from './useAuthSessionTimers.js'\n\nimport { getAuthSessionTiming } from './sessionTiming.js'\nimport { useAuthSessionTimers } from './useAuthSessionTimers.js'\n\nconst timerCleanups: Array<() => void> = []\n\nbeforeEach(() => {\n vi.useFakeTimers()\n vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z'))\n})\n\nafterEach(() => {\n for (const cleanup of timerCleanups.splice(0)) {\n cleanup()\n }\n\n vi.useRealTimers()\n})\n\ndescribe('useAuthSessionTimers', () => {\n it('should scale session timing for tokens shorter than two minutes', () => {\n const nowMs = Date.now()\n const expirationMs = nowMs + 30_000\n\n expect(getAuthSessionTiming({ expirationMs, nowMs })).toEqual({\n activityTrackingStartsAt: nowMs,\n expiresAt: expirationMs,\n refreshStartsAt: nowMs,\n reminderStartsAt: nowMs + 15_000,\n })\n })\n\n it('should return the lifecycle timing when setting an expiration', () => {\n const timers = renderAuthSessionTimers()\n const expirationMs = Date.now() + 300_000\n\n expect(timers.setExpiration(expirationMs)).toEqual({\n activityTrackingStartsAt: expirationMs - 240_000,\n expiresAt: expirationMs,\n refreshStartsAt: expirationMs - 120_000,\n reminderStartsAt: expirationMs - 60_000,\n })\n })\n\n it('should refresh at the checkpoint when activity occurred in the tracking window', () => {\n const onActivity = vi.fn()\n const onActivityRefresh = vi.fn()\n const timers = renderAuthSessionTimers({ onActivity, onActivityRefresh })\n\n timers.setExpiration(Date.now() + 300_000)\n act(() => {\n vi.advanceTimersByTime(120_000)\n window.dispatchEvent(new MouseEvent('mousemove'))\n vi.advanceTimersByTime(1_000)\n window.dispatchEvent(new KeyboardEvent('keydown'))\n vi.advanceTimersByTime(59_000)\n vi.advanceTimersByTime(1_000)\n })\n\n expect(onActivity).toHaveBeenCalledOnce()\n expect(onActivityRefresh).toHaveBeenCalledTimes(1)\n })\n\n it('should ignore activity once the expiration reminder opens', () => {\n const onActivityRefresh = vi.fn()\n const timers = renderAuthSessionTimers({ onActivityRefresh })\n const sessionStartedAt = Date.now()\n\n timers.setExpiration(sessionStartedAt + 300_000)\n act(() => {\n vi.advanceTimersByTime(60_000)\n })\n vi.setSystemTime(sessionStartedAt + 240_000)\n\n expect(timers.recordActivity('route')).toBe(false)\n expect(onActivityRefresh).not.toHaveBeenCalled()\n })\n\n it('should clear timers and listeners on logout', () => {\n const onActivityRefresh = vi.fn()\n const onExpire = vi.fn()\n const timers = renderAuthSessionTimers({ onActivityRefresh, onExpire })\n\n timers.setExpiration(Date.now() + 300_000)\n timers.clear()\n act(() => {\n window.dispatchEvent(new MouseEvent('mousemove'))\n vi.advanceTimersByTime(300_000)\n })\n\n expect(onActivityRefresh).not.toHaveBeenCalled()\n expect(onExpire).not.toHaveBeenCalled()\n })\n\n it('should schedule the reminder and expiration from the current expiration', () => {\n const onExpire = vi.fn()\n const onReminder = vi.fn()\n const timers = renderAuthSessionTimers({ onExpire, onReminder })\n const expirationMs = Date.now() + 300_000\n\n timers.setExpiration(expirationMs)\n act(() => {\n vi.advanceTimersByTime(240_000)\n })\n\n expect(onReminder).toHaveBeenCalledTimes(1)\n expect(onExpire).not.toHaveBeenCalled()\n\n act(() => {\n vi.advanceTimersByTime(60_000)\n })\n\n expect(onExpire).toHaveBeenCalledWith(expirationMs)\n })\n\n it('should respect expirations beyond the maximum browser timeout', () => {\n const onExpire = vi.fn()\n const timers = renderAuthSessionTimers({ onExpire })\n const thirtyDaysMs = 2_592_000_000\n const maximumBrowserTimeoutMs = 2_147_483_647\n const expirationMs = Date.now() + thirtyDaysMs\n\n timers.setExpiration(expirationMs)\n\n act(() => {\n vi.advanceTimersByTime(maximumBrowserTimeoutMs)\n })\n\n expect(onExpire).not.toHaveBeenCalled()\n\n act(() => {\n vi.advanceTimersByTime(thirtyDaysMs - maximumBrowserTimeoutMs - 1)\n })\n\n expect(onExpire).not.toHaveBeenCalled()\n\n act(() => {\n vi.advanceTimersByTime(1)\n })\n\n expect(onExpire).toHaveBeenCalledOnce()\n expect(onExpire).toHaveBeenCalledWith(expirationMs)\n })\n\n it('should schedule expiration when the token expires exactly now', () => {\n const onActivityRefresh = vi.fn()\n const onExpire = vi.fn()\n const onReminder = vi.fn()\n const timers = renderAuthSessionTimers({\n isAuthenticated: false,\n onActivityRefresh,\n onExpire,\n onReminder,\n })\n const expirationMs = Date.now()\n\n timers.setExpiration(expirationMs)\n\n expect(onExpire).not.toHaveBeenCalled()\n\n act(() => {\n vi.advanceTimersByTime(0)\n })\n\n expect(onExpire).toHaveBeenCalledOnce()\n expect(onExpire).toHaveBeenCalledWith(expirationMs)\n expect(onActivityRefresh).not.toHaveBeenCalled()\n expect(onReminder).not.toHaveBeenCalled()\n })\n\n it('should schedule expiration when the token is already past', () => {\n const onActivityRefresh = vi.fn()\n const onExpire = vi.fn()\n const onReminder = vi.fn()\n const timers = renderAuthSessionTimers({\n isAuthenticated: false,\n onActivityRefresh,\n onExpire,\n onReminder,\n })\n const expirationMs = Date.now() - 1\n\n timers.setExpiration(expirationMs)\n\n act(() => {\n vi.advanceTimersByTime(0)\n })\n\n expect(onExpire).toHaveBeenCalledOnce()\n expect(onExpire).toHaveBeenCalledWith(expirationMs)\n expect(onActivityRefresh).not.toHaveBeenCalled()\n expect(onReminder).not.toHaveBeenCalled()\n })\n\n it('should reject a non-finite expiration without activating the session', () => {\n const onActivityRefresh = vi.fn()\n const onExpire = vi.fn()\n const timers = renderAuthSessionTimers({\n isAuthenticated: false,\n onActivityRefresh,\n onExpire,\n })\n\n timers.setExpiration(Number.NaN)\n timers.scheduleRefresh(true)\n act(() => {\n vi.advanceTimersByTime(1_000)\n })\n\n expect(timers.getCurrentExpirationMs()).toBeUndefined()\n expect(onActivityRefresh).not.toHaveBeenCalled()\n expect(onExpire).not.toHaveBeenCalled()\n })\n\n it('should synchronously activate on apply and deactivate on clear', () => {\n const onActivityRefresh = vi.fn()\n const timers = renderAuthSessionTimers({ isAuthenticated: false, onActivityRefresh })\n\n timers.clear()\n timers.setExpiration(Date.now() + 300_000)\n timers.scheduleRefresh(true)\n act(() => {\n vi.advanceTimersByTime(1_000)\n })\n\n expect(onActivityRefresh).toHaveBeenCalledOnce()\n\n timers.clear()\n timers.scheduleRefresh(true)\n act(() => {\n vi.advanceTimersByTime(1_000)\n })\n\n expect(onActivityRefresh).toHaveBeenCalledOnce()\n })\n\n it('should reset tracked activity when replacing the expiration', () => {\n const onActivity = vi.fn()\n const onActivityRefresh = vi.fn()\n const timers = renderAuthSessionTimers({ onActivity, onActivityRefresh })\n const expirationMs = Date.now() + 300_000\n\n timers.setExpiration(expirationMs)\n act(() => {\n vi.advanceTimersByTime(60_000)\n })\n expect(timers.recordActivity('route')).toBe(true)\n expect(onActivity).toHaveBeenCalledOnce()\n\n timers.setExpiration(expirationMs)\n\n expect(timers.recordActivity('route')).toBe(true)\n expect(onActivity).toHaveBeenCalledTimes(2)\n\n act(() => {\n vi.advanceTimersByTime(120_000)\n vi.advanceTimersByTime(1_000)\n })\n\n expect(onActivityRefresh).toHaveBeenCalledOnce()\n })\n\n it('should keep its controller and activity throttle across rerenders', () => {\n const onActivityRefresh = vi.fn()\n const timers = renderAuthSessionTimers({ onActivityRefresh })\n const initialController = timers.getController()\n\n timers.setExpiration(Date.now() + 300_000)\n act(() => {\n vi.advanceTimersByTime(238_000)\n window.dispatchEvent(new MouseEvent('mousemove'))\n })\n timers.rerender()\n\n expect(timers.getController()).toBe(initialController)\n\n act(() => {\n vi.advanceTimersByTime(1)\n window.dispatchEvent(new MouseEvent('mousemove'))\n vi.advanceTimersByTime(999)\n })\n\n expect(onActivityRefresh).toHaveBeenCalledTimes(1)\n })\n\n it('should synchronously replace the current expiration', () => {\n const timers = renderAuthSessionTimers()\n const firstExpirationMs = Date.now() + 300_000\n const replacementExpirationMs = Date.now() + 600_000\n\n timers.setExpiration(firstExpirationMs)\n timers.setExpiration(replacementExpirationMs)\n\n expect(timers.getCurrentExpirationMs()).toBe(replacementExpirationMs)\n })\n})\n\nfunction renderAuthSessionTimers({\n isAuthenticated = true,\n onActivity = vi.fn(),\n onActivityRefresh = vi.fn(),\n onExpire = vi.fn(),\n onReminder = vi.fn(),\n}: {\n isAuthenticated?: boolean\n onActivity?: () => void\n onActivityRefresh?: () => void\n onExpire?: (expirationMs: number) => void\n onReminder?: () => void\n} = {}): RenderedAuthSessionTimers {\n let controller: AuthSessionTimers | undefined\n const container = document.createElement('div')\n const root = createRoot(container)\n\n function SessionTimers() {\n controller = useAuthSessionTimers({\n isAuthenticated,\n onActivity,\n onActivityRefresh,\n onExpire,\n onReminder,\n })\n\n return null\n }\n\n act(() => {\n root.render(React.createElement(SessionTimers))\n })\n\n timerCleanups.push(() => {\n act(() => {\n root.unmount()\n })\n })\n\n const getController = (): AuthSessionTimers => {\n if (!controller) {\n throw new Error('Expected auth session timers.')\n }\n\n return controller\n }\n\n return {\n clear: () => getController().clear(),\n getController,\n getCurrentExpirationMs: () => getController().getCurrentExpirationMs(),\n rerender: () => {\n act(() => {\n root.render(React.createElement(SessionTimers))\n })\n },\n recordActivity: (source) => getController().recordActivity(source),\n scheduleRefresh: (forceRefresh) => getController().scheduleRefresh(forceRefresh),\n setExpiration: (expirationMs) => getController().setExpiration(expirationMs),\n }\n}\n\ntype RenderedAuthSessionTimers = AuthSessionTimers & {\n getController: () => AuthSessionTimers\n recordActivity: (source: 'focus' | 'keydown' | 'mousemove' | 'route') => boolean\n rerender: () => void\n}\n"],"mappings":"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","ignoreList":[]}