@payloadcms/ui 4.0.0-canary.20 → 4.0.0-canary.21

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 (86) hide show
  1. package/dist/elements/CopyToClipboard/index.d.ts +1 -0
  2. package/dist/elements/CopyToClipboard/index.d.ts.map +1 -1
  3. package/dist/elements/CopyToClipboard/index.js +10 -8
  4. package/dist/elements/CopyToClipboard/index.js.map +1 -1
  5. package/dist/elements/FileManager/FileToolbar/index.d.ts.map +1 -1
  6. package/dist/elements/FileManager/FileToolbar/index.js +3 -0
  7. package/dist/elements/FileManager/FileToolbar/index.js.map +1 -1
  8. package/dist/exports/client/index.d.ts +2 -1
  9. package/dist/exports/client/index.d.ts.map +1 -1
  10. package/dist/exports/client/index.js +1 -1
  11. package/dist/exports/client/index.js.map +3 -3
  12. package/dist/forms/Form/index.d.ts.map +1 -1
  13. package/dist/forms/Form/index.js +4 -1
  14. package/dist/forms/Form/index.js.map +1 -1
  15. package/dist/providers/Auth/AuthSessionDebug/index.css +48 -0
  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/utilities.d.ts +21 -0
  21. package/dist/providers/Auth/AuthSessionDebug/utilities.d.ts.map +1 -0
  22. package/dist/providers/Auth/AuthSessionDebug/utilities.js +53 -0
  23. package/dist/providers/Auth/AuthSessionDebug/utilities.js.map +1 -0
  24. package/dist/providers/Auth/authSessionRequests.d.ts +27 -0
  25. package/dist/providers/Auth/authSessionRequests.d.ts.map +1 -0
  26. package/dist/providers/Auth/authSessionRequests.js +119 -0
  27. package/dist/providers/Auth/authSessionRequests.js.map +1 -0
  28. package/dist/providers/Auth/authSessionRequests.spec.js +121 -0
  29. package/dist/providers/Auth/authSessionRequests.spec.js.map +1 -0
  30. package/dist/providers/Auth/index.d.ts +2 -56
  31. package/dist/providers/Auth/index.d.ts.map +1 -1
  32. package/dist/providers/Auth/index.js +283 -135
  33. package/dist/providers/Auth/index.js.map +1 -1
  34. package/dist/providers/Auth/sessionActivity.d.ts +23 -0
  35. package/dist/providers/Auth/sessionActivity.d.ts.map +1 -0
  36. package/dist/providers/Auth/sessionActivity.js +49 -0
  37. package/dist/providers/Auth/sessionActivity.js.map +1 -0
  38. package/dist/providers/Auth/sessionActivity.spec.js +44 -0
  39. package/dist/providers/Auth/sessionActivity.spec.js.map +1 -0
  40. package/dist/providers/Auth/sessionTiming.d.ts +11 -0
  41. package/dist/providers/Auth/sessionTiming.d.ts.map +1 -0
  42. package/dist/providers/Auth/sessionTiming.js +18 -0
  43. package/dist/providers/Auth/sessionTiming.js.map +1 -0
  44. package/dist/providers/Auth/tabSessionSync/index.d.ts +17 -0
  45. package/dist/providers/Auth/tabSessionSync/index.d.ts.map +1 -0
  46. package/dist/providers/Auth/tabSessionSync/index.js +263 -0
  47. package/dist/providers/Auth/tabSessionSync/index.js.map +1 -0
  48. package/dist/providers/Auth/tabSessionSync/index.spec.js +463 -0
  49. package/dist/providers/Auth/tabSessionSync/index.spec.js.map +1 -0
  50. package/dist/providers/Auth/tabSessionSync/types.d.ts +107 -0
  51. package/dist/providers/Auth/tabSessionSync/types.d.ts.map +1 -0
  52. package/dist/providers/Auth/tabSessionSync/types.js +6 -0
  53. package/dist/providers/Auth/tabSessionSync/types.js.map +1 -0
  54. package/dist/providers/Auth/tabSessionSync/useTabSessionSync.d.ts +11 -0
  55. package/dist/providers/Auth/tabSessionSync/useTabSessionSync.d.ts.map +1 -0
  56. package/dist/providers/Auth/tabSessionSync/useTabSessionSync.js +90 -0
  57. package/dist/providers/Auth/tabSessionSync/useTabSessionSync.js.map +1 -0
  58. package/dist/providers/Auth/tabSessionSync/utilities.d.ts +26 -0
  59. package/dist/providers/Auth/tabSessionSync/utilities.d.ts.map +1 -0
  60. package/dist/providers/Auth/tabSessionSync/utilities.js +159 -0
  61. package/dist/providers/Auth/tabSessionSync/utilities.js.map +1 -0
  62. package/dist/providers/Auth/types.d.ts +66 -0
  63. package/dist/providers/Auth/types.d.ts.map +1 -0
  64. package/dist/providers/Auth/types.js +2 -0
  65. package/dist/providers/Auth/types.js.map +1 -0
  66. package/dist/providers/Auth/useAuthSessionTimers.d.ts +26 -0
  67. package/dist/providers/Auth/useAuthSessionTimers.d.ts.map +1 -0
  68. package/dist/providers/Auth/useAuthSessionTimers.js +203 -0
  69. package/dist/providers/Auth/useAuthSessionTimers.js.map +1 -0
  70. package/dist/providers/Auth/useAuthSessionTimers.spec.js +311 -0
  71. package/dist/providers/Auth/useAuthSessionTimers.spec.js.map +1 -0
  72. package/dist/providers/FormErrorHandler/index.d.ts +5 -1
  73. package/dist/providers/FormErrorHandler/index.d.ts.map +1 -1
  74. package/dist/providers/FormErrorHandler/index.js.map +1 -1
  75. package/dist/providers/Root/index.js +4 -4
  76. package/dist/providers/Root/index.js.map +1 -1
  77. package/dist/providers/RouteTransition/ProgressBar/index.js +4 -1
  78. package/dist/providers/RouteTransition/ProgressBar/index.js.map +1 -1
  79. package/dist/providers/RouteTransition/ProgressBar/index.spec.js +44 -0
  80. package/dist/providers/RouteTransition/ProgressBar/index.spec.js.map +1 -0
  81. package/dist/providers/RouteTransition/index.d.ts +2 -0
  82. package/dist/providers/RouteTransition/index.d.ts.map +1 -1
  83. package/dist/providers/RouteTransition/index.js +42 -14
  84. package/dist/providers/RouteTransition/index.js.map +1 -1
  85. package/dist/styles.css +1 -1
  86. package/package.json +5 -5
@@ -0,0 +1,463 @@
1
+ // @vitest-environment jsdom
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { TAB_SESSION_EVENT_TYPES, createTabSessionSync } from './index.js';
4
+ const tabSessionSyncCleanups = [];
5
+ beforeEach(() => {
6
+ resetMockBroadcastChannels();
7
+ });
8
+ afterEach(() => {
9
+ for (const cleanup of tabSessionSyncCleanups.splice(0)) {
10
+ cleanup();
11
+ }
12
+ vi.restoreAllMocks();
13
+ vi.unstubAllGlobals();
14
+ vi.clearAllMocks();
15
+ });
16
+ describe('createTabSessionSync', () => {
17
+ it('should broadcast refreshed sessions with source-tab and timing metadata', () => {
18
+ const session = createSession({
19
+ expirationMs: 20_000,
20
+ token: 'refreshed-token'
21
+ });
22
+ const tabSessionSync = createTestTabSessionSync({
23
+ now: () => 123
24
+ });
25
+ tabSessionSync.broadcast({
26
+ refreshStartedAt: 100,
27
+ session,
28
+ type: TAB_SESSION_EVENT_TYPES.REFRESHED
29
+ });
30
+ expect(getBroadcastChannel().postMessage).toHaveBeenCalledWith({
31
+ refreshStartedAt: 100,
32
+ session,
33
+ sourceTabID: 'local-tab',
34
+ sentAt: 123,
35
+ type: TAB_SESSION_EVENT_TYPES.REFRESHED
36
+ });
37
+ });
38
+ it('should apply a newer refresh when the provider shortens the token lifetime', () => {
39
+ const onSessionRefreshed = vi.fn();
40
+ const session = createSession({
41
+ expirationMs: 30_000
42
+ });
43
+ createTestTabSessionSync({
44
+ localExpirationMs: 40_000,
45
+ onSessionRefreshed
46
+ });
47
+ const channel = getBroadcastChannel();
48
+ channel.emit(createTabSessionMessage({
49
+ sentAt: 100,
50
+ session,
51
+ sourceTabID: 'refresh-tab',
52
+ type: TAB_SESSION_EVENT_TYPES.REFRESHED
53
+ }));
54
+ expect(onSessionRefreshed).toHaveBeenCalledWith(session);
55
+ });
56
+ it('should ignore expiration events for older sessions', () => {
57
+ const onSessionExpired = vi.fn();
58
+ createTestTabSessionSync({
59
+ localExpirationMs: 40_000,
60
+ onSessionExpired
61
+ });
62
+ const channel = getBroadcastChannel();
63
+ channel.emit(createTabSessionMessage({
64
+ expiredTokenAt: 30_000,
65
+ sourceTabID: 'stale-expiration-tab',
66
+ type: TAB_SESSION_EVENT_TYPES.EXPIRED
67
+ }));
68
+ expect(onSessionExpired).not.toHaveBeenCalled();
69
+ });
70
+ it('should converge on logout when equal-time events arrive in opposite orders', () => {
71
+ let firstState = 'initial';
72
+ let secondState = 'initial';
73
+ const refreshMessage = createTabSessionMessage({
74
+ sentAt: 500,
75
+ session: createSession({
76
+ expirationMs: 30_000,
77
+ token: 'refreshed-token'
78
+ }),
79
+ sourceTabID: 'refresh-tab',
80
+ type: TAB_SESSION_EVENT_TYPES.REFRESHED
81
+ });
82
+ const logoutMessage = createTabSessionMessage({
83
+ sentAt: 500,
84
+ sourceTabID: 'logout-tab',
85
+ type: TAB_SESSION_EVENT_TYPES.LOGGED_OUT
86
+ });
87
+ createTestTabSessionSync({
88
+ onSessionLoggedOut: () => {
89
+ firstState = 'logged-out';
90
+ },
91
+ onSessionRefreshed: () => {
92
+ firstState = 'refreshed';
93
+ },
94
+ sourceTabID: 'first-local-tab'
95
+ });
96
+ const firstChannel = getBroadcastChannel();
97
+ createTestTabSessionSync({
98
+ onSessionLoggedOut: () => {
99
+ secondState = 'logged-out';
100
+ },
101
+ onSessionRefreshed: () => {
102
+ secondState = 'refreshed';
103
+ },
104
+ sourceTabID: 'second-local-tab'
105
+ });
106
+ const secondChannel = getBroadcastChannel();
107
+ firstChannel.emit(refreshMessage);
108
+ firstChannel.emit(logoutMessage);
109
+ secondChannel.emit(logoutMessage);
110
+ secondChannel.emit(refreshMessage);
111
+ expect(firstState).toBe('logged-out');
112
+ expect(secondState).toBe('logged-out');
113
+ });
114
+ it('should keep logout ahead of a refresh that started earlier and broadcast later', () => {
115
+ const onSessionLoggedOut = vi.fn();
116
+ const onSessionRefreshed = vi.fn();
117
+ const refreshMessage = createTabSessionMessage({
118
+ refreshStartedAt: 100,
119
+ sentAt: 300,
120
+ session: createSession({
121
+ expirationMs: 30_000,
122
+ token: 'late-refreshed-token'
123
+ }),
124
+ sourceTabID: 'refresh-tab',
125
+ type: TAB_SESSION_EVENT_TYPES.REFRESHED
126
+ });
127
+ const logoutMessage = createTabSessionMessage({
128
+ sentAt: 200,
129
+ sourceTabID: 'logout-tab',
130
+ type: TAB_SESSION_EVENT_TYPES.LOGGED_OUT
131
+ });
132
+ createTestTabSessionSync({
133
+ onSessionLoggedOut,
134
+ onSessionRefreshed,
135
+ sourceTabID: 'receiving-tab'
136
+ });
137
+ const channel = getBroadcastChannel();
138
+ channel.emit(logoutMessage);
139
+ channel.emit(refreshMessage);
140
+ expect(onSessionLoggedOut).toHaveBeenCalledOnce();
141
+ expect(onSessionRefreshed).not.toHaveBeenCalled();
142
+ });
143
+ it('should apply refreshes in publication order instead of request-start ordering for refresh-vs-refresh', () => {
144
+ let appliedSession;
145
+ const onSessionRefreshed = vi.fn(session => {
146
+ appliedSession = session;
147
+ });
148
+ const refreshB = createTabSessionMessage({
149
+ refreshStartedAt: 200,
150
+ sentAt: 100,
151
+ session: createSession({
152
+ expirationMs: 30_000,
153
+ token: 'refresh-b'
154
+ }),
155
+ sourceTabID: 'refresh-b-tab',
156
+ type: TAB_SESSION_EVENT_TYPES.REFRESHED
157
+ });
158
+ const refreshA = createTabSessionMessage({
159
+ refreshStartedAt: 100,
160
+ sentAt: 200,
161
+ session: createSession({
162
+ expirationMs: 40_000,
163
+ token: 'refresh-a'
164
+ }),
165
+ sourceTabID: 'refresh-a-tab',
166
+ type: TAB_SESSION_EVENT_TYPES.REFRESHED
167
+ });
168
+ createTestTabSessionSync({
169
+ onSessionRefreshed,
170
+ sourceTabID: 'receiving-tab'
171
+ });
172
+ const channel = getBroadcastChannel();
173
+ channel.emit(refreshB);
174
+ channel.emit(refreshA);
175
+ expect(onSessionRefreshed).toHaveBeenCalledTimes(2);
176
+ expect(onSessionRefreshed).toHaveBeenLastCalledWith(refreshA.session);
177
+ expect(appliedSession).toBe(refreshA.session);
178
+ });
179
+ it('should ignore storage notifications while BroadcastChannel is healthy', async () => {
180
+ const reconcileSession = vi.fn().mockResolvedValue({
181
+ status: 'indeterminate'
182
+ });
183
+ createTestTabSessionSync({
184
+ reconcileSession,
185
+ sourceTabID: 'receiving-tab'
186
+ });
187
+ window.dispatchEvent(new StorageEvent('storage', {
188
+ key: 'payload:auth-session:tab-sync',
189
+ newValue: JSON.stringify({
190
+ affectedExpirationMs: 30_000,
191
+ refreshStartedAt: 100,
192
+ sentAt: 200,
193
+ sourceTabID: 'refreshing-tab',
194
+ type: TAB_SESSION_EVENT_TYPES.REFRESHED
195
+ })
196
+ }));
197
+ await Promise.resolve();
198
+ expect(reconcileSession).not.toHaveBeenCalled();
199
+ });
200
+ it('should ignore a late storage refresh whose request started before logout', () => {
201
+ vi.stubGlobal('BroadcastChannel', undefined);
202
+ const reconcileSession = vi.fn().mockResolvedValue({
203
+ status: 'indeterminate'
204
+ });
205
+ const onSessionLoggedOut = vi.fn();
206
+ createTestTabSessionSync({
207
+ reconcileSession,
208
+ onSessionLoggedOut,
209
+ sourceTabID: 'receiving-tab'
210
+ });
211
+ window.dispatchEvent(new StorageEvent('storage', {
212
+ key: 'payload:auth-session:tab-sync',
213
+ newValue: JSON.stringify({
214
+ affectedExpirationMs: 0,
215
+ settlesSentAt: 200,
216
+ sentAt: 201,
217
+ sourceTabID: 'logout-tab',
218
+ type: TAB_SESSION_EVENT_TYPES.LOGGED_OUT
219
+ })
220
+ }));
221
+ window.dispatchEvent(new StorageEvent('storage', {
222
+ key: 'payload:auth-session:tab-sync',
223
+ newValue: JSON.stringify({
224
+ affectedExpirationMs: 30_000,
225
+ refreshStartedAt: 100,
226
+ sentAt: 300,
227
+ sourceTabID: 'refresh-tab',
228
+ type: TAB_SESSION_EVENT_TYPES.REFRESHED
229
+ })
230
+ }));
231
+ expect(onSessionLoggedOut).toHaveBeenCalledOnce();
232
+ expect(reconcileSession).not.toHaveBeenCalled();
233
+ });
234
+ it('should keep storage fallback notification-only while reconciling refresh and settling logout', async () => {
235
+ vi.stubGlobal('BroadcastChannel', undefined);
236
+ const reconcileSession = vi.fn().mockResolvedValue({
237
+ status: 'indeterminate'
238
+ });
239
+ const onSessionLoggedOut = vi.fn();
240
+ createTestTabSessionSync({
241
+ reconcileSession,
242
+ onSessionLoggedOut,
243
+ sourceTabID: 'receiving-tab'
244
+ });
245
+ const setItem = vi.spyOn(Storage.prototype, 'setItem');
246
+ const broadcaster = createTestTabSessionSync({
247
+ now: () => 123,
248
+ sourceTabID: 'broadcasting-tab'
249
+ });
250
+ broadcaster.broadcast({
251
+ refreshStartedAt: 100,
252
+ session: createSession({
253
+ expirationMs: 20_000,
254
+ token: 'sensitive-token'
255
+ }),
256
+ type: TAB_SESSION_EVENT_TYPES.REFRESHED
257
+ });
258
+ const [key, value] = setItem.mock.calls[0];
259
+ expect(JSON.parse(value)).toEqual({
260
+ affectedExpirationMs: 20_000,
261
+ isBroadcastChannelFallback: true,
262
+ refreshStartedAt: 100,
263
+ type: TAB_SESSION_EVENT_TYPES.REFRESHED,
264
+ sentAt: 123,
265
+ sourceTabID: 'broadcasting-tab'
266
+ });
267
+ expect(value).not.toContain('sensitive-token');
268
+ expect(value).not.toContain('user');
269
+ window.dispatchEvent(new StorageEvent('storage', {
270
+ key,
271
+ newValue: value
272
+ }));
273
+ await Promise.resolve();
274
+ expect(reconcileSession).toHaveBeenCalledOnce();
275
+ reconcileSession.mockClear();
276
+ const logoutPublication = broadcaster.broadcast({
277
+ type: TAB_SESSION_EVENT_TYPES.LOGGED_OUT
278
+ });
279
+ expect(setItem).toHaveBeenCalledOnce();
280
+ expect(logoutPublication.type).toBe(TAB_SESSION_EVENT_TYPES.LOGGED_OUT);
281
+ if (logoutPublication.type !== TAB_SESSION_EVENT_TYPES.LOGGED_OUT) {
282
+ throw new Error('Expected a logged-out publication.');
283
+ }
284
+ broadcaster.broadcastLogoutSettlement(logoutPublication);
285
+ const [logoutKey, logoutValue] = setItem.mock.calls[1];
286
+ expect(JSON.parse(logoutValue)).toEqual({
287
+ affectedExpirationMs: 0,
288
+ isBroadcastChannelFallback: true,
289
+ settlesSentAt: 124,
290
+ sentAt: 125,
291
+ sourceTabID: 'broadcasting-tab',
292
+ type: TAB_SESSION_EVENT_TYPES.LOGGED_OUT
293
+ });
294
+ expect(logoutValue).not.toContain('sensitive-token');
295
+ expect(logoutValue).not.toContain('user');
296
+ window.dispatchEvent(new StorageEvent('storage', {
297
+ key: logoutKey,
298
+ newValue: logoutValue
299
+ }));
300
+ expect(onSessionLoggedOut).toHaveBeenCalledOnce();
301
+ expect(reconcileSession).not.toHaveBeenCalled();
302
+ await Promise.resolve();
303
+ expect(reconcileSession).not.toHaveBeenCalled();
304
+ });
305
+ it('should downgrade a failed channel and reconcile through storage', async () => {
306
+ const reconcileSession = vi.fn().mockResolvedValue({
307
+ status: 'indeterminate'
308
+ });
309
+ createTestTabSessionSync({
310
+ reconcileSession,
311
+ sourceTabID: 'storage-peer'
312
+ });
313
+ const addEventListener = vi.spyOn(window, 'addEventListener');
314
+ const setItem = vi.spyOn(Storage.prototype, 'setItem');
315
+ const broadcaster = createTestTabSessionSync({
316
+ sourceTabID: 'channel-broadcaster'
317
+ });
318
+ const failedChannel = getBroadcastChannel();
319
+ failedChannel.postMessage.mockImplementationOnce(() => {
320
+ throw new Error('channel closed');
321
+ });
322
+ broadcaster.broadcast({
323
+ refreshStartedAt: 100,
324
+ session: createSession({
325
+ expirationMs: 20_000
326
+ }),
327
+ type: TAB_SESSION_EVENT_TYPES.REFRESHED
328
+ });
329
+ expect(failedChannel.close).toHaveBeenCalledOnce();
330
+ expect(addEventListener).toHaveBeenCalledWith('storage', expect.any(Function));
331
+ expect(setItem).toHaveBeenCalledOnce();
332
+ const [key, newValue] = setItem.mock.calls[0];
333
+ window.dispatchEvent(new StorageEvent('storage', {
334
+ key,
335
+ newValue
336
+ }));
337
+ await Promise.resolve();
338
+ expect(reconcileSession).toHaveBeenCalledOnce();
339
+ });
340
+ it('should ignore a pending storage reconciliation after cleanup', async () => {
341
+ vi.stubGlobal('BroadcastChannel', undefined);
342
+ let resolveReconciliation;
343
+ const reconcileSession = vi.fn(() => new Promise(resolve => {
344
+ resolveReconciliation = resolve;
345
+ }));
346
+ const onTabSessionUnauthenticated = vi.fn();
347
+ const tabSessionSync = createTestTabSessionSync({
348
+ reconcileSession,
349
+ onTabSessionUnauthenticated
350
+ });
351
+ window.dispatchEvent(new StorageEvent('storage', {
352
+ key: 'payload:auth-session:tab-sync',
353
+ newValue: JSON.stringify({
354
+ affectedExpirationMs: 40_000,
355
+ refreshStartedAt: 400,
356
+ type: TAB_SESSION_EVENT_TYPES.REFRESHED,
357
+ sentAt: 500,
358
+ sourceTabID: 'remote-tab'
359
+ })
360
+ }));
361
+ tabSessionSync.cleanup();
362
+ resolveReconciliation?.({
363
+ status: 'unauthenticated'
364
+ });
365
+ await Promise.resolve();
366
+ expect(onTabSessionUnauthenticated).not.toHaveBeenCalled();
367
+ });
368
+ });
369
+ function createTestTabSessionSync({
370
+ reconcileSession = vi.fn().mockResolvedValue({
371
+ status: 'indeterminate'
372
+ }),
373
+ localExpirationMs,
374
+ now,
375
+ onSessionExpired = vi.fn(),
376
+ onSessionLoggedOut = vi.fn(),
377
+ onSessionRefreshed = vi.fn(),
378
+ onTabSessionUnauthenticated = vi.fn(),
379
+ sourceTabID = 'local-tab'
380
+ } = {}) {
381
+ const tabSessionSync = createTabSessionSync({
382
+ reconcileSession,
383
+ getTokenExpirationMs: () => localExpirationMs,
384
+ now,
385
+ onSessionExpired,
386
+ onSessionLoggedOut,
387
+ onSessionRefreshed,
388
+ onTabSessionUnauthenticated,
389
+ sourceTabID
390
+ });
391
+ tabSessionSyncCleanups.push(tabSessionSync.cleanup);
392
+ return tabSessionSync;
393
+ }
394
+ class MockBroadcastChannel {
395
+ static instances = [];
396
+ listeners = new Set();
397
+ addEventListener = vi.fn((_type, listener) => {
398
+ this.listeners.add(listener);
399
+ });
400
+ close = vi.fn();
401
+ postMessage = vi.fn();
402
+ removeEventListener = vi.fn((_type, listener) => {
403
+ this.listeners.delete(listener);
404
+ });
405
+ emit(message) {
406
+ for (const listener of this.listeners) {
407
+ listener(new MessageEvent('message', {
408
+ data: message
409
+ }));
410
+ }
411
+ }
412
+ }
413
+ function createTabSessionMessage(message) {
414
+ const {
415
+ sentAt = 100,
416
+ ...messageWithoutTimestamp
417
+ } = message;
418
+ if (messageWithoutTimestamp.type === TAB_SESSION_EVENT_TYPES.REFRESHED) {
419
+ const {
420
+ refreshStartedAt = sentAt,
421
+ ...refreshedMessage
422
+ } = messageWithoutTimestamp;
423
+ return {
424
+ ...refreshedMessage,
425
+ refreshStartedAt,
426
+ sentAt
427
+ };
428
+ }
429
+ return {
430
+ ...messageWithoutTimestamp,
431
+ sentAt
432
+ };
433
+ }
434
+ function createSession({
435
+ expirationMs,
436
+ token = 'token'
437
+ }) {
438
+ return {
439
+ exp: expirationMs / 1000,
440
+ token,
441
+ user: {
442
+ id: '1',
443
+ collection: 'users'
444
+ }
445
+ };
446
+ }
447
+ function getBroadcastChannel() {
448
+ const channel = MockBroadcastChannel.instances.at(-1);
449
+ if (!channel) {
450
+ throw new Error('Expected a BroadcastChannel instance');
451
+ }
452
+ return channel;
453
+ }
454
+ function resetMockBroadcastChannels() {
455
+ MockBroadcastChannel.instances.length = 0;
456
+ vi.stubGlobal('BroadcastChannel', class extends MockBroadcastChannel {
457
+ constructor() {
458
+ super();
459
+ MockBroadcastChannel.instances.push(this);
460
+ }
461
+ });
462
+ }
463
+ //# sourceMappingURL=index.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.spec.js","names":["afterEach","beforeEach","describe","expect","it","vi","TAB_SESSION_EVENT_TYPES","createTabSessionSync","tabSessionSyncCleanups","resetMockBroadcastChannels","cleanup","splice","restoreAllMocks","unstubAllGlobals","clearAllMocks","session","createSession","expirationMs","token","tabSessionSync","createTestTabSessionSync","now","broadcast","refreshStartedAt","type","REFRESHED","getBroadcastChannel","postMessage","toHaveBeenCalledWith","sourceTabID","sentAt","onSessionRefreshed","fn","localExpirationMs","channel","emit","createTabSessionMessage","onSessionExpired","expiredTokenAt","EXPIRED","not","toHaveBeenCalled","firstState","secondState","refreshMessage","logoutMessage","LOGGED_OUT","onSessionLoggedOut","firstChannel","secondChannel","toBe","toHaveBeenCalledOnce","appliedSession","refreshB","refreshA","toHaveBeenCalledTimes","toHaveBeenLastCalledWith","reconcileSession","mockResolvedValue","status","window","dispatchEvent","StorageEvent","key","newValue","JSON","stringify","affectedExpirationMs","Promise","resolve","stubGlobal","undefined","settlesSentAt","setItem","spyOn","Storage","prototype","broadcaster","value","mock","calls","parse","toEqual","isBroadcastChannelFallback","toContain","mockClear","logoutPublication","Error","broadcastLogoutSettlement","logoutKey","logoutValue","addEventListener","failedChannel","mockImplementationOnce","close","any","Function","resolveReconciliation","onTabSessionUnauthenticated","getTokenExpirationMs","push","MockBroadcastChannel","instances","listeners","Set","_type","listener","add","removeEventListener","delete","message","MessageEvent","data","messageWithoutTimestamp","refreshedMessage","exp","user","id","collection","at","length","constructor"],"sources":["../../../../src/providers/Auth/tabSessionSync/index.spec.ts"],"sourcesContent":["// @vitest-environment jsdom\nimport { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'\n\nimport type { UserWithToken } from '../types.js'\nimport type { TabSessionMessage, TabSessionReconciliationResult } from './index.js'\n\nimport { TAB_SESSION_EVENT_TYPES, createTabSessionSync } from './index.js'\n\nconst tabSessionSyncCleanups: Array<() => void> = []\n\nbeforeEach(() => {\n resetMockBroadcastChannels()\n})\n\nafterEach(() => {\n for (const cleanup of tabSessionSyncCleanups.splice(0)) {\n cleanup()\n }\n\n vi.restoreAllMocks()\n vi.unstubAllGlobals()\n vi.clearAllMocks()\n})\n\ndescribe('createTabSessionSync', () => {\n it('should broadcast refreshed sessions with source-tab and timing metadata', () => {\n const session = createSession({ expirationMs: 20_000, token: 'refreshed-token' })\n const tabSessionSync = createTestTabSessionSync({ now: () => 123 })\n\n tabSessionSync.broadcast({\n refreshStartedAt: 100,\n session,\n type: TAB_SESSION_EVENT_TYPES.REFRESHED,\n })\n\n expect(getBroadcastChannel().postMessage).toHaveBeenCalledWith({\n refreshStartedAt: 100,\n session,\n sourceTabID: 'local-tab',\n sentAt: 123,\n type: TAB_SESSION_EVENT_TYPES.REFRESHED,\n })\n })\n\n it('should apply a newer refresh when the provider shortens the token lifetime', () => {\n const onSessionRefreshed = vi.fn()\n const session = createSession({ expirationMs: 30_000 })\n\n createTestTabSessionSync({\n localExpirationMs: 40_000,\n onSessionRefreshed,\n })\n const channel = getBroadcastChannel()\n\n channel.emit(\n createTabSessionMessage({\n sentAt: 100,\n session,\n sourceTabID: 'refresh-tab',\n type: TAB_SESSION_EVENT_TYPES.REFRESHED,\n }),\n )\n\n expect(onSessionRefreshed).toHaveBeenCalledWith(session)\n })\n\n it('should ignore expiration events for older sessions', () => {\n const onSessionExpired = vi.fn()\n\n createTestTabSessionSync({\n localExpirationMs: 40_000,\n onSessionExpired,\n })\n const channel = getBroadcastChannel()\n\n channel.emit(\n createTabSessionMessage({\n expiredTokenAt: 30_000,\n sourceTabID: 'stale-expiration-tab',\n type: TAB_SESSION_EVENT_TYPES.EXPIRED,\n }),\n )\n\n expect(onSessionExpired).not.toHaveBeenCalled()\n })\n\n it('should converge on logout when equal-time events arrive in opposite orders', () => {\n let firstState = 'initial'\n let secondState = 'initial'\n const refreshMessage = createTabSessionMessage({\n sentAt: 500,\n session: createSession({ expirationMs: 30_000, token: 'refreshed-token' }),\n sourceTabID: 'refresh-tab',\n type: TAB_SESSION_EVENT_TYPES.REFRESHED,\n })\n const logoutMessage = createTabSessionMessage({\n sentAt: 500,\n sourceTabID: 'logout-tab',\n type: TAB_SESSION_EVENT_TYPES.LOGGED_OUT,\n })\n\n createTestTabSessionSync({\n onSessionLoggedOut: () => {\n firstState = 'logged-out'\n },\n onSessionRefreshed: () => {\n firstState = 'refreshed'\n },\n sourceTabID: 'first-local-tab',\n })\n const firstChannel = getBroadcastChannel()\n createTestTabSessionSync({\n onSessionLoggedOut: () => {\n secondState = 'logged-out'\n },\n onSessionRefreshed: () => {\n secondState = 'refreshed'\n },\n sourceTabID: 'second-local-tab',\n })\n const secondChannel = getBroadcastChannel()\n\n firstChannel.emit(refreshMessage)\n firstChannel.emit(logoutMessage)\n secondChannel.emit(logoutMessage)\n secondChannel.emit(refreshMessage)\n\n expect(firstState).toBe('logged-out')\n expect(secondState).toBe('logged-out')\n })\n\n it('should keep logout ahead of a refresh that started earlier and broadcast later', () => {\n const onSessionLoggedOut = vi.fn()\n const onSessionRefreshed = vi.fn()\n const refreshMessage = createTabSessionMessage({\n refreshStartedAt: 100,\n sentAt: 300,\n session: createSession({ expirationMs: 30_000, token: 'late-refreshed-token' }),\n sourceTabID: 'refresh-tab',\n type: TAB_SESSION_EVENT_TYPES.REFRESHED,\n })\n const logoutMessage = createTabSessionMessage({\n sentAt: 200,\n sourceTabID: 'logout-tab',\n type: TAB_SESSION_EVENT_TYPES.LOGGED_OUT,\n })\n\n createTestTabSessionSync({\n onSessionLoggedOut,\n onSessionRefreshed,\n sourceTabID: 'receiving-tab',\n })\n const channel = getBroadcastChannel()\n\n channel.emit(logoutMessage)\n channel.emit(refreshMessage)\n\n expect(onSessionLoggedOut).toHaveBeenCalledOnce()\n expect(onSessionRefreshed).not.toHaveBeenCalled()\n })\n\n it('should apply refreshes in publication order instead of request-start ordering for refresh-vs-refresh', () => {\n let appliedSession: UserWithToken | undefined\n const onSessionRefreshed = vi.fn((session: UserWithToken) => {\n appliedSession = session\n })\n const refreshB = createTabSessionMessage({\n refreshStartedAt: 200,\n sentAt: 100,\n session: createSession({ expirationMs: 30_000, token: 'refresh-b' }),\n sourceTabID: 'refresh-b-tab',\n type: TAB_SESSION_EVENT_TYPES.REFRESHED,\n })\n const refreshA = createTabSessionMessage({\n refreshStartedAt: 100,\n sentAt: 200,\n session: createSession({ expirationMs: 40_000, token: 'refresh-a' }),\n sourceTabID: 'refresh-a-tab',\n type: TAB_SESSION_EVENT_TYPES.REFRESHED,\n })\n\n createTestTabSessionSync({ onSessionRefreshed, sourceTabID: 'receiving-tab' })\n const channel = getBroadcastChannel()\n\n channel.emit(refreshB)\n channel.emit(refreshA)\n\n expect(onSessionRefreshed).toHaveBeenCalledTimes(2)\n expect(onSessionRefreshed).toHaveBeenLastCalledWith(refreshA.session)\n expect(appliedSession).toBe(refreshA.session)\n })\n\n it('should ignore storage notifications while BroadcastChannel is healthy', async () => {\n const reconcileSession = vi.fn().mockResolvedValue({ status: 'indeterminate' } as const)\n\n createTestTabSessionSync({ reconcileSession, sourceTabID: 'receiving-tab' })\n\n window.dispatchEvent(\n new StorageEvent('storage', {\n key: 'payload:auth-session:tab-sync',\n newValue: JSON.stringify({\n affectedExpirationMs: 30_000,\n refreshStartedAt: 100,\n sentAt: 200,\n sourceTabID: 'refreshing-tab',\n type: TAB_SESSION_EVENT_TYPES.REFRESHED,\n }),\n }),\n )\n await Promise.resolve()\n\n expect(reconcileSession).not.toHaveBeenCalled()\n })\n\n it('should ignore a late storage refresh whose request started before logout', () => {\n vi.stubGlobal('BroadcastChannel', undefined)\n const reconcileSession = vi.fn().mockResolvedValue({ status: 'indeterminate' } as const)\n const onSessionLoggedOut = vi.fn()\n\n createTestTabSessionSync({\n reconcileSession,\n onSessionLoggedOut,\n sourceTabID: 'receiving-tab',\n })\n\n window.dispatchEvent(\n new StorageEvent('storage', {\n key: 'payload:auth-session:tab-sync',\n newValue: JSON.stringify({\n affectedExpirationMs: 0,\n settlesSentAt: 200,\n sentAt: 201,\n sourceTabID: 'logout-tab',\n type: TAB_SESSION_EVENT_TYPES.LOGGED_OUT,\n }),\n }),\n )\n window.dispatchEvent(\n new StorageEvent('storage', {\n key: 'payload:auth-session:tab-sync',\n newValue: JSON.stringify({\n affectedExpirationMs: 30_000,\n refreshStartedAt: 100,\n sentAt: 300,\n sourceTabID: 'refresh-tab',\n type: TAB_SESSION_EVENT_TYPES.REFRESHED,\n }),\n }),\n )\n\n expect(onSessionLoggedOut).toHaveBeenCalledOnce()\n expect(reconcileSession).not.toHaveBeenCalled()\n })\n\n it('should keep storage fallback notification-only while reconciling refresh and settling logout', async () => {\n vi.stubGlobal('BroadcastChannel', undefined)\n const reconcileSession = vi.fn().mockResolvedValue({ status: 'indeterminate' })\n const onSessionLoggedOut = vi.fn()\n\n createTestTabSessionSync({\n reconcileSession,\n onSessionLoggedOut,\n sourceTabID: 'receiving-tab',\n })\n const setItem = vi.spyOn(Storage.prototype, 'setItem')\n const broadcaster = createTestTabSessionSync({\n now: () => 123,\n sourceTabID: 'broadcasting-tab',\n })\n\n broadcaster.broadcast({\n refreshStartedAt: 100,\n session: createSession({ expirationMs: 20_000, token: 'sensitive-token' }),\n type: TAB_SESSION_EVENT_TYPES.REFRESHED,\n })\n const [key, value] = setItem.mock.calls[0] as [string, string]\n\n expect(JSON.parse(value)).toEqual({\n affectedExpirationMs: 20_000,\n isBroadcastChannelFallback: true,\n refreshStartedAt: 100,\n type: TAB_SESSION_EVENT_TYPES.REFRESHED,\n sentAt: 123,\n sourceTabID: 'broadcasting-tab',\n })\n expect(value).not.toContain('sensitive-token')\n expect(value).not.toContain('user')\n\n window.dispatchEvent(new StorageEvent('storage', { key, newValue: value }))\n await Promise.resolve()\n\n expect(reconcileSession).toHaveBeenCalledOnce()\n\n reconcileSession.mockClear()\n\n const logoutPublication = broadcaster.broadcast({\n type: TAB_SESSION_EVENT_TYPES.LOGGED_OUT,\n })\n\n expect(setItem).toHaveBeenCalledOnce()\n expect(logoutPublication.type).toBe(TAB_SESSION_EVENT_TYPES.LOGGED_OUT)\n\n if (logoutPublication.type !== TAB_SESSION_EVENT_TYPES.LOGGED_OUT) {\n throw new Error('Expected a logged-out publication.')\n }\n\n broadcaster.broadcastLogoutSettlement(logoutPublication)\n\n const [logoutKey, logoutValue] = setItem.mock.calls[1] as [string, string]\n\n expect(JSON.parse(logoutValue)).toEqual({\n affectedExpirationMs: 0,\n isBroadcastChannelFallback: true,\n settlesSentAt: 124,\n sentAt: 125,\n sourceTabID: 'broadcasting-tab',\n type: TAB_SESSION_EVENT_TYPES.LOGGED_OUT,\n })\n expect(logoutValue).not.toContain('sensitive-token')\n expect(logoutValue).not.toContain('user')\n\n window.dispatchEvent(new StorageEvent('storage', { key: logoutKey, newValue: logoutValue }))\n\n expect(onSessionLoggedOut).toHaveBeenCalledOnce()\n expect(reconcileSession).not.toHaveBeenCalled()\n\n await Promise.resolve()\n\n expect(reconcileSession).not.toHaveBeenCalled()\n })\n\n it('should downgrade a failed channel and reconcile through storage', async () => {\n const reconcileSession = vi.fn().mockResolvedValue({ status: 'indeterminate' })\n\n createTestTabSessionSync({ reconcileSession, sourceTabID: 'storage-peer' })\n const addEventListener = vi.spyOn(window, 'addEventListener')\n const setItem = vi.spyOn(Storage.prototype, 'setItem')\n const broadcaster = createTestTabSessionSync({ sourceTabID: 'channel-broadcaster' })\n const failedChannel = getBroadcastChannel()\n\n failedChannel.postMessage.mockImplementationOnce(() => {\n throw new Error('channel closed')\n })\n broadcaster.broadcast({\n refreshStartedAt: 100,\n session: createSession({ expirationMs: 20_000 }),\n type: TAB_SESSION_EVENT_TYPES.REFRESHED,\n })\n\n expect(failedChannel.close).toHaveBeenCalledOnce()\n expect(addEventListener).toHaveBeenCalledWith('storage', expect.any(Function))\n expect(setItem).toHaveBeenCalledOnce()\n\n const [key, newValue] = setItem.mock.calls[0] as [string, string]\n\n window.dispatchEvent(new StorageEvent('storage', { key, newValue }))\n await Promise.resolve()\n\n expect(reconcileSession).toHaveBeenCalledOnce()\n })\n\n it('should ignore a pending storage reconciliation after cleanup', async () => {\n vi.stubGlobal('BroadcastChannel', undefined)\n let resolveReconciliation: ((result: TabSessionReconciliationResult) => void) | undefined\n const reconcileSession = vi.fn(\n () =>\n new Promise<TabSessionReconciliationResult>((resolve) => {\n resolveReconciliation = resolve\n }),\n )\n const onTabSessionUnauthenticated = vi.fn()\n const tabSessionSync = createTestTabSessionSync({\n reconcileSession,\n onTabSessionUnauthenticated,\n })\n\n window.dispatchEvent(\n new StorageEvent('storage', {\n key: 'payload:auth-session:tab-sync',\n newValue: JSON.stringify({\n affectedExpirationMs: 40_000,\n refreshStartedAt: 400,\n type: TAB_SESSION_EVENT_TYPES.REFRESHED,\n sentAt: 500,\n sourceTabID: 'remote-tab',\n }),\n }),\n )\n tabSessionSync.cleanup()\n resolveReconciliation?.({ status: 'unauthenticated' })\n await Promise.resolve()\n\n expect(onTabSessionUnauthenticated).not.toHaveBeenCalled()\n })\n})\n\nfunction createTestTabSessionSync({\n reconcileSession = vi.fn().mockResolvedValue({ status: 'indeterminate' } as const),\n localExpirationMs,\n now,\n onSessionExpired = vi.fn(),\n onSessionLoggedOut = vi.fn(),\n onSessionRefreshed = vi.fn(),\n onTabSessionUnauthenticated = vi.fn(),\n sourceTabID = 'local-tab',\n}: {\n reconcileSession?: () => Promise<TabSessionReconciliationResult>\n localExpirationMs?: number\n now?: () => number\n onSessionExpired?: (expiredTokenAt: number) => void\n onSessionLoggedOut?: () => void\n onSessionRefreshed?: (session: UserWithToken) => void\n onTabSessionUnauthenticated?: () => void\n sourceTabID?: string\n} = {}) {\n const tabSessionSync = createTabSessionSync({\n reconcileSession,\n getTokenExpirationMs: () => localExpirationMs,\n now,\n onSessionExpired,\n onSessionLoggedOut,\n onSessionRefreshed,\n onTabSessionUnauthenticated,\n sourceTabID,\n })\n\n tabSessionSyncCleanups.push(tabSessionSync.cleanup)\n\n return tabSessionSync\n}\n\nclass MockBroadcastChannel {\n static instances: MockBroadcastChannel[] = []\n\n listeners = new Set<(event: MessageEvent<TabSessionMessage>) => void>()\n addEventListener = vi.fn(\n (_type: string, listener: (event: MessageEvent<TabSessionMessage>) => void) => {\n this.listeners.add(listener)\n },\n )\n close = vi.fn()\n postMessage = vi.fn()\n removeEventListener = vi.fn(\n (_type: string, listener: (event: MessageEvent<TabSessionMessage>) => void) => {\n this.listeners.delete(listener)\n },\n )\n\n emit(message: TabSessionMessage): void {\n for (const listener of this.listeners) {\n listener(new MessageEvent('message', { data: message }))\n }\n }\n}\n\nfunction createTabSessionMessage(\n message:\n | ({\n sentAt?: number\n } & Omit<\n Extract<TabSessionMessage, { type: typeof TAB_SESSION_EVENT_TYPES.EXPIRED }>,\n 'sentAt'\n >)\n | ({\n sentAt?: number\n } & Omit<\n Extract<TabSessionMessage, { type: typeof TAB_SESSION_EVENT_TYPES.LOGGED_OUT }>,\n 'sentAt'\n >)\n | ({\n refreshStartedAt?: number\n sentAt?: number\n } & Omit<\n Extract<TabSessionMessage, { type: typeof TAB_SESSION_EVENT_TYPES.REFRESHED }>,\n 'refreshStartedAt' | 'sentAt'\n >),\n): TabSessionMessage {\n const { sentAt = 100, ...messageWithoutTimestamp } = message\n\n if (messageWithoutTimestamp.type === TAB_SESSION_EVENT_TYPES.REFRESHED) {\n const { refreshStartedAt = sentAt, ...refreshedMessage } = messageWithoutTimestamp\n\n return { ...refreshedMessage, refreshStartedAt, sentAt }\n }\n\n return { ...messageWithoutTimestamp, sentAt } as TabSessionMessage\n}\n\nfunction createSession({\n expirationMs,\n token = 'token',\n}: {\n expirationMs: number\n token?: string\n}): UserWithToken {\n return {\n exp: expirationMs / 1000,\n token,\n user: { id: '1', collection: 'users' },\n }\n}\n\nfunction getBroadcastChannel(): MockBroadcastChannel {\n const channel = MockBroadcastChannel.instances.at(-1)\n\n if (!channel) {\n throw new Error('Expected a BroadcastChannel instance')\n }\n\n return channel\n}\n\nfunction resetMockBroadcastChannels(): void {\n MockBroadcastChannel.instances.length = 0\n vi.stubGlobal(\n 'BroadcastChannel',\n class extends MockBroadcastChannel {\n constructor() {\n super()\n MockBroadcastChannel.instances.push(this)\n }\n },\n )\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,107 @@
1
+ import type { UserWithToken } from '../types.js';
2
+ export declare const TAB_SESSION_EVENT_TYPES: {
3
+ readonly EXPIRED: 'session-expired';
4
+ readonly LOGGED_OUT: 'session-logged-out';
5
+ readonly REFRESHED: 'session-refreshed';
6
+ };
7
+ export type TabSessionEventType = (typeof TAB_SESSION_EVENT_TYPES)[keyof typeof TAB_SESSION_EVENT_TYPES];
8
+ export type TabSessionMessage = {
9
+ expiredTokenAt: number;
10
+ sentAt: number;
11
+ sourceTabID: string;
12
+ type: typeof TAB_SESSION_EVENT_TYPES.EXPIRED;
13
+ } | {
14
+ refreshStartedAt: number;
15
+ sentAt: number;
16
+ session: UserWithToken;
17
+ sourceTabID: string;
18
+ type: typeof TAB_SESSION_EVENT_TYPES.REFRESHED;
19
+ } | {
20
+ sentAt: number;
21
+ sourceTabID: string;
22
+ type: typeof TAB_SESSION_EVENT_TYPES.LOGGED_OUT;
23
+ };
24
+ export type TabSessionEvent = {
25
+ expiredTokenAt: number;
26
+ type: typeof TAB_SESSION_EVENT_TYPES.EXPIRED;
27
+ } | {
28
+ refreshStartedAt: number;
29
+ session: UserWithToken;
30
+ type: typeof TAB_SESSION_EVENT_TYPES.REFRESHED;
31
+ } | {
32
+ type: typeof TAB_SESSION_EVENT_TYPES.LOGGED_OUT;
33
+ };
34
+ export type TabSessionReconciliationOptions = {
35
+ isTabSessionEventStale: () => boolean;
36
+ };
37
+ export type TabSessionReconciliationResult<T = unknown> = {
38
+ expirationMs: number;
39
+ status: 'authenticated';
40
+ user: T;
41
+ } | {
42
+ status: 'indeterminate';
43
+ } | {
44
+ status: 'unauthenticated';
45
+ };
46
+ export type CreateTabSessionSyncArgs = {
47
+ getTokenExpirationMs: () => number | undefined;
48
+ now?: () => number;
49
+ onSessionExpired: (expiredTokenAt: number) => void;
50
+ onSessionLoggedOut: () => void;
51
+ onSessionRefreshed: (session: UserWithToken) => void;
52
+ onTabSessionUnauthenticated: () => void;
53
+ reconcileSession: (options: TabSessionReconciliationOptions) => Promise<TabSessionReconciliationResult>;
54
+ sourceTabID: string;
55
+ };
56
+ export type TabSessionPublication = {
57
+ affectedExpirationMs: 0;
58
+ sentAt: number;
59
+ sourceTabID: string;
60
+ type: typeof TAB_SESSION_EVENT_TYPES.LOGGED_OUT;
61
+ } | {
62
+ affectedExpirationMs: number;
63
+ refreshStartedAt: number;
64
+ sentAt: number;
65
+ sourceTabID: string;
66
+ type: typeof TAB_SESSION_EVENT_TYPES.REFRESHED;
67
+ } | {
68
+ affectedExpirationMs: number;
69
+ sentAt: number;
70
+ sourceTabID: string;
71
+ type: typeof TAB_SESSION_EVENT_TYPES.EXPIRED;
72
+ };
73
+ export type TabSessionLogoutPublication = Extract<TabSessionPublication, {
74
+ type: typeof TAB_SESSION_EVENT_TYPES.LOGGED_OUT;
75
+ }>;
76
+ export type TabSessionLifecycleOrder = TabSessionPublication;
77
+ export type TabSessionStorageNotification = {
78
+ isBroadcastChannelFallback?: true;
79
+ } & (({
80
+ settlesSentAt: number;
81
+ } & TabSessionLogoutPublication) | Extract<TabSessionPublication, {
82
+ type: typeof TAB_SESSION_EVENT_TYPES.EXPIRED;
83
+ }> | Extract<TabSessionPublication, {
84
+ type: typeof TAB_SESSION_EVENT_TYPES.REFRESHED;
85
+ }>);
86
+ export type WriteStorageNotification = (args: {
87
+ notification: TabSessionStorageNotification;
88
+ }) => void;
89
+ export type OnExpiredSession = (args: {
90
+ lifecycleOrder: TabSessionLifecycleOrder;
91
+ message: Extract<TabSessionMessage, {
92
+ type: typeof TAB_SESSION_EVENT_TYPES.EXPIRED;
93
+ }>;
94
+ }) => void;
95
+ export type OnLoggedOutSession = (args: {
96
+ lifecycleOrder: TabSessionLifecycleOrder;
97
+ }) => void;
98
+ export type OnRefreshedSession = (args: {
99
+ lifecycleOrder: TabSessionLifecycleOrder;
100
+ message: Extract<TabSessionMessage, {
101
+ type: typeof TAB_SESSION_EVENT_TYPES.REFRESHED;
102
+ }>;
103
+ }) => void;
104
+ export type ReconcileStorageSession = (args: {
105
+ notification: TabSessionStorageNotification;
106
+ }) => void;
107
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/providers/Auth/tabSessionSync/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export const TAB_SESSION_EVENT_TYPES = {
2
+ EXPIRED: 'session-expired',
3
+ LOGGED_OUT: 'session-logged-out',
4
+ REFRESHED: 'session-refreshed'
5
+ };
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","names":["TAB_SESSION_EVENT_TYPES","EXPIRED","LOGGED_OUT","REFRESHED"],"sources":["../../../../src/providers/Auth/tabSessionSync/types.ts"],"sourcesContent":["import type { UserWithToken } from '../types.js'\n\nexport const TAB_SESSION_EVENT_TYPES = {\n EXPIRED: 'session-expired',\n LOGGED_OUT: 'session-logged-out',\n REFRESHED: 'session-refreshed',\n} as const\n\nexport type TabSessionEventType =\n (typeof TAB_SESSION_EVENT_TYPES)[keyof typeof TAB_SESSION_EVENT_TYPES]\n\nexport type TabSessionMessage =\n | {\n expiredTokenAt: number\n sentAt: number\n sourceTabID: string\n type: typeof TAB_SESSION_EVENT_TYPES.EXPIRED\n }\n | {\n refreshStartedAt: number\n sentAt: number\n session: UserWithToken\n sourceTabID: string\n type: typeof TAB_SESSION_EVENT_TYPES.REFRESHED\n }\n | {\n sentAt: number\n sourceTabID: string\n type: typeof TAB_SESSION_EVENT_TYPES.LOGGED_OUT\n }\n\nexport type TabSessionEvent =\n | {\n expiredTokenAt: number\n type: typeof TAB_SESSION_EVENT_TYPES.EXPIRED\n }\n | {\n refreshStartedAt: number\n session: UserWithToken\n type: typeof TAB_SESSION_EVENT_TYPES.REFRESHED\n }\n | {\n type: typeof TAB_SESSION_EVENT_TYPES.LOGGED_OUT\n }\n\nexport type TabSessionReconciliationOptions = {\n isTabSessionEventStale: () => boolean\n}\n\nexport type TabSessionReconciliationResult<T = unknown> =\n | {\n expirationMs: number\n status: 'authenticated'\n user: T\n }\n | {\n status: 'indeterminate'\n }\n | {\n status: 'unauthenticated'\n }\n\nexport type CreateTabSessionSyncArgs = {\n getTokenExpirationMs: () => number | undefined\n now?: () => number\n onSessionExpired: (expiredTokenAt: number) => void\n onSessionLoggedOut: () => void\n onSessionRefreshed: (session: UserWithToken) => void\n onTabSessionUnauthenticated: () => void\n reconcileSession: (\n options: TabSessionReconciliationOptions,\n ) => Promise<TabSessionReconciliationResult>\n sourceTabID: string\n}\n\nexport type TabSessionPublication =\n | {\n affectedExpirationMs: 0\n sentAt: number\n sourceTabID: string\n type: typeof TAB_SESSION_EVENT_TYPES.LOGGED_OUT\n }\n | {\n affectedExpirationMs: number\n refreshStartedAt: number\n sentAt: number\n sourceTabID: string\n type: typeof TAB_SESSION_EVENT_TYPES.REFRESHED\n }\n | {\n affectedExpirationMs: number\n sentAt: number\n sourceTabID: string\n type: typeof TAB_SESSION_EVENT_TYPES.EXPIRED\n }\n\nexport type TabSessionLogoutPublication = Extract<\n TabSessionPublication,\n { type: typeof TAB_SESSION_EVENT_TYPES.LOGGED_OUT }\n>\n\nexport type TabSessionLifecycleOrder = TabSessionPublication\n\nexport type TabSessionStorageNotification = {\n isBroadcastChannelFallback?: true\n} & (\n | ({ settlesSentAt: number } & TabSessionLogoutPublication)\n | Extract<TabSessionPublication, { type: typeof TAB_SESSION_EVENT_TYPES.EXPIRED }>\n | Extract<TabSessionPublication, { type: typeof TAB_SESSION_EVENT_TYPES.REFRESHED }>\n)\n\nexport type WriteStorageNotification = (args: {\n notification: TabSessionStorageNotification\n}) => void\n\nexport type OnExpiredSession = (args: {\n lifecycleOrder: TabSessionLifecycleOrder\n message: Extract<TabSessionMessage, { type: typeof TAB_SESSION_EVENT_TYPES.EXPIRED }>\n}) => void\n\nexport type OnLoggedOutSession = (args: { lifecycleOrder: TabSessionLifecycleOrder }) => void\n\nexport type OnRefreshedSession = (args: {\n lifecycleOrder: TabSessionLifecycleOrder\n message: Extract<TabSessionMessage, { type: typeof TAB_SESSION_EVENT_TYPES.REFRESHED }>\n}) => void\n\nexport type ReconcileStorageSession = (args: {\n notification: TabSessionStorageNotification\n}) => void\n"],"mappings":"AAEA,OAAO,MAAMA,uBAAA,GAA0B;EACrCC,OAAA,EAAS;EACTC,UAAA,EAAY;EACZC,SAAA,EAAW;AACb","ignoreList":[]}
@@ -0,0 +1,11 @@
1
+ import type { TabSessionEvent, TabSessionLogoutPublication, TabSessionPublication } from './index.js';
2
+ import { createTabSessionSync } from './index.js';
3
+ export type TabSessionSync = {
4
+ /** Broadcasts a session lifecycle event to the other browser tabs. */
5
+ broadcast: (event: TabSessionEvent) => TabSessionPublication | undefined;
6
+ /** Notifies storage-fallback tabs after the server-side logout request settles. */
7
+ broadcastLogoutSettlement: (publication: TabSessionLogoutPublication) => void;
8
+ };
9
+ /** Connects the Auth provider to one cross-tab session coordinator for the current browser tab. */
10
+ export declare function useTabSessionSync(options: Omit<Parameters<typeof createTabSessionSync>[0], 'sourceTabID'>): TabSessionSync;
11
+ //# sourceMappingURL=useTabSessionSync.d.ts.map