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 ↔
My Devices | My Account | My Events | My Files | My Users | My Server | |
General | Desktop | Terminal | Files | Events | Intel® AMT | Console | |
Server disconnected, click to reconnect.
My Account
Device Groups (
New )
My Events
Show
My Files
This files is shared publically, click "link" to get public url.
✓
✗
Intel® AMT Redirection port or KVM feature is disabled, click here to enable it.
Remote computer is not powered on, click here to issue a power command.
Intel® AMT Redirection port or KVM feature is disabled, click here to enable it.
Remote computer is not powered on, click here to issue a power command.
Show
General -
Events -
Show
File Selection
Agent Remote Desktop
Scaling
Frame rate
Intel® AMT Hardware KVM
Image Encoding
\ No newline at end of file
+ MeshCentral ↔
My Devices | My Account | My Events | My Files | My Users | My Server | |
General | Desktop | Terminal | Files | Events | Intel® AMT | Console | |
Server disconnected, click to reconnect.
My Account
Device Groups (
New )
My Events
Show
My Files
This files is shared publically, click "link" to get public url.
✓
✗
Intel® AMT Redirection port or KVM feature is disabled, click here to enable it.
Remote computer is not powered on, click here to issue a power command.
Intel® AMT Redirection port or KVM feature is disabled, click here to enable it.
Remote computer is not powered on, click here to issue a power command.
Show
General -
Events -
Show
File Selection
Agent Remote Desktop
Scaling
Frame rate
Intel® AMT Hardware KVM
Image Encoding
\ 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 Server disconnected, click to reconnect.
◀ | | |
Device Groups (
New )
◀ | | My Files |
◀ | | |
\ No newline at end of file
+ MeshCentral - Login Server disconnected, click to reconnect.
◀ | | |
Device Groups (
New )
◀ | | My Files |
◀ | | |
\ 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); }