agilentics / boiler
// 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),
  };
})();