import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react' import { getMe, login as apiLogin, logout as apiLogout, type CurrentUser } from '@/api/auth' import { authStore } from '@/auth/authStore' import { refreshAccessToken } from '@/api/client' import { AuthContext, type AuthContextValue, type AuthStatus } from '@/auth/authContext' /** * Задержки повторов восстановления сессии, если backend отвечает 5xx. * * Недоступность сервера — не повод объявлять пользователя неавторизованным: * refresh-cookie цела, и через несколько секунд сессия обычно поднимается * сама. Повторов ровно три (суммарно ~7 с) — дальше показываем страницу * входа, потому что бесконечный спиннер хуже честного «войдите заново»: * cookie при этом не стирается, и повторная попытка входа сработает, как * только backend оживёт. */ const BOOTSTRAP_RETRY_DELAYS_MS = [1000, 2000, 4000] const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) /** * Провайдер сессии пользователя. * При монтировании приложения пытается восстановить сессию через * silent-refresh (httpOnly refresh-cookie), т.к. access-токен в памяти * теряется при перезагрузке страницы. */ export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null) const [status, setStatus] = useState('loading') useEffect(() => { let cancelled = false async function bootstrap() { let outcome = await refreshAccessToken() for (const delay of BOOTSTRAP_RETRY_DELAYS_MS) { if (cancelled || outcome.result !== 'unavailable') break await sleep(delay) if (cancelled) return outcome = await refreshAccessToken() } if (cancelled) return if (outcome.result !== 'ok') { setStatus('unauthenticated') return } try { const me = await getMe() if (cancelled) return setUser(me) setStatus('authenticated') } catch { if (!cancelled) setStatus('unauthenticated') } } void bootstrap() return () => { cancelled = true } }, []) const login = useCallback(async (email: string, password: string) => { const token = await apiLogin(email, password) authStore.setAccessToken(token.access_token) const me = await getMe() setUser(me) setStatus('authenticated') }, []) const logout = useCallback(async () => { try { await apiLogout() } finally { authStore.setAccessToken(null) setUser(null) setStatus('unauthenticated') } }, []) const refreshUser = useCallback(async () => { const me = await getMe() setUser(me) }, []) const value = useMemo( () => ({ user, status, login, logout, refreshUser }), [user, status, login, logout, refreshUser], ) return {children} }