// Minimal fetch helpers shared by every page. `api.post/del` attach the CSRF
// token from the meta tag, so a state-changing request from our own page is
// recognised as such (see app/endpoints/crud.check_csrf). Use these rather than
// calling fetch() directly, or the request is rejected with "Stale page".
const api = (() => {
const csrf = document.querySelector('meta[name="csrf-token"]')?.content || "";
async function request(method, url, body) {
const opts = { method, headers: {} };
if (body !== undefined) {
opts.headers["Content-Type"] = "application/json";
opts.body = JSON.stringify(body);
}
if (method !== "GET") opts.headers["X-CSRF-Token"] = csrf;
const res = await fetch(url, opts);
const text = await res.text();
const data = text ? JSON.parse(text) : {};
if (!res.ok) {
// Carry the status and the parsed body on the Error, so a caller that
// needs more than the message can reach it without re-reading a consumed
// response.
const err = new Error(data.error || `Request failed (${res.status}).`);
err.status = res.status;
err.data = data;
throw err;
}
return data;
}
return {
get: (url) => request("GET", url),
post: (url, body) => request("POST", url, body ?? {}),
put: (url, body) => request("PUT", url, body ?? {}),
del: (url) => request("DELETE", url),
};
})();