Random language-exchange video calling. WebRTC 1:1 + matching by language preference + live translated subtitles.
apps/web— Next.js clientapps/signaling— Rust axum WebSocket signaling + matchingapps/api— Nest.js REST API (Sprint 6)packages/shared— shared TS event schema
pnpm install
pnpm dev:signaling # terminal 1 — Rust axum on :8787
pnpm dev:web # terminal 2 — Next.js on :3100Open http://localhost:3100/ in two tabs, click Match in each, and they pair into a call.
For dev/debug: http://localhost:3100/call/<any-room-id> in two tabs forces them into the same room without matchmaking.
⚠️ Cross-device note: browsers require secure context forgetUserMediaon non-localhost origins. Either (a) test both tabs on the same machine vialocalhost, or (b) for two-device testing run web behind HTTPS (mkcert+next dev --experimental-https) or add the LAN IP to Chrome'schrome://flags/#unsafely-treat-insecure-origin-as-secureallowlist.
The signaling server exposes GET /turn-credentials that signs short-lived coturn REST API credentials. Disabled until you set TURN_STATIC_AUTH_SECRET.
brew install coturn
EXTERNAL_IP=$(ipconfig getifaddr en0) \
STATIC_AUTH_SECRET=$(openssl rand -hex 32) \
REALM=hithere.local \
./infra/render-coturn.sh
turnserver -c infra/coturn/turnserver.conf.renderedThe render script prints the env vars to set for the signaling server:
TURN_STATIC_AUTH_SECRET=... TURN_URIS=turn:LAN_IP:3478?transport=udp,... pnpm dev:signalingEXTERNAL_IP=<public-ip> STATIC_AUTH_SECRET=... REALM=hithere.local ./infra/render-coturn.sh
docker compose -f infra/docker-compose.yml up coturncurl http://localhost:8787/turn-credentialsreturns username/password/uris.- In Chrome
chrome://webrtc-internals, after a match you should see at least one ICE candidate of typerelaywhen both peers are on different networks.