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

var util = require('util')
var debug = require('debug')('array-index')

/**
 * JavaScript Array "length" is bound to an unsigned 32-bit int.
 * See: http://stackoverflow.com/a/6155063/376773
 */

var MAX_LENGTH = Math.pow(2, 32)

/**
 * Module exports.
 */

module.exports = ArrayIndex

/**
 * Subclass this.
 */

function ArrayIndex (length) {
  Object.defineProperty(this, 'length', {
    get: getLength,
    set: setLength,
    enumerable: false,
    configurable: true
  })

  Object.defineProperty(this, '__length', {
    value: 0,
    writable: true,
    enumerable: false,
    configurable: true
  })

  if (arguments.length > 0) {
    this.length = length
  }
}

/**
 * You overwrite the "__get__" function in your subclass.
 */

ArrayIndex.prototype.__get__ = function () {
  throw new Error('you must implement the __get__ function')
}

/**
 * You overwrite the "__set__" function in your subclass.
 */

ArrayIndex.prototype.__set__ = function () {
  throw new Error('you must implement the __set__ function')
}

/**
 * Converts this array class into a real JavaScript Array. Note that this
 * is a "flattened" array and your defined getters and setters won't be invoked
 * when you interact with the returned Array. This function will call the
 * getter on every array index of the object.
 *
 * @return {Array} The flattened array
 * @api public
 */

ArrayIndex.prototype.toArray = function toArray () {
  var i = 0, l = this.length, array = new Array(l)
  for (; i < l; i++) {
    array[i] = this[i]
  }
  return array
}

/**
 * Basic support for `JSON.stringify()`.
 */

ArrayIndex.prototype.toJSON = function toJSON () {
  return this.toArray()
}

/**
 * toString() override. Use Array.prototype.toString().
 */

ArrayIndex.prototype.toString = function toString () {
  var a = this.toArray()
  return a.toString.apply(a, arguments)
}

/**
 * inspect() override. For the REPL.
 */

ArrayIndex.prototype.inspect = function inspect () {
  var a = this.toArray()
  Object.keys(this).forEach(function (k) {
    a[k] = this[k]
  }, this)
  return util.inspect(a)
}

/**
 * Getter for the "length" property.
 * Returns the value of the "__length" property.
 */

function getLength () {
  debug('getting "length": %o', this.__length)
  return this.__length
}

/**
 * Setter for the "length" property.
 * Calls "ensureLength()", then sets the "__length" property.
 */

function setLength (v) {
  debug('setting "length": %o', v)
  return this.__length = ensureLength(this, v)
}

/**
 * Ensures that getters/setters from 0 up to "_length" have been defined
 * on `Object.getPrototypeOf(this)`.
 *
 * @api private
 */

function ensureLength (self, _length) {
  var length
  if (_length > MAX_LENGTH) {
    length = MAX_LENGTH
  } else {
    length = _length | 0
  }
  var proto = Object.getPrototypeOf(self)
  var cur = proto.__length__ | 0
  var num = length - cur
  if (num > 0) {
    var desc = {}
    debug('creating a descriptor object with %o entries', num)
    for (var i = cur; i < length; i++) {
      desc[i] = setup(i)
    }
    debug('done creating descriptor object')
    debug('calling `Object.defineProperties()` with %o entries', num)
    Object.defineProperties(proto, desc)
    debug('finished `Object.defineProperties()`')
    proto.__length__ = length
  }
  return length
}

/**
 * Returns a property descriptor for the given "index", with "get" and "set"
 * functions created within the closure.
 *
 * @api private
 */

function setup (index) {
  function get () {
    return this.__get__(index)
  }
  function set (v) {
    return this.__set__(index, v)
  }
  return {
      enumerable: true
    , configurable: true
    , get: get
    , set: set
  }
}
<commit_msg>remove extraneous debug() call<commit_after>
/**
 * Module dependencies.
 */

var util = require('util')
var debug = require('debug')('array-index')

/**
 * JavaScript Array "length" is bound to an unsigned 32-bit int.
 * See: http://stackoverflow.com/a/6155063/376773
 */

var MAX_LENGTH = Math.pow(2, 32)

/**
 * Module exports.
 */

module.exports = ArrayIndex

/**
 * Subclass this.
 */

function ArrayIndex (length) {
  Object.defineProperty(this, 'length', {
    get: getLength,
    set: setLength,
    enumerable: false,
    configurable: true
  })

  Object.defineProperty(this, '__length', {
    value: 0,
    writable: true,
    enumerable: false,
    configurable: true
  })

  if (arguments.length > 0) {
    this.length = length
  }
}

/**
 * You overwrite the "__get__" function in your subclass.
 */

ArrayIndex.prototype.__get__ = function () {
  throw new Error('you must implement the __get__ function')
}

/**
 * You overwrite the "__set__" function in your subclass.
 */

ArrayIndex.prototype.__set__ = function () {
  throw new Error('you must implement the __set__ function')
}

/**
 * Converts this array class into a real JavaScript Array. Note that this
 * is a "flattened" array and your defined getters and setters won't be invoked
 * when you interact with the returned Array. This function will call the
 * getter on every array index of the object.
 *
 * @return {Array} The flattened array
 * @api public
 */

ArrayIndex.prototype.toArray = function toArray () {
  var i = 0, l = this.length, array = new Array(l)
  for (; i < l; i++) {
    array[i] = this[i]
  }
  return array
}

/**
 * Basic support for `JSON.stringify()`.
 */

ArrayIndex.prototype.toJSON = function toJSON () {
  return this.toArray()
}

/**
 * toString() override. Use Array.prototype.toString().
 */

ArrayIndex.prototype.toString = function toString () {
  var a = this.toArray()
  return a.toString.apply(a, arguments)
}

/**
 * inspect() override. For the REPL.
 */

ArrayIndex.prototype.inspect = function inspect () {
  var a = this.toArray()
  Object.keys(this).forEach(function (k) {
    a[k] = this[k]
  }, this)
  return util.inspect(a)
}

/**
 * Getter for the "length" property.
 * Returns the value of the "__length" property.
 */

function getLength () {
  debug('getting "length": %o', this.__length)
  return this.__length
}

/**
 * Setter for the "length" property.
 * Calls "ensureLength()", then sets the "__length" property.
 */

function setLength (v) {
  debug('setting "length": %o', v)
  return this.__length = ensureLength(this, v)
}

/**
 * Ensures that getters/setters from 0 up to "_length" have been defined
 * on `Object.getPrototypeOf(this)`.
 *
 * @api private
 */

function ensureLength (self, _length) {
  var length
  if (_length > MAX_LENGTH) {
    length = MAX_LENGTH
  } else {
    length = _length | 0
  }
  var proto = Object.getPrototypeOf(self)
  var cur = proto.__length__ | 0
  var num = length - cur
  if (num > 0) {
    var desc = {}
    debug('creating a descriptor object with %o entries', num)
    for (var i = cur; i < length; i++) {
      desc[i] = setup(i)
    }
    debug('calling `Object.defineProperties()` with %o entries', num)
    Object.defineProperties(proto, desc)
    debug('finished `Object.defineProperties()`')
    proto.__length__ = length
  }
  return length
}

/**
 * Returns a property descriptor for the given "index", with "get" and "set"
 * functions created within the closure.
 *
 * @api private
 */

function setup (index) {
  function get () {
    return this.__get__(index)
  }
  function set (v) {
    return this.__set__(index, v)
  }
  return {
      enumerable: true
    , configurable: true
    , get: get
    , set: set
  }
}
<|endoftext|>


<commit_before>module.exports = dragDrop

var flatten = require('flatten')
var parallel = require('run-parallel')

function dragDrop (elem, listeners) {
  if (typeof elem === 'string') {
    var selector = elem
    elem = window.document.querySelector(elem)
    if (!elem) {
      throw new Error('"' + selector + '" does not match any HTML elements')
    }
  }

  if (!elem) {
    throw new Error('"' + elem + '" is not a valid HTML element')
  }

  if (typeof listeners === 'function') {
    listeners = { onDrop: listeners }
  }

  var timeout

  elem.addEventListener('dragenter', onDragEnter, false)
  elem.addEventListener('dragover', onDragOver, false)
  elem.addEventListener('dragleave', onDragLeave, false)
  elem.addEventListener('drop', onDrop, false)

  // Function to remove drag-drop listeners
  return function remove () {
    removeDragClass()
    elem.removeEventListener('dragenter', onDragEnter, false)
    elem.removeEventListener('dragover', onDragOver, false)
    elem.removeEventListener('dragleave', onDragLeave, false)
    elem.removeEventListener('drop', onDrop, false)
  }

  function onDragEnter (e) {
    if (listeners.onDragEnter) {
      listeners.onDragEnter(e)
    }

    // Prevent event
    e.stopPropagation()
    e.preventDefault()
    return false
  }

  function onDragOver (e) {
    e.stopPropagation()
    e.preventDefault()
    if (e.dataTransfer.items) {
      // Only add "drag" class when `items` contains items that are able to be
      // handled by the registered listeners (files vs. text)
      var items = toArray(e.dataTransfer.items)
      var fileItems = items.filter(function (item) { return item.kind === 'file' })
      var textItems = items.filter(function (item) { return item.kind === 'string' })

      if (fileItems.length === 0 && !listeners.onDropText) return
      if (textItems.length === 0 && !listeners.onDrop) return
      if (fileItems.length === 0 && textItems.length === 0) return
    }

    elem.classList.add('drag')
    clearTimeout(timeout)

    if (listeners.onDragOver) {
      listeners.onDragOver(e)
    }

    e.dataTransfer.dropEffect = 'copy'
    return false
  }

  function onDragLeave (e) {
    e.stopPropagation()
    e.preventDefault()

    if (listeners.onDragLeave) {
      listeners.onDragLeave(e)
    }

    clearTimeout(timeout)
    timeout = setTimeout(removeDragClass, 50)

    return false
  }

  function onDrop (e) {
    e.stopPropagation()
    e.preventDefault()

    if (listeners.onDragLeave) {
      listeners.onDragLeave(e)
    }

    clearTimeout(timeout)
    removeDragClass()

    var pos = {
      x: e.clientX,
      y: e.clientY
    }

    // text drop support
    var text = e.dataTransfer.getData('text')
    if (text && listeners.onDropText) {
      listeners.onDropText(text, pos)
    }

    // file drop support
    if (e.dataTransfer.files) {
      console.log('e.dataTransfer.files')
      var files = toArray(e.dataTransfer.files)

      if (files.length === 0) return

      files.forEach(function (file) {
        file.fullPath = '/' + file.name
      })

      if (listeners.onDrop) {
        listeners.onDrop(files, pos)
      }
    } else if (e.dataTransfer.items) {
      console.log('e.dataTransfer.items')
      // Handle directories in Chrome using the proprietary FileSystem API
      var items = toArray(e.dataTransfer.items).filter(function (item) {
        return item.kind === 'file'
      })

      if (items.length === 0) return

      parallel(items.map(function (item) {
        return function (cb) {
          processEntry(item.webkitGetAsEntry(), cb)
        }
      }), function (err, results) {
        // This catches permission errors with file:// in Chrome. This should never
        // throw in production code, so the user does not need to use try-catch.
        if (err) throw err
        if (listeners.onDrop) {
          listeners.onDrop(flatten(results), pos)
        }
      })
    }

    return false
  }

  function removeDragClass () {
    elem.classList.remove('drag')
  }
}

function processEntry (entry, cb) {
  var entries = []

  if (entry.isFile) {
    entry.file(function (file) {
      file.fullPath = entry.fullPath // preserve pathing for consumer
      cb(null, file)
    }, function (err) {
      cb(err)
    })
  } else if (entry.isDirectory) {
    var reader = entry.createReader()
    readEntries()
  }

  function readEntries () {
    reader.readEntries(function (entries_) {
      if (entries_.length > 0) {
        entries = entries.concat(toArray(entries_))
        readEntries() // continue reading entries until `readEntries` returns no more
      } else {
        doneEntries()
      }
    })
  }

  function doneEntries () {
    parallel(entries.map(function (entry) {
      return function (cb) {
        processEntry(entry, cb)
      }
    }), cb)
  }
}

function toArray (list) {
  return Array.prototype.slice.call(list || [], 0)
}
<commit_msg>BREAKING: Drop IE11 support (use Array.from)<commit_after>module.exports = dragDrop

var flatten = require('flatten')
var parallel = require('run-parallel')

function dragDrop (elem, listeners) {
  if (typeof elem === 'string') {
    var selector = elem
    elem = window.document.querySelector(elem)
    if (!elem) {
      throw new Error('"' + selector + '" does not match any HTML elements')
    }
  }

  if (!elem) {
    throw new Error('"' + elem + '" is not a valid HTML element')
  }

  if (typeof listeners === 'function') {
    listeners = { onDrop: listeners }
  }

  var timeout

  elem.addEventListener('dragenter', onDragEnter, false)
  elem.addEventListener('dragover', onDragOver, false)
  elem.addEventListener('dragleave', onDragLeave, false)
  elem.addEventListener('drop', onDrop, false)

  // Function to remove drag-drop listeners
  return function remove () {
    removeDragClass()
    elem.removeEventListener('dragenter', onDragEnter, false)
    elem.removeEventListener('dragover', onDragOver, false)
    elem.removeEventListener('dragleave', onDragLeave, false)
    elem.removeEventListener('drop', onDrop, false)
  }

  function onDragEnter (e) {
    if (listeners.onDragEnter) {
      listeners.onDragEnter(e)
    }

    // Prevent event
    e.stopPropagation()
    e.preventDefault()
    return false
  }

  function onDragOver (e) {
    e.stopPropagation()
    e.preventDefault()
    if (e.dataTransfer.items) {
      // Only add "drag" class when `items` contains items that are able to be
      // handled by the registered listeners (files vs. text)
      var items = Array.from(e.dataTransfer.items)
      var fileItems = items.filter(function (item) { return item.kind === 'file' })
      var textItems = items.filter(function (item) { return item.kind === 'string' })

      if (fileItems.length === 0 && !listeners.onDropText) return
      if (textItems.length === 0 && !listeners.onDrop) return
      if (fileItems.length === 0 && textItems.length === 0) return
    }

    elem.classList.add('drag')
    clearTimeout(timeout)

    if (listeners.onDragOver) {
      listeners.onDragOver(e)
    }

    e.dataTransfer.dropEffect = 'copy'
    return false
  }

  function onDragLeave (e) {
    e.stopPropagation()
    e.preventDefault()

    if (listeners.onDragLeave) {
      listeners.onDragLeave(e)
    }

    clearTimeout(timeout)
    timeout = setTimeout(removeDragClass, 50)

    return false
  }

  function onDrop (e) {
    e.stopPropagation()
    e.preventDefault()

    if (listeners.onDragLeave) {
      listeners.onDragLeave(e)
    }

    clearTimeout(timeout)
    removeDragClass()

    var pos = {
      x: e.clientX,
      y: e.clientY
    }

    // text drop support
    var text = e.dataTransfer.getData('text')
    if (text && listeners.onDropText) {
      listeners.onDropText(text, pos)
    }

    // file drop support
    if (e.dataTransfer.files) {
      console.log('e.dataTransfer.files')
      var files = toArray(e.dataTransfer.files)

      if (files.length === 0) return

      files.forEach(function (file) {
        file.fullPath = '/' + file.name
      })

      if (listeners.onDrop) {
        listeners.onDrop(files, pos)
      }
    } else if (e.dataTransfer.items) {
      console.log('e.dataTransfer.items')
      // Handle directories in Chrome using the proprietary FileSystem API
      var items = Array.from(e.dataTransfer.items).filter(function (item) {
        return item.kind === 'file'
      })

      if (items.length === 0) return

      parallel(items.map(function (item) {
        return function (cb) {
          processEntry(item.webkitGetAsEntry(), cb)
        }
      }), function (err, results) {
        // This catches permission errors with file:// in Chrome. This should never
        // throw in production code, so the user does not need to use try-catch.
        if (err) throw err
        if (listeners.onDrop) {
          listeners.onDrop(flatten(results), pos)
        }
      })
    }

    return false
  }

  function removeDragClass () {
    elem.classList.remove('drag')
  }
}

function processEntry (entry, cb) {
  var entries = []

  if (entry.isFile) {
    entry.file(function (file) {
      file.fullPath = entry.fullPath // preserve pathing for consumer
      cb(null, file)
    }, function (err) {
      cb(err)
    })
  } else if (entry.isDirectory) {
    var reader = entry.createReader()
    readEntries()
  }

  function readEntries () {
    reader.readEntries(function (entries_) {
      if (entries_.length > 0) {
        entries = entries.concat(Array.from(entries_))
        readEntries() // continue reading entries until `readEntries` returns no more
      } else {
        doneEntries()
      }
    })
  }

  function doneEntries () {
    parallel(entries.map(function (entry) {
      return function (cb) {
        processEntry(entry, cb)
      }
    }), cb)
  }
}
<|endoftext|>


<commit_before>const request = require('request');

const crypto = require('crypto');

const BASE_URL_GATEWAY_TEST = 'https://stage-secure-gateway.hipay-tpp.com/rest/v1';
const BASE_URL_GATEWAY_PROD = 'https://secure-gateway.hipay-tpp.com/rest/v1/'

const BASE_URL_TOKENIZATION_TEST = 'https://stage-secure-vault.hipay-tpp.com/rest/v1';
const BASE_URL_TOKENIZATION_PROD = 'https://secure-vaalt.hipay-tpp.com/rest/v1/'

class HiPay {
  constructor() {
    this.isInitialized = false;
  }

  init(credentials = {}) {
    if (!credentials.username || !credentials.password) throw new Error('username and password are required');

    this.auth = {
      user: credentials.username,
      pass: credentials.password
    };

    this.passphrase = credentials.passphrase;

    this.baseUrlGateway = credentials.mod === 'production' ? BASE_URL_GATEWAY_PROD : BASE_URL_GATEWAY_TEST;
    this.baseUrlTokenization = credentials.mod === 'production' ? BASE_URL_TOKENIZATION_PROD : BASE_URL_TOKENIZATION_TEST;

    this.isInitialized = true;
  }

  order(order = {}) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'POST',
        url: `${this.baseUrlGateway}/order`,
        json: order,
        auth: this.auth
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(body);
      });
    });
  }

  capture(transaction = {}) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'POST',
        url: `${this.baseUrlGateway}/maintenance/transaction/${transaction.id}`,
        json: {
          operation: 'capture',
          amount: transaction.amount
        },
        auth: this.auth
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(body);
      });
    });
  }

  refund(transaction = {}) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'POST',
        url: `${this.baseUrlGateway}/maintenance/transaction/${transaction.id}`,
        json: {
          operation: 'refund',
          amount: transaction.amount
        },
        auth: this.auth
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(body);
      });
    });
  }

  cancel(transaction = {}) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'POST',
        url: `${this.baseUrlGateway}/maintenance/transaction/${transaction.id}`,
        json: {
          operation: 'cancel',
          amount: transaction.amount
        },
        auth: this.auth
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(body);
      });
    });
  }

  getFormUrl(order = {}) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'POST',
        url: `${this.baseUrlGateway}/hpayment`,
        json: order,
        auth: this.auth
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400 || !body.forwardUrl) return reject(err || body);

        resolve(body.forwardUrl);
      });
    });
  }

  getTransactionDetails(source = {}) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'GET',
        url: `${this.baseUrlGateway}/transaction`,
        auth: this.auth
      };

      if (source.transactionId) options.url += `/${source.transactionId}`;
      else options.qs = { orderid: source.orderId };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(body);
      });
    });
  }

  getTokenDetails(data) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'GET',
        url: `${this.baseUrlTokenization}/token/${data.token}`,
        form: data,
        headers: {
          Accept: 'application/json'
        },
        auth: this.auth,
        qs: { request_id: data.requestId }
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(body);
      });
    });
  }

  createToken(card) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'POST',
        url: `${this.baseUrlTokenization}/token/create`,
        form: card,
        headers: {
          Accept: 'application/json'
        },
        auth: this.auth
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(body);
      });
    });
  }

  updateToken(data) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'POST',
        url: `${this.baseUrlTokenization}/token/update`,
        form: data,
        headers: {
          Accept: 'application/json'
        },
        auth: this.auth
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(body);
      });
    });
  }

  isCallbackSignatureValid(query = {}) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    const receivedHash = query.hash;

    const generatedHash = Object.keys(query)
      .sort()
      .filter( key => key !== 'hash')
      .reduce( (memo, key) => {
        if (query[key].length > 0) memo += `${key}${query[key]}${this.passphrase}`;

        return memo;
      }, '');

    return crypto.createHash('sha1').update(generatedHash).digest('hex') === receivedHash;
  }

  isNotifySignatureValid(rawBody = '', receivedHash) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    const generatedHash = `${rawBody}${this.passphrase}`;

    return crypto.createHash('sha1').update(generatedHash).digest('hex') === receivedHash;
  }
};

module.exports = new HiPay();
<commit_msg>conver token to JSON object<commit_after>const request = require('request');

const crypto = require('crypto');

const BASE_URL_GATEWAY_TEST = 'https://stage-secure-gateway.hipay-tpp.com/rest/v1';
const BASE_URL_GATEWAY_PROD = 'https://secure-gateway.hipay-tpp.com/rest/v1/'

const BASE_URL_TOKENIZATION_TEST = 'https://stage-secure-vault.hipay-tpp.com/rest/v1';
const BASE_URL_TOKENIZATION_PROD = 'https://secure-vaalt.hipay-tpp.com/rest/v1/'

class HiPay {
  constructor() {
    this.isInitialized = false;
  }

  init(credentials = {}) {
    if (!credentials.username || !credentials.password) throw new Error('username and password are required');

    this.auth = {
      user: credentials.username,
      pass: credentials.password
    };

    this.passphrase = credentials.passphrase;

    this.baseUrlGateway = credentials.mod === 'production' ? BASE_URL_GATEWAY_PROD : BASE_URL_GATEWAY_TEST;
    this.baseUrlTokenization = credentials.mod === 'production' ? BASE_URL_TOKENIZATION_PROD : BASE_URL_TOKENIZATION_TEST;

    this.isInitialized = true;
  }

  order(order = {}) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'POST',
        url: `${this.baseUrlGateway}/order`,
        json: order,
        auth: this.auth
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(body);
      });
    });
  }

  capture(transaction = {}) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'POST',
        url: `${this.baseUrlGateway}/maintenance/transaction/${transaction.id}`,
        json: {
          operation: 'capture',
          amount: transaction.amount
        },
        auth: this.auth
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(body);
      });
    });
  }

  refund(transaction = {}) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'POST',
        url: `${this.baseUrlGateway}/maintenance/transaction/${transaction.id}`,
        json: {
          operation: 'refund',
          amount: transaction.amount
        },
        auth: this.auth
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(body);
      });
    });
  }

  cancel(transaction = {}) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'POST',
        url: `${this.baseUrlGateway}/maintenance/transaction/${transaction.id}`,
        json: {
          operation: 'cancel',
          amount: transaction.amount
        },
        auth: this.auth
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(body);
      });
    });
  }

  getFormUrl(order = {}) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'POST',
        url: `${this.baseUrlGateway}/hpayment`,
        json: order,
        auth: this.auth
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400 || !body.forwardUrl) return reject(err || body);

        resolve(body.forwardUrl);
      });
    });
  }

  getTransactionDetails(source = {}) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'GET',
        url: `${this.baseUrlGateway}/transaction`,
        auth: this.auth
      };

      if (source.transactionId) options.url += `/${source.transactionId}`;
      else options.qs = { orderid: source.orderId };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(body);
      });
    });
  }

  getTokenDetails(data) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'GET',
        url: `${this.baseUrlTokenization}/token/${data.token}`,
        form: data,
        headers: {
          Accept: 'application/json'
        },
        auth: this.auth,
        qs: { request_id: data.requestId }
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(body);
      });
    });
  }

  createToken(card) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'POST',
        url: `${this.baseUrlTokenization}/token/create`,
        form: card,
        headers: {
          Accept: 'application/json'
        },
        auth: this.auth
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(JSON.parse(body));
      });
    });
  }

  updateToken(data) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    return new Promise( (resolve, reject) => {
      const options = {
        method: 'POST',
        url: `${this.baseUrlTokenization}/token/update`,
        form: data,
        headers: {
          Accept: 'application/json'
        },
        auth: this.auth
      };

      request(options, (err, response, body) => {
        if (err || response.statusCode >= 400) return reject(err || body);

        resolve(body);
      });
    });
  }

  isCallbackSignatureValid(query = {}) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    const receivedHash = query.hash;

    const generatedHash = Object.keys(query)
      .sort()
      .filter( key => key !== 'hash')
      .reduce( (memo, key) => {
        if (query[key].length > 0) memo += `${key}${query[key]}${this.passphrase}`;

        return memo;
      }, '');

    return crypto.createHash('sha1').update(generatedHash).digest('hex') === receivedHash;
  }

  isNotifySignatureValid(rawBody = '', receivedHash) {
    if (!this.isInitialized) throw new Error('Serice not initialized');

    const generatedHash = `${rawBody}${this.passphrase}`;

    return crypto.createHash('sha1').update(generatedHash).digest('hex') === receivedHash;
  }
};

module.exports = new HiPay();
<|endoftext|>


<commit_before>var extensions = {
  '.babel.js': 'babel/register',
  '.cjsx': 'node-cjsx/register',
  '.co': 'coco',
  '.coffee': 'coffee-script/register',
  '.coffee.md': 'coffee-script/register',
  '.csv': 'require-csv',
  '.iced': 'iced-coffee-script/register',
  '.iced.md': 'iced-coffee-script/register',
  '.ini': 'require-ini',
  '.js': null,
  '.json': null,
  '.json5': 'json5/lib/require',
  '.jsx': 'node-jsx',
  '.litcoffee': 'coffee-script/register',
  '.liticed': 'iced-coffee-script/register',
  '.ls': 'LiveScript',
  '.toml': 'toml-require',
  '.ts': 'typescript-register',
  '.wisp': 'wisp/engine/node',
  '.xml': 'require-xml',
  '.yaml': 'require-yaml',
  '.yml': 'require-yaml'
};

var register = {
  'babel/register': function (module, config) {
    module(config);
    // cleanup the sadness
    delete require.extensions['.es'];
    delete require.extensions['.es6'];
    delete require.extensions['.jsx'];
  },
  'node-jsx': function (module, config) {
    module.install(config);
  },
  'toml-require': function (module) {
    module.install();
  }
};

var legacyModules = {
  '.coffee': 'coffee-script',
  '.coffee.md': 'coffee-script',
  '.iced': 'iced-coffee-script',
  // .iced.md and .liticed weren't available before the register module
  '.litcoffee': 'coffee-script',
  // typescript-require is for versions of TypeScript before 1.4
  '.ts': 'typescript-require'
};

var configurations = {
  'babel/register': {
    extensions: '.js'
  },
  'node-jsx': {
    extension: '.jsx',
    harmony: true
  }
};

var jsVariantExtensions = [
  '.js',
  '.babel.js',
  '.cjsx',
  '.co',
  '.coffee',
  '.coffee.md',
  '.iced',
  '.iced.md',
  '.jsx',
  '.litcoffee',
  '.liticed',
  '.ls',
  '.ts',
  '.wisp'
];

module.exports = {
  extensions: extensions,
  legacy: legacyModules,
  configurations: configurations,
  register: register,
  jsVariants: jsVariantExtensions.reduce(function (result, ext) {
    result[ext] = extensions[ext];
    return result;
  }, {})
};
<commit_msg>add note about workaround<commit_after>var extensions = {
  '.babel.js': 'babel/register',
  '.cjsx': 'node-cjsx/register',
  '.co': 'coco',
  '.coffee': 'coffee-script/register',
  '.coffee.md': 'coffee-script/register',
  '.csv': 'require-csv',
  '.iced': 'iced-coffee-script/register',
  '.iced.md': 'iced-coffee-script/register',
  '.ini': 'require-ini',
  '.js': null,
  '.json': null,
  '.json5': 'json5/lib/require',
  '.jsx': 'node-jsx',
  '.litcoffee': 'coffee-script/register',
  '.liticed': 'iced-coffee-script/register',
  '.ls': 'LiveScript',
  '.toml': 'toml-require',
  '.ts': 'typescript-register',
  '.wisp': 'wisp/engine/node',
  '.xml': 'require-xml',
  '.yaml': 'require-yaml',
  '.yml': 'require-yaml'
};

var register = {
  'babel/register': function (module, config) {
    module(config);
    // cleanup the sadness
    delete require.extensions['.es'];
    delete require.extensions['.es6'];
    delete require.extensions['.jsx'];
  },
  'node-jsx': function (module, config) {
    module.install(config);
  },
  'toml-require': function (module) {
    module.install();
  }
};

var legacyModules = {
  '.coffee': 'coffee-script',
  '.coffee.md': 'coffee-script',
  '.iced': 'iced-coffee-script',
  // .iced.md and .liticed weren't available before the register module
  '.litcoffee': 'coffee-script',
  // typescript-require is for versions of TypeScript before 1.4
  '.ts': 'typescript-require'
};

var configurations = {
  'babel/register': {
    // register on .js extension due to https://github.com/joyent/node/blob/v0.12.0/lib/module.js#L353
    // which only captures the final extension (.babel.js -> .js)
    extensions: '.js'
  },
  'node-jsx': {
    extension: '.jsx',
    harmony: true
  }
};

var jsVariantExtensions = [
  '.js',
  '.babel.js',
  '.cjsx',
  '.co',
  '.coffee',
  '.coffee.md',
  '.iced',
  '.iced.md',
  '.jsx',
  '.litcoffee',
  '.liticed',
  '.ls',
  '.ts',
  '.wisp'
];

module.exports = {
  extensions: extensions,
  legacy: legacyModules,
  configurations: configurations,
  register: register,
  jsVariants: jsVariantExtensions.reduce(function (result, ext) {
    result[ext] = extensions[ext];
    return result;
  }, {})
};
<|endoftext|>


<commit_before>var Transform = require('stream').Transform
var Q = require('q')
var typeForce = require('typeforce')
var inherits = require('util').inherits
var utils = require('tradle-utils')
var debug = require('debug')('chainloader')
var pluck = require('array-pluck')
var extend = require('extend')
var getTxInfo = require('tradle-tx-data').getTxInfo
var Permission = require('tradle-permission')
var FILE_EVENTS = ['file:shared', 'file:public', 'file:permission']

module.exports = Loader
inherits(Loader, Transform)

/**
 * Load data from the chain (blockchain + keeper)
 * @param {Function} lookup (optional) - function to look up identities by fingerprints
 * @param {BitKeeper|BitKeeper client} keeper
 * @param {String} networkName
 * @param {String} prefix - prefix for OP_RETURN data
 * @param {Object} options
 */
function Loader (options) {
  var self = this

  typeForce({
    keeper: 'Object',
    networkName: 'String',
    prefix: 'String'
  }, options)

  typeForce({
    put: 'Function',
    getMany: 'Function'
  }, options.keeper)

  Transform.call(this, {
    objectMode: true,
    highWaterMark: 16
  })

  utils.bindPrototypeFunctions(this)

  extend(this, options)
  if (options.lookup) this.lookupWith(options.lookup)

  FILE_EVENTS.forEach(function (event) {
    self.on(event, function (data) {
      // self.saveIfNew(data)
      self.emit('file', data)
    })
  })
}

Loader.prototype._transform = function (tx, encoding, done) {
  var self = this
  this.load(tx)
    .catch(done)
    .done(function (files) {
      files.forEach(self.push, self)
      done()
    })
}

/**
 *  Optimized data loading with minimum calls to keeper
 *  @return {Q.Promise} for files related to the passed in transactions/ids
 **/
Loader.prototype.load = function (txs) {
  var self = this
  txs = [].concat(txs)

  return this._parseTxs(txs)
    .then(onParsed)

  function onParsed (parsed) {
    if (!parsed.length) return Q.resolve()

    var pub = parsed.filter(function (p) { return p.type === 'public' })
    var enc = parsed.filter(function (p) { return p.type === 'permission' })
    var keys = pluck(pub.concat(enc), 'key')
    var shared
    var files = []
    return self.fetchFiles(keys)
      .then(function (fetched) {
        if (!fetched.length) return

        pub.forEach(function (parsed, i) {
          if (fetched[i]) {
            parsed.file = fetched[i]
            self.emit('file:public', parsed)
            files.push(parsed)
          }
        })

        if (!enc.length) return

        shared = enc.filter(function (parsed, i) {
          var file = fetched[i + pub.length]
          if (!file) return

          try {
            parsed.permission = Permission.recover(file, parsed.sharedKey)
            parsed.key = parsed.permission.fileKeyString()
          } catch (err) {
            debug('Failed to recover permission file contents from raw data', err)
            return
          }

          self.emit('file:permission', parsed)
          return true
        })

        if (!shared.length) return

        return self.fetchFiles(pluck(shared, 'key'))
      })
      .then(function (sharedFiles) {
        if (sharedFiles) {
          sharedFiles.forEach(function (file, idx) {
            var parsed = extend({}, shared[idx])
            parsed.key = parsed.permission.fileKeyString()
            parsed.type = 'sharedfile'

            var decryptionKey = parsed.permission.decryptionKeyBuf()
            if (decryptionKey) {
              try {
                file = utils.decrypt(file, decryptionKey)
              } catch (err) {
                debug('Failed to decrypt ciphertext: ' + file)
                return
              }
            }

            parsed.file = file
            self.emit('file:shared', parsed)
            files.push(parsed)
          })
        }

        return files.sort(function (a, b) {
          return txs.indexOf(a.tx.body) - txs.indexOf(b.tx.body)
        })
      })
  }
}

/*
 * @param {Function} fn - function to look up identities by fingerprints
 *   @example
 *     function lookup (cb) {
 *       cb(err, {
 *         key: key with pub/priv props or functions
 *       })
 *     }
 */
Loader.prototype.lookupWith = function (fn) {
  this.lookup = fn
  return this
}

// /**
//  * Attempt to deduce the permission key and ECDH shared key
//  *   from the parties involved in the bitcoin transaction
//  * @param  {Transaction} tx
//  * @param  {TransactionData} txData
//  * @return {Object}   permission file "key" and ECDH "sharedKey" to decrypt it
//  */
// Loader.prototype.deduceECDHKeys = function (tx, txData) {
//   if (!(this.wallet && txData)) return

//   var wallet = this.wallet
//   var myAddress
//   var myPrivKey
//   var theirPubKey
//   var toMe = this.getSentToMe(tx)
//   var fromMe = this.getSentFromMe(tx)
//   if (!toMe.length && !fromMe.length) {
//     debug("Cannot parse permission data from transaction as it's neither to me nor from me")
//     return
//   }

//   if (fromMe.length) {
//     tx.ins.some(function (input) {
//       var addr = utils.getAddressFromInput(input, this.networkName)
//       myPrivKey = wallet.addressString === addr && wallet.priv
//       return myPrivKey
//     }, this)

//     toMe.some(function (out) {
//       var addr = utils.getAddressFromOutput(out, this.networkName)
//       theirPubKey = addr === wallet.addressString && wallet.pub
//       return theirPubKey
//     }, this)
//   } else {
//     myAddress = utils.getAddressFromOutput(toMe[0], this.networkName)
//     myPrivKey = wallet.addressString === myAddress && wallet.priv
//     theirPubKey = bitcoin.ECPubKey.fromBuffer(tx.ins[0].script.chunks[1])
//   }

//   if (myPrivKey && theirPubKey) {
//     if (myPrivKey.pub.toHex() !== theirPubKey.toHex()) {
//       return {
//         priv: myPrivKey,
//         pub: theirPubKey
//       }
//     }
//   }

// }

/**
 *  @return {Array} outputs in tx that the underlying wallet can spend
 */
// Loader.prototype.getSentToMe = function (tx) {
//   if (!this.wallet) return []

//   return tx.outs.filter(function (out) {
//     var address = utils.getAddressFromOutput(out, this.networkName)
//     return this.wallet.addressString === address
//   }, this)
// }

/**
 *  @return {Array} inputs in tx that are signed by the underlying wallet
 */
// Loader.prototype.getSentFromMe = function (tx) {
//   if (!this.wallet) return []

//   return tx.ins.filter(function (input) {
//     var address = utils.getAddressFromInput(input, this.networkName)
//     return this.wallet.addressString === address
//   }, this)
// }

Loader.prototype.fetchFiles = function (keys) {
  return this.keeper.getMany(keys)
    .catch(function (err) {
      debug('Error fetching files', err)
      throw new Error(err.message || 'Failed to retrieve file from keeper')
    })
}

// Loader.prototype.saveIfNew = function (data) {
//   var self = this

//   var wallet = this.wallet
//   if (!wallet) return

//   var tx = data.tx.body
//   var metadata = data.tx.metadata
//   if (!metadata || metadata.confirmations) return

//   var received = !wallet.isSentByMe(tx)
//   var type = received ? 'received' : 'sent'
//   return this.keeper.put(data.file)
//     .then(function () {
//       self.emit('file:' + type, data)
//     })
// }

Loader.prototype._getSharedKey = function (parsed) {
  if (!(parsed.from && parsed.to)) return

  var from = parsed.from.key
  var to = parsed.to.key
  var priv = getResult(from, 'priv')
  var pub = getResult(to, 'pub')
  if (!priv) {
    priv = getResult(to, 'priv')
    pub = getResult(from, 'pub')
  }

  return priv && pub && utils.sharedEncryptionKey(priv, pub)
}

Loader.prototype._parseTxs = function (txs) {
  return Q.all(txs.map(this._parseTx, this))
    .then(function (parsed) {
      return parsed.filter(function (p) {
        return !!p
      })
    })
}

Loader.prototype._parseTx = function (tx, cb) {
  var self = this
  var parsed = getTxInfo(tx, self.networkName, self.prefix)
  if (!parsed) return Q.resolve()

  var addrs = parsed.tx.addresses
  if (!this.lookup) return onlookedup()

  var allAddrs = addrs.from.concat(addrs.to)
  var lookups = allAddrs.map(function (f) {
    return Q.ninvoke(self, 'lookup', f)
  })

  return Q.allSettled(lookups)
    .then(function (results) {
      results = results.map(function (r) {
        return r.value
      })

      results.slice(0, addrs.from.length)
        .some(function (result) {
          if (result) {
            parsed.from = result
            return true
          }
        })

      results.slice(addrs.from.length)
        .some(function (result) {
          if (result && parsed.from && !parsed.from.key.equals(result.key)) {
            parsed.to = result
            return true
          }
        })

      return onlookedup()
    })

  function onlookedup () {
    if (parsed.type !== 'public') {
      parsed.sharedKey = self._getSharedKey(parsed)
      if (parsed.sharedKey) {
        try {
          parsed.key = utils.decrypt(parsed.key, parsed.sharedKey)
          parsed.permissionKey = parsed.key
        } catch (err) {
          debug('Failed to decrypt permission key: ' + parsed.key)
          return
        }
      }
    }

    parsed.key = parsed.key.toString('hex')
    if (parsed.permissionKey) {
      parsed.permissionKey = parsed.permissionKey.toString('hex')
    }

    return parsed
  }

  // if (self.identity) {
  //   find(addrs.from, function (addr) {
  //     var key = self.identity.keys({ fingerprint: addr })[0]
  //     from = key && {
  //       key: key,
  //       identity: self.identity
  //     }

  //     return from
  //   })

  //   if (from.identity !== self.identity) {
  //     find(addrs.to, function (addr) {
  //       var key = self.identity.keys({ fingerprint: addr })[0]
  //       to = key && {
  //         key: key,
  //         identity: self.identity
  //       }

  //       return to
  //     })
  //   }
  // }

  // if (self.addressBook) {
  //   if (!from) {
  //     find(addrs.from, function (addr) {
  //       from = self.addressBook.byFingerprint(addr)
  //       return from
  //     })
  //   }

  //   if (!to) {
  //     find(addrs.to, function (addr) {
  //       to = self.addressBook.byFingerprint(addr)
  //       return to
  //     })
  //   }
  // }

// parsed.from = from
// parsed.to = to
}

function getResult (obj, p) {
  var val = obj[p]
  if (typeof val === 'function') return obj[p]()
  else return val
}
<commit_msg>nullp check<commit_after>var Transform = require('stream').Transform
var Q = require('q')
var typeForce = require('typeforce')
var inherits = require('util').inherits
var utils = require('tradle-utils')
var debug = require('debug')('chainloader')
var pluck = require('array-pluck')
var extend = require('extend')
var getTxInfo = require('tradle-tx-data').getTxInfo
var Permission = require('tradle-permission')
var FILE_EVENTS = ['file:shared', 'file:public', 'file:permission']

module.exports = Loader
inherits(Loader, Transform)

/**
 * Load data from the chain (blockchain + keeper)
 * @param {Function} lookup (optional) - function to look up identities by fingerprints
 * @param {BitKeeper|BitKeeper client} keeper
 * @param {String} networkName
 * @param {String} prefix - prefix for OP_RETURN data
 * @param {Object} options
 */
function Loader (options) {
  var self = this

  typeForce({
    keeper: 'Object',
    networkName: 'String',
    prefix: 'String'
  }, options)

  typeForce({
    put: 'Function',
    getMany: 'Function'
  }, options.keeper)

  Transform.call(this, {
    objectMode: true,
    highWaterMark: 16
  })

  utils.bindPrototypeFunctions(this)

  extend(this, options)
  if (options.lookup) this.lookupWith(options.lookup)

  FILE_EVENTS.forEach(function (event) {
    self.on(event, function (data) {
      // self.saveIfNew(data)
      self.emit('file', data)
    })
  })
}

Loader.prototype._transform = function (tx, encoding, done) {
  var self = this
  this.load(tx)
    .catch(done)
    .done(function (files) {
      if (files) {
        files.forEach(self.push, self)
      }

      done()
    })
}

/**
 *  Optimized data loading with minimum calls to keeper
 *  @return {Q.Promise} for files related to the passed in transactions/ids
 **/
Loader.prototype.load = function (txs) {
  var self = this
  txs = [].concat(txs)

  return this._parseTxs(txs)
    .then(onParsed)

  function onParsed (parsed) {
    if (!parsed.length) return Q.resolve()

    var pub = parsed.filter(function (p) { return p.type === 'public' })
    var enc = parsed.filter(function (p) { return p.type === 'permission' })
    var keys = pluck(pub.concat(enc), 'key')
    var shared
    var files = []
    return self.fetchFiles(keys)
      .then(function (fetched) {
        if (!fetched.length) return

        pub.forEach(function (parsed, i) {
          if (fetched[i]) {
            parsed.file = fetched[i]
            self.emit('file:public', parsed)
            files.push(parsed)
          }
        })

        if (!enc.length) return

        shared = enc.filter(function (parsed, i) {
          var file = fetched[i + pub.length]
          if (!file) return

          try {
            parsed.permission = Permission.recover(file, parsed.sharedKey)
            parsed.key = parsed.permission.fileKeyString()
          } catch (err) {
            debug('Failed to recover permission file contents from raw data', err)
            return
          }

          self.emit('file:permission', parsed)
          return true
        })

        if (!shared.length) return

        return self.fetchFiles(pluck(shared, 'key'))
      })
      .then(function (sharedFiles) {
        if (sharedFiles) {
          sharedFiles.forEach(function (file, idx) {
            var parsed = extend({}, shared[idx])
            parsed.key = parsed.permission.fileKeyString()
            parsed.type = 'sharedfile'

            var decryptionKey = parsed.permission.decryptionKeyBuf()
            if (decryptionKey) {
              try {
                file = utils.decrypt(file, decryptionKey)
              } catch (err) {
                debug('Failed to decrypt ciphertext: ' + file)
                return
              }
            }

            parsed.file = file
            self.emit('file:shared', parsed)
            files.push(parsed)
          })
        }

        return files.sort(function (a, b) {
          return txs.indexOf(a.tx.body) - txs.indexOf(b.tx.body)
        })
      })
  }
}

/*
 * @param {Function} fn - function to look up identities by fingerprints
 *   @example
 *     function lookup (cb) {
 *       cb(err, {
 *         key: key with pub/priv props or functions
 *       })
 *     }
 */
Loader.prototype.lookupWith = function (fn) {
  this.lookup = fn
  return this
}

// /**
//  * Attempt to deduce the permission key and ECDH shared key
//  *   from the parties involved in the bitcoin transaction
//  * @param  {Transaction} tx
//  * @param  {TransactionData} txData
//  * @return {Object}   permission file "key" and ECDH "sharedKey" to decrypt it
//  */
// Loader.prototype.deduceECDHKeys = function (tx, txData) {
//   if (!(this.wallet && txData)) return

//   var wallet = this.wallet
//   var myAddress
//   var myPrivKey
//   var theirPubKey
//   var toMe = this.getSentToMe(tx)
//   var fromMe = this.getSentFromMe(tx)
//   if (!toMe.length && !fromMe.length) {
//     debug("Cannot parse permission data from transaction as it's neither to me nor from me")
//     return
//   }

//   if (fromMe.length) {
//     tx.ins.some(function (input) {
//       var addr = utils.getAddressFromInput(input, this.networkName)
//       myPrivKey = wallet.addressString === addr && wallet.priv
//       return myPrivKey
//     }, this)

//     toMe.some(function (out) {
//       var addr = utils.getAddressFromOutput(out, this.networkName)
//       theirPubKey = addr === wallet.addressString && wallet.pub
//       return theirPubKey
//     }, this)
//   } else {
//     myAddress = utils.getAddressFromOutput(toMe[0], this.networkName)
//     myPrivKey = wallet.addressString === myAddress && wallet.priv
//     theirPubKey = bitcoin.ECPubKey.fromBuffer(tx.ins[0].script.chunks[1])
//   }

//   if (myPrivKey && theirPubKey) {
//     if (myPrivKey.pub.toHex() !== theirPubKey.toHex()) {
//       return {
//         priv: myPrivKey,
//         pub: theirPubKey
//       }
//     }
//   }

// }

/**
 *  @return {Array} outputs in tx that the underlying wallet can spend
 */
// Loader.prototype.getSentToMe = function (tx) {
//   if (!this.wallet) return []

//   return tx.outs.filter(function (out) {
//     var address = utils.getAddressFromOutput(out, this.networkName)
//     return this.wallet.addressString === address
//   }, this)
// }

/**
 *  @return {Array} inputs in tx that are signed by the underlying wallet
 */
// Loader.prototype.getSentFromMe = function (tx) {
//   if (!this.wallet) return []

//   return tx.ins.filter(function (input) {
//     var address = utils.getAddressFromInput(input, this.networkName)
//     return this.wallet.addressString === address
//   }, this)
// }

Loader.prototype.fetchFiles = function (keys) {
  return this.keeper.getMany(keys)
    .catch(function (err) {
      debug('Error fetching files', err)
      throw new Error(err.message || 'Failed to retrieve file from keeper')
    })
}

// Loader.prototype.saveIfNew = function (data) {
//   var self = this

//   var wallet = this.wallet
//   if (!wallet) return

//   var tx = data.tx.body
//   var metadata = data.tx.metadata
//   if (!metadata || metadata.confirmations) return

//   var received = !wallet.isSentByMe(tx)
//   var type = received ? 'received' : 'sent'
//   return this.keeper.put(data.file)
//     .then(function () {
//       self.emit('file:' + type, data)
//     })
// }

Loader.prototype._getSharedKey = function (parsed) {
  if (!(parsed.from && parsed.to)) return

  var from = parsed.from.key
  var to = parsed.to.key
  var priv = getResult(from, 'priv')
  var pub = getResult(to, 'pub')
  if (!priv) {
    priv = getResult(to, 'priv')
    pub = getResult(from, 'pub')
  }

  return priv && pub && utils.sharedEncryptionKey(priv, pub)
}

Loader.prototype._parseTxs = function (txs) {
  return Q.all(txs.map(this._parseTx, this))
    .then(function (parsed) {
      return parsed.filter(function (p) {
        return !!p
      })
    })
}

Loader.prototype._parseTx = function (tx, cb) {
  var self = this
  var parsed = getTxInfo(tx, self.networkName, self.prefix)
  if (!parsed) return Q.resolve()

  var addrs = parsed.tx.addresses
  if (!this.lookup) return onlookedup()

  var allAddrs = addrs.from.concat(addrs.to)
  var lookups = allAddrs.map(function (f) {
    return Q.ninvoke(self, 'lookup', f)
  })

  return Q.allSettled(lookups)
    .then(function (results) {
      results = results.map(function (r) {
        return r.value
      })

      results.slice(0, addrs.from.length)
        .some(function (result) {
          if (result) {
            parsed.from = result
            return true
          }
        })

      results.slice(addrs.from.length)
        .some(function (result) {
          if (result && parsed.from && !parsed.from.key.equals(result.key)) {
            parsed.to = result
            return true
          }
        })

      return onlookedup()
    })

  function onlookedup () {
    if (parsed.type !== 'public') {
      parsed.sharedKey = self._getSharedKey(parsed)
      if (parsed.sharedKey) {
        try {
          parsed.key = utils.decrypt(parsed.key, parsed.sharedKey)
          parsed.permissionKey = parsed.key
        } catch (err) {
          debug('Failed to decrypt permission key: ' + parsed.key)
          return
        }
      }
    }

    parsed.key = parsed.key.toString('hex')
    if (parsed.permissionKey) {
      parsed.permissionKey = parsed.permissionKey.toString('hex')
    }

    return parsed
  }

  // if (self.identity) {
  //   find(addrs.from, function (addr) {
  //     var key = self.identity.keys({ fingerprint: addr })[0]
  //     from = key && {
  //       key: key,
  //       identity: self.identity
  //     }

  //     return from
  //   })

  //   if (from.identity !== self.identity) {
  //     find(addrs.to, function (addr) {
  //       var key = self.identity.keys({ fingerprint: addr })[0]
  //       to = key && {
  //         key: key,
  //         identity: self.identity
  //       }

  //       return to
  //     })
  //   }
  // }

  // if (self.addressBook) {
  //   if (!from) {
  //     find(addrs.from, function (addr) {
  //       from = self.addressBook.byFingerprint(addr)
  //       return from
  //     })
  //   }

  //   if (!to) {
  //     find(addrs.to, function (addr) {
  //       to = self.addressBook.byFingerprint(addr)
  //       return to
  //     })
  //   }
  // }

// parsed.from = from
// parsed.to = to
}

function getResult (obj, p) {
  var val = obj[p]
  if (typeof val === 'function') return obj[p]()
  else return val
}
<|endoftext|>


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

/*
  Copyright 2014 Marc Juul and MaxB
  License: AGPLv3

  This file is part of service-browser.

  service-browser is free software: you can redistribute it
  and/or modifyit under the terms of the GNU Affero General
  Public License as published by the Free Software Foundation,
  either version 3 of the License, or (at your option) any
  later version.

  service-browser is distributed in the hope that it will be
  useful, but WITHOUT ANY WARRANTY; without even the implied
  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  PURPOSE.  See the GNU Affero General Public License for
  more details.

  You should have received a copy of the GNU Affero General
  Public License along with service-browser.
  If not, see <http://www.gnu.org/licenses/>.
*/

var util = require('util');
var path = require('path');
var http = require('http');
var express = require('express');
var sockjs = require('sockjs');
var mdns = require('mdns2');
var _ = require('lodash');
var level = require('level');
var db = level(__dirname + '/db/services.db', { encoding: 'json' });

var config = require('./config.js');

var clients = [];

// send message to all clients
function broadcast(msg) {
    if(typeof(msg) != 'string') {
        msg = JSON.stringify(msg);
    }
    var i, client;
    for(i=0; i < clients.length; i++) {
        client = clients[i];
        client.write(msg);
    }
}

function send_all_services(client) {
  //broadcat all the services in the db to the client
  var serviceStream = db.createValueStream({start: 'service!', end: 'service~'});
  serviceStream.on('data',function(service){
    client.write(JSON.stringify({
      type: 'service',
      action: 'up',
      service: service
    }));
  });
}

//could go into a module
function key_hash(str) {
  var hash;
  for (var i=0; i < str.length; i += 1) {
    var chr = str.charCodeAt(i);
    hash = ((hash << 5) - hash) + chr;
    hash |= 0;
  }
  hash = Math.abs(hash).toString(16);
  return hash;
};

function construct_key(service) {
  var sufix = key_hash(service.unique);
  return 'service!' + sufix;
};

function already_in_db(service, cbmesh) {
  // Check to see if the service is already in the db
  db.get(construct_key(service), function (err, value) {
    var found;
    if (err) {
      found = false;
    } else {
      found = true;
    }
    cbmesh(found, value, err);
  });
};

// only allow services of the types we're interested in
function filter(service) {
    return service;
}

function createUnique(service) {
    // mdns does this great thing where they'll automatically resolve
    // nameing conflicts so this is indeed a unique identifier even without hostname
    return service.replyDomain + service.type.protocol + '.' + encodeURIComponent(service.type.name) + '.' + service.name.replace(/ /, '');
}

var advert = mdns.createAdvertisement('_http._tcp,_service-browser', config.port, {txtRecord: {
    name: 'Service Browser',
    description: "Browse services on People's Open Network",
    scope: 'peoplesopen.net',
    type: 'service-browser'
}});

// TODO need to listen for all service types
// dev note: try udisks-ssh instead of http
var browser = mdns.createBrowser(mdns.makeServiceType('http', 'tcp'));
browser.on('serviceUp', function(service) {
    // ignore other service browsers
    if(service.type.subtypes.indexOf('service-browser') >= 0) {
        console.log("ignoring other service browser");
        return;
    }
    console.log('service coming up: ');
    console.log(service);

    service.unique = createUnique(service);
    //add service to the db
    already_in_db(service, function(found) {
      if(!found && filter(service)) {
        var newkey = construct_key(service);
        db.put(newkey, service, function () {
          console.log('remembering service ' + newkey);
          broadcast({
              type: 'service',
              action: 'up',
              service: service
          });
        });
      }
    });
});

browser.on('serviceDown', function(service) {
    console.log('service coming down: ');
    //console.log(service);
    service.unique = createUnique(service);
    if(filter(service)) {
        broadcast({
            type: 'service',
            action: 'down',
            service: service
        });
        db.del(construct_key(service), function () {
          console.log('forggeting service ' + construct_key(service));
        })
    }
});

browser.on('error', function (err) {
  console.log('mdns error: ' + err);
});

browser.start();

var websocket = sockjs.createServer();
websocket.on('connection', function(conn) {
    clients.push(conn);
    console.log("New client connected ("+clients.length+" total)");
    send_all_services(conn);
    conn.on('data', function(message) {
        // nothing to do here yet
    });
    conn.on('close', function() {
        var i = clients.indexOf(conn);
        if(i && (i >= 0)) {
            clients.splice(i, 1); // remove the client
        }
        console.log("Client disconnected ("+clients.length+" total)");
    });
});

var app = express();
var server = http.createServer(app);

websocket.installHandlers(server, {prefix:'/websocket'});

// serve static content from the /www dir at /static
app.use('/static/', express.static(path.join(__dirname, 'www')));

// for parsing post request
app.use(express.bodyParser());

app.get('/nodes/:id', function(req, res){
    if(!req.params.id) {
        error(res, "node id must be specified in request");
        return;
    }
    console.log("retrieving node with id: " + req.params.id);

});

console.log('Listening on '+config.hostname+':'+config.port);
server.listen(config.port, config.hostname);
<commit_msg>bugfix related to ignoring other service browsers<commit_after>#!/usr/bin/env node

/*
  Copyright 2014 Marc Juul and MaxB
  License: AGPLv3

  This file is part of service-browser.

  service-browser is free software: you can redistribute it
  and/or modifyit under the terms of the GNU Affero General
  Public License as published by the Free Software Foundation,
  either version 3 of the License, or (at your option) any
  later version.

  service-browser is distributed in the hope that it will be
  useful, but WITHOUT ANY WARRANTY; without even the implied
  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  PURPOSE.  See the GNU Affero General Public License for
  more details.

  You should have received a copy of the GNU Affero General
  Public License along with service-browser.
  If not, see <http://www.gnu.org/licenses/>.
*/

var util = require('util');
var path = require('path');
var http = require('http');
var express = require('express');
var sockjs = require('sockjs');
var mdns = require('mdns2');
var _ = require('lodash');
var level = require('level');
var db = level(__dirname + '/db/services.db', { encoding: 'json' });

var config = require('./config.js');

var clients = [];

// send message to all clients
function broadcast(msg) {
    if(typeof(msg) != 'string') {
        msg = JSON.stringify(msg);
    }
    var i, client;
    for(i=0; i < clients.length; i++) {
        client = clients[i];
        client.write(msg);
    }
}

function send_all_services(client) {
  //broadcat all the services in the db to the client
  var serviceStream = db.createValueStream({start: 'service!', end: 'service~'});
  serviceStream.on('data',function(service){
    client.write(JSON.stringify({
      type: 'service',
      action: 'up',
      service: service
    }));
  });
}

//could go into a module
function key_hash(str) {
  var hash;
  for (var i=0; i < str.length; i += 1) {
    var chr = str.charCodeAt(i);
    hash = ((hash << 5) - hash) + chr;
    hash |= 0;
  }
  hash = Math.abs(hash).toString(16);
  return hash;
};

function construct_key(service) {
  var sufix = key_hash(service.unique);
  return 'service!' + sufix;
};

function already_in_db(service, cbmesh) {
  // Check to see if the service is already in the db
  db.get(construct_key(service), function (err, value) {
    var found;
    if (err) {
      found = false;
    } else {
      found = true;
    }
    cbmesh(found, value, err);
  });
};

// only allow services of the types we're interested in
function filter(service) {
    return service;
}

function createUnique(service) {
    // mdns does this great thing where they'll automatically resolve
    // nameing conflicts so this is indeed a unique identifier even without hostname
    return service.replyDomain + service.type.protocol + '.' + encodeURIComponent(service.type.name) + '.' + service.name.replace(/ /, '');
}

var advert = mdns.createAdvertisement('_http._tcp', config.port, {txtRecord: {
    name: 'Service Browser',
    description: "Browse services on People's Open Network",
    scope: 'peoplesopen.net',
    type: 'service-browser'
}});

// TODO need to listen for all service types
// dev note: try udisks-ssh instead of http
var browser = mdns.createBrowser(mdns.makeServiceType('http', 'tcp'));
browser.on('serviceUp', function(service) {
    // ignore other service browsers
    if(service.txtRecord && service.txtRecord.scope && service.txtRecord.type && (service.txtRecord.scope == 'peoplesopen.net') && (service.txtRecord.type == 'service-browser')) {
        console.log("ignoring other service browser on " + util.inspect(service.addresses));
    }

    service.unique = createUnique(service);
    //add service to the db
    already_in_db(service, function(found) {
      if(!found && filter(service)) {
        var newkey = construct_key(service);
        db.put(newkey, service, function () {
          console.log('remembering service ' + newkey);
          broadcast({
              type: 'service',
              action: 'up',
              service: service
          });
        });
      }
    });
});

browser.on('serviceDown', function(service) {
    console.log('service coming down: ');
    //console.log(service);
    service.unique = createUnique(service);
    if(filter(service)) {
        broadcast({
            type: 'service',
            action: 'down',
            service: service
        });
        db.del(construct_key(service), function () {
          console.log('forggeting service ' + construct_key(service));
        })
    }
});

browser.on('error', function (err) {
  console.log('mdns error: ' + err);
});

browser.start();

var websocket = sockjs.createServer();
websocket.on('connection', function(conn) {
    clients.push(conn);
    console.log("New client connected ("+clients.length+" total)");
    send_all_services(conn);
    conn.on('data', function(message) {
        // nothing to do here yet
    });
    conn.on('close', function() {
        var i = clients.indexOf(conn);
        if(i && (i >= 0)) {
            clients.splice(i, 1); // remove the client
        }
        console.log("Client disconnected ("+clients.length+" total)");
    });
});

var app = express();
var server = http.createServer(app);

websocket.installHandlers(server, {prefix:'/websocket'});

// serve static content from the /www dir at /static
app.use('/static/', express.static(path.join(__dirname, 'www')));

// for parsing post request
app.use(express.bodyParser());

app.get('/nodes/:id', function(req, res){
    if(!req.params.id) {
        error(res, "node id must be specified in request");
        return;
    }
    console.log("retrieving node with id: " + req.params.id);

});

console.log('Listening on '+config.hostname+':'+config.port);
server.listen(config.port, config.hostname);
<|endoftext|>


<commit_before>'use strict';

var Keywordspec = require('./keywordspec');

var newline = /\r?\n|\r/g,
  escapeRegExp = function (str) {
    // source: https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions
    return str.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
  },
  trim = function (str) {
    return str.replace(/^\s+|\s+$/g, '');
  },
  trimQuotes = function (str) {
    return str.replace(/^['"]|['"]$/g, '');
  },
  isQuote = function (chr) {
    return /['"]/.test(chr);
  },
  groupParams = function (result, part) {
    if (result.length > 0) {
      var last = result[result.length - 1],
        firstChar = last[0],
        lastChar = last[last.length - 1];

      if (isQuote(firstChar) && (!isQuote(lastChar) || last[last.length - 2] === '\\')) {
        // merge with previous
        result[result.length - 1] += ',' + part;
      } else {
        result.push(part);
      }
    } else {
      result.push(part);
    }

    return result;
  };

/**
 * Constructor
 * @param Object keywordSpec An object with keywords as keys and parameter indexes as values
 */
function Parser (keywordSpec) {
  // All keywords available from the 'node-gettext' lib and tested with this parser.
  keywordSpec = keywordSpec || new Keywordspec(
    ["gettext", "_",
     //"dgettext:2", "_d:2",
     "ngettext:1,2", "_n:1,2",
     //"dngettext:2,3", "_dn:2,3",
		 "pgettext:1c,2", "_p:1c,2",
     //"dpgettext:2c,3", "_dp:2c,3",
     "npgettext:1c,2,3", "_np:1c,2,3",
     //"dnpgettext:2c,3,4", "_dnp:2c,3,4"
    ]);

  if (typeof keywordSpec !== 'object') {
    throw 'Invalid keyword spec';
  }

  this.keywordSpec = keywordSpec;
  this.expressionPattern = new RegExp([
    '{{ *',
    '(' + Object.keys(keywordSpec).map(escapeRegExp).join('|') + ')',
    '\\(',
    '([\\s\\S]*?)',
    '\\)',
    ' *}}'
  ].join(''), 'g');
}

/**
 * Given a Swig template string returns the list of i18n strings.
 *
 * @param String template The content of a Swig template.
 * @return Object The list of translatable strings, the line(s) on which each appears and an optional plural form.
 */
Parser.prototype.parse = function (template) {
  var results = [],
    match,
    keyword,
    params,
    msgid;

  function findResult (result) {
    return results.find(function(results) {
      return results.msgid === result.msgid && results.msgctxt === result.msgctxt;
    });
  }

  while ((match = this.expressionPattern.exec(template)) !== null) {
    keyword = match[1];
    params = match[2].split(',').reduce(groupParams, []).map(trim).map(trimQuotes);

    // Parse message.
    var msgidIndex = this.keywordSpec[keyword].msgid;
    msgid = params[msgidIndex];

    // Prepare the result object.
    var result = {
      msgid: msgid,
      line: []
    };

    // Parse message lines.
    result.line.push(template.substr(0, match.index).split(newline).length);

    // Parse plural form.
    if(this.keywordSpec[keyword].msgid_plural !== undefined) {
      var pluralIndex = this.keywordSpec[keyword].msgid_plural;
      result.plural = result.plural || params[pluralIndex];
    }

    // Parse context.
    if(this.keywordSpec[keyword].msgctxt !== undefined) {
      var contextIndex = this.keywordSpec[keyword].msgctxt;
      result.msgctxt = result.msgctxt || params[contextIndex];
    }

    // Add result to results.
    var foundResult = findResult(result);

    if(foundResult) {
      foundResult.line.push(result.line[0]);
    } else {
      results.push(result);
    }
  }

  return results;
};

module.exports = Parser;
<commit_msg>Added a more compatible function for finding a result.<commit_after>'use strict';

var Keywordspec = require('./keywordspec');

var newline = /\r?\n|\r/g,
  escapeRegExp = function (str) {
    // source: https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions
    return str.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
  },
  trim = function (str) {
    return str.replace(/^\s+|\s+$/g, '');
  },
  trimQuotes = function (str) {
    return str.replace(/^['"]|['"]$/g, '');
  },
  isQuote = function (chr) {
    return /['"]/.test(chr);
  },
  groupParams = function (result, part) {
    if (result.length > 0) {
      var last = result[result.length - 1],
        firstChar = last[0],
        lastChar = last[last.length - 1];

      if (isQuote(firstChar) && (!isQuote(lastChar) || last[last.length - 2] === '\\')) {
        // merge with previous
        result[result.length - 1] += ',' + part;
      } else {
        result.push(part);
      }
    } else {
      result.push(part);
    }

    return result;
  };

/**
 * Constructor
 * @param Object keywordSpec An object with keywords as keys and parameter indexes as values
 */
function Parser (keywordSpec) {
  // All keywords available from the 'node-gettext' lib and tested with this parser.
  keywordSpec = keywordSpec || new Keywordspec(
    ["gettext", "_",
     //"dgettext:2", "_d:2",
     "ngettext:1,2", "_n:1,2",
     //"dngettext:2,3", "_dn:2,3",
		 "pgettext:1c,2", "_p:1c,2",
     //"dpgettext:2c,3", "_dp:2c,3",
     "npgettext:1c,2,3", "_np:1c,2,3",
     //"dnpgettext:2c,3,4", "_dnp:2c,3,4"
    ]);

  if (typeof keywordSpec !== 'object') {
    throw 'Invalid keyword spec';
  }

  this.keywordSpec = keywordSpec;
  this.expressionPattern = new RegExp([
    '{{ *',
    '(' + Object.keys(keywordSpec).map(escapeRegExp).join('|') + ')',
    '\\(',
    '([\\s\\S]*?)',
    '\\)',
    ' *}}'
  ].join(''), 'g');
}

/**
 * Given a Swig template string returns the list of i18n strings.
 *
 * @param String template The content of a Swig template.
 * @return Object The list of translatable strings, the line(s) on which each appears and an optional plural form.
 */
Parser.prototype.parse = function (template) {
  var results = [],
    match,
    keyword,
    params,
    msgid;

  function findResult (result) {
    for(var i in results) {
      if(results[i].msgid === result.msgid && results[i].msgctxt === result.msgctxt) {
        return results[i];
      }
    }

    return undefined;
  }

  while ((match = this.expressionPattern.exec(template)) !== null) {
    keyword = match[1];
    params = match[2].split(',').reduce(groupParams, []).map(trim).map(trimQuotes);

    // Parse message.
    var msgidIndex = this.keywordSpec[keyword].msgid;
    msgid = params[msgidIndex];

    // Prepare the result object.
    var result = {
      msgid: msgid,
      line: []
    };

    // Parse message lines.
    result.line.push(template.substr(0, match.index).split(newline).length);

    // Parse plural form.
    if(this.keywordSpec[keyword].msgid_plural !== undefined) {
      var pluralIndex = this.keywordSpec[keyword].msgid_plural;
      result.plural = result.plural || params[pluralIndex];
    }

    // Parse context.
    if(this.keywordSpec[keyword].msgctxt !== undefined) {
      var contextIndex = this.keywordSpec[keyword].msgctxt;
      result.msgctxt = result.msgctxt || params[contextIndex];
    }

    // Add result to results.
    var foundResult = findResult(result);

    if(foundResult) {
      foundResult.line.push(result.line[0]);
    } else {
      results.push(result);
    }
  }

  return results;
};

module.exports = Parser;
<|endoftext|>


<commit_before>'use strict';
var extend = require('extend');

/**
 * Default options
 * @const
 * @type {Object}
 */
var DEFAULTS = {
  keyValDelim: ':',
  propsDelim: ',',
  useSpaceBeforeKeyValDelim: false,
  useSpaceAfterKeyValDelim: true,
  useSpaceBeforePropsDelim: false,
  useSpaceAfterPropsDelim: true,
  useAlwaysQuotesForStrings: false
};

/**
 * @const
 * @type {RegExp}
 */
var rDoubleQuotesFilter = /^"(.*)"$/;

/**
 * Removes extra spaces
 * @private
 * @param {String} str
 * @returns {String}
 */
function clearString(str, keyValDelim, propsDelim) {
  var rPropsFilter =  new RegExp('\\s*' + propsDelim + '\\s*', 'g');
  var rKeyValFilter = new RegExp('\\s*' + keyValDelim + '\\s*', 'g');

  return str.replace(rKeyValFilter, keyValDelim).replace(rPropsFilter, propsDelim);
}

/**
 * Splits a string
 * @private
 * @param {String} str
 * @param {String} delimiter
 * @param {Boolean} useOnce Finish after the first match
 * @returns {Array}
 */
function splitString(str, delimiter, useOnce) {
  var i = 0;
  var len = str.length;
  var res = [];
  var isBetweenQuotes = false;
  var char;

  // Find delimiters and cut the string
  while (i !== len) {
    char = str.charAt(i);
    i++;

    // Do not split if the delimiter is between quotes
    if (char === '\"') {
      isBetweenQuotes = !isBetweenQuotes;
    } else if (char === delimiter && !isBetweenQuotes) {
      res.push(str.substring(0, i - 1));
      str = str.substring(i);

      if (useOnce) {
        break;
      }

      len = str.length;
      i = 0;
    }
  }

  // Add the last part
  res.push(str);

  return res;
}

/**
 * Checks if a stringified value is a boolean
 * @private
 * @param {String} value
 * @returns {Boolean}
 */
function isBoolean(value) {
  return value === 'true' || value === 'false';
}

/**
 * Checks if a stringified value is a number
 * @private
 * @param {String} value
 * @returns {Boolean}
 */
function isNumber(value) {
  return !isNaN(value);
}

/**
 * Checks if a stringified value is an undefined
 * @private
 * @param {String} value
 * @returns {Boolean}
 */
function isUndefined(value) {
  return value === 'undefined';
}

/**
 * Checks if a stringified value is a null
 * @private
 * @param {String} value
 * @returns {Boolean}
 */
function isNull(value) {
  return value === 'null';
}

/**
 * Parses a stringified number
 * @private
 * @param {String} value
 * @returns {Number}
 */
function parseNumber(value) {
  // Float
  if (value.indexOf('.') !== -1) {
    return parseFloat(value);
  }

  // If the input string begins with "0x" or "0X", radix is 16
  if (value.length > 2 && (value.indexOf('0x') === 0 || value.indexOf('0X') === 0)) {
    return parseInt(value, 16);
  }

  return parseInt(value, 10);
}

/**
 * Parses a stringified value to the expected type
 * @private
 * @param {String} value
 * @returns {Number|Boolean|String|null|undefined}
 */
function parseValue(value) {
  if (isNull(value)) {
    return null;
  }

  if (isUndefined(value)) {
    return undefined;
  }

  if (isBoolean(value)) {
    return value === 'true';
  }

  if (isNumber(value)) {
    return parseNumber(value);
  }

  // Remove double quotes
  return value.replace(rDoubleQuotesFilter, '$1');
}

/**
 * Parses stringified options
 * @public
 * @param {String} str Stringified options for parsing
 * @param {Object} opts Options
 * @returns {Object}
 */
module.exports.parse = function(str, opts) {
  opts = extend({}, DEFAULTS, opts);

  var keyValDelim = opts.keyValDelim;
  var propsDelim = opts.propsDelim;
  var result = {};
  var i;
  var len;
  var prop;
  var props;

  // Remove extra spaces
  str = clearString(str, keyValDelim, propsDelim);

  // Get an array of stringified properties
  props = splitString(str, propsDelim);

  // Convert stringified properties to the object with parsed values
  for (i = 0, len = props.length; i < len; i++) {
    prop = splitString(props[i], keyValDelim, true);

    if (prop.length === 2) {
      result[prop[0]] = parseValue(prop[1]);
    }
  }

  return result;
};
<commit_msg>Add '.stringify(obj, opts)'<commit_after>'use strict';
var extend = require('extend');

/**
 * Default options
 * @const
 * @type {Object}
 */
var DEFAULTS = {
  keyValDelim: ':',
  propsDelim: ',',
  useSpaceBeforeKeyValDelim: false,
  useSpaceAfterKeyValDelim: false,
  useSpaceBeforePropsDelim: false,
  useSpaceAfterPropsDelim: false,
  useAlwaysQuotesForStrings: false
};

/**
 * @const
 * @type {RegExp}
 */
var rDoubleQuotesFilter = /^"(.*)"$/;

/**
 * Removes extra spaces
 * @private
 * @param {String} str
 * @returns {String}
 */
function clearString(str, keyValDelim, propsDelim) {
  var rPropsFilter =  new RegExp('\\s*' + propsDelim + '\\s*', 'g');
  var rKeyValFilter = new RegExp('\\s*' + keyValDelim + '\\s*', 'g');

  return str.replace(rKeyValFilter, keyValDelim).replace(rPropsFilter, propsDelim);
}

/**
 * Splits a string
 * @private
 * @param {String} str
 * @param {String} delimiter
 * @param {Boolean} useOnce Finish after the first match
 * @returns {Array}
 */
function splitString(str, delimiter, useOnce) {
  var i = 0;
  var len = str.length;
  var res = [];
  var isBetweenQuotes = false;
  var char;

  // Find delimiters and cut the string
  while (i !== len) {
    char = str.charAt(i);
    i++;

    // Do not split if the delimiter is between quotes
    if (char === '\"') {
      isBetweenQuotes = !isBetweenQuotes;
    } else if (char === delimiter && !isBetweenQuotes) {
      res.push(str.substring(0, i - 1));
      str = str.substring(i);

      if (useOnce) {
        break;
      }

      len = str.length;
      i = 0;
    }
  }

  // Add the last part
  res.push(str);

  return res;
}

/**
 * Checks if a stringified value is a boolean
 * @private
 * @param {String} value
 * @returns {Boolean}
 */
function isBoolean(value) {
  return value === 'true' || value === 'false';
}

/**
 * Checks if a stringified value is a number
 * @private
 * @param {String} value
 * @returns {Boolean}
 */
function isNumber(value) {
  return !isNaN(value);
}

/**
 * Checks if a stringified value is an undefined
 * @private
 * @param {String} value
 * @returns {Boolean}
 */
function isUndefined(value) {
  return value === 'undefined';
}

/**
 * Checks if a stringified value is a null
 * @private
 * @param {String} value
 * @returns {Boolean}
 */
function isNull(value) {
  return value === 'null';
}

/**
 * Checks if a stringified value is unsafe
 * @private
 * @param {String} value
 * @param {Object} opts
 * @returns {Boolean}
 */
function isUnsafe(value, opts) {
  return value.search(rDoubleQuotesFilter) !== 0 && value.indexOf(opts.propsDelim) !== -1;
}

/**
 * Parses a stringified number
 * @private
 * @param {String} value
 * @returns {Number}
 */
function parseNumber(value) {
  // Float
  if (value.indexOf('.') !== -1) {
    return parseFloat(value);
  }

  // If the input string begins with "0x" or "0X", radix is 16
  if (value.length > 2 && (value.indexOf('0x') === 0 || value.indexOf('0X') === 0)) {
    return parseInt(value, 16);
  }

  return parseInt(value, 10);
}

/**
 * Parses a stringified value to the expected type
 * @private
 * @param {String} value
 * @returns {Number|Boolean|String|null|undefined}
 */
function parseValue(value) {
  if (isNull(value)) {
    return null;
  }

  if (isUndefined(value)) {
    return undefined;
  }

  if (isBoolean(value)) {
    return value === 'true';
  }

  if (isNumber(value)) {
    return parseNumber(value);
  }

  // Remove double quotes
  return value.replace(rDoubleQuotesFilter, '$1');
}

/**
 * Stringifies a value
 * @private
 * @param {*} value
 * @param {Object} opts
 * @returns {String}
 */
function stringifyValue(value, opts) {
  if (typeof value === 'string' || value instanceof String) {
    if (
      isUnsafe(value, opts) ||
      isNull(value) ||
      isUndefined(value) ||
      isBoolean(value) ||
      isNumber(value)
    ) {
      return '\"' + value + '\"';
    }
  }

  // Convert to the string type, always
  return '' + value;
}

/**
 * Stringifies a property
 * @private
 * @param {String} key
 * @param {*} value
 * @param {Object} opts
 * @returns {String}
 */
function stringifyProp(key, value, opts) {
  return [
    key,
    opts.useSpaceBeforeKeyValDelim ? ' ' : '',
    opts.keyValDelim,
    opts.useSpaceAfterKeyValDelim ? ' ' : '',
    stringifyValue(value, opts)
  ].join('');
}

/**
 * Join an array of processed properties
 * @private
 * @param {Array} props
 * @param {Object} opts
 * @returns {String}
 */
function joinProps(props, opts) {
  var delimiter = [
    opts.useSpaceBeforePropsDelim ? ' ' : '',
    opts.propsDelim,
    opts.useSpaceAfterPropsDelim ? ' ' : ''
  ].join('');

  return props.join(delimiter);
}

/**
 * Parses stringified options
 * @public
 * @param {String} str Stringified options for parsing
 * @param {Object} opts
 * @returns {Object}
 */
module.exports.parse = function(str, opts) {
  opts = extend({}, DEFAULTS, opts);

  var keyValDelim = opts.keyValDelim;
  var propsDelim = opts.propsDelim;
  var result = {};
  var i;
  var len;
  var prop;
  var props;

  // Remove extra spaces
  str = clearString(str, keyValDelim, propsDelim);

  // Get an array of stringified properties
  props = splitString(str, propsDelim);

  // Convert stringified properties to the object with parsed values
  for (i = 0, len = props.length; i < len; i++) {
    prop = splitString(props[i], keyValDelim, true);

    if (prop.length === 2) {
      result[prop[0]] = parseValue(prop[1]);
    }
  }

  return result;
};

/**
 * Stringifies an object with options
 * @public
 * @param {Object} obj
 * @param {Object} opts
 * @returns {String}
 */
module.exports.stringify = function(obj, opts) {
  opts = extend({}, DEFAULTS, opts);

  var res = [];
  var key;

  for (key in obj) {
    res.push(stringifyProp(key, obj[key], opts));
  }

  return joinProps(res, opts);
};
<|endoftext|>