
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
react-native-nitro-update
Advanced tools
OTA updates for React Native via Nitro/JSI: check, download, reload, rollback, checksum, retry, manifest
OTA (over-the-air) updates for React Native using Nitro and JSI. Check for updates, download bundles, reload, rollback, and manage lifecycle with optional checksum verification, retries, and manifest-based version checks.
npm install react-native-nitro-update
# or
yarn add react-native-nitro-update
# or
bun add react-native-nitro-update
Peer dependencies: react, react-native, react-native-nitro-modules (install this explicitly in your app). The repo supports Bun (bun install, bun run <script>); root package.json sets packageManager for Bun.
Quick setup (interactive wizard) — after installing:
npx react-native-nitro-update setup
# or, if you added the script: npm run setupOTA
The wizard asks how you want to host OTA (GitHub branch, S3, Firebase, API, etc.), then generates config files and a code snippet for your app. It also adds an ota:build script so you can build the OTA zip with one command.
Using this in another project? See INTEGRATION.md for step-by-step: install, iOS/Android native wiring, JS auto-update snippet, hosting (e.g. GitHub Releases), and building the OTA zip.
The package ships a single CLI with three commands. Run from your project root (where package.json and ios/ or android/ live):
| Command | Description |
|---|---|
npx react-native-nitro-update build | Build OTA bundle zip (runs react-native bundle + zip). Version is auto-detected from your native project. |
npx react-native-nitro-update doctor | Diagnose setup: Podfile, iOS sources, MainApplication / jsBundleFilePath, Swift settings, Metro. Use --json for CI. |
npx react-native-nitro-update setup | Interactive wizard: hosting choice, config generation, OTA_BOOTSTRAP.md, native/JS snippets. |
npx react-native-nitro-update bootstrap | Write OTA_BOOTSTRAP.md only (checklist + snippets; reads ota-config.js baseUrl if present). |
Use the package-hosted build command so you get improvements with every package update — no generated scripts to maintain:
# Auto-generates OTA version from native version + build number + UTC stamp
npx react-native-nitro-update build --platform ios
# Explicit version and platform
npx react-native-nitro-update build --platform android --version 1.0.2
# Both platforms, custom output directory
npx react-native-nitro-update build --platform both --output ./my-ota
Options: --platform ios|android|both (default: ios), --version <string> (explicit override), --entry <file> (default: index.js), --output <dir> (default: ./ota-output), --dev. Run npx react-native-nitro-update build --help for details.
By default, version.txt is generated as <nativeVersion>+ota.<build>.<UTCSTAMP> (example: 1.1.100+ota.100.202603191230) to avoid collisions with future App Store/Play Store versions.
When this +ota format is used, native checkForUpdate only accepts OTA versions whose <nativeVersion> matches the currently running app version.
After running, upload ota-output/version.txt and ota-output/bundle.zip to your CDN, GitHub Release, or S3. You can add a script to package.json:
"ota:build": "npx react-native-nitro-update build --platform ios"
Then run npm run ota:build whenever you want to ship an OTA.
Install the package and peer dependency (see above).
Host two URLs somewhere (your server, CDN, S3, or GitHub):
1.0.0) or a JSON manifest.bundlePathInZip.Wire native so the app loads the OTA bundle when present:
NitroUpdateBundleManager.getStoredBundleURL() in your bundleURL() (see Native setup – iOS). Add the NitroUpdateBundleManager pod in the Podfile if needed.NitroUpdateBundleLoader.getStoredBundlePath(context); when it’s non-null, load the JS bundle from that path (see Native setup – Android).pendingValidation), so host apps do not need custom rollback patches in AppDelegate/MainApplication.In your app (JS): Call checkForUpdate(versionUrl). If it returns true, call downloadUpdate(downloadUrl), then reloadApp(). After the app restarts on the new bundle, call confirmBundle() once you know the new bundle runs correctly (e.g. after a successful API call or screen load).
Optional: Use checkForUpdateFromManifest(manifestUrl) for a JSON manifest with version, minAppVersion, bundleUrl, checksum, etc. Use downloadUpdateWithRetry() for retries. Use scheduleBackgroundCheck(versionUrl, downloadUrl, intervalSeconds) so the app checks for updates in the background (iOS: add Background Modes + task identifier; see Background check).
If something goes wrong: Call rollbackToPreviousBundle() then reloadApp(), or markCurrentBundleAsBad(reason). Use getRollbackHistory() and onRollback() for analytics or UI.
That’s the full flow: host version + zip → native loads stored bundle when present → JS checks, downloads, reloads, confirms.
When does the OTA apply for users? If you ship a physical (release) build and host the zip on a GitHub release: the app checks for updates shortly after launch, downloads the new bundle in the background while the user keeps using the app, then (in the example) calls reloadApp() so the next cold start loads the new bundle. For a step-by-step diagram and “you do this / user sees this”, see the example’s OTA-FLOW.md.
React Native 0.76+ templates usually use a Swift factory delegate (bundleURL() / sourceURL(for:)) and Android getDefaultReactHost. Canonical copy-paste sources:
See INTEGRATION.md for Podfile, ObjC, and older ReactNativeHost patterns.
Use the NitroUpdateBundleManager pod (Swift + ObjC/ObjC++, no C++) in AppDelegate so the app target does not pull in C++ headers. Add the bundle manager pod from the same path as the main library, then in your delegate:
import NitroUpdateBundleManager
// In your RCTDefaultReactNativeFactoryDelegate (or similar):
override func bundleURL() -> URL? {
#if DEBUG
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
if let otaURL = NitroUpdateBundleManager.getStoredBundleURL() {
return otaURL
}
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}
Podfile: If CocoaPods does not resolve NitroUpdateBundleManager automatically (the main NitroUpdate pod depends on it), add:
pod 'NitroUpdateBundleManager', :path => '../node_modules/react-native-nitro-update'
(Adjust the path if your app lives in a subdirectory; e.g. from example/ios use ../../node_modules/react-native-nitro-update.)
NitroUpdateBundleManager.getStoredBundleURL() includes automatic recovery if a pending OTA bundle crashes before confirmBundle().
If your app uses AppDelegate.m/AppDelegate.mm, use the ObjC API:
#import <NitroUpdateBundleManager/NitroUpdateBundleManagerObjC.h>
- (NSURL *)bundleURL
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
NSURL *otaURL = [NitroUpdateBundleManagerObjC getStoredBundleURL];
return otaURL ?: [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
Do not add custom host-side rollback/crash patching in AppDelegate; loader and crash-guard rollback are handled by this library.
New Architecture default (getDefaultReactHost, RN 0.76+): pass the OTA path as jsBundleFilePath (see example MainApplication.kt):
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.margelo.nitro.nitroupdate.NitroUpdateBundleLoader
override val reactHost: ReactHost by lazy {
getDefaultReactHost(
context = applicationContext,
packageList = PackageList(this).packages,
jsBundleFilePath = NitroUpdateBundleLoader.getStoredBundlePath(this),
)
}
Older ReactNativeHost: override getJSBundleFile() (or equivalent) to return NitroUpdateBundleLoader.getStoredBundlePath(this) when non-null in release.
NitroUpdateBundleLoader.getStoredBundlePath(context) includes the same automatic pending-validation crash recovery on Android.
import {
checkForUpdate,
downloadUpdate,
getStoredVersion,
reloadApp,
confirmBundle,
rollbackToPreviousBundle,
} from 'react-native-nitro-update'
// Check
const hasUpdate = await checkForUpdate('https://example.com/version.txt')
// Download (and optionally confirm after a successful run)
if (hasUpdate) {
await downloadUpdate('https://example.com/bundle.zip')
reloadApp()
// After app restarts, call confirmBundle() once the new bundle runs successfully
confirmBundle()
}
// Stored version (null if none)
const version = getStoredVersion()
// Rollback if needed
const didRollback = await rollbackToPreviousBundle()
if (didRollback) reloadApp()
| API | Description |
|---|---|
checkForUpdate(versionCheckUrl) | Returns true if remote version is newer and not blacklisted. |
downloadUpdate(downloadUrl, bundlePathInZip?, checksum?) | Downloads zip, extracts bundle, optionally verifies SHA-256. |
getStoredVersion() | Current stored OTA version or null. |
getStoredBundlePath() | Path to the stored bundle (or null). |
getAppVersion() | Native app version from Info.plist (iOS) or BuildConfig (Android). |
reloadApp() | Restarts the app (iOS/Android). |
confirmBundle() | Marks the current bundle as valid; clears rollback data. |
rollbackToPreviousBundle() | Restores previous bundle; returns true if rollback was done. |
markCurrentBundleAsBad(reason) | Blacklists current version and rolls back if possible. |
getBlacklistedVersions() | List of blacklisted version strings. |
getRollbackHistory() | Array of { timestamp, fromVersion, toVersion, reason }. |
onUpdateLifecycle(callback) | Subscribe to download/rollback/confirm/error events. |
onRollback(callback) | Subscribe to rollback events. |
scheduleBackgroundCheck(versionCheckUrl, downloadUrl?, intervalSeconds) | Schedules periodic background check (iOS BGTaskScheduler, Android WorkManager). See below. |
Retry and manifest:
downloadUpdateWithRetry(downloadUrl, options?, bundlePathInZip?, checksum?) — exponential backoff retry.checkForUpdateFromManifest(manifestUrl, appVersion?) — JSON manifest with version, minAppVersion, targetVersions, bundleUrl, checksum, etc.See src/index.ts and src/updateManifest.ts for full types and the UpdateManager class.
downloadUpdate, the new bundle is stored and marked pending validation. The app should reloadApp() and then call confirmBundle() once the new bundle has run successfully.confirmBundle(), native bundle loaders automatically recover on next launch by restoring the previous bundle (or embedded fallback when no previous bundle exists).markCurrentBundleAsBad(reason) to blacklist the bad version and force rollback immediately.onUpdateLifecycle and onRollback for UI or analytics.downloadUpdate. If the extracted bundle’s hash doesn’t match, the update is aborted.downloadUpdateWithRetry with RetryOptions (e.g. maxRetries, initialDelayMs, maxDelayMs) for exponential backoff.Use checkForUpdateFromManifest(manifestUrl, appVersion?) with a JSON manifest that can include:
version — remote bundle versionminAppVersion — minimum app version (optional)targetVersions — optional list of compatible app versionsbundleUrl, checksum, releaseNotes, etc.The helper returns hasUpdate, isCompatible, remoteVersion, and metadata for your UI or download step.
confirmBundle(), that backup is cleared.rollbackToPreviousBundle() or the native loader logic to prefer the last known-good bundle when the current one is pending validation.markCurrentBundleAsBad(reason) blacklists the current version and restores the previous bundle so it won’t be offered again.This library is inspired by react-native-nitro-ota and follows the same patterns where it makes sense:
| Aspect | react-native-nitro-ota | react-native-nitro-update |
|---|---|---|
| Tech | Nitro Modules, JSI | Same (Nitro Modules, JSI) |
| iOS bundle loader | Separate NitroOtaBundleManager pod | Same: NitroUpdateBundleManager pod (Swift + ObjC/ObjC++) to avoid C++ in app target |
| Version check | Plain ota.version or ota.version.json | Plain version URL or JSON manifest (checkForUpdateFromManifest) |
| Download | Zip from URL, optional progress | Same + optional SHA-256 checksum |
| Rollback / blacklist | Yes, crash safety, confirm bundle | Same lifecycle (confirm, rollback, blacklist, history) |
| Background check | Experimental (WorkManager / BGTaskScheduler) | Implemented: iOS BGTaskScheduler, Android WorkManager (min 15 min) |
| Extra in this lib | — | Manifest-based check (minAppVersion, targetVersions), retry with backoff, optional checksum, package-hosted build CLI (npx react-native-nitro-update build) |
Same idea: check version → download zip → reload → confirm when the new bundle runs OK; rollback and blacklist if it doesn’t.
You put your bundle zip and version file on any server you want; the app only needs the URLs. The library does not care where they live:
https://github.com/you/repo/raw/main/ota.version and https://github.com/you/repo/releases/download/v1.0.0/bundle.zip.So: one version URL (plain text or JSON) and one download URL (the zip). Same concept as react-native-nitro-ota: server-agnostic.
Call scheduleBackgroundCheck(versionCheckUrl, downloadUrl, intervalSeconds) to run a version check (and optional download) periodically in the background. When an update is found and downloadUrl is set, the library downloads and stores the new bundle; the user gets it on the next app launch (no reload in background).
BGTaskScheduler; interval is a hint (system may delay). You must:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.nitroupdate.backgroundcheck</string>
</array>
Example:
import { scheduleBackgroundCheck } from 'react-native-nitro-update'
scheduleBackgroundCheck(
'https://your-cdn.com/version.txt',
'https://your-cdn.com/bundle.zip',
3600
)
Like react-native-nitro-ota, you can use a githubOTA() helper to build version and download URLs from a GitHub repo:
import { githubOTA, checkForUpdate, downloadUpdate, reloadApp } from 'react-native-nitro-update'
// Option 1: Use Releases (recommended) — one app build; create a new Release for each OTA
const { versionUrl, downloadUrl } = githubOTA({
githubUrl: 'https://github.com/your-username/your-ota-repo',
otaVersionPath: 'version.txt',
bundlePath: 'bundle.zip',
useReleases: true,
})
// Option 2: Use a branch (raw) — push version.txt and bundle.zip to main
const { versionUrl, downloadUrl } = githubOTA({
githubUrl: 'https://github.com/your-username/your-ota-repo',
otaVersionPath: 'version.txt',
ref: 'main',
useReleases: false,
})
const hasUpdate = await checkForUpdate(versionUrl)
if (hasUpdate) {
await downloadUpdate(downloadUrl)
reloadApp()
}
| Option | versionUrl / downloadUrl |
|---|---|
useReleases: true | https://github.com/owner/repo/releases/latest/download/version.txt and .../bundle.zip |
useReleases: false | https://raw.githubusercontent.com/owner/repo/ref/version.txt and .../bundle.zip |
received / total) exposed to JS so you can show a progress bar (API design exists; native wiring may need completion).This repo includes an example app in example/. From the repo root:
npm run example:android
npm run example:ios
See example/README.md for details.
This package is kept compatible with current react-native-nitro-modules so consumers do not need patches:
HybridView (not RecyclableView).equals() with override (since HybridObject::equals is not virtual in NitroModules).Podfile should include NitroUpdate pod utils in post_install:
require_relative '../node_modules/react-native-nitro-update/scripts/nitro_update_pod_utils'NitroUpdatePodUtils.apply!(installer)
This sanitizes SWIFT_ACTIVE_COMPILATION_CONDITIONS and moves invalid compiler flags to OTHER_SWIFT_FLAGS.If you hit:
Conditional compilation flags must be valid Swift identifiers (rather than '-enable-bare-slash-regex')
add the Podfile wiring above and re-run cd ios && pod install && cd ...
After running npm run specs (Nitrogen), scripts/post-nitrogen-fix.sh is run automatically to re-apply the Swift and C++ compatibility fixes to the generated files.
To publish this package to the public npm registry, see PUBLISHING.md for prerequisites (npm account, 2FA), versioning, and the exact npm publish steps.
MIT
FAQs
OTA updates for React Native via Nitro/JSI: check, download, reload, rollback, checksum, retry, manifest
The npm package react-native-nitro-update receives a total of 152 weekly downloads. As such, react-native-nitro-update popularity was classified as not popular.
We found that react-native-nitro-update demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.