node-osc
Version:
pyOSC inspired library for sending and receiving OSC messages
156 lines (148 loc) • 4.26 kB
JavaScript
import { createSocket } from 'node:dgram';
import { EventEmitter } from 'node:events';
import decode from '#decode';
/**
* OSC Server for receiving messages and bundles over UDP.
*
* Emits the following events:
* - 'listening': Emitted when the server starts listening
* - 'message': Emitted when an OSC message is received (receives msg array and rinfo object)
* - 'bundle': Emitted when an OSC bundle is received (receives bundle object and rinfo object)
* - 'error': Emitted when a socket error or decoding error occurs (receives error and rinfo)
* - Address-specific events: Emitted for each message address (e.g., '/test')
*
* @class
* @extends EventEmitter
*
* @fires Server#listening
* @fires Server#message
* @fires Server#bundle
* @fires Server#error
*
* @example
* // Create and listen for messages
* const server = new Server(3333, '0.0.0.0', () => {
* console.log('Server is listening');
* });
*
* server.on('message', (msg, rinfo) => {
* console.log('Message:', msg);
* console.log('From:', rinfo.address, rinfo.port);
* });
*
* @example
* // Using async/await with events.once
* import { once } from 'node:events';
*
* const server = new Server(3333, '0.0.0.0');
* await once(server, 'listening');
*
* server.on('message', (msg) => {
* console.log('Message:', msg);
* });
*
* @example
* // Listen for specific OSC addresses
* server.on('/note', (msg) => {
* const [address, pitch, velocity] = msg;
* console.log(`Note: ${pitch}, Velocity: ${velocity}`);
* });
*/
class Server extends EventEmitter {
/**
* Create an OSC Server.
*
* @param {number} port - The port to listen on.
* @param {string} [host='127.0.0.1'] - The host address to bind to. Use '0.0.0.0' to listen on all interfaces.
* @param {Function} [cb] - Optional callback function called when server starts listening.
*
* @example
* // Basic server
* const server = new Server(3333);
*
* @example
* // Server on all interfaces with callback
* const server = new Server(3333, '0.0.0.0', () => {
* console.log('Server started');
* });
*
* @example
* // Host parameter can be omitted, callback as second parameter
* const server = new Server(3333, () => {
* console.log('Server started on 127.0.0.1');
* });
*/
constructor(port, host='127.0.0.1', cb) {
super();
if (typeof host === 'function') {
cb = host;
host = '127.0.0.1';
}
let decoded;
this.port = port;
this.host = host;
this._sock = createSocket({
type: 'udp4',
reuseAddr: true
});
this._sock.bind(port, host);
// Update port and emit listening event when socket is ready
this._sock.on('listening', () => {
// Update port with actual bound port (important when using port 0)
this.port = this._sock.address().port;
this.emit('listening');
if (cb) cb();
});
this._sock.on('message', (msg, rinfo) => {
try {
decoded = decode(msg);
}
catch (e) {
const error = new Error(`can't decode incoming message: ${e.message}`);
this.emit('error', error, rinfo);
return;
}
if (decoded.elements) {
this.emit('bundle', decoded, rinfo);
}
else if (decoded) {
this.emit('message', decoded, rinfo);
this.emit(decoded[0], decoded, rinfo);
}
});
this._sock.on('error', (err) => {
this.emit('error', err);
});
}
/**
* Close the server socket.
*
* This method can be used with either a callback or as a Promise.
*
* @param {Function} [cb] - Optional callback function called when socket is closed.
* @returns {Promise<void>|undefined} Returns a Promise if no callback is provided.
*
* @example
* // With callback
* server.close((err) => {
* if (err) console.error(err);
* });
*
* @example
* // With async/await
* await server.close();
*/
close(cb) {
if (cb) {
this._sock.close(cb);
} else {
return new Promise((resolve, reject) => {
this._sock.close((err) => {
if (err) reject(err);
else resolve();
});
});
}
}
}
export default Server;