OverviewFilesCommitsIssuesDocsReleasesPeers

mirall-cli / node_modules / bare-ws / lib / server.js

132 lines · javascript ref:
const EventEmitter = require('bare-events')
const http = require('bare-http1')
const https = require('bare-https')
const crypto = require('bare-crypto')
const { GUID, EOL, EOF } = require('./constants')
const errors = require('./errors')
const WebSocket = require('./socket')

const EMPTY = Buffer.alloc(0)

const KEY = /^[+/0-9A-Za-z]{22}==$/

module.exports = exports = class WebSocketServer extends EventEmitter {
  constructor(opts = {}, onconnection) {
    if (typeof opts === 'function') {
      onconnection = opts
      opts = {}
    }

    super()

    const createServer = opts.secure ? https.createServer : http.createServer

    const {
      server = createServer(opts, this._onrequest.bind(this)).listen(
        opts,
        this._onlistening.bind(this)
      )
    } = opts

    this._server = server

    this._server.on('upgrade', this._onupgrade.bind(this))

    if (onconnection) this.on('connection', onconnection)
  }

  get listening() {
    return this._server.listening
  }

  address() {
    return this._server.address()
  }

  close(cb) {
    this._server.close(cb)

    return this
  }

  ref() {
    this._server.ref()

    return this
  }

  unref() {
    this._server.unref()

    return this
  }

  _onlistening() {
    this.emit('listening')
  }

  _onrequest(req, res) {
    const body = http.constants.status[426]

    res.writeHead(426, {
      'Content-Type': 'text/plain',
      'Content-Length': body.length
    })

    res.end(body)
  }

  _onupgrade(req, socket, head) {
    exports.handshake(req, socket, head, (err) => {
      if (err) return socket.destroy(err)

      this.emit('connection', new WebSocket({ socket, isServer: true }), req)
    })
  }
}

// https://datatracker.ietf.org/doc/html/rfc6455#section-4.2
exports.handshake = function handshake(req, socket = req.socket, head = EMPTY, cb) {
  if (typeof socket === 'function') {
    cb = socket
    socket = req.socket
    head = EMPTY
  } else if (typeof head === 'function') {
    cb = head
    head = EMPTY
  }

  if (req.headers.upgrade.toLowerCase() !== 'websocket') {
    return cb(errors.INVALID_UPGRADE_HEADER())
  }

  const version = +req.headers['sec-websocket-version']

  if (version !== 8 && version !== 13) {
    return cb(errors.INVALID_VERSION_HEADER())
  }

  const key = req.headers['sec-websocket-key']

  if (!key || !KEY.test(key)) {
    return cb(errors.INVALID_KEY_HEADER())
  }

  const digest = crypto.createHash('sha1').update(key).update(GUID).digest('base64')

  socket.write(
    'HTTP/1.1 101 Web Socket Protocol Handshake' +
      EOL +
      'Upgrade: WebSocket' +
      EOL +
      'Connection: Upgrade' +
      EOL +
      `Sec-WebSocket-Accept: ${digest}` +
      EOF
  )

  if (head.byteLength) socket.unshift(head)

  cb(null)
}