XTerm improvements.
Ylian Saint-Hilaire committed
Jan 24, 2020 at 12:49 UTC
0fee95920614b51bf6b1a299089c66010288c44e
4 files changed
+136
-99
agents/meshcore.js
+115
-86
@@ -1212,11 +1212,15 @@ function createMeshCore(agent) {
1212
1213
var python = fs.existsSync('/usr/bin/python') ? '/usr/bin/python' : false;
1214
var shell = bash || sh;
1215
-
1216
- var options = { uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: { HISTCONTROL: 'ignoreboth', TERM: 'xterm' } };
1217
- var setupcommands = 'alias ls=\'ls --color=auto\'\n';
1218
- if (shell == sh) setupcommands += 'stty erase ^H\n';
1219
- setupcommands += 'clear\n';
1215
+ var env = { HISTCONTROL: 'ignoreboth', TERM: 'xterm' }; // LINES: '100', COLUMNS: '100'
1216
+ if (this.httprequest.xoptions) {
1217
+ if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); }
1218
+ if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); }
1219
+ }
1220
+ var options = { uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env };
1221
+ var setupcommands = ' alias ls=\'ls --color=auto\'\n';
1222
+ if (shell == sh) setupcommands += ' stty erase ^H\n';
1223
+ setupcommands += ' clear\n';
1224
1225
if (script && shell && process.platform == 'linux') {
1226
this.httprequest.process = childProcess.execFile(script, ['script', '--return', '--quiet', '-c', '"' + shell + '"', '/dev/null'], options); // Start as active user
@@ -1760,94 +1764,119 @@ function createMeshCore(agent) {
1764
return;
1765
}
1766
1763
- if (obj.type == 'options') {
1764
- // These are additional connection options passed in the control channel.
1765
- //sendConsoleText('options: ' + JSON.stringify(obj));
1766
- delete obj.type;
1767
- ws.httprequest.xoptions = obj;
1768
- } else if (obj.type == 'close') {
1769
- // We received the close on the websocket
1770
- //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close');
1771
- try { ws.close(); } catch (e) { }
1772
- } else if (obj.type == 'webrtc0') { // Browser indicates we can start WebRTC switch-over.
1773
- if (ws.httprequest.protocol == 1) { // Terminal
1774
- // This is a terminal data stream, unpipe the terminal now and indicate to the other side that terminal data will no longer be received over WebSocket
1775
- if (process.platform == 'win32') {
1776
- ws.httprequest._term.unpipe(ws);
1777
- }
1778
- else {
1779
- ws.httprequest.process.stdout.unpipe(ws);
1780
- ws.httprequest.process.stderr.unpipe(ws);
1781
- }
1782
- } else if (ws.httprequest.protocol == 2) { // Desktop
1783
- // This is a KVM data stream, unpipe the KVM now and indicate to the other side that KVM data will no longer be received over WebSocket
1784
- ws.httprequest.desktop.kvm.unpipe(ws);
1785
- } else {
1786
- // Switch things around so all WebRTC data goes to onTunnelData().
1787
- ws.rtcchannel.httprequest = ws.httprequest;
1788
- ws.rtcchannel.removeAllListeners('data');
1789
- ws.rtcchannel.on('data', onTunnelData);
1767
+ switch (obj.type) {
1768
+ case 'options': {
1769
+ // These are additional connection options passed in the control channel.
1770
+ //sendConsoleText('options: ' + JSON.stringify(obj));
1771
+ delete obj.type;
1772
+ ws.httprequest.xoptions = obj;
1773
+ break;
1774
}
1791
- ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker
1792
- } else if (obj.type == 'webrtc1') {
1793
- if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal
1794
- // Switch the user input from websocket to webrtc at this point.
1795
- if (process.platform == 'win32') {
1796
- ws.unpipe(ws.httprequest._term);
1797
- ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
1798
- }
1799
- else {
1800
- ws.unpipe(ws.httprequest.process.stdin);
1801
- ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
1802
- }
1803
- ws.resume(); // Resume the websocket to keep receiving control data
1804
- } else if (ws.httprequest.protocol == 2) { // Desktop
1805
- // Switch the user input from websocket to webrtc at this point.
1806
- ws.unpipe(ws.httprequest.desktop.kvm);
1807
- try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (e) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text.
1808
- ws.resume(); // Resume the websocket to keep receiving control data
1775
+ case 'close': {
1776
+ // We received the close on the websocket
1777
+ //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close');
1778
+ try { ws.close(); } catch (e) { }
1779
+ break;
1780
}
1810
- ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point.
1811
- } else if (obj.type == 'webrtc2') {
1812
- // Other side received websocket end of data marker, start sending data on WebRTC channel
1813
- if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal
1814
- if (process.platform == 'win32') {
1815
- ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
1781
+ case 'termsize': {
1782
+ if (ws.httprequest.protocol == 1) { // Terminal
1783
+ // Indicates a change in terminal size
1784
+ if (process.platform == 'win32') {
1785
+ if (ws.httprequest._term == null) return;
1786
+ //sendConsoleText('Win32-TermSize: ' + obj.cols + 'x' + obj.rows);
1787
+ // TODO
1788
+ } else {
1789
+ if (ws.httprequest.process == null) return;
1790
+ //sendConsoleText('Linux-TermSize: ' + obj.cols + 'x' + obj.rows);
1791
+ // TODO
1792
+ }
1793
}
1817
- else {
1818
- ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
1819
- ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
1794
+ break;
1795
+ }
1796
+ case 'webrtc0': { // Browser indicates we can start WebRTC switch-over.
1797
+ if (ws.httprequest.protocol == 1) { // Terminal
1798
+ // This is a terminal data stream, unpipe the terminal now and indicate to the other side that terminal data will no longer be received over WebSocket
1799
+ if (process.platform == 'win32') {
1800
+ ws.httprequest._term.unpipe(ws);
1801
+ } else {
1802
+ ws.httprequest.process.stdout.unpipe(ws);
1803
+ ws.httprequest.process.stderr.unpipe(ws);
1804
+ }
1805
+ } else if (ws.httprequest.protocol == 2) { // Desktop
1806
+ // This is a KVM data stream, unpipe the KVM now and indicate to the other side that KVM data will no longer be received over WebSocket
1807
+ ws.httprequest.desktop.kvm.unpipe(ws);
1808
+ } else {
1809
+ // Switch things around so all WebRTC data goes to onTunnelData().
1810
+ ws.rtcchannel.httprequest = ws.httprequest;
1811
+ ws.rtcchannel.removeAllListeners('data');
1812
+ ws.rtcchannel.on('data', onTunnelData);
1813
}
1821
- } else if (ws.httprequest.protocol == 2) { // Desktop
1822
- ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
1814
+ ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker
1815
+ break;
1816
}
1824
- } else if (obj.type == 'offer') {
1825
- // This is a WebRTC offer.
1826
- if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) return; // TODO: Terminal is currently broken with WebRTC. Reject WebRTC upgrade for now.
1827
- ws.webrtc = rtc.createConnection();
1828
- ws.webrtc.websocket = ws;
1829
- ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ });
1830
- ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ });
1831
- ws.webrtc.on('dataChannel', function (rtcchannel) {
1832
- //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol);
1833
- rtcchannel.xrtc = this;
1834
- rtcchannel.websocket = this.websocket;
1835
- this.rtcchannel = rtcchannel;
1836
- this.websocket.rtcchannel = rtcchannel;
1837
- this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData);
1838
- this.websocket.rtcchannel.on('end', function () {
1839
- // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes.
1840
- //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed');
1841
- if (this.websocket.desktop && this.websocket.desktop.kvm) {
1842
- this.unpipe(this.websocket.desktop.kvm);
1843
- this.websocket.httprequest.desktop.kvm.unpipe(this);
1817
+ case 'webrtc1': {
1818
+ if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal
1819
+ // Switch the user input from websocket to webrtc at this point.
1820
+ if (process.platform == 'win32') {
1821
+ ws.unpipe(ws.httprequest._term);
1822
+ ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
1823
+ } else {
1824
+ ws.unpipe(ws.httprequest.process.stdin);
1825
+ ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
1826
+ }
1827
+ ws.resume(); // Resume the websocket to keep receiving control data
1828
+ } else if (ws.httprequest.protocol == 2) { // Desktop
1829
+ // Switch the user input from websocket to webrtc at this point.
1830
+ ws.unpipe(ws.httprequest.desktop.kvm);
1831
+ try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (e) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text.
1832
+ ws.resume(); // Resume the websocket to keep receiving control data
1833
+ }
1834
+ ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point.
1835
+ break;
1836
+ }
1837
+ case 'webrtc2': {
1838
+ // Other side received websocket end of data marker, start sending data on WebRTC channel
1839
+ if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal
1840
+ if (process.platform == 'win32') {
1841
+ ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
1842
+ } else {
1843
+ ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
1844
+ ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
1845
}
1846
+ } else if (ws.httprequest.protocol == 2) { // Desktop
1847
+ ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
1848
+ }
1849
+ break;
1850
+ }
1851
+ case 'offer': {
1852
+ // This is a WebRTC offer.
1853
+ if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) return; // TODO: Terminal is currently broken with WebRTC. Reject WebRTC upgrade for now.
1854
+ ws.webrtc = rtc.createConnection();
1855
+ ws.webrtc.websocket = ws;
1856
+ ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ });
1857
+ ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ });
1858
+ ws.webrtc.on('dataChannel', function (rtcchannel) {
1859
+ //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol);
1860
+ rtcchannel.xrtc = this;
1861
+ rtcchannel.websocket = this.websocket;
1862
+ this.rtcchannel = rtcchannel;
1863
+ this.websocket.rtcchannel = rtcchannel;
1864
+ this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData);
1865
+ this.websocket.rtcchannel.on('end', function () {
1866
+ // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes.
1867
+ //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed');
1868
+ if (this.websocket.desktop && this.websocket.desktop.kvm) {
1869
+ this.unpipe(this.websocket.desktop.kvm);
1870
+ this.websocket.httprequest.desktop.kvm.unpipe(this);
1871
+ }
1872
+ });
1873
+ this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over.
1874
});
1846
- this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over.
1847
- });
1848
- var sdp = null;
1849
- try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (ex) { }
1850
- if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); }
1875
+ var sdp = null;
1876
+ try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (ex) { }
1877
+ if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); }
1878
+ break;
1879
+ }
1880
}
1881
}
1882
package.json
+1
-1
@@ -1,6 +1,6 @@
1
{
2
"name": "meshcentral",
3
- "version": "0.4.7-s",
3
+ "version": "0.4.7-t",
4
"keywords": [
5
"Remote Management",
6
"Intel AMT",
public/scripts/common-0.0.1.js
+2
-2
@@ -34,8 +34,8 @@ function IntToStrX(v) { return String.fromCharCode(v & 0xFF, (v >> 8) & 0xFF, (v
34
function MakeToArray(v) { if (!v || v == null || typeof v == 'object') return v; return [v]; }
35
function SplitArray(v) { return v.split(','); }
36
function Clone(v) { return JSON.parse(JSON.stringify(v)); }
37
-function EscapeHtml(x) { if (typeof x == "string") return x.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, '''); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
38
-function EscapeHtmlBreaks(x) { if (typeof x == "string") return x.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, ''').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, ' '); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
37
+function EscapeHtml(x) { if (typeof x == 'string') return x.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, '''); if (typeof x == 'boolean') return x; if (typeof x == 'number') return x; }
38
+function EscapeHtmlBreaks(x) { if (typeof x == 'string') return x.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, ''').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, ' '); if (typeof x == 'boolean') return x; if (typeof x == 'number') return x; }
39
40
// Move an element from one position in an array to a new position
41
function ArrayElementMove(arr, from, to) { arr.splice(to, 0, arr.splice(from, 1)[0]); };
views/xterm.handlebars
+18
-10
@@ -18,7 +18,7 @@
18
</head>
19
<body style="overflow:hidden;background-color:black">
20
<div id=p11 class="noselect" style="overflow:hidden">
21
- <div id=deskarea0>
21
+ <div id=deskarea0 style="position:relative">
22
<div id=deskarea1 class="areaHead">
23
<div class="toright2">
24
</div>
@@ -36,7 +36,7 @@
36
<div id="bigfail" style="display:none;left:calc((100vh / 2))"><b>✗</b></div>
37
<div id="metadatadiv" style="padding:20px;color:lightgrey;text-align:left;display:none"></div>
38
<div id=terminal style="max-height:calc(100vh - 54px);height:calc(100vh - 54px);"></div>
39
- <div id=p11DeskConsoleMsg style="display:none;cursor:pointer;position:absolute;left:30px;top:17px;color:yellow;background-color:rgba(0,0,0,0.6);padding:10px;border-radius:5px" onclick=clearConsoleMsg()></div>
39
+ <div id=TermConsoleMsg style="cursor:pointer;z-index:10;position:absolute;left:30px;top:17px;color:yellow;background-color:rgba(0,0,0,0.6);padding:10px;border-radius:5px" onclick=clearConsoleMsg()></div>
40
</div>
41
<div id=deskarea4 class="areaHead">
42
<div class="toright2">
@@ -75,6 +75,7 @@
75
var authRelayCookie = '{{{authRelayCookie}}}';
76
var serverPublicNamePort = '{{{serverDnsName}}}:{{{serverPublicPort}}}';
77
var StatusStrs = ["Disconnected", "Connecting...", "Setup...", "Connected", "Intel® AMT Connected"];
78
+ var resizeTimer = null;
79
80
function start() {
81
// Parse any URL arguments
@@ -123,6 +124,12 @@
124
return obj;
125
}
126
127
+ // Send the new terminal size to the agent
128
+ function sendResize() {
129
+ resizeTimer = null;
130
+ if ((term != null) && (tunnel != null)) { tunnel.sendCtrlMsg(JSON.stringify({ ctrlChannel: '102938', type: 'termsize', cols: term.cols, rows: term.rows })); }
131
+ }
132
+
133
// Called when the connect/disconnect button is pressed
134
function connectButton() {
135
if (!tunnel) {
@@ -133,17 +140,15 @@
140
if (termfit) { term.loadAddon(termfit); }
141
term.open(Q('terminal'));
142
term.onData(function (data) { if (tunnel != null) { tunnel.sendText(data); } })
136
- term.onResize(function (size) {
137
- //console.log('Resize', size);
138
- //term.resize(size.cols, size.rows);
139
- //if (tunnel != null) { tunnel.send(String.fromCharCode(27) + '[8;' + size.cols + ';' + size.rows + 't') }
140
- });
143
+ term.onResize(function (size) { if (resizeTimer == null) { resizeTimer = setTimeout(sendResize, 200); } });
144
if (termfit) { termfit.fit(); }
145
146
// Setup a terminal tunnel to the agent
147
tunnel = CreateAgentRedirect(meshserver, CreateRemoteTunnel(tunnelUpdate), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
148
+ tunnel.options = { cols: term.cols, rows: term.rows };
149
tunnel.Start(args.nodeid);
150
tunnel.onStateChanged = onTunnelStateChange;
151
+ tunnel.onConsoleMessageChange = function (server, msg) { setConsoleMsg(msg); };
152
} else {
153
tunnel.Stop();
154
}
@@ -162,9 +167,7 @@
167
case 0:
168
// Disconnected, clear the terminal
169
term.dispose();
165
- term = null;
166
- termfit = null;
167
- tunnel = null;
170
+ term = termfit = tunnel = null;
171
break;
172
case 3:
173
// Connected
@@ -177,6 +180,11 @@
180
updateButtons();
181
}
182
183
+ // Console messages
184
+ var termConsoleMsgTimer = null;
185
+ function clearConsoleMsg() { QV('TermConsoleMsg', false); if (termConsoleMsgTimer) { clearTimeout(termConsoleMsgTimer); termConsoleMsgTimer = null; } }
186
+ function setConsoleMsg(msg) { QH('TermConsoleMsg', EscapeHtml(msg).split('\n').join('<br />')); QV('TermConsoleMsg', true); termConsoleMsgTimer = setTimeout(clearConsoleMsg, 8000); }
187
+
188
//
189
// POPUP DIALOG
190
//