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 }