Mercurial > hg > rlgwebd
comparison rlgwebd.js @ 107:b64e31c5ec31
RLG-Web server: add playing through WebSockets.
Games can be played with a WebSocket connection to
/play/<game>?key=<loginkey>&w=<cols>&h=<rows>
author | John "Elwin" Edwards <elwin@sdf.org> |
---|---|
date | Sun, 15 Jul 2012 09:04:39 -0700 |
parents | 7d444ba4739e |
children | 67b393f10c2b |
comparison
equal
deleted
inserted
replaced
106:dc1414faee19 | 107:b64e31c5ec31 |
---|---|
180 delete sessions[id]; | 180 delete sessions[id]; |
181 tslog("Game %s ended.", id); | 181 tslog("Game %s ended.", id); |
182 gamemux.emit('end', id); | 182 gamemux.emit('end', id); |
183 }); | 183 }); |
184 this.close = function () { | 184 this.close = function () { |
185 this.term.kill('SIGHUP'); | 185 if (this.sessid in sessions) |
186 this.term.kill('SIGHUP'); | |
186 }; | 187 }; |
187 } | 188 } |
188 TermSession.prototype = new events.EventEmitter(); | 189 TermSession.prototype = new events.EventEmitter(); |
189 | 190 |
190 function Watcher(session) { | 191 function Watcher(session) { |
383 })); | 384 })); |
384 conn.sendUTF(JSON.stringify({"t": "d", | 385 conn.sendUTF(JSON.stringify({"t": "d", |
385 "d": session.framebuf.toString("hex", 0, session.frameoff)})); | 386 "d": session.framebuf.toString("hex", 0, session.frameoff)})); |
386 } | 387 } |
387 | 388 |
389 function wsPlay(wsReq, game, lkey, dims) { | |
390 var conn; | |
391 var session; | |
392 /* Listeners on the WebSocket */ | |
393 function messageH(message) { | |
394 var parsedMsg = getMsgWS(message); | |
395 if (parsedMsg.t == 'q') { | |
396 session.close(); | |
397 } | |
398 else if (parsedMsg.t == 'd') { | |
399 var hexstr = parsedMsg.d.replace(/[^0-9a-f]/gi, ""); | |
400 if (hexstr.length % 2 != 0) { | |
401 hexstr = hexstr.slice(0, -1); | |
402 } | |
403 var keybuf = new Buffer(hexstr, "hex"); | |
404 session.write(keybuf); | |
405 } | |
406 } | |
407 function closeH() { | |
408 session.close(); | |
409 } | |
410 /* These listen on the TermSession. */ | |
411 function openH(success, id) { | |
412 if (success) { | |
413 var reply = {"t": "s", "id": id, "w": sessions[id].w, "h": | |
414 sessions[id].h, "p": sessions[id].pname, "g": game}; | |
415 conn = wsReq.accept(null, wsReq.origin); | |
416 conn.sendUTF(JSON.stringify(reply)); | |
417 conn.on('message', messageH); | |
418 conn.on('close', closeH); | |
419 } | |
420 else { | |
421 wsReq.reject(500, errorcodes[5]); | |
422 tslog("Unable to allocate TTY for %s", game); | |
423 } | |
424 } | |
425 function dataH(chunk) { | |
426 var msg = {}; | |
427 msg.t = "d"; | |
428 msg.d = chunk.toString("hex"); | |
429 conn.sendUTF(JSON.stringify(msg)); | |
430 } | |
431 function exitH() { | |
432 if (conn.connected) | |
433 conn.sendUTF(JSON.stringify({"t": "q"})); | |
434 conn.close(); | |
435 session.removeListener('open', openH); | |
436 session.removeListener('data', dataH); | |
437 session.removeListener('exit', exitH); | |
438 } | |
439 var handlers = {'open': openH, 'data': dataH, 'exit': exitH}; | |
440 session = new TermSession(game, lkey, dims, handlers); | |
441 } | |
442 | |
443 function wsStart(wsReq) { | |
444 var playmatch = wsReq.resourceURL.pathname.match(/^\/play\/([^\/]*)$/); | |
445 if (!playmatch[1] || !(playmatch[1] in games)) { | |
446 wsReq.reject(404, errorcodes[2]); | |
447 return; | |
448 } | |
449 var gname = playmatch[1]; | |
450 if (!allowlogin) { | |
451 wsReq.reject(404, errorcodes[6]); | |
452 return; | |
453 } | |
454 if (!("key" in wsReq.resourceURL.query)) { | |
455 wsReq.reject(404, "No key given."); | |
456 return; | |
457 } | |
458 var lkey = wsReq.resourceURL.query["key"]; | |
459 if (!(lkey in logins)) { | |
460 wsReq.reject(404, errorcodes[1]); | |
461 return; | |
462 } | |
463 var pname = logins[lkey].name; | |
464 var dims = [wsReq.resourceURL.query.h, wsReq.resourceURL.query.w]; | |
465 function progcallback(err, fname) { | |
466 if (fname) { | |
467 wsReq.reject(404, errorcodes[4]); | |
468 tslog("%s is already playing %s", pname, gname); | |
469 } | |
470 else | |
471 wsPlay(wsReq, gname, lkey, dims); | |
472 }; | |
473 checkprogress(pname, games[gname], progcallback, []); | |
474 } | |
475 | |
388 /* Some functions which check whether a player is currently playing or | 476 /* Some functions which check whether a player is currently playing or |
389 * has a saved game. Maybe someday they will provide information on | 477 * has a saved game. Maybe someday they will provide information on |
390 * the game. */ | 478 * the game. */ |
391 function checkprogress(user, game, callback, args) { | 479 function checkprogress(user, game, callback, args) { |
392 var progressdir = "/dgldir/inprogress-" + game.uname; | 480 var progressdir = "/dgldir/inprogress-" + game.uname; |
526 return {}; | 614 return {}; |
527 } | 615 } |
528 if (typeof(jsonobj) != "object") | 616 if (typeof(jsonobj) != "object") |
529 return {}; | 617 return {}; |
530 return jsonobj; | 618 return jsonobj; |
619 } | |
620 | |
621 function getMsgWS(msgObj) { | |
622 if (msgObj.type != "utf8") | |
623 return {}; | |
624 return getMsg(msgObj.utf8Data); | |
531 } | 625 } |
532 | 626 |
533 function reaper() { | 627 function reaper() { |
534 var now = new Date(); | 628 var now = new Date(); |
535 function reapcheck(session) { | 629 function reapcheck(session) { |
1005 if (!(client instanceof Player)) { | 1099 if (!(client instanceof Player)) { |
1006 sendError(res, 7, "Watching", true); | 1100 sendError(res, 7, "Watching", true); |
1007 return; | 1101 return; |
1008 } | 1102 } |
1009 /* process the keys */ | 1103 /* process the keys */ |
1010 hexstr = formdata.d.replace(/[^0-9a-f]/gi, ""); | 1104 var hexstr = formdata.d.replace(/[^0-9a-f]/gi, ""); |
1011 if (hexstr.length % 2 != 0) { | 1105 if (hexstr.length % 2 != 0) { |
1012 sendError(res, 2, "incomplete byte", true); | 1106 sendError(res, 2, "incomplete byte", true); |
1013 return; | 1107 return; |
1014 } | 1108 } |
1015 keybuf = new Buffer(hexstr, "hex"); | 1109 var keybuf = new Buffer(hexstr, "hex"); |
1016 client.write(keybuf, formdata.n); | 1110 client.write(keybuf, formdata.n); |
1017 } | 1111 } |
1018 readFeed(client, res); | 1112 readFeed(client, res); |
1019 } | 1113 } |
1020 else if (target == "/login") { | 1114 else if (target == "/login") { |
1064 | 1158 |
1065 } | 1159 } |
1066 | 1160 |
1067 function wsHandler(wsRequest) { | 1161 function wsHandler(wsRequest) { |
1068 var watchmatch = wsRequest.resource.match(/^\/watch\/([0-9]*)$/); | 1162 var watchmatch = wsRequest.resource.match(/^\/watch\/([0-9]*)$/); |
1163 var playmatch = wsRequest.resource.match(/^\/play\//); | |
1069 if (watchmatch !== null) { | 1164 if (watchmatch !== null) { |
1070 if (watchmatch[1] && Number(watchmatch[1]) in sessions) { | 1165 if (watchmatch[1] && Number(watchmatch[1]) in sessions) { |
1071 var tsession = sessions[Number(watchmatch[1])]; | 1166 var tsession = sessions[Number(watchmatch[1])]; |
1072 var conn = wsRequest.accept(null, wsRequest.origin); | 1167 var conn = wsRequest.accept(null, wsRequest.origin); |
1073 new wsWatcher(conn, tsession); | 1168 new wsWatcher(conn, tsession); |
1074 tslog("Game %d is being watched via WebSockets", tsession.sessid); | 1169 tslog("Game %d is being watched via WebSockets", tsession.sessid); |
1075 } | 1170 } |
1076 else | 1171 else |
1077 wsRequest.reject(404, errorcodes[7]); | 1172 wsRequest.reject(404, errorcodes[7]); |
1078 } | 1173 } |
1079 else if (wsRequest.resource == "/status") { | 1174 else if (playmatch !== null) { |
1175 wsStart(wsRequest); | |
1176 } | |
1177 else if (wsRequest.resourceURL.pathname == "/status") { | |
1080 var conn = wsRequest.accept(null, wsRequest.origin); | 1178 var conn = wsRequest.accept(null, wsRequest.origin); |
1081 var tell = function () { | 1179 var tell = function () { |
1082 getStatus(function (info) { | 1180 getStatus(function (info) { |
1083 info["t"] = "t"; | 1181 info["t"] = "t"; |
1084 conn.sendUTF(JSON.stringify(info)); | 1182 conn.sendUTF(JSON.stringify(info)); |