diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-16 20:57:52 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-16 20:57:52 +0200 |
| commit | 5dea19d9950518887be7eb14696f600ee023dbbd (patch) | |
| tree | 024b5f946f737713b175f33b27426f9d9a74cb5a /packages/meshbay-hub/tests/test_hub_api.py | |
| parent | 373f83236816aaff536dfa2f9589801acac44012 (diff) | |
| download | meshbay-5dea19d9950518887be7eb14696f600ee023dbbd.tar.gz | |
fix(hub): serve the SPA under a fingerprint of what it is
`Cache-Control: no-cache` requires a browser to revalidate, but it only binds
one that asks. A browser that cached app.js before that header existed applies
heuristic freshness instead — a fraction of the file's age, which for a file
dated weeks ago is days — and never asks. It then runs an old player against a
new node.
That cost most of a session. A phone kept a player without the read-ahead bound
and filled the browser's buffer ceiling at 106 MB, the exact symptom the bound
had been written to remove, for an hour after the bounded player went live. A
fix that is written, tested, deployed and served, and still not what runs, is
indistinguishable from a fix that does not work.
The whole module graph now lives under `/a/<content-hash>/`. A path prefix
rather than a query string, because relative imports inherit it: `app.js`
reaching for `./i18n.js` gets the build it was written against, and never a
mixture of two — which does not render a stale page, it fails to link. The URL
changes with the content, so those may be cached hard.
`sw.js` stays at the root. Its scope is its own path, and under the prefix it
would no longer control the pages whose downloads it exists to intercept.
Diffstat (limited to 'packages/meshbay-hub/tests/test_hub_api.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_hub_api.py | 19 |
1 files changed, 13 insertions, 6 deletions
diff --git a/packages/meshbay-hub/tests/test_hub_api.py b/packages/meshbay-hub/tests/test_hub_api.py index 5314b93..724c8ec 100644 --- a/packages/meshbay-hub/tests/test_hub_api.py +++ b/packages/meshbay-hub/tests/test_hub_api.py @@ -722,19 +722,26 @@ async def test_ip_log_cleanup(app): @pytest.mark.asyncio async def test_webapp_html_includes_scripts(client): - """SPA HTML shell includes all required script tags.""" + """SPA HTML shell includes all required script tags. + + The paths carry a build fingerprint (`/a/<hash>/app.js`) so that a browser + cannot serve an older build out of its own cache — see + test_asset_versioning.py. What this test still owns is that every piece is + referenced at all, and in an order where each one's dependencies are + already loaded. + """ + from meshbay_hub.api.webapp import ASSET_V + r = await client.get("/") assert r.status_code == 200 html = r.text assert "<!DOCTYPE html>" in html assert '<div id="app">' in html - assert 'src="/keyderive.js"' in html - assert 'src="/crypto.js"' in html - assert 'src="/transport.js"' in html - assert 'src="/app.js"' in html + for asset in ("keyderive.js", "crypto.js", "transport.js", "app.js"): + assert f'src="/a/{ASSET_V}/{asset}"' in html, f"{asset} is not loaded" assert 'type="module"' in html assert 'rel="stylesheet"' in html - assert 'href="/style.css"' in html + assert f'href="/a/{ASSET_V}/style.css"' in html # ── Password split (T1 fix) ───────────────────────────────────────────────── |