rob@77: rob@77: /** rob@77: * Module dependencies. rob@77: */ rob@77: rob@77: var parser = require('socket.io-parser'); rob@77: var debug = require('debug')('socket.io:client'); rob@77: rob@77: /** rob@77: * Module exports. rob@77: */ rob@77: rob@77: module.exports = Client; rob@77: rob@77: /** rob@77: * Client constructor. rob@77: * rob@77: * @param {Server} server instance rob@77: * @param {Socket} connection rob@77: * @api private rob@77: */ rob@77: rob@77: function Client(server, conn){ rob@77: this.server = server; rob@77: this.conn = conn; rob@77: this.encoder = new parser.Encoder(); rob@77: this.decoder = new parser.Decoder(); rob@77: this.id = conn.id; rob@77: this.request = conn.request; rob@77: this.setup(); rob@77: this.sockets = []; rob@77: this.nsps = {}; rob@77: this.connectBuffer = []; rob@77: } rob@77: rob@77: /** rob@77: * Sets up event listeners. rob@77: * rob@77: * @api private rob@77: */ rob@77: rob@77: Client.prototype.setup = function(){ rob@77: this.onclose = this.onclose.bind(this); rob@77: this.ondata = this.ondata.bind(this); rob@77: this.ondecoded = this.ondecoded.bind(this); rob@77: this.decoder.on('decoded', this.ondecoded); rob@77: this.conn.on('data', this.ondata); rob@77: this.conn.on('close', this.onclose); rob@77: }; rob@77: rob@77: /** rob@77: * Connects a client to a namespace. rob@77: * rob@77: * @param {String} namespace name rob@77: * @api private rob@77: */ rob@77: rob@77: Client.prototype.connect = function(name){ rob@77: debug('connecting to namespace %s', name); rob@77: var nsp = this.server.of(name); rob@77: if ('/' != name && !this.nsps['/']) { rob@77: this.connectBuffer.push(name); rob@77: return; rob@77: } rob@77: rob@77: var self = this; rob@77: var socket = nsp.add(this, function(){ rob@77: self.sockets.push(socket); rob@77: self.nsps[nsp.name] = socket; rob@77: rob@77: if ('/' == nsp.name && self.connectBuffer) { rob@77: self.connectBuffer.forEach(self.connect, self); rob@77: delete self.connectBuffer; rob@77: } rob@77: }); rob@77: }; rob@77: rob@77: /** rob@77: * Disconnects from all namespaces and closes transport. rob@77: * rob@77: * @api private rob@77: */ rob@77: rob@77: Client.prototype.disconnect = function(){ rob@77: var socket; rob@77: // we don't use a for loop because the length of rob@77: // `sockets` changes upon each iteration rob@77: while (socket = this.sockets.shift()) { rob@77: socket.disconnect(); rob@77: } rob@77: this.close(); rob@77: }; rob@77: rob@77: /** rob@77: * Removes a socket. Called by each `Socket`. rob@77: * rob@77: * @api private rob@77: */ rob@77: rob@77: Client.prototype.remove = function(socket){ rob@77: var i = this.sockets.indexOf(socket); rob@77: if (~i) { rob@77: var nsp = this.sockets[i].nsp.name; rob@77: this.sockets.splice(i, 1); rob@77: delete this.nsps[nsp]; rob@77: } else { rob@77: debug('ignoring remove for %s', socket.id); rob@77: } rob@77: }; rob@77: rob@77: /** rob@77: * Closes the underlying connection. rob@77: * rob@77: * @api private rob@77: */ rob@77: rob@77: Client.prototype.close = function(){ rob@77: if ('open' == this.conn.readyState) { rob@77: debug('forcing transport close'); rob@77: this.conn.close(); rob@77: this.onclose('forced server close'); rob@77: } rob@77: }; rob@77: rob@77: /** rob@77: * Writes a packet to the transport. rob@77: * rob@77: * @param {Object} packet object rob@77: * @param {Boolean} whether packet is already encoded rob@77: * @param {Boolean} whether packet is volatile rob@77: * @api private rob@77: */ rob@77: rob@77: Client.prototype.packet = function(packet, preEncoded, volatile){ rob@77: var self = this; rob@77: rob@77: // this writes to the actual connection rob@77: function writeToEngine(encodedPackets) { rob@77: if (volatile && !self.conn.transport.writable) return; rob@77: for (var i = 0; i < encodedPackets.length; i++) { rob@77: self.conn.write(encodedPackets[i]); rob@77: } rob@77: } rob@77: rob@77: if ('open' == this.conn.readyState) { rob@77: debug('writing packet %j', packet); rob@77: if(!preEncoded) { // not broadcasting, need to encode rob@77: this.encoder.encode(packet, function (encodedPackets) { // encode, then write results to engine rob@77: writeToEngine(encodedPackets); rob@77: }); rob@77: } else { // a broadcast pre-encodes a packet rob@77: writeToEngine(packet); rob@77: } rob@77: } else { rob@77: debug('ignoring packet write %j', packet); rob@77: } rob@77: }; rob@77: rob@77: /** rob@77: * Called with incoming transport data. rob@77: * rob@77: * @api private rob@77: */ rob@77: rob@77: Client.prototype.ondata = function(data){ rob@77: this.decoder.add(data); rob@77: }; rob@77: rob@77: /** rob@77: * Called when parser fully decodes a packet. rob@77: * rob@77: * @api private rob@77: */ rob@77: rob@77: Client.prototype.ondecoded = function(packet) { rob@77: if (parser.CONNECT == packet.type) { rob@77: this.connect(packet.nsp); rob@77: } else { rob@77: var socket = this.nsps[packet.nsp]; rob@77: if (socket) { rob@77: socket.onpacket(packet); rob@77: } else { rob@77: debug('no socket for namespace %s', packet.nsp); rob@77: } rob@77: } rob@77: }; rob@77: rob@77: /** rob@77: * Called upon transport close. rob@77: * rob@77: * @param {String} reason rob@77: * @api private rob@77: */ rob@77: rob@77: Client.prototype.onclose = function(reason){ rob@77: debug('client close with reason %s', reason); rob@77: rob@77: // ignore a potential subsequent `close` event rob@77: this.destroy(); rob@77: rob@77: // `nsps` and `sockets` are cleaned up seamlessly rob@77: var socket; rob@77: while (socket = this.sockets.shift()) { rob@77: socket.onclose(reason); rob@77: } rob@77: rob@77: this.decoder.destroy(); // clean up decoder rob@77: }; rob@77: rob@77: /** rob@77: * Cleans up event listeners. rob@77: * rob@77: * @api private rob@77: */ rob@77: rob@77: Client.prototype.destroy = function(){ rob@77: this.conn.removeListener('data', this.ondata); rob@77: this.conn.removeListener('close', this.onclose); rob@77: this.decoder.removeListener('decoded', this.ondecoded); rob@77: };