<commit_before>/*
 * @flow
 */

import Logger from '../utils/Logger';

import {
  APP_API
} from '../constants/ApiNames';

import {
  APP_PATH,
  BULK_PATH,
  CONFIG_PATH,
  INSTALL_PATH,
  TYPE_PATH
} from '../constants/ApiPaths';

import {
  getApiAxiosInstance
} from '../utils/AxiosUtils';

import {
  isNonEmptyString
} from '../utils/LangUtils';

import {
  isValidUuid,
  isValidUuidArray
} from '../utils/ValidationUtils';

const LOG = new Logger('EntityDataModelApi');

/**
  * `GET /app/`
  *
  * Loads all apps.
  *
  * @static
  * @memberof lattice.AppApi
  * @return {Promise<Schema>} - a Promise that will resolve with a list of all apps
  *
  * @example
  * AppApi.getApps();
  */
export function getApps() :Promise<*> {

  let errorMsg = '';

  return getApiAxiosInstance(APP_API)
    .get('/')
    .then((axiosResponse) => {
      return axiosResponse.data;
    })
    .catch((error :Error) => {
      LOG.error(error);
      return Promise.reject(error);
    });
}

/**
  * `GET /app/install/{appId}/{organizationId}/{prefix}`
  *
  * Installs an app for an organization.
  *
  * @static
  * @memberof lattice.AppApi
  * @param {UUID} appId
  * @param {UUID} organizationId
  * @param {string} prefix
  * @return {Promise<Schema>} - a Promise that will resolve after creating an app for an organization
  *
  * @example
  * AppApi.installApp(
  *   "ec6865e6-e60e-424b-a071-6a9c1603d735",
  *   "0c8be4b7-0bd5-4dd1-a623-da78871c9d0e",
  *   "app_prefix"
  * );
  */
export function installApp(appId :UUID, organizationId :UUID, prefix :string) :Promise<*> {

  let errorMsg = '';

  if (!isValidUuid(appId)) {
    errorMsg = 'invalid parameter: appId must be a valid UUID';
    LOG.error(errorMsg, appId);
    return Promise.reject(errorMsg);
  }

  if (!isValidUuid(organizationId)) {
    errorMsg = 'invalid parameter: organizationId must be a valid UUID';
    LOG.error(errorMsg, organizationId);
    return Promise.reject(errorMsg);
  }

  if (!isNonEmptyString(prefix)) {
    errorMsg = 'invalid parameter: prefix must be a non-empty string';
    LOG.error(errorMsg, prefix);
    return Promise.reject(errorMsg);
  }

  return getApiAxiosInstance(APP_API)
    .get(`/${INSTALL_PATH}/${appId}/${organizationId}/${prefix}`)
    .then((axiosResponse) => {
      return axiosResponse.data;
    })
    .catch((error :Error) => {
      LOG.error(error);
      return Promise.reject(error);
    });
}

/**
  * `GET /app/config/{appId}`
  *
  * Loads all available configurations for a provided app id
  *
  * @static
  * @memberof lattice.AppApi
  * @param {UUID} appId
  * @return {Promise<Schema>} - a Promise that will resolve with all available configurations for an app
  *
  * @example
  * AppApi.getConfigurations("ec6865e6-e60e-424b-a071-6a9c1603d735");
  */
export function getConfigurations(appId :UUID) :Promise<*> {

  let errorMsg = '';

  if (!isValidUuid(appId)) {
    errorMsg = 'invalid parameter: appId must be a valid UUID';
    LOG.error(errorMsg, appId);
    return Promise.reject(errorMsg);
  }

  return getApiAxiosInstance(APP_API)
    .get(`/${CONFIG_PATH}/${appId}`)
    .then((axiosResponse) => {
      return axiosResponse.data;
    })
    .catch((error :Error) => {
      LOG.error(error);
      return Promise.reject(error);
    });
}

/**
  * `GET /app/type`
  *
  * Loads app type objects for provided ids
  *
  * @static
  * @memberof lattice.AppApi
  * @param {UUID[]} appTypeIds
  * @return {Promise<Schema>} - a Promise that will resolve with a mapping from app type ids to app types
  *
  * @example
  * AppApi.getAppTypeIds(
  *   "ec6865e6-e60e-424b-a071-6a9c1603d735",
  *   "0c8be4b7-0bd5-4dd1-a623-da78871c9d0e"
  * );
  */
export function getAppTypeIds(appTypeIds :UUID) :Promise<*> {

  let errorMsg = '';

  if (!isValidUuidArray(appTypeIds)) {
    errorMsg = 'invalid parameter: appTypeIds must be a valid UUID array';
    LOG.error(errorMsg, appTypeIds);
    return Promise.reject(errorMsg);
  }

  return getApiAxiosInstance(APP_API)
    .post(`/${TYPE_PATH}/${BULK_PATH}`, appTypeIds)
    .then((axiosResponse) => {
      return axiosResponse.data;
    })
    .catch((error :Error) => {
      LOG.error(error);
      return Promise.reject(error);
    });
}
<commit_msg>get app<commit_after>/*
 * @flow
 */

import Logger from '../utils/Logger';

import {
  APP_API
} from '../constants/ApiNames';

import {
  APP_PATH,
  BULK_PATH,
  CONFIG_PATH,
  INSTALL_PATH,
  TYPE_PATH
} from '../constants/ApiPaths';

import {
  getApiAxiosInstance
} from '../utils/AxiosUtils';

import {
  isNonEmptyString
} from '../utils/LangUtils';

import {
  isValidUuid,
  isValidUuidArray
} from '../utils/ValidationUtils';

const LOG = new Logger('EntityDataModelApi');

/**
  * `GET /app/`
  *
  * Loads all apps.
  *
  * @static
  * @memberof lattice.AppApi
  * @return {Promise<Schema>} - a Promise that will resolve with a list of all apps
  *
  * @example
  * AppApi.getApps();
  */
export function getApps() :Promise<*> {

  let errorMsg = '';

  return getApiAxiosInstance(APP_API)
    .get('/')
    .then((axiosResponse) => {
      return axiosResponse.data;
    })
    .catch((error :Error) => {
      LOG.error(error);
      return Promise.reject(error);
    });
}

/**
  * `GET /app/install/{appId}/{organizationId}/{prefix}`
  *
  * Installs an app for an organization.
  *
  * @static
  * @memberof lattice.AppApi
  * @param {UUID} appId
  * @param {UUID} organizationId
  * @param {string} prefix
  * @return {Promise<Schema>} - a Promise that will resolve after creating an app for an organization
  *
  * @example
  * AppApi.installApp(
  *   "ec6865e6-e60e-424b-a071-6a9c1603d735",
  *   "0c8be4b7-0bd5-4dd1-a623-da78871c9d0e",
  *   "app_prefix"
  * );
  */
export function installApp(appId :UUID, organizationId :UUID, prefix :string) :Promise<*> {

  let errorMsg = '';

  if (!isValidUuid(appId)) {
    errorMsg = 'invalid parameter: appId must be a valid UUID';
    LOG.error(errorMsg, appId);
    return Promise.reject(errorMsg);
  }

  if (!isValidUuid(organizationId)) {
    errorMsg = 'invalid parameter: organizationId must be a valid UUID';
    LOG.error(errorMsg, organizationId);
    return Promise.reject(errorMsg);
  }

  if (!isNonEmptyString(prefix)) {
    errorMsg = 'invalid parameter: prefix must be a non-empty string';
    LOG.error(errorMsg, prefix);
    return Promise.reject(errorMsg);
  }

  return getApiAxiosInstance(APP_API)
    .get(`/${INSTALL_PATH}/${appId}/${organizationId}/${prefix}`)
    .then((axiosResponse) => {
      return axiosResponse.data;
    })
    .catch((error :Error) => {
      LOG.error(error);
      return Promise.reject(error);
    });
}

/**
  * `GET /app/config/{appId}`
  *
  * Loads all available configurations for a provided app id
  *
  * @static
  * @memberof lattice.AppApi
  * @param {UUID} appId
  * @return {Promise<Schema>} - a Promise that will resolve with all available configurations for an app
  *
  * @example
  * AppApi.getConfigurations("ec6865e6-e60e-424b-a071-6a9c1603d735");
  */
export function getConfigurations(appId :UUID) :Promise<*> {

  let errorMsg = '';

  if (!isValidUuid(appId)) {
    errorMsg = 'invalid parameter: appId must be a valid UUID';
    LOG.error(errorMsg, appId);
    return Promise.reject(errorMsg);
  }

  return getApiAxiosInstance(APP_API)
    .get(`/${CONFIG_PATH}/${appId}`)
    .then((axiosResponse) => {
      return axiosResponse.data;
    })
    .catch((error :Error) => {
      LOG.error(error);
      return Promise.reject(error);
    });
}

/**
  * `GET /app/{appId}`
  *
  * Loads app with provided id
  *
  * @static
  * @memberof lattice.AppApi
  * @param {UUID[]} appTypeIds
  * @return {Promise<Schema>} - a Promise that will resolve with the details of an app
  *
  * @example
  * AppApi.getApp("0c8be4b7-0bd5-4dd1-a623-da78871c9d0e");
  */

export function getApp(appId :UUID) :Promise<*> {

  let errorMsg = '';

  if (!isValidUuid(appId)) {
    errorMsg = 'invalid parameter: appId must be a valid UUID';
    LOG.error(errorMsg, appId);
    return Promise.reject(errorMsg);
  }

  return getApiAxiosInstance(APP_API)
    .get(`/${appId}`)
    .then((axiosResponse) => {
      return axiosResponse.data;
    })
    .catch((error :Error) => {
      LOG.error(error);
      return Promise.reject(error);
    });
}

/**
  * `GET /app/type`
  *
  * Loads app type objects for provided ids
  *
  * @static
  * @memberof lattice.AppApi
  * @param {UUID[]} appTypeIds
  * @return {Promise<Schema>} - a Promise that will resolve with a mapping from app type ids to app types
  *
  * @example
  * AppApi.getAppTypeIds(
  *   "ec6865e6-e60e-424b-a071-6a9c1603d735",
  *   "0c8be4b7-0bd5-4dd1-a623-da78871c9d0e"
  * );
  */

export function getAppTypeIds(appTypeIds :UUID) :Promise<*> {

  let errorMsg = '';

  if (!isValidUuidArray(appTypeIds)) {
    errorMsg = 'invalid parameter: appTypeIds must be a valid UUID array';
    LOG.error(errorMsg, appTypeIds);
    return Promise.reject(errorMsg);
  }

  return getApiAxiosInstance(APP_API)
    .post(`/${TYPE_PATH}/${BULK_PATH}`, appTypeIds)
    .then((axiosResponse) => {
      return axiosResponse.data;
    })
    .catch((error :Error) => {
      LOG.error(error);
      return Promise.reject(error);
    });
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import RDX from './RDX';

class RDXClass extends Component {
  constructor(props) {
    super(props);
    this.state = {};
    this.actions = RDX(props.actions, s => this.setState(s) );
  }

  componentWillMount() {
    if (this.actions['initalise']){
      this.actions['initalise']();
    }
  }

  render() {
    return this.props.render(this.state, this.actions);
  }
}

export default function RDXReact(render, actions) {
  return <RDXClass actions={actions} render={render} />;
}

<commit_msg>removed spaces<commit_after>import React, { Component } from 'react';
import RDX from './RDX';

class RDXClass extends Component {
  constructor(props) {
    super(props);
    this.state = {};
    this.actions = RDX(props.actions, s => this.setState(s) );
  }

  componentWillMount() {
    if (this.actions['initalise']){
      this.actions['initalise']();
    }
  }

  render() {
    return this.props.render(this.state, this.actions);
  }
}

export default function RDXReact(render, actions) {
  return <RDXClass actions={actions} render={render} />;
}<|endoftext|>


<commit_before>var superagent = require('superagent');
var hashObject = require('./hash.js').object;
var isoJsLog = require('./isoJsLog.js');
var workData = require('./workData.js');
var url = require('url');

var sendApiRequest = function(req, request, done) {
	var reqUrl = url.resolve(workData.config.getApiServerAddress(), request.url);
	var pendingRequest = superagent;
	switch (request.method.toUpperCase()) {
		case 'GET':
			pendingRequest = pendingRequest.get(reqUrl);
			break;
		case 'POST':
			pendingRequest = pendingRequest.post(reqUrl);
			break;
		default:
			isoJSlog.warn('Not supported request method! (' + request.method + ') isoJS Info: If you need support for this method, please open a GitHub Issue.');
			break;
	}
	pendingRequest = pendingRequest.query(request.qs);

	if (request._formData != null) {
		pendingRequest = pendingRequest.send(request._formData);
	}
	if (request._maxRedirects != null) {
		pendingRequest = pendingRequest.redirects(request._maxRedirects);
	}
	if (request._timeout != null) {
		pendingRequest = pendingRequest.timeout(request._timeout);
	}
	if (request._data != null) {
		pendingRequest = pendingRequest.send(request._data);
	}
	if (request.headers != null) {
		pendingRequest = pendingRequest.set(request.headers);
	}
	if (request._id != null) {
		isoJSlog.error('(fetchDataThis): request._id !== null => Shoud NOT HAPPEN! Please create a GitHub issue.');
		delete request._id;
	}
	var arr = [].concat(workData.forwardHeaders);
	if(req.headers['isojs-forward-headers'] != null){
		try {
			var forwardHeaders = JSON.parse(req.headers['isojs-forward-headers']);
			arr = arr.concat(forwardHeaders);
		} catch(e){
			isoJsLog.error('Invalid forward header!');
			console.error('> ', e);
		}
	}
	for (var i in workData.forwardHeaders) {
		if (req.headers[i] != null) {
			pendingRequest = pendingRequest.set(i, req.headers[i]);
		}
	}

	request._id = hashObject(request);
	if (req.isojs.fetchedData[request._id] == null) {
		pendingRequest.end(function(superErr, superRes) {
			req.isojs.fetchedData[request._id] = {
				err: superErr,
				res: superRes
			};
			done(req, request);
		});
	} else {
		done(req, request);
	}
}
module.exports = sendApiRequest;
<commit_msg>Fixed bug with forward header<commit_after>var superagent = require('superagent');
var hashObject = require('./hash.js').object;
var isoJsLog = require('./isoJsLog.js');
var workData = require('./workData.js');
var url = require('url');

var sendApiRequest = function(req, request, done) {
	var reqUrl = url.resolve(workData.config.getApiServerAddress(), request.url);
	var pendingRequest = superagent;
	switch (request.method.toUpperCase()) {
		case 'GET':
			pendingRequest = pendingRequest.get(reqUrl);
			break;
		case 'POST':
			pendingRequest = pendingRequest.post(reqUrl);
			break;
		default:
			isoJSlog.warn('Not supported request method! (' + request.method + ') isoJS Info: If you need support for this method, please open a GitHub Issue.');
			break;
	}
	pendingRequest = pendingRequest.query(request.qs);

	if (request._formData != null) {
		pendingRequest = pendingRequest.send(request._formData);
	}
	if (request._maxRedirects != null) {
		pendingRequest = pendingRequest.redirects(request._maxRedirects);
	}
	if (request._timeout != null) {
		pendingRequest = pendingRequest.timeout(request._timeout);
	}
	if (request._data != null) {
		pendingRequest = pendingRequest.send(request._data);
	}
	if (request.headers != null) {
		pendingRequest = pendingRequest.set(request.headers);
	}
	if (request._id != null) {
		isoJSlog.error('(fetchDataThis): request._id !== null => Shoud NOT HAPPEN! Please create a GitHub issue.');
		delete request._id;
	}

	var arr = [].concat(workData.config.forwardHeaders);
	if(req.headers['isojs-forward-headers'] != null){
		try {
			var forwardHeaders = JSON.parse(req.headers['isojs-forward-headers']);
			arr = arr.concat(forwardHeaders);
		} catch(e){
			isoJsLog.error('Invalid forward header!');
			console.error('> ', e);
		}
	}
	for (var i in arr) {
		if (req.headers[arr[i]] != null) {
			pendingRequest = pendingRequest.set(arr[i], req.headers[arr[i]]);
		}
	}

	request._id = hashObject(request);
	if (req.isojs.fetchedData[request._id] == null) {
		pendingRequest.end(function(superErr, superRes) {
			req.isojs.fetchedData[request._id] = {
				err: superErr,
				res: superRes
			};
			done(req, request);
		});
	} else {
		done(req, request);
	}
}
module.exports = sendApiRequest;
<|endoftext|>


<commit_before>describe("Game is created", function() {

    beforeEach(function() {
	  this.gameFactory = new GameBoard(50, 80, {biggest_side: 400, middle_side: 270, smallest_side:140});
    });

    it("sets x", function(){
	expect(50).toEqual(this.gameFactory.x);
    });

    it("sets y", function(){
	expect(80).toEqual(this.gameFactory.y);
    });

    it("sets box lengths", function(){
	expect({biggest_side: 400, middle_side: 270, smallest_side:140}).toEqual(this.gameFactory.box_lengths);
    });

    it("sets side length", function(){
	expect(400).toEqual(this.gameFactory.sideLength);
    });

    it("starts in phase one at game start", function(){
	expect(true).toEqual(this.gameFactory.in_phase_1());
    });

    it("does not start in phase 2 at game start", function(){
	expect(false).toEqual(this.gameFactory.in_phase_2());
    });

    it("does not start in phase 3 at game start", function(){
	expect(false).toEqual(this.gameFactory.in_phase_3());
    });

    it("creates 18 game pieces", function(){
	expect(18).toEqual(this.gameFactory.gamePieceArray.length);
    });

    it("creates 24 game spaces", function(){
	expect(24).toEqual(this.gameFactory.gameSpaceArray.length);
    });

    it("creates a stage", function() {

       //this.gameFactory = new GameBoard(50, 80, {biggest_side: 400, middle_side: 270, smallest_side:140});

       var ExpectedStage = new Kinetic.Stage({
        container: 'container',
        width: 500,
        height: 600
    });

        var ActualStage = this.gameFactory.stageContainer;
        expect(ActualStage.attrs).toEqual(ExpectedStage.attrs);
    });

    it("creates a game board", function() {

	//var gameFactory = new GameBoard(50, 80, {biggest_side: 400, middle_side: 270, smallest_side:140});

        var Expected_game_board = new Kinetic.Group({
            x: 50,
            y: 80
        });
        var Actual_game_board = this.gameFactory.game_board;
        //Kinetic objects are a bit complex, if you just grab attrs, it contains the attributes we actually set, such as x and y here
        expect(Actual_game_board.attrs).toEqual(Expected_game_board.attrs);
    });

    it("sets only one piece to movable", function(){

	var countDraggable = 0;

	for(var i = 0; i < this.gameFactory.gamePieceArray.length; i++){
	    if(this.gameFactory.gamePieceArray[i].circle.draggable()){
	       countDraggable++;
	    }
	}

	expect(1).toEqual(countDraggable);
    });

    it("sets the first white piece to movable", function(){
	expect(true).toEqual(this.gameFactory.gamePieceArray[17].circle.draggable());
    });

    it("creates 3 boxes and draws them on the game board", function() {
        expect(true).toEqual(true);
    });

    it("adds game board to game board layer", function() {
        expect(true).toEqual(true);
    });

    it("creates 9 red and 9 white game pieces and adds to game board layer", function() {
        expect(true).toEqual(true);
    });

    it("adds game board layer to the stage", function() {
        expect(true).toEqual(true);
    });
});

describe("Game piece is created", function() {

    beforeEach(function() {

	this.gameFactory = new GameBoard(50, 80, {biggest_side: 400, middle_side: 270, smallest_side:140});

	this.gamePiece = new GamePiece(      450,
					      30,
					      'red',
					      false,
					      this.gameFactory.gameBoardLayer,
					      this.gameFactory.gameSpaceArray,
					      0, this.gameFactory
				      );
    });

    it("creates a circle", function(){
	var Expected_circle = new Kinetic.Circle({
				      x: 450,
                                      y: 30,
                                      radius: 20,
                                      fill: 'red',
                                      stroke: 'black',
                                      strokeWidth: 2,
                                      draggable: false});

	expect(this.gamePiece.circle.attrs).toEqual(Expected_circle.attrs);
    });

    it("test the setDraggable function", function() {

	this.gamePiece.setDraggable(true);

        expect(true).toEqual(this.gamePiece.circle.draggable());
    });

    it("test the set_next function", function() {

	this.gamePiece.set_next(true);

        expect(true).toEqual(this.gamePiece.circle.next);
    });

    it("test the setInMill function", function() {

	this.gamePiece.setInMill();

        expect(false).toEqual(this.gamePiece.removable);
    });

    it("test the setSpace function", function() {

	this.gamePiece.setSpace(5);

        expect(5).toEqual(this.gamePiece.space);
    });

    it("test the moved function", function() {

        expect(0).toEqual(this.gamePiece.moved());
    });

    it("test the on_board function", function() {

        expect(false).toEqual(this.gamePiece.on_board());
    });

});

describe("Transitions from 1 to 2", function() {

    beforeEach(function() {

	this.gameFactory = new GameBoard(50, 80, {biggest_side: 400, middle_side: 270, smallest_side:140});

	for (var i = 0; i < this.gameFactory.gamePieceArray.length; i++){

	  for(var j = 0; j < this.gameFactory.gameSpaceArray.length; j++){

	      if(!this.gameFactory.gameSpaceArray[j].occupied){
		  this.gameFactory.gamePieceArray[i].circle.x(this.gameFactory.gameSpaceArray[j].circle.getAbsolutePosition().x);
		  this.gameFactory.gamePieceArray[i].circle.y(this.gameFactory.gameSpaceArray[j].circle.getAbsolutePosition().y);
		  this.gameFactory.gamePieceArray[i].circle.draggable(true);
		  this.gameFactory.gameSpaceArray[j].occupied = true;
		  this.gameFactory.gamePieceArray[i].circle.on_board = true;
		  this.gameFactory.gameBoardLayer.draw();
		  break;
	      }

	  }

	}

    });

    it("test not in phase 1", function() {

         expect(false).toEqual(this.gameFactory.in_phase_1());
    });

    it("test in phase 2", function() {

        expect(true).toEqual(this.gameFactory.in_phase_2());
    });

    it("test not in phase 3", function() {

         expect(false).toEqual(this.gameFactory.in_phase_3());
    });

});

describe("Transitions from 2 to 3", function() {

    beforeEach(function() {

	this.gameFactory = new GameBoard(50, 80, {biggest_side: 400, middle_side: 270, smallest_side:140});

	for (var i = 0; i < 6; i++){

	  for(var j = 0; j < this.gameFactory.gameSpaceArray.length; j++){

	      if(!this.gameFactory.gameSpaceArray[j].occupied){
		  this.gameFactory.gamePieceArray[i].circle.x(this.gameFactory.gameSpaceArray[j].circle.getAbsolutePosition().x);
		  this.gameFactory.gamePieceArray[i].circle.y(this.gameFactory.gameSpaceArray[j].circle.getAbsolutePosition().y);
		  this.gameFactory.gamePieceArray[i].circle.draggable(true);
		  this.gameFactory.gameSpaceArray[j].occupied = true;
		  this.gameFactory.gamePieceArray[i].circle.on_board = true;
		  this.gameFactory.gameBoardLayer.draw();
		  break;
	      }

	  }

	}

    });

    it("test not in phase 1", function() {

         expect(true).toEqual(this.gameFactory.in_phase_1());
    });

    it("test not in phase 2", function() {

        expect(false).toEqual(this.gameFactory.in_phase_2());
    });

    it("test in phase 3", function() {

         expect(false).toEqual(this.gameFactory.in_phase_3());
    });

});

describe("Decrementer", function() {
    it("Keeps track of how many pieces are on the board", function() {
        decrementer = new Decrementer();
        decrementer.decrement("white");
        decrementer.decrement("red");
        decrementer.decrement("white");
        decrementer.decrement("red");
        expect(decrementer.red).toEqual(7);
        expect(decrementer.white).toEqual(7);
    });

    it("returns true if the number of pieces for a player is less than three.", function() {
        decrementer = new Decrementer();
        for(i = 0; i < 6; i++) {
            decrementer.decrement("white");
            decrementer.decrement("red");
        }
        expect(decrementer.hasThreeOrLess("red")).toEqual(true);
        expect(decrementer.hasThreeOrLess("white")).toEqual(true);
    });
});
<commit_msg>added test of ai_is_active() function<commit_after>describe("Game is created", function() {

    beforeEach(function() {
	  this.gameFactory = new GameBoard(50, 80, {biggest_side: 400, middle_side: 270, smallest_side:140});
    });

    it("sets x", function(){
	expect(50).toEqual(this.gameFactory.x);
    });

    it("sets y", function(){
	expect(80).toEqual(this.gameFactory.y);
    });

    it("sets box lengths", function(){
	expect({biggest_side: 400, middle_side: 270, smallest_side:140}).toEqual(this.gameFactory.box_lengths);
    });

    it("sets side length", function(){
	expect(400).toEqual(this.gameFactory.sideLength);
    });

    it("starts in phase one at game start", function(){
	expect(true).toEqual(this.gameFactory.in_phase_1());
    });

    it("does not start in phase 2 at game start", function(){
	expect(false).toEqual(this.gameFactory.in_phase_2());
    });

    it("does not start in phase 3 at game start", function(){
	expect(false).toEqual(this.gameFactory.in_phase_3());
    });

    it("creates 18 game pieces", function(){
	expect(18).toEqual(this.gameFactory.gamePieceArray.length);
    });

    it("creates 24 game spaces", function(){
	expect(24).toEqual(this.gameFactory.gameSpaceArray.length);
    });

    it("checks if ai is active", function(){
	expect(false).toEqual(this.gameFactory.ai_is_active());
    });

    it("creates a stage", function() {

       //this.gameFactory = new GameBoard(50, 80, {biggest_side: 400, middle_side: 270, smallest_side:140});

       var ExpectedStage = new Kinetic.Stage({
        container: 'container',
        width: 500,
        height: 600
    });

        var ActualStage = this.gameFactory.stageContainer;
        expect(ActualStage.attrs).toEqual(ExpectedStage.attrs);
    });

    it("creates a game board", function() {

	//var gameFactory = new GameBoard(50, 80, {biggest_side: 400, middle_side: 270, smallest_side:140});

        var Expected_game_board = new Kinetic.Group({
            x: 50,
            y: 80
        });
        var Actual_game_board = this.gameFactory.game_board;
        //Kinetic objects are a bit complex, if you just grab attrs, it contains the attributes we actually set, such as x and y here
        expect(Actual_game_board.attrs).toEqual(Expected_game_board.attrs);
    });

    it("sets only one piece to movable", function(){

	var countDraggable = 0;

	for(var i = 0; i < this.gameFactory.gamePieceArray.length; i++){
	    if(this.gameFactory.gamePieceArray[i].circle.draggable()){
	       countDraggable++;
	    }
	}

	expect(1).toEqual(countDraggable);
    });

    it("sets the first white piece to movable", function(){
	expect(true).toEqual(this.gameFactory.gamePieceArray[17].circle.draggable());
    });

    it("creates 3 boxes and draws them on the game board", function() {
        expect(true).toEqual(true);
    });

    it("adds game board to game board layer", function() {
        expect(true).toEqual(true);
    });

    it("creates 9 red and 9 white game pieces and adds to game board layer", function() {
        expect(true).toEqual(true);
    });

    it("adds game board layer to the stage", function() {
        expect(true).toEqual(true);
    });
});

describe("Game piece is created", function() {

    beforeEach(function() {

	this.gameFactory = new GameBoard(50, 80, {biggest_side: 400, middle_side: 270, smallest_side:140});

	this.gamePiece = new GamePiece(      450,
					      30,
					      'red',
					      false,
					      this.gameFactory.gameBoardLayer,
					      this.gameFactory.gameSpaceArray,
					      0, this.gameFactory
				      );
    });

    it("creates a circle", function(){
	var Expected_circle = new Kinetic.Circle({
				      x: 450,
                                      y: 30,
                                      radius: 20,
                                      fill: 'red',
                                      stroke: 'black',
                                      strokeWidth: 2,
                                      draggable: false});

	expect(this.gamePiece.circle.attrs).toEqual(Expected_circle.attrs);
    });

    it("test the setDraggable function", function() {

	this.gamePiece.setDraggable(true);

        expect(true).toEqual(this.gamePiece.circle.draggable());
    });

    it("test the set_next function", function() {

	this.gamePiece.set_next(true);

        expect(true).toEqual(this.gamePiece.circle.next);
    });

    it("test the setInMill function", function() {

	this.gamePiece.setInMill();

        expect(false).toEqual(this.gamePiece.removable);
    });

    it("test the setSpace function", function() {

	this.gamePiece.setSpace(5);

        expect(5).toEqual(this.gamePiece.space);
    });

    it("test the moved function", function() {

        expect(0).toEqual(this.gamePiece.moved());
    });

    it("test the on_board function", function() {

        expect(false).toEqual(this.gamePiece.on_board());
    });

});

describe("Transitions from 1 to 2", function() {

    beforeEach(function() {

	this.gameFactory = new GameBoard(50, 80, {biggest_side: 400, middle_side: 270, smallest_side:140});

	for (var i = 0; i < this.gameFactory.gamePieceArray.length; i++){

	  for(var j = 0; j < this.gameFactory.gameSpaceArray.length; j++){

	      if(!this.gameFactory.gameSpaceArray[j].occupied){
		  this.gameFactory.gamePieceArray[i].circle.x(this.gameFactory.gameSpaceArray[j].circle.getAbsolutePosition().x);
		  this.gameFactory.gamePieceArray[i].circle.y(this.gameFactory.gameSpaceArray[j].circle.getAbsolutePosition().y);
		  this.gameFactory.gamePieceArray[i].circle.draggable(true);
		  this.gameFactory.gameSpaceArray[j].occupied = true;
		  this.gameFactory.gamePieceArray[i].circle.on_board = true;
		  this.gameFactory.gameBoardLayer.draw();
		  break;
	      }

	  }

	}

    });

    it("test not in phase 1", function() {

         expect(false).toEqual(this.gameFactory.in_phase_1());
    });

    it("test in phase 2", function() {

        expect(true).toEqual(this.gameFactory.in_phase_2());
    });

    it("test not in phase 3", function() {

         expect(false).toEqual(this.gameFactory.in_phase_3());
    });

});

describe("Transitions from 2 to 3", function() {

    beforeEach(function() {

	this.gameFactory = new GameBoard(50, 80, {biggest_side: 400, middle_side: 270, smallest_side:140});

	for (var i = 0; i < 6; i++){

	  for(var j = 0; j < this.gameFactory.gameSpaceArray.length; j++){

	      if(!this.gameFactory.gameSpaceArray[j].occupied){
		  this.gameFactory.gamePieceArray[i].circle.x(this.gameFactory.gameSpaceArray[j].circle.getAbsolutePosition().x);
		  this.gameFactory.gamePieceArray[i].circle.y(this.gameFactory.gameSpaceArray[j].circle.getAbsolutePosition().y);
		  this.gameFactory.gamePieceArray[i].circle.draggable(true);
		  this.gameFactory.gameSpaceArray[j].occupied = true;
		  this.gameFactory.gamePieceArray[i].circle.on_board = true;
		  this.gameFactory.gameBoardLayer.draw();
		  break;
	      }

	  }

	}

    });

    it("test not in phase 1", function() {

         expect(true).toEqual(this.gameFactory.in_phase_1());
    });

    it("test not in phase 2", function() {

        expect(false).toEqual(this.gameFactory.in_phase_2());
    });

    it("test in phase 3", function() {

         expect(false).toEqual(this.gameFactory.in_phase_3());
    });

});

describe("Decrementer", function() {
    it("Keeps track of how many pieces are on the board", function() {
        decrementer = new Decrementer();
        decrementer.decrement("white");
        decrementer.decrement("red");
        decrementer.decrement("white");
        decrementer.decrement("red");
        expect(decrementer.red).toEqual(7);
        expect(decrementer.white).toEqual(7);
    });

    it("returns true if the number of pieces for a player is less than three.", function() {
        decrementer = new Decrementer();
        for(i = 0; i < 6; i++) {
            decrementer.decrement("white");
            decrementer.decrement("red");
        }
        expect(decrementer.hasThreeOrLess("red")).toEqual(true);
        expect(decrementer.hasThreeOrLess("white")).toEqual(true);
    });
});
<|endoftext|>


<commit_before>/**
 * Created by rauldablaing on 1/19/15.
 */

/* jshint quotmark:false */

// Add dependencies
var frisby = require('frisby');
// Set up variables for info you will use often
frisby.create('Create contact with post')
.post('http://localhost:3000/api/students',
  {
    firstName: "Jojo",
    lastName: "Jones"
  },
  {json: true})
.expectStatus(200)
.toss();
<commit_msg>makes frisby test able to post, put, get, delete a student and add a course to a student<commit_after>/**
 * Created by rauldablaing on 1/19/15.
 */

/* jshint quotmark:false */

// Add dependencies
var frisby = require('frisby');
var url = 'http://localhost:3000/api/students/';
var initialStudent = {
  "firstName": "Jojo",
  "lastName": "Jones"
};
var newCourse = {
  "title": "Java",
  "courseType": "Programming",
  "description": "Basics"
};
// Set up variables for info you will use often
frisby.create('Create contact with post')
.post('http://localhost:3000/api/students',
    initialStudent,
  {json: true})
.expectStatus(200)
  .afterJSON(function(json){
    getRecord(json.id)
  }
)
.toss();

// Read a record
function getRecord(id){
  frisby.create('Get student using id')
    .get(url + id)
    .expectStatus(200)
    .expectJSON(initialStudent)
    .afterJSON(function(json){
      putRecord(json.id);
    })
    .toss();
}

function putRecord(id){
  frisby.create('Put student using id')
    .put(url + id,{
    "firstName": "Chuck",
    "lastName": "Luck"
  }, {json:true})
    .expectStatus(200)
    .expectJSON({
      "firstName": "Chuck",
      "lastName": "Luck"
    })
    .afterJSON(function(json){
      testCourse(id);
    })
    .toss();
}

function testCourse(id){
  frisby.create('Create course to student')
    .post(url + id + '/courses', newCourse)
    .expectStatus(200)
    .expectJSON(newCourse)
    .afterJSON(function(json){
      deleteRecord(json.id);
    })
    .toss()
}

function deleteRecord(id){
  frisby.create('Delete student using id')
    .delete(url + id)
    .expectStatus(204)
    .toss();
}

<|endoftext|>


<commit_before>chrome.runtime.onInstalled.addListener(function() {
	chrome.contextMenus.create({
		"title":		chrome.i18n.getMessage('menuTitle'),
		"documentUrlPatterns":	["https://*.waze.com/*/editor/*", "https://*.waze.com/editor/*"],
		"contexts":		['page'],
		"id":			'WazeEditorReportSend'});

	chrome.contextMenus.onClicked.addListener(function(info, tab) {
		if (info.menuItemId != "WazeEditorReportSend") { return };

		var desc = prompt(chrome.i18n.getMessage('enterDescription'));
		if (!desc) return;

		chrome.cookies.get({url: "https://www.waze.com", name: "_csrf_token"},
			function(cookie) {
				chrome.tabs.sendMessage(tab.id, 'getCurrentLocation', function(response) {
					console.log('Got coordinates: ' + JSON.stringify(response));

					var xhr = new XMLHttpRequest();
					xhr.open("POST", "https://www.waze.com/row-Descartes-live/app/MapProblems/UpdateRequest", true);
					xhr.setRequestHeader('X-CSRF-Token', cookie.value);
					xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
					xhr.send("geometry=POINT(" + response.lat + "+" + response.lon + ")&" +
						 "description=" + desc + "&" +
						 "type=7");
				});
			}
		);
	});
});<commit_msg>Subscribe to "onClicked" after Chrome restart<commit_after>chrome.runtime.onInstalled.addListener(function() {
	chrome.contextMenus.create({
		"title":		chrome.i18n.getMessage('menuTitle'),
		"documentUrlPatterns":	["https://*.waze.com/*/editor/*", "https://*.waze.com/editor/*"],
		"contexts":		['page'],
		"id":			'WazeEditorReportSend'});

});

chrome.contextMenus.onClicked.addListener(function(info, tab) {
	if (info.menuItemId != "WazeEditorReportSend") { return };

	var desc = prompt(chrome.i18n.getMessage('enterDescription'));
	if (!desc) return;

	chrome.cookies.get({url: "https://www.waze.com", name: "_csrf_token"},
		function(cookie) {
			chrome.tabs.sendMessage(tab.id, 'getCurrentLocation', function(response) {
				console.log('Got coordinates: ' + JSON.stringify(response));

				var xhr = new XMLHttpRequest();
				xhr.open("POST", "https://www.waze.com/row-Descartes-live/app/MapProblems/UpdateRequest", true);
				xhr.setRequestHeader('X-CSRF-Token', cookie.value);
				xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
				xhr.send("geometry=POINT(" + response.lat + "+" + response.lon + ")&" +
					 "description=" + desc + "&" +
					 "type=7");
			});
		}
	);
});
<|endoftext|>


<commit_before>/**
Copyright (c) 2017 Kenny Do

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
**/
/* global browserDetect */
import {updateSetting, validateSettings, cacheCookieStoreIdNames, addExpression, incrementCookieDeletedCounter, cookieCleanup} from "./redux/Actions";
import {getSetting, getHostname, isAWebpage, returnOptionalCookieAPIAttributes} from "./services/libs";
import {checkIfProtected, showNumberOfCookiesInIcon} from "./services/BrowserActionService";
import createStore from "./redux/Store";

let store;
let currentSettings;

// Delay saving to disk to queue up actions
let delaySave = false;
const saveToStorage = () => {
	if (!delaySave) {
		delaySave = true;
		setTimeout(() => {
			delaySave = false;
			return browser.storage.local.set({
				state: JSON.stringify(store.getState())
			});
		}, 1000);
	}
};

const onSettingsChange = () => {
	let previousSettings = currentSettings;
	currentSettings = store.getState().settings;
	// Container Mode enabled
	if (!previousSettings.contextualIdentities.value && currentSettings.contextualIdentities.value) {
		store.dispatch(
			cacheCookieStoreIdNames()
		);
	}

	// Localstorage support enabled
	if (!previousSettings.localstorageCleanup.value && currentSettings.localstorageCleanup.value) {
		browser.browsingData.removeLocalStorage({
			since: 0
		});
	}

	if (previousSettings.activeMode.value && !currentSettings.activeMode.value) {
		browser.alarms.clear("activeModeAlarm");
	}
};

// Create an alarm delay or use setTimeout before cookie cleanup
let alarmFlag = false;
const createActiveModeAlarm = () => {
	const seconds = parseInt(getSetting(store.getState(), "delayBeforeClean"), 10);
	const minutes = seconds / 60;
	const milliseconds = seconds * 1000;
	if (alarmFlag) {
		return;
	}
	alarmFlag = true;
	if (seconds < 1) {
		setTimeout(() => {
			store.dispatch(
				cookieCleanup({
					greyCleanup: false, ignoreOpenTabs: false
				})
			);
			alarmFlag = false;
		}, 500);
	} else if (browserDetect() === "Firefox" || (browserDetect() === "Chrome" && seconds >= 60)) {
		browser.alarms.create("activeModeAlarm", {
			delayInMinutes: minutes
		});
	} else {
		setTimeout(() => {
			if (getSetting(store.getState(), "activeMode")) {
				store.dispatch(
					cookieCleanup({
						greyCleanup: false, ignoreOpenTabs: false
					})
				);
			}
			alarmFlag = false;
		}, milliseconds);
	}
};

// Create an alarm when a tab is closed
const onTabRemoved = async (tabId, removeInfo) => {
	if (getSetting(store.getState(), "activeMode")) {
		const alarm = await browser.alarms.get("activeModeAlarm");
		// This is to resolve differences between Firefox and Chrome implementation of browser.alarms.get()
		// in chrome, it returns an array
		if (store.getState().cache.browserDetect === "Firefox" && !alarm) {
			createActiveModeAlarm();
		} else if (alarm.name !== "activeModeAlarm") {
			createActiveModeAlarm();
		}
	}
};

const getAllCookieActions = async (tab) => {
	const cookies = await browser.cookies.getAll(
		returnOptionalCookieAPIAttributes(store.getState(), {
			domain: getHostname(tab.url),
			storeId: tab.cookieStoreId,
			firstPartyDomain: getHostname(tab.url)
		})
	);

	let cookieLength = cookies.length;
	if (cookies.length === 0 && getSetting(store.getState(), "localstorageCleanup") && isAWebpage(tab.url)) {
		browser.cookies.set(
			returnOptionalCookieAPIAttributes(store.getState(), {
				url: tab.url,
				name: "CookieAutoDelete",
				value: "cookieForLocalstorageCleanup",
				path: "/cookie-for-localstorage-cleanup",
				firstPartyDomain: getHostname(tab.url)
			})
		);
		cookieLength = 1;
	}
	if (getSetting(store.getState(), "showNumOfCookiesInIcon")) {
		showNumberOfCookiesInIcon(tab, cookieLength);
	} else {
		browser.browserAction.setBadgeText({
			text: "", tabId: tab.id
		});
	}
};

export const onTabUpdate = (tabId, changeInfo, tab) => {
	if (tab.status === "complete") {
		checkIfProtected(store.getState(), tab);
		getAllCookieActions(tab);
	}
};

const migration = (oldSettings) => {
	if (Object.keys(oldSettings) !== 0 && oldSettings.migration_1 === undefined && oldSettings.activeMode !== undefined) {
		store.dispatch(
			incrementCookieDeletedCounter(oldSettings.cookieDeletedCounterTotal)
		);
		store.dispatch(
			updateSetting({
				payload: {
					id: 1, name: "activeMode", value: oldSettings.activeMode
				}
			})
		);
		store.dispatch(
			updateSetting({
				payload: {
					id: 2, name: "delayBeforeClean", value: oldSettings.delayBeforeClean
				}
			})
		);
		store.dispatch(
			updateSetting({
				payload: {
					id: 3, name: "statLogging", value: oldSettings.statLoggingSetting
				}
			})
		);
		store.dispatch(
			updateSetting({
				payload: {
					id: 4, name: "showNumOfCookiesInIcon", value: oldSettings.showNumberOfCookiesInIconSetting
				}
			})
		);
		store.dispatch(
			updateSetting({
				payload: {
					id: 5, name: "showNotificationAfterCleanup", value: oldSettings.notifyCookieCleanUpSetting
				}
			})
		);
		store.dispatch(
			updateSetting({
				payload: {
					id: 6, name: "cleanCookiesFromOpenTabsOnStartup", value: oldSettings.cookieCleanUpOnStartSetting
				}
			})
		);
		store.dispatch(
			updateSetting({
				payload: {
					id: 7, name: "contextualIdentities", value: oldSettings.contextualIdentitiesEnabledSetting
				}
			})
		);
		if (oldSettings.contextualIdentitiesEnabledSetting) {
			store.dispatch(
				cacheCookieStoreIdNames()
			);
			const newContainerCache = [
				...oldSettings.containerCache,
				{
					cookieStoreId: "firefox-default"
				}
			];
			newContainerCache.forEach((container) => {
				const {
					cookieStoreId
				} = container;
				const greyPrefixed = `${cookieStoreId}-Grey`;
				if (oldSettings[cookieStoreId] !== undefined) {
					oldSettings[cookieStoreId].forEach((domain) => {
						const expression = oldSettings.enableGlobalSubdomainSetting ? `*.${domain}` : domain;
						store.dispatch(
							addExpression({
								payload: {
									expression,
									listType: "WHITE",
									storeId: cookieStoreId
								}
							})
						);
					});
				}
				if (oldSettings[greyPrefixed] !== undefined) {
					oldSettings[greyPrefixed].forEach((domain) => {
						const expression = oldSettings.enableGlobalSubdomainSetting ? `*.${domain}` : domain;
						store.dispatch(
							addExpression({
								payload: {
									expression,
									listType: "GREY",
									storeId: cookieStoreId
								}
							})
						);
					});
				}
			});
		} else {
			const cookieStoreId = "defaultWhiteList";
			const greyPrefixed = `${cookieStoreId}-Grey`;
			if (oldSettings[cookieStoreId] !== undefined) {
				oldSettings[cookieStoreId].forEach((domain) => {
					const expression = oldSettings.enableGlobalSubdomainSetting ? `*.${domain}` : domain;
					store.dispatch(
						addExpression({
							payload: {
								expression,
								listType: "WHITE",
								storeId: cookieStoreId
							}
						})
					);
				});
			}
			if (oldSettings[greyPrefixed] !== undefined) {
				oldSettings[greyPrefixed].forEach((domain) => {
					const expression = oldSettings.enableGlobalSubdomainSetting ? `*.${domain}` : domain;
					store.dispatch(
						addExpression({
							payload: {
								expression,
								listType: "GREY",
								storeId: cookieStoreId
							}
						})
					);
				});
			}
		}
		saveToStorage();
		browser.storage.local.set({
			migration_1: true
		});
	}
};

const onStartUp = async () => {
	const storage = await browser.storage.local.get();
	let stateFromStorage;
	try {
		if (storage.state !== undefined) {
			stateFromStorage = JSON.parse(storage.state);
		} else {
			stateFromStorage = {};
		}
	} catch (err) {
		stateFromStorage = {};
	}
	store = createStore(stateFromStorage);
	migration(storage);
	store.dispatch(
		validateSettings()
	);
	store.dispatch({
		type: "ON_STARTUP"
	});
	// Store the FF version in cache
	if (browserDetect() === "Firefox") {
		const browserInfo = await browser.runtime.getBrowserInfo();
		const browserVersion = browserInfo.version.split(".")[0];
		store.dispatch({
			type: "ADD_CACHE",
			map: {
				key: "browserVersion", value: browserVersion
			}
		});

		// Store whether firstPartyIsolate is true or false
		if (browserVersion >= 58) {
			const setting = await browser.privacy.websites.firstPartyIsolate.get({});
			store.dispatch({
				type: "ADD_CACHE",
				map: {
					key: "firstPartyIsolateSetting", value: setting.value
				}
			});
			if (browserVersion === "58" && setting.value) {
				browser.notifications.create("FPI_NOTIFICATION", {
					"type": "basic",
					"iconUrl": browser.extension.getURL("icons/icon_48.png"),
					"title": "First Party Isolation Detected",
					"message": "Please turn off privacy.firstparty.isolate and restart the browser as it breaks cookie cleanup"
				});
			}
		}
	}
	// Store which browser environment in cache
	store.dispatch({
		type: "ADD_CACHE",
		map: {
			key: "browserDetect", value: browserDetect()
		}
	});

	// Temporary fix until contextualIdentities events land
	if (getSetting(store.getState(), "contextualIdentities")) {
		store.dispatch(
			cacheCookieStoreIdNames()
		);
	}
	if (getSetting(store.getState(), "activeMode")) {
		store.dispatch(
			cookieCleanup({
				greyCleanup: true, ignoreOpenTabs: getSetting(store.getState(), "cleanCookiesFromOpenTabsOnStartup")
			})
		);
	}
	currentSettings = store.getState().settings;
	store.subscribe(onSettingsChange);
	store.subscribe(saveToStorage);
};

onStartUp();

// Logic that controls when to disable the browser action
browser.tabs.onUpdated.addListener(onTabUpdate);
browser.tabs.onRemoved.addListener(onTabRemoved);

// Alarm event handler for Active Mode
browser.alarms.onAlarm.addListener((alarmInfo) => {
	// console.log(alarmInfo.name);
	if (alarmInfo.name === "activeModeAlarm") {
		store.dispatch(
			cookieCleanup({
				greyCleanup: false, ignoreOpenTabs: false
			})
		);
		alarmFlag = false;
		browser.alarms.clear(alarmInfo.name);
	}
});
<commit_msg>Fix #345<commit_after>/**
Copyright (c) 2017 Kenny Do

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
**/
/* global browserDetect */
import {updateSetting, validateSettings, cacheCookieStoreIdNames, addExpression, incrementCookieDeletedCounter, cookieCleanup} from "./redux/Actions";
import {getSetting, getHostname, isAWebpage, returnOptionalCookieAPIAttributes} from "./services/libs";
import {checkIfProtected, showNumberOfCookiesInIcon} from "./services/BrowserActionService";
import createStore from "./redux/Store";

let store;
let currentSettings;

// Delay saving to disk to queue up actions
let delaySave = false;
const saveToStorage = () => {
	if (!delaySave) {
		delaySave = true;
		setTimeout(() => {
			delaySave = false;
			return browser.storage.local.set({
				state: JSON.stringify(store.getState())
			});
		}, 1000);
	}
};

const onSettingsChange = () => {
	let previousSettings = currentSettings;
	currentSettings = store.getState().settings;
	// Container Mode enabled
	if (!previousSettings.contextualIdentities.value && currentSettings.contextualIdentities.value) {
		store.dispatch(
			cacheCookieStoreIdNames()
		);
	}

	// Localstorage support enabled
	if (!previousSettings.localstorageCleanup.value && currentSettings.localstorageCleanup.value) {
		browser.browsingData.removeLocalStorage({
			since: 0
		});
	}

	if (previousSettings.activeMode.value && !currentSettings.activeMode.value) {
		browser.alarms.clear("activeModeAlarm");
	}
};

// Create an alarm delay or use setTimeout before cookie cleanup
let alarmFlag = false;
const createActiveModeAlarm = () => {
	const seconds = parseInt(getSetting(store.getState(), "delayBeforeClean"), 10);
	const minutes = seconds / 60;
	const milliseconds = seconds * 1000;
	if (alarmFlag) {
		return;
	}
	alarmFlag = true;
	if (seconds < 1) {
		setTimeout(() => {
			store.dispatch(
				cookieCleanup({
					greyCleanup: false, ignoreOpenTabs: false
				})
			);
			alarmFlag = false;
		}, 500);
	} else if (browserDetect() === "Firefox" || (browserDetect() === "Chrome" && seconds >= 60)) {
		browser.alarms.create("activeModeAlarm", {
			delayInMinutes: minutes
		});
	} else {
		setTimeout(() => {
			if (getSetting(store.getState(), "activeMode")) {
				store.dispatch(
					cookieCleanup({
						greyCleanup: false, ignoreOpenTabs: false
					})
				);
			}
			alarmFlag = false;
		}, milliseconds);
	}
};

// Create an alarm when a tab is closed
const onTabRemoved = async (tabId, removeInfo) => {
	if (getSetting(store.getState(), "activeMode")) {
		const alarm = await browser.alarms.get("activeModeAlarm");
		// This is to resolve differences between Firefox and Chrome implementation of browser.alarms.get()
		// in chrome, it returns an array
		if (store.getState().cache.browserDetect === "Firefox" && !alarm) {
			createActiveModeAlarm();
		} else if (alarm.name !== "activeModeAlarm") {
			createActiveModeAlarm();
		}
	}
};

const getAllCookieActions = async (tab) => {
	const cookies = await browser.cookies.getAll(
		returnOptionalCookieAPIAttributes(store.getState(), {
			domain: getHostname(tab.url),
			storeId: tab.cookieStoreId,
			firstPartyDomain: getHostname(tab.url)
		})
	);

	let cookieLength = cookies.length;
	if (cookies.length === 0 && getSetting(store.getState(), "localstorageCleanup") && isAWebpage(tab.url)) {
		browser.cookies.set(
			returnOptionalCookieAPIAttributes(store.getState(), {
				url: tab.url,
				name: "CookieAutoDelete",
				value: "cookieForLocalstorageCleanup",
				path: "/cookie-for-localstorage-cleanup",
				firstPartyDomain: getHostname(tab.url),
				expirationDate: Math.floor(Date.now() / 1000 + 31557600)
			})
		);
		cookieLength = 1;
	}
	if (getSetting(store.getState(), "showNumOfCookiesInIcon")) {
		showNumberOfCookiesInIcon(tab, cookieLength);
	} else {
		browser.browserAction.setBadgeText({
			text: "", tabId: tab.id
		});
	}
};

export const onTabUpdate = (tabId, changeInfo, tab) => {
	if (tab.status === "complete") {
		checkIfProtected(store.getState(), tab);
		getAllCookieActions(tab);
	}
};

const migration = (oldSettings) => {
	if (Object.keys(oldSettings) !== 0 && oldSettings.migration_1 === undefined && oldSettings.activeMode !== undefined) {
		store.dispatch(
			incrementCookieDeletedCounter(oldSettings.cookieDeletedCounterTotal)
		);
		store.dispatch(
			updateSetting({
				payload: {
					id: 1, name: "activeMode", value: oldSettings.activeMode
				}
			})
		);
		store.dispatch(
			updateSetting({
				payload: {
					id: 2, name: "delayBeforeClean", value: oldSettings.delayBeforeClean
				}
			})
		);
		store.dispatch(
			updateSetting({
				payload: {
					id: 3, name: "statLogging", value: oldSettings.statLoggingSetting
				}
			})
		);
		store.dispatch(
			updateSetting({
				payload: {
					id: 4, name: "showNumOfCookiesInIcon", value: oldSettings.showNumberOfCookiesInIconSetting
				}
			})
		);
		store.dispatch(
			updateSetting({
				payload: {
					id: 5, name: "showNotificationAfterCleanup", value: oldSettings.notifyCookieCleanUpSetting
				}
			})
		);
		store.dispatch(
			updateSetting({
				payload: {
					id: 6, name: "cleanCookiesFromOpenTabsOnStartup", value: oldSettings.cookieCleanUpOnStartSetting
				}
			})
		);
		store.dispatch(
			updateSetting({
				payload: {
					id: 7, name: "contextualIdentities", value: oldSettings.contextualIdentitiesEnabledSetting
				}
			})
		);
		if (oldSettings.contextualIdentitiesEnabledSetting) {
			store.dispatch(
				cacheCookieStoreIdNames()
			);
			const newContainerCache = [
				...oldSettings.containerCache,
				{
					cookieStoreId: "firefox-default"
				}
			];
			newContainerCache.forEach((container) => {
				const {
					cookieStoreId
				} = container;
				const greyPrefixed = `${cookieStoreId}-Grey`;
				if (oldSettings[cookieStoreId] !== undefined) {
					oldSettings[cookieStoreId].forEach((domain) => {
						const expression = oldSettings.enableGlobalSubdomainSetting ? `*.${domain}` : domain;
						store.dispatch(
							addExpression({
								payload: {
									expression,
									listType: "WHITE",
									storeId: cookieStoreId
								}
							})
						);
					});
				}
				if (oldSettings[greyPrefixed] !== undefined) {
					oldSettings[greyPrefixed].forEach((domain) => {
						const expression = oldSettings.enableGlobalSubdomainSetting ? `*.${domain}` : domain;
						store.dispatch(
							addExpression({
								payload: {
									expression,
									listType: "GREY",
									storeId: cookieStoreId
								}
							})
						);
					});
				}
			});
		} else {
			const cookieStoreId = "defaultWhiteList";
			const greyPrefixed = `${cookieStoreId}-Grey`;
			if (oldSettings[cookieStoreId] !== undefined) {
				oldSettings[cookieStoreId].forEach((domain) => {
					const expression = oldSettings.enableGlobalSubdomainSetting ? `*.${domain}` : domain;
					store.dispatch(
						addExpression({
							payload: {
								expression,
								listType: "WHITE",
								storeId: cookieStoreId
							}
						})
					);
				});
			}
			if (oldSettings[greyPrefixed] !== undefined) {
				oldSettings[greyPrefixed].forEach((domain) => {
					const expression = oldSettings.enableGlobalSubdomainSetting ? `*.${domain}` : domain;
					store.dispatch(
						addExpression({
							payload: {
								expression,
								listType: "GREY",
								storeId: cookieStoreId
							}
						})
					);
				});
			}
		}
		saveToStorage();
		browser.storage.local.set({
			migration_1: true
		});
	}
};

const onStartUp = async () => {
	const storage = await browser.storage.local.get();
	let stateFromStorage;
	try {
		if (storage.state !== undefined) {
			stateFromStorage = JSON.parse(storage.state);
		} else {
			stateFromStorage = {};
		}
	} catch (err) {
		stateFromStorage = {};
	}
	store = createStore(stateFromStorage);
	migration(storage);
	store.dispatch(
		validateSettings()
	);
	store.dispatch({
		type: "ON_STARTUP"
	});
	// Store the FF version in cache
	if (browserDetect() === "Firefox") {
		const browserInfo = await browser.runtime.getBrowserInfo();
		const browserVersion = browserInfo.version.split(".")[0];
		store.dispatch({
			type: "ADD_CACHE",
			map: {
				key: "browserVersion", value: browserVersion
			}
		});

		// Store whether firstPartyIsolate is true or false
		if (browserVersion >= 58) {
			const setting = await browser.privacy.websites.firstPartyIsolate.get({});
			store.dispatch({
				type: "ADD_CACHE",
				map: {
					key: "firstPartyIsolateSetting", value: setting.value
				}
			});
			if (browserVersion === "58" && setting.value) {
				browser.notifications.create("FPI_NOTIFICATION", {
					"type": "basic",
					"iconUrl": browser.extension.getURL("icons/icon_48.png"),
					"title": "First Party Isolation Detected",
					"message": "Please turn off privacy.firstparty.isolate and restart the browser as it breaks cookie cleanup"
				});
			}
		}
	}
	// Store which browser environment in cache
	store.dispatch({
		type: "ADD_CACHE",
		map: {
			key: "browserDetect", value: browserDetect()
		}
	});

	// Temporary fix until contextualIdentities events land
	if (getSetting(store.getState(), "contextualIdentities")) {
		store.dispatch(
			cacheCookieStoreIdNames()
		);
	}
	if (getSetting(store.getState(), "activeMode")) {
		store.dispatch(
			cookieCleanup({
				greyCleanup: true, ignoreOpenTabs: getSetting(store.getState(), "cleanCookiesFromOpenTabsOnStartup")
			})
		);
	}
	currentSettings = store.getState().settings;
	store.subscribe(onSettingsChange);
	store.subscribe(saveToStorage);
};

onStartUp();

// Logic that controls when to disable the browser action
browser.tabs.onUpdated.addListener(onTabUpdate);
browser.tabs.onRemoved.addListener(onTabRemoved);

// Alarm event handler for Active Mode
browser.alarms.onAlarm.addListener((alarmInfo) => {
	// console.log(alarmInfo.name);
	if (alarmInfo.name === "activeModeAlarm") {
		store.dispatch(
			cookieCleanup({
				greyCleanup: false, ignoreOpenTabs: false
			})
		);
		alarmFlag = false;
		browser.alarms.clear(alarmInfo.name);
	}
});
<|endoftext|>


<commit_before>// This is main process of Electron, started as first thing when your
// app starts. This script is running through entire life of your application.
// It doesn't have any windows which you can see on screen, but we can open
// window from here.

import path from 'path';
import url from 'url';
import { app, Menu } from 'electron';
import os from 'os';
import { getTemplate } from './menu/menu_template';
import createWindow from './helpers/window';
import { autoUpdater } from 'electron-auto-updater';

// Special module holding environment variables which you declared
// in config/env_xxx.json file.
import env from './env';

var mainWindow;
var willQuitApp = false;

// Save userData in separate folders for each environment.
// Thanks to this you can use production and development versions of the app
// on same machine like those are two separate apps.
if (env.name !== 'production') {
    var userDataPath = app.getPath('userData');
    app.setPath('userData', userDataPath + ' (' + env.name + ')');
}

app.on('ready', function () {
    Menu.setApplicationMenu(Menu.buildFromTemplate(getTemplate(app)));
    mainWindow = createWindow('main', {
        width: 1200,
        height: 800
    });

    mainWindow.loadURL(url.format({
        pathname: path.join(__dirname, 'app.html'),
        protocol: 'file:',
        slashes: true
    }));

    mainWindow.on('close', (e) => {
        if (!willQuitApp) {
            e.preventDefault();
            mainWindow.hide();
        }
    })

    if (env.name === 'development') {
        mainWindow.openDevTools();
    }

    autoUpdater.checkForUpdates();
    console.log('autoUpdater.getFeedURL()', autoUpdater.getFeedURL());
    autoUpdater.on('error', (err) => {
        console.log('err');
    });
    autoUpdater.on('checking-for-update', () => {
        console.log('checking-for-update');
    });
    autoUpdater.on('update-available', () => {
        console.log('update-available');
    });
    autoUpdater.on('update-not-available', () => {
        console.log('update-not-available');
    });
});

app.on('activate', () => mainWindow.show());

app.on('before-quit', () => willQuitApp = true);
<commit_msg>Do not prevent closing the app on Windows.<commit_after>// This is main process of Electron, started as first thing when your
// app starts. This script is running through entire life of your application.
// It doesn't have any windows which you can see on screen, but we can open
// window from here.

import path from 'path';
import url from 'url';
import { app, Menu } from 'electron';
import os from 'os';
import { getTemplate } from './menu/menu_template';
import createWindow from './helpers/window';
import { autoUpdater } from 'electron-auto-updater';

// Special module holding environment variables which you declared
// in config/env_xxx.json file.
import env from './env';

var mainWindow;
var willQuitApp = false;

// Save userData in separate folders for each environment.
// Thanks to this you can use production and development versions of the app
// on same machine like those are two separate apps.
if (env.name !== 'production') {
    var userDataPath = app.getPath('userData');
    app.setPath('userData', userDataPath + ' (' + env.name + ')');
}

app.on('ready', function () {
    Menu.setApplicationMenu(Menu.buildFromTemplate(getTemplate(app)));
    mainWindow = createWindow('main', {
        width: 1200,
        height: 800
    });

    mainWindow.loadURL(url.format({
        pathname: path.join(__dirname, 'app.html'),
        protocol: 'file:',
        slashes: true
    }));

    if (process.platform === 'darwin') {
        mainWindow.on('close', (e) => {
            if (!willQuitApp) {
                e.preventDefault();
                mainWindow.hide();
            }
        });
    }

    if (env.name === 'development') {
        mainWindow.openDevTools();
    }

    autoUpdater.checkForUpdates();
    console.log('autoUpdater.getFeedURL()', autoUpdater.getFeedURL());
    autoUpdater.on('error', (err) => {
        console.log('err');
    });
    autoUpdater.on('checking-for-update', () => {
        console.log('checking-for-update');
    });
    autoUpdater.on('update-available', () => {
        console.log('update-available');
    });
    autoUpdater.on('update-not-available', () => {
        console.log('update-not-available');
    });
});

app.on('activate', () => mainWindow.show());

app.on('before-quit', () => willQuitApp = true);
<|endoftext|>


<commit_before>(function () {
  "use strict";

  var currentIndex = [];

  function getCurrentActiveTab() {
    chrome.tabs.query({currentWindow: true, active: true}, function (tabs) {
      console.debug(tabs[0].windowId + ' - chrome.tabs.query - index: ' + tabs[0].index); // debug
      currentIndex[tabs[0].windowId] = tabs[0].index;
    });
    // fallback
    chrome.windows.getCurrent(function(window) {
      if (!Number.isInteger(currentIndex[window.id])) {
        // last position (default behavior)
        chrome.tabs.query({currentWindow: true}, function (tabs) {
          console.debug(tabs[0].windowId + ' - fallback - tabs.length: ' + tabs.length); // debug
          currentIndex[tabs[0].windowId] = tabs.length;
        });
      }
    });
  }

  /**
   * Remember the current tab index
   */
  // on extension install/update
  chrome.runtime.onInstalled.addListener(function(details) {
    if (details.reason == 'install' || details.reason == 'update') {
      console.debug('chrome.runtime.onInstalled'); // debug
      getCurrentActiveTab();
    }
  });
  // on extension enabled
  chrome.management.onEnabled.addListener(function(ExtensionInfo) {
    if (ExtensionInfo.id == chrome.runtime.id) {
      console.debug('chrome.management.onEnabled'); // debug
      getCurrentActiveTab();
    }
  });
  // on window focused
  chrome.windows.onFocusChanged.addListener(function(windowId) {
    // https://developer.chrome.com/extensions/windows#property-WINDOW_ID_NONE
    if (windowId != -1 && windowId != -2) {
      chrome.tabs.getSelected(chrome.tabs.windowId, function(tab) {
        console.debug(windowId + ' - chrome.windows.onFocusChanged - tab.index: ' + tab.index); // debug
        currentIndex[windowId] = tab.index;
      });
    }
  });
  // on tab activated
  chrome.tabs.onActivated.addListener(function(activeInfo) {
    chrome.tabs.get(activeInfo.tabId, function(tab) {
      console.debug(tab.windowId + ' - chrome.tabs.onActivated - tab.index: ' + tab.index); // debug
      currentIndex[tab.windowId] = tab.index;
    });
  });
  // on tab manually moved
  chrome.tabs.onMoved.addListener(function(tabId, moveInfo) {
    console.debug(moveInfo.windowId + ' - chrome.tabs.onMoved - moveInfo.toIndex: ' + moveInfo.toIndex); // debug
    currentIndex[moveInfo.windowId] = moveInfo.toIndex;
  });

  /**
   * Move new tab after the current
   */
  // on created
  chrome.tabs.onCreated.addListener(function(tab) {
    console.debug(tab.windowId + ' - chrome.tabs.onCreated - currentIndex: ' + currentIndex[tab.windowId]); // debug
    if (Number.isInteger(currentIndex[tab.windowId])) {
      chrome.tabs.move(tab.id, {
        index: currentIndex[tab.windowId] + 1
      });
    }
  });
  // on updated
  chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
    console.debug(tab.windowId + ' - chrome.tabs.onUpdated - currentIndex: ' + currentIndex[tab.windowId]); // debug
    if (Number.isInteger(currentIndex[tab.windowId])) {
      chrome.tabs.move(tab.id, {
        index: currentIndex[tab.windowId]
      });
    }
  });
})();<commit_msg>Using onUpdated event instead of onCreated<commit_after>(function () {
  "use strict";

  var currentIndex = [];

  function getCurrentActiveTab() {
    chrome.tabs.query({currentWindow: true, active: true}, function (tabs) {
      console.debug(tabs[0].windowId + ' - chrome.tabs.query - index: ' + tabs[0].index); // debug
      currentIndex[tabs[0].windowId] = tabs[0].index;
    });
    // fallback
    chrome.windows.getCurrent(function(window) {
      if (!Number.isInteger(currentIndex[window.id])) {
        // last position (default behavior)
        chrome.tabs.query({currentWindow: true}, function (tabs) {
          console.debug(tabs[0].windowId + ' - fallback - tabs.length: ' + tabs.length); // debug
          currentIndex[tabs[0].windowId] = tabs.length;
        });
      }
    });
  }

  /**
   * Remember the current tab index
   */
  // on extension install/update
  chrome.runtime.onInstalled.addListener(function(details) {
    if (details.reason == 'install' || details.reason == 'update') {
      console.debug('chrome.runtime.onInstalled'); // debug
      getCurrentActiveTab();
    }
  });
  // on extension enabled
  chrome.management.onEnabled.addListener(function(ExtensionInfo) {
    if (ExtensionInfo.id == chrome.runtime.id) {
      console.debug('chrome.management.onEnabled'); // debug
      getCurrentActiveTab();
    }
  });
  // on window focused
  chrome.windows.onFocusChanged.addListener(function(windowId) {
    // https://developer.chrome.com/extensions/windows#property-WINDOW_ID_NONE
    if (windowId != -1 && windowId != -2) {
      chrome.tabs.getSelected(chrome.tabs.windowId, function(tab) {
        console.debug(windowId + ' - chrome.windows.onFocusChanged - tab.index: ' + tab.index); // debug
        currentIndex[windowId] = tab.index;
      });
    }
  });
  // on tab activated
  chrome.tabs.onActivated.addListener(function(activeInfo) {
    chrome.tabs.get(activeInfo.tabId, function(tab) {
      console.debug(tab.windowId + ' - chrome.tabs.onActivated - tab.index: ' + tab.index); // debug
      currentIndex[tab.windowId] = tab.index;
    });
  });
  // on tab manually moved
  chrome.tabs.onMoved.addListener(function(tabId, moveInfo) {
    console.debug(moveInfo.windowId + ' - chrome.tabs.onMoved - moveInfo.toIndex: ' + moveInfo.toIndex); // debug
    currentIndex[moveInfo.windowId] = moveInfo.toIndex;
  });

  /**
   * Move new tab after the current
   */
  // on created
  chrome.tabs.onCreated.addListener(function(tab) {
    console.debug(tab.windowId + ' - chrome.tabs.onCreated - tab.index: ' + tab.index); // debug
  });
  // on updated
  chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
    console.debug(tab.windowId + ' - chrome.tabs.onUpdated - tab.index: ' + tab.index); // debug
    console.debug('tab.title: ' + tab.title);
    if (Number.isInteger(currentIndex[tab.windowId])) {
      console.debug(tab.windowId + ' - chrome.tabs.onUpdated - currentIndex: ' + currentIndex[tab.windowId]); // debug
      var moveToIndex = currentIndex[tab.windowId] + 1;
      console.debug('Is tab.index: ' + tab.index + ' + 1 != ' + 'moveToIndex: ' + moveToIndex + ' ?'); // debug
      if (tab.index + 1 != moveToIndex) {
        console.debug('Move it to: ' + moveToIndex);
        chrome.tabs.move(tab.id, {
          index: moveToIndex
        });
      }
    }
  });
})();<|endoftext|>


<commit_before>import React from 'react';
import Avatar from 'material-ui/Avatar';
import {List, ListItem} from 'material-ui/List';
import Subheader from 'material-ui/Subheader';

class SongList extends React.Component {
  render() {
    return(
      <List>
        <Subheader>Songs</Subheader>
        <ListItem
          primaryText="Awesome Song"
          leftAvatar={<Avatar src="http://cdn2-www.dogtime.com/assets/uploads/gallery/30-impossibly-cute-puppies/impossibly-cute-puppy-8.jpg" />}
        />
        <ListItem
          primaryText="Awesome Song"
          leftAvatar={<Avatar src="http://cdn2-www.dogtime.com/assets/uploads/gallery/30-impossibly-cute-puppies/impossibly-cute-puppy-8.jpg" />}
        />
        <ListItem
          primaryText="Awesome Song"
          leftAvatar={<Avatar src="http://cdn2-www.dogtime.com/assets/uploads/gallery/30-impossibly-cute-puppies/impossibly-cute-puppy-8.jpg" />}
        />
        <ListItem
          primaryText="Awesome Song"
          leftAvatar={<Avatar src="http://cdn2-www.dogtime.com/assets/uploads/gallery/30-impossibly-cute-puppies/impossibly-cute-puppy-8.jpg" />}
        />
      </List>
    )
  }
}

export default SongList;
<commit_msg>Render songs from state<commit_after>import React from 'react';
import Avatar from 'material-ui/Avatar';
import {List, ListItem} from 'material-ui/List';
import Subheader from 'material-ui/Subheader';

class SongList extends React.Component {
  constructor() {
    super();

    this.renderSong = this.renderSong.bind(this);
  }

  renderSong(song) {
    return(
      <ListItem
        key={song.id.videoId}
        primaryText={song.snippet.title}
        leftAvatar={<Avatar src={song.snippet.thumbnails.default.url} />}
      />
    )
  }

  render() {
    return(
      <List>
        <Subheader>Songs</Subheader>
        {this.props.songs.map(this.renderSong)}
      </List>
    )
  }
}

export default SongList;
<|endoftext|>


<commit_before>var defaultSettings = {
  focus: {
    duration: 25,
    desktopNotification: true,
    newTabNotification: true,
    sound: null
  },
  break: {
    duration: 5,
    desktopNotification: true,
    newTabNotification: true,
    sound: null
  },
  version: 1
};

var sounds = [
  { name: 'Tone', file: 'tone.mp3' },
  { name: 'Electronic Chime', file: 'electronic-chime.mp3' },
  { name: 'Gong 1', file: 'gong-1.mp3' },
  { name: 'Gong 2', file: 'gong-2.mp3' },
  { name: 'Computer Magic', file: 'computer-magic.mp3' },
  { name: 'Fire Pager', file: 'fire-pager.mp3' },
  { name: 'Glass Ping', file: 'glass-ping.mp3' },
  { name: 'Music Box', file: 'music-box.mp3' },
  { name: 'Pin Dropping', file: 'pin-dropping.mp3' },
  { name: 'Robot Blip 1', file: 'robot-blip-1.mp3' },
  { name: 'Robot Blip 2', file: 'robot-blip-2.mp3' },
  { name: 'Ship Bell', file: 'ship-bell.mp3' },
  { name: 'Train Horn', file: 'train-horn.mp3' },
  { name: 'Bike Horn', file: 'bike-horn.mp3' },
  { name: 'Bell Ring', file: 'bell-ring.mp3' },
  { name: 'Reception Bell', file: 'reception-bell.mp3' },
  { name: 'Toaster Oven', file: 'toaster-oven.mp3' },
  { name: 'Battle Horn', file: 'battle-horn.mp3' },
  { name: 'Ding', file: 'ding.mp3' },
  { name: 'Dong', file: 'dong.mp3' },
  { name: 'Ding Dong', file: 'ding-dong.mp3' },
  { name: 'Din Ding', file: 'din-ding.mp3' }
];

for (var i = 0; i < sounds.length; ++i) {
  sounds[i].file = 'chrome-extension://' + chrome.runtime.id + '/audio/' + sounds[i].file;
}

function Timer(durationSec, tickSec) {
  var self = this;
  var state = 'stopped';

  this.tickInterval = null;
  this.expireTimeout = null;

  this.periodStartTime = null;
  this.remainingSec = null;

  this.start = function() {
    if (state !== 'stopped') {
      return;
    }

    this.expireTimeout = createExpireTimeout(durationSec);
    this.tickInterval = createTickInterval(tickSec);

    this.remainingSec = durationSec;

    state = 'running';
    this.periodStartTime = Date.now();
    this.emitEvent('start', [{
      elapsed: 0,
      remaining: this.remainingSec
    }]);
  };

  this.stop = function() {
    if (state === 'stopped') {
      return;
    }

    clearInterval(this.tickInterval);
    clearTimeout(this.expireTimeout);

    this.tickInterval = null;
    this.expireTimeout = null;
    this.periodStartTime = null;
    this.remainingSec = null;

    state = 'stopped';
    this.emitEvent('stop', [{}]);
  };

  this.pause = function() {
    if (state !== 'running') {
      return;
    }

    clearInterval(this.tickInterval);
    clearTimeout(this.expireTimeout);

    var periodSec = (Date.now() - this.periodStartTime) / 1000;
    this.remainingSec -= periodSec;

    state = 'paused';
    this.periodStartTime = null;
    this.emitEvent('pause', [{
      elapsed: durationSec - this.remainingSec,
      remaining: this.remainingSec
    }]);
  };

  this.resume = function() {
    if (state !== 'paused') {
      return;
    }

    this.expireTimeout = createExpireTimeout(this.remainingSec);
    this.tickInterval = createTickInterval(tickSec);

    state = 'running';
    this.periodStartTime = Date.now();
    this.emitEvent('resume', [{
      elapsed: durationSec - this.remainingSec,
      remaining: this.remainingSec
    }]);
  };

  this.reset = function() {
    this.stop();
    this.start();
  };

  this.state = function() {
    return state;
  };

  function createExpireTimeout(seconds) {
    return setTimeout(function() {
      clearInterval(self.tickInterval);
      clearTimeout(self.expireTimeout);

      self.tickInterval = null;
      self.expireTimeout = null;
      self.periodStartTime = null;
      self.remainingSec = null;

      state = 'stopped';
      self.emitEvent('expire', [{
        elapsed: durationSec,
        remaining: 0
      }]);
    }, seconds * 1000);
  }

  function createTickInterval(seconds) {
    return setInterval(function() {
      var periodSec = (Date.now() - self.periodStartTime) / 1000;
      var remainingSec = self.remainingSec - periodSec;

      self.emitEvent('tick', [{
        elapsed: durationSec - remainingSec,
        remaining: remainingSec
      }]);
    }, seconds * 1000);
  }
}

Timer.prototype = Object.create(EventEmitter.prototype);

function BadgeObserver() {
}

BadgeObserver.observe = function(timer, title, color) {
  timer.addListener('start', function(state) {
    updateBadge({ minutes: Math.round(state.remaining / 60) });
  });

  timer.addListener('tick', function(state) {
    updateBadge({ minutes: Math.round(state.remaining / 60) });
  });

  timer.addListener('stop', function() {
    removeBadge();
  });

  timer.addListener('pause', function() {
    updateBadge({ text: '—', title: 'Paused' });
  });

  timer.addListener('resume', function(state) {
    updateBadge({ minutes: Math.round(state.remaining / 60) });
  });

  timer.addListener('expire', function() {
    removeBadge();
  });

  function updateBadge(options) {
    var minutes = options.minutes;
    if (minutes != null) {
      text = ((minutes == 0) ? '<1' : minutes)  + 'm';
      badgeTitle = title + ': ' + minutes + 'm remaining.';
    } else {
      text = options.text;
      badgeTitle = title + ': ' + options.title;
    }

    chrome.browserAction.setTitle({ title: badgeTitle });
    chrome.browserAction.setBadgeText({ text: text });
    chrome.browserAction.setBadgeBackgroundColor({ color: color });
  };

  function removeBadge() {
    chrome.browserAction.setTitle({ title: '' });
    chrome.browserAction.setBadgeText({ text: '' });
  }
}

function MenuEntry(title, action) {
  this.id = null;

  this.show = function() {
    this.hide();

    if (title === null) {
      this.id = chrome.contextMenus.create({
        type: 'separator',
        contexts: ['browser_action']
      });
    } else {
      this.id = chrome.contextMenus.create({
        title: title,
        contexts: ['browser_action'],
        onclick: action
      });
    }
  };

  this.hide = function() {
    if (this.id === null) {
      return;
    }

    chrome.contextMenus.remove(this.id, function() { });
    this.id = null;
  };
}

MenuEntry.create = function(title, action) {
  var menu = new MenuEntry(title, action);
  menu.show();
};

function AudioObserver() {
}

AudioObserver.observe = function(timer, sourceFile) {
  timer.addListener('expire', function() {
    var audio = new Audio();
    audio.src = sourceFile;
    audio.play();
  });
};

function ContextMenuObserver() {
}

ContextMenuObserver.observe = function(controller, timer) {
  var stop = new MenuEntry('Stop', controller.stop);
  var pause = new MenuEntry('Pause', controller.pause);
  var resume = new MenuEntry('Resume', controller.resume);

  timer.addListener('start', function() {
    pause.show();
    stop.show();
    resume.hide();
  });

  timer.addListener('pause', function() {
    resume.show();
    stop.show();
    pause.hide();
  });

  timer.addListener('resume', function() {
    pause.show();
    stop.show();
    resume.hide();
  });

  timer.addListener('stop', function() {
    pause.hide();
    stop.hide();
    resume.hide();
  });

  timer.addListener('expire', function() {
    pause.hide();
    stop.hide();
    resume.hide();
  });
};

function Controller() {
  var self = this;
  var focusNext = true;
  var focusTimer;
  var breakTimer;
  var notificationId = null;

  this.startSession = function() {
    focusTimer.stop();
    breakTimer.stop();

    if (focusNext) {
      focusTimer.start();
    } else {
      breakTimer.start();
    }
  };

  this.browserAction = function() {
    if (focusTimer.state() === 'running') {
      focusTimer.pause();
    } else if (breakTimer.state() === 'running') {
      breakTimer.pause();
    } else if (focusTimer.state() === 'paused') {
      focusTimer.resume();
    } else if (breakTimer.state() === 'paused') {
      breakTimer.resume();
    } else {
      this.startSession();
    }
  };

  this.pause = function() {
    if (focusTimer.state() === 'running') {
      focusTimer.pause();
    } else if (breakTimer.state() === 'running') {
      breakTimer.pause();
    }
  };

  this.stop = function() {
    focusTimer.stop();
    breakTimer.stop();
  };

  this.resume = function() {
    if (focusTimer.state() === 'paused') {
      focusTimer.resume();
    } else if (breakTimer.state() === 'paused') {
      breakTimer.resume();
    }
  };

  this.startBreak = function() {
    focusTimer.stop();
    breakTimer.stop();
    breakTimer.start();
  };

  this.startFocus = function() {
    focusTimer.stop();
    breakTimer.stop();
    focusTimer.start();
  };

  this.focusNext = function() {
    return focusNext;
  };

  this.getSettings = function(callback) {
    chrome.storage.sync.get(function(result) {
      if (Object.keys(result).length == 0) {
        chrome.storage.sync.set(defaultSettings, function() {
          callback(defaultSettings);
        });
      } else {
        callback(result);
      }
    });
  };

  this.setSettings = function(settings, callback) {
    chrome.storage.sync.set(settings, function() {
      createTimers();
      callback();
    });
  };

  function showExpirePage(callback) {
    if (expirePageTabId !== null) {
      chrome.tabs.update(expirePageTabId, { active: true, highlighted: true }, callback);
    } else {
      chrome.tabs.create({ url: chrome.extension.getURL('expire/expire.html') }, function(tab) {
        expirePageTabId = tab.id;
        if (callback) {
          callback(tab);
        }
      });
    }
  }

  var expirePageTabId = null;
  chrome.tabs.onRemoved.addListener(function(tabId) {
    if (tabId === expirePageTabId) {
      expirePageTabId = null;
    }
  });

  function notify(title, message, buttonTitle) {
    var options = {
      type: 'basic',
      title: title,
      message: message,
      iconUrl: 'icons/128.png',
      isClickable: true,
      buttons: [{ title: buttonTitle, iconUrl: 'icons/start.png' }]
    };

    chrome.notifications.create('', options, function (id) {
      notificationId = id;
    });
  }

  chrome.notifications.onClicked.addListener(function() {
    showExpirePage(function(tab) {
      chrome.windows.update(tab.windowId, { focused: true });
    });
  });

  chrome.notifications.onButtonClicked.addListener(function(notificationId, buttonIndex) {
    self.focusNext() ? self.startFocus() : self.startBreak();
    chrome.notifications.clear(notificationId);
  });

  function createTimers() {
    self.getSettings(function(settings) {
      if (focusTimer) {
        focusTimer.stop();
      }

      focusTimer = createFocusTimer(settings);

      if (breakTimer) {
        breakTimer.stop();
      }

      breakTimer = createBreakTimer(settings);
    });
  }

  function createFocusTimer(settings) {
    var timer = new Timer(settings.focus.duration * 60, 60);
    BadgeObserver.observe(timer, 'Focus', '#990000');
    ContextMenuObserver.observe(self, timer);
    AudioObserver.observe(timer, settings.focus.sound);

    timer.addListener('expire', function() {
      focusNext = false;

      if (settings.focus.desktopNotification) {
        notify(
          'Take a break!',
          "Start your break when you're ready",
          'Start break now'
        );
      }

      if (settings.focus.newTabNotification) {
        showExpirePage();
      }
    });

    timer.addListener('start', function () {
      closeExtensionTabs();
      closeNotifications();
    });

    return timer;
  }

  function createBreakTimer(settings) {
    var timer = new Timer(settings.break.duration * 60, 60);
    BadgeObserver.observe(timer, 'Break', '#009900');
    ContextMenuObserver.observe(self, timer);
    AudioObserver.observe(timer, settings.break.sound);

    timer.addListener('expire', function() {
      focusNext = true;

      if (settings.break.desktopNotification) {
        notify(
          'Break finished',
          "Start your focus session when you're ready",
          'Start focusing now'
        );
      }

      if (settings.break.newTabNotification) {
        showExpirePage();
      }
    });

    timer.addListener('start', function () {
      closeExtensionTabs();
      closeNotifications();
    });

    return timer;
  }

  function closeExtensionTabs() {
    var id = chrome.runtime.id;
    var extensionUrl = 'chrome-extension://' + id;

    chrome.tabs.query({}, function(tabs) {
      var remove = [];
      for (var i = 0; i < tabs.length; ++i) {
        if (tabs[i].url.indexOf(extensionUrl) !== -1) {
          remove.push(tabs[i].id);
        }
      }

      chrome.tabs.remove(remove, function() { });
    });
  }

  function closeNotifications() {
    if (notificationId !== null) {
      chrome.notifications.clear(notificationId);
      notificationId = null;
    }
  }

  createTimers();
}

var controller = new Controller();

chrome.contextMenus.removeAll();

MenuEntry.create('Start focusing', controller.startFocus);
MenuEntry.create('Start break', controller.startBreak);
MenuEntry.create(null);

chrome.browserAction.onClicked.addListener(function() {
  controller.browserAction();
});

chrome.runtime.onMessage.addListener(function(request, sender, respond) {
  if (request.command === 'get-session') {
    if (controller.focusNext()) {
      controller.getSettings(function(settings) {
        respond({
          focusNext: true,
          title: 'Break finished',
          subtitle: "Start your " + settings.focus.duration + " minute focus session when you're ready",
          action: 'Start Focusing'
        });
      });
    } else {
      controller.getSettings(function(settings) {
        respond({
          focusNext: false,
          title: 'Take a break!',
          subtitle: "Start your " + settings.break.duration + " minute break when you're ready",
          action: 'Start Break'
        });
      });
    }
  } else if (request.command === 'start-session') {
    controller.startSession();
    respond({});
  } else if (request.command === 'get-sounds') {
    respond(sounds);
  } else if (request.command === 'get-settings') {
    controller.getSettings(respond);
  } else if (request.command === 'set-settings') {
    var newSettings = request.settings;
    var focusDuration = newSettings.focus.duration.trim();
    var breakDuration = newSettings.break.duration.trim();

    if (!focusDuration) {
      respond({ error: 'Focus duration is required.' });
      return true;
    } else if (!breakDuration) {
      respond({ error: 'Break duration is required.' });
      return true;
    }

    var focusParsed = +focusDuration;
    var breakParsed = +breakDuration;

    if (focusParsed <= 0 || isNaN(focusParsed)) {
      respond({ error: 'Focus duration must be a positive number.' });
      return true;
    } else if (breakParsed <= 0 || isNaN(breakParsed)) {
      respond({ error: 'Break duration must be a positive number.' });
      return true;
    }

    newSettings.focus.duration = focusParsed;
    newSettings.break.duration = breakParsed;

    controller.setSettings(newSettings, function() {
      respond({});
    });
  }

  return true;
});
<commit_msg>Fix bug where we tried to play a sound even if none was specified.<commit_after>var defaultSettings = {
  focus: {
    duration: 25,
    desktopNotification: true,
    newTabNotification: true,
    sound: null
  },
  break: {
    duration: 5,
    desktopNotification: true,
    newTabNotification: true,
    sound: null
  },
  version: 1
};

var sounds = [
  { name: 'Tone', file: 'tone.mp3' },
  { name: 'Electronic Chime', file: 'electronic-chime.mp3' },
  { name: 'Gong 1', file: 'gong-1.mp3' },
  { name: 'Gong 2', file: 'gong-2.mp3' },
  { name: 'Computer Magic', file: 'computer-magic.mp3' },
  { name: 'Fire Pager', file: 'fire-pager.mp3' },
  { name: 'Glass Ping', file: 'glass-ping.mp3' },
  { name: 'Music Box', file: 'music-box.mp3' },
  { name: 'Pin Dropping', file: 'pin-dropping.mp3' },
  { name: 'Robot Blip 1', file: 'robot-blip-1.mp3' },
  { name: 'Robot Blip 2', file: 'robot-blip-2.mp3' },
  { name: 'Ship Bell', file: 'ship-bell.mp3' },
  { name: 'Train Horn', file: 'train-horn.mp3' },
  { name: 'Bike Horn', file: 'bike-horn.mp3' },
  { name: 'Bell Ring', file: 'bell-ring.mp3' },
  { name: 'Reception Bell', file: 'reception-bell.mp3' },
  { name: 'Toaster Oven', file: 'toaster-oven.mp3' },
  { name: 'Battle Horn', file: 'battle-horn.mp3' },
  { name: 'Ding', file: 'ding.mp3' },
  { name: 'Dong', file: 'dong.mp3' },
  { name: 'Ding Dong', file: 'ding-dong.mp3' },
  { name: 'Din Ding', file: 'din-ding.mp3' }
];

for (var i = 0; i < sounds.length; ++i) {
  sounds[i].file = 'chrome-extension://' + chrome.runtime.id + '/audio/' + sounds[i].file;
}

function Timer(durationSec, tickSec) {
  var self = this;
  var state = 'stopped';

  this.tickInterval = null;
  this.expireTimeout = null;

  this.periodStartTime = null;
  this.remainingSec = null;

  this.start = function() {
    if (state !== 'stopped') {
      return;
    }

    this.expireTimeout = createExpireTimeout(durationSec);
    this.tickInterval = createTickInterval(tickSec);

    this.remainingSec = durationSec;

    state = 'running';
    this.periodStartTime = Date.now();
    this.emitEvent('start', [{
      elapsed: 0,
      remaining: this.remainingSec
    }]);
  };

  this.stop = function() {
    if (state === 'stopped') {
      return;
    }

    clearInterval(this.tickInterval);
    clearTimeout(this.expireTimeout);

    this.tickInterval = null;
    this.expireTimeout = null;
    this.periodStartTime = null;
    this.remainingSec = null;

    state = 'stopped';
    this.emitEvent('stop', [{}]);
  };

  this.pause = function() {
    if (state !== 'running') {
      return;
    }

    clearInterval(this.tickInterval);
    clearTimeout(this.expireTimeout);

    var periodSec = (Date.now() - this.periodStartTime) / 1000;
    this.remainingSec -= periodSec;

    state = 'paused';
    this.periodStartTime = null;
    this.emitEvent('pause', [{
      elapsed: durationSec - this.remainingSec,
      remaining: this.remainingSec
    }]);
  };

  this.resume = function() {
    if (state !== 'paused') {
      return;
    }

    this.expireTimeout = createExpireTimeout(this.remainingSec);
    this.tickInterval = createTickInterval(tickSec);

    state = 'running';
    this.periodStartTime = Date.now();
    this.emitEvent('resume', [{
      elapsed: durationSec - this.remainingSec,
      remaining: this.remainingSec
    }]);
  };

  this.reset = function() {
    this.stop();
    this.start();
  };

  this.state = function() {
    return state;
  };

  function createExpireTimeout(seconds) {
    return setTimeout(function() {
      clearInterval(self.tickInterval);
      clearTimeout(self.expireTimeout);

      self.tickInterval = null;
      self.expireTimeout = null;
      self.periodStartTime = null;
      self.remainingSec = null;

      state = 'stopped';
      self.emitEvent('expire', [{
        elapsed: durationSec,
        remaining: 0
      }]);
    }, seconds * 1000);
  }

  function createTickInterval(seconds) {
    return setInterval(function() {
      var periodSec = (Date.now() - self.periodStartTime) / 1000;
      var remainingSec = self.remainingSec - periodSec;

      self.emitEvent('tick', [{
        elapsed: durationSec - remainingSec,
        remaining: remainingSec
      }]);
    }, seconds * 1000);
  }
}

Timer.prototype = Object.create(EventEmitter.prototype);

function BadgeObserver() {
}

BadgeObserver.observe = function(timer, title, color) {
  timer.addListener('start', function(state) {
    updateBadge({ minutes: Math.round(state.remaining / 60) });
  });

  timer.addListener('tick', function(state) {
    updateBadge({ minutes: Math.round(state.remaining / 60) });
  });

  timer.addListener('stop', function() {
    removeBadge();
  });

  timer.addListener('pause', function() {
    updateBadge({ text: '—', title: 'Paused' });
  });

  timer.addListener('resume', function(state) {
    updateBadge({ minutes: Math.round(state.remaining / 60) });
  });

  timer.addListener('expire', function() {
    removeBadge();
  });

  function updateBadge(options) {
    var minutes = options.minutes;
    if (minutes != null) {
      text = ((minutes == 0) ? '<1' : minutes)  + 'm';
      badgeTitle = title + ': ' + minutes + 'm remaining.';
    } else {
      text = options.text;
      badgeTitle = title + ': ' + options.title;
    }

    chrome.browserAction.setTitle({ title: badgeTitle });
    chrome.browserAction.setBadgeText({ text: text });
    chrome.browserAction.setBadgeBackgroundColor({ color: color });
  };

  function removeBadge() {
    chrome.browserAction.setTitle({ title: '' });
    chrome.browserAction.setBadgeText({ text: '' });
  }
}

function MenuEntry(title, action) {
  this.id = null;

  this.show = function() {
    this.hide();

    if (title === null) {
      this.id = chrome.contextMenus.create({
        type: 'separator',
        contexts: ['browser_action']
      });
    } else {
      this.id = chrome.contextMenus.create({
        title: title,
        contexts: ['browser_action'],
        onclick: action
      });
    }
  };

  this.hide = function() {
    if (this.id === null) {
      return;
    }

    chrome.contextMenus.remove(this.id, function() { });
    this.id = null;
  };
}

MenuEntry.create = function(title, action) {
  var menu = new MenuEntry(title, action);
  menu.show();
};

function AudioObserver() {
}

AudioObserver.observe = function(timer, sourceFile) {
  if (sourceFile) {
    timer.addListener('expire', function() {
      var audio = new Audio();
      audio.src = sourceFile;
      audio.play();
    });
  }
};

function ContextMenuObserver() {
}

ContextMenuObserver.observe = function(controller, timer) {
  var stop = new MenuEntry('Stop', controller.stop);
  var pause = new MenuEntry('Pause', controller.pause);
  var resume = new MenuEntry('Resume', controller.resume);

  timer.addListener('start', function() {
    pause.show();
    stop.show();
    resume.hide();
  });

  timer.addListener('pause', function() {
    resume.show();
    stop.show();
    pause.hide();
  });

  timer.addListener('resume', function() {
    pause.show();
    stop.show();
    resume.hide();
  });

  timer.addListener('stop', function() {
    pause.hide();
    stop.hide();
    resume.hide();
  });

  timer.addListener('expire', function() {
    pause.hide();
    stop.hide();
    resume.hide();
  });
};

function Controller() {
  var self = this;
  var focusNext = true;
  var focusTimer;
  var breakTimer;
  var notificationId = null;

  this.startSession = function() {
    focusTimer.stop();
    breakTimer.stop();

    if (focusNext) {
      focusTimer.start();
    } else {
      breakTimer.start();
    }
  };

  this.browserAction = function() {
    if (focusTimer.state() === 'running') {
      focusTimer.pause();
    } else if (breakTimer.state() === 'running') {
      breakTimer.pause();
    } else if (focusTimer.state() === 'paused') {
      focusTimer.resume();
    } else if (breakTimer.state() === 'paused') {
      breakTimer.resume();
    } else {
      this.startSession();
    }
  };

  this.pause = function() {
    if (focusTimer.state() === 'running') {
      focusTimer.pause();
    } else if (breakTimer.state() === 'running') {
      breakTimer.pause();
    }
  };

  this.stop = function() {
    focusTimer.stop();
    breakTimer.stop();
  };

  this.resume = function() {
    if (focusTimer.state() === 'paused') {
      focusTimer.resume();
    } else if (breakTimer.state() === 'paused') {
      breakTimer.resume();
    }
  };

  this.startBreak = function() {
    focusTimer.stop();
    breakTimer.stop();
    breakTimer.start();
  };

  this.startFocus = function() {
    focusTimer.stop();
    breakTimer.stop();
    focusTimer.start();
  };

  this.focusNext = function() {
    return focusNext;
  };

  this.getSettings = function(callback) {
    chrome.storage.sync.get(function(result) {
      if (Object.keys(result).length == 0) {
        chrome.storage.sync.set(defaultSettings, function() {
          callback(defaultSettings);
        });
      } else {
        callback(result);
      }
    });
  };

  this.setSettings = function(settings, callback) {
    chrome.storage.sync.set(settings, function() {
      createTimers();
      callback();
    });
  };

  function showExpirePage(callback) {
    if (expirePageTabId !== null) {
      chrome.tabs.update(expirePageTabId, { active: true, highlighted: true }, callback);
    } else {
      chrome.tabs.create({ url: chrome.extension.getURL('expire/expire.html') }, function(tab) {
        expirePageTabId = tab.id;
        if (callback) {
          callback(tab);
        }
      });
    }
  }

  var expirePageTabId = null;
  chrome.tabs.onRemoved.addListener(function(tabId) {
    if (tabId === expirePageTabId) {
      expirePageTabId = null;
    }
  });

  function notify(title, message, buttonTitle) {
    var options = {
      type: 'basic',
      title: title,
      message: message,
      iconUrl: 'icons/128.png',
      isClickable: true,
      buttons: [{ title: buttonTitle, iconUrl: 'icons/start.png' }]
    };

    chrome.notifications.create('', options, function (id) {
      notificationId = id;
    });
  }

  chrome.notifications.onClicked.addListener(function() {
    showExpirePage(function(tab) {
      chrome.windows.update(tab.windowId, { focused: true });
    });
  });

  chrome.notifications.onButtonClicked.addListener(function(notificationId, buttonIndex) {
    self.focusNext() ? self.startFocus() : self.startBreak();
    chrome.notifications.clear(notificationId);
  });

  function createTimers() {
    self.getSettings(function(settings) {
      if (focusTimer) {
        focusTimer.stop();
      }

      focusTimer = createFocusTimer(settings);

      if (breakTimer) {
        breakTimer.stop();
      }

      breakTimer = createBreakTimer(settings);
    });
  }

  function createFocusTimer(settings) {
    var timer = new Timer(settings.focus.duration * 60, 60);
    BadgeObserver.observe(timer, 'Focus', '#990000');
    ContextMenuObserver.observe(self, timer);
    AudioObserver.observe(timer, settings.focus.sound);

    timer.addListener('expire', function() {
      focusNext = false;

      if (settings.focus.desktopNotification) {
        notify(
          'Take a break!',
          "Start your break when you're ready",
          'Start break now'
        );
      }

      if (settings.focus.newTabNotification) {
        showExpirePage();
      }
    });

    timer.addListener('start', function () {
      closeExtensionTabs();
      closeNotifications();
    });

    return timer;
  }

  function createBreakTimer(settings) {
    var timer = new Timer(settings.break.duration * 60, 60);
    BadgeObserver.observe(timer, 'Break', '#009900');
    ContextMenuObserver.observe(self, timer);
    AudioObserver.observe(timer, settings.break.sound);

    timer.addListener('expire', function() {
      focusNext = true;

      if (settings.break.desktopNotification) {
        notify(
          'Break finished',
          "Start your focus session when you're ready",
          'Start focusing now'
        );
      }

      if (settings.break.newTabNotification) {
        showExpirePage();
      }
    });

    timer.addListener('start', function () {
      closeExtensionTabs();
      closeNotifications();
    });

    return timer;
  }

  function closeExtensionTabs() {
    var id = chrome.runtime.id;
    var extensionUrl = 'chrome-extension://' + id;

    chrome.tabs.query({}, function(tabs) {
      var remove = [];
      for (var i = 0; i < tabs.length; ++i) {
        if (tabs[i].url.indexOf(extensionUrl) !== -1) {
          remove.push(tabs[i].id);
        }
      }

      chrome.tabs.remove(remove, function() { });
    });
  }

  function closeNotifications() {
    if (notificationId !== null) {
      chrome.notifications.clear(notificationId);
      notificationId = null;
    }
  }

  createTimers();
}

var controller = new Controller();

chrome.contextMenus.removeAll();

MenuEntry.create('Start focusing', controller.startFocus);
MenuEntry.create('Start break', controller.startBreak);
MenuEntry.create(null);

chrome.browserAction.onClicked.addListener(function() {
  controller.browserAction();
});

chrome.runtime.onMessage.addListener(function(request, sender, respond) {
  if (request.command === 'get-session') {
    if (controller.focusNext()) {
      controller.getSettings(function(settings) {
        respond({
          focusNext: true,
          title: 'Break finished',
          subtitle: "Start your " + settings.focus.duration + " minute focus session when you're ready",
          action: 'Start Focusing'
        });
      });
    } else {
      controller.getSettings(function(settings) {
        respond({
          focusNext: false,
          title: 'Take a break!',
          subtitle: "Start your " + settings.break.duration + " minute break when you're ready",
          action: 'Start Break'
        });
      });
    }
  } else if (request.command === 'start-session') {
    controller.startSession();
    respond({});
  } else if (request.command === 'get-sounds') {
    respond(sounds);
  } else if (request.command === 'get-settings') {
    controller.getSettings(respond);
  } else if (request.command === 'set-settings') {
    var newSettings = request.settings;
    var focusDuration = newSettings.focus.duration.trim();
    var breakDuration = newSettings.break.duration.trim();

    if (!focusDuration) {
      respond({ error: 'Focus duration is required.' });
      return true;
    } else if (!breakDuration) {
      respond({ error: 'Break duration is required.' });
      return true;
    }

    var focusParsed = +focusDuration;
    var breakParsed = +breakDuration;

    if (focusParsed <= 0 || isNaN(focusParsed)) {
      respond({ error: 'Focus duration must be a positive number.' });
      return true;
    } else if (breakParsed <= 0 || isNaN(breakParsed)) {
      respond({ error: 'Break duration must be a positive number.' });
      return true;
    }

    newSettings.focus.duration = focusParsed;
    newSettings.break.duration = breakParsed;

    controller.setSettings(newSettings, function() {
      respond({});
    });
  }

  return true;
});
<|endoftext|>