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

@healthos_ai/react-native-code-push

v0.5.3

Published

HealthOS React Native CodePush SDK for self-hosted OTA updates.

Readme

@healthos_ai/react-native-code-push

React Native CodePush SDK for HealthOS. This package combines the app-facing CodePush wrapper and the native OTA bundle loader, so the app installs one package for OTA updates.

It lives in the code-push repo beside the CodePush admin and server, and is published as @healthos_ai/react-native-code-push. It is a separate package from @vexor-push/react-native-code-push on purpose: publishing that one no longer moves what the HealthOS app installs.

Provenance

Forked from @vexor-push/[email protected] (the exact source the published 0.4.1 tarball was built from). Version 0.5.0 continues that 0.4.x lineage.

Only the npm package name changed. Every native identifier is deliberately unchanged, because the app's native entry points already call them:

  • TurboModule name VexorCodePush (NativeModules.VexorCodePush)
  • Android package com.vexorpush.codepush, Kotlin class VexorCodePush
  • iOS class VexorCodePush, pod name react-native-code-push
  • codegenConfig.name = VexorCodePush

Fixes carried on top of 0.4.1:

  1. hotUpdate rollback crash. The failed-pending branch of checkPendingUpdate() called an undefined hotUpdate global, so the rollback safety net threw a ReferenceError instead of rolling back. It now performs a real rollback, controlled by rollbackOnFailedPending / restartAfterRollback: rollbackToPreviousBundle(0) walks the native bundle history and repoints PATH/VERSION at the previous good OTA release. Deleting the bundle is only the fallback for when history has nothing older left, because deleteBundle zeroes VERSION and drops the device all the way to the bundle compiled into the store build — a silent downgrade past every good release since.
  2. Request timeout. The manifest and report requests had no deadline and could hang the caller indefinitely. They are now aborted after requestTimeoutMs (default 15000).
  3. reset() deletes the native bundle. Previously it cleared only the JS-side state and left the installed bundle on disk. reset(restart) now deletes the native bundle and can reload the runtime. Unlike the rollback path above, reset() keeps the full deleteBundle wipe on purpose — "reset" means going back to pristine, i.e. the bundle compiled into the binary, not to the previous OTA release.
  4. Download progress reaches onProgress. The native download loops emit progress, and the JS layer now subscribes and forwards it to the onProgress callback of installUpdate() / sync(), which previously never fired.

0.5.1

An adversarial review of 0.5.0 found the rollback and progress work to be wrong in ways its tests could not see. Fixed here:

  • A device the native crash handler had already rolled back was rolled back a second time. The JS recovery branch reads the metadata key, while the native handlers write install state under a different one, so JS could not tell the device had already moved. It now compares the installed version with the pending one first.
  • sync() reinstalled the release it had just rolled back. Rolling back lowers the current version, so the next check is offered the same release again — an unbounded download/rollback loop. 0.4.1 never reached it only because checkPendingUpdate threw before returning.
  • Progress reported a false 100%. With no Content-Length, the JS forwarded the received count as the total, which every received / total consumer renders as a full bar from the first tick. totalBytes is now 0 while the length is unknown; test totalBytes > 0 before dividing.
  • iOS emitted no progress at all without a Content-Length, because totalUnitCount stays -1 for the whole transfer and the poll required a positive total. Android and iOS now emit the same contract.
  • rollbackToPreviousBundle refused to repoint when the outgoing bundle's directory could not be deleted — the usual state after a crash loop — so the caller wiped the device to the packaged bundle instead of stepping back one release. Both platforms now repoint regardless; deleting is housekeeping.
  • getBundleList crashed on iOS with NSInvalidArgumentException whenever a migrated history entry held NSNull metadata.
  • A retried download no longer freezes the progress bar at the previous attempt's high-water mark; onDownloadAttempt is throw-guarded like the other callbacks; reset() no longer skips its restart when the bundle directory was already gone; and the download no longer waits on a telemetry request before it starts.

Install

// package.json
{
  "dependencies": {
    "@healthos_ai/react-native-code-push": "0.5.0"
  }
}

Pinned exactly, not ^0.5.0. The app used to track ^0.4.1 of the upstream package, which meant every publish changed what the next install produced with nobody reviewing it — a bump should be a commit someone approves.

After installing, run pod install in ios/ — autolinking picks up the pod (still named react-native-code-push) from node_modules/@healthos_ai/react-native-code-push. Android needs a Gradle sync, because the TurboModule is compiled from source.

The SDK downloads, stores, and installs OTA bundles through the native VexorCodePush bridge. The app does not need a separate downloader or file-system package.

Configure

import {
  checkPendingUpdate,
  checkUpdate,
  configure,
  installUpdate,
  notifyApplicationReady,
  sync,
} from '@healthos_ai/react-native-code-push';

configure({
  baseUrl: 'https://cp.example.com',
  deploymentKey: '<deployment-key-from-admin>',
  binaryVersion: '1.0.0',
  clientId: '<stable-device-or-install-id>',
  requestTimeoutMs: 15000, // optional; this is the default
});

await checkPendingUpdate();
await notifyApplicationReady();

const update = await checkUpdate();
if (update.hasUpdate) {
  await installUpdate(update, {
    onProgress: (receivedBytes, totalBytes) => {
      // totalBytes is 0 when the server sends no content length
    },
  });
}

await sync();

Pass a stable clientId for device/install metrics. If it is omitted, the SDK generates and stores one through the configured storage adapter.

Device context on reports

Every report carries osVersion and deviceModel, read from Platform.constants, so the server can say which devices are failing and not only how many. networkType has no platform API and is left to the app:

import NetInfo from '@react-native-community/netinfo';
import DeviceInfo from 'react-native-device-info';

configure({
  // ...
  getDeviceContext: async () => ({
    networkType: (await NetInfo.fetch()).type,
    deviceModel: DeviceInfo.getModel(),
  }),
});

The result is merged over the platform values, so returning only the fields you have is fine. The hook runs on every report and is never allowed to fail one — a throw or a rejection falls back to the platform values alone, which is also why it must not do anything slow.

Native Loader

The package includes the native CodePush loader. Keep the standard native setup:

  • iOS release builds should return VexorCodePush.getBundle().
  • Android release builds should return VexorCodePush.bundleJS(applicationContext).
  • Debug builds should keep using Metro.

Expo Config Plugin

app.plugin.js re-exports the compiled plugin from plugin/dist, and plugin/dist is committed — it is a checked-in build artifact, not something generated at install time. A workspace or file: consumer never runs prepack, so a build-on-publish step would leave that require dangling; that is how the published 0.4.0 shipped a plugin whose require target was missing.

The output directory is dist rather than build because the repo-root .gitignore ignores build/ everywhere.

healthos is a bare React Native app: it hand-edits MainApplication.kt and AppDelegate.swift and never runs the Expo config plugin. The plugin is kept only so a future Expo-managed consumer can use it.

Rebuilding the plugin

This package deliberately declares no devDependencies and no scripts. The previous build-plugin / prepack pair pulled expo-module-scripts, @expo/config-plugins, typescript and two @types packages — ~130 packages and ~1,180 lockfile lines — into an app that never invokes any of them. Worse, pinning @expo/config-plugins exactly forced that version workspace-wide and re-resolved the unrelated native module react-native-health-connect.

So plugin/dist is now the source of truth for consumers, and rebuilding it is a deliberate, manual step.

plugin/tsconfig.json still extends expo-module-scripts/tsconfig.plugin, which is no longer installed, so tsc --build plugin fails with:

plugin/tsconfig.json(2,14): error TS6053: File 'expo-module-scripts/tsconfig.plugin' not found.

Do not ignore that error and let the build proceed: with the preset missing, tsc falls back to target: es5 and silently rewrites the output to var / function form, which is not what the committed plugin/dist contains.

To rebuild after editing plugin/src/index.ts, put the toolchain back temporarily, build, then take it away again:

cd packages/react-native-code-push
pnpm add -D typescript@^5.9.3 [email protected] \
  @expo/config-plugins@^57.0.3 @types/node@^20.19.43 @types/jest@^29.5.14
pnpm exec tsc --build plugin --force
pnpm remove typescript expo-module-scripts @expo/config-plugins @types/node @types/jest
rm -f plugin/tsconfig.tsbuildinfo
cd ../.. && pnpm install

Then commit the regenerated plugin/dist and check git diff pnpm-lock.yaml is empty — re-adding @expo/config-plugins with an exact pin is what caused the resolution churn described above, so prefer a caret range if you must add it back at all.

The expected output is ES2022 CommonJS: plugin/dist/index.js begins with "use strict"; followed by const config_plugins_1 = require("@expo/config-plugins"); and ends with module.exports = withAction;, and plugin/dist/index.d.ts is just export {};.

@expo/config-plugins is not a dependency of this package. plugin/dist/index.js requires it at runtime, and an Expo-managed consumer always provides it (every Expo project has it by way of expo). Inside healthos it happens to be present only because react-native-health-connect pulls it in as a peer — convenient, but not something this package relies on.

Lint and Tests

This package has no lint, test or typecheck scripts of its own. The repo-root .eslintignore skips packages/react-native-code-push/ entirely: the code is vendored, is kept close to the published 0.4.1 so it can still be diffed against upstream, and does not follow the app's healthos-* rules. Its behaviour is covered by the app's own tests in src/shared/services/codepush/__tests__/.

The app's tsc does still include this package (the root tsconfig.json has no include), so type errors here surface in pnpm typecheck.

Manifest Endpoint

Preferred endpoint:

GET https://cp.example.com/api/ota/key/:deploymentKey/update.json?currentVersion=0&platform=ios&binaryVersion=1.0.0

The SDK also supports app/deployment fallback configuration for debugging, but production apps should use deployment keys.