179 lines
4.8 KiB
JavaScript
179 lines
4.8 KiB
JavaScript
import { createSocket } from 'node:dgram';
|
|
import { EventEmitter } from 'node:events';
|
|
import { encode } from './osc.mjs';
|
|
import Message from './Message.mjs';
|
|
|
|
/**
|
|
* OSC Client for sending messages and bundles over UDP.
|
|
*
|
|
* Extends EventEmitter and emits the following events:
|
|
* - 'error': Emitted when a socket error occurs
|
|
*
|
|
* @class
|
|
* @extends EventEmitter
|
|
* @example
|
|
* // Create a client
|
|
* const client = new Client('127.0.0.1', 3333);
|
|
*
|
|
* // Send a message with callback
|
|
* client.send('/oscAddress', 200, (err) => {
|
|
* if (err) console.error(err);
|
|
* client.close();
|
|
* });
|
|
*
|
|
* @example
|
|
* // Send a message with async/await
|
|
* const client = new Client('127.0.0.1', 3333);
|
|
* await client.send('/oscAddress', 200);
|
|
* await client.close();
|
|
*/
|
|
class Client extends EventEmitter {
|
|
/**
|
|
* Create an OSC Client.
|
|
*
|
|
* @param {string} host - The hostname or IP address of the OSC server.
|
|
* @param {number} port - The port number of the OSC server.
|
|
*
|
|
* @example
|
|
* const client = new Client('127.0.0.1', 3333);
|
|
*/
|
|
constructor(host, port) {
|
|
super();
|
|
this.host = host;
|
|
this.port = port;
|
|
this._sock = createSocket({
|
|
type: 'udp4',
|
|
reuseAddr: true
|
|
});
|
|
|
|
this._sock.on('error', (err) => {
|
|
this.emit('error', err);
|
|
});
|
|
}
|
|
/**
|
|
* Close the client 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
|
|
* client.close((err) => {
|
|
* if (err) console.error(err);
|
|
* });
|
|
*
|
|
* @example
|
|
* // With async/await
|
|
* await client.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();
|
|
});
|
|
});
|
|
}
|
|
}
|
|
_performSend(message, args, callback) {
|
|
let mes;
|
|
let buf;
|
|
try {
|
|
switch (typeof message) {
|
|
case 'object':
|
|
buf = encode(message);
|
|
this._sock.send(buf, 0, buf.length, this.port, this.host, callback);
|
|
break;
|
|
case 'string':
|
|
mes = new Message(args[0]);
|
|
for (let i = 1; i < args.length; i++) {
|
|
mes.append(args[i]);
|
|
}
|
|
buf = encode(mes);
|
|
this._sock.send(buf, 0, buf.length, this.port, this.host, callback);
|
|
break;
|
|
default:
|
|
throw new TypeError('That Message Just Doesn\'t Seem Right');
|
|
}
|
|
}
|
|
catch (e) {
|
|
if (e.code !== 'ERR_SOCKET_DGRAM_NOT_RUNNING') throw e;
|
|
const error = new ReferenceError('Cannot send message on closed socket.');
|
|
error.code = e.code;
|
|
callback(error);
|
|
}
|
|
}
|
|
/**
|
|
* Send an OSC message or bundle to the server.
|
|
*
|
|
* This method can be used with either a callback or as a Promise.
|
|
* Messages can be sent in several formats:
|
|
* - As separate arguments: address followed by values
|
|
* - As a Message or Bundle object
|
|
* - As an array: [address, ...values]
|
|
*
|
|
* @param {...*} args - The message to send. Can be:
|
|
* - (address: string, ...values: any[], callback?: Function)
|
|
* - (message: Message|Bundle, callback?: Function)
|
|
* - (array: Array, callback?: Function)
|
|
* @returns {Promise<void>|undefined} Returns a Promise if no callback is provided.
|
|
*
|
|
* @throws {TypeError} If the message format is invalid.
|
|
* @throws {ReferenceError} If attempting to send on a closed socket.
|
|
*
|
|
* @example
|
|
* // Send with address and arguments
|
|
* client.send('/oscAddress', 200, 'hello', (err) => {
|
|
* if (err) console.error(err);
|
|
* });
|
|
*
|
|
* @example
|
|
* // Send with async/await
|
|
* await client.send('/oscAddress', 200, 'hello');
|
|
*
|
|
* @example
|
|
* // Send a Message object
|
|
* const msg = new Message('/test', 1, 2, 3);
|
|
* await client.send(msg);
|
|
*
|
|
* @example
|
|
* // Send a Bundle object
|
|
* const bundle = new Bundle(['/one', 1], ['/two', 2]);
|
|
* await client.send(bundle);
|
|
*/
|
|
send(...args) {
|
|
let message = args[0];
|
|
let callback;
|
|
|
|
// Convert array syntax to message object
|
|
if (message instanceof Array) {
|
|
message = {
|
|
address: message[0],
|
|
args: message.slice(1)
|
|
};
|
|
}
|
|
|
|
if (typeof args[args.length - 1] === 'function') {
|
|
callback = args.pop();
|
|
this._performSend(message, args, callback);
|
|
}
|
|
else {
|
|
// No callback provided, return a Promise
|
|
return new Promise((resolve, reject) => {
|
|
callback = (err) => {
|
|
if (err) reject(err);
|
|
else resolve();
|
|
};
|
|
this._performSend(message, args, callback);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
export default Client;
|