From 01bded344986da59b5b34091b85979e116a85693 Mon Sep 17 00:00:00 2001 From: jordan lavatai Date: Wed, 5 Jul 2017 09:45:04 -0700 Subject: [PATCH] data channel between host + client, sending/receiving ice candidates when needed, doesn't work on firefox or edge --- client.js | 71 ++++++++++++++++++++++++++----------------------------- host.js | 18 ++++++++------ main.js | 24 ++++++++----------- 3 files changed, 54 insertions(+), 59 deletions(-) diff --git a/client.js b/client.js index 676861a..1f62625 100644 --- a/client.js +++ b/client.js @@ -4,13 +4,13 @@ document.title = "Strapp.io Client" body.appendChild(root) document.body = body const conf = {"iceServers": [{ "urls": "stun:stun.1.google.com:19302" }] } +let dataChannel /* TODO: duplicate in both client.js and host.js */ function getPublicKey() { return new Promise( (resolve, reject) => { /* Check local storage for public key */ if (!window.localStorage.getItem('public-key')) { - console.log('public key is undefined') /* If doesn't exist, generate public and private key pair, store in local storage */ crypto.subtle.generateKey( @@ -47,7 +47,8 @@ function postServer(url, data) { request.send(data) } -/* TODO: All this does is wrap a function in a promise */ +/* TODO: All this does is wrap a function in a promise. Allows pollServerForAnswer + to call itself recursively with the same promise */ function pollServer(url, clientPubKey, func) { return new Promise((resolve, reject) => { func(url, clientPubKey, resolve, reject ) @@ -66,14 +67,14 @@ function pollServerForAnswer(url, data, resolve, reject) { request.onreadystatechange = () => { if (request.status === 200) { if(request.readyState === 4) { - console.log('Client: Recieved response from Host') + console.log('Client: Recieved Answer from Host') console.log(request) resolve(request.response) } } else if (request.status === 504) { console.log('timed out, resending') - pollServerTimeout(url, data, resolve, reject) + pollServerForAnswer(url, data, resolve, reject) } else { reject('server unhandled response of status ' + request.status) @@ -83,67 +84,61 @@ function pollServerForAnswer(url, data, resolve, reject) { } /* Poll server for ice candidates until ice is complete */ -function pollServerForICECandidate(cpc) { - window.setInterval(() => { - if (cpc.iceGatheringState.localeCompare('complete') !== 0) { - return new Promise((resolve, reject) => { +function pollServerForICECandidate(cpc, url, pubKey) { + let intervalID = window.setInterval(() => { + if (cpc.iceConnectionState.localeCompare('connected') !== 0 + && cpc.iceConnectionState.localeCompare('completed') !== 0) { + console.log('Client: Polling server begin for intervalID = ' + intervalID) console.log('Client: Requesting ICE Candidates from server') const request = new XMLHttpRequest() - request.open('GET', window.location, true) + request.open('GET', url, true) request.setRequestHeader('Content-Type', 'application/json' ) request.setRequestHeader('X-Strapp-Type', 'ice-candidate-request') + request.setRequestHeader('X-client-pubkey', pubKey) request.onreadystatechange = () => { if (request.status === 200) { if(request.readyState === 4) { console.log('Client: Recieved ICE Candidate from Host') - resolve(request.response) + cpc.addIceCandidate(new RTCIceCandidate(JSON.parse(request.response).ice)) } } else if (request.status === 204) { console.log('Ice Candidate unavailable, trying again in one second') } else { - reject('server unhandled response of status ' + request.status) + console.log('server unhandled response of status ' + request.status) + clearInterval(intervalID) } } - request.send(cpc.pubKey) - }).then((response) => { - console.log('Client: Adding Ice Candidate ') - cpc.addIceCandidate(response.candidate) - }).catch((err) => { - console.log('pollServerForICECandidate: ' + err) - }) + request.send() } else { clearTimeout() + clearInterval(intervalID) } - }, 2000) + }, 5000) } -/* Create, set, and get client Offer. Poll server for host answer. - Set host answer as client remoteDescription */ +/* Create and send offer -> Send ICE Candidates -> Poll for ICE Candidates */ getPublicKey().then((cpk) => { + console.log('Client: Create and send offer') const cpc = new RTCPeerConnection(conf) /* Start data channel */ - sendChannel = cpc.createDataChannel("sendChannel"); - sendChannel.onopen = () => { - console.log('client data channel on line') - sendChannel.onmessage = (message) => { - console.log(message.data) - } - sendChannel.send('Hi from the Client') - }; - /* Start polling for ice candidate */ + dataChannel = cpc.createDataChannel("sendChannel"); + dataChannel.onmessage = (msg) => { + console.log(msg.data) + } + dataChannel.onopen = () => { + dataChannel.send(`Hi from the Client`) + } - console.log(cpc.iceConnectionState) cpc.oniceconnectionstatechange = () => { console.log('iceConnectionState = ' + cpc.iceConnectionState) } - - cpc.onnegotiationneeded = () => { + console.log('negotiation needed!') cpc.createOffer().then((offer) => { return cpc.setLocalDescription(offer) }) @@ -157,11 +152,9 @@ getPublicKey().then((cpk) => { return pollServer(window.location, offer, pollServerForAnswer) }).then((serverResponse) => { const answer = JSON.parse(serverResponse) - console.log('Client: received host answer') - cpc.setRemoteDescription(answer.sdp).then(() => { - console.log('Client: Polling for ICE candidates') - pollServerForICECandidate(cpc) - }) + console.log('Client: Polling for ICE candidates') + pollServerForICECandidate(cpc, window.location, cpk.n) + cpc.setRemoteDescription(answer.sdp) cpc.onicecandidate = (event) => { if (event.candidate) { console.log('Client: Sending ice candidate to host') @@ -175,6 +168,8 @@ getPublicKey().then((cpk) => { console.log('Client: No more Ice Candidates to send') } } + + }).catch( (err) => { console.log('error in sdp handshake: ' + err) }) diff --git a/host.js b/host.js index 9b84f5c..3af3426 100644 --- a/host.js +++ b/host.js @@ -52,8 +52,9 @@ function handleNewClientConnection(offer) { getPublicKey().then((hpk) => { hpc.onicecandidate = (event) => { if (event.candidate) { + console.log('Host: Allocating ice candidate for client') iceCandidates.push(JSON.stringify({ - cmd: '< ice pubKey', + cmd: "< ice pubKey", ice: event.candidate, hostPubKey: hpk.n, /* TODO: do we need to send this? */ clientPubKey: offer.pubKey, @@ -77,7 +78,9 @@ function handleNewClientConnection(offer) { dataChannel.onmessage = (msg) => { console.log(msg.data) } - dataChannel.send('Hi from the host') + dataChannel.onopen = () => { + dataChannel.send(`Hi ${offer.pubKey} -host`) + } } hpc.oniceconnectionstatechange = () => { console.log('iceConnectionState = ' + hpc.iceConnectionState) @@ -100,13 +103,14 @@ function handleNewIceSubmission(msg) { } function handleIceRequest(msg) { - console.log('Host: Sending ice candidate to client') + console.log('Host: Handling ice candidate request') + console.log(iceCandidates) const hpc = clients.get(msg) - const iceCandidates = iceCandidates.pop() + const iceCandidate = iceCandidates.pop() if (iceCandidate !== undefined) { wsock.send(iceCandidate) } else { - wsock.send('no ice candidates') + wsock.send(`{"cmd" : "< ice pubKey", "clientPubKey":"${msg.pubKey}", "iceCandidateAvailable": false}`) } } @@ -120,10 +124,10 @@ if ("WebSocket" in window) { wsock.onmessage = (serverMsg) => { /* msg is either offer or ice candidate or ice candidate request*/ - + /* What if data null? */ let msg = JSON.parse(serverMsg.data) - const clientID = msg.pubKey || msg + const clientID = msg.pubKey /* TODO: redo this trash */ if (clients.has(clientID)) { diff --git a/main.js b/main.js index a5b105b..9ef0b49 100644 --- a/main.js +++ b/main.js @@ -45,7 +45,7 @@ const router = { if (routeName === '' || routeName === 'index.html') - serveFile(opts['index']) + serveFile(opts['index']) else if (routeName in opts['bindings']) { let localPath = path.normalize(opts['bindings'][routeName].concat(path.sep + routePath.slice(1).join(path.sep))) if (localPath.includes(opts['bindings'][routeName])) { @@ -68,6 +68,7 @@ const router = { + /* Client is INIT GET */ if (headerData === undefined) { console.log('client init GET') @@ -77,17 +78,12 @@ const router = { //TODO: if route.socket == undefined: have server delay this send until host connects // (this happens when a client connects to an active route with no currently-online host) } - else if (headerData.localeCompare('ice-candidate-request') === 0){ + else if (headerData.localeCompare('ice-candidate-request') === 0) { console.log('Server: received ice-candidate-request from Client') - let data = [] - request.on('data', (chunk) => { - data.push(chunk) - }).on('end', () => { - data = Buffer.concat(data).toString(); - console.log('Sending ice-candidate-request to Host' + data) - clients.set(data, response) - route.socket.send(data) - }) + let pubKey = request.headers['x-client-pubkey'] + clients.set(pubKey, response) + pubKey = '{ "pubKey": "' + pubKey + '" }' + route.socket.send(pubKey) } else if (headerData.localeCompare('ice-candidate-submission') === 0) { console.log('Server: recieved ice-candidate-submission from Client') @@ -95,7 +91,7 @@ const router = { request.on('data', (chunk) => { data.push(chunk) }).on('end', () => { - console.log('Sending ice-candidate-submission to Host' + data) + console.log('Sending ice-candidate-submission to Host') data = Buffer.concat(data).toString(); clients.set(JSON.parse(data)['pubKey'], response) route.socket.send(data) @@ -119,9 +115,9 @@ const router = { getport().then( (port) => { newRoute.port = port if (opts['no-tls']) - newRoute.httpd = http.createServer() + newRoute.httpd = http.createServer() else - newRoute.httpd = https.createServer(router.httpsOpts) + newRoute.httpd = https.createServer(router.httpsOpts) newRoute.httpd.listen(newRoute.port) newRoute.wsd = new ws.Server( { server: newRoute.httpd } ) newRoute.wsd.on('connection', (sock) => { -- 2.18.0