@judo/guards
v0.1.1
Published
Navigation guards for JUDO UI Runtime
Readme
@judo/guards
Navigation guards for JUDO UI Runtime
Purpose
Provides two runtime guard mechanisms:
- Unsaved Changes Guard — prevents navigation away from dirty forms, with browser
beforeunloadsupport - Session Timeout Guard — monitors user inactivity, shows a warning dialog before session expiry, and forces logout on timeout
Architecture Layer
Layer 4 (Features) — consumed by app-shell and core for navigation protection.
Dependencies
@judo/model-api— model type definitions (peer)@judo/feedback— dialog service for confirmation dialogs (peer)@mui/material ^7— MUI components for session timeout dialog (peer)react ^19— React (peer)react-router ^7— routing (peer)
File Structure
src/
├── index.ts # Barrel re-exports
├── unsaved-changes/
│ ├── unsaved-changes-guard.ts # Interface + class
│ ├── use-unsaved-changes-guard.ts # React hook
│ ├── unsaved-changes-guard.test.ts
│ └── use-unsaved-changes-guard.test.tsx
└── session-timeout/
├── session-timeout-guard.ts # Interface + config + class
├── session-timeout-dialog.tsx # React component
├── session-timeout-guard.test.tsx
└── session-timeout-dialog.test.tsxExports Summary
Unsaved Changes Guard
| Export | Kind | Description |
| ---------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------- |
| UnsavedChangesGuard | interface | Interface for managing unsaved-changes warnings across multiple forms/pages. |
| UnsavedChangesGuardImpl | class | Concrete implementation. Stores dirty-checkers in a Map. Uses DialogService for confirmation. |
| useUnsavedChangesGuard(guard, isDirty) | hook | Registers the component with the guard when dirty. Attaches browser beforeunload handler. Auto-cleans up. |
UnsavedChangesGuard methods:
| Method | Returns | Description |
| ----------------------- | ------------------ | ---------------------------------------------------------------------------- |
| hasUnsavedChanges() | boolean | Iterates all registered dirty-checkers; returns true if any is dirty. |
| register(id, isDirty) | () => void | Registers a form/page by ID with a dirty callback. Returns cleanup function. |
| unregister(id) | void | Removes a previously registered dirty-checker. |
| confirmNavigation() | Promise<boolean> | If dirty, shows a confirm dialog; resolves true if user chooses to leave. |
Session Timeout Guard
| Export | Kind | Description |
| --------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------ |
| SessionTimeoutGuard | interface | Interface for monitoring user inactivity and triggering timeout flows. |
| SessionTimeoutConfig | interface | Configuration: timeoutMs? (default: 30 min), warningMs? (default: 5 min), onTimeout?, onWarning?. All fields optional. |
| SessionTimeoutGuardImpl | class | Implementation with timer cascade and DOM activity listeners. |
| SessionTimeoutDialog | component | MUI Dialog showing either "Session Expiring" (with continue/logout) or "Session Expired" (with login). |
| SessionTimeoutDialogProps | interface | Props: guard, onLogout. |
SessionTimeoutGuard methods:
| Method | Returns | Description |
| --------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------- |
| start() | void | Begins monitoring; attaches DOM listeners (mousedown, keydown, scroll, touchstart) and schedules timers. |
| stop() | void | Clears timers, detaches listeners, resets state, notifies subscribers. |
| resetActivity() | void | Resets inactivity timer (no-op if already timed out). |
| isTimedOut() | boolean | Returns current timed-out state. |
| subscribe(listener) | () => void | Subscribes to (isWarning, isTimedOut) state changes; returns unsubscribe function. |
Key Patterns
- Interface + Impl class: Both guards define an interface and a separate
*Implclass, enabling DI and testability - Manual pub/sub:
SessionTimeoutGuardImpluses aSet<listener>withsubscribereturning unsubscribe — compatible withuseSyncExternalStore useSyncExternalStore:SessionTimeoutDialogbridges the imperative guard to React's concurrent-safe external store APIuseIdfor registration:useUnsavedChangesGuarduses React 18+useIdfor a stable, unique registration key- Browser
beforeunload: The unsaved-changes hook traps browser-level navigation (tab close, refresh) - Timer cascade: Session timeout uses a two-phase timer:
scheduleWarning()fires first (attimeoutMs - warningMs), then chains toscheduleTimeout()which fires after the remainingwarningMswindow. On timeout, activity listeners are detached. - No React context/provider: Guards are plain classes passed as props or hook arguments — no provider in this package
- Dependency on
@judo/feedback:UnsavedChangesGuardImpltakes aDialogServicefor confirmation dialogs
