<commit_before>// Inspiration => https://github.com/HackedByChinese/ng-idle

(function(root) {

  'use strict';

  var availity = root.availity;

  availity.core.constant('AV_IDLE', {
    EVENTS: {
      INACTIVE: 'av:idle:inactive',
      ACTIVE: 'av:idle:active',
      SESSION_TIMEOUT_ACTIVE: 'av:idle:session:timeout:active',
      SESSION_TIMEOUT_INACTIVE: 'av:idle:session:inactive',
      HUMAN: 'keydown mousemove DOMMouseScroll mousewheel mousedown scroll',
      MACHINE: '$locationChangeSuccess'
    },
    INTERVALS: {
      PING: 3 * 60 * 1000, // 3 minutes
      IDLE: 25 * 60 * 1000, // 25 minutes
      SESSION: 30 * 60 * 1000 // 30 minutes
      // PING: 2000
      // PING: 3 * 60 * 1000, // 3 minutes
      // IDLE: 5000
      // IDLE: 25 * 60 * 1000, // 25 minutes
      // SESSION: 10000
      // SESSION: 30 * 60 * 1000 // 30 minutes
    },
    URLS: {
      HOME: '/availity/web/public.elegant.login',
      PING: '/api/v1/users/me'
    }
  });

  // Rules:
  //
  //  * ping after 3 minutes from last human activity
  //  * reset session after api success except 401
  //  * idle show after 25 of inactivity
  //
  availity.core.provider('avIdle', function() {

    var enabled = true;
    var pingUrl;

    this.enable = function(value) {
      if(arguments.length) {
        enabled = !!value;
      }
      return enabled;
    };

    this.setPingUrl = function(url) {
      pingUrl = url;
    };

    this.$get = function(AV_IDLE, $log, $document, $rootScope, $timeout, avThrottle, $q, $injector) {

      var AvIdle = function() {

        pingUrl = pingUrl || AV_IDLE.URLS.PING;

        this._idleTimer = null;
        this._sessionTimer = null;
        this._pingTimer = null;
        this.idleActive = false;

        this.listeners = [];

        this.init();

      };

      var proto = AvIdle.prototype;

      proto.init = function() {

        if(!enabled) {
          this.onDisabled();
          return;
        }

        $rootScope.$on('$destroy', function() {
          proto.onDisabled();
        });

        this.onEnabled();
      };

      proto.onEnabled = function() {
        var self = this;

        var listener;

        $document.find('body').on(AV_IDLE.EVENTS.HUMAN, function(event) {
          self.onEvent(event);
        });

        listener = $rootScope.$on(AV_IDLE.EVENTS.MACHINE, function(event) {
          self.onEvent(event);
        });
        this.listeners.push(listener);

        listener = $rootScope.$on(AV_IDLE.EVENTS.INACTIVE, function() {
          self.idleTimerInActive();
        });
        this.listeners.push(listener);

        $rootScope.$on(AV_IDLE.EVENTS.SESSION_TIMEOUT_INACTIVE, function() {
          self.onSessionInactive();
        });

        this.idleTimer();
        this.sessionTimer();
      };

      proto.onDisabled = function() {

        $document.find('body').off(AV_IDLE.EVENTS.HUMAN);

        // turns off Angular event listeners => http://stackoverflow.com/a/14898795
        _.each(this.listeners, function(listener) {
          listener();
        });

        $timeout.cancel(this._sessionTimer);
        $timeout.cancel(this._idleTimer);
        this.cancelIdleTimer();
      };

      proto.cancelIdleTimer = function() {
        $timeout.cancel(this._idleTimer);
      };

      proto.isEnabled = function() {
        return enabled;
      };

      proto.enable = function(value) {
        if(arguments.length) {
          enabled = !!value;
        }

        return this;
      };

      proto.response = function(response) {
        this.sessionTimer();
        return response;
      };

      proto.responseError = function(response) {

        if(response.status !== 401) {
          this.sessionTimer();
        }

        return $q.reject(response);
      };

      proto.sessionTimer = function() {

        var self = this;
        $timeout.cancel(this._sessionTimer);

        var later = function() {
          self.onSessionActive();
        };

        this._sessionTimer = $timeout(later, AV_IDLE.INTERVALS.SESSION, false);

      };

      proto.idleTimer = function() {
        var self = this;
        $timeout.cancel(this._idleTimer);

        var later = function() {
          self.idleTimerActive();
        };

        this._idleTimer = $timeout(later, AV_IDLE.INTERVALS.IDLE, false);
      };

      proto.onSessionActive = function() {
        $log.info("idle session active");
        $rootScope.$broadcast(AV_IDLE.EVENTS.SESSION_TIMEOUT_ACTIVE);
      };

      proto.idleTimerActive = function() {
        $log.info("idle active");
        this.idleActive = true;
        $rootScope.$broadcast(AV_IDLE.EVENTS.ACTIVE);
      };

      proto.idleTimerInActive = function() {
        $log.info("idle inactive");
        this.idleActive = false;
      };

      proto.onSessionInactive = function() {
        $log.info("idle session inactive");
        this.onDisabled();
        document.location.href = AV_IDLE.URLS.HOME;
      };

      proto.ping = function() {

        if(this.idleActive) {
          this.unPing();
          return;
        }

        if(!this._send) {
          this._send = avThrottle(this.send, AV_IDLE.INTERVALS.PING, {context: this});
        }

        this._pingTimer = this._send();
      };

      proto.unPing = function() {
        if(this._pingTimer) {
          $timeout.cancel(this._pingTimer);
        }
      };

      proto.send = function() {
        $log.info('sending ping');
        var $http = $injector.get('$http');
        $http.get(pingUrl);
      };

      proto.onEvent = function(event) {
        // $log.info(event);
        this.idleTimer();
        this.ping();
      };

      return new AvIdle();

    };

  });

})(window);
<commit_msg>remove log<commit_after>// Inspiration => https://github.com/HackedByChinese/ng-idle

(function(root) {

  'use strict';

  var availity = root.availity;

  availity.core.constant('AV_IDLE', {
    EVENTS: {
      INACTIVE: 'av:idle:inactive',
      ACTIVE: 'av:idle:active',
      SESSION_TIMEOUT_ACTIVE: 'av:idle:session:timeout:active',
      SESSION_TIMEOUT_INACTIVE: 'av:idle:session:inactive',
      HUMAN: 'keydown mousemove DOMMouseScroll mousewheel mousedown scroll',
      MACHINE: '$locationChangeSuccess'
    },
    INTERVALS: {
      PING: 3 * 60 * 1000, // 3 minutes
      IDLE: 25 * 60 * 1000, // 25 minutes
      SESSION: 30 * 60 * 1000 // 30 minutes
      // PING: 2000
      // PING: 3 * 60 * 1000, // 3 minutes
      // IDLE: 5000
      // IDLE: 25 * 60 * 1000, // 25 minutes
      // SESSION: 10000
      // SESSION: 30 * 60 * 1000 // 30 minutes
    },
    URLS: {
      HOME: '/availity/web/public.elegant.login',
      PING: '/api/v1/users/me'
    }
  });

  // Rules:
  //
  //  * ping after 3 minutes from last human activity
  //  * reset session after api success except 401
  //  * idle show after 25 of inactivity
  //
  availity.core.provider('avIdle', function() {

    var enabled = true;
    var pingUrl;

    this.enable = function(value) {
      if(arguments.length) {
        enabled = !!value;
      }
      return enabled;
    };

    this.setPingUrl = function(url) {
      pingUrl = url;
    };

    this.$get = function(AV_IDLE, $log, $document, $rootScope, $timeout, avThrottle, $q, $injector) {

      var AvIdle = function() {

        pingUrl = pingUrl || AV_IDLE.URLS.PING;

        this._idleTimer = null;
        this._sessionTimer = null;
        this._pingTimer = null;
        this.idleActive = false;

        this.listeners = [];

        this.init();

      };

      var proto = AvIdle.prototype;

      proto.init = function() {

        if(!enabled) {
          this.onDisabled();
          return;
        }

        $rootScope.$on('$destroy', function() {
          proto.onDisabled();
        });

        this.onEnabled();
      };

      proto.onEnabled = function() {
        var self = this;

        var listener;

        $document.find('body').on(AV_IDLE.EVENTS.HUMAN, function(event) {
          self.onEvent(event);
        });

        listener = $rootScope.$on(AV_IDLE.EVENTS.MACHINE, function(event) {
          self.onEvent(event);
        });
        this.listeners.push(listener);

        listener = $rootScope.$on(AV_IDLE.EVENTS.INACTIVE, function() {
          self.idleTimerInActive();
        });
        this.listeners.push(listener);

        $rootScope.$on(AV_IDLE.EVENTS.SESSION_TIMEOUT_INACTIVE, function() {
          self.onSessionInactive();
        });

        this.idleTimer();
        this.sessionTimer();
      };

      proto.onDisabled = function() {

        $document.find('body').off(AV_IDLE.EVENTS.HUMAN);

        // turns off Angular event listeners => http://stackoverflow.com/a/14898795
        _.each(this.listeners, function(listener) {
          listener();
        });

        $timeout.cancel(this._sessionTimer);
        $timeout.cancel(this._idleTimer);
        this.cancelIdleTimer();
      };

      proto.cancelIdleTimer = function() {
        $timeout.cancel(this._idleTimer);
      };

      proto.isEnabled = function() {
        return enabled;
      };

      proto.enable = function(value) {
        if(arguments.length) {
          enabled = !!value;
        }

        return this;
      };

      proto.response = function(response) {
        this.sessionTimer();
        return response;
      };

      proto.responseError = function(response) {

        if(response.status !== 401) {
          this.sessionTimer();
        }

        return $q.reject(response);
      };

      proto.sessionTimer = function() {

        var self = this;
        $timeout.cancel(this._sessionTimer);

        var later = function() {
          self.onSessionActive();
        };

        this._sessionTimer = $timeout(later, AV_IDLE.INTERVALS.SESSION, false);

      };

      proto.idleTimer = function() {
        var self = this;
        $timeout.cancel(this._idleTimer);

        var later = function() {
          self.idleTimerActive();
        };

        this._idleTimer = $timeout(later, AV_IDLE.INTERVALS.IDLE, false);
      };

      proto.onSessionActive = function() {
        $log.info("idle session active");
        $rootScope.$broadcast(AV_IDLE.EVENTS.SESSION_TIMEOUT_ACTIVE);
      };

      proto.idleTimerActive = function() {
        $log.info("idle active");
        this.idleActive = true;
        $rootScope.$broadcast(AV_IDLE.EVENTS.ACTIVE);
      };

      proto.idleTimerInActive = function() {
        $log.info("idle inactive");
        this.idleActive = false;
      };

      proto.onSessionInactive = function() {
        $log.info("idle session inactive");
        this.onDisabled();
        document.location.href = AV_IDLE.URLS.HOME;
      };

      proto.ping = function() {

        if(this.idleActive) {
          this.unPing();
          return;
        }

        if(!this._send) {
          this._send = avThrottle(this.send, AV_IDLE.INTERVALS.PING, {context: this});
        }

        this._pingTimer = this._send();
      };

      proto.unPing = function() {
        if(this._pingTimer) {
          $timeout.cancel(this._pingTimer);
        }
      };

      proto.send = function() {
        $log.info('sending ping');
        var $http = $injector.get('$http');
        $http.get(pingUrl);
      };

      proto.onEvent = function() {
        this.idleTimer();
        this.ping();
      };

      return new AvIdle();

    };

  });

})(window);
<|endoftext|>


<commit_before>// Much of this borrowed from https://github.com/sindresorhus/package-json/blob/master/index.js

const got = require('got');
const url = require('url');
const ini = require('ini');
const delay = require('delay');
const getRegistryUrl = require('registry-auth-token/registry-url');
const registryAuthToken = require('registry-auth-token');
const parse = require('github-url-from-git');
const { isBase64 } = require('validator');

module.exports = {
  setNpmrc,
  getDependency,
  resetMemCache,
  resetCache,
};

let map = new Map();

let memcache = {};

let npmrc = null;

function resetMemCache() {
  logger.debug('resetMemCache()');
  memcache = {};
}

function resetCache() {
  map = new Map();
  resetMemCache();
}

function maskToken(token) {
  // istanbul ignore if
  if (!token) {
    return token;
  }
  return `${token.substring(0, 2)}${new Array(token.length - 3).join(
    '*'
  )}${token.slice(-2)}`;
}

function setNpmrc(input, exposeEnv = false) {
  logger.debug('setNpmrc()');
  if (input) {
    npmrc = ini.parse(input);
    const tokenKey = '//registry.npmjs.org/:_authToken';
    // istanbul ignore if
    if (npmrc && npmrc[tokenKey]) {
      const token = npmrc[tokenKey];
      logger.debug(`Setting authToken to ${maskToken(token)}`);
    }
    // massage _auth to _authToken
    for (const [key, val] of Object.entries(npmrc)) {
      if (key.endsWith('_auth')) {
        logger.debug('Massaging _auth to _authToken');
        npmrc[key + 'Token'] = val;
        npmrc.massagedAuth = true;
        delete npmrc[key];
      }
    }
    if (!exposeEnv) {
      return;
    }
    for (const key in npmrc) {
      if (Object.prototype.hasOwnProperty.call(npmrc, key)) {
        npmrc[key] = envReplace(npmrc[key]);
      }
    }
  } else {
    logger.debug('Resetting npmrc');
    npmrc = null;
  }
}

function envReplace(value, env = process.env) {
  // istanbul ignore if
  if (typeof value !== 'string' || !value) {
    return value;
  }

  const ENV_EXPR = /(\\*)\$\{([^}]+)\}/g;

  return value.replace(ENV_EXPR, (match, esc, envVarName) => {
    if (env[envVarName] === undefined) {
      logger.warn('Failed to replace env in config: ' + match);
      throw new Error('env-replace');
    }
    return env[envVarName];
  });
}

async function getDependency(name, retries = 5) {
  logger.trace(`getDependency(${name})`);
  if (memcache[name]) {
    logger.trace('Returning cached result');
    return memcache[name];
  }
  const scope = name.split('/')[0];
  let regUrl;
  try {
    regUrl = getRegistryUrl(scope, npmrc);
  } catch (err) {
    regUrl = 'https://registry.npmjs.org';
  }
  const pkgUrl = url.resolve(
    regUrl,
    encodeURIComponent(name).replace(/^%40/, '@')
  );
  const authInfo = registryAuthToken(regUrl, { npmrc });
  const headers = {};

  if (authInfo && authInfo.type && authInfo.token) {
    // istanbul ignore if
    if (npmrc && npmrc.massagedAuth && isBase64(authInfo.token)) {
      logger.debug('Massaging authorization type to Basic');
      authInfo.type = 'Basic';
    }
    headers.authorization = `${authInfo.type} ${authInfo.token}`;
  } else if (process.env.NPM_TOKEN && process.env.NPM_TOKEN !== 'undefined') {
    headers.authorization = `Bearer ${process.env.NPM_TOKEN}`;
  }

  // Retrieve from API if not cached
  try {
    const res = (await got(pkgUrl, {
      cache: process.env.RENOVATE_SKIP_CACHE ? undefined : map,
      json: true,
      retries: 5,
      headers,
    })).body;
    if (!res.versions || !Object.keys(res.versions).length) {
      // Registry returned a 200 OK but with no versions
      if (retries <= 0) {
        logger.info({ name }, 'No versions returned');
        return null;
      }
      logger.info('No versions returned, retrying');
      await delay(5000 / retries);
      return getDependency(name, 0);
    }
    // Determine repository URL
    let repositoryUrl;
    if (res.repository) {
      repositoryUrl = parse(res.repository.url);
    }
    if (!repositoryUrl) {
      repositoryUrl = res.homepage;
    }
    // Simplify response before caching and returning
    const dep = {
      name: res.name,
      homepage: res.homepage,
      repositoryUrl,
      versions: res.versions,
      'dist-tags': res['dist-tags'],
      'renovate-config':
        res.versions[res['dist-tags'].latest]['renovate-config'],
    };
    Object.keys(dep.versions).forEach(version => {
      // We don't use any of the version payload currently
      dep.versions[version] = {
        // fall back to arbitrary time for old npm servers
        time: res.time ? res.time[version] : '2017-01-01T12:00:00.000Z',
      };
    });
    logger.trace({ dep }, 'dep');
    memcache[name] = dep;
    return dep;
  } catch (err) {
    if (err.statusCode === 401 || err.statusCode === 403) {
      logger.info(
        {
          pkgUrl,
          authInfoType: authInfo ? authInfo.type : undefined,
          authInfoToken: authInfo ? maskToken(authInfo.token) : undefined,
          err,
          statusCode: err.statusCode,
          name,
        },
        `Dependency lookup failure: unauthorized`
      );
      return null;
    }
    if (err.statusCode === 404) {
      logger.info({ name }, `Dependency lookup failure: not found`);
      logger.debug({ err });
      return null;
    }
    if (err.name === 'ParseError') {
      // Registry returned a 200 OK but got failed to parse it
      if (retries <= 0) {
        logger.warn({ err }, 'npm registry failure: ParseError');
        throw new Error('registry-failure');
      }
      logger.info({ err }, 'npm registry failure: ParseError, retrying');
      await delay(5000 / retries);
      return getDependency(name, retries - 1);
    }
    if (err.statusCode === 429) {
      // This is bad if it ever happens, so we should error
      logger.error({ err }, 'npm registry failure: too many requests');
      throw new Error('registry-failure');
    }
    if (err.statusCode === 408) {
      if (retries <= 0) {
        logger.warn({ err }, 'npm registry failure: timeout, retries=0');
        throw new Error('registry-failure');
      }
      logger.info({ err }, 'npm registry failure: timeout, retrying');
      await delay(5000 / retries);
      return getDependency(name, retries - 1);
    }
    if (err.statusCode >= 500 && err.statusCode < 600) {
      if (retries <= 0) {
        logger.warn({ err }, 'npm registry failure: internal error, retries=0');
        throw new Error('registry-failure');
      }
      logger.info({ err }, 'npm registry failure: internal error, retrying');
      await delay(5000 / retries);
      return getDependency(name, retries - 1);
    }
    logger.warn({ err, name }, 'npm registry failures: Unknown error');
    throw new Error('registry-failure');
  }
}
<commit_msg>fix(npm): don’t warn for ENOTFOUND lookup<commit_after>// Much of this borrowed from https://github.com/sindresorhus/package-json/blob/master/index.js

const got = require('got');
const url = require('url');
const ini = require('ini');
const delay = require('delay');
const getRegistryUrl = require('registry-auth-token/registry-url');
const registryAuthToken = require('registry-auth-token');
const parse = require('github-url-from-git');
const { isBase64 } = require('validator');

module.exports = {
  setNpmrc,
  getDependency,
  resetMemCache,
  resetCache,
};

let map = new Map();

let memcache = {};

let npmrc = null;

function resetMemCache() {
  logger.debug('resetMemCache()');
  memcache = {};
}

function resetCache() {
  map = new Map();
  resetMemCache();
}

function maskToken(token) {
  // istanbul ignore if
  if (!token) {
    return token;
  }
  return `${token.substring(0, 2)}${new Array(token.length - 3).join(
    '*'
  )}${token.slice(-2)}`;
}

function setNpmrc(input, exposeEnv = false) {
  logger.debug('setNpmrc()');
  if (input) {
    npmrc = ini.parse(input);
    const tokenKey = '//registry.npmjs.org/:_authToken';
    // istanbul ignore if
    if (npmrc && npmrc[tokenKey]) {
      const token = npmrc[tokenKey];
      logger.debug(`Setting authToken to ${maskToken(token)}`);
    }
    // massage _auth to _authToken
    for (const [key, val] of Object.entries(npmrc)) {
      if (key.endsWith('_auth')) {
        logger.debug('Massaging _auth to _authToken');
        npmrc[key + 'Token'] = val;
        npmrc.massagedAuth = true;
        delete npmrc[key];
      }
    }
    if (!exposeEnv) {
      return;
    }
    for (const key in npmrc) {
      if (Object.prototype.hasOwnProperty.call(npmrc, key)) {
        npmrc[key] = envReplace(npmrc[key]);
      }
    }
  } else {
    logger.debug('Resetting npmrc');
    npmrc = null;
  }
}

function envReplace(value, env = process.env) {
  // istanbul ignore if
  if (typeof value !== 'string' || !value) {
    return value;
  }

  const ENV_EXPR = /(\\*)\$\{([^}]+)\}/g;

  return value.replace(ENV_EXPR, (match, esc, envVarName) => {
    if (env[envVarName] === undefined) {
      logger.warn('Failed to replace env in config: ' + match);
      throw new Error('env-replace');
    }
    return env[envVarName];
  });
}

async function getDependency(name, retries = 5) {
  logger.trace(`getDependency(${name})`);
  if (memcache[name]) {
    logger.trace('Returning cached result');
    return memcache[name];
  }
  const scope = name.split('/')[0];
  let regUrl;
  try {
    regUrl = getRegistryUrl(scope, npmrc);
  } catch (err) {
    regUrl = 'https://registry.npmjs.org';
  }
  const pkgUrl = url.resolve(
    regUrl,
    encodeURIComponent(name).replace(/^%40/, '@')
  );
  const authInfo = registryAuthToken(regUrl, { npmrc });
  const headers = {};

  if (authInfo && authInfo.type && authInfo.token) {
    // istanbul ignore if
    if (npmrc && npmrc.massagedAuth && isBase64(authInfo.token)) {
      logger.debug('Massaging authorization type to Basic');
      authInfo.type = 'Basic';
    }
    headers.authorization = `${authInfo.type} ${authInfo.token}`;
  } else if (process.env.NPM_TOKEN && process.env.NPM_TOKEN !== 'undefined') {
    headers.authorization = `Bearer ${process.env.NPM_TOKEN}`;
  }

  // Retrieve from API if not cached
  try {
    const res = (await got(pkgUrl, {
      cache: process.env.RENOVATE_SKIP_CACHE ? undefined : map,
      json: true,
      retries: 5,
      headers,
    })).body;
    if (!res.versions || !Object.keys(res.versions).length) {
      // Registry returned a 200 OK but with no versions
      if (retries <= 0) {
        logger.info({ name }, 'No versions returned');
        return null;
      }
      logger.info('No versions returned, retrying');
      await delay(5000 / retries);
      return getDependency(name, 0);
    }
    // Determine repository URL
    let repositoryUrl;
    if (res.repository) {
      repositoryUrl = parse(res.repository.url);
    }
    if (!repositoryUrl) {
      repositoryUrl = res.homepage;
    }
    // Simplify response before caching and returning
    const dep = {
      name: res.name,
      homepage: res.homepage,
      repositoryUrl,
      versions: res.versions,
      'dist-tags': res['dist-tags'],
      'renovate-config':
        res.versions[res['dist-tags'].latest]['renovate-config'],
    };
    Object.keys(dep.versions).forEach(version => {
      // We don't use any of the version payload currently
      dep.versions[version] = {
        // fall back to arbitrary time for old npm servers
        time: res.time ? res.time[version] : '2017-01-01T12:00:00.000Z',
      };
    });
    logger.trace({ dep }, 'dep');
    memcache[name] = dep;
    return dep;
  } catch (err) {
    if (err.statusCode === 401 || err.statusCode === 403) {
      logger.info(
        {
          pkgUrl,
          authInfoType: authInfo ? authInfo.type : undefined,
          authInfoToken: authInfo ? maskToken(authInfo.token) : undefined,
          err,
          statusCode: err.statusCode,
          name,
        },
        `Dependency lookup failure: unauthorized`
      );
      return null;
    }
    if (err.statusCode === 404 || err.code === 'ENOTFOUND') {
      logger.info({ name }, `Dependency lookup failure: not found`);
      logger.debug({ err });
      return null;
    }
    if (err.name === 'ParseError') {
      // Registry returned a 200 OK but got failed to parse it
      if (retries <= 0) {
        logger.warn({ err }, 'npm registry failure: ParseError');
        throw new Error('registry-failure');
      }
      logger.info({ err }, 'npm registry failure: ParseError, retrying');
      await delay(5000 / retries);
      return getDependency(name, retries - 1);
    }
    if (err.statusCode === 429) {
      // This is bad if it ever happens, so we should error
      logger.error({ err }, 'npm registry failure: too many requests');
      throw new Error('registry-failure');
    }
    if (err.statusCode === 408) {
      if (retries <= 0) {
        logger.warn({ err }, 'npm registry failure: timeout, retries=0');
        throw new Error('registry-failure');
      }
      logger.info({ err }, 'npm registry failure: timeout, retrying');
      await delay(5000 / retries);
      return getDependency(name, retries - 1);
    }
    if (err.statusCode >= 500 && err.statusCode < 600) {
      if (retries <= 0) {
        logger.warn({ err }, 'npm registry failure: internal error, retries=0');
        throw new Error('registry-failure');
      }
      logger.info({ err }, 'npm registry failure: internal error, retrying');
      await delay(5000 / retries);
      return getDependency(name, retries - 1);
    }
    logger.warn({ err, name }, 'npm registry failure: Unknown error');
    throw new Error('registry-failure');
  }
}
<|endoftext|>


<commit_before>require('es6-shim');

var assert = require('assert');
var events = require('events');
var path = require('path');
var util = require('util');
var tlv = require('tlv');
var TLV = tlv.TLV;
var PluginLoader = require('plugin-loader').PluginLoader;
var DeviceInfo = require('./device_info').DeviceInfo;
var Datastore = require('nedb');

var DeviceType = {
  TEMPERATURE_SENSOR: 0x0001,
  MOISTURE_SENSOR: 0x0002,
  VELOCITY_SENSOR: 0x0003,
};

var CommandType = {
  GET_DEVICE_INFO: 0xA0,
  CONFIGURE: 0xA1,
  GET_DATA: 0xA2,
  PERFORM: 0xA3,
  SUBSCRIBE: 0xA4,
  UNSUBSCRIBE: 0xA5,
};

exports.Device = function(connector, protocolInfo, subdeviceID) {
  this.connector = connector;
  this.protocolInfo = protocolInfo;
  this.physicalID = this.connector + "|" + this.protocolInfo.id;
  this.subdeviceID = subdeviceID;

  this.id = this.subdeviceID ? (this.physicalID + "#" + this.subdeviceID) : this.physicalID;
  this.deviceInfo = null;
  this.paired = false;
}

exports.DeviceCommand = function(device, type, parameters, callback) {
  this.device = device;
  this.type = type;
  this.parameters = parameters;
  this.callback = callback;
}

exports.DeviceManager = function(config) {
  this._connectorLoader = new PluginLoader(config.connectorDirectories);
  this._config = config;
  this._pairedDevices = new Datastore({ filename: path.join(config.persistencyPath, 'devices.nedb') });
  this._unpairedDevices = [];
  this._subscriptions = new Map();
}

util.inherits(exports.DeviceManager, events.EventEmitter);

exports.DeviceType = DeviceType;
exports.CommandType = CommandType;
exports.DeviceManager.prototype.DeviceType = DeviceType;
exports.DeviceManager.prototype.CommandType = CommandType;

exports.DeviceManager.prototype.start = function(done) {
  this._pairedDevices.loadDatabase(function() {
    this._connectorLoader.on('pluginLoaded', initializeConnector.bind(this))
    this._connectorLoader.on('pluginUnloaded', destroyConnector.bind(this))
    this._connectorLoader.startMonitoring();

    this._pairedDevices.ensureIndex({ fieldName: 'id', unique: true });
    this._pairedDevices.ensureIndex({ fieldName: 'type' });

    done();
  }.bind(this));
}

exports.DeviceManager.prototype.pair = function(device, pairingData, cb) {
  this._connectorLoader.loadedModules[device.connector].pair(device, pairingData, handlePairedDevice.bind(this, cb));
}

exports.DeviceManager.prototype.unpair = function(device, cb) {
  this._connectorLoader.loadedModules[device.connector].unpair(device, handleUnpairedDevice.bind(this, cb));
}

exports.DeviceManager.prototype.discoverDevices = function() {
  this._unpairedDevices.length = 0;

  for (var connectorName in this._connectorLoader.loadedModules) {
    this._connectorLoader.loadedModules[connectorName].discoverDevices();
  }
}

exports.DeviceManager.prototype.getPairedDevices = function(cb) {
  this._pairedDevices.find({}, function (err, devices) {
    cb(devices);
  });
}

exports.DeviceManager.prototype.getUnpairedDevices = function(cb) {
  cb(this._unpairedDevices.filter(function(device) { return device.deviceInfo != null; }));
}

exports.DeviceManager.prototype.getDevicesByType = function(types, cb) {
  if (!types || types.length == 0) {
    this.getPairedDevices(cb);
    return;
  } else if (!util.isArray(types)) {
    types = [ types ];
  }

  this._pairedDevices.find({ 'deviceInfo.type': { $in: types }}, function (err, foundDevices) {
    cb(foundDevices);
  });
}

exports.DeviceManager.prototype.getDevicesConnector = function(connector, cb) {
  this._pairedDevices.find({ connector: connector }, function (err, foundDevices) {
    cb(foundDevices);
  });
}

exports.DeviceManager.prototype.getDeviceByID = function(id, cb) {
  this._pairedDevices.findOne({ id: id }, function (err, device) {
    cb(device);
  });
}

exports.DeviceManager.prototype.getUnpairedDeviceByID = function(id, cb) {
  var device = this._unpairedDevices.find(function(device) {
    return device.id == id;
  });

  cb(device);
}

exports.DeviceManager.prototype.getDeviceInfo = function(device, cb) {
  this.sendCommands([ this.createGetDeviceInfoCommand(device, handleGetInfoResponse.bind(this)) ]);
}

exports.DeviceManager.prototype.createGetDeviceInfoCommand = function(device, cb) {
  return new exports.DeviceCommand(device, CommandType.GET_DEVICE_INFO, [], cb);
}

exports.DeviceManager.prototype.configureDevice = function(device, options, cb) {
  this.sendCommands([ this.createConfigureCommand(device, options, cb) ]);
}

exports.DeviceManager.prototype.createConfigureCommand = function(device, options, cb) {
  var params = [ new TLV(0xA0, options) ];

  return new exports.DeviceCommand(device, CommandType.CONFIGURE, params, cb);
}

exports.DeviceManager.prototype.getData = function(device, options, cb) {
  this.sendCommands([ this.createGetDataCommand(device, options, cb) ]);
}

exports.DeviceManager.prototype.createGetDataCommand = function(device, options, cb) {
  if (options instanceof Function) {
    cb = options;
    options = [];
  }

  var params = [];

  if (options.length > 0) {
    params.push(new TLV(0xA0, options));
  }

  return new exports.DeviceCommand(device, CommandType.GET_DATA, params, cb);
}

exports.DeviceManager.prototype.perform = function(device, action, options, cb) {
  this.sendCommands([ this.createPerformCommand(device, action, options, cb) ]);
}

exports.DeviceManager.prototype.createPerformCommand = function(device, action, options, cb) {
  if (options instanceof Function) {
    cb = options;
    options = [];
  }

  var params = [ new TLV(0x81, new Buffer([action])) ];

  if (options.length > 0) {
    params.push(new TLV(0xA0, options));
  }

  return new exports.DeviceCommand(device, CommandType.PERFORM, params, cb);
}

exports.DeviceManager.prototype.subscribe = function(device, notificationIDs, cb, errorCallback) {
  var subscribedNotificationsForDevice = this._subscriptions.get(device);
  var stillUnsubscribedNotifications = [];

  if (!subscribedNotificationsForDevice) {
    subscribedNotificationsForDevice = new Map();
    this._subscriptions.set(device, subscribedNotificationsForDevice);
  }

  for (var i = 0, len = notificationsIDs.length; i < len; i++) {
    var subscribers = subscribedNotificationsForDevice.get(notificationsIDs[i]);

    if (!subscribers) {
      subscribers = [];
      subscribedNotificationsForDevice.set(notificationsIDs[i], subscribers);
    }

    if (subscribers.length == 0) {
      stillUnsubscribedNotifications.push(notificationsIDs[i]);
    }

    if (subscribers.indexOf(cb) == -1) {
      subscribers.push(cb);
    }
  }

  if (stillUnsubscribedNotifications.length > 0) {
    this.sendCommands([ createSubscribeCommand(device, stillUnsubscribedNotifications, handleSubscribtionResponse.bind(this, errorCallback)) ])
  }
}

exports.DeviceManager.prototype.createSubscribeCommand = function(device, notificationIDs, cb) {
  var params = [ new TLV(0x81, tlv.encodeTags(notificationIDs)) ];
  return new exports.DeviceCommand(device, CommandType.SUBSCRIBE, params, cb);
}

exports.DeviceManager.prototype.unsubscribe = function(device, notificationIDs, cb, errorCallback) {
  var subscribedNotificationsForDevice = this._subscriptions.get(device);
  var notificationsToUnsubscribe = [];

  if (!subscribedNotificationsForDevice) {
    return;
  }

  for (var i = 0, len = notificationsIDs.length; i < len; i++) {
    var subscribers = subscribedNotificationsForDevice.get(notificationsIDs[i]);

    if (subscribers) {
      var idx = subscribers.indexOf(cb);

      if (idx != -1) {
        subscribers.splice(idx, 1);
      }

      if (subscribers.length == 0) {
        notificationsToUnsubscribe.push(notificationsIDs[i]);
      }
    }
  }

  if (notificationsToUnsubscribe.length > 0) {
    this.sendCommands([ createUnsubscribeCommand(device, notificationsToUnsubscribe, handleSubscribtionResponse.bind(this, errorCallback)) ])
  }
}

exports.DeviceManager.prototype.createUnsubscribeCommand = function(device, notificationIDs, cb) {
  var params = [ new TLV(0x81, tlv.encodeTags(notificationIDs)) ];
  return new exports.DeviceCommand(device, CommandType.UNSUBSCRIBE, params, cb);
}

exports.DeviceManager.prototype.sendCommands = function(commands) {
  var groupedCommands = groupCommandsByPhysicalDevice(commands);

  for (var physicalDeviceID in groupedCommands) {
    var commands = groupedCommands[physicalDeviceID];
    this.sendPacket(commands[0].device, commands);
  }
}

exports.DeviceManager.prototype.sendPacket = function(physicalDevice, commands) {
  this._connectorLoader.loadedModules[physicalDevice.connector].send(physicalDevice, this.createPacket(commands).encode(), processResponse.bind(this, commands));
}

exports.DeviceManager.prototype.createPacket = function(commands) {
  var packet = new TLV(0xE0, []);

  for (var i = 0, len = commands.length; i < len; i++) {
    var command = commands[i];
    var commandTLV = new TLV(command.type, []);

    if (command.device.subdeviceID !== undefined) {
      commandTLV.value.push(new TLV(0x80, new Buffer([command.device.subdeviceID])));
    }

    Array.prototype.push.apply(commandTLV.value, command.parameters);
    packet.value.push(commandTLV);
  };

  return packet;
}

function processResponse(commands, response) {
  var responseTLV = tlv.parse(response);

  assert.equal(commands.length, responseTLV.value.length);

  for(var i = 0, len = commands.length; i < len; i++) {
    commands[i].callback(commands[i], responseTLV.value[i]);
  }
}

function handleGetInfoResponse(command, responseTLV) {
  var error = responseTLV.getFirstChild(0x9E);

  if (error) {
    //do something, maybe repeat command?
  }

  var device = command.device;
  device.deviceInfo = new DeviceInfo(responseTLV);

  this.emit('deviceDiscovered', command.device);

  if (device.deviceInfo.subdeviceCount) {
    var getInfoCommands = [];

    for(var i = 0; i < device.deviceInfo.subdeviceCount; i++) {
      var child = new exports.Device(device.connector, device.protocolInfo, i);
      this._unpairedDevices.push(child);

      getInfoCommands.push(this.createGetDeviceInfoCommand(child, handleGetInfoResponse.bind(this, cb)));
    }

    this.sendCommands(getInfoCommands);
  }
}

function handleSubscribtionResponse(errorCallback, command, responseTLV) {
  var error = responseTLV.getFirstChild(0x9E);

  if (error && errorCallback) {
    errorCallback(error.getIntValue());
  }
}

function handlePairedDevice(cb, device, err) {
  if (err) {
    cb(device, err);
    return;
  }

  device.paired = true;

  var idx = this._unpairedDevices.indexOf(device);

  if (idx != -1) {
    this._unpairedDevices.splice(idx);
  }

  this._pairedDevices.insert(device, function(insertErr, insertedDevice) {
    if (cb) {
      cb(insertedDevice);
    }

    this.emit('devicePaired', insertedDevice);
  }.bind(this));
}

function handleUnpairedDevice(cb, device, err) {
  if (err) {
    cb(device, err);
    return;
  }

  device.paired = false;

  db.remove({ id: device.id }, {}, function (err, numRemoved) {
    this._unpairedDevices.push(device);

    if (cb) {
      cb(device);
    }

    this.emit('deviceUnpaired', device);
  }.bind(this));
}

function handleDiscoveredDevice(connectorName, deviceProtocolInfo) {
  var newDevice = new exports.Device(connectorName, deviceProtocolInfo);
  var newID = newDevice.id;

  this.getUnpairedDeviceByID(newID, function(device) {
    if(!device) {
      this._unpairedDevices.push(newDevice);
      this.getDeviceInfo(newDevice);
    }
  }.bind(this));
}

function groupCommandsByPhysicalDevice(commands) {
  var groupedCommands = {};
  for (var i = 0, len = commands.length; i < len; i++) {
    var command = commands[i];
    var mapEntry = groupedCommands[command.device.physicalID];

    if (!mapEntry) {
      mapEntry = [];
      groupedCommands[command.device.physicalID] = mapEntry;
    }

    mapEntry.push(command);
  };

  return groupedCommands;
}

function initializeConnector(connectorName, connector) {
  console.log('Loading connector: ' + connectorName);
  connector.on('deviceDiscovered', handleDiscoveredDevice.bind(this, connectorName));

  this.getDevicesConnector(connectorName, function(pairedDevices) {
    connector.start(pairedDevices);
  }.bind(this));
}

function destroyConnector(connectorName, connector) {
  connector.removeAllListeners();
  console.log('Unloading connector: ' + connectorName);
}

exports.groupCommandsByPhysicalDevice = groupCommandsByPhysicalDevice;<commit_msg>getDevicesConnector => getDevicesByConnector<commit_after>require('es6-shim');

var assert = require('assert');
var events = require('events');
var path = require('path');
var util = require('util');
var tlv = require('tlv');
var TLV = tlv.TLV;
var PluginLoader = require('plugin-loader').PluginLoader;
var DeviceInfo = require('./device_info').DeviceInfo;
var Datastore = require('nedb');

var DeviceType = {
  TEMPERATURE_SENSOR: 0x0001,
  MOISTURE_SENSOR: 0x0002,
  VELOCITY_SENSOR: 0x0003,
};

var CommandType = {
  GET_DEVICE_INFO: 0xA0,
  CONFIGURE: 0xA1,
  GET_DATA: 0xA2,
  PERFORM: 0xA3,
  SUBSCRIBE: 0xA4,
  UNSUBSCRIBE: 0xA5,
};

exports.Device = function(connector, protocolInfo, subdeviceID) {
  this.connector = connector;
  this.protocolInfo = protocolInfo;
  this.physicalID = this.connector + "|" + this.protocolInfo.id;
  this.subdeviceID = subdeviceID;

  this.id = this.subdeviceID ? (this.physicalID + "#" + this.subdeviceID) : this.physicalID;
  this.deviceInfo = null;
  this.paired = false;
}

exports.DeviceCommand = function(device, type, parameters, callback) {
  this.device = device;
  this.type = type;
  this.parameters = parameters;
  this.callback = callback;
}

exports.DeviceManager = function(config) {
  this._connectorLoader = new PluginLoader(config.connectorDirectories);
  this._config = config;
  this._pairedDevices = new Datastore({ filename: path.join(config.persistencyPath, 'devices.nedb') });
  this._unpairedDevices = [];
  this._subscriptions = new Map();
}

util.inherits(exports.DeviceManager, events.EventEmitter);

exports.DeviceType = DeviceType;
exports.CommandType = CommandType;
exports.DeviceManager.prototype.DeviceType = DeviceType;
exports.DeviceManager.prototype.CommandType = CommandType;

exports.DeviceManager.prototype.start = function(done) {
  this._pairedDevices.loadDatabase(function() {
    this._connectorLoader.on('pluginLoaded', initializeConnector.bind(this))
    this._connectorLoader.on('pluginUnloaded', destroyConnector.bind(this))
    this._connectorLoader.startMonitoring();

    this._pairedDevices.ensureIndex({ fieldName: 'id', unique: true });
    this._pairedDevices.ensureIndex({ fieldName: 'type' });

    done();
  }.bind(this));
}

exports.DeviceManager.prototype.pair = function(device, pairingData, cb) {
  this._connectorLoader.loadedModules[device.connector].pair(device, pairingData, handlePairedDevice.bind(this, cb));
}

exports.DeviceManager.prototype.unpair = function(device, cb) {
  this._connectorLoader.loadedModules[device.connector].unpair(device, handleUnpairedDevice.bind(this, cb));
}

exports.DeviceManager.prototype.discoverDevices = function() {
  this._unpairedDevices.length = 0;

  for (var connectorName in this._connectorLoader.loadedModules) {
    this._connectorLoader.loadedModules[connectorName].discoverDevices();
  }
}

exports.DeviceManager.prototype.getPairedDevices = function(cb) {
  this._pairedDevices.find({}, function (err, devices) {
    cb(devices);
  });
}

exports.DeviceManager.prototype.getUnpairedDevices = function(cb) {
  cb(this._unpairedDevices.filter(function(device) { return device.deviceInfo != null; }));
}

exports.DeviceManager.prototype.getDevicesByType = function(types, cb) {
  if (!types || types.length == 0) {
    this.getPairedDevices(cb);
    return;
  } else if (!util.isArray(types)) {
    types = [ types ];
  }

  this._pairedDevices.find({ 'deviceInfo.type': { $in: types }}, function (err, foundDevices) {
    cb(foundDevices);
  });
}

exports.DeviceManager.prototype.getDevicesByConnector = function(connector, cb) {
  this._pairedDevices.find({ connector: connector }, function (err, foundDevices) {
    cb(foundDevices);
  });
}

exports.DeviceManager.prototype.getDeviceByID = function(id, cb) {
  this._pairedDevices.findOne({ id: id }, function (err, device) {
    cb(device);
  });
}

exports.DeviceManager.prototype.getUnpairedDeviceByID = function(id, cb) {
  var device = this._unpairedDevices.find(function(device) {
    return device.id == id;
  });

  cb(device);
}

exports.DeviceManager.prototype.getDeviceInfo = function(device, cb) {
  this.sendCommands([ this.createGetDeviceInfoCommand(device, handleGetInfoResponse.bind(this)) ]);
}

exports.DeviceManager.prototype.createGetDeviceInfoCommand = function(device, cb) {
  return new exports.DeviceCommand(device, CommandType.GET_DEVICE_INFO, [], cb);
}

exports.DeviceManager.prototype.configureDevice = function(device, options, cb) {
  this.sendCommands([ this.createConfigureCommand(device, options, cb) ]);
}

exports.DeviceManager.prototype.createConfigureCommand = function(device, options, cb) {
  var params = [ new TLV(0xA0, options) ];

  return new exports.DeviceCommand(device, CommandType.CONFIGURE, params, cb);
}

exports.DeviceManager.prototype.getData = function(device, options, cb) {
  this.sendCommands([ this.createGetDataCommand(device, options, cb) ]);
}

exports.DeviceManager.prototype.createGetDataCommand = function(device, options, cb) {
  if (options instanceof Function) {
    cb = options;
    options = [];
  }

  var params = [];

  if (options.length > 0) {
    params.push(new TLV(0xA0, options));
  }

  return new exports.DeviceCommand(device, CommandType.GET_DATA, params, cb);
}

exports.DeviceManager.prototype.perform = function(device, action, options, cb) {
  this.sendCommands([ this.createPerformCommand(device, action, options, cb) ]);
}

exports.DeviceManager.prototype.createPerformCommand = function(device, action, options, cb) {
  if (options instanceof Function) {
    cb = options;
    options = [];
  }

  var params = [ new TLV(0x81, new Buffer([action])) ];

  if (options.length > 0) {
    params.push(new TLV(0xA0, options));
  }

  return new exports.DeviceCommand(device, CommandType.PERFORM, params, cb);
}

exports.DeviceManager.prototype.subscribe = function(device, notificationIDs, cb, errorCallback) {
  var subscribedNotificationsForDevice = this._subscriptions.get(device);
  var stillUnsubscribedNotifications = [];

  if (!subscribedNotificationsForDevice) {
    subscribedNotificationsForDevice = new Map();
    this._subscriptions.set(device, subscribedNotificationsForDevice);
  }

  for (var i = 0, len = notificationsIDs.length; i < len; i++) {
    var subscribers = subscribedNotificationsForDevice.get(notificationsIDs[i]);

    if (!subscribers) {
      subscribers = [];
      subscribedNotificationsForDevice.set(notificationsIDs[i], subscribers);
    }

    if (subscribers.length == 0) {
      stillUnsubscribedNotifications.push(notificationsIDs[i]);
    }

    if (subscribers.indexOf(cb) == -1) {
      subscribers.push(cb);
    }
  }

  if (stillUnsubscribedNotifications.length > 0) {
    this.sendCommands([ createSubscribeCommand(device, stillUnsubscribedNotifications, handleSubscribtionResponse.bind(this, errorCallback)) ])
  }
}

exports.DeviceManager.prototype.createSubscribeCommand = function(device, notificationIDs, cb) {
  var params = [ new TLV(0x81, tlv.encodeTags(notificationIDs)) ];
  return new exports.DeviceCommand(device, CommandType.SUBSCRIBE, params, cb);
}

exports.DeviceManager.prototype.unsubscribe = function(device, notificationIDs, cb, errorCallback) {
  var subscribedNotificationsForDevice = this._subscriptions.get(device);
  var notificationsToUnsubscribe = [];

  if (!subscribedNotificationsForDevice) {
    return;
  }

  for (var i = 0, len = notificationsIDs.length; i < len; i++) {
    var subscribers = subscribedNotificationsForDevice.get(notificationsIDs[i]);

    if (subscribers) {
      var idx = subscribers.indexOf(cb);

      if (idx != -1) {
        subscribers.splice(idx, 1);
      }

      if (subscribers.length == 0) {
        notificationsToUnsubscribe.push(notificationsIDs[i]);
      }
    }
  }

  if (notificationsToUnsubscribe.length > 0) {
    this.sendCommands([ createUnsubscribeCommand(device, notificationsToUnsubscribe, handleSubscribtionResponse.bind(this, errorCallback)) ])
  }
}

exports.DeviceManager.prototype.createUnsubscribeCommand = function(device, notificationIDs, cb) {
  var params = [ new TLV(0x81, tlv.encodeTags(notificationIDs)) ];
  return new exports.DeviceCommand(device, CommandType.UNSUBSCRIBE, params, cb);
}

exports.DeviceManager.prototype.sendCommands = function(commands) {
  var groupedCommands = groupCommandsByPhysicalDevice(commands);

  for (var physicalDeviceID in groupedCommands) {
    var commands = groupedCommands[physicalDeviceID];
    this.sendPacket(commands[0].device, commands);
  }
}

exports.DeviceManager.prototype.sendPacket = function(physicalDevice, commands) {
  this._connectorLoader.loadedModules[physicalDevice.connector].send(physicalDevice, this.createPacket(commands).encode(), processResponse.bind(this, commands));
}

exports.DeviceManager.prototype.createPacket = function(commands) {
  var packet = new TLV(0xE0, []);

  for (var i = 0, len = commands.length; i < len; i++) {
    var command = commands[i];
    var commandTLV = new TLV(command.type, []);

    if (command.device.subdeviceID !== undefined) {
      commandTLV.value.push(new TLV(0x80, new Buffer([command.device.subdeviceID])));
    }

    Array.prototype.push.apply(commandTLV.value, command.parameters);
    packet.value.push(commandTLV);
  };

  return packet;
}

function processResponse(commands, response) {
  var responseTLV = tlv.parse(response);

  assert.equal(commands.length, responseTLV.value.length);

  for(var i = 0, len = commands.length; i < len; i++) {
    commands[i].callback(commands[i], responseTLV.value[i]);
  }
}

function handleGetInfoResponse(command, responseTLV) {
  var error = responseTLV.getFirstChild(0x9E);

  if (error) {
    //do something, maybe repeat command?
  }

  var device = command.device;
  device.deviceInfo = new DeviceInfo(responseTLV);

  this.emit('deviceDiscovered', command.device);

  if (device.deviceInfo.subdeviceCount) {
    var getInfoCommands = [];

    for(var i = 0; i < device.deviceInfo.subdeviceCount; i++) {
      var child = new exports.Device(device.connector, device.protocolInfo, i);
      this._unpairedDevices.push(child);

      getInfoCommands.push(this.createGetDeviceInfoCommand(child, handleGetInfoResponse.bind(this, cb)));
    }

    this.sendCommands(getInfoCommands);
  }
}

function handleSubscribtionResponse(errorCallback, command, responseTLV) {
  var error = responseTLV.getFirstChild(0x9E);

  if (error && errorCallback) {
    errorCallback(error.getIntValue());
  }
}

function handlePairedDevice(cb, device, err) {
  if (err) {
    cb(device, err);
    return;
  }

  device.paired = true;

  var idx = this._unpairedDevices.indexOf(device);

  if (idx != -1) {
    this._unpairedDevices.splice(idx);
  }

  this._pairedDevices.insert(device, function(insertErr, insertedDevice) {
    if (cb) {
      cb(insertedDevice);
    }

    this.emit('devicePaired', insertedDevice);
  }.bind(this));
}

function handleUnpairedDevice(cb, device, err) {
  if (err) {
    cb(device, err);
    return;
  }

  device.paired = false;

  db.remove({ id: device.id }, {}, function (err, numRemoved) {
    this._unpairedDevices.push(device);

    if (cb) {
      cb(device);
    }

    this.emit('deviceUnpaired', device);
  }.bind(this));
}

function handleDiscoveredDevice(connectorName, deviceProtocolInfo) {
  var newDevice = new exports.Device(connectorName, deviceProtocolInfo);
  var newID = newDevice.id;

  this.getUnpairedDeviceByID(newID, function(device) {
    if(!device) {
      this._unpairedDevices.push(newDevice);
      this.getDeviceInfo(newDevice);
    }
  }.bind(this));
}

function groupCommandsByPhysicalDevice(commands) {
  var groupedCommands = {};
  for (var i = 0, len = commands.length; i < len; i++) {
    var command = commands[i];
    var mapEntry = groupedCommands[command.device.physicalID];

    if (!mapEntry) {
      mapEntry = [];
      groupedCommands[command.device.physicalID] = mapEntry;
    }

    mapEntry.push(command);
  };

  return groupedCommands;
}

function initializeConnector(connectorName, connector) {
  console.log('Loading connector: ' + connectorName);
  connector.on('deviceDiscovered', handleDiscoveredDevice.bind(this, connectorName));

  this.getDevicesByConnector(connectorName, function(pairedDevices) {
    connector.start(pairedDevices);
  }.bind(this));
}

function destroyConnector(connectorName, connector) {
  connector.removeAllListeners();
  console.log('Unloading connector: ' + connectorName);
}

exports.groupCommandsByPhysicalDevice = groupCommandsByPhysicalDevice;<|endoftext|>


<commit_before>/*
 The MIT License (MIT)

 Copyright ( c ) 2014-2015 Teem2 LLC

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

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

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

Dreem file parser and dependency resolver.
*/
define(function(require, exports, module) {
  var dreemParser = exports;

  var dreemMaker = require('./dreemMaker'),
    dr = require('$LIB/dr/all.js'),
    sprite = require('$SPRITE/sprite.js'),
    acorn = require('$LIB/acorn'),
    coffee = require('$LIB/coffee-script');

  /**
   * @class parser.Error
   * Unified Error class that holds enough information to
   * find the right file at the right line
   */
  dreemParser.Error = (function() {
    /**
     * @constructor
     * @param {String} message Message
     * @param {String} path Path of file
     * @param {Int} line Line of error
     * @param {Int} col Column of error
     */
    function Error(message, path, line, col) {
      this.message = message;
      this.path = path;
      this.line = line;
      this.col = col;
      sprite.console.error('error', message, path, line, col);
    }

    Error.prototype.toString = function() {
      return 'dreemParser Error: ' + this.path + (this.line !== undefined ? ":" + this.line + (this.col ? ":" + this.col : "") : "") + "- " + this.message;
    }

    return Error;
  })();

  /**
   * @class parser.Compiler
   * Client and server usable dreem compiler
   * Parses and loads all dependencies starting from a particular
   * Source XML, and delivers them all compiled and bundled up
   * Ready to be interpreted and turned into classes/instances
   * With this compiler you dont need to handle dependency loading yourself
   * It has an overloadable 'onRead'  that acts as the file loader interface
   * And this method specialized for browser or server elsewhere
   */
  dreemParser.Compiler = (function() {
    /**
     * @constructor
     */
    function Compiler() {};
    var proto = Compiler.prototype;

    /**
     * @method originError
     * Used to decode an origin, they are '0_3' like strings which mean
     * fileid_charoffset
     * These origins are written into the XML JSON so its possible to resolve
     * a particular tag to a particular file/line when needed
     * @param {String} message Message for the error
     * @param {String} origin Origin string ('2_5') to decode
     */
    proto.originError = function(message, origin, module) {
      return new dreemParser.Error(message, module, origin);
    };

    proto.compileError = function(e) {
      var path, line;
      if (e.loc) {
        path = e.loc.line - 1;
        line = e.loc.column;
      } else if (e.location) {
        path = e.location.first_line;
        line = e.location.first_column;
      }
      return new dreemParser.Error(e.message, undefined, path, line);
    };

    /** These are the supported languages for methods, handlers and setters.
        the compile function takes a string of code and the args to the
        function defined by that code. The args are not included in the
        returned string since those get prepended in the Compiler.execute
        method. */
    proto.languages = {
      js:{
        compile:function(codeStr, argsStr) {
          try { // we parse it just for errors
            acorn.parse('function __parsetest__(' + argsStr + '){' + codeStr + '}');
          } catch(e) {
            return dreemParser.Compiler.compileError(e);
          }
          return codeStr;
        }
      },
      coffee:{
        compile:function(codeStr, argsStr) {
          var out;
          try {
            out = coffee.compile(codeStr);
          } catch(e) { // we have an exception. throw it back
            return dreemParser.Compiler.compileError(e);
          }
          // The slice call removes the opening: (function() {
          // and trailing: \n}).call(this);
          // Since all we want is the function body.
          return out.split('\n').slice(1, -2).join('\n');
        }
      }
    };

    /**
     * @method execute
     * Execute the compiler starting from a 'rootname' .dre file
     * The result in the callback is a 'package' that contains
     * all dreem classes
     * package: {
     *     js:{}, A key value list of js files
     *     methods:'' A string containing all methods compiled
     *     root:{} The root in JSONXML
     *     classes:{} A key value list of all classes as JSONXML
     * }
     * @param {String} rootname Root dre file
     * @param {Callback} callback When compiler completes callback(error, result)
     */
    proto.execute = function(rootdre, classmap, callback) {
      this.classmap = classmap;

      // load the root
      this.loader(rootdre, function(err, output) {
        if (err) {
          return callback(err);
        } else {
          // Compile all methods, getters, setters, handlers using the language
          // indicated for each.
          var errors = [], code = '',
            methods = output.methods, i = 0, len = methods.length,
            method, compiled, argsStr;
          for (; i < len; i++) {
            method = methods[i];
            argsStr = method.args.join(',');
            compiled = this.languages[method.language].compile(method.source, argsStr);

            if (compiled instanceof dreemParser.Error) {
              // the compiler returned an error
              var err = this.originError(compiled.message, method.origin, method.module);
              err.line += compiled.line; // adjust for origin
              errors.push(err);
            } else {
              // lets build a nice function out of it
              code += 'methods[' + i + '] = function ' + method.name + '(' + argsStr + '){\n' + compiled + '\n}\n';
            }
          }

          if (errors.length) {
            return callback(errors);
          } else {
            return callback(null, {
              js:output.js, // key value list of js files
              methods:code, // the methods codeblock
              root:rootdre, // the dreem root file passed into execute
              classes:output.classes // the key/value set of dreem classes as JSON-XML
            });
          }
        }
      }.bind(this));
    };

    /**
     * @event onRead
     * called when the compiler needs to read a file
     * @param {String} name Name of the file to read
     * @param {Function} callback Callback to call with (error, result)
     */
    proto.onRead = function(name, callback) {
      throw new Error('Abstract function');
    };

    /* Concats all the childnodes of a jsonxml node */
    proto.__concatCode = function(node) {
      var out = '', children = node.child;
      if (children) {
        var i = 0, len = children.length, child;
        for (; i < len; i++) {
          child = children[i];
          if (child.tag === '$text' || child.tag === '$cdata') out += child.value;
        }
      }
      return out;
    };

    /*
     Main loader function, used internally
     The loader just loads all dependencies and assembles all classes, and
     gathers all compileable methods and their language types
     It does not compile any coffeescript / JS
    */
    proto.loader = function(node, callback) {
      var deps = {}, // dependency hash, stores the from-tags for error reporting
        loading = 0, // number of in flight dependencies
        errors = [], // the error array
        output = {node:node, js:{}, classes:{}, methods:[]},
        method_id = 0,

        // loads javascript
        loadJS = function(file, from_node) {
console.log('loadJS', file, file in output.js, from_node);
          if (file in output.js) return;

          if (deps[file]) {
            deps[file].push(from_node);
          } else {
            deps[file] = [from_node];
          }
        }.bind(this),

        loadClass = function(name, from_node) {
          if (name in dreemMaker.builtin) return;
          if (name in output.classes) return;

          // mark tag as loading but not defined yet
          output.classes[name] = 2;

          if (deps[name]) {
            deps[name].push(from_node);
          } else {
            deps[name] = [from_node];
            // we should make the load order deterministic by force serializing
            // the dependency order

            // lets find the dre file on our require tree
            if (!(name in this.classmap)) throw new Error('Cannot find class ' + name);
            var mod = require(this.classmap[name]);

            walk(mod.dre, null, 'js', name);
          }
        }.bind(this),

        walk = function(node, parent, language, module_name) {
          var attr = node.attr,
            tagName = node.tag;

          // screen and attribute nodes also have types so we don't want to look
          // at them for a compiler language
          if ((tagName !== 'screen' && tagName !== 'attribute') && attr && attr.type) {
            language = attr.type;
            delete attr.type;
          }

          switch (tagName) {
            case 'replicator':
              if (attr && attr.classname) loadClass(attr.classname, node);
              break;
            case 'class':
            case 'mixin':
              var nameattr = attr && attr.name;
              if (!nameattr) {
                errors.push(this.originError('Class has no name ', node.pos, module_name));
                return;
              }

              // create a new tag
              output.classes[nameattr] = node;

              // check extends and view
              if (attr) {
                if (attr.extends) {
                  attr.extends.split(dr.SEPARATOR_REGEX).forEach(function(cls) {if (cls) loadClass(cls, node);});
                }
                if (attr.with) {
                  attr.with.split(dr.SEPARATOR_REGEX).forEach(function(cls) {if (cls) loadClass(cls, node);});
                }
              }
              break;
            case 'method':
            case 'handler':
            case 'setter':
              // give the method a unique but human readable name
              var name = tagName + '_' + (attr && attr.name) + '_' + node._ + '_' + language;
              if (parent && (parent.tag == 'class' || parent.tag == 'mixin')) name = (parent.attr && parent.attr.name) + '_' + name;

              node.method_id = output.methods.length;
              output.methods.push({
                language:language,
                name:name,
                args:attr && attr.args ? attr.args.split(dr.SEPARATOR_REGEX) : [],
                source:this.__concatCode(node),
                module:module_name,
                origin:node.pos
              });

              // clear child
              node.child = undefined;
              break;
            default:
              if (tagName.charAt(0) != '$') {
                if (attr && attr.with) {
                  attr.with.split(dr.SEPARATOR_REGEX).forEach(function(cls) {if (cls) loadClass(cls, node);});
                }
                loadClass(tagName, node);
              }
              break;
          }

          // Look for scriptincludes on every tag
          if (attr && attr.scriptincludes) { // load the script includes
            attr.scriptincludes.split(dr.SEPARATOR_REGEX).forEach(function(js) {loadJS(js, node);});
          }

          if (node.child) {
            for (var i = 0; i < node.child.length; i++) {
              walk(node.child[i], node, language);
            }
          }
        }.bind(this);

      // Walk JSON
      walk(node, null, 'js', 'composition');

      // the impossible case of no dependencies
      errors.length ? callback(errors) : callback(null, output);
    };

    return Compiler;
  })()
})<commit_msg>Remove console output that will mess with phantom tests.<commit_after>/*
 The MIT License (MIT)

 Copyright ( c ) 2014-2015 Teem2 LLC

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

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

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

Dreem file parser and dependency resolver.
*/
define(function(require, exports, module) {
  var dreemParser = exports;

  var dreemMaker = require('./dreemMaker'),
    dr = require('$LIB/dr/all.js'),
    sprite = require('$SPRITE/sprite.js'),
    acorn = require('$LIB/acorn'),
    coffee = require('$LIB/coffee-script');

  /**
   * @class parser.Error
   * Unified Error class that holds enough information to
   * find the right file at the right line
   */
  dreemParser.Error = (function() {
    /**
     * @constructor
     * @param {String} message Message
     * @param {String} path Path of file
     * @param {Int} line Line of error
     * @param {Int} col Column of error
     */
    function Error(message, path, line, col) {
      this.message = message;
      this.path = path;
      this.line = line;
      this.col = col;
      sprite.console.error('error', message, path, line, col);
    }

    Error.prototype.toString = function() {
      return 'dreemParser Error: ' + this.path + (this.line !== undefined ? ":" + this.line + (this.col ? ":" + this.col : "") : "") + "- " + this.message;
    }

    return Error;
  })();

  /**
   * @class parser.Compiler
   * Client and server usable dreem compiler
   * Parses and loads all dependencies starting from a particular
   * Source XML, and delivers them all compiled and bundled up
   * Ready to be interpreted and turned into classes/instances
   * With this compiler you dont need to handle dependency loading yourself
   * It has an overloadable 'onRead'  that acts as the file loader interface
   * And this method specialized for browser or server elsewhere
   */
  dreemParser.Compiler = (function() {
    /**
     * @constructor
     */
    function Compiler() {};
    var proto = Compiler.prototype;

    /**
     * @method originError
     * Used to decode an origin, they are '0_3' like strings which mean
     * fileid_charoffset
     * These origins are written into the XML JSON so its possible to resolve
     * a particular tag to a particular file/line when needed
     * @param {String} message Message for the error
     * @param {String} origin Origin string ('2_5') to decode
     */
    proto.originError = function(message, origin, module) {
      return new dreemParser.Error(message, module, origin);
    };

    proto.compileError = function(e) {
      var path, line;
      if (e.loc) {
        path = e.loc.line - 1;
        line = e.loc.column;
      } else if (e.location) {
        path = e.location.first_line;
        line = e.location.first_column;
      }
      return new dreemParser.Error(e.message, undefined, path, line);
    };

    /** These are the supported languages for methods, handlers and setters.
        the compile function takes a string of code and the args to the
        function defined by that code. The args are not included in the
        returned string since those get prepended in the Compiler.execute
        method. */
    proto.languages = {
      js:{
        compile:function(codeStr, argsStr) {
          try { // we parse it just for errors
            acorn.parse('function __parsetest__(' + argsStr + '){' + codeStr + '}');
          } catch(e) {
            return dreemParser.Compiler.compileError(e);
          }
          return codeStr;
        }
      },
      coffee:{
        compile:function(codeStr, argsStr) {
          var out;
          try {
            out = coffee.compile(codeStr);
          } catch(e) { // we have an exception. throw it back
            return dreemParser.Compiler.compileError(e);
          }
          // The slice call removes the opening: (function() {
          // and trailing: \n}).call(this);
          // Since all we want is the function body.
          return out.split('\n').slice(1, -2).join('\n');
        }
      }
    };

    /**
     * @method execute
     * Execute the compiler starting from a 'rootname' .dre file
     * The result in the callback is a 'package' that contains
     * all dreem classes
     * package: {
     *     js:{}, A key value list of js files
     *     methods:'' A string containing all methods compiled
     *     root:{} The root in JSONXML
     *     classes:{} A key value list of all classes as JSONXML
     * }
     * @param {String} rootname Root dre file
     * @param {Callback} callback When compiler completes callback(error, result)
     */
    proto.execute = function(rootdre, classmap, callback) {
      this.classmap = classmap;

      // load the root
      this.loader(rootdre, function(err, output) {
        if (err) {
          return callback(err);
        } else {
          // Compile all methods, getters, setters, handlers using the language
          // indicated for each.
          var errors = [], code = '',
            methods = output.methods, i = 0, len = methods.length,
            method, compiled, argsStr;
          for (; i < len; i++) {
            method = methods[i];
            argsStr = method.args.join(',');
            compiled = this.languages[method.language].compile(method.source, argsStr);

            if (compiled instanceof dreemParser.Error) {
              // the compiler returned an error
              var err = this.originError(compiled.message, method.origin, method.module);
              err.line += compiled.line; // adjust for origin
              errors.push(err);
            } else {
              // lets build a nice function out of it
              code += 'methods[' + i + '] = function ' + method.name + '(' + argsStr + '){\n' + compiled + '\n}\n';
            }
          }

          if (errors.length) {
            return callback(errors);
          } else {
            return callback(null, {
              js:output.js, // key value list of js files
              methods:code, // the methods codeblock
              root:rootdre, // the dreem root file passed into execute
              classes:output.classes // the key/value set of dreem classes as JSON-XML
            });
          }
        }
      }.bind(this));
    };

    /**
     * @event onRead
     * called when the compiler needs to read a file
     * @param {String} name Name of the file to read
     * @param {Function} callback Callback to call with (error, result)
     */
    proto.onRead = function(name, callback) {
      throw new Error('Abstract function');
    };

    /* Concats all the childnodes of a jsonxml node */
    proto.__concatCode = function(node) {
      var out = '', children = node.child;
      if (children) {
        var i = 0, len = children.length, child;
        for (; i < len; i++) {
          child = children[i];
          if (child.tag === '$text' || child.tag === '$cdata') out += child.value;
        }
      }
      return out;
    };

    /*
     Main loader function, used internally
     The loader just loads all dependencies and assembles all classes, and
     gathers all compileable methods and their language types
     It does not compile any coffeescript / JS
    */
    proto.loader = function(node, callback) {
      var deps = {}, // dependency hash, stores the from-tags for error reporting
        loading = 0, // number of in flight dependencies
        errors = [], // the error array
        output = {node:node, js:{}, classes:{}, methods:[]},
        method_id = 0,

        // loads javascript
        loadJS = function(file, from_node) {
          if (file in output.js) return;

          if (deps[file]) {
            deps[file].push(from_node);
          } else {
            deps[file] = [from_node];
          }
        }.bind(this),

        loadClass = function(name, from_node) {
          if (name in dreemMaker.builtin) return;
          if (name in output.classes) return;

          // mark tag as loading but not defined yet
          output.classes[name] = 2;

          if (deps[name]) {
            deps[name].push(from_node);
          } else {
            deps[name] = [from_node];
            // we should make the load order deterministic by force serializing
            // the dependency order

            // lets find the dre file on our require tree
            if (!(name in this.classmap)) throw new Error('Cannot find class ' + name);
            var mod = require(this.classmap[name]);

            walk(mod.dre, null, 'js', name);
          }
        }.bind(this),

        walk = function(node, parent, language, module_name) {
          var attr = node.attr,
            tagName = node.tag;

          // screen and attribute nodes also have types so we don't want to look
          // at them for a compiler language
          if ((tagName !== 'screen' && tagName !== 'attribute') && attr && attr.type) {
            language = attr.type;
            delete attr.type;
          }

          switch (tagName) {
            case 'replicator':
              if (attr && attr.classname) loadClass(attr.classname, node);
              break;
            case 'class':
            case 'mixin':
              var nameattr = attr && attr.name;
              if (!nameattr) {
                errors.push(this.originError('Class has no name ', node.pos, module_name));
                return;
              }

              // create a new tag
              output.classes[nameattr] = node;

              // check extends and view
              if (attr) {
                if (attr.extends) {
                  attr.extends.split(dr.SEPARATOR_REGEX).forEach(function(cls) {if (cls) loadClass(cls, node);});
                }
                if (attr.with) {
                  attr.with.split(dr.SEPARATOR_REGEX).forEach(function(cls) {if (cls) loadClass(cls, node);});
                }
              }
              break;
            case 'method':
            case 'handler':
            case 'setter':
              // give the method a unique but human readable name
              var name = tagName + '_' + (attr && attr.name) + '_' + node._ + '_' + language;
              if (parent && (parent.tag == 'class' || parent.tag == 'mixin')) name = (parent.attr && parent.attr.name) + '_' + name;

              node.method_id = output.methods.length;
              output.methods.push({
                language:language,
                name:name,
                args:attr && attr.args ? attr.args.split(dr.SEPARATOR_REGEX) : [],
                source:this.__concatCode(node),
                module:module_name,
                origin:node.pos
              });

              // clear child
              node.child = undefined;
              break;
            default:
              if (tagName.charAt(0) != '$') {
                if (attr && attr.with) {
                  attr.with.split(dr.SEPARATOR_REGEX).forEach(function(cls) {if (cls) loadClass(cls, node);});
                }
                loadClass(tagName, node);
              }
              break;
          }

          // Look for scriptincludes on every tag
          if (attr && attr.scriptincludes) { // load the script includes
            attr.scriptincludes.split(dr.SEPARATOR_REGEX).forEach(function(js) {loadJS(js, node);});
          }

          if (node.child) {
            for (var i = 0; i < node.child.length; i++) {
              walk(node.child[i], node, language);
            }
          }
        }.bind(this);

      // Walk JSON
      walk(node, null, 'js', 'composition');

      // the impossible case of no dependencies
      errors.length ? callback(errors) : callback(null, output);
    };

    return Compiler;
  })()
})<|endoftext|>


<commit_before>/**
 * Created by chengfulin on 2015/4/29.
 */
var CFGExt = require('./cfgext'),
    CFGWrapper = require('./cfgwrapper'),
    Scope = require('./scope'),
    Var = require('./var'),
    Def = require('./def'),
    Range = require('./range'),
    namespace = require('./namespace'),
    internal = namespace(),
    Map = require('core-js/es6/map'),
    walkes = require('walkes');

/**
 * Build the tree of scopes (function scope, program scope)
 * @param ast AST parsed from the source
 * @constructor
 */
function FunctionScopeTree(ast) {
    'use strict';
    internal(this).functionScopes = [];
    internal(this).root = null;
    internal(this).numOfAnonymousFunctions = 0;
    internal(this).mapFromRangeToFunctionScope = new Map();
    internal(this).mapFromDefToFunctionScope = new Map();
    internal(this).mapFromScopeNameToFunctionScope = new Map();

    buildCFGWrapperTree(this, ast);
}

/**
 * Add a function scope (CFGWrapper type) into the tree
 * @param tree
 * @param fscope
 * @private
 */
function addFunctionScope(tree, fscope) {
    'use strict';
    if (CFGWrapper.isCFGWrapper(fscope)) {
        internal(tree).functionScopes.push(fscope);
        internal(tree).mapFromRangeToFunctionScope.set(fscope.getRange().toString(), fscope);
        internal(tree).mapFromDefToFunctionScope.set(fscope.getDef().toString(), fscope);
        internal(tree).mapFromScopeNameToFunctionScope.set(fscope.getScope().toString(), fscope);
    }
}

/**
 * Build the function scope tree with an AST
 * @param tree the FunctionScopeTree
 * @param ast AST
 * @private
 */
function buildCFGWrapperTree(tree, ast) {
    'use strict';
    var thisTree = tree, current = null;
    walkes(ast, {
        Program: function (node, recurse) {
            var cfg = CFGExt.getCFG(node),
                cfgWrapper = new CFGWrapper(cfg, Scope.PROGRAM_SCOPE, null);
            internal(thisTree).root = cfgWrapper;/// 'Program' scope should be the root, if any
            addFunctionScope(thisTree, cfgWrapper);
            node.body.forEach(function (elem) {/// to recursively walks to inner functions
                current = cfgWrapper;
                recurse(elem);
            });
        },
        FunctionDeclaration: function (node, recurse) {
            var cfg = CFGExt.getCFG(node.body),
                cfgWrapper = new CFGWrapper(cfg, new Scope(node.id.name), current || null),
                params = [];
            node.params.forEach(function (paramNode) {
                params.push(new Var(paramNode.name, paramNode.range, cfgWrapper.getScope()));
            });
            cfgWrapper.setParams(params);
            if (!!current) {
                current.addChild(cfgWrapper);
            } else {
                internal(thisTree).root = cfgWrapper;
            }
            addFunctionScope(thisTree, cfgWrapper);
            node.body.body.forEach(function (elem) {/// to recursively walks to inner functions
                current = cfgWrapper;
                recurse(elem);
            });
        },
        VariableDeclarator: function (node, recurse) {
            if (node.init.type === 'FunctionExpression') {
                var cfg = CFGExt.getCFG(node.init.body),
                    cfgWrapper = new CFGWrapper(
                        cfg,
                        new Scope(internal(thisTree).numOfAnonymousFunctions),
                        current || null
                    ),
                    params = [];
                node.init.params.forEach(function (paramNode) {
                    params.push(new Var(paramNode.name, paramNode.range, cfgWrapper.getScope()));
                });
                cfgWrapper.setParams(params);
                internal(thisTree).numOfAnonymousFunctions += 1;
                if (!!current) {/// TODO: add function expression as child
                    current.addChild(cfgWrapper, new Var(
                        node.id.name,
                        node.range,
                        current.getScope(),
                        null)
                    );
                } else {
                    internal(thisTree).root = cfgWrapper;
                }
                addFunctionScope(thisTree, cfgWrapper);
                node.init.body.body.forEach(function (elem) {/// to recursively walks to inner functions
                    current = cfgWrapper;
                    recurse(elem);
                });
            }
        }
    });
}

/**
 * Validator for AST used to build the tree
 * @param ast
 * @param msg custom error message if any
 * @throws {Error} when the AST is not start from 'Program', 'FunctionDeclaration' or 'FunctionExpression' type
 */
FunctionScopeTree.prototype.validate = function (ast, msg) {
    'use strict';
    if (!ast.type ||
        (ast.type !== 'Program' && ast.type !== 'FunctionDeclaration' && ast.type !== 'FunctionExpression')) {
        throw new Error(msg || 'Invalid start point for a FunctionScopeTree');
    }
};

/**
 * Getter for the root scope
 * @returns {*|CFGWrapper}
 */
FunctionScopeTree.prototype.getRoot = function () {
    'use strict';
    return internal(this).root;
};

/**
 * method for getting a function scope (CFGWrapper type) by comparing its range
 * @param range Range object or the string representation of it
 * @returns {*|CFGWrapper}
 */
FunctionScopeTree.prototype.getFunctionScopeByRange = function (range) {
    'use strict';
    if (Range.isRange(range)) {
        return internal(this).mapFromRangeToFunctionScope.get(range.toString());
    } else if (typeof range === 'string') {
        return internal(this).mapFromRangeToFunctionScope.get(range);
    }
    /// return undefined
};

/**
 * Method for getting a function scope by comparing its definition
 * @param definition Def object or the string representation of it
 * @returns {*|CFGWrapper}
 */
FunctionScopeTree.prototype.getFunctionScopeByDef = function (definition) {
    'use strict';
    if (Def.isDef(definition)) {
        /// return the function if its definition is the same
        return internal(this).mapFromDefToFunctionScope.get(definition.toString());
    } else if (typeof definition === 'string') {
        return internal(this).mapFromDefToFunctionScope.get(definition);
    }
    /// return undefined
};

/**
 * Method for getting a function scope (CFGWrapper type) by comparing its scope name
 * @param scopeName Scope object or the string representation of it
 * @returns {*|CFGWrapper}
 */
FunctionScopeTree.prototype.getFunctionScopeByScopeName = function (scopeName) {
    'use strict';
    if (Scope.isScope(scopeName)) {
        /// return the function if its scope is the same
        return internal(this).mapFromScopeNameToFunctionScope.get(scopeName.toString());
    } else if (typeof scopeName === 'string') {
        return internal(this).mapFromScopeNameToFunctionScope.get(scopeName);
    }
    /// return undefined
};

/**
 * Check the cfgWrapper is in the tree or not
 * @param cfgWrapper
 * @returns {boolean}
 */
FunctionScopeTree.prototype.hasFunctionScope = function (cfgWrapper) {
    'use strict';
    if(CFGWrapper.isCFGWrapper(cfgWrapper)) {
        internal(this).functionScopes.forEach(function (scope) {
            if (scope.toString() === cfgWrapper) {
                return true;
            }
        });
        return false;
    }
    return false;
};

/**
 * Getter for the number of anonymous functions
 * @returns {*|number}
 */
FunctionScopeTree.prototype.getNumOfAnonymousFunctions = function () {
    'use strict';
    return internal(this).numOfAnonymousFunctions;
};

module.exports = FunctionScopeTree;<commit_msg>Re-added the getter for function scopes<commit_after>/**
 * Created by chengfulin on 2015/4/29.
 */
var CFGExt = require('./cfgext'),
    CFGWrapper = require('./cfgwrapper'),
    Scope = require('./scope'),
    Var = require('./var'),
    Def = require('./def'),
    Range = require('./range'),
    namespace = require('./namespace'),
    internal = namespace(),
    Map = require('core-js/es6/map'),
    walkes = require('walkes');

/**
 * Build the tree of scopes (function scope, program scope)
 * @param ast AST parsed from the source
 * @constructor
 */
function FunctionScopeTree(ast) {
    'use strict';
    internal(this).functionScopes = [];
    internal(this).root = null;
    internal(this).numOfAnonymousFunctions = 0;
    internal(this).mapFromRangeToFunctionScope = new Map();
    internal(this).mapFromDefToFunctionScope = new Map();
    internal(this).mapFromScopeNameToFunctionScope = new Map();

    buildCFGWrapperTree(this, ast);
}

/**
 * Add a function scope (CFGWrapper type) into the tree
 * @param tree
 * @param fscope
 * @private
 */
function addFunctionScope(tree, fscope) {
    'use strict';
    if (CFGWrapper.isCFGWrapper(fscope)) {
        internal(tree).functionScopes.push(fscope);
        internal(tree).mapFromRangeToFunctionScope.set(fscope.getRange().toString(), fscope);
        internal(tree).mapFromDefToFunctionScope.set(fscope.getDef().toString(), fscope);
        internal(tree).mapFromScopeNameToFunctionScope.set(fscope.getScope().toString(), fscope);
    }
}

/**
 * Build the function scope tree with an AST
 * @param tree the FunctionScopeTree
 * @param ast AST
 * @private
 */
function buildCFGWrapperTree(tree, ast) {
    'use strict';
    var thisTree = tree, current = null;
    walkes(ast, {
        Program: function (node, recurse) {
            var cfg = CFGExt.getCFG(node),
                cfgWrapper = new CFGWrapper(cfg, Scope.PROGRAM_SCOPE, null);
            internal(thisTree).root = cfgWrapper;/// 'Program' scope should be the root, if any
            addFunctionScope(thisTree, cfgWrapper);
            node.body.forEach(function (elem) {/// to recursively walks to inner functions
                current = cfgWrapper;
                recurse(elem);
            });
        },
        FunctionDeclaration: function (node, recurse) {
            var cfg = CFGExt.getCFG(node.body),
                cfgWrapper = new CFGWrapper(cfg, new Scope(node.id.name), current || null),
                params = [];
            node.params.forEach(function (paramNode) {
                params.push(new Var(paramNode.name, paramNode.range, cfgWrapper.getScope()));
            });
            cfgWrapper.setParams(params);
            if (!!current) {
                current.addChild(cfgWrapper);
            } else {
                internal(thisTree).root = cfgWrapper;
            }
            addFunctionScope(thisTree, cfgWrapper);
            node.body.body.forEach(function (elem) {/// to recursively walks to inner functions
                current = cfgWrapper;
                recurse(elem);
            });
        },
        VariableDeclarator: function (node, recurse) {
            if (node.init.type === 'FunctionExpression') {
                var cfg = CFGExt.getCFG(node.init.body),
                    cfgWrapper = new CFGWrapper(
                        cfg,
                        new Scope(internal(thisTree).numOfAnonymousFunctions),
                        current || null
                    ),
                    params = [];
                node.init.params.forEach(function (paramNode) {
                    params.push(new Var(paramNode.name, paramNode.range, cfgWrapper.getScope()));
                });
                cfgWrapper.setParams(params);
                internal(thisTree).numOfAnonymousFunctions += 1;
                if (!!current) {/// TODO: add function expression as child
                    current.addChild(cfgWrapper, new Var(
                        node.id.name,
                        node.range,
                        current.getScope(),
                        null)
                    );
                } else {
                    internal(thisTree).root = cfgWrapper;
                }
                addFunctionScope(thisTree, cfgWrapper);
                node.init.body.body.forEach(function (elem) {/// to recursively walks to inner functions
                    current = cfgWrapper;
                    recurse(elem);
                });
            }
        }
    });
}

/**
 * Validator for AST used to build the tree
 * @param ast
 * @param msg custom error message if any
 * @throws {Error} when the AST is not start from 'Program', 'FunctionDeclaration' or 'FunctionExpression' type
 */
FunctionScopeTree.prototype.validate = function (ast, msg) {
    'use strict';
    if (!ast.type ||
        (ast.type !== 'Program' && ast.type !== 'FunctionDeclaration' && ast.type !== 'FunctionExpression')) {
        throw new Error(msg || 'Invalid start point for a FunctionScopeTree');
    }
};

/**
 * Getter for the root scope
 * @returns {*|CFGWrapper}
 */
FunctionScopeTree.prototype.getRoot = function () {
    'use strict';
    return internal(this).root;
};

/**
 * method for getting a function scope (CFGWrapper type) by comparing its range
 * @param range Range object or the string representation of it
 * @returns {*|CFGWrapper}
 */
FunctionScopeTree.prototype.getFunctionScopeByRange = function (range) {
    'use strict';
    if (Range.isRange(range)) {
        return internal(this).mapFromRangeToFunctionScope.get(range.toString());
    } else if (typeof range === 'string') {
        return internal(this).mapFromRangeToFunctionScope.get(range);
    }
    /// return undefined
};

/**
 * Method for getting a function scope by comparing its definition
 * @param definition Def object or the string representation of it
 * @returns {*|CFGWrapper}
 */
FunctionScopeTree.prototype.getFunctionScopeByDef = function (definition) {
    'use strict';
    if (Def.isDef(definition)) {
        /// return the function if its definition is the same
        return internal(this).mapFromDefToFunctionScope.get(definition.toString());
    } else if (typeof definition === 'string') {
        return internal(this).mapFromDefToFunctionScope.get(definition);
    }
    /// return undefined
};

/**
 * Method for getting a function scope (CFGWrapper type) by comparing its scope name
 * @param scopeName Scope object or the string representation of it
 * @returns {*|CFGWrapper}
 */
FunctionScopeTree.prototype.getFunctionScopeByScopeName = function (scopeName) {
    'use strict';
    if (Scope.isScope(scopeName)) {
        /// return the function if its scope is the same
        return internal(this).mapFromScopeNameToFunctionScope.get(scopeName.toString());
    } else if (typeof scopeName === 'string') {
        return internal(this).mapFromScopeNameToFunctionScope.get(scopeName);
    }
    /// return undefined
};

/**
 * Check the cfgWrapper is in the tree or not
 * @param cfgWrapper
 * @returns {boolean}
 */
FunctionScopeTree.prototype.hasFunctionScope = function (cfgWrapper) {
    'use strict';
    if(CFGWrapper.isCFGWrapper(cfgWrapper)) {
        internal(this).functionScopes.forEach(function (scope) {
            if (scope.toString() === cfgWrapper) {
                return true;
            }
        });
        return false;
    }
    return false;
};

/**
 * Getter for getting the list of function scopes
 * @returns {Array}
 */
FunctionScopeTree.prototype.getFunctionScopes = function () {
    'use strict';
    return [].concat(internal(this).functionScopes);
};

/**
 * Getter for the number of anonymous functions
 * @returns {*|number}
 */
FunctionScopeTree.prototype.getNumOfAnonymousFunctions = function () {
    'use strict';
    return internal(this).numOfAnonymousFunctions;
};

module.exports = FunctionScopeTree;<|endoftext|>


<commit_before>var OpenSpending = OpenSpending || {}

OpenSpending.EntriesSearch = function (input, searchResults, template) {
  window.onhashchange = function () {
    var params = _getParams();
    if (!params['q']) { return; }

    input.val(params['q']);
    _search(params);
  };
  window.onhashchange();

  input.closest('form').submit(function (evt) {
    _extendParams({'q': input.val()});

    evt.preventDefault();
  });

  function _getParams() {
    var hash = window.location.hash,
        params = {};

    var paramsArray = hash.replace('#', '').split('&');
    if (paramsArray[0] === '') { return params; }

    for (i in paramsArray) {
      var keyValue = paramsArray[i].split('=');
      params[keyValue[0]] = keyValue[1];
    }

    return params;
  };

  function _extendParams(newAttributes) {
    var newParams = $.extend(_getParams(), newAttributes);

    window.location.hash = _paramsToHash(newParams);

    return newParams;
  };

  function _paramsToHash(params) {
    var values = [];

    for (key in params) {
      values = values.concat(encodeURIComponent(key) + '=' + encodeURIComponent(params[key]));
    }

    return values.join('&');
  };

  function _search(params) {
    var defaultParams = { 'page': 1, 'pagesize': 5, 'order': 'time:desc' };

    params = $.extend(defaultParams, params);

    _toggleSpinner();

    $.getJSON("/api/2/search", params, function (data) {
      _displayResults(data.results, searchResults);
      _toggleSpinner();
      searchResults.highlight(params['q']);
    });
  };

  function _displayResults(results, element) {
    if (results.length === 0) {
      element.html('No data available for this query.');
      return;
    }

    element.html('');

    for (i in results) {
      element.append(_prepareResult(results[i]));
    }
  };

  function _toggleSpinner(element) {
    input.toggleClass('spinner');
  };

  function _prepareResult(result) {
    result['values'] = _prepareDimensionsForHandlebars(result);
    result.amount = OpenSpending.Utils.formatAmountWithCommas(result.amount, 0, result.currency)
    return template(result);
  };

  function _prepareDimensionsForHandlebars(result) {
    var dimensions = [];

    for (dimension in result) {
      var label = result[dimension].label;
      if (dimension !== "time" && dimension !== "dataset" && label) {
        dimensions.push({ key: dimension, value: label });
      }
    }

    return dimensions;
  };
};

$(document).ready(function () {
  var template = $('#search-result-template').html(),
      resultTemplate = Handlebars.compile(template),
      searchInput = $('#search input[name="q"]'),
      searchResults = $('#search-results');

  OpenSpending.EntriesSearch(searchInput, searchResults, resultTemplate)
});

<commit_msg>Fixes bug where it highlighted the error message (#478)<commit_after>var OpenSpending = OpenSpending || {}

OpenSpending.EntriesSearch = function (input, searchResults, template) {
  window.onhashchange = function () {
    var params = _getParams();
    if (!params['q']) { return; }

    input.val(params['q']);
    _search(params);
  };
  window.onhashchange();

  input.closest('form').submit(function (evt) {
    _extendParams({'q': input.val()});

    evt.preventDefault();
  });

  function _getParams() {
    var hash = window.location.hash,
        params = {};

    var paramsArray = hash.replace('#', '').split('&');
    if (paramsArray[0] === '') { return params; }

    for (i in paramsArray) {
      var keyValue = paramsArray[i].split('=');
      params[keyValue[0]] = keyValue[1];
    }

    return params;
  };

  function _extendParams(newAttributes) {
    var newParams = $.extend(_getParams(), newAttributes);

    window.location.hash = _paramsToHash(newParams);

    return newParams;
  };

  function _paramsToHash(params) {
    var values = [];

    for (key in params) {
      values = values.concat(encodeURIComponent(key) + '=' + encodeURIComponent(params[key]));
    }

    return values.join('&');
  };

  function _search(params) {
    var defaultParams = { 'page': 1, 'pagesize': 5, 'order': 'time:desc' };

    params = $.extend(defaultParams, params);

    _toggleSpinner();

    $.getJSON("/api/2/search", params, function (data) {
      _toggleSpinner();

      if (data.results.length === 0) {
        searchResults.html('No data available for this query.');
        return;
      }

      _displayResults(data.results, searchResults);
      searchResults.highlight(params['q']);
    });
  };

  function _displayResults(results, element) {
    element.html('');

    for (i in results) {
      element.append(_prepareResult(results[i]));
    }
  };

  function _toggleSpinner(element) {
    input.toggleClass('spinner');
  };

  function _prepareResult(result) {
    result['values'] = _prepareDimensionsForHandlebars(result);
    result.amount = OpenSpending.Utils.formatAmountWithCommas(result.amount, 0, result.currency)
    return template(result);
  };

  function _prepareDimensionsForHandlebars(result) {
    var dimensions = [];

    for (dimension in result) {
      var label = result[dimension].label;
      if (dimension !== "time" && dimension !== "dataset" && label) {
        dimensions.push({ key: dimension, value: label });
      }
    }

    return dimensions;
  };
};

$(document).ready(function () {
  var template = $('#search-result-template').html(),
      resultTemplate = Handlebars.compile(template),
      searchInput = $('#search input[name="q"]'),
      searchResults = $('#search-results');

  OpenSpending.EntriesSearch(searchInput, searchResults, resultTemplate)
});

<|endoftext|>