feat: the browser terminal on a WebSocket too, long polls when it does not connect or drops
This commit is contained in:
1 parent
73bb720ec6
commit
47f1b2e55b
4 files changed
+127
-12
No files matched your search
@@ -76,13 +76,47 @@
|
||||
this.sid = (await r.json()).session_id;
|
||||
this.setState('wait', 'ожидание агента…');
|
||||
this.fit();
|
||||
// A WebSocket if it connects; the long polls otherwise, and from a
|
||||
// dropped WebSocket on for the rest of this session.
|
||||
if (!(await this.openWS())) this.poll();
|
||||
this.queueIn({ t: 'r', cols: this.term.cols, rows: this.term.rows });
|
||||
this.poll();
|
||||
} catch (e) {
|
||||
this.setState('err', 'ошибка сети');
|
||||
this.term.write('\r\n\x1b[31m[ошибка сети: ' + (e && e.message ? e.message : e) + ']\x1b[0m\r\n');
|
||||
}
|
||||
}
|
||||
openWS() {
|
||||
return new Promise(resolve => {
|
||||
if (!window.WebSocket) { resolve(false); return; }
|
||||
const ws = new WebSocket((location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + base + this.sid + '/ws');
|
||||
let opened = false;
|
||||
const timer = setTimeout(() => { if (!opened) { ws.close(); resolve(false); } }, 5000);
|
||||
ws.onopen = () => { opened = true; clearTimeout(timer); this.ws = ws; resolve(true); };
|
||||
ws.onmessage = ev => {
|
||||
let j;
|
||||
try { j = JSON.parse(ev.data); } catch (e) { return; }
|
||||
this.take(j);
|
||||
};
|
||||
ws.onclose = () => {
|
||||
clearTimeout(timer);
|
||||
if (!opened) { resolve(false); return; }
|
||||
this.ws = null;
|
||||
if (!this.closed && !this.ended) this.poll();
|
||||
};
|
||||
});
|
||||
}
|
||||
// take applies one answer of the output channel, either transport's.
|
||||
take(j) {
|
||||
if (j.closed) { this.finish('[сессия завершена]'); return false; }
|
||||
for (const f of j.frames || []) {
|
||||
if (f.exit) { this.finish('[сессия завершена]'); return false; }
|
||||
if (f.data) {
|
||||
if (!this.connected) { this.connected = true; this.setState('ok', 'подключено'); }
|
||||
this.term.write(b64ToBytes(f.data));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
async poll() {
|
||||
while (!this.closed && this.sid) {
|
||||
let r;
|
||||
@@ -90,15 +124,7 @@
|
||||
catch (e) { await new Promise(res => setTimeout(res, 1000)); continue; }
|
||||
if (this.closed) return;
|
||||
if (!r.ok) { this.finish('[соединение закрыто]'); return; }
|
||||
const j = await r.json();
|
||||
if (j.closed) { this.finish('[сессия завершена]'); return; }
|
||||
for (const f of j.frames || []) {
|
||||
if (f.exit) { this.finish('[сессия завершена]'); return; }
|
||||
if (f.data) {
|
||||
if (!this.connected) { this.connected = true; this.setState('ok', 'подключено'); }
|
||||
this.term.write(b64ToBytes(f.data));
|
||||
}
|
||||
}
|
||||
if (!this.take(await r.json())) return;
|
||||
}
|
||||
}
|
||||
finish(msg) {
|
||||
@@ -126,6 +152,11 @@
|
||||
const body = m.t === 'i'
|
||||
? { t: 'i', d: bytesToB64(new TextEncoder().encode(m.text)), seq: ++this.inSeq }
|
||||
: { t: 'r', cols: m.cols, rows: m.rows, seq: ++this.inSeq };
|
||||
// On the WebSocket a message goes out in order with no answer to wait
|
||||
// for; numbered as well, so a repeat by the long polls is dropped.
|
||||
if (this.ws && this.ws.readyState === 1) {
|
||||
try { this.ws.send(JSON.stringify(body)); continue; } catch (e) { /* by request below */ }
|
||||
}
|
||||
for (let wait = 300; !this.closed; wait = Math.min(wait * 2, 5000)) {
|
||||
let r = null;
|
||||
try {
|
||||
@@ -145,6 +176,7 @@
|
||||
close() {
|
||||
if (this.closed) return;
|
||||
this.closed = true;
|
||||
if (this.ws) { try { this.ws.close(); } catch (e) { /* closed already */ } }
|
||||
if (this.sid) fetch(base + this.sid + '/close', { method: 'POST', keepalive: true }).catch(() => {});
|
||||
this.term.dispose();
|
||||
this.el.remove();
|
||||
|
||||
Reference in new issue
Block a user