A peer-to-peer video call demo built on WebRTC, with a Go signaling server and a Vue 3 + TypeScript client. Includes on-the-fly face detection on the remote stream via face-api.js and an optional "save photo" message that captures a frame to the server.
┌────────────────────────┐
│ Go signaling server │
│ (back-end/, gorilla/ │
│ websocket + pion) │
└────────────┬───────────┘
│ /ws (WebSocket)
│
┌─────────────────────┴─────────────────────┐
│ │
┌──────────▼──────────┐ ┌──────────▼──────────┐
│ Vue 3 + Vite SPA │ ◄────── P2P RTC ──►│ Vue 3 + Vite SPA │
│ (front-end/) │ STUN / TURN │ (front-end/) │
│ face-api.js │ │ face-api.js │
└─────────────────────┘ └─────────────────────┘
back-end/— Go WebSocket signaling server that brokers SDP offers, answers, and ICE candidates between peers. Also accepts asave_photomessage that writes a captured frame to disk.front-end/— Vue 3 + TypeScript + Vite client. Captures the local camera/mic, negotiates the peer connection, runs face detection on the remote stream, and triggers the photo capture when a face is detected.
- Go 1.26+
- Node.js 20+
- A TURN server if peers need to traverse symmetric NATs (e.g. a free tier at metered.ca or Twilio)
1. Back-end (signaling server)
cd back-end
cp .env.example .env # fill in TLS / origin allow-list if needed
go run .Defaults to plain HTTP on :8080. Set TLS_CERT_FILE and TLS_KEY_FILE to enable TLS for production. ALLOWED_ORIGINS accepts a comma-separated allow-list of Origin headers; leave empty to permit only same-origin signaling. See back-end/.env.example for the full list.
2. Front-end
cd front-end
cp .env.example .env # point VITE_WEBSOCKET_URL at the back-end, add TURN
npm install
npm run devOpen the printed URL in two tabs (or two devices on the same network) — each tab registers as a peer and can call the other.
| Variable | Purpose |
|---|---|
VITE_WEBSOCKET_URL |
The back-end's /ws endpoint, e.g. ws://localhost:8080/ws |
VITE_TURN_HOST |
TURN host (optional — falls back to public STUN) |
VITE_TURN_USERNAME |
TURN auth username |
VITE_TURN_CREDENTIAL |
TURN auth credential |
If any TURN variable is missing the client falls back to public STUN servers, which is enough for many local-network calls but will not work behind most NATs in the wild.
cd front-end && npm run build
cd ../back-end && go build -o webrtc-signalingThe back-end serves the front-end's dist/ from STATIC_FILES_DIR (default ../front-end/dist).
cd back-end && go test ./...The front-end currently relies on manual smoke testing against a real WebRTC peer.
The signaling server enforces:
- A strict format for client identifiers (
^[a-zA-Z0-9_-]{1,64}$) — usable as map keys and as filesystem path components without sanitization gaps - Server-side rewriting of the
fromfield on every routed message, so peers cannot spoof other identities - A 2 MB cap on incoming
save_photopayloads with afilepath.Relcheck that the destination stays insideIMAGES_DIR - Read/write deadlines on the WebSocket and an explicit
ALLOWED_ORIGINSallow-list (defaults to same-origin)
MIT — see LICENSE.