import { createContext, useContext, useEffect, useState, useCallback, useMemo } from 'react' import { api } from '../api/client.js' const AuthContext = createContext(null) export function AuthProvider({ children }) { const [user, setUser] = useState(null) const [loading, setLoading] = useState(true) const refresh = useCallback(async () => { try { const data = await api.me() setUser(data.user) } catch { setUser(null) } finally { setLoading(false) } }, []) useEffect(() => { refresh() }, [refresh]) // Step 1. Returns { user } on success, or { totpRequired, challenge } when the // account has 2FA on (caller then calls loginTotp). `extra` carries honeypot. const login = useCallback(async (username, password, extra) => { const data = await api.login(username, password, extra) if (data.user) setUser(data.user) return data }, []) // Public self-registration (player). Creates the account, sets the session // cookie, and returns { user }. `extra` carries the honeypot + optional email. const register = useCallback(async (username, password, extra) => { const data = await api.register(username, password, extra) if (data.user) setUser(data.user) return data }, []) // Step 2 for TOTP users: exchange the challenge + a second factor (TOTP code or a // recovery code) for a real session. `extra` carries recoveryCode + the // trustDevice/deviceName opt-in. Returns the full payload ({ user, // trustLimitReached?, devices? }) so the caller can handle the device-cap prompt. const loginTotp = useCallback(async (challenge, code, extra) => { const data = await api.loginTotp(challenge, code, extra) setUser(data.user) return data }, []) // Step 2 for SSO logins whose account has 2FA on. The pending challenge lives in // an httpOnly cookie, so only the code is sent. Returns { user, returnTo }. const ssoLoginTotp = useCallback(async (code) => { const data = await api.ssoLoginTotp(code) setUser(data.user) return data }, []) const logout = useCallback(async () => { try { await api.logout() } finally { setUser(null) } }, []) // Memoized so consumers don't re-render on every provider render (the callbacks // are already stable via useCallback). const value = useMemo( () => ({ user, loading, login, register, loginTotp, ssoLoginTotp, logout, refresh }), [user, loading, login, register, loginTotp, ssoLoginTotp, logout, refresh], ) return ( {children} ) } export function useAuth() { const ctx = useContext(AuthContext) if (!ctx) throw new Error('useAuth must be used within AuthProvider') return ctx }