Installation
1. Install the package
yarn add @nitropush/react-native react-native-nitro-modules
react-native-nitro-modules is a peer dependency. If you already use it for other Nitro Modules, you can skip the second package.
2. Native wiring
Two pieces of native glue that have to live in your project regardless of whether you’ll drive the SDK from JS or natively:
- The launch-time pointer sweep — runs the rollback safety net before the JS bundle loads.
- The
bundleURL/getJSBundleFileoverride — hands React the active OTA bundle when one’s available.
Wire it into AppDelegate.swift. Touching NitroPushSdk.shared for the first time runs the launch-time pointer sweep + lifecycle observers.
import NitroPush
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// …
override func bundleURL() -> URL? {
#if DEBUG
return RCTBundleURLProvider.sharedSettings()
.jsBundleURL(forBundleRoot: "index")
#else
// Hand React the active OTA bundle, falling back to the
// binary-shipped one. The first time `NitroPushSdk.shared` is
// touched, it runs `consumePendingPointerOnLaunch()` — which
// activates the pending bundle (or rolls back if the previous
// install was unhealthy).
return NitroPushSdk.shared.activeBundleURL()
?? Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}
} Wire it into MainApplication.kt. NitroPushSdk.install(this) creates the singleton; activeBundleFile() goes into ReactNativeHost.
import com.nitropush.sdk.NitroPushSdk
class MainApplication : Application(), ReactApplication {
override fun onCreate() {
super.onCreate()
// Touches the singleton — runs the launch-time pointer sweep
// + lifecycle observers.
NitroPushSdk.install(this)
}
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getJSBundleFile(): String? {
if (BuildConfig.DEBUG) return null
// Returning null in debug keeps Metro in charge.
// In release, returns the active OTA bundle path or null
// (in which case React falls back to the binary-shipped bundle).
return NitroPushSdk.shared.activeBundleFile()
}
// …
}
} After this step, run:
Install the CocoaPods dependency.
cd ios && pod install Gradle picks the package up from your existing React Native autolinking — no extra config.
# nothing — autolinking handles it Xcode 26 — bare React Native only. Xcode 26’s strict modular headers validate the auto-generated CocoaPods umbrella in pure ObjC mode, where nitrogen’s C++
.hppimports fail withunknown type name 'namespace'. Add the followingpost_installhook to yourios/Podfileso the umbrella’s.hppimports get wrapped in#ifdef __cplusplusafterpod install— ObjC validation then skips them while Swift’s C++ interop still picks them up:post_install do |installer| # …existing react_native_post_install(installer, ...) call… umbrella = File.join( installer.sandbox.root.to_s, 'Target Support Files/NitroPushNative/NitroPushNative-umbrella.h' ) if File.exist?(umbrella) content = File.read(umbrella) hpp_imports = content.scan(/^#import\s+"[^"]+\.hpp"\s*$/).join("\n") unless hpp_imports.empty? || content.include?('#ifdef __cplusplus') guarded = "#ifdef __cplusplus\n#{hpp_imports}\n#endif" patched = content.gsub(/^#import\s+"[^"]+\.hpp"\s*\n/, '') .sub(/(FOUNDATION_EXPORT double)/, "#{guarded}\n\n\\1") File.write(umbrella, patched) end end endExpo apps don’t need this — the config plugin injects the same hook at
expo prebuildtime. See apps/react-native-example/ios/Podfile for the full reference.
3. Configure environment variables
The SDK needs three values: serverUrl, deploymentKey, storageBaseUrl. Where they come from depends on whether you’ll call configure() from JS or natively.
| Variable | What |
|---|---|
serverUrl | NitroPush API base URL. Production: https://app.nitropush.org. |
deploymentKey | The environment’s deployment key (from Projects). |
storageBaseUrl | Public root of your storage bucket. Visible on the Storage page once a provider is configured. With MinIO this is the S3 API port (typically :9000), not the Console UI port (typically :9001). |
There are two equivalent ways to feed these to the SDK:
- Native-side — put
NITROPUSH_SERVER_URL,NITROPUSH_DEPLOYMENT_KEY,NITROPUSH_STORAGE_BASE_URLinInfo.plist(iOS) and<application><meta-data>inAndroidManifest.xml(Android), then callNitroPushSdk.configFromInfoPlist()/NitroPushSdk.configFromManifest(). From JS the no-argconfigure()reads the same keys. - Explicit-config — pass an
NPConfig/NlConfig/NitroPushConfigobject yourself. Useful when values come from a build-time secret, a feature-flag service, or per-target xcconfig.
Add NITROPUSH_* keys to Info.plist and use configFromInfoPlist(), or pass an NPConfig you assembled yourself.
<!-- ios/<App>/Info.plist -->
<key>NITROPUSH_SERVER_URL</key>
<string>https://app.nitropush.org</string>
<key>NITROPUSH_DEPLOYMENT_KEY</key>
<string>PROD-KEY-…</string>
<key>NITROPUSH_STORAGE_BASE_URL</key>
<string>https://your-bucket.s3.amazonaws.com</string>// Option A: read the keys above via the SDK helper.
try NitroPushSdk.shared.configure(NitroPushSdk.configFromInfoPlist())
// Option B: assemble your own NPConfig (e.g. from xcconfig + Bundle.main).
let key = Bundle.main.object(forInfoDictionaryKey: "NITROPUSH_DEPLOYMENT_KEY") as? String ?? ""
try NitroPushSdk.shared.configure(
NPConfig(
serverUrl: "https://app.nitropush.org",
deploymentKey: key,
storageBaseUrl: "https://your-bucket.s3.amazonaws.com"
)
) Add NITROPUSH_* under <application> <meta-data> in AndroidManifest.xml and use configFromManifest(), or pass an NlConfig you assembled yourself.
<!-- android/app/src/main/AndroidManifest.xml -->
<application>
…
<meta-data android:name="NITROPUSH_SERVER_URL" android:value="https://app.nitropush.org" />
<meta-data android:name="NITROPUSH_DEPLOYMENT_KEY" android:value="PROD-KEY-…" />
<meta-data android:name="NITROPUSH_STORAGE_BASE_URL" android:value="https://your-bucket.s3.amazonaws.com" />
</application>// Option A: read the meta-data above via the SDK helper.
NitroPushSdk.shared.configure(NitroPushSdk.configFromManifest())
// Option B: assemble your own NlConfig (e.g. from BuildConfig fields).
// app/build.gradle.kts:
// buildConfigField("String", "NL_DEPLOYMENT_KEY", "\"PROD-KEY-…\"")
NitroPushSdk.shared.configure(
NlConfig(
serverUrl = "https://app.nitropush.org",
deploymentKey = BuildConfig.NL_DEPLOYMENT_KEY,
storageBaseUrl = "https://your-bucket.s3.amazonaws.com"
)
) EXPO_PUBLIC_* env vars get inlined in the bundle at build time. For bare React Native, use react-native-config.
With the NITROPUSH_* keys set in Info.plist / AndroidManifest (see the
native tabs above), the no-arg configure() reads them for you. In your
app entry:
import { configure, type NitroPushClient } from '@nitropush/react-native';
// Capture the returned client at module scope; every other call goes on it.
export const client: NitroPushClient = configure();Self-hosted server or custom CDN? Use configureWith() with an
explicit config instead — e.g. sourced from Expo public env vars:
# .env (Expo)
EXPO_PUBLIC_NITROPUSH_SERVER_URL=https://app.nitropush.org
EXPO_PUBLIC_NITROPUSH_DEPLOYMENT_KEY=PROD-KEY-Q1IEQH
EXPO_PUBLIC_NITROPUSH_STORAGE_BASE_URL=https://your-bucket.s3.amazonaws.comimport { configureWith, type NitroPushClient } from '@nitropush/react-native';
export 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!,
});For bare React Native, use react-native-config or babel-plugin-transform-inline-environment-variables.
Local dev with Expo Go. On Android emulators,
localhostfrom the SDK points at the emulator itself, not your dev machine. Either use10.0.2.2(Android emulator’s host loopback) or your LAN IP. iOS sim shares the host’slocalhost, so nothing special is needed.
4. Verify the install
Boot your app. The SDK won’t do anything user-visible until you call sync() (or its native equivalent), but you can opt-in to a verbose per-action trace from native code:
Toggle from AppDelegate.didFinishLaunchingWithOptions. Logs go to stdout (Xcode console + Console.app on a connected device).
NitroPushSdk.shared.setEnableLogs(true)
// → [NitroPush] configure serverUrl=… deploymentKey=… …
// [NitroPush] checkForUpdate deploymentKeyOverride=(none)
// [NitroPush] downloadUpdate releaseId=… label=… kind=codepush
// …Then tail it:
# Console.app → connected device → filter: NitroPush Toggle from MainApplication.onCreate. Logs go to logcat tagged NitroPush.
NitroPushSdk.shared.setEnableLogs(true)
// → I/NitroPush: configure serverUrl=… deploymentKey=… …
// I/NitroPush: checkForUpdate GET http://…/api/sdk/releases/latest?…
// I/NitroPush: downloadUpdate releaseId=… label=… kind=codepush
// …Then tail it:
adb logcat | grep NitroPush setEnableLogs(true) is off by default — leave it off in production. Every SDK action logs a line: configure, checkForUpdate, downloadManifestRelease GET <url>, installUpdate.<mode>, notifyAppReady, restartApp, rollback, lifecycle triggers (ON_NEXT_RESUME activation, etc.) and any failure with URL + status + body snippet.
Next: configure() →