<commit_before>/*
 * (C) 2015 TekMonks. All rights reserved.
 * License: MIT - see enclosed LICENSE file.
 */

const fs = require("fs");
const http = require("http");
const https = require("https");
const conf = require(`${CONSTANTS.HTTPDCONF}`);
const gzipAsync = require("util").promisify(require("zlib").gzip);
const HEADER_ERROR = {"content-type": "text/plain", "content-encoding":"identity"};

function initSync() {
	const options = conf.ssl ? { pfx: fs.readFileSync(conf.pfxPath), passphrase: conf.pfxPassphrase } : null;

	/* create HTTP/S server */
	let host = conf.host || "::";
	LOG.info(`Attaching socket listener on ${host}:${conf.port}`);
	const listener = (req, res) => {
		if (req.method.toLowerCase() == "options") {
			res.writeHead(200, conf.headers);
			res.end();
		} else {
			const servObject = {req, res, env:{}};
			req.on("data", data => module.exports.onData(data, servObject));
			req.on("end", _ => module.exports.onReqEnd(req.url, req.headers, servObject));
		}
	};
	const server = options ? https.createServer(options, listener) : http.createServer(listener);
	server.timeout = conf.timeout;
	server.listen(conf.port, host);

	console.log(`Server started on ${host}:${conf.port}`);
	LOG.info(`Server started on ${host}:${conf.port}`);
}

function onData(_data, _servObject) {}
function onReqEnd(servObject) {statusNotFound(servObject); end(servObject);}

function statusNotFound(servObject, _error) {
	servObject.res.writeHead(404, HEADER_ERROR);
	servObject.res.write("404 Not Found\n");
}

function statusUnauthorized(servObject, _error) {
	servObject.res.writeHead(403, HEADER_ERROR);
	servObject.res.write("403 Unauthorized or forbidden\n");
}

function statusThrottled(servObject, _error) {
	servObject.res.writeHead(429, HEADER_ERROR);
	servObject.res.write("429 Too many requests\n");
}

function statusInternalError(servObject, _error) {
	servObject.res.writeHead(500, HEADER_ERROR);
	servObject.res.write("Internal error\n");
}

function statusOK(headers, servObject) {
	const confHeaders = _cloneLowerCase(conf.headers);
	const headersIn = _cloneLowerCase(headers);

	const respHeaders = {...headersIn, ...confHeaders, "content-encoding":_shouldWeGZIP(servObject)?"gzip":"identity"};
	servObject.res.writeHead(200, respHeaders);
}

async function write(data, servObject) {
	if (_shouldWeGZIP(servObject)) data = await gzipAsync(data);
	servObject.res.write(data);
}

function end(servObject) {servObject.res.end();}

function _shouldWeGZIP(servObject) {
	const acceptEncoding = _cloneLowerCase(servObject.req.headers)["accept-encoding"] || "identity";
	return conf.enableGZIPEncoding && acceptEncoding.toLowerCase().includes("gzip");
}

const _cloneLowerCase = obj => {let clone = {}; for (const key of Object.keys(obj)) clone[key.toLocaleLowerCase()] = obj[key]; return clone;}

module.exports = {initSync, onData, onReqEnd, statusNotFound, statusUnauthorized, statusThrottled, statusInternalError, statusOK, write, end}<commit_msg>Add error handling to the HTTP server.<commit_after>/*
 * (C) 2015 TekMonks. All rights reserved.
 * License: MIT - see enclosed LICENSE file.
 */

const fs = require("fs");
const http = require("http");
const https = require("https");
const conf = require(`${CONSTANTS.HTTPDCONF}`);
const gzipAsync = require("util").promisify(require("zlib").gzip);
const HEADER_ERROR = {"content-type": "text/plain", "content-encoding":"identity"};

function initSync() {
	const options = conf.ssl ? { pfx: fs.readFileSync(conf.pfxPath), passphrase: conf.pfxPassphrase } : null;

	/* create HTTP/S server */
	let host = conf.host || "::";
	LOG.info(`Attaching socket listener on ${host}:${conf.port}`);
	const listener = (req, res) => {
		if (req.method.toLowerCase() == "options") {
			res.writeHead(200, conf.headers);
			res.end();
		} else {
			const servObject = {req, res, env:{}};
			req.on("data", data => module.exports.onData(data, servObject));
			req.on("end", _ => module.exports.onReqEnd(req.url, req.headers, servObject));
			req.on("error", error => module.exports.onReqError(req.url, req.headers, error, servObject));
		}
	};
	const server = options ? https.createServer(options, listener) : http.createServer(listener);
	server.timeout = conf.timeout;
	server.listen(conf.port, host);

	console.log(`Server started on ${host}:${conf.port}`);
	LOG.info(`Server started on ${host}:${conf.port}`);
}

function onData(_,_) {}
function onReqError(_,_,error,servObject) {statusInternalError(servObject, error); end(servObject);}
function onReqEnd(_,_,servObject) {statusNotFound(servObject); end(servObject);}

function statusNotFound(servObject, _error) {
	servObject.res.writeHead(404, HEADER_ERROR);
	servObject.res.write("404 Not Found\n");
}

function statusUnauthorized(servObject, _error) {
	servObject.res.writeHead(403, HEADER_ERROR);
	servObject.res.write("403 Unauthorized or forbidden\n");
}

function statusThrottled(servObject, _error) {
	servObject.res.writeHead(429, HEADER_ERROR);
	servObject.res.write("429 Too many requests\n");
}

function statusInternalError(servObject, _error) {
	servObject.res.writeHead(500, HEADER_ERROR);
	servObject.res.write("Internal error\n");
}

function statusOK(headers, servObject) {
	const confHeaders = _cloneLowerCase(conf.headers);
	const headersIn = _cloneLowerCase(headers);

	const respHeaders = {...headersIn, ...confHeaders, "content-encoding":_shouldWeGZIP(servObject)?"gzip":"identity"};
	servObject.res.writeHead(200, respHeaders);
}

async function write(data, servObject) {
	if (_shouldWeGZIP(servObject)) data = await gzipAsync(data);
	servObject.res.write(data);
}

function end(servObject) {servObject.res.end();}

function _shouldWeGZIP(servObject) {
	const acceptEncoding = _cloneLowerCase(servObject.req.headers)["accept-encoding"] || "identity";
	return conf.enableGZIPEncoding && acceptEncoding.toLowerCase().includes("gzip");
}

const _cloneLowerCase = obj => {let clone = {}; for (const key of Object.keys(obj)) clone[key.toLocaleLowerCase()] = obj[key]; return clone;}

module.exports = {initSync, onData, onReqEnd, onReqError, statusNotFound, statusUnauthorized, statusThrottled, statusInternalError, statusOK, write, end}<|endoftext|>


<commit_before>/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
 * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * @requires OpenLayers/Handler/Drag.js
 */

/**
 * Class: OpenLayers.Handler.RegularPolygon
 * Handler to draw a regular polygon on the map.  Polygon is displayed on mouse
 *     down, moves or is modified on mouse move, and is finished on mouse up.
 *     The handler triggers callbacks for 'done' and 'cancel'.  Create a new
 *     instance with the <OpenLayers.Handler.RegularPolygon> constructor.
 *
 * Inherits from:
 *  - <OpenLayers.Handler>
 */
OpenLayers.Handler.RegularPolygon = OpenLayers.Class(OpenLayers.Handler.Drag, {

    /**
     * APIProperty: sides
     * {Integer} Number of sides for the regular polygon.  Needs to be greater
     *     than 2.  Defaults to 4.
     */
    sides: 4,

    /**
     * APIProperty: radius
     * {Float} Optional radius in map units of the regular polygon.  If this is
     *     set to some non-zero value, a polygon with a fixed radius will be
     *     drawn and dragged with mose movements.  If this property is not
     *     set, dragging changes the radius of the polygon.  Set to null by
     *     default.
     */
    radius: null,

    /**
     * APIProperty: snapAngle
     * {Float} If set to a non-zero value, the handler will snap the polygon
     *     rotation to multiples of the snapAngle.  Value is an angle measured
     *     in degrees counterclockwise from the positive x-axis.
     */
    snapAngle: null,

    /**
     * APIProperty: snapToggle
     * {String} If set, snapToggle is checked on mouse events and will set
     *     the snap mode to the opposite of what it currently is.  To disallow
     *     toggling between snap and non-snap mode, set freehandToggle to
     *     null.  Acceptable toggle values are 'shiftKey', 'ctrlKey', and
     *     'altKey'. Snap mode is only possible if this.snapAngle is set to a
     *     non-zero value.
     */
    snapToggle: 'shiftKey',

    /**
     * APIProperty: persist
     * {Boolean} Leave the feature rendered until clear is called.  Default
     *     is false.  If set to true, the feature remains rendered until
     *     clear is called, typically by deactivating the handler or starting
     *     another drawing.
     */
    persist: false,

    /**
     * APIProperty: irregular
     * {Boolean} Draw an irregular polygon instead of a regular polygon.
     *     Default is false.  If true, the initial mouse down will represent
     *     one corner of the polygon bounds and with each mouse movement, the
     *     polygon will be stretched so the opposite corner of its bounds
     *     follows the mouse position.  This property takes precedence over
     *     the radius property.  If set to true, the radius property will
     *     be ignored.
     */
    irregular: false,

    /**
     * Property: angle
     * {Float} The angle from the origin (mouse down) to the current mouse
     *     position, in radians.  This is measured counterclockwise from the
     *     positive x-axis.
     */
    angle: null,

    /**
     * Property: fixedRadius
     * {Boolean} The polygon has a fixed radius.  True if a radius is set before
     *     drawing begins.  False otherwise.
     */
    fixedRadius: false,

    /**
     * Property: feature
     * {<OpenLayers.Feature.Vector>} The currently drawn polygon feature
     */
    feature: null,

    /**
     * Property: layer
     * {<OpenLayers.Layer.Vector>} The temporary drawing layer
     */
    layer: null,

    /**
     * Property: origin
     * {<OpenLayers.Geometry.Point>} Location of the first mouse down
     */
    origin: null,

    /**
     * Constructor: OpenLayers.Handler.RegularPolygon
     * Create a new regular polygon handler.
     *
     * Parameters:
     * control - {<OpenLayers.Control>} The control that owns this handler
     * callbacks - {Array} An object with a 'done' property whos value is a
     *     function to be called when the polygon drawing is finished.
     *     The callback should expect to recieve a single argument,
     *     the polygon geometry.  If the callbacks object contains a
     *     'cancel' property, this function will be called when the
     *     handler is deactivated while drawing.  The cancel should
     *     expect to receive a geometry.
     * options - {Object} An object with properties to be set on the handler.
     *     If the options.sides property is not specified, the number of sides
     *     will default to 4.
     */
    initialize: function(control, callbacks, options) {
        this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});

        OpenLayers.Handler.prototype.initialize.apply(this,
                                                [control, callbacks, options]);
        this.options = (options) ? options : new Object();
    },

    /**
     * APIMethod: setOptions
     *
     * Parameters:
     * newOptions - {Object}
     */
    setOptions: function (newOptions) {
        OpenLayers.Util.extend(this.options, newOptions);
        OpenLayers.Util.extend(this, newOptions);
    },

    /**
     * APIMethod: activate
     * Turn on the handler.
     *
     * Return:
     * {Boolean} The handler was successfully activated
     */
    activate: function() {
        var activated = false;
        if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
            // create temporary vector layer for rendering geometry sketch
            var options = {
                displayInLayerSwitcher: false,
                // indicate that the temp vector layer will never be out of range
                // without this, resolution properties must be specified at the
                // map-level for this temporary layer to init its resolutions
                // correctly
                calculateInRange: function() { return true; }
            };
            this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
            this.map.addLayer(this.layer);
            activated = true;
        }
        return activated;
    },

    /**
     * APIMethod: deactivate
     * Turn off the handler.
     *
     * Return:
     * {Boolean} The handler was successfully deactivated
     */
    deactivate: function() {
        var deactivated = false;
        if(OpenLayers.Handler.Drag.prototype.deactivate.apply(this, arguments)) {
            // call the cancel callback if mid-drawing
            if(this.dragging) {
                this.cancel();
            }
            // If a layer's map property is set to null, it means that that
            // layer isn't added to the map. Since we ourself added the layer
            // to the map in activate(), we can assume that if this.layer.map
            // is null it means that the layer has been destroyed (as a result
            // of map.destroy() for example.
            if (this.layer.map != null) {
                this.layer.destroy(false);
                if (this.feature) {
                    this.feature.destroy();
                }
            }
            this.layer = null;
            this.feature = null;
            deactivated = true;
        }
        return deactivated;
    },

    /**
     * Method: down
     * Start drawing a new feature
     *
     * Parameters:
     * evt - {Event} The drag start event
     */
    down: function(evt) {
        this.fixedRadius = !!(this.radius);
        var maploc = this.map.getLonLatFromPixel(evt.xy);
        this.origin = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
        // create the new polygon
        if(!this.fixedRadius || this.irregular) {
            // smallest radius should not be less one pixel in map units
            // VML doesn't behave well with smaller
            this.radius = this.map.getResolution();
        }
        if(this.persist) {
            this.clear();
        }
        this.feature = new OpenLayers.Feature.Vector();
        this.createGeometry();
        this.layer.addFeatures([this.feature], {silent: true});
        this.layer.drawFeature(this.feature, this.style);
    },

    /**
     * Method: move
     * Respond to drag move events
     *
     * Parameters:
     * evt - {Evt} The move event
     */
    move: function(evt) {
        var maploc = this.map.getLonLatFromPixel(evt.xy);
        var point = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
        if(this.irregular) {
            var ry = Math.sqrt(2) * Math.abs(point.y - this.origin.y) / 2;
            this.radius = Math.max(this.map.getResolution() / 2, ry);
        } else if(this.fixedRadius) {
            this.origin = point;
        } else {
            this.calculateAngle(point, evt);
            this.radius = Math.max(this.map.getResolution() / 2,
                                   point.distanceTo(this.origin));
        }
        this.modifyGeometry();
        if(this.irregular) {
            var dx = point.x - this.origin.x;
            var dy = point.y - this.origin.y;
            var ratio;
            if(dy == 0) {
                ratio = dx / (this.radius * Math.sqrt(2));
            } else {
                ratio = dx / dy;
            }
            this.feature.geometry.resize(1, this.origin, ratio);
            this.feature.geometry.move(dx / 2, dy / 2);
        }
        this.layer.drawFeature(this.feature, this.style);
    },

    /**
     * Method: up
     * Finish drawing the feature
     *
     * Parameters:
     * evt - {Event} The mouse up event
     */
    up: function(evt) {
        this.finalize();
        // the mouseup method of superclass doesn't call the
        // "done" callback if there's been no move between
        // down and up
        if (this.start == this.last) {
            this.callback("done", [evt.xy]);
        }
    },

    /**
     * Method: out
     * Finish drawing the feature.
     *
     * Parameters:
     * evt - {Event} The mouse out event
     */
    out: function(evt) {
        this.finalize();
    },

    /**
     * Method: createGeometry
     * Create the new polygon geometry.  This is called at the start of the
     *     drag and at any point during the drag if the number of sides
     *     changes.
     */
    createGeometry: function() {
        this.angle = Math.PI * ((1/this.sides) - (1/2));
        if(this.snapAngle) {
            this.angle += this.snapAngle * (Math.PI / 180);
        }
        this.feature.geometry = OpenLayers.Geometry.Polygon.createRegularPolygon(
            this.origin, this.radius, this.sides, this.snapAngle
        );
    },

    /**
     * Method: modifyGeometry
     * Modify the polygon geometry in place.
     */
    modifyGeometry: function() {
        var angle, dx, dy, point;
        var ring = this.feature.geometry.components[0];
        // if the number of sides ever changes, create a new geometry
        if(ring.components.length != (this.sides + 1)) {
            this.createGeometry();
             ring = this.feature.geometry.components[0];
        }
        for(var i=0; i<this.sides; ++i) {
            point = ring.components[i];
            angle = this.angle + (i * 2 * Math.PI / this.sides);
            point.x = this.origin.x + (this.radius * Math.cos(angle));
            point.y = this.origin.y + (this.radius * Math.sin(angle));
            point.clearBounds();
        }
    },

    /**
     * Method: calculateAngle
     * Calculate the angle based on settings.
     *
     * Parameters:
     * point - {<OpenLayers.Geometry.Point>}
     * evt - {Event}
     */
    calculateAngle: function(point, evt) {
        var alpha = Math.atan2(point.y - this.origin.y,
                               point.x - this.origin.x);
        if(this.snapAngle && (this.snapToggle && !evt[this.snapToggle])) {
            var snapAngleRad = (Math.PI / 180) * this.snapAngle;
            this.angle = Math.round(alpha / snapAngleRad) * snapAngleRad;
        } else {
            this.angle = alpha;
        }
    },

    /**
     * APIMethod: cancel
     * Finish the geometry and call the "cancel" callback.
     */
    cancel: function() {
        // the polygon geometry gets cloned in the callback method
        this.callback("cancel", null);
        this.finalize();
    },

    /**
     * Method: finalize
     * Finish the geometry and call the "done" callback.
     */
    finalize: function() {
        this.origin = null;
        this.radius = this.options.radius;
    },

    /**
     * APIMethod: clear
     * Clear any rendered features on the temporary layer.  This is called
     *     when the handler is deactivated, canceled, or done (unless persist
     *     is true).
     */
    clear: function() {
        this.layer.renderer.clear();
        this.layer.destroyFeatures();
    },

    /**
     * Method: callback
     * Trigger the control's named callback with the given arguments
     *
     * Parameters:
     * name - {String} The key for the callback that is one of the properties
     *     of the handler's callbacks object.
     * args - {Array} An array of arguments with which to call the callback
     *     (defined by the control).
     */
    callback: function (name, args) {
        // override the callback method to always send the polygon geometry
        if (this.callbacks[name]) {
            this.callbacks[name].apply(this.control,
                                       [this.feature.geometry.clone()]);
        }
        // since sketch features are added to the temporary layer
        // they must be cleared here if done or cancel
        if(!this.persist && (name == "done" || name == "cancel")) {
            this.clear();
        }
    },

    CLASS_NAME: "OpenLayers.Handler.RegularPolygon"
});
<commit_msg>Whitespace change only.<commit_after>/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
 * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * @requires OpenLayers/Handler/Drag.js
 */

/**
 * Class: OpenLayers.Handler.RegularPolygon
 * Handler to draw a regular polygon on the map.  Polygon is displayed on mouse
 *     down, moves or is modified on mouse move, and is finished on mouse up.
 *     The handler triggers callbacks for 'done' and 'cancel'.  Create a new
 *     instance with the <OpenLayers.Handler.RegularPolygon> constructor.
 *
 * Inherits from:
 *  - <OpenLayers.Handler>
 */
OpenLayers.Handler.RegularPolygon = OpenLayers.Class(OpenLayers.Handler.Drag, {

    /**
     * APIProperty: sides
     * {Integer} Number of sides for the regular polygon.  Needs to be greater
     *     than 2.  Defaults to 4.
     */
    sides: 4,

    /**
     * APIProperty: radius
     * {Float} Optional radius in map units of the regular polygon.  If this is
     *     set to some non-zero value, a polygon with a fixed radius will be
     *     drawn and dragged with mose movements.  If this property is not
     *     set, dragging changes the radius of the polygon.  Set to null by
     *     default.
     */
    radius: null,

    /**
     * APIProperty: snapAngle
     * {Float} If set to a non-zero value, the handler will snap the polygon
     *     rotation to multiples of the snapAngle.  Value is an angle measured
     *     in degrees counterclockwise from the positive x-axis.
     */
    snapAngle: null,

    /**
     * APIProperty: snapToggle
     * {String} If set, snapToggle is checked on mouse events and will set
     *     the snap mode to the opposite of what it currently is.  To disallow
     *     toggling between snap and non-snap mode, set freehandToggle to
     *     null.  Acceptable toggle values are 'shiftKey', 'ctrlKey', and
     *     'altKey'. Snap mode is only possible if this.snapAngle is set to a
     *     non-zero value.
     */
    snapToggle: 'shiftKey',

    /**
     * APIProperty: persist
     * {Boolean} Leave the feature rendered until clear is called.  Default
     *     is false.  If set to true, the feature remains rendered until
     *     clear is called, typically by deactivating the handler or starting
     *     another drawing.
     */
    persist: false,

    /**
     * APIProperty: irregular
     * {Boolean} Draw an irregular polygon instead of a regular polygon.
     *     Default is false.  If true, the initial mouse down will represent
     *     one corner of the polygon bounds and with each mouse movement, the
     *     polygon will be stretched so the opposite corner of its bounds
     *     follows the mouse position.  This property takes precedence over
     *     the radius property.  If set to true, the radius property will
     *     be ignored.
     */
    irregular: false,

    /**
     * Property: angle
     * {Float} The angle from the origin (mouse down) to the current mouse
     *     position, in radians.  This is measured counterclockwise from the
     *     positive x-axis.
     */
    angle: null,

    /**
     * Property: fixedRadius
     * {Boolean} The polygon has a fixed radius.  True if a radius is set before
     *     drawing begins.  False otherwise.
     */
    fixedRadius: false,

    /**
     * Property: feature
     * {<OpenLayers.Feature.Vector>} The currently drawn polygon feature
     */
    feature: null,

    /**
     * Property: layer
     * {<OpenLayers.Layer.Vector>} The temporary drawing layer
     */
    layer: null,

    /**
     * Property: origin
     * {<OpenLayers.Geometry.Point>} Location of the first mouse down
     */
    origin: null,

    /**
     * Constructor: OpenLayers.Handler.RegularPolygon
     * Create a new regular polygon handler.
     *
     * Parameters:
     * control - {<OpenLayers.Control>} The control that owns this handler
     * callbacks - {Array} An object with a 'done' property whos value is a
     *     function to be called when the polygon drawing is finished.
     *     The callback should expect to recieve a single argument,
     *     the polygon geometry.  If the callbacks object contains a
     *     'cancel' property, this function will be called when the
     *     handler is deactivated while drawing.  The cancel should
     *     expect to receive a geometry.
     * options - {Object} An object with properties to be set on the handler.
     *     If the options.sides property is not specified, the number of sides
     *     will default to 4.
     */
    initialize: function(control, callbacks, options) {
        this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});

        OpenLayers.Handler.prototype.initialize.apply(this,
                                                [control, callbacks, options]);
        this.options = (options) ? options : new Object();
    },

    /**
     * APIMethod: setOptions
     *
     * Parameters:
     * newOptions - {Object}
     */
    setOptions: function (newOptions) {
        OpenLayers.Util.extend(this.options, newOptions);
        OpenLayers.Util.extend(this, newOptions);
    },

    /**
     * APIMethod: activate
     * Turn on the handler.
     *
     * Return:
     * {Boolean} The handler was successfully activated
     */
    activate: function() {
        var activated = false;
        if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
            // create temporary vector layer for rendering geometry sketch
            var options = {
                displayInLayerSwitcher: false,
                // indicate that the temp vector layer will never be out of range
                // without this, resolution properties must be specified at the
                // map-level for this temporary layer to init its resolutions
                // correctly
                calculateInRange: function() { return true; }
            };
            this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
            this.map.addLayer(this.layer);
            activated = true;
        }
        return activated;
    },

    /**
     * APIMethod: deactivate
     * Turn off the handler.
     *
     * Return:
     * {Boolean} The handler was successfully deactivated
     */
    deactivate: function() {
        var deactivated = false;
        if(OpenLayers.Handler.Drag.prototype.deactivate.apply(this, arguments)) {
            // call the cancel callback if mid-drawing
            if(this.dragging) {
                this.cancel();
            }
            // If a layer's map property is set to null, it means that that
            // layer isn't added to the map. Since we ourself added the layer
            // to the map in activate(), we can assume that if this.layer.map
            // is null it means that the layer has been destroyed (as a result
            // of map.destroy() for example.
            if (this.layer.map != null) {
                this.layer.destroy(false);
                if (this.feature) {
                    this.feature.destroy();
                }
            }
            this.layer = null;
            this.feature = null;
            deactivated = true;
        }
        return deactivated;
    },

    /**
     * Method: down
     * Start drawing a new feature
     *
     * Parameters:
     * evt - {Event} The drag start event
     */
    down: function(evt) {
        this.fixedRadius = !!(this.radius);
        var maploc = this.map.getLonLatFromPixel(evt.xy);
        this.origin = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
        // create the new polygon
        if(!this.fixedRadius || this.irregular) {
            // smallest radius should not be less one pixel in map units
            // VML doesn't behave well with smaller
            this.radius = this.map.getResolution();
        }
        if(this.persist) {
            this.clear();
        }
        this.feature = new OpenLayers.Feature.Vector();
        this.createGeometry();
        this.layer.addFeatures([this.feature], {silent: true});
        this.layer.drawFeature(this.feature, this.style);
    },

    /**
     * Method: move
     * Respond to drag move events
     *
     * Parameters:
     * evt - {Evt} The move event
     */
    move: function(evt) {
        var maploc = this.map.getLonLatFromPixel(evt.xy);
        var point = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
        if(this.irregular) {
            var ry = Math.sqrt(2) * Math.abs(point.y - this.origin.y) / 2;
            this.radius = Math.max(this.map.getResolution() / 2, ry);
        } else if(this.fixedRadius) {
            this.origin = point;
        } else {
            this.calculateAngle(point, evt);
            this.radius = Math.max(this.map.getResolution() / 2,
                                   point.distanceTo(this.origin));
        }
        this.modifyGeometry();
        if(this.irregular) {
            var dx = point.x - this.origin.x;
            var dy = point.y - this.origin.y;
            var ratio;
            if(dy == 0) {
                ratio = dx / (this.radius * Math.sqrt(2));
            } else {
                ratio = dx / dy;
            }
            this.feature.geometry.resize(1, this.origin, ratio);
            this.feature.geometry.move(dx / 2, dy / 2);
        }
        this.layer.drawFeature(this.feature, this.style);
    },

    /**
     * Method: up
     * Finish drawing the feature
     *
     * Parameters:
     * evt - {Event} The mouse up event
     */
    up: function(evt) {
        this.finalize();
        // the mouseup method of superclass doesn't call the
        // "done" callback if there's been no move between
        // down and up
        if (this.start == this.last) {
            this.callback("done", [evt.xy]);
        }
    },

    /**
     * Method: out
     * Finish drawing the feature.
     *
     * Parameters:
     * evt - {Event} The mouse out event
     */
    out: function(evt) {
        this.finalize();
    },

    /**
     * Method: createGeometry
     * Create the new polygon geometry.  This is called at the start of the
     *     drag and at any point during the drag if the number of sides
     *     changes.
     */
    createGeometry: function() {
        this.angle = Math.PI * ((1/this.sides) - (1/2));
        if(this.snapAngle) {
            this.angle += this.snapAngle * (Math.PI / 180);
        }
        this.feature.geometry = OpenLayers.Geometry.Polygon.createRegularPolygon(
            this.origin, this.radius, this.sides, this.snapAngle
        );
    },

    /**
     * Method: modifyGeometry
     * Modify the polygon geometry in place.
     */
    modifyGeometry: function() {
        var angle, dx, dy, point;
        var ring = this.feature.geometry.components[0];
        // if the number of sides ever changes, create a new geometry
        if(ring.components.length != (this.sides + 1)) {
            this.createGeometry();
            ring = this.feature.geometry.components[0];
        }
        for(var i=0; i<this.sides; ++i) {
            point = ring.components[i];
            angle = this.angle + (i * 2 * Math.PI / this.sides);
            point.x = this.origin.x + (this.radius * Math.cos(angle));
            point.y = this.origin.y + (this.radius * Math.sin(angle));
            point.clearBounds();
        }
    },

    /**
     * Method: calculateAngle
     * Calculate the angle based on settings.
     *
     * Parameters:
     * point - {<OpenLayers.Geometry.Point>}
     * evt - {Event}
     */
    calculateAngle: function(point, evt) {
        var alpha = Math.atan2(point.y - this.origin.y,
                               point.x - this.origin.x);
        if(this.snapAngle && (this.snapToggle && !evt[this.snapToggle])) {
            var snapAngleRad = (Math.PI / 180) * this.snapAngle;
            this.angle = Math.round(alpha / snapAngleRad) * snapAngleRad;
        } else {
            this.angle = alpha;
        }
    },

    /**
     * APIMethod: cancel
     * Finish the geometry and call the "cancel" callback.
     */
    cancel: function() {
        // the polygon geometry gets cloned in the callback method
        this.callback("cancel", null);
        this.finalize();
    },

    /**
     * Method: finalize
     * Finish the geometry and call the "done" callback.
     */
    finalize: function() {
        this.origin = null;
        this.radius = this.options.radius;
    },

    /**
     * APIMethod: clear
     * Clear any rendered features on the temporary layer.  This is called
     *     when the handler is deactivated, canceled, or done (unless persist
     *     is true).
     */
    clear: function() {
        this.layer.renderer.clear();
        this.layer.destroyFeatures();
    },

    /**
     * Method: callback
     * Trigger the control's named callback with the given arguments
     *
     * Parameters:
     * name - {String} The key for the callback that is one of the properties
     *     of the handler's callbacks object.
     * args - {Array} An array of arguments with which to call the callback
     *     (defined by the control).
     */
    callback: function (name, args) {
        // override the callback method to always send the polygon geometry
        if (this.callbacks[name]) {
            this.callbacks[name].apply(this.control,
                                       [this.feature.geometry.clone()]);
        }
        // since sketch features are added to the temporary layer
        // they must be cleared here if done or cancel
        if(!this.persist && (name == "done" || name == "cancel")) {
            this.clear();
        }
    },

    CLASS_NAME: "OpenLayers.Handler.RegularPolygon"
});
<|endoftext|>


<commit_before>@import 'library/easing.js';
@import 'library/Animate.js';

var utils = new Utils();

function Utils() {
    // General values
    this.scriptName = 'AnimateMate';
    this.pluginDomain = "com.creatide.sketch.animatemate";
    this.sketchVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:"CFBundleShortVersionString"];
    this.scriptPath = null;
    this.scriptPathRoot = null;
    this.scriptResourcesPath = null;
    this.scriptLibraryPath = null,
    this.scriptURL = null;
    this.doc = null;
    this.page = null;
    this.artboard = null;
    this.selection = null;
    this.layers = null;
    this.layersCount = 0;
    this.answerBtn = [1000, false];
}

Utils.prototype.init = function (context, loopNestedGroups, forceContinue) {

    this.scriptPath = context.scriptPath;
    this.scriptPathRoot = this.scriptPath.stringByDeletingLastPathComponent();
    this.scriptResourcesPath = this.scriptPathRoot.stringByDeletingLastPathComponent() + '/Resources';
    this.scriptLibraryPath = this.scriptPathRoot + '/library';
    this.scriptURL = context.scriptURL;
    this.doc = context.document;
    this.selection = context.selection;
    this.page = this.doc.currentPage();
    this.artboard = this.page.currentArtboard();
    this.allLayersActive = false;
    this.api = context.api();

    if (this.artboard) {

        // Set artboard name
        this.artboardName = this.artboard.name();

        // Get artboard rect for size
        this.artboardRect = this.artboard.rect();
        this.artboardSize = {
            width: this.artboardRect.size.width,
            height: this.artboardRect.size.height
        };

        if (this.selection.count() > 0) {
            this.layers = this.selection;
            // If artboard is selected use all layers from that
            if (this.selection.firstObject().isMemberOfClass(MSArtboardGroup)) {
                this.layers = this.artboard.layers();
                this.allLayersActive = true;
            }
        }
        // If nothing is selected use all layers from artboard
        else {
            this.layers = this.artboard.layers();
            this.allLayersActive = true;
        }

        // Update layers count number
        this.layersCount = this.layers.count();

        // Init main animate object
        animate.init(this.layers, loopNestedGroups);

        // Force continue to return true even there is no animation layers or all layers is selected
        if (forceContinue) return true;

        // Initialize animation layers in conditional and check if there is all layers active
        if (!animate && this.allLayersActive) {
            dialog.createDialogMessage(3);
            return false;
        }

        return true;

    } else {
        // No artboard selected warning
        dialog.createDialogMessage(1);
        return false;
    }
};

Utils.prototype.getKeyframeNumber = function() {
    return this.api.settingForKey('AnimateMateFrame') || 0;
}

Utils.prototype.setKeyframeNumber = function(keyframe) {
    log('saved keyframe'+ keyframe);
    this.api.setSettingForKey('AnimateMateFrame', keyframe);
}

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

Utils.prototype.getRandomFloat = function (min, max, round) {
    var round = round || true,
        randomNum = Math.random() * (max - min) + min;
    if (round) randomNum = Math.round(randomNum * 100) / 100;
    return randomNum;
};

Utils.prototype.getRandomInt = function (min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
};

Utils.prototype.isNumeric = function (n) {
    return !isNaN(parseFloat(n)) && isFinite(n);
};

Utils.prototype.allNumbers = function (arr) {
    for (i in arr) {
        if (utils.isNumeric(arr[i])) return true;
    }
    return false;
};

Utils.prototype.arrayNext = function (arr, i) {
    return arr[++i];
};

Utils.prototype.arrayPrev = function (arr, i) {
    return arr[--i];
};

Utils.prototype.closestValueAbove = function(value, arr) {
    for (var i = 0; i < arr.length; i++){
        if (arr[i] > value) return arr[i];
    }
    return arr[arr.length-1] || 0;
};

Utils.prototype.closestValueBelow = function(value, arr) {
    for (var i = arr.length-1; i >= 0; i--){
        if (arr[i] < value) return arr[i];
    }
    return arr[0] || 0;
};

Utils.prototype.zeroPadding = function (num, places) {
    var zero = places - num.toString().length + 1;
    return Array(+(zero > 0 && zero)).join("0") + num;
};

// Remove target character from object properties
// Remove periods that NSTextField adds automatically to inputs in Gui.js (newItem.setStringValue())
Utils.prototype.objRemovePropertyCharacter = function (obj, removeChar) {
    for (var prop in obj) {
        obj[prop] = obj[prop].replace(removeChar, "");
    }
}

// http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-an-object
Utils.prototype.cloneObj = function (obj) {
    var target = {};
    for (var i in obj) {
        if (obj.hasOwnProperty(i)) {
            target[i] = obj[i];
        }
    }
    return target;
};

// http://stackoverflow.com/questions/979256/sorting-an-array-of-javascript-objects
Utils.prototype.sortBy = function (field, reverse, primer) {

    var key = primer ?
        function (x) {
            return primer(x[field])
        } :
        function (x) {
            return x[field]
        };

    reverse = !reverse ? 1 : -1;

    return function (a, b) {
        return a = key(a), b = key(b), reverse * ((a > b) - (b > a));
    }
};

// http://stackoverflow.com/questions/6913512/how-to-sort-an-array-of-objects-by-multiple-fields
Utils.prototype.sortByMulti = function () {
    var fields = [].slice.call(arguments),
        n_fields = fields.length;

    return function (A, B) {
        var a, b, field, key, primer, reverse, result, i;

        for (i = 0; i < n_fields; i++) {
            result = 0;
            field = fields[i];

            key = typeof field === 'string' ? field : field.name;

            a = A[key];
            b = B[key];

            if (typeof field.primer !== 'undefined') {
                a = field.primer(a);
                b = field.primer(b);
            }

            reverse = (field.reverse) ? -1 : 1;

            if (a < b) result = reverse * -1;
            if (a > b) result = reverse * 1;
            if (result !== 0) break;
        }
        return result;
    }
};

Utils.prototype.searchArrayIndex = function (array, value) {
    var arrayLength = array.length;
    for (var i = 0; i < arrayLength; i++) {
        if (array[i] == value) return i;
    }
    return null;
};

// http://stackoverflow.com/questions/7364150/find-object-by-id-in-array-of-javascript-objects
Utils.prototype.searchObjectArrayIndex = function (array, key, value) {
    var arrayLength = array.length;
    for (var i = 0; i < arrayLength; i++) {
        if (array[i][key] == value) return i;
    }
    return null;
};

// http://stackoverflow.com/questions/5612787/converting-an-object-to-a-string
Utils.prototype.objToString = function (obj, incPropertyName) {
    var str = '';
    for (var p in obj) {
        if (obj.hasOwnProperty(p)) {
            if (incPropertyName) str += p + ':';
            str += obj[p] + ',';
        }
    }
    str = str.slice(0, -1);
    return str;
};

// http://stackoverflow.com/questions/8072323/best-way-to-prevent-handle-divide-by-0-in-javascript
Utils.prototype.notZero = function (n) {
    n = +n;
    if (!n) {
        n = 0;
    }
    return n;
};

// Convert all values to float and round by two decimals
Utils.prototype.objValuesToFloat = function (obj) {
    for (var p in obj) {
        try {
            obj[p] = Math.round(parseFloat(obj[p]) * 100) / 100;
        } catch (e) {
            log(e);
        }
    }
};

Utils.prototype.uniqueNumber = function (a) {
    return a.sort().filter(function (item, pos, ary) {
        return !pos || item != ary[pos - 1];
    })
};

// http://stackoverflow.com/questions/3971841/how-to-resize-images-proportionally-keeping-the-aspect-ratio
Utils.prototype.getAspectRatio = function (srcWidth, srcHeight, maxWidth, maxHeight) {
    var ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
    return {
        width: srcWidth * ratio,
        height: srcHeight * ratio
    };
};

Utils.prototype.formatTime = function (ms) {
    var days, hours, minutes, seconds, milliseconds;
    milliseconds = Math.floor((ms / 10) % 100);
    seconds = Math.floor(((ms / 1000) % 60));
    minutes = Math.floor((((ms / 1000) / 60) % 60));
    hours = Math.floor(((((ms / 1000) / 60) / 60) % 24));

    if (hours < 10) {
        hours = "0" + hours;
    }
    if (minutes < 10) {
        minutes = "0" + minutes;
    }
    if (seconds < 10) {
        seconds = "0" + seconds;
    }
    if (milliseconds < 10) {
        milliseconds = "0" + milliseconds;
    }
    var time = hours + ':' + minutes + ':' + seconds + ':' + milliseconds;
    return time;
};

// Based to https://stackoverflow.com/questions/15523514/find-by-key-deep-in-nested-json-object
// Used to find only objects that have specific properties even it's inside of property array
// incSearchIndes: this value will give index numbers where index values target is located
Utils.prototype.findObjByProperty = function (obj, searchProperty, arrayName, incSearchIndex) {

    var arrayName = arrayName || null;
    var result = [];

    function getObject(refObj, foundId) {
        if (refObj instanceof Array) {
            for (var i = 0; i < refObj.length; i++) {
                getObject(refObj[i], i);
            }
        } else {
            // Loop every objects
            for (var prop in refObj) {

                // Search only from array by names
                if (prop == arrayName || arrayName == null) {

                    var refObjArr = refObj[prop];
                    var refObjArrLength = refObjArr.length;

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

                        for (var propName in refObjArr[i]) {

                            if (propName == searchProperty) {
                                if (incSearchIndex) {
                                    refObjArr[i]["searchId"] = [foundId, i];
                                }
                                result.push(refObjArr[i]);
                            }
                        }
                    }
                }
            }
        }
    }
    getObject(obj, undefined);
    return result;
};

// ---------------------------------------- //
//                   Debug                  //
// ---------------------------------------- //

Utils.prototype.logObjProperties = function (obj) {
    for (var p in obj) {
        log(p + ": " + obj[p]);
    }
};

Utils.prototype.benchmarkTime = {
    startTime: null,
    intervalTime: null,
    endTime: null,
    start: function () {
        this.startTime = new Date();
        this.intervalTime = this.startTime;
    },
    interval: function () {
        var currentTime = null,
            soloRenderTime = null,
            renderTimePoint = null;
        currentTime = new Date();
        soloRenderTime = Math.abs(this.intervalTime - currentTime);
        renderTimePoint = Math.abs(this.intervalTime - this.startTime) + soloRenderTime;
        this.intervalTime = currentTime;
        return [utils.formatTime(soloRenderTime), utils.formatTime(renderTimePoint)];
    },
    stop: function () {
        this.endTime = new Date();
        return utils.formatTime(Math.abs(this.endTime - this.startTime));
    }
};

Utils.prototype.benchmarkLoop = {
    startTime: null,
    endTime: null,
    runStatus: false,
    start: function (currentIndex, endIndex, logIndexes) {
        if (logIndexes) {
            var tempTime = new Date();
            log(utils.scriptName + " INDEX: " + currentIndex + " TIME: " + utils.formatTime(Math.abs(tempTime - this.startTime)));
        }
        if (!this.runStatus) {
            this.runStatus = true;
            this.startTime = new Date();
        }
        if (currentIndex == endIndex - 1) {
            this.endTime = new Date();
            log(utils.scriptName + " loop benchmark time: " + utils.formatTime(Math.abs(this.endTime - this.startTime)));
            this.reset();
        }
    },
    reset: function () {
        this.startTime = null;
        this.endTime = null;
        this.runStatus = false;
    }
};
<commit_msg>Fixed crash when launching on new api<commit_after>const Settings = require('sketch/settings');
@import 'library/easing.js';
@import 'library/Animate.js';

var utils = new Utils();

function Utils() {
    // General values
    this.scriptName = 'AnimateMate';
    this.pluginDomain = "com.creatide.sketch.animatemate";
    this.sketchVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:"CFBundleShortVersionString"];
    this.scriptPath = null;
    this.scriptPathRoot = null;
    this.scriptResourcesPath = null;
    this.scriptLibraryPath = null,
    this.scriptURL = null;
    this.doc = null;
    this.page = null;
    this.artboard = null;
    this.selection = null;
    this.layers = null;
    this.layersCount = 0;
    this.answerBtn = [1000, false];
}

Utils.prototype.init = function (context, loopNestedGroups, forceContinue) {

    this.scriptPath = context.scriptPath;
    this.scriptPathRoot = this.scriptPath.stringByDeletingLastPathComponent();
    this.scriptResourcesPath = this.scriptPathRoot.stringByDeletingLastPathComponent() + '/Resources';
    this.scriptLibraryPath = this.scriptPathRoot + '/library';
    this.scriptURL = context.scriptURL;
    this.doc = context.document;
    this.selection = context.selection;
    this.page = this.doc.currentPage();
    this.artboard = this.page.currentArtboard();
    this.allLayersActive = false;

    if (this.artboard) {

        // Set artboard name
        this.artboardName = this.artboard.name();

        // Get artboard rect for size
        this.artboardRect = this.artboard.rect();
        this.artboardSize = {
            width: this.artboardRect.size.width,
            height: this.artboardRect.size.height
        };

        if (this.selection.count() > 0) {
            this.layers = this.selection;
            // If artboard is selected use all layers from that
            if (this.selection.firstObject().isMemberOfClass(MSArtboardGroup)) {
                this.layers = this.artboard.layers();
                this.allLayersActive = true;
            }
        }
        // If nothing is selected use all layers from artboard
        else {
            this.layers = this.artboard.layers();
            this.allLayersActive = true;
        }

        // Update layers count number
        this.layersCount = this.layers.count();

        // Init main animate object
        animate.init(this.layers, loopNestedGroups);

        // Force continue to return true even there is no animation layers or all layers is selected
        if (forceContinue) return true;

        // Initialize animation layers in conditional and check if there is all layers active
        if (!animate && this.allLayersActive) {
            dialog.createDialogMessage(3);
            return false;
        }

        return true;

    } else {
        // No artboard selected warning
        dialog.createDialogMessage(1);
        return false;
    }
};

Utils.prototype.getKeyframeNumber = function() {
    return Settings.settingForKey('AnimateMateFrame') || 0;
}

Utils.prototype.setKeyframeNumber = function(keyframe) {
    log('saved keyframe'+ keyframe);
    Settings.setSettingForKey('AnimateMateFrame', keyframe);
}

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

Utils.prototype.getRandomFloat = function (min, max, round) {
    var round = round || true,
        randomNum = Math.random() * (max - min) + min;
    if (round) randomNum = Math.round(randomNum * 100) / 100;
    return randomNum;
};

Utils.prototype.getRandomInt = function (min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
};

Utils.prototype.isNumeric = function (n) {
    return !isNaN(parseFloat(n)) && isFinite(n);
};

Utils.prototype.allNumbers = function (arr) {
    for (i in arr) {
        if (utils.isNumeric(arr[i])) return true;
    }
    return false;
};

Utils.prototype.arrayNext = function (arr, i) {
    return arr[++i];
};

Utils.prototype.arrayPrev = function (arr, i) {
    return arr[--i];
};

Utils.prototype.closestValueAbove = function(value, arr) {
    for (var i = 0; i < arr.length; i++){
        if (arr[i] > value) return arr[i];
    }
    return arr[arr.length-1] || 0;
};

Utils.prototype.closestValueBelow = function(value, arr) {
    for (var i = arr.length-1; i >= 0; i--){
        if (arr[i] < value) return arr[i];
    }
    return arr[0] || 0;
};

Utils.prototype.zeroPadding = function (num, places) {
    var zero = places - num.toString().length + 1;
    return Array(+(zero > 0 && zero)).join("0") + num;
};

// Remove target character from object properties
// Remove periods that NSTextField adds automatically to inputs in Gui.js (newItem.setStringValue())
Utils.prototype.objRemovePropertyCharacter = function (obj, removeChar) {
    for (var prop in obj) {
        obj[prop] = obj[prop].replace(removeChar, "");
    }
}

// http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-an-object
Utils.prototype.cloneObj = function (obj) {
    var target = {};
    for (var i in obj) {
        if (obj.hasOwnProperty(i)) {
            target[i] = obj[i];
        }
    }
    return target;
};

// http://stackoverflow.com/questions/979256/sorting-an-array-of-javascript-objects
Utils.prototype.sortBy = function (field, reverse, primer) {

    var key = primer ?
        function (x) {
            return primer(x[field])
        } :
        function (x) {
            return x[field]
        };

    reverse = !reverse ? 1 : -1;

    return function (a, b) {
        return a = key(a), b = key(b), reverse * ((a > b) - (b > a));
    }
};

// http://stackoverflow.com/questions/6913512/how-to-sort-an-array-of-objects-by-multiple-fields
Utils.prototype.sortByMulti = function () {
    var fields = [].slice.call(arguments),
        n_fields = fields.length;

    return function (A, B) {
        var a, b, field, key, primer, reverse, result, i;

        for (i = 0; i < n_fields; i++) {
            result = 0;
            field = fields[i];

            key = typeof field === 'string' ? field : field.name;

            a = A[key];
            b = B[key];

            if (typeof field.primer !== 'undefined') {
                a = field.primer(a);
                b = field.primer(b);
            }

            reverse = (field.reverse) ? -1 : 1;

            if (a < b) result = reverse * -1;
            if (a > b) result = reverse * 1;
            if (result !== 0) break;
        }
        return result;
    }
};

Utils.prototype.searchArrayIndex = function (array, value) {
    var arrayLength = array.length;
    for (var i = 0; i < arrayLength; i++) {
        if (array[i] == value) return i;
    }
    return null;
};

// http://stackoverflow.com/questions/7364150/find-object-by-id-in-array-of-javascript-objects
Utils.prototype.searchObjectArrayIndex = function (array, key, value) {
    var arrayLength = array.length;
    for (var i = 0; i < arrayLength; i++) {
        if (array[i][key] == value) return i;
    }
    return null;
};

// http://stackoverflow.com/questions/5612787/converting-an-object-to-a-string
Utils.prototype.objToString = function (obj, incPropertyName) {
    var str = '';
    for (var p in obj) {
        if (obj.hasOwnProperty(p)) {
            if (incPropertyName) str += p + ':';
            str += obj[p] + ',';
        }
    }
    str = str.slice(0, -1);
    return str;
};

// http://stackoverflow.com/questions/8072323/best-way-to-prevent-handle-divide-by-0-in-javascript
Utils.prototype.notZero = function (n) {
    n = +n;
    if (!n) {
        n = 0;
    }
    return n;
};

// Convert all values to float and round by two decimals
Utils.prototype.objValuesToFloat = function (obj) {
    for (var p in obj) {
        try {
            obj[p] = Math.round(parseFloat(obj[p]) * 100) / 100;
        } catch (e) {
            log(e);
        }
    }
};

Utils.prototype.uniqueNumber = function (a) {
    return a.sort().filter(function (item, pos, ary) {
        return !pos || item != ary[pos - 1];
    })
};

// http://stackoverflow.com/questions/3971841/how-to-resize-images-proportionally-keeping-the-aspect-ratio
Utils.prototype.getAspectRatio = function (srcWidth, srcHeight, maxWidth, maxHeight) {
    var ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
    return {
        width: srcWidth * ratio,
        height: srcHeight * ratio
    };
};

Utils.prototype.formatTime = function (ms) {
    var days, hours, minutes, seconds, milliseconds;
    milliseconds = Math.floor((ms / 10) % 100);
    seconds = Math.floor(((ms / 1000) % 60));
    minutes = Math.floor((((ms / 1000) / 60) % 60));
    hours = Math.floor(((((ms / 1000) / 60) / 60) % 24));

    if (hours < 10) {
        hours = "0" + hours;
    }
    if (minutes < 10) {
        minutes = "0" + minutes;
    }
    if (seconds < 10) {
        seconds = "0" + seconds;
    }
    if (milliseconds < 10) {
        milliseconds = "0" + milliseconds;
    }
    var time = hours + ':' + minutes + ':' + seconds + ':' + milliseconds;
    return time;
};

// Based to https://stackoverflow.com/questions/15523514/find-by-key-deep-in-nested-json-object
// Used to find only objects that have specific properties even it's inside of property array
// incSearchIndes: this value will give index numbers where index values target is located
Utils.prototype.findObjByProperty = function (obj, searchProperty, arrayName, incSearchIndex) {

    var arrayName = arrayName || null;
    var result = [];

    function getObject(refObj, foundId) {
        if (refObj instanceof Array) {
            for (var i = 0; i < refObj.length; i++) {
                getObject(refObj[i], i);
            }
        } else {
            // Loop every objects
            for (var prop in refObj) {

                // Search only from array by names
                if (prop == arrayName || arrayName == null) {

                    var refObjArr = refObj[prop];
                    var refObjArrLength = refObjArr.length;

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

                        for (var propName in refObjArr[i]) {

                            if (propName == searchProperty) {
                                if (incSearchIndex) {
                                    refObjArr[i]["searchId"] = [foundId, i];
                                }
                                result.push(refObjArr[i]);
                            }
                        }
                    }
                }
            }
        }
    }
    getObject(obj, undefined);
    return result;
};

// ---------------------------------------- //
//                   Debug                  //
// ---------------------------------------- //

Utils.prototype.logObjProperties = function (obj) {
    for (var p in obj) {
        log(p + ": " + obj[p]);
    }
};

Utils.prototype.benchmarkTime = {
    startTime: null,
    intervalTime: null,
    endTime: null,
    start: function () {
        this.startTime = new Date();
        this.intervalTime = this.startTime;
    },
    interval: function () {
        var currentTime = null,
            soloRenderTime = null,
            renderTimePoint = null;
        currentTime = new Date();
        soloRenderTime = Math.abs(this.intervalTime - currentTime);
        renderTimePoint = Math.abs(this.intervalTime - this.startTime) + soloRenderTime;
        this.intervalTime = currentTime;
        return [utils.formatTime(soloRenderTime), utils.formatTime(renderTimePoint)];
    },
    stop: function () {
        this.endTime = new Date();
        return utils.formatTime(Math.abs(this.endTime - this.startTime));
    }
};

Utils.prototype.benchmarkLoop = {
    startTime: null,
    endTime: null,
    runStatus: false,
    start: function (currentIndex, endIndex, logIndexes) {
        if (logIndexes) {
            var tempTime = new Date();
            log(utils.scriptName + " INDEX: " + currentIndex + " TIME: " + utils.formatTime(Math.abs(tempTime - this.startTime)));
        }
        if (!this.runStatus) {
            this.runStatus = true;
            this.startTime = new Date();
        }
        if (currentIndex == endIndex - 1) {
            this.endTime = new Date();
            log(utils.scriptName + " loop benchmark time: " + utils.formatTime(Math.abs(this.endTime - this.startTime)));
            this.reset();
        }
    },
    reset: function () {
        this.startTime = null;
        this.endTime = null;
        this.runStatus = false;
    }
};
<|endoftext|>


<commit_before>/*global window, console, RTCSessionDescription, RoapConnection, webkitRTCPeerConnection*/

var Erizo = Erizo || {};

Erizo.ChromeStableStack = function (spec) {
    "use strict";

    var that = {},
        WebkitRTCPeerConnection = webkitRTCPeerConnection;

    that.pc_config = {
        "iceServers": []
    };

    that.con = {'optional': [{'DtlsSrtpKeyAgreement': true}]};

    if (spec.stunServerUrl !== undefined) {
        that.pc_config.iceServers.push({"url": spec.stunServerUrl});
    }

    if ((spec.turnServer || {}).url) {
        that.pc_config.iceServers.push({"username": spec.turnServer.username, "credential": spec.turnServer.password, "url": spec.turnServer.url});
    }

    if (spec.audio === undefined || spec.nop2p) {
        spec.audio = true;
    }

    if (spec.video === undefined || spec.nop2p) {
        spec.video = true;
    }

    that.mediaConstraints = {
        'mandatory': {
            'OfferToReceiveVideo': spec.video,
            'OfferToReceiveAudio': spec.audio
        }
    };

    that.roapSessionId = 103;

    that.peerConnection = new WebkitRTCPeerConnection(that.pc_config, that.con);

    that.peerConnection.onicecandidate = function (event) {
        L.Logger.debug("PeerConnection: ", spec.session_id);
        if (!event.candidate) {
            // At the moment, we do not renegotiate when new candidates
            // show up after the more flag has been false once.
            L.Logger.debug("State: " + that.peerConnection.iceGatheringState);

            if (that.ices === undefined) {
                that.ices = 0;
            }
            that.ices = that.ices + 1;
            if (that.ices >= 1 && that.moreIceComing) {
                that.moreIceComing = false;
                that.markActionNeeded();
            }
        } else {
            that.iceCandidateCount += 1;
        }
    };

    //L.Logger.debug("Created webkitRTCPeerConnnection with config \"" + JSON.stringify(that.pc_config) + "\".");

    var setMaxBW = function (sdp) {
        if (spec.maxVideoBW) {
            var a = sdp.match(/m=video.*\r\n/);
            var r = a[0] + "b=AS:" + spec.maxVideoBW + "\r\n";
            sdp = sdp.replace(a[0], r);
        }

        if (spec.maxAudioBW) {
            var a = sdp.match(/m=audio.*\r\n/);
            var r = a[0] + "b=AS:" + spec.maxAudioBW + "\r\n";
            sdp = sdp.replace(a[0], r);
        }

        return sdp;
    };

    /**
     * This function processes signalling messages from the other side.
     * @param {string} msgstring JSON-formatted string containing a ROAP message.
     */
    that.processSignalingMessage = function (msgstring) {
        // Offer: Check for glare and resolve.
        // Answer/OK: Remove retransmit for the msg this is an answer to.
        // Send back "OK" if this was an Answer.
        L.Logger.debug('Activity on conn ' + that.sessionId);
        var msg = JSON.parse(msgstring), sd, regExp, exp;
        that.incomingMessage = msg;

        if (that.state === 'new') {
            if (msg.messageType === 'OFFER') {
                // Initial offer.
                sd = {
                    sdp: msg.sdp,
                    type: 'offer'
                };
                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));

                that.state = 'offer-received';
                // Allow other stuff to happen, then reply.
                that.markActionNeeded();
            } else {
                that.error('Illegal message for this state: ' + msg.messageType + ' in state ' + that.state);
            }

        } else if (that.state === 'offer-sent') {
            if (msg.messageType === 'ANSWER') {

                //regExp = new RegExp(/m=video[\w\W]*\r\n/g);

                //exp = msg.sdp.match(regExp);
                //L.Logger.debug(exp);

                //msg.sdp = msg.sdp.replace(regExp, exp + "b=AS:100\r\n");

                sd = {
                    sdp: msg.sdp,
                    type: 'answer'
                };
                L.Logger.debug("Received ANSWER: ", sd.sdp);

                sd.sdp = setMaxBW(sd.sdp);

                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));
                that.sendOK();
                that.state = 'established';

            } else if (msg.messageType === 'pr-answer') {
                sd = {
                    sdp: msg.sdp,
                    type: 'pr-answer'
                };
                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));

                // No change to state, and no response.
            } else if (msg.messageType === 'offer') {
                // Glare processing.
                that.error('Not written yet');
            } else {
                that.error('Illegal message for this state: ' + msg.messageType + ' in state ' + that.state);
            }

        } else if (that.state === 'established') {
            if (msg.messageType === 'OFFER') {
                // Subsequent offer.
                sd = {
                    sdp: msg.sdp,
                    type: 'offer'
                };
                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));

                that.state = 'offer-received';
                // Allow other stuff to happen, then reply.
                that.markActionNeeded();
            } else {
                that.error('Illegal message for this state: ' + msg.messageType + ' in state ' + that.state);
            }
        }
    };

    /**
     * Adds a stream - this causes signalling to happen, if needed.
     * @param {MediaStream} stream The outgoing MediaStream to add.
     */
    that.addStream = function (stream) {
        that.peerConnection.addStream(stream);
        that.markActionNeeded();
    };

    /**
     * Removes a stream.
     * @param {MediaStream} stream The MediaStream to remove.
     */
    that.removeStream = function (stream) {
//        var i;
//        for (i = 0; i < that.peerConnection.localStreams.length; ++i) {
//            if (that.localStreams[i] === stream) {
//                that.localStreams[i] = null;
//            }
//        }
        that.markActionNeeded();
    };

    /**
     * Closes the connection.
     */
    that.close = function () {
        that.state = 'closed';
        that.peerConnection.close();
    };

    /**
     * Internal function: Mark that something happened.
     */
    that.markActionNeeded = function () {
        that.actionNeeded = true;
        that.doLater(function () {
            that.onstablestate();
        });
    };

    /**
     * Internal function: Do something later (not on this stack).
     * @param {function} what Callback to be executed later.
     */
    that.doLater = function (what) {
        // Post an event to myself so that I get called a while later.
        // (needs more JS/DOM info. Just call the processing function on a delay
        // for now.)
        window.setTimeout(what, 1);
    };

    /**
     * Internal function called when a stable state
     * is entered by the browser (to allow for multiple AddStream calls or
     * other interesting actions).
     * This function will generate an offer or answer, as needed, and send
     * to the remote party using our onsignalingmessage function.
     */
    that.onstablestate = function () {
        var mySDP, roapMessage = {};
        if (that.actionNeeded) {
            if (that.state === 'new' || that.state === 'established') {
                // See if the current offer is the same as what we already sent.
                // If not, no change is needed.

                that.peerConnection.createOffer(function (sessionDescription) {

                    //sessionDescription.sdp = newOffer.replace(/a=ice-options:google-ice\r\n/g, "");
                    //sessionDescription.sdp = newOffer.replace(/a=crypto:0 AES_CM_128_HMAC_SHA1_80 inline:.*\r\n/g, "a=crypto:0 AES_CM_128_HMAC_SHA1_80 inline:eUMxlV2Ib6U8qeZot/wEKHw9iMzfKUYpOPJrNnu3\r\n");
                    //sessionDescription.sdp = newOffer.replace(/a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:.*\r\n/g, "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:eUMxlV2Ib6U8qeZot/wEKHw9iMzfKUYpOPJrNnu3\r\n");

                    sessionDescription.sdp = setMaxBW(sessionDescription.sdp);
                    sessionDescription.sdp = sessionDescription.sdp.replace(/a=rtpmap:100 VP8\/90000/g,"a=rtpmap:120 VP8/90000");
                    sessionDescription.sdp = sessionDescription.sdp.replace(/a=fmtp:96 apt=100/g,"a=fmtp:96 apt=120");
                    sessionDescription.sdp = sessionDescription.sdp.replace(/m=video 1 RTP\/SAVPF 100/g,"m=video 1 RTP/SAVPF 120");
                    sessionDescription.sdp = sessionDescription.sdp.replace(/a=rtcp-fb:100/g,"a=rtcp-fb:120");
                    L.Logger.debug("Changed", sessionDescription.sdp);

                    var newOffer = sessionDescription.sdp;

                    if (newOffer !== that.prevOffer) {

                        that.peerConnection.setLocalDescription(sessionDescription);

                        that.state = 'preparing-offer';
                        that.markActionNeeded();
                        return;
                    } else {
                        L.Logger.debug('Not sending a new offer');
                    }

                }, null, that.mediaConstraints);

            } else if (that.state === 'preparing-offer') {
                // Don't do anything until we have the ICE candidates.
                if (that.moreIceComing) {
                    return;
                }

                // Now able to send the offer we've already prepared.
                that.prevOffer = that.peerConnection.localDescription.sdp;
                L.Logger.debug("Sending OFFER: " + that.prevOffer);
                //L.Logger.debug('Sent SDP is ' + that.prevOffer);
                that.sendMessage('OFFER', that.prevOffer);
                // Not done: Retransmission on non-response.
                that.state = 'offer-sent';

            } else if (that.state === 'offer-received') {

                that.peerConnection.createAnswer(function (sessionDescription) {
                    that.peerConnection.setLocalDescription(sessionDescription);
                    that.state = 'offer-received-preparing-answer';

                    if (!that.iceStarted) {
                        var now = new Date();
                        L.Logger.debug(now.getTime() + ': Starting ICE in responder');
                        that.iceStarted = true;
                    } else {
                        that.markActionNeeded();
                        return;
                    }

                }, null, that.mediaConstraints);

            } else if (that.state === 'offer-received-preparing-answer') {
                if (that.moreIceComing) {
                    return;
                }

                mySDP = that.peerConnection.localDescription.sdp;

                that.sendMessage('ANSWER', mySDP);
                that.state = 'established';
            } else {
                that.error('Dazed and confused in state ' + that.state + ', stopping here');
            }
            that.actionNeeded = false;
        }
    };

    /**
     * Internal function to send an "OK" message.
     */
    that.sendOK = function () {
        that.sendMessage('OK');
    };

    /**
     * Internal function to send a signalling message.
     * @param {string} operation What operation to signal.
     * @param {string} sdp SDP message body.
     */
    that.sendMessage = function (operation, sdp) {
        var roapMessage = {};
        roapMessage.messageType = operation;
        roapMessage.sdp = sdp; // may be null or undefined
        if (operation === 'OFFER') {
            roapMessage.offererSessionId = that.sessionId;
            roapMessage.answererSessionId = that.otherSessionId; // may be null
            roapMessage.seq = (that.sequenceNumber += 1);
            // The tiebreaker needs to be neither 0 nor 429496725.
            roapMessage.tiebreaker = Math.floor(Math.random() * 429496723 + 1);
        } else {
            roapMessage.offererSessionId = that.incomingMessage.offererSessionId;
            roapMessage.answererSessionId = that.sessionId;
            roapMessage.seq = that.incomingMessage.seq;
        }
        that.onsignalingmessage(JSON.stringify(roapMessage));
    };

    /**
     * Internal something-bad-happened function.
     * @param {string} text What happened - suitable for logging.
     */
    that.error = function (text) {
        throw 'Error in RoapOnJsep: ' + text;
    };

    that.sessionId = (that.roapSessionId += 1);
    that.sequenceNumber = 0; // Number of last ROAP message sent. Starts at 1.
    that.actionNeeded = false;
    that.iceStarted = false;
    that.moreIceComing = true;
    that.iceCandidateCount = 0;
    that.onsignalingmessage = spec.callback;

    that.peerConnection.onopen = function () {
        if (that.onopen) {
            that.onopen();
        }
    };

    that.peerConnection.onaddstream = function (stream) {
        if (that.onaddstream) {
            that.onaddstream(stream);
        }
    };

    that.peerConnection.onremovestream = function (stream) {
        if (that.onremovestream) {
            that.onremovestream(stream);
        }
    };

    that.peerConnection.oniceconnectionstatechange = function (e) {
        if (that.oniceconnectionstatechange) {
            that.oniceconnectionstatechange(e.currentTarget.iceConnectionState);
        }
    };

    // Variables that are part of the public interface of PeerConnection
    // in the 28 January 2012 version of the webrtc specification.
    that.onaddstream = null;
    that.onremovestream = null;
    that.state = 'new';
    // Auto-fire next events.
    that.markActionNeeded();
    return that;
};
<commit_msg>Fixed compatibility with Chrome, changes to sdp were still being made for firefox compat, they are not needed<commit_after>/*global window, console, RTCSessionDescription, RoapConnection, webkitRTCPeerConnection*/

var Erizo = Erizo || {};

Erizo.ChromeStableStack = function (spec) {
    "use strict";

    var that = {},
        WebkitRTCPeerConnection = webkitRTCPeerConnection;

    that.pc_config = {
        "iceServers": []
    };

    that.con = {'optional': [{'DtlsSrtpKeyAgreement': true}]};

    if (spec.stunServerUrl !== undefined) {
        that.pc_config.iceServers.push({"url": spec.stunServerUrl});
    }

    if ((spec.turnServer || {}).url) {
        that.pc_config.iceServers.push({"username": spec.turnServer.username, "credential": spec.turnServer.password, "url": spec.turnServer.url});
    }

    if (spec.audio === undefined || spec.nop2p) {
        spec.audio = true;
    }

    if (spec.video === undefined || spec.nop2p) {
        spec.video = true;
    }

    that.mediaConstraints = {
        'mandatory': {
            'OfferToReceiveVideo': spec.video,
            'OfferToReceiveAudio': spec.audio
        }
    };

    that.roapSessionId = 103;

    that.peerConnection = new WebkitRTCPeerConnection(that.pc_config, that.con);

    that.peerConnection.onicecandidate = function (event) {
        L.Logger.debug("PeerConnection: ", spec.session_id);
        if (!event.candidate) {
            // At the moment, we do not renegotiate when new candidates
            // show up after the more flag has been false once.
            L.Logger.debug("State: " + that.peerConnection.iceGatheringState);

            if (that.ices === undefined) {
                that.ices = 0;
            }
            that.ices = that.ices + 1;
            if (that.ices >= 1 && that.moreIceComing) {
                that.moreIceComing = false;
                that.markActionNeeded();
            }
        } else {
            that.iceCandidateCount += 1;
        }
    };

    //L.Logger.debug("Created webkitRTCPeerConnnection with config \"" + JSON.stringify(that.pc_config) + "\".");

    var setMaxBW = function (sdp) {
        if (spec.maxVideoBW) {
            var a = sdp.match(/m=video.*\r\n/);
            var r = a[0] + "b=AS:" + spec.maxVideoBW + "\r\n";
            sdp = sdp.replace(a[0], r);
        }

        if (spec.maxAudioBW) {
            var a = sdp.match(/m=audio.*\r\n/);
            var r = a[0] + "b=AS:" + spec.maxAudioBW + "\r\n";
            sdp = sdp.replace(a[0], r);
        }

        return sdp;
    };

    /**
     * This function processes signalling messages from the other side.
     * @param {string} msgstring JSON-formatted string containing a ROAP message.
     */
    that.processSignalingMessage = function (msgstring) {
        // Offer: Check for glare and resolve.
        // Answer/OK: Remove retransmit for the msg this is an answer to.
        // Send back "OK" if this was an Answer.
        L.Logger.debug('Activity on conn ' + that.sessionId);
        var msg = JSON.parse(msgstring), sd, regExp, exp;
        that.incomingMessage = msg;

        if (that.state === 'new') {
            if (msg.messageType === 'OFFER') {
                // Initial offer.
                sd = {
                    sdp: msg.sdp,
                    type: 'offer'
                };
                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));

                that.state = 'offer-received';
                // Allow other stuff to happen, then reply.
                that.markActionNeeded();
            } else {
                that.error('Illegal message for this state: ' + msg.messageType + ' in state ' + that.state);
            }

        } else if (that.state === 'offer-sent') {
            if (msg.messageType === 'ANSWER') {

                //regExp = new RegExp(/m=video[\w\W]*\r\n/g);

                //exp = msg.sdp.match(regExp);
                //L.Logger.debug(exp);

                //msg.sdp = msg.sdp.replace(regExp, exp + "b=AS:100\r\n");

                sd = {
                    sdp: msg.sdp,
                    type: 'answer'
                };
                L.Logger.debug("Received ANSWER: ", sd.sdp);

                sd.sdp = setMaxBW(sd.sdp);

                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));
                that.sendOK();
                that.state = 'established';

            } else if (msg.messageType === 'pr-answer') {
                sd = {
                    sdp: msg.sdp,
                    type: 'pr-answer'
                };
                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));

                // No change to state, and no response.
            } else if (msg.messageType === 'offer') {
                // Glare processing.
                that.error('Not written yet');
            } else {
                that.error('Illegal message for this state: ' + msg.messageType + ' in state ' + that.state);
            }

        } else if (that.state === 'established') {
            if (msg.messageType === 'OFFER') {
                // Subsequent offer.
                sd = {
                    sdp: msg.sdp,
                    type: 'offer'
                };
                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));

                that.state = 'offer-received';
                // Allow other stuff to happen, then reply.
                that.markActionNeeded();
            } else {
                that.error('Illegal message for this state: ' + msg.messageType + ' in state ' + that.state);
            }
        }
    };

    /**
     * Adds a stream - this causes signalling to happen, if needed.
     * @param {MediaStream} stream The outgoing MediaStream to add.
     */
    that.addStream = function (stream) {
        that.peerConnection.addStream(stream);
        that.markActionNeeded();
    };

    /**
     * Removes a stream.
     * @param {MediaStream} stream The MediaStream to remove.
     */
    that.removeStream = function (stream) {
//        var i;
//        for (i = 0; i < that.peerConnection.localStreams.length; ++i) {
//            if (that.localStreams[i] === stream) {
//                that.localStreams[i] = null;
//            }
//        }
        that.markActionNeeded();
    };

    /**
     * Closes the connection.
     */
    that.close = function () {
        that.state = 'closed';
        that.peerConnection.close();
    };

    /**
     * Internal function: Mark that something happened.
     */
    that.markActionNeeded = function () {
        that.actionNeeded = true;
        that.doLater(function () {
            that.onstablestate();
        });
    };

    /**
     * Internal function: Do something later (not on this stack).
     * @param {function} what Callback to be executed later.
     */
    that.doLater = function (what) {
        // Post an event to myself so that I get called a while later.
        // (needs more JS/DOM info. Just call the processing function on a delay
        // for now.)
        window.setTimeout(what, 1);
    };

    /**
     * Internal function called when a stable state
     * is entered by the browser (to allow for multiple AddStream calls or
     * other interesting actions).
     * This function will generate an offer or answer, as needed, and send
     * to the remote party using our onsignalingmessage function.
     */
    that.onstablestate = function () {
        var mySDP, roapMessage = {};
        if (that.actionNeeded) {
            if (that.state === 'new' || that.state === 'established') {
                // See if the current offer is the same as what we already sent.
                // If not, no change is needed.

                that.peerConnection.createOffer(function (sessionDescription) {

                    //sessionDescription.sdp = newOffer.replace(/a=ice-options:google-ice\r\n/g, "");
                    //sessionDescription.sdp = newOffer.replace(/a=crypto:0 AES_CM_128_HMAC_SHA1_80 inline:.*\r\n/g, "a=crypto:0 AES_CM_128_HMAC_SHA1_80 inline:eUMxlV2Ib6U8qeZot/wEKHw9iMzfKUYpOPJrNnu3\r\n");
                    //sessionDescription.sdp = newOffer.replace(/a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:.*\r\n/g, "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:eUMxlV2Ib6U8qeZot/wEKHw9iMzfKUYpOPJrNnu3\r\n");

                    sessionDescription.sdp = setMaxBW(sessionDescription.sdp);
                    L.Logger.debug("Changed", sessionDescription.sdp);

                    var newOffer = sessionDescription.sdp;

                    if (newOffer !== that.prevOffer) {

                        that.peerConnection.setLocalDescription(sessionDescription, function(){
                      console.log("Success");
                    }, function(){
                      console.log("Failed Setting local desc");
                    });

                        that.state = 'preparing-offer';
                        that.markActionNeeded();
                        return;
                    } else {
                        L.Logger.debug('Not sending a new offer');
                    }

                }, null, that.mediaConstraints);

            } else if (that.state === 'preparing-offer') {
                // Don't do anything until we have the ICE candidates.
                if (that.moreIceComing) {
                    return;
                }

                // Now able to send the offer we've already prepared.
                that.prevOffer = that.peerConnection.localDescription.sdp;
                L.Logger.debug("Sending OFFER: " + that.prevOffer);
                //L.Logger.debug('Sent SDP is ' + that.prevOffer);
                that.sendMessage('OFFER', that.prevOffer);
                // Not done: Retransmission on non-response.
                that.state = 'offer-sent';

            } else if (that.state === 'offer-received') {

                that.peerConnection.createAnswer(function (sessionDescription) {
                    that.peerConnection.setLocalDescription(sessionDescription);
                    that.state = 'offer-received-preparing-answer';

                    if (!that.iceStarted) {
                        var now = new Date();
                        L.Logger.debug(now.getTime() + ': Starting ICE in responder');
                        that.iceStarted = true;
                    } else {
                        that.markActionNeeded();
                        return;
                    }

                }, null, that.mediaConstraints);

            } else if (that.state === 'offer-received-preparing-answer') {
                if (that.moreIceComing) {
                    return;
                }

                mySDP = that.peerConnection.localDescription.sdp;

                that.sendMessage('ANSWER', mySDP);
                that.state = 'established';
            } else {
                that.error('Dazed and confused in state ' + that.state + ', stopping here');
            }
            that.actionNeeded = false;
        }
    };

    /**
     * Internal function to send an "OK" message.
     */
    that.sendOK = function () {
        that.sendMessage('OK');
    };

    /**
     * Internal function to send a signalling message.
     * @param {string} operation What operation to signal.
     * @param {string} sdp SDP message body.
     */
    that.sendMessage = function (operation, sdp) {
        var roapMessage = {};
        roapMessage.messageType = operation;
        roapMessage.sdp = sdp; // may be null or undefined
        if (operation === 'OFFER') {
            roapMessage.offererSessionId = that.sessionId;
            roapMessage.answererSessionId = that.otherSessionId; // may be null
            roapMessage.seq = (that.sequenceNumber += 1);
            // The tiebreaker needs to be neither 0 nor 429496725.
            roapMessage.tiebreaker = Math.floor(Math.random() * 429496723 + 1);
        } else {
            roapMessage.offererSessionId = that.incomingMessage.offererSessionId;
            roapMessage.answererSessionId = that.sessionId;
            roapMessage.seq = that.incomingMessage.seq;
        }
        that.onsignalingmessage(JSON.stringify(roapMessage));
    };

    /**
     * Internal something-bad-happened function.
     * @param {string} text What happened - suitable for logging.
     */
    that.error = function (text) {
        throw 'Error in RoapOnJsep: ' + text;
    };

    that.sessionId = (that.roapSessionId += 1);
    that.sequenceNumber = 0; // Number of last ROAP message sent. Starts at 1.
    that.actionNeeded = false;
    that.iceStarted = false;
    that.moreIceComing = true;
    that.iceCandidateCount = 0;
    that.onsignalingmessage = spec.callback;

    that.peerConnection.onopen = function () {
        if (that.onopen) {
            that.onopen();
        }
    };

    that.peerConnection.onaddstream = function (stream) {
        if (that.onaddstream) {
            that.onaddstream(stream);
        }
    };

    that.peerConnection.onremovestream = function (stream) {
        if (that.onremovestream) {
            that.onremovestream(stream);
        }
    };

    that.peerConnection.oniceconnectionstatechange = function (e) {
        if (that.oniceconnectionstatechange) {
            that.oniceconnectionstatechange(e.currentTarget.iceConnectionState);
        }
    };

    // Variables that are part of the public interface of PeerConnection
    // in the 28 January 2012 version of the webrtc specification.
    that.onaddstream = null;
    that.onremovestream = null;
    that.state = 'new';
    // Auto-fire next events.
    that.markActionNeeded();
    return that;
};
<|endoftext|>


<commit_before>const path = require('path')
const fs = require('fs')
const BrowserWindow = require('electron').BrowserWindow
const ipc = require('electron').ipcMain
const chokidar = require('chokidar')
const assign = require('object-assign')
const sharedState = require('../shared/shared-state')
const styles = require('./styles')
const windowStateKeeper = require('electron-window-state')

module.exports = function createWindow (options) {
  const mainWindowState = windowStateKeeper({
    defaultWidth: 800,
    defaultHeight: 600
  })

  options = assign({}, mainWindowState, options)

  const fromFile = typeof options.filePath !== 'undefined'
  var watcher

  var preloadPath = path.resolve(__dirname, 'client-api.js')

  var win = new BrowserWindow({
    webPreferences: {
      preload: preloadPath
    },
    icon: path.join(__dirname, 'assets/app-icon/png/512.png'),
    width: options.width,
    height: options.height,
    x: options.x,
    y: options.y
  })

  mainWindowState.manage(win)

  updateTitle()

  temporarilyInterceptFileProtocol()
  win.loadURL('file://' + path.join(__dirname, '/../renderer/vmd.html'))
  win.on('close', onClose)
  win.webContents.on('did-finish-load', sendMarkdown)

  win.on('closed', function () {
    win = null
  })

  win.on('focus', function () {
    sharedState.setFocusedWindow(win.id)
  })

  if (win.isFocused()) {
    sharedState.setFocusedWindow(win.id)
  }

  if (options.devTools) {
    win.openDevTools()
  }

  if (fromFile) {
    watcher = chokidar.watch(options.filePath, { usePolling: true })
    watcher.on('change', sendMarkdown)
  }

  ipc.on('open-file', onOpenFile)

  sharedState.subscribe(function () {
    if (!win) {
      return
    }

    var state = sharedState.getWindowState(win.id)

    if (state && state.filePath && state.filePath !== options.filePath) {
      changeFile(state.filePath)
    }
  })

  function onOpenFile (ev, filePath) {
    if (ev.sender === win.webContents) {
      createWindow(assign({}, options, {
        filePath: filePath
      }))
    }
  }

  function onClose () {
    if (watcher) {
      watcher.close()
    }

    ipc.removeListener('open-file', onOpenFile)
  }

  function updateTitle () {
    var prefix =
      options.title ||
      (fromFile && (path.basename(options.filePath)))

    win.setTitle(prefix ? prefix + ' - vmd' : 'vmd')

    // (OS X) Set represented filename (icon in title bar)
    if (fromFile && process.platform === 'darwin') {
      win.setRepresentedFilename(path.resolve(options.filePath))
    }
  }

  function changeFile (filePath) {
    if (watcher) {
      watcher.unwatch(options.filePath)
      watcher.add(filePath)
    }

    options.filePath = filePath
    updateTitle()
    sendMarkdown()
  }

  function sendMarkdown () {
    const resolved = fromFile
      ? path.resolve(path.dirname(options.filePath))
      : process.cwd()

    var baseUrl = path.relative(__dirname, resolved)
    if (baseUrl) baseUrl += '/'

    if (win) {
      var contents = fromFile
        ? fs.readFileSync(options.filePath, { encoding: 'utf8' })
        : options.contents

      win.webContents.send('md', {
        filePath: options.filePath,
        baseUrl: baseUrl,
        contents: contents
      })
    }
  }

  function temporarilyInterceptFileProtocol () {
    // very hacky way to dynamically create vmd.html
    const protocol = require('electron').protocol
    const template = require('lodash.template')
    const indexHtml = template(fs.readFileSync(path.join(__dirname, '..', 'renderer', 'vmd.html'), { encoding: 'utf-8' }))

    protocol.interceptStringProtocol(
      'file',
      function (req, callback) {
        var mainStyle = options.mainStylesheet
          ? styles.getStylesheet(options.mainStylesheet)
          : styles.getStylesheet(require.resolve('github-markdown-css'))

        var extraStyle = options.extraStylesheet
          ? styles.getStylesheet(options.extraStylesheet)
          : ''

        var highlightStyle = options.highlightStylesheet
          ? styles.getStylesheet(options.highlightStylesheet)
          : styles.getHighlightTheme('default') + '\n' + styles.getHighlightTheme(options.highlightTheme)

        var data = {
          mainStyle: mainStyle,
          extraStyle: extraStyle,
          highlightStyle: highlightStyle
        }
        callback({
          mimeType: 'text/html',
          data: indexHtml(data)
        })

        process.nextTick(function () {
          protocol.uninterceptProtocol('file')
        })
      },
      function (err, scheme) {
        if (err) {
          console.error('failed to register', scheme, 'protocol')
        }
      }
    )
  }

  return {
    win: win,
    changeFile: changeFile
  }
}
<commit_msg>Namespace window-state file to avoid conflicts<commit_after>const path = require('path')
const fs = require('fs')
const BrowserWindow = require('electron').BrowserWindow
const ipc = require('electron').ipcMain
const chokidar = require('chokidar')
const assign = require('object-assign')
const sharedState = require('../shared/shared-state')
const styles = require('./styles')
const windowStateKeeper = require('electron-window-state')

module.exports = function createWindow (options) {
  const mainWindowState = windowStateKeeper({
    file: 'vmd-window-state.json',
    defaultWidth: 800,
    defaultHeight: 600
  })

  options = assign({}, mainWindowState, options)

  const fromFile = typeof options.filePath !== 'undefined'
  var watcher

  var preloadPath = path.resolve(__dirname, 'client-api.js')

  var win = new BrowserWindow({
    webPreferences: {
      preload: preloadPath
    },
    icon: path.join(__dirname, 'assets/app-icon/png/512.png'),
    width: options.width,
    height: options.height,
    x: options.x,
    y: options.y
  })

  mainWindowState.manage(win)

  updateTitle()

  temporarilyInterceptFileProtocol()
  win.loadURL('file://' + path.join(__dirname, '/../renderer/vmd.html'))
  win.on('close', onClose)
  win.webContents.on('did-finish-load', sendMarkdown)

  win.on('closed', function () {
    win = null
  })

  win.on('focus', function () {
    sharedState.setFocusedWindow(win.id)
  })

  if (win.isFocused()) {
    sharedState.setFocusedWindow(win.id)
  }

  if (options.devTools) {
    win.openDevTools()
  }

  if (fromFile) {
    watcher = chokidar.watch(options.filePath, { usePolling: true })
    watcher.on('change', sendMarkdown)
  }

  ipc.on('open-file', onOpenFile)

  sharedState.subscribe(function () {
    if (!win) {
      return
    }

    var state = sharedState.getWindowState(win.id)

    if (state && state.filePath && state.filePath !== options.filePath) {
      changeFile(state.filePath)
    }
  })

  function onOpenFile (ev, filePath) {
    if (ev.sender === win.webContents) {
      createWindow(assign({}, options, {
        filePath: filePath
      }))
    }
  }

  function onClose () {
    if (watcher) {
      watcher.close()
    }

    ipc.removeListener('open-file', onOpenFile)
  }

  function updateTitle () {
    var prefix =
      options.title ||
      (fromFile && (path.basename(options.filePath)))

    win.setTitle(prefix ? prefix + ' - vmd' : 'vmd')

    // (OS X) Set represented filename (icon in title bar)
    if (fromFile && process.platform === 'darwin') {
      win.setRepresentedFilename(path.resolve(options.filePath))
    }
  }

  function changeFile (filePath) {
    if (watcher) {
      watcher.unwatch(options.filePath)
      watcher.add(filePath)
    }

    options.filePath = filePath
    updateTitle()
    sendMarkdown()
  }

  function sendMarkdown () {
    const resolved = fromFile
      ? path.resolve(path.dirname(options.filePath))
      : process.cwd()

    var baseUrl = path.relative(__dirname, resolved)
    if (baseUrl) baseUrl += '/'

    if (win) {
      var contents = fromFile
        ? fs.readFileSync(options.filePath, { encoding: 'utf8' })
        : options.contents

      win.webContents.send('md', {
        filePath: options.filePath,
        baseUrl: baseUrl,
        contents: contents
      })
    }
  }

  function temporarilyInterceptFileProtocol () {
    // very hacky way to dynamically create vmd.html
    const protocol = require('electron').protocol
    const template = require('lodash.template')
    const indexHtml = template(fs.readFileSync(path.join(__dirname, '..', 'renderer', 'vmd.html'), { encoding: 'utf-8' }))

    protocol.interceptStringProtocol(
      'file',
      function (req, callback) {
        var mainStyle = options.mainStylesheet
          ? styles.getStylesheet(options.mainStylesheet)
          : styles.getStylesheet(require.resolve('github-markdown-css'))

        var extraStyle = options.extraStylesheet
          ? styles.getStylesheet(options.extraStylesheet)
          : ''

        var highlightStyle = options.highlightStylesheet
          ? styles.getStylesheet(options.highlightStylesheet)
          : styles.getHighlightTheme('default') + '\n' + styles.getHighlightTheme(options.highlightTheme)

        var data = {
          mainStyle: mainStyle,
          extraStyle: extraStyle,
          highlightStyle: highlightStyle
        }
        callback({
          mimeType: 'text/html',
          data: indexHtml(data)
        })

        process.nextTick(function () {
          protocol.uninterceptProtocol('file')
        })
      },
      function (err, scheme) {
        if (err) {
          console.error('failed to register', scheme, 'protocol')
        }
      }
    )
  }

  return {
    win: win,
    changeFile: changeFile
  }
}
<|endoftext|>