mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2025-03-09 15:40:18 +00:00
MeshMessenger will now try to switch to a WebRTC data channel.
This commit is contained in:
parent
286ee89f36
commit
67ae73df15
5 changed files with 135 additions and 243 deletions
|
@ -16,14 +16,18 @@
|
|||
<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="xclear(event)" />
|
||||
<input type="button" id="clearButton" value="Clear" style="position:absolute;right:5px;width:100px;top:4px;" onclick="displayClear()" />
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var userInputFocus = 0;
|
||||
var controlsEnabled = true;
|
||||
var args = parseUriArgs();
|
||||
var socket = null;
|
||||
var state = 0;
|
||||
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 webrtc = null; // Main WebRTC object
|
||||
var webchannel = null; // WebRTC data channel
|
||||
var webrtcconfiguration = null; //{ "iceServers": [ { 'urls': 'stun:stun.services.mozilla.com' }, { 'urls': 'stun:stun.l.google.com:19302' } ] };
|
||||
|
||||
// Set the title
|
||||
if (args.title) { QH('xtitle', ' - ' + args.title); document.title = document.title + ' - ' + args.title; }
|
||||
|
@ -47,60 +51,155 @@
|
|||
}
|
||||
|
||||
function onUserInputFocus(x) { userInputFocus = x; }
|
||||
function xclear(event) { QH('xmsg', ''); }
|
||||
function xcontrol(msg) {
|
||||
function displayClear() { QH('xmsg', ''); }
|
||||
|
||||
// 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 xrecv(msg) {
|
||||
|
||||
// Display a message from the remote user
|
||||
function displayRemote(msg) {
|
||||
QA('xmsg', '<div style="clear:both"><div style="background-color:#00cc99;color:black;border-radius:5px;padding:5px;float:left;margin-bottom:5px;margin-right:20px">' + 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 style="background-color:#0099ff;color:black;border-radius:5px;padding:5px;float:right;margin-bottom:5px;margin-left:20px">' + outtext + '</div><div></div></div>');
|
||||
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
|
||||
socket.send(JSON.stringify({ action: 'chat', msg: outtext }));
|
||||
send({ action: 'chat', msg: outtext });
|
||||
}
|
||||
}
|
||||
|
||||
function enableControls(lock) {
|
||||
controlsEnabled = lock;
|
||||
QE('sendButton', lock);
|
||||
QE('clearButton', lock);
|
||||
QE('xouttext', lock);
|
||||
}
|
||||
|
||||
// Enable user controls
|
||||
function enableControls(lock) { controlsEnabled = lock; QE('sendButton', lock); QE('clearButton', lock); QE('xouttext', lock); }
|
||||
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; }
|
||||
|
||||
// This is the WebRTC setup
|
||||
function startWebRTC(description) {
|
||||
// Setup the WebRTC object
|
||||
if (webrtc == null) {
|
||||
if (typeof RTCPeerConnection !== 'undefined') { webrtc = new RTCPeerConnection(webrtcconfiguration); }
|
||||
else if (typeof webkitRTCPeerConnection !== 'undefined') { webrtc = new webkitRTCPeerConnection(webrtcconfiguration); }
|
||||
if (webrtc == null) return;
|
||||
webrtc.onicecandidate = function (e) { try { if (e.candidate != null) { sendws({ action: 'webRtcIce', ice: e.candidate }); } } catch (ex) { } }
|
||||
webrtc.oniceconnectionstatechange = function () { if (webrtc && webrtc.iceConnectionState == 'failed') { closeWebRTC(); } }
|
||||
webrtc.ondatachannel = function (ev) {
|
||||
webchannel = ev.channel;
|
||||
webchannel.onmessage = function (event) { processMessage(event.data, 2); };
|
||||
webchannel.onopen = function () { webchannel.ok = true; sendws({ action: 'rtcSwitch', v: 0 }); };
|
||||
webchannel.onclose = function (event) { if (webchannel && webchannel.ok) { disconnect(); } else { closeWebRTC(); } }
|
||||
}
|
||||
}
|
||||
|
||||
// Initiate the WebRTC offer or handle the offer from the peer.
|
||||
if (description == null) {
|
||||
webchannel = webrtc.createDataChannel("DataChannel", {}); // { ordered: false, maxRetransmits: 2 }
|
||||
webchannel.onmessage = function (event) { processMessage(event.data, 2); };
|
||||
webchannel.onopen = function () { webchannel.ok = true; sendws({ action: 'rtcSwitch', v: 0 }); };
|
||||
webchannel.onclose = function (event) { if (webchannel && webchannel.ok) { disconnect(); } else { closeWebRTC(); } }
|
||||
webrtc.createOffer(function (offer) {
|
||||
webrtc.setLocalDescription(offer, function () { try { sendws({ action: 'webRtcSdp', sdp: offer }); } catch (ex) { } }, closeWebRTC);
|
||||
}, closeWebRTC, { mandatory: { OfferToReceiveAudio: false, OfferToReceiveVideo: false } });
|
||||
} else {
|
||||
webrtc.setRemoteDescription(new RTCSessionDescription(description), function () {
|
||||
if (description.type == 'offer') {
|
||||
webrtc.createAnswer(function (answer) {
|
||||
webrtc.setLocalDescription(answer, function () { try { sendws({ action: 'webRtcSdp', sdp: answer }); } catch (ex) { } }, closeWebRTC);
|
||||
}, closeWebRTC);
|
||||
}
|
||||
}, closeWebRTC);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = []; }
|
||||
}
|
||||
|
||||
// Close the WebRTC connection, should be called if a problem occurs during WebRTC setup.
|
||||
function closeWebRTC() {
|
||||
if (webchannel != null) { try { webchannel.close(); } catch (e) { } webchannel = null; }
|
||||
if (webrtc != null) { try { webrtc.close(); } catch (e) { } webrtc = null; }
|
||||
}
|
||||
|
||||
// Disconnect everything
|
||||
function disconnect() {
|
||||
enableControls(false);
|
||||
closeWebRTC();
|
||||
if (socket != null) { socket.close(); socket = null; }
|
||||
if (state > 0) { displayControl('Connection closed.'); }
|
||||
if (state > 1) { setTimeout(start, 500); }
|
||||
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) { socket.send(data); } } // 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); }
|
||||
}
|
||||
|
||||
// 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; }
|
||||
switch (data.action) {
|
||||
case 'chat': { displayRemote(data.msg); break; } // Incoming chat message.
|
||||
case 'random': { if (random > data.random) { startWebRTC(); } break; } // If we have a larger random value, we start WebRTC.
|
||||
case 'webRtcSdp': { startWebRTC(data.sdp); break; } // Remote WebRTC offer or answer.
|
||||
case 'webRtcIce': { if (webrtc) { webrtc.addIceCandidate(new RTCIceCandidate(data.ice)); } break; } // Remote ICE candidate
|
||||
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;
|
||||
}
|
||||
default: { console.log('Unhandled object data', data); break; }
|
||||
}
|
||||
} else {
|
||||
console.log('Unhandled data', typeof data, data);
|
||||
}
|
||||
}
|
||||
|
||||
// This is the main start
|
||||
function start() {
|
||||
// Get started
|
||||
enableControls(false);
|
||||
if ((typeof args.id == 'string') && (args.id.length > 0)) {
|
||||
//xcontrol('Connecting...');
|
||||
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; xcontrol('Waiting for other user...'); }
|
||||
socket.onopen = function () { state = 1; displayControl('Waiting for other user...'); }
|
||||
socket.onerror = function (e) { console.error(e); }
|
||||
socket.onclose = function () { enableControls(false); if (state > 0) { xcontrol('Connection closed.'); } socket = null; if (state > 1) { setTimeout(start, 500); } state = 0; }
|
||||
socket.onclose = function () { disconnect(); }
|
||||
socket.onmessage = function (msg) {
|
||||
if ((state < 2) && (typeof msg.data == 'string')) { enableControls(true); xcontrol('Connected.'); state = 2; return; }
|
||||
if (state == 2) {
|
||||
if (typeof msg.data == 'string') {
|
||||
var obj = JSON.parse(msg.data);
|
||||
switch (obj.action) {
|
||||
case 'chat': { xrecv(obj.msg); break; }
|
||||
}
|
||||
} else {
|
||||
//xrecv(JSON.stringify(msg));
|
||||
}
|
||||
if ((state < 2) && (typeof msg.data == 'string')) {
|
||||
enableControls(true);
|
||||
closeWebRTC();
|
||||
displayControl('Connected.');
|
||||
state = 2;
|
||||
sendws({ action: 'random', random: random }); // Send a random number. Higher number starts the WebRTC session.
|
||||
return;
|
||||
}
|
||||
if (state == 2) { processMessage(msg.data, 1); }
|
||||
}
|
||||
} else {
|
||||
xcontrol('Error: No connection key specified.');
|
||||
displayControl('Error: No connection key specified.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue