Files
hermes-agent/hermes_cli/web_server_static_assets.py
Teknium dc39239614 fix(web-access): a missing or out-of-dist page icon is a 404, not the app page
The root icons /apple-touch-icon.png and /favicon.ico answer without sign-in, but
is_static_asset_path() only knew directory prefixes, so a missing icon (or one
symlinked outside the dist) fell through to _serve_index() and returned the SPA
shell as 200 text/html to an anonymous request. Treat both exact names as static
assets so a miss is a 404.

Refs #144648
2026-10-27 20:55:07 -07:00

40 lines
2.1 KiB
Python

"""Static asset lookups the SPA catch-all must not answer with ``index.html``.
The sign-in page (``dashboard_auth/login_page.py``) loads its brand fonts from ``/fonts/``. Those
files ship in the dashboard's ``web/public`` tree, which the dashboard build copies into its own
dist, but the Webapp serves ``apps/desktop/dist-webapp`` and has no ``fonts/`` dir. A font missing
from the served dist is read from the packaged dashboard dist (``HERMES_WEB_DIST`` / ``web_dist``,
which is all a Nix or wheel install ships, since those exclude ``web/``), then from a source
checkout's ``web/public``.
"""
from __future__ import annotations
from pathlib import Path
# Requests under these prefixes are files, never client-side routes, so a miss is a 404 rather
# than the SPA shell (a font answered with HTML renders in a fallback face, silently).
STATIC_ASSET_PREFIXES: tuple[str, ...] = ("assets/", "ds-assets/", "fonts/", "fonts-terminal/")
# Root page icons answer without sign-in, so a missing (or out-of-dist) icon must be a 404, not
# the SPA shell served anonymously.
STATIC_ASSET_FILES: frozenset[str] = frozenset({"apple-touch-icon.png", "favicon.ico"})
_PUBLIC_FONT_DIRS: tuple[str, ...] = ("fonts", "fonts-terminal")
_WEB_PUBLIC = Path(__file__).resolve().parent.parent / "web" / "public"
def is_static_asset_path(full_path: str) -> bool:
return full_path in STATIC_ASSET_FILES or full_path.startswith(STATIC_ASSET_PREFIXES)
def public_font_file(dist: Path, dashboard_dist: Path, full_path: str) -> Path | None:
"""The served dist's copy of ``fonts/<name>`` (or ``fonts-terminal/<name>``), else the packaged
dashboard dist's, else the ``web/public`` copy the dashboard build takes it from; ``None`` when
none has it."""
folder, _, name = full_path.partition("/")
if folder not in _PUBLIC_FONT_DIRS or not name:
return None
for root in (dist / folder, dashboard_dist / folder, _WEB_PUBLIC / folder):
candidate = root / name
if candidate.is_file() and candidate.resolve().is_relative_to(root.resolve()):
return candidate
return None