2026-01-16 22:53:04 +01:00

87 lines
3.1 KiB
JavaScript

// public/assets/api.js
/* English comments: centralized API wrapper */
(function () {
function normalizeDateString(value) {
if (typeof value !== 'string') return value;
const s = value.trim();
if (s === '') return value;
const isoDate = /^\d{4}-\d{2}-\d{2}$/;
const isoDateTime = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}/;
const dotDate = /^\d{2}\.\d{2}\.\d{4}$/;
const dotDateTime = /^\d{2}\.\d{2}\.\d{4}\s+\d{2}:\d{2}:\d{2}$/;
let v = s;
if (isoDate.test(v)) v = `${v} 00:00:00`;
if (dotDate.test(v)) {
const parts = v.split('.');
v = `${parts[2]}-${parts[1]}-${parts[0]} 00:00:00`;
} else if (dotDateTime.test(v)) {
const [datePart, timePart] = v.split(/\s+/, 2);
const parts = datePart.split('.');
v = `${parts[2]}-${parts[1]}-${parts[0]} ${timePart}`;
}
if (isoDateTime.test(v)) {
const d = new Date(v.replace(' ', 'T') + 'Z');
if (!Number.isNaN(d.getTime())) {
return d.toISOString().slice(0, 19).replace('T', ' ');
}
}
return v;
}
function normalizeDates(payload) {
if (payload === null || payload === undefined) return payload;
if (Array.isArray(payload)) return payload.map(normalizeDates);
if (typeof payload !== 'object') return payload;
const out = {};
Object.keys(payload).forEach((key) => {
const value = payload[key];
if (value && typeof value === 'object') {
out[key] = normalizeDates(value);
} else if (typeof value === 'string' && (key === 'ts' || key.endsWith('_at') || key.endsWith('_ts'))) {
out[key] = normalizeDateString(value);
} else {
out[key] = value;
}
});
return out;
}
async function request(path, opts = {}) {
const headers = { ...(opts.headers || {}) };
const hasBodyObject = opts.body && typeof opts.body === 'object' && !(opts.body instanceof FormData);
const body = hasBodyObject ? JSON.stringify(normalizeDates(opts.body)) : opts.body;
if (hasBodyObject && !headers['Content-Type']) {
headers['Content-Type'] = 'application/json';
}
if (window.Auth?.isAccessExpired?.() && window.Auth?.refreshTokens) {
await window.Auth.refreshTokens();
}
const token = window.Auth?.getAccessToken?.();
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
let res = await fetch(path, { credentials: 'same-origin', ...opts, headers, body });
if (res.status === 401 && window.Auth?.refreshTokens) {
const refreshed = await window.Auth.refreshTokens();
if (refreshed) {
const newToken = window.Auth.getAccessToken();
if (newToken) headers['Authorization'] = `Bearer ${newToken}`;
res = await fetch(path, { credentials: 'same-origin', ...opts, headers, body });
} else if (window.Auth?.redirectToLogin) {
window.Auth.redirectToLogin();
}
}
const json = await res.json().catch(() => null);
if (!json || json.ok !== true) {
const msg = json?.error?.message || `Request failed: ${res.status}`;
throw new Error(msg);
}
return json;
}
window.Api = {
request,
};
})();