WebRTC group chat hello world

In the following iframe is a chat room. It’s hosted here, and here’s the code on GitHub. This post is my notes on how I built it.

The chat messages themselves are sent via WebRTC data channels. Each client has an RTCPeerConnection to each other client, and each peer connection has a RTCDataChannel.

To establish peer connections, we need a signalling mechanism. In principle, we can use anything as the signalling mechanism; in this old post, I show how to connect two peers by copy-pasting the signalling messages between them. In this app, I will use Ably’s publish/subscribe system to send the signaling messages.

When a page loads, generates a fresh random ID like abc for itself. It then subscribes to an Ably channel called global, and broadcasts a “hello” message with its ID on the global channel. From this point, we could just use the global channel as the medium for chat messages, and ignore WebRTC. But instead we only use Ably to establish peer-to-peer connections.

Note, importantly, that we’re identifying clients, not users. A user could have many simultaneous clients. If we want to introduce a “user” concept, we would do this on top of the “client” concept.

Anyone can directly send a message to a client ID. To achieve this, each client subscribes to its own Ably channel like client-abc. Anyone who wants to talk to client abc can publish a message on client-abc. Each message can contain the recipient ID. (Yes, this is a security hole: a client can forge the sender of a message.) In this way, clients can talk to each other privately, without using the global channel.

When a client receives a “hello” message, it responds to the sender with its own ID. This establishes their knowledge of each other, and establishes the “signalling channel” between them, which consists of their two per-client Ably channels.

Once this signalling channel is established, the clients can attempt to set up a peer connection. Setting up a peer connection is asymmetric: there is always a caller and a callee. I choose the convention that a client responding to a “hello” message becomes the caller. The caller is expected to create an offer and send it to the callee. The callee is then expected to create an answer and send it back to the caller. (Actually, the clients may need several rounds of negotiation. I don’t fully understand how this works.) This process establishes an agreed session description, which describes the MediaStreams and DataChannels that will be used.

Simultaneously, the clients exchange “ICE candidates”. An ICE candidate is a potential way to connect to the client (for example, via 127.0.0.1, or via a proxy). Confusingly, this connectivity information is also included in the session description. Also confusingly, the WebRTC API suggests that ICE candidates are exchanged in parallel to session descriptions, but this is not entirely the case: you have to set the remote session description before you can add ICE candidates, so you may need to buffer the ICE candidates.

When a client goes away, its peers eventually notice: the RTCPeerConnection state changes to disconnected after a few seconds. I don’t yet know how disconnections are detected, or whether they can be detected more cleanly and speedily.

(You might wonder: why am I using Ably, when I used to work at Pusher on a competitor product? The main reason is that Ably lets me configure permissions so that clients can publish and subscribe without an authentication step. This is great for making “serverless” applications like this one.)

Tagged #programming, #web, #webrtc.
👋 I'm Jim, a full-stack product engineer. Want to build an amazing product and a profitable business? Read more about me or Get in touch!

More by Jim

This page copyright James Fisher 2020. Content is not associated with my employer. Found an error? Edit this page.