1 const body
= document
.createElement('body')
2 const root
= document
.createElement('div')
3 document
.title
= "Strapp.io Client"
4 const conf
= {"iceServers": [{ "urls": "stun:stun.1.google.com:19302" }] }
6 /* TODO: This is duplicated in both client.js and host.js */
7 function getPublicKey() {
8 return new Promise( (resolve
, reject
) => {
9 /* Check local storage for public key */
10 if (!window
.localStorage
.getItem('public-key')) {
11 /* If doesn't exist, generate public and private key pair, store in
13 crypto
.subtle
.generateKey(
16 publicExponent
: new Uint8Array([0x01, 0x00, 0x01]),
17 hash
: {name
: "SHA-256"}
20 ['encrypt', 'decrypt']
22 /* TODO: Do we need to store the private key as well? */
23 crypto
.subtle
.exportKey('jwk', keyPair
.publicKey
)
24 .then((exportedKey
) => {
25 window
.localStorage
.setItem('publicKey', exportedKey
)
26 console
.log('public key is' + window
.localStorage
.getItem('publicKey'))
33 resolve(window
.localStorage
.getItem('publicKey'))
39 function sendHost(url
, data
) {
40 const request
= new XMLHttpRequest()
41 request
.open('POST', url
, true)
42 request
.setRequestHeader('Content-Type', 'application/json' )
43 request
.setRequestHeader('X-Strapp-Type', 'ice-candidate-submission')
47 /* Poll the server. Send get request, wait for timeout, send another request.
48 Do this until...? Can be used for either reconnecting or waiting for answer*/
49 function requestHostAnswer(url
, data
) {
50 return new Promise((resolve
, reject
) => {
51 const request
= new XMLHttpRequest()
52 request
.open('GET', url
, true)
53 /* But there is no JSON? */
54 request
.setRequestHeader('Content-Type', 'application/json' )
55 request
.setRequestHeader('X-Strapp-Type', 'client-sdp-offer')
56 request
.setRequestHeader('X-Strapp-Pubkey', data
.pubKey
)
57 request
.onreadystatechange
= () => {
58 if (request
.status
=== 200) {
59 if(request
.readyState
=== 4) {
60 console
.log('Client: Recieved Answer from Host')
62 resolve(request
.response
)
65 else if (request
.status
=== 504) {
66 console
.log('timed out, resending')
67 resolve(requestHostAnswer(url
, data
))
70 reject('server unhandled response of status ' + request
.status
)
77 /* Poll server for ice candidates until ice is complete */
78 function requestHostICE(cpc
, url
, pubKey
) {
79 let intervalID
= window
.setInterval(() => {
80 if (cpc
.iceConnectionState
.localeCompare('connected') !== 0
81 && cpc
.iceConnectionState
.localeCompare('completed') !== 0) {
82 console
.log('Client: Polling server begin for intervalID = ' + intervalID
)
83 console
.log('Client: Requesting ICE Candidates from server')
84 const request
= new XMLHttpRequest()
85 request
.open('GET', url
, true)
86 request
.setRequestHeader('Content-Type', 'application/json' )
87 request
.setRequestHeader('X-Strapp-Type', 'ice-candidate-request')
88 request
.setRequestHeader('X-client-pubkey', pubKey
)
89 request
.onreadystatechange
= () => {
90 if (request
.status
=== 200) {
91 if(request
.readyState
=== 4) {
92 console
.log('Client: Recieved ICE response from Host')
93 let response
= JSON
.parse(request
.response
)
94 switch(response
['iceState']) {
96 cpc
.addIceCandidate(new RTCIceCandidate(response
.ice
))
98 case "g": /* Gathering so let interval keep polling */
100 case "c": /* host iceState == Complete, stop bugging it */
101 clearInterval(intervalID
)
105 console
.log('Unhandled iceState in requestHostICE()' + response
['iceState'])
111 console
.log('server unhandled response of status ' + request
.status
)
112 clearInterval(intervalID
)
119 clearInterval(intervalID
)
124 /* Create and send offer -> Send ICE Candidates -> Poll for ICE Candidates */
125 getPublicKey().then((cpk
) => {
127 console
.log('Client: Create and send offer')
128 const cpc
= new RTCPeerConnection(conf
)
130 cpc
.oniceconnectionstatechange
= () => {
131 console
.log('iceConnectionState = ' + cpc
.iceConnectionState
)
134 cpc
.onnegotiationneeded
= () => {
135 console
.log('negotiation needed!')
136 cpc
.createOffer().then((offer
) => {
137 return cpc
.setLocalDescription(offer
)
140 console
.log('Client: Sending offer to host')
143 sdp
: cpc
.localDescription
,
146 return requestHostAnswer(window
.location
, offer
)
148 .then((serverResponse
) => {
149 const answer
= JSON
.parse(serverResponse
)
150 console
.log('Client: Polling for ICE candidates')
151 requestHostICE(cpc
, window
.location
, cpk
.n
)
152 cpc
.setRemoteDescription(answer
.sdp
)
153 cpc
.onicecandidate
= (event
) => {
154 if (event
.candidate
) {
155 console
.log('Client: Sending ice candidate to host')
156 sendHost(window
.location
, JSON
.stringify({
158 ice
: event
.candidate
,
163 console
.log('Client: No more Ice Candidates to send')
169 console
.log('error in sdp handshake: ' + err
)
172 /* Start data channel, triggers on negotiation needed */
173 dataChannel
= cpc
.createDataChannel("sendChannel");
175 /* Triggered when Host adds track to peer connection */
176 cpc
.ontrack
= (event
) => {
177 let remoteRTPReceivers
= cpc
.getReceivers()
179 let video
= document
.querySelector('video')
180 /* TODO: Audio, video, or other track? */
181 console
.log(remoteRTPReceivers
)
183 hostScreen
= new MediaStream([remoteRTPReceivers
[0].track
])
184 if(!video
.srcObject
) {
185 video
.srcObject
= hostScreen
187 console
.log(hostScreen
.getVideoTracks())
188 console
.log(video
.srcObject
)
189 video
.onloadedmetadata
= () => {
194 dataChannel
.onmessage
= (msg
) => {
195 /* Get mediaStream from host and add it to the video */
196 let hostMessage
= JSON
.parse(msg
.data
)
197 console
.log('Client: Renego')
198 cpc
.setRemoteDescription(hostMessage
.sdp
).then(() => {
199 cpc
.createAnswer().then((answer
) => {
200 return cpc
.setLocalDescription(answer
)
202 dataChannel
.send(JSON
.stringify({
203 "cmd": "> screen dataChannel",
204 "sdp": cpc
.localDescription
211 dataChannel
.onopen
= () => {
212 document
.body
.innerHTML
= (`<div><button> Connection with host established! </button></div> <video controls></video>`)
216 document
.addEventListener('DOMContentLoaded', () => {
218 document
.body
.innerHTML
= `<button> Setting up connection with host </button>`