<commit_before>module.exports = Seeder

var WebTorrent = require('webtorrent')

function Seeder () {
  if (typeof window === 'undefined') throw new Error('must be called in a wep page')

  this.destroyed = false
  this.started = false
  this._seeds = {}
  this._webtorrent = null
}

Seeder.prototype.add = function (torrent) {
  if (this.destroyed) throw new Error('Seeder has been destroyed')
  if (torrent.closed || torrent.infoHash in this._seeds) return

  var listener = this._prioritize.bind(this, torrent)

  this._seeds[torrent.infoHash] = {
    torrent: torrent,
    listener: listener
  }

  if (this.started) {
    torrent._torrentDB.on('priority', listener)
    this._seed(torrent)
  }
}

Seeder.prototype.remove = function (infoHash) {
  if (this.destroyed) throw new Error('Seeder has been destroyed')
  if (!this._seeds[infoHash]) return

  var seed = this._seeds[infoHash]
  delete this._seeds[infoHash]

  if (this._webtorrent) this._webtorrent.remove(seed.torrent.infoHash)

  if (seed != null && !seed.torrent.closed) {
    seed.torrent._torrentDB.removeListener('priority', this._seeds[infoHash].listener)
  }
}

Seeder.prototype.start = function () {
  if (this.destroyed) throw new Error('Seeder is destroyed')
  if (this.started) return

  this.started = true
  this._webtorrent = this._webtorrent || new WebTorrent()

  for (var infoHash in this._seeds) {
    var torrent = this._seeds[infoHash].torrent
    if (torrent.closed) {
      delete this._seeds[infoHash]
    } else {
      torrent._torrentDB.on('priority', this._seeds[infoHash].listener)
      this._seed(torrent)
    }
  }
}

Seeder.prototype.stop = function () {
  if (this.destroyed || !this.started) return

  this.started = false
  if (this._webtorrent) this._webtorrent.destroy()
  this._webtorrent = null

  for (var infoHash in this._seeds) {
    var torrent = this._seeds[infoHash].torrent
    if (torrent.closed) delete this._seeds[infoHash]
    else torrent._torrentDB.removeListener('priority', this._seeds[infoHash].listener)
  }
}

Seeder.prototype._seed = function (torrent) {
  var self = this
  if (self.destroyed || self._webtorrent.get(torrent.infoHash)) return

  var opts = {store: wrapChunkStore(torrent)}
  var torrentId = torrent.torrentMetaBuffer ? torrent.torrentMetaBuffer : torrent.magnetURI
  self._webtorrent.add(torrentId, opts, function (wtorrent) {
    if (torrent.closed || self.destroyed) return

    torrent._torrentDB.updateMeta(wtorrent.torrentFile)

    for (var i in torrent.urlList) {
      var url = torrent.urlList[i]
      if (wtorrent.urlList.indexOf(url) === -1) wtorrent.addWebSeed(url)
    }

    // Process any priority requests that came in before the listener was added
    torrent._torrentDB.getPriorities(function (err, values) {
      if (err) throw err
      values.forEach(function (v) { self._prioritize(torrent, v) })
    })

    wtorrent.on('done', function () {
      if (torrent.closed || self.destroyed) return
      torrent._torrentDB.removeAllPriorities()
    })
  })
}

Seeder.prototype._prioritize = function (torrent, range) {
  if (torrent.closed || this.destroyed || this._webtorrent == null) return

  var start = Math.floor(range.start / torrent.pieceLength)
  var end = Math.floor(range.end / torrent.pieceLength)
  var wtorrent = this._webtorrent.get(torrent.infoHash)

  if (wtorrent != null && wtorrent.ready && wtorrent.progress !== 1) {
    if (range.type === 'critical') wtorrent.critical(start, end)
    else if (range.type === 'select') wtorrent.select(start, end, range.priority)
    else if (range.type === 'deselect') wtorrent.deselect(start, end, range.priority)
  }
}

Seeder.prototype.destroy = function () {
  if (this.destroyed) return
  this.stop()
  this.destroyed = true
  this._seeds = null
}

function wrapChunkStore (torrent) {
  return function (chunkLength, opts) {
    return torrent._torrentDB.createChunkStore(chunkLength, opts)
  }
}
<commit_msg>Fix tests failing due to thrown err in seeder.js<commit_after>module.exports = Seeder

var WebTorrent = require('webtorrent')

function Seeder () {
  if (typeof window === 'undefined') throw new Error('must be called in a wep page')

  this.destroyed = false
  this.started = false
  this._seeds = {}
  this._webtorrent = null
}

Seeder.prototype.add = function (torrent) {
  if (this.destroyed) throw new Error('Seeder has been destroyed')
  if (torrent.closed || torrent.infoHash in this._seeds) return

  var listener = this._prioritize.bind(this, torrent)

  this._seeds[torrent.infoHash] = {
    torrent: torrent,
    listener: listener
  }

  if (this.started) {
    torrent._torrentDB.on('priority', listener)
    this._seed(torrent)
  }
}

Seeder.prototype.remove = function (infoHash) {
  if (this.destroyed) throw new Error('Seeder has been destroyed')
  if (!this._seeds[infoHash]) return

  var seed = this._seeds[infoHash]
  delete this._seeds[infoHash]

  if (this._webtorrent) this._webtorrent.remove(seed.torrent.infoHash)

  if (seed != null && !seed.torrent.closed) {
    seed.torrent._torrentDB.removeListener('priority', this._seeds[infoHash].listener)
  }
}

Seeder.prototype.start = function () {
  if (this.destroyed) throw new Error('Seeder is destroyed')
  if (this.started) return

  this.started = true
  this._webtorrent = this._webtorrent || new WebTorrent()

  for (var infoHash in this._seeds) {
    var torrent = this._seeds[infoHash].torrent
    if (torrent.closed) {
      delete this._seeds[infoHash]
    } else {
      torrent._torrentDB.on('priority', this._seeds[infoHash].listener)
      this._seed(torrent)
    }
  }
}

Seeder.prototype.stop = function () {
  if (this.destroyed || !this.started) return

  this.started = false
  if (this._webtorrent) this._webtorrent.destroy()
  this._webtorrent = null

  for (var infoHash in this._seeds) {
    var torrent = this._seeds[infoHash].torrent
    if (torrent.closed) delete this._seeds[infoHash]
    else torrent._torrentDB.removeListener('priority', this._seeds[infoHash].listener)
  }
}

Seeder.prototype._seed = function (torrent) {
  var self = this
  if (self.destroyed || self._webtorrent.get(torrent.infoHash)) return

  var opts = {store: wrapChunkStore(torrent)}
  var torrentId = torrent.torrentMetaBuffer ? torrent.torrentMetaBuffer : torrent.magnetURI
  self._webtorrent.add(torrentId, opts, function (wtorrent) {
    if (torrent.closed || self.destroyed) return

    torrent._torrentDB.updateMeta(wtorrent.torrentFile)

    for (var i in torrent.urlList) {
      var url = torrent.urlList[i]
      if (wtorrent.urlList.indexOf(url) === -1) wtorrent.addWebSeed(url)
    }

    // Process any priority requests that came in before the listener was added
    torrent._torrentDB.getPriorities(function (err, values) {
      if (torrent.closed || self.destroyed) return
      if (err) return console.log(err)
      values.forEach(function (v) { self._prioritize(torrent, v) })
    })

    wtorrent.on('done', function () {
      if (torrent.closed || self.destroyed) return
      torrent._torrentDB.removeAllPriorities()
    })
  })
}

Seeder.prototype._prioritize = function (torrent, range) {
  if (torrent.closed || this.destroyed || this._webtorrent == null) return

  var start = Math.floor(range.start / torrent.pieceLength)
  var end = Math.floor(range.end / torrent.pieceLength)
  var wtorrent = this._webtorrent.get(torrent.infoHash)

  if (wtorrent != null && wtorrent.ready && wtorrent.progress !== 1) {
    if (range.type === 'critical') wtorrent.critical(start, end)
    else if (range.type === 'select') wtorrent.select(start, end, range.priority)
    else if (range.type === 'deselect') wtorrent.deselect(start, end, range.priority)
  }
}

Seeder.prototype.destroy = function () {
  if (this.destroyed) return
  this.stop()
  this.destroyed = true
  this._seeds = null
}

function wrapChunkStore (torrent) {
  return function (chunkLength, opts) {
    return torrent._torrentDB.createChunkStore(chunkLength, opts)
  }
}
<|endoftext|>


<commit_before>'use strict';
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var msgpack = require('msgpack-lite');
var net = require('net');
var stream = require('stream');
var crypto = require('crypto');
var FluentLoggerError = require('./logger-error');
var EventTime = require('./event-time').EventTime;

var codec = msgpack.createCodec();
codec.addExtPacker(0x00, EventTime, EventTime.pack);
codec.addExtUnpacker(0x00, EventTime.unpack);

function FluentSender(tag_prefix, options){
  options = options || {};
  this.tag_prefix = tag_prefix;
  this.host = options.host || 'localhost';
  this.port = options.port || 24224;
  this.path = options.path;
  this.timeout = options.timeout || 3.0;
  this.reconnectInterval = options.reconnectInterval || 600000; // Default is 10 minutes
  this.requireAckResponse = options.requireAckResponse;
  this.ackResponseTimeout = options.ackResponseTimeout || 190000; // Default is 190 seconds
  this.internalLogger = options.internalLogger || console;
  this._timeResolution = options.milliseconds ? 1 : 1000;
  this._socket = null;
  this._sendQueue = []; // queue for items waiting for being sent.
  this._eventEmitter = new EventEmitter();
  // options.security = { clientHostname: "client.localdomain", sharedKey: "very-secret-shared-key" }
  this.security = options.security || {
    clientHostname: null,
    sharedKey: null,
    username: '',
    passwort: ''
  };
  this.sharedKeySalt = crypto.randomBytes(16).toString('hex');
  // helo, pingpong, established
  this._status = null;
  this._connecting = false;
}

FluentSender.prototype.emit = function(/*[label] <data>, [timestamp], [callback] */){
  var label, data, timestamp, callback;
  var args = Array.prototype.slice.call(arguments);
  // Label must be string always
  if (typeof args[0] === 'string') label = args.shift();

  // Data can be almost anything
  data = args.shift();

  // Date can be either timestamp number or Date object
  if (typeof args[0] !== 'function') timestamp = args.shift();

  // Last argument is an optional callback
  if (typeof args[0] === 'function') callback = args.shift();

  var item = this._makePacketItem(label, data, timestamp);

  var error;
  var options;
  if (item.tag === null) {
    options = {
      tag_prefix: this.tag_prefix,
      label: label
    };
    error = new FluentLoggerError.MissingTag('tag is missing', options);
    this._handleEvent('error', error, callback);
    return;
  }
  if (typeof item.data !== 'object') {
    options = {
      tag_prefix: this.tag_prefix,
      label: label,
      record: item.data
    };
    error = new FluentLoggerError.DataTypeError('data must be an object', options);
    this._handleEvent('error', error, callback);
    return;
  }

  item.callback = callback;

  this._sendQueue.push(item);
  this._connect(() => {
    this._flushSendQueue();
  });
};

['addListener', 'on', 'once', 'removeListener', 'removeAllListeners', 'setMaxListeners', 'getMaxListeners'].forEach(function(attr, i){
  FluentSender.prototype[attr] = function(){
    return this._eventEmitter[attr].apply(this._eventEmitter, Array.prototype.slice.call(arguments));
  };
});

FluentSender.prototype.end = function(label, data, callback){
  if ((label != null && data != null)) {
    this.emit(label, data, (err) => {
      this._close();
      if (err) {
        this._handleEvent('error', err, callback);
      } else {
        callback && callback();
      }
    });
  } else {
    process.nextTick(() => {
      this._close();
      callback && callback();
    });
  }
};

FluentSender.prototype._close = function() {
  if (this._socket) {
    this._socket.end();
    this._socket = null;
    this._status = null;
  }
};

FluentSender.prototype._makeTag = function(label) {
  let tag = null;
  if (this.tag_prefix && label) {
    tag = [this.tag_prefix, label].join('.');
  } else if (this.tag_prefix) {
    tag = this.tag_prefix;
  } else if (label) {
    tag = label;
  }
  return tag;
};

FluentSender.prototype._makePacketItem = function(tag, time, data){
  if (typeof time !== 'number' && !(time instanceof EventTime)) {
    time = Math.floor((time ? time.getTime() : Date.now()) / this._timeResolution);
  }

  var packet = [tag, time, data];
  var options = {};
  if (this.requireAckResponse) {
    options = {
      chunk: crypto.randomBytes(16).toString('base64')
    };
    packet.push(options);
  }
  return {
    packet: msgpack.encode(packet, { codec: codec }),
    tag: tag,
    time: time,
    data: data,
    options: options
  };
};

FluentSender.prototype._makeEventEntry = function(time, data) {
  if (typeof time !== 'number' && !(time instanceof EventTime)) {
    time = Math.floor((time ? time.getTime() : Date.now()) / this._timeResolution);
  }

  return msgpack.encode([time, data], { codec: codec });
};

FluentSender.prototype._connect = function(callback){
  if (this._connecting) {
    return;
  }

  this._connecting = true;
  process.nextTick(() => {
    if (this._socket === null) {
      this._doConnect(() => {
        this._connecting = false;
        callback();
      });
    } else {
      if (!this._socket.writable) {
        this._disconnect();
        process.nextTick(() => {
          this._connecting = false;
          this._connect(callback);
        });
      } else {
        process.nextTick(() => {
          this._connecting = false;
          callback();
        });
      }
    }
  });
};

FluentSender.prototype._doConnect = function(callback) {
  this._socket = new net.Socket();
  this._socket.setTimeout(this.timeout);
  this._socket.on('error', (err) => {
    if (this._socket) {
      this._disconnect();
      this._handleEvent('error', err);
    }
  });
  this._socket.on('connect', () => {
    this._handleEvent('connect');
  });
  if (this.path) {
    this._socket.connect(this.path, () => {
      callback();
    });
  } else {
    this._socket.connect(this.port, this.host, () => {
      if (this.security.clientHostname && this.security.sharedKey) {
        this._handshake(callback);
      } else {
        this._status = 'established';
        callback();
      }
    });
  }
};

FluentSender.prototype._disconnect = function() {
  this._socket && this._socket.destroy();
  this._socket = null;
  this._status = null;
  this._connecting = false;
};

FluentSender.prototype._handshake = function(callback) {
  if (this._status === 'established') {
    return;
  }
  this._status = 'helo';
  this._socket.once('data', (data) => {
    this._socket.pause();
    let heloStatus = this._checkHelo(data);
    if (!heloStatus.succeeded) {
      this.internalLogger.error("Received invalid HELO message from " + this._socket.remoteAddress);
      this._disconnect();
      return;
    }
    this._status = 'pingpong';
    this._socket.write(new Buffer(this._generatePing()), () => {
      this._socket.resume();
      this._socket.once('data', (data) => {
        let pongStatus = this._checkPong(data);
        if (!pongStatus.succeeded) {
          this.internalLogger.error(pongStatus.message);
          let error = new FluentLoggerError.HandshakeError(pongStatus.message);
          this._handleEvent('error', error);
          this._disconnect();
          return;
        }
        this._status = 'established';
        this.internalLogger.info('Established');
        callback();
      });
    });
  });
};

FluentSender.prototype._flushSendQueue = function() {
  if (this._flushingSendQueue)
    return;

  this._flushingSendQueue = true;
  process.nextTick(() => {
    if (!this._socket) {
      this._flushingSendQueue = false;
      return;
    }

    if (this._socket.writable) {
      this._doFlushSendQueue();
    } else {
      process.nextTick(waitToWrite);
    }
  });
};

FluentSender.prototype._doFlushSendQueue = function() {
  var item = this._sendQueue.shift();
  var timeoutId = null;
  if (item === undefined) {
    this._flushingSendQueue = false;
    // nothing written;
  } else {
    this._socket.write(new Buffer(item.packet), () => {
      if (this.requireAckResponse) {
        this._socket.once('data', (data) => {
          timeoutId && clearTimeout(timeoutId);
          var response = msgpack.decode(data, { codec: codec });
          if (response.ack !== item.options.chunk) {
            var error = new FluentLoggerError.ResponseError('ack in response and chunk id in sent data are different',
                                                            { ack: response.ack, chunk: item.options.chunk });
            this._handleEvent('error', error, item.callback);
          }
          item.callback && item.callback();
          process.nextTick(() => {
            this._doFlushSendQueue(); // if socket is still available
          });
        });
        timeoutId = setTimeout(() => {
          var error = new FluentLoggerError.ResponseTimeout('ack response timeout');
          this._handleEvent('error', error, item.callback);
        }, this.ackResponseTimeout);
      } else {
        item.callback && item.callback();
        process.nextTick(() => {
          this._doFlushSendQueue(); // if socket is still available
        });
      }
    });
    // TODO: how should we recorver if dequeued items are not sent.
  }
};

FluentSender.prototype._handleEvent = function _handleEvent(signal, data, callback) {
  callback && callback(data);
  if (this._eventEmitter.listenerCount(signal) > 0) {
    this._eventEmitter.emit(signal, data);
  }
};

FluentSender.prototype._setupErrorHandler = function _setupErrorHandler() {
  if (!this.reconnectInterval) {
    return;
  }
  this.on('error', (error) => {
    this._flushingSendQueue = false;
    this._status = null;
    this.internalLogger.error('Fluentd error', error);
    this.internalLogger.info('Fluentd will reconnect after ' + this.reconnectInterval / 1000 + ' seconds');
    setTimeout(() => {
      this.internalLogger.info('Fluentd is reconnecting...');
      this._connect(() => {
        this.internalLogger.info('Fluentd reconnection finished!!');
      });
    }, this.reconnectInterval);
  });
};

FluentSender.prototype._checkHelo = function _checkHelo(data) {
  // ['HELO', options(hash)]
  this.internalLogger.info('Checking HELO...');
  var message = msgpack.decode(data);
  if (message.length !== 2) {
    return { succeeded: false, message: 'Invalid format for HELO message' };
  }
  if (message[0] !== 'HELO') {
    return { succeeded: false, message: 'Invalid format for HELO message' };
  }
  var options = message[1] || {};
  this.sharedKeyNonce = options['nonce'] || '';
  this.authentication = options['auth'] || '';
  return { succeeded: true };
};

FluentSender.prototype._generatePing = function _generatePing() {
  // [
  //   'PING',
  //   client_hostname,
  //   shared_key_salt,
  //   sha512_hex(sharedkey_salt + client_hostname + nonce + shared_key),
  //   username || '',
  //   sha512_hex(auth_salt + username + password) || ''
  // ]
  var sharedKeyHexdigest = crypto.createHash('sha512')
      .update(this.sharedKeySalt)
      .update(this.security.clientHostname)
      .update(this.sharedKeyNonce)
      .update(this.security.sharedKey)
      .digest('hex');
  var ping = ['PING', this.security.clientHostname, this.sharedKeySalt, sharedKeyHexdigest];
  if (Buffer.isBuffer(this.authentication) && Buffer.byteLength(this.authentication) !== 0) {
    var passwordHexDigest = crypto.createHash('sha512')
        .update(this.authentication)
        .update(this.security.username || '')
        .update(this.security.password || '')
        .digest('hex');
    ping.push(this.username, passwordHexDigest);
  } else {
    ping.push('', '');
  }
  return msgpack.encode(ping);
};

FluentSender.prototype._checkPong = function _checkPong(data) {
  // [
  //   'PONG',
  //   bool(authentication result),
  //   'reason if authentication failed',
  //   server_hostname,
  //   sha512_hex(salt + server_hostname + nonce + sharedkey)
  // ]
  this.internalLogger.info('Checking PONG...');
  var message = msgpack.decode(data);
  if (message.length !== 5) {
    return false;
  }
  if (message[0] !== 'PONG') {
    return { succeeded: false, message: 'Invalid format for PONG message' };
  }
  var _pong = message[0];
  var authResult = message[1];
  var reason = message[2];
  var hostname = message[3];
  var sharedKeyHexdigest = message[4];
  if (!authResult) {
    return { succeeded: false, message: 'Authentication failed: ' + reason };
  }
  if (hostname === this.security.clientHostname) {
    return { succeeded: false, message: 'Same hostname between input and output: invalid configuration' };
  }
  var clientsideHexdigest = crypto.createHash('sha512')
      .update(this.sharedKeySalt)
      .update(hostname)
      .update(this.sharedKeyNonce)
      .update(this.security.sharedKey)
      .digest('hex');
  if (sharedKeyHexdigest !== clientsideHexdigest) {
    return { succeeded: false, message: 'Sharedkey mismatch' };
  }
  return { succeeded: true };
};

FluentSender.prototype.toStream = function(options) {
  if (typeof options === 'string') {
    options = {label: options};
  } else {
    options = options || {};
  }
  var label = options.label;
  if (!label) {
    throw new Error('label is needed');
  }
  var defaultEncoding = options.encoding || 'UTF-8';
  var writable = new stream.Writable();
  var dataString = '';
  writable._write = (chunk, encoding, callback) => {
    var dataArray = chunk.toString(defaultEncoding).split(/\n/);
    var next = () => {
      if (dataArray.length) {
        dataString += dataArray.shift();
      }
      if (!dataArray.length) {
        process.nextTick(callback);
        return;
      }
      this.emit(label, { message: dataString }, (err) => {
        if (err) {
          this._handleEvent('error', err, callback);
          return;
        }
        dataString = '';
        next();
      });
    };
    next();
  };
  return writable;
};

module.exports = exports = {};
exports.FluentSender = FluentSender;
<commit_msg>Add FluentSender._push<commit_after>'use strict';
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var msgpack = require('msgpack-lite');
var net = require('net');
var stream = require('stream');
var crypto = require('crypto');
var FluentLoggerError = require('./logger-error');
var EventTime = require('./event-time').EventTime;

var codec = msgpack.createCodec();
codec.addExtPacker(0x00, EventTime, EventTime.pack);
codec.addExtUnpacker(0x00, EventTime.unpack);

function FluentSender(tag_prefix, options){
  options = options || {};
  this.tag_prefix = tag_prefix;
  this.host = options.host || 'localhost';
  this.port = options.port || 24224;
  this.path = options.path;
  this.timeout = options.timeout || 3.0;
  this.reconnectInterval = options.reconnectInterval || 600000; // Default is 10 minutes
  this.requireAckResponse = options.requireAckResponse;
  this.ackResponseTimeout = options.ackResponseTimeout || 190000; // Default is 190 seconds
  this.internalLogger = options.internalLogger || console;
  this._timeResolution = options.milliseconds ? 1 : 1000;
  this._socket = null;
  this._sendQueue = []; // queue for items waiting for being sent.
  this._eventEmitter = new EventEmitter();
  // options.security = { clientHostname: "client.localdomain", sharedKey: "very-secret-shared-key" }
  this.security = options.security || {
    clientHostname: null,
    sharedKey: null,
    username: '',
    passwort: ''
  };
  this.sharedKeySalt = crypto.randomBytes(16).toString('hex');
  // helo, pingpong, established
  this._status = null;
  this._connecting = false;
}

FluentSender.prototype.emit = function(/*[label] <data>, [timestamp], [callback] */){
  var label, data, timestamp, callback;
  var args = Array.prototype.slice.call(arguments);
  // Label must be string always
  if (typeof args[0] === 'string') label = args.shift();

  // Data can be almost anything
  data = args.shift();

  // Date can be either timestamp number or Date object
  if (typeof args[0] !== 'function') timestamp = args.shift();

  // Last argument is an optional callback
  if (typeof args[0] === 'function') callback = args.shift();

  var item = this._makePacketItem(label, data, timestamp);

  var error;
  var options;
  if (item.tag === null) {
    options = {
      tag_prefix: this.tag_prefix,
      label: label
    };
    error = new FluentLoggerError.MissingTag('tag is missing', options);
    this._handleEvent('error', error, callback);
    return;
  }
  if (typeof item.data !== 'object') {
    options = {
      tag_prefix: this.tag_prefix,
      label: label,
      record: item.data
    };
    error = new FluentLoggerError.DataTypeError('data must be an object', options);
    this._handleEvent('error', error, callback);
    return;
  }

  item.callback = callback;

  this._sendQueue.push(item);
  this._connect(() => {
    this._flushSendQueue();
  });
};

['addListener', 'on', 'once', 'removeListener', 'removeAllListeners', 'setMaxListeners', 'getMaxListeners'].forEach(function(attr, i){
  FluentSender.prototype[attr] = function(){
    return this._eventEmitter[attr].apply(this._eventEmitter, Array.prototype.slice.call(arguments));
  };
});

FluentSender.prototype.end = function(label, data, callback){
  if ((label != null && data != null)) {
    this.emit(label, data, (err) => {
      this._close();
      if (err) {
        this._handleEvent('error', err, callback);
      } else {
        callback && callback();
      }
    });
  } else {
    process.nextTick(() => {
      this._close();
      callback && callback();
    });
  }
};

FluentSender.prototype._close = function() {
  if (this._socket) {
    this._socket.end();
    this._socket = null;
    this._status = null;
  }
};

FluentSender.prototype._makeTag = function(label) {
  let tag = null;
  if (this.tag_prefix && label) {
    tag = [this.tag_prefix, label].join('.');
  } else if (this.tag_prefix) {
    tag = this.tag_prefix;
  } else if (label) {
    tag = label;
  }
  return tag;
};

FluentSender.prototype._makePacketItem = function(tag, time, data){
  if (typeof time !== 'number' && !(time instanceof EventTime)) {
    time = Math.floor((time ? time.getTime() : Date.now()) / this._timeResolution);
  }

  var packet = [tag, time, data];
  var options = {};
  if (this.requireAckResponse) {
    options = {
      chunk: crypto.randomBytes(16).toString('base64')
    };
    packet.push(options);
  }
  return {
    packet: msgpack.encode(packet, { codec: codec }),
    tag: tag,
    time: time,
    data: data,
    options: options
  };
};

FluentSender.prototype._makeEventEntry = function(time, data) {
  if (typeof time !== 'number' && !(time instanceof EventTime)) {
    time = Math.floor((time ? time.getTime() : Date.now()) / this._timeResolution);
  }

  return msgpack.encode([time, data], { codec: codec });
};

FluentSender.prototype._push = function(tag, time, data, callback) {
  if (true) {
    // Message mode
    let item = this._makePacketItem(tag, time, data);
    item.callback = callback;
    this._sendQueue.push(item);
  } else {
    // PackedForward mode
    let eventEntry = this._makeEventEntry(time, data);
    if (this._sendQueue[tag]) {
      this._sendQueue[tag] += eventEntry;
    } else {
      this._sendQueue[tag] = eventEntry;
    }
  }
};

FluentSender.prototype._connect = function(callback){
  if (this._connecting) {
    return;
  }

  this._connecting = true;
  process.nextTick(() => {
    if (this._socket === null) {
      this._doConnect(() => {
        this._connecting = false;
        callback();
      });
    } else {
      if (!this._socket.writable) {
        this._disconnect();
        process.nextTick(() => {
          this._connecting = false;
          this._connect(callback);
        });
      } else {
        process.nextTick(() => {
          this._connecting = false;
          callback();
        });
      }
    }
  });
};

FluentSender.prototype._doConnect = function(callback) {
  this._socket = new net.Socket();
  this._socket.setTimeout(this.timeout);
  this._socket.on('error', (err) => {
    if (this._socket) {
      this._disconnect();
      this._handleEvent('error', err);
    }
  });
  this._socket.on('connect', () => {
    this._handleEvent('connect');
  });
  if (this.path) {
    this._socket.connect(this.path, () => {
      callback();
    });
  } else {
    this._socket.connect(this.port, this.host, () => {
      if (this.security.clientHostname && this.security.sharedKey) {
        this._handshake(callback);
      } else {
        this._status = 'established';
        callback();
      }
    });
  }
};

FluentSender.prototype._disconnect = function() {
  this._socket && this._socket.destroy();
  this._socket = null;
  this._status = null;
  this._connecting = false;
};

FluentSender.prototype._handshake = function(callback) {
  if (this._status === 'established') {
    return;
  }
  this._status = 'helo';
  this._socket.once('data', (data) => {
    this._socket.pause();
    let heloStatus = this._checkHelo(data);
    if (!heloStatus.succeeded) {
      this.internalLogger.error("Received invalid HELO message from " + this._socket.remoteAddress);
      this._disconnect();
      return;
    }
    this._status = 'pingpong';
    this._socket.write(new Buffer(this._generatePing()), () => {
      this._socket.resume();
      this._socket.once('data', (data) => {
        let pongStatus = this._checkPong(data);
        if (!pongStatus.succeeded) {
          this.internalLogger.error(pongStatus.message);
          let error = new FluentLoggerError.HandshakeError(pongStatus.message);
          this._handleEvent('error', error);
          this._disconnect();
          return;
        }
        this._status = 'established';
        this.internalLogger.info('Established');
        callback();
      });
    });
  });
};

FluentSender.prototype._flushSendQueue = function() {
  if (this._flushingSendQueue)
    return;

  this._flushingSendQueue = true;
  process.nextTick(() => {
    if (!this._socket) {
      this._flushingSendQueue = false;
      return;
    }

    if (this._socket.writable) {
      this._doFlushSendQueue();
    } else {
      process.nextTick(waitToWrite);
    }
  });
};

FluentSender.prototype._doFlushSendQueue = function() {
  var item = this._sendQueue.shift();
  var timeoutId = null;
  if (item === undefined) {
    this._flushingSendQueue = false;
    // nothing written;
  } else {
    this._socket.write(new Buffer(item.packet), () => {
      if (this.requireAckResponse) {
        this._socket.once('data', (data) => {
          timeoutId && clearTimeout(timeoutId);
          var response = msgpack.decode(data, { codec: codec });
          if (response.ack !== item.options.chunk) {
            var error = new FluentLoggerError.ResponseError('ack in response and chunk id in sent data are different',
                                                            { ack: response.ack, chunk: item.options.chunk });
            this._handleEvent('error', error, item.callback);
          }
          item.callback && item.callback();
          process.nextTick(() => {
            this._doFlushSendQueue(); // if socket is still available
          });
        });
        timeoutId = setTimeout(() => {
          var error = new FluentLoggerError.ResponseTimeout('ack response timeout');
          this._handleEvent('error', error, item.callback);
        }, this.ackResponseTimeout);
      } else {
        item.callback && item.callback();
        process.nextTick(() => {
          this._doFlushSendQueue(); // if socket is still available
        });
      }
    });
    // TODO: how should we recorver if dequeued items are not sent.
  }
};

FluentSender.prototype._handleEvent = function _handleEvent(signal, data, callback) {
  callback && callback(data);
  if (this._eventEmitter.listenerCount(signal) > 0) {
    this._eventEmitter.emit(signal, data);
  }
};

FluentSender.prototype._setupErrorHandler = function _setupErrorHandler() {
  if (!this.reconnectInterval) {
    return;
  }
  this.on('error', (error) => {
    this._flushingSendQueue = false;
    this._status = null;
    this.internalLogger.error('Fluentd error', error);
    this.internalLogger.info('Fluentd will reconnect after ' + this.reconnectInterval / 1000 + ' seconds');
    setTimeout(() => {
      this.internalLogger.info('Fluentd is reconnecting...');
      this._connect(() => {
        this.internalLogger.info('Fluentd reconnection finished!!');
      });
    }, this.reconnectInterval);
  });
};

FluentSender.prototype._checkHelo = function _checkHelo(data) {
  // ['HELO', options(hash)]
  this.internalLogger.info('Checking HELO...');
  var message = msgpack.decode(data);
  if (message.length !== 2) {
    return { succeeded: false, message: 'Invalid format for HELO message' };
  }
  if (message[0] !== 'HELO') {
    return { succeeded: false, message: 'Invalid format for HELO message' };
  }
  var options = message[1] || {};
  this.sharedKeyNonce = options['nonce'] || '';
  this.authentication = options['auth'] || '';
  return { succeeded: true };
};

FluentSender.prototype._generatePing = function _generatePing() {
  // [
  //   'PING',
  //   client_hostname,
  //   shared_key_salt,
  //   sha512_hex(sharedkey_salt + client_hostname + nonce + shared_key),
  //   username || '',
  //   sha512_hex(auth_salt + username + password) || ''
  // ]
  var sharedKeyHexdigest = crypto.createHash('sha512')
      .update(this.sharedKeySalt)
      .update(this.security.clientHostname)
      .update(this.sharedKeyNonce)
      .update(this.security.sharedKey)
      .digest('hex');
  var ping = ['PING', this.security.clientHostname, this.sharedKeySalt, sharedKeyHexdigest];
  if (Buffer.isBuffer(this.authentication) && Buffer.byteLength(this.authentication) !== 0) {
    var passwordHexDigest = crypto.createHash('sha512')
        .update(this.authentication)
        .update(this.security.username || '')
        .update(this.security.password || '')
        .digest('hex');
    ping.push(this.username, passwordHexDigest);
  } else {
    ping.push('', '');
  }
  return msgpack.encode(ping);
};

FluentSender.prototype._checkPong = function _checkPong(data) {
  // [
  //   'PONG',
  //   bool(authentication result),
  //   'reason if authentication failed',
  //   server_hostname,
  //   sha512_hex(salt + server_hostname + nonce + sharedkey)
  // ]
  this.internalLogger.info('Checking PONG...');
  var message = msgpack.decode(data);
  if (message.length !== 5) {
    return false;
  }
  if (message[0] !== 'PONG') {
    return { succeeded: false, message: 'Invalid format for PONG message' };
  }
  var _pong = message[0];
  var authResult = message[1];
  var reason = message[2];
  var hostname = message[3];
  var sharedKeyHexdigest = message[4];
  if (!authResult) {
    return { succeeded: false, message: 'Authentication failed: ' + reason };
  }
  if (hostname === this.security.clientHostname) {
    return { succeeded: false, message: 'Same hostname between input and output: invalid configuration' };
  }
  var clientsideHexdigest = crypto.createHash('sha512')
      .update(this.sharedKeySalt)
      .update(hostname)
      .update(this.sharedKeyNonce)
      .update(this.security.sharedKey)
      .digest('hex');
  if (sharedKeyHexdigest !== clientsideHexdigest) {
    return { succeeded: false, message: 'Sharedkey mismatch' };
  }
  return { succeeded: true };
};

FluentSender.prototype.toStream = function(options) {
  if (typeof options === 'string') {
    options = {label: options};
  } else {
    options = options || {};
  }
  var label = options.label;
  if (!label) {
    throw new Error('label is needed');
  }
  var defaultEncoding = options.encoding || 'UTF-8';
  var writable = new stream.Writable();
  var dataString = '';
  writable._write = (chunk, encoding, callback) => {
    var dataArray = chunk.toString(defaultEncoding).split(/\n/);
    var next = () => {
      if (dataArray.length) {
        dataString += dataArray.shift();
      }
      if (!dataArray.length) {
        process.nextTick(callback);
        return;
      }
      this.emit(label, { message: dataString }, (err) => {
        if (err) {
          this._handleEvent('error', err, callback);
          return;
        }
        dataString = '';
        next();
      });
    };
    next();
  };
  return writable;
};

module.exports = exports = {};
exports.FluentSender = FluentSender;
<|endoftext|>


<commit_before>(function() {
    'use strict';

    var DIR_ROOT    = __dirname + '/..',

        path        = require('path'),

        join        = require('join-io'),
        mollify     = require('mollify'),
        restafary   = require('restafary'),
        socketFile  = require('socket-file'),
        readjson    = require('readjson'),
        HOME        = require('os-homedir')(),
        Edit        = require('../json/edit.json'),
        jshint      = readjson.sync.try(path.join(HOME, '.jshintrc')),
        Root,
        minifyFunc          = mollify({
            dir     : DIR_ROOT
        });

    if (!jshint)
        jshint = readjson.sync(path.join(DIR_ROOT,'.jshintrc'));

    module.exports          = function(options) {
        return serve.bind(null, options);
    };

    module.exports.listen   = function(socket, options) {
        var ret;

        if (!options)
            options = {};

        if (!options.prefix)
            options.prefix = '/dword';

        if (!options.root)
            options.root = '/';

        Root = options.root;
        ret = socketFile(socket, options);

        return ret;
    };

    function checkOption(isOption) {
        var is,
            type    = typeof isOption;

        switch(type) {
        case 'function':
            is = isOption();
            break;

        case 'undefined':
            is = true;
            break;

        default:
            is = isOption;
        }

        return is;
    }

    function copy(from, to) {
        Object.keys(from).forEach(function(name) {
            to[name] = from[name];
        });
    }

    function readEdit(callback) {
        var homePath = HOME + '/.dword.json';

        readjson(homePath, function(error, edit) {
            var data = {};

            copy(Edit, data);

            if (!error)
                copy(edit, data);
            else if (error.code !== 'ENOENT')
                error = Error('dword --config ' + homePath + ': ' + error.message);
            else
                error = null;

            callback(error, data);
        });
    }

    function serve(options, req, res, next) {
        var joinFunc, restafaryFunc, isJoin, isConfig, isEdit, isRestafary,
            isJsHint,
            o           = options || {},
            isMin       = checkOption(o.minify),
            isOnline    = checkOption(o.online),
            isDiff      = checkOption(o.diff),
            isZip       = checkOption(o.pack),

            url         = req.url,
            prefix      = o.prefix || '/dword',

            isEdward    = !url.indexOf(prefix),

            URL         = '/dword.js',
            CONFIG      = '/options.json',
            MODULES     = '/modules.json',
            PATH        = '/lib/client.js',
            EDIT        = '/edit.json',
            JSHINT      = '/jshint.json',
            sendFile    = function() {
                url = path.normalize(DIR_ROOT + url);
                res.sendFile(url);
            };

        if (!isEdward) {
            next();
        } else {
            url         = url.replace(prefix, '');

            isJoin      = !url.indexOf('/join');
            isConfig    = url === CONFIG;
            isEdit      = url === EDIT;
            isJsHint    = url === JSHINT;
            isRestafary = [
                '/api/v1/fs',
                '/restafary.js'].some(function(item) {
                    return !url.indexOf(item);
                });

            switch(url) {
            case URL:
                url = PATH;
                break;

            case MODULES:
                url = '/json' + url;
                break;
            }

            req.url = url;

            if (isEdit) {
                readEdit(function(error, data) {
                    if (error)
                        res.status(404)
                            .send(error.message);
                    else
                        res .type('json')
                            .send(data);
                });
            } else if (isConfig) {
                res .type('json')
                    .send({
                        diff: isDiff,
                        zip: isZip,
                        online: isOnline
                    });
            } else if (isJsHint) {
                res .type('json')
                    .send(jshint);
            } else if (isJoin) {
                joinFunc = join({
                    dir     : DIR_ROOT,
                    minify  : isMin
                });

                joinFunc(req, res, next);
            } else if (isRestafary) {
                restafaryFunc = restafary({
                    prefix: '/api/v1/fs',
                    root: Root
                });

                restafaryFunc(req, res, next);
            } else if (isMin) {
                minifyFunc(req, res, sendFile);
            } else {
                sendFile();
            }
        }
    }
})();
<commit_msg>feature(server) copy: desctructive -> clean<commit_after>(function() {
    'use strict';

    var DIR_ROOT    = __dirname + '/..',

        path        = require('path'),

        join        = require('join-io'),
        mollify     = require('mollify'),
        restafary   = require('restafary'),
        socketFile  = require('socket-file'),
        readjson    = require('readjson'),
        HOME        = require('os-homedir')(),
        Edit        = require('../json/edit.json'),
        jshint      = readjson.sync.try(path.join(HOME, '.jshintrc')),
        Root,
        minifyFunc          = mollify({
            dir     : DIR_ROOT
        });

    if (!jshint)
        jshint = readjson.sync(path.join(DIR_ROOT,'.jshintrc'));

    module.exports          = function(options) {
        return serve.bind(null, options);
    };

    module.exports.listen   = function(socket, options) {
        var ret;

        if (!options)
            options = {};

        if (!options.prefix)
            options.prefix = '/dword';

        if (!options.root)
            options.root = '/';

        Root = options.root;
        ret = socketFile(socket, options);

        return ret;
    };

    function checkOption(isOption) {
        var is,
            type    = typeof isOption;

        switch(type) {
        case 'function':
            is = isOption();
            break;

        case 'undefined':
            is = true;
            break;

        default:
            is = isOption;
        }

        return is;
    }

    function copy(from) {
        return Object
            .keys(from)
            .reduce(function(value, name) {
                value[name] = from[name];
            }, {});
    }

    function readEdit(callback) {
        var homePath = HOME + '/.dword.json';

        readjson(homePath, function(error, edit) {
            var data;

            if (!error)
                data = copy(edit);
            else if (error.code !== 'ENOENT')
                error = Error('dword --config ' + homePath + ': ' + error.message);
            else
                error = null;

            if (!error && !data)
                data = copy(Edit);

            callback(error, data);
        });
    }

    function serve(options, req, res, next) {
        var joinFunc, restafaryFunc, isJoin, isConfig, isEdit, isRestafary,
            isJsHint,
            o           = options || {},
            isMin       = checkOption(o.minify),
            isOnline    = checkOption(o.online),
            isDiff      = checkOption(o.diff),
            isZip       = checkOption(o.pack),

            url         = req.url,
            prefix      = o.prefix || '/dword',

            isEdward    = !url.indexOf(prefix),

            URL         = '/dword.js',
            CONFIG      = '/options.json',
            MODULES     = '/modules.json',
            PATH        = '/lib/client.js',
            EDIT        = '/edit.json',
            JSHINT      = '/jshint.json',
            sendFile    = function() {
                url = path.normalize(DIR_ROOT + url);
                res.sendFile(url);
            };

        if (!isEdward) {
            next();
        } else {
            url         = url.replace(prefix, '');

            isJoin      = !url.indexOf('/join');
            isConfig    = url === CONFIG;
            isEdit      = url === EDIT;
            isJsHint    = url === JSHINT;
            isRestafary = [
                '/api/v1/fs',
                '/restafary.js'].some(function(item) {
                    return !url.indexOf(item);
                });

            switch(url) {
            case URL:
                url = PATH;
                break;

            case MODULES:
                url = '/json' + url;
                break;
            }

            req.url = url;

            if (isEdit) {
                readEdit(function(error, data) {
                    if (error)
                        res.status(404)
                            .send(error.message);
                    else
                        res .type('json')
                            .send(data);
                });
            } else if (isConfig) {
                res .type('json')
                    .send({
                        diff: isDiff,
                        zip: isZip,
                        online: isOnline
                    });
            } else if (isJsHint) {
                res .type('json')
                    .send(jshint);
            } else if (isJoin) {
                joinFunc = join({
                    dir     : DIR_ROOT,
                    minify  : isMin
                });

                joinFunc(req, res, next);
            } else if (isRestafary) {
                restafaryFunc = restafary({
                    prefix: '/api/v1/fs',
                    root: Root
                });

                restafaryFunc(req, res, next);
            } else if (isMin) {
                minifyFunc(req, res, sendFile);
            } else {
                sendFile();
            }
        }
    }
})();
<|endoftext|>


<commit_before>var WS = require('ws').Server
var server = new WS({ port: 3000 })
var attachAPI = require('./api')

server.on('connection', function (socket) {
  var api = attachAPI(socket)
  socket.on('close', function () {
    api.sensorUnsubscribe()
  })

  socket.on('message', function (data) {
    data = JSON.parse(data)
    try {
      var value = api[data.type](data)
      if (value) {
        socket.send(JSON.stringify(value))
      }
    } catch (e) {
      socket.send(JSON.stringify({
        type: 'error',
        value: e
      }))
    }
  })
})
<commit_msg>add tests<commit_after>var WS = require('ws').Server
var server = new WS({ port: 3000 })
var attachAPI = require('./api')

server.on('connection', function (socket) {
  var api = attachAPI(socket)
  socket.on('close', function () {
    api.sensorUnsubscribe('close')
  })

  socket.on('message', function (data) {
    data = JSON.parse(data)
    try {
      var value = api[data.type](data)
      if (value) {
        socket.send(JSON.stringify(value))
      }
    } catch (e) {
      socket.send(JSON.stringify({
        type: 'error',
        value: e
      }))
    }
  })
})
<|endoftext|>


<commit_before>module.exports = Server;

var Socket = require("./socket");
var util = require("./util");

function Server(transportSocket, options){

    var processConnect = forwardEmptyFrame(onConnect);

    options = util.extend({
        commandHandlers:{
            "STOMP": processConnect,
            "CONNECT": processConnect
        },
        unknownCommand: onUnknownCommand
    }, options);

    this.softwareId = options.softwareId;

    Socket.call(this, transportSocket, options);
}

util.inherits(Server, Socket);

Server.prototype.readEmptyBody = function(frame, callback){

    var self = this;

    frame.readEmptyBody(function(isEmpty){

        if(isEmpty){
            if(typeof callback === "function"){
                callback.call(self);
            }
        }
        else{
            self.destroy(new Error(util.format("client sent a non-empty %s frame", frame.command)));
        }
    });
};

Server.prototype.sendError = function(message){

    var headers = {};

    if(typeof message === "string"){
        headers.message = message;
    }

    var frame = this.sendFrame("ERROR", headers);

    var self = this;

    frame.once("finish", function(){
        self.destroy(new Error(message));
    });

    return frame;
};

Server.prototype._beforeSendResponse = function(requestFrame, responseFrame, responseEndCallback){

    var receiptId = requestFrame.headers["receipt"];

    if(receiptId !== undefined){
        if(responseFrame){
            responseFrame.headers["receipt-id"] = receiptId;
        }
        else{

            responseFrame = this.sendFrame("RECEIPT", {
                "receipt-id": receiptId
            });

            responseFrame.end(responseEndCallback);
        }
    }
    else{
        process.nextTick(responseEndCallback);
    }

    return responseFrame;
};

function forwardEmptyFrame(callback){
    return function(frame, beforeSendResponseCallback){
        this.readEmptyBody(frame, function(){
            callback.apply(this, [frame, beforeSendResponseCallback]);
        });
    };
}

function onConnect(frame, beforeSendResponse){

    var commands = {
        "DISCONNECT": forwardEmptyFrame(onDisconnect)
    };

    if(this._send){
        commands["SEND"] = this._send.bind(this);
    }

    if(this._subscribe){

        commands["SUBSCRIBE"] = forwardEmptyFrame(this._subscribe.bind(this));
        commands["UNSUBSCRIBE"] = forwardEmptyFrame(this._unsubscribe.bind(this));

        if(this._ack){
            commands["ACK"] = forwardEmptyFrame(this._ack.bind(this));
            commands["NACK"] = forwardEmptyFrame(this._nack.bind(this));
        }
    }

    if(this._begin){
        commands["BEGIN"] = forwardEmptyFrame(this._begin.bind(this));
        commands["COMMIT"] = forwardEmptyFrame(this._commit.bind(this));
        commands["ABORT"] = forwardEmptyFrame(this._abort.bind(this));
    }

    this.setCommandHandlers(commands);

    var headers = {
        "heart-beat": this.getHeartbeat().join(",")
    };

    if(this.softwareId){
        headers.server = this.softwareId;
    }

    beforeSendResponse(this.sendFrame("CONNECTED", headers)).end();

    if(frame.headers["heart-beat"] !== undefined){

        var heartbeat = frame.headers["heart-beat"].split(",").map(function(x){return parseInt(x, 10);});

        if(heartbeat.length > 1 && !isNaN(heartbeat[0]) && !isNaN(heartbeat[1])){
            this.setHeartbeat(heartbeat[0], heartbeat[1]);
        }
    }

    this.emit("connection");
}

function onDisconnect(frame, beforeSendResponse){

    this.setCommandHandlers({});

    var self = this;

    this._disconnect.apply(this, [frame, function(frame, responseEndCallback){
        beforeSendResponse(frame, function(){
            if(typeof responseEndCallback === "function"){
                responseEndCallback();
            }
            self.destroy();
        });
    }]);
}

function onUnknownCommand(frame, beforeSendResponse){
    beforeSendResponse(this.sendError("unknown command '" + frame.command + "'")).end();
}
<commit_msg>Set version header<commit_after>module.exports = Server;

var Socket = require("./socket");
var util = require("./util");

function Server(transportSocket, options){

    var processConnect = forwardEmptyFrame(onConnect);

    options = util.extend({
        commandHandlers:{
            "STOMP": processConnect,
            "CONNECT": processConnect
        },
        unknownCommand: onUnknownCommand
    }, options);

    this.softwareId = options.softwareId;
    this.version = "1.1";

    Socket.call(this, transportSocket, options);
}

util.inherits(Server, Socket);

Server.prototype.readEmptyBody = function(frame, callback){

    var self = this;

    frame.readEmptyBody(function(isEmpty){

        if(isEmpty){
            if(typeof callback === "function"){
                callback.call(self);
            }
        }
        else{
            self.destroy(new Error(util.format("client sent a non-empty %s frame", frame.command)));
        }
    });
};

Server.prototype.sendError = function(message){

    var headers = {};

    if(typeof message === "string"){
        headers.message = message;
    }

    var frame = this.sendFrame("ERROR", headers);

    var self = this;

    frame.once("finish", function(){
        self.destroy(new Error(message));
    });

    return frame;
};

Server.prototype._beforeSendResponse = function(requestFrame, responseFrame, responseEndCallback){

    var receiptId = requestFrame.headers["receipt"];

    if(receiptId !== undefined){
        if(responseFrame){
            responseFrame.headers["receipt-id"] = receiptId;
        }
        else{

            responseFrame = this.sendFrame("RECEIPT", {
                "receipt-id": receiptId
            });

            responseFrame.end(responseEndCallback);
        }
    }
    else{
        process.nextTick(responseEndCallback);
    }

    return responseFrame;
};

function forwardEmptyFrame(callback){
    return function(frame, beforeSendResponseCallback){
        this.readEmptyBody(frame, function(){
            callback.apply(this, [frame, beforeSendResponseCallback]);
        });
    };
}

function onConnect(frame, beforeSendResponse){

    var commands = {
        "DISCONNECT": forwardEmptyFrame(onDisconnect)
    };

    if(this._send){
        commands["SEND"] = this._send.bind(this);
    }

    if(this._subscribe){

        commands["SUBSCRIBE"] = forwardEmptyFrame(this._subscribe.bind(this));
        commands["UNSUBSCRIBE"] = forwardEmptyFrame(this._unsubscribe.bind(this));

        if(this._ack){
            commands["ACK"] = forwardEmptyFrame(this._ack.bind(this));
            commands["NACK"] = forwardEmptyFrame(this._nack.bind(this));
        }
    }

    if(this._begin){
        commands["BEGIN"] = forwardEmptyFrame(this._begin.bind(this));
        commands["COMMIT"] = forwardEmptyFrame(this._commit.bind(this));
        commands["ABORT"] = forwardEmptyFrame(this._abort.bind(this));
    }

    this.setCommandHandlers(commands);

    var headers = {
        "version": this.version,
        "heart-beat": this.getHeartbeat().join(",")
    };

    if(this.softwareId){
        headers.server = this.softwareId;
    }

    beforeSendResponse(this.sendFrame("CONNECTED", headers)).end();

    if(frame.headers["heart-beat"] !== undefined){

        var heartbeat = frame.headers["heart-beat"].split(",").map(function(x){return parseInt(x, 10);});

        if(heartbeat.length > 1 && !isNaN(heartbeat[0]) && !isNaN(heartbeat[1])){
            this.setHeartbeat(heartbeat[0], heartbeat[1]);
        }
    }

    this.emit("connection");
}

function onDisconnect(frame, beforeSendResponse){

    this.setCommandHandlers({});

    var self = this;

    this._disconnect.apply(this, [frame, function(frame, responseEndCallback){
        beforeSendResponse(frame, function(){
            if(typeof responseEndCallback === "function"){
                responseEndCallback();
            }
            self.destroy();
        });
    }]);
}

function onUnknownCommand(frame, beforeSendResponse){
    beforeSendResponse(this.sendError("unknown command '" + frame.command + "'")).end();
}
<|endoftext|>


<commit_before>var fs = require('fs');

var getApplication = function() {
    var express = require('express'),
        app = express(),
        ejs = require('ejs-locals'),
        config = require('./config');

    var env = config.get('NODE_ENV');
    var loggerFormat = config.get('muxamp:log') || null;

    app.configure(function() {
        app.use(express.static(__dirname + '/../public/'));
        app.use(express.bodyParser());
        app.engine('ejs', ejs);
        app.set('view engine', 'ejs');
        app.use(express.logger(loggerFormat));
    });

    app.configure('development', function() {
    });

    app.configure('test', function() {
        app.use(express.static(__dirname + '/../publictest/'));
    });

    app.configure('production', function() {
        app.set('json spaces', 0);
    });

    var analytics = fs.existsSync(__dirname + '/../views/analytics.ejs');
    var search = require('./search').search,
        url = require('url'),
        Q = require('q'),
        _ = require('underscore'),
        playlist = require('./playlist'),
        db = require('./db');

    app.get(/^\/([1-9][0-9]*)?$/, function(req, res) {
        var additionalJS = 'js/' + env;
        res.render('playlist', {environment: additionalJS, analytics: analytics});
    });

    app.get('/search/:site/:page([0-9]+)/:query?', function(req, res) {
        var site = req.params.site, page = parseInt(req.params.page), query = req.params.query;
        query || (query = '');
        if (!query.length) {
            res.status(400);
            res.json({error: 'You did not enter a search query!'});
        }
        query = decodeURIComponent(query || "");
        search(query, site, page, 25).then(function(results) {
            var error = results.error;
            if (error) {
                res.status(400);
                res.json({error: error});
                return;
            }
            res.json(results.tracks || []);
        }).fail(function(results) {
            res.status(500);
            res.json({error: 'An internal error occurred.'});
        }).done();
    });

    app.get('/playlists/:queryID', function(req, res) {
        var queryID = parseInt(req.params.queryID);
        var response = {id: false, tracks: []};
        if (!_.isFinite(queryID)) {
            res.status(400);
            res.json(response);
            return;
        }
        playlist.getPlaylist(queryID).then(function(results) {
            if (results && results.length) {
                response.id = queryID;
                response.tracks = results;
            } else {
                res.status(404);
            }
            res.json(response);
        }).fail(function(error) {
            console.log(error);
            error && console.log('get playlist endpoint error', error);
            console.trace();
            var message = 'The specified playlist could not be located: ' + queryID;
            if (!db.canRead()) {
                message = 'Sorry, the Muxamp service is currently unavailable.';
            }
            response.error = message;
            res.status(500);
            res.json(response);
        }).done();
    });

    app.post('/playlists/save', function(req, res) {
        var query = req.body;
        if (!_.isArray(query) || _.isEmpty(query)) {
            if (_.isEmpty(query)) {
                // Reset Content
                res.status(205);
                res.json({id: false});
            } else {
                //Non-empty body has been sent with a non-array--must be nonsense
                var response = {id: false, error: 'A list of tracks was expected.'};
                res.status(400);
                res.json(response);
            }
            return;
        } else if (!_.every(query, function(e) { return e && e.siteCode && e.siteMediaID; })) {
            var response = {id: false, error: 'A playlist could not be parsed from the input.'};
            res.status(400);
            res.json(response);
            return;
        }
        playlist.save(query).then(function(id) {
            res.json({
                id: id
            });
        }).fail(function(error) {
            error && error.message && (error = error.message);
            error && console.log('save playlist endpoint error', error);
            console.trace();
            var message = 'There was a problem in the playlist service.';
            if (!db.canRead()) {
                message = 'Sorry, the Muxamp service is currently unavailable.';
            } else if (!db.canWrite()) {
                message = 'Sorry, Muxamp is currently in read-only mode.';
            }
            res.status(500);
            res.json({error: message, id: false});
        }).done();
    });

    return app;
};

module.exports = {
    getApplication: getApplication,
}<commit_msg>Playlist fetch returns empty JSON response when 404.<commit_after>var fs = require('fs');

var getApplication = function() {
    var express = require('express'),
        app = express(),
        ejs = require('ejs-locals'),
        config = require('./config');

    var env = config.get('NODE_ENV');
    var loggerFormat = config.get('muxamp:log') || null;

    app.configure(function() {
        app.use(express.static(__dirname + '/../public/'));
        app.use(express.bodyParser());
        app.engine('ejs', ejs);
        app.set('view engine', 'ejs');
        app.use(express.logger(loggerFormat));
    });

    app.configure('development', function() {
    });

    app.configure('test', function() {
        app.use(express.static(__dirname + '/../publictest/'));
    });

    app.configure('production', function() {
        app.set('json spaces', 0);
    });

    var analytics = fs.existsSync(__dirname + '/../views/analytics.ejs');
    var search = require('./search').search,
        url = require('url'),
        Q = require('q'),
        _ = require('underscore'),
        playlist = require('./playlist'),
        db = require('./db');

    app.get(/^\/([1-9][0-9]*)?$/, function(req, res) {
        var additionalJS = 'js/' + env;
        res.render('playlist', {environment: additionalJS, analytics: analytics});
    });

    app.get('/search/:site/:page([0-9]+)/:query?', function(req, res) {
        var site = req.params.site, page = parseInt(req.params.page), query = req.params.query;
        query || (query = '');
        if (!query.length) {
            res.status(400);
            res.json({error: 'You did not enter a search query!'});
        }
        query = decodeURIComponent(query || "");
        search(query, site, page, 25).then(function(results) {
            var error = results.error;
            if (error) {
                res.status(400);
                res.json({error: error});
                return;
            }
            res.json(results.tracks || []);
        }).fail(function(results) {
            res.status(500);
            res.json({error: 'An internal error occurred.'});
        }).done();
    });

    app.get('/playlists/:queryID', function(req, res) {
        var queryID = parseInt(req.params.queryID);
        var response = {id: false, tracks: []};
        if (!_.isFinite(queryID)) {
            res.status(400);
            res.json(response);
            return;
        }
        playlist.getPlaylist(queryID).then(function(results) {
            if (results && results.length) {
                response.id = queryID;
                response.tracks = results;
            } else {
                res.status(404);
                res.json({id: false, tracks: []});
            }
            res.json(response);
        }).fail(function(error) {
            console.log(error);
            error && console.log('get playlist endpoint error', error);
            console.trace();
            var message = 'The specified playlist could not be located: ' + queryID;
            if (!db.canRead()) {
                message = 'Sorry, the Muxamp service is currently unavailable.';
            }
            response.error = message;
            res.status(500);
            res.json(response);
        }).done();
    });

    app.post('/playlists/save', function(req, res) {
        var query = req.body;
        if (!_.isArray(query) || _.isEmpty(query)) {
            if (_.isEmpty(query)) {
                // Reset Content
                res.status(205);
                res.json({id: false});
            } else {
                //Non-empty body has been sent with a non-array--must be nonsense
                var response = {id: false, error: 'A list of tracks was expected.'};
                res.status(400);
                res.json(response);
            }
            return;
        } else if (!_.every(query, function(e) { return e && e.siteCode && e.siteMediaID; })) {
            var response = {id: false, error: 'A playlist could not be parsed from the input.'};
            res.status(400);
            res.json(response);
            return;
        }
        playlist.save(query).then(function(id) {
            res.json({
                id: id
            });
        }).fail(function(error) {
            error && error.message && (error = error.message);
            error && console.log('save playlist endpoint error', error);
            console.trace();
            var message = 'There was a problem in the playlist service.';
            if (!db.canRead()) {
                message = 'Sorry, the Muxamp service is currently unavailable.';
            } else if (!db.canWrite()) {
                message = 'Sorry, Muxamp is currently in read-only mode.';
            }
            res.status(500);
            res.json({error: message, id: false});
        }).done();
    });

    return app;
};

module.exports = {
    getApplication: getApplication,
}<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */

/*jslint node:true, nomen: true */

/**
The `express-yui/lib/server` provides a set of features
to control a YUI instance on the server side.

@module express-yui/lib/server
**/

'use strict';

var libpath = require('path'),
    utils = require('./utils'),
    debug = require('debug')('express:yui:server');

/**
Provides a set of features to control a YUI instance on the server side.

@class server
@static
@uses *path, utils
@extensionfor express-yui/lib/yui
*/
module.exports = {

    /**
    Create a synthetic group off the bundle renference to register its modules
    into the server and/or client Y instance so they can be used thru `app.yui.use()`.

    @method registerBundle
    @public
    @param {Object} bundle A locator bundle reference.
    @chainable
    **/
    registerBundle: function (bundle) {
        var groups = {},
            modules,
            key;

        if (bundle.yui) {
            if (bundle.yui.client && bundle.yui.metaModuleName) {
                // storing client modules
                // - we don't own it, locator does it
                for (key in bundle.yui.client) {
                    if (bundle.yui.client.hasOwnProperty(key)) {
                        if (this._clientModules.hasOwnProperty(key) && this._clientModules[key].group !== bundle.yui.client[key].group) {
                            debug("Module name collision: '%s' is used for both group '%s' and group '%s'",
                                key, this._clientModules[key].group, bundle.yui.client[key].group);
                        }
                        this._clientModules[key] = utils.clone(bundle.yui.client[key]);
                    }
                }
                // add the meta module into the core structure
                // to make sure it gets attached to Y upfront on the client side
                this.registerGroup(bundle.name, bundle.buildDirectory, bundle.yui.metaModuleName);
            }
            if (bundle.yui.server) {
                debug('Registering group "%s" for server side, and using it from "%s"',
                      bundle.name, bundle.buildDirectory);

                // provision modules for server side
                groups[bundle.name] = {
                    base: libpath.normalize(bundle.buildDirectory + '/'), // TODO: what if two modules have the same name but different affinity?
                    combine: false
                };
                // we don't own it, locator does it, and yui will potentially augment it
                modules = utils.clone(bundle.yui.server);
                // storing server modules
                // - we don't own it, locator does it

                for (key in modules) {
                    if (modules.hasOwnProperty(key)) {
                        if (this._serverModules.hasOwnProperty(key) && this._serverModules[key].group !== modules[key].group) {
                            debug("Module name collision: '%s' is used for both group '%s' and group '%s'",
                                key, this._serverModules[key].group, modules[key].group);
                        }
                        this._serverModules[key] = modules[key];
                    }
                }

                // applying the new group config and modules globally and to the current Y instance when possible
                this.YUI.applyConfig({ groups: groups });
                this.YUI.applyConfig({ modules: modules });

                if (this._Y) {
                    this._Y.applyConfig({ groups: groups });
                    this._Y.applyConfig({ modules: modules });
                }
            }
        }

        return this;
    },

    /**
    Patches `Y.Loader` to support `optionalRequires`, which enables you
    to require modules that might not be available in a runtime, and avoid
    to throw when that happens.

    @method _patchLoader
    @protected
    **/
    _patchLoader: function () {
        var getRequires = this._Y.Env._loader.getRequires;
        this._Y.Env._loader.getRequires = function (mod) {
            var i, m;
            if (!mod) {
                return [];
            }
            if (mod._parsed) {
                return mod.expanded || [];
            }
            mod.requires = mod.requires || [];
            // expanding requirements with optional requires
            if (mod.optionalRequires) {
                for (i = 0; i < mod.optionalRequires.length; i += 1) {
                    m = this.getModule(mod.optionalRequires[i]);
                    if (m) {
                        mod.requires.push(m.name);
                    }
                }
            }
            // mod.templates and mod.langBundles are not used on the server side
            return getRequires.apply(this, arguments);
        };
    },

    /**
    Patches `Y.Template.get` to use the templates produced by
    `locator-handlebars` or any other similar plugin, and will be available thru locator's bundle
    objects.

    @method _patchTemplate
    @protected
    **/
    _patchTemplate: function () {
        // monkey patching Y.Template
        var locator = this._app.get('locator'),
            // looking in the root bundle (hermes has only one bundle)
            // that's the most common use-case where the app bundle holds all templates
            rootBundle = locator.getRootBundle(),
            originalTemplateGet = this._Y.use('template-base').Template.get;

        this._Y.Template.get = function (name) {
            var pos = name.indexOf('/'),
                bundleName,
                bundle,
                template = name;
            if (pos > 0) {
                bundleName = name.slice(0, pos);
                template = name.slice(pos + 1, name.length);
            }
            bundle = bundleName ? locator.getBundle(bundleName) : rootBundle;
            if (bundle.template && bundle.template[template]) {
                return bundle.template[template];
            }
            // if the template is not in the specified bundle, look for a global/root template
            if (bundle !== rootBundle && rootBundle.template && rootBundle.template[name]) {
                return rootBundle.template[name];
            }
            // falling back to the original implementation
            return originalTemplateGet.apply(this, arguments);
        };
    },

    /**
    Patches `Y.Intl.get` to use the language bundles produced by
    `locator-lang` plugin, and will be available thru locator's bundle
    objects.

    @method _patchIntl
    @protected
    **/
    _patchIntl: function () {
        // monkey patching Y.Intl
        var locator = this._app.get('locator'),
            // looking in the root bundle (hermes has only one bundle)
            // that's the most common use-case where the app bundle holds all lang bundles
            rootBundle = locator.getRootBundle(),
            originalInltGet = this._Y.use('intl').Intl.get;
        this._Y.Intl.get = function (name, key, lang) {
            var pos = name.indexOf('/'),
                bundleName,
                bundle,
                langBundle = name;

            function output(entries) {
                // returning a specific key or all entries (todo: should be make a copy?)
                return (key ? entries[key] : entries);
            }

            if (pos > 0) {
                bundleName = name.slice(0, pos);
                langBundle = name.slice(pos + 1, name.length);
            }
            bundle = bundleName ? locator.getBundle(bundleName) : rootBundle;
            if (bundle.lang && bundle.lang[lang] && bundle.lang[lang][langBundle]) {
                return output(bundle.lang[lang][langBundle]);
            }
            // if the lang bundle is not in the specified bundle, look for a global/root lang bundle
            if (bundle !== rootBundle && rootBundle.lang && rootBundle.lang[lang] && rootBundle.lang[lang][name]) {
                return output(rootBundle.lang[lang][name]);
            }

            // fallbacking back to the original implementation
            return originalInltGet.apply(this, arguments);
        };
    },

    /**
    Waits for the app to be ready, including the YUI instance to notify back that the
    `ready` state of the app was reached by calling the `callback`. The ready state is
    bound to the locator instance mounted into the express app thru `app.set('locator', locator);`
    and depending on `app.get('locator').ready.then()` promise result, the ready state
    will be reached or not.

        app.yui.ready(function (err) {
            if (err) {
                throw err;
            }
            // do something!
        });

    @method ready
    @param {Function} callback when the app is ready. If an error occurr, the error object
                        will be passed as the first argument of the callback function.
    @public
    **/
    ready: function (callback) {
        var self = this,
            app = this._app,
            locator = app.get('locator');

        if (!locator) {
            debug('Call `app.set("locator", locatorObj)` before extending the `express` app with `express-yui`');
            throw new Error('Locator instance should be mounted');
        }

        locator.ready.then(function () {
            var bundleNames = locator.listBundleNames(),
                bundle;

            try {
                bundleNames.forEach(function (bundleName) {
                    bundle = locator.getBundle(bundleName);
                    self.registerBundle(bundle);
                });
            } catch (e) {
                callback(e);
                return;
            }
            // we are now ready to receive traffic
            callback();
        }, callback);
    },

    /**
    Creates a YUI Instance, and wraps the call to `Y.use()` to work appropiatly on
    the server side. The `use` method can be called once the `ready` state is achieved
    thru `app.yui.ready()`.

        app.yui.ready(function (err) {
            if (err) {
                throw err;
            }
            app.yui.use('json-stringify', function (Y) {
                Y.JSON.stringify({ entry: 'value' });
            });
        });

    @method use
    @public
    @return {Object} Y instance
    **/
    use: function () {
        var config = this.config(),
            Y = this._Y,
            modules,
            callback;

        if (!Y) {
            config = utils.clone(config);
            // cleaning up the config for to add on the server specific config
            delete config.modules;
            delete config.groups;

            this.YUI.applyConfig(config);
            this.YUI.applyConfig({
                base: libpath.normalize(this.path + '/'),
                combine: false
            });
            Y = this._Y = this.YUI({
                useSync: true,
                loadErrorFn: function (Y, i, o) {
                    debug('--> Something really bad happened when trying to load yui modules on the server side');
                    debug('--> ' + o.msg);
                    if (Y.config.modules) {
                        debug('--> Y.config.modules = ' + JSON.stringify(Y.config.modules));
                    }
                    if (Y.config.groups) {
                        debug('--> Y.config.groups = ' + JSON.stringify(Y.config.groups));
                    }
                }
            });
            // patching Y
            this._patchLoader();
            this._patchTemplate();
            this._patchIntl();
        }

        // attaching modules
        // in case a callback is passed, we should consider that
        modules = Array.prototype.slice.call(arguments, 0);
        if (modules.length > 0 && (typeof modules[modules.length - 1] === 'function')) {
            callback = modules.pop();
        }
        if (modules.length) {
            try {
                Y.use(modules);
            } catch (e) {
                console.error('error attaching modules: ' + modules);
                console.error(e);
                console.error(e.stack);
            }
        }

        if (callback) {
            callback(Y);
        }

        return Y;

    }

};
<commit_msg>detailed debug msg<commit_after>/*
 * Copyright (c) 2013, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */

/*jslint node:true, nomen: true */

/**
The `express-yui/lib/server` provides a set of features
to control a YUI instance on the server side.

@module express-yui/lib/server
**/

'use strict';

var libpath = require('path'),
    utils = require('./utils'),
    debug = require('debug')('express:yui:server');

/**
Provides a set of features to control a YUI instance on the server side.

@class server
@static
@uses *path, utils
@extensionfor express-yui/lib/yui
*/
module.exports = {

    /**
    Create a synthetic group off the bundle renference to register its modules
    into the server and/or client Y instance so they can be used thru `app.yui.use()`.

    @method registerBundle
    @public
    @param {Object} bundle A locator bundle reference.
    @chainable
    **/
    registerBundle: function (bundle) {
        var groups = {},
            modules,
            key;

        if (bundle.yui) {
            if (bundle.yui.client && bundle.yui.metaModuleName) {
                // storing client modules
                // - we don't own it, locator does it
                for (key in bundle.yui.client) {
                    if (bundle.yui.client.hasOwnProperty(key)) {
                        if (this._clientModules.hasOwnProperty(key) && this._clientModules[key].group !== bundle.yui.client[key].group) {
                            debug("Module name collision on the client: '%s' is used for both group '%s' and group '%s'",
                                key, this._clientModules[key].group, bundle.yui.client[key].group);
                        }
                        this._clientModules[key] = utils.clone(bundle.yui.client[key]);
                    }
                }
                // add the meta module into the core structure
                // to make sure it gets attached to Y upfront on the client side
                this.registerGroup(bundle.name, bundle.buildDirectory, bundle.yui.metaModuleName);
            }
            if (bundle.yui.server) {
                debug('Registering group "%s" for server side, and using it from "%s"',
                      bundle.name, bundle.buildDirectory);

                // provision modules for server side
                groups[bundle.name] = {
                    base: libpath.normalize(bundle.buildDirectory + '/'), // TODO: what if two modules have the same name but different affinity?
                    combine: false
                };
                // we don't own it, locator does it, and yui will potentially augment it
                modules = utils.clone(bundle.yui.server);
                // storing server modules
                // - we don't own it, locator does it

                for (key in modules) {
                    if (modules.hasOwnProperty(key)) {
                        if (this._serverModules.hasOwnProperty(key) && this._serverModules[key].group !== modules[key].group) {
                            debug("Module name collision on the server: '%s' is used for both group '%s' and group '%s'",
                                key, this._serverModules[key].group, modules[key].group);
                        }
                        this._serverModules[key] = modules[key];
                    }
                }

                // applying the new group config and modules globally and to the current Y instance when possible
                this.YUI.applyConfig({ groups: groups });
                this.YUI.applyConfig({ modules: modules });

                if (this._Y) {
                    this._Y.applyConfig({ groups: groups });
                    this._Y.applyConfig({ modules: modules });
                }
            }
        }

        return this;
    },

    /**
    Patches `Y.Loader` to support `optionalRequires`, which enables you
    to require modules that might not be available in a runtime, and avoid
    to throw when that happens.

    @method _patchLoader
    @protected
    **/
    _patchLoader: function () {
        var getRequires = this._Y.Env._loader.getRequires;
        this._Y.Env._loader.getRequires = function (mod) {
            var i, m;
            if (!mod) {
                return [];
            }
            if (mod._parsed) {
                return mod.expanded || [];
            }
            mod.requires = mod.requires || [];
            // expanding requirements with optional requires
            if (mod.optionalRequires) {
                for (i = 0; i < mod.optionalRequires.length; i += 1) {
                    m = this.getModule(mod.optionalRequires[i]);
                    if (m) {
                        mod.requires.push(m.name);
                    }
                }
            }
            // mod.templates and mod.langBundles are not used on the server side
            return getRequires.apply(this, arguments);
        };
    },

    /**
    Patches `Y.Template.get` to use the templates produced by
    `locator-handlebars` or any other similar plugin, and will be available thru locator's bundle
    objects.

    @method _patchTemplate
    @protected
    **/
    _patchTemplate: function () {
        // monkey patching Y.Template
        var locator = this._app.get('locator'),
            // looking in the root bundle (hermes has only one bundle)
            // that's the most common use-case where the app bundle holds all templates
            rootBundle = locator.getRootBundle(),
            originalTemplateGet = this._Y.use('template-base').Template.get;

        this._Y.Template.get = function (name) {
            var pos = name.indexOf('/'),
                bundleName,
                bundle,
                template = name;
            if (pos > 0) {
                bundleName = name.slice(0, pos);
                template = name.slice(pos + 1, name.length);
            }
            bundle = bundleName ? locator.getBundle(bundleName) : rootBundle;
            if (bundle.template && bundle.template[template]) {
                return bundle.template[template];
            }
            // if the template is not in the specified bundle, look for a global/root template
            if (bundle !== rootBundle && rootBundle.template && rootBundle.template[name]) {
                return rootBundle.template[name];
            }
            // falling back to the original implementation
            return originalTemplateGet.apply(this, arguments);
        };
    },

    /**
    Patches `Y.Intl.get` to use the language bundles produced by
    `locator-lang` plugin, and will be available thru locator's bundle
    objects.

    @method _patchIntl
    @protected
    **/
    _patchIntl: function () {
        // monkey patching Y.Intl
        var locator = this._app.get('locator'),
            // looking in the root bundle (hermes has only one bundle)
            // that's the most common use-case where the app bundle holds all lang bundles
            rootBundle = locator.getRootBundle(),
            originalInltGet = this._Y.use('intl').Intl.get;
        this._Y.Intl.get = function (name, key, lang) {
            var pos = name.indexOf('/'),
                bundleName,
                bundle,
                langBundle = name;

            function output(entries) {
                // returning a specific key or all entries (todo: should be make a copy?)
                return (key ? entries[key] : entries);
            }

            if (pos > 0) {
                bundleName = name.slice(0, pos);
                langBundle = name.slice(pos + 1, name.length);
            }
            bundle = bundleName ? locator.getBundle(bundleName) : rootBundle;
            if (bundle.lang && bundle.lang[lang] && bundle.lang[lang][langBundle]) {
                return output(bundle.lang[lang][langBundle]);
            }
            // if the lang bundle is not in the specified bundle, look for a global/root lang bundle
            if (bundle !== rootBundle && rootBundle.lang && rootBundle.lang[lang] && rootBundle.lang[lang][name]) {
                return output(rootBundle.lang[lang][name]);
            }

            // fallbacking back to the original implementation
            return originalInltGet.apply(this, arguments);
        };
    },

    /**
    Waits for the app to be ready, including the YUI instance to notify back that the
    `ready` state of the app was reached by calling the `callback`. The ready state is
    bound to the locator instance mounted into the express app thru `app.set('locator', locator);`
    and depending on `app.get('locator').ready.then()` promise result, the ready state
    will be reached or not.

        app.yui.ready(function (err) {
            if (err) {
                throw err;
            }
            // do something!
        });

    @method ready
    @param {Function} callback when the app is ready. If an error occurr, the error object
                        will be passed as the first argument of the callback function.
    @public
    **/
    ready: function (callback) {
        var self = this,
            app = this._app,
            locator = app.get('locator');

        if (!locator) {
            debug('Call `app.set("locator", locatorObj)` before extending the `express` app with `express-yui`');
            throw new Error('Locator instance should be mounted');
        }

        locator.ready.then(function () {
            var bundleNames = locator.listBundleNames(),
                bundle;

            try {
                bundleNames.forEach(function (bundleName) {
                    bundle = locator.getBundle(bundleName);
                    self.registerBundle(bundle);
                });
            } catch (e) {
                callback(e);
                return;
            }
            // we are now ready to receive traffic
            callback();
        }, callback);
    },

    /**
    Creates a YUI Instance, and wraps the call to `Y.use()` to work appropiatly on
    the server side. The `use` method can be called once the `ready` state is achieved
    thru `app.yui.ready()`.

        app.yui.ready(function (err) {
            if (err) {
                throw err;
            }
            app.yui.use('json-stringify', function (Y) {
                Y.JSON.stringify({ entry: 'value' });
            });
        });

    @method use
    @public
    @return {Object} Y instance
    **/
    use: function () {
        var config = this.config(),
            Y = this._Y,
            modules,
            callback;

        if (!Y) {
            config = utils.clone(config);
            // cleaning up the config for to add on the server specific config
            delete config.modules;
            delete config.groups;

            this.YUI.applyConfig(config);
            this.YUI.applyConfig({
                base: libpath.normalize(this.path + '/'),
                combine: false
            });
            Y = this._Y = this.YUI({
                useSync: true,
                loadErrorFn: function (Y, i, o) {
                    debug('--> Something really bad happened when trying to load yui modules on the server side');
                    debug('--> ' + o.msg);
                    if (Y.config.modules) {
                        debug('--> Y.config.modules = ' + JSON.stringify(Y.config.modules));
                    }
                    if (Y.config.groups) {
                        debug('--> Y.config.groups = ' + JSON.stringify(Y.config.groups));
                    }
                }
            });
            // patching Y
            this._patchLoader();
            this._patchTemplate();
            this._patchIntl();
        }

        // attaching modules
        // in case a callback is passed, we should consider that
        modules = Array.prototype.slice.call(arguments, 0);
        if (modules.length > 0 && (typeof modules[modules.length - 1] === 'function')) {
            callback = modules.pop();
        }
        if (modules.length) {
            try {
                Y.use(modules);
            } catch (e) {
                console.error('error attaching modules: ' + modules);
                console.error(e);
                console.error(e.stack);
            }
        }

        if (callback) {
            callback(Y);
        }

        return Y;

    }

};
<|endoftext|>