diff --git a/MeshCentralServer.njsproj b/MeshCentralServer.njsproj index 8d050d23..09fee8f2 100644 --- a/MeshCentralServer.njsproj +++ b/MeshCentralServer.njsproj @@ -60,6 +60,7 @@ + diff --git a/package.json b/package.json index 14f26b6a..db7f7887 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.2.1-z", + "version": "0.2.2-b", "keywords": [ "Remote Management", "Intel AMT", diff --git a/public/scripts/agent-redir-rtc-0.1.0.js b/public/scripts/agent-redir-rtc-0.1.0.js new file mode 100644 index 00000000..6cf1189a --- /dev/null +++ b/public/scripts/agent-redir-rtc-0.1.0.js @@ -0,0 +1,106 @@ +/** +* @description Mesh Agent Transport Module - using websocket relay +* @author Ylian Saint-Hilaire +* @version v0.0.1 +*/ + +// Construct a MeshServer agent direction object +var CreateKvmDataChannel = function (webchannel, module, keepalive) { + var obj = {}; + obj.m = module; // This is the inner module (Terminal or Desktop) + module.parent = obj; + obj.webchannel = webchannel; + obj.State = 0; + obj.protocol = module.protocol; // 1 = SOL, 2 = KVM, 3 = IDER, 4 = Files, 5 = FileTransfer + obj.onStateChanged = null; + obj.onControlMsg = null; + obj.debugmode = 0; + obj.keepalive = keepalive; + obj.rtcKeepAlive = null; + + // Private method + //obj.debug = function (msg) { console.log(msg); } + + obj.Start = function () { + if (obj.debugmode == 1) { console.log('start'); } + obj.xxStateChange(3); + obj.webchannel.onmessage = obj.xxOnMessage; + obj.rtcKeepAlive = setInterval(obj.xxSendRtcKeepAlive, 30000); + } + + obj.xxOnMessage = function (e) { + //if (obj.debugmode == 1) { console.log('Recv', e.data); } + //if (urlvars && urlvars['webrtctrace']) { console.log('WebRTC-Recv(' + obj.State + '): ', typeof e.data, e.data); } + if (typeof e.data == 'string') { if (obj.onControlMsg != null) { obj.onControlMsg(e.data); } return; } // If this is a control message, handle it here. + if (typeof e.data == 'object') { + var f = new FileReader(); + if (f.readAsBinaryString) { + // Chrome & Firefox (Draft) + f.onload = function (e) { obj.xxOnSocketData(e.target.result); } + f.readAsBinaryString(new Blob([e.data])); + } else if (f.readAsArrayBuffer) { + // Chrome & Firefox (Spec) + f.onloadend = function (e) { obj.xxOnSocketData(e.target.result); } + f.readAsArrayBuffer(e.data); + } else { + // IE10, readAsBinaryString does not exist, use an alternative. + var binary = '', bytes = new Uint8Array(e.data), length = bytes.byteLength; + for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); } + obj.xxOnSocketData(binary); + } + } else { + // If we get a string object, it maybe the WebRTC confirm. Ignore it. + //obj.debug("Agent Redir Relay - OnData - " + typeof e.data + " - " + e.data.length); + obj.xxOnSocketData(e.data); + } + }; + + obj.xxOnSocketData = function (data) { + if (!data) return; + if (typeof data === 'object') { + // This is an ArrayBuffer, convert it to a string array (used in IE) + var binary = "", bytes = new Uint8Array(data), length = bytes.byteLength; + for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); } + data = binary; + } + else if (typeof data !== 'string') return; + //console.log("xxOnSocketData", rstr2hex(data)); + return obj.m.ProcessData(data); + } + + // Send a control message over the WebRTC data channel + obj.sendCtrlMsg = function (x) { + if (typeof x == 'string') { + obj.webchannel.send(x); + //if (urlvars && urlvars['webrtctrace']) { console.log('WebRTC-Send(' + obj.State + '): ', typeof x, x); } + if (obj.keepalive != null) obj.keepalive.sendKeepAlive(); + } + } + + // Send a binary message over the WebRTC data channel + obj.send = function (x) { + if (typeof x == 'string') { var b = new Uint8Array(x.length); for (var i = 0; i < x.length; ++i) { b[i] = x.charCodeAt(i); } x = b; } + //if (urlvars && urlvars['webrtctrace']) { console.log('WebRTC-Send(' + obj.State + '): ', typeof x, x); } + obj.webchannel.send(x); + } + + obj.xxStateChange = function(newstate) { + if (obj.State == newstate) return; + obj.State = newstate; + obj.m.xxStateChange(obj.State); + if (obj.onStateChanged != null) obj.onStateChanged(obj, obj.State); + } + + obj.Stop = function () { + if (obj.debugmode == 1) { console.log('stop'); } + if (obj.rtcKeepAlive != null) { clearInterval(obj.rtcKeepAlive); obj.rtcKeepAlive = null; } + obj.xxStateChange(0); + } + + obj.xxSendRtcKeepAlive = function () { + //if (urlvars && urlvars['webrtctrace']) { console.log('WebRTC-SendKeepAlive()'); } + obj.sendCtrlMsg(JSON.stringify({ action: 'ping' })); + } + + return obj; +} diff --git a/public/scripts/amt-desktop-0.0.2.js b/public/scripts/amt-desktop-0.0.2.js index 3a2aee86..146170d7 100644 --- a/public/scripts/amt-desktop-0.0.2.js +++ b/public/scripts/amt-desktop-0.0.2.js @@ -622,7 +622,7 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { if (obj.kvmDataSupported == false) { obj.kvmDataSupported = true; console.log('KVM Data Channel Supported.'); } if (((obj.onKvmDataAck == -1) && (d.length == 16)) || (d.charCodeAt(15) != 0)) { obj.onKvmDataAck = true; } //if (urlvars && urlvars['kvmdatatrace']) { console.log('KVM-Recv(' + (d.length - 16) + '): ' + d.substring(16)); } - if (d.length > 16) { obj.onKvmData(d.substring(16)); } // Event the data and ack + if (d.length >= 16) { obj.onKvmData(d.substring(16)); } // Event the data and ack if ((obj.onKvmDataAck == true) && (obj.onKvmDataPending.length > 0)) { obj.sendKvmData(obj.onKvmDataPending.shift()); } // Send pending data } } @@ -635,16 +635,16 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { if (obj.onKvmDataAck !== true) { obj.onKvmDataPending.push(x); } else { - if (urlvars && urlvars['kvmdatatrace']) { console.log('KVM-Send(' + x.length + '): ' + x); } + //if (urlvars && urlvars['kvmdatatrace']) { console.log('KVM-Send(' + x.length + '): ' + x); } x = '\0KvmDataChannel\0' + x; - obj.Send(String.fromCharCode(6, 0, 0, 0) + IntToStr(x.length) + x); + obj.send(String.fromCharCode(6, 0, 0, 0) + IntToStr(x.length) + x); obj.onKvmDataAck = false; } } // Send a HWKVM keep alive if it's not been sent in the last 5 seconds. obj.sendKeepAlive = function () { - if (obj.lastKeepAlive < Date.now() - 5000) { obj.lastKeepAlive = Date.now(); obj.Send(String.fromCharCode(6, 0, 0, 0) + IntToStr(16) + '\0KvmDataChannel\0'); } + if (obj.lastKeepAlive < Date.now() - 5000) { obj.lastKeepAlive = Date.now(); obj.send(String.fromCharCode(6, 0, 0, 0) + IntToStr(16) + '\0KvmDataChannel\0'); } } // ###END###{DesktopInband} diff --git a/views/default-min.handlebars b/views/default-min.handlebars index da777d99..c2a2db95 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file + MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default-mobile-min.handlebars b/views/default-mobile-min.handlebars index a87bfe0b..9d11add8 100644 --- a/views/default-mobile-min.handlebars +++ b/views/default-mobile-min.handlebars @@ -1 +1 @@ - MeshCentral - Login
{{{title}}}
{{{title2}}}
\ No newline at end of file + MeshCentral - Login
{{{title}}}
{{{title2}}}
\ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 99037cfd..88ef73bc 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -22,6 +22,7 @@ + @@ -1958,7 +1959,7 @@ desk.m.bpp = 1; desk.m.useZRLE = true; desk.m.showmouse = true; - desk.m.onKvmData = function (data) { console.log('KVMData: ' + data); }; + desk.m.onKvmData = function (data) { console.log('KVM Data received in multi-desktop mode, this is not supported.'); }; // KVM Data Channel not supported in multi-desktop right now. //desk.m.onScreenSizeChange = deskAdjust; desk.Start(nodeid, 16994, '*', '*', 0); desk.contype = 2; @@ -3629,7 +3630,7 @@ // Show the right settings QV('d7amtkvm', (currentNode.intelamt != null && ((currentNode.intelamt.ver != null) || (mesh.mtype == 1))) && ((deskState == 0) || (desktop.contype == 2))); - QV('d7meshkvm', (mesh.mtype == 2) && (currentNode.agent.caps & 1) && ((deskState == false) || (desktop.contype == 1))); + QV('d7meshkvm', (webRtcDesktop) || ((mesh.mtype == 2) && (currentNode.agent.caps & 1) && ((deskState == false) || (desktop.contype == 1)))); // Enable buttons var online = ((currentNode.conn & 1) != 0); // If Agent (1) connected, enable remote desktop @@ -3666,7 +3667,77 @@ desktop.m.useZRLE = (desktopsettings.encoding < 3); desktop.m.showmouse = desktopsettings.showmouse; desktop.m.onScreenSizeChange = deskAdjust; - desktop.m.onKvmData = function (data) { console.log('KVMData: ' + data); }; + desktop.m.onKvmData = function (x) { + //console.log('onKvmData (' + x.length + '): ' + x); + // Send the presense probe only once if needed. + if (x.length == 0) { if (!desktop.m._sentPresence) { desktop.m._sentPresence = true; desktop.m.sendKvmData(JSON.stringify({ action: 'present', ver: 1 })); } return; } + var data = null; + try { data = JSON.parse(x); } catch (e) { } + if ((data != null) && (data.action != null)) { + if (data.action == 'restart') { + // Clear WebRTC channel + webRtcDesktopReset(); + desktop.m.sendKvmData(JSON.stringify({ action: 'present', ver: 1 })); + } else if ((data.action == 'present') && (webRtcDesktop == null)) { + // Setup WebRTC channel + webRtcDesktop = { platform: data.platform }; + var configuration = null; //{ "iceServers": [ { 'urls': 'stun:stun.services.mozilla.com' }, { 'urls': 'stun:stun.l.google.com:19302' } ] }; + if (typeof RTCPeerConnection !== 'undefined') { webRtcDesktop.webrtc = new RTCPeerConnection(configuration); } + else if (typeof webkitRTCPeerConnection !== 'undefined') { webRtcDesktop.webrtc = new webkitRTCPeerConnection(configuration); } + + webRtcDesktop.webchannel = webRtcDesktop.webrtc.createDataChannel("DataChannel", {}); // { ordered: false, maxRetransmits: 2 } + webRtcDesktop.webchannel.onopen = function () { + // Switch to software KVM + //if (urlvars && urlvars['kvmdatatrace']) { console.log('WebRTC Data Channel Open'); } + console.log('WebRTC Data Channel Open'); + Q('deskstatus').textContent = StatusStrs[desktop.State] + ', Soft-KVM'; + desktop.m.hold(true); + webRtcDesktop.webRtcActive = true; + webRtcDesktop.softdesktop = CreateKvmDataChannel(webRtcDesktop.webchannel, CreateAgentRemoteDesktop('Desk', Q('id_mainarea')), desktop.m); + webRtcDesktop.softdesktop.m.setRotation(desktop.m.rotation); + webRtcDesktop.softdesktop.m.onScreenSizeChange = deskAdjust; + if (desktopsettings.quality) { webRtcDesktop.softdesktop.m.CompressionLevel = desktopsettings.quality; } // Number from 1 to 100. 50 or less is best. + if (desktopsettings.scaling) { webRtcDesktop.softdesktop.m.ScalingLevel = desktopsettings.scaling; } + webRtcDesktop.softdesktop.Start(); + + // Check if we can get remote file access + // ###BEGIN###{DesktopInbandFiles} + /* + QV('go24', true); // Files + downloadFile = null; + p24files = webRtcDesktop.softdesktop; + p24targetpath = ''; + webRtcDesktop.softdesktop.onControlMsg = onFilesControlData; + webRtcDesktop.softdesktop.sendCtrlMsg(JSON.stringify({ action: 'ls', reqid: 1, path: '' })); // Ask for the root folder + */ + // ###END###{DesktopInbandFiles} + } + webRtcDesktop.webchannel.onclose = function (event) { + //if (urlvars['kvmdatatrace']) { console.log('WebRTC Data Channel Closed'); } + console.log('WebRTC Data Channel Closed'); + webRtcDesktopReset(); + } + webRtcDesktop.webrtc.onicecandidate = function (e) { + if (e.candidate == null) { + desktop.m.sendKvmData(JSON.stringify({ action: 'offer', ver: 1, sdp: webRtcDesktop.webrtcoffer.sdp })); + } else { + webRtcDesktop.webrtcoffer.sdp += ("a=" + e.candidate.candidate + "\r\n"); // New candidate, add it to the SDP + } + } + webRtcDesktop.webrtc.oniceconnectionstatechange = function () { + if ((webRtcDesktop != null) && (webRtcDesktop.webrtc != null) && ((webRtcDesktop.webrtc.iceConnectionState == 'disconnected') || (webRtcDesktop.webrtc.iceConnectionState == 'failed'))) { /*console.log('WebRTC ICE Failed');*/ webRtcDesktopReset(); } + } + webRtcDesktop.webrtc.createOffer(function (offer) { + // Got the offer + webRtcDesktop.webrtcoffer = offer; + webRtcDesktop.webrtc.setLocalDescription(offer, function () { }, webRtcDesktopReset); + }, webRtcDesktopReset, { mandatory: { OfferToReceiveAudio: false, OfferToReceiveVideo: false } }); + } else if ((data.action == 'answer') && (webRtcDesktop != null)) { + // Complete the WebRTC channel + webRtcDesktop.webrtc.setRemoteDescription(new RTCSessionDescription({ type: 'answer', sdp: data.sdp }), function () { }, webRtcDesktopReset); + } + } + }; desktop.Start(desktopNode._id, 16994, '*', '*', 0); desktop.contype = 2; } else { @@ -3687,10 +3758,34 @@ } else { // Disconnect and clean up the remote desktop desktop.Stop(); + webRtcDesktopReset(); desktopNode = desktop = null; } } + var webRtcDesktop = null; + function webRtcDesktopReset() { + if (webRtcDesktop == null) return; + if (webRtcDesktop.softdesktop != null) { webRtcDesktop.softdesktop.Stop(); webRtcDesktop.softdesktop = null; } + if (webRtcDesktop.webchannel != null) { try { webRtcDesktop.webchannel.close(); } catch (e) { } webRtcDesktop.webchannel = null; } + if (webRtcDesktop.webrtc != null) { try { webRtcDesktop.webrtc.close(); } catch (e) { } webRtcDesktop.webrtc = null; } + webRtcDesktop = null; + // Switch back to hardware KVM + if (desktop && desktop.m) { + desktop.m.hold(false); + Q('deskstatus').textContent = StatusStrs[desktop.State]; + } + // ###BEGIN###{DesktopInbandFiles} + /* + p24files = null; + p24downloadFileCancel() // If any downloads are in process, cancel them. + p24uploadFileCancel(); // If any uploads are in process, cancel them. + QV('go24', false); // Files + if (currentView == 24) { go(14); } + */ + // ###END###{DesktopInbandFiles} + } + function onDesktopStateChange(xdesktop, state) { var xstate = state; if ((xstate == 3) && (xdesktop.contype == 2)) { xstate++; } @@ -3707,6 +3802,7 @@ QV('termdisplays', false); deskFocusBtn.value = 'All Focus'; if (fullscreen == true) { deskToggleFull(); } + webRtcDesktopReset(); break; case 2: break; @@ -3809,6 +3905,7 @@ var mh = (Math.max(document.documentElement.clientHeight, window.innerHeight || 0) - (Q('deskarea1').clientHeight + Q('deskarea2').clientHeight + Q('deskarea4').clientHeight)); var mw = 9999; if (desktop) { mw = (desktop.m.width / desktop.m.height) * mh; } + if (webRtcDesktop && webRtcDesktop.softdesktop) { mw = (webRtcDesktop.softdesktop.m.width / webRtcDesktop.softdesktop.m.height) * mh; } QS('Desk')['max-height'] = mh + 'px'; QS('Desk')['max-width'] = mw + 'px'; x = 0; @@ -3821,6 +3918,7 @@ } else { var mw = 9999, mh = (Math.max(document.documentElement.clientHeight, window.innerHeight || 0) - (webPageFullScreen?276:290)); if (desktop) { mw = (desktop.m.width / desktop.m.height) * mh; } + if (webRtcDesktop && webRtcDesktop.softdesktop) { mw = (webRtcDesktop.softdesktop.m.width / webRtcDesktop.softdesktop.m.height) * mh; } document.documentElement.style.overflow = 'auto'; QS('Desk')['max-height'] = mh + 'px'; QS('Desk')['max-width'] = mw + 'px'; @@ -3945,10 +4043,10 @@ desktop.m.SetDisplay(display); } - function dmousedown(e) { if (!xxdialogMode && desktop != null && Q('DeskControl').checked) desktop.m.mousedown(e) } - function dmouseup(e) { if (!xxdialogMode && desktop != null && Q('DeskControl').checked) desktop.m.mouseup(e) } - function dmousemove(e) { if (!xxdialogMode && desktop != null && Q('DeskControl').checked) desktop.m.mousemove(e) } - function dmousewheel(e) { if (!xxdialogMode && desktop != null && Q('DeskControl').checked && desktop.m.mousewheel) { desktop.m.mousewheel(e); haltEvent(e); return true; } return false; } + function dmousedown(e) { if (!xxdialogMode && desktop != null && Q('DeskControl').checked) if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousedown(e); desktop.m.sendKeepAlive(); } else { desktop.m.mousedown(e); } } + function dmouseup(e) { if (!xxdialogMode && desktop != null && Q('DeskControl').checked) if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mouseup(e); desktop.m.sendKeepAlive(); } else { desktop.m.mouseup(e); } } + function dmousemove(e) { if (!xxdialogMode && desktop != null && Q('DeskControl').checked) { if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousemove(e); desktop.m.sendKeepAlive(); } else { desktop.m.mousemove(e); } } } + function dmousewheel(e) { if (!xxdialogMode && desktop != null && Q('DeskControl').checked) { if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousewheel(e); desktop.m.sendKeepAlive(); } else { if (desktop.m.mousewheel) { desktop.m.mousewheel(e); } } haltEvent(e); return true; } return false; } function drotate(x) { if (!xxdialogMode && desktop != null) { desktop.m.setRotation(desktop.m.rotation + x); deskAdjust(); deskAdjust(); } } function stopProcess(id, name) { setDialogMode(2, "Process Control", 3, stopProcessEx, 'Stop process #' + id + ' "' + name + '"?', id); } function stopProcessEx(buttons, tag) { meshserver.send({ action: 'msg', type:'pskill', nodeid: currentNode._id, value: tag }); setTimeout(refreshDeskTools, 300); }