import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from "react"; import { ApiError, login as apiLogin, logout as apiLogout } from "@/lib/api"; import type { LoginResponse } from "@/lib/types"; const STORAGE_KEY = "nxdns_auth_required"; export function readStoredAuthRequired(): boolean | null { try { const raw = sessionStorage.getItem(STORAGE_KEY); return raw === null ? null : raw === "true"; } catch { return null; } } export function rememberAuthRequired(value: boolean): void { try { sessionStorage.setItem(STORAGE_KEY, String(value)); } catch { // Storage unavailable; the probe will run again next load. } } // Deduped across StrictMode double-effects: one empty-password login answers // whether auth is on (401 → on; 200 with auth_required=false → off). let probePromise: Promise | null = null; function probeAuthRequired(): Promise { probePromise ??= apiLogin({ password: "" }).then( (response) => { rememberAuthRequired(response.auth_required); return response.auth_required; }, (error: unknown) => { probePromise = null; if (error instanceof ApiError && error.status === 401) { rememberAuthRequired(true); return true; } throw error; }, ); return probePromise; } export function resetAuthProbeForTests(): void { probePromise = null; } export interface AuthStore { /** null until a login response, a probe, or a stored value settles it. */ authRequired: boolean | null; /** Resolves true when a password is required (form must be shown). */ probe: () => Promise; login: (password: string) => Promise; /** Ends the session server-side; swallows an already-dead session's 401. */ logout: () => Promise; } const AuthContext = createContext(null); export function AuthProvider({ children }: { children: ReactNode }) { const [authRequired, setAuthRequired] = useState(readStoredAuthRequired); const probe = useCallback(async () => { const required = await probeAuthRequired(); setAuthRequired(required); return required; }, []); useEffect(() => { if (authRequired !== null) return; probe().catch(() => { // Server unreachable; LoginPage's own probe surfaces the error. }); }, [authRequired, probe]); const login = useCallback(async (password: string) => { const response = await apiLogin({ password }); rememberAuthRequired(response.auth_required); setAuthRequired(response.auth_required); return response; }, []); const logout = useCallback(async () => { try { await apiLogout(); } catch (error) { if (error instanceof ApiError && error.status === 401) return; throw error; } }, []); const value = useMemo( () => ({ authRequired, probe, login, logout }), [authRequired, probe, login, logout], ); return {children}; } export function useAuth(): AuthStore { const store = useContext(AuthContext); if (store === null) throw new Error("useAuth requires an AuthProvider"); return store; }