node-osc 9.1.7 → 11.0.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,498 @@
1
+ 'use strict';
2
+
3
+ var tap = require('tap');
4
+ var _osc = require('#osc');
5
+
6
+ tap.test('osc: timetag encoding with non-number value', (t) => {
7
+ // Test the else branch in writeTimeTag that writes zeros for non-number values
8
+ const bundle = {
9
+ oscType: 'bundle',
10
+ timetag: 'immediate', // Non-number value
11
+ elements: [
12
+ {
13
+ oscType: 'message',
14
+ address: '/test',
15
+ args: []
16
+ }
17
+ ]
18
+ };
19
+
20
+ const buffer = _osc.toBuffer(bundle);
21
+ const decoded = _osc.fromBuffer(buffer);
22
+
23
+ t.equal(decoded.oscType, 'bundle', 'should decode as bundle');
24
+ t.equal(decoded.timetag, 0, 'should decode timetag as 0 for immediate execution');
25
+ t.end();
26
+ });
27
+
28
+ tap.test('osc: timetag with immediate execution values', (t) => {
29
+ // Test readTimeTag with seconds === 0 && fraction === 1
30
+ const bundle = {
31
+ oscType: 'bundle',
32
+ timetag: null, // This will trigger the non-number path
33
+ elements: [
34
+ {
35
+ oscType: 'message',
36
+ address: '/test',
37
+ args: []
38
+ }
39
+ ]
40
+ };
41
+
42
+ const buffer = _osc.toBuffer(bundle);
43
+ const decoded = _osc.fromBuffer(buffer);
44
+
45
+ t.equal(decoded.timetag, 0, 'should handle immediate execution timetag');
46
+ t.end();
47
+ });
48
+
49
+ tap.test('osc: argument encoding with unknown type', (t) => {
50
+ // Test encodeArgument with unknown argument type to trigger line 122
51
+ const message = {
52
+ oscType: 'message',
53
+ address: '/test',
54
+ args: [
55
+ {
56
+ type: 'unknown',
57
+ value: 'test'
58
+ }
59
+ ]
60
+ };
61
+
62
+ t.throws(() => {
63
+ _osc.toBuffer(message);
64
+ }, /Unknown argument type: unknown/, 'should throw error for unknown argument type');
65
+ t.end();
66
+ });
67
+
68
+ tap.test('osc: argument encoding with boolean false', (t) => {
69
+ // Test explicit boolean false encoding to cover lines 132-133
70
+ const message = {
71
+ oscType: 'message',
72
+ address: '/test',
73
+ args: [
74
+ {
75
+ type: 'boolean',
76
+ value: false
77
+ }
78
+ ]
79
+ };
80
+
81
+ const buffer = _osc.toBuffer(message);
82
+ const decoded = _osc.fromBuffer(buffer);
83
+
84
+ t.equal(decoded.args[0].value, false, 'should encode and decode false boolean');
85
+ t.end();
86
+ });
87
+
88
+ tap.test('osc: argument encoding with unsupported object', (t) => {
89
+ // Test encodeArgument with unsupported object to trigger lines 139-142
90
+ const message = {
91
+ oscType: 'message',
92
+ address: '/test',
93
+ args: [
94
+ { unsupported: 'object' } // Object without type/value properties
95
+ ]
96
+ };
97
+
98
+ t.throws(() => {
99
+ _osc.toBuffer(message);
100
+ }, /Don't know how to encode argument/, 'should throw error for unsupported object');
101
+ t.end();
102
+ });
103
+
104
+ tap.test('osc: argument encoding with undefined value', (t) => {
105
+ // Test encodeArgument with undefined to trigger error case
106
+ const message = {
107
+ oscType: 'message',
108
+ address: '/test',
109
+ args: [undefined]
110
+ };
111
+
112
+ t.throws(() => {
113
+ _osc.toBuffer(message);
114
+ }, /Don't know how to encode argument/, 'should throw error for undefined argument');
115
+ t.end();
116
+ });
117
+
118
+ tap.test('osc: argument decoding with unknown type tag', (t) => {
119
+ // Test decodeArgument with unknown type tag to trigger line 161
120
+ // We need to manually create a buffer with an invalid type tag
121
+ const addressPart = '/test\0\0\0';
122
+ const typeTagPart = ',X\0\0'; // X is not a valid OSC type tag
123
+ const buffer = Buffer.from(addressPart + typeTagPart);
124
+
125
+ t.throws(() => {
126
+ _osc.fromBuffer(buffer);
127
+ }, /I don't understand the argument code X/, 'should throw error for unknown type tag');
128
+ t.end();
129
+ });
130
+
131
+ tap.test('osc: null argument encoding and decoding', (t) => {
132
+ // Test null argument handling (N type tag)
133
+ // Since our current implementation doesn't directly support null in encoding,
134
+ // let's test that we can at least decode it if we manually create the buffer
135
+ const addressPart = '/test\0\0\0';
136
+ const typeTagPart = ',N\0\0'; // N is null type tag
137
+ const buffer = Buffer.from(addressPart + typeTagPart);
138
+
139
+ const decoded = _osc.fromBuffer(buffer);
140
+ t.equal(decoded.args[0].value, null, 'should decode null argument');
141
+ t.end();
142
+ });
143
+
144
+ tap.test('osc: double type argument encoding', (t) => {
145
+ // Test double type argument which should fall back to float
146
+ const message = {
147
+ oscType: 'message',
148
+ address: '/test',
149
+ args: [
150
+ {
151
+ type: 'd',
152
+ value: 3.14159
153
+ }
154
+ ]
155
+ };
156
+
157
+ const buffer = _osc.toBuffer(message);
158
+ const decoded = _osc.fromBuffer(buffer);
159
+
160
+ t.ok(Math.abs(decoded.args[0].value - 3.14159) < 0.001, 'should encode double as float');
161
+ t.end();
162
+ });
163
+
164
+ tap.test('osc: blob argument with Buffer', (t) => {
165
+ // Test blob encoding with actual Buffer to ensure Buffer.isBuffer path is covered
166
+ const testBuffer = Buffer.from('test data');
167
+ const message = {
168
+ oscType: 'message',
169
+ address: '/test',
170
+ args: [testBuffer] // Direct Buffer without type wrapper
171
+ };
172
+
173
+ const buffer = _osc.toBuffer(message);
174
+ const decoded = _osc.fromBuffer(buffer);
175
+
176
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
177
+ t.equal(decoded.args[0].value.toString(), 'test data', 'should preserve blob content');
178
+ t.end();
179
+ });
180
+
181
+ tap.test('osc: float number encoding', (t) => {
182
+ // Test encoding of float numbers to cover lines 132-133
183
+ const message = {
184
+ oscType: 'message',
185
+ address: '/test',
186
+ args: [3.14159] // Non-integer number should be encoded as float
187
+ };
188
+
189
+ const buffer = _osc.toBuffer(message);
190
+ const decoded = _osc.fromBuffer(buffer);
191
+
192
+ t.ok(typeof decoded.args[0].value === 'number', 'should decode as number');
193
+ t.ok(!Number.isInteger(decoded.args[0].value), 'should be float, not integer');
194
+ t.ok(Math.abs(decoded.args[0].value - 3.14159) < 0.001, 'should preserve float value');
195
+ t.end();
196
+ });
197
+
198
+ tap.test('osc: explicit integer type encoding', (t) => {
199
+ // Test explicit integer type to cover line 102
200
+ const message = {
201
+ oscType: 'message',
202
+ address: '/test',
203
+ args: [
204
+ {
205
+ type: 'i',
206
+ value: 42
207
+ }
208
+ ]
209
+ };
210
+
211
+ const buffer = _osc.toBuffer(message);
212
+ const decoded = _osc.fromBuffer(buffer);
213
+
214
+ t.equal(decoded.args[0].value, 42, 'should encode and decode integer');
215
+ t.end();
216
+ });
217
+
218
+ tap.test('osc: explicit float type encoding', (t) => {
219
+ // Test explicit float type to cover line 105
220
+ const message = {
221
+ oscType: 'message',
222
+ address: '/test',
223
+ args: [
224
+ {
225
+ type: 'f',
226
+ value: 3.14
227
+ }
228
+ ]
229
+ };
230
+
231
+ const buffer = _osc.toBuffer(message);
232
+ const decoded = _osc.fromBuffer(buffer);
233
+
234
+ t.ok(Math.abs(decoded.args[0].value - 3.14) < 0.001, 'should encode and decode float');
235
+ t.end();
236
+ });
237
+
238
+ tap.test('osc: explicit string type encoding', (t) => {
239
+ // Test explicit string type to cover line 108
240
+ const message = {
241
+ oscType: 'message',
242
+ address: '/test',
243
+ args: [
244
+ {
245
+ type: 's',
246
+ value: 'hello'
247
+ }
248
+ ]
249
+ };
250
+
251
+ const buffer = _osc.toBuffer(message);
252
+ const decoded = _osc.fromBuffer(buffer);
253
+
254
+ t.equal(decoded.args[0].value, 'hello', 'should encode and decode string');
255
+ t.end();
256
+ });
257
+
258
+ tap.test('osc: explicit blob type encoding', (t) => {
259
+ // Test explicit blob type to cover line 111
260
+ const testData = Buffer.from('blob data');
261
+ const message = {
262
+ oscType: 'message',
263
+ address: '/test',
264
+ args: [
265
+ {
266
+ type: 'b',
267
+ value: testData
268
+ }
269
+ ]
270
+ };
271
+
272
+ const buffer = _osc.toBuffer(message);
273
+ const decoded = _osc.fromBuffer(buffer);
274
+
275
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
276
+ t.equal(decoded.args[0].value.toString(), 'blob data', 'should preserve blob data');
277
+ t.end();
278
+ });
279
+
280
+ tap.test('osc: explicit boolean true type encoding', (t) => {
281
+ // Test explicit boolean true type to cover line 118
282
+ const message = {
283
+ oscType: 'message',
284
+ address: '/test',
285
+ args: [
286
+ {
287
+ type: 'T',
288
+ value: true
289
+ }
290
+ ]
291
+ };
292
+
293
+ const buffer = _osc.toBuffer(message);
294
+ const decoded = _osc.fromBuffer(buffer);
295
+
296
+ t.equal(decoded.args[0].value, true, 'should encode and decode boolean true');
297
+ t.end();
298
+ });
299
+
300
+ tap.test('osc: MIDI type encoding with Buffer', (t) => {
301
+ // Test MIDI type with 4-byte Buffer
302
+ const midiData = Buffer.from([0x01, 0x90, 0x3C, 0x7F]); // port 1, note on, middle C, velocity 127
303
+ const message = {
304
+ oscType: 'message',
305
+ address: '/midi',
306
+ args: [
307
+ {
308
+ type: 'm',
309
+ value: midiData
310
+ }
311
+ ]
312
+ };
313
+
314
+ const buffer = _osc.toBuffer(message);
315
+ const decoded = _osc.fromBuffer(buffer);
316
+
317
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
318
+ t.equal(decoded.args[0].value.length, 4, 'should be 4 bytes');
319
+ t.equal(decoded.args[0].value[0], 0x01, 'port id should match');
320
+ t.equal(decoded.args[0].value[1], 0x90, 'status byte should match');
321
+ t.equal(decoded.args[0].value[2], 0x3C, 'data1 should match');
322
+ t.equal(decoded.args[0].value[3], 0x7F, 'data2 should match');
323
+ t.end();
324
+ });
325
+
326
+ tap.test('osc: MIDI type encoding with object', (t) => {
327
+ // Test MIDI type with object format
328
+ const message = {
329
+ oscType: 'message',
330
+ address: '/midi',
331
+ args: [
332
+ {
333
+ type: 'midi',
334
+ value: {
335
+ port: 2,
336
+ status: 0x80,
337
+ data1: 0x40,
338
+ data2: 0x00
339
+ }
340
+ }
341
+ ]
342
+ };
343
+
344
+ const buffer = _osc.toBuffer(message);
345
+ const decoded = _osc.fromBuffer(buffer);
346
+
347
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
348
+ t.equal(decoded.args[0].value[0], 2, 'port should match');
349
+ t.equal(decoded.args[0].value[1], 0x80, 'status should match');
350
+ t.equal(decoded.args[0].value[2], 0x40, 'data1 should match');
351
+ t.equal(decoded.args[0].value[3], 0x00, 'data2 should match');
352
+ t.end();
353
+ });
354
+
355
+ tap.test('osc: MIDI type with invalid buffer length', (t) => {
356
+ // Test MIDI type with wrong buffer length
357
+ const message = {
358
+ oscType: 'message',
359
+ address: '/midi',
360
+ args: [
361
+ {
362
+ type: 'm',
363
+ value: Buffer.from([0x90, 0x3C]) // Only 2 bytes
364
+ }
365
+ ]
366
+ };
367
+
368
+ t.throws(() => {
369
+ _osc.toBuffer(message);
370
+ }, /MIDI message must be exactly 4 bytes/, 'should throw error for wrong buffer length');
371
+ t.end();
372
+ });
373
+
374
+ tap.test('osc: MIDI type with invalid value type', (t) => {
375
+ // Test MIDI type with invalid value
376
+ const message = {
377
+ oscType: 'message',
378
+ address: '/midi',
379
+ args: [
380
+ {
381
+ type: 'm',
382
+ value: 'invalid'
383
+ }
384
+ ]
385
+ };
386
+
387
+ t.throws(() => {
388
+ _osc.toBuffer(message);
389
+ }, /MIDI value must be a 4-byte Buffer or object/, 'should throw error for invalid value type');
390
+ t.end();
391
+ });
392
+
393
+ tap.test('osc: MIDI type with partial object', (t) => {
394
+ // Test MIDI type with object having only some properties (should default others to 0)
395
+ const message = {
396
+ oscType: 'message',
397
+ address: '/midi',
398
+ args: [
399
+ {
400
+ type: 'm',
401
+ value: {
402
+ status: 0x90,
403
+ data1: 0x3C
404
+ // port and data2 should default to 0
405
+ }
406
+ }
407
+ ]
408
+ };
409
+
410
+ const buffer = _osc.toBuffer(message);
411
+ const decoded = _osc.fromBuffer(buffer);
412
+
413
+ t.equal(decoded.args[0].value[0], 0, 'port should default to 0');
414
+ t.equal(decoded.args[0].value[1], 0x90, 'status should match');
415
+ t.equal(decoded.args[0].value[2], 0x3C, 'data1 should match');
416
+ t.equal(decoded.args[0].value[3], 0, 'data2 should default to 0');
417
+ t.end();
418
+ });
419
+
420
+ tap.test('osc: MIDI type decoding with insufficient buffer data', (t) => {
421
+ // Test the error case in readMidi when buffer doesn't have enough bytes
422
+ // This manually crafts a malformed OSC buffer with MIDI type tag but insufficient data
423
+
424
+ // Create a minimal OSC message buffer with MIDI type but truncated data
425
+ // OSC Format: address + typetags + arguments
426
+ // Address: "/m" (padded to 4 bytes)
427
+ const address = Buffer.from('/m\0\0', 'ascii'); // 4 bytes
428
+
429
+ // Type tags: ",m" (padded to 4 bytes)
430
+ const typeTags = Buffer.from(',m\0\0', 'ascii'); // 4 bytes
431
+
432
+ // MIDI data: only 2 bytes instead of required 4
433
+ const insufficientMidiData = Buffer.from([0x90, 0x3C]); // Only 2 bytes, need 4
434
+
435
+ // Combine into malformed OSC buffer
436
+ const malformedBuffer = Buffer.concat([address, typeTags, insufficientMidiData]);
437
+
438
+ t.throws(() => {
439
+ _osc.fromBuffer(malformedBuffer);
440
+ }, /Not enough bytes for MIDI message/, 'should throw error when MIDI data is truncated');
441
+ t.end();
442
+ });
443
+
444
+ tap.test('osc: MIDI type with falsy status and data1 values', (t) => {
445
+ // Test MIDI type with object having undefined/falsy status and data1 (should default to 0)
446
+ const message = {
447
+ oscType: 'message',
448
+ address: '/midi',
449
+ args: [
450
+ {
451
+ type: 'm',
452
+ value: {
453
+ port: 5,
454
+ data2: 0x40
455
+ // status and data1 are undefined, should default to 0
456
+ }
457
+ }
458
+ ]
459
+ };
460
+
461
+ const buffer = _osc.toBuffer(message);
462
+ const decoded = _osc.fromBuffer(buffer);
463
+
464
+ t.equal(decoded.args[0].value[0], 5, 'port should match');
465
+ t.equal(decoded.args[0].value[1], 0, 'status should default to 0');
466
+ t.equal(decoded.args[0].value[2], 0, 'data1 should default to 0');
467
+ t.equal(decoded.args[0].value[3], 0x40, 'data2 should match');
468
+ t.end();
469
+ });
470
+
471
+ tap.test('osc: MIDI type with explicit zero status and data1 values', (t) => {
472
+ // Test MIDI type with object having explicit 0 values for status and data1
473
+ const message = {
474
+ oscType: 'message',
475
+ address: '/midi',
476
+ args: [
477
+ {
478
+ type: 'm',
479
+ value: {
480
+ port: 3,
481
+ status: 0,
482
+ data1: 0,
483
+ data2: 0x60
484
+ }
485
+ }
486
+ ]
487
+ };
488
+
489
+ const buffer = _osc.toBuffer(message);
490
+ const decoded = _osc.fromBuffer(buffer);
491
+
492
+ t.equal(decoded.args[0].value[0], 3, 'port should match');
493
+ t.equal(decoded.args[0].value[1], 0, 'status should be 0');
494
+ t.equal(decoded.args[0].value[2], 0, 'data1 should be 0');
495
+ t.equal(decoded.args[0].value[3], 0x60, 'data2 should match');
496
+ t.end();
497
+
498
+ });
package/examples/esm.mjs CHANGED
@@ -7,8 +7,8 @@ server.on('listening', () => {
7
7
  console.log('OSC Server is Listening');
8
8
  });
9
9
 
10
- server.on('message', (msg) => {
11
- console.log(`Message: ${msg}`);
10
+ server.on('message', (msg, rinfo) => {
11
+ console.log(`Message: ${msg}\nReceived from: ${rinfo.address}:${rinfo.port}`);
12
12
  server.close();
13
13
  });
14
14
 
@@ -3,7 +3,7 @@ var { Server } = require('node-osc');
3
3
 
4
4
  var oscServer = new Server(3333, '0.0.0.0');
5
5
 
6
- oscServer.on('message', function (msg) {
7
- console.log(`Message: ${msg}`);
6
+ oscServer.on('message', function (msg, rinfo) {
7
+ console.log(`Message: ${msg}\nReceived from: ${rinfo.address}:${rinfo.port}`);
8
8
  oscServer.close();
9
9
  });
package/lib/Bundle.mjs CHANGED
@@ -11,6 +11,7 @@ class Bundle {
11
11
  elements.unshift(timetag);
12
12
  timetag = 0;
13
13
  }
14
+ this.oscType = 'bundle';
14
15
  this.timetag = timetag;
15
16
  this.elements = elements.map(sanitize);
16
17
  }
package/lib/Client.mjs CHANGED
@@ -1,9 +1,7 @@
1
1
  import { createSocket } from 'node:dgram';
2
- import oscMin from 'osc-min';
2
+ import { toBuffer } from '#osc';
3
3
  import Message from './Message.mjs';
4
4
 
5
- const { toBuffer } = oscMin;
6
-
7
5
  class Client {
8
6
  constructor(host, port) {
9
7
  this.host = host;
@@ -1,4 +1,4 @@
1
- import { fromBuffer } from 'osc-min';
1
+ import { fromBuffer } from '#osc';
2
2
 
3
3
  function sanitizeMessage(decoded) {
4
4
  const message = [];
@@ -17,8 +17,8 @@ function sanitizeBundle(decoded) {
17
17
  return decoded;
18
18
  }
19
19
 
20
- function decode(data) {
21
- const decoded = fromBuffer(data);
20
+ function decode(data, customFromBuffer = fromBuffer) {
21
+ const decoded = customFromBuffer(data);
22
22
  if (decoded.oscType === 'bundle') {
23
23
  return sanitizeBundle(decoded);
24
24
  }