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

var base64 = require('base64-js');
var xmlbuilder = require('xmlbuilder');

/**
 * Module exports.
 */

exports.build = build;

/**
 * Accepts a `Date` instance and returns an ISO date string.
 *
 * @param {Date} d - Date instance to serialize
 * @returns {String} ISO date string representation of `d`
 * @api private
 */

function ISODateString(d){
  function pad(n){
    return n < 10 ? '0' + n : n;
  }
  return d.getUTCFullYear()+'-'
    + pad(d.getUTCMonth()+1)+'-'
    + pad(d.getUTCDate())+'T'
    + pad(d.getUTCHours())+':'
    + pad(d.getUTCMinutes())+':'
    + pad(d.getUTCSeconds())+'Z';
}

/**
 * Returns the internal "type" of `obj` via the
 * `Object.prototype.toString()` trick.
 *
 * @param {Mixed} obj - any value
 * @returns {String} the internal "type" name
 * @api private
 */

var toString = Object.prototype.toString;
function type (obj) {
  var m = toString.call(obj).match(/\[object (.*)\]/);
  return m ? m[1] : m;
}

/**
 * Generate an XML plist string from the input object `obj`.
 *
 * @param {Object} obj - the object to convert
 * @param {Object} [opts] - optional options object
 * @returns {String} converted plist XML string
 * @api public
 */

function build (obj, opts) {
  var XMLHDR = {
    version: '1.0',
    encoding: 'UTF-8'
  };

  var XMLDTD = {
    pubid: '-//Apple//DTD PLIST 1.0//EN',
    sysid: 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'
  };

  var doc = xmlbuilder.create('plist');

  doc.dec(XMLHDR.version, XMLHDR.encoding, XMLHDR.standalone);
  doc.dtd(XMLDTD.pubid, XMLDTD.sysid);
  doc.att('version', '1.0');

  walk_obj(obj, doc);

  if (!opts) opts = {};
  // default `pretty` to `true`
  opts.pretty = opts.pretty !== false;
  return doc.end(opts);
}

/**
 * depth first, recursive traversal of a javascript object. when complete,
 * next_child contains a reference to the build XML object.
 *
 * @api private
 */

function walk_obj(next, next_child) {
  var tag_type, i, prop;
  var name = type(next);

  if (Array.isArray(next)) {
    next_child = next_child.ele('array');
    for (i = 0; i < next.length; i++) {
      walk_obj(next[i], next_child);
    }

  } else if (Buffer.isBuffer(next)) {
    next_child.ele('data').raw(next.toString('base64'));

  } else if ('Object' == name) {
    next_child = next_child.ele('dict');
    for (prop in next) {
      if (next.hasOwnProperty(prop)) {
        next_child.ele('key').txt(prop);
        walk_obj(next[prop], next_child);
      }
    }

  } else if ('Number' == name) {
    // detect if this is an integer or real
    // TODO: add an ability to force one way or another via a "cast"
    tag_type = (next % 1 === 0) ? 'integer' : 'real';
    next_child.ele(tag_type).txt(next.toString());

  } else if ('Date' == name) {
    next_child.ele('date').txt(ISODateString(new Date(next)));

  } else if ('Boolean' == name) {
    val = next ? 'true' : 'false';
    next_child.ele(val);

  } else if ('String' == name) {
    next_child.ele('string').txt(next);

  } else if ('ArrayBuffer' == name) {
    next_child.ele('data').raw(base64.fromByteArray(next));

  } else if (next.buffer && 'ArrayBuffer' == type(next.buffer)) {
    // a typed array
    next_child.ele('data').raw(base64.fromByteArray(new Uint8Array(next.buffer), next_child));

  }
}
<commit_msg>build: fix global variable `val` leak<commit_after>
/**
 * Module dependencies.
 */

var base64 = require('base64-js');
var xmlbuilder = require('xmlbuilder');

/**
 * Module exports.
 */

exports.build = build;

/**
 * Accepts a `Date` instance and returns an ISO date string.
 *
 * @param {Date} d - Date instance to serialize
 * @returns {String} ISO date string representation of `d`
 * @api private
 */

function ISODateString(d){
  function pad(n){
    return n < 10 ? '0' + n : n;
  }
  return d.getUTCFullYear()+'-'
    + pad(d.getUTCMonth()+1)+'-'
    + pad(d.getUTCDate())+'T'
    + pad(d.getUTCHours())+':'
    + pad(d.getUTCMinutes())+':'
    + pad(d.getUTCSeconds())+'Z';
}

/**
 * Returns the internal "type" of `obj` via the
 * `Object.prototype.toString()` trick.
 *
 * @param {Mixed} obj - any value
 * @returns {String} the internal "type" name
 * @api private
 */

var toString = Object.prototype.toString;
function type (obj) {
  var m = toString.call(obj).match(/\[object (.*)\]/);
  return m ? m[1] : m;
}

/**
 * Generate an XML plist string from the input object `obj`.
 *
 * @param {Object} obj - the object to convert
 * @param {Object} [opts] - optional options object
 * @returns {String} converted plist XML string
 * @api public
 */

function build (obj, opts) {
  var XMLHDR = {
    version: '1.0',
    encoding: 'UTF-8'
  };

  var XMLDTD = {
    pubid: '-//Apple//DTD PLIST 1.0//EN',
    sysid: 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'
  };

  var doc = xmlbuilder.create('plist');

  doc.dec(XMLHDR.version, XMLHDR.encoding, XMLHDR.standalone);
  doc.dtd(XMLDTD.pubid, XMLDTD.sysid);
  doc.att('version', '1.0');

  walk_obj(obj, doc);

  if (!opts) opts = {};
  // default `pretty` to `true`
  opts.pretty = opts.pretty !== false;
  return doc.end(opts);
}

/**
 * depth first, recursive traversal of a javascript object. when complete,
 * next_child contains a reference to the build XML object.
 *
 * @api private
 */

function walk_obj(next, next_child) {
  var tag_type, i, prop;
  var name = type(next);

  if (Array.isArray(next)) {
    next_child = next_child.ele('array');
    for (i = 0; i < next.length; i++) {
      walk_obj(next[i], next_child);
    }

  } else if (Buffer.isBuffer(next)) {
    next_child.ele('data').raw(next.toString('base64'));

  } else if ('Object' == name) {
    next_child = next_child.ele('dict');
    for (prop in next) {
      if (next.hasOwnProperty(prop)) {
        next_child.ele('key').txt(prop);
        walk_obj(next[prop], next_child);
      }
    }

  } else if ('Number' == name) {
    // detect if this is an integer or real
    // TODO: add an ability to force one way or another via a "cast"
    tag_type = (next % 1 === 0) ? 'integer' : 'real';
    next_child.ele(tag_type).txt(next.toString());

  } else if ('Date' == name) {
    next_child.ele('date').txt(ISODateString(new Date(next)));

  } else if ('Boolean' == name) {
    next_child.ele(next ? 'true' : 'false');

  } else if ('String' == name) {
    next_child.ele('string').txt(next);

  } else if ('ArrayBuffer' == name) {
    next_child.ele('data').raw(base64.fromByteArray(next));

  } else if (next.buffer && 'ArrayBuffer' == type(next.buffer)) {
    // a typed array
    next_child.ele('data').raw(base64.fromByteArray(new Uint8Array(next.buffer), next_child));

  }
}
<|endoftext|>


<commit_before>/*
 * Simplecrawler - cache module
 * https://github.com/cgiffard/node-simplecrawler
 *
 * Copyright (c) 2011-2015, Christopher Giffard
 *
 */

var EventEmitter = require("events").EventEmitter;
var FilesystemBackend = require("./cache-backend-fs.js");

// Init cache wrapper for backend...
var Cache = function Cache(cacheLoadParameter, cacheBackend) {

    // Ensure parameters are how we want them...
    cacheBackend = typeof cacheBackend === "object" ? cacheBackend : FilesystemBackend;
    cacheLoadParameter = cacheLoadParameter instanceof Array ? cacheLoadParameter : [cacheLoadParameter];

    // Now we can just run the factory.
    this.datastore = cacheBackend.apply(cacheBackend, cacheLoadParameter);

    // Instruct the backend to load up.
    this.datastore.load();
};

Cache.prototype = new EventEmitter();

// Set up data import and export functions
Cache.prototype.setCacheData = function(queueObject, data, callback) {
    this.datastore.setItem(queueObject, data, callback);
    this.emit("setcache", queueObject, data);
};

Cache.prototype.getCacheData = function(queueObject, callback) {
    this.datastore.getItem(queueObject, callback);
};

Cache.prototype.saveCache = function() {
    this.datastore.saveCache();
};

module.exports = Cache;
module.exports.Cache = Cache;
module.exports.FilesystemBackend = FilesystemBackend;
<commit_msg>Change cache backend parameter to a function<commit_after>/*
 * Simplecrawler - cache module
 * https://github.com/cgiffard/node-simplecrawler
 *
 * Copyright (c) 2011-2015, Christopher Giffard
 *
 */

var EventEmitter = require("events").EventEmitter;
var FilesystemBackend = require("./cache-backend-fs.js");

// Init cache wrapper for backend...
var Cache = function Cache(cacheLoadParameter, cacheBackend) {

    // Ensure parameters are how we want them...
    cacheBackend = typeof cacheBackend === "function" ? cacheBackend : FilesystemBackend;
    cacheLoadParameter = cacheLoadParameter instanceof Array ? cacheLoadParameter : [cacheLoadParameter];

    // Now we can just run the factory.
    this.datastore = cacheBackend.apply(cacheBackend, cacheLoadParameter);

    // Instruct the backend to load up.
    this.datastore.load();
};

Cache.prototype = new EventEmitter();

// Set up data import and export functions
Cache.prototype.setCacheData = function(queueObject, data, callback) {
    this.datastore.setItem(queueObject, data, callback);
    this.emit("setcache", queueObject, data);
};

Cache.prototype.getCacheData = function(queueObject, callback) {
    this.datastore.getItem(queueObject, callback);
};

Cache.prototype.saveCache = function() {
    this.datastore.saveCache();
};

module.exports = Cache;
module.exports.Cache = Cache;
module.exports.FilesystemBackend = FilesystemBackend;
<|endoftext|>


<commit_before>var request = require('request')

var CART_ITEMS_URL = 'https://rentoys.xyz/index.php/rest/V1/carts/mine/items'
var CART_DETAILS = "https://rentoys.xyz/index.php/rest/V1/carts/mine/"

/**
 * This function retrieves items in cart of the given userusing acces token
 * @param {String} access_token
 * @param {function} callback callback function
 * @return {json} body returns cart items of the user in json format
 */
exports.cart_items = function (access_token, callback) {
    options = {
        url: CART_ITEMS_URL,
        method: 'GET',
        headers: {
            'Authorization': 'Bearer ' + access_token,
            'Content-Type': 'application/json'
        }
    }
    request(options, function (err, response, body) {
        if (!err){
            callback(body)
        }
    })
}

/**
 * This function creates a list of product sku's of the items in the cart
 * @param {json} data items in te cart of the user
 * @param {function} callback callback function
 * @return {list} returns a list of product sku's
 */
exports.retrieve_product_sku = function (data, callback) {
    var product_sku = []
    for (var i in data){
        var data_frame = data[i]
        product_sku.push(data_frame.sku)
    }
    callback(product_sku)
}

/**
 * This function creates the details of the cart and its items
 * @param {json} cart_details detsils of the cart
 * @param {json} cart_items product details of the items in the cart
 * @param {list} cart_items_mini A list of items in the cart
 * @param {function} callback callback function
 * @return {json} json_obj returns complete details of he cart and its items
 */
exports.cart_info_items = function (cart_details, cart_items, cart_items_mini, callback) {
    cart_details = JSON.parse(cart_details)
    var total_price = 0
    var total_items = 0
    for (var i in cart_items.records){
        var item = cart_items.records[i]
        item.quantity = cart_items_mini[i].qty
        item.total_item_price = item.price * item.quantity
        item.total_item_price_formatted = '$' + item.total_item_price
        total_price += item.total_item_price
        total_items += item.quantity
        item.variants[0].id = cart_items.records[i].id
        item.variants[0].product_id = cart_items.records[i].id
        item.variants[0].remote_id = cart_items.records[i].id
        item.url = cart_items.records[i].url
        item.name = cart_items.records[i].name
        item.price = cart_items.records[i].price
        item.price_formatted = cart_items.records[i].price_formatted
        item.category = cart_items.records[i].category
        item.discount_price = cart_items.records[i].discount_price
        item.discount_prie_formatted = cart_items.records[i].discount_price_formatted
        item.currency = cart_items.records[i].currency
        item.code = cart_items.records[i].code
        item.description = cart_items.records[i].description
        item.main_image = cart_items.records[i].main_image
        item.expiration = 123456
        item.is_reservation = false
    }
    var json_obj = {
        id: cart_details.id,
        product_count: total_items,
        total_price: total_price,
        total_price_formatted: '$' + total_price,
        currency: 'USD',
        items: cart_items.records,
        shipping: null,
        discounts: []

    }
    callback(json_obj)
}

/**
 * This function  retrieves cart details using access token of the user
 * @param {String} access_token access token of the user
 * @param {function} callback callback function
 * @return {json} body returns cart details in json format
 */
exports.cart_details = function (access_token, callback) {
    console.log(access_token)
    options = {
        url: CART_DETAILS,
        method: 'GET',
        headers: {
            'Authorization': 'Bearer ' + access_token,
            'Content-Type': 'application/json'
        }
    }
    request(options, function (err, response, body) {
        if(!err){
            callback(body)
        }
        else{
            callback({status: 500})
        }
    })
}
<commit_msg>fix: fix in cart api "image not showing"<commit_after>var request = require('request')

var CART_ITEMS_URL = 'https://rentoys.xyz/index.php/rest/V1/carts/mine/items'
var CART_DETAILS = "https://rentoys.xyz/index.php/rest/V1/carts/mine/"

/**
 * This function retrieves items in cart of the given userusing acces token
 * @param {String} access_token
 * @param {function} callback callback function
 * @return {json} body returns cart items of the user in json format
 */
exports.cart_items = function (access_token, callback) {
    options = {
        url: CART_ITEMS_URL,
        method: 'GET',
        headers: {
            'Authorization': 'Bearer ' + access_token,
            'Content-Type': 'application/json'
        }
    }
    request(options, function (err, response, body) {
        if (!err){
            callback(body)
        }
    })
}

/**
 * This function creates a list of product sku's of the items in the cart
 * @param {json} data items in te cart of the user
 * @param {function} callback callback function
 * @return {list} returns a list of product sku's
 */
exports.retrieve_product_sku = function (data, callback) {
    var product_sku = []
    for (var i in data){
        var data_frame = data[i]
        product_sku.push(data_frame.sku)
    }
    callback(product_sku)
}

/**
 * This function creates the details of the cart and its items
 * @param {json} cart_details detsils of the cart
 * @param {json} cart_items product details of the items in the cart
 * @param {list} cart_items_mini A list of items in the cart
 * @param {function} callback callback function
 * @return {json} json_obj returns complete details of he cart and its items
 */
exports.cart_info_items = function (cart_details, cart_items, cart_items_mini, callback) {
    cart_details = JSON.parse(cart_details)
    var total_price = 0
    var total_items = 0
    for (var i in cart_items.records){
        var item = cart_items.records[i]
        item.quantity = cart_items_mini[i].qty
        item.total_item_price = item.price * item.quantity
        item.total_item_price_formatted = '$' + item.total_item_price
        total_price += item.total_item_price
        total_items += item.quantity
        item.variants[0].id = cart_items.records[i].id
        item.variants[0].product_id = cart_items.records[i].id
        item.variants[0].remote_id = cart_items.records[i].id
        item.variants[0].main_image = cart_items.records[i].main_image
        item.variants[0].url = cart_items.records[i].url
        item.variants[0].name = cart_items.records[i].name
        item.variants[0].price = cart_items.records[i].price
        item.variants[0].price_formatted = cart_items.records[i].price_formatted
        item.variants[0].category = cart_items.records[i].category
        item.variants[0].discount_price = cart_items.records[i].discount_price
        item.variants[0].discount_prie_formatted = cart_items.records[i].discount_price_formatted
        item.variants[0].currency = cart_items.records[i].currency
        item.variants[0].code = cart_items.records[i].code
        item.variants[0].description = cart_items.records[i].description
        item.variants[0].main_image = cart_items.records[i].main_image
        item.variant = item.variants[0]
        item.expiration = 123456
        item.is_reservation = false
    }
    var json_obj = {
        id: cart_details.id,
        product_count: total_items,
        total_price: total_price,
        total_price_formatted: '$' + total_price,
        currency: 'USD',
        items: cart_items.records,
        shipping: null,
        discounts: []

    }
    callback(json_obj)
}

/**
 * This function  retrieves cart details using access token of the user
 * @param {String} access_token access token of the user
 * @param {function} callback callback function
 * @return {json} body returns cart details in json format
 */
exports.cart_details = function (access_token, callback) {
    console.log(access_token)
    options = {
        url: CART_DETAILS,
        method: 'GET',
        headers: {
            'Authorization': 'Bearer ' + access_token,
            'Content-Type': 'application/json'
        }
    }
    request(options, function (err, response, body) {
        if(!err){
            callback(body)
        }
        else{
            callback({status: 500})
        }
    })
}
<|endoftext|>


<commit_before>/**
 * @constructor
 * @param {o} o Orchestrate object
 * @param {Object} branchConfig
 * @returns {Chain}
 */
function Chain (o, branchConfig) {
	this.steps = [];
	this.branchConfig = branchConfig;
	this.o = o;
	this.currentIndex = -1;
};

/**
 * Registers a new step
 * @param {Object} config
 * @param {Callback} callback
 * @returns {undefined}
 */
Chain.prototype.addStep = function (config, callback) {
	this.steps.push({
		config: config,
		callback: callback
	});
};

/**
 * Executes next step
 * @param {o} o Orechstrate object
 * @param {Chain} chain
 * @param {mixed} value
 * @returns {mixed}
 */
Chain.prototype.next = function (o, chain, value) {
	var nextIndex = this.currentIndex + 1;

	// check if next index exists
	if (nextIndex < this.steps.length) {
		return this.executeStepAt(nextIndex, o, chain, value);
	}
};

/**
 * Executes step
 * @param {Number} stepIndex
 * @param {o} o Orechstrate object
 * @param {Chain} chain
 * @param {mixed} value
 * @returns {undefined}
 */
Chain.prototype.executeStepAt = function (stepIndex, o, chain, value) {
	// validate step exists
	if (typeof this.steps[stepIndex] === undefined) {
		throw new Error('stepIndex: ' + stepIndex + ' does not exist on chain');
	}

	// rebind o if its undefined
	if (o === undefined) {
		o = this.o;
	}

	// bind "this" chain if its undefined
	if (chain === undefined) {
		chain = this;
	}

	// if there's a default value config, apply it
	if (typeof this.steps[stepIndex].config.defaultValue === undefined) {
		value = this.steps[stepIndex].config.defaultValue;
	}

	// set pointer
	this.currentIndex = stepIndex;

	// invoke method (with config)
	this.steps[stepIndex].callback(o, chain, value, this.steps[stepIndex].config);
};

// chain factory
module.exports = function (o, branchConfig) {
	return new Chain(o, branchConfig);
};<commit_msg>added additional default value check<commit_after>/**
 * @constructor
 * @param {o} o Orchestrate object
 * @param {Object} branchConfig
 * @returns {Chain}
 */
function Chain (o, branchConfig) {
	this.steps = [];
	this.branchConfig = branchConfig;
	this.o = o;
	this.currentIndex = -1;
};

/**
 * Registers a new step
 * @param {Object} config
 * @param {Callback} callback
 * @returns {undefined}
 */
Chain.prototype.addStep = function (config, callback) {
	this.steps.push({
		config: config,
		callback: callback
	});
};

/**
 * Executes next step
 * @param {o} o Orechstrate object
 * @param {Chain} chain
 * @param {mixed} value
 * @returns {mixed}
 */
Chain.prototype.next = function (o, chain, value) {
	var nextIndex = this.currentIndex + 1;

	// check if next index exists
	if (nextIndex < this.steps.length) {
		return this.executeStepAt(nextIndex, o, chain, value);
	}
};

/**
 * Executes step
 * @param {Number} stepIndex
 * @param {o} o Orechstrate object
 * @param {Chain} chain
 * @param {mixed} value
 * @returns {undefined}
 */
Chain.prototype.executeStepAt = function (stepIndex, o, chain, value) {
	// validate step exists
	if (typeof this.steps[stepIndex] === undefined) {
		throw new Error('stepIndex: ' + stepIndex + ' does not exist on chain');
	}

	// rebind o if its undefined
	if (o === undefined) {
		o = this.o;
	}

	// bind "this" chain if its undefined
	if (chain === undefined) {
		chain = this;
	}

	// if there's a default value config, apply it
	if (value === undefined && this.steps[stepIndex].config.defaultValue !== undefined) {
		value = this.steps[stepIndex].config.defaultValue;
	}

	// set pointer
	this.currentIndex = stepIndex;

	// invoke method (with config)
	this.steps[stepIndex].callback(o, chain, value, this.steps[stepIndex].config);
};

// chain factory
module.exports = function (o, branchConfig) {
	return new Chain(o, branchConfig);
};<|endoftext|>


<commit_before>'use strict';

const aws4 = require('aws4');
const debug = require('debug')('node-ses');
const parse = require('url').parse;
const querystring = require('querystring');
const request = require('request');
const xml2js = require('xml2js');
const xmlParser = new xml2js.Parser({explicitArray:false,mergeAttrs:true});

const SEND_EMAIL_ACTION = 'SendEmail';
const SEND_RAW_EMAIL_ACTION = 'SendRawEmail';

/**
 * Email constructor.
 *
 * @param {Object} options
 **/
function Email (options) {
  this.action = options.action || SEND_EMAIL_ACTION;
  this.key = options.key;
  this.secret = options.secret;
  this.amazon = options.amazon;
  this.from = options.from;
  this.subject = options.subject;
  this.message = options.message;
  this.altText = options.altText;
  this.rawMessage = options.rawMessage;
  this.configurationSet = options.configurationSet;
  this.messageTags = options.messageTags;
  this.extractRecipient(options, 'to');
  this.extractRecipient(options, 'cc');
  this.extractRecipient(options, 'bcc');
  this.extractRecipient(options, 'replyTo');
}

/**
 * Adds to, cc, and bcc fields to `data`.
 *
 * @param {Object} data
 * @return data
 **/
Email.prototype.addDestination = function (data) {
  this.to.forEach(function (to, i) {
    data['Destination.ToAddresses.member.' + (i + 1)] = to;
  });

  this.cc.forEach(function (to, i) {
    data['Destination.CcAddresses.member.' + (i + 1)] = to;
  });

  this.bcc.forEach(function (to, i) {
    data['Destination.BccAddresses.member.' + (i + 1)] = to;
  });

  return data;
};

/**
 * Adds ConfigurationSetName and Tags to `data`
 *
 * @param {Object} data
 * @return data
 **/
Email.prototype.addSESHeaders = function (data) {
  if (this.configurationSet) {
    data['ConfigurationSetName'] = this.configurationSet;
  }
  if (this.messageTags) {
    this.messageTags.forEach(function (tagSpec, i) {
      data['Tags.member.' + (i + 1) + '.Name'] = tagSpec.name;
      data['Tags.member.' + (i + 1) + '.Value'] = tagSpec.value;
    });
  }
  return data;
};

/**
 * Adds subject, alt text, and message body to `data`.
 *
 * @param {Object} data
 * @return data
 **/
Email.prototype.addMessage = function (data) {
  if (this.subject) {
    data['Message.Subject.Data'] = this.subject;
    data['Message.Subject.Charset'] = 'UTF-8';
  }

  if (this.message) {
    data['Message.Body.Html.Data'] = this.message;
    data['Message.Body.Html.Charset'] = 'UTF-8';
  }

  if (this.altText) {
    data['Message.Body.Text.Data'] = this.altText;
    data['Message.Body.Text.Charset'] = 'UTF-8';
  }

  return data;
};

/**
 * Adds the list of ReplyTos to `data`.
 *
 * @param {Object} data
 * @return data
 **/
Email.prototype.addReplyTo = function (data) {
  this.replyTo.forEach(function(to, i) {
    data['ReplyToAddresses.member.' + (i + 1)] = to;
  });

  return data;
};

/**
 * Prepares param object for the AWS request.
 *
 * @return {Object}
 */
Email.prototype.data = function () {
  var data = {
      Action: this.action
    , AWSAccessKeyId: this.key
    , Source: this.from
  };

  // recipients and reply tos
  data = this.addDestination(data);
  data = this.addReplyTo(data);

  // SES Event Publishing Information
  data = this.addSESHeaders(data);

  // message payload
  if (this.action === SEND_EMAIL_ACTION) {
    data = this.addMessage(data);
  } else if (this.action === SEND_RAW_EMAIL_ACTION) {
    data['RawMessage.Data'] = Buffer.from(this.rawMessage).toString('base64');
  }

  return data;
};

/**
 * Extracts recipients from options.
 *
 * @param {String} prop - either to,cc,bcc,replyTo
 * @param {Object} options
 */
Email.prototype.extractRecipient = function (options, prop) {
  if (options[prop]) {
    this[prop] = Array.isArray(options[prop]) ? options[prop] : [options[prop]];
  } else {
    this[prop] = [];
  }
};

/**
 * Creates required AWS headers.
 *
 * No additional custom headers by default.
 *
 * @return Object
 **/
Email.prototype.headers = function () {
  var headers = {};
  return headers;
};

/**
 * Sends the email.
 *
 * @param {Function} callback
 * @api Public
 **/
Email.prototype.send = function send (callback) {
  const invalid = this.validate();

  if (invalid) {
    return callback(new Error(invalid));
  }

  // Prepare the data and send to it AWS SES REST API

  var data = querystring.stringify(this.data());
  var parsedUrl = parse(this.amazon);
  var headers = this.headers();

  var options = {
      uri: this.amazon
    , host: parsedUrl.hostname
    , headers: headers
    , body: data
    , service: 'ses'
  };

  var credentials = this.key && this.secret && {
    accessKeyId : this.key,
    secretAccessKey : this.secret
  };
  var signedOpts = aws4.sign(options, credentials);

  signedOpts.headers['Connection'] = 'keep-alive';

  debug('posting: %j', signedOpts);

  request(signedOpts, (err, res, data) => this._processResponse(err, res, data, callback));
};

/**
 * Process a response from the 'request' call used to POST email to SES
 * @private
 *
 */
Email.prototype._processResponse = function _processResponse (err, res, data, callback) {
  debug('received: %s %d %j', err, res && res.statusCode || 0, data);

  if (err) {
    return callback({
      Type : "NodeSesInternal",
      Code : "RequestError",
      Message: err,
    }, data, res);
  }

  if (res.statusCode < 200 || res.statusCode >= 400) {
    return xmlParser.parseString(data,function(err,result){
        if(err) {
          return callback({
            Type: "NodeSesInternal",
            Code: "ParseError",
            Message: err
          });
        }

        // Return an error object with keys of Type, Code and Message
        // Reference docs at: http://docs.aws.amazon.com/ses/latest/DeveloperGuide/query-interface-responses.html
        if(result && result.ErrorResponse){
          return callback(result.ErrorResponse.Error)
        }
        // Once during an S3 outage AWS returned valid XML that didn't match the usual error structure.
        // We cover this unlikely case
        else {
          return callback({
            Type : "NodeSesInternal",
            Code : "XmlError",
            Message: result,
          })
        }

    });
  } else {
    return callback(null, data, res);
  }
};

/**
 * Validates the input.
 *
 * @returns {String|Undefined}
 **/
Email.prototype.validate = function () {
  if (this.action === SEND_EMAIL_ACTION) {
    if (!this.to.length && !this.cc.length && !this.bcc.length) {
      return 'To, Cc or Bcc is required';
    }

    if (!(this.subject && this.subject.length)) {
      return 'Subject is required';
    }
  }

  if (this.action === SEND_RAW_EMAIL_ACTION) {
    if (!(this.rawMessage && this.rawMessage.length)) {
      return 'Raw message is required';
    }
  }

  // all actions require the following
  if (!(this.from && this.from.length)) {
    return 'From is required';
  }
};

/**
 * Exports
 **/
exports.Email = Email;

exports.actions = {
  SendEmail : SEND_EMAIL_ACTION,
  SendRawEmail : SEND_RAW_EMAIL_ACTION
};
<commit_msg>Drop the connection header since it is not allowed by SIGv4 to SES anymore.<commit_after>'use strict';

const aws4 = require('aws4');
const debug = require('debug')('node-ses');
const parse = require('url').parse;
const querystring = require('querystring');
const request = require('request');
const xml2js = require('xml2js');
const xmlParser = new xml2js.Parser({explicitArray:false,mergeAttrs:true});

const SEND_EMAIL_ACTION = 'SendEmail';
const SEND_RAW_EMAIL_ACTION = 'SendRawEmail';

/**
 * Email constructor.
 *
 * @param {Object} options
 **/
function Email (options) {
  this.action = options.action || SEND_EMAIL_ACTION;
  this.key = options.key;
  this.secret = options.secret;
  this.amazon = options.amazon;
  this.from = options.from;
  this.subject = options.subject;
  this.message = options.message;
  this.altText = options.altText;
  this.rawMessage = options.rawMessage;
  this.configurationSet = options.configurationSet;
  this.messageTags = options.messageTags;
  this.extractRecipient(options, 'to');
  this.extractRecipient(options, 'cc');
  this.extractRecipient(options, 'bcc');
  this.extractRecipient(options, 'replyTo');
}

/**
 * Adds to, cc, and bcc fields to `data`.
 *
 * @param {Object} data
 * @return data
 **/
Email.prototype.addDestination = function (data) {
  this.to.forEach(function (to, i) {
    data['Destination.ToAddresses.member.' + (i + 1)] = to;
  });

  this.cc.forEach(function (to, i) {
    data['Destination.CcAddresses.member.' + (i + 1)] = to;
  });

  this.bcc.forEach(function (to, i) {
    data['Destination.BccAddresses.member.' + (i + 1)] = to;
  });

  return data;
};

/**
 * Adds ConfigurationSetName and Tags to `data`
 *
 * @param {Object} data
 * @return data
 **/
Email.prototype.addSESHeaders = function (data) {
  if (this.configurationSet) {
    data['ConfigurationSetName'] = this.configurationSet;
  }
  if (this.messageTags) {
    this.messageTags.forEach(function (tagSpec, i) {
      data['Tags.member.' + (i + 1) + '.Name'] = tagSpec.name;
      data['Tags.member.' + (i + 1) + '.Value'] = tagSpec.value;
    });
  }
  return data;
};

/**
 * Adds subject, alt text, and message body to `data`.
 *
 * @param {Object} data
 * @return data
 **/
Email.prototype.addMessage = function (data) {
  if (this.subject) {
    data['Message.Subject.Data'] = this.subject;
    data['Message.Subject.Charset'] = 'UTF-8';
  }

  if (this.message) {
    data['Message.Body.Html.Data'] = this.message;
    data['Message.Body.Html.Charset'] = 'UTF-8';
  }

  if (this.altText) {
    data['Message.Body.Text.Data'] = this.altText;
    data['Message.Body.Text.Charset'] = 'UTF-8';
  }

  return data;
};

/**
 * Adds the list of ReplyTos to `data`.
 *
 * @param {Object} data
 * @return data
 **/
Email.prototype.addReplyTo = function (data) {
  this.replyTo.forEach(function(to, i) {
    data['ReplyToAddresses.member.' + (i + 1)] = to;
  });

  return data;
};

/**
 * Prepares param object for the AWS request.
 *
 * @return {Object}
 */
Email.prototype.data = function () {
  var data = {
      Action: this.action
    , AWSAccessKeyId: this.key
    , Source: this.from
  };

  // recipients and reply tos
  data = this.addDestination(data);
  data = this.addReplyTo(data);

  // SES Event Publishing Information
  data = this.addSESHeaders(data);

  // message payload
  if (this.action === SEND_EMAIL_ACTION) {
    data = this.addMessage(data);
  } else if (this.action === SEND_RAW_EMAIL_ACTION) {
    data['RawMessage.Data'] = Buffer.from(this.rawMessage).toString('base64');
  }

  return data;
};

/**
 * Extracts recipients from options.
 *
 * @param {String} prop - either to,cc,bcc,replyTo
 * @param {Object} options
 */
Email.prototype.extractRecipient = function (options, prop) {
  if (options[prop]) {
    this[prop] = Array.isArray(options[prop]) ? options[prop] : [options[prop]];
  } else {
    this[prop] = [];
  }
};

/**
 * Creates required AWS headers.
 *
 * No additional custom headers by default.
 *
 * @return Object
 **/
Email.prototype.headers = function () {
  var headers = {};
  return headers;
};

/**
 * Sends the email.
 *
 * @param {Function} callback
 * @api Public
 **/
Email.prototype.send = function send (callback) {
  const invalid = this.validate();

  if (invalid) {
    return callback(new Error(invalid));
  }

  // Prepare the data and send to it AWS SES REST API

  var data = querystring.stringify(this.data());
  var parsedUrl = parse(this.amazon);
  var headers = this.headers();

  var options = {
      uri: this.amazon
    , host: parsedUrl.hostname
    , headers: headers
    , body: data
    , service: 'ses'
  };

  var credentials = this.key && this.secret && {
    accessKeyId : this.key,
    secretAccessKey : this.secret
  };
  var signedOpts = aws4.sign(options, credentials);

  debug('posting: %j', signedOpts);

  request(signedOpts, (err, res, data) => this._processResponse(err, res, data, callback));
};

/**
 * Process a response from the 'request' call used to POST email to SES
 * @private
 *
 */
Email.prototype._processResponse = function _processResponse (err, res, data, callback) {
  debug('received: %s %d %j', err, res && res.statusCode || 0, data);

  if (err) {
    return callback({
      Type : "NodeSesInternal",
      Code : "RequestError",
      Message: err,
    }, data, res);
  }

  if (res.statusCode < 200 || res.statusCode >= 400) {
    return xmlParser.parseString(data,function(err,result){
        if(err) {
          return callback({
            Type: "NodeSesInternal",
            Code: "ParseError",
            Message: err
          });
        }

        // Return an error object with keys of Type, Code and Message
        // Reference docs at: http://docs.aws.amazon.com/ses/latest/DeveloperGuide/query-interface-responses.html
        if(result && result.ErrorResponse){
          return callback(result.ErrorResponse.Error)
        }
        // Once during an S3 outage AWS returned valid XML that didn't match the usual error structure.
        // We cover this unlikely case
        else {
          return callback({
            Type : "NodeSesInternal",
            Code : "XmlError",
            Message: result,
          })
        }

    });
  } else {
    return callback(null, data, res);
  }
};

/**
 * Validates the input.
 *
 * @returns {String|Undefined}
 **/
Email.prototype.validate = function () {
  if (this.action === SEND_EMAIL_ACTION) {
    if (!this.to.length && !this.cc.length && !this.bcc.length) {
      return 'To, Cc or Bcc is required';
    }

    if (!(this.subject && this.subject.length)) {
      return 'Subject is required';
    }
  }

  if (this.action === SEND_RAW_EMAIL_ACTION) {
    if (!(this.rawMessage && this.rawMessage.length)) {
      return 'Raw message is required';
    }
  }

  // all actions require the following
  if (!(this.from && this.from.length)) {
    return 'From is required';
  }
};

/**
 * Exports
 **/
exports.Email = Email;

exports.actions = {
  SendEmail : SEND_EMAIL_ACTION,
  SendRawEmail : SEND_RAW_EMAIL_ACTION
};
<|endoftext|>


<commit_before>'use strict';
const logger       = require(__dirname + '/logger');
const _            = require('lodash');
const SparkPost    = require('sparkpost');
const htmlToText   = require('html-to-text');
const dns          = require('dns');

function Email(config) {
  let email = {
    config: config || {}
  };

  if (_.isUndefined(email.config.email_key)) {
    throw new Error("SparkPost API key is required to use SparkPost as the email driver");
  }

  if (_.isUndefined(email.config.sending_address)) {
    throw new Error("A valid sending email address is required");
  }

  if (_.isUndefined(email.config.inbound_address)) {
    throw new Error("An valid inbound email address is required");
  }

  if (_.isUndefined(email.config.domain)) {
    throw new Error("The domain of your bot is required");
  }

  let sparkpost = new SparkPost(email.config.email_key);

  // base send method
  email.send = function(message, cb) {
    let recipients = message.recipients || []
      , cc = message.cc || []
      , bcc = message.bcc || []
      , attachments = message.attachments || []
      , template_id = message.template_id || null
      , substitution_data = message.substitution_data || {}
      , content = { template_id: template_id };

    if (template_id === null) {
      content = {
        from: message.from || config.sending_address,
        reply_to: message.reply_to || config.inbound_address,
        subject: message.subject || '',
        html: message.html || '',
        text: message.text || htmlToText.fromString(message.html || ''),
        headers: message.headers || {},
      }
    }
    // so that you can use template for the reply to headers
    else {
      substitution_data['headers'] = headers;
    }

    let data = {
      content: content,
      substitution_data: substitution_data,
      recipients: recipients
    };

    if (cc.length > 0)
      data.cc = cc;

    if (bcc.length > 0)
      data.bcc = bcc;

    if (attachments.length > 0)
      data.attachments = attachments;

    logger.log('Send message: ', data);

    if (email.config.debug_mode !== true) {
      sparkpost.transmissions.send(data)
        .then((data) => {
          logger.debug('SparkPost response', data);

          if (_.isFunction(cb)) {
            cb(null, data);
          }
        })
        .catch((err) => {
          logger.debug('SparkPost error', err);

          if (_.isFunction(cb)) {
            return cb(err);
          }
        });
    }
    // call callback if one was given in debug mode
    else {
      if (_.isFunction(cb)) {
        return cb(null, {});
      }
    }
  };

  // get the recipients to reply to from the given email
  email.getRecipientList = function(message, listName) {
    // if the message doesn't exist, return an empty array
    if (_.isUndefined(message)) {
      return [];
    }

    let rawMessage = message._raw;
    // if the list doesn't exist, return an empty array
    if (!_.has(rawMessage.content, listName)) {
      return [];
    }

    // get all recipients in the given list
    let recipients = rawMessage.content[listName] || [];

    for (let i = 0; i < recipients.length; i++) {
      recipients[i] = email.formatRecipient(recipients[i]);
    }

    // remove the bot's addresses
    let cleanSendingDomain = _.toLower(_.trim(email.config.sending_address));
    let cleanInboundDomain = _.toLower(_.trim(email.config.inbound_address));

    removeAddress(recipients, cleanSendingDomain);
    removeAddress(recipients, cleanInboundDomain);

    return recipients;
  };

  // given a string or object format it to be a valid sparkpost recipient
  // {address: }
  email.formatRecipient = function(recipient) {
    return {
      address: {
        email: recipient
      }
    };
  };

  // get the headers object to send in order to reply to the received message
  email.getReplyHeaders = function(message) {
    return {
      "References": (message.headers['References'] || '') + ' ' + message.headers['Message-ID'],
      "In-Reply-To": message.headers['Message-ID']
    };
  };

  // set up everything with sparkpost
  // 1. create sending domain
  // 2. create inbound domain
  // 3. create relay webhook
  email.setup = function() {

    let sendingDomain = email.config.sending_address.split('@')[1];
    let inboundDomain = email.config.inbound_address.split('@')[1];
    let port          = (email.config.domain.indexOf('localhost') > 0 ? ":" + email.config.port : '');
    let webhookTarget = email.config.domain + port + email.config.endpoint;
    let webhookName   = email.config.bot_name + " Webhook";

    let completed = false;

    logger.log('=========================');
    logger.log('to skip this set setup to false in the config');

    sendingDomainSetup(function() {
      inboundDomainSetup(function() {
        webhookSetup();
      });
    });

    function sendingDomainSetup(cb) {
      // get the sending domain
      logger.log('Looking up sending domain...');
      sparkpost.sendingDomains.get(sendingDomain)
        // create sending domain
        .catch((err) => {
          logger.log('Domain does not exist\nCreating sending domain...');
          return sparkpost.sendingDomains.create({
            domain: sendingDomain
          });
        })
        // verify sending domain
        .then((data) => {
          logger.log('Domain exists\nVerifying sending domain...');
          return sparkpost.sendingDomains.verify(sendingDomain, {
            dkim_verify: true,
            spf_verify: true
          });
        })
        .then((data) => {
          if (data.results.ownership_verified) {
            logger.log('Domain verified');
            // go onto the next step
            if (_.isFunction(cb)) {
              return cb();
            }
          }

          throw new Error('Please verify your sending domain by following the instructions here:\nhttps://support.sparkpost.com/customer/portal/articles/1933360-verify-sending-domains');
        })
        .catch(throwUpError);
    }

    function inboundDomainSetup(cb) {
      // get the inbound domain
      logger.log('Looking up inbound domain...');
      sparkpost.inboundDomains.get(inboundDomain)
        // create the inbound domain
        .catch(() => {
          logger.log('Domain does not exist\nCreating inbound domain...');
          return sparkpost.inboundDomains.create({
            domain: inboundDomain
          });
        })
        //verify domain
        .then((data) => {
          logger.log('Domain exists\nVerifying inbound domain...');

          // get the mx records
          dns.resolveMx(inboundDomain, function(err, mxRecords) {
            let sparkpostMxRecords = ['rx1.sparkpostmail.com', 'rx2.sparkpostmail.com', 'rx3.sparkpostmail.com'];

            if (err)
              throw err;

            // remove the mx record from the sparkpostMxRecords array as it is found in the domains mx records
            _.each(mxRecords, (mxRecord) => {
              let index = _.indexOf(sparkpostMxRecords, mxRecord.exchange);

              if (index >= 0)
                sparkpostMxRecords.splice(index, 1);
            });

            if (sparkpostMxRecords.length > 0) {
              throw new Error('Inbound Domain failed to verify. Missing records: '+sparkpostMxRecords+'\n verify it by following the instructions here:\nhttps://support.sparkpost.com/customer/portal/articles/2039614-enabling-inbound-email-relaying-relay-webhooks');
            }
            else {
              logger.log('Domain verified');
              // go on to the next step
              if (_.isFunction(cb)) {
                cb();
              }
            }
          });
        })
        .catch(throwUpError);
    }

    function webhookSetup(cb) {
      // function to call at the end of the webhook setup
      function finished() {
        logger.log('Setup succeeded!');
        logger.log('=========================');

        // go on to the next step
        if (_.isFunction(cb)) {
          cb();
        }
      }

      logger.log('Looking up relay webhooks...');
      // get all webhooks
      sparkpost.relayWebhooks.list()
        // create the webhook
        .then((data) => {
          let webhookWithSameDomain = _.filter(data.results, { match: { domain: inboundDomain } });

          let webhookOptions = {
            name: webhookName,
            target: webhookTarget,
            match: {
              protocol: "SMTP",
              domain: inboundDomain
            }
          };

          if (_.isString(email.config.auth_token))
            webhookOptions.auth_token = email.config.auth_token;

          // if the webhook doesn't currently exist...
          if (webhookWithSameDomain.length === 0) {
            logger.log('Relay webhook does not exist with the given inbound domain\nCreating relay webhook...');

            return sparkpost.relayWebhooks.create(webhookOptions)
              .then((data) => {
                logger.log('Relay webhook created');

                finished();
              });
          }
          else {
            logger.log('Relay webhook exists');

            // update the relay webhook if the targets don't match
            if (webhookWithSameDomain[0].target !== webhookTarget || webhookWithSameDomain[0].auth_token !== webhookOptions.auth_token) {
              logger.warn('The existing webhook does not match the relay webhook options.');
              return sparkpost.relayWebhooks.update(webhookWithSameDomain[0].id, webhookOptions)
              .then((data) => {
                logger.log('Relay webhook updated');

                finished();
              });
            }

            // if it already exists the setup also succeeded
            finished();
          }
        })
        .catch(throwUpError);
    }
  };

  return email;
};

// removes the given address from the recipient list
function removeAddress(recipients, address) {
  _.remove(recipients, function(recipient) {
    let email = _.toLower(_.trim(recipient.address.email));

    return email === address;
  });
}

function throwUpError(err) {
  if (err.name === 'SparkPostError')
    console.log(JSON.stringify(err.errors, null, 2));

  setTimeout(function() { throw err; });
}

module.exports = Email;

<commit_msg>use bot name in from and reply_to addresses<commit_after>'use strict';
const logger       = require(__dirname + '/logger');
const _            = require('lodash');
const SparkPost    = require('sparkpost');
const htmlToText   = require('html-to-text');
const dns          = require('dns');

function Email(config) {
  let email = {
    config: config || {}
  };

  if (_.isUndefined(email.config.email_key)) {
    throw new Error("SparkPost API key is required to use SparkPost as the email driver");
  }

  if (_.isUndefined(email.config.sending_address)) {
    throw new Error("A valid sending email address is required");
  }

  if (_.isUndefined(email.config.inbound_address)) {
    throw new Error("An valid inbound email address is required");
  }

  if (_.isUndefined(email.config.domain)) {
    throw new Error("The domain of your bot is required");
  }

  let sparkpost = new SparkPost(email.config.email_key);

  // base send method
  email.send = function(message, cb) {
    let recipients = message.recipients || []
      , cc = message.cc || []
      , bcc = message.bcc || []
      , attachments = message.attachments || []
      , template_id = message.template_id || null
      , substitution_data = message.substitution_data || {}
      , content = { template_id: template_id };

    if (template_id === null) {
      content = {
        from: message.from || '"' + email.config.bot_name + '" <' + email.config.sending_address + '>',
        reply_to: message.reply_to || '"' + email.config.bot_name + '" <' + email.config.inbound_address + '>',
        subject: message.subject || '',
        html: message.html || '',
        text: message.text || htmlToText.fromString(message.html || ''),
        headers: message.headers || {},
      }
    }
    // so that you can use template for the reply to headers
    else {
      substitution_data['headers'] = headers;
    }

    let data = {
      content: content,
      substitution_data: substitution_data,
      recipients: recipients
    };

    if (cc.length > 0)
      data.cc = cc;

    if (bcc.length > 0)
      data.bcc = bcc;

    if (attachments.length > 0)
      data.attachments = attachments;

    logger.log('Send message: ', data);

    if (email.config.debug_mode !== true) {
      sparkpost.transmissions.send(data)
        .then((data) => {
          logger.debug('SparkPost response', data);

          if (_.isFunction(cb)) {
            cb(null, data);
          }
        })
        .catch((err) => {
          logger.debug('SparkPost error', err);

          if (_.isFunction(cb)) {
            return cb(err);
          }
        });
    }
    // call callback if one was given in debug mode
    else {
      if (_.isFunction(cb)) {
        return cb(null, {});
      }
    }
  };

  // get the recipients to reply to from the given email
  email.getRecipientList = function(message, listName) {
    // if the message doesn't exist, return an empty array
    if (_.isUndefined(message)) {
      return [];
    }

    let rawMessage = message._raw;
    // if the list doesn't exist, return an empty array
    if (!_.has(rawMessage.content, listName)) {
      return [];
    }

    // get all recipients in the given list
    let recipients = rawMessage.content[listName] || [];

    for (let i = 0; i < recipients.length; i++) {
      recipients[i] = email.formatRecipient(recipients[i]);
    }

    // remove the bot's addresses
    let cleanSendingDomain = _.toLower(_.trim(email.config.sending_address));
    let cleanInboundDomain = _.toLower(_.trim(email.config.inbound_address));

    removeAddress(recipients, cleanSendingDomain);
    removeAddress(recipients, cleanInboundDomain);

    return recipients;
  };

  // given a string or object format it to be a valid sparkpost recipient
  // {address: }
  email.formatRecipient = function(recipient) {
    return {
      address: {
        email: recipient
      }
    };
  };

  // get the headers object to send in order to reply to the received message
  email.getReplyHeaders = function(message) {
    return {
      "References": (message.headers['References'] || '') + ' ' + message.headers['Message-ID'],
      "In-Reply-To": message.headers['Message-ID']
    };
  };

  // set up everything with sparkpost
  // 1. create sending domain
  // 2. create inbound domain
  // 3. create relay webhook
  email.setup = function() {

    let sendingDomain = email.config.sending_address.split('@')[1];
    let inboundDomain = email.config.inbound_address.split('@')[1];
    let port          = (email.config.domain.indexOf('localhost') > 0 ? ":" + email.config.port : '');
    let webhookTarget = email.config.domain + port + email.config.endpoint;
    let webhookName   = email.config.bot_name + " Webhook";

    let completed = false;

    logger.log('=========================');
    logger.log('to skip this set setup to false in the config');

    sendingDomainSetup(function() {
      inboundDomainSetup(function() {
        webhookSetup();
      });
    });

    function sendingDomainSetup(cb) {
      // get the sending domain
      logger.log('Looking up sending domain...');
      sparkpost.sendingDomains.get(sendingDomain)
        // create sending domain
        .catch((err) => {
          logger.log('Domain does not exist\nCreating sending domain...');
          return sparkpost.sendingDomains.create({
            domain: sendingDomain
          });
        })
        // verify sending domain
        .then((data) => {
          logger.log('Domain exists\nVerifying sending domain...');
          return sparkpost.sendingDomains.verify(sendingDomain, {
            dkim_verify: true,
            spf_verify: true
          });
        })
        .then((data) => {
          if (data.results.ownership_verified) {
            logger.log('Domain verified');
            // go onto the next step
            if (_.isFunction(cb)) {
              return cb();
            }
          }

          throw new Error('Please verify your sending domain by following the instructions here:\nhttps://support.sparkpost.com/customer/portal/articles/1933360-verify-sending-domains');
        })
        .catch(throwUpError);
    }

    function inboundDomainSetup(cb) {
      // get the inbound domain
      logger.log('Looking up inbound domain...');
      sparkpost.inboundDomains.get(inboundDomain)
        // create the inbound domain
        .catch(() => {
          logger.log('Domain does not exist\nCreating inbound domain...');
          return sparkpost.inboundDomains.create({
            domain: inboundDomain
          });
        })
        //verify domain
        .then((data) => {
          logger.log('Domain exists\nVerifying inbound domain...');

          // get the mx records
          dns.resolveMx(inboundDomain, function(err, mxRecords) {
            let sparkpostMxRecords = ['rx1.sparkpostmail.com', 'rx2.sparkpostmail.com', 'rx3.sparkpostmail.com'];

            if (err)
              throw err;

            // remove the mx record from the sparkpostMxRecords array as it is found in the domains mx records
            _.each(mxRecords, (mxRecord) => {
              let index = _.indexOf(sparkpostMxRecords, mxRecord.exchange);

              if (index >= 0)
                sparkpostMxRecords.splice(index, 1);
            });

            if (sparkpostMxRecords.length > 0) {
              throw new Error('Inbound Domain failed to verify. Missing records: '+sparkpostMxRecords+'\n verify it by following the instructions here:\nhttps://support.sparkpost.com/customer/portal/articles/2039614-enabling-inbound-email-relaying-relay-webhooks');
            }
            else {
              logger.log('Domain verified');
              // go on to the next step
              if (_.isFunction(cb)) {
                cb();
              }
            }
          });
        })
        .catch(throwUpError);
    }

    function webhookSetup(cb) {
      // function to call at the end of the webhook setup
      function finished() {
        logger.log('Setup succeeded!');
        logger.log('=========================');

        // go on to the next step
        if (_.isFunction(cb)) {
          cb();
        }
      }

      logger.log('Looking up relay webhooks...');
      // get all webhooks
      sparkpost.relayWebhooks.list()
        // create the webhook
        .then((data) => {
          let webhookWithSameDomain = _.filter(data.results, { match: { domain: inboundDomain } });

          let webhookOptions = {
            name: webhookName,
            target: webhookTarget,
            match: {
              protocol: "SMTP",
              domain: inboundDomain
            }
          };

          if (_.isString(email.config.auth_token))
            webhookOptions.auth_token = email.config.auth_token;

          // if the webhook doesn't currently exist...
          if (webhookWithSameDomain.length === 0) {
            logger.log('Relay webhook does not exist with the given inbound domain\nCreating relay webhook...');

            return sparkpost.relayWebhooks.create(webhookOptions)
              .then((data) => {
                logger.log('Relay webhook created');

                finished();
              });
          }
          else {
            logger.log('Relay webhook exists');

            // update the relay webhook if the targets don't match
            if (webhookWithSameDomain[0].target !== webhookTarget || webhookWithSameDomain[0].auth_token !== webhookOptions.auth_token) {
              logger.warn('The existing webhook does not match the relay webhook options.');
              return sparkpost.relayWebhooks.update(webhookWithSameDomain[0].id, webhookOptions)
              .then((data) => {
                logger.log('Relay webhook updated');

                finished();
              });
            }

            // if it already exists the setup also succeeded
            finished();
          }
        })
        .catch(throwUpError);
    }
  };

  return email;
};

// removes the given address from the recipient list
function removeAddress(recipients, address) {
  _.remove(recipients, function(recipient) {
    let email = _.toLower(_.trim(recipient.address.email));

    return email === address;
  });
}

function throwUpError(err) {
  if (err.name === 'SparkPostError')
    console.log(JSON.stringify(err.errors, null, 2));

  setTimeout(function() { throw err; });
}

module.exports = Email;

<|endoftext|>


<commit_before>export default class DataService {
    constructor($q) {
        this.$q = $q;
    }

    uploadFile(file) {
        var deferred = this.$q.defer();
        var xhr = new XMLHttpRequest();

        xhr.upload.addEventListener('progress', e => {
            if (e.lengthComputable) {
                var percentage = Math.round((e.loaded * 100) / e.total);
                deferred.notify(percentage);
            }
        });

        xhr.upload.addEventListener('load', e => {
            deferred.resolve();
        });

        xhr.upload.addEventListener('error', e => {
            deferred.reject(e);
        });

        xhr.upload.addEventListener('abort', e => {
            deferred.reject('upload aborted');
        });

        var formData = new FormData();
        formData.append('myFile', file.originalFile);

        xhr.open('POST', '/upload', true);
        xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
        xhr.send(formData);

        return deferred.promise;
    }
}

DataService.$inject = ['$q'];<commit_msg>implemented return value handling for upload file service<commit_after>export default class DataService {
    constructor($q) {
        this.$q = $q;
    }

    uploadFile(file) {
        var deferred = this.$q.defer();
        var xhr = new XMLHttpRequest();

        xhr.upload.addEventListener('progress', e => {
            if (e.lengthComputable) {
                var percentage = Math.round((e.loaded * 100) / e.total);
                deferred.notify(percentage);
            }
        });

        xhr.upload.addEventListener('load', e => { // upload finished
            // do nothing
        });

        xhr.addEventListener('readystatechange', () => {
            if (xhr.readyState === 4) { // download response finished
                var data = xhr.responseText;
                deferred.resolve(JSON.parse(data));
            }
        });

        xhr.upload.addEventListener('error', e => {
            deferred.reject(e);
        });

        xhr.upload.addEventListener('abort', e => {
            deferred.reject('upload aborted');
        });

        var formData = new FormData();
        formData.append('files', file.originalFile);

        xhr.open('POST', '/upload', true);
        xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
        xhr.send(formData);

        return deferred.promise;
    }
}

DataService.$inject = ['$q'];<|endoftext|>


<commit_before>/*
 * nwjs.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
define(['./base', '../API'], function(fileSystem, API){
  var _0744 = parseInt('0744', 8);

  return function(){
    if (!! window.nodeRequire) {
      var nw = nodeRequire('nw.gui');
      var fs = nodeRequire('fs');
      var request = nodeRequire('request');
      var mkdirp = nodeRequire('mkdirp');
      var checksum = nodeRequire('checksum');

      fileSystem.dataPath = nw.App.dataPath + '/' + fileSystem.dataSubFolder + '/';
    }

    fileSystem.writeFile = function(filename, data, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      // get directory from path
      var directory = fileSystem.getFolder(filename);

      // first ensure the directory that should be written to exists
      fileSystem.mkdir(directory, _0744, function(){
        // write data to the file
        fs.writeFile(filename, data, function(err){
          // process result
          if (err) {
            if (errorCallback) {
              errorCallback(err);
            }
          } else {
            if (callback) {
              callback();
            }
          }
        });
      });
    };

    fileSystem.readFile = function(filename, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      // check if file exists first
      fs.exists(filename, function(exists){
        if (exists) {
          // if it exists: read file
          fs.readFile(filename, function(err, data){
            if (err) {
              errorCallback(err);
            } else {
              if (callback) {
                callback(data.toString());
              }
            }
          });
        } else {
          if (errorCallback) {
            errorCallback();
          }
        }
      });
    };

    fileSystem.readFileBinary = function(filename, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      // check if file exists first
      fs.exists(filename, function(exists){
        if (exists) {
          // if it exists: read file
          fs.readFile(filename, function(err, data){
            if (err) {
              errorCallback(err);
            } else {
              if (callback) {
                callback(data);
              }
            }
          });
        } else {
          if (errorCallback) {
            errorCallback();
          }
        }
      });
    };

    fileSystem.pipe = function(options){
      // correct file path
      options.local = fileSystem.correctLocalFilePath(options.local);

      // get directory out of local path
      var directory = fileSystem.getFolder(options.local);

      // otherwise ensure local directory exists
      fileSystem.mkdir(directory, _0744, function(){
        // create a temporary file stream (move file to real path when finished correctly)
        var file = fs.createWriteStream(options.local + '_tmp');

        // get request object
        var r = fileSystem.request({url: options.remote, method: options.method, data: options.data});

        // store total and loaded bytes and call progress callback on new data
        var totalBytes = 0;
        var loadedBytes = 0;
        var progress = 0;

        // track if request status is 200
        var fileWasFound = false;
        var didThrowError = false;

        // store basic request response
        r.on('response', function(response){
          totalBytes = response.headers['content-length'];
          if (response.statusCode === 200) {
            fileWasFound = true;
          }
        });

        // if it has a progress handler
        if (options.progress) {
          // call progress on new data
          r.on('data', function(chunk){
            loadedBytes += chunk.length;
            progress = loadedBytes / totalBytes;
            options.progress(progress);
          });
        }

        r.on('error', function(err){
          if (options.error && ! didThrowError) {
            didThrowError = true;
            options.error(err);
          }
        });

        // when downloading has finished
        r.on('end', function(){
          // if file was found on remote
          if (fileWasFound) {
            // rename temporary file accordingly
            fs.rename(options.local + '_tmp', options.local, function(err){
              // if there was an error while renaming
              if (err) {
                // call error callback
                if (options.error && ! didThrowError) {
                  didThrowError = true;
                  options.error(err);
                }
              } else {
                // otherwise: finally call success
                if (options.success) {
                  options.success(options.local);
                }
              }
            });
          } else {
            // if file was not found on remote: call error callback
            if (options.error && ! didThrowError) {
              didThrowError = true;
              options.error();
            }
          }
        });

        // if there was an error while writing the file
        file.on('error', function(err){
          // call error callback
          if (options.error && ! didThrowError) {
            didThrowError = true;
            options.error(err);
          }
        });

        // pipe file to request
        r.pipe(file);
      });
    };

    fileSystem.request = function(options, callback, errorCallback){
      if (typeof options === 'string') {
        options = {url: options};
      }

      if (callback && ! options.success) options.success = callback;
      if (errorCallback && ! options.error) options.error = errorCallback;

      var requestOptions = {url: options.url};

      if (options.url.indexOf(API.endpoint) >= 0) {
        var token = API.get('token');
        if (token && token.length > 0) {
          requestOptions.auth = {bearer: token};
        }
      }

      // pass in request method (GET, POST, PUT, DELETE)
      if (options.method) {
        requestOptions.method = options.method;
      }

      // pass in request data if set
      if (options.data) {
        requestOptions.form = options.data;
      }

      var success = null, r;

      if (options.success) {
        success = function(err, httpResponse, body){
          if (fileWasFound) {
            if (options.success) {
              options.success(body);
            }
          }
        };

        // request remote url
        r = request(requestOptions, success);
      } else {
        r = request(requestOptions);
      }

      // track if request status is 200
      var fileWasFound = false;

      // store basic request response
      r.on('response', function(response){
        if (response.statusCode === 200) {
          fileWasFound = true;
        }
      });

      if (options.error) {
        r.on('error', options.error);
      }

      return r;
    };

    fileSystem.readdir = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      // check if directory exists first
      fs.exists(path, function(exists){
        // return results. if any error is encountered simply return an empty array
        if (exists) {
          fs.readdir(path, function(err, files){
            if (err) {
              if (callback) {
                callback([]);
              }
            } else {
              if (callback) {
                callback(files);
              }
            }
          });
        } else {
          if (callback) {
            callback([]);
          }
        }
      });
    };

    fileSystem.exists = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      // check if exists
      fs.exists(path, function(exists){
        // return result
        if (callback) {
          callback(exists);
        }
      });
    };

    fileSystem.rmdir = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      // if directory exists
      fs.exists(path, function(exists){
        if (exists) {
          // read it
          fs.readdir(path, function(err, files){
            if (err) {
              // for errors just stop
              if (callback) {
                // Pass the error on to callback
                callback(err, []);
              }

              return;
            }

            // track how many files and directories got removed
            var wait = files.length;
            var count = 0;
            var folderDone = function(err){
              count++;

              // If we cleaned out all the files, continue
              if (count >= wait || err) {
                fs.rmdir(path, callback);
              }
            };

            // Empty directory to bail early
            if (! wait) {
              folderDone();
              return;
            }

            // Remove one or more trailing slash to keep from doubling up
            path = path.replace(/\/+$/, '');

            // loop through files
            files.forEach(function(file){
              // correct current path
              var currentPath = path + '/' + file;

              // get file status
              fs.lstat(currentPath, function(err, stats){
                if (err) {
                  if (callback) {
                    callback(err, []);
                  }

                  return;
                }

                // if it is a directory
                if (stats.isDirectory()) {
                  // remove it recursively
                  fileSystem.rmdir(currentPath, folderDone);
                } else {
                  // otherwise just unlink the file
                  fs.unlink(currentPath, folderDone);
                }
              });
            });
          });
        } else {
          // if folder does not exists
          if (callback) {
            // just call the callback
            callback();
          }
        }
      });
    };

    fileSystem.mkdir = function(path, mode, callback){
      if (path + '/' === fileSystem.dataPath) {
        if (callback) {
          callback();
        }

        return;
      }

      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      mkdirp(path, mode, callback);
    };

    fileSystem.unlink = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      fs.exists(path, function(exists){
        if (exists) {
          fs.lstat(path, function(err, stats){
            if (err) {
              if (callback) callback();
              return;
            }

            // if it is a directory
            if (stats.isDirectory()) {
              // remove it recursively
              fileSystem.rmdir(path, callback);
            } else {
              // otherwise just unlink the file
              fs.unlink(path, callback);
            }
          });
        } else {
          if (callback) callback();
        }
      });
    };

    fileSystem.rename = function(path, newPath, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);
      newPath = fileSystem.correctLocalFilePath(newPath);

      fs.exists(path, function(exists){
        if (exists) {
          fs.rename(path, newPath, callback);
        } else {
          if (callback) callback();
        }
      });
    };

    fileSystem.checksum = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      checksum.file(path, {algorithm: 'md5'}, function(err, sum) {
        if (err) {
          if (callback) {
            callback('');
          }
        } else {
          if (callback) {
            callback(sum);
          }
        }
      });
    };
  };
});
<commit_msg>fix: progress NaN<commit_after>/*
 * nwjs.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
define(['./base', '../API'], function(fileSystem, API){
  var _0744 = parseInt('0744', 8);

  return function(){
    if (!! window.nodeRequire) {
      var nw = nodeRequire('nw.gui');
      var fs = nodeRequire('fs');
      var request = nodeRequire('request');
      var mkdirp = nodeRequire('mkdirp');
      var checksum = nodeRequire('checksum');

      fileSystem.dataPath = nw.App.dataPath + '/' + fileSystem.dataSubFolder + '/';
    }

    fileSystem.writeFile = function(filename, data, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      // get directory from path
      var directory = fileSystem.getFolder(filename);

      // first ensure the directory that should be written to exists
      fileSystem.mkdir(directory, _0744, function(){
        // write data to the file
        fs.writeFile(filename, data, function(err){
          // process result
          if (err) {
            if (errorCallback) {
              errorCallback(err);
            }
          } else {
            if (callback) {
              callback();
            }
          }
        });
      });
    };

    fileSystem.readFile = function(filename, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      // check if file exists first
      fs.exists(filename, function(exists){
        if (exists) {
          // if it exists: read file
          fs.readFile(filename, function(err, data){
            if (err) {
              errorCallback(err);
            } else {
              if (callback) {
                callback(data.toString());
              }
            }
          });
        } else {
          if (errorCallback) {
            errorCallback();
          }
        }
      });
    };

    fileSystem.readFileBinary = function(filename, callback, errorCallback){
      // correct file path
      filename = fileSystem.correctLocalFilePath(filename);

      // check if file exists first
      fs.exists(filename, function(exists){
        if (exists) {
          // if it exists: read file
          fs.readFile(filename, function(err, data){
            if (err) {
              errorCallback(err);
            } else {
              if (callback) {
                callback(data);
              }
            }
          });
        } else {
          if (errorCallback) {
            errorCallback();
          }
        }
      });
    };

    fileSystem.pipe = function(options){
      // correct file path
      options.local = fileSystem.correctLocalFilePath(options.local);

      // get directory out of local path
      var directory = fileSystem.getFolder(options.local);

      // otherwise ensure local directory exists
      fileSystem.mkdir(directory, _0744, function(){
        // create a temporary file stream (move file to real path when finished correctly)
        var file = fs.createWriteStream(options.local + '_tmp');

        // get request object
        var r = fileSystem.request({url: options.remote, method: options.method, data: options.data});

        // store total and loaded bytes and call progress callback on new data
        var totalBytes = 0;
        var loadedBytes = 0;
        var progress = 0;

        // track if request status is 200
        var fileWasFound = false;
        var didThrowError = false;

        // store basic request response
        r.on('response', function(response){
          totalBytes = response.headers['content-length'];
          if (response.statusCode === 200) {
            fileWasFound = true;
          }
        });

        // if it has a progress handler
        if (options.progress) {
          // call progress on new data
          r.on('data', function(chunk){
            loadedBytes += chunk.length;
            progress = loadedBytes / totalBytes;
            if (isNaN(progress)) progress = 0;
            options.progress(progress);
          });
        }

        r.on('error', function(err){
          if (options.error && ! didThrowError) {
            didThrowError = true;
            options.error(err);
          }
        });

        // when downloading has finished
        r.on('end', function(){
          // if file was found on remote
          if (fileWasFound) {
            // rename temporary file accordingly
            fs.rename(options.local + '_tmp', options.local, function(err){
              // if there was an error while renaming
              if (err) {
                // call error callback
                if (options.error && ! didThrowError) {
                  didThrowError = true;
                  options.error(err);
                }
              } else {
                // otherwise: finally call success
                if (options.success) {
                  options.success(options.local);
                }
              }
            });
          } else {
            // if file was not found on remote: call error callback
            if (options.error && ! didThrowError) {
              didThrowError = true;
              options.error();
            }
          }
        });

        // if there was an error while writing the file
        file.on('error', function(err){
          // call error callback
          if (options.error && ! didThrowError) {
            didThrowError = true;
            options.error(err);
          }
        });

        // pipe file to request
        r.pipe(file);
      });
    };

    fileSystem.request = function(options, callback, errorCallback){
      if (typeof options === 'string') {
        options = {url: options};
      }

      if (callback && ! options.success) options.success = callback;
      if (errorCallback && ! options.error) options.error = errorCallback;

      var requestOptions = {url: options.url};

      if (options.url.indexOf(API.endpoint) >= 0) {
        var token = API.get('token');
        if (token && token.length > 0) {
          requestOptions.auth = {bearer: token};
        }
      }

      // pass in request method (GET, POST, PUT, DELETE)
      if (options.method) {
        requestOptions.method = options.method;
      }

      // pass in request data if set
      if (options.data) {
        requestOptions.form = options.data;
      }

      var success = null, r;

      if (options.success) {
        success = function(err, httpResponse, body){
          if (fileWasFound) {
            if (options.success) {
              options.success(body);
            }
          }
        };

        // request remote url
        r = request(requestOptions, success);
      } else {
        r = request(requestOptions);
      }

      // track if request status is 200
      var fileWasFound = false;

      // store basic request response
      r.on('response', function(response){
        if (response.statusCode === 200) {
          fileWasFound = true;
        }
      });

      if (options.error) {
        r.on('error', options.error);
      }

      return r;
    };

    fileSystem.readdir = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      // check if directory exists first
      fs.exists(path, function(exists){
        // return results. if any error is encountered simply return an empty array
        if (exists) {
          fs.readdir(path, function(err, files){
            if (err) {
              if (callback) {
                callback([]);
              }
            } else {
              if (callback) {
                callback(files);
              }
            }
          });
        } else {
          if (callback) {
            callback([]);
          }
        }
      });
    };

    fileSystem.exists = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      // check if exists
      fs.exists(path, function(exists){
        // return result
        if (callback) {
          callback(exists);
        }
      });
    };

    fileSystem.rmdir = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      // if directory exists
      fs.exists(path, function(exists){
        if (exists) {
          // read it
          fs.readdir(path, function(err, files){
            if (err) {
              // for errors just stop
              if (callback) {
                // Pass the error on to callback
                callback(err, []);
              }

              return;
            }

            // track how many files and directories got removed
            var wait = files.length;
            var count = 0;
            var folderDone = function(err){
              count++;

              // If we cleaned out all the files, continue
              if (count >= wait || err) {
                fs.rmdir(path, callback);
              }
            };

            // Empty directory to bail early
            if (! wait) {
              folderDone();
              return;
            }

            // Remove one or more trailing slash to keep from doubling up
            path = path.replace(/\/+$/, '');

            // loop through files
            files.forEach(function(file){
              // correct current path
              var currentPath = path + '/' + file;

              // get file status
              fs.lstat(currentPath, function(err, stats){
                if (err) {
                  if (callback) {
                    callback(err, []);
                  }

                  return;
                }

                // if it is a directory
                if (stats.isDirectory()) {
                  // remove it recursively
                  fileSystem.rmdir(currentPath, folderDone);
                } else {
                  // otherwise just unlink the file
                  fs.unlink(currentPath, folderDone);
                }
              });
            });
          });
        } else {
          // if folder does not exists
          if (callback) {
            // just call the callback
            callback();
          }
        }
      });
    };

    fileSystem.mkdir = function(path, mode, callback){
      if (path + '/' === fileSystem.dataPath) {
        if (callback) {
          callback();
        }

        return;
      }

      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      mkdirp(path, mode, callback);
    };

    fileSystem.unlink = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      fs.exists(path, function(exists){
        if (exists) {
          fs.lstat(path, function(err, stats){
            if (err) {
              if (callback) callback();
              return;
            }

            // if it is a directory
            if (stats.isDirectory()) {
              // remove it recursively
              fileSystem.rmdir(path, callback);
            } else {
              // otherwise just unlink the file
              fs.unlink(path, callback);
            }
          });
        } else {
          if (callback) callback();
        }
      });
    };

    fileSystem.rename = function(path, newPath, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);
      newPath = fileSystem.correctLocalFilePath(newPath);

      fs.exists(path, function(exists){
        if (exists) {
          fs.rename(path, newPath, callback);
        } else {
          if (callback) callback();
        }
      });
    };

    fileSystem.checksum = function(path, callback){
      // correct file path
      path = fileSystem.correctLocalFilePath(path);

      checksum.file(path, {algorithm: 'md5'}, function(err, sum) {
        if (err) {
          if (callback) {
            callback('');
          }
        } else {
          if (callback) {
            callback(sum);
          }
        }
      });
    };
  };
});
<|endoftext|>


<commit_before>"use strict";

import os from 'os';
import EnumItem from './enumItem';
import { isString } from './isType';
import { indexOf } from './indexOf';

const endianness = os.endianness();

/**
 * Represents an Enum with enum items.
 * @param {Array || Object}  map     This are the enum items.
 * @param {String || Object} options This are options. [optional]
 */
export default class Enum {
  constructor(map, options) {
    /* implement the "ref type interface", so that Enum types can
     * be used in `node-ffi` function declarations and invokations.
     * In C, these Enums act as `uint32_t` types.
     *
     * https://github.com/TooTallNate/ref#the-type-interface
     */
    this.size = 4;
    this.indirection = 1;

    if (options && isString(options)) {
      options = { name: options };
    }

    this._options = options || {};
    this._options.separator = this._options.separator || " | ";
    this._options.endianness = this._options.endianness || endianness;
    this._options.ignoreCase = this._options.ignoreCase || false;

    this.enums = [];

    if (map.length) {
      var array = map;
      map = {};

      for (var i = 0; i < array.length; i++) {
        map[array[i]] = Math.pow(2, i);
      }
    }

    for (var member in map) {
      if (this._options.name && ['name', '_options', 'get', 'getKey', 'getValue', 'enums', 'isFlaggable'].includes(member)) {
        throw new Error("Enum key \"" + member + "\" is a reserved word!");
      }
      this[member] = new EnumItem(member, map[member], { ignoreCase: this._options.ignoreCase });
      this.enums.push(this[member]);
    }

    if (this._options.ignoreCase) {
      this.getLowerCaseEnums = function () {
        var res = {};
        for (var i = 0, len = this.enums.length; i < len; i++) {
          res[this.enums[i].key.toLowerCase()] = this.enums[i];
        }
        return res;
      };
    }

    if (this._options.name) {
      this.name = this._options.name;
    }

    const isFlaggable = () => {
      for (var i = 0, len = this.enums.length; i < len; i++) {
        var e = this.enums[i];

        if (!(e.value !== 0 && !(e.value & e.value - 1))) {
          return false;
        }
      }
      return true;
    }

    this.isFlaggable = isFlaggable();
    this.freezeEnums(); //this will make instances of Enum non-extensible
  }

  /**
   * Returns the appropriate EnumItem key.
   * @param  {EnumItem || String || Number} key The object to get with.
   * @return {String}                           The get result.
   */
  getKey(value) {
    var item = this.get(value);
    if (item) {
      return item.key;
    }
  }

  /**
   * Returns the appropriate EnumItem value.
   * @param  {EnumItem || String || Number} key The object to get with.
   * @return {Number}                           The get result.
   */
  getValue(key) {
    var item = this.get(key);
    if (item) {
      return item.value;
    }
  }

  /**
   * Returns the appropriate EnumItem.
   * @param  {EnumItem || String || Number} key The object to get with.
   * @return {EnumItem}                         The get result.
   */
  get(key, offset) {
    if (key === null || key === undefined) {
      return;
    } // Buffer instance support, part of the ref Type interface
    if (Buffer.isBuffer(key)) {
      key = key["readUInt32" + this._options.endianness](offset || 0);
    }

    if (EnumItem.isEnumItem(key)) {
      var foundIndex = indexOf.call(this.enums, key);
      if (foundIndex >= 0) {
        return key;
      }
      if (!this.isFlaggable || this.isFlaggable && key.key.indexOf(this._options.separator) < 0) {
        return;
      }
      return this.get(key.key);
    } else if (isString(key)) {

      var enums = this;
      if (this._options.ignoreCase) {
        enums = this.getLowerCaseEnums();
        key = key.toLowerCase();
      }

      if (key.indexOf(this._options.separator) > 0) {
        var parts = key.split(this._options.separator);

        var value = 0;
        for (var i = 0; i < parts.length; i++) {
          var part = parts[i];

          value |= enums[part].value;
        }

        return new EnumItem(key, value);
      } else {
        return enums[key];
      }
    } else {
      for (var m in this) {
        if (this.hasOwnProperty(m)) {
          if (this[m].value === key) {
            return this[m];
          }
        }
      }

      var result = null;

      if (this.isFlaggable) {
        for (var n in this) {
          if (this.hasOwnProperty(n)) {
            if ((key & this[n].value) !== 0) {
              if (result) {
                result += this._options.separator;
              } else {
                result = "";
              }
              result += n;
            }
          }
        }
      }

      return this.get(result || null);
    }
  }

  /**
   * Sets the Enum "value" onto the give `buffer` at the specified `offset`.
   * Part of the ref "Type interface".
   *
   * @param  {Buffer} buffer The Buffer instance to write to.
   * @param  {Number} offset The offset in the buffer to write to. Default 0.
   * @param  {EnumItem || String || Number} value The EnumItem to write.
   */
  set(buffer, offset, value) {
    var item = this.get(value);
    if (item) {
      return buffer["writeUInt32" + this._options.endianness](item.value, offset || 0);
    }
  }

  /**
   * Define freezeEnums() as a property of the prototype.
   * make enumerable items nonconfigurable and deep freeze the properties. Throw Error on property setter.
   */
  freezeEnums() {
    function envSupportsFreezing() {
      return (
        Object.isFrozen && Object.isSealed &&
        Object.getOwnPropertyNames && Object.getOwnPropertyDescriptor &&
        Object.defineProperties && Object.__defineGetter__ && Object.__defineSetter__
      );
    }

    function freezer(o) {
      var props = Object.getOwnPropertyNames(o);
      props.forEach(function (p) {
        if (!Object.getOwnPropertyDescriptor(o, p).configurable) {
          return;
        }

        Object.defineProperties(o, p, { writable: false, configurable: false });
      });
      return o;
    }

    function getPropertyValue(value) {
      return value;
    }

    function deepFreezeEnums(o) {
      if (typeof o !== "object" || o === null || Object.isFrozen(o) || Object.isSealed(o)) {
        return;
      }
      for (var key in o) {
        if (o.hasOwnProperty(key)) {
          o.__defineGetter__(key, getPropertyValue.bind(null, o[key]));
          o.__defineSetter__(key, function throwPropertySetError(value) {
            throw TypeError("Cannot redefine property; Enum Type is not extensible.");
          });
          deepFreezeEnums(o[key]);
        }
      }
      if (Object.freeze) {
        Object.freeze(o);
      } else {
        freezer(o);
      }
    }

    if (envSupportsFreezing() ) {
      deepFreezeEnums(this);
    }

    return this;
  }

  /**
   * Registers the Enum Type globally in node.js.
   * @param  {String} key Global variable. [optional]
   */
  static register(key='Enum') {
    if (!global[key]) {
      global[key] = Enum;
    }
  }
};

<commit_msg>use indexOf to check for reserved name<commit_after>"use strict";

import os from 'os';
import EnumItem from './enumItem';
import { isString } from './isType';
import { indexOf } from './indexOf';

const endianness = os.endianness();

var reservedKeys = ['_options', 'get', 'getKey', 'getValue', 'enums', 'isFlaggable'];

/**
 * Represents an Enum with enum items.
 * @param {Array || Object}  map     This are the enum items.
 * @param {String || Object} options This are options. [optional]
 */
export default class Enum {
  constructor(map, options) {
    /* implement the "ref type interface", so that Enum types can
     * be used in `node-ffi` function declarations and invokations.
     * In C, these Enums act as `uint32_t` types.
     *
     * https://github.com/TooTallNate/ref#the-type-interface
     */
    this.size = 4;
    this.indirection = 1;

    if (options && isString(options)) {
      options = { name: options };
    }

    this._options = options || {};
    this._options.separator = this._options.separator || " | ";
    this._options.endianness = this._options.endianness || endianness;
    this._options.ignoreCase = this._options.ignoreCase || false;

    this.enums = [];

    if (map.length) {
      var array = map;
      map = {};

      for (var i = 0; i < array.length; i++) {
        map[array[i]] = Math.pow(2, i);
      }
    }

    for (var member in map) {
      if (indexOf.call(reservedKeys, member) >= 0) {
        throw new Error("Enum key \"" + member + "\" is a reserved word!");
      }
      this[member] = new EnumItem(member, map[member], { ignoreCase: this._options.ignoreCase });
      this.enums.push(this[member]);
    }

    if (this._options.ignoreCase) {
      this.getLowerCaseEnums = function () {
        var res = {};
        for (var i = 0, len = this.enums.length; i < len; i++) {
          res[this.enums[i].key.toLowerCase()] = this.enums[i];
        }
        return res;
      };
    }

    if (this._options.name) {
      this.name = this._options.name;
    }

    const isFlaggable = () => {
      for (var i = 0, len = this.enums.length; i < len; i++) {
        var e = this.enums[i];

        if (!(e.value !== 0 && !(e.value & e.value - 1))) {
          return false;
        }
      }
      return true;
    }

    this.isFlaggable = isFlaggable();
    this.freezeEnums(); //this will make instances of Enum non-extensible
  }

  /**
   * Returns the appropriate EnumItem key.
   * @param  {EnumItem || String || Number} key The object to get with.
   * @return {String}                           The get result.
   */
  getKey(value) {
    var item = this.get(value);
    if (item) {
      return item.key;
    }
  }

  /**
   * Returns the appropriate EnumItem value.
   * @param  {EnumItem || String || Number} key The object to get with.
   * @return {Number}                           The get result.
   */
  getValue(key) {
    var item = this.get(key);
    if (item) {
      return item.value;
    }
  }

  /**
   * Returns the appropriate EnumItem.
   * @param  {EnumItem || String || Number} key The object to get with.
   * @return {EnumItem}                         The get result.
   */
  get(key, offset) {
    if (key === null || key === undefined) {
      return;
    } // Buffer instance support, part of the ref Type interface
    if (Buffer.isBuffer(key)) {
      key = key["readUInt32" + this._options.endianness](offset || 0);
    }

    if (EnumItem.isEnumItem(key)) {
      var foundIndex = indexOf.call(this.enums, key);
      if (foundIndex >= 0) {
        return key;
      }
      if (!this.isFlaggable || this.isFlaggable && key.key.indexOf(this._options.separator) < 0) {
        return;
      }
      return this.get(key.key);
    } else if (isString(key)) {

      var enums = this;
      if (this._options.ignoreCase) {
        enums = this.getLowerCaseEnums();
        key = key.toLowerCase();
      }

      if (key.indexOf(this._options.separator) > 0) {
        var parts = key.split(this._options.separator);

        var value = 0;
        for (var i = 0; i < parts.length; i++) {
          var part = parts[i];

          value |= enums[part].value;
        }

        return new EnumItem(key, value);
      } else {
        return enums[key];
      }
    } else {
      for (var m in this) {
        if (this.hasOwnProperty(m)) {
          if (this[m].value === key) {
            return this[m];
          }
        }
      }

      var result = null;

      if (this.isFlaggable) {
        for (var n in this) {
          if (this.hasOwnProperty(n)) {
            if ((key & this[n].value) !== 0) {
              if (result) {
                result += this._options.separator;
              } else {
                result = "";
              }
              result += n;
            }
          }
        }
      }

      return this.get(result || null);
    }
  }

  /**
   * Sets the Enum "value" onto the give `buffer` at the specified `offset`.
   * Part of the ref "Type interface".
   *
   * @param  {Buffer} buffer The Buffer instance to write to.
   * @param  {Number} offset The offset in the buffer to write to. Default 0.
   * @param  {EnumItem || String || Number} value The EnumItem to write.
   */
  set(buffer, offset, value) {
    var item = this.get(value);
    if (item) {
      return buffer["writeUInt32" + this._options.endianness](item.value, offset || 0);
    }
  }

  /**
   * Define freezeEnums() as a property of the prototype.
   * make enumerable items nonconfigurable and deep freeze the properties. Throw Error on property setter.
   */
  freezeEnums() {
    function envSupportsFreezing() {
      return (
        Object.isFrozen && Object.isSealed &&
        Object.getOwnPropertyNames && Object.getOwnPropertyDescriptor &&
        Object.defineProperties && Object.__defineGetter__ && Object.__defineSetter__
      );
    }

    function freezer(o) {
      var props = Object.getOwnPropertyNames(o);
      props.forEach(function (p) {
        if (!Object.getOwnPropertyDescriptor(o, p).configurable) {
          return;
        }

        Object.defineProperties(o, p, { writable: false, configurable: false });
      });
      return o;
    }

    function getPropertyValue(value) {
      return value;
    }

    function deepFreezeEnums(o) {
      if (typeof o !== "object" || o === null || Object.isFrozen(o) || Object.isSealed(o)) {
        return;
      }
      for (var key in o) {
        if (o.hasOwnProperty(key)) {
          o.__defineGetter__(key, getPropertyValue.bind(null, o[key]));
          o.__defineSetter__(key, function throwPropertySetError(value) {
            throw TypeError("Cannot redefine property; Enum Type is not extensible.");
          });
          deepFreezeEnums(o[key]);
        }
      }
      if (Object.freeze) {
        Object.freeze(o);
      } else {
        freezer(o);
      }
    }

    if (envSupportsFreezing() ) {
      deepFreezeEnums(this);
    }

    return this;
  }

  /**
   * Registers the Enum Type globally in node.js.
   * @param  {String} key Global variable. [optional]
   */
  static register(key='Enum') {
    if (!global[key]) {
      global[key] = Enum;
    }
  }
};

<|endoftext|>