import { Injectable } from '@angular/core'; import { Terminal } from 'xterm'; import * as fit from 'xterm/lib/addons/fit/fit'; import * as terminado from 'xterm/lib/addons/terminado/terminado'; @Injectable() export class TerminadoService { xterm: Terminal; ws: WebSocket; attached = false; private dataListener: any; private resizeListener: any; constructor() { Terminal.applyAddon(fit); this.xterm = this.createTerminal(); } createTerminal() { return new Terminal({ theme: { foreground: '#ffffff', background: '#0C0C0C', // $tao-gray-800 cursor: '#ffffff', selection: 'rgba(255, 255, 255, 0.3)', black: '#000000', red: '#FF5252', // $tao-red-500 brightRed: '#FF7171', // $tao-red-400 green: '#2fd19f', // $tao-bright-green-500 brightGreen: '#2fd19f', // $tao-bright-green-500 brightYellow: '#FFD283', // $tao-warm-yellow-300 yellow: '#FFB83B', // $tao-warm-yellow-500 magenta: '#FF8FC6', // $tao-pink-200 brightMagenta: '#FF8FC6', // $tao-pink-200 cyan: '#277EEC', // $tao-blue-500 blue: '#277EEC', // $tao-blue-500 brightCyan: '#42B7DF', // $tao-sky-400 brightBlue: '#19A7D8', // $tao-sky-500 white: '#FAFAFA', // $tao-gray-50 brightBlack: '#808080', brightWhite: '#ffffff' }, fontSize: 14 }); } attach(element: HTMLElement) { if (this.attached) { return; } const wsproto = (location.protocol === 'https:') ? 'wss://' : 'ws://'; this.ws = new WebSocket(wsproto + window.location.host + '/terminal'); this.ws.onopen = () => { this.attached = true; this.xterm = this.createTerminal(); this.xterm.open(element); this.fit(); // In order to reset the terminal state after a broken socket, we need to register the listeners manually. (this.xterm)._core.register(this.dataListener = this.xterm.onData(data => { this.ws.send(JSON.stringify(['stdin', data])); })); (this.xterm)._core.register(this.resizeListener = this.xterm.onResize((size: { rows: number, cols: number }) => { this.ws.send(JSON.stringify(['set_size', size.rows, size.cols])); })); }; this.ws.onclose = () => { this.detach(); this.xterm.destroy(); this.attach(element); }; this.ws.onmessage = msg => { const data = JSON.parse(msg.data); if (data[0] === 'stdout') { this.xterm.write(data[1]); } }; } detach() { if (!this.attached) { return; } this.attached = false; this.dataListener.dispose(); this.resizeListener.dispose(); } fit() { if (this.attached) { (this.xterm).fit(); } } }