summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/transport.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/transport.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/transport.js41
1 files changed, 30 insertions, 11 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
index 326aa8e..24c08b9 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
@@ -42,6 +42,12 @@ const UPLOAD_CHUNK_SIZE = 48 * 1024;
const UPLOAD_WINDOW = 32;
const UPLOAD_BUFFER_HIGH = 1024 * 1024;
+function _aborted() {
+ const err = new Error('Cancelled');
+ err.name = 'AbortError';
+ return err;
+}
+
const JOIN_REFUSALS = {
code_required: 'This node does not know this browser yet. Ask the node operator '
+ 'for a pairing code (meshbay-node operator pair).',
@@ -73,7 +79,10 @@ class MeshBayTransport {
this._onStreamData = null;
this._onStreamEnd = null;
this._onIndexSync = null;
- this._onUploadAck = null;
+ // filename → the uploader waiting on it. Keyed rather than FIFO because
+ // several uploads may be in flight at once and their acks interleave; the
+ // node names the file in every one.
+ this._uploaders = new Map();
}
get connected() { return this._connected; }
@@ -533,10 +542,12 @@ class MeshBayTransport {
* free one rather than replacing anything. The ack says which, and that is what
* this returns.
*/
- async uploadFile(file, { chunkSize, onProgress } = {}) {
- // One at a time: the acks are matched by arrival, so two uploads sharing the
- // channel would credit each other's progress and finish at the wrong moment.
- if (this._onUploadAck) throw new Error('Another upload is already running');
+ async uploadFile(file, { chunkSize, onProgress, signal } = {}) {
+ // The same file twice at once would confuse the node, which keys its own
+ // upload state by name — and would race for the same destination.
+ if (this._uploaders.has(file.name)) {
+ throw new Error(`${file.name} is already being uploaded`);
+ }
const size = chunkSize || UPLOAD_CHUNK_SIZE;
const total = Math.max(1, Math.ceil(file.size / size));
let acked = 0;
@@ -544,7 +555,7 @@ class MeshBayTransport {
let failure = null;
const acks = [];
- this._onUploadAck = (msg) => {
+ this._uploaders.set(file.name, (msg) => {
if (msg.type === 'error') {
failure = new Error(msg.detail || 'Upload refused');
} else if (msg.stored_as) {
@@ -554,15 +565,17 @@ class MeshBayTransport {
if (onProgress) onProgress(Math.min(file.size, acked * size), file.size);
const waiter = acks.shift();
if (waiter) waiter();
- };
+ });
const nextAck = () => new Promise(r => acks.push(r));
try {
for (let i = 0; i < total; i++) {
+ if (signal && signal.aborted) throw _aborted();
// Backpressure: without it the whole file lands in the browser's send
// buffer in seconds and the progress bar becomes a work of fiction.
while (this._channel && this._channel.bufferedAmount > UPLOAD_BUFFER_HIGH) {
+ if (signal && signal.aborted) throw _aborted();
await new Promise(r => setTimeout(r, 20));
}
while (i - acked >= UPLOAD_WINDOW) {
@@ -587,7 +600,7 @@ class MeshBayTransport {
if (failure) throw failure;
}
} finally {
- this._onUploadAck = null;
+ this._uploaders.delete(file.name);
}
return stored || {};
}
@@ -772,9 +785,15 @@ class MeshBayTransport {
// While an upload is in flight the acks are its own, and there are many of
// them: they must not be handed to whatever request happens to be oldest in
// the pending map.
- if (this._onUploadAck
- && (msg.type === 'file_upload_ack' || msg.type === 'error')) {
- this._onUploadAck(msg);
+ if (msg.type === 'file_upload_ack' && this._uploaders.has(msg.filename)) {
+ this._uploaders.get(msg.filename)(msg);
+ return;
+ }
+ // An error carries no filename. With one upload running it is that
+ // upload's; with several there is no way to tell, so they all hear it and
+ // stop — which is the safe reading of an error on a shared channel.
+ if (msg.type === 'error' && this._uploaders.size) {
+ for (const handler of [...this._uploaders.values()]) handler(msg);
return;
}
if (msg.type === 'chat_msg' && this._onChat) {