diff --git a/frontend/src/auth/AuthProvider.tsx b/frontend/src/auth/AuthProvider.tsx index 1091794..eb6a398 100644 --- a/frontend/src/auth/AuthProvider.tsx +++ b/frontend/src/auth/AuthProvider.tsx @@ -3,6 +3,7 @@ import type { ReactNode } from "react"; import type { User } from "../utils/types.ts"; import { login as apiLogin, logout as apiLogout, getUser } from "../utils/api/users.ts"; import { AuthContext } from "./AuthContext.ts"; +import {registerUnauthorizedHandler} from "../utils/api/client.ts"; /** * Owns the single source of truth for the authenticated user. Everything that @@ -36,6 +37,10 @@ const AuthProvider = ({ children }: { children: ReactNode }) => { setUser(null); }, []); + useEffect(() => { + registerUnauthorizedHandler(logout); + }, [logout]); + return ( {children} diff --git a/frontend/src/utils/api/client.ts b/frontend/src/utils/api/client.ts index 8d6df5f..abafd74 100644 --- a/frontend/src/utils/api/client.ts +++ b/frontend/src/utils/api/client.ts @@ -6,9 +6,32 @@ class AuthError extends Error { constructor() { super("Not authenticated"); this.name = "AuthError"; } } -const isThumbnailAvailable = async (thumbnailUrl: string): Promise => { - const response = await fetch(thumbnailUrl, { credentials: 'include' }); - return response.ok; -}; +let onUnauthorized: (() => void) | null = null; -export { API_URL, AuthError, isThumbnailAvailable }; +export function registerUnauthorizedHandler(handler: () => void) { + onUnauthorized = handler; +} + +async function apiFetch(url: string, options: RequestInit = {}): Promise { + const response = await fetch(url, { + ...options, + credentials: 'include' + }); + + if (response.status == 401 || response.status == 403) { + onUnauthorized?.(); + throw new AuthError(); + } + + if (!response.ok) { + const text = await response.text(); + let message = text; + try { message = JSON.parse(text).message ?? text; } catch { /* not JSON, use raw text */ } + throw new Error(message || `Request failed: ${response.status}`); + } + + const text = await response.text(); + return text ? JSON.parse(text) : (undefined as T); +} + +export { API_URL, AuthError };