text: /**
   * Module dependencies.
   */
  
  var eio = require('engine.io-client');
  var Socket = require('./socket');
  var Emitter = require('component-emitter');
  var parser = require('socket.io-parser');
  var on = require('./on');
  var bind = require('component-bind');
  var debug = require('debug')('socket.io-client:manager');
  var indexOf = require('indexof');
  var Backoff = require('backo2');
  
  /**
   * IE6+ hasOwnProperty
   */
  
  var has = Object.prototype.hasOwnProperty;
  
  /**
   * Module exports
   */
  
  module.exports = Manager;
  
  /**
   * `Manager` constructor.
   *
   * @param {String} engine instance or engine uri/opts
   * @param {Object} options
   * @api public
   */
  
  function Manager(uri, opts) {
    if (!(this instanceof Manager)) return new Manager(uri, opts);
    if (uri && 'object' === typeof uri) {
      opts = uri;
      uri = undefined;
    }
    opts = opts || {};
  
    opts.path = opts.path || '/socket.io';
    this.nsps = {};
    this.subs = [];
    this.opts = opts;
    this.reconnection(opts.reconnection !== false);
    this.reconnectionAttempts(opts.reconnectionAttempts || Infinity);
    this.reconnectionDelay(opts.reconnectionDelay || 1000);
    this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000);
    this.randomizationFactor(opts.randomizationFactor || 0.5);
    this.backoff = new Backoff({
      min: this.reconnectionDelay(),
      max: this.reconnectionDelayMax(),
      jitter: this.randomizationFactor(),
    });
    this.timeout(null == opts.timeout ? 20000 : opts.timeout);
    this.readyState = 'closed';
    this.uri = uri;
    this.connecting = [];
    this.lastPing = null;
    this.encoding = false;
    this.packetBuffer = [];
    this.encoder = new parser.Encoder();
    this.decoder = new parser.Decoder();
    this.autoConnect = opts.autoConnect !== false;
    if (this.autoConnect) this.open();
  }
  
  /**
   * Propagate given event to sockets and emit on `this`
   *
   * @api private
   */
  
  Manager.prototype.emitAll = function () {
    this.emit.apply(this, arguments);
    for (var nsp in this.nsps) {
      if (has.call(this.nsps, nsp)) {
        this.nsps[nsp].emit.apply(this.nsps[nsp], arguments);
      }
    }
  };
  
  /**
   * Update `socket.id` of all sockets
   *
   * @api private
   */
  
  Manager.prototype.updateSocketIds = function () {
    for (var nsp in this.nsps) {
      if (has.call(this.nsps, nsp)) {
        this.nsps[nsp].id = this.engine.id;
      }
    }
  };
  
  /**
   * Mix in `Emitter`.
   */
  
  Emitter(Manager.prototype);
  
  /**
   * Sets the `reconnection` config.
   *
   * @param {Boolean} true/false if it should automatically reconnect
   * @return {Manager} self or value
   * @api public
   */
  
  Manager.prototype.reconnection = function (v) {
    if (!arguments.length) return this._reconnection;
    this._reconnection = !!v;
    return this;
  };
  
  /**
   * Sets the reconnection attempts config.
   *
   * @param {Number} max reconnection attempts before giving up
   * @return {Manager} self or value
   * @api public
   */
  
  Manager.prototype.reconnectionAttempts = function (v) {
    if (!arguments.length) return this._reconnectionAttempts;
    this._reconnectionAttempts = v;
    return this;
  };
  
  /**
   * Sets the delay between reconnections.
   *
   * @param {Number} delay
   * @return {Manager} self or value
   * @api public
   */
  
  Manager.prototype.reconnectionDelay = function (v) {
    if (!arguments.length) return this._reconnectionDelay;
    this._reconnectionDelay = v;
    this.backoff && this.backoff.setMin(v);
    return this;
  };
  
  Manager.prototype.randomizationFactor = function (v) {
    if (!arguments.length) return this._randomizationFactor;
    this._randomizationFactor = v;
    this.backoff && this.backoff.setJitter(v);
    return this;
  };
  
  /**
   * Sets the maximum delay between reconnections.
   *
   * @param {Number} delay
   * @return {Manager} self or value
   * @api public
   */
  
  Manager.prototype.reconnectionDelayMax = function (v) {
    if (!arguments.length) return this._reconnectionDelayMax;
    this._reconnectionDelayMax = v;
    this.backoff && this.backoff.setMax(v);
    return this;
  };
  
  /**
   * Sets the connection timeout. `false` to disable
   *
   * @return {Manager} self or value
   * @api public
   */
  
  Manager.prototype.timeout = function (v) {
    if (!arguments.length) return this._timeout;
    this._timeout = v;
    return this;
  };
  
  /**
   * Starts trying to reconnect if reconnection is enabled and we have not
   * started reconnecting yet
   *
   * @api private
   */
  
  Manager.prototype.maybeReconnectOnOpen = function () {
    // Only try to reconnect if it's the first time we're connecting
    if (!this.reconnecting && this._reconnection && this.backoff.attempts === 0) {
      // keeps reconnection from firing twice for the same reconnection loop
      this.reconnect();
    }
  };
  
  /**
   * Sets the current transport `socket`.
   *
   * @param {Function} optional, callback
   * @return {Manager} self
   * @api public
   */
  
  Manager.prototype.open = Manager.prototype.connect = function (fn, opts) {
    debug('readyState %s', this.readyState);
    if (~this.readyState.indexOf('open')) return this;
  
    debug('opening %s', this.uri);
    this.engine = eio(this.uri, this.opts);
    var socket = this.engine;
    var self = this;
    this.readyState = 'opening';
    this.skipReconnect = false;
  
    // emit `open`
    var openSub = on(socket, 'open', function () {
      self.onopen();
      fn && fn();
    });
  
    // emit `connect_error`
    var errorSub = on(socket, 'error', function (data) {
      debug('connect_error');
      self.cleanup();
      self.readyState = 'closed';
      self.emitAll('connect_error', data);
      if (fn) {
        var err = new Error('Connection error');
        err.data = data;
        fn(err);
      } else {
        // Only do this if there is no fn to handle the error
        self.maybeReconnectOnOpen();
      }
    });
  
    // emit `connect_timeout`
    if (false !== this._timeout) {
      var timeout = this._timeout;
      debug('connect attempt will timeout after %d', timeout);
  
      // set timer
      var timer = setTimeout(function () {
        debug('connect attempt timed out after %d', timeout);
        openSub.destroy();
        socket.close();
        socket.emit('error', 'timeout');
        self.emitAll('connect_timeout', timeout);
      }, timeout);
  
      this.subs.push({
        destroy: function () {
          clearTimeout(timer);
        },
      });
    }
  
    this.subs.push(openSub);
    this.subs.push(errorSub);
  
    return this;
  };
  
  /**
   * Called upon transport open.
   *
   * @api private
   */
  
  Manager.prototype.onopen = function () {
    debug('open');
  
    // clear old subs
    this.cleanup();
  
    // mark as open
    this.readyState = 'open';
    this.emit('open');
  
    // add new subs
    var socket = this.engine;
    this.subs.push(on(socket, 'data', bind(this, 'ondata')));
    this.subs.push(on(socket, 'ping', bind(this, 'onping')));
    this.subs.push(on(socket, 'pong', bind(this, 'onpong')));
    this.subs.push(on(socket, 'error', bind(this, 'onerror')));
    this.subs.push(on(socket, 'close', bind(this, 'onclose')));
    this.subs.push(on(this.decoder, 'decoded', bind(this, 'ondecoded')));
  };
  
  /**
   * Called upon a ping.
   *
   * @api private
   */
  
  Manager.prototype.onping = function () {
    this.lastPing = new Date();
    this.emitAll('ping');
  };
  
  /**
   * Called upon a packet.
   *
   * @api private
   */
  
  Manager.prototype.onpong = function () {
    this.emitAll('pong', new Date() - this.lastPing);
  };
  
  /**
   * Called with data.
   *
   * @api private
   */
  
  Manager.prototype.ondata = function (data) {
    this.decoder.add(data);
  };
  
  /**
   * Called when parser fully decodes a packet.
   *
   * @api private
   */
  
  Manager.prototype.ondecoded = function (packet) {
    this.emit('packet', packet);
  };
  
  /**
   * Called upon socket error.
   *
   * @api private
   */
  
  Manager.prototype.onerror = function (err) {
    debug('error', err);
    this.emitAll('error', err);
  };
  
  /**
   * Creates a new socket for the given `nsp`.
   *
   * @return {Socket}
   * @api public
   */
  
  Manager.prototype.socket = function (nsp, opts) {
    var socket = this.nsps[nsp];
    if (!socket) {
      socket = new Socket(this, nsp, opts);
      this.nsps[nsp] = socket;
      var self = this;
      socket.on('connecting', onConnecting);
      socket.on('connect', function () {
        socket.id = self.engine.id;
      });
  
      if (this.autoConnect) {
        // manually call here since connecting evnet is fired before listening
        onConnecting();
      }
    }
  
    function onConnecting() {
      if (!~indexOf(self.connecting, socket)) {
        self.connecting.push(socket);
      }
    }
  
    return socket;
  };
  
  /**
   * Called upon a socket close.
   *
   * @param {Socket} socket
   */
  
  Manager.prototype.destroy = function (socket) {
    var index = indexOf(this.connecting, socket);
    if (~index) this.connecting.splice(index, 1);
    if (this.connecting.length) return;
  
    this.close();
  };
  
  /**
   * Writes a packet.
   *
   * @param {Object} packet
   * @api private
   */
  
  Manager.prototype.packet = function (packet) {
    debug('writing packet %j', packet);
    var self = this;
    if (packet.query && packet.type === 0) packet.nsp += '?' + packet.query;
  
    if (!self.encoding) {
      // encode, then write to engine with result
      self.encoding = true;
      this.encoder.encode(packet, function (encodedPackets) {
        for (var i = 0; i < encodedPackets.length; i++) {
          self.engine.write(encodedPackets[i], packet.options);
        }
        self.encoding = false;
        self.processPacketQueue();
      });
    } else {
      // add packet to the queue
      self.packetBuffer.push(packet);
    }
  };
  
  /**
   * If packet buffer is non-empty, begins encoding the
   * next packet in line.
   *
   * @api private
   */
  
  Manager.prototype.processPacketQueue = function () {
    if (this.packetBuffer.length > 0 && !this.encoding) {
      var pack = this.packetBuffer.shift();
      this.packet(pack);
    }
  };
  
  /**
   * Clean up transport subscriptions and packet buffer.
   *
   * @api private
   */
  
  Manager.prototype.cleanup = function () {
    debug('cleanup');
  
    var subsLength = this.subs.length;
    for (var i = 0; i < subsLength; i++) {
      var sub = this.subs.shift();
      sub.destroy();
    }
  
    this.packetBuffer = [];
    this.encoding = false;
    this.lastPing = null;
  
    this.decoder.destroy();
  };
  
  /**
   * Close the current socket.
   *
   * @api private
   */
  
  Manager.prototype.close = Manager.prototype.disconnect = function () {
    debug('disconnect');
    this.skipReconnect = true;
    this.reconnecting = false;
    if ('opening' === this.readyState) {
      // `onclose` will not fire because
      // an open event never happened
      this.cleanup();
    }
    this.backoff.reset();
    this.readyState = 'closed';
    if (this.engine) this.engine.close();
  };
  
  /**
   * Called upon engine close.
   *
   * @api private
   */
  
  Manager.prototype.onclose = function (reason) {
    debug('onclose');
  
    this.cleanup();
    this.backoff.reset();
    this.readyState = 'closed';
    this.emit('close', reason);
  
    if (this._reconnection && !this.skipReconnect) {
      this.reconnect();
    }
  };
  
  /**
   * Attempt a reconnection.
   *
   * @api private
   */
  
  Manager.prototype.reconnect = function () {
    if (this.reconnecting || this.skipReconnect) return this;
  
    var self = this;
  
    if (this.backoff.attempts >= this._reconnectionAttempts) {
      debug('reconnect failed');
      this.backoff.reset();
      this.emitAll('reconnect_failed');
      this.reconnecting = false;
    } else {
      var delay = this.backoff.duration();
      debug('will wait %dms before reconnect attempt', delay);
  
      this.reconnecting = true;
      var timer = setTimeout(function () {
        if (self.skipReconnect) return;
  
        debug('attempting reconnect');
        self.emitAll('reconnect_attempt', self.backoff.attempts);
        self.emitAll('reconnecting', self.backoff.attempts);
  
        // check again for the case socket closed in above events
        if (self.skipReconnect) return;
  
        self.open(function (err) {
          if (err) {
            debug('reconnect attempt error');
            self.reconnecting = false;
            self.reconnect();
            self.emitAll('reconnect_error', err.data);
          } else {
            debug('reconnect success');
            self.onreconnect();
          }
        });
      }, delay);
  
      this.subs.push({
        destroy: function () {
          clearTimeout(timer);
        },
      });
    }
  };
  
  /**
   * Called upon successful reconnect.
   *
   * @api private
   */
  
  Manager.prototype.onreconnect = function () {
    var attempt = this.backoff.attempts;
    this.reconnecting = false;
    this.backoff.reset();
    this.updateSocketIds();
    this.emitAll('reconnect', attempt);
  };
  