A high-performance React Native library for Nordic Semiconductor DFU (Device Firmware Update) built on Nitro Modules.
Originally developed for Zyke Band — a fitness and health tracker created by a small team.
- High Performance: Built on Nitro Modules with JSI for zero-overhead native communication
- Promise-Based API: Clean async/await interface with progress callbacks
- Full DFU Support: Legacy DFU, Secure DFU (nRF5 SDK), and buttonless DFU
- Pause / Resume / Abort: Full control over firmware updates in progress
- Type-Safe: Comprehensive TypeScript definitions for all options, states, and errors
- Expo Ready: Works with Expo (prebuild required)
- New Architecture: Full support for React Native's new architecture
- Zero Bridge: Direct JSI communication eliminates bridge bottlenecks
This library handles firmware updates only and is intended to be used together with react-native-ble-nitro for BLE device scanning, connection, and communication. Use react-native-ble-nitro to discover and connect to your device, then use react-native-dfu-nitro to perform the firmware update.
npm install react-native-nitro-modules react-native-ble-nitro react-native-dfu-nitronpx pod-installNo additional setup required — the library auto-links.
Prebuild and run:
npx expo prebuild
npx expo run:ios
# or
npx expo run:androidimport { DfuNitro, DfuState } from 'react-native-dfu-nitro';
const dfu = DfuNitro.instance();
try {
await dfu.startDfu({
deviceId: 'AA:BB:CC:DD:EE:FF',
filePath: 'file:///path/to/firmware.zip',
onStateChange: (state) => {
console.log('State:', DfuState[state]);
},
onProgress: ({ progress, currentSpeed, part, totalParts }) => {
console.log(`Part ${part}/${totalParts}: ${progress}% (${currentSpeed} kB/s)`);
},
});
console.log('DFU completed successfully!');
} catch (error) {
console.error('DFU failed:', error);
}import { DfuNitroManager } from 'react-native-dfu-nitro';
const dfu = new DfuNitroManager();
await dfu.startDfu({
deviceId: 'AA:BB:CC:DD:EE:FF',
filePath: 'file:///path/to/firmware.zip',
});const dfu = DfuNitro.instance();
// Start DFU (non-blocking — store the promise)
const dfuPromise = dfu.startDfu({
deviceId: 'AA:BB:CC:DD:EE:FF',
filePath: 'file:///path/to/firmware.zip',
onProgress: ({ progress }) => console.log(`${progress}%`),
});
// Pause
dfu.pause();
// Resume
dfu.resume();
// Abort — the promise will reject with a DfuException
dfu.abort();import { DfuNitro, DfuException, DfuError } from 'react-native-dfu-nitro';
const dfu = DfuNitro.instance();
try {
await dfu.startDfu({
deviceId: 'AA:BB:CC:DD:EE:FF',
filePath: 'file:///path/to/firmware.zip',
});
} catch (error) {
if (error instanceof DfuException) {
switch (error.code) {
case DfuError.DeviceDisconnected:
console.error('Device disconnected during DFU');
break;
case DfuError.FileInvalid:
console.error('Firmware file is invalid');
break;
case DfuError.CrcError:
console.error('CRC mismatch — retry the update');
break;
default:
console.error(`DFU error [${error.code}]: ${error.message}`);
}
}
}await dfu.startDfu({
deviceId: 'AA:BB:CC:DD:EE:FF',
filePath: 'file:///path/to/firmware.zip',
// Force legacy DFU mode (for older bootloaders)
forceDfu: true,
// Packet receipt notification (0 = disabled, default varies by platform)
packetReceiptNotificationParameter: 12,
// Number of retries on transient failures
numberOfRetries: 3,
// Enable buttonless DFU for devices without a physical button
enableUnsafeExperimentalButtonlessDfu: true,
// Scan for new address after DFU in legacy mode
forceScanningForNewAddressInLegacyDfu: true,
// Disable resume (start from scratch if interrupted)
disableResume: true,
onStateChange: (state) => { /* ... */ },
onProgress: (progress) => { /* ... */ },
});Singleton wrapper around DfuNitroManager.
import { DfuNitro } from 'react-native-dfu-nitro';
const dfu = DfuNitro.instance();The main class for performing DFU operations.
| Method | Returns | Description |
|---|---|---|
startDfu(options) |
Promise<void> |
Start a DFU process. Resolves on completion, rejects on error/abort. |
pause() |
void |
Pause the current DFU process. |
resume() |
void |
Resume a paused DFU process. |
abort() |
void |
Abort the current DFU process. The startDfu promise will reject. |
Extends DfuOptions with callback handlers.
| Property | Type | Description |
|---|---|---|
onStateChange |
(state: DfuState) => void |
Called when the DFU state changes. |
onProgress |
(progress: DfuProgress) => void |
Called on upload progress updates. |
| Property | Type | Required | Description |
|---|---|---|---|
deviceId |
string |
Yes | Bluetooth device address (MAC on Android, UUID on iOS). |
filePath |
string |
Yes | URI to the firmware zip file. |
firmwareType |
DfuFirmwareType |
No | Type of firmware being flashed. |
packetReceiptNotificationParameter |
number |
No | PRN value. 0 to disable. |
forceDfu |
boolean |
No | Force legacy DFU mode. |
enableUnsafeExperimentalButtonlessDfu |
boolean |
No | Enable experimental buttonless DFU. |
forceScanningForNewAddressInLegacyDfu |
boolean |
No | Scan for new address after legacy DFU. |
numberOfRetries |
number |
No | Number of retries on failure. |
connectionTimeout |
number |
No | Connection timeout in milliseconds (iOS only). |
dataObjectPreparationDelay |
number |
No | Delay between data objects in ms (iOS only). |
alternativeAdvertisingNameEnabled |
boolean |
No | Use alternative advertising name (iOS only). |
disableResume |
boolean |
No | Disable resume — always restart from scratch. |
| Property | Type | Description |
|---|---|---|
part |
number |
Current part number (1-based). |
totalParts |
number |
Total number of parts. |
progress |
number |
Upload progress percentage (0–100). |
currentSpeed |
number |
Current transfer speed (kB/s). |
avgSpeed |
number |
Average transfer speed (kB/s). |
enum DfuState {
Connecting,
Starting,
EnablingDfuMode,
Uploading,
Validating,
Disconnecting,
Completed,
Aborted,
}enum DfuFirmwareType {
Application,
Bootloader,
SoftDevice,
SoftDeviceBootloader,
SoftDeviceBootloaderApplication,
}Thrown when a DFU process fails. Extends Error.
| Property | Type | Description |
|---|---|---|
code |
DfuError |
Machine-readable error code. |
message |
string |
Human-readable error description. |
Unified error codes covering both iOS and Android platforms. See src/specs/NativeDfuNitro.nitro.ts for the full list.
Categories:
| Range | Category | Examples |
|---|---|---|
| 2–6 | Remote Legacy DFU | RemoteLegacyDfuCrcError, RemoteLegacyDfuOperationFailed |
| 12–21 | Remote Secure DFU | RemoteSecureDfuSignatureMismatch, RemoteSecureDfuExtendedError |
| 22–33 | Remote Extended | RemoteExtendedErrorFwVersionFailure, RemoteExtendedErrorInsufficientSpace |
| 92–97 | Remote Buttonless | RemoteButtonlessDfuBusy, RemoteButtonlessDfuNotBonded |
| 101–107 | File / Firmware | FileNotSpecified, FileInvalid, CrcError |
| 201–204 | Connection | FailedToConnect, DeviceDisconnected, BluetoothDisabled |
| 301–309 | Service / Protocol | ServiceDiscoveryFailed, DeviceNotSupported, BytesLost |
| 500 | Internal | InvalidInternalState |
This library expects a DFU zip package created with nRF Util or nrfutil.
nrfutil pkg generate \
--hw-version 52 \
--sd-req 0x00 \
--application firmware.hex \
--application-version 1 \
firmware.zippip install nrfutil
nrfutil pkg generate \
--hw-version 52 \
--sd-req 0x00 \
--application firmware.hex \
--application-version 1 \
firmware.zipThe resulting firmware.zip can be bundled with your app or downloaded at runtime.
- Uses NordicDFU (CocoaPods,
~> 4.16) deviceIdis the Core Bluetooth peripheral UUID (not a MAC address)connectionTimeout,dataObjectPreparationDelay, andalternativeAdvertisingNameEnabledare iOS-only options- Requires iOS 16.0+
- Uses Android DFU Library (
2.10.1— pinned because2.11.0requires Kotlin 2.3, which is incompatible with React Native's Kotlin 2.1 compiler) deviceIdis the Bluetooth MAC address- DFU runs in a foreground service (
DfuService) for reliability - Requires
FOREGROUND_SERVICEandFOREGROUND_SERVICE_CONNECTED_DEVICEpermissions (added automatically) - Requires
BLUETOOTH_CONNECTpermission at runtime (API 31+)
iOS — Add to your Info.plist:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to update device firmware.</string>Android — Add to your AndroidManifest.xml:
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />| Problem | Solution |
|---|---|
DfuError.FileNotSpecified |
Ensure filePath is a valid file URI (file:///...). |
DfuError.FileInvalid |
The zip is not a valid DFU package. Re-generate with nRF Util. |
DfuError.DeviceDisconnected |
Device went out of range or was turned off during DFU. Move closer and retry. |
DfuError.CrcError |
Data corruption during transfer. Retry the DFU. |
DfuError.BluetoothDisabled |
Bluetooth is turned off. Prompt the user to enable it. |
| Android: DFU stalls at 0% | Ensure BLUETOOTH_CONNECT permission is granted at runtime. |
| iOS: Device not found | Verify you're using the Core Bluetooth UUID, not a MAC address. |
Built on Nitro Modules for direct JSI communication, type-safe native bindings, and high performance.
- iOS: Swift implementation using NordicDFU delegates
- Android: Kotlin implementation using Nordic DFU foreground service
- TypeScript: Promise-based manager layer wrapping native callbacks
react-native-dfu-nitro/
├── src/
│ ├── specs/ # Nitro HybridObject specs & types
│ ├── manager.ts # DfuNitroManager (Promise-based API)
│ ├── singleton.ts # DfuNitro (singleton wrapper)
│ └── index.ts # Public exports
├── ios/ # Swift implementation
├── android/ # Kotlin implementation + DfuService
├── nitrogen/generated/ # Auto-generated Nitro bindings
└── nitro.json # Nitro Modules configuration
Contributions are welcome! Fork the repo, make your changes, and submit a pull request.
git clone https://github.com/zykeco/react-native-dfu-nitro.git
cd react-native-dfu-nitro
npm install
npm run specs # Regenerate Nitro bindings
npm run typecheckMIT License — see LICENSE file.
- Zyke Band — The fitness tracker project that inspired this library
- Marc Rousavy — Creator of Nitro Modules
- Nitro Modules — High-performance native module framework
- Nordic Semiconductor — DFU protocol and native libraries
- Bug Reports: GitHub Issues
- Questions: GitHub Discussions