This is an example app for my article: "WebRTC guide".
This is a small web app that uses Web Real-Time Communication (WebRTC
) for between-browser peer-to-peer (p2p
) communication. The biggest problem when using a webpage for p2p is finding the communication channel. How can device A know the IP of the device B? This is resolved using the Interactive Connectivity Establishment (ICE
) protocol. It generates a few connection candidates (e.g. inside a local network, through public IP, or an intermediary TURN server). Then it selects one to facilitate message exchange.
It uses Express and Socket.IO (needed for ICE negotiation, see FAQ below) under the hood. No public demo, as it would be hard for me to deploy it for free while keeping it simple (no firebase, pusher, ably etc.).
yarn install
yarn start
<- dev server- Go to
http://localhost:3000
to open the chat room with a randomroomId
assigned - Use the link given by the first chat to join the existing chat room e.g.
http://localhost:3000?roomId=aUa62LD8NZ
Here is a testing procedure between a desktop browser and a mobile phone (disconnected from the local network):
Running server
yarn start
<- dev servernpm install -g localtunnel
<- install localtunnel to make our local server visible from other networkslt --port 3000
<- expose app to the internet- Go to
localtunnel
-generated url to make sure it works OK. You might need to provide your external IP as perlocaltunnel
TOS. - Get the share link to the current chat room e.g.
https://a-b-c.loca.lt?roomId=aUa62LD8NZ
Connecting from a mobile device
- Disconnect from the local network (Wi-Fi etc.). Disconnect the phone from the PC.
- Connect the phone to a mobile network (e.g. LTE) using your mobile carrier.
- Open the chat url e.g.
https://a-b-c.loca.lt?roomId=aUa62LD8NZ
. - You might need to provide the same IP as in the "Running server" section. Again,
localtunnel
TOS to prevent spammers. - The app should connect to the chat room without any problems. After ICE resolves, you can exchange messages peer-to-peer.
If there is an error when connecting peer-to-peer, it is probably caused by a lack of a TURN server. I only use stun:openrelay.metered.ca:80
, stun:stun.l.google.com:19302
STUN servers. It's not hard to add another entry to ICE_SERVERS. It's just that free TURN servers are either crap or require login.
There are 2 parts to this project: an express server and a simple webpage with corresponding index.js.
Express server
- Hosts the static content from
/public
(.html
,.js
files). - Uses
socket.io
to facilitate socket connection to the client's browser. This is needed for WebRTC ICE candidate selection. The messages inside the chat room (after peer-to-peer connection is established) do not use sockets.
Browser client
- Simple HTML page. Uses CDN for
normalize.css
, andsocket.io
. 230 lines of<style>
tag :) - index.js as a main JS script.
- Establishes socket connection to the server.
- Joins/creates a new chat room.
- On every new connection (received by socket message), try to establish a peer-to-peer connection with the new user.
A lot of the code in the client's index.js is just formatting messages and UI updates. While ICE itself is implemented by the browser, we still have to write some code to cover all the cases. E.g. "user disconnected" message can happen when:
- we receive info from the socket server (broadcasted to the entire chat room when other user's socket disconnects),
- the peer-to-peer connection changes status to
disconnected
(rarely happens in practice for some reason), - the peer-to-peer data channel is closed.
Sockets are required to exchange data between peers before the p2p connection is established. Think about it: you have 2 random devices that try to connect over the internet. How does one know the IP of the other? The p2p connection conditions negotiation is the purpose of the ICE protocol.
Once the ICE chooses a candidate, we can start sending the messages bypassing the server - p2p only. Check the network tab in the browser.
Everything is printed to the console. Both on the client and server. Every message, state change etc. It's a lot, but if you are only doing one ICE at a time, it's quite straightforward.
It does not. Socket.io can use the basic socket implementation: long-polling. Upgrade to sockets is skipped/fails.