401 Unauthorized loop: - client.ts had no 401 handler, leaving stale expired tokens in localStorage. The PrivateRoute guard only checked token existence, so the app would render but every API call would fail silently. - Fix: on any 401 response, clear the token and redirect to /login. 504 Gateway Timeout on nc-scan: - nginx default proxy_read_timeout is 60s. The scan endpoint makes one Nextcloud request per audio file (list + metadata), which easily exceeds that on larger libraries. - Fix: add a dedicated location block for nc-scan with 300s timeouts. General /api/ block gets explicit 60s timeouts for clarity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
49 lines
1.4 KiB
TypeScript
49 lines
1.4 KiB
TypeScript
const BASE = "/api/v1";
|
|
|
|
function getToken(): string | null {
|
|
return localStorage.getItem("rh_token");
|
|
}
|
|
|
|
export function setToken(token: string): void {
|
|
localStorage.setItem("rh_token", token);
|
|
}
|
|
|
|
export function clearToken(): void {
|
|
localStorage.removeItem("rh_token");
|
|
}
|
|
|
|
async function request<T>(
|
|
path: string,
|
|
options: RequestInit = {}
|
|
): Promise<T> {
|
|
const token = getToken();
|
|
const headers: Record<string, string> = {
|
|
"Content-Type": "application/json",
|
|
...(options.headers as Record<string, string>),
|
|
};
|
|
if (token) {
|
|
headers["Authorization"] = `Bearer ${token}`;
|
|
}
|
|
const resp = await fetch(`${BASE}${path}`, { ...options, headers });
|
|
if (!resp.ok) {
|
|
if (resp.status === 401) {
|
|
clearToken();
|
|
window.location.href = "/login";
|
|
throw new Error("Session expired");
|
|
}
|
|
const error = await resp.json().catch(() => ({ detail: resp.statusText }));
|
|
throw new Error(error.detail ?? resp.statusText);
|
|
}
|
|
if (resp.status === 204) return undefined as T;
|
|
return resp.json();
|
|
}
|
|
|
|
export const api = {
|
|
get: <T>(path: string) => request<T>(path),
|
|
post: <T>(path: string, body: unknown) =>
|
|
request<T>(path, { method: "POST", body: JSON.stringify(body) }),
|
|
patch: <T>(path: string, body: unknown) =>
|
|
request<T>(path, { method: "PATCH", body: JSON.stringify(body) }),
|
|
delete: (path: string) => request<void>(path, { method: "DELETE" }),
|
|
};
|