import { createContext, useContext, useEffect, useState } from "react"; import { api, ApiError, getToken, setToken, setUnauthorizedHandler } from "./api"; const AuthContext = createContext(null); export function AuthProvider({ children }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); // Anmeldung war da, ist aber abgelaufen – der Login-Bildschirm sagt warum. const [abgelaufen, setAbgelaufen] = useState(false); // Start fehlgeschlagen, ABER nicht wegen der Anmeldung (Server weg, Netz weg). // Dann bleibt das Token liegen; alles andere hiesse, ein kurzer Aussetzer // meldet einen ab. const [startFehler, setStartFehler] = useState(null); // Holt ein frisches Token, damit die eingestellte Dauer ab dem letzten // Besuch zaehlt statt ab dem Anmelden. // // Der Fehler wird geschluckt, weil ein Server- oder Netzfehler hier nichts // kostet: das bisherige Token gilt ja weiter. Ein 401 waere etwas anderes – // um den kuemmert sich bereits die zentrale Abmeldung in api.js, und er kann // direkt nach einem geglueckten me() ohnehin nicht auftreten. async function sitzungVerlaengern() { try { const res = await api.sitzungVerlaengern(); setToken(res.access_token); } catch { /* siehe oben */ } } async function pruefen() { setStartFehler(null); if (!getToken()) { setLoading(false); return; } try { setUser(await api.me()); await sitzungVerlaengern(); } catch (err) { // NUR bei abgelehnter Anmeldung abmelden. Ein 502 waehrend eines // Server-Neustarts ist kein Grund, die Sitzung wegzuwerfen. if (err instanceof ApiError && (err.status === 401 || err.status === 403)) { setToken(null); setUser(null); } else if (err instanceof ApiError) { setStartFehler(err.message); } else { // Kein HTTP-Fehler, sondern gar keine Antwort (Netz weg, Server aus). // Die Browser-Meldung („Failed to fetch") hilft niemandem. setStartFehler("Server nicht erreichbar."); } } finally { setLoading(false); } } useEffect(() => { pruefen(); }, []); // Laeuft die Anmeldung waehrend der Nutzung ab, scheitert ab da JEDE Abfrage. // Ohne diesen Griff bliebe die Oberflaeche „angemeldet" und zeigte nur noch // leere Seiten – genau das war der Fehler. useEffect(() => { setUnauthorizedHandler(() => { setToken(null); setUser(null); setAbgelaufen(true); }); return () => setUnauthorizedHandler(null); }, []); async function login(username, password) { const res = await api.login(username, password); setToken(res.access_token); const me = await api.me(); setUser(me); setAbgelaufen(false); setStartFehler(null); return me; } // Nach dem Single-Sign-on kommt das fertige Token von aussen zurueck (aus dem // Adressfragment), nicht aus einem Formular. `login` passt dafuer nicht – das // kennt nur Benutzername und Passwort. async function tokenUebernehmen(token) { setToken(token); try { const me = await api.me(); setUser(me); setAbgelaufen(false); setStartFehler(null); return me; } catch (err) { // Ein Token, mit dem sich nichts abrufen laesst, darf nicht liegenbleiben – // sonst zeigt die Oberflaeche „angemeldet" und lauter leere Seiten. setToken(null); setUser(null); throw err; } } function logout() { setToken(null); setUser(null); setAbgelaufen(false); } async function erneutVersuchen() { setLoading(true); await pruefen(); } const isAdmin = user?.role === "admin"; return ( {children} ); } export function useAuth() { return useContext(AuthContext); }