OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-booking / node_modules / bare-net / index.js

415 lines · javascript ref:
const EventEmitter = require('bare-events')
const { Duplex } = require('bare-stream')
const tcp = require('bare-tcp')
const pipe = require('bare-pipe')
const constants = require('./lib/constants')

const defaultReadBufferSize = 65536

exports.Socket = class NetSocket extends Duplex {
  constructor(opts = {}) {
    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = false,
      eagerOpen = false
    } = opts

    super({ eagerOpen, allowHalfOpen })

    this._type = 0
    this._state = 0
    this._socket = null

    this._opts = { readBufferSize, allowHalfOpen, eagerOpen }

    this._pendingOpen = null
    this._pendingWrite = null
    this._pendingFinal = null
    this._pendingDestroy = null
  }

  get connecting() {
    return this._socket !== null && this._socket.connecting
  }

  get pending() {
    return this._socket === null || this._socket.pending
  }

  get timeout() {
    return this._socket === null ? undefined : this._socket.timeout
  }

  get readyState() {
    return this._socket === null ? 'opening' : this._socket.readyState
  }

  get localAddress() {
    return this._socket === null ? undefined : this._socket.localAddress
  }

  get localPort() {
    return this._socket === null ? undefined : this._socket.localPort
  }

  get localFamily() {
    return this._socket === null ? undefined : this._socket.localFamily
  }

  get remoteAddress() {
    return this._socket === null ? undefined : this._socket.remoteAddress
  }

  get remotePort() {
    return this._socket === null ? undefined : this._socket.remotePort
  }

  get remoteFamily() {
    return this._socket === null ? undefined : this._socket.remoteFamily
  }

  connect(...args) {
    let opts = {}
    let onconnect

    if (typeof args[0] === 'string') {
      // connect(path[, onconnect])
      opts.path = args[0]
      onconnect = args[1]
    } else if (typeof args[0] === 'number') {
      // connect(port[, host][, onconnect])
      opts.port = args[0]

      if (typeof args[1] === 'function') {
        onconnect = args[1]
      } else {
        opts.host = args[1]
        onconnect = args[2]
      }
    } else {
      // connect(opts[, onconnect])
      opts = args[0] || {}
      onconnect = args[1]
    }

    opts = { ...opts, ...this._opts }

    if (opts.path) {
      this._attach(constants.type.IPC, pipe.createConnection(opts))
    } else {
      this._attach(constants.type.TCP, tcp.createConnection(opts))
    }

    if (onconnect) this.once('connect', onconnect)

    return this
  }

  setKeepAlive(...args) {
    if (this._socket !== null) this._socket.setKeepAlive(...args)
    return this
  }

  setNoDelay(...args) {
    if (this._socket !== null) this._socket.setNoDelay(...args)
    return this
  }

  setTimeout(...args) {
    if (this._socket !== null) this._socket.setTimeout(...args)
    return this
  }

  ref() {
    this._state &= ~constants.state.UNREFED
    if (this._socket !== null) this._socket.ref()
    return this
  }

  unref() {
    this._state |= constants.state.UNREFED
    if (this._socket !== null) this._socket.unref()
    return this
  }

  _attach(type, socket) {
    this._type = type
    this._socket = socket

    this._socket
      .on('connect', this._onconnect.bind(this))
      .on('timeout', this._ontimeout.bind(this))
      .on('error', this._onerror.bind(this))
      .on('data', this._ondata.bind(this))
      .on('end', this._onend.bind(this))
      .on('finish', this._onfinish.bind(this))
      .on('drain', this._ondrain.bind(this))
      .on('close', this._onclose.bind(this))

    if (this._state & constants.state.UNREFED) this._socket.unref()

    this._continueOpen()

    return this
  }

  _open(cb) {
    if (this._socket !== null) return cb(null)
    this._pendingOpen = cb
  }

  _write(data, encoding, cb) {
    if (this._socket.write(data)) return cb(null)
    this._pendingWrite = cb
  }

  _final(cb) {
    this._socket.end()
    this._pendingFinal = cb
  }

  _destroy(err, cb) {
    if (this._socket === null || this._socket.destroyed) return cb(null)

    this._socket.destroy(err)
    this._pendingDestroy = cb
  }

  _onconnect() {
    this.emit('connect')
  }

  _ontimeout() {
    this.emit('timeout')
  }

  _onerror(err) {
    this.destroy(err)
  }

  _ondata(data) {
    this.push(data)
  }

  _onend() {
    this.push(null)
  }

  _onfinish() {
    this._continueFinal()
  }

  _ondrain() {
    this._continueWrite()
  }

  _onclose() {
    this._continueWrite()
    this._continueFinal()

    if (this._pendingDestroy) this._continueDestroy()
    else this.destroy()
  }

  _continueOpen() {
    if (this._pendingOpen === null) return
    const cb = this._pendingOpen
    this._pendingOpen = null
    cb(null)
  }

  _continueWrite() {
    if (this._pendingWrite === null) return
    const cb = this._pendingWrite
    this._pendingWrite = null
    cb(null)
  }

  _continueFinal() {
    if (this._pendingFinal === null) return
    const cb = this._pendingFinal
    this._pendingFinal = null
    cb(null)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }
}

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

    super()

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = false,
      pauseOnConnect = false
    } = opts

    this._type = 0
    this._state = 0
    this._server = null

    this._opts = { readBufferSize, allowHalfOpen, pauseOnConnect }

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

  get listening() {
    return this._server !== null && this._server.listening
  }

  address() {
    return this._server === null ? null : this._server.address()
  }

  listen(...args) {
    let opts = {}
    let onlistening

    if (typeof args[0] === 'string') {
      // listen(path[, backlog][, onlistening])
      opts.path = args[0]

      if (typeof args[1] === 'function') {
        onlistening = args[1]
      } else {
        opts.backlog = args[1]
        onlistening = args[2]
      }
    } else {
      // listen([port[, host[, backlog]]][, onlistening])
      if (typeof args[0] === 'function') {
        onlistening = args[0]
      } else {
        opts.port = args[0]

        if (typeof args[1] === 'function') {
          onlistening = args[1]
        } else {
          opts.host = args[1]

          if (typeof args[2] === 'function') {
            onlistening = args[2]
          } else {
            opts.backlog = args[2]
            onlistening = args[3]
          }
        }
      }
    }

    opts = { ...opts, ...this._opts }

    if (opts.path) {
      this._attach(constants.type.IPC, pipe.createServer(opts))
    } else {
      this._attach(constants.type.TCP, tcp.createServer(opts))
    }

    this._server.listen(opts)

    if (onlistening) this.once('listening', onlistening)

    return this
  }

  close(onclose) {
    if (onclose) this.once('close', onclose)
    this._server.close()
    return this
  }

  ref() {
    this._state &= ~constants.state.UNREFED
    if (this._server !== null) this._server.ref()
    return this
  }

  unref() {
    this._state |= constants.state.UNREFED
    if (this._server !== null) this._server.unref()
    return this
  }

  _attach(type, server) {
    this._type = type
    this._server = server

    this._server
      .on('listening', this._onlistening.bind(this))
      .on('connection', this._onconnection.bind(this))
      .on('error', this._onerror.bind(this))
      .on('close', this._onclose.bind(this))

    if (this._state & constants.state.UNREFED) this._server.unref()

    return this
  }

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

  _onconnection(socket) {
    this.emit('connection', new exports.Socket(this._opts)._attach(this._type, socket))
  }

  _onerror(err) {
    this.emit('error', err)
  }

  _onclose() {
    this.emit('close')
  }
}

exports.constants = constants

exports.isIP = tcp.isIP
exports.isIPv4 = tcp.isIPv4
exports.isIPv6 = tcp.isIPv6

exports.createConnection = function createConnection(...args) {
  let opts = {}
  let onconnect

  if (typeof args[0] === 'string') {
    // createConnection(path[, onconnect])
    opts.path = args[0]
    onconnect = args[1]
  } else if (typeof args[0] === 'number') {
    // createConnection(port[, host][, onconnect])
    opts.port = args[0]

    if (typeof args[1] === 'function') {
      onconnect = args[1]
    } else {
      opts.host = args[1]
      onconnect = args[2]
    }
  } else {
    // createConnection(opts[, onconnect])
    opts = args[0] || {}
    onconnect = args[1]
  }

  return new exports.Socket(opts).connect(opts, onconnect)
}

// For Node.js compatibility
exports.connect = exports.createConnection

exports.createServer = function createServer(opts, onconnection) {
  return new exports.Server(opts, onconnection)
}