Last active
April 11, 2022 18:02
-
-
Save noonat/91286152bf1882c658bf to your computer and use it in GitHub Desktop.
WebRTC Data Channels
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is a bare bones example of creating a data channel between two WebRTC | |
// peers. Let's imagine two peers trying to connect to each other. We'll call | |
// one the "offer peer", and the other the "answer peer". The offer peer will | |
// be the one initiating a connection, and the answer peer will be the one | |
// responding to it. | |
// | |
// The two peers must use a separate connection to negotiate their connection. | |
// A websocket connection to a shared server is often used for this negotiation. | |
// They will exchange offers and answers using the websocket. Each peer will | |
// also attempt to discover more details about themselves (ICE), such as their | |
// public and private IP addresses. A candidate will be generated for each | |
// potential way the other peer could connect to them, and that candidate must | |
// also be sent to the other peer over the websocket. | |
// | |
// Eventually, the two will hopefully establish a connection, at which time | |
// they can begin communicating with each other directly and forget about the | |
// websocket connection. | |
// | |
// In more detail: | |
// | |
// 1. Offer peer creates a peer connection. | |
// 2. Offer peer listens for ICE candidates on its connection. The handler will | |
// be called for each candidate it discovers, and those candidates should be | |
// sent to answer peer over some other transport, like a websocket | |
// connection to a shared server. | |
// 3. Offer peer uses that connection to create a data channel. Note that only | |
// the offering side actually creates a data channel. | |
// 4. Offer peer creates an offer object (which includes information about the | |
// data channel). | |
// 5. Offer peer sets the local description for its connection to the offer | |
// it created. | |
// 6. Offer peer sends that offer to answer peer via a websocket connection. | |
// 7. Answer peer creates a peer connection. | |
// 8. Answer peer listens for ICE candidates, and sends each candidate to offer | |
// peer via a websocket connection. | |
// 9. Answer peer receives the offer via a websocket connection. | |
// 10. Answer peer sets the remote description for its connection to the offer | |
// it received. | |
// 11. Answer peer creates a corresponding answer object. | |
// 12. Answer peer sets the local description for its connection to the answer | |
// it created. | |
// 13. Answer peer sends its answer to offer peer via a websocket connection. | |
// 14. A data channel event is triggered on answer peer's connection. The | |
// channel on the event should be used to communicate with offer peer. | |
// 14. Offer peer recieves the answer via a websocket connection. | |
// 15. Offer peer sets the remote description for its connection to the answer | |
// it received. | |
// 16. When the connection is established, an open event will be triggered for | |
// both data channels, and the peers can start sending direct messages. | |
var RTCPeerConnection = (window.RTCPeerConnection || | |
window.mozRTCPeerConnection || | |
window.webkitRTCPeerConnection); | |
var RTCSessionDescription = (window.RTCSessionDescription || | |
window.mozRTCSessionDescription || | |
window.webkitRTCSessionDescription); | |
// This configuration is passed when we create a new peer connection object. | |
// It provides a set of servers used to establish a connection. STUN servers | |
// are used to discover our external IP address, and TURN servers (none listed | |
// here) are used to proxy a connection when a peer is behind a restrictive | |
// firewall that prevents a direct connection. | |
var peerConnectionConfig = { | |
'iceServers': [ | |
{'url': 'stun:stun.l.google.com:19302'}, | |
{'url': 'stun:stun1.l.google.com:19302'}, | |
{'url': 'stun:stun2.l.google.com:19302'}, | |
{'url': 'stun:stun3.l.google.com:19302'}, | |
{'url': 'stun:stun4.l.google.com:19302'} | |
] | |
}; | |
// Think of offerPeer as our local client. It's going to create a data channel | |
// and an offer and send them to the other peer. | |
var offerPeer = new RTCPeerConnection(peerConnectionConfig); | |
offerPeer.onicecandidate = onOfferPeerIceCandidate; | |
// Think of answerPeer as our remote server. It's going to accept an offer | |
// from offerPeer, and create a corresponding answer. | |
var answerPeer = new RTCPeerConnection(peerConnectionConfig); | |
answerPeer.ondatachannel = onAnswerPeerDataChannel; | |
answerPeer.onicecandidate = onAnswerPeerIceCandidate; | |
// Create the offered data channel. Note that this must happen before the | |
// offer is created, so that it is included in the offer. | |
var offerDataChannel = offerPeer.createDataChannel('dataChannel', { | |
// This configuration basically makes it act like a UDP connection, which is | |
// useful for games. You can specify different options to a get more TCP-like | |
// connection instead. | |
maxRetransmits: 0, | |
reliable: false | |
}); | |
addDataChannelListeners(offerDataChannel, 'offerPeer'); | |
// Create an offer to send to answerPeer. Like most operations with the WebRTC | |
// API, this is asynchronous. Our callback will be invoked once an offer has | |
// been created. | |
offerPeer.createOffer(onCreatedOffer, handleError('error creating offer')); | |
// This is used to attach generic logging handlers for data channels. | |
function addDataChannelListeners(channel, label) { | |
channel.onclose = function(event) { | |
console.log(label + ' data channel close', event); | |
}; | |
channel.onerror = function(err) { | |
console.log(label + ' data channel error', event); | |
}; | |
channel.onmessage = function(event) { | |
console.log(label + ' data channel message', event); | |
}; | |
channel.onopen = function(event) { | |
console.log(label + ' data channel open', event); | |
channel.send('hello from ' + label); | |
}; | |
} | |
// Generic error handler to just log a message. | |
function handleError(message) { | |
return function onError(err) { | |
console.log(message); | |
}; | |
} | |
// This will be called for each offer candidate. A candidate is a potential | |
// address that the other peer can attempt to connect to. Note that | |
// event.candidate can be null, so we must guard against that. The two peers | |
// will exchange candidates until they find a connection that works. | |
function onOfferPeerIceCandidate(event) { | |
console.log('offerPeer ice candidate', event); | |
if (event && event.candidate) { | |
// These would normally be sent to answerPeer over some other transport, | |
// like a websocket, but since this is local we can just set it here. | |
answerPeer.addIceCandidate(event.candidate); | |
} | |
} | |
// This will be called when answerPeer receives the offer, since a data channel | |
// was included in the offer. event.channel will be answerPeer's end of the | |
// channel. Note that answerPeer does not create a data channel directly, only | |
// uses this one created as a side effect of the offer. | |
function onAnswerPeerDataChannel(event) { | |
console.log('answerPeer data channel', event); | |
addDataChannelListeners(event.channel, 'answerPeer'); | |
} | |
// The answer peer will also have this method called for each candidate. | |
function onAnswerPeerIceCandidate(event) { | |
console.log('answerPeer ice candidate', event); | |
if (event && event.candidate) { | |
// These would normally be sent to offerPeer over some other transport, | |
// like a websocket, but since this is local we can just set it here. | |
offerPeer.addIceCandidate(event.candidate); | |
} | |
} | |
// This is called once offerPeer has created its offer, so we can set it | |
// locally and send it over the wire to answerPeer. | |
function onCreatedOffer(offer) { | |
console.log('created offer', offer); | |
offer = new RTCSessionDescription(offer); | |
offerPeer.setLocalDescription(offer, onSetLocalOffer, | |
handleError('error setting local description for offerPeer')); | |
// Normally we would send this over the wire to answerPeer, but since this is | |
// all local, we can just set it here. | |
answerPeer.setRemoteDescription(offer, onSetRemoteOffer, | |
handleError('error setting remote description for answerPeer')); | |
} | |
// This is called once our local description has been set for offerPeer. | |
function onSetLocalOffer() { | |
console.log('set local description for offerPeer'); | |
} | |
// This is called once our remote description has been set for answerPeer. | |
// Once we have a remote offer, we can create a corresponding answer. | |
function onSetRemoteOffer() { | |
console.log('set remote description for answerPeer'); | |
answerPeer.createAnswer(onCreatedAnswer, | |
handleError('error creating answer')); | |
} | |
// This is called once answerPeer has created its answer, so we can set it | |
// locally and send it over the wire to offerPeer -- just like we did with | |
// the offer, but the other way around. | |
function onCreatedAnswer(answer) { | |
console.log('created answer'); | |
answer = new RTCSessionDescription(answer); | |
answerPeer.setLocalDescription(answer, onSetLocalAnswer, | |
handleError('error setting answerPeer local description')); | |
// Normally we would send this over the wire to offerPeer, but since this is | |
// all local, we can just set it here. | |
offerPeer.setRemoteDescription(answer, onSetRemoteAnswer, | |
handleError('error setting offerPeer remote description')); | |
} | |
function onSetLocalAnswer() { | |
console.log('set local description for answerPeer'); | |
} | |
function onSetRemoteAnswer() { | |
console.log('set remote description for offerPeer'); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment