react-native-nitro-buffer 0.0.1 → 0.0.3

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/README.md CHANGED
@@ -124,6 +124,21 @@ This library achieves **100% API compatibility** with Node.js `Buffer`.
124
124
  const cBuf = CraftzBuffer.from(nBuf); // Works!
125
125
  ```
126
126
 
127
+ ## ⚠️ Compatibility Notes
128
+
129
+ ### `toString('ascii')` Behavior
130
+
131
+ When decoding binary data with non-ASCII bytes (0x80-0xFF), `react-native-nitro-buffer` follows the **Node.js standard** by replacing invalid bytes with the Unicode replacement character (`U+FFFD`, displayed as `�`).
132
+
133
+ ```javascript
134
+ const buf = Buffer.from([0x48, 0x69, 0x80, 0xFF, 0x21]); // "Hi" + invalid bytes + "!"
135
+ buf.toString('ascii');
136
+ // Nitro (Node.js compatible): "Hi��!" (length: 5)
137
+ // @craftzdog/react-native-buffer: "Hi!" (length: 5) - incorrectly drops invalid bytes
138
+ ```
139
+
140
+ This ensures consistent behavior with Node.js when handling binary protocols like WebSocket messages containing mixed text and binary data (e.g., Microsoft TTS audio streams).
141
+
127
142
  ## 📄 License
128
143
 
129
144
  ISC
@@ -217,6 +217,177 @@ double HybridNitroBuffer::write(const std::shared_ptr<ArrayBuffer> &buffer,
217
217
  return actualWrite;
218
218
  }
219
219
 
220
+ // UTF-8 replacement character (U+FFFD) encoded as UTF-8
221
+ static const char UTF8_REPLACEMENT[] = "\xEF\xBF\xBD";
222
+
223
+ // Decode UTF-8 with WHATWG-compliant error handling (replace invalid bytes with
224
+ // U+FFFD) This matches Node.js Buffer.toString('utf8') behavior
225
+ static std::string decodeUtf8WithReplacement(const uint8_t *data, size_t len) {
226
+ std::string result;
227
+ result.reserve(len); // Minimum reservation
228
+
229
+ size_t i = 0;
230
+ while (i < len) {
231
+ uint8_t byte1 = data[i];
232
+
233
+ // ASCII (0x00-0x7F)
234
+ if (byte1 <= 0x7F) {
235
+ result.push_back(static_cast<char>(byte1));
236
+ i++;
237
+ continue;
238
+ }
239
+
240
+ // Invalid leading byte (0x80-0xBF or 0xF8-0xFF)
241
+ if (byte1 < 0xC2 || byte1 > 0xF4) {
242
+ result.append(UTF8_REPLACEMENT);
243
+ i++;
244
+ continue;
245
+ }
246
+
247
+ // 2-byte sequence (0xC2-0xDF)
248
+ if (byte1 >= 0xC2 && byte1 <= 0xDF) {
249
+ if (i + 1 >= len) {
250
+ result.append(UTF8_REPLACEMENT);
251
+ i++;
252
+ continue;
253
+ }
254
+ uint8_t byte2 = data[i + 1];
255
+ if ((byte2 & 0xC0) != 0x80) {
256
+ result.append(UTF8_REPLACEMENT);
257
+ i++;
258
+ continue;
259
+ }
260
+ // Valid 2-byte sequence
261
+ result.push_back(static_cast<char>(byte1));
262
+ result.push_back(static_cast<char>(byte2));
263
+ i += 2;
264
+ continue;
265
+ }
266
+
267
+ // 3-byte sequence (0xE0-0xEF)
268
+ if (byte1 >= 0xE0 && byte1 <= 0xEF) {
269
+ if (i + 2 >= len) {
270
+ result.append(UTF8_REPLACEMENT);
271
+ i++;
272
+ continue;
273
+ }
274
+ uint8_t byte2 = data[i + 1];
275
+ uint8_t byte3 = data[i + 2];
276
+
277
+ // Check continuation bytes
278
+ if ((byte2 & 0xC0) != 0x80 || (byte3 & 0xC0) != 0x80) {
279
+ result.append(UTF8_REPLACEMENT);
280
+ i++;
281
+ continue;
282
+ }
283
+
284
+ // Check for overlong encoding and surrogate halves
285
+ if (byte1 == 0xE0 && byte2 < 0xA0) {
286
+ result.append(UTF8_REPLACEMENT);
287
+ i++;
288
+ continue;
289
+ }
290
+ if (byte1 == 0xED && byte2 >= 0xA0) {
291
+ // Surrogate halves (0xD800-0xDFFF) are invalid in UTF-8
292
+ result.append(UTF8_REPLACEMENT);
293
+ i++;
294
+ continue;
295
+ }
296
+
297
+ // Valid 3-byte sequence
298
+ result.push_back(static_cast<char>(byte1));
299
+ result.push_back(static_cast<char>(byte2));
300
+ result.push_back(static_cast<char>(byte3));
301
+ i += 3;
302
+ continue;
303
+ }
304
+
305
+ // 4-byte sequence (0xF0-0xF4)
306
+ if (byte1 >= 0xF0 && byte1 <= 0xF4) {
307
+ if (i + 3 >= len) {
308
+ result.append(UTF8_REPLACEMENT);
309
+ i++;
310
+ continue;
311
+ }
312
+ uint8_t byte2 = data[i + 1];
313
+ uint8_t byte3 = data[i + 2];
314
+ uint8_t byte4 = data[i + 3];
315
+
316
+ // Check continuation bytes
317
+ if ((byte2 & 0xC0) != 0x80 || (byte3 & 0xC0) != 0x80 ||
318
+ (byte4 & 0xC0) != 0x80) {
319
+ result.append(UTF8_REPLACEMENT);
320
+ i++;
321
+ continue;
322
+ }
323
+
324
+ // Check for overlong encoding and out-of-range code points
325
+ if (byte1 == 0xF0 && byte2 < 0x90) {
326
+ result.append(UTF8_REPLACEMENT);
327
+ i++;
328
+ continue;
329
+ }
330
+ if (byte1 == 0xF4 && byte2 > 0x8F) {
331
+ // Code points above U+10FFFF
332
+ result.append(UTF8_REPLACEMENT);
333
+ i++;
334
+ continue;
335
+ }
336
+
337
+ // Valid 4-byte sequence
338
+ result.push_back(static_cast<char>(byte1));
339
+ result.push_back(static_cast<char>(byte2));
340
+ result.push_back(static_cast<char>(byte3));
341
+ result.push_back(static_cast<char>(byte4));
342
+ i += 4;
343
+ continue;
344
+ }
345
+
346
+ // Fallback (should not reach here)
347
+ result.append(UTF8_REPLACEMENT);
348
+ i++;
349
+ }
350
+
351
+ return result;
352
+ }
353
+
354
+ // Decode as latin1/binary - each byte maps directly to Unicode code point
355
+ // 0x00-0xFF
356
+ static std::string decodeLatin1(const uint8_t *data, size_t len) {
357
+ std::string result;
358
+ result.reserve(len * 2); // Worst case: all bytes > 0x7F need 2 bytes in UTF-8
359
+
360
+ for (size_t i = 0; i < len; i++) {
361
+ uint8_t byte = data[i];
362
+ if (byte <= 0x7F) {
363
+ result.push_back(static_cast<char>(byte));
364
+ } else {
365
+ // Encode as 2-byte UTF-8 sequence
366
+ result.push_back(static_cast<char>(0xC0 | (byte >> 6)));
367
+ result.push_back(static_cast<char>(0x80 | (byte & 0x3F)));
368
+ }
369
+ }
370
+
371
+ return result;
372
+ }
373
+
374
+ // Decode as ASCII - bytes > 0x7F are replaced with U+FFFD
375
+ static std::string decodeAscii(const uint8_t *data, size_t len) {
376
+ std::string result;
377
+ result.reserve(len);
378
+
379
+ for (size_t i = 0; i < len; i++) {
380
+ uint8_t byte = data[i];
381
+ if (byte <= 0x7F) {
382
+ result.push_back(static_cast<char>(byte));
383
+ } else {
384
+ result.append(UTF8_REPLACEMENT);
385
+ }
386
+ }
387
+
388
+ return result;
389
+ }
390
+
220
391
  std::string
221
392
  HybridNitroBuffer::decode(const std::shared_ptr<ArrayBuffer> &buffer,
222
393
  double offset, double length,
@@ -232,8 +403,15 @@ HybridNitroBuffer::decode(const std::shared_ptr<ArrayBuffer> &buffer,
232
403
  size_t actualRead = std::min(available, count);
233
404
 
234
405
  if (encoding == "utf8" || encoding == "utf-8") {
235
- // Check for null termination? No, ArrayBuffer may not be null terminated.
236
- return std::string((char *)(data + start), actualRead);
406
+ // WHATWG-compliant UTF-8 decoding with replacement character for invalid
407
+ // sequences
408
+ return decodeUtf8WithReplacement(data + start, actualRead);
409
+ } else if (encoding == "latin1" || encoding == "binary") {
410
+ // Each byte maps to Unicode code point 0x00-0xFF
411
+ return decodeLatin1(data + start, actualRead);
412
+ } else if (encoding == "ascii") {
413
+ // ASCII with replacement for non-ASCII bytes
414
+ return decodeAscii(data + start, actualRead);
237
415
  } else if (encoding == "hex") {
238
416
  std::string hex;
239
417
  hex.reserve(actualRead * 2);
@@ -248,7 +426,8 @@ HybridNitroBuffer::decode(const std::shared_ptr<ArrayBuffer> &buffer,
248
426
  return base64_encode(data + start, (unsigned int)actualRead);
249
427
  }
250
428
 
251
- return std::string((char *)(data + start), actualRead);
429
+ // Default: UTF-8 with replacement
430
+ return decodeUtf8WithReplacement(data + start, actualRead);
252
431
  }
253
432
 
254
433
  double HybridNitroBuffer::compare(const std::shared_ptr<ArrayBuffer> &a,
package/lib/Buffer.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export declare class Buffer extends Uint8Array {
2
+ static poolSize: number;
2
3
  constructor(length: number);
3
4
  constructor(array: Uint8Array);
4
5
  constructor(arrayBuffer: ArrayBuffer, byteOffset?: number, length?: number);
@@ -11,6 +12,7 @@ export declare class Buffer extends Uint8Array {
11
12
  static byteLength(string: string, encoding?: string): number;
12
13
  static isBuffer(obj: any): obj is Buffer;
13
14
  static compare(buf1: Uint8Array, buf2: Uint8Array): number;
15
+ equals(otherBuffer: Uint8Array): boolean;
14
16
  static copyBytesFrom(view: ArrayBufferView, offset?: number, length?: number): Buffer;
15
17
  static concat(list: Uint8Array[], totalLength?: number): Buffer;
16
18
  write(string: string, offset?: number, length?: number, encoding?: string): number;
@@ -70,7 +72,7 @@ export declare class Buffer extends Uint8Array {
70
72
  type: 'Buffer';
71
73
  data: number[];
72
74
  };
73
- static poolSize: number;
75
+ inspect(): string;
74
76
  static isEncoding(encoding: string): boolean;
75
77
  swap16(): Buffer;
76
78
  swap32(): Buffer;
package/lib/Buffer.js CHANGED
@@ -58,6 +58,9 @@ class Buffer extends Uint8Array {
58
58
  if (Array.isArray(value)) {
59
59
  return new Buffer(new Uint8Array(value).buffer);
60
60
  }
61
+ if (typeof value === 'object' && value !== null && value.type === 'Buffer' && Array.isArray(value.data)) {
62
+ return new Buffer(value.data);
63
+ }
61
64
  throw new TypeError('Unsupported type for Buffer.from');
62
65
  }
63
66
  static alloc(size, fill, encoding) {
@@ -91,6 +94,13 @@ class Buffer extends Uint8Array {
91
94
  static compare(buf1, buf2) {
92
95
  return getNative().compare(buf1.buffer, buf1.byteOffset, buf1.byteLength, buf2.buffer, buf2.byteOffset, buf2.byteLength);
93
96
  }
97
+ equals(otherBuffer) {
98
+ if (!Buffer.isBuffer(otherBuffer))
99
+ throw new TypeError('Argument must be a Buffer');
100
+ if (this === otherBuffer)
101
+ return true;
102
+ return Buffer.compare(this, otherBuffer) === 0;
103
+ }
94
104
  static copyBytesFrom(view, offset, length) {
95
105
  if (offset === undefined)
96
106
  offset = 0;
@@ -544,6 +554,22 @@ class Buffer extends Uint8Array {
544
554
  data: Array.from(this)
545
555
  };
546
556
  }
557
+ inspect() {
558
+ let str = '';
559
+ const max = 50; // Default max bytes to inspect
560
+ const len = Math.min(this.length, max);
561
+ for (let i = 0; i < len; i++) {
562
+ if (i > 0)
563
+ str += ' ';
564
+ str += this[i].toString(16).padStart(2, '0');
565
+ }
566
+ if (this.length > max)
567
+ str += ' ... ' + (this.length - max) + ' more bytes';
568
+ return '<Buffer ' + str + '>';
569
+ }
570
+ [Symbol.for('nodejs.util.inspect.custom')]() {
571
+ return this.inspect();
572
+ }
547
573
  static isEncoding(encoding) {
548
574
  switch (encoding.toLowerCase()) {
549
575
  case 'utf8':
package/package.json CHANGED
@@ -1,55 +1,50 @@
1
1
  {
2
- "name": "react-native-nitro-buffer",
3
- "version": "0.0.1",
4
- "description": "Node.js compatible buffer module for React Native",
5
- "main": "lib/index.js",
6
- "module": "lib/index.js",
7
- "types": "lib/index.d.ts",
8
- "repository": {
9
- "type": "git",
10
- "url": "https://github.com/iwater/react-native-nitro-buffer.git"
11
- },
12
- "homepage": "https://github.com/iwater/react-native-nitro-buffer",
13
- "scripts": {
14
- "build": "tsc",
15
- "test": "jest",
16
- "prepublishOnly": "npm run build"
17
- },
18
- "keywords": [
19
- "react-native",
20
- "ios",
21
- "android",
22
- "buffer",
23
- "node-buffer",
24
- "nitro"
25
- ],
26
- "author": "iwater <iwater@gmail.com>",
27
- "license": "ISC",
28
- "peerDependencies": {
29
- "react": "*",
30
- "react-native": "*",
31
- "react-native-nitro-modules": "*"
32
- },
33
- "devDependencies": {
34
- "@types/jest": "^30.0.0",
35
- "@types/node": "^18.19.130",
36
- "@types/react": "*",
37
- "jest": "^29.0.0",
38
- "react-native-nitro-modules": "*",
39
- "ts-jest": "^29.0.0",
40
- "typescript": "^5.0.0"
41
- },
42
- "dependencies": {
43
- "react-native-nitro-modules": "*"
44
- },
45
- "packageManager": "yarn@4.12.0",
46
- "files": [
47
- "lib/",
48
- "src/",
49
- "cpp/",
50
- "ios/",
51
- "android/",
52
- "nitrogen/",
53
- "*.podspec"
54
- ]
55
- }
2
+ "name": "react-native-nitro-buffer",
3
+ "version": "0.0.3",
4
+ "description": "Node.js compatible buffer module for React Native",
5
+ "main": "lib/index.js",
6
+ "module": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "test": "jest",
11
+ "prepublishOnly": "npm run build"
12
+ },
13
+ "keywords": [
14
+ "react-native",
15
+ "ios",
16
+ "android",
17
+ "buffer",
18
+ "node-buffer",
19
+ "nitro"
20
+ ],
21
+ "author": "iwater <iwater@gmail.com>",
22
+ "license": "ISC",
23
+ "peerDependencies": {
24
+ "react": "*",
25
+ "react-native": "*",
26
+ "react-native-nitro-modules": "*"
27
+ },
28
+ "devDependencies": {
29
+ "@types/jest": "^30.0.0",
30
+ "@types/node": "^18.19.130",
31
+ "@types/react": "*",
32
+ "jest": "^29.0.0",
33
+ "react-native-nitro-modules": "*",
34
+ "ts-jest": "^29.0.0",
35
+ "typescript": "^5.0.0"
36
+ },
37
+ "dependencies": {
38
+ "react-native-nitro-modules": "*"
39
+ },
40
+ "packageManager": "yarn@4.12.0",
41
+ "files": [
42
+ "lib/",
43
+ "src/",
44
+ "cpp/",
45
+ "ios/",
46
+ "android/",
47
+ "nitrogen/",
48
+ "*.podspec"
49
+ ]
50
+ }
@@ -6,12 +6,12 @@ Pod::Spec.new do |s|
6
6
  s.name = "react-native-nitro-buffer"
7
7
  s.version = package["version"]
8
8
  s.summary = package["description"]
9
- s.homepage = "https://github.com/iwater/react-native-nitro-buffer"
9
+ s.homepage = "https://github.com/iwater/rn-http-server"
10
10
  s.license = package["license"]
11
11
  s.authors = package["author"]
12
12
 
13
13
  s.platform = :ios, "13.0"
14
- s.source = { :git => "https://github.com/iwater/react-native-nitro-buffer.git", :tag => "v#{s.version}" }
14
+ s.source = { :git => "https://github.com/iwater/rn-http-server.git", :tag => "v#{s.version}" }
15
15
 
16
16
  s.source_files = [
17
17
  "ios/**/*.{h,m,mm,swift}",
package/src/Buffer.ts CHANGED
@@ -12,6 +12,8 @@ function getNative(): NitroBuffer {
12
12
  }
13
13
 
14
14
  export class Buffer extends Uint8Array {
15
+ static poolSize = 8192
16
+
15
17
  constructor(length: number)
16
18
  constructor(array: Uint8Array)
17
19
  constructor(arrayBuffer: ArrayBuffer, byteOffset?: number, length?: number)
@@ -59,6 +61,9 @@ export class Buffer extends Uint8Array {
59
61
  if (Array.isArray(value)) {
60
62
  return new Buffer(new Uint8Array(value).buffer)
61
63
  }
64
+ if (typeof value === 'object' && value !== null && value.type === 'Buffer' && Array.isArray(value.data)) {
65
+ return new Buffer(value.data)
66
+ }
62
67
  throw new TypeError('Unsupported type for Buffer.from')
63
68
  }
64
69
 
@@ -100,6 +105,12 @@ export class Buffer extends Uint8Array {
100
105
  return getNative().compare(buf1.buffer as ArrayBuffer, buf1.byteOffset, buf1.byteLength, buf2.buffer as ArrayBuffer, buf2.byteOffset, buf2.byteLength)
101
106
  }
102
107
 
108
+ equals(otherBuffer: Uint8Array): boolean {
109
+ if (!Buffer.isBuffer(otherBuffer)) throw new TypeError('Argument must be a Buffer')
110
+ if (this === otherBuffer) return true
111
+ return Buffer.compare(this, otherBuffer) === 0
112
+ }
113
+
103
114
  static copyBytesFrom(view: ArrayBufferView, offset?: number, length?: number): Buffer {
104
115
  if (offset === undefined) offset = 0
105
116
  if (length === undefined) length = view.byteLength - offset
@@ -598,7 +609,21 @@ export class Buffer extends Uint8Array {
598
609
  }
599
610
  }
600
611
 
601
- static poolSize: number = 8192
612
+ inspect(): string {
613
+ let str = ''
614
+ const max = 50 // Default max bytes to inspect
615
+ const len = Math.min(this.length, max)
616
+ for (let i = 0; i < len; i++) {
617
+ if (i > 0) str += ' '
618
+ str += this[i].toString(16).padStart(2, '0')
619
+ }
620
+ if (this.length > max) str += ' ... ' + (this.length - max) + ' more bytes'
621
+ return '<Buffer ' + str + '>'
622
+ }
623
+
624
+ [Symbol.for('nodejs.util.inspect.custom')]() {
625
+ return this.inspect()
626
+ }
602
627
 
603
628
  static isEncoding(encoding: string): boolean {
604
629
  switch (encoding.toLowerCase()) {