Keep a bounded per-tab logout intent and broadcast it to already open tabs so back navigation after a normal logout is not mistaken for renewal failure. Preserve real refresh and session expiry signals, and keep IdP navigation reachable when local cleanup fails.
58 lines
1.6 KiB
TypeScript
58 lines
1.6 KiB
TypeScript
import {
|
|
clearIntentionalLogout,
|
|
isIntentionalLogout,
|
|
markIntentionalLogout,
|
|
} from "./logoutIntent";
|
|
|
|
describe("intentional logout marker", () => {
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
clearIntentionalLogout();
|
|
});
|
|
|
|
it("stores a timestamp only for the current tab", () => {
|
|
jest.spyOn(Date, "now").mockReturnValue(1_700_000_000_000);
|
|
|
|
markIntentionalLogout();
|
|
|
|
expect(sessionStorage.getItem("tjwater:intentional-logout")).toBe(
|
|
"1700000000000",
|
|
);
|
|
expect(localStorage.getItem("tjwater:intentional-logout")).toBeNull();
|
|
expect(isIntentionalLogout()).toBe(true);
|
|
});
|
|
|
|
it("accepts an intentional logout event from another open tab", () => {
|
|
window.dispatchEvent(
|
|
new StorageEvent("storage", {
|
|
key: "tjwater:intentional-logout-event",
|
|
newValue: JSON.stringify({
|
|
type: "mark",
|
|
markedAt: Date.now(),
|
|
}),
|
|
}),
|
|
);
|
|
|
|
expect(isIntentionalLogout()).toBe(true);
|
|
});
|
|
|
|
it("does not broadcast a clear event when the current tab has no marker", () => {
|
|
clearIntentionalLogout();
|
|
const setItem = jest.spyOn(Storage.prototype, "setItem");
|
|
|
|
expect(isIntentionalLogout()).toBe(false);
|
|
expect(setItem).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("expires a stale marker", () => {
|
|
const now = 1_700_000_000_000;
|
|
const nowSpy = jest.spyOn(Date, "now").mockReturnValue(now);
|
|
markIntentionalLogout();
|
|
|
|
nowSpy.mockReturnValue(now + 30 * 60 * 1000 + 1);
|
|
|
|
expect(isIntentionalLogout()).toBe(false);
|
|
expect(sessionStorage.getItem("tjwater:intentional-logout")).toBeNull();
|
|
});
|
|
});
|