Home Reference Source Test

src/plugin/dgram.js

import dgram from 'dgram'

import Plugin from './plugin'

/**
 * Status flags
 * @private
 */
const STATUS = {
  IS_NOT_INITIALIZED: -1,
  IS_CONNECTING: 0,
  IS_OPEN: 1,
  IS_CLOSING: 2,
  IS_CLOSED: 3,
}

/**
 * Default options for open method
 * @private
 */
const defaultOpenOptions = {
  host: 'localhost',
  port: 41234,
  exclusive: false,
}

/**
 * Default options for send method
 * @private
 */
const defaultSendOptions = {
  host: 'localhost',
  port: 41235,
}

/**
 * Default options
 * @private
 */
const defaultOptions = {
  type: 'udp4',
  open: defaultOpenOptions,
  send: defaultSendOptions,
}

/**
 * Helper method to merge nested objects
 * @private
 */
function mergeOptions(base, custom) {
  return {
    ...defaultOptions,
    ...base,
    ...custom,
    open: { ...defaultOptions.open, ...base.open, ...custom.open },
    send: { ...defaultOptions.send, ...base.send, ...custom.send },
  }
}

/**
 * OSC plugin for simple OSC messaging via udp client
 * and udp server
 */
export default class DatagramPlugin extends Plugin {
  /**
   * Create an OSC Plugin instance with given options. Defaults to
   * localhost:41234 for server and localhost:41235 for client messaging
   * @param {object} [options] Custom options
   * @param {string} [options.type='udp4'] 'udp4' or 'udp6'
   * @param {string} [options.open.host='localhost'] Hostname of udp server to bind to
   * @param {number} [options.open.port=41234] Port of udp server to bind to
   * @param {boolean} [options.open.exclusive=false] Exclusive flag
   * @param {string} [options.send.host='localhost'] Hostname of udp client for messaging
   * @param {number} [options.send.port=41235] Port of udp client for messaging
   *
   * @example
   * const plugin = new OSC.DatagramPlugin({ send: { port: 9912 } })
   * const osc = new OSC({ plugin: plugin })
   */
  constructor(options = {}) {
    super()

    // `dgram` gets replaced with an undefined value in builds targeting
    // browser environments
    if (!dgram) {
      throw new Error('DatagramPlugin can not be used in browser context')
    }

    /**
     * @type {object} options
     * @private
     */
    this.options = mergeOptions({}, options)

    /**
     * @type {object} socket
     * @private
     */
    this.socket = dgram.createSocket(this.options.type)
    /**
     * @type {number} socketStatus
     * @private
     */
    this.socketStatus = STATUS.IS_NOT_INITIALIZED

    // register events
    this.socket.on('message', (message, rinfo) => {
      this.notify(message, rinfo)
    })

    this.socket.on('error', (error) => {
      this.notify('error', error)
    })

    /**
     * @type {function} notify
     * @private
     */
    this.notify = () => {}
  }

  /**
   * Internal method to hook into osc library's
   * EventHandler notify method
   * @param {function} fn Notify callback
   * @private
   */
  registerNotify(fn) {
    this.notify = fn
  }

  /**
   * Returns the current status of the connection
   * @return {number} Status ID
   */
  status() {
    return this.socketStatus
  }

  /**
   * Bind a udp socket to a hostname and port
   * @param {object} [customOptions] Custom options
   * @param {string} [customOptions.host='localhost'] Hostname of udp server to bind to
   * @param {number} [customOptions.port=41234] Port of udp server to bind to
   * @param {boolean} [customOptions.exclusive=false] Exclusive flag
   */
  open(customOptions = {}) {
    const options = { ...this.options.open, ...customOptions }
    const { port, exclusive } = options

    this.socketStatus = STATUS.IS_CONNECTING

    this.socket.bind({
      address: options.host,
      port,
      exclusive,
    }, () => {
      this.socketStatus = STATUS.IS_OPEN
      this.notify('open')
    })
  }

  /**
   * Close udp socket
   */
  close() {
    this.socketStatus = STATUS.IS_CLOSING

    this.socket.close(() => {
      this.socketStatus = STATUS.IS_CLOSED
      this.notify('close')
    })
  }

  /**
   * Send an OSC Packet, Bundle or Message. Use options here for
   * custom port and hostname, otherwise the global options will
   * be taken
   * @param {Uint8Array} binary Binary representation of OSC Packet
   * @param {object} [customOptions] Custom options for udp socket
   * @param {string} [customOptions.host] Hostname of udp client
   * @param {number} [customOptions.port] Port of udp client
   */
  send(binary, customOptions = {}) {
    const options = { ...this.options.send, ...customOptions }
    const { port, host } = options

    this.socket.send(Buffer.from(binary), 0, binary.byteLength, port, host)
  }
}