# BugTape for React Native / Expo

One TypeScript file, no native module, no linking. [`bugtape.ts`](./bugtape.ts) posts to
the same `POST /v1/ingest` endpoint the web SDK uses, with `platform: "react-native"`,
so React Native issues land in the same inbox as web and server issues.

Works in Expo (managed and dev client) and bare React Native.

## Install

1. Copy [`bugtape.ts`](./bugtape.ts) into your app, e.g. `src/bugtape.ts`.
2. Replace **one line** — the one marked `REPLACE THIS ONE LINE` near the top:

   ```diff
   - declare const Platform: RNPlatform | undefined;
   + import { Platform } from 'react-native';
   ```

   Leave the rest of the file alone. The `RNPlatform` / `RNErrorUtils` / `BTResponse`
   types are used throughout and must stay. `ErrorUtils` is a React Native global and
   needs no import; `fetch` and `require` are declared at module scope, so they shadow
   the real globals instead of conflicting with them.

   Nothing needs to change for the import to type-check: the file casts `Platform`
   through `unknown`, so React Native's per-OS `Platform` union is accepted as-is.
3. Start it once, at the top of your entry file (`index.js` or `App.tsx`), before
   anything else can throw:

   ```ts
   import { BugTape } from './bugtape';

   BugTape.start({ apiKey: 'bt_live_your_key_here', release: '1.4.0', environment: 'production' });
   ```

4. Call `identify` after sign-in:

   ```ts
   BugTape.identify({ userId: user.id, email: user.email, name: user.name });
   ```

## API

| Call | What it does |
|------|--------------|
| `BugTape.start({ apiKey, endpoint?, release?, environment?, platform? })` | Installs the global error handler and rejection tracker. `endpoint` defaults to `https://app.bugtape.ai/v1/ingest`. `platform` defaults to `'react-native'`. |
| `BugTape.identify({ userId?, email?, name? })` | Attaches an end user to every later report. Call with `{}` on sign-out. |
| `BugTape.breadcrumb(message, level?)` | Ring buffer of the last 100. Levels: `log`, `info`, `warn`, `error`, `debug`. |
| `BugTape.capture(error, { screen?, severity?, extra? })` | Reports a handled error now. |
| `BugTape.captureMessage(title, { screen?, severity?, extra? })` | Reports a message now. |

```ts
BugTape.breadcrumb('tapped Pay');

try {
  await checkout.pay();
} catch (err) {
  BugTape.capture(err, { screen: 'Checkout', extra: { cartId } });
}
```

## Expo note

`release` defaults to `expo-constants`' `expoConfig.version`, looked up in a `try/catch`
so bare RN apps do not need the package. If the lookup fails the release is `unknown` —
**pass `release` explicitly in `start()`** if you want release tracking, which is the
normal case for bare RN and for EAS builds where you want the build number too.

Nothing else is Expo-specific. There is no native module, no config plugin, no
`expo prebuild`, and it works in Expo Go.

## What is captured

| Source | How |
|--------|-----|
| Uncaught JS errors | `ErrorUtils.setGlobalHandler`. Your previous handler is called afterwards, so the red box in dev and the crash in release still happen. |
| Unhandled promise rejections | `HermesInternal.enablePromiseRejectionTracker({ allRejections: true })` when Hermes is present, else `global.onunhandledrejection`. Both are guarded — if neither exists, nothing breaks. |
| Handled errors | `BugTape.capture(err, { screen })`. |
| Breadcrumbs | Last 100, sent as `console:<level>` events. |
| Device context | `device` (`Platform.constants.Model`, else `Platform.OS`), `osVersion` (`Platform.Version`), `release`, `environment`, `sessionId` (one per launch). |

## Limits

| Not captured | Why |
|--------------|-----|
| Session replay | No DOM to record. rrweb has no React Native equivalent; native view capture needs a real native module. |
| Network waterfall | Would require monkey-patching global `fetch`/`XMLHttpRequest`, which changes your app's networking. Log the calls you care about with `BugTape.breadcrumb`. |
| Native crashes | A JS-only handler cannot see an Objective-C or Java crash. For the iOS native side use [`../ios/BugTape.swift`](../ios/BugTape.swift) alongside this file. |
| Console output | `console.log` is not intercepted. Use `BugTape.breadcrumb`. |

**Fatal errors are best-effort.** After your handler runs, the previous handler is
called and in a release build the app terminates. The in-flight `fetch` may not leave
the device. Non-fatal errors, rejections and handled errors are reliable.

Failed sends are retried once, then held in an **in-memory** queue (max 20) and flushed
with the next report. There is no disk queue — that would need `AsyncStorage`, a
dependency this file deliberately avoids. A hard crash loses whatever is queued.

## Grouping: why `screen` matters

BugTape's fingerprint is `errorType + normalizedMessage + urlPattern`, where `errorType`
comes from a `^(\w+Error):` title prefix. The file guarantees the title shape
(`err.name` sanitised to end in `Error`; a bare `Error` becomes `AppError`) and sets
`url` to `app://<screen>`.

**Pass `screen` on every `capture` call.** Without it the report is `app://unknown`, and
unrelated failures across the app can collapse into one issue.

## Seeing it in the console

1. **Issues** — set the **Platform** filter to **React Native**. Rows carry a platform
   tag; an issue hit on both web and React Native shows both and stays one issue.
2. **Bug detail** — the **Platform** fact row, plus a platform/OS/device breakdown on
   the **Impact** tab.
3. **Affected users** — one row per `identify()` user, with platforms and last release.
4. **User timeline** — everything one user hit across every bug.

The console **Setup** page's **Send a test event** has a React Native tab: it mints a
real occurrence through the normal pipeline before you install anything.

See [../../platforms.md](../../platforms.md) for the full field mapping.
