fix: harden UI actions and daemon calls for reliable control (v1.3.2)

Retry and serialize pineapd/hak5 calls, queue virtual-pager keys, and grey out buttons until the pager finishes. Deploy now installs python3-light after factory firmware. Bump version to 1.3.2.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-20 15:49:10 -05:00
co-authored by Cursor
parent cd26553d21
commit 7d48b7ad06
25 changed files with 3625 additions and 782 deletions
@@ -3,32 +3,32 @@
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 WRITE_TIMEOUT_MS = 45000;
async function request(method, path, body, attempt) {
attempt = attempt || 0;
const opts = { method, headers: {}, credentials: 'include' };
if (body !== undefined) {
opts.headers['Content-Type'] = 'application/json';
opts.body = JSON.stringify(body);
}
const timeoutMs = method === 'GET' ? GET_TIMEOUT_MS : WRITE_TIMEOUT_MS;
const ctl = new AbortController();
const timer = method === 'GET' ? setTimeout(() => ctl.abort(), GET_TIMEOUT_MS) : null;
if (timer) opts.signal = ctl.signal;
const timer = setTimeout(() => ctl.abort(), timeoutMs);
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;
clearTimeout(timer);
if (method === 'GET' && attempt < 1) {
return request(method, path, body, attempt + 1);
}
throw e;
} finally {
if (timer) clearTimeout(timer);
const error = new Error((e && e.name === 'AbortError') ? 'Request timed out' : (e && e.message) || 'Network error');
error.status = 0;
throw error;
}
clearTimeout(timer);
if (res.status === 401) {
if (on401) on401();
throw new Error('unauthorized');