node-osc 11.2.3 → 11.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "npm"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ day: "monday"
8
+ time: "08:00"
9
+ timezone: "UTC"
10
+ open-pull-requests-limit: 10
11
+
12
+ - package-ecosystem: "github-actions"
13
+ directory: "/"
14
+ schedule:
15
+ interval: "weekly"
16
+ day: "monday"
17
+ time: "08:30"
18
+ timezone: "UTC"
19
+ open-pull-requests-limit: 10
package/.node-version ADDED
@@ -0,0 +1 @@
1
+ 24
@@ -12,7 +12,7 @@ This document provides context and instructions for AI agents (GitHub Copilot, C
12
12
  - Both callback and async/await APIs
13
13
  - TypeScript type definitions generated from JSDoc
14
14
  - Well-tested with comprehensive test coverage
15
- - Supports Node.js 20, 22, and 24
15
+ - Supports Node.js 20, 22, and 24+
16
16
 
17
17
  ## Architecture
18
18
 
@@ -43,9 +43,9 @@ This document provides context and instructions for AI agents (GitHub Copilot, C
43
43
  The project uses **ESM as the source format** but provides **dual ESM/CommonJS support**:
44
44
  - Source files: `lib/**/*.mjs` (ESM)
45
45
  - Built CommonJS files: `dist/lib/**/*.js` (transpiled via Rollup)
46
- - TypeScript definitions: `types/index.d.mts` (generated from JSDoc)
46
+ - Generated TypeScript definitions: `types/*.d.mts` (with `types/index.d.mts` as the package entry point)
47
47
 
48
- **Important:** The single `.d.mts` type definition file works for both ESM and CommonJS consumers.
48
+ **Important:** `types/index.d.mts` is the exported type entry point for both ESM and CommonJS consumers, and the supporting `.d.mts` files in `types/` are generated artifacts.
49
49
 
50
50
  ### Package Exports
51
51
 
@@ -95,7 +95,7 @@ npm run clean
95
95
  - Tests are written in ESM format in `test/test-*.mjs`
96
96
  - Tests are run against both ESM source (`lib/`) and transpiled CJS (`dist/`)
97
97
  - Uses `tap` test framework
98
- - Test utilities in `test/util.mjs` provide helpers like `getPort()` for getting available ports
98
+ - Tests typically use ephemeral ports via `new Server(0, ...)` and wait for readiness with `once(server, 'listening')`
99
99
  - Always run `npm run build` before running CJS tests
100
100
  - **100% test coverage is required** - All lines, branches, functions, and statements must be covered
101
101
 
@@ -122,7 +122,7 @@ The build is automatically run before publishing (`prepublishOnly` script).
122
122
  - **JSDoc comments**: All public APIs must have JSDoc comments
123
123
  - **Type annotations**: Use JSDoc types for TypeScript generation
124
124
  - **Examples**: Include code examples in JSDoc comments
125
- - **Auto-generated docs**: Run `npm run docs` after changing JSDoc comments
125
+ - **Auto-generated docs**: Run `npm run docs` after changing JSDoc comments; update `scripts/generate-docs.mjs` if the API doc layout or anchor generation needs to change
126
126
 
127
127
  Example JSDoc pattern:
128
128
  ```javascript
@@ -181,21 +181,24 @@ When writing code that needs to work in both ESM and CJS:
181
181
 
182
182
  ### Tests
183
183
  - `test/test-*.mjs` - Test files using tap framework
184
- - `test/util.mjs` - Test utilities and helpers
185
184
  - `test/fixtures/` - Test data and fixtures
186
185
 
187
186
  ### Documentation
188
187
  - `README.md` - Main documentation with quick start guide
189
188
  - `docs/API.md` - Auto-generated API reference (do not edit manually)
190
189
  - `docs/GUIDE.md` - Best practices, error handling, troubleshooting
190
+ - `docs/README.md` - Documentation hub and maintenance notes
191
191
  - `examples/` - Working example code for various use cases
192
192
 
193
193
  ### Configuration
194
+ - `.github/workflows/` - CI, release, and automation workflows
195
+ - `.github/dependabot.yml` - Dependabot configuration for npm and GitHub Actions updates
194
196
  - `package.json` - Package configuration, scripts, exports
195
197
  - `eslint.config.mjs` - ESLint configuration
196
198
  - `rollup.config.mjs` - Rollup build configuration (ESM to CJS)
197
199
  - `tsconfig.json` - TypeScript compiler options for type generation
198
200
  - `jsdoc.json` - JSDoc configuration for documentation generation
201
+ - `scripts/generate-docs.mjs` - Regenerates API documentation and handles API doc anchor/link formatting logic
199
202
 
200
203
  ## Making Changes
201
204
 
@@ -206,7 +209,7 @@ When writing code that needs to work in both ESM and CJS:
206
209
  3. **Export** from `lib/index.mjs` if it's a public API
207
210
  4. **Write tests** in `test/test-*.mjs` - **must achieve 100% coverage** (lines, branches, functions, statements)
208
211
  5. **Run tests**: `npm test` (tests both ESM and CJS)
209
- 6. **Update docs**: `npm run docs` to regenerate API.md
212
+ 6. **Update docs**: `npm run docs` to regenerate `docs/API.md`
210
213
  7. **Update README.md** if adding user-facing functionality
211
214
 
212
215
  ### Fixing a Bug
@@ -273,7 +276,7 @@ const decoded = decode(buffer);
273
276
 
274
277
  ### Test Issues
275
278
 
276
- - **Port conflicts**: Tests use dynamic port allocation via `getPort()` utility
279
+ - **Port conflicts**: Tests usually avoid conflicts by binding servers to port `0` and reading back the assigned port after the `'listening'` event
277
280
  - **Timing issues**: Use async/await and proper event handling
278
281
  - **ESM/CJS differences**: Ensure code works in both environments
279
282
 
package/README.md CHANGED
@@ -52,11 +52,13 @@ server.on('message', (msg) => {
52
52
  - 📘 **[Usage Guide](./docs/GUIDE.md)** - Best practices, error handling, and troubleshooting
53
53
  - 📖 **[Examples](./examples/)** - Working examples for various use cases
54
54
 
55
+ The API reference is generated from the source JSDoc comments. If you change a public API or its JSDoc, run `npm run docs` and review `docs/API.md`.
56
+
55
57
  ## Compatibility
56
58
 
57
- Written using ESM, supports CJS.
59
+ Written as ESM and published with both ESM and CommonJS entry points.
58
60
 
59
- Supports the latest versions of Node.js 20, 22, and 24 in both ESM + CJS.
61
+ Supports Node.js 20, 22, and 24+ in both ESM and CJS environments.
60
62
 
61
63
  ## TypeScript
62
64
 
@@ -2,8 +2,7 @@
2
2
 
3
3
  var node_dgram = require('node:dgram');
4
4
  var node_events = require('node:events');
5
- var osc = require('./osc.js');
6
- var Message = require('./Message.js');
5
+ var send = require('./internal/send.js');
7
6
 
8
7
  /**
9
8
  * OSC Client for sending messages and bundles over UDP.
@@ -82,34 +81,6 @@ class Client extends node_events.EventEmitter {
82
81
  });
83
82
  }
84
83
  }
85
- _performSend(message, args, callback) {
86
- let mes;
87
- let buf;
88
- try {
89
- switch (typeof message) {
90
- case 'object':
91
- buf = osc.encode(message);
92
- this._sock.send(buf, 0, buf.length, this.port, this.host, callback);
93
- break;
94
- case 'string':
95
- mes = new Message(args[0]);
96
- for (let i = 1; i < args.length; i++) {
97
- mes.append(args[i]);
98
- }
99
- buf = osc.encode(mes);
100
- this._sock.send(buf, 0, buf.length, this.port, this.host, callback);
101
- break;
102
- default:
103
- throw new TypeError('That Message Just Doesn\'t Seem Right');
104
- }
105
- }
106
- catch (e) {
107
- if (e.code !== 'ERR_SOCKET_DGRAM_NOT_RUNNING') throw e;
108
- const error = new ReferenceError('Cannot send message on closed socket.');
109
- error.code = e.code;
110
- callback(error);
111
- }
112
- }
113
84
  /**
114
85
  * Send an OSC message or bundle to the server.
115
86
  *
@@ -149,20 +120,12 @@ class Client extends node_events.EventEmitter {
149
120
  * await client.send(bundle);
150
121
  */
151
122
  send(...args) {
152
- let message = args[0];
153
123
  let callback;
154
-
155
- // Convert array syntax to message object
156
- if (message instanceof Array) {
157
- message = {
158
- address: message[0],
159
- args: message.slice(1)
160
- };
161
- }
124
+ const message = args.shift();
162
125
 
163
126
  if (typeof args[args.length - 1] === 'function') {
164
127
  callback = args.pop();
165
- this._performSend(message, args, callback);
128
+ send(this._sock, message, args, this.port, this.host, callback);
166
129
  }
167
130
  else {
168
131
  // No callback provided, return a Promise
@@ -171,7 +134,7 @@ class Client extends node_events.EventEmitter {
171
134
  if (err) reject(err);
172
135
  else resolve();
173
136
  };
174
- this._performSend(message, args, callback);
137
+ send(this._sock, message, args, this.port, this.host, callback);
175
138
  });
176
139
  }
177
140
  }
@@ -2,8 +2,13 @@
2
2
 
3
3
  var node_dgram = require('node:dgram');
4
4
  var node_events = require('node:events');
5
+ var send = require('./internal/send.js');
5
6
  var decode = require('#decode');
6
7
 
8
+ function createSocketNotReadyError() {
9
+ return new Error('Cannot send message before server is listening. Wait for the "listening" event.');
10
+ }
11
+
7
12
  /**
8
13
  * OSC Server for receiving messages and bundles over UDP.
9
14
  *
@@ -85,6 +90,8 @@ class Server extends node_events.EventEmitter {
85
90
  let decoded;
86
91
  this.port = port;
87
92
  this.host = host;
93
+ this._isListening = false;
94
+ this._isClosed = false;
88
95
  this._sock = node_dgram.createSocket({
89
96
  type: 'udp4',
90
97
  reuseAddr: true
@@ -94,6 +101,8 @@ class Server extends node_events.EventEmitter {
94
101
  // Update port and emit listening event when socket is ready
95
102
  this._sock.on('listening', () => {
96
103
  // Update port with actual bound port (important when using port 0)
104
+ this._isListening = true;
105
+ this._isClosed = false;
97
106
  this.port = this._sock.address().port;
98
107
  this.emit('listening');
99
108
  if (cb) cb();
@@ -120,6 +129,62 @@ class Server extends node_events.EventEmitter {
120
129
  this._sock.on('error', (err) => {
121
130
  this.emit('error', err);
122
131
  });
132
+
133
+ this._sock.on('close', () => {
134
+ this._isListening = false;
135
+ this._isClosed = true;
136
+ });
137
+ }
138
+ /**
139
+ * Send an OSC message or bundle from the server's bound socket.
140
+ *
141
+ * This method can be used with either a callback or as a Promise.
142
+ *
143
+ * @param {import('./Message.mjs').default|import('./Bundle.mjs').default|Array|string} message - The message, bundle, address, or array to send.
144
+ * @param {number} port - The remote port to send to.
145
+ * @param {string} host - The remote host to send to.
146
+ * @param {Function} [cb] - Optional callback function called when send completes.
147
+ * @returns {Promise<void>|undefined} Returns a Promise if no callback is provided.
148
+ *
149
+ * @throws {Error} If the server socket is not yet listening.
150
+ * @throws {TypeError} If the message format is invalid.
151
+ * @throws {ReferenceError} If attempting to send on a closed socket.
152
+ *
153
+ * @example
154
+ * // Send an address-only message
155
+ * await server.send('/ping', 9000, '127.0.0.1');
156
+ *
157
+ * @example
158
+ * // Send an array message
159
+ * server.send(['/ack', 1], 9000, '192.168.1.42', (err) => {
160
+ * if (err) console.error(err);
161
+ * });
162
+ */
163
+ send(message, port, host, cb) {
164
+ if (!this._isListening && !this._isClosed) {
165
+ const error = createSocketNotReadyError();
166
+
167
+ if (cb) {
168
+ cb(error);
169
+ return;
170
+ }
171
+
172
+ throw error;
173
+ }
174
+
175
+ if (cb) {
176
+ send(this._sock, message, [], port, host, cb);
177
+ }
178
+ else {
179
+ return new Promise((resolve, reject) => {
180
+ const callback = (err) => {
181
+ if (err) reject(err);
182
+ else resolve();
183
+ };
184
+
185
+ send(this._sock, message, [], port, host, callback);
186
+ });
187
+ }
123
188
  }
124
189
  /**
125
190
  * Close the server socket.
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ var osc = require('../osc.js');
4
+ var Message = require('../Message.js');
5
+
6
+ function normalizeMessage(message) {
7
+ if (message instanceof Array) {
8
+ return {
9
+ address: message[0],
10
+ args: message.slice(1)
11
+ };
12
+ }
13
+
14
+ return message;
15
+ }
16
+
17
+ function performSend(sock, message, args, port, host, callback) {
18
+ let mes;
19
+ let buf;
20
+ const normalizedMessage = normalizeMessage(message);
21
+
22
+ try {
23
+ switch (typeof normalizedMessage) {
24
+ case 'object':
25
+ buf = osc.encode(normalizedMessage);
26
+ sock.send(buf, 0, buf.length, port, host, callback);
27
+ break;
28
+ case 'string':
29
+ mes = new Message(normalizedMessage);
30
+ for (const arg of args) {
31
+ mes.append(arg);
32
+ }
33
+ buf = osc.encode(mes);
34
+ sock.send(buf, 0, buf.length, port, host, callback);
35
+ break;
36
+ default:
37
+ throw new TypeError('That Message Just Doesn\'t Seem Right');
38
+ }
39
+ }
40
+ catch (e) {
41
+ if (e.code !== 'ERR_SOCKET_DGRAM_NOT_RUNNING') throw e;
42
+ const error = new ReferenceError('Cannot send message on closed socket.');
43
+ error.code = e.code;
44
+ callback(error);
45
+ }
46
+ }
47
+
48
+ module.exports = performSend;
@@ -141,6 +141,22 @@ tap.test('server: close with promise', async (t) => {
141
141
  t.pass('Server closed successfully with promise');
142
142
  });
143
143
 
144
+ tap.test('server: send promise rejection on closed socket', async (t) => {
145
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
146
+
147
+ t.plan(1);
148
+
149
+ await node_events.once(oscServer, 'listening');
150
+ await oscServer.close();
151
+
152
+ try {
153
+ await oscServer.send('/boom', 3333, '127.0.0.1');
154
+ t.fail('Should have thrown an error');
155
+ } catch (err) {
156
+ t.equal(err.code, 'ERR_SOCKET_DGRAM_NOT_RUNNING', 'Should reject with correct error code');
157
+ }
158
+ });
159
+
144
160
  tap.test('server: no callback still emits listening event', async (t) => {
145
161
  const oscServer = new nodeOsc.Server(0, '127.0.0.1');
146
162
 
@@ -89,6 +89,67 @@ tap.test('server: callback as second arg', async (t) => {
89
89
  });
90
90
  });
91
91
 
92
+ tap.test('server: send to remote port', async (t) => {
93
+ const sender = new nodeOsc.Server(0, '127.0.0.1');
94
+ const receiver = new nodeOsc.Server(0, '127.0.0.1');
95
+ await Promise.all([node_events.once(sender, 'listening'), node_events.once(receiver, 'listening')]);
96
+
97
+ t.plan(1);
98
+
99
+ t.teardown(async () => {
100
+ await Promise.all([sender.close(), receiver.close()]);
101
+ });
102
+
103
+ const receivedMessage = node_events.once(receiver, 'message');
104
+ await sender.send(['/test', 1], receiver.port, '127.0.0.1');
105
+ const [msg] = await receivedMessage;
106
+
107
+ t.same(msg, ['/test', 1], 'server should send a message from its bound socket');
108
+ });
109
+
110
+ tap.test('server: can receive and reply from within message handler', async (t) => {
111
+ const requester = new nodeOsc.Server(0, '127.0.0.1');
112
+ const responder = new nodeOsc.Server(0, '127.0.0.1');
113
+ await Promise.all([node_events.once(requester, 'listening'), node_events.once(responder, 'listening')]);
114
+
115
+ t.plan(3);
116
+
117
+ t.teardown(async () => {
118
+ await Promise.all([requester.close(), responder.close()]);
119
+ });
120
+
121
+ responder.on('message', (msg, rinfo) => {
122
+ t.same(msg, ['/ping', 1], 'responder should receive the incoming message');
123
+ responder.send(['/ack', 1], rinfo.port, rinfo.address, (err) => {
124
+ t.error(err, 'responder should reply without error');
125
+ });
126
+ });
127
+
128
+ const receivedReply = node_events.once(requester, 'message');
129
+ await requester.send(['/ping', 1], responder.port, '127.0.0.1');
130
+ const [reply] = await receivedReply;
131
+
132
+ t.same(reply, ['/ack', 1], 'requester should receive the reply on the same socket');
133
+ });
134
+
135
+ tap.test('server: send before listening returns a clear error', async (t) => {
136
+ t.plan(3);
137
+
138
+ const server = new nodeOsc.Server(0, '127.0.0.1');
139
+
140
+ t.throws(() => {
141
+ server.send('/test', 3333, '127.0.0.1');
142
+ }, /before server is listening/i, 'send without callback should throw before listening');
143
+
144
+ server.send('/test', 3333, '127.0.0.1', (err) => {
145
+ t.ok(err instanceof Error, 'send should fail with an Error before listening');
146
+ t.match(err.message, /before server is listening/i, 'error message should explain the socket is not ready');
147
+ });
148
+
149
+ await node_events.once(server, 'listening');
150
+ await server.close();
151
+ });
152
+
92
153
  tap.test('server: bad message', async (t) => {
93
154
  t.plan(2);
94
155
  const oscServer = new nodeOsc.Server(0, '127.0.0.1');
package/docs/API.md CHANGED
@@ -13,17 +13,17 @@ For usage guides, best practices, and troubleshooting, see the **[Guide](./GUIDE
13
13
 
14
14
  - [Server](#server)
15
15
  - [Constructor](#server-constructor)
16
- - [close()](#server-close)
16
+ - [close()](#serverclose)
17
17
  - [Client](#client)
18
18
  - [Constructor](#client-constructor)
19
- - [close()](#client-close)
20
- - [send()](#client-send)
19
+ - [close()](#clientclose)
20
+ - [send()](#clientsend)
21
21
  - [Message](#message)
22
22
  - [Constructor](#message-constructor)
23
- - [append()](#message-append)
23
+ - [append()](#messageappend)
24
24
  - [Bundle](#bundle)
25
25
  - [Constructor](#bundle-constructor)
26
- - [append()](#bundle-append)
26
+ - [append()](#bundleappend)
27
27
  - [Low Level Functions](#low-level-functions)
28
28
  - [encode()](#encode)
29
29
  - [decode()](#decode)
package/docs/GUIDE.md CHANGED
@@ -9,6 +9,8 @@ This guide provides best practices, patterns, and detailed information for using
9
9
  - [Type System](#type-system)
10
10
  - [Best Practices](#best-practices)
11
11
  - [Troubleshooting](#troubleshooting)
12
+ - [Advanced Topics](#advanced-topics)
13
+ - [Further Reading](#further-reading)
12
14
 
13
15
  ## Events
14
16
 
@@ -483,10 +485,12 @@ await client.close();
483
485
  Ensure you wait for the server to start before sending messages:
484
486
 
485
487
  ```javascript
488
+ import { once } from 'node:events';
489
+
486
490
  const server = new Server(3333, '0.0.0.0');
487
491
 
488
492
  // Wait for server to be ready
489
- await new Promise(resolve => server.on('listening', resolve));
493
+ await once(server, 'listening');
490
494
 
491
495
  // Now safe to send messages
492
496
  console.log('Server ready!');
package/docs/README.md CHANGED
@@ -56,7 +56,7 @@ A comprehensive guide covering:
56
56
 
57
57
  The API documentation is automatically generated from JSDoc comments:
58
58
 
59
- 1. Edit JSDoc comments in the source files (`lib/**/*.mjs`)
59
+ 1. Edit JSDoc comments in the source files (`lib/**/*.mjs`). If the docs layout or anchor generation needs to change, update `scripts/generate-docs.mjs`.
60
60
  2. Run `npm run docs` to regenerate `API.md`
61
61
  3. Review the changes and commit
62
62
 
package/lib/Client.mjs CHANGED
@@ -1,7 +1,6 @@
1
1
  import { createSocket } from 'node:dgram';
2
2
  import { EventEmitter } from 'node:events';
3
- import { encode } from './osc.mjs';
4
- import Message from './Message.mjs';
3
+ import performSend from './internal/send.mjs';
5
4
 
6
5
  /**
7
6
  * OSC Client for sending messages and bundles over UDP.
@@ -80,34 +79,6 @@ class Client extends EventEmitter {
80
79
  });
81
80
  }
82
81
  }
83
- _performSend(message, args, callback) {
84
- let mes;
85
- let buf;
86
- try {
87
- switch (typeof message) {
88
- case 'object':
89
- buf = encode(message);
90
- this._sock.send(buf, 0, buf.length, this.port, this.host, callback);
91
- break;
92
- case 'string':
93
- mes = new Message(args[0]);
94
- for (let i = 1; i < args.length; i++) {
95
- mes.append(args[i]);
96
- }
97
- buf = encode(mes);
98
- this._sock.send(buf, 0, buf.length, this.port, this.host, callback);
99
- break;
100
- default:
101
- throw new TypeError('That Message Just Doesn\'t Seem Right');
102
- }
103
- }
104
- catch (e) {
105
- if (e.code !== 'ERR_SOCKET_DGRAM_NOT_RUNNING') throw e;
106
- const error = new ReferenceError('Cannot send message on closed socket.');
107
- error.code = e.code;
108
- callback(error);
109
- }
110
- }
111
82
  /**
112
83
  * Send an OSC message or bundle to the server.
113
84
  *
@@ -147,20 +118,12 @@ class Client extends EventEmitter {
147
118
  * await client.send(bundle);
148
119
  */
149
120
  send(...args) {
150
- let message = args[0];
151
121
  let callback;
152
-
153
- // Convert array syntax to message object
154
- if (message instanceof Array) {
155
- message = {
156
- address: message[0],
157
- args: message.slice(1)
158
- };
159
- }
122
+ const message = args.shift();
160
123
 
161
124
  if (typeof args[args.length - 1] === 'function') {
162
125
  callback = args.pop();
163
- this._performSend(message, args, callback);
126
+ performSend(this._sock, message, args, this.port, this.host, callback);
164
127
  }
165
128
  else {
166
129
  // No callback provided, return a Promise
@@ -169,7 +132,7 @@ class Client extends EventEmitter {
169
132
  if (err) reject(err);
170
133
  else resolve();
171
134
  };
172
- this._performSend(message, args, callback);
135
+ performSend(this._sock, message, args, this.port, this.host, callback);
173
136
  });
174
137
  }
175
138
  }
package/lib/Server.mjs CHANGED
@@ -1,8 +1,13 @@
1
1
  import { createSocket } from 'node:dgram';
2
2
  import { EventEmitter } from 'node:events';
3
+ import performSend from './internal/send.mjs';
3
4
 
4
5
  import decode from '#decode';
5
6
 
7
+ function createSocketNotReadyError() {
8
+ return new Error('Cannot send message before server is listening. Wait for the "listening" event.');
9
+ }
10
+
6
11
  /**
7
12
  * OSC Server for receiving messages and bundles over UDP.
8
13
  *
@@ -84,6 +89,8 @@ class Server extends EventEmitter {
84
89
  let decoded;
85
90
  this.port = port;
86
91
  this.host = host;
92
+ this._isListening = false;
93
+ this._isClosed = false;
87
94
  this._sock = createSocket({
88
95
  type: 'udp4',
89
96
  reuseAddr: true
@@ -93,6 +100,8 @@ class Server extends EventEmitter {
93
100
  // Update port and emit listening event when socket is ready
94
101
  this._sock.on('listening', () => {
95
102
  // Update port with actual bound port (important when using port 0)
103
+ this._isListening = true;
104
+ this._isClosed = false;
96
105
  this.port = this._sock.address().port;
97
106
  this.emit('listening');
98
107
  if (cb) cb();
@@ -119,6 +128,62 @@ class Server extends EventEmitter {
119
128
  this._sock.on('error', (err) => {
120
129
  this.emit('error', err);
121
130
  });
131
+
132
+ this._sock.on('close', () => {
133
+ this._isListening = false;
134
+ this._isClosed = true;
135
+ });
136
+ }
137
+ /**
138
+ * Send an OSC message or bundle from the server's bound socket.
139
+ *
140
+ * This method can be used with either a callback or as a Promise.
141
+ *
142
+ * @param {import('./Message.mjs').default|import('./Bundle.mjs').default|Array|string} message - The message, bundle, address, or array to send.
143
+ * @param {number} port - The remote port to send to.
144
+ * @param {string} host - The remote host to send to.
145
+ * @param {Function} [cb] - Optional callback function called when send completes.
146
+ * @returns {Promise<void>|undefined} Returns a Promise if no callback is provided.
147
+ *
148
+ * @throws {Error} If the server socket is not yet listening.
149
+ * @throws {TypeError} If the message format is invalid.
150
+ * @throws {ReferenceError} If attempting to send on a closed socket.
151
+ *
152
+ * @example
153
+ * // Send an address-only message
154
+ * await server.send('/ping', 9000, '127.0.0.1');
155
+ *
156
+ * @example
157
+ * // Send an array message
158
+ * server.send(['/ack', 1], 9000, '192.168.1.42', (err) => {
159
+ * if (err) console.error(err);
160
+ * });
161
+ */
162
+ send(message, port, host, cb) {
163
+ if (!this._isListening && !this._isClosed) {
164
+ const error = createSocketNotReadyError();
165
+
166
+ if (cb) {
167
+ cb(error);
168
+ return;
169
+ }
170
+
171
+ throw error;
172
+ }
173
+
174
+ if (cb) {
175
+ performSend(this._sock, message, [], port, host, cb);
176
+ }
177
+ else {
178
+ return new Promise((resolve, reject) => {
179
+ const callback = (err) => {
180
+ if (err) reject(err);
181
+ else resolve();
182
+ };
183
+
184
+ performSend(this._sock, message, [], port, host, callback);
185
+ });
186
+ }
122
187
  }
123
188
  /**
124
189
  * Close the server socket.
@@ -0,0 +1,46 @@
1
+ import { encode } from '../osc.mjs';
2
+ import Message from '../Message.mjs';
3
+
4
+ function normalizeMessage(message) {
5
+ if (message instanceof Array) {
6
+ return {
7
+ address: message[0],
8
+ args: message.slice(1)
9
+ };
10
+ }
11
+
12
+ return message;
13
+ }
14
+
15
+ function performSend(sock, message, args, port, host, callback) {
16
+ let mes;
17
+ let buf;
18
+ const normalizedMessage = normalizeMessage(message);
19
+
20
+ try {
21
+ switch (typeof normalizedMessage) {
22
+ case 'object':
23
+ buf = encode(normalizedMessage);
24
+ sock.send(buf, 0, buf.length, port, host, callback);
25
+ break;
26
+ case 'string':
27
+ mes = new Message(normalizedMessage);
28
+ for (const arg of args) {
29
+ mes.append(arg);
30
+ }
31
+ buf = encode(mes);
32
+ sock.send(buf, 0, buf.length, port, host, callback);
33
+ break;
34
+ default:
35
+ throw new TypeError('That Message Just Doesn\'t Seem Right');
36
+ }
37
+ }
38
+ catch (e) {
39
+ if (e.code !== 'ERR_SOCKET_DGRAM_NOT_RUNNING') throw e;
40
+ const error = new ReferenceError('Cannot send message on closed socket.');
41
+ error.code = e.code;
42
+ callback(error);
43
+ }
44
+ }
45
+
46
+ export default performSend;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "node-osc",
3
3
  "description": "pyOSC inspired library for sending and receiving OSC messages",
4
- "version": "11.2.3",
4
+ "version": "11.3.0",
5
5
  "exports": {
6
6
  "types": "./types/index.d.mts",
7
7
  "require": "./dist/lib/index.js",
@@ -47,9 +47,9 @@
47
47
  "url": "git+https://github.com/MylesBorins/node-osc.git"
48
48
  },
49
49
  "devDependencies": {
50
- "@eslint/js": "^9.32.0",
51
- "eslint": "^9.32.0",
52
- "globals": "^16.3.0",
50
+ "@eslint/js": "^10.0.1",
51
+ "eslint": "^10.0.3",
52
+ "globals": "^17.4.0",
53
53
  "jsdoc": "^4.0.5",
54
54
  "rollup": "^4.46.2",
55
55
  "tap": "^21.1.0",
@@ -5,120 +5,89 @@
5
5
  * This script reads JSDoc JSON data and generates a formatted Markdown file.
6
6
  */
7
7
 
8
- import { writeFileSync } from 'node:fs';
9
8
  import { execSync } from 'node:child_process';
9
+ import { writeFileSync } from 'node:fs';
10
10
 
11
- // Generate JSDoc JSON
12
- let jsdocJson;
13
- try {
14
- jsdocJson = execSync('npx jsdoc -X -c jsdoc.json', {
15
- encoding: 'utf8',
16
- maxBuffer: 10 * 1024 * 1024
17
- });
18
- } catch (error) {
19
- console.error('❌ Failed to run JSDoc:');
20
- console.error(error.message);
21
- process.exit(1);
22
- }
11
+ const classOrder = ['Server', 'Client', 'Message', 'Bundle'];
12
+ const functionOrder = ['encode', 'decode'];
23
13
 
24
- let docs;
25
- try {
26
- docs = JSON.parse(jsdocJson);
27
- } catch (error) {
28
- console.error('❌ Failed to parse JSDoc JSON output:');
29
- console.error(error.message);
30
- process.exit(1);
14
+ function githubAnchor(text) {
15
+ return text
16
+ .trim()
17
+ .toLowerCase()
18
+ .replace(/[^\w\- ]+/g, '')
19
+ .replace(/\s+/g, '-');
31
20
  }
32
21
 
33
- // Filter and organize documentation
34
- const classes = {};
35
- const functions = {};
36
-
37
- docs.forEach(item => {
38
- if (item.undocumented || item.ignore) return;
39
-
40
- if (item.kind === 'class' && item.classdesc) {
41
- if (!classes[item.name]) {
42
- classes[item.name] = {
43
- desc: item.classdesc,
44
- constructor: null,
45
- methods: [],
46
- examples: item.examples || [],
47
- augments: item.augments || []
48
- };
49
- }
50
- // Look for constructor params
51
- if (item.params) {
52
- classes[item.name].constructor = {
53
- params: item.params,
54
- examples: item.examples || []
55
- };
56
- }
57
- } else if (item.kind === 'function' && item.memberof) {
58
- // Method of a class
59
- const className = item.memberof;
60
- if (!classes[className]) {
61
- classes[className] = {
62
- desc: '',
63
- constructor: null,
64
- methods: [],
65
- examples: []
66
- };
67
- }
68
- classes[className].methods.push(item);
69
- } else if (item.kind === 'function' && !item.memberof && item.scope === 'global') {
70
- // Top-level function
71
- functions[item.name] = item;
22
+ function runJsdoc() {
23
+ try {
24
+ return execSync('npx jsdoc -X -c jsdoc.json', {
25
+ encoding: 'utf8',
26
+ maxBuffer: 10 * 1024 * 1024
27
+ });
28
+ } catch (error) {
29
+ throw new Error(`Failed to run JSDoc: ${error.message}`, { cause: error });
72
30
  }
73
- });
74
-
75
- // Generate Markdown
76
- let markdown = `<!-- Generated by JSDoc. Update this documentation by updating the source code. -->
77
-
78
- # API Reference
79
-
80
- > **⚠️ This file is auto-generated from JSDoc comments in the source code.**
81
- > To update this documentation, edit the JSDoc comments in the source files and run \`npm run docs\`.
82
-
83
- This document provides detailed API reference for all classes, methods, and functions in node-osc.
31
+ }
84
32
 
85
- For usage guides, best practices, and troubleshooting, see the **[Guide](./GUIDE.md)**.
33
+ function parseJsdocJson(jsdocJson) {
34
+ try {
35
+ return JSON.parse(jsdocJson);
36
+ } catch (error) {
37
+ throw new Error(`Failed to parse JSDoc JSON output: ${error.message}`, { cause: error });
38
+ }
39
+ }
86
40
 
87
- ## Table of Contents
41
+ function collectDocs(docs) {
42
+ const classes = {};
43
+ const functions = {};
88
44
 
89
- `;
45
+ docs.forEach(item => {
46
+ if (item.undocumented || item.ignore) return;
90
47
 
91
- // Define order: Server Client → Message → Bundle → Low Level
92
- const classOrder = ['Server', 'Client', 'Message', 'Bundle'];
93
- const functionOrder = ['encode', 'decode'];
48
+ if (item.kind === 'class' && item.classdesc) {
49
+ const classInfo = classes[item.name] || {
50
+ desc: '',
51
+ constructor: null,
52
+ methods: [],
53
+ examples: [],
54
+ augments: []
55
+ };
94
56
 
95
- // Add classes to TOC
96
- classOrder.forEach(name => {
97
- if (classes[name]) {
98
- markdown += `- [${name}](#${name.toLowerCase()})\n`;
99
- if (classes[name].constructor) {
100
- markdown += ` - [Constructor](#${name.toLowerCase()}-constructor)\n`;
57
+ classInfo.desc = item.classdesc;
58
+ classInfo.examples = item.examples || [];
59
+ classInfo.augments = item.augments || [];
60
+ classes[item.name] = classInfo;
61
+
62
+ if (item.params) {
63
+ classes[item.name].constructor = {
64
+ params: item.params,
65
+ examples: item.examples || []
66
+ };
67
+ }
68
+ } else if (item.kind === 'function' && item.memberof) {
69
+ const className = item.memberof;
70
+ if (!classes[className]) {
71
+ classes[className] = {
72
+ desc: '',
73
+ constructor: null,
74
+ methods: [],
75
+ examples: [],
76
+ augments: []
77
+ };
78
+ }
79
+ classes[className].methods.push(item);
80
+ } else if (item.kind === 'function' && !item.memberof && item.scope === 'global') {
81
+ functions[item.name] = item;
101
82
  }
102
- classes[name].methods.forEach(method => {
103
- markdown += ` - [${method.name}()](#${name.toLowerCase()}-${method.name.toLowerCase()})\n`;
104
- });
105
- }
106
- });
107
-
108
- // Add functions to TOC
109
- markdown += `- [Low Level Functions](#low-level-functions)\n`;
110
- functionOrder.forEach(name => {
111
- if (functions[name]) {
112
- markdown += ` - [${name}()](#${name.toLowerCase()})\n`;
113
- }
114
- });
83
+ });
115
84
 
116
- markdown += `\n---\n\n`;
85
+ return { classes, functions };
86
+ }
117
87
 
118
- // Helper function to format parameters
119
88
  function formatParams(params) {
120
89
  if (!params || params.length === 0) return '';
121
-
90
+
122
91
  let result = '\n**Parameters:**\n\n';
123
92
  params.forEach(param => {
124
93
  const optional = param.optional ? ' (optional)' : '';
@@ -129,10 +98,9 @@ function formatParams(params) {
129
98
  return result;
130
99
  }
131
100
 
132
- // Helper function to format examples
133
101
  function formatExamples(examples) {
134
102
  if (!examples || examples.length === 0) return '';
135
-
103
+
136
104
  let result = '\n**Examples:**\n\n';
137
105
  examples.forEach(example => {
138
106
  result += '```javascript\n' + example + '\n```\n\n';
@@ -140,19 +108,17 @@ function formatExamples(examples) {
140
108
  return result;
141
109
  }
142
110
 
143
- // Helper function to format returns
144
111
  function formatReturns(returns) {
145
112
  if (!returns || returns.length === 0) return '';
146
-
113
+
147
114
  const ret = returns[0];
148
115
  const types = ret.type ? ret.type.names.join(' | ') : 'any';
149
116
  return `\n**Returns:** *{${types}}* - ${ret.description || ''}\n`;
150
117
  }
151
118
 
152
- // Helper function to format throws
153
119
  function formatThrows(exceptions) {
154
120
  if (!exceptions || exceptions.length === 0) return '';
155
-
121
+
156
122
  let result = '\n**Throws:**\n\n';
157
123
  exceptions.forEach(ex => {
158
124
  const types = ex.type ? ex.type.names.join(' | ') : 'Error';
@@ -161,69 +127,109 @@ function formatThrows(exceptions) {
161
127
  return result;
162
128
  }
163
129
 
164
- // Generate class documentation
165
- classOrder.forEach(className => {
166
- const classInfo = classes[className];
167
- if (!classInfo) return;
168
-
169
- markdown += `## ${className}\n\n`;
170
-
171
- // Add extends info
172
- if (classInfo.augments && classInfo.augments.length > 0) {
173
- markdown += `**Extends:** ${classInfo.augments.join(', ')}\n\n`;
174
- }
175
-
176
- markdown += `${classInfo.desc}\n`;
177
-
178
- // Class-level examples
179
- if (classInfo.examples.length > 0 && !classInfo.constructor) {
180
- markdown += formatExamples(classInfo.examples);
181
- }
182
-
183
- // Constructor
184
- if (classInfo.constructor) {
185
- markdown += `\n### ${className} Constructor\n\n`;
186
- markdown += `Creates a new ${className} instance.\n`;
187
- markdown += formatParams(classInfo.constructor.params);
188
- markdown += formatExamples(classInfo.constructor.examples);
189
- }
190
-
191
- // Methods
192
- classInfo.methods.forEach(method => {
193
- markdown += `\n### ${className}.${method.name}()\n\n`;
194
- markdown += `${method.description || ''}\n`;
195
- markdown += formatParams(method.params);
196
- markdown += formatReturns(method.returns);
197
- markdown += formatThrows(method.exceptions);
198
- markdown += formatExamples(method.examples);
130
+ function generateMarkdown(classes, functions) {
131
+ let markdown = `<!-- Generated by JSDoc. Update this documentation by updating the source code. -->
132
+
133
+ # API Reference
134
+
135
+ > **⚠️ This file is auto-generated from JSDoc comments in the source code.**
136
+ > To update this documentation, edit the JSDoc comments in the source files and run \`npm run docs\`.
137
+
138
+ This document provides detailed API reference for all classes, methods, and functions in node-osc.
139
+
140
+ For usage guides, best practices, and troubleshooting, see the **[Guide](./GUIDE.md)**.
141
+
142
+ ## Table of Contents
143
+
144
+ `;
145
+
146
+ classOrder.forEach(name => {
147
+ if (classes[name]) {
148
+ markdown += `- [${name}](#${githubAnchor(name)})\n`;
149
+ if (classes[name].constructor) {
150
+ markdown += ` - [Constructor](#${githubAnchor(`${name} Constructor`)})\n`;
151
+ }
152
+ classes[name].methods.forEach(method => {
153
+ markdown += ` - [${method.name}()](#${githubAnchor(`${name}.${method.name}()`)})\n`;
154
+ });
155
+ }
156
+ });
157
+
158
+ markdown += `- [Low Level Functions](#${githubAnchor('Low Level Functions')})\n`;
159
+ functionOrder.forEach(name => {
160
+ if (functions[name]) {
161
+ markdown += ` - [${name}()](#${githubAnchor(`${name}()`)})\n`;
162
+ }
199
163
  });
200
-
164
+
201
165
  markdown += `\n---\n\n`;
202
- });
203
-
204
- // Generate function documentation
205
- markdown += `## Low Level Functions\n\n`;
206
- markdown += `These functions provide low-level access to OSC encoding and decoding for advanced use cases.\n\n`;
207
-
208
- functionOrder.forEach(funcName => {
209
- const func = functions[funcName];
210
- if (!func) return;
211
-
212
- markdown += `### ${funcName}()\n\n`;
213
- markdown += `${func.description || ''}\n`;
214
- markdown += formatParams(func.params);
215
- markdown += formatReturns(func.returns);
216
- markdown += formatThrows(func.exceptions);
217
- markdown += formatExamples(func.examples);
218
- markdown += `\n`;
219
- });
220
-
221
- // Write output
222
- try {
223
- writeFileSync('docs/API.md', markdown, 'utf8');
224
- console.log('✅ API documentation generated: docs/API.md');
225
- } catch (error) {
226
- console.error('❌ Failed to write API.md:');
227
- console.error(error.message);
228
- process.exit(1);
166
+
167
+ classOrder.forEach(className => {
168
+ const classInfo = classes[className];
169
+ if (!classInfo) return;
170
+
171
+ markdown += `## ${className}\n\n`;
172
+
173
+ if (classInfo.augments && classInfo.augments.length > 0) {
174
+ markdown += `**Extends:** ${classInfo.augments.join(', ')}\n\n`;
175
+ }
176
+
177
+ markdown += `${classInfo.desc}\n`;
178
+
179
+ if (classInfo.examples.length > 0 && !classInfo.constructor) {
180
+ markdown += formatExamples(classInfo.examples);
181
+ }
182
+
183
+ if (classInfo.constructor) {
184
+ markdown += `\n### ${className} Constructor\n\n`;
185
+ markdown += `Creates a new ${className} instance.\n`;
186
+ markdown += formatParams(classInfo.constructor.params);
187
+ markdown += formatExamples(classInfo.constructor.examples);
188
+ }
189
+
190
+ classInfo.methods.forEach(method => {
191
+ markdown += `\n### ${className}.${method.name}()\n\n`;
192
+ markdown += `${method.description || ''}\n`;
193
+ markdown += formatParams(method.params);
194
+ markdown += formatReturns(method.returns);
195
+ markdown += formatThrows(method.exceptions);
196
+ markdown += formatExamples(method.examples);
197
+ });
198
+
199
+ markdown += `\n---\n\n`;
200
+ });
201
+
202
+ markdown += `## Low Level Functions\n\n`;
203
+ markdown += 'These functions provide low-level access to OSC encoding and decoding for advanced use cases.\n\n';
204
+
205
+ functionOrder.forEach(funcName => {
206
+ const func = functions[funcName];
207
+ if (!func) return;
208
+
209
+ markdown += `### ${funcName}()\n\n`;
210
+ markdown += `${func.description || ''}\n`;
211
+ markdown += formatParams(func.params);
212
+ markdown += formatReturns(func.returns);
213
+ markdown += formatThrows(func.exceptions);
214
+ markdown += formatExamples(func.examples);
215
+ markdown += '\n';
216
+ });
217
+
218
+ return markdown;
229
219
  }
220
+
221
+ function main() {
222
+ try {
223
+ const jsdocJson = runJsdoc();
224
+ const docs = parseJsdocJson(jsdocJson);
225
+ const { classes, functions } = collectDocs(docs);
226
+ const markdown = generateMarkdown(classes, functions);
227
+ writeFileSync('docs/API.md', markdown, 'utf8');
228
+ console.log('✅ API documentation generated: docs/API.md');
229
+ } catch (error) {
230
+ console.error(`❌ ${error.message}`);
231
+ process.exit(1);
232
+ }
233
+ }
234
+
235
+ main();
@@ -4,6 +4,7 @@ const osc = require('node-osc');
4
4
 
5
5
  // Create server first (typical usage pattern)
6
6
  const server: Server = new osc.Server(3333, '0.0.0.0');
7
+ server.send(['/server-test', 1], 3334, '127.0.0.1', () => {});
7
8
 
8
9
  // Create client after server
9
10
  const client: Client = new osc.Client('127.0.0.1', 3333);
@@ -7,6 +7,7 @@ const server: Server = new Server(3333, '0.0.0.0');
7
7
 
8
8
  // Wait for server to be ready (pattern from examples)
9
9
  await once(server, 'listening');
10
+ await server.send(['/server-test', 1], 3334, '127.0.0.1');
10
11
 
11
12
  server.on('message', (msg) => {
12
13
  console.log('Received message:', msg);
@@ -140,6 +140,22 @@ test('server: close with promise', async (t) => {
140
140
  t.pass('Server closed successfully with promise');
141
141
  });
142
142
 
143
+ test('server: send promise rejection on closed socket', async (t) => {
144
+ const oscServer = new Server(0, '127.0.0.1');
145
+
146
+ t.plan(1);
147
+
148
+ await once(oscServer, 'listening');
149
+ await oscServer.close();
150
+
151
+ try {
152
+ await oscServer.send('/boom', 3333, '127.0.0.1');
153
+ t.fail('Should have thrown an error');
154
+ } catch (err) {
155
+ t.equal(err.code, 'ERR_SOCKET_DGRAM_NOT_RUNNING', 'Should reject with correct error code');
156
+ }
157
+ });
158
+
143
159
  test('server: no callback still emits listening event', async (t) => {
144
160
  const oscServer = new Server(0, '127.0.0.1');
145
161
 
@@ -88,6 +88,67 @@ test('server: callback as second arg', async (t) => {
88
88
  });
89
89
  });
90
90
 
91
+ test('server: send to remote port', async (t) => {
92
+ const sender = new Server(0, '127.0.0.1');
93
+ const receiver = new Server(0, '127.0.0.1');
94
+ await Promise.all([once(sender, 'listening'), once(receiver, 'listening')]);
95
+
96
+ t.plan(1);
97
+
98
+ t.teardown(async () => {
99
+ await Promise.all([sender.close(), receiver.close()]);
100
+ });
101
+
102
+ const receivedMessage = once(receiver, 'message');
103
+ await sender.send(['/test', 1], receiver.port, '127.0.0.1');
104
+ const [msg] = await receivedMessage;
105
+
106
+ t.same(msg, ['/test', 1], 'server should send a message from its bound socket');
107
+ });
108
+
109
+ test('server: can receive and reply from within message handler', async (t) => {
110
+ const requester = new Server(0, '127.0.0.1');
111
+ const responder = new Server(0, '127.0.0.1');
112
+ await Promise.all([once(requester, 'listening'), once(responder, 'listening')]);
113
+
114
+ t.plan(3);
115
+
116
+ t.teardown(async () => {
117
+ await Promise.all([requester.close(), responder.close()]);
118
+ });
119
+
120
+ responder.on('message', (msg, rinfo) => {
121
+ t.same(msg, ['/ping', 1], 'responder should receive the incoming message');
122
+ responder.send(['/ack', 1], rinfo.port, rinfo.address, (err) => {
123
+ t.error(err, 'responder should reply without error');
124
+ });
125
+ });
126
+
127
+ const receivedReply = once(requester, 'message');
128
+ await requester.send(['/ping', 1], responder.port, '127.0.0.1');
129
+ const [reply] = await receivedReply;
130
+
131
+ t.same(reply, ['/ack', 1], 'requester should receive the reply on the same socket');
132
+ });
133
+
134
+ test('server: send before listening returns a clear error', async (t) => {
135
+ t.plan(3);
136
+
137
+ const server = new Server(0, '127.0.0.1');
138
+
139
+ t.throws(() => {
140
+ server.send('/test', 3333, '127.0.0.1');
141
+ }, /before server is listening/i, 'send without callback should throw before listening');
142
+
143
+ server.send('/test', 3333, '127.0.0.1', (err) => {
144
+ t.ok(err instanceof Error, 'send should fail with an Error before listening');
145
+ t.match(err.message, /before server is listening/i, 'error message should explain the socket is not ready');
146
+ });
147
+
148
+ await once(server, 'listening');
149
+ await server.close();
150
+ });
151
+
91
152
  test('server: bad message', async (t) => {
92
153
  t.plan(2);
93
154
  const oscServer = new Server(0, '127.0.0.1');
@@ -56,7 +56,6 @@ declare class Client extends EventEmitter<any> {
56
56
  * await client.close();
57
57
  */
58
58
  close(cb?: Function): Promise<void> | undefined;
59
- _performSend(message: any, args: any, callback: any): void;
60
59
  /**
61
60
  * Send an OSC message or bundle to the server.
62
61
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Client.d.mts","sourceRoot":"","sources":["../lib/Client.mjs"],"names":[],"mappings":";AAKA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;IACE;;;;;;;;OAQG;IACH,kBANW,MAAM,QACN,MAAM,EAiBhB;IAVC,aAAgB;IAChB,aAAgB;IAChB,mCAGE;IAMJ;;;;;;;;;;;;;;;;;OAiBG;IACH,sBAZa,OAAO,CAAC,IAAI,CAAC,GAAC,SAAS,CAuBnC;IACD,2DA2BC;IACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;IACH,cA7Bc,GAAC,EAAA,GAIF,OAAO,CAAC,IAAI,CAAC,GAAC,SAAS,CAmDnC;CACF;6BA9K4B,aAAa"}
1
+ {"version":3,"file":"Client.d.mts","sourceRoot":"","sources":["../lib/Client.mjs"],"names":[],"mappings":";AAIA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;IACE;;;;;;;;OAQG;IACH,kBANW,MAAM,QACN,MAAM,EAiBhB;IAVC,aAAgB;IAChB,aAAgB;IAChB,mCAGE;IAMJ;;;;;;;;;;;;;;;;;OAiBG;IACH,sBAZa,OAAO,CAAC,IAAI,CAAC,GAAC,SAAS,CAuBnC;IACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;IACH,cA7Bc,GAAC,EAAA,GAIF,OAAO,CAAC,IAAI,CAAC,GAAC,SAAS,CA2CnC;CACF;6BAzI4B,aAAa"}
@@ -73,7 +73,35 @@ declare class Server extends EventEmitter<any> {
73
73
  constructor(port: number, host?: string, cb?: Function);
74
74
  port: number;
75
75
  host: string;
76
+ _isListening: boolean;
77
+ _isClosed: boolean;
76
78
  _sock: import("node:dgram").Socket;
79
+ /**
80
+ * Send an OSC message or bundle from the server's bound socket.
81
+ *
82
+ * This method can be used with either a callback or as a Promise.
83
+ *
84
+ * @param {import('./Message.mjs').default|import('./Bundle.mjs').default|Array|string} message - The message, bundle, address, or array to send.
85
+ * @param {number} port - The remote port to send to.
86
+ * @param {string} host - The remote host to send to.
87
+ * @param {Function} [cb] - Optional callback function called when send completes.
88
+ * @returns {Promise<void>|undefined} Returns a Promise if no callback is provided.
89
+ *
90
+ * @throws {Error} If the server socket is not yet listening.
91
+ * @throws {TypeError} If the message format is invalid.
92
+ * @throws {ReferenceError} If attempting to send on a closed socket.
93
+ *
94
+ * @example
95
+ * // Send an address-only message
96
+ * await server.send('/ping', 9000, '127.0.0.1');
97
+ *
98
+ * @example
99
+ * // Send an array message
100
+ * server.send(['/ack', 1], 9000, '192.168.1.42', (err) => {
101
+ * if (err) console.error(err);
102
+ * });
103
+ */
104
+ send(message: import("./Message.mjs").default | import("./Bundle.mjs").default | any[] | string, port: number, host: string, cb?: Function): Promise<void> | undefined;
77
105
  /**
78
106
  * Close the server socket.
79
107
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Server.d.mts","sourceRoot":"","sources":["../lib/Server.mjs"],"names":[],"mappings":";AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH;IACE;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,kBApBW,MAAM,SACN,MAAM,iBAgEhB;IArCC,aAAgB;IAChB,aAAgB;IAChB,mCAGE;IAiCJ;;;;;;;;;;;;;;;;;OAiBG;IACH,sBAZa,OAAO,CAAC,IAAI,CAAC,GAAC,SAAS,CAuBnC;CACF;6BAvJ4B,aAAa"}
1
+ {"version":3,"file":"Server.d.mts","sourceRoot":"","sources":["../lib/Server.mjs"],"names":[],"mappings":";AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH;IACE;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,kBApBW,MAAM,SACN,MAAM,iBAyEhB;IA9CC,aAAgB;IAChB,aAAgB;IAChB,sBAAyB;IACzB,mBAAsB;IACtB,mCAGE;IAwCJ;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,cApBW,OAAO,eAAe,EAAE,OAAO,GAAC,OAAO,cAAc,EAAE,OAAO,WAAO,MAAM,QAC3E,MAAM,QACN,MAAM,kBAEJ,OAAO,CAAC,IAAI,CAAC,GAAC,SAAS,CAyCnC;IACD;;;;;;;;;;;;;;;;;OAiBG;IACH,sBAZa,OAAO,CAAC,IAAI,CAAC,GAAC,SAAS,CAuBnC;CACF;6BAxN4B,aAAa"}
@@ -0,0 +1,3 @@
1
+ export default performSend;
2
+ declare function performSend(sock: any, message: any, args: any, port: any, host: any, callback: any): void;
3
+ //# sourceMappingURL=send.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.d.mts","sourceRoot":"","sources":["../../lib/internal/send.mjs"],"names":[],"mappings":";AAcA,4GA6BC"}