rlgwebd.js: get rid of numerical game identifiers.

Games will be indentified by gamename/username pairs.  This will allow
better interoperability with dgamelaunch.

Polling clients are no longer supported; the code remnants need to be
removed.  The reaper() function will likely crash.  Unexpectedly, the
WebSocket client still works well enough to play.  Watching and listing
current games are probably broken.
This commit is contained in:
John "Elwin" Edwards 2015-01-01 15:56:22 -05:00
parent 0a0f754135
commit 920c6e8829

View file

@ -66,7 +66,6 @@ var sessions = {};
var clients = {}; var clients = {};
var dglgames = {}; var dglgames = {};
var allowlogin = true; var allowlogin = true;
var nextsession = 0;
var gamemux = new events.EventEmitter(); var gamemux = new events.EventEmitter();
/* Constructor. A TermSession handles a pty and the game running on it. /* Constructor. A TermSession handles a pty and the game running on it.
@ -103,8 +102,7 @@ function TermSession(game, lkey, dims, handlers) {
return; return;
} }
/* Grab a spot in the sessions table. */ /* Grab a spot in the sessions table. */
this.sessid = nextsession++; sessions[this.game.uname + "/" + this.pname] = this;
sessions[this.sessid] = this;
/* Set up the sizes. */ /* Set up the sizes. */
this.w = Math.floor(Number(dims[1])); this.w = Math.floor(Number(dims[1]));
if (!(this.w > 0 && this.w < 256)) if (!(this.w > 0 && this.w < 256))
@ -121,10 +119,9 @@ function TermSession(game, lkey, dims, handlers) {
var spawnopts = {"env": childenv, "cwd": "/", "rows": this.h, "cols": this.w, var spawnopts = {"env": childenv, "cwd": "/", "rows": this.h, "cols": this.w,
"name": "xterm-256color"}; "name": "xterm-256color"};
this.term = pty.spawn(this.game.path, args, spawnopts); this.term = pty.spawn(this.game.path, args, spawnopts);
tslog("%s playing %s (index %d, pid %d)", this.pname, this.game.uname, tslog("%s playing %s (pid %d)", this.pname, this.game.uname, this.term.pid);
this.sessid, this.term.pid); this.emit('open', true, this.game.uname, this.pname);
this.emit('open', true, this.sessid); gamemux.emit('begin', this.game.uname, this.pname);
gamemux.emit('begin', this.sessid, this.pname, this.game.uname);
/* Set up the lockfile and ttyrec */ /* Set up the lockfile and ttyrec */
var ts = timestamp(); var ts = timestamp();
var progressdir = path.join("/dgldir/inprogress", this.game.uname); var progressdir = path.join("/dgldir/inprogress", this.game.uname);
@ -138,7 +135,7 @@ function TermSession(game, lkey, dims, handlers) {
* with a complete screen. */ * with a complete screen. */
this.framebuf = new Buffer(1024); this.framebuf = new Buffer(1024);
this.frameoff = 0; this.frameoff = 0;
logins[lkey].sessions.push(this.sessid); logins[lkey].sessions.push(this.game.uname + "/" + this.pname);
/* END setup */ /* END setup */
function ttyrec_chunk(datastr) { function ttyrec_chunk(datastr) {
var ts = new Date(); var ts = new Date();
@ -166,7 +163,7 @@ function TermSession(game, lkey, dims, handlers) {
this.framebuf.copy(nbuf, 0, 0, this.frameoff); this.framebuf.copy(nbuf, 0, 0, this.frameoff);
this.framebuf = nbuf; this.framebuf = nbuf;
if (this.framebuf.length > 65536) { if (this.framebuf.length > 65536) {
tslog("Warning: Game %d frame buffer at %d bytes", this.sessid, tslog("Warning: Game %d frame buffer at %d bytes", this.tag(),
this.framebuf.length); this.framebuf.length);
} }
} }
@ -176,18 +173,21 @@ function TermSession(game, lkey, dims, handlers) {
this.write = function(data) { this.write = function(data) {
this.term.write(data); this.term.write(data);
}; };
this.tag = function() {
return this.game.uname + "/" + this.pname;
};
// Teardown. // Teardown.
this.term.on("exit", function () { this.term.on("exit", function () {
var id = ss.sessid; var tag = ss.tag();
fs.unlink(ss.lock); fs.unlink(ss.lock);
ss.record.end(); ss.record.end();
ss.emit('exit'); ss.emit('exit');
gamemux.emit('end', id, ss.pname, ss.game.uname); gamemux.emit('end', ss.game.uname, ss.pname);
delete sessions[id]; delete sessions[tag];
tslog("Game %s ended.", id); tslog("Game %s ended.", tag);
}); });
this.close = function () { this.close = function () {
if (this.sessid in sessions) if (this.tag() in sessions)
this.term.kill('SIGHUP'); this.term.kill('SIGHUP');
}; };
} }
@ -341,12 +341,12 @@ function Player(gamename, lkey, dims, callback) {
if (this.alive) if (this.alive)
this.session.close(); this.session.close();
}; };
function openH(success, id) { function openH(success, tag) {
if (success) { if (success) {
ss.alive = true; ss.alive = true;
ss.session = sessions[id]; ss.session = sessions[tag];
ss.h = sessions[id].h; ss.h = sessions[tag].h;
ss.w = sessions[id].w; ss.w = sessions[tag].w;
} }
callback(ss, success); callback(ss, success);
} }
@ -380,8 +380,8 @@ function wsWatcher(conn, session) {
conn.on('close', function(code, desc) { conn.on('close', function(code, desc) {
session.removeListener('data', dataH); session.removeListener('data', dataH);
session.removeListener('exit', exitH); session.removeListener('exit', exitH);
if (session.sessid in sessions) if (session.tag() in sessions)
tslog("A WebSocket watcher has left game %d", session.sessid); tslog("A WebSocket watcher has left game %d", session.tag());
}); });
conn.sendUTF(JSON.stringify({ conn.sendUTF(JSON.stringify({
"t": "w", "w": session.w, "h": session.h, "t": "w", "w": session.w, "h": session.h,
@ -413,10 +413,11 @@ function wsPlay(wsReq, game, lkey, dims) {
session.close(); session.close();
} }
/* These listen on the TermSession. */ /* These listen on the TermSession. */
function openH(success, id) { function openH(success, gname, pname) {
if (success) { if (success) {
var reply = {"t": "s", "id": id, "w": sessions[id].w, "h": var tag = gname + "/" + pname;
sessions[id].h, "p": sessions[id].pname, "g": game}; var reply = {"t": "s", "tag": tag, "w": sessions[tag].w, "h":
sessions[tag].h, "p": pname, "g": gname};
conn = wsReq.accept(null, wsReq.origin); conn = wsReq.accept(null, wsReq.origin);
conn.sendUTF(JSON.stringify(reply)); conn.sendUTF(JSON.stringify(reply));
conn.on('message', messageH); conn.on('message', messageH);
@ -629,7 +630,9 @@ function getMsgWS(msgObj) {
return getMsg(msgObj.utf8Data); return getMsg(msgObj.utf8Data);
} }
/* FIXME sessid removal */
function reaper() { function reaper() {
return; // TODO figure out if this function is useful
var now = new Date(); var now = new Date();
function reapcheck(session) { function reapcheck(session) {
fs.fstat(session.record.fd, function (err, stats) { fs.fstat(session.record.fd, function (err, stats) {
@ -787,23 +790,27 @@ function startgame(req, res, formdata) {
} }
function watch(req, res, formdata) { function watch(req, res, formdata) {
if (!("n" in formdata)) { if (!("g" in formdata) | !("p" in formdata)) {
sendError(res, 2, "Game number not given"); sendError(res, 2, "Game or player not given");
return; return;
} }
var gamenumber = Number(formdata["n"]); if (!(formdata.g in games)) {
if (!(gamenumber in sessions)) { sendError(res, 2, "No such game: " + formdata.g);
return;
}
var tag = formdata.g = "/" + formdata.p;
if (!(tag in sessions)) {
sendError(res, 7); sendError(res, 7);
return; return;
} }
var session = sessions[gamenumber]; var session = sessions[tag];
var watch = new Watcher(session); var watch = new Watcher(session);
var reply = {"t": "w", "id": watch.id, "w": session.w, "h": session.h, var reply = {"t": "w", "w": session.w, "h": session.h,
"p": session.pname, "g": session.game.uname}; "p": session.pname, "g": session.game.uname};
res.writeHead(200, {'Content-Type': 'application/json'}); res.writeHead(200, {'Content-Type': 'application/json'});
res.write(JSON.stringify(reply)); res.write(JSON.stringify(reply));
res.end(); res.end();
tslog("Game %d is being watched (key %s)", gamenumber, watch.id); tslog("Game %d is being watched", tag);
} }
/* Sets things up for a new user, like dgamelaunch's commands[register] */ /* Sets things up for a new user, like dgamelaunch's commands[register] */
@ -886,6 +893,7 @@ function endgame(client, res) {
} }
/* Stops a running game if the request has the proper key. */ /* Stops a running game if the request has the proper key. */
/* TODO does this still work? */
function stopgame(res, formdata) { function stopgame(res, formdata) {
if (!("key" in formdata) || !(formdata["key"] in logins)) { if (!("key" in formdata) || !(formdata["key"] in logins)) {
sendError(res, 1); sendError(res, 1);
@ -932,6 +940,7 @@ function stopgame(res, formdata) {
checkprogress(pname, games[gname], checkback, []); checkprogress(pname, games[gname], checkback, []);
} }
/* TODO remove polling */
function findClient(formdata, playersOnly) { function findClient(formdata, playersOnly) {
if (typeof(formdata) != "object") if (typeof(formdata) != "object")
return null; return null;
@ -1023,6 +1032,7 @@ function serveStatic(req, res, fname) {
return; return;
} }
/* TODO remove polling */
function readFeed(client, res) { function readFeed(client, res) {
if (!client) { if (!client) {
sendError(res, 7, null, true); sendError(res, 7, null, true);
@ -1038,12 +1048,13 @@ function readFeed(client, res) {
res.end(); res.end();
} }
/* TODO simplify by storing timestamps instead of callin stat() */
function getStatus(callback) { function getStatus(callback) {
var now = new Date(); var now = new Date();
var statusinfo = {"s": allowlogin, "g": []}; var statusinfo = {"s": allowlogin, "g": []};
function idleset(i, idletime) { function idleset(n, idletime) {
if (i >= 0 && i < statusinfo.g.length) { if (n >= 0 && n < statusinfo.g.length) {
statusinfo.g[i].i = idletime; statusinfo.g[n].i = idletime;
} }
for (var j = 0; j < statusinfo.g.length; j++) { for (var j = 0; j < statusinfo.g.length; j++) {
if (!("i" in statusinfo.g[j])) if (!("i" in statusinfo.g[j]))
@ -1051,11 +1062,11 @@ function getStatus(callback) {
} }
callback(statusinfo); callback(statusinfo);
} }
for (var sessid in sessions) { for (var tag in sessions) {
var gamedesc = {}; var gamedesc = {};
gamedesc["n"] = sessid; gamedesc["tag"] = tag;
gamedesc["p"] = sessions[sessid].pname; gamedesc["p"] = sessions[tag].pname;
gamedesc["g"] = sessions[sessid].game.uname; gamedesc["g"] = sessions[tag].game.uname;
statusinfo["g"].push(gamedesc); statusinfo["g"].push(gamedesc);
} }
statusinfo["dgl"] = []; statusinfo["dgl"] = [];
@ -1076,9 +1087,10 @@ function getStatus(callback) {
} }
for (var i = 0; i < statusinfo.g.length; i++) { for (var i = 0; i < statusinfo.g.length; i++) {
/* fd sometimes isn't a number, presumably when the file isn't open yet. */ /* fd sometimes isn't a number, presumably when the file isn't open yet. */
var ssid = statusinfo.g[i].n; /* FIXME sessid -> tag */
if (ssid in sessions && typeof(sessions[ssid].record.fd) == 'number') { var tag = statusinfo.g[i].tag;
fs.fstat(sessions[ssid].record.fd, makecallback(i)); if (tag in sessions && typeof(sessions[tag].record.fd) == 'number') {
fs.fstat(sessions[tag].record.fd, makecallback(i));
} }
else { else {
idleset(i, null); idleset(i, null);
@ -1344,17 +1356,17 @@ function webHandler(req, res) {
} }
function wsHandler(wsRequest) { function wsHandler(wsRequest) {
var watchmatch = wsRequest.resource.match(/^\/watch\/([0-9]*)$/); var watchmatch = wsRequest.resource.match(/^\/watch\/(.*)$/);
var playmatch = wsRequest.resource.match(/^\/play\//); var playmatch = wsRequest.resource.match(/^\/play\//);
if (watchmatch !== null) { if (watchmatch !== null) {
if (watchmatch[1] && Number(watchmatch[1]) in sessions) { if (!(watchmatch[1] in sessions)) {
var tsession = sessions[Number(watchmatch[1])];
var conn = wsRequest.accept(null, wsRequest.origin);
new wsWatcher(conn, tsession);
tslog("Game %d is being watched via WebSockets", tsession.sessid);
}
else
wsRequest.reject(404, errorcodes[7]); wsRequest.reject(404, errorcodes[7]);
return;
}
var tsession = sessions[watchmatch[1]];
var conn = wsRequest.accept(null, wsRequest.origin);
new wsWatcher(conn, tsession);
tslog("Game %d is being watched via WebSockets", tsession.tag());
} }
else if (playmatch !== null) { else if (playmatch !== null) {
wsStart(wsRequest); wsStart(wsRequest);
@ -1367,14 +1379,14 @@ function wsHandler(wsRequest) {
conn.sendUTF(JSON.stringify(info)); conn.sendUTF(JSON.stringify(info));
}); });
} }
var beginH = function (n, name, game) { var beginH = function (gname, pname) {
conn.sendUTF(JSON.stringify({"t": "b", "n": n, "p": name, "g": game})); conn.sendUTF(JSON.stringify({"t": "b", "p": pname, "g": gname}));
}; };
var listH = function (list) { var listH = function (list) {
conn.sendUTF(JSON.stringify(list)); conn.sendUTF(JSON.stringify(list));
}; };
var endH = function (n, pname, gname) { var endH = function (gname, pname) {
conn.sendUTF(JSON.stringify({"t": "e", "n": n, "p": pname, "g": gname})); conn.sendUTF(JSON.stringify({"t": "e", "p": pname, "g": gname}));
}; };
gamemux.on('begin', beginH); gamemux.on('begin', beginH);
gamemux.on('list', listH); gamemux.on('list', listH);
@ -1406,13 +1418,13 @@ function shutdown () {
process.exit(); process.exit();
} }
function conHandler(chunk) { function consoleHandler(chunk) {
var msg = chunk.toString().split('\n')[0]; var msg = chunk.toString().split('\n')[0];
if (msg == "quit") { if (msg == "quit") {
allowlogin = false; allowlogin = false;
tslog("Disconnecting..."); tslog("Disconnecting...");
for (var sessid in sessions) { for (var tag in sessions) {
sessions[sessid].close(); sessions[tag].close();
} }
progressWatcher.stdin.end("\n"); progressWatcher.stdin.end("\n");
setTimeout(shutdown, 2000); setTimeout(shutdown, 2000);
@ -1420,8 +1432,8 @@ function conHandler(chunk) {
} }
process.on("exit", function () { process.on("exit", function () {
for (var sessid in sessions) { for (var tag in sessions) {
sessions[sessid].term.kill('SIGHUP'); sessions[tag].term.kill('SIGHUP');
} }
tslog("Quitting..."); tslog("Quitting...");
return; return;
@ -1455,7 +1467,7 @@ if (fs.existsSync(ctlsocket)) {
/* Open the control socket before chrooting where it can't be found */ /* Open the control socket before chrooting where it can't be found */
var ctlServer = net.createServer(function (sock) { var ctlServer = net.createServer(function (sock) {
sock.on('data', conHandler); sock.on('data', consoleHandler);
}); });
ctlServer.listen(ctlsocket, function () { ctlServer.listen(ctlsocket, function () {
/* rlgwebd.js now assumes that it has been started by the rlgwebd shell /* rlgwebd.js now assumes that it has been started by the rlgwebd shell