Inital Commit
This commit is contained in:
114
public/js/api.js
Normal file
114
public/js/api.js
Normal file
@@ -0,0 +1,114 @@
|
||||
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() {
|
||||
if (
|
||||
!location.hash.startsWith('#/login') &&
|
||||
!location.hash.startsWith('#/bootstrap')
|
||||
) {
|
||||
location.hash = '#/login';
|
||||
}
|
||||
}
|
||||
|
||||
/** Geschützte REST-API liegt unter /api (Root-URLs bleiben für die SPA frei). */
|
||||
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);
|
||||
}
|
||||
|
||||
/** Ö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);
|
||||
}
|
||||
1502
public/js/app.js
Normal file
1502
public/js/app.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user