@financedistrict/idle-session
v0.1.1
Published
Cross-tab idle-timeout detection for FD apps — tracks aggregate inactivity across every open tab on the same origin and fires a callback once none of them have seen activity for the configured timeout.
Readme
@financedistrict/idle-session
Cross-tab idle-timeout detection for FD apps. Every FD app that mounts this
hook on the same origin (e.g. everything under apps.test.1stdigital.tech)
shares one idle clock: activity in any open tab keeps the session alive;
only once every tab has been quiet for the configured timeout does
onIdle fire.
Why cross-tab, not per-tab
A per-tab timer logs a user out while they're actively working in a
different tab on the same origin — this package exists specifically to avoid
that. It broadcasts activity through a shared localStorage key (not the
usual app-namespaced kind — the whole point is that every app reads and
writes the same one) and reconciles across tabs via the storage event, plus
a visibilitychange recheck to catch up when a backgrounded tab's own timer
gets throttled by the browser.
Usage
import { useIdleSession } from "@financedistrict/idle-session";
import { useMsal } from "@azure/msal-react";
function App() {
const { instance } = useMsal();
useIdleSession({
timeoutMs: 15 * 60 * 1000, // 15 minutes
onIdle: () => instance.logoutRedirect(),
});
return <Routes />;
}Mount the hook once, near the root, alongside the other session-scoped
providers. onIdle is expected to end the session — an MSAL logout is the
norm — the hook itself never touches auth state or calls MSAL.
API
useIdleSession({
timeoutMs: number; // required — ms of aggregate cross-tab inactivity before onIdle fires
onIdle: () => void; // required — called once, in every open tab, when the timeout is reached
activityEvents?: string[]; // default: mousedown, mousemove, keydown, scroll, touchstart, wheel
activityThrottleMs?: number; // default: 1000 — how often activity is broadcast to other tabs
checkIntervalMs?: number; // default: 5000 — how often each tab re-derives elapsed idle time
storagePrefix?: string; // default: "fd:idle-session" — shared across every app on the origin;
// override only if this app deliberately wants its own, isolated idle clock
enabled?: boolean; // default: true
});onIdle fires once per tab, per idle period. A tab that already fired won't
fire again until fresh activity resumes the shared clock — in practice a page
reload after logout resets this naturally.
If you pass a custom activityEvents array, memoize it (useMemo): it's a
dependency of the hook's internal effect, so a fresh array identity on every
render resubscribes the listeners every render.
What this package does not do
- It does not call MSAL, or decide what "logged out" means for your app —
that's
onIdle's job. - It does not show a "you're about to be logged out" warning before firing.
A product that wants one needs a second, shorter-fused
useIdleSessioncall (or a follow-up to this package) — not built in here. - Like a route guard, this is a UX affordance, not a security control: actual session and token lifetime are enforced by MSAL and the BFF, not by this timer. A tampered or bypassed client-side timer never grants extra access.
