<commit_before>var util = require("util");
var crypto = require("crypto");
var _ = require("lodash");

var shlog = require(global.C.BASE_DIR + "/lib/shlog.js");
var sh = require(global.C.BASE_DIR + "/lib/shutil.js");

function ShSet(oid) {
  this._keyType = "kSet";
  this._keyFormat = "set:%s";
  this._data = {};
  this._oid = "";
  this._key = global.C.DB_SCOPE + util.format(this._keyFormat, oid);
//  this._hash = "";
}

module.exports = ShSet;

ShSet.prototype.key = function () {
  return this._key;
};

ShSet.prototype.getAll = function (cb) {
  var self = this;
  global.db.hgetall(this._key, function (err, data) {
    if (err) {
      cb(1, sh.intMsg("hgetall-null", {key: this._key, error: err, data: data}));
      return;
    }
    if (data === null) {
      return cb(0, {});
    }
    try {
      self._data = data;
      _.each(Object.keys(data), function (key) {
        data[key] = JSON.parse(data[key]);
      });
    } catch (e) {
      return cb(1, sh.intMsg("set-parse", {message: e.message, data: data}));
    }
    return cb(0, self._data);
  });
};

ShSet.prototype.loadOrCreate = function (oid, cb) {
  var self = this;
  this.load(oid, function (error, value) {
    if (error) {
      self.create(oid);
    }
    cb(0, self._data);  // object must be valid
  });
};

ShSet.prototype.save = function (cb) {
  cb(1, sh.intMsg("set-save-not-implemented", "must save elements individually for now"));
};

ShSet.prototype.get = function (field, cb) {
  global.db.hget(this._key, field, function (err, value) {
    if (value === null) {
      return cb(1, sh.intMsg("hget-null", value));
    }
    try {
      var data = JSON.parse(value);
      return cb(0, data);
    } catch (e) {
      return cb(1, sh.intMsg("set-parse", {message: e.message, value: value}));
    }
  });
};

ShSet.prototype.set = function (field, value, cb) {
  global.db.hset(this._key, field, JSON.stringify(value), function (err, value) {
    if (err) {
      return cb(1, sh.intMsg("hset-error", value));
    }
    return cb(0, value);
  });
};

ShSet.prototype.remove = function (field, cb) {
  global.db.hdel(this._key, field, function (err, value) {
    if (err) {
      return cb(1, sh.intMsg("hdel-error", value));
    }
    return cb(0, value);
  });
};<commit_msg>change ShSet to ShHash;<commit_after>var util = require("util");
var crypto = require("crypto");
var _ = require("lodash");

var shlog = require(global.C.BASE_DIR + "/lib/shlog.js");
var sh = require(global.C.BASE_DIR + "/lib/shutil.js");

function ShHash(oid) {
  this._keyType = "kSet";
  this._keyFormat = "set:%s";
  this._data = {};
  this._oid = oid;
  this._key = global.C.DB_SCOPE + util.format(this._keyFormat, oid);
//  this._hash = "";
}

module.exports = ShHash;

ShHash.prototype.key = function () {
  return this._key;
};

ShHash.prototype.getAll = function (cb) {
  var self = this;
  global.db.hgetall(this._key, function (err, data) {
    if (err) {
      cb(1, sh.intMsg("hgetall-null", {key: this._key, error: err, data: data}));
      return;
    }
    if (data === null) {
      return cb(0, {});
    }
    try {
      self._data = data;
      _.each(Object.keys(data), function (key) {
        data[key] = JSON.parse(data[key]);
      });
    } catch (e) {
      return cb(1, sh.intMsg("set-parse", {message: e.message, data: data}));
    }
    return cb(0, self._data);
  });
};

ShHash.prototype.loadOrCreate = function (oid, cb) {
  var self = this;
  this.load(oid, function (error, value) {
    if (error) {
      self.create(oid);
    }
    cb(0, self._data);  // object must be valid
  });
};

ShHash.prototype.save = function (cb) {
  cb(1, sh.intMsg("set-save-not-implemented", "must save elements individually for now"));
};

ShHash.prototype.get = function (field, cb) {
  console.log(this._key, field);
  global.db.hget(this._key, field, function (err, value) {
    if (err) {
      return cb(1, sh.intMsg("hget-error", value));
    }
    if (value === null) {
      return cb(0, null);
    }
    try {
      var data = JSON.parse(value);
      return cb(0, data);
    } catch (e) {
      return cb(1, sh.intMsg("set-parse", {message: e.message, value: value}));
    }
  });
};

ShHash.prototype.set = function (field, value, cb) {
  global.db.hset(this._key, field, JSON.stringify(value), function (err, value) {
    if (err) {
      return cb(1, sh.intMsg("hset-error", value));
    }
    return cb(0, value);
  });
};

ShHash.prototype.remove = function (field, cb) {
  global.db.hdel(this._key, field, function (err, value) {
    if (err) {
      return cb(1, sh.intMsg("hdel-error", value));
    }
    return cb(0, value);
  });
};<|endoftext|>


<commit_before>'use strict';

var debug   = require('debug')('kiss.io:socket');
var parser  = require('socket.io-parser');
var url     = require('url');
var hasBin  = require('has-binary');

var Router  = require('./router');
var Plugin  = require('./plugin');

/**
 * A socket is a representation of a singular socket connection that
 * ..might be part of a larger connection pool, represented by `Client`.
 * Socket is specific to one namespace of one server.
 *
 * @constructs Socket
 * @access public
 *
 * @param {Namespace} nsp: the namespace that the socket is connected to.
 * @param {Client} client: the client instance that owns the socket.
 * @param {Object} query: the query params of the request.
 * @returns {Socket}
 */
function Socket(nsp, client, query)
{
  if(!(this instanceof Socket))
  {
    return new Socket(nsp, client, query);
  }

  this.nsp = nsp;
  this.id = nsp.name !== '/' ? nsp.name + '#' + client.id : client.id;
  this.opts = this.nsp.opts || {};
  this.client = client;
  this.conn = client.conn;
  this.request = this.conn.request;
  this.acks = {};
  this.connected = true;
  this.handshake = this.buildHandshake(query);
  this.router = new Router(this.nsp.opts);
}

/**
 * Reserved events.
 */
Socket.events =
[
  'error',
  'pre-setup',
  'connect',
  'connection',
  'pre-disconnect',
  'pre-disconnection',
  'disconnect',
  'disconnection',
  'newListener',
  'removeListener'
];

/**
 * Set/get a temporary flag.
 *
 * @access public
 *
 * @param {String} key: the name of the flag.
 * @param {*} [value]
 * @returns {*|undefined} value
 */
Socket.prototype.flag = function(key, value)
{
  this._flags = this._flags || {};

  if(arguments.length == 2)
  {
    this._flags[key] = value;
  }

  return this._flags;
};

/**
 * Reset temporary flags
 *
 * @access public
 */
Socket.prototype.reset = function()
{
  delete this._flags;
};

/**
 * Builds the `handshake` BC object
 *
 * @access private
 */
Socket.prototype.buildHandshake = function(query)
{
  var self = this;

  /**
   * @returns {Object} query
   */
  function buildQuery()
  {
    var requestQuery = url.parse(self.request.url, true).query;

    //if socket-specific query exist, replace query strings in requestQuery
    if(query)
    {
      query.t = requestQuery.t;
      query.EIO = requestQuery.EIO;
      query.transport = requestQuery.transport;
      return query;
    }

    return requestQuery || {};
  }

  return {
    headers: this.request.headers,
    time: (new Date) + '',
    address: this.conn.remoteAddress,
    xdomain: !!this.request.headers.origin,
    secure: !!this.request.connection.encrypted,
    issued: +(new Date),
    url: this.request.url,
    query: buildQuery()
  };
};

/**
 * Bind a router to this socket and merege it into the main
 * ..socket router.
 *
 * @access public
 *
 * @param {Router} router
 * @returns {Socket} self
 */
Socket.prototype.merge = function(router)
{
  var context =
  {
    nsp: this.nsp,
    socket: this
  };

  router.forRoute(function bind(route)
  {
    route.bindContext(context);
  });

  this.router.merge(router);
  return this;
};

Socket.prototype.plug =
Socket.prototype.plugin = function(plugin)
{
  var exports = plugin.exports.socket || {};

  for(let key of Object.keys(exports))
  {
    let value = exports[key];

    if(typeof value == 'function')
    {
      value = value.bind(plugin, this);
    }
    else if(typeof value == 'object')
    {
      value = Object.create(value);
    }

    this[key] = value;
  }

  this.merge(plugin.exports.router);
  return this;
};

/**
 * Shortcut for `Socket.router.on`.
 * @see Router.on
 *
 * @access public
 *
 * @param {String} event
 * @param {Array<Function>} [handlers]
 * @returns {Route}
 */
Socket.prototype.on = function(event, handlers)
{
  return this.router.on.apply(this.router, arguments);
};

/**
 * Trigger an inside event for Socket.
 *
 * @access private
 *
 * @param {String} ev
 */
Socket.prototype.trigger = function(ev)
{
  this.router.trigger.apply(this.router, arguments);
};

/**
 * Emits an event to the owning `Client`.
 * If event not in the reserved event list (Socket.events),
 * ..then send a packet to the socket with the given arguments.
 *
 * @access public
 *
 * @param {String} ev: event
 * @return {Socket} self
 */
Socket.prototype.emit = function(ev)
{
  if (Socket.events.indexOf(ev) !== -1)
  {
    this.trigger.apply(this, arguments);
  }
  else
  {
    var args = Array.prototype.slice.call(arguments);
    var packet = {};

    packet.type = hasBin(args) ? parser.BINARY_EVENT : parser.EVENT;
    packet.data = args;

    // access last argument to see if it's an ACK callback
    if ('function' == typeof args[args.length - 1])
    {
      debug('emitting packet with ack id %d', this.nsp.ids);

      this.acks[this.nsp.ids] = args.pop();
      packet.id = this.nsp.ids++;
    }

    // dispatch packet
    this.packet(packet, {
      volatile: this.flag('volatile'),
      compress: this.flag('compress')
    });

    // reset flags
    delete this.reset();
  }

  return this;
};

/**
 * Broadcasts a packet to every socket on the same namespace of this socket,
 * ..except this socket.
 *
 * @access public
 *
 * @param {String} ev: event
 * @returns {Socket} self
 */
Socket.prototype.broadcast = function(ev)
{
  this
  .nsp
  .broadcast
  .apply(this.nsp.except(this.id), arguments);

  return this;
};

/**
 * Sends a `message` event to the socket.
 * (fits the engine.io api).
 *
 * @alias {Socket.write}
 * @access public
 *
 * @params {*} params to emit with the message event.
 * @return {Socket} self
 */
Socket.prototype.send =
Socket.prototype.write = function()
{
  var args = Array.prototype.slice.call(arguments);
  args.unshift('message');

  this.emit.apply(this, args);

  return this;
};

/**
 * Writes a packet to the socket.
 *
 * @see Client.packet
 *
 * @access private
 *
 * @param {Object} packet
 * @param {Object} [opts]
 */
Socket.prototype.packet = function(packet, opts)
{
  packet.nsp = this.nsp.name;

  opts = opts || {};
  opts.compress = false !== opts.compress;

  this.client.packet(packet, opts);
};

/**
 * Send a CONNECT packet to the socket. Called by `Namespace` upon success.
 *
 * @access private (used publicly only by `Namespace`)
 */
Socket.prototype.onConnect = function()
{
  debug('socket connected - writing packet');

  this.packet({ type: parser.CONNECT });
};

/**
 * Called with each packet. Called by `Client`.
 *
 * @access private (used publicly only by `Client`)
 *
 * @param {Object} packet
 */
Socket.prototype.onPacket = function(packet)
{
  debug('got packet %j', packet);

  switch (packet.type)
  {
    case parser.EVENT:
      this.onEvent(packet);
      break;

    case parser.BINARY_EVENT:
      this.onEvent(packet);
      break;

    case parser.ACK:
      this.onAck(packet);
      break;

    case parser.BINARY_ACK:
      this.onAck(packet);
      break;

    case parser.DISCONNECT:
      this.onDisconnect();
      break;

    case parser.ERROR:
      this.trigger('error', packet.data);
  }
};

/**
 * Parses packet and emits to this socket. Called upon event packet.
 *
 * @access private
 *
 * @param {Object} packet
 */
Socket.prototype.onEvent = function(packet)
{
  var args = packet.data || [];

  debug('emitting event %j', args);

  if (null != packet.id)
  {
    debug('attaching ack callback to event');
    args.push(this.ack(packet.id));
  }

  this.trigger.apply(this, args);
};

/**
 * Produces an ack callback to emit with an event.
 *
 * @access private
 *
 * @param {Number} id: packet id
 */
Socket.prototype.ack = function(id)
{
  var self = this;
  var sent = false;

  return function()
  {
    // prevent double callbacks
    if (sent) return;

    var args = Array.prototype.slice.call(arguments);
    var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK;

    debug('sending ack %j', args);

    self.packet({
      id: id,
      type: type,
      data: args
    });

    sent = true;
  };
};

/**
 * Called upon ack packet.
 *
 * @access private
 */
Socket.prototype.onAck = function(packet)
{
  var ack = this.acks[packet.id];

  if ('function' == typeof ack)
  {
    debug('calling ack %s with %j', packet.id, packet.data);

    ack.apply(this, packet.data);
    delete this.acks[packet.id];
  }
  else
  {
    debug('bad ack %s', packet.id);
  }
};

/**
 * Closes connection. Called upon client disconnect packet.
 *
 * @access private
 */
Socket.prototype.onDisconnect = function()
{
  debug('got disconnect packet');
  this.disconnect('client namespace disconnect');
};

/**
 * Handles a client error.
 *
 * @access private
 */
Socket.prototype.onError = function(err)
{
  if (this.router.listensOn('error'))
  {
    this.trigger('error', err);
  }
  else
  {
    console.error('Missing error handler on `socket`.');
    console.error(err.stack);
  }
};

/**
 * Called upon closing. Called by `Client`.
 *
 * @access private (used publicly only by `Client`)
 *
 * @param {String} [reason]
 */
Socket.prototype.onClose = function(reason)
{
  debug('closing socket - reason %s', reason);
  this.disconnect(reason);
};

/**
 * Produces and sends an `error` packet to the socket.
 *
 * @access private
 *
 * @param {Object} err: error object
 */
Socket.prototype.error = function(err)
{
  this.packet({ type: parser.ERROR, data: err });
};

/**
 * Disconnects this client.
 *
 * @access public
 *
 * @param {String} [reason]
 * @return {Socket} self
 */
Socket.prototype.disconnect = function(reason)
{
  if (!this.connected) return this;

  debug('closing socket - reason %s', reason);

  this.trigger('pre-disconnect', this, reason);
  this.trigger('pre-disconnection', this, reason);

  this.packet({ type: parser.DISCONNECT });

  this.nsp.remove(this);
  this.client.remove(this);
  this.connected = false;

  this.trigger('disconnect', this, reason);
  this.trigger('disconnection', this, reason);

  return this;
};

/**
 * Sets the compress flag.
 *
 * @access public
 *
 * @param {Boolean} [compress=true]: If `true`, compresses the sending data.
 * @return {Socket} self
 */
Socket.prototype.compress = function(compress)
{
  this.flag('compress', compress);
  return this;
};

/*!
 * Module exports.
 */
module.exports = Socket;
<commit_msg>Made Socket.buildHandshake less cluttery<commit_after>'use strict';

var debug   = require('debug')('kiss.io:socket');
var parser  = require('socket.io-parser');
var url     = require('url');
var hasBin  = require('has-binary');

var Router  = require('./router');
var Plugin  = require('./plugin');

/**
 * A socket is a representation of a singular socket connection that
 * ..might be part of a larger connection pool, represented by `Client`.
 * Socket is specific to one namespace of one server.
 *
 * @constructs Socket
 * @access public
 *
 * @param {Namespace} nsp: the namespace that the socket is connected to.
 * @param {Client} client: the client instance that owns the socket.
 * @param {Object} query: the query params of the request.
 * @returns {Socket}
 */
function Socket(nsp, client, query)
{
  if(!(this instanceof Socket))
  {
    return new Socket(nsp, client, query);
  }

  this.nsp = nsp;
  this.id = nsp.name !== '/' ? nsp.name + '#' + client.id : client.id;
  this.opts = this.nsp.opts || {};
  this.client = client;
  this.conn = client.conn;
  this.request = this.conn.request;
  this.acks = {};
  this.connected = true;
  this.handshake = this.buildHandshake(query);
  this.router = new Router(this.nsp.opts);
}

/**
 * Reserved events.
 */
Socket.events =
[
  'error',
  'pre-setup',
  'connect',
  'connection',
  'pre-disconnect',
  'pre-disconnection',
  'disconnect',
  'disconnection',
  'newListener',
  'removeListener'
];

/**
 * Set/get a temporary flag.
 *
 * @access public
 *
 * @param {String} key: the name of the flag.
 * @param {*} [value]
 * @returns {*|undefined} value
 */
Socket.prototype.flag = function(key, value)
{
  this._flags = this._flags || {};

  if(arguments.length == 2)
  {
    this._flags[key] = value;
  }

  return this._flags;
};

/**
 * Reset temporary flags
 *
 * @access public
 */
Socket.prototype.reset = function()
{
  delete this._flags;
};

/**
 * Builds the `handshake` BC object
 *
 * @access private
 *
 * @param {Object} [query]: if not provided, uses Socket.request query.
 * @returns {Object} handshake
 */
Socket.prototype.buildHandshake = function(query)
{
  var requestQuery = url.parse(this.request.url, true).query || {};

  query = query || requestQuery;
  query.t = requestQuery.t;
  query.EIO = requestQuery.EIO;
  query.transport = requestQuery.transport;

  return {
    headers: this.request.headers,
    time: (new Date) + '',
    address: this.conn.remoteAddress,
    xdomain: !!this.request.headers.origin,
    secure: !!this.request.connection.encrypted,
    issued: +(new Date),
    url: this.request.url,
    query: buildQuery()
  };
};

/**
 * Bind a router to this socket and merege it into the main
 * ..socket router.
 *
 * @access public
 *
 * @param {Router} router
 * @returns {Socket} self
 */
Socket.prototype.merge = function(router)
{
  var context =
  {
    nsp: this.nsp,
    socket: this
  };

  router.forRoute(function bind(route)
  {
    route.bindContext(context);
  });

  this.router.merge(router);
  return this;
};

Socket.prototype.plug =
Socket.prototype.plugin = function(plugin)
{
  var exports = plugin.exports.socket || {};

  for(let key of Object.keys(exports))
  {
    let value = exports[key];

    if(typeof value == 'function')
    {
      value = value.bind(plugin, this);
    }
    else if(typeof value == 'object')
    {
      value = Object.create(value);
    }

    this[key] = value;
  }

  this.merge(plugin.exports.router);
  return this;
};

/**
 * Shortcut for `Socket.router.on`.
 * @see Router.on
 *
 * @access public
 *
 * @param {String} event
 * @param {Array<Function>} [handlers]
 * @returns {Route}
 */
Socket.prototype.on = function(event, handlers)
{
  return this.router.on.apply(this.router, arguments);
};

/**
 * Trigger an inside event for Socket.
 *
 * @access private
 *
 * @param {String} ev
 */
Socket.prototype.trigger = function(ev)
{
  this.router.trigger.apply(this.router, arguments);
};

/**
 * Emits an event to the owning `Client`.
 * If event not in the reserved event list (Socket.events),
 * ..then send a packet to the socket with the given arguments.
 *
 * @access public
 *
 * @param {String} ev: event
 * @return {Socket} self
 */
Socket.prototype.emit = function(ev)
{
  if (Socket.events.indexOf(ev) !== -1)
  {
    this.trigger.apply(this, arguments);
  }
  else
  {
    var args = Array.prototype.slice.call(arguments);
    var packet = {};

    packet.type = hasBin(args) ? parser.BINARY_EVENT : parser.EVENT;
    packet.data = args;

    // access last argument to see if it's an ACK callback
    if ('function' == typeof args[args.length - 1])
    {
      debug('emitting packet with ack id %d', this.nsp.ids);

      this.acks[this.nsp.ids] = args.pop();
      packet.id = this.nsp.ids++;
    }

    // dispatch packet
    this.packet(packet, {
      volatile: this.flag('volatile'),
      compress: this.flag('compress')
    });

    // reset flags
    delete this.reset();
  }

  return this;
};

/**
 * Broadcasts a packet to every socket on the same namespace of this socket,
 * ..except this socket.
 *
 * @access public
 *
 * @param {String} ev: event
 * @returns {Socket} self
 */
Socket.prototype.broadcast = function(ev)
{
  this
  .nsp
  .broadcast
  .apply(this.nsp.except(this.id), arguments);

  return this;
};

/**
 * Sends a `message` event to the socket.
 * (fits the engine.io api).
 *
 * @alias {Socket.write}
 * @access public
 *
 * @params {*} params to emit with the message event.
 * @return {Socket} self
 */
Socket.prototype.send =
Socket.prototype.write = function()
{
  var args = Array.prototype.slice.call(arguments);
  args.unshift('message');

  this.emit.apply(this, args);

  return this;
};

/**
 * Writes a packet to the socket.
 *
 * @see Client.packet
 *
 * @access private
 *
 * @param {Object} packet
 * @param {Object} [opts]
 */
Socket.prototype.packet = function(packet, opts)
{
  packet.nsp = this.nsp.name;

  opts = opts || {};
  opts.compress = false !== opts.compress;

  this.client.packet(packet, opts);
};

/**
 * Send a CONNECT packet to the socket. Called by `Namespace` upon success.
 *
 * @access private (used publicly only by `Namespace`)
 */
Socket.prototype.onConnect = function()
{
  debug('socket connected - writing packet');

  this.packet({ type: parser.CONNECT });
};

/**
 * Called with each packet. Called by `Client`.
 *
 * @access private (used publicly only by `Client`)
 *
 * @param {Object} packet
 */
Socket.prototype.onPacket = function(packet)
{
  debug('got packet %j', packet);

  switch (packet.type)
  {
    case parser.EVENT:
      this.onEvent(packet);
      break;

    case parser.BINARY_EVENT:
      this.onEvent(packet);
      break;

    case parser.ACK:
      this.onAck(packet);
      break;

    case parser.BINARY_ACK:
      this.onAck(packet);
      break;

    case parser.DISCONNECT:
      this.onDisconnect();
      break;

    case parser.ERROR:
      this.trigger('error', packet.data);
  }
};

/**
 * Parses packet and emits to this socket. Called upon event packet.
 *
 * @access private
 *
 * @param {Object} packet
 */
Socket.prototype.onEvent = function(packet)
{
  var args = packet.data || [];

  debug('emitting event %j', args);

  if (null != packet.id)
  {
    debug('attaching ack callback to event');
    args.push(this.ack(packet.id));
  }

  this.trigger.apply(this, args);
};

/**
 * Produces an ack callback to emit with an event.
 *
 * @access private
 *
 * @param {Number} id: packet id
 */
Socket.prototype.ack = function(id)
{
  var self = this;
  var sent = false;

  return function()
  {
    // prevent double callbacks
    if (sent) return;

    var args = Array.prototype.slice.call(arguments);
    var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK;

    debug('sending ack %j', args);

    self.packet({
      id: id,
      type: type,
      data: args
    });

    sent = true;
  };
};

/**
 * Called upon ack packet.
 *
 * @access private
 */
Socket.prototype.onAck = function(packet)
{
  var ack = this.acks[packet.id];

  if ('function' == typeof ack)
  {
    debug('calling ack %s with %j', packet.id, packet.data);

    ack.apply(this, packet.data);
    delete this.acks[packet.id];
  }
  else
  {
    debug('bad ack %s', packet.id);
  }
};

/**
 * Closes connection. Called upon client disconnect packet.
 *
 * @access private
 */
Socket.prototype.onDisconnect = function()
{
  debug('got disconnect packet');
  this.disconnect('client namespace disconnect');
};

/**
 * Handles a client error.
 *
 * @access private
 */
Socket.prototype.onError = function(err)
{
  if (this.router.listensOn('error'))
  {
    this.trigger('error', err);
  }
  else
  {
    console.error('Missing error handler on `socket`.');
    console.error(err.stack);
  }
};

/**
 * Called upon closing. Called by `Client`.
 *
 * @access private (used publicly only by `Client`)
 *
 * @param {String} [reason]
 */
Socket.prototype.onClose = function(reason)
{
  debug('closing socket - reason %s', reason);
  this.disconnect(reason);
};

/**
 * Produces and sends an `error` packet to the socket.
 *
 * @access private
 *
 * @param {Object} err: error object
 */
Socket.prototype.error = function(err)
{
  this.packet({ type: parser.ERROR, data: err });
};

/**
 * Disconnects this client.
 *
 * @access public
 *
 * @param {String} [reason]
 * @return {Socket} self
 */
Socket.prototype.disconnect = function(reason)
{
  if (!this.connected) return this;

  debug('closing socket - reason %s', reason);

  this.trigger('pre-disconnect', this, reason);
  this.trigger('pre-disconnection', this, reason);

  this.packet({ type: parser.DISCONNECT });

  this.nsp.remove(this);
  this.client.remove(this);
  this.connected = false;

  this.trigger('disconnect', this, reason);
  this.trigger('disconnection', this, reason);

  return this;
};

/**
 * Sets the compress flag.
 *
 * @access public
 *
 * @param {Boolean} [compress=true]: If `true`, compresses the sending data.
 * @return {Socket} self
 */
Socket.prototype.compress = function(compress)
{
  this.flag('compress', compress);
  return this;
};

/*!
 * Module exports.
 */
module.exports = Socket;
<|endoftext|>


<commit_before>/*!
 * OS.js - JavaScript Cloud/Web Desktop Platform
 *
 * Copyright (c) 2011-2016, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */
(function(CoreWM, Panel, PanelItem, Utils, API, VFS) {
  'use strict';

  // FIXME: This item does not cache Event collections

  /////////////////////////////////////////////////////////////////////////////
  // ITEM
  /////////////////////////////////////////////////////////////////////////////

  /**
   * PanelItem: WindowList
   */
  var PanelItemWindowList = function() {
    PanelItem.apply(this, ['PanelItemWindowList PanelItemWide']);

    this.$element = null;
  };

  PanelItemWindowList.prototype = Object.create(PanelItem.prototype);
  PanelItemWindowList.Name = 'Window List'; // Static name
  PanelItemWindowList.Description = 'Toggle between open windows'; // Static description
  PanelItemWindowList.Icon = 'apps/xfwm4.png'; // Static icon

  PanelItemWindowList.prototype.init = function() {
    var root = PanelItem.prototype.init.apply(this, arguments);

    this.$element = document.createElement('ul');
    root.appendChild(this.$element);

    var wm = OSjs.Core.getWindowManager();
    if ( wm ) {
      var wins = wm.getWindows();
      for ( var i = 0; i < wins.length; i++ ) {
        if ( wins[i] ) {
          this.update('create', wins[i]);
        }
      }
    }

    return root;
  };

  PanelItemWindowList.prototype.destroy = function() {
    PanelItem.prototype.destroy.apply(this, arguments);
  };

  PanelItemWindowList.prototype.update = function(ev, win) {
    var self = this;
    if ( !this.$element || (win && win._properties.allow_windowlist === false) ) {
      return;
    }

    var cn = 'WindowList_Window_' + win._wid;
    var _change = function(cn, callback) {
      var els = self.$element.getElementsByClassName(cn);
      if ( els.length ) {
        for ( var i = 0, l = els.length; i < l; i++ ) {
          if ( els[i] && els[i].parentNode ) {
            callback(els[i]);
          }
        }
      }
    };

    if ( ev === 'create' ) {
      var className = 'Button WindowList_Window_' + win._wid;
      if ( this.$element.getElementsByClassName(className).length ) { return; }

      var el = document.createElement('li');
      el.innerHTML = '' + win._title + '';
      el.className = className;
      el.title = win._title;

      Utils.$bind(el, 'click', function() {
        win._restore(false, true);
      });
      Utils.$bind(el, 'contextmenu', function(ev) {
        ev.stopPropagation();
        return false;
      });

      var peeking = false;
      OSjs.API.createDroppable(el, {
        onDrop: function(ev, el) {
          if ( win ) {
            win._focus();
          }
        },
        onLeave: function() {
          if ( peeking ) {
            peeking = false;
          }
        },
        onEnter: function(ev, inst, args) {
          if ( !peeking ) {
            if ( win ) {
              win._focus();
            }
            peeking = true;
          }
        },
        onItemDropped: function(ev, el, item, args) {
          if ( win ) {
            return win._onDndEvent(ev, 'itemDrop', item, args);
          }
          return false;
        },
        onFilesDropped: function(ev, el, files, args) {
          if ( win ) {
            return win._onDndEvent(ev, 'filesDrop', files, args);
          }
          return false;
        }
      });

      if ( win._state.focused ) {
        el.className += ' Focused';
      }
      this.$element.appendChild(el);
    } else if ( ev === 'close' ) {
      _change(cn, function(el) {
        el.parentNode.removeChild(el);
      });
    } else if ( ev === 'focus' ) {
      _change(cn, function(el) {
        el.className += ' Focused';
      });
    } else if ( ev === 'blur' ) {
      _change(cn, function(el) {
        el.className = el.className.replace(/\s?Focused/, '');
      });
    } else if ( ev === 'title' ) {
      _change(cn, function(el) {
        el.getElementsByTagName('span')[0].innerHTML = win._title;
        el.title = win._title;
      });
    } else if ( ev === 'icon' ) {
      _change(cn, function(el) {
        el.getElementsByTagName('img')[0].src = win._icon;
      });
    } else if ( ev === 'attention_on' ) {
      _change(cn, function(el) {
        if ( !el.className.match(/Attention/) ) {
          el.className += ' Attention';
        }
      });
    } else if ( ev === 'attention_off' ) {
      _change(cn, function(el) {
        if ( !el.className.match(/Attention/) ) {
          el.className = el.className.replace(/\s?Attention/, '');
        }
      });
    }
  };

  /////////////////////////////////////////////////////////////////////////////
  // EXPORTS
  /////////////////////////////////////////////////////////////////////////////

  OSjs.Applications                                    = OSjs.Applications || {};
  OSjs.Applications.CoreWM                             = OSjs.Applications.CoreWM || {};
  OSjs.Applications.CoreWM.PanelItems                  = OSjs.Applications.CoreWM.PanelItems || {};
  OSjs.Applications.CoreWM.PanelItems.WindowList       = PanelItemWindowList;

})(OSjs.Applications.CoreWM.Class, OSjs.Applications.CoreWM.Panel, OSjs.Applications.CoreWM.PanelItem, OSjs.Utils, OSjs.API, OSjs.VFS);
<commit_msg>CoreWM: Improved WindowList event handling<commit_after>/*!
 * OS.js - JavaScript Cloud/Web Desktop Platform
 *
 * Copyright (c) 2011-2016, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */
(function(CoreWM, Panel, PanelItem, Utils, API, VFS) {
  'use strict';

  /////////////////////////////////////////////////////////////////////////////
  // ITEM
  /////////////////////////////////////////////////////////////////////////////

  function WindowListEntry(win, className) {

    var el = document.createElement('li');
    el.innerHTML = '' + win._title + '';
    el.className = className;
    el.title = win._title;

    this.evClick = Utils.$bind(el, 'click', function() {
      win._restore(false, true);
    });

    this.evMenu = Utils.$bind(el, 'contextmenu', function(ev) {
      ev.stopPropagation();
      return false;
    });

    var peeking = false;
    OSjs.API.createDroppable(el, {
      onDrop: function(ev, el) {
        if ( win ) {
          win._focus();
        }
      },
      onLeave: function() {
        if ( peeking ) {
          peeking = false;
        }
      },
      onEnter: function(ev, inst, args) {
        if ( !peeking ) {
          if ( win ) {
            win._focus();
          }
          peeking = true;
        }
      },
      onItemDropped: function(ev, el, item, args) {
        if ( win ) {
          return win._onDndEvent(ev, 'itemDrop', item, args);
        }
        return false;
      },
      onFilesDropped: function(ev, el, files, args) {
        if ( win ) {
          return win._onDndEvent(ev, 'filesDrop', files, args);
        }
        return false;
      }
    });

    if ( win._state.focused ) {
      el.className += ' Focused';
    }

    this.$element = el;
    this.id = win._wid;
  }

  WindowListEntry.prototype.destroy = function() {
    if ( this.evClick ) {
      this.evClick = this.evClick.destroy();
    }

    if ( this.evMenu ) {
      this.evMenu = this.evMenu.destroy();
    }

    if ( this.$element ) {
      this.$element = Utils.$remove(this.$element);
    }
  };

  WindowListEntry.prototype.event = function(ev, win, parentEl) {
    var cn = 'WindowList_Window_' + win._wid;
    var el = this.$element;

    function _change(cn, callback) {
      var els = parentEl.getElementsByClassName(cn);
      if ( els.length ) {
        for ( var i = 0, l = els.length; i < l; i++ ) {
          if ( els[i] && els[i].parentNode ) {
            callback(els[i]);
          }
        }
      }
    }

    if ( ev === 'focus' ) {
      _change(cn, function(el) {
        el.className += ' Focused';
      });
    } else if ( ev === 'blur' ) {
      _change(cn, function(el) {
        el.className = el.className.replace(/\s?Focused/, '');
      });
    } else if ( ev === 'title' ) {
      _change(cn, function(el) {
        el.getElementsByTagName('span')[0].innerHTML = win._title;
        el.title = win._title;
      });
    } else if ( ev === 'icon' ) {
      _change(cn, function(el) {
        el.getElementsByTagName('img')[0].src = win._icon;
      });
    } else if ( ev === 'attention_on' ) {
      _change(cn, function(el) {
        if ( !el.className.match(/Attention/) ) {
          el.className += ' Attention';
        }
      });
    } else if ( ev === 'attention_off' ) {
      _change(cn, function(el) {
        if ( !el.className.match(/Attention/) ) {
          el.className = el.className.replace(/\s?Attention/, '');
        }
      });
    } else if ( ev === 'close' ) {
      return false;
    }

    return true;
  };

  /**
   * PanelItem: WindowList
   */
  function PanelItemWindowList() {
    PanelItem.apply(this, ['PanelItemWindowList PanelItemWide']);
    this.$element = null;
    this.entries = [];
  }

  PanelItemWindowList.prototype = Object.create(PanelItem.prototype);
  PanelItemWindowList.Name = 'Window List'; // Static name
  PanelItemWindowList.Description = 'Toggle between open windows'; // Static description
  PanelItemWindowList.Icon = 'apps/xfwm4.png'; // Static icon

  PanelItemWindowList.prototype.init = function() {
    var root = PanelItem.prototype.init.apply(this, arguments);

    this.$element = document.createElement('ul');
    root.appendChild(this.$element);

    var wm = OSjs.Core.getWindowManager();
    if ( wm ) {
      var wins = wm.getWindows();
      for ( var i = 0; i < wins.length; i++ ) {
        if ( wins[i] ) {
          this.update('create', wins[i]);
        }
      }
    }

    return root;
  };

  PanelItemWindowList.prototype.destroy = function() {
    this.entries.forEach(function(e) {
      try {
        e.destroy();
      } catch ( e ) {}
      e = null;
    });

    this.entries = [];

    PanelItem.prototype.destroy.apply(this, arguments);
  };

  PanelItemWindowList.prototype.update = function(ev, win) {
    var self = this;
    if ( !this.$element || (win && win._properties.allow_windowlist === false) ) {
      return;
    }

    var entry = null;
    if ( ev === 'create' ) {
      var className = 'Button WindowList_Window_' + win._wid;
      if ( this.$element.getElementsByClassName(className).length ) {
        return;
      }

      entry = new WindowListEntry(win, className);
      this.entries.push(entry);
      this.$element.appendChild(entry.$element);
    } else {
      var found = -1;
      this.entries.forEach(function(e, idx) {
        if ( e.id === win._wid ) {
          found = idx;
        }
        return found !== -1;
      });

      entry = this.entries[found];
      if ( entry ) {
        if ( entry.event(ev, win, this.$element) === false ) {
          entry.destroy();

          this.entries.splice(found, 1);
        }
      }
    }
  };

  /////////////////////////////////////////////////////////////////////////////
  // EXPORTS
  /////////////////////////////////////////////////////////////////////////////

  OSjs.Applications                                    = OSjs.Applications || {};
  OSjs.Applications.CoreWM                             = OSjs.Applications.CoreWM || {};
  OSjs.Applications.CoreWM.PanelItems                  = OSjs.Applications.CoreWM.PanelItems || {};
  OSjs.Applications.CoreWM.PanelItems.WindowList       = PanelItemWindowList;

})(OSjs.Applications.CoreWM.Class, OSjs.Applications.CoreWM.Panel, OSjs.Applications.CoreWM.PanelItem, OSjs.Utils, OSjs.API, OSjs.VFS);
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2013 salesforce.com, inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
function lib(scrollUtil) { //eslint-disable-line no-unused-vars
    'use strict';

    var lib = { //eslint-disable-line no-shadow, no-unused-vars

        validateAnimationName: function(animName) {
        	if(animName && animName.match(/^move(to|from)(bottom|top|left|right|center|pop)$/)) {
                return true;
            }
            return false;
        },

        /**
         * returns the initial, first and last focusable in the given panel
         * @param containerEl
         * @returns {{initial: *, first: *, last: *}}
         */
        getFocusables: function(containerEl) {
            if(!containerEl) {
                return {
                    initial: null,
                    first: null,
                    last: null
                };
            }
            var els = containerEl.querySelectorAll('input,button,a,textarea,select'),
                len = els.length,
                i, el;

            // The 'initial' element is the first non-button focusable element (see W-2512261)
            // whereas the 'first' element is the first (button or non-button) focusable element
            var initial, first, last;

            for (i = 0; i < len; i++) {
                el = els[i];
                if (this.focusAllowed(el)) {
                    if (!first) {
                        first = el;
                    }
                    if (!/button/i.test(el.type)) {
                        initial = el;
                        break;
                    }
                }
            }

            for (i = len - 1; i >= 0; i--) {
                el = els[i];
                if (this.focusAllowed(el)) {
                    last = el;
                    break;
                }
            }

            return {
                // security restriction on iOS doesn't allow focus without user gesture, and focusing an input
                // inside a timeout causes weird behaviours (See W-2564192)
                // we can't completely ignore focus in iOS because the header is positioned on focus. Therefore
                // allowing iOS to focus the header buttons as before.
                initial: initial && !$A.get('$Browser.isIOS') ? initial : first,
                first: first,
                last: last
            };
        },

        /**
         * returns the key event handler function that do things based on the config
         * @param cmp
         * @param config {closeOnEsc, trapFocus, closeOnTabOut}
         * @param closeAction the caller defined close action
         * @returns {Function}
         */
        getKeyEventListener: function(cmp, conf, closeAction) {
            var me = this, config = conf || {};

            return function(e) {
                var el;

                if (!cmp.isValid()) {
                    return;
                }
                var event = e || window.event,
                    keyCode = event.keyCode;
                if (keyCode === 27 && config.closeOnEsc) {
                    //escape to close
                    $A.util.squash(e);
                    if ($A.util.isFunction(closeAction)) {
                    	closeAction(cmp, "closeOnEsc");
                    } else {
                        cmp.close();
                    }
                } else if (keyCode === 9) {
                    //close on tab out
                    var shiftPressed = event.shiftKey,
                        current = document.activeElement,
                        focusables;

                    el = cmp.getElement();
                    if(el) {
                        focusables = me.getFocusables(cmp.getElement());
                    }

                    if (focusables && config.trapFocus) {
                        if (current === focusables.last && !shiftPressed) {
                            $A.util.squash(event, true);
                            focusables.first.focus();
                        } else if (current === focusables.first && shiftPressed) {
                            $A.util.squash(event, true);
                            focusables.last.focus();
                        }
                    } else if (focusables && config.closeOnTabOut) {
                        if (current === focusables.last && !shiftPressed) {
                            $A.util.squash(event, true);
                            if ($A.util.isFunction(closeAction)) {
                            	closeAction(cmp, "closeOnTabOut");
                            } else {
                                cmp.close();
                            }
                        }
                    }
                }
            };
        },

        /**
         * returns the mouse event handler function that do things base on the config
         * @param panelCmp
         * @param config {closeOnClickOut}
         * @param closeAction the caller defined close action
         * @returns {Function}
         */
        getMouseEventListener: function(panelCmp, config, closeAction) {
            return function(e) {
                if (!panelCmp.isValid()) {
                    return;
                }

                var event = e || window.event,
                    panelEl = panelCmp.getElement(),
                    target = event.target || event.srcElement;
                if (config.closeOnClickOut) {
                    var clickedInside = $A.util.contains(panelEl, target);
                    if (panelEl && !clickedInside) {

                    	if ($A.util.isFunction(closeAction)) {
                        	closeAction(panelCmp, "closeOnClickOut");
                        } else {
                    	    panelCmp.close();
                        }
                    }
                }
            };
        },

        /**
         * show panel based on the config
         * @param cmp
         * @param config
         */
        show: function(cmp, config) {
            if(!cmp.isValid()) {
                return;
            }
            var me = this,
                animEnd    = this.getAnimationEndEventName(),
                animName   = config.animationName,
                panel = cmp.getElement(),
                useTransition = config.useTransition,
                closeButton,
                animEl = config.animationEl || panel;

            //make sure animation name is valid
            if(useTransition) {
            	useTransition = this.validateAnimationName(animName);
            }
            //need to notify panel manager to de-activate other panels;
            cmp.getEvent('notify').setParams({
                action: 'beforeShow',
                typeOf: 'ui:panel',
                payload: { panelInstance: cmp.getGlobalId() }
            }).fire();

            //endAnimationHandler: cleanup all classes and events
            var finishHandler = function () {

                if(!cmp.isValid()) {
                    return;
                }

                if (animEl) {
                    $A.util.removeClass(animEl, 'transitioning ' + animName);
                    animEl.removeEventListener(animEnd, finishHandler);
                }
                $A.util.addClass(panel, 'active');

                cmp.set('v.visible', true);

                if (config.autoFocus) {
                    me.setFocus(cmp);
                } else {

                    // If auto focus is false attempt to focus
                    // on the close button.
                    closeButton = cmp.getElement().querySelector('.closeBtn');

                    if(closeButton && closeButton.focus) {
                        closeButton.focus();
                    }
                }
                config.onFinish && config.onFinish();
            };

            panel.setAttribute("aria-hidden", 'false');
            if (useTransition) {
                animEl.addEventListener(animEnd, finishHandler, false);

                setTimeout(function() {
                     $A.util.addClass(panel, 'open');
                 },10);
                $A.util.addClass(animEl, 'transitioning ' + animName);

            } else {
                $A.util.addClass(panel, 'open');
                finishHandler();
            }

        },

        /**
         * hide panel
         * @param cmp
         * @param config
         */
        hide: function(cmp, config) {
            var animEnd    = this.getAnimationEndEventName(),
                animName   = config.animationName,
                panel = cmp.getElement(),
                useTransition = config.useTransition,
                animEl = config.animationEl || panel;

            //make sure animation name is valid
            if(useTransition) {
                useTransition = this.validateAnimationName(animName);
            }

            cmp.set('v.visible', false);

            //endAnimationHandler: cleanup all classes and events
            var finishHandler = function () {

                // make sure the compoment is valid befdore
                // doining anything with it, because
                // this is asynchronous
                if(cmp.isValid()) {

                    if (config.useTransition) {
                        animEl.removeEventListener(animEnd, finishHandler);
                    }

                    config.onFinish && config.onFinish();
                    setTimeout(function() {
                        $A.util.removeClass(panel, 'open');
                        $A.util.removeClass(panel, 'active');
                        $A.util.removeClass(animEl, 'transitioning ' + animName);
                    }, 1000); //make sure all transitions are finished

                } else {
                    config.onFinish && config.onFinish();
                }

            };

            panel.setAttribute("aria-hidden", 'true');
            if (useTransition) {
                animEl.addEventListener(animEnd, finishHandler, false);
                $A.util.addClass(animEl,  'transitioning ' + animName);
            } else {
                finishHandler();
            }
        },

        /**
         * Update panel body
         * @param cmp
         * @param body
         * @param callback
         */
        updatePanel: function(panel, facets, callback) {
            if ($A.util.isObject(facets)) {
                var facet, body = facets['body'] || panel.get('v.body');
                for (var key in facets) {
                    facet = facets[key];
                    if (facets.hasOwnProperty(key) && $A.util.isComponent(facet)) {
                        panel.set('v.' + key, facet);
                    }
                }
                if (!$A.util.isEmpty(body)) {
                    //set body as value provider to route the events to the body
                    //presume only one root body component
                    var avp = $A.util.isComponent(body) ? body : body[0],
                        header = facets['header'] || panel.get('v.header'),
                        footer = facets['footer'] || panel.get('v.footer');

                    if ($A.util.isComponent(avp)) {
                       avp.setAttributeValueProvider(panel);
                    }

                    var i, length;
                    if (!$A.util.isEmpty(header)) {
                        if ($A.util.isComponent(header)) {
                            header.setAttributeValueProvider(avp);
                        } else {
                            for (i = 0, length = header.length; i < length; i++) {
                                header[i].setAttributeValueProvider(avp);
                            }
                        }
                    }
                    if (!$A.util.isEmpty(footer)) {
                        if ($A.util.isComponent(header)) {
                            header.setAttributeValueProvider(avp);
                        } else {
                            for (i = 0, length = footer.length; i < length; i++) {
                                footer[0].setAttributeValueProvider(avp);
                            }
                        }
                    }
                }
            }
            callback && callback();
        },
        handleNotify: function(cmp, event, helper) {
        	var params = event.getParams();
	        if (!params) {
	            return;
	        }
	        switch (params.action) {
	            case 'destroyPanel':
	                //contained component tries to close the panel but doesn't have access to this panelInstance
	                //attach this id to the event and let it bubble up
	                if (params.typeOf === 'ui:destroyPanel' && !params.payload) {
	                    params.payload = {
	                        panelInstance: cmp.getGlobalId()
	                    };
	                }
	                break;
	            case 'closePanel':
	                event.stopPropagation();
	                helper.close(cmp, params.payload ? params.payload.callback : null);
	                break;
	            case 'setFocus':
	                if (cmp.get('v.autoFocus')) {
	                    this.setFocus(cmp);
	                }
	                break;
	        }
        },
        /**
         * Activate or de-activate the panel
         * @param cmp
         * @param active
         */
        setActive: function(cmp, active) {
            if (!cmp.isValid() && !cmp.isRendered()) {
                return;
            }
            var panel = cmp.getElement();
            if (active) {
                $A.util.addClass(panel, 'active');
                panel.setAttribute('aria-hidden', 'false');
                if (cmp.get('v.autoFocus')) {
                    this.setFocus(cmp);
                }
            } else if ($A.util.hasClass(panel, 'active')) {
                cmp.returnFocus = document.activeElement;
                $A.util.removeClass(panel, 'active');
                panel.setAttribute('aria-hidden', 'true');
            }
        },

        /**
         * returns the vendor prefix
         * @private
         */
        getPrefix : function () {
            if (!this._prefix) {
                var styles = window.getComputedStyle(document.documentElement, ''),
                    pre = (Array.prototype.slice.call(styles).join('').match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o']))[1],
                    up = $A.util.isIE;

                this._prefix = up ? pre.toUpperCase() : pre;
            }
            return this._prefix;
        },

        /**
         * returns the animationEnd event name
         * @private
         */
        getAnimationEndEventName: function () {
            var eventName = this.ANIMATION_END_EVENT_NAMES[this.getPrefix()];
            return eventName ? eventName : 'animationend';
        },

        /**
         * determines the element is visible or not
         * @private
         */
        isVisible: function(el) {
            while (el && el.style) {
                if (window.getComputedStyle(el).display === 'none') {
                    return false;
                }
                el = el.parentNode;
            }
            return true;
        },

        /**
         * determines the element is focusable or not
         * @private
         */
        focusAllowed: function(el) {
            return el && !el.disabled && !/hidden/i.test(el.type) && this.isVisible(el);
        },

        /**
         * Set to first focusable element
         * @private
         */
        setFocus: function(cmp) {

            if(cmp.isValid()) {
                var el;

                el = cmp.getElement();
                if(cmp.returnFocus) {
                    cmp.returnFocus.focus();
                } else if(el && el.querySelectorAll) {
                    var focusables = this.getFocusables(el);
                    focusables.initial && focusables.initial.focus();
                }
            }

        },
        scopeScroll: function (dom) {
            scrollUtil.scope(dom);
        },
        unscopeScroll: function (dom) {
            scrollUtil.unscope(dom);
        },
        ANIMATION_END_EVENT_NAMES : {
            webkit : 'webkitAnimationEnd',
            o : 'oAnimationEnd',
            moz : 'animationend',
            ms : 'animationend' // IE 10 or above
        }
    };
    return lib;
}
<commit_msg>Fix panel update issue @bug W-2830197@ @rev ssun@<commit_after>/*
 * Copyright (C) 2013 salesforce.com, inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
function lib(scrollUtil) { //eslint-disable-line no-unused-vars
    'use strict';

    var lib = { //eslint-disable-line no-shadow, no-unused-vars

        validateAnimationName: function(animName) {
        	if(animName && animName.match(/^move(to|from)(bottom|top|left|right|center|pop)$/)) {
                return true;
            }
            return false;
        },

        /**
         * returns the initial, first and last focusable in the given panel
         * @param containerEl
         * @returns {{initial: *, first: *, last: *}}
         */
        getFocusables: function(containerEl) {
            if(!containerEl) {
                return {
                    initial: null,
                    first: null,
                    last: null
                };
            }
            var els = containerEl.querySelectorAll('input,button,a,textarea,select'),
                len = els.length,
                i, el;

            // The 'initial' element is the first non-button focusable element (see W-2512261)
            // whereas the 'first' element is the first (button or non-button) focusable element
            var initial, first, last;

            for (i = 0; i < len; i++) {
                el = els[i];
                if (this.focusAllowed(el)) {
                    if (!first) {
                        first = el;
                    }
                    if (!/button/i.test(el.type)) {
                        initial = el;
                        break;
                    }
                }
            }

            for (i = len - 1; i >= 0; i--) {
                el = els[i];
                if (this.focusAllowed(el)) {
                    last = el;
                    break;
                }
            }

            return {
                // security restriction on iOS doesn't allow focus without user gesture, and focusing an input
                // inside a timeout causes weird behaviours (See W-2564192)
                // we can't completely ignore focus in iOS because the header is positioned on focus. Therefore
                // allowing iOS to focus the header buttons as before.
                initial: initial && !$A.get('$Browser.isIOS') ? initial : first,
                first: first,
                last: last
            };
        },

        /**
         * returns the key event handler function that do things based on the config
         * @param cmp
         * @param config {closeOnEsc, trapFocus, closeOnTabOut}
         * @param closeAction the caller defined close action
         * @returns {Function}
         */
        getKeyEventListener: function(cmp, conf, closeAction) {
            var me = this, config = conf || {};

            return function(e) {
                var el;

                if (!cmp.isValid()) {
                    return;
                }
                var event = e || window.event,
                    keyCode = event.keyCode;
                if (keyCode === 27 && config.closeOnEsc) {
                    //escape to close
                    $A.util.squash(e);
                    if ($A.util.isFunction(closeAction)) {
                    	closeAction(cmp, "closeOnEsc");
                    } else {
                        cmp.close();
                    }
                } else if (keyCode === 9) {
                    //close on tab out
                    var shiftPressed = event.shiftKey,
                        current = document.activeElement,
                        focusables;

                    el = cmp.getElement();
                    if(el) {
                        focusables = me.getFocusables(cmp.getElement());
                    }

                    if (focusables && config.trapFocus) {
                        if (current === focusables.last && !shiftPressed) {
                            $A.util.squash(event, true);
                            focusables.first.focus();
                        } else if (current === focusables.first && shiftPressed) {
                            $A.util.squash(event, true);
                            focusables.last.focus();
                        }
                    } else if (focusables && config.closeOnTabOut) {
                        if (current === focusables.last && !shiftPressed) {
                            $A.util.squash(event, true);
                            if ($A.util.isFunction(closeAction)) {
                            	closeAction(cmp, "closeOnTabOut");
                            } else {
                                cmp.close();
                            }
                        }
                    }
                }
            };
        },

        /**
         * returns the mouse event handler function that do things base on the config
         * @param panelCmp
         * @param config {closeOnClickOut}
         * @param closeAction the caller defined close action
         * @returns {Function}
         */
        getMouseEventListener: function(panelCmp, config, closeAction) {
            return function(e) {
                if (!panelCmp.isValid()) {
                    return;
                }

                var event = e || window.event,
                    panelEl = panelCmp.getElement(),
                    target = event.target || event.srcElement;
                if (config.closeOnClickOut) {
                    var clickedInside = $A.util.contains(panelEl, target);
                    if (panelEl && !clickedInside) {

                    	if ($A.util.isFunction(closeAction)) {
                        	closeAction(panelCmp, "closeOnClickOut");
                        } else {
                    	    panelCmp.close();
                        }
                    }
                }
            };
        },

        /**
         * show panel based on the config
         * @param cmp
         * @param config
         */
        show: function(cmp, config) {
            if(!cmp.isValid()) {
                return;
            }
            var me = this,
                animEnd    = this.getAnimationEndEventName(),
                animName   = config.animationName,
                panel = cmp.getElement(),
                useTransition = config.useTransition,
                closeButton,
                animEl = config.animationEl || panel;

            //make sure animation name is valid
            if(useTransition) {
            	useTransition = this.validateAnimationName(animName);
            }
            //need to notify panel manager to de-activate other panels;
            cmp.getEvent('notify').setParams({
                action: 'beforeShow',
                typeOf: 'ui:panel',
                payload: { panelInstance: cmp.getGlobalId() }
            }).fire();

            //endAnimationHandler: cleanup all classes and events
            var finishHandler = function () {

                if(!cmp.isValid()) {
                    return;
                }

                if (animEl) {
                    $A.util.removeClass(animEl, 'transitioning ' + animName);
                    animEl.removeEventListener(animEnd, finishHandler);
                }
                $A.util.addClass(panel, 'active');

                cmp.set('v.visible', true);

                if (config.autoFocus) {
                    me.setFocus(cmp);
                } else {

                    // If auto focus is false attempt to focus
                    // on the close button.
                    closeButton = cmp.getElement().querySelector('.closeBtn');

                    if(closeButton && closeButton.focus) {
                        closeButton.focus();
                    }
                }
                config.onFinish && config.onFinish();
            };

            panel.setAttribute("aria-hidden", 'false');
            if (useTransition) {
                animEl.addEventListener(animEnd, finishHandler, false);

                setTimeout(function() {
                     $A.util.addClass(panel, 'open');
                 },10);
                $A.util.addClass(animEl, 'transitioning ' + animName);

            } else {
                $A.util.addClass(panel, 'open');
                finishHandler();
            }

        },

        /**
         * hide panel
         * @param cmp
         * @param config
         */
        hide: function(cmp, config) {
            var animEnd    = this.getAnimationEndEventName(),
                animName   = config.animationName,
                panel = cmp.getElement(),
                useTransition = config.useTransition,
                animEl = config.animationEl || panel;

            //make sure animation name is valid
            if(useTransition) {
                useTransition = this.validateAnimationName(animName);
            }

            cmp.set('v.visible', false);

            //endAnimationHandler: cleanup all classes and events
            var finishHandler = function () {

                // make sure the compoment is valid befdore
                // doining anything with it, because
                // this is asynchronous
                if(cmp.isValid()) {

                    if (config.useTransition) {
                        animEl.removeEventListener(animEnd, finishHandler);
                    }

                    config.onFinish && config.onFinish();
                    setTimeout(function() {
                        $A.util.removeClass(panel, 'open');
                        $A.util.removeClass(panel, 'active');
                        $A.util.removeClass(animEl, 'transitioning ' + animName);
                    }, 1000); //make sure all transitions are finished

                } else {
                    config.onFinish && config.onFinish();
                }

            };

            panel.setAttribute("aria-hidden", 'true');
            if (useTransition) {
                animEl.addEventListener(animEnd, finishHandler, false);
                $A.util.addClass(animEl,  'transitioning ' + animName);
            } else {
                finishHandler();
            }
        },

        /**
         * Update panel body
         * @param cmp
         * @param body
         * @param callback
         */
        updatePanel: function(panel, facets, callback) {
            if ($A.util.isObject(facets)) {
                var facet, body = facets.body || panel.get('v.body');
                for (var key in facets) {
                    facet = facets[key];
                    if (facets.hasOwnProperty(key) && $A.util.isComponent(facet)) {
                        panel.set('v.' + key, facet);
                    }
                }
                if (!$A.util.isEmpty(body)) {
                    //set body as value provider to route the events to the body
                    //presume only one root body component
                    var avp = $A.util.isComponent(body) ? body : body[0],
                        header = facets.header || panel.get('v.header'),
                        footer = facets.footer || panel.get('v.footer');

                    if ($A.util.isComponent(avp)) {
                        avp.setAttributeValueProvider(panel);
                        this._updateAVP(header, avp);
                        this._updateAVP(footer, avp);
                    }
                }
            }
            callback && callback();
        },

        _updateAVP: function(cmps, avp) {
            if ($A.util.isComponent(cmps)) {
                cmps.setAttributeValueProvider(avp);
            } else if ($A.util.isArray(cmps)) {
                for (var i = 0; i < cmps.length; i++) {
                    cmps[i].setAttributeValueProvider(avp);
                }
            }
        },

        handleNotify: function(cmp, event, helper) {
        	var params = event.getParams();
	        if (!params) {
	            return;
	        }
	        switch (params.action) {
	            case 'destroyPanel':
	                //contained component tries to close the panel but doesn't have access to this panelInstance
	                //attach this id to the event and let it bubble up
	                if (params.typeOf === 'ui:destroyPanel' && !params.payload) {
	                    params.payload = {
	                        panelInstance: cmp.getGlobalId()
	                    };
	                }
	                break;
	            case 'closePanel':
	                event.stopPropagation();
	                helper.close(cmp, params.payload ? params.payload.callback : null);
	                break;
	            case 'setFocus':
	                if (cmp.get('v.autoFocus')) {
	                    this.setFocus(cmp);
	                }
	                break;
	        }
        },
        /**
         * Activate or de-activate the panel
         * @param cmp
         * @param active
         */
        setActive: function(cmp, active) {
            if (!cmp.isValid() && !cmp.isRendered()) {
                return;
            }
            var panel = cmp.getElement();
            if (active) {
                $A.util.addClass(panel, 'active');
                panel.setAttribute('aria-hidden', 'false');
                if (cmp.get('v.autoFocus')) {
                    this.setFocus(cmp);
                }
            } else if ($A.util.hasClass(panel, 'active')) {
                cmp.returnFocus = document.activeElement;
                $A.util.removeClass(panel, 'active');
                panel.setAttribute('aria-hidden', 'true');
            }
        },

        /**
         * returns the vendor prefix
         * @private
         */
        getPrefix : function () {
            if (!this._prefix) {
                var styles = window.getComputedStyle(document.documentElement, ''),
                    pre = (Array.prototype.slice.call(styles).join('').match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o']))[1],
                    up = $A.util.isIE;

                this._prefix = up ? pre.toUpperCase() : pre;
            }
            return this._prefix;
        },

        /**
         * returns the animationEnd event name
         * @private
         */
        getAnimationEndEventName: function () {
            var eventName = this.ANIMATION_END_EVENT_NAMES[this.getPrefix()];
            return eventName ? eventName : 'animationend';
        },

        /**
         * determines the element is visible or not
         * @private
         */
        isVisible: function(el) {
            while (el && el.style) {
                if (window.getComputedStyle(el).display === 'none') {
                    return false;
                }
                el = el.parentNode;
            }
            return true;
        },

        /**
         * determines the element is focusable or not
         * @private
         */
        focusAllowed: function(el) {
            return el && !el.disabled && !/hidden/i.test(el.type) && this.isVisible(el);
        },

        /**
         * Set to first focusable element
         * @private
         */
        setFocus: function(cmp) {

            if(cmp.isValid()) {
                var el;

                el = cmp.getElement();
                if(cmp.returnFocus) {
                    cmp.returnFocus.focus();
                } else if(el && el.querySelectorAll) {
                    var focusables = this.getFocusables(el);
                    focusables.initial && focusables.initial.focus();
                }
            }

        },
        scopeScroll: function (dom) {
            scrollUtil.scope(dom);
        },
        unscopeScroll: function (dom) {
            scrollUtil.unscope(dom);
        },
        ANIMATION_END_EVENT_NAMES : {
            webkit : 'webkitAnimationEnd',
            o : 'oAnimationEnd',
            moz : 'animationend',
            ms : 'animationend' // IE 10 or above
        }
    };
    return lib;
}
<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var parser = require('socket.io-parser');
var Emitter = require('emitter');
var toArray = require('to-array');
var on = require('./on');
var bind = require('bind');
var debug = require('debug')('socket.io-client:socket');

/**
 * 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;
  this.disconnected = true;
}

/**
 * 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.connected = false;
  this.disconnected = true;
  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 (null != 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.connected = true;
  this.disconnected = false;
  this.emit('connect');
  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();
  this.onclose('io server disconnect');
};

/**
 * 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(){
  if (!this.connected) return this;

  debug('performing disconnect (%s)', this.nsp);
  this.packet({ type: parser.PACKET_DISCONNECT });

  // destroy subscriptions
  this.destroy();

  // fire events
  this.onclose('io client disconnect');
  return this;
};
<commit_msg>socket: fix event blacklisting on ie8<=<commit_after>
/**
 * Module dependencies.
 */

var parser = require('socket.io-parser');
var Emitter = require('emitter');
var toArray = require('to-array');
var on = require('./on');
var bind = require('bind');
var debug = require('debug')('socket.io-client:socket');

/**
 * Module exports.
 */

module.exports = exports = Socket;

/**
 * Internal events (blacklisted).
 * These events can't be emitted by the user.
 *
 * @api private
 */

var events = {
  connect: 1,
  disconnect: 1,
  error: 1
};

/**
 * 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;
  this.disconnected = true;
}

/**
 * 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.hasOwnProperty(ev)) {
    emit.apply(this, arguments);
    return this;
  }

  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.connected = false;
  this.disconnected = true;
  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 (null != 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.connected = true;
  this.disconnected = false;
  this.emit('connect');
  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();
  this.onclose('io server disconnect');
};

/**
 * 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(){
  if (!this.connected) return this;

  debug('performing disconnect (%s)', this.nsp);
  this.packet({ type: parser.PACKET_DISCONNECT });

  // destroy subscriptions
  this.destroy();

  // fire events
  this.onclose('io client disconnect');
  return this;
};
<|endoftext|>


<commit_before>/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/

export default
   [ '$rootScope', '$pendolytics', 'Rest', 'GetBasePath', 'ProcessErrors', '$q',
        'ConfigService', '$log',
   function ($rootScope, $pendolytics, Rest, GetBasePath, ProcessErrors, $q,
        ConfigService, $log) {
       return {
            setPendoOptions: function (config) {
                var tower_version = config.version.split('-')[0],
                options = {
                    visitor: {
                      id: null,
                      role: null,
                      email: null
                    },
                    account: {
                      id: null,
                      planLevel: config.license_type,
                      planPrice: config.instance_count,
                      creationDate: config.license_date,
                      trial: config.trial,
                      tower_version: tower_version,
                      ansible_version: config.ansible_version
                    }
                };
                if(config.analytics_status === 'detailed'){
                    this.setDetailed(options, config);
                }
                else if(config.analytics_status === 'anonymous'){
                    this.setAnonymous(options);
                }
                return options;

            },

            setDetailed: function(options, config) {
                // Detailed mode
                    // VisitorId: username+hash of license_key
                    // AccountId: hash of license_key from license
                    // email: contact_email from license OR email from Tower account

                options.visitor.id = $rootScope.current_user.username + '@' + config.deployment_id;
                options.account.id = config.deployment_id;
                options.visitor.email = $rootScope.current_user.email;
            },

            setAnonymous: function (options) {
                //Anonymous mode
                    // VisitorId: <some hardcoded id that is the same across all anonymous>
                    // AccountId: <some hardcoded id that is the same across all anonymous>
                    // email: <blank>

                options.visitor.id = 0;
                options.account.id = "tower.ansible.com";
                options.visitor.email = "";
            },

            setRole: function(options) {
                var deferred = $q.defer();
                if($rootScope.current_user.is_superuser === true){
                    options.visitor.role = 'admin';
                    deferred.resolve(options);
                }
                else{
                    var url = GetBasePath('users') + $rootScope.current_user.id + '/admin_of_organizations/';
                    Rest.setUrl(url);
                    var promise = Rest.get();
                    promise.then(function (response) {
                        if(response.data.count > 0 ) {
                            options.visitor.role = "orgadmin";
                            deferred.resolve(options);
                        }
                        else {
                            options.visitor.role = "user";
                            deferred.resolve(options);
                        }
                    });
                    promise.catch(function (response) {
                        ProcessErrors($rootScope, response.data, response.status, null, {
                            hdr: 'Error!',
                            msg: 'Failed to get inventory name. GET returned status: ' +
                            response.status });
                        deferred.reject('Could not resolve pendo role.');
                    });
                }
                return deferred.promise;
            },

            issuePendoIdentity: function () {
                var config,
                    options,
                    c = ConfigService.get(),
                config = c.license_info;
                config.analytics_status = c.analytics_status;
                config.version = c.version;
                config.ansible_version = c.ansible_version;
                if(config.analytics_status === 'detailed' || config.analytics_status === 'anonymous'){
                    $pendolytics.bootstrap();
                    options = this.setPendoOptions(config);
                    this.setRole(options).then(function(options){
                        $log.debug('Pendo status is '+ config.analytics_status + '. Object below:');
                        $log.debug(options);
                        $pendolytics.identify(options);
                    }, function(reason){
                        // reject function for setRole
                        $log.debug(reason);
                    });
                }
                else {
                    $log.debug('Pendo is turned off.')
                }
             }
        };
   }
];
<commit_msg>Fix for Pendo if trial key/value pair is missing<commit_after>/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/

export default
   [ '$rootScope', '$pendolytics', 'Rest', 'GetBasePath', 'ProcessErrors', '$q',
        'ConfigService', '$log',
   function ($rootScope, $pendolytics, Rest, GetBasePath, ProcessErrors, $q,
        ConfigService, $log) {
       return {
            setPendoOptions: function (config) {
                var tower_version = config.version.split('-')[0],
                trial = (config.trial) ? config.trial : false,
                options = {
                    visitor: {
                      id: null,
                      role: null,
                      email: null
                    },
                    account: {
                      id: null,
                      planLevel: config.license_type,
                      planPrice: config.instance_count,
                      creationDate: config.license_date,
                      trial: trial,
                      tower_version: tower_version,
                      ansible_version: config.ansible_version
                    }
                };
                if(config.analytics_status === 'detailed'){
                    this.setDetailed(options, config);
                }
                else if(config.analytics_status === 'anonymous'){
                    this.setAnonymous(options);
                }
                return options;

            },

            setDetailed: function(options, config) {
                // Detailed mode
                    // VisitorId: username+hash of license_key
                    // AccountId: hash of license_key from license
                    // email: contact_email from license OR email from Tower account

                options.visitor.id = $rootScope.current_user.username + '@' + config.deployment_id;
                options.account.id = config.deployment_id;
                options.visitor.email = $rootScope.current_user.email;
            },

            setAnonymous: function (options) {
                //Anonymous mode
                    // VisitorId: <some hardcoded id that is the same across all anonymous>
                    // AccountId: <some hardcoded id that is the same across all anonymous>
                    // email: <blank>

                options.visitor.id = 0;
                options.account.id = "tower.ansible.com";
                options.visitor.email = "";
            },

            setRole: function(options) {
                var deferred = $q.defer();
                if($rootScope.current_user.is_superuser === true){
                    options.visitor.role = 'admin';
                    deferred.resolve(options);
                }
                else{
                    var url = GetBasePath('users') + $rootScope.current_user.id + '/admin_of_organizations/';
                    Rest.setUrl(url);
                    var promise = Rest.get();
                    promise.then(function (response) {
                        if(response.data.count > 0 ) {
                            options.visitor.role = "orgadmin";
                            deferred.resolve(options);
                        }
                        else {
                            options.visitor.role = "user";
                            deferred.resolve(options);
                        }
                    });
                    promise.catch(function (response) {
                        ProcessErrors($rootScope, response.data, response.status, null, {
                            hdr: 'Error!',
                            msg: 'Failed to get inventory name. GET returned status: ' +
                            response.status });
                        deferred.reject('Could not resolve pendo role.');
                    });
                }
                return deferred.promise;
            },

            issuePendoIdentity: function () {
                var config,
                    options,
                    c = ConfigService.get(),
                config = c.license_info;
                config.analytics_status = c.analytics_status;
                config.version = c.version;
                config.ansible_version = c.ansible_version;
                if(config.analytics_status === 'detailed' || config.analytics_status === 'anonymous'){
                    $pendolytics.bootstrap();
                    options = this.setPendoOptions(config);
                    this.setRole(options).then(function(options){
                        $log.debug('Pendo status is '+ config.analytics_status + '. Object below:');
                        $log.debug(options);
                        $pendolytics.identify(options);
                    }, function(reason){
                        // reject function for setRole
                        $log.debug(reason);
                    });
                }
                else {
                    $log.debug('Pendo is turned off.')
                }
             }
        };
   }
];
<|endoftext|>