73 lines
2.9 KiB
JavaScript
73 lines
2.9 KiB
JavaScript
/*
|
|
* Das Terminal im eigenen Fenster.
|
|
*
|
|
* Eigener Einstiegspunkt, nicht Teil von app.js: nicht weil diese Seite ohne
|
|
* Livewire liefe — sie ist eine Vollseiten-Livewire-Komponente und lädt
|
|
* `<x-shell.head>`, das app.js über bare.blade.php ohnehin mitzieht —,
|
|
* sondern damit der Terminalcode (xterm.js) nicht in app.js landet und die
|
|
* übrigen Konsolenseiten nicht mitschleppen, und umgekehrt app.js nicht in
|
|
* dieses schlanke Fenster.
|
|
*/
|
|
import { Terminal } from '@xterm/xterm'
|
|
import { FitAddon } from '@xterm/addon-fit'
|
|
import '@xterm/xterm/css/xterm.css'
|
|
|
|
const root = document.querySelector('[data-terminal]')
|
|
if (root) {
|
|
const splash = root.querySelector('[data-terminal-splash]')
|
|
const screen = root.querySelector('[data-terminal-screen]')
|
|
// Der Name allein — nie die Tunneladresse oder der Schlüssel, die sieht
|
|
// dieses Fenster ohnehin nie. Nur in den Meldungen unten, damit klar ist,
|
|
// WELCHER Host gemeint ist, falls je mehr als ein Terminalfenster offen steht.
|
|
const host = root.dataset.host
|
|
|
|
const term = new Terminal({ convertEol: true, fontFamily: 'ui-monospace, monospace', fontSize: 13 })
|
|
const fit = new FitAddon()
|
|
term.loadAddon(fit)
|
|
|
|
const scheme = location.protocol === 'https:' ? 'wss' : 'ws'
|
|
const socket = new WebSocket(`${scheme}://${location.host}/terminal/ws?t=${encodeURIComponent(root.dataset.ticket)}`)
|
|
socket.binaryType = 'arraybuffer'
|
|
|
|
// Der Vorspann weicht beim ERSTEN BYTE, nicht bei `onopen`.
|
|
let opened = false
|
|
const reveal = () => {
|
|
if (opened) return
|
|
opened = true
|
|
splash.classList.add('hidden')
|
|
screen.classList.remove('hidden')
|
|
term.open(screen)
|
|
fit.fit()
|
|
}
|
|
|
|
// Ob je ein Byte ankam — getrennt von `opened`, denn auch ein Verbindungsfehler
|
|
// lässt den Vorspann weichen, um die Fehlermeldung zu zeigen, ohne dass je eine
|
|
// Sitzung entstand. Ohne dieses Merkmal feuert ein fehlgeschlagener Socket
|
|
// `error` UND danach `close`, und `onclose` behauptete eine beendete Sitzung,
|
|
// die nie begonnen hat.
|
|
let received = false
|
|
|
|
socket.onmessage = (event) => {
|
|
received = true
|
|
reveal()
|
|
// Kommt vom Container ein Textrahmen statt eines Binärrahmens, ist
|
|
// `new Uint8Array(event.data)` bei einem String ein leeres Array —
|
|
// `binaryType = 'arraybuffer'` oben regelt nur Binärrahmen.
|
|
term.write(typeof event.data === 'string' ? event.data : new Uint8Array(event.data))
|
|
}
|
|
|
|
term.onData((data) => socket.readyState === WebSocket.OPEN && socket.send(data))
|
|
|
|
socket.onclose = () => {
|
|
if (!received) return
|
|
term.write(`\r\n\x1b[31m— Verbindung zu ${host} beendet —\x1b[0m\r\n`)
|
|
}
|
|
|
|
socket.onerror = () => {
|
|
reveal()
|
|
term.write(`\r\n\x1b[31m— Verbindung zu ${host} nicht möglich —\x1b[0m\r\n`)
|
|
}
|
|
|
|
addEventListener('resize', () => opened && fit.fit())
|
|
}
|