1
0
Fork 0
mirror of https://github.com/Ylianst/MeshCentral.git synced 2025-03-09 15:40:18 +00:00

Many fixes.

This commit is contained in:
Ylian Saint-Hilaire 2018-12-20 12:12:24 -08:00
parent f2a4c0c652
commit 88621aaf2c
19 changed files with 1153 additions and 83 deletions

View file

@ -31,4 +31,11 @@ COPY ..\views\login-mobile.handlebars index.html
..\..\WebSiteCompiler\bin\Debug\WebSiteCompiler.exe compress.wcc -c
COPY compress.htm ..\views\login-mobile-min.handlebars
DEL compress.htm
DEL index.html
REM *** messenger.handlebars
COPY ..\views\messenger.handlebars index.html
..\..\WebSiteCompiler\bin\Debug\WebSiteCompiler.exe compress.wcc -c
COPY compress.htm ..\views\messenger-min.handlebars
DEL compress.htm
DEL index.html

View file

@ -1,545 +0,0 @@
<!DOCTYPE html>
<html style=height:100%>
<head>
<title>MeshMessenger</title>
<meta http-equiv=X-UA-Compatible content="IE=edge">
<meta content="text/html;charset=utf-8" http-equiv=Content-Type>
<meta name=format-detection content="telephone=no">
<link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
<link type="text/css" href="styles/messenger.css" media="screen" rel="stylesheet" title="CSS" />
<script type="text/javascript" src="scripts/common-0.0.1.js"></script>
<script type="text/javascript" src="scripts/filesaver.1.1.20151003.js"></script>
</head>
<body style="font-family:Arial,Helvetica,sans-serif">
<div id="xtop" style="position:absolute;left:0;right:0;top:0;height:38px;background-color:#036;color:#c8c8c8;box-shadow:3px 3px 10px gray">
<div id="fileButton" class="icon4 topButton" title="Share a file" style="display:none" onclick="fileButtonClick()"></div>
<div id="camButton" class="icon2 topButton" title="Activate camera & microphone" style="display:none" onclick="camButtonClick()"></div>
<div id="micButton" class="icon6 topButton" title="Activate microphone" style="display:none" onclick="micButtonClick()"></div>
<div id="hangupButton" class="icon11 topRedButton" title="Hang up" style="display:none" onclick="hangUpButtonClick(1)"></div>
<div style="display:inline-block;width:2px"></div>
<div style="padding-top:9px;padding-left:6px;font-size:20px;display:inline-block"><b>MeshMessenger<span id="xtitle"></span></b></div>
</div>
<div id="xmiddle" style="position:absolute;left:0;right:0;top:38px;bottom:30px">
<div style="position:absolute;left:0;right:0;top:0;bottom:0;overflow-y:scroll">
<div id="xmsg" style="position:absolute;left:0;right:0;bottom:0;padding:5px"></div>
</div>
</div>
<div id="xbottom" style="position:absolute;left:0;right:0;bottom:0px;height:30px;background-color:#036">
<div style="position:absolute;left:5px;right:215px;bottom:4px;top:4px;background-color:aliceblue"><input id="xouttext" type="text" style="width:calc(100% - 5px)" onfocus=onUserInputFocus(1) onblur=onUserInputFocus(0) /></div>
<input type="button" id="sendButton" value="Send" style="position:absolute;right:110px;width:100px;top:4px;" onclick="xsend(event)" />
<input type="button" id="clearButton" value="Clear" style="position:absolute;right:5px;width:100px;top:4px;" onclick="displayClear()" />
</div>
<div id="remoteVideo" style="position:absolute;right:24px;top:45px;width:320px;height:calc(240px + 30px);background-color:gray;border-radius:12px 12px 12px 12px;box-shadow:3px 3px 10px gray;display:none">
<div style="position:absolute;right:0;left:0;top:2.5px;text-align:center">Remote</div>
<video id="remoteVideoCanvas" autoplay style="position:absolute;top:20px;left:0;width:100%;height:calc(100% - 30px);background-color:black"></video>
</div>
<div id="localVideo" style="position:absolute;right:24px;top:320px;width:160px;height:calc(120px + 30px);background-color:gray;border-radius:12px 12px 12px 12px;box-shadow:3px 3px 10px gray;display:none">
<div style="position:absolute;right:0;left:0;top:2.5px;text-align:center">Local</div>
<video id="localVideoCanvas" autoplay muted style="position:absolute;top:20px;left:0;width:100%;height:calc(100% - 30px);background-color:black"></video>
</div>
<input id="uploadFileInput" type="file" multiple style="display:none">
<script type="text/javascript">
var userInputFocus = 0;
var args = parseUriArgs();
var socket = null; // Websocket object
var state = 0; // Connection state. 0 = Disconnected, 1 = Connecting, 2 = Connected.
var random = Math.random(); // Selected random, larger value initiates WebRTC.
var webrtcSessions = { }; // WebRTC objects: 0 for data, 1 for outbound audio/video, 2 for inbound audio/video
var webchannel = null; // WebRTC data channel
var webrtcconfiguration = null; //{ "iceServers": [ { 'urls': 'stun:stun.services.mozilla.com' }, { 'urls': 'stun:stun.l.google.com:19302' } ] };
var localStream = null;
var remoteStream = null;
var fileUploads = [];
var fileDownloads = {};
var currentFileUpload = null;
var currentFileDownload = null;
// Set the title
if (args.title) { QH('xtitle', ' - ' + args.title); document.title = document.title + ' - ' + args.title; }
document.addEventListener('dragover', haltEvent, false);
document.addEventListener('dragleave', haltEvent, false);
document.addEventListener('drop', fileDrop, false);
document.onkeyup = function ondockeypress(e) {
if (state == 2) {
if ((e.keyCode == 8) && (userInputFocus == 0)) {
// Backspace
var outtext = Q('xouttext').value;
if (outtext.length > 0) { Q('xouttext').value = outtext.substring(0, outtext.length - 1); }
}
}
if (userInputFocus == 0) { haltEvent(e); return false; }
}
// Trap document key presses
document.onkeypress = function ondockeypress(e) {
if (state == 2) {
if (e.keyCode == 13) {
// Return
xsend(e);
} else {
// Any other key
if ((userInputFocus == 0) && (e.key.length == 1)) { Q('xouttext').value = Q('xouttext').value + e.key; }
}
}
if (userInputFocus == 0) { haltEvent(e); return false; }
}
function onUserInputFocus(x) { userInputFocus = x; }
function displayClear() { QH('xmsg', ''); cancelAllFileTransfers(); fileUploads = [], fileDownloads = {}; }
// Display a control message
function displayControl(msg) {
QA('xmsg', '<div style="clear:both"><div style="color:gray;float:left;margin-bottom:2px">' + msg + '</div><div></div></div>');
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
}
function displayLocalVideo(active) { QV('localVideo', active); adjustVideoWindows(); }
function displayRemoteVideo(active) { QV('remoteVideo', active); adjustVideoWindows(); }
function adjustVideoWindows() {
//var lv = (QS('localVideo')['display'] != 'none');
var rv = (QS('remoteVideo')['display'] != 'none');
QS('localVideo')['top'] = rv ? '320px' : '45px';
}
// Display a message from the remote user
function displayRemote(msg) {
QA('xmsg', '<div style="clear:both"><div class="remoteBubble">' + msg + '</div><div></div></div>');
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
}
// Display and send a message from the local user
function xsend(event) {
var outtext = Q('xouttext').value;
if (outtext.length > 0) {
Q('xouttext').value = '';
QA('xmsg', '<div style="clear:both"><div class="localBubble">' + outtext + '</div><div></div></div>');
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
send({ action: 'chat', msg: outtext });
}
}
function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
function parseUriArgs() { var name, r = {}, parsedUri = window.document.location.href.split(/[\?&|\=]/); parsedUri.splice(0, 1); for (x in parsedUri) { switch (x % 2) { case 0: { name = parsedUri[x]; break; } case 1: { r[name] = parsedUri[x]; var x = parseInt(r[name]); if (x == r[name]) { r[name] = x; } break; } } } return r; }
// Update user controls
function updateControls() {
QE('sendButton', state == 2);
QE('clearButton', state == 2);
QE('xouttext', state == 2);
QV('fileButton', state == 2);
QV('camButton', webchannel && webchannel.ok && !localStream);
QV('micButton', webchannel && webchannel.ok && !localStream);
QV('hangupButton', webchannel && webchannel.ok && localStream);
}
// This is the WebRTC setup
function startWebRTC(id, startDataChannel) {
// Setup the WebRTC object
var webrtc;
if (typeof RTCPeerConnection !== 'undefined') { webrtc = new RTCPeerConnection(webrtcconfiguration); }
else if (typeof webkitRTCPeerConnection !== 'undefined') { webrtc = new webkitRTCPeerConnection(webrtcconfiguration); }
webrtc.id = id;
webrtc.onicecandidate = function (e) { try { if (e.candidate != null) { sendws({ action: 'webRtcIce', ice: e.candidate, id: this.id }); } } catch (ex) { } }
webrtc.oniceconnectionstatechange = function () { if (webrtc && webrtc.iceConnectionState == 'failed') { hangUpButtonClick(webrtc.id); } }
webrtc.ondatachannel = function (ev) {
webchannel = ev.channel;
webchannel.onmessage = function (event) { processMessage(event.data, 2); };
webchannel.onopen = function () { webchannel.ok = true; updateControls(); sendws({ action: 'rtcSwitch', v: 0 }); };
webchannel.onclose = function (event) { if (webchannel && webchannel.ok) { disconnect(); } else { hangUpButtonClick(0); } }
}
webrtc.onnegotiationneeded = function (event) {
if (webrtc.holdTimer != null) return;
webrtc.holdTimer = setTimeout(function () { // This time is needed to keep Chrome from being to excited. Wait until we add all tracks before kicking this off.
//console.log('onnegotiationneeded', id);
webrtc.holdTimer = null;
webrtc.createOffer(function (offer) { webrtc.setLocalDescription(offer, function () { sendws({ action: 'webRtcSdp', sdp: offer, id: id }); }, function () { hangUpButtonClick(id); }); }, function () { hangUpButtonClick(id); });
}, 20);
}
webrtc.ontrack = function (event) {
//console.log('ontrack', id);
var video = Q('remoteVideoCanvas');
video.srcObject = remoteStream = event.streams[0];
video.onloadedmetadata = function (e) { video.play(); };
displayRemoteVideo(true);
}
//webrtc.onremovetrack = function (event) { console.log('onremovetrack'); }
//webrtc.onicegatheringstatechange = function (event) { console.log('onicegatheringstatechange', event); }
//webrtc.onsignalingstatechange = function (event) { console.log('onsignalingstatechange', event); }
// Initiate the WebRTC offer or handle the offer from the peer.
if (startDataChannel == true) {
webchannel = webrtc.createDataChannel("DataChannel", {}); // { ordered: false, maxRetransmits: 2 }
webchannel.onmessage = function (event) { processMessage(event.data, 2); };
webchannel.onopen = function () { webchannel.ok = true; updateControls(); sendws({ action: 'rtcSwitch', v: 0 }); };
webchannel.onclose = function (event) { if (webchannel && webchannel.ok) { disconnect(); } else { hangUpButtonClick(0); } }
}
webrtcSessions[id] = webrtc;
return webrtc;
}
function webRtcHandleOffer(id, description) {
//console.log('webRtcHandleOffer', description.sdp.length);
var webrtc = webrtcSessions[id];
if (webrtc) {
webrtc.setRemoteDescription(new RTCSessionDescription(description), function () {
if (description.type == 'offer') {
webrtc.createAnswer(function (answer) {
webrtc.setLocalDescription(answer, function (a, b) {
try { sendws({ action: 'webRtcSdp', sdp: answer, id: id }); } catch (ex) { }
}, function () { hangUpButtonClick(id); });
}, function () { hangUpButtonClick(id); });
}
}, function () { hangUpButtonClick(id); });
}
}
// Indicate to peer that data traffic will no longer be sent over websocket and start holding traffic.
function performWebRtcSwitch() {
if (webchannel && webchannel.ok) { sendws({ action: 'rtcSwitch', v: 1 }); webchannel.xoutBuffer = []; }
}
// Disconnect everything
function disconnect() {
if (state > 0) { displayControl('Connection closed.'); }
if (state > 1) { setTimeout(start, 500); }
cancelAllFileTransfers();
hangUpButtonClick(0, true); // Data channel
hangUpButtonClick(1, true); // Local audio/video
hangUpButtonClick(2, true); // Remote audio/video
if (socket != null) { socket.close(); socket = null; }
updateControls();
state = 0;
}
// Send data over the current transport (WebRTC first)
function send(data) {
if (state != 2) return; // If not in connected state, ignore this.
if (typeof data == 'object') { data = JSON.stringify(data); } // If this is an object, convert it to a string.
if (webchannel && webchannel.ok) { if (webchannel.xoutBuffer != null) { webchannel.xoutBuffer.push(data); } else { webchannel.send(data); } } // If WebRTC channel is possible, use it or hold until we can use it.
else { if (socket != null) { try { socket.send(data); } catch (ex) { } } } // If a websocket channel is present, use that.
}
// Send data over the websocket transport (WebSocket only)
function sendws(data) {
if (state != 2) return;
if (typeof data == 'object') { data = JSON.stringify(data); }
if (socket != null) { socket.send(data); }
}
// WebRTC id switcher (0 -> 0, 1 -> 2, 2 -> 1)
function webRtcIdSwitch(id) { if (id == 0) { return 0; } return 3 - id; }
// Process incoming messages
function processMessage(data, transport) {
if (typeof data == 'string') {
try { data = JSON.parse(data); } catch (ex) { console.log('Unable to parse', data); return; }
//console.log(data);
switch (data.action) {
case 'chat': { displayRemote(data.msg); break; } // Incoming chat message.
case 'random': { if (random > data.random) { startWebRTC(0, true); } break; } // If we have a larger random value, we start WebRTC.
case 'webRtcSdp': { if (!webrtcSessions[webRtcIdSwitch(data.id)]) { startWebRTC(webRtcIdSwitch(data.id), false); } webRtcHandleOffer(webRtcIdSwitch(data.id), data.sdp); break; } // Remote WebRTC offer or answer.
case 'webRtcIce': { var webrtc = webrtcSessions[webRtcIdSwitch(data.id)]; if (webrtc) { try { webrtc.addIceCandidate(new RTCIceCandidate(data.ice)); } catch (ex) { } } break; } // Remote ICE candidate
case 'videoStop': { hangUpButtonClick(webRtcIdSwitch(data.id), true); break; }
case 'rtcSwitch': { // WebRTC switch over commands.
switch (data.v) {
case 0: { performWebRtcSwitch(); break; } // Other side is ready for switch over to WebRTC
case 1: { sendws({ action: 'rtcSwitch', v: 2 }); break; } // Other side no longer sending data on websocket, confirm we got the end marker
case 2: { for (var i in webchannel.xoutBuffer) { webchannel.send(webchannel.xoutBuffer[i]); } delete webchannel.xoutBuffer; break; } // Send any pending data over WebRTC and start using WebRTC with all traffic
}
break;
}
case 'file': { startFileDownload(data); break; }
case 'fileUploadCancel': { cancelFileTransfer(data.id); break; }
case 'fileUploadStart': {
if (fileDownloads[data.id]) {
currentFileDownload = fileDownloads[data.id];
currentFileDownload.data = '';
changeFileInfo(data.id, 2, 0);
continueFileDownload(data);
send({ action: 'fileUploadAck', id: data.id });
} break;
}
case 'fileUploadEnd': {
if (currentFileDownload && (currentFileDownload.id == data.id)) {
changeFileInfo(data.id, 3, 200);
currentFileDownload.done = 1;
currentFileDownload = null;
send({ action: 'fileUploadAck', id: data.id });
}
currentFileDownload = null;
break;
}
case 'fileUploadAck': {
continueFileUpload();
break;
}
case 'fileData': {
if (currentFileDownload && (currentFileDownload.id == data.id)) {
currentFileDownload.data += data.data;
changeFileInfo(data.id, 2, (currentFileDownload.data.length * 200 / currentFileDownload.size));
send({ action: 'fileUploadAck', id: data.id });
}
break;
}
default: { console.log('Unhandled object data', data); break; }
}
} else {
console.log('Unhandled data', typeof data, data);
}
}
// File sharing button
function fileButtonClick() {
var chooser = Q('uploadFileInput');
if (chooser.getAttribute("eventset") != 1) {
chooser.setAttribute("eventset", "1");
chooser.addEventListener("change", fileSelect, false);
}
chooser.value = null;
chooser.click();
}
// User selected one or more files to upload to remote user.
function fileSelect() {
if (state != 2) return;
var x = Q('uploadFileInput');
if (x.files.length > 10) {
displayControl('Limit of 10 file uploads at the same time.');
} else {
for (var i = 0; i < x.files.length; i++) {
if (x.files[i].size > 0) {
var reader = new FileReader();
reader.onload = function (e) { this.xfile.data = e.target.result; startFileUpload(this.xfile); };
reader.xfile = x.files[i];
reader.readAsBinaryString(x.files[i]);
}
}
}
}
// User drag & droped one or more files to upload to remote user.
function fileDrop(e) {
haltEvent(e);
if ((state != 2) || (e.dataTransfer == null)) return;
if (e.dataTransfer.files.length > 10) {
displayControl('Limit of 10 file uploads at the same time.');
} else {
for (var i = 0; i < e.dataTransfer.files.length; i++) {
if (e.dataTransfer.files[i].size > 0) {
var reader = new FileReader();
reader.onload = function (e) { this.xfile.data = e.target.result; startFileUpload(this.xfile); };
reader.xfile = e.dataTransfer.files[i];
reader.readAsBinaryString(e.dataTransfer.files[i]);
}
}
}
}
function startFileUpload(file) {
if (state != 2) return;
file.id = Math.random();
fileUploads.push(file);
QA('xmsg', '<div style="clear:both"></div><div id="FILEUP-' + file.id + '" class="localBubble" style="width:240px;cursor:pointer" onclick="cancelFileTransfer(\'' + file.id + '\')"><div id="FILEUP-ICON-' + file.id + '" class="fileicon" style="float:left;width:32px;height:32px"></div><div><div id="FILEUP-NAME-' + file.id + '" style="height:16px;overflow:hidden;white-space:nowrap;" title="' + file.name + '">' + file.name + '</div><div style="width:200px;background-color:lightgray;margin-left:32px;border-radius:3px;margin-top:3px;height:11px"><div id="FILEUP-PROGRESS-' + file.id + '" style="width:0px;background-color:green;border-radius:3px;height:11px">&nbsp;</div></div></div></div>');
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
send({ action: 'file', size: file.size, id: file.id, type: file.type, name: file.name });
if (currentFileUpload == null) continueFileUpload();
}
function startFileDownload(file) {
if (state != 2) return;
fileDownloads[file.id] = file;
QA('xmsg', '<div style="clear:both"></div><div id="FILEUP-' + file.id + '" class="remoteBubble" style="width:240px;cursor:pointer" onclick="saveFileTransfer(\'' + file.id + '\')"><div id="FILEUP-ICON-' + file.id + '" class="fileicon" style="float:left;width:32px;height:32px"></div><div><div id="FILEUP-NAME-' + file.id + '" style="height:16px;overflow:hidden;white-space:nowrap;" title="' + file.name + '">' + file.name + '</div><div style="width:200px;background-color:lightgray;margin-left:32px;border-radius:3px;margin-top:3px;height:11px"><div id="FILEUP-PROGRESS-' + file.id + '" style="width:0px;background-color:green;border-radius:3px;height:11px">&nbsp;</div></div></div></div>');
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
}
// Change the file icon and progress
function changeFileInfo(id, icon, progress, progressColor) {
if (icon) {
Q('FILEUP-ICON-' + id).classList.remove('fileicon');
Q('FILEUP-ICON-' + id).classList.remove('fileiconx');
Q('FILEUP-ICON-' + id).classList.remove('fileicontransfer');
Q('FILEUP-ICON-' + id).classList.remove('fileicondone');
Q('FILEUP-ICON-' + id).classList.add(['fileicon', 'fileiconx', 'fileicontransfer', 'fileicondone'][icon]);
}
if (progress) { QS('FILEUP-PROGRESS-' + id)['width'] = progress + 'px'; }
if (progressColor) { QS('FILEUP-PROGRESS-' + id)['background-color'] = progressColor; }
}
// Convert a string into a blob
data2blob = function (data) {
var bytes = new Array(data.length);
for (var i = 0; i < data.length; i++) bytes[i] = data.charCodeAt(i);
return new Blob([new Uint8Array(bytes)]);
};
function saveFileTransfer(id) {
var f = fileDownloads[id];
if (f && f.done == 1) { saveAs(data2blob(f.data), f.name); }
}
function cancelFileTransfer(id) {
if ((currentFileUpload != null) && (currentFileUpload.id == id)) { currentFileUpload = null; }
if ((currentFileDownload != null) && (currentFileDownload.id == id)) { currentFileDownload = null; }
var found = false;
if (fileDownloads[id] && (fileDownloads[id].done != 1)) {
delete fileDownloads[id];
found = true;
} else {
for (var i in fileUploads) {
if (fileUploads[i].id == id) {
send({ action: 'fileUploadCancel', id: id });
fileUploads.splice(i, 1);
found = true;
break;
}
}
}
if (found) { changeFileInfo(id, 1, 200, 'gray'); } // Only cancel a file if it was in the file queue.
}
function cancelAllFileTransfers() {
for (var i in fileDownloads) { cancelFileTransfer(fileDownloads[i].id); }
for (var i in fileUploads) { cancelFileTransfer(fileUploads[i].id); }
}
function continueFileUpload() {
if (currentFileUpload == null) {
// Select the next file to upload
if (fileUploads.length == 0) { return; } // Nothing to do
currentFileUpload = fileUploads[0];
currentFileUpload.ptr = 0;
// Indicate that we are sending this file
send({ action: 'fileUploadStart', size: currentFileUpload.size, id: currentFileUpload.id, type: currentFileUpload.type, name: currentFileUpload.name });
} else {
if (currentFileUpload.size <= currentFileUpload.ptr) {
// If we are done, send the end marker
send({ action: 'fileUploadEnd', size: currentFileUpload.size, id: currentFileUpload.id, type: currentFileUpload.type, name: currentFileUpload.name });
changeFileInfo(currentFileUpload.id, 3, 200);
fileUploads.splice(0, 1);
currentFileUpload = null;
continueFileUpload(); // Send the next file
} else {
// Send the next block
var nextBlockLen = Math.min(4000, currentFileUpload.data.length - currentFileUpload.ptr);
var data = currentFileUpload.data.substring(currentFileUpload.ptr, currentFileUpload.ptr + nextBlockLen);
send({ action: 'fileData', id: currentFileUpload.id, data: data });
currentFileUpload.ptr += nextBlockLen;
changeFileInfo(currentFileUpload.id, 0, (currentFileUpload.ptr * 200 / currentFileUpload.size));
}
}
}
function continueFileDownload(msg) {
send({ action: 'fileUploadAck', id: msg.id });
}
// Camera button
function camButtonClick() {
if (localStream == null) { startLocalStream({ video: true, audio: true }); }
}
// Microphone
function micButtonClick() {
if (localStream == null) { startLocalStream({ video: false, audio: true }); }
}
function hangUpButtonClick(id, fromRemote) {
//console.log('hangUpButtonClick', id);
var localVideo = Q('localVideoCanvas');
var remoteVideo = Q('remoteVideoCanvas');
var webrtc = webrtcSessions[1];
if ((id == 0) && (webchannel != null)) { try { webchannel.close(); } catch (e) { } webchannel = null; }
if (webrtc) {
webrtc.ontrack = null;
webrtc.onremovetrack = null;
webrtc.onremovestream = null;
webrtc.onnicecandidate = null;
webrtc.oniceconnectionstatechange = null;
webrtc.onsignalingstatechange = null;
webrtc.onicegatheringstatechange = null;
webrtc.onnotificationneeded = null;
if ((id == 1) && localVideo.srcObject) { localVideo.srcObject.getTracks().forEach(track => track.stop()); }
if ((id == 2) && remoteVideo.srcObject) { remoteVideo.srcObject.getTracks().forEach(track => track.stop()); }
webrtc.close();
delete webrtcSessions[id];
}
if (id == 1) {
localVideo.removeAttribute("src");
localVideo.removeAttribute("srcObject");
if (localStream != null) { localStream = null; }
displayLocalVideo(false);
} else if (id == 2) {
remoteVideo.removeAttribute("src");
remoteVideo.removeAttribute("srcObject");
displayRemoteVideo(false);
}
if (fromRemote != true) { send({ action: 'videoStop', id: id }); }
updateControls();
}
// Setup local audio/video
function startLocalStream(constraints) {
if ((localStream != null) || (webrtcSessions[1] != null)) return;
if (navigator.mediaDevices.getUserMedia) {
localStream = 1;
updateControls();
navigator.mediaDevices.getUserMedia(constraints)
.then(function (stream) {
localStream = stream;
if (constraints.video == true) {
var video = Q('localVideoCanvas'), tracks = localStream.getTracks(), webrtc = startWebRTC(1);
video.srcObject = stream;
video.onloadedmetadata = function (e) { video.play(); };
displayLocalVideo(true);
for (var i in tracks) { webrtc.addTrack(tracks[i], localStream); }
}
})
.catch(function (err) {
displayControl(err.message + '.');
hangUpButtonClick(1);
});
}
}
// This is the main start
function start() {
// Get started
updateControls();
if ((typeof args.id == 'string') && (args.id.length > 0)) {
socket = new WebSocket(window.location.protocol.replace("http", "ws") + "//" + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + '/meshrelay.ashx?id=' + args.id);
socket.onopen = function () { state = 1; displayControl('Waiting for other user...'); }
socket.onerror = function (e) { console.error(e); }
socket.onclose = function () { disconnect(); }
socket.onmessage = function (msg) {
if ((state < 2) && (typeof msg.data == 'string') && (msg.data == 'c')) {
hangUpButtonClick(0, true);
hangUpButtonClick(1, true);
hangUpButtonClick(2, true);
displayControl('Connected.');
state = 2;
updateControls();
sendws({ action: 'random', random: random }); // Send a random number. Higher number starts the WebRTC session.
return;
}
if (state == 2) { processMessage(msg.data, 1); }
}
} else {
displayControl('Error: No connection key specified.');
}
}
start();
</script>
</body>
</html>

View file

@ -107,7 +107,7 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
}
obj.webrtc.oniceconnectionstatechange = function () {
if (obj.webrtc != null) {
if (obj.webrtc.iceConnectionState == 'disconnected') { obj.Stop(); }
if (obj.webrtc.iceConnectionState == 'disconnected') { if (obj.webRtcActive == true) { obj.Stop(); } else { obj.xxCloseWebRTC(); } }
else if (obj.webrtc.iceConnectionState == 'failed') { obj.xxCloseWebRTC(); }
}
}

180
public/scripts/filesaver.js Normal file
View file

@ -0,0 +1,180 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define([], factory);
} else if (typeof exports !== "undefined") {
factory();
} else {
var mod = {
exports: {}
};
factory();
global.FileSaver = mod.exports;
}
})(this, function () {
"use strict";
/*
* FileSaver.js
* A saveAs() FileSaver implementation.
*
* By Eli Grey, http://eligrey.com
*
* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)
* source : http://purl.eligrey.com/github/FileSaver.js
*/
// The one and only way of getting global scope in all environments
// https://stackoverflow.com/q/3277182/1008999
var _global = typeof window === 'object' && window.window === window ? window : typeof self === 'object' && self.self === self ? self : typeof global === 'object' && global.global === global ? global : void 0;
function bom(blob, opts) {
if (typeof opts === 'undefined') opts = {
autoBom: false
};else if (typeof opts !== 'object') {
console.warn('Depricated: Expected third argument to be a object');
opts = {
autoBom: !opts
};
} // prepend BOM for UTF-8 XML and text/* types (including HTML)
// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
return new Blob([String.fromCharCode(0xFEFF), blob], {
type: blob.type
});
}
return blob;
}
function download(url, name, opts) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.onload = function () {
saveAs(xhr.response, name, opts);
};
xhr.onerror = function () {
console.error('could not download file');
};
xhr.send();
}
function corsEnabled(url) {
var xhr = new XMLHttpRequest(); // use sync to avoid popup blocker
xhr.open('HEAD', url, false);
xhr.send();
return xhr.status >= 200 && xhr.status <= 299;
} // `a.click()` doesn't work for all browsers (#465)
function click(node) {
try {
node.dispatchEvent(new MouseEvent('click'));
} catch (e) {
var evt = document.createEvent('MouseEvents');
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
node.dispatchEvent(evt);
}
}
var saveAs = _global.saveAs || // probably in some web worker
typeof window !== 'object' || window !== _global ? function saveAs() {}
/* noop */
// Use download attribute first if possible (#193 Lumia mobile)
: 'download' in HTMLAnchorElement.prototype ? function saveAs(blob, name, opts) {
var URL = _global.URL || _global.webkitURL;
var a = document.createElement('a');
name = name || blob.name || 'download';
a.download = name;
a.rel = 'noopener'; // tabnabbing
// TODO: detect chrome extensions & packaged apps
// a.target = '_blank'
if (typeof blob === 'string') {
// Support regular links
a.href = blob;
if (a.origin !== location.origin) {
corsEnabled(a.href) ? download(blob, name, opts) : click(a, a.target = '_blank');
} else {
click(a);
}
} else {
// Support blobs
a.href = URL.createObjectURL(blob);
setTimeout(function () {
URL.revokeObjectURL(a.href);
}, 4E4); // 40s
setTimeout(function () {
click(a);
}, 0);
}
} // Use msSaveOrOpenBlob as a second approach
: 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, opts) {
name = name || blob.name || 'download';
if (typeof blob === 'string') {
if (corsEnabled(blob)) {
download(blob, name, opts);
} else {
var a = document.createElement('a');
a.href = blob;
a.target = '_blank';
setTimeout(function () {
click(a);
});
}
} else {
navigator.msSaveOrOpenBlob(bom(blob, opts), name);
}
} // Fallback to using FileReader and a popup
: function saveAs(blob, name, opts, popup) {
// Open a popup immediately do go around popup blocker
// Mostly only avalible on user interaction and the fileReader is async so...
popup = popup || open('', '_blank');
if (popup) {
popup.document.title = popup.document.body.innerText = 'downloading...';
}
if (typeof blob === 'string') return download(blob, name, opts);
var force = blob.type === 'application/octet-stream';
var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari;
var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);
if ((isChromeIOS || force && isSafari) && typeof FileReader === 'object') {
// Safari doesn't allow downloading of blob urls
var reader = new FileReader();
reader.onloadend = function () {
var url = reader.result;
url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;');
if (popup) popup.location.href = url;else location = url;
popup = null; // reverse-tabnabbing #460
};
reader.readAsDataURL(blob);
} else {
var URL = _global.URL || _global.webkitURL;
var url = URL.createObjectURL(blob);
if (popup) popup.location = url;else location.href = url;
popup = null; // reverse-tabnabbing #460
setTimeout(function () {
URL.revokeObjectURL(url);
}, 4E4); // 40s
}
};
_global.saveAs = saveAs.saveAs = saveAs;
if (typeof module !== 'undefined') {
module.exports = saveAs;
}
});

3
public/scripts/filesaver.min.js vendored Normal file
View file

@ -0,0 +1,3 @@
(function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Depricated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(b,c,d){var e=new XMLHttpRequest;e.open("GET",b),e.responseType="blob",e.onload=function(){a(e.response,c,d)},e.onerror=function(){console.error("could not download file")},e.send()}function d(a){var b=new XMLHttpRequest;return b.open("HEAD",a,!1),b.send(),200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=f.saveAs||"object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(a,b,d,e){if(e=e||open("","_blank"),e&&(e.document.title=e.document.body.innerText="downloading..."),"string"==typeof a)return c(a,b,d);var g="application/octet-stream"===a.type,h=/constructor/i.test(f.HTMLElement)||f.safari,i=/CriOS\/[\d]+/.test(navigator.userAgent);if((i||g&&h)&&"object"==typeof FileReader){var j=new FileReader;j.onloadend=function(){var a=j.result;a=i?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),e?e.location.href=a:location=a,e=null},j.readAsDataURL(a)}else{var k=f.URL||f.webkitURL,l=k.createObjectURL(a);e?e.location=l:location.href=l,e=null,setTimeout(function(){k.revokeObjectURL(l)},4E4)}};f.saveAs=a.saveAs=a,"undefined"!=typeof module&&(module.exports=a)});
//# sourceMappingURL=FileSaver.min.js.map

View file

@ -2,6 +2,7 @@
cursor: pointer;
border: none;
margin: 2px;
margin-top: 3px;
float: right;
border-radius: 3px;
height: 32px;