From e69bae37c621e77b7ac63e8bc09eef7ab639b95f Mon Sep 17 00:00:00 2001
From: Daniel Baumann <daniel.baumann@progress-linux.org>
Date: Sat, 9 Oct 2021 12:23:44 +0200
Subject: Merging upstream version 1.6.3+20210924.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
---
 html/src/components/terminal/index.tsx | 109 ++++++++++++++++++++-------------
 1 file changed, 65 insertions(+), 44 deletions(-)

(limited to 'html/src/components/terminal/index.tsx')

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}`);
-- 
cgit v1.2.3

