Getting Started With WebRTC

Getting Started With WebRTC

June 19, 2018 (6y ago)

Introduction

WebRTC is the latest and greatest technology available to us to allow for Peer-to-Peer Networking. But why use WebRTC over traditional methods of delivering content? We will discuss both the pros and cons of using P2P (Peer-to-Peer) connections via WebRTC in your applications and also see how it works. WebRTC is an ongoing standard that was introduced in May 2011 when Google released an open-source project for real-time communication via the web-browser. \n\nThe W3C draft of WebRTC is still a work in progress and because of that, many browsers have been hesistant on implementing the APIs. Polyfills like Adapter.js have since been introduced to help resolve the problem of unsupported browsers by installing plugins. WebRTC tries to implement three APIs which include `Media Stream`, `RTCPeerConnection` and `RTCDataChannel`. To establish a WebRTC connection there are essentially three parts in the process, Signaling, Discovery and Establishing P2P Connection. We will go through each of these steps, and build a basic application that will exchange data between clients through a WebRTC P2P connection.\n\nWebRTC\n# Signaling\nThe first step in establishing a WebRTC Peer Connection is Signaling. Signaling is the process of coordinating communication between peers, it acts as a way to exchange client information. In this example, we will create a basic signaling server using websockets on port `3434`. The signaling server will act as a way for the peers to exchange information like public IP addresses/ports once available. \n\n```javascript\nvar WebSocketServer = require('ws').Server;\nvar wss = new WebSocketServer({port: 3434});\nwss.broadcast = function(data) {\n var clients = wss.clients;\n clients.forEach(function(client) {\n client.send(data);\n });\n};\n\nwss.on('connection', function(ws) {\n ws.on('message', function(message) {\n wss.broadcast(message);\n });\n});\n\nconsole.log(`Signaling server listening on port: ${port}`);\n```\nWe create a simple WebSocket Server listening on port 3434 and when we recieve a message we broadcast that to all clients on the socket connection. This allows for all messages that are sent through our signaling server be forwarded to all clients.\n\n# Discovery\nIn the discovery stage, the client sends a request to a STUN server which then replies with information about that client like the public ip and port as seen from the public network. Once the client has this information it can then send it to other peers through SDP (Session Description Protocol) that will be handled through our signaling server. An SDP offer is used to "call" other clients, when the client receives the offer it can then send an answer. Both offers and answers are managed through our signaling server and sent to the clients. \n\nOnce the answer is received, the caller then sends an `ICECandidate` to the peers which contains information like ip address/port number. The clients then send their own ICECandidate to the caller and both sides now have the required information to connect via P2P. If for any reason the clients can't connect directly, TURN (Traversing Using Relays around NAT) is used to relay data/media between the clients. This could be useful for a client that may have a strict firewall that doesn't allow P2P connections. All of the data transferred is relayed through a third-party server. \n\n```javascript\nclass PeerConnection extends RTCPeerConnection {\n\n constructor(config, socket) {\n super(config);\n var el = this;\n this.onicecandidate = this.onIceCandidate;\n \n // create a datachannel to exchange data\n this.channel = this.createDataChannel("datachannel");\n this.channel.onmessage = function(d) {\n if(el.onData !== undefined) {\n el.onData(d);\n }\n };\n this.channel.onopen = this.channelopen;\n this.ondatachannel = this.onDataChannel;\n \n // set up a websocket connection\n // to use as our signaling server\n this.socket = new WebSocket(socket);\n this.socket.onmessage = function(m) {\n el.onmessage(m);\n };\n }\n\n onIceCandidate(evt) {\n var el = this;\n if(event.candidate != null) {\n el.socket.send(JSON.stringify({\n 'ice': event.candidate\n }));\n }\n }\n\n // once datachannel is open\n // assign this.channel\n onDataChannel(evt) {\n this.channel = evt.channel;\n }\n \n channelopen(evt) { }\n \n // send data through \n // channel once open\n senddata(d) {\n this.channel.send(d);\n }\n\n gotdescription(description) {\n var el = this;\n el.setLocalDescription(description, function() {\n el.socket.send(JSON.stringify({\n 'sdp': description\n }));\n }, this.error);\n }\n\n // on error\n onError(e) {\n console.log(e);\n }\n\n // websocket messages\n onmessage(m) {\n var el = this;\n var signal = JSON.parse(m.data);\n if(signal.sdp) {\n this.session = new RTCSessionDescription(signal.sdp);\n this.setRemoteDescription(el.session, function() {\n if(signal.sdp.type == 'offer') {\n el.createAnswer(function(d) {\n el.gotdescription(d);\n }, el.onError);\n }\n });\n }\n else if(signal.ice) {\n el.ice = new RTCIceCandidate(signal.ice);\n this.addIceCandidate(el.ice);\n }\n }\n\n startConnection() {\n var config = {\n 'offerToReceiveAudio': false,\n 'offerToReceiveVideo': false\n };\n var el = this;\n this.createOffer(function(d) {\n el.gotdescription(d);\n }, this.onError, config);\n }\n}\n\n```\nIn the example code above, we are creating a class of `PeerConnection` that will extend from the `RTCPeerConnection` API. We will also establish a web socket connection to our signaling server and listen for any incoming offers/answers. We create an `RTCDataChannel` to allow us to transfer data once the P2P connection has been established and pass any incoming data through the `onData` function.\n\n```javascript\n// stun server config\nvar config = {\n 'iceServers': [\n { 'urls': ['stun:stun.services.mozilla.com']}, \n { 'urls': ['stun:stun.l.google.com:19302']}\n ]\n};\n\n// our signaling server\nvar ws = "ws://127.0.0.1:3434";\nvar p2p = new PeerConnection(config, ws);\np2p.startConnection(); // if caller\np2p.sendData("Hello World!"); \np2p.onData = function(d) {\n // once data has been received\n console.log(`Data received: ${d}`);\n};\n\n```\nSince we've established our P2P connection, we can now transfer data between all clients which can be anything from images, videos or files. \n\nDiagram\n