node-osc 11.2.0 → 11.2.1

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.
@@ -35,14 +35,14 @@ jobs:
35
35
  # run that runs on: tag. (Using the GitHub token would
36
36
  # not run the workflow to prevent infinite recursion.)
37
37
  - name: Check out source
38
- uses: actions/checkout@v4
38
+ uses: actions/checkout@v6
39
39
  with:
40
40
  ssh-key: ${{ secrets.DEPLOY_KEY }}
41
41
 
42
42
  - name: Setup Node.js
43
- uses: actions/setup-node@v4
43
+ uses: actions/setup-node@v6
44
44
  with:
45
- node-version: 22
45
+ node-version: 24
46
46
  cache: 'npm'
47
47
 
48
48
  - name: Install npm packages
@@ -17,11 +17,11 @@ jobs:
17
17
  id-token: write
18
18
  steps:
19
19
  - name: Checkout source
20
- uses: actions/checkout@v4
20
+ uses: actions/checkout@v6
21
21
  - name: Setup node
22
- uses: actions/setup-node@v4
22
+ uses: actions/setup-node@v6
23
23
  with:
24
- node-version: 22
24
+ node-version: 24
25
25
  registry-url: 'https://registry.npmjs.org'
26
26
  cache: npm
27
27
  - name: Install latest npm
@@ -39,7 +39,7 @@ jobs:
39
39
  contents: write
40
40
  steps:
41
41
  - name: Checkout code
42
- uses: actions/checkout@v4
42
+ uses: actions/checkout@v6
43
43
  - name: Create Release
44
44
  run: gh release create ${{ github.ref }} --generate-notes
45
45
  env:
@@ -14,16 +14,16 @@ jobs:
14
14
  run-tests:
15
15
  strategy:
16
16
  matrix:
17
- node-version: ['24', '22', '20']
17
+ node-version: ['25', '24', '22', '20']
18
18
  os: [ubuntu-latest, macos-latest, windows-latest]
19
19
 
20
20
  runs-on: ${{ matrix.os }}
21
21
 
22
22
  steps:
23
- - uses: actions/checkout@v4
23
+ - uses: actions/checkout@v6
24
24
 
25
25
  - name: Use Node.js ${{ matrix.node-version }}
26
- uses: actions/setup-node@v4
26
+ uses: actions/setup-node@v6
27
27
  with:
28
28
  node-version: ${{ matrix.node-version }}
29
29
  cache: 'npm'
@@ -156,7 +156,7 @@ class Client extends node_events.EventEmitter {
156
156
  if (message instanceof Array) {
157
157
  message = {
158
158
  address: message[0],
159
- args: message.splice(1)
159
+ args: message.slice(1)
160
160
  };
161
161
  }
162
162
 
@@ -111,7 +111,9 @@ class Message {
111
111
  let argOut;
112
112
  switch (typeof arg) {
113
113
  case 'object':
114
- if (arg instanceof Array) {
114
+ if (Buffer.isBuffer(arg)) {
115
+ this.args.push(arg);
116
+ } else if (arg instanceof Array) {
115
117
  arg.forEach(a => this.append(a));
116
118
  } else if (arg.type) {
117
119
  if (typeTags[arg.type]) arg.type = typeTags[arg.type];
package/dist/lib/osc.js CHANGED
@@ -8,8 +8,9 @@ var node_buffer = require('node:buffer');
8
8
 
9
9
  function padString(str) {
10
10
  const nullTerminated = str + '\0';
11
- const padding = 4 - (nullTerminated.length % 4);
12
- return nullTerminated + '\0'.repeat(padding === 4 ? 0 : padding);
11
+ const byteLength = node_buffer.Buffer.byteLength(nullTerminated);
12
+ const padding = (4 - (byteLength % 4)) % 4;
13
+ return nullTerminated + '\0'.repeat(padding);
13
14
  }
14
15
 
15
16
  function readString(buffer, offset) {
@@ -8,8 +8,9 @@ var node_buffer = require('node:buffer');
8
8
 
9
9
  function padString(str) {
10
10
  const nullTerminated = str + '\0';
11
- const padding = 4 - (nullTerminated.length % 4);
12
- return nullTerminated + '\0'.repeat(padding === 4 ? 0 : padding);
11
+ const byteLength = node_buffer.Buffer.byteLength(nullTerminated);
12
+ const padding = (4 - (byteLength % 4)) % 4;
13
+ return nullTerminated + '\0'.repeat(padding);
13
14
  }
14
15
 
15
16
  function readString(buffer, offset) {
@@ -23,6 +23,28 @@ tap.test('client: with array', (t) => {
23
23
  });
24
24
  });
25
25
 
26
+ tap.test('client: array is not mutated when sent', (t) => {
27
+ const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
28
+ const client = new nodeOsc.Client('127.0.0.1', t.context.port);
29
+
30
+ t.plan(3);
31
+
32
+ const originalArray = ['/test', 0, 1, 'testing', true];
33
+ const expectedArray = ['/test', 0, 1, 'testing', true];
34
+
35
+ oscServer.on('message', (msg) => {
36
+ oscServer.close();
37
+ t.same(msg, ['/test', 0, 1, 'testing', true], 'We should receive expected payload');
38
+ // Verify the original array was not mutated
39
+ t.same(originalArray, expectedArray, 'Original array should not be mutated');
40
+ });
41
+
42
+ client.send(originalArray, (err) => {
43
+ t.error(err, 'there should be no error');
44
+ client.close();
45
+ });
46
+ });
47
+
26
48
  tap.test('client: with string', (t) => {
27
49
  const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
28
50
  const client = new nodeOsc.Client('127.0.0.1', t.context.port);
@@ -847,3 +847,362 @@ tap.test('encode and decode: type "boolean" with false value', (t) => {
847
847
  t.equal(decoded.args[0].value, false);
848
848
  t.end();
849
849
  });
850
+
851
+ // Tests for UTF-8 string padding to 4-byte boundaries
852
+ // The padString function ensures OSC strings are padded based on byte length
853
+ // (not character count) to handle multi-byte UTF-8 correctly
854
+
855
+ tap.test('encode and decode: UTF-8 string padding - ASCII 1 char', (t) => {
856
+ // 1 byte + 1 null terminator = 2 bytes, needs 2 padding bytes to reach 4-byte boundary
857
+ const message = {
858
+ oscType: 'message',
859
+ address: '/test',
860
+ args: [{ type: 'string', value: 'a' }]
861
+ };
862
+
863
+ const buffer = nodeOsc.encode(message);
864
+ const decoded = nodeOsc.decode(buffer);
865
+
866
+ t.equal(decoded.args[0].value, 'a', 'should correctly encode and decode single ASCII character');
867
+
868
+ // Verify the string is properly padded in the buffer
869
+ // Address "/test" is 5 bytes + 1 null = 6 bytes, padded to 8 bytes
870
+ // Type tag ",s" is 2 bytes + 1 null = 3 bytes, padded to 4 bytes
871
+ // String "a" is 1 byte + 1 null = 2 bytes, padded to 4 bytes
872
+ const expectedMinLength = 8 + 4 + 4; // 16 bytes minimum
873
+ t.ok(buffer.length >= expectedMinLength, 'buffer should contain properly padded string');
874
+
875
+ t.end();
876
+ });
877
+
878
+ tap.test('encode and decode: UTF-8 string padding - ASCII 2 chars', (t) => {
879
+ // 2 bytes + 1 null = 3 bytes, needs 1 padding byte to reach 4-byte boundary
880
+ const message = {
881
+ oscType: 'message',
882
+ address: '/test',
883
+ args: [{ type: 'string', value: 'ab' }]
884
+ };
885
+
886
+ const buffer = nodeOsc.encode(message);
887
+ const decoded = nodeOsc.decode(buffer);
888
+
889
+ t.equal(decoded.args[0].value, 'ab', 'should correctly encode and decode 2-byte string');
890
+ t.end();
891
+ });
892
+
893
+ tap.test('encode and decode: UTF-8 string padding - ASCII 3 chars', (t) => {
894
+ // 3 bytes + 1 null terminator = 4 bytes, needs 0 padding (already aligned)
895
+ const message = {
896
+ oscType: 'message',
897
+ address: '/test',
898
+ args: [{ type: 'string', value: 'abc' }]
899
+ };
900
+
901
+ const buffer = nodeOsc.encode(message);
902
+ const decoded = nodeOsc.decode(buffer);
903
+
904
+ t.equal(decoded.args[0].value, 'abc', 'should correctly encode and decode 3-char ASCII string');
905
+ t.end();
906
+ });
907
+
908
+ tap.test('encode and decode: UTF-8 string padding - ASCII 5 chars', (t) => {
909
+ // 5 bytes + 1 null terminator = 6 bytes, needs 2 padding bytes to reach 8-byte boundary
910
+ const message = {
911
+ oscType: 'message',
912
+ address: '/test',
913
+ args: [{ type: 'string', value: 'hello' }]
914
+ };
915
+
916
+ const buffer = nodeOsc.encode(message);
917
+ const decoded = nodeOsc.decode(buffer);
918
+
919
+ t.equal(decoded.args[0].value, 'hello', 'should correctly encode and decode 5-char ASCII string');
920
+ t.end();
921
+ });
922
+
923
+ tap.test('encode and decode: UTF-8 string padding - ASCII 6 chars', (t) => {
924
+ // 6 bytes + 1 null = 7 bytes, needs 1 padding byte to reach 8-byte boundary
925
+ const message = {
926
+ oscType: 'message',
927
+ address: '/test',
928
+ args: [{ type: 'string', value: 'abcdef' }]
929
+ };
930
+
931
+ const buffer = nodeOsc.encode(message);
932
+ const decoded = nodeOsc.decode(buffer);
933
+
934
+ t.equal(decoded.args[0].value, 'abcdef', 'should correctly encode and decode 6-byte string');
935
+ t.end();
936
+ });
937
+
938
+ tap.test('encode and decode: UTF-8 string padding - ASCII 7 chars', (t) => {
939
+ // 7 bytes + 1 null terminator = 8 bytes, needs 0 padding (already aligned)
940
+ const message = {
941
+ oscType: 'message',
942
+ address: '/test',
943
+ args: [{ type: 'string', value: 'testing' }]
944
+ };
945
+
946
+ const buffer = nodeOsc.encode(message);
947
+ const decoded = nodeOsc.decode(buffer);
948
+
949
+ t.equal(decoded.args[0].value, 'testing', 'should correctly encode and decode 7-char ASCII string');
950
+ t.end();
951
+ });
952
+
953
+ tap.test('encode and decode: UTF-8 string padding - empty string', (t) => {
954
+ // 0 bytes + 1 null terminator = 1 byte, needs 3 padding bytes to reach 4-byte boundary
955
+ const message = {
956
+ oscType: 'message',
957
+ address: '/test',
958
+ args: [{ type: 'string', value: '' }]
959
+ };
960
+
961
+ const buffer = nodeOsc.encode(message);
962
+ const decoded = nodeOsc.decode(buffer);
963
+
964
+ t.equal(decoded.args[0].value, '', 'should correctly encode and decode empty string');
965
+ t.end();
966
+ });
967
+
968
+ tap.test('encode and decode: UTF-8 string padding - emoji character', (t) => {
969
+ // Emoji '😀' is 4 bytes in UTF-8 + 1 null = 5 bytes, needs 3 padding to reach 8-byte boundary
970
+ const message = {
971
+ oscType: 'message',
972
+ address: '/test',
973
+ args: [{ type: 'string', value: '😀' }]
974
+ };
975
+
976
+ const buffer = nodeOsc.encode(message);
977
+ const decoded = nodeOsc.decode(buffer);
978
+
979
+ t.equal(decoded.args[0].value, '😀', 'should correctly encode and decode emoji character');
980
+
981
+ // Verify byte length calculation is correct
982
+ const emojiByteLength = Buffer.byteLength('😀');
983
+ t.equal(emojiByteLength, 4, 'emoji should be 4 bytes in UTF-8');
984
+
985
+ t.end();
986
+ });
987
+
988
+ tap.test('encode and decode: UTF-8 string padding - Japanese character', (t) => {
989
+ // Japanese 'あ' is 3 bytes in UTF-8 + 1 null = 4 bytes, needs 0 padding (already aligned)
990
+ const message = {
991
+ oscType: 'message',
992
+ address: '/test',
993
+ args: [{ type: 'string', value: 'あ' }]
994
+ };
995
+
996
+ const buffer = nodeOsc.encode(message);
997
+ const decoded = nodeOsc.decode(buffer);
998
+
999
+ t.equal(decoded.args[0].value, 'あ', 'should correctly encode and decode Japanese character');
1000
+
1001
+ const japaneseByteLength = Buffer.byteLength('あ');
1002
+ t.equal(japaneseByteLength, 3, 'Japanese character should be 3 bytes in UTF-8');
1003
+
1004
+ t.end();
1005
+ });
1006
+
1007
+ tap.test('encode and decode: UTF-8 string padding - Chinese character', (t) => {
1008
+ // Chinese '中' is 3 bytes in UTF-8 + 1 null = 4 bytes, needs 0 padding (already aligned)
1009
+ const message = {
1010
+ oscType: 'message',
1011
+ address: '/test',
1012
+ args: [{ type: 'string', value: '中' }]
1013
+ };
1014
+
1015
+ const buffer = nodeOsc.encode(message);
1016
+ const decoded = nodeOsc.decode(buffer);
1017
+
1018
+ t.equal(decoded.args[0].value, '中', 'should correctly encode and decode Chinese character');
1019
+ t.end();
1020
+ });
1021
+
1022
+ tap.test('encode and decode: UTF-8 string padding - mixed ASCII and emoji', (t) => {
1023
+ // 'a' (1 byte) + '😀' (4 bytes) + 'b' (1 byte) = 6 bytes + 1 null = 7 bytes
1024
+ // needs 1 padding byte to reach 8-byte boundary
1025
+ const message = {
1026
+ oscType: 'message',
1027
+ address: '/test',
1028
+ args: [{ type: 'string', value: 'a😀b' }]
1029
+ };
1030
+
1031
+ const buffer = nodeOsc.encode(message);
1032
+ const decoded = nodeOsc.decode(buffer);
1033
+
1034
+ t.equal(decoded.args[0].value, 'a😀b', 'should correctly encode and decode mixed ASCII and emoji');
1035
+
1036
+ const mixedByteLength = Buffer.byteLength('a😀b');
1037
+ t.equal(mixedByteLength, 6, 'mixed string should be 6 bytes in UTF-8');
1038
+
1039
+ t.end();
1040
+ });
1041
+
1042
+ tap.test('encode and decode: UTF-8 string padding - Japanese string', (t) => {
1043
+ // 'こんにちは' (Hello in Japanese) - 5 characters, each 3 bytes = 15 bytes
1044
+ // 15 bytes + 1 null = 16 bytes, needs 0 padding (already aligned)
1045
+ const message = {
1046
+ oscType: 'message',
1047
+ address: '/test',
1048
+ args: [{ type: 'string', value: 'こんにちは' }]
1049
+ };
1050
+
1051
+ const buffer = nodeOsc.encode(message);
1052
+ const decoded = nodeOsc.decode(buffer);
1053
+
1054
+ t.equal(decoded.args[0].value, 'こんにちは', 'should correctly encode and decode Japanese string');
1055
+
1056
+ const japaneseStringByteLength = Buffer.byteLength('こんにちは');
1057
+ t.equal(japaneseStringByteLength, 15, 'Japanese string should be 15 bytes in UTF-8');
1058
+
1059
+ t.end();
1060
+ });
1061
+
1062
+ tap.test('encode and decode: UTF-8 string padding - accented characters', (t) => {
1063
+ // 'café' - 4 characters but 'é' is 2 bytes in UTF-8
1064
+ // 'c' (1) + 'a' (1) + 'f' (1) + 'é' (2) = 5 bytes + 1 null = 6 bytes
1065
+ // needs 2 padding bytes to reach 8-byte boundary
1066
+ const message = {
1067
+ oscType: 'message',
1068
+ address: '/test',
1069
+ args: [{ type: 'string', value: 'café' }]
1070
+ };
1071
+
1072
+ const buffer = nodeOsc.encode(message);
1073
+ const decoded = nodeOsc.decode(buffer);
1074
+
1075
+ t.equal(decoded.args[0].value, 'café', 'should correctly encode and decode accented string');
1076
+
1077
+ const accentedByteLength = Buffer.byteLength('café');
1078
+ t.equal(accentedByteLength, 5, 'café should be 5 bytes in UTF-8');
1079
+
1080
+ t.end();
1081
+ });
1082
+
1083
+ tap.test('encode and decode: UTF-8 string padding - multiple strings', (t) => {
1084
+ // Test multiple strings with different byte lengths in one message
1085
+ const message = {
1086
+ oscType: 'message',
1087
+ address: '/multi',
1088
+ args: [
1089
+ { type: 'string', value: 'a' }, // 1 byte + null
1090
+ { type: 'string', value: '😀' }, // 4 bytes + null
1091
+ { type: 'string', value: 'abc' }, // 3 bytes + null
1092
+ { type: 'string', value: '' } // 0 bytes + null
1093
+ ]
1094
+ };
1095
+
1096
+ const buffer = nodeOsc.encode(message);
1097
+ const decoded = nodeOsc.decode(buffer);
1098
+
1099
+ t.equal(decoded.args.length, 4, 'should have 4 arguments');
1100
+ t.equal(decoded.args[0].value, 'a', 'first string should be correct');
1101
+ t.equal(decoded.args[1].value, '😀', 'second string should be correct');
1102
+ t.equal(decoded.args[2].value, 'abc', 'third string should be correct');
1103
+ t.equal(decoded.args[3].value, '', 'fourth string should be correct');
1104
+
1105
+ t.end();
1106
+ });
1107
+
1108
+ tap.test('encode and decode: UTF-8 string padding - address with emoji', (t) => {
1109
+ // OSC addresses can also contain UTF-8 characters and must be properly padded
1110
+ const message = {
1111
+ oscType: 'message',
1112
+ address: '/test/😀',
1113
+ args: [{ type: 'string', value: 'data' }]
1114
+ };
1115
+
1116
+ const buffer = nodeOsc.encode(message);
1117
+ const decoded = nodeOsc.decode(buffer);
1118
+
1119
+ t.equal(decoded.address, '/test/😀', 'should correctly encode and decode address with emoji');
1120
+ t.equal(decoded.args[0].value, 'data', 'should correctly encode and decode argument');
1121
+
1122
+ t.end();
1123
+ });
1124
+
1125
+ tap.test('encode and decode: UTF-8 string padding - long mixed string', (t) => {
1126
+ // Test a longer string with mixed content
1127
+ const longString = 'Hello 世界 🌍! Testing UTF-8 encoding with café and naïve.';
1128
+ const message = {
1129
+ oscType: 'message',
1130
+ address: '/test',
1131
+ args: [{ type: 'string', value: longString }]
1132
+ };
1133
+
1134
+ const buffer = nodeOsc.encode(message);
1135
+ const decoded = nodeOsc.decode(buffer);
1136
+
1137
+ t.equal(decoded.args[0].value, longString, 'should correctly encode and decode long mixed UTF-8 string');
1138
+
1139
+ t.end();
1140
+ });
1141
+
1142
+ tap.test('encode and decode: UTF-8 string padding - special characters', (t) => {
1143
+ // Test various special characters that may have different byte lengths
1144
+ const specialChars = '!@#$%^&*()_+-=[]{}|;:,.<>?/~`';
1145
+ const message = {
1146
+ oscType: 'message',
1147
+ address: '/test',
1148
+ args: [{ type: 'string', value: specialChars }]
1149
+ };
1150
+
1151
+ const buffer = nodeOsc.encode(message);
1152
+ const decoded = nodeOsc.decode(buffer);
1153
+
1154
+ t.equal(decoded.args[0].value, specialChars, 'should correctly encode and decode special ASCII characters');
1155
+ t.end();
1156
+ });
1157
+
1158
+ tap.test('encode and decode: UTF-8 string padding - control characters', (t) => {
1159
+ // Test control characters
1160
+ const controlChars = 'line1\nline2\ttab';
1161
+ const message = {
1162
+ oscType: 'message',
1163
+ address: '/test',
1164
+ args: [{ type: 'string', value: controlChars }]
1165
+ };
1166
+
1167
+ const buffer = nodeOsc.encode(message);
1168
+ const decoded = nodeOsc.decode(buffer);
1169
+
1170
+ t.equal(decoded.args[0].value, controlChars, 'should correctly encode and decode strings with newlines and tabs');
1171
+ t.end();
1172
+ });
1173
+
1174
+ tap.test('encode and decode: UTF-8 string padding - surrogate pairs', (t) => {
1175
+ // Test various emoji that are 4-byte UTF-8 sequences
1176
+ const emojis = '🎉🎊🎈🎁';
1177
+ const message = {
1178
+ oscType: 'message',
1179
+ address: '/test',
1180
+ args: [{ type: 'string', value: emojis }]
1181
+ };
1182
+
1183
+ const buffer = nodeOsc.encode(message);
1184
+ const decoded = nodeOsc.decode(buffer);
1185
+
1186
+ t.equal(decoded.args[0].value, emojis, 'should correctly encode and decode multiple 4-byte emoji');
1187
+
1188
+ const emojisByteLength = Buffer.byteLength(emojis);
1189
+ t.equal(emojisByteLength, 16, 'four 4-byte emoji should total 16 bytes');
1190
+
1191
+ t.end();
1192
+ });
1193
+
1194
+ tap.test('encode and decode: UTF-8 string padding - zero-width characters', (t) => {
1195
+ // Test zero-width joiner and other special Unicode characters
1196
+ const zwj = 'a\u200Db'; // zero-width joiner
1197
+ const message = {
1198
+ oscType: 'message',
1199
+ address: '/test',
1200
+ args: [{ type: 'string', value: zwj }]
1201
+ };
1202
+
1203
+ const buffer = nodeOsc.encode(message);
1204
+ const decoded = nodeOsc.decode(buffer);
1205
+
1206
+ t.equal(decoded.args[0].value, zwj, 'should correctly encode and decode strings with zero-width characters');
1207
+ t.end();
1208
+ });
@@ -173,6 +173,29 @@ tap.test('message: blob', (t) => {
173
173
  });
174
174
  });
175
175
 
176
+ tap.test('message: Buffer as blob', (t) => {
177
+ const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
178
+ const client = new nodeOsc.Client('127.0.0.1', t.context.port);
179
+ const m = new nodeOsc.Message('/address');
180
+ const buf = Buffer.from('test buffer data');
181
+ // Directly append Buffer without wrapping in object
182
+ m.append(buf);
183
+
184
+ oscServer.on('message', (msg) => {
185
+ const expected = [
186
+ '/address',
187
+ buf
188
+ ];
189
+ t.same(msg, expected, `We received the buffer payload: ${msg}`);
190
+ oscServer.close();
191
+ t.end();
192
+ });
193
+
194
+ client.send(m, () => {
195
+ client.close();
196
+ });
197
+ });
198
+
176
199
  // test('message: timetag', (t) => {
177
200
  // const oscServer = new osc.Server(3333, '127.0.0.1');
178
201
  // const client = new osc.Client('127.0.0.1', 3333);
@@ -192,6 +215,130 @@ tap.test('message: blob', (t) => {
192
215
  // });
193
216
  // });
194
217
 
218
+ tap.test('message: Buffer with multiple arguments', (t) => {
219
+ const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
220
+ const client = new nodeOsc.Client('127.0.0.1', t.context.port);
221
+ const m = new nodeOsc.Message('/address');
222
+ const buf1 = Buffer.from('first');
223
+ const buf2 = Buffer.from('second');
224
+
225
+ m.append('string');
226
+ m.append(42);
227
+ m.append(buf1);
228
+ m.append(3.14);
229
+ m.append(buf2);
230
+
231
+ oscServer.on('message', (msg) => {
232
+ t.equal(msg[0], '/address', 'Address matches');
233
+ t.equal(msg[1], 'string', 'String matches');
234
+ t.equal(msg[2], 42, 'Integer matches');
235
+ t.same(msg[3], buf1, 'First buffer matches');
236
+ t.equal(round(msg[4]), 3.14, 'Float matches');
237
+ t.same(msg[5], buf2, 'Second buffer matches');
238
+ oscServer.close();
239
+ t.end();
240
+ });
241
+
242
+ client.send(m, () => {
243
+ client.close();
244
+ });
245
+ });
246
+
247
+ tap.test('message: Buffer in constructor', (t) => {
248
+ const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
249
+ const client = new nodeOsc.Client('127.0.0.1', t.context.port);
250
+ const buf = Buffer.from('constructor buffer');
251
+ const m = new nodeOsc.Message('/address', 'test', buf, 123);
252
+
253
+ oscServer.on('message', (msg) => {
254
+ const expected = [
255
+ '/address',
256
+ 'test',
257
+ buf,
258
+ 123
259
+ ];
260
+ t.same(msg, expected, `We received the constructor buffer payload: ${msg}`);
261
+ oscServer.close();
262
+ t.end();
263
+ });
264
+
265
+ client.send(m, () => {
266
+ client.close();
267
+ });
268
+ });
269
+
270
+ tap.test('message: Buffer in array', (t) => {
271
+ const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
272
+ const client = new nodeOsc.Client('127.0.0.1', t.context.port);
273
+ const m = new nodeOsc.Message('/address');
274
+ const buf1 = Buffer.from('array1');
275
+ const buf2 = Buffer.from('array2');
276
+
277
+ m.append([buf1, 'string', buf2, 456]);
278
+
279
+ oscServer.on('message', (msg) => {
280
+ const expected = [
281
+ '/address',
282
+ buf1,
283
+ 'string',
284
+ buf2,
285
+ 456
286
+ ];
287
+ t.same(msg, expected, `We received the array with buffers: ${msg}`);
288
+ oscServer.close();
289
+ t.end();
290
+ });
291
+
292
+ client.send(m, () => {
293
+ client.close();
294
+ });
295
+ });
296
+
297
+ tap.test('message: empty Buffer', (t) => {
298
+ const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
299
+ const client = new nodeOsc.Client('127.0.0.1', t.context.port);
300
+ const m = new nodeOsc.Message('/address');
301
+ const buf = Buffer.from('');
302
+
303
+ m.append(buf);
304
+
305
+ oscServer.on('message', (msg) => {
306
+ const expected = [
307
+ '/address',
308
+ buf
309
+ ];
310
+ t.same(msg, expected, `We received the empty buffer: ${msg}`);
311
+ oscServer.close();
312
+ t.end();
313
+ });
314
+
315
+ client.send(m, () => {
316
+ client.close();
317
+ });
318
+ });
319
+
320
+ tap.test('message: large Buffer', (t) => {
321
+ const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
322
+ const client = new nodeOsc.Client('127.0.0.1', t.context.port);
323
+ const m = new nodeOsc.Message('/address');
324
+ const buf = Buffer.alloc(1024, 'x');
325
+
326
+ m.append(buf);
327
+
328
+ oscServer.on('message', (msg) => {
329
+ t.equal(msg[0], '/address', 'Address matches');
330
+ t.ok(Buffer.isBuffer(msg[1]), 'Second element is a Buffer');
331
+ t.equal(msg[1].length, 1024, 'Buffer size matches');
332
+ t.same(msg[1], buf, 'Buffer content matches');
333
+ oscServer.close();
334
+ t.end();
335
+ });
336
+
337
+ client.send(m, () => {
338
+ client.close();
339
+ });
340
+ });
341
+
195
342
  tap.test('message: error', (t) => {
196
343
  const m = new nodeOsc.Message('/address');
197
344
  t.plan(2);
@@ -22,6 +22,28 @@ tap.test('client: send with promise - array', async (t) => {
22
22
  await client.close();
23
23
  });
24
24
 
25
+ tap.test('client: array is not mutated when sent with promise', 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);
28
+
29
+ t.plan(2);
30
+
31
+ const originalArray = ['/test', 0, 1, 'testing', true];
32
+ const expectedArray = ['/test', 0, 1, 'testing', true];
33
+
34
+ oscServer.on('message', (msg) => {
35
+ oscServer.close();
36
+ t.same(msg, ['/test', 0, 1, 'testing', true], 'We should receive expected payload');
37
+ });
38
+
39
+ await client.send(originalArray);
40
+
41
+ // Verify the original array was not mutated
42
+ t.same(originalArray, expectedArray, 'Original array should not be mutated');
43
+
44
+ await client.close();
45
+ });
46
+
25
47
  tap.test('client: send with promise - string', async (t) => {
26
48
  const oscServer = new nodeOsc.Server(t.context.port, '127.0.0.1');
27
49
  const client = new nodeOsc.Client('127.0.0.1', t.context.port);
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/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) {
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.1",
5
5
  "exports": {
6
6
  "types": "./types/index.d.mts",
7
7
  "require": "./dist/lib/index.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'
@@ -22,6 +22,28 @@ test('client: with array', (t) => {
22
22
  });
23
23
  });
24
24
 
25
+ test('client: array is not mutated when sent', (t) => {
26
+ const oscServer = new Server(t.context.port, '127.0.0.1');
27
+ const client = new Client('127.0.0.1', t.context.port);
28
+
29
+ t.plan(3);
30
+
31
+ const originalArray = ['/test', 0, 1, 'testing', true];
32
+ const expectedArray = ['/test', 0, 1, 'testing', true];
33
+
34
+ oscServer.on('message', (msg) => {
35
+ oscServer.close();
36
+ t.same(msg, ['/test', 0, 1, 'testing', true], 'We should receive expected payload');
37
+ // Verify the original array was not mutated
38
+ t.same(originalArray, expectedArray, 'Original array should not be mutated');
39
+ });
40
+
41
+ client.send(originalArray, (err) => {
42
+ t.error(err, 'there should be no error');
43
+ client.close();
44
+ });
45
+ });
46
+
25
47
  test('client: with string', (t) => {
26
48
  const oscServer = new Server(t.context.port, '127.0.0.1');
27
49
  const client = new Client('127.0.0.1', t.context.port);
@@ -845,3 +845,362 @@ test('encode and decode: type "boolean" with false value', (t) => {
845
845
  t.equal(decoded.args[0].value, false);
846
846
  t.end();
847
847
  });
848
+
849
+ // Tests for UTF-8 string padding to 4-byte boundaries
850
+ // The padString function ensures OSC strings are padded based on byte length
851
+ // (not character count) to handle multi-byte UTF-8 correctly
852
+
853
+ test('encode and decode: UTF-8 string padding - ASCII 1 char', (t) => {
854
+ // 1 byte + 1 null terminator = 2 bytes, needs 2 padding bytes to reach 4-byte boundary
855
+ const message = {
856
+ oscType: 'message',
857
+ address: '/test',
858
+ args: [{ type: 'string', value: 'a' }]
859
+ };
860
+
861
+ const buffer = encode(message);
862
+ const decoded = decode(buffer);
863
+
864
+ t.equal(decoded.args[0].value, 'a', 'should correctly encode and decode single ASCII character');
865
+
866
+ // Verify the string is properly padded in the buffer
867
+ // Address "/test" is 5 bytes + 1 null = 6 bytes, padded to 8 bytes
868
+ // Type tag ",s" is 2 bytes + 1 null = 3 bytes, padded to 4 bytes
869
+ // String "a" is 1 byte + 1 null = 2 bytes, padded to 4 bytes
870
+ const expectedMinLength = 8 + 4 + 4; // 16 bytes minimum
871
+ t.ok(buffer.length >= expectedMinLength, 'buffer should contain properly padded string');
872
+
873
+ t.end();
874
+ });
875
+
876
+ test('encode and decode: UTF-8 string padding - ASCII 2 chars', (t) => {
877
+ // 2 bytes + 1 null = 3 bytes, needs 1 padding byte to reach 4-byte boundary
878
+ const message = {
879
+ oscType: 'message',
880
+ address: '/test',
881
+ args: [{ type: 'string', value: 'ab' }]
882
+ };
883
+
884
+ const buffer = encode(message);
885
+ const decoded = decode(buffer);
886
+
887
+ t.equal(decoded.args[0].value, 'ab', 'should correctly encode and decode 2-byte string');
888
+ t.end();
889
+ });
890
+
891
+ test('encode and decode: UTF-8 string padding - ASCII 3 chars', (t) => {
892
+ // 3 bytes + 1 null terminator = 4 bytes, needs 0 padding (already aligned)
893
+ const message = {
894
+ oscType: 'message',
895
+ address: '/test',
896
+ args: [{ type: 'string', value: 'abc' }]
897
+ };
898
+
899
+ const buffer = encode(message);
900
+ const decoded = decode(buffer);
901
+
902
+ t.equal(decoded.args[0].value, 'abc', 'should correctly encode and decode 3-char ASCII string');
903
+ t.end();
904
+ });
905
+
906
+ test('encode and decode: UTF-8 string padding - ASCII 5 chars', (t) => {
907
+ // 5 bytes + 1 null terminator = 6 bytes, needs 2 padding bytes to reach 8-byte boundary
908
+ const message = {
909
+ oscType: 'message',
910
+ address: '/test',
911
+ args: [{ type: 'string', value: 'hello' }]
912
+ };
913
+
914
+ const buffer = encode(message);
915
+ const decoded = decode(buffer);
916
+
917
+ t.equal(decoded.args[0].value, 'hello', 'should correctly encode and decode 5-char ASCII string');
918
+ t.end();
919
+ });
920
+
921
+ test('encode and decode: UTF-8 string padding - ASCII 6 chars', (t) => {
922
+ // 6 bytes + 1 null = 7 bytes, needs 1 padding byte to reach 8-byte boundary
923
+ const message = {
924
+ oscType: 'message',
925
+ address: '/test',
926
+ args: [{ type: 'string', value: 'abcdef' }]
927
+ };
928
+
929
+ const buffer = encode(message);
930
+ const decoded = decode(buffer);
931
+
932
+ t.equal(decoded.args[0].value, 'abcdef', 'should correctly encode and decode 6-byte string');
933
+ t.end();
934
+ });
935
+
936
+ test('encode and decode: UTF-8 string padding - ASCII 7 chars', (t) => {
937
+ // 7 bytes + 1 null terminator = 8 bytes, needs 0 padding (already aligned)
938
+ const message = {
939
+ oscType: 'message',
940
+ address: '/test',
941
+ args: [{ type: 'string', value: 'testing' }]
942
+ };
943
+
944
+ const buffer = encode(message);
945
+ const decoded = decode(buffer);
946
+
947
+ t.equal(decoded.args[0].value, 'testing', 'should correctly encode and decode 7-char ASCII string');
948
+ t.end();
949
+ });
950
+
951
+ test('encode and decode: UTF-8 string padding - empty string', (t) => {
952
+ // 0 bytes + 1 null terminator = 1 byte, needs 3 padding bytes to reach 4-byte boundary
953
+ const message = {
954
+ oscType: 'message',
955
+ address: '/test',
956
+ args: [{ type: 'string', value: '' }]
957
+ };
958
+
959
+ const buffer = encode(message);
960
+ const decoded = decode(buffer);
961
+
962
+ t.equal(decoded.args[0].value, '', 'should correctly encode and decode empty string');
963
+ t.end();
964
+ });
965
+
966
+ test('encode and decode: UTF-8 string padding - emoji character', (t) => {
967
+ // Emoji '😀' is 4 bytes in UTF-8 + 1 null = 5 bytes, needs 3 padding to reach 8-byte boundary
968
+ const message = {
969
+ oscType: 'message',
970
+ address: '/test',
971
+ args: [{ type: 'string', value: '😀' }]
972
+ };
973
+
974
+ const buffer = encode(message);
975
+ const decoded = decode(buffer);
976
+
977
+ t.equal(decoded.args[0].value, '😀', 'should correctly encode and decode emoji character');
978
+
979
+ // Verify byte length calculation is correct
980
+ const emojiByteLength = Buffer.byteLength('😀');
981
+ t.equal(emojiByteLength, 4, 'emoji should be 4 bytes in UTF-8');
982
+
983
+ t.end();
984
+ });
985
+
986
+ test('encode and decode: UTF-8 string padding - Japanese character', (t) => {
987
+ // Japanese 'あ' is 3 bytes in UTF-8 + 1 null = 4 bytes, needs 0 padding (already aligned)
988
+ const message = {
989
+ oscType: 'message',
990
+ address: '/test',
991
+ args: [{ type: 'string', value: 'あ' }]
992
+ };
993
+
994
+ const buffer = encode(message);
995
+ const decoded = decode(buffer);
996
+
997
+ t.equal(decoded.args[0].value, 'あ', 'should correctly encode and decode Japanese character');
998
+
999
+ const japaneseByteLength = Buffer.byteLength('あ');
1000
+ t.equal(japaneseByteLength, 3, 'Japanese character should be 3 bytes in UTF-8');
1001
+
1002
+ t.end();
1003
+ });
1004
+
1005
+ test('encode and decode: UTF-8 string padding - Chinese character', (t) => {
1006
+ // Chinese '中' is 3 bytes in UTF-8 + 1 null = 4 bytes, needs 0 padding (already aligned)
1007
+ const message = {
1008
+ oscType: 'message',
1009
+ address: '/test',
1010
+ args: [{ type: 'string', value: '中' }]
1011
+ };
1012
+
1013
+ const buffer = encode(message);
1014
+ const decoded = decode(buffer);
1015
+
1016
+ t.equal(decoded.args[0].value, '中', 'should correctly encode and decode Chinese character');
1017
+ t.end();
1018
+ });
1019
+
1020
+ test('encode and decode: UTF-8 string padding - mixed ASCII and emoji', (t) => {
1021
+ // 'a' (1 byte) + '😀' (4 bytes) + 'b' (1 byte) = 6 bytes + 1 null = 7 bytes
1022
+ // needs 1 padding byte to reach 8-byte boundary
1023
+ const message = {
1024
+ oscType: 'message',
1025
+ address: '/test',
1026
+ args: [{ type: 'string', value: 'a😀b' }]
1027
+ };
1028
+
1029
+ const buffer = encode(message);
1030
+ const decoded = decode(buffer);
1031
+
1032
+ t.equal(decoded.args[0].value, 'a😀b', 'should correctly encode and decode mixed ASCII and emoji');
1033
+
1034
+ const mixedByteLength = Buffer.byteLength('a😀b');
1035
+ t.equal(mixedByteLength, 6, 'mixed string should be 6 bytes in UTF-8');
1036
+
1037
+ t.end();
1038
+ });
1039
+
1040
+ test('encode and decode: UTF-8 string padding - Japanese string', (t) => {
1041
+ // 'こんにちは' (Hello in Japanese) - 5 characters, each 3 bytes = 15 bytes
1042
+ // 15 bytes + 1 null = 16 bytes, needs 0 padding (already aligned)
1043
+ const message = {
1044
+ oscType: 'message',
1045
+ address: '/test',
1046
+ args: [{ type: 'string', value: 'こんにちは' }]
1047
+ };
1048
+
1049
+ const buffer = encode(message);
1050
+ const decoded = decode(buffer);
1051
+
1052
+ t.equal(decoded.args[0].value, 'こんにちは', 'should correctly encode and decode Japanese string');
1053
+
1054
+ const japaneseStringByteLength = Buffer.byteLength('こんにちは');
1055
+ t.equal(japaneseStringByteLength, 15, 'Japanese string should be 15 bytes in UTF-8');
1056
+
1057
+ t.end();
1058
+ });
1059
+
1060
+ test('encode and decode: UTF-8 string padding - accented characters', (t) => {
1061
+ // 'café' - 4 characters but 'é' is 2 bytes in UTF-8
1062
+ // 'c' (1) + 'a' (1) + 'f' (1) + 'é' (2) = 5 bytes + 1 null = 6 bytes
1063
+ // needs 2 padding bytes to reach 8-byte boundary
1064
+ const message = {
1065
+ oscType: 'message',
1066
+ address: '/test',
1067
+ args: [{ type: 'string', value: 'café' }]
1068
+ };
1069
+
1070
+ const buffer = encode(message);
1071
+ const decoded = decode(buffer);
1072
+
1073
+ t.equal(decoded.args[0].value, 'café', 'should correctly encode and decode accented string');
1074
+
1075
+ const accentedByteLength = Buffer.byteLength('café');
1076
+ t.equal(accentedByteLength, 5, 'café should be 5 bytes in UTF-8');
1077
+
1078
+ t.end();
1079
+ });
1080
+
1081
+ test('encode and decode: UTF-8 string padding - multiple strings', (t) => {
1082
+ // Test multiple strings with different byte lengths in one message
1083
+ const message = {
1084
+ oscType: 'message',
1085
+ address: '/multi',
1086
+ args: [
1087
+ { type: 'string', value: 'a' }, // 1 byte + null
1088
+ { type: 'string', value: '😀' }, // 4 bytes + null
1089
+ { type: 'string', value: 'abc' }, // 3 bytes + null
1090
+ { type: 'string', value: '' } // 0 bytes + null
1091
+ ]
1092
+ };
1093
+
1094
+ const buffer = encode(message);
1095
+ const decoded = decode(buffer);
1096
+
1097
+ t.equal(decoded.args.length, 4, 'should have 4 arguments');
1098
+ t.equal(decoded.args[0].value, 'a', 'first string should be correct');
1099
+ t.equal(decoded.args[1].value, '😀', 'second string should be correct');
1100
+ t.equal(decoded.args[2].value, 'abc', 'third string should be correct');
1101
+ t.equal(decoded.args[3].value, '', 'fourth string should be correct');
1102
+
1103
+ t.end();
1104
+ });
1105
+
1106
+ test('encode and decode: UTF-8 string padding - address with emoji', (t) => {
1107
+ // OSC addresses can also contain UTF-8 characters and must be properly padded
1108
+ const message = {
1109
+ oscType: 'message',
1110
+ address: '/test/😀',
1111
+ args: [{ type: 'string', value: 'data' }]
1112
+ };
1113
+
1114
+ const buffer = encode(message);
1115
+ const decoded = decode(buffer);
1116
+
1117
+ t.equal(decoded.address, '/test/😀', 'should correctly encode and decode address with emoji');
1118
+ t.equal(decoded.args[0].value, 'data', 'should correctly encode and decode argument');
1119
+
1120
+ t.end();
1121
+ });
1122
+
1123
+ test('encode and decode: UTF-8 string padding - long mixed string', (t) => {
1124
+ // Test a longer string with mixed content
1125
+ const longString = 'Hello 世界 🌍! Testing UTF-8 encoding with café and naïve.';
1126
+ const message = {
1127
+ oscType: 'message',
1128
+ address: '/test',
1129
+ args: [{ type: 'string', value: longString }]
1130
+ };
1131
+
1132
+ const buffer = encode(message);
1133
+ const decoded = decode(buffer);
1134
+
1135
+ t.equal(decoded.args[0].value, longString, 'should correctly encode and decode long mixed UTF-8 string');
1136
+
1137
+ t.end();
1138
+ });
1139
+
1140
+ test('encode and decode: UTF-8 string padding - special characters', (t) => {
1141
+ // Test various special characters that may have different byte lengths
1142
+ const specialChars = '!@#$%^&*()_+-=[]{}|;:,.<>?/~`';
1143
+ const message = {
1144
+ oscType: 'message',
1145
+ address: '/test',
1146
+ args: [{ type: 'string', value: specialChars }]
1147
+ };
1148
+
1149
+ const buffer = encode(message);
1150
+ const decoded = decode(buffer);
1151
+
1152
+ t.equal(decoded.args[0].value, specialChars, 'should correctly encode and decode special ASCII characters');
1153
+ t.end();
1154
+ });
1155
+
1156
+ test('encode and decode: UTF-8 string padding - control characters', (t) => {
1157
+ // Test control characters
1158
+ const controlChars = 'line1\nline2\ttab';
1159
+ const message = {
1160
+ oscType: 'message',
1161
+ address: '/test',
1162
+ args: [{ type: 'string', value: controlChars }]
1163
+ };
1164
+
1165
+ const buffer = encode(message);
1166
+ const decoded = decode(buffer);
1167
+
1168
+ t.equal(decoded.args[0].value, controlChars, 'should correctly encode and decode strings with newlines and tabs');
1169
+ t.end();
1170
+ });
1171
+
1172
+ test('encode and decode: UTF-8 string padding - surrogate pairs', (t) => {
1173
+ // Test various emoji that are 4-byte UTF-8 sequences
1174
+ const emojis = '🎉🎊🎈🎁';
1175
+ const message = {
1176
+ oscType: 'message',
1177
+ address: '/test',
1178
+ args: [{ type: 'string', value: emojis }]
1179
+ };
1180
+
1181
+ const buffer = encode(message);
1182
+ const decoded = decode(buffer);
1183
+
1184
+ t.equal(decoded.args[0].value, emojis, 'should correctly encode and decode multiple 4-byte emoji');
1185
+
1186
+ const emojisByteLength = Buffer.byteLength(emojis);
1187
+ t.equal(emojisByteLength, 16, 'four 4-byte emoji should total 16 bytes');
1188
+
1189
+ t.end();
1190
+ });
1191
+
1192
+ test('encode and decode: UTF-8 string padding - zero-width characters', (t) => {
1193
+ // Test zero-width joiner and other special Unicode characters
1194
+ const zwj = 'a\u200Db'; // zero-width joiner
1195
+ const message = {
1196
+ oscType: 'message',
1197
+ address: '/test',
1198
+ args: [{ type: 'string', value: zwj }]
1199
+ };
1200
+
1201
+ const buffer = encode(message);
1202
+ const decoded = decode(buffer);
1203
+
1204
+ t.equal(decoded.args[0].value, zwj, 'should correctly encode and decode strings with zero-width characters');
1205
+ t.end();
1206
+ });
@@ -172,6 +172,29 @@ test('message: blob', (t) => {
172
172
  });
173
173
  });
174
174
 
175
+ test('message: Buffer as blob', (t) => {
176
+ const oscServer = new Server(t.context.port, '127.0.0.1');
177
+ const client = new Client('127.0.0.1', t.context.port);
178
+ const m = new Message('/address');
179
+ const buf = Buffer.from('test buffer data');
180
+ // Directly append Buffer without wrapping in object
181
+ m.append(buf);
182
+
183
+ oscServer.on('message', (msg) => {
184
+ const expected = [
185
+ '/address',
186
+ buf
187
+ ];
188
+ t.same(msg, expected, `We received the buffer payload: ${msg}`);
189
+ oscServer.close();
190
+ t.end();
191
+ });
192
+
193
+ client.send(m, () => {
194
+ client.close();
195
+ });
196
+ });
197
+
175
198
  // test('message: timetag', (t) => {
176
199
  // const oscServer = new osc.Server(3333, '127.0.0.1');
177
200
  // const client = new osc.Client('127.0.0.1', 3333);
@@ -191,6 +214,130 @@ test('message: blob', (t) => {
191
214
  // });
192
215
  // });
193
216
 
217
+ test('message: Buffer with multiple arguments', (t) => {
218
+ const oscServer = new Server(t.context.port, '127.0.0.1');
219
+ const client = new Client('127.0.0.1', t.context.port);
220
+ const m = new Message('/address');
221
+ const buf1 = Buffer.from('first');
222
+ const buf2 = Buffer.from('second');
223
+
224
+ m.append('string');
225
+ m.append(42);
226
+ m.append(buf1);
227
+ m.append(3.14);
228
+ m.append(buf2);
229
+
230
+ oscServer.on('message', (msg) => {
231
+ t.equal(msg[0], '/address', 'Address matches');
232
+ t.equal(msg[1], 'string', 'String matches');
233
+ t.equal(msg[2], 42, 'Integer matches');
234
+ t.same(msg[3], buf1, 'First buffer matches');
235
+ t.equal(round(msg[4]), 3.14, 'Float matches');
236
+ t.same(msg[5], buf2, 'Second buffer matches');
237
+ oscServer.close();
238
+ t.end();
239
+ });
240
+
241
+ client.send(m, () => {
242
+ client.close();
243
+ });
244
+ });
245
+
246
+ test('message: Buffer in constructor', (t) => {
247
+ const oscServer = new Server(t.context.port, '127.0.0.1');
248
+ const client = new Client('127.0.0.1', t.context.port);
249
+ const buf = Buffer.from('constructor buffer');
250
+ const m = new Message('/address', 'test', buf, 123);
251
+
252
+ oscServer.on('message', (msg) => {
253
+ const expected = [
254
+ '/address',
255
+ 'test',
256
+ buf,
257
+ 123
258
+ ];
259
+ t.same(msg, expected, `We received the constructor buffer payload: ${msg}`);
260
+ oscServer.close();
261
+ t.end();
262
+ });
263
+
264
+ client.send(m, () => {
265
+ client.close();
266
+ });
267
+ });
268
+
269
+ test('message: Buffer in array', (t) => {
270
+ const oscServer = new Server(t.context.port, '127.0.0.1');
271
+ const client = new Client('127.0.0.1', t.context.port);
272
+ const m = new Message('/address');
273
+ const buf1 = Buffer.from('array1');
274
+ const buf2 = Buffer.from('array2');
275
+
276
+ m.append([buf1, 'string', buf2, 456]);
277
+
278
+ oscServer.on('message', (msg) => {
279
+ const expected = [
280
+ '/address',
281
+ buf1,
282
+ 'string',
283
+ buf2,
284
+ 456
285
+ ];
286
+ t.same(msg, expected, `We received the array with buffers: ${msg}`);
287
+ oscServer.close();
288
+ t.end();
289
+ });
290
+
291
+ client.send(m, () => {
292
+ client.close();
293
+ });
294
+ });
295
+
296
+ test('message: empty Buffer', (t) => {
297
+ const oscServer = new Server(t.context.port, '127.0.0.1');
298
+ const client = new Client('127.0.0.1', t.context.port);
299
+ const m = new Message('/address');
300
+ const buf = Buffer.from('');
301
+
302
+ m.append(buf);
303
+
304
+ oscServer.on('message', (msg) => {
305
+ const expected = [
306
+ '/address',
307
+ buf
308
+ ];
309
+ t.same(msg, expected, `We received the empty buffer: ${msg}`);
310
+ oscServer.close();
311
+ t.end();
312
+ });
313
+
314
+ client.send(m, () => {
315
+ client.close();
316
+ });
317
+ });
318
+
319
+ test('message: large Buffer', (t) => {
320
+ const oscServer = new Server(t.context.port, '127.0.0.1');
321
+ const client = new Client('127.0.0.1', t.context.port);
322
+ const m = new Message('/address');
323
+ const buf = Buffer.alloc(1024, 'x');
324
+
325
+ m.append(buf);
326
+
327
+ oscServer.on('message', (msg) => {
328
+ t.equal(msg[0], '/address', 'Address matches');
329
+ t.ok(Buffer.isBuffer(msg[1]), 'Second element is a Buffer');
330
+ t.equal(msg[1].length, 1024, 'Buffer size matches');
331
+ t.same(msg[1], buf, 'Buffer content matches');
332
+ oscServer.close();
333
+ t.end();
334
+ });
335
+
336
+ client.send(m, () => {
337
+ client.close();
338
+ });
339
+ });
340
+
194
341
  test('message: error', (t) => {
195
342
  const m = new Message('/address');
196
343
  t.plan(2);
@@ -21,6 +21,28 @@ test('client: send with promise - array', async (t) => {
21
21
  await client.close();
22
22
  });
23
23
 
24
+ test('client: array is not mutated when sent with promise', async (t) => {
25
+ const oscServer = new Server(t.context.port, '127.0.0.1');
26
+ const client = new Client('127.0.0.1', t.context.port);
27
+
28
+ t.plan(2);
29
+
30
+ const originalArray = ['/test', 0, 1, 'testing', true];
31
+ const expectedArray = ['/test', 0, 1, 'testing', true];
32
+
33
+ oscServer.on('message', (msg) => {
34
+ oscServer.close();
35
+ t.same(msg, ['/test', 0, 1, 'testing', true], 'We should receive expected payload');
36
+ });
37
+
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
+
43
+ await client.close();
44
+ });
45
+
24
46
  test('client: send with promise - string', async (t) => {
25
47
  const oscServer = new Server(t.context.port, '127.0.0.1');
26
48
  const client = new Client('127.0.0.1', t.context.port);
@@ -1 +1 @@
1
- {"version":3,"file":"Message.d.mts","sourceRoot":"","sources":["../lib/Message.mjs"],"names":[],"mappings":";AAyBA;;;;;;;;;;;;;;;;;;GAkBG;AACH;IACE;;;;;;;;;;;;;;OAcG;IACH,qBAZW,MAAM,WACH,GAAC,EAAA,EAed;IAHC,gBAAwB;IACxB,gBAAsB;IACtB,YAAgB;IAGlB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwCG;IACH,YA7BW,GAAC,QA2DX;CACF"}
1
+ {"version":3,"file":"Message.d.mts","sourceRoot":"","sources":["../lib/Message.mjs"],"names":[],"mappings":";AAyBA;;;;;;;;;;;;;;;;;;GAkBG;AACH;IACE;;;;;;;;;;;;;;OAcG;IACH,qBAZW,MAAM,WACH,GAAC,EAAA,EAed;IAHC,gBAAwB;IACxB,gBAAsB;IACtB,YAAgB;IAGlB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwCG;IACH,YA7BW,GAAC,QA6DX;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"osc.d.mts","sourceRoot":"","sources":["../lib/osc.mjs"],"names":[],"mappings":"AA+MA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,sCAtBa,MAAM,CA4BlB;AA6CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,+BAzBW,MAAM,OAgChB;uBAlUsB,aAAa"}
1
+ {"version":3,"file":"osc.d.mts","sourceRoot":"","sources":["../lib/osc.mjs"],"names":[],"mappings":"AAgNA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,sCAtBa,MAAM,CA4BlB;AA6CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,+BAzBW,MAAM,OAgChB;uBAnUsB,aAAa"}