# gibbz voice — POC Discord-clone proof of concept: peer-to-peer WebRTC voice chat + screen sharing with position-based spatial audio and pluggable STUN/TURN. TypeScript everywhere: the backend runs on [Bun](https://bun.sh), the client is built with [Vite](https://vite.dev), and both are organized into classes. ## Layout ``` src/server/ Bun (Node-compatible) WebSocket/HTTP backend index.ts entrypoint + createApp() factory (used by tests) http-server.ts GibbzHttpServer — health probes, /api/ice, /api/rooms, static signaling.ts Peer / Room / RoomRegistry / SignalingServer (ws) ice.ts IceConfigProvider — env-driven STUN/TURN + HMAC TURN creds static-files.ts StaticFileServer — dist/ (or public/) with traversal guard tracing.ts Tracing — OpenTelemetry SDK setup config.ts AppConfig — env + static dir resolution src/client/ Vite root (index.html, style.css) app.ts VoiceApp — orchestrator implementing VoiceAppContext context.ts the subsystem-facing application interface state.ts AppState — shared cross-cutting voice state webrtc/ WebRtcManager, PeerSession, SignalingClient, ReconnectController media/ AudioManager (mic chain), SpatialAudio, Microphone-, ScreenShareController ui/ roster/screens/spatial-map renderers, settings, welcome, layout chat/ MatrixChat (OAuth2/OIDC text channel) public/static/ PWA assets served verbatim (icons, manifest, service worker) tests/ bun test / vitest compatible suite (tests/server.test.ts) ``` ## Develop ```sh bun install bun run dev:server # backend on :8080 (bun --watch) bun run dev # vite dev server for the client bun test # tests (vitest-compatible; `bun run test:node` = vitest run) bun run typecheck # tsc --noEmit bun run build # production client bundle into dist/ bun run start # serve dist/ + signaling (bun src/server/index.ts) ``` The server also runs under Node 22.18+/23+ (`node src/server/index.ts`) since it only uses erasable TypeScript, but Bun is the supported runtime (the Dockerfile is `oven/bun:1`). A `bun.lock` is generated on first `bun install`. ## What works - Mesh WebRTC voice (perfect negotiation; lowest peer id initiates), full pc-rebuild recovery on ICE failure with TURN-relay fallback - Screen share with audio (mic is spatialized, screen audio plays direct), spotlight/grid tiles and pop-out windows - Spatial audio: drag your avatar on the plane; remote mics run through a `PannerNode` (HRTF, inverse distance). Positions sync over signaling. - Hi-Fi rooms (Code, Gaming): stereo Opus 256 kbps, echo cancellation off - Custom STUN/TURN: gear icon → JSON override in `localStorage`, applied on next join. Server default comes from env (below). - Mute (M), deafen (D), per-peer volume, speaking indicators, quality diagnostics (loss/jitter/RTT), auto-reconnect with countdown banner - Matrix-backed text channel (OAuth2/OIDC against matrix.k6n.net) ## ICE configuration (server side) | Env var | Meaning | |---|---| | `STUN_URLS` | comma-separated `stun:` urls | | `TURN_URLS` | comma-separated `turn:`/`turns:` urls | | `TURN_USERNAME` / `TURN_CREDENTIAL` | static TURN creds | | `TURN_SECRET` | coturn static-auth-secret → ephemeral HMAC creds | | `ICE_TRANSPORT_POLICY` | WebRTC ICE policy: `all`, `relay`, or `none` | | `ICE_SERVERS_JSON` | full `{"iceServers":[...]}` override, wins over the above | | `PORT` / `HOST` | listen address (default 8080 / 0.0.0.0) | ## POC shortcuts (productionize later) - No auth, rooms are in-memory, signaling is plain ws behind the ingress. - No media server: N² mesh — fine for small rooms, needs SFU beyond ~6–8 participants. - Text channels are a single live Matrix room; no history pagination.