npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@scalebun-release/react-native

v2.5.38

Published

Production-grade React Native SDK for ScaleBun

Readme

@scalebun-release/react-native

Production-grade React Native SDK for ScaleBun — error reporting, analytics, session replay, and in-app engagement for iOS and Android.

Installation

npm install @scalebun-release/react-native
# or
pnpm add @scalebun-release/react-native
# or
yarn add @scalebun-release/react-native

Releases from 2.5.0 are published as @scalebun-release/react-native; the @scalebun/react-native name stops at 2.4.0. An app that already imports from @scalebun/react-native can keep its imports by installing the new package under the old name:

npm install @scalebun/react-native@npm:@scalebun-release/react-native

iOS

cd ios && pod install

Wire the AppDelegate for OTA bundle loading (required for OTA on iOS):

npx scalebun init ios            # wire OTA (ObjC auto-patched; Swift prints the code)
npx scalebun init ios --check    # verify the OTA wiring (exit 1 if missing)
npx scalebun init ios --push     # also wire the Direct APNs push hooks (optional)

Like Android, iOS OTA needs the app's release bundleURL to return ScaleBunOtaBoot.bundleURL() (Swift) / [ScaleBunOtaBoot bundleURL] (ObjC), or updates install but never load. It returns the installed update, or the bundle compiled into the app when there is none yet. init ios applies this to a standard ObjC AppDelegate and prints the exact code for Swift and custom bundleURL bodies. Rebuild the native app afterwards.

Push is separate: the --push hooks take over the app's notification-center delegate, so leave them out if the app already uses Firebase Messaging or notifee for iOS notifications.

Android

OTA updates need one wiring step in MainApplication so a downloaded bundle actually loads — without it an update installs, reports success, and the app silently keeps running the bundle baked into the APK. Run:

npx scalebun init android            # wire MainApplication for OTA bundle loading
npx scalebun init android --check    # verify (exit 1 if missing)
npx scalebun init android --dry-run  # preview changes without writing

This adds ScaleBunOtaModule.getJSBundleFile(...) in the form matching your React Native version (Kotlin is auto-patched; Java prints manual instructions). Rebuild the native app afterwards — a JS reload does not load the newly wired native path.

For push on Android (optional): add android/app/google-services.json, apply the Google Services Gradle plugin with @react-native-firebase/messaging, and declare the POST_NOTIFICATIONS permission. Run npx scalebun doctor to see exactly what's missing.

Run npx scalebun -h to list every command — SDK setup (doctor, init ios, init android) plus the OTA publishing commands (login, ota publish, rollout, …).

Optional peer dependencies

Some features activate only when their peer package is installed:

| Feature | Peer dependency | | ------------------ | ----------------------------- | | Push notifications | @notifee/react-native |

Quick start

Wrap your app in the provider and pass the app's client key from the dashboard (or run npx scalebun quickstart, which creates one and prints this snippet):

import { ScaleBunProvider } from '@scalebun-release/react-native';

export default function App() {
  return (
    <ScaleBunProvider
      config={{
        clientKey: 'YOUR_CLIENT_KEY',
        // OTA updates are OFF by default. Leave this out and the app never
        // checks for an update. channelOverride is the channel the app asks
        // for — without it, `default`. `scalebun ota publish` publishes to it
        // unless --channel, SCALEBUN_CHANNEL or scalebun.json says otherwise.
        ota: { enabled: true, channelOverride: 'production' },
      }}
    >
      <RootNavigator />
    </ScaleBunProvider>
  );
}

projectId + publishableKey is the legacy configuration: it still sends analytics, but OTA needs clientKey and never starts without it.

Initialising with ScaleBun.init() instead

ScaleBun.init({ clientKey, appId }) collects the same automatic events as the provider (automaticEventTracking defaults to true on both paths; pass false to turn it off). Taps (element_interacted) also need your root wrapped in the provider, because the tapped element is identified from a React root — with init() already called it takes no config:

ScaleBun.init({ clientKey: 'YOUR_CLIENT_KEY', appId: 'YOUR_APP_ID' }); // module scope, e.g. index.js

export default function App() {
  return (
    <ScaleBunProvider>
      <RootNavigator />
    </ScaleBunProvider>
  );
}

Without the provider, screens are still collected and the SDK logs a one-time warning that taps are not.

The default export exposes the imperative facade for use outside React (identifying users, capturing events, etc.):

import ScaleBun from '@scalebun-release/react-native';

Screen names

Screens are named automatically when your navigation container is rendered inside <ScaleBunProvider> (as <RootNavigator /> above) or the provider is rendered inside it. React Navigation 6 and 7 and Expo Router are found at runtime with no extra code — the SDK has no dependency on any navigation library and does not patch one; it locates the container React renders and listens to it. Calling ScaleBun.setNavigationRef(ref) (or passing navigationRef to the provider) still works and takes precedence, but is not required. With any other navigation, name each screen with useScaleBunScreen(name) or <ScaleBunScreen name="…" />.

Automatic screen load timing

Screen loads are timed automatically only when the SDK detects your navigation (React Navigation or Expo Router): each screen change becomes a screen_load named after the route. An app with neither records no automatic screen loads — there is no screen to time — so mark them yourself with ScaleBun.performance.markScreenLoadStart(name) / markScreenLoadEnd(name). Returning from the background is not a screen load (since 2.5.25: earlier versions reported it as a $app_foreground screen whose duration was the time spent in the background); launch timing (cold / warm / hot) is reported separately as app start.

Business revenue telemetry

Send purchases with a stable transaction ID and ISO-4217 currency. productId is the durable plan/SKU key used for grouping; productName is its optional human-readable label. Tax, fee, and discount are positive deductions from gross revenue, while refunds and disputes use their own transaction type.

ScaleBun.trackPurchase({
  revenue: 29,
  currency: 'USD',
  transactionId: 'order_123',
  productId: 'pro_monthly',
  productName: 'Pro monthly',
  quantity: 1,
  tax: 2.1,
  fee: 0.9,
  discount: 0,
});

ScaleBun.trackSubscription({
  subscriptionId: 'sub_123',
  subscriptionEventId: 'invoice:2026-09', // optional; reuse for this same logical change
  status: 'started',
  plan: {
    id: 'pro_monthly',
    name: 'Pro monthly',
    amount: 29,
    currency: 'USD',
    interval: 'month',
  },
});

Subscription lifecycle payloads use schema v2. subscriptionId and optional subscriptionEventId are trimmed identifiers of 1–160 characters using letters, digits, ., _, :, or - (the first character must be alphanumeric). Reuse subscriptionEventId only for the same logical lifecycle change. The backend scopes it to the app and environment and keeps the first accepted payload. Without it, the SDK-generated envelope ID still makes transport and offline retries idempotent, but a repeated host call is a new event.

The SDK, not custom properties, stamps the schema and ledger identity. SDK/server provenance is derived from the authenticated ingestion route and cannot be claimed by the app. React Native does not invent a Web replay coordinate for subscription events. Invalid/bounded events are dropped; ScaleBun.events.stats() exposes { pending, dropped } for integration diagnostics.

Recognised revenue, collected cash, currency conversion, and billing-source provenance are server-side measurements; the SDK does not infer them.

Upgrading

To 2.5.25: no $app_foreground screen load

  • Screen load no longer reports a return from the background. Earlier versions, in an app whose navigation the SDK did not detect, recorded every background → foreground switch as a screen load named $app_foreground lasting as long as the app had been in the background (up to 30 s), which inflated screen-load percentiles and Apdex. Automatic screen loads now come only from detected navigation (React Navigation / Expo Router); see Automatic screen load timing. No code change is needed.
  • Includes 2.5.24's OTA activation fix (architecture-aware restart, no activation restart loop).

To 2.5.22: scalebun.json is optional

  • The CLI finds your app by its package name. It reads the Android applicationId (android/app/build.gradle or .kts), the iOS bundle id (the Xcode project), or Expo's app.json / app.config.*, and looks up the app that carries it. Set the package name as the app's Bundle ID in the dashboard (Workspace → Apps) and no scalebun.json, --app-id or SCALEBUN_APP_ID is needed, in CI too. --app-id, SCALEBUN_APP_ID and an existing scalebun.json still take precedence, in that order.
  • It never guesses between apps. A project that builds several package names (product flavors, per-configuration bundle ids), or a name that matches more than one of your apps, gets a picker in a terminal and an error listing the candidates in CI. Pin one with scalebun link or --app-id.
  • The channel comes from your app's code. With no --channel, SCALEBUN_CHANNEL or channel in scalebun.json, ota publish and ota sign use the literal channelOverride your app sets, and default when it sets none. That is the only channel whose releases reach the app. If the app computes the channel at runtime (__DEV__ ? 'staging' : 'production'), the CLI asks in a terminal and requires --channel in CI.
  • ota sign no longer defaults to production. It used to, while ota publish defaulted to default, so the same project could sign for one channel and publish to another. Both now use the order above. Pass --channel production if you relied on the old default.
  • scalebun link no longer writes channel: production unless you pass --channel. A linked channel outranks the app's own channelOverride, so the old default could send releases to a channel the app never checks. scalebun doctor now reports which app and channel commands will use, and warns when scalebun.json disagrees with the app's package name or channel.

To 2.5.17: OTA signing is enforced by default

  • scalebun ota publish refuses to publish unsigned. Give it a key with --key-dir <dir>, SCALEBUN_SIGNING_PRIVATE_KEY or SCALEBUN_SIGNER_COMMAND. An app that has never registered a signing key can pass --allow-unsigned explicitly. In CI, add the flag to the publish step, or better, add the key secrets. --allow-unsigned publishes unsigned even when a key is available (for example in ./.scalebun): the key is not used, and the CLI says so. It cannot be combined with --require-signing.

  • The server refuses an unsigned publish once the app has an active signing key (UNSIGNED_WHILE_REQUIRED). --allow-unsigned does not get past this; sign the release. A device that embeds the key would refuse it anyway.

  • --key-id now selects the matching key file. Before, a non-default --key-id signed with the default key and labelled the release with the other id, and devices rejected it as INVALID_SIGNATURE. Now each id maps to its own file:

    | --key-id | Private key read from | |---|---| | the default key's id | .scalebun/ota-signing-key.pem | | <default>-standby | .scalebun/ota-standby-key.pem | | anything else | .scalebun/keys/<id>/ota-signing-key.pem |

    A key kept elsewhere needs --key-dir. That directory's ota-key-id.txt must match --key-id.

To 2.5.21

  • useOtaUpdate() now uses the initialized SDK configuration. It no longer requires an API URL, client key, or app version in normal application code; those are resolved from ScaleBun.init() and the native binary. A channel is only an explicit QA override, so production routing stays server-controlled.

To 2.5.19 (2.5.18 was never published; its change ships here)

  • ota publish --bundle-path needs a bundle built by scalebun ota bundle build (it carries a SHA-bound identity sidecar used to verify the install on the device). A bundle built any other way needs --allow-unverified-prebuilt, and install verification is then unavailable for that release.

Also in 2.5.19

  • Trust-anchor failures now fail closed. A key file that is present but invalid refuses every update. Before, iOS silently accepted UNSIGNED releases in that state, and Android errored on every native call. Fix the file (the native log names the problem).
  • New "require signed" declaration. Declare it so a build that loses its key file refuses updates instead of installing them unsigned:
    • Android: <meta-data android:name="com.scalebun.ota.REQUIRE_SIGNED" android:value="true" /> in <application>;
    • iOS: ScaleBunOtaRequireSigned = YES in Info.plist.
  • Unsigned refusals are reported properly. An unsigned release refused by a signing app is no longer retried or reported as a SHA-256 mismatch. It is reported once, as UNSIGNED_RELEASE_REFUSED.
  • checkForUpdate() no longer needs installationId. Omit it and the SDK's own install id is used, the same one sync() and sessions use. Passing your own id still works but splits the device in the Fleet.
  • ota publish and ota promote accept --override, the same as ota rollout, for the high-risk approval gate (owner/admin only).
  • ota keys generate writes the key file into android/ and ios/, or prints exactly what to write. Pass --no-embed to skip.

Requirements

  • React Native >= 0.74
  • React >= 18

License

MIT © ScaleBun