openvibers/openvibe.surf AGENTS.md
AGENTS.md
| 1 | # AGENTS.md — OpenVibe.Surf |
| 2 | |
| 3 | ## Project overview |
| 4 | |
| 5 | OpenVibe.Surf (`https://openvibe.surf`) is one room with three surfs: |
| 6 | |
| 7 | - **Ride** — CS-style surf maps played inside OpenVibe.Games embeds; the catalogue is proxied from Games. |
| 8 | - **Watch** — lean-back channel surfing over OpenVibe.Live: live streams, clips and AI moments merged into |
| 9 | one feed, plus in-memory watch-together parties over SSE. |
| 10 | - **Web** — *Surf with Vibe*: a standing brief sent to OpenVibe.Actor so an agent browses for the person |
| 11 | and returns a cited report. |
| 12 | |
| 13 | - **Entry:** `server/index.js`; app `server/app.js`; port `5110`, service id `surf`; pages in |
| 14 | `server/views/index.js`, `server/render/layout.js`; API in `server/http/`; Watch/Ride/Web clients in |
| 15 | `server/watch/`, `server/ride/`, `server/web/`; auth in `server/auth/`. |
| 16 | - **No database, no Valkey, no Events.** The only state is the 30 s Watch feed cache, the 60 s Ride |
| 17 | cache, the SDK's JWKS cache and the in-memory watch parties. **A restart ends every watch party.** |
| 18 | - **Pages:** server-rendered through the OpenVibe Frame (`openvibe-shared/shell`), readable without |
| 19 | JavaScript; `public/js/surf.js` adds the TV (native HLS, else `/vendor/hls.light.min.js` only when |
| 20 | needed), the Web form and the home timer. Own CSS in `public/css`; no bundler. |
| 21 | |
| 22 | ## Who calls it, what it calls |
| 23 | |
| 24 | Public reads (`/api/v1/watch/feed`, `/api/v1/ride/maps`, `/api/v1/ride/maps/:id`) need no token. |
| 25 | Signed-in writes use the person's httpOnly `surf_at` Network cookie. |
| 26 | |
| 27 | - **OpenVibe.Live** — the Watch feed source (live streams, clips, moments, DVR playlist), with each |
| 28 | source given 2 s; a failed source is named in `sources` and makes the page `partial`, never fatal. |
| 29 | - **OpenVibe.Games** — the Ride map catalogue and the embed origin |
| 30 | (`https://openvibe.games/surf/embed?map=<id>&embed=1`); until Games' surf routes exist, Games answers |
| 31 | 404 and Surf serves an empty catalogue plus `games: "not_ready"`. |
| 32 | - **OpenVibe.Actor** — `POST /api/v1/tasks` with the person's own Bearer token, an `Idempotency-Key` and |
| 33 | the standing brief; the task belongs to the person's allowance and task list. Actor's 4xx keeps its |
| 34 | status and words; no answer is `502 actor.unavailable`. |
| 35 | - **OpenVibe.Network** — OAuth `surf` client (PKCE S256, state, server-side code exchange), JWKS and |
| 36 | sign-in. |
| 37 | - **OpenVibe.Chat** — only a validated room link the host pasted; Surf has no chat. |
| 38 | |
| 39 | ## Frontend loading model |
| 40 | |
| 41 | Every page is server-rendered with the shared Frame; `sf-` ids/classes are stable for scripts and tests. |
| 42 | A page for a signed-in person is `private, no-store`; a public page is `public, max-age=60` with |
| 43 | `Vary: Cookie`. Interactive extras live in `public/js/surf.js` (TV, form, timer) and are progressive |
| 44 | enhancement only. The SSE route `GET /watch/p/<id>/events` needs `proxy_buffering off`, no gzip and a |
| 45 | 1-hour read timeout in nginx. |
| 46 | |
| 47 | ## Auth and security |
| 48 | |
| 49 | OAuth 2 authorization code + PKCE S256 with the `surf` Network client; the session lives in httpOnly |
| 50 | cookies and no script can read or log it. The only place the token goes is the `Authorization` header |
| 51 | of a request to the configured Actor origin. A Web task must come from this site's own `Origin` (a |
| 52 | cross-site request could spend the visitor's allowance); `/web/go` also checks `Sec-Fetch-Site`; `next` |
| 53 | after sign-in is this site's own path only and control characters/backslashes are refused. |
| 54 | |
| 55 | Watch-together: only the host may command, from this site's own Origin; a member's or another site's |
| 56 | command is 403. Party ids are unguessable; at most 200 parties and 50 members each. |
| 57 | |
| 58 | ## Deploy |
| 59 | |
| 60 | `sudo ovhost deploy surf`: checkout `/opt/openvibe.surf`, unit `openvibe-surf.service` on |
| 61 | `127.0.0.1:5110`, env `/etc/openvibe/surf.env`. nginx |
| 62 | `deploy/nginx/openvibe.surf.conf` (installed with `ov-vhost-install`): `/api` reads only, |
| 63 | `/api/v1/web/` and `/web/go` the forms zone, `/auth` GET only, `/auth/me` the API budget, |
| 64 | `/internal` and `/metrics` 404. Readiness answers `degraded` (HTTP 200) when Network, Live or Games is |
| 65 | unreachable. Rollback with `sudo ovhost rollback surf --to <sha>`. |
| 66 | |
| 67 | ## Commands and tests |
| 68 | |
| 69 | ```bash |
| 70 | npm ci |
| 71 | npm run dev # http://127.0.0.1:5110 |
| 72 | npm test # every test/*.test.js against in-process stand-ins |
| 73 | npm test -- watch # only matching files |
| 74 | npm test -- --strict # a skipped file fails the run |
| 75 | ``` |
| 76 | |
| 77 | `test/run.js` uses `openvibe-shared/test-runner` (60 s, serial); each file boots Surf against |
| 78 | in-process Network/Live/Games/Actor stand-ins, never the internet. Tests cover the merged feed and |
| 79 | partial sources, Ride fallbacks, the Actor task (Idempotency-Key, statuses), watch parties and SSE, |
| 80 | the pages, sign-in origin rules, discovery/agent twins, contrast and links. |
| 81 | |
| 82 | ## Conventions and pitfalls |
| 83 | |
| 84 | - CommonJS, 4-space indent, single quotes, semicolons; problem+json for API errors; escaped, |
| 85 | server-rendered HTML. No database and no schema: do not add a migration without a design change. |
| 86 | - **Nothing durable may be assumed.** Parties, caches and tokens-in-flight die on restart; a feature |
| 87 | that needs to survive one belongs in another service, not in Surf's memory. |
| 88 | - **Never weaken the origin checks** on `/api/v1/web/tasks`, `/web/go` or a party command; they protect |
| 89 | a person's Actor allowance and their party. |
| 90 | - **A Live/Games/Actor failure is named, not hidden:** `sources`/`partial`, `games: "not_ready"` and |
| 91 | `502 actor.unavailable` are the contract; never fabricate feed entries or map data. |
| 92 | - **The Web question never goes in a URL:** `/web/go` posts it and 303s to the Actor task page. |
| 93 | - **Progressive enhancement:** every page must work without `public/js/surf.js`; the HLS vendor file is |
| 94 | loaded only when native HLS is missing. |
| 95 |