@otakit/capacitor-updater
v2.1.2
Published
Capacitor plugin for OTA updates
Maintainers
Readme
@otakit/capacitor-updater
Capacitor OTA updater plugin for OtaKit.
What it does
- fetches the latest manifest for its release lane from the CDN
- downloads and verifies OTA bundles
- stages updates safely
- applies staged bundles on cold start, resume, or manual command
- uses
notifyAppReady()as the health handshake - rolls back automatically if a newly applied bundle does not prove healthy
The plugin is intentionally small:
- three automatic lifecycle entry points: runtime, launch, resume
- one shared set of update primitives: check, download, apply
- one rollback safety loop
There is no built-in splash or overlay manager in this model.
Hosted config
plugins: {
OtaKit: {
appId: "YOUR_OTAKIT_APP_ID",
appReadyTimeout: 10000,
// Optional:
// channel: "staging",
// runtimeVersion: "2026.04",
// launchPolicy: "apply-staged",
// resumePolicy: "shadow",
// runtimePolicy: "immediate",
// checkInterval: 600000,
}
}Advanced overrides for self-hosting or custom trust only:
cdnUrlingestUrlserverUrlmanifestKeysallowInsecureUrls
Hosted OtaKit already points at the managed ingest service and CDN and already trusts the managed manifest signing keys.
Policies
The plugin has three automatic events:
runtimeCold start where the currentruntimeVersionlane has not been resolved yet.launchNormal cold start after runtime is already resolved.resumeApp returning from background.
Each event uses the same policy names:
type OtaKitPolicy = 'off' | 'shadow' | 'apply-staged' | 'immediate';Semantics:
shadowcheck + download, never apply on that eventapply-stagedapply a staged bundle if one already exists, otherwise behave likeshadowimmediatecheck + download + apply
Recommended defaults:
launchPolicy = 'apply-staged';
resumePolicy = 'shadow';
runtimePolicy = 'immediate';That means:
- fresh install or new
runtimeVersion: catch up immediately - later cold starts: apply already staged content if present, otherwise download the next update in the background
- resumes: periodically check and stage in the background
If an app wants full JS control:
launchPolicy = 'off';
resumePolicy = 'off';
runtimePolicy = 'off';Check interval
checkInterval defaults to 10 minutes and only applies to background resume
checks:
resumePolicy: "shadow"resumePolicy: "apply-staged"when there is no staged bundle to apply
Set checkInterval to 0 or a negative value to disable resume throttling.
It does not throttle:
- launch handling
- runtime handling
immediate- manual JS APIs
Runtime model
The plugin keeps three important pointers:
currentthe bundle the WebView is serving nowfallbackthe last known-good bundle used for rollbackstageda downloaded bundle waiting to be activated
Bundle lifecycle:
download -> pending -> trial -> success
|
+-> error -> rollbackIf a bundle is applied and never calls notifyAppReady():
- timeout triggers rollback while the app is running
- or the next cold start detects the still-trial bundle and rolls back before boot continues
The last failed applied bundle is persisted so the plugin does not immediately download and apply the same broken release again.
Automatic flow
For the normal hosted path, most apps only need:
await OtaKit.notifyAppReady();The plugin handles checking, staging, applying, rollback, and runtime-lane catch-up based on the configured policies.
Loading screen recommendation
OtaKit does not manage a splash screen or loading overlay for you.
Recommended startup order:
- keep a native splash screen or fullscreen loading view visible
- finish your normal app bootstrap
- call
notifyAppReady() - hide the splash screen or loading view
For React and Next.js apps, treat this as part of the default setup, not an optional polish step.
Manual APIs
The manual surface maps to the same internal engine:
const state = await OtaKit.getState();
const check = await OtaKit.check();
const download = await OtaKit.download();
await OtaKit.notifyAppReady();check() returns:
type CheckResult =
| { kind: 'no_update' }
| { kind: 'already_staged'; latest: LatestVersion }
| { kind: 'update_available'; latest: LatestVersion };download() returns:
type DownloadResult = { kind: 'no_update' } | { kind: 'staged'; bundle: BundleInfo };update() uses the same native immediate-flow operation as automatic
"immediate" policies:
await OtaKit.update();That keeps the manual convenience path atomic inside the native plugin instead
of splitting it into separate download() and apply() calls.
apply() and successful update() are terminal operations:
- on success they reload the WebView
- they do not resolve back into the old JS context
- call
notifyAppReady()from normal startup after the reloaded app boots
There is no listener/event API in this refactor. If an app later needs a smaller reactive surface, that can be added intentionally.
Example manual flow
const check = await OtaKit.check();
if (check.kind === 'update_available') {
const result = await OtaKit.download();
if (result.kind === 'staged') {
await OtaKit.apply();
}
}Or use the one-shot helper:
await OtaKit.update();After the app reloads and starts again, call:
await OtaKit.notifyAppReady();Compatibility lanes
channelanswers "who should get this rollout?"runtimeVersionanswers "which native app shell can safely run this bundle?"
Use channels for rollout tracks such as beta, staging, or production.
Use runtimeVersion when a new store build creates a new compatibility
boundary and you do not want devices on that native shell to keep receiving
older OTA bundles.
Trust model
The plugin does not just download arbitrary zips from a URL.
- it fetches the latest manifest for the current app + channel + runtime lane
- it verifies the manifest signature when keys are configured
- it compares the manifest with current, staged, and last-failed local state
- it downloads only when a newer usable bundle exists
- it verifies the zip against the manifest
sha256 - it stages and later applies the bundle
Source areas
src/definitions.ts: public types and configsrc/index.ts: Capacitor registration and JS wrappersrc/web.ts: web fallback implementationios/Sources/UpdaterPlugin/*: iOS implementationandroid/src/main/java/com/otakit/updater/*: Android implementation
