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 } |