<commit_before>const bitcoin = require('bitcoinjs-lib')
const _ = require('lodash')
const utils = require('./lib/utils.js')

let SERVICES = ['insightapi', 'blockcypher', 'blockr']

let BlockchainAnchor = function (anchorOptions) {
  // in case 'new' was omitted
  if (!(this instanceof BlockchainAnchor)) {
    return new BlockchainAnchor(anchorOptions)
  }

  // set default btc network to mainnet
  let btcUseTestnet = false
  let btcNetwork = bitcoin.networks.bitcoin
  // when btcUseTestnet set to true, testnet is to be used, otherwise defaults to mainnet
  if (anchorOptions && anchorOptions.btcUseTestnet !== undefined) {
    btcUseTestnet = anchorOptions.btcUseTestnet
    btcNetwork = anchorOptions.btcUseTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin
  }

  // set default sevice selection to 'any', allowing for retrying across services
  let service = 'any'
  // check for supplied service name, if not valid, throw error
  if (anchorOptions && anchorOptions.service !== undefined) {
    let serviceLowercase = anchorOptions.service.toLowerCase()
    if (SERVICES.indexOf(serviceLowercase) > -1) {
      service = serviceLowercase
    } else {
      throw new Error(`Unknown service : ${anchorOptions.service}`)
    }
  }

  let insightApiBase = `https://${(btcUseTestnet ? 'test-' : '')}insight.bitpay.com/api`
  // if insightApiBase is supplied, override default bitpay instance
  // be sure that the btcUseTestnet setting matches the configuration of the custom insight-api instance...
  // if the instance at insightApiBase is testnet, you must set btcUseTestnet to true, otherwise methods will fail
  if (anchorOptions && anchorOptions.insightApiBase !== undefined) {
    insightApiBase = anchorOptions.insightApiBase
  }

  let blockcypherToken = null
  // check for blockcypherToken
  if (anchorOptions && anchorOptions.blockcypherToken !== undefined) {
    blockcypherToken = anchorOptions.blockcypherToken
  } else {
    // blockcypher token was not supplied, so remove from available services
    _.remove(SERVICES, (x) => {
      return x === 'blockcypher'
    })
  }

  /// /////////////////////////////////////////
  // PUBLIC functions
  /// /////////////////////////////////////////

  this.btcOpReturnAsync = async (privateKeyWIF, hexData, feeTotalSat) => {
    if (!privateKeyWIF) throw new Error('No privateKeyWIF was provided')
    let keyPair, address
    try {
      // get keyPair for the supplied privateKeyWIF
      keyPair = bitcoin.ECPair.fromWIF(privateKeyWIF, btcNetwork)
      // derive target address from the keyPair
      address = keyPair.getAddress()
    } catch (error) {
      throw new Error(`Bad privateKeyWIF : ${error.message}`)
    }

    let txResults
    if (service !== 'any') {
      // a specific service was chosen, attempt once with that service
      if (service === 'blockcypher' && !blockcypherToken) throw new Error('Requested blockcypher service, but no blockcypherToken supplied')
      txResults = await _embedAsync(service, hexData, address, keyPair, feeTotalSat)
    } else { // use the first service option, continue with the next option upon failure until all have been attempted
      let errors = []
      let success = false
      for (let index in SERVICES) {
        try {
          txResults = await _embedAsync(SERVICES[index], hexData, address, keyPair, feeTotalSat)
          success = true
          break
        } catch (error) {
          errors.push(error.message)
        }
      }
      // if none of the services returned successfully, throw error
      if (!success) throw new Error(errors)
    }
    return txResults
  }

  // BTC functions

  this.btcSplitOutputsAsync = async (privateKeyWIF, maxOutputs, feeTotalSat) => {
    if (!privateKeyWIF) throw new Error('No privateKeyWIF was provided')
    let keyPair, address
    try {
      // get keyPair for the supplied privateKeyWIF
      keyPair = bitcoin.ECPair.fromWIF(privateKeyWIF, btcNetwork)
      // derive target address from the keyPair
      address = keyPair.getAddress()
    } catch (error) {
      throw new Error(`Bad privateKeyWIF : ${error.message}`)
    }

    let txResults
    if (service !== 'any') {
      // a specific service was chosen, attempt once with that service
      if (service === 'blockcypher' && !blockcypherToken) throw new Error('Requested blockcypher service, but no blockcypherToken supplied')
      txResults = await _pushSplitOutputsTxAsync(service, maxOutputs, address, keyPair, feeTotalSat)
    } else { // use the first service option, continue with the next option upon failure until all have been attempted
      let errors = []
      let success = false
      for (let index in SERVICES) {
        try {
          txResults = await _pushSplitOutputsTxAsync(SERVICES[index], maxOutputs, address, keyPair, feeTotalSat)
          success = true
          break
        } catch (error) {
          errors.push(error.message)
        }
      }
      // if none of the services returned successfully, throw error
      if (!success) throw new Error(errors)
    }
    return txResults
  }

  this.btcConfirmOpReturnAsync = async (transactionId, expectedValue) => {
    let confirmed = false
    if (service !== 'any') {
      // a specific service was chosen, attempt once with that service
      confirmed = await _confirmOpReturnAsync(service, transactionId, expectedValue)
    } else { // use the first service option, continue with the next option upon failure until all have been attempted
      let errors = []
      let success = false
      for (let index in SERVICES) {
        try {
          confirmed = await _confirmOpReturnAsync(SERVICES[index], transactionId, expectedValue)
          success = true
          break
        } catch (error) {
          errors.push(error.message)
        }
      }
      // if none of the services returned successfully, throw error
      if (!success) throw new Error(errors)
    }
    return confirmed
  }

  this.btcConfirmBlockHeaderAsync = async (blockHeight, expectedValue) => {
    let confirmed = false
    if (service !== 'any') {
      // a specific service was chosen, attempt once with that service
      confirmed = await _confirmBTCBlockHeaderAsync(service, blockHeight, expectedValue)
    } else { // use the first service option, continue with the next option upon failure until all have been attempted
      let errors = []
      let success = false
      for (let index in SERVICES) {
        try {
          confirmed = await _confirmBTCBlockHeaderAsync(SERVICES[index], blockHeight, expectedValue)
          success = true
          break
        } catch (error) {
          errors.push(error.message)
        }
      }
      // if none of the services returned successfully, throw error
      if (!success) throw new Error(errors)
    }
    return confirmed
  }

  this.btcGetTxConfirmationCountAsync = async (transactionId) => {
    let count = 0
    if (service !== 'any') {
      // a specific service was chosen, attempt once with that service
      count = await _getBTCTransactionConfirmationCountAsync(service, transactionId)
    } else { // use the first service option, continue with the next option upon failure until all have been attempted
      let errors = []
      let success = false
      for (let index in SERVICES) {
        try {
          count = await _getBTCTransactionConfirmationCountAsync(SERVICES[index], transactionId)
          success = true
          break
        } catch (error) {
          errors.push(error.message)
        }
      }
      // if none of the services returned successfully, throw error
      if (!success) throw new Error(errors)
    }
    return count
  }

  this.btcGetBlockTxIdsAsync = async (blockHeight) => {
    let ids = null
    if (service !== 'any') {
      // a specific service was chosen, attempt once with that service
      ids = await _getBTCBlockTxIdsAsync(service, blockHeight)
    } else { // use the first service option, continue with the next option upon failure until all have been attempted
      let errors = []
      let success = false
      for (let index in SERVICES) {
        try {
          ids = await _getBTCBlockTxIdsAsync(SERVICES[index], blockHeight)
          success = true
          break
        } catch (error) {
          errors.push(error.message)
        }
      }
      // if none of the services returned successfully, throw error
      if (!success) throw new Error(errors)
    }
    return ids
  }

  // ETH functions

  this.ethConfirmDataAsync = async (transactionId, expectedValue) => {
    let confirmed = false
    if (service !== 'any') {
      // a specific service was chosen, attempt once with that service
      confirmed = await _confirmEthDataAsync(service, transactionId, expectedValue)
    } else { // use the first service option, continue with the next option upon failure until all have been attempted
      let errors = []
      let success = false
      for (let index in SERVICES) {
        try {
          confirmed = await _confirmEthDataAsync(SERVICES[index], transactionId, expectedValue)
          success = true
          break
        } catch (error) {
          errors.push(error.message)
        }
      }
      // if none of the services returned successfully, throw error
      if (!success) throw new Error(errors)
    }
    return confirmed
  }

  /// ///////////////////////////////////////
  //  Private Utility functions
  /// ///////////////////////////////////////

  async function _embedAsync (serviceName, hexData, address, keyPair, feeTotalSat) {
    // get an instance of the selected service
    let blockchainService = utils.getBlockchainService(serviceName)

    let serviceOptions = {}
    serviceOptions.btcUseTestnet = btcUseTestnet
    if (serviceName === 'insightapi') serviceOptions.insightApiBase = insightApiBase
    if (serviceName === 'blockcypher' && blockcypherToken) serviceOptions.blockcypherToken = blockcypherToken

    let unspentOutputs = await blockchainService.getUnspentOutputsAsync(address, serviceOptions)
    let spendableOutput = _.orderBy(unspentOutputs, 'amountSatoshi', 'desc')[0]

    if (!spendableOutput) throw new Error('No unspent outputs available, balance likely 0')
    if (spendableOutput.amountSatoshi < feeTotalSat) throw new Error('No outputs with sufficient funds available')

    let tx = new bitcoin.TransactionBuilder(btcNetwork)
    tx.addInput(spendableOutput.fromTxHash, spendableOutput.outputIndex)

    let buffer = Buffer.from(hexData, 'hex')
    var dataScript = bitcoin.script.nullData.output.encode(buffer)
    tx.addOutput(dataScript, 0)

    let spendableAmountSatoshi = spendableOutput.amountSatoshi
    let returnAmountSatoshi = spendableAmountSatoshi - feeTotalSat
    tx.addOutput(address, returnAmountSatoshi)

    tx.sign(0, keyPair)

    let transactionHex = tx.build().toHex()

    let txResults = await blockchainService.pushTransactionAsync(transactionHex, serviceOptions)
    return txResults
  }

  async function _pushSplitOutputsTxAsync (serviceName, maxOutputs, address, keyPair, feeTotalSat) {
    // get an instacnce of the selected service
    let blockchainService = utils.getBlockchainService(serviceName)

    let serviceOptions = {}
    serviceOptions.btcUseTestnet = btcUseTestnet
    if (serviceName === 'insightapi') serviceOptions.insightApiBase = insightApiBase
    if (serviceName === 'blockcypher' && blockcypherToken) serviceOptions.blockcypherToken = blockcypherToken

    let unspentOutputs = await blockchainService.getUnspentOutputsAsync(address, serviceOptions)

    let newOutputCount = maxOutputs
    let totalBalanceSatoshi = _.sumBy(unspentOutputs, (x) => { return x.amountSatoshi }) // value of all unspent outputs
    let workingBalanceSatoshi = totalBalanceSatoshi - feeTotalSat // deduct the fee, the remainder is to be divided amongst the outputs
    let perOutputAmountSatoshi = _.floor(workingBalanceSatoshi / newOutputCount) // amount for each output
    // ensure the we dont split the outputs too much, leaving the per output balance too low, reduce output count if needed
    while (perOutputAmountSatoshi < 10000) {
      if (--newOutputCount < 1) throw new Error('Not enough funds to complete transaction')
      perOutputAmountSatoshi = workingBalanceSatoshi / newOutputCount
    }

    let tx = new bitcoin.TransactionBuilder(btcNetwork)

    _(unspentOutputs).forEach((spendableOutput) => {
      tx.addInput(spendableOutput.fromTxHash, spendableOutput.outputIndex)
    })

    for (let x = 0; x < newOutputCount; x++) {
      tx.addOutput(address, perOutputAmountSatoshi)
    }

    for (let x = 0; x < tx.inputs.length; x++) {
      tx.sign(x, keyPair)
    }

    let transactionHex = tx.build().toHex()

    let txResults = await blockchainService.pushTransactionAsync(transactionHex, serviceOptions)
    return txResults
  }

  async function _confirmOpReturnAsync (serviceName, transactionId, expectedValue) {
    // get an instance of the selected service
    let blockchainService = utils.getBlockchainService(serviceName)

    let serviceOptions = {}
    serviceOptions.btcUseTestnet = btcUseTestnet
    if (serviceName === 'insightapi') serviceOptions.insightApiBase = insightApiBase
    if (serviceName === 'blockcypher' && blockcypherToken) serviceOptions.blockcypherToken = blockcypherToken

    let result = await blockchainService.confirmOpReturnAsync(transactionId, expectedValue, serviceOptions)
    return result
  }

  async function _confirmEthDataAsync (serviceName, transactionId, expectedValue) {
    // get an instance of the selected service
    let blockchainService = utils.getBlockchainService(serviceName)

    let serviceOptions = {}
    serviceOptions.btcUseTestnet = btcUseTestnet
    if (serviceName === 'insightapi') serviceOptions.insightApiBase = insightApiBase
    if (serviceName === 'blockcypher' && blockcypherToken) serviceOptions.blockcypherToken = blockcypherToken

    let result = await blockchainService.confirmEthDataAsync(transactionId, expectedValue, serviceOptions)
    return result
  }

  async function _confirmBTCBlockHeaderAsync (serviceName, blockHeight, expectedValue) {
    // get an instance of the selected service
    let blockchainService = utils.getBlockchainService(serviceName)

    let serviceOptions = {}
    serviceOptions.btcUseTestnet = btcUseTestnet
    if (serviceName === 'insightapi') serviceOptions.insightApiBase = insightApiBase
    if (serviceName === 'blockcypher' && blockcypherToken) serviceOptions.blockcypherToken = blockcypherToken

    let result = await blockchainService.confirmBTCBlockHeaderAsync(blockHeight, expectedValue, serviceOptions)
    return result
  }

  async function _getBTCTransactionConfirmationCountAsync (serviceName, transactionId) {
    // get an instance of the selected service
    let blockchainService = utils.getBlockchainService(serviceName)

    let serviceOptions = {}
    serviceOptions.btcUseTestnet = btcUseTestnet
    if (serviceName === 'insightapi') serviceOptions.insightApiBase = insightApiBase
    if (serviceName === 'blockcypher' && blockcypherToken) serviceOptions.blockcypherToken = blockcypherToken

    let result = await blockchainService.getBTCTransactionConfirmationCountAsync(transactionId, serviceOptions)
    return result
  }

  async function _getBTCBlockTxIdsAsync (serviceName, blockHeight) {
    // get an instance of the selected service
    let blockchainService = utils.getBlockchainService(serviceName)

    let serviceOptions = {}
    serviceOptions.btcUseTestnet = btcUseTestnet
    if (serviceName === 'insightapi') serviceOptions.insightApiBase = insightApiBase
    if (serviceName === 'blockcypher' && blockcypherToken) serviceOptions.blockcypherToken = blockcypherToken

    let result = await blockchainService.getBTCBlockTxIdsAsync(blockHeight, serviceOptions)
    return result
  }
}

module.exports = BlockchainAnchor
module.exports.getInstance = (anchorOptions) => {
  return new BlockchainAnchor(anchorOptions)
}
<commit_msg>Updates return values<commit_after>const bitcoin = require('bitcoinjs-lib')
const _ = require('lodash')
const utils = require('./lib/utils.js')

let SERVICES = ['insightapi', 'blockcypher', 'blockr']

let BlockchainAnchor = function (anchorOptions) {
  // in case 'new' was omitted
  if (!(this instanceof BlockchainAnchor)) {
    return new BlockchainAnchor(anchorOptions)
  }

  // set default btc network to mainnet
  let btcUseTestnet = false
  let btcNetwork = bitcoin.networks.bitcoin
  // when btcUseTestnet set to true, testnet is to be used, otherwise defaults to mainnet
  if (anchorOptions && anchorOptions.btcUseTestnet !== undefined) {
    btcUseTestnet = anchorOptions.btcUseTestnet
    btcNetwork = anchorOptions.btcUseTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin
  }

  // set default sevice selection to 'any', allowing for retrying across services
  let service = 'any'
  // check for supplied service name, if not valid, throw error
  if (anchorOptions && anchorOptions.service !== undefined) {
    let serviceLowercase = anchorOptions.service.toLowerCase()
    if (SERVICES.indexOf(serviceLowercase) > -1) {
      service = serviceLowercase
    } else {
      throw new Error(`Unknown service : ${anchorOptions.service}`)
    }
  }

  let insightApiBase = `https://${(btcUseTestnet ? 'test-' : '')}insight.bitpay.com/api`
  // if insightApiBase is supplied, override default bitpay instance
  // be sure that the btcUseTestnet setting matches the configuration of the custom insight-api instance...
  // if the instance at insightApiBase is testnet, you must set btcUseTestnet to true, otherwise methods will fail
  if (anchorOptions && anchorOptions.insightApiBase !== undefined) {
    insightApiBase = anchorOptions.insightApiBase
  }

  let blockcypherToken = null
  // check for blockcypherToken
  if (anchorOptions && anchorOptions.blockcypherToken !== undefined) {
    blockcypherToken = anchorOptions.blockcypherToken
  } else {
    // blockcypher token was not supplied, so remove from available services
    _.remove(SERVICES, (x) => {
      return x === 'blockcypher'
    })
  }

  /// /////////////////////////////////////////
  // PUBLIC functions
  /// /////////////////////////////////////////

  this.btcOpReturnAsync = async (privateKeyWIF, hexData, feeTotalSat) => {
    if (!privateKeyWIF) throw new Error('No privateKeyWIF was provided')
    let keyPair, address
    try {
      // get keyPair for the supplied privateKeyWIF
      keyPair = bitcoin.ECPair.fromWIF(privateKeyWIF, btcNetwork)
      // derive target address from the keyPair
      address = keyPair.getAddress()
    } catch (error) {
      throw new Error(`Bad privateKeyWIF : ${error.message}`)
    }

    let txResults
    if (service !== 'any') {
      // a specific service was chosen, attempt once with that service
      if (service === 'blockcypher' && !blockcypherToken) throw new Error('Requested blockcypher service, but no blockcypherToken supplied')
      txResults = await _embedAsync(service, hexData, address, keyPair, feeTotalSat)
    } else { // use the first service option, continue with the next option upon failure until all have been attempted
      let errors = []
      let success = false
      for (let index in SERVICES) {
        try {
          txResults = await _embedAsync(SERVICES[index], hexData, address, keyPair, feeTotalSat)
          success = true
          break
        } catch (error) {
          errors.push(error.message)
        }
      }
      // if none of the services returned successfully, throw error
      if (!success) throw new Error(errors)
    }
    return txResults
  }

  // BTC functions

  this.btcSplitOutputsAsync = async (privateKeyWIF, maxOutputs, feeTotalSat) => {
    if (!privateKeyWIF) throw new Error('No privateKeyWIF was provided')
    let keyPair, address
    try {
      // get keyPair for the supplied privateKeyWIF
      keyPair = bitcoin.ECPair.fromWIF(privateKeyWIF, btcNetwork)
      // derive target address from the keyPair
      address = keyPair.getAddress()
    } catch (error) {
      throw new Error(`Bad privateKeyWIF : ${error.message}`)
    }

    let txResults
    if (service !== 'any') {
      // a specific service was chosen, attempt once with that service
      if (service === 'blockcypher' && !blockcypherToken) throw new Error('Requested blockcypher service, but no blockcypherToken supplied')
      txResults = await _pushSplitOutputsTxAsync(service, maxOutputs, address, keyPair, feeTotalSat)
    } else { // use the first service option, continue with the next option upon failure until all have been attempted
      let errors = []
      let success = false
      for (let index in SERVICES) {
        try {
          txResults = await _pushSplitOutputsTxAsync(SERVICES[index], maxOutputs, address, keyPair, feeTotalSat)
          success = true
          break
        } catch (error) {
          errors.push(error.message)
        }
      }
      // if none of the services returned successfully, throw error
      if (!success) throw new Error(errors)
    }
    return txResults
  }

  this.btcConfirmOpReturnAsync = async (transactionId, expectedValue) => {
    let confirmed = false
    if (service !== 'any') {
      // a specific service was chosen, attempt once with that service
      confirmed = await _confirmOpReturnAsync(service, transactionId, expectedValue)
    } else { // use the first service option, continue with the next option upon failure until all have been attempted
      let errors = []
      let success = false
      for (let index in SERVICES) {
        try {
          confirmed = await _confirmOpReturnAsync(SERVICES[index], transactionId, expectedValue)
          success = true
          break
        } catch (error) {
          errors.push(error.message)
        }
      }
      // if none of the services returned successfully, throw error
      if (!success) throw new Error(errors)
    }
    return confirmed
  }

  this.btcConfirmBlockHeaderAsync = async (blockHeight, expectedValue) => {
    let confirmed = false
    if (service !== 'any') {
      // a specific service was chosen, attempt once with that service
      confirmed = await _confirmBTCBlockHeaderAsync(service, blockHeight, expectedValue)
    } else { // use the first service option, continue with the next option upon failure until all have been attempted
      let errors = []
      let success = false
      for (let index in SERVICES) {
        try {
          confirmed = await _confirmBTCBlockHeaderAsync(SERVICES[index], blockHeight, expectedValue)
          success = true
          break
        } catch (error) {
          errors.push(error.message)
        }
      }
      // if none of the services returned successfully, throw error
      if (!success) throw new Error(errors)
    }
    return confirmed
  }

  this.btcGetTxConfirmationCountAsync = async (transactionId) => {
    let count = 0
    if (service !== 'any') {
      // a specific service was chosen, attempt once with that service
      count = await _getBTCTransactionConfirmationCountAsync(service, transactionId)
    } else { // use the first service option, continue with the next option upon failure until all have been attempted
      let errors = []
      let success = false
      for (let index in SERVICES) {
        try {
          count = await _getBTCTransactionConfirmationCountAsync(SERVICES[index], transactionId)
          success = true
          break
        } catch (error) {
          errors.push(error.message)
        }
      }
      // if none of the services returned successfully, throw error
      if (!success) throw new Error(errors)
    }
    return count
  }

  this.btcGetBlockTxIdsAsync = async (blockHeight) => {
    let ids = null
    if (service !== 'any') {
      // a specific service was chosen, attempt once with that service
      ids = await _getBTCBlockTxIdsAsync(service, blockHeight)
    } else { // use the first service option, continue with the next option upon failure until all have been attempted
      let errors = []
      let success = false
      for (let index in SERVICES) {
        try {
          ids = await _getBTCBlockTxIdsAsync(SERVICES[index], blockHeight)
          success = true
          break
        } catch (error) {
          errors.push(error.message)
        }
      }
      // if none of the services returned successfully, throw error
      if (!success) throw new Error(errors)
    }
    return ids
  }

  // ETH functions

  this.ethConfirmDataAsync = async (transactionId, expectedValue) => {
    let confirmed = false
    if (service !== 'any') {
      // a specific service was chosen, attempt once with that service
      confirmed = await _confirmEthDataAsync(service, transactionId, expectedValue)
    } else { // use the first service option, continue with the next option upon failure until all have been attempted
      let errors = []
      let success = false
      for (let index in SERVICES) {
        try {
          confirmed = await _confirmEthDataAsync(SERVICES[index], transactionId, expectedValue)
          success = true
          break
        } catch (error) {
          errors.push(error.message)
        }
      }
      // if none of the services returned successfully, throw error
      if (!success) throw new Error(errors)
    }
    return confirmed
  }

  /// ///////////////////////////////////////
  //  Private Utility functions
  /// ///////////////////////////////////////

  async function _embedAsync (serviceName, hexData, address, keyPair, feeTotalSat) {
    // get an instance of the selected service
    let blockchainService = utils.getBlockchainService(serviceName)

    let serviceOptions = {}
    serviceOptions.btcUseTestnet = btcUseTestnet
    if (serviceName === 'insightapi') serviceOptions.insightApiBase = insightApiBase
    if (serviceName === 'blockcypher' && blockcypherToken) serviceOptions.blockcypherToken = blockcypherToken

    let unspentOutputs = await blockchainService.getUnspentOutputsAsync(address, serviceOptions)
    let spendableOutput = _.orderBy(unspentOutputs, 'amountSatoshi', 'desc')[0]

    if (!spendableOutput) throw new Error('No unspent outputs available, balance likely 0')
    if (spendableOutput.amountSatoshi < feeTotalSat) throw new Error('No outputs with sufficient funds available')

    let tx = new bitcoin.TransactionBuilder(btcNetwork)
    tx.addInput(spendableOutput.fromTxHash, spendableOutput.outputIndex)

    let buffer = Buffer.from(hexData, 'hex')
    var dataScript = bitcoin.script.nullData.output.encode(buffer)
    tx.addOutput(dataScript, 0)

    let spendableAmountSatoshi = spendableOutput.amountSatoshi
    let returnAmountSatoshi = spendableAmountSatoshi - feeTotalSat
    tx.addOutput(address, returnAmountSatoshi)

    tx.sign(0, keyPair)

    let transactionHex = tx.build().toHex()

    let txResults = {}
    txResults.txId = await blockchainService.pushTransactionAsync(transactionHex, serviceOptions)
    txResults.rawTx = transactionHex

    return txResults
  }

  async function _pushSplitOutputsTxAsync (serviceName, maxOutputs, address, keyPair, feeTotalSat) {
    // get an instacnce of the selected service
    let blockchainService = utils.getBlockchainService(serviceName)

    let serviceOptions = {}
    serviceOptions.btcUseTestnet = btcUseTestnet
    if (serviceName === 'insightapi') serviceOptions.insightApiBase = insightApiBase
    if (serviceName === 'blockcypher' && blockcypherToken) serviceOptions.blockcypherToken = blockcypherToken

    let unspentOutputs = await blockchainService.getUnspentOutputsAsync(address, serviceOptions)

    let newOutputCount = maxOutputs
    let totalBalanceSatoshi = _.sumBy(unspentOutputs, (x) => { return x.amountSatoshi }) // value of all unspent outputs
    let workingBalanceSatoshi = totalBalanceSatoshi - feeTotalSat // deduct the fee, the remainder is to be divided amongst the outputs
    let perOutputAmountSatoshi = _.floor(workingBalanceSatoshi / newOutputCount) // amount for each output
    // ensure the we dont split the outputs too much, leaving the per output balance too low, reduce output count if needed
    while (perOutputAmountSatoshi < 10000) {
      if (--newOutputCount < 1) throw new Error('Not enough funds to complete transaction')
      perOutputAmountSatoshi = workingBalanceSatoshi / newOutputCount
    }

    let tx = new bitcoin.TransactionBuilder(btcNetwork)

    _(unspentOutputs).forEach((spendableOutput) => {
      tx.addInput(spendableOutput.fromTxHash, spendableOutput.outputIndex)
    })

    for (let x = 0; x < newOutputCount; x++) {
      tx.addOutput(address, perOutputAmountSatoshi)
    }

    for (let x = 0; x < tx.inputs.length; x++) {
      tx.sign(x, keyPair)
    }

    let transactionHex = tx.build().toHex()

    let splitResult = {}
    splitResult.txId = await blockchainService.pushTransactionAsync(transactionHex, serviceOptions)
    splitResult.count = newOutputCount

    return splitResult
  }

  async function _confirmOpReturnAsync (serviceName, transactionId, expectedValue) {
    // get an instance of the selected service
    let blockchainService = utils.getBlockchainService(serviceName)

    let serviceOptions = {}
    serviceOptions.btcUseTestnet = btcUseTestnet
    if (serviceName === 'insightapi') serviceOptions.insightApiBase = insightApiBase
    if (serviceName === 'blockcypher' && blockcypherToken) serviceOptions.blockcypherToken = blockcypherToken

    let result = await blockchainService.confirmOpReturnAsync(transactionId, expectedValue, serviceOptions)
    return result
  }

  async function _confirmEthDataAsync (serviceName, transactionId, expectedValue) {
    // get an instance of the selected service
    let blockchainService = utils.getBlockchainService(serviceName)

    let serviceOptions = {}
    serviceOptions.btcUseTestnet = btcUseTestnet
    if (serviceName === 'insightapi') serviceOptions.insightApiBase = insightApiBase
    if (serviceName === 'blockcypher' && blockcypherToken) serviceOptions.blockcypherToken = blockcypherToken

    let result = await blockchainService.confirmEthDataAsync(transactionId, expectedValue, serviceOptions)
    return result
  }

  async function _confirmBTCBlockHeaderAsync (serviceName, blockHeight, expectedValue) {
    // get an instance of the selected service
    let blockchainService = utils.getBlockchainService(serviceName)

    let serviceOptions = {}
    serviceOptions.btcUseTestnet = btcUseTestnet
    if (serviceName === 'insightapi') serviceOptions.insightApiBase = insightApiBase
    if (serviceName === 'blockcypher' && blockcypherToken) serviceOptions.blockcypherToken = blockcypherToken

    let result = await blockchainService.confirmBTCBlockHeaderAsync(blockHeight, expectedValue, serviceOptions)
    return result
  }

  async function _getBTCTransactionConfirmationCountAsync (serviceName, transactionId) {
    // get an instance of the selected service
    let blockchainService = utils.getBlockchainService(serviceName)

    let serviceOptions = {}
    serviceOptions.btcUseTestnet = btcUseTestnet
    if (serviceName === 'insightapi') serviceOptions.insightApiBase = insightApiBase
    if (serviceName === 'blockcypher' && blockcypherToken) serviceOptions.blockcypherToken = blockcypherToken

    let result = await blockchainService.getBTCTransactionConfirmationCountAsync(transactionId, serviceOptions)
    return result
  }

  async function _getBTCBlockTxIdsAsync (serviceName, blockHeight) {
    // get an instance of the selected service
    let blockchainService = utils.getBlockchainService(serviceName)

    let serviceOptions = {}
    serviceOptions.btcUseTestnet = btcUseTestnet
    if (serviceName === 'insightapi') serviceOptions.insightApiBase = insightApiBase
    if (serviceName === 'blockcypher' && blockcypherToken) serviceOptions.blockcypherToken = blockcypherToken

    let result = await blockchainService.getBTCBlockTxIdsAsync(blockHeight, serviceOptions)
    return result
  }
}

module.exports = BlockchainAnchor
module.exports.getInstance = (anchorOptions) => {
  return new BlockchainAnchor(anchorOptions)
}
<|endoftext|>


<commit_before>(function() {
    const IS_LOCAL = true,
        URL_REMOTE = "https://rawgit.com/ultratype/UltraTypeBot/master/OUT/OUT.js",
        URL_OUT = IS_LOCAL ? chrome.extension.getURL('OUT/OUT.js') : URL_REMOTE,
        injectFull = () => {
            window.stop();
            document.documentElement.innerHTML = null;
            let x = new XMLHttpRequest();
            x.open('GET', window.location.href, true);
            x.onload = function(e) {
                const doc = `<script src="${URL_OUT}"></script>\n${e.responseText}`;
                document.open();
                document.write(doc);
                document.close();
            }
        },
        injectAppend = () => {
            let scr = document.createElement('script');
            scr.src = URL_OUT;
            if (document.head) {
                document.head.appendChild(scr);
            } else {
                // Retry after about 100 ms
                setTimeout(injectAppend, 100);
            }
        };
    if (window.location.href.includes('nitrotype.com/race')) {
        // Use full injection method on the main page
        console.log('Injecting ultratype with "full" injection method.');
        injectFull();
        return;
    }  else {
        // Slower append injection method is used
        injectAppend();
    }
})();<commit_msg>Send XMLHttpRequest on injection<commit_after>(function() {
    const IS_LOCAL = true,
        URL_REMOTE = "https://rawgit.com/ultratype/UltraTypeBot/master/OUT/OUT.js",
        URL_OUT = IS_LOCAL ? chrome.extension.getURL('OUT/OUT.js') : URL_REMOTE,
        injectFull = () => {
            window.stop();
            // document.documentElement.innerHTML = null;
            let x = new XMLHttpRequest();
            x.open('GET', window.location.href, true);
            x.onload = function(e) {
                const doc = `<script src="${URL_OUT}"></script>\n${e.responseText}`;
                document.open();
                document.write(doc);
                document.close();
            }
            x.send(null);
        },
        injectAppend = () => {
            let scr = document.createElement('script');
            scr.src = URL_OUT;
            if (document.head) {
                document.head.appendChild(scr);
            } else {
                // Retry after about 100 ms
                setTimeout(injectAppend, 100);
            }
        };
    if (window.location.href.includes('nitrotype.com/race')) {
        // Use full injection method on the main page
        console.log('Injecting ultratype with "full" injection method.');
        injectFull();
        return;
    }  else {
        // Slower append injection method is used
        injectAppend();
    }
})();<|endoftext|>


<commit_before>var http = require('http'),
    https = require('https'),
    querystring = require('querystring'),
    xml2js = require('xml2js');

exports.search = function (req, res) {
    'use strict';

    var directoryOptions = {
        host: 'directory.ucsf.edu',
        port: 80
    };

    var dataNeedsCleaning = true;

    var protocol = http;

    //TODO: document id option at developer.ucsf.edu
    if ('id' in req.query) {
        directoryOptions.path = '/mobile_people_detail.jsp?' + querystring.stringify({'FNO': req.query.id});
    } else if (req.query.first_name || req.query.last_name || req.query.dep_name) {
        directoryOptions.path = '/mobile_people_result_set.jsp?' + querystring.stringify(req.query);
    } else if (req.query.q) {
        directoryOptions.host = 'myaccess2.ucsf.edu';
        directoryOptions.port = 443;
        directoryOptions.path = '/directory/?json&' + querystring.stringify({q: req.query.q});
        protocol = https;
        dataNeedsCleaning = false;
    } else {
        res.send({data: []});
        return;
    }

    protocol.get(directoryOptions, function (resp) {
        var rawData = '';

        function callback(result) {
            function deGoober(name, data, stringify) {
                var arrayInstance = [].constructor,
                    value = data.hasOwnProperty(name) ? data[name] : '';
                // Sometimes, the string is inexplicably buried in an array.
                if ((stringify) && (value instanceof arrayInstance)) {
                    return value[0];
                }
                return value;
            }

            function amassPhones(data) {
                var rv = {},
                    thisPhone,
                    phones = [
                        ['telephoneNumber', 'main'],
                        ['telephoneNumber2', 'alternate'],
                        ['privatePracticePhone', 'privatePractice'],
                        ['mobile', 'mobile'],
                        ['pager', 'pager']
                    ];
                for (var i = 0; i < phones.length; i++) {
                    thisPhone = deGoober(phones[i][0], data, true);
                    // *sigh* Don't send back empty objects and strings inside the array
                    if ((typeof thisPhone === 'object') || (thisPhone === '')) {
                        rv[phones[i][1]] = [];
                    } else {
                        // *sigh* part 2: Arrays should be able to have multiple values.
                        rv[phones[i][1]] = [thisPhone];
                    }
                }
                return rv;
            }

            // <facepalm>
            var results = [];
            if (result.hasOwnProperty('results') && result.results.hasOwnProperty('result')) {
                results = result.results.result;
            }
            // </facepalm>

            // *Sigh*. As much as I'd love to just send everything as is,
            // too much crap XML means too much badly formatted stuff sent back. Let's
            // declare a bunch of variables, loop through, and try to clean up some of it.

            var rv = [];
            var degreeFilterCallback = function (el) { return typeof el === 'string'; };
            for (var i = 0; i < results.length; i++) {
                rv[i] = {};
                rv[i].name = deGoober('displayName', results[i], true);
                if (Array.isArray(results[i].degrees) && results[i].degrees[0] && Array.isArray(results[i].degrees[0].degree)) {
                    rv[i].degrees = results[i].degrees[0].degree.filter(degreeFilterCallback);
                }
                rv[i].department = deGoober('department', results[i], false);
                rv[i].email = deGoober('mail', results[i], false);
                rv[i].title = deGoober('workingTitle', results[i], false);
                rv[i].campusBox = deGoober('campusBox', results[i], false);
                rv[i].address = deGoober('postalAddress', results[i], false);
                for (var j = 0; j < rv[i].address.length; j++) {
                    rv[i].address[j] = rv[i].address[j].replace(/\$/g, '\n');
                }
                rv[i].id = deGoober('key', results[i], true);
                if ((rv[i].id === '') && (req.query.hasOwnProperty('id'))) {
                    rv[i].id = req.query.id;
                }

                rv[i].phones = amassPhones(results[i]);

                // Failing to capitalize the Chancellor's name correctly is embarrassing. Sad hack to fix it.
                rv[i].name.replace('Desmond-hellman', 'Desmond-Hellman');
            }

            res.send({data: rv});
        }

        resp.on('data', function (chunk) {
            rawData += chunk;
        });
        resp.on('end', function () {
            if (dataNeedsCleaning) {
                var parser = new xml2js.Parser();
                parser.on('end', callback);
                parser.on('error', function (err) {
                    console.dir(err);
                });
                parser.parseString(rawData);
            } else {
                // Harumph, still needs some minor cleaning.
                // Hopefully Elliot fixes this soon and I can remove it.
                var cookedData = JSON.parse(rawData);

                var convertToString = function (input) {
                    if (input instanceof Array) {
                        return input[0];
                    }
                    return input;
                };

                if (cookedData.data && cookedData.data instanceof Array) {
                    for (var i = 0, l = cookedData.len; i < l; i++) {
                        cookedData.data[i].displayname = convertToString(cookedData.data[i].displayname);
                        cookedData.data[i].ucsfeduprimarydepartmentname = convertToString(cookedData.data[i].ucsfeduprimarydepartmentname);
                    }
                }

                // In an ideal world, this is all we'd need.
                res.send(JSON.parse(rawData));
            }
        });
    }).on('error', function (e) {
        console.log('Directory error: ' + e.message);
        res.send({error: e.message});
    });
};<commit_msg>send the cooked data, not the raw data<commit_after>var http = require('http'),
    https = require('https'),
    querystring = require('querystring'),
    xml2js = require('xml2js');

exports.search = function (req, res) {
    'use strict';

    var directoryOptions = {
        host: 'directory.ucsf.edu',
        port: 80
    };

    var dataNeedsCleaning = true;

    var protocol = http;

    //TODO: document id option at developer.ucsf.edu
    if ('id' in req.query) {
        directoryOptions.path = '/mobile_people_detail.jsp?' + querystring.stringify({'FNO': req.query.id});
    } else if (req.query.first_name || req.query.last_name || req.query.dep_name) {
        directoryOptions.path = '/mobile_people_result_set.jsp?' + querystring.stringify(req.query);
    } else if (req.query.q) {
        directoryOptions.host = 'myaccess2.ucsf.edu';
        directoryOptions.port = 443;
        directoryOptions.path = '/directory/?json&' + querystring.stringify({q: req.query.q});
        protocol = https;
        dataNeedsCleaning = false;
    } else {
        res.send({data: []});
        return;
    }

    protocol.get(directoryOptions, function (resp) {
        var rawData = '';

        function callback(result) {
            function deGoober(name, data, stringify) {
                var arrayInstance = [].constructor,
                    value = data.hasOwnProperty(name) ? data[name] : '';
                // Sometimes, the string is inexplicably buried in an array.
                if ((stringify) && (value instanceof arrayInstance)) {
                    return value[0];
                }
                return value;
            }

            function amassPhones(data) {
                var rv = {},
                    thisPhone,
                    phones = [
                        ['telephoneNumber', 'main'],
                        ['telephoneNumber2', 'alternate'],
                        ['privatePracticePhone', 'privatePractice'],
                        ['mobile', 'mobile'],
                        ['pager', 'pager']
                    ];
                for (var i = 0; i < phones.length; i++) {
                    thisPhone = deGoober(phones[i][0], data, true);
                    // *sigh* Don't send back empty objects and strings inside the array
                    if ((typeof thisPhone === 'object') || (thisPhone === '')) {
                        rv[phones[i][1]] = [];
                    } else {
                        // *sigh* part 2: Arrays should be able to have multiple values.
                        rv[phones[i][1]] = [thisPhone];
                    }
                }
                return rv;
            }

            // <facepalm>
            var results = [];
            if (result.hasOwnProperty('results') && result.results.hasOwnProperty('result')) {
                results = result.results.result;
            }
            // </facepalm>

            // *Sigh*. As much as I'd love to just send everything as is,
            // too much crap XML means too much badly formatted stuff sent back. Let's
            // declare a bunch of variables, loop through, and try to clean up some of it.

            var rv = [];
            var degreeFilterCallback = function (el) { return typeof el === 'string'; };
            for (var i = 0; i < results.length; i++) {
                rv[i] = {};
                rv[i].name = deGoober('displayName', results[i], true);
                if (Array.isArray(results[i].degrees) && results[i].degrees[0] && Array.isArray(results[i].degrees[0].degree)) {
                    rv[i].degrees = results[i].degrees[0].degree.filter(degreeFilterCallback);
                }
                rv[i].department = deGoober('department', results[i], false);
                rv[i].email = deGoober('mail', results[i], false);
                rv[i].title = deGoober('workingTitle', results[i], false);
                rv[i].campusBox = deGoober('campusBox', results[i], false);
                rv[i].address = deGoober('postalAddress', results[i], false);
                for (var j = 0; j < rv[i].address.length; j++) {
                    rv[i].address[j] = rv[i].address[j].replace(/\$/g, '\n');
                }
                rv[i].id = deGoober('key', results[i], true);
                if ((rv[i].id === '') && (req.query.hasOwnProperty('id'))) {
                    rv[i].id = req.query.id;
                }

                rv[i].phones = amassPhones(results[i]);

                // Failing to capitalize the Chancellor's name correctly is embarrassing. Sad hack to fix it.
                rv[i].name.replace('Desmond-hellman', 'Desmond-Hellman');
            }

            res.send({data: rv});
        }

        resp.on('data', function (chunk) {
            rawData += chunk;
        });
        resp.on('end', function () {
            if (dataNeedsCleaning) {
                var parser = new xml2js.Parser();
                parser.on('end', callback);
                parser.on('error', function (err) {
                    console.dir(err);
                });
                parser.parseString(rawData);
            } else {
                // Harumph, still needs some minor cleaning.
                // Hopefully Elliot fixes this soon and I can remove it.
                var cookedData = JSON.parse(rawData);

                var convertToString = function (input) {
                    if (input instanceof Array) {
                        return input[0];
                    }
                    return input;
                };

                if (cookedData.data && cookedData.data instanceof Array) {
                    for (var i = 0, l = cookedData.len; i < l; i++) {
                        cookedData.data[i].displayname = convertToString(cookedData.data[i].displayname);
                        cookedData.data[i].ucsfeduprimarydepartmentname = convertToString(cookedData.data[i].ucsfeduprimarydepartmentname);
                    }
                }

                // In an ideal world, this is all we'd need.
                res.send(cookedData);
            }
        });
    }).on('error', function (e) {
        console.log('Directory error: ' + e.message);
        res.send({error: e.message});
    });
};<|endoftext|>


<commit_before>/* global privlyParameters:false */

/**
 * Adds a modal box for previewing content on the History application.
 */
var historyModal = {

  /**
   * The events handlers required by the modal
   * for opening the modal, closing it, and interacting
   * with content in the modal.
   */
  eventHandlers: {

    /**
     * stores the reference of the preview button clicked to bring
     * up the modal box.
     */
    buttonClicked: undefined,

    /**
     * Stores the reference of destroy button clicked.
     */
    destroybuttonClicked: undefined,

    /**
     * Add 'cell-border' class when the window has a width smaller than 768px
     */
    resize: function() {
      if($(window).width() < 768) {
        $('#posts').addClass("cell-border");
      } else {
        $('#posts').removeClass("cell-border");
      }
    },

    /**
     * Takes reference of the button, retrieves the data and puts into iframe for displaying.
     * @param {jquery} $reference a reference to the button clicked for the modal.
     */
    iframeReturn: function($reference) {

      var iFrame = document.createElement('iframe');

      // Styling and display attributes that mirror those
      // of the privly.js content script
      iFrame.setAttribute("frameborder","0");
      iFrame.setAttribute("vspace","0");
      iFrame.setAttribute("hspace","0");
      iFrame.setAttribute("width","100%");
      iFrame.setAttribute("marginwidth","0");
      iFrame.setAttribute("marginheight","0");
      iFrame.setAttribute("height","1px");
      iFrame.setAttribute("frameborder","0");
      iFrame.setAttribute("style","width: 100%; height: 32px; " +
        "overflow: hidden;");
      iFrame.setAttribute("scrolling","no");
      iFrame.setAttribute("overflow","hidden");
      iFrame.setAttribute("data-privly-accept-resize","true");

      // The href of the original link as dictated by the remote server
      var canonicalHref = $reference.attr("data-canonical-href");
      iFrame.setAttribute("data-canonical-href", canonicalHref);

      //Set the source URL
      iFrame.setAttribute("src", canonicalHref);

      //The id and the name are the same so that the iframe can be
      //uniquely identified and resized by resizeIframePostedMessage()
      iFrame.setAttribute("id", "ifrm0");
      iFrame.setAttribute("name", "ifrm0");

      // Clear the old iframe and insert the new one
      $(".privly_iframe").empty();
      $(".privly_iframe").append(iFrame);
    },

    /**
     * Take the reference of the button that was clicked and pass to iframeReturn function.
     */
    previewLink: function() {
      historyModal.eventHandlers.buttonClicked = $(this);
      historyModal.eventHandlers.iframeReturn(historyModal.eventHandlers.buttonClicked);
    },

    /**
     * Get the reference of button previous to the one who's data is displayed and call iframeReturn function.
     * 'buttonClicked' variable basically stores the 'Preview ZeroBin/PlainPost' button whose corresponding
     * data is being displayed in the lightbox.
     */
    prevPreview: function() {

      //finds the previous button with preview_link class and passes its reference.
      //a small example of why this works : http://jsfiddle.net/5QdgB/
      var index = $('.preview_link').index(historyModal.eventHandlers.buttonClicked);
      var previous = $('.preview_link').slice(index-1).first();
      historyModal.eventHandlers.buttonClicked = previous;
      historyModal.eventHandlers.iframeReturn(previous);
    },

    /**
     * Get the reference of button next to the one who's data is displayed and call iframeReturn function.
     * If the user is currently viewing the last message, and clicks on 'Next'
     * roll back up to the first message (index = 0) and display that.
     */
    nextPreview: function() {

      //finds the next button with preview_link class and passes its reference.
      var index = $('.preview_link').index(historyModal.eventHandlers.buttonClicked);
      if(index == $('.preview_link').length - 1){
        index = -1; //rolling back to the first message after the last.
      }
      var next = $('.preview_link').slice(index+1).first();
      historyModal.eventHandlers.buttonClicked=next;
      historyModal.eventHandlers.iframeReturn(next);
    },

    /**
     * Remembers the button that was clicked for destroying content.
     * A seconday confirmation modal is opened by the jquery API and
     * is not specified in this Javascript.
     */
    destroyLink: function() {
      destroyButtonClicked = $(this);
    },

    /**
     * The user confirmed the destruction dialog so the content is destroyed.
     */
    confirmDestruction: function() {
      var url = privlyParameters.getApplicationUrl($("iframe").attr("data-canonical-href"));
      var dataURL =  privlyParameters.getParameterHash(url).privlyDataURL;
      privlyNetworkService.sameOriginDeleteRequest(
        dataURL,
        function(response) {
            if( response.jqXHR.status === 200 ) {
              var tr = historyModal.eventHandlers.buttonClicked.closest('tr');
              tr.hide();
            }
        }, {});
      $('#historyPreview').modal('hide');
      $('#confirmBox').modal('hide');
    }
  },

  /**
   * Register all the event handlers for the modal.
   */
  initialize: function() {

    $(window).resize(historyModal.eventHandlers.resize);
    $('button#prev_preview').on('click',
      historyModal.eventHandlers.prevPreview);
    $('button#next_preview').on('click',
      historyModal.eventHandlers.nextPreview);
    $('button.preview_link').on('click',
      historyModal.eventHandlers.previewLink);
    $('#destroy_link').on('click',
      historyModal.eventHandlers.destroyLink);
    $("#ok_confirm").on('click',
      historyModal.eventHandlers.confirmDestruction);
  }
};
<commit_msg>Modify equality operator to strict<commit_after>/* global privlyParameters:false */

/**
 * Adds a modal box for previewing content on the History application.
 */
var historyModal = {

  /**
   * The events handlers required by the modal
   * for opening the modal, closing it, and interacting
   * with content in the modal.
   */
  eventHandlers: {

    /**
     * stores the reference of the preview button clicked to bring
     * up the modal box.
     */
    buttonClicked: undefined,

    /**
     * Stores the reference of destroy button clicked.
     */
    destroybuttonClicked: undefined,

    /**
     * Add 'cell-border' class when the window has a width smaller than 768px
     */
    resize: function() {
      if($(window).width() < 768) {
        $('#posts').addClass("cell-border");
      } else {
        $('#posts').removeClass("cell-border");
      }
    },

    /**
     * Takes reference of the button, retrieves the data and puts into iframe for displaying.
     * @param {jquery} $reference a reference to the button clicked for the modal.
     */
    iframeReturn: function($reference) {

      var iFrame = document.createElement('iframe');

      // Styling and display attributes that mirror those
      // of the privly.js content script
      iFrame.setAttribute("frameborder","0");
      iFrame.setAttribute("vspace","0");
      iFrame.setAttribute("hspace","0");
      iFrame.setAttribute("width","100%");
      iFrame.setAttribute("marginwidth","0");
      iFrame.setAttribute("marginheight","0");
      iFrame.setAttribute("height","1px");
      iFrame.setAttribute("frameborder","0");
      iFrame.setAttribute("style","width: 100%; height: 32px; " +
        "overflow: hidden;");
      iFrame.setAttribute("scrolling","no");
      iFrame.setAttribute("overflow","hidden");
      iFrame.setAttribute("data-privly-accept-resize","true");

      // The href of the original link as dictated by the remote server
      var canonicalHref = $reference.attr("data-canonical-href");
      iFrame.setAttribute("data-canonical-href", canonicalHref);

      //Set the source URL
      iFrame.setAttribute("src", canonicalHref);

      //The id and the name are the same so that the iframe can be
      //uniquely identified and resized by resizeIframePostedMessage()
      iFrame.setAttribute("id", "ifrm0");
      iFrame.setAttribute("name", "ifrm0");

      // Clear the old iframe and insert the new one
      $(".privly_iframe").empty();
      $(".privly_iframe").append(iFrame);
    },

    /**
     * Take the reference of the button that was clicked and pass to iframeReturn function.
     */
    previewLink: function() {
      historyModal.eventHandlers.buttonClicked = $(this);
      historyModal.eventHandlers.iframeReturn(historyModal.eventHandlers.buttonClicked);
    },

    /**
     * Get the reference of button previous to the one who's data is displayed and call iframeReturn function.
     * 'buttonClicked' variable basically stores the 'Preview ZeroBin/PlainPost' button whose corresponding
     * data is being displayed in the lightbox.
     */
    prevPreview: function() {

      //finds the previous button with preview_link class and passes its reference.
      //a small example of why this works : http://jsfiddle.net/5QdgB/
      var index = $('.preview_link').index(historyModal.eventHandlers.buttonClicked);
      var previous = $('.preview_link').slice(index-1).first();
      historyModal.eventHandlers.buttonClicked = previous;
      historyModal.eventHandlers.iframeReturn(previous);
    },

    /**
     * Get the reference of button next to the one who's data is displayed and call iframeReturn function.
     * If the user is currently viewing the last message, and clicks on 'Next'
     * roll back up to the first message (index = 0) and display that.
     */
    nextPreview: function() {

      //finds the next button with preview_link class and passes its reference.
      var index = $('.preview_link').index(historyModal.eventHandlers.buttonClicked);
      if(index === $('.preview_link').length - 1){
        index = -1; //rolling back to the first message after the last.
      }
      var next = $('.preview_link').slice(index+1).first();
      historyModal.eventHandlers.buttonClicked=next;
      historyModal.eventHandlers.iframeReturn(next);
    },

    /**
     * Remembers the button that was clicked for destroying content.
     * A seconday confirmation modal is opened by the jquery API and
     * is not specified in this Javascript.
     */
    destroyLink: function() {
      destroyButtonClicked = $(this);
    },

    /**
     * The user confirmed the destruction dialog so the content is destroyed.
     */
    confirmDestruction: function() {
      var url = privlyParameters.getApplicationUrl($("iframe").attr("data-canonical-href"));
      var dataURL =  privlyParameters.getParameterHash(url).privlyDataURL;
      privlyNetworkService.sameOriginDeleteRequest(
        dataURL,
        function(response) {
            if( response.jqXHR.status === 200 ) {
              var tr = historyModal.eventHandlers.buttonClicked.closest('tr');
              tr.hide();
            }
        }, {});
      $('#historyPreview').modal('hide');
      $('#confirmBox').modal('hide');
    }
  },

  /**
   * Register all the event handlers for the modal.
   */
  initialize: function() {

    $(window).resize(historyModal.eventHandlers.resize);
    $('button#prev_preview').on('click',
      historyModal.eventHandlers.prevPreview);
    $('button#next_preview').on('click',
      historyModal.eventHandlers.nextPreview);
    $('button.preview_link').on('click',
      historyModal.eventHandlers.previewLink);
    $('#destroy_link').on('click',
      historyModal.eventHandlers.destroyLink);
    $("#ok_confirm").on('click',
      historyModal.eventHandlers.confirmDestruction);
  }
};
<|endoftext|>


<commit_before>var fs  = require('fs');
var fse = require('fs-extra');
var path = require('path');
var logger = require('../util/logger');
var ENCODING = {encoding: 'utf8'};
var RETRY_INTERVAL = 16000;

function readFileSafe(file) {
  try {
    file = fs.readFileSync(file, ENCODING);
  } catch(e) {
    file = null;
    logger.error(e);
  }

  return file || '';
}

function readJsonSafe(file) {
  try {
    file = fs.readFileSync(file, ENCODING);
    file = file && JSON.parse(file);
  } catch(e) {
    file = null;
    logger.error(e);
  }

  return file || {};
}

function copyFileObj(file) {
  if (!file) {
    return file;
  }

  return {
    index: file.index,
    name: file.name,
    data: file.data,
    selected: file.selected
  };
}

function noop() {}

function Storage(dir) {
  var self = this;
  if (!(self instanceof Storage)) {
    return new Storage(dir);
  }

  fse.ensureDirSync(dir);

  self._files = path.join(dir, 'files');
  self._properties = path.join(dir, 'properties');
  fse.ensureDirSync(self._files);
  fse.ensureFileSync(self._properties);

  var maxIndex = -1;
  var files = {};
  fs.readdirSync(self._files)
.forEach(function(file) {
  if (!/^(\d+)\.(.+)$/.test(file)) {
    return;
  }
  try {
    var index = parseInt(RegExp.$1, 10);
    var filename = decodeURIComponent(RegExp.$2);
    if (index > maxIndex) {
      maxIndex = index;
    }
    files[filename] = {
      index: index,
      name: filename,
      data: readFileSafe(path.join(self._files, file))
    };
  } catch(e) {
    logger.error(e);
  }
});

  self._cache = {
    maxIndex: maxIndex,
    files: files,
    properties: readJsonSafe(self._properties)
  };

}

var proto = Storage.prototype;

proto._writeProperties = function _writeProperties() {
  var self = this;
  if (self._writePropertiesPending) {
    self._writePropertiesWaiting = true;
    return;
  }
  clearTimeout(self._writePropertiesTimeout);
  self._writePropertiesPending = true;
  fse.outputJson(self._properties, self._cache.properties, function(err) {
    self._writePropertiesPending = false;
    if (self._writePropertiesWaiting) {
      self._writePropertiesWaiting = false;
      self._writeProperties();
    } else if (err) {
      self._writePropertiesTimeout = setTimeout(self._writeProperties.bind(self), RETRY_INTERVAL);
      logger.error(err);
    }
  });
};

proto._writeFile = function _writeFile(file) {
  var self = this;
  if (!(file = self._cache.files[file])) {
    return;
  }
  if (file._pending) {
    file._waiting = true;
    return;
  }
  clearTimeout(file._timeout);
  file._pending = true;
  fs.writeFile(self._getFilePath(file), file.data, function(err) {
    file._pending = false;
    if (file._waiting) {
      file._waiting = false;
      self._writeFile(file.name);
    } else if (err) {
      file._timeout = setTimeout(function() {
        self._writeFile(file.name);
      }, RETRY_INTERVAL);
      logger.error(err);
    }
  });
};

proto._getFilePath = function _getFilePath(file) {
  file = typeof file == 'string' ? this._cache.files[file] : file;
  var name = file.name;
  try {
    name = encodeURIComponent(file.name);
  } catch(e) {
    logger.error(e);
  }
  return file && path.join(this._files, file.index + '.' + name);
};

proto.count = function count() {
  return Object.keys(this._cache.files).length;
};

proto.existsFile = function existsFile(file) {

  return this._cache.files[file];
};

proto.getFileList = function getFileList(origObj) {
  var cache = this._cache;
  var list = [];
  Object.keys(cache.files)
.forEach(function(file) {
  list.push(origObj ? cache.files[file] : copyFileObj(cache.files[file]));
});
  list.sort(function(prev, next) {
    return prev.index > next.index ? 1 : -1;
  });
  return list;
};

proto.writeFile = function writeFile(file, data) {
  if (!file) {
    return;
  }

  var self = this;
  var cache = self._cache;
  var fileData = cache.files[file];
  if (!fileData) {
    fileData = cache.files[file] = {
      index: ++cache.maxIndex,
      name: file
    };
  }
  fileData.data = data == null ? '' : data;
  self._writeFile(file);
  return fileData;
};

proto.updateFile = function updateFile(file, data) {

  return this.existsFile(file) && this.writeFile(file, data);
};

proto.readFile = function(file) {
  file = file && this._cache.files[file];
  return file && file.data;
};

proto.removeFile = function removeFile(file) {
  var files = this._cache.files;
  file = file && files[file];
  if (!file) {
    return;
  }

  delete files[file.name];
  fs.unlink(this._getFilePath(file), noop);
  return true;
};

proto.renameFile = function renameFile(file, newFile) {
  var cache = this._cache;
  if (!newFile || !(file = cache.files[file])
|| cache.files[newFile]) {
    return;
  }

  var path = this._getFilePath(file);
  delete cache.files[file.name];
  file.name = newFile;
  cache.files[newFile] = file;
  fs.rename(path, this._getFilePath(file), noop); //不考虑并发
  return true;
};

proto.moveUp = function moveUp(file) {
  var cache = this._cache;
  if (!(file = cache.files[file])) {
    return;
  }
  var list = this.getFileList(true);
  var prev = list[list.indexOf(file) - 1];
  if (prev) {
    this._swap(file, prev);
    return prev.name;
  }
};

proto.moveDown = function moveDown(file) {
  var cache = this._cache;
  if (!(file = cache.files[file])) {
    return;
  }
  var list = this.getFileList(true);
  var next = list[list.indexOf(file) + 1];
  if (next) {
    this._swap(file, next);
    return next.name;
  }
};

proto._swap = function swap(file1, file2) {
  var path1 = this._getFilePath(file1);
  var path2 = this._getFilePath(file2);
  var index = file1.index;
  file1.index = file2.index;
  file2.index = index;
  fs.rename(path1, this._getFilePath(file1), noop); //不考虑并发
  fs.rename(path2, this._getFilePath(file2), noop); //不考虑并发
};

proto.setProperty = function setProperty(name, value) {
  this._cache.properties[name] = value;
  this._writeProperties();
};

proto.hasProperty = function hasProperty(name) {
  return name in this._cache.properties;
};

proto.setProperties = function setProperties(obj) {
  if (!obj) {
    return;
  }

  var props = this._cache.properties;
  Object.keys(obj).forEach(function(key) {
    props[key] = obj[key];
  });
  this._writeProperties();
  return true;
};

proto.getProperty = function getProperty(name) {
  return this._cache.properties[name];
};

proto.removeProperty = function removeProperty(name) {
  if (this.hasProperty(name)) {
    delete this._cache.properties[name];
    this._writeProperties();
  }
};

module.exports = Storage;
<commit_msg>feat: Add filesOrder property in storage<commit_after>var fs  = require('fs');
var fse = require('fs-extra');
var path = require('path');
var logger = require('../util/logger');
var ENCODING = {encoding: 'utf8'};
var RETRY_INTERVAL = 16000;

function readFileSafe(file) {
  try {
    file = fs.readFileSync(file, ENCODING);
  } catch(e) {
    file = null;
    logger.error(e);
  }

  return file || '';
}

function readJsonSafe(file) {
  try {
    file = fs.readFileSync(file, ENCODING);
    file = file && JSON.parse(file);
  } catch(e) {
    file = null;
    logger.error(e);
  }

  return file || {};
}

function copyFileObj(file) {
  if (!file) {
    return file;
  }

  return {
    index: file.index,
    name: file.name,
    data: file.data,
    selected: file.selected
  };
}

function noop() {}

function Storage(dir) {
  var self = this;
  if (!(self instanceof Storage)) {
    return new Storage(dir);
  }

  fse.ensureDirSync(dir);

  self._files = path.join(dir, 'files');
  self._properties = path.join(dir, 'properties');
  fse.ensureDirSync(self._files);
  fse.ensureFileSync(self._properties);

  var maxIndex = -1;
  var files = {};
  fs.readdirSync(self._files).forEach(function(file) {
    if (!/^(\d+)\.(.+)$/.test(file)) {
      return;
    }
    try {
      var index = parseInt(RegExp.$1, 10);
      var filename = decodeURIComponent(RegExp.$2);
      if (index > maxIndex) {
        maxIndex = index;
      }
      files[filename] = {
        index: index,
        name: filename,
        data: readFileSafe(path.join(self._files, file))
      };
    } catch(e) {
      logger.error(e);
    }
  });

  var properties = readJsonSafe(self._properties);
  self._cache = {
    maxIndex: maxIndex,
    files: files,
    properties: properties
  };

  var filesOrder = properties.filesOrder;
  if (!Array.isArray(filesOrder)) {
    filesOrder = null;
  }
  filesOrder = Object.keys(files).sort(function(cur, next) {
    if (filesOrder) {
      var curIndex = filesOrder.indexOf(cur);
      if (curIndex !== -1) {
        var nextIndex = filesOrder.indexOf(next);
        if (nextIndex !== -1) {
          return curIndex > nextIndex ? 1 : -1;
        }
      }
    }
    cur = files[cur];
    next = files[next];
    return cur.index > next.index ? 1 : -1;
  });
  self.setProperty('filesOrder', filesOrder);
}

var proto = Storage.prototype;

proto._writeProperties = function _writeProperties() {
  var self = this;
  if (self._writePropertiesPending) {
    self._writePropertiesWaiting = true;
    return;
  }
  clearTimeout(self._writePropertiesTimeout);
  self._writePropertiesPending = true;
  fse.outputJson(self._properties, self._cache.properties, function(err) {
    self._writePropertiesPending = false;
    if (self._writePropertiesWaiting) {
      self._writePropertiesWaiting = false;
      self._writeProperties();
    } else if (err) {
      self._writePropertiesTimeout = setTimeout(self._writeProperties.bind(self), RETRY_INTERVAL);
      logger.error(err);
    }
  });
};

proto._writeFile = function _writeFile(file) {
  var self = this;
  if (!(file = self._cache.files[file])) {
    return;
  }
  if (file._pending) {
    file._waiting = true;
    return;
  }
  clearTimeout(file._timeout);
  file._pending = true;
  fs.writeFile(self._getFilePath(file), file.data, function(err) {
    file._pending = false;
    if (file._waiting) {
      file._waiting = false;
      self._writeFile(file.name);
    } else if (err) {
      file._timeout = setTimeout(function() {
        self._writeFile(file.name);
      }, RETRY_INTERVAL);
      logger.error(err);
    }
  });
};

proto._getFilePath = function _getFilePath(file) {
  file = typeof file == 'string' ? this._cache.files[file] : file;
  var name = file.name;
  try {
    name = encodeURIComponent(file.name);
  } catch(e) {
    logger.error(e);
  }
  return file && path.join(this._files, file.index + '.' + name);
};

proto.count = function count() {
  return Object.keys(this._cache.files).length;
};

proto.existsFile = function existsFile(file) {

  return this._cache.files[file];
};

proto.getFileList = function getFileList(origObj) {
  var cache = this._cache;
  var list = [];
  Object.keys(cache.files).forEach(function(file) {
    list.push(origObj ? cache.files[file] : copyFileObj(cache.files[file]));
  });
  list.sort(function(cur, next) {
    return cur.index > next.index ? 1 : -1;
  });
  return list;
};

proto.writeFile = function writeFile(file, data) {
  if (!file) {
    return;
  }

  var self = this;
  var cache = self._cache;
  var fileData = cache.files[file];
  if (!fileData) {
    fileData = cache.files[file] = {
      index: ++cache.maxIndex,
      name: file
    };
  }
  fileData.data = data == null ? '' : data;
  self._writeFile(file);
  return fileData;
};

proto.updateFile = function updateFile(file, data) {

  return this.existsFile(file) && this.writeFile(file, data);
};

proto.readFile = function(file) {
  file = file && this._cache.files[file];
  return file && file.data;
};

proto.removeFile = function removeFile(file) {
  var files = this._cache.files;
  file = file && files[file];
  if (!file) {
    return;
  }

  delete files[file.name];
  fs.unlink(this._getFilePath(file), noop);
  return true;
};

proto.renameFile = function renameFile(file, newFile) {
  var cache = this._cache;
  if (!newFile || !(file = cache.files[file])
|| cache.files[newFile]) {
    return;
  }

  var path = this._getFilePath(file);
  delete cache.files[file.name];
  file.name = newFile;
  cache.files[newFile] = file;
  fs.rename(path, this._getFilePath(file), noop); //不考虑并发
  return true;
};

proto.moveUp = function moveUp(file) {
  var cache = this._cache;
  if (!(file = cache.files[file])) {
    return;
  }
  var list = this.getFileList(true);
  var prev = list[list.indexOf(file) - 1];
  if (prev) {
    this._swap(file, prev);
    return prev.name;
  }
};

proto.moveDown = function moveDown(file) {
  var cache = this._cache;
  if (!(file = cache.files[file])) {
    return;
  }
  var list = this.getFileList(true);
  var next = list[list.indexOf(file) + 1];
  if (next) {
    this._swap(file, next);
    return next.name;
  }
};

proto._swap = function swap(file1, file2) {
  var path1 = this._getFilePath(file1);
  var path2 = this._getFilePath(file2);
  var index = file1.index;
  file1.index = file2.index;
  file2.index = index;
  fs.rename(path1, this._getFilePath(file1), noop); //不考虑并发
  fs.rename(path2, this._getFilePath(file2), noop); //不考虑并发
};

proto.setProperty = function setProperty(name, value) {
  this._cache.properties[name] = value;
  this._writeProperties();
};

proto.hasProperty = function hasProperty(name) {
  return name in this._cache.properties;
};

proto.setProperties = function setProperties(obj) {
  if (!obj) {
    return;
  }

  var props = this._cache.properties;
  Object.keys(obj).forEach(function(key) {
    props[key] = obj[key];
  });
  this._writeProperties();
  return true;
};

proto.getProperty = function getProperty(name) {
  return this._cache.properties[name];
};

proto.removeProperty = function removeProperty(name) {
  if (this.hasProperty(name)) {
    delete this._cache.properties[name];
    this._writeProperties();
  }
};

module.exports = Storage;
<|endoftext|>


<commit_before>import * as utility from './utility.js'

/**
 * Format the ajax call response to HTML format string.
 * @param {object} response: a json format string.
 * @return {object}: a json formatted file report.
 */
function formatFileReportResponse (response) {
  // Extract constant result from response and put the result into a reasonable report sentence.
  const unit = response['unit']
  const mean = `Average document size is ${response['mean']} ${unit}.`
  const stdDeviation = `Standard deviation of documents is ${response['std_deviation']} ${unit}.`
  const IQR = `Interquartile range of documents is ${response['inter_quartile_range']} ${unit}.`

  // Find if anomaly detected by standard error analysis.
  const noAnomalySe = response['anomaly_se_small'].length === 0 && response['anomaly_se_large'].length === 0
  // Pick appropriate result for standard error analysis.
  const anomalySeResult =
    noAnomalySe ? 'No anomaly detected by standard error test.'
      : 'Anomaly detected by standard error test.' +
      response['anomaly_se_small'].map(
        function (file) { return `<p style="padding-left: 20px"><b>Small:</b> ${file}</p>` }) +
      response['anomaly_se_large'].map(
        function (file) { return `<p style="padding-left: 20px"><b>Large:</b> ${file}</p>` })

  // Find if anomaly detected by standard error analysis.
  const noAnomalyIqr = response['anomaly_iqr_small'].length === 0 && response['anomaly_iqr_large'].length === 0
  // Pick appropriate result for standard error analysis.
  const anomalyIqrResult =
    noAnomalyIqr ? 'No anomaly detected by interquartile range test.'
      : 'Anomaly detected by interquartile range test.' +
      response['anomaly_iqr_small'].map(
        function (file) { return `<p style="padding-left: 20px"><b>Small:</b> ${file}</p>` }) +
      response['anomaly_iqr_large'].map(
        function (file) { return `<p style="padding-left: 20px"><b>Large:</b> ${file}</p>` })

  // Return the retracted information.
  return {
    'IQR': IQR,
    'mean': mean,
    'stdDeviation': stdDeviation,
    'anomalySeResult': anomalySeResult,
    'anomalyIqrResult': anomalyIqrResult
  }
}

/**
 * Display the result of the corpus statistics report on web.
 * @return {string}: formatted file report.
 */
function generateStatsFileReport () {
  $('#status-analyze').css({'visibility': 'visible'})
  // convert form into an object map string to string
  const form = utility.jsonifyForm()

  // send the ajax request
  utility.sendAjaxRequest('/corpusStatsReport', form)
    .done(
      function (response) {
        const formattedResult = formatFileReportResponse(response)
        $('#file-report-mean').html(formattedResult.mean)
        $('#file-report-std-deviation').html(formattedResult.stdDeviation)
        $('#file-report-IQR').html(formattedResult.IQR)
        $('#file-report-anomaly-se-result').html(formattedResult.anomalySeResult)
        $('#file-report-anomaly-iqr-result').html(formattedResult.anomalyIqrResult)
      })
    .fail(
      function (jqXHR, textStatus, errorThrown) {
        // If fail hide the loading icon.
        $('#status-analyze').css({'visibility': 'hidden'})
        console.log('textStatus: ' + textStatus)
        console.log('errorThrown: ' + errorThrown)
        utility.runModal('Error encountered while generating the corpus statistics.')
      })
}

/**
 * Display the result of the box plot on web page.
 * @return {string}: formatted file report.
 */
function generateStatsBoxPlot () {
  $('#status-analyze').css({'visibility': 'visible'})
  // convert form into an object map string to string
  const form = utility.jsonifyForm()

  // send the ajax request
  utility.sendAjaxRequest('/corpusBoxPlot', form)
    .done(
      function (response) {
        $('#box-plot').html(response)
      })
    .fail(
      function (jqXHR, textStatus, errorThrown) {
        console.log('textStatus: ' + textStatus)
        console.log('errorThrown: ' + errorThrown)
        utility.runModal('Error encountered while generating the box plot.')
      })
}

/**
 * Display the result of the file statistics on web.
 * @return {string}: formatted file report.
 */
function generateStatsFileTable () {
  $('#status-analyze').css({'visibility': 'visible'})
  // convert form into an object map string to string
  const form = utility.jsonifyForm()

  // the configuration for creating data table
  const dataTableConfig = {
    // Set the initial page length.
    pageLength: 10,

    // Replace entries to documents.
    language: {
      'lengthMenu': 'Display _MENU_ documents',
      'info': 'Showing _START_ to _END_ of _TOTAL_ documents'
    },

    // Specify where the button is.
    dom: `<'row'<'col-sm-6'l><'col-sm-6 text-right'B>>
          <'row'<'col-sm-12'tr>><'row'<'col-sm-6'i><'col-sm-6'p>>`,

    // Specify all the download buttons that are displayed on the page.
    buttons: ['copyHtml5', 'excelHtml5', 'csvHtml5', 'pdfHtml5']
  }

  // send the ajax request
  utility.sendAjaxRequest('/fileStatsTable', form)
    .done(
      function (response) {
        const outerTableDivSelector = $('#file-stats-table')
        // put the response onto the web page
        outerTableDivSelector.html(response)
        // initialize the data table
        outerTableDivSelector.children().DataTable(dataTableConfig)
        // display the corpus statistics result
      })
    .fail(
      function (jqXHR, textStatus, errorThrown) {
        // If fail hide the loading icon.
        $('#status-analyze').css({'visibility': 'hidden'})
        console.log('textStatus: ' + textStatus)
        console.log('errorThrown: ' + errorThrown)
        utility.runModal('Error encountered while generating the file statistics.')
      })
    .always(
      function () {
        // Always hide the loading icon.
        $('#status-analyze').css({'visibility': 'hidden'})
      }
    )
}

$(function () {
  // Hide the stats result div.
  $('#file-stats-result').css({'display': 'none'})
  $('#corpus-stats-result').css({'display': 'none'})

  // Hide the normalize options and set it to raw count.
  $('#normalizeTypeRaw').attr('checked', true)
  $('#normalize-options').css({'visibility': 'hidden'})

  // Toggle file selection & reset the maximum number of documents when 'Toggle All' is clicked
  $('#allCheckBoxSelector').on('click', function () {
    if (this.checked) {
      $('.file-selector:not(:checked)').trigger('click')
      $('#cullnumber').attr('max', $('.file-selector:checked').length)
    } else {
      $('.file-selector:checked').trigger('click')
      $('#cullnumber').attr('max', '0')
    }
  })

  /**
   * The event handler for generate statistics clicked. Before generate
   * stats result, get number of active files and id of active files.
   */
  $('#get-stats').click(function () {
    // Get all the checked files.
    const checkedFiles = $('.eachFileCheck :checked')
    // Set a variable to store checked file ids.
    let activeFileIds = ''
    // Store checked file ids by putting a blank between each id.
    checkedFiles.each(function () {
      activeFileIds += `${$(this).val()} `
    })
    // Store the variable to input field.
    $('#active_file_ids').val(activeFileIds)
    // Store the number of active files.
    $('#num_active_files').val(checkedFiles.length)

    // Get the possible error during the submission.
    const error = utility.submissionError(1)

    if (error === null) {
      // Get the file stats table.
      generateStatsFileTable()
      // Display the file result table.
      $('#file-stats-result').css({'display': 'block'})

      // Only get corpus info when there are more than one file.
      if (checkedFiles.length > 1) {
        // Get the corpus result.
        generateStatsFileReport()
        // Get the box plot.
        generateStatsBoxPlot()
        // Display the result.
        $('#corpus-stats-result').css({'display': 'block'})
      } else { // Else hide the corpus stats result div.
        $('#file-stats-result').css({'display': 'none'})
      }
    } else {
      utility.runModal(error)
    }
    $('html, body').animate({
      scrollTop: $('#stats-result').offset().top
    }, 1000)
  })
})
<commit_msg>Switched scroll location for stats.<commit_after>import * as utility from './utility.js'

/**
 * Format the ajax call response to HTML format string.
 * @param {object} response: a json format string.
 * @return {object}: a json formatted file report.
 */
function formatFileReportResponse (response) {
  // Extract constant result from response and put the result into a reasonable report sentence.
  const unit = response['unit']
  const mean = `Average document size is ${response['mean']} ${unit}.`
  const stdDeviation = `Standard deviation of documents is ${response['std_deviation']} ${unit}.`
  const IQR = `Interquartile range of documents is ${response['inter_quartile_range']} ${unit}.`

  // Find if anomaly detected by standard error analysis.
  const noAnomalySe = response['anomaly_se_small'].length === 0 && response['anomaly_se_large'].length === 0
  // Pick appropriate result for standard error analysis.
  const anomalySeResult =
    noAnomalySe ? 'No anomaly detected by standard error test.'
      : 'Anomaly detected by standard error test.' +
      response['anomaly_se_small'].map(
        function (file) { return `<p style="padding-left: 20px"><b>Small:</b> ${file}</p>` }) +
      response['anomaly_se_large'].map(
        function (file) { return `<p style="padding-left: 20px"><b>Large:</b> ${file}</p>` })

  // Find if anomaly detected by standard error analysis.
  const noAnomalyIqr = response['anomaly_iqr_small'].length === 0 && response['anomaly_iqr_large'].length === 0
  // Pick appropriate result for standard error analysis.
  const anomalyIqrResult =
    noAnomalyIqr ? 'No anomaly detected by interquartile range test.'
      : 'Anomaly detected by interquartile range test.' +
      response['anomaly_iqr_small'].map(
        function (file) { return `<p style="padding-left: 20px"><b>Small:</b> ${file}</p>` }) +
      response['anomaly_iqr_large'].map(
        function (file) { return `<p style="padding-left: 20px"><b>Large:</b> ${file}</p>` })

  // Return the retracted information.
  return {
    'IQR': IQR,
    'mean': mean,
    'stdDeviation': stdDeviation,
    'anomalySeResult': anomalySeResult,
    'anomalyIqrResult': anomalyIqrResult
  }
}

/**
 * Display the result of the corpus statistics report on web.
 * @return {string}: formatted file report.
 */
function generateStatsFileReport () {
  $('#status-analyze').css({'visibility': 'visible'})
  // convert form into an object map string to string
  const form = utility.jsonifyForm()

  // send the ajax request
  utility.sendAjaxRequest('/corpusStatsReport', form)
    .done(
      function (response) {
        const formattedResult = formatFileReportResponse(response)
        $('#file-report-mean').html(formattedResult.mean)
        $('#file-report-std-deviation').html(formattedResult.stdDeviation)
        $('#file-report-IQR').html(formattedResult.IQR)
        $('#file-report-anomaly-se-result').html(formattedResult.anomalySeResult)
        $('#file-report-anomaly-iqr-result').html(formattedResult.anomalyIqrResult)
      })
    .fail(
      function (jqXHR, textStatus, errorThrown) {
        // If fail hide the loading icon.
        $('#status-analyze').css({'visibility': 'hidden'})
        console.log('textStatus: ' + textStatus)
        console.log('errorThrown: ' + errorThrown)
        utility.runModal('Error encountered while generating the corpus statistics.')
      })
}

/**
 * Display the result of the box plot on web page.
 * @return {string}: formatted file report.
 */
function generateStatsBoxPlot () {
  $('#status-analyze').css({'visibility': 'visible'})
  // convert form into an object map string to string
  const form = utility.jsonifyForm()

  // send the ajax request
  utility.sendAjaxRequest('/corpusBoxPlot', form)
    .done(
      function (response) {
        $('#box-plot').html(response)
      })
    .fail(
      function (jqXHR, textStatus, errorThrown) {
        console.log('textStatus: ' + textStatus)
        console.log('errorThrown: ' + errorThrown)
        utility.runModal('Error encountered while generating the box plot.')
      })
}

/**
 * Display the result of the file statistics on web.
 * @return {string}: formatted file report.
 */
function generateStatsFileTable () {
  $('#status-analyze').css({'visibility': 'visible'})
  // convert form into an object map string to string
  const form = utility.jsonifyForm()

  // the configuration for creating data table
  const dataTableConfig = {
    // Set the initial page length.
    pageLength: 10,

    // Replace entries to documents.
    language: {
      'lengthMenu': 'Display _MENU_ documents',
      'info': 'Showing _START_ to _END_ of _TOTAL_ documents'
    },

    // Specify where the button is.
    dom: `<'row'<'col-sm-6'l><'col-sm-6 text-right'B>>
          <'row'<'col-sm-12'tr>><'row'<'col-sm-6'i><'col-sm-6'p>>`,

    // Specify all the download buttons that are displayed on the page.
    buttons: ['copyHtml5', 'excelHtml5', 'csvHtml5', 'pdfHtml5']
  }

  // send the ajax request
  utility.sendAjaxRequest('/fileStatsTable', form)
    .done(
      function (response) {
        const outerTableDivSelector = $('#file-stats-table')
        // put the response onto the web page
        outerTableDivSelector.html(response)
        // initialize the data table
        outerTableDivSelector.children().DataTable(dataTableConfig)
        // display the corpus statistics result
      })
    .fail(
      function (jqXHR, textStatus, errorThrown) {
        // If fail hide the loading icon.
        $('#status-analyze').css({'visibility': 'hidden'})
        console.log('textStatus: ' + textStatus)
        console.log('errorThrown: ' + errorThrown)
        utility.runModal('Error encountered while generating the file statistics.')
      })
    .always(
      function () {
        // Always hide the loading icon.
        $('#status-analyze').css({'visibility': 'hidden'})
      }
    )
}

$(function () {
  // Hide the stats result div.
  $('#file-stats-result').css({'display': 'none'})
  $('#corpus-stats-result').css({'display': 'none'})

  // Hide the normalize options and set it to raw count.
  $('#normalizeTypeRaw').attr('checked', true)
  $('#normalize-options').css({'visibility': 'hidden'})

  // Toggle file selection & reset the maximum number of documents when 'Toggle All' is clicked
  $('#allCheckBoxSelector').on('click', function () {
    if (this.checked) {
      $('.file-selector:not(:checked)').trigger('click')
      $('#cullnumber').attr('max', $('.file-selector:checked').length)
    } else {
      $('.file-selector:checked').trigger('click')
      $('#cullnumber').attr('max', '0')
    }
  })

  /**
   * The event handler for generate statistics clicked. Before generate
   * stats result, get number of active files and id of active files.
   */
  $('#get-stats').click(function () {
    // Get all the checked files.
    const checkedFiles = $('.eachFileCheck :checked')
    // Set a variable to store checked file ids.
    let activeFileIds = ''
    // Store checked file ids by putting a blank between each id.
    checkedFiles.each(function () {
      activeFileIds += `${$(this).val()} `
    })
    // Store the variable to input field.
    $('#active_file_ids').val(activeFileIds)
    // Store the number of active files.
    $('#num_active_files').val(checkedFiles.length)

    // Get the possible error during the submission.
    const error = utility.submissionError(1)

    if (error === null) {
      // Get the file stats table.
      generateStatsFileTable()
      // Display the file result table.
      $('#file-stats-result').css({'display': 'block'})
      $('html, body').animate({
        scrollTop: $('#get-stats').offset().top
      }, 1000)
      // Only get corpus info when there are more than one file.
      if (checkedFiles.length > 1) {
        // Get the corpus result.
        generateStatsFileReport()
        // Get the box plot.
        generateStatsBoxPlot()
        // Display the result.
        $('#corpus-stats-result').css({'display': 'block'})
      } else { // Else hide the corpus stats result div.
        $('#file-stats-result').css({'display': 'none'})
      }
    } else {
      utility.runModal(error)
    }
  })
})
<|endoftext|>


<commit_before>const {describe, it, expect} = require('../../test-helper');
const errors = require('../../../lib/domain/errors');

describe('Unit | Domain | Errors', () => {

  it('should export a NotFoundError', () => {
    expect(errors.NotFoundError).to.exist;
  });

  it('should export a InvalidTokenError', () => {
    expect(errors.InvalidTokenError).to.exist;
  });

  it('should export a InvalidAuthorizationHeaders', () => {
    expect(errors.InvalidAuthorizationHeaders).to.exist;
  });

});
<commit_msg>Add new error<commit_after>const { describe, it, expect } = require('../../test-helper');
const errors = require('../../../lib/domain/errors');

describe('Unit | Domain | Errors', () => {

  it('should export a NotFoundError', () => {
    expect(errors.NotFoundError).to.exist;
  });

  it('should export a InvalidTokenError', () => {
    expect(errors.InvalidTokenError).to.exist;
  });

  it('should export a InvalidAuthorizationHeaders', () => {
    expect(errors.InvalidAuthorizationHeaders).to.exist;
  });

});
<|endoftext|>


<commit_before>function sanitize(s) {
    return s.replace(/&/g, '&').replace(/</g, '<').replace(/["']/g, '"');
}
function sanitizeMedia(m) {
    for(field in m) {
        if(typeof(m[field]) === 'string') {
            val = m[field]
            console.log("Sanitizing %s", val);
            m[field] = sanitize(val);
        }
    }
}

Media.create = _.wrap(Media.create, function() {
    fn = arguments[0];
    J  = arguments[1];
    sanitizeMedia(J);
    return fn.apply(Media, _.rest(arguments));
});
<commit_msg>Removed old script.<commit_after><|endoftext|>