/** * Module dependencies. */ var parser = require('socket.io-parser') , Emitter = require('./emitter') , toArray = require('to-array') , debug = require('debug')('socket.io-client:socket') , on = require('./on') , bind; /** * Module exports. */ module.exports = exports = Socket; /** * Internal events (blacklisted). * These events can't be emitted by the user. */ var events = exports.events = [ 'connect', 'disconnect', 'error' ]; /** * Shortcut to `Emitter#emit`. */ var emit = Emitter.prototype.emit; /** * `Socket` constructor. * * @api public */ function Socket(io, nsp){ this.io = io; this.nsp = nsp; this.json = this; // compat this.ids = 0; this.acks = {}; this.open(); this.buffer = []; this.connected = false; } /** * Mix in `Emitter`. */ Emitter(Socket.prototype); /** * Called upon engine `open`. * * @api private */ Socket.prototype.open = Socket.prototype.connect = function(){ var io = this.io; io.open(); // ensure open if ('open' == this.io.readyState) this.onopen(); this.subs = [ on(io, 'open', bind(this, 'onopen')), on(io, 'error', bind(this, 'onerror')) ]; }; /** * Sends a `message` event. * * @return {Socket} self * @api public */ Socket.prototype.send = function(){ var args = toArray(arguments); args.unshift('message'); this.emit.apply(this, args); return this; }; /** * Override `emit`. * If the event is in `events`, it's emitted normally. * * @param {String} event name * @return {Socket} self * @api public */ Socket.prototype.emit = function(ev){ if (~events.indexOf(ev)) { emit.apply(this, arguments); } else { var args = toArray(arguments); var packet = { type: parser.EVENT, data: args }; // event ack callback if ('function' == typeof args[args.length - 1]) { debug('emitting packet with ack id %d', this.ids); this.acks[this.ids] = args.pop(); packet.id = this.ids++; } this.packet(packet); } return this; }; /** * Sends a packet. * * @param {Object} packet * @api private */ Socket.prototype.packet = function(packet){ packet.nsp = this.nsp; this.io.packet(packet); }; /** * Called upon `error`. * * @param {Object} data * @api private */ Socket.prototype.onerror = function(data){ this.emit('error', data); }; /** * "Opens" the socket. * * @api private */ Socket.prototype.onopen = function(){ debug('transport is open - connecting'); // write connect packet if necessary if ('/' != this.nsp) { this.packet({ type: parser.CONNECT }); } // subscribe var io = this.io; this.subs.push( on(io, 'packet', bind(this, 'onpacket')), on(io, 'close', bind(this, 'onclose')) ); }; /** * Called upon engine `close`. * * @param {String} reason * @api private */ Socket.prototype.onclose = function(reason){ debug('close (%s)', reason); this.emit('disconnect', reason); }; /** * Called with socket packet. * * @param {Object} packet * @api private */ Socket.prototype.onpacket = function(packet){ if (packet.nsp != this.nsp) return; switch (packet.type) { case parser.CONNECT: this.onconnect(); break; case parser.EVENT: this.onevent(packet); break; case parser.ACK: this.onack(packet); break; case parser.DISCONNECT: this.ondisconnect(); break; case parser.ERROR: this.emit('error', packet.data); break; } }; /** * Called upon a server event. * * @param {Object} packet * @api private */ Socket.prototype.onevent = function(packet){ var args = packet.data || []; debug('emitting event %j', args); if (packet.id) { debug('attaching ack callback to event'); args.push(this.ack(packet.id)); } if (this.connected) { emit.apply(this, args); } else { this.buffer.push(args); } }; /** * Produces an ack callback to emit with an event. * * @api private */ Socket.prototype.ack = function(id){ var self = this; var sent = false; return function(){ // prevent double callbacks if (sent) return; var args = toArray(arguments); debug('sending ack %j', args); self.packet({ type: parser.ACK, id: id, data: args }); }; }; /** * Called upon a server acknowlegement. * * @param {Object} packet * @api private */ Socket.prototype.onack = function(packet){ debug('calling ack %s with %j', packet.id, packet.data); var fn = this.acks[packet.id]; fn.apply(this, packet.data); delete this.acks[packet.id]; }; /** * Called upon server connect. * * @api private */ Socket.prototype.onconnect = function(){ this.emit('connect'); this.connected = true; this.emitBuffered(); }; /** * Emit buffered events. * * @api private */ Socket.prototype.emitBuffered = function(){ for (var i = 0; i < this.buffer.length; i++) { emit.apply(this, this.buffer[i]); } this.buffer = []; }; /** * Called upon server disconnect. * * @api private */ Socket.prototype.ondisconnect = function(){ debug('server disconnect (%s)', this.nsp); this.destroy(); }; /** * Cleans up. * * @api private */ Socket.prototype.destroy = function(){ debug('destroying socket (%s)', this.nsp); // manual close means no reconnect for (var i = 0; i < this.subs.length; i++) { this.subs[i].destroy(); } // notify manager this.io.destroy(this); }; /** * Disconnects the socket manually. * * @return {Socket} self * @api public */ Socket.prototype.close = Socket.prototype.disconnect = function(){ debug('performing disconnect (%s)', this.nsp); this.packet(parser.PACKET_DISCONNECT); // manual close means no reconnect for (var i = 0; i < this.subs.length; i++) { this.subs[i].destroy(); } // notify manager this.io.destroy(this); // fire events this.onclose('io client disconnect'); return this; }; /** * Load `bind`. */ try { bind = require('bind'); } catch(e){ bind = require('bind-component'); }