OOB-Dashboard/web/auth.js

84 lines
2.3 KiB
JavaScript

'use strict';
// Shared by the auth pages and by app.js. Plain script, no modules -- same
// style as app.js, and it has to work when loaded before it.
const A = {
qs(key) {
return new URLSearchParams(location.search).get(key);
},
// Redirect a 401 to the sign-in page instead of letting the caller render an
// empty, broken screen. Everything else is handed back for normal handling.
guard(res) {
if (res.status === 401) {
const here = location.pathname + location.search;
location.replace('/login?next=' + encodeURIComponent(here));
throw new Error('not signed in');
}
return res;
},
async api(url, opts) {
return A.guard(await fetch(url, opts));
},
async postJSON(url, body) {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
let json = {};
try { json = await res.json(); } catch (_) { /* empty or non-JSON body */ }
if (!res.ok) {
const err = new Error(json.error || 'That did not work. Try again.');
err.status = res.status;
err.code = json.code;
throw err;
}
return json;
},
msg(el, text, kind) {
if (!el) return;
el.textContent = text || '';
el.className = 'msg' + (kind ? ' ' + kind : '');
el.hidden = !text;
},
// Only ever bounce to a path on this site: '//evil.example' is a protocol
// relative URL and would leave it.
safeNext(raw) {
if (!raw || !raw.startsWith('/') || raw.startsWith('//')) return '/';
return raw;
},
busy(button, on, labelWhenBusy) {
if (!button) return;
if (on) {
button.dataset.label = button.textContent;
button.textContent = labelWhenBusy || 'Working…';
} else if (button.dataset.label) {
button.textContent = button.dataset.label;
}
button.disabled = on;
},
// Every auth page shares one submit shape: disable, call, show the outcome.
wire(form, button, note, handler) {
form.addEventListener('submit', async (ev) => {
ev.preventDefault();
A.msg(note, '');
A.busy(button, true);
try {
await handler();
} catch (err) {
A.msg(note, String(err.message || err), 'err');
} finally {
A.busy(button, false);
}
});
},
};