Files
Mark-VIII/payload/user/remote_access/pager-webui/www/js/api.js
T
bzuccaro 0ecd8c3135 hardening: 20s client timeout on API GETs
A hung read can no longer stall the recon/pineap poll loops (AbortController).
Writes keep no client abort: radio deploys legitimately take up to 45s
server-side. Cache-bumped api.js.
2026-08-19 00:13:03 -05:00

64 lines
2.0 KiB
JavaScript

'use strict';
const PagerAPI = (() => {
let apiBase = '';
let on401 = null;
// Reads are polled and must never hang a page's refresh loop; writes have
// server-side timeouts up to 45s (radio deploys) so they get no client
// abort.
const GET_TIMEOUT_MS = 20000;
async function request(method, path, body) {
const opts = { method, headers: {}, credentials: 'include' };
if (body !== undefined) {
opts.headers['Content-Type'] = 'application/json';
opts.body = JSON.stringify(body);
}
const ctl = new AbortController();
const timer = method === 'GET' ? setTimeout(() => ctl.abort(), GET_TIMEOUT_MS) : null;
if (timer) opts.signal = ctl.signal;
let res;
try {
res = await fetch(apiBase + path, opts);
} catch (e) {
if (timer && e && e.name === 'AbortError') {
const error = new Error('Request timed out');
error.status = 0;
throw error;
}
throw e;
} finally {
if (timer) clearTimeout(timer);
}
if (res.status === 401) {
if (on401) on401();
throw new Error('unauthorized');
}
const ct = res.headers.get('Content-Type') || '';
let data;
if (ct.indexOf('json') !== -1) {
try { data = await res.json(); }
catch (e) { data = null; }
} else {
data = await res.text();
}
if (!res.ok) {
const detail = data && typeof data.detail === 'string' ? data.detail : '';
const message = (data && data.error ? data.error : ('HTTP ' + res.status)) +
(detail ? ': ' + detail : '');
const error = new Error(message);
error.status = res.status;
error.data = data;
throw error;
}
return { status: res.status, data };
}
return {
setBase: (b) => { apiBase = b; },
on401: (fn) => { on401 = fn; },
get: (p) => request('GET', p),
post: (p, b) => request('POST', p, b === undefined ? {} : b),
del: (p, b) => request('DELETE', p, b === undefined ? {} : b),
login: async (username, password) => request('POST', '/api/login', { username, password })
};
})();