7cec3030c1
Move frontend sources to frontend/, build content-hashed assets at deploy, network-first SW for shell/JS, immutable cache for /assets/, version.json update banner, and Cache-Control middleware. Docs in docs/PWA-STRATEGY.md. Co-authored-by: Cursor <cursoragent@cursor.com>
43 lines
1.4 KiB
Python
43 lines
1.4 KiB
Python
from __future__ import annotations
|
|
|
|
from starlette.types import ASGIApp, Receive, Scope, Send
|
|
|
|
NO_CACHE = b"no-cache, must-revalidate"
|
|
NO_STORE = b"no-store"
|
|
IMMUTABLE = b"public, max-age=31536000, immutable"
|
|
|
|
|
|
def _cache_control_for_path(path: str) -> bytes | None:
|
|
if path in ("/sw.js", "/index.html", "/manifest.json"):
|
|
return NO_CACHE
|
|
if path == "/version.json":
|
|
return NO_STORE
|
|
if path.startswith("/assets/"):
|
|
return IMMUTABLE
|
|
if path.endswith((".js", ".css")) and not path.startswith("/assets/"):
|
|
return NO_CACHE
|
|
return None
|
|
|
|
|
|
class CacheControlMiddleware:
|
|
def __init__(self, app: ASGIApp) -> None:
|
|
self.app = app
|
|
|
|
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
if scope["type"] != "http":
|
|
await self.app(scope, receive, send)
|
|
return
|
|
|
|
path = scope.get("path", "")
|
|
cache_control = _cache_control_for_path(path)
|
|
|
|
async def send_wrapper(message: dict) -> None:
|
|
if message["type"] == "http.response.start" and cache_control is not None:
|
|
headers = list(message.get("headers", []))
|
|
headers = [(k, v) for k, v in headers if k.lower() != b"cache-control"]
|
|
headers.append((b"cache-control", cache_control))
|
|
message = {**message, "headers": headers}
|
|
await send(message)
|
|
|
|
await self.app(scope, receive, send_wrapper)
|