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 */
// 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. */
var games = {
"rogue3": {
@ -81,7 +26,7 @@ var games = {
var session = {
/* The session id assigned by the server. */
id: null,
connect: false,
/* Login name and key are now in sessionStorage. */
/* Whether the game is being played or just watched. */
playing: false,
@ -165,119 +110,15 @@ function writeData(hexstr) {
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() {
message("Unable to connect to the server.", "warn");
}
function sendback(str) {
/* For responding to terminal queries. */
var msgDict = {"id": session.id, "t": "d", "n": nsend++, "d": str};
var datareq = new XMLHttpRequest();
datareq.onerror = errHandler;
datareq.onreadystatechange = postResponseHandler;
datareq.open('POST', '/feed', true);
datareq.send(JSON.stringify(msgDict));
if (session.sock) {
session.sock.send(JSON.stringify({"t": "d", "d": str}));
}
return;
}
@ -323,14 +164,7 @@ function sendkey(ev) {
if (session.sock) {
session.sock.send(JSON.stringify({"t": "d", "d": code}));
}
else {
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));
}
/* Otherwise it is disconnected */
return;
}
@ -386,14 +220,6 @@ function vkey(c) {
if (session.sock) {
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;
}
@ -426,8 +252,8 @@ function setup() {
break;
}
if (!window.WebSocket) {
message("Your browser does not support WebSockets. You can still play, " +
"but it will be slower, and may not work in the future.", "warn");
message("Your browser does not support WebSockets. " +
"This Web app will not work.", "warn");
}
return;
}
@ -454,8 +280,7 @@ function togglectrl() {
function formlogin(ev) {
ev.preventDefault();
if (session.id != null)
return;
/* What to do if logged in already? */
var loginmsg = {};
loginmsg["name"] = document.getElementById("input_name").value;
loginmsg["pw"] = document.getElementById("input_pw").value;
@ -490,6 +315,7 @@ function formlogin(ev) {
return;
}
/* FIXME game list API has changed */
function tableCurrent(gamelist) {
var gamediv = document.getElementById("gametable");
while (gamediv.children.length > 2)
@ -533,7 +359,7 @@ function tableCurrent(gamelist) {
function wsCurrent() {
if (!window.WebSocket)
return;
if (session.id) {
if (session.connect) {
/* Don't bother with status if already playing/watching. */
if (statsock) {
statsock.close();
@ -569,11 +395,12 @@ function wsCurrent() {
}
}
/* FIXME gamelist API has changed */
function getcurrent(clear) {
if (window.WebSocket) {
return;
}
if (session.id || clear) {
if (session.connect || clear) {
if (statInterval) {
window.clearInterval(statInterval);
statInterval = null;
@ -608,7 +435,7 @@ function getcurrent(clear) {
}
function getchoices() {
if (session.id != null || !("lcred" in sessionStorage))
if (session.connect || !("lcred" in sessionStorage))
return;
var req = new XMLHttpRequest();
req.onerror = errHandler;
@ -680,62 +507,6 @@ function makeStarter(gname) {
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) {
if (!(gname in games))
return null;
@ -774,12 +545,17 @@ function stopgame(game) {
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;
sockurl += "?key=" + sessionStorage.getItem("lcred") + "&w=80&h=24";
ws = new WebSocket(sockurl);
ws.onopen = function (event) {
session.id = true;
session.connect = true;
session.playing = true;
session.sock = ws;
setmode("play");
@ -800,55 +576,20 @@ function wsStart(game) {
};
}
function startwatching(gamenumber) {
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 makeWatcher(t) {
function watcher(ev) {
startwatching(n);
startwatching(t);
}
return watcher;
}
function wsWatch(gamenumber) {
var sockurl = "ws://" + window.location.host + "/watch/" + String(gamenumber);
function startwatching(tag) {
if (session.connect)
return;
var sockurl = "ws://" + window.location.host + "/watch/" + tag;
var ws = new WebSocket(sockurl);
ws.onopen = function (event) {
session.id = true;
session.connect = true;
session.sock = ws;
setmode("watch");
};
@ -874,7 +615,8 @@ function wsWatch(gamenumber) {
function formreg(ev) {
ev.preventDefault();
if (session.id != null)
/* This ought to check for being logged in instead. */
if (session.connect)
return;
var regmsg = {};
regmsg["name"] = document.getElementById("regin_name").value;
@ -925,20 +667,16 @@ function formreg(ev) {
}
function gameover() {
if (session.id == null)
if (!session.connect)
return;
/* TODO IFACE2 If the end was unexpected, tell player the game was saved. */
if (session.playing)
message("Finished playing.");
else
message("Finished watching.");
session.id = null;
session.connect = false;
session.playing = false;
ajaxstate.clear();
termemu.toNormBuf();
nsend = 0;
nrecv = 0;
msgQ = [];
if ("lcred" in sessionStorage)
setmode("choose");
else
@ -952,24 +690,14 @@ function logout() {
setmode("login");
}
/* TODO determine whether this is needed */
function stop() {
if (!session.id)
if (!session.connect)
return;
if (session.sock) {
session.sock.close();
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) {