Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 122 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,74 +1,149 @@
# NitroBase64
# react-native-nitro-base64 ⚡

A high-performance, cross-platform Base64 encoding/decoding module for React Native, powered by C++ and simdutf. Implements WHATWG forgiving-base64 and supports both standard and URL-safe variants.
The fastest Base64 library for React Nativepowered by [simdutf](https://github.com/simdutf/simdutf) SIMD C++ and [Nitro Modules](https://github.com/mrousavy/nitro).

| iPhone | Android |
| ------------------------------------------------- | --------------------------------------------------- |
Processes a 1.3MB image **26x faster** than `atob`/`btoa` and **1.8x faster** than `react-native-quick-base64`.

| iPhone | Android |
| --- | --- |
| ![iPhone](./docs/ios.png) | ![Android](./docs/android.png) |
## Features
- Fast C++ implementation using [simdutf](https://github.com/simdutf/simdutf)
- WHATWG forgiving-base64 compliance (removes whitespace, optional padding)
- Supports both standard and URL-safe base64
- Consistent API for iOS and Android
- Detailed error handling

## Installation
---

## 🤔 Why this library?

Most React Native base64 libraries hit the same bottlenecks:

- 🐢 **Pure JS** (`base64-js`) — slow. No native acceleration.
- ⚠️ **`atob`/`btoa`** — fast for tiny strings, falls apart on binary data at scale.
- ⚠️ **String-based native** — crosses the JSI bridge as UTF-16, doubling memory for binary payloads.

`react-native-nitro-base64` uses SIMD instructions (AVX-512 on x86, NEON on Apple Silicon) to process 64 bytes per CPU instruction, and exposes `ArrayBuffer`/`Uint8Array` APIs that bypass string marshaling entirely.

### 📊 Benchmark — 30 iterations, iPhone (Apple Silicon)

> Tested against `react-native-quick-base64@3.0.0`


#### Small image (7KB)

| Library | Time | vs. nitro-base64 |
|---|---|---|
| ⚡ **nitro-base64** `decodeBuffer` / `fromByteArray` | **0.06ms** | — |
| react-native-quick-base64 | 0.11ms | 1.8x slower |
| atob / btoa (Hermes) | 1.04ms | 17x slower |

#### Large image (1.3MB)

| Library | Time | vs. nitro-base64 |
|---|---|---|
| ⚡ **nitro-base64** `decodeBuffer` / `fromByteArray` | **7.5ms** | — |
| react-native-quick-base64 | 13.6ms | 1.8x slower |
| atob / btoa (Hermes) | 203ms | 26x slower |

---

## 📦 Installation

```sh
yarn add react-native-nitro-base64
yarn add react-native-nitro-modules@0.29.8
yarn add react-native-nitro-modules
```

### Linking
This module uses [react-native-nitro-modules](https://github.com/mrousavy/nitro). Follow Nitro's setup instructions for autolinking and native builds.
Then follow [Nitro's setup guide](https://nitro.margelo.com) for autolinking.

## API

```typescript
export interface NitroBase64 extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
install(): void;
encode(input: string, urlSafe: boolean): string;
decode(base64: string, urlSafe: boolean): string;
}
```
### 🔧 Setup

- `install(): void` — Initializes the native module for faster first-time encoding/decoding. Call this early in your app lifecycle via index.js or root
- `encode(input: string, urlSafe: boolean): string` — Encodes binary data to base64. Set `urlSafe` to `true` for base64url encoding.
- `decode(base64: string, urlSafe: boolean): string` — Decodes base64 (WHATWG forgiving) to binary. Set `urlSafe` to `true` for base64url decoding.
Call `install()` once before your app mounts:

## Usage
```javascript
index.js
```js
// index.js
import { install } from 'react-native-nitro-base64';
import { AppRegistry } from 'react-native';
import { install } from 'react-native-nitro-base64';
import { name as appName } from './app.json';
import App from './src/App';
install(); // <=== Call
import { name as appName } from './app.json';

install();
AppRegistry.registerComponent(appName, () => App);
```

---

## 📖 API

```typescript
import { encode,decode } from 'react-native-nitro-base64';
| Function | Input → Output | Notes |
|---|---|---|
| `encode(str, urlSafe?)` | string → base64 string | URL-safe optional |
| `decode(base64)` | base64 string → string | Auto-detects standard & URL-safe |
| `encodeBuffer(ArrayBuffer, urlSafe?)` | ArrayBuffer → base64 string | 🚀 Zero-copy, fastest for images |
| `decodeBuffer(base64)` | base64 string → ArrayBuffer | 🚀 Zero-copy, fastest for images |
| `fromByteArray(Uint8Array, urlSafe?)` | Uint8Array → base64 string | Drop-in for `react-native-quick-base64` |
| `toByteArray(base64)` | base64 string → Uint8Array | Drop-in for `react-native-quick-base64` |

const encoded = encode('Hello World!', false); // "SGVsbG8gV29ybGQh"
const urlEncoded = encode('Hello World!', true); // "SGVsbG8gV29ybGQh"
---

const decoded = decode(encoded, false); // "Hello World!"
const urlDecoded = decode(urlEncoded, true); // "Hello World!"
## 🚀 Usage

### 🖼️ Images & files — `encodeBuffer` / `decodeBuffer`

Images are binary data. Sending binary through a JS string crosses the JSI bridge as UTF-16, doubling memory. `ArrayBuffer` skips that entirely — raw bytes go straight to C++.

```ts
import { encodeBuffer, decodeBuffer } from 'react-native-nitro-base64';

// Upload image to API
const response = await fetch(imageUri);
const buffer = await response.arrayBuffer();
const base64 = encodeBuffer(buffer); // → "iVBORw0KGgo..."

// Decode API response for <Image> component
const buffer = decodeBuffer(base64String); // → ArrayBuffer
```

### 🔑 Text & JWT — `encode` / `decode`

```ts
import { encode, decode } from 'react-native-nitro-base64';

const encoded = encode('Hello World!'); // "SGVsbG8gV29ybGQh"
const decoded = decode(encoded); // "Hello World!"

// URL-safe for JWT / URL params
const token = encode(payload, true);
```

## Platform Support
- **iOS:** C++ implementation via simdutf
- **Android:** C++ implementation via simdutf
### 🔐 Web Crypto & hashing — `fromByteArray` / `toByteArray`

## Error Handling
Throws descriptive errors for invalid base64 input, remainder issues, or extra bits in padding.
Drop-in replacement for `react-native-quick-base64`. Same API, faster engine.

```ts
import { fromByteArray, toByteArray } from 'react-native-nitro-base64';

// SHA-256 hash → base64
const hash = new Uint8Array(await crypto.subtle.digest('SHA-256', data));
const encoded = fromByteArray(hash);

// base64 → text
const bytes = toByteArray(base64String);
const text = new TextDecoder().decode(bytes);
```

---

## 📱 Platform Support

| Platform | Engine |
|---|---|
| iOS | C++ (simdutf, NEON SIMD) |
| Android | C++ (simdutf, NEON / AVX2 SIMD) |

---

## 📄 License

## License
MIT

## Credits
- [simdutf](https://github.com/simdutf/simdutf)
- [Nitro Modules](https://github.com/mrousavy/nitro)
## 🙏 Credits

- [simdutf](https://github.com/simdutf/simdutf) — SIMD-accelerated Unicode & Base64
- [Nitro Modules](https://github.com/mrousavy/nitro) — JSI native module framework
44 changes: 42 additions & 2 deletions cpp/NitroBase64.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "HybridNitroBase64Spec.hpp"
#include "simdutf.h"
#include <NitroModules/ArrayBuffer.hpp>

namespace margelo::nitro::nitrobase64 {
using namespace margelo::nitro;
Expand All @@ -27,7 +28,7 @@ namespace margelo::nitro::nitrobase64 {
return output;
}

std::string decode(const std::string &base64,bool urlSafe = false) override {
std::string decode(const std::string &base64) override {
size_t max_decoded_size = simdutf::maximal_binary_length_from_base64(
base64.data(),
base64.size()
Expand All @@ -39,7 +40,7 @@ namespace margelo::nitro::nitrobase64 {
base64.data(),
base64.size(),
output.data(),
urlSafe ? simdutf::base64_url : simdutf::base64_default
simdutf::base64_default_or_url
);

if (result.error != simdutf::error_code::SUCCESS) {
Expand All @@ -50,6 +51,45 @@ namespace margelo::nitro::nitrobase64 {
return output;
}

std::string encodeBuffer(const std::shared_ptr<ArrayBuffer>& input, bool urlSafe = false) override {
auto opts = urlSafe ? simdutf::base64_url : simdutf::base64_default;
size_t base64_size = simdutf::base64_length_from_binary(input->size(), opts);
std::string output(base64_size, '\0');

size_t actual_size = simdutf::binary_to_base64(
reinterpret_cast<const char*>(input->data()),
input->size(),
output.data(),
opts
);

output.resize(actual_size);
return output;
}

std::shared_ptr<ArrayBuffer> decodeBuffer(const std::string &base64) override {
size_t max_decoded_size = simdutf::maximal_binary_length_from_base64(
base64.data(),
base64.size()
);

std::vector<uint8_t> buffer(max_decoded_size);

auto result = simdutf::base64_to_binary(
base64.data(),
base64.size(),
reinterpret_cast<char*>(buffer.data()),
simdutf::base64_default_or_url
);

if (result.error != simdutf::error_code::SUCCESS) {
throw std::runtime_error("Base64 decode failed");
}

buffer.resize(result.count);
return ArrayBuffer::move(std::move(buffer));
}

private:
static constexpr auto TAG = "NitroBase64";
};
Expand Down
Loading
Loading