1 const body
= document
.createElement('body')
2 const root
= document
.createElement('div')
3 document
.title
= "Strapp.io Client"
6 const conf
= {"iceServers": [{ "urls": "stun:stun.1.google.com:19302" }] }
9 /* TODO: duplicate in both client.js and host.js */
10 function getPublicKey() {
11 return new Promise( (resolve
, reject
) => {
12 /* Check local storage for public key */
13 if (!window
.localStorage
.getItem('public-key')) {
14 /* If doesn't exist, generate public and private key pair, store in
16 crypto
.subtle
.generateKey(
19 publicExponent
: new Uint8Array([0x01, 0x00, 0x01]),
20 hash
: {name
: "SHA-256"}
23 ['encrypt', 'decrypt']
25 /* TODO: Do we need to store the private key as well? */
26 crypto
.subtle
.exportKey('jwk', keyPair
.publicKey
)
27 .then((exportedKey
) => {
28 window
.localStorage
.setItem('publicKey', exportedKey
)
29 console
.log('public key is' + window
.localStorage
.getItem('publicKey'))
36 resolve(window
.localStorage
.getItem('publicKey'))
42 function postServer(url
, data
) {
43 const request
= new XMLHttpRequest()
44 request
.open('POST', url
, true)
45 request
.setRequestHeader('Content-Type', 'application/json' )
46 request
.setRequestHeader('X-Strapp-Type', 'ice-candidate-submission')
50 /* TODO: All this does is wrap a function in a promise. Allows pollServerForAnswer
51 to call itself recursively with the same promise */
52 function pollServer(url
, clientPubKey
, func
) {
53 return new Promise((resolve
, reject
) => {
54 func(url
, clientPubKey
, resolve
, reject
)
58 /* Poll the server. Send get request, wait for timeout, send another request.
59 Do this until...? Can be used for either reconnecting or waiting for answer*/
60 function pollServerForAnswer(url
, data
, resolve
, reject
) {
61 const request
= new XMLHttpRequest()
62 request
.open('GET', url
, true)
63 /* But there is no JSON? */
64 request
.setRequestHeader('Content-Type', 'application/json' )
65 request
.setRequestHeader('X-Strapp-Type', 'client-sdp-offer')
66 request
.setRequestHeader('X-Client-Offer', JSON
.stringify(data
))
67 request
.onreadystatechange
= () => {
68 if (request
.status
=== 200) {
69 if(request
.readyState
=== 4) {
70 console
.log('Client: Recieved Answer from Host')
72 resolve(request
.response
)
75 else if (request
.status
=== 504) {
76 console
.log('timed out, resending')
77 pollServerForAnswer(url
, data
, resolve
, reject
)
80 reject('server unhandled response of status ' + request
.status
)
86 /* Poll server for ice candidates until ice is complete */
87 function pollServerForICECandidate(cpc
, url
, pubKey
) {
88 let intervalID
= window
.setInterval(() => {
89 if (cpc
.iceConnectionState
.localeCompare('connected') !== 0
90 && cpc
.iceConnectionState
.localeCompare('completed') !== 0) {
91 console
.log('Client: Polling server begin for intervalID = ' + intervalID
)
92 console
.log('Client: Requesting ICE Candidates from server')
93 const request
= new XMLHttpRequest()
94 request
.open('GET', url
, true)
95 request
.setRequestHeader('Content-Type', 'application/json' )
96 request
.setRequestHeader('X-Strapp-Type', 'ice-candidate-request')
97 request
.setRequestHeader('X-client-pubkey', pubKey
)
98 request
.onreadystatechange
= () => {
99 if (request
.status
=== 200) {
100 if(request
.readyState
=== 4) {
101 console
.log('Client: Recieved ICE response from Host')
102 let response
= JSON
.parse(request
.response
)
103 switch(response
['iceState']) {
105 cpc
.addIceCandidate(new RTCIceCandidate(response
.ice
))
107 case "g": /* Gathering so let interval keep polling */
109 case "c": /* host iceState == Complete, stop bugging it */
110 clearInterval(intervalID
)
114 console
.log('Unhandled iceState in pollServerForICECandidate()' + response
['iceState'])
120 console
.log('server unhandled response of status ' + request
.status
)
121 clearInterval(intervalID
)
128 clearInterval(intervalID
)
133 /* Create and send offer -> Send ICE Candidates -> Poll for ICE Candidates */
134 getPublicKey().then((cpk
) => {
135 console
.log('Client: Create and send offer')
136 const cpc
= new RTCPeerConnection(conf
)
138 cpc
.oniceconnectionstatechange
= () => {
139 console
.log('iceConnectionState = ' + cpc
.iceConnectionState
)
142 cpc
.onnegotiationneeded
= () => {
143 console
.log('negotiation needed!')
144 cpc
.createOffer().then((offer
) => {
145 return cpc
.setLocalDescription(offer
)
148 console
.log('Client: Sending offer to host')
151 sdp
: cpc
.localDescription
,
154 return pollServer(window
.location
, offer
, pollServerForAnswer
)
155 }).then((serverResponse
) => {
156 const answer
= JSON
.parse(serverResponse
)
157 console
.log('Client: Polling for ICE candidates')
158 pollServerForICECandidate(cpc
, window
.location
, cpk
.n
)
159 cpc
.setRemoteDescription(answer
.sdp
)
160 cpc
.onicecandidate
= (event
) => {
161 if (event
.candidate
) {
162 console
.log('Client: Sending ice candidate to host')
163 postServer(window
.location
, JSON
.stringify({
165 ice
: event
.candidate
,
170 console
.log('Client: No more Ice Candidates to send')
176 console
.log('error in sdp handshake: ' + err
)
179 /* Start data channel */
180 dataChannel
= cpc
.createDataChannel("sendChannel");
181 dataChannel
.onmessage
= (msg
) => {
182 console
.log(msg
.data
)
184 dataChannel
.onopen
= () => {
185 dataChannel
.send(`Hi from the Client`)