aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/sw.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-08 13:20:29 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-08 13:20:29 +0200
commit3f2bb22586d3e1aef765149b555ccc8e174ce7eb (patch)
tree7acde4ff7857c5bc2e38b51f40015f7b41823922 /packages/meshbay-hub/src/meshbay_hub/static/sw.js
parent813d18424ec57963bb56e6f40824a2db0ccce50d (diff)
downloadmeshbay-3f2bb22586d3e1aef765149b555ccc8e174ce7eb.tar.gz
fix(hub): make the streamed download path reliable, and clean up after an abort
On Firefox and Safari the service worker is the only unbounded way to write a download to disk: neither has the File System Access API, and OPFS is not a substitute — measured on Firefox 154, its quota is exactly 10% of the volume's size (389,233,459 bytes of a 3,892,334,592-byte volume, refused to the byte), which a film exceeds. So when this path declines, a large download has nowhere left to go, which makes its reliability a correctness property. Four ways it declined, all of them avoidable: - it was registered inside the first click on Download, so that click paid install, activate and claim while somebody watched a button do nothing; - `_swReady` cached a null for the life of the page. One slow first click left the tab unable to stream anything again, curable only by a reload nobody knew to do. Only a successful controller is remembered now; - control was waited for with a 3 s cap. It is 15 s, and a page that is active but not controlled asks the worker to claim again (`mbdl-claim`) instead of declaring the path unavailable; - a missed navigation gave up at once. It gets a second attempt with a fresh id and iframe, the failed one torn down completely first. Also closes a MessagePort leaked per download, and gives the reason a name (`lastStreamFailure`) so a refusal can say what happened. The timeouts became parameters: the defaults are the production values, no caller passes any, and the tests do not spend a minute waiting. `openTarget` gets an unrelated but adjacent fix, in the same file: it creates the destination with `getFileHandle({create: true})`, so an empty file exists before the first byte, and `abort()` leaves the target untouched — every cancelled download left a 0-byte file behind, and since `freeName` avoids collisions, three cancels left film.mkv, film (2).mkv and film (3).mkv, all empty. Its `abort()` now removes the entry. Safe here and only here, because `freeName` guarantees the name was not taken: the `showSaveFilePicker` path must not do the same, where the person may have picked an existing file whose contents `abort()` correctly preserves. Verified by hand in Chrome. test_streamed_download_reliability.py runs the real module under Node against a stubbed browser — it fails if the null is cached again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HCGdheDLxGReuKHga3BtST
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/sw.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/sw.js8
1 files changed, 8 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/sw.js b/packages/meshbay-hub/src/meshbay_hub/static/sw.js
index 119687d..0dd87d8 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/sw.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/sw.js
@@ -24,6 +24,14 @@ self.addEventListener('activate', (event) => event.waitUntil(self.clients.claim(
self.addEventListener('message', (event) => {
const data = event.data || {};
+ // A page that loaded before any worker existed can miss the claim on
+ // activate. Rather than declare the streamed path unavailable — which on
+ // Firefox and Safari means the download cannot happen at all — the page asks
+ // for another claim and waits a moment longer.
+ if (data.type === 'mbdl-claim') {
+ event.waitUntil(self.clients.claim());
+ return;
+ }
if (data.type !== 'mbdl' || !data.id || !data.readable) return;
pending.set(data.id, {
readable: data.readable,