diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/app.py')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/app.py | 31 |
1 files changed, 30 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/app.py b/packages/meshbay-hub/src/meshbay_hub/app.py index 6240785..423ad03 100644 --- a/packages/meshbay-hub/src/meshbay_hub/app.py +++ b/packages/meshbay-hub/src/meshbay_hub/app.py @@ -35,7 +35,7 @@ from meshbay_hub.api.relay import router as relay_router from meshbay_hub.api.signaling import router as signaling_router from meshbay_hub.api.admin import router as admin_router from meshbay_hub.api.notifications import router as notifications_router -from meshbay_hub.api.webapp import router as webapp_router, STATIC_DIR +from meshbay_hub.api.webapp import router as webapp_router, STATIC_DIR, ASSET_V from meshbay_hub.api.middleware import limiter @@ -145,6 +145,35 @@ def create_app(cfg: HubConfig | None = None) -> FastAPI: response.headers.setdefault("Cache-Control", "no-cache") return response + class VersionedStatics(StaticFiles): + """The same files under a URL that changes when they do. + + `no-cache` above only binds a browser that asks. One that cached the SPA + before that header existed applies heuristic freshness and does not ask + at all, so it runs an old player against a new node — a fix that is + deployed, served, and not running, which looks exactly like a fix that + does not work. Measured: a phone kept a player without the read-ahead + bound and filled the browser's buffer ceiling at 106 MB, while the + server had been serving the bounded one for an hour. + + Serving the graph under /a/<fingerprint>/ solves it for every file at + once, because relative imports inherit the prefix: `app.js` reaching for + `./i18n.js` gets the version it was built against, and never a mixture. + The URL changes with the content, so these may be cached hard. + """ + + async def get_response(self, path, scope): + response = await super().get_response(path, scope) + response.headers["Cache-Control"] = ( + "public, max-age=31536000, immutable") + return response + + # Before "/", which would otherwise swallow it. + app.mount(f"/a/{ASSET_V}", VersionedStatics(directory=STATIC_DIR), + name="static-versioned") + # Still served unversioned: sw.js must stay at the root or its scope stops + # covering the pages it intercepts downloads for, and old bookmarks of + # /style.css and the like should not 404. app.mount("/", RevalidatingStatics(directory=STATIC_DIR), name="static") return app |