bowler-chart/public/js/api.js

51 lines
1.8 KiB
JavaScript

'use strict';
// Tiny fetch wrapper. Tracks the CSRF token (from GET /api/me or login) and
// attaches it to every state-changing request as X-CSRF-Token.
window.API = (function () {
let csrfToken = null;
async function request(method, url, body) {
const headers = {};
if (body !== undefined) headers['Content-Type'] = 'application/json';
if (method !== 'GET' && csrfToken) headers['X-CSRF-Token'] = csrfToken;
const res = await fetch(url, {
method,
headers,
credentials: 'same-origin',
body: body !== undefined ? JSON.stringify(body) : undefined,
});
let data = null;
const text = await res.text();
if (text) {
try { data = JSON.parse(text); } catch (_) { data = { raw: text }; }
}
if (!res.ok) {
// An expired/invalid session (401) on any call other than the login POST
// means we should send the user back to sign in, rather than leaving the
// page in a broken half-loaded state with only a console error.
const onLoginPage = window.location.pathname.endsWith('/login.html');
const isLoginCall = url.endsWith('/api/login');
if (res.status === 401 && !onLoginPage && !isLoginCall) {
window.location.href = '/login.html';
}
const err = new Error((data && data.error) || `Request failed (${res.status})`);
err.status = res.status;
throw err;
}
if (data && data.csrfToken) csrfToken = data.csrfToken;
return data;
}
return {
setCsrf: (t) => { csrfToken = t; },
get: (url) => request('GET', url),
post: (url, body) => request('POST', url, body || {}),
patch: (url, body) => request('PATCH', url, body || {}),
put: (url, body) => request('PUT', url, body || {}),
del: (url) => request('DELETE', url),
};
})();