<commit_before>var net = require('net'),
    Logger = require('../services/Logger');

module.exports = (function() {

  var DeviceController = {},
      _log = Logger('DeviceController');

  DeviceController.find = function(req, res) {
    var current_user = parseInt(req.session.userid, 10);

    if(!(current_user > 0)) return res.forbidden();

    function error(err) {
      sails._log(err);
      res.badRequest('');
    }

    function found(err, devices) {
      if(err) return error(err);
      var valid = [], i = 0, l = devices.length;

      for(i; i < l; i++) {
        var has_permission = devices[i].permissions.length > 0;
        if(has_permission) valid.push(devices[i]);
      }

      return res.json(valid);
    }

    _log('looking for devies for user: ' + current_user);
    Device.find().populate('permissions', {user: current_user}).exec(found);
  };

  DeviceController.create = function(req, res, next) {
    var body = req.body,
        serial = body ? body.serial_number : false,
        name =  body ? body.name : false,
        user_id = parseInt(req.session.userid, 10),
        created_device, found_serial;

    if(!(user_id > 0))
      return res.forbidden();

    if(!serial || !name)
      return res.badRequest('missing device name or serial');

    function finish(err) {
      if(err) return res.badRequest(err);

      return res.json(created_device);
    }

    function created(err, device) {
      if(err) return res.badRequest(err);

      created_device = device;

      DevicePermissionManager.grant({
        device: device.id,
        target: user_id,
        level: DevicePermissionManager.LEVELS.DEVICE_OWNER,
        force: true
      }, finish);
    }

    function duplicateCheck(err, devices) {
      if(err) return res.badRequest(err);

      if(devices.length > 0) {
        _log('duplicated device, uh oh');
        return res.badRequest('invalid serial');
      }

      Device.create({
        serial_number: found_serial.id,
        name: name,
        registered_name: name
      }, created);
    }

    function foundSerial(err, serial_record) {
      if(err) return res.badRequest(err);

      if(!serial_record) {
        _log('attempt made to create device with bad serial');
        return res.badRequest('invalid serial');
      }

      found_serial = serial_record;
      Device.find({serial_number: serial_record.id}, duplicateCheck);
    }

    DeviceSerial.findOne({serial_number: serial}, foundSerial);
  };

  DeviceController.update = function(req, res, next) {
    var user_id = parseInt(req.session.userid, 10),
        device_id = parseInt(req.params.id, 10);

    function finish(err, device) {
      if(err) {
        _log('failed saving after update err['+err+']');
        return res.status(422).json(err);
      }

      return res.status(200).json(device[0]);
    }

    function foundDevice(err, device) {
      if(err) {
        _log('failed getting device for updating');
        return res.notFound();
      }

      if(!device) {
        _log('unable to find device for update');
        return res.notFound();
      }

      _log('found device, checking permissions');

      var allowed = false,
          LEVELS = DevicePermissionManager.LEVELS;

      for(var i = 0; i < device.permissions.length; i++) {
        var current = device.permissions[i],
            is_current = current.user === user_id,
            is_owner = current.level == LEVELS.DEVICE_OWNER;

        if(is_current && is_owner)
          allowed = true;
      }

      if(!allowed) {
        _log('current user not allowed to update the device, fail out');
        return res.notFound();
      }

      var updates = {},
          body = req.body || {};

      if(body.name)
        updates.name = body.name;

      Device.update({id: device_id}, updates).exec(finish);
    }

    _log('attempting to get device info for device['+device_id+']');
    Device.findOne({id: device_id}).populate('permissions').exec(foundDevice);
  };

  DeviceController.destroy = function(req, res, next) {
    var device_id = parseInt(req.params.id, 10),
        user_id = req.session.userid;

    function destroyed(err, device) {
      return res.status(200).send('');
    }

    function finish(err, device) {
      if(err) {
        _log('errored finding device: ' + err);
        return res.status(404).send('');
      }

      if(!device) {
        _log('unable to find device for destroy');
        return res.status(404).send('');
      }

      _log('found device');

      var can_destroy = false,
          permissions = device.permissions,
          levels = DevicePermissionManager.LEVELS;

      for(var i = 0; i < permissions.length; i++) {
        var permission = permissions[i],
            is_owner =  permission.level === levels.DEVICE_OWNER,
            is_current_user = permission.user === user_id;

        if(!is_current_user)
          continue;

        if(is_owner)
          can_destroy = true;
        else
          can_destroy = false;

        break;
      }

      if(can_destroy)
        device.destroy(destroyed);
      else
        return res.status(404).send('');
    }

    if(device_id >= 0)
      Device.findOne(device_id).populate('permissions').exec(finish);
    else
      return res.status(404).send('');
  };

  DeviceController.findOne = function(req, res, next) {
    var device_id = parseInt(req.params.id, 10);

    function finish(err, device) {
      if(err) {
        _log('errored finding device: ' + err);
        return rest.status(404).send('');
      }

      return device ? res.json(device) : res.status(404).send('');
    }

    if(!(device_id >= 0)) return res.badRequest('invalid device id');

    Device.findOne(device_id)
      .populate('permissions')
      .exec(finish);
  };

  return DeviceController;

})();

<commit_msg>[LFTAPI-57] correcting misplaced log reference in device controller<commit_after>var net = require('net'),
    Logger = require('../services/Logger');

module.exports = (function() {

  var DeviceController = {},
      _log = Logger('DeviceController');

  DeviceController.find = function(req, res) {
    var current_user = parseInt(req.session.userid, 10);

    if(!(current_user > 0)) return res.forbidden();

    function error(err) {
      _log(err);
      res.badRequest('');
    }

    function found(err, devices) {
      if(err) return error(err);
      var valid = [], i = 0, l = devices.length;

      for(i; i < l; i++) {
        var has_permission = devices[i].permissions.length > 0;
        if(has_permission) valid.push(devices[i]);
      }

      return res.json(valid);
    }

    _log('looking for devies for user: ' + current_user);
    Device.find().populate('permissions', {user: current_user}).exec(found);
  };

  DeviceController.create = function(req, res, next) {
    var body = req.body,
        serial = body ? body.serial_number : false,
        name =  body ? body.name : false,
        user_id = parseInt(req.session.userid, 10),
        created_device, found_serial;

    if(!(user_id > 0))
      return res.forbidden();

    if(!serial || !name)
      return res.badRequest('missing device name or serial');

    function finish(err) {
      if(err) return res.badRequest(err);

      return res.json(created_device);
    }

    function created(err, device) {
      if(err) return res.badRequest(err);

      created_device = device;

      DevicePermissionManager.grant({
        device: device.id,
        target: user_id,
        level: DevicePermissionManager.LEVELS.DEVICE_OWNER,
        force: true
      }, finish);
    }

    function duplicateCheck(err, devices) {
      if(err) return res.badRequest(err);

      if(devices.length > 0) {
        _log('duplicated device, uh oh');
        return res.badRequest('invalid serial');
      }

      Device.create({
        serial_number: found_serial.id,
        name: name,
        registered_name: name
      }, created);
    }

    function foundSerial(err, serial_record) {
      if(err) return res.badRequest(err);

      if(!serial_record) {
        _log('attempt made to create device with bad serial');
        return res.badRequest('invalid serial');
      }

      found_serial = serial_record;
      Device.find({serial_number: serial_record.id}, duplicateCheck);
    }

    DeviceSerial.findOne({serial_number: serial}, foundSerial);
  };

  DeviceController.update = function(req, res, next) {
    var user_id = parseInt(req.session.userid, 10),
        device_id = parseInt(req.params.id, 10);

    function finish(err, device) {
      if(err) {
        _log('failed saving after update err['+err+']');
        return res.status(422).json(err);
      }

      return res.status(200).json(device[0]);
    }

    function foundDevice(err, device) {
      if(err) {
        _log('failed getting device for updating');
        return res.notFound();
      }

      if(!device) {
        _log('unable to find device for update');
        return res.notFound();
      }

      _log('found device, checking permissions');

      var allowed = false,
          LEVELS = DevicePermissionManager.LEVELS;

      for(var i = 0; i < device.permissions.length; i++) {
        var current = device.permissions[i],
            is_current = current.user === user_id,
            is_owner = current.level == LEVELS.DEVICE_OWNER;

        if(is_current && is_owner)
          allowed = true;
      }

      if(!allowed) {
        _log('current user not allowed to update the device, fail out');
        return res.notFound();
      }

      var updates = {},
          body = req.body || {};

      if(body.name)
        updates.name = body.name;

      Device.update({id: device_id}, updates).exec(finish);
    }

    _log('attempting to get device info for device['+device_id+']');
    Device.findOne({id: device_id}).populate('permissions').exec(foundDevice);
  };

  DeviceController.destroy = function(req, res, next) {
    var device_id = parseInt(req.params.id, 10),
        user_id = req.session.userid;

    function destroyed(err, device) {
      return res.status(200).send('');
    }

    function finish(err, device) {
      if(err) {
        _log('errored finding device: ' + err);
        return res.status(404).send('');
      }

      if(!device) {
        _log('unable to find device for destroy');
        return res.status(404).send('');
      }

      _log('found device');

      var can_destroy = false,
          permissions = device.permissions,
          levels = DevicePermissionManager.LEVELS;

      for(var i = 0; i < permissions.length; i++) {
        var permission = permissions[i],
            is_owner =  permission.level === levels.DEVICE_OWNER,
            is_current_user = permission.user === user_id;

        if(!is_current_user)
          continue;

        if(is_owner)
          can_destroy = true;
        else
          can_destroy = false;

        break;
      }

      if(can_destroy)
        device.destroy(destroyed);
      else
        return res.status(404).send('');
    }

    if(device_id >= 0)
      Device.findOne(device_id).populate('permissions').exec(finish);
    else
      return res.status(404).send('');
  };

  DeviceController.findOne = function(req, res, next) {
    var device_id = parseInt(req.params.id, 10);

    function finish(err, device) {
      if(err) {
        _log('errored finding device: ' + err);
        return rest.status(404).send('');
      }

      return device ? res.json(device) : res.status(404).send('');
    }

    if(!(device_id >= 0)) return res.badRequest('invalid device id');

    Device.findOne(device_id)
      .populate('permissions')
      .exec(finish);
  };

  return DeviceController;

})();

<|endoftext|>


<commit_before>var webpack = require("webpack");
var path = require("path");
module.exports = {
  devtool: "source-map",

  context: __dirname, //  + "/../app/assets/javascripts",
  entry: [
    "webpack-dev-server/client?http://localhost:3000",
    "webpack/hot/dev-server",
    "./scripts/webpack_only",
    "./assets/javascripts/example"
  ],
  // Note, this file is not actually saved, but used by the express server
  output: {
    filename: "express-bundle.js",
    path: __dirname
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ],

  // Let's load jQuery from the CDN
  externals: {
    jquery: "var jQuery"
  },
  resolve: {
    root: [ path.join(__dirname, "scripts"), path.join(__dirname, "assets/javascripts"),
            path.join(__dirname, "assets/stylesheets") ],
    extensions: ["", ".webpack.js", ".web.js", ".js", ".jsx", ".scss", ".css", "config.js"]
  },
  module: {
    loaders: [
      { test: require.resolve("react"), loader: "expose?React" },
      { test: /\.jsx$/, loaders: ["react-hot", "es6", "jsx?harmony"] },
      { test: /\.css$/, loader: "style-loader!css-loader" },
      { test: /\.scss$/, loader: "style!css!sass?outputStyle=expanded&imagePath=/assets/images"}
    ]
  }
};
<commit_msg>Tweaks to source map<commit_after>var webpack = require("webpack");
var path = require("path");
module.exports = {
  devtool: "eval-source-map",

  context: __dirname, //  + "/../app/assets/javascripts",
  entry: [
    "webpack-dev-server/client?http://localhost:3000",
    "webpack/hot/dev-server",
    "./scripts/webpack_only",
    "./assets/javascripts/example"
  ],
  // Note, this file is not actually saved, but used by the express server
  output: {
    filename: "express-bundle.js",
    path: __dirname
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ],

  // Let's load jQuery from the CDN
  externals: {
    jquery: "var jQuery"
  },
  resolve: {
    root: [ path.join(__dirname, "scripts"), path.join(__dirname, "assets/javascripts"),
            path.join(__dirname, "assets/stylesheets") ],
    extensions: ["", ".webpack.js", ".web.js", ".js", ".jsx", ".scss", ".css", "config.js"]
  },
  module: {
    loaders: [
      { test: require.resolve("react"), loader: "expose?React" },
      { test: /\.jsx$/, loaders: ["react-hot", "es6", "jsx?harmony"] },
      { test: /\.css$/, loader: "style-loader!css-loader" },
      { test: /\.scss$/, loader: "style!css!sass?outputStyle=expanded&imagePath=/assets/images"}
    ]
  }
};
<|endoftext|>


<commit_before>/**
	Jonathan Stassen - June 2015
	https://github.com/TheBox193/diningin-enhancements
	Feature requests: send a Slack, Email, or PR
	A quick set of enhancmetns to dingingin.com
	Eat it up!
**/

// var TAX = 0.1248;
var TAX = 0.105;
var options;

function menuHighlight() {
	$(".menu-item").each(function(){
		var t=$(this).closest("tr"),
		price = getPriceByEl( t ),
		priceWithTax = Number( price * ( 1 + TAX ) ).toFixed(2);
		if( priceWithTax <= ( Number(options.doHighlightUnderValue) || 11 ) )
			t.css("background-color","lightblue");
	});
}

function fixCheckout() {
	var chekcoutButton = $(".ibtnContinueBtnBev")[0];
	if(chekcoutButton && chekcoutButton !== "/checkout") {
		chekcoutButton.href = "/checkout";
	}
}

function getLoved() {
	var loved = [];
	$("#MostedLoved .menu-item-data").each(function(){
		loved.push(parseInt(this.textContent, 10));
	});
	return loved;
}

function setLoved(id) {
	getMenuItemElById(id)
		.find('.ItemName')
		.prepend('❤');
}

function getPriceByEl(el) {
	return el.find(".ItemCost").text().replace(/[^0-9\.]+/g,"");
}

function getPriceById(id) {
	return getPriceByEl( getMenuItemElById(id) );
}

function getMenuItemElById(id) {
	return $('#'+id).closest("tr");
}

function showLoved() {
	var loved = getLoved();
	$.each(loved, function(index, id) {
		setLoved(id);
	});
}

function removeExtraneousElements() {
	var elementsToRemove = [
		/** Calendar */
		"#spnAddressInfo", // address
		"#MPSContent > #tabnav", // tabs
		"#mps_time_header_container", // delivery time

		/** Cart */
		"#ctl00_pageContentPlaceholder_cart_pnlTimeCart1", // delivery time
		"#menu_delivery_address", // address

		/** Menu */
		"#ctl00_pageContentPlaceholder_CRestaurantTopBar_imgIsPremier", // premier badge
		"#DIVAlertNote", // food alergies
		"#spnCategoryDDL", // catagory label
		// "#MenuPageMenu li:contains('ZAGAT')", // Zagat
		// "#MenuPageMenu li:contains('TIMES')", // Restraunt Times
		"#RestMenu td:contains('no prices')", // no prices pdf

		/** Checkout */
		"#payment_info_title_container", // label
		".tip_container", // tip
		"#pnlStepTipsCoupons .clear", // paddings
		"#menu_your_order_delivery_time", // delivery time
		".phone_number_email_container_item2 .email-receipts-message", // going green
		".checkout_left_container_mid_payment_method #menu_delivery_time", // delivery time
		".checkout_left_container_mid_payment_method #menu_delivery_loc", // delivery location
		".checkout_left_container_mid_payment_method .delivery_instructions_for_driver_container:first", // number of people
		".checkout_left_container_mid_payment_method #select_payment_method_container:contains('following options')", // number of people
		"#divPaymentContainer span:contains('is this')", // 'what is this'
		"#CartContainer #ctl01_pnlOrderPaymentMsg", // false positive success before success checkout
		".tip_coupon_gift_certificates_title", // delivery instructions title
		"#pnlStepDeliveryInfo .checkout_left_container", // delivery instructions

		/** Footer */
		"#footer_container", // Footer
		"#InnerFooter_Container", // Footer
	];

	elementsToRemove.map(removeElement);
}

function removeExtraneousMenuSections() {
	var elementsToRemove = [
		".MenuList:contains('12 Cans of Diet Coke')", // Beverages
		".MenuList:contains('Large Catering Tongs')", // Options
		".MenuList:contains('Single Use Canned Heat')", // Service Items
	];

	elementsToRemove.map(removeElement);
}

function removeElement(el) {
	var $el = $(el) ;
	if ($el) {
		$el.remove();
	}
}
chrome.storage.sync.get({
	doHighlightUnder: true,
	doHighlightUnderValue: 11,
	doShowHearts: true,
	doSkipDrinks: true,
	doRemoveExtraneousElements: false,
	doRemoveExtraneousMenuSections: false,
	doRemoveBackground: true
}, function(items) {
	options = items;
	$(function() {
		setup();
	});
});

function setup() {
	if (options.doRemoveBackground) {
		document.body.style.background = 'white';
	}

	if (options.doHighlightUnder) {
		menuHighlight();
	}

	if (options.doShowHearts) {
		showLoved();
	}

	if (options.doSkipDrinks) {
		window.setInterval(fixCheckout, 250);
	}

	if (options.doRemoveExtraneousElements) {
		removeExtraneousElements();
	}

	if (options.doRemoveExtraneousMenuSections) {
		removeExtraneousMenuSections();
	}
}
<commit_msg>Fix selectors<commit_after>/**
	Jonathan Stassen - June 2015
	https://github.com/TheBox193/diningin-enhancements
	Feature requests: send a Slack, Email, or PR
	A quick set of enhancmetns to dingingin.com
	Eat it up!
**/

// var TAX = 0.1248;
var TAX = 0.105;
var options;

function menuHighlight() {
	$(".menu-item").each(function(){
		var t=$(this).closest("tr"),
		price = getPriceByEl( t ),
		priceWithTax = Number( price * ( 1 + TAX ) ).toFixed(2);
		if( priceWithTax <= ( Number(options.doHighlightUnderValue) || 11 ) )
			t.css("background-color","lightblue");
	});
}

function fixCheckout() {
	var chekcoutButton = $(".ibtnContinueBtnBev")[0];
	if(chekcoutButton && chekcoutButton !== "/checkout") {
		chekcoutButton.href = "/checkout";
	}
}

function getLoved() {
	var loved = [];
	$("#MostedLoved .menu-item-data").each(function(){
		loved.push(parseInt(this.textContent, 10));
	});
	return loved;
}

function setLoved(id) {
	getMenuItemElById(id)
		.find('.ItemName')
		.prepend('❤');
}

function getPriceByEl(el) {
	return el.find(".ItemCost").text().replace(/[^0-9\.]+/g,"");
}

function getPriceById(id) {
	return getPriceByEl( getMenuItemElById(id) );
}

function getMenuItemElById(id) {
	return $('#'+id).closest("tr");
}

function showLoved() {
	var loved = getLoved();
	$.each(loved, function(index, id) {
		setLoved(id);
	});
}

function removeExtraneousElements() {
	var elementsToRemove = [
		/** Calendar */
		"#spnAddressInfo", // address
		"#MPSContent > #tabnav", // tabs
		"#mps_time_header_container", // delivery time

		/** Cart */
		"#ctl00_pageContentPlaceholder_cart_pnlTimeCart1", // delivery time
		"#menu_delivery_address", // address

		/** Menu */
		"#ctl00_pageContentPlaceholder_CRestaurantTopBar_imgIsPremier", // premier badge
		"#DIVAlertNote", // food alergies
		"#spnCategoryDDL", // catagory label
		// "#MenuPageMenu li:contains('ZAGAT')", // Zagat
		// "#MenuPageMenu li:contains('TIMES')", // Restraunt Times
		"#RestMenu td:contains('no prices')", // no prices pdf
		"#MostedLoved", // Most loved

		/** Checkout */
		"#payment_info_title_container", // label
		".tip_container", // tip
		"#pnlStepTipsCoupons .clear", // paddings
		"#menu_your_order_delivery_time", // delivery time
		".phone_number_email_container_item2 .email-receipts-message", // going green
		".checkout_left_container_mid_payment_method #menu_delivery_time", // delivery time
		".checkout_left_container_mid_payment_method #menu_delivery_loc", // delivery location
		".checkout_left_container_mid_payment_method .delivery_instructions_for_driver_container:last", // driver instructions (optional)
		".checkout_left_container_mid_payment_method #select_payment_method_container:contains('following options')", // number of people
		"#divPaymentContainer span:contains('is this')", // 'what is this'
		"#CartContainer #ctl01_pnlOrderPaymentMsg", // false positive success before success checkout
		".tip_coupon_gift_certificates_title", // delivery instructions title

		/** Footer */
		"#footer_container", // Footer
		"#InnerFooter_Container", // Footer
	];

	elementsToRemove.map(removeElement);
}

function removeExtraneousMenuSections() {
	var elementsToRemove = [
		".MenuList:contains('12 Cans of Diet Coke')", // Beverages
		".MenuList:contains('Large Catering Tongs')", // Options
		".MenuList:contains('Single Use Canned Heat')", // Service Items
	];

	elementsToRemove.map(removeElement);
}

function removeElement(el) {
	var $el = $(el) ;
	if ($el) {
		$el.remove();
	}
}
chrome.storage.sync.get({
	doHighlightUnder: true,
	doHighlightUnderValue: 11,
	doShowHearts: true,
	doSkipDrinks: true,
	doRemoveExtraneousElements: false,
	doRemoveExtraneousMenuSections: false,
	doRemoveBackground: true
}, function(items) {
	options = items;
	$(function() {
		setup();
	});
});

function setup() {
	if (options.doRemoveBackground) {
		document.body.style.background = 'white';
	}

	if (options.doHighlightUnder) {
		menuHighlight();
	}

	if (options.doShowHearts) {
		showLoved();
	}

	if (options.doSkipDrinks) {
		window.setInterval(fixCheckout, 250);
	}

	if (options.doRemoveExtraneousElements) {
		removeExtraneousElements();
	}

	if (options.doRemoveExtraneousMenuSections) {
		removeExtraneousMenuSections();
	}
}
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';

import {autobind} from '../helpers';

export default class DonutChart extends React.Component {
  static propTypes = {
    baseOffset: PropTypes.number,
    slices: PropTypes.arrayOf(
      PropTypes.shape({
        type: PropTypes.string,
        className: PropTypes.string,
        count: PropTypes.number,
      }),
    ),
  }

  static defaultProps = {
    baseOffset: 25,
  }

  constructor(props) {
    super(props);
    autobind(this, 'renderArc');
  }

  render() {
    const {slices, baseOffset, ...others} = this.props; // eslint-disable-line no-unused-vars
    const arcs = this.calculateArcs(slices);

    return (
      <svg {...others}>
        {arcs.map(this.renderArc)}
      </svg>
    );
  }

  calculateArcs(slices) {
    const total = slices.reduce((acc, item) => acc + item.count, 0);
    let lengthSoFar = 0;

    return slices.map(({count, ...others}) => {
      const piece = {
        length: count / total * 100,
        position: lengthSoFar,
        ...others,
      };
      lengthSoFar += piece.length;
      return piece;
    });
  }

  renderArc({length, position, type, className}) {
    return (
      <circle
        key={type}
        cx="21"
        cy="21"
        r="15.91549430918954"
        fill="transparent"
        className={`donut-ring-${type}`}
        strokeWidth="3"
        strokeDasharray={`${length} ${100 - length}`}
        strokeDashoffset={`${100 - position + this.props.baseOffset}`}
      />
    );
  }
}
<commit_msg>Set an explicit pathLength on the donut chart circle<commit_after>import React from 'react';
import PropTypes from 'prop-types';

import {autobind} from '../helpers';

export default class DonutChart extends React.Component {
  static propTypes = {
    baseOffset: PropTypes.number,
    slices: PropTypes.arrayOf(
      PropTypes.shape({
        type: PropTypes.string,
        className: PropTypes.string,
        count: PropTypes.number,
      }),
    ),
  }

  static defaultProps = {
    baseOffset: 25,
  }

  constructor(props) {
    super(props);
    autobind(this, 'renderArc');
  }

  render() {
    const {slices, baseOffset, ...others} = this.props; // eslint-disable-line no-unused-vars
    const arcs = this.calculateArcs(slices);

    return (
      <svg {...others}>
        {arcs.map(this.renderArc)}
      </svg>
    );
  }

  calculateArcs(slices) {
    const total = slices.reduce((acc, item) => acc + item.count, 0);
    let lengthSoFar = 0;

    return slices.map(({count, ...others}) => {
      const piece = {
        length: count / total * 100,
        position: lengthSoFar,
        ...others,
      };
      lengthSoFar += piece.length;
      return piece;
    });
  }

  renderArc({length, position, type, className}) {
    return (
      <circle
        key={type}
        cx="21"
        cy="21"
        r="15.91549430918954"
        fill="transparent"
        className={`donut-ring-${type}`}
        pathLength="100"
        strokeWidth="3"
        strokeDasharray={`${length} ${100 - length}`}
        strokeDashoffset={`${100 - position + this.props.baseOffset}`}
      />
    );
  }
}
<|endoftext|>


<commit_before>import { CompanionAd } from './companion_ad';
import { CreativeLinear } from './creative/creative_linear';
import { EventEmitter } from 'events';
import { NonLinearAd } from './non_linear_ad';
import { util } from './util/util';

/**
 * This class provides methods to track an ad execution.
 *
 * @export
 * @class VASTTracker
 * @extends EventEmitter
 */
export class VASTTracker extends EventEmitter {
  /**
   * Creates an instance of VASTTracker.
   *
   * @param {VASTClient} client - An instance of VASTClient that can be updated by the tracker. [optional]
   * @param {Ad} ad - The ad to track.
   * @param {Creative} creative - The creative to track.
   * @param {CompanionAd|NonLinearAd} [variation=null] - An optional variation of the creative.
   * @constructor
   */
  constructor(client, ad, creative, variation = null) {
    super();
    this.ad = ad;
    this.creative = creative;
    this.variation = variation;
    this.muted = false;
    this.impressed = false;
    this.skippable = false;
    this.skipDelayDefault = -1;
    this.trackingEvents = {};
    // Tracker listeners should be notified with some events
    // no matter if there is a tracking URL or not
    this.emitAlwaysEvents = [
      'creativeView',
      'start',
      'firstQuartile',
      'midpoint',
      'thirdQuartile',
      'complete',
      'resume',
      'pause',
      'rewind',
      'skip',
      'closeLinear',
      'close'
    ];
    // Have to save already triggered quartile, to not trigger again
    this._alreadyTriggeredQuartiles = {};
    // Duplicate the creative's trackingEvents property so we can alter it
    for (let eventName in this.creative.trackingEvents) {
      const events = this.creative.trackingEvents[eventName];
      this.trackingEvents[eventName] = events.slice(0);
    }
    if (this.creative instanceof CreativeLinear) {
      this.setDuration(this.creative.duration);

      this.skipDelay = this.creative.skipDelay;
      this.linear = true;
      this.clickThroughURLTemplate = this.creative.videoClickThroughURLTemplate;
      this.clickTrackingURLTemplates = this.creative.videoClickTrackingURLTemplates;
      // Nonlinear and Companion
    } else {
      this.skipDelay = -1;
      this.linear = false;
      // Used variation has been specified
      if (this.variation) {
        if (this.variation instanceof NonLinearAd) {
          this.clickThroughURLTemplate = this.variation.nonlinearClickThroughURLTemplate;
          this.clickTrackingURLTemplates = this.variation.nonlinearClickTrackingURLTemplates;
        } else if (this.variation instanceof CompanionAd) {
          this.clickThroughURLTemplate = this.variation.companionClickThroughURLTemplate;
          this.clickTrackingURLTemplates = this.variation.companionClickTrackingURLTemplates;
        }
      }
    }

    // If the tracker is associated with a client we add a listener to the start event
    // to update the lastSuccessfulAd property.
    if (client) {
      this.on('start', () => {
        client.lastSuccessfulAd = Date.now();
      });
    }
  }

  /**
   * Sets the duration of the ad and updates the quartiles based on that.
   *
   * @param  {Number} duration - The duration of the ad.
   */
  setDuration(duration) {
    this.assetDuration = duration;
    // beware of key names, theses are also used as event names
    this.quartiles = {
      firstQuartile: Math.round(25 * this.assetDuration) / 100,
      midpoint: Math.round(50 * this.assetDuration) / 100,
      thirdQuartile: Math.round(75 * this.assetDuration) / 100
    };
  }

  /**
   * Sets the duration of the ad and updates the quartiles based on that.
   * This is required for tracking time related events.
   *
   * @param {Number} progress - Current playback time in seconds.
   * @emits VASTTracker#start
   * @emits VASTTracker#skip-countdown
   * @emits VASTTracker#progress-[0-100]%
   * @emits VASTTracker#progress-[currentTime]
   * @emits VASTTracker#rewind
   * @emits VASTTracker#firstQuartile
   * @emits VASTTracker#midpoint
   * @emits VASTTracker#thirdQuartile
   */
  setProgress(progress) {
    const skipDelay = this.skipDelay || this.skipDelayDefault;

    if (skipDelay !== -1 && !this.skippable) {
      if (skipDelay > progress) {
        this.emit('skip-countdown', skipDelay - progress);
      } else {
        this.skippable = true;
        this.emit('skip-countdown', 0);
      }
    }

    if (this.linear && this.assetDuration > 0) {
      const events = [];

      if (progress > 0) {
        const percent = Math.round((progress / this.assetDuration) * 100);

        events.push('start');
        events.push(`progress-${percent}%`);
        events.push(`progress-${Math.round(progress)}`);

        for (let quartile in this.quartiles) {
          if (
            this.isQuartileReached(quartile, this.quartiles[quartile], progress)
          ) {
            events.push(quartile);
            this._alreadyTriggeredQuartiles[quartile] = true;
          }
        }
      }

      events.forEach(eventName => {
        this.track(eventName, true);
      });

      if (progress < this.progress) {
        this.track('rewind');
      }
    }

    this.progress = progress;
  }

  /**
   * Checks if a quartile has been reached without have being triggered already.
   *
   * @param {String} quartile - Quartile name
   * @param {Number} time - Time offset, when this quartile is reached in seconds.
   * @param {Number} progress - Current progress of the ads in seconds.
   *
   * @return {Boolean}
   */
  isQuartileReached(quartile, time, progress) {
    let quartileReached = false;
    // if quartile time already reached and never triggered
    if (time <= progress && !this._alreadyTriggeredQuartiles[quartile]) {
      quartileReached = true;
    }
    return quartileReached;
  }

  /**
   * Updates the mute state and calls the mute/unmute tracking URLs.
   *
   * @param {Boolean} muted - Indicates if the video is muted or not.
   * @emits VASTTracker#mute
   * @emits VASTTracker#unmute
   */
  setMuted(muted) {
    if (this.muted !== muted) {
      this.track(muted ? 'mute' : 'unmute');
    }
    this.muted = muted;
  }

  /**
   * Update the pause state and call the resume/pause tracking URLs.
   *
   * @param {Boolean} paused - Indicates if the video is paused or not.
   * @emits VASTTracker#pause
   * @emits VASTTracker#resume
   */
  setPaused(paused) {
    if (this.paused !== paused) {
      this.track(paused ? 'pause' : 'resume');
    }
    this.paused = paused;
  }

  /**
   * Updates the fullscreen state and calls the fullscreen tracking URLs.
   *
   * @param {Boolean} fullscreen - Indicates if the video is in fulscreen mode or not.
   * @emits VASTTracker#fullscreen
   * @emits VASTTracker#exitFullscreen
   */
  setFullscreen(fullscreen) {
    if (this.fullscreen !== fullscreen) {
      this.track(fullscreen ? 'fullscreen' : 'exitFullscreen');
    }
    this.fullscreen = fullscreen;
  }

  /**
   * Updates the expand state and calls the expand/collapse tracking URLs.
   *
   * @param {Boolean} expanded - Indicates if the video is expanded or not.
   * @emits VASTTracker#expand
   * @emits VASTTracker#collapse
   */
  setExpand(expanded) {
    if (this.expanded !== expanded) {
      this.track(expanded ? 'expand' : 'collapse');
    }
    this.expanded = expanded;
  }

  /**
   * Must be called if you want to overwrite the <Linear> Skipoffset value.
   * This will init the skip countdown duration. Then, every time setProgress() is called,
   * it will decrease the countdown and emit a skip-countdown event with the remaining time.
   * Do not call this method if you want to keep the original Skipoffset value.
   *
   * @param {Number} duration - The time in seconds until the skip button is displayed.
   */
  setSkipDelay(duration) {
    if (typeof duration === 'number') {
      this.skipDelay = duration;
    }
  }

  /**
   * Tracks an impression (can be called only once).
   *
   * @emits VASTTracker#creativeView
   */
  trackImpression() {
    if (!this.impressed) {
      this.impressed = true;
      this.trackURLs(this.ad.impressionURLTemplates);
      this.track('creativeView');
    }
  }

  /**
   * Send a request to the URI provided by the VAST <Error> element.
   * If an [ERRORCODE] macro is included, it will be substitute with errorCode.
   *
   * @param {String} errorCode - Replaces [ERRORCODE] macro. [ERRORCODE] values are listed in the VAST specification.
   */
  errorWithCode(errorCode) {
    this.trackURLs(this.ad.errorURLTemplates, { ERRORCODE: errorCode });
  }

  /**
   * Must be called when the user watched the linear creative until its end.
   * Calls the complete tracking URLs.
   *
   * @emits VASTTracker#complete
   */
  complete() {
    this.track('complete');
  }

  /**
   * Must be called when the player or the window is closed during the ad.
   * Calls the `closeLinear` (in VAST 3.0) and `close` tracking URLs.
   *
   * @emits VASTTracker#closeLinear
   * @emits VASTTracker#close
   */
  close() {
    this.track(this.linear ? 'closeLinear' : 'close');
  }

  /**
   * Must be called when the skip button is clicked. Calls the skip tracking URLs.
   *
   * @emits VASTTracker#skip
   */
  skip() {
    this.track('skip');
    this.trackingEvents = [];
  }

  /**
   * Must be called when the user clicks on the creative.
   * It calls the tracking URLs and emits a 'clickthrough' event with the resolved
   * clickthrough URL when done.
   *
   * @emits VASTTracker#clickthrough
   */
  click() {
    if (
      this.clickTrackingURLTemplates &&
      this.clickTrackingURLTemplates.length
    ) {
      this.trackURLs(this.clickTrackingURLTemplates);
    }

    if (this.clickThroughURLTemplate) {
      const variables = this.linear
        ? { CONTENTPLAYHEAD: this.progressFormatted() }
        : {};
      const clickThroughURL = util.resolveURLTemplates(
        [this.clickThroughURLTemplate],
        variables
      )[0];

      this.emit('clickthrough', clickThroughURL);
    }
  }

  /**
   * Calls the tracking URLs for the given eventName and emits the event.
   *
   * @param {String} eventName - The name of the event.
   * @param {Boolean} [once=false] - Boolean to define if the event has to be tracked only once.
   */
  track(eventName, once = false) {
    // closeLinear event was introduced in VAST 3.0
    // Fallback to vast 2.0 close event if necessary
    if (
      eventName === 'closeLinear' &&
      !this.trackingEvents[eventName] &&
      this.trackingEvents['close']
    ) {
      eventName = 'close';
    }

    const trackingURLTemplates = this.trackingEvents[eventName];
    const isAlwaysEmitEvent = this.emitAlwaysEvents.indexOf(eventName) > -1;

    if (trackingURLTemplates) {
      this.emit(eventName, '');
      this.trackURLs(trackingURLTemplates);
    } else if (isAlwaysEmitEvent) {
      this.emit(eventName, '');
    }

    if (once) {
      delete this.trackingEvents[eventName];
      if (isAlwaysEmitEvent) {
        this.emitAlwaysEvents.splice(
          this.emitAlwaysEvents.indexOf(eventName),
          1
        );
      }
    }
  }

  /**
   * Calls the tracking urls templates with the given variables.
   *
   * @param {Array} URLTemplates - An array of tracking url templates.
   * @param {Object} [variables={}] - An optional Object of parameters to be used in the tracking calls.
   */
  trackURLs(URLTemplates, variables = {}) {
    if (this.linear) {
      if (
        this.creative &&
        this.creative.mediaFiles &&
        this.creative.mediaFiles[0] &&
        this.creative.mediaFiles[0].fileURL
      ) {
        variables['ASSETURI'] = this.creative.mediaFiles[0].fileURL;
      }
      variables['CONTENTPLAYHEAD'] = this.progressFormatted();
    }

    util.track(URLTemplates, variables);
  }

  /**
   * Formats time progress in a readable string.
   *
   * @return {String}
   */
  progressFormatted() {
    const seconds = parseInt(this.progress);
    let h = seconds / (60 * 60);
    if (h.length < 2) {
      h = `0${h}`;
    }
    let m = (seconds / 60) % 60;
    if (m.length < 2) {
      m = `0${m}`;
    }
    let s = seconds % 60;
    if (s.length < 2) {
      s = `0${m}`;
    }
    const ms = parseInt((this.progress - seconds) * 100);
    return `${h}:${m}:${s}.${ms}`;
  }
}
<commit_msg>[tracker] added fallback clickthrough parameter to VASTTracker click method<commit_after>import { CompanionAd } from './companion_ad';
import { CreativeLinear } from './creative/creative_linear';
import { EventEmitter } from 'events';
import { NonLinearAd } from './non_linear_ad';
import { util } from './util/util';

/**
 * This class provides methods to track an ad execution.
 *
 * @export
 * @class VASTTracker
 * @extends EventEmitter
 */
export class VASTTracker extends EventEmitter {
  /**
   * Creates an instance of VASTTracker.
   *
   * @param {VASTClient} client - An instance of VASTClient that can be updated by the tracker. [optional]
   * @param {Ad} ad - The ad to track.
   * @param {Creative} creative - The creative to track.
   * @param {CompanionAd|NonLinearAd} [variation=null] - An optional variation of the creative.
   * @constructor
   */
  constructor(client, ad, creative, variation = null) {
    super();
    this.ad = ad;
    this.creative = creative;
    this.variation = variation;
    this.muted = false;
    this.impressed = false;
    this.skippable = false;
    this.skipDelayDefault = -1;
    this.trackingEvents = {};
    // Tracker listeners should be notified with some events
    // no matter if there is a tracking URL or not
    this.emitAlwaysEvents = [
      'creativeView',
      'start',
      'firstQuartile',
      'midpoint',
      'thirdQuartile',
      'complete',
      'resume',
      'pause',
      'rewind',
      'skip',
      'closeLinear',
      'close'
    ];
    // Have to save already triggered quartile, to not trigger again
    this._alreadyTriggeredQuartiles = {};
    // Duplicate the creative's trackingEvents property so we can alter it
    for (let eventName in this.creative.trackingEvents) {
      const events = this.creative.trackingEvents[eventName];
      this.trackingEvents[eventName] = events.slice(0);
    }
    if (this.creative instanceof CreativeLinear) {
      this.setDuration(this.creative.duration);

      this.skipDelay = this.creative.skipDelay;
      this.linear = true;
      this.clickThroughURLTemplate = this.creative.videoClickThroughURLTemplate;
      this.clickTrackingURLTemplates = this.creative.videoClickTrackingURLTemplates;
      // Nonlinear and Companion
    } else {
      this.skipDelay = -1;
      this.linear = false;
      // Used variation has been specified
      if (this.variation) {
        if (this.variation instanceof NonLinearAd) {
          this.clickThroughURLTemplate = this.variation.nonlinearClickThroughURLTemplate;
          this.clickTrackingURLTemplates = this.variation.nonlinearClickTrackingURLTemplates;
        } else if (this.variation instanceof CompanionAd) {
          this.clickThroughURLTemplate = this.variation.companionClickThroughURLTemplate;
          this.clickTrackingURLTemplates = this.variation.companionClickTrackingURLTemplates;
        }
      }
    }

    // If the tracker is associated with a client we add a listener to the start event
    // to update the lastSuccessfulAd property.
    if (client) {
      this.on('start', () => {
        client.lastSuccessfulAd = Date.now();
      });
    }
  }

  /**
   * Sets the duration of the ad and updates the quartiles based on that.
   *
   * @param  {Number} duration - The duration of the ad.
   */
  setDuration(duration) {
    this.assetDuration = duration;
    // beware of key names, theses are also used as event names
    this.quartiles = {
      firstQuartile: Math.round(25 * this.assetDuration) / 100,
      midpoint: Math.round(50 * this.assetDuration) / 100,
      thirdQuartile: Math.round(75 * this.assetDuration) / 100
    };
  }

  /**
   * Sets the duration of the ad and updates the quartiles based on that.
   * This is required for tracking time related events.
   *
   * @param {Number} progress - Current playback time in seconds.
   * @emits VASTTracker#start
   * @emits VASTTracker#skip-countdown
   * @emits VASTTracker#progress-[0-100]%
   * @emits VASTTracker#progress-[currentTime]
   * @emits VASTTracker#rewind
   * @emits VASTTracker#firstQuartile
   * @emits VASTTracker#midpoint
   * @emits VASTTracker#thirdQuartile
   */
  setProgress(progress) {
    const skipDelay = this.skipDelay || this.skipDelayDefault;

    if (skipDelay !== -1 && !this.skippable) {
      if (skipDelay > progress) {
        this.emit('skip-countdown', skipDelay - progress);
      } else {
        this.skippable = true;
        this.emit('skip-countdown', 0);
      }
    }

    if (this.linear && this.assetDuration > 0) {
      const events = [];

      if (progress > 0) {
        const percent = Math.round((progress / this.assetDuration) * 100);

        events.push('start');
        events.push(`progress-${percent}%`);
        events.push(`progress-${Math.round(progress)}`);

        for (let quartile in this.quartiles) {
          if (
            this.isQuartileReached(quartile, this.quartiles[quartile], progress)
          ) {
            events.push(quartile);
            this._alreadyTriggeredQuartiles[quartile] = true;
          }
        }
      }

      events.forEach(eventName => {
        this.track(eventName, true);
      });

      if (progress < this.progress) {
        this.track('rewind');
      }
    }

    this.progress = progress;
  }

  /**
   * Checks if a quartile has been reached without have being triggered already.
   *
   * @param {String} quartile - Quartile name
   * @param {Number} time - Time offset, when this quartile is reached in seconds.
   * @param {Number} progress - Current progress of the ads in seconds.
   *
   * @return {Boolean}
   */
  isQuartileReached(quartile, time, progress) {
    let quartileReached = false;
    // if quartile time already reached and never triggered
    if (time <= progress && !this._alreadyTriggeredQuartiles[quartile]) {
      quartileReached = true;
    }
    return quartileReached;
  }

  /**
   * Updates the mute state and calls the mute/unmute tracking URLs.
   *
   * @param {Boolean} muted - Indicates if the video is muted or not.
   * @emits VASTTracker#mute
   * @emits VASTTracker#unmute
   */
  setMuted(muted) {
    if (this.muted !== muted) {
      this.track(muted ? 'mute' : 'unmute');
    }
    this.muted = muted;
  }

  /**
   * Update the pause state and call the resume/pause tracking URLs.
   *
   * @param {Boolean} paused - Indicates if the video is paused or not.
   * @emits VASTTracker#pause
   * @emits VASTTracker#resume
   */
  setPaused(paused) {
    if (this.paused !== paused) {
      this.track(paused ? 'pause' : 'resume');
    }
    this.paused = paused;
  }

  /**
   * Updates the fullscreen state and calls the fullscreen tracking URLs.
   *
   * @param {Boolean} fullscreen - Indicates if the video is in fulscreen mode or not.
   * @emits VASTTracker#fullscreen
   * @emits VASTTracker#exitFullscreen
   */
  setFullscreen(fullscreen) {
    if (this.fullscreen !== fullscreen) {
      this.track(fullscreen ? 'fullscreen' : 'exitFullscreen');
    }
    this.fullscreen = fullscreen;
  }

  /**
   * Updates the expand state and calls the expand/collapse tracking URLs.
   *
   * @param {Boolean} expanded - Indicates if the video is expanded or not.
   * @emits VASTTracker#expand
   * @emits VASTTracker#collapse
   */
  setExpand(expanded) {
    if (this.expanded !== expanded) {
      this.track(expanded ? 'expand' : 'collapse');
    }
    this.expanded = expanded;
  }

  /**
   * Must be called if you want to overwrite the <Linear> Skipoffset value.
   * This will init the skip countdown duration. Then, every time setProgress() is called,
   * it will decrease the countdown and emit a skip-countdown event with the remaining time.
   * Do not call this method if you want to keep the original Skipoffset value.
   *
   * @param {Number} duration - The time in seconds until the skip button is displayed.
   */
  setSkipDelay(duration) {
    if (typeof duration === 'number') {
      this.skipDelay = duration;
    }
  }

  /**
   * Tracks an impression (can be called only once).
   *
   * @emits VASTTracker#creativeView
   */
  trackImpression() {
    if (!this.impressed) {
      this.impressed = true;
      this.trackURLs(this.ad.impressionURLTemplates);
      this.track('creativeView');
    }
  }

  /**
   * Send a request to the URI provided by the VAST <Error> element.
   * If an [ERRORCODE] macro is included, it will be substitute with errorCode.
   *
   * @param {String} errorCode - Replaces [ERRORCODE] macro. [ERRORCODE] values are listed in the VAST specification.
   */
  errorWithCode(errorCode) {
    this.trackURLs(this.ad.errorURLTemplates, { ERRORCODE: errorCode });
  }

  /**
   * Must be called when the user watched the linear creative until its end.
   * Calls the complete tracking URLs.
   *
   * @emits VASTTracker#complete
   */
  complete() {
    this.track('complete');
  }

  /**
   * Must be called when the player or the window is closed during the ad.
   * Calls the `closeLinear` (in VAST 3.0) and `close` tracking URLs.
   *
   * @emits VASTTracker#closeLinear
   * @emits VASTTracker#close
   */
  close() {
    this.track(this.linear ? 'closeLinear' : 'close');
  }

  /**
   * Must be called when the skip button is clicked. Calls the skip tracking URLs.
   *
   * @emits VASTTracker#skip
   */
  skip() {
    this.track('skip');
    this.trackingEvents = [];
  }

  /**
   * Must be called when the user clicks on the creative.
   * It calls the tracking URLs and emits a 'clickthrough' event with the resolved
   * clickthrough URL when done.
   *
   * @param {String} [fallbackClickThroughURL=null] - an optional clickThroughURL template that could be used as a fallback
   * @emits VASTTracker#clickthrough
   */
  click(fallbackClickThroughURL = null) {
    if (
      this.clickTrackingURLTemplates &&
      this.clickTrackingURLTemplates.length
    ) {
      this.trackURLs(this.clickTrackingURLTemplates);
    }

    // Use the provided fallbackClickThroughURL as a fallback
    const clickThroughURLTemplate =
      this.clickThroughURLTemplate || fallbackClickThroughURL;

    if (clickThroughURLTemplate) {
      const variables = this.linear
        ? { CONTENTPLAYHEAD: this.progressFormatted() }
        : {};
      const clickThroughURL = util.resolveURLTemplates(
        [clickThroughURLTemplate],
        variables
      )[0];

      this.emit('clickthrough', clickThroughURL);
    }
  }

  /**
   * Calls the tracking URLs for the given eventName and emits the event.
   *
   * @param {String} eventName - The name of the event.
   * @param {Boolean} [once=false] - Boolean to define if the event has to be tracked only once.
   */
  track(eventName, once = false) {
    // closeLinear event was introduced in VAST 3.0
    // Fallback to vast 2.0 close event if necessary
    if (
      eventName === 'closeLinear' &&
      !this.trackingEvents[eventName] &&
      this.trackingEvents['close']
    ) {
      eventName = 'close';
    }

    const trackingURLTemplates = this.trackingEvents[eventName];
    const isAlwaysEmitEvent = this.emitAlwaysEvents.indexOf(eventName) > -1;

    if (trackingURLTemplates) {
      this.emit(eventName, '');
      this.trackURLs(trackingURLTemplates);
    } else if (isAlwaysEmitEvent) {
      this.emit(eventName, '');
    }

    if (once) {
      delete this.trackingEvents[eventName];
      if (isAlwaysEmitEvent) {
        this.emitAlwaysEvents.splice(
          this.emitAlwaysEvents.indexOf(eventName),
          1
        );
      }
    }
  }

  /**
   * Calls the tracking urls templates with the given variables.
   *
   * @param {Array} URLTemplates - An array of tracking url templates.
   * @param {Object} [variables={}] - An optional Object of parameters to be used in the tracking calls.
   */
  trackURLs(URLTemplates, variables = {}) {
    if (this.linear) {
      if (
        this.creative &&
        this.creative.mediaFiles &&
        this.creative.mediaFiles[0] &&
        this.creative.mediaFiles[0].fileURL
      ) {
        variables['ASSETURI'] = this.creative.mediaFiles[0].fileURL;
      }
      variables['CONTENTPLAYHEAD'] = this.progressFormatted();
    }

    util.track(URLTemplates, variables);
  }

  /**
   * Formats time progress in a readable string.
   *
   * @return {String}
   */
  progressFormatted() {
    const seconds = parseInt(this.progress);
    let h = seconds / (60 * 60);
    if (h.length < 2) {
      h = `0${h}`;
    }
    let m = (seconds / 60) % 60;
    if (m.length < 2) {
      m = `0${m}`;
    }
    let s = seconds % 60;
    if (s.length < 2) {
      s = `0${m}`;
    }
    const ms = parseInt((this.progress - seconds) * 100);
    return `${h}:${m}:${s}.${ms}`;
  }
}
<|endoftext|>


<commit_before>(function (global) {
	function start(filename, frames) {
		var currentFrames = 0,
			results = [],
			listenerName = "detailProfiler_listener_" + Math.random();

		global.sys.on("onProfileEnd", listenerName, function (args) {
			results.push(args);
			if (currentFrames++ >= frames) {
				global.sys.clearEvent(listenerName);
				console.toast("Saving profile results to: " + filename);
				global.fs.writeFile(filename, JSON.stringify(results));
				results = [];
			}
		});
	}

	global.sys.on("detailProfile", "detailProfiler_start", function (args) {
		start(args.filename, args.frames);
	});

})(this);<commit_msg>Fixed detailProfiler<commit_after>(function (global) {
	function start(filename, frames) {
		var currentFrames = 0,
			results = [],
			listenerName = "detailProfiler_listener_" + Math.random();

		global.sys.on("onProfileEnd", listenerName, function (args) {
			results.push(args);
			if (currentFrames++ >= frames) {
				global.sys.clearEvent(listenerName);
				console.toast("Saving profile results to: " + filename);
				global.fs.writeFile(filename, JSON.stringify(results));
				results = [];
				currentFrames = 0;
			}
		});
	}

	global.sys.on("detailProfile", "detailProfiler_start", function (args) {
		start(args.filename, args.frames);
	});

})(this);<|endoftext|>


<commit_before>var MIXPANEL_LIB_URL = 'plugins/cordova-plugin-mixpanel/src/browser/mixpanel-js-lib.js';

var errors = {
  invalid: function(paramName, value) {
    if (typeof paramName !== 'string') {
      (console && console.error) && console.error(paramName, value);
      return "invalid";
    }
    if (typeof value !== 'string') {
      (JSON && JSON.stringify) && (value = JSON.stringify(value));
    }
    return 'invalid ' + paramName + ': ' + value;
  },
  notsupported: function(methodName) {
    if (typeof paramName !== 'string') {
      methodName = "[invalid methodName]";
    }
    return 'Cordova-Plugin-Mixpanel :  ' + methodName + ' is not supported...';
  }
};

window.mixpanel = null; // clean plugin method

// On Mixpanel lib Loaded
function on_mixpanel_loaded() {

  // ALIAS
  mixpanel.original_alias = mixpanel.alias;
  mixpanel.alias = mixpanel.createAlias = function(alias, originalId, onSuccess, onFail) {

    if (!alias || typeof alias !== 'string') {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('alias', alias));
      else return false;
    }

    var r = mixpanel.original_alias(alias, originalId);

    if (typeof r !== "object" || typeof r.properties !== "object") {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('alias', alias));
    } else {
      if (onSuccess && typeof onSuccess === 'function')
        return onSuccess();
    }
  };

  // DISTINCT_ID
  mixpanel.original_get_distinct_id = mixpanel.get_distinct_id;
  mixpanel.distinctId = function(onSuccess, onFail) {

    var distinctId = mixpanel.original_get_distinct_id();

    if (distinctId && onSuccess && typeof onSuccess === 'function')
      return onSuccess(distinctId);

    if (!distinctId && onFail && typeof onFail === 'function')
      return onFail(errors.invalid('distinctId', distinctId));

    return false;
  };

  // FLUSH
  mixpanel.flush = function(onSuccess, onFail) {
    if (onFail && typeof onFail === 'function')
      return onFail(errors.notsupported("flush"));
  };

  // IDENTITY
  mixpanel.original_identify = mixpanel.identify;
  mixpanel.identify = function(unique_id, onSuccess, onFail) {

    if (!unique_id || typeof unique_id !== 'string') {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('unique_id', unique_id));
      else return false;
    }

    mixpanel.original_identify(unique_id, function(r) {
      if (typeof r !== "object" || typeof r.properties !== "object") {
        if (onFail && typeof onFail === 'function')
          return onFail(errors.invalid('identify', unique_id));
      } else {
        if (onSuccess && typeof onSuccess === 'function')
          return onSuccess();
      }
    });
  };

  // registerSuperProperties
  mixpanel.registerSuperProperties = function(superProperties, onSuccess, onFail) {
    if (!superProperties || typeof superProperties !== 'object') {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('superProperties', superProperties));
      else return false;
    }

    if (onFail && typeof onFail === 'function')
      return onFail(errors.notsupported("registerSuperProperties"));
  };

  // RESET
  mixpanel.reset = function(onSuccess, onFail) {
    if (onFail && typeof onFail === 'function')
      return onFail(errors.notsupported("reset"));
  };

  // TRACK
  mixpanel.original_track = mixpanel.track;
  mixpanel.track = function(eventName, eventProperties, onSuccess, onFail) {

    if (!eventName || typeof eventName != 'string') {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('event', eventName));
    }

    mixpanel.original_track(eventName, eventProperties, function(r) {
      if (!r) {
        if (onFail && typeof onFail === 'function')
          return onFail(errors.invalid('track', eventName));
      } else {
        if (onSuccess && typeof onSuccess === 'function')
          return onSuccess();
      }
    });

  };

  mixpanel.people.original_identify = mixpanel.people.identify;
  mixpanel.people.identify = function(distinctId, onSuccess, onFail) {
    return mixpanel.original_identify(distinctId, onSuccess, onFail);
  };

  mixpanel.people.original_set = mixpanel.people.set;
  mixpanel.people.set = function(peopleProperties, onSuccess, onFail) {
    if (!peopleProperties || (typeof peopleProperties === 'object' && Object.keys(peopleProperties).length === 0)) {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('peopleProperties', peopleProperties));
    }

    mixpanel.people.original_set(peopleProperties, function(r) {
      if (typeof r !== "object") {
        if (onFail && typeof onFail === 'function')
          return onFail(errors.invalid('people.set', peopleProperties));
      } else {
        if (onSuccess && typeof onSuccess === 'function')
          return onSuccess();
      }
    });

  };

  mixpanel.people.original_set_once = mixpanel.people.set_once;
  mixpanel.people.setOnce = function(peopleProperties, onSuccess, onFail) {
    if (!peopleProperties || (typeof peopleProperties === 'object' && Object.keys(peopleProperties).length === 0)) {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('peopleProperties', peopleProperties));
    }

    mixpanel_js.people.original_set_once(peopleProperties, function(r) {
      if (typeof r !== "object") {
        if (onFail && typeof onFail === 'function')
          return onFail(errors.invalid('people.setOnce', peopleProperties));
      } else {
        if (onSuccess && typeof onSuccess === 'function')
          return onSuccess();
      }
    });

  };

  // TRACK CHARGE
  mixpanel.people.original_track_charge = mixpanel.people.track_charge;
  mixpanel.people.track_charge = function(charge, eventProperties, onSuccess, onFail) {

    if (!charge || typeof charge != 'number') {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('charge', charge));
    }

    mixpanel.people.original_track_charge(charge, eventProperties, function(r) {
      if (!r) {
        if (onFail && typeof onFail === 'function')
          return onFail(errors.invalid('people.track_charge', charge));
      } else {
        if (onSuccess && typeof onSuccess === 'function')
          return onSuccess();
      }
    });

  };

  mixpanel.people.original_increment = mixpanel.people.increment;
  mixpanel.people.increment = function(peopleProperties, onSuccess, onFail) {
    if (!peopleProperties || (typeof peopleProperties === 'object' && Object.keys(peopleProperties).length === 0)) {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('peopleProperties', peopleProperties));
    }

    mixpanel_js.people.original_increment(peopleProperties, function(r) {
      if (typeof r !== "object") {
        if (onFail && typeof onFail === 'function')
          return onFail(errors.invalid('people.increment', peopleProperties));
      } else {
        if (onSuccess && typeof onSuccess === 'function')
          return onSuccess();
      }
    });

  };

  // SetPushId
  mixpanel.people.setPushId = function(pushId, onSuccess, onFail) { // No push in browser, noooo ?
    if (onFail && typeof onFail === 'function')
      return onFail(errors.notsupported("setPushId", pushId));
  }

}

// On snippet Loaded
function on_snippet_loaded() {
  mixpanel.original_init = mixpanel.init;
  mixpanel.init = function(token, onSuccess, onFail) {
    var r = mixpanel.original_init(token, {
      'loaded': function() {
        setTimeout(on_mixpanel_loaded, 10); // Let the original_init function be executed after lib loaded
      }
    });
  }
}

// SNIPET Mixpanel 1.2 (modified : setup an onload event )
(function(document, mixpanel) {
  // Only stub out if this is the first time running the snippet.
  if (!mixpanel['__SV']) {
    var script, first_script, gen_fn, functions, i, lib_name = "mixpanel";
    window[lib_name] = mixpanel;

    mixpanel['_i'] = [];

    mixpanel['init'] = function(token, config, name) {
      // support multiple mixpanel instances
      var target = mixpanel;
      if (typeof(name) !== 'undefined') {
        target = mixpanel[name] = [];
      } else {
        name = lib_name;
      }

      // Pass in current people object if it exists
      target['people'] = target['people'] || [];
      target['toString'] = function(no_stub) {
        var str = lib_name;
        if (name !== lib_name) {
          str += "." + name;
        }
        if (!no_stub) {
          str += " (stub)";
        }
        return str;
      };
      target['people']['toString'] = function() {
        // 1 instead of true for minifying
        return target.toString(1) + ".people (stub)";
      };

      function _set_and_defer(target, fn) {
        var split = fn.split(".");
        if (split.length == 2) {
          target = target[split[0]];
          fn = split[1];
        }
        target[fn] = function() {
          target.push([fn].concat(Array.prototype.slice.call(arguments, 0)));
        };
      }

      // create shallow clone of the public mixpanel interface
      // Note: only supports 1 additional level atm, e.g. mixpanel.people.set, not mixpanel.people.set.do_something_else.
      functions = "disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(' ');
      for (i = 0; i < functions.length; i++) {
        _set_and_defer(target, functions[i]);
      }

      // register mixpanel instance
      mixpanel['_i'].push([token, config, name]);
    };

    // Snippet version, used to fail on new features w/ old snippet
    mixpanel['__SV'] = 1.2;

    script = document.createElement("script");
    script.type = "text/javascript";
    script.async = true;

    var onComplete = function() {
      if (on_snippet_loaded) on_snippet_loaded();
    };

    script.onreadystatechange = script.onload = function() {
      var state = script.readyState;
      if (!state || /loaded|complete/.test(state)) {
        onComplete();
      }
    };

    if (typeof MIXPANEL_CUSTOM_LIB_URL !== 'undefined') {
      script.src = MIXPANEL_CUSTOM_LIB_URL;
    } else if (document.location.protocol === 'file:' && MIXPANEL_LIB_URL.match(/^\/\//)) {
      script.src = 'https:' + MIXPANEL_LIB_URL;
    } else {
      script.src = MIXPANEL_LIB_URL;
    }

    first_script = document.getElementsByTagName("script")[0];
    first_script.parentNode.insertBefore(script, first_script);
  }
  // Pass in current Mixpanel object if it exists (for ppl like Optimizely)
})(document, window['mixpanel'] || []);

module.exports = window.mixpanel;

require("cordova/exec/proxy").add("Mixpanel", module.exports);
<commit_msg>oups (methodName  vs paramName. Fix var name mistake.<commit_after>var MIXPANEL_LIB_URL = 'plugins/cordova-plugin-mixpanel/src/browser/mixpanel-js-lib.js';

var errors = {
  invalid: function(paramName, value) {
    if (typeof paramName !== 'string') {
      (console && console.error) && console.error(paramName, value);
      return "invalid";
    }
    if (typeof value !== 'string') {
      (JSON && JSON.stringify) && (value = JSON.stringify(value));
    }
    return 'invalid ' + paramName + ': ' + value;
  },
  notsupported: function(methodName) {
    if (typeof methodName !== 'string') {
      methodName = "[invalid methodName]";
    }
    return 'Cordova-Plugin-Mixpanel :  ' + methodName + ' is not supported...';
  }
};

window.mixpanel = null; // clean plugin method

// On Mixpanel lib Loaded
function on_mixpanel_loaded() {

  // ALIAS
  mixpanel.original_alias = mixpanel.alias;
  mixpanel.alias = mixpanel.createAlias = function(alias, originalId, onSuccess, onFail) {

    if (!alias || typeof alias !== 'string') {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('alias', alias));
      else return false;
    }

    var r = mixpanel.original_alias(alias, originalId);

    if (typeof r !== "object" || typeof r.properties !== "object") {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('alias', alias));
    } else {
      if (onSuccess && typeof onSuccess === 'function')
        return onSuccess();
    }
  };

  // DISTINCT_ID
  mixpanel.original_get_distinct_id = mixpanel.get_distinct_id;
  mixpanel.distinctId = function(onSuccess, onFail) {

    var distinctId = mixpanel.original_get_distinct_id();

    if (distinctId && onSuccess && typeof onSuccess === 'function')
      return onSuccess(distinctId);

    if (!distinctId && onFail && typeof onFail === 'function')
      return onFail(errors.invalid('distinctId', distinctId));

    return false;
  };

  // FLUSH
  mixpanel.flush = function(onSuccess, onFail) {
    if (onFail && typeof onFail === 'function')
      return onFail(errors.notsupported("flush"));
  };

  // IDENTITY
  mixpanel.original_identify = mixpanel.identify;
  mixpanel.identify = function(unique_id, onSuccess, onFail) {

    if (!unique_id || typeof unique_id !== 'string') {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('unique_id', unique_id));
      else return false;
    }

    mixpanel.original_identify(unique_id, function(r) {
      if (typeof r !== "object" || typeof r.properties !== "object") {
        if (onFail && typeof onFail === 'function')
          return onFail(errors.invalid('identify', unique_id));
      } else {
        if (onSuccess && typeof onSuccess === 'function')
          return onSuccess();
      }
    });
  };

  // registerSuperProperties
  mixpanel.registerSuperProperties = function(superProperties, onSuccess, onFail) {
    if (!superProperties || typeof superProperties !== 'object') {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('superProperties', superProperties));
      else return false;
    }

    if (onFail && typeof onFail === 'function')
      return onFail(errors.notsupported("registerSuperProperties"));
  };

  // RESET
  mixpanel.reset = function(onSuccess, onFail) {
    if (onFail && typeof onFail === 'function')
      return onFail(errors.notsupported("reset"));
  };

  // TRACK
  mixpanel.original_track = mixpanel.track;
  mixpanel.track = function(eventName, eventProperties, onSuccess, onFail) {

    if (!eventName || typeof eventName != 'string') {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('event', eventName));
    }

    mixpanel.original_track(eventName, eventProperties, function(r) {
      if (!r) {
        if (onFail && typeof onFail === 'function')
          return onFail(errors.invalid('track', eventName));
      } else {
        if (onSuccess && typeof onSuccess === 'function')
          return onSuccess();
      }
    });

  };

  mixpanel.people.original_identify = mixpanel.people.identify;
  mixpanel.people.identify = function(distinctId, onSuccess, onFail) {
    return mixpanel.original_identify(distinctId, onSuccess, onFail);
  };

  mixpanel.people.original_set = mixpanel.people.set;
  mixpanel.people.set = function(peopleProperties, onSuccess, onFail) {
    if (!peopleProperties || (typeof peopleProperties === 'object' && Object.keys(peopleProperties).length === 0)) {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('peopleProperties', peopleProperties));
    }

    mixpanel.people.original_set(peopleProperties, function(r) {
      if (typeof r !== "object") {
        if (onFail && typeof onFail === 'function')
          return onFail(errors.invalid('people.set', peopleProperties));
      } else {
        if (onSuccess && typeof onSuccess === 'function')
          return onSuccess();
      }
    });

  };

  mixpanel.people.original_set_once = mixpanel.people.set_once;
  mixpanel.people.setOnce = function(peopleProperties, onSuccess, onFail) {
    if (!peopleProperties || (typeof peopleProperties === 'object' && Object.keys(peopleProperties).length === 0)) {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('peopleProperties', peopleProperties));
    }

    mixpanel_js.people.original_set_once(peopleProperties, function(r) {
      if (typeof r !== "object") {
        if (onFail && typeof onFail === 'function')
          return onFail(errors.invalid('people.setOnce', peopleProperties));
      } else {
        if (onSuccess && typeof onSuccess === 'function')
          return onSuccess();
      }
    });

  };

  // TRACK CHARGE
  mixpanel.people.original_track_charge = mixpanel.people.track_charge;
  mixpanel.people.track_charge = function(charge, eventProperties, onSuccess, onFail) {

    if (!charge || typeof charge != 'number') {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('charge', charge));
    }

    mixpanel.people.original_track_charge(charge, eventProperties, function(r) {
      if (!r) {
        if (onFail && typeof onFail === 'function')
          return onFail(errors.invalid('people.track_charge', charge));
      } else {
        if (onSuccess && typeof onSuccess === 'function')
          return onSuccess();
      }
    });

  };

  mixpanel.people.original_increment = mixpanel.people.increment;
  mixpanel.people.increment = function(peopleProperties, onSuccess, onFail) {
    if (!peopleProperties || (typeof peopleProperties === 'object' && Object.keys(peopleProperties).length === 0)) {
      if (onFail && typeof onFail === 'function')
        return onFail(errors.invalid('peopleProperties', peopleProperties));
    }

    mixpanel_js.people.original_increment(peopleProperties, function(r) {
      if (typeof r !== "object") {
        if (onFail && typeof onFail === 'function')
          return onFail(errors.invalid('people.increment', peopleProperties));
      } else {
        if (onSuccess && typeof onSuccess === 'function')
          return onSuccess();
      }
    });

  };

  // SetPushId
  mixpanel.people.setPushId = function(pushId, onSuccess, onFail) { // No push in browser, noooo ?
    if (onFail && typeof onFail === 'function')
      return onFail(errors.notsupported("setPushId", pushId));
  }

}

// On snippet Loaded
function on_snippet_loaded() {
  mixpanel.original_init = mixpanel.init;
  mixpanel.init = function(token, onSuccess, onFail) {
    var r = mixpanel.original_init(token, {
      'loaded': function() {
        setTimeout(on_mixpanel_loaded, 10); // Let the original_init function be executed after lib loaded
      }
    });
  }
}

// SNIPET Mixpanel 1.2 (modified : setup an onload event )
(function(document, mixpanel) {
  // Only stub out if this is the first time running the snippet.
  if (!mixpanel['__SV']) {
    var script, first_script, gen_fn, functions, i, lib_name = "mixpanel";
    window[lib_name] = mixpanel;

    mixpanel['_i'] = [];

    mixpanel['init'] = function(token, config, name) {
      // support multiple mixpanel instances
      var target = mixpanel;
      if (typeof(name) !== 'undefined') {
        target = mixpanel[name] = [];
      } else {
        name = lib_name;
      }

      // Pass in current people object if it exists
      target['people'] = target['people'] || [];
      target['toString'] = function(no_stub) {
        var str = lib_name;
        if (name !== lib_name) {
          str += "." + name;
        }
        if (!no_stub) {
          str += " (stub)";
        }
        return str;
      };
      target['people']['toString'] = function() {
        // 1 instead of true for minifying
        return target.toString(1) + ".people (stub)";
      };

      function _set_and_defer(target, fn) {
        var split = fn.split(".");
        if (split.length == 2) {
          target = target[split[0]];
          fn = split[1];
        }
        target[fn] = function() {
          target.push([fn].concat(Array.prototype.slice.call(arguments, 0)));
        };
      }

      // create shallow clone of the public mixpanel interface
      // Note: only supports 1 additional level atm, e.g. mixpanel.people.set, not mixpanel.people.set.do_something_else.
      functions = "disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(' ');
      for (i = 0; i < functions.length; i++) {
        _set_and_defer(target, functions[i]);
      }

      // register mixpanel instance
      mixpanel['_i'].push([token, config, name]);
    };

    // Snippet version, used to fail on new features w/ old snippet
    mixpanel['__SV'] = 1.2;

    script = document.createElement("script");
    script.type = "text/javascript";
    script.async = true;

    var onComplete = function() {
      if (on_snippet_loaded) on_snippet_loaded();
    };

    script.onreadystatechange = script.onload = function() {
      var state = script.readyState;
      if (!state || /loaded|complete/.test(state)) {
        onComplete();
      }
    };

    if (typeof MIXPANEL_CUSTOM_LIB_URL !== 'undefined') {
      script.src = MIXPANEL_CUSTOM_LIB_URL;
    } else if (document.location.protocol === 'file:' && MIXPANEL_LIB_URL.match(/^\/\//)) {
      script.src = 'https:' + MIXPANEL_LIB_URL;
    } else {
      script.src = MIXPANEL_LIB_URL;
    }

    first_script = document.getElementsByTagName("script")[0];
    first_script.parentNode.insertBefore(script, first_script);
  }
  // Pass in current Mixpanel object if it exists (for ppl like Optimizely)
})(document, window['mixpanel'] || []);

module.exports = window.mixpanel;

require("cordova/exec/proxy").add("Mixpanel", module.exports);
<|endoftext|>


<commit_before>var DOM = (function(){
  var setupTimerFunction = function(setFunc, clearFunc, callback, time){
    var obj = {
      cancel: function(){
        if (this.isActive){
          clearFunc(this.id);
          this.isActive = false;
        }
      },

      start: function(){
        if (!this.isActive){
          this.id = setFunc(this._callback.bind(obj), this._time);
          this.isActive = true;
        }
      },

      _callback: callback,
      _time: time
    };

    obj.start();
    return obj;
  };

  return {
    /*
     * Returns a child element by its ID. Parent defaults to the entire document.
     */
    id: function(id, parent){
      return (parent || document).getElementById(id);
    },

    /*
     * Returns an array of all child elements containing the specified class. Parent defaults to the entire document.
     */
    cls: function(cls, parent){
      return Array.prototype.slice.call((parent || document).getElementsByClassName(cls));
    },

    /*
     * Returns an array of all child elements that have the specified tag. Parent defaults to the entire document.
     */
    tag: function(tag, parent){
      return Array.prototype.slice.call((parent || document).getElementsByTagName(tag));
    },

    /*
     * Creates an element, adds it to the DOM, and returns it.
     */
    createElement: function(tag, parent){
      var ele = document.createElement(tag);
      parent.appendChild(ele);
      return ele;
    },

    /*
     * Removes an element from the DOM.
     */
    removeElement: function(ele){
      ele.parentNode.removeChild(ele);
    },

    /*
     * Creates a new style element with the specified CSS contents and returns it.
     */
    createStyle: function(styles){
      var ele = document.createElement("style");
      ele.setAttribute("type", "text/css");

      document.head.appendChild(ele);

      styles.forEach(function(style){
        ele.sheet.insertRule(style, 0);
      });

      return ele;
    },

    /*
     * Runs a callback function after a set amount of time. Returns an object which contains several functions and properties for easy management.
     */
    setTimer: function(callback, timeout){
      return setupTimerFunction(window.setTimeout, window.clearTimeout, callback, timeout);
    },

    /*
     * Runs a callback function periodically after a set amount of time. Returns an object which contains several functions and properties for easy management.
     */
    setInterval: function(callback, interval){
      return setupTimerFunction(window.setInterval, window.clearInterval, callback, interval);
    },

    /*
     * Triggers a UTF-8 text file download.
     */
    downloadTextFile: function(fileName, fileContents){
      var ele = document.createElement("a");
      ele.setAttribute("href", "data:text/plain;charset=utf-8,"+encodeURIComponent(fileContents));
      ele.setAttribute("download", fileName);
      ele.style.display = "none";

      document.body.appendChild(ele);
      ele.click();
      document.body.removeChild(ele);
    }
  };
})();
<commit_msg>Use blob to allow larger downloads<commit_after>var DOM = (function(){
  var setupTimerFunction = function(setFunc, clearFunc, callback, time){
    var obj = {
      cancel: function(){
        if (this.isActive){
          clearFunc(this.id);
          this.isActive = false;
        }
      },

      start: function(){
        if (!this.isActive){
          this.id = setFunc(this._callback.bind(obj), this._time);
          this.isActive = true;
        }
      },

      _callback: callback,
      _time: time
    };

    obj.start();
    return obj;
  };

  return {
    /*
     * Returns a child element by its ID. Parent defaults to the entire document.
     */
    id: function(id, parent){
      return (parent || document).getElementById(id);
    },

    /*
     * Returns an array of all child elements containing the specified class. Parent defaults to the entire document.
     */
    cls: function(cls, parent){
      return Array.prototype.slice.call((parent || document).getElementsByClassName(cls));
    },

    /*
     * Returns an array of all child elements that have the specified tag. Parent defaults to the entire document.
     */
    tag: function(tag, parent){
      return Array.prototype.slice.call((parent || document).getElementsByTagName(tag));
    },

    /*
     * Creates an element, adds it to the DOM, and returns it.
     */
    createElement: function(tag, parent){
      var ele = document.createElement(tag);
      parent.appendChild(ele);
      return ele;
    },

    /*
     * Removes an element from the DOM.
     */
    removeElement: function(ele){
      ele.parentNode.removeChild(ele);
    },

    /*
     * Creates a new style element with the specified CSS contents and returns it.
     */
    createStyle: function(styles){
      var ele = document.createElement("style");
      ele.setAttribute("type", "text/css");

      document.head.appendChild(ele);

      styles.forEach(function(style){
        ele.sheet.insertRule(style, 0);
      });

      return ele;
    },

    /*
     * Runs a callback function after a set amount of time. Returns an object which contains several functions and properties for easy management.
     */
    setTimer: function(callback, timeout){
      return setupTimerFunction(window.setTimeout, window.clearTimeout, callback, timeout);
    },

    /*
     * Runs a callback function periodically after a set amount of time. Returns an object which contains several functions and properties for easy management.
     */
    setInterval: function(callback, interval){
      return setupTimerFunction(window.setInterval, window.clearInterval, callback, interval);
    },

    /*
     * Triggers a UTF-8 text file download.
     */
    downloadTextFile: function(fileName, fileContents){
      var a = document.createElement("a");
      document.body.appendChild(a);
      a.style = "display: none";
      var blob = new Blob([fileContents], {type: "octet/stream"}),
          url = window.URL.createObjectURL(blob);
      a.href = url;
      a.download = fileName;
      a.click();
      window.URL.revokeObjectURL(url);
    }
  };
})();
<|endoftext|>


<commit_before>'use strict';

/**
 * Service to handle all scrobbling behavior.
 */
define((require) => {
	const LastFm = require('scrobbler/lastfm');
	const LibreFm = require('scrobbler/librefm');
	const ListenBrainz = require('scrobbler/listenbrainz');
	const ServiceCallResult = require('object/service-call-result');

	/**
	 * Scrobblers that are bound, meaning they have valid session IDs.
	 *
	 * @type {Array}
	 */
	const boundScrobblers = [];

	/**
	 * Scrobblers that are registered and that can be bound.
	 *
	 * @type {Array}
	 */
	const registeredScrobblers = [LastFm, LibreFm, ListenBrainz];

	/**
	 * Check if scrobbler is in given array of scrobblers.
	 * @param  {Object} scrobbler Scrobbler instance
	 * @param  {Array} array Array of scrobblers
	 * @return {Boolean} True if scrobbler is in array, false otherwise
	 */
	function isScrobblerInArray(scrobbler, array) {
		return array.some((s) => {
			return s.getLabel() === scrobbler.getLabel();
		});
	}

	return {
		/**
		 * Bind all registered scrobblers.
		 * @return {Array} Bound scrobblers
		 */
		async bindAllScrobblers() {
			for (const scrobbler of registeredScrobblers) {
				try {
					await scrobbler.getSession();
					this.bindScrobbler(scrobbler);
				} catch (e) {
					console.warn(`Unable to bind ${scrobbler.getLabel()}`);
				}
			}

			return boundScrobblers;
		},

		/**
		 * Bind given scrobbler.
		 * @param {Object} scrobbler Scrobbler instance
		 */
		bindScrobbler(scrobbler) {
			if (!isScrobblerInArray(scrobbler, boundScrobblers)) {
				boundScrobblers.push(scrobbler);
				console.log(`Bind ${scrobbler.getLabel()} scrobbler`);
			}
		},

		/**
		 * Unbind given scrobbler.
		 * @param {Object} scrobbler Scrobbler instance
		 */
		unbindScrobbler(scrobbler) {
			if (isScrobblerInArray(scrobbler, boundScrobblers)) {
				const index = boundScrobblers.indexOf(scrobbler);
				boundScrobblers.splice(index, 1);

				console.log(`Unbind ${scrobbler.getLabel()} scrobbler`);
			} else {
				console.error(`${scrobbler.getLabel()} is not bound`);
			}
		},

		/**
		 * Retrieve song info using scrobbler APIs.
		 * @param  {Object} song Song instance
		 * @return {Promise} Promise resolved with array of song info objects
		 */
		getSongInfo(song) {
			const scrobblers = registeredScrobblers.filter((scrobbler) => {
				return scrobbler.canLoadSongInfo();
			});
			console.log(`Send "get info" request: ${scrobblers.length}`);

			return Promise.all(scrobblers.map((scrobbler) => {
				return scrobbler.getSongInfo(song).catch(() => {
					console.warn(`Unable to get song info from ${scrobbler.label}`);
					return null;
				});
			}));
		},

		/**
		 * Send now playing notification to each bound scrobbler.
		 * @param  {Object} song Song instance
		 * @return {Promise} Promise that will be resolved then the task will complete
		 */
		sendNowPlaying(song) {
			console.log(`Send "now playing" request: ${boundScrobblers.length}`);

			return Promise.all(boundScrobblers.map((scrobbler) => {
				// Forward result (including errors) to caller
				return scrobbler.sendNowPlaying(song).catch((result) => {
					return this.processResult(scrobbler, result);
				});
			}));
		},

		/**
		 * Scrobble song to each bound scrobbler.
		 * @param  {Object} song Song instance
		 * @return {Promise} Promise that will be resolved then the task will complete
		 */
		scrobble(song) {
			console.log(`Send "scrobble" request: ${boundScrobblers.length}`);

			return Promise.all(boundScrobblers.map((scrobbler) => {
				// Forward result (including errors) to caller
				return scrobbler.scrobble(song).catch((result) => {
					return this.processResult(scrobbler, result);
				});
			}));
		},

		/**
		 * Toggle song love status.
		 * @param  {Object} song Song instance
		 * @param  {Boolean} flag Flag indicates song is loved
		 * @return {Promise} Promise that will be resolved then the task will complete
		 */
		toggleLove(song, flag) {
			const scrobblers = registeredScrobblers.filter((scrobbler) => {
				return scrobbler.canLoveSong();
			});
			const requestName = flag ? 'love' : 'unlove';
			console.log(`Send "${requestName}" request: ${scrobblers.length}`);

			return Promise.all(scrobblers.map((scrobbler) => {
				// Forward result (including errors) to caller
				return scrobbler.toggleLove(song, flag).catch((result) => {
					return this.processResult(scrobbler, result);
				});
			}));
		},

		/**
		 * Get all registered scrobblers.
		 * @return {Array} Array of bound scrobblers
		 */
		getRegisteredScrobblers() {
			return registeredScrobblers;
		},

		/**
		 * Get scrobbler by label.
		 * @param  {String} label Scrobbler label
		 * @return {Object} Found scrobbler object
		 */
		getScrobblerByLabel(label) {
			for (const scrobbler of registeredScrobblers) {
				if (scrobbler.getLabel() === label) {
					return scrobbler;
				}
			}

			return null;
		},

		/**
		 * Process result received from scrobbler.
		 * @param  {Object} scrobbler Scrobbler instance
		 * @param  {Object} result API call result
		 * @return {Promise} Promise resolved with result object
		 */
		async processResult(scrobbler, result) {
			if (result === ServiceCallResult.AUTH_ERROR) {
				// Don't unbind scrobblers which have tokens
				const isReady = await scrobbler.isReadyForGrantAccess();
				if (!isReady) {
					this.unbindScrobbler(scrobbler);
				}
			}

			if (!(result in ServiceCallResult)) {
				throw new Error(`Invalid result: ${result}`);
			}

			// Forward result
			return result;
		}
	};
});
<commit_msg>Fix check if service result if invalid<commit_after>'use strict';

/**
 * Service to handle all scrobbling behavior.
 */
define((require) => {
	const LastFm = require('scrobbler/lastfm');
	const LibreFm = require('scrobbler/librefm');
	const ListenBrainz = require('scrobbler/listenbrainz');
	const ServiceCallResult = require('object/service-call-result');

	/**
	 * Scrobblers that are bound, meaning they have valid session IDs.
	 *
	 * @type {Array}
	 */
	const boundScrobblers = [];

	/**
	 * Scrobblers that are registered and that can be bound.
	 *
	 * @type {Array}
	 */
	const registeredScrobblers = [LastFm, LibreFm, ListenBrainz];

	/**
	 * Check if scrobbler is in given array of scrobblers.
	 * @param  {Object} scrobbler Scrobbler instance
	 * @param  {Array} array Array of scrobblers
	 * @return {Boolean} True if scrobbler is in array, false otherwise
	 */
	function isScrobblerInArray(scrobbler, array) {
		return array.some((s) => {
			return s.getLabel() === scrobbler.getLabel();
		});
	}

	return {
		/**
		 * Bind all registered scrobblers.
		 * @return {Array} Bound scrobblers
		 */
		async bindAllScrobblers() {
			for (const scrobbler of registeredScrobblers) {
				try {
					await scrobbler.getSession();
					this.bindScrobbler(scrobbler);
				} catch (e) {
					console.warn(`Unable to bind ${scrobbler.getLabel()}`);
				}
			}

			return boundScrobblers;
		},

		/**
		 * Bind given scrobbler.
		 * @param {Object} scrobbler Scrobbler instance
		 */
		bindScrobbler(scrobbler) {
			if (!isScrobblerInArray(scrobbler, boundScrobblers)) {
				boundScrobblers.push(scrobbler);
				console.log(`Bind ${scrobbler.getLabel()} scrobbler`);
			}
		},

		/**
		 * Unbind given scrobbler.
		 * @param {Object} scrobbler Scrobbler instance
		 */
		unbindScrobbler(scrobbler) {
			if (isScrobblerInArray(scrobbler, boundScrobblers)) {
				const index = boundScrobblers.indexOf(scrobbler);
				boundScrobblers.splice(index, 1);

				console.log(`Unbind ${scrobbler.getLabel()} scrobbler`);
			} else {
				console.error(`${scrobbler.getLabel()} is not bound`);
			}
		},

		/**
		 * Retrieve song info using scrobbler APIs.
		 * @param  {Object} song Song instance
		 * @return {Promise} Promise resolved with array of song info objects
		 */
		getSongInfo(song) {
			const scrobblers = registeredScrobblers.filter((scrobbler) => {
				return scrobbler.canLoadSongInfo();
			});
			console.log(`Send "get info" request: ${scrobblers.length}`);

			return Promise.all(scrobblers.map((scrobbler) => {
				return scrobbler.getSongInfo(song).catch(() => {
					console.warn(`Unable to get song info from ${scrobbler.label}`);
					return null;
				});
			}));
		},

		/**
		 * Send now playing notification to each bound scrobbler.
		 * @param  {Object} song Song instance
		 * @return {Promise} Promise that will be resolved then the task will complete
		 */
		sendNowPlaying(song) {
			console.log(`Send "now playing" request: ${boundScrobblers.length}`);

			return Promise.all(boundScrobblers.map((scrobbler) => {
				// Forward result (including errors) to caller
				return scrobbler.sendNowPlaying(song).catch((result) => {
					return this.processErrorResult(scrobbler, result);
				});
			}));
		},

		/**
		 * Scrobble song to each bound scrobbler.
		 * @param  {Object} song Song instance
		 * @return {Promise} Promise that will be resolved then the task will complete
		 */
		scrobble(song) {
			console.log(`Send "scrobble" request: ${boundScrobblers.length}`);

			return Promise.all(boundScrobblers.map((scrobbler) => {
				// Forward result (including errors) to caller
				return scrobbler.scrobble(song).catch((result) => {
					return this.processErrorResult(scrobbler, result);
				});
			}));
		},

		/**
		 * Toggle song love status.
		 * @param  {Object} song Song instance
		 * @param  {Boolean} flag Flag indicates song is loved
		 * @return {Promise} Promise that will be resolved then the task will complete
		 */
		toggleLove(song, flag) {
			const scrobblers = registeredScrobblers.filter((scrobbler) => {
				return scrobbler.canLoveSong();
			});
			const requestName = flag ? 'love' : 'unlove';
			console.log(`Send "${requestName}" request: ${scrobblers.length}`);

			return Promise.all(scrobblers.map((scrobbler) => {
				// Forward result (including errors) to caller
				return scrobbler.toggleLove(song, flag).catch((result) => {
					return this.processErrorResult(scrobbler, result);
				});
			}));
		},

		/**
		 * Get all registered scrobblers.
		 * @return {Array} Array of bound scrobblers
		 */
		getRegisteredScrobblers() {
			return registeredScrobblers;
		},

		/**
		 * Get scrobbler by label.
		 * @param  {String} label Scrobbler label
		 * @return {Object} Found scrobbler object
		 */
		getScrobblerByLabel(label) {
			for (const scrobbler of registeredScrobblers) {
				if (scrobbler.getLabel() === label) {
					return scrobbler;
				}
			}

			return null;
		},

		/**
		 * Process result received from scrobbler.
		 * @param  {Object} scrobbler Scrobbler instance
		 * @param  {Object} result API call result
		 * @return {Promise} Promise resolved with result object
		 */
		async processErrorResult(scrobbler, result) {
			if (result === ServiceCallResult.AUTH_ERROR) {
				// Don't unbind scrobblers which have tokens
				const isReady = await scrobbler.isReadyForGrantAccess();
				if (!isReady) {
					this.unbindScrobbler(scrobbler);
				}
			}

			const isError = result === ServiceCallResult.AUTH_ERROR ||
				result === ServiceCallResult.ERROR_OTHER;
			if (!isError) {
				throw new Error(`Invalid result: ${result}`);
			}

			// Forward result
			return result;
		}
	};
});
<|endoftext|>


<commit_before>var bower = require('../bower.json');
bower.release = false;

module.exports = {
    bower: bower,
    aws:{
        release: false,
        bucket: process.env.YOUR_AWS_BUCKET,
        key: process.env.YOUR_AWS_ACCESS_KEY_ID,
        secret: process.env.YOUR_AWS_SECRET_ACCESS_KEY,
        region: process.env.YOUR_AWS_REGION
    }
};<commit_msg>config changes<commit_after>var bower = require('../bower.json');
bower.release = true;

module.exports = {
    bower: bower,
    aws:{
        release: true,
        bucket: process.env.TOOLKIT_AWS_SKYGLOBAL_BUCKET,
        key: process.env.TOOLKIT_AWS_ACCESS_KEY_ID,
        secret: process.env.TOOLKIT_AWS_SECRET_ACCESS_KEY,
        region: process.env.TOOLKIT_AWS_REGION
    }
};<|endoftext|>