@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.
- package/dist/elements/CopyToClipboard/index.d.ts +1 -0
- package/dist/elements/CopyToClipboard/index.d.ts.map +1 -1
- package/dist/elements/CopyToClipboard/index.js +10 -8
- package/dist/elements/CopyToClipboard/index.js.map +1 -1
- package/dist/elements/FileManager/FileToolbar/index.d.ts.map +1 -1
- package/dist/elements/FileManager/FileToolbar/index.js +3 -0
- package/dist/elements/FileManager/FileToolbar/index.js.map +1 -1
- package/dist/exports/client/index.d.ts +2 -1
- package/dist/exports/client/index.d.ts.map +1 -1
- package/dist/exports/client/index.js +1 -1
- package/dist/exports/client/index.js.map +3 -3
- package/dist/forms/Form/index.d.ts.map +1 -1
- package/dist/forms/Form/index.js +4 -1
- package/dist/forms/Form/index.js.map +1 -1
- package/dist/providers/Auth/AuthSessionDebug/index.css +48 -0
- package/dist/providers/Auth/AuthSessionDebug/index.d.ts +13 -0
- package/dist/providers/Auth/AuthSessionDebug/index.d.ts.map +1 -0
- package/dist/providers/Auth/AuthSessionDebug/index.js +93 -0
- package/dist/providers/Auth/AuthSessionDebug/index.js.map +1 -0
- package/dist/providers/Auth/AuthSessionDebug/utilities.d.ts +21 -0
- package/dist/providers/Auth/AuthSessionDebug/utilities.d.ts.map +1 -0
- package/dist/providers/Auth/AuthSessionDebug/utilities.js +53 -0
- package/dist/providers/Auth/AuthSessionDebug/utilities.js.map +1 -0
- package/dist/providers/Auth/authSessionRequests.d.ts +27 -0
- package/dist/providers/Auth/authSessionRequests.d.ts.map +1 -0
- package/dist/providers/Auth/authSessionRequests.js +119 -0
- package/dist/providers/Auth/authSessionRequests.js.map +1 -0
- package/dist/providers/Auth/authSessionRequests.spec.js +121 -0
- package/dist/providers/Auth/authSessionRequests.spec.js.map +1 -0
- package/dist/providers/Auth/index.d.ts +2 -56
- package/dist/providers/Auth/index.d.ts.map +1 -1
- package/dist/providers/Auth/index.js +283 -135
- package/dist/providers/Auth/index.js.map +1 -1
- package/dist/providers/Auth/sessionActivity.d.ts +23 -0
- package/dist/providers/Auth/sessionActivity.d.ts.map +1 -0
- package/dist/providers/Auth/sessionActivity.js +49 -0
- package/dist/providers/Auth/sessionActivity.js.map +1 -0
- package/dist/providers/Auth/sessionActivity.spec.js +44 -0
- package/dist/providers/Auth/sessionActivity.spec.js.map +1 -0
- package/dist/providers/Auth/sessionTiming.d.ts +11 -0
- package/dist/providers/Auth/sessionTiming.d.ts.map +1 -0
- package/dist/providers/Auth/sessionTiming.js +18 -0
- package/dist/providers/Auth/sessionTiming.js.map +1 -0
- package/dist/providers/Auth/tabSessionSync/index.d.ts +17 -0
- package/dist/providers/Auth/tabSessionSync/index.d.ts.map +1 -0
- package/dist/providers/Auth/tabSessionSync/index.js +263 -0
- package/dist/providers/Auth/tabSessionSync/index.js.map +1 -0
- package/dist/providers/Auth/tabSessionSync/index.spec.js +463 -0
- package/dist/providers/Auth/tabSessionSync/index.spec.js.map +1 -0
- package/dist/providers/Auth/tabSessionSync/types.d.ts +107 -0
- package/dist/providers/Auth/tabSessionSync/types.d.ts.map +1 -0
- package/dist/providers/Auth/tabSessionSync/types.js +6 -0
- package/dist/providers/Auth/tabSessionSync/types.js.map +1 -0
- package/dist/providers/Auth/tabSessionSync/useTabSessionSync.d.ts +11 -0
- package/dist/providers/Auth/tabSessionSync/useTabSessionSync.d.ts.map +1 -0
- package/dist/providers/Auth/tabSessionSync/useTabSessionSync.js +90 -0
- package/dist/providers/Auth/tabSessionSync/useTabSessionSync.js.map +1 -0
- package/dist/providers/Auth/tabSessionSync/utilities.d.ts +26 -0
- package/dist/providers/Auth/tabSessionSync/utilities.d.ts.map +1 -0
- package/dist/providers/Auth/tabSessionSync/utilities.js +159 -0
- package/dist/providers/Auth/tabSessionSync/utilities.js.map +1 -0
- package/dist/providers/Auth/types.d.ts +66 -0
- package/dist/providers/Auth/types.d.ts.map +1 -0
- package/dist/providers/Auth/types.js +2 -0
- package/dist/providers/Auth/types.js.map +1 -0
- package/dist/providers/Auth/useAuthSessionTimers.d.ts +26 -0
- package/dist/providers/Auth/useAuthSessionTimers.d.ts.map +1 -0
- package/dist/providers/Auth/useAuthSessionTimers.js +203 -0
- package/dist/providers/Auth/useAuthSessionTimers.js.map +1 -0
- package/dist/providers/Auth/useAuthSessionTimers.spec.js +311 -0
- package/dist/providers/Auth/useAuthSessionTimers.spec.js.map +1 -0
- package/dist/providers/FormErrorHandler/index.d.ts +5 -1
- package/dist/providers/FormErrorHandler/index.d.ts.map +1 -1
- package/dist/providers/FormErrorHandler/index.js.map +1 -1
- package/dist/providers/Root/index.js +4 -4
- package/dist/providers/Root/index.js.map +1 -1
- package/dist/providers/RouteTransition/ProgressBar/index.js +4 -1
- package/dist/providers/RouteTransition/ProgressBar/index.js.map +1 -1
- package/dist/providers/RouteTransition/ProgressBar/index.spec.js +44 -0
- package/dist/providers/RouteTransition/ProgressBar/index.spec.js.map +1 -0
- package/dist/providers/RouteTransition/index.d.ts +2 -0
- package/dist/providers/RouteTransition/index.d.ts.map +1 -1
- package/dist/providers/RouteTransition/index.js +42 -14
- package/dist/providers/RouteTransition/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type SessionActivitySource = 'focus' | 'keydown' | 'mousemove' | 'route';
|
|
2
|
+
export type MarkSessionActivity = (source: SessionActivitySource) => boolean;
|
|
3
|
+
export type SessionActivityTracker = {
|
|
4
|
+
record: MarkSessionActivity;
|
|
5
|
+
reset: () => void;
|
|
6
|
+
};
|
|
7
|
+
export declare const sessionActivityThrottleMs = 5000;
|
|
8
|
+
/**
|
|
9
|
+
* Coalesces browser and route activity into periodic session activity.
|
|
10
|
+
*
|
|
11
|
+
* The tracker reports whether the activity was accepted. Activity inside the throttle
|
|
12
|
+
* interval is ignored and does not call `onActivity`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createSessionActivityTracker({ now, onActivity, throttleMs, }: {
|
|
15
|
+
now?: () => number;
|
|
16
|
+
onActivity: (source: SessionActivitySource, occurredAt: number) => void;
|
|
17
|
+
throttleMs?: number;
|
|
18
|
+
}): SessionActivityTracker;
|
|
19
|
+
export declare function registerSessionActivityListeners({ markActivity, window, }: {
|
|
20
|
+
markActivity: MarkSessionActivity;
|
|
21
|
+
window: Window;
|
|
22
|
+
}): () => void;
|
|
23
|
+
//# sourceMappingURL=sessionActivity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionActivity.d.ts","sourceRoot":"","sources":["../../../src/providers/Auth/sessionActivity.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAA;AAE/E,MAAM,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,qBAAqB,KAAK,OAAO,CAAA;AAE5E,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,mBAAmB,CAAA;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB,CAAA;AAED,eAAO,MAAM,yBAAyB,OAAQ,CAAA;AAE9C;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,EAC3C,GAAc,EACd,UAAU,EACV,UAAsC,GACvC,EAAE;IACD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,UAAU,EAAE,CAAC,MAAM,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IACvE,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,GAAG,sBAAsB,CAoBzB;AAED,wBAAgB,gCAAgC,CAAC,EAC/C,YAAY,EACZ,MAAM,GACP,EAAE;IACD,YAAY,EAAE,mBAAmB,CAAA;IACjC,MAAM,EAAE,MAAM,CAAA;CACf,GAAG,MAAM,IAAI,CAeb"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export const sessionActivityThrottleMs = 5_000;
|
|
2
|
+
/**
|
|
3
|
+
* Coalesces browser and route activity into periodic session activity.
|
|
4
|
+
*
|
|
5
|
+
* The tracker reports whether the activity was accepted. Activity inside the throttle
|
|
6
|
+
* interval is ignored and does not call `onActivity`.
|
|
7
|
+
*/
|
|
8
|
+
export function createSessionActivityTracker({
|
|
9
|
+
now = Date.now,
|
|
10
|
+
onActivity,
|
|
11
|
+
throttleMs = sessionActivityThrottleMs
|
|
12
|
+
}) {
|
|
13
|
+
let lastActivityAt;
|
|
14
|
+
return {
|
|
15
|
+
record: source => {
|
|
16
|
+
const occurredAt = now();
|
|
17
|
+
if (lastActivityAt !== undefined && occurredAt - lastActivityAt < throttleMs) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
lastActivityAt = occurredAt;
|
|
21
|
+
onActivity(source, occurredAt);
|
|
22
|
+
return true;
|
|
23
|
+
},
|
|
24
|
+
reset: () => {
|
|
25
|
+
lastActivityAt = undefined;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function registerSessionActivityListeners({
|
|
30
|
+
markActivity,
|
|
31
|
+
window
|
|
32
|
+
}) {
|
|
33
|
+
const focusListener = () => markActivity('focus');
|
|
34
|
+
const keydownListener = () => markActivity('keydown');
|
|
35
|
+
const mousemoveListener = () => markActivity('mousemove');
|
|
36
|
+
const mousemoveListenerOptions = {
|
|
37
|
+
capture: true,
|
|
38
|
+
passive: true
|
|
39
|
+
};
|
|
40
|
+
window.addEventListener('focus', focusListener, true);
|
|
41
|
+
window.addEventListener('keydown', keydownListener, true);
|
|
42
|
+
window.addEventListener('mousemove', mousemoveListener, mousemoveListenerOptions);
|
|
43
|
+
return () => {
|
|
44
|
+
window.removeEventListener('focus', focusListener, true);
|
|
45
|
+
window.removeEventListener('keydown', keydownListener, true);
|
|
46
|
+
window.removeEventListener('mousemove', mousemoveListener, mousemoveListenerOptions);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=sessionActivity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionActivity.js","names":["sessionActivityThrottleMs","createSessionActivityTracker","now","Date","onActivity","throttleMs","lastActivityAt","record","source","occurredAt","undefined","reset","registerSessionActivityListeners","markActivity","window","focusListener","keydownListener","mousemoveListener","mousemoveListenerOptions","capture","passive","addEventListener","removeEventListener"],"sources":["../../../src/providers/Auth/sessionActivity.ts"],"sourcesContent":["export type SessionActivitySource = 'focus' | 'keydown' | 'mousemove' | 'route'\n\nexport type MarkSessionActivity = (source: SessionActivitySource) => boolean\n\nexport type SessionActivityTracker = {\n record: MarkSessionActivity\n reset: () => void\n}\n\nexport const sessionActivityThrottleMs = 5_000\n\n/**\n * Coalesces browser and route activity into periodic session activity.\n *\n * The tracker reports whether the activity was accepted. Activity inside the throttle\n * interval is ignored and does not call `onActivity`.\n */\nexport function createSessionActivityTracker({\n now = Date.now,\n onActivity,\n throttleMs = sessionActivityThrottleMs,\n}: {\n now?: () => number\n onActivity: (source: SessionActivitySource, occurredAt: number) => void\n throttleMs?: number\n}): SessionActivityTracker {\n let lastActivityAt: number | undefined\n\n return {\n record: (source) => {\n const occurredAt = now()\n\n if (lastActivityAt !== undefined && occurredAt - lastActivityAt < throttleMs) {\n return false\n }\n\n lastActivityAt = occurredAt\n onActivity(source, occurredAt)\n\n return true\n },\n reset: () => {\n lastActivityAt = undefined\n },\n }\n}\n\nexport function registerSessionActivityListeners({\n markActivity,\n window,\n}: {\n markActivity: MarkSessionActivity\n window: Window\n}): () => void {\n const focusListener = () => markActivity('focus')\n const keydownListener = () => markActivity('keydown')\n const mousemoveListener = () => markActivity('mousemove')\n const mousemoveListenerOptions = { capture: true, passive: true }\n\n window.addEventListener('focus', focusListener, true)\n window.addEventListener('keydown', keydownListener, true)\n window.addEventListener('mousemove', mousemoveListener, mousemoveListenerOptions)\n\n return () => {\n window.removeEventListener('focus', focusListener, true)\n window.removeEventListener('keydown', keydownListener, true)\n window.removeEventListener('mousemove', mousemoveListener, mousemoveListenerOptions)\n }\n}\n"],"mappings":"AASA,OAAO,MAAMA,yBAAA,GAA4B;AAEzC;;;;;;AAMA,OAAO,SAASC,6BAA6B;EAC3CC,GAAA,GAAMC,IAAA,CAAKD,GAAG;EACdE,UAAU;EACVC,UAAA,GAAaL;AAAyB,CAKvC;EACC,IAAIM,cAAA;EAEJ,OAAO;IACLC,MAAA,EAASC,MAAA;MACP,MAAMC,UAAA,GAAaP,GAAA;MAEnB,IAAII,cAAA,KAAmBI,SAAA,IAAaD,UAAA,GAAaH,cAAA,GAAiBD,UAAA,EAAY;QAC5E,OAAO;MACT;MAEAC,cAAA,GAAiBG,UAAA;MACjBL,UAAA,CAAWI,MAAA,EAAQC,UAAA;MAEnB,OAAO;IACT;IACAE,KAAA,EAAOA,CAAA;MACLL,cAAA,GAAiBI,SAAA;IACnB;EACF;AACF;AAEA,OAAO,SAASE,iCAAiC;EAC/CC,YAAY;EACZC;AAAM,CAIP;EACC,MAAMC,aAAA,GAAgBA,CAAA,KAAMF,YAAA,CAAa;EACzC,MAAMG,eAAA,GAAkBA,CAAA,KAAMH,YAAA,CAAa;EAC3C,MAAMI,iBAAA,GAAoBA,CAAA,KAAMJ,YAAA,CAAa;EAC7C,MAAMK,wBAAA,GAA2B;IAAEC,OAAA,EAAS;IAAMC,OAAA,EAAS;EAAK;EAEhEN,MAAA,CAAOO,gBAAgB,CAAC,SAASN,aAAA,EAAe;EAChDD,MAAA,CAAOO,gBAAgB,CAAC,WAAWL,eAAA,EAAiB;EACpDF,MAAA,CAAOO,gBAAgB,CAAC,aAAaJ,iBAAA,EAAmBC,wBAAA;EAExD,OAAO;IACLJ,MAAA,CAAOQ,mBAAmB,CAAC,SAASP,aAAA,EAAe;IACnDD,MAAA,CAAOQ,mBAAmB,CAAC,WAAWN,eAAA,EAAiB;IACvDF,MAAA,CAAOQ,mBAAmB,CAAC,aAAaL,iBAAA,EAAmBC,wBAAA;EAC7D;AACF","ignoreList":[]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { createSessionActivityTracker, registerSessionActivityListeners, sessionActivityThrottleMs } from './sessionActivity.js';
|
|
4
|
+
describe('createSessionActivityTracker', () => {
|
|
5
|
+
it('should throttle activity for five seconds', () => {
|
|
6
|
+
expect(sessionActivityThrottleMs).toBe(5_000);
|
|
7
|
+
let now = 100;
|
|
8
|
+
const onActivity = vi.fn();
|
|
9
|
+
const activityTracker = createSessionActivityTracker({
|
|
10
|
+
now: () => now,
|
|
11
|
+
onActivity
|
|
12
|
+
});
|
|
13
|
+
expect(activityTracker.record('mousemove')).toBe(true);
|
|
14
|
+
expect(onActivity).toHaveBeenCalledWith('mousemove', 100);
|
|
15
|
+
now += sessionActivityThrottleMs - 1;
|
|
16
|
+
expect(activityTracker.record('focus')).toBe(false);
|
|
17
|
+
expect(onActivity).toHaveBeenCalledTimes(1);
|
|
18
|
+
activityTracker.reset();
|
|
19
|
+
expect(activityTracker.record('focus')).toBe(true);
|
|
20
|
+
expect(onActivity).toHaveBeenCalledTimes(2);
|
|
21
|
+
now += 1;
|
|
22
|
+
expect(activityTracker.record('focus')).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe('registerSessionActivityListeners', () => {
|
|
26
|
+
it('should register and remove focus, keyboard, and mouse movement listeners', () => {
|
|
27
|
+
const markActivity = vi.fn();
|
|
28
|
+
const cleanup = registerSessionActivityListeners({
|
|
29
|
+
markActivity,
|
|
30
|
+
window
|
|
31
|
+
});
|
|
32
|
+
window.dispatchEvent(new Event('focus'));
|
|
33
|
+
window.dispatchEvent(new KeyboardEvent('keydown'));
|
|
34
|
+
window.dispatchEvent(new MouseEvent('mousemove'));
|
|
35
|
+
expect(markActivity.mock.calls).toEqual([['focus'], ['keydown'], ['mousemove']]);
|
|
36
|
+
cleanup();
|
|
37
|
+
markActivity.mockClear();
|
|
38
|
+
window.dispatchEvent(new Event('focus'));
|
|
39
|
+
window.dispatchEvent(new KeyboardEvent('keydown'));
|
|
40
|
+
window.dispatchEvent(new MouseEvent('mousemove'));
|
|
41
|
+
expect(markActivity).not.toHaveBeenCalled();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
//# sourceMappingURL=sessionActivity.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionActivity.spec.js","names":["describe","expect","it","vi","createSessionActivityTracker","registerSessionActivityListeners","sessionActivityThrottleMs","toBe","now","onActivity","fn","activityTracker","record","toHaveBeenCalledWith","toHaveBeenCalledTimes","reset","markActivity","cleanup","window","dispatchEvent","Event","KeyboardEvent","MouseEvent","mock","calls","toEqual","mockClear","not","toHaveBeenCalled"],"sources":["../../../src/providers/Auth/sessionActivity.spec.ts"],"sourcesContent":["// @vitest-environment jsdom\nimport { describe, expect, it, vi } from 'vitest'\n\nimport {\n createSessionActivityTracker,\n registerSessionActivityListeners,\n sessionActivityThrottleMs,\n} from './sessionActivity.js'\n\ndescribe('createSessionActivityTracker', () => {\n it('should throttle activity for five seconds', () => {\n expect(sessionActivityThrottleMs).toBe(5_000)\n\n let now = 100\n const onActivity = vi.fn()\n const activityTracker = createSessionActivityTracker({\n now: () => now,\n onActivity,\n })\n\n expect(activityTracker.record('mousemove')).toBe(true)\n expect(onActivity).toHaveBeenCalledWith('mousemove', 100)\n\n now += sessionActivityThrottleMs - 1\n\n expect(activityTracker.record('focus')).toBe(false)\n expect(onActivity).toHaveBeenCalledTimes(1)\n\n activityTracker.reset()\n\n expect(activityTracker.record('focus')).toBe(true)\n expect(onActivity).toHaveBeenCalledTimes(2)\n\n now += 1\n\n expect(activityTracker.record('focus')).toBe(false)\n })\n})\n\ndescribe('registerSessionActivityListeners', () => {\n it('should register and remove focus, keyboard, and mouse movement listeners', () => {\n const markActivity = vi.fn()\n\n const cleanup = registerSessionActivityListeners({\n markActivity,\n window,\n })\n\n window.dispatchEvent(new Event('focus'))\n window.dispatchEvent(new KeyboardEvent('keydown'))\n window.dispatchEvent(new MouseEvent('mousemove'))\n\n expect(markActivity.mock.calls).toEqual([['focus'], ['keydown'], ['mousemove']])\n\n cleanup()\n markActivity.mockClear()\n\n window.dispatchEvent(new Event('focus'))\n window.dispatchEvent(new KeyboardEvent('keydown'))\n window.dispatchEvent(new MouseEvent('mousemove'))\n\n expect(markActivity).not.toHaveBeenCalled()\n })\n})\n"],"mappings":"AAAA;AACA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ;AAEzC,SACEC,4BAA4B,EAC5BC,gCAAgC,EAChCC,yBAAyB,QACpB;AAEPN,QAAA,CAAS,gCAAgC;EACvCE,EAAA,CAAG,6CAA6C;IAC9CD,MAAA,CAAOK,yBAAA,EAA2BC,IAAI,CAAC;IAEvC,IAAIC,GAAA,GAAM;IACV,MAAMC,UAAA,GAAaN,EAAA,CAAGO,EAAE;IACxB,MAAMC,eAAA,GAAkBP,4BAAA,CAA6B;MACnDI,GAAA,EAAKA,CAAA,KAAMA,GAAA;MACXC;IACF;IAEAR,MAAA,CAAOU,eAAA,CAAgBC,MAAM,CAAC,cAAcL,IAAI,CAAC;IACjDN,MAAA,CAAOQ,UAAA,EAAYI,oBAAoB,CAAC,aAAa;IAErDL,GAAA,IAAOF,yBAAA,GAA4B;IAEnCL,MAAA,CAAOU,eAAA,CAAgBC,MAAM,CAAC,UAAUL,IAAI,CAAC;IAC7CN,MAAA,CAAOQ,UAAA,EAAYK,qBAAqB,CAAC;IAEzCH,eAAA,CAAgBI,KAAK;IAErBd,MAAA,CAAOU,eAAA,CAAgBC,MAAM,CAAC,UAAUL,IAAI,CAAC;IAC7CN,MAAA,CAAOQ,UAAA,EAAYK,qBAAqB,CAAC;IAEzCN,GAAA,IAAO;IAEPP,MAAA,CAAOU,eAAA,CAAgBC,MAAM,CAAC,UAAUL,IAAI,CAAC;EAC/C;AACF;AAEAP,QAAA,CAAS,oCAAoC;EAC3CE,EAAA,CAAG,4EAA4E;IAC7E,MAAMc,YAAA,GAAeb,EAAA,CAAGO,EAAE;IAE1B,MAAMO,OAAA,GAAUZ,gCAAA,CAAiC;MAC/CW,YAAA;MACAE;IACF;IAEAA,MAAA,CAAOC,aAAa,CAAC,IAAIC,KAAA,CAAM;IAC/BF,MAAA,CAAOC,aAAa,CAAC,IAAIE,aAAA,CAAc;IACvCH,MAAA,CAAOC,aAAa,CAAC,IAAIG,UAAA,CAAW;IAEpCrB,MAAA,CAAOe,YAAA,CAAaO,IAAI,CAACC,KAAK,EAAEC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,UAAU,EAAE,CAAC,YAAY,CAAC;IAE/ER,OAAA;IACAD,YAAA,CAAaU,SAAS;IAEtBR,MAAA,CAAOC,aAAa,CAAC,IAAIC,KAAA,CAAM;IAC/BF,MAAA,CAAOC,aAAa,CAAC,IAAIE,aAAA,CAAc;IACvCH,MAAA,CAAOC,aAAa,CAAC,IAAIG,UAAA,CAAW;IAEpCrB,MAAA,CAAOe,YAAA,EAAcW,GAAG,CAACC,gBAAgB;EAC3C;AACF","ignoreList":[]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AuthSession } from './types.js';
|
|
2
|
+
export type AuthSessionTiming = Omit<AuthSession, 'activityRecorded'>;
|
|
3
|
+
export type GetAuthSessionTimingArgs = {
|
|
4
|
+
expirationMs: number;
|
|
5
|
+
nowMs?: number;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Returns the fixed lifecycle boundaries for a token expiration.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getAuthSessionTiming({ expirationMs, nowMs, }: GetAuthSessionTimingArgs): AuthSessionTiming;
|
|
11
|
+
//# sourceMappingURL=sessionTiming.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionTiming.d.ts","sourceRoot":"","sources":["../../../src/providers/Auth/sessionTiming.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAA;AAErE,MAAM,MAAM,wBAAwB,GAAG;IACrC,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,YAAY,EACZ,KAAkB,GACnB,EAAE,wBAAwB,GAAG,iBAAiB,CAY9C"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the fixed lifecycle boundaries for a token expiration.
|
|
3
|
+
*/export function getAuthSessionTiming({
|
|
4
|
+
expirationMs,
|
|
5
|
+
nowMs = Date.now()
|
|
6
|
+
}) {
|
|
7
|
+
const expiresInMs = Math.max(0, expirationMs - nowMs);
|
|
8
|
+
const reminderWindowMs = Math.min(60_000, expiresInMs / 2);
|
|
9
|
+
const refreshWindowMs = reminderWindowMs * 2;
|
|
10
|
+
const activityTrackingWindowMs = refreshWindowMs * 2;
|
|
11
|
+
return Object.freeze({
|
|
12
|
+
activityTrackingStartsAt: Math.max(expirationMs - activityTrackingWindowMs, nowMs),
|
|
13
|
+
expiresAt: expirationMs,
|
|
14
|
+
refreshStartsAt: Math.max(expirationMs - refreshWindowMs, nowMs),
|
|
15
|
+
reminderStartsAt: Math.max(expirationMs - reminderWindowMs, nowMs)
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=sessionTiming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionTiming.js","names":["getAuthSessionTiming","expirationMs","nowMs","Date","now","expiresInMs","Math","max","reminderWindowMs","min","refreshWindowMs","activityTrackingWindowMs","Object","freeze","activityTrackingStartsAt","expiresAt","refreshStartsAt","reminderStartsAt"],"sources":["../../../src/providers/Auth/sessionTiming.ts"],"sourcesContent":["import type { AuthSession } from './types.js'\n\nexport type AuthSessionTiming = Omit<AuthSession, 'activityRecorded'>\n\nexport type GetAuthSessionTimingArgs = {\n expirationMs: number\n nowMs?: number\n}\n\n/**\n * Returns the fixed lifecycle boundaries for a token expiration.\n */\nexport function getAuthSessionTiming({\n expirationMs,\n nowMs = Date.now(),\n}: GetAuthSessionTimingArgs): AuthSessionTiming {\n const expiresInMs = Math.max(0, expirationMs - nowMs)\n const reminderWindowMs = Math.min(60_000, expiresInMs / 2)\n const refreshWindowMs = reminderWindowMs * 2\n const activityTrackingWindowMs = refreshWindowMs * 2\n\n return Object.freeze({\n activityTrackingStartsAt: Math.max(expirationMs - activityTrackingWindowMs, nowMs),\n expiresAt: expirationMs,\n refreshStartsAt: Math.max(expirationMs - refreshWindowMs, nowMs),\n reminderStartsAt: Math.max(expirationMs - reminderWindowMs, nowMs),\n })\n}\n"],"mappings":"AASA;;GAGA,OAAO,SAASA,qBAAqB;EACnCC,YAAY;EACZC,KAAA,GAAQC,IAAA,CAAKC,GAAG;AAAE,CACO;EACzB,MAAMC,WAAA,GAAcC,IAAA,CAAKC,GAAG,CAAC,GAAGN,YAAA,GAAeC,KAAA;EAC/C,MAAMM,gBAAA,GAAmBF,IAAA,CAAKG,GAAG,CAAC,QAAQJ,WAAA,GAAc;EACxD,MAAMK,eAAA,GAAkBF,gBAAA,GAAmB;EAC3C,MAAMG,wBAAA,GAA2BD,eAAA,GAAkB;EAEnD,OAAOE,MAAA,CAAOC,MAAM,CAAC;IACnBC,wBAAA,EAA0BR,IAAA,CAAKC,GAAG,CAACN,YAAA,GAAeU,wBAAA,EAA0BT,KAAA;IAC5Ea,SAAA,EAAWd,YAAA;IACXe,eAAA,EAAiBV,IAAA,CAAKC,GAAG,CAACN,YAAA,GAAeS,eAAA,EAAiBR,KAAA;IAC1De,gBAAA,EAAkBX,IAAA,CAAKC,GAAG,CAACN,YAAA,GAAeO,gBAAA,EAAkBN,KAAA;EAC9D;AACF","ignoreList":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CreateTabSessionSyncArgs, TabSessionEvent, TabSessionLogoutPublication, TabSessionPublication } from './types.js';
|
|
2
|
+
export type { CreateTabSessionSyncArgs, TabSessionEvent, TabSessionEventType, TabSessionLogoutPublication, TabSessionMessage, TabSessionPublication, TabSessionReconciliationOptions, TabSessionReconciliationResult, } from './types.js';
|
|
3
|
+
export { TAB_SESSION_EVENT_TYPES } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Coordinates authentication session state across browser tabs.
|
|
6
|
+
*
|
|
7
|
+
* BroadcastChannel carries complete session events when available. Transient localStorage
|
|
8
|
+
* notifications carry lifecycle metadata only and allow tabs to reconcile through
|
|
9
|
+
* `reconcileSession` when BroadcastChannel is unavailable. Lifecycle ordering prevents older async
|
|
10
|
+
* work from replacing a newer session state.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createTabSessionSync({ getTokenExpirationMs, now, onSessionExpired, onSessionLoggedOut, onSessionRefreshed, onTabSessionUnauthenticated, reconcileSession, sourceTabID, }: CreateTabSessionSyncArgs): {
|
|
13
|
+
broadcast: (event: TabSessionEvent) => TabSessionPublication;
|
|
14
|
+
broadcastLogoutSettlement: (publication: TabSessionLogoutPublication) => void;
|
|
15
|
+
cleanup: () => void;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/providers/Auth/tabSessionSync/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EAKxB,eAAe,EAEf,2BAA2B,EAC3B,qBAAqB,EAGtB,MAAM,YAAY,CAAA;AAanB,YAAY,EACV,wBAAwB,EACxB,eAAe,EACf,mBAAmB,EACnB,2BAA2B,EAC3B,iBAAiB,EACjB,qBAAqB,EACrB,+BAA+B,EAC/B,8BAA8B,GAC/B,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAEpD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,oBAAoB,EACpB,GAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,2BAA2B,EAC3B,gBAAgB,EAChB,WAAW,GACZ,EAAE,wBAAwB,GAAG;IAC5B,SAAS,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,qBAAqB,CAAA;IAC5D,yBAAyB,EAAE,CAAC,WAAW,EAAE,2BAA2B,KAAK,IAAI,CAAA;IAC7E,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CA8PA"}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { TAB_SESSION_EVENT_TYPES } from './types.js';
|
|
2
|
+
import { compareLifecycleOrders, createTabSessionMessage, getLifecycleOrder, isTabSessionMessage, parseTabSessionStorageNotification, tabSessionSyncChannelName, tabSessionSyncStorageKey } from './utilities.js';
|
|
3
|
+
export { TAB_SESSION_EVENT_TYPES } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Coordinates authentication session state across browser tabs.
|
|
6
|
+
*
|
|
7
|
+
* BroadcastChannel carries complete session events when available. Transient localStorage
|
|
8
|
+
* notifications carry lifecycle metadata only and allow tabs to reconcile through
|
|
9
|
+
* `reconcileSession` when BroadcastChannel is unavailable. Lifecycle ordering prevents older async
|
|
10
|
+
* work from replacing a newer session state.
|
|
11
|
+
*/
|
|
12
|
+
export function createTabSessionSync({
|
|
13
|
+
getTokenExpirationMs,
|
|
14
|
+
now = Date.now,
|
|
15
|
+
onSessionExpired,
|
|
16
|
+
onSessionLoggedOut,
|
|
17
|
+
onSessionRefreshed,
|
|
18
|
+
onTabSessionUnauthenticated,
|
|
19
|
+
reconcileSession,
|
|
20
|
+
sourceTabID
|
|
21
|
+
}) {
|
|
22
|
+
let channel;
|
|
23
|
+
let isActive = true;
|
|
24
|
+
let isStorageListenerInstalled = false;
|
|
25
|
+
let latestLifecycleOrder;
|
|
26
|
+
let pendingStorageLifecycleOrder;
|
|
27
|
+
const onExpiredSession = ({
|
|
28
|
+
lifecycleOrder,
|
|
29
|
+
message
|
|
30
|
+
}) => {
|
|
31
|
+
const localExpirationMs = getTokenExpirationMs();
|
|
32
|
+
if (localExpirationMs === undefined || localExpirationMs <= message.expiredTokenAt) {
|
|
33
|
+
pendingStorageLifecycleOrder = undefined;
|
|
34
|
+
latestLifecycleOrder = lifecycleOrder;
|
|
35
|
+
onSessionExpired(message.expiredTokenAt);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const onLoggedOutSession = ({
|
|
39
|
+
lifecycleOrder
|
|
40
|
+
}) => {
|
|
41
|
+
pendingStorageLifecycleOrder = undefined;
|
|
42
|
+
latestLifecycleOrder = lifecycleOrder;
|
|
43
|
+
onSessionLoggedOut();
|
|
44
|
+
};
|
|
45
|
+
const onRefreshedSession = ({
|
|
46
|
+
lifecycleOrder,
|
|
47
|
+
message
|
|
48
|
+
}) => {
|
|
49
|
+
pendingStorageLifecycleOrder = undefined;
|
|
50
|
+
latestLifecycleOrder = lifecycleOrder;
|
|
51
|
+
onSessionRefreshed(message.session);
|
|
52
|
+
};
|
|
53
|
+
const handleChannelMessage = ({
|
|
54
|
+
data
|
|
55
|
+
}) => {
|
|
56
|
+
if (!isTabSessionMessage(data) || data.sourceTabID === sourceTabID) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const lifecycleOrder = getLifecycleOrder(data);
|
|
60
|
+
const lifecycleComparison = latestLifecycleOrder ? compareLifecycleOrders({
|
|
61
|
+
first: lifecycleOrder,
|
|
62
|
+
second: latestLifecycleOrder
|
|
63
|
+
}) : undefined;
|
|
64
|
+
const resolvesPendingStorageBarrier = lifecycleComparison === 0 && pendingStorageLifecycleOrder === latestLifecycleOrder;
|
|
65
|
+
if (lifecycleComparison !== undefined && (lifecycleComparison < 0 || lifecycleComparison === 0 && !resolvesPendingStorageBarrier)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
switch (data.type) {
|
|
69
|
+
case TAB_SESSION_EVENT_TYPES.EXPIRED:
|
|
70
|
+
onExpiredSession({
|
|
71
|
+
lifecycleOrder,
|
|
72
|
+
message: data
|
|
73
|
+
});
|
|
74
|
+
break;
|
|
75
|
+
case TAB_SESSION_EVENT_TYPES.LOGGED_OUT:
|
|
76
|
+
onLoggedOutSession({
|
|
77
|
+
lifecycleOrder
|
|
78
|
+
});
|
|
79
|
+
break;
|
|
80
|
+
case TAB_SESSION_EVENT_TYPES.REFRESHED:
|
|
81
|
+
onRefreshedSession({
|
|
82
|
+
lifecycleOrder,
|
|
83
|
+
message: data
|
|
84
|
+
});
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const reconcileStorageSession = ({
|
|
89
|
+
notification
|
|
90
|
+
}) => {
|
|
91
|
+
const isTabSessionEventStale = () => !isActive || latestLifecycleOrder !== notification;
|
|
92
|
+
void reconcileSession({
|
|
93
|
+
isTabSessionEventStale
|
|
94
|
+
}).then(result => {
|
|
95
|
+
if (!isTabSessionEventStale()) {
|
|
96
|
+
if (result.status === 'authenticated') {
|
|
97
|
+
pendingStorageLifecycleOrder = undefined;
|
|
98
|
+
latestLifecycleOrder = {
|
|
99
|
+
type: TAB_SESSION_EVENT_TYPES.REFRESHED,
|
|
100
|
+
affectedExpirationMs: result.expirationMs,
|
|
101
|
+
refreshStartedAt: notification.type === TAB_SESSION_EVENT_TYPES.REFRESHED ? notification.refreshStartedAt : notification.sentAt,
|
|
102
|
+
sentAt: notification.sentAt,
|
|
103
|
+
sourceTabID: notification.sourceTabID
|
|
104
|
+
};
|
|
105
|
+
} else if (result.status === 'unauthenticated') {
|
|
106
|
+
pendingStorageLifecycleOrder = undefined;
|
|
107
|
+
latestLifecycleOrder = {
|
|
108
|
+
type: TAB_SESSION_EVENT_TYPES.LOGGED_OUT,
|
|
109
|
+
affectedExpirationMs: 0,
|
|
110
|
+
sentAt: notification.sentAt,
|
|
111
|
+
sourceTabID: notification.sourceTabID
|
|
112
|
+
};
|
|
113
|
+
onTabSessionUnauthenticated();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}).catch(() => undefined);
|
|
117
|
+
};
|
|
118
|
+
const handleStorageNotification = event => {
|
|
119
|
+
const notification = parseTabSessionStorageNotification(event);
|
|
120
|
+
if (!notification || notification.sourceTabID === sourceTabID) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (channel && !notification.isBroadcastChannelFallback) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (latestLifecycleOrder && compareLifecycleOrders({
|
|
127
|
+
first: notification,
|
|
128
|
+
second: latestLifecycleOrder
|
|
129
|
+
}) <= 0) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
pendingStorageLifecycleOrder = notification;
|
|
133
|
+
latestLifecycleOrder = notification;
|
|
134
|
+
if (notification.type === TAB_SESSION_EVENT_TYPES.LOGGED_OUT) {
|
|
135
|
+
onLoggedOutSession({
|
|
136
|
+
lifecycleOrder: notification
|
|
137
|
+
});
|
|
138
|
+
} else {
|
|
139
|
+
reconcileStorageSession({
|
|
140
|
+
notification
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
function closeChannel(channelToClose) {
|
|
145
|
+
if (!channelToClose) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
channelToClose.removeEventListener('message', handleChannelMessage);
|
|
150
|
+
} catch {
|
|
151
|
+
// Cross-tab synchronization cleanup is best-effort.
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
channelToClose.close();
|
|
155
|
+
} catch {
|
|
156
|
+
// Cross-tab synchronization cleanup is best-effort.
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function attachStorageListener() {
|
|
160
|
+
if (isStorageListenerInstalled) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
window.addEventListener('storage', handleStorageNotification);
|
|
165
|
+
isStorageListenerInstalled = true;
|
|
166
|
+
} catch {
|
|
167
|
+
// Local authentication must continue when storage events are unavailable.
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function downgradeToStorage() {
|
|
171
|
+
closeChannel(channel);
|
|
172
|
+
channel = undefined;
|
|
173
|
+
attachStorageListener();
|
|
174
|
+
}
|
|
175
|
+
function getNextLifecycleTimestamp() {
|
|
176
|
+
const currentTime = now();
|
|
177
|
+
const latestSentAt = latestLifecycleOrder?.sentAt ?? Number.NEGATIVE_INFINITY;
|
|
178
|
+
return currentTime > latestSentAt ? currentTime : latestSentAt + 1;
|
|
179
|
+
}
|
|
180
|
+
const writeStorageNotification = ({
|
|
181
|
+
notification
|
|
182
|
+
}) => {
|
|
183
|
+
const storageNotification = channel ? notification : {
|
|
184
|
+
...notification,
|
|
185
|
+
isBroadcastChannelFallback: true
|
|
186
|
+
};
|
|
187
|
+
try {
|
|
188
|
+
localStorage.setItem(tabSessionSyncStorageKey, JSON.stringify(storageNotification));
|
|
189
|
+
} catch {
|
|
190
|
+
// Local authentication must continue when storage is unavailable.
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
localStorage.removeItem(tabSessionSyncStorageKey);
|
|
194
|
+
} catch {
|
|
195
|
+
// Local authentication must continue when storage is unavailable.
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
if (typeof BroadcastChannel === 'function') {
|
|
199
|
+
let nextChannel;
|
|
200
|
+
try {
|
|
201
|
+
nextChannel = new BroadcastChannel(tabSessionSyncChannelName);
|
|
202
|
+
nextChannel.addEventListener('message', handleChannelMessage);
|
|
203
|
+
channel = nextChannel;
|
|
204
|
+
} catch {
|
|
205
|
+
closeChannel(nextChannel);
|
|
206
|
+
channel = undefined;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
attachStorageListener();
|
|
210
|
+
return {
|
|
211
|
+
broadcast: event => {
|
|
212
|
+
const sentAt = getNextLifecycleTimestamp();
|
|
213
|
+
const message = createTabSessionMessage({
|
|
214
|
+
event,
|
|
215
|
+
sentAt,
|
|
216
|
+
sourceTabID
|
|
217
|
+
});
|
|
218
|
+
const lifecycleOrder = getLifecycleOrder(message);
|
|
219
|
+
pendingStorageLifecycleOrder = undefined;
|
|
220
|
+
latestLifecycleOrder = lifecycleOrder;
|
|
221
|
+
if (channel) {
|
|
222
|
+
try {
|
|
223
|
+
channel.postMessage(message);
|
|
224
|
+
} catch {
|
|
225
|
+
downgradeToStorage();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (lifecycleOrder.type !== TAB_SESSION_EVENT_TYPES.LOGGED_OUT) {
|
|
229
|
+
writeStorageNotification({
|
|
230
|
+
notification: lifecycleOrder
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return lifecycleOrder;
|
|
234
|
+
},
|
|
235
|
+
broadcastLogoutSettlement: publication => {
|
|
236
|
+
const notification = {
|
|
237
|
+
...publication,
|
|
238
|
+
sentAt: getNextLifecycleTimestamp(),
|
|
239
|
+
settlesSentAt: publication.sentAt
|
|
240
|
+
};
|
|
241
|
+
pendingStorageLifecycleOrder = undefined;
|
|
242
|
+
latestLifecycleOrder = notification;
|
|
243
|
+
writeStorageNotification({
|
|
244
|
+
notification
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
cleanup: () => {
|
|
248
|
+
isActive = false;
|
|
249
|
+
pendingStorageLifecycleOrder = undefined;
|
|
250
|
+
closeChannel(channel);
|
|
251
|
+
channel = undefined;
|
|
252
|
+
if (isStorageListenerInstalled) {
|
|
253
|
+
try {
|
|
254
|
+
window.removeEventListener('storage', handleStorageNotification);
|
|
255
|
+
} catch {
|
|
256
|
+
// Cross-tab synchronization cleanup is best-effort.
|
|
257
|
+
}
|
|
258
|
+
isStorageListenerInstalled = false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["TAB_SESSION_EVENT_TYPES","compareLifecycleOrders","createTabSessionMessage","getLifecycleOrder","isTabSessionMessage","parseTabSessionStorageNotification","tabSessionSyncChannelName","tabSessionSyncStorageKey","createTabSessionSync","getTokenExpirationMs","now","Date","onSessionExpired","onSessionLoggedOut","onSessionRefreshed","onTabSessionUnauthenticated","reconcileSession","sourceTabID","channel","isActive","isStorageListenerInstalled","latestLifecycleOrder","pendingStorageLifecycleOrder","onExpiredSession","lifecycleOrder","message","localExpirationMs","undefined","expiredTokenAt","onLoggedOutSession","onRefreshedSession","session","handleChannelMessage","data","lifecycleComparison","first","second","resolvesPendingStorageBarrier","type","EXPIRED","LOGGED_OUT","REFRESHED","reconcileStorageSession","notification","isTabSessionEventStale","then","result","status","affectedExpirationMs","expirationMs","refreshStartedAt","sentAt","catch","handleStorageNotification","event","isBroadcastChannelFallback","closeChannel","channelToClose","removeEventListener","close","attachStorageListener","window","addEventListener","downgradeToStorage","getNextLifecycleTimestamp","currentTime","latestSentAt","Number","NEGATIVE_INFINITY","writeStorageNotification","storageNotification","localStorage","setItem","JSON","stringify","removeItem","BroadcastChannel","nextChannel","broadcast","postMessage","broadcastLogoutSettlement","publication","settlesSentAt","cleanup"],"sources":["../../../../src/providers/Auth/tabSessionSync/index.ts"],"sourcesContent":["import type {\n CreateTabSessionSyncArgs,\n OnExpiredSession,\n OnLoggedOutSession,\n OnRefreshedSession,\n ReconcileStorageSession,\n TabSessionEvent,\n TabSessionLifecycleOrder,\n TabSessionLogoutPublication,\n TabSessionPublication,\n TabSessionStorageNotification,\n WriteStorageNotification,\n} from './types.js'\n\nimport { TAB_SESSION_EVENT_TYPES } from './types.js'\nimport {\n compareLifecycleOrders,\n createTabSessionMessage,\n getLifecycleOrder,\n isTabSessionMessage,\n parseTabSessionStorageNotification,\n tabSessionSyncChannelName,\n tabSessionSyncStorageKey,\n} from './utilities.js'\n\nexport type {\n CreateTabSessionSyncArgs,\n TabSessionEvent,\n TabSessionEventType,\n TabSessionLogoutPublication,\n TabSessionMessage,\n TabSessionPublication,\n TabSessionReconciliationOptions,\n TabSessionReconciliationResult,\n} from './types.js'\n\nexport { TAB_SESSION_EVENT_TYPES } from './types.js'\n\n/**\n * Coordinates authentication session state across browser tabs.\n *\n * BroadcastChannel carries complete session events when available. Transient localStorage\n * notifications carry lifecycle metadata only and allow tabs to reconcile through\n * `reconcileSession` when BroadcastChannel is unavailable. Lifecycle ordering prevents older async\n * work from replacing a newer session state.\n */\nexport function createTabSessionSync({\n getTokenExpirationMs,\n now = Date.now,\n onSessionExpired,\n onSessionLoggedOut,\n onSessionRefreshed,\n onTabSessionUnauthenticated,\n reconcileSession,\n sourceTabID,\n}: CreateTabSessionSyncArgs): {\n broadcast: (event: TabSessionEvent) => TabSessionPublication\n broadcastLogoutSettlement: (publication: TabSessionLogoutPublication) => void\n cleanup: () => void\n} {\n let channel: BroadcastChannel | undefined\n let isActive = true\n let isStorageListenerInstalled = false\n let latestLifecycleOrder: TabSessionLifecycleOrder | undefined\n let pendingStorageLifecycleOrder: TabSessionLifecycleOrder | undefined\n\n const onExpiredSession: OnExpiredSession = ({ lifecycleOrder, message }) => {\n const localExpirationMs = getTokenExpirationMs()\n\n if (localExpirationMs === undefined || localExpirationMs <= message.expiredTokenAt) {\n pendingStorageLifecycleOrder = undefined\n latestLifecycleOrder = lifecycleOrder\n onSessionExpired(message.expiredTokenAt)\n }\n }\n\n const onLoggedOutSession: OnLoggedOutSession = ({ lifecycleOrder }) => {\n pendingStorageLifecycleOrder = undefined\n latestLifecycleOrder = lifecycleOrder\n onSessionLoggedOut()\n }\n\n const onRefreshedSession: OnRefreshedSession = ({ lifecycleOrder, message }) => {\n pendingStorageLifecycleOrder = undefined\n latestLifecycleOrder = lifecycleOrder\n onSessionRefreshed(message.session)\n }\n\n const handleChannelMessage = ({ data }: MessageEvent<unknown>) => {\n if (!isTabSessionMessage(data) || data.sourceTabID === sourceTabID) {\n return\n }\n\n const lifecycleOrder = getLifecycleOrder(data)\n const lifecycleComparison = latestLifecycleOrder\n ? compareLifecycleOrders({ first: lifecycleOrder, second: latestLifecycleOrder })\n : undefined\n const resolvesPendingStorageBarrier =\n lifecycleComparison === 0 && pendingStorageLifecycleOrder === latestLifecycleOrder\n\n if (\n lifecycleComparison !== undefined &&\n (lifecycleComparison < 0 || (lifecycleComparison === 0 && !resolvesPendingStorageBarrier))\n ) {\n return\n }\n\n switch (data.type) {\n case TAB_SESSION_EVENT_TYPES.EXPIRED:\n onExpiredSession({ lifecycleOrder, message: data })\n break\n\n case TAB_SESSION_EVENT_TYPES.LOGGED_OUT:\n onLoggedOutSession({ lifecycleOrder })\n break\n\n case TAB_SESSION_EVENT_TYPES.REFRESHED:\n onRefreshedSession({ lifecycleOrder, message: data })\n break\n }\n }\n\n const reconcileStorageSession: ReconcileStorageSession = ({ notification }) => {\n const isTabSessionEventStale = () => !isActive || latestLifecycleOrder !== notification\n\n void reconcileSession({ isTabSessionEventStale })\n .then((result) => {\n if (!isTabSessionEventStale()) {\n if (result.status === 'authenticated') {\n pendingStorageLifecycleOrder = undefined\n latestLifecycleOrder = {\n type: TAB_SESSION_EVENT_TYPES.REFRESHED,\n affectedExpirationMs: result.expirationMs,\n refreshStartedAt:\n notification.type === TAB_SESSION_EVENT_TYPES.REFRESHED\n ? notification.refreshStartedAt\n : notification.sentAt,\n sentAt: notification.sentAt,\n sourceTabID: notification.sourceTabID,\n }\n } else if (result.status === 'unauthenticated') {\n pendingStorageLifecycleOrder = undefined\n latestLifecycleOrder = {\n type: TAB_SESSION_EVENT_TYPES.LOGGED_OUT,\n affectedExpirationMs: 0,\n sentAt: notification.sentAt,\n sourceTabID: notification.sourceTabID,\n }\n\n onTabSessionUnauthenticated()\n }\n }\n })\n .catch(() => undefined)\n }\n\n const handleStorageNotification = (event: StorageEvent) => {\n const notification = parseTabSessionStorageNotification(event)\n\n if (!notification || notification.sourceTabID === sourceTabID) {\n return\n }\n\n if (channel && !notification.isBroadcastChannelFallback) {\n return\n }\n\n if (\n latestLifecycleOrder &&\n compareLifecycleOrders({ first: notification, second: latestLifecycleOrder }) <= 0\n ) {\n return\n }\n\n pendingStorageLifecycleOrder = notification\n latestLifecycleOrder = notification\n\n if (notification.type === TAB_SESSION_EVENT_TYPES.LOGGED_OUT) {\n onLoggedOutSession({ lifecycleOrder: notification })\n } else {\n reconcileStorageSession({ notification })\n }\n }\n\n function closeChannel(channelToClose: BroadcastChannel | undefined): void {\n if (!channelToClose) {\n return\n }\n\n try {\n channelToClose.removeEventListener('message', handleChannelMessage)\n } catch {\n // Cross-tab synchronization cleanup is best-effort.\n }\n\n try {\n channelToClose.close()\n } catch {\n // Cross-tab synchronization cleanup is best-effort.\n }\n }\n\n function attachStorageListener(): void {\n if (isStorageListenerInstalled) {\n return\n }\n\n try {\n window.addEventListener('storage', handleStorageNotification)\n isStorageListenerInstalled = true\n } catch {\n // Local authentication must continue when storage events are unavailable.\n }\n }\n\n function downgradeToStorage(): void {\n closeChannel(channel)\n channel = undefined\n attachStorageListener()\n }\n\n function getNextLifecycleTimestamp(): number {\n const currentTime = now()\n const latestSentAt = latestLifecycleOrder?.sentAt ?? Number.NEGATIVE_INFINITY\n\n return currentTime > latestSentAt ? currentTime : latestSentAt + 1\n }\n\n const writeStorageNotification: WriteStorageNotification = ({ notification }) => {\n const storageNotification: TabSessionStorageNotification = channel\n ? notification\n : { ...notification, isBroadcastChannelFallback: true }\n\n try {\n localStorage.setItem(tabSessionSyncStorageKey, JSON.stringify(storageNotification))\n } catch {\n // Local authentication must continue when storage is unavailable.\n }\n\n try {\n localStorage.removeItem(tabSessionSyncStorageKey)\n } catch {\n // Local authentication must continue when storage is unavailable.\n }\n }\n\n if (typeof BroadcastChannel === 'function') {\n let nextChannel: BroadcastChannel | undefined\n\n try {\n nextChannel = new BroadcastChannel(tabSessionSyncChannelName)\n\n nextChannel.addEventListener('message', handleChannelMessage)\n channel = nextChannel\n } catch {\n closeChannel(nextChannel)\n channel = undefined\n }\n }\n\n attachStorageListener()\n\n return {\n broadcast: (event) => {\n const sentAt = getNextLifecycleTimestamp()\n const message = createTabSessionMessage({ event, sentAt, sourceTabID })\n const lifecycleOrder = getLifecycleOrder(message)\n\n pendingStorageLifecycleOrder = undefined\n latestLifecycleOrder = lifecycleOrder\n\n if (channel) {\n try {\n channel.postMessage(message)\n } catch {\n downgradeToStorage()\n }\n }\n\n if (lifecycleOrder.type !== TAB_SESSION_EVENT_TYPES.LOGGED_OUT) {\n writeStorageNotification({ notification: lifecycleOrder })\n }\n\n return lifecycleOrder\n },\n broadcastLogoutSettlement: (publication) => {\n const notification: TabSessionStorageNotification = {\n ...publication,\n sentAt: getNextLifecycleTimestamp(),\n settlesSentAt: publication.sentAt,\n }\n\n pendingStorageLifecycleOrder = undefined\n latestLifecycleOrder = notification\n writeStorageNotification({ notification })\n },\n cleanup: () => {\n isActive = false\n pendingStorageLifecycleOrder = undefined\n closeChannel(channel)\n channel = undefined\n\n if (isStorageListenerInstalled) {\n try {\n window.removeEventListener('storage', handleStorageNotification)\n } catch {\n // Cross-tab synchronization cleanup is best-effort.\n }\n\n isStorageListenerInstalled = false\n }\n },\n }\n}\n"],"mappings":"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","ignoreList":[]}
|