51 lines
1.8 KiB
JavaScript
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),
|
|
};
|
|
})();
|