summaryrefslogtreecommitdiffstats
path: root/src/zmodem_browser.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/zmodem_browser.js')
-rw-r--r--src/zmodem_browser.js182
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);
+ },
+};