<commit_before>
// Dependences
const http  = require('http');
const https = require('https');
const constants = require('../config/constants.js');
const errors = require('../config/errors.js');
const querystring = require('querystring');
const _response = require('./response.js');
const _rerr = require('./response_error');
const _request_body = require('./request_body.js');
const _log = require('./trestc_log');

/**
 * Main of request. Is the first function that execute request and call rest of
 * modules.
 */
exports.r = function(httpMethod,isHttps,host,port,path,params,data,options,callback){
    options = options || {};

    //If exists path params, we add to query
    if (params!= null){
        var q = querystring.stringify(params);
        if (q != '')
            path += '?'+q;
    }

    //We get original parameters that will be usefull if we have to redirect
    var originalQueryRequest = _getOriginalRequestQuery(httpMethod,isHttps,host,port,path,params,data,options);

    //Default Options
    var httpOptions = _getHttpOptionsOfRequest(httpMethod,host,port,path,options);

    //Get body
    var body = _request_body.getBodyOfRequest(httpMethod,options,data,httpOptions);
    if (body == null){
        return _rerr.process(errors.ERROR_PARSING_BODY_REQUEST,null,null,callback)
    }

    options.trace = true;
    //Object for trace to return
    var trace = _createTraceOfRequest(httpOptions,isHttps,data,body,options);

    //Log request if required
    _logRequest(trace,options);

    //Generating request
    _executeHttpRequest(httpOptions,isHttps,body,options,originalQueryRequest,trace,callback);
};

/**
 * This function execute a the http request
 */
var _executeHttpRequest = function(httpOptions,isHttps,body,options,originalQueryRequest,trace,callback){
	//Flag for avoid recallback problem found in some node versions
	var callbackit = true;

	//Select correct protocol
	var ohttp = isHttps?https:http;

    //Execute request
    var req = ohttp.request(httpOptions, function(res) {
        if (callbackit){
            callbackit = false;
            _response.r(res,options,trace,function(err,response,statusCode,ttrace,redirection){
                if (err != null){
                    callback(err,response,statusCode,ttrace);
                } else if (redirection !== undefined && redirection!=null){
                    _executeRedirection(redirection,originalQueryRequest,ttrace,callback);
                } else {
                    callback(null,response,statusCode,ttrace);
                }
            });
        }
    });

    //On error request callback handler
    req.on('error', function(e) {
        if (callbackit){
            callbackit = false;
            return _rerr.process(errors.ERROR_CONNECTION_TO_SERVER,null,trace,callback)
        }
    });

    //Implementing timeout on request
    var timeout = (typeof options.timeout == 'undefined')?constants.DEFAULT_TIMEOUT:options.timeout;
    if (timeout > 0){
        req.setTimeout(timeout,function () {
            if (callbackit){
                callbackit = false;
                req.abort();
                return _rerr.process(errors.ERROR_TIMEOUT_ON_REQUEST,null,trace,callback)
            }
        });
    }

    //Req execution
    if (body != null && body != '')
        req.write(body);
    req.end();
}

/**
 * This function is called when a redirection is received from server and we have to repeat
 * request to other new location
 */
var _executeRedirection = function(redirection,originalQueryRequest,trace,callback){
    if (typeof originalQueryRequest.options == 'undefined'){
        originalQueryRequest.options = {}
    }
    if (typeof originalQueryRequest.options.maxRedirects == 'undefined'){
        originalQueryRequest.options.maxRedirects = constants.MAX_REDIRECTION_DEPTH;
    }
    originalQueryRequest.options.maxRedirects--;
    if (originalQueryRequest.options.maxRedirects==0){
        return _rerr.process(errors.ERROR_INFINITE_REDIRECTIONS,null,trace,callback);
    }

    //Redirect
    exports.r(originalQueryRequest.httpMethod,
                redirection.isHttps,
                redirection.host,
                redirection.port,
                redirection.path,
                originalQueryRequest.params,
                originalQueryRequest.data,
                originalQueryRequest.options,
                callback);
};

/**
 * This options return an object with all original data of request. This object will be useful
 * later in redirects
 */
var _getOriginalRequestQuery = function(httpMethod,isHttps,host,port,path,params,data,options){
    return {
        httpMethod: httpMethod,
        isHttps   : isHttps,
        host      : host,
        port      : port,
        path      : path,
        options   : options,
        data      : data,
        params    : params
    }
};

/**
 * This function return http options of request
 */
var _getHttpOptionsOfRequest = function(httpMethod,host,port,path,options){
	options = options || {};

	//Get default options
    var httpOptions = _getHttpDefaultOptions(httpMethod,host,port,path);

    //Check if auth
    _authOptions(httpOptions,options);

    //Adding request headers
    _addRequestHeaders(httpOptions,options);

    //Adding cert
    _addCertOptions(httpOptions,options);

    return httpOptions;
};

/**
 * This funciton return the http request default options
 */
var _getHttpDefaultOptions = function(httpMethod,host,port,path){
    var defOptions = {
        host     : host,
        port     : port,
        path     : path,
        encoding : 'utf8', //default encoding????
        //encoding: 'iso-8859-15',
        method   : httpMethod,
        headers  : {
        	'accept-encoding' : 'gzip, deflate' //DRM. By default we allow this but...
        },
    };

    return defOptions;
};

/**
 * This function add HTTP AUTH options to request if it's configured
 */
var _authOptions = function(httpOptions,options){
    if (typeof options.auth !== 'undefined' && options.auth.type !== undefined){
        switch(options.auth.type){
            case 'basic':
                if (options.auth.username !== undefined && options.auth.password !== undefined ){
                    httpOptions.auth = options.auth.username+':'+options.auth.password;
                }
                break;
        }
    }
};

/**
 * This function add HTTPs Cert options to request if it's configured
 */
var _addCertOptions = function(httpOptions,options){
    if (typeof options.cert !== 'undefined'){
        httpOptions.key = options.cert.key;
        httpOptions.cert = options.cert.cert;
        if (typeof options.cert.ca !== 'undefined'){
            httpOptions.ca = options.cert.ca;
        }
    }
};

/**
 * This function add custom header options given
 */
var _addRequestHeaders = function(httpOptions,options){
    options.headers = options.headers||{};

    for(var headertag in options.headers){
        var cshtag = headertag.toLowerCase();
        if ((cshtag == 'accept-encoding') && (options.headers[headertag] == '')){
            delete httpOptions.headers[cshtag];
            continue;
        }
        httpOptions.headers[cshtag] = options.headers[headertag];
    }
};

/**
 * This function check if trace is enabled in options and in that
 * case create trace object and set req field with information of request.
 * If disabled, return null
 */
var _createTraceOfRequest = function(httpOptions,isHttps,data,rawdata,options){
    if ((options.trace !== undefined && options.trace)||(options.log !== undefined && options.log !== null && options.log.tag !== undefined && options.log.tag != null)){
        return _getTrace(httpOptions,isHttps,data,rawdata,options);
    } else {
        return null;
    }
}

/**
 * This function call to logRequest funciton (if apply)
 */
var _logRequest = function(trace,options){
    if (options.log !== undefined && options.log !== null && options.log.tag !== undefined && options.log.tag != null){
        _log.logRequest(options.log.tag,trace.req)
    }
};

/**
 * Return the trace object created with request data
 */
var _getTrace = function(httpOptions,isHttps,data,rawdata,options){
    var trace = {
        req : {
            protocol : isHttps?'https':'http',
            method   : httpOptions.method,
            host     : httpOptions.host,
            port     : httpOptions.port,
            path     : httpOptions.path,
            encoding : httpOptions.encoding,
            headers  : httpOptions.headers,
            body     : data,
            rawbody  : rawdata,
            timestamp: new Date(),
        }
    }
    return trace;
}<commit_msg>bug fix<commit_after>
// Dependences
const http  = require('http');
const https = require('https');
const constants = require('../config/constants.js');
const errors = require('../config/errors.js');
const querystring = require('querystring');
const _response = require('./response.js');
const _rerr = require('./response_error');
const _request_body = require('./request_body.js');
const _log = require('./trestc_log');

/**
 * Main of request. Is the first function that execute request and call rest of
 * modules.
 */
exports.r = function(httpMethod,isHttps,host,port,path,params,data,options,callback){
    options = options || {};

    //If exists path params, we add to query
    if (params!= null){
        var q = querystring.stringify(params);
        if (q != '')
            path += '?'+q;
    }

    //We get original parameters that will be usefull if we have to redirect
    var originalQueryRequest = _getOriginalRequestQuery(httpMethod,isHttps,host,port,path,params,data,options);

    //Default Options
    var httpOptions = _getHttpOptionsOfRequest(httpMethod,host,port,path,options);

    //Get body
    var body = _request_body.getBodyOfRequest(httpMethod,options,data,httpOptions);
    if (body == null){
        return _rerr.process(errors.ERROR_PARSING_BODY_REQUEST,null,null,callback)
    }

    //Object for trace to return
    var trace = _createTraceOfRequest(httpOptions,isHttps,data,body,options);

    //Log request if required
    _logRequest(trace,options);

    //Generating request
    _executeHttpRequest(httpOptions,isHttps,body,options,originalQueryRequest,trace,callback);
};

/**
 * This function execute a the http request
 */
var _executeHttpRequest = function(httpOptions,isHttps,body,options,originalQueryRequest,trace,callback){
	//Flag for avoid recallback problem found in some node versions
	var callbackit = true;

	//Select correct protocol
	var ohttp = isHttps?https:http;

    //Execute request
    var req = ohttp.request(httpOptions, function(res) {
        if (callbackit){
            callbackit = false;
            _response.r(res,options,trace,function(err,response,statusCode,ttrace,redirection){
                if (err != null){
                    callback(err,response,statusCode,ttrace);
                } else if (redirection !== undefined && redirection!=null){
                    _executeRedirection(redirection,originalQueryRequest,ttrace,callback);
                } else {
                    callback(null,response,statusCode,ttrace);
                }
            });
        }
    });

    //On error request callback handler
    req.on('error', function(e) {
        if (callbackit){
            callbackit = false;
            return _rerr.process(errors.ERROR_CONNECTION_TO_SERVER,null,trace,callback)
        }
    });

    //Implementing timeout on request
    var timeout = (typeof options.timeout == 'undefined')?constants.DEFAULT_TIMEOUT:options.timeout;
    if (timeout > 0){
        req.setTimeout(timeout,function () {
            if (callbackit){
                callbackit = false;
                req.abort();
                return _rerr.process(errors.ERROR_TIMEOUT_ON_REQUEST,null,trace,callback)
            }
        });
    }

    //Req execution
    if (body != null && body != '')
        req.write(body);
    req.end();
}

/**
 * This function is called when a redirection is received from server and we have to repeat
 * request to other new location
 */
var _executeRedirection = function(redirection,originalQueryRequest,trace,callback){
    if (typeof originalQueryRequest.options == 'undefined'){
        originalQueryRequest.options = {}
    }
    if (typeof originalQueryRequest.options.maxRedirects == 'undefined'){
        originalQueryRequest.options.maxRedirects = constants.MAX_REDIRECTION_DEPTH;
    }
    originalQueryRequest.options.maxRedirects--;
    if (originalQueryRequest.options.maxRedirects==0){
        return _rerr.process(errors.ERROR_INFINITE_REDIRECTIONS,null,trace,callback);
    }

    //Redirect
    exports.r(originalQueryRequest.httpMethod,
                redirection.isHttps,
                redirection.host,
                redirection.port,
                redirection.path,
                originalQueryRequest.params,
                originalQueryRequest.data,
                originalQueryRequest.options,
                callback);
};

/**
 * This options return an object with all original data of request. This object will be useful
 * later in redirects
 */
var _getOriginalRequestQuery = function(httpMethod,isHttps,host,port,path,params,data,options){
    return {
        httpMethod: httpMethod,
        isHttps   : isHttps,
        host      : host,
        port      : port,
        path      : path,
        options   : options,
        data      : data,
        params    : params
    }
};

/**
 * This function return http options of request
 */
var _getHttpOptionsOfRequest = function(httpMethod,host,port,path,options){
	options = options || {};

	//Get default options
    var httpOptions = _getHttpDefaultOptions(httpMethod,host,port,path);

    //Check if auth
    _authOptions(httpOptions,options);

    //Adding request headers
    _addRequestHeaders(httpOptions,options);

    //Adding cert
    _addCertOptions(httpOptions,options);

    return httpOptions;
};

/**
 * This funciton return the http request default options
 */
var _getHttpDefaultOptions = function(httpMethod,host,port,path){
    var defOptions = {
        host     : host,
        port     : port,
        path     : path,
        encoding : 'utf8', //default encoding????
        //encoding: 'iso-8859-15',
        method   : httpMethod,
        headers  : {
        	'accept-encoding' : 'gzip, deflate' //DRM. By default we allow this but...
        },
    };

    return defOptions;
};

/**
 * This function add HTTP AUTH options to request if it's configured
 */
var _authOptions = function(httpOptions,options){
    if (typeof options.auth !== 'undefined' && options.auth.type !== undefined){
        switch(options.auth.type){
            case 'basic':
                if (options.auth.username !== undefined && options.auth.password !== undefined ){
                    httpOptions.auth = options.auth.username+':'+options.auth.password;
                }
                break;
        }
    }
};

/**
 * This function add HTTPs Cert options to request if it's configured
 */
var _addCertOptions = function(httpOptions,options){
    if (typeof options.cert !== 'undefined'){
        httpOptions.key = options.cert.key;
        httpOptions.cert = options.cert.cert;
        if (typeof options.cert.ca !== 'undefined'){
            httpOptions.ca = options.cert.ca;
        }
    }
};

/**
 * This function add custom header options given
 */
var _addRequestHeaders = function(httpOptions,options){
    options.headers = options.headers||{};

    for(var headertag in options.headers){
        var cshtag = headertag.toLowerCase();
        if ((cshtag == 'accept-encoding') && (options.headers[headertag] == '')){
            delete httpOptions.headers[cshtag];
            continue;
        }
        httpOptions.headers[cshtag] = options.headers[headertag];
    }
};

/**
 * This function check if trace is enabled in options and in that
 * case create trace object and set req field with information of request.
 * If disabled, return null
 */
var _createTraceOfRequest = function(httpOptions,isHttps,data,rawdata,options){
    if ((options.trace !== undefined && options.trace)||(options.log !== undefined && options.log !== null && options.log.tag !== undefined && options.log.tag != null)){
        return _getTrace(httpOptions,isHttps,data,rawdata,options);
    } else {
        return null;
    }
}

/**
 * This function call to logRequest funciton (if apply)
 */
var _logRequest = function(trace,options){
    if (options.log !== undefined && options.log !== null && options.log.tag !== undefined && options.log.tag != null){
        _log.logRequest(options.log.tag,trace.req)
    }
};

/**
 * Return the trace object created with request data
 */
var _getTrace = function(httpOptions,isHttps,data,rawdata,options){
    var trace = {
        req : {
            protocol : isHttps?'https':'http',
            method   : httpOptions.method,
            host     : httpOptions.host,
            port     : httpOptions.port,
            path     : httpOptions.path,
            encoding : httpOptions.encoding,
            headers  : httpOptions.headers,
            body     : data,
            rawbody  : rawdata,
            timestamp: new Date(),
        }
    }
    return trace;
}<|endoftext|>


<commit_before>/**
 * Module for loading of plugins and external modules. Handles injecting the
 * main Kalabox api into the modules.
 * @module kbox#require
 */

'use strict';

var S = require('string');
var _ = require('lodash');
var assert = require('assert');
var fs = require('fs');
var npm = require('npm');
var path = require('path');
var util = require('util');

var __require = require;

/*
 * Singleton object used to cache previously loaded modules.
 */
var cache = {};

/*
 * Given a callback function, inspect the function's arguments, and return
 * an array of strings representing the names of the function's arguments.
 */
var inspect = function(cb) {
  var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
  var text = cb.toString();
  var rawArgs = text.match(FN_ARGS)[1];
  if (rawArgs === '') {
    return [];
  } else {
    var trim = function(v) { return v.trim(); };
    var args = rawArgs.split(',').map(trim);
    return args;
  }
};

/*
 * Return true if this argument key is a registered dependency.
 */
var isRegisteredArg = function(kbox, key) {
  return kbox.core.deps.contains(key);
};

/*
 * List of argument keys that fit into the NEW way of loading modules.
 */
var validKboxArgs = ['kbox', 'app'];

/*
 * Return true if the argument key fits into the NEW way of loading modules.
 */
var isValidKboxArg = function(key) {
  return _.contains(validKboxArgs, key);
};

/*
 * Map an array of argument keys to an array of values.
 */
var mapArgs = function(obj, keys) {
  var args = _.map(keys, function(key) {
    if (isValidKboxArg(key)) {
      return obj[key];
    } else {
      assert.fail(undefined, undefined, 'This should never happen!');
    }
  });
  return args;
};

/*
 * Apply/map an array of agument keys to a function.
 */
var applyArgs = function(mod, vals, args) {
  var mappedArgs = mapArgs(vals, args);
  return mod.apply(null, mappedArgs);
};

/*
 * Returns a module, that will at some later time have it's api
 * populated when needed dependencies are registered.
 */
var deferLoading = function(kbox, mod, args) {
  var api = {loaded: false};
  kbox.core.events.on('dep-registered', function(context, done) {
    if (context.key === 'app') {
      var vals = {
        app: context.val,
        kbox: kbox
      };
      _.assign(api, applyArgs(mod, vals, args), {loaded: true});
    }
    done();
  });
  return api;
};

/*
 * Subclassed Error for a more useful and specific error.
 */
function KboxRequireError(id, err) {
  Error.call(this);
  this.message = [id, err.message].join(' -> ');
  this.stack = [this.message, err.stack].join('\n');
}
util.inherits(KboxRequireError, Error);

/*
 * A regular run of the mill require, but return null if the
 * module cannot be found.
 */
var requireFromNodeModules = function(id, callback) {
  var mod = null;
  try {
    mod = module.parent.parent.require(id);
  } catch (err) {
    if (!S(err.message).startsWith('Cannot find module')) {
      return callback(new KboxRequireError(id, err));
    }
  }

  callback(null, mod);
};

/*
 * A custom require that searches some directories for the
 * module to be loaded. Null is returned if the module cannot
 * be found.
 */
var requireFromOtherDirs = function(kbox, id, callback) {
  // Use custom search to locate module.
  var filepathsToSearch = kbox.core.deps.call(function(globalConfig) {
    var app = kbox.core.deps.lookup('app', {optional: true});
    var dirs = [
      globalConfig.srcRoot,
      globalConfig.kalaboxRoot
    ];
    if (app) {
      dirs.push(app.config.appRoot);
    }
    var arr = [];
    _.each(dirs, function(dir) {
      [
        'node_modules',
        'plugins'
      ].forEach(function(x) {
        arr.push(path.join(dir, x, id, 'index.js'));
      });
    });
    return arr;
  });
  var filepath = _.find(filepathsToSearch, fs.existsSync);
  var mod = null;
  if (filepath) {
    try {
      mod = require(filepath);
    } catch (err) {
      return callback(new KboxRequireError(filepath, err));
    }
  }
  callback(null, mod);
};

/*
 * Query the npm registry to find out if this module is an npm package.
 */
var isNpmPackage = exports.isNpmPackage = function(id, callback) {
  npm.load(function(err) {
    if (err) {
      callback(err);
    } else {
      var silent = true;
      npm.commands.view([id], silent, function(err, data) {
        if (err) {
          if (S(err.message).startsWith('404 Not Found: ' + id)) {
            callback(null, false);
          } else {
            callback(err);
          }
        } else {
          // @todo: perhaps do some validation of the returned json data.
          callback(null, true);
        }
      });
    }
  });
};

/*
 * Instead of just doing a require, try to load the module from various
 * different places and even try a 'npm install' if it's a valid npm
 * package.
 */
var requireModule = function(kbox, id, callback) {

  // Try to load module from node_modules directory.
  requireFromNodeModules(id, function(err, mod) {
    if (err) {

      callback(err);

    } else if (mod) {

      callback(null, mod);

    } else {

      // Try to load module from other directories.
      requireFromOtherDirs(kbox, id, function(err, mod) {
        if (err) {

          callback(err);

        } else if (mod) {

          callback(null, mod);

        } else {

          // Query npm package registry for this id.
          isNpmPackage(id, function(err, isNpmPackage) {
            if (err) {

              callback(err);

            } else {

              if (!isNpmPackage) {

                // Module could not be loaded, throw an error.
                callback(new Error('Cannot find module ' + id));

              } else {

                // Do an npm install.
                npm.commands.install([id], function(err) {
                  if (err) {

                    callback(err);

                  } else {

                    var mod = null;
                    try {
                      mod = module.parent.parent.require(id);
                    } catch (err) {
                      return callback(err);
                    }
                    callback(null, mod);

                  }
                });

              }

            }
          });

        }
      });

    }
  });

};

/*
 * The main guts of the module.
 */
var requireInternal = function(kbox, id, callback) {

  // Try to load module.
  requireModule(kbox, id, function(err, mod) {
    if (err) {

      callback(err);

    } else {

      // Module was successfully loaded.
      if (typeof mod === 'object') {

        // Nothing extra needs to be done, just return module.
        return callback(null, mod);

      } else if (typeof mod === 'function') {

        if (mod.length === 0) {

          // No arguments, so just execute function and return.
          return callback(null, mod());

        } else {

          // Argument names of the mod function.
          var args = inspect(mod);

          // Mod function has no arguments
          var hasNoArgs = args.length === 0;

          // Mod function only has arguments that are registered dependencies.
          var hasAllRegisteredArgs = (function() {
            var filteredArgs = _.filter(args, function(arg) {
              return isRegisteredArg(kbox, arg);
            });
            return filteredArgs.length === args.length;
          })();

          // Mod function only has arguments that are the NEW way of
          // loading modules.
          var hasAllKboxArgs = (function() {
            var filteredArgs = _.filter(args, isValidKboxArg);
            return filteredArgs.length === args.length;
          })();

          // Mod function uses the app dependency, which may NOT already be
          // known when this code runs.
          var hasAppArg = (function() {
            return _.find(args, function(arg) {
              return arg === 'app';
            });
          })();

          var retVal = null;
          if (hasNoArgs) {

            // No arguments so just execute mod function and return results.
            try {
              retVal = mod();
            } catch (err) {
              return callback(new KboxRequireError(id, err));
            }
            callback(null, retVal);

          } else if (hasAllRegisteredArgs) {

            // Execute mod function with dependencies.
            try {
              retVal = kbox.core.deps.call(mod);
            } catch (err) {
              return callback(new KboxRequireError(id, err));
            }
            callback(null, retVal);

          } else if (hasAllKboxArgs) {

            if (hasAppArg) {

              // Execute mod function but do something a little different since
              // it needs the app dependency which might not be known/registered
              // yet.
              try {
                retVal = deferLoading(kbox, mod, args);
              } catch (err) {
                return callback(new KboxRequireError(id, err));
              }
              callback(null, retVal);

            } else {

              // Execute mod function and return results.
              try {
                retVal = applyArgs(mod, {kbox: kbox}, args);
              } catch (err) {
                return callback(new KboxRequireError(id, err));
              }
              callback(null, retVal);

            }

          } else {

            // We don't know how to init this module, throw an error.
            return callback(new Error('Module ' + id + ' is invalid!'));

          }
        }

      } else {

        // This should never happen so assert fail if it does.
        assert.fail(undefined, undefined,
          'Required module expected to be either function or object');

      }

    }

  });

};

/*
 * Load module and inject kalabox api into module.
 */
exports.require = function(kbox, id, callback) {

  if (typeof callback !== 'function') {
    // Validate callback function.
    throw new TypeError('Invalid callback: ' + typeof callback);
  }

  if (typeof kbox !== 'object') {
    // Validate kbox.
    return callback(new TypeError('Invalid kbox object.'));
  }

  if (typeof id !== 'string') {
    // Validate id.
    return callback(new TypeError('Invalid module id.'));
  }

  if (cache[id]) {
    // Return module from cache.
    callback(null, cache[id]);
  } else {
    // Load module, store in cache, then return.
    requireInternal(kbox, id, function(err, mod) {
      if (err) {
        callback(err);
      } else {
        if (typeof mod !== 'object') {
          mod = {};
        }
        cache[id] = mod;
        callback(null, mod);
      }
    });
  }
};
<commit_msg>Jank bug fix for plugin require.<commit_after>/**
 * Module for loading of plugins and external modules. Handles injecting the
 * main Kalabox api into the modules.
 * @module kbox#require
 */

'use strict';

var S = require('string');
var _ = require('lodash');
var assert = require('assert');
var fs = require('fs');
var npm = require('npm');
var path = require('path');
var util = require('util');

var __require = require;

/*
 * Singleton object used to cache previously loaded modules.
 */
var cache = {};

/*
 * Given a callback function, inspect the function's arguments, and return
 * an array of strings representing the names of the function's arguments.
 */
var inspect = function(cb) {
  var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
  var text = cb.toString();
  var rawArgs = text.match(FN_ARGS)[1];
  if (rawArgs === '') {
    return [];
  } else {
    var trim = function(v) { return v.trim(); };
    var args = rawArgs.split(',').map(trim);
    return args;
  }
};

/*
 * Return true if this argument key is a registered dependency.
 */
var isRegisteredArg = function(kbox, key) {
  return kbox.core.deps.contains(key);
};

/*
 * List of argument keys that fit into the NEW way of loading modules.
 */
var validKboxArgs = ['kbox', 'app'];

/*
 * Return true if the argument key fits into the NEW way of loading modules.
 */
var isValidKboxArg = function(key) {
  return _.contains(validKboxArgs, key);
};

/*
 * Map an array of argument keys to an array of values.
 */
var mapArgs = function(obj, keys) {
  var args = _.map(keys, function(key) {
    if (isValidKboxArg(key)) {
      return obj[key];
    } else {
      assert.fail(undefined, undefined, 'This should never happen!');
    }
  });
  return args;
};

/*
 * Apply/map an array of agument keys to a function.
 */
var applyArgs = function(mod, vals, args) {
  var mappedArgs = mapArgs(vals, args);
  return mod.apply(null, mappedArgs);
};

/*
 * Returns a module, that will at some later time have it's api
 * populated when needed dependencies are registered.
 */
var deferLoading = function(kbox, mod, args) {
  var api = {loaded: false};
  kbox.core.events.on('dep-registered', function(context, done) {
    if (context.key === 'app') {
      var vals = {
        app: context.val,
        kbox: kbox
      };
      _.assign(api, applyArgs(mod, vals, args), {loaded: true});
    }
    done();
  });
  return api;
};

/*
 * Subclassed Error for a more useful and specific error.
 */
function KboxRequireError(id, err) {
  Error.call(this);
  this.message = [id, err.message].join(' -> ');
  this.stack = [this.message, err.stack].join('\n');
}
util.inherits(KboxRequireError, Error);

/*
 * A regular run of the mill require, but return null if the
 * module cannot be found.
 */
var requireFromNodeModules = function(id, callback) {
  var mod = null;
  try {
    mod = module.parent.parent.require(id);
  } catch (err) {
    if (!S(err.message).startsWith('Cannot find module')) {
      return callback(new KboxRequireError(id, err));
    }
  }

  callback(null, mod);
};

/*
 * A custom require that searches some directories for the
 * module to be loaded. Null is returned if the module cannot
 * be found.
 */
var requireFromOtherDirs = function(kbox, id, callback) {
  // Use custom search to locate module.
  var filepathsToSearch = kbox.core.deps.call(function(globalConfig) {
    var app = kbox.core.deps.lookup('app', {optional: true});
    var dirs = [
      globalConfig.srcRoot,
      globalConfig.kalaboxRoot
    ];
    if (app) {
      dirs.push(app.config.appRoot);
    }
    var arr = [];
    _.each(dirs, function(dir) {
      [
        'node_modules',
        'plugins'
      ].forEach(function(x) {
        arr.push(path.join(dir, x, id, 'index.js'));
      });
    });
    return arr;
  });
  var filepath = _.find(filepathsToSearch, fs.existsSync);
  var mod = null;
  if (filepath) {
    try {
      mod = require(filepath);
    } catch (err) {
      return callback(new KboxRequireError(filepath, err));
    }
  }
  callback(null, mod);
};

/*
 * Query the npm registry to find out if this module is an npm package.
 */
var isNpmPackage = exports.isNpmPackage = function(id, callback) {
  npm.load(function(err) {
    if (err) {
      callback(err);
    } else {
      var silent = true;
      npm.commands.view([id], silent, function(err, data) {
        if (err) {
          if (S(err.message).startsWith('404 Not Found: ' + id)) {
            callback(null, false);
          } else {
            callback(err);
          }
        } else {
          // @todo: perhaps do some validation of the returned json data.
          callback(null, true);
        }
      });
    }
  });
};

/*
 * Instead of just doing a require, try to load the module from various
 * different places and even try a 'npm install' if it's a valid npm
 * package.
 */
var requireModule = function(kbox, id, callback) {

  // Try to load module from node_modules directory.
  requireFromNodeModules(id, function(err, mod) {
    if (err) {

      callback(err);

    } else if (mod) {

      callback(null, mod);

    } else {

      // Try to load module from other directories.
      requireFromOtherDirs(kbox, id, function(err, mod) {
        if (err) {

          callback(err);

        } else if (mod) {

          callback(null, mod);

        } else {

          // Query npm package registry for this id.
          isNpmPackage(id, function(err, isNpmPackage) {
            if (err) {

              callback(err);

            } else {

              if (!isNpmPackage) {

                // Module could not be loaded, throw an error.
                callback(new Error('Cannot find module ' + id));

              } else {

                // Do an npm install.
                npm.commands.install([id], function(err) {
                  if (err) {

                    callback(err);

                  } else {

                    var mod = null;
                    try {
                      mod = require(id);
                    } catch (err) {

                      return requireFromOtherDirs(kbox, id, function(err, mod) {
                        if (err) {
                          callback(err);
                          } else {
                            callback(null, mod);
                          }
                      });
                    }
                    callback(null, mod);

                  }
                });

              }

            }
          });

        }
      });

    }
  });

};

/*
 * The main guts of the module.
 */
var requireInternal = function(kbox, id, callback) {

  // Try to load module.
  requireModule(kbox, id, function(err, mod) {
    if (err) {

      callback(err);

    } else {

      // Module was successfully loaded.
      if (typeof mod === 'object') {

        // Nothing extra needs to be done, just return module.
        return callback(null, mod);

      } else if (typeof mod === 'function') {

        if (mod.length === 0) {

          // No arguments, so just execute function and return.
          return callback(null, mod());

        } else {

          // Argument names of the mod function.
          var args = inspect(mod);

          // Mod function has no arguments
          var hasNoArgs = args.length === 0;

          // Mod function only has arguments that are registered dependencies.
          var hasAllRegisteredArgs = (function() {
            var filteredArgs = _.filter(args, function(arg) {
              return isRegisteredArg(kbox, arg);
            });
            return filteredArgs.length === args.length;
          })();

          // Mod function only has arguments that are the NEW way of
          // loading modules.
          var hasAllKboxArgs = (function() {
            var filteredArgs = _.filter(args, isValidKboxArg);
            return filteredArgs.length === args.length;
          })();

          // Mod function uses the app dependency, which may NOT already be
          // known when this code runs.
          var hasAppArg = (function() {
            return _.find(args, function(arg) {
              return arg === 'app';
            });
          })();

          var retVal = null;
          if (hasNoArgs) {

            // No arguments so just execute mod function and return results.
            try {
              retVal = mod();
            } catch (err) {
              return callback(new KboxRequireError(id, err));
            }
            callback(null, retVal);

          } else if (hasAllRegisteredArgs) {

            // Execute mod function with dependencies.
            try {
              retVal = kbox.core.deps.call(mod);
            } catch (err) {
              return callback(new KboxRequireError(id, err));
            }
            callback(null, retVal);

          } else if (hasAllKboxArgs) {

            if (hasAppArg) {

              // Execute mod function but do something a little different since
              // it needs the app dependency which might not be known/registered
              // yet.
              try {
                retVal = deferLoading(kbox, mod, args);
              } catch (err) {
                return callback(new KboxRequireError(id, err));
              }
              callback(null, retVal);

            } else {

              // Execute mod function and return results.
              try {
                retVal = applyArgs(mod, {kbox: kbox}, args);
              } catch (err) {
                return callback(new KboxRequireError(id, err));
              }
              callback(null, retVal);

            }

          } else {

            // We don't know how to init this module, throw an error.
            return callback(new Error('Module ' + id + ' is invalid!'));

          }
        }

      } else {

        // This should never happen so assert fail if it does.
        assert.fail(undefined, undefined,
          'Required module expected to be either function or object');

      }

    }

  });

};

/*
 * Load module and inject kalabox api into module.
 */
exports.require = function(kbox, id, callback) {

  if (typeof callback !== 'function') {
    // Validate callback function.
    throw new TypeError('Invalid callback: ' + typeof callback);
  }

  if (typeof kbox !== 'object') {
    // Validate kbox.
    return callback(new TypeError('Invalid kbox object.'));
  }

  if (typeof id !== 'string') {
    // Validate id.
    return callback(new TypeError('Invalid module id.'));
  }

  if (cache[id]) {
    // Return module from cache.
    callback(null, cache[id]);
  } else {
    // Load module, store in cache, then return.
    requireInternal(kbox, id, function(err, mod) {
      if (err) {
        callback(err);
      } else {
        if (typeof mod !== 'object') {
          mod = {};
        }
        cache[id] = mod;
        callback(null, mod);
      }
    });
  }
};
<|endoftext|>


<commit_before>'use strict';

const
	$C = require('collection.js'),
	Sugar = require('sugar'),
	config = require('./config');

const
	fs = require('fs-extra-promise'),
	path = require('upath');

const
	vinyl = require('vinyl-fs'),
	glob = require('glob-promise'),
	findNodeModules = require('find-node-modules'),
	isPathInside = require('is-path-inside');

const
	cwd = process.cwd(),
	lib = [].concat(findNodeModules({relative: false, cwd})).filter((el) => isPathInside(el, cwd)).slice(-1)[0];

const
	depMap = {},
	dependencies = [],
	rootDependencies = [],
	serverDependencies = [],
	entryDependencies = [];

const
	ignore = [],
	types = [config.projectType];

$C(config.dependencies).forEach((obj) => {
	const
		isStr = Sugar.Object.isString(obj),
		baseSrc = isStr ? obj : obj.src,
		src = path.join(lib, baseSrc);

	if (depMap[baseSrc]) {
		return;
	}

	const decl = depMap[baseSrc] = isStr ?
		{
			src: baseSrc,
			exclude: new Set()
		} :

		{
			...obj,
			exclude: new Set(obj.exclude)
		}
	;

	ignore.push(
		decl.exclude
	);

	let
		cfg = path.join(src, '.pzlrrc');

	if (fs.existsSync(cfg)) {
		cfg = decl.config = $C.extend(true, {}, require('./pzrlrc-default.json'), fs.readJsonSync(cfg));
		entryDependencies.push(path.join(baseSrc, cfg.sourceDir, cfg.entriesDir));

		const
			base = path.join(src, cfg.sourceDir);

		dependencies.push(path.join(base, cfg.blockDir));
		serverDependencies.push(path.join(base, cfg.serverDir));
		rootDependencies.push(base);

	} else {
		decl.config = $C.extend(true, {}, config);
		entryDependencies.push(path.join(baseSrc, config.sourceDir, config.entriesDir));

		const
			base = path.join(src, config.sourceDir);

		dependencies.push(path.join(base, config.blockDir));
		serverDependencies.push(path.join(base, config.serverDir));
		rootDependencies.push(base);
	}

	types.push(cfg.projectType || config.projectType);
});

const
	sourceDir = path.join(cwd, config.sourceDir),
	sourceDirs = [sourceDir].concat(dependencies);

/**
 * Returns an absolute path to a block by the specified name
 *
 * @param {string=} [name]
 * @param {(number|string)=} [skip]
 * @returns {!Promise<(string|Array<string>|{path: (string|Array<string>), lvl: number}|null)>}
 */
async function block(name = '', skip = 0) {
	const
		baseRoot = path.join(sourceDirs[0], config.blockDir);

	if (!name) {
		return baseRoot;
	}

	const
		baseExt = path.extname(name),
		advExt = baseExt ? '' : 'index.js';

	let
		r = sourceDirs,
		ctx;

	if (Sugar.Object.isString(skip)) {
		ctx = skip;
		skip = 0;
	}

	if (config.superRgxp.test(name)) {
		if (!ctx) {
			throw Error('Context for block is not defined');
		}

		name = name.replace(config.superRgxp, '');
		r = r.slice(1);

		for (let i = 0; i < r.length; i++) {
			if (isPathInside(ctx, r[i])) {
				r = r.slice(i + 1);
				break;
			}
		}
	}

	const
		wrap = (path, lvl) => skip ? {path, from: lvl + 1} : path;

	for (let i = skip; i < r.length; i++) {
		if (i && baseExt && ignore[i - 1].has(baseExt)) {
			continue;
		}

		const
			root = r[i],
			file = path.join(name, advExt).replace(/\.logic$/, `.${types[i]}`);

		if (!glob.hasMagic(file) && fs.existsSync(path.join(root, file))) {
			return wrap(path.join(root, baseExt ? file : name), i);
		}

		const
			components = `/**/${file}`,
			virtualComponents = !baseExt && `/**/${name}.index.js`,
			mask = [path.join(root, components)].concat(virtualComponents ? path.join(root, virtualComponents) : []);

		let src;
		await $C(vinyl.src(mask, {read: false})).one.forEach((el, i, data, o) => {
			src = el.path;
			o.cursor.destroy();
		});

		if (src) {
			return wrap(advExt ? path.dirname(src) : src, i);
		}
	}

	return baseExt || skip ? null : path.join(baseRoot, name);
}

/**
 * Returns an absolute path to a block by the specified name
 *
 * @param {string=} [name]
 * @param {number=} [skip]
 * @returns {(string|Array<string>|{path: (string|Array<string>), lvl: number}|null)}
 */
function blockSync(name = '', skip = 0) {
	const
		baseRoot = path.join(sourceDirs[0], config.blockDir);

	if (!name) {
		return baseRoot;
	}

	const
		baseExt = path.extname(name),
		advExt = baseExt ? '' : 'index.js';

	let
		r = sourceDirs,
		ctx;

	if (Sugar.Object.isString(skip)) {
		ctx = skip;
		skip = 0;
	}

	if (config.superRgxp.test(name)) {
		if (!ctx) {
			throw Error('Context for block is not defined');
		}

		name = name.replace(config.superRgxp, '');
		r = r.slice(1);

		for (let i = 0; i < r.length; i++) {
			if (isPathInside(ctx, r[i])) {
				r = r.slice(i + 1);
				break;
			}
		}
	}

	const
		wrap = (path, lvl) => skip ? {path, from: lvl + 1} : path;

	for (let i = skip; i < r.length; i++) {
		if (i && baseExt && ignore[i - 1].has(baseExt)) {
			continue;
		}

		const
			root = r[i],
			file = path.join(name, advExt).replace(/\.logic$/, `.${types[i]}`);

		if (!glob.hasMagic(file) && fs.existsSync(path.join(root, file))) {
			return wrap(path.join(root, baseExt ? file : name), i);
		}

		const
			components = `/**/${file}`,
			virtualComponents = !baseExt && `/**/${name}.index.js`;

		let
			src = glob.sync(path.join(root, components))[0];

		if (!src && virtualComponents) {
			src = glob.sync(path.join(root, virtualComponents))[0];
		}

		if (src) {
			return wrap(advExt ? path.dirname(src) : src, i);
		}
	}

	return baseExt || skip ? null : path.join(baseRoot, name);
}

/**
 * Returns an absolute path to an entry by the specified name
 * @param {string=} [name] - entry name (if empty, returns path to the entries folder)
 */
function entry(name = '') {
	return path.normalize(path.join(sourceDir, config.entriesDir, name ? `${name}.js` : ''));
}

/**
 * Returns true if the specified url is a node module
 *
 * @param {string} url
 * @returns {boolean}
 */
function isNodeModule(url) {
	return !path.isAbsolute(url) && /^[^./\\]/.test(url);
}

module.exports = {
	cwd,
	lib,
	depMap,
	sourceDir,
	sourceDirs,
	dependencies,
	rootDependencies,
	serverDependencies,
	entryDependencies,
	blockSync,
	block,
	entry,
	isNodeModule
};
<commit_msg>Fixed resolving with full dep url<commit_after>'use strict';

const
	$C = require('collection.js'),
	Sugar = require('sugar'),
	config = require('./config');

const
	fs = require('fs-extra-promise'),
	path = require('upath');

const
	vinyl = require('vinyl-fs'),
	glob = require('glob-promise'),
	findNodeModules = require('find-node-modules'),
	isPathInside = require('is-path-inside');

const
	cwd = process.cwd(),
	lib = [].concat(findNodeModules({relative: false, cwd})).filter((el) => isPathInside(el, cwd)).slice(-1)[0];

const
	depMap = {},
	dependencies = [],
	rootDependencies = [],
	serverDependencies = [],
	entryDependencies = [],
	rgxpDependencies = [];

const
	ignore = [],
	types = [config.projectType];

$C(config.dependencies).forEach((obj) => {
	const
		isStr = Sugar.Object.isString(obj),
		baseSrc = isStr ? obj : obj.src,
		src = path.join(lib, baseSrc);

	if (depMap[baseSrc]) {
		return;
	}

	const decl = depMap[baseSrc] = isStr ?
		{
			src: baseSrc,
			exclude: new Set()
		} :

		{
			...obj,
			exclude: new Set(obj.exclude)
		}
	;

	ignore.push(decl.exclude);
	rgxpDependencies.push(new RegExp(`^${Sugar.RegExp.escape(baseSrc)}`));

	let
		cfg = path.join(src, '.pzlrrc');

	if (fs.existsSync(cfg)) {
		cfg = decl.config = $C.extend(true, {}, require('./pzrlrc-default.json'), fs.readJsonSync(cfg));
		entryDependencies.push(path.join(baseSrc, cfg.sourceDir, cfg.entriesDir));

		const
			base = path.join(src, cfg.sourceDir);

		dependencies.push(path.join(base, cfg.blockDir));
		serverDependencies.push(path.join(base, cfg.serverDir));
		rootDependencies.push(base);

	} else {
		decl.config = $C.extend(true, {}, config);
		entryDependencies.push(path.join(baseSrc, config.sourceDir, config.entriesDir));

		const
			base = path.join(src, config.sourceDir);

		dependencies.push(path.join(base, config.blockDir));
		serverDependencies.push(path.join(base, config.serverDir));
		rootDependencies.push(base);
	}

	types.push(cfg.projectType || config.projectType);
});

const
	sourceDir = path.join(cwd, config.sourceDir),
	sourceDirs = [sourceDir].concat(dependencies);

/**
 * Returns an absolute path to a block by the specified name
 *
 * @param {string=} [name]
 * @param {(number|string)=} [skip]
 * @returns {!Promise<(string|Array<string>|{path: (string|Array<string>), lvl: number}|null)>}
 */
async function block(name = '', skip = 0) {
	const
		baseRoot = path.join(sourceDirs[0], config.blockDir);

	if (!name) {
		return baseRoot;
	}

	const
		baseExt = path.extname(name),
		advExt = baseExt ? '' : 'index.js';

	let
		r = sourceDirs,
		ctx;

	if (Sugar.Object.isString(skip)) {
		ctx = skip;
		skip = 0;
	}

	if (config.superRgxp.test(name)) {
		if (!ctx) {
			throw Error('Context for block is not defined');
		}

		name = name.replace(config.superRgxp, '');
		r = r.slice(1);

		for (let i = 0; i < r.length; i++) {
			if (isPathInside(ctx, r[i])) {
				r = r.slice(i + 1);
				break;
			}
		}
	}

	const
		wrap = (path, lvl) => skip ? {path, from: lvl + 1} : path;

	for (let i = skip; i < r.length; i++) {
		if (i && baseExt && ignore[i - 1].has(baseExt)) {
			continue;
		}

		const
			root = r[i],
			file = path.join(name, advExt).replace(/\.logic$/, `.${types[i]}`);

		if (!glob.hasMagic(file) && fs.existsSync(path.join(root, file))) {
			return wrap(path.join(root, baseExt ? file : name), i);
		}

		const
			components = `/**/${file}`,
			virtualComponents = !baseExt && `/**/${name}.index.js`,
			mask = [path.join(root, components)].concat(virtualComponents ? path.join(root, virtualComponents) : []);

		let src;
		await $C(vinyl.src(mask, {read: false})).one.forEach((el, i, data, o) => {
			src = el.path;
			o.cursor.destroy();
		});

		if (src) {
			return wrap(advExt ? path.dirname(src) : src, i);
		}
	}

	for (let i = 0; i < rgxpDependencies.length; i++) {
		const
			root = dependencies[i],
			rgxp = rgxpDependencies[i];

		if (!rgxp.test(name) || baseExt && ignore[i].has(baseExt)) {
			continue;
		}

		const
			realName = name.replace(rgxp, ''),
			file = path.join(realName, advExt).replace(/\.logic$/, `.${types[i]}`);

		if (!glob.hasMagic(file) && fs.existsSync(path.join(root, file))) {
			return path.join(root, baseExt ? file : realName);
		}

		const
			components = `/**/${file}`,
			virtualComponents = !baseExt && `/**/${realName}.index.js`,
			mask = [path.join(root, components)].concat(virtualComponents ? path.join(root, virtualComponents) : []);

		let src;
		await $C(vinyl.src(mask, {read: false})).one.forEach((el, i, data, o) => {
			src = el.path;
			o.cursor.destroy();
		});

		if (src) {
			return advExt ? path.dirname(src) : src;
		}

		break;
	}

	return baseExt || skip ? null : path.join(baseRoot, name);
}

/**
 * Returns an absolute path to a block by the specified name
 *
 * @param {string=} [name]
 * @param {number=} [skip]
 * @returns {(string|Array<string>|{path: (string|Array<string>), lvl: number}|null)}
 */
function blockSync(name = '', skip = 0) {
	const
		baseRoot = path.join(sourceDirs[0], config.blockDir);

	if (!name) {
		return baseRoot;
	}

	const
		baseExt = path.extname(name),
		advExt = baseExt ? '' : 'index.js';

	let
		r = sourceDirs,
		ctx;

	if (Sugar.Object.isString(skip)) {
		ctx = skip;
		skip = 0;
	}

	if (config.superRgxp.test(name)) {
		if (!ctx) {
			throw Error('Context for block is not defined');
		}

		name = name.replace(config.superRgxp, '');
		r = r.slice(1);

		for (let i = 0; i < r.length; i++) {
			if (isPathInside(ctx, r[i])) {
				r = r.slice(i + 1);
				break;
			}
		}
	}

	const
		wrap = (path, lvl) => skip ? {path, from: lvl + 1} : path;

	for (let i = skip; i < r.length; i++) {
		if (i && baseExt && ignore[i - 1].has(baseExt)) {
			continue;
		}

		const
			root = r[i],
			file = path.join(name, advExt).replace(/\.logic$/, `.${types[i]}`);

		if (!glob.hasMagic(file) && fs.existsSync(path.join(root, file))) {
			return wrap(path.join(root, baseExt ? file : name), i);
		}

		const
			components = `/**/${file}`,
			virtualComponents = !baseExt && `/**/${name}.index.js`;

		let
			src = glob.sync(path.join(root, components))[0];

		if (!src && virtualComponents) {
			src = glob.sync(path.join(root, virtualComponents))[0];
		}

		if (src) {
			return wrap(advExt ? path.dirname(src) : src, i);
		}
	}

	for (let i = 0; i < rgxpDependencies.length; i++) {
		const
			root = dependencies[i],
			rgxp = rgxpDependencies[i];

		if (!rgxp.test(name) || baseExt && ignore[i].has(baseExt)) {
			continue;
		}

		const
			realName = name.replace(rgxp, ''),
			file = path.join(realName, advExt).replace(/\.logic$/, `.${types[i]}`);

		if (!glob.hasMagic(file) && fs.existsSync(path.join(root, file))) {
			return path.join(root, baseExt ? file : realName);
		}

		const
			components = `/**/${file}`,
			virtualComponents = !baseExt && `/**/${realName}.index.js`;

		let
			src = glob.sync(path.join(root, components))[0];

		if (!src && virtualComponents) {
			src = glob.sync(path.join(root, virtualComponents))[0];
		}

		if (src) {
			return advExt ? path.dirname(src) : src;
		}

		break;
	}

	return baseExt || skip ? null : path.join(baseRoot, name);
}

/**
 * Returns an absolute path to an entry by the specified name
 * @param {string=} [name] - entry name (if empty, returns path to the entries folder)
 */
function entry(name = '') {
	return path.normalize(path.join(sourceDir, config.entriesDir, name ? `${name}.js` : ''));
}

/**
 * Returns true if the specified url is a node module
 *
 * @param {string} url
 * @returns {boolean}
 */
function isNodeModule(url) {
	return !path.isAbsolute(url) && /^[^./\\]/.test(url);
}

module.exports = {
	cwd,
	lib,
	depMap,
	sourceDir,
	sourceDirs,
	dependencies,
	rootDependencies,
	serverDependencies,
	entryDependencies,
	blockSync,
	block,
	entry,
	isNodeModule
};
<|endoftext|>


<commit_before>require('string').extendPrototype();

var RESTClient  = require('node-rest-client').Client;

exports.API = function(config, callback) {
    var API = {
        request: function(endpoint, data) {
            var url  = getURL(endpoint);
            var args = {
                headers: getHeaders(config),
                data   : data
            };
            doPOST(url, args, callback);
        }
    };

    return API;

    function getURL (endpoint) {
        var url = config.url.toLowerCase().ensureRight('/');
        url = url.ensureRight('Api2.svc/');
        return url + endpoint;
    }

    function getHeaders() {
        return {
            'RpmApiKey': config.key
        };
    }
    var request = null;
    function doPOST(url, args) {

        request = new RESTClient();
        request
            .post(url, args, function(data, response) {
                // console.log('\nRESPONSE ' + url + '\n\n' + JSON.stringify(data) + '\n\n');

                var isError  = true;
                if (data.Result) {
                    isError  = data.Result.Error !== undefined;
                    data     = isError ? data.Result.Error: data.Result;
                }

                if (response.statusCode !== 200) {
                    data = 'HTTP Error: ' + response.statusCode;
                    isError = true;
                }

                if (isError) {
                    callback(data);
                } else {
                    callback(null, data);
                }

                removeRequestListeners();
            })
            .on('error', handleError)
            .on('requestTimeout', handleRequestTimeout)
            .on('responseTimeout', handleResponseTimeout);
    }

    function handleError(err) {
        callback(err);
        removeRequestListeners();
    }
    function handleRequestTimeout(req){
        console.log("Request Timeout", config.url);
        req.abort();
        callback(true);
        removeRequestListeners();
    }
    function handleResponseTimeout(res){
        console.log("responseTimeout", config.url);
        callback(true);
        removeRequestListeners();
    }

    function removeRequestListeners() {
        request.removeListener('error', handleError);
        request.removeListener('requestTimeout', handleRequestTimeout);
        request.removeListener('responseTimeout', handleResponseTimeout);
    }
};
<commit_msg>Enforce the connection time outs when calling EvaluateNextReminders<commit_after>require('string').extendPrototype();

var RESTClient  = require('node-rest-client').Client;

exports.API = function(config, callback) {
    var API = {
        request: function(endpoint, data) {
            var url  = getURL(endpoint);
            var args = {
                headers: getHeaders(config),
                data   : data,
                requestConfig: {
                    timeout: 5000
                }
            };
            doPOST(url, args, callback);
        }
    };

    return API;

    function getURL (endpoint) {
        var url = config.url.toLowerCase().ensureRight('/');
        url = url.ensureRight('Api2.svc/');
        return url + endpoint;
    }

    function getHeaders() {
        return {
            'RpmApiKey': config.key
        };
    }
    var request = null;
    function doPOST(url, args) {
        request = new RESTClient();
        request
            .post(url, args, function(data, response) {
                // console.log('\nRESPONSE ' + url + '\n\n' + JSON.stringify(data) + '\n\n');

                var isError  = true;
                if (data.Result) {
                    isError  = data.Result.Error !== undefined;
                    data     = isError ? data.Result.Error: data.Result;
                }

                if (response.statusCode !== 200) {
                    data = 'HTTP Error: ' + response.statusCode;
                    isError = true;
                }

                if (isError) {
                    callback(data);
                } else {
                    callback(null, data);
                }

                removeRequestListeners();
            })
            .on('error', handleError)
            .on('requestTimeout', handleRequestTimeout);
    }

    function handleError(err) {
        removeRequestListeners();
        if (err.request && err.request.aborted) {
            return;
        }
        if (err.code === 'ECONNRESET') {
            return;
        }
        callback(err);
    }

    function handleRequestTimeout(req){
        req.abort();
        removeRequestListeners();
        callback({Message: 'Request Timeout'});
    }

    function removeRequestListeners() {
        request.removeListener('error', handleError);
        request.removeListener('requestTimeout', handleRequestTimeout);
    }
};
<|endoftext|>


<commit_before>var seret = require('seret');
var manatee = require('manatee');
var supyo = require('supyo');
var Png = require('png');
var async = require('async');

var configuration = null;
var cam = null;
var width;
var height;

module.exports = {
  config: config,
  scanQR: scanQR,
  scanPDF417: scanPDF417,
  scanPhotoID: scanPhotoID
};

function config(_configuration) {
  configuration = _configuration;
  cam = new seret.Camera(configuration.device);
  width = configuration.width;
  height = configuration.height;
  cam.configSet({width: width, height: height});
}

function scanQR(callback) {
  var result = null;
  var t0 = Date.now();

  function noResult() {
    var timeout = Date.now() - t0 > configuration.timeout;
    return !result && !timeout;
  }

  function capture(_callback) {
    cam.capture(function (err) {
      if (err) return _callback(err);
      var image = cam.toGrey();
      result = manatee.scanQR(image, width, height);
      callback();
    });
  }

  cam.start();
  async.whilst(noResult, capture, function(err) {
    cam.stop();
    callback(err, result);
  });
}

function scanPDF417(callback) {
  var result = null;
  var t0 = Date.now();

  function noResult() {
    var timeout = Date.now() - t0 > configuration.timeout;
    return !result && !timeout;
  }

  function capture(_callback) {
    cam.capture(function (err) {
      if (err) return _callback(err);
      var image = cam.toGrey();
      result = manatee.scanPDF417(image, width, height);
      callback();
    });
  }

  cam.start();
  async.whilst(noResult, capture, function(err) {
    cam.stop();
    callback(err, result);
  });
}

function scanPhotoID(callback) {
  var result = null;
  var t0 = Date.now();

  function noResult() {
    var timeout = Date.now() - t0 > configuration.timeout;
    return !result && !timeout;
  }

  function capture(_callback) {
    cam.capture(function (err) {
      if (err) return _callback(err);
      var image = cam.toGrey();
      var detected = supyo.detect(image, width, height);
      if (detected) {
        var rgb = cam.toRGB();
        result = new Png(rgb, width, height);
      }
      callback();
    });
  }

  cam.start();
  async.whilst(noResult, capture, function(err) {
    cam.stop();
    callback(err, result);
  });
}
<commit_msg>feat(scanner) added pairing scan and main QR scan<commit_after>var seret = require('seret');
var manatee = require('manatee');
var supyo = require('supyo');
var Png = require('png');
var async = require('async');
var bitcoinAddressValidator = require ('bitcoin-address');
var PairingData = require('./pairingdata');

var configuration = null;
var cam = null;
var width;
var height;

module.exports = {
  config: config,
  scanPairingCode: scanPairingCode,
  scanMainQR: scanMainQR,
  scanPDF417: scanPDF417,
  scanPhotoID: scanPhotoID
};

function config(_configuration) {
  configuration = _configuration;
  cam = new seret.Camera(configuration.device);
  width = configuration.width;
  height = configuration.height;
  cam.configSet({width: width, height: height});
}

function scanQR(callback) {
  var result = null;
  var t0 = Date.now();

  function noResult() {
    var timeout = Date.now() - t0 > configuration.timeout;
    return !result && !timeout;
  }

  function capture(_callback) {
    cam.capture(function (err) {
      if (err) return _callback(err);
      var image = cam.toGrey();
      result = manatee.scanQR(image, width, height);
      callback();
    });
  }

  cam.start();
  async.whilst(noResult, capture, function(err) {
    cam.stop();
    callback(err, result);
  });
}

function scanPDF417(callback) {
  var result = null;
  var t0 = Date.now();

  function noResult() {
    var timeout = Date.now() - t0 > configuration.timeout;
    return !result && !timeout;
  }

  function capture(_callback) {
    cam.capture(function (err) {
      if (err) return _callback(err);
      var image = cam.toGrey();
      result = manatee.scanPDF417(image, width, height);
      callback();
    });
  }

  cam.start();
  async.whilst(noResult, capture, function(err) {
    cam.stop();
    callback(err, result);
  });
}

function scanPhotoID(callback) {
  var result = null;
  var t0 = Date.now();

  function noResult() {
    var timeout = Date.now() - t0 > configuration.timeout;
    return !result && !timeout;
  }

  function capture(_callback) {
    cam.capture(function (err) {
      if (err) return _callback(err);
      var image = cam.toGrey();
      var detected = supyo.detect(image, width, height);
      if (detected) {
        var rgb = cam.toRGB();
        result = new Png(rgb, width, height);
      }
      callback();
    });
  }

  cam.start();
  async.whilst(noResult, capture, function(err) {
    cam.stop();
    callback(err, result);
  });
}

function scanPairingCode(callback) {
  scanQR(function (err, result) {
    if (err) return callback(err);
    if (!result) return callback(null, null);
    callback(null, PairingData.process(result.toString()));
  });
}

function scanMainQR(callback) {
  scanQR(function (err, result) {
    if (err) return callback(err);
    if (!result) return callback(null, null);
    callback(null, processBitcoinURI(result.toString()));
  });
}

function processBitcoinURI(data) {
  var address = parseBitcoinURI(data);
  if (!address) return null;
  if (!bitcoinAddressValidator.validate(address)) {
    console.log('Invalid bitcoin address: %s', address);
    return null;
  }
  return address;
}

function parseBitcoinURI(uri) {
  var res = /^(bitcoin:\/{0,2})?(\w+)/.exec(uri);
  var address = res && res[2];
  if (!address) {
    return null;
  } else return address;
}
<|endoftext|>


<commit_before>/**
 * @module schemas
 */

exports.message = {
  title: 'message',
  description: 'piece of information envelope into a conversation',
  type: 'object',
  properties: {
    from: {type: 'string'},
    to: {type: 'string'},
    type: {type: 'string'},
    payload: {
      type: 'object',
      description: 'message content',
      properties: {
        err: {},
        content: {}
      },
      required: ['content'],
      additionalProperties: false
    },
    id: {type: 'string'},
    date: {type: 'integer'}
  },
  required: ['to', 'payload', 'id'],
  additionalProperties: false
};
<commit_msg>content not mandatory any more<commit_after>/**
 * @module schemas
 */

exports.message = {
  title: 'message',
  description: 'piece of information envelope into a conversation',
  type: 'object',
  properties: {
    from: {type: 'string'},
    to: {type: 'string'},
    type: {type: 'string'},
    payload: {
      type: 'object',
      description: 'message content',
      properties: {
        err: {},
        content: {}
      },
      additionalProperties: false
    },
    id: {type: 'string'},
    date: {type: 'integer'}
  },
  required: ['to', 'payload', 'id'],
  additionalProperties: false
};
<|endoftext|>


<commit_before>/**
 * Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You
 * may not use this file except in compliance with the License. A copy of
 * the License is located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
 * ANY KIND, either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 */

var AWS = require('./core');
var inherit = AWS.util.inherit;

/**
 * @api private
 */
AWS.Service = inherit({

  constructor: function Service(config) {
    if (!this.loadServiceClass) {
      throw AWS.util.error(new Error(),
        'Service must be constructed with `new\' operator');
    }
    var ServiceClass = this.loadServiceClass(config || {});
    if (ServiceClass) return new ServiceClass(config);
    this.initialize(config);
  },

  initialize: function initialize(config) {
    this.client = this; // backward compatibility with client property
    this.config = new AWS.Config(AWS.config);
    if (config) this.config.update(config, true);
    this.setEndpoint(this.config.endpoint);
  },

  loadServiceClass: function loadServiceClass(serviceConfig) {
    var config = serviceConfig;
    if (!AWS.util.isEmpty(this.api)) {
      return;
    } else if (config.apiConfig) {
      return AWS.Service.defineServiceApi(this.constructor, config.apiConfig);
    } else if (!this.constructor.services) {
      return;
    } else {
      config = new AWS.Config(AWS.config);
      config.update(serviceConfig, true);
      var version = config.apiVersions[this.constructor.serviceIdentifier];
      version = version || config.apiVersion;
      return this.getLatestServiceClass(version);
    }
  },

  getLatestServiceClass: function getLatestServiceClass(version) {
    version = this.getLatestServiceVersion(version);
    if (this.constructor.services[version] === null) {
      AWS.Service.defineServiceApi(this.constructor, version);
    }

    return this.constructor.services[version];
  },

  getLatestServiceVersion: function getLatestServiceVersion(version) {
    /*jshint maxcomplexity:10*/
    if (!this.constructor.services || this.constructor.services.length === 0) {
      throw new Error('No services defined on ' +
                      this.constructor.serviceIdentifier);
    }

    if (!version) {
      version = 'latest';
    } else if (AWS.util.isType(version, Date)) {
      version = AWS.util.date.iso8601(version).split('T')[0];
    }

    if (Object.hasOwnProperty(this.constructor.services, version)) {
      return version;
    }

    var keys = Object.keys(this.constructor.services).sort();
    for (var i = keys.length - 1; i >= 0; i--) {
      if (keys[i] <= version) return keys[i];
    }

    throw new Error('Could not find ' + this.constructor.serviceIdentifier +
                    ' API to satisfy version constraint `' + version + '\'');
  },

  api: {},

  defaultRetryCount: 3,

  makeRequest: function makeRequest(operation, params, callback) {
    if (typeof params === 'function') {
      callback = params;
      params = null;
    }

    params = params || {};
    if (this.config.params) { // copy only toplevel bound params
      var rules = this.api.operations[operation];
      if (rules) {
        params = AWS.util.copy(params);
        AWS.util.each(this.config.params, function(key, value) {
          if (rules.input.members[key]) {
            if (params[key] === undefined || params[key] === null) {
              params[key] = value;
            }
          }
        });
      }
    }

    var request = new AWS.Request(this, operation, params);
    this.addAllRequestListeners(request);

    if (callback) request.send(callback);
    return request;
  },

  addAllRequestListeners: function addAllRequestListeners(request) {
    var list = [AWS.events, AWS.EventListeners.Core,
                this.serviceInterface()];
    for (var i = 0; i < list.length; i++) {
      if (list[i]) request.addListeners(list[i]);
    }

    // disable parameter validation
    if (!this.config.paramValidation) {
      request.removeListener('validate',
        AWS.EventListeners.Core.VALIDATE_PARAMETERS);
    }

    this.setupRequestListeners(request);
  },

  setupRequestListeners: function setupRequestListeners() {
  },

  serviceInterface: function serviceInterface() {
    /*jshint maxcomplexity:8*/
    switch (this.api.format) {
      case 'query': return AWS.EventListeners.Query;
      case 'json': return AWS.EventListeners.Json;
      case 'rest-json': return AWS.EventListeners.RestJson;
      case 'rest-xml': return AWS.EventListeners.RestXml;
    }
    if (this.api.format) {
      throw new Error('Invalid service `format\' ' +
        this.api.format + ' in API config');
    }
  },

  successfulResponse: function successfulResponse(resp) {
    return resp.httpResponse.statusCode < 300;
  },

  /**
   * How many times a failed request should be retried before giving up.
   * the defaultRetryCount can be overriden by service classes.
   */
  numRetries: function numRetries() {
    if (this.config.maxRetries !== undefined) {
      return this.config.maxRetries;
    } else {
      return this.defaultRetryCount;
    }
  },

  retryDelays: function retryDelays() {
    var retryCount = this.numRetries();
    var delays = [];
    for (var i = 0; i < retryCount; ++i) {
      delays[i] = Math.pow(2, i) * 30;
    }
    return delays;
  },

  retryableError: function retryableError(error) {
    if (this.networkingError(error)) return true;
    if (this.expiredCredentialsError(error)) return true;
    if (this.throttledError(error)) return true;
    if (error.statusCode >= 500) return true;
    return false;
  },

  networkingError: function networkingError(error) {
    return error.code == 'NetworkingError';
  },

  expiredCredentialsError: function expiredCredentialsError(error) {
    // TODO : this only handles *one* of the expired credential codes
    return (error.code === 'ExpiredTokenException');
  },

  throttledError: function throttledError(error) {
    // this logic varies between services
    return (error.code == 'ProvisionedThroughputExceededException');
  },

  setEndpoint: function setEndpoint(endpoint) {
    if (endpoint) {
      this.endpoint = new AWS.Endpoint(endpoint, this.config);
    } else if (this.api.globalEndpoint) {
      this.endpoint = new AWS.Endpoint(this.api.globalEndpoint, this.config);
    } else {
      var host = this.api.endpointPrefix + '.' + this.config.region + '.amazonaws.com';
      this.endpoint = new AWS.Endpoint(host, this.config);
    }
  },

  paginationConfig: function paginationConfig(operation, throwException) {
    function fail(name) {
      if (throwException) {
        var e = new Error();
        throw AWS.util.error(e, 'No pagination configuration for ' + name);
      }
      return null;
    }

    if (!this.api.pagination) return fail('service');
    if (!this.api.pagination[operation]) return fail(operation);
    return this.api.pagination[operation];
  }
});

AWS.util.update(AWS.Service, {

  /**
   * Adds one method for each operation described in the api configuration
   */
  defineMethods: function defineMethods(svc) {
    AWS.util.each(svc.prototype.api.operations, function iterator(method) {
      if (svc.prototype[method]) return;
      svc.prototype[method] = function (params, callback) {
        return this.makeRequest(method, params, callback);
      };
    });
  },

  defineService: function defineService(serviceIdentifier, versions, features) {
    if (!AWS.util.isType(versions, Array)) {
      features = versions;
      versions = [];
    }

    var svc = inherit(AWS.Service, features || {});
    svc.Client = svc; // backward compatibility for Client class

    if (typeof serviceIdentifier === 'string') {
      // create versions hash
      var services = {};
      for (var i = 0; i < versions.length; i++) {
        services[versions[i]] = null;
      }

      svc.services = svc.services || services;
      svc.serviceIdentifier = svc.serviceIdentifier || serviceIdentifier;
    } else { // defineService called with an API
      svc.prototype.api = serviceIdentifier;
      AWS.Service.defineMethods(svc);
    }

    return svc;
  },

  defineServiceApi: function defineServiceApi(superclass, version) {
    var svc = inherit(superclass, {
      serviceIdentifier: superclass.serviceIdentifier
    });

    if (typeof version === 'string') {
      var apiFile = superclass.serviceIdentifier + '-' + version;
      try {
        svc.prototype.api = require('./services/api/' + apiFile);
      } catch (err) {
        throw AWS.util.error(err, {
          message: 'Could not find API configuration ' + apiFile
        });
      }
      superclass.services[version] = svc;
    } else {
      svc.prototype.api = version;
    }

    AWS.Service.defineMethods(svc);
    return svc;
  }
});
<commit_msg>Allow service API to be defined directly with apiConfig object<commit_after>/**
 * Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You
 * may not use this file except in compliance with the License. A copy of
 * the License is located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
 * ANY KIND, either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 */

var AWS = require('./core');
var inherit = AWS.util.inherit;

/**
 * @api private
 */
AWS.Service = inherit({

  constructor: function Service(config) {
    if (!this.loadServiceClass) {
      throw AWS.util.error(new Error(),
        'Service must be constructed with `new\' operator');
    }
    var ServiceClass = this.loadServiceClass(config || {});
    if (ServiceClass) return new ServiceClass(config);
    this.initialize(config);
  },

  initialize: function initialize(config) {
    this.client = this; // backward compatibility with client property
    this.config = new AWS.Config(AWS.config);
    if (config) this.config.update(config, true);
    this.setEndpoint(this.config.endpoint);
  },

  loadServiceClass: function loadServiceClass(serviceConfig) {
    var config = serviceConfig;
    if (!AWS.util.isEmpty(this.api)) {
      return;
    } else if (config.apiConfig) {
      return AWS.Service.defineServiceApi(this.constructor, config.apiConfig);
    } else if (!this.constructor.services) {
      return;
    } else {
      config = new AWS.Config(AWS.config);
      config.update(serviceConfig, true);
      var version = config.apiVersions[this.constructor.serviceIdentifier];
      version = version || config.apiVersion;
      return this.getLatestServiceClass(version);
    }
  },

  getLatestServiceClass: function getLatestServiceClass(version) {
    version = this.getLatestServiceVersion(version);
    if (this.constructor.services[version] === null) {
      AWS.Service.defineServiceApi(this.constructor, version);
    }

    return this.constructor.services[version];
  },

  getLatestServiceVersion: function getLatestServiceVersion(version) {
    /*jshint maxcomplexity:10*/
    if (!this.constructor.services || this.constructor.services.length === 0) {
      throw new Error('No services defined on ' +
                      this.constructor.serviceIdentifier);
    }

    if (!version) {
      version = 'latest';
    } else if (AWS.util.isType(version, Date)) {
      version = AWS.util.date.iso8601(version).split('T')[0];
    }

    if (Object.hasOwnProperty(this.constructor.services, version)) {
      return version;
    }

    var keys = Object.keys(this.constructor.services).sort();
    for (var i = keys.length - 1; i >= 0; i--) {
      if (keys[i] <= version) return keys[i];
    }

    throw new Error('Could not find ' + this.constructor.serviceIdentifier +
                    ' API to satisfy version constraint `' + version + '\'');
  },

  api: {},

  defaultRetryCount: 3,

  makeRequest: function makeRequest(operation, params, callback) {
    if (typeof params === 'function') {
      callback = params;
      params = null;
    }

    params = params || {};
    if (this.config.params) { // copy only toplevel bound params
      var rules = this.api.operations[operation];
      if (rules) {
        params = AWS.util.copy(params);
        AWS.util.each(this.config.params, function(key, value) {
          if (rules.input.members[key]) {
            if (params[key] === undefined || params[key] === null) {
              params[key] = value;
            }
          }
        });
      }
    }

    var request = new AWS.Request(this, operation, params);
    this.addAllRequestListeners(request);

    if (callback) request.send(callback);
    return request;
  },

  addAllRequestListeners: function addAllRequestListeners(request) {
    var list = [AWS.events, AWS.EventListeners.Core,
                this.serviceInterface()];
    for (var i = 0; i < list.length; i++) {
      if (list[i]) request.addListeners(list[i]);
    }

    // disable parameter validation
    if (!this.config.paramValidation) {
      request.removeListener('validate',
        AWS.EventListeners.Core.VALIDATE_PARAMETERS);
    }

    this.setupRequestListeners(request);
  },

  setupRequestListeners: function setupRequestListeners() {
  },

  serviceInterface: function serviceInterface() {
    /*jshint maxcomplexity:8*/
    switch (this.api.format) {
      case 'query': return AWS.EventListeners.Query;
      case 'json': return AWS.EventListeners.Json;
      case 'rest-json': return AWS.EventListeners.RestJson;
      case 'rest-xml': return AWS.EventListeners.RestXml;
    }
    if (this.api.format) {
      throw new Error('Invalid service `format\' ' +
        this.api.format + ' in API config');
    }
  },

  successfulResponse: function successfulResponse(resp) {
    return resp.httpResponse.statusCode < 300;
  },

  /**
   * How many times a failed request should be retried before giving up.
   * the defaultRetryCount can be overriden by service classes.
   */
  numRetries: function numRetries() {
    if (this.config.maxRetries !== undefined) {
      return this.config.maxRetries;
    } else {
      return this.defaultRetryCount;
    }
  },

  retryDelays: function retryDelays() {
    var retryCount = this.numRetries();
    var delays = [];
    for (var i = 0; i < retryCount; ++i) {
      delays[i] = Math.pow(2, i) * 30;
    }
    return delays;
  },

  retryableError: function retryableError(error) {
    if (this.networkingError(error)) return true;
    if (this.expiredCredentialsError(error)) return true;
    if (this.throttledError(error)) return true;
    if (error.statusCode >= 500) return true;
    return false;
  },

  networkingError: function networkingError(error) {
    return error.code == 'NetworkingError';
  },

  expiredCredentialsError: function expiredCredentialsError(error) {
    // TODO : this only handles *one* of the expired credential codes
    return (error.code === 'ExpiredTokenException');
  },

  throttledError: function throttledError(error) {
    // this logic varies between services
    return (error.code == 'ProvisionedThroughputExceededException');
  },

  setEndpoint: function setEndpoint(endpoint) {
    if (endpoint) {
      this.endpoint = new AWS.Endpoint(endpoint, this.config);
    } else if (this.api.globalEndpoint) {
      this.endpoint = new AWS.Endpoint(this.api.globalEndpoint, this.config);
    } else {
      var host = this.api.endpointPrefix + '.' + this.config.region + '.amazonaws.com';
      this.endpoint = new AWS.Endpoint(host, this.config);
    }
  },

  paginationConfig: function paginationConfig(operation, throwException) {
    function fail(name) {
      if (throwException) {
        var e = new Error();
        throw AWS.util.error(e, 'No pagination configuration for ' + name);
      }
      return null;
    }

    if (!this.api.pagination) return fail('service');
    if (!this.api.pagination[operation]) return fail(operation);
    return this.api.pagination[operation];
  }
});

AWS.util.update(AWS.Service, {

  /**
   * Adds one method for each operation described in the api configuration
   */
  defineMethods: function defineMethods(svc) {
    AWS.util.each(svc.prototype.api.operations, function iterator(method) {
      if (svc.prototype[method]) return;
      svc.prototype[method] = function (params, callback) {
        return this.makeRequest(method, params, callback);
      };
    });
  },

  defineService: function defineService(serviceIdentifier, versions, features) {
    if (!AWS.util.isType(versions, Array)) {
      features = versions;
      versions = [];
    }

    var svc = inherit(AWS.Service, features || {});
    svc.Client = svc; // backward compatibility for Client class

    if (typeof serviceIdentifier === 'string') {
      // create versions hash
      var services = {};
      for (var i = 0; i < versions.length; i++) {
        services[versions[i]] = null;
      }

      svc.services = svc.services || services;
      svc.serviceIdentifier = svc.serviceIdentifier || serviceIdentifier;
    } else { // defineService called with an API
      svc.prototype.api = serviceIdentifier;
      AWS.Service.defineMethods(svc);
    }

    return svc;
  },

  defineServiceApi: function defineServiceApi(superclass, version, apiConfig) {
    var svc = inherit(superclass, {
      serviceIdentifier: superclass.serviceIdentifier
    });

    if (typeof version === 'string') {
      var apiFile = superclass.serviceIdentifier + '-' + version;
      try {
        svc.prototype.api = apiConfig || require('./services/api/' + apiFile);
      } catch (err) {
        throw AWS.util.error(err, {
          message: 'Could not find API configuration ' + apiFile
        });
      }
      superclass.services[version] = svc;
    } else {
      svc.prototype.api = version;
    }

    AWS.Service.defineMethods(svc);
    return svc;
  }
});
<|endoftext|>


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

var path = require('path');
var Asset = require('./asset');
var debug = require('debug')('micromono:service');
var Router = require('./router');
var assign = require('lodash.assign');
var extend = require('ampersand-class-extend');
var express = require('express');
var getFnArgs = require('./helper').getFnArgs;
var randomPort = require('random-port');
var EventEmitter = require('eventemitter3');

/**
 * Module constants
 */

var RPC_PORT_RANGE = {
  from: 16262,
  range: 10000
};
var WEB_PORT_RANGE = {
  from: 36262,
  range: 10000
};

/**
 * MicroMono Service class constructor.
 */
var Service = module.exports = function MicroMonoService() {
  if (!this.packagePath) {
    throw new Error('Please provide `packagePath` property when you extend Service class');
  }

  var packagePath = this.packagePath;
  this.packageInfo = require(path.join(packagePath, 'package.json'));

  this.emitter = new EventEmitter();

  this.announcement = {
    name: this.packageInfo.name,
    version: this.packageInfo.version
  };

  if (this.packageInfo.jspm) {
    // found jspm info, initialize Asset
    this.asset = new Asset(this.packagePath);
    this.announcement.client = this.asset.parseJSPM();
  }

  if (this.route || this.asset || this.middleware) {
    var router = new Router(this);
    this.router = router;
    this.app = router.routeApp;
  }

  this.baseUrl = this.baseUrl || '/';
  this.announcement.baseUrl = this.baseUrl;

  var api = getAPIInfo(this);
  if (Object.keys(api).length > 0) {
    this.announcement.api = api;
    assign(this, require('./rpc/socket.io').server);
  }

  if (this.use) {
    this.announcement.use = this.use;
  }

  if (this.upgradeUrl) {
    this.allowUpgrade(this.upgradeUrl);
  }

  if (!this.init) {
    this.init = function() {
      return Promise.resolve();
    };
  }
};

// Provide the ability to extend the Service class.
Service.extend = extend;

/**
 * MicroMono Service public API.
 *
 * @type {Object}
 */
Service.prototype = {

  isRemote: function() {
    return false;
  },

  express: function(app) {
    var router = this.router;

    if (router) {
      if (router.assetApp) {
        app.use(router.assetApp);
      }

      if (router.routeApp) {
        app.use(this.baseUrl, router.routeApp);
      }

      if (router.middlewareApp) {
        app.use(router.middlewareApp);
      }
    }
  },

  on: function(event, callback) {
    if (event === 'server') {
      if (this.server) {
        return callback(this.server);
      }
    }

    this.emitter.on(event, callback);
  },

  setHttpServer: function(server) {
    this.server = server;
    this.emitter.emit('server', server);
  },

  allowUpgrade: function(upgradeUrl) {
    if (upgradeUrl === undefined) {
      return this.announcement.allowUpgrade;
    } else if (upgradeUrl) {
      upgradeUrl = path.join(this.baseUrl, upgradeUrl);
    }
    return (this.announcement.allowUpgrade = upgradeUrl);
  },

  startWebServer: function(host) {
    if (!this.asset && !this.router) {
      // no need to start http server if we don't have asset or route to serve.
      return Promise.resolve();
    }

    if (typeof host === 'function') {
      // host is an express instance, the main app
      this.mainApp = host;
      this.express(host);
      // we don't need to call the listen function since it will be handled externally
      return Promise.resolve();
    }

    var self = this;
    return this.getRandomPort(WEB_PORT_RANGE).then(function(port) {
      var mainApp = express();
      self.express(mainApp);
      self.mainApp = mainApp;
      self.announcement.webPort = port;

      var promise = new Promise(function(resolve, reject) {
        self.server = mainApp.listen(port, host, function(error) {
          error ? reject(error) : resolve();
        });
        self.setHttpServer(self.server);
      });

      return promise;
    });
  },

  getRandomPort: function(range) {
    range = range || RPC_PORT_RANGE;
    var promise = new Promise(function(resolve, reject) {
      randomPort(range, function(port) {
        resolve(port);
      });
    });
    return promise;
  },

  encodeData: function(data) {
    return JSON.stringify(data);
  },

  decodeData: function(msg) {
    return JSON.parse(msg);
  },

  getHandler: function(name) {
    return this.announcement.api[name].handler;
  },

  run: function(app) {
    var self = this;

    var promise = this.startWebServer(app)
      .then(function() {
        return self.init();
      })
      .then(function() {
        if (self.router) {
          // load route handlers
          self.announcement.route = self.router.getRoutes();
          self.announcement.middleware = self.router.getMiddlewares();
        }
      })
      .then(function() {
        if (self.announcement.api) {
          return self.getRandomPort().then(function(port) {
            return self.startRPCServer(port);
          });
        }
      })
      .then(function() {
        debug('Service "%s" started with following service info: ', self.announcement.name);
        debug(self.announcement);
      });

    return promise;
  }
};

/**
 * MicroMonoService private functions.
 */

var API_BLACK_LIST = ['constructor', 'init', 'app'].concat(Object.keys(Service.prototype));

/**
 * [getAPIInfo description]
 * @return {Object} [description]
 */
function getAPIInfo(service) {
  var _apis = {};
  for (var name in service) {
    if (API_BLACK_LIST.indexOf(name) === -1 && name[0] !== '_') {
      var fn = service[name];
      if (typeof fn === 'function') {
        var args = getFnArgs(fn);
        _apis[name] = {
          name: name,
          args: args,
          handler: fn
        };
      }
    }
  }
  return _apis;
}
<commit_msg>Allow override service name by using `Service#name`.<commit_after>/**
 * Module dependencies.
 */

var path = require('path');
var Asset = require('./asset');
var debug = require('debug')('micromono:service');
var Router = require('./router');
var assign = require('lodash.assign');
var extend = require('ampersand-class-extend');
var express = require('express');
var getFnArgs = require('./helper').getFnArgs;
var randomPort = require('random-port');
var EventEmitter = require('eventemitter3');

/**
 * Module constants
 */

var RPC_PORT_RANGE = {
  from: 16262,
  range: 10000
};
var WEB_PORT_RANGE = {
  from: 36262,
  range: 10000
};

/**
 * MicroMono Service class constructor.
 */
var Service = module.exports = function MicroMonoService() {
  if (!this.packagePath) {
    throw new Error('Please provide `packagePath` property when you extend Service class');
  }

  var packagePath = this.packagePath;
  this.packageInfo = require(path.join(packagePath, 'package.json'));

  this.emitter = new EventEmitter();

  this.announcement = {
    name: this.name || this.packageInfo.name,
    version: this.packageInfo.version
  };

  if (this.packageInfo.jspm) {
    // found jspm info, initialize Asset
    this.asset = new Asset(this.packagePath);
    this.announcement.client = this.asset.parseJSPM();
  }

  if (this.route || this.asset || this.middleware) {
    var router = new Router(this);
    this.router = router;
    this.app = router.routeApp;
  }

  this.baseUrl = this.baseUrl || '/';
  this.announcement.baseUrl = this.baseUrl;

  var api = getAPIInfo(this);
  if (Object.keys(api).length > 0) {
    this.announcement.api = api;
    assign(this, require('./rpc/socket.io').server);
  }

  if (this.use) {
    this.announcement.use = this.use;
  }

  if (this.upgradeUrl) {
    this.allowUpgrade(this.upgradeUrl);
  }

  if (!this.init) {
    this.init = function() {
      return Promise.resolve();
    };
  }
};

// Provide the ability to extend the Service class.
Service.extend = extend;

/**
 * MicroMono Service public API.
 *
 * @type {Object}
 */
Service.prototype = {

  isRemote: function() {
    return false;
  },

  express: function(app) {
    var router = this.router;

    if (router) {
      if (router.assetApp) {
        app.use(router.assetApp);
      }

      if (router.routeApp) {
        app.use(this.baseUrl, router.routeApp);
      }

      if (router.middlewareApp) {
        app.use(router.middlewareApp);
      }
    }
  },

  on: function(event, callback) {
    if (event === 'server') {
      if (this.server) {
        return callback(this.server);
      }
    }

    this.emitter.on(event, callback);
  },

  setHttpServer: function(server) {
    this.server = server;
    this.emitter.emit('server', server);
  },

  allowUpgrade: function(upgradeUrl) {
    if (upgradeUrl === undefined) {
      return this.announcement.allowUpgrade;
    } else if (upgradeUrl) {
      upgradeUrl = path.join(this.baseUrl, upgradeUrl);
    }
    return (this.announcement.allowUpgrade = upgradeUrl);
  },

  startWebServer: function(host) {
    if (!this.asset && !this.router) {
      // no need to start http server if we don't have asset or route to serve.
      return Promise.resolve();
    }

    if (typeof host === 'function') {
      // host is an express instance, the main app
      this.mainApp = host;
      this.express(host);
      // we don't need to call the listen function since it will be handled externally
      return Promise.resolve();
    }

    var self = this;
    return this.getRandomPort(WEB_PORT_RANGE).then(function(port) {
      var mainApp = express();
      self.express(mainApp);
      self.mainApp = mainApp;
      self.announcement.webPort = port;

      var promise = new Promise(function(resolve, reject) {
        self.server = mainApp.listen(port, host, function(error) {
          error ? reject(error) : resolve();
        });
        self.setHttpServer(self.server);
      });

      return promise;
    });
  },

  getRandomPort: function(range) {
    range = range || RPC_PORT_RANGE;
    var promise = new Promise(function(resolve, reject) {
      randomPort(range, function(port) {
        resolve(port);
      });
    });
    return promise;
  },

  encodeData: function(data) {
    return JSON.stringify(data);
  },

  decodeData: function(msg) {
    return JSON.parse(msg);
  },

  getHandler: function(name) {
    return this.announcement.api[name].handler;
  },

  run: function(app) {
    var self = this;

    var promise = this.startWebServer(app)
      .then(function() {
        return self.init();
      })
      .then(function() {
        if (self.router) {
          // load route handlers
          self.announcement.route = self.router.getRoutes();
          self.announcement.middleware = self.router.getMiddlewares();
        }
      })
      .then(function() {
        if (self.announcement.api) {
          return self.getRandomPort().then(function(port) {
            return self.startRPCServer(port);
          });
        }
      })
      .then(function() {
        debug('Service "%s" started with following service info: ', self.announcement.name);
        debug(self.announcement);
      });

    return promise;
  }
};

/**
 * MicroMonoService private functions.
 */

var API_BLACK_LIST = ['constructor', 'init', 'app'].concat(Object.keys(Service.prototype));

/**
 * [getAPIInfo description]
 * @return {Object} [description]
 */
function getAPIInfo(service) {
  var _apis = {};
  for (var name in service) {
    if (API_BLACK_LIST.indexOf(name) === -1 && name[0] !== '_') {
      var fn = service[name];
      if (typeof fn === 'function') {
        var args = getFnArgs(fn);
        _apis[name] = {
          name: name,
          args: args,
          handler: fn
        };
      }
    }
  }
  return _apis;
}
<|endoftext|>