Files
SDS-CRM/public/js/api.js
2026-03-23 02:09:14 +01:00

130 lines
3.6 KiB
JavaScript

async function parseError(res, text) {
try {
const j = JSON.parse(text);
if (j.message) {
const base = Array.isArray(j.message) ? j.message.join(', ') : j.message;
const parts = [base];
if (j.detail && String(j.detail).trim()) {
parts.push(String(j.detail).trim());
}
if (j.hint && String(j.hint).trim()) {
parts.push(String(j.hint).trim());
}
return parts.join(' — ');
}
} catch {
/* ignore */
}
return text || res.statusText;
}
function redirectToLogin() {
const p = location.pathname;
if (p !== '/login.html' && p !== '/bootstrap.html') {
location.href = '/login.html';
}
}
/** Geschützte REST-API liegt unter /api (Root-URLs bleiben für die SPA frei). */
export function apiUrl(path) {
if (path.startsWith('/auth/')) return path;
return `/api${path.startsWith('/') ? path : `/${path}`}`;
}
function onUnauthorized(path) {
if (path.startsWith('/auth/')) return;
redirectToLogin();
}
/** Wird bei 401 geworfen: Aufrufer sollen keine Fehlerseite rendern. */
export function isAuthRedirectError(e) {
return Boolean(e && (e.authRedirect === true || e.name === 'AuthRedirect'));
}
function authRedirectError() {
const err = new Error('SESSION');
err.name = 'AuthRedirect';
err.authRedirect = true;
return err;
}
function parseJsonBody(text) {
if (!text) return null;
const trimmed = text.trim();
if (trimmed.startsWith('<')) {
throw new Error(
'Server lieferte HTML statt JSON (API-URL/Proxy prüfen oder Server neu starten).',
);
}
try {
return JSON.parse(text);
} catch (e) {
throw new Error(
e && String(e.message || e).includes('JSON')
? 'Ungültige Server-Antwort (kein JSON).'
: e.message || 'Ungültige Server-Antwort',
);
}
}
async function apiRequest(method, path, body) {
const url = apiUrl(path);
const opt = { method, credentials: 'include', headers: {} };
if (body !== undefined) {
opt.headers['Content-Type'] = 'application/json';
opt.body = JSON.stringify(body);
}
const res = await fetch(url, opt);
const text = await res.text();
if (res.status === 401) {
if (path.startsWith('/auth/')) {
throw new Error((await parseError(res, text)) || 'Anmeldung fehlgeschlagen');
}
onUnauthorized(path);
throw authRedirectError();
}
if (!res.ok) throw new Error(await parseError(res, text));
return parseJsonBody(text);
}
export async function apiGet(path) {
return apiRequest('GET', path);
}
export async function apiPost(path, body) {
return apiRequest('POST', path, body);
}
export async function apiPut(path, body) {
return apiRequest('PUT', path, body);
}
export async function apiDelete(path) {
return apiRequest('DELETE', path);
}
/** multipart/form-data (kein Content-Type setzen — Boundary setzt der Browser) */
export async function apiPostForm(path, formData) {
const url = apiUrl(path);
const res = await fetch(url, {
method: 'POST',
credentials: 'include',
body: formData,
});
const text = await res.text();
if (res.status === 401) {
onUnauthorized(path);
throw authRedirectError();
}
if (!res.ok) throw new Error(await parseError(res, text));
return parseJsonBody(text);
}
/** Öffentlich: keine Session nötig, kein Redirect bei 401 */
export async function authFetchStatus() {
const res = await fetch('/auth/status', { credentials: 'include' });
const text = await res.text();
if (!res.ok) throw new Error(await parseError(res, text));
return parseJsonBody(text);
}