2017-05-21 23:16:24 +00:00
|
|
|
export const protocols = ["webtty"];
|
|
|
|
|
|
|
|
export const msgInputUnknown = '0';
|
|
|
|
export const msgInput = '1';
|
|
|
|
export const msgPing = '2';
|
|
|
|
export const msgResizeTerminal = '3';
|
|
|
|
|
|
|
|
export const msgUnknownOutput = '0';
|
|
|
|
export const msgOutput = '1';
|
|
|
|
export const msgPong = '2';
|
|
|
|
export const msgSetWindowTitle = '3';
|
|
|
|
export const msgSetPreferences = '4';
|
|
|
|
export const msgSetReconnect = '5';
|
2021-04-13 02:39:32 +00:00
|
|
|
export const msgSetBufferSize = '6';
|
2017-05-21 23:16:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
export interface Terminal {
|
|
|
|
info(): { columns: number, rows: number };
|
|
|
|
output(data: string): void;
|
|
|
|
showMessage(message: string, timeout: number): void;
|
|
|
|
removeMessage(): void;
|
|
|
|
setWindowTitle(title: string): void;
|
|
|
|
setPreferences(value: object): void;
|
|
|
|
onInput(callback: (input: string) => void): void;
|
|
|
|
onResize(callback: (colmuns: number, rows: number) => void): void;
|
|
|
|
reset(): void;
|
|
|
|
deactivate(): void;
|
|
|
|
close(): void;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Connection {
|
|
|
|
open(): void;
|
|
|
|
close(): void;
|
|
|
|
send(data: string): void;
|
|
|
|
isOpen(): boolean;
|
|
|
|
onOpen(callback: () => void): void;
|
|
|
|
onReceive(callback: (data: string) => void): void;
|
|
|
|
onClose(callback: () => void): void;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ConnectionFactory {
|
|
|
|
create(): Connection;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export class WebTTY {
|
|
|
|
term: Terminal;
|
|
|
|
connectionFactory: ConnectionFactory;
|
|
|
|
args: string;
|
|
|
|
authToken: string;
|
|
|
|
reconnect: number;
|
2021-04-13 02:39:32 +00:00
|
|
|
bufSize: number;
|
2017-05-21 23:16:24 +00:00
|
|
|
|
|
|
|
constructor(term: Terminal, connectionFactory: ConnectionFactory, args: string, authToken: string) {
|
|
|
|
this.term = term;
|
|
|
|
this.connectionFactory = connectionFactory;
|
|
|
|
this.args = args;
|
|
|
|
this.authToken = authToken;
|
|
|
|
this.reconnect = -1;
|
2021-04-13 02:39:32 +00:00
|
|
|
this.bufSize = 1024;
|
2017-05-21 23:16:24 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
open() {
|
|
|
|
let connection = this.connectionFactory.create();
|
2021-04-16 13:49:17 +00:00
|
|
|
let pingTimer: NodeJS.Timeout;
|
|
|
|
let reconnectTimeout: NodeJS.Timeout;
|
2017-05-21 23:16:24 +00:00
|
|
|
|
|
|
|
const setup = () => {
|
|
|
|
connection.onOpen(() => {
|
|
|
|
const termInfo = this.term.info();
|
|
|
|
|
|
|
|
connection.send(JSON.stringify(
|
|
|
|
{
|
|
|
|
Arguments: this.args,
|
|
|
|
AuthToken: this.authToken,
|
|
|
|
}
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
const resizeHandler = (colmuns: number, rows: number) => {
|
|
|
|
connection.send(
|
|
|
|
msgResizeTerminal + JSON.stringify(
|
|
|
|
{
|
|
|
|
columns: colmuns,
|
|
|
|
rows: rows
|
|
|
|
}
|
|
|
|
)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
this.term.onResize(resizeHandler);
|
|
|
|
resizeHandler(termInfo.columns, termInfo.rows);
|
|
|
|
|
|
|
|
this.term.onInput(
|
|
|
|
(input: string) => {
|
2021-04-13 02:39:32 +00:00
|
|
|
// Leave room for message type id
|
|
|
|
let effectiveBufferSize = this.bufSize - 1;
|
|
|
|
|
|
|
|
// Split input into buffer sized chunks
|
|
|
|
for (let i = 0; i < Math.ceil(input.length/effectiveBufferSize); i++) {
|
|
|
|
let inputChunk = input.substring(i*effectiveBufferSize, Math.min((i+1)*effectiveBufferSize, input.length))
|
|
|
|
connection.send(msgInput + inputChunk);
|
|
|
|
}
|
2017-05-21 23:16:24 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
pingTimer = setInterval(() => {
|
|
|
|
connection.send(msgPing)
|
|
|
|
}, 30 * 1000);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
connection.onReceive((data) => {
|
|
|
|
const payload = data.slice(1);
|
|
|
|
switch (data[0]) {
|
|
|
|
case msgOutput:
|
2017-08-26 07:53:17 +00:00
|
|
|
this.term.output(atob(payload));
|
2017-05-21 23:16:24 +00:00
|
|
|
break;
|
|
|
|
case msgPong:
|
|
|
|
break;
|
|
|
|
case msgSetWindowTitle:
|
|
|
|
this.term.setWindowTitle(payload);
|
|
|
|
break;
|
|
|
|
case msgSetPreferences:
|
|
|
|
const preferences = JSON.parse(payload);
|
|
|
|
this.term.setPreferences(preferences);
|
|
|
|
break;
|
|
|
|
case msgSetReconnect:
|
|
|
|
const autoReconnect = JSON.parse(payload);
|
|
|
|
console.log("Enabling reconnect: " + autoReconnect + " seconds")
|
|
|
|
this.reconnect = autoReconnect;
|
|
|
|
break;
|
2021-04-13 02:39:32 +00:00
|
|
|
case msgSetBufferSize:
|
|
|
|
const bufSize = JSON.parse(payload);
|
|
|
|
this.bufSize = bufSize;
|
|
|
|
break;
|
2017-05-21 23:16:24 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
connection.onClose(() => {
|
|
|
|
clearInterval(pingTimer);
|
|
|
|
this.term.deactivate();
|
|
|
|
this.term.showMessage("Connection Closed", 0);
|
|
|
|
if (this.reconnect > 0) {
|
|
|
|
reconnectTimeout = setTimeout(() => {
|
|
|
|
connection = this.connectionFactory.create();
|
|
|
|
this.term.reset();
|
|
|
|
setup();
|
|
|
|
}, this.reconnect * 1000);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
connection.open();
|
|
|
|
}
|
|
|
|
|
|
|
|
setup();
|
|
|
|
return () => {
|
|
|
|
clearTimeout(reconnectTimeout);
|
|
|
|
connection.close();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|