rc-web@69: rc-web@69: /** rc-web@69: * Module dependencies. rc-web@69: */ rc-web@69: rob@76: var Emitter = require('events').EventEmitter; rob@76: var parser = require('socket.io-parser'); rob@76: var url = require('url'); rob@76: var debug = require('debug')('socket.io:socket'); rob@76: var hasBin = require('has-binary-data'); rc-web@69: rc-web@69: /** rob@76: * Module exports. rc-web@69: */ rc-web@69: rob@76: module.exports = exports = Socket; rc-web@69: rc-web@69: /** rob@76: * Blacklisted events. rc-web@69: * rc-web@69: * @api public rc-web@69: */ rc-web@69: rob@76: exports.events = [ rob@76: 'error', rob@76: 'connect', rob@76: 'disconnect', rob@76: 'newListener', rob@76: 'removeListener' rob@76: ]; rc-web@69: rc-web@69: /** rob@76: * Flags. rc-web@69: * rc-web@69: * @api private rc-web@69: */ rc-web@69: rob@76: var flags = [ rob@76: 'json', rob@76: 'volatile', rob@76: 'broadcast' rob@76: ]; rob@76: rob@76: /** rob@76: * `EventEmitter#emit` reference. rob@76: */ rob@76: rob@76: var emit = Emitter.prototype.emit; rob@76: rob@76: /** rob@76: * Interface to a `Client` for a given `Namespace`. rob@76: * rob@76: * @param {Namespace} nsp rob@76: * @param {Client} client rob@76: * @api public rob@76: */ rob@76: rob@76: function Socket(nsp, client){ rob@76: this.nsp = nsp; rob@76: this.server = nsp.server; rob@76: this.adapter = this.nsp.adapter; rob@76: this.id = client.id; rob@76: this.request = client.request; rob@76: this.client = client; rob@76: this.conn = client.conn; rob@76: this.rooms = []; rob@76: this.acks = {}; rob@76: this.connected = true; rob@76: this.disconnected = false; rob@76: this.handshake = this.buildHandshake(); rob@76: } rob@76: rob@76: /** rob@76: * Inherits from `EventEmitter`. rob@76: */ rob@76: rob@76: Socket.prototype.__proto__ = Emitter.prototype; rob@76: rob@76: /** rob@76: * Apply flags from `Socket`. rob@76: */ rob@76: rob@76: flags.forEach(function(flag){ rob@76: Socket.prototype.__defineGetter__(flag, function(){ rob@76: this.flags = this.flags || {}; rob@76: this.flags[flag] = true; rob@76: return this; rob@76: }); rc-web@69: }); rc-web@69: rc-web@69: /** rob@76: * `request` engine.io shorcut. rob@76: * rob@76: * @api public rob@76: */ rob@76: rob@76: Socket.prototype.__defineGetter__('request', function(){ rob@76: return this.conn.request; rob@76: }); rob@76: rob@76: /** rob@76: * Builds the `handshake` BC object rc-web@69: * rc-web@69: * @api private rc-web@69: */ rc-web@69: rob@76: Socket.prototype.buildHandshake = function(){ rob@76: return { rob@76: headers: this.request.headers, rob@76: time: (new Date) + '', rob@76: address: this.request.connection.address(), rob@76: xdomain: !!this.request.headers.origin, rob@76: secure: !!this.request.connection.encrypted, rob@76: issued: +(new Date), rob@76: url: this.request.url, rob@76: query: url.parse(this.request.url, true).query || {} rob@76: }; rob@76: }; rc-web@69: rc-web@69: /** rob@76: * Emits to this client. rob@76: * rob@76: * @return {Socket} self rob@76: * @api public rob@76: */ rob@76: rob@76: Socket.prototype.emit = function(ev){ rob@76: if (~exports.events.indexOf(ev)) { rob@76: emit.apply(this, arguments); rob@76: } else { rob@76: var args = Array.prototype.slice.call(arguments); rob@76: var packet = {}; rob@76: packet.type = hasBin(args) ? parser.BINARY_EVENT : parser.EVENT; rob@76: packet.data = args; rob@76: rob@76: // access last argument to see if it's an ACK callback rob@76: if ('function' == typeof args[args.length - 1]) { rob@76: if (this._rooms || (this.flags && this.flags.broadcast)) { rob@76: throw new Error('Callbacks are not supported when broadcasting'); rob@76: } rob@76: rob@76: debug('emitting packet with ack id %d', this.nsp.ids); rob@76: this.acks[this.nsp.ids] = args.pop(); rob@76: packet.id = this.nsp.ids++; rob@76: } rob@76: rob@76: if (this._rooms || (this.flags && this.flags.broadcast)) { rob@76: this.adapter.broadcast(packet, { rob@76: except: [this.id], rob@76: rooms: this._rooms, rob@76: flags: this.flags rob@76: }); rob@76: } else { rob@76: // dispatch packet rob@76: this.packet(packet); rob@76: } rob@76: rob@76: // reset flags rob@76: delete this._rooms; rob@76: delete this.flags; rob@76: } rob@76: return this; rob@76: }; rob@76: rob@76: /** rob@76: * Targets a room when broadcasting. rob@76: * rob@76: * @param {String} name rob@76: * @return {Socket} self rob@76: * @api public rob@76: */ rob@76: rob@76: Socket.prototype.to = rob@76: Socket.prototype.in = function(name){ rob@76: this._rooms = this._rooms || []; rob@76: if (!~this._rooms.indexOf(name)) this._rooms.push(name); rob@76: return this; rob@76: }; rob@76: rob@76: /** rob@76: * Sends a `message` event. rob@76: * rob@76: * @return {Socket} self rob@76: * @api public rob@76: */ rob@76: rob@76: Socket.prototype.send = rob@76: Socket.prototype.write = function(){ rob@76: var args = Array.prototype.slice.call(arguments); rob@76: args.unshift('message'); rob@76: this.emit.apply(this, args); rob@76: return this; rob@76: }; rob@76: rob@76: /** rob@76: * Writes a packet. rob@76: * rob@76: * @param {Object} packet object rob@76: * @api private rob@76: */ rob@76: rob@76: Socket.prototype.packet = function(packet, preEncoded){ rob@76: packet.nsp = this.nsp.name; rob@76: var volatile = this.flags && this.flags.volatile; rob@76: this.client.packet(packet, preEncoded, volatile); rob@76: }; rob@76: rob@76: /** rob@76: * Joins a room. rob@76: * rob@76: * @param {String} room rob@76: * @param {Function} optional, callback rob@76: * @return {Socket} self rob@76: * @api private rob@76: */ rob@76: rob@76: Socket.prototype.join = function(room, fn){ rob@76: debug('joining room %s', room); rob@76: var self = this; rob@76: if (~this.rooms.indexOf(room)) return this; rob@76: this.adapter.add(this.id, room, function(err){ rob@76: if (err) return fn && fn(err); rob@76: debug('joined room %s', room); rob@76: self.rooms.push(room); rob@76: fn && fn(null); rob@76: }); rob@76: return this; rob@76: }; rob@76: rob@76: /** rob@76: * Leaves a room. rob@76: * rob@76: * @param {String} room rob@76: * @param {Function} optional, callback rob@76: * @return {Socket} self rob@76: * @api private rob@76: */ rob@76: rob@76: Socket.prototype.leave = function(room, fn){ rob@76: debug('leave room %s', room); rob@76: var self = this; rob@76: this.adapter.del(this.id, room, function(err){ rob@76: if (err) return fn && fn(err); rob@76: debug('left room %s', room); rob@76: self.rooms.splice(self.rooms.indexOf(room, 1)); rob@76: fn && fn(null); rob@76: }); rob@76: return this; rob@76: }; rob@76: rob@76: /** rob@76: * Leave all rooms. rc-web@69: * rc-web@69: * @api private rc-web@69: */ rc-web@69: rob@76: Socket.prototype.leaveAll = function(){ rob@76: this.adapter.delAll(this.id); rc-web@69: }; rc-web@69: rc-web@69: /** rob@76: * Called by `Namespace` upon succesful rob@76: * middleware execution (ie: authorization). rc-web@69: * rc-web@69: * @api private rc-web@69: */ rc-web@69: rob@76: Socket.prototype.onconnect = function(){ rob@76: debug('socket connected - writing packet'); rob@76: this.join(this.id); rob@76: this.packet({ type: parser.CONNECT }); rob@76: this.nsp.connected[this.id] = this; rc-web@69: }; rc-web@69: rc-web@69: /** rob@76: * Called with each packet. Called by `Client`. rob@76: * rob@76: * @param {Object} packet rob@76: * @api private rob@76: */ rob@76: rob@76: Socket.prototype.onpacket = function(packet){ rob@76: debug('got packet %j', packet); rob@76: switch (packet.type) { rob@76: case parser.EVENT: rob@76: this.onevent(packet); rob@76: break; rob@76: rob@76: case parser.BINARY_EVENT: rob@76: this.onevent(packet); rob@76: break; rob@76: rob@76: case parser.ACK: rob@76: this.onack(packet); rob@76: break; rob@76: rob@76: case parser.BINARY_ACK: rob@76: this.onack(packet); rob@76: break; rob@76: rob@76: case parser.DISCONNECT: rob@76: this.ondisconnect(); rob@76: break; rob@76: rob@76: case parser.ERROR: rob@76: this.emit('error', packet.data); rob@76: } rob@76: }; rob@76: rob@76: /** rob@76: * Called upon event packet. rob@76: * rob@76: * @param {Object} packet object rob@76: * @api private rob@76: */ rob@76: rob@76: Socket.prototype.onevent = function(packet){ rob@76: var args = packet.data || []; rob@76: debug('emitting event %j', args); rob@76: rob@76: if (null != packet.id) { rob@76: debug('attaching ack callback to event'); rob@76: args.push(this.ack(packet.id)); rob@76: } rob@76: rob@76: emit.apply(this, args); rob@76: }; rob@76: rob@76: /** rob@76: * Produces an ack callback to emit with an event. rob@76: * rob@76: * @param {Number} packet id rob@76: * @api private rob@76: */ rob@76: rob@76: Socket.prototype.ack = function(id){ rob@76: var self = this; rob@76: var sent = false; rob@76: return function(){ rob@76: // prevent double callbacks rob@76: if (sent) return; rob@76: var args = Array.prototype.slice.call(arguments); rob@76: debug('sending ack %j', args); rob@76: rob@76: var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK; rob@76: self.packet({ rob@76: id: id, rob@76: type: type, rob@76: data: args rob@76: }); rob@76: }; rob@76: }; rob@76: rob@76: /** rob@76: * Called upon ack packet. rc-web@69: * rc-web@69: * @api private rc-web@69: */ rc-web@69: rob@76: Socket.prototype.onack = function(packet){ rob@76: var ack = this.acks[packet.id]; rob@76: if ('function' == typeof ack) { rob@76: debug('calling ack %s with %j', packet.id, packet.data); rob@76: ack.apply(this, packet.data); rob@76: delete this.acks[packet.id]; rob@76: } else { rob@76: debug('bad ack %s', packet.id); rc-web@69: } rc-web@69: }; rc-web@69: rc-web@69: /** rob@76: * Called upon client disconnect packet. rc-web@69: * rc-web@69: * @api private rc-web@69: */ rc-web@69: rob@76: Socket.prototype.ondisconnect = function(){ rob@76: debug('got disconnect packet'); rob@76: this.onclose('client namespace disconnect'); rc-web@69: }; rc-web@69: rc-web@69: /** rob@76: * Called upon closing. Called by `Client`. rc-web@69: * rob@76: * @param {String} reason rc-web@69: * @api private rc-web@69: */ rc-web@69: rob@76: Socket.prototype.onclose = function(reason){ rob@76: if (!this.connected) return this; rob@76: debug('closing socket - reason %s', reason); rob@76: this.leaveAll(); rob@76: this.nsp.remove(this); rob@76: this.client.remove(this); rob@76: this.connected = false; rob@76: this.disconnected = true; rob@76: delete this.nsp.connected[this.id]; rob@76: this.emit('disconnect', reason); rc-web@69: }; rc-web@69: rc-web@69: /** rob@76: * Produces an `error` packet. rc-web@69: * rob@76: * @param {Object} error object rob@76: * @api private rob@76: */ rob@76: rob@76: Socket.prototype.error = function(err){ rob@76: this.packet({ type: parser.ERROR, data: err }); rob@76: }; rob@76: rob@76: /** rob@76: * Disconnects this client. rob@76: * rob@76: * @param {Boolean} if `true`, closes the underlying connection rob@76: * @return {Socket} self rc-web@69: * @api public rc-web@69: */ rc-web@69: rob@76: Socket.prototype.disconnect = function(close){ rob@76: if (!this.connected) return this; rob@76: if (close) { rob@76: this.client.disconnect(); rob@76: } else { rob@76: this.packet({ type: parser.DISCONNECT }); rob@76: this.onclose('server namespace disconnect'); rob@76: } rc-web@69: return this; rc-web@69: };