UNPKG

19.7 kBJavaScriptView Raw
1window.onbeforeunload = function (e) {
2 var dialogText = 'Please, close the appointment by pressing "End call" or the appointment will stay active!';
3 e.returnValue = dialogText;
4 return dialogText;
5};
6
7function getCookie(name) {
8 var nameEQ = name + "=";
9 var ca = document.cookie.split(';');
10 for(var i=0;i < ca.length;i++) {
11 var c = ca[i];
12 while (c.charAt(0)==' ') c = c.substring(1,c.length);
13 if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
14 }
15 return null;
16}
17
18$(document).ready(function () {
19 $(window).on('beforeunload', function () {
20 return 'Please, close the appointment by pressing "End call" or the appointment will stay active!';
21 });
22 var appointmentId = window.location.search.split('?appointmentId=').pop();
23 var ROLE_DOCTOR = 'IDCR';
24 var socket = io.connect();
25 var user;
26
27 var PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
28 var IceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate;
29 var SessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription;
30 navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
31 var pc; // PeerConnection
32 var notificationServiceWorker = null;
33 var localStream = null;
34 var timer;
35 var notifications = [];
36 var restartCallTimer = null;
37 var constraints = {
38 audio: true,
39 video: true
40 };
41 var token = getCookie('JSESSIONID');
42
43 getNotificationPermission();
44
45 if (navigator.mediaDevices.getUserMedia) {
46 navigator.mediaDevices.getUserMedia(constraints).then(setLocalStream).catch(errorHandler);
47 } else {
48 navigator.getUserMedia(constraints).then(setLocalStream).catch(errorHandler);
49 }
50
51 /**
52 * WebRTC
53 */
54 function cretePeerConnection() {
55 gotStream(localStream);
56 }
57
58 function setLocalStream(stream) {
59
60 $.get('/api/user').then(function (usr) {
61 user = usr;
62 if (!user.username) {
63 user.username = user.email.split('@')[0];
64 }
65 socket.emit('user:init', {
66 username: user.username,
67 nhsNumber: user.nhsNumber,
68 role: user.role,
69 surname: user.family_name,
70 name: user.given_name,
71 token: token
72 });
73 })
74 .fail(function(error) {
75 console.log('error! ' + JSON.stringify(error.responseJSON));
76 if (error.responseJSON.error && error.responseJSON.error) {
77 console.log('You are not logged in or your session has expired');
78 return;
79 }
80 });
81
82
83 localStream = stream;
84 cretePeerConnection();
85 }
86
87 function gotStream(stream) {
88 $('#localVideo').toggleClass('inactive').attr('src', URL.createObjectURL(stream));
89
90 pc = new PeerConnection({
91 "iceServers": [{url: 'stun:stun01.sipphone.com'},
92 {url: 'stun:stun.ekiga.net'},
93 {url: 'stun:stun.fwdnet.net'},
94 {url: 'stun:stun.ideasip.com'},
95 {url: 'stun:stun.iptel.org'},
96 {url: 'stun:stun.rixtelecom.se'},
97 {url: 'stun:stun.schlund.de'},
98 {url: 'stun:stun.l.google.com:19302'},
99 {url: 'stun:stun1.l.google.com:19302'},
100 {url: 'stun:stun2.l.google.com:19302'},
101 {url: 'stun:stun3.l.google.com:19302'},
102 {url: 'stun:stun4.l.google.com:19302'},
103 {url: 'stun:stunserver.org'},
104 {url: 'stun:stun.softjoys.com'},
105 {url: 'stun:stun.voiparound.com'},
106 {url: 'stun:stun.voipbuster.com'},
107 {url: 'stun:stun.voipstunt.com'},
108 {url: 'stun:stun.voxgratia.org'},
109 {url: 'stun:stun.xten.com'},
110 {
111 url: 'turn:numb.viagenie.ca',
112 credential: 'muazkh',
113 username: 'webrtc@live.com'
114 },
115 {
116 url: 'turn:192.158.29.39:3478?transport=udp',
117 credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
118 username: '28224511:1379330808'
119 },
120 {
121 url: 'turn:192.158.29.39:3478?transport=tcp',
122 credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
123 username: '28224511:1379330808'
124 }]
125 });
126
127 pc.addStream(stream);
128 pc.onicecandidate = gotIceCandidate;
129 pc.onaddstream = gotRemoteStream;
130 // window.localStream = stream;
131
132 if ($('#muteAudio').hasClass('inactive')) {
133 toggleAudioStreams(false);
134 }
135 if ($('#muteVideo').hasClass('inactive')) {
136 toggleVideoStreams(false);
137 toggleVideo('#localVideo');
138 }
139 }
140
141 function createOffer() {
142 var options = {
143 'mandatory': {
144 'OfferToReceiveAudio': true,
145 'OfferToReceiveVideo': true
146 }
147 };
148 pc.createOffer(options)
149 .then(gotLocalDescription)
150 .catch(errorHandler);
151 }
152
153 function createAnswer() {
154 var options = {
155 'mandatory': {
156 'OfferToReceiveAudio': true,
157 'OfferToReceiveVideo': true
158 }
159 };
160 pc.createAnswer(options)
161 .then(gotLocalDescription)
162 .catch(errorHandler);
163 }
164
165
166 function gotLocalDescription(description) {
167 pc.setLocalDescription(description);
168 sendWebRTCMessage(description);
169 }
170
171 function gotIceCandidate(event) {
172 if (event.candidate) {
173 sendWebRTCMessage({
174 type: 'candidate',
175 label: event.candidate.sdpMLineIndex,
176 id: event.candidate.sdpMid,
177 candidate: event.candidate.candidate
178 });
179 }
180 }
181
182 function gotRemoteStream(event) {
183 $('#remoteVideo').removeClass('inactive').attr('src', URL.createObjectURL(event.stream));
184 $('#noSound').hide();
185 socket.emit('call:remoteStreamProp:get', {appointmentId: appointmentId, token: token});
186 }
187
188 function errorHandler(err) {
189 console.error(err);
190 }
191
192 /**
193 * Socket.io
194 */
195
196 function sendWebRTCMessage(message) {
197 socket.emit('call:webrtc:message', {message: message, appointmentId: appointmentId, token: token});
198 }
199
200 socket.on('user:init', function(data) {
201 console.log('user:init response - ' + JSON.stringify(data));
202 if (data.ok) {
203 socket.emit('call:init', {
204 appointmentId: appointmentId,
205 token: token
206 });
207 }
208 });
209
210 socket.on('call:webrtc:init', function (data) {
211 $('#remoteVideo').removeClass('inactive');
212 if (data.isInitiator) {
213 createOffer();
214 }
215 });
216
217 socket.on('call:getPatientInfo', function (data) {
218 $.get('/api/patients/' + data.patientId).then(function (data) {
219 $('#patientName').text(data.name);
220 $('#patientDob').text(moment(data.dateOfBirth).format('DD-MMM-YYYY'));
221 $('#patientGender').text(data.gender);
222 $('#patientNhs').text(data.nhsNumber);
223 });
224 $.get('/api/patients/' + data.patientId + '/appointments/' + appointmentId).then(function (data) {
225 var appointment = data;
226 if (!appointment) {
227 appointment = {
228 author: "c4h_ripple_osi",
229 dateCreated: 1446216927376,
230 dateOfAppointment: 1423612800000,
231 location: "Leeds Royal Infirmary",
232 serviceTeam: "Prostate cancer MDT Team",
233 source: "Marand",
234 sourceId: "88536cbb-2f09-4624-a8da-fd468f045e60::ripple_osi.ehrscape.c4h::3",
235 status: "Scheduled",
236 timeOfAppointment: 50400000,
237 }
238 }
239 $('#appointmentCst').text(appointment.serviceTeam);
240 $('#appointmentLocation').text(appointment.location);
241 });
242 });
243
244 socket.on('call:opponent:join', function (data) {
245 addTextMessage(data.timestamp, null, data.message + ' has entered the chat room');
246 createNotification({title: data.message + ' has entered the chat room'});
247 });
248
249 socket.on('call:busy', function () {
250 if (pc.signalingState !== 'closed') {
251 pc.close();
252 }
253 toggleVideo('#remoteVideo');
254 window.close();
255 });
256
257 socket.on('call:opponent:left', function (data) {
258 addTextMessage(data.timestamp, null, data.message + ' has left the chat room');
259 if ($(window).data('isBlur')) {
260 createNotification({
261 title: data.message + ' has left the chat room'
262 });
263 }
264 if (pc.signalingState !== 'closed') {
265 pc.close();
266 }
267 $('#remoteVideo').attr('src', '').addClass('inactive');
268 $('#noSound').hide();
269 cretePeerConnection();
270 });
271
272 socket.on('call:webrtc:message', function (message) {
273 if (message.type === 'offer') {
274 pc.setRemoteDescription(new SessionDescription(message));
275 createAnswer();
276 } else if (message.type === 'answer') {
277 pc.setRemoteDescription(new SessionDescription(message));
278 } else if (message.type === 'candidate') {
279 var candidate = new IceCandidate({sdpMLineIndex: message.label, candidate: message.candidate});
280 pc.addIceCandidate(candidate);
281 }
282 });
283
284 socket.on('call:text:message', function (data) {
285 addTextMessage(data.timestamp, data.author, data.message);
286 if ($(window).data('isBlur')) {
287 createNotification({
288 title: 'You received a new Message' + (data.author ? (' from ' + data.author) : ''),
289 body: data.message
290 });
291 }
292 });
293
294 socket.on('call:text:messages:history', function (data) {
295 var role = isDoctor(user) ? 'doctor' : 'patient';
296 var opponent = data.appointment[(isDoctor(user) ? 'patient' : 'doctor')];
297 for (var i = 0; i < data.messages.length; i++) {
298 addTextMessage(data.messages[i].timestamp, (data.messages[i].author) ? ((role == data.messages[i].author) ? 'You' : opponent) : null, data.messages[i].message, true);
299 }
300 });
301
302 socket.on('call:timer', function (data) {
303 console.log(data);
304 clearInterval(timer);
305 initTimer(data.timestamp);
306 });
307
308 socket.on('call:remoteStreamProp:get', function () {
309 var remoteStreamProp = {};
310 remoteStreamProp.audio = localStream.getAudioTracks()[0].enabled;
311 remoteStreamProp.video = localStream.getVideoTracks()[0].enabled;
312 socket.emit('call:remoteStreamProp:post', {appointmentId: appointmentId, remoteStreamProp: remoteStreamProp, token: token})
313 });
314
315 socket.on('call:remoteStreamProp:post', function (data) {
316 if (!data.remoteStreamProp.audio) {
317 toggleAudio();
318 }
319 if (!data.remoteStreamProp.video) {
320 toggleVideo('#remoteVideo');
321 }
322 });
323
324 socket.on('call:video:toggle', function () {
325 toggleVideo('#remoteVideo');
326 });
327
328 socket.on('call:audio:toggle', function () {
329 toggleAudio();
330 });
331
332 socket.on('call:restart', function (data) {
333 clearInterval(timer);
334 addTextMessage(data.timestamp, null, data.user + ' has restarted the appointment');
335 createNotification({title: data.user + ' has restarted the appointment'});
336
337 clearTimeout(restartCallTimer);
338 restartCallTimer = null;
339 $('#sendMessage button').removeClass('disabled');
340
341 $('#endCall').show();
342 $('#closeWindow').hide();
343 $('#restartCall').hide();
344 var duration = getDiffTime(Date.now(), data.created_at);
345 $('#callDuration').appendTo('.video-chat-controller').empty().append('Connected - <span id="timer">' + duration + '</span>');
346 clearInterval(timer);
347 initTimer(data.created_at);
348 $('#localVideo').css('display', '');
349 cretePeerConnection();
350 // socket.emit('call:init', {appointmentId: appointmentId});
351 });
352
353 socket.on('call:close', function (data) {
354 if (pc.signalingState !== 'closed') {
355 pc.close();
356 }
357 if (!data) return;
358 if (data.user) {
359 addTextMessage(data.timestamp, null, data.user + ' has ended the conversation');
360 createNotification({title: data.user + ' has ended the conversation'});
361 }
362
363 $('#remoteVideo').css('background', '').attr('src', '').addClass('inactive');
364 $('#localVideo').attr('src', '').hide();
365
366 var callDuration = getDiffTime(Date.now(), data.created_at);
367 $('#endCall').hide();
368 $('#closeWindow').show();
369 $('#restartCall').removeClass('disabled').show();
370
371 var endedIn = moment.utc(data.timestamp).add(60, 's').valueOf();
372
373 console.log('moment 1', moment.utc(endedIn).diff(Date.now(), 'seconds'), moment.utc(endedIn).format('HH:mm'), moment.utc(Date.now()).format('HH:mm'));
374
375 if (moment.utc(endedIn).diff(Date.now(), 'seconds') > 60) {
376 console.log('moment rly', moment.utc(endedIn).diff(Date.now(), 'seconds'), moment.utc(endedIn).format('HH:mm'), moment.utc(Date.now()).format('HH:mm'));
377 endedIn = moment.utc(Date.now()).add(59, 's').valueOf();
378 }
379
380 restartCallTimer = setTimeout(function () {
381 $('#restartCall').addClass('disabled');
382 $('#sendMessage button').addClass('disabled');
383 clearInterval(timer);
384 $('#callDuration').appendTo('.video-wrapper').empty().append('Call Ended<br/>Duration - <span id="duration">' + callDuration + '</span>');
385 }, moment.utc(endedIn).diff(Date.now(), 'milliseconds'));
386
387 $('#callDuration').appendTo('.video-wrapper').empty().append('Call Ended in: <span id="timer">01:00</span><br/>Duration - <span id="duration">' + callDuration + '</span>');
388
389 clearInterval(timer);
390 initTimer(endedIn, true);
391 });
392
393 /**
394 * Events
395 */
396
397 $('#muteAudio').off('click').on('click', function () {
398 $(this).toggleClass('inactive').toggleClass('fa-microphone').toggleClass('fa-microphone-slash');
399 toggleAudioStreams();
400 socket.emit('call:audio:toggle', {appointmentId: appointmentId, token: token});
401 });
402
403 $('#muteVideo').off('click').on('click', function () {
404 $(this).toggleClass('inactive').toggleClass('fa-video-camera-2').toggleClass('fa-video-camera-slash-2');
405 toggleVideoStreams();
406 toggleVideo('#localVideo');
407 socket.emit('call:video:toggle', {appointmentId: appointmentId, token: token});
408 });
409
410 $('#endCall').off('click').on('click', function () {
411 $('#endCallModal').fadeIn();
412 });
413
414 $('#closeWindow').off('click').on('click', function () {
415 window.close();
416 });
417
418 $('#restartCall').off('click').on('click', function () {
419 clearTimeout(restartCallTimer);
420 restartCallTimer = null;
421
422 socket.emit('call:restart', {appointmentId: appointmentId, token: token});
423 });
424
425 $('#endCallModal-cancel').off('click').on('click', function () {
426 $('#endCallModal').fadeOut();
427 });
428
429 $('#endCallModal-submit').off('click').on('click', function () {
430 $('#endCallModal').fadeOut();
431 socket.emit('call:close', {appointmentId: appointmentId, token: token});
432 });
433
434 $('#sendMessage').off('keydown').on('keydown', function (e) {
435 if (e.ctrlKey && e.keyCode == 13) {
436 $(this).submit();
437 }
438 }).on('submit', function (e) {
439 e.preventDefault();
440 var message = $(this).find('textarea').val();
441 message = message.replace(/<(.|\n)*?>/g, '');
442 $(this).find('textarea').val('');
443 if (message == false) return;
444 // addTextMessage(timestamp, 'You', message);
445 socket.emit('call:text:message', {appointmentId: appointmentId, message: message, token: token})
446 });
447
448 $(window).on("blur focus", function (e) {
449 var prevType = $(this).data("isBlur") ? 'blur' : 'focus';
450
451 if (prevType != e.type) {
452 switch (e.type) {
453 case 'blur':
454 break;
455 case 'focus':
456 while (notifications.length) {
457 var notification = notifications.pop();
458 notification.close();
459 }
460 break;
461 }
462 }
463
464 $(this).data('isBlur', e.type == 'blur');
465 });
466
467 function toggleVideo(video_selector) {
468 var $video = $(video_selector);
469 $video.toggleClass('inactive');
470 if (video_selector.indexOf('remote') !== -1) return;
471 var src = $video.attr('src');
472 if (!src) {
473 src = $video.data('src');
474 $video.attr('src', src);
475 } else {
476 $video.data('src', src);
477 $video.attr('src', '');
478 }
479 }
480
481 function toggleAudio() {
482 $('#noSound').toggle();
483 }
484
485 function toggleAudioStreams(enabled) {
486 localStream.getAudioTracks().forEach(function (stream) {
487 stream.enabled = (enabled !== undefined) ? enabled : !stream.enabled;
488 });
489 }
490
491 function toggleVideoStreams(enabled) {
492 localStream.getVideoTracks().forEach(function (stream) {
493 stream.enabled = (enabled !== undefined) ? enabled : !stream.enabled;
494 });
495 }
496
497 function addTextMessage(timestamp, author, message, prepend) {
498 var $list = $('.list-messages');
499 var li = document.createElement('li');
500 var textNode = document.createTextNode(moment.utc(timestamp).local().format('HH:mm') + ' - ' + ( (author !== null) ? (author + ': ') : '') + message);
501 $(li).append(textNode);
502 $list[(prepend) ? 'prepend' : 'append'](li);
503 var height = $list[0].scrollHeight;
504 $list.scrollTop(height);
505 }
506
507 function createNotification(data) {
508 if (!('Notification' in window)) {
509 console.log('This browser does not support desktop notification');
510 } else if (Notification.permission === 'granted') {
511 _createNotification(data);
512 } else if (Notification.permission !== 'denied') {
513 Notification.requestPermission(function (permission) {
514 if (permission === 'granted') {
515 _createNotification(data)
516 }
517 });
518 }
519 }
520
521 function _createNotification(data) {
522 try {
523 notifications.push(new Notification(data.title, {
524 body: data.body,
525 icon: '../images/ripple-icon.png'
526 }));
527 playSound('alert');
528 } catch (err) {
529 if (err.name == 'TypeError') { // if browser doesn't support new Notification syntax (Chrome Android)
530 if (notificationServiceWorker === null) {
531 notificationServiceWorker = navigator.serviceWorker.register('/scripts/chat/sw.js');
532 }
533 notificationServiceWorker.then(function (registration) {
534 registration.showNotification(data.title, {
535 body: data.body,
536 icon: '../images/ripple-icon.png',
537 vibrate: [200, 100, 200, 100, 200, 100, 200],
538 });
539 registration.getNotifications().then(function (data) {
540 notifications = [].concat(data);
541 });
542 });
543 }
544 }
545 }
546
547 function getNotificationPermission(cb) {
548 if (!('Notification' in window)) return;
549 if (Notification.permission !== 'denied' || Notification.permission !== 'granted') {
550 Notification.requestPermission(cb);
551 }
552 }
553
554 function playSound(filename) {
555 $('#notificationSound').empty().html('<audio autoplay="autoplay">' +
556 '<source src="sounds/' + filename + '.mp3" type="audio/mpeg" />' +
557 '<source src="sounds/' + filename + '.ogg" type="audio/ogg" />' +
558 '<embed hidden="true" autostart="true" loop="false" src="sounds/' + filename + '.mp3" />' +
559 '</audio>');
560 }
561
562 function isDoctor(user) {
563 return user && user.role == ROLE_DOCTOR;
564 }
565
566 function initTimer(time, withoutHours) {
567 console.log('initTimer', time);
568 timer = setInterval(function () {
569 $('#timer').text(getDiffTime(Date.now(), time, withoutHours));
570 }, 1000);
571 }
572
573 function getDiffTime(from, to, withoutHours) {
574 return ((withoutHours) ?
575 [
576 ('0' + (Math.abs(moment.utc(from).diff(to, 'minutes')) % 60)).slice(-2),
577 ('0' + (Math.abs(moment.utc(from).diff(to, 'seconds')) % 60)).slice(-2)] :
578 [
579 ('0' + Math.abs(moment.utc(from).diff(to, 'hours'))).slice(-2),
580 ('0' + (Math.abs(moment.utc(from).diff(to, 'minutes')) % 60)).slice(-2),
581 ('0' + (Math.abs(moment.utc(from).diff(to, 'seconds')) % 60)).slice(-2)
582 ]).join(':');
583 }
584});
\No newline at end of file