@masx200/zmodem.js
Version:
ZMODEM file transfers in JavaScript
190 lines (161 loc) • 6.86 kB
JavaScript
;
var Zmodem = module.exports;
//TODO: Make this usable without require.js or what not.
window.Zmodem = Zmodem;
Object.assign(
Zmodem,
require("./zmodem"),
);
function _check_aborted(session) {
if (session.aborted()) {
throw new Zmodem.Error("aborted");
}
}
/** Browser-specific tools
*
* @exports Browser
*/
Zmodem.Browser = {
/**
* Send a batch of files in sequence. The session is left open
* afterward, which allows for more files to be sent if desired.
*
* @param {Zmodem.Session} session - The send session
*
* @param {FileList|Array} files - A list of File objects
*
* @param {Object} [options]
* @param {Function} [options.on_offer_response] - Called when an
* offer response arrives. Arguments are:
*
* - (File) - The File object that corresponds to the offer.
* - (Transfer|undefined) - If the receiver accepts the offer, then
* this is a Transfer object; otherwise it’s undefined.
*
* @param {Function} [options.on_progress] - Called immediately
* after a chunk of a file is sent. Arguments are:
*
* - (File) - The File object that corresponds to the file.
* - (Transfer) - The Transfer object for the current transfer.
* - (Uint8Array) - The chunk of data that was just loaded from disk
* and sent to the receiver.
*
* @param {Function} [options.on_file_complete] - Called immediately
* after the last file packet is sent. Arguments are:
*
* - (File) - The File object that corresponds to the file.
* - (Transfer) - The Transfer object for the now-completed transfer.
*
* @return {Promise} A Promise that fulfills when the batch is done.
* Note that skipped files are not considered an error condition.
*/
send_files: function send_files(session, files, options) {
if (!options) options = {};
//Populate the batch in reverse order to simplify sending
//the remaining files/bytes components.
var batch = [];
var total_size = 0;
for (var f = files.length - 1; f >= 0; f--) {
var fobj = files[f];
total_size += fobj.size;
batch[f] = {
obj: fobj,
name: fobj.name,
size: fobj.size,
mtime: new Date(fobj.lastModified),
files_remaining: files.length - f,
bytes_remaining: total_size,
};
}
var file_idx = 0;
function promise_callback() {
var cur_b = batch[file_idx];
if (!cur_b) {
return Promise.resolve(); //batch done!
}
file_idx++;
return session.send_offer(cur_b).then(
function after_send_offer(xfer) {
if (options.on_offer_response) {
options.on_offer_response(cur_b.obj, xfer);
}
if (xfer === undefined) {
return promise_callback(); //skipped
}
return new Promise(function (res) {
var reader = new FileReader();
//This really shouldn’t happen … so let’s
//blow up if it does.
reader.onerror = function reader_onerror(e) {
console.error("file read error", e);
throw ("File read error: " + e);
};
var piece;
reader.onprogress = function reader_onprogress(e) {
//Some browsers (e.g., Chrome) give partial returns,
//while others (e.g., Firefox) don’t.
if (e.target.result) {
piece = new Uint8Array(
e.target.result,
xfer.get_offset(),
);
_check_aborted(session);
xfer.send(piece);
if (options.on_progress) {
options.on_progress(cur_b.obj, xfer, piece);
}
}
};
reader.onload = function reader_onload(e) {
piece = new Uint8Array(
e.target.result,
xfer,
piece,
);
_check_aborted(session);
xfer.end(piece).then(function () {
if (options.on_progress && piece.length) {
options.on_progress(cur_b.obj, xfer, piece);
}
if (options.on_file_complete) {
options.on_file_complete(cur_b.obj, xfer);
}
//Resolve the current file-send promise with
//another promise. That promise resolves immediately
//if we’re done, or with another file-send promise
//if there’s more to send.
res(promise_callback());
});
};
reader.readAsArrayBuffer(cur_b.obj);
});
},
);
}
return promise_callback();
},
/**
* Prompt a user to save the given packets as a file by injecting an
* `<a>` element (with `display: none` styling) into the page and
* calling the element’s `click()`
* method. The element is removed immediately after.
*
* @param {Array} packets - Same as the first argument to [Blob’s constructor](https://developer.mozilla.org/en-US/docs/Web/API/Blob).
* @param {string} name - The name to give the file.
*/
save_to_disk: function save_to_disk(packets, name) {
var blob = new Blob(packets);
var url = URL.createObjectURL(blob);
var el = document.createElement("a");
el.style.display = "none";
el.href = url;
el.download = name;
document.body.appendChild(el);
//It seems like a security problem that this actually works;
//I’d think there would need to be some confirmation before
//a browser could save arbitrarily many bytes onto the disk.
//But, hey.
el.click();
document.body.removeChild(el);
},
};