changeset 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 dc1414faee19
children 86a458080e80
files rlgwebd.js
diffstat 1 files changed, 102 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/rlgwebd.js	Sat Jul 14 10:11:13 2012 -0700
+++ b/rlgwebd.js	Sun Jul 15 09:04:39 2012 -0700
@@ -182,7 +182,8 @@
     gamemux.emit('end', id);
   });
   this.close = function () {
-    this.term.kill('SIGHUP');
+    if (this.sessid in sessions)
+      this.term.kill('SIGHUP');
   };
 }
 TermSession.prototype = new events.EventEmitter();
@@ -385,6 +386,93 @@
         "d": session.framebuf.toString("hex", 0, session.frameoff)}));
 }
 
+function wsPlay(wsReq, game, lkey, dims) {
+  var conn;
+  var session;
+  /* Listeners on the WebSocket */
+  function messageH(message) {
+    var parsedMsg = getMsgWS(message);
+    if (parsedMsg.t == 'q') {
+      session.close();
+    }
+    else if (parsedMsg.t == 'd') {
+      var hexstr = parsedMsg.d.replace(/[^0-9a-f]/gi, "");
+      if (hexstr.length % 2 != 0) {
+        hexstr = hexstr.slice(0, -1);
+      }
+      var keybuf = new Buffer(hexstr, "hex");
+      session.write(keybuf);
+    }
+  }
+  function closeH() {
+    session.close();
+  }
+  /* These listen on the TermSession. */
+  function openH(success, id) {
+    if (success) {
+      var reply = {"t": "s", "id": id, "w": sessions[id].w, "h": 
+                   sessions[id].h, "p": sessions[id].pname, "g": game};
+      conn = wsReq.accept(null, wsReq.origin);
+      conn.sendUTF(JSON.stringify(reply));
+      conn.on('message', messageH);
+      conn.on('close', closeH);
+    }
+    else {
+      wsReq.reject(500, errorcodes[5]);
+      tslog("Unable to allocate TTY for %s", game);
+    }
+  }
+  function dataH(chunk) {
+    var msg = {};
+    msg.t = "d";
+    msg.d = chunk.toString("hex");
+    conn.sendUTF(JSON.stringify(msg));
+  }
+  function exitH() {
+    if (conn.connected)
+      conn.sendUTF(JSON.stringify({"t": "q"}));
+    conn.close();
+    session.removeListener('open', openH);
+    session.removeListener('data', dataH);
+    session.removeListener('exit', exitH);
+  }
+  var handlers = {'open': openH, 'data': dataH, 'exit': exitH};
+  session = new TermSession(game, lkey, dims, handlers);
+}
+
+function wsStart(wsReq) {
+  var playmatch = wsReq.resourceURL.pathname.match(/^\/play\/([^\/]*)$/);
+  if (!playmatch[1] || !(playmatch[1] in games)) {
+    wsReq.reject(404, errorcodes[2]);
+    return;
+  }
+  var gname = playmatch[1];
+  if (!allowlogin) {
+    wsReq.reject(404, errorcodes[6]);
+    return;
+  }
+  if (!("key" in wsReq.resourceURL.query)) {
+    wsReq.reject(404, "No key given.");
+    return;
+  }
+  var lkey = wsReq.resourceURL.query["key"];
+  if (!(lkey in logins)) {
+    wsReq.reject(404, errorcodes[1]);
+    return;
+  }
+  var pname = logins[lkey].name;
+  var dims = [wsReq.resourceURL.query.h, wsReq.resourceURL.query.w];
+  function progcallback(err, fname) {
+    if (fname) {
+      wsReq.reject(404, errorcodes[4]);
+      tslog("%s is already playing %s", pname, gname);
+    }
+    else
+      wsPlay(wsReq, gname, lkey, dims);
+  };
+  checkprogress(pname, games[gname], progcallback, []);
+}
+
 /* Some functions which check whether a player is currently playing or 
  * has a saved game.  Maybe someday they will provide information on 
  * the game. */
@@ -530,6 +618,12 @@
   return jsonobj;
 }
 
+function getMsgWS(msgObj) {
+  if (msgObj.type != "utf8")
+    return {};
+  return getMsg(msgObj.utf8Data);
+}
+
 function reaper() {
   var now = new Date();
   function reapcheck(session) {
@@ -1007,12 +1101,12 @@
             return;
           }
           /* process the keys */
-          hexstr = formdata.d.replace(/[^0-9a-f]/gi, "");
+          var hexstr = formdata.d.replace(/[^0-9a-f]/gi, "");
           if (hexstr.length % 2 != 0) {
             sendError(res, 2, "incomplete byte", true);
             return;
           }
-          keybuf = new Buffer(hexstr, "hex");
+          var keybuf = new Buffer(hexstr, "hex");
           client.write(keybuf, formdata.n);
         }
         readFeed(client, res);
@@ -1066,6 +1160,7 @@
 
 function wsHandler(wsRequest) {
   var watchmatch = wsRequest.resource.match(/^\/watch\/([0-9]*)$/);
+  var playmatch = wsRequest.resource.match(/^\/play\//);
   if (watchmatch !== null) {
     if (watchmatch[1] && Number(watchmatch[1]) in sessions) {
       var tsession = sessions[Number(watchmatch[1])];
@@ -1076,7 +1171,10 @@
     else
       wsRequest.reject(404, errorcodes[7]);
   }
-  else if (wsRequest.resource == "/status") {
+  else if (playmatch !== null) {
+    wsStart(wsRequest);
+  }
+  else if (wsRequest.resourceURL.pathname == "/status") {
     var conn = wsRequest.accept(null, wsRequest.origin);
     var tell = function () {
       getStatus(function (info) {