Home Manual Reference Source Repository

src/SignalingGate.js

import ServiceFactory, {WEB_RTC, WEB_SOCKET} from 'ServiceFactory'

/**
 * This class represents a door of the `WebChannel` for the current peer. If the door
 * is open, then clients can join the `WebChannel` through this peer. There are as
 * many doors as peers in the `WebChannel` and each of them can be closed or opened.
 */
class SignalingGate {

  /**
   * @param {WebChannel} wc
   */
  constructor (wc) {
    /**
     * @type {WebChannel}
     */
    this.webChannel = wc
    /**
     * Signaling server url.
     * @private
     * @type {string}
     */
    this.url = null
    /**
     * Key related to the `url`.
     * @private
     * @type {string}
     */
    this.key = null
    /**
     * Web socket with the signaling server.
     * @private
     * @type {external:WebSocket|external:ws/WebSocket}
     */
    this.ws = null
  }

  /**
   * Open the gate.
   *
   * @param {string} url Signaling server url
   * @param {function(ch: RTCDataChannel)} onChannel
   * @param {string} key
   * @returns {Promise<OpenData, string>}
   */
  open (url, onChannel, key = null) {
    return new Promise((resolve, reject) => {
      if (key === null) key = this.generateKey()
      ServiceFactory.get(WEB_SOCKET).connect(url)
        .then(ws => {
          ws.onclose = closeEvt => {
            this.key = null
            this.ws = null
            this.url = null
            this.webChannel.onClose(closeEvt)
            reject(closeEvt.reason)
          }
          ws.onerror = err => reject(err.message)
          ws.onmessage = evt => {
            try {
              const msg = JSON.parse(evt.data)
              if ('isKeyOk' in msg) {
                if (msg.isKeyOk) {
                  ServiceFactory.get(WEB_RTC, this.webChannel.settings.iceServers)
                    .listenFromSignaling(ws, onChannel)
                  this.ws = ws
                  this.key = key
                  this.url = url
                  resolve({url, key})
                } else reject(`${key} key already exists`)
              } else reject(`Unknown message from ${url}: ${evt.data}`)
            } catch (err) {
              reject('Server responce is not a JSON string: ' + err.message)
            }
          }
          ws.send(JSON.stringify({open: key}))
        })
        .catch(reject)
    })
  }

  /**
   * Check if the door is opened or closed.
   *
   * @returns {boolean} - Returns true if the door is opened and false if it is
   * closed
   */
  isOpen () {
    return this.ws !== null && this.ws.readyState === this.ws.OPEN
  }

  /**
   * Get open data.
   *
   * @returns {OpenData|null} Open data if the door is open and null otherwise
   */
  getOpenData () {
    if (this.isOpen()) {
      return {
        url: this.url,
        key: this.key
      }
    }
    return null
  }

  /**
   * Close the door if it is open and do nothing if it is closed already.
   */
  close () {
    if (this.isOpen()) {
      this.ws.close()
    }
  }

  /**
   * Generate random key which will be used to join the `WebChannel`.
   *
   * @private
   * @returns {string} - Generated key
   */
  generateKey () {
    const MIN_LENGTH = 5
    const DELTA_LENGTH = 0
    const MASK = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
    let result = ''
    const length = MIN_LENGTH + Math.round(Math.random() * DELTA_LENGTH)

    for (let i = 0; i < length; i++) {
      result += MASK[Math.round(Math.random() * (MASK.length - 1))]
    }
    return result
  }
}

export default SignalingGate