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

@bugmojo/react-native

v0.1.1

Published

Official BugMojo React Native + Expo SDK — shake-to-report, masked screenshot replay, console/network capture, PII+secret redaction, and remote-config-gated bug capture for mobile apps.

Readme

@bugmojo/react-native

Official React Native + Expo SDK for BugMojo — shake-to-report, masked screenshot "replay", automatic console + network capture, on-device PII & secret redaction, and remote-config-gated bug capture. Expo-first, mostly JavaScript, zero required native code.

npm version license: MIT

Docs & dashboard: https://www.bugmojo.com

Why

Mobile bug reports usually arrive as "it broke" with no logs, no device info, and no picture. This SDK captures the console, network activity, device metadata, and (opt-in) masked screenshots at the moment a user shakes their phone — redacts PII and provider secrets on the device, then files everything into your BugMojo inbox. It reuses the exact redaction engine and ingest endpoint (POST /api/widget/v1/captures) as the BugMojo web widget, so mobile bugs land next to your web bugs — already redacted, already sampled, already gated by the same remote-config kill switch.

Requirements

  • Expo SDK 50+ (expo >= 50) — or bare React Native 0.73+
  • React 18+
  • Node 18+ (for expo prebuild running the config plugin)

expo-device, expo-constants, expo-sensors, and react-native-view-shot are optional peer dependencies — each missing module just disables its feature (no expo-sensors → no shake gesture).

Install

# Expo (recommended — installs compatible versions)
npx expo install @bugmojo/react-native expo-device expo-constants expo-sensors react-native-view-shot
# or plain package managers
pnpm add @bugmojo/react-native
npm install @bugmojo/react-native
yarn add @bugmojo/react-native

Expo config plugin (zero native code)

Add the plugin to your app.json / app.config.js so the iOS motion permission, the Apple Privacy Manifest entries, and the Android sensor permission are wired for you:

{
  "expo": {
    "plugins": [
      ["@bugmojo/react-native", { "motionUsageDescription": "Shake to report a bug." }]
    ]
  }
}

Then run npx expo prebuild (or build with EAS). The plugin:

  • sets NSMotionUsageDescription (iOS accelerometer permission copy for shake-to-report);
  • merges the SDK's privacy-manifest entries (required-reason APIs + collected data types) into expo.ios.privacyManifests, which prebuild aggregates into your app's generated PrivacyInfo.xcprivacy;
  • adds the Android HIGH_SAMPLING_RATE_SENSORS permission (normal, non-dangerous — Android 12+).

Bare React Native apps (no prebuild): copy the entries from ios/PrivacyInfo.xcprivacy (shipped in this package) into your own privacy manifest and set NSMotionUsageDescription in Info.plist yourself.

Quickstart

import { BugMojoProvider, useBugMojo } from '@bugmojo/react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as ReactNative from 'react-native';
import * as Sensors from 'expo-sensors';
import * as Device from 'expo-device';
import Constants from 'expo-constants';

export default function App() {
  return (
    <BugMojoProvider
      projectId="YOUR_EMBED_TOKEN"            // PUBLIC data-project token (not a secret)
      apiBase="https://www.bugmojo.com"        // where BugMojo is served
      env="production"
      release="1.4.2"
      store={AsyncStorage}                     // persists the last-good config cache
      // Inject the optional modules you installed — Metro/Hermes release builds cannot
      // resolve modules by name at runtime, so this is the reliable path (see below).
      nativeModules={{
        sensors: Sensors,
        device: Device,
        constants: Constants,
        reactNative: ReactNative,
      }}
    >
      <RootNavigator />
    </BugMojoProvider>
  );
}

Shake the device to open the report modal, or file a report programmatically:

function PayButton() {
  const { report, identify, track } = useBugMojo();
  identify({ id: user.id, email: user.email });
  track('checkout_started', { cartId });
  // ...
  await report({ title: 'Payment button unresponsive' });
}

Features

  • Shake-to-report — accelerometer state machine (threshold/window/cooldown) opens the built-in report modal; fully configurable or replaceable with your own UI.
  • Masked screenshot "replay" — opt-in rolling buffer of periodic screenshots (rrweb DOM recording doesn't exist on native); fails closed if masking can't be confirmed before rasterizing.
  • Console + network capture — patches console.* and fetch/XHR into ring buffers; attached to every report.
  • On-device PII + secret redaction — emails, cards (Luhn-gated), SSNs, phones, auth headers, JWTs, and provider secrets (AWS/Stripe/GitHub/Slack/Google/PEM) are scrubbed before anything leaves the device, using the same engine as the BugMojo web SDK (bundled into dist — no extra dependency).
  • Remote-config gating — capture runs only when your project's CaptureRuleset says enabled, the session passes deterministic sticky sampling, and the user's cohort matches. Fail-closed with a last-good cache.
  • Error boundary<BugMojoErrorBoundary> auto-files a bug with the React component stack.
  • Crash-isolated — every capture hook runs behind a guard with a circuit breaker; a faulting hook self-disables instead of crashing your app.
  • Expo config plugin — permissions + privacy manifest handled at prebuild, no native edits.

Native module injection (read this for production builds)

Metro only bundles modules it can see in a static import/require, and Hermes/JSC release bundles don't expose a runtime string-resolving require. The SDK therefore cannot reliably "discover" your optional peers on its own in a production build — you import them and hand them over:

import * as Sensors from 'expo-sensors';
import * as ViewShot from 'react-native-view-shot';
import * as Device from 'expo-device';
import Constants from 'expo-constants';
import * as ReactNative from 'react-native';

// Either as a provider/client option:
<BugMojoProvider nativeModules={{ sensors: Sensors, viewShot: ViewShot, device: Device, constants: Constants, reactNative: ReactNative }} ... />

// ...or standalone (e.g. at app bootstrap, before createBugMojoClient):
import { registerNativeModules } from '@bugmojo/react-native';
registerNativeModules({ sensors: Sensors, viewShot: ViewShot });

Every entry is optional — omit what you didn't install and that feature quietly turns off. A lazy require fallback exists for dev/test runtimes, but do not rely on it in release builds.

Masking sensitive views

A screenshot is an opaque image the server can never redact after upload, so masking is entirely the client's responsibility.

Unsafe-by-default. Screenshot replay only runs when you set both enableScreenshotReplay and acknowledgeScreenshotRisk. This is a deliberate interlock: any sensitive view you forget to wrap would leak its raw pixels. Enable it only after auditing your screens.

Wrap secret UI so it never appears in a frame:

import { BugMojoMask } from '@bugmojo/react-native';

<BugMojoMask label="Card number">
  <Text>{card.number}</Text>
</BugMojoMask>

For text fields, use BugMojoInput (a drop-in TextInput replacement). It honors your company's server-side mask_all_inputs policy — when that policy is on, every BugMojoInput is masked during a capture without per-field wrapping, and fields matching mask_selector / block_selector (by testID / accessibilityLabel) are masked too:

import { BugMojoInput } from '@bugmojo/react-native';

<BugMojoInput testID="card-number" value={card.number} onChangeText={setCard} />

The SDK cannot auto-mask arbitrary host <TextInput>s it never sees — those must be wrapped in <BugMojoMask> or swapped for BugMojoInput.

How masking is proven safe: during each screenshot the SDK turns masking on, waits for a real paint (chained animation frames) and for every mounted mask/input to acknowledge it committed its opaque overlay, then rasterizes. If that commit can't be confirmed in time, the frame is skipped (fail-closed) rather than shipped possibly-unmasked. Between captures everything renders normally.

Screenshot replay

Give the SDK a view to rasterize (typically your root view) plus the react-native-view-shot module, and switch on both halves of the interlock:

import { useRef } from 'react';
import { View } from 'react-native';
import * as ViewShot from 'react-native-view-shot';
import { BugMojoProvider } from '@bugmojo/react-native';

export default function App() {
  // Pass the RefObject itself — react-native-view-shot unwraps `.current` at capture time.
  const rootRef = useRef<View>(null);

  return (
    <View ref={rootRef} collapsable={false} style={{ flex: 1 }}>
      <BugMojoProvider
        projectId="YOUR_EMBED_TOKEN"
        apiBase="https://www.bugmojo.com"
        nativeModules={{ viewShot: ViewShot }}
        screenshotRef={rootRef}          // what gets rasterized
        enableScreenshotReplay           // opt in to the masked screenshot loop
        acknowledgeScreenshotRisk        // REQUIRED: you accept the pixel-leak risk
      >
        <RootNavigator />
      </BugMojoProvider>
    </View>
  );
}

Headless equivalent: client.setScreenshotRef(rootRef).

Error boundary

import { BugMojoErrorBoundary } from '@bugmojo/react-native';

<BugMojoErrorBoundary fallback={({ error, reset }) => <Crash onRetry={reset} />}>
  <Screen />
</BugMojoErrorBoundary>

A caught crash auto-files a bug with the React component stack. Props: fallback (node or render-prop { error, reset }), title, onError(error, componentStack).

Headless (no React)

import { createBugMojoClient } from '@bugmojo/react-native';

const bugmojo = createBugMojoClient({
  projectId: 'YOUR_EMBED_TOKEN',
  apiBase: 'https://www.bugmojo.com',
});
await bugmojo.report({ title: 'Background sync failed' });
// later: bugmojo.destroy()

createBugMojoClient(options, { store }) accepts the same options as the provider (including nativeModules) and returns a BugMojoClient with report(), identify(), setRelease(), track(), setScreenshotRef(), and destroy().

Privacy & security

  • Redaction runs on-device before upload — emails, cards (Luhn-gated), SSNs, phones, auth headers, JWTs, and provider secrets (AWS/Stripe/GitHub/Slack/Google/PEM) are scrubbed from console + network data.
  • URLs are scrubbed — sensitive query params masked, fragments dropped.
  • Fail-closed remote config — capture only runs when your CaptureRuleset says enabled and the session passes deterministic sticky sampling and cohort match. A config outage past 24h disables capture.
  • Network bodies are OFF by default (opt-in), matching the web SDK.
  • No tracking — the shipped privacy manifest declares NSPrivacyTracking = false; collected data is not linked to identity by default.

Configuration reference

Options accepted by <BugMojoProvider> and createBugMojoClient:

| Option | Default | Description | | --- | --- | --- | | projectId / embedToken | — | PUBLIC project token (required). | | apiBase | — | BugMojo origin (required), e.g. https://www.bugmojo.com. | | env | — | Environment label forwarded to remote config for gating. | | release | — | Release/version string for release gating + report context. | | user | — | Initial reporter identity ({ id, email, name, cohort }). | | customData | — | Arbitrary metadata attached to every submission. | | nativeModules | — | Statically-imported optional modules (sensors, viewShot, device, constants, reactNative). Strongly recommended in production. | | enableShakeToReport | true | Shake gesture opens the report modal. | | captureConsole | true | Patch console.* into the capture buffer. | | captureNetwork | true | Patch fetch + XHR into the capture buffer. | | enableScreenshotReplay | false | Periodic masked screenshots (needs a view-shot ref and acknowledgeScreenshotRisk). | | acknowledgeScreenshotRisk | false | Required with enableScreenshotReplay — you accept that any un-wrapped sensitive view can leak pixels. | | screenshotIntervalMs | 2000 | Interval between replay frames. | | screenshotBufferSize | 10 | Max frames kept in the rolling replay buffer. | | shakeThreshold | 1.8 | Accelerometer magnitude (g) for a shake. | | sessionId | random | Persist your own for cross-launch sticky sampling. | | onOpen / onClose / onSubmit | — | Lifecycle callbacks (modal opened/closed, report submitted). |

Provider-only props:

| Prop | Default | Description | | --- | --- | --- | | store | in-memory | KV store for the last-good config cache (pass AsyncStorage). | | renderModal | true | Set false to supply your own UI and drive it via useBugMojo(). | | screenshotRef | — | RefObject of the view rasterized for screenshot replay. |

useBugMojo()

| Member | Description | | --- | --- | | ready | true once remote config resolved and the SDK booted. | | capturing | true when this session passed the capture gate (enabled + sampling + cohort). | | report(input) | File a report — { title, description?, category?, reporterEmail?, context? }. Always resolves with { ok, number?, pendingModeration?, error? }. | | identify(user) / setUser(user) | Merge reporter identity. | | setRelease(release) | Update the release string used for gating + reports. | | track(name, data?) | Record a breadcrumb (last 50 are attached to reports). | | open() / close() | Open/close the built-in report modal. |

Troubleshooting / FAQ

Shake doesn't open the modal. In order: (1) install expo-sensors and inject it via nativeModules={{ sensors }} — in release builds the SDK cannot find it otherwise; (2) make sure the config plugin ran (npx expo prebuild) so iOS has NSMotionUsageDescription; (3) shake on a physical device — most simulators have no accelerometer; (4) check you didn't set enableShakeToReport={false}. useBugMojo().report() and open() always work as a fallback.

Reports have no screenshots. Screenshot replay needs all four: react-native-view-shot installed and injected (nativeModules.viewShot), a screenshotRef (or client.setScreenshotRef), and both enableScreenshotReplay + acknowledgeScreenshotRisk. Also note frames are deliberately skipped when mounted masks can't confirm their overlay committed in time (fail-closed by design).

Reports have no console/network logs. Passive capture only installs when the capture gate passes — your project's ruleset must be enabled, the session must fall inside sample_rate, and the user's cohort must match. Check useBugMojo().capturing; if false, review the capture ruleset in your BugMojo project settings.

Does it record video or use rrweb? No. There is no DOM on native, so "replay" is a rolling buffer of masked screenshots. rrweb is never bundled into this SDK.

Do I need to eject or write native code? No. Everything native comes from the optional Expo peer modules plus the config plugin; expo prebuild / EAS Build handles the rest. Bare RN apps work too — set the Info.plist key and privacy manifest manually (see the config plugin section).

Device-lab testing

The pure JS logic (config gating, sticky sampling, redaction reuse, capture buffer, payload builder, shake detector, mask controller) is unit-tested. The native paths — the expo-sensors accelerometer, react-native-view-shot rasterization, ErrorUtils global handler, AppState background flush, and the Expo config plugin prebuild — must be validated on physical iOS + Android devices and against a TestFlight / Play Internal build before you rely on them in production. Injecting nativeModules (above) removes the largest source of on-device surprise.

Related packages

| Package | Use it for | | ------- | ---------- | | @bugmojo/widget | Framework-agnostic on-site feedback + capture widget core | | @bugmojo/react | React / Next.js SDK — provider, error boundary, hooks | | @bugmojo/cli | Pull a bug's Playwright repro pack, verify fixes locally | | @bugmojo/mcp-server | Connect AI coding agents (Claude Code, Cursor) to BugMojo |

License

MIT © Softech Infra — see LICENSE. Docs & issues at viveksinra/bugmojo-sdk.