npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-ota-controller

v1.1.0

Published

Native module for handling OTA JS bundle updates in React Native

Readme

react-native-ota-controller

npm version npm downloads platforms typescript portal license

The Official React Native Client SDK for OTALink
Ship JavaScript updates before the store review catches up.


🌐 Powered by OTALink Web Platform

OTALink (https://otalink.metadevzone.com/) is a dedicated release desk and dashboard for React Native teams. Register apps, stage bundles, and activate releases — with a clear record of what is live on every device.

The 3-Step Release Flow:

  1. Register: Add your app’s Android package ID (applicationId) and iOS bundle ID (PRODUCT_BUNDLE_IDENTIFIER) once.
  2. Stage: Upload your bundle archive, review bundle size, native version compatibility, and release notes before anything reaches user devices.
  3. Activate & Track: Activate the release with targeted rollout control, tracking real-time downloads and successful installs directly in your OTALink console.

Get your free workspace API key at otalink.metadevzone.com.


Key Features

  • Single Root Configuration (<OTAProvider />): Configure your API key once at the app root. Zero credentials passed anywhere else.
  • Strict App ID Security Guard: Verifies bundle application ID and package name at runtime to prevent cross-app bundle execution.
  • Native Binary Version Matching: Ensures OTA updates only execute on matching native binaries, preventing native runtime crashes.
  • Store Update Priority (skipOnStoreUpdate): Exposes release flags so your app can easily defer OTA downloads whenever a major native App Store / Google Play update is available.
  • Silent & Interactive Updates: Support for silent background downloads as well as drop-in interactive UI screens with real-time download progress.
  • Automated Install & Download Telemetry: Reports "download" and "install" events to your OTALink dashboard with zero manual tracking code.
  • Smart Blacklisting & Auto-Skip: Isolates incompatible bundles and automatically resets the blacklist upon new store releases.
  • Native Crash Rollback Protection: Automatically reverts to the native binary bundle if consecutive boot failures are detected.
  • Hermes & New Architecture Ready: Compatible with Hermes bytecode bundles, TurboModules, Bridgeless mode, and React Native 0.70+.

1. Installation & Linking

# Using npm
npm install react-native-ota-controller react-native-fs react-native-zip-archive

# Using Yarn
yarn add react-native-ota-controller react-native-fs react-native-zip-archive

# Using pnpm
pnpm add react-native-ota-controller react-native-fs react-native-zip-archive
# iOS only (CocoaPods)
cd ios && pod install && cd ..

Automated Native Setup (CLI)

You can automatically configure your iOS AppDelegate and Android MainApplication using the built-in CLI command:

npx ota setup

(To remove native code modifications, run npx ota unlink).

If you prefer to configure native files manually:

Android (MainApplication.kt)

import com.otacontroller.OTAController

// React Native 0.76+ (reactHost):
override val reactHost: ReactHost by lazy {
  getDefaultReactHost(
    context = applicationContext,
    packageList = PackageList(this).packages,
    jsBundleFilePath = OTAController.resolveBundlePath(applicationContext)
  )
}

// Legacy ReactNativeHost:
override fun getJSBundleFile(): String? =
  OTAController.resolveBundlePath(applicationContext)

iOS (AppDelegate.swift)

import OtaController

override func bundleURL() -> URL? {
#if DEBUG
  RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
  if let otaURL = OTAController.resolveBundlePath() {
    return otaURL
  }
  return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}

Release Mode Requirement: OTA bundle loading executes only in Release builds (APK, AAB, TestFlight, Production). In Debug mode, React Native automatically routes to your local Metro Bundler.


2. SDK Integration (Zero Complexity)

Step 1: Initialize in App.tsx / App.js

Mount <OTAProvider /> once at your root. Pass your API key from otalink.metadevzone.com.

You can use it either as a standalone tag inside a Fragment or as a wrapper:

Option A: Standalone Tag inside Fragment (Recommended)

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { OTAProvider } from 'react-native-ota-controller';

export default function App() {
  return (
    <>
      <NavigationContainer>
        {/* Your screens and navigation */}
      </NavigationContainer>

      {/* Initialize OTALink SDK once with your portal key & target channel */}
      <OTAProvider
        apiKey="ota_live_YOUR_APP_KEY"
        channel="production" // "production" | "development" (defaults to "production")
      />
    </>
  );
}

Option B: Wrapper Syntax

export default function App() {
  return (
    <OTAProvider
      apiKey="ota_live_YOUR_APP_KEY"
      channel="production" // "production" | "development"
    >
      <NavigationContainer>
        {/* Your screens and navigation */}
      </NavigationContainer>
    </OTAProvider>
  );
}

What <OTAProvider /> does on mount:

  • Sets credentials globally across the entire app.
  • Confirms native startup to native boot-guard timers.
  • Automatically reports the "install" telemetry event to your OTALink console if the app just restarted with a new OTA update.
  • Automatically cleans up older, inactive bundle directories on disk.

Step 2: Check for Updates in Splash Screen

Anywhere in your app, call OTA.checkForUpdate() with zero parameters. It automatically detects the platform, app ID, current OTA version, and uses the API key configured at root:

import { OTA } from 'react-native-ota-controller';
import { checkAppUpdate } from './functions/checkAppUpdate'; // Your store version checker

async function checkUpdates() {
  // Check store & OTA in parallel
  const [storeCheck, otaCheck] = await Promise.all([
    checkAppUpdate(),
    OTA.checkForUpdate(), // Zero arguments needed!
  ]);

  if (otaCheck?.updateAvailable && otaCheck?.release) {
    const release = otaCheck.release;

    // 1. Manage skipOnStoreUpdate: If store update is needed, prioritize store!
    if (release.skipOnStoreUpdate && storeCheck?.needed) {
      refUpdateModal.current?.show({
        url: storeCheck.storeUrl,
        force: true,
      });
      return; // Stop: Do not download OTA update
    }

    // 2. Silent background update
    if (release.updateSilently) {
      OTA.downloadAndApplyUpdate({
        release,
        autoRestart: release.autoRestart, // Respects portal autoRestart setting
      });
      return;
    }

    // 3. Interactive update -> navigate to OTAScreen
    navigation.replace('OTAScreen', { release });
  }
}

Step 3: Interactive Progress Screen (<OTAController />)

On your animated OTA screen (e.g. OTAScreen.js), drop in <OTAController />. Back to clean v1.0.3 simplicity: takes release object, downloads with real-time progress, and auto-restarts. Zero duplicate network check:

import React from 'react';
import { View, Text } from 'react-native';
import { OTAController } from 'react-native-ota-controller';

export default function OTAScreen({ route }) {
  const { release } = route.params || {};
  const [progress, setProgress] = React.useState(0);
  const [status, setStatus] = React.useState('checking');

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Updating App: {progress}%</Text>
      <Text>Status: {status}</Text>

      <OTAController
        release={release}
        autoRestart={true}
        onProgress={({ percentage, downloadedMB, totalMB }) => {
          setProgress(percentage);
        }}
        onStateChange={(state) => {
          setStatus(state); // 'idle' | 'checking' | 'downloading' | 'downloaded' | 'installed' | 'failed'
        }}
        onError={(err) => {
          console.warn('OTA Error:', err.message);
        }}
      />
    </View>
  );
}

(Also supports legacy callbacks={{ onProgress, onStateChange, onError }}).


3. Creating & Staging Bundles (CLI)

Build your release bundles directly from your project root:

# Auto-detects target channel directly from <OTAProvider channel={...} /> in your app:
npx ota build

# Platform-specific builds
npx ota build android
npx ota build ios

# Explicit channel override ("production" | "development")
npx ota build android --channel development
npx ota build android --channel production

The CLI outputs zip archives ready to stage in otalink.metadevzone.com:

  • ota-dist/bundle-android-<channel>(<appVersion>-<otaVersion>).zip (e.g. bundle-android-production(1.0.7-1).zip)
  • ota-dist/bundle-ios-<channel>(<appVersion>-<otaVersion>).zip (e.g. bundle-ios-production(1.0.7-1).zip)

Staging to OTALink:

  1. Open your app workspace at otalink.metadevzone.com.
  2. Click Stage Bundle and select the .zip from ota-dist/.
  3. Fill in release notes and verify native version compatibility.
  4. Click Activate to roll out the update to users!

The CLI automatically isolates and maintains independent build counters for production and development channels in .ota-version.json:

{
  "production": {
    "android": {
      "appVersion": "1.0.7",
      "otaVersion": 1,
      "updatedAt": "2026-09-03T18:00:00.000Z"
    }
  },
  "development": {
    "android": {
      "appVersion": "1.0.7",
      "otaVersion": 5,
      "updatedAt": "2026-09-03T18:30:00.000Z"
    }
  }
}

To set an explicit sequence number for a channel:

npx ota build android --channel development --ota-version 5

4. Complete API Reference

<OTAProvider />

Root configuration component. Mount once in App.tsx.

| Prop | Type | Required | Description | | :--- | :--- | :---: | :--- | | apiKey | string | Yes | App API key obtained from otalink.metadevzone.com. | | channel | 'development' \| 'production' | No | Target deployment channel. Defaults to 'production'. | | disableInDev | boolean | No | If true, skips update checks in __DEV__ mode. Defaults to false. | | children | ReactNode | No | Optional child elements if used as a wrapper. |


OTA.checkForUpdate(options?)

Queries the OTALink check-update endpoint (POST /api/ota/public/check-update). Automatically reads the apiKey and channel from <OTAProvider />.

const result = await OTA.checkForUpdate({
  channel: 'production', // Optional override: 'development' | 'production'
});

Outgoing POST Request Payload:

{
  "bundleId": "com.yourcompany.app",
  "platform": "android",
  "version_no": "1.0.7",
  "build_no": 0,
  "channel": "production"
}

Returns Promise<OTACheckUpdateResult>:

| Field | Type | Description | | :--- | :--- | :--- | | updateAvailable | boolean | true if a newer active OTA release is ready for this device. | | release | OTAReleaseInfo | Metadata for the active release (see table below). | | isBlackList | boolean | true if the release bundleUrl was previously rejected/blacklisted on this device. | | currentOtaVersion | number | Running OTA version on device. | | skipOnStoreUpdate | boolean | true if the release specifies prioritizing store updates. | | updateSilently | boolean | true if the release should be downloaded quietly in the background. | | autoRestart | boolean | true if the app should reload immediately upon download completion. | | reason | string | Reason if no update available (e.g. "no_active_release", "native_version_too_low"). |

release Object Properties:

| Property | Type | Description | | :--- | :--- | :--- | | id | string | Unique release ID on the OTALink platform. | | platform | 'android' \| 'ios' | Target platform for this release. | | appVersion | string | Native app version targeting this release (e.g. "1.0.7"). | | buildNumber | number | Native build number targeting this release. | | bundleUrl | string | Secure S3/CDN URL to download the update ZIP. | | bundleSizeBytes | number | Exact file size of the bundle ZIP in bytes. | | skipOnStoreUpdate | boolean | true if store update takes priority. | | updateSilently | boolean | true if update should install without interactive screen. | | autoRestart | boolean | true if update should restart JS automatically. | | publishedAt | string | ISO timestamp when release was activated. |


OTA.downloadAndApplyUpdate(options)

Downloads, unpacks into staging, validates meta.json, promotes to active bundle directory, and optionally restarts.

await OTA.downloadAndApplyUpdate({
  release,
  autoRestart: false,
  onProgress: (payload) => console.log(payload.percentage),
  onError: (err) => console.error(err.code, err.message),
});

| Option | Type | Required | Description | | :--- | :--- | :---: | :--- | | release | OTAReleaseInfo | No (defaults to cached) | Target release from checkForUpdate(). | | autoRestart | boolean | No | If true, restarts JS bundle automatically after 3.5s. Default: false. | | onProgress | Function | No | Callback receiving OTAProgressPayload. | | onError | Function | No | Callback receiving OTAErrorPayload. |

OTAProgressPayload:

interface OTAProgressPayload {
  downloadedBytes: number;
  totalBytes: number;
  percentage: number;
  downloadedMB: string;   // e.g. "2.40"
  totalMB: string;        // e.g. "4.80"
  status: 'idle' | 'checking' | 'downloading' | 'downloaded' | 'installed' | 'failed';
}

<OTAController />

Declarative headless component for progress screens (1.0.3 simplicity). Zero duplicate network check.

| Prop | Type | Default | Description | | :--- | :--- | :---: | :--- | | release | OTAReleaseInfo | (cached) | Target release metadata from checkForUpdate(). | | autoRestart | boolean | true | Whether to automatically reload JS after installation. | | onProgress | Function | - | Emits { downloaded, fullSize, percentage, downloadedMB, totalMB }. | | onStateChange | Function | - | Emits state string ('checking', 'downloading', 'downloaded', 'installed', 'failed'). | | onError | Function | - | Emits OTAErrorPayload on download or extraction failure. | | callbacks | Object | - | Legacy container object { onProgress, onStateChange, onError }. |


Native Helpers

import {
  getAppVersion,
  getOtaVersion,
  getAppId,
  restartApp,
} from 'react-native-ota-controller';

getAppVersion(); // string: native binary version (e.g. "1.0.0")
getOtaVersion(); // number: running OTA version (0 if running binary bundle)
getAppId();      // string: native package/bundle identifier (e.g. "com.company.app")
restartApp();    // triggers immediate in-place JS bundle reload

5. Error Codes Reference

| Error Code | Description | | :--- | :--- | | API_KEY_MISSING | No apiKey provided. Configure <OTAProvider apiKey="..." /> in App.tsx. | | UNAUTHORIZED | Invalid app API key (HTTP 401). Verify key in otalink.metadevzone.com. | | FORBIDDEN | App bundle ID mismatch or app inactive (HTTP 403). Check registered package ID. | | NETWORK_ERROR | Device offline, connection timeout, or server HTTP 500 error. | | DOWNLOAD_FAILED | ZIP download failed or returned non-200 status code. | | EXTRACTION_FAILED | ZIP archive is corrupted or could not be decompressed. | | INVALID_META | Missing or invalid meta.json inside update archive. | | APP_ID_MISMATCH | Bundle was compiled for a different package ID than the running binary. | | APP_VERSION_MISMATCH| Bundle was compiled for a different native version than the running binary. | | UPDATE_BLACKLISTED | Bundle previously failed extraction or validation; skipped to save bandwidth. | | ALREADY_IN_PROGRESS | Another download or extraction operation is currently active. | | STORAGE_ERROR | Failed writing current.json or promoting bundle directory. |


6. Architecture & Reliability Guarantees

  • Application ID Isolation: Every bundle's embedded meta.json must match the running application identifier. Cross-app bundle execution is impossible.
  • Native Version Matching: Ensures bundles compiled for native binary version 1.0.0 will never execute on 1.1.0.
  • Automated Blacklisting: If an archive is corrupt or fails metadata validation, it is blacklisted to prevent infinite download loops. The blacklist automatically clears when a user updates via Google Play or App Store.
  • Crash Recovery & Rollback: If consecutive boot crashes occur after an update, the module rolls back to the bundled asset automatically.
  • Zero-Overhead Debug Bypass: In debug builds, all native hooks automatically route to Metro bundler.

Frequently Asked Questions

Yes. Both Apple App Store Review Guidelines (Section 2.5.2) and Google Play Store policies permit Over-The-Air JavaScript updates, provided the updates do not alter the primary purpose or core functionality of the application.

Yes. The CLI generates optimized Hermes bytecode (.hbc) when Hermes is enabled in your project, ensuring instant startup identical to native release binaries.

Create a free workspace at https://otalink.metadevzone.com/. Free workspaces include 1 organization, 5 apps, and 3 teammates with no credit card required.

Yes. react-native-ota-controller is purpose-built for Bare React Native projects, supporting React Native 0.70 through the latest New Architecture releases.


License

MIT © metadevzone