MeshMessenger will now try to switch to a WebRTC data channel.
Ylian Saint-Hilaire committed
Dec 16, 2018 at 00:17 UTC
67ae73df158509160e32015255017ca73a6d27c0
5 files changed
+135
-243
MeshCentralServer.njsproj
+2
@@ -31,6 +31,8 @@
31
<Compile Include="agents\modules_meshcmd\amt-xml.js" />
32
<Compile Include="agents\modules_meshcmd\amt.js" />
33
<Compile Include="agents\modules_meshcmd\process-manager.js" />
34
+ <Compile Include="agents\modules_meshcmd\service-host.js" />
35
+ <Compile Include="agents\modules_meshcmd\service-manager.js" />
36
<Compile Include="agents\modules_meshcmd\smbios.js" />
37
<Compile Include="agents\modules_meshcmd\user-sessions.js" />
38
<Compile Include="agents\modules_meshcore\amt-lme.js" />
agents/modules_meshcmd/promise.js
deleted
-207
@@ -1,207 +0,0 @@
1
-/*
2
-Copyright 2018 Intel Corporation
3
-
4
-Licensed under the Apache License, Version 2.0 (the "License");
5
-you may not use this file except in compliance with the License.
6
-You may obtain a copy of the License at
7
-
8
- http://www.apache.org/licenses/LICENSE-2.0
9
-
10
-Unless required by applicable law or agreed to in writing, software
11
-distributed under the License is distributed on an "AS IS" BASIS,
12
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
-See the License for the specific language governing permissions and
14
-limitations under the License.
15
-*/
16
-
17
-var refTable = {};
18
-
19
-function event_switcher_helper(desired_callee, target)
20
-{
21
- this._ObjectID = 'event_switcher';
22
- this.func = function func()
23
- {
24
- var args = [];
25
- for(var i in arguments)
26
- {
27
- args.push(arguments[i]);
28
- }
29
- return (func.target.apply(func.desired, args));
30
- };
31
- this.func.desired = desired_callee;
32
- this.func.target = target;
33
- this.func.self = this;
34
-}
35
-function event_switcher(desired_callee, target)
36
-{
37
- return (new event_switcher_helper(desired_callee, target));
38
-}
39
-
40
-function Promise(promiseFunc)
41
-{
42
- this._ObjectID = 'promise';
43
- this.promise = this;
44
- this._internal = { _ObjectID: 'promise.internal', promise: this, func: promiseFunc, completed: false, errors: false, completedArgs: [] };
45
- require('events').EventEmitter.call(this._internal);
46
- this._internal.on('_eventHook', function (eventName, eventCallback)
47
- {
48
- //console.log('hook', eventName, 'errors/' + this.errors + ' completed/' + this.completed);
49
- var r = null;
50
-
51
- if (eventName == 'resolved' && !this.errors && this.completed)
52
- {
53
- r = eventCallback.apply(this, this.completedArgs);
54
- if(r!=null)
55
- {
56
- this.emit_returnValue('resolved', r);
57
- }
58
- }
59
- if (eventName == 'rejected' && this.errors && this.completed)
60
- {
61
- eventCallback.apply(this, this.completedArgs);
62
- }
63
- if (eventName == 'settled' && this.completed)
64
- {
65
- eventCallback.apply(this, []);
66
- }
67
- });
68
- this._internal.resolver = function _resolver()
69
- {
70
- _resolver._self.errors = false;
71
- _resolver._self.completed = true;
72
- _resolver._self.completedArgs = [];
73
- var args = ['resolved'];
74
- if (this.emit_returnValue && this.emit_returnValue('resolved') != null)
75
- {
76
- _resolver._self.completedArgs.push(this.emit_returnValue('resolved'));
77
- args.push(this.emit_returnValue('resolved'));
78
- }
79
- else
80
- {
81
- for (var a in arguments)
82
- {
83
- _resolver._self.completedArgs.push(arguments[a]);
84
- args.push(arguments[a]);
85
- }
86
- }
87
- _resolver._self.emit.apply(_resolver._self, args);
88
- _resolver._self.emit('settled');
89
- };
90
- this._internal.rejector = function _rejector()
91
- {
92
- _rejector._self.errors = true;
93
- _rejector._self.completed = true;
94
- _rejector._self.completedArgs = [];
95
- var args = ['rejected'];
96
- for (var a in arguments)
97
- {
98
- _rejector._self.completedArgs.push(arguments[a]);
99
- args.push(arguments[a]);
100
- }
101
-
102
- _rejector._self.emit.apply(_rejector._self, args);
103
- _rejector._self.emit('settled');
104
- };
105
- this.catch = function(func)
106
- {
107
- this._internal.once('rejected', event_switcher(this, func).func);
108
- }
109
- this.finally = function (func)
110
- {
111
- this._internal.once('settled', event_switcher(this, func).func);
112
- };
113
- this.then = function (resolved, rejected)
114
- {
115
- if (resolved) { this._internal.once('resolved', event_switcher(this, resolved).func); }
116
- if (rejected) { this._internal.once('rejected', event_switcher(this, rejected).func); }
117
-
118
- var retVal = new Promise(function (r, j) { });
119
- this._internal.once('resolved', retVal._internal.resolver);
120
- this._internal.once('rejected', retVal._internal.rejector);
121
- retVal.parentPromise = this;
122
- return (retVal);
123
- };
124
-
125
- this._internal.resolver._self = this._internal;
126
- this._internal.rejector._self = this._internal;;
127
-
128
- try
129
- {
130
- promiseFunc.call(this, this._internal.resolver, this._internal.rejector);
131
- }
132
- catch(e)
133
- {
134
- this._internal.errors = true;
135
- this._internal.completed = true;
136
- this._internal.completedArgs = [e];
137
- this._internal.emit('rejected', e);
138
- this._internal.emit('settled');
139
- }
140
-
141
- if(!this._internal.completed)
142
- {
143
- // Save reference of this object
144
- refTable[this._internal._hashCode()] = this._internal;
145
- this._internal.once('settled', function () { refTable[this._hashCode()] = null; });
146
- }
147
-}
148
-
149
-Promise.resolve = function resolve()
150
-{
151
- var retVal = new Promise(function (r, j) { });
152
- var args = [];
153
- for (var i in arguments)
154
- {
155
- args.push(arguments[i]);
156
- }
157
- retVal._internal.resolver.apply(retVal._internal, args);
158
- return (retVal);
159
-};
160
-Promise.reject = function reject() {
161
- var retVal = new Promise(function (r, j) { });
162
- var args = [];
163
- for (var i in arguments) {
164
- args.push(arguments[i]);
165
- }
166
- retVal._internal.rejector.apply(retVal._internal, args);
167
- return (retVal);
168
-};
169
-Promise.all = function all(promiseList)
170
-{
171
- var ret = new Promise(function (res, rej)
172
- {
173
- this.__rejector = rej;
174
- this.__resolver = res;
175
- this.__promiseList = promiseList;
176
- this.__done = false;
177
- this.__count = 0;
178
- });
179
-
180
- for (var i in promiseList)
181
- {
182
- promiseList[i].then(function ()
183
- {
184
- // Success
185
- if(++ret.__count == ret.__promiseList.length)
186
- {
187
- ret.__done = true;
188
- ret.__resolver(ret.__promiseList);
189
- }
190
- }, function (arg)
191
- {
192
- // Failure
193
- if(!ret.__done)
194
- {
195
- ret.__done = true;
196
- ret.__rejector(arg);
197
- }
198
- });
199
- }
200
- if (promiseList.length == 0)
201
- {
202
- ret.__resolver(promiseList);
203
- }
204
- return (ret);
205
-};
206
-
207
-module.exports = Promise;
\ No newline at end of file
agents/modules_meshcore/win-terminal.js
+5
-7
@@ -249,7 +249,7 @@ function windows_terminal() {
249
break;
250
case EVENT_CONSOLE_UPDATE_SIMPLE:
251
//console.log('UPDATE SIMPLE: [X: ' + LOWORD(idObject.Val) + ' Y: ' + HIWORD(idObject.Val) + ' Char: ' + LOWORD(idChild.Val) + ' Attr: ' + HIWORD(idChild.Val) + ']');
252
- var simplebuffer = { data: [Buffer.alloc(1, LOWORD(idChild.Val))], attributes: [HIWORD(idChild.Val)], width: 1, height: 1, x: LOWORD(idObject.Val) + 1, y: HIWORD(idObject.Val) };
252
+ var simplebuffer = { data: [ Buffer.alloc(1, LOWORD(idChild.Val)) ], attributes: [ HIWORD(idChild.Val) ], width: 1, height: 1, x: LOWORD(idObject.Val), y: HIWORD(idObject.Val) };
253
this.terminal._SendDataBuffer(simplebuffer);
254
break;
255
case EVENT_CONSOLE_UPDATE_SCROLL:
@@ -330,8 +330,8 @@ function windows_terminal() {
330
}
331
this._WriteCharacter = function (key, bControlKey) {
332
var rec = GM.CreateVariable(20);
333
- rec.Deref(0, 2).toBuffer().writeUInt16LE(KEY_EVENT); // rec.EventType
334
- rec.Deref(4, 4).toBuffer().writeUInt16LE(1); // rec.Event.KeyEvent.bKeyDown
333
+ rec.Deref(0, 2).toBuffer().writeUInt16LE(KEY_EVENT); // rec.EventType
334
+ rec.Deref(4, 4).toBuffer().writeUInt16LE(1); // rec.Event.KeyEvent.bKeyDown
335
rec.Deref(16, 4).toBuffer().writeUInt32LE(bControlKey); // rec.Event.KeyEvent.dwControlKeyState
336
rec.Deref(14, 1).toBuffer()[0] = key; // rec.Event.KeyEvent.uChar.AsciiChar
337
rec.Deref(8, 2).toBuffer().writeUInt16LE(1); // rec.Event.KeyEvent.wRepeatCount
@@ -345,9 +345,8 @@ function windows_terminal() {
345
return (this._kernel32.WriteConsoleInputA(this._stdinput, rec, 1, dwWritten).Val != 0);
346
}
347
348
+ // Get the current visible screen buffer
349
this._GetScreenBuffer = function (sx, sy, ex, ey) {
349
- // get the current visible screen buffer
350
-
350
var info = GM.CreateVariable(22);
351
if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, info).Val == 0) { throw ('Error getting screen buffer info'); }
352
@@ -366,7 +365,6 @@ function windows_terminal() {
365
this._scrx = this._scry = 0;
366
}
367
369
-
368
var nBuffer = GM.CreateVariable((ex - sx + 1) * (ey - sy + 1) * 4);
369
var size = GM.CreateVariable(4);
370
size.Deref(0, 2).toBuffer().writeUInt16LE(ex - sx + 1, 0);
@@ -418,7 +416,7 @@ function windows_terminal() {
416
417
//line = data.data.slice(data.width * dy, (data.width * dy) + data.width);
418
//attr = data.attributes.slice(data.width * dy, (data.width * dy) + data.width);
421
- this._stream.push(TranslateLine(data.x, data.y + dy + 1, line, attr));
419
+ this._stream.push(TranslateLine(data.x + 1, data.y + dy + 1, line, attr));
420
}
421
}
422
package.json
+1
-1
@@ -1,6 +1,6 @@
1
{
2
"name": "meshcentral",
3
- "version": "0.2.4-p",
3
+ "version": "0.2.4-q",
4
"keywords": [
5
"Remote Management",
6
"Intel AMT",
public/messenger.htm
+127
-28
@@ -16,14 +16,18 @@
16
<div id="xbottom" style="position:absolute;left:0;right:0;bottom:0px;height:30px;background-color:#036">
17
<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>
18
<input type="button" id="sendButton" value="Send" style="position:absolute;right:110px;width:100px;top:4px;" onclick="xsend(event)" />
19
- <input type="button" id="clearButton" value="Clear" style="position:absolute;right:5px;width:100px;top:4px;" onclick="xclear(event)" />
19
+ <input type="button" id="clearButton" value="Clear" style="position:absolute;right:5px;width:100px;top:4px;" onclick="displayClear()" />
20
</div>
21
<script type="text/javascript">
22
var userInputFocus = 0;
23
var controlsEnabled = true;
24
var args = parseUriArgs();
25
- var socket = null;
26
- var state = 0;
25
+ var socket = null; // Websocket object
26
+ var state = 0; // Connection state. 0 = Disconnected, 1 = Connecting, 2 = Connected.
27
+ var random = Math.random(); // Selected random, larger value initiates WebRTC.
28
+ var webrtc = null; // Main WebRTC object
29
+ var webchannel = null; // WebRTC data channel
30
+ var webrtcconfiguration = null; //{ "iceServers": [ { 'urls': 'stun:stun.services.mozilla.com' }, { 'urls': 'stun:stun.l.google.com:19302' } ] };
31
32
// Set the title
33
if (args.title) { QH('xtitle', ' - ' + args.title); document.title = document.title + ' - ' + args.title; }
@@ -47,60 +51,155 @@
51
}
52
53
function onUserInputFocus(x) { userInputFocus = x; }
50
- function xclear(event) { QH('xmsg', ''); }
51
- function xcontrol(msg) {
54
+ function displayClear() { QH('xmsg', ''); }
55
+
56
+ // Display a control message
57
+ function displayControl(msg) {
58
QA('xmsg', '<div style="clear:both"><div style="color:gray;float:left;margin-bottom:2px">' + msg + '</div><div></div></div>');
59
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
60
}
55
- function xrecv(msg) {
61
+
62
+ // Display a message from the remote user
63
+ function displayRemote(msg) {
64
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>');
65
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
66
}
67
68
+ // Display and send a message from the local user
69
function xsend(event) {
70
var outtext = Q('xouttext').value;
71
if (outtext.length > 0) {
72
Q('xouttext').value = '';
73
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>');
74
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
66
- socket.send(JSON.stringify({ action: 'chat', msg: outtext }));
75
+ send({ action: 'chat', msg: outtext });
76
}
77
}
78
70
- function enableControls(lock) {
71
- controlsEnabled = lock;
72
- QE('sendButton', lock);
73
- QE('clearButton', lock);
74
- QE('xouttext', lock);
75
- }
76
-
79
+ // Enable user controls
80
+ function enableControls(lock) { controlsEnabled = lock; QE('sendButton', lock); QE('clearButton', lock); QE('xouttext', lock); }
81
function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
82
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; }
83
84
+ // This is the WebRTC setup
85
+ function startWebRTC(description) {
86
+ // Setup the WebRTC object
87
+ if (webrtc == null) {
88
+ if (typeof RTCPeerConnection !== 'undefined') { webrtc = new RTCPeerConnection(webrtcconfiguration); }
89
+ else if (typeof webkitRTCPeerConnection !== 'undefined') { webrtc = new webkitRTCPeerConnection(webrtcconfiguration); }
90
+ if (webrtc == null) return;
91
+ webrtc.onicecandidate = function (e) { try { if (e.candidate != null) { sendws({ action: 'webRtcIce', ice: e.candidate }); } } catch (ex) { } }
92
+ webrtc.oniceconnectionstatechange = function () { if (webrtc && webrtc.iceConnectionState == 'failed') { closeWebRTC(); } }
93
+ webrtc.ondatachannel = function (ev) {
94
+ webchannel = ev.channel;
95
+ webchannel.onmessage = function (event) { processMessage(event.data, 2); };
96
+ webchannel.onopen = function () { webchannel.ok = true; sendws({ action: 'rtcSwitch', v: 0 }); };
97
+ webchannel.onclose = function (event) { if (webchannel && webchannel.ok) { disconnect(); } else { closeWebRTC(); } }
98
+ }
99
+ }
100
+
101
+ // Initiate the WebRTC offer or handle the offer from the peer.
102
+ if (description == null) {
103
+ webchannel = webrtc.createDataChannel("DataChannel", {}); // { ordered: false, maxRetransmits: 2 }
104
+ webchannel.onmessage = function (event) { processMessage(event.data, 2); };
105
+ webchannel.onopen = function () { webchannel.ok = true; sendws({ action: 'rtcSwitch', v: 0 }); };
106
+ webchannel.onclose = function (event) { if (webchannel && webchannel.ok) { disconnect(); } else { closeWebRTC(); } }
107
+ webrtc.createOffer(function (offer) {
108
+ webrtc.setLocalDescription(offer, function () { try { sendws({ action: 'webRtcSdp', sdp: offer }); } catch (ex) { } }, closeWebRTC);
109
+ }, closeWebRTC, { mandatory: { OfferToReceiveAudio: false, OfferToReceiveVideo: false } });
110
+ } else {
111
+ webrtc.setRemoteDescription(new RTCSessionDescription(description), function () {
112
+ if (description.type == 'offer') {
113
+ webrtc.createAnswer(function (answer) {
114
+ webrtc.setLocalDescription(answer, function () { try { sendws({ action: 'webRtcSdp', sdp: answer }); } catch (ex) { } }, closeWebRTC);
115
+ }, closeWebRTC);
116
+ }
117
+ }, closeWebRTC);
118
+ }
119
+ }
120
+
121
+ // Indicate to peer that data traffic will no longer be sent over websocket and start holding traffic.
122
+ function performWebRtcSwitch() {
123
+ if (webchannel && webchannel.ok) { sendws({ action: 'rtcSwitch', v: 1 }); webchannel.xoutBuffer = []; }
124
+ }
125
+
126
+ // Close the WebRTC connection, should be called if a problem occurs during WebRTC setup.
127
+ function closeWebRTC() {
128
+ if (webchannel != null) { try { webchannel.close(); } catch (e) { } webchannel = null; }
129
+ if (webrtc != null) { try { webrtc.close(); } catch (e) { } webrtc = null; }
130
+ }
131
+
132
+ // Disconnect everything
133
+ function disconnect() {
134
+ enableControls(false);
135
+ closeWebRTC();
136
+ if (socket != null) { socket.close(); socket = null; }
137
+ if (state > 0) { displayControl('Connection closed.'); }
138
+ if (state > 1) { setTimeout(start, 500); }
139
+ state = 0;
140
+ }
141
+
142
+ // Send data over the current transport (WebRTC first)
143
+ function send(data) {
144
+ if (state != 2) return; // If not in connected state, ignore this.
145
+ if (typeof data == 'object') { data = JSON.stringify(data); } // If this is an object, convert it to a string.
146
+ 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.
147
+ else { if (socket != null) { socket.send(data); } } // If a websocket channel is present, use that.
148
+ }
149
+
150
+ // Send data over the websocket transport (WebSocket only)
151
+ function sendws(data) {
152
+ if (state != 2) return;
153
+ if (typeof data == 'object') { data = JSON.stringify(data); }
154
+ if (socket != null) { socket.send(data); }
155
+ }
156
+
157
+ // Process incoming messages
158
+ function processMessage(data, transport) {
159
+ if (typeof data == 'string') {
160
+ try { data = JSON.parse(data); } catch (ex) { console.log('Unable to parse', data); return; }
161
+ switch (data.action) {
162
+ case 'chat': { displayRemote(data.msg); break; } // Incoming chat message.
163
+ case 'random': { if (random > data.random) { startWebRTC(); } break; } // If we have a larger random value, we start WebRTC.
164
+ case 'webRtcSdp': { startWebRTC(data.sdp); break; } // Remote WebRTC offer or answer.
165
+ case 'webRtcIce': { if (webrtc) { webrtc.addIceCandidate(new RTCIceCandidate(data.ice)); } break; } // Remote ICE candidate
166
+ case 'rtcSwitch': { // WebRTC switch over commands.
167
+ switch (data.v) {
168
+ case 0: { performWebRtcSwitch(); break; } // Other side is ready for switch over to WebRTC
169
+ case 1: { sendws({ action: 'rtcSwitch', v: 2 }); break; } // Other side no longer sending data on websocket, confirm we got the end marker
170
+ 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
171
+ }
172
+ break;
173
+ }
174
+ default: { console.log('Unhandled object data', data); break; }
175
+ }
176
+ } else {
177
+ console.log('Unhandled data', typeof data, data);
178
+ }
179
+ }
180
+
181
+ // This is the main start
182
function start() {
183
// Get started
184
enableControls(false);
185
if ((typeof args.id == 'string') && (args.id.length > 0)) {
84
- //xcontrol('Connecting...');
186
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);
86
- socket.onopen = function () { state = 1; xcontrol('Waiting for other user...'); }
187
+ socket.onopen = function () { state = 1; displayControl('Waiting for other user...'); }
188
socket.onerror = function (e) { console.error(e); }
88
- socket.onclose = function () { enableControls(false); if (state > 0) { xcontrol('Connection closed.'); } socket = null; if (state > 1) { setTimeout(start, 500); } state = 0; }
189
+ socket.onclose = function () { disconnect(); }
190
socket.onmessage = function (msg) {
90
- if ((state < 2) && (typeof msg.data == 'string')) { enableControls(true); xcontrol('Connected.'); state = 2; return; }
91
- if (state == 2) {
92
- if (typeof msg.data == 'string') {
93
- var obj = JSON.parse(msg.data);
94
- switch (obj.action) {
95
- case 'chat': { xrecv(obj.msg); break; }
96
- }
97
- } else {
98
- //xrecv(JSON.stringify(msg));
99
- }
191
+ if ((state < 2) && (typeof msg.data == 'string')) {
192
+ enableControls(true);
193
+ closeWebRTC();
194
+ displayControl('Connected.');
195
+ state = 2;
196
+ sendws({ action: 'random', random: random }); // Send a random number. Higher number starts the WebRTC session.
197
+ return;
198
}
199
+ if (state == 2) { processMessage(msg.data, 1); }
200
}
201
} else {
103
- xcontrol('Error: No connection key specified.');
202
+ displayControl('Error: No connection key specified.');
203
}
204
}
205