<commit_before>'use strict';

var async        = require('async'),
    buffertools  = require('buffertools'),
    HashUtil     = require('bitcore/util'),
    LEB128       = require('./LEB128'),
    MarkerOutput = require('./MarkerOutput'),
    Opcode       = require('bitcore/lib/Opcode'),
    OutputType   = require('./OutputType'),
    Parser       = require('bitcore/util/BinaryParser'),
    Put          = require('bufferput'),
    Script       = require('bitcore/lib/Script'),
    Transaction  = require('bitcore/lib/Transaction'),
    TransactionOutput = require('./TransactionOutput');

/**
 * The backtracking engine used to find the asset ID and asset quantity
 * of any output.
**/

/**
 * Constructor
 * @param function transactionProvider  A function that accepts a transaction hash,
 *                                      performs a transaction lookup, and populates
 *                                      a callback. The first parameter to this
 *                                      function should be the transaction hash to
 *                                      look up. The second parameter should be a
 *                                      callback function with the following
 *                                      signature: cb(err, data). See the test cases
 *                                      for this class for an example provider using
 *                                      the Bitcore RpcClient library.
**/
function ColoringEngine (transactionProvider) {

  this.transactionProvider = transactionProvider;

}

/**
 * Get an output and information about its asset ID and asset quantity.
 * @param Buffer   transactionHash The hash of the transaction containing the output
 * @param int      outputIndex     The index of the output
 * @param function cb              A callback function to invoke with the result
**/
ColoringEngine.prototype.getOutput = function (transactionHash, outputIndex, cb) {

  var coloringEngine = this;

  coloringEngine.transactionProvider(transactionHash, function (err, data) {
    if (err) return cb(err);

    // Propagate error message from underlying JSON RPC call
    if (data.message) return cb(data.message);

    // Successful lookups will always populate 'result'
    if (!data.result) return cb('Transaction could not be retrieved.');

    // Create and populate a Transaction object using the raw data
    var tx = new Transaction();
    tx.parse(new Buffer(data.result,'hex'));

    // Compute ID and asset quantity of transaction outputs
    coloringEngine.colorTransaction(tx, function (err, data) {
      if (err) return cb(err);

      // If an output matching outputIndex exists, return it
      if (data[outputIndex]) {
        return cb(null, data[outputIndex]);
      }
      // Otherwise, report the error
      else {
        return cb('No data for output matching index ' + outputIndex);
      }

    });

  });

}

/**
 * Compute the asset ID and and asset quantity of every output in the transaction
 * @param Buffer   transaction        The transaction to color
 * @param function cb                 A callback function to invoke with the result. The
 *                                    function should have the signature cb(err, data). If
 *                                    successful, 'data' will be populated with an array
 *                                    containing all the colored outputs of the transaction.
**/
ColoringEngine.prototype.colorTransaction = function (transaction, cb) {

  var coloringEngine = this,
      foundMarkerOutput = false,
      markerOutputPayload = null,
      markerOutput = null;

  // Helper function to make the appropriate response in the case
  // where no valid asset ids were found in a transaction. In
  // this case all of the transaction outputs are considered uncolored.
  var makeUncoloredResponse = function (tx) {
    var outs = [];
    tx.outs.forEach(function (o) {
      outs.push(new TransactionOutput(o.v.readInt32LE(0),o.s));
    });
    return outs;
  };

  // If the transaction is a coinbase transaction, the marker output is always invalid
  if (transaction.isCoinBase()) {
    return cb(null, makeUncoloredResponse(transaction));
  }

  // Search transaction outputs for an Open Assets "Marker Output"
  transaction.outs.forEach(function (o, outIdx) {
    // If a valid marker is found, we can stop processing subsequent outputs
    // since, according to the spec: "if multiple valid marker outputs
    // exist in the same transaction, the first one is used and the other
    // ones are considered as regular outputs." [1]
    // [1] https://github.com/OpenAssets/open-assets-protocol/blob/master/specification.mediawiki
    if (!foundMarkerOutput) {
      // Attempt to decode this output as a Marker Output.
      markerOutputPayload = MarkerOutput.prototype.parseScript(o.s);
      // If a valid marker output payload was decoded
      if (markerOutputPayload) {
        // Extract the marker output (asset quantity and metadata) information
        markerOutput = MarkerOutput.prototype.deserializePayload(markerOutputPayload);
        // If valid marker output information was extracted, we have all the
        // information necessary to compute the colored outputs for this tx
        if (markerOutput) {
          foundMarkerOutput = true;

          // Build a recursive backtracking function for each of this
          // transactions inputs. Looking at the colored outputs from
          // transactions linked to this transaction's inputs will allow
          // us to dertermine which assets flow into the current transaction.
          var prevouts = [];
          transaction.inputs().forEach(function (i, idx) {

            prevouts.push(function (fcb) {
              var outHash = buffertools.reverse(i[0]).toString('hex');
              coloringEngine.getOutput(outHash, i[1], fcb);
            });
          }, coloringEngine);

          // Fetch the colored outputs for each previous transaction
          async.parallel(prevouts, function (err, inTxs){
            if (err) return cb(err);

            // Store results of all recursive backtracking
            var inputs = inTxs;

            // Ensure all inputs were processed
            if (inputs.length !== transaction.ins.length) {
              return ("Error processing inputs: expected "
                + transaction.ins.length + " results, got " + inputs.length);
            }

            // Compute the asset ids of the colored outputs
            var outputsWithAssetIds = ColoringEngine.prototype._computeAssetIds(
              inputs,
              outIdx,
              transaction.outs,
              markerOutput.assetQuantities);

            if (outputsWithAssetIds) {
              // If successful, return the colored outputs
              return cb(null, outputsWithAssetIds);
            } else {
              // Otherwise, the transaction should be considered uncolored
              return cb(null, makeUncoloredResponse(transaction));
            }
          });
        }
      }
    }
  }, coloringEngine);

  // If no marker output was encountered in any of the transaction
  // outputs, all transaction outputs are considered uncolored.
  if (!foundMarkerOutput) {
    return cb(null, makeUncoloredResponse(transaction));
  }

};

/**
 * Compute Asset IDs of every output in a transaction
 * @param array(TransactionOutput) inputs            The outputs referenced by the inputs of the transaction
 * @param int                      markerOutputIndex The position of the marker output in the transaction
 * @param array(TransactionOut)    outputs           The outputs of the transaction
 * @param array(int)               assetQuantities   The list of asset quantities of the outputs
 * @param function                 cb                A callback to invoke with the array of computed asset ids
 * @return array(TransactionOutput) An array of transaction outputs with computed asset ids
**/
ColoringEngine.prototype._computeAssetIds = function (inputs, markerOutputIndex, outputs, assetQuantities) {

  var coloringEngine = this,
      result = [],
      assetId,
      issuanceAssetId,
      outputAssetQuantity,
      curInput,
      inputUnitsLeft,
      outputUnitsLeft,
      progress,
      i;

  // If there are more items in the asset quantities list than outputs in
  // the transaction (excluding the marker output), the marker output is
  // deemed invalid
  if (assetQuantities.length > outputs.length - 1) {
    return false;
  }

  // If there is no input in the transaction, the marker output is always invalid
  if (inputs.length == 0) {
    return false;
  }

  // Add the issuance outputs
  issuanceAssetId = coloringEngine.hashScript(inputs[0].script);
  for (i = 0; i < markerOutputIndex; i++) {
    if (i < assetQuantities.length && assetQuantities[i] > 0) {
      result.push(new TransactionOutput(
        outputs[i].v.readInt32LE(0),
        outputs[i].s,
        issuanceAssetId,
        assetQuantities[i],
        OutputType.ISSUANCE));
    } else {
      result.push(new TransactionOutput(
        outputs[i].v.readInt32LE(0),
        outputs[i].s,
        null,
        null,
        OutputType.ISSUANCE));
    }
  }

  // Add the marker output
  result.push(new TransactionOutput(
    outputs[markerOutputIndex].v.readInt32LE(0),
    outputs[markerOutputIndex].s,
    null,
    null,
    OutputType.MARKER_OUTPUT));

  // Add the transfer outputs
  for (i = markerOutputIndex + 1; i < outputs.length; i++) {

    if (i <= assetQuantities.length) {
      outputAssetQuantity = assetQuantities[i-1];
    } else {
      outputAssetQuantity = 0;
    }

    outputUnitsLeft = outputAssetQuantity;
    assetId = null;

    var BACKSTOP = 0;

    curInput = 0;
    assetId  = (inputs[curInput])
      ? inputs[curInput].assetId
      : null;
    inputUnitsLeft = (inputs[curInput])
      ? ((null == inputs[curInput].assetQuantity) ? 0 : inputs[curInput].assetQuantity)
      : 0;

    while (outputUnitsLeft > 0 && (++BACKSTOP < 1000)) {
      // Move to the next input if the current one is depleted
      if (inputUnitsLeft == 0) {
        curInput++;

        // If there are less asset units available than in the outputs
        // the marker output is considered invalid
        if (!inputs[curInput]) {
          return false;
        // Otherwise, use the assetQuantity associated with the current input
        } else {
          inputUnitsLeft = (null == inputs[curInput].assetQuantity) ? 0 : inputs[curInput].assetQuantity;
        }
      }

      // If the current input is colored, assign its asset id to the
      // current output
      if (inputs[curInput].assetId != null) {
        progress = Math.min(inputUnitsLeft, outputUnitsLeft);
        outputUnitsLeft -= progress;
        inputUnitsLeft  -= progress;

        if (assetId == null) {
          // This is the first input to map to this output
          assetId = inputs[curInput].assetId;
        } else if (!buffertools.equals(assetId, inputs[curInput].assetId)) {
          // Another different asset ID has already been assigned to
          // that output. The marker output is considered invalid
          return false;
        }
      }
    }

    result.push(new TransactionOutput(
      outputs[i].v.readInt32LE(0),
      outputs[i].s,
      (outputAssetQuantity > 0) ? assetId : null,
      (outputAssetQuantity > 0) ? outputAssetQuantity : null,
      OutputType.TRANSFER));

  }

  return result;
}

/**
 * Hash a script into an Asset ID using SHA256 followed by RIPEMD160
 * @param Buffer data           The data to hash
 * @return       The resulting Asset ID
**/
ColoringEngine.prototype.hashScript = function (data) {

  return HashUtil.sha256ripe160(data);

}

module.exports = ColoringEngine;
<commit_msg>Remove loop backstop used during development<commit_after>'use strict';

var async        = require('async'),
    buffertools  = require('buffertools'),
    HashUtil     = require('bitcore/util'),
    LEB128       = require('./LEB128'),
    MarkerOutput = require('./MarkerOutput'),
    Opcode       = require('bitcore/lib/Opcode'),
    OutputType   = require('./OutputType'),
    Parser       = require('bitcore/util/BinaryParser'),
    Put          = require('bufferput'),
    Script       = require('bitcore/lib/Script'),
    Transaction  = require('bitcore/lib/Transaction'),
    TransactionOutput = require('./TransactionOutput');

/**
 * The backtracking engine used to find the asset ID and asset quantity
 * of any output.
**/

/**
 * Constructor
 * @param function transactionProvider  A function that accepts a transaction hash,
 *                                      performs a transaction lookup, and populates
 *                                      a callback. The first parameter to this
 *                                      function should be the transaction hash to
 *                                      look up. The second parameter should be a
 *                                      callback function with the following
 *                                      signature: cb(err, data). See the test cases
 *                                      for this class for an example provider using
 *                                      the Bitcore RpcClient library.
**/
function ColoringEngine (transactionProvider) {

  this.transactionProvider = transactionProvider;

}

/**
 * Get an output and information about its asset ID and asset quantity.
 * @param Buffer   transactionHash The hash of the transaction containing the output
 * @param int      outputIndex     The index of the output
 * @param function cb              A callback function to invoke with the result
**/
ColoringEngine.prototype.getOutput = function (transactionHash, outputIndex, cb) {

  var coloringEngine = this;

  coloringEngine.transactionProvider(transactionHash, function (err, data) {
    if (err) return cb(err);

    // Propagate error message from underlying JSON RPC call
    if (data.message) return cb(data.message);

    // Successful lookups will always populate 'result'
    if (!data.result) return cb('Transaction could not be retrieved.');

    // Create and populate a Transaction object using the raw data
    var tx = new Transaction();
    tx.parse(new Buffer(data.result,'hex'));

    // Compute ID and asset quantity of transaction outputs
    coloringEngine.colorTransaction(tx, function (err, data) {
      if (err) return cb(err);

      // If an output matching outputIndex exists, return it
      if (data[outputIndex]) {
        return cb(null, data[outputIndex]);
      }
      // Otherwise, report the error
      else {
        return cb('No data for output matching index ' + outputIndex);
      }

    });

  });

}

/**
 * Compute the asset ID and and asset quantity of every output in the transaction
 * @param Buffer   transaction        The transaction to color
 * @param function cb                 A callback function to invoke with the result. The
 *                                    function should have the signature cb(err, data). If
 *                                    successful, 'data' will be populated with an array
 *                                    containing all the colored outputs of the transaction.
**/
ColoringEngine.prototype.colorTransaction = function (transaction, cb) {

  var coloringEngine = this,
      foundMarkerOutput = false,
      markerOutputPayload = null,
      markerOutput = null;

  // Helper function to make the appropriate response in the case
  // where no valid asset ids were found in a transaction. In
  // this case all of the transaction outputs are considered uncolored.
  var makeUncoloredResponse = function (tx) {
    var outs = [];
    tx.outs.forEach(function (o) {
      outs.push(new TransactionOutput(o.v.readInt32LE(0),o.s));
    });
    return outs;
  };

  // If the transaction is a coinbase transaction, the marker output is always invalid
  if (transaction.isCoinBase()) {
    return cb(null, makeUncoloredResponse(transaction));
  }

  // Search transaction outputs for an Open Assets "Marker Output"
  transaction.outs.forEach(function (o, outIdx) {
    // If a valid marker is found, we can stop processing subsequent outputs
    // since, according to the spec: "if multiple valid marker outputs
    // exist in the same transaction, the first one is used and the other
    // ones are considered as regular outputs." [1]
    // [1] https://github.com/OpenAssets/open-assets-protocol/blob/master/specification.mediawiki
    if (!foundMarkerOutput) {
      // Attempt to decode this output as a Marker Output.
      markerOutputPayload = MarkerOutput.prototype.parseScript(o.s);
      // If a valid marker output payload was decoded
      if (markerOutputPayload) {
        // Extract the marker output (asset quantity and metadata) information
        markerOutput = MarkerOutput.prototype.deserializePayload(markerOutputPayload);
        // If valid marker output information was extracted, we have all the
        // information necessary to compute the colored outputs for this tx
        if (markerOutput) {
          foundMarkerOutput = true;

          // Build a recursive backtracking function for each of this
          // transactions inputs. Looking at the colored outputs from
          // transactions linked to this transaction's inputs will allow
          // us to dertermine which assets flow into the current transaction.
          var prevouts = [];
          transaction.inputs().forEach(function (i, idx) {

            prevouts.push(function (fcb) {
              var outHash = buffertools.reverse(i[0]).toString('hex');
              coloringEngine.getOutput(outHash, i[1], fcb);
            });
          }, coloringEngine);

          // Fetch the colored outputs for each previous transaction
          async.parallel(prevouts, function (err, inTxs){
            if (err) return cb(err);

            // Store results of all recursive backtracking
            var inputs = inTxs;

            // Ensure all inputs were processed
            if (inputs.length !== transaction.ins.length) {
              return ("Error processing inputs: expected "
                + transaction.ins.length + " results, got " + inputs.length);
            }

            // Compute the asset ids of the colored outputs
            var outputsWithAssetIds = ColoringEngine.prototype._computeAssetIds(
              inputs,
              outIdx,
              transaction.outs,
              markerOutput.assetQuantities);

            if (outputsWithAssetIds) {
              // If successful, return the colored outputs
              return cb(null, outputsWithAssetIds);
            } else {
              // Otherwise, the transaction should be considered uncolored
              return cb(null, makeUncoloredResponse(transaction));
            }
          });
        }
      }
    }
  }, coloringEngine);

  // If no marker output was encountered in any of the transaction
  // outputs, all transaction outputs are considered uncolored.
  if (!foundMarkerOutput) {
    return cb(null, makeUncoloredResponse(transaction));
  }

};

/**
 * Compute Asset IDs of every output in a transaction
 * @param array(TransactionOutput) inputs            The outputs referenced by the inputs of the transaction
 * @param int                      markerOutputIndex The position of the marker output in the transaction
 * @param array(TransactionOut)    outputs           The outputs of the transaction
 * @param array(int)               assetQuantities   The list of asset quantities of the outputs
 * @param function                 cb                A callback to invoke with the array of computed asset ids
 * @return array(TransactionOutput) An array of transaction outputs with computed asset ids
**/
ColoringEngine.prototype._computeAssetIds = function (inputs, markerOutputIndex, outputs, assetQuantities) {

  var coloringEngine = this,
      result = [],
      assetId,
      issuanceAssetId,
      outputAssetQuantity,
      curInput,
      inputUnitsLeft,
      outputUnitsLeft,
      progress,
      i;

  // If there are more items in the asset quantities list than outputs in
  // the transaction (excluding the marker output), the marker output is
  // deemed invalid
  if (assetQuantities.length > outputs.length - 1) {
    return false;
  }

  // If there is no input in the transaction, the marker output is always invalid
  if (inputs.length == 0) {
    return false;
  }

  // Add the issuance outputs
  issuanceAssetId = coloringEngine.hashScript(inputs[0].script);
  for (i = 0; i < markerOutputIndex; i++) {
    if (i < assetQuantities.length && assetQuantities[i] > 0) {
      result.push(new TransactionOutput(
        outputs[i].v.readInt32LE(0),
        outputs[i].s,
        issuanceAssetId,
        assetQuantities[i],
        OutputType.ISSUANCE));
    } else {
      result.push(new TransactionOutput(
        outputs[i].v.readInt32LE(0),
        outputs[i].s,
        null,
        null,
        OutputType.ISSUANCE));
    }
  }

  // Add the marker output
  result.push(new TransactionOutput(
    outputs[markerOutputIndex].v.readInt32LE(0),
    outputs[markerOutputIndex].s,
    null,
    null,
    OutputType.MARKER_OUTPUT));

  // Add the transfer outputs
  for (i = markerOutputIndex + 1; i < outputs.length; i++) {

    if (i <= assetQuantities.length) {
      outputAssetQuantity = assetQuantities[i-1];
    } else {
      outputAssetQuantity = 0;
    }

    outputUnitsLeft = outputAssetQuantity;
    assetId = null;

    curInput = 0;
    assetId  = (inputs[curInput])
      ? inputs[curInput].assetId
      : null;
    inputUnitsLeft = (inputs[curInput])
      ? ((null == inputs[curInput].assetQuantity) ? 0 : inputs[curInput].assetQuantity)
      : 0;

    while (outputUnitsLeft > 0) {
      // Move to the next input if the current one is depleted
      if (inputUnitsLeft == 0) {
        curInput++;

        // If there are less asset units available than in the outputs
        // the marker output is considered invalid
        if (!inputs[curInput]) {
          return false;
        // Otherwise, use the assetQuantity associated with the current input
        } else {
          inputUnitsLeft = (null == inputs[curInput].assetQuantity) ? 0 : inputs[curInput].assetQuantity;
        }
      }

      // If the current input is colored, assign its asset id to the
      // current output
      if (inputs[curInput].assetId != null) {
        progress = Math.min(inputUnitsLeft, outputUnitsLeft);
        outputUnitsLeft -= progress;
        inputUnitsLeft  -= progress;

        if (assetId == null) {
          // This is the first input to map to this output
          assetId = inputs[curInput].assetId;
        } else if (!buffertools.equals(assetId, inputs[curInput].assetId)) {
          // Another different asset ID has already been assigned to
          // that output. The marker output is considered invalid
          return false;
        }
      }
    }

    result.push(new TransactionOutput(
      outputs[i].v.readInt32LE(0),
      outputs[i].s,
      (outputAssetQuantity > 0) ? assetId : null,
      (outputAssetQuantity > 0) ? outputAssetQuantity : null,
      OutputType.TRANSFER));

  }

  return result;
}

/**
 * Hash a script into an Asset ID using SHA256 followed by RIPEMD160
 * @param Buffer data           The data to hash
 * @return       The resulting Asset ID
**/
ColoringEngine.prototype.hashScript = function (data) {

  return HashUtil.sha256ripe160(data);

}

module.exports = ColoringEngine;
<|endoftext|>


<commit_before>let categories = [
  {
    id: 0,
    names: {
        "default": "☕ Café y Té"
    }
  },
  {
    id: 1,
    names: {
        "default": "🍽 Americana"
    }
  },
  {
    id: 3,
    names: {
        "default": "🍽 China"
    }
  },
  {
    id: 4,
    names: {
        "default": "🍽 Italiana"
    }
  },
  {
    id: 5,
    names: {
        "default": "🍽 Japonesa"
    }
  },
  {
    id: 6,
    names: {
        "default": "🍽 Mexicana"
    }
  },
  {
    id: 7,
    names: {
        "default": "🍽 Thai"
    }
  },
  {
    id: 8,
    names: {
        "default": "🦐 Mariscos"
    }
  },
  {
    id: 9,
    names: {
        "default": "🍕 Pizza"
    }
  },
  {
    id: 10,
    names: {
        "default": "🍨 Postres"
    }
  },
  {
    id: 11,
    names: {
        "default": "💃 Antro/Club"
    }
  },
  {
    id: 12,
    names: {
        "default": "🍗 Alitas/Boneless"
    }
  },
  {
    id: 13,
    names: {
        "default": "🍿 Entretenimiento"
    }
  },
  {
    id: 14,
    names: {
        "default": "🥗 Ensaladas"
    }
  },
  {
    id: 15,
    names: {
        "default": "🍻 Bar"
    }
  },
  {
    id: 16,
    names: {
        "default": "🍳 Desayunos"
    }
  }

];

module.exports = categories;<commit_msg>🕶🍹 Updated with Glitch<commit_after>let categories = [
  {
    id: 0,
    names: {
        "default": "Café y Té ☕"
    }
  },
  {
    id: 1,
    names: {
        "default": "🍽 Americana"
    }
  },
  {
    id: 3,
    names: {
        "default": "🍽 China"
    }
  },
  {
    id: 4,
    names: {
        "default": "🍽 Italiana"
    }
  },
  {
    id: 5,
    names: {
        "default": "🍽 Japonesa"
    }
  },
  {
    id: 6,
    names: {
        "default": "🍽 Mexicana"
    }
  },
  {
    id: 7,
    names: {
        "default": "🍽 Thai"
    }
  },
  {
    id: 8,
    names: {
        "default": "Mariscos 🦐"
    }
  },
  {
    id: 9,
    names: {
        "default": "Pizza 🍕"
    }
  },
  {
    id: 10,
    names: {
        "default": "Postres 🍨"
    }
  },
  {
    id: 11,
    names: {
        "default": "Antro/Club 💃"
    }
  },
  {
    id: 12,
    names: {
        "default": "Alitas/Boneless 🍗"
    }
  },
  {
    id: 13,
    names: {
        "default": "Entretenimiento 🍿"
    }
  },
  {
    id: 14,
    names: {
        "default": "Ensaladas 🥗"
    }
  },
  {
    id: 15,
    names: {
        "default": "Bar 🍻"
    }
  },
  {
    id: 16,
    names: {
        "default": "Desayunos 🍳"
    }
  },
  {
    id: 17,
    names: {
        "default": "Cortes/Filetes 🍖"
    }
  }

];

module.exports = categories;<|endoftext|>


<commit_before>
if (location.search) {
  var qs = location.search.replace('?', '').split('&')
    .reduce((qs, pair) => (
      ([key, value] = pair.split('=')) => (qs[key] = value, qs)
    )(), {})

  if (qs.login) {
    localStorage.setItem('v-login', qs.login)
  }
  if (qs.admin) {
    localStorage.setItem('v-admin', qs.admin)
  }
  if (qs.jwt) {
    localStorage.setItem('v-jwt', qs.jwt)
  }

  window.location = '/'
}

var v = {
  origin: 'https://simo.varnalab.org/api',
  // origin: 'http://192.168.1.101:3000/varnalab/api',
  prefix: '',

  module: {},
  route: {},
  view: {},
  component: {},

  state: {
    login: localStorage.getItem('v-login'),
    admin: localStorage.getItem('v-admin'),
    jwt: localStorage.getItem('v-jwt'),
    theme: localStorage.getItem('v-theme'),

    route: '',
    title: '',
    avatar: '/assets/images/logo.png',

    toolbar: [
      {path: '/whois', icon: 'directions_run'},
    ],
    menu: {
      items: [
        {theme: 'light', text: 'Светла'},
        {theme: 'shadow', text: 'Тъмна'},
        {theme: 'hacker', text: 'Хакер'},
      ]
    },
    drawer: {
      items: [
        [
          {path: '/', icon: 'refresh', text: '[ Начало ]', route: 'index'},
          {path: '/whois', icon: 'directions_run', text: '[ Хора ]', route: 'whois'}
        ],
        [
          {path: '/login', icon: 'lock_open', text: '[ Влез ]', route: 'login'},
        ],
      ]
    },
    fab: {},
    snackbar: {},
    dialog: {},
  }
}

window.addEventListener('DOMContentLoaded', () => {

  if (location.host === 'box.outofindex.com') {
    v.prefix = '/varnalab/app'
    m.route.prefix(v.prefix)
  }
  else if (location.protocol === 'file:') {
    v.prefix = '/android_asset/www'
  }

  if (location.host === 'ssd') {
    v.state.avatar = v.state.avatar.slice(1)
  }
  v.state.avatar = v.prefix + v.state.avatar

  m.route(document.querySelector('body'), '/', {

    '/': v.route.index(
      v.module.whois(v)
    ),

    '/whois': v.route.whois(
      v.module.whois(v)
    ),

    '/whois/:filter': v.route.whois(
      v.module.whois(v)
    ),

    '/user': v.route.edit(
      // v.module.whois(v),
      v.module.edit(v)
    ),

    '/user/:id': v.route.user(
      v.module.whois(v),
      v.module.user(v)
    ),

    '/login': {
      onmatch: () => window.location = v.origin + '/auth/login'
    }

  })
})
<commit_msg>Set dev origin<commit_after>
if (location.search) {
  var qs = location.search.replace('?', '').split('&')
    .reduce((qs, pair) => (
      ([key, value] = pair.split('=')) => (qs[key] = value, qs)
    )(), {})

  if (qs.login) {
    localStorage.setItem('v-login', qs.login)
  }
  if (qs.admin) {
    localStorage.setItem('v-admin', qs.admin)
  }
  if (qs.jwt) {
    localStorage.setItem('v-jwt', qs.jwt)
  }

  window.location = '/'
}

var v = {
  origin: 'https://simo.varnalab.org/api',
  prefix: '',

  module: {},
  route: {},
  view: {},
  component: {},

  state: {
    login: localStorage.getItem('v-login'),
    admin: localStorage.getItem('v-admin'),
    jwt: localStorage.getItem('v-jwt'),
    theme: localStorage.getItem('v-theme'),

    route: '',
    title: '',
    avatar: '/assets/images/logo.png',

    toolbar: [
      {path: '/whois', icon: 'directions_run'},
    ],
    menu: {
      items: [
        {theme: 'light', text: 'Светла'},
        {theme: 'shadow', text: 'Тъмна'},
        {theme: 'hacker', text: 'Хакер'},
      ]
    },
    drawer: {
      items: [
        [
          {path: '/', icon: 'refresh', text: '[ Начало ]', route: 'index'},
          {path: '/whois', icon: 'directions_run', text: '[ Хора ]', route: 'whois'}
        ],
        [
          {path: '/login', icon: 'lock_open', text: '[ Влез ]', route: 'login'},
        ],
      ]
    },
    fab: {},
    snackbar: {},
    dialog: {},
  }
}

if (location.host === 'box.outofindex.com') {
  v.prefix = '/varnalab/app'
  m.route.prefix(v.prefix)
}

if (location.protocol === 'file:') {
  v.prefix = '/android_asset/www'
}

if (location.host === 'ssd' || location.host ===  '192.168.1.101:8001') {
  v.state.avatar = v.state.avatar.slice(1)
  v.origin = 'http://192.168.1.101:3000/varnalab/api'
}

v.state.avatar = v.prefix + v.state.avatar

window.addEventListener('DOMContentLoaded', () => {

  m.route(document.querySelector('body'), '/', {

    '/': v.route.index(
      v.module.whois(v)
    ),

    '/whois': v.route.whois(
      v.module.whois(v)
    ),

    '/whois/:filter': v.route.whois(
      v.module.whois(v)
    ),

    '/user': v.route.edit(
      // v.module.whois(v),
      v.module.edit(v)
    ),

    '/user/:id': v.route.user(
      v.module.whois(v),
      v.module.user(v)
    ),

    '/login': {
      onmatch: () => window.location = v.origin + '/auth/login'
    }

  })
})
<|endoftext|>


<commit_before>module.exports = {
    meta: {
        docs: {
            description: 'Limit the length of function bodies',
            category: 'Best Practices',
            recommended: false
        },
        schema: [
            {
                type: 'number'
            }
        ]
    },
    create(context) {

        function validateFuncBodyLength(node) {
            const max = context.options[ 0 ];
            const len = context.getSourceCode().getText(node.body).length;

            if (len > max) {
                context.report(
                    node,
                    node.loc.start,
                    `Function body greater than ${max} characters (${len})`
                );
            }
        }

        return {
            ArrowFunctionExpression: validateFuncBodyLength,
            FunctionDeclaration: validateFuncBodyLength,
            FunctionExpression: validateFuncBodyLength,
            MethodDefinition: validateFuncBodyLength
        };
    }
};<commit_msg>Default options to 300<commit_after>module.exports = {
    meta: {
        docs: {
            description: 'Limit the length of function bodies',
            category: 'Best Practices',
            recommended: false
        },
        schema: [
            {
                type: 'number'
            }
        ]
    },
    create(context) {

        function validateFuncBodyLength(node) {
            const max = context.options[ 0 ] || 300;
            const len = context.getSourceCode().getText(node.body).length;

            if (len > max) {
                context.report(
                    node,
                    node.loc.start,
                    `Function body greater than ${max} characters (${len})`
                );
            }
        }

        return {
            ArrowFunctionExpression: validateFuncBodyLength,
            FunctionDeclaration: validateFuncBodyLength,
            FunctionExpression: validateFuncBodyLength,
            MethodDefinition: validateFuncBodyLength
        };
    }
};<|endoftext|>


<commit_before>App.info({
  id: 'br.com.app.sistemameionorte',
  name: 'MNAPPv2',
  description: 'Aplicativo Interativo Sistema Meio Norte de Comunicação',
  version: '0.0.1'
});

App.icons({

  // iOS
  // 'iphone': 'public/icons/ios/Icon-60.png',
  // 'iphone_2x': 'public/icons/ios/AppIcon.appiconset/Icon-60@2x.png',
  // 'ipad': 'public/icons/ios/Icon-72.png',
  // 'ipad_2x': 'public/icons/ios/Icon-72@2x.png',

  // Android
  'android_ldpi': 'public/icons/android/drawable-ldpi/ic_launcher.png',
  'android_mdpi': 'public/icons/android/drawable-mdpi/ic_launcher.png',
  'android_hdpi': 'public/icons/android/drawable-hdpi/ic_launcher.png',
  'android_xhdpi': 'public/icons/android/drawable-xhdpi/ic_launcher.png'

});

App.launchScreens({

  // iOS
  // 'iphone': 'public/images/splash/splash-320x480.png',
  // 'iphone_2x': 'public/images/splash/splash-320x480@2x.png',
  // 'iphone5': 'public/images/splash/splash-320x568@2x.png',
  // 'ipad_portrait': 'public/images/splash/splash-768x1024.png',
  // 'ipad_portrait_2x': 'public/images/splash/splash-768x1024@2x.png',
  // 'ipad_landscape': 'public/images/splash/splash-1024x768.png',
  // 'ipad_landscape_2x': 'public/images/splash/splash-1024x768@2x.png',

  // Android
  'android_ldpi_portrait': 'public/splash/android/android_ldpi_portrait.png',
  'android_ldpi_landscape': 'public/splash/android/android_ldpi_landscape.png',
  'android_mdpi_portrait': 'public/splash/android/android_mdpi_portrait.png',
  'android_mdpi_landscape': 'public/splash/android/android_mdpi_landscape.png',
  'android_hdpi_portrait': 'public/splash/android/android_hdpi_portrait.png',
  'android_hdpi_landscape': 'public/splash/android/android_hdpi_landscape.png',
  'android_xhdpi_portrait': 'public/splash/android/android_xhdpi_portrait.png',
  'android_xhdpi_landscape': 'public/splash/android/android_xhdpi_landscape.png'
});

App.setPreference('BackgroundColor', '0xffffffff');
App.setPreference('HideKeyboardFormAccessoryBar', true);
App.setPreference('StatusBarOverlaysWebView', 'true');
App.setPreference('StatusBarStyle', 'lightcontent');

// cordova plugin facebook
App.configurePlugin('com.phonegap.plugins.facebookconnect', {
     APP_ID: '638977909535835',
     APP_NAME: 'vtv'
});

// // regras de acesso cross origen
App.accessRule("*");
// //App.accessRule("*://localhost:3010/*");
<commit_msg>mobile config<commit_after>App.info({
  id: 'br.com.app.sistemameionorte',
  name: 'MNAPPv2',
  description: 'Aplicativo Interativo Sistema Meio Norte de Comunicação',
  version: '0.0.2'
});

App.icons({

  // iOS
  // 'iphone': 'public/icons/ios/Icon-60.png',
  // 'iphone_2x': 'public/icons/ios/AppIcon.appiconset/Icon-60@2x.png',
  // 'ipad': 'public/icons/ios/Icon-72.png',
  // 'ipad_2x': 'public/icons/ios/Icon-72@2x.png',

  // Android
  'android_ldpi': 'public/icons/android/drawable-ldpi/ic_launcher.png',
  'android_mdpi': 'public/icons/android/drawable-mdpi/ic_launcher.png',
  'android_hdpi': 'public/icons/android/drawable-hdpi/ic_launcher.png',
  'android_xhdpi': 'public/icons/android/drawable-xhdpi/ic_launcher.png'

});

App.launchScreens({

  // iOS
  // 'iphone': 'public/images/splash/splash-320x480.png',
  // 'iphone_2x': 'public/images/splash/splash-320x480@2x.png',
  // 'iphone5': 'public/images/splash/splash-320x568@2x.png',
  // 'ipad_portrait': 'public/images/splash/splash-768x1024.png',
  // 'ipad_portrait_2x': 'public/images/splash/splash-768x1024@2x.png',
  // 'ipad_landscape': 'public/images/splash/splash-1024x768.png',
  // 'ipad_landscape_2x': 'public/images/splash/splash-1024x768@2x.png',

  // Android
  'android_ldpi_portrait': 'public/splash/android/android_ldpi_portrait.png',
  'android_ldpi_landscape': 'public/splash/android/android_ldpi_landscape.png',
  'android_mdpi_portrait': 'public/splash/android/android_mdpi_portrait.png',
  'android_mdpi_landscape': 'public/splash/android/android_mdpi_landscape.png',
  'android_hdpi_portrait': 'public/splash/android/android_hdpi_portrait.png',
  'android_hdpi_landscape': 'public/splash/android/android_hdpi_landscape.png',
  'android_xhdpi_portrait': 'public/splash/android/android_xhdpi_portrait.png',
  'android_xhdpi_landscape': 'public/splash/android/android_xhdpi_landscape.png'
});

App.setPreference('BackgroundColor', '0xffffffff');
App.setPreference('HideKeyboardFormAccessoryBar', true);
App.setPreference('StatusBarOverlaysWebView', 'true');
App.setPreference('StatusBarStyle', 'lightcontent');

// cordova plugin facebook
App.configurePlugin('com.phonegap.plugins.facebookconnect', {
     APP_ID: '638977909535835',
     APP_NAME: 'vtv'
});

// // regras de acesso cross origen
App.accessRule("*");
// //App.accessRule("*://localhost:3010/*");
<|endoftext|>


<commit_before>'use strict';

const ServerCode     = require('../api'),
      events         = require('../events'),
      ServiceWrapper = require('./service'),
      printer        = require('./printer'),
      jsdoc          = require('../../util/jsdoc'),
      logger         = require('../../util/logger');

class Dictionary {
  keys() {
    return Object.keys(this);
  }

  values() {
    return this.keys().map(key => this[key]);
  }
}

class Definitions {
  constructor() {
    this.files = [];
    this.types = {};
  }

  setExplicitly(definitions) {
    this.explicit = true;

    this.files = definitions && definitions.files || [];
    this.types = definitions && definitions.types || [];
  }

  addFile(file) {
    if (!this.explicit && this.files.indexOf(file.relativePath) === -1) {
      this.files.push(file.relativePath);

      const foundClasses = jsdoc.describeClasses(file.absolutePath);
      foundClasses.forEach(classDef => this.types[classDef.name] = classDef);
    }
  }
}

class ServerCodeModel {
  constructor() {
    this.types = new Dictionary();
    this.handlers = new Dictionary();
    this.timers = new Dictionary();
    this.services = new Dictionary();
    this.definitions = new Definitions();
    this.errors = [];
  }

  addHandler(handler) {
    const key = ServerCodeModel.computeHandlerKey(handler.eventId, handler.target);

    if (this.handlers[key]) {
      const methodName = events.get(handler.eventId).name;

      throw new Error(`[${methodName}(${handler.target})] event handler already exists`);
    }

    this.handlers[key] = handler;
  }

  addTimer(timer) {
    if (this.timers[timer.name]) {
      throw new Error(`[${timer.name}] timer already exists`);
    }

    this.timers[timer.name] = timer;
  }

  getHandler(eventId, target) {
    const isTimer = events.get(eventId).provider === events.providers.TIMER;

    return isTimer
      ? this.timers[target]
      : this.handlers[ServerCodeModel.computeHandlerKey(eventId, target)];
  }

  getService(serviceName) {
    return this.services[serviceName];
  }

  addService(service, file) {
    this.definitions.addFile(file);

    if (!service.description) {
      const serviceDef = this.definitions.types[service.name];

      if (serviceDef) {
        service.description = serviceDef.description;
      }
    }

    const serviceWrapper = new ServiceWrapper(service, file.relativePath, this);

    if (this.services[serviceWrapper.name]) {
      throw new Error(`[${serviceWrapper.name}] service already exists`);
    }

    this.services[serviceWrapper.name] = serviceWrapper;
  }

  addType(type, file) {
    this.definitions.addFile(file);

    if (this.types[type.name]) {
      throw new Error(`[${type.name}] custom type already exists`);
    }

    this.types[type.name] = { name: type.name, clazz: type, provider: file.relativePath };
  }

  addError(error, serverCodeFile, erredFile) {
    this.errors.push({
      message : error.message,
      position: error.position,
      serverCodeFile,
      erredFile
    });
  }

  get classMappings() {
    const result = {};

    Object.keys(this.types).forEach(name => {
      result[name] = this.types[name].clazz;
    });

    return result;
  }

  print() {
    printer.print(this);
  }

  toJSON() {
    return {
      handlers   : this.handlers.values(),
      timers     : this.timers.values(),
      services   : this.services.values(),
      definitions: this.definitions,
      types      : this.types,
      errors     : this.errors
    };
  }

  loadFiles(basePath, exclude, files) {
    ServerCode.load(basePath, exclude, this, files);
  }

  isEmpty() {
    return this.handlers.values().length === 0 && this.services.values().length === 0;
  }

  static computeHandlerKey(eventId, target) {
    return [eventId, target].join('-');
  }

  /**
   * @param {String} basePath
   * @param {Array.<String>} [exclude]
   * @returns {ServerCodeModel}
   */
  static build(basePath, exclude) {
    logger.info('Building Model..');

    const model = new ServerCodeModel();
    model.loadFiles(basePath, exclude);

    logger.info('Model Build completed');

    model.print();

    return model;
  }
}

module.exports = ServerCodeModel;<commit_msg>fix (model) 'Nothing to debug/deploy' error when only timers are present<commit_after>'use strict';

const ServerCode     = require('../api'),
      events         = require('../events'),
      ServiceWrapper = require('./service'),
      printer        = require('./printer'),
      jsdoc          = require('../../util/jsdoc'),
      logger         = require('../../util/logger');

class Dictionary {
  keys() {
    return Object.keys(this);
  }

  values() {
    return this.keys().map(key => this[key]);
  }
}

class Definitions {
  constructor() {
    this.files = [];
    this.types = {};
  }

  setExplicitly(definitions) {
    this.explicit = true;

    this.files = definitions && definitions.files || [];
    this.types = definitions && definitions.types || [];
  }

  addFile(file) {
    if (!this.explicit && this.files.indexOf(file.relativePath) === -1) {
      this.files.push(file.relativePath);

      const foundClasses = jsdoc.describeClasses(file.absolutePath);
      foundClasses.forEach(classDef => this.types[classDef.name] = classDef);
    }
  }
}

class ServerCodeModel {
  constructor() {
    this.types = new Dictionary();
    this.handlers = new Dictionary();
    this.timers = new Dictionary();
    this.services = new Dictionary();
    this.definitions = new Definitions();
    this.errors = [];
  }

  addHandler(handler) {
    const key = ServerCodeModel.computeHandlerKey(handler.eventId, handler.target);

    if (this.handlers[key]) {
      const methodName = events.get(handler.eventId).name;

      throw new Error(`[${methodName}(${handler.target})] event handler already exists`);
    }

    this.handlers[key] = handler;
  }

  addTimer(timer) {
    if (this.timers[timer.name]) {
      throw new Error(`[${timer.name}] timer already exists`);
    }

    this.timers[timer.name] = timer;
  }

  getHandler(eventId, target) {
    const isTimer = events.get(eventId).provider === events.providers.TIMER;

    return isTimer
      ? this.timers[target]
      : this.handlers[ServerCodeModel.computeHandlerKey(eventId, target)];
  }

  getService(serviceName) {
    return this.services[serviceName];
  }

  addService(service, file) {
    this.definitions.addFile(file);

    if (!service.description) {
      const serviceDef = this.definitions.types[service.name];

      if (serviceDef) {
        service.description = serviceDef.description;
      }
    }

    const serviceWrapper = new ServiceWrapper(service, file.relativePath, this);

    if (this.services[serviceWrapper.name]) {
      throw new Error(`[${serviceWrapper.name}] service already exists`);
    }

    this.services[serviceWrapper.name] = serviceWrapper;
  }

  addType(type, file) {
    this.definitions.addFile(file);

    if (this.types[type.name]) {
      throw new Error(`[${type.name}] custom type already exists`);
    }

    this.types[type.name] = { name: type.name, clazz: type, provider: file.relativePath };
  }

  addError(error, serverCodeFile, erredFile) {
    this.errors.push({
      message : error.message,
      position: error.position,
      serverCodeFile,
      erredFile
    });
  }

  get classMappings() {
    const result = {};

    Object.keys(this.types).forEach(name => {
      result[name] = this.types[name].clazz;
    });

    return result;
  }

  print() {
    printer.print(this);
  }

  toJSON() {
    return {
      handlers   : this.handlers.values(),
      timers     : this.timers.values(),
      services   : this.services.values(),
      definitions: this.definitions,
      types      : this.types,
      errors     : this.errors
    };
  }

  loadFiles(basePath, exclude, files) {
    ServerCode.load(basePath, exclude, this, files);
  }

  isEmpty() {
    return this.handlers.values().length === 0
      && this.services.values().length === 0
      && this.timers.values().length === 0;
  }

  static computeHandlerKey(eventId, target) {
    return [eventId, target].join('-');
  }

  /**
   * @param {String} basePath
   * @param {Array.<String>} [exclude]
   * @returns {ServerCodeModel}
   */
  static build(basePath, exclude) {
    logger.info('Building Model..');

    const model = new ServerCodeModel();
    model.loadFiles(basePath, exclude);

    logger.info('Model Build completed');

    model.print();

    return model;
  }
}

module.exports = ServerCodeModel;<|endoftext|>


<commit_before>/*
 * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of iotagent-manager
 *
 * iotagent-manager is free software: you can redistribute it and/or
 * modify it 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.
 *
 * iotagent-manager 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 iotagent-manager.
 * If not, seehttp://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with::daniel.moranjimenez@telefonica.com
 */

'use strict';

/*jshint camelcase:false */

var protocols = require('./protocolData'),
    request = require('request'),
    logger = require('logops'),
    errors = require('../errors'),
    _ = require('underscore'),
    async = require('async'),
    context = {
        op: 'IoTAManager.Redirector'
    };

function guessCollection(body) {
    if (body.services) {
        return 'services';
    } else if (body.devices) {
        return 'devices';
    } else {
        return null;
    }
}

function extractProtocols(body) {
    var collectionName = guessCollection(body),
        protocols;

    logger.debug(context, 'Extracting protocols from body for collection [%s]', collectionName);

    function extractProtocol(previous, item) {
        if (item.protocol) {
            if (Array.isArray(item.protocol)) {
                return _.union(previous, item.protocol);
            } else {
                previous.push(item.protocol);

                return _.uniq(previous);
            }
        } else {
            return previous;
        }
    }

    if (collectionName) {
        protocols = body[collectionName].reduce(extractProtocol, []);

        logger.debug(context, 'Protocols found: %j', protocols);

        return protocols;
    } else {
        return null;
    }
}

function queryParamExtractor(req, res, next) {
    if (req.query.protocol) {
        logger.debug(context, 'Protocol found in the query parameters [%s]', req.query.protocol);
        req.protocolId = [req.query.protocol];
    } else if (req.method !== 'GET') {
        req.splitAndRedirect = true;
        req.protocolId = extractProtocols(req.body);
    }

    next();
}

function getProtocols(req, res, next) {
    function addProtocolAsync(previous, item, callback) {
        protocols.get(item, function(error, protocol) {
            if (error) {
                callback(error);
            } else {
                previous[item] = protocol;
                callback(null, previous);
            }
        });
    }

    function addProtocolSync(previous, item) {
        previous[item.protocol] = item;
        return previous;
    }

    function extractId(protocol) {
        return protocol.protocol;
    }

    if (req.protocolId) {
        logger.debug(context, 'Finding objects for the following protocols: %j', req.protocolId);

        async.reduce(req.protocolId, {}, addProtocolAsync, function endReduction(error, result) {
            req.protocolObj = result;
            next(error);
        });
    } else {
        logger.debug(context, 'Finding objects for all the protocols');

        protocols.list(function(error, protocols) {
            if (error) {
                next(error);
            } else {
                req.protocolId = protocols.map(extractId);
                req.protocolObj = protocols.reduce(addProtocolSync, {});
                logger.debug(context, 'Finding objects for this protocol: %j', req.protocolId);
                next();
            }
        });
    }
}

function splitByProtocol(req) {
    var collectionName = guessCollection(req.body),
        collections = {},
        clonedObject;

    for (var i = 0; i < req.body[collectionName].length; i++) {
        if (Array.isArray(req.body[collectionName][i].protocol)) {
            for (var j = 0; j < req.body[collectionName][i].protocol.length; j++) {
                clonedObject = _.clone(req.body[collectionName][i]);

                clonedObject.protocol = req.body[collectionName][i].protocol[j];
                clonedObject.resource = req.protocolObj[clonedObject.protocol].resource;

                if (collections[req.body[collectionName][i].protocol[j]]) {
                    collections[req.body[collectionName][i].protocol[j]][collectionName].push(clonedObject);
                } else {
                    collections[req.body[collectionName][i].protocol[j]] = {};
                    collections[req.body[collectionName][i].protocol[j]][collectionName] = [clonedObject];
                }
            }
        } else {
            clonedObject = _.clone(req.body[collectionName][i]);

            clonedObject.protocol = req.body[collectionName][i].protocol;
            clonedObject.resource = req.protocolObj[clonedObject.protocol].resource;

            collections[req.body[collectionName][i].protocol] = {};
            collections[req.body[collectionName][i].protocol][collectionName] = [_.clone(req.body[collectionName][i])];
        }
    }

    return collections;
}

/**
 * Create the redirected request based on the protocol information and the original request. For backwards compatibility
 * the IoT Agent Manager removes the "/iot" prefix from the protocol address if found.
 *
 * @param {Object} req           Original request object to be forwarded.
 * @param {String} protocol      ID of the protocl where the request will be forwarded
 * @param {Object} body          JSON body of the original request.
 * @return {Object}             Request object prepared to be sent.
 */
function createRequest(req, protocol, body) {
    var options = {
            qs: _.clone(req.query),
            method: req.method,
            headers: req.headers
        },
        protocolObj = req.protocolObj[protocol],
        protocolAddress = protocolObj.iotagent;

    logger.debug(context, 'creating request using protocol:\n\n%j\n', protocolObj);
    delete options.qs.protocol;

    // Save original apikey before be overwrite by apikey from body (probably modified)
    if (req.query.apikey) {
        options.qs.apikeyReq = req.query.apikey;
    }
    if (protocolObj.resource) {
        options.qs.resource = protocolObj.resource;
    }
    if (protocolAddress.substr(-1) === '/') {
        protocolAddress = protocolAddress.slice(0, -1);
    }

    if (protocolAddress.substr(-4) === '/iot') {
        protocolAddress = protocolAddress.slice(0, -4);
    }

    options.uri = protocolAddress + req.path;

    if (body && body.services) {
        body.services = body.services.map(function cleanProtocol(item) {
            delete item.protocol;
            return item;
        });
        // Translate body.services

        if (req.method === 'PUT') {
            body = body.services[0];
        }
    }

    if (req.method === 'PUT' || req.method === 'POST') {
        options.body = JSON.stringify(body);
    }

    delete options.headers['content-length'];
    options.headers.connection = 'close';

    logger.debug(context, 'Forwarding request:\n\n%j\n', options);

    return options;
}

function createRequests(req, res, next) {
    function mapToBody(split, protocol) {
        return createRequest(req, protocol, split[protocol]);
    }

    if (req.splitAndRedirect) {
        var split = splitByProtocol(req);

        logger.debug(context, 'Creating request for multiple elements');

        req.requests = req.protocolId.map(mapToBody.bind(null, split));
    } else {
        logger.debug(context, 'Creating request for redirecting single requests');
        req.requests = [];
        for (var i = 0; i < req.protocolId.length; i++) {
            req.requests.push(createRequest(req, req.protocolId[i], req.body));
        }
    }

    next();
}

function processRequests(req, res, next) {

    function extractStatusCode(result) {
        if (result && result.length === 2 && result[0].statusCode) {
            return result[0].statusCode;
        } else {
            return 0;
        }
    }

    function sendRequest(options, callback) {
        logger.debug(context, 'Sending redirection with following options: %j', options);
        // Use original apikey, not apikey from body
        if (options.qs.apikeyReq) {
            options.qs.apikey = options.qs.apikeyReq;
        }
        request(options, function(error, response, body) {
            if (error) {
                logger.error(context, 'Error found redirecting requests: %j', error);

                callback(new errors.TargetServerError(error));
            } else if (response.statusCode !== 200 &&
                       response.statusCode !== 201 &&
                       response.statusCode !== 204 &&
                       response.statusCode !== 409) {
                logger.error(context, 'Wrong status code detected [%s] and body response [%s] redirecting request',
                             response.statusCode,
                             response.body);
                callback(new errors.TargetServerError(
                    'Wrong status code detected [' + response.statusCode + '] and body response [' + response.body +
                        '] redirecting request'));
            } else if (body && body.length > 0) {
                var parsedBody,
                    parseError;

                try {
                    parsedBody = JSON.parse(body);
                    if (response.statusCode === 409) {
                        parseError = new errors.DuplicateError(parsedBody);
                    }
                    logger.debug(context, 'Response body from the redirected request parsed: %j', parsedBody);
                } catch (e) {
                    logger.error(context, 'Error parsing response body from the redirected request: %s', e);
                    parseError = new errors.TargetResponseError(e);
                }

                callback(parseError, [response, parsedBody]);
            } else {
                callback(null, [response, null]);
            }
        });
    }

    function combineResults(results) {
        /* jshint camelcase: false */

        function arrUnique(arr) {
            var cleaned = [];
            arr.forEach(function(itm) {
                var unique = true;
                cleaned.forEach(function(itm2) {
                    if (_.isEqual(itm, itm2)) {
                        unique = false;
                    }
                });
                if (unique) {
                    cleaned.push(itm);
                }
            });
            return cleaned;
        }

        var totalCount = 0,
            finalResult = [],
            collectionName,
            resultObj = {};

        if (results[0] && results[0][1]) {
            if (results[0][1].services || results[0][1].apikey) {
                collectionName = 'services';
            } else if (results[0][1].devices || results[0][1].device_id) {
                collectionName = 'devices';
            } else {
                return null;
            }
        } else {
            return null;
        }

        for (var i = 0; i < results.length; i++) {
            logger.debug(context, 'results[%s][1] %j ', i, results[i][1]);
            if (results[i][1].count && results[i][1].count > 0) {
                totalCount += results[i][1].count;
            } else if (results[i][1].devices && results[i][1].devices.length > 0) {
                totalCount += results[i][1].devices.length;
            } else {  // simple device
                totalCount++;
            }

            if (results[i][1][collectionName]) {
                finalResult = finalResult.concat(results[i][1][collectionName]);
            } else {
                finalResult = finalResult.concat(results[i][1]);
            }
        }

        // Remove duplicated devices provided by SMS and Repsol IoTAgents
        // for backward compability with old IOTAM cpp
        if (collectionName === 'devices') {
            var finalResultUnique = arrUnique(finalResult);
            var totalCountUnique = finalResultUnique.length;
            logger.debug(context, 'Number of result devices %s and unique %s',
                         totalCount, totalCountUnique);
            finalResult = finalResultUnique;
            totalCount = totalCountUnique;

        }
        resultObj.count = totalCount;
        resultObj[collectionName] = finalResult;

        return resultObj;
    }

    function requestHandler(error, results) {
        var combinedResult;

        if (error) {
            logger.error(context, 'The redirection ended up in error: ', error);
            next(error);
        } else {
            logger.debug(context, 'results %j', results);
            var statusCodes = _.uniq(results.map(extractStatusCode));

            if (statusCodes.length === 1) {
                if (req.method === 'POST' || req.method === 'DELETE' || req.method === 'PUT') {
                    res.status(statusCodes[0]).send();
                } else if (results.length >= 1) {
                    combinedResult = combineResults(results);

                    if (combinedResult) {
                        res.status(statusCodes[0]).json(combinedResult);
                    } else {
                        next(new errors.TargetServerError('No valid replies from any IoTAgent'));
                    }
                } else {
                    res.status(statusCodes[0]).json({});
                }
            } else {
                var errorMsg = 'Wrong status code obtained in some of the responses [' +
                    JSON.stringify(statusCodes) + '] redirecting request';

                logger.error(context, errorMsg);

                next(new errors.TargetServerError(errorMsg));
            }
        }
    }

    async.map(req.requests, sendRequest, requestHandler);
}

function loadContextRoutes(router) {
    var middlewareList = [
        queryParamExtractor,
        getProtocols,
        createRequests,
        processRequests
    ];

    router.post('/iot/services', middlewareList);
    router.post('/iot/devices', middlewareList);
    router.put('/iot/services', middlewareList);
    router.delete('/iot/services', middlewareList);
    router.get('/iot/devices', middlewareList);
    router.get('/iot/devices/:id', middlewareList);
    router.put('/iot/devices/:id', middlewareList);
    router.delete('/iot/devices/:id', middlewareList);

}

exports.loadContextRoutes = loadContextRoutes;

// Ccustom handleError for IOTAM CPP backward compability
function handleError(error, req, res, next)
{
    var code = 500;

    logger.debug(context, 'Error  [%s] handling request: %s', error.name, error.message);

    if (error.code && String(error.code).match(/^[2345]\d\d$/)) {
        code = error.code;
    }

    var response = {
        name: error.name,
        message: error.message
    };
    // Expect IOTAM CPP format: errors[{ endpoint, code, details { name, mesage} }]
    if (error.errors) {
        response.errors = error.errors;
    }
    res.status(code).json(response);
}

exports.handleError = handleError;
<commit_msg>check collectionName before use it<commit_after>/*
 * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of iotagent-manager
 *
 * iotagent-manager is free software: you can redistribute it and/or
 * modify it 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.
 *
 * iotagent-manager 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 iotagent-manager.
 * If not, seehttp://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with::daniel.moranjimenez@telefonica.com
 */

'use strict';

/*jshint camelcase:false */

var protocols = require('./protocolData'),
    request = require('request'),
    logger = require('logops'),
    errors = require('../errors'),
    _ = require('underscore'),
    async = require('async'),
    context = {
        op: 'IoTAManager.Redirector'
    };

function guessCollection(body) {
    if (body.services) {
        return 'services';
    } else if (body.devices) {
        return 'devices';
    } else {
        return null;
    }
}

function extractProtocols(body) {
    var collectionName = guessCollection(body),
        protocols;

    logger.debug(context, 'Extracting protocols from body for collection [%s]', collectionName);

    function extractProtocol(previous, item) {
        if (item.protocol) {
            if (Array.isArray(item.protocol)) {
                return _.union(previous, item.protocol);
            } else {
                previous.push(item.protocol);

                return _.uniq(previous);
            }
        } else {
            return previous;
        }
    }

    if (collectionName) {
        protocols = body[collectionName].reduce(extractProtocol, []);

        logger.debug(context, 'Protocols found: %j', protocols);

        return protocols;
    } else {
        return null;
    }
}

function queryParamExtractor(req, res, next) {
    if (req.query.protocol) {
        logger.debug(context, 'Protocol found in the query parameters [%s]', req.query.protocol);
        req.protocolId = [req.query.protocol];
    } else if (req.method !== 'GET') {
        req.splitAndRedirect = true;
        req.protocolId = extractProtocols(req.body);
    }

    next();
}

function getProtocols(req, res, next) {
    function addProtocolAsync(previous, item, callback) {
        protocols.get(item, function(error, protocol) {
            if (error) {
                callback(error);
            } else {
                previous[item] = protocol;
                callback(null, previous);
            }
        });
    }

    function addProtocolSync(previous, item) {
        previous[item.protocol] = item;
        return previous;
    }

    function extractId(protocol) {
        return protocol.protocol;
    }

    if (req.protocolId) {
        logger.debug(context, 'Finding objects for the following protocols: %j', req.protocolId);

        async.reduce(req.protocolId, {}, addProtocolAsync, function endReduction(error, result) {
            req.protocolObj = result;
            next(error);
        });
    } else {
        logger.debug(context, 'Finding objects for all the protocols');

        protocols.list(function(error, protocols) {
            if (error) {
                next(error);
            } else {
                req.protocolId = protocols.map(extractId);
                req.protocolObj = protocols.reduce(addProtocolSync, {});
                logger.debug(context, 'Finding objects for this protocol: %j', req.protocolId);
                next();
            }
        });
    }
}

function splitByProtocol(req) {
    var collectionName = guessCollection(req.body),
        collections = {},
        clonedObject;

    if (collectionName) {
        for (var i = 0; i < req.body[collectionName].length; i++) {
            if (Array.isArray(req.body[collectionName][i].protocol)) {
                for (var j = 0; j < req.body[collectionName][i].protocol.length; j++) {
                    clonedObject = _.clone(req.body[collectionName][i]);

                    clonedObject.protocol = req.body[collectionName][i].protocol[j];
                    clonedObject.resource = req.protocolObj[clonedObject.protocol].resource;

                    if (collections[req.body[collectionName][i].protocol[j]]) {
                        collections[req.body[collectionName][i].protocol[j]][collectionName].push(clonedObject);
                    } else {
                        collections[req.body[collectionName][i].protocol[j]] = {};
                        collections[req.body[collectionName][i].protocol[j]][collectionName] = [clonedObject];
                    }
                }
            } else {
                clonedObject = _.clone(req.body[collectionName][i]);

                clonedObject.protocol = req.body[collectionName][i].protocol;
                clonedObject.resource = req.protocolObj[clonedObject.protocol].resource;

                collections[req.body[collectionName][i].protocol] = {};
                collections[req.body[collectionName][i].protocol][collectionName] = [_.clone(req.body[collectionName][i])];
            }
        }
    }

    return collections;
}

/**
 * Create the redirected request based on the protocol information and the original request. For backwards compatibility
 * the IoT Agent Manager removes the "/iot" prefix from the protocol address if found.
 *
 * @param {Object} req           Original request object to be forwarded.
 * @param {String} protocol      ID of the protocl where the request will be forwarded
 * @param {Object} body          JSON body of the original request.
 * @return {Object}             Request object prepared to be sent.
 */
function createRequest(req, protocol, body) {
    var options = {
            qs: _.clone(req.query),
            method: req.method,
            headers: req.headers
        },
        protocolObj = req.protocolObj[protocol],
        protocolAddress = protocolObj.iotagent;

    logger.debug(context, 'creating request using protocol:\n\n%j\n', protocolObj);
    delete options.qs.protocol;

    // Save original apikey before be overwrite by apikey from body (probably modified)
    if (req.query.apikey) {
        options.qs.apikeyReq = req.query.apikey;
    }
    if (protocolObj.resource) {
        options.qs.resource = protocolObj.resource;
    }
    if (protocolAddress.substr(-1) === '/') {
        protocolAddress = protocolAddress.slice(0, -1);
    }

    if (protocolAddress.substr(-4) === '/iot') {
        protocolAddress = protocolAddress.slice(0, -4);
    }

    options.uri = protocolAddress + req.path;

    if (body && body.services) {
        body.services = body.services.map(function cleanProtocol(item) {
            delete item.protocol;
            return item;
        });
        // Translate body.services

        if (req.method === 'PUT') {
            body = body.services[0];
        }
    }

    if (req.method === 'PUT' || req.method === 'POST') {
        options.body = JSON.stringify(body);
    }

    delete options.headers['content-length'];
    options.headers.connection = 'close';

    logger.debug(context, 'Forwarding request:\n\n%j\n', options);

    return options;
}

function createRequests(req, res, next) {
    function mapToBody(split, protocol) {
        return createRequest(req, protocol, split[protocol]);
    }

    if (req.splitAndRedirect) {
        var split = splitByProtocol(req);

        logger.debug(context, 'Creating request for multiple elements');

        req.requests = req.protocolId.map(mapToBody.bind(null, split));
    } else {
        logger.debug(context, 'Creating request for redirecting single requests');
        req.requests = [];
        for (var i = 0; i < req.protocolId.length; i++) {
            req.requests.push(createRequest(req, req.protocolId[i], req.body));
        }
    }

    next();
}

function processRequests(req, res, next) {

    function extractStatusCode(result) {
        if (result && result.length === 2 && result[0].statusCode) {
            return result[0].statusCode;
        } else {
            return 0;
        }
    }

    function sendRequest(options, callback) {
        logger.debug(context, 'Sending redirection with following options: %j', options);
        // Use original apikey, not apikey from body
        if (options.qs.apikeyReq) {
            options.qs.apikey = options.qs.apikeyReq;
        }
        request(options, function(error, response, body) {
            if (error) {
                logger.error(context, 'Error found redirecting requests: %j', error);

                callback(new errors.TargetServerError(error));
            } else if (response.statusCode !== 200 &&
                       response.statusCode !== 201 &&
                       response.statusCode !== 204 &&
                       response.statusCode !== 409) {
                logger.error(context, 'Wrong status code detected [%s] and body response [%s] redirecting request',
                             response.statusCode,
                             response.body);
                callback(new errors.TargetServerError(
                    'Wrong status code detected [' + response.statusCode + '] and body response [' + response.body +
                        '] redirecting request'));
            } else if (body && body.length > 0) {
                var parsedBody,
                    parseError;

                try {
                    parsedBody = JSON.parse(body);
                    if (response.statusCode === 409) {
                        parseError = new errors.DuplicateError(parsedBody);
                    }
                    logger.debug(context, 'Response body from the redirected request parsed: %j', parsedBody);
                } catch (e) {
                    logger.error(context, 'Error parsing response body from the redirected request: %s', e);
                    parseError = new errors.TargetResponseError(e);
                }

                callback(parseError, [response, parsedBody]);
            } else {
                callback(null, [response, null]);
            }
        });
    }

    function combineResults(results) {
        /* jshint camelcase: false */

        function arrUnique(arr) {
            var cleaned = [];
            arr.forEach(function(itm) {
                var unique = true;
                cleaned.forEach(function(itm2) {
                    if (_.isEqual(itm, itm2)) {
                        unique = false;
                    }
                });
                if (unique) {
                    cleaned.push(itm);
                }
            });
            return cleaned;
        }

        var totalCount = 0,
            finalResult = [],
            collectionName,
            resultObj = {};

        if (results[0] && results[0][1]) {
            if (results[0][1].services || results[0][1].apikey) {
                collectionName = 'services';
            } else if (results[0][1].devices || results[0][1].device_id) {
                collectionName = 'devices';
            } else {
                return null;
            }
        } else {
            return null;
        }

        for (var i = 0; i < results.length; i++) {
            logger.debug(context, 'results[%s][1] %j ', i, results[i][1]);
            if (results[i][1].count && results[i][1].count > 0) {
                totalCount += results[i][1].count;
            } else if (results[i][1].devices && results[i][1].devices.length > 0) {
                totalCount += results[i][1].devices.length;
            } else {  // simple device
                totalCount++;
            }

            if (results[i][1][collectionName]) {
                finalResult = finalResult.concat(results[i][1][collectionName]);
            } else {
                finalResult = finalResult.concat(results[i][1]);
            }
        }

        // Remove duplicated devices provided by SMS and Repsol IoTAgents
        // for backward compability with old IOTAM cpp
        if (collectionName === 'devices') {
            var finalResultUnique = arrUnique(finalResult);
            var totalCountUnique = finalResultUnique.length;
            logger.debug(context, 'Number of result devices %s and unique %s',
                         totalCount, totalCountUnique);
            finalResult = finalResultUnique;
            totalCount = totalCountUnique;

        }
        resultObj.count = totalCount;
        resultObj[collectionName] = finalResult;

        return resultObj;
    }

    function requestHandler(error, results) {
        var combinedResult;

        if (error) {
            logger.error(context, 'The redirection ended up in error: ', error);
            next(error);
        } else {
            logger.debug(context, 'results %j', results);
            var statusCodes = _.uniq(results.map(extractStatusCode));

            if (statusCodes.length === 1) {
                if (req.method === 'POST' || req.method === 'DELETE' || req.method === 'PUT') {
                    res.status(statusCodes[0]).send();
                } else if (results.length >= 1) {
                    combinedResult = combineResults(results);

                    if (combinedResult) {
                        res.status(statusCodes[0]).json(combinedResult);
                    } else {
                        next(new errors.TargetServerError('No valid replies from any IoTAgent'));
                    }
                } else {
                    res.status(statusCodes[0]).json({});
                }
            } else {
                var errorMsg = 'Wrong status code obtained in some of the responses [' +
                    JSON.stringify(statusCodes) + '] redirecting request';

                logger.error(context, errorMsg);

                next(new errors.TargetServerError(errorMsg));
            }
        }
    }

    async.map(req.requests, sendRequest, requestHandler);
}

function loadContextRoutes(router) {
    var middlewareList = [
        queryParamExtractor,
        getProtocols,
        createRequests,
        processRequests
    ];

    router.post('/iot/services', middlewareList);
    router.post('/iot/devices', middlewareList);
    router.put('/iot/services', middlewareList);
    router.delete('/iot/services', middlewareList);
    router.get('/iot/devices', middlewareList);
    router.get('/iot/devices/:id', middlewareList);
    router.put('/iot/devices/:id', middlewareList);
    router.delete('/iot/devices/:id', middlewareList);

}

exports.loadContextRoutes = loadContextRoutes;

// Ccustom handleError for IOTAM CPP backward compability
function handleError(error, req, res, next)
{
    var code = 500;

    logger.debug(context, 'Error  [%s] handling request: %s', error.name, error.message);

    if (error.code && String(error.code).match(/^[2345]\d\d$/)) {
        code = error.code;
    }

    var response = {
        name: error.name,
        message: error.message
    };
    // Expect IOTAM CPP format: errors[{ endpoint, code, details { name, mesage} }]
    if (error.errors) {
        response.errors = error.errors;
    }
    res.status(code).json(response);
}

exports.handleError = handleError;
<|endoftext|>


<commit_before>(function() {

var lastNotificationTime = 0;

function bind(audio) {
    if (!$) return;

    audio.bind("play", function() {
        sendStartPlaying(audio);
    });

    audio.bind("loadedmetadata", function() {
        sendStartPlaying(audio);
    });

    audio.bind("timeupdate", function() {
        if (getTimestamp() >= lastNotificationTime + LASTIQUE_UPDATE_INTERVAL_SEC)
            sendContinuePlaying(audio);
    });
}

function sendStartPlaying(audio) {
    if (!window.Player || isNaN(getDuration(audio)))
        return;

    lastNotificationTime = getTimestamp();

    var info = getCurrentTrackInfo(audio);
    // console.log("sendStartPlaying: " + "id: " + info.id + "; artist: " + info.artist + "; track: " + info.track);
    sendToBackground({
        event: "start_playing",
        service: "bandcamp.com",
        song: info
    });
}

function sendContinuePlaying(audio) {
    if (!window.Player)
        return;

    lastNotificationTime = getTimestamp();

    // console.log("sendContinuePlaying: " + "id: " + getId(audio));
    sendToBackground({
        event: "continue_playing",
        song: {
            id: getId(audio)
        }
    });
}

function getCurrentTrackInfo(audio) {
    var info = {
        id: getId(audio),
        duration: getDuration(audio),
        downloadUrl: getDownloadUrl(audio)
    }

    var track = getTrack();
    if (!tryGetCompilationArtistAndTrack(track, info)) {
        info.artist = getArtist();
        info.track = track;
    }

    return info;
}

function getMode() {
    if (window.location.hostname == "bandcamp.com")
        return Mode.Discover;

    var path = window.location.pathname.split("/")[1];
    if (!path) // in case of default album
        return Mode.Album;
    if (path == "album")
        return Mode.Album;
    if (path == "track")
        return Mode.Track;
}

function tryGetCompilationArtistAndTrack(track, resultObj) {
    if (getMode() != Mode.Album)
        return false;

    var result = true;
    var delimiter = " - "
    $("#track_table .title span[itemprop='name']").each(function() {
        var title = $(this).text();
        if (title.indexOf(delimiter) == -1)
            result = false;
    });

    if (result && typeof(resultObj) === "object") {
        var pos = track.indexOf(delimiter);
        resultObj.artist = track.substr(0, pos);
        resultObj.track = track.substr(pos + 3);
    }

    return result;
}

function getId(audio) {
    var src = audio.attr("src");

    var idStrPos = Math.max(src.indexOf("&id="), src.indexOf("?id="));
    if (idStrPos == -1)
        return getArtist() + " - " + getTrack();

    var idPos = idStrPos + 4;
    var idStrEnd = src.indexOf("&", idStrPos + 1);
    return idStrEnd == -1 ? src.substr(idPos) : src.substr(idPos, idStrEnd - idPos);
}

function getDuration(audio) {
    return audio.prop("duration");
}

function getTrack() {
    var mode = getMode();
    if (mode == Mode.Album)
        return $(".track_info .title").text().trim();
    if (mode == Mode.Track)
        return $("#name-section .trackTitle").text().trim();
    if (mode == Mode.Discover) {
        var defaultPlayerPlaying = $(".detail-player .playing,.busy").parents(".discover-detail-inner");
        if (defaultPlayerPlaying.length)
            return defaultPlayerPlaying.find(".track_info .title-section").text().trim();

        var notableItemPlaying = $("#notable .result-current a.playing .plb-btn").parents("div.notable-item");
        if (notableItemPlaying.length)
            return notableItemPlaying.find(".item-title span").first().text().trim();
    }
}

function getArtist() {
    var mode = getMode();

    if (mode == Mode.Album || mode == Mode.Track) {
        var bandName = $("#band-name-location .title").text();
        var byArtist = $("span[itemprop=byArtist]").text();
        return (bandName || byArtist).trim();
    }

    if (mode == Mode.Discover) {
        var defaultPlayerPlaying = $(".detail-player .busy,.playing").parents(".discover-detail-inner");
        if (defaultPlayerPlaying.length)
            return defaultPlayerPlaying.find(".detail-body .detail-artist a").text().trim();

        var notableItemPlaying = $("#notable .result-current a.playing .plb-btn").parents("div.notable-item");
        if (notableItemPlaying.length)
            return notableItemPlaying.find(".item-title .item-artist span").first().text().trim();
    }
}

function getDownloadUrl(audio) {
    return audio.attr("src");
}

var Mode = { Album : 0, Track : 1, Discover : 2 };

var mode = getMode();
if (mode == Mode.Album || mode == Mode.Track)
    bind($("audio").first());
if (mode == Mode.Discover)
    $(document).ajaxComplete(function() {
        bind($("audio").eq(1));
        bind($("audio").eq(2));
    });
})();
<commit_msg>band-name-location tag might contain record label name as well, so stop using it<commit_after>(function() {

var lastNotificationTime = 0;

function bind(audio) {
    if (!$) return;

    audio.bind("play", function() {
        sendStartPlaying(audio);
    });

    audio.bind("loadedmetadata", function() {
        sendStartPlaying(audio);
    });

    audio.bind("timeupdate", function() {
        if (getTimestamp() >= lastNotificationTime + LASTIQUE_UPDATE_INTERVAL_SEC)
            sendContinuePlaying(audio);
    });
}

function sendStartPlaying(audio) {
    if (!window.Player || isNaN(getDuration(audio)))
        return;

    lastNotificationTime = getTimestamp();

    var info = getCurrentTrackInfo(audio);
    // console.log("sendStartPlaying: " + "id: " + info.id + "; artist: " + info.artist + "; track: " + info.track);
    sendToBackground({
        event: "start_playing",
        service: "bandcamp.com",
        song: info
    });
}

function sendContinuePlaying(audio) {
    if (!window.Player)
        return;

    lastNotificationTime = getTimestamp();

    // console.log("sendContinuePlaying: " + "id: " + getId(audio));
    sendToBackground({
        event: "continue_playing",
        song: {
            id: getId(audio)
        }
    });
}

function getCurrentTrackInfo(audio) {
    var info = {
        id: getId(audio),
        duration: getDuration(audio),
        downloadUrl: getDownloadUrl(audio)
    }

    var track = getTrack();
    if (!tryGetCompilationArtistAndTrack(track, info)) {
        info.artist = getArtist();
        info.track = track;
    }

    return info;
}

function getMode() {
    if (window.location.hostname == "bandcamp.com")
        return Mode.Discover;

    var path = window.location.pathname.split("/")[1];
    if (!path) // in case of default album
        return Mode.Album;
    if (path == "album")
        return Mode.Album;
    if (path == "track")
        return Mode.Track;
}

function tryGetCompilationArtistAndTrack(track, resultObj) {
    if (getMode() != Mode.Album)
        return false;

    var result = true;
    var delimiter = " - "
    $("#track_table .title span[itemprop='name']").each(function() {
        var title = $(this).text();
        if (title.indexOf(delimiter) == -1)
            result = false;
    });

    if (result && typeof(resultObj) === "object") {
        var pos = track.indexOf(delimiter);
        resultObj.artist = track.substr(0, pos);
        resultObj.track = track.substr(pos + 3);
    }

    return result;
}

function getId(audio) {
    var src = audio.attr("src");

    var idStrPos = Math.max(src.indexOf("&id="), src.indexOf("?id="));
    if (idStrPos == -1)
        return getArtist() + " - " + getTrack();

    var idPos = idStrPos + 4;
    var idStrEnd = src.indexOf("&", idStrPos + 1);
    return idStrEnd == -1 ? src.substr(idPos) : src.substr(idPos, idStrEnd - idPos);
}

function getDuration(audio) {
    return audio.prop("duration");
}

function getTrack() {
    var mode = getMode();
    if (mode == Mode.Album)
        return $(".track_info .title").text().trim();
    if (mode == Mode.Track)
        return $("#name-section .trackTitle").text().trim();
    if (mode == Mode.Discover) {
        var defaultPlayerPlaying = $(".detail-player .playing,.busy").parents(".discover-detail-inner");
        if (defaultPlayerPlaying.length)
            return defaultPlayerPlaying.find(".track_info .title-section").text().trim();

        var notableItemPlaying = $("#notable .result-current a.playing .plb-btn").parents("div.notable-item");
        if (notableItemPlaying.length)
            return notableItemPlaying.find(".item-title span").first().text().trim();
    }
}

function getArtist() {
    var mode = getMode();

    if (mode == Mode.Album || mode == Mode.Track)
        return $("span[itemprop=byArtist]").text().trim();

    if (mode == Mode.Discover) {
        var defaultPlayerPlaying = $(".detail-player .busy,.playing").parents(".discover-detail-inner");
        if (defaultPlayerPlaying.length)
            return defaultPlayerPlaying.find(".detail-body .detail-artist a").text().trim();

        var notableItemPlaying = $("#notable .result-current a.playing .plb-btn").parents("div.notable-item");
        if (notableItemPlaying.length)
            return notableItemPlaying.find(".item-title .item-artist span").first().text().trim();
    }
}

function getDownloadUrl(audio) {
    return audio.attr("src");
}

var Mode = { Album : 0, Track : 1, Discover : 2 };

var mode = getMode();
if (mode == Mode.Album || mode == Mode.Track)
    bind($("audio").first());
if (mode == Mode.Discover)
    $(document).ajaxComplete(function() {
        bind($("audio").eq(1));
        bind($("audio").eq(2));
    });
})();
<|endoftext|>


<commit_before>Split(['#left', '#center', '#right'], {
  minSize: 200,
})

Split(['#left-top', '#left-bottom'], {
  sizes: [75, 25],
  direction: 'vertical'
})

Split(['#center-top', '#center-bottom'], {
  sizes: [75, 25],
  direction: 'vertical'
})

ZMarkdown.use(ZMarkdownZHTML);
ZMarkdown.use(ZMarkdownZLatex);

ZMarkdown.setDefaultModule("zhtml");

const ansiUp = new AnsiUp()
const editor = document.querySelector('#left-top > textarea')
const ast = document.querySelector('#left-bottom > pre > code')
const render = document.querySelector('#center-top > div')
const html = document.querySelector('#center-bottom > pre > code')
const latex = document.querySelector('#right > pre')
editor.addEventListener('input', update)

document.addEventListener('DOMContentLoaded', update)

function buildSpoilers (elems) {
  elems.forEach(elem => {
    if (!elem.classList.contains('spoiler-build')) {
      let a = document.createElement('a');
      a.textContent = 'Afficher/Masquer le contenu masqué'
      a.classList.add('spoiler-title')
      a.classList.add('ico-after')
      a.classList.add('view')
      a.href = '#'
      a.onclick = (e) => {
        elem.style.display = !elem.style.display || elem.style.display === 'none' ? 'block' : 'none'
        e.preventDefault()
      }
      elem.parentNode.insertBefore(a, elem)
    }
  })
}

function update () {
  ZMarkdown.render(editor.value).then((vFile) => {
    render.innerHTML = vFile.toString().trim()
    html.textContent = vFile.toString().trim()
    buildSpoilers(render.querySelectorAll(".custom-block-spoiler"))
  })
  ZMarkdown.render(editor.value, 'zlatex').then((vFile) => {
    latex.textContent = vFile.toString().trim()
  })
  const mdast = ZMarkdown.parse(editor.value)
  ast.innerHTML = ansiUp.ansi_to_html(ZMarkdown.getParser().inspect.color(mdast))
}
<commit_msg>chore: Add missing spoiler-build style class when spoiler mechanism are built<commit_after>Split(['#left', '#center', '#right'], {
  minSize: 200,
})

Split(['#left-top', '#left-bottom'], {
  sizes: [75, 25],
  direction: 'vertical'
})

Split(['#center-top', '#center-bottom'], {
  sizes: [75, 25],
  direction: 'vertical'
})

ZMarkdown.use(ZMarkdownZHTML);
ZMarkdown.use(ZMarkdownZLatex);

ZMarkdown.setDefaultModule("zhtml");

const ansiUp = new AnsiUp()
const editor = document.querySelector('#left-top > textarea')
const ast = document.querySelector('#left-bottom > pre > code')
const render = document.querySelector('#center-top > div')
const html = document.querySelector('#center-bottom > pre > code')
const latex = document.querySelector('#right > pre')
editor.addEventListener('input', update)

document.addEventListener('DOMContentLoaded', update)

function buildSpoilers (elems) {
  elems.forEach(elem => {
    if (!elem.classList.contains('spoiler-build')) {
      let a = document.createElement('a');
      a.textContent = 'Afficher/Masquer le contenu masqué'
      a.classList.add('spoiler-title')
      a.classList.add('ico-after')
      a.classList.add('view')
      a.href = '#'
      a.onclick = (e) => {
        elem.style.display = !elem.style.display || elem.style.display === 'none' ? 'block' : 'none'
        e.preventDefault()
      }
      elem.parentNode.insertBefore(a, elem)
      elem.classList.add('spoiler-build')
    }
  })
}

function update () {
  ZMarkdown.render(editor.value).then((vFile) => {
    render.innerHTML = vFile.toString().trim()
    html.textContent = vFile.toString().trim()
    buildSpoilers(render.querySelectorAll(".custom-block-spoiler"))
  })
  ZMarkdown.render(editor.value, 'zlatex').then((vFile) => {
    latex.textContent = vFile.toString().trim()
  })
  const mdast = ZMarkdown.parse(editor.value)
  ast.innerHTML = ansiUp.ansi_to_html(ZMarkdown.getParser().inspect.color(mdast))
}
<|endoftext|>