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>
105 lines
2.8 KiB
JavaScript
105 lines
2.8 KiB
JavaScript
'use strict';
|
|
|
|
const Term = (() => {
|
|
let term = null;
|
|
let fitAddon = null;
|
|
let ws = null;
|
|
let panel = null;
|
|
let wantOpen = false;
|
|
let retryTimer = null;
|
|
let retryMs = 400;
|
|
|
|
function ensure() {
|
|
if (term) return;
|
|
panel = document.getElementById('terminal-panel');
|
|
term = new Terminal({ cursorBlink: true, scrollback: 2000, cols: 80, rows: 24 });
|
|
fitAddon = new FitAddon.FitAddon();
|
|
term.loadAddon(fitAddon);
|
|
term.open(document.getElementById('terminal'));
|
|
try { fitAddon.fit(); } catch (e) {}
|
|
term.onData((d) => {
|
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
try { ws.send(d); } catch (e) {}
|
|
}
|
|
});
|
|
}
|
|
|
|
function scheduleReconnect() {
|
|
if (!wantOpen) return;
|
|
clearTimeout(retryTimer);
|
|
retryTimer = setTimeout(() => {
|
|
if (wantOpen) connect();
|
|
}, retryMs);
|
|
retryMs = Math.min(5000, Math.max(400, retryMs * 2));
|
|
}
|
|
|
|
function toggle() {
|
|
ensure();
|
|
if (panel.classList.contains('hidden')) {
|
|
panel.classList.remove('hidden');
|
|
document.getElementById('terminal-btn').classList.add('active');
|
|
try { fitAddon.fit(); } catch (e) {}
|
|
wantOpen = true;
|
|
retryMs = 400;
|
|
connect();
|
|
} else {
|
|
panel.classList.add('hidden');
|
|
document.getElementById('terminal-btn').classList.remove('active');
|
|
disconnect();
|
|
}
|
|
}
|
|
|
|
function connect() {
|
|
if (!wantOpen) return;
|
|
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return;
|
|
if (term) term.reset();
|
|
let sock;
|
|
try {
|
|
sock = new WebSocket(App.terminalWs);
|
|
} catch (e) {
|
|
term.writeln('\r\n[cannot reach daemon terminal: ' + e.message + ']');
|
|
scheduleReconnect();
|
|
return;
|
|
}
|
|
ws = sock;
|
|
sock.onopen = () => { retryMs = 400; };
|
|
sock.onmessage = (ev) => {
|
|
if (typeof ev.data === 'string') term.write(ev.data);
|
|
else ev.data.text().then((t) => term.write(t));
|
|
};
|
|
sock.onclose = () => {
|
|
if (ws === sock) ws = null;
|
|
if (term && wantOpen) {
|
|
term.writeln('\r\n[connection closed — reconnecting]');
|
|
scheduleReconnect();
|
|
} else if (term) {
|
|
term.writeln('\r\n[connection closed]');
|
|
}
|
|
};
|
|
sock.onerror = () => { try { sock.close(); } catch (e) {} };
|
|
}
|
|
|
|
function disconnect() {
|
|
wantOpen = false;
|
|
clearTimeout(retryTimer);
|
|
retryTimer = null;
|
|
if (ws) { try { ws.close(); } catch (e) {} ws = null; }
|
|
}
|
|
|
|
function isOpen() {
|
|
return !!panel && !panel.classList.contains('hidden');
|
|
}
|
|
|
|
function close() {
|
|
if (isOpen()) toggle();
|
|
}
|
|
|
|
window.addEventListener('resize', () => {
|
|
if (fitAddon && panel && !panel.classList.contains('hidden')) {
|
|
try { fitAddon.fit(); } catch (e) {}
|
|
}
|
|
});
|
|
|
|
return { toggle, close, isOpen };
|
|
})();
|