<commit_before>// A physical location on the Earth's surface
//
// properties:
//   latitude - Latitude in decimal degrees
//   longitude - Longitude in decimal degrees
//   elevation - Elevation in feet
//   reference_position - Position to use when calculating offsets
//   x - Offset from reference position in km
//   y - Offset from reference position in km
//   position - Array containing the x,y pair
//
var Position=Fiber.extend(function() {
  return {
    // coordinates - Array containing offset pair or latitude/longitude pair
    // reference - Position to use for calculating offsets when lat/long given
    //
    // coordinates may contain an optional elevation as a third
    // element.  It must be suffixed by either 'ft' or 'm' to indicate
    // the units.
    // Latitude and Longitude numbers may be one of the following forms:
    //   Decimal degrees - 'N47.112388112'
    //   Decimal minutes - 'N38d38.109808'
    //   Decimal seconds - 'N58d27m12.138'
    init: function(coordinates, reference, magnetic_north) {
      if(!coordinates) coordinates=[];

      this.latitude = 0;
      this.longitude = 0;
      this.elevation = 0;

      this.reference_position = reference;
      this.magnetic_north = magnetic_north;
      if (!this.magnetic_north) this.magnetic_north = 0;
      this.x = 0;
      this.y = 0;
      this.position = [this.x, this.y];

      this.parse(coordinates);
    },
    parse: function(coordinates) {
      if (! /^[NESW]/.test(coordinates[0])) {
        this.x = coordinates[0];
        this.y = coordinates[1];
        this.position = [this.x, this.y];
        return;
      }

      this.latitude = this.parseCoordinate(coordinates[0]);
      this.longitude = this.parseCoordinate(coordinates[1]);

      if (coordinates[2] != null) {
        this.elevation = parseElevation(coordinates[2]);
      }

      // this function (parse4326) is moved to be able to call it if point is
      // EPSG:4326, numeric decimal, like those from GeoJSON
      if (this.reference_position != null) {
        this.x = this.longitude;
        this.y = this.latitude;
        this.parse4326();
      }
    },
    parse4326: function() {
      // if coordinates were in WGS84 EPSG:4326 (signed decimal lat/lon -12.123,83.456)
      // parse them
      this.longitude = this.x;
      this.latitude = this.y;
      this.x = this.distanceToPoint(this.reference_position.latitude,
                                    this.reference_position.longitude,
                                    this.reference_position.latitude,
                                    this.longitude);
      if (this.reference_position.longitude > this.longitude) {
        this.x *= -1;
      }

      this.y = this.distanceToPoint(this.reference_position.latitude,
                                    this.reference_position.longitude,
                                    this.latitude,
                                    this.reference_position.longitude);
      if (this.reference_position.latitude > this.latitude) {
        this.y *= -1;
      }

      // Adjust to use magnetic north instead of true north
      var t = Math.atan2(this.y, this.x);
      var r = Math.sqrt(this.x*this.x + this.y*this.y);
      t += this.magnetic_north;
      this.x = r * Math.cos(t);
      this.y = r * Math.sin(t);

      this.position = [this.x, this.y];
    },
    distanceTo: function(point) {
      return this.distanceToPoint(this.latitude,
                                  this.longitude,
                                  point.latitude,
                                  point.longitude);
    },
    // The distance in km between two locations
    distanceToPoint: function(lat_a, lng_a, lat_b, lng_b) {
      var d_lat = radians(lat_a - lat_b);
      var d_lng = radians(lng_a - lng_b);

      var a = Math.pow(Math.sin(d_lat/2), 2) +
        (Math.cos(radians(lat_a)) *
         Math.cos(radians(lat_b)) *
         Math.pow(Math.sin(d_lng/2), 2));
      var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

      return c * 6371.00;
    },
    parseCoordinate: function(coord) {
      var r = /^([NESW])(\d+(\.\d+)?)([d °](\d+(\.\d+)?))?([m '](\d+(\.\d+)?))?$/;
      var match = r.exec(coord)
      if (match == null) {
        log('Unable to parse coordinate ' + coord);
        return;
      }
      var ret = parseFloat(match[2]);
      if (match[5] != null) {
        ret += parseFloat(match[5])/60;
        if (match[8] != null) {
          ret += parseFloat(match[8])/3600;
        }
      }

      if (/[SW]/.test(match[1])) {
        ret *= -1;
      }
      return ret;
    },
  };
});
<commit_msg>Add class 'Area'<commit_after>// A physical location on the Earth's surface
//
// properties:
//   latitude - Latitude in decimal degrees
//   longitude - Longitude in decimal degrees
//   elevation - Elevation in feet
//   reference_position - Position to use when calculating offsets
//   x - Offset from reference position in km
//   y - Offset from reference position in km
//   position - Array containing the x,y pair
//
var Position=Fiber.extend(function() {
  return {
    // coordinates - Array containing offset pair or latitude/longitude pair
    // reference - Position to use for calculating offsets when lat/long given
    //
    // coordinates may contain an optional elevation as a third
    // element.  It must be suffixed by either 'ft' or 'm' to indicate
    // the units.
    // Latitude and Longitude numbers may be one of the following forms:
    //   Decimal degrees - 'N47.112388112'
    //   Decimal minutes - 'N38d38.109808'
    //   Decimal seconds - 'N58d27m12.138'
    init: function(coordinates, reference, magnetic_north) {
      if(!coordinates) coordinates=[];

      this.latitude = 0;
      this.longitude = 0;
      this.elevation = 0;

      this.reference_position = reference;
      this.magnetic_north = magnetic_north;
      if (!this.magnetic_north) this.magnetic_north = 0;
      this.x = 0;
      this.y = 0;
      this.position = [this.x, this.y];

      this.parse(coordinates);
    },
    parse: function(coordinates) {
      if (! /^[NESW]/.test(coordinates[0])) {
        this.x = coordinates[0];
        this.y = coordinates[1];
        this.position = [this.x, this.y];
        return;
      }

      this.latitude = this.parseCoordinate(coordinates[0]);
      this.longitude = this.parseCoordinate(coordinates[1]);

      if (coordinates[2] != null) {
        this.elevation = parseElevation(coordinates[2]);
      }

      // this function (parse4326) is moved to be able to call it if point is
      // EPSG:4326, numeric decimal, like those from GeoJSON
      if (this.reference_position != null) {
        this.x = this.longitude;
        this.y = this.latitude;
        this.parse4326();
      }
    },
    parse4326: function() {
      // if coordinates were in WGS84 EPSG:4326 (signed decimal lat/lon -12.123,83.456)
      // parse them
      this.longitude = this.x;
      this.latitude = this.y;
      this.x = this.distanceToPoint(this.reference_position.latitude,
                                    this.reference_position.longitude,
                                    this.reference_position.latitude,
                                    this.longitude);
      if (this.reference_position.longitude > this.longitude) {
        this.x *= -1;
      }

      this.y = this.distanceToPoint(this.reference_position.latitude,
                                    this.reference_position.longitude,
                                    this.latitude,
                                    this.reference_position.longitude);
      if (this.reference_position.latitude > this.latitude) {
        this.y *= -1;
      }

      // Adjust to use magnetic north instead of true north
      var t = Math.atan2(this.y, this.x);
      var r = Math.sqrt(this.x*this.x + this.y*this.y);
      t += this.magnetic_north;
      this.x = r * Math.cos(t);
      this.y = r * Math.sin(t);

      this.position = [this.x, this.y];
    },
    distanceTo: function(point) {
      return this.distanceToPoint(this.latitude,
                                  this.longitude,
                                  point.latitude,
                                  point.longitude);
    },
    // The distance in km between two locations
    distanceToPoint: function(lat_a, lng_a, lat_b, lng_b) {
      var d_lat = radians(lat_a - lat_b);
      var d_lng = radians(lng_a - lng_b);

      var a = Math.pow(Math.sin(d_lat/2), 2) +
        (Math.cos(radians(lat_a)) *
         Math.cos(radians(lat_b)) *
         Math.pow(Math.sin(d_lng/2), 2));
      var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

      return c * 6371.00;
    },
    parseCoordinate: function(coord) {
      var r = /^([NESW])(\d+(\.\d+)?)([d °](\d+(\.\d+)?))?([m '](\d+(\.\d+)?))?$/;
      var match = r.exec(coord)
      if (match == null) {
        log('Unable to parse coordinate ' + coord);
        return;
      }
      var ret = parseFloat(match[2]);
      if (match[5] != null) {
        ret += parseFloat(match[5])/60;
        if (match[8] != null) {
          ret += parseFloat(match[8])/3600;
        }
      }

      if (/[SW]/.test(match[1])) {
        ret *= -1;
      }
      return ret;
    },
  };
});

/** An enclosed region defined by a series of Position objects and an altitude range
 ** @param {array} poly - series of Position objects that outline the shape
 **                Note: DO NOT repeat the origin to 'close' the shape. Unnecessary.
 ** @param {number} floor - (optional) altitude of bottom of area, in hundreds of feet
 ** @param {number} ceiling - (optional) altitude of top of area, in hundreds of feet
 ** @param {string} airspace_class - (optional) FAA airspace classification (A,B,C,D,E,G)
 */
var Area = Fiber.extend(function() {
  return {
    init: function(positions, /*optional*/ floor, ceiling, airspace_class) {
      if(!positions) return;
      this.poly     = [];
      this.floor    = null;
      this.ceiling  = null;
      this.airspace_class = null;

      if(floor != null) this.floor = floor;
      if(ceiling != null) this.ceiling = ceiling;
      if(airspace_class) this.airspace_class = airspace_class;

      this.parse(positions);
    },
    parse: function(positions) {
      for(var i=0; i<positions.length; i++) this.poly.push(positions[i]);
      if(this.poly[0] == this.poly[this.poly.length-1])
        this.poly.pop();  // shape shouldn't fully close; will draw with 'cc.closepath()'
    }
  };
});<|endoftext|>


<commit_before>'use strict';

module.exports = {
  env: {
    es6: true,
    node: true,
  },
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module',
  },
  rules: {
    // possible errors
    'no-cond-assign': ['error', 'always'],
    'no-console': 'warn',
    'no-constant-condition': 'error',
    'no-control-regex': 'error',
    'no-debugger': 'warn',
    'no-dupe-args': 'error',
    'no-dupe-keys': 'error',
    'no-duplicate-case': 'error',
    'no-empty-character-class': 'error',
    'no-empty': ['error', {allowEmptyCatch: true}],
    'no-ex-assign': 'error',
    'no-extra-boolean-cast': 'error',
    // issue: https://github.com/eslint/eslint/issues/3107
    'no-extra-parens': ['error', 'functions'],
    'no-extra-semi': 'error',
    'no-func-assign': 'error',
    'no-inner-declarations': 'error',
    'no-invalid-regexp': 'error',
    'no-irregular-whitespace': 'error',
    'no-obj-calls': 'error',
    'no-prototype-builtins': 'error',
    'no-regex-spaces': 'error',
    'no-sparse-arrays': 'error',
    'no-template-curly-in-string': 'error',
    'no-unexpected-multiline': 'error',
    'no-unreachable': 'error',
    'no-unsafe-finally': 'error',
    'no-unsafe-negation': 'error',
    'use-isnan': 'error',
    'valid-jsdoc': 'off',
    'valid-typeof': ['error', {requireStringLiterals: true}],

    // best practices
    'accessor-pairs': 'error',
    'array-callback-return': 'error',
    'block-scoped-var': 'error', // rely on `no-var` rule
    'class-methods-use-this': 'off',
    'complexity': ['warn', 11],
    'consistent-return': 'error',
    'curly': 'error',
    'default-case': 'error',
    'dot-location': ['error', 'property'],
    'dot-notation': 'error',
    'eqeqeq': 'error',
    'guard-for-in': 'error', // rely on `no-restricted-syntax` rule
    'no-alert': 'warn',
    'no-caller': 'error',
    'no-case-declarations': 'error',
    'no-div-regex': 'off',
    'no-else-return': 'error',
    'no-empty-function': 'error',
    'no-empty-pattern': 'error',
    'no-eq-null': 'error', // rely on `eqeqeq` rule
    'no-eval': 'error',
    'no-extend-native': 'error',
    'no-extra-bind': 'error',
    'no-extra-label': 'error',
    'no-fallthrough': 'error',
    'no-floating-decimal': 'error',
    'no-global-assign': 'error',
    'no-implicit-coercion': ['error', {boolean: false}],
    'no-implicit-globals': 'error',
    'no-implied-eval': 'error',
    // TODO: Enable when issue has been fixed.
    // issue: https://github.com/babel/eslint-plugin-babel/issues/12
    // 'no-invalid-this': 'error',
    'no-iterator': 'error',
    'no-labels': 'error',
    'no-lone-blocks': 'error',
    'no-loop-func': 'error',
    'no-magic-numbers': 'off',
    'no-multi-spaces': 'error',
    'no-multi-str': 'error',
    'no-new-func': 'error',
    'no-new-wrappers': 'error',
    'no-new': 'error',
    'no-octal-escape': 'error',
    'no-octal': 'error',
    'no-param-reassign': ['error', {props: true}],
    'no-proto': 'error',
    'no-redeclare': 'error',
    'no-return-assign': ['error', 'always'],
    'no-script-url': 'off',
    'no-self-assign': ['error', {props: true}],
    'no-self-compare': 'error',
    'no-sequences': 'error',
    'no-throw-literal': 'error',
    'no-unmodified-loop-condition': 'error',
    'no-unused-expressions': 'error',
    'no-unused-labels': 'error',
    'no-useless-call': 'error',
    'no-useless-concat': 'error',
    'no-useless-escape': 'error',
    'no-void': 'error',
    'no-warning-comments': 'warn',
    'no-with': 'error',
    'radix': ['error', 'as-needed'],
    'vars-on-top': 'error', // rely on `one-var` rule
    'wrap-iife': ['error', 'inside'],
    'yoda': 'error',

    // strict mode
    'strict': 'error',

    // variables
    'init-declarations': 'error',
    'no-catch-shadow': 'off',
    'no-delete-var': 'error',
    'no-label-var': 'error',
    'no-restricted-globals': ['error', 'isNaN', 'parseInt', 'parseFloat'], // use `Number` property functions instead
    'no-shadow-restricted-names': 'error',
    'no-shadow': 'error',
    'no-undef-init': 'error',
    'no-undef': 'error',
    'no-undefined': 'off',
    'no-unused-vars': 'error',
    'no-use-before-define': ['error', 'nofunc'],

    // node.js and commonjs
    'callback-return': 'off',
    'global-require': 'off',
    'handle-callback-err': 'error',
    'no-mixed-requires': 'error', // rely on `one-var` rule
    'no-new-require': 'error',
    'no-path-concat': 'error',
    'no-process-env': 'off',
    'no-process-exit': 'off',
    'no-restricted-modules': 'off',
    'no-restricted-properties': [
      'error',
      {
        object: 'Object',
        property: 'assign',
        message: 'Use object spread operator `{...obj}` instead.',
      },
    ],
    'no-sync': 'off',

    // stylistic issues
    'array-bracket-spacing': 'error',
    'block-spacing': 'error',
    'brace-style': ['error', '1tbs', {allowSingleLine: true}],
    'camelcase': ['error', {properties: 'never'}],
    'comma-dangle': ['error', 'always-multiline'],
    'comma-spacing': 'error',
    'comma-style': ['error', 'last'],
    'computed-property-spacing': 'error',
    'consistent-this': 'off',
    'eol-last': 'error',
    'func-call-spacing': 'error',
    'func-name-matching': 'error',
    'func-names': ['error', 'never'],
    'func-style': ['error', 'declaration', {allowArrowFunctions: true}],
    'id-blacklist': 'off',
    'id-length': 'off',
    'id-match': 'off',
    'indent': ['error', 2, {SwitchCase: 1}],
    'jsx-quotes': 'off',
    'key-spacing': 'error',
    'line-comment-position': 'off',
    'keyword-spacing': 'error',
    'linebreak-style': 'error',
    'lines-around-comment': 'off',
    'lines-around-directive': ['error', {before: 'never', after: 'always'}],
    'max-depth': 'warn',
    'max-len': 'off',
    'max-lines': 'off',
    'max-nested-callbacks': ['warn', 4],
    'max-params': ['warn', 5],
    'max-statements-per-line': 'warn',
    'max-statements': 'off',
    'multiline-ternary': 'off',
    'new-cap': 'error',
    'new-parens': 'error',
    'newline-after-var': 'error',
    'newline-before-return': 'error',
    'newline-per-chained-call': 'error',
    'no-array-constructor': 'error',
    'no-bitwise': 'off',
    'no-continue': 'error',
    'no-inline-comments': 'off',
    'no-lonely-if': 'error',
    'no-mixed-operators': 'error',
    'no-mixed-spaces-and-tabs': 'error',
    'no-multiple-empty-lines': ['error', {max: 1, maxBOF: 0}],
    'no-negated-condition': 'error',
    // IMO, there is no better alternatives
    // http://stackoverflow.com/questions/32289340/alternative-to-nested-ternary-operator-in-js
    'no-nested-ternary': 'off',
    'no-new-object': 'error',
    'no-plusplus': 'off',
    // use for-of with Object.keys() instead of for-in
    // https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/
    'no-restricted-syntax': ['error', 'ForInStatement', 'LabeledStatement', 'WithStatement'],
    'no-tabs': 'error',
    'no-ternary': 'off',
    'no-trailing-spaces': ['error', {skipBlankLines: true}],
    'no-underscore-dangle': ['error', {allowAfterThis: true, allowAfterSuper: true}],
    'no-unneeded-ternary': ['error', {defaultAssignment: false}],
    'no-whitespace-before-property': 'error',
    // TODO: Enable when issue has been fixed.
    // issue: https://github.com/eslint/eslint/issues/6488
    // 'object-curly-newline': ['error', {multiline: true, minProperties: 4}],
    'object-curly-spacing': 'error',
    'object-property-newline': ['error', {allowMultiplePropertiesPerLine: true}],
    'one-var-declaration-per-line': 'error', // rely on `one-var` rule
    'one-var': ['error', 'never'],
    'operator-assignment': ['error', 'always'],
    'operator-linebreak': 'error',
    'padded-blocks': ['error', 'never'],
    'quote-props': ['error', 'as-needed'],
    'quotes': ['error', 'single'],
    'require-jsdoc': 'off',
    'semi-spacing': 'error',
    'semi': 'error',
    'sort-keys': 'off',
    'sort-vars': 'off',
    'space-before-blocks': 'error',
    'space-before-function-paren': ['error', {anonymous: 'always', named: 'never'}],
    'space-in-parens': 'error',
    'space-infix-ops': 'error',
    'space-unary-ops': 'error',
    'spaced-comment': ['error', 'always', {block: {balanced: true}}],
    'unicode-bom': 'error',
    'wrap-regex': 'off',

    // ecmascript 6 rules
    'arrow-body-style': ['error', 'as-needed'],
    'arrow-parens': 'error',
    'arrow-spacing': 'error',
    'constructor-super': 'error',
    'generator-star-spacing': 'error',
    'no-class-assign': 'error',
    'no-confusing-arrow': 'off',
    'no-const-assign': 'error',
    'no-dupe-class-members': 'error',
    'no-duplicate-imports': ['error', {includeExports: true}],
    'no-new-symbol': 'error',
    'no-restricted-imports': 'off',
    'no-this-before-super': 'error',
    'no-useless-computed-key': 'error',
    'no-useless-constructor': 'error',
    'no-useless-rename': 'error',
    'no-var': 'error',
    'object-shorthand': 'error',
    'prefer-arrow-callback': 'error',
    'prefer-const': ['error', {destructuring: 'all'}],
    'prefer-numeric-literals': 'error',
    'prefer-reflect': 'error',
    'prefer-rest-params': 'error',
    'prefer-spread': 'error',
    'prefer-template': 'error',
    'require-yield': 'error',
    'rest-spread-spacing': 'error',
    'sort-imports': 'off',
    'symbol-description': 'off',
    'template-curly-spacing': 'error',
    'yield-star-spacing': 'error',
  },
};
<commit_msg>Update rules against `eslint@3.8.0`<commit_after>'use strict';

module.exports = {
  env: {
    es6: true,
    node: true,
  },
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module',
  },
  rules: {
    // possible errors
    'no-cond-assign': ['error', 'always'],
    'no-console': 'warn',
    'no-constant-condition': 'error',
    'no-control-regex': 'error',
    'no-debugger': 'warn',
    'no-dupe-args': 'error',
    'no-dupe-keys': 'error',
    'no-duplicate-case': 'error',
    'no-empty-character-class': 'error',
    'no-empty': ['error', {allowEmptyCatch: true}],
    'no-ex-assign': 'error',
    'no-extra-boolean-cast': 'error',
    // issue: https://github.com/eslint/eslint/issues/3107
    'no-extra-parens': ['error', 'functions'],
    'no-extra-semi': 'error',
    'no-func-assign': 'error',
    'no-inner-declarations': 'error',
    'no-invalid-regexp': 'error',
    'no-irregular-whitespace': 'error',
    'no-obj-calls': 'error',
    'no-prototype-builtins': 'error',
    'no-regex-spaces': 'error',
    'no-sparse-arrays': 'error',
    'no-template-curly-in-string': 'error',
    'no-unexpected-multiline': 'error',
    'no-unreachable': 'error',
    'no-unsafe-finally': 'error',
    'no-unsafe-negation': 'error',
    'use-isnan': 'error',
    'valid-jsdoc': 'off',
    'valid-typeof': ['error', {requireStringLiterals: true}],

    // best practices
    'accessor-pairs': 'error',
    'array-callback-return': 'error',
    'block-scoped-var': 'error', // rely on `no-var` rule
    'class-methods-use-this': 'off',
    'complexity': ['warn', 11],
    'consistent-return': 'error',
    'curly': 'error',
    'default-case': 'error',
    'dot-location': ['error', 'property'],
    'dot-notation': 'error',
    'eqeqeq': 'error',
    'guard-for-in': 'error', // rely on `no-restricted-syntax` rule
    'no-alert': 'warn',
    'no-caller': 'error',
    'no-case-declarations': 'error',
    'no-div-regex': 'off',
    'no-else-return': 'error',
    'no-empty-function': 'error',
    'no-empty-pattern': 'error',
    'no-eq-null': 'error', // rely on `eqeqeq` rule
    'no-eval': 'error',
    'no-extend-native': 'error',
    'no-extra-bind': 'error',
    'no-extra-label': 'error',
    'no-fallthrough': 'error',
    'no-floating-decimal': 'error',
    'no-global-assign': 'error',
    'no-implicit-coercion': ['error', {boolean: false}],
    'no-implicit-globals': 'error',
    'no-implied-eval': 'error',
    // TODO: Enable when issue has been fixed.
    // issue: https://github.com/babel/eslint-plugin-babel/issues/12
    // 'no-invalid-this': 'error',
    'no-iterator': 'error',
    'no-labels': 'error',
    'no-lone-blocks': 'error',
    'no-loop-func': 'error',
    'no-magic-numbers': 'off',
    'no-multi-spaces': 'error',
    'no-multi-str': 'error',
    'no-new-func': 'error',
    'no-new-wrappers': 'error',
    'no-new': 'error',
    'no-octal-escape': 'error',
    'no-octal': 'error',
    'no-param-reassign': ['error', {props: true}],
    'no-proto': 'error',
    'no-redeclare': 'error',
    'no-return-assign': ['error', 'always'],
    'no-script-url': 'off',
    'no-self-assign': ['error', {props: true}],
    'no-self-compare': 'error',
    'no-sequences': 'error',
    'no-throw-literal': 'error',
    'no-unmodified-loop-condition': 'error',
    'no-unused-expressions': 'error',
    'no-unused-labels': 'error',
    'no-useless-call': 'error',
    'no-useless-concat': 'error',
    'no-useless-escape': 'error',
    'no-void': 'error',
    'no-warning-comments': 'warn',
    'no-with': 'error',
    'radix': ['error', 'as-needed'],
    'vars-on-top': 'error', // rely on `one-var` rule
    'wrap-iife': ['error', 'inside', {functionPrototypeMethods: true}],
    'yoda': 'error',

    // strict mode
    'strict': 'error',

    // variables
    'init-declarations': 'error',
    'no-catch-shadow': 'off',
    'no-delete-var': 'error',
    'no-label-var': 'error',
    'no-restricted-globals': ['error', 'isNaN', 'parseInt', 'parseFloat'], // use `Number` property functions instead
    'no-shadow-restricted-names': 'error',
    'no-shadow': 'error',
    'no-undef-init': 'error',
    'no-undef': 'error',
    'no-undefined': 'off',
    'no-unused-vars': 'error',
    'no-use-before-define': ['error', 'nofunc'],

    // node.js and commonjs
    'callback-return': 'off',
    'global-require': 'off',
    'handle-callback-err': 'error',
    'no-mixed-requires': 'error', // rely on `one-var` rule
    'no-new-require': 'error',
    'no-path-concat': 'error',
    'no-process-env': 'off',
    'no-process-exit': 'off',
    'no-restricted-modules': 'off',
    'no-restricted-properties': [
      'error',
      {
        object: 'Object',
        property: 'assign',
        message: 'Use object spread operator `{...obj}` instead.',
      },
    ],
    'no-sync': 'off',

    // stylistic issues
    'array-bracket-spacing': 'error',
    'block-spacing': 'error',
    'brace-style': ['error', '1tbs', {allowSingleLine: true}],
    'camelcase': ['error', {properties: 'never'}],
    'comma-dangle': [
      'error',
      {
        arrays: 'always-multiline',
        objects: 'always-multiline',
        imports: 'always-multiline',
        exports: 'always-multiline',
        functions: 'always-multiline',
      },
    ],
    'comma-spacing': 'error',
    'comma-style': ['error', 'last'],
    'computed-property-spacing': 'error',
    'consistent-this': 'off',
    'eol-last': 'error',
    'func-call-spacing': 'error',
    'func-name-matching': 'error',
    'func-names': ['error', 'never'],
    'func-style': ['error', 'declaration', {allowArrowFunctions: true}],
    'id-blacklist': 'off',
    'id-length': 'off',
    'id-match': 'off',
    'indent': ['error', 2, {SwitchCase: 1}],
    'jsx-quotes': 'off',
    'key-spacing': 'error',
    'line-comment-position': 'off',
    'keyword-spacing': 'error',
    'linebreak-style': 'error',
    'lines-around-comment': 'off',
    'lines-around-directive': ['error', {before: 'never', after: 'always'}],
    'max-depth': 'warn',
    'max-len': 'off',
    'max-lines': 'off',
    'max-nested-callbacks': ['warn', 4],
    'max-params': ['warn', 5],
    'max-statements-per-line': 'warn',
    'max-statements': 'off',
    'multiline-ternary': 'off',
    'new-cap': 'error',
    'new-parens': 'error',
    'newline-after-var': 'error',
    'newline-before-return': 'error',
    'newline-per-chained-call': 'error',
    'no-array-constructor': 'error',
    'no-bitwise': 'off',
    'no-continue': 'error',
    'no-inline-comments': 'off',
    'no-lonely-if': 'error',
    'no-mixed-operators': 'error',
    'no-mixed-spaces-and-tabs': 'error',
    'no-multiple-empty-lines': ['error', {max: 1, maxBOF: 0}],
    'no-negated-condition': 'error',
    // IMO, there is no better alternatives
    // http://stackoverflow.com/questions/32289340/alternative-to-nested-ternary-operator-in-js
    'no-nested-ternary': 'off',
    'no-new-object': 'error',
    'no-plusplus': 'off',
    // use for-of with Object.keys() instead of for-in
    // https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/
    'no-restricted-syntax': ['error', 'ForInStatement', 'LabeledStatement', 'WithStatement'],
    'no-tabs': 'error',
    'no-ternary': 'off',
    'no-trailing-spaces': ['error', {skipBlankLines: true}],
    'no-underscore-dangle': ['error', {allowAfterThis: true, allowAfterSuper: true}],
    'no-unneeded-ternary': ['error', {defaultAssignment: false}],
    'no-whitespace-before-property': 'error',
    // TODO: Enable when issue has been fixed.
    // issue: https://github.com/eslint/eslint/issues/6488
    // 'object-curly-newline': ['error', {multiline: true, minProperties: 4}],
    'object-curly-spacing': 'error',
    'object-property-newline': ['error', {allowMultiplePropertiesPerLine: true}],
    'one-var-declaration-per-line': 'error', // rely on `one-var` rule
    'one-var': ['error', 'never'],
    'operator-assignment': ['error', 'always'],
    'operator-linebreak': 'error',
    'padded-blocks': ['error', 'never'],
    'quote-props': ['error', 'as-needed'],
    'quotes': ['error', 'single'],
    'require-jsdoc': 'off',
    'semi-spacing': 'error',
    'semi': 'error',
    'sort-keys': 'off',
    'sort-vars': 'off',
    'space-before-blocks': 'error',
    'space-before-function-paren': ['error', {anonymous: 'always', named: 'never', asyncArrow: 'always'}],
    'space-in-parens': 'error',
    'space-infix-ops': 'error',
    'space-unary-ops': 'error',
    'spaced-comment': ['error', 'always', {block: {balanced: true}}],
    'unicode-bom': 'error',
    'wrap-regex': 'off',

    // ecmascript 6 rules
    'arrow-body-style': ['error', 'as-needed'],
    'arrow-parens': 'error',
    'arrow-spacing': 'error',
    'constructor-super': 'error',
    'generator-star-spacing': 'error',
    'no-class-assign': 'error',
    'no-confusing-arrow': 'off',
    'no-const-assign': 'error',
    'no-dupe-class-members': 'error',
    'no-duplicate-imports': ['error', {includeExports: true}],
    'no-new-symbol': 'error',
    'no-restricted-imports': 'off',
    'no-this-before-super': 'error',
    'no-useless-computed-key': 'error',
    'no-useless-constructor': 'error',
    'no-useless-rename': 'error',
    'no-var': 'error',
    'object-shorthand': 'error',
    'prefer-arrow-callback': 'error',
    'prefer-const': ['error', {destructuring: 'all'}],
    'prefer-numeric-literals': 'error',
    'prefer-reflect': 'error',
    'prefer-rest-params': 'error',
    'prefer-spread': 'error',
    'prefer-template': 'error',
    'require-yield': 'error',
    'rest-spread-spacing': 'error',
    'sort-imports': 'off',
    'symbol-description': 'off',
    'template-curly-spacing': 'error',
    'yield-star-spacing': 'error',
  },
};
<|endoftext|>


<commit_before>/* eslint-env node */

module.exports = {
  plugins: ["flowtype", "flowtype-errors"],

  extends: ["prettier/flowtype"],

  settings: {
    flowtype: {
      onlyFilesWithFlowAnnotation: true,
    },
  },

  /*
   * Disabled by prettier
   * https://github.com/prettier/eslint-config-prettier/blob/master/flowtype.js
   *
   * "flowtype/boolean-style": "off",
   * "flowtype/delimiter-dangle": "off",
   * "flowtype/generic-spacing": "off",
   * "flowtype/object-type-delimiter": "off",
   * "flowtype/semi": "off",
   * "flowtype/space-after-type-colon": "off",
   * "flowtype/space-before-generic-bracket": "off",
   * "flowtype/space-before-type-colon": "off",
   * "flowtype/union-intersection-spacing": "off"
   */

  rules: {
    // does not play well with
    "react/default-props-match-prop-types": "off",
    "react/require-default-props": "off",

    // Show flow errors
    "flowtype-errors/show-errors": "error",

    /*
     * Requires all type declarations to be at the top of the file, after any
     * import declarations.
     */
    "flowtype/require-types-at-top": "off",

    /*
     * An extension of ESLint's no-unused-expressions. This rule ignores type
     * cast expressions, but otherwise behaves the same as ESLint's
     * no-unused-expressions.
     */
    "flowtype/no-unused-expressions": "off",

    // Type[] instead of Array<Type>
    "flowtype/array-style-complex-type": ["error", "shorthand"],

    // string[] instead of Array<string>
    "flowtype/array-style-simple-type": ["error", "shorthand"],

    // Error if flow coverage is below 80%
    "flowtype-errors/enforce-min-coverage": ["error", 60],

    /*
     * Enforces a particular style for type imports: 'declaration' style
     * import type {T, U, V} from '...';
     */
    "flowtype/type-import-style": "off",

    /*
     * Enforces that `@flow` annotations be followed by an empty line,
     * separated by newline
     */
    "flowtype/newline-after-flow-annotation": ["error", "always"],

    /*
     * Marks Flow type identifiers as defined.
     * Used to suppress no-undef reporting of type identifiers.
     */
    "flowtype/define-flow-type": 1,

    // Report all object type definitions that aren't exact.
    "flowtype/require-exact-type": "off",

    /*
     * Checks for duplicate properties in Object annotations.
     * Same as eslint "no-dupe-keys".
     */
    "flowtype/no-dupe-keys": "error",

    /*
     * Disallows use of primitive constructors as types, such as Boolean,
     * Number and String.
     */
    "flowtype/no-primitive-constructor-types": "error",

    /*
     * Disallows Flow type imports, aliases, and annotations in files
     * missing a valid Flow file declaration (or a @noflow annotation).
     */
    "flowtype/no-types-missing-file-annotation": "error",

    /*
     * Warns against weak type annotations any, Object and Function. These
     * types can cause flow to silently skip over portions of your code,
     * which would have otherwise caused type errors.
     */
    "flowtype/no-weak-types": [
      "error",
      {
        Function: false,
        Object: false,
        any: true,
      },
    ],

    "flowtype/no-existential-type": "error",

    // Requires that all function parameters have type annotations.
    "flowtype/require-parameter-type": "off",

    // Requires that functions have return type annotation.
    "flowtype/require-return-type": "off",

    /*
     * This rule can optionally report missing or missed placed
     * annotations, common typos (e.g. // @floww), and enforce a
     * consistant annotation style.
     */
    "flowtype/require-valid-file-annotation": [
      "error",
      "always",
      {
        annotationStyle: "line",
      },
    ],

    // Requires that all variable declarators have type annotations.
    "flowtype/require-variable-type": "off",

    // Enforces sorting of Object annotations. Same as eslint "sort-keys"
    "flowtype/sort-keys": "off",

    // Enforces a consistent naming pattern for type aliases.
    "flowtype/type-id-match": ["error", "^([A-Z]{1,}[a-z0-9]{0,})+$"],

    /*
     * Marks Flow type alias declarations as used.
     * Used to suppress no-unused-vars errors that are triggered by type
     * aliases.
     */
    "flowtype/use-flow-type": 1,

    //
    "flowtype/valid-syntax": 1,
  },
}
<commit_msg>Disable "react/display-name", "react-prop-types" when using flow<commit_after>/* eslint-env node */

module.exports = {
  plugins: ["flowtype", "flowtype-errors"],

  extends: ["prettier/flowtype"],

  settings: {
    flowtype: {
      onlyFilesWithFlowAnnotation: true,
    },
  },

  /*
   * Disabled by prettier
   * https://github.com/prettier/eslint-config-prettier/blob/master/flowtype.js
   *
   * "flowtype/boolean-style": "off",
   * "flowtype/delimiter-dangle": "off",
   * "flowtype/generic-spacing": "off",
   * "flowtype/object-type-delimiter": "off",
   * "flowtype/semi": "off",
   * "flowtype/space-after-type-colon": "off",
   * "flowtype/space-before-generic-bracket": "off",
   * "flowtype/space-before-type-colon": "off",
   * "flowtype/union-intersection-spacing": "off"
   */

  rules: {
    // does not play well with
    "react/default-props-match-prop-types": "off",
    "react/require-default-props": "off",
    "react/display-name": "off",
    "react/prop-types": "off",

    // Show flow errors
    "flowtype-errors/show-errors": "error",

    /*
     * Requires all type declarations to be at the top of the file, after any
     * import declarations.
     */
    "flowtype/require-types-at-top": "off",

    /*
     * An extension of ESLint's no-unused-expressions. This rule ignores type
     * cast expressions, but otherwise behaves the same as ESLint's
     * no-unused-expressions.
     */
    "flowtype/no-unused-expressions": "off",

    // Type[] instead of Array<Type>
    "flowtype/array-style-complex-type": ["error", "shorthand"],

    // string[] instead of Array<string>
    "flowtype/array-style-simple-type": ["error", "shorthand"],

    // Error if flow coverage is below 80%
    "flowtype-errors/enforce-min-coverage": ["error", 60],

    /*
     * Enforces a particular style for type imports: 'declaration' style
     * import type {T, U, V} from '...';
     */
    "flowtype/type-import-style": "off",

    /*
     * Enforces that `@flow` annotations be followed by an empty line,
     * separated by newline
     */
    "flowtype/newline-after-flow-annotation": ["error", "always"],

    /*
     * Marks Flow type identifiers as defined.
     * Used to suppress no-undef reporting of type identifiers.
     */
    "flowtype/define-flow-type": 1,

    // Report all object type definitions that aren't exact.
    "flowtype/require-exact-type": "off",

    /*
     * Checks for duplicate properties in Object annotations.
     * Same as eslint "no-dupe-keys".
     */
    "flowtype/no-dupe-keys": "error",

    /*
     * Disallows use of primitive constructors as types, such as Boolean,
     * Number and String.
     */
    "flowtype/no-primitive-constructor-types": "error",

    /*
     * Disallows Flow type imports, aliases, and annotations in files
     * missing a valid Flow file declaration (or a @noflow annotation).
     */
    "flowtype/no-types-missing-file-annotation": "error",

    /*
     * Warns against weak type annotations any, Object and Function. These
     * types can cause flow to silently skip over portions of your code,
     * which would have otherwise caused type errors.
     */
    "flowtype/no-weak-types": [
      "error",
      {
        Function: false,
        Object: false,
        any: true,
      },
    ],

    "flowtype/no-existential-type": "error",

    // Requires that all function parameters have type annotations.
    "flowtype/require-parameter-type": "off",

    // Requires that functions have return type annotation.
    "flowtype/require-return-type": "off",

    /*
     * This rule can optionally report missing or missed placed
     * annotations, common typos (e.g. // @floww), and enforce a
     * consistant annotation style.
     */
    "flowtype/require-valid-file-annotation": [
      "error",
      "always",
      {
        annotationStyle: "line",
      },
    ],

    // Requires that all variable declarators have type annotations.
    "flowtype/require-variable-type": "off",

    // Enforces sorting of Object annotations. Same as eslint "sort-keys"
    "flowtype/sort-keys": "off",

    // Enforces a consistent naming pattern for type aliases.
    "flowtype/type-id-match": ["error", "^([A-Z]{1,}[a-z0-9]{0,})+$"],

    /*
     * Marks Flow type alias declarations as used.
     * Used to suppress no-unused-vars errors that are triggered by type
     * aliases.
     */
    "flowtype/use-flow-type": 1,

    //
    "flowtype/valid-syntax": 1,
  },
}
<|endoftext|>


<commit_before><commit_msg>Adds script to determine users to notify<commit_after><|endoftext|>


<commit_before>import React from 'react';
import {
    StyleSheet,
    View,
    Slider,
    Text,
    TouchableOpacity,
    DeviceEventEmitter,
    ActivityIndicator,
    AsyncStorage,
    BackHandler,
    Alert,
    NativeModules
} from 'react-native';
import RNAudioStreamer from 'react-native-audio-streamer';
import MusicControl from 'react-native-music-control';
import Icon from 'react-native-vector-icons/dist/MaterialCommunityIcons';
import RNExitApp from 'react-native-exit-app';
const { AudioFocusManager }  = NativeModules;

const statusMap = {
    PLAYING: MusicControl.STATE_PLAYING,
    PAUSED: MusicControl.STATE_PAUSED,
    BUFFERING: MusicControl.STATE_BUFFERING
};

const defaultState = {
    status: 'STOPPED',
    nowPlaying: '',
    duration: 0,
    currTime: 0
};

const PINK = '#E91E63';

export default class Player extends React.Component {
    state = defaultState;

    constructor(props) {
        super(props);
        console.log('in ctor!!!');
    }

    componentDidMount() {
        console.log('in componentDidMount');
        DeviceEventEmitter.addListener('RNAudioStreamerStatusChanged', this._audioStatus.bind(this));
        DeviceEventEmitter.addListener('WiredHeadset', this._headphonePlugged.bind(this));
        DeviceEventEmitter.addListener('onAudioFocusChange', this._audioFocusChanged.bind(this));
        BackHandler.addEventListener('hardwareBackPress', this._backPressed.bind(this));
        MusicControl.enableControl('play', true);
        MusicControl.enableControl('pause', true);
        MusicControl.enableControl('skipForward', true, {interval: 5});
        MusicControl.enableControl('skipBackward', true, {interval: 5});
        MusicControl.enableBackgroundMode(true);
        MusicControl.enableControl('closeNotification', true, {when: 'always'});
        MusicControl.on('play', ()=> {
            RNAudioStreamer.play();
        });
        MusicControl.on('pause', ()=> {
            RNAudioStreamer.pause();
        });
        MusicControl.on('closeNotification', async ()=> {
            await this._saveStateAndExit();
        });
        MusicControl.on('skipForward', ()=> {
            let seek = Math.min(this.state.currTime + 5, this.state.duration);
            RNAudioStreamer.seekToTime(seek);
            this.setState({currTime: seek});
        });
        MusicControl.on('skipBackward', ()=> {
            let seek = Math.max(this.state.currTime - 5, 0);
            RNAudioStreamer.seekToTime(seek);
            this.setState({currTime: seek});
        });
        this._checkSavedStateAndLoad();
    }

    componentWillUnmount() {
        console.log('in componentWillUnmount');
        if (this.timeInterval) {
            clearInterval(this.timeInterval);
        }
    }

    async _saveStateAndExit() {
        if (this.segment) {
            await this._getCurrentTime();
            RNAudioStreamer.setUrl('');
            await AsyncStorage.setItem('saved', JSON.stringify(this.segment));
            MusicControl.resetNowPlaying();
        }
        RNExitApp.exitApp();
    }

    _backPressed() {
        if (this.state.status === 'PLAYING') {
            Alert.alert(
                'בטוח לצאת?',
                'יציאה תפסיק את הנגינה',
                [
                    {text: 'בטל', onPress: () => {}},
                    {text: 'צא', onPress: () => {
                        this._saveStateAndExit();
                    }}
                ]
            );
        } else {
            this._saveStateAndExit();
        }

        return true;
    }

    async _checkSavedStateAndLoad() {
        let savedSegemnt = await AsyncStorage.getItem('saved');
        if (savedSegemnt) {
            savedSegemnt = JSON.parse(savedSegemnt);
            console.log('found saved state!!! ', savedSegemnt);
            // this.playSegment(savedSegemnt);
            // RNAudioStreamer.pause();
            savedSegemnt.currTime = Math.max(0, savedSegemnt.currTime - 5);
            // RNAudioStreamer.seekToTime(currTime);
            this.savedSagment = savedSegemnt;
            this.setState({
                status: 'STANDBY',
                nowPlaying: savedSegemnt.title,
                currTime: savedSegemnt.currTime,
                duration: savedSegemnt.duration
            });
        }
    }

    _headphonePlugged(data) {
        if (this.state.status === 'PLAYING' && !data.isPlugged) {
            RNAudioStreamer.pause();
        }
    }

    _audioFocusChanged(data) {
        console.log('_audioFocusChanged: ', data);
        if (this.state.status === 'PLAYING' && !data.inFocus) {
            RNAudioStreamer.pause();
            this.pausedFromFocusLoss = true;
        } else if (this.state.status === 'PAUSED' && data.inFocus && this.pausedFromFocusLoss) {
            RNAudioStreamer.play();
            this.pausedFromFocusLoss = false;
        }
    }

    _audioStatus(status) {
        console.log(status);
        this.setState({status});
        if (status === 'PLAYING') {
            RNAudioStreamer.duration((err, duration) => {
                console.log(duration);
                this.setState({duration});
                this.segment.duration = duration;
            })
        } else if (status === 'FINISHED') {
            clearInterval(this.timeInterval);
            this.timeInterval = null;
            this.segment = null;
            AsyncStorage.removeItem('saved');
            MusicControl.resetNowPlaying();
            this.setState(defaultState);
        }
        MusicControl.updatePlayback({
            state: statusMap[status]
        });
    }

    playSegment(segment, play = true) {
        if (this.timeInterval) {
            clearInterval(this.timeInterval);
        }
        this.segment = segment;
        RNAudioStreamer.setUrl(segment.url);
        if (play) {
            this.setState({
                nowPlaying: segment.title,
                currTime: 0
            });
            RNAudioStreamer.play();
        }
        MusicControl.setNowPlaying({
            title: segment.title,
            artwork: segment.image,
            color: 0xE91E63
        });
        AudioFocusManager.startListening();
        this.timeInterval = setInterval(this._getCurrentTime.bind(this), 1000);
    }

    async _getCurrentTime() {
        return new Promise(resolve => {
            RNAudioStreamer.currentTime((err, currentTime) => {
                if (err) {
                    console.log(err);
                } else {
                    this.segment.currTime = currentTime;
                    if (!this.seeking) {
                        this.setState({currTime: currentTime});
                    }
                }
                resolve();
            })
        })
    }

    _playPausePressed() {
        if (this.state.status === 'PLAYING') {
            RNAudioStreamer.pause();
        } else if (this.state.status === 'PAUSED') {
            RNAudioStreamer.play();
        } else if (this.state.status === 'STANDBY') {
            this.playSegment(this.savedSagment, false);
            RNAudioStreamer.seekToTime(this.savedSagment.currTime);
            RNAudioStreamer.play();
            this.savedSagment = null;
        }
    }

    _seekingComplete(value) {
        this.seeking = false;
        this.segment && RNAudioStreamer.seekToTime(value);
    }

    _seek(value) {
        value = Math.floor(value);
        this.seeking = true;
        this.setState({currTime: value});
        this.savedSagment && (this.savedSagment.currTime = value);
    }

    render() {
        let icon = null;
        if (this.state.status === 'PLAYING') {
            icon = <Icon name="pause" size={30} color={PINK}/>;
        } else if (this.state.status === 'PAUSED' || this.state.status === "STANDBY") {
            icon = <Icon name="play" size={30} color={PINK}/>;
        } else if (this.state.status === 'BUFFERING') {
            icon = <ActivityIndicator color={PINK}/>
        }

        return (
            <View style={styles.container}>
                <Text style={styles.title} lineNumber={1}>{this.state.nowPlaying}</Text>
                <View style={styles.playBox}>
                    <TouchableOpacity style={styles.playButton} onPress={this._playPausePressed.bind(this)}>
                        {icon}
                    </TouchableOpacity>
                    <View style={styles.slider}>
                        <Text>{secondsToTime(this.state.currTime)}</Text>
                        <Slider
                            style={{flex: 1}}
                            value={this.state.currTime}
                            maximumValue={this.state.duration}
                            onSlidingComplete={this._seekingComplete.bind(this)}
                            onValueChange={this._seek.bind(this)}
                            minimumTrackTintColor={PINK}
                            maximumTrackTintColor={PINK}
                            thumbTintColor={PINK}
                        />
                        <Text>{secondsToTime(this.state.duration)}</Text>
                    </View>
                </View>
            </View>
        )
    }
}

const styles = StyleSheet.create({
    container: {
        // position: 'absolute',
        height: 100,
        // bottom: 0,
        // right: 0,
        // left: 0,
        elevation: 10,
        backgroundColor: 'white',
        alignItems: 'center',
        justifyContent: 'center',
        paddingHorizontal: 10,
        paddingVertical: 10,
    },
    title: {
        flex: 1
    },
    playBox: {
        height: 50,
        backgroundColor: 'white',
        flexDirection: 'row',
        alignItems: 'center'
    },
    playButton: {
        height: 50,
        width: 50,
        justifyContent: 'center',
        alignItems: 'center'
    },
    slider: {
        flex: 1,
        marginVertical: 10,
        flexDirection: 'row',
        alignItems: 'center'
    }
});

function secondsToTime(seconds) {
    let minutes = Math.floor(seconds / 60);
    let minutesStr = ('0' + minutes).slice(-2);
    seconds = seconds % 60;
    let secondsStr = ('0' + seconds).slice(-2);

    if (minutes / 60 > 1) {
        let hours = Math.floor(minutes / 60);
        minutes = minutes % 60;
        minutesStr = ('0' + minutes).slice(-2);
        return `${hours}:${minutesStr}:${secondsStr}`;
    }

    return `${minutesStr}:${secondsStr}`;
}<commit_msg>use next and prev track so you can do it in the car<commit_after>import React from 'react';
import {
    StyleSheet,
    View,
    Slider,
    Text,
    TouchableOpacity,
    DeviceEventEmitter,
    ActivityIndicator,
    AsyncStorage,
    BackHandler,
    Alert,
    NativeModules
} from 'react-native';
import RNAudioStreamer from 'react-native-audio-streamer';
import MusicControl from 'react-native-music-control';
import Icon from 'react-native-vector-icons/dist/MaterialCommunityIcons';
import RNExitApp from 'react-native-exit-app';
const { AudioFocusManager }  = NativeModules;

const statusMap = {
    PLAYING: MusicControl.STATE_PLAYING,
    PAUSED: MusicControl.STATE_PAUSED,
    BUFFERING: MusicControl.STATE_BUFFERING
};

const defaultState = {
    status: 'STOPPED',
    nowPlaying: '',
    duration: 0,
    currTime: 0
};

const PINK = '#E91E63';

export default class Player extends React.Component {
    state = defaultState;

    constructor(props) {
        super(props);
        console.log('in ctor!!!');
    }

    componentDidMount() {
        console.log('in componentDidMount');
        DeviceEventEmitter.addListener('RNAudioStreamerStatusChanged', this._audioStatus.bind(this));
        DeviceEventEmitter.addListener('WiredHeadset', this._headphonePlugged.bind(this));
        DeviceEventEmitter.addListener('onAudioFocusChange', this._audioFocusChanged.bind(this));
        BackHandler.addEventListener('hardwareBackPress', this._backPressed.bind(this));
        MusicControl.enableControl('play', true);
        MusicControl.enableControl('pause', true);
        MusicControl.enableControl('nextTrack', true);
        MusicControl.enableControl('previousTrack', true);
        MusicControl.enableBackgroundMode(true);
        MusicControl.enableControl('closeNotification', true, {when: 'always'});
        MusicControl.on('play', ()=> {
            RNAudioStreamer.play();
        });
        MusicControl.on('pause', ()=> {
            RNAudioStreamer.pause();
        });
        MusicControl.on('closeNotification', async ()=> {
            await this._saveStateAndExit();
        });
        MusicControl.on('nextTrack', ()=> {
            let seek = Math.min(this.state.currTime + 5, this.state.duration);
            RNAudioStreamer.seekToTime(seek);
            this.setState({currTime: seek});
        });
        MusicControl.on('previousTrack', ()=> {
            let seek = Math.max(this.state.currTime - 5, 0);
            RNAudioStreamer.seekToTime(seek);
            this.setState({currTime: seek});
        });
        this._checkSavedStateAndLoad();
    }

    componentWillUnmount() {
        console.log('in componentWillUnmount');
        if (this.timeInterval) {
            clearInterval(this.timeInterval);
        }
    }

    async _saveStateAndExit() {
        if (this.segment) {
            await this._getCurrentTime();
            RNAudioStreamer.setUrl('');
            await AsyncStorage.setItem('saved', JSON.stringify(this.segment));
            MusicControl.resetNowPlaying();
        }
        RNExitApp.exitApp();
    }

    _backPressed() {
        if (this.state.status === 'PLAYING') {
            Alert.alert(
                'בטוח לצאת?',
                'יציאה תפסיק את הנגינה',
                [
                    {text: 'בטל', onPress: () => {}},
                    {text: 'צא', onPress: () => {
                        this._saveStateAndExit();
                    }}
                ]
            );
        } else {
            this._saveStateAndExit();
        }

        return true;
    }

    async _checkSavedStateAndLoad() {
        let savedSegemnt = await AsyncStorage.getItem('saved');
        if (savedSegemnt) {
            savedSegemnt = JSON.parse(savedSegemnt);
            console.log('found saved state!!! ', savedSegemnt);
            // this.playSegment(savedSegemnt);
            // RNAudioStreamer.pause();
            savedSegemnt.currTime = Math.max(0, savedSegemnt.currTime - 5);
            // RNAudioStreamer.seekToTime(currTime);
            this.savedSagment = savedSegemnt;
            this.setState({
                status: 'STANDBY',
                nowPlaying: savedSegemnt.title,
                currTime: savedSegemnt.currTime,
                duration: savedSegemnt.duration
            });
        }
    }

    _headphonePlugged(data) {
        if (this.state.status === 'PLAYING' && !data.isPlugged) {
            RNAudioStreamer.pause();
        }
    }

    _audioFocusChanged(data) {
        console.log('_audioFocusChanged: ', data);
        if (this.state.status === 'PLAYING' && !data.inFocus) {
            RNAudioStreamer.pause();
            this.pausedFromFocusLoss = true;
        } else if (this.state.status === 'PAUSED' && data.inFocus && this.pausedFromFocusLoss) {
            RNAudioStreamer.play();
            this.pausedFromFocusLoss = false;
        }
    }

    _audioStatus(status) {
        console.log(status);
        this.setState({status});
        if (status === 'PLAYING') {
            RNAudioStreamer.duration((err, duration) => {
                console.log(duration);
                this.setState({duration});
                this.segment.duration = duration;
            })
        } else if (status === 'FINISHED') {
            clearInterval(this.timeInterval);
            this.timeInterval = null;
            this.segment = null;
            AsyncStorage.removeItem('saved');
            MusicControl.resetNowPlaying();
            this.setState(defaultState);
        }
        MusicControl.updatePlayback({
            state: statusMap[status]
        });
    }

    playSegment(segment, play = true) {
        if (this.timeInterval) {
            clearInterval(this.timeInterval);
        }
        this.segment = segment;
        RNAudioStreamer.setUrl(segment.url);
        if (play) {
            this.setState({
                nowPlaying: segment.title,
                currTime: 0
            });
            RNAudioStreamer.play();
        }
        MusicControl.setNowPlaying({
            title: segment.title,
            artwork: segment.image,
            color: 0xE91E63
        });
        AudioFocusManager.startListening();
        this.timeInterval = setInterval(this._getCurrentTime.bind(this), 1000);
    }

    async _getCurrentTime() {
        return new Promise(resolve => {
            RNAudioStreamer.currentTime((err, currentTime) => {
                if (err) {
                    console.log(err);
                } else {
                    this.segment.currTime = currentTime;
                    if (!this.seeking) {
                        this.setState({currTime: currentTime});
                    }
                }
                resolve();
            })
        })
    }

    _playPausePressed() {
        if (this.state.status === 'PLAYING') {
            RNAudioStreamer.pause();
        } else if (this.state.status === 'PAUSED') {
            RNAudioStreamer.play();
        } else if (this.state.status === 'STANDBY') {
            this.playSegment(this.savedSagment, false);
            RNAudioStreamer.seekToTime(this.savedSagment.currTime);
            RNAudioStreamer.play();
            this.savedSagment = null;
        }
    }

    _seekingComplete(value) {
        this.seeking = false;
        this.segment && RNAudioStreamer.seekToTime(value);
    }

    _seek(value) {
        value = Math.floor(value);
        this.seeking = true;
        this.setState({currTime: value});
        this.savedSagment && (this.savedSagment.currTime = value);
    }

    render() {
        let icon = null;
        if (this.state.status === 'PLAYING') {
            icon = <Icon name="pause" size={30} color={PINK}/>;
        } else if (this.state.status === 'PAUSED' || this.state.status === "STANDBY") {
            icon = <Icon name="play" size={30} color={PINK}/>;
        } else if (this.state.status === 'BUFFERING') {
            icon = <ActivityIndicator color={PINK}/>
        }

        return (
            <View style={styles.container}>
                <Text style={styles.title} lineNumber={1}>{this.state.nowPlaying}</Text>
                <View style={styles.playBox}>
                    <TouchableOpacity style={styles.playButton} onPress={this._playPausePressed.bind(this)}>
                        {icon}
                    </TouchableOpacity>
                    <View style={styles.slider}>
                        <Text>{secondsToTime(this.state.currTime)}</Text>
                        <Slider
                            style={{flex: 1}}
                            value={this.state.currTime}
                            maximumValue={this.state.duration}
                            onSlidingComplete={this._seekingComplete.bind(this)}
                            onValueChange={this._seek.bind(this)}
                            minimumTrackTintColor={PINK}
                            maximumTrackTintColor={PINK}
                            thumbTintColor={PINK}
                        />
                        <Text>{secondsToTime(this.state.duration)}</Text>
                    </View>
                </View>
            </View>
        )
    }
}

const styles = StyleSheet.create({
    container: {
        // position: 'absolute',
        height: 100,
        // bottom: 0,
        // right: 0,
        // left: 0,
        elevation: 10,
        backgroundColor: 'white',
        alignItems: 'center',
        justifyContent: 'center',
        paddingHorizontal: 10,
        paddingVertical: 10,
    },
    title: {
        flex: 1
    },
    playBox: {
        height: 50,
        backgroundColor: 'white',
        flexDirection: 'row',
        alignItems: 'center'
    },
    playButton: {
        height: 50,
        width: 50,
        justifyContent: 'center',
        alignItems: 'center'
    },
    slider: {
        flex: 1,
        marginVertical: 10,
        flexDirection: 'row',
        alignItems: 'center'
    }
});

function secondsToTime(seconds) {
    let minutes = Math.floor(seconds / 60);
    let minutesStr = ('0' + minutes).slice(-2);
    seconds = seconds % 60;
    let secondsStr = ('0' + seconds).slice(-2);

    if (minutes / 60 > 1) {
        let hours = Math.floor(minutes / 60);
        minutes = minutes % 60;
        minutesStr = ('0' + minutes).slice(-2);
        return `${hours}:${minutesStr}:${secondsStr}`;
    }

    return `${minutesStr}:${secondsStr}`;
}<|endoftext|>


<commit_before>

(function(){

  'use strict';

  var Element;

  var isFunction = function(obj) {
    return typeof obj === 'function';
  };

  var isString = function(obj) {
    return typeof obj === 'string';
  };

  var isObject = function(obj) {
    return obj === Object(obj)
  };

  var isArray = Array.isArray || function(obj) {
    return toString.call(obj) == '[object Array]';
  };

  var getAncestor = function(element) {
    var ancestor = [];
    var p = function(e){
      if( e.parent ){
        ancestor.push(e.parent);
        p(e.parent);
      }
    };
    p(element);
    return ancestor;
  }

  var Exception = function(value, message) {
    this.value = value;
    this.message = message || "";
    this.toString = function() {
      return this.value + ":" + this.message;
    };
  };

  var root = global || window, block = {};

  var tintin = function(attr, _) {
    var element, attr, _;

    if (!_){
      var _ref;
      _ref = [_, attr], attr = _ref[0], _ = _ref[1];
    }

    if (isFunction(_)) {
      element = new Element(null, "html", attr);
      _.call(element, element._.bind(element), block);
    } else {
      throw new Exception({attr: attr, _: _}, "Illegal arguments.");
    }

    return element;

  };

  tintin.extend = function(template, _block){
    var element;
    block = _block;
    if( process /*is NodeJS */ ) {
      require("coffee-script/register");

      try {
        element = require(template);
      } catch (e) {

      }
      //console.log(fs.readdirSync(process.cwd()));
      //element = require(template);
    }
    return element;
  };

  tintin.__express = function(options) {

    root.tintin = tintin;

    return function(template, data, next){

      require('coffee-script/register');

      var uglify = data.settings.env === 'production';

      tintin.data = data;

      try {
        next(null, require(template).toHTML(uglify));
      } catch (e) {
        console.error(e);
        next(e);
      }
      return ;
    };
  }

  Element = (function(){
    function Element(parent, name, attr) {
      this.parent = parent instanceof Element ? parent : null;
      this.name = name;
      this.attr = attr;

      this.childrens = [];
    }

    Element.prototype._ = function(name, attr, _) {

      if(!_){
        if(isFunction(attr) || isString(attr)){
          var _ref;
          _ref = [_, attr], attr = _ref[0], _ = _ref[1];
        }
        if(!attr) {
          attr = {};
        }
      }

      var reg = /(\[(.+?)\])/g;
      var match = name.match(reg);
      if( match ) {
        var _ref = null;
        _ref = match.map(function(s){
          return s.substring(1, s.length - 1).match(/(.+?)=(.+)/);
        });
        for ( var o in _ref) {
          attr[ _ref[o][1] ] = _ref[o][2];
        }
        name = name.replace(reg, '');
      }

      var reg = /(#([\w-]+))/;
      var match = name.match(reg);
      if(match){
        attr['id'] = match[2];
      }

      var match = name.match(/(\.([\w-]+))/g);
      if( match ) {
        attr['class'] = match.map(function(s){
          return s.substring(1, s.length);
        });
      }

      var match = name.match(/^([\w]+)/);
      if( match ) {
        name = match[0];
      } else {
        name = "div";
      }

      if( isString(name) ) {
        var element = new Element(this, name, attr);
        this.childrens.push(element);
        if( isFunction(_) ) {
          _.call(element, element._.bind(element));
        } else
        if( isString(_) ) {
          element.content = _;
        } else {

        }
      }
    };

    Element.prototype.toString = Element.prototype.toHTML = function(uglify) {
      var tag = "";
      var attr = "";
      var tab = "  ";
      var br = uglify ? '' : "\n";
      var indent = "";
      var doctype = "<!DOCTYPE html>";

      if( this.name === "html"){
        tag += doctype + br;
      }

      if( !uglify && this.parent ) {
        for (var i=0; i < getAncestor(this).length; i++){
          indent += tab;
        }
        tag += br + indent;
      }
      if( Object.keys(this.attr).length > 0 ) {
        var attrs = [];
        attr += " ";
        for ( var o in this.attr) {
          var _str = isArray(this.attr[o]) ? this.attr[o].join(' ') : this.attr[o];
          attrs.push(o + "=\"" + _str + "\"");
        }
        attr += attrs.join(" ");
      }

      tag += "<" + this.name + attr + ">";

      if( this.childrens.length > 0 ) {
        var i;
        var c;
        for ( var e in c = this.childrens) {
          tag += c[e].toHTML(uglify);
        }

        tag += br + indent + "</" + this.name + ">";
      }

      if( this.content ){
        tag += this.content || "";
        tag += "</" + this.name + ">";
      }

      return tag;
    };

    Element.prototype.extend = function(blocks){
      var element = this;
      if( isObject(blocks) ){
        console.log(block = blocks);
      } else {
        throw new Exception(block, "Illegal arguments. blocks only Object.");
      }
      return element;
    };

    return Element;
  })();

  module.exports = exports = tintin;

})();
<commit_msg>v 0.0.1<commit_after>

(function(){

  'use strict';

  var Element;

  var isFunction = function(obj) {
    return typeof obj === 'function';
  };

  var isString = function(obj) {
    return typeof obj === 'string';
  };

  var isObject = function(obj) {
    return obj === Object(obj)
  };

  var isArray = Array.isArray || function(obj) {
    return toString.call(obj) == '[object Array]';
  };

  var getAncestor = function(element) {
    var ancestor = [];
    var p = function(e){
      if( e.parent ){
        ancestor.push(e.parent);
        p(e.parent);
      }
    };
    p(element);
    return ancestor;
  }

  var Exception = function(value, message) {
    this.value = value;
    this.message = message || "";
    this.toString = function() {
      return this.value + ":" + this.message;
    };
  };

  var root = global || window, block = {}, renderInfo = {};

  var tintin = function(attr, _) {
    var element, attr, _;

    if (!_){
      var _ref;
      _ref = [_, attr], attr = _ref[0], _ = _ref[1];
    }

    if (isFunction(_)) {
      element = new Element(null, "html", attr);
      _.call(element, element._.bind(element), block);
    } else {
      throw new Exception({attr: attr, _: _}, "Illegal arguments.");
    }

    return element;

  };

  tintin.extend = function(template, _block){
    var element;
    block = _block;
    if( process /*is NodeJS */ ) {
      try {
        element = require(template);
      } catch (e) {
        var fs = require('fs');
        var path = require('path');
        var dir = path.dirname(renderInfo.template);
        var layout = template.toString() + ".tintin" + path.extname(renderInfo.template);
        var files = fs.readdirSync(dir);
        for (var key in files ) {
          var file = files[key];
          if( layout === file){
            element = require(path.join(dir, layout));
            break;
          }
        }
      }
    }
    return element;
   };

  tintin.__express = function(options) {

    root.tintin = tintin;

    return function(template, data, next){

      require('coffee-script/register');

      var uglify = data.settings.env === 'production';

      renderInfo.template = template;
      renderInfo.data = data;
      renderInfo.uglify = uglify;

      tintin.data = data;

      try {
        next(null, require(template).toHTML(uglify));
      } catch (e) {
        console.error(e);
        next(e);
      }
      return ;
    };
  }

  Element = (function(){
    function Element(parent, name, attr) {
      this.parent = parent instanceof Element ? parent : null;
      this.name = name;
      this.attr = attr;

      this.childrens = [];
    }

    Element.prototype._ = function(name, attr, _) {

      if(!_){
        if(isFunction(attr) || isString(attr)){
          var _ref;
          _ref = [_, attr], attr = _ref[0], _ = _ref[1];
        }
        if(!attr) {
          attr = {};
        }
      }

      var reg = /(\[(.+?)\])/g;
      var match = name.match(reg);
      if( match ) {
        var _ref = null;
        _ref = match.map(function(s){
          return s.substring(1, s.length - 1).match(/(.+?)=(.+)/);
        });
        for ( var o in _ref) {
          attr[ _ref[o][1] ] = _ref[o][2];
        }
        name = name.replace(reg, '');
      }

      var reg = /(#([\w-]+))/;
      var match = name.match(reg);
      if(match){
        attr['id'] = match[2];
      }

      var match = name.match(/(\.([\w-]+))/g);
      if( match ) {
        attr['class'] = match.map(function(s){
          return s.substring(1, s.length);
        });
      }

      var match = name.match(/^([\w]+)/);
      if( match ) {
        name = match[0];
      } else {
        name = "div";
      }

      if( isString(name) ) {
        var element = new Element(this, name, attr);
        this.childrens.push(element);
        if( isFunction(_) ) {
          _.call(element, element._.bind(element));
        } else
        if( isString(_) ) {
          element.content = _;
        } else {

        }
      }
    };

    Element.prototype.toString = Element.prototype.toHTML = function(uglify) {
      var tag = "";
      var attr = "";
      var tab = "  ";
      var br = uglify ? '' : "\n";
      var indent = "";
      var doctype = "<!DOCTYPE html>";

      if( this.name === "html"){
        tag += doctype + br;
      }

      if( !uglify && this.parent ) {
        for (var i=0; i < getAncestor(this).length; i++){
          indent += tab;
        }
        tag += br + indent;
      }
      if( Object.keys(this.attr).length > 0 ) {
        var attrs = [];
        attr += " ";
        for ( var o in this.attr) {
          var _str = isArray(this.attr[o]) ? this.attr[o].join(' ') : this.attr[o];
          attrs.push(o + "=\"" + _str + "\"");
        }
        attr += attrs.join(" ");
      }

      tag += "<" + this.name + attr + ">";

      if( this.childrens.length > 0 ) {
        var i;
        var c;
        for ( var e in c = this.childrens) {
          tag += c[e].toHTML(uglify);
        }

        tag += br + indent + "</" + this.name + ">";
      }

      if( this.content ){
        tag += this.content || "";
        tag += "</" + this.name + ">";
      }

      return tag;
    };

    Element.prototype.extend = function(blocks){
      var element = this;
      if( isObject(blocks) ){
        console.log(block = blocks);
      } else {
        throw new Exception(block, "Illegal arguments. blocks only Object.");
      }
      return element;
    };

    return Element;
  })();

  module.exports = exports = tintin;

})();
<|endoftext|>


<commit_before>var url = require('url');
var ideal = require("ideal");
var oden = module.parent.exports;

oden.HttpServerRequest = ideal.Observable.clone().newSlots({
	type: "oden.HttpServerRequest",

	url: null,

	server: null,
	nodeRequest: null,
	body: ""
}).setSlots({
	setNodeRequest: function(nodeRequest)
	{
		this._nodeRequest = nodeRequest;
		nodeRequest.setEncoding('utf8');
		var self = this;
		var data = "";
		nodeRequest.on('data', function(chunk){
			data += chunk;
			self.setBody(data);
		});
		nodeRequest.on('end', function(){
			if (nodeRequest.url.contains("creatives.json"))
			{
				console.log("DEBUG " + self + " receivedBody: " + nodeRequest.url);
				self.observations().forEach(function(o){
					console.log("DEBUG " + self + " observer: " + o.observer());
				});

			}
			self.sendNotification('receivedBody');
		});
		return this;
	},

	url: function()
	{
		if (!this._url)
		{
			this._url = oden.Url.clone().setString(this.nodeRequest().url).setHost(this.host()).setPort(this.port());
		}

		return this._url;
	},

	path: function()
	{
		return this.url().path();
	},

	queryMap: function()
	{
		return this.url().queryMap();
	},

	ip: function()
	{
		return (this.headers().lowerCased().at('x-forwarded-for') || '').split(",").first() || this.nodeRequest().connection.remoteAddress;
	},

	host: function()
	{
		return this.headers().lowerCased().at('host') || this.ip();
	},

	port: function()
	{
		return this.server().port();
	},

	isGet: function()
	{
		return this.method() == "GET";
	},

	method: function()
	{
		return this.nodeRequest().method;
	},

	headers: function()
	{
		return map(this.nodeRequest().headers);
	},

	acceptsGzip: function()
	{
		return Object_perform(this.headers().lowerCased().at('accept-encoding'), "contains", "gzip");
	}
});<commit_msg>HttpServerRequest.hasReceivedBody<commit_after>var url = require('url');
var ideal = require("ideal");
var oden = module.parent.exports;

oden.HttpServerRequest = ideal.Observable.clone().newSlots({
	type: "oden.HttpServerRequest",

	url: null,

	server: null,
	nodeRequest: null,
	body: "",
	hasReceivedBody: false
}).setSlots({
	setNodeRequest: function(nodeRequest)
	{
		this._nodeRequest = nodeRequest;
		nodeRequest.setEncoding('utf8');
		var self = this;
		var data = "";
		nodeRequest.on('data', function(chunk){
			data += chunk;
			self.setBody(data);
		});
		nodeRequest.on('end', function(){
			self.setHasReceivedBody(true);
			self.sendNotification('receivedBody');
		});
		return this;
	},

	url: function()
	{
		if (!this._url)
		{
			this._url = oden.Url.clone().setString(this.nodeRequest().url).setHost(this.host()).setPort(this.port());
		}

		return this._url;
	},

	path: function()
	{
		return this.url().path();
	},

	queryMap: function()
	{
		return this.url().queryMap();
	},

	ip: function()
	{
		return (this.headers().lowerCased().at('x-forwarded-for') || '').split(",").first() || this.nodeRequest().connection.remoteAddress;
	},

	host: function()
	{
		return this.headers().lowerCased().at('host') || this.ip();
	},

	port: function()
	{
		return this.server().port();
	},

	isGet: function()
	{
		return this.method() == "GET";
	},

	method: function()
	{
		return this.nodeRequest().method;
	},

	headers: function()
	{
		return map(this.nodeRequest().headers);
	},

	acceptsGzip: function()
	{
		return Object_perform(this.headers().lowerCased().at('accept-encoding'), "contains", "gzip");
	}
});<|endoftext|>


<commit_before>'use strict';

const assert = require('assert');
const Crypto = require('crypto');
const runJob = require('./run_job');
const SNS    = require('./sns');

// Abstraction for a topic.
class Topic {

  constructor(server, topicName) {
    this._server     = server;
    this.name        = topicName;
    this.subscribers = new Set();
  }

  // Publish to a topic.
  publish(message) {
    const stringMessage = JSON.stringify(message);

    if (process.env.NODE_ENV === 'test') {
      // JSON encode/decode the message before passing to
      // subscriber, same transformation as when using SNS.
      const parsedMessage = JSON.parse(stringMessage);
      const messageID     = Crypto.randomBytes(32).toString('hex');
      // Intentionally not returning the promise returned
      // by dispatch. This is what happens in production:
      // publish and subscribe are not synchronized.
      this.dispatch(messageID, parsedMessage);
      return Promise.resolve();
    } else {
      return this.clientPromise
        .then(client => client.publish(this.name, stringMessage));
    }
  }

  // Subscribe to a topic by registering a handler function.
  //
  // Returns a subscription object which can be used to unsubscribe:
  //
  //   const subscription = Ironium.subscribe('topic', function(message) {
  //     // ...
  //     subscription.unsubscribe();
  //   });
  //
  subscribe(handler) {
    if (process.env.NODE_ENV !== 'test')
      throw new Error('Subscribe only available in test mode');

    if (this.subscribers.has(handler))
      throw new Error(`Handler already subscribed to topic ${this.name}`);

    this.subscribers.add(handler);

    return {
      unsubscribe: () => {
        if (this.subscribers.has(handler))
          this.subscribers.delete(handler);
        else
          throw new Error('Already unsubscribed');
      }
    };
  }

  // Dispatches a message to all subscribers.
  // Returns a promise that resolves when all subscribers resolve.
  dispatch(messageID, payload) {
    const handlers = Array.from(this.subscribers);
    const promises = handlers.map(handler => {
      return runJob(messageID, handler, [ payload ])
        .catch(error => {
          this._server._ironium.reportError(`${this.name}#${messageID}`, error);
          throw error;
        });
    });
    return Promise.all(promises);
  }

  // -- Client --

  get clientPromise() {
    if (!this._clientPromise)
      this._clientPromise = this._server.configPromise.then(client => this._clientFromConfig(client));
    return this._clientPromise;
  }

  _clientFromConfig(config) {
    switch (config.service) {
      case 'aws': {
        const awsConfig = {
          accessKeyId:     config.accessKeyId,
          secretAccessKey: config.secretAccessKey,
          region:          config.region,
          topicName:       config.prefixedName(this.name)
        };
        return new SNS(awsConfig);
      }

      default:
        throw new Error('Pub/sub only supported in AWS.');
    }
  }

}

// Abstracts the topic server.
module.exports = class Topics {

  constructor(ironium) {
    this._ironium = ironium;
    this._topics  = new Map();
  }

  // Returns the named queue, queue created on demand.
  getTopic(topicName) {
    assert(topicName, 'Missing topic name');

    if (!this._topics.has(topicName)) {
      const topic = new Topic(this, topicName);
      this._topics.set(topicName, topic);
    }

    return this._topics.get(topicName);
  }

  // Returns an array of all topics.
  get topics() {
    return Array.from(this._topics.values());
  }

  // Resolves to Configuration object.
  get configPromise() {
    // Lazy load configuration.
    return this._ironium.configPromise;
  }

};
<commit_msg>Use setImmediate instead of code comment.<commit_after>'use strict';

const assert = require('assert');
const Crypto = require('crypto');
const runJob = require('./run_job');
const SNS    = require('./sns');

// Abstraction for a topic.
class Topic {

  constructor(server, topicName) {
    this._server     = server;
    this.name        = topicName;
    this.subscribers = new Set();
  }

  // Publish to a topic.
  publish(message) {
    const stringMessage = JSON.stringify(message);

    if (process.env.NODE_ENV === 'test') {
      // JSON encode/decode the message before passing to
      // subscriber, same transformation as when using SNS.
      const parsedMessage = JSON.parse(stringMessage);
      const messageID     = Crypto.randomBytes(32).toString('hex');
      setImmediate(() => {
        this.dispatch(messageID, parsedMessage);
      });
      return Promise.resolve();
    } else {
      return this.clientPromise
        .then(client => client.publish(this.name, stringMessage));
    }
  }

  // Subscribe to a topic by registering a handler function.
  //
  // Returns a subscription object which can be used to unsubscribe:
  //
  //   const subscription = Ironium.subscribe('topic', function(message) {
  //     // ...
  //     subscription.unsubscribe();
  //   });
  //
  subscribe(handler) {
    if (process.env.NODE_ENV !== 'test')
      throw new Error('Subscribe only available in test mode');

    if (this.subscribers.has(handler))
      throw new Error(`Handler already subscribed to topic ${this.name}`);

    this.subscribers.add(handler);

    return {
      unsubscribe: () => {
        if (this.subscribers.has(handler))
          this.subscribers.delete(handler);
        else
          throw new Error('Already unsubscribed');
      }
    };
  }

  // Dispatches a message to all subscribers.
  // Returns a promise that resolves when all subscribers resolve.
  dispatch(messageID, payload) {
    const handlers = Array.from(this.subscribers);
    const promises = handlers.map(handler => {
      return runJob(messageID, handler, [ payload ])
        .catch(error => {
          this._server._ironium.reportError(`${this.name}#${messageID}`, error);
          throw error;
        });
    });
    return Promise.all(promises);
  }

  // -- Client --

  get clientPromise() {
    if (!this._clientPromise)
      this._clientPromise = this._server.configPromise.then(client => this._clientFromConfig(client));
    return this._clientPromise;
  }

  _clientFromConfig(config) {
    switch (config.service) {
      case 'aws': {
        const awsConfig = {
          accessKeyId:     config.accessKeyId,
          secretAccessKey: config.secretAccessKey,
          region:          config.region,
          topicName:       config.prefixedName(this.name)
        };
        return new SNS(awsConfig);
      }

      default:
        throw new Error('Pub/sub only supported in AWS.');
    }
  }

}

// Abstracts the topic server.
module.exports = class Topics {

  constructor(ironium) {
    this._ironium = ironium;
    this._topics  = new Map();
  }

  // Returns the named queue, queue created on demand.
  getTopic(topicName) {
    assert(topicName, 'Missing topic name');

    if (!this._topics.has(topicName)) {
      const topic = new Topic(this, topicName);
      this._topics.set(topicName, topic);
    }

    return this._topics.get(topicName);
  }

  // Returns an array of all topics.
  get topics() {
    return Array.from(this._topics.values());
  }

  // Resolves to Configuration object.
  get configPromise() {
    // Lazy load configuration.
    return this._ironium.configPromise;
  }

};
<|endoftext|>


<commit_before>'use strict';

// We don't want to require strong-agent, because anything it requires will
// become untraceable. Still, we need to use its idea of the appName, and access
// lrtime from the addon, so require the agent internals.
var addon = require('strong-agent/lib/addon');
var assert = require('assert');
var configure = require('strong-agent/lib/config').configure;

exports = module.exports = tracer;
exports.start = start;

var TRACER = null;

var lrtime = addon ? addon.lrtime : undefined;
var options = {
  archiveInterval: 20000,
  accountKey: undefined, // Filled in later
  useHttp: false,
  lrtime: lrtime,
};

function tracer() {
  return TRACER;
}

function start() {
  assert(!TRACER);
  // This is delayed to allow process.env to be modified up until start time.
  var config = configure(null, null, {}, process.env);
  options.accountKey = config.appName;
  if (options.accountKey)
    TRACER = require('strong-trace')(options);
  return TRACER;
}
<commit_msg>add agent probes to the black list<commit_after>'use strict';

// We don't want to require strong-agent, because anything it requires will
// become untraceable. Still, we need to use its idea of the appName, and access
// lrtime from the addon, so require the agent internals.
var addon = require('strong-agent/lib/addon');
var assert = require('assert');
var configure = require('strong-agent/lib/config').configure;

exports = module.exports = tracer;
exports.start = start;

var TRACER = null;

var lrtime = addon ? addon.lrtime : undefined;
var options = {
  archiveInterval: 20000,
  accountKey: undefined, // Filled in later
  useHttp: false,
  lrtime: lrtime,
  blacklist: ['*node_modules/strong-agent/*'],
};

function tracer() {
  return TRACER;
}

function start() {
  assert(!TRACER);
  // This is delayed to allow process.env to be modified up until start time.
  var config = configure(null, null, {}, process.env);
  options.accountKey = config.appName;
  if (options.accountKey)
    TRACER = require('strong-trace')(options);
  return TRACER;
}
<|endoftext|>