node-osc 11.1.0 → 11.2.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.
- package/.gitattributes +11 -0
- package/.github/workflows/bump-version.yml +2 -0
- package/.github/workflows/nodejs.yml +3 -0
- package/LICENSE +201 -165
- package/README.md +135 -42
- package/dist/lib/Bundle.js +66 -0
- package/dist/lib/Client.js +137 -22
- package/dist/lib/Message.js +87 -1
- package/dist/lib/Server.js +117 -6
- package/dist/lib/index.js +3 -0
- package/dist/lib/internal/decode.js +4 -4
- package/{lib/internal/osc.mjs → dist/lib/osc.js} +94 -23
- package/dist/test/lib/osc.js +395 -0
- package/dist/test/test-client.js +152 -0
- package/dist/test/test-e2e.js +9 -3
- package/dist/test/test-encode-decode.js +849 -0
- package/dist/test/test-error-handling.js +116 -0
- package/dist/test/test-osc-internal.js +399 -41
- package/dist/test/test-promises.js +250 -0
- package/dist/test/test-types.js +42 -0
- package/dist/test/util.js +15 -8
- package/docs/API.md +477 -0
- package/docs/GUIDE.md +605 -0
- package/examples/README.md +119 -0
- package/examples/async-await.mjs +57 -0
- package/examples/bundle-example.mjs +92 -0
- package/examples/client.js +22 -5
- package/examples/error-handling.mjs +152 -0
- package/examples/esm.mjs +21 -0
- package/examples/server.js +16 -0
- package/jsdoc.json +16 -0
- package/lib/Bundle.mjs +66 -0
- package/lib/Client.mjs +137 -22
- package/lib/Message.mjs +87 -1
- package/lib/Server.mjs +117 -6
- package/lib/index.mjs +1 -0
- package/lib/internal/decode.mjs +4 -4
- package/{dist/lib/internal/osc.js → lib/osc.mjs} +71 -7
- package/package.json +12 -10
- package/rollup.config.mjs +48 -37
- package/scripts/generate-docs.mjs +229 -0
- package/test/fixtures/types/test-cjs-types.ts +19 -0
- package/test/fixtures/types/test-esm-types.ts +35 -0
- package/test/fixtures/types/tsconfig-cjs.test.json +17 -0
- package/test/fixtures/types/tsconfig-esm.test.json +17 -0
- package/test/test-bundle.mjs +0 -1
- package/test/test-client.mjs +152 -0
- package/test/test-e2e.mjs +9 -3
- package/test/test-encode-decode.mjs +847 -0
- package/test/test-error-handling.mjs +115 -0
- package/test/test-osc-internal.mjs +400 -42
- package/test/test-promises.mjs +249 -0
- package/test/test-types.mjs +39 -0
- package/test/util.mjs +15 -8
- package/tsconfig.json +45 -0
- package/types/Bundle.d.mts +70 -0
- package/types/Bundle.d.mts.map +1 -0
- package/types/Client.d.mts +101 -0
- package/types/Client.d.mts.map +1 -0
- package/types/Message.d.mts +84 -0
- package/types/Message.d.mts.map +1 -0
- package/types/Server.d.mts +98 -0
- package/types/Server.d.mts.map +1 -0
- package/types/index.d.mts +6 -0
- package/types/index.d.mts.map +1 -0
- package/types/internal/decode.d.mts +4 -0
- package/types/internal/decode.d.mts.map +1 -0
- package/types/osc.d.mts +66 -0
- package/types/osc.d.mts.map +1 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var node_buffer = require('node:buffer');
|
|
4
|
+
|
|
5
|
+
// OSC 1.0 Protocol Implementation
|
|
6
|
+
// Based on http://opensoundcontrol.org/spec-1_0
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
function padString(str) {
|
|
10
|
+
const nullTerminated = str + '\0';
|
|
11
|
+
const padding = 4 - (nullTerminated.length % 4);
|
|
12
|
+
return nullTerminated + '\0'.repeat(padding === 4 ? 0 : padding);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readString(buffer, offset) {
|
|
16
|
+
let end = offset;
|
|
17
|
+
while (end < buffer.length && buffer[end] !== 0) {
|
|
18
|
+
end++;
|
|
19
|
+
}
|
|
20
|
+
const str = buffer.subarray(offset, end).toString('utf8');
|
|
21
|
+
// Find next 4-byte boundary
|
|
22
|
+
const paddedLength = Math.ceil((end - offset + 1) / 4) * 4;
|
|
23
|
+
return { value: str, offset: offset + paddedLength };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function writeInt32(value) {
|
|
27
|
+
const buffer = node_buffer.Buffer.alloc(4);
|
|
28
|
+
buffer.writeInt32BE(value, 0);
|
|
29
|
+
return buffer;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readInt32(buffer, offset) {
|
|
33
|
+
const value = buffer.readInt32BE(offset);
|
|
34
|
+
return { value, offset: offset + 4 };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function writeFloat32(value) {
|
|
38
|
+
const buffer = node_buffer.Buffer.alloc(4);
|
|
39
|
+
buffer.writeFloatBE(value, 0);
|
|
40
|
+
return buffer;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readFloat32(buffer, offset) {
|
|
44
|
+
const value = buffer.readFloatBE(offset);
|
|
45
|
+
return { value, offset: offset + 4 };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function writeBlob(value) {
|
|
49
|
+
const length = value.length;
|
|
50
|
+
const lengthBuffer = writeInt32(length);
|
|
51
|
+
const padding = 4 - (length % 4);
|
|
52
|
+
const paddingBuffer = node_buffer.Buffer.alloc(padding === 4 ? 0 : padding);
|
|
53
|
+
return node_buffer.Buffer.concat([lengthBuffer, value, paddingBuffer]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function readBlob(buffer, offset) {
|
|
57
|
+
const lengthResult = readInt32(buffer, offset);
|
|
58
|
+
const length = lengthResult.value;
|
|
59
|
+
const data = buffer.subarray(lengthResult.offset, lengthResult.offset + length);
|
|
60
|
+
const padding = 4 - (length % 4);
|
|
61
|
+
const nextOffset = lengthResult.offset + length + (padding === 4 ? 0 : padding);
|
|
62
|
+
return { value: data, offset: nextOffset };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function writeTimeTag(value) {
|
|
66
|
+
// For now, treat timetag as a double (8 bytes)
|
|
67
|
+
// OSC timetag is 64-bit: 32-bit seconds since 1900, 32-bit fractional
|
|
68
|
+
const buffer = node_buffer.Buffer.alloc(8);
|
|
69
|
+
if (typeof value === 'number') {
|
|
70
|
+
// Convert to OSC timetag format
|
|
71
|
+
const seconds = Math.floor(value);
|
|
72
|
+
const fraction = Math.floor((value - seconds) * 0x100000000);
|
|
73
|
+
buffer.writeUInt32BE(seconds + 2208988800, 0); // Add epoch offset (1900 vs 1970)
|
|
74
|
+
buffer.writeUInt32BE(fraction, 4);
|
|
75
|
+
} else {
|
|
76
|
+
// If not a number, write zeros (immediate execution)
|
|
77
|
+
buffer.writeUInt32BE(0, 0);
|
|
78
|
+
buffer.writeUInt32BE(1, 4);
|
|
79
|
+
}
|
|
80
|
+
return buffer;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readTimeTag(buffer, offset) {
|
|
84
|
+
const seconds = buffer.readUInt32BE(offset);
|
|
85
|
+
const fraction = buffer.readUInt32BE(offset + 4);
|
|
86
|
+
|
|
87
|
+
let value;
|
|
88
|
+
if (seconds === 0 && fraction === 1) {
|
|
89
|
+
// Immediate execution
|
|
90
|
+
value = 0;
|
|
91
|
+
} else {
|
|
92
|
+
// Convert from OSC epoch (1900) to Unix epoch (1970)
|
|
93
|
+
const unixSeconds = seconds - 2208988800;
|
|
94
|
+
const fractionalSeconds = fraction / 0x100000000;
|
|
95
|
+
value = unixSeconds + fractionalSeconds;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return { value, offset: offset + 8 };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function writeMidi(value) {
|
|
102
|
+
// MIDI message is 4 bytes: port id, status byte, data1, data2
|
|
103
|
+
const buffer = node_buffer.Buffer.alloc(4);
|
|
104
|
+
|
|
105
|
+
if (node_buffer.Buffer.isBuffer(value)) {
|
|
106
|
+
if (value.length !== 4) {
|
|
107
|
+
throw new Error('MIDI message must be exactly 4 bytes');
|
|
108
|
+
}
|
|
109
|
+
value.copy(buffer);
|
|
110
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
111
|
+
// Allow object format: { port: 0, status: 144, data1: 60, data2: 127 }
|
|
112
|
+
buffer.writeUInt8(value.port || 0, 0);
|
|
113
|
+
buffer.writeUInt8(value.status || 0, 1);
|
|
114
|
+
buffer.writeUInt8(value.data1 || 0, 2);
|
|
115
|
+
buffer.writeUInt8(value.data2 || 0, 3);
|
|
116
|
+
} else {
|
|
117
|
+
throw new Error('MIDI value must be a 4-byte Buffer or object with port, status, data1, data2 properties');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return buffer;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readMidi(buffer, offset) {
|
|
124
|
+
if (offset + 4 > buffer.length) {
|
|
125
|
+
throw new Error('Not enough bytes for MIDI message');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const value = buffer.subarray(offset, offset + 4);
|
|
129
|
+
return { value, offset: offset + 4 };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function encodeArgument(arg) {
|
|
133
|
+
if (typeof arg === 'object' && arg.type && arg.value !== undefined) {
|
|
134
|
+
// Explicit type specification
|
|
135
|
+
switch (arg.type) {
|
|
136
|
+
case 'i':
|
|
137
|
+
case 'integer':
|
|
138
|
+
return { tag: 'i', data: writeInt32(arg.value) };
|
|
139
|
+
case 'f':
|
|
140
|
+
case 'float':
|
|
141
|
+
return { tag: 'f', data: writeFloat32(arg.value) };
|
|
142
|
+
case 's':
|
|
143
|
+
case 'string':
|
|
144
|
+
return { tag: 's', data: node_buffer.Buffer.from(padString(arg.value)) };
|
|
145
|
+
case 'b':
|
|
146
|
+
case 'blob':
|
|
147
|
+
return { tag: 'b', data: writeBlob(arg.value) };
|
|
148
|
+
case 'd':
|
|
149
|
+
case 'double':
|
|
150
|
+
// For doubles, use float for now (OSC 1.0 doesn't have double)
|
|
151
|
+
return { tag: 'f', data: writeFloat32(arg.value) };
|
|
152
|
+
case 'T':
|
|
153
|
+
return { tag: 'T', data: node_buffer.Buffer.alloc(0) };
|
|
154
|
+
case 'F':
|
|
155
|
+
return { tag: 'F', data: node_buffer.Buffer.alloc(0) };
|
|
156
|
+
case 'boolean':
|
|
157
|
+
return arg.value ? { tag: 'T', data: node_buffer.Buffer.alloc(0) } : { tag: 'F', data: node_buffer.Buffer.alloc(0) };
|
|
158
|
+
case 'm':
|
|
159
|
+
case 'midi':
|
|
160
|
+
return { tag: 'm', data: writeMidi(arg.value) };
|
|
161
|
+
default:
|
|
162
|
+
throw new Error(`Unknown argument type: ${arg.type}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Infer type from JavaScript type
|
|
167
|
+
switch (typeof arg) {
|
|
168
|
+
case 'number':
|
|
169
|
+
if (Number.isInteger(arg)) {
|
|
170
|
+
return { tag: 'i', data: writeInt32(arg) };
|
|
171
|
+
} else {
|
|
172
|
+
return { tag: 'f', data: writeFloat32(arg) };
|
|
173
|
+
}
|
|
174
|
+
case 'string':
|
|
175
|
+
return { tag: 's', data: node_buffer.Buffer.from(padString(arg)) };
|
|
176
|
+
case 'boolean':
|
|
177
|
+
return arg ? { tag: 'T', data: node_buffer.Buffer.alloc(0) } : { tag: 'F', data: node_buffer.Buffer.alloc(0) };
|
|
178
|
+
default:
|
|
179
|
+
if (node_buffer.Buffer.isBuffer(arg)) {
|
|
180
|
+
return { tag: 'b', data: writeBlob(arg) };
|
|
181
|
+
}
|
|
182
|
+
throw new Error(`Don't know how to encode argument: ${arg}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function decodeArgument(tag, buffer, offset) {
|
|
187
|
+
switch (tag) {
|
|
188
|
+
case 'i':
|
|
189
|
+
return readInt32(buffer, offset);
|
|
190
|
+
case 'f':
|
|
191
|
+
return readFloat32(buffer, offset);
|
|
192
|
+
case 's':
|
|
193
|
+
return readString(buffer, offset);
|
|
194
|
+
case 'b':
|
|
195
|
+
return readBlob(buffer, offset);
|
|
196
|
+
case 'T':
|
|
197
|
+
return { value: true, offset };
|
|
198
|
+
case 'F':
|
|
199
|
+
return { value: false, offset };
|
|
200
|
+
case 'N':
|
|
201
|
+
return { value: null, offset };
|
|
202
|
+
case 'm':
|
|
203
|
+
return readMidi(buffer, offset);
|
|
204
|
+
default:
|
|
205
|
+
throw new Error(`I don't understand the argument code ${tag}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Encode an OSC message or bundle to a Buffer.
|
|
211
|
+
*
|
|
212
|
+
* This low-level function converts OSC messages and bundles into binary format
|
|
213
|
+
* for transmission or storage. Useful for sending OSC over custom transports
|
|
214
|
+
* (WebSocket, TCP, HTTP), storing to files, or implementing custom OSC routers.
|
|
215
|
+
*
|
|
216
|
+
* @param {Object} message - OSC message or bundle object with oscType property
|
|
217
|
+
* @returns {Buffer} The encoded OSC data ready for transmission
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* // Encode a message
|
|
221
|
+
* import { Message, encode } from 'node-osc';
|
|
222
|
+
*
|
|
223
|
+
* const message = new Message('/oscillator/frequency', 440);
|
|
224
|
+
* const buffer = encode(message);
|
|
225
|
+
* console.log('Encoded bytes:', buffer.length);
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* // Encode a bundle
|
|
229
|
+
* import { Bundle, encode } from 'node-osc';
|
|
230
|
+
*
|
|
231
|
+
* const bundle = new Bundle(['/one', 1], ['/two', 2]);
|
|
232
|
+
* const buffer = encode(bundle);
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* // Send over WebSocket
|
|
236
|
+
* const buffer = encode(message);
|
|
237
|
+
* websocket.send(buffer);
|
|
238
|
+
*/
|
|
239
|
+
function encode(message) {
|
|
240
|
+
if (message.oscType === 'bundle') {
|
|
241
|
+
return encodeBundleToBuffer(message);
|
|
242
|
+
} else {
|
|
243
|
+
return encodeMessageToBuffer(message);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function encodeMessageToBuffer(message) {
|
|
248
|
+
// OSC Message format:
|
|
249
|
+
// Address pattern (padded string)
|
|
250
|
+
// Type tag string (padded string starting with ,)
|
|
251
|
+
// Arguments (encoded according to type tags)
|
|
252
|
+
|
|
253
|
+
const address = padString(message.address);
|
|
254
|
+
const addressBuffer = node_buffer.Buffer.from(address);
|
|
255
|
+
|
|
256
|
+
const encodedArgs = message.args.map(encodeArgument);
|
|
257
|
+
const typeTags = ',' + encodedArgs.map(arg => arg.tag).join('');
|
|
258
|
+
const typeTagsBuffer = node_buffer.Buffer.from(padString(typeTags));
|
|
259
|
+
|
|
260
|
+
const argumentBuffers = encodedArgs.map(arg => arg.data);
|
|
261
|
+
|
|
262
|
+
return node_buffer.Buffer.concat([addressBuffer, typeTagsBuffer, ...argumentBuffers]);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function encodeBundleToBuffer(bundle) {
|
|
266
|
+
// OSC Bundle format:
|
|
267
|
+
// "#bundle" (padded string)
|
|
268
|
+
// Timetag (8 bytes)
|
|
269
|
+
// Elements (each prefixed with size)
|
|
270
|
+
|
|
271
|
+
const bundleString = padString('#bundle');
|
|
272
|
+
const bundleStringBuffer = node_buffer.Buffer.from(bundleString);
|
|
273
|
+
|
|
274
|
+
const timetagBuffer = writeTimeTag(bundle.timetag);
|
|
275
|
+
|
|
276
|
+
const elementBuffers = bundle.elements.map(element => {
|
|
277
|
+
let elementBuffer;
|
|
278
|
+
if (element.oscType === 'bundle') {
|
|
279
|
+
elementBuffer = encodeBundleToBuffer(element);
|
|
280
|
+
} else {
|
|
281
|
+
elementBuffer = encodeMessageToBuffer(element);
|
|
282
|
+
}
|
|
283
|
+
const sizeBuffer = writeInt32(elementBuffer.length);
|
|
284
|
+
return node_buffer.Buffer.concat([sizeBuffer, elementBuffer]);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
return node_buffer.Buffer.concat([bundleStringBuffer, timetagBuffer, ...elementBuffers]);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Decode a Buffer containing OSC data into a message or bundle object.
|
|
292
|
+
*
|
|
293
|
+
* This low-level function parses binary OSC data back into JavaScript objects.
|
|
294
|
+
* Useful for receiving OSC over custom transports, reading from files,
|
|
295
|
+
* or implementing custom OSC routers.
|
|
296
|
+
*
|
|
297
|
+
* @param {Buffer} buffer - The Buffer containing OSC data
|
|
298
|
+
* @returns {Object} The decoded OSC message or bundle. Messages have
|
|
299
|
+
* {oscType: 'message', address: string, args: Array}, bundles have
|
|
300
|
+
* {oscType: 'bundle', timetag: number, elements: Array}
|
|
301
|
+
* @throws {Error} If the buffer contains malformed OSC data
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* // Decode received data
|
|
305
|
+
* import { decode } from 'node-osc';
|
|
306
|
+
*
|
|
307
|
+
* const decoded = decode(buffer);
|
|
308
|
+
* if (decoded.oscType === 'message') {
|
|
309
|
+
* console.log('Address:', decoded.address);
|
|
310
|
+
* console.log('Arguments:', decoded.args);
|
|
311
|
+
* }
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* // Round-trip encode/decode
|
|
315
|
+
* import { Message, encode, decode } from 'node-osc';
|
|
316
|
+
*
|
|
317
|
+
* const original = new Message('/test', 42, 'hello');
|
|
318
|
+
* const buffer = encode(original);
|
|
319
|
+
* const decoded = decode(buffer);
|
|
320
|
+
* console.log(decoded.address); // '/test'
|
|
321
|
+
*/
|
|
322
|
+
function decode(buffer) {
|
|
323
|
+
// Check if it's a bundle or message
|
|
324
|
+
if (buffer.length >= 8 && buffer.subarray(0, 8).toString() === '#bundle\0') {
|
|
325
|
+
return decodeBundleFromBuffer(buffer);
|
|
326
|
+
} else {
|
|
327
|
+
return decodeMessageFromBuffer(buffer);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function decodeMessageFromBuffer(buffer) {
|
|
332
|
+
let offset = 0;
|
|
333
|
+
|
|
334
|
+
// Read address pattern
|
|
335
|
+
const addressResult = readString(buffer, offset);
|
|
336
|
+
const address = addressResult.value;
|
|
337
|
+
offset = addressResult.offset;
|
|
338
|
+
|
|
339
|
+
// Read type tag string
|
|
340
|
+
const typeTagsResult = readString(buffer, offset);
|
|
341
|
+
const typeTags = typeTagsResult.value;
|
|
342
|
+
offset = typeTagsResult.offset;
|
|
343
|
+
|
|
344
|
+
if (!typeTags.startsWith(',')) {
|
|
345
|
+
throw new Error('Malformed Packet');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const tags = typeTags.slice(1); // Remove leading comma
|
|
349
|
+
const args = [];
|
|
350
|
+
|
|
351
|
+
for (const tag of tags) {
|
|
352
|
+
const argResult = decodeArgument(tag, buffer, offset);
|
|
353
|
+
args.push({ value: argResult.value });
|
|
354
|
+
offset = argResult.offset;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
oscType: 'message',
|
|
359
|
+
address,
|
|
360
|
+
args
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function decodeBundleFromBuffer(buffer) {
|
|
365
|
+
let offset = 8; // Skip "#bundle\0"
|
|
366
|
+
|
|
367
|
+
// Read timetag
|
|
368
|
+
const timetagResult = readTimeTag(buffer, offset);
|
|
369
|
+
const timetag = timetagResult.value;
|
|
370
|
+
offset = timetagResult.offset;
|
|
371
|
+
|
|
372
|
+
const elements = [];
|
|
373
|
+
|
|
374
|
+
while (offset < buffer.length) {
|
|
375
|
+
// Read element size
|
|
376
|
+
const sizeResult = readInt32(buffer, offset);
|
|
377
|
+
const size = sizeResult.value;
|
|
378
|
+
offset = sizeResult.offset;
|
|
379
|
+
|
|
380
|
+
// Read element data
|
|
381
|
+
const elementBuffer = buffer.subarray(offset, offset + size);
|
|
382
|
+
const element = decode(elementBuffer);
|
|
383
|
+
elements.push(element);
|
|
384
|
+
offset += size;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
oscType: 'bundle',
|
|
389
|
+
timetag,
|
|
390
|
+
elements
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
exports.decode = decode;
|
|
395
|
+
exports.encode = encode;
|
package/dist/test/test-client.js
CHANGED
|
@@ -107,3 +107,155 @@ tap.test('client: failure', (t) => {
|
|
|
107
107
|
t.equal(err.code, 'ERR_SOCKET_DGRAM_NOT_RUNNING');
|
|
108
108
|
});
|
|
109
109
|
});
|
|
110
|
+
|
|
111
|
+
tap.test('client: close with callback', (t) => {
|
|
112
|
+
const client = new nodeOsc.Client('127.0.0.1', t.context.port);
|
|
113
|
+
|
|
114
|
+
t.plan(1);
|
|
115
|
+
|
|
116
|
+
client.close((err) => {
|
|
117
|
+
t.error(err, 'close should not error');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
tap.test('client: send bundle with non-numeric timetag', (t) => {
|
|
122
|
+
const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
|
|
123
|
+
const client = new nodeOsc.Client('127.0.0.1', t.context.port);
|
|
124
|
+
|
|
125
|
+
t.plan(2);
|
|
126
|
+
|
|
127
|
+
oscServer.on('bundle', (bundle) => {
|
|
128
|
+
oscServer.close();
|
|
129
|
+
t.equal(bundle.timetag, 0, 'should receive immediate execution timetag as 0');
|
|
130
|
+
t.ok(bundle.elements.length > 0, 'should have elements');
|
|
131
|
+
client.close();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Send bundle with non-numeric timetag (will be encoded as immediate execution)
|
|
135
|
+
const bundle = {
|
|
136
|
+
oscType: 'bundle',
|
|
137
|
+
timetag: 'immediate', // Non-numeric, will trigger the else branch in writeTimeTag
|
|
138
|
+
elements: [
|
|
139
|
+
{
|
|
140
|
+
oscType: 'message',
|
|
141
|
+
address: '/test1',
|
|
142
|
+
args: [{ type: 'i', value: 42 }]
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
client.send(bundle);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
tap.test('client: send bundle with null timetag', (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
|
+
|
|
154
|
+
t.plan(2);
|
|
155
|
+
|
|
156
|
+
oscServer.on('bundle', (bundle) => {
|
|
157
|
+
oscServer.close();
|
|
158
|
+
t.equal(bundle.timetag, 0, 'should receive immediate execution timetag as 0');
|
|
159
|
+
t.ok(bundle.elements.length > 0, 'should have elements');
|
|
160
|
+
client.close();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Send bundle with null timetag (will be encoded as immediate execution)
|
|
164
|
+
const bundle = {
|
|
165
|
+
oscType: 'bundle',
|
|
166
|
+
timetag: null, // Null, will trigger the else branch in writeTimeTag
|
|
167
|
+
elements: [
|
|
168
|
+
{
|
|
169
|
+
oscType: 'message',
|
|
170
|
+
address: '/test2',
|
|
171
|
+
args: [{ type: 's', value: 'hello' }]
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
client.send(bundle);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
tap.test('client: send message with float type arg', (t) => {
|
|
180
|
+
const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
|
|
181
|
+
const client = new nodeOsc.Client('127.0.0.1', t.context.port);
|
|
182
|
+
|
|
183
|
+
t.plan(2);
|
|
184
|
+
|
|
185
|
+
oscServer.on('message', (msg) => {
|
|
186
|
+
oscServer.close();
|
|
187
|
+
t.equal(msg[0], '/float-test', 'should receive address');
|
|
188
|
+
t.ok(Math.abs(msg[1] - 9.876) < 0.001, 'should receive float value');
|
|
189
|
+
client.close();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Send raw message with 'float' type to hit that case label
|
|
193
|
+
client.send({
|
|
194
|
+
oscType: 'message',
|
|
195
|
+
address: '/float-test',
|
|
196
|
+
args: [{ type: 'float', value: 9.876 }]
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
tap.test('client: send message with blob type arg', (t) => {
|
|
201
|
+
const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
|
|
202
|
+
const client = new nodeOsc.Client('127.0.0.1', t.context.port);
|
|
203
|
+
|
|
204
|
+
t.plan(2);
|
|
205
|
+
|
|
206
|
+
oscServer.on('message', (msg) => {
|
|
207
|
+
oscServer.close();
|
|
208
|
+
t.equal(msg[0], '/blob-test', 'should receive address');
|
|
209
|
+
t.ok(Buffer.isBuffer(msg[1]), 'should receive blob as buffer');
|
|
210
|
+
client.close();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Send raw message with 'blob' type to hit that case label
|
|
214
|
+
client.send({
|
|
215
|
+
oscType: 'message',
|
|
216
|
+
address: '/blob-test',
|
|
217
|
+
args: [{ type: 'blob', value: Buffer.from([0xAA, 0xBB]) }]
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
tap.test('client: send message with double type arg', (t) => {
|
|
222
|
+
const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
|
|
223
|
+
const client = new nodeOsc.Client('127.0.0.1', t.context.port);
|
|
224
|
+
|
|
225
|
+
t.plan(2);
|
|
226
|
+
|
|
227
|
+
oscServer.on('message', (msg) => {
|
|
228
|
+
oscServer.close();
|
|
229
|
+
t.equal(msg[0], '/double-test', 'should receive address');
|
|
230
|
+
t.ok(Math.abs(msg[1] - 1.23456789) < 0.001, 'should receive double value as float');
|
|
231
|
+
client.close();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Send raw message with 'double' type to hit that case label
|
|
235
|
+
client.send({
|
|
236
|
+
oscType: 'message',
|
|
237
|
+
address: '/double-test',
|
|
238
|
+
args: [{ type: 'double', value: 1.23456789 }]
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
tap.test('client: send message with midi type arg', (t) => {
|
|
243
|
+
const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
|
|
244
|
+
const client = new nodeOsc.Client('127.0.0.1', t.context.port);
|
|
245
|
+
|
|
246
|
+
t.plan(2);
|
|
247
|
+
|
|
248
|
+
oscServer.on('message', (msg) => {
|
|
249
|
+
oscServer.close();
|
|
250
|
+
t.equal(msg[0], '/midi-test', 'should receive address');
|
|
251
|
+
t.ok(Buffer.isBuffer(msg[1]), 'should receive MIDI as buffer');
|
|
252
|
+
client.close();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Send raw message with 'midi' type to hit that case label
|
|
256
|
+
client.send({
|
|
257
|
+
oscType: 'message',
|
|
258
|
+
address: '/midi-test',
|
|
259
|
+
args: [{ type: 'midi', value: Buffer.from([0x00, 0x90, 0x40, 0x60]) }]
|
|
260
|
+
});
|
|
261
|
+
});
|
package/dist/test/test-e2e.js
CHANGED
|
@@ -23,9 +23,12 @@ tap.test('osc: argument message no callback', (t) => {
|
|
|
23
23
|
|
|
24
24
|
t.plan(1);
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
t.teardown(() => {
|
|
27
27
|
oscServer.close();
|
|
28
28
|
client.close();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
oscServer.on('message', (msg) => {
|
|
29
32
|
t.same(msg, ['/test', 1, 2, 'testing'], 'We should receive expected payload');
|
|
30
33
|
});
|
|
31
34
|
|
|
@@ -40,13 +43,16 @@ tap.test('osc: client with callback and message as arguments', (t) => {
|
|
|
40
43
|
|
|
41
44
|
t.plan(2);
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
t.teardown(() => {
|
|
44
47
|
oscServer.close();
|
|
48
|
+
client.close();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
oscServer.on('message', (msg) => {
|
|
45
52
|
t.same(msg, ['/test', 1, 2, 'testing'], 'We should receive expected payload');
|
|
46
53
|
});
|
|
47
54
|
|
|
48
55
|
client.send('/test', 1, 2, 'testing', (err) => {
|
|
49
56
|
t.error(err, 'there should be no error');
|
|
50
|
-
client.close();
|
|
51
57
|
});
|
|
52
58
|
});
|