<commit_before>(function() { // wrapping script in immediately invoked function expression (IIFE, 'iffy') to contain scope
    'use strict'; // forcing good behavior

    var Chart,
        MovingBlockChart,
        app,
        extendPrototype,

        SQUARE_WIDTH = 10, // symbolic constants all caps following convention
        SQUARE_SPACER = 2,
        ROWS = 12,
        DATA_FILE = 'https://raw.githubusercontent.com/codefordc/housing-insights/dev/scripts/small_data/PresCat_Export_20160401/Project.csv';

    extendPrototype = function(destinationPrototype, obj){ // using this function for inheritance.
                                                           // extend a constructor's prototype with
                                                           // the keys/values in obj.
        for(var i in obj){
            destinationPrototype[i] = obj[i];
        }
    }

    Chart = function(el,field,sortField,asc) {

        this.initialSetup(el,field,sortField,asc); //on chart creation, we run setup function; setup function adds listeners for update chart behavior (e.g. resorting, etc.).

    };

    Chart.prototype = {
        initialSetup: function(el,field,sortField,asc) { // renamed from 'setup' to avoid clashes with
                                                         // child objects of Chart, which also use 'setup'. Can
                                                         // perhaps rename to 'setup' after changing the
                                                         // inheritance code.
            var chart = this // stashing `this` (Chart) into variable for access later within narrower scopes (like the scale functions)

            app.data.sort(function(a, b) { // sorting data array with JS prototype sort function
                if (asc) return a[sortField] - b[sortField]; // if asc parameter in Chart constructor call is true
                return b[sortField] - a[sortField]; // if not
              });

            chart.svg = d3.select(el) // select elem (div#chart-0)
                .append('svg')        // append svg element
                .attr('width', 100 + '%')   // d3 v4 requires setting attributes one at a time. no native support for setting attr or style with objects as in v3. this library would make it possible: mini library D3-selection-mult
                .attr('height', 200); // SVG object dimensions are hard-coded now, but it may be
                                      // useful to set these as, say, a parameter in constructors that
                                      // inherit from Chart.

            chart.minValue = d3.min(app.data, function(d) { // d3.min iterates through datums (d) and return the smallest
                return d[field]
            });

            chart.maxValue = d3.max(app.data, function(d) { // d3.max iterates through datums (d) and return the largest
                return d[field]
            });

            chart.scale = d3.scaleLinear().domain([chart.minValue, chart.maxValue]).range([0.1, 1]); // in v3 this was d3.scale.linear(). maps the values in the data (domain) to the output values you want (range)

        } // end setup()

    }; // end prototype

    MovingBlockChart = function(el, field, sortField, asc) {
        Chart.call(this, el, field, sortField, asc); // First step of inheriting from Chart
        this.setup(el, field, sortField, asc);
    }

    MovingBlockChart.prototype = Object.create(Chart.prototype); // Second step of inheriting from Chart

    extendPrototype(MovingBlockChart.prototype, { // Final step of inheriting from Chart.
        setup: function(el, field, sortField, asc){
            var chart = this,
                tool_tip = d3.tip()
                    .attr("class", "d3-tip")
                    .offset([-8, 0])
                    .direction('e')
                    .html(function(d){
                        return '' + d.Proj_Name + '
' + d.Proj_Addre + '

Total units: ' + d.Proj_Units_Tot;  //here the text of the tooltip is hard coded in but we'll need a human-readable field in the data to provide that text
                      });

            chart.svg.selectAll('rect') // selects svg element `rect`s whether they exist yet or not
                .data(app.data) // binds to data
                .enter() // for all `rect`s that don't yet exist
                .append('rect') // create the `rect`
                .attr('width', SQUARE_WIDTH)
                .attr('height', SQUARE_WIDTH)
                .attr('fill', '#325d88') // note svg-specific property names eg fill, not background
                .attr('fill-opacity', 0.1)
                .on('mouseover', tool_tip.show) // .show is defined in links d3-tip library
                .on('mouseout', tool_tip.hide)  // .hide is defined in links d3-tip library
                .call(tool_tip);

            chart.positionBlocks(0);
            chart.changeOpacity(field);

            chart.buttonRand = d3.select(el) // creates the button to randomly resort and appends it in the el (div#chart-0)
                .append('button')
                .text('Resort randomly')
                .on('click', function(){
                    chart.resort('random');
                });

            chart.buttonZip = d3.select(el) // creates the button to randomly resort and appends it in the el (div#chart-0)
                .append('button')
                .text('Resort by zip')
                .on('click', function(){
                    chart.resort('zip');
                });

            chart.buttonUnit = d3.select(el) // creates the button to randomly resort and appends it in the el (div#chart-0)
                .append('button')
                .text('Resort by unit count')
                .on('click', function(){
                    chart.resort('unit');

                });

            chart.slider = d3.select(el)
                .append('input')
                .attr('type', 'range')
                .attr('id', 'inputSlider')
                .attr('step', '.01')      // .01 is kind of a magic number and probably should be made responsive to the data
                .style('margin-top', '1em') // these two style elements are pretty arbitrary
                .style('width', '40%');

            d3.select('#inputSlider')
                .attr('min', chart.minValue)
                .attr('max', chart.maxValue);

            chart.slider.on('mousedown', function(){ // this anonymous function wraps
                     chart.sliderAction(field);  // another function in order to pass a
                }, false);                      // parameter despite being an event listener

        },  // end setup()

        positionBlocks: function(duration){

            this.svg.selectAll('rect')
                .transition().duration(duration)
                .attr('y', function(d, i) {
                    return (i * SQUARE_WIDTH + SQUARE_SPACER * i) - Math.floor(i / ROWS) * (SQUARE_WIDTH + SQUARE_SPACER) * ROWS;
                }) // horizontal placement is function of the index and the number of ROWS desired
                .attr('x', function(d, i) {
                    return Math.floor(i / ROWS) * SQUARE_WIDTH + (Math.floor(i / ROWS) * SQUARE_SPACER);
                }) // vertical placement function of index and number of columns. Math.floor rounds down to nearest integer
        }, // end positionBlocks

        changeOpacity: function(field){
          var chart = this;
          chart.svg.selectAll('rect')

          .transition().delay(250).duration(750)
          .attr('fill-opacity', function(d) { // opacity is a function of the value
              return chart.scale(d[field]); // takes the field value and maps it according to the scaling function defined above
          })
        },

        resort: function(value){

            /* for demo purposes only. production tool will have many events that trigger update and
             * (potentially) resort functions. probably best and easiest to eventually  use an observer pattern
             * or pub/sub (publish/subscribe) (same thing?) pattern to connect user- ot client-inititiated events (including resize)
             * with update functions
             */

          switch(value){

                case 'random':
                this.svg.selectAll('rect').sort(function(a,b){
                    return d3.ascending(Math.random(), Math.random());
                });
                break;

                case 'zip':
                this.svg.selectAll('rect').sort(function(a,b){
                    return d3.ascending(a.Proj_Zip, b.Proj_Zip);
                });
                break;

                case 'unit':
                this.svg.selectAll('rect').sort(function(a,b){
                    return d3.ascending(a.Proj_Units_Tot, b.Proj_Units_Tot);
                });
                break;
            }
            this.positionBlocks(500);

        }, // end resort()

        sliderAction: function(field){
                        document.querySelector('body').addEventListener('mousemove', checkColors, false);

                        function checkColors() {
                            let value = document.querySelector('#inputSlider').value;
                            d3.selectAll('rect').attr('fill', function(d){
                              if(d[field] < value){
                                return 'gray';
                              } else {
                                return 'blue';
                              }
                            });

                            d3.select('body').on('mouseup', function(){
                                document.querySelector('body').removeEventListener('mousemove', checkColors);
                            }, false);
                        };
        }       // end sliderAction()

    }; // end prototype

    });

    app = {
        data: [],
        initialize: function(json) {
            json.forEach(function(obj) { //iterate over each object of the data array
                for (var key in obj) { // iterate over each property of the object
                    if (obj.hasOwnProperty(key)) {
                        obj[key] = isNaN(+obj[key]) ? obj[key] : +obj[key]; // + operator converts to number unless result would be NaN
                    }
                }
            });
            app.data = json;
            console.log(app.data);
                        //params:(element    , field to visualize            ,  field to sort by         , ascending [boolean])
            app.blockChart = new MovingBlockChart('#chart-0', 'Proj_Units_Tot', 'Proj_Zip', false);

        }
    }

    d3.csv(DATA_FILE, app.initialize); // ALL STARTS HERE

})(); // end IIFE<commit_msg>more merging<commit_after>(function() { // wrapping script in immediately invoked function expression (IIFE, 'iffy') to contain scope
    'use strict'; // forcing good behavior

    var Chart,
        MovingBlockChart,
        app,
        extendPrototype,

        SQUARE_WIDTH = 10, // symbolic constants all caps following convention
        SQUARE_SPACER = 2,
        ROWS = 12,
        DATA_FILE = 'https://raw.githubusercontent.com/codefordc/housing-insights/dev/scripts/small_data/PresCat_Export_20160401/Project.csv';

    extendPrototype = function(destinationPrototype, obj){ // using this function for inheritance.
                                                           // extend a constructor's prototype with
                                                           // the keys/values in obj.
        for(var i in obj){
            destinationPrototype[i] = obj[i];
        }
    }

    Chart = function(el,field,sortField,asc) {

        this.initialSetup(el,field,sortField,asc); //on chart creation, we run setup function; setup function adds listeners for update chart behavior (e.g. resorting, etc.).

    };

    Chart.prototype = {
        initialSetup: function(el,field,sortField,asc) { // renamed from 'setup' to avoid clashes with
                                                         // child objects of Chart, which also use 'setup'. Can
                                                         // perhaps rename to 'setup' after changing the
                                                         // inheritance code.
            var chart = this // stashing `this` (Chart) into variable for access later within narrower scopes (like the scale functions)

            app.data.sort(function(a, b) { // sorting data array with JS prototype sort function
                if (asc) return a[sortField] - b[sortField]; // if asc parameter in Chart constructor call is true
                return b[sortField] - a[sortField]; // if not
              });

            chart.svg = d3.select(el) // select elem (div#chart-0)
                .append('svg')        // append svg element
                .attr('width', 100 + '%')   // d3 v4 requires setting attributes one at a time. no native support for setting attr or style with objects as in v3. this library would make it possible: mini library D3-selection-mult
                .attr('height', 200); // SVG object dimensions are hard-coded now, but it may be
                                      // useful to set these as, say, a parameter in constructors that
                                      // inherit from Chart.

            chart.minValue = d3.min(app.data, function(d) { // d3.min iterates through datums (d) and return the smallest
                return d[field]
            });

            chart.maxValue = d3.max(app.data, function(d) { // d3.max iterates through datums (d) and return the largest
                return d[field]
            });

            chart.scale = d3.scaleLinear().domain([chart.minValue, chart.maxValue]).range([0.1, 1]); // in v3 this was d3.scale.linear(). maps the values in the data (domain) to the output values you want (range)

        } // end setup()

    }; // end prototype

    MovingBlockChart = function(el, field, sortField, asc) {
        Chart.call(this, el, field, sortField, asc); // First step of inheriting from Chart
        this.setup(el, field, sortField, asc);
    }

    MovingBlockChart.prototype = Object.create(Chart.prototype); // Second step of inheriting from Chart

    extendPrototype(MovingBlockChart.prototype, { // Final step of inheriting from Chart.
        setup: function(el, field, sortField, asc){
            var chart = this,
                tool_tip = d3.tip()
                    .attr("class", "d3-tip")
                    .offset([-8, 0])
                    .direction('e')
                    .html(function(d){
                        return '' + d.Proj_Name + '
' + d.Proj_Addre + '

Total units: ' + d.Proj_Units_Tot;  //here the text of the tooltip is hard coded in but we'll need a human-readable field in the data to provide that text
                      });

            chart.svg.selectAll('rect') // selects svg element `rect`s whether they exist yet or not
                .data(app.data) // binds to data
                .enter() // for all `rect`s that don't yet exist
                .append('rect') // create the `rect`
                .attr('width', SQUARE_WIDTH)
                .attr('height', SQUARE_WIDTH)
                .attr('fill', '#325d88') // note svg-specific property names eg fill, not background
                .attr('fill-opacity', 0.1)
                .on('mouseover', tool_tip.show) // .show is defined in links d3-tip library
                .on('mouseout', tool_tip.hide)  // .hide is defined in links d3-tip library
                .call(tool_tip);

            chart.positionBlocks(0);
            chart.changeOpacity(field);

            chart.buttonRand = d3.select(el) // creates the button to randomly resort and appends it in the el (div#chart-0)
                .append('button')
                .text('Resort randomly')
                .on('click', function(){
                    chart.resort('random');
                });

            chart.buttonZip = d3.select(el) // creates the button to randomly resort and appends it in the el (div#chart-0)
                .append('button')
                .text('Resort by zip')
                .on('click', function(){
                    chart.resort('zip');
                });

            chart.buttonUnit = d3.select(el) // creates the button to randomly resort and appends it in the el (div#chart-0)
                .append('button')
                .text('Resort by unit count')
                .on('click', function(){
                    chart.resort('unit');

                });

            chart.slider = d3.select(el)
                .append('input')
                .attr('type', 'range')
                .attr('id', 'inputSlider')
                .attr('step', '.01')      // .01 is kind of a magic number and probably should be made responsive to the data
                .style('margin-top', '1em') // these two style elements are pretty arbitrary
                .style('width', '40%');

            d3.select('#inputSlider')
                .attr('min', chart.minValue)
                .attr('max', chart.maxValue);

            chart.slider.on('mousedown', function(){ // this anonymous function wraps
                     chart.sliderAction(field);  // another function in order to pass a
                }, false);                      // parameter despite being an event listener

        },  // end setup()

        positionBlocks: function(duration){

            this.svg.selectAll('rect')
                .transition().duration(duration)
                .attr('y', function(d, i) {
                    return (i * SQUARE_WIDTH + SQUARE_SPACER * i) - Math.floor(i / ROWS) * (SQUARE_WIDTH + SQUARE_SPACER) * ROWS;
                }) // horizontal placement is function of the index and the number of ROWS desired
                .attr('x', function(d, i) {
                    return Math.floor(i / ROWS) * SQUARE_WIDTH + (Math.floor(i / ROWS) * SQUARE_SPACER);
                }) // vertical placement function of index and number of columns. Math.floor rounds down to nearest integer
        }, // end positionBlocks

        changeOpacity: function(field){
          var chart = this;
          chart.svg.selectAll('rect')

          .transition().delay(250).duration(750)
          .attr('fill-opacity', function(d) { // opacity is a function of the value
              return chart.scale(d[field]); // takes the field value and maps it according to the scaling function defined above
          })
        },

        resort: function(value){

            /* for demo purposes only. production tool will have many events that trigger update and
             * (potentially) resort functions. probably best and easiest to eventually  use an observer pattern
             * or pub/sub (publish/subscribe) (same thing?) pattern to connect user- ot client-inititiated events (including resize)
             * with update functions
             */

          switch(value){

                case 'random':
                this.svg.selectAll('rect').sort(function(a,b){
                    return d3.ascending(Math.random(), Math.random());
                });
                break;

                case 'zip':
                this.svg.selectAll('rect').sort(function(a,b){
                    return d3.ascending(a.Proj_Zip, b.Proj_Zip);
                });
                break;

                case 'unit':
                this.svg.selectAll('rect').sort(function(a,b){
                    return d3.ascending(a.Proj_Units_Tot, b.Proj_Units_Tot);
                });
                break;
            }
            this.positionBlocks(500);

        }, // end resort()

        sliderAction: function(field){
                        document.querySelector('body').addEventListener('mousemove', checkColors, false);

                        function checkColors() {
                            let value = document.querySelector('#inputSlider').value;
                            d3.selectAll('rect').attr('fill', function(d){
                              if(d[field] < value){
                                return 'gray';
                              } else {
                                return 'blue';
                              }
                            });

                            d3.select('body').on('mouseup', function(){
                                document.querySelector('body').removeEventListener('mousemove', checkColors);
                            }, false);
                        };
        }       // end sliderAction()

    }); // end prototype

    app = {
        data: [],
        initialize: function(json) {
            json.forEach(function(obj) { //iterate over each object of the data array
                for (var key in obj) { // iterate over each property of the object
                    if (obj.hasOwnProperty(key)) {
                        obj[key] = isNaN(+obj[key]) ? obj[key] : +obj[key]; // + operator converts to number unless result would be NaN
                    }
                }
            });
            app.data = json;
            console.log(app.data);
                        //params:(element    , field to visualize            ,  field to sort by         , ascending [boolean])
            app.blockChart = new MovingBlockChart('#chart-0', 'Proj_Units_Tot', 'Proj_Zip', false);

        }
    }

    d3.csv(DATA_FILE, app.initialize); // ALL STARTS HERE

})(); // end IIFE<|endoftext|>


<commit_before>//var socket = require('socket.io-client')('http://isaacparker.co:3030');
//var socket = require('socket.io-client')('http://192.168.86.38:8300');
var socket = require('socket.io-client')('http://heylex.us:8300');
var rpio = require('rpio');

// TODO: Pull this from somewhere
var SERVICE_MODE = false;

const PIN_REMOTE_START = 12;
const PIN_UNLOCK = 16;
const PIN_LOCK = 18;
const PIN_TRUNK = 22;
const PIN_PARKING_LIGHTS = 40;
const PIN_HORN = 38;

rpio.open(PIN_REMOTE_START, rpio.OUTPUT); // RS Trigger
rpio.open(PIN_UNLOCK, rpio.OUTPUT); // Unlock
rpio.open(PIN_LOCK, rpio.OUTPUT); // Lock
rpio.open(PIN_TRUNK, rpio.OUTPUT); // Trunk
rpio.open(PIN_PARKING_LIGHTS, rpio.OUTPUT); // Parking lights
rpio.open(PIN_HORN, rpio.OUTPUT); // Horn

socket.on('connect', function () {
	console.log("Connected");
});

socket.on('disconnect', function () {
	console.log("Disconnected");
});

function confirmAction(action, eventId) {
	socket.emit('lex-confirm', {
		action: action,
		eventId: eventId
	});
}

function returnError(requestedAction, reason) {
	socket.emit("lex-error", {
		"requestedAction": requestedAction,
		"reason": reason
	});
}

function pulsePin(pin, duration) {
	var pulseDuration = 500;
	if (typeof (duration) !== 'undefined') {
		pulseDuration = duration;
	}
	rpio.write(pin, rpio.HIGH);
	rpio.msleep(pulseDuration);
	rpio.write(pin, rpio.LOW);
}

var commandHistory = {};

socket.on('lex-meta-request', function incoming(metaRequest) {
	switch(metaRequest) {
		case "ping":
		socket.emit("lext-meta-response", {
			type: "ping",
			time: new Date()
		});
		break;
	}
});

socket.on('lex-command', function incoming(actionMessage) {
	console.log('GPIO Command: %s', actionMessage);

	commandHistory[actionMessage] ? commandHistory[actionMessage] += 1 : commandHistory[actionMessage] = 1;

	if (actionMessage === "remote-start:engine:on" || actionMessage === "remote-start:engine:off") {
		if (SERVICE_MODE) {
			returnError(actionMessage, 'service-mode-enabled')
			return
		}
		pulsePin(PIN_PARKING_LIGHTS);
		pulsePin(PIN_REMOTE_START);
		confirmAction(actionMessage, commandHistory[actionMessage]);
	}

	if (actionMessage === "remote-start:fast-honk") {
		const honkDuration = 5;

		const honkCount = 30;

		var isOn = false;
		var honks = 0;
		var fastHonkInterval = setInterval(function() {
			if(isOn) {
				rpio.write(PIN_HORN, rpio.LOW);
				isOn = false;
				if(honks > honkCount) {
					clearInterval(fastHonkInterval);
				}
			} else {
				rpio.write(PIN_HORN, rpio.HIGH);
				isOn = true;
				honks++;
			}

		}, honkDuration);

		// Ensure we don't accidentally leave the horn on
		rpio.write(PIN_HORN, rpio.LOW);

		confirmAction(actionMessage, commandHistory[actionMessage]);
	}

	if (actionMessage === "remote-start:security:unlock") {
		if (SERVICE_MODE) {
			returnError(actionMessage, 'service-mode-enabled')
			return
		}
		pulsePin(PIN_UNLOCK);
		pulsePin(PIN_PARKING_LIGHTS);
		confirmAction(actionMessage, commandHistory[actionMessage]);
	}

	if (actionMessage === "remote-start:security:lock") {
		if (SERVICE_MODE) {
			returnError(actionMessage, 'service-mode-enabled')
			return
		}
		pulsePin(PIN_LOCK);
		pulsePin(PIN_PARKING_LIGHTS);
		confirmAction(actionMessage, commandHistory[actionMessage]);
	}

	if (actionMessage === "remote-start:security:trunk") {
		if (SERVICE_MODE) {
			returnError(actionMessage, 'service-mode-enabled')
			return
		}
		pulsePin(PIN_TRUNK);
		pulsePin(PIN_PARKING_LIGHTS);
		confirmAction(actionMessage, commandHistory[actionMessage]);
	}

});<commit_msg>Reverting changes<commit_after>//var socket = require('socket.io-client')('http://isaacparker.co:3030');
//var socket = require('socket.io-client')('http://192.168.86.38:8300');
var socket = require('socket.io-client')('http://heylex.us:8300');
var rpio = require('rpio');

// TODO: Pull this from somewhere
var SERVICE_MODE = false;

const PIN_REMOTE_START = 12;
const PIN_UNLOCK = 16;
const PIN_LOCK = 18;
const PIN_TRUNK = 22;
const PIN_PARKING_LIGHTS = 40;
const PIN_HORN = 38;

rpio.open(PIN_REMOTE_START, rpio.OUTPUT); // RS Trigger
rpio.open(PIN_UNLOCK, rpio.OUTPUT); // Unlock
rpio.open(PIN_LOCK, rpio.OUTPUT); // Lock
rpio.open(PIN_TRUNK, rpio.OUTPUT); // Trunk
rpio.open(PIN_PARKING_LIGHTS, rpio.OUTPUT); // Parking lights
rpio.open(PIN_HORN, rpio.OUTPUT); // Horn

socket.on('connect', function () {
	console.log("Connected");
});

socket.on('disconnect', function () {
	console.log("Disconnected");
});

function confirmAction(action, eventId) {
	socket.emit('lex-confirm', {
		action: action,
		eventId: eventId
	});
}

function returnError(requestedAction, reason) {
	socket.emit("lex-error", {
		"requestedAction": requestedAction,
		"reason": reason
	});
}

function pulsePin(pin, duration) {
	var pulseDuration = 500;
	if (typeof (duration) !== 'undefined') {
		pulseDuration = duration;
	}
	rpio.write(pin, rpio.HIGH);
	rpio.msleep(pulseDuration);
	rpio.write(pin, rpio.LOW);
}

var commandHistory = {};

socket.on('lex-meta-request', function incoming(metaRequest) {
	switch(metaRequest) {
		case "ping":
		socket.emit("lext-meta-response", {
			type: "ping",
			time: new Date()
		});
		break;
	}
});

socket.on('lex-command', function incoming(actionMessage) {
	console.log('GPIO Command: %s', actionMessage);

	commandHistory[actionMessage] ? commandHistory[actionMessage] += 1 : commandHistory[actionMessage] = 1;

	if (actionMessage === "remote-start:engine:on" || actionMessage === "remote-start:engine:off") {
		if (SERVICE_MODE) {
			returnError(actionMessage, 'service-mode-enabled')
			return
		}
		pulsePin(PIN_PARKING_LIGHTS);
		pulsePin(PIN_REMOTE_START);
		confirmAction(actionMessage, commandHistory[actionMessage]);
	}

	if (actionMessage === "remote-start:fast-honk") {
		const honkDuration = 35;

		const honkCount = 5;

		var isOn = false;
		var honks = 0;
		var fastHonkInterval = setInterval(function() {
			if(isOn) {
				rpio.write(PIN_HORN, rpio.LOW);
				isOn = false;
				if(honks > honkCount) {
					clearInterval(fastHonkInterval);
				}
			} else {
				rpio.write(PIN_HORN, rpio.HIGH);
				isOn = true;
				honks++;
			}

		}, honkDuration);

		// Ensure we don't accidentally leave the horn on
		rpio.write(PIN_HORN, rpio.LOW);

		confirmAction(actionMessage, commandHistory[actionMessage]);
	}

	if (actionMessage === "remote-start:security:unlock") {
		if (SERVICE_MODE) {
			returnError(actionMessage, 'service-mode-enabled')
			return
		}
		pulsePin(PIN_UNLOCK);
		pulsePin(PIN_PARKING_LIGHTS);
		confirmAction(actionMessage, commandHistory[actionMessage]);
	}

	if (actionMessage === "remote-start:security:lock") {
		if (SERVICE_MODE) {
			returnError(actionMessage, 'service-mode-enabled')
			return
		}
		pulsePin(PIN_LOCK);
		pulsePin(PIN_PARKING_LIGHTS);
		confirmAction(actionMessage, commandHistory[actionMessage]);
	}

	if (actionMessage === "remote-start:security:trunk") {
		if (SERVICE_MODE) {
			returnError(actionMessage, 'service-mode-enabled')
			return
		}
		pulsePin(PIN_TRUNK);
		pulsePin(PIN_PARKING_LIGHTS);
		confirmAction(actionMessage, commandHistory[actionMessage]);
	}

});<|endoftext|>


<commit_before>/*
 * Copyright 2015 Network New Technologies Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

require('bootstrap/less/bootstrap.less');

var React = require('react');

var Router = require('react-router')
    , RouteHandler = Router.RouteHandler
    , Route = Router.Route;

//var AjaxInterceptor = require('ajax-interceptor');
var AuthActionCreators = require('./actions/AuthActionCreators.js');
var AuthStore = require('./stores/AuthStore.js');
var AppConstants = require('./constants/AppConstants.js');
var axios = require('axios');

// Add a request interceptor
axios.interceptors.request.use(function (config) {
    config.headers = config.headers || {};
    var accessToken = AuthStore.getAccessToken();
    // TODO Do not put access token into header of refresh token post. In this case,
    // we don't need to remove the authorizationData before sending refresh token post.
    // chances are some other requests might be sent during the time slot and got login
    // is required error and forced to login page.
    // TODO I really don't like this checking. need to find another way? backend?
    if (accessToken) {
        if(config.data && config.data.name && config.data.name === 'refreshToken') {
            return config;
        }
        config.headers.Authorization = 'Bearer ' + accessToken;
    }
    return config;
}, function (error) {
    // Do something with request error
    return Promise.reject(error);
});

// Add a response interceptor
axios.interceptors.response.use(function (response) {
    // Do something with response data
    console.log('response interceptor', response);
    return response;
}, function (error) {
    // Do something with response error
    console.log('error response interceptor', error);
    if(error.status == 401) {
        console.log('401 error response')
        if(error.data.error == 'token_expired') {
            console.log('token expired');
            var savedConfig = error.config;
            // get accessToken from refreshToken
            var refreshToken = {
                category:'user',
                name:'refreshToken',
                readOnly:true,
                data : {
                    refreshToken: AuthStore.getRefreshToken(),
                    userId: AuthStore.getUserId(),
                    clientId: AppConstants.ClientId
                }
            };
            axios.post('http://example:8080/api/rs', refreshToken)
                .then(function (response) {
                    console.log('refresh token result', response.data);
                    var accessToken = response.data.accessToken;
                    AuthActionCreators.refresh(accessToken);
                    // now need to resend the savedConfig here.
                    savedConfig.headers = savedConfig.headers || {};
                    var accessToken = AuthStore.getAccessToken();
                    if(accessToken) {
                        savedConfig.headers.Authorization = 'Bearer ' + accessToken;
                    }
                    console.log('saveConfig before post', savedConfig);
                    axios.get('http://example:8080/api/rs', savedConfig.params)
                        .then(function (response) {
                            console.log('retry is OK', response.data);
                            Promise.resolve(response);
                        })
                        .catch(function(response) {
                            console.log('retry get error', response.data);
                            Promise.reject(response);
                        });
                })
                .catch(function(response) {
                    console.log('error in refresh token', response.data);
                });
        }

    } else if(error.status == 403) {
        console.log('403 error response');
        // 403 forbidden. The user is logged in but doesn't have permission for the request.
        // logout and redirect to login page.

    }
    return Promise.reject(error);
});

//var httpBuffer = [];

/**
 * init http hooks for oauth
 *
 */
/*
AjaxInterceptor.addRequestCallback(function(xhr) {
    console.debug("request callback",xhr);
    // get the access token from store and put it into the header.
    var accessToken = AuthStore.getAccessToken();
    if(accessToken) {
        xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
    }
});

AjaxInterceptor.addResponseCallback(function(xhr) {
    console.debug("response",xhr);
    // intercept token expire error and refresh token.
    //console.log('xhr.status', xhr.status);
    //console.log('xhr.requesturl', xhr.responseURL);
    //console.log('xhr.responseText', xhr.responseText);
    if(xhr.status === 401 && xhr.responseText === '{"error":"token_expired"}') {
        console.log('token expired, renewing...');
        httpBuffer.push(xhr);
        var refreshToken = {
            category:'user',
            name:'refreshToken',
            readOnly:true,
            data : {
                refreshToken: AuthStore.getRefreshToken(),
                userId: AuthStore.getUserId(),
                clientId: AppConstants.ClientId
            }
        };

        var refreshReq = new XMLHttpRequest();
        refreshReq.onreadystatechange = function () {
            if(refreshReq.readyState == 4 && refreshReq.status == 200) {
                console.log('refreshToken', refreshReq.responseText);
                var jsonPayload = JSON.parse(refreshReq.responseText);
                AuthActionCreators.refresh(jsonPayload.accessToken);
            }

        }
        refreshReq.open('POST', 'http://example:8080/api/rs', true);
        refreshReq.setRequestHeader('Authorization', '');
        refreshReq.send(JSON.stringify(refreshToken));

    } else {
        console.log('other error or success that needs to be displayed.', xhr.status);
        console.log('response', xhr.responseText);

    }
});

// Will proxify XHR to fire the above callbacks
AjaxInterceptor.wire();
*/

var router = require('./stores/RouteStore.js').getRouter();

router.run(function (Handler) {
    React.render(<Handler/>, document.getElementById('content'));
});
<commit_msg>simple logic for replay after refresh token<commit_after>/*
 * Copyright 2015 Network New Technologies Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

require('bootstrap/less/bootstrap.less');

var React = require('react');

var Router = require('react-router')
    , RouteHandler = Router.RouteHandler
    , Route = Router.Route;

//var AjaxInterceptor = require('ajax-interceptor');
var AuthActionCreators = require('./actions/AuthActionCreators.js');
var AuthStore = require('./stores/AuthStore.js');
var AppConstants = require('./constants/AppConstants.js');
var axios = require('axios');

// Add a request interceptor
axios.interceptors.request.use(function (config) {
    config.headers = config.headers || {};
    var accessToken = AuthStore.getAccessToken();
    // TODO Do not put access token into header of refresh token post. In this case,
    // we don't need to remove the authorizationData before sending refresh token post.
    // chances are some other requests might be sent during the time slot and got login
    // is required error and forced to login page.
    // TODO I really don't like this checking. need to find another way? backend?
    if (accessToken) {
        if(config.data && config.data.name && config.data.name === 'refreshToken') {
            return config;
        }
        config.headers.Authorization = 'Bearer ' + accessToken;
    }
    return config;
}, function (error) {
    // Do something with request error
    return Promise.reject(error);
});

// Add a response interceptor
axios.interceptors.response.use(function (response) {
    // Do something with response data
    console.log('response interceptor', response);
    return response;
}, function (error) {
    // Do something with response error
    console.log('error response interceptor', error);
    if(error.status == 401) {
        console.log('401 error response')
        if(error.data.error == 'token_expired') {
            console.log('token expired');
            var savedConfig = error.config;
            // get accessToken from refreshToken
            var refreshToken = {
                category:'user',
                name:'refreshToken',
                readOnly:true,
                data : {
                    refreshToken: AuthStore.getRefreshToken(),
                    userId: AuthStore.getUserId(),
                    clientId: AppConstants.ClientId
                }
            };
            axios.post('http://example:8080/api/rs', refreshToken)
                .then(function (response) {
                    console.log('refresh token result', response.data);
                    var accessToken = response.data.accessToken;
                    AuthActionCreators.refresh(accessToken);
                    // now need to resend the savedConfig here.
                    savedConfig.headers = savedConfig.headers || {};
                    var accessToken = AuthStore.getAccessToken();
                    if(accessToken) {
                        savedConfig.headers.Authorization = 'Bearer ' + accessToken;
                    }
                    console.log('saveConfig before post', savedConfig);
                    if(savedConfig.method == 'get') {
                        axios.get(buildUrl(savedConfig.url, savedConfig.paramSerializer(savedConfig.params)))
                            .then(function (response) {
                                console.log('retry is OK', response.data);
                                Promise.resolve(response);
                            })
                            .catch(function(response) {
                                console.log('retry get error', response.data);
                                Promise.reject(response);
                            });
                    } else if (savedConfig.method == 'post') {
                        axios.post(savedConfig.url, savedConfig.data)
                            .then(function (response) {
                                console.log('retry is OK', response.data);
                                Promise.resolve(response);
                            })
                            .catch(function(response) {
                                console.log('retry get error', response.data);
                                Promise.reject(response);
                            });

                    }
                    axios.get('http://example:8080/api/rs', savedConfig.params)
                })
                .catch(function(response) {
                    console.log('error in refresh token', response);
                });
        }

    } else if(error.status == 403) {
        console.log('403 error response');
        // 403 forbidden. The user is logged in but doesn't have permission for the request.
        // logout and redirect to login page.

    }
    return Promise.reject(error);
});

//var httpBuffer = [];

/**
 * init http hooks for oauth
 *
 */
/*
AjaxInterceptor.addRequestCallback(function(xhr) {
    console.debug("request callback",xhr);
    // get the access token from store and put it into the header.
    var accessToken = AuthStore.getAccessToken();
    if(accessToken) {
        xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
    }
});

AjaxInterceptor.addResponseCallback(function(xhr) {
    console.debug("response",xhr);
    // intercept token expire error and refresh token.
    //console.log('xhr.status', xhr.status);
    //console.log('xhr.requesturl', xhr.responseURL);
    //console.log('xhr.responseText', xhr.responseText);
    if(xhr.status === 401 && xhr.responseText === '{"error":"token_expired"}') {
        console.log('token expired, renewing...');
        httpBuffer.push(xhr);
        var refreshToken = {
            category:'user',
            name:'refreshToken',
            readOnly:true,
            data : {
                refreshToken: AuthStore.getRefreshToken(),
                userId: AuthStore.getUserId(),
                clientId: AppConstants.ClientId
            }
        };

        var refreshReq = new XMLHttpRequest();
        refreshReq.onreadystatechange = function () {
            if(refreshReq.readyState == 4 && refreshReq.status == 200) {
                console.log('refreshToken', refreshReq.responseText);
                var jsonPayload = JSON.parse(refreshReq.responseText);
                AuthActionCreators.refresh(jsonPayload.accessToken);
            }

        }
        refreshReq.open('POST', 'http://example:8080/api/rs', true);
        refreshReq.setRequestHeader('Authorization', '');
        refreshReq.send(JSON.stringify(refreshToken));

    } else {
        console.log('other error or success that needs to be displayed.', xhr.status);
        console.log('response', xhr.responseText);

    }
});

// Will proxify XHR to fire the above callbacks
AjaxInterceptor.wire();
*/

function buildUrl(url, serializedParams) {
    if (serializedParams.length > 0) {
        url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams;
    }
    return url;
};

var router = require('./stores/RouteStore.js').getRouter();

router.run(function (Handler) {
    React.render(<Handler/>, document.getElementById('content'));
});
<|endoftext|>


<commit_before>/**
 * This module executes inside of electron's main process. You can start
 * electron renderer process from here and communicate with the other processes
 * through IPC.
 *
 * When running `npm run build` or `npm run build-main`, this file is compiled to
 * `/dist/main.js` using webpack. This gives us some performance wins.
 */
import { app, BrowserWindow, session } from 'electron'
import isDev from 'electron-is-dev'
import installExtension, {
  REACT_DEVELOPER_TOOLS,
  REDUX_DEVTOOLS,
} from 'electron-devtools-installer'
import get from 'lodash.get'
import path from 'path'
import os from 'os'
import fs from 'fs'
import bip21 from 'bip21'
import config from 'config'
import { mainLog } from '@zap/utils/log'
import appRootPath from '@zap/utils/appRootPath'
import themes from '@zap/renderer/themes'
import getDbName from '@zap/utils/db'
import ZapMenuBuilder from './menuBuilder'
import ZapController from './controller'
import ZapUpdater from './updater'
import ZapMigrator from './migrator'

// Set the Electron userDir to a temporary directory if the ELECTRON_USER_DIR_TEMP env var is set.
// This provides an easy way to run the app with a completely fresh environment, useful for e2e tests.
if (process.env.ELECTRON_USER_DIR_TEMP) {
  const folder = fs.mkdtempSync(path.join(os.tmpdir(), 'zap-'))
  mainLog.info('Using temporary directory %s for userData', folder)
  app.setPath('userData', folder)
}

// By default, run the app in development mode.
if (!process.env.NODE_ENV) {
  process.env.NODE_ENV = 'development'
}

// Set up references to application helpers and controllers.
let zap
let updater
let menuBuilder
let mainWindow
let protocolUrl

// Set up a couple of timers to track the app startup progress.
mainLog.time('Time until app is ready')

/**
 * Handler for open-link events.
 */
const handleOpenUrl = (urlToOpen = '') => {
  // If we already have the mainWindow, handle the link right away.
  if (mainWindow) {
    mainLog.debug('handleOpenUrl: %s', urlToOpen)
    const type = urlToOpen.split(':')[0]

    switch (type) {
      case 'bitcoin':
        handleBitcoinLink(urlToOpen)
        break

      case 'lightning':
        handleLightningLink(urlToOpen)
        break

      case 'lndconnect':
        handleLndconnectLink(urlToOpen)
        break
    }
  }

  // Otherwise, defer until the winow content has fully loaded.
  // See mainWindow.webContents.on('did-finish-load') below.
  else {
    protocolUrl = urlToOpen
  }
}

/**
 * Handler for lndconnect: links
 */
const handleBitcoinLink = input => {
  try {
    const decoded = bip21.decode(input)
    zap.sendMessage('bitcoinPaymentUri', decoded)
    mainWindow.show()
  } catch (e) {
    mainLog.warn('Unable to process bitcoin uri: %s', e)
  }
}

/**
 * Handler for lightning: links
 */
const handleLightningLink = input => {
  const payReq = input.split(':')[1]
  zap.sendMessage('lightningPaymentUri', { payReq })
  mainWindow.show()
}

/**
 * Handler for lndconnect: links
 */
const handleLndconnectLink = input => {
  try {
    zap.sendMessage('lndconnectUri', input)
    mainWindow.show()
  } catch (e) {
    mainLog.warn('Unable to process lndconnect uri: %s', e)
  }
}

/**
 * Fetch user settings from indexedDb.
 * We do this by starting up a new browser window and accessing indexedDb from within it.
 */
const fetchSettings = () => {
  const win = new BrowserWindow({
    show: false,
    focusable: false,
    webPreferences: {
      nodeIntegration: false,
    },
  })
  if (process.env.HOT) {
    const port = process.env.PORT || 1212
    win.loadURL(`http://localhost:${port}/dist/empty.html`)
  } else {
    win.loadURL(`file://${__dirname}/empty.html`)
  }

  // Once we have fetched (or failed to fetch) the user settings, destroy the window.
  win.on('load-settings-done', () => process.nextTick(() => win.destroy()))

  const { namespace, domain } = config.db
  const { NODE_ENV: environment } = process.env
  const dbName = getDbName({
    namespace,
    domain,
    environment,
  })
  mainLog.debug(`Fetching user settings from indexedDb (using database "%s")`, dbName)

  return win.webContents
    .executeJavaScript(
      `
      new Promise((resolve, reject) => {
        var DBOpenRequest = window.indexedDB.open('${dbName}')
        DBOpenRequest.onupgradeneeded = function(event) {
          event.target.transaction.abort()
          return reject(new Error('Database does not exist'))
        }
        DBOpenRequest.onerror = function() {
          return reject(new Error('Error loading database'))
        }
        DBOpenRequest.onsuccess = function() {
          const db = DBOpenRequest.result
          var transaction = db.transaction(['settings'], 'readwrite')
          transaction.onerror = function() {
            return reject(transaction.error)
          }
          var objectStore = transaction.objectStore('settings')
          var objectStoreRequest = objectStore.getAll()
          objectStoreRequest.onsuccess = function() {
            return resolve(objectStoreRequest.result)
          }
        }
      })
    `
    )
    .then(res => {
      mainLog.debug('Got user settings: %o', res)
      win.emit('load-settings-done')
      return res
    })
    .catch(err => {
      win.emit('load-settings-done')
      throw err
    })
}

/**
 * Helper method to fetch a a settings property value.'
 */
const getSetting = (store, key) => {
  const setting = store.find(s => s.key === key)
  return setting && setting.hasOwnProperty('value') ? setting.value : null
}

/**
 * If we are not able to get a single instnace lock, quit immediately.
 */
const singleInstanceLock = app.requestSingleInstanceLock()
if (!singleInstanceLock) {
  mainLog.error('Unable to get single instance lock. It looks like you already have Zap open?')
  app.quit()
}

app.on('will-finish-launching', () => {
  app.on('open-url', (event, urlToOpen) => {
    mainLog.trace('app.open-url')
    event.preventDefault()
    handleOpenUrl(urlToOpen)
  })
})

/**
 * Initialize Zap as soon as electron is ready.
 */
app.on('ready', async () => {
  mainLog.trace('app.ready')
  mainLog.timeEnd('Time until app is ready')

  // Get the users preference so that we can:
  //  - set the background colour of the window to avoid unwanted flicker.
  //  - Initialise the Language menu with the users locale selected by default.
  let theme = {}
  let locale

  if (!process.env.DISABLE_INIT) {
    try {
      const settings = await fetchSettings()
      locale = getSetting(settings, 'locale')
      const themeKey = getSetting(settings, 'theme')
      theme = themes[themeKey]
    } catch (e) {
      mainLog.warn('Unable to determine user locale and theme', e)
    }
  }

  // Create a new browser window.
  mainWindow = new BrowserWindow({
    show: false,
    useContentSize: true,
    titleBarStyle: 'hidden',
    width: 900,
    height: 680,
    minWidth: 900,
    minHeight: 425,
    backgroundColor: get(theme, 'colors.primaryColor', '#242633'),
    icon: isDev
      ? path.resolve('resources', 'icon.png')
      : path.resolve(appRootPath(), 'resources', 'icon.png'),
    webPreferences: {
      nodeIntegration: false,
      nodeIntegrationInWorker: true,
      preload: isDev
        ? path.resolve(__dirname, '..', 'dist', 'preload.js')
        : path.resolve(__dirname, 'preload.js'),
    },
  })

  // Initialise the migrator and run any pending migrations.
  if (!process.env.DISABLE_INIT) {
    const migrator = new ZapMigrator()
    await migrator.up()
  }

  // Initialise the updater.
  updater = new ZapUpdater(mainWindow)
  updater.init()

  // Initialise the application.
  zap = new ZapController(mainWindow)
  zap.init()

  // Initialise the application menus.
  menuBuilder = new ZapMenuBuilder(mainWindow)
  menuBuilder.buildMenu(locale)

  /**
   * Add application event listener:
   *  - Stop gRPC and kill lnd process before the app windows are closed and the app quits.
   */
  app.on('before-quit', event => {
    mainLog.trace('app.before-quit')
    if (mainWindow && !mainWindow.forceClose) {
      event.preventDefault()
      mainWindow.forceClose = true
      zap.terminate()
    }
  })

  app.on('will-quit', () => {
    mainLog.trace('app.will-quit')
  })

  app.on('quit', () => {
    mainLog.trace('app.quit')
  })

  /**
   * On OS X it's common to re-open a window in the app when the dock icon is clicked.
   */
  app.on('activate', () => {
    mainLog.trace('app.activate')
    mainWindow.show()
  })

  app.on('window-all-closed', () => {
    mainLog.trace('app.window-all-closed')
    if (os.platform() !== 'darwin' || mainWindow.forceClose) {
      app.quit()
    }
  })

  /**
   * Someone tried to run a second instance, we should focus our window.
   */
  app.on('second-instance', (event, commandLine) => {
    mainLog.trace('app.second-instance')
    if (os.platform() !== 'darwin') {
      const urlToOpen = commandLine && commandLine.slice(1)[0]
      if (urlToOpen) {
        handleOpenUrl(urlToOpen)
      }
    }
    if (mainWindow) {
      if (mainWindow.isMinimized()) {
        mainWindow.restore()
      }
      mainWindow.focus()
    }
  })

  // When the window is closed, just hide it unless we are force closing.
  mainWindow.on('close', event => {
    mainLog.trace('mainWindow.close')
    if (os.platform() === 'darwin' && !mainWindow.forceClose) {
      event.preventDefault()
      if (mainWindow.isFullScreen()) {
        mainWindow.once('leave-full-screen', () => {
          mainWindow.hide()
        })
        mainWindow.setFullScreen(false)
      } else {
        mainWindow.hide()
      }
    }
  })

  // Dereference the window object.
  mainWindow.on('closed', () => {
    mainLog.trace('mainWindow.closed')
    mainWindow = null
    updater.mainWindow = null
    zap.mainWindow = null
    menuBuilder.mainWindow = null
  })

  // Once the app has finished loading, handle any deferred protocol urls.
  mainWindow.webContents.on('did-finish-load', () => {
    mainLog.trace('webContents.did-finish-load')

    // Check to see if we have a protocol link to handle from app startup time.
    // On the mac, `protocolUrl` will be set as a result of us trying to process the link from the `open-url` handler.
    // On windows/linux the link will be passed to electron as the first process argument.
    const urlToOpen = protocolUrl || process.argv.slice(1)[0]
    if (urlToOpen) {
      handleOpenUrl(urlToOpen)
      protocolUrl = null
    }
  })

  /**
   * In production mode, enable source map support.
   */
  if (process.env.NODE_ENV === 'production') {
    const sourceMapSupport = require('source-map-support') // eslint-disable-line global-require
    sourceMapSupport.install()
  }

  /**
   * In development mode or when DEBUG_PROD is set, enable debugging tools.
   */
  if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD) {
    installExtension(REACT_DEVELOPER_TOOLS)
      .then(name => mainLog.debug(`Added Extension: ${name}`))
      .catch(err => mainLog.warn(`An error occurred when installing REACT_DEVELOPER_TOOLS: ${err}`))

    installExtension(REDUX_DEVTOOLS)
      .then(name => mainLog.debug(`Added Extension: ${name}`))
      .catch(err => mainLog.warn(`An error occurred when installing REDUX_DEVTOOLS: ${err}`))

    mainWindow.webContents.once('dom-ready', () => {
      mainLog.trace('webContents.dom-ready')
      mainWindow.openDevTools()
    })
  }

  // HACK: patch webrequest to fix devtools incompatibility with electron 2.x.
  // See https://github.com/electron/electron/issues/13008#issuecomment-400261941
  session.defaultSession.webRequest.onBeforeRequest({}, (details, callback) => {
    if (details.url.indexOf('7accc8730b0f99b5e7c0702ea89d1fa7c17bfe33') === -1) {
      callback({ cancel: false })
    } else {
      callback({
        redirectURL: details.url.replace(
          '7accc8730b0f99b5e7c0702ea89d1fa7c17bfe33',
          '57c9d07b416b5a2ea23d28247300e4af36329bdc'
        ),
      })
    }
  })
})

// ------------------------------------
// Helpers
// ------------------------------------

/**
 * Add application event listeners:
 *  - lightning: Open zap payment form when bitcoin url is opened
 *  - lightning: Open zap payment form when lightning url is opened
 *  - lndconnect: Populate onboarding connection details form when lndconnect url is opened
 */
app.setAsDefaultProtocolClient('bitcoin')
app.setAsDefaultProtocolClient('lightning')
app.setAsDefaultProtocolClient('lndconnect')
<commit_msg>refactor: disable context isolation<commit_after>/**
 * This module executes inside of electron's main process. You can start
 * electron renderer process from here and communicate with the other processes
 * through IPC.
 *
 * When running `npm run build` or `npm run build-main`, this file is compiled to
 * `/dist/main.js` using webpack. This gives us some performance wins.
 */
import { app, BrowserWindow, session } from 'electron'
import isDev from 'electron-is-dev'
import installExtension, {
  REACT_DEVELOPER_TOOLS,
  REDUX_DEVTOOLS,
} from 'electron-devtools-installer'
import get from 'lodash.get'
import path from 'path'
import os from 'os'
import fs from 'fs'
import bip21 from 'bip21'
import config from 'config'
import { mainLog } from '@zap/utils/log'
import appRootPath from '@zap/utils/appRootPath'
import themes from '@zap/renderer/themes'
import getDbName from '@zap/utils/db'
import ZapMenuBuilder from './menuBuilder'
import ZapController from './controller'
import ZapUpdater from './updater'
import ZapMigrator from './migrator'

// Set the Electron userDir to a temporary directory if the ELECTRON_USER_DIR_TEMP env var is set.
// This provides an easy way to run the app with a completely fresh environment, useful for e2e tests.
if (process.env.ELECTRON_USER_DIR_TEMP) {
  const folder = fs.mkdtempSync(path.join(os.tmpdir(), 'zap-'))
  mainLog.info('Using temporary directory %s for userData', folder)
  app.setPath('userData', folder)
}

// By default, run the app in development mode.
if (!process.env.NODE_ENV) {
  process.env.NODE_ENV = 'development'
}

// Set up references to application helpers and controllers.
let zap
let updater
let menuBuilder
let mainWindow
let protocolUrl

// Set up a couple of timers to track the app startup progress.
mainLog.time('Time until app is ready')

/**
 * Handler for open-link events.
 */
const handleOpenUrl = (urlToOpen = '') => {
  // If we already have the mainWindow, handle the link right away.
  if (mainWindow) {
    mainLog.debug('handleOpenUrl: %s', urlToOpen)
    const type = urlToOpen.split(':')[0]

    switch (type) {
      case 'bitcoin':
        handleBitcoinLink(urlToOpen)
        break

      case 'lightning':
        handleLightningLink(urlToOpen)
        break

      case 'lndconnect':
        handleLndconnectLink(urlToOpen)
        break
    }
  }

  // Otherwise, defer until the winow content has fully loaded.
  // See mainWindow.webContents.on('did-finish-load') below.
  else {
    protocolUrl = urlToOpen
  }
}

/**
 * Handler for lndconnect: links
 */
const handleBitcoinLink = input => {
  try {
    const decoded = bip21.decode(input)
    zap.sendMessage('bitcoinPaymentUri', decoded)
    mainWindow.show()
  } catch (e) {
    mainLog.warn('Unable to process bitcoin uri: %s', e)
  }
}

/**
 * Handler for lightning: links
 */
const handleLightningLink = input => {
  const payReq = input.split(':')[1]
  zap.sendMessage('lightningPaymentUri', { payReq })
  mainWindow.show()
}

/**
 * Handler for lndconnect: links
 */
const handleLndconnectLink = input => {
  try {
    zap.sendMessage('lndconnectUri', input)
    mainWindow.show()
  } catch (e) {
    mainLog.warn('Unable to process lndconnect uri: %s', e)
  }
}

/**
 * Fetch user settings from indexedDb.
 * We do this by starting up a new browser window and accessing indexedDb from within it.
 */
const fetchSettings = () => {
  const win = new BrowserWindow({
    show: false,
    focusable: false,
    webPreferences: {
      nodeIntegration: false,
    },
  })
  if (process.env.HOT) {
    const port = process.env.PORT || 1212
    win.loadURL(`http://localhost:${port}/dist/empty.html`)
  } else {
    win.loadURL(`file://${__dirname}/empty.html`)
  }

  // Once we have fetched (or failed to fetch) the user settings, destroy the window.
  win.on('load-settings-done', () => process.nextTick(() => win.destroy()))

  const { namespace, domain } = config.db
  const { NODE_ENV: environment } = process.env
  const dbName = getDbName({
    namespace,
    domain,
    environment,
  })
  mainLog.debug(`Fetching user settings from indexedDb (using database "%s")`, dbName)

  return win.webContents
    .executeJavaScript(
      `
      new Promise((resolve, reject) => {
        var DBOpenRequest = window.indexedDB.open('${dbName}')
        DBOpenRequest.onupgradeneeded = function(event) {
          event.target.transaction.abort()
          return reject(new Error('Database does not exist'))
        }
        DBOpenRequest.onerror = function() {
          return reject(new Error('Error loading database'))
        }
        DBOpenRequest.onsuccess = function() {
          const db = DBOpenRequest.result
          var transaction = db.transaction(['settings'], 'readwrite')
          transaction.onerror = function() {
            return reject(transaction.error)
          }
          var objectStore = transaction.objectStore('settings')
          var objectStoreRequest = objectStore.getAll()
          objectStoreRequest.onsuccess = function() {
            return resolve(objectStoreRequest.result)
          }
        }
      })
    `
    )
    .then(res => {
      mainLog.debug('Got user settings: %o', res)
      win.emit('load-settings-done')
      return res
    })
    .catch(err => {
      win.emit('load-settings-done')
      throw err
    })
}

/**
 * Helper method to fetch a a settings property value.'
 */
const getSetting = (store, key) => {
  const setting = store.find(s => s.key === key)
  return setting && setting.hasOwnProperty('value') ? setting.value : null
}

/**
 * If we are not able to get a single instnace lock, quit immediately.
 */
const singleInstanceLock = app.requestSingleInstanceLock()
if (!singleInstanceLock) {
  mainLog.error('Unable to get single instance lock. It looks like you already have Zap open?')
  app.quit()
}

app.on('will-finish-launching', () => {
  app.on('open-url', (event, urlToOpen) => {
    mainLog.trace('app.open-url')
    event.preventDefault()
    handleOpenUrl(urlToOpen)
  })
})

/**
 * Initialize Zap as soon as electron is ready.
 */
app.on('ready', async () => {
  mainLog.trace('app.ready')
  mainLog.timeEnd('Time until app is ready')

  // Get the users preference so that we can:
  //  - set the background colour of the window to avoid unwanted flicker.
  //  - Initialise the Language menu with the users locale selected by default.
  let theme = {}
  let locale

  if (!process.env.DISABLE_INIT) {
    try {
      const settings = await fetchSettings()
      locale = getSetting(settings, 'locale')
      const themeKey = getSetting(settings, 'theme')
      theme = themes[themeKey]
    } catch (e) {
      mainLog.warn('Unable to determine user locale and theme', e)
    }
  }

  // Create a new browser window.
  mainWindow = new BrowserWindow({
    show: false,
    useContentSize: true,
    titleBarStyle: 'hidden',
    width: 900,
    height: 680,
    minWidth: 900,
    minHeight: 425,
    backgroundColor: get(theme, 'colors.primaryColor', '#242633'),
    icon: isDev
      ? path.resolve('resources', 'icon.png')
      : path.resolve(appRootPath(), 'resources', 'icon.png'),
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: false,
      nodeIntegrationInWorker: true,
      preload: isDev
        ? path.resolve(__dirname, '..', 'dist', 'preload.js')
        : path.resolve(__dirname, 'preload.js'),
    },
  })

  // Initialise the migrator and run any pending migrations.
  if (!process.env.DISABLE_INIT) {
    const migrator = new ZapMigrator()
    await migrator.up()
  }

  // Initialise the updater.
  updater = new ZapUpdater(mainWindow)
  updater.init()

  // Initialise the application.
  zap = new ZapController(mainWindow)
  zap.init()

  // Initialise the application menus.
  menuBuilder = new ZapMenuBuilder(mainWindow)
  menuBuilder.buildMenu(locale)

  /**
   * Add application event listener:
   *  - Stop gRPC and kill lnd process before the app windows are closed and the app quits.
   */
  app.on('before-quit', event => {
    mainLog.trace('app.before-quit')
    if (mainWindow && !mainWindow.forceClose) {
      event.preventDefault()
      mainWindow.forceClose = true
      zap.terminate()
    }
  })

  app.on('will-quit', () => {
    mainLog.trace('app.will-quit')
  })

  app.on('quit', () => {
    mainLog.trace('app.quit')
  })

  /**
   * On OS X it's common to re-open a window in the app when the dock icon is clicked.
   */
  app.on('activate', () => {
    mainLog.trace('app.activate')
    mainWindow.show()
  })

  app.on('window-all-closed', () => {
    mainLog.trace('app.window-all-closed')
    if (os.platform() !== 'darwin' || mainWindow.forceClose) {
      app.quit()
    }
  })

  /**
   * Someone tried to run a second instance, we should focus our window.
   */
  app.on('second-instance', (event, commandLine) => {
    mainLog.trace('app.second-instance')
    if (os.platform() !== 'darwin') {
      const urlToOpen = commandLine && commandLine.slice(1)[0]
      if (urlToOpen) {
        handleOpenUrl(urlToOpen)
      }
    }
    if (mainWindow) {
      if (mainWindow.isMinimized()) {
        mainWindow.restore()
      }
      mainWindow.focus()
    }
  })

  // When the window is closed, just hide it unless we are force closing.
  mainWindow.on('close', event => {
    mainLog.trace('mainWindow.close')
    if (os.platform() === 'darwin' && !mainWindow.forceClose) {
      event.preventDefault()
      if (mainWindow.isFullScreen()) {
        mainWindow.once('leave-full-screen', () => {
          mainWindow.hide()
        })
        mainWindow.setFullScreen(false)
      } else {
        mainWindow.hide()
      }
    }
  })

  // Dereference the window object.
  mainWindow.on('closed', () => {
    mainLog.trace('mainWindow.closed')
    mainWindow = null
    updater.mainWindow = null
    zap.mainWindow = null
    menuBuilder.mainWindow = null
  })

  // Once the app has finished loading, handle any deferred protocol urls.
  mainWindow.webContents.on('did-finish-load', () => {
    mainLog.trace('webContents.did-finish-load')

    // Check to see if we have a protocol link to handle from app startup time.
    // On the mac, `protocolUrl` will be set as a result of us trying to process the link from the `open-url` handler.
    // On windows/linux the link will be passed to electron as the first process argument.
    const urlToOpen = protocolUrl || process.argv.slice(1)[0]
    if (urlToOpen) {
      handleOpenUrl(urlToOpen)
      protocolUrl = null
    }
  })

  /**
   * In production mode, enable source map support.
   */
  if (process.env.NODE_ENV === 'production') {
    const sourceMapSupport = require('source-map-support') // eslint-disable-line global-require
    sourceMapSupport.install()
  }

  /**
   * In development mode or when DEBUG_PROD is set, enable debugging tools.
   */
  if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD) {
    installExtension(REACT_DEVELOPER_TOOLS)
      .then(name => mainLog.debug(`Added Extension: ${name}`))
      .catch(err => mainLog.warn(`An error occurred when installing REACT_DEVELOPER_TOOLS: ${err}`))

    installExtension(REDUX_DEVTOOLS)
      .then(name => mainLog.debug(`Added Extension: ${name}`))
      .catch(err => mainLog.warn(`An error occurred when installing REDUX_DEVTOOLS: ${err}`))

    mainWindow.webContents.once('dom-ready', () => {
      mainLog.trace('webContents.dom-ready')
      mainWindow.openDevTools()
    })
  }

  // HACK: patch webrequest to fix devtools incompatibility with electron 2.x.
  // See https://github.com/electron/electron/issues/13008#issuecomment-400261941
  session.defaultSession.webRequest.onBeforeRequest({}, (details, callback) => {
    if (details.url.indexOf('7accc8730b0f99b5e7c0702ea89d1fa7c17bfe33') === -1) {
      callback({ cancel: false })
    } else {
      callback({
        redirectURL: details.url.replace(
          '7accc8730b0f99b5e7c0702ea89d1fa7c17bfe33',
          '57c9d07b416b5a2ea23d28247300e4af36329bdc'
        ),
      })
    }
  })
})

// ------------------------------------
// Helpers
// ------------------------------------

/**
 * Add application event listeners:
 *  - lightning: Open zap payment form when bitcoin url is opened
 *  - lightning: Open zap payment form when lightning url is opened
 *  - lndconnect: Populate onboarding connection details form when lndconnect url is opened
 */
app.setAsDefaultProtocolClient('bitcoin')
app.setAsDefaultProtocolClient('lightning')
app.setAsDefaultProtocolClient('lndconnect')
<|endoftext|>


<commit_before>/**
 * Created by Bart on 25/05/2016.
 */
const getMaxCount = function (cameraTimelines) {
    // Get the maxCount of the entire grid
    let maxCount = 0;
    cameraTimelines.forEach((timeline) => {
        if (timeline.cameraShots.length > 0) {
            console.log("here");
            const curEndCount = parseFloat(
                timeline.cameraShots[timeline.cameraShots.length - 1].endCount);
            if (curEndCount > maxCount) {
                maxCount = curEndCount;
            }
        }

        // Set visible property of existing blocks
        timeline.cameraShots.forEach((shot) => {
            shot.visible = "visible";
        });
    });
    return maxCount;
};

(function() {
    'use strict';
    class timelineGrid {
        beforeRegister() {
            this.is = 'timeline-grid';

            this.properties = {
                name: String,
                timeline_data: Object
            };
        }
        handleResponse(event) {
            // Setup lists for timelines
            // TODO: find a way to correctly execute this in timeline-element

            // Get data from ajax response
            const responseData = event.detail.response;
            const maxCount = getMaxCount(responseData.cameraTimelines);

            // Setup new data
            const newData = {};
            newData.minCount = 0;
            newData.maxCount = maxCount;
            newData.cameraTimelines = [];

            // Loop over all counts and pad with non-visible blocks where necessary
            responseData.cameraTimelines.forEach(function (timeline, index) {
                // Setup timeline
                const newTimeline = {};
                newTimeline.name = timeline.name;
                newTimeline.description = timeline.description;
                newTimeline.cameraShots = [];
                newData.cameraTimelines[index] = newTimeline;

                // Only do following if at least one block is present
                if(timeline.cameraShots.length > 0) {
                    // Add padding before the first block
                    if (timeline.cameraShots[0].beginCount != 0) {
                        var newShot = {
                            beginCount: "0",
                            endCount: timeline.cameraShots[0].beginCount,
                            visible: "hidden"
                        };
                        newTimeline.cameraShots.push(newShot);
                    }

                    // Add padding after each consecutive block:
                    timeline.cameraShots.forEach(function (shot, i) {
                        newTimeline.cameraShots.push(shot);
                        if (i < timeline.cameraShots.length - 1) {
                            // Not the last block
                            var endCount = (timeline.cameraShots[i + 1].beginCount * 4 ) / 4;
                            var newShot = {
                                beginCount: shot.endCount,
                                endCount: endCount,
                                visible: "hidden"
                            };
                            newTimeline.cameraShots.push(newShot);
                        } else {
                            // The last block
                            if (parseFloat(shot.endCount) != maxCount) {
                                // There is room leeft in timeline
                                var newShot = {
                                    beginCount: shot.endCount,
                                    endCount: maxCount,
                                    visible: "hidden"
                                };
                                newTimeline.cameraShots.push(newShot);
                            }
                        }
                    });
                } else {
                    // Add one block to fill the timeline
                    var newShot = {
                        beginCount: 0,
                        endCount: maxCount,
                        visible: "hidden"
                    };
                    newTimeline.cameraShots.push(newShot);
                }

                // Set length of blocks
                newTimeline.cameraShots.forEach(function (shot) {
                    // 4 = number of blocks per count, 10 is height per block
                    shot.pixelLength = (shot.endCount - shot.beginCount) * 4 * 10;
                });
            });

            // Set data to property
            this.timeline_data = newData;
        }
    }
    Polymer(timelineGrid);
})();<commit_msg>fixed some more eslint errors<commit_after>/**
 * Created by Bart on 25/05/2016.
 */
const getMaxCount = function (cameraTimelines) {
    // Get the maxCount of the entire grid
    let maxCount = 0;
    cameraTimelines.forEach((timeline) => {
        if (timeline.cameraShots.length > 0) {
            console.log("here");
            const curEndCount = parseFloat(
                timeline.cameraShots[timeline.cameraShots.length - 1].endCount);
            if (curEndCount > maxCount) {
                maxCount = curEndCount;
            }
        }

        // Set visible property of existing blocks
        timeline.cameraShots.forEach((shot) => {
            shot.visible = "visible";
        });
    });
    return maxCount;
};

(function () {
    class timelineGrid {
        beforeRegister() {
            this.is = "timeline-grid";

            this.properties = {
                name: String,
                timeline_data: Object,
            };
        }
        handleResponse(event) {
            // Setup lists for timelines
            // TODO: find a way to correctly execute this in timeline-element

            // Get data from ajax response
            const responseData = event.detail.response;
            const maxCount = getMaxCount(responseData.cameraTimelines);

            // Setup new data
            const newData = {};
            newData.minCount = 0;
            newData.maxCount = maxCount;
            newData.cameraTimelines = [];

            // Loop over all counts and pad with non-visible blocks where necessary
            responseData.cameraTimelines.forEach(function (timeline, index) {
                // Setup timeline
                const newTimeline = {};
                newTimeline.name = timeline.name;
                newTimeline.description = timeline.description;
                newTimeline.cameraShots = [];
                newData.cameraTimelines[index] = newTimeline;

                // Only do following if at least one block is present
                if (timeline.cameraShots.length > 0) {
                    // Add padding before the first block
                    if (timeline.cameraShots[0].beginCount !== 0) {
                        const newShot = {
                            beginCount: "0",
                            endCount: timeline.cameraShots[0].beginCount,
                            visible: "hidden",
                        };
                        newTimeline.cameraShots.push(newShot);
                    }

                    // Add padding after each consecutive block:
                    timeline.cameraShots.forEach((shot, i) => {
                        newTimeline.cameraShots.push(shot);
                        if (i < timeline.cameraShots.length - 1) {
                            // Not the last block
                            const endCount = (timeline.cameraShots[i + 1].beginCount * 4) / 4;
                            const newShot = {
                                beginCount: shot.endCount,
                                endCount,
                                visible: "hidden",
                            };
                            newTimeline.cameraShots.push(newShot);
                        } else {
                            // The last block
                            if (parseFloat(shot.endCount) !== maxCount) {
                                // There is room leeft in timeline
                                const newShot = {
                                    beginCount: shot.endCount,
                                    endCount: maxCount,
                                    visible: "hidden",
                                };
                                newTimeline.cameraShots.push(newShot);
                            }
                        }
                    });
                } else {
                    // Add one block to fill the timeline
                    const newShot = {
                        beginCount: 0,
                        endCount: maxCount,
                        visible: "hidden",
                    };
                    newTimeline.cameraShots.push(newShot);
                }

                // Set length of blocks
                newTimeline.cameraShots.forEach((shot) => {
                    // 4 = number of blocks per count, 10 is height per block
                    shot.pixelLength = (shot.endCount - shot.beginCount) * 4 * 10;
                });
            });

            // Set data to property
            this.timeline_data = newData;
        }
    }
    Polymer(timelineGrid);
})();<|endoftext|>


<commit_before>/* global APP */

import React from 'react';
import ReactDOM from 'react-dom';

import { getJitsiMeetTransport } from '../modules/transport';

import { App } from './features/app';

const logger = require('jitsi-meet-logger').getLogger(__filename);

/**
 * Renders the app when the DOM tree has been loaded.
 */
document.addEventListener('DOMContentLoaded', () => {
    const now = window.performance.now();

    APP.connectionTimes['document.ready'] = now;
    logger.log('(TIME) document ready:\t', now);

    // Render the main/root Component.
    ReactDOM.render(<App />, document.getElementById('react'));
});

/**
 * Stops collecting the logs and disposing the API when the user closes the
 * page.
 */
window.addEventListener('beforeunload', () => {
    // Stop the LogCollector
    if (APP.logCollectorStarted) {
        APP.logCollector.stop();
        APP.logCollectorStarted = false;
    }
    APP.API.dispose();
    getJitsiMeetTransport().dispose();
});
<commit_msg>fix(external_api): fire videoConferenceLeft event on unload (#2243)<commit_after>/* global APP */

import React from 'react';
import ReactDOM from 'react-dom';

import { getJitsiMeetTransport } from '../modules/transport';

import { App } from './features/app';

const logger = require('jitsi-meet-logger').getLogger(__filename);

/**
 * Renders the app when the DOM tree has been loaded.
 */
document.addEventListener('DOMContentLoaded', () => {
    const now = window.performance.now();

    APP.connectionTimes['document.ready'] = now;
    logger.log('(TIME) document ready:\t', now);

    // Render the main/root Component.
    ReactDOM.render(<App />, document.getElementById('react'));
});

/**
 * Stops collecting the logs and disposing the API when the user closes the
 * page.
 */
window.addEventListener('beforeunload', () => {
    // Stop the LogCollector
    if (APP.logCollectorStarted) {
        APP.logCollector.stop();
        APP.logCollectorStarted = false;
    }
    APP.API.notifyConferenceLeft(APP.conference.roomName);
    APP.API.dispose();
    getJitsiMeetTransport().dispose();
});
<|endoftext|>


<commit_before>'use strict';

var aFrom           = require('es5-ext/array/from')
  , toArray         = require('es5-ext/array/to-array')
  , flatten         = require('es5-ext/array/#/flatten')
  , ensureIterable  = require('es5-ext/iterable/validate-object')
  , forEach         = require('es5-ext/object/for-each')
  , ensureCallable  = require('es5-ext/object/valid-callable')
  , ensureObject    = require('es5-ext/object/valid-object')
  , ensureString    = require('es5-ext/object/validate-stringifiable-value')
  , Map             = require('es6-map')
  , Set             = require('es6-set')
  , memoize         = require('memoizee/plain')
  , deferred        = require('deferred')
  , genStamp        = require('time-uuid/time')
  , fork            = require('child_process').fork
  , cpus            = require('os').cpus
  , ensureDriver    = require('../ensure-driver')
  , registerEmitter = require('../lib/emitter')

  , ceil = Math.ceil, min = Math.min
  , create = Object.create, keys = Object.keys
  , byStamp = function (a, b) { return a.data.stamp - b.data.stamp; };

module.exports = function (driver, data) {
	var promise, slaveScriptPath, ids, getData;
	ensureDriver(driver);
	ensureObject(data);
	ids = ensureObject(data.ids);
	getData = ensureCallable(data.getData);
	slaveScriptPath = ensureString(data.slaveScriptPath);
	promise = deferred(ids)(function (ids) {
		var count = 0, indexes, processesCount, promises;
		ids = toArray(ensureIterable(ids));
		if (!ids.length) return;
		var resolveOwners = memoize(function () {
			var owners = new Map();
			return deferred.map(keys(indexes), function (storageName) {
				var storageOwners = new Map()
				  , storage = driver.getStorage(storageName);
				owners.set(storageName, storageOwners);
				return deferred.map(indexes[storageName], function (name) {
					var ownerIds = new Set();
					storageOwners.set(name, ownerIds);
					// Get all owner ids for saved records
					return storage.searchComputed(name, function (ownerId) {
						ownerIds.add(ownerId);
					});
				});
			})(owners);
		});
		var cleanup = function () {
			return resolveOwners()(function (owners) {
				return deferred.map(keys(indexes), function (storageName) {
					return deferred.map(indexes[storageName], function (name) {
						var storage = driver.getStorage(storageName);
						// Delete not used ownerids
						return deferred.map(aFrom(owners.get(storageName).get(name)), function (ownerId) {
							return storage._handleStoreComputed(name, ownerId, '', genStamp());
						});
					});
				});
			});
		};

		var initializePool = function (id) {
			var pool, reinitializePool, indexesData, directData, emitData, getStamp, closeDef
			  , poolError;
			var clearPool = function () {
				return resolveOwners()(function (owners) {
					return deferred.map(keys(indexes), function (storageName) {
						var storage = driver.getStorage(storageName);
						return deferred.map(indexes[storageName], function (name) {
							var ownerIds = owners.get(storageName).get(name);
							// Apply calculations
							return deferred(
								deferred.map(keys(indexesData[storageName][name]), function (ownerId) {
									var data = this[ownerId], stamp;
									ownerIds.delete(ownerId);
									delete this[ownerId];
									if (data.stamp === 'async') {
										stamp = function () { return getStamp(ownerId + '/' + name); };
									} else {
										stamp = data.stamp;
									}
									return storage._handleStoreComputed(name, ownerId, data.value, stamp);
								}, indexesData[storageName][name]),
								deferred.map(directData, function (data) {
									return driver.getStorage(data.name)
										._handleStoreDirect(data.ns, data.path, data.value, data.stamp);
								})
							);
						});
					});
				})(function () {
					if (poolError) throw poolError;
					closeDef = deferred();
					emitData.destroy();
					getStamp.destroy();
					pool.send({ type: 'close' });
					return closeDef.promise;
				});
			};
			var sendData = function (poolHealth) {
				if (poolError) throw poolError;
				if (!ids.length) return clearPool();
				if (!poolHealth || (poolHealth < 1500)) {
					if (!(++count % 10)) promise.emit('progress', { type: 'nextObject' });
					return deferred.map(ids.splice(0, 10), function (objId) {
						return getData(objId);
					})(function (data) {
						return flatten.call(data).sort(byStamp);
					})(emitData)(function (data) {
						data.events.forEach(function (data) {
							if (data.type === 'direct') directData.push(data);
							else indexesData[data.name][data.ns][data.path] = data;
						});
						return sendData(data.health);
					});
				}
				promise.emit('progress', { type: 'nextPool' });
				return clearPool()(reinitializePool);
			};
			reinitializePool = function () {
				var def = deferred();
				pool = fork(slaveScriptPath);
				emitData = registerEmitter('data', pool);
				getStamp = registerEmitter('stamp', pool);
				pool.once('message', function (message) {
					if (poolError) {
						def.reject(poolError);
						return;
					}
					if (message.type !== 'init') {
						def.reject(new Error("Unexpected message"));
						return;
					}
					if (!indexes) indexes = message.indexes;
					directData = [];
					if (!indexesData) {
						indexesData = create(null);
						forEach(indexes, function (storageIndexes, name) {
							var storageIndexesData = indexesData[name] = create(null);
							storageIndexes.forEach(function (name) { storageIndexesData[name] = create(null); });
						});
					}
					def.resolve(sendData());
				});
				pool.on('error', function (err) { poolError = err; });
				pool.on('exit', function () {
					if (this !== pool) return;
					if (!closeDef) {
						if (!poolError) poolError = new Error("Slave process stopped working");
					} else if (poolError) {
						closeDef.reject(poolError);
					} else {
						closeDef.resolve();
					}
				});
				return def.promise;
			};
			return reinitializePool();
		};

		processesCount = min(cpus().length, ceil(ids.length / 10));
		promises = [];
		while (processesCount--) promises.push(initializePool());
		return deferred.map(promises)(cleanup);
	});
	return promise;
};
<commit_msg>Support initialData in recompute<commit_after>'use strict';

var aFrom           = require('es5-ext/array/from')
  , toArray         = require('es5-ext/array/to-array')
  , flatten         = require('es5-ext/array/#/flatten')
  , ensureIterable  = require('es5-ext/iterable/validate-object')
  , forEach         = require('es5-ext/object/for-each')
  , ensureCallable  = require('es5-ext/object/valid-callable')
  , ensureObject    = require('es5-ext/object/valid-object')
  , ensureString    = require('es5-ext/object/validate-stringifiable-value')
  , Map             = require('es6-map')
  , Set             = require('es6-set')
  , memoize         = require('memoizee/plain')
  , deferred        = require('deferred')
  , genStamp        = require('time-uuid/time')
  , fork            = require('child_process').fork
  , cpus            = require('os').cpus
  , ensureDriver    = require('../ensure-driver')
  , registerEmitter = require('../lib/emitter')

  , ceil = Math.ceil, min = Math.min
  , create = Object.create, keys = Object.keys
  , byStamp = function (a, b) { return a.data.stamp - b.data.stamp; };

module.exports = function (driver, data) {
	var promise, slaveScriptPath, ids, getData, initialData;
	ensureDriver(driver);
	ensureObject(data);
	ids = ensureObject(data.ids);
	getData = ensureCallable(data.getData);
	slaveScriptPath = ensureString(data.slaveScriptPath);
	if (data.initialData != null) initialData = ensureObject(data.initialData);
	promise = deferred(ids)(function (ids) {
		var count = 0, indexes, processesCount, promises;
		ids = toArray(ensureIterable(ids));
		if (!ids.length) return;
		var resolveOwners = memoize(function () {
			var owners = new Map();
			return deferred.map(keys(indexes), function (storageName) {
				var storageOwners = new Map()
				  , storage = driver.getStorage(storageName);
				owners.set(storageName, storageOwners);
				return deferred.map(indexes[storageName], function (name) {
					var ownerIds = new Set();
					storageOwners.set(name, ownerIds);
					// Get all owner ids for saved records
					return storage.searchComputed(name, function (ownerId) {
						ownerIds.add(ownerId);
					});
				});
			})(owners);
		});
		var cleanup = function () {
			return resolveOwners()(function (owners) {
				return deferred.map(keys(indexes), function (storageName) {
					return deferred.map(indexes[storageName], function (name) {
						var storage = driver.getStorage(storageName);
						// Delete not used ownerids
						return deferred.map(aFrom(owners.get(storageName).get(name)), function (ownerId) {
							return storage._handleStoreComputed(name, ownerId, '', genStamp());
						});
					});
				});
			});
		};

		var initializePool = function (id) {
			var pool, reinitializePool, indexesData, directData, emitData, getStamp, closeDef
			  , poolError;
			var clearPool = function () {
				return resolveOwners()(function (owners) {
					return deferred.map(keys(indexes), function (storageName) {
						var storage = driver.getStorage(storageName);
						return deferred.map(indexes[storageName], function (name) {
							var ownerIds = owners.get(storageName).get(name);
							// Apply calculations
							return deferred(
								deferred.map(keys(indexesData[storageName][name]), function (ownerId) {
									var data = this[ownerId], stamp;
									ownerIds.delete(ownerId);
									delete this[ownerId];
									if (data.stamp === 'async') {
										stamp = function () { return getStamp(ownerId + '/' + name); };
									} else {
										stamp = data.stamp;
									}
									return storage._handleStoreComputed(name, ownerId, data.value, stamp);
								}, indexesData[storageName][name]),
								deferred.map(directData, function (data) {
									return driver.getStorage(data.name)
										._handleStoreDirect(data.ns, data.path, data.value, data.stamp);
								})
							);
						});
					});
				})(function () {
					if (poolError) throw poolError;
					closeDef = deferred();
					emitData.destroy();
					getStamp.destroy();
					pool.send({ type: 'close' });
					return closeDef.promise;
				});
			};
			var sendData = function (poolHealth) {
				if (poolError) throw poolError;
				if (!ids.length) return clearPool();
				if (!poolHealth || (poolHealth < 1500)) {
					if (!(++count % 10)) promise.emit('progress', { type: 'nextObject' });
					return deferred.map(ids.splice(0, 10), function (objId) {
						return getData(objId);
					})(function (data) {
						return flatten.call(data).sort(byStamp);
					})(emitData)(function (data) {
						data.events.forEach(function (data) {
							if (data.type === 'direct') directData.push(data);
							else indexesData[data.name][data.ns][data.path] = data;
						});
						return sendData(data.health);
					});
				}
				promise.emit('progress', { type: 'nextPool' });
				return clearPool()(reinitializePool);
			};
			reinitializePool = function () {
				var def = deferred();
				pool = fork(slaveScriptPath);
				emitData = registerEmitter('data', pool);
				getStamp = registerEmitter('stamp', pool);
				pool.once('message', function (message) {
					if (poolError) {
						def.reject(poolError);
						return;
					}
					if (message.type !== 'init') {
						def.reject(new Error("Unexpected message"));
						return;
					}
					if (!indexes) indexes = message.indexes;
					directData = [];
					if (!indexesData) {
						indexesData = create(null);
						forEach(indexes, function (storageIndexes, name) {
							var storageIndexesData = indexesData[name] = create(null);
							storageIndexes.forEach(function (name) { storageIndexesData[name] = create(null); });
						});
					}
					if (initialData) {
						def.resolve(deferred(initialData)(emitData)(function (data) {
							if (data.events.length) {
								throw new Error("Unexpected events triggered by initial data");
							}
							return sendData();
						}));
					} else {
						def.resolve(sendData());
					}
				});
				pool.on('error', function (err) { poolError = err; });
				pool.on('exit', function () {
					if (this !== pool) return;
					if (!closeDef) {
						if (!poolError) poolError = new Error("Slave process stopped working");
					} else if (poolError) {
						closeDef.reject(poolError);
					} else {
						closeDef.resolve();
					}
				});
				return def.promise;
			};
			return reinitializePool();
		};

		processesCount = min(cpus().length, ceil(ids.length / 10));
		promises = [];
		while (processesCount--) promises.push(initializePool());
		return deferred.map(promises)(cleanup);
	});
	return promise;
};
<|endoftext|>