/* * 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 * ``, 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()) }