html: reuse xterm instance on reconnect

This commit is contained in:
Shuanglei Tao
2020-03-07 15:09:26 +08:00
parent 5f25521209
commit 339889eadc
3 changed files with 2077 additions and 2050 deletions

View File

@@ -53,6 +53,7 @@ export class Xterm extends Component<Props> {
private resizeTimeout: number; private resizeTimeout: number;
private backoff: backoff.Backoff; private backoff: backoff.Backoff;
private backoffLock = false; private backoffLock = false;
private reconnect = false;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
@@ -65,19 +66,27 @@ export class Xterm extends Component<Props> {
initialDelay: 100, initialDelay: 100,
maxDelay: 10000, maxDelay: 10000,
}); });
this.backoff.failAfter(15);
this.backoff.on('ready', () => { this.backoff.on('ready', () => {
this.backoffLock = false; this.backoffLock = false;
this.refreshToken().then(this.openTerminal); this.refreshToken().then(this.connect);
}); });
this.backoff.on('backoff', (_, delay: number) => { this.backoff.on('backoff', (_, delay: number) => {
console.log(`[ttyd] will attempt to reconnect websocket in ${delay}ms`); console.log(`[ttyd] will attempt to reconnect websocket in ${delay}ms`);
this.backoffLock = true; this.backoffLock = true;
}); });
this.backoff.on('fail', () => {
this.backoffLock = true; // break backoff
});
} }
async componentDidMount() { async componentDidMount() {
await this.refreshToken(); await this.refreshToken();
this.openTerminal(); this.openTerminal();
this.connect();
window.addEventListener('resize', this.onWindowResize);
window.addEventListener('beforeunload', this.onWindowUnload);
} }
componentWillUnmount() { componentWillUnmount() {
@@ -125,32 +134,26 @@ export class Xterm extends Component<Props> {
this.resizeTimeout = setTimeout(() => fitAddon.fit(), 250) as any; this.resizeTimeout = setTimeout(() => fitAddon.fit(), 250) as any;
} }
private onWindowUnload(event: BeforeUnloadEvent): string { @bind
const message = 'Close terminal? this will also terminate the command.'; private onWindowUnload(event: BeforeUnloadEvent): any {
event.returnValue = message; const { socket } = this;
return message; if (socket && socket.readyState === WebSocket.OPEN) {
const message = 'Close terminal? this will also terminate the command.';
event.returnValue = message;
return message;
}
event.preventDefault();
} }
@bind @bind
private openTerminal() { private openTerminal() {
if (this.terminal) {
this.terminal.dispose();
}
this.socket = new WebSocket(this.props.wsUrl, ['tty']);
this.terminal = new Terminal(this.props.options); this.terminal = new Terminal(this.props.options);
const { socket, terminal, container, fitAddon, overlayAddon } = this; const { terminal, container, fitAddon, overlayAddon } = this;
window.term = terminal as TtydTerminal; window.term = terminal as TtydTerminal;
window.term.fit = () => { window.term.fit = () => {
this.fitAddon.fit(); this.fitAddon.fit();
}; };
socket.binaryType = 'arraybuffer';
socket.onopen = this.onSocketOpen;
socket.onmessage = this.onSocketData;
socket.onclose = this.onSocketClose;
socket.onerror = this.onSocketError;
terminal.loadAddon(fitAddon); terminal.loadAddon(fitAddon);
terminal.loadAddon(overlayAddon); terminal.loadAddon(overlayAddon);
terminal.loadAddon(new WebLinksAddon()); terminal.loadAddon(new WebLinksAddon());
@@ -171,46 +174,61 @@ export class Xterm extends Component<Props> {
}); });
} }
terminal.open(container); terminal.open(container);
terminal.focus();
window.addEventListener('resize', this.onWindowResize);
window.addEventListener('beforeunload', this.onWindowUnload);
} }
@bind @bind
private reconnect() { private connect() {
if (!this.backoffLock) { this.socket = new WebSocket(this.props.wsUrl, ['tty']);
this.backoff.backoff(); const { socket } = this;
}
socket.binaryType = 'arraybuffer';
socket.onopen = this.onSocketOpen;
socket.onmessage = this.onSocketData;
socket.onclose = this.onSocketClose;
socket.onerror = this.onSocketError;
} }
@bind @bind
private onSocketOpen() { private onSocketOpen() {
console.log('[ttyd] Websocket connection opened'); console.log('[ttyd] websocket connection opened');
this.backoff.reset(); this.backoff.reset();
const { socket, textEncoder, fitAddon } = this; const { socket, textEncoder, terminal, fitAddon } = this;
socket.send(textEncoder.encode(JSON.stringify({ AuthToken: this.token }))); socket.send(textEncoder.encode(JSON.stringify({ AuthToken: this.token })));
fitAddon.fit();
if (this.reconnect) {
const dims = fitAddon.proposeDimensions();
terminal.reset();
terminal.resize(dims.cols, dims.rows);
this.onTerminalResize(dims); // may not be triggered by terminal.resize
} else {
this.reconnect = true;
fitAddon.fit();
}
terminal.focus();
} }
@bind @bind
private onSocketClose(event: CloseEvent) { private onSocketClose(event: CloseEvent) {
console.log(`[ttyd] websocket connection closed with code: ${event.code}`); console.log(`[ttyd] websocket connection closed with code: ${event.code}`);
const { overlayAddon } = this; const { backoff, backoffLock, overlayAddon } = this;
overlayAddon.showOverlay('Connection Closed', null); overlayAddon.showOverlay('Connection Closed', null);
window.removeEventListener('beforeunload', this.onWindowUnload);
// 1000: CLOSE_NORMAL // 1000: CLOSE_NORMAL
if (event.code !== 1000) { if (event.code !== 1000 && !backoffLock) {
this.reconnect(); backoff.backoff();
} }
} }
@bind @bind
private onSocketError() { private onSocketError(event: Event) {
this.reconnect(); console.error('[ttyd] websocket connection error: ', event);
const { backoff, backoffLock } = this;
if (!backoffLock) {
backoff.backoff();
}
} }
@bind @bind

View File

@@ -63,7 +63,6 @@ const baseConfig = {
performance : { performance : {
hints : false hints : false
}, },
devtool: 'source-map',
}; };
const devConfig = { const devConfig = {
@@ -77,7 +76,8 @@ const devConfig = {
target: 'http://localhost:7681', target: 'http://localhost:7681',
ws: true ws: true
}] }]
} },
devtool: 'inline-source-map',
}; };
const prodConfig = { const prodConfig = {
@@ -96,7 +96,8 @@ const prodConfig = {
} }
}), }),
] ]
} },
devtool: 'source-map',
}; };

4034
src/html.h

File diff suppressed because it is too large Load Diff