Mercurial > hg > rlgwebd
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") { |
