@zepto-labs/react-native-delta
v1.0.0
Published
Delta sdk for react native apps
Readme
Ship the change. Not the bundle.
The next generation of runtime updates for React Native.
delta is a production-ready patch-based runtime update platform for React Native that enables you to ship bug fixes, performance improvements, and new features instantly—without waiting for App Store or Play Store approvals.
Unlike traditional OTA solutions that download and replace the entire JavaScript bundle, delta generates and applies lightweight binary patches, delivering only what changed. The result is dramatically smaller downloads, faster update adoption, lower bandwidth consumption, and a seamless update experience for your users.
Why delta?
Traditional OTA platforms replace the entire JavaScript bundle every time you publish an update—even if you've changed only a few lines of code.
Delta takes a fundamentally different approach.
Using intelligent binary delta patching, it computes the difference between releases and delivers only the bytes required to transform one version into another. This significantly reduces update sizes, accelerates deployments, and helps users stay on the latest version with minimal data usage.
What does the SDK do?
The react-native-delta SDK seamlessly integrates with your React Native application and manages the complete runtime update lifecycle.
- ⚡ Automatically checks for available updates.
- 📦 Downloads lightweight delta patches instead of full JavaScript bundles.
- 🔐 Verifies every update before installation to ensure integrity and security.
- 🚀 Applies updates seamlessly with minimal interruption to the user experience.
- 🔄 Safely rolls back to the previous version if an update fails.
- 🎯 Targets compatible app versions, ensuring only supported devices receive updates.
- 📊 Provides update metadata and lifecycle events for monitoring and analytics.
Whether you're shipping a critical hotfix or rolling out your next feature, Delta enables continuous delivery while keeping updates fast, reliable, and bandwidth-efficient.
Ship only the difference. Deliver updates that users barely notice—but always appreciate.
Related Repositories
This SDK is one part of the Delta platform. It works alongside:
| Repository | Description | | ---------------------------------------------------------- | ------------------------------------------------------------------ | | delta-cli | CLI for registry management, bundle uploads, and release workflows | | delta-server | Backend server for patch delivery, releases, and the update API |
Table of Contents
Installation
yarn add @zepto-labs/react-native-deltaor
npm install @zepto-labs/react-native-deltaPrerequisites
- Node.js >= 20
- JDK 17 (for Android builds)
- Xcode >= 14 (for iOS builds)
- React Native >= 0.73
Integration
Android
1. Apply the delta Gradle Plugin
In your app-level build.gradle, apply the Delta Gradle plugin at the bottom:
apply from: file("../../node_modules/@zepto-labs/react-native-delta/android/delta.gradle");2. Configure delta ext properties
Add the following ext block at the top of your app-level build.gradle to configure Delta:
ext {
DELTA_JS_VERSION = "1" // Unique per native app version; increment for each new store release or breaking native change
ANDROID_DELTA_ID = "YOUR_ANDROID_APP_ID"
DELTA_SERVER_URL = "YOUR_DELTA_SERVER_URL"
}Then expose ANDROID_DELTA_ID and DELTA_SERVER_URL as BuildConfig fields inside your android { defaultConfig { ... } } block:
android {
defaultConfig {
buildConfigField "String", "ANDROID_DELTA_ID", "\"${ANDROID_DELTA_ID}\""
buildConfigField "String", "DELTA_SERVER_URL", "\"${DELTA_SERVER_URL}\""
}
}Each platform requires its own unique App ID registered in the delta registry — the Android and iOS App IDs must be different values and must not be reused across platforms. Use delta-cli to create one for Android:
# Install delta-cli (if not already installed)
npm install -D @zepto-labs/delta-cli
# Create a registry entry for Android.
yarn delta-cli createRegistry --appId YOUR_ANDROID_DELTA_ID --platform android --appName "YourAppName"Important: Choose a unique
--appIdfor Android. This value must not be reused for iOS or any other app.
3. Configure MainApplication
Your MainApplication must implement IDeltaDelegate:
import com.delta.IDeltaDelegate
import com.delta.Delta
class MainApplication : Application(), ReactApplication, IDeltaDelegate {
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getJSBundleFile(): String {
return Delta.getJSBundlePath()
}
// ... other overrides
}
override fun onCreate() {
super.onCreate()
Delta.initialize(this)
}
override fun onEventOccurred(eventName: String, params: JSONObject) {
// Handle Delta lifecycle events — see Events section below
}
}4. Configure BSPatch Maven Repo
delta ships a pre-built bspatch AAR inside the npm package. Add the following to your android/gradle.properties so Gradle can resolve it:
deltaMavenRepo=../node_modules/@zepto-labs/react-native-delta/maven-repoNote: Adjust the relative path if your
android/folder is nested deeper (e.g. use../../node_modules/...in a monorepo).
5. Manifest File
A manifest.json is auto-generated in src/main/assets/ during release builds:
{
"appId": "yourAppId",
"appVersion": "1.0.0",
"jsVersion": 1,
"bundleVersion": 0,
"hash": "md5-hash-of-bundle"
}iOS
1. Podfile
bspatch-ios is a C pod that doesn't define modules by default. Since react-native-delta is a Swift pod that depends on it, you need to enable modular headers for bspatch-ios so Swift can import it. Add the following inside your target block:
target 'YourApp' do
pod 'bspatch-ios', :modular_headers => true
# ... rest of your pods
end2. AppDelegate.h
Import Delta and conform to IDeltaDelegate:
#import <RCTAppDelegate.h>
#import <UIKit/UIKit.h>
@import react_native_delta;
@interface AppDelegate : RCTAppDelegate<IDeltaDelegate>
@end3. AppDelegate.m
Initialize Delta and configure the bundle URL:
@import react_native_code_delta;
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.moduleName = @"YourApp";
self.initialProps = @{};
[Delta initialize:self];
BOOL success = [super application:application didFinishLaunchingWithOptions:launchOptions];
[Delta checkUpdate];
return success;
}
- (NSURL *)bundleURL
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [Delta getJSBundlePath];
#endif
}
- (void)onEventOccurredWithEventName:(NSString *)eventName params:(NSDictionary<NSString *,id> *)params {
// Handle delta lifecycle events — see Events section below
}
@end4. Info.plist Keys
iOS requires a separate, unique App ID from Android. Use delta-cli to create one for iOS:
# Install delta-cli (if not already installed)
npm install -D @zepto-labs/delta-cli
# Create a registry entry for iOS.
yarn delta-cli createRegistry --appId YOUR_IOS_APP_ID --platform ios --appName "YourAppName"Important: Choose a unique
--appIdfor iOS. This value must be different from the Android App ID and must not be reused across platforms.
Add the following keys to your Info.plist, using the above App ID for DeltaAppId:
| Key | Type | Description |
| ---------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| DeltaAppId | String | Your Delta iOS App ID you used (from createRegistry above) |
| DeltaJsVersion | String | Current JS version number (integer starting from 1). Must be unique per native app version — two different app versions cannot share the same value. |
| DeltaServerUrl | String | Base URL of the Delta backend server |
Note:
DeltaBundleVersionis not required — it is inserted automatically by the build scripts.
Build Process
Delta hooks into the native build process to generate manifests and upload bundles automatically.
JS Version
jsVersion identifies the native binary a JavaScript bundle is compatible with. Two different app versions cannot share the same jsVersion. Each native app version (for example, 1.0.0 and 1.1.0) must use its own unique jsVersion value.
Increment jsVersion when you ship a new native app version to the store, or when you make breaking native changes that require a fresh JS bundle baseline. OTA patches and bundle uploads within the same native app version reuse the same jsVersion and only increment bundleVersion.
Configure jsVersion via DELTA_JS_VERSION on Android and DeltaJsVersion in Info.plist on iOS.
Android
The delta.gradle plugin automatically:
- Generates
manifest.json— After the JS bundle is created (duringcreateBundle<Variant>JsAndAssets), it generates a manifest withappId,appVersion,jsVersion,bundleVersion, andhash. - Uploads the bundle — After
assemble<Variant>orbundle<Variant>, it runsdelta-cli createAndroidNativeBundleto upload the bundle to staging.
Important: Delta build tasks only run when the
-PdeltaBuild=trueflag is passed. Without this flag, the gradle plugin is a no-op.
Command Line:
./gradlew assembleRelease -PdeltaBuild=trueor with bundle:
./gradlew bundleRelease -PdeltaBuild=trueAndroid Studio:
Add the flag to your run configuration:
- Go to Run → Edit Configurations
- Select your build configuration
- In the Gradle section, add
-PdeltaBuild=trueto the Command line options field
Alternatively, add it to your project's gradle.properties for always-on Delta builds:
deltaBuild=trueiOS
Two Ruby scripts run as part of the Xcode build phases:
Xcode Scheme Setup
- Open your app scheme in Xcode (Product → Scheme → Edit Scheme…)
- Under Build → Pre-actions, add a Run Script action:
cat "${SRCROOT}/../.env" > "${SRCROOT}/ProdConfig.xcconfig"
if [ "${CONFIGURATION}" = "Release" ]; then
ruby "${SRCROOT}/../node_modules/@zepto-labs/react-native-delta/scripts/ios/update_info_plist.rb"
fi- Under Archive → Post-actions, add a Run Script action:
ruby "${SRCROOT}/../node_modules/@zepto-labs/react-native-delta/scripts/ios/upload_delta_bundle.rb"Note:
update_info_plist.rbruns only for Release builds. The bundle upload runs automatically after you Archive the app, not on regular Run builds.
Delta Config
For CLI operations, create a delta.config.json at your project root:
{
"s3": {
"bucket": "your-delta-bucket",
"region": "your-region"
},
"lambda": {
"region": "your-region",
"getLatestPatch": "<Your Function Name from backend>",
"getReleaseList": "<Your Function Name from backend>",
"createRelease": "<Your Function Name from backend>",
"updateRelease": "<Your Function Name from backend>",
"getRegistryList": "<Your Function Name from backend>",
"createRegistry": "<Your Function Name from backend>",
"invalidateCache": "<Your Function Name from backend>"
}
}API Reference
React Native
Import from the package:
import {
checkUpdate,
getManifestData,
updateUserType,
getUserType,
} from '@zepto-labs/react-native-delta'checkUpdate()
Triggers a background check for available OTA updates.
checkUpdate()| Parameter | Type | Description | | --------- | ---- | ------------- | | — | — | No parameters |
Returns: void
getManifestData()
Returns the cached manifest data as a JSON string containing the current bundle information.
const manifest = await getManifestData()| Parameter | Type | Description | | --------- | ---- | ------------- | | — | — | No parameters |
Returns: Promise<string> — JSON string with manifest data
Response Shape:
{
"jsVersion": 1,
"bundleVersion": 0,
"appVersion": "1.0.0",
"hash": "md5-hash",
"status": "ACTIVE"
}updateUserType(value)
Marks the user as a Delta internal user. Internal users can receive staged/beta rollouts.
updateUserType(true)| Parameter | Type | Default | Description |
| --------- | --------- | ------- | -------------------------------- |
| value | boolean | true | Whether to mark user as internal |
Returns: void
getUserType()
Returns whether the current user is marked as a Delta internal user.
const isInternal = await getUserType()| Parameter | Type | Description | | --------- | ---- | ------------- | | — | — | No parameters |
Returns: Promise<boolean>
Android Native
These methods can be called directly from Kotlin/Java without going through React Native. All methods are accessible via Delta.methodName().
Delta.initialize(delegate)
Initializes the Delta SDK. Must be called before any other Delta method (typically in Application.onCreate). It reads ANDROID_DELTA_ID and DELTA_SERVER_URL from BuildConfig via reflection.
Delta.initialize(this)| Parameter | Type | Description |
| ---------- | ---------------- | -------------------------------------------- |
| delegate | IDeltaDelegate | Delegate implementing the required callbacks |
Delta.getJSBundlePath()
Returns the path to the current JavaScript bundle. Use this in getJSBundleFile() to load Delta-managed bundles.
val bundlePath: String = Delta.getJSBundlePath()Returns: String — file path to the JS bundle
Delta.checkUpdate()
Triggers a background check for available OTA updates. Downloads and applies the update if one is available.
Delta.checkUpdate()Delta.getManifestData()
Returns the cached manifest data as a JSON string. Returns an empty JSON object {} if no valid manifest exists or if the app version has changed.
val manifest: String = Delta.getManifestData()Returns: String — JSON string with manifest data
iOS Native
These methods can be called directly from Objective-C/Swift without going through React Native.
Delta.initialize(_:)
Initializes the Delta SDK. Must be called before any other Delta method (typically in didFinishLaunchingWithOptions). It reads the server URL from Info.plist's DeltaServerUrl key.
Objective-C:
[Delta initialize:self];Swift:
Delta.initialize(self)| Parameter | Type | Description |
| ---------- | ---------------- | ------------------------------------------- |
| delegate | IDeltaDelegate | Delegate implementing the required callback |
Delta.getJSBundlePath()
Returns the URL to the current JavaScript bundle. Use this in bundleURL to load Delta-managed bundles.
Objective-C:
NSURL *bundlePath = [Delta getJSBundlePath];Swift:
let bundlePath: URL? = Delta.getJSBundlePath()Returns: URL? — URL to the JS bundle file
Delta.checkUpdate()
Triggers a background check for available OTA updates.
Objective-C:
[Delta checkUpdate];Swift:
Delta.checkUpdate()Delta.getInternalUser()
Returns whether the current user is marked as a Delta internal user.
Swift:
let isInternal: Bool = Delta.getInternalUser()Returns: Bool
Events
Delta fires lifecycle events through the onEventOccurred delegate callback. Use these events for analytics, debugging, or custom UI feedback.
Delegate Signature
Android (Kotlin):
override fun onEventOccurred(eventName: String, params: JSONObject) {
// eventName: one of the event names below
// params: JSON object with contextual data
}iOS (Objective-C):
- (void)onEventOccurredWithEventName:(NSString *)eventName params:(NSDictionary<NSString *,id> *)params {
// eventName: one of the event names below
// params: dictionary with contextual data
}Event List
| Event Name | Description |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| UPDATE_STATUS | Fired when the update check completes. Params include whether an update is available. |
| GET_UPDATE_REQUEST_SUCCESS | The check-update API call returned successfully. |
| UPDATE_API_FAILED | The check-update API call failed. Params include exception and reason. |
| DOWNLOADING_START | Bundle or patch download has started. Params include status (download_patch, download_bundle, or url_not_found). |
| DOWNLOAD_BUNDLE_REQUEST_SUCCESS | Bundle file downloaded successfully from the server. |
| DOWNLOAD_BUNDLE_FROM_SERVER_FAILED | Bundle download failed. Params include reason. |
| DOWNLOAD_PATCH_REQUEST_SUCCESS | Patch file downloaded successfully from the server. |
| DOWNLOAD_PATCH_FROM_SERVER_FAILED | Patch download failed. Params include reason. |
| UNZIPPED_BUNDLE_STATUS | Bundle unzip completed. Params include status (BUNDLE_UNZIPPED or BUNDLE_UNZIPPED_FAILED). |
| UNZIPPED_PATCH_STATUS | Patch unzip completed. Params include status (PATCH_UNZIPPED or PATCH_UNZIPPED_FAILED). |
| BUNDLE_MD5_STATUS | MD5 verification result for the downloaded bundle. Params include status (md5_matched or md5_not_matched). |
| PATCH_MD5_STATUS | MD5 verification result for the downloaded patch. Params include status (md5_matched or md5_not_matched). |
| PATCH_APPLICATION_STATUS | Patch application result. Params include status (PATCH_APPLIED or PATCH_FAILED). |
| UPDATE_INSTALLED | A new bundle has been successfully installed and will be used on next app launch. |
| UPDATE_CACHE_DATA | Manifest cache has been updated with new bundle information. |
| ROLL_BACK_INITIATED | A rollback has been triggered (server instructed to revert to default bundle). |
| ROLL_BACK_FROM_MANIFEST | Rollback detected from manifest state. Params include from (same_app_version or different_app_version). |
| ALREADY_UPDATE_IN_PROGRESS_EXIST | An update is already being downloaded/applied; skipping this check. |
| MANIFEST_NOT_FOUND | No valid manifest found during check update. |
| SHARED_DEFAULT_BUNDLE | Falling back to the default bundled JS. Params include reason. |
Contributing
See the contributing guide to learn how to contribute to the repository and the development workflow.
Third-Party Licenses
This project depends on the following third-party packages. Their respective licenses apply to those packages.
| Package | Version | License | License Changed? | | ------------------------------------------------------------------------------------------------ | ------- | -------------------------------------------------------- | ---------------- | | @commitlint/config-conventional | 17.0.2 | MIT | No | | @react-native/eslint-config | 0.73.1 | MIT | No | | @types/jest | 29.5.5 | MIT | No | | @types/react | 18.2.44 | MIT | No | | commitlint | 17.0.2 | MIT | No | | del-cli | 5.1.0 | MIT | No | | eslint | 8.51.0 | MIT | No | | jest | 29.7.0 | MIT | No | | lint-staged | 15.2.2 | MIT | No | | prettier | 3.2.5 | MIT | No | | react | 18.2.0 | MIT | No | | react-native | 0.73.6 | MIT | No | | react-native-builder-bob | 0.20.0 | MIT | No | | turbo | 1.10.7 | MPL-2.0 | No | | typescript | 5.2.2 | Apache-2.0 | No |
