This is a Livecoding Recap – an almost-weekly post about interesting things discovered while livecoding. Usually shorter than 500 words. Often with pictures. Livecoding happens almost every Sunday at 2pm PDT on multiple channels. You should subscribe to ￼my Youtube￼ channel to catch me live.

It worked! We got two browsers on different machines talking to each other without a server.

Well… after the initial handshake. You still need a server for that.

OMG IT WORKS! I got two browsers to talk to each other without a server



Well ... a server is used for the initial handshake. BUT THEN NO SERVER #WebRTC



Article coming soon pic.twitter.com/LRWVt4BhV8 — Swizec Teller (@Swizec) May 14, 2018

That was a fun weekend. Two livecoding sessions. Finally got it working late at night when the cameras weren't rolling. I am nowhere near having it working in time for a true blockchain demo for my WeAreDevelopers talk this Friday. 😅

You can try it out here. Open that link in two browsers, possibly on different machines.

You might have to try a couple of times, it's a little finicky. 🤨

Here's how it works 👇

You can use WebRTC to make browsers talk to each other directly without a server. But because there's no service discovery, you need a signaling server so browsers can find each other.

The flow goes like this:

Client 1 says hi to the server and registers Client 2 says hi to the server and registers Server holds list of identifiers (usernames) Client 1 tells server to call Client 2 Server tells Client 2 there's a call Client 2 answers the call Client 1 and Client 2 are now talking directly

We followed this WebRTC chat example from MDN to model our code.

Signaling is the handshake process between two browsers. Our implementation uses WebSockets to do that.

The server part is the same as the MDN example. Pure copy pasta.

We made some changes to make it work with now.sh. Namely, we removed all SSL stuff. Zeit wraps our servers in a secure SSL server that then talks to our actual server via an unencrypted connection.

That was a painful gotcha to learn.

WebSockets don't work in modern browsers without SSL. And they don't work with self-signed certificates unless you're running localhost. If you want browsers that are not on your machine to talk, you're gonna have to ensure a real SSL cert.

Easiest way is to deploy on now .

Talking to the server happens via WebSockets with a 40 line helper class. Instantiate the class, make a connection, listen for messages.

We create a new WebSocket in connectToSocket , add some callbacks, and hope for the best. The onmessage listener allows us to add additional message listeners later via the messageListeners array.

sendToServer lets us send a JSON object to our server, and addMsgListener lets us add a new message listener. We'll use this to wire up our PeerConnection helper to our server.

Learning our lesson from WebRTC part 1, we split our RTCPeerConnection stuff into a helper class.

It's about 148 lines and handles the whole lifecycle. We talked about the code before, so here's a recap 👇

constructor sets a bunch of instance vars, sets up a new RTCPeerConnection object, tells it which iceServers to use, connects local event listeners, starts listening for messages on our signaling server, and adds our media stream to the peerConnection .

The next step is handleICECandidate , interactive connectivity establishment, which triggers when a new connection is attempted. It pings our signaling server and says "Yo, new ice candidate here".

handleICECandidateEvent = ( event ) => { if ( event . candidate ) { this . signalingConnection . sendToServer ( { type : "new-ice-candidate" , target : this . targetUsername , candidate : event . candidate , } ) ; } } ;

After that, we’ve got the handleNegotiationNeededEvent , which is called when RTCPeerConnection says some negotiation needs to happen. I don't know what makes it say that.

But the function creates a new connection offer, updates the local SDP description, and tells our signaling server that we're trying to call someone.

handleNegotiationNeededEvent = ( ) => { const { username , targetUsername } = this ; this . peerConnection . createOffer ( ) . then ( ( offer ) => this . peerConnection . setLocalDescription ( offer ) ) . then ( ( ) => this . signalingConnection . sendToServer ( { name : username , target : targetUsername , type : "video-offer" , sdp : this . peerConnection . localDescription , } ) ) . catch ( console . error ) ; } ;

Then we have the fun stuff: handling messages from our signaling server.

onSignalingMessage = ( msg ) => { switch ( msg . type ) { case "video-answer" : this . videoAnswer ( msg ) ; break ; case "new-ice-candidate" : this . newICECandidate ( msg ) ; break ; case "hang-up" : this . close ( ) ; break ; } } ;

When a message comes in, we can do a couple different things. Set ourselves up as the answer party, add a new candidate to our connection, or close.

Those functions are thin wrappers over WebRTC APIs.

videoAnswer = ( { sdp } ) => { this . peerConnection . setRemoteDescription ( new RTCSessionDescription ( sdp ) ) . catch ( console . error ) ; } ; newICECandidate = ( { candidate } ) => { this . peerConnection . addIceCandidate ( new RTCIceCandidate ( candidate ) ) ; } ; close = ( ) => { this . peerConnection . close ( ) ; this . peerConnection = null ; this . onClose ( ) ; } ;

That's our PeerConnection object. In theory, we could instantiate many of them to connect to multiple remote machines at the same time.

That will be a fun experiment.

Holding it all together is our WebRTCPeerConnectionWithServer React component. It renders the UI, instantiates both helper classes from above, and handles the user clicking on buttons to drive the process.

You can see the whole file on GitHub.

Here are the salient parts. 👇

call = ( user ) => { this . setState ( { targetUsername : user , } ) ; this . createPeerConnection ( ) ; } ; hangUp = ( ) => { this . signalingConnection . sendToServer ( { name : this . state . username , target : this . state . targetUsername , type : "hang-up" , } ) ; this . peerConnection . close ( ) ; } ; createPeerConnection = ( ) => { if ( this . peerConnection ) return ; this . peerConnection = new PeerConnection ( { gotRemoteStream : this . gotRemoteStream , gotRemoteTrack : this . gotRemoteTrack , signalingConnection : this . signalingConnection , onClose : this . closeVideoCall , localStream : this . state . localStream , username : this . state . username , targetUsername : this . state . targetUsername , } ) ; } ; closeVideoCall = ( ) => { this . remoteVideoRef . current . srcObject && this . remoteVideoRef . current . srcObject . getTracks ( ) . forEach ( ( track ) => track . stop ( ) ) ; this . remoteVideoRef . current . src = null ; this . setState ( { targetUsername : null , callDisabled : false , } ) ; } ;

call is where the fun starts. Saves whom we're calling to state and creates a peer connection.

createPeerConnection passes all the things into our PeerConnection class.

hangUp and closeVideoCall work together to finish our call. We need both because one is user-driven and the other is called when hangup comes from the other side.

There's one message from the signaling server we have to handle in the glue area: An offer for a call.

case "video-offer" : this . createPeerConnection ( ) ; this . peerConnection . videoOffer ( msg ) ; break ;

When the server tells us someone wants to connect, we have to create a new PeerConnection object on our client and handle the offer. Handling the offer means setting a remote SDP description and sending an answer.

videoOffer = ( { sdp } ) => { const { username , targetUsername } = this ; this . peerConnection . setRemoteDescription ( new RTCSessionDescription ( sdp ) ) . then ( ( ) => this . peerConnection . createAnswer ( ) ) . then ( ( answer ) => { return this . peerConnection . setLocalDescription ( answer ) ; } ) . then ( ( ) => { this . signalingConnection . sendToServer ( { name : username , targetUsername : targetUsername , type : "video-answer" , sdp : this . peerConnection . localDescription , } ) ; } ) . catch ( console . error ) ; } ;

👌

If all the stars align, you can now have a call between 2 browsers on different machines without talking to the server again.

OMG IT WORKS! I got two browsers to talk to each other without a server



Well ... a server is used for the initial handshake. BUT THEN NO SERVER #WebRTC



Article coming soon pic.twitter.com/LRWVt4BhV8 — Swizec Teller (@Swizec) May 14, 2018

Did you enjoy this article? 👎 👍

Published on May 16th, 2018 in Front End, Technical

Learned something new?

Want to become a high value JavaScript expert? Here's how it works 👇 Leave your email and I'll send you an Interactive Modern JavaScript Cheatsheet 📖right away. After that you'll get thoughtfully written emails every week about React, JavaScript, and your career. Lessons learned over my 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths. Start with an interactive cheatsheet 📖 Then get thoughtful letters 💌 on mindsets, tactics, and technical skills for your career. "Man, love your simple writing! Yours is the only email I open from marketers and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌" ~ Ashish Kumar Your Name Your Email Your Address Subscribe & Become an expert 💌 Join over 10,000 engineers just like you already improving their JS careers with my letters, workshops, courses, and talks. ✌️

Have a burning question that you think I can answer? I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

Ready to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz components your whole team can understand with React for Data Visualization

Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first 💰 on the side with ServerlessReact.Dev

Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️