const MILLISECONDS_PER_SECOND = 1000; export function decodeJwtPayload(token: string): Record | null { const parts = token.split('.'); if (parts.length !== 3) { return null; } const base64Url = parts[1]; const base64 = base64Url.replaceAll('-', '+').replaceAll('_', '/'); const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '='); try { const payload = JSON.parse(atob(padded)); if (payload && typeof payload === 'object') { return payload as Record; } } catch { return null; } return null; } export function isJwtExpired(token: string, skewSeconds = 0) { const payload = decodeJwtPayload(token); const exp = payload?.exp; if (typeof exp !== 'number' || !Number.isFinite(exp)) { return false; } const expiresAt = exp * MILLISECONDS_PER_SECOND; const now = Date.now(); return expiresAt <= now + skewSeconds * MILLISECONDS_PER_SECOND; }