Merge branch 'master' of github.com:Jlavatai/strapp
[henge/kiak.git] / client-test.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('POST', 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.setRequestHeader('X-Strapp-Offer', JSON.stringify(data.sdp))
58 request.onreadystatechange = () => {
59 if (request.status === 200) {
60 if(request.readyState === 4) {
61 console.log('Client: Recieved Answer from Host')
62 console.log(request)
63 resolve(request.response)
64 }
65 }
66 else if (request.status === 504) {
67 console.log('timed out, resending')
68 resolve(requestHostAnswer(url, data))
69 }
70 else {
71 reject('server unhandled response of status ' + request.status)
72 }
73 }
74 request.send()
75 })
76 }
77
78 /* Poll server for ice candidates until ice is complete */
79 function requestHostICE(cpc, url, pubKey) {
80 let intervalID = window.setInterval(() => {
81 if (cpc.iceConnectionState.localeCompare('connected') !== 0
82 && cpc.iceConnectionState.localeCompare('completed') !== 0) {
83 console.log('Client: Polling server begin for intervalID = ' + intervalID)
84 console.log('Client: Requesting ICE Candidates from server')
85 const request = new XMLHttpRequest()
86 request.open('GET', url, true)
87 request.setRequestHeader('Content-Type', 'application/json' )
88 request.setRequestHeader('X-Strapp-Type', 'ice-candidate-request')
89 request.setRequestHeader('X-client-pubkey', pubKey)
90 request.onreadystatechange = () => {
91 if (request.status === 200) {
92 if(request.readyState === 4) {
93 console.log('Client: Recieved ICE response from Host')
94 let response = JSON.parse(request.response)
95 switch(response['iceState']) {
96 case "a":
97 cpc.addIceCandidate(new RTCIceCandidate(response.ice))
98 break
99 case "g": /* Gathering so let interval keep polling */
100 break
101 case "c": /* host iceState == Complete, stop bugging it */
102 clearInterval(intervalID)
103 clearTimeout()
104 break
105 default:
106 console.log('Unhandled iceState in requestHostICE()' + response['iceState'])
107 break
108 }
109 }
110 }
111 else {
112 console.log('server unhandled response of status ' + request.status)
113 clearInterval(intervalID)
114 }
115 }
116 request.send()
117 }
118 else {
119 clearTimeout()
120 clearInterval(intervalID)
121 }
122 }, 5000)
123 }
124
125 /* Create and send offer -> Send ICE Candidates -> Poll for ICE Candidates */
126 getPublicKey().then((cpk) => {
127 let dataChannel
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 console.log(offer)
148 return requestHostAnswer(window.location, offer)
149 })
150 .then((serverResponse) => {
151 const answer = JSON.parse(serverResponse)
152 console.log('Client: Polling for ICE candidates')
153 requestHostICE(cpc, window.location, cpk.n)
154 cpc.setRemoteDescription(answer.sdp)
155 cpc.onicecandidate = (event) => {
156 if (event.candidate) {
157 console.log('Client: Sending ice candidate to host')
158 sendHost(window.location, JSON.stringify({
159 cmd: '> ice pubkey',
160 ice: event.candidate,
161 pubKey: cpk.n
162 }))
163 }
164 else {
165 console.log('Client: No more Ice Candidates to send')
166 }
167 }
168
169
170 }).catch( (err) => {
171 console.log('error in sdp handshake: ' + err)
172 })
173 }
174 /* Start data channel, triggers on negotiation needed */
175 dataChannel = cpc.createDataChannel("sendChannel");
176
177 /* Triggered when Host adds track to peer connection */
178 cpc.ontrack = (event) => {
179 let remoteRTPReceivers = cpc.getReceivers()
180 let hostScreen
181 let video = document.querySelector('video')
182 /* TODO: Audio, video, or other track? */
183 console.log(remoteRTPReceivers)
184 console.log(video)
185 hostScreen = new MediaStream([remoteRTPReceivers[0].track])
186 if(!video.srcObject) {
187 video.srcObject = hostScreen
188 }
189 console.log(hostScreen.getVideoTracks())
190 console.log(video.srcObject)
191 video.onloadedmetadata = () => {
192 video.play()
193 }
194 }
195
196 dataChannel.onmessage = (msg) => {
197 /* Get mediaStream from host and add it to the video */
198 let hostMessage = JSON.parse(msg.data)
199 console.log('Client: Renego')
200 cpc.setRemoteDescription(hostMessage.sdp).then(() => {
201 cpc.createAnswer().then((answer) => {
202 return cpc.setLocalDescription(answer)
203 }).then(() => {
204 dataChannel.send(JSON.stringify({
205 "cmd": "> screen dataChannel",
206 "sdp": cpc.localDescription
207 }))
208 })
209 })
210
211
212 }
213 dataChannel.onopen = () => {
214 document.body.innerHTML = (`<div><button> Connection with host established! </button></div> <video controls></video>`)
215 }
216
217 })
218 document.addEventListener('DOMContentLoaded', () => {
219
220 document.body.innerHTML = `<button> Setting up connection with host </button>`
221
222 });
223