(this.container = c)} />;
+ render({ id }: Props, { modal }: State) {
+ return (
+
(this.container = c)}>
+
+
+
+
+ );
+ }
+
+ @bind
+ private zmodemWrite(data: ArrayBuffer): void {
+ const { terminal } = this;
+ terminal.writeUtf8(new Uint8Array(data));
+ }
+
+ @bind
+ private zmodemSend(data: number[]): void {
+ const { socket } = this;
+ const buffer = new Uint8Array(data.length + 1);
+ buffer[0] = Command.INPUT.charCodeAt(0);
+ buffer.set(data, 1);
+ socket.send(buffer);
+ }
+
+ @bind
+ private zmodemDetect(detection: any): void {
+ const { terminal, receiveFile } = this;
+
+ terminal.setOption('disableStdin', true);
+ this.session = detection.confirm();
+ if (this.session.type === 'send') {
+ this.setState({ modal: true });
+ } else {
+ receiveFile();
+ }
+ }
+
+ @bind
+ private sendFile(event: Event) {
+ this.setState({ modal: false });
+
+ const { terminal, session, writeProgress } = this;
+ const files: FileList = (event.target as HTMLInputElement).files;
+ if (files.length === 0) {
+ session.close();
+ terminal.setOption('disableStdin', false);
+ return;
+ }
+
+ Zmodem.Browser.send_files(session, files, {
+ on_progress: (_, xfer) => writeProgress(xfer),
+ on_file_complete: () => {},
+ }).then(() => {
+ session.close();
+ terminal.setOption('disableStdin', false);
+ });
+ }
+
+ @bind
+ private receiveFile() {
+ const { terminal, session, writeProgress } = this;
+
+ session.on('offer', (xfer: any) => {
+ const fileBuffer = [];
+ xfer.on('input', payload => {
+ writeProgress(xfer);
+ fileBuffer.push(new Uint8Array(payload));
+ });
+ xfer.accept().then(() => {
+ Zmodem.Browser.save_to_disk(fileBuffer, xfer.get_details().name);
+ terminal.setOption('disableStdin', false);
+ });
+ });
+
+ session.start();
+ }
+
+ @bind
+ private writeProgress(xfer: any) {
+ const { terminal, bytesHuman } = this;
+
+ const file = xfer.get_details();
+ const name = file.name;
+ const size = file.size;
+ const offset = xfer.get_offset();
+ const percent = ((100 * offset) / size).toFixed(2);
+
+ terminal.write(
+ `${name} ${percent}% ${bytesHuman(offset, 2)}/${bytesHuman(size, 2)}\r`
+ );
}
@bind
@@ -154,29 +268,34 @@ export class Xterm extends Component
{
@bind
private onSocketData(event: MessageEvent) {
- const { terminal, textDecoder } = this;
-
- const rawData = new Uint8Array(event.data);
- const cmd = String.fromCharCode(rawData[0]);
+ const { terminal, textDecoder, socket, openTerminal } = 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:
- terminal.writeUtf8(data);
+ try {
+ this.sentry.consume(data);
+ } catch (e) {
+ console.log(`[ttyd] zmodem consume: `, e);
+ socket.close();
+ setTimeout(() => openTerminal(), 500);
+ }
break;
case Command.SET_WINDOW_TITLE:
- this.title = textDecoder.decode(data.buffer);
+ this.title = textDecoder.decode(data);
document.title = this.title;
break;
case Command.SET_PREFERENCES:
- const preferences = JSON.parse(textDecoder.decode(data.buffer));
+ const preferences = JSON.parse(textDecoder.decode(data));
Object.keys(preferences).forEach(key => {
console.log(`[ttyd] setting ${key}: ${preferences[key]}`);
terminal.setOption(key, preferences[key]);
});
break;
case Command.SET_RECONNECT:
- this.autoReconnect = Number(textDecoder.decode(data.buffer));
+ this.autoReconnect = Number(textDecoder.decode(data));
console.log(`[ttyd] enabling reconnect: ${this.autoReconnect} seconds`);
break;
default:
@@ -204,4 +323,16 @@ export class Xterm extends Component {
socket.send(textEncoder.encode(Command.INPUT + data));
}
}
+
+ private bytesHuman(bytes: any, precision: number): string {
+ if (!/^([-+])?|(\.\d+)(\d+(\.\d+)?|(\d+\.)|Infinity)$/.test(bytes)) {
+ return '-';
+ }
+ if (bytes === 0) return '0';
+ if (typeof precision === 'undefined') precision = 1;
+ const units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
+ const num = Math.floor(Math.log(bytes) / Math.log(1024));
+ const value = (bytes / Math.pow(1024, Math.floor(num))).toFixed(precision);
+ return `${value} ${units[num]}`;
+ }
}
diff --git a/html/yarn.lock b/html/yarn.lock
index c5cd108..69b8c8b 100644
--- a/html/yarn.lock
+++ b/html/yarn.lock
@@ -1601,6 +1601,14 @@ cosmiconfig@^5.0.0, cosmiconfig@^5.2.0:
js-yaml "^3.13.1"
parse-json "^4.0.0"
+crc-32@^1.1.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208"
+ integrity sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==
+ dependencies:
+ exit-on-epipe "~1.0.1"
+ printj "~1.1.0"
+
create-ecdh@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff"
@@ -2519,6 +2527,11 @@ execa@^1.0.0:
signal-exit "^3.0.0"
strip-eof "^1.0.0"
+exit-on-epipe@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692"
+ integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==
+
expand-brackets@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
@@ -6643,6 +6656,11 @@ pretty-hrtime@^1.0.0:
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=
+printj@~1.1.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222"
+ integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==
+
process-nextick-args@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
@@ -9164,3 +9182,10 @@ yup@^0.27.0:
property-expr "^1.5.0"
synchronous-promise "^2.0.6"
toposort "^2.0.2"
+
+zmodem.js@^0.1.9:
+ version "0.1.9"
+ resolved "https://registry.yarnpkg.com/zmodem.js/-/zmodem.js-0.1.9.tgz#8dda36d45091bbdf263819f961d3c1a20223daf7"
+ integrity sha512-xixLjW1eML0uiWULsXDInyfwNW9mqESzz7ra+2MWHNG2F5JINEkE5vzF5MigpPcLvrYoHdnehPcJwQZlDph3hQ==
+ dependencies:
+ crc-32 "^1.1.1"
diff --git a/src/index.html b/src/index.html
index 36263cd..3b6cbe1 100644
--- a/src/index.html
+++ b/src/index.html
@@ -1 +1 @@
-ttyd - Terminal
\ No newline at end of file
+ttyd - Terminal
\ No newline at end of file