<commit_before>(function (angular) {
	'use strict';

	function RelationshipController($scope, $element, $attrs, $compile, $uibModal) {
		var ctrl = this;

		ctrl.connectors = [];
		ctrl.relationConnectors = [];

		ctrl.$onInit = function () {
			ctrl.model.dom = $element;
			// add handler
			angular.element($element).draggable({
				drag: function (event, ui) {
					if (ui.position.left < 0) {
						ui.position.left = 0;
					}
					if (ui.position.top < 0) {
						ui.position.top = 0;
					}
					ctrl.redrawConnectors();
					ctrl.redrawRelationConnectors();
				}
			});
		};

		/*
		 Rendering related
		 */
		$scope.$on('attr:done', function () {
			ctrl.alignAttributes();
		});

		$scope.$on('attr:remove', function () {
			ctrl.alignAttributes();
		});

		ctrl.alignAttributes = function () {
			var radius = 200, // radius of the circle
				elementWidth = 100 / 2,
				elementHeight = 28 / 2;

			var fields = angular.element('#relationship-' + ctrl.model.name + ' er-attribute'),
				width = 200,
				height = 120,
				angle = 0,
				step = (2 * Math.PI) / fields.length;

			// for drawing
			fields.each(function () {
				var x = Math.round(width / 2 + radius * Math.cos(angle) - elementWidth);
				var y = Math.round(height / 2 + radius * Math.sin(angle) - elementHeight);
				angular.element(this).css({
					position: 'absolute',
					left: x + 'px',
					top: y + 'px'
				});

				angle += step;
			});

			ctrl.redrawConnectors();
		};

		ctrl.redrawConnectors = function () {
			ctrl.connectors.forEach(function (e) {
				e.redraw();
			});
		};

		ctrl.redrawRelationConnectors = function () {
			ctrl.relationConnectors.forEach(function (e) {
				e.redraw();
			});
		};

		/**
		 *
		 * @param {Entity} toEntity
		 */
		ctrl.addRelationConnectors = function (toEntity) {
			var hasConnected = ctrl.relationConnectors.some(function(connector) {
				if (connector.diagram2 === toEntity || connector.diagram1 === toEntity) {
					return true;
				}
			});
			if (hasConnected) {
				console.log('already connected with ' + toEntity.name)
				return;
			}
			ctrl.relationConnectors.push(new RelationConnector(ctrl.model, toEntity));
		};

		ctrl.removeRelationConnectors = function (index) {
			var relationConnectors = ctrl.relationConnectors[index];
			relationConnectors.destroy();
			this.relationConnectors.splice(index, 1);
		};

		ctrl.addConnectors = function (connector) {
			ctrl.connectors.push(connector);
		};

		ctrl.removeConnectors = function (index) {
			var connector = ctrl.connectors[index];
			connector.destroy();
			this.connectors.splice(index, 1);
		};

		ctrl.showDetail = function () {
			console.log('show detail');
		};

		/*
		 Context menu
		 */
		ctrl.menuOptions = [
			['Add attribute', function () {
				ctrl.addAttribute();
			}],
			['Add reference', function () {
				ctrl.addCreateReference();
			}],
			null,
			['Rename', function () {
				ctrl.renameModel();
			}],
			null,
			['Remove', function () {
				ctrl.removeModel();
			}]
		];

		ctrl.renameModel = function () {
			ctrl.askForModelName(ctrl.model.name).then(function (newName) {
				ctrl.model.rename(newName);
			});
		};

		ctrl.removeModel = function () {
			ctrl.connectors.forEach(function (e) {
				e.destroy();
			});
			ctrl.onDestroy(ctrl.model);
		};

		/*
		 Attributes related
		 */
		ctrl.addAttribute = function () {
			ctrl.addNewAttributeModal().then(function (data) {
				var newAttr = ctrl.model.addAttribute(data);
				if (!newAttr) {
					return alert('The attribute name already exists in this relationship');
				}
				ctrl.addConnectors(new Connector(ctrl.model, newAttr));
			});
		};

		ctrl.addCreateReference = function () {
			ctrl.onAddReference().then(function(data) {
				var res = ctrl.model.addReference(data);
				if (!res) {
					return alert('This reference already exists in this relationship');
				}
				// then connect entity with this relationship
				ctrl.addRelationConnectors(data.entity);
				ctrl.redrawRelationConnectors();
			});
		};

		/**
		 * Edit an attribute
		 * @param index array index of the attribute
		 */
		ctrl.editAttribute = function (index) {
			ctrl.editAttributeModal(ctrl.model.getAttribute(index)).then(function (data) {
				var res = ctrl.model.editAttribute(index, data);
				if (!res) {
					return alert('The attribute name already exists in this relationship');
				}
				ctrl.redrawConnectors();
			});
		};

		ctrl.removeAttribute = function (index) {
			ctrl.removeConnectors(index);
			ctrl.model.removeAttribute(index);
		};

		ctrl.renameRelationship = function () {
			ctrl.askForModelName(ctrl.model.name).then(function (newName) {
				ctrl.model.rename(newName);
			});
		};

		/*
		 Modals related
		 */
		ctrl.askForModelName = function (currentName) {
			var newScope = $scope.$new(true);
			newScope.data = currentName;
			var modalInstance = $uibModal.open({
				templateUrl: 'input-prompt.html',
				controller: 'PromptModalCtrl',
				size: 'lg',
				scope: newScope,
				resolve: {
					title: function () {
						return 'Please enter new relationship name';
					}
				}
			});

			return modalInstance.result; // return the promise
		};

		ctrl.addNewAttributeModal = function () {
			var modalInstance = $uibModal.open({
				templateUrl: 'new-attribute-prompt.html',
				controller: 'AttributeModalCtrl',
				size: 'lg',
				resolve: {
					title: function () {
						return 'Create new attribute';
					}
				}
			});

			return modalInstance.result; // return the promise
		};

		/**
		 *
		 * @param attribute the Attribute model
		 * @returns {*}
		 */
		ctrl.editAttributeModal = function (attribute) {
			// create a scope for the modal that contains attribute data
			var newScope = $scope.$new(true);
			newScope.attributeData = {
				name: attribute.name,
				type: attribute.type,
				notNull: attribute.notNull,
				isPrimaryKey: attribute.isPrimaryKey,
				isForeignKey: attribute.isForeignKey
			};

			var modalInstance = $uibModal.open({
				templateUrl: 'new-attribute-prompt.html',
				controller: 'AttributeModalCtrl',
				size: 'lg',
				scope: newScope, // pass the scope created
				resolve: {
					title: function () {
						return 'Edit attribute';
					}
				}
			});

			return modalInstance.result; // return the promise
		};
	}

	angular.module('editor').component('erRelationship', {
		bindings: {
			model: '<',
			onDestroy: '&',
			onAddReference: '&'
		},
		templateUrl: './app/components/editor/diagram/erRelationship.html',
		controller: RelationshipController
	});
})(window.angular);<commit_msg>Use ctrl instead of this<commit_after>(function (angular) {
	'use strict';

	function RelationshipController($scope, $element, $attrs, $compile, $uibModal) {
		var ctrl = this;

		ctrl.connectors = [];
		ctrl.relationConnectors = [];

		ctrl.$onInit = function () {
			ctrl.model.dom = $element;
			// add handler
			angular.element($element).draggable({
				drag: function (event, ui) {
					if (ui.position.left < 0) {
						ui.position.left = 0;
					}
					if (ui.position.top < 0) {
						ui.position.top = 0;
					}
					ctrl.redrawConnectors();
					ctrl.redrawRelationConnectors();
				}
			});
		};

		/*
		 Rendering related
		 */
		$scope.$on('attr:done', function () {
			ctrl.alignAttributes();
		});

		$scope.$on('attr:remove', function () {
			ctrl.alignAttributes();
		});

		ctrl.alignAttributes = function () {
			var radius = 200, // radius of the circle
				elementWidth = 100 / 2,
				elementHeight = 28 / 2;

			var fields = angular.element('#relationship-' + ctrl.model.name + ' er-attribute'),
				width = 200,
				height = 120,
				angle = 0,
				step = (2 * Math.PI) / fields.length;

			// for drawing
			fields.each(function () {
				var x = Math.round(width / 2 + radius * Math.cos(angle) - elementWidth);
				var y = Math.round(height / 2 + radius * Math.sin(angle) - elementHeight);
				angular.element(this).css({
					position: 'absolute',
					left: x + 'px',
					top: y + 'px'
				});

				angle += step;
			});

			ctrl.redrawConnectors();
		};

		ctrl.redrawConnectors = function () {
			ctrl.connectors.forEach(function (e) {
				e.redraw();
			});
		};

		ctrl.redrawRelationConnectors = function () {
			ctrl.relationConnectors.forEach(function (e) {
				e.redraw();
			});
		};

		/**
		 *
		 * @param {Entity} toEntity
		 */
		ctrl.addRelationConnectors = function (toEntity) {
			var hasConnected = ctrl.relationConnectors.some(function(connector) {
				if (connector.diagram2 === toEntity || connector.diagram1 === toEntity) {
					return true;
				}
			});
			if (hasConnected) {
				console.log('already connected with ' + toEntity.name)
				return;
			}
			ctrl.relationConnectors.push(new RelationConnector(ctrl.model, toEntity));
		};

		ctrl.removeRelationConnectors = function (index) {
			var relationConnectors = ctrl.relationConnectors[index];
			relationConnectors.destroy();
			ctrl.relationConnectors.splice(index, 1);
		};

		ctrl.addConnectors = function (connector) {
			ctrl.connectors.push(connector);
		};

		ctrl.removeConnectors = function (index) {
			var connector = ctrl.connectors[index];
			connector.destroy();
			ctrl.connectors.splice(index, 1);
		};

		ctrl.showDetail = function () {
			console.log('show detail');
		};

		/*
		 Context menu
		 */
		ctrl.menuOptions = [
			['Add attribute', function () {
				ctrl.addAttribute();
			}],
			['Add reference', function () {
				ctrl.addCreateReference();
			}],
			null,
			['Rename', function () {
				ctrl.renameModel();
			}],
			null,
			['Remove', function () {
				ctrl.removeModel();
			}]
		];

		ctrl.renameModel = function () {
			ctrl.askForModelName(ctrl.model.name).then(function (newName) {
				ctrl.model.rename(newName);
			});
		};

		ctrl.removeModel = function () {
			ctrl.connectors.forEach(function (e) {
				e.destroy();
			});
			ctrl.onDestroy(ctrl.model);
		};

		/*
		 Attributes related
		 */
		ctrl.addAttribute = function () {
			ctrl.addNewAttributeModal().then(function (data) {
				var newAttr = ctrl.model.addAttribute(data);
				if (!newAttr) {
					return alert('The attribute name already exists in this relationship');
				}
				ctrl.addConnectors(new Connector(ctrl.model, newAttr));
			});
		};

		ctrl.addCreateReference = function () {
			ctrl.onAddReference().then(function(data) {
				var res = ctrl.model.addReference(data);
				if (!res) {
					return alert('This reference already exists in this relationship');
				}
				// then connect entity with this relationship
				ctrl.addRelationConnectors(data.entity);
				ctrl.redrawRelationConnectors();
			});
		};

		/**
		 * Edit an attribute
		 * @param index array index of the attribute
		 */
		ctrl.editAttribute = function (index) {
			ctrl.editAttributeModal(ctrl.model.getAttribute(index)).then(function (data) {
				var res = ctrl.model.editAttribute(index, data);
				if (!res) {
					return alert('The attribute name already exists in this relationship');
				}
				ctrl.redrawConnectors();
			});
		};

		ctrl.removeAttribute = function (index) {
			ctrl.removeConnectors(index);
			ctrl.model.removeAttribute(index);
		};

		ctrl.renameRelationship = function () {
			ctrl.askForModelName(ctrl.model.name).then(function (newName) {
				ctrl.model.rename(newName);
			});
		};

		/*
		 Modals related
		 */
		ctrl.askForModelName = function (currentName) {
			var newScope = $scope.$new(true);
			newScope.data = currentName;
			var modalInstance = $uibModal.open({
				templateUrl: 'input-prompt.html',
				controller: 'PromptModalCtrl',
				size: 'lg',
				scope: newScope,
				resolve: {
					title: function () {
						return 'Please enter new relationship name';
					}
				}
			});

			return modalInstance.result; // return the promise
		};

		ctrl.addNewAttributeModal = function () {
			var modalInstance = $uibModal.open({
				templateUrl: 'new-attribute-prompt.html',
				controller: 'AttributeModalCtrl',
				size: 'lg',
				resolve: {
					title: function () {
						return 'Create new attribute';
					}
				}
			});

			return modalInstance.result; // return the promise
		};

		/**
		 *
		 * @param attribute the Attribute model
		 * @returns {*}
		 */
		ctrl.editAttributeModal = function (attribute) {
			// create a scope for the modal that contains attribute data
			var newScope = $scope.$new(true);
			newScope.attributeData = {
				name: attribute.name,
				type: attribute.type,
				notNull: attribute.notNull,
				isPrimaryKey: attribute.isPrimaryKey,
				isForeignKey: attribute.isForeignKey
			};

			var modalInstance = $uibModal.open({
				templateUrl: 'new-attribute-prompt.html',
				controller: 'AttributeModalCtrl',
				size: 'lg',
				scope: newScope, // pass the scope created
				resolve: {
					title: function () {
						return 'Edit attribute';
					}
				}
			});

			return modalInstance.result; // return the promise
		};
	}

	angular.module('editor').component('erRelationship', {
		bindings: {
			model: '<',
			onDestroy: '&',
			onAddReference: '&'
		},
		templateUrl: './app/components/editor/diagram/erRelationship.html',
		controller: RelationshipController
	});
})(window.angular);<|endoftext|>


<commit_before>/**
 * justlazy 1.3.0
 *
 * Repo: https://github.com/fhopeman/justlazy
 * Demo: http://fhopeman.github.io/justlazy
 */
(function(root, factory) {
    if (typeof define === "function" && define.amd) {
        define([], factory);
    } else if (typeof module === "object" && module.exports) {
        module.exports = factory();
    } else {
        root.Justlazy = factory();
    }
}(this, function() {
    "use strict";

    /**
     * Creates an img html node and sets the attributes of the
     * image. If the image is loaded successfully, the placeholder
     * will be replaced with the image.
     *
     * @param {Object} imgPlaceholder Placeholder element of the img to lazy load.
     * @param {Object} imgAttributes Attributes of the image which will be created.
     * @param {Function} onloadCallback Optional onload callback function.
     *
     */
    var _createImage = function(imgPlaceholder, imgAttributes, onloadCallback) {
        var img = document.createElement("img");

        img.onload = function() {
            _replacePlacholderWithImage(imgPlaceholder, img);
            if (!!onloadCallback) {
                onloadCallback.call(img);
            }
        };
        img.src = imgAttributes.src;
        img.alt = imgAttributes.alt;
        if (!!imgAttributes.title) {
            img.title = imgAttributes.title;
        }
        if (!!imgAttributes.errorHandler) {
            img.setAttribute("onerror", imgAttributes.errorHandler);
        }
        if (!!imgAttributes.srcset) {
            img.setAttribute("srcset", imgAttributes.srcset);
        }
    };

    /**
     * Replaces the img placeholder (html node of any type) with the img.
     *
     * @param {Object} imgPlaceholder Image placeholder html node.
     * @param {Object} img Image node itself.
     */
    var _replacePlacholderWithImage = function(imgPlaceholder, img) {
        var parentNode = imgPlaceholder.parentNode;
        if (!!parentNode) {
            parentNode.replaceChild(img, imgPlaceholder);
        }
    };

    /**
     * Reads out the relevant attributes of the imagePlaceholder.
     *
     * @param {Object} imgPlaceholder Lazy image placeholder which holds image attributes.
     *
     * @returns {Object} Object with image attributes.
     */
    var _resolveImageAttributes = function(imgPlaceholder) {
        return {
            src: imgPlaceholder.getAttribute("data-src"),
            alt: imgPlaceholder.getAttribute("data-alt"),
            title: imgPlaceholder.getAttribute("data-title"),
            errorHandler: imgPlaceholder.getAttribute("data-error-handler"),
            srcset: imgPlaceholder.getAttribute("data-srcset")
        };
    };

    var _validateOptions = function(options) {
        return options || {};
    };

    /**
     * Lazy loads image with img tag.
     *
     * @param {Object} imgPlaceholder The placeholder is a html node of any type (e.g. a span element).
     *                                The node has to provide the data element data-src and data-alt.
     *                                All other attributes are optional.
     * @param {Object} options Optional object with following attributes:
     *                           - onloadCallback:
     *                                 Optional callback which is invoked after the image is loaded.
     *                           - onerrorCallback:
     *                                 Optional error handler which is invoked if the
     *                                 replacement of the lazy placeholder fails (e.g. mandatory
     *                                 attributes missing).
     */
    var lazyLoad = function(imgPlaceholder, options) {
        var imgAttributes = _resolveImageAttributes(imgPlaceholder);
        options = _validateOptions(options);

        if (!!imgAttributes.src && (!!imgAttributes.alt || imgAttributes.alt === "")) {
            _createImage(imgPlaceholder, imgAttributes, options.onloadCallback);
        } else {
            if (!!options.onerrorCallback) {
                options.onerrorCallback.call(imgPlaceholder);
            }
        }
    };

    var _isVisible = function(placeholder, optionalThreshold) {
        var windowInnerHeight = window.innerHeight || document.documentElement.clientHeight;
        var threshold = optionalThreshold || 0;

        return placeholder.getBoundingClientRect().top - windowInnerHeight <= threshold;
    };

    var _loadImgIfVisible = function(imgPlaceholder, options) {
        var scrollEventCallback = function(e) {
            if (_isVisible(imgPlaceholder, options.threshold)) {
                lazyLoad(imgPlaceholder, options);

                if (window.removeEventListener) {
                    window.removeEventListener(e.type, scrollEventCallback, false);
                } else {
                    window.detachEvent("on" + e.type, scrollEventCallback);
                }
            }
        };

        return scrollEventCallback;
    };

    /**
     * Registers the lazy loading event. If the image become visible, it will
     * be loaded automatically.
     *
     * @param {Object} imgPlaceholder The placeholder is a html node of any type (e.g. a span element).
     *                                The node has to provide the data element data-src and data-alt.
     *                                All other attributes are optional.
     * @param {Object} options Optional object with following attributes:
     *                           - onloadCallback:
     *                                 Optional callback which is invoked after the image is loaded.
     *                           - onerrorCallback:
     *                                 Optional error handler which is invoked if the
     *                                 replacement of the lazy placeholder fails (e.g. mandatory
     *                                 attributes missing).
     *                           - threshold:
     *                                 The image is loaded the defined pixels before it appears
     *                                 on the screen. E.g. 200px before it become visible.
     */
    var registerLazyLoad = function(imgPlaceholder, options) {
        var loadImgIfVisible = _loadImgIfVisible(imgPlaceholder, _validateOptions(options));

        if (window.addEventListener) {
            window.addEventListener("scroll", loadImgIfVisible, false);
        } else {
            window.attachEvent("onscroll", loadImgIfVisible);
        }
    };

    var registerLazyLoadByClass = function(imgPlaceholderClass, options) {
        var placeholders = document.querySelectorAll("." + imgPlaceholderClass);
        for (var i = 0; i < placeholders.length; ++i) {
            Justlazy.registerLazyLoad(placeholders[i], options);
        }
    };

    return {
        lazyLoad: lazyLoad,
        registerLazyLoad: registerLazyLoad,
        registerLazyLoadByClass: registerLazyLoadByClass
    };
}));
<commit_msg>bump version to 1.4.0<commit_after>/**
 * justlazy 1.4.0
 *
 * Repo: https://github.com/fhopeman/justlazy
 * Demo: http://fhopeman.github.io/justlazy
 */
(function(root, factory) {
    if (typeof define === "function" && define.amd) {
        define([], factory);
    } else if (typeof module === "object" && module.exports) {
        module.exports = factory();
    } else {
        root.Justlazy = factory();
    }
}(this, function() {
    "use strict";

    /**
     * Creates an img html node and sets the attributes of the
     * image. If the image is loaded successfully, the placeholder
     * will be replaced with the image.
     *
     * @param {Object} imgPlaceholder Placeholder element of the img to lazy load.
     * @param {Object} imgAttributes Attributes of the image which will be created.
     * @param {Function} onloadCallback Optional onload callback function.
     *
     */
    var _createImage = function(imgPlaceholder, imgAttributes, onloadCallback) {
        var img = document.createElement("img");

        img.onload = function() {
            _replacePlacholderWithImage(imgPlaceholder, img);
            if (!!onloadCallback) {
                onloadCallback.call(img);
            }
        };
        img.src = imgAttributes.src;
        img.alt = imgAttributes.alt;
        if (!!imgAttributes.title) {
            img.title = imgAttributes.title;
        }
        if (!!imgAttributes.errorHandler) {
            img.setAttribute("onerror", imgAttributes.errorHandler);
        }
        if (!!imgAttributes.srcset) {
            img.setAttribute("srcset", imgAttributes.srcset);
        }
    };

    /**
     * Replaces the img placeholder (html node of any type) with the img.
     *
     * @param {Object} imgPlaceholder Image placeholder html node.
     * @param {Object} img Image node itself.
     */
    var _replacePlacholderWithImage = function(imgPlaceholder, img) {
        var parentNode = imgPlaceholder.parentNode;
        if (!!parentNode) {
            parentNode.replaceChild(img, imgPlaceholder);
        }
    };

    /**
     * Reads out the relevant attributes of the imagePlaceholder.
     *
     * @param {Object} imgPlaceholder Lazy image placeholder which holds image attributes.
     *
     * @returns {Object} Object with image attributes.
     */
    var _resolveImageAttributes = function(imgPlaceholder) {
        return {
            src: imgPlaceholder.getAttribute("data-src"),
            alt: imgPlaceholder.getAttribute("data-alt"),
            title: imgPlaceholder.getAttribute("data-title"),
            errorHandler: imgPlaceholder.getAttribute("data-error-handler"),
            srcset: imgPlaceholder.getAttribute("data-srcset")
        };
    };

    var _validateOptions = function(options) {
        return options || {};
    };

    /**
     * Lazy loads image with img tag.
     *
     * @param {Object} imgPlaceholder The placeholder is a html node of any type (e.g. a span element).
     *                                The node has to provide the data element data-src and data-alt.
     *                                All other attributes are optional.
     * @param {Object} options Optional object with following attributes:
     *                           - onloadCallback:
     *                                 Optional callback which is invoked after the image is loaded.
     *                           - onerrorCallback:
     *                                 Optional error handler which is invoked if the
     *                                 replacement of the lazy placeholder fails (e.g. mandatory
     *                                 attributes missing).
     */
    var lazyLoad = function(imgPlaceholder, options) {
        var imgAttributes = _resolveImageAttributes(imgPlaceholder);
        options = _validateOptions(options);

        if (!!imgAttributes.src && (!!imgAttributes.alt || imgAttributes.alt === "")) {
            _createImage(imgPlaceholder, imgAttributes, options.onloadCallback);
        } else {
            if (!!options.onerrorCallback) {
                options.onerrorCallback.call(imgPlaceholder);
            }
        }
    };

    var _isVisible = function(placeholder, optionalThreshold) {
        var windowInnerHeight = window.innerHeight || document.documentElement.clientHeight;
        var threshold = optionalThreshold || 0;

        return placeholder.getBoundingClientRect().top - windowInnerHeight <= threshold;
    };

    var _loadImgIfVisible = function(imgPlaceholder, options) {
        var scrollEventCallback = function(e) {
            if (_isVisible(imgPlaceholder, options.threshold)) {
                lazyLoad(imgPlaceholder, options);

                if (window.removeEventListener) {
                    window.removeEventListener(e.type, scrollEventCallback, false);
                } else {
                    window.detachEvent("on" + e.type, scrollEventCallback);
                }
            }
        };

        return scrollEventCallback;
    };

    /**
     * Registers the lazy loading event. If the image become visible, it will
     * be loaded automatically.
     *
     * @param {Object} imgPlaceholder The placeholder is a html node of any type (e.g. a span element).
     *                                The node has to provide the data element data-src and data-alt.
     *                                All other attributes are optional.
     * @param {Object} options Optional object with following attributes:
     *                           - onloadCallback:
     *                                 Optional callback which is invoked after the image is loaded.
     *                           - onerrorCallback:
     *                                 Optional error handler which is invoked if the
     *                                 replacement of the lazy placeholder fails (e.g. mandatory
     *                                 attributes missing).
     *                           - threshold:
     *                                 The image is loaded the defined pixels before it appears
     *                                 on the screen. E.g. 200px before it become visible.
     */
    var registerLazyLoad = function(imgPlaceholder, options) {
        var loadImgIfVisible = _loadImgIfVisible(imgPlaceholder, _validateOptions(options));

        if (window.addEventListener) {
            window.addEventListener("scroll", loadImgIfVisible, false);
        } else {
            window.attachEvent("onscroll", loadImgIfVisible);
        }
    };

    var registerLazyLoadByClass = function(imgPlaceholderClass, options) {
        var placeholders = document.querySelectorAll("." + imgPlaceholderClass);
        for (var i = 0; i < placeholders.length; ++i) {
            Justlazy.registerLazyLoad(placeholders[i], options);
        }
    };

    return {
        lazyLoad: lazyLoad,
        registerLazyLoad: registerLazyLoad,
        registerLazyLoadByClass: registerLazyLoadByClass
    };
}));
<|endoftext|>


<commit_before>'use strict';

// LiFX Platform Shim for HomeBridge
//
// Remember to add platform to config.json. Example:
// "platforms": [
//     {
//         "platform": "LIFx",             // required
//         "name": "LIFx",                 // required
//         "access_token": "access token", // required
//         "use_lan": "true"               // optional set to "true" (gets and sets over the lan) or "get" (gets only over the lan)
//     }
// ],
//
// When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154.
//

var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var lifxRemoteObj = require('lifx-api');
var lifx_remote;

var lifxLanObj;
var lifx_lan;
var use_lan;

function LIFxPlatform(log, config){
    // auth info
    this.access_token = config["access_token"];

    lifx_remote = new lifxRemoteObj(this.access_token);

    // use remote or lan api ?
    use_lan = config["use_lan"] || false;

    if (use_lan != false) {
        lifxLanObj = require('lifx');
        lifx_lan = lifxLanObj.init();
    }

    this.log = log;
}

LIFxPlatform.prototype = {
    accessories: function(callback) {
        this.log("Fetching LIFx devices.");

        var that = this;
        var foundAccessories = [];

        lifx_remote.listLights("all", function(body) {
            var bulbs = JSON.parse(body);

            for(var i = 0; i < bulbs.length; i ++) {
                var accessory = new LIFxBulbAccessory(that.log, bulbs[i]);
                foundAccessories.push(accessory);
            }
            callback(foundAccessories)
        });
    }
}

function LIFxBulbAccessory(log, bulb) {
    // device info
    this.name = bulb.label;
    this.model = bulb.product_name;
    this.deviceId = bulb.id;
    this.serial = bulb.uuid;
    this.capabilities = bulb.capabilities;
    this.log = log;
}

LIFxBulbAccessory.prototype = {
    getLan: function(type, callback){
        var that = this;

        if (!lifx_lan.bulbs[this.deviceId]) {
            callback(new Error("Device not found"), false);
            return;
        }

        lifx_lan.requestStatus();
        lifx_lan.on('bulbstate', function(bulb) {
            if (callback == null) {
                return;
            }

            if (bulb.addr.toString('hex') == that.deviceId) {
                switch(type) {
                    case "power":
                        callback(null, bulb.state.power > 0);
                        break;
                    case "brightness":
                        callback(null, Math.round(bulb.state.brightness * 100 / 65535));
                        break;
                    case "hue":
                        callback(null, Math.round(bulb.state.hue * 360 / 65535));
                        break;
                    case "saturation":
                        callback(null, Math.round(bulb.state.saturation * 100 / 65535));
                        break;
                }

                callback = null
            }
        });
    },
    getRemote: function(type, callback){
        var that = this;

        lifx_remote.listLights("id:"+ that.deviceId, function(body) {
            var bulb = JSON.parse(body);

            if (bulb.connected != true) {
                callback(new Error("Device not found"), false);
                return;
            }

            switch(type) {
                case "power":
                    callback(null, bulb.power == "on" ? 1 : 0);
                    break;
                case "brightness":
                    callback(null, Math.round(bulb.brightness * 100));
                    break;
                case "hue":
                    callback(null, bulb.color.hue);
                    break;
                case "saturation":
                    callback(null, Math.round(bulb.color.saturation * 100));
                    break;
            }
        });
    },
    identify: function(callback) {
        lifx_remote.breatheEffect("id:"+ this.deviceId, 'green', null, 1, 3, false, true, 0.5, function (body) {
            callback();
        });
    },
    setLanColor: function(type, value, callback){
        var bulb = lifx_lan.bulbs[this.deviceId];

        if (!bulb) {
            callback(new Error("Device not found"), false);
            return;
        }

        var state = {
            hue: bulb.state.hue,
            saturation: bulb.state.saturation,
            brightness: bulb.state.brightness,
            kelvin: bulb.state.kelvin
        };

        var scale = type == "hue" ? 360 : 100;

        state[type] = Math.round(value * 65535 / scale) & 0xffff;
        lifx_lan.lightsColour(state.hue, state.saturation, state.brightness, state.kelvin, 0, bulb);

        callback(null);
    },
    setLanPower: function(state, callback){
        var bulb = lifx_lan.bulbs[this.deviceId];

        if (!bulb) {
            callback(new Error("Device not found"), false);
            return;
        }

        if (state) {
            lifx_lan.lightsOn(bulb);
        }
        else {
            lifx_lan.lightsOff(bulb);
        }

        callback(null);
    },
    setRemoteColor: function(type, value, callback){
        var color;

        switch(type) {
            case "brightness":
                color = "brightness:" + (value / 100);
                break;
            case "hue":
                color = "hue:" + value;
                break;
            case "saturation":
                color = "saturation:" + (value / 100);
                break;
        }

        lifx_remote.setColor("id:"+ this.deviceId, color, 0, null, function (body) {
            callback();
        });
    },
    setRemotePower: function(state, callback){
        var that = this;

        lifx_remote.setPower("id:"+ that.deviceId, (state == 1 ? "on" : "off"), 0, function (body) {
            callback();
        });
    },
    getServices: function() {
        var that = this;
        var services = []
        var service = new Service.Lightbulb(this.name);

        switch(use_lan) {
            case true:
            case "true":
                // gets and sets over the lan api
                service
                .getCharacteristic(Characteristic.On)
                .on('identify', function(callback) {})
                .on('get', function(callback) { that.getLan("power", callback);})
                .on('set', function(value, callback) {that.setLanPower(value, callback);});

                service
                .addCharacteristic(Characteristic.Brightness)
                .on('get', function(callback) { that.getLan("brightness", callback);})
                .on('set', function(value, callback) { that.setLanColor("brightness", value, callback);});

                if (this.capabilities.has_color == true) {
                    service
                    .addCharacteristic(Characteristic.Hue)
                    .on('get', function(callback) { that.getLan("hue", callback);})
                    .on('set', function(value, callback) { that.setLanColor("hue", value, callback);});

                    service
                    .addCharacteristic(Characteristic.Saturation)
                    .on('get', function(callback) { that.getLan("saturation", callback);})
                    .on('set', function(value, callback) { that.setLanColor("saturation", value, callback);});
                }
                break;
            case "get":
                // gets over the lan api, sets over the remote api
                service
                .getCharacteristic(Characteristic.On)
                .on('identify', function(callback) {})
                .on('get', function(callback) { that.getLan("power", callback);})
                .on('set', function(value, callback) {that.setRemotePower(value, callback);});

                service
                .addCharacteristic(Characteristic.Brightness)
                .on('get', function(callback) { that.getLan("brightness", callback);})
                .on('set', function(value, callback) { that.setRemoteColor("brightness", value, callback);});

                if (this.capabilities.has_color == true) {
                    service
                    .addCharacteristic(Characteristic.Hue)
                    .on('get', function(callback) { that.getLan("hue", callback);})
                    .on('set', function(value, callback) { that.setRemoteColor("hue", value, callback);});

                    service
                    .addCharacteristic(Characteristic.Saturation)
                    .on('get', function(callback) { that.getLan("saturation", callback);})
                    .on('set', function(value, callback) { that.setRemoteColor("saturation", value, callback);});
                }
                break;
            default:
                // gets and sets over the remote api
                service
                .getCharacteristic(Characteristic.On)
                .on('identify', function(callback) {})
                .on('get', function(callback) { that.getRemote("power", callback);})
                .on('set', function(value, callback) {that.setRemotePower(value, callback);});

                service
                .addCharacteristic(Characteristic.Brightness)
                .on('get', function(callback) { that.getRemote("brightness", callback);})
                .on('set', function(value, callback) { that.setRemoteColor("brightness", value, callback);});

                if (this.capabilities.has_color == true) {
                    service
                    .addCharacteristic(Characteristic.Hue)
                    .on('get', function(callback) { that.getRemote("hue", callback);})
                    .on('set', function(value, callback) { that.setRemoteColor("hue", value, callback);});

                    service
                    .addCharacteristic(Characteristic.Saturation)
                    .on('get', function(callback) { that.getRemote("saturation", callback);})
                    .on('set', function(value, callback) { that.setRemoteColor("saturation", value, callback);});
                }
        }

        services.push(service);

        service = new Service.AccessoryInformation();

        service
            .setCharacteristic(Characteristic.Manufacturer, "LiFX")
            .setCharacteristic(Characteristic.Model, this.model)
            .setCharacteristic(Characteristic.SerialNumber, this.serial);

        services.push(service);

        return services;
    }
}

module.exports.accessory = LIFxBulbAccessory;
module.exports.platform = LIFxPlatform;
<commit_msg>minor cleanup<commit_after>'use strict';

// LiFX Platform Shim for HomeBridge
//
// Remember to add platform to config.json. Example:
// "platforms": [
//     {
//         "platform": "LIFx",             // required
//         "name": "LIFx",                 // required
//         "access_token": "access token", // required
//         "use_lan": "true"               // optional set to "true" (gets and sets over the lan) or "get" (gets only over the lan)
//     }
// ],
//
// When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154.
//

var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var lifxRemoteObj = require('lifx-api');
var lifx_remote;

var lifxLanObj;
var lifx_lan;
var use_lan;

function LIFxPlatform(log, config){
    // auth info
    this.access_token = config["access_token"];

    lifx_remote = new lifxRemoteObj(this.access_token);

    // use remote or lan api ?
    use_lan = config["use_lan"] || false;

    if (use_lan != false) {
        lifxLanObj = require('lifx');
        lifx_lan = lifxLanObj.init();
    }

    this.log = log;
}

LIFxPlatform.prototype = {
    accessories: function(callback) {
        this.log("Fetching LIFx devices.");

        var that = this;
        var foundAccessories = [];

        lifx_remote.listLights("all", function(body) {
            var bulbs = JSON.parse(body);

            for(var i = 0; i < bulbs.length; i ++) {
                var accessory = new LIFxBulbAccessory(that.log, bulbs[i]);
                foundAccessories.push(accessory);
            }
            callback(foundAccessories)
        });
    }
}

function LIFxBulbAccessory(log, bulb) {
    // device info
    this.name = bulb.label;
    this.model = bulb.product_name;
    this.deviceId = bulb.id;
    this.serial = bulb.uuid;
    this.capabilities = bulb.capabilities;
    this.log = log;
}

LIFxBulbAccessory.prototype = {
    getLan: function(type, callback){
        var that = this;

        if (!lifx_lan.bulbs[this.deviceId]) {
            callback(new Error("Device not found"), false);
            return;
        }

        lifx_lan.requestStatus();
        lifx_lan.on('bulbstate', function(bulb) {
            if (callback == null) {
                return;
            }

            if (bulb.addr.toString('hex') == that.deviceId) {
                switch(type) {
                    case "power":
                        callback(null, bulb.state.power > 0);
                        break;
                    case "brightness":
                        callback(null, Math.round(bulb.state.brightness * 100 / 65535));
                        break;
                    case "hue":
                        callback(null, Math.round(bulb.state.hue * 360 / 65535));
                        break;
                    case "saturation":
                        callback(null, Math.round(bulb.state.saturation * 100 / 65535));
                        break;
                }

                callback = null
            }
        });
    },
    getRemote: function(type, callback){
        var that = this;

        lifx_remote.listLights("id:"+ that.deviceId, function(body) {
            var bulb = JSON.parse(body);

            if (bulb.connected != true) {
                callback(new Error("Device not found"), false);
                return;
            }

            switch(type) {
                case "power":
                    callback(null, bulb.power == "on" ? 1 : 0);
                    break;
                case "brightness":
                    callback(null, Math.round(bulb.brightness * 100));
                    break;
                case "hue":
                    callback(null, bulb.color.hue);
                    break;
                case "saturation":
                    callback(null, Math.round(bulb.color.saturation * 100));
                    break;
            }
        });
    },
    identify: function(callback) {
        lifx_remote.breatheEffect("id:"+ this.deviceId, 'green', null, 1, 3, false, true, 0.5, function (body) {
            callback();
        });
    },
    setLanColor: function(type, value, callback){
        var bulb = lifx_lan.bulbs[this.deviceId];

        if (!bulb) {
            callback(new Error("Device not found"), false);
            return;
        }

        var state = {
            hue: bulb.state.hue,
            saturation: bulb.state.saturation,
            brightness: bulb.state.brightness,
            kelvin: bulb.state.kelvin
        };

        var scale = type == "hue" ? 360 : 100;

        state[type] = Math.round(value * 65535 / scale) & 0xffff;
        lifx_lan.lightsColour(state.hue, state.saturation, state.brightness, state.kelvin, 0, bulb);

        callback(null);
    },
    setLanPower: function(state, callback){
        var bulb = lifx_lan.bulbs[this.deviceId];

        if (!bulb) {
            callback(new Error("Device not found"), false);
            return;
        }

        if (state) {
            lifx_lan.lightsOn(bulb);
        }
        else {
            lifx_lan.lightsOff(bulb);
        }

        callback(null);
    },
    setRemoteColor: function(type, value, callback){
        var color;

        switch(type) {
            case "brightness":
                color = "brightness:" + (value / 100);
                break;
            case "hue":
                color = "hue:" + value;
                break;
            case "saturation":
                color = "saturation:" + (value / 100);
                break;
        }

        lifx_remote.setColor("id:"+ this.deviceId, color, 0, null, function (body) {
            callback();
        });
    },
    setRemotePower: function(state, callback){
        var that = this;

        lifx_remote.setPower("id:"+ that.deviceId, (state == 1 ? "on" : "off"), 0, function (body) {
            callback();
        });
    },
    getServices: function() {
        var that = this;
        var services = []
        var service = new Service.Lightbulb(this.name);

        switch(use_lan) {
            case true:
            case "true":
                // gets and sets over the lan api
                service
                .getCharacteristic(Characteristic.On)
                .on('get', function(callback) { that.getLan("power", callback);})
                .on('set', function(value, callback) {that.setLanPower(value, callback);});

                service
                .addCharacteristic(Characteristic.Brightness)
                .on('get', function(callback) { that.getLan("brightness", callback);})
                .on('set', function(value, callback) { that.setLanColor("brightness", value, callback);});

                if (this.capabilities.has_color == true) {
                    service
                    .addCharacteristic(Characteristic.Hue)
                    .on('get', function(callback) { that.getLan("hue", callback);})
                    .on('set', function(value, callback) { that.setLanColor("hue", value, callback);});

                    service
                    .addCharacteristic(Characteristic.Saturation)
                    .on('get', function(callback) { that.getLan("saturation", callback);})
                    .on('set', function(value, callback) { that.setLanColor("saturation", value, callback);});
                }
                break;
            case "get":
                // gets over the lan api, sets over the remote api
                service
                .getCharacteristic(Characteristic.On)
                .on('get', function(callback) { that.getLan("power", callback);})
                .on('set', function(value, callback) {that.setRemotePower(value, callback);});

                service
                .addCharacteristic(Characteristic.Brightness)
                .on('get', function(callback) { that.getLan("brightness", callback);})
                .on('set', function(value, callback) { that.setRemoteColor("brightness", value, callback);});

                if (this.capabilities.has_color == true) {
                    service
                    .addCharacteristic(Characteristic.Hue)
                    .on('get', function(callback) { that.getLan("hue", callback);})
                    .on('set', function(value, callback) { that.setRemoteColor("hue", value, callback);});

                    service
                    .addCharacteristic(Characteristic.Saturation)
                    .on('get', function(callback) { that.getLan("saturation", callback);})
                    .on('set', function(value, callback) { that.setRemoteColor("saturation", value, callback);});
                }
                break;
            default:
                // gets and sets over the remote api
                service
                .getCharacteristic(Characteristic.On)
                .on('get', function(callback) { that.getRemote("power", callback);})
                .on('set', function(value, callback) {that.setRemotePower(value, callback);});

                service
                .addCharacteristic(Characteristic.Brightness)
                .on('get', function(callback) { that.getRemote("brightness", callback);})
                .on('set', function(value, callback) { that.setRemoteColor("brightness", value, callback);});

                if (this.capabilities.has_color == true) {
                    service
                    .addCharacteristic(Characteristic.Hue)
                    .on('get', function(callback) { that.getRemote("hue", callback);})
                    .on('set', function(value, callback) { that.setRemoteColor("hue", value, callback);});

                    service
                    .addCharacteristic(Characteristic.Saturation)
                    .on('get', function(callback) { that.getRemote("saturation", callback);})
                    .on('set', function(value, callback) { that.setRemoteColor("saturation", value, callback);});
                }
        }

        services.push(service);

        service = new Service.AccessoryInformation();

        service
            .setCharacteristic(Characteristic.Manufacturer, "LIFX")
            .setCharacteristic(Characteristic.Model, this.model)
            .setCharacteristic(Characteristic.SerialNumber, this.serial);

        services.push(service);

        return services;
    }
}

module.exports.accessory = LIFxBulbAccessory;
module.exports.platform = LIFxPlatform;
<|endoftext|>


<commit_before>define(['EventEmitter'], function(EventEmitter) {
	var parseJWT = function(token) { // todo: improve robustness of this parsing
		return token ? JSON.parse(atob(token.split('.')[1])) : token; // https://gist.github.com/katowulf/6231937
	};

	var EVENT_ADDED = 'added';
	var EVENT_REMOVED = 'removed';
	var EVENT_CHANGED = 'changed';
	var EVENT_EXPIRING = 'expiring';

	JWTStore.DEBUG = false;

	var debug = function() {
		if(console && console.log && JWTStore.DEBUG) {
			var args = Array.prototype.slice.call(arguments);
			args.unshift('jwtstore');
			console.log.apply(console, args);
		}
	};

	function JWTStore(prefix) {
		prefix = 'jwtstore::' + (prefix || 'default') + '::';

		var timers = this._timers = {};

		var localStorageForEach = function(callback, owner) {
			var items = [];
			for(var i = 0; i < localStorage.length; i++) { // race condition?
				var key = localStorage.key(i);
				if(key.indexOf(prefix) === 0) {
					items.push([key.slice(prefix.length), localStorage.getItem(key)]);
				}
			}

			debug('localStorageForEach', items);

			items.forEach(function(item) {
				callback.apply(owner, item);
			});
		};

		var emitter = new EventEmitter();

		['on', 'once', 'off'].forEach(function(f) {
			[EVENT_ADDED, EVENT_REMOVED, EVENT_CHANGED, EVENT_EXPIRING].forEach(function(e) {
				this[f + e.charAt(0).toUpperCase() + e.slice(1)] = function(callback) {
					emitter[f](e, callback);
					return this;
				};
			}, this);
		}, this);

		this.onAdded = function(callback) {
			emitter[f](EVENT_ADDED, callback);
			localStorageForEach(function(key, token) {
				setTimout(function() {
					callback(key, token, parseJWT(token));
				}, 5);
			});
			return this;
		};

		var getToken, setToken, removeToken;

		var updater = function(key) {
			return function(token) {
				setToken(key, token);
			};
		};

		getToken = this.getToken = function(key) {
			key = key || '';
			debug('getToken', key);
			var token = localStorage.getItem(prefix + key);
			if(token) {
				return {
					token: token,
					data: parseJWT(token)
				};
			} else {
				return {
					token: token,
					data: null
				};
			}
		};

		setToken = this.setToken = function(key, token) {
			key = key || '';
			debug('setToken', key, token);
			localStorage.setItem(prefix + key, token);
			clearTimeout(timers[key]);
			var data = parseJWT(token);
			var expiresIn = data.exp - Math.round(Date.now()/1000);
			debug('setToken expiresIn', expiresIn);
			if(expiresIn >= 60) {
				timers[key] = setTimeout(function() {
					emitter.emit(EVENT_EXPIRING, key, token, data, updater(key));
				}, (expiresIn - Math.round((Math.random()*30)+15))*1000);
			}
		};

		removeToken = this.removeToken = function(key) {
			debug('removeToken', key);
			localStorage.removeItem(prefix + key);
			clearTimeout(timers[key]);
		};

		(function(handler) {
			if (window.addEventListener) {
				window.addEventListener("storage", handler, false);
			} else {
				window.attachEvent("onstorage", handler);
			}
		})(function(e) {
			e = e || window.event;
			if(e.key.indexOf(prefix) === 0) {
				var key = e.key.slice(prefix.length);
				debug('storage', key, e.oldValue, e.newValue);
				clearTimeout(timers[key]);
				var token = e.newValue;
				if(e.oldValue == null) {
					var data = parseJWT(token);
					var expiresIn = data.exp - Math.round(Date.now()/1000);
					debug('localStorage added expiresIn', expiresIn);
					if(expiresIn >= 60) {
						timers[key] = setTimeout(function() {
							emitter.emit(EVENT_EXPIRING, key, token, data, updater(key));
						}, (expiresIn - Math.round((Math.random()*30)+15))*1000);
					}
					emitter.emit(EVENT_ADDED, key, token, data);
				} else if(e.newValue == null) {
					emitter.emit(EVENT_REMOVED, key);
				} else {
					var data = parseJWT(token);
					var expiresIn = data.exp - Math.round(Date.now()/1000);
					debug('localStorage changed expiresIn', expiresIn);
					if(expiresIn >= 60) {
						timers[key] = setTimeout(function() {
							emitter.emit(EVENT_EXPIRING, key, token, data, updater(key));
						}, (expiresIn - Math.round((Math.random()*30)+15))*1000);
					}
					emitter.emit(EVENT_CHANGED, key, token, data);
				}
			}
		});

		localStorageForEach(function(key, token) {
			debug('init', key, token);
			var data = parseJWT(token);
			if(data && data.exp) {
				var expiresIn = data.exp - Math.round(Date.now()/1000);
				debug('init expiresIn', expiresIn);
				if(expiresIn < 0) {
					removeToken(key);
				} else if(expiresIn < 60) {
					setTimeout(function() {
						emitter.emit(EVENT_EXPIRING, key, token, data, updater(key));
					}, 5);
				} else {
					timers[key] = setTimeout(function() {
						emitter.emit(EVENT_EXPIRING, key, token, data, updater(key));
					}, (expiresIn - Math.round((Math.random()*30)+15))*1000);
				}
			}
		});
	}

	return JWTStore;
});
<commit_msg>Remove temporary debugging.<commit_after>define(['EventEmitter'], function(EventEmitter) {
	var parseJWT = function(token) { // todo: improve robustness of this parsing
		return token ? JSON.parse(atob(token.split('.')[1])) : token; // https://gist.github.com/katowulf/6231937
	};

	var EVENT_ADDED = 'added';
	var EVENT_REMOVED = 'removed';
	var EVENT_CHANGED = 'changed';
	var EVENT_EXPIRING = 'expiring';

	JWTStore.DEBUG = false;

	var debug = function() {
		if(console && console.log && JWTStore.DEBUG) {
			var args = Array.prototype.slice.call(arguments);
			args.unshift('jwtstore');
			console.log.apply(console, args);
		}
	};

	function JWTStore(prefix) {
		prefix = 'jwtstore::' + (prefix || 'default') + '::';

		var timers = {};

		var localStorageForEach = function(callback, owner) {
			var items = [];
			for(var i = 0; i < localStorage.length; i++) { // race condition?
				var key = localStorage.key(i);
				if(key.indexOf(prefix) === 0) {
					items.push([key.slice(prefix.length), localStorage.getItem(key)]);
				}
			}

			debug('localStorageForEach', items);

			items.forEach(function(item) {
				callback.apply(owner, item);
			});
		};

		var emitter = new EventEmitter();

		['on', 'once', 'off'].forEach(function(f) {
			[EVENT_ADDED, EVENT_REMOVED, EVENT_CHANGED, EVENT_EXPIRING].forEach(function(e) {
				this[f + e.charAt(0).toUpperCase() + e.slice(1)] = function(callback) {
					emitter[f](e, callback);
					return this;
				};
			}, this);
		}, this);

		this.onAdded = function(callback) {
			emitter[f](EVENT_ADDED, callback);
			localStorageForEach(function(key, token) {
				setTimout(function() {
					callback(key, token, parseJWT(token));
				}, 5);
			});
			return this;
		};

		var getToken, setToken, removeToken;

		var updater = function(key) {
			return function(token) {
				setToken(key, token);
			};
		};

		getToken = this.getToken = function(key) {
			key = key || '';
			debug('getToken', key);
			var token = localStorage.getItem(prefix + key);
			if(token) {
				return {
					token: token,
					data: parseJWT(token)
				};
			} else {
				return {
					token: token,
					data: null
				};
			}
		};

		setToken = this.setToken = function(key, token) {
			key = key || '';
			debug('setToken', key, token);
			localStorage.setItem(prefix + key, token);
			clearTimeout(timers[key]);
			var data = parseJWT(token);
			var expiresIn = data.exp - Math.round(Date.now()/1000);
			debug('setToken expiresIn', expiresIn);
			if(expiresIn >= 60) {
				timers[key] = setTimeout(function() {
					emitter.emit(EVENT_EXPIRING, key, token, data, updater(key));
				}, (expiresIn - Math.round((Math.random()*30)+15))*1000);
			}
		};

		removeToken = this.removeToken = function(key) {
			debug('removeToken', key);
			localStorage.removeItem(prefix + key);
			clearTimeout(timers[key]);
		};

		(function(handler) {
			if (window.addEventListener) {
				window.addEventListener("storage", handler, false);
			} else {
				window.attachEvent("onstorage", handler);
			}
		})(function(e) {
			e = e || window.event;
			if(e.key.indexOf(prefix) === 0) {
				var key = e.key.slice(prefix.length);
				debug('storage', key, e.oldValue, e.newValue);
				clearTimeout(timers[key]);
				var token = e.newValue;
				if(e.oldValue == null) {
					var data = parseJWT(token);
					var expiresIn = data.exp - Math.round(Date.now()/1000);
					debug('localStorage added expiresIn', expiresIn);
					if(expiresIn >= 60) {
						timers[key] = setTimeout(function() {
							emitter.emit(EVENT_EXPIRING, key, token, data, updater(key));
						}, (expiresIn - Math.round((Math.random()*30)+15))*1000);
					}
					emitter.emit(EVENT_ADDED, key, token, data);
				} else if(e.newValue == null) {
					emitter.emit(EVENT_REMOVED, key);
				} else {
					var data = parseJWT(token);
					var expiresIn = data.exp - Math.round(Date.now()/1000);
					debug('localStorage changed expiresIn', expiresIn);
					if(expiresIn >= 60) {
						timers[key] = setTimeout(function() {
							emitter.emit(EVENT_EXPIRING, key, token, data, updater(key));
						}, (expiresIn - Math.round((Math.random()*30)+15))*1000);
					}
					emitter.emit(EVENT_CHANGED, key, token, data);
				}
			}
		});

		localStorageForEach(function(key, token) {
			debug('init', key, token);
			var data = parseJWT(token);
			if(data && data.exp) {
				var expiresIn = data.exp - Math.round(Date.now()/1000);
				debug('init expiresIn', expiresIn);
				if(expiresIn < 0) {
					removeToken(key);
				} else if(expiresIn < 60) {
					setTimeout(function() {
						emitter.emit(EVENT_EXPIRING, key, token, data, updater(key));
					}, 5);
				} else {
					timers[key] = setTimeout(function() {
						emitter.emit(EVENT_EXPIRING, key, token, data, updater(key));
					}, (expiresIn - Math.round((Math.random()*30)+15))*1000);
				}
			}
		});
	}

	return JWTStore;
});
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies.
 */
var passport = require('passport'),
	SamlStrategy = require('passport-saml').Strategy,
  fs = require('fs'),
	User = require('mongoose').model('User'),
  pvk = fs.readFileSync('./config/keys/mykey.key', 'utf-8');

// Set in global scope, so it can be used again for the metadata route
global.SAMLStrategy = new SamlStrategy({
      path: '/login/callback',
      entryPoint: 'https://engine.surfconext.nl/authentication/idp/single-sign-on',
      issuer: 'goalsetting.ihatestatistics.com',
      //decryptionPvk: pvk,
      //cert: 'MIID3zCCAsegAwIBAgIJAMVC9xn1ZfsuMA0GCSqGSIb3DQEBCwUAMIGFMQswCQYDVQQGEwJOTDEQMA4GA1UECAwHVXRyZWNodDEQMA4GA1UEBwwHVXRyZWNodDEVMBMGA1UECgwMU1VSRm5ldCBCLlYuMRMwEQYDVQQLDApTVVJGY29uZXh0MSYwJAYDVQQDDB1lbmdpbmUuc3VyZmNvbmV4dC5ubCAyMDE0MDUwNTAeFw0xNDA1MDUxNDIyMzVaFw0xOTA1MDUxNDIyMzVaMIGFMQswCQYDVQQGEwJOTDEQMA4GA1UECAwHVXRyZWNodDEQMA4GA1UEBwwHVXRyZWNodDEVMBMGA1UECgwMU1VSRm5ldCBCLlYuMRMwEQYDVQQLDApTVVJGY29uZXh0MSYwJAYDVQQDDB1lbmdpbmUuc3VyZmNvbmV4dC5ubCAyMDE0MDUwNTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKthMDbB0jKHefPzmRu9t2h7iLP4wAXr42bHpjzTEk6gttHFb4l/hFiz1YBI88TjiH6hVjnozo/YHA2c51us+Y7g0XoS7653lbUN/EHzvDMuyis4Xi2Ijf1A/OUQfH1iFUWttIgtWK9+fatXoGUS6tirQvrzVh6ZstEp1xbpo1SF6UoVl+fh7tM81qz+Crr/Kroan0UjpZOFTwxPoK6fdLgMAieKSCRmBGpbJHbQ2xxbdykBBrBbdfzIX4CDepfjE9h/40ldw5jRn3e392jrS6htk23N9BWWrpBT5QCk0kH3h/6F1Dm6TkyG9CDtt73/anuRkvXbeygI4wml9bL3rE8CAwEAAaNQME4wHQYDVR0OBBYEFD+Ac7akFxaMhBQAjVfvgGfY8hNKMB8GA1UdIwQYMBaAFD+Ac7akFxaMhBQAjVfvgGfY8hNKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAC8L9D67CxIhGo5aGVu63WqRHBNOdo/FAGI7LURDFeRmG5nRw/VXzJLGJksh4FSkx7aPrxNWF1uFiDZ80EuYQuIv7bDLblK31ZEbdg1R9LgiZCdYSr464I7yXQY9o6FiNtSKZkQO8EsscJPPy/Zp4uHAnADWACkOUHiCbcKiUUFu66dX0Wr/v53Gekz487GgVRs8HEeT9MU1reBKRgdENR8PNg4rbQfLc3YQKLWK7yWnn/RenjDpuCiePj8N8/80tGgrNgK/6fzM3zI18sSywnXLswxqDb/J+jgVxnQ6MrsTf1urM8MnfcxG/82oHIwfMh/sXPCZpo+DTLkhQxctJ3M=',
      identifierFormat: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
      callbackUrl: 'https://goalsetting.ihatestatistics.com/login/callback',
      acceptedClockSkewMs: -1
    },
    function(profile, done) {
      User.find({email: profile.email}).exec(function (err, user) {
        if (err) {
          return done(err);
        } else {
          if (user) {
            return done(null, user);
          }

          var newUser = new User();
          newUser.firstName = profile['urn:mace:dir:attribute-def:givenName'];
          newUser.lastName = profile['urn:mace:dir:attribute-def:sn'];
          newUser.email = profile['urn:mace:dir:attribute-def:mail'];
          newUser.provider = 'SurfConext';
          newUser.roles = [];
          newUser.roles.push(profile['urn:mace:dir:attribute-def:eduPersonAffiliation']);
          newUser.save(function (err) {
            if (err) {
              return done(err);
            }

            return done(null, newUser);
          });
        }
      });
    }
);

module.exports = function() {
  var samlStrategy = global.SAMLStrategy;

  // Use SAML strategy for Surfconext
  passport.use(samlStrategy);
};<commit_msg>Check what happening<commit_after>'use strict';

/**
 * Module dependencies.
 */
var passport = require('passport'),
	SamlStrategy = require('passport-saml').Strategy,
  fs = require('fs'),
	User = require('mongoose').model('User'),
  pvk = fs.readFileSync('./config/keys/mykey.key', 'utf-8');

// Set in global scope, so it can be used again for the metadata route
global.SAMLStrategy = new SamlStrategy({
      path: '/login/callback',
      entryPoint: 'https://engine.surfconext.nl/authentication/idp/single-sign-on',
      issuer: 'goalsetting.ihatestatistics.com',
      //decryptionPvk: pvk,
      //cert: 'MIID3zCCAsegAwIBAgIJAMVC9xn1ZfsuMA0GCSqGSIb3DQEBCwUAMIGFMQswCQYDVQQGEwJOTDEQMA4GA1UECAwHVXRyZWNodDEQMA4GA1UEBwwHVXRyZWNodDEVMBMGA1UECgwMU1VSRm5ldCBCLlYuMRMwEQYDVQQLDApTVVJGY29uZXh0MSYwJAYDVQQDDB1lbmdpbmUuc3VyZmNvbmV4dC5ubCAyMDE0MDUwNTAeFw0xNDA1MDUxNDIyMzVaFw0xOTA1MDUxNDIyMzVaMIGFMQswCQYDVQQGEwJOTDEQMA4GA1UECAwHVXRyZWNodDEQMA4GA1UEBwwHVXRyZWNodDEVMBMGA1UECgwMU1VSRm5ldCBCLlYuMRMwEQYDVQQLDApTVVJGY29uZXh0MSYwJAYDVQQDDB1lbmdpbmUuc3VyZmNvbmV4dC5ubCAyMDE0MDUwNTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKthMDbB0jKHefPzmRu9t2h7iLP4wAXr42bHpjzTEk6gttHFb4l/hFiz1YBI88TjiH6hVjnozo/YHA2c51us+Y7g0XoS7653lbUN/EHzvDMuyis4Xi2Ijf1A/OUQfH1iFUWttIgtWK9+fatXoGUS6tirQvrzVh6ZstEp1xbpo1SF6UoVl+fh7tM81qz+Crr/Kroan0UjpZOFTwxPoK6fdLgMAieKSCRmBGpbJHbQ2xxbdykBBrBbdfzIX4CDepfjE9h/40ldw5jRn3e392jrS6htk23N9BWWrpBT5QCk0kH3h/6F1Dm6TkyG9CDtt73/anuRkvXbeygI4wml9bL3rE8CAwEAAaNQME4wHQYDVR0OBBYEFD+Ac7akFxaMhBQAjVfvgGfY8hNKMB8GA1UdIwQYMBaAFD+Ac7akFxaMhBQAjVfvgGfY8hNKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAC8L9D67CxIhGo5aGVu63WqRHBNOdo/FAGI7LURDFeRmG5nRw/VXzJLGJksh4FSkx7aPrxNWF1uFiDZ80EuYQuIv7bDLblK31ZEbdg1R9LgiZCdYSr464I7yXQY9o6FiNtSKZkQO8EsscJPPy/Zp4uHAnADWACkOUHiCbcKiUUFu66dX0Wr/v53Gekz487GgVRs8HEeT9MU1reBKRgdENR8PNg4rbQfLc3YQKLWK7yWnn/RenjDpuCiePj8N8/80tGgrNgK/6fzM3zI18sSywnXLswxqDb/J+jgVxnQ6MrsTf1urM8MnfcxG/82oHIwfMh/sXPCZpo+DTLkhQxctJ3M=',
      identifierFormat: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
      callbackUrl: 'https://goalsetting.ihatestatistics.com/login/callback',
      acceptedClockSkewMs: -1
    },
    function(profile, done) {
      User.find({email: profile.email}).exec(function (err, user) {
        if (err) {
          return done(err);
        } else {
          if (user) {
            console.log('found');
            console.log(user);
            return done(null, user);
          }

          var newUser = new User();
          newUser.firstName = profile['urn:mace:dir:attribute-def:givenName'];
          newUser.lastName = profile['urn:mace:dir:attribute-def:sn'];
          newUser.email = profile['urn:mace:dir:attribute-def:mail'];
          newUser.provider = 'SurfConext';
          newUser.roles = [];
          newUser.roles.push(profile['urn:mace:dir:attribute-def:eduPersonAffiliation']);
          newUser.save(function (err) {
            if (err) {
              return done(err);
            }
            console.log('new');
            console.log(newUser);
            return done(null, newUser);
          });
        }
      });
    }
);

module.exports = function() {
  var samlStrategy = global.SAMLStrategy;

  // Use SAML strategy for Surfconext
  passport.use(samlStrategy);
};<|endoftext|>


<commit_before>var express = require('express');
var path = require('path');
var logger = require('morgan');
var bodyParser = require('body-parser');
var favicon = require('serve-favicon');
var cors = require('cors');
var lawRoutes = require('./routes/laws');
var viewRoutes = require('./routes/views');
var appRoutes = require('./routes/app');
var port = Number(process.env.PORT || 8080);

var app = express();

app.use(favicon(__dirname + '/public/favicon.ico'));
app.engine('jade', require('jade').__express);
app.set('view engine', 'jade');
app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:true}));
app.use(cors());
app.use('/api', lawRoutes);
app.use('', viewRoutes);
app.use('/app', appRoutes);

if(process.env.NODE_ENV === 'development') {
  app.locals.pretty = true;
  app.use(logger('dev'));
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

app.use(function(req, res, next){
  res.status(404);
  if (req.accepts('html')) {
    res.render('404', { url: req.url, title: req.url + ' - Not found' });
    return;
  }
  if (req.accepts('json')) {
    res.send({ error: 'Not found' });
    return;
  }
  res.type('txt').send('Not found');
});

module.exports = app;
<commit_msg>pretty print json<commit_after>var express = require('express');
var path = require('path');
var logger = require('morgan');
var bodyParser = require('body-parser');
var favicon = require('serve-favicon');
var cors = require('cors');
var lawRoutes = require('./routes/laws');
var viewRoutes = require('./routes/views');
var appRoutes = require('./routes/app');
var port = Number(process.env.PORT || 8080);

var app = express();

app.use(favicon(__dirname + '/public/favicon.ico'));
app.engine('jade', require('jade').__express);
app.set('view engine', 'jade');
app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:true}));
app.use(cors());
app.use('/api', lawRoutes);
app.use('', viewRoutes);
app.use('/app', appRoutes);
app.set('json spaces', 2);

if(process.env.NODE_ENV === 'development') {
  app.locals.pretty = true;
  app.use(logger('dev'));
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

app.use(function(req, res, next){
  res.status(404);
  if (req.accepts('html')) {
    res.render('404', { url: req.url, title: req.url + ' - Not found' });
    return;
  }
  if (req.accepts('json')) {
    res.send({ error: 'Not found' });
    return;
  }
  res.type('txt').send('Not found');
});

module.exports = app;
<|endoftext|>


<commit_before>/**
* Pull in required modules
**/
const EventEmitter  = require('events');
const async         = require('async');
const url           = require('url');
const dns           = require('dns');
const _             = require('lodash');
const S             = require('string');
const crypto        = require('crypto');

const api           = require('../api');
const Models        = require('../models');
const Validate      = require('../utils/validate');
const LANG          = require('../../lang/en.json');
const Channel       = require('../utils/channel');

/**
* Expose our creation class that will be called
* with properties.
**/
module.exports = exports = function(params) {

  /**
  * Local parameters
  **/
  var channel               = Channel();
  var model                 = {};
  var issues                = [];
  var currentReportID       = null;
  var currentCrawlID        = null;
  var isDone                = false;
  var isStarted             = false;

  /**
  * The Report object to return that we can use
  **/
  var Report = new EventEmitter();

  /**
  * Handle a error from the channel, so a timeout
  * or the connection broke.
  **/
  channel.on('error', function(err){

    // stop the show here and report this error
    Report.handleCallback(err);

  });

  // configure the events
  channel.on('message', _.bind(function(data) {

    // handle the message
    Report.handleMessage.push(data);

  }, this));

  /**
  * Set our local parameters to use
  **/
  Report.config     = _.extend({

    recursive:  false,
    bail:       false,
    url:        null,
    token:      null,
    limit:      null,
    filters:    [],
    patterns:   []

  }, params);

  /**
  * Returns the result from the run
  **/
  Report.getResult = function() {

    // depending on type
    if(this.isRecursive() === true)
      return Models.createReport(model);
    else
      return Models.createPage(model);

  };

  /**
  * Does the final call to done,
  * this function ensure the call
  * is only done once and removes
  * all the event listeners just
  * to be safe.
  **/
  Report.handleCallback = _.once(function(err) {

    // try to close the channel if not closed
    try {

      // call #close() to free up fd
      channel.close();

    } catch(err) {}

    // get the result
    var result = Report.getResult();

    // emit the ending event
    if(err) {

      // emit the error
      Report.emit('error',  err);

    }

    // emit the end
    Report.emit('done', result);
    Report.emit('end',  result);

    // stop it
    Report.removeAllListeners();

  }, this);

  /**
  * Returns the list of filters to use
  **/
  Report.getFilters = function() { return (this.config || {}).filters || []; }

  /**
  * Returns the list of patterns we should use
  **/
  Report.getPatterns = function() { return (this.config || {}).patterns || []; }

  /**
  * Returns the limit of pages to check in this crawl
  **/
  Report.getLimit = function() { return (this.config || {}).limit || null; }

  /**
  * Returns the token used for this request
  **/
  Report.getToken = function() { return (this.config || {}).token || null; }

  /**
  * Returns true if this is recursive, IE "crawl"
  **/
  Report.isRecursive = function() { return (this.config || {}).recursive === true; }

  /**
  * Returns true if configured to bail after a error
  **/
  Report.isBail = function() { return (this.config || {}).bail === true; }

  /**
  * Returns the URL that this report was created on
  **/
  Report.getURL = function() { return (this.config || {}).url || null; }

  /**
  * Parses the domain
  **/
  Report.getFriendlyName = function() {

    // parse the url
    var uri = url.parse(this.getURL() || '');

    // must have a hostname
    if(!uri.hostname) return this.getURL();

    // the list of params we are going to build out
    var nameStrs = [];

    // if https add it
    if(uri.protocol === 'https:') {

      // add the protocol
      nameStrs.push(uri.protocol + '//');

    }

    // add the hostname
    nameStrs.push(uri.hostname);

    // add the port if not default
    if(uri.path != '/')
      nameStrs.push(uri.path);

    // return the full name
    return nameStrs.join('');

  };

  /**
  * Update the message we got
  **/
  Report.handleMessage = async.queue(function(data, cb) {

    // get the item details
    var item = (data || {}).item || {};

    // right so first check that this messages matches
    // either a crawl ora  report we are looking after.
    if(currentCrawlID !== null) {

      // should match our current crawl
      if(data.crawlid != currentCrawlID &&
          item.crawlid != currentCrawlID) return cb(null);

      // define rules if nothing defined yet
      if(!model.rules) model.rules = [];

      // check th eitem
      if(data.action == 'stats') {

        // update our crawl item
        model = _.extend({}, model, item);

        // check the status
        if(item.status === 40)
          Report.handleCallback();

        // emit out update event
        Report.emit('update', Report.getResult());

      } else if(data.action == 'issue') {

        // flag to track if we found it
        var foundFlag = false;

        // loop all the current rules and update ours
        for(var a = 0; a < model.rules.length; a++) {

          // handle it
          if( model.rules[a].uid != item.uid ) continue

          // update it
          model.rules[a] = _.extend({}, model.rules[a], item);

          // found it
          foundFlag = true;

          // stop right there to save a few ms
          break;

        }

        // report the issue we found
        Report.emit('issue', item);

        // add it
        if(foundFlag == false)
          model.rules.push(item);

      } else if(data.action == 'report') {

        // emit page
        Report.emit('page', item);

      }

    } else if(currentReportID !== null) {

      // check that the reportid matches
      if(currentReportID !== data.uid) return cb(null);

      // then check the action
      if(data.event === 'page' ||
          data.event === 'session') {

        // update the item
        model = _.extend({}, model, item);

        // emit update
        Report.emit('update', Report.getResult());

        // check if we got a done signal :)
        if(item.status === 'done')
          Report.handleCallback();

      } else if(data.event === 'test') {

        // loop the test
        for(var a = 0; a < (model.tests || []).length; a++) {

          // check if the id matches
          if( model.tests[a].id !== data.testid ) continue;

          // update our test objects
          model.tests[a] = _.extend({}, model.tests[a], item);

          // emit update
          Report.emit('test', _.extend({}, model.tests[a]));

          // done processing
          break;

        }

      } else if(data.event === 'issue') {

        // loop the test
        if(!model.issues) model.issues = [];

        // check if already defined
        var alreadyDefined = false;
        var definedIndex = 0;

        // loop the current issues
        for(var a = 0; a < (model.issues || []).length; a++) {

          // is defined ?
          if(model.issues[a].uid === item.uid &&
              model.issues[a].test === item.test){

            // update
            model.issues[a] = _.extend({}, item);

            // set it
            alreadyDefined = true;

            // stop
            break;

          }

        }

        // is defined ?
        if(alreadyDefined === false) model.issues.push(item);

      }

      // emit update
      Report.emit('update', Report.getResult());

    }

    // done
    cb(null);

  }, 1);

  /**
  * Does the request to create a crawl
  **/
  Report.createCrawl = function(params, fn) {

    // parse the url
    var uri = url.parse(params.url);

    /**
    * Submit to the actual API
    **/
    api.submitCrawl(

      params,
      function(err, response) {

        // handle the error if any
        if(err) return fn(err);

        // was this a error ?
        if(response.status === 'error') {

          // yes output that instead then
          return fn(new Error(response.message || 'Something went wrong while starting the report'))

        }

        // set the id
        currentCrawlID = response.item.id;

        // set the initial item
        model = response.item;

        // register for events to this report
        channel.subscribe('reports.' + response.item.uid);

        // finish with the callback
        return fn(err, response);

      }

    );

  };

  /**
  * Does the request to start a report on the service
  **/
  Report.create = function(params, fn) {

    /**
    * Submit to the actual API
    **/
    api.submit(

      params,
      function(err, response) {

        // handle the error if any
        if(err) return fn(err);

        // was this a error ?
        if(response.status === 'error') {

          // yes output that instead then
          return fn(new Error(response.message || 'Something went wrong while starting the report'))

        }

        // get the item
        var item = response.item;

        // set the id
        currentReportID = item.uid;

        // set the initial item
        model = response;

        // register for events to this report
        channel.subscribe('report'  + item.uid);
        channel.subscribe('report.' + item.uid);

        // finish with the callback
        fn(null);

      }

    );

  };

  /**
  * Appends the protocol if missing
  **/
  Report.prefixProtocol = function(url) {

    // lowercase the url
    var cleanedUrl = ('' + url).toLowerCase();
    if( S(cleanedUrl).startsWith("http://") === false &&
      S(cleanedUrl).startsWith("https://") === false )
        return 'http://' + url;

    // already has the protcol, just return it
    return url;

  };

  /**
  * Validates the passed domain, must be a valid domain
  **/
  Report.validateTarget = function(target, fn) {

    // first check if a ip ... ?
    Validate.isIP(target, function(err, result) {

      // error ?
      if(err) return fn(err, false);

      // output result
      if(result === true) {

        // check if not local
        return Validate.isLocalIP(target, function(err, result) {

          // done with error
          if(err) return fn(err);

          // check if we got a result
          if(result === true)
            return fn(new Error(LANG.ERROR_IP_LOCALHOST));

          // all good
          fn(null);

        });

      }

      // add the target
      var cleanedTarget   = (target || '').toLowerCase();
      cleanedTarget       = Report.prefixProtocol(cleanedTarget);

      // parse the url
      var parsedUrl = url.parse(cleanedTarget);

      // must be defined
      if(!parsedUrl) return fn(new Error(LANG.ERROR_INVALID_DOMAIN));

      // get domain path
      Validate.isDomain(parsedUrl.hostname, function(err, result, address) {

        // check if we got a error
        if(err) return fn(err);

        // check if we found it
        if(result === false) return fn(new Error(LANG.ERROR_IP_LOCALHOST));

        // validate the address
        Validate.isLocalIP(address, function(err, result) {

          // done with error
          if(err) return fn(err);

          // check if we got a result
          if(result === true)
            return fn(new Error(LANG.ERROR_IP_LOCALHOST));

          // all good
          fn(null);

        });

      });

    });

  };

  /**
  * Start the report
  **/
  Report.start    = function(fn) {

    // sanity check if we found a callback
    if(!fn)
      throw new Error(LANG.ERROR_CALLBACK_REQUIRED);

    // check if started
    if(isStarted === true) {
      fn(new Error(LANG.ERROR_ALREADY_STARTED));
      return this;
    }

    // mark as started
    isStarted = true;

    // check if this is a crawl, with no token ...
    if(this.isRecursive() === true && this.getToken() === null) {
      fn(new Error(LANG.ERROR_RECURSIVE_TOKEN));
      return this;
    }

    // validate the domain
    this.validateTarget(this.getURL(), _.bind(function(err) {

      // did we get a error ?
      if(err) return fn(err);

      // boot our channel
      channel.connect();

      // wait till the channel is ready
      channel.once('ready', _.bind(function() {

        // clean the url
        var cleanedUrl = Report.prefixProtocol(this.getURL());

        // parse the url
        var uri = url.parse(cleanedUrl);

        // create the sha1 engine
        var shasum = crypto.createHash('sha1');

        // add our digest
        shasum.update(cleanedUrl);

        // get the key
        var hash = shasum.digest('hex');

        // start to listen for a domain wide key,
        // as to make sure we don't miss anything.
        channel.subscribe('url.' + hash);

        // according to type start
        if(this.isRecursive()) {

          // start a multi-page crawl
          Report.createCrawl({

            url:          this.getURL(),
            token:        this.getToken(),
            bail:         this.isBail(),
            recursive:    this.isRecursive(),
            patterns:     this.getPatterns()

          }, function(err, response) {

            // all good, run this report
            return fn(err, response);

          });

        } else {

          // start a single page report
          Report.create({

            url:          this.getURL(),
            token:        this.getToken()

          }, function(err, response) {

            // all good, run this report
            return fn(err, response);

          });

        }

      }, this));

    }, this));

    // return our instance
    return this;

  };

  // return the report object to use
  return Report;

};
<commit_msg>Added a report fallback http check<commit_after>/**
* Pull in required modules
**/
const EventEmitter  = require('events');
const async         = require('async');
const url           = require('url');
const dns           = require('dns');
const _             = require('lodash');
const S             = require('string');
const crypto        = require('crypto');

const api           = require('../api');
const Models        = require('../models');
const Validate      = require('../utils/validate');
const LANG          = require('../../lang/en.json');
const Channel       = require('../utils/channel');

/**
* Expose our creation class that will be called
* with properties.
**/
module.exports = exports = function(params) {

  /**
  * Local parameters
  **/
  var channel               = Channel();
  var model                 = {};
  var issues                = [];
  var currentReportID       = null;
  var currentCrawlID        = null;
  var isDone                = false;
  var isStarted             = false;

  /**
  * The Report object to return that we can use
  **/
  var Report = new EventEmitter();

  /**
  * Handle a error from the channel, so a timeout
  * or the connection broke.
  **/
  channel.on('error', function(err){

    // stop the show here and report this error
    Report.handleCallback(err);

  });

  // configure the events
  channel.on('message', _.bind(function(data) {

    // handle the message
    Report.handleMessage.push(data);

  }, this));

  /**
  * Set our local parameters to use
  **/
  Report.config     = _.extend({

    recursive:  false,
    bail:       false,
    url:        null,
    token:      null,
    limit:      null,
    filters:    [],
    patterns:   [],
    update:     4

  }, params);

  /**
  * Returns the result from the run
  **/
  Report.getResult = function() {

    // depending on type
    if(this.isRecursive() === true)
      return Models.createReport(model);
    else
      return Models.createPage(model);

  };

  /**
  * Does the final call to done,
  * this function ensure the call
  * is only done once and removes
  * all the event listeners just
  * to be safe.
  **/
  Report.handleCallback = _.once(function(err) {

    // try to close the channel if not closed
    try {

      // call #close() to free up fd
      channel.close();

    } catch(err) {}

    // get the result
    var result = Report.getResult();

    // emit the ending event
    if(err) {

      // emit the error
      Report.emit('error',  err);

    }

    // emit the end
    Report.emit('done', result);
    Report.emit('end',  result);

    // stop it
    Report.removeAllListeners();

  }, this);

  /**
  * Returns the list of filters to use
  **/
  Report.getFilters = function() { return (this.config || {}).filters || []; }

  /**
  * Returns the list of patterns we should use
  **/
  Report.getPatterns = function() { return (this.config || {}).patterns || []; }

  /**
  * Returns the limit of pages to check in this crawl
  **/
  Report.getLimit = function() { return (this.config || {}).limit || null; }

  /**
  * Returns the token used for this request
  **/
  Report.getToken = function() { return (this.config || {}).token || null; }

  /**
  * Returns true if this is recursive, IE "crawl"
  **/
  Report.isRecursive = function() { return (this.config || {}).recursive === true; }

  /**
  * Returns true if configured to bail after a error
  **/
  Report.isBail = function() { return (this.config || {}).bail === true; }

  /**
  * Returns the URL that this report was created on
  **/
  Report.getURL = function() { return (this.config || {}).url || null; }

  /**
  * Parses the domain
  **/
  Report.getFriendlyName = function() {

    // parse the url
    var uri = url.parse(this.getURL() || '');

    // must have a hostname
    if(!uri.hostname) return this.getURL();

    // the list of params we are going to build out
    var nameStrs = [];

    // if https add it
    if(uri.protocol === 'https:') {

      // add the protocol
      nameStrs.push(uri.protocol + '//');

    }

    // add the hostname
    nameStrs.push(uri.hostname);

    // add the port if not default
    if(uri.path != '/')
      nameStrs.push(uri.path);

    // return the full name
    return nameStrs.join('');

  };

  /**
  * Update the message we got
  **/
  Report.handleMessage = async.queue(function(data, cb) {

    // get the item details
    var item = (data || {}).item || {};

    // right so first check that this messages matches
    // either a crawl ora  report we are looking after.
    if(currentCrawlID !== null) {

      // should match our current crawl
      if(data.crawlid != currentCrawlID &&
          item.crawlid != currentCrawlID) return cb(null);

      // define rules if nothing defined yet
      if(!model.rules) model.rules = [];

      // check th eitem
      if(data.action == 'stats') {

        // update our crawl item
        model = _.extend({}, model, item);

        // check the status
        if(item.status === 40)
          Report.handleCallback();

        // emit out update event
        Report.emit('update', Report.getResult());

      } else if(data.action == 'issue') {

        // flag to track if we found it
        var foundFlag = false;

        // loop all the current rules and update ours
        for(var a = 0; a < model.rules.length; a++) {

          // handle it
          if( model.rules[a].uid != item.uid ) continue

          // update it
          model.rules[a] = _.extend({}, model.rules[a], item);

          // found it
          foundFlag = true;

          // stop right there to save a few ms
          break;

        }

        // report the issue we found
        Report.emit('issue', item);

        // add it
        if(foundFlag == false)
          model.rules.push(item);

      } else if(data.action == 'report') {

        // emit page
        Report.emit('page', item);

      }

    } else if(currentReportID !== null) {

      // check that the reportid matches
      if(currentReportID !== data.uid) return cb(null);

      // then check the action
      if(data.event === 'page' ||
          data.event === 'session') {

        // update the item
        model = _.extend({}, model, item);

        // emit update
        Report.emit('update', Report.getResult());

        // check if we got a done signal :)
        if(item.status === 'done')
          Report.handleCallback();

      } else if(data.event === 'test') {

        // loop the test
        for(var a = 0; a < (model.tests || []).length; a++) {

          // check if the id matches
          if( model.tests[a].id !== data.testid ) continue;

          // update our test objects
          model.tests[a] = _.extend({}, model.tests[a], item);

          // emit update
          Report.emit('test', _.extend({}, model.tests[a]));

          // done processing
          break;

        }

      } else if(data.event === 'issue') {

        // loop the test
        if(!model.issues) model.issues = [];

        // check if already defined
        var alreadyDefined = false;
        var definedIndex = 0;

        // loop the current issues
        for(var a = 0; a < (model.issues || []).length; a++) {

          // is defined ?
          if(model.issues[a].uid === item.uid &&
              model.issues[a].test === item.test){

            // update
            model.issues[a] = _.extend({}, item);

            // set it
            alreadyDefined = true;

            // stop
            break;

          }

        }

        // is defined ?
        if(alreadyDefined === false) model.issues.push(item);

      }

      // emit update
      Report.emit('update', Report.getResult());

    }

    // done
    cb(null);

  }, 1);

  /**
  * Does the request to create a crawl
  **/
  Report.createCrawl = function(params, fn) {

    // parse the url
    var uri = url.parse(params.url);

    /**
    * Submit to the actual API
    **/
    api.submitCrawl(

      params,
      function(err, response) {

        // handle the error if any
        if(err) return fn(err);

        // was this a error ?
        if(response.status === 'error') {

          // yes output that instead then
          return fn(new Error(response.message || 'Something went wrong while starting the report'))

        }

        // set the id
        currentCrawlID = response.item.id;

        // set the initial item
        model = response.item;

        // register for events to this report
        channel.subscribe('reports.' + response.item.uid);
        channel.subscribe('report.' + response.item.uid);

        // add the listener to check when done
        Report.createStatusChecker();

        // finish with the callback
        return fn(err, response);

      }

    );

  };

  /**
  * Does the request to start a report on the service
  **/
  Report.create = function(params, fn) {

    /**
    * Submit to the actual API
    **/
    api.submit(

      params,
      function(err, response) {

        // handle the error if any
        if(err) return fn(err);

        // was this a error ?
        if(response.status === 'error') {

          // yes output that instead then
          return fn(new Error(response.message || 'Something went wrong while starting the report'))

        }

        // get the item
        var item = response.item;

        // set the id
        currentReportID = item.uid;

        // set the initial item
        model = response;

        // register for events to this report
        channel.subscribe('reports'  + item.uid);
        channel.subscribe('report.' + item.uid);

        // add the listener to check when done
        Report.createStatusChecker();

        // finish with the callback
        fn(null);

      }

    );

  };

  /**
  * Appends the protocol if missing
  **/
  Report.prefixProtocol = function(url) {

    // lowercase the url
    var cleanedUrl = ('' + url).toLowerCase();
    if( S(cleanedUrl).startsWith("http://") === false &&
      S(cleanedUrl).startsWith("https://") === false )
        return 'http://' + url;

    // already has the protcol, just return it
    return url;

  };

  /**
  * Validates the passed domain, must be a valid domain
  **/
  Report.validateTarget = function(target, fn) {

    // first check if a ip ... ?
    Validate.isIP(target, function(err, result) {

      // error ?
      if(err) return fn(err, false);

      // output result
      if(result === true) {

        // check if not local
        return Validate.isLocalIP(target, function(err, result) {

          // done with error
          if(err) return fn(err);

          // check if we got a result
          if(result === true)
            return fn(new Error(LANG.ERROR_IP_LOCALHOST));

          // all good
          fn(null);

        });

      }

      // add the target
      var cleanedTarget   = (target || '').toLowerCase();
      cleanedTarget       = Report.prefixProtocol(cleanedTarget);

      // parse the url
      var parsedUrl = url.parse(cleanedTarget);

      // must be defined
      if(!parsedUrl) return fn(new Error(LANG.ERROR_INVALID_DOMAIN));

      // get domain path
      Validate.isDomain(parsedUrl.hostname, function(err, result, address) {

        // check if we got a error
        if(err) return fn(err);

        // check if we found it
        if(result === false) return fn(new Error(LANG.ERROR_IP_LOCALHOST));

        // validate the address
        Validate.isLocalIP(address, function(err, result) {

          // done with error
          if(err) return fn(err);

          // check if we got a result
          if(result === true)
            return fn(new Error(LANG.ERROR_IP_LOCALHOST));

          // all good
          fn(null);

        });

      });

    });

  };

  /**
  * Creates the status checker
  **/
  Report.createStatusChecker = function() {

    // already created ?
    if(Report.checker) clearTimeout(Report.checker);

    // are we not done already ?
    if(model && model.status == 'done') return;

    // is the config not blank ...
    if(Report.config.update === null ||
        Report.config.update === undefined)
          return;

    // create it
    Report.checker = setTimeout(function() {

      // check the status
      Report.checkStatus(function() {

        // so are we good ?
        if(model.status === 'done') {

          // finish up
          Report.handleCallback();

        } else {

          // create the check again
          Report.createStatusChecker();

        }

      });

    }, 1000 * Report.config.update);

  };

  /**
  * Check status of report
  **/
  Report.checkStatus = function(fn) {

    /**
    * Submit to the actual API
    **/
    api.get(

      params,
      function(err, response) {

        // handle the error if any
        if(err) return fn(err);

        // was this a error ?
        if(response.status !== 'ok') {

          // yes output that instead then
          return fn(new Error(response.message || 'Something went wrong while check the report status'))

        }

        // get the item
        var item = response.item;

        // did we get the icon
        if(!item) return fn(null);

        // set the id
        currentReportID = item.uid;

        // set the initial item
        model = _extend(model, item);

        // finish with the callback
        fn(null);

      }

    );

  };

  /**
  * Start the report
  **/
  Report.start    = function(fn) {

    // sanity check if we found a callback
    if(!fn)
      throw new Error(LANG.ERROR_CALLBACK_REQUIRED);

    // check if started
    if(isStarted === true) {
      fn(new Error(LANG.ERROR_ALREADY_STARTED));
      return this;
    }

    // local ref
    var self = this;

    // mark as started
    isStarted = true;

    // check if this is a crawl, with no token ...
    if(this.isRecursive() === true && this.getToken() === null) {
      fn(new Error(LANG.ERROR_RECURSIVE_TOKEN));
      return this;
    }

    // validate the domain
    this.validateTarget(this.getURL(), _.bind(function(err) {

      // did we get a error ?
      if(err) return fn(err);

      // boot our channel
      channel.connect();

      // wait till the channel is ready
      channel.once('ready', _.bind(function() {

        // clean the url
        var cleanedUrl = Report.prefixProtocol(this.getURL());

        // parse the url
        var uri = url.parse(cleanedUrl);

        // create the sha1 engine
        var shasum = crypto.createHash('sha1');

        // add our digest
        shasum.update(cleanedUrl);

        // get the key
        var hash = shasum.digest('hex');

        // start to listen for a domain wide key,
        // as to make sure we don't miss anything.
        channel.subscribe('url.' + hash);

        // according to type start
        if(this.isRecursive()) {

          // start a multi-page crawl
          Report.createCrawl({

            url:          this.getURL(),
            token:        this.getToken(),
            bail:         this.isBail(),
            recursive:    this.isRecursive(),
            patterns:     this.getPatterns()

          }, function(err, response) {

            // all good, run this report
            return fn(err, response);

          });

        } else {

          // start a single page report
          Report.create({

            url:          this.getURL(),
            token:        this.getToken()

          }, function(err, response) {

            // all good, run this report
            return fn(err, response);

          });

        }

      }, this));

    }, this));

    // return our instance
    return this;

  };

  // return the report object to use
  return Report;

};
<|endoftext|>


<commit_before>#!/usr/bin/env node

const Program = require('commander');

const version = require('./lib/version');

// Define CLI
Program
    .version(version)
    .command('changelog', 'Echos a plugin zip file\'s changelog.')
    .command('description', 'Updates the plugin description of a supported locale.')
    .command('upload', 'Uploads a plugin zip file and makes it available for download.')
    .command('list', 'Lists all available plugins.', { isDefault: true })
    .parse(process.argv);
<commit_msg>Remove whitespace<commit_after>#!/usr/bin/env node

const Program = require('commander');
const version = require('./lib/version');

// Define CLI
Program
    .version(version)
    .command('changelog', 'Echos a plugin zip file\'s changelog.')
    .command('description', 'Updates the plugin description of a supported locale.')
    .command('upload', 'Uploads a plugin zip file and makes it available for download.')
    .command('list', 'Lists all available plugins.', { isDefault: true })
    .parse(process.argv);
<|endoftext|>