@upfluxhq/react-native
v1.2.0
Published
React Native SDK for Upflux OTA updates
Maintainers
Readme
@upfluxhq/react-native
React Native SDK for Upflux OTA updates.
Installation
npm install @upfluxhq/react-native
# or
yarn add @upfluxhq/react-native
# or
pnpm add @upfluxhq/react-nativeiOS Setup
cd ios && pod installNative Module Setup (Required for OTA to work)
The native module is autolinked automatically when you install the package. However, you need to configure your app to load the OTA bundle.
Android Setup
The UpfluxPackage is autolinked automatically. You only need to modify MainApplication.kt to load the custom bundle path.
MainApplication.kt (React Native 0.73+):
import com.upflux.UpfluxModule
class MainApplication : Application(), ReactApplication {
override val reactHost: ReactHost by lazy {
// Get custom bundle path from Upflux (returns null if no OTA update)
val customBundlePath = UpfluxModule.getJSBundleFile(this)
getDefaultReactHost(
context = applicationContext,
packageList = PackageList(this).packages,
jsBundleFilePath = customBundlePath
)
}
override fun onCreate() {
super.onCreate()
loadReactNative(this)
}
}MainApplication.kt (React Native < 0.73 Legacy):
import com.upflux.UpfluxModule
// Add this method to your MainApplication class:
override fun getJSBundleFile(): String? {
return UpfluxModule.getJSBundleFile(this) ?: super.getJSBundleFile()
}MainApplication.java (for Java-based projects):
import com.upflux.UpfluxModule;
// Add this method to your MainApplication class:
@Override
protected String getJSBundleFile() {
String customBundle = UpfluxModule.getJSBundleFile(this);
if (customBundle != null) {
return customBundle;
}
return super.getJSBundleFile();
}iOS Setup
The native module is autolinked via CocoaPods. Run pod install after installing the package:
cd ios && pod installThen modify AppDelegate.m (or AppDelegate.mm) to load the OTA bundle:
#import <upflux_react_native/UpfluxModule.h>
// Modify sourceURLForBridge:
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
// Check for OTA bundle first
NSURL *customBundle = [UpfluxModule bundleURL];
if (customBundle) {
return customBundle;
}
// Fall back to default bundle
return [self bundleURL];
}AppDelegate.swift (for Swift-based projects):
First, ensure your bridging header includes the Upflux module:
// YourApp-Bridging-Header.h
#import <upflux_react_native/UpfluxModule.h>Then modify your AppDelegate.swift:
override func sourceURL(for bridge: RCTBridge) -> URL? {
if let customBundle = UpfluxModule.bundleURL() {
return customBundle
}
return bundleURL()
}Usage
Basic Setup
import { Upflux } from '@upfluxhq/react-native';
// Configure the SDK
const upflux = new Upflux({
appId: 'your-app-id',
deployment: 'production',
clientKey: 'your-client-key',
serverUrl: 'https://your-api-server.com'
});
// Initialize on app startup
await upflux.init();Note: The
binaryVersionis automatically detected from the native platform (versionNameon Android,CFBundleShortVersionStringon iOS). You do not need to pass it manually.
Check for Updates
const updateInfo = await upflux.checkForUpdates();
if (updateInfo.updateAvailable) {
console.log('Update available:', updateInfo.releaseLabel);
}Download and Apply Update
if (updateInfo.updateAvailable) {
// Download the update
const downloadResult = await upflux.downloadUpdate(updateInfo, {
onProgress: (progress) => {
console.log(`Download progress: ${progress.percentage}%`);
}
});
if (downloadResult.success) {
// Apply and restart
await upflux.applyUpdate(downloadResult);
// App will restart here
}
}Complete Example
import React, { useEffect, useState } from 'react';
import { View, Text, Button, Alert } from 'react-native';
import { Upflux } from '@upfluxhq/react-native';
const upflux = new Upflux({
appId: 'my-app',
deployment: 'production',
clientKey: 'your-client-key',
serverUrl: 'http://10.0.2.2:4000' // For Android emulator
});
export default function App() {
const [checking, setChecking] = useState(false);
const [updating, setUpdating] = useState(false);
useEffect(() => {
upflux.init();
}, []);
const checkForUpdates = async () => {
setChecking(true);
try {
const updateInfo = await upflux.checkForUpdates();
if (updateInfo.updateAvailable) {
Alert.alert(
'Update Available',
`Version ${updateInfo.releaseLabel} is available`,
[
{ text: 'Later', style: 'cancel' },
{ text: 'Update Now', onPress: () => installUpdate(updateInfo) }
]
);
} else {
Alert.alert('Up to Date', 'You have the latest version');
}
} catch (error) {
console.error('Error checking for updates:', error);
} finally {
setChecking(false);
}
};
const installUpdate = async (updateInfo) => {
setUpdating(true);
try {
const downloadResult = await upflux.downloadUpdate(updateInfo);
if (downloadResult.success) {
await upflux.applyUpdate(downloadResult);
// App will restart
}
} catch (error) {
console.error('Error installing update:', error);
Alert.alert('Error', 'Failed to install update');
} finally {
setUpdating(false);
}
};
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>My App</Text>
<Button
title={checking ? 'Checking...' : 'Check for Updates'}
onPress={checkForUpdates}
disabled={checking || updating}
/>
</View>
);
}Binary Upgrade Detection
The SDK automatically detects when a user upgrades or downgrades the app via the Play Store or App Store, and clears any stale OTA bundles so the new native binary's default bundle is used.
How It Works
The SDK uses a two-layer defense to detect binary version changes:
Native Layer (before JS loads):
getJSBundleFile()(Android) andbundleURL(iOS) compare the native version stored when the OTA was applied against the current native version. If they differ, the stale OTA path is cleared and the default bundle loads. This ensures the correct bundle is used on the very first launch after an upgrade.JS Layer (after JS loads):
resolveInitialBundle()performs the same check and additionally cleans up stale metadata (activeBundle.json) and deletes old bundle files from disk to free storage.
Version Auto-Detection
The binaryVersion sent to the server during update checks is automatically read from the native platform:
- Android:
PackageInfo.versionName(frombuild.gradle) - iOS:
CFBundleShortVersionString(fromInfo.plist)
This means the SDK always reports the correct binary version, even when running from an OTA bundle that was built for a previous version. You never need to manually set this value.
API Reference
Upflux Class
constructor(config: UpfluxConfig)
Create a new Upflux instance.
interface UpfluxConfig {
appId: string; // Your app identifier
deployment: string; // Deployment name (e.g., 'production', 'staging')
clientKey: string; // Client key from dashboard
serverUrl: string; // Upflux API server URL
collectDeviceInfo?: boolean; // Whether to collect device info (default: true)
}init(): Promise<StartupBundle>
Initialize the SDK. Call this early in app startup. Resolves which bundle to load and detects binary upgrades.
checkForUpdates(deviceInfo?: Partial<DeviceInfo>): Promise<UpdateInfo>
Check if an update is available. Binary version is auto-detected from the native platform.
downloadUpdate(info: UpdateInfo, options?: DownloadOptions): Promise<DownloadResult>
Download an available update.
applyUpdate(result: DownloadResult): Promise<ApplyResult>
Apply a downloaded update and restart the app.
clearUpdate(): Promise<void>
Clear the current update and revert to default bundle on next restart.
isInitialized(): boolean
Check if the SDK has been initialized.
getStartupBundle(): StartupBundle | null
Get the startup bundle info from init().
getConfig(): UpfluxConfig
Get the current SDK configuration.
destroy(): void
Destroy the SDK instance and clean up resources.
Store Update Required
When a release is published with --min-binary-version, the server returns storeUpdateRequired: true for devices whose native binary is below that version. Check it directly on the checkForUpdates() return value:
const info = await upflux.checkForUpdates();
if (info.storeUpdateRequired) {
// Device binary is too old — direct user to the store
Alert.alert(
'App Update Required',
`Please update the app to v${info.requiredBinaryVersion} from the store first.`,
[{ text: 'Update Now', onPress: () => Linking.openURL('https://play.google.com/...') }]
);
} else if (info.updateAvailable) {
// Normal OTA flow
const result = await upflux.downloadUpdate(info);
await upflux.applyUpdate(result);
}Storage Location
Updates are stored in the app's document directory:
<DocumentDirectory>/upflux/
├── activeBundle.json # Currently active release info
├── deviceId.txt # Persistent device identifier
├── v1.0.0/ # Release directory
│ ├── bundle.js # JS bundle
│ └── assets/ # Assets
└── v1.0.1/ # Another releaseWhen a binary upgrade is detected, all old bundle directories are automatically cleaned up to free storage.
Troubleshooting
Network Error on Android Emulator
Use 10.0.2.2 instead of localhost:
serverUrl: 'http://10.0.2.2:4000'Bundle Not Loading After Restart
Make sure you've completed the native module setup (see above).
Update Downloads But Doesn't Apply
Check that:
- Native module is properly registered
- Bundle path override is implemented in AppDelegate/MainApplication
- File permissions allow reading from document directory
Stale OTA After Play Store Update
This is automatically handled in v1.1.0+. The SDK detects binary version changes at the native level and clears stale OTA bundles before any JS code runs. If you're upgrading from an earlier version, simply update the SDK — no code changes needed (except removing binaryVersion from your config if you were passing it).
Migration from v1.0.x
Breaking Changes in v1.1.0
binaryVersion removed from UpfluxConfig:
The binaryVersion field has been removed from the configuration. It is now automatically detected from the native platform. Remove it from your config:
const upflux = new Upflux({
appId: 'my-app',
deployment: 'production',
clientKey: 'your-client-key',
serverUrl: 'https://api.example.com',
- binaryVersion: '1.0.0', // Remove this line
});checkForUpdates() no longer needs binaryVersion:
- const updateInfo = await upflux.checkForUpdates({
- deviceId: 'device-123',
- binaryVersion: '1.0.0',
- });
+ const updateInfo = await upflux.checkForUpdates();License
MIT
