diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2021-11-20 06:01:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2021-11-20 06:19:39 +0000 |
commit | 56eec1de7018759c0ec251dba4455c18f73c3bbd (patch) | |
tree | 3aeb2d10356530bc2cc3f24e74f41048a13885b4 /src/zmodem_browser.js | |
parent | Initial commit. (diff) | |
download | zmodemjs-upstream.tar.xz zmodemjs-upstream.zip |
Adding upstream version 0.1.10+dfsg.upstream/0.1.10+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/zmodem_browser.js | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/src/zmodem_browser.js b/src/zmodem_browser.js new file mode 100644 index 0000000..7405f54 --- /dev/null +++ b/src/zmodem_browser.js @@ -0,0 +1,182 @@ +"use strict"; + +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); + }, +}; |