Mercurial > hg > rlgwebd
diff rlgwebd.js @ 159:a613380ffdc2
RLGWebD: excise polling.
WebSockets are supported nearly everywhere now.
Listing current games and watching them are still broken.
author | John "Elwin" Edwards |
---|---|
date | Sat, 03 Jan 2015 15:23:04 -0500 |
parents | 9961a538c00e |
children | ed837da65e5f |
line wrap: on
line diff
--- a/rlgwebd.js Thu Jan 01 15:56:22 2015 -0500 +++ b/rlgwebd.js Sat Jan 03 15:23:04 2015 -0500 @@ -63,7 +63,6 @@ /* Global state */ var logins = {}; var sessions = {}; -var clients = {}; var dglgames = {}; var allowlogin = true; var gamemux = new events.EventEmitter(); @@ -193,178 +192,6 @@ } TermSession.prototype = new events.EventEmitter(); -function Watcher(session) { - var ss = this; // that - this.session = session; - this.alive = true; - /* State for messaging. */ - this.nsend = 0; - this.sendQ = []; - /* Get a place in the table. */ - this.id = randkey(2); - while (this.id in clients) { - this.id = randkey(2); - } - clients[this.id] = this; - /* Recreate the current screen state from the session's buffer. */ - this.sendQ.push({"t": "d", "n": this.nsend++, - "d": session.framebuf.toString("hex", 0, session.frameoff)}); - function dataH(buf) { - var reply = {}; - reply.t = "d"; - reply.n = ss.nsend++; - reply.d = buf.toString("hex"); - ss.sendQ.push(reply); - } - function exitH() { - ss.alive = false; - ss.sendQ.push({"t": "q"}); - } - session.on('data', dataH); - session.on('exit', exitH); - this.read = function() { - /* Returns an array of all outstanding messages, empty if none. */ - var temp = this.sendQ; - this.sendQ = []; - /* Clean up if finished. */ - if (!this.alive) { - delete clients[this.id]; - } - return temp; - }; - this.quit = function() { - this.session.removeListener('data', dataH); - this.session.removeListener('exit', exitH); - delete clients[this.id]; - }; -} - -function Player(gamename, lkey, dims, callback) { - var ss = this; - this.alive = false; - /* State for messaging. */ - this.nsend = 0; - this.nrecv = 0; - this.sendQ = []; - this.recvQ = [] - this.Qtimeout = null; - /* Get a place in the table. */ - this.id = randkey(2); - while (this.id in clients) { - this.id = randkey(2); - } - clients[this.id] = this; - - this.read = function() { - var temp = this.sendQ; - this.sendQ = []; - /* Clean up if finished. */ - if (!this.alive) { - clearTimeout(this.Qtimeout); - delete clients[this.id]; - } - return temp; - }; - this.write = function (data, n) { - if (!this.alive || typeof (n) != "number") { - return; - } - var oindex = n - this.nrecv; - if (oindex === 0) { - this.session.write(data); - this.nrecv++; - var next; - while ((next = this.recvQ.shift()) !== undefined) { - this.session.write(next); - this.nrecv++; - } - if (this.recvQ.length == 0 && this.Qtimeout) { - clearTimeout(this.Qtimeout); - this.Qtimeout = null; - } - } - else if (oindex > 0 && oindex <= 1024) { - tslog("Client %s: Stashing message %d at %d", this.id, n, oindex - 1); - this.recvQ[oindex - 1] = data; - if (!this.Qtimeout) { - var nextn = this.nrecv + this.recvQ.length + 1; - this.Qtimeout = setTimeout(this.flushQ, 30000, this, nextn); - } - } - /* Otherwise, discard it */ - return; - }; - this.flushQ = function (client, n) { - /* Callback for when an unreceived message times out. - * n is the first empty space that will not be given up on. */ - if (!client.alive || client.nrecv >= n) - return; - client.nrecv++; - var next; - /* Clear the queue up to n */ - while (client.nrecv < n) { - next = client.recvQ.shift(); - if (next !== undefined) - client.session.write(next); - client.nrecv++; - } - /* Clear out anything that's ready. */ - while ((next = client.recvQ.shift()) !== undefined) { - client.session.write(next); - client.nrecv++; - } - /* Now set another timeout if necessary. */ - if (client.recvQ.length != 0) { - var nextn = client.nrecv + client.recvQ.length + 1; - client.Qtimeout = setTimeout(client.flushQ, 30000, client, nextn); - } - tslog("Flushing queue for player %s", player.id); - }; - this.reset = function () { - /* To be called when the game is taken over. */ - if (this.Qtimeout) { - clearTimeout(this.Qtimeout); - this.Qtimeout = null; - } - for (var i = 0; i < this.recvQ.length; i++) { - if (this.recvQ[i] !== undefined) { - this.session.write(this.recvQ[i]); - } - } - this.recvQ = []; - this.nrecv = 0; - this.nsend = 0; - this.sendQ = [{"t": "d", "n": this.nsend++, - "d": this.session.framebuf.toString("hex", 0, this.session.frameoff)}]; - }; - this.quit = function() { - if (this.alive) - this.session.close(); - }; - function openH(success, tag) { - if (success) { - ss.alive = true; - ss.session = sessions[tag]; - ss.h = sessions[tag].h; - ss.w = sessions[tag].w; - } - callback(ss, success); - } - function dataH(chunk) { - var reply = {}; - reply.t = "d"; - reply.n = ss.nsend++; - reply.d = chunk.toString("hex"); - ss.sendQ.push(reply); - } - function exitH() { - ss.alive = false; - ss.sendQ.push({"t": "q"}); - } - var handlers = {'open': openH, 'data': dataH, 'exit': exitH}; - this.session = new TermSession(gamename, lkey, dims, handlers); -} - // Also known as WebSocketAndTermSessionClosureGlueFactory function wsWatcher(conn, session) { var ss = this; // is this even needed? @@ -716,103 +543,6 @@ return; } -function startgame(req, res, formdata) { - if (!allowlogin) { - sendError(res, 6, null); - return; - } - if (!("key" in formdata)) { - sendError(res, 2, "No key given."); - return; - } - else if (!("game" in formdata)) { - sendError(res, 2, "No game specified."); - return; - } - var lkey = String(formdata["key"]); - if (!(lkey in logins)) { - sendError(res, 1, null); - return; - } - var username = logins[lkey].name; - var gname = formdata["game"]; - // If dims are not given or invalid, the constructor will handle it. - var dims = [formdata["h"], formdata["w"]]; - if (!(gname in games)) { - sendError(res, 2, "No such game: " + gname); - tslog("Request for nonexistant game \"%s\"", gname); - return; - } - // A callback to pass to the game-in-progress checker. - var launch = function(err, fname) { - var nodematch = new RegExp("^" + username + ":node:"); - if (fname && (fname.match(nodematch) === null)) { - /* It's being played in dgamelaunch. */ - sendError(res, 4, "dgamelaunch"); - tslog("%s is already playing %s", username, gname); - return; - } - // Game starting has been approved. - var respondlaunch = function(nclient, success) { - if (success) { - res.writeHead(200, {'Content-Type': 'application/json'}); - var reply = {"t": "s", "id": nclient.id, "w": nclient.w, "h": - nclient.h, "p": username, "g": gname}; - res.write(JSON.stringify(reply)); - res.end(); - } - else { - sendError(res, 5, "Failed to open TTY"); - tslog("Unable to allocate TTY for %s", gname); - } - }; - if (fname) { - for (var cid in clients) { - cli = clients[cid]; - if ((cli instanceof Player) && - cli.session.pname == username && - cli.session.game.uname == gname) { - cli.reset(); - respondlaunch(cli, true); - tslog("Game %d has been taken over.", cli.session.sessid); - return; - } - } - /* If there's no player, it's a WebSocket game, and shouldn't be - * seized. */ - sendError(res, 4, "WebSocket"); - } - else { - new Player(gname, lkey, dims, respondlaunch); - } - }; - checkprogress(username, games[gname], launch, []); -} - -function watch(req, res, formdata) { - if (!("g" in formdata) | !("p" in formdata)) { - sendError(res, 2, "Game or player not given"); - return; - } - if (!(formdata.g in games)) { - sendError(res, 2, "No such game: " + formdata.g); - return; - } - var tag = formdata.g = "/" + formdata.p; - if (!(tag in sessions)) { - sendError(res, 7); - return; - } - var session = sessions[tag]; - var watch = new Watcher(session); - var reply = {"t": "w", "w": session.w, "h": session.h, - "p": session.pname, "g": session.game.uname}; - res.writeHead(200, {'Content-Type': 'application/json'}); - res.write(JSON.stringify(reply)); - res.end(); - tslog("Game %d is being watched", tag); -} - /* Sets things up for a new user, like dgamelaunch's commands[register] */ function regsetup(username) { function regsetup_l2(err) { @@ -876,24 +606,7 @@ return; } -/* Ends the game, obviously. Less obviously, stops watching the game if - * the client is a Watcher instead of a Player. */ -function endgame(client, res) { - if (!client.alive) { - sendError(res, 7, null, true); - return; - } - client.quit(); - // Give things some time to happen. - if (client instanceof Player) - setTimeout(readFeed, 200, client, res); - else - readFeed(client, res); - return; -} - /* Stops a running game if the request has the proper key. */ -/* TODO does this still work? */ function stopgame(res, formdata) { if (!("key" in formdata) || !(formdata["key"] in logins)) { sendError(res, 1); @@ -940,19 +653,6 @@ checkprogress(pname, games[gname], checkback, []); } -/* TODO remove polling */ -function findClient(formdata, playersOnly) { - if (typeof(formdata) != "object") - return null; - if ("id" in formdata) { - var id = formdata["id"]; - if (id in clients && (!playersOnly || clients[id] instanceof Player)) { - return clients[id]; - } - } - return null; -} - function startProgressWatcher() { var watchdirs = []; for (var gname in games) { @@ -1032,22 +732,6 @@ return; } -/* TODO remove polling */ -function readFeed(client, res) { - if (!client) { - sendError(res, 7, null, true); - return; - } - var msgs = client.read(); - if (!allowlogin && !msgs.length) { - sendError(res, 6, null, true); - return; - } - res.writeHead(200, { "Content-Type": "application/json" }); - res.write(JSON.stringify(msgs)); - res.end(); -} - /* TODO simplify by storing timestamps instead of callin stat() */ function getStatus(callback) { var now = new Date(); @@ -1272,45 +956,12 @@ var target = url.parse(req.url).pathname; /* First figure out if the client is POSTing to a command interface. */ if (req.method == 'POST') { - if (target == '/feed') { - var client = findClient(formdata, false); - if (!client) { - sendError(res, 7, null, true); - return; - } - if (formdata.t == "q") { - /* The client wants to terminate the process. */ - endgame(client, res); - return; // endgame() calls readFeed() itself. - } - else if (formdata.t == "d" && typeof(formdata.d) == "string") { - if (!(client instanceof Player)) { - sendError(res, 7, "Watching", true); - return; - } - /* process the keys */ - var hexstr = formdata.d.replace(/[^0-9a-f]/gi, ""); - if (hexstr.length % 2 != 0) { - sendError(res, 2, "incomplete byte", true); - return; - } - var keybuf = new Buffer(hexstr, "hex"); - client.write(keybuf, formdata.n); - } - readFeed(client, res); - } - else if (target == "/login") { + if (target == "/login") { login(req, res, formdata); } else if (target == "/addacct") { register(req, res, formdata); } - else if (target == "/play") { - startgame(req, res, formdata); - } - else if (target == "/watch") { - watch(req, res, formdata); - } else if (target == "/quit") { stopgame(res, formdata); } @@ -1323,16 +974,7 @@ } } else if (req.method == 'GET' || req.method == 'HEAD') { - if (target == '/feed') { - if (req.method == 'HEAD') { - res.writeHead(200, {"Content-Type": "application/json"}); - res.end(); - } - else - sendError(res, 7, null, true); - return; - } - else if (target == '/status') { + if (target == '/status') { statusmsg(req, res); } else if (target.match(/^\/uinfo\//)) { @@ -1352,7 +994,6 @@ return; } req.on('end', respond); - } function wsHandler(wsRequest) { @@ -1403,6 +1044,7 @@ wsRequest.reject(404, "No such resource."); } +/* TODO use a list instead */ function pushStatus() { getStatus(function(info) { info["t"] = "t";