Hot Updater
repository·main·Indexed 23 days ago
https://github.com/gronxb/hot-updaterA self-hosted, multi-platform Over-The-Air (OTA) update system for iOS and Android mobile apps. It supports React Native and new architectures, featuring bundle diffing for compact updates and an extensible plugin system for storage and databases. The system includes a server implementation compatible with Elysia.js, Express.js, and Hono, as well as a web-based management console for managing update bundles.
What's inside hot-updater
- Hot Updater is a self-hostable Over-The-Air (OTA) update solution designed for React Native applications. It allows developers to deploy JavaScript bundle updates instantly to users without requiring a new submission to the Apple App Store or Google Play Store.
Key Features of the Expo Plugin
mainThe
@hot-updater/expoplugin provides the following capabilities:- Uses Expo's
expo export:embedcommand for bundling. - Automatically detects Hermes configuration from
app.json. - Supports both managed and bare Expo workflows.
- Compatible with
expo prebuildfor native builds. - Automatically configures the bundler based on your existing Expo settings.
- Uses Expo's
Key Features of the Bare (CLI) Plugin
mainThe
@hot-updater/bareplugin provides the following capabilities:- Metro Bundler Integration: Uses the standard React Native CLI's Metro bundler.
- Hermes Support: Provides automatic Hermes bytecode compilation when
enableHermesis set totrue. - Optimized Minification: Minification is automatically handled by Hermes when enabled (standard minification is disabled in this mode).
- Customization: Supports specifying custom entry files and output directories.
Key features of Hot Updater
mainHot Updater provides several core capabilities for managing mobile app updates:
- Self-Hosting: Complete control over your update infrastructure.
- Multi-Platform: Support for both iOS and Android.
- Web Console: A management interface for overseeing updates.
- Version Control: Robust versioning including semantic versioning support.
- Forced Updates: The ability to push critical updates that users must install.
- Channel Management: Environment separation (e.g.,
dev,staging,production). - Fingerprint Strategy: Automatic checking to ensure updates are compatible with the current native code.
Understand Native Build prerequisites and artifact storage
mainPrerequisites
- A React Native project with
android/andios/directories tracked. hot-updater.config.tsmust define at least one scheme fornativeBuild.androidand/ornativeBuild.ios.- Local platform toolchains installed (
gradlew,xcodebuild, CocoaPods/Bundler,devicectl/simctl). - Run
hot-updater channel set <channel>soHOT_UPDATER_CHANNELis present inInfo.plist/strings.xml. - An active update strategy (
appVersionorfingerprint) configured.
Artifact Storage
By default, artifacts are stored in
.hot-updater/output/build/<platform>/<scheme>. This directory is recreated on every run. If you need to persist older artifacts for CI/CD, use the-oflag to point to a different directory.- A React Native project with
Understand the Project Structure and Database
mainThe project is organized as follows:
src/index.ts: Main server entry point.src/db.ts: Database setup (PGlite + Kysely + Hot Updater schema).src/routes.ts: API route definitions.data/: PGlite database files (stored at./data/hot-updater.db). This directory is gitignored.
Database Details
The server uses PGlite for file-based persistence. The database schema is generated from Hot Updater's versioned schema and is migrated through the Hot Updater CLI. The schema is initialized automatically on the first run.
Understand the Console API and Data Flow
mainThe console integrates with Hot Updater's plugin system via TanStack Start server functions. These functions provide type-safe access to bundle management operations.
Available Server Functions
getConfig(): Load console configuration.getChannels(): List available channels.getBundles(filters): List bundles with pagination.getBundle(bundleId): Get single bundle details.updateBundle(bundleId, data): Update bundle configuration.createBundle(bundle): Create a new bundle.deleteBundle(bundleId): Delete a bundle.
Data Flow Model
- URL State: The
useFilterParams()hook manages filter state via the URL. - Server Functions: TanStack Start server functions call the underlying Hot Updater plugins.
- Data Fetching: React Query (e.g.,
useBundlesQuery()) fetches and caches data. - UI Rendering: Components display data using
shadcn/ui. - Mutations:
useUpdateBundleMutation()performs updates with optimistic UI updates. - Invalidation: React Query automatically refreshes queries after mutations.
Trigger a Force Update
mainTo trigger a force update, set a bundle'sshouldForceUpdatestatus totruein the console (or usenpx hot-updater bundle update <bundle-id> --force-update true -y). Users currently using that bundle will immediately be forced to update to the latest bundle.How Supabase Storage profiles work
mainThe
supabaseStorageplugin implements two distinct storage profiles to handle different parts of the Hot Updater lifecycle:nodeprofile: Used during CLI or Console workflows. It handles uploading, deleting, and downloading files to/from the local filesystem.runtimeprofile: Used by the client application. It creates signed download URLs and reads small metadata files directly through the Supabase Storage API.
Note for Supabase Edge Functions: If you are running in a Supabase Edge Function environment, ensure you use the runtime-specific storage export. This allows update checks to resolve downloads without requiring local filesystem APIs.
Understand Bundle Diffing and Patching
mainHot Updater automatically includes artifacts for bundle diffing with every deployment. If the app runtime supports manifest-based diffing, the client will only download changed files instead of the entire archive.
Hermes bsdiff Patches
If you enable
patch.enabled: truein yourhot-updater.config.ts, the deployment process will also attempt to prepare compatiblebsdiffpatches for the Hermes bundle.Note: Patch generation is best-effort. If a patch fails to generate, the deployment will still succeed using the standard archive update.
How Automatic Rollback works
mainAutomatic rollback is a safety mechanism that prevents a broken Over-The-Air (OTA) bundle from trapping users in a crash loop. Hot Updater manages two roles: a Staging bundle (the newly installed bundle waiting for verification) and a Stable bundle (the last bundle known to start successfully).
The Rollback Lifecycle:
- Install: The new bundle becomes the staging bundle; the previous working bundle is kept as a fallback.
- Launch: Hot Updater attempts to load the staging bundle first.
- Verify: If the app reaches its first successful render, the staging bundle is promoted to the trusted bundle.
- Recover: If the app crashes or exits before successful verification, the staging bundle is marked as failed, and Hot Updater automatically restores the last stable bundle (or the embedded bundle if no stable OTA fallback exists).
To enable this mechanism, you must use
HotUpdater.wrap()or callHotUpdater.init()when your runtime is ready.Control auto-reload behavior with reloadOnForceUpdate
mainThe
reloadOnForceUpdateoption determines if the app automatically reloads after a force update bundle is downloaded.- If
true(default): The app reloads automatically. - If
false: The app will not reload automatically, butshouldForceUpdatewill be returned astruein theonUpdateProcessCompletedcallback, allowing you to trigger a reload manually viaHotUpdater.reload().
// Example without auto-reload export default HotUpdater.wrap({ baseURL: "<your-update-server-url>", updateStrategy: "appVersion", reloadOnForceUpdate: false, // The app won't reload on force updates onUpdateProcessCompleted: ({ status, shouldForceUpdate, id, message }) => { console.log("Bundle updated:", status, shouldForceUpdate, id, message); if (shouldForceUpdate) { // Manually reload if needed await HotUpdater.reload(); } }, })(App);- If