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

@marsadco/react-native

v0.1.0-beta.0

Published

Marsad native crash capture for React Native and Expo (iOS + Android). Pairs with @marsadco/sdk.

Readme

@marsadco/react-native

Native crash capture for React Native and Expo apps, on iOS and Android. It ships the MarsadNativeCrash native module; @marsadco/sdk finds it by name and uses it automatically.

Beta. Install it with the @beta tag. It captures native crashes on iOS and Android, and with debug files uploaded they resolve to function, file and line. It hasn't been run on a physical iPhone yet. Report problems, or see what others hit, at https://www.marsad.space/community/issues.

Why a separate package

@marsadco/sdk is pure JavaScript and runs on the web, Node and React Native. Native code lives here so that:

  • web and Next.js users never download it, and
  • a native build problem can only affect apps that installed this package.

Without it, @marsadco/sdk still captures JavaScript errors in React Native — it just can't see crashes that happen below JavaScript.

Requirements

  • Expo SDK 54–57, or React Native 0.81+ without Expo.
  • The New Architecture, which is the default everywhere in that range.
  • Not Expo Go, which can't load custom native code — use a development or EAS build.

Each release is checked with a release build of Expo SDK 54, 55, 56 and 57 and bare React Native 0.86, on the iOS simulator and the Android emulator. Each build must link the module, catch a native crash and deliver exactly one event for it. Built with Xcode 27 and JDK 17. It hasn't run on a physical iPhone yet.

The old architecture isn't supported. On SDK 54 with newArchEnabled: false, Android still loads the module through React Native's interop layer, and it works. On iOS the module isn't visible to JavaScript, and the app runs without it. (On Xcode 27 that iOS build first fails inside React Native's own fmt pod, which React Native 0.81 compiles from source on the old architecture. We checked the module with fmt built as C++17.)

Install

npx expo install @marsadco/sdk @marsadco/react-native@beta

Add the config plugin to app.json, then run expo prebuild or an EAS build:

{ "expo": { "plugins": [["@marsadco/react-native", { "key": "bk_live_…" }]] } }

The key lets native code send a crash before JavaScript starts (see Delivery) and lets release builds upload their debug files (see Symbols). Pass endpoint too if you self-host.

Bare React Native: npm install @marsadco/sdk @marsadco/react-native@beta, and add one call at the top of the post_install block in ios/Podfile (the Expo plugin adds it for you):

post_install do |installer|
  require File.join(File.dirname(Pod::Executable.execute_command('node', ['-p', 'require.resolve("@marsadco/react-native/package.json", {paths: [process.argv[1]]})', __dir__]).strip), "ios", "marsad_post_install")
  marsad_post_install(installer)
  # … react_native_post_install(…) as before

Then pod install in ios/. Autolinking does the rest. The call raises the deployment target of KSCrash's privacy-manifest bundles, which Xcode 27 refuses to build at the iOS 12 their podspec declares.

Nothing to call: capture starts before your app code does, so crashes during startup are caught too. Every report carries what JS had set when the app crashed: user, tags, breadcrumbs, context, release and environment.

What it captures on iOS

KSCrash 2.6 (MIT, a CocoaPods dependency) is installed when the app binary loads, before main.

| Crash | Reported as | |---|---| | EXC_BAD_ACCESS / SIGSEGV, stack overflow | SIGSEGV, with the fault address | | abort() and other signals | SIGABRT, … | | Swift fatalError, force-unwrap of nil | SIGTRAP (EXC_BREAKPOINT) | | Uncaught NSException | the exception's name and reason | | Uncaught C++ exception | the exception's type and what() |

Each comes with the crashed thread's stack: address, binary image and its UUID per frame, and the symbol where the device could resolve one. On the next launch the KSCrash report is converted on the device into a compact report built from an explicit list of fields, then deleted: register values, stack memory, environment variables and the list of every loaded image never leave the phone.

Not caught: watchdog kills, hangs and out-of-memory terminations. iOS ends those from outside the process, so no in-app handler runs.

What it captures on Android

A content provider starts capture before Application.onCreate.

| Crash | How | Needs | |---|---|---| | Uncaught Kotlin/Java exception | An uncaught-exception handler writes the report before the process dies | Any Android | | Native crash (SIGSEGV, SIGABRT, …) | Read from Android's exit history on the next launch, with the tombstone's crashed-thread backtrace (build id + relative pc per frame) | Android 12+ for the stack; Android 11 gets the signal only | | ANR | Exit history, with the main thread's stack from the ANR dump | Android 11+, and only when the ANR ended the process | | Low-memory kill in the foreground | Exit history, sent as a warning | Android 11+ |

Symbols: function, file and line

A native crash arrives as addresses. To show and group it by function, file and line, Marsad needs the build's debug files: the dSYMs on iOS, and on Android the unstripped .so files plus R8's mapping.txt. With the config plugin, release builds upload them for you, using the plugin's key and endpoint:

  • iOS: an "Upload Marsad debug files" build phase, after the dSYMs are made. Release configuration only (MARSAD_UPLOAD_ALL_CONFIGURATIONS=1 for all).
  • Android: a marsadUploadDebugFiles<Variant> task after assemble/bundle of every non-debuggable variant.

Only files the server doesn't have yet are sent, so a rebuild with no native change uploads nothing. An upload that fails prints a warning; it never fails the build. React Native's own libraries are skipped. MARSAD_KEY and MARSAD_URL override the plugin's values (for a CI secret), MARSAD_UPLOAD_DEBUG_FILES=0 turns it off for one build, and { "uploadDebugFiles": false } in the plugin options removes it.

A crash that arrives before its symbols, or while the server can't resolve it, waits up to 15 minutes, then is recorded with addresses only. Uploading symbols later doesn't change crashes already recorded. Issues whose frames were resolved show a Symbolicated badge.

Without the plugin (bare React Native, or your own CI), upload from the build outputs:

npx marsad upload-debug-files --platform ios --key $MARSAD_KEY ./ios/build/Build/Products/Release-iphoneos
npx marsad upload-debug-files --platform android --key $MARSAD_KEY \
  ./android/app/build/intermediates/merged_native_libs/release ./android/app/build/outputs/mapping/release

Obfuscated builds (R8 / ProGuard)

R8 renames classes and methods differently in every release. So the server can restore the real names (and keep one crash in one issue across releases), each build's mapping gets an id, which goes into the app's manifest and is uploaded with the mapping. The plugin does both: each minified build gets a fresh id. By hand, it's:

<!-- AndroidManifest.xml, inside <application> -->
<meta-data android:name="com.marsadco.PROGUARD_UUID" android:value="6b4ab3c4-…" />
npx marsad upload-debug-files --platform android --proguard-id 6b4ab3c4-… ./android/app/build/outputs/mapping/release

Kotlin/Java frames then carry that id. Without it they're grouped and shown with their obfuscated names.

Fatal JS errors

Saved, not raced. React Native kills the app right after a fatal JS error, too soon for a network request to finish. So in release builds @marsadco/sdk hands the finished event to this module, which writes it to disk before returning, and it's sent on the next launch like a native crash (with beforeSend, the original event code and the crashed run's breadcrumbs).

React Native then ends the app with its own exception (JavascriptException on Android, RCTFatalException on iOS). Native doesn't report that one when the SDK saved the error, so it's never sent twice. When the SDK didn't get to see it (an error while the bundle is still loading), native reports it, grouped by the JS error rather than by React Native's crash frames.

Delivery

Reports are sent on the next launch. Normally JS sends them as soon as init() runs, so beforeSend applies. Native sends them itself when:

  • JS hasn't configured the SDK within 10 seconds, or
  • the app is crashing on launch: the last two foreground launches died within 10 seconds, so waiting for JS would never end.

beforeSend doesn't run on reports native sends. Native uses the key and endpoint from the last launch where JS ran, or what the config plugin writes (the marsad dictionary in Info.plist; com.marsadco.API_KEY and com.marsadco.INGEST_URL in the Android manifest). With neither, reports wait for JS.

On Android, a native send happens in the app process while it's in the foreground, and otherwise through a JobScheduler job, because Android freezes background app processes and blocks their network. That's why the library declares ACCESS_NETWORK_STATE, a normal permission granted at install. On iOS the send runs as a background task, so leaving the app doesn't cut it off.

Alongside another crash reporter

Both reporters get the crash. Marsad installs first: before main on iOS, and in a content provider on Android. A reporter that starts later, as Sentry does at Sentry.init, installs its handlers on top and passes the crash on. Android native crashes come from the system's exit record on the next launch, not from a signal handler, so they don't compete for one.

Tested with @sentry/react-native 8.26.0 in its default setup, on the iOS simulator and the Android emulator. Each crash below reached both, and Marsad sent exactly one event, with its stack:

| iOS | Android | |---|---| | SIGSEGV, SIGABRT, NSException, C++ exception, Swift fatalError | Kotlin exception, native SIGSEGV, native abort |

One difference on iOS: an uncaught C++ exception reaches Marsad as SIGABRT rather than the exception's type and message, because Sentry's terminate handler takes the exception first. Sentry's report still has them. Other reporters (Crashlytics, Bugsnag) haven't been tested.

Turning it off

<!-- iOS: Info.plist -->
<key>marsad</key>
<dict>
  <key>captureNativeCrashes</key>
  <false/>
</dict>
<!-- Android: AndroidManifest.xml, inside <application> -->
<meta-data android:name="com.marsadco.CAPTURE_NATIVE_CRASHES" android:value="false" />

init({ enabled: false }) also stops native from sending: saved reports are deleted instead.

Checking it's linked

import { getNativeCrashInfo, nativeCrash } from "@marsadco/react-native";

getNativeCrashInfo(); // { moduleVersion, platform, captureInstalled, … } — null if not linked
nativeCrash();        // crashes the app from native code, to test the pipeline

Layout

| Path | What | |---|---| | src/NativeMarsadNativeCrash.ts | Codegen spec — the native contract. Keep in step with NativeCrashModule in @marsadco/sdk. | | ios/MarsadNativeCrash.mm | iOS TurboModule: forwards to MarsadCrash | | ios/MarsadCrash.m | iOS capture: KSCrash install, launch processing, scope, delivery | | ios/MarsadReportConverter.m, ios/MarsadCrashStore.m | KSCrash report conversion and report storage. Foundation only, tested on macOS with real KSCrash reports: sh ios-tests/run.sh | | android/…/MarsadNativeCrashModule.kt | Android TurboModule: forwards to crash/MarsadCrash | | android/…/crash/ | Android capture: tombstone and ANR parsers, report storage, native sender. JVM tests with real emulator captures (npm run test:android in crash-lab) | | MarsadReactNative.podspec | CocoaPods spec (autolinking reads it from the package root) | | app.plugin.js | Expo config plugin (delegates to @marsadco/sdk) |

Both platforms are exercised end to end by examples/crash-lab.