Reconnect websockets automatically

This commit is contained in:
R. Richard 2020-05-21 17:44:01 +02:00
parent d8545c48be
commit ea5bf30a95
3 changed files with 61 additions and 15 deletions

View File

@ -5,7 +5,8 @@
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json", "start": "ng serve --proxy-config proxy.conf.json",
"build": "ng build --prod --aot --build-optimizer" "build": "ng build --prod --aot --build-optimizer",
"lint": "ng lint --fix"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {

View File

@ -8,11 +8,16 @@ export class TerminadoService {
xterm: Terminal; xterm: Terminal;
ws: WebSocket; ws: WebSocket;
attached = false; attached = false;
private dataListener: any;
private resizeListener: any;
constructor() { constructor() {
Terminal.applyAddon(fit); Terminal.applyAddon(fit);
Terminal.applyAddon(terminado); this.xterm = this.createTerminal();
this.xterm = new Terminal({ }
createTerminal() {
return new Terminal({
theme: { theme: {
foreground: '#ffffff', foreground: '#ffffff',
background: '#0C0C0C', // $tao-gray-800 background: '#0C0C0C', // $tao-gray-800
@ -37,26 +42,52 @@ export class TerminadoService {
}, },
fontSize: 14 fontSize: 14
}); });
const wsproto = (location.protocol === 'https:') ? 'wss://' : 'ws://';
this.ws = new WebSocket(wsproto + window.location.host + '/terminal');
} }
attach(element: HTMLElement) { 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.ws.onopen = () => {
(<any>this.xterm).terminadoAttach(this.ws); this.attached = true;
this.xterm = this.createTerminal();
this.xterm.open(element); this.xterm.open(element);
this.fit(); this.fit();
this.xterm.blur(); // In order to reset the terminal state after a broken socket, we need to register the listeners manually.
this.attached = true; (<any>this.xterm)._core.register(this.dataListener = this.xterm.onData(data => {
this.ws.send(JSON.stringify(['stdin', data]));
}));
(<any>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() { detach() {
(<any>this.xterm).terminadoDetach(this.ws); if (!this.attached) {
this.xterm.destroy(); return;
this.ws.close(); }
this.attached = false; this.attached = false;
this.dataListener.dispose();
this.resizeListener.dispose();
} }
fit() { fit() {

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable, Subject, Subscription } from 'rxjs';
import { webSocket, WebSocketSubject } from 'rxjs/webSocket'; import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
import { filter, map } from 'rxjs/operators'; import { filter, map } from 'rxjs/operators';
import { WebSocketMessage } from '../message-types/websocket-message'; import { WebSocketMessage } from '../message-types/websocket-message';
@ -17,14 +17,28 @@ export enum Intent {
@Injectable() @Injectable()
export class WebSocketService { export class WebSocketService {
private ws: WebSocketSubject<WebSocketMessage>; private ws: WebSocketSubject<WebSocketMessage>;
private subject: Subject<WebSocketMessage> = new Subject<WebSocketMessage>();
private subscription: Subscription;
constructor() {} constructor() {}
public connect() { public connect() {
if (!this.ws) { if (!this.ws) {
if (this.subscription) {
this.subscription.unsubscribe();
}
const wsproto = (location.protocol === 'https:') ? 'wss://' : 'ws://'; const wsproto = (location.protocol === 'https:') ? 'wss://' : 'ws://';
const connAddr = wsproto + window.location.host + '/ws'; const connAddr = wsproto + window.location.host + '/ws';
this.ws = webSocket<WebSocketMessage>(connAddr); this.ws = webSocket<WebSocketMessage>({
url: connAddr,
closeObserver: {
next: closeEvent => {
this.ws = null;
this.connect();
}
}
});
this.subscription = this.ws.subscribe(msg => this.subject.next(msg));
} }
} }
@ -35,7 +49,7 @@ export class WebSocketService {
} }
public observeAll<T extends WebSocketMessage>(key: string): Observable<T> { public observeAll<T extends WebSocketMessage>(key: string): Observable<T> {
return this.ws.pipe( return this.subject.pipe(
filter(message => message.key.startsWith(key)), filter(message => message.key.startsWith(key)),
map(message => <T> message) map(message => <T> message)
); );