-
-
Save GitSquared/2049d7e85eaddeeeaa44e8404fe0b0e1 to your computer and use it in GitHub Desktop.
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <title>Remote terminal</title> | |
| <link rel="stylesheet" href="xterm.css" /> | |
| <script src="terminal.class.js"></script> | |
| <style> | |
| div#terminal { | |
| width: 500px; | |
| height: 300px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="terminal-container"></div> | |
| <script> | |
| window.terminalRemote = new Terminal({ | |
| role: "client", | |
| parentId: "terminal-container", | |
| host: "127.0.0.1", | |
| port: 3000 | |
| }); | |
| // Wait for terminal to be initialized then resize it to fit in the container | |
| setTimeout(() => { | |
| window.term.fit(); | |
| }, 500); | |
| </script> | |
| </body> | |
| </html> |
| // Server demo (running node.js) | |
| const Terminal = require("./terminal.class.js").Terminal; | |
| let terminalServer = new Terminal({ | |
| role: "server", | |
| shell: (process.platform === "win32") ? "cmd.exe" : "bash", | |
| port: 3000 | |
| }); | |
| terminalServer.onclosed = (code, signal) => { | |
| console.log("Terminal closed - "+code+", "+signal); | |
| app.quit(); | |
| }; | |
| terminalServer.onopened = () => { | |
| console.log("Connected to remote"); | |
| }; | |
| terminalServer.onresized = (cols, rows) => { | |
| console.log("Resized terminal to "+cols+"x"+rows); | |
| }; | |
| terminalServer.ondisconnected = () => { | |
| console.log("Remote disconnected"); | |
| }; |
| /* **************************************************************** | |
| Create a terminal receiver or server. | |
| Currently the server only accepts one connection at a time and | |
| does not support SSL. If you wish to use to connect to a | |
| terminal over the internet, PLEASE do not use this class as-is. | |
| --SERVER------------------------------------------------------- | |
| Server requirements: | |
| - node-pty | |
| - ws | |
| Server usage: | |
| terminalServer = new Terminal({ | |
| role: "server", | |
| port: 3000, // 3000 if empty | |
| shell: "bash" // Command to run, "bash" by default | |
| }) | |
| Server events: | |
| terminalServer.onopened // Connected to a receiver | |
| .ondisconnected // Disconnected from receiver | |
| .onclosed // Terminal was exited | |
| // Args: code, signal | |
| .onresized // Terminal was resized | |
| // Args: cols, rows | |
| Server methods: | |
| None. | |
| --CLIENT-------------------------------------------------------- | |
| Client requirements: | |
| - xterm.js | |
| - browser with WebSocket support | |
| Client usage: | |
| terminalClient = new Terminal({ | |
| role: "client", | |
| parentId: "someid", // ID of the terminal container element | |
| port: 3000, // 3000 if empty | |
| host: "127.0.0.1" // Localhost by default | |
| }) | |
| Client events: | |
| None. | |
| Client methods: | |
| terminalClient.fit() // Resizes the terminal to match the container's size | |
| .resize(cols, rows) // Manual resize | |
| ******************************************************************* */ | |
| class Terminal { | |
| constructor(opts) { | |
| if (opts.role === "client") { | |
| if (!opts.parentId) throw "Missing options"; | |
| this.xTerm = require("xterm"); | |
| this.xTerm.loadAddon('attach'); | |
| this.xTerm.loadAddon('fit'); | |
| this.sendSizeToServer = () => { | |
| let cols = this.term.cols.toString(); | |
| let rows = this.term.rows.toString(); | |
| while (cols.length < 3) { | |
| cols = "0"+cols; | |
| } | |
| while (rows.length < 3) { | |
| rows = "0"+rows; | |
| } | |
| this.socket.send("ESCAPED|-- RESIZE:"+cols+";"+rows); | |
| }; | |
| this.term = new this.xTerm({ | |
| cols: 80, | |
| rows: 24 | |
| }); | |
| this.term.open(document.getElementById(opts.parentId), true); | |
| let sockHost = opts.host || "127.0.0.1"; | |
| let sockPort = opts.port || 3000; | |
| this.socket = new WebSocket("ws://"+sockHost+":"+sockPort); | |
| this.socket.onopen = () => { | |
| this.term.attach(this.socket); | |
| }; | |
| this.socket.onerror = (e) => {throw e}; | |
| this.fit = () => { | |
| this.term.fit(); | |
| setTimeout(() => { | |
| this.sendSizeToServer(); | |
| }, 50); | |
| } | |
| this.resize = (cols, rows) => { | |
| this.term.resize(cols, rows); | |
| this.sendSizeToServer(); | |
| } | |
| } else if (opts.role === "server") { | |
| this.Pty = require("node-pty"); | |
| this.Websocket = require("ws").Server; | |
| this.onclosed = () => {}; | |
| this.onopened = () => {}; | |
| this.onresize = () => {}; | |
| this.ondisconnected = () => {}; | |
| this.tty = this.Pty.spawn(opts.shell || "bash", [], { | |
| name: 'xterm-color', | |
| cols: 80, | |
| rows: 24, | |
| cwd: process.env.PWD, | |
| env: process.env | |
| }); | |
| this.tty.on('exit', (code, signal) => { | |
| this.onclosed(code, signal); | |
| }); | |
| this.wss = new this.Websocket({ | |
| port: opts.port || 3000, | |
| clientTracking: true, | |
| verifyClient: (info) => { | |
| if (this.wss.clients.length >= 1) { | |
| return false; | |
| } else { | |
| return true; | |
| } | |
| } | |
| }); | |
| this.wss.on('connection', (ws) => { | |
| this.onopened(); | |
| ws.on('message', (msg) => { | |
| if (msg.startsWith("ESCAPED|-- ")) { | |
| if (msg.startsWith("ESCAPED|-- RESIZE:")) { | |
| msg = msg.substr(18); | |
| let cols = msg.slice(0, -4); | |
| let rows = msg.substr(4); | |
| this.tty.resize(Number(cols), Number(rows)); | |
| this.onresized(cols, rows); | |
| } | |
| } else { | |
| this.tty.write(msg); | |
| } | |
| }); | |
| this.tty.on('data', (data) => { | |
| try { | |
| ws.send(data); | |
| } catch (e) { | |
| // Websocket closed | |
| } | |
| }); | |
| }); | |
| this.wss.on('close', () => { | |
| this.ondisconnected(); | |
| }); | |
| } else { | |
| throw "Unknow purpose"; | |
| } | |
| } | |
| } | |
| module.exports = { | |
| Terminal | |
| }; |
ops, a typo. anyway, this code help me a lot, thank u again!
Hey! your code works like a charm. but, when I open the page, the bash-3.2$ message doesn't work until I press enter in the terminal.
@ilayalmalem you can work around that by programmatically sending a \n to the PTY after the first time the receiver connects over websocket. It'll cause the shell to print out the prompt again
@GitSquared Thank you! i managed to do the following:
ws.on('connection', () => {
pty.send('clear\r');
})
This will actually hit Enter inside it.
You can also do something like:
pty.send('echo 10\r')
To execute "echo 10" in the pty.
@ilayalmalem note that \r is Windows-specific.
@GitSquared strange, cause it seems to work just fine on my MacBook..
seems to be a lot of things missing in this code. How does anyone get this to work?
sorry @djmalc, i don't maintain gists. this code is outdated and will not work with latest versions of xterm.js/node-pty, i think.
Amazing code snipet, thanks a log!