react-native-nitro-buffer 0.0.3 → 0.0.11
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 +31 -11
- package/android/CMakeLists.txt +25 -0
- package/android/OnLoad.cpp +7 -0
- package/android/build.gradle +81 -0
- package/android/gradle.properties +1 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/margelo/nitro/buffer/NitroBufferPackage.kt +36 -0
- package/cpp/HybridNitroBuffer.cpp +105 -76
- package/cpp/HybridNitroBuffer.hpp +2 -2
- package/lib/utils.js +6 -0
- package/nitrogen/generated/android/NitroBufferOnLoad.cpp +3 -3
- package/nitrogen/generated/android/NitroBufferOnLoad.hpp +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/{nitro_buffer → buffer}/NitroBufferOnLoad.kt +1 -1
- package/nitrogen/generated/ios/NitroBuffer+autolinking.rb +1 -1
- package/nitrogen/generated/ios/NitroBuffer-Swift-Cxx-Bridge.cpp +2 -2
- package/nitrogen/generated/ios/NitroBuffer-Swift-Cxx-Bridge.hpp +2 -2
- package/nitrogen/generated/ios/NitroBufferAutolinking.mm +1 -1
- package/nitrogen/generated/ios/NitroBufferAutolinking.swift +1 -1
- package/nitrogen/generated/shared/c++/HybridNitroBufferSpec.cpp +2 -2
- package/nitrogen/generated/shared/c++/HybridNitroBufferSpec.hpp +2 -2
- package/package.json +6 -8
- package/src/utils.ts +6 -0
package/README.md
CHANGED
|
@@ -14,23 +14,43 @@ A high-performance, Node.js compatible `Buffer` implementation for React Native,
|
|
|
14
14
|
|
|
15
15
|
`react-native-nitro-buffer` is significantly faster than other Buffer implementations for React Native.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
### Device: iPad Air 5 (M1) - Physical Device
|
|
18
18
|
|
|
19
19
|
| Operation | Nitro Buffer | Competitor (Craftz) | Improvement |
|
|
20
20
|
|:---|:---:|:---:|:---:|
|
|
21
|
-
| `fill(0)` | **0.019ms** | 10.
|
|
22
|
-
| `write(utf8)` | **2.
|
|
23
|
-
| `toString(utf8)` | **0.
|
|
24
|
-
| `toString(base64)` | **0.
|
|
25
|
-
| `from(base64)` | **1.
|
|
26
|
-
| `toString(hex)` | **4.
|
|
27
|
-
| `from(hex)` | **11.
|
|
28
|
-
| `
|
|
21
|
+
| `fill(0)` | **0.019ms** | 10.37ms | **~545x 🚀** |
|
|
22
|
+
| `write(utf8)` | **2.47ms** | 212.04ms | **~85x 🚀** |
|
|
23
|
+
| `toString(utf8)` | **0.89ms** | 169.16ms | **~190x 🚀** |
|
|
24
|
+
| `toString(base64)` | **0.69ms** | 3.40ms | **~4.9x 🚀** |
|
|
25
|
+
| `from(base64)` | **1.40ms** | 146.56ms | **~104x 🚀** |
|
|
26
|
+
| `toString(hex)` | **4.85ms** | 57.34ms | **~11.8x 🚀** |
|
|
27
|
+
| `from(hex)` | **11.06ms** | 138.04ms | **~12.5x 🚀** |
|
|
28
|
+
| `btoa(1MB)` | **3.00ms** | 45.90ms | **~15.3x 🚀** |
|
|
29
|
+
| `atob(1MB)` | **5.12ms** | 149.73ms | **~29.2x 🚀** |
|
|
30
|
+
| `alloc(1MB)` | 0.33ms | 0.09ms | 0.27x |
|
|
31
|
+
|
|
32
|
+
### Device: iPhone 16 Pro Simulator (Mac mini M4)
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
| Operation | Nitro Buffer | Competitor (Craftz) | Improvement |
|
|
35
|
+
|:---|:---:|:---:|:---:|
|
|
36
|
+
| `fill(0)` | **0.015ms** | 13.78ms | **~918x 🚀** |
|
|
37
|
+
| `write(utf8)` | **4.27ms** | 163.46ms | **~38x 🚀** |
|
|
38
|
+
| `toString(utf8)` | **0.93ms** | 141.56ms | **~152x 🚀** |
|
|
39
|
+
| `toString(base64)` | **1.71ms** | 4.71ms | **~3x 🚀** |
|
|
40
|
+
| `from(base64)` | **16.45ms** | 104.67ms | **~6x 🚀** |
|
|
41
|
+
| `toString(hex)` | **4.89ms** | 43.46ms | **~9x 🚀** |
|
|
42
|
+
| `from(hex)` | **17.93ms** | 95.00ms | **~5x 🚀** |
|
|
43
|
+
| `btoa(1MB)` | **1.13ms** | 34.87ms | **~31x 🚀** |
|
|
44
|
+
| `atob(1MB)` | **2.18ms** | 91.41ms | **~42x 🚀** |
|
|
45
|
+
| `alloc(1MB)` | 0.18ms | 0.03ms | 0.16x |
|
|
46
|
+
|
|
47
|
+
*> Benchmarks averaged over 50 iterations on 1MB Buffer operations.*
|
|
31
48
|
|
|
32
49
|
> [!NOTE]
|
|
33
|
-
> **About `alloc` Performance**: The slight difference in allocation time (~0.3ms) is due to the overhead of initializing the ES6 Class structure (`Object.setPrototypeOf`), which provides a cleaner and safer type inheritance model compared to the functional mixin approach. This one-time initialization cost is negligible compared to the massive **
|
|
50
|
+
> **About `alloc` Performance**: The slight difference in allocation time (~0.3ms) is due to the overhead of initializing the ES6 Class structure (`Object.setPrototypeOf`), which provides a cleaner and safer type inheritance model compared to the functional mixin approach. This one-time initialization cost is negligible compared to the massive **5x - 550x** performance gains in actual Buffer operations.
|
|
51
|
+
|
|
52
|
+
> [!TIP]
|
|
53
|
+
> **`atob`/`btoa` Optimization**: In modern React Native environments (Hermes), `global.atob` and `global.btoa` are natively implemented and highly optimized. `react-native-nitro-buffer` automatically detects and uses these native implementations if available, ensuring your app runs at peak performance while maintaining Node.js utility compatibility.
|
|
34
54
|
|
|
35
55
|
## 📦 Installation
|
|
36
56
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.10)
|
|
2
|
+
project(NitroBuffer)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
5
|
+
|
|
6
|
+
# Add custom implementation and JNI adapter
|
|
7
|
+
add_library(NitroBuffer SHARED
|
|
8
|
+
OnLoad.cpp
|
|
9
|
+
../cpp/HybridNitroBuffer.cpp
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
# Include paths for our headers
|
|
13
|
+
include_directories(
|
|
14
|
+
${CMAKE_CURRENT_SOURCE_DIR}/../cpp
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Include the Nitrogen-generated CMake file (handles defines, sources, and linking)
|
|
18
|
+
include(${CMAKE_CURRENT_SOURCE_DIR}/../nitrogen/generated/android/NitroBuffer+autolinking.cmake)
|
|
19
|
+
|
|
20
|
+
# Link additional libraries
|
|
21
|
+
find_library(LOG_LIB log)
|
|
22
|
+
target_link_libraries(
|
|
23
|
+
NitroBuffer
|
|
24
|
+
${LOG_LIB}
|
|
25
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
apply plugin: "com.android.library"
|
|
2
|
+
apply plugin: "org.jetbrains.kotlin.android"
|
|
3
|
+
|
|
4
|
+
def reactNativeArchitectures() {
|
|
5
|
+
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
6
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
def getExtOrDefault(name, defaultValue) {
|
|
10
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : defaultValue
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
android {
|
|
14
|
+
namespace "com.margelo.nitro.buffer"
|
|
15
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion", 34)
|
|
16
|
+
|
|
17
|
+
defaultConfig {
|
|
18
|
+
minSdkVersion getExtOrDefault("minSdkVersion", 21)
|
|
19
|
+
targetSdkVersion getExtOrDefault("targetSdkVersion", 34)
|
|
20
|
+
|
|
21
|
+
externalNativeBuild {
|
|
22
|
+
cmake {
|
|
23
|
+
cppFlags "-fexceptions -frtti -std=c++20"
|
|
24
|
+
arguments "-DANDROID_STL=c++_shared"
|
|
25
|
+
abiFilters (*reactNativeArchitectures())
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
buildFeatures {
|
|
31
|
+
buildConfig true
|
|
32
|
+
prefab true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
externalNativeBuild {
|
|
36
|
+
cmake {
|
|
37
|
+
path "CMakeLists.txt"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
compileOptions {
|
|
42
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
43
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
kotlinOptions {
|
|
47
|
+
jvmTarget = "17"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
sourceSets {
|
|
51
|
+
main {
|
|
52
|
+
jniLibs.srcDirs += ['libs']
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
packagingOptions {
|
|
57
|
+
excludes = [
|
|
58
|
+
"META-INF",
|
|
59
|
+
"META-INF/**",
|
|
60
|
+
"**/libc++_shared.so",
|
|
61
|
+
"**/libfbjni.so",
|
|
62
|
+
"**/libjsi.so",
|
|
63
|
+
"**/libreactnative.so",
|
|
64
|
+
"**/libreact_nativemodule_core.so",
|
|
65
|
+
"**/libturbomodulejsijni.so",
|
|
66
|
+
"**/libhermes.so",
|
|
67
|
+
"**/libhermes_executor.so"
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
dependencies {
|
|
73
|
+
// Use project reference for Nitro Modules to ensure autolinking works correctly in this repo
|
|
74
|
+
implementation project(":react-native-nitro-modules")
|
|
75
|
+
|
|
76
|
+
// Use react-android instead of react-native for better compatibility with 0.71+
|
|
77
|
+
implementation "com.facebook.react:react-android"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Apply the Nitrogen-generated Gradle file
|
|
81
|
+
apply from: "../nitrogen/generated/android/NitroBuffer+autolinking.gradle"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
NitroBuffer_cmakeVersion=3.22.1
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package com.margelo.nitro.buffer
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.TurboReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
8
|
+
import java.util.HashMap
|
|
9
|
+
|
|
10
|
+
class NitroBufferPackage : TurboReactPackage() {
|
|
11
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
16
|
+
return ReactModuleInfoProvider {
|
|
17
|
+
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
|
|
18
|
+
moduleInfos["NitroBuffer"] = ReactModuleInfo(
|
|
19
|
+
"NitroBuffer",
|
|
20
|
+
"NitroBuffer",
|
|
21
|
+
false, // canOverrideExistingModule
|
|
22
|
+
false, // needsEagerInit
|
|
23
|
+
true, // hasConstants
|
|
24
|
+
false, // isCxxModule
|
|
25
|
+
true // isTurboModule
|
|
26
|
+
)
|
|
27
|
+
moduleInfos
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
companion object {
|
|
32
|
+
init {
|
|
33
|
+
System.loadLibrary("NitroBuffer")
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
#include <iostream>
|
|
6
6
|
#include <vector>
|
|
7
7
|
|
|
8
|
-
namespace margelo::nitro::
|
|
8
|
+
namespace margelo::nitro::buffer {
|
|
9
9
|
|
|
10
10
|
static const char base64_chars[] =
|
|
11
11
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
@@ -220,11 +220,74 @@ double HybridNitroBuffer::write(const std::shared_ptr<ArrayBuffer> &buffer,
|
|
|
220
220
|
// UTF-8 replacement character (U+FFFD) encoded as UTF-8
|
|
221
221
|
static const char UTF8_REPLACEMENT[] = "\xEF\xBF\xBD";
|
|
222
222
|
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
static
|
|
223
|
+
// Fast validation: returns true if all data is valid UTF-8, false otherwise
|
|
224
|
+
// This allows us to use memcpy for the common case of valid UTF-8
|
|
225
|
+
static bool isValidUtf8(const uint8_t *data, size_t len) {
|
|
226
|
+
size_t i = 0;
|
|
227
|
+
while (i < len) {
|
|
228
|
+
uint8_t byte1 = data[i];
|
|
229
|
+
|
|
230
|
+
// ASCII (0x00-0x7F) - most common case
|
|
231
|
+
if (byte1 <= 0x7F) {
|
|
232
|
+
i++;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Invalid leading byte
|
|
237
|
+
if (byte1 < 0xC2 || byte1 > 0xF4) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 2-byte sequence (0xC2-0xDF)
|
|
242
|
+
if (byte1 <= 0xDF) {
|
|
243
|
+
if (i + 1 >= len || (data[i + 1] & 0xC0) != 0x80) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
i += 2;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 3-byte sequence (0xE0-0xEF)
|
|
251
|
+
if (byte1 <= 0xEF) {
|
|
252
|
+
if (i + 2 >= len)
|
|
253
|
+
return false;
|
|
254
|
+
uint8_t byte2 = data[i + 1];
|
|
255
|
+
uint8_t byte3 = data[i + 2];
|
|
256
|
+
if ((byte2 & 0xC0) != 0x80 || (byte3 & 0xC0) != 0x80)
|
|
257
|
+
return false;
|
|
258
|
+
// Check overlong and surrogate
|
|
259
|
+
if (byte1 == 0xE0 && byte2 < 0xA0)
|
|
260
|
+
return false;
|
|
261
|
+
if (byte1 == 0xED && byte2 >= 0xA0)
|
|
262
|
+
return false;
|
|
263
|
+
i += 3;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 4-byte sequence (0xF0-0xF4)
|
|
268
|
+
if (i + 3 >= len)
|
|
269
|
+
return false;
|
|
270
|
+
uint8_t byte2 = data[i + 1];
|
|
271
|
+
uint8_t byte3 = data[i + 2];
|
|
272
|
+
uint8_t byte4 = data[i + 3];
|
|
273
|
+
if ((byte2 & 0xC0) != 0x80 || (byte3 & 0xC0) != 0x80 ||
|
|
274
|
+
(byte4 & 0xC0) != 0x80)
|
|
275
|
+
return false;
|
|
276
|
+
if (byte1 == 0xF0 && byte2 < 0x90)
|
|
277
|
+
return false;
|
|
278
|
+
if (byte1 == 0xF4 && byte2 > 0x8F)
|
|
279
|
+
return false;
|
|
280
|
+
i += 4;
|
|
281
|
+
}
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Slow path: Decode UTF-8 with replacement for invalid bytes
|
|
286
|
+
// Only called when isValidUtf8 returns false
|
|
287
|
+
static std::string decodeUtf8WithReplacementSlow(const uint8_t *data,
|
|
288
|
+
size_t len) {
|
|
226
289
|
std::string result;
|
|
227
|
-
result.reserve(len); //
|
|
290
|
+
result.reserve(len + len / 10); // Add 10% for potential replacements
|
|
228
291
|
|
|
229
292
|
size_t i = 0;
|
|
230
293
|
while (i < len) {
|
|
@@ -245,27 +308,20 @@ static std::string decodeUtf8WithReplacement(const uint8_t *data, size_t len) {
|
|
|
245
308
|
}
|
|
246
309
|
|
|
247
310
|
// 2-byte sequence (0xC2-0xDF)
|
|
248
|
-
if (byte1
|
|
249
|
-
if (i + 1 >= len) {
|
|
311
|
+
if (byte1 <= 0xDF) {
|
|
312
|
+
if (i + 1 >= len || (data[i + 1] & 0xC0) != 0x80) {
|
|
250
313
|
result.append(UTF8_REPLACEMENT);
|
|
251
314
|
i++;
|
|
252
315
|
continue;
|
|
253
316
|
}
|
|
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
317
|
result.push_back(static_cast<char>(byte1));
|
|
262
|
-
result.push_back(static_cast<char>(
|
|
318
|
+
result.push_back(static_cast<char>(data[i + 1]));
|
|
263
319
|
i += 2;
|
|
264
320
|
continue;
|
|
265
321
|
}
|
|
266
322
|
|
|
267
323
|
// 3-byte sequence (0xE0-0xEF)
|
|
268
|
-
if (byte1
|
|
324
|
+
if (byte1 <= 0xEF) {
|
|
269
325
|
if (i + 2 >= len) {
|
|
270
326
|
result.append(UTF8_REPLACEMENT);
|
|
271
327
|
i++;
|
|
@@ -273,28 +329,12 @@ static std::string decodeUtf8WithReplacement(const uint8_t *data, size_t len) {
|
|
|
273
329
|
}
|
|
274
330
|
uint8_t byte2 = data[i + 1];
|
|
275
331
|
uint8_t byte3 = data[i + 2];
|
|
276
|
-
|
|
277
|
-
|
|
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
|
|
332
|
+
if ((byte2 & 0xC0) != 0x80 || (byte3 & 0xC0) != 0x80 ||
|
|
333
|
+
(byte1 == 0xE0 && byte2 < 0xA0) || (byte1 == 0xED && byte2 >= 0xA0)) {
|
|
292
334
|
result.append(UTF8_REPLACEMENT);
|
|
293
335
|
i++;
|
|
294
336
|
continue;
|
|
295
337
|
}
|
|
296
|
-
|
|
297
|
-
// Valid 3-byte sequence
|
|
298
338
|
result.push_back(static_cast<char>(byte1));
|
|
299
339
|
result.push_back(static_cast<char>(byte2));
|
|
300
340
|
result.push_back(static_cast<char>(byte3));
|
|
@@ -303,54 +343,43 @@ static std::string decodeUtf8WithReplacement(const uint8_t *data, size_t len) {
|
|
|
303
343
|
}
|
|
304
344
|
|
|
305
345
|
// 4-byte sequence (0xF0-0xF4)
|
|
306
|
-
if (
|
|
307
|
-
|
|
308
|
-
|
|
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;
|
|
346
|
+
if (i + 3 >= len) {
|
|
347
|
+
result.append(UTF8_REPLACEMENT);
|
|
348
|
+
i++;
|
|
343
349
|
continue;
|
|
344
350
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
351
|
+
uint8_t byte2 = data[i + 1];
|
|
352
|
+
uint8_t byte3 = data[i + 2];
|
|
353
|
+
uint8_t byte4 = data[i + 3];
|
|
354
|
+
if ((byte2 & 0xC0) != 0x80 || (byte3 & 0xC0) != 0x80 ||
|
|
355
|
+
(byte4 & 0xC0) != 0x80 || (byte1 == 0xF0 && byte2 < 0x90) ||
|
|
356
|
+
(byte1 == 0xF4 && byte2 > 0x8F)) {
|
|
357
|
+
result.append(UTF8_REPLACEMENT);
|
|
358
|
+
i++;
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
result.push_back(static_cast<char>(byte1));
|
|
362
|
+
result.push_back(static_cast<char>(byte2));
|
|
363
|
+
result.push_back(static_cast<char>(byte3));
|
|
364
|
+
result.push_back(static_cast<char>(byte4));
|
|
365
|
+
i += 4;
|
|
349
366
|
}
|
|
350
367
|
|
|
351
368
|
return result;
|
|
352
369
|
}
|
|
353
370
|
|
|
371
|
+
// Decode UTF-8 with WHATWG-compliant error handling
|
|
372
|
+
// Uses fast path (memcpy) for valid UTF-8, slow path with replacement for
|
|
373
|
+
// invalid
|
|
374
|
+
static std::string decodeUtf8WithReplacement(const uint8_t *data, size_t len) {
|
|
375
|
+
// Fast path: if data is valid UTF-8, just copy it directly
|
|
376
|
+
if (isValidUtf8(data, len)) {
|
|
377
|
+
return std::string(reinterpret_cast<const char *>(data), len);
|
|
378
|
+
}
|
|
379
|
+
// Slow path: need to replace invalid sequences
|
|
380
|
+
return decodeUtf8WithReplacementSlow(data, len);
|
|
381
|
+
}
|
|
382
|
+
|
|
354
383
|
// Decode as latin1/binary - each byte maps directly to Unicode code point
|
|
355
384
|
// 0x00-0xFF
|
|
356
385
|
static std::string decodeLatin1(const uint8_t *data, size_t len) {
|
|
@@ -673,4 +702,4 @@ void HybridNitroBuffer::fill(const std::shared_ptr<ArrayBuffer> &buffer,
|
|
|
673
702
|
memset(data + start, (int)value, actualFill);
|
|
674
703
|
}
|
|
675
704
|
|
|
676
|
-
} // namespace margelo::nitro::
|
|
705
|
+
} // namespace margelo::nitro::buffer
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
#include "HybridNitroBufferSpec.hpp"
|
|
3
3
|
#include <NitroModules/ArrayBuffer.hpp>
|
|
4
4
|
|
|
5
|
-
namespace margelo::nitro::
|
|
5
|
+
namespace margelo::nitro::buffer {
|
|
6
6
|
|
|
7
7
|
class HybridNitroBuffer : public HybridNitroBufferSpec {
|
|
8
8
|
public:
|
|
@@ -41,4 +41,4 @@ public:
|
|
|
41
41
|
double length) override;
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
} // namespace margelo::nitro::
|
|
44
|
+
} // namespace margelo::nitro::buffer
|
package/lib/utils.js
CHANGED
|
@@ -8,9 +8,15 @@ exports.transcode = transcode;
|
|
|
8
8
|
exports.resolveObjectURL = resolveObjectURL;
|
|
9
9
|
const Buffer_1 = require("./Buffer");
|
|
10
10
|
function atob(data) {
|
|
11
|
+
if (typeof global.atob === 'function') {
|
|
12
|
+
return global.atob(data);
|
|
13
|
+
}
|
|
11
14
|
return Buffer_1.Buffer.from(data, 'base64').toString('binary');
|
|
12
15
|
}
|
|
13
16
|
function btoa(data) {
|
|
17
|
+
if (typeof global.btoa === 'function') {
|
|
18
|
+
return global.btoa(data);
|
|
19
|
+
}
|
|
14
20
|
return Buffer_1.Buffer.from(data, 'binary').toString('base64');
|
|
15
21
|
}
|
|
16
22
|
function isAscii(input) {
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
|
|
18
18
|
#include "HybridNitroBuffer.hpp"
|
|
19
19
|
|
|
20
|
-
namespace margelo::nitro::
|
|
20
|
+
namespace margelo::nitro::buffer {
|
|
21
21
|
|
|
22
22
|
int initialize(JavaVM* vm) {
|
|
23
23
|
using namespace margelo::nitro;
|
|
24
|
-
using namespace margelo::nitro::
|
|
24
|
+
using namespace margelo::nitro::buffer;
|
|
25
25
|
using namespace facebook;
|
|
26
26
|
|
|
27
27
|
return facebook::jni::initialize(vm, [] {
|
|
@@ -41,4 +41,4 @@ int initialize(JavaVM* vm) {
|
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
} // namespace margelo::nitro::
|
|
44
|
+
} // namespace margelo::nitro::buffer
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
#include <jni.h>
|
|
9
9
|
#include <NitroModules/NitroDefines.hpp>
|
|
10
10
|
|
|
11
|
-
namespace margelo::nitro::
|
|
11
|
+
namespace margelo::nitro::buffer {
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Initializes the native (C++) part of NitroBuffer, and autolinks all Hybrid Objects.
|
|
@@ -16,10 +16,10 @@ namespace margelo::nitro::nitro_buffer {
|
|
|
16
16
|
* Example:
|
|
17
17
|
* ```cpp (cpp-adapter.cpp)
|
|
18
18
|
* JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
|
|
19
|
-
* return margelo::nitro::
|
|
19
|
+
* return margelo::nitro::buffer::initialize(vm);
|
|
20
20
|
* }
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
int initialize(JavaVM* vm);
|
|
24
24
|
|
|
25
|
-
} // namespace margelo::nitro::
|
|
25
|
+
} // namespace margelo::nitro::buffer
|
|
@@ -52,7 +52,7 @@ def add_nitrogen_files(spec)
|
|
|
52
52
|
spec.pod_target_xcconfig = current_pod_target_xcconfig.merge({
|
|
53
53
|
# Use C++ 20
|
|
54
54
|
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
|
|
55
|
-
# Enables C++ <-> Swift interop (by default it's only
|
|
55
|
+
# Enables C++ <-> Swift interop (by default it's only ObjC)
|
|
56
56
|
"SWIFT_OBJC_INTEROP_MODE" => "objcxx",
|
|
57
57
|
# Enables stricter modular headers
|
|
58
58
|
"DEFINES_MODULE" => "YES",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
// Include C++ implementation defined types
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
namespace margelo::nitro::
|
|
13
|
+
namespace margelo::nitro::buffer::bridge::swift {
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
} // namespace margelo::nitro::
|
|
17
|
+
} // namespace margelo::nitro::buffer::bridge::swift
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
* Contains specialized versions of C++ templated types so they can be accessed from Swift,
|
|
21
21
|
* as well as helper functions to interact with those C++ types from Swift.
|
|
22
22
|
*/
|
|
23
|
-
namespace margelo::nitro::
|
|
23
|
+
namespace margelo::nitro::buffer::bridge::swift {
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
} // namespace margelo::nitro::
|
|
27
|
+
} // namespace margelo::nitro::buffer::bridge::swift
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
#include "HybridNitroBufferSpec.hpp"
|
|
9
9
|
|
|
10
|
-
namespace margelo::nitro::
|
|
10
|
+
namespace margelo::nitro::buffer {
|
|
11
11
|
|
|
12
12
|
void HybridNitroBufferSpec::loadHybridMethods() {
|
|
13
13
|
// load base methods/properties
|
|
@@ -29,4 +29,4 @@ namespace margelo::nitro::nitro_buffer {
|
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
} // namespace margelo::nitro::
|
|
32
|
+
} // namespace margelo::nitro::buffer
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
#include <NitroModules/ArrayBuffer.hpp>
|
|
19
19
|
#include <string>
|
|
20
20
|
|
|
21
|
-
namespace margelo::nitro::
|
|
21
|
+
namespace margelo::nitro::buffer {
|
|
22
22
|
|
|
23
23
|
using namespace margelo::nitro;
|
|
24
24
|
|
|
@@ -71,4 +71,4 @@ namespace margelo::nitro::nitro_buffer {
|
|
|
71
71
|
static constexpr auto TAG = "NitroBuffer";
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
} // namespace margelo::nitro::
|
|
74
|
+
} // namespace margelo::nitro::buffer
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-nitro-buffer",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Node.js
|
|
3
|
+
"version": "0.0.11",
|
|
4
|
+
"description": "The fastest, 100% Node.js-compatible Buffer implementation for React Native, powered by Nitro Modules and C++.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
8
|
+
"react-native": "src/index.ts",
|
|
8
9
|
"scripts": {
|
|
9
|
-
"build": "tsc",
|
|
10
|
+
"build": "npx nitrogen@0.32.0 && tsc",
|
|
10
11
|
"test": "jest",
|
|
11
12
|
"prepublishOnly": "npm run build"
|
|
12
13
|
},
|
|
@@ -23,20 +24,17 @@
|
|
|
23
24
|
"peerDependencies": {
|
|
24
25
|
"react": "*",
|
|
25
26
|
"react-native": "*",
|
|
26
|
-
"react-native-nitro-modules": "
|
|
27
|
+
"react-native-nitro-modules": "^0.32.0"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@types/jest": "^30.0.0",
|
|
30
31
|
"@types/node": "^18.19.130",
|
|
31
32
|
"@types/react": "*",
|
|
32
33
|
"jest": "^29.0.0",
|
|
33
|
-
"react-native-nitro-modules": "
|
|
34
|
+
"react-native-nitro-modules": "^0.32.0",
|
|
34
35
|
"ts-jest": "^29.0.0",
|
|
35
36
|
"typescript": "^5.0.0"
|
|
36
37
|
},
|
|
37
|
-
"dependencies": {
|
|
38
|
-
"react-native-nitro-modules": "*"
|
|
39
|
-
},
|
|
40
38
|
"packageManager": "yarn@4.12.0",
|
|
41
39
|
"files": [
|
|
42
40
|
"lib/",
|
package/src/utils.ts
CHANGED
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
import { Buffer } from './Buffer'
|
|
3
3
|
|
|
4
4
|
export function atob(data: string): string {
|
|
5
|
+
if (typeof global.atob === 'function') {
|
|
6
|
+
return global.atob(data)
|
|
7
|
+
}
|
|
5
8
|
return Buffer.from(data, 'base64').toString('binary')
|
|
6
9
|
}
|
|
7
10
|
|
|
8
11
|
export function btoa(data: string): string {
|
|
12
|
+
if (typeof global.btoa === 'function') {
|
|
13
|
+
return global.btoa(data)
|
|
14
|
+
}
|
|
9
15
|
return Buffer.from(data, 'binary').toString('base64')
|
|
10
16
|
}
|
|
11
17
|
|