import { useEffect, useState } from 'react' import { useAuthStore } from '@/store/authStore' const SOON_MS = 5 * 60 * 1000 // 5 minutes export type ExpiryWarning = 'none' | 'soon' | 'now' export type ExpiryReason = 'idle' | 'absolute' interface ExpiryState { idleExpiresAt: Date | null absoluteExpiresAt: Date | null warning: ExpiryWarning /** * Which window is the closer (and therefore the active) deadline. Used by * SessionExpiryToast to pick the right copy + action button: idle gets * "Stay signed in" (calls /auth/refresh); absolute is informational only. */ reason: ExpiryReason | null } function computeState(token: ReturnType['token']): ExpiryState { const idleStr = token?.idle_expires_at const absStr = token?.absolute_expires_at if (!idleStr || !absStr) { return { idleExpiresAt: null, absoluteExpiresAt: null, warning: 'none', reason: null } } const idle = new Date(idleStr) const abs = new Date(absStr) const now = Date.now() const idleMs = idle.getTime() - now const absMs = abs.getTime() - now // Closer window wins. const reason: ExpiryReason = idleMs <= absMs ? 'idle' : 'absolute' const closestMs = Math.min(idleMs, absMs) let warning: ExpiryWarning = 'none' if (closestMs <= 0) warning = 'now' else if (closestMs <= SOON_MS) warning = 'soon' return { idleExpiresAt: idle, absoluteExpiresAt: abs, warning, reason } } /** * Track how close the active session is to its idle/absolute deadline. * * Returns `warning: "soon"` within 5 min of whichever window comes first, * and `reason: "idle" | "absolute"` so callers can choose the right UX * (idle is recoverable via /auth/refresh; absolute is not). Re-evaluates * every 30 seconds while authenticated; cheap (single Date subtraction). * * See docs/plans/2026-05-13-session-expiration-policy.md ยง4.8. */ export function useAuthSessionExpiry(): ExpiryState { const token = useAuthStore((s) => s.token) const [state, setState] = useState(() => computeState(token)) useEffect(() => { setState(computeState(token)) if (!token?.idle_expires_at || !token?.absolute_expires_at) return const interval = window.setInterval(() => setState(computeState(token)), 30_000) return () => window.clearInterval(interval) }, [token]) return state }