Nitro SDK
@nitropush/react-native is the device-side library that ships in your React Native or Expo app. The whole core is plain Swift + Kotlin — the JS surface (via Nitro Modules) is a thin wrapper. That means every operation works in three runtimes:
- From JS —
configureWith({...})returns aNitroPushClient; you callclient.checkForUpdate()/client.notifyAppReady()/ etc. on the returned client.sync(client, ...)is a one-call helper that wraps check → download → install. - From Swift —
NitroPushSdk.sharedexposed as apublic final class. Use it for pre-JS downloads, background fetches viaBGTaskScheduler, or recovering from a broken JS bundle. - From Kotlin —
NitroPushSdk.shared(afterNitroPushSdk.install(this)in yourMainApplication). Same role: background updates viaWorkManager, native splash-screen update flows, etc.
What it does
- Update checks — polls NitroPush for new bundles.
- Downloads — streams bundle + assets straight from your storage bucket, verifies SHA-256.
- Installs — flips the JS bundle pointer at the moment you choose (immediate / next restart / next resume / next suspend).
- Rollback safety net — if
notifyAppReady()doesn’t run on a fresh install, the next launch falls back to the previous bundle. - Native analytics — download / install / rollback events fire from Swift + Kotlin, never the JS thread, so you see the truth even when a bundle crashes on cold start.
- Toggleable debug logs —
NitroPushSdk.shared.setEnableLogs(true)from native turns on per-action traces (taggedNitroPush) for the whole SDK lifecycle.
The public surface
The Swift singleton. ~14 public methods on NitroPushSdk.shared.
import NitroPush
NitroPushSdk.shared.configure(_:) // or configFromInfoPlist()
NitroPushSdk.shared.setEnableLogs(_:) // debug log toggle
NitroPushSdk.shared.checkForUpdate() // async
NitroPushSdk.shared.downloadUpdate(_:) // async
NitroPushSdk.shared.installUpdate(_:_:_:) // async
NitroPushSdk.shared.notifyAppReady()
NitroPushSdk.shared.restartApp(onlyIfUpdateIsPending:)
NitroPushSdk.shared.rollback(releaseId:) // throw on no-previous
NitroPushSdk.shared.getCurrentPackage()
NitroPushSdk.shared.getPendingPackage()
NitroPushSdk.shared.clearPendingUpdate()
NitroPushSdk.shared.clearUpdates()
NitroPushSdk.shared.activeBundleURL() // for AppDelegate.bundleURL()
NitroPushSdk.shared.addDownloadProgressListener(_:) The Kotlin singleton. Same shape — call install() from MainApplication first.
import com.nitropush.sdk.NitroPushSdk
NitroPushSdk.install(application) // once, from MainApplication
NitroPushSdk.shared.configure(config) // or configFromManifest()
NitroPushSdk.shared.setEnableLogs(true) // debug log toggle
NitroPushSdk.shared.checkForUpdate() // BLOCKING
NitroPushSdk.shared.downloadUpdate(pkg) // BLOCKING
NitroPushSdk.shared.installUpdate(pkg, mode, minimumBgSeconds)
NitroPushSdk.shared.notifyAppReady()
NitroPushSdk.shared.restartApp(onlyIfUpdateIsPending)
NitroPushSdk.shared.rollback(releaseId)
NitroPushSdk.shared.getCurrentPackage()
NitroPushSdk.shared.getPendingPackage()
NitroPushSdk.shared.clearPendingUpdate()
NitroPushSdk.shared.clearUpdates()
NitroPushSdk.shared.activeBundleFile() // for ReactNativeHost
NitroPushSdk.shared.addDownloadProgressListener { … } Top-level exports from @nitropush/react-native. configureWith() returns a NitroPushClient — every runtime method lives on the client, not as a free function.
import {
// 3 top-level functions
configure, // no-arg, reads Info.plist / AndroidManifest meta-data
configureWith, // explicit config
sync, // sync(client, options?, onStatus?, onProgress?)
// enums
InstallMode,
SyncStatus,
// types
type NitroPushClient,
type NitroPushConfig,
type LocalPackage,
type RemotePackage,
type DownloadProgress,
type SyncOptions,
} from '@nitropush/react-native';
// Everything else is on the client returned from configure/configureWith:
// client.checkForUpdate(deploymentKeyOverride?)
// client.notifyAppReady()
// client.restartApp(onlyIfUpdateIsPending)
// client.getCurrentPackage()
// client.getUpdateMetadataSync()
// client.getPendingPackage()
// client.clearUpdates()
// …plus methods on the returned packages:
// remote.download(onProgress)
// local.install(installMode, minimumBackgroundDuration)
// local.rollback() How to read these docs
- Installation — npm + native wiring (~5 min).
configure()— required runtime config, in any of the three runtimes.sync()— the main update loop. JS one-call helper + the three native steps that compose into it.- Lifecycle —
notifyAppReady,restartApp,rollback, the rollback safety net,setEnableLogs.
Quick example
The shortest path to “my app self-updates.” Pick the runtime closest to where the rest of your bootstrap code already lives.
Configure + sync from AppDelegate — runs before React Native loads, perfect for pre-JS update checks.
import UIKit
import NitroPush
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ app: UIApplication,
didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
try? NitroPushSdk.shared.configure(
NPConfig(
serverUrl: "https://app.nitropush.org",
deploymentKey: "PROD-KEY-…",
storageBaseUrl: "https://your-bucket.s3.amazonaws.com"
)
)
// Fire and forget — checks for an update during launch animation.
Task {
if let remote = try? await NitroPushSdk.shared.checkForUpdate() {
let local = try? await NitroPushSdk.shared.downloadUpdate(remote)
if let local {
try? await NitroPushSdk.shared.installUpdate(
pkg: local,
installMode: .onNextRestart,
minimumBackgroundDuration: 0
)
}
}
}
return true
}
} Configure + sync from MainApplication — runs before React Native loads.
import android.app.Application
import com.nitropush.sdk.NitroPushSdk
import com.nitropush.sdk.NlConfig
import com.nitropush.sdk.NlInstallMode
import kotlinx.coroutines.*
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
NitroPushSdk.install(this)
NitroPushSdk.shared.configure(
NlConfig(
serverUrl = "https://app.nitropush.org",
deploymentKey = BuildConfig.NL_KEY,
storageBaseUrl = "https://your-bucket.s3.amazonaws.com"
)
)
CoroutineScope(Dispatchers.IO).launch {
runCatching {
val remote = NitroPushSdk.shared.checkForUpdate() ?: return@runCatching
val local = NitroPushSdk.shared.downloadUpdate(remote)
NitroPushSdk.shared.installUpdate(
pkg = local,
installMode = NlInstallMode.ON_NEXT_RESTART,
minimumBackgroundDurationSeconds = 0.0
)
}
}
}
} configureWith() at module-load → returned client used everywhere. sync(client, ...) from a top-level useEffect.
import { useEffect } from 'react';
import {
configureWith,
sync,
InstallMode,
type NitroPushClient,
} from '@nitropush/react-native';
// One-time, at JS startup. Reads env vars set at build time. The
// returned client owns every runtime method.
const client: NitroPushClient = configureWith({
serverUrl: process.env.EXPO_PUBLIC_NITROPUSH_SERVER_URL!,
deploymentKey: process.env.EXPO_PUBLIC_NITROPUSH_DEPLOYMENT_KEY!,
storageBaseUrl: process.env.EXPO_PUBLIC_NITROPUSH_STORAGE_BASE_URL!,
});
export function App() {
useEffect(() => {
// Confirms a healthy boot. Without this, fresh installs roll back.
client.notifyAppReady();
// Background sync — gentle default. `sync` takes the client as its
// first arg, then options / status / progress callbacks.
sync(client, { installMode: InstallMode.ON_NEXT_RESTART }).catch(() => {});
}, []);
return /* your app */;
} What gets sent on the wire
For every release event the SDK observes, it posts a small analytics blob to the platform — release.created, download_started, download_completed, install_completed, install_failed_rollback. This is what powers the live release counts in the dashboard and what feeds the Slack/Discord notifications you set up in Get started → Notifications.
The events fire from native code on a background thread (regardless of whether you triggered the operation from JS or native), queue locally if the network’s down, and retry with exponential backoff. There’s no opt-out today — analytics is core to how the rollout / rollback flows work.
Next: Installation →