API Documentation

Documentation

E2E download investigation, 2026-09-16

Reproducer: node tests/e2e-browser-probe.mjs. Isolated HTTP fixture serves AES-GCM chunks to the production download service worker. Playwright saves the download and Node compares SHA-256 against generated plaintext.

Observed results:

  • Original Firefox: 3,221,225,609-byte input reported downloaded but only 1,164,183,192 bytes saved. Chromium saved all bytes with matching SHA-256.
  • Pull-based stream alone, retaining iframe, and byte stream alone did not prevent Firefox truncation.
  • Explicit fetch-event lifetime improved duration but still truncated.
  • 16,777,353 bytes with 4-second request latency and iframe retained: Firefox saved only 13,631,124 bytes. This also reproduces with small files.
  • With a page message to the service worker every second, Firefox downloaded 3,221,225,609 bytes with matching SHA-256: 39405a645b45edba1e32f75e54c603ec5d48666e617766cb8d4e2f0efcd0b162.
  • Bounded range retries recover a single HTTP 503 or socket disconnect on Firefox and Chromium (16,777,353 bytes, matching SHA-256).
  • Integrated shared download helper: Firefox and Chromium each saved all 3,221,225,609 bytes with the SHA-256 above.
  • Permanent 503: four attempts; Chromium cancels, Firefox’s download manager can remain pending. The page now reports failure explicitly on all three engines. A failed/partial download must be discarded, not treated as valid.

Implemented: page heartbeat until worker completion/error, before-unload warning, no iframe removal while streaming, pull-based byte stream, explicit worker lifetime, bounded retries and 60-second timeout per ciphertext range attempt. AES-GCM authentication failures are never retried or bypassed. The sandbox’s worker wrapper uses the same implementation as production.

Verified matrix (Playwright, Linux; WebKit is not a physical Safari test):

Case Firefox Chromium WebKit
3 GiB + 137 B download, real helper, SHA-256 pass pass pass (full round trip)
1 B, chunk boundary -1/exact/+1, two full chunks pass pass pass
16 MiB + 137 B; transient 503, disconnect, short body pass pass pass
Persistent 503, corrupt GCM tag, wrong Content-Range, 404: explicit UI error pass pass pass
Three simultaneous ranges including a chunk boundary; invalid range rejected pass pass pass
Actual uploader + download page, password/KDF/sentinel, 16 MiB + 137 B pass pass pass
Upload transient 503/disconnect: retry, final SHA-256 pass pass pass
Upload 403: rejected without finalization pass pass pass
Zero-byte upload explicitly rejected, never finalized pass pass pass
Download request stalled 60 seconds: timeout/retry, final SHA-256 pass pass pass
Slow download (>60 seconds), another page brought to foreground pass pass pass
Full 3 GiB + 137 B upload/download round trip pass pass pass
Initial header request HTTP 503: retry, final SHA-256 pass pass pass

The upload fixture runs the actual static/upclass.js, the download EJS view, and the actual download app/worker against a local mock chunk API. It checks reassembly length, wrong-password rejection, and downloaded SHA-256. It does not exercise production databases, reverse proxies, or cold storage.

Reproduction commands:

E2E_REAL_UI=1 node tests/e2e-browser-probe.mjs
E2E_REAL_UI=1 E2E_SIZE=3221225609 E2E_BROWSERS=firefox,chromium node tests/e2e-browser-probe.mjs
E2E_REAL_UI=1 E2E_FAULT=permanent node tests/e2e-browser-probe.mjs
E2E_REAL_UI=1 E2E_RANGES=1 node tests/e2e-browser-probe.mjs
E2E_REAL_UI=1 E2E_BACKGROUND=1 E2E_DELAY=4000 node tests/e2e-browser-probe.mjs
node tests/e2e-upload-browser-probe.mjs
E2E_UPLOAD_FAULT=disconnect node tests/e2e-upload-browser-probe.mjs
E2E_SIZE=3221225609 E2E_BROWSERS=firefox node tests/e2e-upload-browser-probe.mjs

Fixtures exit nonzero on failed assertions and retain generated artifacts in the printed /tmp/ax-e2e-* directories. Large upload tests require substantial temporary disk space. Run large tests sequentially on memory-limited machines.

Manual Safari/mobile/sleep tests are outside this Linux fixture. Bringing another headless page to the foreground is not equivalent to OS sleep or mobile app suspension. Closing/suspending the page is not supported during an active download; the page explicitly warns users to keep it open. Firefox may retain a pending browser download after an unrecoverable error even though the page reports failure; cancel/discard that partial download.

The new shared script and versioned worker wrappers were checked via HTTPS on file.ax. Views are not cached by the app, and these static/template changes do not require a backend restart. Reload the E2E page to pick up the updated worker. Custom-domain views load the shared page helper from mainBaseUrl; the worker and ciphertext remain same-origin, preserving the existing runtime allowlist.