removed leftover merge text
[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
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
12 local storage */
13 crypto.subtle.generateKey(
14 { name:'RSA-OAEP',
15 modulusLength: 2048,
16 publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
17 hash: {name: "SHA-256"}
18 },
19 true,
20 ['encrypt', 'decrypt']
21 ).then((keyPair) => {
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'))
27 resolve(exportedKey)
28 })
29
30 })
31 }
32 else {
33 resolve(window.localStorage.getItem('publicKey'))
34 }
35 })
36
37 }
38
39 function sendHost(url, data) {
40 const request = new XMLHttpRequest()
41 request.open('GET', url, true)
42 request.setRequestHeader('Content-Type', 'application/json' )
43 request.setRequestHeader('x-strapp-type', 'ice-candidate-submission')
44 request.send(data)
45 }
46
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')
61 console.log(request)
62 resolve(request.response)
63 }
64 }
65 else if (request.status === 504) {
66 console.log('timed out, resending')
67 resolve(requestHostAnswer(url, data))
68 }
69 else {
70 reject('server unhandled response of status ' + request.status)
71 }
72 }
73 request.send(data)
74 })
75 }
76
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']) {
95 case "a":
96 cpc.addIceCandidate(new RTCIceCandidate(response.ice))
97 break
98 case "g": /* Gathering so let interval keep polling */
99 break
100 case "c": /* host iceState == Complete, stop bugging it */
101 clearInterval(intervalID)
102 clearTimeout()
103 break
104 default:
105 console.log('Unhandled iceState in requestHostICE()' + response['iceState'])
106 break
107 }
108 }
109 }
110 else {
111 console.log('server unhandled response of status ' + request.status)
112 clearInterval(intervalID)
113 }
114 }
115 request.send()
116 }
117 else {
118 clearTimeout()
119 clearInterval(intervalID)
120 }
121 }, 5000)
122 }
123
124 /* Create and send offer -> Send ICE Candidates -> Poll for ICE Candidates */
125 getPublicKey().then((cpk) => {
126 let dataChannel
127 console.log('Client: Create and send offer')
128 const cpc = new RTCPeerConnection(conf)
129
130 cpc.oniceconnectionstatechange = () => {
131 console.log('iceConnectionState = ' + cpc.iceConnectionState)
132 }
133
134 cpc.onnegotiationneeded = () => {
135 console.log('negotiation needed!')
136 cpc.createOffer().then((offer) => {
137 return cpc.setLocalDescription(offer)
138 })
139 .then(() => {
140 console.log('Client: Sending offer to host')
141 let offer = {
142 cmd: '> sdp pubKey',
143 sdp: cpc.localDescription,
144 pubKey: cpk.n
145 }
146 return requestHostAnswer(window.location, offer)
147 })
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({
157 cmd: '> ice pubkey',
158 ice: event.candidate,
159 pubKey: cpk.n
160 }))
161 }
162 else {
163 console.log('Client: No more Ice Candidates to send')
164 }
165 }
166
167
168 }).catch( (err) => {
169 console.log('error in sdp handshake: ' + err)
170 })
171 }
172 /* Start data channel, triggers on negotiation needed */
173 dataChannel = cpc.createDataChannel("sendChannel");
174
175 /* Triggered when Host adds track to peer connection */
176 cpc.ontrack = (event) => {
177 let remoteRTPReceivers = cpc.getReceivers()
178 let hostScreen
179 let video = document.querySelector('video')
180 /* TODO: Audio, video, or other track? */
181 console.log(remoteRTPReceivers)
182 console.log(video)
183 hostScreen = new MediaStream([remoteRTPReceivers[0].track])
184 if(!video.srcObject) {
185 video.srcObject = hostScreen
186 }
187 console.log(hostScreen.getVideoTracks())
188 console.log(video.srcObject)
189 video.onloadedmetadata = () => {
190 video.play()
191 }
192 }
193
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)
201 }).then(() => {
202 dataChannel.send(JSON.stringify({
203 "cmd": "> screen dataChannel",
204 "sdp": cpc.localDescription
205 }))
206 })
207 })
208
209
210 }
211 dataChannel.onopen = () => {
212 document.body.innerHTML = (`<div><button> Connection with host established! </button></div> <video controls></video>`)
213 }
214
215 })
216 document.addEventListener('DOMContentLoaded', () => {
217
218 document.body.innerHTML = `<button> Setting up connection with host </button>`
219
220 });