openvibers/openvibe.surf AGENTS.md

AGENTS.md

main 5.6 KB · 95 lines · markdown

AGENTS.md — OpenVibe.Surf

Project overview

OpenVibe.Surf (https://openvibe.surf) is one room with three surfs:

  • Ride — CS-style surf maps played inside OpenVibe.Games embeds; the catalogue is proxied from Games.
  • Watch — lean-back channel surfing over OpenVibe.Live: live streams, clips and AI moments merged into
    one feed, plus in-memory watch-together parties over SSE.
  • Web — Surf with Vibe: a standing brief sent to OpenVibe.Actor so an agent browses for the person
    and returns a cited report.
  • Entry: server/index.js; app server/app.js; port 5110, service id surf; pages in
    server/views/index.js, server/render/layout.js; API in server/http/; Watch/Ride/Web clients in
    server/watch/, server/ride/, server/web/; auth in server/auth/.
  • No database, no Valkey, no Events. The only state is the 30 s Watch feed cache, the 60 s Ride
    cache, the SDK's JWKS cache and the in-memory watch parties. A restart ends every watch party.
  • Pages: server-rendered through the OpenVibe Frame (openvibe-shared/shell), readable without
    JavaScript; public/js/surf.js adds the TV (native HLS, else /vendor/hls.light.min.js only when
    needed), the Web form and the home timer. Own CSS in public/css; no bundler.

Who calls it, what it calls

Public reads (/api/v1/watch/feed, /api/v1/ride/maps, /api/v1/ride/maps/:id) need no token.
Signed-in writes use the person's httpOnly surf_at Network cookie.

  • OpenVibe.Live — the Watch feed source (live streams, clips, moments, DVR playlist), with each
    source given 2 s; a failed source is named in sources and makes the page partial, never fatal.
  • OpenVibe.Games — the Ride map catalogue and the embed origin
    (https://openvibe.games/surf/embed?map=<id>&embed=1); until Games' surf routes exist, Games answers
    404 and Surf serves an empty catalogue plus games: "not_ready".
  • OpenVibe.Actor — POST /api/v1/tasks with the person's own Bearer token, an Idempotency-Key and
    the standing brief; the task belongs to the person's allowance and task list. Actor's 4xx keeps its
    status and words; no answer is 502 actor.unavailable.
  • OpenVibe.Network — OAuth surf client (PKCE S256, state, server-side code exchange), JWKS and
    sign-in.
  • OpenVibe.Chat — only a validated room link the host pasted; Surf has no chat.

Frontend loading model

Every page is server-rendered with the shared Frame; sf- ids/classes are stable for scripts and tests.
A page for a signed-in person is private, no-store; a public page is public, max-age=60 with
Vary: Cookie. Interactive extras live in public/js/surf.js (TV, form, timer) and are progressive
enhancement only. The SSE route GET /watch/p/<id>/events needs proxy_buffering off, no gzip and a
1-hour read timeout in nginx.

Auth and security

OAuth 2 authorization code + PKCE S256 with the surf Network client; the session lives in httpOnly
cookies and no script can read or log it. The only place the token goes is the Authorization header
of a request to the configured Actor origin. A Web task must come from this site's own Origin (a
cross-site request could spend the visitor's allowance); /web/go also checks Sec-Fetch-Site; next
after sign-in is this site's own path only and control characters/backslashes are refused.

Watch-together: only the host may command, from this site's own Origin; a member's or another site's
command is 403. Party ids are unguessable; at most 200 parties and 50 members each.

Deploy

sudo ovhost deploy surf: checkout /opt/openvibe.surf, unit openvibe-surf.service on
127.0.0.1:5110, env /etc/openvibe/surf.env. nginx
deploy/nginx/openvibe.surf.conf (installed with ov-vhost-install): /api reads only,
/api/v1/web/ and /web/go the forms zone, /auth GET only, /auth/me the API budget,
/internal and /metrics 404. Readiness answers degraded (HTTP 200) when Network, Live or Games is
unreachable. Rollback with sudo ovhost rollback surf --to <sha>.

Commands and tests

npm ci
npm run dev                 # http://127.0.0.1:5110
npm test                    # every test/*.test.js against in-process stand-ins
npm test -- watch           # only matching files
npm test -- --strict        # a skipped file fails the run

test/run.js uses openvibe-shared/test-runner (60 s, serial); each file boots Surf against
in-process Network/Live/Games/Actor stand-ins, never the internet. Tests cover the merged feed and
partial sources, Ride fallbacks, the Actor task (Idempotency-Key, statuses), watch parties and SSE,
the pages, sign-in origin rules, discovery/agent twins, contrast and links.

Conventions and pitfalls

  • CommonJS, 4-space indent, single quotes, semicolons; problem+json for API errors; escaped,
    server-rendered HTML. No database and no schema: do not add a migration without a design change.
  • Nothing durable may be assumed. Parties, caches and tokens-in-flight die on restart; a feature
    that needs to survive one belongs in another service, not in Surf's memory.
  • Never weaken the origin checks on /api/v1/web/tasks, /web/go or a party command; they protect
    a person's Actor allowance and their party.
  • A Live/Games/Actor failure is named, not hidden: sources/partial, games: "not_ready" and
    502 actor.unavailable are the contract; never fabricate feed entries or map data.
  • The Web question never goes in a URL: /web/go posts it and 303s to the Actor task page.
  • Progressive enhancement: every page must work without public/js/surf.js; the HLS vendor file is
    loaded only when native HLS is missing.