openvibers/openvibe.live AGENTS.md

AGENTS.md

main 14.7 KB · 97 lines · markdown

AGENTS.md — OpenVibe.Live

Project Overview

Self-hosted live streaming platform — Node.js/Express monolith, vanilla JS SPA frontend, PostgreSQL (openvibe-sdk/db; embedded PGlite in development and tests; on PostgreSQL since 2026-10-08). Part of the OpenVibe network with SSO via openvibe.network. See [README.md](README.md) for features and [docs/architecture.md](docs/architecture.md) for system design.

Network context: SSO/OAuth2 + the OpenCoins wallet come from OpenVibe.Network (server/monetization/wallet-client.js). The media subsystem (VODs/clips/pastes/thumbnails/files) lives in OpenVibe.Media — Live talks to it via [server/media-client.js](server/media-client.js), the thin proxy routers in [server/media-proxy/](server/media-proxy/), and the Media-backed recorder [server/streaming/recorder.js](server/streaming/recorder.js). Media completion events arrive over OpenVibe.Events at POST /internal/media-events (the only path; the direct webhook was retired 2026-10-10). The legacy local vods/clips/pastes/paste_likes/paste_comments tables are FROZEN: never read or write them (test/frozen-tables.test.js enforces it; ask Media or Community through server/media-proxy/lookups.js, media-client.js, pastes-client.js); VOD/clip comments are OpenVibe.Community threads ([server/comments-client.js](server/comments-client.js)) and the old local comments table is not in the PostgreSQL database either; Chat owns every chat table — the staged set (channel_moderators, channel_moderation_settings, emotes, user_tags, chat_ai_summaries, chat_timeline_events; Live reads those only through server/chat/moderation-client.js, capability chat.moderation.read, cached 30 s, and the summaries through server/chat/insight-client.js, cached 60 s) and the twelve the chat cutover imports covered (chat_messages, the DM tables, tts_voice_overrides, channel_sounds, relay_users, hidden_relay_users, pending_ip_messages, stream_first_chats, moderation_actions); Live keeps no copy of any of them since 2026-10-05 — chat stats, queues, history, first-chat and DM-block state all come through server/chat/chat-reads.js, and the twelve tables were never created on PostgreSQL (migrations/0002_live.sql leaves them out; the SQLite-era boot migration 007 dropped them before the move) — test/chat-tables-dropped.test.js guards that nothing under server/ names them and no migration creates one; Live-owned AI/transcript state for Media-hosted content lives in vod_ai_state/clip_ai_state. See ../CONTRACTS.md for the binding inter-service contracts. Money: OpenVibe.Billing is the only money ledger since the cutover of 2026-10-17 ([billing-client.js](server/monetization/billing-client.js), [billing-actions.js](server/monetization/billing-actions.js)): every checkout, donation, cashout, recycle, subscription, balance and entitlement is a Billing call, provider and PowerChat webhooks go to Billing (Live's answer 410), and Live's old openvibe_bucks_*/openvibe_coins_balance columns and transactions/payment_orders/subscriptions tables are gone (contract migration 0016_drop_old_money_ledger; test/money-tables-gone.test.js). The owner-only money_writes_frozen flag ([server/monetization/money-authority.js](server/monetization/money-authority.js), /api/admin/money) refuses money actions. Cosmetics: OpenVibe.Inventory owns them since 2026-10-09 (ADR-054: live.* items issued by service:live, Live's item ids as aliases); [server/monetization/cosmetics.js](server/monetization/cosmetics.js) reads and writes through [inventory-client.js](server/monetization/inventory-client.js) (Chat's decor lookups batch 100 people per read) and Live keeps only the catalog's renderers. Live's user_cosmetics/user_equipped/user_equipped_tag are read by nothing (test/cosmetics-tables-gone.test.js) and are dropped by a contract migration.

Commands

npm run dev               # Start dev server (NODE_ENV=development)
npm start                 # Start production server (node server/index.js)
# The schema is migrations/NNNN_*.sql, applied at boot (DATABASE_DIRECT_URL, owner); without DATABASE_URL, development uses PGlite under DATA_DIR
node --check <file.js>    # Syntax check (no linter configured)
npm test                  # All unit/security/migration/deploy tests + size budgets (test/run.js), on PGlite
npm run test:pg           # The same on the PostgreSQL + PgBouncer containers (openvibe-sdk scripts/test-services.sh up)
node test/<file>.test.js  # One test
BASE=http://127.0.0.1:3000 npm run test:browser  # Browser smoke (running server + Chrome); add -- --a11y for axe (WCAG 2.1 AA)

No build step. Frontend is plain JS served directly — no bundler, no transpiler. Asset URLs are content-hashed at serve time (server/web/assets.js) — never add ?v= by hand.

Deploy: production runs the release layout (/opt/openvibe.live/current → releases/<time>-<sha>; deploy with cd /opt/openvibe.live/current && sudo deploy/scripts/deploy.sh), env /etc/openvibe/live.env, unit openvibe-live.service. Static-only changes deploy without a restart. See [docs/deploy.md](docs/deploy.md).

Architecture at a Glance

  • Entry: [server/index.js](server/index.js) — Express app, middleware, route mounting, WS upgrade handler, sub-service init
  • Config: [server/config.js](server/config.js) reads .env ([.env.example](.env.example))
  • Database: [server/db/database.js](server/db/database.js) — all queries (async, openvibe-sdk/db), schema in [migrations/](migrations/) (0001_analytics.sql, 0002_live.sql)
  • Auth: [server/auth/auth.js](server/auth/auth.js) — RS256 JWT from openvibe.tools SSO + hbt_ API tokens
  • Permissions: [server/auth/permissions.js](server/auth/permissions.js) — role hierarchy: user < streamer < global_mod < admin
  • Frontend shell: [public/index.html](public/index.html) — navbar and <section id="page-*"> shells (page markup, the home page's included, is in public/fragments/, inlined on its own route); routing via history.pushState in [public/js/app.js](public/js/app.js)
  • Route loading: [public/features.json](public/features.json) maps routes → features (fragment in public/fragments/, CSS in public/css/features/, scripts, deps, stubs); [public/js/ov-loader.js](public/js/ov-loader.js) loads them. New page code goes in a feature, not in index.html/core app.js. See [docs/architecture.md](docs/architecture.md#frontend-loading).

Each feature lives in its own server/<feature>/ directory with routes.js + service files. Frontend: one JS file per feature in public/js/, registered in public/features.json.

Conventions

  • CommonJS (require/module.exports) everywhere. No ES modules except dynamic import() for mediasoup-client.
  • Style: 4-space indent, single quotes, semicolons. No linter/formatter configured.
  • Naming: camelCase for JS, snake_case for PostgreSQL columns/tables.
  • DB access: database.js helpers, every one async (await db.getUserById(), await db.run(), await db.get(), await db.all()); an INSERT that needs its id says RETURNING id. Never leave a write un-awaited when anything after it depends on it: the pool runs un-awaited work out of order (PGlite does not, so only npm run test:pg shows it).
  • Auth middleware: requireAuth from auth.js. Permission checks via permissions.js.
  • DB migrations: a new migrations/NNNN_name.sql with its -- phase: expand|migrate|contract header (a contract names its expand with -- after: NNNN and waits out the N-1 window); never edit one that has run. No table is created at runtime.
  • Public responses: serialize through [server/web/serializers.js](server/web/serializers.js) — never return raw managed_streams/users rows.
  • TURN: ICE lists come from [server/net/turn.js](server/net/turn.js); set TURN_AUTH_SECRET (coturn use-auth-secret) for short-lived credentials.
  • Outbound fetches of user-chosen URLs: [server/net/egress.js](server/net/egress.js) only. Background loops: server/utils/jobs.js.
  • Files and restore drills: every file location comes from [server/paths.js](server/paths.js) (DATA_DIR), never a literal ./data; the database is DATABASE_URL (drill mode refuses production's ov_live). Anything started at boot or at module load (timers, listeners, sockets, jobs, outbound calls) must stay off under LIVE_DRILL ([server/drill.js](server/drill.js)); test/drill-mode.test.js fails on any new one.
  • WebSocket servers: Each has init(server) and handleUpgrade(req, socket, head) methods.
  • Frontend globals: currentUser, api(), navigate(), handleLinkClick(). Cross-component sync via CustomEvent (e.g., openvibe-auth-changed).

Key Pitfalls

  • No build step: Changes to public/ take effect on deploy without a restart; caching follows content hashes. npm test fails if the home page's size budgets grow (scripts/perf/check-budgets.js).
  • First-paint CSS: public routes block only on /css/style.core.css, a subset of style.css generated from public/css/critical-selectors.json (server/web/critical-css.js); the full style.css follows without blocking. After changing how a public page looks, re-run node scripts/perf/critical-css.js (docs/performance-audit.md).
  • Inline handlers on lazy features: a function called from onclick= in markup that exists before its feature loads must be listed in that feature's stubs.
  • New page routes: the SPA fallback answers 404 for any path [server/web/page-status.js](server/web/page-status.js) does not know. A new top-level route goes in both routeFromURL() and that file's page list.
  • innerHTML usage: Frontend has heavy innerHTML — prefer DOM node creation for new code to avoid XSS.
  • Public AI text may describe people, and nudity is fine (owner, 2026-10-31: the old no-looks rule is gone, openvibe-sdk/no-looks is deprecated; "dick is art… titties is art… stop being against art on my platform"). What stays: the AI never picks, posts or auto-clips a moment whose text says it shows sex acts or porn (server/ai/explicit.js; porn would need an adults-only section with age verification), and the home tagline never pairs a child with bare skin (server/ai/hero-copy.js). Anything sexual involving a minor is illegal and always blocked.
  • WebSocket auth lifecycle: WS connections can start anonymous and upgrade via join message. On account switch, the socket must be rebuilt (not just re-joined) — see openvibe-auth-changed handling in chat.js.
  • openvibe-shared: Pinned release of OpenVibers/OpenVibe.Shared ("openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/vX.Y.Z"), served at /shared/* from node_modules. Change it there and bump the tag; never edit node_modules.
  • DM delivery: Server verifies dm.isParticipant() before delivering — always maintain this check.
  • Schema: every table is in migrations/; ensureTables() is gone. The frozen and Chat-owned tables are not in the PostgreSQL schema at all.

Robots: OpenVibe.Bot owns them (plan T15). Live has no control socket, hardware API or ONVIF code: a channel bound to a robot (channels.bot_robot_id, PUT /api/streams/channel/:username/bot) embeds the robot's panel (server/bot/embed.js), and chat's !forward/!say … reach Live at POST /internal/chat-effects/hardware, which forwards them to Bot's POST /api/v1/robots/:id/commands as the viewer (server/bot/bot-client.js, Network grant bot.robot.control). Live's old controls tables (control_configs, its buttons and whitelist, stream_controls, the ONVIF camera tables, api_keys) and the columns that pointed at them are dropped by migration 0011_drop_live_controls (test/controls-tables-gone.test.js).

WebSocket Endpoints

/ws/broadcast, /ws/call, /ws/robotstreamer-publish — upgraded via the handler in server/index.js with origin checks and IP bans. /ws/chat is OpenVibe.Chat's (nginx routes it to 127.0.0.1:4400); Live runs no chat WebSocket server. Any other path gets a plain HTTP answer, not a dropped socket (nginx logs a drop as a 502): 404, or 410 naming OpenVibe.Bot for the retired robot control socket /ws/control ([server/net/ws-paths.js](server/net/ws-paths.js)); a new WebSocket server adds its path there.

Testing

Standalone Node scripts in test/ using assert, run together by npm test. They run on one migrated PGlite database per run (test/helpers/pg-preload.mjs; npm run test:pg uses the containers). Browser smoke: test/browser/smoke.js (routes × widths, console errors, overflow, duplicate scripts, resource growth). Always node --check modified files before committing.

Documentation

Every file below is also served on the site at /docs/<name> (rendered by server/docs/routes.js, GitHub-compatible heading anchors) — link to https://openvibe.live/docs/whip#… rather than to GitHub when pointing users at a doc.

  • [docs/architecture.md](docs/architecture.md) — System design, data flows, module map
  • [docs/broadcasting.md](docs/broadcasting.md) — Streaming protocols (WebRTC/RTMP/JSMPEG/WHIP)
  • [docs/go-live-in-your-browser.md](docs/go-live-in-your-browser.md) — Going live from a browser with no OBS and no follower minimum (the headline feature: keep it on the home page, in /llms.txt and in search metadata)
  • [docs/whip.md](docs/whip.md) — WHIP ingest API reference (auth forms, CORS, browser-only publishing, error codes)
  • [docs/agent-streams.md](docs/agent-streams.md) — Agent streams: the ags_ resource, publish and view tickets, the curtain, the owner page and the embed
  • [docs/streams-api.md](docs/streams-api.md) — Stream slots API: everything the Go live screen does, as calls (slots, settings, key, title ideas, history, going live and ending)
  • [docs/rundown.md](docs/rundown.md) — The Rundown (/rundown, was the Arena): what streamers talked about, written up from their speech — topics, takes, episodes, crossovers
  • [docs/chat-system.md](docs/chat-system.md) — Chat features and moderation
  • [docs/api-tokens.md](docs/api-tokens.md) — Bot/integration token system
  • [docs/vods-and-clips.md](docs/vods-and-clips.md) — VOD/clip pipeline (pre-split; storage/cutting now in OpenVibe.Media)
  • [docs/dashboard.md](docs/dashboard.md) — Streamer dashboard
  • [docs/onboarding.md](docs/onboarding.md) — New user flow
  • [docs/deploy.md](docs/deploy.md) — Deploy kinds, release layout, caching, nginx
  • [docs/performance-audit.md](docs/performance-audit.md) — Measurements and tools
  • [SETUP.md](SETUP.md) — Full deployment guide
  • [SECURITY_AUDIT.md](SECURITY_AUDIT.md) — Security audit findings
  • [hardware/README.md](hardware/README.md) — Raspberry Pi integration (moved to OpenVibe.Extensions hardware/; this is a redirect)