import { ref } from 'vue' const hasLocalStorage = typeof localStorage !== 'undefined' && typeof localStorage.getItem === 'function' const AUTH_SYNC_EVENT_KEY = 'authSyncEvent' const PARENT_AUTH_KEY = 'parentAuth' const PARENT_RETURN_URL_KEY = 'parentReturnUrl' const PARENT_AUTH_EXPIRY_NON_PERSISTENT = 60_000 // 1 minute const PARENT_AUTH_EXPIRY_PERSISTENT = 172_800_000 // 2 days // --- Parent auth expiry state --- export const isParentAuthenticated = ref(false) export const isParentPersistent = ref(false) export const parentAuthExpiresAt = ref(null) // --- Background expiry watcher --- // Declared before the init block so startParentExpiryWatcher is safe to call during restore. let expiryWatcherIntervalId: ReturnType | null = null function runExpiryCheck() { if (parentAuthExpiresAt.value !== null && Date.now() >= parentAuthExpiresAt.value) { applyParentLoggedOutState() if (typeof window !== 'undefined') { window.location.href = '/child' } } } export function startParentExpiryWatcher() { stopParentExpiryWatcher() expiryWatcherIntervalId = setInterval(runExpiryCheck, 15_000) } export function stopParentExpiryWatcher() { if (expiryWatcherIntervalId !== null) { clearInterval(expiryWatcherIntervalId) expiryWatcherIntervalId = null } } // Restore persistent parent auth from localStorage on store init if (hasLocalStorage) { try { const stored = localStorage.getItem(PARENT_AUTH_KEY) if (stored) { const parsed = JSON.parse(stored) as { expiresAt: number } if (parsed.expiresAt && Date.now() < parsed.expiresAt) { parentAuthExpiresAt.value = parsed.expiresAt isParentPersistent.value = true isParentAuthenticated.value = true startParentExpiryWatcher() } else { localStorage.removeItem(PARENT_AUTH_KEY) } } } catch { localStorage.removeItem(PARENT_AUTH_KEY) } } export const isUserLoggedIn = ref(false) export const isAuthReady = ref(false) export const currentUserId = ref('') export const suppressForceLogout = ref(false) let authSyncInitialized = false /** * Explicitly checks whether parent auth has expired and clears it if so. * Called by the router guard before allowing /parent routes. */ export function enforceParentExpiry() { runExpiryCheck() } export function setPendingReturnUrl(url: string) { if (!url.startsWith('/parent')) return if (hasLocalStorage) { localStorage.setItem(PARENT_RETURN_URL_KEY, url) } } export function consumePendingReturnUrl(): string | null { if (!hasLocalStorage) return null const url = localStorage.getItem(PARENT_RETURN_URL_KEY) if (url) { localStorage.removeItem(PARENT_RETURN_URL_KEY) } return url } export function hasPendingReturnUrl(): boolean { if (!hasLocalStorage) return false return localStorage.getItem(PARENT_RETURN_URL_KEY) !== null } export function clearPendingReturnUrl() { if (hasLocalStorage) { localStorage.removeItem(PARENT_RETURN_URL_KEY) } } export function authenticateParent(persistent: boolean) { const duration = persistent ? PARENT_AUTH_EXPIRY_PERSISTENT : PARENT_AUTH_EXPIRY_NON_PERSISTENT parentAuthExpiresAt.value = Date.now() + duration isParentPersistent.value = persistent isParentAuthenticated.value = true if (persistent && hasLocalStorage && typeof localStorage.setItem === 'function') { localStorage.setItem(PARENT_AUTH_KEY, JSON.stringify({ expiresAt: parentAuthExpiresAt.value })) } startParentExpiryWatcher() } export function logoutParent() { if (hasLocalStorage && typeof localStorage.removeItem === 'function') { localStorage.removeItem(PARENT_AUTH_KEY) } applyParentLoggedOutState() broadcastParentLogoutEvent() } function applyParentLoggedOutState() { parentAuthExpiresAt.value = null isParentPersistent.value = false isParentAuthenticated.value = false stopParentExpiryWatcher() } function broadcastParentLogoutEvent() { if (!hasLocalStorage || typeof localStorage.setItem !== 'function') return localStorage.setItem( AUTH_SYNC_EVENT_KEY, JSON.stringify({ type: 'parent_logout', at: Date.now() }), ) } export function loginUser() { isUserLoggedIn.value = true // Always start in child mode after login. Also explicitly remove persistent parent // auth from localStorage so a fresh login always starts in child mode. if (hasLocalStorage && typeof localStorage.removeItem === 'function') { localStorage.removeItem(PARENT_AUTH_KEY) } applyParentLoggedOutState() } function applyLoggedOutState() { isUserLoggedIn.value = false currentUserId.value = '' applyParentLoggedOutState() } function broadcastLogoutEvent() { if (!hasLocalStorage || typeof localStorage.setItem !== 'function') return localStorage.setItem(AUTH_SYNC_EVENT_KEY, JSON.stringify({ type: 'logout', at: Date.now() })) } export function logoutUser() { applyLoggedOutState() broadcastLogoutEvent() } export function initAuthSync() { if (authSyncInitialized || typeof window === 'undefined') return authSyncInitialized = true window.addEventListener('storage', (event) => { if (event.key !== AUTH_SYNC_EVENT_KEY || !event.newValue) return try { const payload = JSON.parse(event.newValue) if (payload?.type === 'logout') { applyLoggedOutState() if (!window.location.pathname.startsWith('/auth') && window.location.pathname !== '/') { window.location.href = '/' } } else if (payload?.type === 'parent_logout') { applyParentLoggedOutState() if (window.location.pathname.startsWith('/parent')) { window.location.href = '/child' } } } catch { // Ignore malformed sync events. } }) } export async function checkAuth() { try { const res = await fetch('/api/auth/me', { method: 'GET' }) if (res.ok) { const data = await res.json() currentUserId.value = data.id isUserLoggedIn.value = true } else { isUserLoggedIn.value = false currentUserId.value = '' } } catch { isUserLoggedIn.value = false currentUserId.value = '' } isAuthReady.value = true }