From e4425120870adc53acba0ad4cb472e0592d25f1d Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Sat, 9 Sep 2017 23:12:01 -0400 Subject: [PATCH 01/38] trying to make it work --- .gitmodules | 3 + demo/app.js | 5 ++ demo/index.html | 48 +++++++++++++++ demo/main.js | 151 +++++++++++++++++++++++++++++++++++++++++++++++- demo/zmodemjs | 1 + 5 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 .gitmodules create mode 160000 demo/zmodemjs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..b4a5a6c1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "zmodemjs"] + path = demo/zmodemjs + url = https://github.com/FGasper/zmodemjs.git diff --git a/demo/app.js b/demo/app.js index 8637dad8..089ccf6e 100644 --- a/demo/app.js +++ b/demo/app.js @@ -21,6 +21,10 @@ app.get('/main.js', function(req, res){ res.sendFile(__dirname + '/main.js'); }); +app.get('/zmodemjs/*.js', function(req, res){ + res.sendFile(__dirname + req.url); +}); + app.post('/terminals', function (req, res) { var cols = parseInt(req.query.cols), rows = parseInt(req.query.rows), @@ -59,6 +63,7 @@ app.ws('/terminals/:pid', function (ws, req) { ws.send(logs[term.pid]); term.on('data', function(data) { +console.log("From PTY: ", escape(data) ); try { ws.send(data); } catch (ex) { diff --git a/demo/index.html b/demo/index.html index daa60b6f..60d0b376 100644 --- a/demo/index.html +++ b/demo/index.html @@ -12,9 +12,57 @@ + + + + + + + + + +

xterm.js: xterm, in the browser

+ +
+ + + + + + + + + +
+

Actions

diff --git a/demo/main.js b/demo/main.js index f4fb0a2a..c1d4d4e9 100644 --- a/demo/main.js +++ b/demo/main.js @@ -90,7 +90,7 @@ function createTerminal() { socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; term.open(terminalContainer); - term.fit(); + //term.fit(); // fit is called within a setTimeout, cols and rows need this. setTimeout(() => { @@ -109,13 +109,160 @@ function createTerminal() { socket.onopen = runRealTerminal; socket.onclose = runFakeTerminal; socket.onerror = runFakeTerminal; + + socket.addEventListener("message", handleWSMessage); }); }); }, 0); } +var text_encoder = new TextEncoder(); +var zsentry = new Zmodem.Sentry(); + +var zsession; +function handleWSMessage(evt) { + var binary = text_encoder.encode(evt.data); + var input = Array.prototype.slice.call( new Uint8Array(binary) ); +console.log("from WS", evt.data, input, evt.data.charCodeAt( evt.data.length - 2 )); + + if (zsession) { + zsession.consume(input); + if (zsession.has_ended()) { + input = zsession.get_trailing_bytes(); + zsession = null; + } + } + else { + let termbytes; + [termbytes, zsession] = zsentry.parse(input); + input = termbytes; + + if (zsession) { + + zsession.set_sender( (octets) => { + socket.send( String.fromCharCode.apply(String, octets) ); + } ); + + var start_form = document.getElementById("zm_start"); + start_form.style.display = ""; + start_form.onsubmit = function(e) { + start_form.style.display = "none"; + + if (zsession.type === "receive") { + zsession.on("offer", function(xfer) { + _show_file_info(xfer); + + var offer_form = document.getElementById("zm_offer"); + offer_form.style.display = ""; + offer_form.onsubmit = function(e) { + offer_form.style.display = "none"; + + if (offer_form.zmaccept) { + var FILE_BUFFER = []; + xfer.on("input", (payload) => { + _update_progress(xfer); + FILE_BUFFER.push.apply(FILE_BUFFER, payload); + }); + xfer.accept().then( () => { + _download(xfer, FILE_BUFFER); + _hide_file_info(); + } ); + } + else { + _hide_file_info(); + xfer.skip(); + } + }; + } ); + + zsession.start(); + } + else { + var choose_form = document.getElementById("zm_choose"); + choose_form.style.display = ""; + choose_form.onsubmit = function(e) { + choose_form.style.display = "none"; + + var file_el = document.getElementById("zm_files"); + var batch = []; + var total_size = 0; + for (var f=0; f { + if (xfer === undefined) { + return promise_callback(); //skipped + } + + return new Promise( (res) => { + var reader = new FileReader(); + reader.onerror = function(e) { + console.error("file read error", e); + throw("File read error: " + e); + }; + + var offset = xfer.get_offset(); + reader.onprogress = function(e) { + xfer.send( + new Uint8Array(e.target.result, offset) + ); + offset = e.loaded; + }; + + reader.onload = function(e) { + xfer.end( + new Uint8Array(e.target.result, offset) + ).then(res).then(promise_callback); + }; + + reader.readAsArrayBuffer(cur_b.obj); + } ); + } ) + } + else { + zsession.close(); + choose_form.style.display = "none"; + } + } + + promise_callback(); + }; + + } + }; + } + } + + if (input.length) { + term.write( + String.fromCharCode.apply(String, input) + ); + } +} + +//var text_encoder = new TextEncoder(); + function runRealTerminal() { - term.attach(socket); + //term.attach(socket); + + term.on("data", (d) => { + //socket.send( text_encoder.encode(d) ); + socket.send(d); + }); + term._initialized = true; } diff --git a/demo/zmodemjs b/demo/zmodemjs new file mode 160000 index 00000000..eefc61c2 --- /dev/null +++ b/demo/zmodemjs @@ -0,0 +1 @@ +Subproject commit eefc61c2c4cbad743dea53b417a4948a08b7b652 From 91aa8c51fc3bdf04e4cb0f7712ceed5719686f46 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Mon, 11 Sep 2017 00:01:07 -0400 Subject: [PATCH 02/38] got it --- demo/app.js | 2 +- demo/main.js | 70 +++++++++++++++++++++++++++++++++++++++++++++++---- demo/zmodemjs | 2 +- package.json | 2 +- 4 files changed, 68 insertions(+), 8 deletions(-) diff --git a/demo/app.js b/demo/app.js index 089ccf6e..0c137a1d 100644 --- a/demo/app.js +++ b/demo/app.js @@ -29,6 +29,7 @@ app.post('/terminals', function (req, res) { var cols = parseInt(req.query.cols), rows = parseInt(req.query.rows), term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], { + encoding: null, name: 'xterm-color', cols: cols || 80, rows: rows || 24, @@ -63,7 +64,6 @@ app.ws('/terminals/:pid', function (ws, req) { ws.send(logs[term.pid]); term.on('data', function(data) { -console.log("From PTY: ", escape(data) ); try { ws.send(data); } catch (ex) { diff --git a/demo/main.js b/demo/main.js index c1d4d4e9..38124de7 100644 --- a/demo/main.js +++ b/demo/main.js @@ -106,6 +106,7 @@ function createTerminal() { window.pid = pid; socketURL += pid; socket = new WebSocket(socketURL); + socket.binaryType = "arraybuffer"; socket.onopen = runRealTerminal; socket.onclose = runFakeTerminal; socket.onerror = runFakeTerminal; @@ -116,21 +117,79 @@ function createTerminal() { }, 0); } +//---------------------------------------------------------------------- +// UI STUFF + +function _show_file_info(xfer) { + var file_info = xfer.get_details(); + + document.getElementById("name").textContent = file_info.name; + document.getElementById("size").textContent = file_info.size; + document.getElementById("mtime").textContent = file_info.mtime; + document.getElementById("files_remaining").textContent = file_info.files_remaining; + document.getElementById("bytes_remaining").textContent = file_info.bytes_remaining; + + document.getElementById("mode").textContent = "0" + file_info.mode.toString(8); + + document.getElementById("zm_file").style.display = ""; +} +function _hide_file_info() { + document.getElementById("zm_file").style.display = "none"; +} + +function _download(xfer, buffer) { + var uint8array = new Uint8Array(buffer); + var blob = new Blob([uint8array]); + var url = URL.createObjectURL(blob); + + var el = document.createElement("a"); + el.style.display = "none"; + el.href = url; + el.download = xfer.get_details().name; + document.body.appendChild(el); + + //It seems like a security problem that this actually works. + //But, hey. + el.click(); +} + +function _update_progress(xfer) { + document.getElementById("zm_progress").style.display = ""; + + var total_in = xfer.get_offset(); + + document.getElementById("bytes_received").textContent = total_in; + + var percent_received = 100 * total_in / xfer.get_details().size; + document.getElementById("percent_received").textContent = percent_received; +} + +// END UI STUFF +//---------------------------------------------------------------------- + var text_encoder = new TextEncoder(); var zsentry = new Zmodem.Sentry(); var zsession; function handleWSMessage(evt) { - var binary = text_encoder.encode(evt.data); - var input = Array.prototype.slice.call( new Uint8Array(binary) ); -console.log("from WS", evt.data, input, evt.data.charCodeAt( evt.data.length - 2 )); + var input = Array.prototype.slice.call( + new Uint8Array( + (typeof evt.data === "string") ? text_encoder.encode(evt.data) : evt.data + ) + ); if (zsession) { zsession.consume(input); if (zsession.has_ended()) { - input = zsession.get_trailing_bytes(); + if (zsession.type === "receive") { + input = zsession.get_trailing_bytes(); + } + else { + input = []; + } zsession = null; } + else input = []; //keep ZMODEM from going to the terminal } else { let termbytes; @@ -140,7 +199,8 @@ console.log("from WS", evt.data, input, evt.data.charCodeAt( evt.data.length - 2 if (zsession) { zsession.set_sender( (octets) => { - socket.send( String.fromCharCode.apply(String, octets) ); + //socket.send( String.fromCharCode.apply(String, octets) ); + socket.send( new Uint8Array(octets) ); } ); var start_form = document.getElementById("zm_start"); diff --git a/demo/zmodemjs b/demo/zmodemjs index eefc61c2..7220230d 160000 --- a/demo/zmodemjs +++ b/demo/zmodemjs @@ -1 +1 @@ -Subproject commit eefc61c2c4cbad743dea53b417a4948a08b7b652 +Subproject commit 7220230d12ce5eb4c42c59442a5924d98abc1c1f diff --git a/package.json b/package.json index f32918df..dc1e47ae 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "jsdoc": "3.4.3", "jsdom": "^11.1.0", "merge-stream": "^1.0.1", - "node-pty": "^0.4.1", + "node-pty": "0.6.4", "nodemon": "1.10.2", "sorcery": "^0.10.0", "tslint": "^4.0.2", From 6181262c76e53fefe2f1e5eb370ab6f9f36c68c3 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Mon, 11 Sep 2017 22:39:13 -0400 Subject: [PATCH 03/38] remove debug changes --- demo/main.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/demo/main.js b/demo/main.js index 38124de7..f1245cb0 100644 --- a/demo/main.js +++ b/demo/main.js @@ -90,7 +90,7 @@ function createTerminal() { socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; term.open(terminalContainer); - //term.fit(); + term.fit(); // fit is called within a setTimeout, cols and rows need this. setTimeout(() => { @@ -199,7 +199,6 @@ function handleWSMessage(evt) { if (zsession) { zsession.set_sender( (octets) => { - //socket.send( String.fromCharCode.apply(String, octets) ); socket.send( new Uint8Array(octets) ); } ); From cdcedd6b34d799f7b48d75febaf300a5d0855b55 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Fri, 15 Sep 2017 02:31:32 -0400 Subject: [PATCH 04/38] updates to factor out more logic to library --- demo/index.html | 1 + demo/main.js | 251 ++++++++++++++++++++---------------------------- demo/zmodemjs | 2 +- 3 files changed, 105 insertions(+), 149 deletions(-) diff --git a/demo/index.html b/demo/index.html index 60d0b376..264e50ae 100644 --- a/demo/index.html +++ b/demo/index.html @@ -22,6 +22,7 @@ +

xterm.js: xterm, in the browser

diff --git a/demo/main.js b/demo/main.js index f1245cb0..ee4dca70 100644 --- a/demo/main.js +++ b/demo/main.js @@ -138,19 +138,7 @@ function _hide_file_info() { } function _download(xfer, buffer) { - var uint8array = new Uint8Array(buffer); - var blob = new Blob([uint8array]); - var url = URL.createObjectURL(blob); - - var el = document.createElement("a"); - el.style.display = "none"; - el.href = url; - el.download = xfer.get_details().name; - document.body.appendChild(el); - - //It seems like a security problem that this actually works. - //But, hey. - el.click(); + return Zmodem.Browser.save_to_disk(buffer, xfer.get_details().name); } function _update_progress(xfer) { @@ -164,151 +152,118 @@ function _update_progress(xfer) { document.getElementById("percent_received").textContent = percent_received; } +var start_form = document.getElementById("zm_start"); + // END UI STUFF //---------------------------------------------------------------------- -var text_encoder = new TextEncoder(); -var zsentry = new Zmodem.Sentry(); +function _handle_receive_session(zsession) { + zsession.on("offer", function(xfer) { + _show_file_info(xfer); -var zsession; -function handleWSMessage(evt) { - var input = Array.prototype.slice.call( - new Uint8Array( - (typeof evt.data === "string") ? text_encoder.encode(evt.data) : evt.data - ) - ); + var offer_form = document.getElementById("zm_offer"); + offer_form.style.display = ""; + offer_form.onsubmit = function(e) { + offer_form.style.display = "none"; - if (zsession) { - zsession.consume(input); - if (zsession.has_ended()) { - if (zsession.type === "receive") { - input = zsession.get_trailing_bytes(); + //START + if (offer_form.zmaccept.value) { + var FILE_BUFFER = []; + xfer.on("input", (payload) => { + _update_progress(xfer); + FILE_BUFFER.push.apply(FILE_BUFFER, payload); + }); + xfer.accept().then( () => { + _download(xfer, FILE_BUFFER); + _hide_file_info(); + } ); } else { - input = []; + _hide_file_info(); + xfer.skip(); } - zsession = null; - } - else input = []; //keep ZMODEM from going to the terminal + //END + }; + } ); + + zsession.start(); +} + +function _handle_send_session(zsession) { + var choose_form = document.getElementById("zm_choose"); + choose_form.style.display = ""; + choose_form.onsubmit = function(e) { + choose_form.style.display = "none"; + + var file_el = document.getElementById("zm_files"); + var files_obj = file_el.files; + + Zmodem.Browser.send_files( + zsession, + files_obj, + { + on_offer_response(obj, xfer) { + console.log("offer", xfer ? "accepted" : "skipped"); + }, + on_file_complete(obj) { + console.log("COMPLETE", obj); + }, + } + ).then( + zsession.close.bind(zsession), + console.error.bind(console) + ); + }; +} + +var text_encoder = new TextEncoder(); +var zsentry = new Zmodem.Sentry( { + to_terminal(octets) { + term.write( + String.fromCharCode.apply(String, octets) + ); + }, + + sender(octets) { + socket.send( new Uint8Array(octets) ); + }, + + on_retract() { + start_form.style.display = "none"; + start_form.onsubmit = null; + }, + + on_detect(detection) { + start_form.style.display = ""; + start_form.onsubmit = function(e) { + start_form.style.display = "none"; + + //Aborted + if (!e.currentTarget.zmstart.value) { + return; + } + + var zsession = detection.confirm(); + + //HERE + if (zsession.type === "receive") { + _handle_receive_session(zsession); + } + else { + _handle_send_session(zsession); + } + }; + }, +} ); + +function handleWSMessage(evt) { + + //For some reason the first message from the server is text. + if (typeof evt.data === "string") { + term.write(evt.data); } else { - let termbytes; - [termbytes, zsession] = zsentry.parse(input); - input = termbytes; - - if (zsession) { - - zsession.set_sender( (octets) => { - socket.send( new Uint8Array(octets) ); - } ); - - var start_form = document.getElementById("zm_start"); - start_form.style.display = ""; - start_form.onsubmit = function(e) { - start_form.style.display = "none"; - - if (zsession.type === "receive") { - zsession.on("offer", function(xfer) { - _show_file_info(xfer); - - var offer_form = document.getElementById("zm_offer"); - offer_form.style.display = ""; - offer_form.onsubmit = function(e) { - offer_form.style.display = "none"; - - if (offer_form.zmaccept) { - var FILE_BUFFER = []; - xfer.on("input", (payload) => { - _update_progress(xfer); - FILE_BUFFER.push.apply(FILE_BUFFER, payload); - }); - xfer.accept().then( () => { - _download(xfer, FILE_BUFFER); - _hide_file_info(); - } ); - } - else { - _hide_file_info(); - xfer.skip(); - } - }; - } ); - - zsession.start(); - } - else { - var choose_form = document.getElementById("zm_choose"); - choose_form.style.display = ""; - choose_form.onsubmit = function(e) { - choose_form.style.display = "none"; - - var file_el = document.getElementById("zm_files"); - var batch = []; - var total_size = 0; - for (var f=0; f { - if (xfer === undefined) { - return promise_callback(); //skipped - } - - return new Promise( (res) => { - var reader = new FileReader(); - reader.onerror = function(e) { - console.error("file read error", e); - throw("File read error: " + e); - }; - - var offset = xfer.get_offset(); - reader.onprogress = function(e) { - xfer.send( - new Uint8Array(e.target.result, offset) - ); - offset = e.loaded; - }; - - reader.onload = function(e) { - xfer.end( - new Uint8Array(e.target.result, offset) - ).then(res).then(promise_callback); - }; - - reader.readAsArrayBuffer(cur_b.obj); - } ); - } ) - } - else { - zsession.close(); - choose_form.style.display = "none"; - } - } - - promise_callback(); - }; - - } - }; - } - } - - if (input.length) { - term.write( - String.fromCharCode.apply(String, input) - ); + zsentry.consume(evt.data); } } diff --git a/demo/zmodemjs b/demo/zmodemjs index 7220230d..f19e3fcf 160000 --- a/demo/zmodemjs +++ b/demo/zmodemjs @@ -1 +1 @@ -Subproject commit 7220230d12ce5eb4c42c59442a5924d98abc1c1f +Subproject commit f19e3fcfc0bc80eaa25e4cbae1dedf26ca7d6b2b From ce681fe48165c9297b76ba8891c14f3b57cbf9a9 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Fri, 15 Sep 2017 03:26:54 -0400 Subject: [PATCH 05/38] demo: hide progress after receive --- demo/main.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/demo/main.js b/demo/main.js index ee4dca70..54150b15 100644 --- a/demo/main.js +++ b/demo/main.js @@ -152,6 +152,10 @@ function _update_progress(xfer) { document.getElementById("percent_received").textContent = percent_received; } +function _hide_progress() { + document.getElementById("zm_progress").style.display = "none"; +} + var start_form = document.getElementById("zm_start"); // END UI STUFF @@ -176,6 +180,7 @@ function _handle_receive_session(zsession) { xfer.accept().then( () => { _download(xfer, FILE_BUFFER); _hide_file_info(); + _hide_progress(); } ); } else { From 5f62c7607d4aac41c81b7da2f64981a2f5797772 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Fri, 15 Sep 2017 03:30:51 -0400 Subject: [PATCH 06/38] progress indicators for send --- demo/main.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/demo/main.js b/demo/main.js index 54150b15..69b86aba 100644 --- a/demo/main.js +++ b/demo/main.js @@ -210,11 +210,14 @@ function _handle_send_session(zsession) { on_offer_response(obj, xfer) { console.log("offer", xfer ? "accepted" : "skipped"); }, + on_progress(obj, xfer) { + _update_progress(xfer); + }, on_file_complete(obj) { console.log("COMPLETE", obj); }, } - ).then( + ).then(_hide_progress).then( zsession.close.bind(zsession), console.error.bind(console) ); From 94586b669f7310a39b6764aca6e6e50b668a4403 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Fri, 15 Sep 2017 03:45:23 -0400 Subject: [PATCH 07/38] update submodule --- demo/zmodemjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/zmodemjs b/demo/zmodemjs index f19e3fcf..a48daa36 160000 --- a/demo/zmodemjs +++ b/demo/zmodemjs @@ -1 +1 @@ -Subproject commit f19e3fcfc0bc80eaa25e4cbae1dedf26ca7d6b2b +Subproject commit a48daa360ad4c31ce6f8c77042b34d0b4f74e930 From 65f5f1c38c7dda72bf6b4befa89cd4654c859f56 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Mon, 18 Sep 2017 07:19:22 -0400 Subject: [PATCH 08/38] deny() handling --- demo/main.js | 19 +++++++++---------- demo/zmodemjs | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/demo/main.js b/demo/main.js index 69b86aba..9fcdbda2 100644 --- a/demo/main.js +++ b/demo/main.js @@ -246,19 +246,18 @@ var zsentry = new Zmodem.Sentry( { start_form.onsubmit = function(e) { start_form.style.display = "none"; - //Aborted - if (!e.currentTarget.zmstart.value) { - return; - } + if (e.currentTarget.zmstart.value) { + let zsession = detection.confirm(); - var zsession = detection.confirm(); - - //HERE - if (zsession.type === "receive") { - _handle_receive_session(zsession); + if (zsession.type === "receive") { + _handle_receive_session(zsession); + } + else { + _handle_send_session(zsession); + } } else { - _handle_send_session(zsession); + detection.deny(); } }; }, diff --git a/demo/zmodemjs b/demo/zmodemjs index a48daa36..46c04daf 160000 --- a/demo/zmodemjs +++ b/demo/zmodemjs @@ -1 +1 @@ -Subproject commit a48daa360ad4c31ce6f8c77042b34d0b4f74e930 +Subproject commit 46c04daf223891ed850ef8cdb8327812df9c7a57 From 3f467b33e4d7431c85a836d1e0a0601093759af6 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Tue, 26 Sep 2017 10:31:24 -0400 Subject: [PATCH 09/38] update demo to latest zmodem.js code --- demo/index.html | 2 ++ demo/main.js | 19 +++++++++++++++---- demo/zmodemjs | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/demo/index.html b/demo/index.html index 264e50ae..cbcc8043 100644 --- a/demo/index.html +++ b/demo/index.html @@ -22,6 +22,7 @@ + @@ -56,6 +57,7 @@
+

ZMODEM file transfers are supported via an addon. To try it out, install lrzsz onto the remote peer, then type rz to send from your browser or sz <file> to send from the remote peer.

Attention: The demo is a barebones implementation and is designed for xterm.js evaluation purposes only. Exposing the demo to the public as is would introduce security risks for the host.

From ed6e2e5055014a649efe418d14bfaf48f5972adf Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Tue, 17 Oct 2017 09:28:41 +0200 Subject: [PATCH 23/38] Reorder demo components, ensure we hide at end of receive. --- demo/index.html | 24 +++++++++++++++--------- demo/main.js | 12 ++++++++++-- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/demo/index.html b/demo/index.html index f808ccbe..dea1e400 100644 --- a/demo/index.html +++ b/demo/index.html @@ -34,15 +34,6 @@ - - + +