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") { |