comparison rlgwebd.js @ 100:3dbfdaf62623

RLG-Web: begin converting to WebSockets. Use WebSockets for watching, if the browser supports it. Functionality is not complete yet.
author John "Elwin" Edwards <elwin@sdf.org>
date Thu, 12 Jul 2012 22:16:15 -0700
parents 597e9477b8ae
children e59d68082664
comparison
equal deleted inserted replaced
99:2880cc4372bd 100:3dbfdaf62623
9 var fs = require('fs'); 9 var fs = require('fs');
10 var events = require('events'); 10 var events = require('events');
11 var child_process = require('child_process'); 11 var child_process = require('child_process');
12 var daemon = require(path.join(localModules, "daemon")); 12 var daemon = require(path.join(localModules, "daemon"));
13 var pty = require(path.join(localModules, "pty.js")); 13 var pty = require(path.join(localModules, "pty.js"));
14 var WebSocketServer = require(path.join(localModules, "websocket")).server;
14 15
15 /* Configuration variables */ 16 /* Configuration variables */
16 // These first two files are NOT in the chroot. 17 // These first two files are NOT in the chroot.
17 var ctlsocket = "/var/local/rlgwebd/ctl"; 18 var ctlsocket = "/var/local/rlgwebd/ctl";
18 var logfile = "/var/local/rlgwebd/log"; 19 var logfile = "/var/local/rlgwebd/log";
128 this.record = fs.createWriteStream(ttyrec, { mode: 0664 }); 129 this.record = fs.createWriteStream(ttyrec, { mode: 0664 });
129 /* Holds the output since the last screen clear, so watchers can begin 130 /* Holds the output since the last screen clear, so watchers can begin
130 * with a complete screen. */ 131 * with a complete screen. */
131 this.framebuf = new Buffer(1024); 132 this.framebuf = new Buffer(1024);
132 this.frameoff = 0; 133 this.frameoff = 0;
134 /* List of WebSockets watching the game. */
135 this.watchsocks = [];
133 logins[lkey].sessions.push(this.sessid); 136 logins[lkey].sessions.push(this.sessid);
134 /* END setup */ 137 /* END setup */
135 function ttyrec_chunk(datastr) { 138 function ttyrec_chunk(datastr) {
136 var ts = new Date(); 139 var ts = new Date();
137 var buf = new Buffer(datastr); 140 var buf = new Buffer(datastr);
166 this.frameoff += chunk.length; 169 this.frameoff += chunk.length;
167 }; 170 };
168 this.write = function(data) { 171 this.write = function(data) {
169 this.term.write(data); 172 this.term.write(data);
170 }; 173 };
174 // WebSocket watchers.
175 this.addWS = function (conn) {
176 this.watchsocks.push(conn);
177 };
178 this.removeWS = function (conn) {
179 var i = this.watchsocks.indexOf(conn);
180 if (i >= 0) {
181 if (conn.connected)
182 conn.close();
183 this.watchsocks.splice(i, 1);
184 return true;
185 }
186 else
187 return false;
188 };
189 // Teardown.
171 this.term.on("exit", function () { 190 this.term.on("exit", function () {
172 fs.unlink(ss.lock); 191 fs.unlink(ss.lock);
173 ss.record.end(); 192 ss.record.end();
174 ss.emit('exit'); 193 ss.emit('exit');
175 var id = ss.sessid; 194 var id = ss.sessid;
350 ss.alive = false; 369 ss.alive = false;
351 ss.sendQ.push({"t": "q"}); 370 ss.sendQ.push({"t": "q"});
352 } 371 }
353 var handlers = {'open': openH, 'data': dataH, 'exit': exitH}; 372 var handlers = {'open': openH, 'data': dataH, 'exit': exitH};
354 this.session = new TermSession(gamename, lkey, dims, handlers); 373 this.session = new TermSession(gamename, lkey, dims, handlers);
374 }
375
376 // Also known as WebSocketAndTermSessionClosureGlueFactory
377 function wsWatcher(conn, session) {
378 var ss = this; // is this even needed?
379 var dataH = function(buf) {
380 conn.sendUTF(JSON.stringify({"t": "d", "d": buf.toString("hex")}));
381 };
382 var exitH = function() {
383 if (conn.connected)
384 conn.close();
385 }
386 session.on('data', dataH);
387 session.on('exit', exitH);
388 conn.on('close', function(code, desc) {
389 session.removeListener('data', dataH);
390 session.removeListener('exit', exitH);
391 tslog("A WebSocket watcher has left game %d", session.sessid);
392 });
393 conn.sendUTF(JSON.stringify({"t": "d",
394 "d": session.framebuf.toString("hex", 0, session.frameoff)}));
355 } 395 }
356 396
357 /* Some functions which check whether a player is currently playing or 397 /* Some functions which check whether a player is currently playing or
358 * has a saved game. Maybe someday they will provide information on 398 * has a saved game. Maybe someday they will provide information on
359 * the game. */ 399 * the game. */
1027 } 1067 }
1028 req.on('end', respond); 1068 req.on('end', respond);
1029 1069
1030 } 1070 }
1031 1071
1072 function wsHandler(wsRequest) {
1073 var urlmatch = wsRequest.resource.match(/^\/watch\/([0-9]*)$/);
1074 if (urlmatch !== null && urlmatch[1] && Number(urlmatch[1]) in sessions) {
1075 var tsession = sessions[Number(urlmatch[1])];
1076 var conn = wsRequest.accept(null, wsRequest.origin);
1077 new wsWatcher(conn, tsession);
1078 tslog("Game %d is being watched via WebSockets", tsession.sessid);
1079 }
1080 else
1081 wsRequest.reject(404, errorcodes[7]);
1082 }
1083
1032 function shutdown () { 1084 function shutdown () {
1033 httpServer.close(); 1085 httpServer.close();
1034 httpServer.removeAllListeners('request'); 1086 httpServer.removeAllListeners('request');
1035 ctlServer.close(); 1087 ctlServer.close();
1036 tslog("Shutting down..."); 1088 tslog("Shutting down...");
1064 tslog("Not running as root, cannot chroot."); 1116 tslog("Not running as root, cannot chroot.");
1065 process.exit(1); 1117 process.exit(1);
1066 } 1118 }
1067 1119
1068 var httpServer; // declare here so shutdown() can find it 1120 var httpServer; // declare here so shutdown() can find it
1121 var wsServer;
1069 1122
1070 /* This could be nonblocking, but nothing else can start yet anyway. */ 1123 /* This could be nonblocking, but nothing else can start yet anyway. */
1071 if (fs.existsSync(ctlsocket)) { 1124 if (fs.existsSync(ctlsocket)) {
1072 fs.unlinkSync(ctlsocket); 1125 fs.unlinkSync(ctlsocket);
1073 } 1126 }
1104 } 1157 }
1105 httpServer = http.createServer(webHandler); 1158 httpServer = http.createServer(webHandler);
1106 httpServer.listen(httpPort); 1159 httpServer.listen(httpPort);
1107 tslog('rlgwebd running on port %d', httpPort); 1160 tslog('rlgwebd running on port %d', httpPort);
1108 setInterval(reaper, playtimeout / 4); 1161 setInterval(reaper, playtimeout / 4);
1162 wsServer = new WebSocketServer({"httpServer": httpServer});
1163 wsServer.on("request", wsHandler);
1164 tslog('WebSockets are online');
1109 }); 1165 });
1110 1166