summaryrefslogtreecommitdiffstats
path: root/html/src/components/terminal/index.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'html/src/components/terminal/index.tsx')
-rw-r--r--html/src/components/terminal/index.tsx109
1 files changed, 65 insertions, 44 deletions
diff --git a/html/src/components/terminal/index.tsx b/html/src/components/terminal/index.tsx
index b344287..7368915 100644
--- a/html/src/components/terminal/index.tsx
+++ b/html/src/components/terminal/index.tsx
@@ -1,5 +1,4 @@
import { bind } from 'decko';
-import * as backoff from 'backoff';
import { Component, h } from 'preact';
import { ITerminalOptions, Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
@@ -58,18 +57,17 @@ export class Xterm extends Component<Props> {
private fitAddon: FitAddon;
private overlayAddon: OverlayAddon;
private zmodemAddon: ZmodemAddon;
+ private webglAddon: WebglAddon;
private socket: WebSocket;
private token: string;
+ private opened = false;
private title: string;
private titleFixed: string;
private resizeTimeout: number;
private resizeOverlay = true;
-
- private backoff: backoff.Backoff;
- private backoffLock = false;
- private doBackoff = true;
- private reconnect = false;
+ private reconnect = true;
+ private doReconnect = true;
constructor(props: Props) {
super(props);
@@ -78,22 +76,6 @@ export class Xterm extends Component<Props> {
this.textDecoder = new TextDecoder();
this.fitAddon = new FitAddon();
this.overlayAddon = new OverlayAddon();
- this.backoff = backoff.exponential({
- initialDelay: 100,
- maxDelay: 10000,
- });
- this.backoff.failAfter(15);
- this.backoff.on('ready', () => {
- this.backoffLock = false;
- this.refreshToken().then(this.connect);
- });
- this.backoff.on('backoff', (_, delay: number) => {
- console.log(`[ttyd] will attempt to reconnect websocket in ${delay}ms`);
- this.backoffLock = true;
- });
- this.backoff.on('fail', () => {
- this.backoffLock = true; // break backoff
- });
}
async componentDidMount() {
@@ -225,8 +207,7 @@ export class Xterm extends Component<Props> {
}
@bind
- private applyOptions(options: any) {
- const { terminal, fitAddon } = this;
+ private setRendererType(value: string) {
const isWebGL2Available = () => {
try {
const canvas = document.createElement('canvas');
@@ -236,14 +217,38 @@ export class Xterm extends Component<Props> {
}
};
+ const { terminal } = this;
+ switch (value) {
+ case 'webgl':
+ if (this.webglAddon) return;
+ if (isWebGL2Available()) {
+ this.webglAddon = new WebglAddon();
+ terminal.loadAddon(this.webglAddon);
+ console.log(`[ttyd] WebGL renderer enabled`);
+ }
+ break;
+ default:
+ try {
+ this.webglAddon?.dispose();
+ } catch {
+ // ignore
+ }
+ this.webglAddon = undefined;
+ console.log(`[ttyd] option: rendererType=${value}`);
+ terminal.setOption('rendererType', value);
+ break;
+ }
+ }
+
+ @bind
+ private applyOptions(options: any) {
+ const { terminal, fitAddon } = this;
+
Object.keys(options).forEach(key => {
const value = options[key];
switch (key) {
case 'rendererType':
- if (value === 'webgl' && isWebGL2Available()) {
- terminal.loadAddon(new WebglAddon());
- console.log(`[ttyd] WebGL renderer enabled`);
- }
+ this.setRendererType(value);
break;
case 'disableLeaveAlert':
if (value) {
@@ -260,7 +265,7 @@ export class Xterm extends Component<Props> {
case 'disableReconnect':
if (value) {
console.log(`[ttyd] Reconnect disabled`);
- this.doBackoff = false;
+ this.reconnect = false;
}
break;
case 'titleFixed':
@@ -281,23 +286,29 @@ export class Xterm extends Component<Props> {
@bind
private onSocketOpen() {
console.log('[ttyd] websocket connection opened');
- this.backoff.reset();
const { socket, textEncoder, terminal, fitAddon, overlayAddon } = this;
- socket.send(textEncoder.encode(JSON.stringify({ AuthToken: this.token })));
+ const dims = fitAddon.proposeDimensions();
+ socket.send(
+ textEncoder.encode(
+ JSON.stringify({
+ AuthToken: this.token,
+ columns: dims.cols,
+ rows: dims.rows,
+ })
+ )
+ );
- if (this.reconnect) {
- const dims = fitAddon.proposeDimensions();
+ if (this.opened) {
terminal.reset();
terminal.resize(dims.cols, dims.rows);
- this.onTerminalResize(dims); // may not be triggered by terminal.resize
overlayAddon.showOverlay('Reconnected', 300);
} else {
- this.reconnect = true;
+ this.opened = true;
fitAddon.fit();
}
- this.applyOptions(this.props.clientOptions);
+ this.doReconnect = this.reconnect;
terminal.focus();
}
@@ -306,22 +317,31 @@ export class Xterm extends Component<Props> {
private onSocketClose(event: CloseEvent) {
console.log(`[ttyd] websocket connection closed with code: ${event.code}`);
- const { backoff, doBackoff, backoffLock, overlayAddon } = this;
+ const { refreshToken, connect, doReconnect, overlayAddon } = this;
overlayAddon.showOverlay('Connection Closed', null);
// 1000: CLOSE_NORMAL
- if (event.code !== 1000 && doBackoff && !backoffLock) {
- backoff.backoff();
+ if (event.code !== 1000 && doReconnect) {
+ overlayAddon.showOverlay('Reconnecting...', null);
+ refreshToken().then(connect);
+ } else {
+ const { terminal } = this;
+ const keyDispose = terminal.onKey(e => {
+ const event = e.domEvent;
+ if (event.key === 'Enter') {
+ keyDispose.dispose();
+ overlayAddon.showOverlay('Reconnecting...', null);
+ refreshToken().then(connect);
+ }
+ });
+ overlayAddon.showOverlay('Press ⏎ to Reconnect', null);
}
}
@bind
private onSocketError(event: Event) {
console.error('[ttyd] websocket connection error: ', event);
- const { backoff, doBackoff, backoffLock } = this;
- if (doBackoff && !backoffLock) {
- backoff.backoff();
- }
+ this.doReconnect = false;
}
@bind
@@ -340,7 +360,8 @@ export class Xterm extends Component<Props> {
document.title = this.title;
break;
case Command.SET_PREFERENCES:
- this.applyOptions(JSON.parse(textDecoder.decode(data)));
+ const prefs = JSON.parse(textDecoder.decode(data));
+ this.applyOptions(Object.assign({}, this.props.clientOptions, prefs));
break;
default:
console.warn(`[ttyd] unknown command: ${cmd}`);