aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/playlists.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/playlists.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/playlists.js54
1 files changed, 50 insertions, 4 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/playlists.js b/packages/meshbay-hub/src/meshbay_hub/static/playlists.js
index 5aa501a..af359f3 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/playlists.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/playlists.js
@@ -30,6 +30,28 @@ import {
* what §7 refuses.
*/
+/**
+ * The largest sealed body this browser can actually send.
+ *
+ * Not a policy — a wire limit. A DataChannel `send()` throws when the frame is
+ * larger than the value the far end advertised, and the node's aiortc fixes
+ * that at 65 536 (`docs/playlists.md` §15.3). The node's own 1 MB body cap is
+ * therefore unreachable from here, and the same constraint is why uploads chunk
+ * at 48 KB.
+ *
+ * 62 KB leaves room for the frame around the bytes — the four-byte length
+ * prefix and the msgpack map that carries the kind, the revision and a request
+ * id. Measured, a thousand tracks seal to 53 276 bytes and each further track
+ * costs about 50, so the ceiling is near **1200 tracks in one playlist**
+ * (`test_playlist_store.py`, which fails if that stops being true).
+ *
+ * Checked *after* sealing rather than estimated before it: compression makes
+ * the length of a playlist a poor guide to the length of its blob, and a guess
+ * that is wrong in the permissive direction is a thrown exception in the middle
+ * of a sync.
+ */
+const MAX_BODY_BYTES = 62 * 1024;
+
// ── local identity ───────────────────────────────────────────────────────────
const DEVICE_KEY = 'meshbay_playlist_device';
@@ -535,7 +557,12 @@ async function saveQueueAsPlaylist(userId, name, entries) {
* be reconciled must never break whatever that connection was opened for.
*/
async function syncWith(transport, userId) {
- const result = { ok: false, pulled: 0, pushed: 0, reason: null };
+ const result = {
+ ok: false, pulled: 0, pushed: 0, reason: null,
+ // Named, not counted: "one playlist is too large" is only actionable if
+ // the reader is told which one.
+ tooLarge: [], failed: [],
+ };
if (!transport || !transport.connected) {
result.reason = 'offline';
return result;
@@ -634,11 +661,30 @@ async function syncWith(transport, userId) {
result.unreadable = true;
}
} else if (localRev > nodeRev && localRev > 0) {
+ // Was: one `catch {}` covering both of the cases below. A node that went
+ // away mid-sweep and a playlist that can never be sent are not the same
+ // event, and swallowing the second is the silent loss this whole design
+ // exists to prevent — the reader keeps adding tracks to a playlist that
+ // stopped leaving the browser, and nothing anywhere says so.
+ let sealed;
try {
- await transport.storeUserBlob(
- kind, localRev, await seal(local, kind, userId, key));
+ sealed = await seal(local, kind, userId, key);
+ } catch {
+ result.failed.push(p.name);
+ continue;
+ }
+ if (sealed.byteLength > MAX_BODY_BYTES) {
+ result.tooLarge.push(p.name);
+ continue;
+ }
+ try {
+ await transport.storeUserBlob(kind, localRev, sealed);
result.pushed += 1;
- } catch { /* a cap, or a node that went away mid-sweep */ }
+ } catch {
+ // A node that went away mid-sweep: the next sync pushes this, because
+ // the node's revision is still behind the local one.
+ result.failed.push(p.name);
+ }
}
}