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, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;'); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
38 -function EscapeHtmlBreaks(x) { if (typeof x == "string") return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, '&nbsp;&nbsp;'); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
37 +function EscapeHtml(x) { if (typeof x == 'string') return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;'); if (typeof x == 'boolean') return x; if (typeof x == 'number') return x; }
38 +function EscapeHtmlBreaks(x) { if (typeof x == 'string') return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, '&nbsp;&nbsp;'); 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>&#10007;</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&reg; 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 //