node-osc 11.2.0 → 11.2.2

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.
Files changed (48) hide show
  1. package/.github/workflows/bump-version.yml +3 -3
  2. package/.github/workflows/create-release.yml +4 -4
  3. package/.github/workflows/nodejs.yml +4 -4
  4. package/README.md +3 -2
  5. package/agent.md +330 -0
  6. package/dist/lib/Client.js +1 -1
  7. package/dist/lib/Message.js +3 -1
  8. package/dist/lib/Server.js +7 -12
  9. package/dist/lib/internal/decode.js +3 -1
  10. package/dist/lib/osc.js +32 -3
  11. package/dist/test/lib/osc.js +32 -3
  12. package/dist/test/test-bundle.js +13 -12
  13. package/dist/test/test-client.js +68 -37
  14. package/dist/test/test-decode.js +35 -0
  15. package/dist/test/test-e2e.js +9 -9
  16. package/dist/test/test-encode-decode.js +455 -0
  17. package/dist/test/test-error-handling.js +14 -13
  18. package/dist/test/test-message.js +261 -64
  19. package/dist/test/test-osc-internal.js +151 -0
  20. package/dist/test/test-promises.js +90 -26
  21. package/dist/test/test-server.js +19 -16
  22. package/docs/README.md +81 -0
  23. package/examples/README.md +3 -1
  24. package/lib/Client.mjs +1 -1
  25. package/lib/Message.mjs +3 -1
  26. package/lib/Server.mjs +7 -12
  27. package/lib/internal/decode.mjs +3 -1
  28. package/lib/osc.mjs +32 -3
  29. package/package.json +2 -2
  30. package/rollup.config.mjs +1 -0
  31. package/test/test-bundle.mjs +14 -13
  32. package/test/test-client.mjs +69 -38
  33. package/test/test-decode.mjs +35 -0
  34. package/test/test-e2e.mjs +10 -10
  35. package/test/test-encode-decode.mjs +455 -0
  36. package/test/test-error-handling.mjs +15 -14
  37. package/test/test-message.mjs +262 -66
  38. package/test/test-osc-internal.mjs +151 -0
  39. package/test/test-promises.mjs +91 -27
  40. package/test/test-server.mjs +20 -17
  41. package/types/Message.d.mts.map +1 -1
  42. package/types/Server.d.mts.map +1 -1
  43. package/types/internal/decode.d.mts.map +1 -1
  44. package/types/osc.d.mts.map +1 -1
  45. package/dist/test/test-getPort.js +0 -20
  46. package/dist/test/util.js +0 -34
  47. package/test/test-getPort.mjs +0 -18
  48. package/test/util.mjs +0 -34
@@ -2,29 +2,51 @@
2
2
 
3
3
  var node_events = require('node:events');
4
4
  var tap = require('tap');
5
- var util = require('./util.js');
6
5
  var nodeOsc = require('node-osc');
7
6
 
8
- tap.beforeEach(util.bootstrap);
9
-
10
7
  tap.test('client: send with promise - array', async (t) => {
11
- const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
12
- const client = new nodeOsc.Client('127.0.0.1', t.context.port);
8
+ const server = new nodeOsc.Server(0, '127.0.0.1');
9
+ await node_events.once(server, 'listening');
10
+ const client = new nodeOsc.Client('127.0.0.1', server.port);
13
11
 
14
12
  t.plan(1);
15
13
 
14
+ server.on('message', (msg) => {
15
+ server.close();
16
+ t.same(msg, ['/test', 0, 1, 'testing', true], 'We should receive expected payload');
17
+ });
18
+
19
+ await client.send(['/test', 0, 1, 'testing', true]);
20
+ await client.close();
21
+ });
22
+
23
+ tap.test('client: array is not mutated when sent with promise', async (t) => {
24
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
25
+ await node_events.once(oscServer, 'listening');
26
+ const client = new nodeOsc.Client('127.0.0.1', oscServer.port);
27
+
28
+ t.plan(2);
29
+
30
+ const originalArray = ['/test', 0, 1, 'testing', true];
31
+ const expectedArray = ['/test', 0, 1, 'testing', true];
32
+
16
33
  oscServer.on('message', (msg) => {
17
34
  oscServer.close();
18
35
  t.same(msg, ['/test', 0, 1, 'testing', true], 'We should receive expected payload');
19
36
  });
20
37
 
21
- await client.send(['/test', 0, 1, 'testing', true]);
38
+ await client.send(originalArray);
39
+
40
+ // Verify the original array was not mutated
41
+ t.same(originalArray, expectedArray, 'Original array should not be mutated');
42
+
22
43
  await client.close();
23
44
  });
24
45
 
25
46
  tap.test('client: send with promise - string', async (t) => {
26
- const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
27
- const client = new nodeOsc.Client('127.0.0.1', t.context.port);
47
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
48
+ await node_events.once(oscServer, 'listening');
49
+ const client = new nodeOsc.Client('127.0.0.1', oscServer.port);
28
50
 
29
51
  t.plan(1);
30
52
 
@@ -38,8 +60,9 @@ tap.test('client: send with promise - string', async (t) => {
38
60
  });
39
61
 
40
62
  tap.test('client: send with promise - message object', async (t) => {
41
- const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
42
- const client = new nodeOsc.Client('127.0.0.1', t.context.port);
63
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
64
+ await node_events.once(oscServer, 'listening');
65
+ const client = new nodeOsc.Client('127.0.0.1', oscServer.port);
43
66
 
44
67
  t.plan(1);
45
68
 
@@ -56,8 +79,9 @@ tap.test('client: send with promise - message object', async (t) => {
56
79
  });
57
80
 
58
81
  tap.test('client: send with promise - multiple args', async (t) => {
59
- const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
60
- const client = new nodeOsc.Client('127.0.0.1', t.context.port);
82
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
83
+ await node_events.once(oscServer, 'listening');
84
+ const client = new nodeOsc.Client('127.0.0.1', oscServer.port);
61
85
 
62
86
  t.plan(1);
63
87
 
@@ -71,12 +95,15 @@ tap.test('client: send with promise - multiple args', async (t) => {
71
95
  });
72
96
 
73
97
  tap.test('client: send promise rejection on closed socket', async (t) => {
74
- const client = new nodeOsc.Client('127.0.0.1', t.context.port);
98
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
99
+ await node_events.once(oscServer, 'listening');
100
+ const client = new nodeOsc.Client('127.0.0.1', oscServer.port);
75
101
 
76
102
  t.plan(1);
77
103
 
78
104
  await client.close();
79
-
105
+ await oscServer.close();
106
+
80
107
  try {
81
108
  await client.send('/boom');
82
109
  t.fail('Should have thrown an error');
@@ -86,8 +113,9 @@ tap.test('client: send promise rejection on closed socket', async (t) => {
86
113
  });
87
114
 
88
115
  tap.test('client: async/await usage', async (t) => {
89
- const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
90
- const client = new nodeOsc.Client('127.0.0.1', t.context.port);
116
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
117
+ await node_events.once(oscServer, 'listening');
118
+ const client = new nodeOsc.Client('127.0.0.1', oscServer.port);
91
119
 
92
120
  t.plan(1);
93
121
 
@@ -103,7 +131,7 @@ tap.test('client: async/await usage', async (t) => {
103
131
  });
104
132
 
105
133
  tap.test('server: close with promise', async (t) => {
106
- const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
134
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
107
135
 
108
136
  t.plan(1);
109
137
 
@@ -114,7 +142,7 @@ tap.test('server: close with promise', async (t) => {
114
142
  });
115
143
 
116
144
  tap.test('server: no callback still emits listening event', async (t) => {
117
- const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
145
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
118
146
 
119
147
  t.plan(1);
120
148
 
@@ -125,15 +153,16 @@ tap.test('server: no callback still emits listening event', async (t) => {
125
153
  });
126
154
 
127
155
  tap.test('client and server: full async/await workflow', async (t) => {
128
- const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
129
- const client = new nodeOsc.Client('127.0.0.1', t.context.port);
130
-
131
- t.plan(2);
156
+ t.plan(3);
157
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
132
158
 
133
159
  // Wait for server to be ready
134
160
  await node_events.once(oscServer, 'listening');
135
161
  t.pass('Server started');
136
162
 
163
+ const client = new nodeOsc.Client('127.0.0.1', oscServer.port);
164
+ t.pass('Client created');
165
+
137
166
  // Set up message handler
138
167
  const messageReceived = node_events.once(oscServer, 'message');
139
168
 
@@ -148,9 +177,9 @@ tap.test('client and server: full async/await workflow', async (t) => {
148
177
  });
149
178
 
150
179
  tap.test('client: multiple sends with promises', async (t) => {
151
- const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
152
- const client = new nodeOsc.Client('127.0.0.1', t.context.port);
153
-
180
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
181
+ await node_events.once(oscServer, 'listening');
182
+ const client = new nodeOsc.Client('127.0.0.1', oscServer.port);
154
183
  t.plan(3);
155
184
 
156
185
  const messages = [];
@@ -174,6 +203,8 @@ tap.test('client: multiple sends with promises', async (t) => {
174
203
  });
175
204
 
176
205
  tap.test('client: close promise rejection on error', async (t) => {
206
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
207
+ await node_events.once(oscServer, 'listening');
177
208
  const client = new nodeOsc.Client('127.0.0.1', t.context.port);
178
209
 
179
210
  t.plan(1);
@@ -203,6 +234,7 @@ tap.test('client: close promise rejection on error', async (t) => {
203
234
  };
204
235
 
205
236
  try {
237
+ await oscServer.close();
206
238
  await client.close();
207
239
  t.fail('Should have thrown an error');
208
240
  } catch (err) {
@@ -211,7 +243,7 @@ tap.test('client: close promise rejection on error', async (t) => {
211
243
  });
212
244
 
213
245
  tap.test('server: close promise rejection on error', async (t) => {
214
- const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
246
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
215
247
 
216
248
  t.plan(1);
217
249
 
@@ -248,3 +280,35 @@ tap.test('server: close promise rejection on error', async (t) => {
248
280
  t.equal(err.code, 'MOCK_ERROR', 'Should reject with mock error');
249
281
  }
250
282
  });
283
+
284
+ tap.test('client: send promise rejection on send error', async (t) => {
285
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
286
+ await node_events.once(oscServer, 'listening');
287
+ const client = new nodeOsc.Client('127.0.0.1', oscServer.port);
288
+
289
+ t.plan(1);
290
+
291
+ // Mock the socket's send method to simulate an error
292
+ const originalSend = client._sock.send;
293
+ client._sock.send = function(msg, offset, length, port, address, callback) {
294
+ // Simulate an error being passed to callback
295
+ const err = new Error('Mock send error');
296
+ err.code = 'MOCK_SEND_ERROR';
297
+ if (callback) {
298
+ setImmediate(() => callback(err));
299
+ }
300
+ };
301
+
302
+ t.teardown(async () => {
303
+ client._sock.send = originalSend;
304
+ await client.close();
305
+ await oscServer.close();
306
+ });
307
+
308
+ try {
309
+ await client.send('/test', 'data');
310
+ t.fail('Should have thrown an error');
311
+ } catch (err) {
312
+ t.equal(err.code, 'MOCK_SEND_ERROR', 'Should reject with mock send error');
313
+ }
314
+ });
@@ -1,22 +1,22 @@
1
1
  'use strict';
2
2
 
3
+ var node_events = require('node:events');
3
4
  var tap = require('tap');
4
- var util = require('./util.js');
5
5
  var nodeOsc = require('node-osc');
6
6
 
7
- tap.beforeEach(util.bootstrap);
8
-
9
- tap.test('server: create and close', (t) => {
7
+ tap.test('server: create and close', async (t) => {
10
8
  t.plan(1);
11
- const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
9
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
10
+ await node_events.once(oscServer, 'listening');
12
11
  oscServer.close((err) => {
13
12
  t.error(err);
14
13
  });
15
14
  });
16
15
 
17
- tap.test('server: listen to message', (t) => {
18
- const oscServer = new nodeOsc.Server(t.context.port);
19
- const client = new nodeOsc.Client('127.0.0.1', t.context.port);
16
+ tap.test('server: listen to message', async (t) => {
17
+ const oscServer = new nodeOsc.Server(0);
18
+ await node_events.once(oscServer, 'listening');
19
+ const client = new nodeOsc.Client('127.0.0.1', oscServer.port);
20
20
 
21
21
  t.plan(3);
22
22
 
@@ -38,9 +38,10 @@ tap.test('server: listen to message', (t) => {
38
38
  });
39
39
  });
40
40
 
41
- tap.test('server: no defined host', (t) => {
42
- const oscServer = new nodeOsc.Server(t.context.port);
43
- const client = new nodeOsc.Client('127.0.0.1', t.context.port);
41
+ tap.test('server: no defined host', async (t) => {
42
+ const oscServer = new nodeOsc.Server(0);
43
+ await node_events.once(oscServer, 'listening');
44
+ const client = new nodeOsc.Client('127.0.0.1', oscServer.port);
44
45
 
45
46
  t.plan(3);
46
47
 
@@ -62,12 +63,13 @@ tap.test('server: no defined host', (t) => {
62
63
  });
63
64
  });
64
65
 
65
- tap.test('server: callback as second arg', (t) => {
66
+ tap.test('server: callback as second arg', async (t) => {
66
67
  t.plan(4);
67
- const oscServer = new nodeOsc.Server(t.context.port, () => {
68
+ const oscServer = new nodeOsc.Server(0, () => {
68
69
  t.ok('callback called');
69
70
  });
70
- const client = new nodeOsc.Client('127.0.0.1', t.context.port);
71
+ await node_events.once(oscServer, 'listening');
72
+ const client = new nodeOsc.Client('127.0.0.1', oscServer.port);
71
73
 
72
74
  t.teardown(() => {
73
75
  oscServer.close();
@@ -87,9 +89,10 @@ tap.test('server: callback as second arg', (t) => {
87
89
  });
88
90
  });
89
91
 
90
- tap.test('server: bad message', (t) => {
92
+ tap.test('server: bad message', async (t) => {
91
93
  t.plan(2);
92
- const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
94
+ const oscServer = new nodeOsc.Server(0, '127.0.0.1');
95
+ await node_events.once(oscServer, 'listening');
93
96
  t.throws(() => {
94
97
  oscServer._sock.emit('message', 'whoops');
95
98
  }, /can't decode incoming message:/);
package/docs/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # node-osc Documentation
2
+
3
+ Welcome to the node-osc documentation! This directory contains comprehensive documentation for the node-osc library.
4
+
5
+ ## Documentation Overview
6
+
7
+ ### 📚 [API Reference](./API.md)
8
+ **Complete API documentation for all classes and functions**
9
+
10
+ Auto-generated from JSDoc comments in the source code. This is your reference for:
11
+ - All classes: `Server`, `Client`, `Message`, `Bundle`
12
+ - All methods and their parameters
13
+ - Low-level functions: `encode()` and `decode()`
14
+ - Return types and error conditions
15
+ - Code examples for each API
16
+
17
+ > **Note:** This file is automatically generated. To update it, edit the JSDoc comments in the source code and run `npm run docs`.
18
+
19
+ ### 📘 [Usage Guide](./GUIDE.md)
20
+ **Best practices, patterns, and troubleshooting**
21
+
22
+ A comprehensive guide covering:
23
+ - Event handling patterns
24
+ - Error handling strategies
25
+ - OSC type system details
26
+ - Best practices for production use
27
+ - Troubleshooting common issues
28
+ - Advanced topics like custom transports and performance optimization
29
+
30
+ ## Quick Navigation
31
+
32
+ **New to node-osc?**
33
+ 1. Start with the [main README](../README.md) for a quick introduction and installation
34
+ 2. Try the [examples](../examples/) to see working code
35
+ 3. Read the [Usage Guide](./GUIDE.md) to learn best practices
36
+ 4. Reference the [API documentation](./API.md) as needed
37
+
38
+ **Looking for something specific?**
39
+ - **How to send/receive messages** → [API Reference](./API.md) (Server and Client sections)
40
+ - **How to handle errors** → [Usage Guide](./GUIDE.md#error-handling)
41
+ - **Type system and data types** → [Usage Guide](./GUIDE.md#type-system)
42
+ - **Working with bundles** → [API Reference](./API.md#bundle)
43
+ - **Troubleshooting** → [Usage Guide](./GUIDE.md#troubleshooting)
44
+ - **Code examples** → [Examples directory](../examples/)
45
+ - **Advanced use cases** → [Usage Guide](./GUIDE.md#advanced-topics)
46
+
47
+ ## Additional Resources
48
+
49
+ - **[Examples](../examples/)** - Working code examples for various use cases
50
+ - **[Main README](../README.md)** - Quick start and project overview
51
+ - **[OSC Specification](http://opensoundcontrol.org/spec-1_0)** - Official OSC protocol documentation
52
+
53
+ ## Contributing to Documentation
54
+
55
+ ### Updating API Documentation
56
+
57
+ The API documentation is automatically generated from JSDoc comments:
58
+
59
+ 1. Edit JSDoc comments in the source files (`lib/**/*.mjs`)
60
+ 2. Run `npm run docs` to regenerate `API.md`
61
+ 3. Review the changes and commit
62
+
63
+ ### Updating the Usage Guide
64
+
65
+ The Usage Guide (`GUIDE.md`) is manually maintained. When editing:
66
+
67
+ - Keep it focused on patterns, best practices, and how-to content
68
+ - Avoid duplicating API details (link to API.md instead)
69
+ - Include practical code examples
70
+ - Update the table of contents if adding new sections
71
+
72
+ ## Documentation Structure Philosophy
73
+
74
+ Our documentation is organized to minimize duplication while maximizing usefulness:
75
+
76
+ - **README.md** (main) → Quick start, basic examples, installation
77
+ - **API.md** → Complete API reference with all technical details
78
+ - **GUIDE.md** → How to use the library effectively, patterns, and troubleshooting
79
+ - **examples/** → Working code you can run and learn from
80
+
81
+ This structure ensures you can find what you need without reading through repeated content.
@@ -115,5 +115,7 @@ Demonstrates:
115
115
  ## Further Reading
116
116
 
117
117
  - [Main README](../README.md) - Quick start guide
118
- - [API Documentation](../docs/API.md) - Complete API reference
118
+ - [Documentation Hub](../docs/) - Complete documentation with navigation guide
119
+ - [API Reference](../docs/API.md) - Complete API reference
120
+ - [Usage Guide](../docs/GUIDE.md) - Best practices and troubleshooting
119
121
  - [OSC Specification](http://opensoundcontrol.org/spec-1_0) - Learn about the OSC protocol
package/lib/Client.mjs CHANGED
@@ -154,7 +154,7 @@ class Client extends EventEmitter {
154
154
  if (message instanceof Array) {
155
155
  message = {
156
156
  address: message[0],
157
- args: message.splice(1)
157
+ args: message.slice(1)
158
158
  };
159
159
  }
160
160
 
package/lib/Message.mjs CHANGED
@@ -109,7 +109,9 @@ class Message {
109
109
  let argOut;
110
110
  switch (typeof arg) {
111
111
  case 'object':
112
- if (arg instanceof Array) {
112
+ if (Buffer.isBuffer(arg)) {
113
+ this.args.push(arg);
114
+ } else if (arg instanceof Array) {
113
115
  arg.forEach(a => this.append(a));
114
116
  } else if (arg.type) {
115
117
  if (typeTags[arg.type]) arg.type = typeTags[arg.type];
package/lib/Server.mjs CHANGED
@@ -90,18 +90,13 @@ class Server extends EventEmitter {
90
90
  });
91
91
  this._sock.bind(port, host);
92
92
 
93
- // Support both callback and promise-based listening
94
- if (cb) {
95
- this._sock.on('listening', () => {
96
- this.emit('listening');
97
- cb();
98
- });
99
- } else {
100
- // For promise support, still emit the event but don't require a callback
101
- this._sock.on('listening', () => {
102
- this.emit('listening');
103
- });
104
- }
93
+ // Update port and emit listening event when socket is ready
94
+ this._sock.on('listening', () => {
95
+ // Update port with actual bound port (important when using port 0)
96
+ this.port = this._sock.address().port;
97
+ this.emit('listening');
98
+ if (cb) cb();
99
+ });
105
100
 
106
101
  this._sock.on('message', (msg, rinfo) => {
107
102
  try {
@@ -3,7 +3,8 @@ import { decode } from '../osc.mjs';
3
3
  function sanitizeMessage(decoded) {
4
4
  const message = [];
5
5
  message.push(decoded.address);
6
- decoded.args.forEach(arg => {
6
+ const args = decoded.args ?? [];
7
+ args.forEach(arg => {
7
8
  message.push(arg.value);
8
9
  });
9
10
  return message;
@@ -13,6 +14,7 @@ function sanitizeBundle(decoded) {
13
14
  decoded.elements = decoded.elements.map(element => {
14
15
  if (element.oscType === 'bundle') return sanitizeBundle(element);
15
16
  else if (element.oscType === 'message') return sanitizeMessage(element);
17
+ throw new Error('Malformed Packet');
16
18
  });
17
19
  return decoded;
18
20
  }
package/lib/osc.mjs CHANGED
@@ -7,8 +7,9 @@ import { Buffer } from 'node:buffer';
7
7
 
8
8
  function padString(str) {
9
9
  const nullTerminated = str + '\0';
10
- const padding = 4 - (nullTerminated.length % 4);
11
- return nullTerminated + '\0'.repeat(padding === 4 ? 0 : padding);
10
+ const byteLength = Buffer.byteLength(nullTerminated);
11
+ const padding = (4 - (byteLength % 4)) % 4;
12
+ return nullTerminated + '\0'.repeat(padding);
12
13
  }
13
14
 
14
15
  function readString(buffer, offset) {
@@ -16,6 +17,9 @@ function readString(buffer, offset) {
16
17
  while (end < buffer.length && buffer[end] !== 0) {
17
18
  end++;
18
19
  }
20
+ if (end >= buffer.length) {
21
+ throw new Error('Malformed Packet: Missing null terminator for string');
22
+ }
19
23
  const str = buffer.subarray(offset, end).toString('utf8');
20
24
  // Find next 4-byte boundary
21
25
  const paddedLength = Math.ceil((end - offset + 1) / 4) * 4;
@@ -29,6 +33,9 @@ function writeInt32(value) {
29
33
  }
30
34
 
31
35
  function readInt32(buffer, offset) {
36
+ if (offset + 4 > buffer.length) {
37
+ throw new Error('Malformed Packet: Not enough bytes for int32');
38
+ }
32
39
  const value = buffer.readInt32BE(offset);
33
40
  return { value, offset: offset + 4 };
34
41
  }
@@ -40,6 +47,9 @@ function writeFloat32(value) {
40
47
  }
41
48
 
42
49
  function readFloat32(buffer, offset) {
50
+ if (offset + 4 > buffer.length) {
51
+ throw new Error('Malformed Packet: Not enough bytes for float32');
52
+ }
43
53
  const value = buffer.readFloatBE(offset);
44
54
  return { value, offset: offset + 4 };
45
55
  }
@@ -55,9 +65,18 @@ function writeBlob(value) {
55
65
  function readBlob(buffer, offset) {
56
66
  const lengthResult = readInt32(buffer, offset);
57
67
  const length = lengthResult.value;
68
+ if (length < 0) {
69
+ throw new Error('Malformed Packet: Invalid blob length');
70
+ }
71
+ if (lengthResult.offset + length > buffer.length) {
72
+ throw new Error('Malformed Packet: Not enough bytes for blob');
73
+ }
58
74
  const data = buffer.subarray(lengthResult.offset, lengthResult.offset + length);
59
75
  const padding = 4 - (length % 4);
60
76
  const nextOffset = lengthResult.offset + length + (padding === 4 ? 0 : padding);
77
+ if (nextOffset > buffer.length) {
78
+ throw new Error('Malformed Packet: Not enough bytes for blob padding');
79
+ }
61
80
  return { value: data, offset: nextOffset };
62
81
  }
63
82
 
@@ -65,7 +84,11 @@ function writeTimeTag(value) {
65
84
  // For now, treat timetag as a double (8 bytes)
66
85
  // OSC timetag is 64-bit: 32-bit seconds since 1900, 32-bit fractional
67
86
  const buffer = Buffer.alloc(8);
68
- if (typeof value === 'number') {
87
+ if (value === 0 || value === null || value === undefined) {
88
+ // Immediate execution
89
+ buffer.writeUInt32BE(0, 0);
90
+ buffer.writeUInt32BE(1, 4);
91
+ } else if (typeof value === 'number') {
69
92
  // Convert to OSC timetag format
70
93
  const seconds = Math.floor(value);
71
94
  const fraction = Math.floor((value - seconds) * 0x100000000);
@@ -80,6 +103,9 @@ function writeTimeTag(value) {
80
103
  }
81
104
 
82
105
  function readTimeTag(buffer, offset) {
106
+ if (offset + 8 > buffer.length) {
107
+ throw new Error('Malformed Packet: Not enough bytes for timetag');
108
+ }
83
109
  const seconds = buffer.readUInt32BE(offset);
84
110
  const fraction = buffer.readUInt32BE(offset + 4);
85
111
 
@@ -375,6 +401,9 @@ function decodeBundleFromBuffer(buffer) {
375
401
  const sizeResult = readInt32(buffer, offset);
376
402
  const size = sizeResult.value;
377
403
  offset = sizeResult.offset;
404
+ if (size <= 0 || offset + size > buffer.length) {
405
+ throw new Error('Malformed Packet');
406
+ }
378
407
 
379
408
  // Read element data
380
409
  const elementBuffer = buffer.subarray(offset, offset + size);
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.0",
4
+ "version": "11.2.2",
5
5
  "exports": {
6
6
  "types": "./types/index.d.mts",
7
7
  "require": "./dist/lib/index.js",
@@ -28,7 +28,7 @@
28
28
  "build:types": "tsc",
29
29
  "docs": "node scripts/generate-docs.mjs",
30
30
  "prepublishOnly": "npm run build",
31
- "lint": "eslint \"lib/**/*.mjs\" \"test/test-*.mjs\" test/util.mjs examples/*.js examples/*.mjs rollup.config.mjs",
31
+ "lint": "eslint \"lib/**/*.mjs\" \"test/test-*.mjs\" \"examples/*.js\" \"examples/*.mjs\" \"scripts/*.mjs\" rollup.config.mjs",
32
32
  "test": "npm run lint && npm run build && npm run test:esm && npm run test:cjs",
33
33
  "test:esm": "tap test/test-*.mjs",
34
34
  "test:cjs": "tap dist/test/test-*.js"
package/rollup.config.mjs CHANGED
@@ -69,6 +69,7 @@ function walkTest(config) {
69
69
  'node:fs',
70
70
  'node:path',
71
71
  'node:url',
72
+ 'node:timers/promises',
72
73
  'node-osc',
73
74
  'tap',
74
75
  '#decode'
@@ -1,13 +1,12 @@
1
- import { beforeEach, test } from 'tap';
1
+ import { once } from 'node:events';
2
+ import { test } from 'tap';
2
3
 
3
4
  import { Client, Server, Bundle } from 'node-osc';
4
5
 
5
- import { bootstrap } from './util.mjs';
6
- beforeEach(bootstrap);
7
-
8
- test('bundle: verbose bundle', (t) => {
9
- const server = new Server(t.context.port, '127.0.0.1');
10
- const client = new Client('127.0.0.1', t.context.port);
6
+ test('bundle: verbose bundle', async (t) => {
7
+ const server = new Server(0, '127.0.0.1');
8
+ await once(server, 'listening');
9
+ const client = new Client('127.0.0.1', server.port);
11
10
 
12
11
  t.plan(2);
13
12
 
@@ -34,9 +33,10 @@ test('bundle: verbose bundle', (t) => {
34
33
  }));
35
34
  });
36
35
 
37
- test('bundle: array syntax', (t) => {
38
- const server = new Server(t.context.port, '127.0.0.1');
39
- const client = new Client('127.0.0.1', t.context.port);
36
+ test('bundle: array syntax', async (t) => {
37
+ const server = new Server(0, '127.0.0.1');
38
+ await once(server, 'listening');
39
+ const client = new Client('127.0.0.1', server.port);
40
40
 
41
41
  t.plan(2);
42
42
 
@@ -56,9 +56,10 @@ test('bundle: array syntax', (t) => {
56
56
  ));
57
57
  });
58
58
 
59
- test('bundle: nested bundle', (t) => {
60
- const server = new Server(t.context.port, '127.0.0.1');
61
- const client = new Client('127.0.0.1', t.context.port);
59
+ test('bundle: nested bundle', async (t) => {
60
+ const server = new Server(0, '127.0.0.1');
61
+ await once(server, 'listening');
62
+ const client = new Client('127.0.0.1', server.port);
62
63
 
63
64
  t.plan(4);
64
65