comparison rlgterm.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 e54018b26ed8
children a2a25b7631f1
comparison
equal deleted inserted replaced
158:9961a538c00e 159:a613380ffdc2
1 /* rlgterm.js: Roguelike Gallery's driver for termemu.js */ 1 /* rlgterm.js: Roguelike Gallery's driver for termemu.js */
2
3 // A state machine that keeps track of polling the server.
4 var ajaxstate = {
5 state: 0,
6 timerID: null,
7 clear: function () {
8 if (this.timerID != null) {
9 window.clearTimeout(this.timerID);
10 this.timerID = null;
11 }
12 },
13 set: function (ms) {
14 this.clear();
15 this.timerID = window.setTimeout(getData, ms);
16 },
17 gotdata: function () {
18 this.set(1000);
19 this.state = 1;
20 },
21 gotnothing: function () {
22 if (this.state == 0) {
23 this.set(1000);
24 this.state = 1;
25 }
26 else if (this.state < 4) {
27 this.set(4000);
28 this.state++;
29 }
30 else if (session.playing) {
31 if (this.state < 8) {
32 this.set(15000);
33 this.state++;
34 }
35 else {
36 /* It's been over a minute. Stop polling. */
37 this.clear();
38 }
39 }
40 else {
41 /* If watching, it can't stop polling entirely, because there
42 * are no POST events to start it up again. */
43 this.set(10000);
44 }
45 },
46 posted: function (wasdata) {
47 if (wasdata) {
48 this.set(1000);
49 this.state = 1;
50 }
51 else {
52 this.set(200);
53 this.state = 0;
54 }
55 }
56 };
57 2
58 /* Data on the available games. */ 3 /* Data on the available games. */
59 var games = { 4 var games = {
60 "rogue3": { 5 "rogue3": {
61 "name": "Rogue V3", 6 "name": "Rogue V3",
79 } 24 }
80 }; 25 };
81 26
82 var session = { 27 var session = {
83 /* The session id assigned by the server. */ 28 /* The session id assigned by the server. */
84 id: null, 29 connect: false,
85 /* Login name and key are now in sessionStorage. */ 30 /* Login name and key are now in sessionStorage. */
86 /* Whether the game is being played or just watched. */ 31 /* Whether the game is being played or just watched. */
87 playing: false, 32 playing: false,
88 /* WebSocket for communication */ 33 /* WebSocket for communication */
89 sock: null 34 sock: null
163 } 108 }
164 termemu.write(codes); 109 termemu.write(codes);
165 return; 110 return;
166 } 111 }
167 112
168 /* State for sending and receiving messages. */
169 var nsend = 0; // The number of the next packet to send.
170 var nrecv = 0; // The next packet expected.
171 var msgQ = []; // Queue for out-of-order messages.
172
173 /* Processes a message from the server, returning true or false if it was a
174 * data message with or without data, null if not data.
175 * All non-special responseTexts should be handed directly to this function.
176 */
177 function processMsg(msg) {
178 var msgDicts;
179 var havedata = null; // eventual return value
180 try {
181 msgDicts = JSON.parse(msg);
182 } catch (e) {
183 if (e instanceof SyntaxError)
184 return null;
185 }
186 if (msgDicts.length === 0)
187 return false;
188 for (var j = 0; j < msgDicts.length; j++) {
189 if (!msgDicts[j].t)
190 continue;
191 else if (msgDicts[j].t == "E") {
192 if (msgDicts[j].c == 1 || msgDicts[j].c == 6 || msgDicts[j].c == 7) {
193 gameover();
194 if (msgDicts[j].c == 1) {
195 logout();
196 }
197 }
198 debug(1, "Server error: " + msgDicts[j].s);
199 }
200 // A data message
201 else if (msgDicts[j].t == "d") {
202 if (msgDicts[j].n === nrecv) {
203 writeData(msgDicts[j].d);
204 nrecv++;
205 /* Process anything in the queue that's now ready. */
206 var next;
207 while ((next = msgQ.shift()) !== undefined) {
208 writeData(next.d);
209 nrecv++;
210 }
211 }
212 else if (msgDicts[j].n > nrecv) {
213 /* The current message comes after one still missing. Queue this one
214 * for later use. */
215 debug(1, "Got packet " + msgDicts[j].n + ", expected " + nrecv);
216 msgQ[msgDicts[j].n - nrecv - 1] = msgDicts[j];
217 }
218 else {
219 /* This message's number was encountered previously. */
220 debug(1, "Discarding packet " + msgDicts[j].n + ", expected " + nrecv);
221 }
222 havedata = true;
223 }
224 else if (msgDicts[j].t == "T") {
225 setTitle(msgDicts[j].d);
226 }
227 else if (msgDicts[j].t == "q") {
228 gameover();
229 }
230 else {
231 debug(1, "Unrecognized server message " + msg);
232 }
233 }
234 return havedata;
235 }
236
237 function getData() {
238 if (session.id == null)
239 return;
240 var datareq = new XMLHttpRequest();
241 var msg = JSON.stringify({"id": session.id, "t": "n"});
242 datareq.onerror = errHandler;
243 datareq.onreadystatechange = function () {
244 if (datareq.readyState == 4 && datareq.status == 200) {
245 var wasdata = processMsg(datareq.responseText);
246 if (wasdata != null) {
247 if (wasdata)
248 ajaxstate.gotdata();
249 else
250 ajaxstate.gotnothing();
251 }
252 return;
253 }
254 };
255 datareq.open('POST', '/feed', true);
256 datareq.send(msg);
257 return;
258 }
259
260 function postResponseHandler() {
261 if (this.readyState == 4 && this.status == 200) {
262 // We might want to do something with wasdata someday.
263 var wasdata = processMsg(this.responseText);
264 ajaxstate.posted();
265 return;
266 }
267 }
268
269 function errHandler() { 113 function errHandler() {
270 message("Unable to connect to the server.", "warn"); 114 message("Unable to connect to the server.", "warn");
271 } 115 }
272 116
273 function sendback(str) { 117 function sendback(str) {
274 /* For responding to terminal queries. */ 118 /* For responding to terminal queries. */
275 var msgDict = {"id": session.id, "t": "d", "n": nsend++, "d": str}; 119 if (session.sock) {
276 var datareq = new XMLHttpRequest(); 120 session.sock.send(JSON.stringify({"t": "d", "d": str}));
277 datareq.onerror = errHandler; 121 }
278 datareq.onreadystatechange = postResponseHandler;
279 datareq.open('POST', '/feed', true);
280 datareq.send(JSON.stringify(msgDict));
281 return; 122 return;
282 } 123 }
283 124
284 function sendkey(ev) { 125 function sendkey(ev) {
285 if (!session.playing) 126 if (!session.playing)
321 } 162 }
322 ev.preventDefault(); 163 ev.preventDefault();
323 if (session.sock) { 164 if (session.sock) {
324 session.sock.send(JSON.stringify({"t": "d", "d": code})); 165 session.sock.send(JSON.stringify({"t": "d", "d": code}));
325 } 166 }
326 else { 167 /* Otherwise it is disconnected */
327 var datareq = new XMLHttpRequest();
328 var msgDict = {"id": session.id, "t": "d", "n": nsend++, "d": code};
329 datareq.onerror = errHandler;
330 datareq.onreadystatechange = postResponseHandler;
331 datareq.open('POST', '/feed', true);
332 datareq.send(JSON.stringify(msgDict));
333 }
334 return; 168 return;
335 } 169 }
336 170
337 var charshifts = { '-': "5f", '=': "2b", '[': "7b", ']': "7d", '\\': "7c", 171 var charshifts = { '-': "5f", '=': "2b", '[': "7b", ']': "7d", '\\': "7c",
338 ';': "3a", '\'': "22", ',': "3c", '.': "3e", '/': "3f", '`': "7e" 172 ';': "3a", '\'': "22", ',': "3c", '.': "3e", '/': "3f", '`': "7e"
384 else 218 else
385 return; 219 return;
386 if (session.sock) { 220 if (session.sock) {
387 session.sock.send(JSON.stringify({"t": "d", "d": keystr})); 221 session.sock.send(JSON.stringify({"t": "d", "d": keystr}));
388 } 222 }
389 else {
390 var datareq = new XMLHttpRequest();
391 var msgDict = {"id": session.id, "t": "d", "n": nsend++, "d": keystr};
392 datareq.onerror = errHandler;
393 datareq.onreadystatechange = postResponseHandler;
394 datareq.open('POST', '/feed', true);
395 datareq.send(JSON.stringify(msgDict));
396 }
397 return; 223 return;
398 } 224 }
399 225
400 function setup() { 226 function setup() {
401 keyHexCodes.init(); 227 keyHexCodes.init();
424 } 250 }
425 else 251 else
426 break; 252 break;
427 } 253 }
428 if (!window.WebSocket) { 254 if (!window.WebSocket) {
429 message("Your browser does not support WebSockets. You can still play, " + 255 message("Your browser does not support WebSockets. " +
430 "but it will be slower, and may not work in the future.", "warn"); 256 "This Web app will not work.", "warn");
431 } 257 }
432 return; 258 return;
433 } 259 }
434 260
435 function toggleshift() { 261 function toggleshift() {
452 return; 278 return;
453 } 279 }
454 280
455 function formlogin(ev) { 281 function formlogin(ev) {
456 ev.preventDefault(); 282 ev.preventDefault();
457 if (session.id != null) 283 /* What to do if logged in already? */
458 return;
459 var loginmsg = {}; 284 var loginmsg = {};
460 loginmsg["name"] = document.getElementById("input_name").value; 285 loginmsg["name"] = document.getElementById("input_name").value;
461 loginmsg["pw"] = document.getElementById("input_pw").value; 286 loginmsg["pw"] = document.getElementById("input_pw").value;
462 var req = new XMLHttpRequest(); 287 var req = new XMLHttpRequest();
463 req.onerror = errHandler; 288 req.onerror = errHandler;
488 req.open('POST', '/login', true); 313 req.open('POST', '/login', true);
489 req.send(JSON.stringify(loginmsg)); 314 req.send(JSON.stringify(loginmsg));
490 return; 315 return;
491 } 316 }
492 317
318 /* FIXME game list API has changed */
493 function tableCurrent(gamelist) { 319 function tableCurrent(gamelist) {
494 var gamediv = document.getElementById("gametable"); 320 var gamediv = document.getElementById("gametable");
495 while (gamediv.children.length > 2) 321 while (gamediv.children.length > 2)
496 gamediv.removeChild(gamediv.children[2]); 322 gamediv.removeChild(gamediv.children[2]);
497 if (gamelist.length === 0) { 323 if (gamelist.length === 0) {
531 357
532 /* Handles the status socket, opening and closing it when necessary. */ 358 /* Handles the status socket, opening and closing it when necessary. */
533 function wsCurrent() { 359 function wsCurrent() {
534 if (!window.WebSocket) 360 if (!window.WebSocket)
535 return; 361 return;
536 if (session.id) { 362 if (session.connect) {
537 /* Don't bother with status if already playing/watching. */ 363 /* Don't bother with status if already playing/watching. */
538 if (statsock) { 364 if (statsock) {
539 statsock.close(); 365 statsock.close();
540 statsock = null; 366 statsock = null;
541 } 367 }
567 statsock.onclose = function (ev) { 393 statsock.onclose = function (ev) {
568 statsock = null; 394 statsock = null;
569 } 395 }
570 } 396 }
571 397
398 /* FIXME gamelist API has changed */
572 function getcurrent(clear) { 399 function getcurrent(clear) {
573 if (window.WebSocket) { 400 if (window.WebSocket) {
574 return; 401 return;
575 } 402 }
576 if (session.id || clear) { 403 if (session.connect || clear) {
577 if (statInterval) { 404 if (statInterval) {
578 window.clearInterval(statInterval); 405 window.clearInterval(statInterval);
579 statInterval = null; 406 statInterval = null;
580 } 407 }
581 return; 408 return;
606 req.send(); 433 req.send();
607 return; 434 return;
608 } 435 }
609 436
610 function getchoices() { 437 function getchoices() {
611 if (session.id != null || !("lcred" in sessionStorage)) 438 if (session.connect || !("lcred" in sessionStorage))
612 return; 439 return;
613 var req = new XMLHttpRequest(); 440 var req = new XMLHttpRequest();
614 req.onerror = errHandler; 441 req.onerror = errHandler;
615 req.onreadystatechange = function () { 442 req.onreadystatechange = function () {
616 if (req.readyState != 4 || req.status != 200) 443 if (req.readyState != 4 || req.status != 200)
678 startgame(game); 505 startgame(game);
679 } 506 }
680 return starter; 507 return starter;
681 } 508 }
682 509
683 function startgame(game) {
684 if (session.id != null || !("lcred" in sessionStorage))
685 return;
686 if (window.WebSocket) {
687 wsStart(game);
688 return;
689 }
690 var smsg = {};
691 smsg["key"] = sessionStorage.getItem("lcred");
692 smsg["game"] = game.uname;
693 smsg["h"] = 24;
694 smsg["w"] = 80;
695 var req = new XMLHttpRequest();
696 req.onerror = errHandler;
697 req.onreadystatechange = function () {
698 if (req.readyState != 4 || req.status != 200)
699 return;
700 var reply = JSON.parse(req.responseText);
701 if (reply.t == 's') {
702 /* Success */
703 session.id = reply.id;
704 session.playing = true;
705 termemu.resize(reply.h, reply.w);
706 message("You are now playing " + game.name + ".");
707 setmode("play");
708 getData();
709 }
710 else if (reply.t == 'E') {
711 if (reply.c == 1) {
712 logout();
713 message("The server forgot about you, please log in again.", "warn");
714 }
715 else if (reply.c == 4) {
716 if (reply.s == "dgamelaunch") {