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 nsend = 0; // The number of the next packet to send.
|
||||||
var nrecv = 0; // The next packet expected.
|
var nrecv = 0; // The next packet expected.
|
||||||
var msgQ = []; // Queue for out-of-order messages.
|
var msgQ = []; // Queue for out-of-order messages.
|
||||||
|
var conn = null; // WebSocket
|
||||||
|
|
||||||
// A state machine that keeps track of polling the server.
|
// A state machine that keeps track of polling the server.
|
||||||
var ajaxstate = {
|
var ajaxstate = {
|
||||||
|
|
@ -202,15 +203,23 @@ function postResponseHandler() {
|
||||||
|
|
||||||
function sendback(str) {
|
function sendback(str) {
|
||||||
/* For responding to terminal queries. */
|
/* 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 formdata = {"t": "d", "n": nsend++, "d": str};
|
||||||
var datareq = new XMLHttpRequest();
|
var datareq = new XMLHttpRequest();
|
||||||
datareq.onreadystatechange = postResponseHandler;
|
datareq.onreadystatechange = postResponseHandler;
|
||||||
datareq.open('POST', '/feed', true);
|
datareq.open('POST', '/feed', true);
|
||||||
datareq.send(JSON.stringify(formdata));
|
datareq.send(JSON.stringify(formdata));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendkey(ev) {
|
function sendkey(ev) {
|
||||||
|
if (!isalive)
|
||||||
|
return;
|
||||||
var keynum = ev.keyCode;
|
var keynum = ev.keyCode;
|
||||||
var code;
|
var code;
|
||||||
if (keynum >= 65 && keynum <= 90) {
|
if (keynum >= 65 && keynum <= 90) {
|
||||||
|
|
@ -245,13 +254,18 @@ function sendkey(ev) {
|
||||||
debug(1, "Ignoring keycode " + keynum);
|
debug(1, "Ignoring keycode " + keynum);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isalive)
|
|
||||||
ev.preventDefault();
|
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 formdata = {"t": "d", "n": nsend++, "d": code};
|
||||||
var datareq = new XMLHttpRequest();
|
var datareq = new XMLHttpRequest();
|
||||||
datareq.onreadystatechange = postResponseHandler;
|
datareq.onreadystatechange = postResponseHandler;
|
||||||
datareq.open('POST', '/feed', true);
|
datareq.open('POST', '/feed', true);
|
||||||
datareq.send(JSON.stringify(formdata));
|
datareq.send(JSON.stringify(formdata));
|
||||||
|
}
|
||||||
//dkey(code);
|
//dkey(code);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -265,6 +279,8 @@ var kpkeys = { "KP1": "1b4f46", "KP2": "1b4f42", "KP3": "1b5b367e",
|
||||||
"KP7": "1b4f48", "KP8": "1b4f41", "KP9": "1b5b357e" };
|
"KP7": "1b4f48", "KP8": "1b4f41", "KP9": "1b5b357e" };
|
||||||
|
|
||||||
function vkey(c) {
|
function vkey(c) {
|
||||||
|
if (!isalive)
|
||||||
|
return;
|
||||||
var keystr;
|
var keystr;
|
||||||
if (c.match(/^[a-z]$/)) {
|
if (c.match(/^[a-z]$/)) {
|
||||||
if (termemu.ctrlp()) {
|
if (termemu.ctrlp()) {
|
||||||
|
|
@ -304,11 +320,17 @@ function vkey(c) {
|
||||||
else
|
else
|
||||||
return;
|
return;
|
||||||
//writeData("Sending " + keystr);
|
//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 formdata = {"t": "d", "n": nsend++, "d": keystr};
|
||||||
var datareq = new XMLHttpRequest();
|
var datareq = new XMLHttpRequest();
|
||||||
datareq.onreadystatechange = postResponseHandler;
|
datareq.onreadystatechange = postResponseHandler;
|
||||||
datareq.open('POST', '/feed', true);
|
datareq.open('POST', '/feed', true);
|
||||||
datareq.send(JSON.stringify(formdata));
|
datareq.send(JSON.stringify(formdata));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -339,9 +361,45 @@ function togglectrl() {
|
||||||
return;
|
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) {
|
function login(h, w) {
|
||||||
if (isalive)
|
if (isalive)
|
||||||
return;
|
return;
|
||||||
|
if (window.WebSocket) {
|
||||||
|
loginWS(h, w);
|
||||||
|
return;
|
||||||
|
}
|
||||||
params = {"login": true, "h": h, "w": w};
|
params = {"login": true, "h": h, "w": w};
|
||||||
var req = new XMLHttpRequest();
|
var req = new XMLHttpRequest();
|
||||||
req.onreadystatechange = function () {
|
req.onreadystatechange = function () {
|
||||||
|
|
@ -368,6 +426,10 @@ function login(h, w) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop() {
|
function stop() {
|
||||||
|
if (conn) {
|
||||||
|
conn.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
var req = new XMLHttpRequest();
|
var req = new XMLHttpRequest();
|
||||||
req.onreadystatechange = function () {
|
req.onreadystatechange = function () {
|
||||||
if (req.readyState == 4 && req.status == 200) {
|
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 path = require('path');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var pty = require(path.join(localModules, "pty.js"));
|
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 serveStaticRoot = fs.realpathSync(".");
|
||||||
var sessions = {};
|
var sessions = {};
|
||||||
|
var sessionsWS = {};
|
||||||
|
|
||||||
var env_dontuse = {"TMUX": true, "TMUX_PANE": true};
|
var env_dontuse = {"TMUX": true, "TMUX_PANE": true};
|
||||||
|
|
||||||
/* Constructor for TermSessions. Note that it opens the terminal and
|
/* Constructor for TermSessions. Note that it opens the terminal and
|
||||||
* adds itself to the sessions dict.
|
* 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) {
|
function TermSession(sessid, h, w) {
|
||||||
/* Set up the sizes. */
|
/* Set up the sizes. */
|
||||||
w = Math.floor(Number(w));
|
w = Math.floor(Number(w));
|
||||||
|
|
@ -355,6 +423,41 @@ process.on("exit", function () {
|
||||||
return;
|
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";
|
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/');
|
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