Some checks failed
		
		
	
	backend / cross (mips64el) (push) Has been cancelled
				
			backend / cross (mipsel) (push) Has been cancelled
				
			backend / cross (s390x) (push) Has been cancelled
				
			backend / cross (win32) (push) Has been cancelled
				
			backend / cross (x86_64) (push) Has been cancelled
				
			backend / cross (aarch64) (push) Has been cancelled
				
			backend / cross (arm) (push) Has been cancelled
				
			backend / cross (armhf) (push) Has been cancelled
				
			backend / cross (i686) (push) Has been cancelled
				
			backend / cross (mips) (push) Has been cancelled
				
			backend / cross (mips64) (push) Has been cancelled
				
			docker / build (push) Has been cancelled
				
			
		
			
				
	
	
		
			655 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			655 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { bind } from 'decko';
 | 
						|
import type { IDisposable, ITerminalOptions } from '@xterm/xterm';
 | 
						|
import { Terminal } from '@xterm/xterm';
 | 
						|
import { CanvasAddon } from '@xterm/addon-canvas';
 | 
						|
import { ClipboardAddon } from '@xterm/addon-clipboard';
 | 
						|
import { WebglAddon } from '@xterm/addon-webgl';
 | 
						|
import { FitAddon } from '@xterm/addon-fit';
 | 
						|
import { WebLinksAddon } from '@xterm/addon-web-links';
 | 
						|
import { ImageAddon } from '@xterm/addon-image';
 | 
						|
import { Unicode11Addon } from '@xterm/addon-unicode11';
 | 
						|
import { OverlayAddon } from './addons/overlay';
 | 
						|
import { ZmodemAddon } from './addons/zmodem';
 | 
						|
 | 
						|
import '@xterm/xterm/css/xterm.css';
 | 
						|
 | 
						|
interface TtydTerminal extends Terminal {
 | 
						|
    fit(): void;
 | 
						|
}
 | 
						|
 | 
						|
declare global {
 | 
						|
    interface Window {
 | 
						|
        term: TtydTerminal;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
enum Command {
 | 
						|
    // server side
 | 
						|
    OUTPUT = '0',
 | 
						|
    SET_WINDOW_TITLE = '1',
 | 
						|
    SET_PREFERENCES = '2',
 | 
						|
 | 
						|
    // client side
 | 
						|
    INPUT = '0',
 | 
						|
    RESIZE_TERMINAL = '1',
 | 
						|
    PAUSE = '2',
 | 
						|
    RESUME = '3',
 | 
						|
}
 | 
						|
type Preferences = ITerminalOptions & ClientOptions;
 | 
						|
 | 
						|
export type RendererType = 'dom' | 'canvas' | 'webgl';
 | 
						|
 | 
						|
export interface ClientOptions {
 | 
						|
    rendererType: RendererType;
 | 
						|
    disableLeaveAlert: boolean;
 | 
						|
    disableResizeOverlay: boolean;
 | 
						|
    enableZmodem: boolean;
 | 
						|
    enableTrzsz: boolean;
 | 
						|
    enableSixel: boolean;
 | 
						|
    titleFixed?: string;
 | 
						|
    isWindows: boolean;
 | 
						|
    trzszDragInitTimeout: number;
 | 
						|
    unicodeVersion: string;
 | 
						|
    closeOnDisconnect: boolean;
 | 
						|
}
 | 
						|
 | 
						|
export interface FlowControl {
 | 
						|
    limit: number;
 | 
						|
    highWater: number;
 | 
						|
    lowWater: number;
 | 
						|
}
 | 
						|
 | 
						|
export interface XtermOptions {
 | 
						|
    wsUrl: string;
 | 
						|
    tokenUrl: string;
 | 
						|
    flowControl: FlowControl;
 | 
						|
    clientOptions: ClientOptions;
 | 
						|
    termOptions: ITerminalOptions;
 | 
						|
}
 | 
						|
 | 
						|
function toDisposable(f: () => void): IDisposable {
 | 
						|
    return { dispose: f };
 | 
						|
}
 | 
						|
 | 
						|
function addEventListener(target: EventTarget, type: string, listener: EventListener): IDisposable {
 | 
						|
    target.addEventListener(type, listener);
 | 
						|
    return toDisposable(() => target.removeEventListener(type, listener));
 | 
						|
}
 | 
						|
 | 
						|
export class Xterm {
 | 
						|
    private disposables: IDisposable[] = [];
 | 
						|
    private textEncoder = new TextEncoder();
 | 
						|
    private textDecoder = new TextDecoder();
 | 
						|
    private written = 0;
 | 
						|
    private pending = 0;
 | 
						|
 | 
						|
    private terminal: Terminal;
 | 
						|
    private fitAddon = new FitAddon();
 | 
						|
    private overlayAddon = new OverlayAddon();
 | 
						|
    private clipboardAddon = new ClipboardAddon();
 | 
						|
    private webLinksAddon = new WebLinksAddon();
 | 
						|
    private webglAddon?: WebglAddon;
 | 
						|
    private canvasAddon?: CanvasAddon;
 | 
						|
    private zmodemAddon?: ZmodemAddon;
 | 
						|
 | 
						|
    private socket?: WebSocket;
 | 
						|
    private token: string;
 | 
						|
    private opened = false;
 | 
						|
    private title?: string;
 | 
						|
    private titleFixed?: string;
 | 
						|
    private resizeOverlay = true;
 | 
						|
    private reconnect = true;
 | 
						|
    private doReconnect = true;
 | 
						|
    private closeOnDisconnect = false;
 | 
						|
    private intervalID: NodeJS.Timeout;
 | 
						|
    private writeFunc = (data: ArrayBuffer) => this.writeData(new Uint8Array(data));
 | 
						|
    private status = false;
 | 
						|
    private titleStatus = false;
 | 
						|
    private checkStatus = false;
 | 
						|
    private connectStatus = false;
 | 
						|
    private beforeCommand?: string;
 | 
						|
    constructor(
 | 
						|
        private options: XtermOptions,
 | 
						|
        private sendCb: () => void
 | 
						|
    ) {}
 | 
						|
 | 
						|
    dispose() {
 | 
						|
        for (const d of this.disposables) {
 | 
						|
            d.dispose();
 | 
						|
        }
 | 
						|
        this.disposables.length = 0;
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    private register<T extends IDisposable>(d: T): T {
 | 
						|
        this.disposables.push(d);
 | 
						|
        return d;
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    public sendFile(files: FileList) {
 | 
						|
        this.zmodemAddon?.sendFile(files);
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    public async refreshToken() {
 | 
						|
        try {
 | 
						|
            const resp = await fetch(this.options.tokenUrl);
 | 
						|
            if (resp.ok) {
 | 
						|
                const json = await resp.json();
 | 
						|
                this.token = json.token;
 | 
						|
            }
 | 
						|
        } catch (e) {
 | 
						|
            console.error(`[ttyd] fetch ${this.options.tokenUrl}: `, e);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    private onWindowUnload(event: BeforeUnloadEvent) {
 | 
						|
        event.preventDefault();
 | 
						|
        if (this.socket?.readyState === WebSocket.OPEN) {
 | 
						|
            const message = 'Close terminal? this will also terminate the command.';
 | 
						|
            event.returnValue = message;
 | 
						|
            return message;
 | 
						|
        }
 | 
						|
        return undefined;
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    public open(parent: HTMLElement) {
 | 
						|
        this.terminal = new Terminal(this.options.termOptions);
 | 
						|
        const { terminal, fitAddon, overlayAddon, clipboardAddon, webLinksAddon } = this;
 | 
						|
        window.term = terminal as TtydTerminal;
 | 
						|
        window.term.fit = () => {
 | 
						|
            this.fitAddon.fit();
 | 
						|
        };
 | 
						|
 | 
						|
        terminal.loadAddon(fitAddon);
 | 
						|
        terminal.loadAddon(overlayAddon);
 | 
						|
        terminal.loadAddon(clipboardAddon);
 | 
						|
        terminal.loadAddon(webLinksAddon);
 | 
						|
 | 
						|
        terminal.open(parent);
 | 
						|
        fitAddon.fit();
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    private initListeners() {
 | 
						|
        const { terminal, fitAddon, overlayAddon, register, sendData } = this;
 | 
						|
        register(
 | 
						|
            terminal.onTitleChange(data => {
 | 
						|
                if (data && data !== '' && !this.titleFixed) {
 | 
						|
                    document.title = data + ' | ' + this.title;
 | 
						|
                }
 | 
						|
            })
 | 
						|
        );
 | 
						|
        register(
 | 
						|
            terminal.onData(data =>
 | 
						|
            {
 | 
						|
                if (this.status) {
 | 
						|
                    sendData(data);
 | 
						|
                } else {
 | 
						|
                    this.writeData('\b \b');
 | 
						|
                }
 | 
						|
            })
 | 
						|
        );
 | 
						|
        register(terminal.onBinary(data => sendData(Uint8Array.from(data, v => v.charCodeAt(0)))));
 | 
						|
        register(
 | 
						|
            terminal.onResize(({ cols, rows }) => {
 | 
						|
                const msg = JSON.stringify({ columns: cols, rows: rows });
 | 
						|
                this.socket?.send(this.textEncoder.encode(Command.RESIZE_TERMINAL + msg));
 | 
						|
                if (this.resizeOverlay) overlayAddon.showOverlay(`${cols}x${rows}`, 300);
 | 
						|
            })
 | 
						|
        );
 | 
						|
        register(
 | 
						|
            terminal.onSelectionChange(() => {
 | 
						|
                if (this.terminal.getSelection() === '') return;
 | 
						|
                try {
 | 
						|
                    document.execCommand('copy');
 | 
						|
                } catch (e) {
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
                this.overlayAddon?.showOverlay('\u2702', 200);
 | 
						|
            })
 | 
						|
        );
 | 
						|
        register(addEventListener(window, 'resize', () => fitAddon.fit()));
 | 
						|
        register(addEventListener(window, 'beforeunload', this.onWindowUnload));
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    public writeData(data: string | Uint8Array) {
 | 
						|
        const { terminal, textEncoder } = this;
 | 
						|
        const { limit, highWater, lowWater } = this.options.flowControl;
 | 
						|
 | 
						|
        this.written += data.length;
 | 
						|
        if (this.written > limit) {
 | 
						|
            terminal.write(data, () => {
 | 
						|
                this.pending = Math.max(this.pending - 1, 0);
 | 
						|
                if (this.pending < lowWater) {
 | 
						|
                    this.socket?.send(textEncoder.encode(Command.RESUME));
 | 
						|
                }
 | 
						|
            });
 | 
						|
            this.pending++;
 | 
						|
            this.written = 0;
 | 
						|
            if (this.pending > highWater) {
 | 
						|
                this.socket?.send(textEncoder.encode(Command.PAUSE));
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            terminal.write(data);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    public sendData(data: string | Uint8Array) {
 | 
						|
        const { socket, textEncoder } = this;
 | 
						|
        if (socket?.readyState !== WebSocket.OPEN) return;
 | 
						|
 | 
						|
        if (typeof data === 'string') {
 | 
						|
            const payload = new Uint8Array(data.length * 3 + 1);
 | 
						|
            payload[0] = Command.INPUT.charCodeAt(0);
 | 
						|
            const stats = textEncoder.encodeInto(data, payload.subarray(1));
 | 
						|
            socket.send(payload.subarray(0, (stats.written as number) + 1));
 | 
						|
        } else {
 | 
						|
            const payload = new Uint8Array(data.length + 1);
 | 
						|
            payload[0] = Command.INPUT.charCodeAt(0);
 | 
						|
            payload.set(data, 1);
 | 
						|
            socket.send(payload);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    public changeUrl(ip: string, port: string) {
 | 
						|
        const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
 | 
						|
        this.options.wsUrl = [protocol, '//' + ip + ':' + port +'/ws', window.location.search].join('');
 | 
						|
        this.options.tokenUrl = [window.location.protocol, '//' + ip + ':' + port +'/token'].join('');
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    public changeStatus(v: boolean){
 | 
						|
        this.status = v;
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    public connect() {
 | 
						|
        this.socket = new WebSocket(this.options.wsUrl, ['tty']);
 | 
						|
        const { socket, register } = this;
 | 
						|
 | 
						|
        socket.binaryType = 'arraybuffer';
 | 
						|
        register(addEventListener(socket, 'open', this.onSocketOpen));
 | 
						|
        register(addEventListener(socket, 'message', this.onSocketData as EventListener));
 | 
						|
        register(addEventListener(socket, 'close', this.onSocketClose as EventListener));
 | 
						|
        register(addEventListener(socket, 'error', () => (this.doReconnect = false)));
 | 
						|
        const options = new URLSearchParams(decodeURIComponent(window.location.search));
 | 
						|
        if (options.get('type') === 'docker') {
 | 
						|
            this.intervalID = setInterval(this.loadCommand, 3000);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    private onSocketOpen() {
 | 
						|
        console.log('[ttyd] websocket connection opened');
 | 
						|
 | 
						|
        const { textEncoder, terminal, overlayAddon } = this;
 | 
						|
        const msg = JSON.stringify({ AuthToken: this.token, columns: terminal.cols, rows: terminal.rows });
 | 
						|
        this.socket?.send(textEncoder.encode(msg));
 | 
						|
 | 
						|
        if (this.opened) {
 | 
						|
            terminal.reset();
 | 
						|
            terminal.options.disableStdin = false;
 | 
						|
            overlayAddon.showOverlay('Reconnected', 300);
 | 
						|
        } else {
 | 
						|
            this.opened = true;
 | 
						|
        }
 | 
						|
 | 
						|
        this.doReconnect = this.reconnect;
 | 
						|
        this.initListeners();
 | 
						|
        terminal.focus();
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    private onSocketClose(event: CloseEvent) {
 | 
						|
        console.log(`[ttyd] websocket connection closed with code: ${event.code}`);
 | 
						|
 | 
						|
        const { refreshToken, connect, doReconnect, overlayAddon } = this;
 | 
						|
        overlayAddon.showOverlay('Connection Closed');
 | 
						|
        this.dispose();
 | 
						|
 | 
						|
        // 1000: CLOSE_NORMAL
 | 
						|
        if (event.code !== 1000 && doReconnect) {
 | 
						|
            overlayAddon.showOverlay('Reconnecting...');
 | 
						|
            refreshToken().then(connect);
 | 
						|
        } else if (this.closeOnDisconnect) {
 | 
						|
            window.close();
 | 
						|
        } else {
 | 
						|
            const { terminal } = this;
 | 
						|
            const keyDispose = terminal.onKey(e => {
 | 
						|
                const event = e.domEvent;
 | 
						|
                if (event.key === 'Enter') {
 | 
						|
                    keyDispose.dispose();
 | 
						|
                    overlayAddon.showOverlay('Reconnecting...');
 | 
						|
                    refreshToken().then(connect);
 | 
						|
                }
 | 
						|
            });
 | 
						|
            overlayAddon.showOverlay('Press ⏎ to Reconnect');
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    private loadCommand() {
 | 
						|
        const options = new URLSearchParams(decodeURIComponent(window.location.search));
 | 
						|
        const params = new URLSearchParams({
 | 
						|
            repo: options.get('repoid') as string,
 | 
						|
            user: options.get('userid') as string,
 | 
						|
        });
 | 
						|
 | 
						|
        fetch(
 | 
						|
            'http://' + options.get('domain') + ':'+ options.get('port') +'/' +
 | 
						|
                options.get('user') +
 | 
						|
                '/' +
 | 
						|
                options.get('repo') +
 | 
						|
                '/devcontainer/command?' +
 | 
						|
                params
 | 
						|
        )
 | 
						|
            .then(response => response.json())
 | 
						|
            .then(data => {
 | 
						|
                if (data.status !== '4' && data.status !== '0') {
 | 
						|
                    this.sendData(data.command);
 | 
						|
                } else {
 | 
						|
                    clearInterval(this.intervalID);
 | 
						|
                    if (data.status === '4') {
 | 
						|
                        fetch(
 | 
						|
                            'http://' + options.get('domain') + ':'+ options.get('port') +'/' +
 | 
						|
                                options.get('user') +
 | 
						|
                                '/' +
 | 
						|
                                options.get('repo') +
 | 
						|
                                '/devcontainer/command?' +
 | 
						|
                                params
 | 
						|
                        )
 | 
						|
                            .then(response => response.json())
 | 
						|
                            .then(data => {
 | 
						|
                                this.sendData(data.command);
 | 
						|
                            })
 | 
						|
                            .catch(error => {
 | 
						|
                                console.error('Error:', error);
 | 
						|
                            });
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            })
 | 
						|
            .catch(error => {
 | 
						|
                console.error('Error:', error);
 | 
						|
            });
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    private parseOptsFromUrlQuery(query: string): Preferences {
 | 
						|
        const { terminal } = this;
 | 
						|
        const { clientOptions } = this.options;
 | 
						|
        const prefs = {} as Preferences;
 | 
						|
        const queryObj = Array.from(new URLSearchParams(query) as unknown as Iterable<[string, string]>);
 | 
						|
 | 
						|
        for (const [k, queryVal] of queryObj) {
 | 
						|
            let v = clientOptions[k];
 | 
						|
            if (v === undefined) v = terminal.options[k];
 | 
						|
            switch (typeof v) {
 | 
						|
                case 'boolean':
 | 
						|
                    prefs[k] = queryVal === 'true' || queryVal === '1';
 | 
						|
                    break;
 | 
						|
                case 'number':
 | 
						|
                case 'bigint':
 | 
						|
                    prefs[k] = Number.parseInt(queryVal, 10);
 | 
						|
                    break;
 | 
						|
                case 'string':
 | 
						|
                    prefs[k] = queryVal;
 | 
						|
                    break;
 | 
						|
                case 'object':
 | 
						|
                    prefs[k] = JSON.parse(queryVal);
 | 
						|
                    break;
 | 
						|
                default:
 | 
						|
                    console.warn(`[ttyd] maybe unknown option: ${k}=${queryVal}, treating as string`);
 | 
						|
                    prefs[k] = queryVal;
 | 
						|
                    break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return prefs;
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    private onSocketData(event: MessageEvent) {
 | 
						|
        const { textDecoder } = this;
 | 
						|
        const rawData = event.data as ArrayBuffer;
 | 
						|
        const cmd = String.fromCharCode(new Uint8Array(rawData)[0]);
 | 
						|
        const data = rawData.slice(1);
 | 
						|
        switch (cmd) {
 | 
						|
            case Command.OUTPUT:
 | 
						|
                console.log(this.status + ': ' + textDecoder.decode(data) + this.connectStatus + ' ' + this.checkStatus + ' ' + this.titleStatus );
 | 
						|
                const options = new URLSearchParams(decodeURIComponent(window.location.search));
 | 
						|
                if (options.get('type') === 'docker') {
 | 
						|
                    if (
 | 
						|
                        this.status === false &&
 | 
						|
                        textDecoder.decode(data).replace(/\s/g, '').includes('Successfully connected to the container'.replace(/\s/g, ''))
 | 
						|
                    ) {
 | 
						|
                        if(this.connectStatus == true){
 | 
						|
                            this.status = true;
 | 
						|
                        }
 | 
						|
                        this.connectStatus = true;
 | 
						|
                    }
 | 
						|
                    if (this.checkStatus) {
 | 
						|
                        if (textDecoder.decode(data).replace(/\s/g, '').includes('You have out the container. Please refresh the terminal to reconnect.'.replace(/\s/g, ''))) {
 | 
						|
                            this.checkStatus = false;
 | 
						|
                            this.status = false;
 | 
						|
                            this.connectStatus = false;
 | 
						|
                        }
 | 
						|
                        if(textDecoder.decode(data).includes('\x1b')){
 | 
						|
                            this.checkStatus = false;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    if (this.titleStatus && textDecoder.decode(data).includes('\x1b')) {
 | 
						|
                        this.titleStatus = false;
 | 
						|
                        this.checkStatus = true;
 | 
						|
                        this.sendData(
 | 
						|
                            `echo $WEB_TERMINAL\n`
 | 
						|
                        );
 | 
						|
                    }
 | 
						|
                    if ( 
 | 
						|
                        !(this.status === false && (textDecoder.decode(data).includes('\x1b') || textDecoder.decode(data).replace(/\s/g, '').includes('docker'))) &&
 | 
						|
                        this.titleStatus !== true &&
 | 
						|
                        this.checkStatus !== true
 | 
						|
                    ){
 | 
						|
                        this.writeFunc(data);
 | 
						|
                    }
 | 
						|
                    if (textDecoder.decode(data).replace(/\s/g, '').includes('exit')) {
 | 
						|
                        this.titleStatus = true;
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                } else {
 | 
						|
                    this.writeFunc(data);
 | 
						|
                }
 | 
						|
                console.log(this.status + ': ' + textDecoder.decode(data) + this.connectStatus + ' ' + this.checkStatus + ' ' + this.titleStatus );
 | 
						|
                break;
 | 
						|
            case Command.SET_WINDOW_TITLE:
 | 
						|
                console.log('SET_WINDOW_TITLESET_WINDOW_TITLE');
 | 
						|
                this.title = textDecoder.decode(data);
 | 
						|
                document.title = this.title;
 | 
						|
                break;
 | 
						|
            case Command.SET_PREFERENCES:
 | 
						|
                console.log('SET_PREFERENCESSET_PREFERENCESSET_PREFERENCES');
 | 
						|
                this.applyPreferences({
 | 
						|
                    ...this.options.clientOptions,
 | 
						|
                    ...JSON.parse(textDecoder.decode(data)),
 | 
						|
                    ...this.parseOptsFromUrlQuery(window.location.search),
 | 
						|
                } as Preferences);
 | 
						|
                break;
 | 
						|
            default:
 | 
						|
                console.warn(`[ttyd] unknown command: ${cmd}`);
 | 
						|
                break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    private applyPreferences(prefs: Preferences) {
 | 
						|
        const { terminal, fitAddon, register } = this;
 | 
						|
        if (prefs.enableZmodem || prefs.enableTrzsz) {
 | 
						|
            this.zmodemAddon = new ZmodemAddon({
 | 
						|
                zmodem: prefs.enableZmodem,
 | 
						|
                trzsz: prefs.enableTrzsz,
 | 
						|
                windows: prefs.isWindows,
 | 
						|
                trzszDragInitTimeout: prefs.trzszDragInitTimeout,
 | 
						|
                onSend: this.sendCb,
 | 
						|
                sender: this.sendData,
 | 
						|
                writer: this.writeData,
 | 
						|
            });
 | 
						|
            this.writeFunc = data => this.zmodemAddon?.consume(data);
 | 
						|
            terminal.loadAddon(register(this.zmodemAddon));
 | 
						|
        }
 | 
						|
 | 
						|
        for (const [key, value] of Object.entries(prefs)) {
 | 
						|
            switch (key) {
 | 
						|
                case 'rendererType':
 | 
						|
                    this.setRendererType(value);
 | 
						|
                    break;
 | 
						|
                case 'disableLeaveAlert':
 | 
						|
                    if (value) {
 | 
						|
                        window.removeEventListener('beforeunload', this.onWindowUnload);
 | 
						|
                        console.log('[ttyd] Leave site alert disabled');
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
                case 'disableResizeOverlay':
 | 
						|
                    if (value) {
 | 
						|
                        console.log('[ttyd] Resize overlay disabled');
 | 
						|
                        this.resizeOverlay = false;
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
                case 'disableReconnect':
 | 
						|
                    if (value) {
 | 
						|
                        console.log('[ttyd] Reconnect disabled');
 | 
						|
                        this.reconnect = false;
 | 
						|
                        this.doReconnect = false;
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
                case 'enableZmodem':
 | 
						|
                    if (value) console.log('[ttyd] Zmodem enabled');
 | 
						|
                    break;
 | 
						|
                case 'enableTrzsz':
 | 
						|
                    if (value) console.log('[ttyd] trzsz enabled');
 | 
						|
                    break;
 | 
						|
                case 'trzszDragInitTimeout':
 | 
						|
                    if (value) console.log(`[ttyd] trzsz drag init timeout: ${value}`);
 | 
						|
                    break;
 | 
						|
                case 'enableSixel':
 | 
						|
                    if (value) {
 | 
						|
                        terminal.loadAddon(register(new ImageAddon()));
 | 
						|
                        console.log('[ttyd] Sixel enabled');
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
                case 'closeOnDisconnect':
 | 
						|
                    if (value) {
 | 
						|
                        console.log('[ttyd] close on disconnect enabled (Reconnect disabled)');
 | 
						|
                        this.closeOnDisconnect = true;
 | 
						|
                        this.reconnect = false;
 | 
						|
                        this.doReconnect = false;
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
                case 'titleFixed':
 | 
						|
                    if (!value || value === '') return;
 | 
						|
                    console.log(`[ttyd] setting fixed title: ${value}`);
 | 
						|
                    this.titleFixed = value;
 | 
						|
                    document.title = value;
 | 
						|
                    break;
 | 
						|
                case 'isWindows':
 | 
						|
                    if (value) console.log('[ttyd] is windows');
 | 
						|
                    break;
 | 
						|
                case 'unicodeVersion':
 | 
						|
                    switch (value) {
 | 
						|
                        case 6:
 | 
						|
                        case '6':
 | 
						|
                            console.log('[ttyd] setting Unicode version: 6');
 | 
						|
                            break;
 | 
						|
                        case 11:
 | 
						|
                        case '11':
 | 
						|
                        default:
 | 
						|
                            console.log('[ttyd] setting Unicode version: 11');
 | 
						|
                            terminal.loadAddon(new Unicode11Addon());
 | 
						|
                            terminal.unicode.activeVersion = '11';
 | 
						|
                            break;
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
                default:
 | 
						|
                    console.log(`[ttyd] option: ${key}=${JSON.stringify(value)}`);
 | 
						|
                    if (terminal.options[key] instanceof Object) {
 | 
						|
                        terminal.options[key] = Object.assign({}, terminal.options[key], value);
 | 
						|
                    } else {
 | 
						|
                        terminal.options[key] = value;
 | 
						|
                    }
 | 
						|
                    if (key.indexOf('font') === 0) fitAddon.fit();
 | 
						|
                    break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    @bind
 | 
						|
    private setRendererType(value: RendererType) {
 | 
						|
        const { terminal } = this;
 | 
						|
        const disposeCanvasRenderer = () => {
 | 
						|
            try {
 | 
						|
                this.canvasAddon?.dispose();
 | 
						|
            } catch {
 | 
						|
                // ignore
 | 
						|
            }
 | 
						|
            this.canvasAddon = undefined;
 | 
						|
        };
 | 
						|
        const disposeWebglRenderer = () => {
 | 
						|
            try {
 | 
						|
                this.webglAddon?.dispose();
 | 
						|
            } catch {
 | 
						|
                // ignore
 | 
						|
            }
 | 
						|
            this.webglAddon = undefined;
 | 
						|
        };
 | 
						|
        const enableCanvasRenderer = () => {
 | 
						|
            if (this.canvasAddon) return;
 | 
						|
            this.canvasAddon = new CanvasAddon();
 | 
						|
            disposeWebglRenderer();
 | 
						|
            try {
 | 
						|
                this.terminal.loadAddon(this.canvasAddon);
 | 
						|
                console.log('[ttyd] canvas renderer loaded');
 | 
						|
            } catch (e) {
 | 
						|
                console.log('[ttyd] canvas renderer could not be loaded, falling back to dom renderer', e);
 | 
						|
                disposeCanvasRenderer();
 | 
						|
            }
 | 
						|
        };
 | 
						|
        const enableWebglRenderer = () => {
 | 
						|
            if (this.webglAddon) return;
 | 
						|
            this.webglAddon = new WebglAddon();
 | 
						|
            disposeCanvasRenderer();
 | 
						|
            try {
 | 
						|
                this.webglAddon.onContextLoss(() => {
 | 
						|
                    this.webglAddon?.dispose();
 | 
						|
                });
 | 
						|
                terminal.loadAddon(this.webglAddon);
 | 
						|
                console.log('[ttyd] WebGL renderer loaded');
 | 
						|
            } catch (e) {
 | 
						|
                console.log('[ttyd] WebGL renderer could not be loaded, falling back to canvas renderer', e);
 | 
						|
                disposeWebglRenderer();
 | 
						|
                enableCanvasRenderer();
 | 
						|
            }
 | 
						|
        };
 | 
						|
 | 
						|
        switch (value) {
 | 
						|
            case 'canvas':
 | 
						|
                enableCanvasRenderer();
 | 
						|
                break;
 | 
						|
            case 'webgl':
 | 
						|
                enableWebglRenderer();
 | 
						|
                break;
 | 
						|
            case 'dom':
 | 
						|
                disposeWebglRenderer();
 | 
						|
                disposeCanvasRenderer();
 | 
						|
                console.log('[ttyd] dom renderer loaded');
 | 
						|
                break;
 | 
						|
            default:
 | 
						|
                break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |