WebTTY: use WebSockets when possible.
This commit is contained in:
parent
fabaea6849
commit
d7df88f3cf
2 changed files with 183 additions and 18 deletions
64
shterm.js
64
shterm.js
|
|
@ -6,6 +6,7 @@ var isalive = false; // Whether the session is currently active.
|
|||
var nsend = 0; // The number of the next packet to send.
|
||||
var nrecv = 0; // The next packet expected.
|
||||
var msgQ = []; // Queue for out-of-order messages.
|
||||
var conn = null; // WebSocket
|
||||
|
||||
// A state machine that keeps track of polling the server.
|
||||
var ajaxstate = {
|
||||
|
|
@ -202,15 +203,23 @@ function postResponseHandler() {
|
|||
|
||||
function sendback(str) {
|
||||
/* For responding to terminal queries. */
|
||||
if (conn) {
|
||||
var msgObj = {"t": "d", "d": str};
|
||||
conn.send(JSON.stringify(msgObj));
|
||||
}
|
||||
else {
|
||||
var formdata = {"t": "d", "n": nsend++, "d": str};
|
||||
var datareq = new XMLHttpRequest();
|
||||
datareq.onreadystatechange = postResponseHandler;
|
||||
datareq.open('POST', '/feed', true);
|
||||
datareq.send(JSON.stringify(formdata));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function sendkey(ev) {
|
||||
if (!isalive)
|
||||
return;
|
||||
var keynum = ev.keyCode;
|
||||
var code;
|
||||
if (keynum >= 65 && keynum <= 90) {
|
||||
|
|
@ -245,13 +254,18 @@ function sendkey(ev) {
|
|||
debug(1, "Ignoring keycode " + keynum);
|
||||
return;
|
||||
}
|
||||
if (isalive)
|
||||
ev.preventDefault();
|
||||
if (conn) {
|
||||
var msgObj = {"t": "d", "d": code};
|
||||
conn.send(JSON.stringify(msgObj));
|
||||
}
|
||||
else {
|
||||
var formdata = {"t": "d", "n": nsend++, "d": code};
|
||||
var datareq = new XMLHttpRequest();
|
||||
datareq.onreadystatechange = postResponseHandler;
|
||||
datareq.open('POST', '/feed', true);
|
||||
datareq.send(JSON.stringify(formdata));
|
||||
}
|
||||
//dkey(code);
|
||||
return;
|
||||
}
|
||||
|
|
@ -265,6 +279,8 @@ var kpkeys = { "KP1": "1b4f46", "KP2": "1b4f42", "KP3": "1b5b367e",
|
|||
"KP7": "1b4f48", "KP8": "1b4f41", "KP9": "1b5b357e" };
|
||||
|
||||
function vkey(c) {
|
||||
if (!isalive)
|
||||
return;
|
||||
var keystr;
|
||||
if (c.match(/^[a-z]$/)) {
|
||||
if (termemu.ctrlp()) {
|
||||
|
|
@ -304,11 +320,17 @@ function vkey(c) {
|
|||
else
|
||||
return;
|
||||
//writeData("Sending " + keystr);
|
||||
if (conn) {
|
||||
var msgObj = {"t": "d", "d": keystr};
|
||||
conn.send(JSON.stringify(msgObj));
|
||||
}
|
||||
else {
|
||||
var formdata = {"t": "d", "n": nsend++, "d": keystr};
|
||||
var datareq = new XMLHttpRequest();
|
||||
datareq.onreadystatechange = postResponseHandler;
|
||||
datareq.open('POST', '/feed', true);
|
||||
datareq.send(JSON.stringify(formdata));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -339,9 +361,45 @@ function togglectrl() {
|
|||
return;
|
||||
}
|
||||
|
||||
function loginWS(h, w) {
|
||||
if (conn)
|
||||
return;
|
||||
var sockurl = "ws://" + window.location.host + "/sock?w=" + w + "&h=" + h;
|
||||
conn = new WebSocket(sockurl);
|
||||
conn.onopen = function (event) {
|
||||
isalive = true;
|
||||
setTitle("Logged in");
|
||||
debug(1, "Logged in via WebSocket");
|
||||
}
|
||||
conn.onmessage = function (event) {
|
||||
var msgObj = JSON.parse(event.data);
|
||||
if (msgObj.t == 'l') {
|
||||
termemu.resize(msgObj.h, msgObj.w);
|
||||
}
|
||||
else if (msgObj.t == 'd') {
|
||||
debug(0, msgObj.d);
|
||||
writeData(msgObj.d);
|
||||
}
|
||||
else if (msgObj.t == 'q') {
|
||||
debug(0, "Quit message!");
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
conn.onclose = function (event) {
|
||||
conn = null;
|
||||
isalive = false;
|
||||
debug(1, "WebSocket connection closed.");
|
||||
setTitle("Not connected.");
|
||||
}
|
||||
}
|
||||
|
||||
function login(h, w) {
|
||||
if (isalive)
|
||||
return;
|
||||
if (window.WebSocket) {
|
||||
loginWS(h, w);
|
||||
return;
|
||||
}
|
||||
params = {"login": true, "h": h, "w": w};
|
||||
var req = new XMLHttpRequest();
|
||||
req.onreadystatechange = function () {
|
||||
|
|
@ -368,6 +426,10 @@ function login(h, w) {
|
|||
}
|
||||
|
||||
function stop() {
|
||||
if (conn) {
|
||||
conn.close();
|
||||
return;
|
||||
}
|
||||
var req = new XMLHttpRequest();
|
||||
req.onreadystatechange = function () {
|
||||
if (req.readyState == 4 && req.status == 200) {
|
||||
|
|
|
|||
105
webtty.js
105
webtty.js
|
|
@ -6,15 +6,83 @@ var url = require('url');
|
|||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var pty = require(path.join(localModules, "pty.js"));
|
||||
var child_process = require("child_process");
|
||||
var webSocketServer = require(path.join(localModules, "websocket")).server;
|
||||
|
||||
var serveStaticRoot = fs.realpathSync(".");
|
||||
var sessions = {};
|
||||
var sessionsWS = {};
|
||||
|
||||
var env_dontuse = {"TMUX": true, "TMUX_PANE": true};
|
||||
|
||||
/* Constructor for TermSessions. Note that it opens the terminal and
|
||||
* adds itself to the sessions dict.
|
||||
*/
|
||||
function TermSessionWS(conn, h, w) {
|
||||
var ss = this;
|
||||
/* Set up the sizes. */
|
||||
w = Math.floor(Number(w));
|
||||
if (!(w > 0 && w < 256))
|
||||
w = 80;
|
||||
this.w = w;
|
||||
h = Math.floor(Number(h));
|
||||
if (!(h > 0 && h < 256))
|
||||
h = 25;
|
||||
this.h = h;
|
||||
this.conn = conn;
|
||||
/* Customize the environment. */
|
||||
var childenv = {};
|
||||
for (var key in process.env) {
|
||||
if (!(key in env_dontuse))
|
||||
childenv[key] = process.env[key];
|
||||
}
|
||||
var spawnopts = {"env": childenv, "cwd": process.env["HOME"],
|
||||
"rows": this.h, "cols": this.w};
|
||||
this.term = pty.spawn("bash", [], spawnopts);
|
||||
this.alive = true;
|
||||
this.term.on("data", function (datastr) {
|
||||
var buf = new Buffer(datastr);
|
||||
if (ss.conn.connected)
|
||||
ss.conn.sendUTF(JSON.stringify({"t": "d", "d": buf.toString("hex")}));
|
||||
});
|
||||
this.term.on("exit", function () {
|
||||
ss.alive = false;
|
||||
/* Wait for all the data to get collected */
|
||||
setTimeout(ss.cleanup, 1000);
|
||||
});
|
||||
this.conn.on("message", function (msg) {
|
||||
try {
|
||||
var msgObj = JSON.parse(msg.utf8Data);
|
||||
}
|
||||
catch (e) {
|
||||
return;
|
||||
}
|
||||
if (msgObj.t == "d") {
|
||||
var hexstr = msgObj["d"].replace(/[^0-9a-f]/gi, "");
|
||||
if (hexstr.length % 2 != 0) {
|
||||
return;
|
||||
}
|
||||
var keybuf = new Buffer(hexstr, "hex");
|
||||
ss.term.write(keybuf);
|
||||
}
|
||||
});
|
||||
this.conn.on("close", function (msg) {
|
||||
if (ss.alive)
|
||||
ss.term.kill('SIGHUP');
|
||||
console.log("WebSocket connection closed.");
|
||||
});
|
||||
this.cleanup = function () {
|
||||
/* Call this when the child is dead. */
|
||||
if (ss.alive)
|
||||
return;
|
||||
if (ss.conn.connected) {
|
||||
ss.conn.sendUTF(JSON.stringify({"t": "q"}));
|
||||
}
|
||||
};
|
||||
this.conn.sendUTF(JSON.stringify({"t": "l", "w": w, "h": h}));
|
||||
console.log("New WebSocket connection.");
|
||||
}
|
||||
|
||||
function TermSession(sessid, h, w) {
|
||||
/* Set up the sizes. */
|
||||
w = Math.floor(Number(w));
|
||||
|
|
@ -355,6 +423,41 @@ process.on("exit", function () {
|
|||
return;
|
||||
});
|
||||
|
||||
function wsRespond(req) {
|
||||
var w, h, conn;
|
||||
if (req.resourceURL.pathname == "/sock") {
|
||||
w = parseInt(req.resourceURL.query.w);
|
||||
if (isNaN(w) || w <= 0 || w > 256)
|
||||
w = 80;
|
||||
h = parseInt(req.resourceURL.query.h);
|
||||
if (isNaN(h) || h <= 0 || h > 256)
|
||||
h = 25;
|
||||
conn = req.accept(null, req.origin);
|
||||
new TermSessionWS(conn, h, w);
|
||||
}
|
||||
else {
|
||||
req.reject(404, "No such resource.");
|
||||
}
|
||||
}
|
||||
|
||||
/* The pty.js module doesn't wait for the processes it spawns, so they
|
||||
* become zombies, which leads to unpleasantness when the system runs
|
||||
* out of process table entries. But if the child_process module is
|
||||
* initialized and a child spawned, node will continue waiting for any
|
||||
* children.
|
||||
* Someday, some developer will get the bright idea of tracking how many
|
||||
* processes the child_process module has spawned, and not waiting if
|
||||
* it's zero. Until then, the following useless line will protect us
|
||||
* from the zombie hordes.
|
||||
* Figuring this out was almost as interesting as the Rogue bug where
|
||||
* printf debugging altered whether the high score list was checked.
|
||||
*/
|
||||
child_process.spawn("/bin/true");
|
||||
|
||||
process.env["TERM"] = "xterm-256color";
|
||||
http.createServer(handler).listen(8080, "127.0.0.1");
|
||||
var webServer = http.createServer(handler);
|
||||
webServer.listen(8080, "127.0.0.1");
|
||||
console.log('Server running at http://127.0.0.1:8080/');
|
||||
var wsServer = new webSocketServer({"httpServer": webServer});
|
||||
wsServer.on("request", wsRespond);
|
||||
console.log('WebSockets online');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue