Problem
Remote teams sketching ideas together need three things at once: a shared canvas, a chat channel, and a face-to-face call. They need all of it with low enough latency that drawing feels live, and they shouldn’t have to juggle three separate tools.
Approach
- A shared whiteboard with drawing tools, synced to everyone in a room over WebSockets.
- Instant chat on the same socket connection.
- Peer-to-peer video over WebRTC, so media never touches the server. It connects through STUN, with a TURN server as a fallback for strict NATs.
Architecture
Browser (React) ── WebSocket ──► Node.js / Express
│ canvas ops, chat, signaling │ rooms, broadcast, rate limiting
│
└──── WebRTC (media, P2P) ────► other browsers
▲
STUN / TURN
The server is only a signaling and broadcast hub. Video and audio flow directly between peers.
Tech stack
React, Node.js, Express.js, WebSockets, WebRTC, STUN/TURN.
Key trade-offs
Perfect Negotiation instead of a fixed caller/callee
When two peers renegotiate at the same time (someone toggles their camera while another joins), naive offer/answer logic ends in “glare” and a broken connection. I implemented WebRTC’s Perfect Negotiation pattern: one peer is “polite” and rolls back its own offer on collision, so negotiation always converges.
Queuing ICE candidates in custom signaling
ICE candidates can arrive before the remote description is set, and applying them early throws. Candidates are buffered per peer and flushed once the description lands.
Mesh P2P instead of an SFU
Direct peer connections keep the server cheap and latency low for small rooms. Bandwidth grows with every participant, though, so large rooms would need a selective forwarding unit (SFU).
Results
- Multi-user rooms with synced drawing, chat and video in one tab.
- Rate limiting on the backend guards the socket and HTTP endpoints against abuse.