'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 }; })();