<commit_before>/**
 * hive.js
 * Copyright (C) 2013-2015 Marcel Klehr <mklehr@gmx.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
var jsonParse = require('json-stream')
  , through = require('through2')
  , duplexify = require('duplexify')
  , vdom = require('virtual-dom')
  , h = vdom.h

module.exports = setup
module.exports.consumes = ['ui', 'editor', 'api']
function setup(plugin, imports, register) {
  var ui = imports.ui
    , editor = imports.editor
    , api = imports.api

  ui.reduxReducerMap.chat = reducer

  function reducer(state, action) {
    if(!state) {
      return {
        active: false
      , windowSize: 'full'
      , messages: []
      , users: {}
      }
    }
    if('CHAT_ACTIVATE' === action.type) {
      return {...state, messages: [], active: true}
    }
    if('CHAT_ADD_MESSAGE' === action.type) {
      return {...state, messages: state.messages.concat([action.payload])}
    }
    if('CHAT_LOAD_USER' === action.type) {
      return {...state, users: {...state.users, [action.payload.id]: action.payload}}
    }
    if('CHAT_RESIZE' === action.type) {
      return {...state, windowSize: action.payload}
    }
    return state
  }

  var chat = {
    action_activate: function() {
      return {type: 'CHAT_ACTIVATE'}
    }
  , action_createMessage: function*(text) {
      var msg = yield {type: 'CHAT_CREATE_MESSAGE', payload: text}
      return yield {type: 'CHAT_ADD_MESSAGE', payload: msg}
    }
  , action_addMessage: function(msg) {
      return {type: 'CHAT_ADD_MESSAGE', payload: msg}
    }
  , action_resize: function(size) {
      return {type: 'CHAT_RESIZE', payload: size}
    }
  , action_loadUser: function*(userId) {
      var user = yield api.action_user_get(userId)
      return yield {type: 'CHAT_LOAD_USER', payload: user}
    }
  , stream: null
  }

  const middleware = store => next => action => {
    if('CHAT_CREATE_MESSAGE' === action.type) {
      var user = store.getState().session.user.id
        , msg = {user, text: action.payload}
      chat.stream.write(msg)
      return Promise.resolve(msg)
    }
    if('CHAT_ADD_MESSAGE' === action.type) {
      // intercept add message actions and load the associated user object
      return store.dispatch(chat.action_loadUser(action.payload.user))
      .then(function() {
        return next(action)
      })
    }
    return next(action)
  }
  ui.reduxMiddleware.push(middleware)

  editor.onLoad((editableDoc, broadcast) => {
    ui.store.dispatch(chat.action_activate())

    // Set up the chat broadcast
    var writable = jsonStringify()
      , readable = jsonParse()
    chat.stream = duplexify.obj(writable, readable)

    writable
    .pipe(broadcast.createDuplexStream(new Buffer('chat')))
    .pipe(readable)

    ui.store.dispatch({type: 'CHAT_LOAD_USER'
    , payload: ui.store.getState().session.user})

    chat.stream.on('readable', function() {
      var msg
      while(msg = chat.stream.read()) {
        ui.store.dispatch(chat.action_addMessage(msg))
      }
    })
  })

  ui.onRenderBody((store, children) => {
    if(ui.store.getState().chat.active) children.unshift(render(store))
  })

  function render(store) {
    var state = store.getState().chat
    // set window size
    var windowSize = state.windowSize == 'minimized'?
      '.Chat--minimized' : ''
    return h('div.Chat'+windowSize, [
      renderHeader(store)
    , renderMessages(store)
    , renderInterface(store)
    ])
  }

  function renderHeader(store) {
    return h('div.Chat__header', [
      h('div.btn-group.Chat__header__controls', [
        h('a.btn.glyphicon.glyphicon-minus', {
          attributes: {'aria-label':'Minimize chat window'}
        , 'ev-click': evt => store.dispatch(chat.action_resize('minimized'))
        })
      , h('a.btn.glyphicon.glyphicon-pushpin', {
          attributes: {'aria-label':'Keep chat window open'}
        , 'ev-click': evt => store.dispatch(chat.action_resize('full'))
        })
      ])
    , h('h5', [
        h('i.glyphicon.glyphicon-comment')
      , ' Chat '
      , h('small', 'discuss and inspire')
      ])
    ])
  }

  function renderInterface(store) {
    return h('form.form-inline.Chat__Interface', [
      h('div.input-group',[
        h('label.sr-only', {attributes:{for: 'message'}}, 'Chat message'),
        h('input.form-control', {attributes:{
            type:'text'
          , name: 'message'
          , placeholder: 'Chat message'
          },
          'ev-keydown': evt => {
          if(evt.keyCode == 13) {
            evt.preventDefault()
            store.dispatch(chat.action_createMessage(evt.currentTarget.value))
            evt.currentTarget.value = ''
          }
        }})
      ]),
      h('input.btn.btn-default.btn-block.hidden-md.hidden-lg', {
        attributes:{type:'submit', value: 'send'}
      , 'ev-click': evt => {
          evt.preventDefault()
          store.dispatch(chat.action_createMessage(evt.currentTarget.previousSibling.value))
          evt.currentTarget.previousSibling.value = ''
        }
      })
    ])
  }

  function renderMessages(store) {
    var state = store.getState().chat
    return h('div.Chat__messages', {
      scrollTop: new ScrollHook()
    , key: 1
    },
    state.messages.map(function(msg) {
      return renderMessage(state, msg)
    })
    )
  }

  function ScrollHook() { }
  ScrollHook.prototype.hook = function(node, propName, prevVal) {
    var viewportHeight = node.getBoundingClientRect().height
    if(prevVal < node.clientHeight-viewportHeight) return
    node.scrollTop = node.clientHeight
  }

  function renderMessage(state, msg) {
    return h('div.Chat__Message', {attributes:{'data-user': msg.user}}, [
      h('span.Chat__Message__User', state.users[msg.user].name)
    , ': '
    , h('span.Chat__Message__Text', msg.text)
    ])
  }

  register()
}

function jsonStringify() {
  return through.obj(function(buf, enc, cb) {
    this.push(JSON.stringify(buf)+'\n')
    cb()
  })
}
<commit_msg>Fix ScrollHook: needs a setImmediate to wait for the scrollHeight to update<commit_after>/**
 * hive.js
 * Copyright (C) 2013-2015 Marcel Klehr <mklehr@gmx.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
var jsonParse = require('json-stream')
  , through = require('through2')
  , duplexify = require('duplexify')
  , vdom = require('virtual-dom')
  , h = vdom.h

module.exports = setup
module.exports.consumes = ['ui', 'editor', 'api']
function setup(plugin, imports, register) {
  var ui = imports.ui
    , editor = imports.editor
    , api = imports.api

  ui.reduxReducerMap.chat = reducer

  function reducer(state, action) {
    if(!state) {
      return {
        active: false
      , windowSize: 'full'
      , messages: []
      , users: {}
      }
    }
    if('CHAT_ACTIVATE' === action.type) {
      return {...state, messages: [], active: true}
    }
    if('CHAT_ADD_MESSAGE' === action.type) {
      return {...state, messages: state.messages.concat([action.payload])}
    }
    if('CHAT_LOAD_USER' === action.type) {
      return {...state, users: {...state.users, [action.payload.id]: action.payload}}
    }
    if('CHAT_RESIZE' === action.type) {
      return {...state, windowSize: action.payload}
    }
    return state
  }

  var chat = {
    action_activate: function() {
      return {type: 'CHAT_ACTIVATE'}
    }
  , action_createMessage: function*(text) {
      var msg = yield {type: 'CHAT_CREATE_MESSAGE', payload: text}
      return yield {type: 'CHAT_ADD_MESSAGE', payload: msg}
    }
  , action_addMessage: function(msg) {
      return {type: 'CHAT_ADD_MESSAGE', payload: msg}
    }
  , action_resize: function(size) {
      return {type: 'CHAT_RESIZE', payload: size}
    }
  , action_loadUser: function*(userId) {
      var user = yield api.action_user_get(userId)
      return yield {type: 'CHAT_LOAD_USER', payload: user}
    }
  , stream: null
  }

  const middleware = store => next => action => {
    if('CHAT_CREATE_MESSAGE' === action.type) {
      var user = store.getState().session.user.id
        , msg = {user, text: action.payload}
      chat.stream.write(msg)
      return Promise.resolve(msg)
    }
    if('CHAT_ADD_MESSAGE' === action.type) {
      // intercept add message actions and load the associated user object
      return store.dispatch(chat.action_loadUser(action.payload.user))
      .then(function() {
        return next(action)
      })
    }
    return next(action)
  }
  ui.reduxMiddleware.push(middleware)

  editor.onLoad((editableDoc, broadcast) => {
    ui.store.dispatch(chat.action_activate())

    // Set up the chat broadcast
    var writable = jsonStringify()
      , readable = jsonParse()
    chat.stream = duplexify.obj(writable, readable)

    writable
    .pipe(broadcast.createDuplexStream(new Buffer('chat')))
    .pipe(readable)

    ui.store.dispatch({type: 'CHAT_LOAD_USER'
    , payload: ui.store.getState().session.user})

    chat.stream.on('readable', function() {
      var msg
      while(msg = chat.stream.read()) {
        ui.store.dispatch(chat.action_addMessage(msg))
      }
    })
  })

  ui.onRenderBody((store, children) => {
    if(ui.store.getState().chat.active) children.unshift(render(store))
  })

  function render(store) {
    var state = store.getState().chat
    // set window size
    var windowSize = state.windowSize == 'minimized'?
      '.Chat--minimized' : ''
    return h('div.Chat'+windowSize, [
      renderHeader(store)
    , renderMessages(store)
    , renderInterface(store)
    ])
  }

  function renderHeader(store) {
    return h('div.Chat__header', [
      h('div.btn-group.Chat__header__controls', [
        h('a.btn.glyphicon.glyphicon-minus', {
          attributes: {'aria-label':'Minimize chat window'}
        , 'ev-click': evt => store.dispatch(chat.action_resize('minimized'))
        })
      , h('a.btn.glyphicon.glyphicon-pushpin', {
          attributes: {'aria-label':'Keep chat window open'}
        , 'ev-click': evt => store.dispatch(chat.action_resize('full'))
        })
      ])
    , h('h5', [
        h('i.glyphicon.glyphicon-comment')
      , ' Chat '
      , h('small', 'discuss and inspire')
      ])
    ])
  }

  function renderInterface(store) {
    return h('form.form-inline.Chat__Interface', [
      h('div.input-group',[
        h('label.sr-only', {attributes:{for: 'message'}}, 'Chat message'),
        h('input.form-control', {attributes:{
            type:'text'
          , name: 'message'
          , placeholder: 'Chat message'
          },
          'ev-keydown': evt => {
          if(evt.keyCode == 13) {
            evt.preventDefault()
            store.dispatch(chat.action_createMessage(evt.currentTarget.value))
            evt.currentTarget.value = ''
          }
        }})
      ]),
      h('input.btn.btn-default.btn-block.hidden-md.hidden-lg', {
        attributes:{type:'submit', value: 'send'}
      , 'ev-click': evt => {
          evt.preventDefault()
          store.dispatch(chat.action_createMessage(evt.currentTarget.previousSibling.value))
          evt.currentTarget.previousSibling.value = ''
        }
      })
    ])
  }

  function renderMessages(store) {
    var state = store.getState().chat
    return h('div.Chat__messages', {
      scrollTop: new ScrollHook()
    , key: 1
    },
    state.messages.map(function(msg) {
      return renderMessage(state, msg)
    })
    )
  }

  function ScrollHook() {
    this.scrollHeight
  }
  ScrollHook.prototype.hook = function(node, propName, previous) {
    if(previous && node.scrollTop < previous.scrollHeight-node.clientHeight) {
      this.scrollHeight = node.scrollHeight
      return
    }else{
      this.scrollHeight = node.scrollHeight
      setImmediate(function() {
        node.scrollTop = node.scrollHeight-node.clientHeight
      })
    }
  }

  function renderMessage(state, msg) {
    return h('div.Chat__Message', {attributes:{'data-user': msg.user}}, [
      h('span.Chat__Message__User', state.users[msg.user].name)
    , ': '
    , h('span.Chat__Message__Text', msg.text)
    ])
  }

  register()
}

function jsonStringify() {
  return through.obj(function(buf, enc, cb) {
    this.push(JSON.stringify(buf)+'\n')
    cb()
  })
}
<|endoftext|>


<commit_before>var model = new falcor.Model({
  cache: {
    user: {
      name: "Frank",
      surname: "Underwood",
      address: "1600 Pennsylvania Avenue, Washington, DC"
    }
  }
});

model
  .getValue('user.surname')
  .then(function(surname) {
    console.log(surname);
  });

renderTiles([{
  title: 'falcor',
  src: 'https://zemanifesto.files.wordpress.com/2014/07/bravecor1.jpg?w=590&h=442'
}]);

function renderTiles(tiles) {
  const contentDiv = document.querySelector('#content');
  tiles
    .map(tile => tileToDom(tile))
    .forEach(domTile => contentDiv.appendChild(domTile))
}

function tileToDom(tile) {
  const img = document.createElement('img');
  img.src = tile.src;
  img.alt = tile.title;
  return img;
}
<commit_msg>Add header to tiles<commit_after>var model = new falcor.Model({
  cache: {
    user: {
      name: "Frank",
      surname: "Underwood",
      address: "1600 Pennsylvania Avenue, Washington, DC"
    }
  }
});

model
  .getValue('user.surname')
  .then(function(surname) {
    console.log(surname);
  });

renderTiles([{
  title: 'falcor',
  src: 'https://zemanifesto.files.wordpress.com/2014/07/bravecor1.jpg?w=590&h=442'
}]);

function renderTiles(tiles) {
  const contentDiv = document.querySelector('#content');
  tiles
    .map(tile => tileToDom(tile))
    .forEach(domTile => contentDiv.appendChild(domTile))
}

function tileToDom(tile) {
  const header = document.createElement('h2');
  header.appendChild(document.createTextNode(tile.title));

  const img = document.createElement('img');
  img.src = tile.src;
  img.alt = tile.title;

  const tileDom = document.createElement('div');
  tileDom.appendChild(header);
  tileDom.appendChild(img);

  return tileDom;
}
<|endoftext|>


<commit_before>const debug = require('debug')('bittorrent-tracker:client')
const EventEmitter = require('events')
const once = require('once')
const parallel = require('run-parallel')
const Peer = require('simple-peer')
const uniq = require('uniq')

const common = require('./lib/common')
const HTTPTracker = require('./lib/client/http-tracker') // empty object in browser
const UDPTracker = require('./lib/client/udp-tracker') // empty object in browser
const WebSocketTracker = require('./lib/client/websocket-tracker')

/**
 * BitTorrent tracker client.
 *
 * Find torrent peers, to help a torrent client participate in a torrent swarm.
 *
 * @param {Object} opts                          options object
 * @param {string|Buffer} opts.infoHash          torrent info hash
 * @param {string|Buffer} opts.peerId            peer id
 * @param {string|Array.<string>} opts.announce  announce
 * @param {number} opts.port                     torrent client listening port
 * @param {function} opts.getAnnounceOpts        callback to provide data to tracker
 * @param {number} opts.rtcConfig                RTCPeerConnection configuration object
 * @param {number} opts.userAgent                User-Agent header for http requests
 * @param {number} opts.wrtc                     custom webrtc impl (useful in node.js)
 */
class Client extends EventEmitter {
  constructor (opts = {}) {
    super()

    if (!opts.peerId) throw new Error('Option `peerId` is required')
    if (!opts.infoHash) throw new Error('Option `infoHash` is required')
    if (!opts.announce) throw new Error('Option `announce` is required')
    if (!process.browser && !opts.port) throw new Error('Option `port` is required')

    this.peerId = typeof opts.peerId === 'string'
      ? opts.peerId
      : opts.peerId.toString('hex')
    this._peerIdBuffer = Buffer.from(this.peerId, 'hex')
    this._peerIdBinary = this._peerIdBuffer.toString('binary')

    this.infoHash = typeof opts.infoHash === 'string'
      ? opts.infoHash.toLowerCase()
      : opts.infoHash.toString('hex')
    this._infoHashBuffer = Buffer.from(this.infoHash, 'hex')
    this._infoHashBinary = this._infoHashBuffer.toString('binary')

    debug('new client %s', this.infoHash)

    this.destroyed = false

    this._port = opts.port
    this._getAnnounceOpts = opts.getAnnounceOpts
    this._rtcConfig = opts.rtcConfig
    this._userAgent = opts.userAgent

    // Support lazy 'wrtc' module initialization
    // See: https://github.com/webtorrent/webtorrent-hybrid/issues/46
    this._wrtc = typeof opts.wrtc === 'function' ? opts.wrtc() : opts.wrtc

    let announce = typeof opts.announce === 'string'
      ? [opts.announce]
      : opts.announce == null ? [] : opts.announce

    // Remove trailing slash from trackers to catch duplicates
    announce = announce.map(announceUrl => {
      announceUrl = announceUrl.toString()
      if (announceUrl[announceUrl.length - 1] === '/') {
        announceUrl = announceUrl.substring(0, announceUrl.length - 1)
      }
      return announceUrl
    })
    announce = uniq(announce)

    const webrtcSupport = this._wrtc !== false && (!!this._wrtc || Peer.WEBRTC_SUPPORT)

    const nextTickWarn = err => {
      process.nextTick(() => {
        this.emit('warning', err)
      })
    }

    this._trackers = announce
      .map(announceUrl => {
        let parsedUrl
        try {
          parsedUrl = new URL(announceUrl)
        } catch (err) {
          nextTickWarn(new Error(`Invalid tracker URL: ${announceUrl}`))
          return null
        }

        const port = parsedUrl.port
        if (port < 0 || port > 65535) {
          nextTickWarn(new Error(`Invalid tracker port: ${announceUrl}`))
          return null
        }

        const protocol = parsedUrl.protocol
        if ((protocol === 'http:' || protocol === 'https:') &&
            typeof HTTPTracker === 'function') {
          return new HTTPTracker(this, announceUrl)
        } else if (protocol === 'udp:' && typeof UDPTracker === 'function') {
          return new UDPTracker(this, announceUrl)
        } else if ((protocol === 'ws:' || protocol === 'wss:') && webrtcSupport) {
          // Skip ws:// trackers on https:// sites because they throw SecurityError
          if (protocol === 'ws:' && typeof window !== 'undefined' &&
              window.location.protocol === 'https:') {
            nextTickWarn(new Error(`Unsupported tracker protocol: ${announceUrl}`))
            return null
          }
          return new WebSocketTracker(this, announceUrl)
        } else {
          nextTickWarn(new Error(`Unsupported tracker protocol: ${announceUrl}`))
          return null
        }
      })
      .filter(Boolean)
  }

  /**
   * Send a `start` announce to the trackers.
   * @param {Object} opts
   * @param {number=} opts.uploaded
   * @param {number=} opts.downloaded
   * @param {number=} opts.left (if not set, calculated automatically)
   */
  start (opts) {
    debug('send `start`')
    opts = this._defaultAnnounceOpts(opts)
    opts.event = 'started'
    this._announce(opts)

    // start announcing on intervals
    this._trackers.forEach(tracker => {
      tracker.setInterval()
    })
  }

  /**
   * Send a `stop` announce to the trackers.
   * @param {Object} opts
   * @param {number=} opts.uploaded
   * @param {number=} opts.downloaded
   * @param {number=} opts.numwant
   * @param {number=} opts.left (if not set, calculated automatically)
   */
  stop (opts) {
    debug('send `stop`')
    opts = this._defaultAnnounceOpts(opts)
    opts.event = 'stopped'
    this._announce(opts)
  }

  /**
   * Send a `complete` announce to the trackers.
   * @param {Object} opts
   * @param {number=} opts.uploaded
   * @param {number=} opts.downloaded
   * @param {number=} opts.numwant
   * @param {number=} opts.left (if not set, calculated automatically)
   */
  complete (opts) {
    debug('send `complete`')
    if (!opts) opts = {}
    opts = this._defaultAnnounceOpts(opts)
    opts.event = 'completed'
    this._announce(opts)
  }

  /**
   * Send a `update` announce to the trackers.
   * @param {Object} opts
   * @param {number=} opts.uploaded
   * @param {number=} opts.downloaded
   * @param {number=} opts.numwant
   * @param {number=} opts.left (if not set, calculated automatically)
   */
  update (opts) {
    debug('send `update`')
    opts = this._defaultAnnounceOpts(opts)
    if (opts.event) delete opts.event
    this._announce(opts)
  }

  _announce (opts) {
    this._trackers.forEach(tracker => {
      // tracker should not modify `opts` object, it's passed to all trackers
      tracker.announce(opts)
    })
  }

  /**
   * Send a scrape request to the trackers.
   * @param {Object} opts
   */
  scrape (opts) {
    debug('send `scrape`')
    if (!opts) opts = {}
    this._trackers.forEach(tracker => {
      // tracker should not modify `opts` object, it's passed to all trackers
      tracker.scrape(opts)
    })
  }

  setInterval (intervalMs) {
    debug('setInterval %d', intervalMs)
    this._trackers.forEach(tracker => {
      tracker.setInterval(intervalMs)
    })
  }

  destroy (cb) {
    if (this.destroyed) return
    this.destroyed = true
    debug('destroy')

    const tasks = this._trackers.map(tracker => cb => {
      tracker.destroy(cb)
    })

    parallel(tasks, cb)

    this._trackers = []
    this._getAnnounceOpts = null
  }

  _defaultAnnounceOpts (opts = {}) {
    if (opts.numwant == null) opts.numwant = common.DEFAULT_ANNOUNCE_PEERS

    if (opts.uploaded == null) opts.uploaded = 0
    if (opts.downloaded == null) opts.downloaded = 0

    if (this._getAnnounceOpts) opts = Object.assign({}, opts, this._getAnnounceOpts())

    return opts
  }
}

/**
 * Simple convenience function to scrape a tracker for an info hash without needing to
 * create a Client, pass it a parsed torrent, etc. Support scraping a tracker for multiple
 * torrents at the same time.
 * @params {Object} opts
 * @param  {string|Array.<string>} opts.infoHash
 * @param  {string} opts.announce
 * @param  {function} cb
 */
Client.scrape = (opts, cb) => {
  cb = once(cb)

  if (!opts.infoHash) throw new Error('Option `infoHash` is required')
  if (!opts.announce) throw new Error('Option `announce` is required')

  const clientOpts = Object.assign({}, opts, {
    infoHash: Array.isArray(opts.infoHash) ? opts.infoHash[0] : opts.infoHash,
    peerId: Buffer.from('01234567890123456789'), // dummy value
    port: 6881 // dummy value
  })

  const client = new Client(clientOpts)
  client.once('error', cb)
  client.once('warning', cb)

  let len = Array.isArray(opts.infoHash) ? opts.infoHash.length : 1
  const results = {}
  client.on('scrape', data => {
    len -= 1
    results[data.infoHash] = data
    if (len === 0) {
      client.destroy()
      const keys = Object.keys(results)
      if (keys.length === 1) {
        cb(null, results[keys[0]])
      } else {
        cb(null, results)
      }
    }
  })

  opts.infoHash = Array.isArray(opts.infoHash)
    ? opts.infoHash.map(infoHash => {
      return Buffer.from(infoHash, 'hex')
    })
    : Buffer.from(opts.infoHash, 'hex')
  client.scrape({ infoHash: opts.infoHash })
  return client
}

module.exports = Client
<commit_msg>add additional info in debug logs<commit_after>const debug = require('debug')('bittorrent-tracker:client')
const EventEmitter = require('events')
const once = require('once')
const parallel = require('run-parallel')
const Peer = require('simple-peer')
const uniq = require('uniq')

const common = require('./lib/common')
const HTTPTracker = require('./lib/client/http-tracker') // empty object in browser
const UDPTracker = require('./lib/client/udp-tracker') // empty object in browser
const WebSocketTracker = require('./lib/client/websocket-tracker')

/**
 * BitTorrent tracker client.
 *
 * Find torrent peers, to help a torrent client participate in a torrent swarm.
 *
 * @param {Object} opts                          options object
 * @param {string|Buffer} opts.infoHash          torrent info hash
 * @param {string|Buffer} opts.peerId            peer id
 * @param {string|Array.<string>} opts.announce  announce
 * @param {number} opts.port                     torrent client listening port
 * @param {function} opts.getAnnounceOpts        callback to provide data to tracker
 * @param {number} opts.rtcConfig                RTCPeerConnection configuration object
 * @param {number} opts.userAgent                User-Agent header for http requests
 * @param {number} opts.wrtc                     custom webrtc impl (useful in node.js)
 */
class Client extends EventEmitter {
  constructor (opts = {}) {
    super()

    if (!opts.peerId) throw new Error('Option `peerId` is required')
    if (!opts.infoHash) throw new Error('Option `infoHash` is required')
    if (!opts.announce) throw new Error('Option `announce` is required')
    if (!process.browser && !opts.port) throw new Error('Option `port` is required')

    this.peerId = typeof opts.peerId === 'string'
      ? opts.peerId
      : opts.peerId.toString('hex')
    this._peerIdBuffer = Buffer.from(this.peerId, 'hex')
    this._peerIdBinary = this._peerIdBuffer.toString('binary')

    this.infoHash = typeof opts.infoHash === 'string'
      ? opts.infoHash.toLowerCase()
      : opts.infoHash.toString('hex')
    this._infoHashBuffer = Buffer.from(this.infoHash, 'hex')
    this._infoHashBinary = this._infoHashBuffer.toString('binary')

    debug('new client %s', this.infoHash)

    this.destroyed = false

    this._port = opts.port
    this._getAnnounceOpts = opts.getAnnounceOpts
    this._rtcConfig = opts.rtcConfig
    this._userAgent = opts.userAgent

    // Support lazy 'wrtc' module initialization
    // See: https://github.com/webtorrent/webtorrent-hybrid/issues/46
    this._wrtc = typeof opts.wrtc === 'function' ? opts.wrtc() : opts.wrtc

    let announce = typeof opts.announce === 'string'
      ? [opts.announce]
      : opts.announce == null ? [] : opts.announce

    // Remove trailing slash from trackers to catch duplicates
    announce = announce.map(announceUrl => {
      announceUrl = announceUrl.toString()
      if (announceUrl[announceUrl.length - 1] === '/') {
        announceUrl = announceUrl.substring(0, announceUrl.length - 1)
      }
      return announceUrl
    })
    announce = uniq(announce)

    const webrtcSupport = this._wrtc !== false && (!!this._wrtc || Peer.WEBRTC_SUPPORT)

    const nextTickWarn = err => {
      process.nextTick(() => {
        this.emit('warning', err)
      })
    }

    this._trackers = announce
      .map(announceUrl => {
        let parsedUrl
        try {
          parsedUrl = new URL(announceUrl)
        } catch (err) {
          nextTickWarn(new Error(`Invalid tracker URL: ${announceUrl}`))
          return null
        }

        const port = parsedUrl.port
        if (port < 0 || port > 65535) {
          nextTickWarn(new Error(`Invalid tracker port: ${announceUrl}`))
          return null
        }

        const protocol = parsedUrl.protocol
        if ((protocol === 'http:' || protocol === 'https:') &&
            typeof HTTPTracker === 'function') {
          return new HTTPTracker(this, announceUrl)
        } else if (protocol === 'udp:' && typeof UDPTracker === 'function') {
          return new UDPTracker(this, announceUrl)
        } else if ((protocol === 'ws:' || protocol === 'wss:') && webrtcSupport) {
          // Skip ws:// trackers on https:// sites because they throw SecurityError
          if (protocol === 'ws:' && typeof window !== 'undefined' &&
              window.location.protocol === 'https:') {
            nextTickWarn(new Error(`Unsupported tracker protocol: ${announceUrl}`))
            return null
          }
          return new WebSocketTracker(this, announceUrl)
        } else {
          nextTickWarn(new Error(`Unsupported tracker protocol: ${announceUrl}`))
          return null
        }
      })
      .filter(Boolean)
  }

  /**
   * Send a `start` announce to the trackers.
   * @param {Object} opts
   * @param {number=} opts.uploaded
   * @param {number=} opts.downloaded
   * @param {number=} opts.left (if not set, calculated automatically)
   */
  start (opts) {
    opts = this._defaultAnnounceOpts(opts)
    opts.event = 'started'
    debug('send `start` %o', opts)
    this._announce(opts)

    // start announcing on intervals
    this._trackers.forEach(tracker => {
      tracker.setInterval()
    })
  }

  /**
   * Send a `stop` announce to the trackers.
   * @param {Object} opts
   * @param {number=} opts.uploaded
   * @param {number=} opts.downloaded
   * @param {number=} opts.numwant
   * @param {number=} opts.left (if not set, calculated automatically)
   */
  stop (opts) {
    opts = this._defaultAnnounceOpts(opts)
    opts.event = 'stopped'
    debug('send `stop` %o', opts)
    this._announce(opts)
  }

  /**
   * Send a `complete` announce to the trackers.
   * @param {Object} opts
   * @param {number=} opts.uploaded
   * @param {number=} opts.downloaded
   * @param {number=} opts.numwant
   * @param {number=} opts.left (if not set, calculated automatically)
   */
  complete (opts) {
    if (!opts) opts = {}
    opts = this._defaultAnnounceOpts(opts)
    opts.event = 'completed'
    debug('send `complete` %o', opts)
    this._announce(opts)
  }

  /**
   * Send a `update` announce to the trackers.
   * @param {Object} opts
   * @param {number=} opts.uploaded
   * @param {number=} opts.downloaded
   * @param {number=} opts.numwant
   * @param {number=} opts.left (if not set, calculated automatically)
   */
  update (opts) {
    opts = this._defaultAnnounceOpts(opts)
    if (opts.event) delete opts.event
    debug('send `update` %o', opts)
    this._announce(opts)
  }

  _announce (opts) {
    this._trackers.forEach(tracker => {
      // tracker should not modify `opts` object, it's passed to all trackers
      tracker.announce(opts)
    })
  }

  /**
   * Send a scrape request to the trackers.
   * @param {Object} opts
   */
  scrape (opts) {
    debug('send `scrape`')
    if (!opts) opts = {}
    this._trackers.forEach(tracker => {
      // tracker should not modify `opts` object, it's passed to all trackers
      tracker.scrape(opts)
    })
  }

  setInterval (intervalMs) {
    debug('setInterval %d', intervalMs)
    this._trackers.forEach(tracker => {
      tracker.setInterval(intervalMs)
    })
  }

  destroy (cb) {
    if (this.destroyed) return
    this.destroyed = true
    debug('destroy')

    const tasks = this._trackers.map(tracker => cb => {
      tracker.destroy(cb)
    })

    parallel(tasks, cb)

    this._trackers = []
    this._getAnnounceOpts = null
  }

  _defaultAnnounceOpts (opts = {}) {
    if (opts.numwant == null) opts.numwant = common.DEFAULT_ANNOUNCE_PEERS

    if (opts.uploaded == null) opts.uploaded = 0
    if (opts.downloaded == null) opts.downloaded = 0

    if (this._getAnnounceOpts) opts = Object.assign({}, opts, this._getAnnounceOpts())

    return opts
  }
}

/**
 * Simple convenience function to scrape a tracker for an info hash without needing to
 * create a Client, pass it a parsed torrent, etc. Support scraping a tracker for multiple
 * torrents at the same time.
 * @params {Object} opts
 * @param  {string|Array.<string>} opts.infoHash
 * @param  {string} opts.announce
 * @param  {function} cb
 */
Client.scrape = (opts, cb) => {
  cb = once(cb)

  if (!opts.infoHash) throw new Error('Option `infoHash` is required')
  if (!opts.announce) throw new Error('Option `announce` is required')

  const clientOpts = Object.assign({}, opts, {
    infoHash: Array.isArray(opts.infoHash) ? opts.infoHash[0] : opts.infoHash,
    peerId: Buffer.from('01234567890123456789'), // dummy value
    port: 6881 // dummy value
  })

  const client = new Client(clientOpts)
  client.once('error', cb)
  client.once('warning', cb)

  let len = Array.isArray(opts.infoHash) ? opts.infoHash.length : 1
  const results = {}
  client.on('scrape', data => {
    len -= 1
    results[data.infoHash] = data
    if (len === 0) {
      client.destroy()
      const keys = Object.keys(results)
      if (keys.length === 1) {
        cb(null, results[keys[0]])
      } else {
        cb(null, results)
      }
    }
  })

  opts.infoHash = Array.isArray(opts.infoHash)
    ? opts.infoHash.map(infoHash => {
      return Buffer.from(infoHash, 'hex')
    })
    : Buffer.from(opts.infoHash, 'hex')
  client.scrape({ infoHash: opts.infoHash })
  return client
}

module.exports = Client
<|endoftext|>


<commit_before>define(function () {
    var plugin = {
        settings: {
            name: "upload",
            type: "general",
            icon: "plugins/upload/icon.png",
            subMenus: [
                {name: "browsephotoalbums", menuURL: "#upload/browse", icon: ""},
                {name: "takepicture", menuURL: "#upload/take", icon: ""},
                {name: "recordaudio", menuURL: "#upload/record", icon: ""},
                {name: "video", menuURL: "#upload/video", icon: ""}
            ],
            lang: {
                component: "core"
            },
            toogler: true
        },

        routes: [
            ["upload/browse", "upload_browse", "browseAlbums"],
            ["upload/take", "upload_take", "takeMedia"],
            ["upload/record", "upload_record", "recordAudio"],
            ["upload/video", "upload_video", "uploadVideo"]
        ],

        /**
         * Determines is the plugin is visible.
         * It may check Moodle remote site version, device OS, device type, etc...
         * This function is called when a alink to a plugin functinality is going to be rendered.
         *
         * @return {bool} True if the plugin is visible for the site and device
         */
        isPluginVisible: function() {
            if (MM.config && MM.config.current_site &&
                typeof(MM.config.current_site.uploadfiles) != "undefined" &&
                MM.config.current_site.uploadfiles === 0) {

                return false;
            }
            return true;
        },

        browseAlbums: function() {
            MM.log('Trying to get a image from albums', 'Upload');
            MM.Router.navigate("");

            var width  =  $(document).innerWidth()  - 200;
            var height =  $(document).innerHeight() - 200;

            // iPad popOver, see https://tracker.moodle.org/browse/MOBILE-208
            var popover = new CameraPopoverOptions(10, 10, width, height, Camera.PopoverArrowDirection.ARROW_ANY);

            navigator.camera.getPicture(MM.plugins.upload.photoSuccess, MM.plugins.upload.photoFails, {
                quality: 50,
                destinationType: navigator.camera.DestinationType.FILE_URI,
                sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY,
                popoverOptions : popover
            });
        },

        takeMedia: function() {
            MM.log('Trying to get a image from camera', 'Upload');
            MM.Router.navigate("");

            navigator.camera.getPicture(MM.plugins.upload.photoSuccess, MM.plugins.upload.photoFails, {
                quality: 50,
                destinationType: navigator.camera.DestinationType.FILE_URI
            });
        },

        recordAudio: function() {
            MM.Router.navigate("");
            MM.log('Trying to record and Audio', 'Upload');
            navigator.device.capture.captureAudio(MM.plugins.upload.recordAudioSuccess, MM.plugins.upload.recordAudioFails, {limit: 1});
        },

        uploadVideo: function() {
            MM.Router.navigate("");
            MM.log('Trying to record a video', 'Upload');
            navigator.device.capture.captureVideo(
                MM.plugins.upload.uploadVideoSuccess,
                MM.plugins.upload.uploadVideoFails,
                {limit: 1});
        },

        photoSuccess: function(uri) {

            MM.log('Uploading an image to Moodle', 'Upload');
            var d = new Date();

            var options = {};
            options.fileKey="file";

            // Check if we are in desktop or mobile.
            if (MM.inNodeWK) {
                options.fileName = uri.lastIndexOf("/") + 1;
            } else {
                options.fileName = "image_" + d.getTime() + ".jpg";
            }

            options.mimeType="image/jpeg";

            MM.moodleUploadFile(uri, options,
                                function(){ MM.popMessage(MM.lang.s("imagestored")); },
                                function(){ MM.popErrorMessage(MM.lang.s("erroruploading")) }
            );

        },

        photoFails: function(message) {
            MM.log('Error trying getting a photo', 'Upload');
            if (message) {
                MM.log('Error message: ' + JSON.stringify(message));
            }
            if (message.toLowerCase().indexOf("error") > -1 || message.toLowerCase().indexOf("unable") > -1) {
                MM.popErrorMessage(message);
            }
        },

        recordAudioSuccess: function(mediaFiles) {

            MM.log('Auddio sucesfully recorded', 'Upload');

            var i, len;
            for (i = 0, len = mediaFiles.length; i < len; i += 1) {
                var options = {};
                options.fileKey = null;
                options.fileName = mediaFiles[i].name;
                options.mimeType = null;

                MM.moodleUploadFile(mediaFiles[i].fullPath, options,
                                    function(){
                                        MM.popMessage(MM.lang.s("recordstored"));
                                    },
                                    function(){
                                        MM.popErrorMessage(MM.lang.s("erroruploading"))
                                    }
                );
            }
        },

        recordAudioFails: function(error) {
            if (!error) {
                error = { code: 0};
            }

            MM.log('Error trying recording an audio ' + error.code, 'Upload');
            if (error.code != CaptureError.CAPTURE_NO_MEDIA_FILES) {
                MM.popErrorMessage(MM.lang.s("errorcapturingaudio"));
            }
        },

        uploadVideoSuccess: function(mediaFiles) {

            MM.log('Video sucesfully recorded', 'Upload');

            var i, len;
            for (i = 0, len = mediaFiles.length; i < len; i += 1) {
                var options = {};
                options.fileKey = null;
                options.fileName = mediaFiles[i].name;
                options.mimeType = null;

                MM.moodleUploadFile(mediaFiles[i].fullPath, options,
                                    function(){
                                        MM.popMessage(MM.lang.s("videostored"));
                                    },
                                    function(){
                                        MM.popErrorMessage(MM.lang.s("erroruploading"))
                                    }
                );
            }
        },

        uploadVideoFails: function(error) {
            if (!error) {
                error = { code: 0};
            }

            MM.log('Error trying recording a video ' + error.code, 'Upload');
            if (error.code != CaptureError.CAPTURE_NO_MEDIA_FILES) {
                MM.popErrorMessage(MM.lang.s("errorcapturingvideo"));
            }
        }
    }

    MM.registerPlugin(plugin);

});<commit_msg>MOBILE-580 Remove upload option from menu<commit_after>define(function () {
    var plugin = {
        settings: {
            name: "upload",
            type: "general",
            icon: "plugins/upload/icon.png",
            subMenus: [
                {name: "browsephotoalbums", menuURL: "#upload/browse", icon: ""},
                {name: "takepicture", menuURL: "#upload/take", icon: ""},
                {name: "recordaudio", menuURL: "#upload/record", icon: ""},
                {name: "video", menuURL: "#upload/video", icon: ""}
            ],
            lang: {
                component: "core"
            },
            toogler: true
        },

        routes: [
            ["upload/browse", "upload_browse", "browseAlbums"],
            ["upload/take", "upload_take", "takeMedia"],
            ["upload/record", "upload_record", "recordAudio"],
            ["upload/video", "upload_video", "uploadVideo"]
        ],

        /**
         * Determines is the plugin is visible.
         * It may check Moodle remote site version, device OS, device type, etc...
         * This function is called when a alink to a plugin functinality is going to be rendered.
         *
         * @return {bool} True if the plugin is visible for the site and device
         */
        isPluginVisible: function() {
            if (MM.config && MM.config.current_site &&
                typeof(MM.config.current_site.uploadfiles) != "undefined" &&
                MM.config.current_site.uploadfiles === 0) {

                return false;
            }
            return true;
        },

        browseAlbums: function() {
            MM.log('Trying to get a image from albums', 'Upload');
            MM.Router.navigate("");

            var width  =  $(document).innerWidth()  - 200;
            var height =  $(document).innerHeight() - 200;

            // iPad popOver, see https://tracker.moodle.org/browse/MOBILE-208
            var popover = new CameraPopoverOptions(10, 10, width, height, Camera.PopoverArrowDirection.ARROW_ANY);

            navigator.camera.getPicture(MM.plugins.upload.photoSuccess, MM.plugins.upload.photoFails, {
                quality: 50,
                destinationType: navigator.camera.DestinationType.FILE_URI,
                sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY,
                popoverOptions : popover
            });
        },

        takeMedia: function() {
            MM.log('Trying to get a image from camera', 'Upload');
            MM.Router.navigate("");

            navigator.camera.getPicture(MM.plugins.upload.photoSuccess, MM.plugins.upload.photoFails, {
                quality: 50,
                destinationType: navigator.camera.DestinationType.FILE_URI
            });
        },

        recordAudio: function() {
            MM.Router.navigate("");
            MM.log('Trying to record and Audio', 'Upload');
            navigator.device.capture.captureAudio(MM.plugins.upload.recordAudioSuccess, MM.plugins.upload.recordAudioFails, {limit: 1});
        },

        uploadVideo: function() {
            MM.Router.navigate("");
            MM.log('Trying to record a video', 'Upload');
            navigator.device.capture.captureVideo(
                MM.plugins.upload.uploadVideoSuccess,
                MM.plugins.upload.uploadVideoFails,
                {limit: 1});
        },

        photoSuccess: function(uri) {

            MM.log('Uploading an image to Moodle', 'Upload');
            var d = new Date();

            var options = {};
            options.fileKey="file";

            // Check if we are in desktop or mobile.
            if (MM.inNodeWK) {
                options.fileName = uri.lastIndexOf("/") + 1;
            } else {
                options.fileName = "image_" + d.getTime() + ".jpg";
            }

            options.mimeType="image/jpeg";

            MM.moodleUploadFile(uri, options,
                                function(){ MM.popMessage(MM.lang.s("imagestored")); },
                                function(){ MM.popErrorMessage(MM.lang.s("erroruploading")) }
            );

        },

        photoFails: function(message) {
            MM.log('Error trying getting a photo', 'Upload');
            if (message) {
                MM.log('Error message: ' + JSON.stringify(message));
            }
            if (message.toLowerCase().indexOf("error") > -1 || message.toLowerCase().indexOf("unable") > -1) {
                MM.popErrorMessage(message);
            }
        },

        recordAudioSuccess: function(mediaFiles) {

            MM.log('Auddio sucesfully recorded', 'Upload');

            var i, len;
            for (i = 0, len = mediaFiles.length; i < len; i += 1) {
                var options = {};
                options.fileKey = null;
                options.fileName = mediaFiles[i].name;
                options.mimeType = null;

                MM.moodleUploadFile(mediaFiles[i].fullPath, options,
                                    function(){
                                        MM.popMessage(MM.lang.s("recordstored"));
                                    },
                                    function(){
                                        MM.popErrorMessage(MM.lang.s("erroruploading"))
                                    }
                );
            }
        },

        recordAudioFails: function(error) {
            if (!error) {
                error = { code: 0};
            }

            MM.log('Error trying recording an audio ' + error.code, 'Upload');
            if (error.code != CaptureError.CAPTURE_NO_MEDIA_FILES) {
                MM.popErrorMessage(MM.lang.s("errorcapturingaudio"));
            }
        },

        uploadVideoSuccess: function(mediaFiles) {

            MM.log('Video sucesfully recorded', 'Upload');

            var i, len;
            for (i = 0, len = mediaFiles.length; i < len; i += 1) {
                var options = {};
                options.fileKey = null;
                options.fileName = mediaFiles[i].name;
                options.mimeType = null;

                MM.moodleUploadFile(mediaFiles[i].fullPath, options,
                                    function(){
                                        MM.popMessage(MM.lang.s("videostored"));
                                    },
                                    function(){
                                        MM.popErrorMessage(MM.lang.s("erroruploading"))
                                    }
                );
            }
        },

        uploadVideoFails: function(error) {
            if (!error) {
                error = { code: 0};
            }

            MM.log('Error trying recording a video ' + error.code, 'Upload');
            if (error.code != CaptureError.CAPTURE_NO_MEDIA_FILES) {
                MM.popErrorMessage(MM.lang.s("errorcapturingvideo"));
            }
        }
    }

    if (MM.inNodeWK) {
        // Remove the not supported upload video.
        plugin.settings.subMenus.pop();
    }

    MM.registerPlugin(plugin);

});<|endoftext|>


<commit_before>

var ws;
var HOST = '192.168.2.100';
//var HOST = 'localhost';
//var HOST = '212.227.97.146';
var PORT = 8080;

var Hist = [];
var Client = {
  active: false,
  timeout: null,
  id: null,
  p_id: -1,
  sendqueue: new ArrayBuffer(0),

  initialize: function() {

    ws = new WebSocket('ws://'+HOST+':'+PORT+'/');

    var that = this;

    ws.binaryType = 'arraybuffer';
    console.log("connecting to server "+HOST);

    ws.onopen = function(e) {
      that.active = true;
      Client.p_id = -1;
      console.log("Connected to "+HOST+":"+PORT);
    };

    ws.onclose = function(e) {
      that.active = false;

      console.log("Connection lost to server", e);;
      clearTimeout(that.timeout);
      that.timeout = setTimeout(function() {
        that.initialize();
      }, 1000);
    };

    ws.onmessage = function (e, flags) {
      if(typeof(e.data) == "string") {
        console.log("recieve string ", e.data);
      }else { //binary
        Structure.parse(e.data, processMessage);
      }
    };

    var processMessage = function(objs) {
      //console.log("message", objs);
      for(var i = 0; i < objs.length; i++) {
        var obj = objs[i];
        console.log("mypid "+Client.p_id+" vs "+obj.p_id);
        if(Client.p_id == obj.p_id) return; // already allied this change
        if(Client.p_id > obj.p_id) {  // client already has this patch applied
          //console.log("error wrong pids");
          console.log(obj);
          //debugger;
          return;
        }
        if(Client.p_id != obj.p_id -1) {
          //debugger;
        }
        Hist.push(obj);
        //console.log(obj);
        App.time = obj.time;
        Client.p_id = Math.max(obj.p_id, Client.p_id);

        switch(obj.type) {
        case 0:
          Client.id = obj.id;
          App.state = obj.state;
          break;
        case 1:
          App.dispatchEvent(obj);
          break;
        case 2:
          App.setActor(obj);
          break;
        case 3:
          App.time = obj.time;
          App.restartMatch();
          break;
        case 4:
          console.log("add pickup in client");
          console.log(obj);
          Pickups.add(obj.num, obj.x, obj.y);
          break;
        case 5:
          Pickups.effect(obj.id, obj.num);
          break;
        case 6:
          App.scores[obj.id] = App.scores[obj.id] + 1 || 1;
          App.state = 0;  // after match;
          App.last_win = obj.id;
        break;
        }

      }
    }
  },

  push: function() {  // send data with timestamp to the client inform him abut what has changed
    if(ws.readyState !== 1 || this.id === null) return;

    var sendDir = 0;
    // no pause
    if(App.state == 2) {
      if(Key.down(39) || Key.down(68)) {
        sendDir = 1;
      }else if(Key.down(37) || Key.down(65)) {
        sendDir = -1;
      }
    }
    if(App.state != 0) {
      this.appendQueue({
        type: 1,
        id: this.id,
        dir: sendDir,
        time: App.time,
        p_id: Client.p_id
      });
    }

    console.log("send ", Client.sendqueue);
    console.log("length ", Client.sendqueue.byteLength);
    if(Client.sendqueue.byteLength > 0)
      ws.send(Client.sendqueue);
    Client.sendqueue = new ArrayBuffer(0);
  },

  getId: function() {
    return this.id;
  },

  appendQueue: function(obj) {
    Client.sendqueue = Structure.append(
      Client.sendqueue,
      Structure.pack(obj, obj.type)
    );
  },

  sendPickup: function(type) {
    this.appendQueue({
      type: 5,
      p_id: Client.p_id,
      time: App.time,
      id: this.id,
      num: type
    });
  },

  sendEnd: function(id) {
    this.appendQueue({
      type: 6,
      p_id: Client.p_id,
      time: App.time,
      id: id
    });
  }
};

setInterval(function() {
  Client.push()
}, 10);<commit_msg>add environment switch<commit_after>

var ws;
var HOST = (location.host == "and7s.github.io") ? '212.227.97.146' : '192.168.2.100';
//var HOST = 'localhost';
//var HOST = '212.227.97.146';
var PORT = 8080;

var Hist = [];
var Client = {
  active: false,
  timeout: null,
  id: null,
  p_id: -1,
  sendqueue: new ArrayBuffer(0),

  initialize: function() {

    ws = new WebSocket('ws://'+HOST+':'+PORT+'/');

    var that = this;

    ws.binaryType = 'arraybuffer';
    console.log("connecting to server "+HOST);

    ws.onopen = function(e) {
      that.active = true;
      Client.p_id = -1;
      console.log("Connected to "+HOST+":"+PORT);
    };

    ws.onclose = function(e) {
      that.active = false;

      console.log("Connection lost to server", e);;
      clearTimeout(that.timeout);
      that.timeout = setTimeout(function() {
        that.initialize();
      }, 1000);
    };

    ws.onmessage = function (e, flags) {
      if(typeof(e.data) == "string") {
        console.log("recieve string ", e.data);
      }else { //binary
        Structure.parse(e.data, processMessage);
      }
    };

    var processMessage = function(objs) {
      //console.log("message", objs);
      for(var i = 0; i < objs.length; i++) {
        var obj = objs[i];
        console.log("mypid "+Client.p_id+" vs "+obj.p_id);
        if(Client.p_id == obj.p_id) return; // already allied this change
        if(Client.p_id > obj.p_id) {  // client already has this patch applied
          //console.log("error wrong pids");
          console.log(obj);
          //debugger;
          return;
        }
        if(Client.p_id != obj.p_id -1) {
          //debugger;
        }
        Hist.push(obj);
        //console.log(obj);
        App.time = obj.time;
        Client.p_id = Math.max(obj.p_id, Client.p_id);

        switch(obj.type) {
        case 0:
          Client.id = obj.id;
          App.state = obj.state;
          break;
        case 1:
          App.dispatchEvent(obj);
          break;
        case 2:
          App.setActor(obj);
          break;
        case 3:
          App.time = obj.time;
          App.restartMatch();
          break;
        case 4:
          console.log("add pickup in client");
          console.log(obj);
          Pickups.add(obj.num, obj.x, obj.y);
          break;
        case 5:
          Pickups.effect(obj.id, obj.num);
          break;
        case 6:
          App.scores[obj.id] = App.scores[obj.id] + 1 || 1;
          App.state = 0;  // after match;
          App.last_win = obj.id;
        break;
        }

      }
    }
  },

  push: function() {  // send data with timestamp to the client inform him abut what has changed
    if(ws.readyState !== 1 || this.id === null) return;

    var sendDir = 0;
    // no pause
    if(App.state == 2) {
      if(Key.down(39) || Key.down(68)) {
        sendDir = 1;
      }else if(Key.down(37) || Key.down(65)) {
        sendDir = -1;
      }
    }
    if(App.state != 0) {
      this.appendQueue({
        type: 1,
        id: this.id,
        dir: sendDir,
        time: App.time,
        p_id: Client.p_id
      });
    }

    console.log("send ", Client.sendqueue);
    console.log("length ", Client.sendqueue.byteLength);
    if(Client.sendqueue.byteLength > 0)
      ws.send(Client.sendqueue);
    Client.sendqueue = new ArrayBuffer(0);
  },

  getId: function() {
    return this.id;
  },

  appendQueue: function(obj) {
    Client.sendqueue = Structure.append(
      Client.sendqueue,
      Structure.pack(obj, obj.type)
    );
  },

  sendPickup: function(type) {
    this.appendQueue({
      type: 5,
      p_id: Client.p_id,
      time: App.time,
      id: this.id,
      num: type
    });
  },

  sendEnd: function(id) {
    this.appendQueue({
      type: 6,
      p_id: Client.p_id,
      time: App.time,
      id: id
    });
  }
};

setInterval(function() {
  Client.push()
}, 10);<|endoftext|>


<commit_before>/**
 * hive.js
 * Copyright (C) 2013-2015 Marcel Klehr <mklehr@gmx.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
var jsonParse = require('json-stream')
  , through = require('through2')
  , duplexify = require('duplexify')
  , vdom = require('virtual-dom')
  , h = vdom.h

module.exports = setup
module.exports.consumes = ['ui', 'editor', 'api', 'settings']
module.exports.provides = ['chat']
function setup(plugin, imports, register) {
  var ui = imports.ui
    , editor = imports.editor
    , api = imports.api
    , settings = imports.settings

  ui.reduxReducerMap.chat = reducer

  function reducer(state, action) {
    if(!state) {
      return {
        active: false
      , windowSize: 'full'
      , messages: []
      , users: {}
      }
    }
    if('CHAT_ACTIVATE' === action.type) {
      return {...state, messages: [], active: true}
    }
    if('CHAT_ADD_MESSAGE' === action.type) {
      return {...state, messages: state.messages.concat([action.payload])}
    }
    if('CHAT_LOAD_USER' === action.type) {
      return {...state, users: {...state.users, [action.payload.id]: action.payload}}
    }
    if('CHAT_RESIZE' === action.type) {
      return {...state, windowSize: action.payload}
    }
    return state
  }

  var chat = {
    action_activate: function() {
      return {type: 'CHAT_ACTIVATE'}
    }
  , action_createMessage: function*(text) {
      var msg = yield {type: 'CHAT_CREATE_MESSAGE', payload: text}
      return yield {type: 'CHAT_ADD_MESSAGE', payload: msg}
    }
  , action_addMessage: function(msg) {
      return {type: 'CHAT_ADD_MESSAGE', payload: msg}
    }
  , action_resize: function*(size) {
      return yield [
        {type: 'CHAT_RESIZE', payload: size}
      , settings.action_setForUser({'chat:windowSize': size})
      ]
    }
  , action_loadUser: function*(userId) {
      var user = yield api.action_user_get(userId)
      return yield {type: 'CHAT_LOAD_USER', payload: user}
    }
  , stream: null
  }

  const middleware = store => next => action => {
    if('CHAT_CREATE_MESSAGE' === action.type) {
      var user = store.getState().session.user.id
        , msg = {user, text: action.payload}
      chat.stream.write(msg)
      return Promise.resolve(msg)
    }
    if('CHAT_ADD_MESSAGE' === action.type) {
      // intercept add message actions and load the associated user object
      return store.dispatch(chat.action_loadUser(action.payload.user))
      .then(function() {
        return next(action)
      })
    }
    return next(action)
  }
  ui.reduxMiddleware.push(middleware)

  editor.onLoad((editableDoc, broadcast) => {
    ui.store.dispatch(chat.action_activate())

    // Set up the chat broadcast
    var writable = jsonStringify()
      , readable = jsonParse()
    chat.stream = duplexify.obj(writable, readable)

    writable
    .pipe(broadcast.createDuplexStream(new Buffer('chat')))
    .pipe(readable)

    ui.store.dispatch({type: 'CHAT_LOAD_USER'
    , payload: ui.store.getState().session.user})

    chat.stream.on('readable', function() {
      var msg
      while(msg = chat.stream.read()) {
        ui.store.dispatch(chat.action_addMessage(msg))
      }
    })

    settings.onChange(_=> {
      ui.store.dispatch(
        {type: 'CHAT_RESIZE', payload: settings.getForUser('chat:windowSize')})
    })
  })

  ui.onRenderBody((store, children) => {
    if(ui.store.getState().chat.active) children.unshift(render(store))
  })

  settings.onRenderUserSettings((children) => {
    children.push(renderSetting(ui.store))
  })

  function renderSetting(store) {
    var state = store.getState()
    return h('div', [
    , h('h4', 'Chat')
    , h('ul.list-group',
        h('li.list-group-item', [
          h('label', [
            h('input', {
              type: 'checkbox'
            , 'ev-change': evt => {
                store.dispatch(settings.action_setForUser({
                  'chat:windowSize': evt.currentTarget.checked? 'minimized' : 'full'
                }))
              }
            , attributes: settings.getForUser('chat:windowSize') == 'minimized'? {checked: true} : {}
            })
          , ' Keep chat minimized'
          ])
        ])
      )
    ])
  }

  function render(store) {
    var state = store.getState().chat
    // set window size
    var windowSize = state.windowSize == 'minimized'?
      '.Chat--minimized' : ''
    return h('div.Chat'+windowSize, [
      renderHeader(store)
    , renderMessages(store)
    , renderInterface(store)
    ])
  }

  function renderHeader(store) {
    return h('div.Chat__header', [
      h('div.btn-group.Chat__header__controls', [
        h('a.btn.glyphicon.glyphicon-minus', {
          attributes: {'aria-label':'Minimize chat window'}
        , 'ev-click': evt => store.dispatch(chat.action_resize('minimized'))
        })
      , h('a.btn.glyphicon.glyphicon-pushpin', {
          attributes: {'aria-label':'Keep chat window open'}
        , 'ev-click': evt => store.dispatch(chat.action_resize('full'))
        })
      ])
    , h('h5', [
        h('i.glyphicon.glyphicon-comment')
      , ' Chat '
      , h('small', 'discuss and inspire')
      ])
    ])
  }

  function renderInterface(store) {
    return h('form.form-inline.Chat__Interface', [
      h('div.input-group',[
        h('label.sr-only', {attributes:{for: 'message'}}, 'Chat message'),
        h('input.form-control', {attributes:{
            type:'text'
          , name: 'message'
          , placeholder: 'Chat message'
          },
          'ev-keydown': evt => {
          if(evt.keyCode == 13) {
            evt.preventDefault()
            store.dispatch(chat.action_createMessage(evt.currentTarget.value))
            evt.currentTarget.value = ''
          }
        }})
      ]),
      h('input.btn.btn-default.btn-block.hidden-md.hidden-lg', {
        attributes:{type:'submit', value: 'send'}
      , 'ev-click': evt => {
          evt.preventDefault()
          store.dispatch(chat.action_createMessage(evt.currentTarget.previousSibling.value))
          evt.currentTarget.previousSibling.value = ''
        }
      })
    ])
  }

  function renderMessages(store) {
    var state = store.getState().chat
    return h('div.Chat__messages', {
      scrollTop: new ScrollHook()
    , key: 1
    },
    state.messages.map(function(msg) {
      return renderMessage(state, msg)
    })
    )
  }

  function ScrollHook() {
    this.scrollHeight
  }
  ScrollHook.prototype.hook = function(node, propName, previous) {
    if(previous && node.scrollTop < previous.scrollHeight-node.clientHeight) {
      this.scrollHeight = node.scrollHeight
      return
    }else{
      this.scrollHeight = node.scrollHeight
      setImmediate(function() {
        node.scrollTop = node.scrollHeight-node.clientHeight
      })
    }
  }

  function renderMessage(state, msg) {
    return h('div.Chat__Message', {attributes:{'data-user': msg.user}}, [
      h('span.Chat__Message__User', state.users[msg.user].name)
    , ': '
    , h('span.Chat__Message__Text', msg.text)
    ])
  }

  register(null, {chat})
}

function jsonStringify() {
  return through.obj(function(buf, enc, cb) {
    this.push(JSON.stringify(buf)+'\n')
    cb()
  })
}
<commit_msg>implement onClose<commit_after>/**
 * hive.js
 * Copyright (C) 2013-2015 Marcel Klehr <mklehr@gmx.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
var jsonParse = require('json-stream')
  , through = require('through2')
  , duplexify = require('duplexify')
  , vdom = require('virtual-dom')
  , h = vdom.h

module.exports = setup
module.exports.consumes = ['ui', 'editor', 'api', 'settings']
module.exports.provides = ['chat']
function setup(plugin, imports, register) {
  var ui = imports.ui
    , editor = imports.editor
    , api = imports.api
    , settings = imports.settings

  ui.reduxReducerMap.chat = reducer

  function reducer(state, action) {
    if(!state) {
      return {
        active: false
      , windowSize: 'full'
      , messages: []
      , users: {}
      }
    }
    if('CHAT_ACTIVATE' === action.type) {
      return {...state, messages: [], active: true}
    }
    if('CHAT_ADD_MESSAGE' === action.type) {
      return {...state, messages: state.messages.concat([action.payload])}
    }
    if('CHAT_LOAD_USER' === action.type) {
      return {...state, users: {...state.users, [action.payload.id]: action.payload}}
    }
    if('CHAT_RESIZE' === action.type) {
      return {...state, windowSize: action.payload}
    }
    return state
  }

  var chat = {
    action_activate: function() {
      return {type: 'CHAT_ACTIVATE'}
    }
  , action_createMessage: function*(text) {
      var msg = yield {type: 'CHAT_CREATE_MESSAGE', payload: text}
      return yield {type: 'CHAT_ADD_MESSAGE', payload: msg}
    }
  , action_addMessage: function(msg) {
      return {type: 'CHAT_ADD_MESSAGE', payload: msg}
    }
  , action_resize: function*(size) {
      return yield [
        {type: 'CHAT_RESIZE', payload: size}
      , settings.action_setForUser({'chat:windowSize': size})
      ]
    }
  , action_loadUser: function*(userId) {
      var user = yield api.action_user_get(userId)
      return yield {type: 'CHAT_LOAD_USER', payload: user}
    }
  , stream: null
  }

  const middleware = store => next => action => {
    if('CHAT_CREATE_MESSAGE' === action.type) {
      var user = store.getState().session.user.id
        , msg = {user, text: action.payload}
      chat.stream.write(msg)
      return Promise.resolve(msg)
    }
    if('CHAT_ADD_MESSAGE' === action.type) {
      // intercept add message actions and load the associated user object
      return store.dispatch(chat.action_loadUser(action.payload.user))
      .then(function() {
        return next(action)
      })
    }
    return next(action)
  }
  ui.reduxMiddleware.push(middleware)

  editor.onLoad((editableDoc, broadcast, onClose) => {
    ui.store.dispatch(chat.action_activate())

    // Set up the chat broadcast
    var writable = jsonStringify()
      , readable = jsonParse()
    chat.stream = duplexify.obj(writable, readable)

    writable
    .pipe(broadcast.createDuplexStream(new Buffer('chat')))
    .pipe(readable)

    ui.store.dispatch({type: 'CHAT_LOAD_USER'
    , payload: ui.store.getState().session.user})

    chat.stream.on('readable', function() {
      var msg
      while(msg = chat.stream.read()) {
        ui.store.dispatch(chat.action_addMessage(msg))
      }
    })

    var dispose = settings.onChange(_=> {
      ui.store.dispatch(
        {type: 'CHAT_RESIZE', payload: settings.getForUser('chat:windowSize')})
    })

    onClose(_=> {
      chat.stream = null
      dispose()
    })
  })

  ui.onRenderBody((store, children) => {
    if(ui.store.getState().chat.active) children.unshift(render(store))
  })

  settings.onRenderUserSettings((children) => {
    children.push(renderSetting(ui.store))
  })

  function renderSetting(store) {
    var state = store.getState()
    return h('div', [
    , h('h4', 'Chat')
    , h('ul.list-group',
        h('li.list-group-item', [
          h('label', [
            h('input', {
              type: 'checkbox'
            , 'ev-change': evt => {
                store.dispatch(settings.action_setForUser({
                  'chat:windowSize': evt.currentTarget.checked? 'minimized' : 'full'
                }))
              }
            , attributes: settings.getForUser('chat:windowSize') == 'minimized'? {checked: true} : {}
            })
          , ' Keep chat minimized'
          ])
        ])
      )
    ])
  }

  function render(store) {
    var state = store.getState().chat
    // set window size
    var windowSize = state.windowSize == 'minimized'?
      '.Chat--minimized' : ''
    return h('div.Chat'+windowSize, [
      renderHeader(store)
    , renderMessages(store)
    , renderInterface(store)
    ])
  }

  function renderHeader(store) {
    return h('div.Chat__header', [
      h('div.btn-group.Chat__header__controls', [
        h('a.btn.glyphicon.glyphicon-minus', {
          attributes: {'aria-label':'Minimize chat window'}
        , 'ev-click': evt => store.dispatch(chat.action_resize('minimized'))
        })
      , h('a.btn.glyphicon.glyphicon-pushpin', {
          attributes: {'aria-label':'Keep chat window open'}
        , 'ev-click': evt => store.dispatch(chat.action_resize('full'))
        })
      ])
    , h('h5', [
        h('i.glyphicon.glyphicon-comment')
      , ' Chat '
      , h('small', 'discuss and inspire')
      ])
    ])
  }

  function renderInterface(store) {
    return h('form.form-inline.Chat__Interface', [
      h('div.input-group',[
        h('label.sr-only', {attributes:{for: 'message'}}, 'Chat message'),
        h('input.form-control', {attributes:{
            type:'text'
          , name: 'message'
          , placeholder: 'Chat message'
          },
          'ev-keydown': evt => {
          if(evt.keyCode == 13) {
            evt.preventDefault()
            store.dispatch(chat.action_createMessage(evt.currentTarget.value))
            evt.currentTarget.value = ''
          }
        }})
      ]),
      h('input.btn.btn-default.btn-block.hidden-md.hidden-lg', {
        attributes:{type:'submit', value: 'send'}
      , 'ev-click': evt => {
          evt.preventDefault()
          store.dispatch(chat.action_createMessage(evt.currentTarget.previousSibling.value))
          evt.currentTarget.previousSibling.value = ''
        }
      })
    ])
  }

  function renderMessages(store) {
    var state = store.getState().chat
    return h('div.Chat__messages', {
      scrollTop: new ScrollHook()
    , key: 1
    },
    state.messages.map(function(msg) {
      return renderMessage(state, msg)
    })
    )
  }

  function ScrollHook() {
    this.scrollHeight
  }
  ScrollHook.prototype.hook = function(node, propName, previous) {
    if(previous && node.scrollTop < previous.scrollHeight-node.clientHeight) {
      this.scrollHeight = node.scrollHeight
      return
    }else{
      this.scrollHeight = node.scrollHeight
      setImmediate(function() {
        node.scrollTop = node.scrollHeight-node.clientHeight
      })
    }
  }

  function renderMessage(state, msg) {
    return h('div.Chat__Message', {attributes:{'data-user': msg.user}}, [
      h('span.Chat__Message__User', state.users[msg.user].name)
    , ': '
    , h('span.Chat__Message__Text', msg.text)
    ])
  }

  register(null, {chat})
}

function jsonStringify() {
  return through.obj(function(buf, enc, cb) {
    this.push(JSON.stringify(buf)+'\n')
    cb()
  })
}
<|endoftext|>


<commit_before>var zoom = 1;
var nodes = {};
var myNodes = [];
var playerx,playery;
var startx = 2000,starty = 2000;
var mousex,mousey;
var canvas;
var socket = null;

window.onload = function () {
  console.log('init');
  canvas = document.getElementById("canvas");
  canvas.onmousemove = function(e) {
    mousex = e.clientX;
    mousey = e.clientY;
  };
  setInterval(tick, 100);
}

var Node = function(x, y, radius, name, virus) {
  this.x = x;
  this.y = y;
  this.radius = radius;
  if (name) {
    this.name = name;
  }
  if (virus) {
    this.rgb = [0,0,0];
  } else {
    this.rgb = [255,0,0];
  }
  this.virus = virus;
}
Node.prototype.update = function (x, y, radius) {
  this.x = x;
  this.y = y;
  this.radius = radius;
}

function connect() {
  nodes = {};
  myNodes = [];
  url = document.getElementById('url').value;
  socket = new WebSocket(url);
  socket.binaryType = 'arraybuffer';

  window.onresize = onWindowResize;
  onWindowResize ();

  socket.onopen = function (event) {
    sendPacket(socket, 254);
    sendPacket(socket, 255);
    sendPacket(socket, 0, document.getElementById('nickname').value);
    sendPacket(socket, 16, [10,10]);
    //~ drawCircle(125,125,100);
  }

  socket.onmessage = function (event) {
    var view = new DataView(event.data);
    var packetId = view.getUint8(0, true);
    console.log('received packetId', packetId);

    switch (packetId) {
      case 16: // Update nodes
          var nbNodesToDestroy = view.getUint16(1, true);
          console.log('nbNodesToDestroy', nbNodesToDestroy);
          var i = 3;
          for (var j = 0; j < nbNodesToDestroy; ++j) {
            var killerId = view.getUint32(i, true);
            i+=4;
            var killedId = view.getUint32(i, true);
            i+=4;
            var n;
            if ((n = myNodes.indexOf(killedId)) > -1) {
              myNodes.splice(n, 1);
            }
            delete nodes[killedId];
          }
          var nodeId = view.getUint32(i, true);
          i+=4;
          while (nodeId != 0) {
            var x = view.getUint16(i, true);
            i+=2;
            var y = view.getUint16(i, true);
            i+=2;
            var radius = view.getUint16(i, true);
            i+=2;
            var r = view.getUint8(i++, true);
            var g = view.getUint8(i++, true);
            var b = view.getUint8(i++, true);
            var flags = view.getUint8(i++, true);
            var virus = !!(flags & 1);
            flags & 2 && (i += 4);
            flags & 4 && (i += 8);
            flags & 8 && (i += 16);
            var nodeName;
            for (nodeName = ""; ; ) {
                var c = view.getUint16(i, true);
                i+=2;
                if (c == 0)
                    break;
                nodeName += String.fromCharCode(c)
            }
            if (nodes[nodeId] == null) {
              nodes[nodeId] = new Node(x,y,radius,nodeName,virus);
            } else {
              nodes[nodeId].update(x,y,radius);
            }
            var nodeId = view.getUint32(i, true);
            i+=4;
          }
          i+=2;
          nbNodesToDestroy = view.getUint16(i, true);
          console.log('nbNodesToDestroy', nbNodesToDestroy);
          i+=2;
          for (var j = 0; j < nbNodesToDestroy; ++j) {
            var killedId = view.getUint32(i, true);
            i+=4;
            delete nodes[killedId];
          }
          break;
      case 17: // Update position and size
      case 20: // Clear all nodes
      case 21: // Draw line
          alert(packetId);
          socket.close();
          break;
      case 32: // Add node
          var nodeId = view.getUint32(1, true);
          myNodes.push(nodeId);
          console.log('myNodes', myNodes);
          break;
      case 49: // Update leaderboard FFA
          var i = 1;
          var nbScores = view.getUint32(i, true);
          i+=4;
          var board = "
";
          for (var j=0; j<nbScores; ++j) {
            var nodeId = view.getUint32(i, true);
            i+=4;
            var nodeName;
            for (nodeName = ""; ; ) {
                var c = view.getUint16(i, true);
                i+=2;
                if (c == 0)
                    break;
                nodeName += String.fromCharCode(c)
            }
            console.log('Leaderboard', j, nodeName);
            board += "
"+nodeName+"
";
          }
          board += "
";
          document.getElementById('leaderboard').innerHTML = board;
          break;
      case 50: // Update leaderboard team
          break;
      case 64: // Set border
          var left = view.getFloat64(1, true);
          var top = view.getFloat64(9, true);
          var right = view.getFloat64(17, true);
          var bottom = view.getFloat64(25, true);
          console.log('setBorder', left,top,right,bottom);
          break;
      default:
          console.log('received unknown packetId', packetId);
          break;
    }
  };
}

function sendPacket(socket, packetId, data)
{
  var size;
  switch (packetId) {
    case 0:
    size = 1 + 2*data.length;
    break;
    case 16:
    size = 21;
    break;
    case 254:
    case 255:
    size = 5;
    break;
  }
  var buf = new ArrayBuffer(size);
  var view = new DataView(buf);

  view.setUint8(0, packetId, true);
  switch (packetId) {
    case 0:
      console.log('sending', data);
      var i = 1
      for (var j = 0; j < data.length; j++) {
        var c = data.charCodeAt(j);
        if (c) {
          view.setUint16(i, c, true);
        }
        i += 2;
      }
    break;
    case 16:
      console.log('sending', data);
      view.setFloat64(1, data[0], true);
      view.setFloat64(9, data[1], true);
      view.setUint32(17, 0, true);
    case 254:
      view.setUint32(1, 4, true);
    break;
    case 255:
      view.setUint32(1, 673720361, true);
    break;
  }
  socket.send(view);
}

function tick() {
  if (socket == null) {
    return;
  }
  drawScreen();
  sendPacket(socket, 16, [startx+mousex,starty+mousey]);
}

function drawScreen() {
  var ctx = canvas.getContext("2d");
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  if (myNodes.length > 0) {
    startx = nodes[myNodes[0]].x - canvas.width/2;
    starty = nodes[myNodes[0]].y - canvas.height/2;
  }
  for (var i in nodes) {
    drawCircle(nodes[i].x-startx, nodes[i].y-starty, nodes[i].radius, nodes[i].name);
  }
}

function drawCircle(x, y, radius, text) {
  // Full circle
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    ctx.arc(x*zoom, y*zoom, radius*zoom, 0, Math.PI*2, false);
    ctx.closePath();
    ctx.stroke();

    if (text) {
      ctx.font = "30px Serif";
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      ctx.fillText(text, x, y);
    }
  }
}

function onWindowResize ()
{
  canvas.width = window.innerWidth-20;
  title = document.getElementById("title");
  if(title.offsetHeight)          {titleHeight=title.offsetHeight;}
  else if(title.style.pixelHeight){titleHeight=title.style.pixelHeight;}
  canvas.height = window.innerHeight-titleHeight-50;
}
<commit_msg>Added split and w support<commit_after>var zoom = 1;
var nodes = {};
var myNodes = [];
var playerx,playery;
var startx = 2000,starty = 2000;
var mousex,mousey;
var canvas;
var socket = null;
var splitKey = 32;
var qKey = 81;
var wKey = 87;
var splitKeyPressed = false;
var qKeyPressed = false;
var wKeyPressed = false;

window.onload = function () {
  console.log('init');
  canvas = document.getElementById("canvas");
  canvas.onmousemove = function(e) {
    mousex = e.clientX;
    mousey = e.clientY;
  };
  window.onkeydown = onKeyDown;
  window.onkeyup = onKeyUp;
  setInterval(tick, 100);
}

var Node = function(x, y, radius, name, virus) {
  this.x = x;
  this.y = y;
  this.radius = radius;
  if (name) {
    this.name = name;
  }
  if (virus) {
    this.rgb = [0,0,0];
  } else {
    this.rgb = [255,0,0];
  }
  this.virus = virus;
}
Node.prototype.update = function (x, y, radius) {
  this.x = x;
  this.y = y;
  this.radius = radius;
}

function connect() {
  nodes = {};
  myNodes = [];
  url = document.getElementById('url').value;
  socket = new WebSocket(url);
  socket.binaryType = 'arraybuffer';

  window.onresize = onWindowResize;
  onWindowResize ();

  socket.onopen = function (event) {
    sendPacket(socket, 254);
    sendPacket(socket, 255);
    sendPacket(socket, 0, document.getElementById('nickname').value);
    sendPacket(socket, 16, [10,10]);
    //~ drawCircle(125,125,100);
  }

  socket.onmessage = function (event) {
    var view = new DataView(event.data);
    var packetId = view.getUint8(0, true);
    console.log('received packetId', packetId);

    switch (packetId) {
      case 16: // Update nodes
          var nbNodesToDestroy = view.getUint16(1, true);
          console.log('nbNodesToDestroy', nbNodesToDestroy);
          var i = 3;
          for (var j = 0; j < nbNodesToDestroy; ++j) {
            var killerId = view.getUint32(i, true);
            i+=4;
            var killedId = view.getUint32(i, true);
            i+=4;
            var n;
            if ((n = myNodes.indexOf(killedId)) > -1) {
              myNodes.splice(n, 1);
            }
            delete nodes[killedId];
          }
          var nodeId = view.getUint32(i, true);
          i+=4;
          while (nodeId != 0) {
            var x = view.getUint16(i, true);
            i+=2;
            var y = view.getUint16(i, true);
            i+=2;
            var radius = view.getUint16(i, true);
            i+=2;
            var r = view.getUint8(i++, true);
            var g = view.getUint8(i++, true);
            var b = view.getUint8(i++, true);
            var flags = view.getUint8(i++, true);
            var virus = !!(flags & 1);
            flags & 2 && (i += 4);
            flags & 4 && (i += 8);
            flags & 8 && (i += 16);
            var nodeName;
            for (nodeName = ""; ; ) {
                var c = view.getUint16(i, true);
                i+=2;
                if (c == 0)
                    break;
                nodeName += String.fromCharCode(c)
            }
            if (nodes[nodeId] == null) {
              nodes[nodeId] = new Node(x,y,radius,nodeName,virus);
            } else {
              nodes[nodeId].update(x,y,radius);
            }
            var nodeId = view.getUint32(i, true);
            i+=4;
          }
          i+=2;
          nbNodesToDestroy = view.getUint16(i, true);
          console.log('nbNodesToDestroy', nbNodesToDestroy);
          i+=2;
          for (var j = 0; j < nbNodesToDestroy; ++j) {
            var killedId = view.getUint32(i, true);
            i+=4;
            delete nodes[killedId];
          }
          break;
      case 17: // Update position and size
      case 20: // Clear all nodes
      case 21: // Draw line
          alert(packetId);
          socket.close();
          break;
      case 32: // Add node
          var nodeId = view.getUint32(1, true);
          myNodes.push(nodeId);
          console.log('myNodes', myNodes);
          break;
      case 49: // Update leaderboard FFA
          var i = 1;
          var nbScores = view.getUint32(i, true);
          i+=4;
          var board = "
";
          for (var j=0; j<nbScores; ++j) {
            var nodeId = view.getUint32(i, true);
            i+=4;
            var nodeName;
            for (nodeName = ""; ; ) {
                var c = view.getUint16(i, true);
                i+=2;
                if (c == 0)
                    break;
                nodeName += String.fromCharCode(c)
            }
            console.log('Leaderboard', j, nodeName);
            board += "
"+nodeName+"
";
          }
          board += "
";
          document.getElementById('leaderboard').innerHTML = board;
          break;
      case 50: // Update leaderboard team
          break;
      case 64: // Set border
          var left = view.getFloat64(1, true);
          var top = view.getFloat64(9, true);
          var right = view.getFloat64(17, true);
          var bottom = view.getFloat64(25, true);
          console.log('setBorder', left,top,right,bottom);
          break;
      default:
          console.log('received unknown packetId', packetId);
          break;
    }
  };
}

function sendPacket(socket, packetId, data)
{
  var size;
  switch (packetId) {
    case 0:
    size = 1 + 2*data.length;
    break;
    case 16:
    size = 21;
    break;
    case 17:
    case 18:
    case 19:
    case 21:
    size = 1;
    break;
    case 254:
    case 255:
    size = 5;
    break;
  }
  var buf = new ArrayBuffer(size);
  var view = new DataView(buf);

  view.setUint8(0, packetId, true);
  switch (packetId) {
    case 0:
      console.log('sending', data);
      var i = 1
      for (var j = 0; j < data.length; j++) {
        var c = data.charCodeAt(j);
        if (c) {
          view.setUint16(i, c, true);
        }
        i += 2;
      }
    break;
    case 16:
      console.log('sending', data);
      view.setFloat64(1, data[0], true);
      view.setFloat64(9, data[1], true);
      view.setUint32(17, 0, true);
    case 17:
    case 18:
    case 19:
    case 21:
    break;
    case 254:
      view.setUint32(1, 4, true);
    break;
    case 255:
      view.setUint32(1, 673720361, true);
    break;
  }
  socket.send(view);
}

function tick() {
  if (socket == null) {
    return;
  }
  drawScreen();
  sendPacket(socket, 16, [startx+mousex,starty+mousey]);
}

function drawScreen() {
  var ctx = canvas.getContext("2d");
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  if (myNodes.length > 0) {
    startx = nodes[myNodes[0]].x - canvas.width/2;
    starty = nodes[myNodes[0]].y - canvas.height/2;
  }
  for (var i in nodes) {
    drawCircle(nodes[i].x-startx, nodes[i].y-starty, nodes[i].radius, nodes[i].name);
  }
}

function drawCircle(x, y, radius, text) {
  // Full circle
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    ctx.arc(x*zoom, y*zoom, radius*zoom, 0, Math.PI*2, false);
    ctx.closePath();
    ctx.stroke();

    if (text) {
      ctx.font = "30px Serif";
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      ctx.fillText(text, x, y);
    }
  }
}

function onKeyDown (event)
{
  console.log("key", event.keyCode);
  if ((event.keyCode == splitKey) && !splitKeyPressed) {
    splitKeyPressed = true;
    sendPacket(socket, 17);
  } else if ((event.keyCode == wKey) && !wKeyPressed) {
    wKeyPressed = true;
    sendPacket(socket, 21);
  } else if ((event.keyCode == qKey) && !qKeyPressed) {
    qKeyPressed = true;
    sendPacket(socket, 18);
  }
}

function onKeyUp (event)
{
  if ((event.keyCode == splitKey) && splitKeyPressed) {
    splitKeyPressed = false;
  } else if ((event.keyCode == wKey) && wKeyPressed) {
    wKeyPressed = false;
  } else if ((event.keyCode == qKey) && qKeyPressed) {
    qKeyPressed = false;
    sendPacket(socket, 19);
  }

}

function onWindowResize ()
{
  canvas.width = window.innerWidth-20;
  title = document.getElementById("title");
  if(title.offsetHeight)          {titleHeight=title.offsetHeight;}
  else if(title.style.pixelHeight){titleHeight=title.style.pixelHeight;}
  canvas.height = window.innerHeight-titleHeight-50;
}
<|endoftext|>