add apiFetch that checks for logout
All checks were successful
CI / Maven unit tests (push) Successful in 14s
CI / Build and push images (push) Has been skipped
CI / Deploy to server (push) Has been skipped

This commit is contained in:
2026-09-06 21:14:29 +01:00
parent 6fa02a34a3
commit 529b4c7725
2 changed files with 33 additions and 5 deletions

View File

@@ -3,6 +3,7 @@ import type { ReactNode } from "react";
import type { User } from "../utils/types.ts"; import type { User } from "../utils/types.ts";
import { login as apiLogin, logout as apiLogout, getUser } from "../utils/api/users.ts"; import { login as apiLogin, logout as apiLogout, getUser } from "../utils/api/users.ts";
import { AuthContext } from "./AuthContext.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 * Owns the single source of truth for the authenticated user. Everything that
@@ -36,6 +37,10 @@ const AuthProvider = ({ children }: { children: ReactNode }) => {
setUser(null); setUser(null);
}, []); }, []);
useEffect(() => {
registerUnauthorizedHandler(logout);
}, [logout]);
return ( return (
<AuthContext.Provider value={{ user, loading, login, logout, refresh }}> <AuthContext.Provider value={{ user, loading, login, logout, refresh }}>
{children} {children}

View File

@@ -6,9 +6,32 @@ class AuthError extends Error {
constructor() { super("Not authenticated"); this.name = "AuthError"; } constructor() { super("Not authenticated"); this.name = "AuthError"; }
} }
const isThumbnailAvailable = async (thumbnailUrl: string): Promise<boolean> => { let onUnauthorized: (() => void) | null = null;
const response = await fetch(thumbnailUrl, { credentials: 'include' });
return response.ok;
};
export { API_URL, AuthError, isThumbnailAvailable }; export function registerUnauthorizedHandler(handler: () => void) {
onUnauthorized = handler;
}
async function apiFetch<T>(url: string, options: RequestInit = {}): Promise<T> {
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 };