<commit_before>'use strict';

var _ = require('lodash'),
    AlexaSkill = require('./AlexaSkill'),
    Request = require('./Request'),
    Reply = require('alexa-helpers').reply,
    MessageRenderer = require('alexa-helpers').messageRenderer,
    messageRenderer = null,
    Promise = require('bluebird'),
    universalAnalytics = require('universal-analytics'),
    verbose = true
    ;

var StateMachineSkill = module.exports = function (appId,
  StateMachine,
  responses,
  variables,
  config) {
  var _this = this;
  _this._StateMachine = StateMachine;
  _this._responses = responses || {};
  _this._variables = variables || {};
  _this._config = config || { verbose: verbose };
  if (typeof _this._config.verbose !== undefined) verbose = _this._config.verbose;
  messageRenderer = MessageRenderer(_this._responses, _this._variables);
  AlexaSkill.call(_this, appId);
};

module.exports.verbose = verbose;

// Extend AlexaSkill
StateMachineSkill.prototype = Object.create(AlexaSkill.prototype);
StateMachineSkill.prototype.constructor = StateMachineSkill;

var ERRORS = module.exports.ERRORS = {
  AUTHORIZATION: 'Authorization Error',
  BAD_RESPONSE: 'Bad Response Error',
};

StateMachineSkill.prototype.eventHandlers.onSessionStarted = function (sessionStartedRequest,
  session) {
  var _this = this;
  if (_this._config.verbose) console.log('onSessionStarted requestId: ' +
    sessionStartedRequest.requestId + ', sessionId: ' + session.sessionId);
  var handler = _this._StateMachine.onSessionStart;
  if (handler) return handler(new Request(sessionStartedRequest, session));
};

StateMachineSkill.prototype.eventHandlers.onLaunch = function (launchRequest, session,
  response, context) {
  var _this = this;
  if (_this._config.verbose)
    console.log('onLaunch requestId: ' + launchRequest.requestId + ', sessionId: ' +
      session.sessionId);
  var intent = _this._StateMachine.openIntent;
  _this.eventHandlers.onIntent.call(_this, {
    type: 'IntentRequest',
    requestId: launchRequest.requestId,
    timestamp: launchRequest.timestamp,
    intent: {
        name: intent,
        slots: {},
    },

  }, session, response, context);
};

StateMachineSkill.prototype.eventHandlers.onSessionEnded = function (sessionEndedRequest, session) {
  var _this = this;
  if (_this._config.verbose)
    console.log('onSessionEnded requestId: ' + sessionEndedRequest.requestId + ', sessionId: ' +
      session.sessionId);
  var handler = _this._StateMachine.onSessionEnd;
  if (handler) handler(new Request(sessionEndedRequest, session));
};

StateMachineSkill.prototype.eventHandlers.onIntent = function (request, session,
  response, context) {
  var _this = this;
  request = new Request(request, session);
  if (_this._config.verbose) console.log('Got intent ' + request.intent.name);

  var fromState = session.new ? 'entry' : session.attributes.state || 'entry',
      stateMachine = _this._StateMachine(fromState);

  if (stateMachine.onRequestStart) stateMachine.onRequestStart(request);

  return stateMachine.transition(request).then(function (trans) {
    return transitionEndingInterceptor.call(_this, trans, stateMachine, request);
  }).then(function (trans) {
    if (_this._config.verbose)
      console.log('Yielding to [' + (trans.to ? trans.to.name : 'nowhere') + ']');
    if (trans.to) response._session.attributes.state = trans.to.name;else trans.reply.end();
    trans.reply.write(response);
  }).catch(respondError.call(_this, stateMachine, request, response));
};

StateMachineSkill.prototype.eventHandlers.onPlaybackStarted = function (playBackRequest, response) {
  var _this = this;
  if (_this._config.verbose) console.log('onPlaybackStarted requestId: ' +
    playBackRequest.request.requestId);

  Promise.resolve(playBackRequest)
  .then(function () { return _this._StateMachine.onPlaybackStart(playBackRequest); })
  .then(function (reply) {
      reply.write(response);
  });
};

StateMachineSkill.prototype.eventHandlers.onPlaybackFinished = function (playBackRequest,
  response) {
  var _this = this;
  if (_this._config.verbose) console.log('onPlaybackFinished requestId: ' +
    playBackRequest.request.requestId);

  Promise.resolve(playBackRequest)
  .then(function () { return _this._StateMachine.onPlaybackFinish(playBackRequest); })
  .then(function (reply) {
      reply.write(response);
  });
};

StateMachineSkill.prototype.eventHandlers.onPlaybackNearlyFinished = function (playBackRequest,
  response) {
  var _this = this;
  if (_this._config.verbose) console.log('onPlaybackNearlyFinished requestId: ' +
    playBackRequest.request.requestId);

  Promise.resolve(playBackRequest)
  .then(function () { return _this._StateMachine.onPlaybackNearlyFinish(playBackRequest); })
  .then(function (reply) {
      reply.write(response);
  });
};

StateMachineSkill.prototype.eventHandlers.onPlaybackStopped = function (playBackRequest, response) {
  var _this = this;
  if (_this._config.verbose) console.log('onPlaybackStopped requestId: ' +
    playBackRequest.request.requestId);

  Promise.resolve(playBackRequest)
  .then(function () { return _this._StateMachine.onPlaybackStop(playBackRequest); })
  .then(function (reply) {
      reply.write(response);
  });
};

StateMachineSkill.prototype.eventHandlers.onPlaybackFailed = function (playBackRequest, response) {
  var _this = this;
  if (_this._config.verbose) console.log('onPlaybackFailed requestId: ' +
    playBackRequest.request.requestId);

  Promise.resolve(playBackRequest)
  .then(function () { return _this._StateMachine.onPlaybackFail(playBackRequest); })
  .then(function (reply) {
      reply.write(response);
  });
};

var transitionEndingInterceptor = function (trans, stateMachine, request) {
  if (!stateMachine.onTransition) return trans;
  return Promise.try(function () {
    return stateMachine.onTransition(trans, request);
  }).then(function (back) {
    return back || trans;
  });
};

var respondAuthFailure = function (response) {
  var _this = this;
  var errorMsg = _this._StateMachine.onAuthError();
  errorMsg.write(response);
};

var respondBadResponse = function (stateMachine, request, response) {
  var _this = this;

  Promise.try(function () {
    return _this._StateMachine.onBadResponse(request);
  }).then(function (reply) {
    if (reply) {
      if (_this._config.verbose)
        console.log('Got an error response, and it was handled by a custom handler');
      reply.write(response);
    } else {
      respondError(stateMachine, request, response)();
    }
  }).catch(respondError(stateMachine, request, response));
};

var respondError = function (stateMachine, request, response) {
  var _this = this;
  return function (error) {
    if (error && (error.statusCode == 401 || error == ERRORS.AUTHORIZATION))
      return respondAuthFailure.call(_this, response);
    console.error('Respond with error',
      error ? error.stack || error.statusCode || error : 'unknown');
    if (error && error == ERRORS.BAD_RESPONSE)
      return respondBadResponse.call(_this, stateMachine, request, response);
    Promise.try(function () {
      return stateMachine.onError(request, error);
    }).then(function (errorMsg) {
      // Can give back a transition or a message. Even if it's a transition,
      // we ignore it and just say the message
      if (errorMsg.message) errorMsg = new Reply(errorMsg.message);
      errorMsg.write(response);
    }).catch(function () {
      new Reply({ tell: 'An unrecoverable error occurred.' }).write(response);
    });
  }.bind(_this);
};

var replyWith = module.exports.replyWith = function (msgPath, state, request, data) {
  var _this = this;
  if (verbose) console.log('Move to state [' + state + '] and say ' + msgPath);
  return renderMessage(msgPath, data).then(function (msg) {
    // For AMAZON.RepeatIntent
    var reply = null;
    if (msg && msg.ask) reply = { msgPath: msgPath, state: state };
    return {
      message: msg,
      to: state,
      session: {
        data: data ? data.serialize() : request.session.attributes.data,
        startTimestamp: request.session.attributes.startTimestamp,
        reply: reply,
      },
    };
  });
};

var replyWithAudioDirectives = module.exports.replyWithAudioDirectives = function (msgPath, state,
  request, data, directives) {
  var _this = this;
  if (verbose) console.log('Move to state [' + state + '] and say ' + msgPath);
  return renderMessage(msgPath, data).then(function (msg) {
    // For AMAZON.RepeatIntent
    var reply = null;
    if (msg && msg.ask) reply = { msgPath: msgPath, state: state };
    return {
      message: msg,
      directives: directives,
      to: state,
      session: {
        data: data ? data.serialize() : request.session.attributes.data,
        startTimestamp: request.session.attributes.startTimestamp,
        reply: reply,
      },
    };
  });
};

function renderMessage(msgPath, data) {
  if (!msgPath) return Promise.resolve(null);
  return messageRenderer(msgPath, data);
}

function SimpleHelpMessage(msgPath, analyticEvent, toState) {
  return {
    enter: function enter(request) {
      //var analytics = universalAnalytics(config.googleAnalytics.trackingCode,
      //  request.session.user.userId,
      //  { strictCidFormat: false });
      analytics(request).event('Help Flow', analyticEvent).send();
      return replyWith(msgPath, toState || 'die', request, null);
    },
  };
}

var analytics = module.exports.analytics = function (request) {
  var _this = this;
  return universalAnalytics(_this._config.googleAnalytics.trackingCode,
    request.session.user.userId,
    { strictCidFormat: false });
};
<commit_msg>Fixed issue from last commit<commit_after>'use strict';

var _ = require('lodash'),
    AlexaSkill = require('./AlexaSkill'),
    Request = require('./Request'),
    Reply = require('alexa-helpers').reply,
    MessageRenderer = require('alexa-helpers').messageRenderer,
    messageRenderer = null,
    Promise = require('bluebird'),
    universalAnalytics = require('universal-analytics'),
    verbose = true
    ;

var StateMachineSkill = module.exports = function (appId,
  StateMachine,
  responses,
  variables,
  config) {
  var _this = this;
  _this._StateMachine = StateMachine;
  _this._responses = responses || {};
  _this._variables = variables || {};
  _this._config = config || { verbose: verbose };
  if (typeof _this._config.verbose !== undefined) verbose = _this._config.verbose;
  messageRenderer = MessageRenderer(_this._responses, _this._variables);
  AlexaSkill.call(_this, appId);
};

module.exports.verbose = verbose;

// Extend AlexaSkill
StateMachineSkill.prototype = Object.create(AlexaSkill.prototype);
StateMachineSkill.prototype.constructor = StateMachineSkill;

var ERRORS = module.exports.ERRORS = {
  AUTHORIZATION: 'Authorization Error',
  BAD_RESPONSE: 'Bad Response Error',
};

StateMachineSkill.prototype.eventHandlers.onSessionStarted = function (sessionStartedRequest,
  session) {
  var _this = this;
  if (_this._config.verbose) console.log('onSessionStarted requestId: ' +
    sessionStartedRequest.requestId + ', sessionId: ' + session.sessionId);
  var handler = _this._StateMachine.onSessionStart;
  if (handler) return handler(new Request(sessionStartedRequest, session));
};

StateMachineSkill.prototype.eventHandlers.onLaunch = function (launchRequest, session,
  response, context) {
  var _this = this;
  if (_this._config.verbose)
    console.log('onLaunch requestId: ' + launchRequest.requestId + ', sessionId: ' +
      session.sessionId);
  var intent = _this._StateMachine.openIntent;
  _this.eventHandlers.onIntent.call(_this, {
    type: 'IntentRequest',
    requestId: launchRequest.requestId,
    timestamp: launchRequest.timestamp,
    intent: {
        name: intent,
        slots: {},
    },

  }, session, response, context);
};

StateMachineSkill.prototype.eventHandlers.onSessionEnded = function (sessionEndedRequest, session) {
  var _this = this;
  if (_this._config.verbose)
    console.log('onSessionEnded requestId: ' + sessionEndedRequest.requestId + ', sessionId: ' +
      session.sessionId);
  var handler = _this._StateMachine.onSessionEnd;
  if (handler) handler(new Request(sessionEndedRequest, session));
};

StateMachineSkill.prototype.eventHandlers.onIntent = function (request, session,
  response, context) {
  var _this = this;
  request = new Request(request, session);
  if (_this._config.verbose) console.log('Got intent ' + request.intent.name);

  var fromState = session.new ? 'entry' : session.attributes.state || 'entry',
      stateMachine = _this._StateMachine(fromState);

  if (stateMachine.onRequestStart) stateMachine.onRequestStart(request);

  return stateMachine.transition(request, context).then(function (trans) {
    return transitionEndingInterceptor.call(_this, trans, stateMachine, request);
  }).then(function (trans) {
    if (_this._config.verbose)
      console.log('Yielding to [' + (trans.to ? trans.to.name : 'nowhere') + ']');
    if (trans.to) response._session.attributes.state = trans.to.name;else trans.reply.end();
    trans.reply.write(response);
  }).catch(respondError.call(_this, stateMachine, request, response));
};

StateMachineSkill.prototype.eventHandlers.onPlaybackStarted = function (playBackRequest, response) {
  var _this = this;
  if (_this._config.verbose) console.log('onPlaybackStarted requestId: ' +
    playBackRequest.request.requestId);

  Promise.resolve(playBackRequest)
  .then(function () { return _this._StateMachine.onPlaybackStart(playBackRequest); })
  .then(function (reply) {
      reply.write(response);
  });
};

StateMachineSkill.prototype.eventHandlers.onPlaybackFinished = function (playBackRequest,
  response) {
  var _this = this;
  if (_this._config.verbose) console.log('onPlaybackFinished requestId: ' +
    playBackRequest.request.requestId);

  Promise.resolve(playBackRequest)
  .then(function () { return _this._StateMachine.onPlaybackFinish(playBackRequest); })
  .then(function (reply) {
      reply.write(response);
  });
};

StateMachineSkill.prototype.eventHandlers.onPlaybackNearlyFinished = function (playBackRequest,
  response) {
  var _this = this;
  if (_this._config.verbose) console.log('onPlaybackNearlyFinished requestId: ' +
    playBackRequest.request.requestId);

  Promise.resolve(playBackRequest)
  .then(function () { return _this._StateMachine.onPlaybackNearlyFinish(playBackRequest); })
  .then(function (reply) {
      reply.write(response);
  });
};

StateMachineSkill.prototype.eventHandlers.onPlaybackStopped = function (playBackRequest, response) {
  var _this = this;
  if (_this._config.verbose) console.log('onPlaybackStopped requestId: ' +
    playBackRequest.request.requestId);

  Promise.resolve(playBackRequest)
  .then(function () { return _this._StateMachine.onPlaybackStop(playBackRequest); })
  .then(function (reply) {
      reply.write(response);
  });
};

StateMachineSkill.prototype.eventHandlers.onPlaybackFailed = function (playBackRequest, response) {
  var _this = this;
  if (_this._config.verbose) console.log('onPlaybackFailed requestId: ' +
    playBackRequest.request.requestId);

  Promise.resolve(playBackRequest)
  .then(function () { return _this._StateMachine.onPlaybackFail(playBackRequest); })
  .then(function (reply) {
      reply.write(response);
  });
};

var transitionEndingInterceptor = function (trans, stateMachine, request) {
  if (!stateMachine.onTransition) return trans;
  return Promise.try(function () {
    return stateMachine.onTransition(trans, request);
  }).then(function (back) {
    return back || trans;
  });
};

var respondAuthFailure = function (response) {
  var _this = this;
  var errorMsg = _this._StateMachine.onAuthError();
  errorMsg.write(response);
};

var respondBadResponse = function (stateMachine, request, response) {
  var _this = this;

  Promise.try(function () {
    return _this._StateMachine.onBadResponse(request);
  }).then(function (reply) {
    if (reply) {
      if (_this._config.verbose)
        console.log('Got an error response, and it was handled by a custom handler');
      reply.write(response);
    } else {
      respondError(stateMachine, request, response)();
    }
  }).catch(respondError(stateMachine, request, response));
};

var respondError = function (stateMachine, request, response) {
  var _this = this;
  return function (error) {
    if (error && (error.statusCode == 401 || error == ERRORS.AUTHORIZATION))
      return respondAuthFailure.call(_this, response);
    console.error('Respond with error',
      error ? error.stack || error.statusCode || error : 'unknown');
    if (error && error == ERRORS.BAD_RESPONSE)
      return respondBadResponse.call(_this, stateMachine, request, response);
    Promise.try(function () {
      return stateMachine.onError(request, error);
    }).then(function (errorMsg) {
      // Can give back a transition or a message. Even if it's a transition,
      // we ignore it and just say the message
      if (errorMsg.message) errorMsg = new Reply(errorMsg.message);
      errorMsg.write(response);
    }).catch(function () {
      new Reply({ tell: 'An unrecoverable error occurred.' }).write(response);
    });
  }.bind(_this);
};

var replyWith = module.exports.replyWith = function (msgPath, state, request, data) {
  var _this = this;
  if (verbose) console.log('Move to state [' + state + '] and say ' + msgPath);
  return renderMessage(msgPath, data).then(function (msg) {
    // For AMAZON.RepeatIntent
    var reply = null;
    if (msg && msg.ask) reply = { msgPath: msgPath, state: state };
    return {
      message: msg,
      to: state,
      session: {
        data: data ? data.serialize() : request.session.attributes.data,
        startTimestamp: request.session.attributes.startTimestamp,
        reply: reply,
      },
    };
  });
};

var replyWithAudioDirectives = module.exports.replyWithAudioDirectives = function (msgPath, state,
  request, data, directives) {
  var _this = this;
  if (verbose) console.log('Move to state [' + state + '] and say ' + msgPath);
  return renderMessage(msgPath, data).then(function (msg) {
    // For AMAZON.RepeatIntent
    var reply = null;
    if (msg && msg.ask) reply = { msgPath: msgPath, state: state };
    return {
      message: msg,
      directives: directives,
      to: state,
      session: {
        data: data ? data.serialize() : request.session.attributes.data,
        startTimestamp: request.session.attributes.startTimestamp,
        reply: reply,
      },
    };
  });
};

function renderMessage(msgPath, data) {
  if (!msgPath) return Promise.resolve(null);
  return messageRenderer(msgPath, data);
}

function SimpleHelpMessage(msgPath, analyticEvent, toState) {
  return {
    enter: function enter(request) {
      //var analytics = universalAnalytics(config.googleAnalytics.trackingCode,
      //  request.session.user.userId,
      //  { strictCidFormat: false });
      analytics(request).event('Help Flow', analyticEvent).send();
      return replyWith(msgPath, toState || 'die', request, null);
    },
  };
}

var analytics = module.exports.analytics = function (request) {
  var _this = this;
  return universalAnalytics(_this._config.googleAnalytics.trackingCode,
    request.session.user.userId,
    { strictCidFormat: false });
};
<|endoftext|>


<commit_before>var socketIo = require('socket.io');
var command = require('./command');
var wrapper = require('./wrapper');
var api = require('./api');

var DEFAULT_TIMEOUT = 10 * 1000;

function deepClone(base) {
  if (!base || typeof base != 'object')
    return base;

  var cloned = Object.create(null);
  Object.keys(base).forEach(function(key) {
    cloned[key] = deepClone(base[key]);
  });
  return cloned;
}

function sanitizeBackendMessage(envelope) {
  return {
    statusCode: envelope.statusCode,
    body: deepClone(envelope.body)
  };
}
exports.sanitizeBackendMessage = sanitizeBackendMessage;

exports.register = function(application, server, params) {
  params = params || {};
  var connection = params.connection;
  if (!connection)
    throw new Error('Connection to the backend is required!');

  if (!server)
    throw new Error('A server instance is required!');

  function createClientMessageHandler(commandName, socket, onResponseCallback) {
    var commandDefinition = unifiedCommandSet[commandName];
    var defaultParameters = {};

    var droongaMessageOptions = {
      dataset:   commandDefinition.dataset,
      timeout:   DEFAULT_TIMEOUT,
      sessionId: socket.id
    };
    if (command.PublishSubscribe.isInstance(commandDefinition)) {
      defaultParameters.route      = connection.getRouteToSelf(droongaMessageOptions);
      defaultParameters.subscriber = command.sha1sum(defaultParameters.route);
    }

    return (function(data, clientOptions) {
      if (!commandDefinition)
        return;

      clientOptions = clientOptions || {};

      var event = commandName;
      var requestHandler  = 'onRequest';
      var responseHandler = 'onResponse';

      if (command.PublishSubscribe.isInstance(commandDefinition))  {
        data.subscriber = data.subscriber || defaultParameters.subscriber;
        data.route      = data.route      || defaultParameters.route;
        if (/\.subscribe$/.test(commandName)) {
          requestHandler  = 'onSubscribe';
          responseHandler = 'onSubscribed';
        } else if (/\.unsubscribe$/.test(commandName)) {
          requestHandler  = 'onUnsubscribe';
          responseHandler = 'onUnsubscribed';
        }
      }
      var callback = function(error, envelope) {
        if (error) {
          socket.emit('error', error);
          return;
        }

        var responseEvent = envelope.type;
        var responseData = envelope.body;
        var options = {};
        if (clientOptions.responseEvent) {
          responseEvent = clientOptions.responseEvent;
          options.event = responseEvent;
        }

        if (onResponseCallback && typeof onResponseCallback == 'function')
          onResponseCallback();

        var wrappedSocket = new wrapper.SocketIOClientSocketWrapper(socket, options);
        if (commandDefinition[responseHandler]) {
          try {
            commandDefinition[responseHandler](responseData, wrappedSocket);
          } catch(error) {
            wrappedSocket.emit('error', error.message || error);
          }
        } else {
          wrappedSocket.emit(responseEvent, responseData);
          wrappedSocket.destroy();
        }
      };

      var wrappedConection = new wrapper.DroongaProtocolConnectionWrapper(connection, callback, droongaMessageOptions);
      if (commandDefinition[requestHandler]) {
        try {
          commandDefinition[requestHandler](data, wrappedConection);
        } catch(error) {
          wrappedConection.destroy();
          socket.emit('error', error.message || error);
        }
      } else {
        wrappedConection.emit(event, data);
      }
    });
  }

  function createNotificationHandler(commandName, socket) {
    return (function(body) {
      var commandDefinition = unifiedCommandSet[commandName];

      var event = commandName;
      var data = body;

      var wrappedSocket = new wrapper.SocketIOClientSocketWrapper(socket);
      if (commandDefinition.onNotify) {
        try {
          commandDefinition.onNotify(data, wrappedSocket);
        } catch(error) {
          wrappedSocket.emit('error', error.message || error);
        }
      } else {
        wrappedSocket.emit(event, data);
        wrappedSocket.destroy();
      }
    });
  }

  function createErrorHandler(socket) {
    return (function(error) {
      socket.emit('error', error);
    });
  }

  var commandSets = api.normalize(params.plugins);

  var unifiedCommandSet = {};
  commandSets.forEach(function(commandSet) {
    Object.keys(commandSet).forEach(function(commandName) {
      var definition = commandSet[commandName];
      if (!command.SocketCommand.isInstance(definition))
        return;
      unifiedCommandSet[commandName] = definition;
    });
  });

  var registeredCommands = [];
  Object.keys(unifiedCommandSet).forEach(function(commandName) {
    var definition = unifiedCommandSet[commandName];
    if (!command.SocketCommand.isInstance(definition))
      return;
    registeredCommands.push({ name:       commandName,
                              definition: definition });
  });

  var io = socketIo.listen(server);
  io.sockets.on('connection', function(socket) {
    application.emit('connection', socket);

    var events = [];
    var handlers = {};
    registeredCommands.forEach(function(registeredCommand) {
      if (command.PublishSubscribe.isInstance(registeredCommand.definition)) {
        var subscribeEvent = registeredCommand.name + '.subscribe';
        var unsubscribeEvent = registeredCommand.name + '.unsubscribe';
        var notificationEvent = registeredCommand.name + '.notification';
        var notificationHandler = createNotificationHandler(notificationEvent, socket);

        socket.on(subscribeEvent,
                  createClientMessageHandler(subscribeEvent, socket, function() {
                    if (handlers[notificationEvent])
                      return;
                    connection.on(notificationEvent, notificationHandler);
                    handlers[notificationEvent] = notificationHandler;
                  }));
        socket.on(unsubscribeEvent,
                  createClientMessageHandler(unsubscribeEvent, socket, function() {
                    if (!handlers[notificationEvent])
                      return;
                    connection.removeListener(notificationEvent, notificationHandler);
                    delete handlers[notificationEvent];
                  }));

        unifiedCommandSet[subscribeEvent] = registeredCommand.definition;
        unifiedCommandSet[unsubscribeEvent] = registeredCommand.definition;
        unifiedCommandSet[notificationEvent] = registeredCommand.definition;

        events.push(notificationEvent);
      } else {
        socket.on(registeredCommand.name,
                  createClientMessageHandler(registeredCommand.name, socket));
      }
    });

    var errorHandler = createErrorHandler(socket);
    connection.on('error', errorHandler);

    socket.on('disconnect', function() {
      events.forEach(function(event) {
        if (handlers[event])
          connection.removeListener(event, handlers[event]);
      });
      events = undefined;
      handlers = undefined;
      connection.removeListener('error', errorHandler);
      socket.removeAllListeners();
    });
  });

  return registeredCommands;
}
<commit_msg>Resolve event listener method names when the handler is created<commit_after>var socketIo = require('socket.io');
var command = require('./command');
var wrapper = require('./wrapper');
var api = require('./api');

var DEFAULT_TIMEOUT = 10 * 1000;

function deepClone(base) {
  if (!base || typeof base != 'object')
    return base;

  var cloned = Object.create(null);
  Object.keys(base).forEach(function(key) {
    cloned[key] = deepClone(base[key]);
  });
  return cloned;
}

function sanitizeBackendMessage(envelope) {
  return {
    statusCode: envelope.statusCode,
    body: deepClone(envelope.body)
  };
}
exports.sanitizeBackendMessage = sanitizeBackendMessage;

exports.register = function(application, server, params) {
  params = params || {};
  var connection = params.connection;
  if (!connection)
    throw new Error('Connection to the backend is required!');

  if (!server)
    throw new Error('A server instance is required!');

  function createClientMessageHandler(commandName, socket, onResponseCallback) {
    var commandDefinition = unifiedCommandSet[commandName];
    var defaultParameters = {};

    var droongaMessageOptions = {
      dataset:   commandDefinition.dataset,
      timeout:   DEFAULT_TIMEOUT,
      sessionId: socket.id
    };
    if (command.PublishSubscribe.isInstance(commandDefinition)) {
      defaultParameters.route      = connection.getRouteToSelf(droongaMessageOptions);
      defaultParameters.subscriber = command.sha1sum(defaultParameters.route);
    }

    var requestHandler  = 'onRequest';
    var responseHandler = 'onResponse';
    if (command.PublishSubscribe.isInstance(commandDefinition))  {
      if (/\.subscribe$/.test(commandName)) {
        requestHandler  = 'onSubscribe';
        responseHandler = 'onSubscribed';
      } else if (/\.unsubscribe$/.test(commandName)) {
        requestHandler  = 'onUnsubscribe';
        responseHandler = 'onUnsubscribed';
      }
    }

    return (function(data, clientOptions) {
      if (!commandDefinition)
        return;

      clientOptions = clientOptions || {};

      if (command.PublishSubscribe.isInstance(commandDefinition))  {
        data.subscriber = data.subscriber || defaultParameters.subscriber;
        data.route      = data.route || defaultParameters.route;
      }

      var callback = function(error, envelope) {
        if (error) {
          socket.emit('error', error);
          return;
        }

        var responseEvent = envelope.type;
        var responseData = envelope.body;
        var options = {};
        if (clientOptions.responseEvent) {
          responseEvent = clientOptions.responseEvent;
          options.event = responseEvent;
        }

        if (onResponseCallback && typeof onResponseCallback == 'function')
          onResponseCallback();

        var wrappedSocket = new wrapper.SocketIOClientSocketWrapper(socket, options);
        if (commandDefinition[responseHandler]) {
          try {
            commandDefinition[responseHandler](responseData, wrappedSocket);
          } catch(error) {
            wrappedSocket.emit('error', error.message || error);
          }
        } else {
          wrappedSocket.emit(responseEvent, responseData);
          wrappedSocket.destroy();
        }
      };

      var wrappedConection = new wrapper.DroongaProtocolConnectionWrapper(connection, callback, droongaMessageOptions);
      if (commandDefinition[requestHandler]) {
        try {
          commandDefinition[requestHandler](data, wrappedConection);
        } catch(error) {
          wrappedConection.destroy();
          socket.emit('error', error.message || error);
        }
      } else {
        wrappedConection.emit(commandName, data);
      }
    });
  }

  function createNotificationHandler(commandName, socket) {
    return (function(body) {
      var commandDefinition = unifiedCommandSet[commandName];

      var event = commandName;
      var data = body;

      var wrappedSocket = new wrapper.SocketIOClientSocketWrapper(socket);
      if (commandDefinition.onNotify) {
        try {
          commandDefinition.onNotify(data, wrappedSocket);
        } catch(error) {
          wrappedSocket.emit('error', error.message || error);
        }
      } else {
        wrappedSocket.emit(event, data);
        wrappedSocket.destroy();
      }
    });
  }

  function createErrorHandler(socket) {
    return (function(error) {
      socket.emit('error', error);
    });
  }

  var commandSets = api.normalize(params.plugins);

  var unifiedCommandSet = {};
  commandSets.forEach(function(commandSet) {
    Object.keys(commandSet).forEach(function(commandName) {
      var definition = commandSet[commandName];
      if (!command.SocketCommand.isInstance(definition))
        return;
      unifiedCommandSet[commandName] = definition;
    });
  });

  var registeredCommands = [];
  Object.keys(unifiedCommandSet).forEach(function(commandName) {
    var definition = unifiedCommandSet[commandName];
    if (!command.SocketCommand.isInstance(definition))
      return;
    registeredCommands.push({ name:       commandName,
                              definition: definition });
  });

  var io = socketIo.listen(server);
  io.sockets.on('connection', function(socket) {
    application.emit('connection', socket);

    var events = [];
    var handlers = {};
    registeredCommands.forEach(function(registeredCommand) {
      if (command.PublishSubscribe.isInstance(registeredCommand.definition)) {
        var subscribeEvent = registeredCommand.name + '.subscribe';
        var unsubscribeEvent = registeredCommand.name + '.unsubscribe';
        var notificationEvent = registeredCommand.name + '.notification';
        var notificationHandler = createNotificationHandler(notificationEvent, socket);

        socket.on(subscribeEvent,
                  createClientMessageHandler(subscribeEvent, socket, function() {
                    if (handlers[notificationEvent])
                      return;
                    connection.on(notificationEvent, notificationHandler);
                    handlers[notificationEvent] = notificationHandler;
                  }));
        socket.on(unsubscribeEvent,
                  createClientMessageHandler(unsubscribeEvent, socket, function() {
                    if (!handlers[notificationEvent])
                      return;
                    connection.removeListener(notificationEvent, notificationHandler);
                    delete handlers[notificationEvent];
                  }));

        unifiedCommandSet[subscribeEvent] = registeredCommand.definition;
        unifiedCommandSet[unsubscribeEvent] = registeredCommand.definition;
        unifiedCommandSet[notificationEvent] = registeredCommand.definition;

        events.push(notificationEvent);
      } else {
        socket.on(registeredCommand.name,
                  createClientMessageHandler(registeredCommand.name, socket));
      }
    });

    var errorHandler = createErrorHandler(socket);
    connection.on('error', errorHandler);

    socket.on('disconnect', function() {
      events.forEach(function(event) {
        if (handlers[event])
          connection.removeListener(event, handlers[event]);
      });
      events = undefined;
      handlers = undefined;
      connection.removeListener('error', errorHandler);
      socket.removeAllListeners();
    });
  });

  return registeredCommands;
}
<|endoftext|>


<commit_before>// Dependencies
var fs = require('fs');
var request = require('request-json');
var conflate = require('conflate');

// Constants
var ENDPOINT_URL = 'http://trader.adgear.com/api/v2/';

/**
 * Constructor for the API client
 * @param authToken
 * @param config
 * @returns {Trader}
 * @constructor
 */
function Trader(authToken, config) {
	if (!authToken) throw new TypeError('required auth token');

	// Defaults
	config = config || {};

	// Config
	var self = this;
		self.authToken = authToken;
		self.endpoint = config.endpoint || ENDPOINT_URL;
		self.client = request.createClient(this.endpoint);

	return self;
}

/**
 * Requests JSON from the API and returns to callback
 * @param methodName
 * @param methodType
 * @param params
 * @param callback
 * @private
 */
Trader.prototype._request = function(methodName, methodType, params, callback) {
	if (!callback) throw new Error('Missing callback.');

	// Defaults
	params = params || {};

	// Add auth token
	params.qs = params.qs || {};
	params.qs.auth_token = this.authToken;

	var url = (typeof methodName === 'object') ? methodName.join('/') : methodName;

	return this.client[methodType](url, params, function(err, res, body) {
		if (err) throw new Error(url + ' error');

		callback(body);
	});
};

// Load resources from the resources folder to extend the prototype
var files = fs.readdirSync(__dirname + '/resources/');
for (var i = 0; i < files.length; i++) {
	if (files[i].match(/.*\.js/)) {
		var mod = require('./resources/' + files[i]);
		conflate(Trader.prototype, mod);
	}
}

module.exports = function(authToken, config) {
	return new Trader(authToken, config);
};<commit_msg>change var name<commit_after>// Dependencies
var fs = require('fs');
var request = require('request-json');
var conflate = require('conflate');

// Constants
var ENDPOINT_URL = 'http://trader.adgear.com/api/v2/';

/**
 * Constructor for the API client
 * @param authToken
 * @param config
 * @returns {Trader}
 * @constructor
 */
function Trader(authToken, config) {
	if (!authToken) throw new TypeError('required auth token');

	// Defaults
	config = config || {};

	// Config
	var self = this;
		self.authToken = authToken;
		self.endpoint = config.endpoint || ENDPOINT_URL;
		self.client = request.createClient(this.endpoint);

	return self;
}

/**
 * Requests JSON from the API and returns to callback
 * @param methodName
 * @param methodType
 * @param params
 * @param callback
 * @private
 */
Trader.prototype._request = function(methodName, methodType, params, callback) {
	if (!callback) throw new Error('Missing callback.');

	// Defaults
	params = params || {};

	// Add auth token
	params.qs = params.qs || {};
	params.qs.auth_token = this.authToken;

	var url = (typeof methodName === 'object') ? methodName.join('/') : methodName;

	return this.client[methodType](url, params, function(err, res, body) {
		if (err) throw new Error(url + ' error');

		callback(body);
	});
};

// Load resources from the resources folder to extend the prototype
var files = fs.readdirSync(__dirname + '/resources/');
for (var i = 0; i < files.length; i++) {
	if (files[i].match(/.*\.js/)) {
		var res = require('./resources/' + files[i]);
		conflate(Trader.prototype, res);
	}
}

module.exports = function(authToken, config) {
	return new Trader(authToken, config);
};<|endoftext|>


<commit_before>'use babel'

import TabController from './tab_controller'
import TabListView from './tab_list_view'
import { CompositeDisposable } from 'atom'
import _ from 'underscore-plus'

export default {
  tabControllers: [],
  tabListView: null,
  subscriptions: null,
  showIndex: 0,

  activate() {
    this.subscriptions = new CompositeDisposable()

    const initPanes = atom.workspace.getPanes()
    this.pushTabController(new TabController(initPanes))

    this.subscriptions.add(atom.commands.add('atom-workspace', {
      'atom-vim-like-tab:new': () => this.createNewTab(),
      'atom-vim-like-tab:close': () => this.closeTab(),
      'atom-vim-like-tab:next': () => this.showNextTab(),
      'atom-vim-like-tab:previous': () => this.showPreviousTab(),
      'atom-vim-like-tab:list': () => this.showTabList(),
    }))
  },

  deactivate() {
    this.subscriptions.dispose()
    this.tabControllers.forEach((tabContoller) => { tabContoller.destroy() })
    this.tabControllers = []
  },

  createNewTab() {
    // hide current tab
    this.tabControllers.forEach((tabContoller) => tabContoller.hide())

    // create new pane for new tab and change active pane
    const tabContoller = new TabController
    const activePane = atom.workspace.getActivePane()
    activePane.splitRight()

    this.pushTabController(tabContoller)
    this.showIndex = this.tabControllers.length - 1
  },

  closeTab() {
    const closingTabController = _.find(this.tabControllers, (_tabController) =>
      _tabController.isActive === true
    )
    closingTabController.closeAllPanes()
  },

  showTabByIndex(index) {
    this.showIndex = this.getValidShowIndex(index)
    _.each(this.tabControllers, (tabController, i) => {
      if (i !== this.showIndex) tabController.hide()
    })
    this.tabControllers[this.showIndex].show()
  },

  showNextTab() {
    this.showTabByIndex(this.showIndex + 1)
  },

  showPreviousTab() {
    this.showTabByIndex(this.showIndex - 1)
  },

  getValidShowIndex(index) {
    if (index >= this.tabControllers.length) {
      return 0
    } else if (index < 0) {
      return this.tabControllers.length - 1
    }

    return index
  },

  pushTabController(tabController) {
    this.tabControllers.push(tabController)

    tabController.onDidPanesEmpty((_tabController) => {
      _tabController.destroy()
      _.remove(this.tabControllers, _tabController)
      if (this.tabControllers.length > 0) this.showPreviousTab()
    })
  },

  showTabList() {
    this.tabListView = new TabListView(this.tabControllers)
    // when tabListView confirmed show selected tab
    this.tabListView.onDidTabListConfirmed((index) => {
      this.showTabByIndex(index)
    })
    this.tabListView.show()
  },
}
<commit_msg>use same list-view DOM when open list second time<commit_after>'use babel'

import TabController from './tab_controller'
import TabListView from './tab_list_view'
import { CompositeDisposable } from 'atom'
import _ from 'underscore-plus'

export default {
  tabControllers: [],
  tabListView: null,
  subscriptions: null,
  showIndex: 0,

  activate() {
    this.subscriptions = new CompositeDisposable()

    const initPanes = atom.workspace.getPanes()
    this.pushTabController(new TabController(initPanes))

    this.tabListView = new TabListView(this.tabControllers)
    // when tabListView confirmed show selected tab
    this.tabListView.onDidTabListConfirmed((index) => {
      this.showTabByIndex(index)
    })

    this.subscriptions.add(atom.commands.add('atom-workspace', {
      'atom-vim-like-tab:new': () => this.createNewTab(),
      'atom-vim-like-tab:close': () => this.closeTab(),
      'atom-vim-like-tab:next': () => this.showNextTab(),
      'atom-vim-like-tab:previous': () => this.showPreviousTab(),
      'atom-vim-like-tab:list': () => this.showTabList(),
    }))
  },

  deactivate() {
    this.subscriptions.dispose()
    this.tabControllers.forEach((tabContoller) => { tabContoller.destroy() })
    this.tabControllers = []
    this.tabListView = null
  },

  createNewTab() {
    // hide current tab
    this.tabControllers.forEach((tabContoller) => tabContoller.hide())

    // create new pane for new tab and change active pane
    const tabContoller = new TabController
    const activePane = atom.workspace.getActivePane()
    activePane.splitRight()

    this.pushTabController(tabContoller)
    this.showIndex = this.tabControllers.length - 1
  },

  closeTab() {
    const closingTabController = _.find(this.tabControllers, (_tabController) =>
      _tabController.isActive === true
    )
    closingTabController.closeAllPanes()
  },

  showTabByIndex(index) {
    this.showIndex = this.getValidShowIndex(index)
    _.each(this.tabControllers, (tabController, i) => {
      if (i !== this.showIndex) tabController.hide()
    })
    this.tabControllers[this.showIndex].show()
  },

  showNextTab() {
    this.showTabByIndex(this.showIndex + 1)
  },

  showPreviousTab() {
    this.showTabByIndex(this.showIndex - 1)
  },

  getValidShowIndex(index) {
    if (index >= this.tabControllers.length) {
      return 0
    } else if (index < 0) {
      return this.tabControllers.length - 1
    }

    return index
  },

  pushTabController(tabController) {
    this.tabControllers.push(tabController)

    tabController.onDidPanesEmpty((_tabController) => {
      _tabController.destroy()
      _.remove(this.tabControllers, _tabController)
      if (this.tabControllers.length > 0) this.showPreviousTab()
    })
  },

  showTabList() {
    this.tabListView.show()
  },
}
<|endoftext|>


<commit_before>if ( typeof bbop == "undefined" ){ var bbop = {}; }
if ( typeof bbop.widget == "undefined" ){ bbop.widget = {}; }
if ( typeof bbop.widget.phylo == "undefined" ){ bbop.widget.phylo = {}; }

(function() {

bbop.widget.phylo.renderer = renderer;

function renderer(parent, golr_loc, golr_conf, config) {
    this.parent = parent;

    var default_config = {
        // these are in pixels
        // row vertical height, including padding and borders
        row_height: 18,
        // vertical space between the contents of adjacent rows
        row_spacing: 2,
        font: "Helvetica, Arial, sans-serif",
        mat_cell_width: 15,
        transition_time: "0.8s"
    };

    this.config = ("object" == typeof config
                   ? bbop.core.merge(default_config, config)
                   : default_config);

    this.phylo_facet_list = [
        "panther_family_label",
        "phylo_graph_json",
        "score"
    ].join(",");
    this.ann_facet_list = [
        "id",
        "bioentity",
        "bioentity_label",
        "taxon_label",
        "bioentity_name",
        "annotation_class_list_map",
        "score"
    ].join(",");

    var golr = new bbop.golr.manager.jquery(golr_loc, golr_conf);
    golr.set_personality('bbop_bio');
    golr.add_query_filter('document_category', 'bioentity', ['*']);
    this.golr = golr;
}

renderer.prototype.show_family = function(family_id) {
    var self = this;
    var pgraph;

    function phyloCallback(response) {
        // make sure we don't show the phylo tree before the doc is ready
        jQuery(document).ready(function() {
            if (response.documents().length > 0) {
                var pgraph_json = response.documents()[0].phylo_graph_json;
                pgraph = (JSON.parse(pgraph_json));

                self.golr.register('search', 's', annCallback);
                self.golr.set_query("panther_family:" + family_id);
                self.golr.current_fl = self.ann_facet_list;
                self.golr.set("fl", self.ann_facet_list);
                self.golr.page(response.total_documents(), 0);
            } else {
                console.log("no documents received");
            }
        });
    }

    function annCallback(response) {
        if (response.documents().length > 0) {
            self.bioentity_map =
                self.match_pnodes(pgraph.nodes, response.documents());
            self.show_pgraph(pgraph);
        } else {
            console.log("no documents received");
        }
    }

    this.golr.register('search', 's', phyloCallback);
    this.golr.set_query("panther_family:" + family_id);
    this.golr.current_fl = this.phylo_facet_list;
    this.golr.set("fl", this.phylo_facet_list);
    this.golr.page(1, 0);
};

renderer.prototype.match_pnodes = function(pnodes, bioentities) {
    var bioent_id_map = {};
    var bioents_by_pthr_id = {};
    var bioents_matched = 0;

    for (var b = 0; b < bioentities.length; b++) {
        bioent_id_map[bioentities[b].id] = bioentities[b];
    }

    for (var n = 0; n < pnodes.length; n++) {
        if ("annotations" in pnodes[n].meta) {
            var pgraph_bioent_ids = pnodes[n].meta.annotations.split("|");
            for (var pbi = 0; pbi < pgraph_bioent_ids.length; pbi++) {
                if (pgraph_bioent_ids[pbi] in bioent_id_map) {
                    bioents_by_pthr_id[pnodes[n].id] =
                        bioent_id_map[pgraph_bioent_ids[pbi]];
                    bioents_matched++;
                    //console.log("found bioentity for " + pnodes[n].id + " (" + pgraph_bioent_ids[pbi] + ")");

                }
            }
            if (! (pnodes[n].id in bioents_by_pthr_id)) {
                //console.log("no bioentity for " + pnodes[n].id + " (" + pgraph_bioent_ids.join(",") + ")");
            }
        } else {
            // this is typically the case for internal nodes
            //console.log("no non-panther IDs for " + pnodes[n].id);
        }
    }
    //console.log(pnodes.length + " phylo nodes");
    //console.log(bioentities.length + " bioentities");

    //console.log(bioents_matched + " matched");
    return bioents_by_pthr_id;
};

renderer.prototype.show_pgraph = function(pgraph) {
    var self = this;
    this.parent = ( ( "string" == typeof this.parent )
		    ? document.getElementById(this.parent)
		    : this.parent );
    jQuery(this.parent).empty();

    var tree_container = document.createElement("div");
    tree_container.style.cssText = "position: absolute; top: 0px; bottom: 100%; left: 0px;";

    var mat_container = document.createElement("div");
    mat_container.style.cssText = "position: absolute; top: 0px; bottom: 100%; background-color: #ddd;";

    var all_go_terms = {};
    var go_term_list = [];
    var node_go_annots = {};
    for (var nid in this.bioentity_map) {
        var bioent = this.bioentity_map[nid];
        var annots = JSON.parse(bioent.annotation_class_list_map);
        node_go_annots[nid] = annots;
        for (var goterm in annots) {
            var term_desc = all_go_terms[goterm];
            if (term_desc === undefined) {
                term_desc = {
                    count: 0,
                    id: goterm,
                    name: annots[goterm]
                };
                all_go_terms[goterm] = term_desc;
                go_term_list.push(term_desc);
            }
            term_desc.count += 1;
        }
    }

    go_term_list.sort( function(a, b) { return b.count - a.count; } );
    var coldescs = go_term_list.map( function(x) { return x.id; } );
    //console.log(coldescs);

    var mat_width = coldescs.length * this.config.mat_cell_width;
    var tree_width = 500;
    tree_container.style.width = tree_width + "px";
    mat_container.style.width = mat_width + "px";
    mat_container.style.left = tree_width + "px";
    this.parent.style.width = (tree_width + mat_width) + "px";

    this.parent.appendChild(tree_container);
    this.parent.appendChild(mat_container);
    var tree_renderer = new bbop.widget.phylo_tree.renderer(tree_container, {
        box_height: this.config.row_height,
        box_spacing: this.config.row_spacing,
        leaf_font: this.config.font,
        leaf_border: 0,
        leaf_padding: 3,
        node_size: 8,
        transition_time: this.config.transition_time
    });
    tree_renderer.leaf_style.background = "none";
    tree_renderer.node_style.cursor = "pointer";

    var nodes = pgraph.nodes;
    var edges = pgraph.edges;
    //console.log(nodes.length + " nodes");
    //console.log(edges.length + " edges");
    function node_label(node) {
        var bioe =
            node.id in self.bioentity_map ?
            self.bioentity_map[node.id] :
            null;

        var label = node.lbl;
        // abbreviate genus
        if (bioe) {
            var taxon_words = [];
            if (bioe.taxon_label) taxon_words = bioe.taxon_label.split(/\s+/);
            // sometimes there are more than two taxon words (strain name?)
            // but taxon_words[0] appears to be the genus name
            if (taxon_words.length >= 2) {
                taxon_words[0] = taxon_words[0].substr(0, 1) + ".";
            }
            label = taxon_words.join(" ") + ":" + bioe.bioentity_label;
        }
        return label
    }

    for (var i = 0; i < nodes.length; i++) {
        tree_renderer.add_node(nodes[i].id,
                               node_label(nodes[i]),
                               nodes[i].meta);
    }

    for (var i = 0; i < edges.length; i++) {
        tree_renderer.add_edge(edges[i].sub,
                               edges[i].obj,
                               parseFloat(edges[i].meta.distance));
    }

    tree_renderer.set_sort(function(a, b) {
        return parseInt(a.meta.layout_index) - parseInt(b.meta.layout_index);
    });

    tree_renderer.node_clicked = function(node, node_elem) {
        if (tree_renderer.subtree_hidden(node.id)) {
            tree_renderer.show_subtree(node.id);
            var leaf_id_list = tree_renderer.leaves().map(
                function(x) { return x.id }
            );
            mat_renderer.show_rows(leaf_id_list);
            self.update_heights();
        } else {
            tree_renderer.hide_subtree(node.id);
            var leaf_id_list = tree_renderer.leaves().map(
                function(x) { return x.id }
            );
            mat_renderer.show_rows(leaf_id_list);
            self.update_heights();
        }
    };

    var node_colors = {};
    var cur_color = 1;
    tree_renderer.traverse(
        function(node, down_data) {
            if (node.id in node_colors) {
                down_data = node_colors[node.id]
            }
            if ("true" == node.meta.duplication_p) {
                var have_grandchildren = false;
                var nearest_child_dist = Infinity;
                var nearest_child;
                for (var i = 0; i < node.children.length; i++) {
                    if (node.children[i].parent_distance < nearest_child_dist) {
                        nearest_child = node.children[i];
                        nearest_child_dist = nearest_child.parent_distance;
                    }
                    have_grandchildren =
                        have_grandchildren
                        || (! node.children[i].is_leaf() );
                }
                if (have_grandchildren) {
                    // under duplication nodes, we give new colors to
                    // all children other than the nearest
                    for (var i = 0; i < node.children.length; i++) {
                        if (node.children[i] === nearest_child) {
                            node_colors[node.children[i].id] = down_data;
                        } else {
                            node_colors[node.children[i].id] = cur_color++;
                        }
                    }
                }
            }
            if (! (node.id in node_colors)) {
                node_colors[node.id] = down_data;
            }
            return node_colors[node.id];
        },
        0,
        function() {}
    );
    this.node_colors = node_colors;

    this.tree_renderer = tree_renderer;
    this.render_tree();

    var node_id_list = tree_renderer.leaves().map(function(x) { return x.id });
    var node_id_map = {};
    for (var i = 0; i < nodes.length; i++) {
        node_id_map[nodes[i].id] = nodes[i];
    }

    function cell_renderer(cell, row, col) {
        var cell_bg = "#fff";
        var node_go = node_go_annots[row];
        if (node_go !== undefined) {
            if (col in node_go) {
                cell_bg = "#555";
            }
        }
        cell.style.backgroundColor = cell_bg;
        cell.title =
            node_label(node_id_map[row]) + " - " + all_go_terms[col].name;
    }

    var mat_config = {
        cell_width: this.config.mat_cell_width,
        cell_height: this.config.row_height + 2,
        header_height: 0,
        show_headers: false,
        transition_time: this.config.transition_time
    };
    var mat_renderer =
        new bbop.widget.matrix.renderer(mat_container, node_id_list,
                                        coldescs, cell_renderer, mat_config);

    var leaf_id_list = tree_renderer.leaves().map(
        function(x) { return x.id }
    );
    mat_renderer.show_rows(leaf_id_list);
    this.parent.style.transition =
        "height " + this.config.transition_time + " ease-in-out";
    this.update_heights();

    this.mat_renderer = mat_renderer;
};

renderer.prototype.update_heights = function() {
    this.parent.style.height = this.tree_renderer.tree_height + "px";
};

renderer.prototype.render_tree = function() {
    this.tree_renderer.display();

    this.tree_renderer.iterate_preorder(
        function(id, label, meta, dom_elem, children) {
            if ("true" == meta.speciation_p) {
                dom_elem.style.borderRadius = "6px";
            } else if ("true" == meta.duplication_p) {
                var has_children = children.length > 0;
                if (has_children) {
                    dom_elem.style.backgroundColor = "black";
                }
            }
        }
    );

    var color_list = [
        "white",
        "rgb(238, 232, 170)",
        "rgb(255, 182, 193)",
        "rgb(135, 206, 250)",
        "rgb(240, 128, 128)",
        "rgb(152, 251, 152)",
        "rgb(216, 191, 216)",
        "rgb(240, 230, 140)",
        "rgb(224, 255, 255)",
        "rgb(255, 218, 185)",
        "rgb(211, 211, 211)",
        "rgb(255, 250, 205)",
        "rgb(176, 196, 222)",
        "rgb(255, 228, 173)",
        "rgb(175, 238, 238)",
        "rgb(244, 164, 96)",
        "rgb(127, 255, 212)",
        "rgb(245, 222, 179)",
        "rgb(255, 160, 122)",
        "rgb(221, 160, 221)"
    ];
    var self = this;
    this.tree_renderer.iterate_preorder(
        function(id, label, meta, dom_elem, children) {
            var is_leaf = 0 == children.length;
            if (is_leaf) {
                dom_elem.style.backgroundColor =
                    color_list[self.node_colors[id] % color_list.length];
                if (id in self.bioentity_map) {
                    dom_elem.style.color = "black";
                } else {
                    dom_elem.style.color = "#aaa";
                }
            }
        }
    );

};

})();
<commit_msg>enforce position relative on phylo tree container<commit_after>if ( typeof bbop == "undefined" ){ var bbop = {}; }
if ( typeof bbop.widget == "undefined" ){ bbop.widget = {}; }
if ( typeof bbop.widget.phylo == "undefined" ){ bbop.widget.phylo = {}; }

(function() {

bbop.widget.phylo.renderer = renderer;

function renderer(parent, golr_loc, golr_conf, config) {
    this.parent = parent;

    var default_config = {
        // these are in pixels
        // row vertical height, including padding and borders
        row_height: 18,
        // vertical space between the contents of adjacent rows
        row_spacing: 2,
        font: "Helvetica, Arial, sans-serif",
        mat_cell_width: 15,
        transition_time: "0.8s"
    };

    this.config = ("object" == typeof config
                   ? bbop.core.merge(default_config, config)
                   : default_config);

    this.phylo_facet_list = [
        "panther_family_label",
        "phylo_graph_json",
        "score"
    ].join(",");
    this.ann_facet_list = [
        "id",
        "bioentity",
        "bioentity_label",
        "taxon_label",
        "bioentity_name",
        "annotation_class_list_map",
        "score"
    ].join(",");

    var golr = new bbop.golr.manager.jquery(golr_loc, golr_conf);
    golr.set_personality('bbop_bio');
    golr.add_query_filter('document_category', 'bioentity', ['*']);
    this.golr = golr;
}

renderer.prototype.show_family = function(family_id) {
    var self = this;
    var pgraph;

    function phyloCallback(response) {
        // make sure we don't show the phylo tree before the doc is ready
        jQuery(document).ready(function() {
            if (response.documents().length > 0) {
                var pgraph_json = response.documents()[0].phylo_graph_json;
                pgraph = (JSON.parse(pgraph_json));

                self.golr.register('search', 's', annCallback);
                self.golr.set_query("panther_family:" + family_id);
                self.golr.current_fl = self.ann_facet_list;
                self.golr.set("fl", self.ann_facet_list);
                self.golr.page(response.total_documents(), 0);
            } else {
                console.log("no documents received");
            }
        });
    }

    function annCallback(response) {
        if (response.documents().length > 0) {
            self.bioentity_map =
                self.match_pnodes(pgraph.nodes, response.documents());
            self.show_pgraph(pgraph);
        } else {
            console.log("no documents received");
        }
    }

    this.golr.register('search', 's', phyloCallback);
    this.golr.set_query("panther_family:" + family_id);
    this.golr.current_fl = this.phylo_facet_list;
    this.golr.set("fl", this.phylo_facet_list);
    this.golr.page(1, 0);
};

renderer.prototype.match_pnodes = function(pnodes, bioentities) {
    var bioent_id_map = {};
    var bioents_by_pthr_id = {};
    var bioents_matched = 0;

    for (var b = 0; b < bioentities.length; b++) {
        bioent_id_map[bioentities[b].id] = bioentities[b];
    }

    for (var n = 0; n < pnodes.length; n++) {
        if ("annotations" in pnodes[n].meta) {
            var pgraph_bioent_ids = pnodes[n].meta.annotations.split("|");
            for (var pbi = 0; pbi < pgraph_bioent_ids.length; pbi++) {
                if (pgraph_bioent_ids[pbi] in bioent_id_map) {
                    bioents_by_pthr_id[pnodes[n].id] =
                        bioent_id_map[pgraph_bioent_ids[pbi]];
                    bioents_matched++;
                    //console.log("found bioentity for " + pnodes[n].id + " (" + pgraph_bioent_ids[pbi] + ")");

                }
            }
            if (! (pnodes[n].id in bioents_by_pthr_id)) {
                //console.log("no bioentity for " + pnodes[n].id + " (" + pgraph_bioent_ids.join(",") + ")");
            }
        } else {
            // this is typically the case for internal nodes
            //console.log("no non-panther IDs for " + pnodes[n].id);
        }
    }
    //console.log(pnodes.length + " phylo nodes");
    //console.log(bioentities.length + " bioentities");

    //console.log(bioents_matched + " matched");
    return bioents_by_pthr_id;
};

renderer.prototype.show_pgraph = function(pgraph) {
    var self = this;
    this.parent = ( ( "string" == typeof this.parent )
		    ? document.getElementById(this.parent)
		    : this.parent );
    jQuery(this.parent).empty();

    var parentPos = getStyle(this.parent, "position");
    console.log("parent position: " + parentPos);
    if ( ! ( ("absolute" == parentPos)
             ||
             ("relative" == parentPos) ) ) {
        this.parent.style.position = "relative";
    }

    var tree_container = document.createElement("div");
    tree_container.style.cssText = "position: absolute; top: 0px; bottom: 100%; left: 0px;";

    var mat_container = document.createElement("div");
    mat_container.style.cssText = "position: absolute; top: 0px; bottom: 100%; background-color: #ddd;";

    var all_go_terms = {};
    var go_term_list = [];
    var node_go_annots = {};
    for (var nid in this.bioentity_map) {
        var bioent = this.bioentity_map[nid];
        var annots = JSON.parse(bioent.annotation_class_list_map);
        node_go_annots[nid] = annots;
        for (var goterm in annots) {
            var term_desc = all_go_terms[goterm];
            if (term_desc === undefined) {
                term_desc = {
                    count: 0,
                    id: goterm,
                    name: annots[goterm]
                };
                all_go_terms[goterm] = term_desc;
                go_term_list.push(term_desc);
            }
            term_desc.count += 1;
        }
    }

    go_term_list.sort( function(a, b) { return b.count - a.count; } );
    var coldescs = go_term_list.map( function(x) { return x.id; } );
    //console.log(coldescs);

    var mat_width = coldescs.length * this.config.mat_cell_width;
    var tree_width = 500;
    tree_container.style.width = tree_width + "px";
    mat_container.style.width = mat_width + "px";
    mat_container.style.left = tree_width + "px";
    this.parent.style.width = (tree_width + mat_width) + "px";

    this.parent.appendChild(tree_container);
    this.parent.appendChild(mat_container);
    var tree_renderer = new bbop.widget.phylo_tree.renderer(tree_container, {
        box_height: this.config.row_height,
        box_spacing: this.config.row_spacing,
        leaf_font: this.config.font,
        leaf_border: 0,
        leaf_padding: 3,
        node_size: 8,
        transition_time: this.config.transition_time
    });
    tree_renderer.leaf_style.background = "none";
    tree_renderer.node_style.cursor = "pointer";

    var nodes = pgraph.nodes;
    var edges = pgraph.edges;
    //console.log(nodes.length + " nodes");
    //console.log(edges.length + " edges");
    function node_label(node) {
        var bioe =
            node.id in self.bioentity_map ?
            self.bioentity_map[node.id] :
            null;

        var label = node.lbl;
        // abbreviate genus
        if (bioe) {
            var taxon_words = [];
            if (bioe.taxon_label) taxon_words = bioe.taxon_label.split(/\s+/);
            // sometimes there are more than two taxon words (strain name?)
            // but taxon_words[0] appears to be the genus name
            if (taxon_words.length >= 2) {
                taxon_words[0] = taxon_words[0].substr(0, 1) + ".";
            }
            label = taxon_words.join(" ") + ":" + bioe.bioentity_label;
        }
        return label
    }

    for (var i = 0; i < nodes.length; i++) {
        tree_renderer.add_node(nodes[i].id,
                               node_label(nodes[i]),
                               nodes[i].meta);
    }

    for (var i = 0; i < edges.length; i++) {
        tree_renderer.add_edge(edges[i].sub,
                               edges[i].obj,
                               parseFloat(edges[i].meta.distance));
    }

    tree_renderer.set_sort(function(a, b) {
        return parseInt(a.meta.layout_index) - parseInt(b.meta.layout_index);
    });

    tree_renderer.node_clicked = function(node, node_elem) {
        if (tree_renderer.subtree_hidden(node.id)) {
            tree_renderer.show_subtree(node.id);
            var leaf_id_list = tree_renderer.leaves().map(
                function(x) { return x.id }
            );
            mat_renderer.show_rows(leaf_id_list);
            self.update_heights();
        } else {
            tree_renderer.hide_subtree(node.id);
            var leaf_id_list = tree_renderer.leaves().map(
                function(x) { return x.id }
            );
            mat_renderer.show_rows(leaf_id_list);
            self.update_heights();
        }
    };

    var node_colors = {};
    var cur_color = 1;
    tree_renderer.traverse(
        function(node, down_data) {
            if (node.id in node_colors) {
                down_data = node_colors[node.id]
            }
            if ("true" == node.meta.duplication_p) {
                var have_grandchildren = false;
                var nearest_child_dist = Infinity;
                var nearest_child;
                for (var i = 0; i < node.children.length; i++) {
                    if (node.children[i].parent_distance < nearest_child_dist) {
                        nearest_child = node.children[i];
                        nearest_child_dist = nearest_child.parent_distance;
                    }
                    have_grandchildren =
                        have_grandchildren
                        || (! node.children[i].is_leaf() );
                }
                if (have_grandchildren) {
                    // under duplication nodes, we give new colors to
                    // all children other than the nearest
                    for (var i = 0; i < node.children.length; i++) {
                        if (node.children[i] === nearest_child) {
                            node_colors[node.children[i].id] = down_data;
                        } else {
                            node_colors[node.children[i].id] = cur_color++;
                        }
                    }
                }
            }
            if (! (node.id in node_colors)) {
                node_colors[node.id] = down_data;
            }
            return node_colors[node.id];
        },
        0,
        function() {}
    );
    this.node_colors = node_colors;

    this.tree_renderer = tree_renderer;
    this.render_tree();

    var node_id_list = tree_renderer.leaves().map(function(x) { return x.id });
    var node_id_map = {};
    for (var i = 0; i < nodes.length; i++) {
        node_id_map[nodes[i].id] = nodes[i];
    }

    function cell_renderer(cell, row, col) {
        var cell_bg = "#fff";
        var node_go = node_go_annots[row];
        if (node_go !== undefined) {
            if (col in node_go) {
                cell_bg = "#555";
            }
        }
        cell.style.backgroundColor = cell_bg;
        cell.title =
            node_label(node_id_map[row]) + " - " + all_go_terms[col].name;
    }

    var mat_config = {
        cell_width: this.config.mat_cell_width,
        cell_height: this.config.row_height + 2,
        header_height: 0,
        show_headers: false,
        transition_time: this.config.transition_time
    };
    var mat_renderer =
        new bbop.widget.matrix.renderer(mat_container, node_id_list,
                                        coldescs, cell_renderer, mat_config);

    var leaf_id_list = tree_renderer.leaves().map(
        function(x) { return x.id }
    );
    mat_renderer.show_rows(leaf_id_list);
    this.parent.style.transition =
        "height " + this.config.transition_time + " ease-in-out";
    this.update_heights();

    this.mat_renderer = mat_renderer;
};

renderer.prototype.update_heights = function() {
    this.parent.style.height = this.tree_renderer.tree_height + "px";
};

renderer.prototype.render_tree = function() {
    this.tree_renderer.display();

    this.tree_renderer.iterate_preorder(
        function(id, label, meta, dom_elem, children) {
            if ("true" == meta.speciation_p) {
                dom_elem.style.borderRadius = "6px";
            } else if ("true" == meta.duplication_p) {
                var has_children = children.length > 0;
                if (has_children) {
                    dom_elem.style.backgroundColor = "black";
                }
            }
        }
    );

    var color_list = [
        "white",
        "rgb(238, 232, 170)",
        "rgb(255, 182, 193)",
        "rgb(135, 206, 250)",
        "rgb(240, 128, 128)",
        "rgb(152, 251, 152)",
        "rgb(216, 191, 216)",
        "rgb(240, 230, 140)",
        "rgb(224, 255, 255)",
        "rgb(255, 218, 185)",
        "rgb(211, 211, 211)",
        "rgb(255, 250, 205)",
        "rgb(176, 196, 222)",
        "rgb(255, 228, 173)",
        "rgb(175, 238, 238)",
        "rgb(244, 164, 96)",
        "rgb(127, 255, 212)",
        "rgb(245, 222, 179)",
        "rgb(255, 160, 122)",
        "rgb(221, 160, 221)"
    ];
    var self = this;
    this.tree_renderer.iterate_preorder(
        function(id, label, meta, dom_elem, children) {
            var is_leaf = 0 == children.length;
            if (is_leaf) {
                dom_elem.style.backgroundColor =
                    color_list[self.node_colors[id] % color_list.length];
                if (id in self.bioentity_map) {
                    dom_elem.style.color = "black";
                } else {
                    dom_elem.style.color = "#aaa";
                }
            }
        }
    );

};

function getStyle(el, styleProp) {
    if (el.currentStyle) {
        var y = el.currentStyle[styleProp];
    } else if (window.getComputedStyle) {
        var y = document.defaultView.getComputedStyle(el,null).getPropertyValue(styleProp);
    }
    return y;
}

})();
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var BlockHeader = require('./blockheader');
var BufferUtil = require('../util/buffer');
var BufferReader = require('../encoding/bufferreader');
var BufferWriter = require('../encoding/bufferwriter');
var Hash = require('../crypto/hash');
var JSUtil = require('../util/js');
var Transaction = require('../transaction');
var errors = require('../errors');
var $ = require('../util/preconditions');

/**
 * Instantiate a MerkleBlock from a Buffer, JSON object, or Object with
 * the properties of the Block
 *
 * @param {*} - A Buffer, JSON string, or Object representing a MerkleBlock
 * @returns {MerkleBlock}
 * @constructor
 */
function MerkleBlock(arg) {
  /* jshint maxstatements: 18 */

  if (!(this instanceof MerkleBlock)) {
    return new MerkleBlock(arg);
  }

  var info = {};
  if (BufferUtil.isBuffer(arg)) {
    info = MerkleBlock._fromBufferReader(BufferReader(arg));
  } else if (_.isObject(arg)) {
    var header;
    if(arg.header instanceof BlockHeader) {
      header = arg.header;
    } else {
      header = BlockHeader.fromObject(arg.header);
    }
    info = {
      /**
       * @name MerkleBlock#header
       * @type {BlockHeader}
       */
      header: header,
      /**
       * @name MerkleBlock#numTransactions
       * @type {Number}
       */
      numTransactions: arg.numTransactions,
      /**
       * @name MerkleBlock#hashes
       * @type {String[]}
       */
      hashes: arg.hashes,
      /**
       * @name MerkleBlock#flags
       * @type {Number[]}
       */
      flags: arg.flags
    };
  } else {
    throw new TypeError('Unrecognized argument for MerkleBlock');
  }
  _.extend(this,info);
  this._flagBitsUsed = 0;
  this._hashesUsed = 0;

  return this;
}

/**
 * @param {Buffer} - MerkleBlock data in a Buffer object
 * @returns {MerkleBlock} - A MerkleBlock object
 */
MerkleBlock.fromBuffer = function fromBuffer(buf) {
  return MerkleBlock.fromBufferReader(BufferReader(buf));
};

/**
 * @param {BufferReader} - MerkleBlock data in a BufferReader object
 * @returns {MerkleBlock} - A MerkleBlock object
 */
MerkleBlock.fromBufferReader = function fromBufferReader(br) {
  return new MerkleBlock(MerkleBlock._fromBufferReader(br));
};

/**
 * @returns {Buffer} - A buffer of the block
 */
MerkleBlock.prototype.toBuffer = function toBuffer() {
  return this.toBufferWriter().concat();
};

/**
 * @param {BufferWriter} - An existing instance of BufferWriter
 * @returns {BufferWriter} - An instance of BufferWriter representation of the MerkleBlock
 */
MerkleBlock.prototype.toBufferWriter = function toBufferWriter(bw) {
  if (!bw) {
    bw = new BufferWriter();
  }
  bw.write(this.header.toBuffer());
  bw.writeUInt32LE(this.numTransactions);
  bw.writeVarintNum(this.hashes.length);
  for (var i = 0; i < this.hashes.length; i++) {
    bw.write(new Buffer(this.hashes[i], 'hex'));
  }
  bw.writeVarintNum(this.flags.length);
  for (i = 0; i < this.flags.length; i++) {
    bw.writeUInt8(this.flags[i]);
  }
  return bw;
};

/**
 * @returns {Object} - A plain object with the MerkleBlock properties
 */
MerkleBlock.prototype.toObject = MerkleBlock.prototype.toJSON = function toObject() {
  return {
    header: this.header.toObject(),
    numTransactions: this.numTransactions,
    hashes: this.hashes,
    flags: this.flags
  };
};

/**
 * Verify that the MerkleBlock is valid
 * @returns {Boolean} - True/False whether this MerkleBlock is Valid
 */
MerkleBlock.prototype.validMerkleTree = function validMerkleTree() {
  $.checkState(_.isArray(this.flags), 'MerkleBlock flags is not an array');
  $.checkState(_.isArray(this.hashes), 'MerkleBlock hashes is not an array');

  // Can't have more hashes than numTransactions
  if(this.hashes.length > this.numTransactions) {
    return false;
  }

  // Can't have more flag bits than num hashes
  if(this.flags.length * 8 < this.hashes.length) {
    return false;
  }

  var height = this._calcTreeHeight();
  var opts = { hashesUsed: 0, flagBitsUsed: 0 };
  var root = this._traverseMerkleTree(height, 0, opts);
  if(opts.hashesUsed !== this.hashes.length) {
    return false;
  }
  return BufferUtil.equals(root, this.header.merkleRoot);
};

/**
 * Return a list of all the txs hash that match the filter
 * @returns {Array} - txs hash that match the filter
 */
MerkleBlock.prototype.filterdTxsHash = function validMerkleTree() {
  $.checkState(_.isArray(this.flags), 'MerkleBlock flags is not an array');
  $.checkState(_.isArray(this.hashes), 'MerkleBlock hashes is not an array');

  // Can't have more hashes than numTransactions
  if(this.hashes.length > this.numTransactions) {
    throw new errors.MerkleBlock.InvalidMerkleTree();
  }

  // Can't have more flag bits than num hashes
  if(this.flags.length * 8 < this.hashes.length) {
    throw new errors.MerkleBlock.InvalidMerkleTree();
  }

  var height = this._calcTreeHeight();
  var opts = { hashesUsed: 0, flagBitsUsed: 0 };
  var txs = this._traverseMerkleTree(height, 0, opts, true);
  if(opts.hashesUsed !== this.hashes.length) {
    throw new errors.MerkleBlock.InvalidMerkleTree();
  }
  return txs;
};

/**
 * Traverse a the tree in this MerkleBlock, validating it along the way
 * Modeled after Bitcoin Core merkleblock.cpp TraverseAndExtract()
 * @param {Number} - depth - Current height
 * @param {Number} - pos - Current position in the tree
 * @param {Object} - opts - Object with values that need to be mutated throughout the traversal
 * @param {Boolean} - checkForTxs - if true return opts.txs else return the Merkle Hash
 * @param {Number} - opts.flagBitsUsed - Number of flag bits used, should start at 0
 * @param {Number} - opts.hashesUsed - Number of hashes used, should start at 0
 * @param {Array} - opts.txs - Will finish populated by transactions found during traversal that match the filter
 * @returns {Buffer|null} - Buffer containing the Merkle Hash for that height
 * @returns {Array} - transactions found during traversal that match the filter
 * @private
 */
MerkleBlock.prototype._traverseMerkleTree = function traverseMerkleTree(depth, pos, opts, checkForTxs) {
  /* jshint maxcomplexity:  12*/
  /* jshint maxstatements: 20 */

  opts = opts || {};
  opts.txs = opts.txs || [];
  opts.flagBitsUsed = opts.flagBitsUsed || 0;
  opts.hashesUsed = opts.hashesUsed || 0;
  var checkForTxs = checkForTxs || false;

  if(opts.flagBitsUsed > this.flags.length * 8) {
    return null;
  }
  var isParentOfMatch = (this.flags[opts.flagBitsUsed >> 3] >>> (opts.flagBitsUsed++ & 7)) & 1;
  if(depth === 0 || !isParentOfMatch) {
    if(opts.hashesUsed >= this.hashes.length) {
      return null;
    }
    var hash = this.hashes[opts.hashesUsed++];
    if(depth === 0 && isParentOfMatch) {
      opts.txs.push(hash);
    }
    return new Buffer(hash, 'hex');
  } else {
    var left = this._traverseMerkleTree(depth-1, pos*2, opts);
    var right = left;
    if(pos*2+1 < this._calcTreeWidth(depth-1)) {
      right = this._traverseMerkleTree(depth-1, pos*2+1, opts);
    }
    if (checkForTxs){
      return opts.txs;
    } else {
      return Hash.sha256sha256(new Buffer.concat([left, right]));
    };
  }
};

/** Calculates the width of a merkle tree at a given height.
 *  Modeled after Bitcoin Core merkleblock.h CalcTreeWidth()
 * @param {Number} - Height at which we want the tree width
 * @returns {Number} - Width of the tree at a given height
 * @private
 */
MerkleBlock.prototype._calcTreeWidth = function calcTreeWidth(height) {
  return (this.numTransactions + (1 << height) - 1) >> height;
};

/** Calculates the height of the merkle tree in this MerkleBlock
 * @param {Number} - Height at which we want the tree width
 * @returns {Number} - Height of the merkle tree in this MerkleBlock
 * @private
 */
MerkleBlock.prototype._calcTreeHeight = function calcTreeHeight() {
  var height = 0;
  while (this._calcTreeWidth(height) > 1) {
    height++;
  }
  return height;
};

/**
 * @param {Transaction|String} - Transaction or Transaction ID Hash
 * @returns {Boolean} - return true/false if this MerkleBlock has the TX or not
 * @private
 */
MerkleBlock.prototype.hasTransaction = function hasTransaction(tx) {
  $.checkArgument(!_.isUndefined(tx), 'tx cannot be undefined');
  $.checkArgument(tx instanceof Transaction || typeof tx === 'string',
      'Invalid tx given, tx must be a "string" or "Transaction"');

  var hash = tx;
  if(tx instanceof Transaction) {
    // We need to reverse the id hash for the lookup
    hash = BufferUtil.reverse(new Buffer(tx.id, 'hex')).toString('hex');
  }

  var txs = [];
  var height = this._calcTreeHeight();
  this._traverseMerkleTree(height, 0, { txs: txs });
  return txs.indexOf(hash) !== -1;
};

/**
 * @param {Buffer} - MerkleBlock data
 * @returns {Object} - An Object representing merkleblock data
 * @private
 */
MerkleBlock._fromBufferReader = function _fromBufferReader(br) {
  $.checkState(!br.finished(), 'No merkleblock data received');
  var info = {};
  info.header = BlockHeader.fromBufferReader(br);
  info.numTransactions = br.readUInt32LE();
  var numHashes = br.readVarintNum();
  info.hashes = [];
  for (var i = 0; i < numHashes; i++) {
    info.hashes.push(br.read(32).toString('hex'));
  }
  var numFlags = br.readVarintNum();
  info.flags = [];
  for (i = 0; i < numFlags; i++) {
    info.flags.push(br.readUInt8());
  }
  return info;
};

/**
 * @param {Object} - A plain JavaScript object
 * @returns {Block} - An instance of block
 */
MerkleBlock.fromObject = function fromObject(obj) {
  return new MerkleBlock(obj);
};

module.exports = MerkleBlock;
<commit_msg>fixed filteredTxsHash If there is only one hash the filter do not match any txs in the block and should return a void aray<commit_after>'use strict';

var _ = require('lodash');
var BlockHeader = require('./blockheader');
var BufferUtil = require('../util/buffer');
var BufferReader = require('../encoding/bufferreader');
var BufferWriter = require('../encoding/bufferwriter');
var Hash = require('../crypto/hash');
var JSUtil = require('../util/js');
var Transaction = require('../transaction');
var errors = require('../errors');
var $ = require('../util/preconditions');

/**
 * Instantiate a MerkleBlock from a Buffer, JSON object, or Object with
 * the properties of the Block
 *
 * @param {*} - A Buffer, JSON string, or Object representing a MerkleBlock
 * @returns {MerkleBlock}
 * @constructor
 */
function MerkleBlock(arg) {
  /* jshint maxstatements: 18 */

  if (!(this instanceof MerkleBlock)) {
    return new MerkleBlock(arg);
  }

  var info = {};
  if (BufferUtil.isBuffer(arg)) {
    info = MerkleBlock._fromBufferReader(BufferReader(arg));
  } else if (_.isObject(arg)) {
    var header;
    if(arg.header instanceof BlockHeader) {
      header = arg.header;
    } else {
      header = BlockHeader.fromObject(arg.header);
    }
    info = {
      /**
       * @name MerkleBlock#header
       * @type {BlockHeader}
       */
      header: header,
      /**
       * @name MerkleBlock#numTransactions
       * @type {Number}
       */
      numTransactions: arg.numTransactions,
      /**
       * @name MerkleBlock#hashes
       * @type {String[]}
       */
      hashes: arg.hashes,
      /**
       * @name MerkleBlock#flags
       * @type {Number[]}
       */
      flags: arg.flags
    };
  } else {
    throw new TypeError('Unrecognized argument for MerkleBlock');
  }
  _.extend(this,info);
  this._flagBitsUsed = 0;
  this._hashesUsed = 0;

  return this;
}

/**
 * @param {Buffer} - MerkleBlock data in a Buffer object
 * @returns {MerkleBlock} - A MerkleBlock object
 */
MerkleBlock.fromBuffer = function fromBuffer(buf) {
  return MerkleBlock.fromBufferReader(BufferReader(buf));
};

/**
 * @param {BufferReader} - MerkleBlock data in a BufferReader object
 * @returns {MerkleBlock} - A MerkleBlock object
 */
MerkleBlock.fromBufferReader = function fromBufferReader(br) {
  return new MerkleBlock(MerkleBlock._fromBufferReader(br));
};

/**
 * @returns {Buffer} - A buffer of the block
 */
MerkleBlock.prototype.toBuffer = function toBuffer() {
  return this.toBufferWriter().concat();
};

/**
 * @param {BufferWriter} - An existing instance of BufferWriter
 * @returns {BufferWriter} - An instance of BufferWriter representation of the MerkleBlock
 */
MerkleBlock.prototype.toBufferWriter = function toBufferWriter(bw) {
  if (!bw) {
    bw = new BufferWriter();
  }
  bw.write(this.header.toBuffer());
  bw.writeUInt32LE(this.numTransactions);
  bw.writeVarintNum(this.hashes.length);
  for (var i = 0; i < this.hashes.length; i++) {
    bw.write(new Buffer(this.hashes[i], 'hex'));
  }
  bw.writeVarintNum(this.flags.length);
  for (i = 0; i < this.flags.length; i++) {
    bw.writeUInt8(this.flags[i]);
  }
  return bw;
};

/**
 * @returns {Object} - A plain object with the MerkleBlock properties
 */
MerkleBlock.prototype.toObject = MerkleBlock.prototype.toJSON = function toObject() {
  return {
    header: this.header.toObject(),
    numTransactions: this.numTransactions,
    hashes: this.hashes,
    flags: this.flags
  };
};

/**
 * Verify that the MerkleBlock is valid
 * @returns {Boolean} - True/False whether this MerkleBlock is Valid
 */
MerkleBlock.prototype.validMerkleTree = function validMerkleTree() {
  $.checkState(_.isArray(this.flags), 'MerkleBlock flags is not an array');
  $.checkState(_.isArray(this.hashes), 'MerkleBlock hashes is not an array');

  // Can't have more hashes than numTransactions
  if(this.hashes.length > this.numTransactions) {
    return false;
  }

  // Can't have more flag bits than num hashes
  if(this.flags.length * 8 < this.hashes.length) {
    return false;
  }

  var height = this._calcTreeHeight();
  var opts = { hashesUsed: 0, flagBitsUsed: 0 };
  var root = this._traverseMerkleTree(height, 0, opts);
  if(opts.hashesUsed !== this.hashes.length) {
    return false;
  }
  return BufferUtil.equals(root, this.header.merkleRoot);
};

/**
 * Return a list of all the txs hash that match the filter
 * @returns {Array} - txs hash that match the filter
 */
MerkleBlock.prototype.filterdTxsHash = function filterdTxsHash() {
  $.checkState(_.isArray(this.flags), 'MerkleBlock flags is not an array');
  $.checkState(_.isArray(this.hashes), 'MerkleBlock hashes is not an array');

  // Can't have more hashes than numTransactions
  if(this.hashes.length > this.numTransactions) {
    throw new errors.MerkleBlock.InvalidMerkleTree();
  }

  // Can't have more flag bits than num hashes
  if(this.flags.length * 8 < this.hashes.length) {
    throw new errors.MerkleBlock.InvalidMerkleTree();
  }

  // If there is only one hash the filter do not match any txs in the block
  if(this.hashes.length === 1) {
    return [];
  };

  var height = this._calcTreeHeight();
  var opts = { hashesUsed: 0, flagBitsUsed: 0 };
  var txs = this._traverseMerkleTree(height, 0, opts, true);
  if(opts.hashesUsed !== this.hashes.length) {
    throw new errors.MerkleBlock.InvalidMerkleTree();
  }
  return txs;
};

/**
 * Traverse a the tree in this MerkleBlock, validating it along the way
 * Modeled after Bitcoin Core merkleblock.cpp TraverseAndExtract()
 * @param {Number} - depth - Current height
 * @param {Number} - pos - Current position in the tree
 * @param {Object} - opts - Object with values that need to be mutated throughout the traversal
 * @param {Boolean} - checkForTxs - if true return opts.txs else return the Merkle Hash
 * @param {Number} - opts.flagBitsUsed - Number of flag bits used, should start at 0
 * @param {Number} - opts.hashesUsed - Number of hashes used, should start at 0
 * @param {Array} - opts.txs - Will finish populated by transactions found during traversal that match the filter
 * @returns {Buffer|null} - Buffer containing the Merkle Hash for that height
 * @returns {Array} - transactions found during traversal that match the filter
 * @private
 */
MerkleBlock.prototype._traverseMerkleTree = function traverseMerkleTree(depth, pos, opts, checkForTxs) {
  /* jshint maxcomplexity:  12*/
  /* jshint maxstatements: 20 */

  opts = opts || {};
  opts.txs = opts.txs || [];
  opts.flagBitsUsed = opts.flagBitsUsed || 0;
  opts.hashesUsed = opts.hashesUsed || 0;
  var checkForTxs = checkForTxs || false;

  if(opts.flagBitsUsed > this.flags.length * 8) {
    return null;
  }
  var isParentOfMatch = (this.flags[opts.flagBitsUsed >> 3] >>> (opts.flagBitsUsed++ & 7)) & 1;
  if(depth === 0 || !isParentOfMatch) {
    if(opts.hashesUsed >= this.hashes.length) {
      return null;
    }
    var hash = this.hashes[opts.hashesUsed++];
    if(depth === 0 && isParentOfMatch) {
      opts.txs.push(hash);
    }
    return new Buffer(hash, 'hex');
  } else {
    var left = this._traverseMerkleTree(depth-1, pos*2, opts);
    var right = left;
    if(pos*2+1 < this._calcTreeWidth(depth-1)) {
      right = this._traverseMerkleTree(depth-1, pos*2+1, opts);
    }
    if (checkForTxs){
      return opts.txs;
    } else {
      return Hash.sha256sha256(new Buffer.concat([left, right]));
    };
  }
};

/** Calculates the width of a merkle tree at a given height.
 *  Modeled after Bitcoin Core merkleblock.h CalcTreeWidth()
 * @param {Number} - Height at which we want the tree width
 * @returns {Number} - Width of the tree at a given height
 * @private
 */
MerkleBlock.prototype._calcTreeWidth = function calcTreeWidth(height) {
  return (this.numTransactions + (1 << height) - 1) >> height;
};

/** Calculates the height of the merkle tree in this MerkleBlock
 * @param {Number} - Height at which we want the tree width
 * @returns {Number} - Height of the merkle tree in this MerkleBlock
 * @private
 */
MerkleBlock.prototype._calcTreeHeight = function calcTreeHeight() {
  var height = 0;
  while (this._calcTreeWidth(height) > 1) {
    height++;
  }
  return height;
};

/**
 * @param {Transaction|String} - Transaction or Transaction ID Hash
 * @returns {Boolean} - return true/false if this MerkleBlock has the TX or not
 * @private
 */
MerkleBlock.prototype.hasTransaction = function hasTransaction(tx) {
  $.checkArgument(!_.isUndefined(tx), 'tx cannot be undefined');
  $.checkArgument(tx instanceof Transaction || typeof tx === 'string',
      'Invalid tx given, tx must be a "string" or "Transaction"');

  var hash = tx;
  if(tx instanceof Transaction) {
    // We need to reverse the id hash for the lookup
    hash = BufferUtil.reverse(new Buffer(tx.id, 'hex')).toString('hex');
  }

  var txs = [];
  var height = this._calcTreeHeight();
  this._traverseMerkleTree(height, 0, { txs: txs });
  return txs.indexOf(hash) !== -1;
};

/**
 * @param {Buffer} - MerkleBlock data
 * @returns {Object} - An Object representing merkleblock data
 * @private
 */
MerkleBlock._fromBufferReader = function _fromBufferReader(br) {
  $.checkState(!br.finished(), 'No merkleblock data received');
  var info = {};
  info.header = BlockHeader.fromBufferReader(br);
  info.numTransactions = br.readUInt32LE();
  var numHashes = br.readVarintNum();
  info.hashes = [];
  for (var i = 0; i < numHashes; i++) {
    info.hashes.push(br.read(32).toString('hex'));
  }
  var numFlags = br.readVarintNum();
  info.flags = [];
  for (i = 0; i < numFlags; i++) {
    info.flags.push(br.readUInt8());
  }
  return info;
};

/**
 * @param {Object} - A plain JavaScript object
 * @returns {Block} - An instance of block
 */
MerkleBlock.fromObject = function fromObject(obj) {
  return new MerkleBlock(obj);
};

module.exports = MerkleBlock;
<|endoftext|>