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" }] }
8 /* TODO: duplicate in both client.js and host.js */
9 function getPublicKey() {
10 return new Promise( (resolve
, reject
) => {
11 /* Check local storage for public key */
12 if (!window
.localStorage
.getItem('public-key')) {
13 console
.log('public key is undefined')
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 */
51 function pollServer(url
, clientPubKey
, func
) {
52 return new Promise((resolve
, reject
) => {
53 func(url
, clientPubKey
, resolve
, reject
)
57 /* Poll the server. Send get request, wait for timeout, send another request.
58 Do this until...? Can be used for either reconnecting or waiting for answer*/
59 function pollServerForAnswer(url
, data
, resolve
, reject
) {
60 const request
= new XMLHttpRequest()
61 request
.open('GET', url
, true)
62 /* But there is no JSON? */
63 request
.setRequestHeader('Content-Type', 'application/json' )
64 request
.setRequestHeader('X-Strapp-Type', 'client-sdp-offer')
65 request
.setRequestHeader('X-Client-Offer', JSON
.stringify(data
))
66 request
.onreadystatechange
= () => {
67 if (request
.status
=== 200) {
68 if(request
.readyState
=== 4) {
69 console
.log('Client: Recieved response from Host')
71 resolve(request
.response
)
74 else if (request
.status
=== 504) {
75 console
.log('timed out, resending')
76 pollServerTimeout(url
, data
, resolve
, reject
)
79 reject('server unhandled response of status ' + request
.status
)
85 /* Poll server for ice candidates until ice is complete */
86 function pollServerForICECandidate(cpc
) {
87 window
.setInterval(() => {
88 if (cpc
.iceGatheringState
!== 'complete') {
89 return new Promise((resolve
, reject
) => {
90 console
.log('Client: Requesting ICE Candidates from server')
91 const request
= new XMLHttpRequest()
92 request
.open('GET', window
.location
, true)
93 request
.setRequestHeader('Content-Type', 'application/json' )
94 request
.setRequestHeader('X-Strapp-Type', 'ice-candidate-request')
95 request
.onreadystatechange
= () => {
96 if (request
.status
=== 200) {
97 if(request
.readyState
=== 4) {
98 console
.log('Client: Recieved ICE Candidate from Host')
99 resolve(request
.response
)
102 else if (request
.status
=== 204) {
103 console
.log('Ice Candidate unavailable, trying again in one second')
106 reject('server unhandled response of status ' + request
.status
)
109 request
.send(cpc
.pubKey
)
110 }).then((response
) => {
111 console
.log('Client: Adding Ice Candidate ')
112 cpc
.addIceCandidate(response
.candidate
)
114 console
.log('pollServerForICECandidate: ' + err
)
123 /* Create, set, and get client Offer. Poll server for host answer.
124 Set host answer as client remoteDescription */
125 getPublicKey().then((cpk
) => {
126 const cpc
= new RTCPeerConnection(conf
)
127 /* Start data channel */
128 sendChannel
= cpc
.createDataChannel("sendChannel");
129 sendChannel
.onopen
= () => {
130 console
.log('client data channel on line')
131 sendChannel
.onmessage
= (message
) => {
132 console
.log(message
.data
)
134 sendChannel
.send('Hi from the Client')
136 /* Start polling for ice candidate */
139 console
.log(cpc
.iceConnectionState
)
140 cpc
.oniceconnectionstatechange
= () => {
141 console
.log('iceConnectionState = ' + cpc
.iceConnectionState
)
146 cpc
.onnegotiationneeded
= () => {
147 cpc
.createOffer().then((offer
) => {
148 return cpc
.setLocalDescription(offer
)
151 console
.log('Client: Sending offer to host')
154 sdp
: cpc
.localDescription
,
157 return pollServer(window
.location
, offer
, pollServerForAnswer
)
158 }).then((serverResponse
) => {
159 const answer
= JSON
.parse(serverResponse
)
160 console
.log('Client: received host answer')
161 cpc
.setRemoteDescription(answer
.sdp
).then(() => {
162 console
.log('Client: Polling for ICE candidates')
163 pollServerForICECandidate(cpc
)
165 cpc
.onicecandidate
= (event
) => {
166 if (event
.candidate
) {
167 console
.log('Client: Sending ice candidate to host')
168 postServer(window
.location
, JSON
.stringify({
170 ice
: event
.candidate
,
175 console
.log('Client: No more Ice Candidates to send')
179 console
.log('error in sdp handshake: ' + err
)