<commit_before>/* global __coverage__ */
var _ = require('lodash')
var fs = require('fs')
var glob = require('glob')
var minimatch = require('minimatch')
var mkdirp = require('mkdirp')
var Module = require('module')
var path = require('path')
var rimraf = require('rimraf')
var onExit = require('signal-exit')
var stripBom = require('strip-bom')
var SourceMapCache = require('./lib/source-map-cache')

function NYC (opts) {
  _.extend(this, {
    subprocessBin: path.resolve(
      __dirname,
      './bin/nyc.js'
    ),
    tempDirectory: './.nyc_output',
    cwd: process.env.NYC_CWD || process.cwd(),
    reporter: 'text',
    istanbul: require('istanbul'),
    sourceMapCache: new SourceMapCache(),
    require: []
  }, opts)

  if (!Array.isArray(this.reporter)) this.reporter = [this.reporter]

  // you can specify config in the nyc stanza of package.json.
  var config = require(path.resolve(this.cwd, './package.json')).config || {}
  config = config.nyc || {}

  // load exclude stanza from config.
  this.exclude = ['**/node_modules/**'].concat(config.exclude || ['test/**', 'test{,-*}.js'])
  if (!Array.isArray(this.exclude)) this.exclude = [this.exclude]
  this.exclude = this._prepExcludePatterns(this.exclude)

  // require extensions can be provided as config in package.json.
  this.require = config.require ? config.require : this.require

  this.instrumenter = this._createInstrumenter()
  this._createOutputDirectory()
}

NYC.prototype._loadAdditionalModules = function () {
  var _this = this
  this.require.forEach(function (r) {
    try {
      // first attempt to require the module relative to
      // the directory being instrumented.
      require(path.resolve(_this.cwd, './node_modules', r))
    } catch (e) {
      // now try other locations, .e.g, the nyc node_modules folder.
      require(r)
    }
  })
}

NYC.prototype._createInstrumenter = function () {
  var configFile = path.resolve(this.cwd, './.istanbul.yml')

  if (!fs.existsSync(configFile)) configFile = undefined

  var instrumenterConfig = this.istanbul.config.loadFile(configFile).instrumentation.config

  return new this.istanbul.Instrumenter({
    coverageVariable: '__coverage__',
    embedSource: instrumenterConfig['embed-source'],
    noCompact: !instrumenterConfig.compact,
    preserveComments: instrumenterConfig['preserve-comments']
  })
}

NYC.prototype._prepExcludePatterns = function (excludes) {
  var directories = []
  excludes = _.map(excludes, function (exclude) {
    // Remove leading "current folder" prefix
    if (_.startsWith(exclude, './')) {
      exclude = exclude.slice(2)
    }

    // Allow gitignore style of directory exclusion
    if (!_.endsWith(exclude, '/**')) {
      directories.push(exclude.replace(/\/$/, '').concat('/**'))
    }

    return exclude
  })
  return _.union(excludes, directories)
}

NYC.prototype.addFile = function (filename, returnImmediately) {
  var relFile = path.relative(this.cwd, filename)
  var instrument = this.shouldInstrumentFile(relFile)
  var content = stripBom(fs.readFileSync(filename, 'utf8'))

  if (instrument) {
    this.sourceMapCache.add(filename, content)
    content = this.instrumenter.instrumentSync(content, './' + relFile)
  } else if (returnImmediately) {
    return {}
  }

  return {
    instrument: instrument,
    content: content,
    relFile: relFile
  }
}

NYC.prototype.addContent = function (filename, content) {
  var relFile = path.relative(this.cwd, filename)
  var instrument = this.shouldInstrumentFile(relFile)

  if (instrument) {
    content = this.instrumenter.instrumentSync(content, './' + relFile)
  }

  return {
    instrument: instrument,
    content: content,
    relFile: relFile
  }
}

NYC.prototype.shouldInstrumentFile = function (relFile) {
  // only instrument a file if it's not on the exclude list.
  for (var i = 0, exclude; (exclude = this.exclude[i]) !== undefined; i++) {
    if (minimatch(relFile, exclude)) {
      return false
    }
  }
  return true
}

NYC.prototype.addAllFiles = function () {
  var _this = this

  this._createOutputDirectory()

  glob.sync('**/*.js', {nodir: true, ignore: this.exclude}).forEach(function (filename) {
    var obj = _this.addFile(filename, true)
    if (obj.instrument) {
      module._compile(
        _this.instrumenter.getPreamble(obj.content, obj.relFile),
        filename
      )
    }
  })

  this.writeCoverageFile()
}

NYC.prototype._wrapRequire = function () {
  var _this = this

  var defaultHook = function (module, filename) {
    // instrument the required file.
    var obj = _this.addFile(filename, false)

    // always use node's original _compile method to compile the instrumented
    // code. if a custom hook invoked the default hook the code should not be
    // compiled using the custom hook.
    Module.prototype._compile.call(module, obj.content, filename)
  }

  var wrapCustomHook = function (hook) {
    return function (module, filename) {
      // override the _compile method so the code can be instrumented first.
      module._compile = function (compiledSrc) {
        _this.sourceMapCache.add(filename, compiledSrc)

        // now instrument the compiled code.
        var obj = _this.addContent(filename, compiledSrc)
        Module.prototype._compile.call(module, obj.content, filename)
      }

      // allow the custom hook to compile the code. it can fall back to the
      // default hook if necessary (accessed via require.extensions['.js'] prior
      // to setting itself)
      hook(module, filename)
    }
  }

  var requireHook = defaultHook
  // track existing hooks so they can be restored without wrapping them a second
  // time.
  var hooks = [requireHook]

  // use a getter and setter to capture any external require hooks that are
  // registered, e.g., babel-core/register
  require.extensions.__defineGetter__('.js', function () {
    return requireHook
  })

  require.extensions.__defineSetter__('.js', function (hook) {
    var restoreIndex = hooks.indexOf(hook)
    if (restoreIndex !== -1) {
      requireHook = hook
      hooks.splice(restoreIndex + 1, hooks.length)
    } else {
      requireHook = wrapCustomHook(hook)
      hooks.push(requireHook)
    }
  })
}

NYC.prototype.cleanup = function () {
  if (!process.env.NYC_CWD) rimraf.sync(this.tmpDirectory())
}

NYC.prototype._createOutputDirectory = function () {
  mkdirp.sync(this.tmpDirectory())
}

NYC.prototype._wrapExit = function () {
  var _this = this

  // we always want to write coverage
  // regardless of how the process exits.
  onExit(function () {
    _this.writeCoverageFile()
  }, {alwaysLast: true})
}

NYC.prototype.wrap = function (bin) {
  this._wrapRequire()
  this._wrapExit()
  this._loadAdditionalModules()
  return this
}

NYC.prototype.writeCoverageFile = function () {
  var coverage = global.__coverage__
  if (typeof __coverage__ === 'object') coverage = __coverage__
  if (!coverage) return

  fs.writeFileSync(
    path.resolve(this.tmpDirectory(), './', process.pid + '.json'),
    JSON.stringify(this.sourceMapCache.applySourceMaps(coverage)),
    'utf-8'
  )
}

NYC.prototype.report = function (cb, _collector, _reporter) {
  cb = cb || function () {}

  var collector = _collector || new this.istanbul.Collector()
  var reporter = _reporter || new this.istanbul.Reporter()

  this._loadReports().forEach(function (report) {
    collector.add(report)
  })

  this.reporter.forEach(function (_reporter) {
    reporter.add(_reporter)
  })

  reporter.write(collector, true, cb)
}

NYC.prototype._loadReports = function () {
  var _this = this
  var files = fs.readdirSync(this.tmpDirectory())

  return _.map(files, function (f) {
    try {
      return JSON.parse(fs.readFileSync(
        path.resolve(_this.tmpDirectory(), './', f),
        'utf-8'
      ))
    } catch (e) { // handle corrupt JSON output.
      return {}
    }
  })
}

NYC.prototype.tmpDirectory = function () {
  return path.resolve(this.cwd, './', this.tempDirectory)
}

NYC.prototype.mungeArgs = function (yargv) {
  var argv = process.argv.slice(1)
  argv = argv.slice(argv.indexOf(yargv._[0]))
  if (!/^(node|iojs)$/.test(argv[0]) &&
      process.platform === 'win32' &&
      (/\.js$/.test(argv[0]) ||
        (!/\.(cmd|exe)$/.test(argv[0]) &&
        !fs.existsSync(argv[0] + '.cmd') &&
        !fs.existsSync(argv[0] + '.exe')))) {
    argv.unshift(process.execPath)
  }

  return argv
}

module.exports = NYC
<commit_msg>Remove leading `./` from path<commit_after>/* global __coverage__ */
var _ = require('lodash')
var fs = require('fs')
var glob = require('glob')
var minimatch = require('minimatch')
var mkdirp = require('mkdirp')
var Module = require('module')
var path = require('path')
var rimraf = require('rimraf')
var onExit = require('signal-exit')
var stripBom = require('strip-bom')
var SourceMapCache = require('./lib/source-map-cache')

function NYC (opts) {
  _.extend(this, {
    subprocessBin: path.resolve(
      __dirname,
      './bin/nyc.js'
    ),
    tempDirectory: './.nyc_output',
    cwd: process.env.NYC_CWD || process.cwd(),
    reporter: 'text',
    istanbul: require('istanbul'),
    sourceMapCache: new SourceMapCache(),
    require: []
  }, opts)

  if (!Array.isArray(this.reporter)) this.reporter = [this.reporter]

  // you can specify config in the nyc stanza of package.json.
  var config = require(path.resolve(this.cwd, './package.json')).config || {}
  config = config.nyc || {}

  // load exclude stanza from config.
  this.exclude = ['**/node_modules/**'].concat(config.exclude || ['test/**', 'test{,-*}.js'])
  if (!Array.isArray(this.exclude)) this.exclude = [this.exclude]
  this.exclude = this._prepExcludePatterns(this.exclude)

  // require extensions can be provided as config in package.json.
  this.require = config.require ? config.require : this.require

  this.instrumenter = this._createInstrumenter()
  this._createOutputDirectory()
}

NYC.prototype._loadAdditionalModules = function () {
  var _this = this
  this.require.forEach(function (r) {
    try {
      // first attempt to require the module relative to
      // the directory being instrumented.
      require(path.resolve(_this.cwd, './node_modules', r))
    } catch (e) {
      // now try other locations, .e.g, the nyc node_modules folder.
      require(r)
    }
  })
}

NYC.prototype._createInstrumenter = function () {
  var configFile = path.resolve(this.cwd, './.istanbul.yml')

  if (!fs.existsSync(configFile)) configFile = undefined

  var instrumenterConfig = this.istanbul.config.loadFile(configFile).instrumentation.config

  return new this.istanbul.Instrumenter({
    coverageVariable: '__coverage__',
    embedSource: instrumenterConfig['embed-source'],
    noCompact: !instrumenterConfig.compact,
    preserveComments: instrumenterConfig['preserve-comments']
  })
}

NYC.prototype._prepExcludePatterns = function (excludes) {
  var directories = []
  excludes = _.map(excludes, function (exclude) {
    // Remove leading "current folder" prefix
    if (_.startsWith(exclude, './')) {
      exclude = exclude.slice(2)
    }

    // Allow gitignore style of directory exclusion
    if (!_.endsWith(exclude, '/**')) {
      directories.push(exclude.replace(/\/$/, '').concat('/**'))
    }

    return exclude
  })
  return _.union(excludes, directories)
}

NYC.prototype.addFile = function (filename, returnImmediately) {
  var relFile = path.relative(this.cwd, filename)
  var instrument = this.shouldInstrumentFile(relFile)
  var content = stripBom(fs.readFileSync(filename, 'utf8'))

  if (instrument) {
    this.sourceMapCache.add(filename, content)
    content = this.instrumenter.instrumentSync(content, './' + relFile)
  } else if (returnImmediately) {
    return {}
  }

  return {
    instrument: instrument,
    content: content,
    relFile: relFile
  }
}

NYC.prototype.addContent = function (filename, content) {
  var relFile = path.relative(this.cwd, filename)
  var instrument = this.shouldInstrumentFile(relFile)

  if (instrument) {
    content = this.instrumenter.instrumentSync(content, './' + relFile)
  }

  return {
    instrument: instrument,
    content: content,
    relFile: relFile
  }
}

NYC.prototype.shouldInstrumentFile = function (relFile) {
  // Remove leading "current folder" prefix
  if (_.startsWith(relFile, './')) {
    relFile = relFile.slice(2)
  }

  // only instrument a file if it's not on the exclude list.
  for (var i = 0, exclude; (exclude = this.exclude[i]) !== undefined; i++) {
    if (minimatch(relFile, exclude)) {
      return false
    }
  }
  return true
}

NYC.prototype.addAllFiles = function () {
  var _this = this

  this._createOutputDirectory()

  glob.sync('**/*.js', {nodir: true, ignore: this.exclude}).forEach(function (filename) {
    var obj = _this.addFile(filename, true)
    if (obj.instrument) {
      module._compile(
        _this.instrumenter.getPreamble(obj.content, obj.relFile),
        filename
      )
    }
  })

  this.writeCoverageFile()
}

NYC.prototype._wrapRequire = function () {
  var _this = this

  var defaultHook = function (module, filename) {
    // instrument the required file.
    var obj = _this.addFile(filename, false)

    // always use node's original _compile method to compile the instrumented
    // code. if a custom hook invoked the default hook the code should not be
    // compiled using the custom hook.
    Module.prototype._compile.call(module, obj.content, filename)
  }

  var wrapCustomHook = function (hook) {
    return function (module, filename) {
      // override the _compile method so the code can be instrumented first.
      module._compile = function (compiledSrc) {
        _this.sourceMapCache.add(filename, compiledSrc)

        // now instrument the compiled code.
        var obj = _this.addContent(filename, compiledSrc)
        Module.prototype._compile.call(module, obj.content, filename)
      }

      // allow the custom hook to compile the code. it can fall back to the
      // default hook if necessary (accessed via require.extensions['.js'] prior
      // to setting itself)
      hook(module, filename)
    }
  }

  var requireHook = defaultHook
  // track existing hooks so they can be restored without wrapping them a second
  // time.
  var hooks = [requireHook]

  // use a getter and setter to capture any external require hooks that are
  // registered, e.g., babel-core/register
  require.extensions.__defineGetter__('.js', function () {
    return requireHook
  })

  require.extensions.__defineSetter__('.js', function (hook) {
    var restoreIndex = hooks.indexOf(hook)
    if (restoreIndex !== -1) {
      requireHook = hook
      hooks.splice(restoreIndex + 1, hooks.length)
    } else {
      requireHook = wrapCustomHook(hook)
      hooks.push(requireHook)
    }
  })
}

NYC.prototype.cleanup = function () {
  if (!process.env.NYC_CWD) rimraf.sync(this.tmpDirectory())
}

NYC.prototype._createOutputDirectory = function () {
  mkdirp.sync(this.tmpDirectory())
}

NYC.prototype._wrapExit = function () {
  var _this = this

  // we always want to write coverage
  // regardless of how the process exits.
  onExit(function () {
    _this.writeCoverageFile()
  }, {alwaysLast: true})
}

NYC.prototype.wrap = function (bin) {
  this._wrapRequire()
  this._wrapExit()
  this._loadAdditionalModules()
  return this
}

NYC.prototype.writeCoverageFile = function () {
  var coverage = global.__coverage__
  if (typeof __coverage__ === 'object') coverage = __coverage__
  if (!coverage) return

  fs.writeFileSync(
    path.resolve(this.tmpDirectory(), './', process.pid + '.json'),
    JSON.stringify(this.sourceMapCache.applySourceMaps(coverage)),
    'utf-8'
  )
}

NYC.prototype.report = function (cb, _collector, _reporter) {
  cb = cb || function () {}

  var collector = _collector || new this.istanbul.Collector()
  var reporter = _reporter || new this.istanbul.Reporter()

  this._loadReports().forEach(function (report) {
    collector.add(report)
  })

  this.reporter.forEach(function (_reporter) {
    reporter.add(_reporter)
  })

  reporter.write(collector, true, cb)
}

NYC.prototype._loadReports = function () {
  var _this = this
  var files = fs.readdirSync(this.tmpDirectory())

  return _.map(files, function (f) {
    try {
      return JSON.parse(fs.readFileSync(
        path.resolve(_this.tmpDirectory(), './', f),
        'utf-8'
      ))
    } catch (e) { // handle corrupt JSON output.
      return {}
    }
  })
}

NYC.prototype.tmpDirectory = function () {
  return path.resolve(this.cwd, './', this.tempDirectory)
}

NYC.prototype.mungeArgs = function (yargv) {
  var argv = process.argv.slice(1)
  argv = argv.slice(argv.indexOf(yargv._[0]))
  if (!/^(node|iojs)$/.test(argv[0]) &&
      process.platform === 'win32' &&
      (/\.js$/.test(argv[0]) ||
        (!/\.(cmd|exe)$/.test(argv[0]) &&
        !fs.existsSync(argv[0] + '.cmd') &&
        !fs.existsSync(argv[0] + '.exe')))) {
    argv.unshift(process.execPath)
  }

  return argv
}

module.exports = NYC
<|endoftext|>


<commit_before>const helper = require('./helper')

helper.mockedAppInit()
var app = require('../server/app')
module.exports = app
<commit_msg>refactor(init.test.js): change var to const<commit_after>const helper = require('./helper')

helper.mockedAppInit()
const app = require('../server/app')
module.exports = app
<|endoftext|>


<commit_before>/* jshint node: true */
'use strict';

var Funnel = require('broccoli-funnel');
var mergeTrees = require('broccoli-merge-trees');
var path = require('path');
var esTranspiler = require('broccoli-babel-transpiler');
const concat = require('broccoli-concat');

module.exports = {
  isDevelopingAddon: function(){
    return true;
  },

  name: 'd3',

  d3Modules: [
    'd3-path',
    'd3-shape',
    'd3-selection',
    'd3-array',
    'd3-scale',
    'd3-interpolate',
    'd3-color',
    'd3-format',
    'd3-time-format',
    'd3-time',
    'd3-ease',
    // 'd3-transition', // Disabled until this is ported from d3 properly
  ],

  included: function(app) {
    this._super.included.apply(this, arguments);
    this.app = app;

    this.d3Modules.forEach(function(packageName) {
      app.import(path.join('vendor', 'd3', packageName + '.js'));
    });
  },

  treeForVendor: function(tree) {
    var trees = [];

    if (tree) { trees.push(tree); }

    this.d3Modules.forEach(function(packageName) {
      var packageBuildPath = path.join('build', packageName + '.js');
      var d3PathToSrc = require.resolve(packageName).replace(packageBuildPath, '');

      let srcTree = new Funnel(path.join(d3PathToSrc), {
        include: ['src/**/*.js', 'index.js'],
        destDir: '/' + packageName,
        annotation: 'Funnel: D3 Source ['+ packageName + ']'
      });

      var compiledSrcTree = esTranspiler(srcTree, {
        stage: 1,
        moduleIds: true,
        modules: 'amd',

        comments: false,
        compact: false,
        loose: true,

        moduleRoot: packageName,

        blacklist: [
          'useStrict'
        ],
        code: true,

        getModuleId: function (name) {
          name = name
            .replace(/\/index$/, '')
            .replace(/\/src/, '');
          return name;
        },

        resolveModuleSource: function (source, filename) {
          if (/\.\//.test(source)) {
            // This is a relative import from within the package
            var newPath = path.resolve("/", filename, "..", source)
              .replace(/\/src/, '')
              .replace(/^\//, '');

            return newPath;
          }else{
            // This is *probably* an import for another package.
            return source;
          }
        }
      }, {
        annotation: 'Transpile D3 source to AMD ['+ packageName+ ']',
      });

      trees.push(concat(compiledSrcTree, {
        inputFiles: [
          '**/**/*.js'
        ],
        outputFile: '/d3/' + packageName + '.js'
      }));
    });

    return mergeTrees(trees);
  }
};
<commit_msg>Repackaged amd modules<commit_after>/* jshint node: true */
'use strict';

var Funnel = require('broccoli-funnel');
var mergeTrees = require('broccoli-merge-trees');
var path = require('path');
var esTranspiler = require('broccoli-babel-transpiler');
const concat = require('broccoli-concat');

module.exports = {
  isDevelopingAddon: function(){
    return true;
  },

  name: 'd3',

  d3Modules: [
    'd3-path',
    'd3-shape',
    'd3-selection',
    'd3-array',
    'd3-scale',
    'd3-interpolate',
    'd3-color',
    'd3-format',
    'd3-time-format',
    'd3-time',
    'd3-ease',
    // 'd3-transition', // Disabled until this is ported from d3 properly
  ],

  included: function(app) {
    this._super.included.apply(this, arguments);
    this.app = app;

    this.d3Modules.forEach(function(packageName) {
      app.import(path.join('vendor', packageName, packageName + '.js'));
    });
  },

  treeForVendor: function(tree) {
    var trees = [];

    if (tree) { trees.push(tree); }

    this.d3Modules.forEach(function(packageName) {
      var packageBuildPath = path.join('build', packageName + '.js');
      var d3PathToSrc = require.resolve(packageName).replace(packageBuildPath, '');

      let srcTree = new Funnel(path.join(d3PathToSrc), {
        include: ['src/**/*.js', 'index.js'],
        destDir: '/' + packageName,
        annotation: 'Funnel: D3 Source ['+ packageName + ']'
      });

      var compiledSrcTree = esTranspiler(srcTree, {
        persist: false,

        stage: 0,
        moduleIds: true,
        modules: 'amd',

        comments: true,
        compact: false,
        loose: true,

        moduleRoot: packageName,

        blacklist: [
        code: true,
          'useStrict'
        ],

        getModuleId: function (name) {
          name = name
            .replace(/\/index$/, '')
            .replace(/\/src/, '');
          return name;
        },

        resolveModuleSource: function (source, filename) {
          if (/\.\//.test(source)) {
            // This is a relative import from within the package
            var newPath = path.resolve("/", filename, "..", source)
              .replace(/\/src/, '')
              .replace(/^\//, '');

            return newPath;
          }else{
            // This is *probably* an import for another package.
            return source;
          }
        }
      }, {
        annotation: 'Transpile D3 source to AMD ['+ packageName+ ']',
      });

      trees.push(concat(compiledSrcTree, {
        inputFiles: [
          '**/**/*.js'
        ],
        outputFile: '/' + packageName + '/' + packageName + '.js'
      }));
    });

    return mergeTrees(trees);
  }
};
<|endoftext|>


<commit_before>/* global THREE, AFRAME  */
var cylinderTexture = require('./lib/cylinderTexture');
var parabolicCurve = require('./lib/ParabolicCurve');
var RayCurve = require('./lib/RayCurve');

if (typeof AFRAME === 'undefined') {
  throw new Error('Component attempted to register before AFRAME was available.');
}

if (!Element.prototype.matches) {
  Element.prototype.matches =
    Element.prototype.matchesSelector ||
    Element.prototype.mozMatchesSelector ||
    Element.prototype.msMatchesSelector ||
    Element.prototype.oMatchesSelector ||
    Element.prototype.webkitMatchesSelector ||
    function (s) {
      var matches = (this.document || this.ownerDocument).querySelectorAll(s);
      var i = matches.length;
      while (--i >= 0 && matches.item(i) !== this) { /* no-op */ }
      return i > -1;
    };
}

AFRAME.registerComponent('teleport-controls', {
  schema: {
    type: {default: 'parabolic', oneOf: ['parabolic', 'line']},
    button: {default: 'trackpad', oneOf: ['trackpad', 'trigger', 'grip', 'menu']},
    collisionEntities: {default: ''},
    hitEntity: {type: 'selector'},
    cameraRig: {type: 'selector'},
    hitCylinderColor: {type: 'color', default: '#99ff99'},
    hitCylinderRadius: {default: 0.25, min: 0},
    hitCylinderHeight: {default: 0.3, min: 0},
    maxLength: {default: 10, min: 0, if: {type: ['line']}},
    curveNumberPoints: {default: 30, min: 2, if: {type: ['parabolic']}},
    curveLineWidth: {default: 0.025},
    curveHitColor: {type: 'color', default: '#99ff99'},
    curveMissColor: {type: 'color', default: '#ff0000'},
    curveShootingSpeed: {default: 5, min: 0, if: {type: ['parabolic']}},
    defaultPlaneSize: { default: 100 },
    landingNormal: {type: 'vec3', default: '0 1 0'},
    landingMaxAngle: {default: '45', min: 0, max: 360}
  },

  init: function () {
    var data = this.data;
    var el = this.el;
    var teleportEntity;

    this.active = false;
    this.obj = el.object3D;
    this.hitPoint = new THREE.Vector3();
    this.hit = false;
    this.prevHeightDiff = 0;
    this.referenceNormal = new THREE.Vector3();
    this.curveMissColor = new THREE.Color();
    this.curveHitColor = new THREE.Color();
    this.raycaster = new THREE.Raycaster();

    this.defaultPlane = createDefaultPlane();

    teleportEntity = this.teleportEntity = document.createElement('a-entity');
    teleportEntity.classList.add('teleportRay');
    teleportEntity.setAttribute('visible', false);
    el.sceneEl.appendChild(this.teleportEntity);

    el.addEventListener(data.button + 'down', this.onButtonDown.bind(this));
    el.addEventListener(data.button + 'up', this.onButtonUp.bind(this));

    this.queryCollisionEntities();
  },

  update: function (oldData) {
    var data = this.data;
    var diff = AFRAME.utils.diff(data, oldData);

    // Update normal.
    this.referenceNormal.copy(data.landingNormal);

    // Update colors.
    this.curveMissColor.set(data.curveMissColor);
    this.curveHitColor.set(data.curveHitColor);

    // Create or update line mesh.
    if (!this.line ||
        'curveLineWidth' in diff || 'curveNumberPoints' in diff || 'type' in diff) {
      this.line = createLine(data);
      this.teleportEntity.setObject3D('mesh', this.line.mesh);
    }

    // Create or update hit entity.
    if (data.hitEntity) {
      this.hitEntity = data.hitEntity;
    } else if (!this.hitEntity || 'hitCylinderColor' in diff || 'hitCylinderHeight' in diff ||
               'hitCylinderRadius' in diff) {
      // Remove previous entity, create new entity (could be more performant).
      if (this.hitEntity) { this.hitEntity.parentNode.removeChild(this.hitEntity); }
      this.hitEntity = createHitEntity(data);
      this.el.sceneEl.appendChild(this.hitEntity);
    }
    this.hitEntity.setAttribute('visible', false);

    if ('collisionEntities' in diff) { this.queryCollisionEntities(); }
  },

  remove: function () {
    var el = this.el;
    var hitEntity = this.hitEntity;
    var teleportEntity = this.teleportEntity;

    if (hitEntity) { hitEntity.parentNode.removeChild(hitEntity); }
    if (teleportEntity) { teleportEntity.parentNode.removeChild(teleportEntity); }

    el.sceneEl.removeEventListener('child-attached', this.childAttachHandler);
    el.sceneEl.removeEventListener('child-detached', this.childDetachHandler);
  },

  tick: (function () {
    var p0 = new THREE.Vector3();
    var quaternion = new THREE.Quaternion();
    var translation = new THREE.Vector3();
    var scale = new THREE.Vector3();
    var shootAngle = new THREE.Vector3();
    var lastNext = new THREE.Vector3();

    return function (time, delta) {
      if (!this.active) { return; }

      var matrixWorld = this.obj.matrixWorld;
      matrixWorld.decompose(translation, quaternion, scale);

      var direction = shootAngle.set(0, 0, -1)
        .applyQuaternion(quaternion).normalize();
      this.line.setDirection(direction.clone());
      p0.copy(this.obj.getWorldPosition());

      var last = p0.clone();
      var next;

      // Set default status as non-hit
      this.teleportEntity.setAttribute('visible', true);
      this.line.material.color.set(this.curveMissColor);
      this.hitEntity.setAttribute('visible', false);
      this.hit = false;

      if (this.data.type === 'parabolic') {
        var v0 = direction.clone().multiplyScalar(this.data.curveShootingSpeed);
        var g = -9.8;
        var a = new THREE.Vector3(0, g, 0);

        for (var i = 0; i < this.line.numPoints; i++) {
          var t = i / (this.line.numPoints - 1);
          next = parabolicCurve(p0, v0, a, t);
          // Update the raycaster with the length of the current segment last->next
          var dirLastNext = lastNext.copy(next).sub(last).normalize();
          this.raycaster.far = dirLastNext.length();
          this.raycaster.set(last, dirLastNext);

          if (this.checkMeshCollisions(i, next)) { break; }
          last.copy(next);
        }
      } else if (this.data.type === 'line') {
        next = last.add(direction.clone().multiplyScalar(this.data.maxLength));
        this.raycaster.far = this.data.maxLength;

        this.raycaster.set(p0, direction);
        this.line.setPoint(0, p0);

        this.checkMeshCollisions(1, next);
      }
    };
  })(),

  /**
   * Run `querySelectorAll` for `collisionEntities` and maintain it with `child-attached`
   * and `child-detached` events.
   */
  queryCollisionEntities: function () {
    var collisionEntities;
    var data = this.data;
    var el = this.el;

    if (!data.collisionEntities) {
      this.collisionEntities = [];
      return
    }

    collisionEntities = [].slice.call(el.sceneEl.querySelectorAll(data.collisionEntities));
    this.collisionEntities = collisionEntities;

    // Update entity list on attach.
    this.childAttachHandler = function childAttachHandler (evt) {
      if (!evt.detail.el.matches(data.collisionEntities)) { return; }
      collisionEntities.push(evt.detail.el);
    };
    el.sceneEl.addEventListener('child-attached', this.childAttachHandler);

    // Update entity list on detach.
    this.childDetachHandler = function childDetachHandler (evt) {
      var index;
      if (!evt.detail.el.matches(data.collisionEntities)) { return; }
      index = collisionEntities.indexOf(evt.detail.el);
      if (index === -1) { return; }
      collisionEntities.splice(index, 1);
    };
    el.sceneEl.addEventListener('child-detached', this.childDetachHandler);
  },

  onButtonDown: function () {
    this.active = true;
  },

  /**
   * Jump!
   */
  onButtonUp: function (evt) {
    if (!this.active) { return; }

    // Jump!

    // Hide the hit point and the curve
    this.active = false;
    this.hitEntity.setAttribute('visible', false);
    this.teleportEntity.setAttribute('visible', false);

    if (!this.hit) {
      // Button released but not hit point
      return;
    }

    // @todo Create this aux vectors outside
    if (this.data.cameraRig) {
      var cameraRigPosition = new THREE.Vector3().copy(this.data.cameraRig.getAttribute('position'));
      var newCameraRigPositionY = cameraRigPosition.y + this.hitPoint.y - this.prevHeightDiff;
      var newCameraRigPosition = new THREE.Vector3(this.hitPoint.x, newCameraRigPositionY, this.hitPoint.z);
      this.prevHeightDiff = this.hitPoint.y;
      this.data.cameraRig.setAttribute('position', newCameraRigPosition);
    } else {
      var cameraEl = this.el.sceneEl.camera.el;
      var camPosition = new THREE.Vector3().copy(cameraEl.getAttribute('position'));

      var newCamPositionY = camPosition.y + this.hitPoint.y - this.prevHeightDiff;
      var newCamPosition = new THREE.Vector3(this.hitPoint.x, newCamPositionY, this.hitPoint.z);
      this.prevHeightDiff = this.hitPoint.y;

      cameraEl.setAttribute('position', newCamPosition);

      // Find the hands and move them proportionally
      var hands = document.querySelectorAll('a-entity[tracked-controls]');
      for (var i = 0; i < hands.length; i++) {
        var position = hands[i].getAttribute('position');
        var pos = new THREE.Vector3().copy(position);
        var diff = camPosition.clone().sub(pos);
        var newPosition = newCamPosition.clone().sub(diff);
        hands[i].setAttribute('position', newPosition);
      }
    }

    this.el.emit('teleport', {
      oldPosition: camPosition,
      newPosition: newCamPosition,
      hitPoint: this.hitPoint
    });
  },

  /**
   * Check for raycaster intersection.
   *
   * @param {number} Line fragment point index.
   * @param {number} Next line fragment point index.
   * @returns {boolean} true if there's an intersection.
   */
  checkMeshCollisions: function (i, next) {
    var intersects;
    var meshes;

    // Gather the meshes here to avoid having to wait for entities to iniitalize.
    meshes = this.collisionEntities.map(function (entity) {
      return entity.getObject3D('mesh');
    }).filter(function (n) { return n; });
    meshes = meshes.length ? meshes : [this.defaultPlane];

    intersects = this.raycaster.intersectObjects(meshes, true);
    if (intersects.length > 0 && !this.hit &&
        this.isValidNormalsAngle(intersects[0].face.normal)) {
      var point = intersects[0].point;

      this.line.material.color.set(this.curveHitColor);
      this.hitEntity.setAttribute('position', point);
      this.hitEntity.setAttribute('visible', true);

      this.hit = true;
      this.hitPoint.copy(intersects[0].point);

      // If hit, just fill the rest of the points with the hit point and break the loop
      for (var j = i; j < this.line.numPoints; j++) {
        this.line.setPoint(j, this.hitPoint);
      }
      return true;
    } else {
      this.line.setPoint(i, next);
      return false;
    }
  },

  isValidNormalsAngle: function (collisionNormal) {
    var angleNormals = this.referenceNormal.angleTo(collisionNormal);
    return (THREE.Math.RAD2DEG * angleNormals <= this.data.landingMaxAngle);
  },
});

function createLine (data) {
  var numPoints = data.type === 'line' ? 2 : data.curveNumberPoints;
  return new RayCurve(numPoints, data.curveLineWidth);
}

/**
 * Create mesh to represent the area of intersection.
 * Default to a combination of torus and cylinder.
 */
function createHitEntity (data) {
  var cylinder;
  var hitEntity;
  var torus;

  // Parent.
  hitEntity = document.createElement('a-entity');
  hitEntity.className = 'hitEntity';

  // Torus.
  torus = document.createElement('a-entity');
  torus.setAttribute('geometry', {
    primitive: 'torus',
    radius: data.hitCylinderRadius,
    radiusTubular: 0.01
  });
  torus.setAttribute('rotation', {x: 90, y: 0, z: 0});
  torus.setAttribute('material', {
    shader: 'flat',
    color: data.hitCylinderColor,
    side: 'double',
    depthTest: false
  });
  hitEntity.appendChild(torus);

  // Cylinder.
  cylinder = document.createElement('a-entity');
  cylinder.setAttribute('position', {x: 0, y: data.hitCylinderHeight / 2, z: 0});
  cylinder.setAttribute('geometry', {
    primitive: 'cylinder',
    segmentsHeight: 1,
    radius: data.hitCylinderRadius,
    height: data.hitCylinderHeight,
    openEnded: true
  });
  cylinder.setAttribute('material', {
    shader: 'flat',
    color: data.hitCylinderColor,
    side: 'double',
    src: cylinderTexture,
    transparent: true,
    depthTest: false
  });
  hitEntity.appendChild(cylinder);

  return hitEntity;
}

function createDefaultPlane () {
  var geometry;
  var material;

  // @hack: Because I can't get THREE.BufferPlane working on raycaster.
  geometry = new THREE.BoxBufferGeometry(this.data.defaultPlaneSize, 0.5, this.data.defaultPlaneSize);
  geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0, -0.25, 0));
  material = new THREE.MeshBasicMaterial({color: 0xffff00});
  return new THREE.Mesh(geometry, material);
}
<commit_msg>Fix defaultPlaneSize<commit_after>/* global THREE, AFRAME  */
var cylinderTexture = require('./lib/cylinderTexture');
var parabolicCurve = require('./lib/ParabolicCurve');
var RayCurve = require('./lib/RayCurve');

if (typeof AFRAME === 'undefined') {
  throw new Error('Component attempted to register before AFRAME was available.');
}

if (!Element.prototype.matches) {
  Element.prototype.matches =
    Element.prototype.matchesSelector ||
    Element.prototype.mozMatchesSelector ||
    Element.prototype.msMatchesSelector ||
    Element.prototype.oMatchesSelector ||
    Element.prototype.webkitMatchesSelector ||
    function (s) {
      var matches = (this.document || this.ownerDocument).querySelectorAll(s);
      var i = matches.length;
      while (--i >= 0 && matches.item(i) !== this) { /* no-op */ }
      return i > -1;
    };
}

AFRAME.registerComponent('teleport-controls', {
  schema: {
    type: {default: 'parabolic', oneOf: ['parabolic', 'line']},
    button: {default: 'trackpad', oneOf: ['trackpad', 'trigger', 'grip', 'menu']},
    collisionEntities: {default: ''},
    hitEntity: {type: 'selector'},
    cameraRig: {type: 'selector'},
    hitCylinderColor: {type: 'color', default: '#99ff99'},
    hitCylinderRadius: {default: 0.25, min: 0},
    hitCylinderHeight: {default: 0.3, min: 0},
    maxLength: {default: 10, min: 0, if: {type: ['line']}},
    curveNumberPoints: {default: 30, min: 2, if: {type: ['parabolic']}},
    curveLineWidth: {default: 0.025},
    curveHitColor: {type: 'color', default: '#99ff99'},
    curveMissColor: {type: 'color', default: '#ff0000'},
    curveShootingSpeed: {default: 5, min: 0, if: {type: ['parabolic']}},
    defaultPlaneSize: { default: 100 },
    landingNormal: {type: 'vec3', default: '0 1 0'},
    landingMaxAngle: {default: '45', min: 0, max: 360}
  },

  init: function () {
    var data = this.data;
    var el = this.el;
    var teleportEntity;

    this.active = false;
    this.obj = el.object3D;
    this.hitPoint = new THREE.Vector3();
    this.hit = false;
    this.prevHeightDiff = 0;
    this.referenceNormal = new THREE.Vector3();
    this.curveMissColor = new THREE.Color();
    this.curveHitColor = new THREE.Color();
    this.raycaster = new THREE.Raycaster();

    this.defaultPlane = createDefaultPlane(this.data.defaultPlaneSize);

    teleportEntity = this.teleportEntity = document.createElement('a-entity');
    teleportEntity.classList.add('teleportRay');
    teleportEntity.setAttribute('visible', false);
    el.sceneEl.appendChild(this.teleportEntity);

    el.addEventListener(data.button + 'down', this.onButtonDown.bind(this));
    el.addEventListener(data.button + 'up', this.onButtonUp.bind(this));

    this.queryCollisionEntities();
  },

  update: function (oldData) {
    var data = this.data;
    var diff = AFRAME.utils.diff(data, oldData);

    // Update normal.
    this.referenceNormal.copy(data.landingNormal);

    // Update colors.
    this.curveMissColor.set(data.curveMissColor);
    this.curveHitColor.set(data.curveHitColor);

    // Create or update line mesh.
    if (!this.line ||
        'curveLineWidth' in diff || 'curveNumberPoints' in diff || 'type' in diff) {
      this.line = createLine(data);
      this.teleportEntity.setObject3D('mesh', this.line.mesh);
    }

    // Create or update hit entity.
    if (data.hitEntity) {
      this.hitEntity = data.hitEntity;
    } else if (!this.hitEntity || 'hitCylinderColor' in diff || 'hitCylinderHeight' in diff ||
               'hitCylinderRadius' in diff) {
      // Remove previous entity, create new entity (could be more performant).
      if (this.hitEntity) { this.hitEntity.parentNode.removeChild(this.hitEntity); }
      this.hitEntity = createHitEntity(data);
      this.el.sceneEl.appendChild(this.hitEntity);
    }
    this.hitEntity.setAttribute('visible', false);

    if ('collisionEntities' in diff) { this.queryCollisionEntities(); }
  },

  remove: function () {
    var el = this.el;
    var hitEntity = this.hitEntity;
    var teleportEntity = this.teleportEntity;

    if (hitEntity) { hitEntity.parentNode.removeChild(hitEntity); }
    if (teleportEntity) { teleportEntity.parentNode.removeChild(teleportEntity); }

    el.sceneEl.removeEventListener('child-attached', this.childAttachHandler);
    el.sceneEl.removeEventListener('child-detached', this.childDetachHandler);
  },

  tick: (function () {
    var p0 = new THREE.Vector3();
    var quaternion = new THREE.Quaternion();
    var translation = new THREE.Vector3();
    var scale = new THREE.Vector3();
    var shootAngle = new THREE.Vector3();
    var lastNext = new THREE.Vector3();

    return function (time, delta) {
      if (!this.active) { return; }

      var matrixWorld = this.obj.matrixWorld;
      matrixWorld.decompose(translation, quaternion, scale);

      var direction = shootAngle.set(0, 0, -1)
        .applyQuaternion(quaternion).normalize();
      this.line.setDirection(direction.clone());
      p0.copy(this.obj.getWorldPosition());

      var last = p0.clone();
      var next;

      // Set default status as non-hit
      this.teleportEntity.setAttribute('visible', true);
      this.line.material.color.set(this.curveMissColor);
      this.hitEntity.setAttribute('visible', false);
      this.hit = false;

      if (this.data.type === 'parabolic') {
        var v0 = direction.clone().multiplyScalar(this.data.curveShootingSpeed);
        var g = -9.8;
        var a = new THREE.Vector3(0, g, 0);

        for (var i = 0; i < this.line.numPoints; i++) {
          var t = i / (this.line.numPoints - 1);
          next = parabolicCurve(p0, v0, a, t);
          // Update the raycaster with the length of the current segment last->next
          var dirLastNext = lastNext.copy(next).sub(last).normalize();
          this.raycaster.far = dirLastNext.length();
          this.raycaster.set(last, dirLastNext);

          if (this.checkMeshCollisions(i, next)) { break; }
          last.copy(next);
        }
      } else if (this.data.type === 'line') {
        next = last.add(direction.clone().multiplyScalar(this.data.maxLength));
        this.raycaster.far = this.data.maxLength;

        this.raycaster.set(p0, direction);
        this.line.setPoint(0, p0);

        this.checkMeshCollisions(1, next);
      }
    };
  })(),

  /**
   * Run `querySelectorAll` for `collisionEntities` and maintain it with `child-attached`
   * and `child-detached` events.
   */
  queryCollisionEntities: function () {
    var collisionEntities;
    var data = this.data;
    var el = this.el;

    if (!data.collisionEntities) {
      this.collisionEntities = [];
      return
    }

    collisionEntities = [].slice.call(el.sceneEl.querySelectorAll(data.collisionEntities));
    this.collisionEntities = collisionEntities;

    // Update entity list on attach.
    this.childAttachHandler = function childAttachHandler (evt) {
      if (!evt.detail.el.matches(data.collisionEntities)) { return; }
      collisionEntities.push(evt.detail.el);
    };
    el.sceneEl.addEventListener('child-attached', this.childAttachHandler);

    // Update entity list on detach.
    this.childDetachHandler = function childDetachHandler (evt) {
      var index;
      if (!evt.detail.el.matches(data.collisionEntities)) { return; }
      index = collisionEntities.indexOf(evt.detail.el);
      if (index === -1) { return; }
      collisionEntities.splice(index, 1);
    };
    el.sceneEl.addEventListener('child-detached', this.childDetachHandler);
  },

  onButtonDown: function () {
    this.active = true;
  },

  /**
   * Jump!
   */
  onButtonUp: function (evt) {
    if (!this.active) { return; }

    // Jump!

    // Hide the hit point and the curve
    this.active = false;
    this.hitEntity.setAttribute('visible', false);
    this.teleportEntity.setAttribute('visible', false);

    if (!this.hit) {
      // Button released but not hit point
      return;
    }

    // @todo Create this aux vectors outside
    if (this.data.cameraRig) {
      var cameraRigPosition = new THREE.Vector3().copy(this.data.cameraRig.getAttribute('position'));
      var newCameraRigPositionY = cameraRigPosition.y + this.hitPoint.y - this.prevHeightDiff;
      var newCameraRigPosition = new THREE.Vector3(this.hitPoint.x, newCameraRigPositionY, this.hitPoint.z);
      this.prevHeightDiff = this.hitPoint.y;
      this.data.cameraRig.setAttribute('position', newCameraRigPosition);
    } else {
      var cameraEl = this.el.sceneEl.camera.el;
      var camPosition = new THREE.Vector3().copy(cameraEl.getAttribute('position'));

      var newCamPositionY = camPosition.y + this.hitPoint.y - this.prevHeightDiff;
      var newCamPosition = new THREE.Vector3(this.hitPoint.x, newCamPositionY, this.hitPoint.z);
      this.prevHeightDiff = this.hitPoint.y;

      cameraEl.setAttribute('position', newCamPosition);

      // Find the hands and move them proportionally
      var hands = document.querySelectorAll('a-entity[tracked-controls]');
      for (var i = 0; i < hands.length; i++) {
        var position = hands[i].getAttribute('position');
        var pos = new THREE.Vector3().copy(position);
        var diff = camPosition.clone().sub(pos);
        var newPosition = newCamPosition.clone().sub(diff);
        hands[i].setAttribute('position', newPosition);
      }
    }

    this.el.emit('teleport', {
      oldPosition: camPosition,
      newPosition: newCamPosition,
      hitPoint: this.hitPoint
    });
  },

  /**
   * Check for raycaster intersection.
   *
   * @param {number} Line fragment point index.
   * @param {number} Next line fragment point index.
   * @returns {boolean} true if there's an intersection.
   */
  checkMeshCollisions: function (i, next) {
    var intersects;
    var meshes;

    // Gather the meshes here to avoid having to wait for entities to iniitalize.
    meshes = this.collisionEntities.map(function (entity) {
      return entity.getObject3D('mesh');
    }).filter(function (n) { return n; });
    meshes = meshes.length ? meshes : [this.defaultPlane];

    intersects = this.raycaster.intersectObjects(meshes, true);
    if (intersects.length > 0 && !this.hit &&
        this.isValidNormalsAngle(intersects[0].face.normal)) {
      var point = intersects[0].point;

      this.line.material.color.set(this.curveHitColor);
      this.hitEntity.setAttribute('position', point);
      this.hitEntity.setAttribute('visible', true);

      this.hit = true;
      this.hitPoint.copy(intersects[0].point);

      // If hit, just fill the rest of the points with the hit point and break the loop
      for (var j = i; j < this.line.numPoints; j++) {
        this.line.setPoint(j, this.hitPoint);
      }
      return true;
    } else {
      this.line.setPoint(i, next);
      return false;
    }
  },

  isValidNormalsAngle: function (collisionNormal) {
    var angleNormals = this.referenceNormal.angleTo(collisionNormal);
    return (THREE.Math.RAD2DEG * angleNormals <= this.data.landingMaxAngle);
  },
});

function createLine (data) {
  var numPoints = data.type === 'line' ? 2 : data.curveNumberPoints;
  return new RayCurve(numPoints, data.curveLineWidth);
}

/**
 * Create mesh to represent the area of intersection.
 * Default to a combination of torus and cylinder.
 */
function createHitEntity (data) {
  var cylinder;
  var hitEntity;
  var torus;

  // Parent.
  hitEntity = document.createElement('a-entity');
  hitEntity.className = 'hitEntity';

  // Torus.
  torus = document.createElement('a-entity');
  torus.setAttribute('geometry', {
    primitive: 'torus',
    radius: data.hitCylinderRadius,
    radiusTubular: 0.01
  });
  torus.setAttribute('rotation', {x: 90, y: 0, z: 0});
  torus.setAttribute('material', {
    shader: 'flat',
    color: data.hitCylinderColor,
    side: 'double',
    depthTest: false
  });
  hitEntity.appendChild(torus);

  // Cylinder.
  cylinder = document.createElement('a-entity');
  cylinder.setAttribute('position', {x: 0, y: data.hitCylinderHeight / 2, z: 0});
  cylinder.setAttribute('geometry', {
    primitive: 'cylinder',
    segmentsHeight: 1,
    radius: data.hitCylinderRadius,
    height: data.hitCylinderHeight,
    openEnded: true
  });
  cylinder.setAttribute('material', {
    shader: 'flat',
    color: data.hitCylinderColor,
    side: 'double',
    src: cylinderTexture,
    transparent: true,
    depthTest: false
  });
  hitEntity.appendChild(cylinder);

  return hitEntity;
}

function createDefaultPlane (size) {
  var geometry;
  var material;

  // @hack: Because I can't get THREE.BufferPlane working on raycaster.
  geometry = new THREE.BoxBufferGeometry(size, 0.5, size);
  geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0, -0.25, 0));
  material = new THREE.MeshBasicMaterial({color: 0xffff00});
  return new THREE.Mesh(geometry, material);
}
<|endoftext|>


<commit_before>/* eslint global-require: 0 */
/* eslint import/no-extraneous-dependencies: 0 */

const http = require('http');
const crypto = require('crypto');
const LRUCache = require('lru-cache');
const Memory = require('./Memory.js');
const Worker = require('./Worker.js');
const Logger = require('./Logger.js');
const Handler = require('./kernel/Handler.js');
const promise = require('./kernel/promise.js');
const { IncomingMessage } = require('./kernel/Message.js');

let pm2 = null;
try {
  pm2 = require('pm2');
} catch (err) {
  console.err(err);
  pm2 = null;
}

const PRIVATE_METHODS = [
  'checkMethod', 'handleRequest', 'useCachePolicy',
  'getRequestDigest', 'verifyResponse', 'willPurgeCache'
];
const PROTECT_METHODS = ['listen'];
const VIRTUAL_METHODS = [
  'reviseRequest', 'getCachePolicy',
  'mustSkipCache', 'mustWaitAgent', 'willEmitError',
  'inferUpstream'
];

const PRIVATE = {};
PRIVATE_METHODS.forEach((key) => {
  PRIVATE[key] = Symbol(key);
});

class Server {
  /**
   * constructor
   * @memberof Server
   */
  constructor(options) {
    this.server = http.createServer(this[PRIVATE.handleRequest].bind(this));
    this.caches = {
      digest: new LRUCache({ max: 1000, maxAge: 1e3 * 3600 * 24 })
    };
    this.memory = new Memory(options);
    this.worker = new Worker();
    this.pm2_ids = new Set();
    this.logger = Logger.getInstance();
    this[PRIVATE.checkMethod]();

    // 若当前使用 pm2 环境，则增加监听器，保存 pm2 id 集合
    if (pm2) {
      pm2.list((error, list) => {
        if (error) {
          return console.error(error);
        }
        if (list.length) {
          list.forEach(item => this.pm2_ids.add(item.pm2_env.pm_id));
          process.on('message', ({ data = {} }) => {
            const { action, url } = data;
            if (!action) {
              return null;
            }
            // type=purge-cache 则干掉当前进程缓存
            if (action === 'purge-cache') {
              this.purgeCache(url);
            }
          });
        }
      });
    }
  }

  /**
   * check public method
   *
   * @memberof Server
   */
  [PRIVATE.checkMethod]() {
    const proto = Server.prototype;
    VIRTUAL_METHODS.forEach((key) => {
      Object.defineProperty(
        proto, key, { configurable: false, writable: false }
      );
    });
    const errorMethod = PROTECT_METHODS.find(key => this[key] !== proto[key]);
    if (! errorMethod) return;
    throw new Error(`[Server::${errorMethod}] can NOT be overrided`);
  }

  /**
   * purge cache
   *
   * @param {String} url
   * @CacheKeys cacheKeys
   */
  async purgeCache(url) {
    const cacheKeys = this.caches.digest
      .keys()
      .filter(key => url === key.substr(key.indexOf('/')));
    await Promise.all(
      cacheKeys.map(
        key => promise.autoCatch(this.memory.purgeItem(this.caches.digest.get(key)))
      )
    );
    return cacheKeys;
  }

  /**
   * handle server request
   *
   * @param {http.IncomingMessage} request
   * @param {http.ServerResponse} response
   * @memberof Server
   */
  async [PRIVATE.handleRequest](request, response) {
    const handler = new Handler(response);
    const incoming = new IncomingMessage(request);

    // todo - support POST admin command
    // if (incoming.method === 'POST' && incoming.url === '/report') {
    //   return handler.respond(200, this.report.debugFrame())
    // }

    const digest = this[PRIVATE.getRequestDigest](incoming);

    // purge cache
    if (incoming.method === 'DELETE') {
      const cacheKeys = await this.purgeCache(incoming.url);
      // 在 pm2 环境下给其他进程发送清理缓存消息
      if (pm2) {
        const ids = Array.from(this.pm2_ids);
        for (let i = 0; i < ids.length; i += 1) {
          pm2.sendDataToProcessId(ids[i], {
            data: {
              action: 'purge-cache',
              url: incoming.url,
            },
            topic: 'message'
          });
        }
      }
      return handler.respond(200, `${cacheKeys.join(',')} purged`);
    }

    // skip non GET requests
    if (incoming.method !== 'GET') {
      return handler.respond(403, 'not implemented');
    }

    // todo - support HEAD
    // todo - defend DoS according to report

    // this.report.recordAction(Report.ACTION.TOTAL)
    if (! this.mustSkipCache(incoming)) {
      const outgoing = await promise.autoCatch(this.memory.fetchItem(digest));
      if (outgoing) {
        this[PRIVATE.useCachePolicy](incoming, outgoing);
        if (! this[PRIVATE.willPurgeCache](outgoing)) {
          return handler.respondRaw(outgoing);
        }
        await promise.autoCatch(this.memory.purgeItem(digest));
      }
    }

    // todo - use getOptions
    let thread = this.worker.getThread(digest);
    if (! thread) {
      const upstream = this.inferUpstream(incoming);
      if (! upstream) {
        return handler.respond(500, 'upstream not provided');
      }
      thread = this.worker.createThread(digest, upstream);
    }
    if (this.mustWaitAgent(incoming)) {
      thread.enqueue((outgoing) => {
        if (this[PRIVATE.verifyResponse](incoming, outgoing)) {
          this[PRIVATE.useCachePolicy](incoming, outgoing);
        }
        handler.respondRaw(outgoing);
      });
    } else {
      handler.respond(404);
    }
    thread.execute(10 * 1000, (outgoing) => {
      this.worker.removeThread(digest);
      // write cache memory
      if (this[PRIVATE.verifyResponse](incoming, outgoing)) {
        promise.autoCatch(this.memory.writeItem(digest, outgoing));
      }
    });
  }

  /**
   * use cache policy
   *
   * @param {IncomingMessage} incoming
   * @param {OutgoingMessage} outgoing
   * @memberof Server
   */
  [PRIVATE.useCachePolicy](incoming, outgoing) {
    const cachePolicy = this.getCachePolicy(incoming, outgoing);
    if (cachePolicy) {
      outgoing.setCacheHeaders(cachePolicy.time, cachePolicy.pragma);
    }
  }

  /**
   * get incoming message digest
   * only consider URL
   *
   * @param {IncomingMessage} incoming message
   * @returns {String} digest
   * @memberof Server
   */
  [PRIVATE.getRequestDigest](incoming) {
    this.reviseRequest(incoming);
    const source = incoming.headers.host + incoming.url;
    const cache = this.caches.digest;
    if (! cache.has(source)) {
      // cache this slow operation
      const hasher = crypto.createHash('md5');
      hasher.update(source);
      cache.set(source, hasher.digest('hex'));
    }
    return cache.get(source);
  }

  /**
   * verify response status
   *
   * @param {IncomingMessage} incoming
   * @param {OutgoingMessage} outgoing
   * @memberof Server
   */
  [PRIVATE.verifyResponse](incoming, outgoing) {
    return ! this.willEmitError(incoming, outgoing)
      && outgoing.status > 199 && outgoing.status < 400;
  }

  /**
   * indicate if cache will be purged
   *
   * @param {OutgoingMessage} outgoing
   * @returns {Boolean}
   * @memberof Server
   */
  [PRIVATE.willPurgeCache](outgoing) {
    const { expires } = outgoing.headers;
    const now = new Date();
    if (expires) {
      return now - new Date(expires) > 0;
    }
    return false;
  }

  /* protect methods */

  /**
   * start server and listen
   *
   * @param {any} port
   * @param {any} callback
   * @memberof Server
   */
  listen(port, callback) {
    this.server.listen(port, callback);
  }

  /* virtual methods */

  reviseRequest(message) {
    return message;
  }

  getCachePolicy(message) {
    return {
      time: 60 * 60,
      pragma: 'public'
    };
  }

  /**
   * get incoming message for upstream
   *
   * @param {IncomingMessage} message
   * @returns {IncomingMessage} upstream
   * @memberof Server
   */
  inferUpstream(message) {
    return message;
  }

  mustSkipCache(message) {
    return false;
  }

  mustWaitAgent(message) {
    return true;
  }

  willEmitError(message) {
    return false;
  }
}

module.exports = Server;
<commit_msg>1.6.1, = fix sendDataToProccessId api<commit_after>/* eslint global-require: 0 */
/* eslint import/no-extraneous-dependencies: 0 */

const http = require('http');
const crypto = require('crypto');
const LRUCache = require('lru-cache');
const Memory = require('./Memory.js');
const Worker = require('./Worker.js');
const Logger = require('./Logger.js');
const Handler = require('./kernel/Handler.js');
const promise = require('./kernel/promise.js');
const { IncomingMessage } = require('./kernel/Message.js');

let pm2 = null;
try {
  pm2 = require('pm2');
} catch (err) {
  console.err(err);
  pm2 = null;
}

const PRIVATE_METHODS = [
  'checkMethod', 'handleRequest', 'useCachePolicy',
  'getRequestDigest', 'verifyResponse', 'willPurgeCache'
];
const PROTECT_METHODS = ['listen'];
const VIRTUAL_METHODS = [
  'reviseRequest', 'getCachePolicy',
  'mustSkipCache', 'mustWaitAgent', 'willEmitError',
  'inferUpstream'
];

const PRIVATE = {};
PRIVATE_METHODS.forEach((key) => {
  PRIVATE[key] = Symbol(key);
});

class Server {
  /**
   * constructor
   * @memberof Server
   */
  constructor(options) {
    this.server = http.createServer(this[PRIVATE.handleRequest].bind(this));
    this.caches = {
      digest: new LRUCache({ max: 1000, maxAge: 1e3 * 3600 * 24 })
    };
    this.memory = new Memory(options);
    this.worker = new Worker();
    this.pm2_ids = [];
    this.logger = Logger.getInstance();
    this[PRIVATE.checkMethod]();

    // 若当前使用 pm2 环境，则增加监听器，保存 pm2 id 集合
    if (pm2) {
      pm2.list((error, list) => {
        if (error) {
          return console.error(error);
        }
        if (list.length) {
          this.pm2_ids = list.map(item => item.pm2_env.pm_id);
          process.on('message', ({ data = {} }) => {
            const { action, url } = data;
            if (!action) {
              return null;
            }
            // type=purge-cache 则干掉当前进程缓存
            if (action === 'purge-cache') {
              this.purgeCache(url);
            }
          });
        }
      });
    }
  }

  /**
   * check public method
   *
   * @memberof Server
   */
  [PRIVATE.checkMethod]() {
    const proto = Server.prototype;
    VIRTUAL_METHODS.forEach((key) => {
      Object.defineProperty(
        proto, key, { configurable: false, writable: false }
      );
    });
    const errorMethod = PROTECT_METHODS.find(key => this[key] !== proto[key]);
    if (! errorMethod) return;
    throw new Error(`[Server::${errorMethod}] can NOT be overrided`);
  }

  /**
   * purge cache
   *
   * @param {String} url
   * @return cacheKeys
   */
  async purgeCache(url) {
    const cacheKeys = this.caches.digest
      .keys()
      .filter(key => url === key.substr(key.indexOf('/')));
    await Promise.all(
      cacheKeys.map(
        key => promise.autoCatch(this.memory.purgeItem(this.caches.digest.get(key)))
      )
    );
    return cacheKeys;
  }

  /**
   * handle server request
   *
   * @param {http.IncomingMessage} request
   * @param {http.ServerResponse} response
   * @memberof Server
   */
  async [PRIVATE.handleRequest](request, response) {
    const handler = new Handler(response);
    const incoming = new IncomingMessage(request);

    // todo - support POST admin command
    // if (incoming.method === 'POST' && incoming.url === '/report') {
    //   return handler.respond(200, this.report.debugFrame())
    // }

    const digest = this[PRIVATE.getRequestDigest](incoming);

    // purge cache
    if (incoming.method === 'DELETE') {
      const cacheKeys = await this.purgeCache(incoming.url);
      // 在 pm2 环境下给其他进程发送清理缓存消息
      if (pm2) {
        const ids = this.pm2_ids;
        for (let i = 0; i < ids.length; i += 1) {
          if (Number(process.env.pm_id) !== Number(ids[i])) {
            pm2.sendDataToProcessId({
              id: ids[i],
              data: {
                action: 'purge-cache',
                url: incoming.url,
              },
              topic: 'message',
            }, function (error) {
              if (error) {
                console.log('send pm2 IPC message error => ', error);
              }
            });
          }
        }
      }
      return handler.respond(200, `${cacheKeys.join(',')} purged`);
    }

    // skip non GET requests
    if (incoming.method !== 'GET') {
      return handler.respond(403, 'not implemented');
    }

    // todo - support HEAD
    // todo - defend DoS according to report

    // this.report.recordAction(Report.ACTION.TOTAL)
    if (! this.mustSkipCache(incoming)) {
      const outgoing = await promise.autoCatch(this.memory.fetchItem(digest));
      if (outgoing) {
        this[PRIVATE.useCachePolicy](incoming, outgoing);
        if (! this[PRIVATE.willPurgeCache](outgoing)) {
          return handler.respondRaw(outgoing);
        }
        await promise.autoCatch(this.memory.purgeItem(digest));
      }
    }

    // todo - use getOptions
    let thread = this.worker.getThread(digest);
    if (! thread) {
      const upstream = this.inferUpstream(incoming);
      if (! upstream) {
        return handler.respond(500, 'upstream not provided');
      }
      thread = this.worker.createThread(digest, upstream);
    }
    if (this.mustWaitAgent(incoming)) {
      thread.enqueue((outgoing) => {
        if (this[PRIVATE.verifyResponse](incoming, outgoing)) {
          this[PRIVATE.useCachePolicy](incoming, outgoing);
        }
        handler.respondRaw(outgoing);
      });
    } else {
      handler.respond(404);
    }
    thread.execute(10 * 1000, (outgoing) => {
      this.worker.removeThread(digest);
      // write cache memory
      if (this[PRIVATE.verifyResponse](incoming, outgoing)) {
        promise.autoCatch(this.memory.writeItem(digest, outgoing));
      }
    });
  }

  /**
   * use cache policy
   *
   * @param {IncomingMessage} incoming
   * @param {OutgoingMessage} outgoing
   * @memberof Server
   */
  [PRIVATE.useCachePolicy](incoming, outgoing) {
    const cachePolicy = this.getCachePolicy(incoming, outgoing);
    if (cachePolicy) {
      outgoing.setCacheHeaders(cachePolicy.time, cachePolicy.pragma);
    }
  }

  /**
   * get incoming message digest
   * only consider URL
   *
   * @param {IncomingMessage} incoming message
   * @returns {String} digest
   * @memberof Server
   */
  [PRIVATE.getRequestDigest](incoming) {
    this.reviseRequest(incoming);
    const source = incoming.headers.host + incoming.url;
    const cache = this.caches.digest;
    if (! cache.has(source)) {
      // cache this slow operation
      const hasher = crypto.createHash('md5');
      hasher.update(source);
      cache.set(source, hasher.digest('hex'));
    }
    return cache.get(source);
  }

  /**
   * verify response status
   *
   * @param {IncomingMessage} incoming
   * @param {OutgoingMessage} outgoing
   * @memberof Server
   */
  [PRIVATE.verifyResponse](incoming, outgoing) {
    return ! this.willEmitError(incoming, outgoing)
      && outgoing.status > 199 && outgoing.status < 400;
  }

  /**
   * indicate if cache will be purged
   *
   * @param {OutgoingMessage} outgoing
   * @returns {Boolean}
   * @memberof Server
   */
  [PRIVATE.willPurgeCache](outgoing) {
    const { expires } = outgoing.headers;
    const now = new Date();
    if (expires) {
      return now - new Date(expires) > 0;
    }
    return false;
  }

  /* protect methods */

  /**
   * start server and listen
   *
   * @param {any} port
   * @param {any} callback
   * @memberof Server
   */
  listen(port, callback) {
    this.server.listen(port, callback);
  }

  /* virtual methods */

  reviseRequest(message) {
    return message;
  }

  getCachePolicy(message) {
    return {
      time: 60 * 60,
      pragma: 'public'
    };
  }

  /**
   * get incoming message for upstream
   *
   * @param {IncomingMessage} message
   * @returns {IncomingMessage} upstream
   * @memberof Server
   */
  inferUpstream(message) {
    return message;
  }

  mustSkipCache(message) {
    return false;
  }

  mustWaitAgent(message) {
    return true;
  }

  willEmitError(message) {
    return false;
  }
}

module.exports = Server;
<|endoftext|>


<commit_before>"use strict";

var Stream = require('./lib/stream');
var Instance = require('./lib/instance');
var parseEvent = require('./lib/parse');
var CoreInst;
var requiredAdapterMethods = ['module', 'composition', 'net'];

// set adapter api object (singleton)
module.exports = function (adapter) {

    if (CoreInst) {
        return CoreInst;
    }

    // check if adapter has all the required methods.
    requiredAdapterMethods.forEach(function (key) {
        if (typeof adapter[key] !== 'function') {
            throw new Error('Flow: "adapter.' + key + '" is not a function.');
        }
    });

    // create core instance
    CoreInst = factory(adapter);

    CoreInst.load = Instance.factory;
    CoreInst.factory = factory;
    CoreInst._name = '@';
    CoreInst._roles = {'*': true};
    CoreInst._ready = true;
    CoreInst._events = {};
    CoreInst._reset = Instance.reset;

    // save core instance in instances cache
    Instance.instances[CoreInst._name] = CoreInst;

    // init event parser with core instance
    parseEvent = parseEvent(CoreInst);

    return CoreInst;
};

// flow factory
function factory (object) {

    var clone = Object.create(object);
    clone.flow = emit;
    clone._flow = clone._flow || {};
    return clone;
}

function emit (eventName,  options, callback) {

    /*
        this.flow(flowEvent, {
            emit: 'event',
            to: 'instance',
            end: function () {}
            session: {}
        });
    */

    options = typeof options === 'function' ? {end: options} : options || {};
    options.emit = eventName;

    if (typeof options.end === 'function') {
        callback = options.end;
        delete options.end;
    }

    // return if event name is missing
    if (!options.emit) {
        throw new Error('Flow call without event name.');
    }

    options.session = options.session || {};
    options.to = options.to || this._name;

    // if request handler request call like a stream handler
    if (options.net) {
        var netStream = CoreInst.net(this, options);

        // flow callback
        if (typeof callback === 'function') {
            concatStream(netStream.o, callback);
        }

        return netStream;
    }

    // create new event stream
    var initial = {
        i: Stream.Pass(),
        o: Stream.Pass()
    };
    initial.i.cork();

    // load or get instance
    CoreInst.load(options.to, options.session, function (err, instance) {

        if (err) {
            return initial.o.emit('error', err);
        }

        // link event handler to event stream
        getEvent(instance, options, function (err, flowEvent) {

            if (err) {
                return initial.o.emit('error', err);
            }

            // setup sub streams (sequences)
            if (flowEvent.d) {
                linkStreams(instance, initial, flowEvent, options);
                initial.i.emit('sequence');
            }

            // end handler
            if (flowEvent.e) {
                initial.o.on('end', function () {
                    if (!this._errEmit) {
                        instance.flow(flowEvent.e).end(true);
                    }
                });
            }

            // flow callback
            if (typeof callback === 'function') {
                concatStream(initial.o, callback);
            }

            initial.i.uncork();
        });
    });

    return initial;
};

function concatStream (stream, callback) {
    var body = '';
    var error;

    stream.on('data', function (chunk) {
        body += chunk;
    })
    .on('error', function (err) {
        error = err;
        body = undefined;
    })
    .on('end', function () {
        callback(error, body);
    });
}

function linkStreams (instance, initial, flowEvent, options) {

    var input = initial.i;
    var first = true;

    // TODO make it possible to emit error events on external instances
    var errEvent = flowEvent.r ? instance.flow(flowEvent.r) : undefined;
    var handleError = function (err) {

        // write error to error event
        if (errEvent) {
            errEvent.i.end(err);
        }

        // TODO option to keep stream alive
        initial.i.end();
        delete instance._events[options.emit];

        // write error to origin ouput stream
        initial.o.emit('error', err);
        //console.error(err);
    };

    // emit error event on initial error
    initial.i.on('error', handleError);

    flowEvent.d.forEach(function (section, index) {

        // create a new sub-stream to call handlers
        var output = Stream.Event(options);
        output.seq = section[0];
        output.on('error', handleError);

        // tee streams
        if (section[1] && section[1][0] === '|' || first) {
            input.pipe(output);
            first = false;
        }

        // pipe to next stream
        if (section[1]) {

            var shOptions = Object.assign({}, section[1][1][1]);
            shOptions.arg = options;
            shOptions.session = options.session || {};

            if (typeof section[1][1][0] === 'string') {
                var fes = instance.flow(section[1][1][0], shOptions);
                input.pipe(fes.i);
                fes.o.on('error', output.emit.bind(output, 'error'));
                fes.o.pipe(output);

            } else if (typeof section[1][1][0] === 'function') {
                section[1][1][0].call(
                    section[1][1][2],
                    {i: input, o: output},
                    shOptions,
                    handleError
                );
            }
        }

        // event stream sequence handler is the new input
        input = output;
    });

    input.pipe(initial.o);
}

var cbBuffer = {};
function getEvent (instance, options, callback) {

    // return cached event
    if (instance._events[options.emit]) {
        return callback(null, instance._events[options.emit]);
    }

    // buffer callback
    if (cbBuffer[options.emit]) {
        return cbBuffer[options.emit].push(callback);
    }

    // check if event is configured
    if (!instance._flow[options.emit]) {
        return callback(new Error('Flow.getEvent: Event "' + options.emit + '" not found on instance "' + instance._name+ '".'));
    }

    // collect all handlers for specific flow event
    cbBuffer[options.emit] = [];
    parseEvent(instance, options, function (err, event) {

        if (!err) {

            // cache event
            instance._events[options.emit] = event;
        }

        callback(err, event);

        // call buffered callbacks
        if (cbBuffer[options.emit]) {
            cbBuffer[options.emit].forEach(function (cb) {
                cb(err, event);
            });
            delete cbBuffer[options.emit];
        }
    });
}
<commit_msg>fix end and error events<commit_after>"use strict";

var Stream = require('./lib/stream');
var Instance = require('./lib/instance');
var parseEvent = require('./lib/parse');
var CoreInst;
var requiredAdapterMethods = ['module', 'composition', 'net'];

// set adapter api object (singleton)
module.exports = function (adapter) {

    if (CoreInst) {
        return CoreInst;
    }

    // check if adapter has all the required methods.
    requiredAdapterMethods.forEach(function (key) {
        if (typeof adapter[key] !== 'function') {
            throw new Error('Flow: "adapter.' + key + '" is not a function.');
        }
    });

    // create core instance
    CoreInst = factory(adapter);

    CoreInst.load = Instance.factory;
    CoreInst.factory = factory;
    CoreInst._name = '@';
    CoreInst._roles = {'*': true};
    CoreInst._ready = true;
    CoreInst._events = {};
    CoreInst._reset = Instance.reset;

    // save core instance in instances cache
    Instance.instances[CoreInst._name] = CoreInst;

    // init event parser with core instance
    parseEvent = parseEvent(CoreInst);

    return CoreInst;
};

// flow factory
function factory (object) {

    var clone = Object.create(object);
    clone.flow = emit;
    clone._flow = clone._flow || {};
    return clone;
}

function emit (eventName,  options, callback) {

    /*
        this.flow(flowEvent, {
            emit: 'event',
            to: 'instance',
            end: function () {}
            session: {}
        });
    */

    options = typeof options === 'function' ? {end: options} : options || {};
    options.emit = eventName;

    if (typeof options.end === 'function') {
        callback = options.end;
        delete options.end;
    }

    // return if event name is missing
    if (!options.emit) {
        throw new Error('Flow call without event name.');
    }

    options.session = options.session || {};
    options.to = options.to || this._name;

    // if request handler request call like a stream handler
    if (options.net) {
        var netStream = CoreInst.net(this, options);

        // flow callback
        if (typeof callback === 'function') {
            concatStream(netStream.o, callback);
        }

        return netStream;
    }

    // create new event stream
    var initial = {
        i: Stream.Pass(),
        o: Stream.Pass()
    };
    initial.i.cork();

    // load or get instance
    CoreInst.load(options.to, options.session, function (err, instance) {

        if (err) {
            return initial.o.emit('error', err);
        }

        // link event handler to event stream
        getEvent(instance, options, function (err, flowEvent) {

            if (err) {
                return initial.o.emit('error', err);
            }

            // setup sub streams (sequences)
            if (flowEvent.d) {
                linkStreams(instance, initial, flowEvent, options);
            }

            // end handler
            if (flowEvent.e) {
                initial.o.on('end', function () {
                    if (!initial.i._errEH) {
                        instance.flow(flowEvent.e[0], flowEvent.e[1]).i.on('ready', function () {
                            endEvent.i.end(options);
                        });
                    }
                });
            }

            // flow callback
            if (typeof callback === 'function') {
                concatStream(initial.o, callback);
            }

            initial.i.emit('ready');
            initial.ready = true;
            initial.i.uncork();
        });
    });

    return initial;
};

function concatStream (stream, callback) {
    var body = '';
    var error;

    stream.on('data', function (chunk) {
        body += chunk;
    })
    .on('error', function (err) {
        error = err;
        body = undefined;
    })
    .on('end', function () {
        callback(error, body);
    });
}

function linkStreams (instance, initial, flowEvent, options) {

    var input = initial.i;
    var first = true;
    var errEvent;
    var handleError = function (err) {

        // emit error on origin ouput stream
        initial.o.emit('error', err);

        // write error to error event
        if (flowEvent.r) {
            errEvent = errEvent || instance.flow(flowEvent.r[0], flowEvent.r[1]);
            if (errEvent.ready) {
                errEvent.i.write(err);
            } else {
                errEvent.i.on('ready', function () {
                    errEvent.i.write(err);
                });
            }

            // end error stream on initial end
            initial.o.on('end', errEvent.i.end.bind(errEvent));
        }

        // TODO keep streams piped
        // https://github.com/nodejs/node/issues/3045#issuecomment-142975237
    };

    // emit error event on initial error
    initial.i.on('error', handleError);

    flowEvent.d.forEach(function (section, index) {

        // create a new sub-stream to call handlers
        var output = Stream.Event(options);
        output.seq = section[0];
        output.on('error', handleError);

        // tee streams
        if (section[1] && section[1][0] === '|' || first) {
            input.pipe(output);
            first = false;
        }

        // pipe to next stream
        if (section[1]) {

            var shOptions = Object.assign({}, section[1][1][1]);
            shOptions.arg = options;
            shOptions.session = options.session || {};

            if (typeof section[1][1][0] === 'string') {
                var fes = instance.flow(section[1][1][0], shOptions);
                input.pipe(fes.i);
                fes.o.on('error', output.emit.bind(output, 'error'));
                fes.o.pipe(output);

            } else if (typeof section[1][1][0] === 'function') {
                section[1][1][0].call(
                    section[1][1][2],
                    {i: input, o: output},
                    shOptions,
                    handleError
                );
            }
        }

        // event stream sequence handler is the new input
        input = output;
    });

    input.pipe(initial.o);
}

var cbBuffer = {};
function getEvent (instance, options, callback) {

    // return cached event
    if (instance._events[options.emit]) {
        return callback(null, instance._events[options.emit]);
    }

    // buffer callback
    if (cbBuffer[options.emit]) {
        return cbBuffer[options.emit].push(callback);
    }

    // check if event is configured
    if (!instance._flow[options.emit]) {
        return callback(new Error('Flow.getEvent: Event "' + options.emit + '" not found on instance "' + instance._name+ '".'));
    }

    // collect all handlers for specific flow event
    cbBuffer[options.emit] = [];
    parseEvent(instance, options, function (err, event) {

        if (!err) {

            // cache event
            instance._events[options.emit] = event;
        }

        callback(err, event);

        // call buffered callbacks
        if (cbBuffer[options.emit]) {
            cbBuffer[options.emit].forEach(function (cb) {
                cb(err, event);
            });
            delete cbBuffer[options.emit];
        }
    });
}
<|endoftext|>


<commit_before>'use strict';

var util = require('util');

var LegoContainer = require('./lib/lego-container');
var LegoTransform = require('./lib/lego-transform');
var LegoFilter = require('./lib/lego-filter');

function Legos() {
  LegoContainer.call(this);

  this.negate = false;
}

Legos.prototype = {
  get when() {
    this.negate = false;

    var self = this;

    var when = function when(filter) {
      self.push(new LegoFilter(filter, self.negate));
      return self;
    }

    when.not = function not() {
      self.negate = true;
      return when.apply(self, arguments);
    };

    return when;
  },

  get filter() {
    return this.when;
  },

  transform: function transform(transformation) {
    this.push(new LegoTransform(transformation));
    return this;
  },

  get and() {
    return this;
  }
};

for (var k in LegoContainer.prototype) {
  Legos.prototype[k] = LegoContainer.prototype[k];
}

module.exports = {
  get allow() {
    return new Legos();
  },

  get pass() {
    return new Legos();
  },

  get deny() {
    var lego = new Legos();
    lego.negate = true;
    return lego;
  }
};
<commit_msg>exposed internal lego classes<commit_after>'use strict';

var util = require('util');

var Lego = require('./lib/lego');
var LegoContainer = require('./lib/lego-container');
var LegoTransform = require('./lib/lego-transform');
var LegoFilter = require('./lib/lego-filter');

function Legos() {
  LegoContainer.call(this);

  this.negate = false;
}

Legos.prototype = {
  get when() {
    this.negate = false;

    var self = this;

    var when = function when(filter) {
      self.push(new LegoFilter(filter, self.negate));
      return self;
    }

    when.not = function not() {
      self.negate = true;
      return when.apply(self, arguments);
    };

    return when;
  },

  get filter() {
    return this.when;
  },

  transform: function transform(transformation) {
    this.push(new LegoTransform(transformation));
    return this;
  },

  get and() {
    return this;
  }
};

for (var k in LegoContainer.prototype) {
  Legos.prototype[k] = LegoContainer.prototype[k];
}

module.exports = {
  Lego: Lego,
  LegoTransform: LegoTransform,
  LegoFilter: LegoFilter,
  LegoContainer: LegoContainer,

  get allow() {
    return new Legos();
  },

  get pass() {
    return new Legos();
  },

  get deny() {
    var lego = new Legos();
    lego.negate = true;
    return lego;
  }
};
<|endoftext|>


<commit_before>/**
 *  Shapes
 *
 *  @copyright 2017, Kevin Lindsey
 */

if (typeof module !== "undefined") {
    var Point2D = require('kld-affine').Point2D,
        IntersectionArgs = require('./IntersectionArgs');
}

var Shapes = {};

/**
 *  quadraticBezier
 *
 *  @param {Number} p1x
 *  @param {Number} p1y
 *  @param {Number} p2x
 *  @param {Number} p2y
 *  @param {Number} p3x
 *  @param {Number} p3y
 *  @returns {IntersectionArgs}
 */
Shapes.quadraticBezier = function(p1x, p1y, p2x, p2y, p3x, p3y) {
    return new IntersectionArgs("Bezier2", [
        new Point2D(p1x, p1y),
        new Point2D(p2x, p2y),
        new Point2D(p3x, p3y)
    ]);
}

/**
 *  cubicBezier
 *
 *  @param {Number} p1x
 *  @param {Number} p1y
 *  @param {Number} p2x
 *  @param {Number} p2y
 *  @param {Number} p3x
 *  @param {Number} p3y
 *  @param {Number} p4x
 *  @param {Number} p4y
 *  @returns {IntersectionArgs}
 */
Shapes.cubicBezier = function(p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y) {
    return new IntersectionArgs("Bezier3", [
        new Point2D(p1x, p1y),
        new Point2D(p2x, p2y),
        new Point2D(p3x, p3y),
        new Point2D(p4x, p4y)
    ]);
}

/**
 *  circle
 *
 *  @param {Number} centerX
 *  @param {Number} centerY
 *  @param {Number} radius
 *  @returns {IntersectionArgs}
 */
Shapes.circle = function(centerX, centerY, radius) {
    return new IntersectionArgs("Circle", [
        new Point2D(centerX, centerY),
        radius
    ]);
}

/**
 *  ellipse
 *
 *  @param {Number} centerX
 *  @param {Number} centerY
 *  @param {Number} radiusX
 *  @param {Number} radiusY
 *  @returns {IntersectionArgs}
 */
Shapes.ellipse = function(centerX, centerY, radiusX, radiusY) {
    return new IntersectionArgs("Ellipse", [
        new Point2D(centerX, centerY),
        radiusX,
        radiusY
    ]);
}

/**
 *  line
 *
 *  @param {Number} p1x
 *  @param {Number} p1y
 *  @param {Number} p2x
 *  @param {Number} p2y
 *  @returns {IntersectionArgs}
 */
Shapes.line = function(p1x, p1y, p2x, p2y) {
    return new IntersectionArgs("Line", [
        new Point2D(p1x, p1y),
        new Point2D(p2x, p2y)
    ]);
}

/**
 *  path
 *
 *  @param {Array<Shapes}> segments
 *  @returns {IntersectionArgs}
 */
Shapes.path = function(segments) {
    return new IntersectionArgs("Path", [segments]);
};

/**
 *  polygon
 *
 *  @param {Array<Number}> coords
 *  @returns {IntersectionArgs}
 */
Shapes.polygon = function(coords) {
    var points = [];

    for (var i = 0; i < coords.length ; i += 2) {
        points.push(new Point2D(coords[i], coords[i+1]));
    }

    return new IntersectionArgs("Polygon", [points]);
}

/**
 *  polyline
 *
 *  @param {Array<Number}> coords
 *  @returns {IntersectionArgs}
 */
Shapes.polyline = function(coords) {
    var points = [];

    for (var i = 0; i < coords.length ; i += 2) {
        points.push(new Point2D(coords[i], coords[i+1]));
    }

    return new IntersectionArgs("Polyline", [points]);
}

/**
 *  rectangle
 *
 *  @param {Number} x
 *  @param {Number} y
 *  @param {Number} width
 *  @param {Number} height
 *  @returns {IntersectionArgs}
 */
Shapes.rectangle = function(x, y, width, height) {
    return new IntersectionArgs("Rectangle", [
        new Point2D(x, y),
        new Point2D(x + width, y + height)
    ]);
}

if (typeof module !== "undefined") {
    module.exports = Shapes;
}
<commit_msg>Do not wrap segments in array<commit_after>/**
 *  Shapes
 *
 *  @copyright 2017, Kevin Lindsey
 */

if (typeof module !== "undefined") {
    var Point2D = require('kld-affine').Point2D,
        IntersectionArgs = require('./IntersectionArgs');
}

var Shapes = {};

/**
 *  quadraticBezier
 *
 *  @param {Number} p1x
 *  @param {Number} p1y
 *  @param {Number} p2x
 *  @param {Number} p2y
 *  @param {Number} p3x
 *  @param {Number} p3y
 *  @returns {IntersectionArgs}
 */
Shapes.quadraticBezier = function(p1x, p1y, p2x, p2y, p3x, p3y) {
    return new IntersectionArgs("Bezier2", [
        new Point2D(p1x, p1y),
        new Point2D(p2x, p2y),
        new Point2D(p3x, p3y)
    ]);
}

/**
 *  cubicBezier
 *
 *  @param {Number} p1x
 *  @param {Number} p1y
 *  @param {Number} p2x
 *  @param {Number} p2y
 *  @param {Number} p3x
 *  @param {Number} p3y
 *  @param {Number} p4x
 *  @param {Number} p4y
 *  @returns {IntersectionArgs}
 */
Shapes.cubicBezier = function(p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y) {
    return new IntersectionArgs("Bezier3", [
        new Point2D(p1x, p1y),
        new Point2D(p2x, p2y),
        new Point2D(p3x, p3y),
        new Point2D(p4x, p4y)
    ]);
}

/**
 *  circle
 *
 *  @param {Number} centerX
 *  @param {Number} centerY
 *  @param {Number} radius
 *  @returns {IntersectionArgs}
 */
Shapes.circle = function(centerX, centerY, radius) {
    return new IntersectionArgs("Circle", [
        new Point2D(centerX, centerY),
        radius
    ]);
}

/**
 *  ellipse
 *
 *  @param {Number} centerX
 *  @param {Number} centerY
 *  @param {Number} radiusX
 *  @param {Number} radiusY
 *  @returns {IntersectionArgs}
 */
Shapes.ellipse = function(centerX, centerY, radiusX, radiusY) {
    return new IntersectionArgs("Ellipse", [
        new Point2D(centerX, centerY),
        radiusX,
        radiusY
    ]);
}

/**
 *  line
 *
 *  @param {Number} p1x
 *  @param {Number} p1y
 *  @param {Number} p2x
 *  @param {Number} p2y
 *  @returns {IntersectionArgs}
 */
Shapes.line = function(p1x, p1y, p2x, p2y) {
    return new IntersectionArgs("Line", [
        new Point2D(p1x, p1y),
        new Point2D(p2x, p2y)
    ]);
}

/**
 *  path
 *
 *  @param {Array<Shapes}> segments
 *  @returns {IntersectionArgs}
 */
Shapes.path = function(segments) {
    return new IntersectionArgs("Path", segments);
};

/**
 *  polygon
 *
 *  @param {Array<Number}> coords
 *  @returns {IntersectionArgs}
 */
Shapes.polygon = function(coords) {
    var points = [];

    for (var i = 0; i < coords.length ; i += 2) {
        points.push(new Point2D(coords[i], coords[i+1]));
    }

    return new IntersectionArgs("Polygon", [points]);
}

/**
 *  polyline
 *
 *  @param {Array<Number}> coords
 *  @returns {IntersectionArgs}
 */
Shapes.polyline = function(coords) {
    var points = [];

    for (var i = 0; i < coords.length ; i += 2) {
        points.push(new Point2D(coords[i], coords[i+1]));
    }

    return new IntersectionArgs("Polyline", [points]);
}

/**
 *  rectangle
 *
 *  @param {Number} x
 *  @param {Number} y
 *  @param {Number} width
 *  @param {Number} height
 *  @returns {IntersectionArgs}
 */
Shapes.rectangle = function(x, y, width, height) {
    return new IntersectionArgs("Rectangle", [
        new Point2D(x, y),
        new Point2D(x + width, y + height)
    ]);
}

if (typeof module !== "undefined") {
    module.exports = Shapes;
}
<|endoftext|>