Skip to content

Commit ecb6c3b

Browse files
committed
add Zmodem.Browser, with a batch-send for browsers
1 parent 88b29e2 commit ecb6c3b

File tree

3 files changed

+144
-7
lines changed

3 files changed

+144
-7
lines changed

tools/all_bytes

256 Bytes
Binary file not shown.

zbrowser.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Browser-specific tools
2+
( function() {
3+
"use strict";
4+
5+
/**
6+
* Send a batch of files in sequence. The session is left open
7+
* afterward, which allows for more files to be sent if desired.
8+
*
9+
* @param {Zmodem.Session} session - The send session
10+
*
11+
* @param {FileList|Array} files - A list of File objects
12+
*
13+
* @param {Object} options - Optional, can be:
14+
*
15+
* - on_offer_response(File object, Transfer object)
16+
* Called when an offer response arrives. If the offer is
17+
* not accepted (i.e., skipped), the 2nd argument will be undefined.
18+
*
19+
* - on_progress(File, Transfer, Uint8Array)
20+
* Called immediately after a chunk of a file is sent.
21+
* That chunk is represented by the Uint8Array.
22+
*
23+
* - on_file_complete(File, Transfer, Uint8Array)
24+
* Called immediately after the last chunk of a file is sent.
25+
* That chunk is represented by the Uint8Array.
26+
* (It’s probably empty.)
27+
*
28+
* @return {Promise} A Promise that fulfills when the batch is done.
29+
* Note that skipped files are not considered an error condition.
30+
*/
31+
function send_files(session, files, options) {
32+
if (!options) options = {};
33+
34+
var batch = [];
35+
var total_size = 0;
36+
for (var f=0; f<files_obj.length; f++) {
37+
var fobj = files_obj[f];
38+
batch.push( {
39+
obj: fobj,
40+
name: fobj.name,
41+
size: fobj.size,
42+
} );
43+
total_size += fobj.size;
44+
}
45+
46+
var file_idx = 0;
47+
function promise_callback() {
48+
var cur_b = batch[file_idx];
49+
50+
if (!cur_b) return; //batch done!
51+
52+
file_idx++;
53+
54+
return zsession.send_offer(cur_b).then( function after_send_offer(xfer) {
55+
if (options.on_offer_response) {
56+
options.on_offer_response(cur_b.obj, xfer);
57+
}
58+
59+
if (xfer === undefined) {
60+
return promise_callback(); //skipped
61+
}
62+
63+
return new Promise( function(res) {
64+
var reader = new FileReader();
65+
66+
//This really shouldn’t happen … so let’s
67+
//blow up if it does.
68+
reader.onerror = function reader_onerror(e) {
69+
console.error("file read error", e);
70+
throw("File read error: " + e);
71+
};
72+
73+
var piece;
74+
reader.onprogress = function reader_onprogress(e) {
75+
piece = new Uint8Array(e.target.result, xfer.get_offset())
76+
xfer.send(piece);
77+
78+
if (options.on_progress) {
79+
options.on_progress(cur_b.obj, xfer, piece);
80+
}
81+
};
82+
83+
reader.onload = function reader_onload(e) {
84+
piece = new Uint8Array(e.target.result, xfer, piece)
85+
xfer.end(piece).then(res).then(promise_callback);
86+
87+
if (options.on_file_complete) {
88+
options.on_file_complete(cur_b.obj, xfer);
89+
}
90+
};
91+
92+
reader.readAsArrayBuffer(cur_b.obj);
93+
} );
94+
} );
95+
}
96+
97+
return promise_callback();
98+
}
99+
100+
Zmodem.Browser = {
101+
send_files: send_files,
102+
};
103+
104+
}());

zsession.js

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ const
66
KEEPALIVE_INTERVAL = 5000,
77
ZRINIT_FLAGS = [ "CANFDX", "CANOVIO" ],
88

9+
//We do this because some WebSocket shell servers
10+
//(e.g., xterm.js’s demo server) enable the IEXTEN termios flag,
11+
//which bars 0x0f and 0x16 from reaching the shell process,
12+
//which results in transmission errors.
13+
FORCE_ESCAPE_CTRL_CHARS = true,
14+
915
//pertinent to ZMODEM
1016
MAX_CHUNK_LENGTH = 8192, //1 KiB officially, but lrzsz allows 8192
1117
CAN = 0x18,
@@ -670,11 +676,23 @@ class ZmodemTransfer {
670676
constructor(offset, send_func, end_func) {
671677
this._file_offset = offset || 0;
672678

673-
this.send = send_func;
674-
this.end = end_func;
679+
this._send = send_func;
680+
this._end = end_func;
675681
}
676682

677683
get_offset() { return this._file_offset }
684+
685+
send(array_like) {
686+
var ret = this._send(array_like);
687+
this._file_offset += array_like.length;
688+
return ret;
689+
}
690+
691+
end(array_like) {
692+
var ret = this._end(array_like);
693+
this._file_offset += array_like.length;
694+
return ret;
695+
}
678696
}
679697

680698
class ZmodemOffer extends _Eventer {
@@ -838,8 +856,14 @@ Zmodem.Session.Send = class ZmodemSendSession extends Zmodem.Session {
838856
_send_ZSINIT() {
839857
//See note at _ensure_receiver_escapes_ctrl_chars()
840858
//for why we have to pass ESCCTL.
841-
this._send_header("ZSINIT", ["ESCCTL"]);
842-
this._zencoder.set_escape_ctrl_chars(true);
859+
860+
var zsinit_flags = [];
861+
if (this._zencoder.escapes_ctrl_chars()) {
862+
zsinit_flags.push("ESCCTL");
863+
}
864+
865+
this._send_header("ZSINIT", zsinit_flags);
866+
843867
//this._send_data( this._get_attn(), "end_ack" );
844868
this._build_and_send_subpacket( [0], "end_ack" );
845869
}
@@ -873,8 +897,14 @@ Zmodem.Session.Send = class ZmodemSendSession extends Zmodem.Session {
873897
throw( "8-bit escaping is unsupported!" );
874898
}
875899

876-
if (!hdr.escape_ctrl_chars()) {
877-
console.info("Peer didn’t request escape of all control characters. Will send ZSINIT to force recognition of escaped control characters.");
900+
if (FORCE_ESCAPE_CTRL_CHARS) {
901+
this._zencoder.set_escape_ctrl_chars(true);
902+
if (!hdr.escape_ctrl_chars()) {
903+
console.info("Peer didn’t request escape of all control characters. Will send ZSINIT to force recognition of escaped control characters.");
904+
}
905+
}
906+
else {
907+
this._zencoder.set_escape_ctrl_chars(hdr.escape_ctrl_chars());
878908
}
879909
}
880910

@@ -933,7 +963,10 @@ Zmodem.Session.Send = class ZmodemSendSession extends Zmodem.Session {
933963
payload_array = Array.prototype.slice.call(payload_array);
934964

935965
var sess = this;
936-
return this._ensure_receiver_escapes_ctrl_chars().then( function() {
966+
967+
var first_promise = FORCE_ESCAPE_CTRL_CHARS ? this._ensure_receiver_escapes_ctrl_chars() : Promise.resolve();
968+
969+
return first_promise.then( function() {
937970

938971
//TODO: Might as well combine these together?
939972
sess._send_header( "ZFILE" );

0 commit comments

Comments
 (0)