<commit_before>//
//   Copyright 2014 Ilkka Oksanen <iao@iki.fi>
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing,
//   software distributed under the License is distributed on an "AS
//   IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
//   express or implied.  See the License for the specific language
//   governing permissions and limitations under the License.
//

'use strict';

const path = require('path'),
      assert = require('assert'),
      coRedis = require('co-redis'),
      redis = require('redis'),
      thunkify = require('thunkify'),
      redisLuaHelper = require('redis-lua-helper'),
      log = require('./log'),
      conf = require('./conf');

let rlh = redisLuaHelper({
    root: path.join(__dirname, '..', 'lua'),
    macro: '#include',
    extension: 'lua'
});

let activeClients = [];

exports.createClient = function(options) {
    return createClient(options);
};

exports.loadScripts = function*() {
    let redisClient = createClient();
    let loadDir = thunkify(function(callback) {
        rlh.loadDir(callback);
    });
    let scripts = yield loadDir();

    for (let scriptName of scripts) {
        log.info('Loading Redis script: ' + scriptName);
        try {
            yield redisClient.script('load', rlh.code(scriptName));
        } catch (e) {
            log.error('Lua script loading failed: ' + scriptName + ', ' + e);
        }
    }

    yield redisClient.quit();
};

exports.initDB = function*() {
    let redisClient = createClient();
    let networks = [ 'MAS' ].concat(Object.keys(conf.get('irc:networks')));

    yield redisClient.run.apply(null, [ 'initNetworkList' ].concat(networks));
    yield redisClient.quit();
};

exports.shutdown = function() {
    shutdown();
};

function createClient(options) {
    options = {
        autoClose: options.autoClose || true // Auto close client during server shutdown
    };

    let plainRedisClient;

    if (conf.get('redis:connection_type') === 'socket') {
        plainRedisClient = redis.createClient(conf.get('redis:port'), conf.get('redis:host'));
    } else {
        plainRedisClient = redis.createClient('/tmp/redis.sock');
    }

    let coRedisClient = coRedis(plainRedisClient);

    coRedisClient.plainRedis = redis;
    coRedisClient.plainRedisClient = plainRedisClient;
    coRedisClient.options = options;

    coRedisClient.run = function*() {
        let params = [].slice.call(arguments);
        let scriptName = params.shift();
        let sha = rlh.shasum(scriptName);

        assert(sha);

        let args = [ sha, 0 ].concat(params);
        let res;

        try {
            res = yield coRedisClient.evalsha.apply(this, args);
        } catch (e) {
            log.warn('Lua script failed: ' + scriptName + ', ' + e);
        }

        return res;
    };

    coRedisClient.quit = function*() {
        let index = activeClients.indexOf(plainRedisClient);

        if (index > -1) {
            activeClients.splice(index, 1);
        }

        plainRedisClient.quit();
    };

    if (options.autoClose) {
        activeClients.push(plainRedisClient);
    }

    return coRedisClient;
}

function shutdown() {
    log.info(`Closing ${activeClients.length} redis connections`);

    for (let client of activeClients) {
        client.quit();
    }
}
<commit_msg>Fix default value<commit_after>//
//   Copyright 2014 Ilkka Oksanen <iao@iki.fi>
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing,
//   software distributed under the License is distributed on an "AS
//   IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
//   express or implied.  See the License for the specific language
//   governing permissions and limitations under the License.
//

'use strict';

const path = require('path'),
      assert = require('assert'),
      coRedis = require('co-redis'),
      redis = require('redis'),
      thunkify = require('thunkify'),
      redisLuaHelper = require('redis-lua-helper'),
      log = require('./log'),
      conf = require('./conf');

let rlh = redisLuaHelper({
    root: path.join(__dirname, '..', 'lua'),
    macro: '#include',
    extension: 'lua'
});

let activeClients = [];

exports.createClient = function(options) {
    return createClient(options);
};

exports.loadScripts = function*() {
    let redisClient = createClient();
    let loadDir = thunkify(function(callback) {
        rlh.loadDir(callback);
    });
    let scripts = yield loadDir();

    for (let scriptName of scripts) {
        log.info('Loading Redis script: ' + scriptName);
        try {
            yield redisClient.script('load', rlh.code(scriptName));
        } catch (e) {
            log.error('Lua script loading failed: ' + scriptName + ', ' + e);
        }
    }

    yield redisClient.quit();
};

exports.initDB = function*() {
    let redisClient = createClient();
    let networks = [ 'MAS' ].concat(Object.keys(conf.get('irc:networks')));

    yield redisClient.run.apply(null, [ 'initNetworkList' ].concat(networks));
    yield redisClient.quit();
};

exports.shutdown = function() {
    shutdown();
};

function createClient(options) {
    options = options || {};

    options = {
        autoClose: options.autoClose || true // Auto close client during server shutdown
    };

    let plainRedisClient;

    if (conf.get('redis:connection_type') === 'socket') {
        plainRedisClient = redis.createClient(conf.get('redis:port'), conf.get('redis:host'));
    } else {
        plainRedisClient = redis.createClient('/tmp/redis.sock');
    }

    let coRedisClient = coRedis(plainRedisClient);

    coRedisClient.plainRedis = redis;
    coRedisClient.plainRedisClient = plainRedisClient;
    coRedisClient.options = options;

    coRedisClient.run = function*() {
        let params = [].slice.call(arguments);
        let scriptName = params.shift();
        let sha = rlh.shasum(scriptName);

        assert(sha);

        let args = [ sha, 0 ].concat(params);
        let res;

        try {
            res = yield coRedisClient.evalsha.apply(this, args);
        } catch (e) {
            log.warn('Lua script failed: ' + scriptName + ', ' + e);
        }

        return res;
    };

    coRedisClient.quit = function*() {
        let index = activeClients.indexOf(plainRedisClient);

        if (index > -1) {
            activeClients.splice(index, 1);
        }

        plainRedisClient.quit();
    };

    if (options.autoClose) {
        activeClients.push(plainRedisClient);
    }

    return coRedisClient;
}

function shutdown() {
    log.info(`Closing ${activeClients.length} redis connections`);

    for (let client of activeClients) {
        client.quit();
    }
}
<|endoftext|>


<commit_before>var TelegramBot = require('node-telegram-bot-api');
var bot = new TelegramBot(process.env.CHAINTACT_BOT_TOKEN, {polling: true});
var mongoose = require('mongoose');
var dbUrl = "mongodb://chaintact_db/chaintactdb";

mongoose.connect(dbUrl);
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
  console.log("I'm in!");
});

//Database
var userSchema = mongoose.Schema({
    telegramID: String,
    telegramUsername: String,
    firstName: String,
    lastName: String,
    ETHAddress: String
});

var User = mongoose.model('User', userSchema);

bot.getMe().then(function (me) {
  console.log('Hi my name is %s!', me.username);
});

//BOT commands

var helpText = "Commands for /chaintact :\n"
  + "    help - List of commands\n"
  + "    set [ETH address] - Set your ETH address to your profil\n"
  + "    get [telegram user] - Sends this user ETH address\n"
  + "    group - Sends the list of all group users ETH addresses"

//matches /start
bot.onText(/\/start/, function (msg, match) {
  var fromId = msg.from.id; // get the id, of who is sending the message
  var message = "Welcome to your ChaintactBot\n"
  message += helpText
  bot.sendMessage(fromId, message);
});

//match /chaintact [whatever]
bot.onText(/\/chaintact (.+)/, function (msg, match) {
  var fromId = msg.from.id; // get the id, of who is sending the message
  var fromUsername = "";
  var fromLastName = "";
  var fromFirstName = "";
  if(msg.from.username != null) fromUsername = msg.from.username
  if(msg.from.first_name != null) fromFirstName = msg.from.username
  if(msg.from.last_name != null) fromLastName = msg.from.username
  var command = match[1];
  var message;
  switch(command) {
    case "help":
      message = helpText;
      break;
    case "set":

      if(match[2] == null) {
        message = "please enter an ETH address!";
        break;
      }

      User.findOne({telegramID: fromId}, function(err, user) {
        if(user == null) {
          var newUser = new User({telegramID:fromId, telegramUsername:fromUsername, firstName:fromFirstName, lastName:fromLastName, ETHAddress:match[2]});
          newUser.save(function (err) {
            if (err) return handleError(err);
            console.log("User created : " + telegramID);
          })
        } else {
          user.telegramUsername = fromUsername
          user.firstName = fromFirstName
          user.lastName = fromLastName
          user.ETHAddress = match[2]
          .save(function (err) {
            if (err) return handleError(err);
            console.log("User updated : " + telegramID);
          })
        }
      })
      break;
    case "get":
      message = "Coming soon!";
      break;
    case "group":
      message = "Coming soon!";
      break;
    default:
      message = helpText;
  }
  bot.sendMessage(fromId, message);
});
<commit_msg>Set function<commit_after>var TelegramBot = require('node-telegram-bot-api');
var bot = new TelegramBot(process.env.CHAINTACT_BOT_TOKEN, {polling: true});
var mongoose = require('mongoose');
var dbUrl = "mongodb://chaintact_db/chaintactdb";

mongoose.connect(dbUrl);
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
  console.log("I'm in!");
});

//Database
var userSchema = mongoose.Schema({
    telegramID: String,
    telegramUsername: String,
    firstName: String,
    lastName: String,
    ETHAddress: String
});

var User = mongoose.model('User', userSchema);

bot.getMe().then(function (me) {
  console.log('Hi my name is %s!', me.username);
});

//BOT commands

var helpText = "Commands for /chaintact :\n"
  + "    help - List of commands\n"
  + "    set [ETH address] - Set your ETH address to your profil\n"
  + "    get [telegram user] - Sends this user ETH address\n"
  + "    group - Sends the list of all group users ETH addresses"

//matches /start
bot.onText(/\/start/, function (msg, match) {
  var fromId = msg.from.id; // get the id, of who is sending the message
  var message = "Welcome to your ChaintactBot\n"
  message += helpText
  bot.sendMessage(fromId, message);
});

//match /chaintact [whatever]
bot.onText(/\/chaintact (.+)/, function (msg, match) {
  var fromId = msg.from.id; // get the id, of who is sending the message
  var fromUsername = "";
  var fromLastName = "";
  var fromFirstName = "";
  if(msg.from.username != null) fromUsername = msg.from.username
  if(msg.from.first_name != null) fromFirstName = msg.from.username
  if(msg.from.last_name != null) fromLastName = msg.from.username
  var command = match[1];
  var message;
  console.log(match[2]);
  switch(command) {
    case "help":
      message = helpText;
      break;
    case "set":

      if(match[2] == null) {
        message = "Please enter an ETH address!";
        break;
      }

      User.findOne({telegramID: fromId}, function(err, user) {
        if(user == null) {
          var newUser = new User({telegramID:fromId, telegramUsername:fromUsername, firstName:fromFirstName, lastName:fromLastName, ETHAddress:match[2]});
          newUser.save(function (err) {
            if (err) return handleError(err);
            console.log("User created : " + telegramID);
          })
        } else {
          user.telegramUsername = fromUsername
          user.firstName = fromFirstName
          user.lastName = fromLastName
          user.ETHAddress = match[2]
          .save(function (err) {
            if (err) return handleError(err);
            console.log("User updated : " + telegramID);
          })
        }
      })
      break;
    case "get":
      message = "Coming soon!";
      break;
    case "group":
      message = "Coming soon!";
      break;
    default:
      message = helpText;
  }
  bot.sendMessage(fromId, message);
});
<|endoftext|>


<commit_before>//functionality of manticore with asynchronous behavior and dependencies for integration testing
var consuler;
var nomader = require('nomad-helper');
var core = require('./core.js');
var needle = require('needle');
var uuid = require('node-uuid');
var randomString = require('randomstring');
var exec = require('child_process').exec;
var fs = require('fs');
var ip = require('ip');
var logger = require('../lib/logger');
var AWS = require('aws-sdk');
AWS.config.update({region: process.env.AWS_REGION});
var ec2;
var nomadAddress;
var self;
var io;

module.exports = {
	init: function (address, socketIo, callback) {
		consuler = require('consul-helper')(address);
		//set the address
		nomadAddress = address + ":4646";
		logger.debug("Nomad address: " + nomadAddress);
		self = this; //keep a consistent context around
		io = socketIo;
		//set up AWS SDK. assume this EC2 instance has an IAM role so we don't need to put in extra credentials
		/*ec2 = new AWS.EC2();
		var params = {
			GroupId: "",

		}
		console.log(ec2.modifyInstanceAttribute);
		ec2.describeSecurityGroups({}, function (err, data) {
			console.log(data.SecurityGroups[16].IpPermissions);
			console.log(data.SecurityGroups[16].IpPermissionsEgress);
		});
		//make a security group because why not
		var params = {
			Description: "Im computer generated!",
			GroupName: "Please delete me"
		};
		ec2.createSecurityGroup(params, function (err, data) {
			console.log(err);
			console.log(data);
		});*/
		consuler.setKeyValue("manticore/filler", "Keep me here please!", function () {
			callback();
		});
	},
	startWatches: function (postUrl) {
		//set a watch for the KV store
		consuler.watchKVStore("manticore/", keysWatch);

		function keysWatch (keys) {
			//if keys is undefined, set it to an empty array
			keys = keys || [];
			keys = core.filterKeys(keys, "manticore/requests/");
			logger.debug("KV store update (after filtering)");
			logger.debug(keys);

			//set up an expectation that we want the values of <keys.length> keys.
			//send a callback function about what to do once we get all the values
			var expecting = core.expect(keys.length, function (job) {
				core.checkJobs(job, function () {//there are tasks. submit the job
					logger.debug("Core tasks exist");
					job.submitJob(nomadAddress, function (result) {
						logger.debug(result);
					});
				}, function () { //there are no tasks. delete the job
					logger.debug("No core tasks");
					self.deleteJob("core", function () {});
				});
			});
			for (let i = 0; i < keys.length; i++) {
				//go through each key and get their value. send the value to expecting
				//expecting will keep track of how many more keys are left
				consuler.getKeyValue(keys[i], function (value) {
					expecting.send(keys[i], value);
				});
			}
		}

		//set up a watch for all services
		consuler.watchServices(serviceWatch);

		function serviceWatch (services) {
			logger.debug("Services update");
			//services updated. get information about core and hmi if possible
			let cores = services.filter("core-master");
			let hmis = services.filter("hmi-master");
			logger.debug("Core services: " + cores.length);
			logger.debug("Hmi services: " + hmis.length);
			//for every core service, ensure it has a corresponding HMI
			var job = nomader.createJob("hmi");
			core.addHmisToJob(job, cores);
			//submit the job. if there are no task groups then
			//we want to remove the job completely. delete the job in that case
			core.checkJobs(job, function () {//there are tasks
				logger.debug("HMI tasks exist");
				job.submitJob(nomadAddress, function (result) {
					logger.debug(result);
				});
			}, function () { //there are no tasks
				logger.debug("No HMI tasks");
				self.deleteJob("hmi", function () {});
			});

			var pairs = core.findPairs(cores, hmis, function (userId) {
				//remove user from KV store because the HMI has no paired core which
				//indicates that the user exited the HMI page and is done with their instance
				self.deleteKey("manticore/requests/" + userId, function () {});
			});
			pairs = {
				pairs: pairs
			};
			//post all pairs at once
			logger.info(pairs);
			needle.post(postUrl, pairs, function (err, res) {
			});

			//if HAPROXY_OFF was not set to "true". write the file and reload haproxy
			core.checkHaProxyFlag(function () {
				//create an haproxy file and write it so that haproxy notices it
				//use the pairs because that has information about what addresses to use
				//NOTE: the user that runs manticore should own this directory or it may not write to the file!
				logger.debug("Updating HAProxy conf file");
				var file = core.generateHAProxyConfig(pairs);

			    fs.writeFile("/etc/haproxy/haproxy.cfg", file, function (err) {
			    	if (err) {
			    		logger.error(err);
			    	}
			    	//done! send a request to reload HAProxy
			    	core.oneAtATime(function (done) {
			    		exec("sudo service haproxy reload", function (err, stdout, stderr) {
				    		if (stdout) {
				    			logger.debug(stdout);
				    		}
				    		if (stderr) {
				    			logger.error(stderr);
				    		}
				    		done(); //this function is done
				    	});
			    	}, function (){
			    		//done executing. don't do anything
			    	});
			    });

			}, function () {//HAPROXY_OFF is set to true. do nothing
			});
		}
	},
	requestCore: function (userId, body) {
		//store the userId and request info in the database. wait for this app to find it
		//also generate unique strings to append to the external IP address that will
		//be given to users. HAProxy will map those IPs to the correct internal IP addresses
		//of core and hmi
		//generate random letters and numbers for the user and hmi addresses
		//get all keys in the KV store and find their external address prefixes
		consuler.getKeyAll("manticore/requests/", function (results) {
			var addresses = core.getAddressesFromUserRequests(results);
			var options1 = {
				length: 12,
				charset: 'alphanumeric',
				capitalization: 'lowercase'
			}

			var func1 = randomString.generate.bind(undefined, options1);
			const userToHmiAddress = core.getUniqueString(addresses, func1); //userAddress prefix
			const hmiToCoreAddress = core.getUniqueString(addresses, func1); //hmiAddress prefix

			//since we must have one TCP port open per connection to SDL core (it's a long story)
			//generate a number within reasonable bounds and isn't already used by other core connections
			//WARNING: this does not actually check if the port is used on the OS! please make sure the
			//port range specified in the environment variables are all open!
			var usedPorts = core.getPortsFromUserRequests(results);
			const tcpPortExternal = core.getUniquePort(process.env.TCP_PORT_RANGE_START,
				process.env.TCP_PORT_RANGE_END, usedPorts);
			body.userToHmiPrefix = userToHmiAddress;
			body.hmiToCorePrefix = hmiToCoreAddress;
			body.tcpPortExternal = tcpPortExternal;
			logger.debug("Store request " + userId);
			consuler.setKeyValue("manticore/requests/" + userId, JSON.stringify(body));
		});

	},
	//send back connection information in order for the client to make a websocket connection to
	//receive sdl_core logs
	requestLogs: function (userId, callback) {
		//point the user to the appropriate address
		var address = core.getWsUrl();
		//use the userId to generate a unique ID intended for the socket connection
		var connectionId = userId;
		logger.debug("Connection ID Generated:" + connectionId);
		//make sure there is an allocation for core intended for this user before
		//starting up a connection
		nomader.getAllocations("core", nomadAddress, function (res) {
			//we know which allocation to find because the TaskGroup name has the client ID
			//make sure the allocation is alive, which indicates it's the one that's running core
			var allocation = core.findAliveCoreAllocation(res.allocations, userId);
			if (allocation === null) {
				//core isn't available to stream logs
				logger.debug("Core isn't available for streaming for connection ID " + userId);
				callback(null);
			}
			else {
				//we can stream logs! return the appropriate connection details
				//pass back the address and connectionID to connect to the websocket server
				var connectionInfo = {
					url: address,
					connectionId: connectionId
				}
				logger.debug("Sending connection information to user " + userId);
				logger.debug("Address: " + connectionInfo.url);
				logger.debug("Connection ID: " + connectionInfo.connectionId);
				callback(connectionInfo);
				var taskName; //get the task name
				for (var obj in allocation.TaskStates) {
					taskName = obj;
					break;
				}
				//start streaming logs to the client once they connect using the connection details
				var custom = io.of('/' + userId);
				custom.on('connection', function (socket) {
					logger.debug("User connected! Stream core logs");
					//get the stdout logs and stream them
					nomader.streamLogs(allocation.ID, taskName, "stdout", nomadAddress, function (data) {
						//this function gets invoked whenever new data arrives from core
						socket.emit("logs", data);
					});
				});
			}

		});

	},
	deleteKey: function (key, callback) {
		consuler.delKey(key, function () {
			callback();
		});
	},
	deleteJob: function (jobName, callback) {
		nomader.deleteJob(jobName, nomadAddress, function () {
			callback();
		});
	}
}<commit_msg>bug hunting<commit_after>//functionality of manticore with asynchronous behavior and dependencies for integration testing
var consuler;
var nomader = require('nomad-helper');
var core = require('./core.js');
var needle = require('needle');
var uuid = require('node-uuid');
var randomString = require('randomstring');
var exec = require('child_process').exec;
var fs = require('fs');
var ip = require('ip');
var logger = require('../lib/logger');
var AWS = require('aws-sdk');
AWS.config.update({region: process.env.AWS_REGION});
var ec2;
var nomadAddress;
var self;
var io;

module.exports = {
	init: function (address, socketIo, callback) {
		consuler = require('consul-helper')(address);
		//set the address
		nomadAddress = address + ":4646";
		logger.debug("Nomad address: " + nomadAddress);
		self = this; //keep a consistent context around
		io = socketIo;
		//set up AWS SDK. assume this EC2 instance has an IAM role so we don't need to put in extra credentials
		/*ec2 = new AWS.EC2();
		var params = {
			GroupId: "",

		}
		console.log(ec2.modifyInstanceAttribute);
		ec2.describeSecurityGroups({}, function (err, data) {
			console.log(data.SecurityGroups[16].IpPermissions);
			console.log(data.SecurityGroups[16].IpPermissionsEgress);
		});
		//make a security group because why not
		var params = {
			Description: "Im computer generated!",
			GroupName: "Please delete me"
		};
		ec2.createSecurityGroup(params, function (err, data) {
			console.log(err);
			console.log(data);
		});*/
		consuler.setKeyValue("manticore/filler", "Keep me here please!", function () {
			callback();
		});
	},
	startWatches: function (postUrl) {
		//set a watch for the KV store
		consuler.watchKVStore("manticore/", keysWatch);

		function keysWatch (keys) {
			//if keys is undefined, set it to an empty array
			keys = keys || [];
			keys = core.filterKeys(keys, "manticore/requests/");
			logger.debug("KV store update (after filtering)");
			logger.debug(keys);

			//set up an expectation that we want the values of <keys.length> keys.
			//send a callback function about what to do once we get all the values
			var expecting = core.expect(keys.length, function (job) {
				core.checkJobs(job, function () {//there are tasks. submit the job
					logger.debug("Core tasks exist");
					job.submitJob(nomadAddress, function (result) {
						logger.debug(result);
					});
				}, function () { //there are no tasks. delete the job
					logger.debug("No core tasks");
					self.deleteJob("core", function () {});
				});
			});
			for (let i = 0; i < keys.length; i++) {
				//go through each key and get their value. send the value to expecting
				//expecting will keep track of how many more keys are left
				consuler.getKeyValue(keys[i], function (value) {
					expecting.send(keys[i], value);
				});
			}
		}

		//set up a watch for all services
		consuler.watchServices(serviceWatch);

		function serviceWatch (services) {
			logger.debug("Services update");
			//services updated. get information about core and hmi if possible
			let cores = services.filter("core-master");
			let hmis = services.filter("hmi-master");
			logger.debug("Core services: " + cores.length);
			logger.debug("Hmi services: " + hmis.length);
			//for every core service, ensure it has a corresponding HMI
			var job = nomader.createJob("hmi");
			core.addHmisToJob(job, cores);
			//submit the job. if there are no task groups then
			//we want to remove the job completely. delete the job in that case
			core.checkJobs(job, function () {//there are tasks
				logger.debug("HMI tasks exist");
				job.submitJob(nomadAddress, function (result) {
					logger.debug(result);
				});
			}, function () { //there are no tasks
				logger.debug("No HMI tasks");
				self.deleteJob("hmi", function () {});
			});

			var pairs = core.findPairs(cores, hmis, function (userId) {
				//remove user from KV store because the HMI has no paired core which
				//indicates that the user exited the HMI page and is done with their instance
				self.deleteKey("manticore/requests/" + userId, function () {});
			});
			pairs = {
				pairs: pairs
			};
			//post all pairs at once
			logger.info(pairs);
			needle.post(postUrl, pairs, function (err, res) {
			});

			//if HAPROXY_OFF was not set to "true". write the file and reload haproxy
			core.checkHaProxyFlag(function () {
				//create an haproxy file and write it so that haproxy notices it
				//use the pairs because that has information about what addresses to use
				//NOTE: the user that runs manticore should own this directory or it may not write to the file!
				logger.debug("Updating HAProxy conf file");
				var file = core.generateHAProxyConfig(pairs);

			    fs.writeFile("/etc/haproxy/haproxy.cfg", file, function (err) {
			    	if (err) {
			    		logger.error(err);
			    	}
			    	//done! send a request to reload HAProxy
			    	core.oneAtATime(function (done) {
			    		exec("sudo service haproxy reload", function (err, stdout, stderr) {
				    		if (stdout) {
				    			logger.debug(stdout);
				    		}
				    		if (stderr) {
				    			logger.error(stderr);
				    		}
				    		done(); //this function is done
				    	});
			    	}, function (){
			    		//done executing. don't do anything
			    	});
			    });

			}, function () {//HAPROXY_OFF is set to true. do nothing
			});
		}
	},
	requestCore: function (userId, body) {
		//store the userId and request info in the database. wait for this app to find it
		//also generate unique strings to append to the external IP address that will
		//be given to users. HAProxy will map those IPs to the correct internal IP addresses
		//of core and hmi
		//generate random letters and numbers for the user and hmi addresses
		//get all keys in the KV store and find their external address prefixes
		consuler.getKeyAll("manticore/requests/", function (results) {
			console.log(results);
			var addresses = core.getAddressesFromUserRequests(results);
			var options1 = {
				length: 12,
				charset: 'alphanumeric',
				capitalization: 'lowercase'
			}

			var func1 = randomString.generate.bind(undefined, options1);
			const userToHmiAddress = core.getUniqueString(addresses, func1); //userAddress prefix
			const hmiToCoreAddress = core.getUniqueString(addresses, func1); //hmiAddress prefix

			//since we must have one TCP port open per connection to SDL core (it's a long story)
			//generate a number within reasonable bounds and isn't already used by other core connections
			//WARNING: this does not actually check if the port is used on the OS! please make sure the
			//port range specified in the environment variables are all open!
			var usedPorts = core.getPortsFromUserRequests(results);
			const tcpPortExternal = core.getUniquePort(process.env.TCP_PORT_RANGE_START,
				process.env.TCP_PORT_RANGE_END, usedPorts);
			body.userToHmiPrefix = userToHmiAddress;
			body.hmiToCorePrefix = hmiToCoreAddress;
			body.tcpPortExternal = tcpPortExternal;
			logger.debug("Store request " + userId);
			consuler.setKeyValue("manticore/requests/" + userId, JSON.stringify(body));
		});

	},
	//send back connection information in order for the client to make a websocket connection to
	//receive sdl_core logs
	requestLogs: function (userId, callback) {
		//point the user to the appropriate address
		var address = core.getWsUrl();
		//use the userId to generate a unique ID intended for the socket connection
		var connectionId = userId;
		logger.debug("Connection ID Generated:" + connectionId);
		//make sure there is an allocation for core intended for this user before
		//starting up a connection
		nomader.getAllocations("core", nomadAddress, function (res) {
			//we know which allocation to find because the TaskGroup name has the client ID
			//make sure the allocation is alive, which indicates it's the one that's running core
			var allocation = core.findAliveCoreAllocation(res.allocations, userId);
			if (allocation === null) {
				//core isn't available to stream logs
				logger.debug("Core isn't available for streaming for connection ID " + userId);
				callback(null);
			}
			else {
				//we can stream logs! return the appropriate connection details
				//pass back the address and connectionID to connect to the websocket server
				var connectionInfo = {
					url: address,
					connectionId: connectionId
				}
				logger.debug("Sending connection information to user " + userId);
				logger.debug("Address: " + connectionInfo.url);
				logger.debug("Connection ID: " + connectionInfo.connectionId);
				callback(connectionInfo);
				var taskName; //get the task name
				for (var obj in allocation.TaskStates) {
					taskName = obj;
					break;
				}
				//start streaming logs to the client once they connect using the connection details
				var custom = io.of('/' + userId);
				custom.on('connection', function (socket) {
					logger.debug("User connected! Stream core logs");
					//get the stdout logs and stream them
					nomader.streamLogs(allocation.ID, taskName, "stdout", nomadAddress, function (data) {
						//this function gets invoked whenever new data arrives from core
						socket.emit("logs", data);
					});
				});
			}

		});

	},
	deleteKey: function (key, callback) {
		consuler.delKey(key, function () {
			callback();
		});
	},
	deleteJob: function (jobName, callback) {
		nomader.deleteJob(jobName, nomadAddress, function () {
			callback();
		});
	}
}<|endoftext|>


<commit_before>#!/usr/bin/env node

console.log('Hello World!')
<commit_msg>Check if package version is contained in last 10 versions<commit_after>#!/usr/bin/env node

const util = require('util');
const packageVersion = require('./package.json').version
const exec = util.promisify(require('child_process').exec)

;(async () => {
  const { stdout } = await exec("curl 'https://registry.hub.docker.com/v2/repositories/library/rethinkdb/tags/'")

  const tags = JSON.parse(stdout).results.map(i => i.name)

  if (tags.includes(packageVersion)) {
    console.error('Please update the package version before publishing.')
    process.exit(1)
  }
})()
<|endoftext|>


<commit_before>var browser = browser || chrome

let filters = []

browser.runtime.sendMessage({ type: 'get-filters' }, getFilters)
browser.runtime.onMessage.addListener(getFilters)

function getFilters(upToDateFilters) {
  filters = upToDateFilters
  hideVideos()
}

function hideVideos() {
  const noFilter = filters.length
  const lis = document.getElementsByClassName('yt-shelf-grid-item')
  const videosToHide = filters.map(title => title.toUpperCase())
  const regex = new RegExp(videosToHide.join('|'))
  const displayedVideos = []
  const hiddenVideos = []

  for (const li of lis) {
    const title = li.querySelector('h3 > a').innerHTML
    const upperCaseTitle = title.toUpperCase()
    if (noFilter && regex.test(upperCaseTitle)) {
      if (li.style.display !== 'none') {
        hiddenVideos.push(title)
        li.style.display = 'none'
      }
    } else {
      if (li.style.display === 'none') {
        displayedVideos.push(title)
        li.style.display = 'inline-block'
      }
    }
  }

  logVideosStateChange('Hiding', hiddenVideos)
  logVideosStateChange('Displaying', displayedVideos)
}

function logVideosStateChange(verb, videos) {
  if (videos.length === 0) {
    return
  }

  console.groupCollapsed(`${verb} ${videos.length} videos`)

  for (const title of videos) {
    console.info(title)
  }

  console.groupEnd()
}
<commit_msg>Migrate to OOP<commit_after>var browser = browser || chrome

class VideosDisplayManager {

  constructor(browser) {
    this.filters          = []
    this.upperCaseFilters = []

    browser.runtime.sendMessage({ type: 'get-filters' }, (filters) => this.updateFilters(filters))
    browser.runtime.onMessage.addListener((filters) => this.updateFilters(filters))
  }

  updateFilters(upToDateFilters) {
    this.filters = upToDateFilters
    this.manageVideosDisplay()
  }

  manageVideosDisplay() {
    const { videosToHide, videosToDisplay } = this.getVideosToHideAndDisplay()

    this.hideVideos(videosToHide)
    this.displayVideos(videosToDisplay)
  }

  hideVideos(videos) {
    console.groupCollapsed(`Hiding ${videos.length} videos`)

    for (const video of videos) {
      console.info(video.title)
      video.hide()
    }

    console.groupEnd()
  }

  displayVideos(videos) {
    console.groupCollapsed(`Displaying ${videos.length} videos`)

    for (const video of videos) {
      console.info(video.title)
      video.display()
    }

    console.groupEnd()
  }

  getVideosToHideAndDisplay() {
    const videos          = new YoutubeVideosFinder().find()
    const videosToHide    = []
    const videosToDisplay = []

    for (const video of videos) {
      if (this.thereIsFilters() && this.titleMatchFilters(video.title)) {
        if (video.isDisplayed()) {
          videosToHide.push(video)
        }
      } else {
        if (video.isHidden()) {
          videosToDisplay.push(video)
        }
      }
    }

    return { videosToHide, videosToDisplay }
  }

  thereIsFilters() {
    return this.filters.length > 0
  }

  titleMatchFilters(title) {
    const isTitleFinderRegexUndefined = typeof this.titleFinderRegex === undefined
    const upperCaseFilters            = this.filters.map(title => title.toUpperCase())
    const upperCaseFiltersHaveChanged = ! areArraysEqual(this.upperCaseFilters, upperCaseFilters)

    // Optimize by not recreating a new RegExp at each iteration
    if (isTitleFinderRegexUndefined || upperCaseFiltersHaveChanged) {
      this.upperCaseFilters = upperCaseFilters
      this.titleFinderRegex = new RegExp(upperCaseFilters.join('|'))
    }

    return this.titleFinderRegex.test(title.toUpperCase())
  }

}

class YoutubeVideosFinder {

  find() {
    const lis = document.getElementsByClassName('yt-shelf-grid-item')
    const videos = []

    for (const li of lis) {
      videos.push(new YoutubeVideo(li))
    }

    return videos
  }

}

class YoutubeVideo {

  constructor(element) {
    this.element = element
    this.title   = element.querySelector('h3 > a').innerHTML
  }

  display() {
    this.element.style.display = 'inline-block'
  }

  hide() {
    this.element.style.display = 'none'
  }

  isDisplayed() {
    return this.element.style.display !== 'none'
  }

  isHidden() {
    return this.element.style.display === 'none'
  }

}

function areArraysEqual(firstArray, secondArray) {
  return firstArray.length === secondArray.length && firstArray.every((value, index) => value === secondArray[index])
}

const videosManager = new VideosDisplayManager(browser)
<|endoftext|>


<commit_before>/*
 * ForceSniffer.js v1.1.0
 * Device Detect Library for Phone and Tablets and other
 * device include (Amazon Silk, mini-opera and Windows Phone),
 * and any kind of seven inch device, via user agent sniffing.
 * @author: Harshit Pandey (mailtoharshit@gmail.com)
 */

//Forked and Inspired from isMobile.js

//Script to detect all mobile devices
(function (global) {

    var apple_phone         = /iPhone/i,
        apple_ipod          = /iPod/i,
        apple_tablet        = /iPad/i,
        android_phone       = /(?=.*\bAndroid\b)(?=.*\bMobile\b)/i, // Match 'Android' AND 'Mobile'
        android_tablet      = /Android/i,
        amazon_phone        = /(?=.*\bAndroid\b)(?=.*\bSD4930UR\b)/i,
        amazon_tablet       = /(?=.*\bAndroid\b)(?=.*\b(?:KFOT|KFTT|KFJWI|KFJWA|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|KFARWI|KFASWI|KFSAWI|KFSAWA)\b)/i,
        windows_phone       = /IEMobile/i,
        windows_tablet      = /(?=.*\bWindows\b)(?=.*\bARM\b)/i, // Match 'Windows' AND 'ARM'
        other_blackberry    = /BlackBerry/i,
        other_blackberry_10 = /BB10/i,
        other_opera         = /Opera Mini/i,
        other_chrome        = /(CriOS|Chrome)(?=.*\bMobile\b)/i,
        other_firefox       = /(?=.*\bFirefox\b)(?=.*\bMobile\b)/i, // Match 'Firefox' AND 'Mobile'
        seven_inch = new RegExp(
            '(?:' +         // Non-capturing group

            'Nexus 7' +     // Nexus 7

            '|' +           // OR

            'BNTV250' +     // B&N Nook Tablet 7 inch

            '|' +           // OR

            'Kindle Fire' + // Kindle Fire

            '|' +           // OR

            'Silk' +        // Kindle Fire, Silk Accelerated

            '|' +           // OR

            'GT-P1000' +    // Galaxy Tab 7 inch

            ')',            // End non-capturing group

            'i');           // Case-insensitive matching

    var match = function(regex, userAgent) {
        return regex.test(userAgent);
    };

    var IsMobileClass = function(userAgent) {
        var ua = userAgent || navigator.userAgent;
        // Facebook mobile app's integrated browser adds a bunch of strings that
        // match everything. Strip it out if it exists.
        var tmp = ua.split('[FBAN');
        if (typeof tmp[1] !== 'undefined') {
            ua = tmp[0];
        }

        this.apple = {
            phone:  match(apple_phone, ua),
            ipod:   match(apple_ipod, ua),
            tablet: !match(apple_phone, ua) && match(apple_tablet, ua),
            device: match(apple_phone, ua) || match(apple_ipod, ua) || match(apple_tablet, ua)
        };
        this.amazon = {
            phone:  match(amazon_phone, ua),
            tablet: !match(amazon_phone, ua) && match(amazon_tablet, ua),
            device: match(amazon_phone, ua) || match(amazon_tablet, ua)
        };
        this.android = {
            phone:  match(amazon_phone, ua) || match(android_phone, ua),
            tablet: !match(amazon_phone, ua) && !match(android_phone, ua) && (match(amazon_tablet, ua) || match(android_tablet, ua)),
            device: match(amazon_phone, ua) || match(amazon_tablet, ua) || match(android_phone, ua) || match(android_tablet, ua)
        };
        this.windows = {
            phone:  match(windows_phone, ua),
            tablet: match(windows_tablet, ua),
            device: match(windows_phone, ua) || match(windows_tablet, ua)
        };
        this.other = {
            blackberry:   match(other_blackberry, ua),
            blackberry10: match(other_blackberry_10, ua),
            opera:        match(other_opera, ua),
            firefox:      match(other_firefox, ua),
            chrome:       match(other_chrome, ua),
            device:       match(other_blackberry, ua) || match(other_blackberry_10, ua) || match(other_opera, ua) || match(other_firefox, ua) || match(other_chrome, ua)
        };
        this.seven_inch = match(seven_inch, ua);
        this.any = this.apple.device || this.android.device || this.windows.device || this.other.device || this.seven_inch;
        // excludes 'other' devices and ipods, targeting touchscreen phones
        this.phone = this.apple.phone || this.android.phone || this.windows.phone;
        // excludes 7 inch devices, classifying as phone or tablet is left to the user
        this.tablet = this.apple.tablet || this.android.tablet || this.windows.tablet;

        if (typeof window === 'undefined') {
            return this;
        }
    };

    var instantiate = function() {
        var IM = new IsMobileClass();
        IM.Class = IsMobileClass;
        return IM;
    };

    if (typeof module != 'undefined' && module.exports && typeof window === 'undefined') {
        //node
        module.exports = IsMobileClass;
    } else if (typeof module != 'undefined' && module.exports && typeof window !== 'undefined') {
        //browserify
        module.exports = instantiate();
    } else if (typeof define === 'function' && define.amd) {
        //AMD
        define('isMobile', [], global.isMobile = instantiate());
    } else {
        global.isMobile = instantiate();
    }

})(this);

//Detect if Salesforce1
(function(myContext){
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.isSalesforce1 = function() {
        return((typeof sforce != 'undefined') && sforce && (!!sforce.one));
    }
    })(this);
//Detect if LightingX
(function(myContext){
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.isLightingX = function() {
        return(isLightning() && (!isMobile.any()));
    }
    })(this);

//Detect if Visualforce Inside Saleforce1
(function(myContext){
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.isVF_In_Salesforce1 = function() {
        return((!isLightning()) && ForceUI.isSalesforce1());
    }
    })(this);

//Detect if Visualforce inside Salesforce Classic
(function(myContext){
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.isVF_In_SalesforceClassic = function() {
        return((!isLightning()) && !ForceUI.isSalesforce1());
    }
    })(this);

//Detect browser type
(function(myContext) {
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.browserType = function() {
        if (navigator.userAgent.indexOf("OPR") != -1) { // if browser type is Opera
            return 'Opera';
        } else if (navigator.userAgent.indexOf("Chrome") != -1) { // if browser type is Chrome
            return 'Chrome';
        } else if (navigator.userAgent.indexOf("Safari") != -1) { // if browser type is Safari
            return 'Safari';
        } else if (navigator.userAgent.indexOf("Firefox") != -1) { // if browser type is Firefox
            return 'Firefox';
        } else if ((navigator.userAgent.indexOf("MSIE") != -1) || (!!document.documentMode == true)) { // if browser is IE
            return 'IE';
        } else {
            return 'unknown'; // if browser is something else
        }
    }
})(this);

//Detects browser version
(function(myContext) {
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.browserVersion = function() {
        var ua = window.navigator.userAgent;
        var browserStartPoint = ua.indexOf(ForceUI.browserType());

        // If browser type is found, return it's version number
        if (ForceUI.browserType() == 'Chrome') // get Chrome browser version
            return ua.substring(browserStartPoint + 7, ua.indexOf(" ", browserStartPoint));
        else if (ForceUI.browserType() == 'IE') { // get IE browser version
            browserStartPoint = navigator.userAgent.indexOf("MSIE");
            return ua.substring(browserStartPoint + 5, ua.indexOf(";", browserStartPoint));
        } else if (ForceUI.browserType() == 'Firefox') // get Firefox browser version
            return ua.substring(browserStartPoint + 8, ua.length);
        else if (ForceUI.browserType() == 'Safari') // get Safari browser version
            return ua.substring(browserStartPoint + 7, ua.length);
        else if (ForceUI.browserType() == 'Opera') { // get Opera browser version
            browserStartPoint = navigator.userAgent.indexOf("OPR");
            return ua.substring(browserStartPoint + 4, ua.length);
        } else // If another browser, return 0
            return 0

    }
})(this);

//Detect browser type with Browser Version
(function(myContext) {

    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.browserTypeWithVersion = function() { // get browser Type along with version
        var result = ForceUI.browserType() + ' ' + ForceUI.browserVersion();
        return result;
    }
})(this);

//Detect OS type
(function(myContext){
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.osType = function() {
    var os=navigator.platform.toLowerCase();
    var osType="";

    switch(os)
    {
        case "win32" :
        {
            osType="Windows";
            break;
        }
        case "macintel" :
        {
             osType="Mac";
             break;
        }

        case "iphone" :
        {
             osType="OSX-Iphone";
             break;
        }

        case "ipad" :
        {
             osType="OSX-Ipad";
             break;
        }

        default :
        {
             osType="Android";
             break;
        }

    }
    return osType;
   }
})(this);

//Detect OS type with OS Version
(function(myContext){
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.osTypeWithVersion = function() {
    var os = 'unknown';
    var clientStrings = [
            {s:'Windows 10', r:/(Windows 10.0|Windows NT 10.0)/},
            {s:'Windows 8.1', r:/(Windows 8.1|Windows NT 6.3)/},
            {s:'Windows 8', r:/(Windows 8|Windows NT 6.2)/},
            {s:'Windows 7', r:/(Windows 7|Windows NT 6.1)/},
            {s:'Windows Vista', r:/Windows NT 6.0/},
            {s:'Windows Server 2003', r:/Windows NT 5.2/},
            {s:'Windows XP', r:/(Windows NT 5.1|Windows XP)/},
            {s:'Windows 2000', r:/(Windows NT 5.0|Windows 2000)/},
            {s:'Windows ME', r:/(Win 9x 4.90|Windows ME)/},
            {s:'Windows 98', r:/(Windows 98|Win98)/},
            {s:'Windows 95', r:/(Windows 95|Win95|Windows_95)/},
            {s:'Windows NT 4.0', r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},
            {s:'Windows CE', r:/Windows CE/},
            {s:'Windows 3.11', r:/Win16/},
            {s:'Android', r:/Android/},
            {s:'Open BSD', r:/OpenBSD/},
            {s:'Sun OS', r:/SunOS/},
            {s:'Linux', r:/(Linux|X11)/},
            {s:'iOS', r:/(iPhone|iPad|iPod)/},
            {s:'Mac OS X', r:/Mac OS X/},
            {s:'Mac OS', r:/(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},
            {s:'QNX', r:/QNX/},
            {s:'UNIX', r:/UNIX/},
            {s:'BeOS', r:/BeOS/},
            {s:'OS/2', r:/OS\/2/},
            {s:'Search Bot', r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}
        ];
        for (var id in clientStrings) {
            var cs = clientStrings[id];
            if (cs.r.test(navigator.userAgent)) {
                os = cs.s;
                break;
            }
        }
        var osVersion = 'unknown';
        if (/Windows/.test(os)) {
            osVersion = /Windows (.*)/.exec(os)[1];
            os = 'Windows';
        }
        switch (os) {
            case 'Mac OS X':
                osVersion = /Mac OS X (10[\.\_\d]+)/.exec(navigator.userAgent)[1];
                break;

            case 'Android':
                osVersion = /Android ([\.\_\d]+)/.exec(navigator.userAgent)[1];
                break;

            case 'iOS':
                osVersion = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer);
                osVersion = osVersion[1] + '.' + osVersion[2] + '.' + (osVersion[3] | 0);
                break;
        }

       return os + ' '+osVersion;
   }
})(this);

<commit_msg>Replace OSX with iOS<commit_after>/*
 * ForceSniffer.js v1.1.0
 * Device Detect Library for Phone and Tablets and other
 * device include (Amazon Silk, mini-opera and Windows Phone),
 * and any kind of seven inch device, via user agent sniffing.
 * @author: Harshit Pandey (mailtoharshit@gmail.com)
 */

//Forked and Inspired from isMobile.js

//Script to detect all mobile devices
(function (global) {

    var apple_phone         = /iPhone/i,
        apple_ipod          = /iPod/i,
        apple_tablet        = /iPad/i,
        android_phone       = /(?=.*\bAndroid\b)(?=.*\bMobile\b)/i, // Match 'Android' AND 'Mobile'
        android_tablet      = /Android/i,
        amazon_phone        = /(?=.*\bAndroid\b)(?=.*\bSD4930UR\b)/i,
        amazon_tablet       = /(?=.*\bAndroid\b)(?=.*\b(?:KFOT|KFTT|KFJWI|KFJWA|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|KFARWI|KFASWI|KFSAWI|KFSAWA)\b)/i,
        windows_phone       = /IEMobile/i,
        windows_tablet      = /(?=.*\bWindows\b)(?=.*\bARM\b)/i, // Match 'Windows' AND 'ARM'
        other_blackberry    = /BlackBerry/i,
        other_blackberry_10 = /BB10/i,
        other_opera         = /Opera Mini/i,
        other_chrome        = /(CriOS|Chrome)(?=.*\bMobile\b)/i,
        other_firefox       = /(?=.*\bFirefox\b)(?=.*\bMobile\b)/i, // Match 'Firefox' AND 'Mobile'
        seven_inch = new RegExp(
            '(?:' +         // Non-capturing group

            'Nexus 7' +     // Nexus 7

            '|' +           // OR

            'BNTV250' +     // B&N Nook Tablet 7 inch

            '|' +           // OR

            'Kindle Fire' + // Kindle Fire

            '|' +           // OR

            'Silk' +        // Kindle Fire, Silk Accelerated

            '|' +           // OR

            'GT-P1000' +    // Galaxy Tab 7 inch

            ')',            // End non-capturing group

            'i');           // Case-insensitive matching

    var match = function(regex, userAgent) {
        return regex.test(userAgent);
    };

    var IsMobileClass = function(userAgent) {
        var ua = userAgent || navigator.userAgent;
        // Facebook mobile app's integrated browser adds a bunch of strings that
        // match everything. Strip it out if it exists.
        var tmp = ua.split('[FBAN');
        if (typeof tmp[1] !== 'undefined') {
            ua = tmp[0];
        }

        this.apple = {
            phone:  match(apple_phone, ua),
            ipod:   match(apple_ipod, ua),
            tablet: !match(apple_phone, ua) && match(apple_tablet, ua),
            device: match(apple_phone, ua) || match(apple_ipod, ua) || match(apple_tablet, ua)
        };
        this.amazon = {
            phone:  match(amazon_phone, ua),
            tablet: !match(amazon_phone, ua) && match(amazon_tablet, ua),
            device: match(amazon_phone, ua) || match(amazon_tablet, ua)
        };
        this.android = {
            phone:  match(amazon_phone, ua) || match(android_phone, ua),
            tablet: !match(amazon_phone, ua) && !match(android_phone, ua) && (match(amazon_tablet, ua) || match(android_tablet, ua)),
            device: match(amazon_phone, ua) || match(amazon_tablet, ua) || match(android_phone, ua) || match(android_tablet, ua)
        };
        this.windows = {
            phone:  match(windows_phone, ua),
            tablet: match(windows_tablet, ua),
            device: match(windows_phone, ua) || match(windows_tablet, ua)
        };
        this.other = {
            blackberry:   match(other_blackberry, ua),
            blackberry10: match(other_blackberry_10, ua),
            opera:        match(other_opera, ua),
            firefox:      match(other_firefox, ua),
            chrome:       match(other_chrome, ua),
            device:       match(other_blackberry, ua) || match(other_blackberry_10, ua) || match(other_opera, ua) || match(other_firefox, ua) || match(other_chrome, ua)
        };
        this.seven_inch = match(seven_inch, ua);
        this.any = this.apple.device || this.android.device || this.windows.device || this.other.device || this.seven_inch;
        // excludes 'other' devices and ipods, targeting touchscreen phones
        this.phone = this.apple.phone || this.android.phone || this.windows.phone;
        // excludes 7 inch devices, classifying as phone or tablet is left to the user
        this.tablet = this.apple.tablet || this.android.tablet || this.windows.tablet;

        if (typeof window === 'undefined') {
            return this;
        }
    };

    var instantiate = function() {
        var IM = new IsMobileClass();
        IM.Class = IsMobileClass;
        return IM;
    };

    if (typeof module != 'undefined' && module.exports && typeof window === 'undefined') {
        //node
        module.exports = IsMobileClass;
    } else if (typeof module != 'undefined' && module.exports && typeof window !== 'undefined') {
        //browserify
        module.exports = instantiate();
    } else if (typeof define === 'function' && define.amd) {
        //AMD
        define('isMobile', [], global.isMobile = instantiate());
    } else {
        global.isMobile = instantiate();
    }

})(this);

//Detect if Salesforce1
(function(myContext){
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.isSalesforce1 = function() {
        return((typeof sforce != 'undefined') && sforce && (!!sforce.one));
    }
    })(this);
//Detect if LightingX
(function(myContext){
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.isLightingX = function() {
        return(isLightning() && (!isMobile.any()));
    }
    })(this);

//Detect if Visualforce Inside Saleforce1
(function(myContext){
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.isVF_In_Salesforce1 = function() {
        return((!isLightning()) && ForceUI.isSalesforce1());
    }
    })(this);

//Detect if Visualforce inside Salesforce Classic
(function(myContext){
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.isVF_In_SalesforceClassic = function() {
        return((!isLightning()) && !ForceUI.isSalesforce1());
    }
    })(this);

//Detect browser type
(function(myContext) {
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.browserType = function() {
        if (navigator.userAgent.indexOf("OPR") != -1) { // if browser type is Opera
            return 'Opera';
        } else if (navigator.userAgent.indexOf("Chrome") != -1) { // if browser type is Chrome
            return 'Chrome';
        } else if (navigator.userAgent.indexOf("Safari") != -1) { // if browser type is Safari
            return 'Safari';
        } else if (navigator.userAgent.indexOf("Firefox") != -1) { // if browser type is Firefox
            return 'Firefox';
        } else if ((navigator.userAgent.indexOf("MSIE") != -1) || (!!document.documentMode == true)) { // if browser is IE
            return 'IE';
        } else {
            return 'unknown'; // if browser is something else
        }
    }
})(this);

//Detects browser version
(function(myContext) {
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.browserVersion = function() {
        var ua = window.navigator.userAgent;
        var browserStartPoint = ua.indexOf(ForceUI.browserType());

        // If browser type is found, return it's version number
        if (ForceUI.browserType() == 'Chrome') // get Chrome browser version
            return ua.substring(browserStartPoint + 7, ua.indexOf(" ", browserStartPoint));
        else if (ForceUI.browserType() == 'IE') { // get IE browser version
            browserStartPoint = navigator.userAgent.indexOf("MSIE");
            return ua.substring(browserStartPoint + 5, ua.indexOf(";", browserStartPoint));
        } else if (ForceUI.browserType() == 'Firefox') // get Firefox browser version
            return ua.substring(browserStartPoint + 8, ua.length);
        else if (ForceUI.browserType() == 'Safari') // get Safari browser version
            return ua.substring(browserStartPoint + 7, ua.length);
        else if (ForceUI.browserType() == 'Opera') { // get Opera browser version
            browserStartPoint = navigator.userAgent.indexOf("OPR");
            return ua.substring(browserStartPoint + 4, ua.length);
        } else // If another browser, return 0
            return 0

    }
})(this);

//Detect browser type with Browser Version
(function(myContext) {

    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.browserTypeWithVersion = function() { // get browser Type along with version
        var result = ForceUI.browserType() + ' ' + ForceUI.browserVersion();
        return result;
    }
})(this);

//Detect OS type
(function(myContext){
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.osType = function() {
    var os=navigator.platform.toLowerCase();
    var osType="";

    switch(os)
    {
        case "win32" :
        {
            osType="Windows";
            break;
        }
        case "macintel" :
        {
             osType="OSX-Mac";
             break;
        }

        case "iphone" :
        {
             osType="iOS-iPhone";
             break;
        }

        case "ipad" :
        {
             osType="iOS-iPad";
             break;
        }

        default :
        {
             osType="Android";
             break;
        }

    }
    return osType;
   }
})(this);

//Detect OS type with OS Version
(function(myContext){
    myContext.ForceUI = myContext.ForceUI || {};
    myContext.ForceUI.osTypeWithVersion = function() {
    var os = 'unknown';
    var clientStrings = [
            {s:'Windows 10', r:/(Windows 10.0|Windows NT 10.0)/},
            {s:'Windows 8.1', r:/(Windows 8.1|Windows NT 6.3)/},
            {s:'Windows 8', r:/(Windows 8|Windows NT 6.2)/},
            {s:'Windows 7', r:/(Windows 7|Windows NT 6.1)/},
            {s:'Windows Vista', r:/Windows NT 6.0/},
            {s:'Windows Server 2003', r:/Windows NT 5.2/},
            {s:'Windows XP', r:/(Windows NT 5.1|Windows XP)/},
            {s:'Windows 2000', r:/(Windows NT 5.0|Windows 2000)/},
            {s:'Windows ME', r:/(Win 9x 4.90|Windows ME)/},
            {s:'Windows 98', r:/(Windows 98|Win98)/},
            {s:'Windows 95', r:/(Windows 95|Win95|Windows_95)/},
            {s:'Windows NT 4.0', r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},
            {s:'Windows CE', r:/Windows CE/},
            {s:'Windows 3.11', r:/Win16/},
            {s:'Android', r:/Android/},
            {s:'Open BSD', r:/OpenBSD/},
            {s:'Sun OS', r:/SunOS/},
            {s:'Linux', r:/(Linux|X11)/},
            {s:'iOS', r:/(iPhone|iPad|iPod)/},
            {s:'Mac OS X', r:/Mac OS X/},
            {s:'Mac OS', r:/(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},
            {s:'QNX', r:/QNX/},
            {s:'UNIX', r:/UNIX/},
            {s:'BeOS', r:/BeOS/},
            {s:'OS/2', r:/OS\/2/},
            {s:'Search Bot', r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}
        ];
        for (var id in clientStrings) {
            var cs = clientStrings[id];
            if (cs.r.test(navigator.userAgent)) {
                os = cs.s;
                break;
            }
        }
        var osVersion = 'unknown';
        if (/Windows/.test(os)) {
            osVersion = /Windows (.*)/.exec(os)[1];
            os = 'Windows';
        }
        switch (os) {
            case 'Mac OS X':
                osVersion = /Mac OS X (10[\.\_\d]+)/.exec(navigator.userAgent)[1];
                break;

            case 'Android':
                osVersion = /Android ([\.\_\d]+)/.exec(navigator.userAgent)[1];
                break;

            case 'iOS':
                osVersion = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer);
                osVersion = osVersion[1] + '.' + osVersion[2] + '.' + (osVersion[3] | 0);
                break;
        }

       return os + ' '+osVersion;
   }
})(this);

<|endoftext|>


<commit_before>// eBay API client for Node.js

var restler = require('restler'),
    _ = require('underscore'),
    util = require('util'),
    async = require('async');

// [internal] convert params hash to url string.
// some items may be arrays, use key(0)..(n)
// param usage:
//  - use null values for plain params
//  - use arrays for repeating keys
var buildParams = function(params) {
  var urlFilters = [];  // string parts to be joined

  // (force each to be string w/ ''+var)
  _(params).each(function(value, key) {
    if (value === null) urlFilters.push('' + key);
    else if (_.isArray(value)) {
      _(value).each(function(subValue, subInd) {
        urlFilters.push('' + key + '(' + subInd + ')' + "=" + subValue);
      });
    }
    else urlFilters.push( '' + key + "=" + value );
  });

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

// [helper] constructor for an 'itemFilter' filter (used by the Finding Service)
module.exports.ItemFilter = function(name, value, paramName, paramValue) {
  // required
  this.name = name;
  this.value = value;

  // optional
  this.paramName = _.isUndefined(paramName) ? '' : paramName;
  this.paramValue = _.isUndefined(paramValue) ? '' : paramValue;
};

// [internal] convert a filters array to a url string
// adapted from client-side JS example in ebay docs
var buildFilters = function(filterType, filters) {
  var urlFilter = '';
  _(filters).each(function eachItemFilter(filter, filterInd) {
    // each parameter in each item filter
    _(filter).each(function eachItemParam(paramVal, paramKey) {
      // Check to see if the paramter has a value (some don't)
      if (paramVal !== "") {
        // multi-value param
        if (_.isArray(paramVal)) {
          _(paramVal).each(function eachSubFilter(paramSubVal, paramSubIndex) {
            urlFilter += '&' + filterType + '(' + filterInd + ').' + paramKey + '(' + paramSubIndex + ')=' + paramSubVal;
          });
        }
        // single-value param
        else {
          urlFilter += '&' + filterType + '(' + filterInd + ').' + paramKey + '=' + paramVal;
        }
      }
    });
  });
  return urlFilter;
};

// build URL to PRODUCTION endpoint of GET APIs
// - params is a 1D obj
// - filters is an obj of { filterType:[filters] } (where filters is an array of ItemFilter)
var buildRequestUrl = function(serviceName, params, filters) {
  var url;

  switch (serviceName) {
    case 'FindingService':
      url = "https://svcs.ebay.com/services/search/" + serviceName + "/v1?";
      break;

    case 'Shopping':
      url = "http://open.api.ebay.com/shopping?";
      break;

    default:
      url = "https://svcs.ebay.com/" + serviceName + '?';
  }

  url += buildParams(params);     // no trailing &

  _(filters).each(function(typeFilters, type) {
    url += buildFilters(type, typeFilters);     // each has leading &
  });

  return url;
};
module.exports.buildRequestUrl = buildRequestUrl;

// default params per service type
var defaultParams = function(serviceName, opType, appId) {
  var params = {
    'OPERATION-NAME': opType,
    'GLOBAL-ID': 'EBAY-US',
    'RESPONSE-DATA-FORMAT': 'JSON',
    'REST-PAYLOAD': null     // (not sure what this does)
  };

  switch (serviceName) {
    case 'FindingService':
      _.extend(params, {
        'SECURITY-APPNAME': appId,
        'SERVICE-VERSION': '1.11.0'
      });
      break;

    case 'MerchandisingService':
      _.extend(params, {
        'SERVICE-NAME': serviceName,
        'CONSUMER-ID': appId,
        'SERVICE-VERSION': '1.5.0'   // based on response data
      });
      break;

    case 'Shopping':
      _.extend(params, {
        'appid': appId,
        'version': '771',
        'siteid': '0',
        'responseencoding': 'JSON',
        'callname': opType
      });
      break;
  }

  return params;
};

// make a single GET request to a service
var ebayApiGetRequest = function(options, callback) {
  if (! options.serviceName) return callback(new Error("Missing serviceName"));
  if (! options.opType) return callback(new Error("Missing opType"));
  if (! options.appId) return callback(new Error("Missing appId"));
  options.params = options.params || {};
  options.filters = options.filters || {};
  options.reqOptions = options.reqOptions || {};
  options.parser = options.parser || parseItemsFromResponse;

  if (options.serviceName === 'MerchandisingService') {
    options.reqOptions.decoding = 'buffer';   // otherwise fails to decode json. doesn't seem to be necessary w/ FindingService.
  }

  _.defaults(options.params, defaultParams(options.serviceName, options.opType, options.appId));

  var url = buildRequestUrl(options.serviceName, options.params, options.filters);
  // console.log('url for', options.opType, 'request:\n', url.replace(/\&/g, '\n&'));

  var request = restler.get(url, options.reqOptions);
  var data;

  // emitted when the request has finished whether it was successful or not
  request.on('complete', function(result, response) {
    // [restler docs] 'If some error has occurred, result is always instance of Error'
    if (result instanceof Error) {
      var error = result;
      error.message = "Completed with error: " + error.message;
      return callback(error);
    }
    else if (response.statusCode !== 200) {
      return callback(new Error(util.format("Bad response status code", response.statusCode, result)));
    }

    try {
      data = JSON.parse(result);

      // drill down to item(s). each service has its own structure.
      if (options.serviceName !== 'Shopping') {
        var responseKey = options.opType + 'Response';
        if (_.isUndefined(data[responseKey])) {
          return callback(new Error("Response missing " + responseKey + " element"));
        }
        data = data[responseKey];
      }

      if (_.isArray(data)) {
        data = _(data).first();
      }

      // 'ack' and 'errMsg' indicate errors.
      // - in FindingService it's nested, in Merchandising it's flat - flatten to normalize
      if (!_.isUndefined(data.ack)) data.ack = flatten(data.ack);
      else if (!_.isUndefined(data.Ack)) {      // uppercase, standardize.
        data.ack = flatten(data.Ack);
        delete data.Ack;
      }

      if (_.isUndefined(data.ack) || data.ack !== 'Success') {
        var errMsg = _.isUndefined(data.errorMessage) ? null : flatten(data.errorMessage);
        return callback(new Error(util.format("Bad 'ack' code", data.ack, 'errorMessage?', util.inspect(errMsg, true, 3))));
      }
    }
    catch(error) {
      return callback(error);
    }
    // console.log('completed successfully:\n', util.inspect(data, true, 10, true));

    // parse the response
    options.parser(data, function(error, items) {
      callback(error, items);
    });
  });

  // emitted when some errors have occurred
  // either this OR 'completed' should fire
  request.on('error', function(error, response) {
    error.message = "Request error: " + error.message;
    callback(error);
  });

  // emitted when the request was successful
  // -- overlaps w/ 'completed', don't use
  // request.on('success', function(data, response) {
  // });

  // emitted when the request was successful, but 4xx status code returned
  // -- overlaps w/ 'completed', don't use
  // request.on('fail', function(data, response) {
  // });

};
module.exports.ebayApiGetRequest = ebayApiGetRequest;

// PAGINATE multiple requests in parallel (max 100 per page, 100 pages = 10k items)
var paginateGetRequest = function(options, callback) {
  if (! options.serviceName) return callback(new Error("Missing serviceName"));
  if (! options.opType) return callback(new Error("Missing opType"));
  if (! options.appId) return callback(new Error("Missing appId"));
  options.params = options.params || {};
  options.filters = options.filters || {};
  options.reqOptions = options.reqOptions || {};
  options.pages = options.pages || 2;
  options.perPage = options.perPage || 10;
  options.parser = options.parser || parseItemsFromResponse;

  console.log('Paginated request to', options.serviceName, 'for', options.pages, 'pages of', options.perPage, 'items each');

  var mergedItems = [],   // to be merged
      pageParams = [],
      p;

  if (!(_.isNumber(options.pages) && options.pages > 0)) return callback(new Error("Invalid number of pages requested", options.pages));

  // index is pageInd-1. can't start array from 1, it just fills in 0 with undefined.
  for(p = 0; p < options.pages; p++) {
    pageParams[p] = {
      'paginationInput.entriesPerPage': options.perPage,
      'paginationInput.pageNumber': p+1
    };
  }
  // console.log(pageParams.length, 'pages:', pageParams);

  // run pagination requests in parallel
  async.forEach(pageParams,
    function eachPage(thisPageParams, nextPage) {

      // merge the pagination params. new var to avoid confusing scope.
      var thisPageOptions = _.extend({}, options);
      thisPageOptions.params = _.extend({}, thisPageOptions.params, thisPageParams);

      console.log("Requesting page", thisPageOptions.params['paginationInput.pageNumber'], 'with', thisPageOptions.params['paginationInput.entriesPerPage'], 'items...');

      ebayApiGetRequest(thisPageOptions, function(error, items) {
        // console.log("Got response from page", thisPageOptions.params['paginationInput.pageNumber']);

        if (error) {
          error.message = "Error on page " + thisPageOptions.params['paginationInput.pageNumber'] + ": " + error.message;
          return nextPage(error);
        }

        if (!_.isArray(items)) {
          return nextPage(new Error("Parser did not return an array, returned a " + typeof items));
        }

        console.log('Got', items.length, 'items from page', thisPageOptions.params['paginationInput.pageNumber']);

        // console.log('have', mergedItems.length, 'previous items, adding', items.length, 'new items...');
        mergedItems = mergedItems.concat(items);
        // console.log('now have', mergedItems.length, 'merged items');

        nextPage(null);
      });
    },

    function pagesDone(error) {
      // console.log('pages are done');
      if (error) callback(error);
      else callback(null, mergedItems);
    }
  );  //forEach

};
module.exports.paginateGetRequest = paginateGetRequest;

// helper: RECURSIVELY turn 1-element arrays/objects into flat vars
// (different from _.flatten() which returns an array)
var flatten = function(el, iter) {
  // sanity check
  if (_.isUndefined(iter)) var iter = 1;
  if (iter > 100) {
    console.error("recursion error, stop at", iter);
    return;
  }

  // flatten 1-item arrays
  if (_.isArray(el) && el.length === 1) {
    el = _.first(el);
  }

  // special value-pair structure in the ebay API: turn { @key:KEY, __value__:VALUE } into { KEY: VALUE }
  if (isValuePair(el)) {
    var values = _.values(el);
    // console.log('found special:', el);
    el = {};
    el[ values[0] ] = values[1];
    // console.log('handled special:', el);
  }

  // previous fix just creates an array of these. we want a clean key:val obj.
  // so, is this an array of special value-pairs?
  if (isArrayOfValuePairs(el)) {
    var fixEl = {};
    _(el).each(function(pair) {
      _.extend(fixEl, flatten(pair));   // fix each, combine
    });
    el = fixEl;
  }

  // flatten sub-elements
  if (_.isArray(el) || _.isObject(el)) {
    _.each(el, function(subEl, subInd) {
      el[subInd] = flatten(el[subInd], iter++);
    });
  }

  return el;
};
module.exports.flatten = flatten;

// helper: identify a structure returned from the API:
// { @key:KEY, __value__:VALUE } => want to turn into { KEY: VALUE }
// (and array of these into single obj)
var isValuePair = function(el) {
  if (_.isObject(el) && _.size(el) === 2) {
    var keys = _.keys(el);
    if (new RegExp(/^@/).test(keys[0]) && keys[1] === '__value__') {
      return true;
    }
  }
  return false;
};

// helper: find an array containing only special key-value pairs
// e.g. 'galleryURL' (makes it easier to handle in MongoDB)
var isArrayOfValuePairs = function(el) {
  if (_.isArray(el)) {
    if (_.all(el, isValuePair)) return true;
  }
  return false;
};

// extract an array of items from responses. differs by query type.
// @todo build this out as more queries are added...
var parseItemsFromResponse = function(data, callback) {
  // console.log('parse data', data);

  var items = [];
  try {
    if (typeof data.Item !== 'undefined') {       // e.g. for Shopping::GetSingleItem
      items = [ data.Item ];    // preserve array for standardization (?)
    }

    else if (typeof data.searchResult !== 'undefined') {    // e.g. for FindingService
      // reduce in steps so successful-but-empty responses don't throw error
      if (!_.isEmpty(data.searchResult)) {
        data = _(data.searchResult).first();
        if (typeof data !== 'undefined') {
          if (typeof data.item !== 'undefined') {
            items = data.item;
          }
        }
      }
    }
    else if (typeof data.itemRecommendations !== 'undefined') {
      if (typeof data.itemRecommendations !== 'undefined') {
        if (typeof data.itemRecommendations.item !== 'undefined') {
          items = _.isArray(data.itemRecommendations.item) ? data.itemRecommendations.item : [];
        }
      }
    }

    // recursively flatten 1-level arrays and "@key:__VALUE__" pairs
    items = _(items).map(function(item) {
      return flatten(item);
    });
  }
  catch(error) {
    callback(error);
  }

  callback(null, items);
};
module.exports.parseItemsFromResponse = parseItemsFromResponse;

// check if an item URL is an affiliate URL
// non-affil URLs look like 'http://www.ebay.com...', affil URLs look like 'http://rover.ebay.com/rover...'
//  and have param &campid=TRACKINGID (campid=1234567890)
module.exports.checkAffiliateUrl = function(url) {
  var regexAffil = /http\:\/\/rover\.ebay\.com\/rover/,
      regexNonAffil = /http\:\/\/www\.ebay\.com/,
      regexCampaign = /campid=[0-9]{5}/;

  return (regexAffil.test(url) && !regexNonAffil.test(url) && regexCampaign.test(url));
};

<commit_msg>put string not buffer in error<commit_after>// eBay API client for Node.js

var restler = require('restler'),
    _ = require('underscore'),
    util = require('util'),
    async = require('async');

// [internal] convert params hash to url string.
// some items may be arrays, use key(0)..(n)
// param usage:
//  - use null values for plain params
//  - use arrays for repeating keys
var buildParams = function(params) {
  var urlFilters = [];  // string parts to be joined

  // (force each to be string w/ ''+var)
  _(params).each(function(value, key) {
    if (value === null) urlFilters.push('' + key);
    else if (_.isArray(value)) {
      _(value).each(function(subValue, subInd) {
        urlFilters.push('' + key + '(' + subInd + ')' + "=" + subValue);
      });
    }
    else urlFilters.push( '' + key + "=" + value );
  });

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

// [helper] constructor for an 'itemFilter' filter (used by the Finding Service)
module.exports.ItemFilter = function(name, value, paramName, paramValue) {
  // required
  this.name = name;
  this.value = value;

  // optional
  this.paramName = _.isUndefined(paramName) ? '' : paramName;
  this.paramValue = _.isUndefined(paramValue) ? '' : paramValue;
};

// [internal] convert a filters array to a url string
// adapted from client-side JS example in ebay docs
var buildFilters = function(filterType, filters) {
  var urlFilter = '';
  _(filters).each(function eachItemFilter(filter, filterInd) {
    // each parameter in each item filter
    _(filter).each(function eachItemParam(paramVal, paramKey) {
      // Check to see if the paramter has a value (some don't)
      if (paramVal !== "") {
        // multi-value param
        if (_.isArray(paramVal)) {
          _(paramVal).each(function eachSubFilter(paramSubVal, paramSubIndex) {
            urlFilter += '&' + filterType + '(' + filterInd + ').' + paramKey + '(' + paramSubIndex + ')=' + paramSubVal;
          });
        }
        // single-value param
        else {
          urlFilter += '&' + filterType + '(' + filterInd + ').' + paramKey + '=' + paramVal;
        }
      }
    });
  });
  return urlFilter;
};

// build URL to PRODUCTION endpoint of GET APIs
// - params is a 1D obj
// - filters is an obj of { filterType:[filters] } (where filters is an array of ItemFilter)
var buildRequestUrl = function(serviceName, params, filters) {
  var url;

  switch (serviceName) {
    case 'FindingService':
      url = "https://svcs.ebay.com/services/search/" + serviceName + "/v1?";
      break;

    case 'Shopping':
      url = "http://open.api.ebay.com/shopping?";
      break;

    default:
      url = "https://svcs.ebay.com/" + serviceName + '?';
  }

  url += buildParams(params);     // no trailing &

  _(filters).each(function(typeFilters, type) {
    url += buildFilters(type, typeFilters);     // each has leading &
  });

  return url;
};
module.exports.buildRequestUrl = buildRequestUrl;

// default params per service type
var defaultParams = function(serviceName, opType, appId) {
  var params = {
    'OPERATION-NAME': opType,
    'GLOBAL-ID': 'EBAY-US',
    'RESPONSE-DATA-FORMAT': 'JSON',
    'REST-PAYLOAD': null     // (not sure what this does)
  };

  switch (serviceName) {
    case 'FindingService':
      _.extend(params, {
        'SECURITY-APPNAME': appId,
        'SERVICE-VERSION': '1.11.0'
      });
      break;

    case 'MerchandisingService':
      _.extend(params, {
        'SERVICE-NAME': serviceName,
        'CONSUMER-ID': appId,
        'SERVICE-VERSION': '1.5.0'   // based on response data
      });
      break;

    case 'Shopping':
      _.extend(params, {
        'appid': appId,
        'version': '771',
        'siteid': '0',
        'responseencoding': 'JSON',
        'callname': opType
      });
      break;
  }

  return params;
};

// make a single GET request to a service
var ebayApiGetRequest = function(options, callback) {
  if (! options.serviceName) return callback(new Error("Missing serviceName"));
  if (! options.opType) return callback(new Error("Missing opType"));
  if (! options.appId) return callback(new Error("Missing appId"));
  options.params = options.params || {};
  options.filters = options.filters || {};
  options.reqOptions = options.reqOptions || {};
  options.parser = options.parser || parseItemsFromResponse;

  if (options.serviceName === 'MerchandisingService') {
    options.reqOptions.decoding = 'buffer';   // otherwise fails to decode json. doesn't seem to be necessary w/ FindingService.
  }

  _.defaults(options.params, defaultParams(options.serviceName, options.opType, options.appId));

  var url = buildRequestUrl(options.serviceName, options.params, options.filters);
  // console.log('url for', options.opType, 'request:\n', url.replace(/\&/g, '\n&'));

  var request = restler.get(url, options.reqOptions);
  var data;

  // emitted when the request has finished whether it was successful or not
  request.on('complete', function(result, response) {
    // [restler docs] 'If some error has occurred, result is always instance of Error'
    if (result instanceof Error) {
      var error = result;
      error.message = "Completed with error: " + error.message;
      return callback(error);
    }
    else if (response.statusCode !== 200) {
      return callback(new Error(util.format("Bad response status code", response.statusCode, result.toString())));
    }

    try {
      data = JSON.parse(result);

      // drill down to item(s). each service has its own structure.
      if (options.serviceName !== 'Shopping') {
        var responseKey = options.opType + 'Response';
        if (_.isUndefined(data[responseKey])) {
          return callback(new Error("Response missing " + responseKey + " element"));
        }
        data = data[responseKey];
      }

      if (_.isArray(data)) {
        data = _(data).first();
      }

      // 'ack' and 'errMsg' indicate errors.
      // - in FindingService it's nested, in Merchandising it's flat - flatten to normalize
      if (!_.isUndefined(data.ack)) data.ack = flatten(data.ack);
      else if (!_.isUndefined(data.Ack)) {      // uppercase, standardize.
        data.ack = flatten(data.Ack);
        delete data.Ack;
      }

      if (_.isUndefined(data.ack) || data.ack !== 'Success') {
        var errMsg = _.isUndefined(data.errorMessage) ? null : flatten(data.errorMessage);
        return callback(new Error(util.format("Bad 'ack' code", data.ack, 'errorMessage?', util.inspect(errMsg, true, 3))));
      }
    }
    catch(error) {
      return callback(error);
    }
    // console.log('completed successfully:\n', util.inspect(data, true, 10, true));

    // parse the response
    options.parser(data, function(error, items) {
      callback(error, items);
    });
  });

  // emitted when some errors have occurred
  // either this OR 'completed' should fire
  request.on('error', function(error, response) {
    error.message = "Request error: " + error.message;
    callback(error);
  });

  // emitted when the request was successful
  // -- overlaps w/ 'completed', don't use
  // request.on('success', function(data, response) {
  // });

  // emitted when the request was successful, but 4xx status code returned
  // -- overlaps w/ 'completed', don't use
  // request.on('fail', function(data, response) {
  // });

};
module.exports.ebayApiGetRequest = ebayApiGetRequest;

// PAGINATE multiple requests in parallel (max 100 per page, 100 pages = 10k items)
var paginateGetRequest = function(options, callback) {
  if (! options.serviceName) return callback(new Error("Missing serviceName"));
  if (! options.opType) return callback(new Error("Missing opType"));
  if (! options.appId) return callback(new Error("Missing appId"));
  options.params = options.params || {};
  options.filters = options.filters || {};
  options.reqOptions = options.reqOptions || {};
  options.pages = options.pages || 2;
  options.perPage = options.perPage || 10;
  options.parser = options.parser || parseItemsFromResponse;

  console.log('Paginated request to', options.serviceName, 'for', options.pages, 'pages of', options.perPage, 'items each');

  var mergedItems = [],   // to be merged
      pageParams = [],
      p;

  if (!(_.isNumber(options.pages) && options.pages > 0)) return callback(new Error("Invalid number of pages requested", options.pages));

  // index is pageInd-1. can't start array from 1, it just fills in 0 with undefined.
  for(p = 0; p < options.pages; p++) {
    pageParams[p] = {
      'paginationInput.entriesPerPage': options.perPage,
      'paginationInput.pageNumber': p+1
    };
  }
  // console.log(pageParams.length, 'pages:', pageParams);

  // run pagination requests in parallel
  async.forEach(pageParams,
    function eachPage(thisPageParams, nextPage) {

      // merge the pagination params. new var to avoid confusing scope.
      var thisPageOptions = _.extend({}, options);
      thisPageOptions.params = _.extend({}, thisPageOptions.params, thisPageParams);

      console.log("Requesting page", thisPageOptions.params['paginationInput.pageNumber'], 'with', thisPageOptions.params['paginationInput.entriesPerPage'], 'items...');

      ebayApiGetRequest(thisPageOptions, function(error, items) {
        // console.log("Got response from page", thisPageOptions.params['paginationInput.pageNumber']);

        if (error) {
          error.message = "Error on page " + thisPageOptions.params['paginationInput.pageNumber'] + ": " + error.message;
          return nextPage(error);
        }

        if (!_.isArray(items)) {
          return nextPage(new Error("Parser did not return an array, returned a " + typeof items));
        }

        console.log('Got', items.length, 'items from page', thisPageOptions.params['paginationInput.pageNumber']);

        // console.log('have', mergedItems.length, 'previous items, adding', items.length, 'new items...');
        mergedItems = mergedItems.concat(items);
        // console.log('now have', mergedItems.length, 'merged items');

        nextPage(null);
      });
    },

    function pagesDone(error) {
      // console.log('pages are done');
      if (error) callback(error);
      else callback(null, mergedItems);
    }
  );  //forEach

};
module.exports.paginateGetRequest = paginateGetRequest;

// helper: RECURSIVELY turn 1-element arrays/objects into flat vars
// (different from _.flatten() which returns an array)
var flatten = function(el, iter) {
  // sanity check
  if (_.isUndefined(iter)) var iter = 1;
  if (iter > 100) {
    console.error("recursion error, stop at", iter);
    return;
  }

  // flatten 1-item arrays
  if (_.isArray(el) && el.length === 1) {
    el = _.first(el);
  }

  // special value-pair structure in the ebay API: turn { @key:KEY, __value__:VALUE } into { KEY: VALUE }
  if (isValuePair(el)) {
    var values = _.values(el);
    // console.log('found special:', el);
    el = {};
    el[ values[0] ] = values[1];
    // console.log('handled special:', el);
  }

  // previous fix just creates an array of these. we want a clean key:val obj.
  // so, is this an array of special value-pairs?
  if (isArrayOfValuePairs(el)) {
    var fixEl = {};
    _(el).each(function(pair) {
      _.extend(fixEl, flatten(pair));   // fix each, combine
    });
    el = fixEl;
  }

  // flatten sub-elements
  if (_.isArray(el) || _.isObject(el)) {
    _.each(el, function(subEl, subInd) {
      el[subInd] = flatten(el[subInd], iter++);
    });
  }

  return el;
};
module.exports.flatten = flatten;

// helper: identify a structure returned from the API:
// { @key:KEY, __value__:VALUE } => want to turn into { KEY: VALUE }
// (and array of these into single obj)
var isValuePair = function(el) {
  if (_.isObject(el) && _.size(el) === 2) {
    var keys = _.keys(el);
    if (new RegExp(/^@/).test(keys[0]) && keys[1] === '__value__') {
      return true;
    }
  }
  return false;
};

// helper: find an array containing only special key-value pairs
// e.g. 'galleryURL' (makes it easier to handle in MongoDB)
var isArrayOfValuePairs = function(el) {
  if (_.isArray(el)) {
    if (_.all(el, isValuePair)) return true;
  }
  return false;
};

// extract an array of items from responses. differs by query type.
// @todo build this out as more queries are added...
var parseItemsFromResponse = function(data, callback) {
  // console.log('parse data', data);

  var items = [];
  try {
    if (typeof data.Item !== 'undefined') {       // e.g. for Shopping::GetSingleItem
      items = [ data.Item ];    // preserve array for standardization (?)
    }

    else if (typeof data.searchResult !== 'undefined') {    // e.g. for FindingService
      // reduce in steps so successful-but-empty responses don't throw error
      if (!_.isEmpty(data.searchResult)) {
        data = _(data.searchResult).first();
        if (typeof data !== 'undefined') {
          if (typeof data.item !== 'undefined') {
            items = data.item;
          }
        }
      }
    }
    else if (typeof data.itemRecommendations !== 'undefined') {
      if (typeof data.itemRecommendations !== 'undefined') {
        if (typeof data.itemRecommendations.item !== 'undefined') {
          items = _.isArray(data.itemRecommendations.item) ? data.itemRecommendations.item : [];
        }
      }
    }

    // recursively flatten 1-level arrays and "@key:__VALUE__" pairs
    items = _(items).map(function(item) {
      return flatten(item);
    });
  }
  catch(error) {
    callback(error);
  }

  callback(null, items);
};
module.exports.parseItemsFromResponse = parseItemsFromResponse;

// check if an item URL is an affiliate URL
// non-affil URLs look like 'http://www.ebay.com...', affil URLs look like 'http://rover.ebay.com/rover...'
//  and have param &campid=TRACKINGID (campid=1234567890)
module.exports.checkAffiliateUrl = function(url) {
  var regexAffil = /http\:\/\/rover\.ebay\.com\/rover/,
      regexNonAffil = /http\:\/\/www\.ebay\.com/,
      regexCampaign = /campid=[0-9]{5}/;

  return (regexAffil.test(url) && !regexNonAffil.test(url) && regexCampaign.test(url));
};

<|endoftext|>