<commit_before>elation.require(['janusweb.janusbase'], function() {
  elation.component.add('engine.things.januslight', function() {
    this.postinit = function() {
      elation.engine.things.januslight.extendclass.postinit.call(this);
      this.defineProperties({
        light_directional: { type: 'bool', default: false, set: this.updateLight },
        light_range: { type: 'float', default: 0, set: this.updateLight, min: 0, comment: 'Max distance at which light can affect objects' },
        light_intensity: { type: 'float', default: 10, set: this.updateLight, comment: 'Light brightness' },
        light_cone_angle: { type: 'float', default: 0, set: this.updateLight, min: 0, max: 1, comment: 'Light cone shape. 0 = directional, 1 = point light, anything inbetween is a spotlight' },
        light_cone_exponent: { type: 'float', default: 1, set: this.updateLight },
        light_penumbra: { type: 'float', default: 1, set: this.updateLight, min: 0, max: 1, comment: 'Spotlight fall-off. 0 is crisp, 1 is smooth' },
        light_decay: { type: 'float', default: 2, set: this.updateLight, comment: 'The amount the light dims along the distance of the light. Default is 1. For physically correct lighting, set this to 2.' },
        light_target: { type: 'object', set: this.updateLightTarget },
        light_shadow: { type: 'boolean', default: false, set: this.updateLight, comment: 'Light casts shadows on scene' },
        light_shadow_near: { type: 'float', default: .1, set: this.updateLight },
        light_shadow_far: { type: 'float', set: this.updateLight },
        light_shadow_bias: { type: 'float', default: .0001, set: this.updateLight },
        light_shadow_radius: { type: 'float', default: 2.5, set: this.updateLight },
        light_helper: { type: 'boolean', default: false, set: this.updateLightHelper },
        collision_id: { type: 'string', default: 'sphere', set: this.updateCollider },
        collision_trigger: { type: 'boolean', default: true, set: this.updateCollider },
      });
    }
    this.createObject3D = function() {
      var obj = new THREE.Object3D();
      return obj;
    }
    this.updateColor = function() {
      this.updateLight();
    }
    this.createChildren = function() {
      elation.engine.things.januslight.extendclass.createChildren.call(this);

      this.createLight();
      this.updateLight();
      this.created = true;

      var scene = this.objects['3d'];
      while (scene.parent) {
        scene = scene.parent;
      }

      if (this.light_cone_angle == 0) {
        this.helper = new THREE.PointLightHelper(this.light);
      } else if (this.light_cone_angle == 1) {
        this.helper = new THREE.DirectionalLightHelper(this.light);
      } else {
        this.helper = new THREE.SpotLightHelper(this.light);
      }
      if (this.helper) {
        this.helper.traverse((n) => {
          n.layers.set(10);
        });
        scene.add(this.helper);
      }
      let placeholdergeo = this.getPlaceholderGeometry();
      if (placeholdergeo) {
        let placeholder = new THREE.Mesh(placeholdergeo, new THREE.MeshPhongMaterial({color: 0x666666, emissive: 0x333333, opacity: .8, transparent: true}));
        placeholder.layers.set(10);

        let collider = placeholder.clone();
        placeholder.userData.thing = this;
        collider.userData.thing = this;

        this.objects['3d'].add(placeholder);
        this.colliders.add(collider);
        this.placeholder = placeholder;
      }
    }
    this.getPlaceholderGeometry = function() {
      let placeholdergeo = null;
      if (this.light_cone_angle == 0) {
        placeholdergeo = new THREE.SphereBufferGeometry(.1);
      } else if (this.light_cone_angle == 1) {
        placeholdergeo = new THREE.SphereBufferGeometry(.1);
      } else {
        let angle = Math.acos(this.light_cone_angle),
            height = this.light_shadow_near,
            radius = Math.tan(angle) * height;
        placeholdergeo = new THREE.ConeBufferGeometry(radius, height);
        placeholdergeo.applyMatrix(new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(-Math.PI/2, 0, 0)).setPosition(0, 0, height / 2));
      }
      return placeholdergeo;
    }
    this.update = function() {
      if (!this.light) {
        this.createLight();
        this.updateLight();
      }
      if (this.light && !this.light.parent) {
        this.objects['3d'].add(this.light);
      }
      if (this.helper) {
        this.helper.update();
      }
      if (this.light_directional || this.light_cone_angle == 1) {
        this.light.position.subVectors(this.light.position, this.light.target.position).add(player.pos);
        this.light.target.position.copy(player.pos);
      }
    }
    this.createLight = function() {
      if (!this.light) {
        if (this.light_directional || this.light_cone_angle == 1) {
          this.light = new THREE.DirectionalLight(this.properties.color, this.light_intensity);
          this.updateLightTarget();
        } else if (this.light_cone_angle == 0) {
          this.light = new THREE.PointLight(this.properties.color, 1, this.light_range);
          this.light.position.set(0,0,0);
        } else if (this.light_cone_angle > 0) {
          var angle = Math.acos(this.light_cone_angle);
          this.light = new THREE.SpotLight(this.properties.color, 1, this.light_range, angle, this.light_penumbra, this.light_decay);
          this.light.position.set(0,0,0);

          this.updateLightTarget();
        }
      }
    }
    this.updateLight = function() {
      if (this.light) {
        if (this.light.parent !== this.objects['3d']) {
          this.objects['3d'].add(this.light);
        }
        //this.light.intensity = this.light_intensity / 100;
        var avgscale = (this.scale.x + this.scale.y + this.scale.z) / 3;
        this.light.intensity = this.light_intensity / 100;
        this.light.penumbra = this.light_penumbra;
        this.light.decay = this.light_decay;
        this.light.color.copy(this.color);
        this.light.color.multiplyScalar(this.light_intensity * avgscale * avgscale);
        this.light.distance = this.light_range * avgscale;
        if (this.light_cone_angle > 0 && this.light_cone_angle < 1) {
          this.light.angle = Math.acos(this.light_cone_angle);
          if (this.placeholder) {
            this.placeholder.geometry = this.getPlaceholderGeometry();
          }
        }
        this.updateShadowmap();
      }
    }
    this.updateLightTarget = function() {
      if (!this.light) return;

      if (this.light_target) {
        if (elation.utils.isString(this.light_target)) {
          if (room.objects[this.light_target]) {
            let obj = room.objects[this.light_target];
            this.light.target = obj.objects['3d'];
          } else if (this.light_target == 'player') {
            this.light.target = player.objects['3d'];
          }
        } else if (this.light_target.objects && this.light_target.objects['3d']) {
          this.light.target = this.light_target.objects['3d'];
        } else if (this.light_target instanceof THREE.Object3D) {
          this.light.target = this.light_target;
        }
      }

      // If no target was specified, we're using the default, so make sure it's set up right
      var target = this.light.target;
      if (!target.parent) {
        this.objects['3d'].add(target);
        target.position.set(0,0,1);
      }
    }
    this.updateShadowmap = function() {
      var player = this.engine.client.player;
      var shadowSize = player.getSetting('render.shadows.size', elation.config.get('janusweb.materials.shadows.size', 512));

      this.light.castShadow = this.light_shadow;
      this.light.shadow.radius = this.light_shadow_radius;
      this.light.shadow.camera.near = elation.utils.any(this.light_shadow_near, 0.1);
      this.light.shadow.camera.far = elation.utils.any(this.light_shadow_far, this.light_range);
      this.light.shadow.camera.fov = 90;
      this.light.shadow.mapSize.set(shadowSize, shadowSize);
      this.light.shadow.bias = this.light_shadow_bias;

      // directional light shadow parameters
      let d = this.light_range;
      this.light.shadow.camera.left = -d;
      this.light.shadow.camera.right = d;
      this.light.shadow.camera.top = d;
      this.light.shadow.camera.bottom = -d;
    }
    this.getProxyObject = function(classdef) {
      if (!this._proxyobject) {
        this._proxyobject = elation.engine.things.janusobject.extendclass.getProxyObject.call(this, classdef);
        this._proxyobject._proxydefs = {
          light_range:         [ 'property', 'light_range'],
          light_intensity:     [ 'property', 'light_intensity'],
          light_cone_angle:    [ 'property', 'light_cone_angle'],
          light_cone_exponent: [ 'property', 'light_cone_exponent'],
          light_penumbra:      [ 'property', 'light_penumbra'],
          light_decay:         [ 'property', 'light_decay'],
          light_target:        [ 'property', 'light_target'],
          light_shadow:        [ 'property', 'light_shadow'],
          light_shadow_near:   [ 'property', 'light_shadow_near'],
          light_shadow_far:    [ 'property', 'light_shadow_far'],
          light_shadow_bias:   [ 'property', 'light_shadow_bias'],
          light_shadow_radius: [ 'property', 'light_shadow_radius'],
        };
      }
      return this._proxyobject;
    }
  }, elation.engine.things.janusbase);
});
<commit_msg>Update light target when changed<commit_after>elation.require(['janusweb.janusbase'], function() {
  elation.component.add('engine.things.januslight', function() {
    this.postinit = function() {
      elation.engine.things.januslight.extendclass.postinit.call(this);
      this.targetIsSet = false;
      this.defineProperties({
        light_directional: { type: 'bool', default: false, set: this.updateLight },
        light_range: { type: 'float', default: 0, set: this.updateLight, min: 0, comment: 'Max distance at which light can affect objects' },
        light_intensity: { type: 'float', default: 10, set: this.updateLight, comment: 'Light brightness' },
        light_cone_angle: { type: 'float', default: 0, set: this.updateLight, min: 0, max: 1, comment: 'Light cone shape. 0 = directional, 1 = point light, anything inbetween is a spotlight' },
        light_cone_exponent: { type: 'float', default: 1, set: this.updateLight },
        light_penumbra: { type: 'float', default: 1, set: this.updateLight, min: 0, max: 1, comment: 'Spotlight fall-off. 0 is crisp, 1 is smooth' },
        light_decay: { type: 'float', default: 2, set: this.updateLight, comment: 'The amount the light dims along the distance of the light. Default is 1. For physically correct lighting, set this to 2.' },
        light_target: { type: 'object', set: this.updateLightTarget },
        light_shadow: { type: 'boolean', default: false, set: this.updateLight, comment: 'Light casts shadows on scene' },
        light_shadow_near: { type: 'float', default: .1, set: this.updateLight },
        light_shadow_far: { type: 'float', set: this.updateLight },
        light_shadow_bias: { type: 'float', default: .0001, set: this.updateLight },
        light_shadow_radius: { type: 'float', default: 2.5, set: this.updateLight },
        light_helper: { type: 'boolean', default: false, set: this.updateLightHelper },
        collision_id: { type: 'string', default: 'sphere', set: this.updateCollider },
        collision_trigger: { type: 'boolean', default: true, set: this.updateCollider },
      });
    }
    this.createObject3D = function() {
      var obj = new THREE.Object3D();
      return obj;
    }
    this.updateColor = function() {
      this.updateLight();
    }
    this.createChildren = function() {
      elation.engine.things.januslight.extendclass.createChildren.call(this);

      this.createLight();
      this.updateLight();
      this.created = true;

      var scene = this.objects['3d'];
      while (scene.parent) {
        scene = scene.parent;
      }

      if (this.light_cone_angle == 0) {
        this.helper = new THREE.PointLightHelper(this.light);
      } else if (this.light_cone_angle == 1) {
        this.helper = new THREE.DirectionalLightHelper(this.light);
      } else {
        this.helper = new THREE.SpotLightHelper(this.light);
      }
      if (this.helper) {
        this.helper.traverse((n) => {
          n.layers.set(10);
        });
        scene.add(this.helper);
      }
      let placeholdergeo = this.getPlaceholderGeometry();
      if (placeholdergeo) {
        let placeholder = new THREE.Mesh(placeholdergeo, new THREE.MeshPhongMaterial({color: 0x666666, emissive: 0x333333, opacity: .8, transparent: true}));
        placeholder.layers.set(10);

        let collider = placeholder.clone();
        placeholder.userData.thing = this;
        collider.userData.thing = this;

        this.objects['3d'].add(placeholder);
        this.colliders.add(collider);
        this.placeholder = placeholder;
      }
    }
    this.getPlaceholderGeometry = function() {
      let placeholdergeo = null;
      if (this.light_cone_angle == 0) {
        placeholdergeo = new THREE.SphereBufferGeometry(.1);
      } else if (this.light_cone_angle == 1) {
        placeholdergeo = new THREE.SphereBufferGeometry(.1);
      } else {
        let angle = Math.acos(this.light_cone_angle),
            height = this.light_shadow_near,
            radius = Math.tan(angle) * height;
        placeholdergeo = new THREE.ConeBufferGeometry(radius, height);
        placeholdergeo.applyMatrix4(new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(-Math.PI/2, 0, 0)).setPosition(0, 0, height / 2));
      }
      return placeholdergeo;
    }
    this.update = function() {
      if (!this.light) {
        this.createLight();
        this.updateLight();
      }
      if (this.light && !this.light.parent) {
        this.objects['3d'].add(this.light);
      }
      if (this.helper) {
        this.helper.update();
      }
      if (this.light_target && !this.targetIsSet) {
        this.updateLightTarget();
      }
      if (this.light_directional || this.light_cone_angle == 1) {
        this.light.position.subVectors(this.light.position, this.light.target.position).add(player.pos);
        this.light.target.position.copy(player.pos);
      }
    }
    this.createLight = function() {
      if (!this.light) {
        if (this.light_directional || this.light_cone_angle == 1) {
          this.light = new THREE.DirectionalLight(this.properties.color, this.light_intensity);
          this.updateLightTarget();
        } else if (this.light_cone_angle == 0) {
          this.light = new THREE.PointLight(this.properties.color, 1, this.light_range);
          this.light.position.set(0,0,0);
        } else if (this.light_cone_angle > 0) {
          var angle = Math.acos(this.light_cone_angle);
          this.light = new THREE.SpotLight(this.properties.color, 1, this.light_range, angle, this.light_penumbra, this.light_decay);
          this.light.position.set(0,0,0);

          this.updateLightTarget();
        }
      }
    }
    this.updateLight = function() {
      if (this.light) {
        if (this.light.parent !== this.objects['3d']) {
          this.objects['3d'].add(this.light);
        }
        //this.light.intensity = this.light_intensity / 100;
        var avgscale = (this.scale.x + this.scale.y + this.scale.z) / 3;
        //this.light.intensity = this.light_intensity / 100;
        this.light.intensity = .1;
        this.light.penumbra = this.light_penumbra;
        this.light.decay = this.light_decay;
        this.light.color.copy(this.color);
        this.light.color.multiplyScalar(this.light_intensity * avgscale * avgscale);
        this.light.distance = this.light_range * avgscale;
        if (this.light_cone_angle > 0 && this.light_cone_angle < 1) {
          this.light.angle = Math.acos(this.light_cone_angle);
          if (this.placeholder) {
            this.placeholder.geometry = this.getPlaceholderGeometry();
          }
        }
        this.updateShadowmap();
      }
    }
    this.updateLightTarget = function() {
      if (!this.light) return;

      if (this.light_target) {
        if (elation.utils.isString(this.light_target)) {
          if (room.objects[this.light_target]) {
            let obj = room.objects[this.light_target];
            this.light.target = obj.objects['3d'];
            this.targetIsSet = true;
          } else if (this.light_target == 'player') {
            this.light.target = player.objects['3d'];
            this.targetIsSet = true;
          }
        } else if (this.light_target.objects && this.light_target.objects['3d']) {
          this.light.target = this.light_target.objects['3d'];
          this.targetIsSet = true;
        } else if (this.light_target instanceof THREE.Object3D) {
          this.light.target = this.light_target;
          this.targetIsSet = true;
        }
      }

      // If no target was specified, we're using the default, so make sure it's set up right
      var target = this.light.target;
      if (!target.parent) {
        this.objects['3d'].add(target);
        target.position.set(0,0,1);
      }
    }
    this.updateShadowmap = function() {
      var player = this.engine.client.player;
      var shadowSize = player.getSetting('render.shadows.size', elation.config.get('janusweb.materials.shadows.size', 512));

      this.light.castShadow = this.light_shadow;
      this.light.shadow.radius = this.light_shadow_radius;
      this.light.shadow.camera.near = elation.utils.any(this.light_shadow_near, 0.1);
      this.light.shadow.camera.far = elation.utils.any(this.light_shadow_far, this.light_range);
      this.light.shadow.camera.fov = 90;
      this.light.shadow.mapSize.set(shadowSize, shadowSize);
      this.light.shadow.bias = this.light_shadow_bias;

      // directional light shadow parameters
      let d = this.light_range;
      this.light.shadow.camera.left = -d;
      this.light.shadow.camera.right = d;
      this.light.shadow.camera.top = d;
      this.light.shadow.camera.bottom = -d;
    }
    this.getProxyObject = function(classdef) {
      if (!this._proxyobject) {
        this._proxyobject = elation.engine.things.janusobject.extendclass.getProxyObject.call(this, classdef);
        this._proxyobject._proxydefs = {
          light_range:         [ 'property', 'light_range'],
          light_intensity:     [ 'property', 'light_intensity'],
          light_cone_angle:    [ 'property', 'light_cone_angle'],
          light_cone_exponent: [ 'property', 'light_cone_exponent'],
          light_penumbra:      [ 'property', 'light_penumbra'],
          light_decay:         [ 'property', 'light_decay'],
          light_target:        [ 'property', 'light_target'],
          light_shadow:        [ 'property', 'light_shadow'],
          light_shadow_near:   [ 'property', 'light_shadow_near'],
          light_shadow_far:    [ 'property', 'light_shadow_far'],
          light_shadow_bias:   [ 'property', 'light_shadow_bias'],
          light_shadow_radius: [ 'property', 'light_shadow_radius'],
        };
      }
      return this._proxyobject;
    }
  }, elation.engine.things.janusbase);
});
<|endoftext|>


<commit_before>// Karma configuration
const path = require("path");
const argv = require("yargs")
	.option("grep", {
		default: "",
	})
	.argv;

let BROWSERS = ["HeadlessChrome", "HeadlessFirefox", "Safari"];

if (process.env.BROWSER === "chrome") {
	BROWSERS = ["HeadlessChrome"];
} else if (process.env.BROWSER === "firefox") {
	BROWSERS = ["HeadlessFirefox"];
} else if (process.env.BROWSER === "safari") {
	BROWSERS = ["Safari"];
} else {
	BROWSERS = ["HeadlessChrome", "HeadlessFirefox"];
}

module.exports = function(config) {
	const configuration = {

		// base path that will be used to resolve all patterns (eg. files, exclude)
		basePath : "../",

		// frameworks to use
		// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
		frameworks : ["mocha", "karma-typescript"],

		// list of files / patterns to load in the browser
		files : [
			"test/**/*.ts",
			"Tone/**/*.ts",
			{ pattern : "test/audio/**", included : false },
			{ pattern : "test/html/**", included : false },
		],

		// Karma Typescript compiler options
		karmaTypescriptConfig: {
			compilerOptions : {
				// baseUrl: ".",
				// module: "commonjs",
				// paths : {
				// 	"../Tone" : ["./Tone"],
				// 	// "@tonejs/plot": ["./node_modules/@tonejs/plot/dist/index.d.ts"],
				// },
			},
			bundlerOptions: {
				resolve: {
					directories: ["Tone", "node_modules", "test"],
				},
			},
			coverageOptions : {
				exclude: /.*\.test\.ts$/i,
			},
			reports: {
				html: path.resolve(__dirname, "../coverage"),
				lcovonly: {
					directory : path.resolve(__dirname, "../coverage"),
					filename: "coverage.lcov",
				},
			},
			tsconfig: "./tsconfig.json",
		},

		// list of files to exclude
		exclude : [
			"node_modules/*",
		],

		// preprocess matching files before serving them to the browser
		// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
		preprocessors: {
			"**/*.ts": "karma-typescript",
			// "Tone/**/*.ts": "coverage",
		},

		// test results reporter to use
		// possible values: 'dots', 'progress'
		// available reporters: https://npmjs.org/browse/keyword/karma-reporter
		reporters : ["dots", "karma-typescript"],

		// coverageReporter : {
		// 	type : "lcov",
		// 	dir: path.resolve(__dirname, "../coverage"),
		// },

		//plugins
		plugins : [
			"karma-typescript",
			// "karma-coverage",
			"karma-mocha",
			"karma-chrome-launcher",
			"karma-firefox-launcher",
			"karma-safari-launcher",
			// "karma-sourcemap-loader",
		],

		client : {
			mocha : {
				grep: argv.grep,
				reporter : "html",
				timeout : 10000,
				ui : "bdd",
			},
		},

		// web server port
		port : 9876,

		// enable / disable colors in the output (reporters and logs)
		colors : true,

		// set the inactivity level to longer
		browserNoActivityTimeout : 40000,

		// level of logging
		// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
		logLevel: config.LOG_ERROR,

		// enable / disable watching file and executing tests whenever any file changes
		autoWatch : false,
		// restartOnFileChange : true,

		// start these browsers
		// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
		browsers : BROWSERS,

		// Continuous Integration mode
		// if true, Karma captures browsers, runs the tests and exits
		singleRun : false,

		// Concurrency level
		// how many browser should be started simultaneous
		// concurrency: process.env.TRAVIS ? 1 : Infinity,
		concurrency : Infinity,

		//custom launcher for travis
		customLaunchers : {
			HeadlessChrome : {
				base : "ChromeHeadless",
				flags : ["--no-sandbox", "--use-fake-ui-for-media-stream", "--use-fake-device-for-media-stream",
						"--autoplay-policy=no-user-gesture-required"],
			},
			HeadlessFirefox : {
				base : "Firefox",
				flags : ["-headless"],
				prefs : {
					"focusmanager.testmode" : true,
					"media.navigator.permission.disabled" : true,
				},
			},
			OnlineChrome: {
				base: "Chrome",
				flags: ["--no-sandbox", "--use-fake-ui-for-media-stream", "--use-fake-device-for-media-stream",
					"--autoplay-policy=no-user-gesture-required"],
			},
		},
	};

	config.set(configuration);
};
<commit_msg>using commonjs module for karma<commit_after>// Karma configuration
const path = require("path");
const argv = require("yargs")
	.option("grep", {
		default: "",
	})
	.argv;

let BROWSERS = ["HeadlessChrome", "HeadlessFirefox", "Safari"];

if (process.env.BROWSER === "chrome") {
	BROWSERS = ["HeadlessChrome"];
} else if (process.env.BROWSER === "firefox") {
	BROWSERS = ["HeadlessFirefox"];
} else if (process.env.BROWSER === "safari") {
	BROWSERS = ["Safari"];
} else {
	BROWSERS = ["HeadlessChrome", "HeadlessFirefox"];
}

module.exports = function(config) {
	const configuration = {

		// base path that will be used to resolve all patterns (eg. files, exclude)
		basePath : "../",

		// frameworks to use
		// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
		frameworks : ["mocha", "karma-typescript"],

		// list of files / patterns to load in the browser
		files : [
			"test/**/*.ts",
			"Tone/**/*.ts",
			{ pattern : "test/audio/**", included : false },
			{ pattern : "test/html/**", included : false },
		],

		// Karma Typescript compiler options
		karmaTypescriptConfig: {
			compilerOptions : {
				module: "commonjs",
			},
			bundlerOptions: {
				resolve: {
					directories: ["Tone", "node_modules", "test"],
				},
			},
			coverageOptions : {
				exclude: /.*\.test\.ts$/i,
			},
			reports: {
				html: path.resolve(__dirname, "../coverage"),
				lcovonly: {
					directory : path.resolve(__dirname, "../coverage"),
					filename: "coverage.lcov",
				},
			},
			tsconfig: "./tsconfig.json",
		},

		// list of files to exclude
		exclude : [
			"node_modules/*",
		],

		// preprocess matching files before serving them to the browser
		// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
		preprocessors: {
			"**/*.ts": "karma-typescript",
			// "Tone/**/*.ts": "coverage",
		},

		// test results reporter to use
		// possible values: 'dots', 'progress'
		// available reporters: https://npmjs.org/browse/keyword/karma-reporter
		reporters : ["dots", "karma-typescript"],

		// coverageReporter : {
		// 	type : "lcov",
		// 	dir: path.resolve(__dirname, "../coverage"),
		// },

		//plugins
		plugins : [
			"karma-typescript",
			// "karma-coverage",
			"karma-mocha",
			"karma-chrome-launcher",
			"karma-firefox-launcher",
			"karma-safari-launcher",
			// "karma-sourcemap-loader",
		],

		client : {
			mocha : {
				grep: argv.grep,
				reporter : "html",
				timeout : 10000,
				ui : "bdd",
			},
		},

		// web server port
		port : 9876,

		// enable / disable colors in the output (reporters and logs)
		colors : true,

		// set the inactivity level to longer
		browserNoActivityTimeout : 40000,

		// level of logging
		// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
		logLevel: config.LOG_ERROR,

		// enable / disable watching file and executing tests whenever any file changes
		autoWatch : false,
		// restartOnFileChange : true,

		// start these browsers
		// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
		browsers : BROWSERS,

		// Continuous Integration mode
		// if true, Karma captures browsers, runs the tests and exits
		singleRun : false,

		// Concurrency level
		// how many browser should be started simultaneous
		// concurrency: process.env.TRAVIS ? 1 : Infinity,
		concurrency : Infinity,

		//custom launcher for travis
		customLaunchers : {
			HeadlessChrome : {
				base : "ChromeHeadless",
				flags : ["--no-sandbox", "--use-fake-ui-for-media-stream", "--use-fake-device-for-media-stream",
						"--autoplay-policy=no-user-gesture-required"],
			},
			HeadlessFirefox : {
				base : "Firefox",
				flags : ["-headless"],
				prefs : {
					"focusmanager.testmode" : true,
					"media.navigator.permission.disabled" : true,
				},
			},
			OnlineChrome: {
				base: "Chrome",
				flags: ["--no-sandbox", "--use-fake-ui-for-media-stream", "--use-fake-device-for-media-stream",
					"--autoplay-policy=no-user-gesture-required"],
			},
		},
	};

	config.set(configuration);
};
<|endoftext|>


<commit_before>/* global hexo */

'use strict';

const { createHash } = require('crypto');
const { url_for } = require('hexo-util');
const sharp = require('sharp');

async function responsive() {
  const { route } = this;
  const routeList = route.list();
  const pngFiles = routeList.filter((path) => path.startsWith('themes') && path.endsWith('.png'));
  const updatePng = {};

  await Promise.all(pngFiles.map((path) => {
    return new Promise((resolve, reject) => {
      const assetPath = route.get(path);
      const assetData = [];
      assetPath.on('data', (chunk) => assetData.push(chunk));
      assetPath.on('end', async() => {
        if (assetData.length) {
          try {
            const input = Buffer.concat(assetData);
            const jpeg = await sharp(input)
              .jpeg({
                progressive: true,
                quality: 70
              })
              .toBuffer();
            const metadata = await sharp(jpeg).metadata();
            const halfJpeg = await sharp(jpeg)
              .resize({ width: Math.round(metadata.width / 2) })
              .toBuffer();

            const pngHash = createHash('sha1').update(input).digest('hex').slice(0, 10);
            const jpegHash = createHash('sha1').update(jpeg).digest('hex').slice(0, 10);
            const halfJpegHash = createHash('sha1').update(halfJpeg).digest('hex').slice(0, 10);
            const pngPath = path.replace(/\.png$/, `-${pngHash}.png`);
            const jpeg2xPath = path.replace(/\.png$/, `-${jpegHash}@2x.jpg`);
            const jpegPath = path.replace(/\.png$/, `-${halfJpegHash}.jpg`);
            const validPath = url_for.call(this, path);
            const validPngPath = url_for.call(this, pngPath);
            const validJpg2xPath = url_for.call(this, jpeg2xPath);
            const validJpgPath = url_for.call(this, jpegPath);

            updatePng[validPath] = { 'png': validPngPath, 'jpg2x': validJpg2xPath, 'jpg': validJpgPath };

            route.set(pngPath, input);
            route.set(jpegPath, halfJpeg);
            resolve(route.set(jpeg2xPath, jpeg));
          } catch (err) {
            reject(err);
          }
        }
      });
    });
  }));

  return Promise.all(['themes/index.html'].map((path) => {
    return new Promise((resolve, reject) => {
      const assetPath = route.get(path);
      const assetData = [];
      assetPath.on('data', (chunk) => assetData.push(chunk));
      assetPath.on('end', async() => {
        if (assetData.length) {
          try {
            const result = assetData.join().replace(/<img (src|data-src)=['"](.*?)['"](.*?)>/gi, (imgTag, attr, value, alt) => {
              imgTag = imgTag.replace(value, (img) => {
                if (updatePng[img]) return updatePng[img].png;
                return img;
              });

              if (attr === 'data-src') {
                const png = updatePng[value] ? updatePng[value].png : value;
                const jpg = updatePng[value] ? updatePng[value].jpg : value;
                const jpg2x = updatePng[value] ? updatePng[value].jpg2x : value;

                imgTag = imgTag.replace(alt, (postAlt) => {
                  postAlt += ` data-srcset="${jpg}, ${jpg2x} 2x" data-org="${png}" sizes="360px" srcset="${jpg}, ${jpg2x} 2x" src="${jpg}"`;
                  return postAlt;
                });
              }

              return imgTag;
            });

            resolve(route.set(path, result));
          } catch (err) {
            reject(err);
          }
        }
      });
    });
  }));
}

hexo.extend.filter.register('after_generate', responsive);
<commit_msg>fix: alt argument might be empty<commit_after>/* global hexo */

'use strict';

const { createHash } = require('crypto');
const { url_for } = require('hexo-util');
const sharp = require('sharp');

async function responsive() {
  const { route } = this;
  const routeList = route.list();
  const pngFiles = routeList.filter((path) => path.startsWith('themes') && path.endsWith('.png'));
  const updatePng = {};

  await Promise.all(pngFiles.map((path) => {
    return new Promise((resolve, reject) => {
      const assetPath = route.get(path);
      const assetData = [];
      assetPath.on('data', (chunk) => assetData.push(chunk));
      assetPath.on('end', async() => {
        if (assetData.length) {
          try {
            const input = Buffer.concat(assetData);
            const jpeg = await sharp(input)
              .jpeg({
                progressive: true,
                quality: 70
              })
              .toBuffer();
            const metadata = await sharp(jpeg).metadata();
            const halfJpeg = await sharp(jpeg)
              .resize({ width: Math.round(metadata.width / 2) })
              .toBuffer();

            const pngHash = createHash('sha1').update(input).digest('hex').slice(0, 10);
            const jpegHash = createHash('sha1').update(jpeg).digest('hex').slice(0, 10);
            const halfJpegHash = createHash('sha1').update(halfJpeg).digest('hex').slice(0, 10);
            const pngPath = path.replace(/\.png$/, `-${pngHash}.png`);
            const jpeg2xPath = path.replace(/\.png$/, `-${jpegHash}@2x.jpg`);
            const jpegPath = path.replace(/\.png$/, `-${halfJpegHash}.jpg`);
            const validPath = url_for.call(this, path);
            const validPngPath = url_for.call(this, pngPath);
            const validJpg2xPath = url_for.call(this, jpeg2xPath);
            const validJpgPath = url_for.call(this, jpegPath);

            updatePng[validPath] = { 'png': validPngPath, 'jpg2x': validJpg2xPath, 'jpg': validJpgPath };

            route.set(pngPath, input);
            route.set(jpegPath, halfJpeg);
            resolve(route.set(jpeg2xPath, jpeg));
          } catch (err) {
            reject(err);
          }
        }
      });
    });
  }));

  return Promise.all(['themes/index.html'].map((path) => {
    return new Promise((resolve, reject) => {
      const assetPath = route.get(path);
      const assetData = [];
      assetPath.on('data', (chunk) => assetData.push(chunk));
      assetPath.on('end', async() => {
        if (assetData.length) {
          try {
            const result = assetData.join().replace(/<img (src|data-src)=['"](.*?)['"](.*?)>/gi, (imgTag, attr, value, alt) => {
              imgTag = imgTag.replace(value, (img) => {
                if (updatePng[img]) return updatePng[img].png;
                return img;
              });

              if (attr === 'data-src' && alt.length > 0) {
                const png = updatePng[value] ? updatePng[value].png : value;
                const jpg = updatePng[value] ? updatePng[value].jpg : value;
                const jpg2x = updatePng[value] ? updatePng[value].jpg2x : value;

                imgTag = imgTag.replace(alt, (postAlt) => {
                  postAlt += ` data-srcset="${jpg}, ${jpg2x} 2x" data-org="${png}" sizes="360px" srcset="${jpg}, ${jpg2x} 2x" src="${jpg}"`;
                  return postAlt;
                });
              }

              return imgTag;
            });

            resolve(route.set(path, result));
          } catch (err) {
            reject(err);
          }
        }
      });
    });
  }));
}

hexo.extend.filter.register('after_generate', responsive);
<|endoftext|>


<commit_before>function RocketSystem() {
    this.rockets = []
    this.dying = []
}

RocketSystem.prototype.add = function () {
    var rocket = new Rocket()
    this.rockets.push(rocket)
}

RocketSystem.prototype.render = function (nap) {
    var i, len, rocket, av, fv
    len = this.rockets.length
    av = 0.0002 * nap

    R2.setLineDash([4])

    for (i = 0; i < len; ++i) {
        if ((rocket = this.rockets[i]).update(av)) {
            this.rockets.splice(i, 1)
            this.dying.push(rocket)
            // jshint -W017
            --i
            --len
        }
        else {
            R2.beginPath()
            R2.arc(rocket.x0, rocket.y0, rocket.r, rocket.a0, rocket.a1, true)
            R2.strokeStyle = COLOR_BRIGHT[rocket.side]
            R2.stroke()
            R2.drawImage(rocket.tex, rocket.x - 16.5, rocket.y - 16.5)
        }
    }

    if (!(len = this.dying.length))
        return

    fv = 0.005 * nap

    for (i = 0; i < len; ++i) {
        rocket = this.dying[i]
        SCORE++ //move somewhere outside the loop
        if ((rocket.opacity -= fv) <= 0) {
            this.dying.splice(i, 1)
            // jshint -W017
            --i
            --len
        }
        else {
            R2.globalAlpha = rocket.opacity
            R2.drawImage(rocket.tex, rocket.x - 16.5, rocket.y - 16.5)
        }
    }

    R2.globalAlpha = 1
}
<commit_msg>akbar<commit_after>function RocketSystem() {
    this.rockets = []
    this.dying = []
}

RocketSystem.prototype.add = function () {
    var rocket = new Rocket()
    this.rockets.push(rocket)
}

RocketSystem.prototype.render = function (nap) {
    var i, len, rocket, av, fv
    len = this.rockets.length
    av = 0.0002 * nap

    R2.setLineDash([4])

    for (i = 0; i < len; ++i) {
        if ((rocket = this.rockets[i]).update(av)) {
            this.rockets.splice(i, 1)
            this.dying.push(rocket)
            // jshint -W017
            --i
            --len
        }
        else {
            R2.beginPath()
            R2.arc(rocket.x0, rocket.y0, rocket.r, rocket.a0, rocket.a1, true)
            R2.strokeStyle = COLOR_BRIGHT[rocket.side]
            R2.stroke()
            R2.drawImage(rocket.tex, rocket.x - 16.5, rocket.y - 16.5)
        }
    }

    if (!(len = this.dying.length))
        return

    fv = 0.005 * nap

    for (i = 0; i < len; ++i) {
        rocket = this.dying[i]
        SCORE++ //move somewhere outside the loop to bullet collision //currently death by moon counts too
        //$id('hamas').click //AHAHAHA REVENGE THE DEAD
        if ((rocket.opacity -= fv) <= 0) {
            this.dying.splice(i, 1)
            // jshint -W017
            --i
            --len
        }
        else {
            R2.globalAlpha = rocket.opacity
            R2.drawImage(rocket.tex, rocket.x - 16.5, rocket.y - 16.5)
        }
    }

    R2.globalAlpha = 1
}
<|endoftext|>


<commit_before>;(function()
{
	// CommonJS
	SyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);

	function Brush()
	{
		var funcs	=	'abs avg case cast coalesce convert count current_timestamp ' +
						'current_user day isnull left lower month nullif replace right ' +
						'session_user space substring sum system_user upper user year';

		var keywords =	'absolute action add after alter as asc at authorization begin bigint ' +
						'binary bit by cascade char character check checkpoint close collate ' +
						'column commit committed connect connection constraint contains continue ' +
						'create cube current current_date current_time cursor database date ' +
						'deallocate dec decimal declare default delete desc distinct double drop ' +
						'dynamic else end end-exec escape except exec execute false fetch first ' +
						'float for force foreign forward free from full function global goto grant ' +
						'group grouping having hour ignore index inner insensitive insert instead ' +
						'int integer intersect into is isolation key last level load local max min ' +
						'minute modify move name national nchar next no numeric of off on only ' +
						'open option order out output partial password precision prepare primary ' +
						'prior privileges procedure public read real references relative repeatable ' +
						'restrict return returns revoke rollback rollup rows rule schema scroll ' +
						'second section select sequence serializable set size smallint static ' +
						'statistics table temp temporary then time timestamp to top transaction ' +
						'translation trigger true truncate uncommitted union unique update values ' +
						'varchar varying view when where with work';

		var operators =	'all and any between cross in join like not null or outer some';

		this.regexList = [
			{ regex: /--(.*)$/gm,												css: 'comments' },			// one line and multiline comments
			{ regex: /\/\*([^\*][\s\S]*)?\*\//gm,
			{ regex: SyntaxHighlighter.regexLib.multiLineDoubleQuotedString,	css: 'string' },			// double quoted strings
			{ regex: SyntaxHighlighter.regexLib.multiLineSingleQuotedString,	css: 'string' },			// single quoted strings
			{ regex: new RegExp(this.getKeywords(funcs), 'gmi'),				css: 'color2' },			// functions
			{ regex: new RegExp(this.getKeywords(operators), 'gmi'),			css: 'color1' },			// operators and such
			{ regex: new RegExp(this.getKeywords(keywords), 'gmi'),				css: 'keyword' }			// keyword
			];
	};

	Brush.prototype	= new SyntaxHighlighter.Highlighter();
	Brush.aliases	= ['sql'];

	SyntaxHighlighter.brushes.Sql = Brush;

	// CommonJS
	typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
})();

<commit_msg>Update scripts/shBrushSql.js<commit_after>;(function()
{
	// CommonJS
	SyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);

	function Brush()
	{
		var funcs	=	'abs avg case cast coalesce convert count current_timestamp ' +
						'current_user day isnull left lower month nullif replace right ' +
						'session_user space substring sum system_user upper user year';

		var keywords =	'absolute action add after alter as asc at authorization begin bigint ' +
						'binary bit by cascade char character check checkpoint close collate ' +
						'column commit committed connect connection constraint contains continue ' +
						'create cube current current_date current_time cursor database date ' +
						'deallocate dec decimal declare default delete desc distinct double drop ' +
						'dynamic else end end-exec escape except exec execute false fetch first ' +
						'float for force foreign forward free from full function global goto grant ' +
						'group grouping having hour ignore index inner insensitive insert instead ' +
						'int integer intersect into is isolation key last level load local max min ' +
						'minute modify move name national nchar next no numeric of off on only ' +
						'open option order out output partial password precision prepare primary ' +
						'prior privileges procedure public read real references relative repeatable ' +
						'restrict return returns revoke rollback rollup rows rule schema scroll ' +
						'second section select sequence serializable set size smallint static ' +
						'statistics table temp temporary then time timestamp to top transaction ' +
						'translation trigger true truncate uncommitted union unique update values ' +
						'varchar varying view when where with work';

		var operators =	'all and any between cross in join like not null or outer some';

		this.regexList = [
			{ regex: /--(.*)$/gm,												css: 'comments' },			// one line and multiline comments
			{ regex: /\/\*([^\*][\s\S]*?)?\*\//gm,
			{ regex: SyntaxHighlighter.regexLib.multiLineDoubleQuotedString,	css: 'string' },			// double quoted strings
			{ regex: SyntaxHighlighter.regexLib.multiLineSingleQuotedString,	css: 'string' },			// single quoted strings
			{ regex: new RegExp(this.getKeywords(funcs), 'gmi'),				css: 'color2' },			// functions
			{ regex: new RegExp(this.getKeywords(operators), 'gmi'),			css: 'color1' },			// operators and such
			{ regex: new RegExp(this.getKeywords(keywords), 'gmi'),				css: 'keyword' }			// keyword
			];
	};

	Brush.prototype	= new SyntaxHighlighter.Highlighter();
	Brush.aliases	= ['sql'];

	SyntaxHighlighter.brushes.Sql = Brush;

	// CommonJS
	typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
})();

<|endoftext|>


<commit_before>const Twitter = require('twitter');
const schedule = require('node-schedule');
const config = require('../config/twitter-config.js');
const _ = require('lodash');

/**
 * Twitterbot - Handles all communication between Ariadne and the Twitter
 * Streaming and REST APIs
 */

let client = {};
let stream;
let screenName = 'ariadnebot'; // TODO: Pull this from a database.
let restartInterval = 1000 * 30; // Default to 30 seconds
const tweetHandlers = [];

const isTweet = _.conforms({
  id_str: _.isString,
  text: _.isString,
});

const tweet = (status, params, callback) => {
  // Handle in case no params are provided
  if ('function' === typeof params && 'undefined' === typeof callback) {
    /* eslint-disable no-param-reassign */
    callback = params;
    params = {};
    /* eslint-enable no-param-reassign */
  }

  // Check for message
  if (status && 'string' === typeof status && '' !== status) {
    console.log(`tweeting: ${status}`);
    client.post('statuses/update', Object.assign({}, params, { status }), (err, tweet, res) => {
      if (err) {
        console.log(`tweet failed: response body: ${res.body}`);
      } else {
        console.log(`tweet success: ${tweet.id_str}`);
      }
    });
  } else if (callback) {
    callback(new Error('No message provided')); // Write Tests
  } else {
    throw new Error('Failure with no callback');
  }
};

const attach = (command, description, usage, callback) => {
  /* eslint-disable no-param-reassign */
  if ('function' === typeof description) {
    callback = description;
    description = 'No description provided';
    usage = `${command} <args>`;
  }
  if ('function' === typeof usage) {
    callback = usage;
    usage = `${command} <args>`;
  }
  /* eslint-enable no-param-reassign */
  if ('undefined' === typeof command || 'function' === typeof command || '' === command || 1 < command.split(' ').length) {
    throw new Error('No command to watch');
  }
  if ('undefined' === typeof callback) {
    throw new Error('No callback to return');
  }

  tweetHandlers.push({ command, description, usage, callback });
  console.log(`module loaded: '${command}'`);
};

const sendResponse = (event, handler, res) => {
  const replyID = event.id_str;
  const status = `@${event.user.screen_name} ${res}`;

  tweet(status, { in_reply_to_status_id: replyID });
};

// Checks if a specified command exist or returns a list of all valid commands.
function commands() {
  return tweetHandlers;
}

function restartStream() {
  if (1000 * 60 * 20 >= restartInterval) {
    restartInterval *= 2;
  }
  console.log(`stopped twitter stream...restarting in ${restartInterval}`);
  stream.destroy();
  setTimeout(activateStream, restartInterval);
}

function activateStream() {
  console.log('watching twitter stream');
  stream = client.stream('statuses/filter', { track: screenName });
  stream.on('data', (event) => {
    if (isTweet(event)) {
      const command = event.text.split(' ')[1];
      tweetHandlers.forEach((handler) => {
        if (handler.command.toLowerCase() === command.toLowerCase()) {
          handler.callback(event.text.split(' ').slice(2).join(' '), (err, res) => {
            if (err || 'undefined' === typeof res) {
              console.log(`middleware failed: ${handler.command}`);
            } else {
              sendResponse(event, handler, res);
            }
          });
        }
      });
    } else {
      console.log('twitter message:');
      console.log(event);
    }
  });

  // Handle stream errors
  stream.on('error', (err) => {
    if ('Status Code: 420' === err.message) {
      console.log('rate limit hit');
      restartStream();
    } else {
      console.log(`streaming error: ${err.message}`);
    }
  });
}

const init = (callback) => {
  if (callback) {
    console.log('initializing twitterbot...');
    console.log('checking twitterbot configuration...');
    if (!config.consumer_key || !config.consumer_secret
      || !config.access_token_key || !config.access_token_secret) {
      console.log('no twitter api credentials found...');
      callback(Error('No Twitter API credentials.'));
    } else {
      client = new Twitter(config);

      // Fetch Username
      client.get('account/settings', (err, data) => {
        console.log('fetching twitter account info...');
        if (err) {
          console.log(err);
        }

        if (data && data.screen_name !== screenName) {
          screenName = data.screen_name;
        }
        console.log(`twitterbot screen name: @${screenName}`);

        // Watch Twitter Feed
        activateStream();

        // Return client for syncronous modules (Ding)
        callback(null);
      });
    }
  } else {
    throw new Error('No Callback Provided');
  }
};

// Write Tests
module.exports.init = init;
module.exports.tweet = tweet;
module.exports.attach = attach;
module.exports.commands = commands;

// Reset the rate limit timer backoff every two hours
schedule.scheduleJob('0 */2 * * *', () => {
  restartInterval = 1000 * 30;
});<commit_msg>Pull screen name from Twitter<commit_after>const Twitter = require('twitter');
const schedule = require('node-schedule');
const config = require('../config/twitter-config.js');
const _ = require('lodash');

/**
 * Twitterbot - Handles all communication between Ariadne and the Twitter
 * Streaming and REST APIs
 */

let client = {};
let stream;
let restartInterval = 1000 * 30; // Default to 30 seconds
const tweetHandlers = [];

const isTweet = _.conforms({
  id_str: _.isString,
  text: _.isString,
});

const tweet = (status, params, callback) => {
  // Handle in case no params are provided
  if ('function' === typeof params && 'undefined' === typeof callback) {
    /* eslint-disable no-param-reassign */
    callback = params;
    params = {};
    /* eslint-enable no-param-reassign */
  }

  // Check for message
  if (status && 'string' === typeof status && '' !== status) {
    console.log(`tweeting: ${status}`);
    client.post('statuses/update', Object.assign({}, params, { status }), (err, tweet, res) => {
      if (err) {
        console.log(`tweet failed: response body: ${res.body}`);
      } else {
        console.log(`tweet success: ${tweet.id_str}`);
      }
    });
  } else if (callback) {
    callback(new Error('No message provided')); // Write Tests
  } else {
    throw new Error('Failure with no callback');
  }
};

const attach = (command, description, usage, callback) => {
  /* eslint-disable no-param-reassign */
  if ('function' === typeof description) {
    callback = description;
    description = 'No description provided';
    usage = `${command} <args>`;
  }
  if ('function' === typeof usage) {
    callback = usage;
    usage = `${command} <args>`;
  }
  /* eslint-enable no-param-reassign */
  if ('undefined' === typeof command || 'function' === typeof command || '' === command || 1 < command.split(' ').length) {
    throw new Error('No command to watch');
  }
  if ('undefined' === typeof callback) {
    throw new Error('No callback to return');
  }

  tweetHandlers.push({ command, description, usage, callback });
  console.log(`module loaded: '${command}'`);
};

const sendResponse = (event, handler, res) => {
  const replyID = event.id_str;
  const status = `@${event.user.screen_name} ${res}`;

  tweet(status, { in_reply_to_status_id: replyID });
};

// Checks if a specified command exist or returns a list of all valid commands.
function commands() {
  return tweetHandlers;
}

function restartStream(screenName) {
  if (1000 * 60 * 20 >= restartInterval) {
    restartInterval *= 2;
  }
  console.log(`stopped twitter stream...restarting in ${restartInterval}`);
  stream.destroy();
  setTimeout(() => {
    activateStream(screenName);
  }, restartInterval);
}

function activateStream(screenName) {
  console.log('watching twitter stream');
  stream = client.stream('statuses/filter', { track: screenName });
  stream.on('data', (event) => {
    if (isTweet(event)) {
      const command = event.text.split(' ')[1];
      tweetHandlers.forEach((handler) => {
        if (handler.command.toLowerCase() === command.toLowerCase()) {
          handler.callback(event.text.split(' ').slice(2).join(' '), (err, res) => {
            if (err || 'undefined' === typeof res) {
              console.log(`middleware failed: ${handler.command}`);
            } else {
              sendResponse(event, handler, res);
            }
          });
        }
      });
    } else {
      console.log('twitter message:');
      console.log(event);
    }
  });

  // Handle stream errors
  stream.on('error', (err) => {
    if ('Status Code: 420' === err.message) {
      console.log('rate limit hit');
      restartStream(screenName);
    } else {
      console.log(`streaming error: ${err.message}`);
    }
  });
}

function fetchUsername(client, interval, callback) {
  client.get('account/settings', (err, data, res) => {
    console.log('fetching twitter account info...');

    // Log non-breaking errors
    if (err) {
      console.log(err);
    }

    // Restart if connection limit hit
    if (420 === res.statusCode) {
      console.log(`connection limit exceeded...retrying in ${interval / 1000} seconds`);
      setInterval(() => {
        fetchUsername(client, interval * 2, () => {
          console.log('twitter connection established...');
        });
      }, interval);
    }

    // Check for screen name
    if (data && data.screen_name) {
      callback(null, data.screen_name);
    } else {
      callback(Error('no screen name.'));
    }
  });
}

const init = (callback) => {
  if (callback) {
    console.log('initializing twitterbot...');
    console.log('checking twitterbot configuration...');
    if (!config.consumer_key || !config.consumer_secret
      || !config.access_token_key || !config.access_token_secret) {
      console.log('no twitter api credentials found...');
      callback(Error('no twitter api credentials.'));
    } else {
      client = new Twitter(config);
      // Fetch Username
      fetchUsername(client, restartInterval, (err, screenName) => {
        if (err) {
          callback(Error('no screen name'));
        } else {
          console.log(`twitterbot screen name: @${screenName}`);
          // Watch Twitter Feed
          activateStream(screenName);

          // Return client for syncronous modules (Ding)
          callback(null);
        }
      });
    }
  } else {
    throw new Error('No Callback Provided');
  }
};

// Write Tests
module.exports.init = init;
module.exports.tweet = tweet;
module.exports.attach = attach;
module.exports.commands = commands;

// Reset the rate limit timer backoff every two hours
schedule.scheduleJob('0 */2 * * *', () => {
  restartInterval = 1000 * 30;
});<|endoftext|>


<commit_before>import { $each, $setRef, $sel } from 'core/dom';
import { $extend, $isObject, $isString, $toArray } from 'core/types';
import { $exec } from 'core/core';
import { _doc } from 'core/variables';

const bound = [];
const custom = {};

function _bind(els, obj, options) {
	// Redefine variables when delegating
	if (options && options.delegate) {
		options.targ = els;
		els = options.delegate;
	}

	// For each element attach events
	$each(els, el => {
		// Loop through object events
		for (let key in obj) {
			let evts = key.split(' ');
			let	i = 0;

			for (; i < evts.length; i++) {
				let conf = $extend({
						args: [],
						once: false,
						scope: el
					}, options);
				let	fn = obj[key];
				let	evt = evts[i];
				let	ev = evt;
				let	parts = ev.split('.');
				let	f = fn;
				evt = parts[0];

				if (parts.length == 1 && conf.namespace) {
					ev += '.' + conf.namespace;
				}

				// Prepend element to callback arguments if necessary
				if (conf.args[1] !== el) {
					conf.args.unshift(0, el);
				}

				(function(el, evt, fn, f, conf) {
					let cb = e => {
						let cont = true;
						conf.args[0] = e;

						// If watch within ancestor make sure the target
						// matches the selector
						if (conf.targ) {
							let targ = conf.targ;
							let	sel = targ._$ ? targ.sel : targ;

							// Update refs when targeting ref
							if ($isString(sel) &&
								sel.indexOf('ref:') > -1) {
								$setRef(el);
							}

							cont = $toArray($sel(sel)).some(el => el.contains(e.target) && (targ = el));

							// Ensure element argument is the target
							conf.args[1] = conf.scope = targ;
						}

						if (cont) {
							$exec(fn, conf);

							// Unbind after first execution
							if (conf.once) {
								_off(el, evt, f);
							}
						}
					};

					// Ensure the specified element, event, and function
					// combination hasn't already been bound
					if (evt != 'init' && ! _bound(el, ev, f, conf.targ).length) {
						// Determine if the event is native or custom
						if ('on' + evt in el) {
							el.addEventListener(evt, cb, false);
						} else if (custom[evt]) {
							custom[evt][0](el, fn, conf);
						}

						bound.push({
							el: el,
							ev: ev,
							evt: evt,
							cb: cb,
							fn: f,
							targ: conf.targ
						});
					}

					if (evt == 'init' || conf.init === true) {
						cb();
					}
				})(el, evt, fn, f, conf);
			}
		}
	}, options);
}

/**
 * Detach event(s) from element
 *
 * @private
 * @param {(HTMLElement|string)} [sel]
 * @param {string} [evt]
 * @param {function} [fn]
 */
function _off(sel, evt, fn) {
	$each(_bound(sel, evt, fn), e => {
		if ('on' + e.evt in _doc) {
			e.el.removeEventListener(e.evt, e.cb);
		} else if (custom[e.evt]) {
			custom[e.evt][1](e.el, e.cb);
		}

		bound.splice(bound.indexOf(e), 1);
	});
}

function _bound(target, event, fn, delegateTarg) {
	let segs = (event || '').split('.');
	let	matches = [];
	target = target || [0];

	$each(target, el => {
		Object.keys(bound).forEach(e => {
			let binding = bound[e];
			let	parts = binding.ev.split('.');
			let	match = true;

			if (el && el !== binding.el) {
				match = false;
			}

			if (event &&
				(
					segs[0] !== '' &&
					segs[0] != parts[0]
				) ||
				(
					segs[1] &&
					segs[1] != parts[1]
				)) {
				match = false;
			}

			if (fn && String(fn) !== String(binding.fn)) {
				match = false;
			}

			// If delegated event, check against target element
			if ((delegateTarg && binding.targ) && delegateTarg.sel !== binding.targ.sel) {
				match = false;
			}

			if (match) {
				matches.push(binding);
			}
		});
	});

	return target ? matches : bound;
}

export default {
	/**
	 * Bind event function to element
	 *
	 * @param {(HTMLElement|object|string)} target
	 * @param {(object|string)} a - event name or object of events
	 * @param {(function|object)} [b] - event callback or options object
	 * @param {(object|string)} [c] - event options
	 * @param {Array} [c.args] - callback arguments
	 * @param {(HTMLElement|string)} [c.context=document]
	 * @param {(HTMLElement|string)} [c.delegate]
	 * @param {boolean} [c.once=false] - remove event after first execution
	 * @param {object} [c.scope]
	 */
	on(target, a, b, c) {
		let evts = [];

		if ($isObject(target) && ! target._$) {
			let keys = Object.keys(target);
			let	i = 0;

			for (; i < keys.length; i++) {
				let key = keys[i];
				evts = target[key];

				_bind(key, evts, a);
			}
		} else {
			if ($isString(a)) {
				evts[a] = b;
			} else {
				evts = a;
				c = b;
			}

			_bind(target, evts, c);
		}
	},

	/**
	 * Remove specified event from specified element
	 *
	 * @param {(HTMLElement|string)} [target]
	 * @param {(object|string)} a - event name or object of events
	 * @param {function} [b] - specific function to remove
	 */
	off(target, a, b) {
		let obj = a;

		if (a) {
			if ($isString(a)) {
				obj = [];
				obj[a] = b;
			}

			for (let key in obj) {
				let evts = key.split(' ');
				let	i = 0;

				for (; i < evts.length; i++) {
					let evt = evts[i];
					let fn = obj[evt];

					_off(target, evt, fn);
				}
			}
		} else {
			_off(target);
		}
	},

	/**
	 * Get currently bound events to optional specified element and event|function
	 *
	 * @param {(boolean|HTMLElement|string)} [target]
	 * @param {string} [event] - event name to match
	 * @param {function} [fn] - specific function to match
	 * @param {HTMLElement} [delegateTarg] - targets of delegated event
	 * @returns {Array} matches
	 */
	bound(target, event, fn, delegateTarg) {
		return _bound(target, event, fn, delegateTarg);
	},

	/**
	 * Execute bound event for each matching selection
	 *
	 * @param {(HTMLElement|string)} target
	 * @param {string} name
	 */
	trigger(target, name) {
		let fn = function() {};

		_bound(target, name).forEach(e => {
			e.cb({
				target: e.el,
				preventDefault: fn,
				stopPropagation: fn
			});
		});
	},

	/**
	 * Add a custom event
	 *
	 * @param {string} name
	 * @param {function} on
	 * @param {function} off
	 */
	addEvent(name, on, off) {
		custom[name] = [on, off];
	}
}<commit_msg>Change variable declarations to let<commit_after>import { $each, $setRef, $sel } from 'core/dom';
import { $extend, $isObject, $isString, $toArray } from 'core/types';
import { $exec } from 'core/core';
import { _doc } from 'core/variables';

let custom = {};
let bound = [];

function _bind(els, obj, options) {
	// Redefine variables when delegating
	if (options && options.delegate) {
		options.targ = els;
		els = options.delegate;
	}

	// For each element attach events
	$each(els, el => {
		// Loop through object events
		for (let key in obj) {
			let evts = key.split(' ');
			let	i = 0;

			for (; i < evts.length; i++) {
				let conf = $extend({
						args: [],
						once: false,
						scope: el
					}, options);
				let	fn = obj[key];
				let	evt = evts[i];
				let	ev = evt;
				let	parts = ev.split('.');
				let	f = fn;
				evt = parts[0];

				if (parts.length == 1 && conf.namespace) {
					ev += '.' + conf.namespace;
				}

				// Prepend element to callback arguments if necessary
				if (conf.args[1] !== el) {
					conf.args.unshift(0, el);
				}

				(function(el, evt, fn, f, conf) {
					let cb = e => {
						let cont = true;
						conf.args[0] = e;

						// If watch within ancestor make sure the target
						// matches the selector
						if (conf.targ) {
							let targ = conf.targ;
							let	sel = targ._$ ? targ.sel : targ;

							// Update refs when targeting ref
							if ($isString(sel) &&
								sel.indexOf('ref:') > -1) {
								$setRef(el);
							}

							cont = $toArray($sel(sel)).some(el => el.contains(e.target) && (targ = el));

							// Ensure element argument is the target
							conf.args[1] = conf.scope = targ;
						}

						if (cont) {
							$exec(fn, conf);

							// Unbind after first execution
							if (conf.once) {
								_off(el, evt, f);
							}
						}
					};

					// Ensure the specified element, event, and function
					// combination hasn't already been bound
					if (evt != 'init' && ! _bound(el, ev, f, conf.targ).length) {
						// Determine if the event is native or custom
						if ('on' + evt in el) {
							el.addEventListener(evt, cb, false);
						} else if (custom[evt]) {
							custom[evt][0](el, fn, conf);
						}

						bound.push({
							el: el,
							ev: ev,
							evt: evt,
							cb: cb,
							fn: f,
							targ: conf.targ
						});
					}

					if (evt == 'init' || conf.init === true) {
						cb();
					}
				})(el, evt, fn, f, conf);
			}
		}
	}, options);
}

/**
 * Detach event(s) from element
 *
 * @private
 * @param {(HTMLElement|string)} [sel]
 * @param {string} [evt]
 * @param {function} [fn]
 */
function _off(sel, evt, fn) {
	$each(_bound(sel, evt, fn), e => {
		if ('on' + e.evt in _doc) {
			e.el.removeEventListener(e.evt, e.cb);
		} else if (custom[e.evt]) {
			custom[e.evt][1](e.el, e.cb);
		}

		bound.splice(bound.indexOf(e), 1);
	});
}

function _bound(target, event, fn, delegateTarg) {
	let segs = (event || '').split('.');
	let	matches = [];
	target = target || [0];

	$each(target, el => {
		Object.keys(bound).forEach(e => {
			let binding = bound[e];
			let	parts = binding.ev.split('.');
			let	match = true;

			if (el && el !== binding.el) {
				match = false;
			}

			if (event &&
				(
					segs[0] !== '' &&
					segs[0] != parts[0]
				) ||
				(
					segs[1] &&
					segs[1] != parts[1]
				)) {
				match = false;
			}

			if (fn && String(fn) !== String(binding.fn)) {
				match = false;
			}

			// If delegated event, check against target element
			if ((delegateTarg && binding.targ) && delegateTarg.sel !== binding.targ.sel) {
				match = false;
			}

			if (match) {
				matches.push(binding);
			}
		});
	});

	return target ? matches : bound;
}

export default {
	/**
	 * Bind event function to element
	 *
	 * @param {(HTMLElement|object|string)} target
	 * @param {(object|string)} a - event name or object of events
	 * @param {(function|object)} [b] - event callback or options object
	 * @param {(object|string)} [c] - event options
	 * @param {Array} [c.args] - callback arguments
	 * @param {(HTMLElement|string)} [c.context=document]
	 * @param {(HTMLElement|string)} [c.delegate]
	 * @param {boolean} [c.once=false] - remove event after first execution
	 * @param {object} [c.scope]
	 */
	on(target, a, b, c) {
		let evts = [];

		if ($isObject(target) && ! target._$) {
			let keys = Object.keys(target);
			let	i = 0;

			for (; i < keys.length; i++) {
				let key = keys[i];
				evts = target[key];

				_bind(key, evts, a);
			}
		} else {
			if ($isString(a)) {
				evts[a] = b;
			} else {
				evts = a;
				c = b;
			}

			_bind(target, evts, c);
		}
	},

	/**
	 * Remove specified event from specified element
	 *
	 * @param {(HTMLElement|string)} [target]
	 * @param {(object|string)} a - event name or object of events
	 * @param {function} [b] - specific function to remove
	 */
	off(target, a, b) {
		let obj = a;

		if (a) {
			if ($isString(a)) {
				obj = [];
				obj[a] = b;
			}

			for (let key in obj) {
				let evts = key.split(' ');
				let	i = 0;

				for (; i < evts.length; i++) {
					let evt = evts[i];
					let fn = obj[evt];

					_off(target, evt, fn);
				}
			}
		} else {
			_off(target);
		}
	},

	/**
	 * Get currently bound events to optional specified element and event|function
	 *
	 * @param {(boolean|HTMLElement|string)} [target]
	 * @param {string} [event] - event name to match
	 * @param {function} [fn] - specific function to match
	 * @param {HTMLElement} [delegateTarg] - targets of delegated event
	 * @returns {Array} matches
	 */
	bound(target, event, fn, delegateTarg) {
		return _bound(target, event, fn, delegateTarg);
	},

	/**
	 * Execute bound event for each matching selection
	 *
	 * @param {(HTMLElement|string)} target
	 * @param {string} name
	 */
	trigger(target, name) {
		let fn = function() {};

		_bound(target, name).forEach(e => {
			e.cb({
				target: e.el,
				preventDefault: fn,
				stopPropagation: fn
			});
		});
	},

	/**
	 * Add a custom event
	 *
	 * @param {string} name
	 * @param {function} on
	 * @param {function} off
	 */
	addEvent(name, on, off) {
		custom[name] = [on, off];
	}
}<|endoftext|>


<commit_before>var scrolled = false;
var objs = [];
var defaultConfig = {
  querySelector: '.ak-scrolltoggle',
  upClassName: 'ak-scrolltoggle--up',
  downClassName: 'ak-scrolltoggle--down',
  topClassName: null,
  offset: 5
};

/**
 * Toggles a class on an element when a scroll direction has been detected.
 * @param {Element} el The element.
 * @param {Object} config Config options.
 * @constructor
 */
function ScrollToggle(el, config) {
  this.el_ = el;
  // TODO(stevenle): support config overrides per element using a data- attr.
  this.config_ = config;
  this.lastScrollPos_ = 0;
}

/**
 * Callback for scroll events.
 */
ScrollToggle.prototype.onScroll = function() {
  var scrollPos = document.body.scrollTop;
  if (Math.abs(this.lastScrollPos_ - scrollPos) < this.config_.offset) {
    return;
  }

  if (scrollPos == 0 && this.config_.topClassName) {
    // Top of page.
    this.el_.classList.remove(this.config_.upClassName);
    this.el_.classList.remove(this.config_.downClassName);
    this.el_.classList.add(this.config_.topClassName);
  } else if (scrollPos > this.lastScrollPos_) {
    // Scrolled down.
    this.el_.classList.remove(this.config_.topClassName);
    this.el_.classList.remove(this.config_.upClassName);
    this.el_.classList.add(this.config_.downClassName);
  } else {
    // Scrolled up.
    this.el_.classList.remove(this.config_.topClassName);
    this.el_.classList.remove(this.config_.downClassName);
    this.el_.classList.add(this.config_.upClassName);
  }
  this.lastScrollPos_ = scrollPos;
};

/**
 * Initializes the scroll listener for all elements tagged with the
 * "ak-scrolltoggle" class (configurable using the "className" config).
 * @param {Object=} opt_config Config options.
 */
function init(opt_config) {
  var config = cloneAndMerge(defaultConfig, opt_config || {});

  var els = document.querySelectorAll(config.querySelector);
  for (var i = 0, el; el = els[i]; i++) {
    var obj = new ScrollToggle(el, config);
    objs.push(obj);
  }

  // Debounce the scroll event by executing the onScroll callback using a timed
  // interval.
  document.addEventListener('scroll', function(e) {
    scrolled = true;
  });
  window.setInterval(function() {
    if (scrolled) {
      for (var i = 0, obj; obj = objs[i]; i++) {
        obj.onScroll();
      }
    }
  }, 250);
}

/**
 * Creates a clone of a and merges b.
 * @param {Object} a Object.
 * @param {Object} b Object.
 */
function cloneAndMerge(a, b) {
  // Clone a version of a.
  var result = {};
  for (var key in a) {
    result[key] = a[key];
  }
  // Merge b into copy of a.
  for (var key in b) {
    result[key] = b[key];
  }
  return result;
}

module.exports = {
  init: init
};
<commit_msg>Initialize element's scroll pos<commit_after>var scrolled = false;
var objs = [];
var defaultConfig = {
  querySelector: '.ak-scrolltoggle',
  upClassName: 'ak-scrolltoggle--up',
  downClassName: 'ak-scrolltoggle--down',
  topClassName: null,
  offset: 5
};

/**
 * Toggles a class on an element when a scroll direction has been detected.
 * @param {Element} el The element.
 * @param {Object} config Config options.
 * @constructor
 */
function ScrollToggle(el, config) {
  this.el_ = el;
  // TODO(stevenle): support config overrides per element using a data- attr.
  this.config_ = config;
  this.lastScrollPos_ = 0;

  // Initialize the current scroll position.
  this.onScroll();
}

/**
 * Callback for scroll events.
 */
ScrollToggle.prototype.onScroll = function() {
  var scrollPos = document.body.scrollTop;
  if (Math.abs(this.lastScrollPos_ - scrollPos) < this.config_.offset) {
    return;
  }

  if (scrollPos == 0 && this.config_.topClassName) {
    // Top of page.
    this.el_.classList.remove(this.config_.upClassName);
    this.el_.classList.remove(this.config_.downClassName);
    this.el_.classList.add(this.config_.topClassName);
  } else if (scrollPos > this.lastScrollPos_) {
    // Scrolled down.
    this.el_.classList.remove(this.config_.topClassName);
    this.el_.classList.remove(this.config_.upClassName);
    this.el_.classList.add(this.config_.downClassName);
  } else {
    // Scrolled up.
    this.el_.classList.remove(this.config_.topClassName);
    this.el_.classList.remove(this.config_.downClassName);
    this.el_.classList.add(this.config_.upClassName);
  }
  this.lastScrollPos_ = scrollPos;
};

/**
 * Initializes the scroll listener for all elements tagged with the
 * "ak-scrolltoggle" class (configurable using the "className" config).
 * @param {Object=} opt_config Config options.
 */
function init(opt_config) {
  var config = cloneAndMerge(defaultConfig, opt_config || {});

  var els = document.querySelectorAll(config.querySelector);
  for (var i = 0, el; el = els[i]; i++) {
    var obj = new ScrollToggle(el, config);
    objs.push(obj);
  }

  // Debounce the scroll event by executing the onScroll callback using a timed
  // interval.
  document.addEventListener('scroll', function(e) {
    scrolled = true;
  });
  window.setInterval(function() {
    if (scrolled) {
      for (var i = 0, obj; obj = objs[i]; i++) {
        obj.onScroll();
      }
    }
  }, 250);
}

/**
 * Creates a clone of a and merges b.
 * @param {Object} a Object.
 * @param {Object} b Object.
 */
function cloneAndMerge(a, b) {
  // Clone a version of a.
  var result = {};
  for (var key in a) {
    result[key] = a[key];
  }
  // Merge b into copy of a.
  for (var key in b) {
    result[key] = b[key];
  }
  return result;
}

module.exports = {
  init: init
};
<|endoftext|>


<commit_before>// WebTorrent Desktop server API
// - Tell auto-updaters the latest version
// - Log crash reports
// - Log telemetry
module.exports = { serve }

var bodyParser = require('body-parser')
var cp = require('child_process')
var express = require('express')
var fs = require('fs')
var mkdirp = require('mkdirp')
var multer = require('multer')
var path = require('path')
var semver = require('semver')
var url = require('url')

var auth = require('./auth')
var config = require('../config')
var secret = require('../secret')

var DESKTOP_VERSION = config.desktopVersion
var RELEASES_URL = 'https://github.com/webtorrent/webtorrent-desktop/releases/download'

var TELEMETRY_PATH = path.join(config.logPath, 'telemetry')
var CRASH_REPORTS_PATH = path.join(config.logPath, 'crash-reports')

var SUMMARIZE_PATH = path.join(__dirname, '..', 'bin', 'summarize-telemetry.js')

// Queue telemetry messages, log them to a file in order
var telemetryLines = []
var isWritingTelemetry = false

// Attempt to create the needed log folders
try { mkdirp.sync(TELEMETRY_PATH) } catch (err) {}
try { mkdirp.sync(CRASH_REPORTS_PATH) } catch (err) {}

/**
 * Summarize the telemetry into summary.json, every day at UTC 12:15AM. Takes
 * a bit of time to crunch, so we do it in the background. The dashboard uses
 * the data in summary.json.
 *
 * This is a poor-man's cron-job. Not running in development by default to
 * avoid hitting Github's API too often.
 */
if (config.isProd) cronSummarizeTelemetry()

function cronSummarizeTelemetry () {
  // Run immediately
  summarizeTelemetry()

  // Wait until tomorrow, 12:15AM UTC
  var msPerDay = 24 * 3600 * 1000
  var msOfDay = new Date().getTime() % msPerDay
  var msWait = (msPerDay - msOfDay + (15 * 60 * 1000)) % msPerDay
  var minsWait = msWait / 60 / 1000
  console.log('Running summarizeTelemetry next in ' + minsWait + ' minutes')
  setTimeout(cronSummarizeTelemetry, msWait)
}

function serve (app) {
  serveTelemetryAPI(app)
  serveCrashReportsAPI(app)
  serveAnnouncementsAPI(app)
  serveUpdateAPI(app)
}

// Log telemetry JSON summaries to a file, one per line
function serveTelemetryAPI (app) {
  app.post('/desktop/telemetry', bodyParser.json(), function (req, res) {
    var summary = req.body
    summary.ip = req.ip
    var summaryJSON = JSON.stringify(summary)
    telemetryLines.push(summaryJSON)
    writeTelemetryLines()
    res.end()
  })

  app.use('/desktop/telemetry/', [
    auth(secret.credentials),
    serveTelemetryDashboard,
    express.static(TELEMETRY_PATH)
  ])
}

function writeTelemetryLines () {
  if (isWritingTelemetry) return // Don't interleave writes
  if (telemetryLines.length === 0) return // Nothing to do

  var today = new Date().toISOString().substring(0, 10) // YYYY-MM-DD
  var telemetryPath = path.join(TELEMETRY_PATH, today + '.log')
  var lines = telemetryLines.join('\n') + '\n'
  telemetryLines = []
  isWritingTelemetry = true
  fs.appendFile(telemetryPath, lines, function (err) {
    isWritingTelemetry = false
    if (err) console.error('Error saving telemetry: ' + err.message)
    writeTelemetryLines()
  })
}

// Summarize telemetry information: active users, monthly growth, most common errors, etc
function serveTelemetryDashboard (req, res, next) {
  if (req.url !== '/') return next()
  var orig = req.originalUrl // eg '/desktop/telemetry'
  if (!orig.endsWith('/')) return res.redirect(orig + '/')

  // Once we get here, we're serving this exact path: /desktop/telemetry/
  fs.readdir(TELEMETRY_PATH, function (err, files) {
    if (err) return res.status(500).send(err.message)

    var filesByMonth = []
    files.forEach(function (file, i) {
      if (i === 0 || file.substring(0, 7) !== files[i - 1].substring(0, 7)) {
        filesByMonth.push([])
      }
      filesByMonth[filesByMonth.length - 1].push(file)
    })

    // Load summary.json from the telemetry log folder...
    var summaryPath = path.join(TELEMETRY_PATH, 'summary.json')
    fs.readFile(summaryPath, 'utf8', function (err, summaryJSON) {
      var summary = err
        ? { telemetry: [] }
        : JSON.parse(summaryJSON)

      // Versions
      var versions = summary.releases
        .map(function (release) {
          // Tag name is something like 'v0.12.0', version is '0.12.0'
          return release.tag_name.substring(1)
        }).filter(function (version) {
          return semver.gt(version, '0.10.0')
        }).map(function (version) {
          return version === '0.11.0' ? 'pre-0.12' : version
        }).reverse()

      // Calculate week-on-week growth
      var telem = summary.telemetry
      var yesterday = telem && telem[telem.length - 2]
      var t7 = telem && telem[telem.length - 9]
      var percentWeeklyGrowth = (yesterday && t7)
        ? ((100 * yesterday.actives.last7 / t7.actives.last7) - 100).toFixed(1)
        : '-'

      // Most common errors
      var latestVersion = versions[versions.length - 1]
      var mostCommonErrorsDate = yesterday ? yesterday.date : '-'
      var allErrors = (yesterday ? yesterday.errors : [])
        .map(function (err) {
          err.stack = err.stack.replace(/\(.*app.asar/g, '(...')
          return err
        })
      var mostCommonErrors = {
        all: allErrors.slice(0, 10),
        latest: allErrors.filter(function (err) {
          return err.versions.includes(latestVersion)
        }).slice(0, 10)
      }
      console.log('Latest version: ' + latestVersion)

      res.render('telemetry-dashboard', {
        filesByMonth,
        summary,
        percentWeeklyGrowth,
        mostCommonErrors,
        mostCommonErrorsDate,
        versions,
        latestVersion
      })
    })
  })
}

// Save electron process crash reports (from Crashpad), each in its own file
function serveCrashReportsAPI (app) {
  var upload = multer({ dest: CRASH_REPORTS_PATH }).single('upload_file_minidump')

  app.post('/desktop/crash-report', upload, function (req, res) {
    req.body.filename = req.file.filename
    var crashLog = JSON.stringify(req.body, undefined, 2)

    fs.writeFile(req.file.path + '.json', crashLog, function (err) {
      if (err) {
        console.error('Error saving crash report: ' + err.message)
        res.status(500)
      }
      res.end()
    })
  })
}

// This lets us send a message to all WebTorrent Desktop users
function serveAnnouncementsAPI (app) {
  app.get('/desktop/announcement', function (req, res) {
    res.status(204).end()
  })
}

// Tell the auto updaters when new version is available
function serveUpdateAPI (app) {
  // Deprecated: WebTorrent Desktop v0.0.0 - 0.2.0 use this update URL
  app.get('/app/update/?*', function (req, res) {
    res.redirect(301, req.url.replace('/app/', '/desktop/'))
  })

  // WebTorrent Desktop Mac auto-update endpoint
  app.get('/desktop/update', function (req, res) {
    var version = req.query.version
    logUpdateCheck({
      date: (new Date()).toString(),
      platform: req.query.platform,
      version: version,
      sysarch: req.query.sysarch,
      ip: req.ip
    })
    if (!semver.valid(version) || semver.lt(version, DESKTOP_VERSION)) {
      // Update is required. Send update JSON.
      // Response format docs: https://github.com/Squirrel/Squirrel.Mac#update-json-format
      res.status(200).send({
        name: 'WebTorrent v' + DESKTOP_VERSION,
        url: `${RELEASES_URL}/v${DESKTOP_VERSION}/WebTorrent-v${DESKTOP_VERSION}-darwin.zip`,
        version: DESKTOP_VERSION
      })
    } else {
      // No update required. User is on latest app version.
      res.status(204).end()
    }
  })

  // WebTorrent Desktop Windows auto-update endpoint
  app.get('/desktop/update/*', function (req, res) {
    var pathname = url.parse(req.url).pathname
    var filename = pathname.replace(/^\/desktop\/update\//i, '')

    var sysarch = req.query.sysarch || 'ia32' // if not specified, default to ia32
    var fileVersion
    if (filename === 'RELEASES') {
      logUpdateCheck({
        date: (new Date()).toString(),
        platform: req.query.platform,
        version: req.query.version,
        sysarch: sysarch,
        ip: req.ip
      })
      fileVersion = DESKTOP_VERSION
      if (sysarch === 'ia32') {
        // 32-bit Windows users get a different Squirrel RELEASES file
        filename = 'RELEASES-ia32'
      }
    } else {
      var match = /-(\d+\.\d+\.\d+)-/.exec(filename)
      fileVersion = match && match[1]
    }
    if (!fileVersion) {
      return res.status(404).end()
    }
    var redirectURL = `${RELEASES_URL}/v${fileVersion}/${filename}`
    res.redirect(302, redirectURL)
  })
}

function logUpdateCheck (log) {
  console.log('UPDATE CHECK: ' + JSON.stringify(log))
}

function summarizeTelemetry () {
  var child = cp.spawn(SUMMARIZE_PATH, [])

  // Log the progress, for easier debugging...
  child.stdout.pipe(process.stdout)
  child.stderr.pipe(process.stderr)

  child.on('close', function (code) {
    console.log(`Summarize telemetry script exited with code: ${code}`)
  })
}
<commit_msg>Fix TypeError: Cannot read property 'filename'<commit_after>// WebTorrent Desktop server API
// - Tell auto-updaters the latest version
// - Log crash reports
// - Log telemetry
module.exports = { serve }

var bodyParser = require('body-parser')
var cp = require('child_process')
var express = require('express')
var fs = require('fs')
var mkdirp = require('mkdirp')
var multer = require('multer')
var path = require('path')
var semver = require('semver')
var url = require('url')

var auth = require('./auth')
var config = require('../config')
var secret = require('../secret')

var DESKTOP_VERSION = config.desktopVersion
var RELEASES_URL = 'https://github.com/webtorrent/webtorrent-desktop/releases/download'

var TELEMETRY_PATH = path.join(config.logPath, 'telemetry')
var CRASH_REPORTS_PATH = path.join(config.logPath, 'crash-reports')

var SUMMARIZE_PATH = path.join(__dirname, '..', 'bin', 'summarize-telemetry.js')

// Queue telemetry messages, log them to a file in order
var telemetryLines = []
var isWritingTelemetry = false

// Attempt to create the needed log folders
try { mkdirp.sync(TELEMETRY_PATH) } catch (err) {}
try { mkdirp.sync(CRASH_REPORTS_PATH) } catch (err) {}

/**
 * Summarize the telemetry into summary.json, every day at UTC 12:15AM. Takes
 * a bit of time to crunch, so we do it in the background. The dashboard uses
 * the data in summary.json.
 *
 * This is a poor-man's cron-job. Not running in development by default to
 * avoid hitting Github's API too often.
 */
if (config.isProd) cronSummarizeTelemetry()

function cronSummarizeTelemetry () {
  // Run immediately
  summarizeTelemetry()

  // Wait until tomorrow, 12:15AM UTC
  var msPerDay = 24 * 3600 * 1000
  var msOfDay = new Date().getTime() % msPerDay
  var msWait = (msPerDay - msOfDay + (15 * 60 * 1000)) % msPerDay
  var minsWait = msWait / 60 / 1000
  console.log('Running summarizeTelemetry next in ' + minsWait + ' minutes')
  setTimeout(cronSummarizeTelemetry, msWait)
}

function serve (app) {
  serveTelemetryAPI(app)
  serveCrashReportsAPI(app)
  serveAnnouncementsAPI(app)
  serveUpdateAPI(app)
}

// Log telemetry JSON summaries to a file, one per line
function serveTelemetryAPI (app) {
  app.post('/desktop/telemetry', bodyParser.json(), function (req, res) {
    var summary = req.body
    summary.ip = req.ip
    var summaryJSON = JSON.stringify(summary)
    telemetryLines.push(summaryJSON)
    writeTelemetryLines()
    res.end()
  })

  app.use('/desktop/telemetry/', [
    auth(secret.credentials),
    serveTelemetryDashboard,
    express.static(TELEMETRY_PATH)
  ])
}

function writeTelemetryLines () {
  if (isWritingTelemetry) return // Don't interleave writes
  if (telemetryLines.length === 0) return // Nothing to do

  var today = new Date().toISOString().substring(0, 10) // YYYY-MM-DD
  var telemetryPath = path.join(TELEMETRY_PATH, today + '.log')
  var lines = telemetryLines.join('\n') + '\n'
  telemetryLines = []
  isWritingTelemetry = true
  fs.appendFile(telemetryPath, lines, function (err) {
    isWritingTelemetry = false
    if (err) console.error('Error saving telemetry: ' + err.message)
    writeTelemetryLines()
  })
}

// Summarize telemetry information: active users, monthly growth, most common errors, etc
function serveTelemetryDashboard (req, res, next) {
  if (req.url !== '/') return next()
  var orig = req.originalUrl // eg '/desktop/telemetry'
  if (!orig.endsWith('/')) return res.redirect(orig + '/')

  // Once we get here, we're serving this exact path: /desktop/telemetry/
  fs.readdir(TELEMETRY_PATH, function (err, files) {
    if (err) return res.status(500).send(err.message)

    var filesByMonth = []
    files.forEach(function (file, i) {
      if (i === 0 || file.substring(0, 7) !== files[i - 1].substring(0, 7)) {
        filesByMonth.push([])
      }
      filesByMonth[filesByMonth.length - 1].push(file)
    })

    // Load summary.json from the telemetry log folder...
    var summaryPath = path.join(TELEMETRY_PATH, 'summary.json')
    fs.readFile(summaryPath, 'utf8', function (err, summaryJSON) {
      var summary = err
        ? { telemetry: [] }
        : JSON.parse(summaryJSON)

      // Versions
      var versions = summary.releases
        .map(function (release) {
          // Tag name is something like 'v0.12.0', version is '0.12.0'
          return release.tag_name.substring(1)
        }).filter(function (version) {
          return semver.gt(version, '0.10.0')
        }).map(function (version) {
          return version === '0.11.0' ? 'pre-0.12' : version
        }).reverse()

      // Calculate week-on-week growth
      var telem = summary.telemetry
      var yesterday = telem && telem[telem.length - 2]
      var t7 = telem && telem[telem.length - 9]
      var percentWeeklyGrowth = (yesterday && t7)
        ? ((100 * yesterday.actives.last7 / t7.actives.last7) - 100).toFixed(1)
        : '-'

      // Most common errors
      var latestVersion = versions[versions.length - 1]
      var mostCommonErrorsDate = yesterday ? yesterday.date : '-'
      var allErrors = (yesterday ? yesterday.errors : [])
        .map(function (err) {
          err.stack = err.stack.replace(/\(.*app.asar/g, '(...')
          return err
        })
      var mostCommonErrors = {
        all: allErrors.slice(0, 10),
        latest: allErrors.filter(function (err) {
          return err.versions.includes(latestVersion)
        }).slice(0, 10)
      }
      console.log('Latest version: ' + latestVersion)

      res.render('telemetry-dashboard', {
        filesByMonth,
        summary,
        percentWeeklyGrowth,
        mostCommonErrors,
        mostCommonErrorsDate,
        versions,
        latestVersion
      })
    })
  })
}

// Save electron process crash reports (from Crashpad), each in its own file
function serveCrashReportsAPI (app) {
  var upload = multer({ dest: CRASH_REPORTS_PATH }).single('upload_file_minidump')

  app.post('/desktop/crash-report', upload, function (req, res) {
    if (!req.file) return res.status(500).end()

    req.body.filename = req.file.filename
    var crashLog = JSON.stringify(req.body, undefined, 2)

    fs.writeFile(req.file.path + '.json', crashLog, function (err) {
      if (err) {
        console.error('Error saving crash report: ' + err.message)
        res.status(500)
      }
      res.end()
    })
  })
}

// This lets us send a message to all WebTorrent Desktop users
function serveAnnouncementsAPI (app) {
  app.get('/desktop/announcement', function (req, res) {
    res.status(204).end()
  })
}

// Tell the auto updaters when new version is available
function serveUpdateAPI (app) {
  // Deprecated: WebTorrent Desktop v0.0.0 - 0.2.0 use this update URL
  app.get('/app/update/?*', function (req, res) {
    res.redirect(301, req.url.replace('/app/', '/desktop/'))
  })

  // WebTorrent Desktop Mac auto-update endpoint
  app.get('/desktop/update', function (req, res) {
    var version = req.query.version
    logUpdateCheck({
      date: (new Date()).toString(),
      platform: req.query.platform,
      version: version,
      sysarch: req.query.sysarch,
      ip: req.ip
    })
    if (!semver.valid(version) || semver.lt(version, DESKTOP_VERSION)) {
      // Update is required. Send update JSON.
      // Response format docs: https://github.com/Squirrel/Squirrel.Mac#update-json-format
      res.status(200).send({
        name: 'WebTorrent v' + DESKTOP_VERSION,
        url: `${RELEASES_URL}/v${DESKTOP_VERSION}/WebTorrent-v${DESKTOP_VERSION}-darwin.zip`,
        version: DESKTOP_VERSION
      })
    } else {
      // No update required. User is on latest app version.
      res.status(204).end()
    }
  })

  // WebTorrent Desktop Windows auto-update endpoint
  app.get('/desktop/update/*', function (req, res) {
    var pathname = url.parse(req.url).pathname
    var filename = pathname.replace(/^\/desktop\/update\//i, '')

    var sysarch = req.query.sysarch || 'ia32' // if not specified, default to ia32
    var fileVersion
    if (filename === 'RELEASES') {
      logUpdateCheck({
        date: (new Date()).toString(),
        platform: req.query.platform,
        version: req.query.version,
        sysarch: sysarch,
        ip: req.ip
      })
      fileVersion = DESKTOP_VERSION
      if (sysarch === 'ia32') {
        // 32-bit Windows users get a different Squirrel RELEASES file
        filename = 'RELEASES-ia32'
      }
    } else {
      var match = /-(\d+\.\d+\.\d+)-/.exec(filename)
      fileVersion = match && match[1]
    }
    if (!fileVersion) {
      return res.status(404).end()
    }
    var redirectURL = `${RELEASES_URL}/v${fileVersion}/${filename}`
    res.redirect(302, redirectURL)
  })
}

function logUpdateCheck (log) {
  console.log('UPDATE CHECK: ' + JSON.stringify(log))
}

function summarizeTelemetry () {
  var child = cp.spawn(SUMMARIZE_PATH, [])

  // Log the progress, for easier debugging...
  child.stdout.pipe(process.stdout)
  child.stderr.pipe(process.stderr)

  child.on('close', function (code) {
    console.log(`Summarize telemetry script exited with code: ${code}`)
  })
}
<|endoftext|>


<commit_before>
const { pickBy, size } = require('lodash');
const { receiveFood, removeMultipleFood } = require('../reducers/food');
const { removeRoom } = require('../reducers/rooms');
const store = require('../store');

let types = ['box', 'sphere'];
let elapsedTime = Date.now(),
    id = 1;

const spawnFood = io => {
  if (Date.now() - elapsedTime > 33){
    //console.log('spawning food');
    elapsedTime = Date.now();
    let { rooms, food, players } = store.getState();
    //console.log('rooms', rooms);
      for (let currentRoom of rooms) {
      //  console.log('currentRoom', currentRoom);
        let roomPlayers = pickBy(players, ({ room }) => room === currentRoom);
        let roomFood = pickBy(food, ({ room }) => room === currentRoom );
        if (size(roomPlayers)) {
        //  console.log('generating food');
          if (Object.keys(food).length < 300) {
            let x = (Math.random() * 1000) - 500,
                y = (Math.random() * 1000) - 500,
                z = (Math.random() * 1000) - 500,
                type = types[~~(Math.random() * types.length)],
                foodSize = [];

            switch (type){
              case 'box':
                foodSize = [
                  2 + (Math.random() * 20),
                  2 + (Math.random() * 20),
                  2 + (Math.random() * 20),
                ];
                break;
              case 'sphere':
              default:
                foodSize = [
                  3 + (Math.random() * 20)
                ];
                break;
            }

            // scale food to random player
            let randomPlayerId = Object.keys(roomPlayers)[~~(Math.random() * Object.keys(roomPlayers).length)];
            let playerToFeed = roomPlayers[randomPlayerId];
            let parms = foodSize.map(e => ~~(e * playerToFeed.scale));
            //console.log(parms)

            let data = { x, y, z, type, parms, room: currentRoom };
            store.dispatch(receiveFood(id, data));
            io.sockets.in(currentRoom).emit('add_food', id, data);
            id++;
          }
        } else {
          store.dispatch(removeMultipleFood(roomFood));
          store.dispatch(removeRoom(currentRoom));
        }
      }
    }
};

const broadcastState = (io) => {
  setInterval(() => {
    // On set interval, emit all player positions to all players in each room
    let { players, rooms } = store.getState();
    for (let currentRoom of rooms) {
      let roomPlayers = pickBy(players, ({ room }) => room === currentRoom);
      io.sockets.in(currentRoom).emit('player_data', roomPlayers);
    }
    spawnFood(io, store);
  }, (1000 / 30));
};

// function respawn(io, store, socket, room){
//   console.log("in respawn")
//     io.sockets.in(room).emit('remove_player', socket.id);
//     store.dispatch(updatePlayer(socket.id, initPos, room));
//     io.sockets.in(room).emit('add_player', socket.id, initPos, true);
//     socket.emit('you_lose', 'You died!');
// }

module.exports = { broadcastState, spawnFood };
<commit_msg>rescale food<commit_after>
const { pickBy, size } = require('lodash');
const { receiveFood, removeMultipleFood } = require('../reducers/food');
const { removeRoom } = require('../reducers/rooms');
const store = require('../store');

let types = ['box', 'sphere'];
let elapsedTime = Date.now(),
    id = 1;

const spawnFood = io => {
  if (Date.now() - elapsedTime > 33){
    //console.log('spawning food');
    elapsedTime = Date.now();
    let { rooms, food, players } = store.getState();
    //console.log('rooms', rooms);
      for (let currentRoom of rooms) {
      //  console.log('currentRoom', currentRoom);
        let roomPlayers = pickBy(players, ({ room }) => room === currentRoom);
        let roomFood = pickBy(food, ({ room }) => room === currentRoom );
        if (size(roomPlayers)) {
        //  console.log('generating food');
          if (Object.keys(food).length < 300) {
            let x = (Math.random() * 1000) - 500,
                y = (Math.random() * 1000) - 500,
                z = (Math.random() * 1000) - 500,
                type = types[~~(Math.random() * types.length)],
                foodSize = [];

            switch (type){
              case 'box':
                foodSize = [
                  2 + (Math.random() * 8),
                  2 + (Math.random() * 4),
                  2 + (Math.random() * 3),
                ];
                break;
              case 'sphere':
              default:
                foodSize = [
                  3 + (Math.random() * 2)
                ];
                break;
            }

            // scale food to random player
            let randomPlayerId = Object.keys(roomPlayers)[~~(Math.random() * Object.keys(roomPlayers).length)];
            let playerToFeed = roomPlayers[randomPlayerId];
            let parms = foodSize.map(e => ~~(e * playerToFeed.scale));
            //console.log(parms)

            let data = { x, y, z, type, parms, room: currentRoom };
            store.dispatch(receiveFood(id, data));
            io.sockets.in(currentRoom).emit('add_food', id, data);
            id++;
          }
        } else {
          store.dispatch(removeMultipleFood(roomFood));
          store.dispatch(removeRoom(currentRoom));
        }
      }
    }
};

const broadcastState = (io) => {
  setInterval(() => {
    // On set interval, emit all player positions to all players in each room
    let { players, rooms } = store.getState();
    for (let currentRoom of rooms) {
      let roomPlayers = pickBy(players, ({ room }) => room === currentRoom);
      io.sockets.in(currentRoom).emit('player_data', roomPlayers);
    }
    spawnFood(io, store);
  }, (1000 / 30));
};

// function respawn(io, store, socket, room){
//   console.log("in respawn")
//     io.sockets.in(room).emit('remove_player', socket.id);
//     store.dispatch(updatePlayer(socket.id, initPos, room));
//     io.sockets.in(room).emit('add_player', socket.id, initPos, true);
//     socket.emit('you_lose', 'You died!');
// }

module.exports = { broadcastState, spawnFood };
<|endoftext|>


<commit_before>// Easier, more readable way of retrieving an HTTP Error type for passing through Koa
// example: throw new httpErrors.Forbidden('No way, Jose'))

import http from 'http'
import util from 'util'

class HttpError extends Error {
  constructor(statusCode, message, cause) {
    super(message)

    this.status = +(statusCode || 500)
    this.message = message
    this.cause = cause
  }

  toString() {
    let str = this.name || this.constructor.name || this.constructor.prototype.name
    if (this.message) str += ': ' + this.message
    if (this.cause) str += '; caused by ' + this.cause.toString()

    return str
  }
}

Object.keys(http.STATUS_CODES)
  .filter(code => code >= 400)
  .forEach(code => {
    let errorName = http.STATUS_CODES[code]
      .split(/\s+/)
      .map(str => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase())
      .join('')
      .replace(/\W+/g, '')

    errorName = errorName.substr(-5) === 'Error' ? (errorName) : (errorName + 'Error')

    module.exports[errorName] = function(message, cause) {
      HttpError.call(this, code, message, cause)
    }
    util.inherits(module.exports[errorName], HttpError)
    module.exports[errorName].prototype.name = errorName
  })
<commit_msg>Fix the creation of object to follow the ES6 spec.<commit_after>// Easier, more readable way of retrieving an HTTP Error type for passing through Koa
// example: throw new httpErrors.Forbidden('No way, Jose'))

import http from 'http'
import util from 'util'

class HttpError extends Error {
  constructor(statusCode, message, cause) {
    super(message)

    this.status = +(statusCode || 500)
    this.message = message
    this.cause = cause
  }

  toString() {
    let str = this.name || this.constructor.name || this.constructor.prototype.name
    if (this.message) str += ': ' + this.message
    if (this.cause) str += '; caused by ' + this.cause.toString()

    return str
  }
}

Object.keys(http.STATUS_CODES)
  .filter(code => code >= 400)
  .forEach(code => {
    let errorName = http.STATUS_CODES[code]
      .split(/\s+/)
      .map(str => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase())
      .join('')
      .replace(/\W+/g, '')

    errorName = errorName.substr(-5) === 'Error' ? (errorName) : (errorName + 'Error')

    module.exports[errorName] = function(message, cause) {
      return new HttpError(code, message, cause)
    }
    util.inherits(module.exports[errorName], HttpError)
    module.exports[errorName].prototype.name = errorName
  })
<|endoftext|>