Open Source on GitHub

Building A Simple Peer-to-Peer WebRTC Chat

by Thomas Gorissen

Audio/Video communication is not the only thing that WebRTC enables you to do. The data channel offers a variety of use-cases, too, by sending information from one peer to another without going through a server. In this tutorial I show you how to build a simple chat.

See the Pen WebRTC Chat with SkylinkJS by Thomas Gorissen (@serrynaimo) on CodePen.

I'm using our open-source SkylinkJS library to build this. It takes care of the hard parts of introducing peers in different network environments and makes WebRTC really easy to use. If you haven't seen my Getting Started tutorial, you can find it here.

Tip: You can click on the function and event names in this tutorial to see the corresponding entry in our API documentation.

Step 1: Include SkylinkJS into your website

<html>
<head>
    <title>WebRTC chat with SkylinkJS</title>

<span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"//cdn.temasys.com.sg/skylink/skylinkjs/0.6.x/skylink.complete.min.js"</span><span class="nt">&gt;&lt;/script&gt;</span>

</head> <body>

<header> <input type="text" id="name" placeholder="My name" autofocus /> <button onclick="setName()">Set my name</button> <button onclick="joinRoom()">Join room</button> <button onclick="leaveRoom()">Leave room</button> <br/> <input type="text" id="message" placeholder="My message" /> <button onclick="sendMessage()">Send message</button> </header>

<div id="container"> <div id="chatbox"></div> </div>

</body> </html>

In the head of our super simple chat website I embed the complete version of SkylinkJS. It comes with all the dependencies that I need to get started. In the header I add input fields for my name and chat messages and some buttons that allow me to trigger my setName(), joinRoom(), leaveRoom() and sendMessage() functions later. The two divs underneath is where I want the chat messages to appear.

Step 2: Instantiate Skylink and creating simple helper functions

var skylink = new Skylink();

I create a new Skylink object and now go on to define the functions that I was referencing in the onclick attribute of my buttons before. The following lines of code describe where we get to trigger actions on Skylink that other peers then will receive and react to.

function setName() {
  var input = document.getElementById('name');
  skylink.setUserData({
    name: input.value
  });
}

In the setName function, we simply read out the string value that the user has written into our name input field and use Skylink setUserData() function to pass any kind of user information in form of a JavaScript object into Skylink. This can be done before or after you join a room context and will automatically become available to every peer joining the same room context.

function joinRoom() {
  skylink.joinRoom();
}

Here I just call joinRoom() on Skylink. This tells the Skylink signaling server that I'm now ready to be connected to other peers joining the default room context.

function leaveRoom() {
  skylink.leaveRoom();
}

leaveRoom() leaves little surprise here.

function sendMessage() {
  var input = document.getElementById('message');
  skylink.sendP2PMessage(input.value);
  input.value = '';
}

This is really where the magic happens. I use the sendP2PMessage() function to directly send my string message from the message input field to all connected peers in my room. This happens one-by-one in a peer-to-peer fashion and through an encrypted transmission. No data touches a server. It's a great way to have super private conversations. (Be aware: This is pretty safe to use for private data exchange, but nothing is ever totally safe!)

function addMessage(message, className) {
  var chatbox = document.getElementById('chatbox'),
    div = document.createElement('div');
  div.className = className;
  div.textContent = message;
  chatbox.appendChild(div);
}

This is my helper function to add a new message to the chatbox div that I created earlier. I can also pass a className variable to leverage CSS, so I can make different messages stand out. I use the textContent property of the div element to avoid that somebody can send me script tags or HTML inside a message that gets executed.

Step 3: Subscribing the Skylink events

When we use the functions above on one peer in the room, the other peers will get notified. Skylink informs them with events. In order for us to react to those event, we need to subscribe to them and execute our own functionality when they get triggered.

skylink.on('peerJoined', function(peerId, peerInfo, isSelf) {
  var user = 'You';
  if(!isSelf) {
    user = peerInfo.userData.name || peerId;
  }
  addMessage(user + ' joined the room', 'action');
});

peerJoined: When somebody joins the chat room, I want to be informed and show a little action message. I use the isSelf parameter to determine if it's myself joining the room or somebody else and in the latter case I use Skylinks peerInfo attribute to retrieve the userData object that the user might have set earlier using Skylinks setUserData function. If there is no name set yet, I fall back and use Skylinks peerId.

skylink.on('peerUpdated', function(peerId, peerInfo, isSelf) {
  if(isSelf) {
    user = peerInfo.userData.name || peerId;
    addMessage('You\'re now known as ' + user, 'action');
  }
});

peerUpdated: It may happen that a peer updates its userData object while connected to the room. In this case the peerUpdated event is fired. I receive the new userData object the same way I got it in the peerJoined event earlier.

skylink.on('peerLeft', function(peerId, peerInfo, isSelf) {
  var user = 'You';
  if(!isSelf) {
    user = peerInfo.userData.name || peerId;
  }
  addMessage(user + ' left the room', 'action');
});

peerLeft: When a peer leaves the chatroom, I also write a short message into the chat room to inform the user.

skylink.on('incomingMessage', function(message, peerId, peerInfo, isSelf) {
  var user = 'You',
    className = 'you';
  if(!isSelf) {
    user = peerInfo.userData.name || peerId;
    className = 'message';
  }
  addMessage(user + ': ' + message.content, className);
});

incomingMessage: This event is triggered whenever Skylink receives a public or private message from another peer. Messages sent with sendP2PMessage() are always private messages, although they could have been sent to multiple peers. If you want to find out if a message was a non-peer-to-peer public broadcast, you can check peerInfo.isPrivate to be false.

Step 4: Initialize!

skylink.init('Your App key');

By calling init() SkylinkJS starts establishing a signaling connection with our servers and requires your App key as a parameter. Only after calling this function, you're able to join room contexts.

If you don't have one yet, you can register and get your own App key using our Developer Console. I'm aware you can make a lot fancier of a chat than this, but I think this example shows pretty well that creating a chat is not as hard anymore as it used to be. :) Leave us your feedback in our developer chat! I'd love to hear from you.

Resources