const TOKEN_KEY = 'foxhunt_token'; const REFRESH_KEY = 'foxhunt_refresh_token'; /** Store tokens after login */ export function setTokens(accessToken: string, refreshToken?: string): void { localStorage.setItem(TOKEN_KEY, accessToken); if (refreshToken) { localStorage.setItem(REFRESH_KEY, refreshToken); } } /** Clear tokens on logout */ export function clearTokens(): void { localStorage.removeItem(TOKEN_KEY); localStorage.removeItem(REFRESH_KEY); } /** Get current access token */ export function getToken(): string | null { return localStorage.getItem(TOKEN_KEY); } /** Check if the user is authenticated (has a non-expired token) */ export function isAuthenticated(): boolean { const token = getToken(); if (!token) return false; try { const payload = JSON.parse(atob(token.split('.')[1])); return payload.exp * 1000 > Date.now(); } catch { return false; } } /** Get the expiry timestamp (seconds since epoch) from a JWT, or null if undecodable */ export function getTokenExpiry(token: string): number | null { try { const payload = JSON.parse(atob(token.split('.')[1])); return typeof payload.exp === 'number' ? payload.exp : null; } catch { return null; } } /** Check whether a JWT token is expired or within `marginSeconds` of expiry */ export function isTokenExpired(token: string, marginSeconds = 0): boolean { const exp = getTokenExpiry(token); if (exp === null) return true; // treat undecodable tokens as expired return exp - marginSeconds <= Date.now() / 1000; } /** Session-expired flag key for sessionStorage */ export const SESSION_EXPIRED_KEY = 'session_expired'; /** Decode JWT claims without verification (for display purposes) */ export function getTokenClaims(): { sub: string; roles: string[] } | null { const token = getToken(); if (!token) return null; try { return JSON.parse(atob(token.split('.')[1])); } catch { return null; } }