<commit_before>import path from 'path'
import styled from 'styled-components'
import pump from 'pump'
import through from 'through2'
import React from 'react'
import randombytes from 'randombytes'
import {ipcRenderer} from 'electron'

import MapEditor from './MapEditor'
import replicate from '../lib/replicate'
import Form from './Form'
import View from './View'
import i18n from '../lib/i18n'

// turn the messages into strings once
// so the function isn't called for every row
var messages = {
  'replication-data-complete': i18n('replication-data-complete'),
  'replication-started': i18n('replication-started'),
  'replication-complete': i18n('replication-complete'),
  'replication-progress': i18n('replication-progress')
}

var SyncButton = styled.button`
  background-color: orange;
  padding: 0px 20px;
`

var Buttons = styled.div`
  display: flex;
  flex-direction: row;
  justify-content: space-between;
`

var Subtitle = styled.div`
  background-color: var(--dark-bg-color);
  color: white;
  vertical-align: middle;
  padding: 5px ;
`

var LoadingText = styled.div`
  color: grey;
  text-align: center;
  font-style: italic;
  font-size: 24px;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
`

var Target = styled.li`
  background-color: white;
  padding: 20px;
  border-bottom: 1px solid grey;
  display: flex;
  justify-content: space-between;
  line-height: 30px;
  .target {
    vertical-align: middle;
    font-weight: bold;
    font-size: 16px;
  }
  .info {
    padding-left: 10px;
    font-weight: normal;
    font-size: 14px;
    font-style: italic;
  }
}
`

var TargetsDiv = styled.div`
  max-width: 700px;
  color: black;
  button {
    margin: 10px 0px !important;
  }
`

export default class SyncView extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      targets: [],
      wifis: {},
      files: {}
    }
    this.streams = {}
    this.selectFile = this.selectFile.bind(this)
  }

  replicate (target) {
    var self = this
    if (!target) return
    var stream = replicate.start(target)
    var id = randombytes(16).toString('hex')
    this.streams[id] = stream
    stream.on('data', function (data) {
      try {
        var row = JSON.parse(data)
      } catch (err) {
        console.error(err)
        return
      }
      var status = row.topic
      var message = messages[status] || row.message
      // TODO: this is clunky, improve status rendering via external module?
      var msg = { status, message, target }
      if (target.host) self.state.wifis[target.host] = msg
      if (target.filename) self.state.files[target.filename] = msg
      if (status !== 'replication-progress') self.setState({wifis: self.state.wifis, files: self.state.files})
    })

    stream.on('error', function (err) {
      if (err) console.error(err)
    })

    stream.on('end', function () {
      delete self.streams[id]
    })
  }

  componentWillUnmount () {
    var self = this
    Object.keys(this.streams).map((k) => self.streams[k].destroy())
    this.streams = {}
    clearInterval(this.interval)
    ipcRenderer.removeListener('select-file', this.selectFile)
  }

  selectFile (event, filename) {
    if (!filename) return
    this.replicate({filename})
  }

  componentDidMount () {
    this.interval = setInterval(this.updateTargets.bind(this), 1000)
    ipcRenderer.on('select-file', this.selectFile)
    replicate.announce(function (err) {
      if (err) console.error(err)
    })
  }

  updateTargets () {
    var self = this
    replicate.getTargets(function (err, targets) {
      if (err) return console.error(err)
      targets = JSON.parse(targets)
      self.setState({targets})
    })
  }

  selectExisting (event) {
    event.preventDefault()
    ipcRenderer.send('open-file')
  }

  selectNew (event) {
    event.preventDefault()
    ipcRenderer.send('save-file')
  }

  render () {
    var self = this
    var {message, status, targets, wifis, files} = this.state
    const {filename} = this.props
    if (filename) this.selectFile(filename)
    var disabled = Object.keys(self.streams).length > 0

    return (
      <View>

{i18n('sync-database-lead')}

        <TargetsDiv>
        {targets.length === 0
          ? <Subtitle>Searching for devices…</Subtitle>
          : <Subtitle>Available Devices</Subtitle>
        }

            {targets.map(function (t) {
              if (t.name === 'localhost') return
              return (
                <Target key={t.host}>

                    {t.name}
                    via WiFi

                  {wifis[t.host] ?
{wifis[t.host].message}
 :
                    <SyncButton onClick={self.replicate.bind(self, t)}>
                      arrow
                    </SyncButton>
                  }
                </Target>
              )
            })}
            {Object.keys(files).map(function (k) {
              var t = files[k]
              return (
                <Target key={t.target.filename}>

                    {path.basename(t.target.filename)}
                    via File

{t.message}

                </Target>
              )
            })}

          <Form method='POST'>
            <input type='hidden' name='source' />
            <Buttons>
              <button className='big' onClick={this.selectExisting}>

                  {i18n('sync-database-open-button')}…

              </button>
              <button className='big' onClick={this.selectNew}>

                  {i18n('sync-database-new-button')}…

              </button>
            </Buttons>
          </Form>
        </TargetsDiv>
      </View>
    )
  }
}
<commit_msg>use name as unique id, not host, so most recent peer is present<commit_after>import path from 'path'
import styled from 'styled-components'
import pump from 'pump'
import through from 'through2'
import React from 'react'
import randombytes from 'randombytes'
import {ipcRenderer} from 'electron'

import MapEditor from './MapEditor'
import replicate from '../lib/replicate'
import Form from './Form'
import View from './View'
import i18n from '../lib/i18n'

// turn the messages into strings once
// so the function isn't called for every row
var messages = {
  'replication-data-complete': i18n('replication-data-complete'),
  'replication-started': i18n('replication-started'),
  'replication-complete': i18n('replication-complete'),
  'replication-progress': i18n('replication-progress')
}

var SyncButton = styled.button`
  background-color: orange;
  padding: 0px 20px;
`

var Buttons = styled.div`
  display: flex;
  flex-direction: row;
  justify-content: space-between;
`

var Subtitle = styled.div`
  background-color: var(--dark-bg-color);
  color: white;
  vertical-align: middle;
  padding: 5px ;
`

var LoadingText = styled.div`
  color: grey;
  text-align: center;
  font-style: italic;
  font-size: 24px;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
`

var Target = styled.li`
  background-color: white;
  padding: 20px;
  border-bottom: 1px solid grey;
  display: flex;
  justify-content: space-between;
  line-height: 30px;
  .target {
    vertical-align: middle;
    font-weight: bold;
    font-size: 16px;
  }
  .info {
    padding-left: 10px;
    font-weight: normal;
    font-size: 14px;
    font-style: italic;
  }
}
`

var TargetsDiv = styled.div`
  max-width: 700px;
  color: black;
  button {
    margin: 10px 0px !important;
  }
`

export default class SyncView extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      targets: [],
      wifis: {},
      files: {}
    }
    this.streams = {}
    this.selectFile = this.selectFile.bind(this)
  }

  replicate (target) {
    var self = this
    if (!target) return
    var stream = replicate.start(target)
    var id = randombytes(16).toString('hex')
    this.streams[id] = stream
    stream.on('data', function (data) {
      try {
        var row = JSON.parse(data)
      } catch (err) {
        console.error(err)
        return
      }
      var status = row.topic
      var message = messages[status] || row.message
      // TODO: this is clunky, improve status rendering via external module?
      var msg = { status, message, target }
      if (target.name) self.state.wifis[target.name] = msg
      if (target.filename) self.state.files[target.filename] = msg
      if (status !== 'replication-progress') self.setState({wifis: self.state.wifis, files: self.state.files})
    })

    stream.on('error', function (err) {
      if (err) console.error(err)
    })

    stream.on('end', function () {
      delete self.streams[id]
    })
  }

  componentWillUnmount () {
    var self = this
    Object.keys(this.streams).map((k) => self.streams[k].destroy())
    this.streams = {}
    clearInterval(this.interval)
    ipcRenderer.removeListener('select-file', this.selectFile)
  }

  selectFile (event, filename) {
    if (!filename) return
    this.replicate({filename})
  }

  componentDidMount () {
    this.interval = setInterval(this.updateTargets.bind(this), 1000)
    ipcRenderer.on('select-file', this.selectFile)
    replicate.announce(function (err) {
      if (err) console.error(err)
    })
  }

  updateTargets () {
    var self = this
    replicate.getTargets(function (err, targets) {
      if (err) return console.error(err)
      targets = JSON.parse(targets)
      self.setState({targets})
    })
  }

  selectExisting (event) {
    event.preventDefault()
    ipcRenderer.send('open-file')
  }

  selectNew (event) {
    event.preventDefault()
    ipcRenderer.send('save-file')
  }

  render () {
    var self = this
    var {message, status, targets, wifis, files} = this.state
    const {filename} = this.props
    if (filename) this.selectFile(filename)
    var disabled = Object.keys(self.streams).length > 0

    return (
      <View>

{i18n('sync-database-lead')}

        <TargetsDiv>
        {targets.length === 0
          ? <Subtitle>Searching for devices…</Subtitle>
          : <Subtitle>Available Devices</Subtitle>
        }

            {targets.map(function (t) {
              if (t.name === 'localhost') return
              return (
                <Target key={t.name}>

                    {t.name}
                    via WiFi

                  {wifis[t.name] ?
{wifis[t.name].message}
 :
                    <SyncButton onClick={self.replicate.bind(self, t)}>
                      arrow
                    </SyncButton>
                  }
                </Target>
              )
            })}
            {Object.keys(files).map(function (k) {
              var t = files[k]
              return (
                <Target key={t.target.filename}>

                    {path.basename(t.target.filename)}
                    via File

{t.message}

                </Target>
              )
            })}

          <Form method='POST'>
            <input type='hidden' name='source' />
            <Buttons>
              <button className='big' onClick={this.selectExisting}>

                  {i18n('sync-database-open-button')}…

              </button>
              <button className='big' onClick={this.selectNew}>

                  {i18n('sync-database-new-button')}…

              </button>
            </Buttons>
          </Form>
        </TargetsDiv>
      </View>
    )
  }
}
<|endoftext|>


<commit_before>'use strict'

let tls = require('tls')
let url = require('url')
let tty = require('tty')
let stream = require('stream')
let cli = require('heroku-cli-util')
let helpers = require('../lib/helpers')

const http = require('https')
const net = require('net')
const spawn = require('child_process').spawn

/** Represents a dyno process */
class Dyno {
  /**
   * @param {Object} options
   * @param {Object} options.heroku - instance of heroku-client
   * @param {boolean} options.exit-code - get exit code from process
   * @param {string} options.command - command to run
   * @param {string} options.app - app to run dyno on
   * @param {string} options.attach - attach to dyno
   * @param {string} options.size - size of dyno to create
   * @param {boolean} options.no-tty - force not to use a tty
   * @param {Object} options.env - dyno environment variables
  */
  constructor (opts) {
    this.opts = opts
    this.heroku = opts.heroku
    if (this.opts.showStatus === undefined) this.opts.showStatus = true
  }

  /**
   * Starts the dyno
   * @returns {Promise} promise resolved when dyno process is created
   */
  start () {
    let command = this.opts['exit-code'] ? `${this.opts.command}; echo "\uFFFF heroku-command-exit-status $?"` : this.opts.command
    let start = this.heroku.request({
      path: this.opts.dyno ? `/apps/${this.opts.app}/dynos/${this.opts.dyno}` : `/apps/${this.opts.app}/dynos`,
      method: 'POST',
      headers: {
        Accept: this.opts.dyno ? 'application/vnd.heroku+json; version=3.run-inside' : 'application/vnd.heroku+json; version=3'
      },
      body: {
        command: command,
        attach: this.opts.attach,
        size: this.opts.size,
        env: this._env(),
        force_no_tty: this.opts['no-tty']
      }
    })
    .then(dyno => {
      this.dyno = dyno
      if (this.dyno.name) this.opts.dyno = this.dyno.name
      if (this.opts.attach || this.opts.dyno) return this.attach()
      else if (this.opts.showStatus) cli.action.done(this._status('done'))
    })

    if (this.opts.showStatus) {
      return cli.action(`Running ${cli.color.cyan.bold(this.opts.command)} on ${cli.color.app(this.opts.app)}`, {success: false}, start)
    } else return start
  }

  /**
   * Attaches stdin/stdout to dyno
   */
  attach () {
    this.uri = url.parse(this.dyno.attach_url)
    if (this.uri.protocol === 'http:' || this.uri.protocol === 'https:') {
      return this._ssh()
    } else {
      return this._rendezvous()
    }
  }

  _rendezvous () {
    return new Promise((resolve, reject) => {
      this.resolve = resolve
      this.reject = reject

      if (this.opts.showStatus) cli.action.status(this._status('starting'))
      let c = tls.connect(this.uri.port, this.uri.hostname, {rejectUnauthorized: this.heroku.options.rejectUnauthorized})
      c.setTimeout(1000 * 60 * 20)
      c.setEncoding('utf8')
      c.on('connect', () => {
        c.write(this.uri.path.substr(1) + '\r\n', () => {
          if (this.opts.showStatus) cli.action.status(this._status('connecting'))
        })
      })
      c.on('data', this._readData(c))
      c.on('close', () => {
        this.opts['exit-code'] ? this.reject('No exit code returned') : this.resolve()
        if (this.unpipeStdin) this.unpipeStdin()
      })
      c.on('error', this.reject)
      process.once('SIGINT', () => c.end())
    })
  }

  _ssh () {
    const interval = 30 * 1000
    const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

    return this.heroku.request({
      path: `/apps/${this.opts.app}/dynos/${this.opts.dyno}`,
      method: 'GET',
      headers: {Accept: 'application/vnd.heroku+json; version=3'}
    })
    .then(dyno => {
      this.dyno = dyno
      cli.action.done(this._status(this.dyno.state))

      if (this.dyno.state === 'starting' || this.dyno.state === 'up') return this._connect()
      else return wait(interval).then(this._ssh.bind(this))
    })
    .catch(() => {
      return wait(interval).then(this._ssh.bind(this))
    })
  }

  _connect () {
    return new Promise((resolve, reject) => {
      this.resolve = resolve
      this.reject = reject

      let options = this.uri
      options.headers = {'Connection': 'Upgrade', 'Upgrade': 'tcp'}
      options.rejectUnauthorized = false
      let r = http.request(options)
      r.end()

      r.on('error', this.reject)
      r.on('upgrade', (_, remote, head) => {
        let s = net.createServer((client) => {
          client.on('end', () => {
            s.close()
            this.resolve()
          })
          client.on('connect', () => s.close())

          client.on('error', () => this.reject)
          remote.on('error', () => this.reject)

          client.setNoDelay(true)
          remote.setNoDelay(true)

          remote.on('data', (data) => client.write(data))
          client.on('data', (data) => remote.write(data))
        })

        s.listen(null, 'localhost', () => this._handle(s.address()))
      })
    })
  }

  _handle (options) {
    const host = options.address
    const port = options.port

    if (this.opts.listen) {
      cli.console.log(`listening on port ${host}:${port} for ssh client`)
    } else {
      spawn('ssh', [host, '-p', port, '-oStrictHostKeyChecking=no', '-oUserKnownHostsFile=/dev/null', '-oServerAliveInterval=20'], {
        detached: false,
        stdio: 'inherit'
      })
    }
  }

  _env () {
    let c = this.opts.env ? helpers.buildEnvFromFlag(this.opts.env) : {}
    c.TERM = process.env.TERM
    if (tty.isatty(1)) {
      c.COLUMNS = process.stdout.columns
      c.LINES = process.stdout.rows
    }
    return c
  }

  _status (status) {
    let size = this.dyno.size ? ` (${this.dyno.size})` : ''
    return `${status}, ${this.dyno.name || this.opts.dyno}${size}`
  }

  _readData (c) {
    let firstLine = true
    return data => {
      // discard first line
      if (firstLine) {
        if (this.opts.showStatus) cli.action.done(this._status('up'))
        firstLine = false
        this._readStdin(c)
        return
      }
      data = data.replace('\r\n', '\n')
      let exitCode = data.match(/\uFFFF heroku-command-exit-status (\d+)/m)
      if (exitCode) {
        process.stdout.write(data.replace(/^\uFFFF heroku-command-exit-status \d+$\n?/m, ''))
        let code = parseInt(exitCode[1])
        if (code === 0) this.resolve()
        else {
          let err = new Error(`Process exited with code ${cli.color.red(code)}`)
          err.exitCode = code
          this.reject(err)
        }
        return
      }
      process.stdout.write(data)
    }
  }

  _readStdin (c) {
    let stdin = process.stdin
    stdin.setEncoding('utf8')
    if (stdin.unref) stdin.unref()
    if (tty.isatty(0)) {
      stdin.setRawMode(true)
      stdin.pipe(c)
      let sigints = []
      stdin.on('data', function (c) {
        if (c === '\u0003') sigints.push(new Date())
        sigints = sigints.filter(d => d > new Date() - 1000)
        if (sigints.length >= 4) {
          cli.error('forcing dyno disconnect')
          process.exit(1)
        }
      })
    } else {
      stdin.pipe(new stream.Transform({
        objectMode: true,
        transform: (chunk, _, next) => c.write(chunk, next),
        flush: done => c.write('\x04', done)
      }))
    }
  }
}

module.exports = Dyno
<commit_msg>set dyno name for attached dynos only (#25)<commit_after>'use strict'

let tls = require('tls')
let url = require('url')
let tty = require('tty')
let stream = require('stream')
let cli = require('heroku-cli-util')
let helpers = require('../lib/helpers')

const http = require('https')
const net = require('net')
const spawn = require('child_process').spawn

/** Represents a dyno process */
class Dyno {
  /**
   * @param {Object} options
   * @param {Object} options.heroku - instance of heroku-client
   * @param {boolean} options.exit-code - get exit code from process
   * @param {string} options.command - command to run
   * @param {string} options.app - app to run dyno on
   * @param {string} options.attach - attach to dyno
   * @param {string} options.size - size of dyno to create
   * @param {boolean} options.no-tty - force not to use a tty
   * @param {Object} options.env - dyno environment variables
  */
  constructor (opts) {
    this.opts = opts
    this.heroku = opts.heroku
    if (this.opts.showStatus === undefined) this.opts.showStatus = true
  }

  /**
   * Starts the dyno
   * @returns {Promise} promise resolved when dyno process is created
   */
  start () {
    let command = this.opts['exit-code'] ? `${this.opts.command}; echo "\uFFFF heroku-command-exit-status $?"` : this.opts.command
    let start = this.heroku.request({
      path: this.opts.dyno ? `/apps/${this.opts.app}/dynos/${this.opts.dyno}` : `/apps/${this.opts.app}/dynos`,
      method: 'POST',
      headers: {
        Accept: this.opts.dyno ? 'application/vnd.heroku+json; version=3.run-inside' : 'application/vnd.heroku+json; version=3'
      },
      body: {
        command: command,
        attach: this.opts.attach,
        size: this.opts.size,
        env: this._env(),
        force_no_tty: this.opts['no-tty']
      }
    })
    .then(dyno => {
      this.dyno = dyno
      if (this.opts.attach || this.opts.dyno) {
        if (this.dyno.name && this.opts.dyno === undefined) this.opts.dyno = this.dyno.name
        return this.attach()
      } else if (this.opts.showStatus) {
        cli.action.done(this._status('done'))
      }
    })

    if (this.opts.showStatus) {
      return cli.action(`Running ${cli.color.cyan.bold(this.opts.command)} on ${cli.color.app(this.opts.app)}`, {success: false}, start)
    } else return start
  }

  /**
   * Attaches stdin/stdout to dyno
   */
  attach () {
    this.uri = url.parse(this.dyno.attach_url)
    if (this.uri.protocol === 'http:' || this.uri.protocol === 'https:') {
      return this._ssh()
    } else {
      return this._rendezvous()
    }
  }

  _rendezvous () {
    return new Promise((resolve, reject) => {
      this.resolve = resolve
      this.reject = reject

      if (this.opts.showStatus) cli.action.status(this._status('starting'))
      let c = tls.connect(this.uri.port, this.uri.hostname, {rejectUnauthorized: this.heroku.options.rejectUnauthorized})
      c.setTimeout(1000 * 60 * 20)
      c.setEncoding('utf8')
      c.on('connect', () => {
        c.write(this.uri.path.substr(1) + '\r\n', () => {
          if (this.opts.showStatus) cli.action.status(this._status('connecting'))
        })
      })
      c.on('data', this._readData(c))
      c.on('close', () => {
        this.opts['exit-code'] ? this.reject('No exit code returned') : this.resolve()
        if (this.unpipeStdin) this.unpipeStdin()
      })
      c.on('error', this.reject)
      process.once('SIGINT', () => c.end())
    })
  }

  _ssh () {
    const interval = 30 * 1000
    const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

    return this.heroku.request({
      path: `/apps/${this.opts.app}/dynos/${this.opts.dyno}`,
      method: 'GET',
      headers: {Accept: 'application/vnd.heroku+json; version=3'}
    })
    .then(dyno => {
      this.dyno = dyno
      cli.action.done(this._status(this.dyno.state))

      if (this.dyno.state === 'starting' || this.dyno.state === 'up') return this._connect()
      else return wait(interval).then(this._ssh.bind(this))
    })
    .catch(() => {
      return wait(interval).then(this._ssh.bind(this))
    })
  }

  _connect () {
    return new Promise((resolve, reject) => {
      this.resolve = resolve
      this.reject = reject

      let options = this.uri
      options.headers = {'Connection': 'Upgrade', 'Upgrade': 'tcp'}
      options.rejectUnauthorized = false
      let r = http.request(options)
      r.end()

      r.on('error', this.reject)
      r.on('upgrade', (_, remote, head) => {
        let s = net.createServer((client) => {
          client.on('end', () => {
            s.close()
            this.resolve()
          })
          client.on('connect', () => s.close())

          client.on('error', () => this.reject)
          remote.on('error', () => this.reject)

          client.setNoDelay(true)
          remote.setNoDelay(true)

          remote.on('data', (data) => client.write(data))
          client.on('data', (data) => remote.write(data))
        })

        s.listen(null, 'localhost', () => this._handle(s.address()))
      })
    })
  }

  _handle (options) {
    const host = options.address
    const port = options.port

    if (this.opts.listen) {
      cli.console.log(`listening on port ${host}:${port} for ssh client`)
    } else {
      spawn('ssh', [host, '-p', port, '-oStrictHostKeyChecking=no', '-oUserKnownHostsFile=/dev/null', '-oServerAliveInterval=20'], {
        detached: false,
        stdio: 'inherit'
      })
    }
  }

  _env () {
    let c = this.opts.env ? helpers.buildEnvFromFlag(this.opts.env) : {}
    c.TERM = process.env.TERM
    if (tty.isatty(1)) {
      c.COLUMNS = process.stdout.columns
      c.LINES = process.stdout.rows
    }
    return c
  }

  _status (status) {
    let size = this.dyno.size ? ` (${this.dyno.size})` : ''
    return `${status}, ${this.dyno.name || this.opts.dyno}${size}`
  }

  _readData (c) {
    let firstLine = true
    return data => {
      // discard first line
      if (firstLine) {
        if (this.opts.showStatus) cli.action.done(this._status('up'))
        firstLine = false
        this._readStdin(c)
        return
      }
      data = data.replace('\r\n', '\n')
      let exitCode = data.match(/\uFFFF heroku-command-exit-status (\d+)/m)
      if (exitCode) {
        process.stdout.write(data.replace(/^\uFFFF heroku-command-exit-status \d+$\n?/m, ''))
        let code = parseInt(exitCode[1])
        if (code === 0) this.resolve()
        else {
          let err = new Error(`Process exited with code ${cli.color.red(code)}`)
          err.exitCode = code
          this.reject(err)
        }
        return
      }
      process.stdout.write(data)
    }
  }

  _readStdin (c) {
    let stdin = process.stdin
    stdin.setEncoding('utf8')
    if (stdin.unref) stdin.unref()
    if (tty.isatty(0)) {
      stdin.setRawMode(true)
      stdin.pipe(c)
      let sigints = []
      stdin.on('data', function (c) {
        if (c === '\u0003') sigints.push(new Date())
        sigints = sigints.filter(d => d > new Date() - 1000)
        if (sigints.length >= 4) {
          cli.error('forcing dyno disconnect')
          process.exit(1)
        }
      })
    } else {
      stdin.pipe(new stream.Transform({
        objectMode: true,
        transform: (chunk, _, next) => c.write(chunk, next),
        flush: done => c.write('\x04', done)
      }))
    }
  }
}

module.exports = Dyno
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Grid, Row, Col, PageHeader } from 'react-bootstrap';
import ReactTooltip from 'react-tooltip'

import Instrument from 'components/Instrument';
import WersiClient from 'modules/midi/WersiClient';
import MidiConfig from 'components/MidiConfig';

export default class WersiApp extends Component {
  constructor() {
    super();

    // Instrument reference
    this.instrument = null;

    // Create Wersi device client
    this.client = new WersiClient();
  }

  componentWillMount() {
  }

  render() {
    let midiConfig = (<MidiConfig
      client={this.client}
      url={"ws://localhost:9002"}
      token={"5UOfQAtnjnNIaZUWzpX2LLBkHNxrXALECEpj0ssklTM7ptYCuSOVQNn0qemO8Zat"}
      populateCallback={() => {
        if (this.instrument) {
          this.instrument.ready();
        }
      }}
    />);

    return (

            MK1 editor
            {this.props.version}

            wer.si

        {midiConfig}
        <Instrument
          instrumentAddress={WersiClient.ADDRESS.CV(0)}
          client={this.client}
          ref={(ref) => {
            this.instrument = ref;
          }}
        />

            This software is no way affiliated with, authorized, maintained, sponsored or endorsed with Wersi nor any of its affiliates or subsidiaries.

            Available as open source software under the AGPLv3 license @ wer.si.

    );
  }
}
<commit_msg>Changed localhost to sysexd.wer.si convenience alias.<commit_after>import React, { Component } from 'react';
import { Grid, Row, Col, PageHeader } from 'react-bootstrap';
import ReactTooltip from 'react-tooltip'

import Instrument from 'components/Instrument';
import WersiClient from 'modules/midi/WersiClient';
import MidiConfig from 'components/MidiConfig';

export default class WersiApp extends Component {
  constructor() {
    super();

    // Instrument reference
    this.instrument = null;

    // Create Wersi device client
    this.client = new WersiClient();
  }

  componentWillMount() {
  }

  render() {
    let midiConfig = (<MidiConfig
      client={this.client}
      url={"ws://sysexd.wer.si:9002"}
      token={"5UOfQAtnjnNIaZUWzpX2LLBkHNxrXALECEpj0ssklTM7ptYCuSOVQNn0qemO8Zat"}
      populateCallback={() => {
        if (this.instrument) {
          this.instrument.ready();
        }
      }}
    />);

    return (

            MK1 editor
            {this.props.version}

            wer.si

        {midiConfig}
        <Instrument
          instrumentAddress={WersiClient.ADDRESS.CV(0)}
          client={this.client}
          ref={(ref) => {
            this.instrument = ref;
          }}
        />

            This software is no way affiliated with, authorized, maintained, sponsored or endorsed with Wersi nor any of its affiliates or subsidiaries.

            Available as open source software under the AGPLv3 license @ wer.si.

    );
  }
}
<|endoftext|>


<commit_before>// Copyright (c) 2017, Matt Godbolt
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

const child_process = require('child_process'),
    path = require('path'),
    _ = require('underscore'),
    fs = require('fs'),
    logger = require('./logger').logger,
    treeKill = require('tree-kill'),
    execProps = require('./properties').propsFor('execution'),
    Graceful = require('node-graceful');

function setupOnError(stream, name) {
    if (stream === undefined) return;
    stream.on('error', err => {
        logger.error(`Error with ${name} stream:`, err);
    });
}

function executeDirect(command, args, options, filenameTransform) {
    // filename transform is expected to have been pre-applied by the caller.
    // it is passed through here only so clients can see it in the result.
    filenameTransform = filenameTransform || (x => x);
    options = options || {};
    const maxOutput = options.maxOutput || 1024 * 1024;
    const timeoutMs = options.timeoutMs || 0;
    const env = options.env ? options.env : _.clone(process.env);

    if (options.ldPath) {
        env.LD_LIBRARY_PATH = options.ldPath;
    }

    if (options.wrapper) {
        args = args.slice(0); // prevent mutating the caller's arguments
        args.unshift(command);
        command = options.wrapper;

        if (command.startsWith('./')) command = path.join(process.cwd(), command);
    }

    let okToCache = true;
    const cwd = options.customCwd ? options.customCwd : (
        (command.startsWith("/mnt") && process.env.wsl) ? process.env.winTmp : process.env.tmpDir
    );
    logger.debug("Execution", {type: "executing", command: command, args: args, env: env, cwd: cwd});
    // AP: Run Windows-volume executables in winTmp. Otherwise, run in tmpDir (which may be undefined).
    // https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options
    const child = child_process.spawn(command, args, {
        cwd: cwd,
        env: env,
        detached: process.platform === 'linux'
    });
    let running = true;

    const kill = options.killChild || (() => {
        if (running) treeKill(child.pid);
    });

    const streams = {
        stderr: "",
        stdout: "",
        truncated: false
    };
    let timeout;
    if (timeoutMs) timeout = setTimeout(() => {
        logger.warn(`Timeout for ${command} ${args} after ${timeoutMs}ms`);
        okToCache = false;
        kill();
        streams.stderr += "\nKilled - processing time exceeded";
    }, timeoutMs);

    function setupStream(stream, name) {
        if (stream === undefined) return;
        stream.on('data', data => {
            if (streams.truncated) return;
            const newLength = (streams[name].length + data.length);
            if ((maxOutput > 0) && (newLength > maxOutput)) {
                streams[name] = streams[name] + data.slice(0, maxOutput - streams[name].length);
                streams[name] += "\n[Truncated]";
                streams.truncated = true;
                kill();
                return;
            }
            streams[name] += data;
        });
        setupOnError(stream, name);
    }

    setupOnError(child.stdin, 'stdin');
    setupStream(child.stdout, 'stdout');
    setupStream(child.stderr, 'stderr');
    child.on('exit', code => {
        logger.debug("Execution", {type: 'exited', code: code});
        if (timeout !== undefined) clearTimeout(timeout);
        running = false;
    });
    return new Promise((resolve, reject) => {
        child.on('error', e => {
            logger.debug(`Execution error with ${command} args: ${args}:`, e);
            reject(e);
        });
        child.on('close', code => {
            // Being killed externally gives a NULL error code. Synthesize something different here.
            if (code === null) code = -1;
            if (timeout !== undefined) clearTimeout(timeout);
            const result = {
                code,
                okToCache,
                filenameTransform,
                stdout: streams.stdout,
                stderr: streams.stderr
            };
            logger.debug("Execution", {type: "executed", command: command, args: args, result: result});
            resolve(result);
        });
        if (child.stdin) {
            if (options.input) child.stdin.write(options.input);
            child.stdin.end();
        }
    });
}

// function msToFjTimeout(ms) {
//     const totalSecs = Math.round(ms / 1000);
//     return `${Math.floor(totalSecs / (60 * 60))}:${Math.floor(totalSecs / 60)}:${totalSecs % 60}`;
// }

function withFirejailTimeout(args, options) {
    if (options && options.timeoutMs) {
        // const ExtraWallClockLeewayMs = 1000;
        const ExtraCpuLeewayMs = 1500;
        return args.concat([
            // TODO: reinstate once we work out why this causes a 1s+ delay on every execution!
            // `--timeout=${msToFjTimeout(options.timeoutMs + ExtraWallClockLeewayMs)}`,
            `--rlimit-cpu=${Math.round((options.timeoutMs + ExtraCpuLeewayMs) / 1000)}`
        ]);
    }
    return args;
}

function sandboxFirejail(command, args, options) {
    logger.info("Sandbox execution via firejail", {command, args});
    const execPath = path.dirname(command);
    const execName = path.basename(command);
    const jailingOptions = withFirejailTimeout([
        '--quiet',
        '--deterministic-exit-code',
        '--terminate-orphans',
        '--profile=etc/firejail/sandbox.profile',
        `--private=${execPath}`,
        '--private-cwd']);

    if (options.ldPath) {
        jailingOptions.push(`--env=LD_LIBRARY_PATH=${options.ldPath}`);
        delete options.ldPath;
    }

    return executeDirect(
        execProps("firejail"),
        jailingOptions
            .concat([`./${execName}`])
            .concat(args),
        options);
}

const sandboxDispatchTable = {
    none: (command, args, options) => {
        logger.info("Sandbox execution (sandbox disabled)", {command, args});
        return executeDirect(command, args, options);
    },
    firejail: sandboxFirejail
};

function sandbox(command, args, options) {
    const type = execProps("sandboxType", "firejail");
    const dispatchEntry = sandboxDispatchTable[type];
    if (!dispatchEntry)
        return Promise.reject(`Bad sandbox type ${type}`);
    return dispatchEntry(command, args, options);
}

const wineSandboxName = "ce-wineserver";

function initialiseWine() {
    const wine = execProps("wine");
    if (!wine) {
        logger.info("WINE not configured");
        return Promise.resolve();
    }

    const server = execProps("wineServer");
    const firejail = execProps("executionType", "none") === "firejail" ? execProps("firejail") : null;
    const env = applyWineEnv({PATH: process.env.PATH});
    const prefix = env.WINEPREFIX;

    logger.info(`Initialising WINE in ${prefix}`);
    if (!fs.existsSync(prefix) || !fs.statSync(prefix).isDirectory()) {
        logger.info(`Creating directory ${prefix}`);
        fs.mkdirSync(prefix);
    }

    logger.info(`Killing any pre-existing wine-server`);
    let result = child_process.execSync(`${server} -k || true`, {env: env});
    logger.info(`Result: ${result}`);
    logger.info(`Waiting for any pre-existing server to stop...`);
    result = child_process.execSync(`${server} -w`, {env: env});
    logger.info(`Result: ${result}`);

    // We run a long-lived cmd process, to:
    // * test that WINE works
    // * be something which holds open a working firejail sandbox
    // All future WINE compiles go through the same sandbox.
    // We wait until the process has printed out some known good text, but don't wait
    // for it to exit (it won't, on purpose).

    let wineServer;
    if (firejail) {
        logger.info(`Starting a new, firejailed, long-lived wineserver complex`);
        wineServer = child_process.spawn(
            firejail,
            [
                "--quiet",
                "--profile=etc/firejail/wine.profile",
                "--private",
                `--name=${wineSandboxName}`,
                wine,
                "cmd"
            ],
            {env: env, detached: true});
        logger.info(`firejailed pid=${wineServer.pid}`);
    } else {
        logger.info(`Starting a new, long-lived wineserver complex ${server}`);
        wineServer = child_process.spawn(
            wine,
            ["cmd"],
            {env: env, detached: true});
        logger.info(`wineserver pid=${wineServer.pid}`);
    }

    wineServer.on('close', code => {
        logger.info(`WINE server complex exited with code ${code}`);
    });

    Graceful.on('exit', () => {
        const waitingPromises = [];

        function waitForExit(process, name) {
            return new Promise((resolve) => {
                process.on('close', () => {
                    logger.info(`Process '${name}' closed`);
                    resolve();
                });
            });
        }

        if (wineServer && !wineServer.killed) {
            logger.info('Shutting down WINE server complex');
            wineServer.kill();
            if (wineServer.killed) {
                waitingPromises.push(waitForExit(wineServer, "WINE server"));
            }
            wineServer = null;
        }
        return Promise.all(waitingPromises);
    });

    return new Promise((resolve, reject) => {
        setupOnError(wineServer.stdin, "stdin");
        setupOnError(wineServer.stdout, "stdout");
        setupOnError(wineServer.stderr, "stderr");
        const magicString = "!!EVERYTHING IS WORKING!!";
        wineServer.stdin.write(`echo ${magicString}`);

        let output = "";
        wineServer.stdout.on('data', data => {
            logger.info(`Output from wine server complex: ${data.toString().trim()}`);
            output += data;
            if (output.includes(magicString)) {
                resolve();
            }
        });
        wineServer.stderr.on('data',
            data => logger.info(`stderr output from wine server complex: ${data.toString().trim()}`));
        wineServer.on('error', e => {
            logger.error(`WINE server complex exited with error ${e}`);
            reject(e);
        });
        wineServer.on('close', code => {
            logger.info(`WINE server complex exited with code ${code}`);
            reject();
        });
    });
}

function applyWineEnv(env) {
    const prefix = execProps("winePrefix");
    env = env || {};
    // Force use of wine vcruntime (See change 45106c382)
    env.WINEDLLOVERRIDES = "vcruntime140=b";
    env.WINEDEBUG = "-all";
    env.WINEPREFIX = prefix;
    return env;
}

function needsWine(command) {
    return command.match(/\.exe$/i) && process.platform === 'linux';
}

function executeWineDirect(command, args, options) {
    options = options || {};
    options.env = applyWineEnv(options.env);
    args.unshift(command);
    return executeDirect(execProps("wine"), args, options);
}

function executeFirejail(command, args, options) {
    options = options || {};
    const firejail = execProps("firejail");
    const baseOptions = withFirejailTimeout(['--quiet', '--deterministic-exit-code', '--terminate-orphans'], options);
    if (needsWine(command)) {
        logger.debug("WINE execution via firejail", {command, args});
        options.env = applyWineEnv(options.env);
        args.unshift(command);
        command = execProps("wine");
        baseOptions.push('--profile=etc/firejail/wine.profile');
        baseOptions.push(`--join=${wineSandboxName}`);
        delete options.customCwd;
        baseOptions.push(command);
        return executeDirect(firejail, baseOptions.concat(args), options);
    }

    logger.debug("Regular execution via firejail", {command, args});
    baseOptions.push('--profile=etc/firejail/execute.profile');
    let filenameTransform;
    if (options.customCwd) {
        baseOptions.push(`--private=${options.customCwd}`);
        const replacement = options.customCwd;
        filenameTransform = opt => opt.replace(replacement, ".");
        args = args.map(filenameTransform);
        delete options.customCwd;
        // TODO: once it's supported properly in our patched firejail, make this option common to both customCwd and
        // non-customCwd code paths.
        baseOptions.push('--private-cwd');
    } else {
        baseOptions.push('--private');
    }
    baseOptions.push(command);
    return executeDirect(firejail, baseOptions.concat(args), options, filenameTransform);
}

function executeNone(command, args, options) {
    if (needsWine(command)) {
        return executeWineDirect(command, args, options);
    }
    return executeDirect(command, args, options);
}

const executeDispatchTable = {
    none: executeNone,
    firejail: executeFirejail
};

function execute(command, args, options) {
    const type = execProps("executionType", "none");
    const dispatchEntry = executeDispatchTable[type];
    if (!dispatchEntry)
        return Promise.reject(`Bad sandbox type ${type}`);
    return dispatchEntry(command, args, options);
}

module.exports = {
    execute,
    sandbox,
    initialiseWine
};
<commit_msg>Don't error by returning a rejected promise as a string; throw an error. Fixes #1848<commit_after>// Copyright (c) 2017, Matt Godbolt
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

const child_process = require('child_process'),
    path = require('path'),
    _ = require('underscore'),
    fs = require('fs'),
    logger = require('./logger').logger,
    treeKill = require('tree-kill'),
    execProps = require('./properties').propsFor('execution'),
    Graceful = require('node-graceful');

function setupOnError(stream, name) {
    if (stream === undefined) return;
    stream.on('error', err => {
        logger.error(`Error with ${name} stream:`, err);
    });
}

function executeDirect(command, args, options, filenameTransform) {
    // filename transform is expected to have been pre-applied by the caller.
    // it is passed through here only so clients can see it in the result.
    filenameTransform = filenameTransform || (x => x);
    options = options || {};
    const maxOutput = options.maxOutput || 1024 * 1024;
    const timeoutMs = options.timeoutMs || 0;
    const env = options.env ? options.env : _.clone(process.env);

    if (options.ldPath) {
        env.LD_LIBRARY_PATH = options.ldPath;
    }

    if (options.wrapper) {
        args = args.slice(0); // prevent mutating the caller's arguments
        args.unshift(command);
        command = options.wrapper;

        if (command.startsWith('./')) command = path.join(process.cwd(), command);
    }

    let okToCache = true;
    const cwd = options.customCwd ? options.customCwd : (
        (command.startsWith("/mnt") && process.env.wsl) ? process.env.winTmp : process.env.tmpDir
    );
    logger.debug("Execution", {type: "executing", command: command, args: args, env: env, cwd: cwd});
    // AP: Run Windows-volume executables in winTmp. Otherwise, run in tmpDir (which may be undefined).
    // https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options
    const child = child_process.spawn(command, args, {
        cwd: cwd,
        env: env,
        detached: process.platform === 'linux'
    });
    let running = true;

    const kill = options.killChild || (() => {
        if (running) treeKill(child.pid);
    });

    const streams = {
        stderr: "",
        stdout: "",
        truncated: false
    };
    let timeout;
    if (timeoutMs) timeout = setTimeout(() => {
        logger.warn(`Timeout for ${command} ${args} after ${timeoutMs}ms`);
        okToCache = false;
        kill();
        streams.stderr += "\nKilled - processing time exceeded";
    }, timeoutMs);

    function setupStream(stream, name) {
        if (stream === undefined) return;
        stream.on('data', data => {
            if (streams.truncated) return;
            const newLength = (streams[name].length + data.length);
            if ((maxOutput > 0) && (newLength > maxOutput)) {
                streams[name] = streams[name] + data.slice(0, maxOutput - streams[name].length);
                streams[name] += "\n[Truncated]";
                streams.truncated = true;
                kill();
                return;
            }
            streams[name] += data;
        });
        setupOnError(stream, name);
    }

    setupOnError(child.stdin, 'stdin');
    setupStream(child.stdout, 'stdout');
    setupStream(child.stderr, 'stderr');
    child.on('exit', code => {
        logger.debug("Execution", {type: 'exited', code: code});
        if (timeout !== undefined) clearTimeout(timeout);
        running = false;
    });
    return new Promise((resolve, reject) => {
        child.on('error', e => {
            logger.debug(`Execution error with ${command} args: ${args}:`, e);
            reject(e);
        });
        child.on('close', code => {
            // Being killed externally gives a NULL error code. Synthesize something different here.
            if (code === null) code = -1;
            if (timeout !== undefined) clearTimeout(timeout);
            const result = {
                code,
                okToCache,
                filenameTransform,
                stdout: streams.stdout,
                stderr: streams.stderr
            };
            logger.debug("Execution", {type: "executed", command: command, args: args, result: result});
            resolve(result);
        });
        if (child.stdin) {
            if (options.input) child.stdin.write(options.input);
            child.stdin.end();
        }
    });
}

// function msToFjTimeout(ms) {
//     const totalSecs = Math.round(ms / 1000);
//     return `${Math.floor(totalSecs / (60 * 60))}:${Math.floor(totalSecs / 60)}:${totalSecs % 60}`;
// }

function withFirejailTimeout(args, options) {
    if (options && options.timeoutMs) {
        // const ExtraWallClockLeewayMs = 1000;
        const ExtraCpuLeewayMs = 1500;
        return args.concat([
            // TODO: reinstate once we work out why this causes a 1s+ delay on every execution!
            // `--timeout=${msToFjTimeout(options.timeoutMs + ExtraWallClockLeewayMs)}`,
            `--rlimit-cpu=${Math.round((options.timeoutMs + ExtraCpuLeewayMs) / 1000)}`
        ]);
    }
    return args;
}

function sandboxFirejail(command, args, options) {
    logger.info("Sandbox execution via firejail", {command, args});
    const execPath = path.dirname(command);
    const execName = path.basename(command);
    const jailingOptions = withFirejailTimeout([
        '--quiet',
        '--deterministic-exit-code',
        '--terminate-orphans',
        '--profile=etc/firejail/sandbox.profile',
        `--private=${execPath}`,
        '--private-cwd']);

    if (options.ldPath) {
        jailingOptions.push(`--env=LD_LIBRARY_PATH=${options.ldPath}`);
        delete options.ldPath;
    }

    return executeDirect(
        execProps("firejail"),
        jailingOptions
            .concat([`./${execName}`])
            .concat(args),
        options);
}

const sandboxDispatchTable = {
    none: (command, args, options) => {
        logger.info("Sandbox execution (sandbox disabled)", {command, args});
        return executeDirect(command, args, options);
    },
    firejail: sandboxFirejail
};

async function sandbox(command, args, options) {
    const type = execProps("sandboxType", "firejail");
    const dispatchEntry = sandboxDispatchTable[type];
    if (!dispatchEntry)
        throw new Error(`Bad sandbox type ${type}`);
    return dispatchEntry(command, args, options);
}

const wineSandboxName = "ce-wineserver";

function initialiseWine() {
    const wine = execProps("wine");
    if (!wine) {
        logger.info("WINE not configured");
        return Promise.resolve();
    }

    const server = execProps("wineServer");
    const firejail = execProps("executionType", "none") === "firejail" ? execProps("firejail") : null;
    const env = applyWineEnv({PATH: process.env.PATH});
    const prefix = env.WINEPREFIX;

    logger.info(`Initialising WINE in ${prefix}`);
    if (!fs.existsSync(prefix) || !fs.statSync(prefix).isDirectory()) {
        logger.info(`Creating directory ${prefix}`);
        fs.mkdirSync(prefix);
    }

    logger.info(`Killing any pre-existing wine-server`);
    let result = child_process.execSync(`${server} -k || true`, {env: env});
    logger.info(`Result: ${result}`);
    logger.info(`Waiting for any pre-existing server to stop...`);
    result = child_process.execSync(`${server} -w`, {env: env});
    logger.info(`Result: ${result}`);

    // We run a long-lived cmd process, to:
    // * test that WINE works
    // * be something which holds open a working firejail sandbox
    // All future WINE compiles go through the same sandbox.
    // We wait until the process has printed out some known good text, but don't wait
    // for it to exit (it won't, on purpose).

    let wineServer;
    if (firejail) {
        logger.info(`Starting a new, firejailed, long-lived wineserver complex`);
        wineServer = child_process.spawn(
            firejail,
            [
                "--quiet",
                "--profile=etc/firejail/wine.profile",
                "--private",
                `--name=${wineSandboxName}`,
                wine,
                "cmd"
            ],
            {env: env, detached: true});
        logger.info(`firejailed pid=${wineServer.pid}`);
    } else {
        logger.info(`Starting a new, long-lived wineserver complex ${server}`);
        wineServer = child_process.spawn(
            wine,
            ["cmd"],
            {env: env, detached: true});
        logger.info(`wineserver pid=${wineServer.pid}`);
    }

    wineServer.on('close', code => {
        logger.info(`WINE server complex exited with code ${code}`);
    });

    Graceful.on('exit', () => {
        const waitingPromises = [];

        function waitForExit(process, name) {
            return new Promise((resolve) => {
                process.on('close', () => {
                    logger.info(`Process '${name}' closed`);
                    resolve();
                });
            });
        }

        if (wineServer && !wineServer.killed) {
            logger.info('Shutting down WINE server complex');
            wineServer.kill();
            if (wineServer.killed) {
                waitingPromises.push(waitForExit(wineServer, "WINE server"));
            }
            wineServer = null;
        }
        return Promise.all(waitingPromises);
    });

    return new Promise((resolve, reject) => {
        setupOnError(wineServer.stdin, "stdin");
        setupOnError(wineServer.stdout, "stdout");
        setupOnError(wineServer.stderr, "stderr");
        const magicString = "!!EVERYTHING IS WORKING!!";
        wineServer.stdin.write(`echo ${magicString}`);

        let output = "";
        wineServer.stdout.on('data', data => {
            logger.info(`Output from wine server complex: ${data.toString().trim()}`);
            output += data;
            if (output.includes(magicString)) {
                resolve();
            }
        });
        wineServer.stderr.on('data',
            data => logger.info(`stderr output from wine server complex: ${data.toString().trim()}`));
        wineServer.on('error', e => {
            logger.error(`WINE server complex exited with error ${e}`);
            reject(e);
        });
        wineServer.on('close', code => {
            logger.info(`WINE server complex exited with code ${code}`);
            reject();
        });
    });
}

function applyWineEnv(env) {
    const prefix = execProps("winePrefix");
    env = env || {};
    // Force use of wine vcruntime (See change 45106c382)
    env.WINEDLLOVERRIDES = "vcruntime140=b";
    env.WINEDEBUG = "-all";
    env.WINEPREFIX = prefix;
    return env;
}

function needsWine(command) {
    return command.match(/\.exe$/i) && process.platform === 'linux';
}

function executeWineDirect(command, args, options) {
    options = options || {};
    options.env = applyWineEnv(options.env);
    args.unshift(command);
    return executeDirect(execProps("wine"), args, options);
}

function executeFirejail(command, args, options) {
    options = options || {};
    const firejail = execProps("firejail");
    const baseOptions = withFirejailTimeout(['--quiet', '--deterministic-exit-code', '--terminate-orphans'], options);
    if (needsWine(command)) {
        logger.debug("WINE execution via firejail", {command, args});
        options.env = applyWineEnv(options.env);
        args.unshift(command);
        command = execProps("wine");
        baseOptions.push('--profile=etc/firejail/wine.profile');
        baseOptions.push(`--join=${wineSandboxName}`);
        delete options.customCwd;
        baseOptions.push(command);
        return executeDirect(firejail, baseOptions.concat(args), options);
    }

    logger.debug("Regular execution via firejail", {command, args});
    baseOptions.push('--profile=etc/firejail/execute.profile');
    let filenameTransform;
    if (options.customCwd) {
        baseOptions.push(`--private=${options.customCwd}`);
        const replacement = options.customCwd;
        filenameTransform = opt => opt.replace(replacement, ".");
        args = args.map(filenameTransform);
        delete options.customCwd;
        // TODO: once it's supported properly in our patched firejail, make this option common to both customCwd and
        // non-customCwd code paths.
        baseOptions.push('--private-cwd');
    } else {
        baseOptions.push('--private');
    }
    baseOptions.push(command);
    return executeDirect(firejail, baseOptions.concat(args), options, filenameTransform);
}

function executeNone(command, args, options) {
    if (needsWine(command)) {
        return executeWineDirect(command, args, options);
    }
    return executeDirect(command, args, options);
}

const executeDispatchTable = {
    none: executeNone,
    firejail: executeFirejail
};

async function execute(command, args, options) {
    const type = execProps("executionType", "none");
    const dispatchEntry = executeDispatchTable[type];
    if (!dispatchEntry)
        throw new Error(`Bad sandbox type ${type}`);
    return dispatchEntry(command, args, options);
}

module.exports = {
    execute,
    sandbox,
    initialiseWine
};
<|endoftext|>


<commit_before>var SteamTotp = require('steam-totp');
var SteamCommunity = require('../index.js');

var ETwoFactorTokenType = {
	"None": 0,                  // No token-based two-factor authentication
	"ValveMobileApp": 1,        // Tokens generated using Valve's special charset (5 digits, alphanumeric)
	"ThirdParty": 2             // Tokens generated using literally everyone else's standard charset (6 digits, numeric). This is disabled.
};

SteamCommunity.prototype.enableTwoFactor = function(callback) {
	var self = this;

	this.getWebApiOauthToken(function(err, token) {
		if(err) {
			callback(err);
			return;
		}

		// Create a random device ID hash
		var hash = require('crypto').createHash('sha1');
		hash.update(Math.random().toString());
		hash = hash.digest('hex');

		self.request.post({
			"uri": "https://api.steampowered.com/ITwoFactorService/AddAuthenticator/v1/",
			"form": {
				"steamid": self.steamID.getSteamID64(),
				"access_token": token,
				"authenticator_time": Math.floor(Date.now() / 1000),
				"authenticator_type": ETwoFactorTokenType.ValveMobileApp,
				"device_identifier": 'android:' + hash,
				"sms_phone_id": "1"
			},
			"json": true
		}, function(err, response, body) {
			if(self._checkHttpError(err, response, callback)) {
				return;
			}

			if(!body.response) {
				callback(new Error("Malformed response"));
				return;
			}

			if(body.response.status != 1) {
				var error = new Error("Error " + body.response.status);
				error.eresult = body.response.status;
				callback(error);
				return;
			}

			callback(null, body.response);
		});
	});
};

SteamCommunity.prototype.finalizeTwoFactor = function(secret, activationCode, callback) {
	var attemptsLeft = 30;
	var diff = 0;

	var self = this;
	this.getWebApiOauthToken(function(err, token) {
		if(err) {
			callback(err);
			return;
		}

		finalize(token);
	});

	function finalize(token) {
		var code = SteamTotp.generateAuthCode(secret, diff);

		self.request.post({
			"uri": "https://api.steampowered.com/ITwoFactorService/FinalizeAddAuthenticator/v1/",
			"form": {
				"steamid": self.steamID.getSteamID64(),
				"access_token": token,
				"authenticator_code": code,
				"authenticator_time": Math.floor(Date.now() / 1000),
				"activation_code": activationCode
			},
			"json": true
		}, function(err, response, body) {
			if(self._checkHttpError(err, response, callback)) {
				return;
			}

			if(!body.response) {
				callback(new Error("Malformed response"));
				return;
			}

			body = body.response;

			if(body.server_time) {
				diff = body.server_time - Math.floor(Date.now() / 1000);
			}

			if(body.status == 89) {
				callback(new Error("Invalid activation code"));
			} else if(body.want_more) {
				attemptsLeft--;
				diff += 30;

				finalize(token);
			} else if(!body.success) {
				callback(new Error("Error " + body.status));
			} else {
				callback(null);
			}
		});
	}
};

SteamCommunity.prototype.disableTwoFactor = function(revocationCode, callback) {
	var self = this;

	this.getWebApiOauthToken(function(err, token) {
		if(err) {
			callback(err);
			return;
		}

		self.request.post({
			"uri": "https://api.steampowered.com/ITwoFactorService/RemoveAuthenticator/v1/",
			"form": {
				"steamid": self.steamID.getSteamID64(),
				"access_token": token,
				"revocation_code": revocationCode,
				"steamguard_scheme": 1
			},
			"json": true
		}, function(err, response, body) {
			if(self._checkHttpError(err, response, callback)) {
				return;
			}

			if(!body.response) {
				callback(new Error("Malformed response"));
				return;
			}

			if(!body.response.success) {
				callback(new Error("Request failed"));
				return;
			}

			if(body.response.status == 1) {
				callback(null);
				return;
			}

			var error = new Error("Cannot remove authenticator (" + body.response.status + ")");
			error.eresult = body.response.status;
			callback(error);
		});
	});
};
<commit_msg>Use SHA-1 hash of our SteamID when enabling 2FA<commit_after>var SteamTotp = require('steam-totp');
var SteamCommunity = require('../index.js');

var ETwoFactorTokenType = {
	"None": 0,                  // No token-based two-factor authentication
	"ValveMobileApp": 1,        // Tokens generated using Valve's special charset (5 digits, alphanumeric)
	"ThirdParty": 2             // Tokens generated using literally everyone else's standard charset (6 digits, numeric). This is disabled.
};

SteamCommunity.prototype.enableTwoFactor = function(callback) {
	var self = this;

	this.getWebApiOauthToken(function(err, token) {
		if(err) {
			callback(err);
			return;
		}

		// Create a random device ID hash
		var hash = require('crypto').createHash('sha1');
		hash.update(self.steamID.getSteamID64());
		hash = hash.digest('hex');

		self.request.post({
			"uri": "https://api.steampowered.com/ITwoFactorService/AddAuthenticator/v1/",
			"form": {
				"steamid": self.steamID.getSteamID64(),
				"access_token": token,
				"authenticator_time": Math.floor(Date.now() / 1000),
				"authenticator_type": ETwoFactorTokenType.ValveMobileApp,
				"device_identifier": 'android:' + hash,
				"sms_phone_id": "1"
			},
			"json": true
		}, function(err, response, body) {
			if(self._checkHttpError(err, response, callback)) {
				return;
			}

			if(!body.response) {
				callback(new Error("Malformed response"));
				return;
			}

			if(body.response.status != 1) {
				var error = new Error("Error " + body.response.status);
				error.eresult = body.response.status;
				callback(error);
				return;
			}

			callback(null, body.response);
		});
	});
};

SteamCommunity.prototype.finalizeTwoFactor = function(secret, activationCode, callback) {
	var attemptsLeft = 30;
	var diff = 0;

	var self = this;
	this.getWebApiOauthToken(function(err, token) {
		if(err) {
			callback(err);
			return;
		}

		finalize(token);
	});

	function finalize(token) {
		var code = SteamTotp.generateAuthCode(secret, diff);

		self.request.post({
			"uri": "https://api.steampowered.com/ITwoFactorService/FinalizeAddAuthenticator/v1/",
			"form": {
				"steamid": self.steamID.getSteamID64(),
				"access_token": token,
				"authenticator_code": code,
				"authenticator_time": Math.floor(Date.now() / 1000),
				"activation_code": activationCode
			},
			"json": true
		}, function(err, response, body) {
			if(self._checkHttpError(err, response, callback)) {
				return;
			}

			if(!body.response) {
				callback(new Error("Malformed response"));
				return;
			}

			body = body.response;

			if(body.server_time) {
				diff = body.server_time - Math.floor(Date.now() / 1000);
			}

			if(body.status == 89) {
				callback(new Error("Invalid activation code"));
			} else if(body.want_more) {
				attemptsLeft--;
				diff += 30;

				finalize(token);
			} else if(!body.success) {
				callback(new Error("Error " + body.status));
			} else {
				callback(null);
			}
		});
	}
};

SteamCommunity.prototype.disableTwoFactor = function(revocationCode, callback) {
	var self = this;

	this.getWebApiOauthToken(function(err, token) {
		if(err) {
			callback(err);
			return;
		}

		self.request.post({
			"uri": "https://api.steampowered.com/ITwoFactorService/RemoveAuthenticator/v1/",
			"form": {
				"steamid": self.steamID.getSteamID64(),
				"access_token": token,
				"revocation_code": revocationCode,
				"steamguard_scheme": 1
			},
			"json": true
		}, function(err, response, body) {
			if(self._checkHttpError(err, response, callback)) {
				return;
			}

			if(!body.response) {
				callback(new Error("Malformed response"));
				return;
			}

			if(!body.response.success) {
				callback(new Error("Request failed"));
				return;
			}

			if(body.response.status == 1) {
				callback(null);
				return;
			}

			var error = new Error("Cannot remove authenticator (" + body.response.status + ")");
			error.eresult = body.response.status;
			callback(error);
		});
	});
};
<|endoftext|>


<commit_before>var Packages = module.exports = {};

var _ = require('lodash'),
  fs = require('fs-extra'),
  path = require('path'),
  shell = require('shelljs');

// We rely on system git command and its configuration
// In this way it is possible to pick up .netrc
if (!shell.which('git')) {
  shell.echo('Sorry, this script requires git');
  shell.exit(1);
}

var PACKAGE_DIR = process.cwd() + '/packages';

function resolvePath(string) {
  if (string.substr(0, 1) === '~') {
    var homedir = (process.platform.substr(0, 3) === 'win') ? process.env.HOMEPATH : process.env.HOME;
    string = homedir + string.substr(1);
  } else if (string.substr(0, 1) !== '/') {
    string = process.cwd() + '/' + string;
  }
  return path.resolve(string);
}

/**
 * Configure the package directory.
 * @param packageDir The directory to copy the packages to.
 *                             Defaults to cwd/packages
 */
Packages.config = function (packageDir) {
  PACKAGE_DIR = resolvePath(packageDir);
};

/**
 * Load the packages file.
 * @param [file] Defaults to cwd/git-packages.json
 * @param callback
 */
Packages.fromFile = function (file, callback) {
  if (_.isFunction(file)) {
    callback = file;
    file = null;
  }

  var packagesFile = file || process.cwd() + '/git-packages.json';
  fs.readJson(packagesFile, function (err, packages) {
    // XXX check the packages schema
    if (callback) callback(err, packages);
  });
};

// Create a packages document per git url.
// { gitRepo: { version: { packageName: packagePath, .. }, ... } }
var getPackagesDict = function (packages) {
  var resolvedPackages = {};

  _.forOwn(packages, function (definition, packageName) {
    if (!definition) return;

    var git = definition.git;
    if (!git) return;

    var repo = resolvedPackages[git] = resolvedPackages[git] || {};

    // If no version provided - pull HEAD
    version = definition.version || 'HEAD';
    repo[version] = repo[version] || {};
    repo[version][packageName] = definition.path || '';
  });

  return resolvedPackages;
};

// Test if path exists and fail with error if it is not exists
var checkPathExist = function (path, errorMessage) {
  if (!shell.test('-e', path)) {
    shell.echo('Error: ' + errorMessage);
    shell.exit(1);
  }
};

/**
 * Create a git ignore in the package directory for the packages.
 * @param packages
 * @param {Function} callback
 */
Packages.ensureGitIgnore = function (packages, callback) {
  var filePath = PACKAGE_DIR + '/.gitignore';

  fs.ensureFileSync(filePath);
  fs.readFile(filePath, 'utf8', function (err, gitIgnore) {
    // Append packages to the gitignore
    _.forOwn(packages, function (def, packageName) {
      // Convert colons in package names to underscores for Windows
      packageName = packageName.replace(/:/g, '_');

      if (packageName === 'token' || gitIgnore.indexOf(packageName) > -1) return;

      gitIgnore += packageName + '\n';
    });

    fs.writeFile(filePath, gitIgnore, callback);
  });
};

/**
 * Symlink local directories to the packages directory.
 * @param packages The packages to symlink.
 * @param {Function} callback
 */
Packages.link = function (packages, callback) {
  shell.mkdir('-p', PACKAGE_DIR);

  var dirLinked = _.after(_.keys(packages).length, callback);

  _.forOwn(packages, function (def, packageName) {
    if (!def.path || !packageName) return;

    // Convert colons in package names to underscores for Windows
    packageName = packageName.replace(/:/g, '_');
    var dest = PACKAGE_DIR + '/' + packageName;
    shell.rm('-fr', dest);

    var src = resolvePath(def.path);
    checkPathExist(src, 'Cannot find package ' + packageName + ' at ' + src);

    shell.ln('-s', src, dest);
    checkPathExist(src, 'Link failed for ' + dest);

    dirLinked();
  });
};

/**
 * Clones repositories and copy the packages.
 * @param packages The packages to load.
 * @param {Function} callback
 */
Packages.load = function (packages, callback) {
  shell.mkdir('-p', PACKAGE_DIR);

  // Create a temp directory to store the tarballs
  var tempDir = PACKAGE_DIR + '/temp';
  shell.rm('-fr', tempDir);
  shell.mkdir('-p', tempDir);
  shell.cd(tempDir);

  var resolvedPackages = getPackagesDict(packages);

  var repoDirIndex = 0;

  _.forOwn(resolvedPackages, function (repoPackages, gitRepo) {
    var repoDir = tempDir + '/' + repoDirIndex;

    // Change to the temp directory before cloning the repo
    shell.cd(tempDir);

    if (shell.exec('git clone ' + gitRepo + ' ' + repoDirIndex, {silent: true}).code !== 0) {
      shell.echo('Error: Git clone failed');
      shell.exit(1);
    }

    // Change to the repo directory
    shell.cd(repoDir);

    repoDirIndex++;

    _.forOwn(repoPackages, function (storedPackages, version) {
      _.forOwn(storedPackages, function (src, packageName) {
        if (shell.exec('git reset --hard ' + version, {silent: false}).code !== 0) {
          shell.echo('Error: Git checkout failed for ' + packageName + '@' + version);
          shell.exit(1);
        }
        packageName = packageName.replace(/:/g, '_');
        shell.echo('\nProcessing ' + packageName + ' at ' + version);

        shell.echo('Cleaning up');
        var dest = PACKAGE_DIR + '/' + packageName;
        shell.rm('-rf', dest);

        src = repoDir + '/' + src + '/';
        checkPathExist(src, 'Cannot find package in repository: ' + src);

        shell.echo('Copying package');
        shell.cp('-rf', src, dest);
        checkPathExist(dest, 'Cannot copy package: ' + dest);
        shell.echo('Done...\n');
      });
    });

  });

  // Remove the temp directory after the packages are copied.
  shell.cd(process.cwd());
  shell.rm('-fr', tempDir);
  callback();
};
<commit_msg>Copy hidden files too<commit_after>var Packages = module.exports = {};

var _ = require('lodash'),
  fs = require('fs-extra'),
  path = require('path'),
  shell = require('shelljs');

// We rely on system git command and its configuration
// In this way it is possible to pick up .netrc
if (!shell.which('git')) {
  shell.echo('Sorry, this script requires git');
  shell.exit(1);
}

var PACKAGE_DIR = process.cwd() + '/packages';

function resolvePath(string) {
  if (string.substr(0, 1) === '~') {
    var homedir = (process.platform.substr(0, 3) === 'win') ? process.env.HOMEPATH : process.env.HOME;
    string = homedir + string.substr(1);
  } else if (string.substr(0, 1) !== '/') {
    string = process.cwd() + '/' + string;
  }
  return path.resolve(string);
}

/**
 * Configure the package directory.
 * @param packageDir The directory to copy the packages to.
 *                             Defaults to cwd/packages
 */
Packages.config = function (packageDir) {
  PACKAGE_DIR = resolvePath(packageDir);
};

/**
 * Load the packages file.
 * @param [file] Defaults to cwd/git-packages.json
 * @param callback
 */
Packages.fromFile = function (file, callback) {
  if (_.isFunction(file)) {
    callback = file;
    file = null;
  }

  var packagesFile = file || process.cwd() + '/git-packages.json';
  fs.readJson(packagesFile, function (err, packages) {
    // XXX check the packages schema
    if (callback) callback(err, packages);
  });
};

// Create a packages document per git url.
// { gitRepo: { version: { packageName: packagePath, .. }, ... } }
var getPackagesDict = function (packages) {
  var resolvedPackages = {};

  _.forOwn(packages, function (definition, packageName) {
    if (!definition) return;

    var git = definition.git;
    if (!git) return;

    var repo = resolvedPackages[git] = resolvedPackages[git] || {};

    // If no version provided - pull HEAD
    version = definition.version || 'HEAD';
    repo[version] = repo[version] || {};
    repo[version][packageName] = definition.path || '';
  });

  return resolvedPackages;
};

// Test if path exists and fail with error if it is not exists
var checkPathExist = function (path, errorMessage) {
  if (!shell.test('-e', path)) {
    shell.echo('Error: ' + errorMessage);
    shell.exit(1);
  }
};

/**
 * Create a git ignore in the package directory for the packages.
 * @param packages
 * @param {Function} callback
 */
Packages.ensureGitIgnore = function (packages, callback) {
  var filePath = PACKAGE_DIR + '/.gitignore';

  fs.ensureFileSync(filePath);
  fs.readFile(filePath, 'utf8', function (err, gitIgnore) {
    // Append packages to the gitignore
    _.forOwn(packages, function (def, packageName) {
      // Convert colons in package names to underscores for Windows
      packageName = packageName.replace(/:/g, '_');

      if (packageName === 'token' || gitIgnore.indexOf(packageName) > -1) return;

      gitIgnore += packageName + '\n';
    });

    fs.writeFile(filePath, gitIgnore, callback);
  });
};

/**
 * Symlink local directories to the packages directory.
 * @param packages The packages to symlink.
 * @param {Function} callback
 */
Packages.link = function (packages, callback) {
  shell.mkdir('-p', PACKAGE_DIR);

  var dirLinked = _.after(_.keys(packages).length, callback);

  _.forOwn(packages, function (def, packageName) {
    if (!def.path || !packageName) return;

    // Convert colons in package names to underscores for Windows
    packageName = packageName.replace(/:/g, '_');
    var dest = PACKAGE_DIR + '/' + packageName;
    shell.rm('-fr', dest);

    var src = resolvePath(def.path);
    checkPathExist(src, 'Cannot find package ' + packageName + ' at ' + src);

    shell.ln('-s', src, dest);
    checkPathExist(src, 'Link failed for ' + dest);

    dirLinked();
  });
};

/**
 * Clones repositories and copy the packages.
 * @param packages The packages to load.
 * @param {Function} callback
 */
Packages.load = function (packages, callback) {
  shell.mkdir('-p', PACKAGE_DIR);

  // Create a temp directory to store the tarballs
  var tempDir = PACKAGE_DIR + '/temp';
  shell.rm('-fr', tempDir);
  shell.mkdir('-p', tempDir);
  shell.cd(tempDir);

  var resolvedPackages = getPackagesDict(packages);

  var repoDirIndex = 0;

  _.forOwn(resolvedPackages, function (repoPackages, gitRepo) {
    var repoDir = tempDir + '/' + repoDirIndex;

    // Change to the temp directory before cloning the repo
    shell.cd(tempDir);

    if (shell.exec('git clone ' + gitRepo + ' ' + repoDirIndex, {silent: true}).code !== 0) {
      shell.echo('Error: Git clone failed');
      shell.exit(1);
    }

    // Change to the repo directory
    shell.cd(repoDir);

    repoDirIndex++;

    _.forOwn(repoPackages, function (storedPackages, version) {
      _.forOwn(storedPackages, function (src, packageName) {
        if (shell.exec('git reset --hard ' + version, {silent: false}).code !== 0) {
          shell.echo('Error: Git checkout failed for ' + packageName + '@' + version);
          shell.exit(1);
        }
        packageName = packageName.replace(/:/g, '_');
        shell.echo('\nProcessing ' + packageName + ' at ' + version);

        shell.echo('Cleaning up');
        var dest = PACKAGE_DIR + '/' + packageName;
        shell.rm('-rf', dest);

        src = repoDir + '/' + src + '/';
        checkPathExist(src, 'Cannot find package in repository: ' + src);

        shell.echo('Copying package');
        // Adding the dot after `src` forces it to copy hidden files as well
        shell.cp('-rf', src + '.', dest);
        checkPathExist(dest, 'Cannot copy package: ' + dest);
        shell.echo('Done...\n');
      });
    });

  });

  // Remove the temp directory after the packages are copied.
  shell.cd(process.cwd());
  shell.rm('-fr', tempDir);
  callback();
};
<|endoftext|>


<commit_before>var debug = require('../utils/debug');
var registerComponent = require('../core/component').registerComponent;
var THREE = require('../../lib/three');
var utils = require('../utils');

var DEFAULT_RADIUS = 1;
var helperMatrix = new THREE.Matrix4();
var rad = THREE.Math.degToRad;
var warn = debug('components:geometry:warn');

/**
 * Geometry component. Combined with material component to make a mesh in 3D object.
 *
 * @param {number} [arc=360] -
 *   Used by torus. A central angle that determines arc length of the torus. In degrees.
 * @param {number} [depth=2] - Used by box. Depth of the sides on the Z axis.
 * @param {number} [height=2] -
 *   Used by box, cylinder, plane. Height of the sides on the Y axis.
 * @param {bool} [openEnded=false] - Used by cylinder.
 * @param {number} [p=2] - Used by torusKnot. Coprime of q.
 * @param {number} [primitive=null] - type of shape (e.g., box, sphere).
 * @param {number} [q=3] - Used by torusKnot. Coprime of p.
 * @param {number} [radius=1] - Used by circle, cylinder, ring, sphere, torus, torusKnot.
 * @param {number} [radiusBottom=1] - Used by cylinder.
 * @param {number} [radiusInner=0.8] - Used by ring.
 * @param {number} [radiusOuter=1.2] - Used by ring.
 * @param {number} [radiusTop=1] - Used by cylinder.
 * @param {number} [radiusTube=0.2] - Used by torus. Tube radius.
 * @param {number} [scaleHeight=1] - Used by torusKnot.
 * @param {number} [segments=8] - Used by circle. Number of segments.
 * @param {number} [segmentsHeight=18] - Used by cylinder, sphere. Number of segments.
 * @param {number} [segmentsPhi=8] - Used by ring.
 * @param {number} [segmentsRadial=36] - Used by cylinder. Number of segments.
 * @param {number} [segmentsTheta=8] -
 *   Used by ring. Number of segments. A higher number means the ring will be more round.
 *   Minimum is 3.
 * @param {number} [segmentsTubular=8] - Used by torus, torusKnot. Number of segments.
 * @param {number} [segmentsWidth=36] - Used by sphere.
 * @param {number} [thetaLength=360] - Used by circle, cylinder, ring. In degrees.
 * @param {number} [thetaStart=0] - Used by circle, cylinder, ring. In degrees.
 * @param {string} translate -
 *   Defined as a coordinate (e.g., `-1 0 5`) that translates geometry vertices. Useful for
 *   effectively changing the pivot point.
 * @param {number} [width=2] - Used by box, plane.
 */
module.exports.Component = registerComponent('geometry', {
  schema: {
    arc: { default: 360, if: { primitive: ['torus'] } },
    depth: { default: 2, min: 0, if: { primitive: ['box'] } },
    height: { default: 2, min: 0, if: { primitive: ['box', 'plane'] } },
    openEnded: { default: false, if: { primitive: ['cylinder'] } },
    p: { default: 2, if: { primitive: ['torusKnot'] } },
    translate: { default: { x: 0, y: 0, z: 0 } },
    primitive: {
      default: '',
      oneOf: ['', 'box', 'circle', 'cylinder', 'plane',
              'ring', 'sphere', 'torus', 'torusKnot'] },
    q: { default: 3, if: { primitive: ['torusKnot'] } },
    radius: { default: DEFAULT_RADIUS, min: 0, if: { primitive: ['circle', 'cylinder', 'sphere', 'torus', 'torusKnot'] } },
    radiusBottom: { default: DEFAULT_RADIUS, min: 0, if: { primitive: ['cylinder'] } },
    radiusInner: { default: 0.8, min: 0, if: { primitive: ['ring'] } },
    radiusOuter: { default: 1.2, min: 0, if: { primitive: ['ring'] } },
    radiusTop: { default: DEFAULT_RADIUS, if: { primitive: ['cylinder'] } },
    radiusTubular: { default: 0.2, min: 0, if: { primitive: ['torus'] } },
    scaleHeight: { default: 1, min: 0, if: { primitive: ['torusKnot'] } },
    segments: { default: 8, min: 0, if: { primitive: ['circle'] } },
    segmentsHeight: { default: 18, min: 0, if: { primitive: ['cylinder', 'sphere'] } },
    segmentsPhi: { default: 8, min: 0, if: { primitive: ['ring'] } },
    segmentsRadial: { default: 36, min: 0, if: { primitive: ['cylinder'] } },
    segmentsTheta: { default: 8, min: 0, if: { primitive: ['ring'] } },
    segmentsTubular: { default: 8, min: 0, if: { primitive: ['torus', 'torusKnot'] } },
    segmentsWidth: { default: 36, min: 0, if: { primitive: ['sphere'] } },
    thetaLength: { default: 360, min: 0, if: { primitive: ['circle', 'cylinder', 'ring'] } },
    thetaStart: { default: 0, if: { primitive: ['circle', 'cylinder', 'ring'] } },
    width: { default: 2, min: 0, if: { primitive: ['box', 'plane'] } }
  },

  /**
   * Creates a new geometry on every update as there's not an easy way to
   * update a geometry that would be faster than just creating a new one.
   */
  update: function (previousData) {
    previousData = previousData || {};
    var data = this.data;
    var currentTranslate = previousData.translate || this.schema.translate.default;
    var diff = utils.diff(previousData, data);
    var geometry = this.el.object3D.geometry;
    var geometryNeedsUpdate = !(Object.keys(diff).length === 1 && 'translate' in diff);
    var translateNeedsUpdate = !utils.deepEqual(data.translate, currentTranslate);

    if (geometryNeedsUpdate) {
      geometry = this.el.object3D.geometry = getGeometry(this.data, this.schema);
    }
    if (translateNeedsUpdate) {
      applyTranslate(geometry, data.translate, currentTranslate);
    }
  },

  /**
   * Removes geometry on remove (callback).
   */
  remove: function () {
    this.el.object3D.geometry = new THREE.Geometry();
  }
});

/**
 * Creates a three.js geometry.
 *
 * @param {object} data
 * @param {object} schema
 * @returns {object} geometry
 */
function getGeometry (data, schema) {
  switch (data.primitive) {
    case 'box': {
      return new THREE.BoxGeometry(data.width, data.height, data.depth);
    }
    case 'circle': {
      return new THREE.CircleGeometry(
        data.radius, data.segments, rad(data.thetaStart), rad(data.thetaLength));
    }
    case 'cone': {
      return new THREE.CylinderGeometry(
        data.radiusTop, data.radiusBottom, data.height,
        data.segmentsRadial, data.segmentsHeight,
        data.openEnded, rad(data.thetaStart), rad(data.thetaLength));
    }
    case 'cylinder': {
      return new THREE.CylinderGeometry(
        data.radius, data.radius, data.height,
        data.segmentsRadial, data.segmentsHeight,
        data.openEnded, rad(data.thetaStart), rad(data.thetaLength));
    }
    case 'plane': {
      return new THREE.PlaneBufferGeometry(data.width, data.height);
    }
    case 'ring': {
      return new THREE.RingGeometry(
        data.radiusInner, data.radiusOuter, data.segmentsTheta, data.segmentsPhi,
        rad(data.thetaStart), rad(data.thetaLength));
    }
    case 'sphere': {
      return new THREE.SphereGeometry(
        data.radius, data.segmentsWidth, data.segmentsHeight);
    }
    case 'torus': {
      return new THREE.TorusGeometry(
        data.radius, data.radiusTubular * 2, data.segmentsRadial, data.segmentsTubular,
        rad(data.arc));
    }
    case 'torusKnot': {
      return new THREE.TorusKnotGeometry(
        data.radius, data.radiusTubular * 2, data.segmentsRadial, data.segmentsTubular,
        data.p, data.q, data.scaleHeight);
    }
    default: {
      warn('Primitive type not supported: ' + data.primitive);
      return new THREE.Geometry();
    }
  }
}

/**
 * Translates geometry vertices.
 *
 * @param {object} geometry - three.js geometry.
 * @param {object} translate - New translation.
 * @param {object} currentTranslate - Currently applied translation.
 */
function applyTranslate (geometry, translate, currentTranslate) {
  var translation = helperMatrix.makeTranslation(
    translate.x - currentTranslate.x,
    translate.y - currentTranslate.y,
    translate.z - currentTranslate.z
  );
  geometry.applyMatrix(translation);
  geometry.verticesNeedsUpdate = true;
}
<commit_msg>SphereBufferGeometry instead of SphereGeometry.<commit_after>var debug = require('../utils/debug');
var registerComponent = require('../core/component').registerComponent;
var THREE = require('../../lib/three');
var utils = require('../utils');

var DEFAULT_RADIUS = 1;
var helperMatrix = new THREE.Matrix4();
var rad = THREE.Math.degToRad;
var warn = debug('components:geometry:warn');

/**
 * Geometry component. Combined with material component to make a mesh in 3D object.
 *
 * @param {number} [arc=360] -
 *   Used by torus. A central angle that determines arc length of the torus. In degrees.
 * @param {number} [depth=2] - Used by box. Depth of the sides on the Z axis.
 * @param {number} [height=2] -
 *   Used by box, cylinder, plane. Height of the sides on the Y axis.
 * @param {bool} [openEnded=false] - Used by cylinder.
 * @param {number} [p=2] - Used by torusKnot. Coprime of q.
 * @param {number} [primitive=null] - type of shape (e.g., box, sphere).
 * @param {number} [q=3] - Used by torusKnot. Coprime of p.
 * @param {number} [radius=1] - Used by circle, cylinder, ring, sphere, torus, torusKnot.
 * @param {number} [radiusBottom=1] - Used by cylinder.
 * @param {number} [radiusInner=0.8] - Used by ring.
 * @param {number} [radiusOuter=1.2] - Used by ring.
 * @param {number} [radiusTop=1] - Used by cylinder.
 * @param {number} [radiusTube=0.2] - Used by torus. Tube radius.
 * @param {number} [scaleHeight=1] - Used by torusKnot.
 * @param {number} [segments=8] - Used by circle. Number of segments.
 * @param {number} [segmentsHeight=18] - Used by cylinder, sphere. Number of segments.
 * @param {number} [segmentsPhi=8] - Used by ring.
 * @param {number} [segmentsRadial=36] - Used by cylinder. Number of segments.
 * @param {number} [segmentsTheta=8] -
 *   Used by ring. Number of segments. A higher number means the ring will be more round.
 *   Minimum is 3.
 * @param {number} [segmentsTubular=8] - Used by torus, torusKnot. Number of segments.
 * @param {number} [segmentsWidth=36] - Used by sphere.
 * @param {number} [thetaLength=360] - Used by circle, cylinder, ring. In degrees.
 * @param {number} [thetaStart=0] - Used by circle, cylinder, ring. In degrees.
 * @param {string} translate -
 *   Defined as a coordinate (e.g., `-1 0 5`) that translates geometry vertices. Useful for
 *   effectively changing the pivot point.
 * @param {number} [width=2] - Used by box, plane.
 */
module.exports.Component = registerComponent('geometry', {
  schema: {
    arc: { default: 360, if: { primitive: ['torus'] } },
    depth: { default: 2, min: 0, if: { primitive: ['box'] } },
    height: { default: 2, min: 0, if: { primitive: ['box', 'plane'] } },
    openEnded: { default: false, if: { primitive: ['cylinder'] } },
    p: { default: 2, if: { primitive: ['torusKnot'] } },
    translate: { default: { x: 0, y: 0, z: 0 } },
    primitive: {
      default: '',
      oneOf: ['', 'box', 'circle', 'cylinder', 'plane',
              'ring', 'sphere', 'torus', 'torusKnot'] },
    q: { default: 3, if: { primitive: ['torusKnot'] } },
    radius: { default: DEFAULT_RADIUS, min: 0, if: { primitive: ['circle', 'cylinder', 'sphere', 'torus', 'torusKnot'] } },
    radiusBottom: { default: DEFAULT_RADIUS, min: 0, if: { primitive: ['cylinder'] } },
    radiusInner: { default: 0.8, min: 0, if: { primitive: ['ring'] } },
    radiusOuter: { default: 1.2, min: 0, if: { primitive: ['ring'] } },
    radiusTop: { default: DEFAULT_RADIUS, if: { primitive: ['cylinder'] } },
    radiusTubular: { default: 0.2, min: 0, if: { primitive: ['torus'] } },
    scaleHeight: { default: 1, min: 0, if: { primitive: ['torusKnot'] } },
    segments: { default: 8, min: 0, if: { primitive: ['circle'] } },
    segmentsHeight: { default: 18, min: 0, if: { primitive: ['cylinder', 'sphere'] } },
    segmentsPhi: { default: 8, min: 0, if: { primitive: ['ring'] } },
    segmentsRadial: { default: 36, min: 0, if: { primitive: ['cylinder'] } },
    segmentsTheta: { default: 8, min: 0, if: { primitive: ['ring'] } },
    segmentsTubular: { default: 8, min: 0, if: { primitive: ['torus', 'torusKnot'] } },
    segmentsWidth: { default: 36, min: 0, if: { primitive: ['sphere'] } },
    thetaLength: { default: 360, min: 0, if: { primitive: ['circle', 'cylinder', 'ring'] } },
    thetaStart: { default: 0, if: { primitive: ['circle', 'cylinder', 'ring'] } },
    width: { default: 2, min: 0, if: { primitive: ['box', 'plane'] } }
  },

  /**
   * Creates a new geometry on every update as there's not an easy way to
   * update a geometry that would be faster than just creating a new one.
   */
  update: function (previousData) {
    previousData = previousData || {};
    var data = this.data;
    var currentTranslate = previousData.translate || this.schema.translate.default;
    var diff = utils.diff(previousData, data);
    var geometry = this.el.object3D.geometry;
    var geometryNeedsUpdate = !(Object.keys(diff).length === 1 && 'translate' in diff);
    var translateNeedsUpdate = !utils.deepEqual(data.translate, currentTranslate);

    if (geometryNeedsUpdate) {
      geometry = this.el.object3D.geometry = getGeometry(this.data, this.schema);
    }
    if (translateNeedsUpdate) {
      applyTranslate(geometry, data.translate, currentTranslate);
    }
  },

  /**
   * Removes geometry on remove (callback).
   */
  remove: function () {
    this.el.object3D.geometry = new THREE.Geometry();
  }
});

/**
 * Creates a three.js geometry.
 *
 * @param {object} data
 * @param {object} schema
 * @returns {object} geometry
 */
function getGeometry (data, schema) {
  switch (data.primitive) {
    case 'box': {
      return new THREE.BoxGeometry(data.width, data.height, data.depth);
    }
    case 'circle': {
      return new THREE.CircleGeometry(
        data.radius, data.segments, rad(data.thetaStart), rad(data.thetaLength));
    }
    case 'cone': {
      return new THREE.CylinderGeometry(
        data.radiusTop, data.radiusBottom, data.height,
        data.segmentsRadial, data.segmentsHeight,
        data.openEnded, rad(data.thetaStart), rad(data.thetaLength));
    }
    case 'cylinder': {
      return new THREE.CylinderGeometry(
        data.radius, data.radius, data.height,
        data.segmentsRadial, data.segmentsHeight,
        data.openEnded, rad(data.thetaStart), rad(data.thetaLength));
    }
    case 'plane': {
      return new THREE.PlaneBufferGeometry(data.width, data.height);
    }
    case 'ring': {
      return new THREE.RingGeometry(
        data.radiusInner, data.radiusOuter, data.segmentsTheta, data.segmentsPhi,
        rad(data.thetaStart), rad(data.thetaLength));
    }
    case 'sphere': {
      return new THREE.SphereBufferGeometry(
        data.radius, data.segmentsWidth, data.segmentsHeight);
    }
    case 'torus': {
      return new THREE.TorusGeometry(
        data.radius, data.radiusTubular * 2, data.segmentsRadial, data.segmentsTubular,
        rad(data.arc));
    }
    case 'torusKnot': {
      return new THREE.TorusKnotGeometry(
        data.radius, data.radiusTubular * 2, data.segmentsRadial, data.segmentsTubular,
        data.p, data.q, data.scaleHeight);
    }
    default: {
      warn('Primitive type not supported: ' + data.primitive);
      return new THREE.Geometry();
    }
  }
}

/**
 * Translates geometry vertices.
 *
 * @param {object} geometry - three.js geometry.
 * @param {object} translate - New translation.
 * @param {object} currentTranslate - Currently applied translation.
 */
function applyTranslate (geometry, translate, currentTranslate) {
  var translation = helperMatrix.makeTranslation(
    translate.x - currentTranslate.x,
    translate.y - currentTranslate.y,
    translate.z - currentTranslate.z
  );
  geometry.applyMatrix(translation);
  geometry.verticesNeedsUpdate = true;
}
<|endoftext|>