src/plugin/wsserver.js
import { WebSocketServer } from 'ws'
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
* @private
*/
const defaultOptions = {
host: 'localhost',
port: 8080,
}
/**
* This will import the types for JSDoc/Type declarations without
* impacting the runtime
* @typedef {import('http').Server|import('https').Server} Server
*/
/**
* OSC plugin for a Websocket client running in node or browser context
*/
export default class WebsocketServerPlugin extends Plugin {
/**
* Create an OSC WebsocketServerPlugin instance with given options.
* Defaults to *localhost:8080* for the Websocket server
* @param {object} [options] Custom options
* @param {string} [options.host='localhost'] Hostname of Websocket server
* @param {number} [options.port=8080] Port of Websocket server
* @param {Server} [options.server] Use existing Node.js HTTP/S server
*
* @example
* const plugin = new OSC.WebsocketServerPlugin({ port: 9912 })
* const osc = new OSC({ plugin: plugin })
*
* osc.open() // start server
* @example <caption>Using an existing HTTP server</caption>
* const http = require('http')
* const httpServer = http.createServer();
* const plugin = new OSC.WebsocketServerPlugin({ server: httpServer })
* const osc = new OSC({ plugin: plugin })
*/
constructor(options) {
super()
// `WebSocketServer` gets replaced with an undefined value in builds
// targeting browser environments
if (!WebSocketServer) {
throw new Error('WebsocketServerPlugin can not be used in browser context')
}
/**
* @type {object} options
* @private
*/
this.options = { ...defaultOptions, ...options }
/**
* @type {object} socket
* @private
*/
this.socket = null
/**
* @type {number} socketStatus
* @private
*/
this.socketStatus = STATUS.IS_NOT_INITIALIZED
/**
* @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 identifier
*/
status() {
return this.socketStatus
}
/**
* Start a Websocket server. Defaults to global options
* @param {object} [customOptions] Custom options
* @param {string} [customOptions.host] Hostname of Websocket server
* @param {number} [customOptions.port] Port of Websocket server
*/
open(customOptions = {}) {
const options = { ...this.options, ...customOptions }
const { port, host } = options
const rinfo = {
address: host,
family: 'wsserver',
port,
size: 0,
}
// close socket when already given
if (this.socket) {
this.close()
}
// create websocket server
if (options.server) {
this.socket = new WebSocketServer({ server: options.server })
} else {
this.socket = new WebSocketServer({ host, port })
}
this.socket.binaryType = 'arraybuffer'
this.socketStatus = STATUS.IS_CONNECTING
// register events
this.socket.on('listening', () => {
this.socketStatus = STATUS.IS_OPEN
this.notify('open')
})
this.socket.on('error', (error) => {
this.notify('error', error)
})
this.socket.on('connection', (client) => {
client.on('message', (message) => {
this.notify(new Uint8Array(message), rinfo)
})
})
}
/**
* Close Websocket server
*/
close() {
this.socketStatus = STATUS.IS_CLOSING
this.socket.close(() => {
this.socketStatus = STATUS.IS_CLOSED
this.notify('close')
})
}
/**
* Send an OSC Packet, Bundle or Message to Websocket clients
* @param {Uint8Array} binary Binary representation of OSC Packet
*/
send(binary) {
this.socket.clients.forEach((client) => {
client.send(binary, { binary: true })
})
}
}