<commit_before>import Polymer from './polymer';

import hass from './util/home-assistant-js-instance';

import nuclearObserver from './util/bound-nuclear-behavior';
import validateAuth from './util/validate-auth';

require('./layouts/login-form');
require('./layouts/home-assistant-main');

const {
  localStoragePreferences,
  navigationActions,
  reactor,
  startLocalStoragePreferencesSync,
  syncGetters,
} = hass;

export default new Polymer({
  is: 'home-assistant',

  hostAttributes: {
    auth: null,
    icons: null,
  },

  behaviors: [nuclearObserver],

  properties: {
    auth: {
      type: String,
    },
    icons: {
      type: String,
    },
    dataLoaded: {
      type: Boolean,
      bindNuclear: syncGetters.isDataLoaded,
    },
    iconsLoaded: {
      type: Boolean,
      value: false,
    },
    loaded: {
      type: Boolean,
      computed: 'computeLoaded(dataLoaded, iconsLoaded)',
    },
  },

  computeLoaded(dataLoaded, iconsLoaded) {
    return dataLoaded && iconsLoaded;
  },

  computeForceShowLoading(dataLoaded, iconsLoaded) {
    return dataLoaded && !iconsLoaded;
  },

  loadIcons() {
    // If the import fails, we'll try to import again, must be a server glitch
    // Since HTML imports only resolve once, we import another url.
    const success = () => this.iconsLoaded = true;
    this.importHref(`/static/mdi-${this.icons}.html`,
                    success,
                    () => this.importHref(`/static/mdi.html`, success, success));
  },

  created() {
    this.registerServiceWorker();
  },

  attached() {
    // remove the HTML init message
    const initMsg = document.getElementById('init');
    initMsg.parentElement.removeChild(initMsg);
  },

  ready() {
    reactor.batch(() => {
      // if auth was given, tell the backend
      if (this.auth) {
        validateAuth(this.auth, false);
      } else if (localStoragePreferences.authToken) {
        validateAuth(localStoragePreferences.authToken, true);
      }
      navigationActions.showSidebar(localStoragePreferences.showSidebar);
    });

    startLocalStoragePreferencesSync();

    this.loadIcons();
  },

  registerServiceWorker() {
    if (!('serviceWorker' in navigator)) {
      return;
    }

    navigator.serviceWorker.register('./service_worker.js').then(reg => {
      console.log('Service Worker registration succeeded.');
    }).catch(error => {
      console.log(`Service Worker registration failed: ${error}`);
    });
  },
});
<commit_msg>Fix lint issues<commit_after>import Polymer from './polymer';

import hass from './util/home-assistant-js-instance';

import nuclearObserver from './util/bound-nuclear-behavior';
import validateAuth from './util/validate-auth';

require('./layouts/login-form');
require('./layouts/home-assistant-main');

const {
  localStoragePreferences,
  navigationActions,
  reactor,
  startLocalStoragePreferencesSync,
  syncGetters,
} = hass;

export default new Polymer({
  is: 'home-assistant',

  hostAttributes: {
    auth: null,
    icons: null,
  },

  behaviors: [nuclearObserver],

  properties: {
    auth: {
      type: String,
    },
    icons: {
      type: String,
    },
    dataLoaded: {
      type: Boolean,
      bindNuclear: syncGetters.isDataLoaded,
    },
    iconsLoaded: {
      type: Boolean,
      value: false,
    },
    loaded: {
      type: Boolean,
      computed: 'computeLoaded(dataLoaded, iconsLoaded)',
    },
  },

  computeLoaded(dataLoaded, iconsLoaded) {
    return dataLoaded && iconsLoaded;
  },

  computeForceShowLoading(dataLoaded, iconsLoaded) {
    return dataLoaded && !iconsLoaded;
  },

  loadIcons() {
    // If the import fails, we'll try to import again, must be a server glitch
    // Since HTML imports only resolve once, we import another url.
    const success = () => this.iconsLoaded = true;
    this.importHref(`/static/mdi-${this.icons}.html`,
                    success,
                    () => this.importHref(`/static/mdi.html`, success, success));
  },

  created() {
    this.registerServiceWorker();
  },

  attached() {
    // remove the HTML init message
    const initMsg = document.getElementById('init');
    initMsg.parentElement.removeChild(initMsg);
  },

  ready() {
    reactor.batch(() => {
      // if auth was given, tell the backend
      if (this.auth) {
        validateAuth(this.auth, false);
      } else if (localStoragePreferences.authToken) {
        validateAuth(localStoragePreferences.authToken, true);
      }
      navigationActions.showSidebar(localStoragePreferences.showSidebar);
    });

    startLocalStoragePreferencesSync();

    this.loadIcons();
  },

  registerServiceWorker() {
    if (!('serviceWorker' in navigator)) {
      return;
    }

    navigator.serviceWorker.register('./service_worker.js');
  },
});
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import zhCN from './zh-CN'
import enUS from './en-US'

const languages = {
  'zh-CN': zhCN,
  'en-US': enUS
}

const global = 'Global'

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}

/**
 * 国际化高阶组件
 *
 * @export
 * @param {string} key 组件名称
 * @returns
 */
export default function translate(key) {
  return (WrappedComponent) => {
    class TranslationComponent extends React.Component {
      render() {
        const currentLanguage = this.context.currentLanguage
        // 获取全局词条
        const globalMessages = languages[currentLanguage][global]
        // 获取组件词条
        const partialMessages = key ? languages[currentLanguage][key] : {}

        // 合并词条
        // 如有重复, 组件词条会覆盖全局词条
        const messages = {
          ...globalMessages,
          ...partialMessages
        }
        return <WrappedComponent {...this.props} {...this.state} messages={messages} />
      }
    }

    TranslationComponent.displayName = `Translated(${getDisplayName(WrappedComponent)})`

    TranslationComponent.contextTypes = {
      currentLanguage: PropTypes.string
    }

    return TranslationComponent
  }
}
<commit_msg>chore: 删除无意义的注释<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import zhCN from './zh-CN'
import enUS from './en-US'

const languages = {
  'zh-CN': zhCN,
  'en-US': enUS
}

const global = 'Global'

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}

/**
 * 国际化高阶组件
 *
 * @export
 * @param {string} key 组件名称
 * @returns
 */
export default function translate(key) {
  return (WrappedComponent) => {
    class TranslationComponent extends React.Component {
      render() {
        const currentLanguage = this.context.currentLanguage
        const globalMessages = languages[currentLanguage][global]
        const partialMessages = key ? languages[currentLanguage][key] : {}

        const messages = {
          ...globalMessages,
          ...partialMessages
        }
        return <WrappedComponent {...this.props} {...this.state} messages={messages} />
      }
    }

    TranslationComponent.displayName = `Translated(${getDisplayName(WrappedComponent)})`

    TranslationComponent.contextTypes = {
      currentLanguage: PropTypes.string
    }

    return TranslationComponent
  }
}
<|endoftext|>


<commit_before>// TODO: stop reflresh loop when closing connection

'use strict'

var AbstractTransport = require('./abstract')
var turn = require('turn-js')
var TurnStream = require('./streams/turn')
var TurnTransports = turn.transports
var util = require('util')

var debug = require('debug')
var debugLog = debug('1tp:transports:turn')
var errorLog = debug('1tp:transports:turn:error')

/**
 * Turn transport
 *
 * @constructor
 * @fires TurnTransport#listening
 * @fires TurnTransport#connection
 * @fires TurnTransport#connect
 * @fires TurnTransport#error
 * @fires TurnTransport#signaling
 */
function TurnTransport (args) {
  if (!(this instanceof TurnTransport)) {
    return new TurnTransport(args)
  }
  // verify args
  if (
    args.turnServer === undefined ||
    args.turnPort === undefined ||
    args.turnUsername === undefined ||
    args.turnPassword === undefined
  ) {
    var turnArgsError = 'incorrect args: turnServer and/or turnPort and/or turnUsername and/or turnPassword are undefined'
    errorLog(turnArgsError)
    throw new Error(turnArgsError)
  }
  if (args.signaling === undefined) {
    var signalingArgsError = 'incorrect args: signaling is undefined'
    errorLog(signalingArgsError)
    throw new Error(signalingArgsError)
  }
  AbstractTransport.call(this)
  // store args
  this._turnProtocol = (args.turnProtocol === undefined) ? new TurnTransports.UDP() : args.turnProtocol
  this._turn = turn(
    args.turnServer,
    args.turnPort,
    args.turnUsername,
    args.turnPassword,
    this._turnProtocol
  )
  this._signaling = args.signaling
  // accept new incoming connections
  this._acceptIncomingConnections = true
  // done
  debugLog('created turn transport')
}

// Inherit from abstract transport
util.inherits(TurnTransport, AbstractTransport)

TurnTransport.TIMEOUT_MARGIN = 10000

TurnTransport.prototype.transportType = function () {
  return 'turn'
}

TurnTransport.prototype.connectTimeout = function () {
  return 2000
}

TurnTransport.prototype.listen = function (listeningInfo, onSuccess, onFailure) {
  var requestedRegistrationInfo
  if (listeningInfo !== undefined) {
    // verify listeningInfo
    if (listeningInfo.transportType !== this.transportType()) {
      var transportTypeError = 'incorrect listeningInfo: unexpected transportType -- ignoring request'
      errorLog(transportTypeError)
      this._error(transportTypeError, onFailure)
      return
    }
    if (listeningInfo.transportInfo === undefined) {
      var transportInfoUndefined = 'incorrect connectionInfo: transportInfo is undefined'
      errorLog(transportInfoUndefined)
      this._error(transportInfoUndefined, onFailure)
      return
    }
    requestedRegistrationInfo = listeningInfo.transportInfo
  }
  // register callback with signaling connector
  var self = this
  this._signaling.registerP(this._onSignalingMessage.bind(this), requestedRegistrationInfo)
    .then(function (actualRegistrationInfo) {
      var myConnectionInfo = {
        transportType: self.transportType(),
        transportInfo: actualRegistrationInfo
      }
      self._myConnectionInfo = myConnectionInfo
      // send 'listening' event
      self._fireListeningEvent(myConnectionInfo, onSuccess)
    })
    .catch(function (error) {
      errorLog(error)
      self._error(error, onFailure)
    })
}

TurnTransport.prototype.connect = function (peerConnectionInfo, onSuccess, onFailure) {
  debugLog('connect to ' + JSON.stringify(peerConnectionInfo))
  // verify peerConnectionInfo
  if (peerConnectionInfo.transportType !== this.transportType()) {
    var transportTypeError = 'incorrect connectionInfo: unexpected transportType -- ignoring request'
    errorLog(transportTypeError)
    this._error(transportTypeError, onFailure)
    return
  }
  if (peerConnectionInfo.transportInfo === undefined) {
    var transportInfoUndefined = 'incorrect connectionInfo: transportInfo is undefined'
    errorLog(transportInfoUndefined)
    this._error(transportInfoUndefined, onFailure)
    return
  }
  // register callback with signaling connector
  var self = this
  this._signaling.registerP(this._onSignalingMessage.bind(this))
    .then(function (actualRegistrationInfo) {
      var myConnectionInfo = {
        transportType: self.transportType(),
        transportInfo: actualRegistrationInfo
      }
      self._myConnectionInfo = myConnectionInfo
      return self._turn.allocateP()
    })
    .then(function (allocateReply) {
      self._srflxAddress = allocateReply.mappedAddress
      self._relayAddress = allocateReply.relayedAddress
      if (allocateReply.lifetime !== undefined) {
        self._allocationLifetime = allocateReply.lifetime
      }
      debugLog('srflx address = ' + self._srflxAddress.address + ':' + self._srflxAddress.port)
      debugLog('relay address = ' + self._relayAddress.address + ':' + self._relayAddress.port)
      debugLog('lifetime = ' + self._allocationLifetime)
      // start refresh interval
      self._startRefreshLoop()
      // store callbacks for later use
      self._connectOnSuccess = onSuccess
      self._connectOnFailure = onFailure
      self._peerConnectionInfo = peerConnectionInfo
      // send connect request to peer
      var signalingDestination = peerConnectionInfo.transportInfo
      var signalingMessage = {
        sender: self._myConnectionInfo.transportInfo,
        operationType: 'connect',
        operationContent: {
          srflxAddress: self._srflxAddress,
          relayAddress: self._relayAddress
        }
      }
      return self._signaling.sendP(signalingMessage, signalingDestination)
    })
    .then(function () {
      debugLog("'connect' message sent")
    })
    .catch(function (error) {
      errorLog(error)
      self._error(error, onFailure)
    })
}

TurnTransport.prototype.blockIncomingConnections = function () {
  this._acceptIncomingConnections = false
}

TurnTransport.prototype._onSignalingMessage = function (message) {
  debugLog('receiving signaling message ' + JSON.stringify(message))
  var operationType = message.operationType
  if (operationType === undefined) {
    var undefinedOperationTypeError = 'incorrect signaling message: undefined operationType -- ignoring request'
    errorLog(undefinedOperationTypeError)
    this._error(undefinedOperationTypeError)
    return
  }
  switch (operationType) {
    case 'connect':
      if (this._acceptIncomingConnections) {
        this._onConnectRequest(message)
      } else {
        debugLog('not accepting new connections -- dropping connect request on the floor')
      }
      break
    case 'ready':
      this._onReadyMessage(message)
      break
    default:
      var unknownOperationTypeError = "incorrect signaling message: don't know how to process operationType " + operationType + ' -- ignoring request'
      errorLog(unknownOperationTypeError)
      this._error(unknownOperationTypeError)
  }
}

TurnTransport.prototype._onConnectRequest = function (message) {
  var operationContent = message.operationContent
  if (operationContent === undefined) {
    var undefinedOperationContentError = 'incorrect signaling message: undefined operationContent -- ignoring request'
    errorLog(undefinedOperationContentError)
    this._error(undefinedOperationContentError)
    return
  }
  var sender = message.sender
  if (sender === undefined) {
    var undefinedSenderError = 'incorrect signaling message: undefined sender -- ignoring request'
    errorLog(undefinedSenderError)
    this._error(undefinedSenderError)
    return
  }
  var self = this
  // first allocate TURN resource
  this._turn.allocateP()
    .then(function (allocateReply) {
      self._srflxAddress = allocateReply.mappedAddress
      self._relayAddress = allocateReply.relayedAddress
      if (allocateReply.lifetime !== undefined) {
        self._allocationLifetime = allocateReply.lifetime
      }
      debugLog('srflx address = ' + self._srflxAddress.address + ':' + self._srflxAddress.port)
      debugLog('relay address = ' + self._relayAddress.address + ':' + self._relayAddress.port)
      debugLog('lifetime = ' + self._allocationLifetime)
      // start refresh interval
      self._startRefreshLoop()
      // then create permission for peer to reach me
      return self._turn.createPermissionP(operationContent.relayAddress.address)
    })
    .then(function () {
      // create duplex stream
      var peerConnectionInfo = {
        mappedAddress: operationContent.srflxAddress,
        relayedAddress: operationContent.relayAddress
      }
      var stream = new TurnStream(peerConnectionInfo, self._turn)
      // fire connection event
      self._fireConnectionEvent(stream, self, peerConnectionInfo)
      // send ready response to peer
      var signalingDestination = sender
      var signalingMessage = {
        sender: self._myConnectionInfo.transportInfo,
        operationType: 'ready',
        operationContent: {
          srflxAddress: self._srflxAddress,
          relayAddress: self._relayAddress
        }
      }
      return self._signaling.sendP(signalingMessage, signalingDestination)
    })
    .then(function () {
      debugLog("'ready' message sent")
    })
    .catch(function (error) {
      errorLog(error)
      self._error(error)
    })
}

TurnTransport.prototype._onReadyMessage = function (message) {
  var operationContent = message.operationContent
  if (operationContent === undefined) {
    var undefinedOperationContentError = 'incorrect signaling message: undefined operationContent -- ignoring request'
    errorLog(undefinedOperationContentError)
    this._error(undefinedOperationContentError)
    return
  }
  var sender = message.sender
  if (sender === undefined) {
    var undefinedSenderError = 'incorrect signaling message: undefined sender -- ignoring request'
    errorLog(undefinedSenderError)
    this._error(undefinedSenderError)
    return
  }
  var self = this
  // create permission for peer to reach me
  this._turn.createPermissionP(operationContent.relayAddress.address)
    .then(function () {
      // create duplex stream
      var peerConnectionInfo = {
        mappedAddress: operationContent.srflxAddress,
        relayedAddress: operationContent.relayAddress
      }
      // create duplex stream
      var stream = new TurnStream(peerConnectionInfo, self._turn)
      // fire connect event
      self._fireConnectEvent(stream, self._peerConnectionInfo, self._connectOnSuccess)
    })
    .catch(function (error) {
      errorLog(error)
      self._error(error)
    })
}

TurnTransport.prototype._startRefreshLoop = function () {
  var self = this
  // start refresh timer if allocation lifetime is specified
  if (this._allocationLifetime !== undefined) {
    debugLog('activating refresh loop -- lifetime was set to ' + this._allocationLifetime)
    this._startRefreshTimer(this._allocationLifetime)
    return
  }
  // otherwise execute refresh operation using the default TURN allocation timeout to retrieve actual lifetime
  this._turn.refreshP(this._turn.DEFAULT_ALLOCATION_LIFETIME)
    .then(function (lifetime) {
      debugLog('activating refresh loop -- allocation lifetime ' + lifetime)
      self._allocationLifetime = lifetime
      self._startRefreshTimer(self._allocationLifetime)
    })
    .catch(function (error) {
      errorLog(error)
      self._error(error)
    })
}

TurnTransport.prototype._startRefreshTimer = function (lifetime) {
  var self = this
  this._refreshTimer = setInterval(function () {
    self._turn.refreshP(lifetime)
      .then(function (duration) {
        debugLog('executed refresh operation, retrieving lifetime ' + duration)
      })
      .catch(function (error) {
        self._stopRefreshTimer()
        var errorMsg = 'error while sending TURN refresh message: ' + error
        errorLog(errorMsg)
        self._error(errorMsg)
      })
  }, lifetime * 1000 - TurnTransport.TIMEOUT_MARGIN)
}

TurnTransport.prototype._stopRefreshTimer = function () {
  clearInterval(this._refreshTimer)
}

TurnTransport.prototype._startCreatePermissionTimer = function (address) {
  var self = this
  this._createPermissionTimer = setInterval(function () {
    self._turn.createPermissionP(address)
      .then(function () {
        debugLog('executed create permission refresh')
      })
      .catch(function (error) {
        self._stopCreatePermissionTimer()
        var errorMsg = 'error while refreshing TURN permission: ' + error
        errorLog(errorMsg)
        self._error(errorMsg)
      })
  }, self._turn.CREATE_PERMISSION_LIFETIME * 1000 - TurnTransport.TIMEOUT_MARGIN)
}

TurnTransport.prototype._stopCreatePermissionTimer = function () {
  clearInterval(this._createPermissionTimer)
}

TurnTransport.prototype._startChannelBindTimer = function (address, port, channel) {
  var self = this
  this._channelBindTimer = setInterval(function () {
    self._turn.bindChannelP(address, port, channel)
      .then(function (channel) {
        debugLog('executed bind channel refresh, retrieving channel ' + channel)
      })
      .catch(function (error) {
        self._stopChannelBindTimer()
        var errorMsg = 'failure while refreshing channel binding: ' + error
        errorLog(errorMsg)
        self._error(errorMsg)
      })
  }, self._turn.CHANNEL_BINDING_LIFETIME * 1000 - TurnTransport.TIMEOUT_MARGIN)
}

TurnTransport.prototype._stopChannelBindTimer = function () {
  clearInterval(this._channelBindTimer)
}

module.exports = TurnTransport
<commit_msg>integrated permission refresh timer<commit_after>// TODO: stop reflresh loop when closing connection

'use strict'

var AbstractTransport = require('./abstract')
var turn = require('turn-js')
var TurnStream = require('./streams/turn')
var TurnTransports = turn.transports
var util = require('util')

var debug = require('debug')
var debugLog = debug('1tp:transports:turn')
var errorLog = debug('1tp:transports:turn:error')

/**
 * Turn transport
 *
 * @constructor
 * @fires TurnTransport#listening
 * @fires TurnTransport#connection
 * @fires TurnTransport#connect
 * @fires TurnTransport#error
 * @fires TurnTransport#signaling
 */
function TurnTransport (args) {
  if (!(this instanceof TurnTransport)) {
    return new TurnTransport(args)
  }
  // verify args
  if (
    args.turnServer === undefined ||
    args.turnPort === undefined ||
    args.turnUsername === undefined ||
    args.turnPassword === undefined
  ) {
    var turnArgsError = 'incorrect args: turnServer and/or turnPort and/or turnUsername and/or turnPassword are undefined'
    errorLog(turnArgsError)
    throw new Error(turnArgsError)
  }
  if (args.signaling === undefined) {
    var signalingArgsError = 'incorrect args: signaling is undefined'
    errorLog(signalingArgsError)
    throw new Error(signalingArgsError)
  }
  AbstractTransport.call(this)
  // store args
  this._turnProtocol = (args.turnProtocol === undefined) ? new TurnTransports.UDP() : args.turnProtocol
  this._turn = turn(
    args.turnServer,
    args.turnPort,
    args.turnUsername,
    args.turnPassword,
    this._turnProtocol
  )
  this._signaling = args.signaling
  // accept new incoming connections
  this._acceptIncomingConnections = true
  // done
  debugLog('created turn transport')
}

// Inherit from abstract transport
util.inherits(TurnTransport, AbstractTransport)

TurnTransport.TIMEOUT_MARGIN = 10000

TurnTransport.prototype.transportType = function () {
  return 'turn'
}

TurnTransport.prototype.connectTimeout = function () {
  return 2000
}

TurnTransport.prototype.listen = function (listeningInfo, onSuccess, onFailure) {
  var requestedRegistrationInfo
  if (listeningInfo !== undefined) {
    // verify listeningInfo
    if (listeningInfo.transportType !== this.transportType()) {
      var transportTypeError = 'incorrect listeningInfo: unexpected transportType -- ignoring request'
      errorLog(transportTypeError)
      this._error(transportTypeError, onFailure)
      return
    }
    if (listeningInfo.transportInfo === undefined) {
      var transportInfoUndefined = 'incorrect connectionInfo: transportInfo is undefined'
      errorLog(transportInfoUndefined)
      this._error(transportInfoUndefined, onFailure)
      return
    }
    requestedRegistrationInfo = listeningInfo.transportInfo
  }
  // register callback with signaling connector
  var self = this
  this._signaling.registerP(this._onSignalingMessage.bind(this), requestedRegistrationInfo)
    .then(function (actualRegistrationInfo) {
      var myConnectionInfo = {
        transportType: self.transportType(),
        transportInfo: actualRegistrationInfo
      }
      self._myConnectionInfo = myConnectionInfo
      // send 'listening' event
      self._fireListeningEvent(myConnectionInfo, onSuccess)
    })
    .catch(function (error) {
      errorLog(error)
      self._error(error, onFailure)
    })
}

TurnTransport.prototype.connect = function (peerConnectionInfo, onSuccess, onFailure) {
  debugLog('connect to ' + JSON.stringify(peerConnectionInfo))
  // verify peerConnectionInfo
  if (peerConnectionInfo.transportType !== this.transportType()) {
    var transportTypeError = 'incorrect connectionInfo: unexpected transportType -- ignoring request'
    errorLog(transportTypeError)
    this._error(transportTypeError, onFailure)
    return
  }
  if (peerConnectionInfo.transportInfo === undefined) {
    var transportInfoUndefined = 'incorrect connectionInfo: transportInfo is undefined'
    errorLog(transportInfoUndefined)
    this._error(transportInfoUndefined, onFailure)
    return
  }
  // register callback with signaling connector
  var self = this
  this._signaling.registerP(this._onSignalingMessage.bind(this))
    .then(function (actualRegistrationInfo) {
      var myConnectionInfo = {
        transportType: self.transportType(),
        transportInfo: actualRegistrationInfo
      }
      self._myConnectionInfo = myConnectionInfo
      return self._turn.allocateP()
    })
    .then(function (allocateReply) {
      self._srflxAddress = allocateReply.mappedAddress
      self._relayAddress = allocateReply.relayedAddress
      if (allocateReply.lifetime !== undefined) {
        self._allocationLifetime = allocateReply.lifetime
      }
      debugLog('srflx address = ' + self._srflxAddress.address + ':' + self._srflxAddress.port)
      debugLog('relay address = ' + self._relayAddress.address + ':' + self._relayAddress.port)
      debugLog('lifetime = ' + self._allocationLifetime)
      // start refresh interval
      self._startRefreshLoop()
      // store callbacks for later use
      self._connectOnSuccess = onSuccess
      self._connectOnFailure = onFailure
      self._peerConnectionInfo = peerConnectionInfo
      // send connect request to peer
      var signalingDestination = peerConnectionInfo.transportInfo
      var signalingMessage = {
        sender: self._myConnectionInfo.transportInfo,
        operationType: 'connect',
        operationContent: {
          srflxAddress: self._srflxAddress,
          relayAddress: self._relayAddress
        }
      }
      return self._signaling.sendP(signalingMessage, signalingDestination)
    })
    .then(function () {
      debugLog("'connect' message sent")
    })
    .catch(function (error) {
      errorLog(error)
      self._error(error, onFailure)
    })
}

TurnTransport.prototype.blockIncomingConnections = function () {
  this._acceptIncomingConnections = false
}

TurnTransport.prototype._onSignalingMessage = function (message) {
  debugLog('receiving signaling message ' + JSON.stringify(message))
  var operationType = message.operationType
  if (operationType === undefined) {
    var undefinedOperationTypeError = 'incorrect signaling message: undefined operationType -- ignoring request'
    errorLog(undefinedOperationTypeError)
    this._error(undefinedOperationTypeError)
    return
  }
  switch (operationType) {
    case 'connect':
      if (this._acceptIncomingConnections) {
        this._onConnectRequest(message)
      } else {
        debugLog('not accepting new connections -- dropping connect request on the floor')
      }
      break
    case 'ready':
      this._onReadyMessage(message)
      break
    default:
      var unknownOperationTypeError = "incorrect signaling message: don't know how to process operationType " + operationType + ' -- ignoring request'
      errorLog(unknownOperationTypeError)
      this._error(unknownOperationTypeError)
  }
}

TurnTransport.prototype._onConnectRequest = function (message) {
  var connectRequest = message.operationContent
  if (connectRequest === undefined) {
    var undefinedOperationContentError = 'incorrect signaling message: undefined operationContent -- ignoring request'
    errorLog(undefinedOperationContentError)
    this._error(undefinedOperationContentError)
    return
  }
  var sender = message.sender
  if (sender === undefined) {
    var undefinedSenderError = 'incorrect signaling message: undefined sender -- ignoring request'
    errorLog(undefinedSenderError)
    this._error(undefinedSenderError)
    return
  }
  var self = this
  // first allocate TURN resource
  this._turn.allocateP()
    .then(function (allocateReply) {
      self._srflxAddress = allocateReply.mappedAddress
      self._relayAddress = allocateReply.relayedAddress
      if (allocateReply.lifetime !== undefined) {
        self._allocationLifetime = allocateReply.lifetime
      }
      debugLog('srflx address = ' + self._srflxAddress.address + ':' + self._srflxAddress.port)
      debugLog('relay address = ' + self._relayAddress.address + ':' + self._relayAddress.port)
      debugLog('lifetime = ' + self._allocationLifetime)
      // start refresh interval
      self._startRefreshLoop()
      // then create permission for peer to reach me
      return self._turn.createPermissionP(connectRequest.relayAddress.address)
    })
    .then(function () {
      // start permission refresh timer
      self._startCreatePermissionTimer(connectRequest.relayAddress.address)
      // create duplex stream
      var peerConnectionInfo = {
        mappedAddress: connectRequest.srflxAddress,
        relayedAddress: connectRequest.relayAddress
      }
      var stream = new TurnStream(peerConnectionInfo, self._turn)
      // fire connection event
      self._fireConnectionEvent(stream, self, peerConnectionInfo)
      // send ready response to peer
      var signalingDestination = sender
      var signalingMessage = {
        sender: self._myConnectionInfo.transportInfo,
        operationType: 'ready',
        operationContent: {
          srflxAddress: self._srflxAddress,
          relayAddress: self._relayAddress
        }
      }
      return self._signaling.sendP(signalingMessage, signalingDestination)
    })
    .then(function () {
      debugLog("'ready' message sent")
    })
    .catch(function (error) {
      errorLog(error)
      self._error(error)
    })
}

TurnTransport.prototype._onReadyMessage = function (message) {
  var connectRequest = message.operationContent
  if (connectRequest === undefined) {
    var undefinedOperationContentError = 'incorrect signaling message: undefined operationContent -- ignoring request'
    errorLog(undefinedOperationContentError)
    this._error(undefinedOperationContentError)
    return
  }
  var sender = message.sender
  if (sender === undefined) {
    var undefinedSenderError = 'incorrect signaling message: undefined sender -- ignoring request'
    errorLog(undefinedSenderError)
    this._error(undefinedSenderError)
    return
  }
  var self = this
  // create permission for peer to reach me
  this._turn.createPermissionP(connectRequest.relayAddress.address)
    .then(function () {
      // start permission refresh timer
      self._startCreatePermissionTimer(connectRequest.relayAddress.address)
      // create duplex stream
      var peerConnectionInfo = {
        mappedAddress: connectRequest.srflxAddress,
        relayedAddress: connectRequest.relayAddress
      }
      // create duplex stream
      var stream = new TurnStream(peerConnectionInfo, self._turn)
      // fire connect event
      self._fireConnectEvent(stream, self._peerConnectionInfo, self._connectOnSuccess)
    })
    .catch(function (error) {
      errorLog(error)
      self._error(error)
    })
}

TurnTransport.prototype._startRefreshLoop = function () {
  var self = this
  // start refresh timer if allocation lifetime is specified
  if (this._allocationLifetime !== undefined) {
    debugLog('activating refresh loop -- lifetime was set to ' + this._allocationLifetime)
    this._startRefreshTimer(this._allocationLifetime)
    return
  }
  // otherwise execute refresh operation using the default TURN allocation timeout to retrieve actual lifetime
  this._turn.refreshP(this._turn.DEFAULT_ALLOCATION_LIFETIME)
    .then(function (lifetime) {
      debugLog('activating refresh loop -- allocation lifetime ' + lifetime)
      self._allocationLifetime = lifetime
      self._startRefreshTimer(self._allocationLifetime)
    })
    .catch(function (error) {
      errorLog(error)
      self._error(error)
    })
}

TurnTransport.prototype._startRefreshTimer = function (lifetime) {
  var self = this
  this._refreshTimer = setInterval(function () {
    self._turn.refreshP(lifetime)
      .then(function (duration) {
        debugLog('executed refresh operation, retrieving lifetime ' + duration)
      })
      .catch(function (error) {
        self._stopRefreshTimer()
        var errorMsg = 'error while sending TURN refresh message: ' + error
        errorLog(errorMsg)
        self._error(errorMsg)
      })
  }, lifetime * 1000 - TurnTransport.TIMEOUT_MARGIN)
}

TurnTransport.prototype._stopRefreshTimer = function () {
  clearInterval(this._refreshTimer)
}

TurnTransport.prototype._startCreatePermissionTimer = function (address) {
  var self = this
  this._createPermissionTimer = setInterval(function () {
    self._turn.createPermissionP(address)
      .then(function () {
        debugLog('executed create permission refresh')
      })
      .catch(function (error) {
        self._stopCreatePermissionTimer()
        var errorMsg = 'error while refreshing TURN permission: ' + error
        errorLog(errorMsg)
        self._error(errorMsg)
      })
  }, self._turn.CREATE_PERMISSION_LIFETIME * 1000 - TurnTransport.TIMEOUT_MARGIN)
}

TurnTransport.prototype._stopCreatePermissionTimer = function () {
  clearInterval(this._createPermissionTimer)
}

TurnTransport.prototype._startChannelBindTimer = function (address, port, channel) {
  var self = this
  this._channelBindTimer = setInterval(function () {
    self._turn.bindChannelP(address, port, channel)
      .then(function (channel) {
        debugLog('executed bind channel refresh, retrieving channel ' + channel)
      })
      .catch(function (error) {
        self._stopChannelBindTimer()
        var errorMsg = 'failure while refreshing channel binding: ' + error
        errorLog(errorMsg)
        self._error(errorMsg)
      })
  }, self._turn.CHANNEL_BINDING_LIFETIME * 1000 - TurnTransport.TIMEOUT_MARGIN)
}

TurnTransport.prototype._stopChannelBindTimer = function () {
  clearInterval(this._channelBindTimer)
}

module.exports = TurnTransport
<|endoftext|>


<commit_before>var quiz = {
  question: document.getElementById('question'),
  answer: document.getElementById('answer'),

  getRandomInt: function(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  },
  check: function() {
    attempt = answer.value;
    return attempt == solution;
  },
  grade: function() {
    questions = document.getElementsByClassName('quiz-question');
    previousGrade = document.getElementById('grade');
    if (previousGrade) {
      previousGrade.id = '';
      previousGrade.innerHTML = " .";
      previousGrade.style.color = 'gray';
    }
    grade = document.createElement("span");
    grade.id = 'grade';
    var spantext = '';
    if (quiz.check()) {
      quiz.clearMarks;
      quiz.addToHistory(questions[0]);
      quiz.init();
    } else {
      grade = quiz.markWrong(grade);
    }
    questions[0].appendChild(grade);
  },
  clearMarks: function() {
    marks = document.getElementsByClassName('mark');
    for (var i = marks.length - 1; i >= 0; i--) {
      marks[i].parentNode.removeChild(marks[i]);
    };
  },
  markWrong: function(grade) {
    // highlight the incorrect answer for modification
    answer.select();
    // mark with red x
    grade.innerHTML = " x";
    grade.style.color = 'red';
    grade.className = 'mark';
    return grade;
  },
  addToHistory: function(problem) {
    var history = document.getElementById('history');
    var item = document.createElement('li');
    var solvedProblem = document.createElement('span');
    solvedProblem.innerHTML = question.innerHTML + solution;
    item.appendChild(solvedProblem);
    history.appendChild(item);
  },
  init: function () {
    // pick 2 numbers and display them in the DOM
    num1 = quiz.getRandomInt(0, 8);
    num2 = quiz.getRandomInt(0, 10);

    answer.value = '';

    question.innerHTML = num1 + ' × ' + num2 + ' = ';

    // work out the solution and store it for later
    solution = num1 * num2;

    // attach behaviour to check button
    button = document.getElementById('check');
    button.addEventListener('click', quiz.grade, false);
  }
}

quiz.addEvent(window, 'load', quiz.init());<commit_msg>rename grade function to avoid clash<commit_after>var quiz = {
  question: document.getElementById('question'),
  answer: document.getElementById('answer'),

  getRandomInt: function(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  },
  check: function() {
    attempt = answer.value;
    return attempt == solution;
  },
  grader: function() {
    var questions = document.getElementsByClassName('quiz-question');
    previousGrade = document.getElementById('grade');
    if (previousGrade) {
      previousGrade.id = '';
      previousGrade.innerHTML = " .";
      previousGrade.style.color = 'gray';
    } else {
      grade = document.createElement("span");
      grade.id = 'grade';
    }
    var spantext = '';
    if (quiz.check()) {
      quiz.clearMarks;
      quiz.addToHistory(questions[0]);
      quiz.init();
    } else {
      grade = quiz.markWrong(grade);
    }
    questions[0].appendChild(grade);
  },
  clearMarks: function() {
    marks = document.getElementsByClassName('mark');
    grade.parentNode.removeChild(grade);
    for (var i = marks.length - 1; i >= 0; i--) {
      marks[i].parentNode.removeChild(marks[i]);
    };
  },
  markWrong: function(grade) {
    // highlight the incorrect answer for modification
    answer.select();
    // mark with red x
    grade.innerHTML = " x";
    grade.style.color = 'red';
    grade.className = 'mark';
    return grade;
  },
  addToHistory: function(problem) {
    var history = document.getElementById('history');
    var item = document.createElement('li');
    var solvedProblem = document.createElement('span');
    solvedProblem.innerHTML = question.innerHTML + solution;
    item.appendChild(solvedProblem);
    history.appendChild(item);
  },
  init: function () {
    // pick 2 numbers and display them in the DOM
    num1 = quiz.getRandomInt(0, 8);
    num2 = quiz.getRandomInt(0, 10);

    answer.value = '';

    question.innerHTML = num1 + ' × ' + num2 + ' = ';

    // work out the solution and store it for later
    solution = num1 * num2;

    // attach behaviour to check button
    button = document.getElementById('check');
    button.addEventListener('click', quiz.grader, false);
  }
}

quiz.addEvent(window, 'load', quiz.init());<|endoftext|>


<commit_before>const _ = require('lodash');
const nql = require('@nexes/nql');
const debug = require('@tryghost/debug')('services:url:generator');
const localUtils = require('../../../shared/url-utils');

// @TODO: merge with filter plugin
const EXPANSIONS = [{
    key: 'author',
    replacement: 'authors.slug'
}, {
    key: 'tags',
    replacement: 'tags.slug'
}, {
    key: 'tag',
    replacement: 'tags.slug'
}, {
    key: 'authors',
    replacement: 'authors.slug'
}, {
    key: 'primary_tag',
    replacement: 'primary_tag.slug'
}, {
    key: 'primary_author',
    replacement: 'primary_author.slug'
}];

/**
 * The UrlGenerator class is responsible to generate urls based on a router's conditions.
 * It is the component which sits between routers and resources and connects them together.
 * Each url generator can own resources. Each resource can only be owned by one generator,
 * because each resource can only live on one url at a time.
 *
 * Each router is represented by a url generator.
 */
class UrlGenerator {
    constructor(router, queue, resources, urls, position) {
        this.router = router;
        this.queue = queue;
        this.urls = urls;
        this.resources = resources;
        this.uid = position;

        debug('constructor', this.toString());

        // CASE: routers can define custom filters, but not required.
        if (this.router.getFilter()) {
            this.filter = this.router.getFilter();
            this.nql = nql(this.filter, {
                expansions: EXPANSIONS,
                transformer: nql.utils.mapKeyValues({
                    key: {
                        from: 'page',
                        to: 'type'
                    },
                    values: [{
                        from: false,
                        to: 'post'
                    }, {
                        from: true,
                        to: 'page'
                    }]
                })
            });
            debug('filter', this.filter);
        }

        this._listeners();
    }

    /**
     * @NOTE: currently only used if the permalink setting changes and it's used for this url generator.
     * @TODO: https://github.com/TryGhost/Ghost/issues/10699
     */
    regenerateResources() {
        const myResources = this.urls.getByGeneratorId(this.uid);

        myResources.forEach((object) => {
            this.urls.removeResourceId(object.resource.data.id);
            object.resource.release();
            this._try(object.resource);
        });
    }

    /**
     * @description Helper function to register listeners for each url generator instance.
     * @private
     */
    _listeners() {
        /**
         * Listen on two events:
         *
         * - init: bootstrap or url reset
         * - added: resource was added to the database
         */
        this.queue.register({
            event: 'init',
            tolerance: 100
        }, this._onInit.bind(this));

        this.queue.register({
            event: 'added'
        }, this._onAdded.bind(this));
    }

    /**
     * @description Listener which get's called when the resources were fully fetched from the database.
     *
     * Each url generator will be called and can try to own resources now.
     *
     * @private
     */
    _onInit() {
        debug('_onInit', this.router.getResourceType());

        // @NOTE: get the resources of my type e.g. posts.
        const resources = this.resources.getAllByType(this.router.getResourceType());

        debug(resources.length);

        _.each(resources, (resource) => {
            this._try(resource);
        });
    }

    /**
     * @description Listener which get's called when a resource was added on runtime.
     * @param {String} event
     * @private
     */
    _onAdded(event) {
        debug('onAdded', this.toString());

        // CASE: you are type "pages", but the incoming type is "users"
        if (event.type !== this.router.getResourceType()) {
            return;
        }

        const resource = this.resources.getByIdAndType(event.type, event.id);

        this._try(resource);
    }

    /**
     * @description Try to own a resource and generate it's url if so.
     * @param {import('./Resource')} resource - instance of the Resource class
     * @returns {boolean}
     * @private
     */
    _try(resource) {
        /**
         * CASE: another url generator has taken this resource already.
         *
         * We have to remember that, because each url generator can generate a different url
         * for a resource. So we can't directly check `this.urls.getUrl(url)`.
         */
        if (resource.isReserved()) {
            return false;
        }

        const url = this._generateUrl(resource);

        // CASE 1: route has no custom filter, it will own the resource for sure
        // CASE 2: find out if my filter matches the resource
        if ((!this.filter) || (this.nql.queryJSON(resource.data))) {
            this.urls.add({
                url: url,
                generatorId: this.uid,
                resource: resource
            });

            resource.reserve();
            this._resourceListeners(resource);
            return true;
        } else {
            return false;
        }
    }

    /**
     * @description Generate url based on the permlink configuration of the target router.
     *
     * @NOTE We currently generate relative urls (https://github.com/TryGhost/Ghost/commit/7b0d5d465ba41073db0c3c72006da625fa11df32).
     */
    _generateUrl(resource) {
        const permalink = this.router.getPermalinks().getValue();
        return localUtils.replacePermalink(permalink, resource.data);
    }

    /**
     * @description Helper function to register resource listeners.
     *
     * I want to know if my resources changes.
     *
     * If the owned resource get's updated, we simply release/free the resource and push it back to the queue.
     * This is the easiest, less error prone implementation.
     *
     * Imagine you have two collections: `featured:true` and `page:false`.
     * If a published post status get's featured and you have not explicitly defined `featured:false`, we wouldn't
     * be able to figure out if this resource still belongs to me, because the filter still matches.
     */
    _resourceListeners(resource) {
        const onUpdate = (updatedResource) => {
            // 1. remove old resource
            this.urls.removeResourceId(updatedResource.data.id);

            // 2. free resource, the url <-> resource connection no longer exists
            updatedResource.release();

            // 3. post has the change to get owned from a different collection again
            debug('put back in queue', updatedResource.data.id);

            this.queue.start({
                event: 'added',
                action: 'added:' + resource.data.id,
                eventData: {
                    id: resource.data.id,
                    type: this.router.getResourceType()
                }
            });
        };

        const onRemoved = (removedResource) => {
            this.urls.removeResourceId(removedResource.data.id);
            removedResource.release();
        };

        resource.removeAllListeners();
        resource.addListener('updated', onUpdate.bind(this));
        resource.addListener('removed', onRemoved.bind(this));
    }

    /**
     * @description Figure out if this url generator own's a resource id.
     * @param {String} id
     * @returns {boolean}
     */
    hasId(id) {
        const existingUrl = this.urls.getByResourceId(id);

        if (existingUrl && existingUrl.generatorId === this.uid) {
            return true;
        }

        return false;
    }

    /**
     * @description Get all urls of this url generator.
     * @returns {Array}
     */
    getUrls() {
        return this.urls.getByGeneratorId(this.uid);
    }

    /**
     * @description Override of `toString`
     * @returns {string}
     */
    toString() {
        return this.router.toString();
    }
}

module.exports = UrlGenerator;
<commit_msg>Added JSDocs to the UrlGenerator class<commit_after>const _ = require('lodash');
const nql = require('@nexes/nql');
const debug = require('@tryghost/debug')('services:url:generator');
const localUtils = require('../../../shared/url-utils');

// @TODO: merge with filter plugin
const EXPANSIONS = [{
    key: 'author',
    replacement: 'authors.slug'
}, {
    key: 'tags',
    replacement: 'tags.slug'
}, {
    key: 'tag',
    replacement: 'tags.slug'
}, {
    key: 'authors',
    replacement: 'authors.slug'
}, {
    key: 'primary_tag',
    replacement: 'primary_tag.slug'
}, {
    key: 'primary_author',
    replacement: 'primary_author.slug'
}];

/**
 * The UrlGenerator class is responsible to generate urls based on a router's conditions.
 * It is the component which sits between routers and resources and connects them together.
 * Each url generator can own resources. Each resource can only be owned by one generator,
 * because each resource can only live on one url at a time.
 *
 * Each router is represented by a url generator.
 */
class UrlGenerator {
    /**
     *
     * @param {Object} router instance of a frontend Routes (e.g. CollectionRouter, PreviewRouter)
     * @param {Object} queue instance of the backend Queue
     * @param {Object} resources instance of the backend Resources
     * @param {Object} urls instance of the backend URLs (used to store the urls)
     * @param {Number} position an ID of the generator
     */
    constructor(router, queue, resources, urls, position) {
        this.router = router;
        this.queue = queue;
        this.urls = urls;
        this.resources = resources;
        this.uid = position;

        debug('constructor', this.toString());

        // CASE: routers can define custom filters, but not required.
        if (this.router.getFilter()) {
            this.filter = this.router.getFilter();
            this.nql = nql(this.filter, {
                expansions: EXPANSIONS,
                transformer: nql.utils.mapKeyValues({
                    key: {
                        from: 'page',
                        to: 'type'
                    },
                    values: [{
                        from: false,
                        to: 'post'
                    }, {
                        from: true,
                        to: 'page'
                    }]
                })
            });
            debug('filter', this.filter);
        }

        this._listeners();
    }

    /**
     * @NOTE: currently only used if the permalink setting changes and it's used for this url generator.
     * @TODO: https://github.com/TryGhost/Ghost/issues/10699
     */
    regenerateResources() {
        const myResources = this.urls.getByGeneratorId(this.uid);

        myResources.forEach((object) => {
            this.urls.removeResourceId(object.resource.data.id);
            object.resource.release();
            this._try(object.resource);
        });
    }

    /**
     * @description Helper function to register listeners for each url generator instance.
     * @private
     */
    _listeners() {
        /**
         * Listen on two events:
         *
         * - init: bootstrap or url reset
         * - added: resource was added to the database
         */
        this.queue.register({
            event: 'init',
            tolerance: 100
        }, this._onInit.bind(this));

        this.queue.register({
            event: 'added'
        }, this._onAdded.bind(this));
    }

    /**
     * @description Listener which get's called when the resources were fully fetched from the database.
     *
     * Each url generator will be called and can try to own resources now.
     *
     * @private
     */
    _onInit() {
        debug('_onInit', this.router.getResourceType());

        // @NOTE: get the resources of my type e.g. posts.
        const resources = this.resources.getAllByType(this.router.getResourceType());

        debug(resources.length);

        _.each(resources, (resource) => {
            this._try(resource);
        });
    }

    /**
     * @description Listener which get's called when a resource was added on runtime.
     * @param {String} event
     * @private
     */
    _onAdded(event) {
        debug('onAdded', this.toString());

        // CASE: you are type "pages", but the incoming type is "users"
        if (event.type !== this.router.getResourceType()) {
            return;
        }

        const resource = this.resources.getByIdAndType(event.type, event.id);

        this._try(resource);
    }

    /**
     * @description Try to own a resource and generate it's url if so.
     * @param {import('./Resource')} resource - instance of the Resource class
     * @returns {boolean}
     * @private
     */
    _try(resource) {
        /**
         * CASE: another url generator has taken this resource already.
         *
         * We have to remember that, because each url generator can generate a different url
         * for a resource. So we can't directly check `this.urls.getUrl(url)`.
         */
        if (resource.isReserved()) {
            return false;
        }

        const url = this._generateUrl(resource);

        // CASE 1: route has no custom filter, it will own the resource for sure
        // CASE 2: find out if my filter matches the resource
        if ((!this.filter) || (this.nql.queryJSON(resource.data))) {
            this.urls.add({
                url: url,
                generatorId: this.uid,
                resource: resource
            });

            resource.reserve();
            this._resourceListeners(resource);
            return true;
        } else {
            return false;
        }
    }

    /**
     * @description Generate url based on the permlink configuration of the target router.
     *
     * @NOTE We currently generate relative urls (https://github.com/TryGhost/Ghost/commit/7b0d5d465ba41073db0c3c72006da625fa11df32).
     */
    _generateUrl(resource) {
        const permalink = this.router.getPermalinks().getValue();
        return localUtils.replacePermalink(permalink, resource.data);
    }

    /**
     * @description Helper function to register resource listeners.
     *
     * I want to know if my resources changes.
     *
     * If the owned resource get's updated, we simply release/free the resource and push it back to the queue.
     * This is the easiest, less error prone implementation.
     *
     * Imagine you have two collections: `featured:true` and `page:false`.
     * If a published post status get's featured and you have not explicitly defined `featured:false`, we wouldn't
     * be able to figure out if this resource still belongs to me, because the filter still matches.
     */
    _resourceListeners(resource) {
        const onUpdate = (updatedResource) => {
            // 1. remove old resource
            this.urls.removeResourceId(updatedResource.data.id);

            // 2. free resource, the url <-> resource connection no longer exists
            updatedResource.release();

            // 3. post has the change to get owned from a different collection again
            debug('put back in queue', updatedResource.data.id);

            this.queue.start({
                event: 'added',
                action: 'added:' + resource.data.id,
                eventData: {
                    id: resource.data.id,
                    type: this.router.getResourceType()
                }
            });
        };

        const onRemoved = (removedResource) => {
            this.urls.removeResourceId(removedResource.data.id);
            removedResource.release();
        };

        resource.removeAllListeners();
        resource.addListener('updated', onUpdate.bind(this));
        resource.addListener('removed', onRemoved.bind(this));
    }

    /**
     * @description Figure out if this url generator own's a resource id.
     * @param {String} id
     * @returns {boolean}
     */
    hasId(id) {
        const existingUrl = this.urls.getByResourceId(id);

        if (existingUrl && existingUrl.generatorId === this.uid) {
            return true;
        }

        return false;
    }

    /**
     * @description Get all urls of this url generator.
     * NOTE: the method is only used for testing purposes at the moment.
     * @returns {Array}
     */
    getUrls() {
        return this.urls.getByGeneratorId(this.uid);
    }

    /**
     * @description Override of `toString`
     * @returns {string}
     */
    toString() {
        return this.router.toString();
    }
}

module.exports = UrlGenerator;
<|endoftext|>


<commit_before>/**
 * A Map with additional utility methods. This is used throughout discord.js rather than Arrays for anything that has
 * an ID, for significantly improved performance and ease-of-use.
 * @extends {Map}
 */
class Collection extends Map {
  constructor(iterable) {
    super(iterable);

    /**
     * Cached array for the `array()` method - will be reset to `null` whenever `set()` or `delete()` are called.
     * @type {?Array}
     * @private
     */
    this._array = null;

    /**
     * Cached array for the `keyArray()` method - will be reset to `null` whenever `set()` or `delete()` are called.
     * @type {?Array}
     * @private
     */
    this._keyArray = null;
  }

  set(key, val) {
    this._array = null;
    this._keyArray = null;
    return super.set(key, val);
  }

  delete(key) {
    this._array = null;
    this._keyArray = null;
    return super.delete(key);
  }

  /**
   * Creates an ordered array of the values of this collection, and caches it internally. The array will only be
   * reconstructed if an item is added to or removed from the collection, or if you change the length of the array
   * itself. If you don't want this caching behaviour, use `Array.from(collection.values())` instead.
   * @returns {Array}
   */
  array() {
    if (!this._array || this._array.length !== this.size) this._array = Array.from(this.values());
    return this._array;
  }

  /**
   * Creates an ordered array of the keys of this collection, and caches it internally. The array will only be
   * reconstructed if an item is added to or removed from the collection, or if you change the length of the array
   * itself. If you don't want this caching behaviour, use `Array.from(collection.keys())` instead.
   * @returns {Array}
   */
  keyArray() {
    if (!this._keyArray || this._keyArray.length !== this.size) this._keyArray = Array.from(this.keys());
    return this._keyArray;
  }

  /**
   * Obtains the first item in this collection.
   * @returns {*}
   */
  first() {
    return this.values().next().value;
  }

  /**
   * Obtains the first key in this collection.
   * @returns {*}
   */
  firstKey() {
    return this.keys().next().value;
  }

  /**
   * Obtains the last item in this collection. This relies on the `array()` method, and thus the caching mechanism
   * applies here as well.
   * @returns {*}
   */
  last() {
    const arr = this.array();
    return arr[arr.length - 1];
  }

  /**
   * Obtains the last key in this collection. This relies on the `keyArray()` method, and thus the caching mechanism
   * applies here as well.
   * @returns {*}
   */
  lastKey() {
    const arr = this.keyArray();
    return arr[arr.length - 1];
  }

  /**
   * Obtains a random item from this collection. This relies on the `array()` method, and thus the caching mechanism
   * applies here as well.
   * @returns {*}
   */
  random() {
    const arr = this.array();
    return arr[Math.floor(Math.random() * arr.length)];
  }

  /**
   * Obtains a random key from this collection. This relies on the `keyArray()` method, and thus the caching mechanism
   * applies here as well.
   * @returns {*}
   */
  randomKey() {
    const arr = this.keyArray();
    return arr[Math.floor(Math.random() * arr.length)];
  }

  /**
   * Searches for all items where their specified property's value is identical to the given value
   * (`item[prop] === value`).
   * @param {string} prop The property to test against
   * @param {*} value The expected value
   * @returns {Array}
   * @example
   * collection.findAll('username', 'Bob');
   */
  findAll(prop, value) {
    if (typeof prop !== 'string') throw new TypeError('Key must be a string.');
    if (typeof value === 'undefined') throw new Error('Value must be specified.');
    const results = [];
    for (const item of this.values()) {
      if (item[prop] === value) results.push(item);
    }
    return results;
  }

  /**
   * Searches for a single item where its specified property's value is identical to the given value
   * (`item[prop] === value`), or the given function returns a truthy value. In the latter case, this is identical to
   * [Array.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find).
   * @param {string|Function} propOrFn The property to test against, or the function to test with
   * @param {*} [value] The expected value - only applicable and required if using a property for the first argument
   * @returns {*}
   * @example
   * collection.find('username', 'Bob');
   * @example
   * collection.find(val => val.username === 'Bob');
   */
  find(propOrFn, value) {
    if (typeof propOrFn === 'string') {
      if (typeof value === 'undefined') throw new Error('Value must be specified.');
      if (propOrFn === 'id') return this.get(value);
      for (const item of this.values()) {
        if (item[propOrFn] === value) return item;
      }
      return null;
    } else if (typeof propOrFn === 'function') {
      for (const [key, val] of this) {
        if (propOrFn(val, key, this)) return val;
      }
      return null;
    } else {
      throw new Error('First argument must be a property string or a function.');
    }
  }

  /* eslint-disable max-len */
  /**
   * Searches for the key of a single item where its specified property's value is identical to the given value
   * (`item[prop] === value`), or the given function returns a truthy value. In the latter case, this is identical to
   * [Array.findIndex()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex).
   * @param {string|Function} propOrFn The property to test against, or the function to test with
   * @param {*} [value] The expected value - only applicable and required if using a property for the first argument
   * @returns {*}
   * @example
   * collection.findKey('username', 'Bob');
   * @example
   * collection.findKey(val => val.username === 'Bob');
   */
  /* eslint-enable max-len */
  findKey(propOrFn, value) {
    if (typeof propOrFn === 'string') {
      if (typeof value === 'undefined') throw new Error('Value must be specified.');
      for (const [key, val] of this) {
        if (val[propOrFn] === value) return key;
      }
      return null;
    } else if (typeof propOrFn === 'function') {
      for (const [key, val] of this) {
        if (propOrFn(val, key, this)) return key;
      }
      return null;
    } else {
      throw new Error('First argument must be a property string or a function.');
    }
  }

  /**
   * Searches for the existence of a single item where its specified property's value is identical to the given value
   * (`item[prop] === value`).
   * @param {string} prop The property to test against
   * @param {*} value The expected value
   * @returns {boolean}
   * @example
   * if (collection.exists('username', 'Bob')) {
   *  console.log('user here!');
   * }
   */
  exists(prop, value) {
    if (prop === 'id') return this.has(value);
    return Boolean(this.find(prop, value));
  }

  /**
   * Identical to
   * [Array.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter),
   * but returns a Collection instead of an Array.
   * @param {Function} fn Function used to test (should return a boolean)
   * @param {Object} [thisArg] Value to use as `this` when executing function
   * @returns {Collection}
   */
  filter(fn, thisArg) {
    if (thisArg) fn = fn.bind(thisArg);
    const results = new Collection();
    for (const [key, val] of this) {
      if (fn(val, key, this)) results.set(key, val);
    }
    return results;
  }

  /**
   * Identical to
   * [Array.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter).
   * @param {Function} fn Function used to test (should return a boolean)
   * @param {Object} [thisArg] Value to use as `this` when executing function
   * @returns {Array}
   */
  filterArray(fn, thisArg) {
    if (thisArg) fn = fn.bind(thisArg);
    const results = [];
    for (const [key, val] of this) {
      if (fn(val, key, this)) results.push(val);
    }
    return results;
  }

  /**
   * Identical to
   * [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).
   * @param {Function} fn Function that produces an element of the new array, taking three arguments
   * @param {*} [thisArg] Value to use as `this` when executing function
   * @returns {Array}
   */
  map(fn, thisArg) {
    if (thisArg) fn = fn.bind(thisArg);
    const arr = new Array(this.size);
    let i = 0;
    for (const [key, val] of this) arr[i++] = fn(val, key, this);
    return arr;
  }

  /**
   * Identical to
   * [Array.some()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some).
   * @param {Function} fn Function used to test (should return a boolean)
   * @param {Object} [thisArg] Value to use as `this` when executing function
   * @returns {boolean}
   */
  some(fn, thisArg) {
    if (thisArg) fn = fn.bind(thisArg);
    for (const [key, val] of this) {
      if (fn(val, key, this)) return true;
    }
    return false;
  }

  /**
   * Identical to
   * [Array.every()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every).
   * @param {Function} fn Function used to test (should return a boolean)
   * @param {Object} [thisArg] Value to use as `this` when executing function
   * @returns {boolean}
   */
  every(fn, thisArg) {
    if (thisArg) fn = fn.bind(thisArg);
    for (const [key, val] of this) {
      if (!fn(val, key, this)) return false;
    }
    return true;
  }

  /**
   * Identical to
   * [Array.reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce).
   * @param {Function} fn Function used to reduce, taking four arguments; `accumulator`, `currentValue`, `currentKey`,
   * and `collection`
   * @param {*} [initialValue] Starting value for the accumulator
   * @returns {*}
   */
  reduce(fn, initialValue) {
    let accumulator;
    if (typeof initialValue !== 'undefined') {
      accumulator = initialValue;
      for (const [key, val] of this) accumulator = fn(accumulator, val, key, this);
    } else {
      let first = true;
      for (const [key, val] of this) {
        if (first) {
          accumulator = val;
          first = false;
          continue;
        }
        accumulator = fn(accumulator, val, key, this);
      }
    }
    return accumulator;
  }

  /**
   * Combines this collection with others into a new collection. None of the source collections are modified.
   * @param {...Collection} collections Collections to merge
   * @returns {Collection}
   * @example const newColl = someColl.concat(someOtherColl, anotherColl, ohBoyAColl);
   */
  concat(...collections) {
    const newColl = new this.constructor();
    for (const [key, val] of this) newColl.set(key, val);
    for (const coll of collections) {
      for (const [key, val] of coll) newColl.set(key, val);
    }
    return newColl;
  }

  /**
   * Calls the `delete()` method on all items that have it.
   * @returns {Promise[]}
   */
  deleteAll() {
    const returns = [];
    for (const item of this.values()) {
      if (item.delete) returns.push(item.delete());
    }
    return returns;
  }

  /**
   * Checks if this collection shares identical key-value pairings with another.
   * This is different to checking for equality using equal-signs, because
   * the collections may be different objects, but contain the same data.
   * @param {Collection} collection Collection to compare with
   * @returns {boolean} Whether the collections have identical contents
   */
  equals(collection) {
    if (!collection) return false;
    if (this === collection) return true;
    if (this.size !== collection.size) return false;
    return !this.find((value, key) => {
      const testVal = collection.get(key);
      return testVal !== value || (testVal === undefined && !collection.has(key));
    });
  }
}

module.exports = Collection;
<commit_msg>Revert "This expands the consistency of .find and .exists to include the id property" (#1074)<commit_after>/**
 * A Map with additional utility methods. This is used throughout discord.js rather than Arrays for anything that has
 * an ID, for significantly improved performance and ease-of-use.
 * @extends {Map}
 */
class Collection extends Map {
  constructor(iterable) {
    super(iterable);

    /**
     * Cached array for the `array()` method - will be reset to `null` whenever `set()` or `delete()` are called.
     * @type {?Array}
     * @private
     */
    this._array = null;

    /**
     * Cached array for the `keyArray()` method - will be reset to `null` whenever `set()` or `delete()` are called.
     * @type {?Array}
     * @private
     */
    this._keyArray = null;
  }

  set(key, val) {
    this._array = null;
    this._keyArray = null;
    return super.set(key, val);
  }

  delete(key) {
    this._array = null;
    this._keyArray = null;
    return super.delete(key);
  }

  /**
   * Creates an ordered array of the values of this collection, and caches it internally. The array will only be
   * reconstructed if an item is added to or removed from the collection, or if you change the length of the array
   * itself. If you don't want this caching behaviour, use `Array.from(collection.values())` instead.
   * @returns {Array}
   */
  array() {
    if (!this._array || this._array.length !== this.size) this._array = Array.from(this.values());
    return this._array;
  }

  /**
   * Creates an ordered array of the keys of this collection, and caches it internally. The array will only be
   * reconstructed if an item is added to or removed from the collection, or if you change the length of the array
   * itself. If you don't want this caching behaviour, use `Array.from(collection.keys())` instead.
   * @returns {Array}
   */
  keyArray() {
    if (!this._keyArray || this._keyArray.length !== this.size) this._keyArray = Array.from(this.keys());
    return this._keyArray;
  }

  /**
   * Obtains the first item in this collection.
   * @returns {*}
   */
  first() {
    return this.values().next().value;
  }

  /**
   * Obtains the first key in this collection.
   * @returns {*}
   */
  firstKey() {
    return this.keys().next().value;
  }

  /**
   * Obtains the last item in this collection. This relies on the `array()` method, and thus the caching mechanism
   * applies here as well.
   * @returns {*}
   */
  last() {
    const arr = this.array();
    return arr[arr.length - 1];
  }

  /**
   * Obtains the last key in this collection. This relies on the `keyArray()` method, and thus the caching mechanism
   * applies here as well.
   * @returns {*}
   */
  lastKey() {
    const arr = this.keyArray();
    return arr[arr.length - 1];
  }

  /**
   * Obtains a random item from this collection. This relies on the `array()` method, and thus the caching mechanism
   * applies here as well.
   * @returns {*}
   */
  random() {
    const arr = this.array();
    return arr[Math.floor(Math.random() * arr.length)];
  }

  /**
   * Obtains a random key from this collection. This relies on the `keyArray()` method, and thus the caching mechanism
   * applies here as well.
   * @returns {*}
   */
  randomKey() {
    const arr = this.keyArray();
    return arr[Math.floor(Math.random() * arr.length)];
  }

  /**
   * Searches for all items where their specified property's value is identical to the given value
   * (`item[prop] === value`).
   * @param {string} prop The property to test against
   * @param {*} value The expected value
   * @returns {Array}
   * @example
   * collection.findAll('username', 'Bob');
   */
  findAll(prop, value) {
    if (typeof prop !== 'string') throw new TypeError('Key must be a string.');
    if (typeof value === 'undefined') throw new Error('Value must be specified.');
    const results = [];
    for (const item of this.values()) {
      if (item[prop] === value) results.push(item);
    }
    return results;
  }

  /**
   * Searches for a single item where its specified property's value is identical to the given value
   * (`item[prop] === value`), or the given function returns a truthy value. In the latter case, this is identical to
   * [Array.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find).
   * <warn>Do not use this to obtain an item by its ID. Instead, use `collection.get(id)`. See
   * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) for details.</warn>
   * @param {string|Function} propOrFn The property to test against, or the function to test with
   * @param {*} [value] The expected value - only applicable and required if using a property for the first argument
   * @returns {*}
   * @example
   * collection.find('username', 'Bob');
   * @example
   * collection.find(val => val.username === 'Bob');
   */
  find(propOrFn, value) {
    if (typeof propOrFn === 'string') {
      if (typeof value === 'undefined') throw new Error('Value must be specified.');
      if (propOrFn === 'id') throw new RangeError('Don\'t use .find() with IDs. Instead, use .get(id).');
      for (const item of this.values()) {
        if (item[propOrFn] === value) return item;
      }
      return null;
    } else if (typeof propOrFn === 'function') {
      for (const [key, val] of this) {
        if (propOrFn(val, key, this)) return val;
      }
      return null;
    } else {
      throw new Error('First argument must be a property string or a function.');
    }
  }

  /* eslint-disable max-len */
  /**
   * Searches for the key of a single item where its specified property's value is identical to the given value
   * (`item[prop] === value`), or the given function returns a truthy value. In the latter case, this is identical to
   * [Array.findIndex()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex).
   * @param {string|Function} propOrFn The property to test against, or the function to test with
   * @param {*} [value] The expected value - only applicable and required if using a property for the first argument
   * @returns {*}
   * @example
   * collection.findKey('username', 'Bob');
   * @example
   * collection.findKey(val => val.username === 'Bob');
   */
  /* eslint-enable max-len */
  findKey(propOrFn, value) {
    if (typeof propOrFn === 'string') {
      if (typeof value === 'undefined') throw new Error('Value must be specified.');
      for (const [key, val] of this) {
        if (val[propOrFn] === value) return key;
      }
      return null;
    } else if (typeof propOrFn === 'function') {
      for (const [key, val] of this) {
        if (propOrFn(val, key, this)) return key;
      }
      return null;
    } else {
      throw new Error('First argument must be a property string or a function.');
    }
  }

  /**
   * Searches for the existence of a single item where its specified property's value is identical to the given value
   * (`item[prop] === value`).
   * <warn>Do not use this to check for an item by its ID. Instead, use `collection.has(id)`. See
   * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) for details.</warn>
   * @param {string} prop The property to test against
   * @param {*} value The expected value
   * @returns {boolean}
   * @example
   * if (collection.exists('username', 'Bob')) {
   *  console.log('user here!');
   * }
   */
  exists(prop, value) {
    if (prop === 'id') throw new RangeError('Don\'t use .exists() with IDs. Instead, use .has(id).');
    return Boolean(this.find(prop, value));
  }

  /**
   * Identical to
   * [Array.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter),
   * but returns a Collection instead of an Array.
   * @param {Function} fn Function used to test (should return a boolean)
   * @param {Object} [thisArg] Value to use as `this` when executing function
   * @returns {Collection}
   */
  filter(fn, thisArg) {
    if (thisArg) fn = fn.bind(thisArg);
    const results = new Collection();
    for (const [key, val] of this) {
      if (fn(val, key, this)) results.set(key, val);
    }
    return results;
  }

  /**
   * Identical to
   * [Array.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter).
   * @param {Function} fn Function used to test (should return a boolean)
   * @param {Object} [thisArg] Value to use as `this` when executing function
   * @returns {Array}
   */
  filterArray(fn, thisArg) {
    if (thisArg) fn = fn.bind(thisArg);
    const results = [];
    for (const [key, val] of this) {
      if (fn(val, key, this)) results.push(val);
    }
    return results;
  }

  /**
   * Identical to
   * [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).
   * @param {Function} fn Function that produces an element of the new array, taking three arguments
   * @param {*} [thisArg] Value to use as `this` when executing function
   * @returns {Array}
   */
  map(fn, thisArg) {
    if (thisArg) fn = fn.bind(thisArg);
    const arr = new Array(this.size);
    let i = 0;
    for (const [key, val] of this) arr[i++] = fn(val, key, this);
    return arr;
  }

  /**
   * Identical to
   * [Array.some()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some).
   * @param {Function} fn Function used to test (should return a boolean)
   * @param {Object} [thisArg] Value to use as `this` when executing function
   * @returns {boolean}
   */
  some(fn, thisArg) {
    if (thisArg) fn = fn.bind(thisArg);
    for (const [key, val] of this) {
      if (fn(val, key, this)) return true;
    }
    return false;
  }

  /**
   * Identical to
   * [Array.every()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every).
   * @param {Function} fn Function used to test (should return a boolean)
   * @param {Object} [thisArg] Value to use as `this` when executing function
   * @returns {boolean}
   */
  every(fn, thisArg) {
    if (thisArg) fn = fn.bind(thisArg);
    for (const [key, val] of this) {
      if (!fn(val, key, this)) return false;
    }
    return true;
  }

  /**
   * Identical to
   * [Array.reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce).
   * @param {Function} fn Function used to reduce, taking four arguments; `accumulator`, `currentValue`, `currentKey`,
   * and `collection`
   * @param {*} [initialValue] Starting value for the accumulator
   * @returns {*}
   */
  reduce(fn, initialValue) {
    let accumulator;
    if (typeof initialValue !== 'undefined') {
      accumulator = initialValue;
      for (const [key, val] of this) accumulator = fn(accumulator, val, key, this);
    } else {
      let first = true;
      for (const [key, val] of this) {
        if (first) {
          accumulator = val;
          first = false;
          continue;
        }
        accumulator = fn(accumulator, val, key, this);
      }
    }
    return accumulator;
  }

  /**
   * Combines this collection with others into a new collection. None of the source collections are modified.
   * @param {...Collection} collections Collections to merge
   * @returns {Collection}
   * @example const newColl = someColl.concat(someOtherColl, anotherColl, ohBoyAColl);
   */
  concat(...collections) {
    const newColl = new this.constructor();
    for (const [key, val] of this) newColl.set(key, val);
    for (const coll of collections) {
      for (const [key, val] of coll) newColl.set(key, val);
    }
    return newColl;
  }

  /**
   * Calls the `delete()` method on all items that have it.
   * @returns {Promise[]}
   */
  deleteAll() {
    const returns = [];
    for (const item of this.values()) {
      if (item.delete) returns.push(item.delete());
    }
    return returns;
  }

  /**
   * Checks if this collection shares identical key-value pairings with another.
   * This is different to checking for equality using equal-signs, because
   * the collections may be different objects, but contain the same data.
   * @param {Collection} collection Collection to compare with
   * @returns {boolean} Whether the collections have identical contents
   */
  equals(collection) {
    if (!collection) return false;
    if (this === collection) return true;
    if (this.size !== collection.size) return false;
    return !this.find((value, key) => {
      const testVal = collection.get(key);
      return testVal !== value || (testVal === undefined && !collection.has(key));
    });
  }
}

module.exports = Collection;
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import _ from 'lodash';

export const PRESENTATION_ATTRIBUTES = {
  alignmentBaseline: PropTypes.string,
  baselineShift: PropTypes.string,
  clip: PropTypes.string,
  clipPath: PropTypes.string,
  clipRule: PropTypes.string,
  color: PropTypes.string,
  colorInterpolation: PropTypes.string,
  colorInterpolationFilters: PropTypes.string,
  colorProfile: PropTypes.string,
  colorRendering: PropTypes.string,
  cursor: PropTypes.string,
  direction: PropTypes.oneOf(['ltr', 'rtl', 'inherit']),
  display: PropTypes.string,
  dominantBaseline: PropTypes.string,
  enableBackground: PropTypes.string,
  fill: PropTypes.string,
  fillOpacity: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  fillRule: PropTypes.oneOf(['nonzero', 'evenodd', 'inherit']),
  filter: PropTypes.string,
  floodColor: PropTypes.string,
  floodOpacity: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  font: PropTypes.string,
  fontFamily: PropTypes.string,
  fontSize: PropTypes.number,
  fontSizeAdjust: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  fontStretch: PropTypes.oneOf([
    'normal', 'wider', 'narrower', 'ultra-condensed', 'extra-condensed',
    'condensed', 'semi-condensed', 'semi-expanded', 'expanded',
    'extra-expanded', 'ultra-expanded', 'inherit',
  ]),
  fontStyle: PropTypes.oneOf(['normal', 'italic', 'oblique', 'inherit']),
  fontVariant: PropTypes.oneOf(['normal', 'small-caps', 'inherit']),
  fontWeight: PropTypes.oneOf([
    'normal', 'bold', 'bolder', 'lighter',
    100, 200, 300, 400, 500, 600, 700, 800, 900, 'inherit',
  ]),
  glyphOrientationHorizontal: PropTypes.string,
  glyphOrientationVertical: PropTypes.string,
  imageRendering: PropTypes.oneOf([
    'auto', 'optimizeSpeed', 'optimizeQuality', 'inherit',
  ]),
  kerning: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  letterSpacing: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  lightingColor: PropTypes.string,
  markerEnd: PropTypes.string,
  markerMid: PropTypes.string,
  markerStart: PropTypes.string,
  mask: PropTypes.string,
  opacity: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  overflow: PropTypes.oneOf(['visible', 'hidden', 'scroll', 'auto', 'inherit']),
  pointerEvents: PropTypes.oneOf([
    'visiblePainted', 'visibleFill', 'visibleStroke', 'visible', 'painted',
    'fill', 'stroke', 'all', 'none', 'inherit',
  ]),
  shapeRendering: PropTypes.oneOf([
    'auto', 'optimizeSpeed', 'crispEdges', 'geometricPrecision', 'inherit',
  ]),
  stopColor: PropTypes.string,
  stopOpacity: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  stroke: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  strokeDasharray: PropTypes.string,
  strokeDashoffset: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  strokeLinecap: PropTypes.oneOf(['butt', 'round', 'square', 'inherit']),
  strokeLinejoin: PropTypes.oneOf(['miter', 'round', 'bevel', 'inherit']),
  strokeMiterlimit: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  strokeOpacity: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  strokeWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  textAnchor: PropTypes.oneOf(['start', 'middle', 'end', 'inherit']),
  textDecoration: PropTypes.oneOf([
    'none', 'underline', 'overline', 'line-through', 'blink', 'inherit',
  ]),
  textRendering: PropTypes.oneOf([
    'auto', 'optimizeSpeed', 'optimizeLegibility', 'geometricPrecision',
    'inherit',
  ]),
  unicodeBidi: PropTypes.oneOf(['normal', 'embed', 'bidi-override', 'inherit']),
  visibility: PropTypes.oneOf(['visible', 'hidden', 'collapse', 'inherit']),
  wordSpacing: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  writingMode: PropTypes.oneOf([
    'lr-tb', 'rl-tb', 'tb-rl', 'lr', 'rl', 'tb', 'inherit',
  ]),
  transform: PropTypes.string,
  style: PropTypes.object,

  width: PropTypes.number,
  height: PropTypes.number,
  dx: PropTypes.number,
  dy: PropTypes.number,
  x: PropTypes.number,
  y: PropTypes.number,
  r: PropTypes.number,
};

const EVENT_ATTRIBUTES = {
  onClick: PropTypes.func,
  onMouseDown: PropTypes.func,
  onMouseUp: PropTypes.func,
  onMouseOver: PropTypes.func,
  onMouseMove: PropTypes.func,
  onMouseOut: PropTypes.func,
  onMouseEnter: PropTypes.func,
  onMouseLeave: PropTypes.func,
};
/**
 * Get the display name of a component
 * @param  {Object} Comp Specified Component
 * @return {String}      Display name of Component
 */
export const getDisplayName = (Comp) => {
  if (!Comp) { return ''; }
  if (typeof Comp === 'string') {
    return Comp;
  }
  return Comp.displayName || Comp.name || 'Component';
};

/*
 * Find and return all matched children by type. `type` can be a React element class or
 * string
 */
export const findAllByType = (children, type) => {
  const result = [];
  let types = [];

  if (_.isArray(type)) {
    types = type.map(t => getDisplayName(t));
  } else {
    types = [getDisplayName(type)];
  }

  React.Children.forEach(children, child => {
    const childType = child && child.type && (child.type.displayName || child.type.name);
    if (types.indexOf(childType) !== -1) {
      result.push(child);
    }
  });

  return result;
};
/*
 * Return the first matched child by type, return null otherwise.
 * `type` can be a React element class or string.
 */
export const findChildByType = (children, type) => {
  const result = findAllByType(children, type);

  return result && result[0];
};

/*
 * Create a new array of children excluding the ones matched the type
 */
export const withoutType = (children, type) => {
  const newChildren = [];
  let types;

  if (_.isArray(type)) {
    types = type.map(t => getDisplayName(t));
  } else {
    types = [getDisplayName(type)];
  }

  React.Children.forEach(children, child => {
    if (child && child.type && child.type.displayName
      && types.indexOf(child.type.displayName) !== -1) {
      return;
    }
    newChildren.push(child);
  });

  return newChildren;
};

/**
 * get all the presentation attribute of svg element
 * @param  {Object} el A react element or the props of a react element
 * @return {Object}    attributes or null
 */
export const getPresentationAttributes = (el) => {
  if (!el || _.isFunction(el)) { return null; }

  const props = React.isValidElement(el) ? el.props : el;

  if (!_.isObject(props)) { return null; }

  const keys = Object.keys(props).filter(k => PRESENTATION_ATTRIBUTES[k]);

  return (keys && keys.length) ?
    keys.reduce((result, k) => ({ ...result, [k]: props[k] }), {}) :
    null;
};

/**
 * get all the event attribute of svg element
 * @param  {Object} el A react element or the props of a react element
 * @return {Object}    attributes or null
 */
export const filterEventAttributes = (el) => {
  if (!el || _.isFunction(el)) { return null; }

  const props = React.isValidElement(el) ? el.props : el;

  if (!_.isObject(props)) { return null; }

  const keys = Object.keys(props).filter(k => EVENT_ATTRIBUTES[k]);

  return (keys && keys.length) ?
    keys.reduce((result, k) => ({ ...result, [k]: props[k] }), {}) :
    null;
};

const getEventHandler = (originalHandler, data, index) => (
  (e) => {
    originalHandler(data, index, e);

    return null;
  }
);

export const filterEventsOfChild = (props, data, index) => {
  if (!_.isObject(props)) { return null; }

  const events = Object.keys(props).filter(k => (
    EVENT_ATTRIBUTES[k] && _.isFunction(props[k])
  ));

  return (events && events.length) ?
    events.reduce((result, e) => ({
      ...result, [e]: getEventHandler(props[e], data, index),
    }), {}) :
    null;
};

/**
 * validate the width and height props of a chart element
 * @param  {Object} el A chart element
 * @return {Boolean}   true If the props width and height are number, and greater than 0
 */
export const validateWidthHeight = (el) => {
  if (!el || !el.props) { return false; }
  const { width, height } = el.props;

  if (!_.isNumber(width) || width <= 0 ||
    !_.isNumber(height) || height <= 0) {
    return false;
  }

  return true;
};

export const isSsr = () => (typeof document === 'undefined');

const SVG_TAGS = ['a', 'altGlyph', 'altGlyphDef', 'altGlyphItem', 'animate',
'animateColor', 'animateMotion', 'animateTransform', 'circle', 'clipPath',
'color-profile', 'cursor', 'defs', 'desc', 'ellipse', 'feBlend', 'feColormatrix',
'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting',
'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG',
'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology',
'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile',
'feTurbulence', 'filter', 'font', 'font-face', 'font-face-format', 'font-face-name',
'font-face-url', 'foreignObject', 'g', 'glyph', 'glyphRef', 'hkern', 'image',
'line', 'lineGradient', 'marker', 'mask', 'metadata', 'missing-glyph', 'mpath',
'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'script',
'set', 'stop', 'style', 'svg', 'switch', 'symbol', 'text', 'textPath', 'title',
'tref', 'tspan', 'use', 'view', 'vkern'];
/**
 * Filter all the svg elements of children
 * @param  {Array} children The children of a react element
 * @return {Array}          All the svg elements
 */
export const filterSvgElements = (children) => {
  const svgElements = [];

  React.Children.forEach(children, entry => {
    if (entry && entry.type && _.isString(entry.type) &&
      SVG_TAGS.indexOf(entry.type) >= 0) {
      svgElements.push(entry);
    }
  });

  return svgElements;
};

<commit_msg>Add angle property to PRESENTATION_ATTRIBUTES (#307)<commit_after>import React, { PropTypes } from 'react';
import _ from 'lodash';

export const PRESENTATION_ATTRIBUTES = {
  alignmentBaseline: PropTypes.string,
  angle: PropTypes.number,
  baselineShift: PropTypes.string,
  clip: PropTypes.string,
  clipPath: PropTypes.string,
  clipRule: PropTypes.string,
  color: PropTypes.string,
  colorInterpolation: PropTypes.string,
  colorInterpolationFilters: PropTypes.string,
  colorProfile: PropTypes.string,
  colorRendering: PropTypes.string,
  cursor: PropTypes.string,
  direction: PropTypes.oneOf(['ltr', 'rtl', 'inherit']),
  display: PropTypes.string,
  dominantBaseline: PropTypes.string,
  enableBackground: PropTypes.string,
  fill: PropTypes.string,
  fillOpacity: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  fillRule: PropTypes.oneOf(['nonzero', 'evenodd', 'inherit']),
  filter: PropTypes.string,
  floodColor: PropTypes.string,
  floodOpacity: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  font: PropTypes.string,
  fontFamily: PropTypes.string,
  fontSize: PropTypes.number,
  fontSizeAdjust: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  fontStretch: PropTypes.oneOf([
    'normal', 'wider', 'narrower', 'ultra-condensed', 'extra-condensed',
    'condensed', 'semi-condensed', 'semi-expanded', 'expanded',
    'extra-expanded', 'ultra-expanded', 'inherit',
  ]),
  fontStyle: PropTypes.oneOf(['normal', 'italic', 'oblique', 'inherit']),
  fontVariant: PropTypes.oneOf(['normal', 'small-caps', 'inherit']),
  fontWeight: PropTypes.oneOf([
    'normal', 'bold', 'bolder', 'lighter',
    100, 200, 300, 400, 500, 600, 700, 800, 900, 'inherit',
  ]),
  glyphOrientationHorizontal: PropTypes.string,
  glyphOrientationVertical: PropTypes.string,
  imageRendering: PropTypes.oneOf([
    'auto', 'optimizeSpeed', 'optimizeQuality', 'inherit',
  ]),
  kerning: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  letterSpacing: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  lightingColor: PropTypes.string,
  markerEnd: PropTypes.string,
  markerMid: PropTypes.string,
  markerStart: PropTypes.string,
  mask: PropTypes.string,
  opacity: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  overflow: PropTypes.oneOf(['visible', 'hidden', 'scroll', 'auto', 'inherit']),
  pointerEvents: PropTypes.oneOf([
    'visiblePainted', 'visibleFill', 'visibleStroke', 'visible', 'painted',
    'fill', 'stroke', 'all', 'none', 'inherit',
  ]),
  shapeRendering: PropTypes.oneOf([
    'auto', 'optimizeSpeed', 'crispEdges', 'geometricPrecision', 'inherit',
  ]),
  stopColor: PropTypes.string,
  stopOpacity: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  stroke: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  strokeDasharray: PropTypes.string,
  strokeDashoffset: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  strokeLinecap: PropTypes.oneOf(['butt', 'round', 'square', 'inherit']),
  strokeLinejoin: PropTypes.oneOf(['miter', 'round', 'bevel', 'inherit']),
  strokeMiterlimit: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  strokeOpacity: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  strokeWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  textAnchor: PropTypes.oneOf(['start', 'middle', 'end', 'inherit']),
  textDecoration: PropTypes.oneOf([
    'none', 'underline', 'overline', 'line-through', 'blink', 'inherit',
  ]),
  textRendering: PropTypes.oneOf([
    'auto', 'optimizeSpeed', 'optimizeLegibility', 'geometricPrecision',
    'inherit',
  ]),
  unicodeBidi: PropTypes.oneOf(['normal', 'embed', 'bidi-override', 'inherit']),
  visibility: PropTypes.oneOf(['visible', 'hidden', 'collapse', 'inherit']),
  wordSpacing: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  writingMode: PropTypes.oneOf([
    'lr-tb', 'rl-tb', 'tb-rl', 'lr', 'rl', 'tb', 'inherit',
  ]),
  transform: PropTypes.string,
  style: PropTypes.object,

  width: PropTypes.number,
  height: PropTypes.number,
  dx: PropTypes.number,
  dy: PropTypes.number,
  x: PropTypes.number,
  y: PropTypes.number,
  r: PropTypes.number,
};

const EVENT_ATTRIBUTES = {
  onClick: PropTypes.func,
  onMouseDown: PropTypes.func,
  onMouseUp: PropTypes.func,
  onMouseOver: PropTypes.func,
  onMouseMove: PropTypes.func,
  onMouseOut: PropTypes.func,
  onMouseEnter: PropTypes.func,
  onMouseLeave: PropTypes.func,
};
/**
 * Get the display name of a component
 * @param  {Object} Comp Specified Component
 * @return {String}      Display name of Component
 */
export const getDisplayName = (Comp) => {
  if (!Comp) { return ''; }
  if (typeof Comp === 'string') {
    return Comp;
  }
  return Comp.displayName || Comp.name || 'Component';
};

/*
 * Find and return all matched children by type. `type` can be a React element class or
 * string
 */
export const findAllByType = (children, type) => {
  const result = [];
  let types = [];

  if (_.isArray(type)) {
    types = type.map(t => getDisplayName(t));
  } else {
    types = [getDisplayName(type)];
  }

  React.Children.forEach(children, child => {
    const childType = child && child.type && (child.type.displayName || child.type.name);
    if (types.indexOf(childType) !== -1) {
      result.push(child);
    }
  });

  return result;
};
/*
 * Return the first matched child by type, return null otherwise.
 * `type` can be a React element class or string.
 */
export const findChildByType = (children, type) => {
  const result = findAllByType(children, type);

  return result && result[0];
};

/*
 * Create a new array of children excluding the ones matched the type
 */
export const withoutType = (children, type) => {
  const newChildren = [];
  let types;

  if (_.isArray(type)) {
    types = type.map(t => getDisplayName(t));
  } else {
    types = [getDisplayName(type)];
  }

  React.Children.forEach(children, child => {
    if (child && child.type && child.type.displayName
      && types.indexOf(child.type.displayName) !== -1) {
      return;
    }
    newChildren.push(child);
  });

  return newChildren;
};

/**
 * get all the presentation attribute of svg element
 * @param  {Object} el A react element or the props of a react element
 * @return {Object}    attributes or null
 */
export const getPresentationAttributes = (el) => {
  if (!el || _.isFunction(el)) { return null; }

  const props = React.isValidElement(el) ? el.props : el;

  if (!_.isObject(props)) { return null; }

  const keys = Object.keys(props).filter(k => PRESENTATION_ATTRIBUTES[k]);

  return (keys && keys.length) ?
    keys.reduce((result, k) => ({ ...result, [k]: props[k] }), {}) :
    null;
};

/**
 * get all the event attribute of svg element
 * @param  {Object} el A react element or the props of a react element
 * @return {Object}    attributes or null
 */
export const filterEventAttributes = (el) => {
  if (!el || _.isFunction(el)) { return null; }

  const props = React.isValidElement(el) ? el.props : el;

  if (!_.isObject(props)) { return null; }

  const keys = Object.keys(props).filter(k => EVENT_ATTRIBUTES[k]);

  return (keys && keys.length) ?
    keys.reduce((result, k) => ({ ...result, [k]: props[k] }), {}) :
    null;
};

const getEventHandler = (originalHandler, data, index) => (
  (e) => {
    originalHandler(data, index, e);

    return null;
  }
);

export const filterEventsOfChild = (props, data, index) => {
  if (!_.isObject(props)) { return null; }

  const events = Object.keys(props).filter(k => (
    EVENT_ATTRIBUTES[k] && _.isFunction(props[k])
  ));

  return (events && events.length) ?
    events.reduce((result, e) => ({
      ...result, [e]: getEventHandler(props[e], data, index),
    }), {}) :
    null;
};

/**
 * validate the width and height props of a chart element
 * @param  {Object} el A chart element
 * @return {Boolean}   true If the props width and height are number, and greater than 0
 */
export const validateWidthHeight = (el) => {
  if (!el || !el.props) { return false; }
  const { width, height } = el.props;

  if (!_.isNumber(width) || width <= 0 ||
    !_.isNumber(height) || height <= 0) {
    return false;
  }

  return true;
};

export const isSsr = () => (typeof document === 'undefined');

const SVG_TAGS = ['a', 'altGlyph', 'altGlyphDef', 'altGlyphItem', 'animate',
'animateColor', 'animateMotion', 'animateTransform', 'circle', 'clipPath',
'color-profile', 'cursor', 'defs', 'desc', 'ellipse', 'feBlend', 'feColormatrix',
'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting',
'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG',
'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology',
'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile',
'feTurbulence', 'filter', 'font', 'font-face', 'font-face-format', 'font-face-name',
'font-face-url', 'foreignObject', 'g', 'glyph', 'glyphRef', 'hkern', 'image',
'line', 'lineGradient', 'marker', 'mask', 'metadata', 'missing-glyph', 'mpath',
'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'script',
'set', 'stop', 'style', 'svg', 'switch', 'symbol', 'text', 'textPath', 'title',
'tref', 'tspan', 'use', 'view', 'vkern'];
/**
 * Filter all the svg elements of children
 * @param  {Array} children The children of a react element
 * @return {Array}          All the svg elements
 */
export const filterSvgElements = (children) => {
  const svgElements = [];

  React.Children.forEach(children, entry => {
    if (entry && entry.type && _.isString(entry.type) &&
      SVG_TAGS.indexOf(entry.type) >= 0) {
      svgElements.push(entry);
    }
  });

  return svgElements;
};
<|endoftext|>