When we built Dino Chat, we made a deliberate choice: no PeerJS. Most WebRTC tutorials rely on PeerJS as an abstraction layer, but it hides the protocol and limits flexibility. We implemented the WebRTC signaling from scratch for full control.
Signaling Server Running — UI Needs Hosting
The signaling server is deployed at wss://streamora-9cvm.onrender.com but the frontend is not publicly hosted. Support us to get the full app live.
Why Skip PeerJS?
PeerJS wraps RTCPeerConnection but hides how it works. When things break — and in WebRTC, they do — you are debugging an abstraction, not the protocol. Working directly with WebRTC APIs gives complete control over ICE candidate handling, offer/answer exchange, and media stream management.
Modular Architecture
socket.js
All Socket.io events — room joining, signaling, user matching.
video.js
RTCPeerConnection, ICE candidates, offer/answer lifecycle.
chat.js
Text messaging over WebRTC data channels — zero server relay.
Signaling Server
Node.js and Socket.io on Render — session setup only, never media.
Signaling Flow
- User A connects and declares interests
- Server matches User A with User B sharing at least one interest
- User A creates RTCPeerConnection and generates an offer SDP
- Offer sent to User B via the signaling server
- User B creates an answer SDP and sends it back
- Both sides exchange ICE candidates as discovered
- Connection established — media flows directly peer-to-peer
- Signaling server is no longer involved in the call
Text messages use WebRTC Data Channels — not the signaling server. Once connected, even chat travels directly between browsers with zero server involvement.
Interest-Based Matching
Users enter interests before connecting. The server finds a partner with at least one overlap. If no match is found within 10 seconds, it falls back to random pairing — same as NovaTalk but with smarter defaults when possible.