<commit_before>import uPlot from '../lib/uPlot.esm'

import RelativeScale from './scale'

import { formatNumber, formatTimestamp, formatDate, formatMinecraftServerAddress, formatMinecraftVersions } from './util'

import MISSING_FAVICON from '../images/missing_favicon.svg'

export class ServerRegistry {
  constructor (app) {
    this._app = app
    this._serverIdsByName = []
    this._serverDataById = []
    this._registeredServers = []
  }

  assignServers (servers) {
    for (let i = 0; i < servers.length; i++) {
      const data = servers[i]
      this._serverIdsByName[data.name] = i
      this._serverDataById[i] = data
    }
  }

  createServerRegistration (serverId) {
    const serverData = this._serverDataById[serverId]
    const serverRegistration = new ServerRegistration(this._app, serverId, serverData)
    this._registeredServers[serverId] = serverRegistration
    return serverRegistration
  }

  getServerRegistration (serverKey) {
    if (typeof serverKey === 'string') {
      const serverId = this._serverIdsByName[serverKey]
      return this._registeredServers[serverId]
    } else if (typeof serverKey === 'number') {
      return this._registeredServers[serverKey]
    }
  }

  getServerRegistrations = () => Object.values(this._registeredServers)

  reset () {
    this._serverIdsByName = []
    this._serverDataById = []
    this._registeredServers = []

    // Reset modified DOM structures
    document.getElementById('server-list').innerHTML = ''
  }
}

export class ServerRegistration {
  playerCount = 0
  isVisible = true
  isFavorite = false
  rankIndex
  lastRecordData
  lastPeakData

  constructor (app, serverId, data) {
    this._app = app
    this.serverId = serverId
    this.data = data
    this._graphData = []
    this._failedSequentialPings = 0
  }

  addGraphPoints (points, timestampPoints) {
    this._graphData = [
      timestampPoints.map(val => Math.floor(val / 1000)),
      points
    ]
  }

  buildPlotInstance () {
    const tickCount = 5

    // eslint-disable-next-line new-cap
    this._plotInstance = new uPlot({
      height: 100,
      width: 400,
      cursor: {
        y: false,
        drag: {
          setScale: false,
          x: false,
          y: false
        }
      },
      series: [
        {},
        {
          scale: 'Players',
          stroke: '#E9E581',
          width: 2,
          value: (_, raw) => formatNumber(raw) + ' Players'
        }
      ],
      axes: [
        {
          show: false
        },
        {
          ticks: {
            show: false
          },
          font: '14px "Open Sans", sans-serif',
          stroke: '#A3A3A3',
          size: 55,
          grid: {
            stroke: '#333',
            width: 1
          },
          split: (self) => {
            const [min, max, scale] = RelativeScale.scale(self.data[1], tickCount)
            const ticks = RelativeScale.generateTicks(min, max, scale)
            return ticks
          }
        }
      ],
      scales: {
        Players: {
          auto: false,
          range: (self) => {
            const [scaledMin, scaledMax] = RelativeScale.scale(self.data[1], tickCount)
            return [scaledMin, scaledMax]
          }
        }
      },
      legend: {
        show: false
      }
    }, this._graphData, document.getElementById('chart_' + this.serverId))
  }

  handlePing (payload, timestamp) {
    if (typeof payload.playerCount !== 'undefined') {
      this.playerCount = payload.playerCount

      // Only update graph for successful pings
      // This intentionally pauses the server graph when pings begin to fail
      this._graphData[0].push(Math.floor(timestamp / 1000))
      this._graphData[1].push(this.playerCount)

      // Trim graphData to within the max length by shifting out the leading elements
      for (const series of this._graphData) {
        if (series.length > this._app.publicConfig.serverGraphMaxLength) {
          series.shift()
        }
      }

      this.redraw()

      // Reset failed ping counter to ensure the next connection error
      // doesn't instantly retrigger a layout change
      this._failedSequentialPings = 0
    } else {
      // Attempt to retain a copy of the cached playerCount for up to N failed pings
      // This prevents minor connection issues from constantly reshuffling the layout
      if (++this._failedSequentialPings > 5) {
        this.playerCount = 0
      }
    }
  }

  redraw () {
    // Redraw the plot instance
    this._plotInstance.setData(this._graphData)
  }

  updateServerRankIndex (rankIndex) {
    this.rankIndex = rankIndex

    document.getElementById('ranking_' + this.serverId).innerText = '#' + (rankIndex + 1)
  }

  updateServerPeak (data) {
    const peakLabelElement = document.getElementById('peak_' + this.serverId)

    // Always set label once any peak data has been received
    peakLabelElement.style.display = 'block'

    const peakValueElement = document.getElementById('peak-value_' + this.serverId)

    peakValueElement.innerText = formatNumber(data.playerCount)
    peakLabelElement.title = 'At ' + formatTimestamp(data.timestamp)

    this.lastPeakData = data
  }

  updateServerStatus (ping, minecraftVersions) {
    if (ping.versions) {
      const versionsElement = document.getElementById('version_' + this.serverId)

      versionsElement.style.display = 'block'
      versionsElement.innerText = formatMinecraftVersions(ping.versions, minecraftVersions[this.data.type]) || ''
    }

    if (ping.recordData) {
      // Always set label once any record data has been received
      const recordLabelElement = document.getElementById('record_' + this.serverId)

      recordLabelElement.style.display = 'block'

      const recordValueElement = document.getElementById('record-value_' + this.serverId)

      const recordData = ping.recordData

      // Safely handle legacy recordData that may not include the timestamp payload
      if (recordData.timestamp > 0) {
        recordValueElement.innerHTML = formatNumber(recordData.playerCount) + ' (' + formatDate(recordData.timestamp) + ')'
        recordLabelElement.title = 'At ' + formatDate(recordData.timestamp) + ' ' + formatTimestamp(recordData.timestamp)
      } else {
        recordValueElement.innerText = formatNumber(recordData.playerCount)
      }

      this.lastRecordData = recordData
    }

    if (ping.graphPeakData) {
      this.updateServerPeak(ping.graphPeakData)
    }

    const playerCountLabelElement = document.getElementById('player-count_' + this.serverId)
    const errorElement = document.getElementById('error_' + this.serverId)

    if (ping.error) {
      // Hide any visible player-count and show the error element
      playerCountLabelElement.style.display = 'none'
      errorElement.style.display = 'block'

      errorElement.innerText = ping.error.message
    } else if (typeof ping.playerCount !== 'undefined') {
      // Ensure the player-count element is visible and hide the error element
      playerCountLabelElement.style.display = 'block'
      errorElement.style.display = 'none'

      document.getElementById('player-count-value_' + this.serverId).innerText = formatNumber(ping.playerCount)
    }

    // An updated favicon has been sent, update the src
    if (ping.favicon) {
      const faviconElement = document.getElementById('favicon_' + this.serverId)

      // Since favicons may be URLs, only update the attribute when it has changed
      // Otherwise the browser may send multiple requests to the same URL
      if (faviconElement.getAttribute('src') !== ping.favicon) {
        faviconElement.setAttribute('src', ping.favicon)
      }
    }
  }

  initServerStatus (latestPing) {
    const serverElement = document.createElement('div')

    serverElement.id = 'container_' + this.serverId
    serverElement.innerHTML = '
' +
        '' +
        '' +
      '
' +
      '
' +
        '
 ' + this.data.name + '
' +
        '' +
        'Players: ' +
        '' + this._app.publicConfig.graphDurationLabel + ' Peak: -' +
        'Record: -' +
        '' +
      '
' +
      '

'

    serverElement.setAttribute('class', 'server')

    document.getElementById('server-list').appendChild(serverElement)
  }

  updateHighlightedValue (selectedCategory) {
    ['player-count', 'peak', 'record'].forEach((category) => {
      const labelElement = document.getElementById(category + '_' + this.serverId)
      const valueElement = document.getElementById(category + '-value_' + this.serverId)

      if (selectedCategory && category === selectedCategory) {
        labelElement.setAttribute('class', 'server-highlighted-label')
        valueElement.setAttribute('class', 'server-highlighted-value')
      } else {
        labelElement.setAttribute('class', 'server-label')
        valueElement.setAttribute('class', 'server-value')
      }
    })
  }

  initEventListeners () {
    $('#chart_' + this.serverId).bind('plothover', this._app.graphDisplayManager.handlePlotHover)

    document.getElementById('favorite-toggle_' + this.serverId).addEventListener('click', () => {
      this._app.favoritesManager.handleFavoriteButtonClick(this)
    }, false)
  }
}
<commit_msg>reference graphData instead of obscure self.data<commit_after>import uPlot from '../lib/uPlot.esm'

import RelativeScale from './scale'

import { formatNumber, formatTimestamp, formatDate, formatMinecraftServerAddress, formatMinecraftVersions } from './util'

import MISSING_FAVICON from '../images/missing_favicon.svg'

export class ServerRegistry {
  constructor (app) {
    this._app = app
    this._serverIdsByName = []
    this._serverDataById = []
    this._registeredServers = []
  }

  assignServers (servers) {
    for (let i = 0; i < servers.length; i++) {
      const data = servers[i]
      this._serverIdsByName[data.name] = i
      this._serverDataById[i] = data
    }
  }

  createServerRegistration (serverId) {
    const serverData = this._serverDataById[serverId]
    const serverRegistration = new ServerRegistration(this._app, serverId, serverData)
    this._registeredServers[serverId] = serverRegistration
    return serverRegistration
  }

  getServerRegistration (serverKey) {
    if (typeof serverKey === 'string') {
      const serverId = this._serverIdsByName[serverKey]
      return this._registeredServers[serverId]
    } else if (typeof serverKey === 'number') {
      return this._registeredServers[serverKey]
    }
  }

  getServerRegistrations = () => Object.values(this._registeredServers)

  reset () {
    this._serverIdsByName = []
    this._serverDataById = []
    this._registeredServers = []

    // Reset modified DOM structures
    document.getElementById('server-list').innerHTML = ''
  }
}

export class ServerRegistration {
  playerCount = 0
  isVisible = true
  isFavorite = false
  rankIndex
  lastRecordData
  lastPeakData

  constructor (app, serverId, data) {
    this._app = app
    this.serverId = serverId
    this.data = data
    this._graphData = []
    this._failedSequentialPings = 0
  }

  addGraphPoints (points, timestampPoints) {
    this._graphData = [
      timestampPoints.map(val => Math.floor(val / 1000)),
      points
    ]
  }

  buildPlotInstance () {
    const tickCount = 5

    // eslint-disable-next-line new-cap
    this._plotInstance = new uPlot({
      height: 100,
      width: 400,
      cursor: {
        y: false,
        drag: {
          setScale: false,
          x: false,
          y: false
        }
      },
      series: [
        {},
        {
          scale: 'Players',
          stroke: '#E9E581',
          width: 2,
          value: (_, raw) => formatNumber(raw) + ' Players'
        }
      ],
      axes: [
        {
          show: false
        },
        {
          ticks: {
            show: false
          },
          font: '14px "Open Sans", sans-serif',
          stroke: '#A3A3A3',
          size: 55,
          grid: {
            stroke: '#333',
            width: 1
          },
          split: () => {
            const [min, max, scale] = RelativeScale.scale(this._graphData[1], tickCount)
            const ticks = RelativeScale.generateTicks(min, max, scale)
            return ticks
          }
        }
      ],
      scales: {
        Players: {
          auto: false,
          range: () => {
            const [scaledMin, scaledMax] = RelativeScale.scale(this._graphData[1], tickCount)
            return [scaledMin, scaledMax]
          }
        }
      },
      legend: {
        show: false
      }
    }, this._graphData, document.getElementById('chart_' + this.serverId))
  }

  handlePing (payload, timestamp) {
    if (typeof payload.playerCount !== 'undefined') {
      this.playerCount = payload.playerCount

      // Only update graph for successful pings
      // This intentionally pauses the server graph when pings begin to fail
      this._graphData[0].push(Math.floor(timestamp / 1000))
      this._graphData[1].push(this.playerCount)

      // Trim graphData to within the max length by shifting out the leading elements
      for (const series of this._graphData) {
        if (series.length > this._app.publicConfig.serverGraphMaxLength) {
          series.shift()
        }
      }

      this.redraw()

      // Reset failed ping counter to ensure the next connection error
      // doesn't instantly retrigger a layout change
      this._failedSequentialPings = 0
    } else {
      // Attempt to retain a copy of the cached playerCount for up to N failed pings
      // This prevents minor connection issues from constantly reshuffling the layout
      if (++this._failedSequentialPings > 5) {
        this.playerCount = 0
      }
    }
  }

  redraw () {
    // Redraw the plot instance
    this._plotInstance.setData(this._graphData)
  }

  updateServerRankIndex (rankIndex) {
    this.rankIndex = rankIndex

    document.getElementById('ranking_' + this.serverId).innerText = '#' + (rankIndex + 1)
  }

  updateServerPeak (data) {
    const peakLabelElement = document.getElementById('peak_' + this.serverId)

    // Always set label once any peak data has been received
    peakLabelElement.style.display = 'block'

    const peakValueElement = document.getElementById('peak-value_' + this.serverId)

    peakValueElement.innerText = formatNumber(data.playerCount)
    peakLabelElement.title = 'At ' + formatTimestamp(data.timestamp)

    this.lastPeakData = data
  }

  updateServerStatus (ping, minecraftVersions) {
    if (ping.versions) {
      const versionsElement = document.getElementById('version_' + this.serverId)

      versionsElement.style.display = 'block'
      versionsElement.innerText = formatMinecraftVersions(ping.versions, minecraftVersions[this.data.type]) || ''
    }

    if (ping.recordData) {
      // Always set label once any record data has been received
      const recordLabelElement = document.getElementById('record_' + this.serverId)

      recordLabelElement.style.display = 'block'

      const recordValueElement = document.getElementById('record-value_' + this.serverId)

      const recordData = ping.recordData

      // Safely handle legacy recordData that may not include the timestamp payload
      if (recordData.timestamp > 0) {
        recordValueElement.innerHTML = formatNumber(recordData.playerCount) + ' (' + formatDate(recordData.timestamp) + ')'
        recordLabelElement.title = 'At ' + formatDate(recordData.timestamp) + ' ' + formatTimestamp(recordData.timestamp)
      } else {
        recordValueElement.innerText = formatNumber(recordData.playerCount)
      }

      this.lastRecordData = recordData
    }

    if (ping.graphPeakData) {
      this.updateServerPeak(ping.graphPeakData)
    }

    const playerCountLabelElement = document.getElementById('player-count_' + this.serverId)
    const errorElement = document.getElementById('error_' + this.serverId)

    if (ping.error) {
      // Hide any visible player-count and show the error element
      playerCountLabelElement.style.display = 'none'
      errorElement.style.display = 'block'

      errorElement.innerText = ping.error.message
    } else if (typeof ping.playerCount !== 'undefined') {
      // Ensure the player-count element is visible and hide the error element
      playerCountLabelElement.style.display = 'block'
      errorElement.style.display = 'none'

      document.getElementById('player-count-value_' + this.serverId).innerText = formatNumber(ping.playerCount)
    }

    // An updated favicon has been sent, update the src
    if (ping.favicon) {
      const faviconElement = document.getElementById('favicon_' + this.serverId)

      // Since favicons may be URLs, only update the attribute when it has changed
      // Otherwise the browser may send multiple requests to the same URL
      if (faviconElement.getAttribute('src') !== ping.favicon) {
        faviconElement.setAttribute('src', ping.favicon)
      }
    }
  }

  initServerStatus (latestPing) {
    const serverElement = document.createElement('div')

    serverElement.id = 'container_' + this.serverId
    serverElement.innerHTML = '
' +
        '' +
        '' +
      '
' +
      '
' +
        '
 ' + this.data.name + '
' +
        '' +
        'Players: ' +
        '' + this._app.publicConfig.graphDurationLabel + ' Peak: -' +
        'Record: -' +
        '' +
      '
' +
      '

'

    serverElement.setAttribute('class', 'server')

    document.getElementById('server-list').appendChild(serverElement)
  }

  updateHighlightedValue (selectedCategory) {
    ['player-count', 'peak', 'record'].forEach((category) => {
      const labelElement = document.getElementById(category + '_' + this.serverId)
      const valueElement = document.getElementById(category + '-value_' + this.serverId)

      if (selectedCategory && category === selectedCategory) {
        labelElement.setAttribute('class', 'server-highlighted-label')
        valueElement.setAttribute('class', 'server-highlighted-value')
      } else {
        labelElement.setAttribute('class', 'server-label')
        valueElement.setAttribute('class', 'server-value')
      }
    })
  }

  initEventListeners () {
    $('#chart_' + this.serverId).bind('plothover', this._app.graphDisplayManager.handlePlotHover)

    document.getElementById('favorite-toggle_' + this.serverId).addEventListener('click', () => {
      this._app.favoritesManager.handleFavoriteButtonClick(this)
    }, false)
  }
}
<|endoftext|>


<commit_before>var ws;
// var heartbeat_msg = '--heartbeat--', heartbeat_interval = null, missed_heartbeats = 0;

var exp_num = 0;

function WebSocketTest(server) {
    var ws;
    if ("WebSocket" in window) {
        ws = new WebSocket("ws://" + server + "/ws/");
        ws.onopen = function() {
            // Setup a heartbeat
            // if (heartbeat_interval === null) {
            //     missed_heartbeats = 0;
            //     heartbeat_interval = setInterval(function() {
            //         try {
            //             missed_heartbeats++;
            //             if (missed_heartbeats >= 3)
            //                 throw new Error("Too many missed heartbeats.");
            //             ws.send(heartbeat_msg);
            //         } catch(e) {
            //             clearInterval(heartbeat_interval);
            //             heartbeat_interval = null;
            //             console.warn("Closing connection. Reason: " + e.message);
            //             ws.close();
            //         }
            //     }, 5000);
            // }
            toggle_status('on');
            console.log("Connection established");
        };
        ws.onmessage = function (evt) {
            var channel = evt.data.split(' ', 1)[0];
            var received_msg = evt.data.substring(evt.data.indexOf(' ') + 1)

            var msg = jQuery.parseJSON(received_msg);

            // // Do heartbeat check first
            // if (msg['message'] === heartbeat_msg) {
            //     // reset the counter for missed heartbeats
            //     missed_heartbeats = 0;
            //     return;
            // }

            switch(channel.toUpperCase()){
                case 'STATE':
                    change_state(msg['state']);
                    break;
                case 'OBSERVER':
                    update_info(msg['observer']);
                    break;
                case 'FIELD':
                    update_info(msg['field']);
                    break;
                case 'STATUS':
                    change_state(msg['state']);

                    var observer = msg['observatory']['observer'];
                    var observation = msg['observatory']['observation'];

                    observer['local_evening_astro_time'] = trim_time(observer['local_evening_astro_time']);
                    observer['local_morning_astro_time'] = trim_time(observer['local_morning_astro_time']);

                    observer['local_moon_illumination'] = pretty_number(observer['local_moon_illumination'] * 100);
                    observer['local_moon_alt'] = pretty_number(observer['local_moon_alt']);

                    // Update parts of the page

                    update_info(observation);
                    if (observation['current_exp'] != exp_num){
                        $('#observation_info .timer').timer('reset');
                        exp_num = observation['current_exp'];
                    }

                    update_info(observer);
                    $('#system_panel .timer').timer('reset');

                    update_info(msg['observatory']['mount']);
                    $('#mount_panel .timer').timer('reset');

                    refresh_images();
                    break;
                case 'ENVIRONMENT':
                    update_environment(msg['data']);
                    break;
                case 'WEATHER':
                    update_info(msg['data']);
                    update_safety(msg['data']['safe']);

                    $('#weather_panel .timer').timer('reset');
                    break;
                case 'CAMERA':
                    update_cameras(msg);
                    break;
                case 'PANBOT':
                case 'PANCHAT':
                case 'PEAS_SHELL':
                case 'POCS_SHELL':
                    add_chat_item(channel, msg.message, msg.timestamp);
                    break;
                default:
                    break;
            }

        };
        ws.onclose = function() {
            toggle_status('off');
        };
    } else {
        toggle_status('error');
    }

    return ws;
}

function update_safety(is_safe){
    if(is_safe){
        $('.safe_condition').html('Safe').removeClass('alert').addClass('success');
        toggle_status('on');
    } else {
        $('.safe_condition').html('Unsafe').removeClass('success').addClass('alert');
        toggle_status('unsafe');
    }
}

function toggle_status(status){
    var safety_cls, border_cls;

    // Update depending on status
    if (status == 'on'){
        safety_cls = 'success';
        border_cls = 'safe_borders';
    } else if (status == 'off'){
        safety_cls = 'warning';
        border_cls = 'warning_borders';
    } else {
        safety_cls = 'danger';
        border_cls = 'unsafe_borders';
    }

    $('.title-bar').removeClass('warning danger success').addClass(safety_cls);
    $('.callout').removeClass('safe_borders warning_borders unsafe_borders').addClass(border_cls);
}

/******************** Update Info Methods ****************************/

function change_state(state){
    var icon = $('.current_state i');
    var text = $('.current_state span');

    icon.removeClass().addClass('fa').addClass('success');
    text.removeClass('warning danger').html(state.toUpperCase());
    switch(state) {
        case 'analyzing':
            icon.addClass('fa-calculator');
            break;
        case 'tracking':
            icon.addClass('fa-binoculars');
            break;
        case 'observing':
            icon.addClass('fa-camera');
            break;
        case 'pointing':
            icon.addClass('fa-bullseye');
            break;
        case 'slewing':
            icon.addClass('fa-cog fa-spin');
            break;
        case 'scheduling':
            icon.addClass('fa-tasks');
            break;
        case 'ready':
            icon.addClass('fa-thumbs-o-up');
            break;
        case 'parking':
        case 'parked':
            icon.addClass('fa-car');
            break;
        case 'sleeping':
            icon.addClass('fa-check-circle');
            break;
        default:
            icon.addClass('fa-circle');
    }
    reload_img($('.state_img'));
}

// Find all the elements with the class that matches a return value
// and update their html
function update_info(status){
    $.each(status, function(key, val){
        $('.' + key).each(function(idx, elem){
            $(elem).html(val);
        })
    });
}

function update_cameras(cameras){
    $.each(cameras, function(cam_name, props){
        $.each(props, function(prop, val){
            $('#' + cam_name + ' .' + prop).each(function(idx, elem){
                $(elem).html(val);
            });
            if (prop == 'exptime'){
                // Start the progress bar
                var pb = $('#' + cam_name + ' .progress-bar');
                pb.width('100%');

                var exptime = $('#' + cam_name + ' .exptime');
                exptime.html(val);

                var total_time = val;
                var count_time = val;

                var exp_count = $('#' + cam_name + ' .exp_count');
                // exp_count.timer('remove');
                exp_count.timer({
                    duration: '2s',
                    callback: function(){
                        count_time = count_time - 2;
                        var perc = (count_time/total_time) * 100;
                        console.log(perc);

                        if (perc < 0){
                            pb.width('0%');
                            exp_count.timer('remove');
                        } else {
                            pb.width(perc + '%');
                        }

                    },
                    repeat: true,
                });

                var pb_count = function(cb, count){
                    var width_perc = (count / val) * 100;
                    pb.width(width_perc + '%');
                    setTimeout(cb, 1000, cb, --count);
                };

                setTimeout(pb_count, 1000, pb_count, val);
            }
        });

    });

}

function update_environment(info){
    try {
        var camera_info = info['camera_box'];
        $('.camera_box_humidity_00').html(pretty_number(camera_info['humidity']));
        $('.camera_box_temp_00').html(pretty_number(camera_info['temp_01']));
    } catch(err) {
        console.log(err);
    }

    try {
        var computer_info = info['computer_box'];
        $('.computer_box_humidity_00').html(pretty_number(computer_info['humidity']));
        $('.computer_box_temp_00').html(pretty_number(computer_info['temp_00']));
        $('.computer_box_temp_01').html(pretty_number(computer_info['temp_01']));
        $('.computer_box_temp_02').html(pretty_number(computer_info['temp_02']));
        $('.computer_box_temp_03').html(pretty_number(computer_info['temp_03']));
    } catch(err) {
        console.log(err);
    }
}

function add_chat_item(name, msg, time){

    item = '
';
    item = item + ' ';
    item = item + '  ' + time +' UTC';
    item = item + '
';
    item = item + '    ';
    item = item + name;
    item = item + '    ';
    item = item + msg;
    item = item + '
';
    item = item + '
';

    $('#bot_chat').prepend(item);
}

/******************** Convenience Methods ****************************/

// Refresh all images with `img_refresh` container class
function refresh_images(){
    $.each($('.img_refresh img'), function(idx, img){
        reload_img(img);
    });
}

// Reload individual image
function reload_img(img){
    base = $(img).attr('src').split('?')[0];
    // console.log("Reloading image: " + $(img).attr('src'));

    // Hack for others
    if(base.startsWith('http')){
        new_src = $(img).attr('src');
    } else {
        new_src = base + '?' + Math.random()
    }

    $(img).attr('src', new_src);

    // Update the link so not pointint at thumbnail
    $(img.parentElement).attr('href', new_src.replace('tn_', ''));
}

function trim_time(t){
    var time = t;
    if (typeof(t) == 'string'){
        time = t.split(':').slice(0,-1).join(':');
    }
    return time;
}

function pretty_number(num){
    return parseFloat(num).toFixed(2);
}
<commit_msg>Only update observation if we have one<commit_after>var ws;
// var heartbeat_msg = '--heartbeat--', heartbeat_interval = null, missed_heartbeats = 0;

var exp_num = 0;

function WebSocketTest(server) {
    var ws;
    if ("WebSocket" in window) {
        ws = new WebSocket("ws://" + server + "/ws/");
        ws.onopen = function() {
            // Setup a heartbeat
            // if (heartbeat_interval === null) {
            //     missed_heartbeats = 0;
            //     heartbeat_interval = setInterval(function() {
            //         try {
            //             missed_heartbeats++;
            //             if (missed_heartbeats >= 3)
            //                 throw new Error("Too many missed heartbeats.");
            //             ws.send(heartbeat_msg);
            //         } catch(e) {
            //             clearInterval(heartbeat_interval);
            //             heartbeat_interval = null;
            //             console.warn("Closing connection. Reason: " + e.message);
            //             ws.close();
            //         }
            //     }, 5000);
            // }
            toggle_status('on');
            console.log("Connection established");
        };
        ws.onmessage = function (evt) {
            var channel = evt.data.split(' ', 1)[0];
            var received_msg = evt.data.substring(evt.data.indexOf(' ') + 1)

            var msg = jQuery.parseJSON(received_msg);

            // // Do heartbeat check first
            // if (msg['message'] === heartbeat_msg) {
            //     // reset the counter for missed heartbeats
            //     missed_heartbeats = 0;
            //     return;
            // }

            switch(channel.toUpperCase()){
                case 'STATE':
                    change_state(msg['state']);
                    break;
                case 'OBSERVER':
                    update_info(msg['observer']);
                    break;
                case 'FIELD':
                    update_info(msg['field']);
                    break;
                case 'STATUS':
                    change_state(msg['state']);

                    var observer = msg['observatory']['observer'];
                    var observation = msg['observatory']['observation'];

                    observer['local_evening_astro_time'] = trim_time(observer['local_evening_astro_time']);
                    observer['local_morning_astro_time'] = trim_time(observer['local_morning_astro_time']);

                    observer['local_moon_illumination'] = pretty_number(observer['local_moon_illumination'] * 100);
                    observer['local_moon_alt'] = pretty_number(observer['local_moon_alt']);

                    // Update parts of the page

                    if (observation){
                        update_info(observation);
                        if (observation['current_exp'] != exp_num){
                            $('#observation_info .timer').timer('reset');
                            exp_num = observation['current_exp'];
                        }
                    }

                    update_info(observer);
                    $('#system_panel .timer').timer('reset');

                    update_info(msg['observatory']['mount']);
                    $('#mount_panel .timer').timer('reset');

                    refresh_images();
                    break;
                case 'ENVIRONMENT':
                    update_environment(msg['data']);
                    break;
                case 'WEATHER':
                    update_info(msg['data']);
                    update_safety(msg['data']['safe']);

                    $('#weather_panel .timer').timer('reset');
                    break;
                case 'CAMERA':
                    update_cameras(msg);
                    break;
                case 'PANBOT':
                case 'PANCHAT':
                case 'PEAS_SHELL':
                case 'POCS_SHELL':
                    add_chat_item(channel, msg.message, msg.timestamp);
                    break;
                default:
                    break;
            }

        };
        ws.onclose = function() {
            toggle_status('off');
        };
    } else {
        toggle_status('error');
    }

    return ws;
}

function update_safety(is_safe){
    if(is_safe){
        $('.safe_condition').html('Safe').removeClass('alert').addClass('success');
        toggle_status('on');
    } else {
        $('.safe_condition').html('Unsafe').removeClass('success').addClass('alert');
        toggle_status('unsafe');
    }
}

function toggle_status(status){
    var safety_cls, border_cls;

    // Update depending on status
    if (status == 'on'){
        safety_cls = 'success';
        border_cls = 'safe_borders';
    } else if (status == 'off'){
        safety_cls = 'warning';
        border_cls = 'warning_borders';
    } else {
        safety_cls = 'danger';
        border_cls = 'unsafe_borders';
    }

    $('.title-bar').removeClass('warning danger success').addClass(safety_cls);
    $('.callout').removeClass('safe_borders warning_borders unsafe_borders').addClass(border_cls);
}

/******************** Update Info Methods ****************************/

function change_state(state){
    var icon = $('.current_state i');
    var text = $('.current_state span');

    icon.removeClass().addClass('fa').addClass('success');
    text.removeClass('warning danger').html(state.toUpperCase());
    switch(state) {
        case 'analyzing':
            icon.addClass('fa-calculator');
            break;
        case 'tracking':
            icon.addClass('fa-binoculars');
            break;
        case 'observing':
            icon.addClass('fa-camera');
            break;
        case 'pointing':
            icon.addClass('fa-bullseye');
            break;
        case 'slewing':
            icon.addClass('fa-cog fa-spin');
            break;
        case 'scheduling':
            icon.addClass('fa-tasks');
            break;
        case 'ready':
            icon.addClass('fa-thumbs-o-up');
            break;
        case 'parking':
        case 'parked':
            icon.addClass('fa-car');
            break;
        case 'sleeping':
            icon.addClass('fa-check-circle');
            break;
        default:
            icon.addClass('fa-circle');
    }
    reload_img($('.state_img'));
}

// Find all the elements with the class that matches a return value
// and update their html
function update_info(status){
    $.each(status, function(key, val){
        $('.' + key).each(function(idx, elem){
            $(elem).html(val);
        })
    });
}

function update_cameras(cameras){
    $.each(cameras, function(cam_name, props){
        $.each(props, function(prop, val){
            $('#' + cam_name + ' .' + prop).each(function(idx, elem){
                $(elem).html(val);
            });
            if (prop == 'exptime'){
                // Start the progress bar
                var pb = $('#' + cam_name + ' .progress-bar');
                pb.width('100%');

                var exptime = $('#' + cam_name + ' .exptime');
                exptime.html(val);

                var total_time = val;
                var count_time = val;

                var exp_count = $('#' + cam_name + ' .exp_count');
                // exp_count.timer('remove');
                exp_count.timer({
                    duration: '2s',
                    callback: function(){
                        count_time = count_time - 2;
                        var perc = (count_time/total_time) * 100;
                        console.log(perc);

                        if (perc < 0){
                            pb.width('0%');
                            exp_count.timer('remove');
                        } else {
                            pb.width(perc + '%');
                        }

                    },
                    repeat: true,
                });

                var pb_count = function(cb, count){
                    var width_perc = (count / val) * 100;
                    pb.width(width_perc + '%');
                    setTimeout(cb, 1000, cb, --count);
                };

                setTimeout(pb_count, 1000, pb_count, val);
            }
        });

    });

}

function update_environment(info){
    try {
        var camera_info = info['camera_box'];
        $('.camera_box_humidity_00').html(pretty_number(camera_info['humidity']));
        $('.camera_box_temp_00').html(pretty_number(camera_info['temp_01']));
    } catch(err) {
        console.log(err);
    }

    try {
        var computer_info = info['computer_box'];
        $('.computer_box_humidity_00').html(pretty_number(computer_info['humidity']));
        $('.computer_box_temp_00').html(pretty_number(computer_info['temp_00']));
        $('.computer_box_temp_01').html(pretty_number(computer_info['temp_01']));
        $('.computer_box_temp_02').html(pretty_number(computer_info['temp_02']));
        $('.computer_box_temp_03').html(pretty_number(computer_info['temp_03']));
    } catch(err) {
        console.log(err);
    }
}

function add_chat_item(name, msg, time){

    item = '
';
    item = item + ' ';
    item = item + '  ' + time +' UTC';
    item = item + '
';
    item = item + '    ';
    item = item + name;
    item = item + '    ';
    item = item + msg;
    item = item + '
';
    item = item + '
';

    $('#bot_chat').prepend(item);
}

/******************** Convenience Methods ****************************/

// Refresh all images with `img_refresh` container class
function refresh_images(){
    $.each($('.img_refresh img'), function(idx, img){
        reload_img(img);
    });
}

// Reload individual image
function reload_img(img){
    base = $(img).attr('src').split('?')[0];
    // console.log("Reloading image: " + $(img).attr('src'));

    // Hack for others
    if(base.startsWith('http')){
        new_src = $(img).attr('src');
    } else {
        new_src = base + '?' + Math.random()
    }

    $(img).attr('src', new_src);

    // Update the link so not pointint at thumbnail
    $(img.parentElement).attr('href', new_src.replace('tn_', ''));
}

function trim_time(t){
    var time = t;
    if (typeof(t) == 'string'){
        time = t.split(':').slice(0,-1).join(':');
    }
    return time;
}

function pretty_number(num){
    return parseFloat(num).toFixed(2);
}
<|endoftext|>


<commit_before>
var auth 			= require('./auth'),
    search 			= require('../controllers/search'),
	users 			= require('../controllers/users'),
	datasets  		= require('../controllers/datasets'),
	commodities 	= require('../controllers/commodities'),
	concessions 	= require('../controllers/concessions'),
	companies 		= require('../controllers/companies'),
	projects 		= require('../controllers/projects'),
	contracts 		= require('../controllers/contracts'),
	companyGroups 	= require('../controllers/companyGroups'),
	countries 		= require('../controllers/countries'),
	sources 		= require('../controllers/sources'),
	sites 			= require('../controllers/sites');
	//etl         = require('../controllers/etl');
	// answers 	= require('../controllers/answers'),
	// questions 	= require('../controllers/questions'),
	// assessments = require('../controllers/assessments');
	// mongoose 	= require('mongoose');
	// User 		= mongoose.model('User');

module.exports	= function(app) {

	app.get('/api/search', search.searchText);

	//CONTRACTS
	app.get('/api/contracts/:limit/:skip', contracts.getContracts);
	app.get('/api/contracts/:id', contracts.getContractByID);

	/////////////////////////
	///// CONCESSIONS CRUD ////////
	/////////////////////////
	app.get('/api/concessions/:limit/:skip', concessions.getConcessions);
	app.get('/api/concessions/:id', concessions.getConcessionByID);
	// POST
	app.post('/api/concessions',  concessions.createConcession);
	// PUT
	app.put('/api/concessions',  concessions.updateConcession);
	// DELETE
	app.delete('/api/concessions/:id', concessions.deleteConcession);

	/////////////////////////
	///// PROJECTS CRUD ////////
	/////////////////////////
	app.get('/api/projects/:limit/:skip', projects.getProjects);
	app.get('/api/projects/:id', projects.getProjectByID);
	app.get('/api/projects/', projects.getProjectsMap);
	// POST
	app.post('/api/projects',  projects.createProject);
	// PUT
	app.put('/api/projects',  projects.updateProject);
	// DELETE
	app.delete('/api/projects/:id', projects.deleteProject);

	/////////////////////////
	///// COMPANIES CRUD ////
	/////////////////////////
	//app.get('/api/companies/:limit/:skip', companies.getCompanies);
	app.get('/api/companies/:limit/:skip', companies.getCompanies);
	app.get('/api/companies/:id', companies.getCompanyByID);
	// POST
	app.post('/api/companies',  companies.createCompany);
	// PUT
	app.put('/api/companies',  companies.updateCompany);
	// DELETE
	app.delete('/api/companies/:id', companies.deleteCompany);

	/////////////////////////
	///// COMPANYGROUPS CRUD ////////
	/////////////////////////
	app.get('/api/companyGroups/:limit/:skip', companyGroups.getCompanyGroups);
	app.get('/api/companyGroups/:id', companyGroups.getCompanyGroupByID);
	// POST
	app.post('/api/companyGroups',  companyGroups.createCompanyGroup);
	// PUT
	app.put('/api/companyGroups',  companyGroups.updateCompanyGroup);
	// DELETE
	app.delete('/api/companyGroups/:id', companyGroups.deleteCompanyGroup);

	/////////////////////////
	///// COMMODITIES ////////
	/////////////////////////
	app.get('/api/commodities/:limit/:skip', commodities.getCommodities);
	app.get('/api/commodities/:id', commodities.getCommodityByID);
	// POST
	app.post('/api/commodities',  commodities.createCommodity);
	// PUT
	app.put('/api/commodities',  commodities.updateCommodity);
	// DELETE
	app.delete('/api/commodities/:id', commodities.deleteCommodity);

	/////////////////////////
	///// COUNTRIES CRUD ////////
	/////////////////////////
	app.get('/api/countries/:limit/:skip', countries.getCountries);
	app.get('/api/countries/:id', countries.getCountryByID);
	// POST
	app.post('/api/countries',  countries.createCountry);
	// PUT
	app.put('/api/countries',  countries.updateCountry);
	// DELETE
	app.delete('/api/countries/:id', countries.deleteCountry);

	/////////////////////////
	///// SOURCES CRUD ////////
	/////////////////////////
	app.get('/api/sources/:limit/:skip', sources.getSources);
	app.get('/api/sources/:id', sources.getSourceByID);

	// POST
	app.post('/api/sources',  sources.createSource);
	// PUT
	app.put('/api/sources',  sources.updateSource);
	// DELETE
	app.delete('/api/sources/:id', sources.deleteSource);

	//DATASETS - TODO: protect with admin
	app.get('/api/datasets', datasets.getDatasets);
	app.get('/api/datasets/:limit/:skip', datasets.getDatasets);
	app.get('/api/datasets/:id', datasets.getDatasetByID);

	//Reporting
	app.get('/admin/etl/datasets/:dataset_id/actions/:action_id/report',
		datasets.getActionReport,
		function(req, res) {
		    res.render('actionreport', {report: req.report});
	});

	//Reporting
	app.get('/admin/etl/datasets/:dataset_id/actions/:action_id/report',
		datasets.getActionReport,
		function(req, res) {
		    res.render('actionreport', {report: req.report});
	});

	//Reporting
	app.get('/admin/etl/datasets/:dataset_id/actions/:action_id/report',
		datasets.getActionReport,
		function(req, res) {
		    res.render('actionreport', {report: req.report});
	});

	//Reporting
	app.get('/admin/etl/datasets/:dataset_id/actions/:action_id/report',
		datasets.getActionReport,
		function(req, res) {
		    res.render('actionreport', {report: req.report});
	});

	//Reporting
	app.get('/admin/etl/datasets/:dataset_id/actions/:action_id/report',
		datasets.getActionReport,
		function(req, res) {
		    res.render('actionreport', {report: req.report});
	});

	//Reporting
	app.get('/admin/etl/datasets/:dataset_id/actions/:action_id/report',
		datasets.getActionReport,
		function(req, res) {
		    res.render('actionreport', {report: req.report});
	});

	//Create a dataset
	app.post('/api/datasets', auth.requiresApiLogin, auth.requiresRole('admin'), datasets.createDataset);
	//Start an ETL step
	app.post('/api/datasets/:id/actions', auth.requiresApiLogin, auth.requiresRole('admin'), datasets.createAction);

	// POST
	app.post('/api/sources',  sources.createSource);
	// PUT
	app.put('/api/sources',  sources.updateSource);
	// DELETE
	app.delete('/api/sources/:id', sources.deleteSource);

	/////////////////////////////////////////////
	///// COMPANIES HOUSE DUMMY DATASETS ////////
	/////////////////////////////////////////////

	app.get('/api/testdata', datasets.getTestdata);

	/////////////////////////////////////////////
	///// COMPANIES HOUSE DUMMY DATASETS ////////
	/////////////////////////////////////////////

	app.get('/api/testdata', datasets.getTestdata);

	/////////////////////////
	///// SITES CRUD ////////
	/////////////////////////
	app.get('/api/sites/:limit/:skip/:field', sites.getSites);
	app.get('/api/sites/:id', sites.getSiteByID);
	app.get('/api/sites/map/:field', sites.getSitesMap);

	//DATASETS - TODO: protect with admin
	app.get('/api/datasets', datasets.getDatasets);
	app.get('/api/datasets/:limit/:skip', datasets.getDatasets);
	app.get('/api/datasets/:id', datasets.getDatasetByID);

	//Reporting
	app.get('/admin/etl/datasets/:dataset_id/actions/:action_id/report',
		datasets.getActionReport,
		function(req, res) {
		    res.render('actionreport', {report: req.report});
	});

	//Create a dataset
	app.post('/api/datasets', auth.requiresApiLogin, auth.requiresRole('admin'), datasets.createDataset);
	//Start an ETL step
	app.post('/api/datasets/:id/actions', auth.requiresApiLogin, auth.requiresRole('admin'), datasets.createAction);

	// POST
	app.post('/api/sites',  sites.createSite);
	// PUT
	app.put('/api/sites',  sites.updateSite);
	// DELETE
	app.delete('/api/sites/:id', sites.deleteSite);

	/////////////////////////////////////////////
	///// COMPANIES HOUSE DUMMY DATASETS ////////
	/////////////////////////////////////////////

	app.get('/api/testdata', datasets.getTestdata);

	/////////////////////////////////////////////
	///// COMPANIES HOUSE DUMMY DATASETS ////////
	/////////////////////////////////////////////

	app.get('/api/testdata', datasets.getTestdata);

	/////////////////////////////////////////////
	///// COMPANIES HOUSE DUMMY DATASETS ////////
	/////////////////////////////////////////////

	app.get('/api/testdata', datasets.getTestdata);

	/////////////////////////
	///// USERS CRUD ////////
	/////////////////////////
	app.get('/api/users', auth.requiresRole('admin'), users.getUsers);
	app.get('/api/users/:id', auth.requiresRole('admin'), users.getUsersByID);
	app.get('/api/user-list/:id', auth.requiresRole('admin'), users.getUsersListByID);

	// POST
	app.post('/api/users', auth.requiresApiLogin, auth.requiresRole('admin'), users.createUser);

	// PUT
	app.put('/api/users', auth.requiresRole('admin'), users.updateUser);

	// DELETE
	app.delete('/api/users/:id', auth.requiresRole('admin'), users.deleteUser);
	////////////////////
	///// OTHER ////////
	////////////////////
	app.get('/partials/*', function(req, res) {
		res.render('../../public/app/' + req.params[0]);
	});

	app.post('/login', auth.authenticate);

	app.post('/logout', function(req, res) {
		req.logout();
		res.end();
	});

	app.all('/api/*', function(req, res) {
		res.sendStatus(404);
	});
	//app.get('*', function(req, res) {
	//	res.render('landing_page', {
	//		bootstrappedUser: req.user
	//	});
	//});
	app.get('*', function(req, res) {
		res.render('index', {
			bootstrappedUser: req.user
		});
	});
};<commit_msg>Don't protect datasets stuff<commit_after>
var auth 			= require('./auth'),
    search 			= require('../controllers/search'),
	users 			= require('../controllers/users'),
	datasets  		= require('../controllers/datasets'),
	commodities 	= require('../controllers/commodities'),
	concessions 	= require('../controllers/concessions'),
	companies 		= require('../controllers/companies'),
	projects 		= require('../controllers/projects'),
	contracts 		= require('../controllers/contracts'),
	companyGroups 	= require('../controllers/companyGroups'),
	countries 		= require('../controllers/countries'),
	sources 		= require('../controllers/sources'),
	sites 			= require('../controllers/sites');
	//etl         = require('../controllers/etl');
	// answers 	= require('../controllers/answers'),
	// questions 	= require('../controllers/questions'),
	// assessments = require('../controllers/assessments');
	// mongoose 	= require('mongoose');
	// User 		= mongoose.model('User');

module.exports	= function(app) {

	app.get('/api/search', search.searchText);

	//CONTRACTS
	app.get('/api/contracts/:limit/:skip', contracts.getContracts);
	app.get('/api/contracts/:id', contracts.getContractByID);

	/////////////////////////
	///// CONCESSIONS CRUD ////////
	/////////////////////////
	app.get('/api/concessions/:limit/:skip', concessions.getConcessions);
	app.get('/api/concessions/:id', concessions.getConcessionByID);
	// POST
	app.post('/api/concessions',  concessions.createConcession);
	// PUT
	app.put('/api/concessions',  concessions.updateConcession);
	// DELETE
	app.delete('/api/concessions/:id', concessions.deleteConcession);

	/////////////////////////
	///// PROJECTS CRUD ////////
	/////////////////////////
	app.get('/api/projects/:limit/:skip', projects.getProjects);
	app.get('/api/projects/:id', projects.getProjectByID);
	app.get('/api/projects/', projects.getProjectsMap);
	// POST
	app.post('/api/projects',  projects.createProject);
	// PUT
	app.put('/api/projects',  projects.updateProject);
	// DELETE
	app.delete('/api/projects/:id', projects.deleteProject);

	/////////////////////////
	///// COMPANIES CRUD ////
	/////////////////////////
	//app.get('/api/companies/:limit/:skip', companies.getCompanies);
	app.get('/api/companies/:limit/:skip', companies.getCompanies);
	app.get('/api/companies/:id', companies.getCompanyByID);
	// POST
	app.post('/api/companies',  companies.createCompany);
	// PUT
	app.put('/api/companies',  companies.updateCompany);
	// DELETE
	app.delete('/api/companies/:id', companies.deleteCompany);

	/////////////////////////
	///// COMPANYGROUPS CRUD ////////
	/////////////////////////
	app.get('/api/companyGroups/:limit/:skip', companyGroups.getCompanyGroups);
	app.get('/api/companyGroups/:id', companyGroups.getCompanyGroupByID);
	// POST
	app.post('/api/companyGroups',  companyGroups.createCompanyGroup);
	// PUT
	app.put('/api/companyGroups',  companyGroups.updateCompanyGroup);
	// DELETE
	app.delete('/api/companyGroups/:id', companyGroups.deleteCompanyGroup);

	/////////////////////////
	///// COMMODITIES ////////
	/////////////////////////
	app.get('/api/commodities/:limit/:skip', commodities.getCommodities);
	app.get('/api/commodities/:id', commodities.getCommodityByID);
	// POST
	app.post('/api/commodities',  commodities.createCommodity);
	// PUT
	app.put('/api/commodities',  commodities.updateCommodity);
	// DELETE
	app.delete('/api/commodities/:id', commodities.deleteCommodity);

	/////////////////////////
	///// COUNTRIES CRUD ////////
	/////////////////////////
	app.get('/api/countries/:limit/:skip', countries.getCountries);
	app.get('/api/countries/:id', countries.getCountryByID);
	// POST
	app.post('/api/countries',  countries.createCountry);
	// PUT
	app.put('/api/countries',  countries.updateCountry);
	// DELETE
	app.delete('/api/countries/:id', countries.deleteCountry);

	/////////////////////////
	///// SOURCES CRUD ////////
	/////////////////////////
	app.get('/api/sources/:limit/:skip', sources.getSources);
	app.get('/api/sources/:id', sources.getSourceByID);

	// POST
	app.post('/api/sources',  sources.createSource);
	// PUT
	app.put('/api/sources',  sources.updateSource);
	// DELETE
	app.delete('/api/sources/:id', sources.deleteSource);

	//DATASETS - TODO: protect with admin
	app.get('/api/datasets', datasets.getDatasets);
	app.get('/api/datasets/:limit/:skip', datasets.getDatasets);
	app.get('/api/datasets/:id', datasets.getDatasetByID);

	//Reporting
	app.get('/admin/etl/datasets/:dataset_id/actions/:action_id/report',
		datasets.getActionReport,
		function(req, res) {
		    res.render('actionreport', {report: req.report});
	});

	//Reporting
	app.get('/admin/etl/datasets/:dataset_id/actions/:action_id/report',
		datasets.getActionReport,
		function(req, res) {
		    res.render('actionreport', {report: req.report});
	});

	//Reporting
	app.get('/admin/etl/datasets/:dataset_id/actions/:action_id/report',
		datasets.getActionReport,
		function(req, res) {
		    res.render('actionreport', {report: req.report});
	});

	//Reporting
	app.get('/admin/etl/datasets/:dataset_id/actions/:action_id/report',
		datasets.getActionReport,
		function(req, res) {
		    res.render('actionreport', {report: req.report});
	});

	//Reporting
	app.get('/admin/etl/datasets/:dataset_id/actions/:action_id/report',
		datasets.getActionReport,
		function(req, res) {
		    res.render('actionreport', {report: req.report});
	});

	//Reporting
	app.get('/admin/etl/datasets/:dataset_id/actions/:action_id/report',
		datasets.getActionReport,
		function(req, res) {
		    res.render('actionreport', {report: req.report});
	});

	//Create a dataset
	app.post('/api/datasets', auth.requiresApiLogin, /* auth.requiresRole('admin'),*/ datasets.createDataset);
	//Start an ETL step
	app.post('/api/datasets/:id/actions', /* auth.requiresApiLogin, auth.requiresRole('admin'), */ datasets.createAction);

	// POST
	app.post('/api/sources',  sources.createSource);
	// PUT
	app.put('/api/sources',  sources.updateSource);
	// DELETE
	app.delete('/api/sources/:id', sources.deleteSource);

	/////////////////////////////////////////////
	///// COMPANIES HOUSE DUMMY DATASETS ////////
	/////////////////////////////////////////////

	app.get('/api/testdata', datasets.getTestdata);

	/////////////////////////////////////////////
	///// COMPANIES HOUSE DUMMY DATASETS ////////
	/////////////////////////////////////////////

	app.get('/api/testdata', datasets.getTestdata);

	/////////////////////////
	///// SITES CRUD ////////
	/////////////////////////
	app.get('/api/sites/:limit/:skip/:field', sites.getSites);
	app.get('/api/sites/:id', sites.getSiteByID);
	app.get('/api/sites/map/:field', sites.getSitesMap);

	//DATASETS - TODO: protect with admin?
	app.get('/api/datasets', datasets.getDatasets);
	app.get('/api/datasets/:limit/:skip', datasets.getDatasets);
	app.get('/api/datasets/:id', datasets.getDatasetByID);

	//Reporting
	app.get('/admin/etl/datasets/:dataset_id/actions/:action_id/report',
		datasets.getActionReport,
		function(req, res) {
		    res.render('actionreport', {report: req.report});
	});

	//Create a dataset - todo protect with admin
	app.post('/api/datasets', /* auth.requiresApiLogin, auth.requiresRole('admin'), */ datasets.createDataset);
	//Start an ETL step
	app.post('/api/datasets/:id/actions', /* auth.requiresApiLogin, auth.requiresRole('admin'), */ datasets.createAction);

	// POST
	app.post('/api/sites',  sites.createSite);
	// PUT
	app.put('/api/sites',  sites.updateSite);
	// DELETE
	app.delete('/api/sites/:id', sites.deleteSite);

	/////////////////////////////////////////////
	///// COMPANIES HOUSE DUMMY DATASETS ////////
	/////////////////////////////////////////////

	app.get('/api/testdata', datasets.getTestdata);

	/////////////////////////////////////////////
	///// COMPANIES HOUSE DUMMY DATASETS ////////
	/////////////////////////////////////////////

	app.get('/api/testdata', datasets.getTestdata);

	/////////////////////////////////////////////
	///// COMPANIES HOUSE DUMMY DATASETS ////////
	/////////////////////////////////////////////

	app.get('/api/testdata', datasets.getTestdata);

	/////////////////////////
	///// USERS CRUD ////////
	/////////////////////////
	app.get('/api/users', auth.requiresRole('admin'), users.getUsers);
	app.get('/api/users/:id', auth.requiresRole('admin'), users.getUsersByID);
	app.get('/api/user-list/:id', auth.requiresRole('admin'), users.getUsersListByID);

	// POST
	app.post('/api/users', auth.requiresApiLogin, auth.requiresRole('admin'), users.createUser);

	// PUT
	app.put('/api/users', auth.requiresRole('admin'), users.updateUser);

	// DELETE
	app.delete('/api/users/:id', auth.requiresRole('admin'), users.deleteUser);
	////////////////////
	///// OTHER ////////
	////////////////////
	app.get('/partials/*', function(req, res) {
		res.render('../../public/app/' + req.params[0]);
	});

	app.post('/login', auth.authenticate);

	app.post('/logout', function(req, res) {
		req.logout();
		res.end();
	});

	app.all('/api/*', function(req, res) {
		res.sendStatus(404);
	});
	//app.get('*', function(req, res) {
	//	res.render('landing_page', {
	//		bootstrappedUser: req.user
	//	});
	//});
	app.get('*', function(req, res) {
		res.render('index', {
			bootstrappedUser: req.user
		});
	});
};<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013, IT Services, Stockholm University
 * 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.
 *
 * Neither the name of Stockholm University nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * 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.
 */

/**
 * The search view for the map module.
 *
 * @class A Backbone view to handle the search popup.
 * @author Joakim Lundin
 * @type {Backbone.View}
 */
var SearchView = Backbone.View.extend(
    /** @lends SearchView */
    {

      /**
       * @constructs
       * @param options Options for this class. Expects campus name and a searchResult collection to add results to
       */
      initialize: function (options) {
        _.bindAll(this, "render", "populateFilter");

        this.inputField = $("#search-autocomplete").parent().find("form input");

        // This is done to show a search icon or text in the mobile keyboard
        this.inputField.get(0).type = "search";
        if (options.placeholderSuffix) {
          this.inputField.attr(
              "placeholder",
              i18n.t('map.menu.searchfor') + i18n.t(options.placeholderSuffix).toLowerCase()
          );
        }
        $("#search-autocomplete").listview("option", "filterCallback", this.filterSearch);

        this.collection.on("reset", this.render);
      },

      /** Registers events */
      events: {
        'focus input': 'showFilteredList',
        'keyup input': 'inputKeyup',
        'click #cancelFilter': 'handleCancelClick',
        'click .autocomplete-link': 'showClickedLoction'
      },

      /**
       * Render the search view.
       */
      render: function () {
        this.populateFilter();
        this.delegateEvents();
      },

      inputKeyup: function (e) {
        if (e.which === 13) {
          $(e.target).trigger("blur");
        }

        var $noresults = $('#noresults');
        var results = $("#search-autocomplete").children(':visible').length;

        if (results === 0 && !$noresults.is(':visible')) {
          $noresults.show();
        }
        else if (results > 0 && $noresults.is(':visible')) {
          $noresults.hide();
        }
      },

      showFilteredList: function () {
        $("#search-box form").addClass("tight");
        $("#cancelFilter").show();

        //if input field not empty trigger new filtering with existing value, else show whole filter
        if ($('div#search-box input').val() !== "") {
          // jquerymobile remembers the old value in the input field.
          // in order to trigger a change event, we must set another value inbetween.
          var prevValue = this.inputField.val();
          this.inputField.val('').trigger('change');
          this.inputField.val(prevValue).trigger('change');
        } else {
          $("#search-autocomplete li").removeClass("ui-screen-hidden");
        }
      },

      /**
       * Handle click on cancel button.
       *
       * @param evt the click event.
       */
      handleCancelClick: function (evt) {
        this.hideFilteredList();

        if (this.inputField.val() === "") {
          this.resetLocations();
        }
      },

      /**
       * Hides the filter list.
       */
      hideFilteredList: function () {
        $("#search-box form").removeClass("tight");
        $("#cancelFilter").hide();
        $("#search-autocomplete li").addClass("ui-screen-hidden");
      },

      /**
       * getClickedLocation takes the clicked html snippet from the search-filter list,
       * i.e: E 421.
       * From this html snippet getClickedLocation extracts the element name, in this
       * case "E 421" and finds it in the this.items collection (containing) all items
       * for the given type (i.e. auditorium).
       *
       *  @returns A new Locations collection with just the inputed item.
       *           If for some reason the item is not found in this.items collection,
       *           an empty Locations collection is returned.
       */
      getClickedLocation: function (target) {
        var modelid = $(target).attr('data-modelid');
        return this.collection.get(modelid);
      },

      showClickedLoction: function (event, ui) {
        this.hideFilteredList();
        var location = this.getClickedLocation(event.target);
        this.trigger("selected", location);
      },

      resetLocations: function () {
        this.collection.trigger("reset");
      },

      populateFilter: function () {
        var html = this.collection.bySearchable().reduce(function (memo, location) {
          //TODO: Use JST
          return memo + '
' +
              '' + location.getI18n('name') + '' +
              '
';
        }, "");

        var $ul = $('#search-autocomplete');
        $ul.hide();
        $ul.html(html);
        $ul.listview("refresh");
        $ul.trigger("updatelayout");

        // After populating the list, hide it (only show it when search-box has focus)
        // hide the list only if the input doesn't have focus.
        if (!this.inputField.is(":focus")) {
          this.hideFilteredList();
        }
        $ul.show();
      },

      filterSearch: function (text, searchValue) {
        searchValue = searchValue.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
        var pattern = new RegExp("(^| )" + searchValue, 'i');

        return !pattern.test(text);
      }
    });
<commit_msg>MOB-232 Added sorting to list of departments.<commit_after>/*
 * Copyright (c) 2013, IT Services, Stockholm University
 * 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.
 *
 * Neither the name of Stockholm University nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * 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.
 */

/**
 * The search view for the map module.
 *
 * @class A Backbone view to handle the search popup.
 * @author Joakim Lundin
 * @type {Backbone.View}
 */
var SearchView = Backbone.View.extend(
    /** @lends SearchView */
    {

      /**
       * @constructs
       * @param options Options for this class. Expects campus name and a searchResult collection to add results to
       */
      initialize: function (options) {
        _.bindAll(this, "render", "populateFilter");

        this.inputField = $("#search-autocomplete").parent().find("form input");

        // This is done to show a search icon or text in the mobile keyboard
        this.inputField.get(0).type = "search";
        if (options.placeholderSuffix) {
          this.inputField.attr(
              "placeholder",
              i18n.t('map.menu.searchfor') + i18n.t(options.placeholderSuffix).toLowerCase()
          );
        }
        $("#search-autocomplete").listview("option", "filterCallback", this.filterSearch);

        this.collection.on("reset", this.render);
      },

      /** Registers events */
      events: {
        'focus input': 'showFilteredList',
        'keyup input': 'inputKeyup',
        'click #cancelFilter': 'handleCancelClick',
        'click .autocomplete-link': 'showClickedLoction'
      },

      /**
       * Render the search view.
       */
      render: function () {
        this.populateFilter();
        this.delegateEvents();
      },

      inputKeyup: function (e) {
        if (e.which === 13) {
          $(e.target).trigger("blur");
        }

        var $noresults = $('#noresults');
        var results = $("#search-autocomplete").children(':visible').length;

        if (results === 0 && !$noresults.is(':visible')) {
          $noresults.show();
        }
        else if (results > 0 && $noresults.is(':visible')) {
          $noresults.hide();
        }
      },

      showFilteredList: function () {
        $("#search-box form").addClass("tight");
        $("#cancelFilter").show();

        //if input field not empty trigger new filtering with existing value, else show whole filter
        if ($('div#search-box input').val() !== "") {
          // jquerymobile remembers the old value in the input field.
          // in order to trigger a change event, we must set another value inbetween.
          var prevValue = this.inputField.val();
          this.inputField.val('').trigger('change');
          this.inputField.val(prevValue).trigger('change');
        } else {
          $("#search-autocomplete li").removeClass("ui-screen-hidden");
        }
      },

      /**
       * Handle click on cancel button.
       *
       * @param evt the click event.
       */
      handleCancelClick: function (evt) {
        this.hideFilteredList();

        if (this.inputField.val() === "") {
          this.resetLocations();
        }
      },

      /**
       * Hides the filter list.
       */
      hideFilteredList: function () {
        $("#search-box form").removeClass("tight");
        $("#cancelFilter").hide();
        $("#search-autocomplete li").addClass("ui-screen-hidden");
      },

      /**
       * getClickedLocation takes the clicked html snippet from the search-filter list,
       * i.e: E 421.
       * From this html snippet getClickedLocation extracts the element name, in this
       * case "E 421" and finds it in the this.items collection (containing) all items
       * for the given type (i.e. auditorium).
       *
       *  @returns A new Locations collection with just the inputed item.
       *           If for some reason the item is not found in this.items collection,
       *           an empty Locations collection is returned.
       */
      getClickedLocation: function (target) {
        var modelid = $(target).attr('data-modelid');
        return this.collection.get(modelid);
      },

      showClickedLoction: function (event, ui) {
        this.hideFilteredList();
        var location = this.getClickedLocation(event.target);
        this.trigger("selected", location);
      },

      resetLocations: function () {
        this.collection.trigger("reset");
      },

      populateFilter: function () {
        var html = this.collection.bySearchable().sortBy(function(location){
          return location.getI18n('name');
        }).reduce(function (memo, location) {
          //TODO: Use JST
          return memo + '
' +
              '' + location.getI18n('name') + '' +
              '
';
        }, "");

        var $ul = $('#search-autocomplete');
        $ul.hide();
        $ul.html(html);
        $ul.listview("refresh");
        $ul.trigger("updatelayout");

        // After populating the list, hide it (only show it when search-box has focus)
        // hide the list only if the input doesn't have focus.
        if (!this.inputField.is(":focus")) {
          this.hideFilteredList();
        }
        $ul.show();
      },

      filterSearch: function (text, searchValue) {
        searchValue = searchValue.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
        var pattern = new RegExp("(^| )" + searchValue, 'i');

        return !pattern.test(text);
      }
    });
<|endoftext|>


<commit_before>(function(window) {

	/*!
	 * EventEmitter v4.2.7 - git.io/ee
	 * Oliver Caldwell
	 * MIT license
	 * @preserve
	 */
	(function(){"use strict";function t(){}function r(t,n){for(var e=t.length;e--;)if(t[e].listener===n)return e;return-1}function n(e){return function(){return this[e].apply(this,arguments)}}var e=t.prototype,i=this,s=i.EventEmitter;e.getListeners=function(n){var r,e,t=this._getEvents();if(n instanceof RegExp){r={};for(e in t)t.hasOwnProperty(e)&&n.test(e)&&(r[e]=t[e])}else r=t[n]||(t[n]=[]);return r},e.flattenListeners=function(t){var e,n=[];for(e=0;e<t.length;e+=1)n.push(t[e].listener);return n},e.getListenersAsObject=function(n){var e,t=this.getListeners(n);return t instanceof Array&&(e={},e[n]=t),e||t},e.addListener=function(i,e){var t,n=this.getListenersAsObject(i),s="object"==typeof e;for(t in n)n.hasOwnProperty(t)&&-1===r(n[t],e)&&n[t].push(s?e:{listener:e,once:!1});return this},e.on=n("addListener"),e.addOnceListener=function(e,t){return this.addListener(e,{listener:t,once:!0})},e.once=n("addOnceListener"),e.defineEvent=function(e){return this.getListeners(e),this},e.defineEvents=function(t){for(var e=0;e<t.length;e+=1)this.defineEvent(t[e]);return this},e.removeListener=function(i,s){var n,e,t=this.getListenersAsObject(i);for(e in t)t.hasOwnProperty(e)&&(n=r(t[e],s),-1!==n&&t[e].splice(n,1));return this},e.off=n("removeListener"),e.addListeners=function(e,t){return this.manipulateListeners(!1,e,t)},e.removeListeners=function(e,t){return this.manipulateListeners(!0,e,t)},e.manipulateListeners=function(r,t,i){var e,n,s=r?this.removeListener:this.addListener,o=r?this.removeListeners:this.addListeners;if("object"!=typeof t||t instanceof RegExp)for(e=i.length;e--;)s.call(this,t,i[e]);else for(e in t)t.hasOwnProperty(e)&&(n=t[e])&&("function"==typeof n?s.call(this,e,n):o.call(this,e,n));return this},e.removeEvent=function(e){var t,r=typeof e,n=this._getEvents();if("string"===r)delete n[e];else if(e instanceof RegExp)for(t in n)n.hasOwnProperty(t)&&e.test(t)&&delete n[t];else delete this._events;return this},e.removeAllListeners=n("removeEvent"),e.emitEvent=function(r,o){var e,i,t,s,n=this.getListenersAsObject(r);for(t in n)if(n.hasOwnProperty(t))for(i=n[t].length;i--;)e=n[t][i],e.once===!0&&this.removeListener(r,e.listener),s=e.listener.apply(this,o||[]),s===this._getOnceReturnValue()&&this.removeListener(r,e.listener);return this},e.trigger=n("emitEvent"),e.emit=function(e){var t=Array.prototype.slice.call(arguments,1);return this.emitEvent(e,t)},e.setOnceReturnValue=function(e){return this._onceReturnValue=e,this},e._getOnceReturnValue=function(){return this.hasOwnProperty("_onceReturnValue")?this._onceReturnValue:!0},e._getEvents=function(){return this._events||(this._events={})},t.noConflict=function(){return i.EventEmitter=s,t},"function"==typeof define&&define.amd?define(function(){return t}):"object"==typeof module&&module.exports?module.exports=t:this.EventEmitter=t}).call(this);

	var socketList = {
		tcp: {},
		udp: {},
		tcpServer: {}
	};
	window.socketList = socketList;

	/**
	 * Create a new ChromeTcpSocket
	 * @param  {Object}   properties SocketProperties sent to the chrome.sockets.tcp.create
	 * @param  {Function} cb         Callback function(ChromeTcpSocket)
	 */
	chrome.sockets.tcp.open = function(properties, cb) {
		chrome.sockets.tcp.create(properties, function(createInfo) {
			var socket = new ChromeTcpSocket(createInfo.socketId);
			socket.on('closed', onSocketClosed);
			socketList.tcp[createInfo.socketId] = socket;
			cb(socket);
		});
	};

	/**
	 * Create a new ChromeTcpServerSocket
	 * @param  {Object}   properties SocketProperties sent to the chrome.sockets.tcpServer.create
	 * @param  {Function} cb         Callback function(ChromeTcpServerSocket)
	 */
	chrome.sockets.tcpServer.open = function(properties, cb) {
		chrome.sockets.tcpServer.create(properties, function(createInfo) {
			var socket = new ChromeTcpServerSocket(createInfo.socketId);
			socket.on('closed', onSocketClosed);
			socketList.tcpServer[createInfo.socketId] = socket;
			cb(socket);
		});
	};

	/**
	 * Create a new ChromeUdpSocket
	 * @param  {Object}   properties SocketProperties sent to the chrome.sockets.udpServer.create
	 * @param  {Function} cb         Callback function(ChromeUdpServerSocket)
	 */
	chrome.sockets.udp.open = function(properties, cb) {
		chrome.sockets.udp.create(properties, function(createInfo) {
			var socket = new ChromeUdpSocket(createInfo.socketId);
			socket.on('closed', onSocketClosed);
			socketList.udp[createInfo.socketId] = socket;
			cb(socket);
		})
	};

	/**
	 * Closes ALL tcp sockets! Even ones not created using this wrapper.
	 * @param  {Function} cb Callback function
	 */
	chrome.sockets.tcp.closeAll = function(cb) {
		chrome.sockets.tcp.getSockets(function(sockets) {
			for (var n = 0; n < sockets.length; n++) {
				chrome.sockets.tcp.close(sockets[n].socketId);
			}
			cb && cb();
		});
	};

	/**
	 * Closes ALL udp sockets! Even ones not created using this wrapper.
	 * @param  {Function} cb Callback function
	 */
	chrome.sockets.udp.closeAll = function(cb) {
		chrome.sockets.udp.getSockets(function(sockets) {
			for (var n = 0; n < sockets.length; n++) {
				chrome.sockets.udp.close(sockets[n].socketId);
			}
			cb && cb();
		});
	};

	/**
	 * Closes ALL tcpSerer sockets! Even ones not created using this wrapper.
	 * @param  {Function} cb Callback function
	 */
	chrome.sockets.tcpServer.closeAll = function(cb) {
		chrome.sockets.tcpServer.getSockets(function(sockets) {
			for (var n = 0; n < sockets.length; n++) {
				chrome.sockets.tcpServer.close(sockets[n].socketId);
			}
			cb && cb();
		});
	};

	/**
	 * Bound to ALL SOCKETS THAT GET CREATED - this releases all event listeners and drops references to the socket object
	 */
	var onSocketClosed = function() {
		socketList[this.type][this.socketId] = null;
		delete socketList[this.type][this.socketId];
		this.removeAllListeners();
	};

	/**
	 * onReceive handler for TCP - finds the appropriate ChromeTcpSocket objects and emit receive with the info
	 * @param  {Object} receiveInfo Data emitted from the chrome.sockets.tcp.onReceive handler
	 */
	chrome.sockets.tcp.onReceive.addListener(function(receiveInfo) {
		var socketId = receiveInfo.socketId;
		if (socketList.tcp[socketId]) {
			socketList.tcp[socketId].emit('receive', receiveInfo);
		}
	});

	/**
	 * onReceiveError handler for TCP - finds the appropriate ChromeTcpSocket objects and emit receiveError with the info
	 * @param  {Object} receiveErrorInfo Data emitted from the chrome.sockets.tcp.onReceive handler
	 */
	chrome.sockets.tcp.onReceiveError.addListener(function(receiveErrorInfo) {
		if (receiveErrorInfo && receiveErrorInfo.socketId && socketList.tcp[receiveErrorInfo.socketId]) {
			socketList.tcp[receiveErrorInfo.socketId].emit('receiveError', receiveErrorInfo.resultCode);
		}
	});

	/**
	 * onReceive handler for UDP - finds the appropriate ChromeUdpSocket objects and emit receive with the info
	 * @param  {Object} receiveInfo Data emitted from the chrome.sockets.udp.onReceive handler
	 */
	chrome.sockets.udp.onReceive.addListener(function(receiveInfo) {
		if (socketList.udp[receiveInfo.socketId]) {
			socketList.udp[receiveInfo.socketId].emit('receive', receiveInfo);
		}
	});

	/**
	 * onReceiveError handler for UDP - finds the appropriate ChromeTcpSocket objects and emit receiveError with the info
	 * @param  {Object} receiveErrorInfo Data emitted from the chrome.sockets.udp.onReceive handler
	 */
	chrome.sockets.udp.onReceiveError.addListener(function(receiveErrorInfo) {
		if (receiveErrorInfo && receiveErrorInfo.socketId && socketList.udp[receiveErrorInfo.socketId]) {
			socketList.udp[receiveErrorInfo.socketId].emit('receiveError', receiveErrorInfo.resultCode);
		}
	});

	/**
	 * onAccept handler for TCP Server sockets - finds the appropriate ChromeTcpServerSocket and emit a new ChromeTcpSocket
	 * to represent the new socket connection to a client
	 * @param  {Object} acceptInfo Server socketId and clientSocketId
	 */
	chrome.sockets.tcpServer.onAccept.addListener(function(acceptInfo) {
		if (socketList.tcpServer[acceptInfo.socketId]) {
			var socket = new ChromeTcpSocket(acceptInfo.clientSocketId);
			socket.on('closed', onSocketClosed);
			socketList.tcp[acceptInfo.clientSocketId] = socket;
			socketList.tcpServer[acceptInfo.socketId].emit('accept', socket);
		}
	});

	/**
	 * onAcceptError handler for TCP Server sockets - finds the appropriate ChromeTcpServerSocket and emit the error info
	 * @param  {Object} acceptErrorInfo Data emitted from the chrome.sockets.tcpServer.onAccept handler
	 */
	chrome.sockets.tcpServer.onAcceptError.addListener(function(acceptErrorInfo) {
		if (acceptErrorInfo && acceptErrorInfo.socketId && socketList.udp[acceptErrorInfo.socketId]) {
			socketList.tcpServer[acceptErrorInfo.socketId].emit('receiveError', acceptErrorInfo.resultCode);
		}
	});

	// BASE CLASS
	var ChromeSocket = function(type) {
		EventEmitter.call(this);
		this._api = chrome.sockets[type];
		this.type = type;
	}
	ChromeSocket.prototype = Object.create(EventEmitter.prototype);

	ChromeSocket.prototype.update = function(properties, cb) {
		this._api.update(this.socketId, properties, cb);
	};

	ChromeSocket.prototype.setPaused = function(paused, cb) {
		this._api.setPaused(this.socketId, paused, cb);
	};

	ChromeSocket.prototype.close = function(cb) {
		var self = this;
		this._api.close(this.socketId, function() {
			self.emit('closed');
		});
	};

	ChromeSocket.prototype.getInfo = function(cb) {
		this._api.getInfo(this.socketId, cb);
	};

	// TCP
	var ChromeTcpSocket = function(socketId) {
		ChromeSocket.call(this, 'tcp');
		this.socketId = socketId;
	};
	ChromeTcpSocket.prototype = Object.create(ChromeSocket.prototype);

	ChromeTcpSocket.prototype.setKeepAlive = function(enable, delay, cb) {
		chrome.sockets.tcp.setKeepAlive(this.socketId, enable, delay, cb);
	};

	ChromeTcpSocket.prototype.setNoDelay = function(noDelay, cb) {
		chrome.sockets.tcp.setNoDelay(this.socketId, noDelay, cb);
	};

	ChromeTcpSocket.prototype.connect = function(host, port, cb) {
		chrome.sockets.tcp.connect(this.socketId, host, port, cb);
	};

	ChromeTcpSocket.prototype.disconnect = function(cb) {
		chrome.sockets.tcp.disconnect(this.socketId, cb);
	};

	ChromeTcpSocket.prototype.send = function(arrayBuffer, cb) {
		chrome.sockets.tcp.send(this.socketId, arrayBuffer, cb);
	};

	// UDP
	var ChromeUdpSocket = function(socketId) {
		ChromeSocket.call(this, 'udp');
		this.socketId = socketId;
	};
	ChromeUdpSocket.prototype = Object.create(ChromeSocket.prototype);

	ChromeUdpSocket.prototype.bind = function(address, port, cb) {
		chrome.sockets.udp.bind(this.socketId, address, port, cb);
	};

	ChromeUdpSocket.prototype.send = function(arrayBuffer, host, port, cb) {
		chrome.sockets.udp.send(this.socketId, arrayBuffer, host, port, cb);
	};

	ChromeUdpSocket.prototype.joinGroup = function(address, cb) {
		chrome.sockets.udp.joinGroup(this.socketId, address, cb);
	};

	ChromeUdpSocket.prototype.leaveGroup = function(address, cb) {
		chrome.sockets.udp.leaveGroup(this.socketId, address, cb);
	};

	ChromeUdpSocket.prototype.setMulticastTimeToLive = function(ttl, cb) {
		chrome.sockets.udp.setMulticastTimeToLive(this.socketId, ttl, cb);
	};

	ChromeUdpSocket.prototype.setMulticastLoopbackMode = function(enabled, cb) {
		chrome.sockets.udp.setMulticastLoopbackMode(this.socketId, enabled, cb);
	};

	ChromeUdpSocket.prototype.getJoinedGroups = function(cb) {
		chrome.sockets.udp.getJoinedGroups(this.socketId, cb);
	};

	// TCP SERVER
	var ChromeTcpServerSocket = function(socketId) {
		ChromeSocket.call(this, 'tcpServer');
		this.socketId = socketId;
	};
	ChromeTcpServerSocket.prototype = Object.create(ChromeSocket.prototype);

	ChromeTcpServerSocket.prototype.listen = function(host, port, cb) {
		chrome.sockets.tcpServer.listen(this.socketId, host, port, cb);
	};

})(window);<commit_msg>Lil bit of documentation<commit_after>(function(window) {

	/*!
	 * EventEmitter v4.2.7 - git.io/ee
	 * Oliver Caldwell
	 * MIT license
	 * @preserve
	 */
	(function(){"use strict";function t(){}function r(t,n){for(var e=t.length;e--;)if(t[e].listener===n)return e;return-1}function n(e){return function(){return this[e].apply(this,arguments)}}var e=t.prototype,i=this,s=i.EventEmitter;e.getListeners=function(n){var r,e,t=this._getEvents();if(n instanceof RegExp){r={};for(e in t)t.hasOwnProperty(e)&&n.test(e)&&(r[e]=t[e])}else r=t[n]||(t[n]=[]);return r},e.flattenListeners=function(t){var e,n=[];for(e=0;e<t.length;e+=1)n.push(t[e].listener);return n},e.getListenersAsObject=function(n){var e,t=this.getListeners(n);return t instanceof Array&&(e={},e[n]=t),e||t},e.addListener=function(i,e){var t,n=this.getListenersAsObject(i),s="object"==typeof e;for(t in n)n.hasOwnProperty(t)&&-1===r(n[t],e)&&n[t].push(s?e:{listener:e,once:!1});return this},e.on=n("addListener"),e.addOnceListener=function(e,t){return this.addListener(e,{listener:t,once:!0})},e.once=n("addOnceListener"),e.defineEvent=function(e){return this.getListeners(e),this},e.defineEvents=function(t){for(var e=0;e<t.length;e+=1)this.defineEvent(t[e]);return this},e.removeListener=function(i,s){var n,e,t=this.getListenersAsObject(i);for(e in t)t.hasOwnProperty(e)&&(n=r(t[e],s),-1!==n&&t[e].splice(n,1));return this},e.off=n("removeListener"),e.addListeners=function(e,t){return this.manipulateListeners(!1,e,t)},e.removeListeners=function(e,t){return this.manipulateListeners(!0,e,t)},e.manipulateListeners=function(r,t,i){var e,n,s=r?this.removeListener:this.addListener,o=r?this.removeListeners:this.addListeners;if("object"!=typeof t||t instanceof RegExp)for(e=i.length;e--;)s.call(this,t,i[e]);else for(e in t)t.hasOwnProperty(e)&&(n=t[e])&&("function"==typeof n?s.call(this,e,n):o.call(this,e,n));return this},e.removeEvent=function(e){var t,r=typeof e,n=this._getEvents();if("string"===r)delete n[e];else if(e instanceof RegExp)for(t in n)n.hasOwnProperty(t)&&e.test(t)&&delete n[t];else delete this._events;return this},e.removeAllListeners=n("removeEvent"),e.emitEvent=function(r,o){var e,i,t,s,n=this.getListenersAsObject(r);for(t in n)if(n.hasOwnProperty(t))for(i=n[t].length;i--;)e=n[t][i],e.once===!0&&this.removeListener(r,e.listener),s=e.listener.apply(this,o||[]),s===this._getOnceReturnValue()&&this.removeListener(r,e.listener);return this},e.trigger=n("emitEvent"),e.emit=function(e){var t=Array.prototype.slice.call(arguments,1);return this.emitEvent(e,t)},e.setOnceReturnValue=function(e){return this._onceReturnValue=e,this},e._getOnceReturnValue=function(){return this.hasOwnProperty("_onceReturnValue")?this._onceReturnValue:!0},e._getEvents=function(){return this._events||(this._events={})},t.noConflict=function(){return i.EventEmitter=s,t},"function"==typeof define&&define.amd?define(function(){return t}):"object"==typeof module&&module.exports?module.exports=t:this.EventEmitter=t}).call(this);

	/**
	 * Object that holds reference to open sockets, used to track down and emit events when they are globally emitted
	 * @type {Object}
	 */
	var socketList = {
		tcp: {},
		udp: {},
		tcpServer: {}
	};

	/**
	 * Create a new ChromeTcpSocket
	 * @param  {Object}   properties SocketProperties sent to the chrome.sockets.tcp.create
	 * @param  {Function} cb         Callback function(ChromeTcpSocket)
	 */
	chrome.sockets.tcp.open = function(properties, cb) {
		chrome.sockets.tcp.create(properties, function(createInfo) {
			var socket = new ChromeTcpSocket(createInfo.socketId);
			socket.on('closed', onSocketClosed);
			socketList.tcp[createInfo.socketId] = socket;
			cb(socket);
		});
	};

	/**
	 * Create a new ChromeTcpServerSocket
	 * @param  {Object}   properties SocketProperties sent to the chrome.sockets.tcpServer.create
	 * @param  {Function} cb         Callback function(ChromeTcpServerSocket)
	 */
	chrome.sockets.tcpServer.open = function(properties, cb) {
		chrome.sockets.tcpServer.create(properties, function(createInfo) {
			var socket = new ChromeTcpServerSocket(createInfo.socketId);
			socket.on('closed', onSocketClosed);
			socketList.tcpServer[createInfo.socketId] = socket;
			cb(socket);
		});
	};

	/**
	 * Create a new ChromeUdpSocket
	 * @param  {Object}   properties SocketProperties sent to the chrome.sockets.udpServer.create
	 * @param  {Function} cb         Callback function(ChromeUdpServerSocket)
	 */
	chrome.sockets.udp.open = function(properties, cb) {
		chrome.sockets.udp.create(properties, function(createInfo) {
			var socket = new ChromeUdpSocket(createInfo.socketId);
			socket.on('closed', onSocketClosed);
			socketList.udp[createInfo.socketId] = socket;
			cb(socket);
		})
	};

	/**
	 * Closes ALL tcp sockets! Even ones not created using this wrapper.
	 * @param  {Function} cb Callback function
	 */
	chrome.sockets.tcp.closeAll = function(cb) {
		chrome.sockets.tcp.getSockets(function(sockets) {
			for (var n = 0; n < sockets.length; n++) {
				chrome.sockets.tcp.close(sockets[n].socketId);
			}
			cb && cb();
		});
	};

	/**
	 * Closes ALL udp sockets! Even ones not created using this wrapper.
	 * @param  {Function} cb Callback function
	 */
	chrome.sockets.udp.closeAll = function(cb) {
		chrome.sockets.udp.getSockets(function(sockets) {
			for (var n = 0; n < sockets.length; n++) {
				chrome.sockets.udp.close(sockets[n].socketId);
			}
			cb && cb();
		});
	};

	/**
	 * Closes ALL tcpSerer sockets! Even ones not created using this wrapper.
	 * @param  {Function} cb Callback function
	 */
	chrome.sockets.tcpServer.closeAll = function(cb) {
		chrome.sockets.tcpServer.getSockets(function(sockets) {
			for (var n = 0; n < sockets.length; n++) {
				chrome.sockets.tcpServer.close(sockets[n].socketId);
			}
			cb && cb();
		});
	};

	/**
	 * Bound to ALL SOCKETS THAT GET CREATED - this releases all event listeners and drops references to the socket object
	 */
	var onSocketClosed = function() {
		socketList[this.type][this.socketId] = null;
		delete socketList[this.type][this.socketId];
		this.removeAllListeners();
	};

	/**
	 * onReceive handler for TCP - finds the appropriate ChromeTcpSocket objects and emit receive with the info
	 * @param  {Object} receiveInfo Data emitted from the chrome.sockets.tcp.onReceive handler
	 */
	chrome.sockets.tcp.onReceive.addListener(function(receiveInfo) {
		var socketId = receiveInfo.socketId;
		if (socketList.tcp[socketId]) {
			socketList.tcp[socketId].emit('receive', receiveInfo);
		}
	});

	/**
	 * onReceiveError handler for TCP - finds the appropriate ChromeTcpSocket objects and emit receiveError with the info
	 * @param  {Object} receiveErrorInfo Data emitted from the chrome.sockets.tcp.onReceive handler
	 */
	chrome.sockets.tcp.onReceiveError.addListener(function(receiveErrorInfo) {
		if (receiveErrorInfo && receiveErrorInfo.socketId && socketList.tcp[receiveErrorInfo.socketId]) {
			socketList.tcp[receiveErrorInfo.socketId].emit('receiveError', receiveErrorInfo.resultCode);
		}
	});

	/**
	 * onReceive handler for UDP - finds the appropriate ChromeUdpSocket objects and emit receive with the info
	 * @param  {Object} receiveInfo Data emitted from the chrome.sockets.udp.onReceive handler
	 */
	chrome.sockets.udp.onReceive.addListener(function(receiveInfo) {
		if (socketList.udp[receiveInfo.socketId]) {
			socketList.udp[receiveInfo.socketId].emit('receive', receiveInfo);
		}
	});

	/**
	 * onReceiveError handler for UDP - finds the appropriate ChromeTcpSocket objects and emit receiveError with the info
	 * @param  {Object} receiveErrorInfo Data emitted from the chrome.sockets.udp.onReceive handler
	 */
	chrome.sockets.udp.onReceiveError.addListener(function(receiveErrorInfo) {
		if (receiveErrorInfo && receiveErrorInfo.socketId && socketList.udp[receiveErrorInfo.socketId]) {
			socketList.udp[receiveErrorInfo.socketId].emit('receiveError', receiveErrorInfo.resultCode);
		}
	});

	/**
	 * onAccept handler for TCP Server sockets - finds the appropriate ChromeTcpServerSocket and emit a new ChromeTcpSocket
	 * to represent the new socket connection to a client
	 * @param  {Object} acceptInfo Server socketId and clientSocketId
	 */
	chrome.sockets.tcpServer.onAccept.addListener(function(acceptInfo) {
		if (socketList.tcpServer[acceptInfo.socketId]) {
			var socket = new ChromeTcpSocket(acceptInfo.clientSocketId);
			socket.on('closed', onSocketClosed);
			socketList.tcp[acceptInfo.clientSocketId] = socket;
			socketList.tcpServer[acceptInfo.socketId].emit('accept', socket);
		}
	});

	/**
	 * onAcceptError handler for TCP Server sockets - finds the appropriate ChromeTcpServerSocket and emit the error info
	 * @param  {Object} acceptErrorInfo Data emitted from the chrome.sockets.tcpServer.onAccept handler
	 */
	chrome.sockets.tcpServer.onAcceptError.addListener(function(acceptErrorInfo) {
		if (acceptErrorInfo && acceptErrorInfo.socketId && socketList.udp[acceptErrorInfo.socketId]) {
			socketList.tcpServer[acceptErrorInfo.socketId].emit('receiveError', acceptErrorInfo.resultCode);
		}
	});

	// BASE CLASS
	var ChromeSocket = function(type) {
		EventEmitter.call(this);
		this._api = chrome.sockets[type];
		this.type = type;
	}
	ChromeSocket.prototype = Object.create(EventEmitter.prototype);

	ChromeSocket.prototype.update = function(properties, cb) {
		this._api.update(this.socketId, properties, cb);
	};

	ChromeSocket.prototype.setPaused = function(paused, cb) {
		this._api.setPaused(this.socketId, paused, cb);
	};

	ChromeSocket.prototype.close = function(cb) {
		var self = this;
		this._api.close(this.socketId, function() {
			self.emit('closed');
		});
	};

	ChromeSocket.prototype.getInfo = function(cb) {
		this._api.getInfo(this.socketId, cb);
	};

	// TCP
	var ChromeTcpSocket = function(socketId) {
		ChromeSocket.call(this, 'tcp');
		this.socketId = socketId;
	};
	ChromeTcpSocket.prototype = Object.create(ChromeSocket.prototype);

	ChromeTcpSocket.prototype.setKeepAlive = function(enable, delay, cb) {
		chrome.sockets.tcp.setKeepAlive(this.socketId, enable, delay, cb);
	};

	ChromeTcpSocket.prototype.setNoDelay = function(noDelay, cb) {
		chrome.sockets.tcp.setNoDelay(this.socketId, noDelay, cb);
	};

	ChromeTcpSocket.prototype.connect = function(host, port, cb) {
		chrome.sockets.tcp.connect(this.socketId, host, port, cb);
	};

	ChromeTcpSocket.prototype.disconnect = function(cb) {
		chrome.sockets.tcp.disconnect(this.socketId, cb);
	};

	ChromeTcpSocket.prototype.send = function(arrayBuffer, cb) {
		chrome.sockets.tcp.send(this.socketId, arrayBuffer, cb);
	};

	// UDP
	var ChromeUdpSocket = function(socketId) {
		ChromeSocket.call(this, 'udp');
		this.socketId = socketId;
	};
	ChromeUdpSocket.prototype = Object.create(ChromeSocket.prototype);

	ChromeUdpSocket.prototype.bind = function(address, port, cb) {
		chrome.sockets.udp.bind(this.socketId, address, port, cb);
	};

	ChromeUdpSocket.prototype.send = function(arrayBuffer, host, port, cb) {
		chrome.sockets.udp.send(this.socketId, arrayBuffer, host, port, cb);
	};

	ChromeUdpSocket.prototype.joinGroup = function(address, cb) {
		chrome.sockets.udp.joinGroup(this.socketId, address, cb);
	};

	ChromeUdpSocket.prototype.leaveGroup = function(address, cb) {
		chrome.sockets.udp.leaveGroup(this.socketId, address, cb);
	};

	ChromeUdpSocket.prototype.setMulticastTimeToLive = function(ttl, cb) {
		chrome.sockets.udp.setMulticastTimeToLive(this.socketId, ttl, cb);
	};

	ChromeUdpSocket.prototype.setMulticastLoopbackMode = function(enabled, cb) {
		chrome.sockets.udp.setMulticastLoopbackMode(this.socketId, enabled, cb);
	};

	ChromeUdpSocket.prototype.getJoinedGroups = function(cb) {
		chrome.sockets.udp.getJoinedGroups(this.socketId, cb);
	};

	// TCP SERVER
	var ChromeTcpServerSocket = function(socketId) {
		ChromeSocket.call(this, 'tcpServer');
		this.socketId = socketId;
	};
	ChromeTcpServerSocket.prototype = Object.create(ChromeSocket.prototype);

	ChromeTcpServerSocket.prototype.listen = function(host, port, cb) {
		chrome.sockets.tcpServer.listen(this.socketId, host, port, cb);
	};

})(window);<|endoftext|>