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));