Overview
B3 OTA (Over-The-Air) provides a universal infrastructure to update your React Native application's logic and assets instantly. It eliminates the need for App Store or Google Play resubmissions for non-native changes, ensuring your users always have the latest features and fixes.
1. Build JS Bundle → 2. Package to .zip → 3. Upload to OTA Dashboard → 4. App auto-checks for updates → 5. Download & Apply updates.
Installation
Integrate the B3 OTA core SDK into your project using your preferred package manager.
npm install react-native-ota-hot-update react-native-blob-util
Then run pod install (iOS) or gradle sync (Android) to install native dependencies.
iOS Integration (Xcode)
Open your project in Xcode and modify AppDelegate.mm to allow the B3 engine to resolve the dynamic bundle path.
1 Source URL Resolution
#import- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { #if DEBUG return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; #else // B3 Manager resolves the highest priority local bundle return [B3OTAManager getActiveBundleURL]; #endif }
Android Integration (Studio)
Modify MainApplication.kt (or .java) to point the React host to the B3-managed storage path.
1 Bundle Path Override
import com.b3.ota.B3OTAManager
override fun getJSBundleFile(): String? {
// B3OTAManager checks for OTA updates, fallback to assets
return B3OTAManager.resolveBundlePath(applicationContext)
}
JS Initialization
Initialize OTA update checking service in your React Native application.
Create OTA Service
Create src/services/ota/otaService.ts
import hotUpdate from 'react-native-ota-hot-update';
import ReactNativeBlobUtil from 'react-native-blob-util';
import * as Device from 'expo-device';
import * as Application from 'expo-application';
const OTA_CONFIG = {
apiUrl: 'https://ota.2maru.com',
apiKey: 'your_api_key_here',
bundleId: 'com.yourcompany.app',
platform: Platform.OS,
deviceId: '', // Will be set during initialization
};
export async function checkForUpdates() {
const response = await fetch(`${OTA_CONFIG.apiUrl}/api/v1/ota/check-update`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': OTA_CONFIG.apiKey,
},
body: JSON.stringify({
bundleId: OTA_CONFIG.bundleId,
platform: OTA_CONFIG.platform,
currentVersion: '1.0.0',
deviceId: OTA_CONFIG.deviceId,
}),
});
const data = await response.json();
return data.data;
}
export async function performUpdate(onProgress) {
const updateInfo = await checkForUpdates();
if (!updateInfo?.updateAvailable) {
return { success: false, message: 'No update available' };
}
return new Promise((resolve) => {
hotUpdate.downloadBundleUri(
ReactNativeBlobUtil,
updateInfo.downloadUrl,
updateInfo.latestVersion,
{
updateSuccess: () => resolve({ success: true }),
updateFail: (msg) => resolve({ success: false, message: msg }),
updateProgress: onProgress,
restartAfterInstall: true,
}
);
});
}
Check for Updates on App Launch
// App.tsx ??UpdateScreen.tsx
import { checkForUpdates, performUpdate } from './services/ota/otaService';
useEffect(() => {
const checkAndUpdate = async () => {
const updateInfo = await checkForUpdates();
if (updateInfo?.updateAvailable) {
await performUpdate((progress) => {
console.log('Download progress:', progress);
});
}
};
checkAndUpdate();
}, []);
Bundle Packaging
Prepare your application assets and package them into .zip files for OTA platform upload. Below are packaging scripts for various frameworks.
1 Expo (Recommended)
Use Expo's built-in tools to export JS Bundle
# iOS npx expo export:embed --platform ios --entry-file index.js --bundle-output ios/main.jsbundle --assets-dest ios/assets cd ios && zip -r main.jsbundle.zip main.jsbundle assets && cd .. # Android npx expo export:embed --platform android --entry-file index.js --bundle-output android/index.android.bundle --assets-dest android/assets cd android && zip -r index.android.bundle.zip index.android.bundle assets && cd ..
2 React Native CLI (Pure RN)
# iOS npx react-native bundle \ --platform ios \ --dev false \ --entry-file index.js \ --bundle-output ios/main.jsbundle \ --assets-dest ios/assets cd ios && zip -r main.jsbundle.zip main.jsbundle assets && cd .. # Android npx react-native bundle \ --platform android \ --dev false \ --entry-file index.js \ --bundle-output android/index.android.bundle \ --assets-dest android/assets cd android && zip -r index.android.bundle.zip index.android.bundle assets && cd ..
3 Using Xcode Build Script (iOS)
Add Build Phase Script in Xcode
#!/bin/bash set -e BUNDLE_FILE="$CONFIGURATION_BUILD_DIR/main.jsbundle" ASSETS_DIR="$CONFIGURATION_BUILD_DIR/assets" # Export Bundle export NODE_BINARY=node ../node_modules/react-native/scripts/react-native-xcode.sh # Package as zip cd "$CONFIGURATION_BUILD_DIR" zip -r main.jsbundle.zip main.jsbundle assets echo "Bundle packaged: main.jsbundle.zip"
4 Using Gradle Task (Android)
Add to android/app/build.gradle
task bundleReleaseJsAndAssets(type: Exec) {
def bundleFile = "$buildDir/intermediates/assets/release/index.android.bundle"
def assetsDir = "$buildDir/intermediates/assets/release"
commandLine "npx", "react-native", "bundle",
"--platform", "android",
"--dev", "false",
"--entry-file", "index.js",
"--bundle-output", bundleFile,
"--assets-dest", assetsDir
doLast {
exec {
workingDir buildDir
commandLine "zip", "-r", "index.android.bundle.zip",
"intermediates/assets/release/index.android.bundle",
"intermediates/assets/release/assets"
}
}
}
// Run before assembleRelease
assembleRelease.dependsOn bundleReleaseJsAndAssets
5 Automation Script (Node.js)
Create scripts/build-bundle.js
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const platform = process.argv[2]; // 'ios' or 'android'
const outputDir = platform === 'ios' ? 'ios' : 'android';
const bundleName = platform === 'ios' ? 'main.jsbundle' : 'index.android.bundle';
// 1. Build Bundle
console.log(`Building ${platform} bundle...`);
execSync(`npx react-native bundle \\
--platform ${platform} \\
--dev false \\
--entry-file index.js \\
--bundle-output ${outputDir}/${bundleName} \\
--assets-dest ${outputDir}/assets`, { stdio: 'inherit' });
// 2. Package as zip
console.log('Creating zip file...');
execSync(`cd ${outputDir} && zip -r ${bundleName}.zip ${bundleName} assets`,
{ stdio: 'inherit' });
console.log(`??Bundle ready: ${outputDir}/${bundleName}.zip`);
// Usage:
// node scripts/build-bundle.js ios
// node scripts/build-bundle.js android
Files uploaded to OTA platform must be .zip files containing the Bundle file and assets folder. Ensure zip file names meet platform requirements (iOS: main.jsbundle.zip, Android: index.android.bundle.zip).
Hermes Bytecode (Optional)
If your app uses Hermes engine, you can pre-compile Bundle to HBC format for better performance. Note: Current OTA system supports standard JS Bundle, HBC support is under development.
# Compile to HBC (Future support) ./node_modules/react-native/sdks/hermesc/osx-bin/hermesc \ -emit-binary \ -out index.android.hbc \ index.android.bundle # Package zip -r index.android.bundle.zip index.android.hbc assets
Dashboard Operations Guide
Complete OTA management platform workflow, from creating organizations to releasing updates.
Step 1: Create Organization
- Click "New Organization" button in top right
- Enter organization name (e.g., "AMC Group")
- Add optional description
- Click "Create"
Step 2: Add Project (Application)
- Click "Add Project" in Organization card
- Fill in project information:
- Project Name: Application name
- Bundle ID: com.company.app
- Platform: Select iOS or Android
- Click "Create Project"
Step 3: Create Test Group (Optional)
- Go to "Test Groups" page
- Click "New Test Group"
- Enter test group name (e.g., "QA Team")
- Select target Project
- Add Device IDs of test devices
Step 4: Upload Bundle
- Click Project card to enter details page
- Click "Upload OTA Bundle" button
- Enter version number (e.g., 1.0.8)
- Select Initial Release Strategy:
- Public Release: Release to all users immediately
- Staged Rollout: Gradual release (10%, 25%, 50%)
- Beta Release: Release to Beta test group
- Internal Testing: Internal test group only
- If Beta/Internal selected, choose corresponding Test Group
- Drag & drop .zip file or click to select file
- Click "Start Upload"
Step 5: Manage Release Status
Click "Promote" to upgrade to ACTIVE (public release)
Click "Pause" to pause release (when issues found)
Click "Resume" to restore release or "Archive" to permanently archive
OTA Release Strategies
Choose the right release strategy to ensure update stability and safety.
Public Release
Release to all users immediately, suitable for stable updates.
Use Cases: Bug fixes, minor feature updates
Staged Rollout
Gradual release (10% ??25% ??50% ??100%), reducing risk.
Use Cases: Major feature updates, architecture changes
Beta Release
Release to Beta test group, collect user feedback.
Use Cases: New feature testing, UX validation
Internal Testing
Internal team only, for development testing.
Use Cases: Development phase, QA testing
Recommended Release Flow
If a critical bug is found, immediately click "Pause" to stop the current version, then click "Resume" on the previous stable version. Users will automatically rollback on next update check.