@wuguishifu/crash-observer
v0.0.1
Published
Expo native module that captures native-layer crashes (uncaught exceptions and POSIX signals) and delivers them to your own logging system on the next launch
Downloads
134
Maintainers
Readme
@wuguishifu/crash-observer
An Expo native module that captures crashes at the native layer (uncaught exceptions and, optionally, POSIX signals) and delivers them to your logging system on the next launch.
The library has no networking and no opinions about where reports go. A native crash kills the process, so reports can't reliably be sent in-process anyway — instead, the native layer persists each crash as a file, and on the next launch your JS handler receives the reports and sends them wherever you like (Sentry, your analytics pipeline, a plain HTTP endpoint, ...).
Quick start
import * as CrashObserver from '@wuguishifu/crash-observer';
// at app startup
CrashObserver.start({
onReport: async (report) => {
// plug in your own transport - throwing keeps the report for a retry
await myLogger.send('native-crash', report);
},
});
// attach whatever identifies the session in your system
CrashObserver.setContext({ userId: user.id, sessionId });
// leave a trail for debugging
CrashObserver.addBreadcrumb({ message: 'opened reader', category: 'navigation' });Delivery semantics
- Reports are written to app-private storage at crash time, one file per crash (multiple undelivered crashes don't overwrite each other).
- On
start({ onReport }), every pending report is passed to your handler, oldest first. - If the handler resolves, the report is deleted. If it throws, the report is kept and retried on the next
startorflushPendingReportscall — at-least-once delivery, so make your sink idempotent (report.idis stable). - At most
maxPendingReports(default 10) reports are kept; the oldest are pruned. start()resolves after delivery finishes. Call it withoutawaitif you don't want delivery to block startup.
API
| Function | Description |
| --- | --- |
| start(options) | Installs crash handlers and delivers pending reports to options.onReport. Returns { delivered, failed }. |
| flushPendingReports(handler?) | Re-runs delivery (e.g. after connectivity returns). Defaults to the onReport from start. |
| getPendingReports() | Returns undelivered reports, oldest first, without deleting them. |
| deleteReport(id) / deleteAllReports() | Manual cleanup if you manage delivery yourself. |
| setContext(values) | Merges values into the context attached to future reports. |
| clearContext() | Removes all context values. |
| addBreadcrumb(crumb) | Records a breadcrumb (message, category, level, data). Kept in memory, capped at maxBreadcrumbs. |
| clearBreadcrumbs() | Removes all breadcrumbs. |
| simulateCrash(type?) | Actually crashes the app to test the pipeline. Test builds only. |
StartOptions
| Option | Default | Description |
| --- | --- | --- |
| onReport | – | Your delivery handler. |
| installSignalHandlersIOS | false | Also capture SIGSEGV/SIGABRT/etc. on iOS. Opt-in because signal handlers run in a constrained environment and can conflict with other crash SDKs. |
| maxBreadcrumbs | 200 | In-memory breadcrumb cap. |
| maxPendingReports | 10 | On-disk undelivered report cap. |
CrashReport
{
id: string; // stable unique id (dedupe key)
timestamp: number; // epoch ms at capture time
platform: 'ios' | 'android';
kind: 'exception' | 'signal';
name: string; // e.g. NSRangeException, java.lang.NullPointerException, SIGSEGV
message: string;
stack: string;
thread: string;
app: { version, build, bundleId };
device: { model, manufacturer, os };
context: Record<string, unknown>; // whatever you passed to setContext
breadcrumbs: Breadcrumb[];
}Testing the pipeline
simulateCrash schedules a real native crash off the JS call stack (so React error boundaries can't catch it). Relaunch the app afterwards and your onReport handler will receive the report.
import { simulateCrash, IOS_SIMULATED_CRASH_TYPES, ANDROID_SIMULATED_CRASH_TYPES } from '@wuguishifu/crash-observer';
simulateCrash('null_pointer');Per-platform types are exported as IOS_SIMULATED_CRASH_TYPES and ANDROID_SIMULATED_CRASH_TYPES, with display labels in SIMULATED_CRASH_TYPE_LABELS.
What this library does not do
- JS error handling — pair it with an ErrorBoundary /
ErrorUtilshandler for JS-level errors. - Symbolication — stacks are raw native stacks; symbolicate server-side if you need to.
- Crashes handlers can't see — watchdog terminations, iOS OOM kills, and
fatalError-style traps (unless signal handlers are enabled) are not captured. - Persistence of context/breadcrumbs across launches — they live in memory and are baked into the report at crash time; re-set them on each launch.
