Multi-peer WebRTC video call platform designed to be embedded in other applications via an iframe + postMessage SDK.
videocall-sdk/
├── apps/
│ └── call/ # React 18 frontend — port 5173
└── packages/
└── server/ # Express + Socket.io signaling server — port 4000
Full-mesh peer-to-peer WebRTC — each participant connects directly to every other participant. The signaling server only brokers session setup (offer/answer/ICE) and is not in the media path.
- Node 22+
- pnpm
npm install -g pnpmpnpm installpnpm dev # starts both frontend (5173) and signaling server (4000)Or run individually:
pnpm --filter @videocall/call dev # frontend only
pnpm --filter @videocall/server dev # server onlypnpm build # builds the React frontendVITE_SIGNALING_URL=http://localhost:4000 # defaults to this if unsetFRONTEND_URL=http://localhost:5173 # CORS origin + join URL base; defaults to this
PORT=4000- A host calls
POST /roomsto create a room and gets back ajoinUrl. - Participants open the join URL (or the host shares it).
- Before joining, participants enter a display name on the lobby screen.
- On join, the frontend connects to the signaling server via Socket.io and exchanges WebRTC offers/answers/ICE candidates with existing peers.
- Once ICE is complete, media flows peer-to-peer — the server is no longer involved.
client ──join-room──────────────────────► server
◄──existing-peers────────────────── server (list of current participants)
◄──peer-joined──────────────────── server (broadcast to room on new join)
# For each peer pair:
A ──offer──► server ──► B
B ──answer─► server ──► A
A/B ──ice-candidate─► server ──► B/A
| Method | Route | Description |
|---|---|---|
GET |
/health |
Health check |
POST |
/rooms |
Create room → { roomId, joinUrl } |
GET |
/rooms/:roomId |
Check room exists |
POST |
/participants |
Issue / re-validate a participantKey for a display name |
- TURN servers: Google STUN alone fails behind symmetric NAT. Add TURN credentials to
STUN_CONFIGinapps/call/src/constants.ts. - Persistence: The signaling server keeps all state in memory — a restart clears all rooms. Replace with a persistent store (Redis, DB) if needed.
- CORS: Set
FRONTEND_URLon the server to match your deployed frontend origin. - Scale: Full-mesh doesn't scale well beyond ~6–8 participants. For larger rooms, consider an SFU (e.g. mediasoup, LiveKit).
| Layer | Technology |
|---|---|
| Frontend | React 18, React Router 7, TypeScript, Vite |
| Signaling | Express, Socket.io, TypeScript, tsx |
| WebRTC | Browser native APIs, Google STUN |
| Package manager | pnpm workspaces |