diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-08 13:20:29 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-08 13:20:29 +0200 |
| commit | 3f2bb22586d3e1aef765149b555ccc8e174ce7eb (patch) | |
| tree | 7acde4ff7857c5bc2e38b51f40015f7b41823922 /packages/meshbay-hub/src/meshbay_hub/static/app.js | |
| parent | 813d18424ec57963bb56e6f40824a2db0ccce50d (diff) | |
| download | meshbay-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/app.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/app.js | 9 |
1 files changed, 9 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index c2858f4..9d64292 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -6,6 +6,7 @@ import { t, getLocale, setLocale, initLocale, LOCALES } from './i18n.js'; import { ZipStream, entriesUnder } from './zipstream.js'; import { transfers, formatSpeed } from './transfers.js'; import * as platform from './platform.js'; +import * as downloads from './downloads.js'; import { Icon } from './icon.js'; import { formatSize } from './file-utils.js'; import { @@ -941,6 +942,14 @@ const trayLabels = () => ({ // falls back to English rather than rejecting, so this cannot strand the page. const mount = () => { render(html`<${App} />`, document.getElementById('app')); + // Get the download worker registered and this page under its control now, + // rather than inside the first click on Download. On Firefox and Safari it is + // the only unbounded way to write a file to disk, and it used to be + // registered lazily — so the first download of a session paid install, + // activate and claim while somebody watched, and a claim that missed its + // budget sent the file to a path that cannot hold a film. Fire-and-forget: + // nothing renders differently for it, and a failure is retried on demand. + downloads.primeServiceWorker(); // After the catalogue, so the labels are in the right language. A no-op in a // browser and on macOS. A language change reloads the page, which comes back // through here, so nothing else has to watch for it. |