Mercurial > hg > rlgwebd
comparison rlgwebd.js @ 171:671bed5039aa
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...
| author | John "Elwin" Edwards |
|---|---|
| date | Sat, 10 Jan 2015 18:54:55 -0500 |
| parents | 50e4c9feeac2 |
| children | dc12ba30d559 |
comparison
equal
deleted
inserted
replaced
| 170:50e4c9feeac2 | 171:671bed5039aa |
|---|---|
| 123 /* Holds the output since the last screen clear, so watchers can begin | 123 /* Holds the output since the last screen clear, so watchers can begin |
| 124 * with a complete screen. */ | 124 * with a complete screen. */ |
| 125 this.framebuf = new Buffer(1024); | 125 this.framebuf = new Buffer(1024); |
| 126 this.frameoff = 0; | 126 this.frameoff = 0; |
| 127 this.playerconn = wsReq.accept(null, wsReq.origin); | 127 this.playerconn = wsReq.accept(null, wsReq.origin); |
| 128 /* Array for watcher connections. */ | |
| 129 this.watchers = []; | |
| 128 /* END setup */ | 130 /* END setup */ |
| 129 function ttyrec_chunk(datastr) { | 131 function ttyrec_chunk(datastr) { |
| 130 ss.lasttime = new Date(); | 132 ss.lasttime = new Date(); |
| 131 var buf = new Buffer(datastr); | 133 var buf = new Buffer(datastr); |
| 132 var chunk = new Buffer(buf.length + 12); | 134 var chunk = new Buffer(buf.length + 12); |
| 136 chunk.writeUInt32LE(buf.length, 8); | 138 chunk.writeUInt32LE(buf.length, 8); |
| 137 buf.copy(chunk, 12); | 139 buf.copy(chunk, 12); |
| 138 ss.record.write(chunk); | 140 ss.record.write(chunk); |
| 139 ss.framepush(buf); | 141 ss.framepush(buf); |
| 140 /* Send to the player. */ | 142 /* Send to the player. */ |
| 141 var msg = {"t": "d", "d": buf.toString("hex")}; | 143 var msg = JSON.stringify({"t": "d", "d": buf.toString("hex")}); |
| 142 ss.playerconn.sendUTF(JSON.stringify(msg)); | 144 ss.playerconn.sendUTF(msg); |
| 143 /* For the benefit of watchers. */ | 145 /* Send to any watchers. */ |
| 146 for (var i = 0; i < ss.watchers.length; i++) { | |
| 147 if (ss.watchers[i].connected) | |
| 148 ss.watchers[i].sendUTF(msg); | |
| 149 } | |
| 144 ss.emit('data', buf); | 150 ss.emit('data', buf); |
| 145 } | 151 } |
| 146 this.term.on("data", ttyrec_chunk); | 152 this.term.on("data", ttyrec_chunk); |
| 147 this.framepush = function(chunk) { | 153 this.framepush = function(chunk) { |
| 148 /* If this chunk resets the screen, discard what preceded it. */ | 154 /* If this chunk resets the screen, discard what preceded it. */ |
| 172 // Teardown. | 178 // Teardown. |
| 173 this.term.on("exit", function () { | 179 this.term.on("exit", function () { |
| 174 var tag = ss.tag(); | 180 var tag = ss.tag(); |
| 175 fs.unlink(ss.lock); | 181 fs.unlink(ss.lock); |
| 176 ss.record.end(); | 182 ss.record.end(); |
| 183 var watchsocks = ss.watchers; | |
| 184 ss.watchers = []; | |
| 185 for (var i = 0; i < watchsocks.length; i++) { | |
| 186 if (watchsocks[i].connected) | |
| 187 watchsocks[i].close(); | |
| 188 } | |
| 177 if (ss.playerconn.connected) { | 189 if (ss.playerconn.connected) { |
| 178 ss.playerconn.sendUTF(JSON.stringify({"t": "q"})); | 190 ss.playerconn.sendUTF(JSON.stringify({"t": "q"})); |
| 179 ss.playerconn.close(); | 191 ss.playerconn.close(); |
| 180 } | 192 } |
| 181 ss.emit('exit'); | 193 ss.emit('exit'); |
| 205 ss.write(keybuf); | 217 ss.write(keybuf); |
| 206 } | 218 } |
| 207 } | 219 } |
| 208 this.playerconn.on('message', messageH); | 220 this.playerconn.on('message', messageH); |
| 209 this.playerconn.on('close', this.close); | 221 this.playerconn.on('close', this.close); |
| 222 /* To attach a watcher. */ | |
| 223 this.attach = function (wsReq) { | |
| 224 var conn = wsReq.accept(null, wsReq.origin); | |
| 225 conn.sendUTF(JSON.stringify({ | |
| 226 "t": "w", "w": this.w, "h": this.h, "p": this.pname, | |
| 227 "g": this.game.uname | |
| 228 })); | |
| 229 conn.sendUTF(JSON.stringify({"t": "d", | |
| 230 "d": this.framebuf.toString("hex", 0, this.frameoff)})); | |
| 231 conn.on('close', function () { | |
| 232 /* 'this' is the connection when triggered */ | |
| 233 var n = ss.watchers.indexOf(this); | |
| 234 if (n >= 0) { | |
| 235 ss.watchers.splice(n, 1); | |
| 236 tslog("A WebSocket watcher has left game %s", ss.tag()); | |
| 237 } | |
| 238 }); | |
| 239 this.watchers.push(conn); | |
| 240 }; | |
| 210 } | 241 } |
| 211 TermSession.prototype = new events.EventEmitter(); | 242 TermSession.prototype = new events.EventEmitter(); |
| 212 | 243 |
| 213 function DglSession(filename) { | 244 function DglSession(filename) { |
| 214 var ss = this; | 245 var ss = this; |
| 313 this.emit("close"); | 344 this.emit("close"); |
| 314 tslog("DGL %s: closed", ss.tag()); | 345 tslog("DGL %s: closed", ss.tag()); |
| 315 }; | 346 }; |
| 316 } | 347 } |
| 317 DglSession.prototype = new events.EventEmitter(); | 348 DglSession.prototype = new events.EventEmitter(); |
| 318 | |
| 319 // Also known as WebSocketAndTermSessionClosureGlueFactory | |
| 320 function wsWatcher(conn, session) { | |
| 321 var ss = this; // is this even needed? | |
| 322 var dataH = function(buf) { | |
| 323 conn.sendUTF(JSON.stringify({"t": "d", "d": buf.toString("hex")})); | |
| 324 }; | |
| 325 var exitH = function() { | |
| 326 if (conn.connected) | |
| 327 conn.close(); | |
| 328 } | |
| 329 session.on('data', dataH); | |
| 330 session.on('exit', exitH); | |
| 331 conn.on('close', function(code, desc) { | |
| 332 session.removeListener('data', dataH); | |
| 333 session.removeListener('exit', exitH); | |
| 334 if (session.tag() in sessions) | |
| 335 tslog("A WebSocket watcher has left game %s", session.tag()); | |
| 336 }); | |
| 337 conn.sendUTF(JSON.stringify({ | |
| 338 "t": "w", "w": session.w, "h": session.h, | |
| 339 "p": session.pname, "g": session.game.uname | |
| 340 })); | |
| 341 conn.sendUTF(JSON.stringify({"t": "d", | |
| 342 "d": session.framebuf.toString("hex", 0, session.frameoff)})); | |
| 343 } | |
| 344 | 349 |
| 345 function wsStartGame(wsReq) { | 350 function wsStartGame(wsReq) { |
| 346 var playmatch = wsReq.resourceURL.pathname.match(/^\/play\/([^\/]*)$/); | 351 var playmatch = wsReq.resourceURL.pathname.match(/^\/play\/([^\/]*)$/); |
| 347 if (!playmatch[1] || !(playmatch[1] in games)) { | 352 if (!playmatch[1] || !(playmatch[1] in games)) { |
| 348 wsReq.reject(404, errorcodes[2]); | 353 wsReq.reject(404, errorcodes[2]); |
| 1007 if (!(watchmatch[1] in sessions)) { | 1012 if (!(watchmatch[1] in sessions)) { |
| 1008 wsRequest.reject(404, errorcodes[7]); | 1013 wsRequest.reject(404, errorcodes[7]); |
| 1009 return; | 1014 return; |
| 1010 } | 1015 } |
| 1011 var tsession = sessions[watchmatch[1]]; | 1016 var tsession = sessions[watchmatch[1]]; |
| 1012 var conn = wsRequest.accept(null, wsRequest.origin); | 1017 tsession.attach(wsRequest); |
| 1013 new wsWatcher(conn, tsession); | |
| 1014 tslog("Game %s is being watched via WebSockets", tsession.tag()); | 1018 tslog("Game %s is being watched via WebSockets", tsession.tag()); |
| 1015 } | 1019 } |
| 1016 else if (playmatch !== null) { | 1020 else if (playmatch !== null) { |
| 1017 wsStartGame(wsRequest); | 1021 wsStartGame(wsRequest); |
| 1018 } | 1022 } |
