diff --git a/rlgwebd.js b/rlgwebd.js index 83cadf0..fc9b017 100755 --- a/rlgwebd.js +++ b/rlgwebd.js @@ -69,35 +69,32 @@ var gamemux = new events.EventEmitter(); /* Constructor. A TermSession handles a pty and the game running on it. * gname: (String) Name of the game to launch. * pname: (String) The player's name. - * dims: (Array [Number, Number]) Height and width of the pty. - * handlers: (Object) Key-value pairs, event names and functions to - * install to handle them. + * wsReq: (WebSocketRequest) The request from the client. + * * Events: * "data": Data generated by child. Parameters: buf (Buffer) * "exit": Child terminated. Parameters: none */ -function TermSession(gname, pname, dims, handlers) { +function TermSession(gname, pname, wsReq) { var ss = this; /* Subclass EventEmitter to do the hard work. */ events.EventEmitter.call(this); - for (var evname in handlers) - this.on(evname, handlers[evname]); /* Don't launch anything that's not a real game. */ if (gname in games) { this.game = games[gname]; } else { this.failed = true; + wsReq.reject(404, errorcodes[2], "No such game"); + tslog("Game %s is not available", game); return; } this.pname = pname; - /* Grab a spot in the sessions table. */ - sessions[this.game.uname + "/" + this.pname] = this; /* Set up the sizes. */ - this.w = Math.floor(Number(dims[1])); + this.w = Math.floor(Number(wsReq.resourceURL.query.w)); if (!(this.w > 0 && this.w < 256)) this.w = 80; - this.h = Math.floor(Number(dims[0])); + this.h = Math.floor(Number(wsReq.resourceURL.query.h)); if (!(this.h > 0 && this.h < 256)) this.h = 24; /* Environment. */ @@ -111,6 +108,7 @@ function TermSession(gname, pname, dims, handlers) { this.term = pty.spawn(this.game.path, args, spawnopts); tslog("%s playing %s (pid %d)", this.pname, this.game.uname, this.term.pid); this.failed = false; + sessions[this.game.uname + "/" + this.pname] = this; gamemux.emit('begin', this.game.uname, this.pname); /* Set up the lockfile and ttyrec */ this.lasttime = new Date(); @@ -126,6 +124,7 @@ function TermSession(gname, pname, dims, handlers) { * with a complete screen. */ this.framebuf = new Buffer(1024); this.frameoff = 0; + this.playerconn = wsReq.accept(null, wsReq.origin); /* END setup */ function ttyrec_chunk(datastr) { ss.lasttime = new Date(); @@ -138,6 +137,10 @@ function TermSession(gname, pname, dims, handlers) { buf.copy(chunk, 12); 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. */ ss.emit('data', buf); } this.term.on("data", ttyrec_chunk); @@ -171,15 +174,39 @@ function TermSession(gname, pname, dims, handlers) { var tag = ss.tag(); fs.unlink(ss.lock); ss.record.end(); + if (ss.playerconn.connected) { + ss.playerconn.sendUTF(JSON.stringify({"t": "q"})); + ss.playerconn.close(); + } ss.emit('exit'); gamemux.emit('end', ss.game.uname, ss.pname); delete sessions[tag]; tslog("Game %s ended.", tag); }); this.close = function () { - if (this.tag() in sessions) - this.term.kill('SIGHUP'); + if (ss.tag() in sessions) + ss.term.kill('SIGHUP'); }; + /* Send initial data. */ + this.playerconn.sendUTF(JSON.stringify({"t": "s", "w": this.w, "h": this.h, + "p": this.pname, "g": this.game.uname})); + /* Attach handlers. */ + function messageH(message) { + var parsedMsg = getMsgWS(message); + if (parsedMsg.t == 'q') { + ss.close(); + } + else if (parsedMsg.t == 'd') { + var hexstr = parsedMsg.d.replace(/[^0-9a-f]/gi, ""); + if (hexstr.length % 2 != 0) { + hexstr = hexstr.slice(0, -1); + } + var keybuf = new Buffer(hexstr, "hex"); + ss.write(keybuf); + } + } + this.playerconn.on('message', messageH); + this.playerconn.on('close', this.close); } TermSession.prototype = new events.EventEmitter(); @@ -315,64 +342,7 @@ function wsWatcher(conn, session) { "d": session.framebuf.toString("hex", 0, session.frameoff)})); } -function wsPlay(wsReq, game, pname, dims) { - tslog("wsPlay: running for %s/%s", game, pname); - tslog("Request is for %s", logins[wsReq.resourceURL.query["key"]].name); - var conn; - var session; - /* Listeners on the WebSocket */ - function messageH(message) { - var parsedMsg = getMsgWS(message); - if (parsedMsg.t == 'q') { - session.close(); - } - else if (parsedMsg.t == 'd') { - var hexstr = parsedMsg.d.replace(/[^0-9a-f]/gi, ""); - if (hexstr.length % 2 != 0) { - hexstr = hexstr.slice(0, -1); - } - var keybuf = new Buffer(hexstr, "hex"); - session.write(keybuf); - } - } - function closeH() { - session.close(); - } - /* These listen on the TermSession. */ - function dataH(chunk) { - var msg = {}; - msg.t = "d"; - msg.d = chunk.toString("hex"); - conn.sendUTF(JSON.stringify(msg)); - } - function exitH() { - if (conn.connected) - conn.sendUTF(JSON.stringify({"t": "q"})); - conn.close(); - session.removeListener('data', dataH); - session.removeListener('exit', exitH); - } - var handlers = {'data': dataH, 'exit': exitH}; - session = new TermSession(game, pname, dims, handlers); - if (!session.failed) { - var tag = session.game.uname + "/" + session.pname; - var reply = {"t": "s", "tag": tag, "w": session.w, "h": session.h, - "p": session.pname, "g": session.game.uname}; - tslog("Accepting for %s", tag); - tslog("Request is for %s", logins[wsReq.resourceURL.query["key"]].name); - tslog("Session is for %s", session.pname); - conn = wsReq.accept(null, wsReq.origin); - conn.sendUTF(JSON.stringify(reply)); - conn.on('message', messageH); - conn.on('close', closeH); - } - else { - wsReq.reject(500, errorcodes[5]); - tslog("Unable to allocate TTY for %s", game); - } -} - -function wsStart(wsReq) { +function wsStartGame(wsReq) { var playmatch = wsReq.resourceURL.pathname.match(/^\/play\/([^\/]*)$/); if (!playmatch[1] || !(playmatch[1] in games)) { wsReq.reject(404, errorcodes[2]); @@ -393,14 +363,14 @@ function wsStart(wsReq) { return; } var pname = logins[lkey].name; - var dims = [wsReq.resourceURL.query.h, wsReq.resourceURL.query.w]; function progcallback(err, fname) { if (fname) { wsReq.reject(404, errorcodes[4]); tslog("%s is already playing %s", pname, gname); } - else - wsPlay(wsReq, gname, pname, dims); + else { + new TermSession(gname, pname, wsReq); + } }; checkprogress(pname, games[gname], progcallback, []); } @@ -1044,7 +1014,7 @@ function wsHandler(wsRequest) { tslog("Game %s is being watched via WebSockets", tsession.tag()); } else if (playmatch !== null) { - wsStart(wsRequest); + wsStartGame(wsRequest); } else if (wsRequest.resourceURL.pathname == "/status") { var conn = wsRequest.accept(null, wsRequest.origin);