adding test files
[henge/kiak.git] / client.js
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" }] }
5 let dataChannel
6 let hostScreen
7
8 /* TODO: This is duplicated 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 /* If doesn't exist, generate public and private key pair, store in
14 local storage */
15 crypto.subtle.generateKey(
16 { name:'RSA-OAEP',
17 modulusLength: 2048,
18 publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
19 hash: {name: "SHA-256"}
20 },
21 true,
22 ['encrypt', 'decrypt']
23 ).then((keyPair) => {
24 /* TODO: Do we need to store the private key as well? */
25 crypto.subtle.exportKey('jwk', keyPair.publicKey)
26 .then((exportedKey) => {
27 window.localStorage.setItem('publicKey', exportedKey)
28 console.log('public key is' + window.localStorage.getItem('publicKey'))
29 resolve(exportedKey)
30 })
31
32 })
33 }
34 else {
35 resolve(window.localStorage.getItem('publicKey'))
36 }
37 })
38
39 }
40
41 function sendHost(url, data) {
42 const request = new XMLHttpRequest()
43 request.open('POST', url, true)
44 request.setRequestHeader('Content-Type', 'application/json' )
45 request.setRequestHeader('X-Strapp-Type', 'ice-candidate-submission')
46 request.send(data)
47 }
48
49 /* Poll the server. Send get request, wait for timeout, send another request.
50 Do this until...? Can be used for either reconnecting or waiting for answer*/
51 function requestHostAnswer(url, data) {
52 return new Promise((resolve, reject) => {
53 const request = new XMLHttpRequest()
54 request.open('GET', url, true)
55 /* But there is no JSON? */
56 request.setRequestHeader('Content-Type', 'application/json' )
57 request.setRequestHeader('X-Strapp-Type', 'client-sdp-offer')
58 request.setRequestHeader('X-Strapp-Pubkey', data.pubKey)
59 request.onreadystatechange = () => {
60 if (request.status === 200) {
61 if(request.readyState === 4) {
62 console.log('Client: Recieved Answer from Host')
63 console.log(request)
64 resolve(request.response)
65 }
66 }
67 else if (request.status === 504) {
68 console.log('timed out, resending')
69 resolve(requestHostAnswer(url, data))
70 }
71 else {
72 reject('server unhandled response of status ' + request.status)
73 }
74 }
75 request.send(data)
76 })
77 }
78
79 /* Poll server for ice candidates until ice is complete */
80 function requestHostICE(cpc, url, pubKey) {
81 let intervalID = window.setInterval(() => {
82 if (cpc.iceConnectionState.localeCompare('connected') !== 0
83 && cpc.iceConnectionState.localeCompare('completed') !== 0) {
84 console.log('Client: Polling server begin for intervalID = ' + intervalID)
85 console.log('Client: Requesting ICE Candidates from server')
86 const request = new XMLHttpRequest()
87 request.open('GET', url, true)
88 request.setRequestHeader('Content-Type', 'application/json' )
89 request.setRequestHeader('X-Strapp-Type', 'ice-candidate-request')
90 request.setRequestHeader('X-client-pubkey', pubKey)
91 request.onreadystatechange = () => {
92 if (request.status === 200) {
93 if(request.readyState === 4) {
94 console.log('Client: Recieved ICE response from Host')
95 let response = JSON.parse(request.response)
96 switch(response['iceState']) {
97 case "a":
98 cpc.addIceCandidate(new RTCIceCandidate(response.ice))
99 break
100 case "g": /* Gathering so let interval keep polling */
101 break
102 case "c": /* host iceState == Complete, stop bugging it */
103 clearInterval(intervalID)
104 clearTimeout()
105 break
106 default:
107 console.log('Unhandled iceState in requestHostICE()' + response['iceState'])
108 break
109 }
110 }
111 }
112 else {
113 console.log('server unhandled response of status ' + request.status)
114 clearInterval(intervalID)
115 }
116 }
117 request.send()
118 }
119 else {
120 clearTimeout()
121 clearInterval(intervalID)
122 }
123 }, 5000)
124 }
125
126 /* Create and send offer -> Send ICE Candidates -> Poll for ICE Candidates */
127 getPublicKey().then((cpk) => {
128 console.log('Client: Create and send offer')
129 const cpc = new RTCPeerConnection(conf)
130
131 cpc.oniceconnectionstatechange = () => {
132 console.log('iceConnectionState = ' + cpc.iceConnectionState)
133 }
134
135 cpc.onnegotiationneeded = () => {
136 console.log('negotiation needed!')
137 cpc.createOffer().then((offer) => {
138 return cpc.setLocalDescription(offer)
139 })
140 .then(() => {
141 console.log('Client: Sending offer to host')
142 let offer = {
143 cmd: '> sdp pubKey',
144 sdp: cpc.localDescription,
145 pubKey: cpk.n
146 }
147 return requestHostAnswer(window.location, offer)
148 })
149 .then((serverResponse) => {
150 const answer = JSON.parse(serverResponse)
151 console.log('Client: Polling for ICE candidates')
152 requestHostICE(cpc, window.location, cpk.n)
153 cpc.setRemoteDescription(answer.sdp)
154 cpc.onicecandidate = (event) => {
155 if (event.candidate) {
156 console.log('Client: Sending ice candidate to host')
157 sendHost(window.location, JSON.stringify({
158 cmd: '> ice pubkey',
159 ice: event.candidate,
160 pubKey: cpk.n
161 }))
162 }
163 else {
164 console.log('Client: No more Ice Candidates to send')
165 }
166 }
167
168
169 }).catch( (err) => {
170 console.log('error in sdp handshake: ' + err)
171 })
172 }
173 /* Start data channel, triggers on negotiation needed */
174 dataChannel = cpc.createDataChannel("sendChannel");
175
176 /* Triggered when Host adds track to peer connection */
177 cpc.ontrack = (event) => {
178 console.log(`track event is ${event}`)
179 let remoteRTPSenders = cpc.getSenders()
180 let remoteRTPReceivers = cpc.getReceivers()
181 console.log(remoteRTPReceivers)
182 /* Add each remoteRTPSenders.track to own stream */
183 let video = document.querySelector('video')
184 video.autoplay = true
185 console.log(video)
186 hostScreen = new MediaStream([remoteRTPReceivers[0].track])
187 if(!video.srcObject) {
188 video.srcObject = hostScreen
189 }
190 console.log(hostScreen.getVideoTracks())
191 console.log(video.srcObject)
192 video.play()
193 video.onloadedmetadata = () => {
194 video.play()
195 }
196 }
197
198 dataChannel.onmessage = (msg) => {
199 /* Get mediaStream from host and add it to the video */
200 let hostMessage = JSON.parse(msg.data)
201 console.log('Client: Renego')
202 cpc.setRemoteDescription(hostMessage.sdp).then(() => {
203 cpc.createAnswer().then((answer) => {
204 return cpc.setLocalDescription(answer)
205 }).then(() => {
206 dataChannel.send(JSON.stringify({
207 "cmd": "> screen dataChannel",
208 "sdp": cpc.localDescription
209 }))
210 })
211 })
212
213
214 }
215 dataChannel.onopen = () => {
216 document.body.innerHTML = (`<div><button> Connection with host established! </button></div> <video controls></video>`)
217 }
218
219 })
220 document.addEventListener('DOMContentLoaded', () => {
221
222 document.body.innerHTML = `<button> Setting up connection with host </button>`
223
224 });