# HG changeset patch
# User John "Elwin" Edwards
# Date 1420934095 18000
# Node ID 671bed5039aa9dfa4fa3efb383a080496e8cd9e0
# Parent  50e4c9feeac2368d53a9ca08d8b9e108c698b430
RLGWebD: fix simultaneous watcher bugs.

WebSockets should now only receive the intended data, no matter how
many of them there are or what they are doing.  They should...

diff -r 50e4c9feeac2 -r 671bed5039aa rlgwebd.js
--- a/rlgwebd.js	Fri Jan 09 13:06:41 2015 -0500
+++ b/rlgwebd.js	Sat Jan 10 18:54:55 2015 -0500
@@ -125,6 +125,8 @@
   this.framebuf = new Buffer(1024);
   this.frameoff = 0;
   this.playerconn = wsReq.accept(null, wsReq.origin);
+  /* Array for watcher connections. */
+  this.watchers = [];
   /* END setup */
   function ttyrec_chunk(datastr) {
     ss.lasttime = new Date();
@@ -138,9 +140,13 @@
     ss.record.write(chunk);
     ss.framepush(buf);
     /* Send to the player. */
-    var msg = {"t": "d", "d": buf.toString("hex")};
-    ss.playerconn.sendUTF(JSON.stringify(msg));
-    /* For the benefit of watchers. */
+    var msg = JSON.stringify({"t": "d", "d": buf.toString("hex")});
+    ss.playerconn.sendUTF(msg);
+    /* Send to any watchers. */
+    for (var i = 0; i < ss.watchers.length; i++) {
+      if (ss.watchers[i].connected)
+        ss.watchers[i].sendUTF(msg);
+    }
     ss.emit('data', buf);
   }
   this.term.on("data", ttyrec_chunk);
@@ -174,6 +180,12 @@
     var tag = ss.tag();
     fs.unlink(ss.lock);
     ss.record.end();
+    var watchsocks = ss.watchers;
+    ss.watchers = [];
+    for (var i = 0; i < watchsocks.length; i++) {
+      if (watchsocks[i].connected)
+        watchsocks[i].close();
+    }
     if (ss.playerconn.connected) {
       ss.playerconn.sendUTF(JSON.stringify({"t": "q"}));
       ss.playerconn.close();
@@ -207,6 +219,25 @@
   }
   this.playerconn.on('message', messageH);
   this.playerconn.on('close', this.close);
+  /* To attach a watcher. */
+  this.attach = function (wsReq) {
+    var conn = wsReq.accept(null, wsReq.origin);
+    conn.sendUTF(JSON.stringify({
+            "t": "w", "w": this.w, "h": this.h, "p": this.pname, 
+            "g": this.game.uname
+    }));
+    conn.sendUTF(JSON.stringify({"t": "d",
+        "d": this.framebuf.toString("hex", 0, this.frameoff)}));
+    conn.on('close', function () {
+      /* 'this' is the connection when triggered */
+      var n = ss.watchers.indexOf(this);
+      if (n >= 0) {
+        ss.watchers.splice(n, 1);
+        tslog("A WebSocket watcher has left game %s", ss.tag());
+      }
+    });
+    this.watchers.push(conn);
+  };
 }
 TermSession.prototype = new events.EventEmitter();
 
@@ -316,32 +347,6 @@
 }
 DglSession.prototype = new events.EventEmitter();
 
-// Also known as WebSocketAndTermSessionClosureGlueFactory
-function wsWatcher(conn, session) {
-  var ss = this; // is this even needed?
-  var dataH = function(buf) {
-    conn.sendUTF(JSON.stringify({"t": "d", "d": buf.toString("hex")}));
-  };
-  var exitH = function() {
-    if (conn.connected)
-      conn.close();
-  }
-  session.on('data', dataH);
-  session.on('exit', exitH);
-  conn.on('close', function(code, desc) {
-    session.removeListener('data', dataH);
-    session.removeListener('exit', exitH);
-    if (session.tag() in sessions)
-      tslog("A WebSocket watcher has left game %s", session.tag());
-  });
-  conn.sendUTF(JSON.stringify({
-            "t": "w", "w": session.w, "h": session.h, 
-            "p": session.pname, "g": session.game.uname
-  }));
-  conn.sendUTF(JSON.stringify({"t": "d",
-        "d": session.framebuf.toString("hex", 0, session.frameoff)}));
-}
-
 function wsStartGame(wsReq) {
   var playmatch = wsReq.resourceURL.pathname.match(/^\/play\/([^\/]*)$/);
   if (!playmatch[1] || !(playmatch[1] in games)) {
@@ -1009,8 +1014,7 @@
       return;
     }
     var tsession = sessions[watchmatch[1]];
-    var conn = wsRequest.accept(null, wsRequest.origin);
-    new wsWatcher(conn, tsession);
+    tsession.attach(wsRequest);
     tslog("Game %s is being watched via WebSockets", tsession.tag());
   }
   else if (playmatch !== null) {