aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/downloads.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/downloads.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/downloads.js81
1 files changed, 78 insertions, 3 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/downloads.js b/packages/meshbay-hub/src/meshbay_hub/static/downloads.js
index a71f289..7f48fed 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/downloads.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/downloads.js
@@ -171,8 +171,83 @@ export async function openTarget(filename) {
}
/**
- * Below this, a download with no granted folder is collected in memory and
- * handed to the browser, which saves it without asking. Above it that would
- * mean holding gigabytes in a tab, so it is worth one Save As dialog instead.
+ * Below this, a download with no granted folder and no service worker is
+ * collected in memory and handed to the browser. Above it that would mean
+ * holding gigabytes in a tab, so it is worth one Save As dialog instead.
*/
export const BLOB_LIMIT = 512 * 1024 * 1024;
+
+// ── Streaming to disk without the File System Access API ────────────────────
+
+const SW_PATH = '/sw.js';
+let _swReady = null;
+
+export const STREAMS_VIA_SW = typeof window !== 'undefined'
+ && 'serviceWorker' in navigator
+ && typeof TransformStream === 'function'
+ && window.isSecureContext;
+
+async function serviceWorker() {
+ if (!STREAMS_VIA_SW) return null;
+ if (!_swReady) {
+ _swReady = navigator.serviceWorker.register(SW_PATH, { scope: '/' })
+ .then(() => navigator.serviceWorker.ready)
+ .then(reg => reg.active || navigator.serviceWorker.controller)
+ .catch(err => {
+ console.warn('[MeshBay] service worker unavailable:', err.message);
+ return null;
+ });
+ }
+ return _swReady;
+}
+
+/**
+ * A sink the browser writes to disk, for Firefox and anything else without the
+ * File System Access API.
+ *
+ * The page keeps the writable half of a stream and gives the readable half to
+ * the service worker, which answers a made-up URL with it. Navigating a hidden
+ * iframe there turns it into an ordinary download: written as it arrives, with
+ * the browser's own progress, and nothing held in the tab. Backpressure is
+ * real — `writer.write()` waits when the browser is behind.
+ *
+ * Returns {writable, name} shaped like the File System Access one, or null if
+ * this browser cannot do it either.
+ */
+export async function openStreamedDownload(filename, size = 0) {
+ const worker = await serviceWorker();
+ if (!worker) return null;
+
+ const id = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
+ const { readable, writable } = new TransformStream();
+
+ try {
+ worker.postMessage({ type: 'mbdl', id, filename, size, readable }, [readable]);
+ } catch (err) {
+ // Transferable streams are what makes the backpressure work; without them
+ // this would be a memory buffer wearing a stream's clothes.
+ console.warn('[MeshBay] streams cannot be transferred here:', err.message);
+ return null;
+ }
+
+ const frame = document.createElement('iframe');
+ frame.hidden = true;
+ frame.src = `/_mbdl/${id}`;
+ document.body.appendChild(frame);
+
+ const writer = writable.getWriter();
+ return {
+ name: filename,
+ writable: {
+ write: (bytes) => writer.write(bytes),
+ close: async () => {
+ await writer.close();
+ setTimeout(() => frame.remove(), 2000);
+ },
+ abort: async (reason) => {
+ try { await writer.abort(reason); } catch { /* already gone */ }
+ frame.remove();
+ },
+ },
+ };
+}