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

ubriot-updates-rn

v0.1.4

Published

Ubriot over-the-air JS update client for bare React Native (iOS + Android)

Readme

@ubriot/updates-rn

Over-the-air (OTA) JavaScript update client for bare React Native apps (no Expo), built on top of the existing Ubriot update backend. It lets a shipped app download a newer JS bundle in the background, swap it in on the next launch, and automatically roll back to the last known good bundle if a new one fails to boot.

It reuses the Ubriot update service that already powers the Expo clients. You publish updates the same way, with the Ubriot CLI update command. This package is only the native + JS runtime that consumes those updates in a bare app.

What it does

  • Talks to the existing GET /ubriot/updates/manifest endpoint (Expo-Updates-style manifest contract, including the expo-runtime-version, expo-channel-name, and expo-current-update-id request headers).
  • Downloads the manifest's launchAsset bundle to app storage (iOS Library/ubriot-updates/, Android filesDir/ubriot-updates/).
  • Persists which bundle is pending, active, verified, and lastKnownGood in a small state.json next to the bundles.
  • Boots the verified/active downloaded bundle instead of the embedded one, with crash-rollback safety.
  • Never blocks or crashes app start: every network and file operation is wrapped, and a failed OTA quietly falls back to the embedded bundle.

Install

Add it as a dependency (workspace or file path), then rebuild the native app.

// package.json
"dependencies": {
  "@ubriot/updates-rn": "*"
}

The native module autolinks (CocoaPods on iOS, autolinkLibrariesWithApp() / gradle on Android). Run pod install in ios/ and rebuild.

JS API

import * as UbriotUpdates from "@ubriot/updates-rn";

UbriotUpdates.configure({
  apiBase: "https://api.ubriot.dev/api/v1",
  appSlug: "your-app-slug",
  channel: "production",
  runtimeVersion: "your-app-1.0.0",
});

// Convenience: check -> download -> apply (does not restart by default)
const result = await UbriotUpdates.sync(); // 'up-to-date' | 'updated'

// Or drive each step:
const { available, manifestId } = await UbriotUpdates.checkForUpdate();
const { ready } = await UbriotUpdates.downloadUpdate();
await UbriotUpdates.applyUpdate({ restart: true }); // reload JS now

// Call once the app has rendered and run OK. Promotes the active bundle to
// "verified" so it becomes the rollback target instead of being rolled back.
UbriotUpdates.markLaunchSuccessful();

Recommended app-start pattern:

useEffect(() => {
  UbriotUpdates.configure({ /* ... */ });
  UbriotUpdates.sync().catch(() => {});
  const t = setTimeout(() => UbriotUpdates.markLaunchSuccessful(), 4000);
  return () => clearTimeout(t);
}, []);

sync() downloads and stages the update; it applies on the next launch. Use applyUpdate({ restart: true }) if you want to reload immediately.

Rollback model

  • A freshly applied bundle is active but unverified.
  • On the first launch of an unverified active bundle, a launch counter is incremented and persisted before JS runs.
  • If the app boots that bundle again while still unverified (meaning the previous launch never reached markLaunchSuccessful(), e.g. it crashed on load), the client reverts to the last known good bundle (or the embedded bundle) instead.
  • markLaunchSuccessful() marks the active bundle verified and records it as the new last known good.

Native integration

Autolinking registers the native module. You only need to point the app at the resolved bundle so downloaded updates actually load.

iOS (AppDelegate)

Use the embedded bundle in DEBUG (Metro) and the OTA-resolved bundle in RELEASE.

Objective-C:

#import <UbriotUpdatesRn/UbriotUpdates.h>

- (NSURL *)bundleURL {
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
  return [UbriotUpdates bundleURL];
#endif
}

Swift (add a bridging header that imports <UbriotUpdatesRn/UbriotUpdates.h>):

override func bundleURL() -> URL? {
#if DEBUG
  RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
  UbriotUpdates.bundleURL()
#endif
}

+ [UbriotUpdates bundleURL] returns the verified/active downloaded bundle when present and valid (applying rollback), otherwise the embedded main.jsbundle.

Android (MainApplication)

Pass the resolved bundle path to getDefaultReactHost (new architecture) or return it from ReactNativeHost.getJSBundleFile() (classic). Return null to fall back to the embedded asset bundle.

import dev.ubriot.updates.UbriotUpdatesModule

override val reactHost: ReactHost by lazy {
  getDefaultReactHost(
    context = applicationContext,
    packageList = PackageList(this).packages,
    jsBundleFilePath =
      if (BuildConfig.DEBUG) null
      else UbriotUpdatesModule.resolveBundleFilePath(applicationContext),
  )
}

resolveBundleFilePath(context) applies the same rollback logic as iOS and returns the bundle file path or null.

Publishing an update

Publishing uses the existing Ubriot CLI, unchanged:

UBRIOT_API_BASE=https://api.ubriot.dev/api/v1 \
  node ../cli/dist/index.js update \
  --app your-app-slug \
  --channel production \
  --runtime-version your-app-1.0.0 \
  --public-url https://api.ubriot.dev/api/v1 \
  --message "what changed"

The runtimeVersion you pass here MUST match the runtimeVersion passed to configure(). Only JS-only changes can ship over the air; anything touching native code requires a new store/TestFlight build with a bumped runtime version.

Signature verification

Manifest signature verification is stubbed (verifyManifestSignature currently returns true) and structured as a single async gate in checkForUpdate, ready to enforce a signed-manifest policy later.