Capture the current unreleased K1 connection, recovery, lifecycle, viewer, and test work as a single known-bad baseline for subsequent fixes.
50 lines
1.6 KiB
Python
50 lines
1.6 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
from fastapi.staticfiles import StaticFiles
|
|
from starlette.responses import Response
|
|
from starlette.types import Scope
|
|
|
|
HTML_NO_STORE = "no-store"
|
|
HASHED_ASSET_IMMUTABLE = "public, max-age=31536000, immutable"
|
|
|
|
_MODULE_SCRIPT = re.compile(
|
|
r'<script\b[^>]*\bsrc=["\'](?P<src>/assets/[^"\']+)["\'][^>]*>',
|
|
re.IGNORECASE,
|
|
)
|
|
_HASHED_ASSET = re.compile(
|
|
r"^assets/(?:.+)-[A-Za-z0-9_-]{8,}\.[A-Za-z0-9]+$",
|
|
)
|
|
|
|
|
|
def frontend_build_id(frontend_root: Path) -> str | None:
|
|
"""Return the exact content-hashed module loaded by the current index."""
|
|
|
|
try:
|
|
index = (frontend_root / "index.html").read_text(encoding="utf-8")
|
|
except OSError:
|
|
return None
|
|
for match in _MODULE_SCRIPT.finditer(index):
|
|
source = match.group("src")
|
|
if _HASHED_ASSET.fullmatch(source.removeprefix("/")):
|
|
return source
|
|
return None
|
|
|
|
|
|
class ControlStationStaticFiles(StaticFiles):
|
|
"""Serve the SPA shell fresh while retaining immutable hashed assets."""
|
|
|
|
async def get_response(self, path: str, scope: Scope) -> Response:
|
|
response = await super().get_response(path, scope)
|
|
if response.status_code >= 400:
|
|
return response
|
|
content_type = response.headers.get("content-type", "").lower()
|
|
normalized = path.lstrip("/")
|
|
if content_type.startswith("text/html"):
|
|
response.headers["Cache-Control"] = HTML_NO_STORE
|
|
elif _HASHED_ASSET.fullmatch(normalized):
|
|
response.headers["Cache-Control"] = HASHED_ASSET_IMMUTABLE
|
|
return response
|