feat(share): a link's address says what it opens

A video stays on /watch/, anything else is given out on /file/ and a folder on /folder/. The token
is the whole key, so every path opens every link and the links already sent keep working. The new
paths are kept out of search indexes like /watch/, boot the player alone, and the unfurl card calls a
file a file.
This commit is contained in:
2026-11-04 19:10:09 +01:00
parent 2bdf1ec76f
commit 4c09a22add
12 changed files with 233 additions and 26 deletions
+10 -4
View File
@@ -368,6 +368,10 @@ INDEX_HTML = STATIC_DIR / "index.html"
# A public share link's own page: the player alone, without the app's bundle (frontend/watch.html).
WATCH_HTML = STATIC_DIR / "watch.html"
INDEX_HEADERS = {"Cache-Control": "no-cache"}
# The paths a share link opens on, by what it opens (`links.share_path`). Any of them opens any link.
# A new one also goes in frontend/public/robots.txt, the Vite dev entry and WatchPage's token parsing
# (watchEntry.contract.test.ts).
SHARE_PREFIXES = ("watch/", "file/", "folder/")
app.mount(
"/assets",
StaticFiles(directory=STATIC_DIR / "assets", check_dir=False),
@@ -381,8 +385,10 @@ async def index() -> FileResponse:
@app.get("/watch/{token}")
async def watch_page(token: str):
"""Serve the public player page for a share link, with per-video Open Graph tags injected so the
@app.get("/file/{token}")
@app.get("/folder/{token}")
async def share_page(token: str):
"""Serve the public page of a share link, with per-link Open Graph tags injected so the
link unfurls richly (title/channel/thumbnail) in Messenger/social crawlers. A real browser
ignores the extra tags and boots WatchPage; invalid/expired/password links fall back to the
generic card."""
@@ -409,8 +415,8 @@ async def spa_fallback(full_path: str) -> FileResponse:
# Real SPA-root assets (welcome images, favicon, robots.txt) rarely change and have
# stable names, so a moderate cache is safe and satisfies the cache-lifetime audit.
return FileResponse(candidate, headers={"Cache-Control": "public, max-age=604800"})
# A /watch/ path the route above does not match (a trailing segment, no token) is still the
# A share path the route above does not match (a trailing segment, no token) is still the
# player's: WatchPage reads the token off the path and says when a link is gone.
if full_path.startswith("watch/"):
if full_path.startswith(SHARE_PREFIXES):
return FileResponse(WATCH_HTML, headers=INDEX_HEADERS)
return FileResponse(INDEX_HTML, headers=INDEX_HEADERS)