Skip to content

feat: reach a host from a packaged Samsung TV page, and build the package - #42

Merged
enricobuehler merged 4 commits into
mainfrom
tizen/t2-tunnel-and-packaging
Oct 2, 2026
Merged

enricobuehler merged 4 commits into
mainfrom
tizen/t2-tunnel-and-packaging

Conversation

@enricobuehler

Copy link
Copy Markdown
Contributor

T2 of design/tizen-client-implementation-plan.md (planning repo, main @ 6f6c989): the client half of §4, the tizen build mode, the lifecycle and the two platform gates. The host half (T1: the plain-HTTP bootstrap and the /mgmt tunnel) is a monorepo PR; this one runs against it.

What changed

@punktfunk/stream

  • platform.ts: packaged() (a location that is not http(s):) and tizen() (window.tizen), never the user agent. deviceName() moves here and names a set Samsung <model>. tizenInfo() for the log header, exitApp() for T3.
  • tunnel.ts: tunnelFetch(url, hash), a fetch over a WebTransport session to /mgmt, pinned by serverCertificateHashes with allowPooling: false. One request per bidirectional stream, u32 BE head length + JSON {m, p, h} + body until FIN; the reply {s, h} + body streamed into a real Response. Sixteen in flight, the rest queue; reopens after the host's idle close; rejects with a TypeError as fetch does. Its own session, not the glue's.
  • pf-connect.ts: HostTarget gains tunnel; bootstrapUrl (plain HTTP on the management port at the plane's address), bootstrap (one GET: ok with the plane, no-plane on the host's own JSON 404, else unreachable), reachTarget for the probes.
  • engine.ts: a tunnel target bootstraps instead of reachWhy + fetchPlane, keeps the attestation check unchanged, then opens the tunnel and hands it to Host. blocked cannot happen on that path; a plane-off host gets its own sentence.
  • host.ts: connection({fetch}) and deviceKey({fetch}) take the injected fetch; art() and the posts already go through conn.fetch.
  • video.ts: no AV1 offered on Tizen (its WebCodecs says yes to everything; a hint, not a measurement). webgpuUsable() already reads a null adapter as no, and createPlane falls through to WebGL2 on the throw VideoSurfaceWebGPU.create makes for one.
  • logs.ts: the sent log's header carries model, Tizen version, firmware and Chromium major on a set.

punktfunk-web

  • targetOf returns a tunnel target on a packaged page; the reach probes follow. pickUi starts the console on a packaged page. visibilitychange → hidden leaves the stream; a frame-loop gap over 5 s is read as standby and does the same.
  • vite build --mode tizen: dist-tizen/, no source maps, config.xml (version from git describe, numeric part) and icon.png emitted beside the page, $WEBAPIS/webapis/webapis.js on the page. npm run build:tizen zips it into punktfunk-tizen-<version>.wgt (unsigned; tools/wgt.mjs is a dependency-free zip writer). tools/tizen-icon.mjs renders the 512×423 icon from the brand mark.
  • apps/web/tizen/config.xml per plan §6: use.game.mode, the four public privileges, <access origin="*">, no allow-navigation. Package id punktfunk0, fixed from here on.
  • _e2e.html?tunnel=1&host=<address> drives the tunnel route from a desktop browser: that is T1's gate.

Verified here

  • npm run check and npm test for @punktfunk/stream: 41 tests pass (9 new, tunnel.test.ts, against a fake session: framing, headers, body, 204, refusals, reconnect, the in-flight cap).
  • tsc --noEmit on apps/web (with a shim for @unom/ui, which this environment cannot fetch).
  • tools/wgt.mjs on a stand-in dist-tizen/: a valid zip with config.xml at the root.
  • Not run here: the wasm build (no emsdk) and the Tizen package on a set.

Gate (open)

On the monitor: the packaged app pairs with .21 by Request access and lists the library and covers. Needs a host build that includes T1 and a session on the Mac Studio. Not done in this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AMdVnA5hEykPfKrc5UByY7


Generated by Claude Code

Enrico Bühler added 4 commits October 2, 2026 22:21
A packaged page on a Samsung TV is told apart by what the runtime
installs — `window.tizen`, a location that is not http(s) — never by the
user agent. From that: the device pairs as "Samsung <model>", AV1 is not
offered (the set says yes to every codec string, which is a hint and not a
measurement), and a sent log names the model, Tizen version, firmware and
Chromium behind it.
A packaged page cannot fetch the host: nothing from file:// completes a
TLS connection to a self-signed certificate, and nobody accepts one on a
TV. The plane it can dial, pinned by hash. So a target with `tunnel` reads
the plane's hash over plain HTTP on the management port (the host's
bootstrap route), checks the attestation as before, and then rides the
plane's /mgmt session for every call: one request per bidirectional
stream, a length-prefixed JSON head and the body until FIN.

`tunnelFetch` is a fetch the SDK takes unchanged, so the connection and
the device key get it injected and nothing above them knows the API
moved. Sixteen requests in flight, the rest queue; a session the host
closes idle is reopened by the next call.
A packaged page has one way to a host, the plane's tunnel, so every
target it builds says so and its reach probes use the bootstrap. It
starts in console mode: an app on a TV, held by a remote, with no
address bar to ask. The set can put it behind Home or into standby, and
a stream into a dark panel ends: hidden leaves the stream (the host
keeps the game, the library offers Resume), and a frame loop that skips
five seconds is read as the set having slept.

`_e2e.html?tunnel=1&host=<address>` drives the same route from a desktop
browser, which is how the host side is proven without a set.
`vite build --mode tizen` writes dist-tizen/: the same page with no
source maps, the widget's config.xml at the build's numeric version, the
icon, and Samsung's webapis.js on the page. `npm run build:tizen` then
zips it into punktfunk-tizen-<version>.wgt, unsigned: a set only
installs a package signed for its own DUID, so each owner signs their
copy, and CI needs no Tizen SDK.

The manifest asks for game mode and the four public privileges the page
needs, and no allow-navigation, which would switch the runtime to a CSP
that blocks the page's inline styles.
@enricobuehler
enricobuehler merged commit 372a6bb into main Oct 2, 2026
7 checks passed
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.

1 participant