import { createContext, type PropsWithChildren, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { ApiError } from '../api/client'; import { getSessionMe, loginSession, logoutSession, refreshSession, type SessionLoginRequest, type SessionProfile } from '../api/sessionApi'; export type SessionStatus = 'loading' | 'authenticated' | 'unauthenticated'; type SessionContextValue = { status: SessionStatus; profile: SessionProfile | null; error: string | null; login: (request: Omit & { correlationId?: string }) => Promise; refresh: () => Promise; logout: () => Promise; revalidate: () => Promise; }; const SessionContext = createContext(undefined); export function SessionProvider({ children }: PropsWithChildren) { const [status, setStatus] = useState('loading'); const [profile, setProfile] = useState(null); const [error, setError] = useState(null); const revalidate = useCallback(async () => { try { const me = await getSessionMe(); if (me.isAuthenticated) { setProfile(me); setStatus('authenticated'); } else { setProfile(null); setStatus('unauthenticated'); } setError(null); } catch (err) { if (err instanceof ApiError && err.status === 401) { setProfile(null); setStatus('unauthenticated'); setError(null); return; } const message = err instanceof Error ? err.message : 'Session validation failed.'; setProfile(null); setStatus('unauthenticated'); setError(message); } }, []); useEffect(() => { void revalidate(); }, [revalidate]); const login = useCallback( async (request) => { setError(null); await loginSession({ ...request, correlationId: request.correlationId && request.correlationId.length > 0 ? request.correlationId : createCorrelationId() }); await revalidate(); }, [revalidate] ); const refresh = useCallback(async () => { setError(null); await refreshSession(); await revalidate(); }, [revalidate]); const logout = useCallback(async () => { setError(null); try { await logoutSession(); } finally { setProfile(null); setStatus('unauthenticated'); } }, []); const value = useMemo( () => ({ status, profile, error, login, refresh, logout, revalidate }), [status, profile, error, login, refresh, logout, revalidate] ); return {children}; } export function useSessionContext(): SessionContextValue { const value = useContext(SessionContext); if (!value) { throw new Error('useSessionContext must be used within SessionProvider.'); } return value; } function createCorrelationId(): string { if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { return crypto.randomUUID(); } return `corr-${Date.now()}-${Math.random().toString(16).slice(2)}`; }