diff --git a/vehicle-map/index.html b/vehicle-map/index.html index e2768866..4822837c 100644 --- a/vehicle-map/index.html +++ b/vehicle-map/index.html @@ -8,7 +8,7 @@ - + - + diff --git a/vehicle-map/server.py b/vehicle-map/server.py index d248811f..7df0d764 100644 --- a/vehicle-map/server.py +++ b/vehicle-map/server.py @@ -18,6 +18,17 @@ from urllib.request import Request, urlopen ROOT = Path(__file__).resolve().parent + + +def _static_asset_version(): + """Content fingerprint used to bypass upstream static-asset caches on release.""" + digest = hashlib.sha256() + for filename in ("app.js", "styles.css"): + digest.update((ROOT / filename).read_bytes()) + return digest.hexdigest()[:16] + + +STATIC_ASSET_VERSION = _static_asset_version() HOST = os.getenv("VEHICLE_MAP_HOST", "0.0.0.0") PORT = int(os.getenv("VEHICLE_MAP_PORT", "20800")) OPEN_PLATFORM_BASE_URL = os.getenv("OPEN_PLATFORM_BASE_URL", "https://open.d.lnoneos.com").rstrip("/") @@ -293,6 +304,9 @@ class VehicleMapHandler(SimpleHTTPRequestHandler): def do_GET(self): path = urlparse(self.path).path + if path in {"/", "/index.html"}: + self._serve_index() + return if path == "/api/health": self._write_json(200, { "status": "ok", @@ -380,6 +394,21 @@ class VehicleMapHandler(SimpleHTTPRequestHandler): }) return False + def _serve_index(self): + try: + template = (ROOT / "index.html").read_text(encoding="utf-8") + body = template.replace("__ASSET_VERSION__", STATIC_ASSET_VERSION).encode("utf-8") + except OSError as exc: + self.log_error("index template unavailable: %s", exc) + self.send_error(500, "Map application is temporarily unavailable") + return + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Cache-Control", "no-cache") + self.end_headers() + self.wfile.write(body) + def end_headers(self): if self.path == "/" or self.path.split("?", 1)[0].endswith((".html", ".css", ".js", ".svg")): self.send_header("Cache-Control", "no-cache") diff --git a/vehicle-map/tests/test_server.py b/vehicle-map/tests/test_server.py index 3661bb50..1296319c 100644 --- a/vehicle-map/tests/test_server.py +++ b/vehicle-map/tests/test_server.py @@ -64,6 +64,12 @@ class DashboardTest(unittest.TestCase): self.assertNotIn("totalHydrogenKg", station) self.assertNotIn("internalOwner", station) + def test_static_asset_version_is_a_content_fingerprint(self): + self.assertEqual(len(server.STATIC_ASSET_VERSION), 16) + self.assertRegex(server.STATIC_ASSET_VERSION, r"^[0-9a-f]{16}$") + index_template = (Path(__file__).parents[1] / "index.html").read_text(encoding="utf-8") + self.assertEqual(index_template.count("__ASSET_VERSION__"), 2) + def test_signed_operations_session_requires_the_operations_scope(self): with patch.object(server, "LOCAL_ACCESS_CODE", "LN-map-test-code"), patch.object(server, "SESSION_SECRET", b"test-session-secret"), patch.object(server, "SESSION_TTL_SECONDS", 600): principal = server._authorizer().authorize_access_code("LN-map-test-code")