<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');

/**
 * 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',
  '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>Forgotten to commit as part of the Socket.plug commit<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',
  '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;
<|endoftext|>


<commit_before>import dom from 'vd'

export default function splash ({ path, name, org, coc, logo, active, total, channels, large, iframe, gcaptcha_sitekey }){
  let div = dom('.splash',
    !iframe && dom('.logos',
      logo && dom('.logo.org'),
      dom('.logo.slack')
    ),
    dom('p',
      'Rejoignez ', dom('b', name),
      // mention single single-channel inline
      channels && channels.length === 1 && dom('span', ' #', channels[0]),
      ' sur Slack.'
    ),
    dom('p.status',
      active
        ? [
          dom('b.active', active), ' utilisateurs connectés et ',
          dom('b.total', total), ' inscrits.'
        ]
        : [dom('b.total', total), ' inscrits jusqu\'à présent.']
    ),
    dom('form id=invite',
      channels && (
        channels.length > 1
          // channel selection when there are multiple
          ? dom('select.form-item name=channel',
              channels.map(channel => {
                return dom('option', { value: channel, text: channel })
              })
            )
          // otherwise a fixed channel
          : dom('input type=hidden name=channel', { value: channels[0] })
      ),
      dom('input.form-item type=email name=email placeholder=vous@votredomaine.com '
        + (!iframe ? 'autofocus' : '')),
      dom('br'),
      dom(`div class="g-recaptcha" data-sitekey="${gcaptcha_sitekey}"`),
      coc && dom('.coc',
        dom('label',
          dom('input type=checkbox name=coc value=1'),
          'J\'accepte les règles de conduite',
          dom('a', { href: coc, target: '_blank' }, 'Règles de conduite'),
          '.'
        )
      ),
      dom('button.loading', 'Recevoir mon invitation')
    ),
    dom('p.signin',
      'ou ',
      dom(`a href=https://${org}.slack.com target=_top`, 's\'identifier'),
      '.'
    ),
    !iframe && dom('footer',
      'powered by ',
      dom('a href=http://rauchg.com/slackin target=_blank', 'slackin')
    ),
    style({ logo, active, large, iframe }),
    // xxx: single build
    dom('script', `
      data = {};
      data.path = ${JSON.stringify(path)};
    `),
    dom('script src=https://cdn.socket.io/socket.io-1.4.4.js'),
    dom(`script src=${path}assets/superagent.js`),
    dom(`script src=${path}assets/client.js`)
  )
  return div
}

const pink = '#E01563'

function style ({ logo, active, large, iframe } = {}){
  var css = dom.style()
  css.add('html', { 'font-size': large ? '14px' : '10px' })

  css.add('.splash', {
    'width': iframe ? '25rem' : '30rem',
    'margin': iframe ? '0' : '20rem auto',
    'text-align': 'center',
    'font-family': '"Helvetica Neue", Helvetica, Arial'
  })

  if (iframe) {
    css.add('body, html', {
      'margin': '0',
      'padding': '0',
      'background': '#FAFAFA',
      'overflow': 'hidden' // ff
    })

    css.add('.splash', {
      'box-sizing': 'border-box',
      'padding': '1rem'
    })
  }

  if (!iframe) {
    css
    .media('(max-width: 50rem)')
    .add('.splash', {
      'margin-top': '10rem'
    })
  }

  css.add('.head', {
    'margin-bottom': '4rem'
  })

  css.add('.logos', {
    'position': 'relative',
    'margin-bottom': '4rem'
  })

  if (!iframe) {
    css.add('.logo', {
      'width': '4.8rem',
      'height': '4.8rem',
      'display': 'inline-block',
      'background-size': 'cover'
    })

    css.add('.logo.slack', {
      'background-image': 'url(assets/slack.svg)'
    })

    if (logo) {
      let pw = 1 // '+' width in rem
      let lp = 3 // logos separation in rem

      css.add('.logo.org::after', {
        'position': 'absolute',
        'display': 'block',
        'content': '"+"',
        'top': '1.5rem',
        'left': '0',
        'width': '30rem',
        'text-align': 'center',
        'color': '#D6D6D6',
        'font-size': '1.5rem', // can't use rem in font shorthand IE9-10
              // http://codersblock.com/blog/font-shorthand-bug-in-ie10/
        'font-family': 'Helvetica Neue'
      })

      css.add('.logo.org', {
        'background-image': `url(${logo})`,
        'margin-right': `${lp + pw + lp}rem`
      })
    }
  }

  css.add('.coc', {
    'font-size': '1.2rem',
    padding: '1.5rem 0 .5rem',
    color: '#666'
  })

  if (iframe) {
    css.add('.coc', {
      'font-size': '1.1rem',
      'padding-top': '1rem'
    })

    css.add('.coc input', {
      position: 'relative',
      top: '-.2rem'
    })
  }

  css.add('.coc label', {
    cursor: 'pointer'
  })

  css.add('.coc input', {
    'appearance': 'none',
    '-webkit-appearance': 'none',
    border: 'none',
    'vertical-align': 'middle',
    margin: '0 .5rem 0 0'
  })

  css.add('.coc input::after', {
    content: '""',
    display: 'inline-block',
    width: '1.5rem',
    height: '1.5rem',
    'vertical-align': 'middle',
    background: 'url(/assets/checkbox.svg)',
    cursor: 'pointer'
  })

  css.add('.coc input:checked::after', {
    'background-position': 'right'
  })

  css.add('.coc a', {
    color: '#666'
  })

  css.add('.coc a:hover', {
    'background-color': '#666',
    'text-decoration': 'none',
    color: '#fff'
  })

  css.add('p', {
    'font-size': iframe ? '1.2rem' : '1.5rem',
    'margin': iframe ? '0 0 .5rem' : '.5rem 0'
  })

  if (iframe) {
    css.add('p.status', {
      'font-size': '1.1rem'
    })
  }

  css.add('select', {
    'background': 'none'
  })

  css.add('button, .form-item', {
    'font-size': '1.2rem',
    'margin-top': iframe ? '.5rem' : '1rem',
    'vertical-align': 'middle',
    'display': 'block',
    'text-align': 'center',
    'box-sizing': 'border-box',
    'width': '100%',
    'padding': '.9rem'
  })

  css.add('button', {
    'color': '#fff',
    'font-weight': 'bold',
    'border-width': 0,
    'background': pink,
    'text-transform': 'uppercase',
    'cursor': 'pointer',
    'appearence': 'none',
    '-webkit-appearence': 'none',
    'outline': '0',
    'transition': 'background-color 150ms ease-in, color 150ms ease-in'
  })

  css.add('button.loading', {
    'pointer-events': 'none'
  })

  css.add('button:disabled', {
    'color': '#9B9B9B',
    'background-color': '#D6D6D6',
    'cursor': 'default',
    'pointer-events': 'none'
  })

  css.add('button.error', {
    'background-color': '#F4001E',
    'text-transform': 'none'
  })

  css.add('button.success:disabled', {
    'color': '#fff',
    'background-color': '#68C200'
  })

  css.add('button:not(.disabled):active', {
    'background-color': '#7A002F'
  })

  css.add('b', {
    'transition': 'transform 150ms ease-in'
  })

  css.add('b.grow', {
    'transform': 'scale(1.3)'
  })

  css.add('form', {
    'margin-top': iframe ? '1rem' : '2rem',
    'margin-bottom': '0'
  })

  css.add('input', {
    'color': '#9B9B9B',
    'border': '.1rem solid #D6D6D6'
  })

  if (iframe) {
    css.add('button, .form-item', {
      'height': '2.8rem',
      'line-height': '2.8rem',
      'padding': 0,
      'font-size': '1.1rem'
    })
  }

  css.add('input:focus', {
    'color': '#666',
    'border-color': '#999',
    'outline': '0'
  })

  if (active) {
    css.add('.active', {
      'color': pink
    })
  }

  css.add('p.signin', {
    'padding': '1rem 0 1rem',
    'font-size': '1.1rem'
  })

  css.add('p.signin a', {
    'color': pink,
    'text-decoration': 'none'
  })

  css.add('p.signin a:hover', {
    'background-color': '#E01563',
    color: '#fff'
  })

  if (!iframe) {
    css.add('footer', {
      'color': '#D6D6D6',
      'font-size': '1.1rem',
      'margin': '20rem auto 0',
      'width': '30rem',
      'text-align': 'center'
    })

    css.add('footer a', {
      'color': '#9B9B9B',
      'text-decoration': 'none',
      'border-bottom': '.1rem solid #9B9B9B'
    })

    css.add('footer a:hover', {
      'color': '#fff',
      'background-color': '#9B9B9B'
    })
  }

  return css
}
<commit_msg>Translates footer<commit_after>import dom from 'vd'

export default function splash ({ path, name, org, coc, logo, active, total, channels, large, iframe, gcaptcha_sitekey }){
  let div = dom('.splash',
    !iframe && dom('.logos',
      logo && dom('.logo.org'),
      dom('.logo.slack')
    ),
    dom('p',
      'Rejoignez ', dom('b', name),
      // mention single single-channel inline
      channels && channels.length === 1 && dom('span', ' #', channels[0]),
      ' sur Slack.'
    ),
    dom('p.status',
      active
        ? [
          dom('b.active', active), ' utilisateurs connectés et ',
          dom('b.total', total), ' inscrits.'
        ]
        : [dom('b.total', total), ' inscrits jusqu\'à présent.']
    ),
    dom('form id=invite',
      channels && (
        channels.length > 1
          // channel selection when there are multiple
          ? dom('select.form-item name=channel',
              channels.map(channel => {
                return dom('option', { value: channel, text: channel })
              })
            )
          // otherwise a fixed channel
          : dom('input type=hidden name=channel', { value: channels[0] })
      ),
      dom('input.form-item type=email name=email placeholder=vous@votredomaine.com '
        + (!iframe ? 'autofocus' : '')),
      dom('br'),
      dom(`div class="g-recaptcha" data-sitekey="${gcaptcha_sitekey}"`),
      coc && dom('.coc',
        dom('label',
          dom('input type=checkbox name=coc value=1'),
          'J\'accepte les règles de conduite',
          dom('a', { href: coc, target: '_blank' }, 'Règles de conduite'),
          '.'
        )
      ),
      dom('button.loading', 'Recevoir mon invitation')
    ),
    dom('p.signin',
      'ou ',
      dom(`a href=https://${org}.slack.com target=_top`, 's\'identifier'),
      '.'
    ),
    !iframe && dom('footer',
      'propulsé par ',
      dom('a href=http://rauchg.com/slackin target=_blank', 'slackin')
    ),
    style({ logo, active, large, iframe }),
    // xxx: single build
    dom('script', `
      data = {};
      data.path = ${JSON.stringify(path)};
    `),
    dom('script src=https://cdn.socket.io/socket.io-1.4.4.js'),
    dom(`script src=${path}assets/superagent.js`),
    dom(`script src=${path}assets/client.js`)
  )
  return div
}

const pink = '#E01563'

function style ({ logo, active, large, iframe } = {}){
  var css = dom.style()
  css.add('html', { 'font-size': large ? '14px' : '10px' })

  css.add('.splash', {
    'width': iframe ? '25rem' : '30rem',
    'margin': iframe ? '0' : '20rem auto',
    'text-align': 'center',
    'font-family': '"Helvetica Neue", Helvetica, Arial'
  })

  if (iframe) {
    css.add('body, html', {
      'margin': '0',
      'padding': '0',
      'background': '#FAFAFA',
      'overflow': 'hidden' // ff
    })

    css.add('.splash', {
      'box-sizing': 'border-box',
      'padding': '1rem'
    })
  }

  if (!iframe) {
    css
    .media('(max-width: 50rem)')
    .add('.splash', {
      'margin-top': '10rem'
    })
  }

  css.add('.head', {
    'margin-bottom': '4rem'
  })

  css.add('.logos', {
    'position': 'relative',
    'margin-bottom': '4rem'
  })

  if (!iframe) {
    css.add('.logo', {
      'width': '4.8rem',
      'height': '4.8rem',
      'display': 'inline-block',
      'background-size': 'cover'
    })

    css.add('.logo.slack', {
      'background-image': 'url(assets/slack.svg)'
    })

    if (logo) {
      let pw = 1 // '+' width in rem
      let lp = 3 // logos separation in rem

      css.add('.logo.org::after', {
        'position': 'absolute',
        'display': 'block',
        'content': '"+"',
        'top': '1.5rem',
        'left': '0',
        'width': '30rem',
        'text-align': 'center',
        'color': '#D6D6D6',
        'font-size': '1.5rem', // can't use rem in font shorthand IE9-10
              // http://codersblock.com/blog/font-shorthand-bug-in-ie10/
        'font-family': 'Helvetica Neue'
      })

      css.add('.logo.org', {
        'background-image': `url(${logo})`,
        'margin-right': `${lp + pw + lp}rem`
      })
    }
  }

  css.add('.coc', {
    'font-size': '1.2rem',
    padding: '1.5rem 0 .5rem',
    color: '#666'
  })

  if (iframe) {
    css.add('.coc', {
      'font-size': '1.1rem',
      'padding-top': '1rem'
    })

    css.add('.coc input', {
      position: 'relative',
      top: '-.2rem'
    })
  }

  css.add('.coc label', {
    cursor: 'pointer'
  })

  css.add('.coc input', {
    'appearance': 'none',
    '-webkit-appearance': 'none',
    border: 'none',
    'vertical-align': 'middle',
    margin: '0 .5rem 0 0'
  })

  css.add('.coc input::after', {
    content: '""',
    display: 'inline-block',
    width: '1.5rem',
    height: '1.5rem',
    'vertical-align': 'middle',
    background: 'url(/assets/checkbox.svg)',
    cursor: 'pointer'
  })

  css.add('.coc input:checked::after', {
    'background-position': 'right'
  })

  css.add('.coc a', {
    color: '#666'
  })

  css.add('.coc a:hover', {
    'background-color': '#666',
    'text-decoration': 'none',
    color: '#fff'
  })

  css.add('p', {
    'font-size': iframe ? '1.2rem' : '1.5rem',
    'margin': iframe ? '0 0 .5rem' : '.5rem 0'
  })

  if (iframe) {
    css.add('p.status', {
      'font-size': '1.1rem'
    })
  }

  css.add('select', {
    'background': 'none'
  })

  css.add('button, .form-item', {
    'font-size': '1.2rem',
    'margin-top': iframe ? '.5rem' : '1rem',
    'vertical-align': 'middle',
    'display': 'block',
    'text-align': 'center',
    'box-sizing': 'border-box',
    'width': '100%',
    'padding': '.9rem'
  })

  css.add('button', {
    'color': '#fff',
    'font-weight': 'bold',
    'border-width': 0,
    'background': pink,
    'text-transform': 'uppercase',
    'cursor': 'pointer',
    'appearence': 'none',
    '-webkit-appearence': 'none',
    'outline': '0',
    'transition': 'background-color 150ms ease-in, color 150ms ease-in'
  })

  css.add('button.loading', {
    'pointer-events': 'none'
  })

  css.add('button:disabled', {
    'color': '#9B9B9B',
    'background-color': '#D6D6D6',
    'cursor': 'default',
    'pointer-events': 'none'
  })

  css.add('button.error', {
    'background-color': '#F4001E',
    'text-transform': 'none'
  })

  css.add('button.success:disabled', {
    'color': '#fff',
    'background-color': '#68C200'
  })

  css.add('button:not(.disabled):active', {
    'background-color': '#7A002F'
  })

  css.add('b', {
    'transition': 'transform 150ms ease-in'
  })

  css.add('b.grow', {
    'transform': 'scale(1.3)'
  })

  css.add('form', {
    'margin-top': iframe ? '1rem' : '2rem',
    'margin-bottom': '0'
  })

  css.add('input', {
    'color': '#9B9B9B',
    'border': '.1rem solid #D6D6D6'
  })

  if (iframe) {
    css.add('button, .form-item', {
      'height': '2.8rem',
      'line-height': '2.8rem',
      'padding': 0,
      'font-size': '1.1rem'
    })
  }

  css.add('input:focus', {
    'color': '#666',
    'border-color': '#999',
    'outline': '0'
  })

  if (active) {
    css.add('.active', {
      'color': pink
    })
  }

  css.add('p.signin', {
    'padding': '1rem 0 1rem',
    'font-size': '1.1rem'
  })

  css.add('p.signin a', {
    'color': pink,
    'text-decoration': 'none'
  })

  css.add('p.signin a:hover', {
    'background-color': '#E01563',
    color: '#fff'
  })

  if (!iframe) {
    css.add('footer', {
      'color': '#D6D6D6',
      'font-size': '1.1rem',
      'margin': '20rem auto 0',
      'width': '30rem',
      'text-align': 'center'
    })

    css.add('footer a', {
      'color': '#9B9B9B',
      'text-decoration': 'none',
      'border-bottom': '.1rem solid #9B9B9B'
    })

    css.add('footer a:hover', {
      'color': '#fff',
      'background-color': '#9B9B9B'
    })
  }

  return css
}
<|endoftext|>


<commit_before>/*The MIT License (MIT)

Copyright (c) 2015 Jens Malmborg

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.*/

var mat4 = require("/gl-matrix/mat4.js");

var Component = require('/entity.js').Component;
var Utils = require('/utils.js').Utils;
var Vector3 = require('/matrix.js').Vector3;

var vertexDeclaration = [
    { name: 'position', type: 'vec2' },
    { name: 'rotation', type: 'float' },
    { name: 'source', type: 'vec4' },
    { name: 'size', type: 'vec2' },
    { name: 'origin', type: 'vec2' },
    { name: 'scaling', type: 'vec2' },
    { name: 'color', type: 'vec4' },
    { name: 'text', type: 'float' }
];

function DefaultSprite() {
    this.position = { x: 0, y: 0 };
    this.source = { x: 0, y: 0, w: 0, h: 0 };
    this.scaling = { x: 1, y: 1 };
    this.rotation = 0;
    this.origin = { x: 0, y: 0 };
    this.color = { r: 1, g: 1, b: 1, a: 1 };
    this.drawAsText = 0;
}

// Creates an array with vertex data that is later stored in the vertex buffer.
// The order of these properties must match the above vertex declaration.
DefaultSprite.prototype.toArray = function () {
    return [
        // Position
        this.position.x, this.position.y,
        // Rotation
        this.rotation,
        // Source
        this.source.x, this.source.y, this.source.w, this.source.h,
        // Texture Size
        this.texture.width, this.texture.height,
        // Origin
        this.origin.x, this.origin.y,
        // Scaling
        this.scaling.x, this.scaling.y,
        // Color
        this.color.r, this.color.g, this.color.b, this.color.a,
        // Draw as text
        this.drawAsText
    ];
};

DefaultSprite.prototype.resetAsSprite = function (sprite, texture) {
    this.texture = texture;
    if (sprite.position) {
        this.position.x = sprite.position.x;
        this.position.y = sprite.position.y;
    } else {
        this.position.x = 0;
        this.position.y = 0;
    }
    if (sprite.scaling) {
        this.scaling.x = sprite.scaling.x;
        this.scaling.y = sprite.scaling.y;
    } else {
        this.scaling.x = 1;
        this.scaling.y = 1;
    }
    if (sprite.origin) {
        this.origin.x = sprite.origin.x;
        this.origin.y = sprite.origin.y;
    } else {
        this.origin.x = 0;
        this.origin.y = 0;
    }
    if (sprite.color) {
        this.color.r = sprite.color.r;
        this.color.g = sprite.color.g;
        this.color.b = sprite.color.b;
        this.color.a = sprite.color.a;
    } else {
        this.color.r = 1;
        this.color.g = 1;
        this.color.b = 1;
        this.color.a = 1;
    }
    if (sprite.source) {
        this.source.x = sprite.source.x;
        this.source.y = sprite.source.y;
        this.source.w = sprite.source.w;
        this.source.h = sprite.source.h;
    } else {
        this.source.x = 0;
        this.source.y = 0;
        this.source.w = texture.width;
        this.source.h = texture.height;
    }
    if (sprite.rotation) {
        this.rotation = sprite.rotation;
    } else {
        this.rotation = 0;
    }
    if (sprite.drawAsText) {
        this.drawAsText = sprite.drawAsText;
    } else {
        this.drawAsText = 0;
    }
    return this;
}

DefaultSprite.prototype.resetAsText = function (text) {
    defaultSprite = this.resetAsSprite(text, text.font.texture);
    defaultSprite.drawAsText = 1;
    if (text.baseline) {
        defaultSprite.baseline = text.baseline;
    } else {
        defaultSprite.baseline = 'top';
    }
    return defaultSprite;
}

var defaultSprite = new DefaultSprite();

function SpriteBatch (window) {
    this.graphics = window.graphics;
    this.shaderProgram = new ShaderProgram(this.graphics,
        module.path + 'assets/shaders/sprite');
    this.shaderProgram.projection = mat4.ortho(
        mat4.create(), 0, window.width, window.height, 0, 0, -1);
    this.vertexDataState = new VertexDataState(this.graphics);
    this.vertexDataState.setVertexDeclaration(vertexDeclaration,
        this.shaderProgram);
    this.texture = null;
    this.vertices = [];
    this.drawCount = 0;
}

SpriteBatch.prototype.begin = function () {
    this.graphics.setVertexDataState(this.vertexDataState);
    this.graphics.setShaderProgram(this.shaderProgram);
    this.graphics.setDepthState('none');
    this.graphics.setBlendState('alphaBlend');
};

SpriteBatch.prototype.end = function () {
    this.flush();
};

SpriteBatch.prototype.draw = function (sprite) {
    if (typeof sprite !== 'object') {
        throw new TypeError('Argument must be an object');
    }
    if (!sprite.texture) {
        throw new TypeError('Missing texture when drawing sprite.');
    }
    defaultSprite.resetAsSprite(sprite, sprite.texture);
    if (defaultSprite.texture !== this.texture) {
        this.flush();
    }
    this.texture = defaultSprite.texture;
    //this.vertices.push.apply(this.vertices, defaultSprite.toArray());
    this.vertices.push.apply(this.vertices, sprite.vertexArray());
    this.drawCount++;
};

SpriteBatch.prototype.drawText = function (text) {
    if (typeof text !== 'object') {
        throw new TypeError('Argument must be an object');
    }
    if (!text.font) {
        throw new TypeError('Missing font when drawing text.');
    }
    if (typeof text.text !== 'string') {
        throw new TypeError('Missing text when drawing text.');
    }
    defaultSprite.resetAsText(text);
    var origin = { x: defaultSprite.origin.x, y: 0 };

    if (defaultSprite.baseline === 'top') {
        var glyph = text.font.glyphs[text.text[0]];
        origin.y = defaultSprite.origin.y - glyph.offset.y;
    }
    if (defaultSprite.baseline === 'middle') {
        var glyph = text.font.glyphs[text.text[0]];
        origin.y = defaultSprite.origin.y - glyph.offset.y / 2;
    }
    if (defaultSprite.baseline === 'alphabetic') {
        origin.y = defaultSprite.origin.y;
    }

    for (var i = 0; i < text.text.length; i++) {
        var glyph = text.font.glyphs[text.text.charAt(i)];
        defaultSprite.source.x = glyph.source.x;
        defaultSprite.source.y = glyph.source.y;
        defaultSprite.source.w = glyph.source.w;
        defaultSprite.source.h = glyph.source.h;
        defaultSprite.origin.x = origin.x - glyph.offset.x;
        defaultSprite.origin.y = origin.y + glyph.offset.y;
        this.draw(defaultSprite);
        origin.x -= glyph.advance.x;
        origin.y -= glyph.advance.y;
    }
};

SpriteBatch.prototype.flush = function () {
    if (!this.texture || this.vertices.length === 0) {
        return;
    }
    this.graphics.textures[0] = this.texture;
    //this.vertexBuffer.setData(this.vertices, 'dynamic');
    this.vertexDataState.setVertices(this.vertices, 'dynamic');
    this.graphics.drawPrimitives({
        primitiveType: 'pointList',
        vertexStart: 0,
        primitiveCount: this.drawCount
    });
    this.vertices = [];
    this.drawCount = 0;
};

var Sprite = function (spriteBatch, texture) {
    if (typeof texture === 'string') {
        this.texture = new Texture2D(texture);
    } else {
        this.texture = texture;
    }
    this.position = new Vector3();
    this.scaling = new Vector3(1,1,0);
    this.rotation = new Vector3();
    this.color = { r: 1, g: 1, b: 1, a: 1 };
    this.visible = true;
    this.origin = { x: 0, y: 0 };
    this.spriteBatch = spriteBatch;
};

Sprite.prototype.center = function () {
  this.origin.x = this.texture.width / 2;
  this.origin.y = this.texture.height / 2;
};

Sprite.prototype.update = function (elapsed) {
  if (this.animation) {
    this.animation.update(elapsed);
  }
};

Sprite.prototype.draw = function () {
  if (!this.visible) {
    return;
  }
  if (this.animation) {
    options.texture = this.animation.texture();
  }
  this.spriteBatch.draw(this);
};

Sprite.prototype.vertexArray = function () {
    return [
        // Position
        this.position.x(), this.position.y(),
        // Rotation
        this.rotation,
        // Source
        //this.source.x, this.source.y, this.source.w, this.source.h,
        0,0,this.texture.width, this.texture.height,
        // Texture Size
        this.texture.width, this.texture.height,
        // Origin
        this.origin.x, this.origin.y,
        // Scaling
        this.scaling.x(), this.scaling.y(),
        // Color
        this.color.r, this.color.g, this.color.b, this.color.a,
        // Draw as text
        false
    ];
};

var SpriteAnimation = function (textures, fps) {
  this.textures = [];
  for (var i = 0; i < textures.length; i++) {
    if (typeof textures[i] === 'string') {
      this.textures.push(new Texture2D(textures[i]));
    } else {
      this.textures.push(textures[i]);
    }
  }
  this.elapsed = 0;
  this.frame = 0;
  this.fps = fps || 20;
};

SpriteAnimation.prototype.update = function (elapsed) {
  this.elapsed += elapsed;
  if (this.elapsed >= 1.0 / this.fps) {
    this.frame = (this.frame + 1) % this.textures.length;
    this.elapsed -= 1.0 / this.fps;
  }
};

SpriteAnimation.prototype.texture = function () {
  return this.textures[this.frame];
};

function SpriteComponent(spriteBatch, texture) {
    Component.call(this);
    this.spriteBatch = spriteBatch;
    this.sprite = new Sprite(spriteBatch, texture);
}

var _sprites = [];

SpriteComponent.draw = function() {
    if (_sprites.length === 0) {
        return;
    }
    var batches = [];
    for (var i=0; i<_sprites.length; i++) {
        if (!_sprites[i].spriteBatch.hasBegun) {
            _sprites[i].spriteBatch.begin();
            batches.push(_sprites[i].spriteBatch);
            _sprites[i].spriteBatch.hasBegun = true;
        }
        _sprites[i].draw();
    }
    for (i=0; i<batches.length; i++) {
        batches[i].end();
        _sprites[i].spriteBatch.hasBegun = false;
    }
    _sprites = [];
};

Utils.extend(SpriteComponent, Component);

SpriteComponent.prototype.draw = (function() {
    var _scaling = new Vector3(1,1,0);

    return function () {
        var transform = this.entity.transform;
        this.sprite.position.set(transform.position);
        this.sprite.rotation = transform.eulerAngles.z();
        this.sprite.scaling.set(transform.scaling);
        _sprites.push(this.sprite);
    }
})();

module.exports.SpriteBatch = SpriteBatch;
module.exports.Sprite = Sprite;
module.exports.SpriteAnimation = SpriteAnimation;
module.exports.SpriteComponent = SpriteComponent;<commit_msg>Minor update.<commit_after>/*The MIT License (MIT)

Copyright (c) 2015 Jens Malmborg

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.*/

var mat4 = require("/gl-matrix/mat4.js");

var Component = require('/entity.js').Component;
var Utils = require('/utils.js').Utils;
var Vector3 = require('/matrix.js').Vector3;

var vertexDeclaration = [
    { name: 'position', type: 'vec2' },
    { name: 'rotation', type: 'float' },
    { name: 'source', type: 'vec4' },
    { name: 'size', type: 'vec2' },
    { name: 'origin', type: 'vec2' },
    { name: 'scaling', type: 'vec2' },
    { name: 'color', type: 'vec4' },
    { name: 'text', type: 'float' }
];

function DefaultSprite() {
    this.position = { x: 0, y: 0 };
    this.source = { x: 0, y: 0, w: 0, h: 0 };
    this.scaling = { x: 1, y: 1 };
    this.rotation = 0;
    this.origin = { x: 0, y: 0 };
    this.color = { r: 1, g: 1, b: 1, a: 1 };
    this.drawAsText = 0;
}

// Creates an array with vertex data that is later stored in the vertex buffer.
// The order of these properties must match the above vertex declaration.
DefaultSprite.prototype.toArray = function () {
    return [
        // Position
        this.position.x, this.position.y,
        // Rotation
        this.rotation,
        // Source
        this.source.x, this.source.y, this.source.w, this.source.h,
        // Texture Size
        this.texture.width, this.texture.height,
        // Origin
        this.origin.x, this.origin.y,
        // Scaling
        this.scaling.x, this.scaling.y,
        // Color
        this.color.r, this.color.g, this.color.b, this.color.a,
        // Draw as text
        this.drawAsText
    ];
};

DefaultSprite.prototype.resetAsSprite = function (sprite, texture) {
    this.texture = texture;
    if (sprite.position) {
        this.position.x = sprite.position.x;
        this.position.y = sprite.position.y;
    } else {
        this.position.x = 0;
        this.position.y = 0;
    }
    if (sprite.scaling) {
        this.scaling.x = sprite.scaling.x;
        this.scaling.y = sprite.scaling.y;
    } else {
        this.scaling.x = 1;
        this.scaling.y = 1;
    }
    if (sprite.origin) {
        this.origin.x = sprite.origin.x;
        this.origin.y = sprite.origin.y;
    } else {
        this.origin.x = 0;
        this.origin.y = 0;
    }
    if (sprite.color) {
        this.color.r = sprite.color.r;
        this.color.g = sprite.color.g;
        this.color.b = sprite.color.b;
        this.color.a = sprite.color.a;
    } else {
        this.color.r = 1;
        this.color.g = 1;
        this.color.b = 1;
        this.color.a = 1;
    }
    if (sprite.source) {
        this.source.x = sprite.source.x;
        this.source.y = sprite.source.y;
        this.source.w = sprite.source.w;
        this.source.h = sprite.source.h;
    } else {
        this.source.x = 0;
        this.source.y = 0;
        this.source.w = texture.width;
        this.source.h = texture.height;
    }
    if (sprite.rotation) {
        this.rotation = sprite.rotation;
    } else {
        this.rotation = 0;
    }
    if (sprite.drawAsText) {
        this.drawAsText = sprite.drawAsText;
    } else {
        this.drawAsText = 0;
    }
    return this;
}

DefaultSprite.prototype.resetAsText = function (text) {
    defaultSprite = this.resetAsSprite(text, text.font.texture);
    defaultSprite.drawAsText = 1;
    if (text.baseline) {
        defaultSprite.baseline = text.baseline;
    } else {
        defaultSprite.baseline = 'top';
    }
    return defaultSprite;
}

var defaultSprite = new DefaultSprite();

function SpriteBatch (window) {
    this.graphics = window.graphics;
    this.shaderProgram = new ShaderProgram(this.graphics,
        module.path + 'assets/shaders/sprite');
    this.shaderProgram.projection = mat4.ortho(
        mat4.create(), 0, window.width, window.height, 0, 0, -1);
    this.vertexDataState = new VertexDataState(this.graphics);
    this.vertexDataState.setVertexDeclaration(vertexDeclaration,
        this.shaderProgram);
    this.texture = null;
    this.vertices = [];
    this.drawCount = 0;
}

SpriteBatch.prototype.begin = function () {
    this.graphics.setVertexDataState(this.vertexDataState);
    this.graphics.setShaderProgram(this.shaderProgram);
    this.graphics.setDepthState('none');
    this.graphics.setBlendState('alphaBlend');
};

SpriteBatch.prototype.end = function () {
    this.flush();
};

SpriteBatch.prototype.draw = function (sprite) {
    if (typeof sprite !== 'object') {
        throw new TypeError('Argument must be an object');
    }
    if (!sprite.texture) {
        throw new TypeError('Missing texture when drawing sprite.');
    }
    defaultSprite.resetAsSprite(sprite, sprite.texture);
    if (defaultSprite.texture !== this.texture) {
        this.flush();
    }
    this.texture = defaultSprite.texture;
    //this.vertices.push.apply(this.vertices, defaultSprite.toArray());
    this.vertices.push.apply(this.vertices, sprite.vertexArray());
    this.drawCount++;
};

SpriteBatch.prototype.drawText = function (text) {
    if (typeof text !== 'object') {
        throw new TypeError('Argument must be an object');
    }
    if (!text.font) {
        throw new TypeError('Missing font when drawing text.');
    }
    if (typeof text.text !== 'string') {
        throw new TypeError('Missing text when drawing text.');
    }
    defaultSprite.resetAsText(text);
    var origin = { x: defaultSprite.origin.x, y: 0 };

    if (defaultSprite.baseline === 'top') {
        var glyph = text.font.glyphs[text.text[0]];
        origin.y = defaultSprite.origin.y - glyph.offset.y;
    }
    if (defaultSprite.baseline === 'middle') {
        var glyph = text.font.glyphs[text.text[0]];
        origin.y = defaultSprite.origin.y - glyph.offset.y / 2;
    }
    if (defaultSprite.baseline === 'alphabetic') {
        origin.y = defaultSprite.origin.y;
    }

    for (var i = 0; i < text.text.length; i++) {
        var glyph = text.font.glyphs[text.text.charAt(i)];
        defaultSprite.source.x = glyph.source.x;
        defaultSprite.source.y = glyph.source.y;
        defaultSprite.source.w = glyph.source.w;
        defaultSprite.source.h = glyph.source.h;
        defaultSprite.origin.x = origin.x - glyph.offset.x;
        defaultSprite.origin.y = origin.y + glyph.offset.y;
        this.draw(defaultSprite);
        origin.x -= glyph.advance.x;
        origin.y -= glyph.advance.y;
    }
};

SpriteBatch.prototype.flush = function () {
    if (!this.texture || this.vertices.length === 0) {
        return;
    }
    this.graphics.textures[0] = this.texture;
    //this.vertexBuffer.setData(this.vertices, 'dynamic');
    this.vertexDataState.setVertices(this.vertices, 'dynamic');
    this.graphics.drawPrimitives({
        primitiveType: 'pointList',
        vertexStart: 0,
        primitiveCount: this.drawCount
    });
    this.vertices = [];
    this.drawCount = 0;
};

var Sprite = function (spriteBatch, texture) {
    if (typeof texture === 'string') {
        this.texture = new Texture2D(texture);
    } else {
        this.texture = texture;
    }
    this.position = new Vector3();
    this.scaling = new Vector3(1,1,0);
    this.rotation = new Vector3();
    this.color = { r: 1, g: 1, b: 1, a: 1 };
    this.visible = true;
    this.origin = { x: 0, y: 0 };
    this.spriteBatch = spriteBatch;
};

Sprite.prototype.center = function () {
  this.origin.x = this.texture.width / 2;
  this.origin.y = this.texture.height / 2;
};

Sprite.prototype.update = function (elapsed) {
  if (this.animation) {
    this.animation.update(elapsed);
  }
};

Sprite.prototype.draw = function () {
  if (!this.visible) {
    return;
  }
  if (this.animation) {
    options.texture = this.animation.texture();
  }
  this.spriteBatch.draw(this);
};

Sprite.prototype.vertexArray = function () {
    return [
        // Position
        this.position.x(), this.position.y(),
        // Rotation
        this.rotation,
        // Source
        //this.source.x, this.source.y, this.source.w, this.source.h,
        0,0,this.texture.width, this.texture.height,
        // Texture Size
        this.texture.width, this.texture.height,
        // Origin
        this.origin.x, this.origin.y,
        // Scaling
        this.scaling.x(), this.scaling.y(),
        // Color
        this.color.r, this.color.g, this.color.b, this.color.a,
        // Draw as text
        false
    ];
};

var SpriteAnimation = function (textures, fps) {
  this.textures = [];
  for (var i = 0; i < textures.length; i++) {
    if (typeof textures[i] === 'string') {
      this.textures.push(new Texture2D(textures[i]));
    } else {
      this.textures.push(textures[i]);
    }
  }
  this.elapsed = 0;
  this.frame = 0;
  this.fps = fps || 20;
};

SpriteAnimation.prototype.update = function (elapsed) {
  this.elapsed += elapsed;
  if (this.elapsed >= 1.0 / this.fps) {
    this.frame = (this.frame + 1) % this.textures.length;
    this.elapsed -= 1.0 / this.fps;
  }
};

SpriteAnimation.prototype.texture = function () {
  return this.textures[this.frame];
};

function SpriteComponent(spriteBatch, texture) {
    Component.call(this);
    this.spriteBatch = spriteBatch;
    this.sprite = new Sprite(spriteBatch, texture);
}

var _sprites = [];

SpriteComponent.draw = function() {
    if (_sprites.length === 0) {
        return;
    }
    var batches = [];
    for (var i=0; i<_sprites.length; i++) {
        if (!_sprites[i].spriteBatch.hasBegun) {
            _sprites[i].spriteBatch.begin();
            batches.push(_sprites[i].spriteBatch);
            _sprites[i].spriteBatch.hasBegun = true;
        }
        _sprites[i].draw();
    }
    for (i=0; i<batches.length; i++) {
        batches[i].end();
        _sprites[i].spriteBatch.hasBegun = false;
    }
    _sprites = [];
};

Utils.extend(SpriteComponent, Component);

SpriteComponent.prototype.draw = function () {
    var transform = this.entity.transform;
    this.sprite.position.set(transform.position);
    this.sprite.rotation = transform.eulerAngles.z();
    this.sprite.scaling.set(transform.scaling);
    _sprites.push(this.sprite);
};

module.exports.SpriteBatch = SpriteBatch;
module.exports.Sprite = Sprite;
module.exports.SpriteAnimation = SpriteAnimation;
module.exports.SpriteComponent = SpriteComponent;<|endoftext|>


<commit_before>"use strict";

var dgram         = require('dgram'),
    util          = require('util'),
    dns           = require('dns'),
    helpers       = require('./helpers'),
    applyStatsFns = require('./statsFunctions');

/**
 * The UDP Client for StatsD
 * @param options
 *   @option host        {String}  The host to connect to default: localhost
 *   @option port        {String|Integer} The port to connect to default: 8125
 *   @option prefix      {String}  An optional prefix to assign to each stat name sent
 *   @option suffix      {String}  An optional suffix to assign to each stat name sent
 *   @option globalize   {boolean} An optional boolean to add "statsd" as an object in the global namespace
 *   @option cacheDns    {boolean} An optional option to only lookup the hostname -> ip address once
 *   @option mock        {boolean} An optional boolean indicating this Client is a mock object, no stats are sent.
 *   @option globalTags {Array=} Optional tags that will be added to every metric
 *   @option errorHandler {Function=} Optional function to handle errors when callback is not provided
 *   @maxBufferSize      {Number} An optional value for aggregating metrics to send, mainly for performance improvement
 *   @bufferFlushInterval {Number} the time out value to flush out buffer if not
 *   @option sampleRate {Float} Global Sampling rate, default: 1 (No sampling)
 *   @option useDefaultRoute {boolean} An optional boolean to use the default route on Linux. Useful for containers
 * @constructor
 */
var Client = function (host, port, prefix, suffix, globalize, cacheDns, mock,
    globalTags, maxBufferSize, bufferFlushInterval, telegraf, sampleRate) {
  var options = host || {},
         self = this;

  if(arguments.length > 1 || typeof(host) === 'string'){
    options = {
      host        : host,
      port        : port,
      prefix      : prefix,
      suffix      : suffix,
      globalize   : globalize,
      cacheDns    : cacheDns,
      mock        : mock === true,
      globalTags  : globalTags,
      maxBufferSize : maxBufferSize,
      bufferFlushInterval: bufferFlushInterval,
      telegraf    : telegraf,
      sampleRate    : sampleRate
    };
  }

  // hidden global_tags option for backwards compatibility
  options.globalTags = options.globalTags || options.global_tags;

  this.host        = options.host || 'localhost';
  this.port        = options.port || 8125;
  this.prefix      = options.prefix || '';
  this.suffix      = options.suffix || '';
  this.socket      = options.isChild ? options.socket : dgram.createSocket('udp4');
  this.mock        = options.mock;
  this.globalTags  = typeof options.globalTags === "object" ?
      helpers.formatTags(options.globalTags, options.telegraf) : [];
  this.telegraf    = options.telegraf || false;
  this.maxBufferSize = options.maxBufferSize || 0;
  this.sampleRate  = options.sampleRate || 1;
  this.bufferFlushInterval = options.bufferFlushInterval || 1000;
  this.bufferHolder = options.isChild ? options.bufferHolder : { buffer: "" };
  this.errorHandler = options.errorHandler;

  // We only want a single flush event per parent and all its child clients
  if(!options.isChild && this.maxBufferSize > 0) {
    this.intervalHandle = setInterval(this.onBufferFlushInterval.bind(this), this.bufferFlushInterval);
  }

  if (options.isChild) {
    if (options.dnsError) {
      this.dnsError = options.dnsError;
    }
  } else if (options.cacheDns === true){
    dns.lookup(options.host, function(err, address, family){
      if(err === null){
        self.host = address;
      } else {
        self.dnsError = err;
      }
    });
  }

  if (!options.isChild && options.errorHandler) {
    this.socket.on('error', options.errorHandler);
  }

  if(options.globalize){
    global.statsd = this;
  }

  if(options.useDefaultRoute) {
    var defaultRoute = helpers.getDefaultRoute();
    if (defaultRoute) {
      console.log('Got ' + defaultRoute + " for the system's default route");
      this.host = defaultRoute;
    }
  }

  this.CHECKS = {
    OK: 0,
    WARNING: 1,
    CRITICAL: 2,
    UNKNOWN: 3,
  };
};

applyStatsFns(Client);

/**
 * Checks if stats is an array and sends all stats calling back once all have sent
 * @param stat {String|Array} The stat(s) to send
 * @param value The value to send
 * @param type The type of the metric
 * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional.
 * @param tags {Array=} The Array of tags to add to metrics. Optional.
 * @param callback {Function=} Callback when message is done being delivered. Optional.
 */
Client.prototype.sendAll = function(stat, value, type, sampleRate, tags, callback){
  var completed = 0,
      calledback = false,
      sentBytes = 0,
      self = this;

  if(sampleRate && typeof sampleRate !== 'number'){
    callback = tags;
    tags = sampleRate;
    sampleRate = undefined;
  }

  if(tags && typeof tags !== "object"){
    callback = tags;
    tags = undefined;
  }

  /**
   * Gets called once for each callback, when all callbacks return we will
   * call back from the function
   * @private
   */
  function onSend(error, bytes){
    completed += 1;
    if(calledback){
      return;
    }

    if(error){
      if (typeof callback === 'function') {
        calledback = true;
        callback(error);
      }
      else if (self.errorHandler) {
        calledback = true;
        self.errorHandler(error);
      }
      return;
    }

    sentBytes += bytes;
    if(completed === stat.length && typeof callback === 'function'){
      callback(null, sentBytes);
    }
  }

  if(Array.isArray(stat)){
    stat.forEach(function(item){
      self.sendStat(item, value, type, sampleRate, tags, onSend);
    });
  } else {
    this.sendStat(stat, value, type, sampleRate, tags, callback);
  }
};

/**
 * Sends a stat across the wire
 * @param stat {String|Array} The stat(s) to send
 * @param value The value to send
 * @param type {String} The type of message to send to statsd
 * @param sampleRate {Number} The Number of times to sample (0 to 1)
 * @param tags {Array} The Array of tags to add to metrics
 * @param callback {Function=} Callback when message is done being delivered. Optional.
 */
Client.prototype.sendStat = function (stat, value, type, sampleRate, tags, callback) {
  var message = this.prefix + stat + this.suffix + ':' + value + '|' + type;

  sampleRate = sampleRate || this.sampleRate;
  if(sampleRate && sampleRate < 1){
    if(Math.random() < sampleRate){
      message += '|@' + sampleRate;
    } else {
      //don't want to send if we don't meet the sample ratio
      return callback ? callback() : undefined;
    }
  }
  this.send(message, tags, callback);
};

/**
 * Send a stat or event across the wire
 * @param message {String} The constructed message without tags
 * @param tags {Array} The tags to include (along with global tags). Optional.
 * @param callback {Function=} Callback when message is done being delivered (only if maxBufferSize == 0). Optional.
 */
Client.prototype.send = function (message, tags, callback) {
  var mergedTags = this.globalTags;
  if(tags && typeof tags === "object"){
    mergedTags = helpers.overrideTags(mergedTags, tags, this.telegraf);
  }
  if(mergedTags.length > 0){
    if (this.telegraf) {
      message = message.split(':');
      message = message[0] + ',' + mergedTags.join(',').replace(/:/g, '=') + ':' + message.slice(1).join(':');
    } else {
      message += '|#' + mergedTags.join(',');
    }
  }
  this._send(message, callback);
};

/**
 * Send a stat or event across the wire
 * @param message {String} The constructed message without tags
 * @param callback {Function=} Callback when message is done being delivered (only if maxBufferSize == 0). Optional.
 */
Client.prototype._send = function (message, callback) {
  // we may have a cached error rather than a cached lookup, so
  // throw it on
  if (this.dnsError) {
    if (callback) {
      return callback(this.dnsError);
    }
    else if (this.errorHandler) {
      return this.errorHandler(this.dnsError);
    }
    throw this.dnsError;
  }

  // Only send this stat if we're not a mock Client.
  if(!this.mock) {
    if(this.maxBufferSize === 0) {
      this.sendMessage(message, callback);
    }
    else {
      this.enqueue(message, callback);
    }
  }
  else {
    if(typeof callback === 'function'){
      callback(null, 0);
    }
  }
};

/**
 * Add the message to the buffer and flush the buffer if needed
 *
 * @param message {String} The constructed message without tags
 */
Client.prototype.enqueue = function(message, callback){
  message += "\n";

  if (this.bufferHolder.buffer.length + message.length > this.maxBufferSize) {
    this.flushQueue(callback);
    this.bufferHolder.buffer += message;
  }
  else {
    this.bufferHolder.buffer += message;
    if (callback) {
      callback(null);
    }
  }
};

/**
 * Flush the buffer, sending on the messages
 */
Client.prototype.flushQueue = function(callback){
  this.sendMessage(this.bufferHolder.buffer, callback);
  this.bufferHolder.buffer = "";
};

/**
 * Send on the message through the socket
 *
 * @param message {String} The constructed message without tags
 * @param callback {Function=} Callback when message is done being delivered. Optional.
 */
Client.prototype.sendMessage = function(message, callback){
  // Guard against "RangeError: Offset into buffer too large" in node 0.10
  // https://github.com/nodejs/node-v0.x-archive/issues/7884
  if (message === "") {
    if (callback) {
      callback(null);
    }
    return;
  }
  var buf = new Buffer(message);
  try {
    this.socket.send(buf, 0, buf.length, this.port, this.host, callback);
  }
  catch(err) {
    var errMessage = 'Error sending hot-shots message: ' + err;
    if (callback) {
      callback(new Error(errMessage));
    }
    else if (this.errorHandler) {
      this.errorHandler(new Error(errMessage));
    }
    else {
      console.log(errMessage);
    }
  }
};

/**
 * Called every bufferFlushInterval to flush any buffer that is around
 */
Client.prototype.onBufferFlushInterval = function() {
  this.flushQueue();
};

/**
 * Close the underlying socket and stop listening for data on it.
 */
Client.prototype.close = function(callback){
  if(this.intervalHandle) {
    clearInterval(this.intervalHandle);
  }

  this.flushQueue();

  if (callback) {
    // use the close event rather than adding a callback to close()
    // because that API is not available in older Node versions
    this.socket.on('close', callback);
  }

  try {
    this.socket.close();
  }
  catch (err) {
    var errMessage = 'Error closing hot-shots socket: ' + err;
    if (callback) {
      callback(new Error(errMessage));
    }
    else if (this.errorHandler) {
      this.errorHandler(new Error(errMessage));
    }
    else {
      console.log(errMessage);
    }
  }
};

var ChildClient = function (parent, options) {
  options = options || {};
  Client.call(this, {
    isChild     : true,
    socket      : parent.socket, // Child inherits socket from parent. Parent itself can be a child.
    // All children and parent share the same buffer via sharing an object (cannot mutate strings)
    bufferHolder: parent.bufferHolder,
    dnsError    : parent.dnsError, // Child inherits an error from parent (if it is there)
    errorHandler: options.errorHandler || parent.errorHandler, // Handler for callback errors

    host        : parent.host,
    port        : parent.port,
    prefix      : (options.prefix || '') + parent.prefix, // Child has its prefix prepended to parent's prefix
    suffix      : parent.suffix + (options.suffix || ''), // Child has its suffix appended to parent's suffix
    globalize   : false, // Only "root" client can be global
    mock        : parent.mock,
    // Append child's tags to parent's tags
    globalTags  : typeof options.globalTags === "object" ?
        helpers.overrideTags(parent.globalTags, options.globalTags, parent.telegraf) : parent.globalTags,
    maxBufferSize : parent.maxBufferSize,
    bufferFlushInterval: parent.bufferFlushInterval,
    telegraf    : parent.telegraf
  });
};
util.inherits(ChildClient, Client);

/**
 * Creates a child client that adds prefix, suffix and/or tags to this client. Child client can itself have children.
 * @param options
 *   @option prefix      {String}  An optional prefix to assign to each stat name sent
 *   @option suffix      {String}  An optional suffix to assign to each stat name sent
 *   @option globalTags {Array=} Optional tags that will be added to every metric
 */
Client.prototype.childClient = function(options) {
  return new ChildClient(this, options);
};

exports = module.exports = Client;
exports.StatsD = Client;
<commit_msg>Add TCP support<commit_after>"use strict";

var dgram         = require('dgram'),
    util          = require('util'),
    dns           = require('dns'),
    net           = require('net'),
    helpers       = require('./helpers'),
    applyStatsFns = require('./statsFunctions');

/**
 * The UDP Client for StatsD
 * @param options
 *   @option host        {String}  The host to connect to default: localhost
 *   @option port        {String|Integer} The port to connect to default: 8125
 *   @option prefix      {String}  An optional prefix to assign to each stat name sent
 *   @option suffix      {String}  An optional suffix to assign to each stat name sent
 *   @option globalize   {boolean} An optional boolean to add "statsd" as an object in the global namespace
 *   @option cacheDns    {boolean} An optional option to only lookup the hostname -> ip address once
 *   @option mock        {boolean} An optional boolean indicating this Client is a mock object, no stats are sent.
 *   @option globalTags {Array=} Optional tags that will be added to every metric
 *   @option errorHandler {Function=} Optional function to handle errors when callback is not provided
 *   @maxBufferSize      {Number} An optional value for aggregating metrics to send, mainly for performance improvement
 *   @bufferFlushInterval {Number} the time out value to flush out buffer if not
 *   @option sampleRate {Float} Global Sampling rate, default: 1 (No sampling)
 *   @option useDefaultRoute {boolean} An optional boolean to use the default route on Linux. Useful for containers
 * @constructor
 */
var Client = function (host, port, prefix, suffix, globalize, cacheDns, mock,
    globalTags, maxBufferSize, bufferFlushInterval, telegraf, sampleRate, protocol) {
  var options = host || {},
         self = this;

  if(arguments.length > 1 || typeof(host) === 'string'){
    options = {
      host        : host,
      port        : port,
      prefix      : prefix,
      suffix      : suffix,
      globalize   : globalize,
      cacheDns    : cacheDns,
      mock        : mock === true,
      globalTags  : globalTags,
      maxBufferSize : maxBufferSize,
      bufferFlushInterval: bufferFlushInterval,
      telegraf    : telegraf,
      sampleRate  : sampleRate,
      protocol    : protocol
    };
  }

  function createSocket(args) {
    return args.protocol === 'tcp' ? net.connect(args.port, args.host) : dgram.createSocket('udp4');
  }

  // hidden global_tags option for backwards compatibility
  options.globalTags = options.globalTags || options.global_tags;

  this.protocol = options.protocol;
  this.host        = options.host || 'localhost';
  this.port        = options.port || 8125;
  this.prefix      = options.prefix || '';
  this.suffix      = options.suffix || '';
  this.socket      = options.isChild ? options.socket : createSocket({
    host: this.host,
    port: this.port,
    protocol: this.protocol
  });
  this.mock        = options.mock;
  this.globalTags  = typeof options.globalTags === "object" ?
      helpers.formatTags(options.globalTags, options.telegraf) : [];
  this.telegraf    = options.telegraf || false;
  this.maxBufferSize = options.maxBufferSize || 0;
  this.sampleRate  = options.sampleRate || 1;
  this.bufferFlushInterval = options.bufferFlushInterval || 1000;
  this.bufferHolder = options.isChild ? options.bufferHolder : { buffer: "" };
  this.errorHandler = options.errorHandler;

  // We only want a single flush event per parent and all its child clients
  if(!options.isChild && this.maxBufferSize > 0) {
    this.intervalHandle = setInterval(this.onBufferFlushInterval.bind(this), this.bufferFlushInterval);
  }

  if (options.isChild) {
    if (options.dnsError) {
      this.dnsError = options.dnsError;
    }
  } else if (options.cacheDns === true){
    dns.lookup(options.host, function(err, address, family){
      if(err === null){
        self.host = address;
      } else {
        self.dnsError = err;
      }
    });
  }

  if (!options.isChild && options.errorHandler) {
    this.socket.on('error', options.errorHandler);
  }

  if(options.globalize){
    global.statsd = this;
  }

  if (options.protocol === 'tcp') {
    this.socket.setKeepAlive(true);
  }

  if(options.useDefaultRoute) {
    var defaultRoute = helpers.getDefaultRoute();
    if (defaultRoute) {
      console.log('Got ' + defaultRoute + " for the system's default route");
      this.host = defaultRoute;
    }
  }

  this.CHECKS = {
    OK: 0,
    WARNING: 1,
    CRITICAL: 2,
    UNKNOWN: 3,
  };
};

applyStatsFns(Client);

/**
 * Checks if stats is an array and sends all stats calling back once all have sent
 * @param stat {String|Array} The stat(s) to send
 * @param value The value to send
 * @param type The type of the metric
 * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional.
 * @param tags {Array=} The Array of tags to add to metrics. Optional.
 * @param callback {Function=} Callback when message is done being delivered. Optional.
 */
Client.prototype.sendAll = function(stat, value, type, sampleRate, tags, callback){
  var completed = 0,
      calledback = false,
      sentBytes = 0,
      self = this;

  if(sampleRate && typeof sampleRate !== 'number'){
    callback = tags;
    tags = sampleRate;
    sampleRate = undefined;
  }

  if(tags && typeof tags !== "object"){
    callback = tags;
    tags = undefined;
  }

  /**
   * Gets called once for each callback, when all callbacks return we will
   * call back from the function
   * @private
   */
  function onSend(error, bytes){
    completed += 1;
    if(calledback){
      return;
    }

    if(error){
      if (typeof callback === 'function') {
        calledback = true;
        callback(error);
      }
      else if (self.errorHandler) {
        calledback = true;
        self.errorHandler(error);
      }
      return;
    }

    sentBytes += bytes;
    if(completed === stat.length && typeof callback === 'function'){
      callback(null, sentBytes);
    }
  }

  if(Array.isArray(stat)){
    stat.forEach(function(item){
      self.sendStat(item, value, type, sampleRate, tags, onSend);
    });
  } else {
    this.sendStat(stat, value, type, sampleRate, tags, callback);
  }
};

/**
 * Sends a stat across the wire
 * @param stat {String|Array} The stat(s) to send
 * @param value The value to send
 * @param type {String} The type of message to send to statsd
 * @param sampleRate {Number} The Number of times to sample (0 to 1)
 * @param tags {Array} The Array of tags to add to metrics
 * @param callback {Function=} Callback when message is done being delivered. Optional.
 */
Client.prototype.sendStat = function (stat, value, type, sampleRate, tags, callback) {
  var message = this.prefix + stat + this.suffix + ':' + value + '|' + type;

  sampleRate = sampleRate || this.sampleRate;
  if(sampleRate && sampleRate < 1){
    if(Math.random() < sampleRate){
      message += '|@' + sampleRate;
    } else {
      //don't want to send if we don't meet the sample ratio
      return callback ? callback() : undefined;
    }
  }
  this.send(message, tags, callback);
};

/**
 * Send a stat or event across the wire
 * @param message {String} The constructed message without tags
 * @param tags {Array} The tags to include (along with global tags). Optional.
 * @param callback {Function=} Callback when message is done being delivered (only if maxBufferSize == 0). Optional.
 */
Client.prototype.send = function (message, tags, callback) {
  var mergedTags = this.globalTags;
  if(tags && typeof tags === "object"){
    mergedTags = helpers.overrideTags(mergedTags, tags, this.telegraf);
  }
  if(mergedTags.length > 0){
    if (this.telegraf) {
      message = message.split(':');
      message = message[0] + ',' + mergedTags.join(',').replace(/:/g, '=') + ':' + message.slice(1).join(':');
    } else {
      message += '|#' + mergedTags.join(',');
    }
  }

  if (this.protocol === 'tcp') {
    message += '\n';
  }

  this._send(message, callback);
};

/**
 * Send a stat or event across the wire
 * @param message {String} The constructed message without tags
 * @param callback {Function=} Callback when message is done being delivered (only if maxBufferSize == 0). Optional.
 */
Client.prototype._send = function (message, callback) {
  // we may have a cached error rather than a cached lookup, so
  // throw it on
  if (this.dnsError) {
    if (callback) {
      return callback(this.dnsError);
    }
    else if (this.errorHandler) {
      return this.errorHandler(this.dnsError);
    }
    throw this.dnsError;
  }

  // Only send this stat if we're not a mock Client.
  if(!this.mock) {
    if(this.maxBufferSize === 0) {
      this.sendMessage(message, callback);
    }
    else {
      this.enqueue(message, callback);
    }
  }
  else {
    if(typeof callback === 'function'){
      callback(null, 0);
    }
  }
};

/**
 * Add the message to the buffer and flush the buffer if needed
 *
 * @param message {String} The constructed message without tags
 */
Client.prototype.enqueue = function(message, callback){
  message += "\n";

  if (this.bufferHolder.buffer.length + message.length > this.maxBufferSize) {
    this.flushQueue(callback);
    this.bufferHolder.buffer += message;
  }
  else {
    this.bufferHolder.buffer += message;
    if (callback) {
      callback(null);
    }
  }
};

/**
 * Flush the buffer, sending on the messages
 */
Client.prototype.flushQueue = function(callback){
  this.sendMessage(this.bufferHolder.buffer, callback);
  this.bufferHolder.buffer = "";
};

/**
 * Send on the message through the socket
 *
 * @param message {String} The constructed message without tags
 * @param callback {Function=} Callback when message is done being delivered. Optional.
 */
Client.prototype.sendMessage = function(message, callback){
  // Guard against "RangeError: Offset into buffer too large" in node 0.10
  // https://github.com/nodejs/node-v0.x-archive/issues/7884
  if (message === "") {
    if (callback) {
      callback(null);
    }
    return;
  }
  var buf = new Buffer(message);
  try {
    if(this.protocol === 'tcp') {
      this.socket.write(buf, 'ascii', callback);
    } else {
      this.socket.send(buf, 0, buf.length, this.port, this.host, callback);
    }
  }
  catch(err) {
    var errMessage = 'Error sending hot-shots message: ' + err;
    if (callback) {
      callback(new Error(errMessage));
    }
    else if (this.errorHandler) {
      this.errorHandler(new Error(errMessage));
    }
    else {
      console.log(errMessage);
    }
  }
};

/**
 * Called every bufferFlushInterval to flush any buffer that is around
 */
Client.prototype.onBufferFlushInterval = function() {
  this.flushQueue();
};

/**
 * Close the underlying socket and stop listening for data on it.
 */
Client.prototype.close = function(callback){
  if(this.intervalHandle) {
    clearInterval(this.intervalHandle);
  }

  this.flushQueue();

  if (callback) {
    // use the close event rather than adding a callback to close()
    // because that API is not available in older Node versions
    this.socket.on('close', callback);
  }

  try {
    this.socket.close();
  }
  catch (err) {
    var errMessage = 'Error closing hot-shots socket: ' + err;
    if (callback) {
      callback(new Error(errMessage));
    }
    else if (this.errorHandler) {
      this.errorHandler(new Error(errMessage));
    }
    else {
      console.log(errMessage);
    }
  }
};

var ChildClient = function (parent, options) {
  options = options || {};
  Client.call(this, {
    isChild     : true,
    socket      : parent.socket, // Child inherits socket from parent. Parent itself can be a child.
    // All children and parent share the same buffer via sharing an object (cannot mutate strings)
    bufferHolder: parent.bufferHolder,
    dnsError    : parent.dnsError, // Child inherits an error from parent (if it is there)
    errorHandler: options.errorHandler || parent.errorHandler, // Handler for callback errors

    host        : parent.host,
    port        : parent.port,
    prefix      : (options.prefix || '') + parent.prefix, // Child has its prefix prepended to parent's prefix
    suffix      : parent.suffix + (options.suffix || ''), // Child has its suffix appended to parent's suffix
    globalize   : false, // Only "root" client can be global
    mock        : parent.mock,
    // Append child's tags to parent's tags
    globalTags  : typeof options.globalTags === "object" ?
        helpers.overrideTags(parent.globalTags, options.globalTags, parent.telegraf) : parent.globalTags,
    maxBufferSize : parent.maxBufferSize,
    bufferFlushInterval: parent.bufferFlushInterval,
    telegraf    : parent.telegraf,
    protocol    : parent.protocol
  });
};
util.inherits(ChildClient, Client);

/**
 * Creates a child client that adds prefix, suffix and/or tags to this client. Child client can itself have children.
 * @param options
 *   @option prefix      {String}  An optional prefix to assign to each stat name sent
 *   @option suffix      {String}  An optional suffix to assign to each stat name sent
 *   @option globalTags {Array=} Optional tags that will be added to every metric
 */
Client.prototype.childClient = function(options) {
  return new ChildClient(this, options);
};

exports = module.exports = Client;
exports.StatsD = Client;
<|endoftext|>


<commit_before>'use strict';

var http = require('http'),
    https = require('https'),
    promise = require('promise'),
    options = require('./options');

module.exports = function(url) {
  return new promise(function(resolve, reject) {
    var parsedUrl = require('url').parse(url),
        protocol = (parsedUrl.protocol === 'http:') ? http : https;
    parsedUrl.method = (protocol === http) ? 'HEAD' : 'GET';

    var makeRequest = function() {
      var request = protocol
        .request(parsedUrl, function(response) {
          if (options.parsed.verbose) {
            console.log(response.statusCode, url);
          }
          resolve(response.statusCode);
        });

      request.on('error', function(error) {
        setTimeout(function() {
          makeRequest();
        }, 500);

      })

      request.end();

      request.setTimeout(500);
    };

    makeRequest();
  });
};
<commit_msg>Minor cleanup<commit_after>'use strict';

var http = require('http'),
    https = require('https'),
    promise = require('promise'),
    options = require('./options');

module.exports = function(url) {
  return new promise(function(resolve, reject) {
    var parsedUrl = require('url').parse(url),
        protocol = (parsedUrl.protocol === 'http:') ? http : https;
    parsedUrl.method = (protocol === http) ? 'HEAD' : 'GET';

    var makeRequest = function() {
      var request = protocol
        .request(parsedUrl, function(response) {
          if (options.parsed.verbose) {
            console.log(response.statusCode, url);
          }
          resolve(response.statusCode);
        });

      request.on('error', function(error) {
        setTimeout(function() {
          makeRequest();
        }, 500);
      });

      request.setTimeout(500);

      request.end();
    };

    makeRequest();
  });
};
<|endoftext|>