Skip to content

feat(huds): import broadcast HUDs from the panel - #407

Merged
lukepolo merged 4 commits into
5stackgg:mainfrom
LCrew:pr/broadcast-huds
Sep 28, 2026
Merged

lukepolo merged 4 commits into
5stackgg:mainfrom
LCrew:pr/broadcast-huds

Conversation

@LCrew

@LCrew LCrew commented Sep 9, 2026

Copy link
Copy Markdown

The HUD burned into live streams, demo playback and batch highlights was a closed two-value choice. resolveHudMode read default_hud_mode, stamped HUD_MODE onto the pod, and the spec-server forwarded it as a ?variant= on the one bundle JTs Hud Manager ships -- horizontal and vertical were never separate HUDs, they are two layouts of default.

This adds the dimension that was missing: a hudId. JTs Hud Manager already knows how to hold more than one (~/jthm-huds, POST /api/huds/upload-zip, and an /api/overlay/start that has always taken a hudId the pod never varied), so a broadcast_huds row is an entry in that library and the two shipped layouts are seeded as rows -- the pickers become "list the library" without the familiar options disappearing.

The archive is stored whole and never extracted here: JTHud's own upload-zip does that inside the pod, signature verification included, and a second extractor would only be a second thing to keep in agreement with it. What we do own is refusing an archive it would mishandle -- its extract writes entries with path.join and no traversal guard, and takes the hud id straight out of the archive when hud.json sits one level deep. The panel is the only thing that ever uploads to it, so the panel is where a hostile bundle has to be stopped.

An imported HUD carries no variant of its own, and the empty string is the answer rather than a fallback layout: handing an imported bundle ?variant=horizontal names a layout its hud.json very likely does not declare. HUD_MODE keeps carrying a real layout, since an older game-streamer image reads that one.

GET /huds/:slug/bundle.zip is deliberately unauthenticated -- JTs Hud Manager inside the pod holds no 5stack session, and it serves nothing but an archive an administrator uploaded. /hud-data/:matchId stays cluster-internal as before.

Nothing regresses on an older game-streamer image: HUD_MODE is still sent carrying the variant, and setHudMode keeps its signature, with the three old layout names mapping onto the seeded builtin slugs.

Needs the matching game-streamer change to load an imported bundle, and the /huds ingress path in 5stack-panel for the browser-side import.

LCrew and others added 4 commits September 28, 2026 13:30
The HUD burned into live streams, demo playback and batch highlights was a
closed two-value choice. resolveHudMode read `default_hud_mode`, stamped
HUD_MODE onto the pod, and the spec-server forwarded it as a `?variant=` on the
one bundle JTs Hud Manager ships -- horizontal and vertical were never separate
HUDs, they are two layouts of `default`.

This adds the dimension that was missing: a hudId. JTs Hud Manager already knows
how to hold more than one (~/jthm-huds, POST /api/huds/upload-zip, and an
/api/overlay/start that has always taken a hudId the pod never varied), so a
broadcast_huds row is an entry in that library and the two shipped layouts are
seeded as rows -- the pickers become "list the library" without the familiar
options disappearing.

The archive is stored whole and never extracted here: JTHud's own upload-zip
does that inside the pod, signature verification included, and a second
extractor would only be a second thing to keep in agreement with it. What we do
own is refusing an archive it would mishandle -- its extract writes entries with
path.join and no traversal guard, and takes the hud id straight out of the
archive when hud.json sits one level deep. The panel is the only thing that ever
uploads to it, so the panel is where a hostile bundle has to be stopped.

An imported HUD carries no variant of its own, and the empty string is the
answer rather than a fallback layout: handing an imported bundle
`?variant=horizontal` names a layout its hud.json very likely does not declare.
HUD_MODE keeps carrying a real layout, since an older game-streamer image reads
that one.

GET /huds/:slug/bundle.zip is deliberately unauthenticated -- JTs Hud Manager
inside the pod holds no 5stack session, and it serves nothing but an archive an
administrator uploaded. /hud-data/:matchId stays cluster-internal as before.

Nothing regresses on an older game-streamer image: HUD_MODE is still sent
carrying the variant, and setHudMode keeps its signature, with the three old
layout names mapping onto the seeded builtin slugs.

Needs the matching game-streamer change to load an imported bundle, and the
/huds ingress path in 5stack-panel for the browser-side import.
- renumber the migration to 1887000000200; 1887000000000 is taken on main
- demoControl always resolves hud-mode server-side instead of forwarding
  the client body, which could hand the pod any bundleUrl to install
- the pod gets a presigned S3 URL plus HUD_SLUG; drop the unauthenticated
  GET /huds/:slug/bundle.zip route
- jthud_id for a root-level bundle is its slug, the filename the pod posts
- updated_at trigger in hasura/triggers
- s3.remove() returns a boolean, it never throws
- move slug normalization onto BroadcastHudsService, braces on ifs
- strip narrating comments, restore the web-push comment placement
- broadcast_huds gains preview and page_url; the migration moves to
  1887000000300 so stacks that already applied 200 pick up the columns
- POST /huds/:slug/page stores the link and takes the page's og:image (or
  a direct image link) as the preview, resized to 1280x720 webp
- the fetch only follows https to public addresses, checked at connect
  time, with redirect, size and time limits
- the importer renders preview.* / screenshot.* from the bundle root
@lukepolo
lukepolo merged commit 30aeae7 into 5stackgg:main Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants