Skip to content

Instantly share code, notes, and snippets.

@matt-wratt
Created July 26, 2017 12:06
Show Gist options
  • Save matt-wratt/3957539de5f2030b8b57904ec0de9fce to your computer and use it in GitHub Desktop.
Save matt-wratt/3957539de5f2030b8b57904ec0de9fce to your computer and use it in GitHub Desktop.
A "simple" example of setting up, connecting, and sending messages over WebRTC
// Please excuse the functional fluff
const tap = fn => v => (fn(v), v)
const log = key => (...args) => console.log(key, ...args)
const pipe = (...fs) => v => fs.reduce((v, fn) => fn(v), v)
const prop = k => v => v[k]
const at = (...ks) => v => ks.reduce((a, b) => prop(b)(a), v)
const error = when => err => console.error(when, err)
const connection = (name) => {
const pc = new RTCPeerConnection({
iceServers: [{ url: 'stun:stun.l.google.com:19302' }]
}, null)
pc.onsignalingstatechange = pipe(
at('target', 'signalingState'),
log(name + ' signal state change')
)
pc.ondatachannel = e => {
log(name + ' received data channel')
e.channel.onerror = error(name + ' channel')
e.channel.onmessage = log(name + ' channel message')
e.channel.onopen = log(name + ' channel open')
e.channel.onclose = log(name + ' channel close')
}
const channel = pc.createDataChannel(name, null)
channel.onerror = error(name + ' channel')
channel.onmessage = log(name + ' channel message')
channel.onopen = () => channel.send('hello from ' + name)
channel.onclose = log(name + ' channel close')
const ice = []
const client = {
name: name,
pc: pc,
channel: channel,
ice: ice,
remote: null,
}
pc.onicecandidate = e => {
if (!e || !e.candidate) return
log(name + ' ice candidate')(e.candidate)
ice.push(e.candidate)
if (!client.remote) return
addIce(client.remote)(e.candidate)
.then(log(name + ' added ice'))
.catch(error(name + ' ice failed'))
}
return client
}
const addIce = client => ice => {
log(client.name + ' added ice')(ice)
return client.pc.addIceCandidate(new RTCIceCandidate(ice))
}
const swapIce = (a, b) => () => {
a.remote = b
b.remote = a
return Promise.all([
...a.ice.map(addIce(b)),
...b.ice.map(addIce(a))
])
}
const clientA = connection('Client A')
const clientB = connection('Client B')
clientA.pc.createOffer()
.then(tap(log('offer')))
.then(tap(offer => clientA.pc.setLocalDescription(offer)))
// new RTCSessionDescription isn't required, but would be if the offer was being deserialized from json for example.
.then(offer => clientB.pc.setRemoteDescription(new RTCSessionDescription(offer)))
.then(() => clientB.pc.createAnswer())
.then(tap(log('answer')))
.then(tap(answer => clientB.pc.setLocalDescription(answer)))
.then(answer => clientA.pc.setRemoteDescription(new RTCSessionDescription(answer)))
.then(swapIce(clientA, clientB))
.then(() => log('connected')(clientA, clientB))
.then(() => window.clients = { a: clientA, b: clientB })
.catch(error('connection failed'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment