RLGWebD: excise polling.

WebSockets are supported nearly everywhere now.

Listing current games and watching them are still broken.
This commit is contained in:
John "Elwin" Edwards 2015-01-03 15:23:04 -05:00
parent 920c6e8829
commit c7fc6418ff
2 changed files with 36 additions and 666 deletions

View file

@ -1,60 +1,5 @@
/* rlgterm.js: Roguelike Gallery's driver for termemu.js */ /* rlgterm.js: Roguelike Gallery's driver for termemu.js */
// A state machine that keeps track of polling the server.
var ajaxstate = {
state: 0,
timerID: null,
clear: function () {
if (this.timerID != null) {
window.clearTimeout(this.timerID);
this.timerID = null;
}
},
set: function (ms) {
this.clear();
this.timerID = window.setTimeout(getData, ms);
},
gotdata: function () {
this.set(1000);
this.state = 1;
},
gotnothing: function () {
if (this.state == 0) {
this.set(1000);
this.state = 1;
}
else if (this.state < 4) {
this.set(4000);
this.state++;
}
else if (session.playing) {
if (this.state < 8) {
this.set(15000);
this.state++;
}
else {
/* It's been over a minute. Stop polling. */
this.clear();
}
}
else {
/* If watching, it can't stop polling entirely, because there
* are no POST events to start it up again. */
this.set(10000);
}
},
posted: function (wasdata) {
if (wasdata) {
this.set(1000);
this.state = 1;
}
else {
this.set(200);
this.state = 0;
}
}
};
/* Data on the available games. */ /* Data on the available games. */
var games = { var games = {
"rogue3": { "rogue3": {
@ -81,7 +26,7 @@ var games = {
var session = { var session = {
/* The session id assigned by the server. */ /* The session id assigned by the server. */
id: null, connect: false,
/* Login name and key are now in sessionStorage. */ /* Login name and key are now in sessionStorage. */
/* Whether the game is being played or just watched. */ /* Whether the game is being played or just watched. */
playing: false, playing: false,
@ -165,119 +110,15 @@ function writeData(hexstr) {
return; return;
} }
/* State for sending and receiving messages. */
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.
/* Processes a message from the server, returning true or false if it was a
* data message with or without data, null if not data.
* All non-special responseTexts should be handed directly to this function.
*/
function processMsg(msg) {
var msgDicts;
var havedata = null; // eventual return value
try {
msgDicts = JSON.parse(msg);
} catch (e) {
if (e instanceof SyntaxError)
return null;
}
if (msgDicts.length === 0)
return false;
for (var j = 0; j < msgDicts.length; j++) {
if (!msgDicts[j].t)
continue;
else if (msgDicts[j].t == "E") {
if (msgDicts[j].c == 1 || msgDicts[j].c == 6 || msgDicts[j].c == 7) {
gameover();
if (msgDicts[j].c == 1) {
logout();
}
}
debug(1, "Server error: " + msgDicts[j].s);
}
// A data message
else if (msgDicts[j].t == "d") {
if (msgDicts[j].n === nrecv) {
writeData(msgDicts[j].d);
nrecv++;
/* Process anything in the queue that's now ready. */
var next;
while ((next = msgQ.shift()) !== undefined) {
writeData(next.d);
nrecv++;
}
}
else if (msgDicts[j].n > nrecv) {
/* The current message comes after one still missing. Queue this one
* for later use. */
debug(1, "Got packet " + msgDicts[j].n + ", expected " + nrecv);
msgQ[msgDicts[j].n - nrecv - 1] = msgDicts[j];
}
else {
/* This message's number was encountered previously. */
debug(1, "Discarding packet " + msgDicts[j].n + ", expected " + nrecv);
}
havedata = true;
}
else if (msgDicts[j].t == "T") {
setTitle(msgDicts[j].d);
}
else if (msgDicts[j].t == "q") {
gameover();
}
else {
debug(1, "Unrecognized server message " + msg);
}
}
return havedata;
}
function getData() {
if (session.id == null)
return;
var datareq = new XMLHttpRequest();
var msg = JSON.stringify({"id": session.id, "t": "n"});
datareq.onerror = errHandler;
datareq.onreadystatechange = function () {
if (datareq.readyState == 4 && datareq.status == 200) {
var wasdata = processMsg(datareq.responseText);
if (wasdata != null) {
if (wasdata)
ajaxstate.gotdata();
else
ajaxstate.gotnothing();
}
return;
}
};
datareq.open('POST', '/feed', true);
datareq.send(msg);
return;
}
function postResponseHandler() {
if (this.readyState == 4 && this.status == 200) {
// We might want to do something with wasdata someday.
var wasdata = processMsg(this.responseText);
ajaxstate.posted();
return;
}
}
function errHandler() { function errHandler() {
message("Unable to connect to the server.", "warn"); message("Unable to connect to the server.", "warn");
} }
function sendback(str) { function sendback(str) {
/* For responding to terminal queries. */ /* For responding to terminal queries. */
var msgDict = {"id": session.id, "t": "d", "n": nsend++, "d": str}; if (session.sock) {
var datareq = new XMLHttpRequest(); session.sock.send(JSON.stringify({"t": "d", "d": str}));
datareq.onerror = errHandler; }
datareq.onreadystatechange = postResponseHandler;
datareq.open('POST', '/feed', true);
datareq.send(JSON.stringify(msgDict));
return; return;
} }
@ -323,14 +164,7 @@ function sendkey(ev) {
if (session.sock) { if (session.sock) {
session.sock.send(JSON.stringify({"t": "d", "d": code})); session.sock.send(JSON.stringify({"t": "d", "d": code}));
} }
else { /* Otherwise it is disconnected */
var datareq = new XMLHttpRequest();
var msgDict = {"id": session.id, "t": "d", "n": nsend++, "d": code};
datareq.onerror = errHandler;
datareq.onreadystatechange = postResponseHandler;
datareq.open('POST', '/feed', true);
datareq.send(JSON.stringify(msgDict));
}
return; return;
} }
@ -386,14 +220,6 @@ function vkey(c) {
if (session.sock) { if (session.sock) {
session.sock.send(JSON.stringify({"t": "d", "d": keystr})); session.sock.send(JSON.stringify({"t": "d", "d": keystr}));
} }
else {
var datareq = new XMLHttpRequest();
var msgDict = {"id": session.id, "t": "d", "n": nsend++, "d": keystr};
datareq.onerror = errHandler;
datareq.onreadystatechange = postResponseHandler;
datareq.open('POST', '/feed', true);
datareq.send(JSON.stringify(msgDict));
}
return; return;
} }
@ -426,8 +252,8 @@ function setup() {
break; break;
} }
if (!window.WebSocket) { if (!window.WebSocket) {
message("Your browser does not support WebSockets. You can still play, " + message("Your browser does not support WebSockets. " +
"but it will be slower, and may not work in the future.", "warn"); "This Web app will not work.", "warn");
} }
return; return;
} }
@ -454,8 +280,7 @@ function togglectrl() {
function formlogin(ev) { function formlogin(ev) {
ev.preventDefault(); ev.preventDefault();
if (session.id != null) /* What to do if logged in already? */
return;
var loginmsg = {}; var loginmsg = {};
loginmsg["name"] = document.getElementById("input_name").value; loginmsg["name"] = document.getElementById("input_name").value;
loginmsg["pw"] = document.getElementById("input_pw").value; loginmsg["pw"] = document.getElementById("input_pw").value;
@ -490,6 +315,7 @@ function formlogin(ev) {
return; return;
} }
/* FIXME game list API has changed */
function tableCurrent(gamelist) { function tableCurrent(gamelist) {
var gamediv = document.getElementById("gametable"); var gamediv = document.getElementById("gametable");
while (gamediv.children.length > 2) while (gamediv.children.length > 2)
@ -533,7 +359,7 @@ function tableCurrent(gamelist) {
function wsCurrent() { function wsCurrent() {
if (!window.WebSocket) if (!window.WebSocket)
return; return;
if (session.id) { if (session.connect) {
/* Don't bother with status if already playing/watching. */ /* Don't bother with status if already playing/watching. */
if (statsock) { if (statsock) {
statsock.close(); statsock.close();
@ -569,11 +395,12 @@ function wsCurrent() {
} }
} }
/* FIXME gamelist API has changed */
function getcurrent(clear) { function getcurrent(clear) {
if (window.WebSocket) { if (window.WebSocket) {
return; return;
} }
if (session.id || clear) { if (session.connect || clear) {
if (statInterval) { if (statInterval) {
window.clearInterval(statInterval); window.clearInterval(statInterval);
statInterval = null; statInterval = null;
@ -608,7 +435,7 @@ function getcurrent(clear) {
} }
function getchoices() { function getchoices() {
if (session.id != null || !("lcred" in sessionStorage)) if (session.connect || !("lcred" in sessionStorage))
return; return;
var req = new XMLHttpRequest(); var req = new XMLHttpRequest();
req.onerror = errHandler; req.onerror = errHandler;
@ -680,62 +507,6 @@ function makeStarter(gname) {
return starter; return starter;
} }
function startgame(game) {
if (session.id != null || !("lcred" in sessionStorage))
return;
if (window.WebSocket) {
wsStart(game);
return;
}
var smsg = {};
smsg["key"] = sessionStorage.getItem("lcred");
smsg["game"] = game.uname;
smsg["h"] = 24;
smsg["w"] = 80;
var req = new XMLHttpRequest();
req.onerror = errHandler;
req.onreadystatechange = function () {
if (req.readyState != 4 || req.status != 200)
return;
var reply = JSON.parse(req.responseText);
if (reply.t == 's') {
/* Success */
session.id = reply.id;
session.playing = true;
termemu.resize(reply.h, reply.w);
message("You are now playing " + game.name + ".");
setmode("play");
getData();
}
else if (reply.t == 'E') {
if (reply.c == 1) {
logout();
message("The server forgot about you, please log in again.", "warn");
}
else if (reply.c == 4) {
if (reply.s == "dgamelaunch") {
message("You are already playing " + game.name + " over SSH.",
"warn");
}
else {
message("You are already playing " + game.name +
" in another browser window.", "warn");
}
}
else if (reply.c == 7) {
message("The game is being saved, try again in a few seconds.");
}
else {
message("The server says it can't start your game because \"" +
reply.s + "\". This is probably a bug.", "warn");
}
}
};
req.open('POST', '/play', true);
req.send(JSON.stringify(smsg));
return;
}
function makeStopper(gname) { function makeStopper(gname) {
if (!(gname in games)) if (!(gname in games))
return null; return null;
@ -774,12 +545,17 @@ function stopgame(game) {
return; return;
} }
function wsStart(game) { function startgame(game) {
if (!("lcred" in sessionStorage) || session.connect)
return;
if (!window.WebSocket) {
return;
}
var sockurl = "ws://" + window.location.host + "/play/" + game.uname; var sockurl = "ws://" + window.location.host + "/play/" + game.uname;
sockurl += "?key=" + sessionStorage.getItem("lcred") + "&w=80&h=24"; sockurl += "?key=" + sessionStorage.getItem("lcred") + "&w=80&h=24";
ws = new WebSocket(sockurl); ws = new WebSocket(sockurl);
ws.onopen = function (event) { ws.onopen = function (event) {
session.id = true; session.connect = true;
session.playing = true; session.playing = true;
session.sock = ws; session.sock = ws;
setmode("play"); setmode("play");
@ -800,55 +576,20 @@ function wsStart(game) {
}; };
} }
function startwatching(gamenumber) { function makeWatcher(t) {
if (session.id != null)
return;
if (window.WebSocket) {
wsWatch(gamenumber);
return;
}
var wmsg = {"n": Number(gamenumber)};
var req = new XMLHttpRequest();
req.onerror = errHandler;
req.onreadystatechange = function () {
if (req.readyState != 4 || req.status != 200)
return;
var reply = JSON.parse(req.responseText);
if (reply.t == 'w') {
/* Success */
session.id = reply.id;
session.playing = false;
termemu.resize(reply.h, reply.w);
termemu.reset();
termemu.toAltBuf();
var pname = reply.p;
var gname = games[reply.g].name;
message("You are now watching " + pname + " play " + gname + ".");
setmode("watch");
getData();
}
else if (reply.t == 'E') {
message("The game could not be watched: " + reply.s, "warn");
getcurrent();
}
};
req.open('POST', '/watch', true);
req.send(JSON.stringify(wmsg));
return;
}
function makeWatcher(n) {
function watcher(ev) { function watcher(ev) {
startwatching(n); startwatching(t);
} }
return watcher; return watcher;
} }
function wsWatch(gamenumber) { function startwatching(tag) {
var sockurl = "ws://" + window.location.host + "/watch/" + String(gamenumber); if (session.connect)
return;
var sockurl = "ws://" + window.location.host + "/watch/" + tag;
var ws = new WebSocket(sockurl); var ws = new WebSocket(sockurl);
ws.onopen = function (event) { ws.onopen = function (event) {
session.id = true; session.connect = true;
session.sock = ws; session.sock = ws;
setmode("watch"); setmode("watch");
}; };
@ -874,7 +615,8 @@ function wsWatch(gamenumber) {
function formreg(ev) { function formreg(ev) {
ev.preventDefault(); ev.preventDefault();
if (session.id != null) /* This ought to check for being logged in instead. */
if (session.connect)
return; return;
var regmsg = {}; var regmsg = {};
regmsg["name"] = document.getElementById("regin_name").value; regmsg["name"] = document.getElementById("regin_name").value;
@ -925,20 +667,16 @@ function formreg(ev) {
} }
function gameover() { function gameover() {
if (session.id == null) if (!session.connect)
return; return;
/* TODO IFACE2 If the end was unexpected, tell player the game was saved. */ /* TODO IFACE2 If the end was unexpected, tell player the game was saved. */
if (session.playing) if (session.playing)
message("Finished playing."); message("Finished playing.");
else else
message("Finished watching."); message("Finished watching.");
session.id = null; session.connect = false;
session.playing = false; session.playing = false;
ajaxstate.clear();
termemu.toNormBuf(); termemu.toNormBuf();
nsend = 0;
nrecv = 0;
msgQ = [];
if ("lcred" in sessionStorage) if ("lcred" in sessionStorage)
setmode("choose"); setmode("choose");
else else
@ -952,24 +690,14 @@ function logout() {
setmode("login"); setmode("login");
} }
/* TODO determine whether this is needed */
function stop() { function stop() {
if (!session.id) if (!session.connect)
return; return;
if (session.sock) { if (session.sock) {
session.sock.close(); session.sock.close();
return; return;
} }
var req = new XMLHttpRequest();
req.onerror = errHandler;
req.onreadystatechange = function () {
if (req.readyState == 4 && req.status == 200) {
processMsg(req.responseText);
return;
}
};
req.open('POST', '/feed', true);
req.send(JSON.stringify({"id": session.id, "t": "q"}));
return;
} }
function setmode(mode, ev) { function setmode(mode, ev) {

View file

@ -63,7 +63,6 @@ var games = {
/* Global state */ /* Global state */
var logins = {}; var logins = {};
var sessions = {}; var sessions = {};
var clients = {};
var dglgames = {}; var dglgames = {};
var allowlogin = true; var allowlogin = true;
var gamemux = new events.EventEmitter(); var gamemux = new events.EventEmitter();
@ -193,178 +192,6 @@ function TermSession(game, lkey, dims, handlers) {
} }
TermSession.prototype = new events.EventEmitter(); 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 // Also known as WebSocketAndTermSessionClosureGlueFactory
function wsWatcher(conn, session) { function wsWatcher(conn, session) {
var ss = this; // is this even needed? var ss = this; // is this even needed?
@ -716,103 +543,6 @@ function login(req, res, formdata) {
return; 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] */ /* Sets things up for a new user, like dgamelaunch's commands[register] */
function regsetup(username) { function regsetup(username) {
function regsetup_l2(err) { function regsetup_l2(err) {
@ -876,24 +606,7 @@ function register(req, res, formdata) {
return; 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. */ /* 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);
@ -940,19 +653,6 @@ function stopgame(res, formdata) {
checkprogress(pname, games[gname], checkback, []); 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() { function startProgressWatcher() {
var watchdirs = []; var watchdirs = [];
for (var gname in games) { for (var gname in games) {
@ -1032,22 +732,6 @@ function serveStatic(req, res, fname) {
return; 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() */ /* TODO simplify by storing timestamps instead of callin stat() */
function getStatus(callback) { function getStatus(callback) {
var now = new Date(); var now = new Date();
@ -1272,45 +956,12 @@ function webHandler(req, res) {
var target = url.parse(req.url).pathname; var target = url.parse(req.url).pathname;
/* First figure out if the client is POSTing to a command interface. */ /* First figure out if the client is POSTing to a command interface. */
if (req.method == 'POST') { if (req.method == 'POST') {
if (target == '/feed') { if (target == "/login") {
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") {
login(req, res, formdata); login(req, res, formdata);
} }
else if (target == "/addacct") { else if (target == "/addacct") {
register(req, res, formdata); register(req, res, formdata);
} }
else if (target == "/play") {
startgame(req, res, formdata);
}
else if (target == "/watch") {
watch(req, res, formdata);
}
else if (target == "/quit") { else if (target == "/quit") {
stopgame(res, formdata); stopgame(res, formdata);
} }
@ -1323,16 +974,7 @@ function webHandler(req, res) {
} }
} }
else if (req.method == 'GET' || req.method == 'HEAD') { else if (req.method == 'GET' || req.method == 'HEAD') {
if (target == '/feed') { if (target == '/status') {
if (req.method == 'HEAD') {
res.writeHead(200, {"Content-Type": "application/json"});
res.end();
}
else
sendError(res, 7, null, true);
return;
}
else if (target == '/status') {
statusmsg(req, res); statusmsg(req, res);
} }
else if (target.match(/^\/uinfo\//)) { else if (target.match(/^\/uinfo\//)) {
@ -1352,7 +994,6 @@ function webHandler(req, res) {
return; return;
} }
req.on('end', respond); req.on('end', respond);
} }
function wsHandler(wsRequest) { function wsHandler(wsRequest) {
@ -1403,6 +1044,7 @@ function wsHandler(wsRequest) {
wsRequest.reject(404, "No such resource."); wsRequest.reject(404, "No such resource.");
} }
/* TODO use a list instead */
function pushStatus() { function pushStatus() {
getStatus(function(info) { getStatus(function(info) {
info["t"] = "t"; info["t"] = "t";