mirror of
https://github.com/tsl0922/ttyd.git
synced 2025-12-23 20:24:20 +01:00
html: migrate to typescript
This commit is contained in:
225
html/js/app.ts
Normal file
225
html/js/app.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import '../sass/app.scss';
|
||||
|
||||
// polyfills for ie11
|
||||
import 'core-js/fn/array';
|
||||
import 'core-js/fn/object';
|
||||
import 'core-js/fn/promise';
|
||||
import 'core-js/fn/typed';
|
||||
import 'fast-text-encoding';
|
||||
|
||||
import { Terminal, ITerminalOptions, IDisposable } from 'xterm';
|
||||
import * as fit from 'xterm/lib/addons/fit/fit'
|
||||
import * as overlay from './overlay'
|
||||
import { Modal } from './zmodem'
|
||||
import * as Zmodem from 'zmodem.js/src/zmodem_browser';
|
||||
import * as urljoin from 'url-join';
|
||||
|
||||
Terminal.applyAddon(fit);
|
||||
Terminal.applyAddon(overlay);
|
||||
|
||||
interface ITtydTerminal extends Terminal {
|
||||
resizeDisposable: IDisposable;
|
||||
dataDisposable: IDisposable;
|
||||
reconnectTimeout: number;
|
||||
showOverlay(msg: string, timeout?: number);
|
||||
}
|
||||
|
||||
export interface IWindowWithTerminal extends Window {
|
||||
term: ITtydTerminal;
|
||||
resizeTimeout?: number;
|
||||
tty_auth_token?: string;
|
||||
}
|
||||
declare let window: IWindowWithTerminal;
|
||||
|
||||
const modal = new Modal();
|
||||
const terminalContainer = document.getElementById('terminal-container');
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss://': 'ws://';
|
||||
const url = urljoin(protocol, window.location.host, window.location.pathname, 'ws', window.location.search);
|
||||
const textDecoder = new TextDecoder();
|
||||
const textEncoder = new TextEncoder();
|
||||
|
||||
let authToken = (typeof window.tty_auth_token !== 'undefined') ? window.tty_auth_token : null;
|
||||
let autoReconnect = -1;
|
||||
let term: ITtydTerminal;
|
||||
let title: string;
|
||||
let wsError: boolean;
|
||||
|
||||
let openWs = function() {
|
||||
let ws = new WebSocket(url, ['tty']);
|
||||
let sendMessage = function (message) {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(textEncoder.encode(message));
|
||||
}
|
||||
};
|
||||
let unloadCallback = function (event) {
|
||||
let message = 'Close terminal? this will also terminate the command.';
|
||||
event.returnValue = message;
|
||||
return message;
|
||||
};
|
||||
let resetTerm = function() {
|
||||
modal.hide();
|
||||
clearTimeout(term.reconnectTimeout);
|
||||
if (ws.readyState !== WebSocket.CLOSED) {
|
||||
ws.close();
|
||||
}
|
||||
openWs();
|
||||
};
|
||||
|
||||
let zsentry = new Zmodem.Sentry({
|
||||
to_terminal: function _to_terminal(octets) {
|
||||
let buffer = new Uint8Array(octets).buffer;
|
||||
term.write(textDecoder.decode(buffer));
|
||||
},
|
||||
|
||||
sender: function _ws_sender_func(octets) {
|
||||
// limit max packet size to 4096
|
||||
while (octets.length) {
|
||||
let chunk = octets.splice(0, 4095);
|
||||
let buffer = new Uint8Array(chunk.length + 1);
|
||||
buffer[0]= '0'.charCodeAt(0);
|
||||
buffer.set(chunk, 1);
|
||||
ws.send(buffer);
|
||||
}
|
||||
},
|
||||
|
||||
on_retract: function _on_retract() {
|
||||
// console.log('on_retract');
|
||||
},
|
||||
|
||||
on_detect: function _on_detect(detection) {
|
||||
term.setOption('disableStdin', true);
|
||||
let zsession = detection.confirm();
|
||||
let promise = zsession.type === 'send' ? modal.handleSend(zsession) : modal.handleReceive(zsession);
|
||||
promise.catch(console.error.bind(console)).then(() => {
|
||||
modal.hide();
|
||||
term.setOption('disableStdin', false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ws.binaryType = 'arraybuffer';
|
||||
|
||||
ws.onopen = function() {
|
||||
console.log('[ttyd] websocket opened');
|
||||
wsError = false;
|
||||
sendMessage(JSON.stringify({AuthToken: authToken}));
|
||||
|
||||
if (typeof term !== 'undefined') {
|
||||
term.dispose();
|
||||
}
|
||||
|
||||
// expose term handle for some programatic cases
|
||||
// which need to get the content of the terminal
|
||||
term = window.term = <ITtydTerminal>new Terminal({
|
||||
fontSize: 13,
|
||||
fontFamily: '"Menlo for Powerline", Menlo, Consolas, "Liberation Mono", Courier, monospace',
|
||||
theme: {
|
||||
foreground: '#d2d2d2',
|
||||
background: '#2b2b2b',
|
||||
cursor: '#adadad',
|
||||
black: '#000000',
|
||||
red: '#d81e00',
|
||||
green: '#5ea702',
|
||||
yellow: '#cfae00',
|
||||
blue: '#427ab3',
|
||||
magenta: '#89658e',
|
||||
cyan: '#00a7aa',
|
||||
white: '#dbded8',
|
||||
brightBlack: '#686a66',
|
||||
brightRed: '#f54235',
|
||||
brightGreen: '#99e343',
|
||||
brightYellow: '#fdeb61',
|
||||
brightBlue: '#84b0d8',
|
||||
brightMagenta: '#bc94b7',
|
||||
brightCyan: '#37e6e8',
|
||||
brightWhite: '#f1f1f0'
|
||||
}
|
||||
} as ITerminalOptions);
|
||||
|
||||
term.resizeDisposable = term.onResize((size: {cols: number, rows: number}) => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
sendMessage('1' + JSON.stringify({columns: size.cols, rows: size.rows}));
|
||||
}
|
||||
setTimeout(() => (<any>term).showOverlay(size.cols + 'x' + size.rows), 500);
|
||||
});
|
||||
|
||||
term.onTitleChange((data: string) => {
|
||||
if (data && data !== '') {
|
||||
document.title = (data + ' | ' + title);
|
||||
}
|
||||
});
|
||||
|
||||
term.dataDisposable = term.onData((data: string) => sendMessage('0' + data));
|
||||
|
||||
while (terminalContainer.firstChild) {
|
||||
terminalContainer.removeChild(terminalContainer.firstChild);
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/27923937/1727928
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(window.resizeTimeout);
|
||||
window.resizeTimeout = <number><any>setTimeout(() => (<any>term).fit(), 250);
|
||||
});
|
||||
window.addEventListener('beforeunload', unloadCallback);
|
||||
|
||||
term.open(terminalContainer);
|
||||
(<any>term).fit();
|
||||
term.focus();
|
||||
};
|
||||
|
||||
ws.onmessage = function(event: MessageEvent) {
|
||||
let rawData = new Uint8Array(event.data),
|
||||
cmd = String.fromCharCode(rawData[0]),
|
||||
data = rawData.slice(1).buffer;
|
||||
switch(cmd) {
|
||||
case '0':
|
||||
try {
|
||||
zsentry.consume(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
resetTerm();
|
||||
}
|
||||
break;
|
||||
case '1':
|
||||
title = textDecoder.decode(data);
|
||||
document.title = title;
|
||||
break;
|
||||
case '2':
|
||||
let preferences = JSON.parse(textDecoder.decode(data));
|
||||
Object.keys(preferences).forEach((key) => {
|
||||
console.log('[ttyd] xterm option: ' + key + '=' + preferences[key]);
|
||||
term.setOption(key, preferences[key]);
|
||||
});
|
||||
break;
|
||||
case '3':
|
||||
autoReconnect = JSON.parse(textDecoder.decode(data));
|
||||
console.log('[ttyd] reconnect: ' + autoReconnect + ' seconds');
|
||||
break;
|
||||
default:
|
||||
console.log('[ttyd] unknown command: ' + cmd);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = function(event: CloseEvent) {
|
||||
console.log('[ttyd] websocket closed, code: ' + event.code);
|
||||
if (term) {
|
||||
term.resizeDisposable.dispose();
|
||||
term.dataDisposable.dispose();
|
||||
if (!wsError) {
|
||||
(<any>term).showOverlay('Connection Closed', null);
|
||||
}
|
||||
}
|
||||
window.removeEventListener('beforeunload', unloadCallback);
|
||||
// 1000: CLOSE_NORMAL
|
||||
if (event.code !== 1000 && autoReconnect > 0) {
|
||||
term.reconnectTimeout = <number><any>setTimeout(openWs, autoReconnect * 1000);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if (document.readyState === 'complete' || document.readyState !== 'loading') {
|
||||
openWs();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', openWs);
|
||||
}
|
||||
Reference in New Issue
Block a user