@rishav9875/react-native-ota-update
v1.0.1
Published
Over-the-Air (OTA) JavaScript bundle updates for React Native apps on Android and iOS
Maintainers
Readme
react-native-ota-update
Over-the-Air (OTA) JavaScript bundle updates for React Native apps on Android and iOS.
Features
- ✅ Download and apply JavaScript bundles at runtime
- ✅ ZIP support - Download bundles with assets
- ✅ Works on both Android and iOS
- ✅ Simple API
- ✅ Automatic bundle switching on app restart
- ✅ Bundle verification and error handling
- ✅ Asset extraction and management both Android and iOS
- ✅ Easy to Use: Simple JavaScript API
- ✅ TypeScript Support: Full TypeScript definitions included
- ✅ Secure: Downloads to app's private storage
- ✅ Lightweight: Minimal dependencies
- ✅ Production Ready: Used in production apps
Installation
npm install react-native-ota-update
# or
yarn add react-native-ota-updateiOS Setup
- Install pods:
cd ios && pod install && cd ..- Update your
AppDelegate.mm:
#import "OTAUpdateManager.h"
// In the bundleURL method, add this before returning:
- (NSURL *)bundleURL
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
// Check for OTA update bundle first
NSURL *customBundleURL = [[OTAUpdateManager sharedManager] getCustomBundleURL];
if (customBundleURL) {
return customBundleURL;
}
// Fall back to default bundle
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}Android Setup
- Update your
MainApplication.ktorMainApplication.java:
Kotlin:
import com.otaupdate.OTAUpdatePackage
import com.otaupdate.OTAUpdateManager
// In getPackages():
override fun getPackages(): List<ReactPackage> {
val packages = PackageList(this).packages.toMutableList()
packages.add(OTAUpdatePackage())
return packages
}
// In ReactNativeHost, add getJSBundleFile():
override fun getJSBundleFile(): String? {
val otaManager = OTAUpdateManager(this@MainApplication)
val bundleFile = OTAUpdateManager.getBundleFile(this@MainApplication)
return if (otaManager.hasPendingUpdate() && bundleFile.exists()) {
otaManager.setPendingUpdate(false)
bundleFile.absolutePath
} else {
super.getJSBundleFile()
}
}Java:
import com.otaupdate.OTAUpdatePackage;
import com.otaupdate.OTAUpdateManager;
// In getPackages():
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new OTAUpdatePackage());
return packages;
}
// In ReactNativeHost:
@Override
protected String getJSBundleFile() {
OTAUpdateManager otaManager = new OTAUpdateManager(getApplicationContext());
File bundleFile = OTAUpdateManager.getBundleFile(getApplicationContext());
if (otaManager.hasPendingUpdate() && bundleFile.exists()) {
otaManager.setPendingUpdate(false);
return bundleFile.getAbsolutePath();
}
return super.getJSBundleFile();
}- Add Kotlin coroutines dependency to
android/app/build.gradle:
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
}Usage
import OTAUpdateManager from 'react-native-ota-update';
// Download an update
async function downloadUpdate() {
try {
const result = await OTAUpdateManager.downloadUpdate(
'https://your-server.com/bundle.js'
);
console.log(result); // "Bundle downloaded successfully. Restart app to apply."
// Restart to apply update
await OTAUpdateManager.restartApp();
} catch (error) {
console.error('Download failed:', error);
}
}
// Get bundle information
async function checkUpdate() {
const info = await OTAUpdateManager.getBundleInfo();
console.log('Bundle exists:', info.exists);
console.log('Bundle size:', info.size, 'bytes');
console.log('Pending update:', info.pendingUpdate);
}
// Clear update
async function clearUpdate() {
await OTAUpdateManager.clearUpdate();
console.log('Update cleared');
}API Reference
downloadUpdate(url: string): Promise<string>
Downloads a JavaScript bundle from the specified URL.
Parameters:
url- The URL to download the bundle from
Returns: Promise that resolves with a success message
Example:
await OTAUpdateManager.downloadUpdate('https://example.com/bundle.js');getBundleInfo(): Promise<BundleInfo>
Gets information about the downloaded bundle.
Returns: Promise that resolves with bundle information:
{
exists: boolean; // Whether a bundle exists
size: number; // Size in bytes
path: string; // File system path
pendingUpdate: boolean; // Whether update is pending
}clearUpdate(): Promise<string>
Clears the downloaded bundle and reverts to the original.
Returns: Promise that resolves with a success message
restartApp(): Promise<string>
Restarts the app to apply the update.
Note: On iOS, this only shows a message. Users must manually restart. On Android, the app restarts programmatically.
Returns: Promise that resolves with a success message or instruction
getPlatform(): 'android' | 'ios'
Returns the current platform.
canRestartProgrammatically(): boolean
Returns true for Android, false for iOS.
Creating Bundles
For Android:
npx react-native bundle \
--platform android \
--dev false \
--entry-file index.js \
--bundle-output ./bundle/index.android.bundle \
--assets-dest ./bundle/assetsFor iOS:
npx react-native bundle \
--platform ios \
--dev false \
--entry-file index.js \
--bundle-output ./bundle/main.jsbundle \
--assets-dest ./bundle/assetsFor Expo:
Android:
npx expo export:embed \
--platform android \
--entry-file node_modules/expo/AppEntry.js \
--bundle-output ./bundle/index.android.bundle \
--assets-dest ./bundle/assets \
--dev falseiOS:
npx expo export:embed \
--platform ios \
--entry-file node_modules/expo/AppEntry.js \
--bundle-output ./bundle/main.jsbundle \
--assets-dest ./bundle/assets \
--dev falseServer Setup
You need a server to host your bundles. Here's a simple Express.js example:
const express = require('express');
const app = express();
app.get('/update/android', (req, res) => {
res.sendFile('/path/to/index.android.bundle');
});
app.get('/update/ios', (req, res) => {
res.sendFile('/path/to/main.jsbundle');
});
app.listen(3000);Or use a CDN:
- AWS S3 + CloudFront
- Google Cloud Storage
- Firebase Storage
- Azure Blob Storage
Platform Differences
| Feature | Android | iOS |
|---------|---------|-----|
| Bundle Name | index.android.bundle | main.jsbundle |
| Storage | Internal storage | Documents directory |
| Restart | Programmatic ✅ | Manual ⚠️ |
| Permissions | None needed | None needed |
Security Considerations
Production Checklist
- [ ] Use HTTPS only
- [ ] Add authentication (API keys, JWT)
- [ ] Implement bundle signature verification
- [ ] Add version control
- [ ] Monitor crash rates after updates
- [ ] Implement rollback mechanism
- [ ] Use gradual rollouts
What Can Be Updated
✅ Allowed:
- JavaScript code
- React components
- Business logic
- UI changes
- Bug fixes
❌ Not Allowed:
- Native code (Kotlin/Swift/Objective-C)
- Native dependencies
- App permissions
- AndroidManifest.xml / Info.plist
App Store Guidelines
Google Play (Android)
- ✅ Fully allowed
- ✅ No restrictions
Apple App Store (iOS)
- ✅ Allowed for JavaScript updates
- ⚠️ Don't change core app functionality
- ⚠️ Only update interpreted code (JavaScript)
- ⚠️ Document for App Review if asked
Troubleshooting
Android
Module not found:
cd android && ./gradlew clean && cd ..
npx react-native run-androidBundle not loading:
adb shell run-as <your.package.name> ls -lh files/iOS
Module not found:
cd ios && pod install && cd ..
npx react-native run-iosBundle not loading:
Check Xcode console for [OTAUpdateManager] logs
Example App
See the example directory for a complete working example.
Contributing
Contributions are welcome! Please open an issue or submit a PR.
License
MIT
Credits
Created by [Your Name]
