<commit_before>'use strict';
var BPromise = require('bluebird');
var PouchDB = require('pouchdb');
var util = require('./../util');
var seed = require('pouchdb-seed-design');
var request = require('superagent');

module.exports = function (config, userDB, couchAuthDB) {

  var cloudant = config.getItem('dbServer.cloudant');

  var adapter;

  if(cloudant) {
    adapter = require('./cloudant');
  } else {
    var CouchAdapter = require('./couchdb');
    adapter = new CouchAdapter(couchAuthDB);
  }

  this.storeKey = function (username, key, password, expires, roles) {
    return adapter.storeKey(username, key, password, expires, roles);
  };

  this.removeKeys = function(keys) {
    return adapter.removeKeys(keys);
  };

  this.authorizeKeys = function (user_id, db, keys, permissions, roles) {
    return adapter.authorizeKeys(user_id, db, keys, permissions, roles);
  };

  this.deauthorizeKeys = function (db, keys) {
    return adapter.deauthorizeKeys(db, keys);
  };

  this.authorizeUserSessions = function(user_id, personalDBs, sessionKeys, roles) {
    var self = this;
    var promises = [];
    sessionKeys = util.toArray(sessionKeys);
    Object.keys(personalDBs).forEach(function(personalDB) {
      var permissions = personalDBs[personalDB].permissions;
      if(!permissions) {
        permissions = config.getItem('userDBs.model.' + personalDBs[personalDB].name + '.permissions') || config.getItem('userDBs.model._default.permissions') || [];
      }
      var db = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + personalDB);
      promises.push(self.authorizeKeys(user_id, db, sessionKeys, permissions, roles));
    });
    return BPromise.all(promises);
  };

  this.addUserDB = function (userDoc, dbName, designDocs, type, permissions, adminRoles, memberRoles) {
    var self = this;
    var promises = [];
    adminRoles = adminRoles || [];
    memberRoles = memberRoles || [];
    // Create and the database and seed it if a designDoc is specified
    var prefix = config.getItem('userDBs.privatePrefix') ? config.getItem('userDBs.privatePrefix') + '_' : '';
    var finalDBName, newDB;
    // Make sure we have a legal database name
    var username = userDoc._id;
    username = getLegalDBName(username);
    if(type === 'shared') {
      finalDBName = dbName;
    } else {
      finalDBName = prefix + dbName + '$' + username;
    }
    return self.createDB(finalDBName)
      .then(function() {
        newDB = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + finalDBName);
        return adapter.initSecurity(newDB, adminRoles, memberRoles);
      })
      .then(function() {
        // Seed the design docs
        if (designDocs && designDocs instanceof Array) {
          designDocs.forEach(function(ddName) {
            var dDoc = self.getDesignDoc(ddName);
            if(dDoc) {
              promises.push(seed(newDB, dDoc));
            } else {
              console.warn('Failed to locate design doc: ' + ddName);
            }
          });
        }
        // Authorize the user's existing DB keys to access the new database
        var keysToAuthorize = [];
        if (userDoc.session) {
          for (var key in userDoc.session) {
            if(userDoc.session.hasOwnProperty(key) && userDoc.session[key].expires > Date.now()) {
              keysToAuthorize.push(key);
            }
          }
        }
        if (keysToAuthorize.length > 0) {
          promises.push(self.authorizeKeys(userDoc._id, newDB, keysToAuthorize, permissions, userDoc.roles));
        }
        return BPromise.all(promises);
      })
      .then(function() {
        return BPromise.resolve(finalDBName);
      });
  };

  this.removeExpiredKeys = function () {
    var self = this;
    var keysByUser = {};
    var userDocs = {};
    var expiredKeys = [];
    // query a list of expired keys by user
    return userDB.query('auth/expiredKeys', {endkey: Date.now(), include_docs: true})
      .then(function(results) {
        // group by user
        results.rows.forEach(function(row) {
          keysByUser[row.value.user] = row.value.key;
          expiredKeys.push(row.value.key);
          // Add the user doc if it doesn't already exist
          if(typeof userDocs[row.value.user] === 'undefined') {
            userDocs[row.value.user] = row.doc;
          }
          // remove each key from user.session
          if(userDocs[row.value.user].session) {
            Object.keys(userDocs[row.value.user].session).forEach(function(session) {
              if(row.value.key === session) {
                delete userDocs[row.value.user].session[session];
              }
            });
          }
        });
        return self.removeKeys(expiredKeys);
      })
      .then(function() {
        // - deauthorize keys for each personal database of each user
        var deauthorize = [];
        Object.keys(keysByUser).forEach(function(user) {
          deauthorize.push(self.deauthorizeUser(userDocs[user], keysByUser[user]));
        });
        return BPromise.all(deauthorize);
      })
      .then(function() {
        var userUpdates = [];
        Object.keys(userDocs).forEach(function(user) {
          userUpdates.push(userDocs[user]);
        });
        // Bulk save user doc updates
        return userDB.bulkDocs(userUpdates);
      })
      .then(function() {
        return BPromise.resolve(expiredKeys);
      });
  };

  this.deauthorizeUser = function(userDoc, keys) {
    var self = this;
    var promises = [];
    // If keys is not specified we will deauthorize all of the users sessions
    if(!keys) {
      keys = util.getSessions(userDoc);
    }
    keys = util.toArray(keys);
    if(userDoc.personalDBs && typeof userDoc.personalDBs === 'object') {
      Object.keys(userDoc.personalDBs).forEach(function(personalDB) {
        var db = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + personalDB);
        promises.push(self.deauthorizeKeys(db, keys));
      });
      return BPromise.all(promises);
    } else {
      return BPromise.resolve(false);
    }
  };

  this.getDesignDoc = function(docName) {
    if(!docName) {
      return null;
    }
    var designDoc;
    var designDocDir = config.getItem('userDBs.designDocDir');
    if(!designDocDir) {
      designDocDir = __dirname;
    }
    try {
      designDoc = require(designDocDir + '/' + docName);
    }
    catch(err) {
      console.warn('Design doc: ' + designDocDir + '/' + docName + ' not found.');
      designDoc = null;
    }
    return designDoc;
  };

  this.getDBConfig = function(dbName, type) {
    var dbConfig = {
      name: dbName
    };
    dbConfig.adminRoles = config.getItem('userDBs.defaultSecurityRoles.admins') || [];
    dbConfig.memberRoles = config.getItem('userDBs.defaultSecurityRoles.members') || [];
    var dbConfigRef = 'userDBs.model.' + dbName;
    if(config.getItem(dbConfigRef)) {
      dbConfig.permissions = config.getItem(dbConfigRef + '.permissions') || [];
      dbConfig.designDocs = config.getItem(dbConfigRef + '.designDocs') || [];
      dbConfig.type = type || config.getItem(dbConfigRef + '.type') || 'private';
      var dbAdminRoles = config.getItem(dbConfigRef + '.adminRoles');
      var dbMemberRoles = config.getItem(dbConfigRef + '.memberRoles');
      if(dbAdminRoles && dbAdminRoles instanceof Array) {
        dbAdminRoles.forEach(function(role) {
          if(role && dbConfig.adminRoles.indexOf(role) === -1) {
            dbConfig.adminRoles.push(role);
          }
        });
      }
      if(dbMemberRoles && dbMemberRoles instanceof Array) {
        dbMemberRoles.forEach(function(role) {
          if(role && dbConfig.memberRoles.indexOf(role) === -1) {
            dbConfig.memberRoles.push(role);
          }
        });
      }
    } else if(config.getItem('userDBs.model._default')) {
      dbConfig.permissions = config.getItem('userDBs.model._default.permissions') || [];
      // Only add the default design doc to a private database
      if(!type || type === 'private') {
        dbConfig.designDocs = config.getItem('userDBs.model._default.designDocs') || [];
      } else {
        dbConfig.designDocs = [];
      }
      dbConfig.type = type || 'private';
    } else {
      dbConfig.type = type || 'private';
    }
    return dbConfig;
  };

  this.createDB = function (dbName) {
    var finalUrl = util.getDBURL(config.getItem('dbServer')) + '/' + dbName;
    return BPromise.fromNode(function(callback) {
      request.put(finalUrl)
        .send({})
        .end(callback);
    })
      .then(function(res) {
        return BPromise.resolve(JSON.parse(res.text));
      }, function(err) {
        if(err.status === 412) {
          return BPromise.resolve(false);
        } else {
          return BPromise.reject(err.text);
        }
      });
  };

  this.removeDB = function(dbName) {
    var db = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + dbName);
    return db.destroy();
  };

  return this;
};

// Escapes any characters that are illegal in a CouchDB database name using percent codes inside parenthesis
// Example: 'My.name@example.com' => 'my(2e)name(40)example(2e)com'
// CouchDB rules on DB names:
// Name must begin with a lowercase letter (a-z)
// Lowercase characters (a-z)
// Digits (0-9)
// Any of the characters _, $, (, ), +, -, and /.
// If you’re familiar with Regular Expressions, the rules above could be written as ^[a-z][a-z0-9_$()+/-]*$.
function getLegalDBName(input) {
  input = input.toLowerCase();
  var output = encodeURIComponent(input);
  output = output.replace(/\./g, '%2E');
  output = output.replace(/!/g, '%21');
  output = output.replace(/~/g, '%7E');
  output = output.replace(/\*/g, '%2A');
  output = output.replace(/'/g, '%27');
  output = output.replace(/\(/g, '%28');
  output = output.replace(/\)/g, '%29');
  // output = output.replace(/\-/g, '%2D');
  output = output.toLowerCase();
  output = output.replace(/(%..)/g, function(esc) {
    esc = esc.substr(1);
    return '(' + esc + ')';
  });
  return output;
}
<commit_msg>On second thought the $ seems essential because of path splitting<commit_after>'use strict';
var BPromise = require('bluebird');
var PouchDB = require('pouchdb');
var util = require('./../util');
var seed = require('pouchdb-seed-design');
var request = require('superagent');

module.exports = function (config, userDB, couchAuthDB) {

  var cloudant = config.getItem('dbServer.cloudant');

  var adapter;

  if(cloudant) {
    adapter = require('./cloudant');
  } else {
    var CouchAdapter = require('./couchdb');
    adapter = new CouchAdapter(couchAuthDB);
  }

  this.storeKey = function (username, key, password, expires, roles) {
    return adapter.storeKey(username, key, password, expires, roles);
  };

  this.removeKeys = function(keys) {
    return adapter.removeKeys(keys);
  };

  this.authorizeKeys = function (user_id, db, keys, permissions, roles) {
    return adapter.authorizeKeys(user_id, db, keys, permissions, roles);
  };

  this.deauthorizeKeys = function (db, keys) {
    return adapter.deauthorizeKeys(db, keys);
  };

  this.authorizeUserSessions = function(user_id, personalDBs, sessionKeys, roles) {
    var self = this;
    var promises = [];
    sessionKeys = util.toArray(sessionKeys);
    Object.keys(personalDBs).forEach(function(personalDB) {
      var permissions = personalDBs[personalDB].permissions;
      if(!permissions) {
        permissions = config.getItem('userDBs.model.' + personalDBs[personalDB].name + '.permissions') || config.getItem('userDBs.model._default.permissions') || [];
      }
      var db = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + personalDB);
      promises.push(self.authorizeKeys(user_id, db, sessionKeys, permissions, roles));
    });
    return BPromise.all(promises);
  };

  this.addUserDB = function (userDoc, dbName, designDocs, type, permissions, adminRoles, memberRoles) {
    var self = this;
    var promises = [];
    adminRoles = adminRoles || [];
    memberRoles = memberRoles || [];
    // Create and the database and seed it if a designDoc is specified
    var prefix = config.getItem('userDBs.privatePrefix') ? config.getItem('userDBs.privatePrefix') + '_' : '';
    var finalDBName, newDB;
    // Make sure we have a legal database name
    var username = userDoc._id;
    username = getLegalDBName(username);
    if(type === 'shared') {
      finalDBName = dbName;
    } else {
      finalDBName = prefix + dbName + '/' + username;
    }
    return self.createDB(finalDBName)
      .then(function() {
        newDB = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + finalDBName);
        return adapter.initSecurity(newDB, adminRoles, memberRoles);
      })
      .then(function() {
        // Seed the design docs
        if (designDocs && designDocs instanceof Array) {
          designDocs.forEach(function(ddName) {
            var dDoc = self.getDesignDoc(ddName);
            if(dDoc) {
              promises.push(seed(newDB, dDoc));
            } else {
              console.warn('Failed to locate design doc: ' + ddName);
            }
          });
        }
        // Authorize the user's existing DB keys to access the new database
        var keysToAuthorize = [];
        if (userDoc.session) {
          for (var key in userDoc.session) {
            if(userDoc.session.hasOwnProperty(key) && userDoc.session[key].expires > Date.now()) {
              keysToAuthorize.push(key);
            }
          }
        }
        if (keysToAuthorize.length > 0) {
          promises.push(self.authorizeKeys(userDoc._id, newDB, keysToAuthorize, permissions, userDoc.roles));
        }
        return BPromise.all(promises);
      })
      .then(function() {
        return BPromise.resolve(finalDBName);
      });
  };

  this.removeExpiredKeys = function () {
    var self = this;
    var keysByUser = {};
    var userDocs = {};
    var expiredKeys = [];
    // query a list of expired keys by user
    return userDB.query('auth/expiredKeys', {endkey: Date.now(), include_docs: true})
      .then(function(results) {
        // group by user
        results.rows.forEach(function(row) {
          keysByUser[row.value.user] = row.value.key;
          expiredKeys.push(row.value.key);
          // Add the user doc if it doesn't already exist
          if(typeof userDocs[row.value.user] === 'undefined') {
            userDocs[row.value.user] = row.doc;
          }
          // remove each key from user.session
          if(userDocs[row.value.user].session) {
            Object.keys(userDocs[row.value.user].session).forEach(function(session) {
              if(row.value.key === session) {
                delete userDocs[row.value.user].session[session];
              }
            });
          }
        });
        return self.removeKeys(expiredKeys);
      })
      .then(function() {
        // - deauthorize keys for each personal database of each user
        var deauthorize = [];
        Object.keys(keysByUser).forEach(function(user) {
          deauthorize.push(self.deauthorizeUser(userDocs[user], keysByUser[user]));
        });
        return BPromise.all(deauthorize);
      })
      .then(function() {
        var userUpdates = [];
        Object.keys(userDocs).forEach(function(user) {
          userUpdates.push(userDocs[user]);
        });
        // Bulk save user doc updates
        return userDB.bulkDocs(userUpdates);
      })
      .then(function() {
        return BPromise.resolve(expiredKeys);
      });
  };

  this.deauthorizeUser = function(userDoc, keys) {
    var self = this;
    var promises = [];
    // If keys is not specified we will deauthorize all of the users sessions
    if(!keys) {
      keys = util.getSessions(userDoc);
    }
    keys = util.toArray(keys);
    if(userDoc.personalDBs && typeof userDoc.personalDBs === 'object') {
      Object.keys(userDoc.personalDBs).forEach(function(personalDB) {
        var db = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + personalDB);
        promises.push(self.deauthorizeKeys(db, keys));
      });
      return BPromise.all(promises);
    } else {
      return BPromise.resolve(false);
    }
  };

  this.getDesignDoc = function(docName) {
    if(!docName) {
      return null;
    }
    var designDoc;
    var designDocDir = config.getItem('userDBs.designDocDir');
    if(!designDocDir) {
      designDocDir = __dirname;
    }
    try {
      designDoc = require(designDocDir + '/' + docName);
    }
    catch(err) {
      console.warn('Design doc: ' + designDocDir + '/' + docName + ' not found.');
      designDoc = null;
    }
    return designDoc;
  };

  this.getDBConfig = function(dbName, type) {
    var dbConfig = {
      name: dbName
    };
    dbConfig.adminRoles = config.getItem('userDBs.defaultSecurityRoles.admins') || [];
    dbConfig.memberRoles = config.getItem('userDBs.defaultSecurityRoles.members') || [];
    var dbConfigRef = 'userDBs.model.' + dbName;
    if(config.getItem(dbConfigRef)) {
      dbConfig.permissions = config.getItem(dbConfigRef + '.permissions') || [];
      dbConfig.designDocs = config.getItem(dbConfigRef + '.designDocs') || [];
      dbConfig.type = type || config.getItem(dbConfigRef + '.type') || 'private';
      var dbAdminRoles = config.getItem(dbConfigRef + '.adminRoles');
      var dbMemberRoles = config.getItem(dbConfigRef + '.memberRoles');
      if(dbAdminRoles && dbAdminRoles instanceof Array) {
        dbAdminRoles.forEach(function(role) {
          if(role && dbConfig.adminRoles.indexOf(role) === -1) {
            dbConfig.adminRoles.push(role);
          }
        });
      }
      if(dbMemberRoles && dbMemberRoles instanceof Array) {
        dbMemberRoles.forEach(function(role) {
          if(role && dbConfig.memberRoles.indexOf(role) === -1) {
            dbConfig.memberRoles.push(role);
          }
        });
      }
    } else if(config.getItem('userDBs.model._default')) {
      dbConfig.permissions = config.getItem('userDBs.model._default.permissions') || [];
      // Only add the default design doc to a private database
      if(!type || type === 'private') {
        dbConfig.designDocs = config.getItem('userDBs.model._default.designDocs') || [];
      } else {
        dbConfig.designDocs = [];
      }
      dbConfig.type = type || 'private';
    } else {
      dbConfig.type = type || 'private';
    }
    return dbConfig;
  };

  this.createDB = function (dbName) {
    var finalUrl = util.getDBURL(config.getItem('dbServer')) + '/' + dbName;
    return BPromise.fromNode(function(callback) {
      request.put(finalUrl)
        .send({})
        .end(callback);
    })
      .then(function(res) {
        return BPromise.resolve(JSON.parse(res.text));
      }, function(err) {
        if(err.status === 412) {
          return BPromise.resolve(false);
        } else {
          return BPromise.reject(err.text);
        }
      });
  };

  this.removeDB = function(dbName) {
    var db = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + dbName);
    return db.destroy();
  };

  return this;
};

// Escapes any characters that are illegal in a CouchDB database name using percent codes inside parenthesis
// Example: 'My.name@example.com' => 'my(2e)name(40)example(2e)com'
// CouchDB rules on DB names:
// Name must begin with a lowercase letter (a-z)
// Lowercase characters (a-z)
// Digits (0-9)
// Any of the characters _, $, (, ), +, -, and /.
// If you’re familiar with Regular Expressions, the rules above could be written as ^[a-z][a-z0-9_$()+/-]*$.
function getLegalDBName(input) {
  input = input.toLowerCase();
  var output = encodeURIComponent(input);
  output = output.replace(/\./g, '%2E');
  output = output.replace(/!/g, '%21');
  output = output.replace(/~/g, '%7E');
  output = output.replace(/\*/g, '%2A');
  output = output.replace(/'/g, '%27');
  output = output.replace(/\(/g, '%28');
  output = output.replace(/\)/g, '%29');
  // output = output.replace(/\-/g, '%2D');
  output = output.toLowerCase();
  output = output.replace(/(%..)/g, function(esc) {
    esc = esc.substr(1);
    return '(' + esc + ')';
  });
  return output;
}
<|endoftext|>


<commit_before>'use strict'

let Base = require('../Server')
let axon = require('axon')
let rpc = require('ngn-axon-rpc')
let rep = axon.socket('rep')
let _server = null

/**
 * @class NGN.rpc.Server
 * The RPC server is a special TCP socket server that exposes
 * API functions to other processes. This class encapsulates application
 * logic in a manner that can be reused by both local and remote NGN processes.
 * For example, a REST interface and an XMPP chat interface can both use the same
 * underlying logic by making remote procedure calls to the same server.
 *
 * **Example: Hard-coded Object**
 * ```js
 * var controller = new NGN.rpc.Server({
 *   port: 4000,
 *   expose: {
 *     sayHello: function(name){
 *       return "Hello, "+name
 *     },
 *     sayGoodbye: function(name){
 *       return "Goodbye "+name
 *     }
 *   }
 * })
 * ```
 *
 * **Example: Module**
 * ```js
 * var controller = new NGN.rpc.Server({
 *   port: 4000,
 *   module: require('./path/to/module.js')
 * })
 * ```
 * @docauthor Corey Butler
 * @extends NGN.Server
 * @fires clientconnection
 * Fired when a client is connected. The payload is a #net.Socket.
 */

class Server extends Base {
  constructor (config) {
    config = config || {}

    /**
     * @cfg port (required)
     * @inheritdoc
     */

    /**
     * @cfg {Object} module
     * This can be a filepath or an Object. It contains the functions
     * that make up the API.
     *
     * **Example: Hard-coded Object**
     * ```js
     * var controller = new NGN.rpc.Server({
     *   port: 4000,
     *   expose: {
     *     sayHello: function(name){
     *       return "Hello, "+name
     *     },
     *     sayGoodbye: function(name){
     *       return "Goodbye "+name
     *     }
     *   }
     * })
     * ```
     *
     * **Example: Module**
     * ```js
     * var controller = new NGN.rpc.Server({
     *   port: 4000,
     *   module: require('./path/to/module.js')
     * })
     * ```
     */
    super(config)

    this.module = config.expose || {}
  }

  /**
   * @property {Object} server
   * The RPC server.
   * @private
   * @readonly
   */
  get server () {
    return _server || null
  }

  /**
   * @method expose
   * Expose a custom module (require) or an object, just like #module.
   * @param {Object} module (required)
   */
  expose (module) {
    if (!module) {
      return console.warn('Invalid module specified in expose() method. Skipping.')
    }
    if (!this.running) {
      this.on('start', function () {
        console.info('Exposing module (delayed).')
        _server.expose(module)
      })
      return console.warn('Cannot expose module until the server is started.')
    }
    _server.expose(module)
  }

  /**
   * @method start
   * Start listening for requests.
   */
  start () {
    if (!this.running && !this.starting) {
      try {
        let me = this

        this._starting = true

        // Bind to a port (i.e. listen for TCP requests)
        rep.bind(this.port, function () {
          // Create the logical server
          _server = new rpc.Server(rep)

          // Handle a shutdown of the connection.
          _server.sock.once('close', function () {
            me._running = false
            me._starting = false
            me.emit('stop', this)
          })

          _server.sock.on('connect', function (socket) {
            me.emit('clientconnection')
          })

          // Expose functionality
          me.module && _server.expose(me.module)

          // Cleanup & emit a notification that the server has started.
          me._running = true
          me._starting = false
          me.emit && me.emit('start')
        })
      } catch (e) {
        this._running = false
        this._starting = false
        this.emit('error', e)
      }
    } else {
      console.warn('Server already started. Cannot start twice. Make sure autoStart=true and start() are not being executed sequentially.')
    }
  }

  /**
   * @method stop
   * Stop the server.
   */
  stop () {
    this.server.sock && this.server.sock.close()
  }

}

// Create a module out of this.
module.exports = Server
<commit_msg>Updated dependency<commit_after>'use strict'

let Base = require('../Server')
let axon = require('axon')
let rpc = require('ngn-rpc')
let rep = axon.socket('rep')
let _server = null

/**
 * @class NGN.rpc.Server
 * The RPC server is a special TCP socket server that exposes
 * API functions to other processes. This class encapsulates application
 * logic in a manner that can be reused by both local and remote NGN processes.
 * For example, a REST interface and an XMPP chat interface can both use the same
 * underlying logic by making remote procedure calls to the same server.
 *
 * **Example: Hard-coded Object**
 * ```js
 * var controller = new NGN.rpc.Server({
 *   port: 4000,
 *   expose: {
 *     sayHello: function(name){
 *       return "Hello, "+name
 *     },
 *     sayGoodbye: function(name){
 *       return "Goodbye "+name
 *     }
 *   }
 * })
 * ```
 *
 * **Example: Module**
 * ```js
 * var controller = new NGN.rpc.Server({
 *   port: 4000,
 *   module: require('./path/to/module.js')
 * })
 * ```
 * @docauthor Corey Butler
 * @extends NGN.Server
 * @fires clientconnection
 * Fired when a client is connected. The payload is a #net.Socket.
 */

class Server extends Base {
  constructor (config) {
    config = config || {}

    /**
     * @cfg port (required)
     * @inheritdoc
     */

    /**
     * @cfg {Object} module
     * This can be a filepath or an Object. It contains the functions
     * that make up the API.
     *
     * **Example: Hard-coded Object**
     * ```js
     * var controller = new NGN.rpc.Server({
     *   port: 4000,
     *   expose: {
     *     sayHello: function(name){
     *       return "Hello, "+name
     *     },
     *     sayGoodbye: function(name){
     *       return "Goodbye "+name
     *     }
     *   }
     * })
     * ```
     *
     * **Example: Module**
     * ```js
     * var controller = new NGN.rpc.Server({
     *   port: 4000,
     *   module: require('./path/to/module.js')
     * })
     * ```
     */
    super(config)

    this.module = config.expose || {}
  }

  /**
   * @property {Object} server
   * The RPC server.
   * @private
   * @readonly
   */
  get server () {
    return _server || null
  }

  /**
   * @method expose
   * Expose a custom module (require) or an object, just like #module.
   * @param {Object} module (required)
   */
  expose (module) {
    if (!module) {
      return console.warn('Invalid module specified in expose() method. Skipping.')
    }
    if (!this.running) {
      this.on('start', function () {
        console.info('Exposing module (delayed).')
        _server.expose(module)
      })
      return console.warn('Cannot expose module until the server is started.')
    }
    _server.expose(module)
  }

  /**
   * @method start
   * Start listening for requests.
   */
  start () {
    if (!this.running && !this.starting) {
      try {
        let me = this

        this._starting = true

        // Bind to a port (i.e. listen for TCP requests)
        rep.bind(this.port, function () {
          // Create the logical server
          _server = new rpc.Server(rep)

          // Handle a shutdown of the connection.
          _server.sock.once('close', function () {
            me._running = false
            me._starting = false
            me.emit('stop', this)
          })

          _server.sock.on('connect', function (socket) {
            me.emit('clientconnection')
          })

          // Expose functionality
          me.module && _server.expose(me.module)

          // Cleanup & emit a notification that the server has started.
          me._running = true
          me._starting = false
          me.emit && me.emit('start')
        })
      } catch (e) {
        this._running = false
        this._starting = false
        this.emit('error', e)
      }
    } else {
      console.warn('Server already started. Cannot start twice. Make sure autoStart=true and start() are not being executed sequentially.')
    }
  }

  /**
   * @method stop
   * Stop the server.
   */
  stop () {
    this.server.sock && this.server.sock.close()
  }

}

// Create a module out of this.
module.exports = Server
<|endoftext|>


<commit_before>var request = require('../request');
var redis = require('../redis');
var xmlParser = require('xml2json');
var util = require('util');
var config = require('../../config/weixin');

function Weixin(id) {
  this.id = id;
}

Weixin.prototype.getData = function*() {
  var rss = {};
  var now = new Date();

  //rss-cache
  var rssCacheId = util.format(config.listCachePolicy.cacheId, this.id);
  var rssCache = yield redis.get(rssCacheId);
  if (config.cacheEnable && rssCache) {
    return JSON.parse(rssCache);
  }

  //query-list-url
  var users = yield this.searchNameProxy(this.id);
  if (users.length == 0)
    throw Error('不存在此微信号');
  var rss = users[0];
  var apiUrl = rss.url;

  //list-collect
  rss.items = yield this.getListData(apiUrl);
  for (var i = 0, l = rss.items.length; i < l; i++) {
    var item = rss.items[i];
    now.setTime(item.lastModified*1000);
    item.formatDate = now.toUTCString();
    var content = yield this.getContentDataProxy(item);
    if (content) {
      item.fullContent = content.content;
    }
  };
  if (rss.items.length == 0)
    throw Error('无相关文章');

  rss = this.getRssInfo(rss);
  yield redis.setex(rssCacheId, config.listCachePolicy.lifeTime, JSON.stringify(rss));
  return rss;
};

Weixin.prototype.getRssInfo = function(rss) {
  var now = new Date();
  var recentItem = rss.items[0];
  rss.id = this.id;
  rss.title = recentItem.sourcename;
  rss.lastModified = recentItem.lastModified;
  now.setTime(rss.lastModified*1000);
  rss.formatDate = now.toUTCString();
  rss.author = recentItem.sourcename;
  return rss;
};

Weixin.prototype.getListData = function*(apiUrl) {
  var items = [];
  var response =
    yield request(apiUrl, {
      headers: yield this.getRequestHeaders()
    });
  if (response.error)
    throw new Error(response.error);
  if (response.statusCode != 200)
    throw new Error('response statusCode: ' + response.statusCode);

  var body = response.body;
  var match = body.match(/var\smsgList\s=\s'([\s\S]*?)';/);

  if (!match || !match[1]) {
    throw new Error('无法获取列表数据');
  }
  var dataJson = JSON.parse(match[1].html());
  if (dataJson.list.length === 0)
    throw new Error(this.id + ': 无相关文章');
  dataJson.list.forEach(function(item) {
    var _item = {
      'docid' : item.comm_msg_info.id,
      'lastModified' : item.comm_msg_info.datetime,
      'title' : item.app_msg_ext_info.title,
      'content' : item.app_msg_ext_info.digest,
      'url' : 'http://mp.weixin.qq.com' + item.app_msg_ext_info.content_url.substr(1).html(),
      'sourcename' : item.app_msg_ext_info.author
    };
    items.push(_item);
  });
  return items;
}

Weixin.prototype.getContentDataProxy = function*(item) {
  //content-cache
  var contentCacheId = util.format(config.contentCachePolicy.cacheId, item.docid);
  var content = yield redis.get(contentCacheId);
  if (!config.cacheEnable || !content || !content.content) {
    //content-collect
    content = yield this.getContentData(item);
    if (content) {
      yield redis.setex(contentCacheId, config.contentCachePolicy.lifeTime, JSON.stringify(content));
    }
  } else {
    content = JSON.parse(content);
  }
  return content;
};

Weixin.prototype.getContentData = function*(item) {
  var url = item.url;
  var result =
    yield request(url, {
      headers: yield this.getRequestHeaders()
    });
  if (!result.error && result.statusCode == 200) {
    var match = result.body.match(/id=\"js_content\">([\s\S]*?)<\/div>/);
    if (match && match[1]) {
      content = match[1].replace(/data-src/g, 'src');
      var coverHtml = item.imglink ? '

' : '';
      content = coverHtml + content;
      return {'content' : content, 'source' : result.request.href};
    }
  }
}

Weixin.prototype.getRequestHeaders = function*() {
  return {
    'Accept': '*/*',
    'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6',
    'Cache-Control': 'max-age=0',
    'Connection': 'keep-alive',
    'Cookie': 'sd_userid=74761436088560498; sd_cookie_crttime=1436088560498; 3g_guest_id=-9124332030488707072; ts_refer=www.baidu.com/link; ts_uid=1816741185; eas_sid=f1w4I5H7w224X4z3w9J27844g9; _ga=GA1.2.1140860462.1438155424; pgv_pvid=1467256065; o_cookie=527114214; noticeLoginFlag=1; ptui_loginuin=527114214; ptcz=2a4b878f712cb71f3eeb0fda5a3d488fd4688d292820e1485fa04ee62c2c9500; pt2gguin=o0527114214; uin=o0527114214; skey=@k3f0ouWCY; qm_username=527114214; qm_sid=705e43ed71261bdea26e56d389d4d334,cpfgcNUS3Cs4.; ptisp=ctc',
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36'
  }
}

Weixin.prototype.searchNameProxy = function*(keyword) {
  var searchCacheId = util.format(config.searchCachePolicy.cacheId, keyword);
  var result = yield redis.get(searchCacheId);
  if (!config.cacheEnable || !result) {
    var result = yield this.searchName(keyword);
    yield redis.setex(searchCacheId, config.searchCachePolicy.lifeTime, JSON.stringify(result));
  } else {
    result = JSON.parse(result);
  }
  return result;
};

Weixin.prototype.searchName = function*(keyword) {
  var apiUrl = util.format(config.api.searchName, encodeURIComponent(keyword));
  var response = yield request(apiUrl, {
      headers: yield this.getRequestHeaders()
    });
  if (response.error)
    throw new Error(response.error);
  if (response.statusCode != 200)
    throw new Error('response statusCode: ' + response.statusCode);
  var regexp = /<div\starget="_blank"([\s\S]*?)
/g;
  var matches = response.body.match(regexp);
  if (!matches)
    throw new Error('查询不到相关账号');
  var result = [];
  for (var i = 0, l = matches.length; i < l; i++) {
    var item = matches[i];
    var match = item.match(/href="([^"]*)"[\s\S]*<img\sstyle="visibility:hidden"\ssrc="([^"]*)"[\s\S]*
(.*)<\/h3>[\s\S]*微信号：(.*)<\/label><\/span>[\s\S]*功能介绍：<\/span>(.*)<\/span>/);
    if (match) {
      var info = {
        'url'         : match[1].html(),
        'pic'         : util.format(config.imageProxy, match[2]),
        'name'        : this.stripTags(match[3]),
        'id'          : match[4],
        'description' : this.stripTags(match[5])
      }
      result.push(info);
    }
  };
  return result;
}

Weixin.prototype.stripTags = function(string) {
  return string.replace(/<(.*?)>/g, '');
}

String.prototype.html = function(encode) {
    var replace =["'", "'", """, '"', " ", " ", ">", ">", "<", "<", "&", "&", "¥", "¥"];
    if (encode) {
        replace.reverse();
    }
    for (var i=0,str=this;i< replace.length;i+= 2) {
         str=str.replace(new RegExp(replace[i],'g'),replace[i+1]);
    }
    return str;
};

module.exports = Weixin;<commit_msg>修复采集问题<commit_after>var request = require('../request');
var redis = require('../redis');
var xmlParser = require('xml2json');
var util = require('util');
var config = require('../../config/weixin');

function Weixin(id) {
  this.id = id;
}

Weixin.prototype.getData = function*() {
  var rss = {};
  var now = new Date();

  //rss-cache
  var rssCacheId = util.format(config.listCachePolicy.cacheId, this.id);
  var rssCache = yield redis.get(rssCacheId);
  if (config.cacheEnable && rssCache) {
    return JSON.parse(rssCache);
  }

  //query-list-url
  var users = yield this.searchNameProxy(this.id);
  if (users.length == 0)
    throw Error('不存在此微信号');
  var rss = users[0];
  var apiUrl = rss.url;

  //list-collect
  rss.items = yield this.getListData(apiUrl);
  for (var i = 0, l = rss.items.length; i < l; i++) {
    var item = rss.items[i];
    now.setTime(item.lastModified*1000);
    item.formatDate = now.toUTCString();
    var content = yield this.getContentDataProxy(item);
    if (content) {
      item.fullContent = content.content;
    }
  };
  if (rss.items.length == 0)
    throw Error('无相关文章');

  rss = this.getRssInfo(rss);
  yield redis.setex(rssCacheId, config.listCachePolicy.lifeTime, JSON.stringify(rss));
  return rss;
};

Weixin.prototype.getRssInfo = function(rss) {
  var now = new Date();
  var recentItem = rss.items[0];
  rss.id = this.id;
  rss.title = recentItem.sourcename;
  rss.lastModified = recentItem.lastModified;
  now.setTime(rss.lastModified*1000);
  rss.formatDate = now.toUTCString();
  rss.author = recentItem.sourcename;
  return rss;
};

Weixin.prototype.getListData = function*(apiUrl) {
  var items = [];
  var response =
    yield request(apiUrl, {
      headers: yield this.getRequestHeaders()
    });
  if (response.error)
    throw new Error(response.error);
  if (response.statusCode != 200)
    throw new Error('response statusCode: ' + response.statusCode);

  var body = response.body;
  var match = body.match(/var\smsgList\s=\s'([\s\S]*?)';/);
  if (!match || !match[1]) {
    throw new Error('无法获取列表数据');
  }
  var dataJson = JSON.parse(match[1].html());
  if (dataJson.list.length === 0)
    throw new Error(this.id + ': 无相关文章');
  dataJson.list.forEach(function(item) {
    var _item = {
      'docid' : item.comm_msg_info.id,
      'lastModified' : item.comm_msg_info.datetime,
      'title' : item.app_msg_ext_info.title,
      'content' : item.app_msg_ext_info.digest,
      'url' : 'http://mp.weixin.qq.com' + item.app_msg_ext_info.content_url.substr(1).html(),
      'sourcename' : item.app_msg_ext_info.author
    };
    items.push(_item);
    //subList
    var subList = item.app_msg_ext_info.multi_app_msg_item_list;
    if (subList.length > 0) {
      subList.forEach(function(subItem) {
        var _item = {
          'docid' : subItem.fileid,
          'lastModified' : item.comm_msg_info.datetime,
          'title' : subItem.title,
          'content' : subItem.digest,
          'url' : 'http://mp.weixin.qq.com' + subItem.content_url.substr(1).html(),
          'sourcename' : subItem.author
        };
        items.push(_item);
      });
    }
  });
  return items;
}

Weixin.prototype.getContentDataProxy = function*(item) {
  //content-cache
  var contentCacheId = util.format(config.contentCachePolicy.cacheId, item.docid);
  var content = yield redis.get(contentCacheId);
  if (!config.cacheEnable || !content || !content.content) {
    //content-collect
    content = yield this.getContentData(item);
    if (content) {
      yield redis.setex(contentCacheId, config.contentCachePolicy.lifeTime, JSON.stringify(content));
    }
  } else {
    content = JSON.parse(content);
  }
  return content;
};

Weixin.prototype.getContentData = function*(item) {
  var url = item.url;
  var result =
    yield request(url, {
      headers: yield this.getRequestHeaders()
    });
  if (!result.error && result.statusCode == 200) {
    var match = result.body.match(/id=\"js_content\">([\s\S]*?)<\/div>/);
    var cover = this.getCover(result.body);
    if (match && match[1]) {
      content = match[1].replace(/data-src/g, 'src');
      var coverHtml = cover ? '

' : '';
      content = coverHtml + content;
      return {'content' : content, 'source' : result.request.href};
    }
  }
}

Weixin.prototype.getCover = function(html) {
  var match = html.match(/var\scover\s=\s\"([\s\S]*?)\";/);
  if (match && match[1]) {
    return match[1];
  }
};

Weixin.prototype.getRequestHeaders = function*() {
  return {
    'Accept': '*/*',
    'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6',
    'Cache-Control': 'max-age=0',
    'Connection': 'keep-alive',
    'Cookie': 'sd_userid=74761436088560498; sd_cookie_crttime=1436088560498; 3g_guest_id=-9124332030488707072; ts_refer=www.baidu.com/link; ts_uid=1816741185; eas_sid=f1w4I5H7w224X4z3w9J27844g9; _ga=GA1.2.1140860462.1438155424; pgv_pvid=1467256065; o_cookie=527114214; noticeLoginFlag=1; ptui_loginuin=527114214; ptcz=2a4b878f712cb71f3eeb0fda5a3d488fd4688d292820e1485fa04ee62c2c9500; pt2gguin=o0527114214; uin=o0527114214; skey=@k3f0ouWCY; qm_username=527114214; qm_sid=705e43ed71261bdea26e56d389d4d334,cpfgcNUS3Cs4.; ptisp=ctc',
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36',
    'Referer' : 'http://weixin.sogou.com/weixin?type=1&query=%E4%B8%80%E5%85%9C%E7%B3%96&ie=utf8&_sug_=y&_sug_type_=&w=01019900&sut=2112&sst0=1468836385188&lkt=1%2C1468836385080%2C1468836385080'
  }
}

Weixin.prototype.searchNameProxy = function*(keyword) {
  var searchCacheId = util.format(config.searchCachePolicy.cacheId, keyword);
  var result = yield redis.get(searchCacheId);
  if (!config.cacheEnable || !result) {
    var result = yield this.searchName(keyword);
    yield redis.setex(searchCacheId, config.searchCachePolicy.lifeTime, JSON.stringify(result));
  } else {
    result = JSON.parse(result);
  }
  return result;
};

Weixin.prototype.searchName = function*(keyword) {
  var apiUrl = util.format(config.api.searchName, encodeURIComponent(keyword));
  var response = yield request(apiUrl, {
      headers: yield this.getRequestHeaders()
    });
  if (response.error)
    throw new Error(response.error);
  if (response.statusCode != 200)
    throw new Error('response statusCode: ' + response.statusCode);
  var regexp = /<div\starget="_blank"([\s\S]*?)
/g;
  var matches = response.body.match(regexp);
  if (!matches)
    throw new Error('查询不到相关账号');
  var result = [];
  for (var i = 0, l = matches.length; i < l; i++) {
    var item = matches[i];
    var match = item.match(/href="([^"]*)"[\s\S]*<img\sstyle="visibility:hidden"\ssrc="([^"]*)"[\s\S]*
(.*)<\/h3>[\s\S]*微信号：(.*)<\/label><\/span>[\s\S]*功能介绍：<\/span>(.*)<\/span>/);
    if (match) {
      var info = {
        'url'         : match[1].html(),
        'pic'         : util.format(config.imageProxy, match[2]),
        'name'        : this.stripTags(match[3]),
        'id'          : match[4],
        'description' : this.stripTags(match[5])
      }
      result.push(info);
    }
  };
  return result;
}

Weixin.prototype.stripTags = function(string) {
  return string.replace(/<(.*?)>/g, '');
}

String.prototype.html = function(encode) {
    var replace =["'", "'", """, '"', " ", " ", ">", ">", "<", "<", "&", "&", "¥", "¥"];
    if (encode) {
        replace.reverse();
    }
    for (var i=0,str=this;i< replace.length;i+= 2) {
         str=str.replace(new RegExp(replace[i],'g'),replace[i+1]);
    }
    return str;
};

module.exports = Weixin;<|endoftext|>


<commit_before>var http = require('http'),
    https = require('https'),
    EventEmitter = require('events').EventEmitter,
    inherits = require('util').inherits,
    extend = require('util')._extend,
    fs = require('fs'),
    path = require('path'),
    express = require('express'),
    favicon = require('serve-favicon'),
    WebSocketServer = require('ws').Server,
    Session = require('./session'),
    buildInspectorUrl = require('../index.js').buildInspectorUrl,
    buildWebSocketUrl = require('../index.js').buildWebSocketUrl,
    plugins = require('./plugins'),
    InspectorJson = plugins.InspectorJson,
    ProtocolJson = plugins.ProtocolJson,
    PLUGINS = plugins.cwd,
    OVERRIDES = path.join(__dirname, '../front-end-node'),
    WEBROOT = path.join(__dirname, '../front-end');

function debugAction(req, res) {
  if (!req.query.ws)
    return redirectToRoot.call(this, req, res);

  res.sendFile(path.join(WEBROOT, 'inspector.html'));
}

function redirectToRoot(req, res) {
  return res.redirect(this._getUrlFromReq(req));
}

function inspectorJson(req, res) {
  res.send(this._inspectorJson);
}

function emptyJson(req, res) {
  res.send('{}');
}

function jsonAction(req, res) {
  res.json({
   'description': 'Node.js app (powered by node-inspector)',
   'devtoolsFrontendUrl': this.address().url,
   'id': process.pid,
   'title': process.title ||'',
   'type': 'page',
   'url': '',
   'webSocketDebuggerUrl': this.wsAddress().url
  });
}

function jsonVersionAction(req, res) {
  res.json({
    'browser': 'Node ' + process.version,
    'protocol-version': '1.1',
    'user-agent': 'Node ' + process.version,
    // webKit-version is a dummy value as it's used to match compatible DevTools front-ends
    'webKit-version': '537.36 (@181352)'
  });
}

function protocolJson(req, res) {
  res.send(this._protocolJson);
}

function handleWebSocketConnection(socket) {
  var debugPort = this._getDebuggerPort(socket.upgradeReq.url);
  this._createSession(debugPort, socket);
}

function handleServerListening() {
  this.emit('listening');
}

function handleServerError(err) {
  if (err._handledByInspector) return;
  err._handledByInspector = true;
  this.emit('error', err);
}

function DebugServer() {}

inherits(DebugServer, EventEmitter);

DebugServer.prototype.start = function(options) {
  this._config = extend({}, options);
  this._isHTTPS = this._config.sslKey && this._config.sslCert ? true : false;
  this._inspectorJson = new InspectorJson(this._config);
  this._protocolJson = new ProtocolJson(this._config);

  var app = express();
  var httpServer;

  if (this._isHTTPS) {
    httpServer = https.createServer({
      key: fs.readFileSync(this._config.sslKey, {encoding: 'utf8'}),
      cert: fs.readFileSync(this._config.sslCert, {encoding: 'utf8'})
    }, app);
  } else {
    httpServer = http.createServer(app);
  }

  this._httpServer = httpServer;

  app.use(favicon(path.join(__dirname, '../front-end-node/Images/favicon.png')));

  // Json handshake
  app.get('/json', jsonAction.bind(this));
  app.get('/json/version', jsonVersionAction.bind(this));

  // Dynamically generated front-end content
  app.get('/inspector.json', inspectorJson.bind(this));
  app.get('/protocol.json', protocolJson.bind(this));
  app.get('/InspectorBackendCommands.js', emptyJson);
  app.get('/SupportedCSSProperties.js', emptyJson);

  // Main routing
  app.get('/', debugAction.bind(this));
  app.get('/debug', redirectToRoot.bind(this));
  app.use('/node', express.static(OVERRIDES));
  app.use('/plugins', express.static(PLUGINS));
  app.use(express.static(WEBROOT));

  this.wsServer = new WebSocketServer({
    server: httpServer
  });
  this.wsServer.on('connection', handleWebSocketConnection.bind(this));
  this.wsServer.on('error', handleServerError.bind(this));

  httpServer.on('listening', handleServerListening.bind(this));
  httpServer.on('error', handleServerError.bind(this));
  httpServer.listen(this._config.webPort, this._config.webHost);
};

DebugServer.prototype._getDebuggerPort = function(url) {
  return parseInt((/[\?\&]port=(\d+)/.exec(url) || [null, this._config.debugPort])[1], 10);
};

DebugServer.prototype._getUrlFromReq = function(req) {
  var urlParts = req.headers.host.split(':'),
      debugPort = this._getDebuggerPort(req.url);
  return buildInspectorUrl(urlParts[0], urlParts[1], debugPort, null, this._isHTTPS);
};

DebugServer.prototype._createSession = function(debugPort, wsConnection) {
  return new Session(this._config, debugPort, wsConnection);
};

DebugServer.prototype.close = function() {
  if (this.wsServer) {
    this.wsServer.close();
    this.emit('close');
  }
};

DebugServer.prototype.address = function() {
  var address = this._httpServer.address();
  var config = this._config;
  address.url = buildInspectorUrl(config.webHost, address.port, config.debugPort, null, this._isHTTPS);
  return address;
};

DebugServer.prototype.wsAddress = function() {
  var address = this._httpServer.address();
  var config = this._config;
  address.url = buildWebSocketUrl(config.webHost, address.port, config.debugPort, null, this._isHTTPS);
  return address;
};

exports.DebugServer = DebugServer;
<commit_msg>JSON endpoints /json and /json/list should return a JSON array<commit_after>var http = require('http'),
    https = require('https'),
    EventEmitter = require('events').EventEmitter,
    inherits = require('util').inherits,
    extend = require('util')._extend,
    fs = require('fs'),
    path = require('path'),
    express = require('express'),
    favicon = require('serve-favicon'),
    WebSocketServer = require('ws').Server,
    Session = require('./session'),
    buildInspectorUrl = require('../index.js').buildInspectorUrl,
    buildWebSocketUrl = require('../index.js').buildWebSocketUrl,
    plugins = require('./plugins'),
    InspectorJson = plugins.InspectorJson,
    ProtocolJson = plugins.ProtocolJson,
    PLUGINS = plugins.cwd,
    OVERRIDES = path.join(__dirname, '../front-end-node'),
    WEBROOT = path.join(__dirname, '../front-end');

function debugAction(req, res) {
  if (!req.query.ws)
    return redirectToRoot.call(this, req, res);

  res.sendFile(path.join(WEBROOT, 'inspector.html'));
}

function redirectToRoot(req, res) {
  return res.redirect(this._getUrlFromReq(req));
}

function inspectorJson(req, res) {
  res.send(this._inspectorJson);
}

function emptyJson(req, res) {
  res.send('{}');
}

function jsonAction(req, res) {
  res.json([{
   'description': 'Node.js app (powered by node-inspector)',
   'devtoolsFrontendUrl': this.address().url,
   'id': process.pid,
   'title': process.title ||'',
   'type': 'page',
   'url': '',
   'webSocketDebuggerUrl': this.wsAddress().url
  }]);
}

function jsonVersionAction(req, res) {
  res.json({
    'browser': 'Node ' + process.version,
    'protocol-version': '1.1',
    'user-agent': 'Node ' + process.version,
    // webKit-version is a dummy value as it's used to match compatible DevTools front-ends
    'webKit-version': '537.36 (@181352)'
  });
}

function protocolJson(req, res) {
  res.send(this._protocolJson);
}

function handleWebSocketConnection(socket) {
  var debugPort = this._getDebuggerPort(socket.upgradeReq.url);
  this._createSession(debugPort, socket);
}

function handleServerListening() {
  this.emit('listening');
}

function handleServerError(err) {
  if (err._handledByInspector) return;
  err._handledByInspector = true;
  this.emit('error', err);
}

function DebugServer() {}

inherits(DebugServer, EventEmitter);

DebugServer.prototype.start = function(options) {
  this._config = extend({}, options);
  this._isHTTPS = this._config.sslKey && this._config.sslCert ? true : false;
  this._inspectorJson = new InspectorJson(this._config);
  this._protocolJson = new ProtocolJson(this._config);

  var app = express();
  var httpServer;

  if (this._isHTTPS) {
    httpServer = https.createServer({
      key: fs.readFileSync(this._config.sslKey, {encoding: 'utf8'}),
      cert: fs.readFileSync(this._config.sslCert, {encoding: 'utf8'})
    }, app);
  } else {
    httpServer = http.createServer(app);
  }

  this._httpServer = httpServer;

  app.use(favicon(path.join(__dirname, '../front-end-node/Images/favicon.png')));

  // Json handshake
  app.get('/json', jsonAction.bind(this));
  app.get('/json/list', jsonAction.bind(this));
  app.get('/json/version', jsonVersionAction.bind(this));

  // Dynamically generated front-end content
  app.get('/inspector.json', inspectorJson.bind(this));
  app.get('/protocol.json', protocolJson.bind(this));
  app.get('/InspectorBackendCommands.js', emptyJson);
  app.get('/SupportedCSSProperties.js', emptyJson);

  // Main routing
  app.get('/', debugAction.bind(this));
  app.get('/debug', redirectToRoot.bind(this));
  app.use('/node', express.static(OVERRIDES));
  app.use('/plugins', express.static(PLUGINS));
  app.use(express.static(WEBROOT));

  this.wsServer = new WebSocketServer({
    server: httpServer
  });
  this.wsServer.on('connection', handleWebSocketConnection.bind(this));
  this.wsServer.on('error', handleServerError.bind(this));

  httpServer.on('listening', handleServerListening.bind(this));
  httpServer.on('error', handleServerError.bind(this));
  httpServer.listen(this._config.webPort, this._config.webHost);
};

DebugServer.prototype._getDebuggerPort = function(url) {
  return parseInt((/[\?\&]port=(\d+)/.exec(url) || [null, this._config.debugPort])[1], 10);
};

DebugServer.prototype._getUrlFromReq = function(req) {
  var urlParts = req.headers.host.split(':'),
      debugPort = this._getDebuggerPort(req.url);
  return buildInspectorUrl(urlParts[0], urlParts[1], debugPort, null, this._isHTTPS);
};

DebugServer.prototype._createSession = function(debugPort, wsConnection) {
  return new Session(this._config, debugPort, wsConnection);
};

DebugServer.prototype.close = function() {
  if (this.wsServer) {
    this.wsServer.close();
    this.emit('close');
  }
};

DebugServer.prototype.address = function() {
  var address = this._httpServer.address();
  var config = this._config;
  address.url = buildInspectorUrl(config.webHost, address.port, config.debugPort, null, this._isHTTPS);
  return address;
};

DebugServer.prototype.wsAddress = function() {
  var address = this._httpServer.address();
  var config = this._config;
  address.url = buildWebSocketUrl(config.webHost, address.port, config.debugPort, null, this._isHTTPS);
  return address;
};

exports.DebugServer = DebugServer;
<|endoftext|>


<commit_before>/**
 * @file: Implementation of FNV-1 32bit hash algorithm
 * @see: http://isthe.com/chongo/tech/comp/fnv/
 */

(function(exports){

    /**
     * Constant FNV-1 32bit prime number
     */
    var FNV132_PRIME = 16777619;

    /**
     * High 16 bits of FNV-1 32bit prime number
     */
    var FNV132_PRIME_H = (FNV132_PRIME >>> 16) & 0xFFFF;

    /**
     * Low 16 bits of FNV-1 32bit prime number
     */
    var FNV132_PRIME_L = FNV132_PRIME & 0xFFFF;

    /**
     * Constant FNV-1 32bit offset basis
     */
    var FNV132_INIT = 2166136261;

    /**
     * Create and initialize a new 32bit FNV-1 hash object.
     */
    function FNV132Hash() {
        this.hash = FNV132_INIT;
    }

    /**
     * Update the hash with the given string and return the new hash value. No
     * calculation is performed when the bytes-parameter is left out.
     */
    FNV132Hash.prototype.update = function (bytes) {
        var i, ah, al;
        for (i=0; i<(bytes && bytes.length); i++) {
            // A rather complicated way to multiply this.hash times FNV132_PRIME.
            // Regrettably a workaround is necessary because the value of a Number
            // class is represented as a 64bit floating point internally. This can
            // lead to precision issues if the factors are big enough.
            //
            // Each factor is separated into two 16bit numbers by shifting left the
            // high part and masking the low one.
            ah = (this.hash >>> 16) & 0xFFFF;
            al = this.hash & 0xFFFF;

            // Now the both low parts are multiplied. Also each low-high pair gets
            // multiplied. There is no reason to multiply the high-high pair
            // because overflow is guaranteed here.
            // The result is the sum of the three multiplications. Because of the
            // floating point nature of JavaScript numbers, bitwise operations are
            // *not* faster than multiplications. Therefore we do not use "<< 16"
            // here.
            this.hash = (al * FNV132_PRIME_L) + ((ah * FNV132_PRIME_L) * 0x10000) + ((al * FNV132_PRIME_H) * 0x10000);

            this.hash ^= bytes.charCodeAt(i);
        }

        // Get rid of signum
        return this.hash >>> 0;
    };

    // CommonJS exports
    exports.Hash = FNV132Hash;

}(typeof exports === 'undefined' ? (DeltaJS.fnv132={}) : exports));
<commit_msg>Reformat fnv-1 code<commit_after>/**
 * @file: Implementation of FNV-1 32bit hash algorithm
 * @see: http://isthe.com/chongo/tech/comp/fnv/
 */

(function(exports){

    /**
     * Constant FNV-1 32bit prime number
     */
    var FNV132_PRIME = 16777619;

    /**
     * High 16 bits of FNV-1 32bit prime number
     */
    var FNV132_PRIME_H = (FNV132_PRIME >>> 16) & 0xFFFF;

    /**
     * Low 16 bits of FNV-1 32bit prime number
     */
    var FNV132_PRIME_L = FNV132_PRIME & 0xFFFF;

    /**
     * Constant FNV-1 32bit offset basis
     */
    var FNV132_INIT = 2166136261;

    /**
     * Create and initialize a new 32bit FNV-1 hash object.
     */
    function FNV132Hash() {
        this.hash = FNV132_INIT;
    }

    /**
     * Update the hash with the given string and return the new hash value. No
     * calculation is performed when the bytes-parameter is left out.
     */
    FNV132Hash.prototype.update = function (bytes) {
        var i, ah, al;
        for (i=0; i<(bytes && bytes.length); i++) {
            // A rather complicated way to multiply this.hash times
            // FNV132_PRIME.  Regrettably a workaround is necessary because the
            // value of a Number class is represented as a 64bit floating point
            // internally. This can lead to precision issues if the factors are
            // big enough.
            //
            // Each factor is separated into two 16bit numbers by shifting left
            // the high part and masking the low one.
            ah = (this.hash >>> 16) & 0xFFFF;
            al = this.hash & 0xFFFF;

            // Now the both low parts are multiplied. Also each low-high pair
            // gets multiplied. There is no reason to multiply the high-high
            // pair because overflow is guaranteed here.  The result is the sum
            // of the three multiplications. Because of the floating point
            // nature of JavaScript numbers, bitwise operations are *not*
            // faster than multiplications. Therefore we do not use "<< 16"
            // here but instead "* 0x100000".
            this.hash = (al * FNV132_PRIME_L) +
                ((ah * FNV132_PRIME_L) * 0x10000) +
                ((al * FNV132_PRIME_H) * 0x10000);

            this.hash ^= bytes.charCodeAt(i);
        }

        // Get rid of signum
        return this.hash >>> 0;
    };

    // CommonJS exports
    exports.Hash = FNV132Hash;

}(typeof exports === 'undefined' ? (DeltaJS.fnv132={}) : exports));
<|endoftext|>


<commit_before>import log from './logger';
import validator from 'validate.js';

let desiredCapabilityConstraints = {
  platformName: {
    presence: true,
    isString: true,
    inclusion: [
      'iOS',
      'Android',
      'FirefoxOS',
      'Fake'
    ]
  },
  deviceName: {
    presence: true,
    isString: true
  },
  platformVersion: {},
  newCommandTimeout: {
    isNumber: true
  },
  automationName: {
    inclusion: [
      'Appium',
      'Selendroid'
    ]
  },
  autoLaunch: {
    isBoolean: true
  },
  udid: {
    isString: true
  },
  orientation: {
    inclusion: [
      'LANDSCAPE',
      'PORTRAIT'
    ]
  },
  autoWebview: {
    isBoolean: true
  },
  noReset: {
    isBoolean: true
  },
  fullReset: {
    isBoolean: true
  }
};

validator.validators.isString = function (value) {
  if (typeof value === 'string') {
    return null;
  }

  if (typeof value === 'undefined') {
    return null;
  }

  return `must be of type string`;
};
validator.validators.isNumber = function (value) {
  if (typeof value === 'number') {
    return null;
  }

  if (typeof value === 'undefined') {
    return null;
  }

  return `must be of type number`;
};
validator.validators.isBoolean = function (value) {
  if (typeof value === 'boolean') {
    return null;
  }

  if (typeof value === 'undefined') {
    return null;
  }

  return `must be of type boolean`;
};
validator.validators.isObject = function (value) {
  if (typeof value === 'object') {
    return null;
  }

  if (typeof value === 'undefined') {
    return null;
  }

  return `must be of type object`;
};
validator.validators.deprecated = function (value, options, key) {
  if (options) {
    log.warn(`${key} is a deprecated capability`);
  }
  return null;
};

export { desiredCapabilityConstraints, validator };
<commit_msg>add WebDriverAgent automationName<commit_after>import log from './logger';
import validator from 'validate.js';

let desiredCapabilityConstraints = {
  platformName: {
    presence: true,
    isString: true,
    inclusion: [
      'iOS',
      'Android',
      'FirefoxOS',
      'Fake'
    ]
  },
  deviceName: {
    presence: true,
    isString: true
  },
  platformVersion: {},
  newCommandTimeout: {
    isNumber: true
  },
  automationName: {
    inclusion: [
      'Appium',
      'Selendroid',
      'WebDriverAgent'
    ]
  },
  autoLaunch: {
    isBoolean: true
  },
  udid: {
    isString: true
  },
  orientation: {
    inclusion: [
      'LANDSCAPE',
      'PORTRAIT'
    ]
  },
  autoWebview: {
    isBoolean: true
  },
  noReset: {
    isBoolean: true
  },
  fullReset: {
    isBoolean: true
  }
};

validator.validators.isString = function (value) {
  if (typeof value === 'string') {
    return null;
  }

  if (typeof value === 'undefined') {
    return null;
  }

  return `must be of type string`;
};
validator.validators.isNumber = function (value) {
  if (typeof value === 'number') {
    return null;
  }

  if (typeof value === 'undefined') {
    return null;
  }

  return `must be of type number`;
};
validator.validators.isBoolean = function (value) {
  if (typeof value === 'boolean') {
    return null;
  }

  if (typeof value === 'undefined') {
    return null;
  }

  return `must be of type boolean`;
};
validator.validators.isObject = function (value) {
  if (typeof value === 'object') {
    return null;
  }

  if (typeof value === 'undefined') {
    return null;
  }

  return `must be of type object`;
};
validator.validators.deprecated = function (value, options, key) {
  if (options) {
    log.warn(`${key} is a deprecated capability`);
  }
  return null;
};

export { desiredCapabilityConstraints, validator };
<|endoftext|>


<commit_before>import _ from "underscore";
import CreateConfig from "./CreateConfig";
import SpecRef from "./SpecRef";

class SpecRole {
    constructor(role) {
        this._role = role;
    }
    get specKey() {
        return this._role[0].toLowerCase() + this._role.slice(1);
    }
}

/**
 * @param  {string|Array} roleNames
 */
export default class SpecRoles {
    constructor(names) {
        names = Array.isArray(names) ? names : [names];
        this._roles = names.map((role) => new SpecRole(role));
    }
    get specKey() {
        return this.primaryRole.specKey;
    }
    writeAliasesTo(config) {
        // RETURN EARLY if no secondary roles. Nothing to alias.
        if (this._roles.length === 1) {
            return;
        }
        _.extend(config, _.object(
            this.secondaryRoles.map(
                (role) => [
                    role.specKey,
                    new CreateConfig({
                        fn: _.identity,
                        args: [new SpecRef(this.primaryRole.specKey)]
                    }).value
                ]
            )
        ));
    }
    get primaryRole() {
        return this._roles[0];
    }
    get secondaryRoles() {
        return this._roles.slice(1);
    }
    toSpecKeys() {
        return this._roles.map((role) => role.specKey);
    }
}
<commit_msg>Update SpecRole to not lower case first char.<commit_after>import _ from "underscore";
import CreateConfig from "./CreateConfig";
import SpecRef from "./SpecRef";

class SpecRole {
    constructor(role) {
        this._role = role;
    }
    get specKey() {
        return this._role;
    }
    toString() {
        return this._role;
    }
}

/**
 * @param  {string|Array} roleNames
 */
export default class SpecRoles {
    constructor(names) {
        names = Array.isArray(names) ? names : [names];
        this._roles = names.map((role) => new SpecRole(role));
    }
    get specKey() {
        return this.primaryRole.specKey;
    }
    writeAliasesTo(config) {
        // RETURN EARLY if no secondary roles. Nothing to alias.
        if (this._roles.length === 1) {
            return;
        }
        _.extend(config, _.object(
            this.secondaryRoles.map(
                (role) => [
                    role.specKey,
                    new CreateConfig({
                        fn: _.identity,
                        args: [new SpecRef(this.primaryRole.specKey)]
                    }).value
                ]
            )
        ));
    }
    get primaryRole() {
        return this._roles[0];
    }
    get secondaryRoles() {
        return this._roles.slice(1);
    }
    toSpecKeys() {
        return this._roles.map((role) => role.specKey);
    }
}
<|endoftext|>


<commit_before>'use strict';

var rsvp = require('rsvp');
var Inflector = require('inflected');
var _ = require('lodash');

function Serializer(model, options) {
  this.model = model;
  this.options = options || {};
  this.attributes = this.constructor.prototype.attributes || [];
  this.belongTos = [];
  this.hasManys = [];
  if (this.options.rootKey) {
    this.rootKey = this.options.rootKey;
  }
}

Serializer.prototype.serialize = function(object){
  if (!this.model) {
    return rsvp.resolve(object);
  }

  return new rsvp.Promise(function(resolve, reject) {
    var attrs = this.attributes;
    var length = this.attributes.length;
    var rootKey = this.rootKey;
    var alreadySerialized = false;

    if (this.options.sideload) {
      rootKey = Inflector.pluralize(rootKey);
    }

    rootKey = Inflector.camelize(rootKey, false);

    if (object[rootKey] && Array.isArray(object[rootKey])) {
      var alreadySerialized = _.find(object[rootKey], function(model) {
        return model.guid == this.serializeAttribute('guid');
      }, this);
      if (alreadySerialized) {
        return resolve(null);
      }
    }

    var attribute;
    var i;
    var serialized = Object.create(null);

    for (i = 0; i < length; i++) {
      attribute = attrs[i];
      serialized[attribute] = this.serializeAttribute(attribute);
    }

    var belongTos = this.belongTos.map(function(belongsTo) {
      return this.serializeBelongsTo(belongsTo, object, serialized);
    }, this);

    var hasManys = this.hasManys.map(function(hasMany) {
      return this.serializeHasMany(hasMany, object, serialized);
    }, this);

    if (this.options.sideload) {
      object[rootKey] = object[rootKey] || [];
      object[rootKey].push(serialized);
    } else {
      object[rootKey] = serialized;
    }

    return rsvp.all(belongTos.concat(hasManys)).then(function(){
      return object;
    })
    .catch(reject);

  }.bind(this));
};

Serializer.prototype.serializeAttribute = function(attribute){
  if (typeof this[attribute] === 'function') {
    return this[attribute]();
  }
  return this.model[attribute];
};

Serializer.prototype.serializeIntoHash = function(hash) {
  var rootKey = Inflector.pluralize(this.rootKey);
  var models = hash[rootKey] = hash[rootKey] || {};
};

Serializer.prototype.serializeBelongsTo = function(belongsTo, hash, modelHash) {
  var getter = 'get' + belongsTo.name;
  return this.model[getter]().then(function(relationship) {
    return new belongsTo.serializer(relationship, {rootKey: belongsTo.type, sideload: true}).serialize(hash).then(function(relationshipHash) {
      var belongsToKey = Inflector.camelize(belongsTo.name, false);
      modelHash[belongsToKey] = relationship && relationship[belongsTo.primaryKey];
      return hash;
    });
  });
};

Serializer.prototype.serializeHasMany = function(hasMany, hash, modelHash) {
  var hasManyName = Inflector.pluralize(hasMany.name);
  var getter = 'get' + Inflector.capitalize(hasManyName);
  return this.model[getter]().then(function(models) {
    return rsvp.map(models, function(relationship) {
      return new hasMany.serializer(relationship, {rootKey: hasMany.type, sideload: true}).serialize(hash);
    }).then(function(){
      return models.map(function(model) {
        return model[hasMany.primaryKey];
      });
    });
  }).then(function(ids) {
    modelHash[hasManyName.toLowerCase()] = ids;
    return hash;
  });
};

module.exports = Serializer;
<commit_msg>allow only loading IDS<commit_after>'use strict';

var rsvp = require('rsvp');
var Inflector = require('inflected');
var _ = require('lodash');

function Serializer(model, options) {
  this.model = model;
  this.options = options || {};
  this.attributes = this.constructor.prototype.attributes || [];
  this.belongTos = [];
  this.hasManys = [];
  if (this.options.rootKey) {
    this.rootKey = this.options.rootKey;
  }
}

Serializer.prototype.serialize = function(object){
  if (!this.model) {
    return rsvp.resolve(object);
  }

  return new rsvp.Promise(function(resolve, reject) {
    var attrs = this.attributes;
    var length = this.attributes.length;
    var rootKey = this.rootKey;
    var alreadySerialized = false;

    if (this.options.sideload) {
      rootKey = Inflector.pluralize(rootKey);
    }

    rootKey = Inflector.camelize(rootKey, false);

    if (object[rootKey] && Array.isArray(object[rootKey])) {
      var alreadySerialized = _.find(object[rootKey], function(model) {
        return model.guid == this.serializeAttribute('guid');
      }, this);
      if (alreadySerialized) {
        return resolve(object);
      }
    }

    var attribute;
    var i;
    var serialized = Object.create(null);

    for (i = 0; i < length; i++) {
      attribute = attrs[i];
      serialized[attribute] = this.serializeAttribute(attribute);
    }

    var belongTos = this.belongTos.map(function(belongsTo) {
      return this.serializeBelongsTo(belongsTo, object, serialized);
    }, this);

    var hasManys = this.hasManys.map(function(hasMany) {
      return this.serializeHasMany(hasMany, object, serialized);
    }, this);

    if (this.options.sideload) {
      object[rootKey] = object[rootKey] || [];
      object[rootKey].push(serialized);
    } else {
      object[rootKey] = serialized;
    }

    return rsvp.all(belongTos.concat(hasManys)).then(function(){
      return resolve(object);
    })
    .catch(reject);

  }.bind(this));
};

Serializer.prototype.serializeAttribute = function(attribute){
  if (typeof this[attribute] === 'function') {
    return this[attribute]();
  }
  return this.model[attribute];
};

Serializer.prototype.serializeIntoHash = function(hash) {
  var rootKey = Inflector.pluralize(this.rootKey);
  var models = hash[rootKey] = hash[rootKey] || {};
};

Serializer.prototype.serializeBelongsTo = function(belongsTo, hash, modelHash) {
  var getter = 'get' + belongsTo.name;
  var idGetter = 'get' + belongsTo.name + 'ID';
  var id;
  return this.model[getter]().then(function(relationship) {
    return new belongsTo.serializer(relationship, {rootKey: belongsTo.type, sideload: !belongsTo.onlyIDs}).serialize(hash).then(function(relationshipHash) {
      var belongsToKey = Inflector.camelize(belongsTo.name, false);
      modelHash[belongsToKey] = relationship && relationship[belongsTo.primaryKey];
      return hash;
    });
  });
};

Serializer.prototype.serializeHasMany = function(hasMany, hash, modelHash) {
  var hasManyName = Inflector.camelize(Inflector.pluralize(hasMany.name), false);
  var getter = 'get' + Inflector.capitalize(hasManyName);

  return this.model[getter]().then(function(models) {
    return rsvp.map(models, function(relationship) {
      if (!hasMany.onlyIDs) {
        return new hasMany.serializer(relationship, {rootKey: hasMany.type, sideload: true}).serialize(hash);
      } else {
        return rsvp.resolve();
      }
    }).then(function(){
      return models.map(function(model) {
        return model[hasMany.primaryKey];
      });
    });
  }).then(function(ids) {
    modelHash[hasManyName] = ids;
    return hash;
  });
};

module.exports = Serializer;
<|endoftext|>


<commit_before>'use strict';

// Copyright 2011 Chris Williams <chris@iterativedesigns.com>

// Require serialport binding from pre-compiled binaries using
// node-pre-gyp, if something fails or package not available fallback
// to regular build from source.

// 3rd Party Dependencies
var debug = require('debug')('serialport');

// shims
var assign = require('object.assign').getPolyfill();

// Internal Dependencies
var SerialPortBinding = require('./bindings');
var parsers = require('./parsers');

// Built-ins Dependencies
var EventEmitter = require('events').EventEmitter;
var fs = require('fs');
var stream = require('stream');
var util = require('util');

// Setup the factory
var factory = new EventEmitter();
factory.parsers = parsers;
factory.list = SerialPortBinding.list;

//  VALIDATION ARRAYS
var DATABITS = [5, 6, 7, 8];
var STOPBITS = [1, 1.5, 2];
var PARITY = ['none', 'even', 'mark', 'odd', 'space'];
var FLOWCONTROLS = ['xon', 'xoff', 'xany', 'rtscts'];
var SET_OPTIONS = ['brk', 'cts', 'dtr', 'dts', 'rts'];

// Stuff from ReadStream, refactored for our usage:
var kPoolSize = 40 * 1024;
var kMinPoolSpace = 128;

var defaultSettings = {
  baudRate: 9600,
  parity: 'none',
  xon: false,
  xoff: false,
  xany: false,
  rtscts: false,
  hupcl: true,
  dataBits: 8,
  stopBits: 1,
  bufferSize: 64 * 1024,
  parser: parsers.raw,
  platformOptions: SerialPortBinding.platformOptions
};

var defaultSetFlags = {
  brk: false,
  cts: false,
  dtr: true,
  dts: false,
  rts: true
};

// deprecate the lowercase version of these options next major release
var LOWERCASE_OPTIONS = [
  'baudRate',
  'dataBits',
  'stopBits',
  'bufferSize',
  'platformOptions',
  'flowControl'
];

function correctOptions(options) {
  LOWERCASE_OPTIONS.forEach(function(name) {
    var lowerName = name.toLowerCase();
    if (options.hasOwnProperty(lowerName)) {
      var value = options[lowerName];
      delete options[lowerName];
      options[name] = value;
    }
  });
  return options;
}

function SerialPort(path, options, openImmediately, callback) {
  var args = Array.prototype.slice.call(arguments);
  callback = args.pop();
  if (typeof callback !== 'function') {
    callback = null;
  }

  options = (typeof options !== 'function') && options || {};

  if (openImmediately === undefined || openImmediately === null) {
    openImmediately = true;
  }

  stream.Stream.call(this);
  callback = callback || function (err) {
    if (err) {
      if (this._events.error) {
        this.emit('error', err);
      } else {
        factory.emit('error', err);
      }
    }
  }.bind(this);

  if (!path) {
    return callback(new Error('Invalid port specified: ' + path));
  }
  this.path = path;

  var correctedOptions = correctOptions(options);
  var settings = assign({}, defaultSettings, correctedOptions);

  if (DATABITS.indexOf(settings.dataBits) === -1) {
    return callback(new Error('Invalid "databits": ' + settings.dataBits));
  }

  if (STOPBITS.indexOf(settings.stopBits) === -1) {
    return callback(new Error('Invalid "stopbits": ' + settings.stopbits));
  }

  if (PARITY.indexOf(settings.parity) === -1) {
    return callback(new Error('Invalid "parity": ' + settings.parity));
  }

  var fc = settings.flowControl;
  if (fc === true) {
    // Why!?
    settings.rtscts = true;
  } else if (Array.isArray(fc)) {
    for (var i = fc.length - 1; i >= 0; i--) {
      var fcSetting = fc[i].toLowerCase();
      if (FLOWCONTROLS.indexOf(fcSetting) > -1) {
        settings[fcSetting] = true;
      } else {
        return callback(new Error('Invalid flowControl option: ' + fcSetting));
      }
    }
  }

  // TODO remove this option
  settings.dataCallback = options.dataCallback || function (data) {
    settings.parser(this, data);
  }.bind(this);

  // TODO remove this option
  settings.disconnectedCallback = options.disconnectedCallback || function (err) {
    if (this.closing) {
      return;
    }
    if (!err) {
      err = new Error('Disconnected');
    }
    this.emit('disconnect', err);
  }.bind(this);

  this.fd = null;
  this.paused = true;
  this.opening = false;
  this.closing = false;

  if (process.platform !== 'win32') {
    this.bufferSize = settings.bufferSize;
    this.readable = true;
    this.reading = false;
  }

  this.options = settings;

  if (openImmediately) {
    process.nextTick(function () {
      this.open(callback);
    }.bind(this));
  }
}

factory.SerialPort = SerialPort;
util.inherits(SerialPort, stream.Stream);

SerialPort.prototype._error = function(error, callback) {
  if (callback) {
    callback(error);
  } else {
    this.emit('error', error);
  }
};

SerialPort.prototype.open = function (callback) {
  if (this.isOpen()) {
    return this._error(new Error('Port is already open'), callback);
  }

  if (this.opening) {
    return this._error(new Error('Port is opening'), callback);
  }

  this.paused = true;
  this.readable = true;
  this.reading = false;
  this.opening = true;

  var self = this;
  SerialPortBinding.open(this.path, this.options, function (err, fd) {
    if (err) {
      debug('SerialPortBinding.open had an error', err);
      return self._error(err, callback);
    }
    self.fd = fd;
    self.paused = false;
    self.opening = false;

    if (process.platform !== 'win32') {
      self.serialPoller = new SerialPortBinding.SerialportPoller(self.fd, function (err) {
        if (!err) {
          self._read();
        } else {
          self.disconnected(err);
        }
      });
      self.serialPoller.start();
    }

    self.emit('open');
    if (callback) { callback() }
  });
};

// underlying code is written to update all options, but for now
// only baud is respected as I don't want to duplicate all the option
// verification code above
SerialPort.prototype.update = function (options, callback) {
  if (!this.isOpen()) {
    debug('update attempted, but port is not open');
    return this._error(new Error('Port is not open'), callback);
  }

  var correctedOptions = correctOptions(options);
  var settings = assign({}, defaultSettings, correctedOptions);
  this.options.baudRate = settings.baudRate;

  SerialPortBinding.update(this.fd, this.options, function (err) {
    if (err) {
      return this._error(err, callback);
    }
    this.emit('open');
    if (callback) { callback() }
  }.bind(this));
};

SerialPort.prototype.isOpen = function() {
  return this.fd !== null && !this.closing;
};

SerialPort.prototype.write = function (buffer, callback) {
  if (!this.isOpen()) {
    debug('write attempted, but port is not open');
    return this._error(new Error('Port is not open'), callback);
  }

  if (!Buffer.isBuffer(buffer)) {
    buffer = new Buffer(buffer);
  }
  debug('write data: ' + JSON.stringify(buffer));

  var self = this;
  SerialPortBinding.write(this.fd, buffer, function (err, results) {
    if (callback) {
      callback(err, results);
    } else {
      if (err) {
        self.emit('error', err);
      }
    }
  });
};

if (process.platform !== 'win32') {
  SerialPort.prototype._read = function () {
    var self = this;
    if (!self.readable || self.paused || self.reading || this.closing) {
      return;
    }

    self.reading = true;

    if (!self.pool || self.pool.length - self.pool.used < kMinPoolSpace) {
      // discard the old pool. Can't add to the free list because
      // users might have references to slices on it.
      self.pool = new Buffer(kPoolSize);
      self.pool.used = 0;
    }

    // Grab another reference to the pool in the case that while we're in the
    // thread pool another read() finishes up the pool, and allocates a new
    // one.
    var toRead = Math.min(self.pool.length - self.pool.used, ~~self.bufferSize);
    var start = self.pool.used;

    function afterRead(err, bytesRead, readPool, bytesRequested) {
      self.reading = false;
      if (err) {
        if (err.code && err.code === 'EAGAIN') {
          if (!self.closing && self.isOpen()) {
            self.serialPoller.start();
          }
        // handle edge case were mac/unix doesn't clearly know the error.
        } else if (err.code && (err.code === 'EBADF' || err.code === 'ENXIO' || (err.errno === -1 || err.code === 'UNKNOWN'))) {
          self.disconnected(err);
        } else {
          self.fd = null;
          self.readable = false;
          self.emit('error', err);
        }
      } else {
        // Since we will often not read the number of bytes requested,
        // let's mark the ones we didn't need as available again.
        self.pool.used -= bytesRequested - bytesRead;

        if (bytesRead === 0) {
          if (self.isOpen()) {
            self.serialPoller.start();
          }
        } else {
          var b = self.pool.slice(start, start + bytesRead);

          // do not emit events if the stream is paused
          if (self.paused) {
            self.buffer = Buffer.concat([self.buffer, b]);
            return;
          }
          self._emitData(b);

          // do not emit events anymore after we declared the stream unreadable
          if (!self.readable) {
            return;
          }
          self._read();
        }
      }
    }

    fs.read(self.fd, self.pool, self.pool.used, toRead, null, function (err, bytesRead) {
      var readPool = self.pool;
      var bytesRequested = toRead;
      afterRead(err, bytesRead, readPool, bytesRequested);
    });

    self.pool.used += toRead;
  };

  SerialPort.prototype._emitData = function (data) {
    this.options.dataCallback(data);
  };

  SerialPort.prototype.pause = function () {
    var self = this;
    self.paused = true;
  };

  SerialPort.prototype.resume = function () {
    var self = this;
    self.paused = false;

    if (self.buffer) {
      var buffer = self.buffer;
      self.buffer = null;
      self._emitData(buffer);
    }

    // No longer open?
    if (!this.isOpen()) {
      return;
    }

    self._read();
  };
} // if !'win32'

SerialPort.prototype.disconnected = function (err) {
  var self = this;
  var fd = self.fd;

  // send notification of disconnect
  if (self.options.disconnectedCallback) {
    self.options.disconnectedCallback(err);
  } else {
    self.emit('disconnect', err);
  }
  self.paused = true;
  self.closing = true;

  self.emit('close');

  // clean up all other items
  fd = self.fd;

  try {
    SerialPortBinding.close(fd, function (err) {
      if (err) {
        debug('Disconnect completed with error: ' + JSON.stringify(err));
      } else {
        debug('Disconnect completed.');
      }
    });
  } catch (e) {
    debug('Disconnect completed with an exception: ' + JSON.stringify(e));
  }

  self.closing = false;
  self.fd = null;

  if (process.platform !== 'win32') {
    self.readable = false;
    self.serialPoller.close();
  }
};

SerialPort.prototype.close = function (callback) {
  var self = this;
  var fd = self.fd;

  if (self.closing) {
    debug('close attempted, but port is already closing');
    return this._error(new Error('Port is not open'), callback);
  }

  if (!this.isOpen()) {
    debug('close attempted, but port is not open');
    return this._error(new Error('Port is not open'), callback);
  }

  self.closing = true;

  // Stop polling before closing the port.
  if (process.platform !== 'win32') {
    self.readable = false;
    self.serialPoller.close();
  }

  try {
    SerialPortBinding.close(fd, function (err) {
      self.closing = false;
      if (err) {
        debug('SerialPortBinding.close had an error', err);
        return self._error(err, callback);
      }

      self.fd = null;
      self.emit('close');
      if (callback) { callback() }
    });
  } catch (err) {
    this.closing = false;
    debug('SerialPortBinding.close had an throwing error', err);
    return this._error(err, callback);
  }
};

SerialPort.prototype.flush = function (callback) {
  var self = this;
  var fd = self.fd;

  if (!this.isOpen()) {
    debug('flush attempted, but port is not open');
    return this._error(new Error('Port is not open'), callback);
  }

  SerialPortBinding.flush(fd, function (err, result) {
    if (callback) {
      callback(err, result);
    } else if (err) {
      self.emit('error', err);
    }
  });
};

SerialPort.prototype.set = function (options, callback) {
  if (!this.isOpen()) {
    debug('set attempted, but port is not open');
    return this._error(new Error('Port is not open'), callback);
  }

  options = options || {};
  if (!callback && typeof options === 'function') {
    callback = options;
    options = {};
  }

  var settings = {};
  for (var i = SET_OPTIONS.length - 1; i >= 0; i--) {
    var flag = SET_OPTIONS[i];
    if (options[flag] !== undefined) {
      settings[flag] = options[flag];
    } else {
      settings[flag] = defaultSetFlags[flag];
    }
  }

  SerialPortBinding.set(this.fd, settings, function (err, result) {
    if (err) {
      debug('SerialPortBinding.set had an error', err);
      return this._error(err, callback);
    }
    if (callback) { callback(null, result) }
  }.bind(this));
};

SerialPort.prototype.drain = function (callback) {
  var self = this;
  var fd = this.fd;

  if (!this.isOpen()) {
    debug('drain attempted, but port is not open');
    return this._error(new Error('Port is not open'), callback);
  }

  SerialPortBinding.drain(fd, function (err, result) {
    if (callback) {
      callback(err, result);
    } else if (err) {
      self.emit('error', err);
    }
  });
};

module.exports = factory;
<commit_msg>Remove the last bit of references to `self` (#765)<commit_after>'use strict';

// Copyright 2011 Chris Williams <chris@iterativedesigns.com>

// Require serialport binding from pre-compiled binaries using
// node-pre-gyp, if something fails or package not available fallback
// to regular build from source.

// 3rd Party Dependencies
var debug = require('debug')('serialport');

// shims
var assign = require('object.assign').getPolyfill();

// Internal Dependencies
var SerialPortBinding = require('./bindings');
var parsers = require('./parsers');

// Built-ins Dependencies
var EventEmitter = require('events').EventEmitter;
var fs = require('fs');
var stream = require('stream');
var util = require('util');

// Setup the factory
var factory = new EventEmitter();
factory.parsers = parsers;
factory.list = SerialPortBinding.list;

//  VALIDATION ARRAYS
var DATABITS = [5, 6, 7, 8];
var STOPBITS = [1, 1.5, 2];
var PARITY = ['none', 'even', 'mark', 'odd', 'space'];
var FLOWCONTROLS = ['xon', 'xoff', 'xany', 'rtscts'];
var SET_OPTIONS = ['brk', 'cts', 'dtr', 'dts', 'rts'];

// Stuff from ReadStream, refactored for our usage:
var kPoolSize = 40 * 1024;
var kMinPoolSpace = 128;

var defaultSettings = {
  baudRate: 9600,
  parity: 'none',
  xon: false,
  xoff: false,
  xany: false,
  rtscts: false,
  hupcl: true,
  dataBits: 8,
  stopBits: 1,
  bufferSize: 64 * 1024,
  parser: parsers.raw,
  platformOptions: SerialPortBinding.platformOptions
};

var defaultSetFlags = {
  brk: false,
  cts: false,
  dtr: true,
  dts: false,
  rts: true
};

// deprecate the lowercase version of these options next major release
var LOWERCASE_OPTIONS = [
  'baudRate',
  'dataBits',
  'stopBits',
  'bufferSize',
  'platformOptions',
  'flowControl'
];

function correctOptions(options) {
  LOWERCASE_OPTIONS.forEach(function(name) {
    var lowerName = name.toLowerCase();
    if (options.hasOwnProperty(lowerName)) {
      var value = options[lowerName];
      delete options[lowerName];
      options[name] = value;
    }
  });
  return options;
}

function SerialPort(path, options, openImmediately, callback) {
  var args = Array.prototype.slice.call(arguments);
  callback = args.pop();
  if (typeof callback !== 'function') {
    callback = null;
  }

  options = (typeof options !== 'function') && options || {};

  if (openImmediately === undefined || openImmediately === null) {
    openImmediately = true;
  }

  stream.Stream.call(this);
  callback = callback || function (err) {
    if (err) {
      if (this._events.error) {
        this.emit('error', err);
      } else {
        factory.emit('error', err);
      }
    }
  }.bind(this);

  if (!path) {
    return callback(new Error('Invalid port specified: ' + path));
  }
  this.path = path;

  var correctedOptions = correctOptions(options);
  var settings = assign({}, defaultSettings, correctedOptions);

  if (DATABITS.indexOf(settings.dataBits) === -1) {
    return callback(new Error('Invalid "databits": ' + settings.dataBits));
  }

  if (STOPBITS.indexOf(settings.stopBits) === -1) {
    return callback(new Error('Invalid "stopbits": ' + settings.stopbits));
  }

  if (PARITY.indexOf(settings.parity) === -1) {
    return callback(new Error('Invalid "parity": ' + settings.parity));
  }

  var fc = settings.flowControl;
  if (fc === true) {
    // Why!?
    settings.rtscts = true;
  } else if (Array.isArray(fc)) {
    for (var i = fc.length - 1; i >= 0; i--) {
      var fcSetting = fc[i].toLowerCase();
      if (FLOWCONTROLS.indexOf(fcSetting) > -1) {
        settings[fcSetting] = true;
      } else {
        return callback(new Error('Invalid flowControl option: ' + fcSetting));
      }
    }
  }

  // TODO remove this option
  settings.dataCallback = options.dataCallback || function (data) {
    settings.parser(this, data);
  }.bind(this);

  // TODO remove this option
  settings.disconnectedCallback = options.disconnectedCallback || function (err) {
    if (this.closing) {
      return;
    }
    if (!err) {
      err = new Error('Disconnected');
    }
    this.emit('disconnect', err);
  }.bind(this);

  this.fd = null;
  this.paused = true;
  this.opening = false;
  this.closing = false;

  if (process.platform !== 'win32') {
    this.bufferSize = settings.bufferSize;
    this.readable = true;
    this.reading = false;
  }

  this.options = settings;

  if (openImmediately) {
    process.nextTick(function () {
      this.open(callback);
    }.bind(this));
  }
}

factory.SerialPort = SerialPort;
util.inherits(SerialPort, stream.Stream);

SerialPort.prototype._error = function(error, callback) {
  if (callback) {
    callback(error);
  } else {
    this.emit('error', error);
  }
};

SerialPort.prototype.open = function (callback) {
  if (this.isOpen()) {
    return this._error(new Error('Port is already open'), callback);
  }

  if (this.opening) {
    return this._error(new Error('Port is opening'), callback);
  }

  this.paused = true;
  this.readable = true;
  this.reading = false;
  this.opening = true;

  SerialPortBinding.open(this.path, this.options, function (err, fd) {
    if (err) {
      debug('SerialPortBinding.open had an error', err);
      return this._error(err, callback);
    }
    this.fd = fd;
    this.paused = false;
    this.opening = false;

    if (process.platform !== 'win32') {
      this.serialPoller = new SerialPortBinding.SerialportPoller(this.fd, function (err) {
        if (!err) {
          this._read();
        } else {
          this.disconnected(err);
        }
      }.bind(this));
      this.serialPoller.start();
    }

    this.emit('open');
    if (callback) { callback() }
  }.bind(this));
};

// underlying code is written to update all options, but for now
// only baud is respected as I don't want to duplicate all the option
// verification code above
SerialPort.prototype.update = function (options, callback) {
  if (!this.isOpen()) {
    debug('update attempted, but port is not open');
    return this._error(new Error('Port is not open'), callback);
  }

  var correctedOptions = correctOptions(options);
  var settings = assign({}, defaultSettings, correctedOptions);
  this.options.baudRate = settings.baudRate;

  SerialPortBinding.update(this.fd, this.options, function (err) {
    if (err) {
      return this._error(err, callback);
    }
    this.emit('open');
    if (callback) { callback() }
  }.bind(this));
};

SerialPort.prototype.isOpen = function() {
  return this.fd !== null && !this.closing;
};

SerialPort.prototype.write = function (buffer, callback) {
  if (!this.isOpen()) {
    debug('write attempted, but port is not open');
    return this._error(new Error('Port is not open'), callback);
  }

  if (!Buffer.isBuffer(buffer)) {
    buffer = new Buffer(buffer);
  }
  debug('write data: ' + JSON.stringify(buffer));
  SerialPortBinding.write(this.fd, buffer, function (err, result) {
    if (err) {
      debug('SerialPortBinding.write had an error', err);
      return this._error(err, callback);
    }
    if (callback) { callback(null, result) }
  }.bind(this));
};

if (process.platform !== 'win32') {
  SerialPort.prototype._read = function () {
    if (!this.readable || this.paused || this.reading || this.closing) {
      return;
    }

    this.reading = true;

    if (!this.pool || this.pool.length - this.pool.used < kMinPoolSpace) {
      // discard the old pool. Can't add to the free list because
      // users might have references to slices on it.
      this.pool = new Buffer(kPoolSize);
      this.pool.used = 0;
    }

    // Grab another reference to the pool in the case that while we're in the
    // thread pool another read() finishes up the pool, and allocates a new
    // one.
    var toRead = Math.min(this.pool.length - this.pool.used, ~~this.bufferSize);
    var start = this.pool.used;

    var _afterRead = function(err, bytesRead, readPool, bytesRequested) {
      this.reading = false;
      if (err) {
        if (err.code && err.code === 'EAGAIN') {
          if (this.isOpen()) {
            this.serialPoller.start();
          }
        // handle edge case were mac/unix doesn't clearly know the error.
        } else if (err.code && (err.code === 'EBADF' || err.code === 'ENXIO' || (err.errno === -1 || err.code === 'UNKNOWN'))) {
          this.disconnected(err);
        } else {
          this.fd = null;
          this.readable = false;
          this.emit('error', err);
        }
        return;
      }

      // Since we will often not read the number of bytes requested,
      // let's mark the ones we didn't need as available again.
      this.pool.used -= bytesRequested - bytesRead;

      if (bytesRead === 0) {
        if (this.isOpen()) {
          this.serialPoller.start();
        }
      } else {
        var b = this.pool.slice(start, start + bytesRead);

        // do not emit events if the stream is paused
        if (this.paused) {
          this.buffer = Buffer.concat([this.buffer, b]);
          return;
        }
        this._emitData(b);

        // do not emit events anymore after we declared the stream unreadable
        if (!this.readable) {
          return;
        }
        this._read();
      }
    }.bind(this);

    fs.read(this.fd, this.pool, this.pool.used, toRead, null, function (err, bytesRead) {
      var readPool = this.pool;
      var bytesRequested = toRead;
      _afterRead(err, bytesRead, readPool, bytesRequested);
    }.bind(this));

    this.pool.used += toRead;
  };

  SerialPort.prototype._emitData = function (data) {
    this.options.dataCallback(data);
  };

  SerialPort.prototype.pause = function () {
    this.paused = true;
  };

  SerialPort.prototype.resume = function () {
    this.paused = false;

    if (this.buffer) {
      var buffer = this.buffer;
      this.buffer = null;
      this._emitData(buffer);
    }

    // No longer open?
    if (!this.isOpen()) {
      return;
    }

    this._read();
  };
} // if !'win32'

SerialPort.prototype.disconnected = function (err) {
  // send notification of disconnect
  if (this.options.disconnectedCallback) {
    this.options.disconnectedCallback(err);
  } else {
    this.emit('disconnect', err);
  }
  this.paused = true;
  this.closing = true;

  this.emit('close');

  // clean up all other items
  var fd = this.fd;
  try {
    SerialPortBinding.close(fd, function (err) {
      if (err) {
        debug('Disconnect completed with error: ' + JSON.stringify(err));
      } else {
        debug('Disconnect completed.');
      }
    });
  } catch (e) {
    debug('Disconnect completed with an exception: ' + JSON.stringify(e));
  }

  this.closing = false;
  this.fd = null;

  if (process.platform !== 'win32') {
    this.readable = false;
    this.serialPoller.close();
  }
};

SerialPort.prototype.close = function (callback) {
  var fd = this.fd;

  if (this.closing) {
    debug('close attempted, but port is already closing');
    return this._error(new Error('Port is not open'), callback);
  }

  if (!this.isOpen()) {
    debug('close attempted, but port is not open');
    return this._error(new Error('Port is not open'), callback);
  }

  this.closing = true;

  // Stop polling before closing the port.
  if (process.platform !== 'win32') {
    this.readable = false;
    this.serialPoller.close();
  }
  try {
    SerialPortBinding.close(fd, function (err) {
      this.closing = false;
      if (err) {
        debug('SerialPortBinding.close had an error', err);
        return this._error(err, callback);
      }

      this.fd = null;
      this.emit('close');
      if (callback) { callback() }
    }.bind(this));
  } catch (err) {
    this.closing = false;
    debug('SerialPortBinding.close had an throwing error', err);
    return this._error(err, callback);
  }
};

SerialPort.prototype.flush = function (callback) {
  if (!this.isOpen()) {
    debug('flush attempted, but port is not open');
    return this._error(new Error('Port is not open'), callback);
  }

  SerialPortBinding.flush(this.fd, function (err, result) {
    if (err) {
      debug('SerialPortBinding.flush had an error', err);
      return this._error(err, callback);
    }
    if (callback) { callback(null, result) }
  }.bind(this));
};

SerialPort.prototype.set = function (options, callback) {
  if (!this.isOpen()) {
    debug('set attempted, but port is not open');
    return this._error(new Error('Port is not open'), callback);
  }

  options = options || {};
  if (!callback && typeof options === 'function') {
    callback = options;
    options = {};
  }

  var settings = {};
  for (var i = SET_OPTIONS.length - 1; i >= 0; i--) {
    var flag = SET_OPTIONS[i];
    if (options[flag] !== undefined) {
      settings[flag] = options[flag];
    } else {
      settings[flag] = defaultSetFlags[flag];
    }
  }

  SerialPortBinding.set(this.fd, settings, function (err, result) {
    if (err) {
      debug('SerialPortBinding.set had an error', err);
      return this._error(err, callback);
    }
    if (callback) { callback(null, result) }
  }.bind(this));
};

SerialPort.prototype.drain = function (callback) {
  var fd = this.fd;

  if (!this.isOpen()) {
    debug('drain attempted, but port is not open');
    return this._error(new Error('Port is not open'), callback);
  }

  SerialPortBinding.drain(fd, function (err, result) {
    if (err) {
      debug('SerialPortBinding.drain had an error', err);
      return this._error(err, callback);
    }
    if (callback) { callback(null, result) }
  }.bind(this));
};

module.exports = factory;
<|endoftext|>


<commit_before>var express = require('express');
var server = express();
var path = require('path');

// var addTweet = require('./manip/twitter/addTweet.js');

server.use(express.static(path.join(__dirname, 'public')));

server.get('/', require('./routes/index.js'));

server.get('/twitter', require('./routes/twitter/index.js'));
server.get('/twitter/auth', require('./routes/twitter/auth.js'));
server.get('/twitter/redirect', require('./routes/twitter/redirect.js'));

module.exports = function(db) {
  server.listen(process.env.PORT);
  return server;
};<commit_msg>prepping passport<commit_after>var express = require('express');
var server = express();
var path = require('path');
var passport = require('passport');
var GitHubStrategy = require('passport-github').Strategy;

// var addTweet = require('./manip/twitter/addTweet.js');

server.use(express.static(path.join(__dirname, 'public')));

server.get('/', require('./routes/index.js'));

server.get('/twitter', require('./routes/twitter/index.js'));
server.get('/twitter/auth', require('./routes/twitter/auth.js'));
server.get('/twitter/redirect', require('./routes/twitter/redirect.js'));

module.exports = function(db) {
  server.listen(process.env.PORT);
  return server;
};<|endoftext|>