Documentation menu

Automatic native crashes

@saaspro/native-crashes is an optional native add-on for @saaspro/react-native. It records fatal iOS processes and Android native crashes, keeps a bounded private queue, and delivers on a later consented foreground launch. It requires a native rebuild; Expo Go cannot load it. Package publication is a separate release step.

npm install @saaspro/react-native @saaspro/native-crashes
# iOS: run pod install, then rebuild. Expo: prebuild/development or release build.

Create the mobile client with a public ingest key, a static release, an optional appVersion, and your existing consent/storage controls. Then create one crash controller outside rendering:

import { AppState, NativeModules } from "react-native";
import { NativeCrashes } from "@saaspro/native-crashes";
import { analytics } from "./analytics";

export const crashes = new NativeCrashes({
  client: analytics,
  bridge: NativeModules.SaaSProNativeCrashes,
  appState: AppState,
  consent: "pending",
  onError: error => console.warn("Native crash delivery unavailable", error.message),
});
await crashes.ready();

// Connect these independently to the user's choices.
await analytics.setConsent("granted");
await crashes.setConsent("granted");

Both choices must be granted. Pending consent stops recording and delivery but preserves earlier consented reports until grant or expiry. Revocation immediately disables capture, cancels delivery, and purges the SDK's queue and launch manifest. A durable opt-out wins over an initial grant; an explicit setConsent("granted") clears it. analytics.reset() or switching between known users purges pending reports. privacyMode: "ephemeral" refuses this durable collector.

Method Behavior
ready() Reads consented native state and arms supported capture
setConsent("pending" / "granted" / "revoked") Independent native crash choice
getStatus() Supported/enabled/blocked, queue/drop counts and coverage boundary
flush() Up to three eligible reports, with one shared in-flight delivery
reset() Purges previous records and rearms if both choices remain granted
shutdown() Disables capture, cancels delivery, purges, and removes listeners

Capture and delivery boundaries

  • iOS: pinned KSCrash 2.6.0 Mach/signal and uncaught Objective-C/C++ monitors. The adapter registers only its four fatal C monitors and a fixed writer; it never installs the standard report writer or device-information sidecars. It records only bounded physical frames and technical module identifiers. It refuses installation when another KSCrash recorder already owns the process. Do not combine competing crash handlers without validating their integration. Attached debuggers can intercept fatal signals; verify a release app launched without a debugger.
  • Android 12 / API 31 and later: uses the OS ApplicationExitInfo native-crash reason and tombstone stream on relaunch. It does not install an NDK signal handler or collect JVM exceptions, ANRs, low-memory exits, or ordinary process kills. The OS rotates tombstones; observed native crashes can have unavailable frames. Older Android versions report unsupported. Initialize in the primary application process only; shared multi-process collection is not supported.
  • Capture starts after successful native configuration from a consented JavaScript launch. Crashes before initialization and processes never relaunched are outside delivery coverage. No network request runs in the fatal callback. iOS force kills, watchdog/OOM exits and nonfatal exceptions are outside the collector's scope.
  • Private queue: at most 10 reports / 256 KiB, 80 physical frames per report, seven-day lifetime, ten persisted attempts. Each foreground pass sends at most three reports; a one-minute timer and foreground transitions resume work. Exponential retry delay is persisted. Network failures, 429 and 5xx retain a report; terminal acknowledgements or other HTTP refusals remove it. A 401/403 disables and purges the current binding.
  • Native HTTP uses an eight-second whole-request timeout, refuses redirects, omits cookies, and caps acknowledgements at 16 KiB. HTTPS is required except loopback development origins. A custom fetch override assumes responsibility for equivalent redirect, cookie, cancellation and timeout guarantees.
  • Reports bind the complete ingest key and host. Key rotation or a different origin purges incompatible records. Retries preserve the original event UUID, timestamp, release, environment and version even after an app update. Ambiguous delivery can repeat the same UUID; server ingest deduplicates it.

Privacy and symbols

The SDK does not retain raw dumps, registers, memory pages, exception messages, thread names, device identifiers, file paths, user traits, emails, or analytics person/session IDs. Android's OS-owned tombstone is read transiently through a bounded parser; this library does not control the operating system's own diagnostic storage. The iOS crash writer uses a preopened private file, fixed fields, and a checksum; incomplete records are discarded. SDK data is excluded from backups. Storage errors fail closed and report through onError; a killed process or failing disk can prevent durable cleanup. Already delivered data cannot be retracted by device opt-out.

Events use a per-crash event UUID and a static fatal message, with no anonymous, person or session identifiers. native_coverage distinguishes physical, partial and unavailable frames. Physical frames do not imply complete call stacks. These events do not establish unique affected people or session correlation; existing IP-derived aggregates remain a proxy. Fingerprints use physical PCs and module identifiers, so grouping is exact-build scoped; it does not promise cross-build equivalence. Supply only static technical release/version labels, never user data.

Export the exact shipped binary to Breakpad symbols using Mozilla dump_syms, then upload through the authenticated Build artifacts workflow. Preserve its MODULE identifier and architecture. iOS uses the Mach-O UUID; Android converts the ELF build ID to the matching Breakpad module identifier. No dSYM, ELF binary or raw tombstone is sent through an ingest key. Symbol files are private Management API artifacts. Function names and source paths in uploaded symbols are visible only through authorized error inspection.

Test a native fatal crash, relaunch offline, verify the private queue, then grant both choices and reconnect. In Errors, confirm one event, the original release, and the function/line from that exact build's symbols. Repeat with pending/revoked consent, an updated release, an invalid key, and missing symbols. Ordinary captureException() calls do not establish native crash collection.