<commit_before>/*
 * Copyright 2017 Teppo Kurki <teppo.kurki@iki.fi>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

const fs = require('fs')
const page = require('./page')
const debug = require('debug')('signalk-server:serverroutes')
const path = require('path')
const _ = require('lodash')
const config = require('./config/config')

const defaultSecurityStrategy = '@signalk/sk-simple-token-security'

module.exports = function(app, saveSecurityConfig, getSecurityConfig) {
  var securityWasEnabled

  app.get('/', (req, res) => {
    let { result, footer } = page(__dirname + '/index.html', '
')

    if (app.securityStrategy) {
      result +=
        '
Restart
'
      if (req.userLoggedIn) {
        result +=
          '
Logout
'
      }
    }

    res.send(result + footer)
  })

  app.get('/apps', (req, res, next) => {
    var html = fs.readFileSync(__dirname + '/appindex.html', {
      encoding: 'utf8'
    })
    var insertionIndex = html.indexOf('
')
    var sliceToInsertion = html.slice(0, insertionIndex)
    var sliceToEnd = html.slice(insertionIndex)

    var result = sliceToInsertion
    result += '
'
    result += app.webapps.reduce(function(result, componentInfo) {
      result += '
'
      result +=
        '' +
        componentInfo.name +
        ' '
      result += componentInfo.description
      result += '
\n'
      return result
    }, '')
    result += '
'
    result += sliceToEnd
    res.send(result)
  })

  app.get('/restart', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowRestart &&
      app.securityStrategy.allowRestart(req)
    ) {
      res.send('Restarting...')
      setTimeout(function() {
        process.exit(0)
      }, 2000)
    } else {
      res.status(401).json('Restart not allowed')
    }
  })

  app.get('/loginStatus', (req, res, next) => {
    let result = {
      status: 'notLoggedIn',
      readOnlyAccess: false,
      authenticationRequired: false,
      securityWasEnabled: !_.isUndefined(securityWasEnabled)
    }

    if (app.securityStrategy) {
      result = app.securityStrategy.getLoginStatus(req)
    }

    res.header('Cache-Control', 'no-cache, no-store, must-revalidate')
    res.header('Pragma', 'no-cache')
    res.header('Expires', 0)
    res.json(result)
  })

  app.get('/security/config', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowConfigure &&
      app.securityStrategy.allowConfigure(req)
    ) {
      var config = getSecurityConfig(app)
      res.json(app.securityStrategy.getConfig(config))
    } else {
      res.status(401).json('Security config not allowed')
    }
  })

  app.put('/security/config', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowConfigure &&
      app.securityStrategy.allowConfigure(req)
    ) {
      var config = getSecurityConfig(app)
      var config = app.securityStrategy.setConfig(config, req.body)
      saveSecurityConfig(app, config, err => {
        if (err) {
          console.log(err)
          res.status(500)
          res.send('Unable to save configuration change')
          return
        }
        res.send('security config saved')
      })
    } else {
      res.status(401).send('Security config not allowed')
    }
  })

  app.get('/security/users', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowConfigure &&
      app.securityStrategy.allowConfigure(req)
    ) {
      var config = getSecurityConfig(app)
      res.json(app.securityStrategy.getUsers(config))
    } else {
      res.status(401).json('Security config not allowed')
    }
  })

  app.put('/security/users/:id', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowConfigure &&
      app.securityStrategy.allowConfigure(req)
    ) {
      var config = getSecurityConfig(app)
      app.securityStrategy.updateUser(
        config,
        req.params.id,
        req.body,
        (err, config) => {
          if (err) {
            console.log(err)
            res.status(500)
            res.send('Unable to add user')
          } else if (config) {
            saveSecurityConfig(app, config, err => {
              if (err) {
                console.log(err)
                res.status(500)
                res.send('Unable to save configuration change')
                return
              }
              res.send('User updated')
            })
          } else {
            res.send('User updated')
          }
        }
      )
    } else {
      res.status(401).json('security config not allowed')
    }
  })

  app.post('/security/users/:id', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowConfigure &&
      app.securityStrategy.allowConfigure(req)
    ) {
      var config = getSecurityConfig(app)
      var user = req.body
      user.userId = req.params.id
      app.securityStrategy.addUser(config, user, (err, config) => {
        if (err) {
          console.log(err)
          res.status(500)
          res.send('Unable to add user')
        } else if (config) {
          saveSecurityConfig(app, config, err => {
            if (err) {
              console.log(err)
              res.status(500)
              res.send('Unable to save configuration change')
              return
            }
            res.send('User added')
          })
        } else {
          res.send('User added')
        }
      })
    } else {
      res.status(401).json('Security config not allowed')
    }
  })

  app.put('/security/user/:username/password', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowConfigure &&
      app.securityStrategy.allowConfigure(req)
    ) {
      var config = getSecurityConfig(app)
      app.securityStrategy.setPassword(
        config,
        req.params.username,
        req.body,
        (err, config) => {
          if (err) {
            console.log(err)
            res.status(500)
            res.send(err)
            res.send('Unable to change password')
            return
          }
          if (config) {
            saveSecurityConfig(app, config, err => {
              if (err) {
                console.log(err)
                res.status(500)
                res.send('Unable to save configuration change')
                return
              }
              res.send('Password changed')
            })
          } else {
            res.send('Password changed')
          }
        }
      )
    } else {
      res.status(401).json('Security config not allowed')
    }
  })

  app.delete('/security/users/:username', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowConfigure &&
      app.securityStrategy.allowConfigure(req)
    ) {
      var config = getSecurityConfig(app)
      app.securityStrategy.deleteUser(
        config,
        req.params.username,
        (err, config) => {
          if (err) {
            console.log(err)
            res.status(500)
            res.send('Unable to delete user')
            return
          }
          if (config) {
            saveSecurityConfig(app, config, err => {
              if (err) {
                console.log(err)
                res.status(500)
                res.send('Unable to save configuration change')
                return
              }
              res.send('User deleted')
            })
          } else {
            res.send('User deleted')
          }
        }
      )
    } else {
      res.status(401).json('Security config not allowed')
    }
  })

  app.get('/security/token/:id/:expiration', (req, res, next) => {
    app.securityStrategy.generateToken(
      req,
      res,
      next,
      req.params.id,
      req.params.expiration
    )
  })

  app.get('/settings', (req, res, next) => {
    var settings = {
      interfaces: {},
      options: {
        ssl: app.config.settings.ssl,
        mdns: app.config.settings.mdns
      },
      loggingDirectory: app.config.settings.loggingDirectory || '/tmp',
      port: app.config.settings.port || 3000,
      sslport: app.config.settings.sslport || 3433
    }

    var availableInterfaces = require('./interfaces')
    _.forIn(availableInterfaces, function(interface, name) {
      settings.interfaces[name] =
        _.isUndefined(app.config.settings.interfaces) ||
        _.isUndefined(app.config.settings.interfaces[name]) ||
        app.config.settings.interfaces[name]
    })

    res.json(settings)
  })

  if (
    app.securityStrategy == null ||
    app.securityStrategy.getUsers().length == 0
  ) {
    app.post('/enableSecurity', (req, res, next) => {
      if (app.securityStrategy == null) {
        app.config.settings.security = { strategy: defaultSecurityStrategy }
        config.writeSettingsFile(app, app.config.settings, err => {
          if (err) {
            console.log(err)
            res.status(500).send('Unable to save to settings file')
          } else {
            var config = {}
            var securityStrategy = require(defaultSecurityStrategy)(
              app,
              config,
              saveSecurityConfig
            )
            addUser(req, res, securityStrategy, config)
          }
        })
      } else {
        addUser(req, res, app.securityStrategy)
      }
      securityWasEnabled = true

      function addUser(req, res, securityStrategy, config) {
        var config
        if (!config) {
          config = getSecurityConfig(app)
        }
        securityStrategy.addUser(config, req.body, (err, config) => {
          if (err) {
            console.log(err)
            res.status(500)
            res.send('Unable to add user')
          } else {
            saveSecurityConfig(app, config, err => {
              if (err) {
                console.log(err)
                res.status(500)
                res.send('Unable to save security configuration change')
              }
              res.send('Security enabled')
            })
          }
        })
      }
    })
  }

  app.put('/settings', (req, res, next) => {
    var settings = req.body

    _.forIn(settings.interfaces, (enabled, name) => {
      app.config.settings.interfaces[name] = enabled
    })

    if (!_.isUndefined(settings.options.mdns)) {
      app.config.settings.mdns = settings.options.mdns
    }

    if (!_.isUndefined(settings.options.ssl)) {
      app.config.settings.ssl = settings.options.ssl
    }

    if (!_.isUndefined(settings.port)) {
      app.config.settings.port = Number(settings.port)
    }

    if (!_.isUndefined(settings.sslport)) {
      app.config.settings.sslport = Number(settings.sslport)
    }

    if (!_.isUndefined(settings.loggingDirectory)) {
      app.config.settings.loggingDirectory = settings.loggingDirectory
    }

    config.writeSettingsFile(app, app.config.settings, err => {
      if (err) {
        res.status(500).send('Unable to save to settings file')
      } else {
        res.send('Settings changed')
      }
    })
  })

  app.get('/vessel', (req, res, next) => {
    if (!app.config.defaults) {
      app.config.defaults = {
        vessels: {
          self: {}
        }
      }
    }

    var self = app.config.defaults.vessels.self

    var json = {
      name: self.name,
      mmsi: self.mmsi,
      uuid: self.uuid,
      draft: _.get(self, 'design.draft.value.maximum'),
      length: _.get(self, 'design.length.value.overall'),
      beam: _.get(self, 'design.beam.value'),
      height: _.get(self, 'design.airHeight.value'),
      gpsFromBow: _.get(self, 'sensors.gps.fromBow.value'),
      gpsFromCenter: _.get(self, 'sensors.gps.fromCenter.value')
    }
    res.json(json)
  })

  app.put('/vessel', (req, res, next) => {
    var data

    try {
      data = config.readDefaultsFile(app)
    } catch (e) {
      if (e.code && e.code === 'ENOENT') {
        data = {}
      } else {
        console.log(e)
        res.status(500).send('Unable to read defaults file')
      }
    }

    var self = _.get(data, 'data.vessels.self')

    if (_.isUndefined(self)) {
      self = _.set(data, 'vessels.self', {})
    }

    var newVessel = req.body

    var current = app.config.defaults

    function set(path, value) {
      _.set(data.vessels.self, path, value)
      _.set(current, path, value)
    }

    if (newVessel.name) {
      set('name', newVessel.name)
    }
    if (newVessel.mmsi) {
      set('mmsi', newVessel.mmsi)
    }
    if (newVessel.uuid && !self.mmmsi) {
      set('uuid', newVessel.uuid)
    } else {
      delete self.uuid
    }
    if (newVessel.draft) {
      set('design.draft.value.maximum', Number(newVessel.draft))
    }
    if (newVessel.length) {
      set('design.length.value.overall', Number(newVessel.length))
    }
    if (newVessel.beam) {
      set('design.beam.value', Number(newVessel.beam))
    }
    if (newVessel.height) {
      set('design.airHeight.value', Number(newVessel.height))
    }
    if (newVessel.gpsFromBow) {
      set('sensors.gps.fromBow.value', Number(newVessel.gpsFromBow))
    }
    if (newVessel.gpsFromCenter) {
      set('sensors.gps.fromCenter.value', Number(newVessel.gpsFromCenter))
    }

    config.writeDefaultsFile(app, data, err => {
      if (err) {
        res.status(500).send('Unable to save to defaults file')
      } else {
        res.send('Vessel changed')
      }
    })
  })

  app.get('/availablePaths', (req, res, next) => {
    res.json(app.streambundle.getAvailablePaths())
  })
}
<commit_msg>feature: use PUT for restart<commit_after>/*
 * Copyright 2017 Teppo Kurki <teppo.kurki@iki.fi>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

const fs = require('fs')
const page = require('./page')
const debug = require('debug')('signalk-server:serverroutes')
const path = require('path')
const _ = require('lodash')
const config = require('./config/config')

const defaultSecurityStrategy = '@signalk/sk-simple-token-security'

module.exports = function(app, saveSecurityConfig, getSecurityConfig) {
  var securityWasEnabled

  app.get('/', (req, res) => {
    const { result, footer } = page(__dirname + '/index.html', '
')
    res.send(result + footer)
  })

  app.get('/apps', (req, res, next) => {
    var html = fs.readFileSync(__dirname + '/appindex.html', {
      encoding: 'utf8'
    })
    var insertionIndex = html.indexOf('
')
    var sliceToInsertion = html.slice(0, insertionIndex)
    var sliceToEnd = html.slice(insertionIndex)

    var result = sliceToInsertion
    result += '
'
    result += app.webapps.reduce(function(result, componentInfo) {
      result += '
'
      result +=
        '' +
        componentInfo.name +
        ' '
      result += componentInfo.description
      result += '
\n'
      return result
    }, '')
    result += '
'
    result += sliceToEnd
    res.send(result)
  })

  app.put('/restart', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowRestart &&
      app.securityStrategy.allowRestart(req)
    ) {
      res.send('Restarting...')
      setTimeout(function() {
        process.exit(0)
      }, 2000)
    } else {
      res.status(401).json('Restart not allowed')
    }
  })

  app.get('/loginStatus', (req, res, next) => {
    let result = {
      status: 'notLoggedIn',
      readOnlyAccess: false,
      authenticationRequired: false,
      securityWasEnabled: !_.isUndefined(securityWasEnabled)
    }

    if (app.securityStrategy) {
      result = app.securityStrategy.getLoginStatus(req)
    }

    res.header('Cache-Control', 'no-cache, no-store, must-revalidate')
    res.header('Pragma', 'no-cache')
    res.header('Expires', 0)
    res.json(result)
  })

  app.get('/security/config', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowConfigure &&
      app.securityStrategy.allowConfigure(req)
    ) {
      var config = getSecurityConfig(app)
      res.json(app.securityStrategy.getConfig(config))
    } else {
      res.status(401).json('Security config not allowed')
    }
  })

  app.put('/security/config', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowConfigure &&
      app.securityStrategy.allowConfigure(req)
    ) {
      var config = getSecurityConfig(app)
      var config = app.securityStrategy.setConfig(config, req.body)
      saveSecurityConfig(app, config, err => {
        if (err) {
          console.log(err)
          res.status(500)
          res.send('Unable to save configuration change')
          return
        }
        res.send('security config saved')
      })
    } else {
      res.status(401).send('Security config not allowed')
    }
  })

  app.get('/security/users', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowConfigure &&
      app.securityStrategy.allowConfigure(req)
    ) {
      var config = getSecurityConfig(app)
      res.json(app.securityStrategy.getUsers(config))
    } else {
      res.status(401).json('Security config not allowed')
    }
  })

  app.put('/security/users/:id', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowConfigure &&
      app.securityStrategy.allowConfigure(req)
    ) {
      var config = getSecurityConfig(app)
      app.securityStrategy.updateUser(
        config,
        req.params.id,
        req.body,
        (err, config) => {
          if (err) {
            console.log(err)
            res.status(500)
            res.send('Unable to add user')
          } else if (config) {
            saveSecurityConfig(app, config, err => {
              if (err) {
                console.log(err)
                res.status(500)
                res.send('Unable to save configuration change')
                return
              }
              res.send('User updated')
            })
          } else {
            res.send('User updated')
          }
        }
      )
    } else {
      res.status(401).json('security config not allowed')
    }
  })

  app.post('/security/users/:id', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowConfigure &&
      app.securityStrategy.allowConfigure(req)
    ) {
      var config = getSecurityConfig(app)
      var user = req.body
      user.userId = req.params.id
      app.securityStrategy.addUser(config, user, (err, config) => {
        if (err) {
          console.log(err)
          res.status(500)
          res.send('Unable to add user')
        } else if (config) {
          saveSecurityConfig(app, config, err => {
            if (err) {
              console.log(err)
              res.status(500)
              res.send('Unable to save configuration change')
              return
            }
            res.send('User added')
          })
        } else {
          res.send('User added')
        }
      })
    } else {
      res.status(401).json('Security config not allowed')
    }
  })

  app.put('/security/user/:username/password', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowConfigure &&
      app.securityStrategy.allowConfigure(req)
    ) {
      var config = getSecurityConfig(app)
      app.securityStrategy.setPassword(
        config,
        req.params.username,
        req.body,
        (err, config) => {
          if (err) {
            console.log(err)
            res.status(500)
            res.send(err)
            res.send('Unable to change password')
            return
          }
          if (config) {
            saveSecurityConfig(app, config, err => {
              if (err) {
                console.log(err)
                res.status(500)
                res.send('Unable to save configuration change')
                return
              }
              res.send('Password changed')
            })
          } else {
            res.send('Password changed')
          }
        }
      )
    } else {
      res.status(401).json('Security config not allowed')
    }
  })

  app.delete('/security/users/:username', (req, res, next) => {
    if (
      app.securityStrategy &&
      app.securityStrategy.allowConfigure &&
      app.securityStrategy.allowConfigure(req)
    ) {
      var config = getSecurityConfig(app)
      app.securityStrategy.deleteUser(
        config,
        req.params.username,
        (err, config) => {
          if (err) {
            console.log(err)
            res.status(500)
            res.send('Unable to delete user')
            return
          }
          if (config) {
            saveSecurityConfig(app, config, err => {
              if (err) {
                console.log(err)
                res.status(500)
                res.send('Unable to save configuration change')
                return
              }
              res.send('User deleted')
            })
          } else {
            res.send('User deleted')
          }
        }
      )
    } else {
      res.status(401).json('Security config not allowed')
    }
  })

  app.get('/security/token/:id/:expiration', (req, res, next) => {
    app.securityStrategy.generateToken(
      req,
      res,
      next,
      req.params.id,
      req.params.expiration
    )
  })

  app.get('/settings', (req, res, next) => {
    var settings = {
      interfaces: {},
      options: {
        ssl: app.config.settings.ssl,
        mdns: app.config.settings.mdns
      },
      loggingDirectory: app.config.settings.loggingDirectory || '/tmp',
      port: app.config.settings.port || 3000,
      sslport: app.config.settings.sslport || 3433
    }

    var availableInterfaces = require('./interfaces')
    _.forIn(availableInterfaces, function(interface, name) {
      settings.interfaces[name] =
        _.isUndefined(app.config.settings.interfaces) ||
        _.isUndefined(app.config.settings.interfaces[name]) ||
        app.config.settings.interfaces[name]
    })

    res.json(settings)
  })

  if (
    app.securityStrategy == null ||
    app.securityStrategy.getUsers().length == 0
  ) {
    app.post('/enableSecurity', (req, res, next) => {
      if (app.securityStrategy == null) {
        app.config.settings.security = { strategy: defaultSecurityStrategy }
        config.writeSettingsFile(app, app.config.settings, err => {
          if (err) {
            console.log(err)
            res.status(500).send('Unable to save to settings file')
          } else {
            var config = {}
            var securityStrategy = require(defaultSecurityStrategy)(
              app,
              config,
              saveSecurityConfig
            )
            addUser(req, res, securityStrategy, config)
          }
        })
      } else {
        addUser(req, res, app.securityStrategy)
      }
      securityWasEnabled = true

      function addUser(req, res, securityStrategy, config) {
        var config
        if (!config) {
          config = getSecurityConfig(app)
        }
        securityStrategy.addUser(config, req.body, (err, config) => {
          if (err) {
            console.log(err)
            res.status(500)
            res.send('Unable to add user')
          } else {
            saveSecurityConfig(app, config, err => {
              if (err) {
                console.log(err)
                res.status(500)
                res.send('Unable to save security configuration change')
              }
              res.send('Security enabled')
            })
          }
        })
      }
    })
  }

  app.put('/settings', (req, res, next) => {
    var settings = req.body

    _.forIn(settings.interfaces, (enabled, name) => {
      app.config.settings.interfaces[name] = enabled
    })

    if (!_.isUndefined(settings.options.mdns)) {
      app.config.settings.mdns = settings.options.mdns
    }

    if (!_.isUndefined(settings.options.ssl)) {
      app.config.settings.ssl = settings.options.ssl
    }

    if (!_.isUndefined(settings.port)) {
      app.config.settings.port = Number(settings.port)
    }

    if (!_.isUndefined(settings.sslport)) {
      app.config.settings.sslport = Number(settings.sslport)
    }

    if (!_.isUndefined(settings.loggingDirectory)) {
      app.config.settings.loggingDirectory = settings.loggingDirectory
    }

    config.writeSettingsFile(app, app.config.settings, err => {
      if (err) {
        res.status(500).send('Unable to save to settings file')
      } else {
        res.send('Settings changed')
      }
    })
  })

  app.get('/vessel', (req, res, next) => {
    if (!app.config.defaults) {
      app.config.defaults = {
        vessels: {
          self: {}
        }
      }
    }

    var self = app.config.defaults.vessels.self

    var json = {
      name: self.name,
      mmsi: self.mmsi,
      uuid: self.uuid,
      draft: _.get(self, 'design.draft.value.maximum'),
      length: _.get(self, 'design.length.value.overall'),
      beam: _.get(self, 'design.beam.value'),
      height: _.get(self, 'design.airHeight.value'),
      gpsFromBow: _.get(self, 'sensors.gps.fromBow.value'),
      gpsFromCenter: _.get(self, 'sensors.gps.fromCenter.value')
    }
    res.json(json)
  })

  app.put('/vessel', (req, res, next) => {
    var data

    try {
      data = config.readDefaultsFile(app)
    } catch (e) {
      if (e.code && e.code === 'ENOENT') {
        data = {}
      } else {
        console.log(e)
        res.status(500).send('Unable to read defaults file')
      }
    }

    var self = _.get(data, 'data.vessels.self')

    if (_.isUndefined(self)) {
      self = _.set(data, 'vessels.self', {})
    }

    var newVessel = req.body

    var current = app.config.defaults

    function set(path, value) {
      _.set(data.vessels.self, path, value)
      _.set(current, path, value)
    }

    if (newVessel.name) {
      set('name', newVessel.name)
    }
    if (newVessel.mmsi) {
      set('mmsi', newVessel.mmsi)
    }
    if (newVessel.uuid && !self.mmmsi) {
      set('uuid', newVessel.uuid)
    } else {
      delete self.uuid
    }
    if (newVessel.draft) {
      set('design.draft.value.maximum', Number(newVessel.draft))
    }
    if (newVessel.length) {
      set('design.length.value.overall', Number(newVessel.length))
    }
    if (newVessel.beam) {
      set('design.beam.value', Number(newVessel.beam))
    }
    if (newVessel.height) {
      set('design.airHeight.value', Number(newVessel.height))
    }
    if (newVessel.gpsFromBow) {
      set('sensors.gps.fromBow.value', Number(newVessel.gpsFromBow))
    }
    if (newVessel.gpsFromCenter) {
      set('sensors.gps.fromCenter.value', Number(newVessel.gpsFromCenter))
    }

    config.writeDefaultsFile(app, data, err => {
      if (err) {
        res.status(500).send('Unable to save to defaults file')
      } else {
        res.send('Vessel changed')
      }
    })
  })

  app.get('/availablePaths', (req, res, next) => {
    res.json(app.streambundle.getAvailablePaths())
  })
}
<|endoftext|>


<commit_before>/*global XMLHttpRequest, document, window */
(function(window) {

    // Substrat Reload Handler
    var uid = '%uid',
        reloadUrl = '%reloadUrl',
        initUrl = '%initUrl';

    function handler() {

        // Restore scroll position
        try {
            var x = window.sessionStorage[uid + '.scroll.x'] || null,
                y = window.sessionStorage[uid + '.scroll.y'] || null;

            if (x !== null && y !== null) {

                // Since the layout might still reflow and the document size might
                // change, we need to periodically check the scroll offset
                var retries = 0,
                    lx = document.body.scrollLeft,
                    ly = document.body.scrollTop;

                var restore = window.setInterval(function() {

                    var sx = document.body.scrollLeft,
                        sy = document.body.scrollTop;

                    if (sx !== lx || sy !== ly) {
                        retries = 0;
                        lx = sx;
                        ly = sy;
                    }

                    // No further changes, should be restored now
                    if ((sx === +x && sy === +y) || retries > 30) {
                        window.clearInterval(restore);

                    } else {
                        retries++;
                        document.body.scrollLeft = +x;
                        document.body.scrollTop = +y;
                    }

                }, 25);
            }

        } catch(e) {
        }

        var retry = false,
            reloadTimeout = null,
            retryTimeout = null;

        function reload(reconnect, url) {

            var xhr = new XMLHttpRequest();
            xhr.open('GET', url);
            xhr.onreadystatechange = function() {

                // Substrat triggered a reload
                if (xhr.readyState === 4 && xhr.status === 200) {

                    // Save scroll position
                    try {
                        window.sessionStorage[uid + '.scroll.x'] = '' + document.body.scrollLeft;
                        window.sessionStorage[uid + '.scroll.y'] = '' + document.body.scrollTop;

                    } catch(e) {
                    }

                    document.location.reload();

                // Substrat exited, try to reconnect
                } else if (!retry) {
                    retryTimeout = window.setTimeout(function() {
                        xhr.abort();
                        window.clearTimeout(reloadTimeout);
                        window.clearTimeout(retryTimeout);
                        reload(true, initUrl);

                    }, 1000);
                }

                retry = false;

            };

            xhr.send();

            // The request will eventually fail if delayed indefinitely by the
            // server; thus, we need manually reset it every 30 seconds
            reloadTimeout = window.setTimeout(function() {
                retry = true;
                xhr.abort();
                reload(false, reloadUrl);

            }, 30000);

        }

        reload(false, reloadUrl);

    }

    if (window.addEventListener) {
        window.addEventListener('load', handler, false);

    } else {
        window.attachEvent('onload', handler);
    }

})(window);

<commit_msg>Save scroll position on beforeunload so that it works with manual reloads too. Also clear the session storage of the scroll offsets on reload.<commit_after>/*global XMLHttpRequest, document, window */
(function(window) {

    // Substrat Reload Handler
    var uid = '%uid',
        reloadUrl = '%reloadUrl',
        initUrl = '%initUrl';

    // Save scroll position
    function save() {
        try {
            window.sessionStorage[uid + '.scroll.x'] = '' + document.body.scrollLeft;
            window.sessionStorage[uid + '.scroll.y'] = '' + document.body.scrollTop;

        } catch(e) {
        }
    }

    // Restore scroll position
    function restore() {

        var x = null,
            y = null;

        try {
            x = window.sessionStorage[uid + '.scroll.x'] || null;
            y = window.sessionStorage[uid + '.scroll.y'] || null;
            delete window.sessionStorage[uid + '.scroll.x'];
            delete window.sessionStorage[uid + '.scroll.y'];

        } catch(e) {
            return;
        }

        if (x !== null && y !== null) {

            // Since the layout might still reflow and the document size might
            // change, we need to periodically check the scroll offset
            var retries = 0,
                lx = document.body.scrollLeft,
                ly = document.body.scrollTop;

            var t = window.setInterval(function() {

                var sx = document.body.scrollLeft,
                    sy = document.body.scrollTop;

                if (sx !== lx || sy !== ly) {
                    retries = 0;
                    lx = sx;
                    ly = sy;
                }

                // No further changes, should be restored now
                if ((sx === +x && sy === +y) || retries > 30) {
                    window.clearInterval(t);

                } else {
                    retries++;
                    document.body.scrollLeft = +x;
                    document.body.scrollTop = +y;
                }

            }, 25);
        }

    }

    // Init Watcher
    function init() {

        var retry = false,
            reloadTimeout = null,
            retryTimeout = null;

        function reload(reconnect, url) {

            var xhr = new XMLHttpRequest();
            xhr.open('GET', url);
            xhr.onreadystatechange = function() {

                // Substrat triggered a reload
                if (xhr.readyState === 4 && xhr.status === 200) {
                    document.location.reload();

                // Substrat exited, try to reconnect
                } else if (!retry) {
                    retryTimeout = window.setTimeout(function() {
                        xhr.abort();
                        window.clearTimeout(reloadTimeout);
                        window.clearTimeout(retryTimeout);
                        reload(true, initUrl);

                    }, 1000);
                }

                retry = false;

            };

            xhr.send();

            // The request will eventually fail if delayed indefinitely by the
            // server; thus, we need manually reset it every 30 seconds
            reloadTimeout = window.setTimeout(function() {
                retry = true;
                xhr.abort();
                reload(false, reloadUrl);

            }, 30000);

        }

        restore();
        reload(false, reloadUrl);

    }

    // Events
    if (window.addEventListener) {
        window.addEventListener('load', init, false);
        window.addEventListener('beforeunload', save, false);

    } else {
        window.attachEvent('onload', init);
        window.attachEvent('onbeforeunload', save, false);
    }

})(window);

<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var net = require('net')
  , Parser = require('../parser')
  , Message = require('../message')
  , codecs = require('../codecs')
  , Emitter = require('events').EventEmitter
  , debug = require('debug')('axon:sock')
  , Configurable = require('configurable')
  , url = require('url');

/**
 * Expose `Socket`.
 */

module.exports = Socket;

/**
 * Initialize a new `Socket`.
 *
 * A "Socket" encapsulates the ability of being
 * the "client" or the "server" depending on
 * whether `connect()` or `bind()` was called.
 *
 * Upon sending and recieving messages, the correct codec
 * set by `format` will be applied. Both sides must elect
 * the same codec, or all hell will break loose on the app.
 *
 * @api private
 */

function Socket() {
  var self = this;
  this.opts = {};
  this.server = null;
  this.socks = [];
  this.settings = {};
  this.format('none');
  this.set('identity', String(process.pid));
  this.set('retry timeout', 100);
  this.set('retry max timeout', 2000);
}

/**
 * Inherit from `Emitter.prototype`.
 */

Socket.prototype.__proto__ = Emitter.prototype;

/**
 * Make it configurable `.set()` etc.
 */

Configurable(Socket.prototype);

/**
 * Use the given `plugin`.
 *
 * @param {Function} plugin
 * @api private
 */

Socket.prototype.use = function(plugin){
  plugin(this);
  return this;
};

/**
 * Set format to `type`.
 *
 * @param {String} type
 * @return {Socket}
 * @api public
 */

Socket.prototype.format = function(type){
  var codec = codecs.byName(type);
  if (!codec) throw new Error('unknown format "' + type + '"');
  this.codec = codec;
  return this;
};

/**
 * Creates a new `Message` and writes `data` using the
 * `codec` set already by `format`.
 *
 * This will only work for single part messages or multi
 * part message that all use the same codec. If you need
 * otherwise, use the `Message` constructor to craft
 * your own message.
 *
 * @param {Mixed} data
 * @return {Buffer}
 * @api private
 */

Socket.prototype.pack = function(data){
  var msg = new Message
    , codec = this.codec;

  if (Array.isArray(data)) {
    for (var i = 0; i < data.length; i++) {
      msg.write(codec.encode(data[i]), codec.id);
    }
  } else {
    msg.write(codec.encode(data), codec.id);
  }

  return msg.toBuffer();
};

/**
 * Close the socket.
 *
 * Delegates to the server or clients
 * based on the socket `type`.
 *
 * @api public
 */

Socket.prototype.close = function(){
  debug('close');
  this.closing = true;
  if ('server' == this.type) {
    this.server && this.server.close();
  } else {
    this.socks.forEach(function(sock){
      sock.destroy();
    });
  }
};

/**
 * Return the server address.
 *
 * @return {Object}
 * @api public
 */

Socket.prototype.address = function(){
  if (!this.server) return;
  var addr = this.server.address();
  addr.string = 'tcp://' + addr.address + ':' + addr.port;
  return addr;
};

/**
 * Remove `sock`.
 *
 * @param {Socket} sock
 * @api private
 */

Socket.prototype.removeSocket = function(sock){
  var i = this.socks.indexOf(sock);
  this.socks.splice(i, 1);
  if (sock._axon_id) delete this.map[sock._axon_id];
};

/**
 * Add `sock`.
 *
 * @param {Socket} sock
 * @api private
 */

Socket.prototype.addSocket = function(sock){
  var self = this
    , parser = new Parser
    , n = this.socks.push(sock);

  sock.on('data', parser.write.bind(parser));
  parser.onmessage = self.onmessage(sock);
};

/**
 * Handles framed messages emitted from the parser, by
 * default it will go ahead and emit the "message" events on
 * the socket. However, if the "higher level" socket needs
 * to hook into the messages before they are emitted, it
 * should override this method and take care of everything
 * it self, including emitted the "message" event.
 *
 * @param {net.Socket} sock
 * @return {Function} closure(msg, mulitpart)
 * @api private
 */

Socket.prototype.onmessage = function(sock){
  var self = this;
  return function(msg, multipart){
    if (multipart) {
      msg.unshift('message');
      self.emit.apply(self, msg);
    } else {
      self.emit('message', msg);
    }
  };
};

/**
 * Connect to `port` at `host` and invoke `fn()`.
 *
 * Defaults `host` to localhost.
 *
 * @param {Number|String} port
 * @param {String} host
 * @param {Function} fn
 * @return {Socket}
 * @api public
 */

Socket.prototype.connect = function(port, host, fn){
  var self = this;
  if ('server' == this.type) throw new Error('cannot connect() after bind()');
  if ('function' == typeof host) fn = host, host = undefined;

  if ('string' == typeof port) {
    port = url.parse(port);
    host = port.hostname;
    port = parseInt(port.port, 10);
  }

  var sock = new net.Socket;
  sock.setNoDelay();
  this.type = 'client';
  port = port;
  host = host || '127.0.0.1';

  sock.on('error', function(err){
    if ('ECONNREFUSED' != err.code) {
      self.emit('error', err);
    }
  });

  sock.on('close', function(){
    self.connected = false;
    self.removeSocket(sock);
    if (self.closing) return self.emit('close');
    var max = self.get('max retry timeout');
    var retry = self.retry || self.get('retry timeout');
    setTimeout(function(){
      debug('attempting reconnect');
      self.emit('reconnect attempt');
      sock.destroy();
      self.connect(port, host);
      self.retry = Math.min(max, retry * 1.5);
    }, retry);
  });

  sock.on('connect', function(){
    debug('connect');
    self.connected = true;
    self.addSocket(sock);
    self.retry = self.get('retry timeout');
    self.emit('connect');
    fn && fn();
  });

  debug('connect attempt %s:%s', host, port);
  sock.connect(port, host);
  return this;
};

/**
 * Handle connection.
 *
 * @param {Socket} sock
 * @api private
 */

Socket.prototype.onconnect = function(sock){
  var self = this;
  var addr = sock.remoteAddress + ':' + sock.remotePort;
  debug('connection %s', addr);
  this.addSocket(sock);
  this.emit('connect', sock);
  sock.on('close', function(){
    debug('disconnect %s', addr);
    self.emit('disconnect', sock);
    self.removeSocket(sock);
  });
};

/**
 * Bind to `port` at `host` and invoke `fn()`.
 *
 * Defaults `host` to INADDR_ANY.
 *
 * Emits:
 *
 *  - `connection` when a client connects
 *  - `disconnect` when a client disconnects
 *  - `bind` when bound and listening
 *
 * @param {Number|String} port
 * @param {Function} fn
 * @return {Socket}
 * @api public
 */

Socket.prototype.bind = function(port, host, fn){
  var self = this;
  if ('client' == this.type) throw new Error('cannot bind() after connect()');
  if ('function' == typeof host) fn = host, host = undefined;

  if ('string' == typeof port) {
    port = url.parse(port);
    host = port.hostname;
    port = parseInt(port.port, 10);
  }

  this.type = 'server';
  host = host || '0.0.0.0';

  this.server = net.createServer(this.onconnect.bind(this));

  debug('bind %s:%s', host, port);
  this.server.on('listening', this.emit.bind('bind'));
  this.server.listen(port, host, fn);
  return this;
};
<commit_msg>refactor Socket#addSocket(sock)<commit_after>
/**
 * Module dependencies.
 */

var net = require('net')
  , Parser = require('../parser')
  , Message = require('../message')
  , codecs = require('../codecs')
  , Emitter = require('events').EventEmitter
  , debug = require('debug')('axon:sock')
  , Configurable = require('configurable')
  , url = require('url');

/**
 * Expose `Socket`.
 */

module.exports = Socket;

/**
 * Initialize a new `Socket`.
 *
 * A "Socket" encapsulates the ability of being
 * the "client" or the "server" depending on
 * whether `connect()` or `bind()` was called.
 *
 * Upon sending and recieving messages, the correct codec
 * set by `format` will be applied. Both sides must elect
 * the same codec, or all hell will break loose on the app.
 *
 * @api private
 */

function Socket() {
  var self = this;
  this.opts = {};
  this.server = null;
  this.socks = [];
  this.settings = {};
  this.format('none');
  this.set('identity', String(process.pid));
  this.set('retry timeout', 100);
  this.set('retry max timeout', 2000);
}

/**
 * Inherit from `Emitter.prototype`.
 */

Socket.prototype.__proto__ = Emitter.prototype;

/**
 * Make it configurable `.set()` etc.
 */

Configurable(Socket.prototype);

/**
 * Use the given `plugin`.
 *
 * @param {Function} plugin
 * @api private
 */

Socket.prototype.use = function(plugin){
  plugin(this);
  return this;
};

/**
 * Set format to `type`.
 *
 * @param {String} type
 * @return {Socket}
 * @api public
 */

Socket.prototype.format = function(type){
  var codec = codecs.byName(type);
  if (!codec) throw new Error('unknown format "' + type + '"');
  this.codec = codec;
  return this;
};

/**
 * Creates a new `Message` and writes `data` using the
 * `codec` set already by `format`.
 *
 * This will only work for single part messages or multi
 * part message that all use the same codec. If you need
 * otherwise, use the `Message` constructor to craft
 * your own message.
 *
 * @param {Mixed} data
 * @return {Buffer}
 * @api private
 */

Socket.prototype.pack = function(data){
  var msg = new Message
    , codec = this.codec;

  if (Array.isArray(data)) {
    for (var i = 0; i < data.length; i++) {
      msg.write(codec.encode(data[i]), codec.id);
    }
  } else {
    msg.write(codec.encode(data), codec.id);
  }

  return msg.toBuffer();
};

/**
 * Close the socket.
 *
 * Delegates to the server or clients
 * based on the socket `type`.
 *
 * @api public
 */

Socket.prototype.close = function(){
  debug('close');
  this.closing = true;
  if ('server' == this.type) {
    this.server && this.server.close();
  } else {
    this.socks.forEach(function(sock){
      sock.destroy();
    });
  }
};

/**
 * Return the server address.
 *
 * @return {Object}
 * @api public
 */

Socket.prototype.address = function(){
  if (!this.server) return;
  var addr = this.server.address();
  addr.string = 'tcp://' + addr.address + ':' + addr.port;
  return addr;
};

/**
 * Remove `sock`.
 *
 * @param {Socket} sock
 * @api private
 */

Socket.prototype.removeSocket = function(sock){
  var i = this.socks.indexOf(sock);
  this.socks.splice(i, 1);
  if (sock._axon_id) delete this.map[sock._axon_id];
};

/**
 * Add `sock`.
 *
 * @param {Socket} sock
 * @api private
 */

Socket.prototype.addSocket = function(sock){
  var parser = new Parser;
  this.socks.push(sock);
  sock.on('data', parser.write.bind(parser));
  parser.onmessage = this.onmessage(sock);
};

/**
 * Handles framed messages emitted from the parser, by
 * default it will go ahead and emit the "message" events on
 * the socket. However, if the "higher level" socket needs
 * to hook into the messages before they are emitted, it
 * should override this method and take care of everything
 * it self, including emitted the "message" event.
 *
 * @param {net.Socket} sock
 * @return {Function} closure(msg, mulitpart)
 * @api private
 */

Socket.prototype.onmessage = function(sock){
  var self = this;
  return function(msg, multipart){
    if (multipart) {
      msg.unshift('message');
      self.emit.apply(self, msg);
    } else {
      self.emit('message', msg);
    }
  };
};

/**
 * Connect to `port` at `host` and invoke `fn()`.
 *
 * Defaults `host` to localhost.
 *
 * @param {Number|String} port
 * @param {String} host
 * @param {Function} fn
 * @return {Socket}
 * @api public
 */

Socket.prototype.connect = function(port, host, fn){
  var self = this;
  if ('server' == this.type) throw new Error('cannot connect() after bind()');
  if ('function' == typeof host) fn = host, host = undefined;

  if ('string' == typeof port) {
    port = url.parse(port);
    host = port.hostname;
    port = parseInt(port.port, 10);
  }

  var sock = new net.Socket;
  sock.setNoDelay();
  this.type = 'client';
  port = port;
  host = host || '127.0.0.1';

  sock.on('error', function(err){
    if ('ECONNREFUSED' != err.code) {
      self.emit('error', err);
    }
  });

  sock.on('close', function(){
    self.connected = false;
    self.removeSocket(sock);
    if (self.closing) return self.emit('close');
    var max = self.get('max retry timeout');
    var retry = self.retry || self.get('retry timeout');
    setTimeout(function(){
      debug('attempting reconnect');
      self.emit('reconnect attempt');
      sock.destroy();
      self.connect(port, host);
      self.retry = Math.min(max, retry * 1.5);
    }, retry);
  });

  sock.on('connect', function(){
    debug('connect');
    self.connected = true;
    self.addSocket(sock);
    self.retry = self.get('retry timeout');
    self.emit('connect');
    fn && fn();
  });

  debug('connect attempt %s:%s', host, port);
  sock.connect(port, host);
  return this;
};

/**
 * Handle connection.
 *
 * @param {Socket} sock
 * @api private
 */

Socket.prototype.onconnect = function(sock){
  var self = this;
  var addr = sock.remoteAddress + ':' + sock.remotePort;
  debug('connection %s', addr);
  this.addSocket(sock);
  this.emit('connect', sock);
  sock.on('close', function(){
    debug('disconnect %s', addr);
    self.emit('disconnect', sock);
    self.removeSocket(sock);
  });
};

/**
 * Bind to `port` at `host` and invoke `fn()`.
 *
 * Defaults `host` to INADDR_ANY.
 *
 * Emits:
 *
 *  - `connection` when a client connects
 *  - `disconnect` when a client disconnects
 *  - `bind` when bound and listening
 *
 * @param {Number|String} port
 * @param {Function} fn
 * @return {Socket}
 * @api public
 */

Socket.prototype.bind = function(port, host, fn){
  var self = this;
  if ('client' == this.type) throw new Error('cannot bind() after connect()');
  if ('function' == typeof host) fn = host, host = undefined;

  if ('string' == typeof port) {
    port = url.parse(port);
    host = port.hostname;
    port = parseInt(port.port, 10);
  }

  this.type = 'server';
  host = host || '0.0.0.0';

  this.server = net.createServer(this.onconnect.bind(this));

  debug('bind %s:%s', host, port);
  this.server.on('listening', this.emit.bind('bind'));
  this.server.listen(port, host, fn);
  return this;
};
<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var net = require('net')
  , Parser = require('../parser')
  , Message = require('../message')
  , codecs = require('../codecs')
  , Emitter = require('events').EventEmitter
  , debug = require('debug')('axon:sock')
  , Configurable = require('configurable')
  , url = require('url');

/**
 * Expose `Socket`.
 */

module.exports = Socket;

/**
 * Initialize a new `Socket`.
 *
 * A "Socket" encapsulates the ability of being
 * the "client" or the "server" depending on
 * whether `connect()` or `bind()` was called.
 *
 * Upon sending and recieving messages, the correct codec
 * set by `format` will be applied. Both sides must elect
 * the same codec, or all hell will break loose on the app.
 *
 * @api private
 */

function Socket() {
  var self = this;
  this.opts = {};
  this.server = null;
  this.socks = [];
  this.map = {};
  this.format('none');
  this.option('identity', '\u0000');
  this.option('retry timeout', 100);
  this.option('retry max timeout', 2000);
}

/**
 * Inherit from `Emitter.prototype`.
 */

Socket.prototype.__proto__ = Emitter.prototype;

/**
 * Make it configurable `.set()` etc.
 */

Configurable(Socket.prototype);

/**
 * Use the given `plugin`.
 *
 * @param {Function} plugin
 * @api private
 */

Socket.prototype.use = function(plugin){
  plugin(this);
  return this;
};

/**
 * Set or get the option `key`.
 *
 * @param {String} key
 * @param {Mixed} value
 * @api public
 */

Socket.prototype.option = function(key, value){
  if (arguments.length == 1) {
    return this.opts[key];
  } else {
    this.opts[key] = value;
    return this;
  }
};

/**
 * Set format to `type`.
 *
 * @param {String} type
 * @return {Socket}
 * @api public
 */

Socket.prototype.format = function(type){
  var codec = codecs.byName(type);
  if (!codec) throw new Error('unknown format "' + type + '"');
  this.codec = codec;
  return this;
};

/**
 * Creates a new `Message` and writes `data` using the
 * `codec` set already by `format`.
 *
 * This will only work for single part messages or multi
 * part message that all use the same codec. If you need
 * otherwise, use the `Message` constructor to craft
 * your own message.
 *
 * @param {Mixed} data
 * @return {Buffer}
 * @api private
 */

Socket.prototype.pack = function(data){
  var msg = new Message();
  var codec = this.codec;

  if (Array.isArray(data)) {
    for (var i = 0; i < data.length; i++) {
      msg.write(codec.encode(data[i]), codec.id);
    }
  } else {
    msg.write(codec.encode(data), codec.id);
  }

  return msg.toBuffer();
};

/**
 * Close the socket.
 *
 * Delegates to the server or clients
 * based on the socket `type`.
 *
 * @api public
 */

Socket.prototype.close = function(){
  debug('close');
  this.closing = true;
  if ('server' == this.type) {
    this.server && this.server.close();
  } else {
    this.socks.forEach(function(sock){
      sock.destroy();
    });
  }
};

/**
 * Remove `sock`.
 *
 * @param {Socket} sock
 * @api private
 */

Socket.prototype.removeSocket = function(sock){
  var i = this.socks.indexOf(sock);
  this.socks.splice(i, 1);
  if (sock._axon_id) delete this.map[sock._axon_id];
};

/**
 * Add `sock`.
 *
 * TODO: Better ways to hook into the parsed
 * messages prior to the application receiving
 * them?
 *
 * @param {Socket} sock
 * @api private
 */

Socket.prototype.addSocket = function(sock){
  var self = this
    , pid = String(process.pid)
    , parser = new Parser
    , n = this.socks.push(sock);

  // send our greeting
  sock.write(this.pack(this.option('identity')));

  // parse incoming
  sock.on('data', parser.write.bind(parser));

  // accept greeting once, emit messages there on out
  parser.onmessage = ongreeting;

  function ongreeting(msg){
    var id = String(msg);
    if ('0' === id) id = pid + Date.now() + n;
    sock._axon_id = id;
    self.map[id] = sock;
    parser.onmessage = onmessage;
  }

  function onmessage(msg, multipart){
    if (multipart) {
      self.emit.apply(self, ['message'].concat(msg));
    } else {
      self.emit('message', msg);
    }
  }
};

/**
 * Connect to `port` at `host` and invoke `fn()`.
 *
 * Defaults `host` to localhost.
 *
 * @param {Number|String} port
 * @param {String} host
 * @param {Function} fn
 * @return {Socket}
 * @api public
 */

Socket.prototype.connect = function(port, host, fn){
  var self = this;
  if ('server' == this.type) throw new Error('cannot connect() after bind()');
  if ('function' == typeof host) fn = host, host = undefined;

  if ('string' == typeof port) {
    port = url.parse(port);
    host = port.hostname;
    port = parseInt(port.port, 10);
  }

  var sock = new net.Socket;
  sock.setNoDelay();
  this.type = 'client';
  port = port;
  host = host || '127.0.0.1';

  sock.on('error', function(err){
    if ('ECONNREFUSED' != err.code) {
      self.emit('error', err);
    }
  });

  sock.on('close', function(){
    self.connected = false;
    self.removeSocket(sock);
    if (self.closing) return self.emit('close');
    var max = self.option('max retry timeout');
    var retry = self.retry || self.option('retry timeout');
    setTimeout(function(){
      debug('attempting reconnect');
      self.emit('reconnect attempt');
      sock.destroy();
      self.connect(port, host);
      self.retry = Math.min(max, retry * 1.5);
    }, retry);
  });

  sock.on('connect', function(){
    debug('connect');
    self.connected = true;
    self.addSocket(sock);
    self.retry = self.option('retry timeout');
    self.emit('connect');
    fn && fn();
  });

  debug('connect attempt %s:%s', host, port);
  sock.connect(port, host);
  return this;
};

/**
 * Handle connection.
 *
 * @param {Socket} sock
 * @api private
 */

Socket.prototype.onconnect = function(sock){
  var self = this;
  var addr = sock.remoteAddress + ':' + sock.remotePort;
  debug('connection %s', addr);
  this.addSocket(sock);
  this.emit('connect', sock);
  sock.on('close', function(){
    debug('disconnect %s', addr);
    self.emit('disconnect', sock);
    self.removeSocket(sock);
  });
};

/**
 * Bind to `port` at `host` and invoke `fn()`.
 *
 * Defaults `host` to INADDR_ANY.
 *
 * Emits:
 *
 *  - `connection` when a client connects
 *  - `disconnect` when a client disconnects
 *  - `bind` when bound and listening
 *
 * @param {Number|String} port
 * @param {Function} fn
 * @return {Socket}
 * @api public
 */

Socket.prototype.bind = function(port, host, fn){
  var self = this;
  if ('client' == this.type) throw new Error('cannot bind() after connect()');
  if ('function' == typeof host) fn = host, host = undefined;

  if ('string' == typeof port) {
    port = url.parse(port);
    host = port.hostname;
    port = parseInt(port.port, 10);
  }

  this.type = 'server';
  host = host || '0.0.0.0';

  this.server = net.createServer(this.onconnect.bind(this));

  debug('bind %s:%s', host, port);
  this.server.on('listening', this.emit.bind('bind'));
  this.server.listen(port, host, fn);
  return this;
};
<commit_msg>remove Socket#option()<commit_after>
/**
 * Module dependencies.
 */

var net = require('net')
  , Parser = require('../parser')
  , Message = require('../message')
  , codecs = require('../codecs')
  , Emitter = require('events').EventEmitter
  , debug = require('debug')('axon:sock')
  , Configurable = require('configurable')
  , url = require('url');

/**
 * Expose `Socket`.
 */

module.exports = Socket;

/**
 * Initialize a new `Socket`.
 *
 * A "Socket" encapsulates the ability of being
 * the "client" or the "server" depending on
 * whether `connect()` or `bind()` was called.
 *
 * Upon sending and recieving messages, the correct codec
 * set by `format` will be applied. Both sides must elect
 * the same codec, or all hell will break loose on the app.
 *
 * @api private
 */

function Socket() {
  var self = this;
  this.opts = {};
  this.server = null;
  this.socks = [];
  this.map = {};
  this.format('none');
  this.option('identity', '\u0000');
  this.option('retry timeout', 100);
  this.option('retry max timeout', 2000);
}

/**
 * Inherit from `Emitter.prototype`.
 */

Socket.prototype.__proto__ = Emitter.prototype;

/**
 * Make it configurable `.set()` etc.
 */

Configurable(Socket.prototype);

/**
 * Use the given `plugin`.
 *
 * @param {Function} plugin
 * @api private
 */

Socket.prototype.use = function(plugin){
  plugin(this);
  return this;
};

/**
 * Set format to `type`.
 *
 * @param {String} type
 * @return {Socket}
 * @api public
 */

Socket.prototype.format = function(type){
  var codec = codecs.byName(type);
  if (!codec) throw new Error('unknown format "' + type + '"');
  this.codec = codec;
  return this;
};

/**
 * Creates a new `Message` and writes `data` using the
 * `codec` set already by `format`.
 *
 * This will only work for single part messages or multi
 * part message that all use the same codec. If you need
 * otherwise, use the `Message` constructor to craft
 * your own message.
 *
 * @param {Mixed} data
 * @return {Buffer}
 * @api private
 */

Socket.prototype.pack = function(data){
  var msg = new Message();
  var codec = this.codec;

  if (Array.isArray(data)) {
    for (var i = 0; i < data.length; i++) {
      msg.write(codec.encode(data[i]), codec.id);
    }
  } else {
    msg.write(codec.encode(data), codec.id);
  }

  return msg.toBuffer();
};

/**
 * Close the socket.
 *
 * Delegates to the server or clients
 * based on the socket `type`.
 *
 * @api public
 */

Socket.prototype.close = function(){
  debug('close');
  this.closing = true;
  if ('server' == this.type) {
    this.server && this.server.close();
  } else {
    this.socks.forEach(function(sock){
      sock.destroy();
    });
  }
};

/**
 * Remove `sock`.
 *
 * @param {Socket} sock
 * @api private
 */

Socket.prototype.removeSocket = function(sock){
  var i = this.socks.indexOf(sock);
  this.socks.splice(i, 1);
  if (sock._axon_id) delete this.map[sock._axon_id];
};

/**
 * Add `sock`.
 *
 * TODO: Better ways to hook into the parsed
 * messages prior to the application receiving
 * them?
 *
 * @param {Socket} sock
 * @api private
 */

Socket.prototype.addSocket = function(sock){
  var self = this
    , pid = String(process.pid)
    , parser = new Parser
    , n = this.socks.push(sock);

  // send our greeting
  sock.write(this.pack(this.option('identity')));

  // parse incoming
  sock.on('data', parser.write.bind(parser));

  // accept greeting once, emit messages there on out
  parser.onmessage = ongreeting;

  function ongreeting(msg){
    var id = String(msg);
    if ('0' === id) id = pid + Date.now() + n;
    sock._axon_id = id;
    self.map[id] = sock;
    parser.onmessage = onmessage;
  }

  function onmessage(msg, multipart){
    if (multipart) {
      self.emit.apply(self, ['message'].concat(msg));
    } else {
      self.emit('message', msg);
    }
  }
};

/**
 * Connect to `port` at `host` and invoke `fn()`.
 *
 * Defaults `host` to localhost.
 *
 * @param {Number|String} port
 * @param {String} host
 * @param {Function} fn
 * @return {Socket}
 * @api public
 */

Socket.prototype.connect = function(port, host, fn){
  var self = this;
  if ('server' == this.type) throw new Error('cannot connect() after bind()');
  if ('function' == typeof host) fn = host, host = undefined;

  if ('string' == typeof port) {
    port = url.parse(port);
    host = port.hostname;
    port = parseInt(port.port, 10);
  }

  var sock = new net.Socket;
  sock.setNoDelay();
  this.type = 'client';
  port = port;
  host = host || '127.0.0.1';

  sock.on('error', function(err){
    if ('ECONNREFUSED' != err.code) {
      self.emit('error', err);
    }
  });

  sock.on('close', function(){
    self.connected = false;
    self.removeSocket(sock);
    if (self.closing) return self.emit('close');
    var max = self.option('max retry timeout');
    var retry = self.retry || self.option('retry timeout');
    setTimeout(function(){
      debug('attempting reconnect');
      self.emit('reconnect attempt');
      sock.destroy();
      self.connect(port, host);
      self.retry = Math.min(max, retry * 1.5);
    }, retry);
  });

  sock.on('connect', function(){
    debug('connect');
    self.connected = true;
    self.addSocket(sock);
    self.retry = self.option('retry timeout');
    self.emit('connect');
    fn && fn();
  });

  debug('connect attempt %s:%s', host, port);
  sock.connect(port, host);
  return this;
};

/**
 * Handle connection.
 *
 * @param {Socket} sock
 * @api private
 */

Socket.prototype.onconnect = function(sock){
  var self = this;
  var addr = sock.remoteAddress + ':' + sock.remotePort;
  debug('connection %s', addr);
  this.addSocket(sock);
  this.emit('connect', sock);
  sock.on('close', function(){
    debug('disconnect %s', addr);
    self.emit('disconnect', sock);
    self.removeSocket(sock);
  });
};

/**
 * Bind to `port` at `host` and invoke `fn()`.
 *
 * Defaults `host` to INADDR_ANY.
 *
 * Emits:
 *
 *  - `connection` when a client connects
 *  - `disconnect` when a client disconnects
 *  - `bind` when bound and listening
 *
 * @param {Number|String} port
 * @param {Function} fn
 * @return {Socket}
 * @api public
 */

Socket.prototype.bind = function(port, host, fn){
  var self = this;
  if ('client' == this.type) throw new Error('cannot bind() after connect()');
  if ('function' == typeof host) fn = host, host = undefined;

  if ('string' == typeof port) {
    port = url.parse(port);
    host = port.hostname;
    port = parseInt(port.port, 10);
  }

  this.type = 'server';
  host = host || '0.0.0.0';

  this.server = net.createServer(this.onconnect.bind(this));

  debug('bind %s:%s', host, port);
  this.server.on('listening', this.emit.bind('bind'));
  this.server.listen(port, host, fn);
  return this;
};
<|endoftext|>


<commit_before>'use strict';

// module entry point
var SetupRabbitMQHelper = function setupRabbitMQHelper(options, eventHandlers) {
    if (!(this instanceof SetupRabbitMQHelper)) {
        return new SetupRabbitMQHelper(options, eventHandlers);
    }
    options = options || {};

    var _initialize = true;
    var _this = this;

    var tool = require('cloneextend');
    var config = tool.cloneextend({
        connection: {
            host: '127.0.0.1',
            options: {
                heartbeat: 5,
                reconnect: true
            }
        },
        exchange: {
            name: 'testing-exchange',
            options: {
                type: 'direct',
                durable: false,
                autoDelete: false
            }
        },
        queue: {
            name: 'testing-queue',
            options: {
                routingKey: 'event-routing',
                durable: true,
                autoDelete: false,
                prefetchCount: 1
            }
        }
    }, options.config || {});

    require('./validators/config')(config, function (err) {
        if (err) {
            throw new Error(err);
        }
        else {
            // setup internal EventEmitter
            var InternalEventsHandler = function internalEventsHandler() {
                if (!(this instanceof InternalEventsHandler)) {
                    return new InternalEventsHandler();
                }
            };
            require('util').inherits(InternalEventsHandler, require('events').EventEmitter);

            var internalEvents = new InternalEventsHandler();

            // var logger = options.logger || console;

            // setup storage variables
            var connection;
            var exchange;
            var queue;
            var queueHandler;

            var currentStatus = 'initializing';

            // setup helper functions
            var helpers = {};

            // emit external errors
            var EmitError = function emitError(err) {
                // if you don't listen to this it is not a good idea
                _this.emit('error', {
                    err: err,
                    message: err.message || 'no_message'
                });
            };
            helpers.emitError = EmitError;

            var EnsureExchange = function ensureExchange() {
                exchange = connection.exchange(config.exchange.name, config.exchange.options, function (err) {
                    if (err) {
                        helpers.emitError(err);
                    }
                });
                if (exchange) {
                    internalEvents.emit('exchange-ensured');
                }
            };
            helpers.ensureExchange = EnsureExchange;

            var EnsureQueue = function ensureQueue() {
                queue = connection.queue(config.queue.name, config.queue.options, function (err) {
                    if (err) {
                        helpers.emitError(err);
                    }
                });
                if (queue) {
                    internalEvents.emit('queue-ensured');
                }
            };
            helpers.ensureQueue = EnsureQueue;

            var BindQueueToExchange = function bindQueueToExchange(emitEnsured) {
                queue.bindQueue(exchange.name, config.queue.options.routingKey, function () {
                    internalEvents.emit('queue-bound');
                    var previousStatus = currentStatus;
                    currentStatus = 'connected';
                    if (emitEnsured) {
                        switch (previousStatus) {
                        case 'initializing':
                            _this.emit('initialized');
                            break;
                        case 'reconnecting':
                            _this.emit('reconnected');
                            break;
                        }
                    }
                });
            };
            helpers.bindQueueToExchange = BindQueueToExchange;

            var BindListenerToQueue = function bindListenerToQueue() {
                if (queueHandler) {
                    queue.listen({
                        ack: true,
                        prefetchCount: config.queue.options.prefetchCount
                    }, function (message, ack, headers, fields, m) {
                        queueHandler({
                            message: message,
                            ack: ack,
                            headers: headers,
                            fields: fields,
                            m: m
                        });
                    });
                    _this.emit('queue-listening');
                }
            };
            helpers.bindListenerToQueue = BindListenerToQueue;

            // setup exported functions
            var SetQueueHandler = function setQueueHandler(handler) {
                if (queueHandler) {
                    helpers.emitError(new Error('handler_already_set'));
                }
                else {
                    queueHandler = handler;
                    helpers.bindListenerToQueue();
                }
            };

            var PublishMessage = function publishMessage(message, messageOptions, callback) {
                if ('function' === typeof messageOptions) {
                    callback = messageOptions;
                    messageOptions = {};
                }
                messageOptions = messageOptions || {};
                if (undefined === messageOptions.persistent) {
                    messageOptions.persistent = true;
                }
                if (undefined === messageOptions.mandatory) {
                    messageOptions.mandatory = true;
                }
                if ('connected' === currentStatus) {
                    var routingKey = messageOptions.routingKey || config.queue.options.routingKey;
                    exchange.send(routingKey, message, messageOptions, callback);
                }
                else {
                    callback('rabbitmq_helper_not_ensured');
                }
            };

            // initializer
            var InitializeRabbitMQHelper = function initializeRabbitMQHelper() {
                if (!(this instanceof InitializeRabbitMQHelper)) {
                    return new InitializeRabbitMQHelper();
                }
                internalEvents.on('connected', function () {
                    connection.on('connection error', function (err) {
                        connection.channel = null;
                        if ('Unexpected close' === err.message) {
                            currentStatus = 'reconnecting';
                        }
                        if (err) {
                            helpers.emitError(err);
                        }
                    });
                    connection.on('reconnected', function () {
                        helpers.ensureExchange();
                    });
                    helpers.ensureExchange();
                });
                internalEvents.on('exchange-ensured', function () {
                    helpers.ensureQueue();
                });
                internalEvents.on('queue-ensured', function () {
                    helpers.bindQueueToExchange(true);
                });
                internalEvents.on('queue-bound', function () {
                    helpers.bindListenerToQueue();
                });
                // connect and fire off events
                connection = _this.rabbit('amqp://' + config.connection.host, config.connection.options).connect(function (err) {
                    if (err) {
                        helpers.emitError(err);
                    }
                });
                if (connection) {
                    internalEvents.emit('connected');
                }
                return _this;
            };

            if (eventHandlers) {
                for (var key in eventHandlers) {
                    _this.on(key, eventHandlers[key]);
                }
            }
            _this.rabbit = require('rabbit-wrapper');
            // variables that need to be different/changeable can be exported
            // as noted at the top of this file any variables there are global to all instantiations

            _this.setQueueHandler = SetQueueHandler;
            _this.publishMessage = PublishMessage;
            // exports functions if needed
            if (options.mocking) {
                _initialize = false;
                _this.initializeRabbitMQHelper = InitializeRabbitMQHelper;
            }
            if (_initialize) {
                return new InitializeRabbitMQHelper();
            }
            return _this;
        }
    });
};

module.exports = SetupRabbitMQHelper;

// setup external EventEmitter
require('util').inherits(module.exports, require('events').EventEmitter);<commit_msg>nice little fun stuff for optional params<commit_after>'use strict';

// module entry point
var SetupRabbitMQHelper = function setupRabbitMQHelper(options, eventHandlers) {
    if (!(this instanceof SetupRabbitMQHelper)) {
        return new SetupRabbitMQHelper(options, eventHandlers);
    }

    if (!eventHandlers) {
        eventHandlers = options || {};
        options = null;
    }
    options = options || {};

    var _initialize = true;
    var _this = this;

    var tool = require('cloneextend');
    var config = tool.cloneextend({
        connection: {
            host: '127.0.0.1',
            options: {
                heartbeat: 5,
                reconnect: true
            }
        },
        exchange: {
            name: 'testing-exchange',
            options: {
                type: 'direct',
                durable: false,
                autoDelete: false
            }
        },
        queue: {
            name: 'testing-queue',
            options: {
                routingKey: 'event-routing',
                durable: true,
                autoDelete: false,
                prefetchCount: 1
            }
        }
    }, options.config || {});

    require('./validators/config')(config, function (err) {
        if (err) {
            throw new Error(err);
        }
        else {
            // setup internal EventEmitter
            var InternalEventsHandler = function internalEventsHandler() {
                if (!(this instanceof InternalEventsHandler)) {
                    return new InternalEventsHandler();
                }
            };
            require('util').inherits(InternalEventsHandler, require('events').EventEmitter);

            var internalEvents = new InternalEventsHandler();

            // var logger = options.logger || console;

            // setup storage variables
            var connection;
            var exchange;
            var queue;
            var queueHandler;
            var helpers;

            var currentStatus = 'initializing';

            // emit external errors
            var EmitError = function emitError(err) {
                // if you don't listen to this it is not a good idea
                _this.emit('error', {
                    err: err,
                    message: err.message || 'no_message'
                });
            };

            var EnsureExchange = function ensureExchange() {
                exchange = connection.exchange(config.exchange.name, config.exchange.options, function (err) {
                    if (err) {
                        helpers.emitError(err);
                    }
                });
                if (exchange) {
                    internalEvents.emit('exchange-ensured');
                }
            };

            var EnsureQueue = function ensureQueue() {
                queue = connection.queue(config.queue.name, config.queue.options, function (err) {
                    if (err) {
                        helpers.emitError(err);
                    }
                });
                if (queue) {
                    internalEvents.emit('queue-ensured');
                }
            };

            var BindQueueToExchange = function bindQueueToExchange(emitEnsured) {
                queue.bindQueue(exchange.name, config.queue.options.routingKey, function () {
                    internalEvents.emit('queue-bound');
                    var previousStatus = currentStatus;
                    currentStatus = 'connected';
                    if (emitEnsured) {
                        switch (previousStatus) {
                        case 'initializing':
                            _this.emit('initialized');
                            break;
                        case 'reconnecting':
                            _this.emit('reconnected');
                            break;
                        }
                    }
                });
            };

            var BindListenerToQueue = function bindListenerToQueue() {
                if (queueHandler) {
                    queue.listen({
                        ack: true,
                        prefetchCount: config.queue.options.prefetchCount
                    }, function (message, ack, headers, fields, m) {
                        queueHandler({
                            message: message,
                            ack: ack,
                            headers: headers,
                            fields: fields,
                            m: m
                        });
                    });
                    _this.emit('queue-listening');
                }
            };

            // setup helper functions
            helpers = {
                emitError: EmitError,
                ensureExchange: EnsureExchange,
                ensureQueue: EnsureQueue,
                bindQueueToExchange: BindQueueToExchange,
                bindListenerToQueue: BindListenerToQueue
            };

            // setup exported functions
            var SetQueueHandler = function setQueueHandler(handler) {
                if (queueHandler) {
                    helpers.emitError(new Error('handler_already_set'));
                }
                else {
                    queueHandler = handler;
                    helpers.bindListenerToQueue();
                }
            };

            var PublishMessage = function publishMessage(message, messageOptions, callback) {
                if ('function' === typeof messageOptions) {
                    callback = messageOptions;
                    messageOptions = {};
                }
                messageOptions = messageOptions || {};
                if (undefined === messageOptions.persistent) {
                    messageOptions.persistent = true;
                }
                if (undefined === messageOptions.mandatory) {
                    messageOptions.mandatory = true;
                }
                if ('connected' === currentStatus) {
                    var routingKey = messageOptions.routingKey || config.queue.options.routingKey;
                    exchange.send(routingKey, message, messageOptions, callback);
                }
                else {
                    callback('rabbitmq_helper_not_ensured');
                }
            };

            // initializer
            var InitializeRabbitMQHelper = function initializeRabbitMQHelper() {
                if (!(this instanceof InitializeRabbitMQHelper)) {
                    return new InitializeRabbitMQHelper();
                }
                internalEvents.on('connected', function () {
                    connection.on('connection error', function (err) {
                        connection.channel = null;
                        if ('Unexpected close' === err.message) {
                            currentStatus = 'reconnecting';
                        }
                        if (err) {
                            helpers.emitError(err);
                        }
                    });
                    connection.on('reconnected', function () {
                        helpers.ensureExchange();
                    });
                    helpers.ensureExchange();
                });
                internalEvents.on('exchange-ensured', function () {
                    helpers.ensureQueue();
                });
                internalEvents.on('queue-ensured', function () {
                    helpers.bindQueueToExchange(true);
                });
                internalEvents.on('queue-bound', function () {
                    helpers.bindListenerToQueue();
                });
                // connect and fire off events
                connection = _this.rabbit('amqp://' + config.connection.host, config.connection.options).connect(function (err) {
                    if (err) {
                        helpers.emitError(err);
                    }
                });
                if (connection) {
                    internalEvents.emit('connected');
                }
                return _this;
            };

            if (eventHandlers) {
                for (var key in eventHandlers) {
                    _this.on(key, eventHandlers[key]);
                }
            }
            _this.rabbit = require('rabbit-wrapper');
            // variables that need to be different/changeable can be exported
            // as noted at the top of this file any variables there are global to all instantiations

            _this.setQueueHandler = SetQueueHandler;
            _this.publishMessage = PublishMessage;
            // exports functions if needed
            if (options.mocking) {
                _initialize = false;
                _this.initializeRabbitMQHelper = InitializeRabbitMQHelper;
            }
            if (_initialize) {
                return new InitializeRabbitMQHelper();
            }
            return _this;
        }
    });
};

module.exports = SetupRabbitMQHelper;

// setup external EventEmitter
require('util').inherits(module.exports, require('events').EventEmitter);<|endoftext|>


<commit_before>module.exports = {
    'junit': require('./junit'),
    'default': require('./default'),
    'skip_passed': require('./skip_passed'),
    'minimal': require('./minimal'),
    'html': require('./html'),
    'eclipse': require('./eclipse'),
    'machineout': require('./machineout'),
    'tap': require('./tap'),
    'nested': require('./nested'),
    'verbose' : require('./verbose'),
    'lcov' : require('./lcov')
    // browser test reporter is not listed because it cannot be used
    // with the command line tool, only inside a browser.
};
<commit_msg>A little trick to stop tap breaking browserify<commit_after>// This is a hack to make browserify skip tap
var tap;
try {
    tap = require('./' + 'tap');
} catch (ex) {
    tap = {
        run: function() {
            throw new Error('Sorry, tap reporter not available');
        }
    };
}

module.exports = {
    'junit': require('./junit'),
    'default': require('./default'),
    'skip_passed': require('./skip_passed'),
    'minimal': require('./minimal'),
    'html': require('./html'),
    'eclipse': require('./eclipse'),
    'machineout': require('./machineout'),
    'tap': tap,
    'nested': require('./nested'),
    'verbose' : require('./verbose'),
    'lcov' : require('./lcov')
    // browser test reporter is not listed because it cannot be used
    // with the command line tool, only inside a browser.
};
<|endoftext|>


<commit_before>'use strict';

const chalk = require('chalk');
const ttest = require('ttest');
const Table = require('cli-table');
const union = require('lodash.union');

function getMetrics (result) {
  return union.apply(null, Object.keys(result).map(key => Object.keys(result[key])));
}

function table (result, headings) {
  let head, rows;
  headings = headings || Object.keys(result) || [];
  const metrics = getMetrics(result);
  const urls = Object.keys(result);
  if (urls.length === 1) {
    result = result[urls[0]];
    head = ['metric', 'mean', 'min', 'max'];
    rows = metrics.map((metric) => {
      const stddev = typeof result[metric].stddev === 'number' ? `(+/-${result[metric].stddev.toFixed(3)})` : '';
      let mean = result[metric].mean;
      if (!mean && result[metric].raw.length === 1) {
        mean = result[metric].raw[0];
      }
      return [
        metric,
        `${mean.toFixed(3)} ${stddev}`,
        result[metric].min.toFixed(3),
        result[metric].max.toFixed(3)
      ]
    });
  } else {
    head = ['metric'];
    urls.forEach((r, i) => head.push(headings[i] || i));
    if (urls.length === 2) {
      head.push('p < 0.05');
    }
    rows = metrics.map((metric) => {
      const row = urls.map((url) => {
        return result[url][metric] && result[url][metric].mean;
      });
      const min = Math.min.apply(Math, row);
      const max = Math.max.apply(Math, row);

      const colored = urls.reduce((r, url) => {
        const m = result[url][metric];
        let value = typeof m.mean === 'number' ? m.mean.toFixed(3) : '-';
        const stddev = typeof m.stddev === 'number' ? m.stddev.toFixed(3) : '-';
        if (m === min) {
          value = chalk.green(value);
        } else if (m === max) {
          value = chalk.red(value);
        }
        r.push(`${value} (±${stddev})`);
        return r;
      }, [metric]);

      // if doing a two-up comparison, add a column for p-values
      if (urls.length === 2) {
        if (result[urls[0]][metric] && result[urls[1]][metric] && result[urls[0]][metric].raw.length > 1 && result[urls[1]][metric].raw.length > 1) {
          const tt = ttest(result[urls[0]][metric].raw, result[urls[1]][metric].raw, { varEqual: true });
          if (!tt.valid()) {
            colored.push(tt.pValue().toFixed(3) + ' ' + chalk.green('✔'));
          } else {
            colored.push(tt.pValue().toFixed(3) + ' ' + chalk.red('✘'));
          }
        } else {
          colored.push('-');
        }
      }
      return colored;
    });
  }

  const table = new Table({ head: head });
  table.push.apply(table, rows);
  return table.toString();
}

module.exports = table;
<commit_msg>Linting<commit_after>'use strict';

const chalk = require('chalk');
const ttest = require('ttest');
const Table = require('cli-table');
const union = require('lodash.union');

function getMetrics (result) {
  return union.apply(null, Object.keys(result).map(key => Object.keys(result[key])));
}

function table (result, headings) {
  let head, rows;
  headings = headings || Object.keys(result) || [];
  const metrics = getMetrics(result);
  const urls = Object.keys(result);
  if (urls.length === 1) {
    result = result[urls[0]];
    head = ['metric', 'mean', 'min', 'max'];
    rows = metrics.map((metric) => {
      const stddev = typeof result[metric].stddev === 'number' ? `(+/-${result[metric].stddev.toFixed(3)})` : '';
      let mean = result[metric].mean;
      if (!mean && result[metric].raw.length === 1) {
        mean = result[metric].raw[0];
      }
      return [
        metric,
        `${mean.toFixed(3)} ${stddev}`,
        result[metric].min.toFixed(3),
        result[metric].max.toFixed(3)
      ];
    });
  } else {
    head = ['metric'];
    urls.forEach((r, i) => head.push(headings[i] || i));
    if (urls.length === 2) {
      head.push('p < 0.05');
    }
    rows = metrics.map((metric) => {
      const row = urls.map((url) => {
        return result[url][metric] && result[url][metric].mean;
      });
      const min = Math.min.apply(Math, row);
      const max = Math.max.apply(Math, row);

      const colored = urls.reduce((r, url) => {
        const m = result[url][metric];
        let value = typeof m.mean === 'number' ? m.mean.toFixed(3) : '-';
        const stddev = typeof m.stddev === 'number' ? m.stddev.toFixed(3) : '-';
        if (m === min) {
          value = chalk.green(value);
        } else if (m === max) {
          value = chalk.red(value);
        }
        r.push(`${value} (±${stddev})`);
        return r;
      }, [metric]);

      // if doing a two-up comparison, add a column for p-values
      if (urls.length === 2) {
        const result0 = result[urls[0]][metric];
        const result1 = result[urls[1]][metric];
        if (result0 && result1 && result0.raw.length > 1 && result1.raw.length > 1) {
          const tt = ttest(result0.raw, result1.raw, { varEqual: true });
          if (!tt.valid()) {
            colored.push(`${tt.pValue().toFixed(3)} ${chalk.green('✔')}`);
          } else {
            colored.push(`${tt.pValue().toFixed(3)} ${chalk.red('✘')}`);
          }
        } else {
          colored.push('-');
        }
      }
      return colored;
    });
  }

  const table = new Table({ head: head });
  table.push.apply(table, rows);
  return table.toString();
}

module.exports = table;
<|endoftext|>


<commit_before>'use strict';
const BigPipeStream = require('./streams/big-pipe-stream');
const Fragment = require('./fragment');
const PassThroughStream = require('stream').PassThrough;
const StringifierStream = require('./streams/stringifier-stream');
const FRAGMENT_EVENTS = ['start', 'response', 'end', 'error', 'timeout'];

module.exports = function processRequest (options, request, response) {

    this.emit('start', request);

    const fetchContext = options.fetchContext;
    const fetchTemplate = options.fetchTemplate;
    const filterHeaders = options.filterHeaders;
    const cdnUrl = options.cdnUrl;
    const handleTag = options.handleTag;
    const parseTemplate = options.parseTemplate;

    const bigPipeStream = new BigPipeStream();
    const contextPromise = fetchContext(request).catch((err) => {
        this.emit('context:error', request, err);
        return {};
    });
    const forceSmartPipe = options.forceSmartPipe(request);
    const resultStream = new PassThroughStream();
    const templatePromise = fetchTemplate(request, parseTemplate);

    let shouldWriteHead = true;
    let primaryId;

    contextPromise.then((context) => {

        const templateStream = new StringifierStream((tag) => {

            if (!tag.name && !tag.closingTag) {
                // end of body tag
                return bigPipeStream;
            }

            if (tag.name === options.fragmentTag) {
                const fragment = new Fragment(tag, context, forceSmartPipe, cdnUrl);

                FRAGMENT_EVENTS.forEach((eventName) => {
                    fragment.on(eventName, (function () {
                        const prefixedName = 'fragment:' + eventName;
                        const args = Array.from(arguments);
                        const prefixedArgs = [prefixedName, request, fragment].concat(args);
                        this.emit.apply(this, prefixedArgs);
                    }).bind(this));
                });

                if (fragment.async) {
                    bigPipeStream.write(fragment.asyncStream);
                }

                if (fragment.primary && !primaryId) {
                    primaryId = fragment.id;
                    shouldWriteHead = false;
                }
                fragment.on('response', (statusCode, headers) => {
                    if (fragment.id === primaryId) {
                        this.emit('response', request, statusCode, {
                            'Content-Type': 'text/html',
                            'Location': headers.location
                        });
                        response.writeHead(
                            statusCode,
                            {
                                'Content-Type': 'text/html',
                                'Location': headers.location
                            }
                        );
                        let contentLength = 0;
                        resultStream.on('data', (chunk) => contentLength += chunk.length);
                        resultStream.on('end', () => {
                            this.emit('end', request, contentLength);
                        });
                        resultStream.pipe(response);
                    }
                });
                fragment.on('error', (err) => {
                    if (fragment.id === primaryId) {
                        this.emit('primary:error', request, fragment, err);
                        response.writeHead(500, {'Content-Type': 'text/html'});
                        response.end();
                    }
                });
                return fragment.fetch(
                    filterHeaders(request.headers)
                );
            }

            return handleTag(tag);
        });

        templateStream.on('finish', () => {
            bigPipeStream.end();
            if (shouldWriteHead) {
                shouldWriteHead = false;
                this.emit('response', request, 200, {'Content-Type': 'text/html'});
                response.writeHead(200, {'Content-Type': 'text/html'});
                let contentLength = 0;
                resultStream.on('data', (chunk) => contentLength += chunk.length);
                resultStream.on('end', () => {
                    this.emit('end', request, contentLength);
                });
                resultStream.pipe(response);
            }
        });

        templateStream.on('error', (err) => {
            this.emit('template:error', request, err);
            if (shouldWriteHead) {
                shouldWriteHead = false;
                response.writeHead(500, {'Content-Type': 'text/plain'});
            }
            response.end();
        });

        templatePromise
            .then((template) => {
                template.pipe(templateStream).pipe(resultStream);
            })
            .catch((err) => {
                this.emit('template:error', request, err);
                if (shouldWriteHead) {
                    shouldWriteHead = false;
                    response.writeHead(500, {'Content-Type': 'text/plain'});
                }
                response.end();
            });
    });
};
<commit_msg>Added comment<commit_after>'use strict';
const BigPipeStream = require('./streams/big-pipe-stream');
const Fragment = require('./fragment');
const PassThroughStream = require('stream').PassThrough;
const StringifierStream = require('./streams/stringifier-stream');
const FRAGMENT_EVENTS = ['start', 'response', 'end', 'error', 'timeout'];

module.exports = function processRequest (options, request, response) {

    this.emit('start', request);

    const fetchContext = options.fetchContext;
    const fetchTemplate = options.fetchTemplate;
    const filterHeaders = options.filterHeaders;
    const cdnUrl = options.cdnUrl;
    const handleTag = options.handleTag;
    const parseTemplate = options.parseTemplate;

    const bigPipeStream = new BigPipeStream();
    const contextPromise = fetchContext(request).catch((err) => {
        this.emit('context:error', request, err);
        return {};
    });
    const forceSmartPipe = options.forceSmartPipe(request);
    const resultStream = new PassThroughStream();
    const templatePromise = fetchTemplate(request, parseTemplate);

    let shouldWriteHead = true;
    let primaryId;

    contextPromise.then((context) => {

        const templateStream = new StringifierStream((tag) => {

            if (!tag.name && !tag.closingTag) {
                // end of body tag
                return bigPipeStream;
            }

            if (tag.name === options.fragmentTag) {
                const fragment = new Fragment(tag, context, forceSmartPipe, cdnUrl);

                FRAGMENT_EVENTS.forEach((eventName) => {
                    fragment.on(eventName, (function () {
                        // this has to be a function, because
                        // arrow functions don't have `arguments`
                        const prefixedName = 'fragment:' + eventName;
                        const args = Array.from(arguments);
                        const prefixedArgs = [prefixedName, request, fragment].concat(args);
                        this.emit.apply(this, prefixedArgs);
                    }).bind(this));
                });

                if (fragment.async) {
                    bigPipeStream.write(fragment.asyncStream);
                }

                if (fragment.primary && !primaryId) {
                    primaryId = fragment.id;
                    shouldWriteHead = false;
                }
                fragment.on('response', (statusCode, headers) => {
                    if (fragment.id === primaryId) {
                        this.emit('response', request, statusCode, {
                            'Content-Type': 'text/html',
                            'Location': headers.location
                        });
                        response.writeHead(
                            statusCode,
                            {
                                'Content-Type': 'text/html',
                                'Location': headers.location
                            }
                        );
                        let contentLength = 0;
                        resultStream.on('data', (chunk) => contentLength += chunk.length);
                        resultStream.on('end', () => {
                            this.emit('end', request, contentLength);
                        });
                        resultStream.pipe(response);
                    }
                });
                fragment.on('error', (err) => {
                    if (fragment.id === primaryId) {
                        this.emit('primary:error', request, fragment, err);
                        response.writeHead(500, {'Content-Type': 'text/html'});
                        response.end();
                    }
                });
                return fragment.fetch(
                    filterHeaders(request.headers)
                );
            }

            return handleTag(tag);
        });

        templateStream.on('finish', () => {
            bigPipeStream.end();
            if (shouldWriteHead) {
                shouldWriteHead = false;
                this.emit('response', request, 200, {'Content-Type': 'text/html'});
                response.writeHead(200, {'Content-Type': 'text/html'});
                let contentLength = 0;
                resultStream.on('data', (chunk) => contentLength += chunk.length);
                resultStream.on('end', () => {
                    this.emit('end', request, contentLength);
                });
                resultStream.pipe(response);
            }
        });

        templateStream.on('error', (err) => {
            this.emit('template:error', request, err);
            if (shouldWriteHead) {
                shouldWriteHead = false;
                response.writeHead(500, {'Content-Type': 'text/plain'});
            }
            response.end();
        });

        templatePromise
            .then((template) => {
                template.pipe(templateStream).pipe(resultStream);
            })
            .catch((err) => {
                this.emit('template:error', request, err);
                if (shouldWriteHead) {
                    shouldWriteHead = false;
                    response.writeHead(500, {'Content-Type': 'text/plain'});
                }
                response.end();
            });
    });
};
<|endoftext|>