react-native-github-ota
v1.1.1
Published
GitHub-based OTA updates for React Native / Expo apps. Downloads JS bundles from GitHub Releases and applies them on next restart.
Downloads
273
Maintainers
Readme
react-native-github-ota
GitHub-based OTA (Over-The-Air) updates for React Native / Expo apps.
Downloads JS bundles from a GitHub Release (ota-latest tag) and applies them on next app restart — no app-store review needed.
How It Works
- A GitHub Action builds your JS bundle on every push to
mainand uploads it to a GitHub Release taggedota-latest. - This library compares the current build commit with the remote branch using the GitHub API.
- When an update is available, it downloads the bundle to the device and prompts the user to restart.
- On next launch, your native bootstrap code loads the downloaded bundle instead of the built-in one.
Installation
npm install react-native-github-ota
# or
yarn add react-native-github-otaPeer Dependencies
Make sure these are installed in your project:
expo-file-system(>= 16)react(>= 18)react-native(>= 0.72)
Quick Start
Run the init command to set everything up automatically:
npx github-ota-initThis will:
- Copy the GitHub Actions workflow into
.github/workflows/ota-bundle.yml - Add
embed-commitandprebuildscripts to yourpackage.json - Add the config plugin to your
app.json— native code is applied automatically onexpo prebuild - For bare RN projects (no Expo): patches
MainApplication.ktdirectly
After running init, rebuild your native project:
npx expo prebuild --cleanNative Code — Handled Automatically
The library ships an Expo Config Plugin that automatically patches MainApplication.kt during expo prebuild. No manual native code changes needed.
For Expo projects: Just add the plugin to your app.json (the init command does this for you):
{
"expo": {
"plugins": ["react-native-github-ota"]
}
}For bare React Native projects: The init command patches MainApplication.kt directly. If you need to do it manually, add this:
import java.io.File
// Inside your DefaultReactNativeHost object, add:
override fun getJSBundleFile(): String? {
val otaBundle = File(applicationContext.filesDir, "ota/index.android.bundle")
if (!otaBundle.exists()) return null
// If the APK was updated after the OTA bundle was downloaded, the OTA is stale
try {
val appInfo = applicationContext.packageManager.getPackageInfo(applicationContext.packageName, 0)
if (appInfo.lastUpdateTime > otaBundle.lastModified()) {
otaBundle.delete()
File(applicationContext.filesDir, "ota/meta.json").delete()
return null
}
} catch (_: Exception) {}
return otaBundle.absolutePath
}This tells React Native: "if an OTA bundle exists on disk and is newer than the installed APK, load it; otherwise use the built-in one." This prevents stale OTA bundles from overriding a newer rebuild.
Manual Setup
If you prefer to set things up manually:
1. Embed Build Info (required)
Before each build, run the embed-commit script to stamp the current git SHA into your app:
node node_modules/react-native-github-ota/scripts/embed-commit.jsOr add it to your build scripts:
{
"scripts": {
"embed-commit": "node node_modules/react-native-github-ota/scripts/embed-commit.js",
"prebuild": "node node_modules/react-native-github-ota/scripts/embed-commit.js && npx expo prebuild"
}
}The script creates/updates a constants/buildInfo.ts file in your project root.
2. Add the GitHub Actions Workflow
Copy the workflow template to your project:
mkdir -p .github/workflows
cp node_modules/react-native-github-ota/workflow/ota-bundle.yml .github/workflows/Or create .github/workflows/ota-bundle.yml manually — see workflow/ota-bundle.yml for the full template.
What the workflow does:
- Triggers on every push to
main - Installs dependencies and embeds the commit hash
- Runs
react-native bundleto create the JS bundle - Creates an
ota-manifest.jsonwith commit metadata - Uploads both files to a GitHub Release tagged
ota-latest
Note: The workflow uses
GITHUB_TOKENwhich is automatically provided by GitHub Actions — no extra secrets needed.
Usage
Configure
Call configureOta once at app startup (e.g. in your root layout):
import { configureOta } from "react-native-github-ota";
configureOta({
owner: "your-github-username",
repo: "your-repo-name",
branch: "main", // optional, default "main"
releaseTag: "ota-latest", // optional, default "ota-latest"
bundleFileName: "index.android.bundle", // optional
});Hook
import { useGithubOta } from "react-native-github-ota";
import { BUILD_INFO } from "./constants/buildInfo";
function MyComponent() {
const {
status,
error,
updateInfo,
isChecking,
isDownloading,
checkUpdate,
downloadAndApplyUpdate,
buildInfo,
} = useGithubOta({
autoCheckOnMount: true,
buildInfo: BUILD_INFO,
// Optional: control auto-check with your own settings
shouldAutoCheck: async () => {
// e.g. read from AsyncStorage
return true;
},
});
// ...
}Pre-built Banner
import { OtaUpdateBanner } from "react-native-github-ota";
<OtaUpdateBanner
visible={bannerVisible}
status={status}
error={error}
updateInfo={updateInfo}
isDownloading={isDownloading}
onDownload={downloadAndApplyUpdate}
onClose={() => setBannerVisible(false)}
// Optional theme overrides:
colors={{
surface: "#1a1a1a",
text: "#ffffff",
textSecondary: "#a1a1aa",
primary: "#3b82f6",
danger: "#ef4444",
border: "#27272a",
}}
/>;API
configureOta(config)
| Option | Type | Default | Description |
| ----------------- | -------- | ------------------------ | ------------------------------------- |
| owner | string | required | GitHub repository owner |
| repo | string | required | GitHub repository name |
| branch | string | "main" | Branch to compare against |
| releaseTag | string | "ota-latest" | Release tag containing the OTA bundle |
| bundleFileName | string | "index.android.bundle" | Bundle asset filename in the release |
| autoSettingsKey | string | "@github_ota_settings" | AsyncStorage key for settings |
useGithubOta(options)
| Option | Type | Default | Description |
| ------------------ | ----------------------------------- | ------------ | ----------------------------------------- |
| buildInfo | BuildInfo | required | Build info from embed-commit script |
| autoCheckOnMount | boolean | true | Auto-check for updates on mount |
| shouldAutoCheck | () => boolean \| Promise<boolean> | — | Guard for auto-check (e.g. user settings) |
Returns:
| Field | Type | Description |
| ------------------------ | ----------------------------- | ---------------------------------- |
| status | string \| null | Current status message |
| error | string \| null | Error message if any |
| isAvailable | boolean | Whether an update is available |
| isChecking | boolean | Currently checking for updates |
| isDownloading | boolean | Currently downloading |
| downloadProgress | number | Download progress (0-100) |
| updateInfo | UpdateInfo \| null | Details about the available update |
| lastChecked | number \| null | Timestamp of last check |
| checkUpdate | (manual?: boolean) => void | Trigger an update check |
| downloadAndApplyUpdate | () => void | Download and install the update |
| setStatus | (s: string \| null) => void | Override status message |
| buildInfo | BuildInfo | The build info passed in |
OtaUpdateBanner
A ready-made banner component. Accepts theme colors for dark/light mode support.
npx github-ota-init
CLI command that scaffolds the GitHub Actions workflow, build scripts, config plugin, and native setup into your project. For Expo projects it adds the config plugin to app.json; for bare RN projects it patches MainApplication.kt directly.
License
MIT
