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.
This commit is contained in:
@@ -262,7 +262,7 @@
|
|||||||
|
|
||||||
<script src="js/config.js"></script>
|
<script src="js/config.js"></script>
|
||||||
<script src="js/icons.js?v=20260818-7"></script>
|
<script src="js/icons.js?v=20260818-7"></script>
|
||||||
<script src="js/api.js?v=20260817-4"></script>
|
<script src="js/api.js?v=20260819-2"></script>
|
||||||
<script src="js/chart.js?v=20260819-1"></script>
|
<script src="js/chart.js?v=20260819-1"></script>
|
||||||
<script src="js/xterm.min.js"></script>
|
<script src="js/xterm.min.js"></script>
|
||||||
<script src="js/xterm-addon-fit.min.js"></script>
|
<script src="js/xterm-addon-fit.min.js"></script>
|
||||||
|
|||||||
@@ -3,13 +3,32 @@
|
|||||||
const PagerAPI = (() => {
|
const PagerAPI = (() => {
|
||||||
let apiBase = '';
|
let apiBase = '';
|
||||||
let on401 = null;
|
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) {
|
async function request(method, path, body) {
|
||||||
const opts = { method, headers: {}, credentials: 'include' };
|
const opts = { method, headers: {}, credentials: 'include' };
|
||||||
if (body !== undefined) {
|
if (body !== undefined) {
|
||||||
opts.headers['Content-Type'] = 'application/json';
|
opts.headers['Content-Type'] = 'application/json';
|
||||||
opts.body = JSON.stringify(body);
|
opts.body = JSON.stringify(body);
|
||||||
}
|
}
|
||||||
const res = await fetch(apiBase + path, opts);
|
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 (res.status === 401) {
|
||||||
if (on401) on401();
|
if (on401) on401();
|
||||||
throw new Error('unauthorized');
|
throw new Error('unauthorized');
|
||||||
|
|||||||
Reference in New Issue
Block a user