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

relay-ota-react-native

v0.1.2

Published

Relay OTA SDK for React Native — ship updates without app store review

Readme

relay-ota-react-native

Over-the-air updates for React Native — ship JS bundle changes instantly, without App Store review.

Drop-in replacement for the retired Microsoft CodePush and an alternative to Expo EAS Updates.

npm version npm downloads GitHub stars

Website: relayota.com · Docs: relayota.com/docs · GitHub: relay-ota-react-native


What is Relay OTA?

Relay OTA lets you push JavaScript bundle updates directly to your users' devices — no App Store or Play Store review needed.

  • Zero review wait — critical fixes reach users in seconds
  • One-click rollback — bad update? revert instantly from the dashboard
  • Channels — separate production and staging environments
  • Staged rollouts — release to a percentage of users before going 100%
  • Mandatory updates — auto-apply security patches without user interaction
  • Analytics — track check, download, and apply rates per release

Works with Expo managed workflow and bare React Native (0.72+).


Installation

npm install relay-ota-react-native @react-native-async-storage/async-storage

Expo projects

npx expo install expo-file-system

Bare React Native projects

npm install react-native-fs
cd ios && pod install

Quick start — Expo

import {
  OtaProvider,
  useOtaUpdate,
  createExpoFileSystemAdapter,
} from "relay-ota-react-native";

const fs = createExpoFileSystemAdapter();

export default function App() {
  return (
    <OtaProvider
      fileSystem={fs}
      config={{
        serverUrl: "https://ota.yourcompany.com", // your Relay OTA server URL
        appId: "YOUR_APP_ID",
        channel: "production",
        platform: "IOS", // or 'ANDROID'
        currentVersion: "1.0.0",
        runtimeVersion: "1.0.0",
      }}
    >
      <YourNavigator />
    </OtaProvider>
  );
}

Show an update prompt anywhere in your app:

import { useOtaUpdate } from "relay-ota-react-native";
import { View, Text, Button } from "react-native";

export function UpdateBanner() {
  const {
    hasUpdate,
    isReady,
    isLoading,
    progress,
    downloadUpdate,
    applyUpdate,
  } = useOtaUpdate();

  if (hasUpdate)
    return (
      <View>
        <Text>New version available</Text>
        <Button title="Download" onPress={downloadUpdate} />
      </View>
    );

  if (isLoading) return <Text>Downloading… {Math.round(progress * 100)}%</Text>;

  if (isReady)
    return (
      <View>
        <Text>Update ready</Text>
        <Button title="Restart now" onPress={applyUpdate} />
      </View>
    );

  return null;
}

Quick start — Bare React Native

import {
  OtaProvider,
  createRNFSFileSystemAdapter,
} from "relay-ota-react-native";

const fs = createRNFSFileSystemAdapter();

export default function App() {
  return (
    <OtaProvider
      fileSystem={fs}
      config={{
        serverUrl: "https://ota.yourcompany.com",
        appId: "YOUR_APP_ID",
        channel: "production",
        platform: "ANDROID",
        currentVersion: "1.0.0",
        runtimeVersion: "1.0.0",
      }}
    >
      <YourNavigator />
    </OtaProvider>
  );
}

Bare React Native requires the OtaBundleUpdater native module — see Native module setup below.


OtaProvider config

| Prop | Type | Required | Description | | ------------------- | ------------------------ | -------- | ----------------------------------------------------------- | | serverUrl | string | ✅ | Base URL of your Relay OTA server | | appId | string | ✅ | App ID from the OTA dashboard | | channel | string | ✅ | Release channel — e.g. 'production', 'staging' | | platform | 'IOS' \| 'ANDROID' | ✅ | Target platform | | currentVersion | string | ✅ | Current JS bundle version | | runtimeVersion | string | ✅ | Native runtime version — must match the release | | checkOnForeground | boolean | — | Auto-check when app returns to foreground (default: true) | | onUpdate | (release) => void | — | Called when a new update is found | | onError | (error: Error) => void | — | Called on check/download errors |


Hooks

useOtaUpdate

Full update state and actions:

const {
  status, // UpdateStatus
  progress, // number 0–100 during download
  release, // release metadata or null
  error, // Error | null
  localBundlePath, // path to downloaded bundle or null

  isLoading, // checking or downloading
  hasUpdate, // status === 'update_available'
  isReady, // status === 'ready'

  checkForUpdates, // () => Promise<void>
  downloadUpdate, // () => Promise<void>
  applyUpdate, // () => Promise<void> — reloads the app
  rollback, // () => Promise<void> — removes staged bundle, reloads
} = useOtaUpdate();

UpdateStatus values

| Status | Meaning | | ------------------ | --------------------------------------- | | idle | No check has run yet | | checking | Checking for an update | | up_to_date | No update available | | update_available | Update found, not downloaded | | downloading | Download in progress | | ready | Downloaded, waiting for applyUpdate() | | error | Last operation failed — see error |


useAutoUpdate

Automatically downloads mandatory updates. Optionally applies on next foreground.

import { useAutoUpdate } from "relay-ota-react-native";

const { isReady, applyUpdate } = useAutoUpdate({ applyImmediately: false });

| Option | Type | Default | Description | | ------------------ | --------- | ------- | ---------------------------------------------- | | applyImmediately | boolean | false | Auto-call applyUpdate() when bundle is ready |


Rollback

const { rollback } = useOtaUpdate();

// Clears staged bundle and reloads — app falls back to bundled JS
<Button title="Rollback" onPress={rollback} />;

Channels

// Staging builds
config={{ ..., channel: 'staging' }}

// Production builds
config={{ ..., channel: 'production' }}

Releases are scoped per-channel on the server — staging users never receive production releases.


Push a release (CLI)

# Build the JS bundle
npx react-native bundle \
  --platform ios \
  --dev false \
  --bundle-output ./main.jsbundle

# Push to your OTA server
ota release push \
  --channel production \
  --bundle ./main.jsbundle

Native module setup (bare React Native only)

Expo managed workflow: skip — the Expo filesystem adapter handles everything.

iOS

  1. Copy OtaBundleUpdater.h and OtaBundleUpdater.m from node_modules/relay-ota-react-native/src/native/ into your Xcode project
  2. Run pod install

Android

  1. Copy OtaBundleUpdaterModule.kt and OtaBundleUpdaterPackage.kt into android/app/src/main/java/<your-package>/
  2. Register in MainApplication.kt:
override fun getPackages(): List<ReactPackage> =
  PackageList(this).packages.apply {
    add(OtaBundleUpdaterPackage())
  }

TypeScript types

import type {
  OtaConfig,
  UpdateStatus,
  UpdateState,
  IFileSystemAdapter,
  INativeBundleUpdater,
  Platform,
} from "relay-ota-react-native";

OtaConfig

interface OtaConfig {
  serverUrl: string;
  appId: string;
  channel: string;
  platform: "IOS" | "ANDROID";
  currentVersion: string;
  runtimeVersion: string;
  checkOnForeground?: boolean;
  onUpdate?: (release: Release) => void;
  onError?: (error: Error) => void;
}

IFileSystemAdapter

Implement this to bring your own filesystem layer:

interface IFileSystemAdapter {
  getDocumentDirectory(): string;
  exists(path: string): Promise<boolean>;
  readAsString(path: string, encoding: "utf8" | "base64"): Promise<string>;
  writeAsString(
    path: string,
    content: string,
    encoding: "utf8" | "base64",
  ): Promise<void>;
  deleteFile(path: string): Promise<void>;
  makeDirectory(path: string): Promise<void>;
}

Low-level API

import {
  checkForUpdate, // (serverUrl, request) => Promise<OtaCheckResponse>
  downloadBundle, // (release, fs, onProgress) => Promise<string>
  OtaUpdater, // Class — manages the full state machine
} from "relay-ota-react-native";

Migrating from CodePush

Microsoft retired CodePush in 2025. Relay OTA uses the same update model and a compatible SDK surface. Key differences:

| | CodePush | Relay OTA | | --------------- | ------------------ | ------------------ | | Hosting | Vendor (shut down) | Relay OTA platform | | Rollback | Yes | Yes | | Analytics | Basic | Built-in, detailed | | Staged rollouts | Yes | Yes |


Links