@backtrack-js/browser 0.4.2 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,364 +1,507 @@
1
- # Backtrack
2
-
3
- A flight recorder for your web app. Replay the exact moments before a bug, privately and offline.
4
-
5
- [![npm version](https://img.shields.io/npm/v/@backtrack-js/browser.svg)](https://www.npmjs.com/package/@backtrack-js/browser)
6
- [![license](https://img.shields.io/badge/license-Free_to_Use-blue.svg)](LICENSE)
7
-
8
- <!-- TODO: add a GIF or screenshot of the Incident Viewer here (this is the highest-impact change) -->
9
- <!-- ![Backtrack Incident Viewer](./docs/viewer.gif) -->
10
-
11
- Your users hit a bug you can't reproduce. Error trackers tell you what broke, but not what the user did before it did.
12
-
13
- Backtrack keeps the last few minutes of DOM changes, clicks, network calls, console logs and errors in a rolling local buffer. When something breaks (or the user asks for help), it freezes that buffer into a replayable incident file. Open it in the built-in viewer and press play.
14
-
15
- - **Private by default.** Passwords, card numbers, CPFs and form inputs are masked. Request and response bodies are off unless you turn them on. Nothing leaves the browser unless you export an incident or configure a transport.
16
- - **Light on the page.** Recording runs into a ring buffer in IndexedDB with automatic retention, so old events are dropped instead of piling up.
17
- - **Framework-agnostic.** Pure TypeScript core, no React or other framework in your client bundle. ESM and CommonJS builds.
18
- - **Replay offline.** An interactive viewer with DOM playback, a synchronized timeline, and console and network panels. No account, no server.
19
- - **Readable production stack traces.** `backtrack symbolize` maps minified frames like `app.min.js:1:48213` back to your original TypeScript, with the surrounding source lines.
20
- - **Send it where you want.** `transport` and `beforeSend` let you forward incidents to your own backend or observability stack.
21
-
22
- ---
23
-
24
- ## Why Backtrack?
25
-
26
- | Feature | Error trackers | Hosted session replay | Backtrack |
27
- | :--- | :---: | :---: | :---: |
28
- | Shows the error and stack trace | ✅ | ✅ | ✅ |
29
- | Shows what the user did before it | ⚠️ limited breadcrumbs | ✅ | ✅ |
30
- | Data stays in the browser by default | ❌ | ❌ | ✅ |
31
- | Works fully offline | ❌ | ❌ | ✅ |
32
- | Only records around incidents | n/a | ❌ usually continuous upload | ✅ rolling buffer |
33
-
34
- Backtrack doesn't replace your error tracker. It gives you the context your error tracker is missing, and you can correlate the two with `traceId` and `release` (see [Release and trace correlation](#release-and-trace-correlation)).
35
-
36
- ---
37
-
38
- ## Installation
39
-
40
- ```bash
41
- # npm
42
- npm install @backtrack-js/browser
43
-
44
- # yarn
45
- yarn add @backtrack-js/browser
46
-
47
- # pnpm
48
- pnpm add @backtrack-js/browser
49
- ```
50
-
51
- ---
52
-
53
- ## Quick Start
54
-
55
- ```typescript
56
- import { Backtrack } from '@backtrack-js/browser';
57
-
58
- const recorder = new Backtrack({
59
- bufferMinutes: 5, // keep the last 5 minutes in the rolling buffer
60
- afterErrorSeconds: 15, // keep recording 15s after an error
61
- });
62
-
63
- await recorder.start();
64
-
65
- // Optional: capture manually, e.g. from a "Report a problem" button
66
- const incidentId = await recorder.capture('User reported payment bug', 30);
67
- ```
68
-
69
- Then inspect it:
70
-
71
- ```bash
72
- npx backtrack
73
- ```
74
-
75
- Drag an exported incident `.json` into the viewer and hit play.
76
-
77
- ---
78
-
79
- ## Readable production stack traces (`backtrack symbolize`)
80
-
81
- Production bundles are minified, so a stack frame like `app.3f9a1c.js:1:48213` tells you nothing. `backtrack symbolize` reads your source maps and rewrites the incident's errors with the original file, line, column and the surrounding source code.
82
-
83
- ```bash
84
- npx backtrack symbolize <incident> --sourcemaps <directory> [options]
85
- ```
86
-
87
- | Argument / option | Description |
88
- | :--- | :--- |
89
- | `<incident>` | Path to an exported incident: `.json`, `.ffr.json`, `.gz` or `.ffr.json.gz`. Gzip is detected automatically. |
90
- | `--sourcemaps <directory>` | Directory containing your `.js.map`, `.mjs.map` and `.cjs.map` files. Searched recursively (e.g. `./dist`, `./build`). |
91
- | `--output <file>` | Custom output path. Defaults to `<name>.symbolicated.ffr.json` next to the original. The output can never overwrite the input file. |
92
- | `--context-lines <number>` | Lines of original source shown before and after the failing line. Range 0–10, default 3. |
93
- | `-h, --help` | Show help. |
94
-
95
- ### Example
96
-
97
- ```bash
98
- npx backtrack symbolize ./incidents/checkout-bug.ffr.json --sourcemaps ./dist --context-lines 5
99
- ```
100
-
101
- ```text
102
- [Backtrack] Symbolication concluída.
103
-
104
- Incidente: inc_1727500000_3f2a
105
- Erros encontrados: 1
106
- Frames resolvidos: 4
107
- Frames não resolvidos: 0
108
- Saída: /projects/app/incidents/checkout-bug.symbolicated.ffr.json
109
- ```
110
-
111
- If some frames have no matching source map, the command reports a partial symbolication with warnings instead of failing.
112
-
113
- Open the `.symbolicated.ffr.json` file in the viewer (`npx backtrack`) and the error tab shows the resolved stack trace, with the failing line highlighted:
114
-
115
- ```text
116
- applyCoupon src/cart/coupon.ts:42:18
117
-
118
- 40 function applyCoupon(cart, coupon) {
119
- 41 // coupon discount might be missing from API
120
- 42 const off = coupon.discount.percent; <-- highlighted
121
- 43 return cart.total * (1 - off / 100);
122
- 44 }
123
- ```
124
-
125
- ### What it does (and doesn't do)
126
-
127
- - **Validates safely:** Reads the incident (up to 50 MB, gzip supported) and validates its schema with a strict validator. The original file is never modified.
128
- - **Maps V8 frames:** Finds error events that carry a stack and maps each minified frame with [@jridgewell/trace-mapping](https://github.com/jridgewell/trace-mapping).
129
- - **Extracts source context:** Extracts the original code from `sourcesContent`, so your source maps must include it.
130
- - **Doesn't leak local paths:** File paths are normalized (e.g. `C:\Users\alice\...` or `/home/bob/...` become paths starting at `src/`).
131
- - **Atomic write:** Writes the result atomically (to a `.tmp` file, then renames) and stores it in a new `resolvedStack` field.
132
-
133
- ---
134
-
135
- ## Privacy & Data Masking
136
-
137
- Backtrack is designed with strict security defaults:
138
-
139
- - **Inputs & Forms:** All `<input>`, `<textarea>`, and `<select>` values are masked by default (`maskAllInputs: true`).
140
- - **Network Headers:** Authorization, Cookies, API keys, and Bearer tokens are automatically redacted.
141
- - **Sensitive Selectors:** Use standard selectors or CSS classes to protect proprietary or personal data:
142
-
143
- ```typescript
144
- const recorder = new Backtrack({
145
- privacy: {
146
- maskAllInputs: true,
147
- blockMedia: true,
148
- blockSelector: '.backtrack-block, [data-private]', // Completely hides elements in replay
149
- maskTextSelector: '.backtrack-mask, .customer-pII', // Scrambles text content
150
- sanitizeUrl: (url) => {
151
- // Remove sensitive query parameters
152
- url.searchParams.delete('token');
153
- return url.toString();
154
- }
155
- }
156
- });
157
- ```
158
-
159
- ---
160
-
161
- ## Network & Payload Capture
162
-
163
- Network requests and responses can be configured using `network` options:
164
-
165
- ```typescript
166
- const recorder = new Backtrack({
167
- network: {
168
- capturePayloads: false, // Bodies are disabled by default for privacy
169
- maxPayloadSize: 64 * 1024
170
- }
171
- });
172
- ```
173
-
174
- - **Bodies are not captured by default (`capturePayloads: false`):** Request and response bodies are disabled by default to prevent unintentional data leakage. HTTP method, URL, status, timing, and sanitized headers continue to be captured.
175
- - **Enabling bodies increases the risk of PII:** Enabling `capturePayloads: true` should be done mindfully according to your compliance and privacy requirements.
176
- - **Sanitization remains active:** Even when payload capture is explicitly enabled, Backtrack continues to sanitize and redact known sensitive keys (passwords, tokens, credentials, etc.) and patterns (Bearer tokens, emails, CPFs, cards).
177
- - **`maxPayloadSize` is a limit per payload, not per incident:** It bounds each individual request or response body (default is `64 * 1024` bytes). Payloads exceeding this limit are truncated safely with a truncation marker.
178
-
179
- ---
180
-
181
- ## Capturing Incidents
182
-
183
- ### Automatic Triggers
184
- Backtrack automatically triggers and freezes an incident when:
185
- 1. An unhandled window exception occurs (`window.onerror`).
186
- 2. An unhandled promise rejection occurs (`unhandledrejection`).
187
- 3. An HTTP request returns a matching error status (`captureHttpStatus`).
188
-
189
- ### Manual Capture
190
- You can trigger incident recording manually anywhere in your code (e.g. from an error boundary or user feedback modal):
191
-
192
- ```typescript
193
- // Capture the last 60 seconds of context
194
- const incidentId = await recorder.capture('Checkout error', 60);
195
-
196
- // Export the complete incident artifact as a JSON object
197
- const artifact = await recorder.exportIncident(incidentId);
198
-
199
- // Download or send artifact to your support/logging service
200
- const jsonBlob = new Blob([JSON.stringify(artifact, null, 2)], { type: 'application/json' });
201
- ```
202
-
203
- ### Exception Capture
204
- In React Error Boundaries or try/catch blocks:
205
-
206
- ```typescript
207
- recorder.captureException(error, {
208
- source: 'react',
209
- componentStack: errorInfo.componentStack
210
- });
211
- ```
212
-
213
- ---
214
-
215
- ## Remote Transport & beforeSend
216
-
217
- Backtrack allows applications to automatically dispatch finalized incidents to any remote endpoint or telemetry backend using `transport` and `beforeSend`:
218
-
219
- ```typescript
220
- const recorder = new Backtrack({
221
- transport: {
222
- async send(artifact) {
223
- const response = await fetch('/api/backtrack/incidents', {
224
- method: 'POST',
225
- headers: {
226
- 'content-type': 'application/json'
227
- },
228
- body: JSON.stringify(artifact)
229
- });
230
-
231
- if (!response.ok) {
232
- throw new Error(`Upload failed: ${response.status}`);
233
- }
234
- }
235
- },
236
-
237
- beforeSend(artifact) {
238
- if (artifact.environment.url.includes('/healthcheck')) {
239
- return null;
240
- }
241
-
242
- return artifact;
243
- }
244
- });
245
- ```
246
-
247
- ### Dispatch Flow & Semantics
248
-
249
- - **Sent Only When Finalized:** Incidents are dispatched only after they have been completely finalized and safely written to IndexedDB.
250
- ```text
251
- incident finalized
252
- ↓
253
- build artifact
254
- ↓
255
- beforeSend
256
- ↓
257
- null? cancel upload
258
- ↓
259
- transport.send
260
- ```
261
- - **Role of `beforeSend`:**
262
- - Affects **only** the automated transmission via `transport.send`.
263
- - Does **not** mutate the artifact stored in local IndexedDB.
264
- - Does **not** affect `getArtifact()` or explicit file downloads via `exportIncident()`.
265
- - Returning `null` cancels the upload.
266
- - If `beforeSend` throws an error, the upload is aborted cleanly without affecting the application or the recorded incident.
267
- - **Fault Isolation:** Network or transport errors will never break the host application, reject user captures, delete local incidents, prevent finalization, or trigger unhandled promise rejections.
268
- - **Concurrency & Delivery Guarantees:**
269
- - Each finalized incident triggers at most one upload attempt.
270
- - Multiple incidents can upload concurrently.
271
- - `stop()` does not wait for pending network uploads.
272
- - There is currently no delivery guarantee (persistent retry queue, offline backoff, etc.); if transport fails, the incident remains saved in local IndexedDB and can be accessed or resent by the application.
273
-
274
- ---
275
-
276
- ## Release and trace correlation
277
-
278
- Backtrack artifacts (`formatVersion: 2`) provide an observability `context` to correlate client-side replays with backend traces, application releases, and environments.
279
-
280
- ### Static Context
281
-
282
- Configure static metadata when initializing Backtrack:
283
-
284
- ```typescript
285
- const recorder = new Backtrack({
286
- context: {
287
- release: 'checkout-web@2.14.0',
288
- environment: 'production',
289
- traceId: '4bf92f3577b34da6a3ce929d0e0e4736',
290
- spanId: '00f067aa0ba902b7'
291
- }
292
- });
293
- ```
294
-
295
- ### Dynamic Context (`getContext`)
296
-
297
- When trace IDs or active environments change dynamically during user navigation (e.g. from OpenTelemetry, Datadog, Sentry, or custom APM SDKs), use `getContext` to evaluate the latest context at incident export time:
298
-
299
- ```typescript
300
- const recorder = new Backtrack({
301
- context: {
302
- release: 'checkout-web@2.14.0',
303
- environment: 'production'
304
- },
305
- getContext: async () => {
306
- const activeSpan = tracer.getActiveSpan();
307
- const spanContext = activeSpan?.spanContext();
308
-
309
- return {
310
- release: 'checkout-web@2.14.0',
311
- environment: 'production',
312
- traceId: spanContext?.traceId,
313
- spanId: spanContext?.spanId
314
- };
315
- }
316
- });
317
- ```
318
-
319
- ### Correlation Semantics
320
-
321
- - **Precedence:** `getContext() > context (static) > environment metadata`.
322
- - **Trace correlation scope:** Backtrack **only correlates** external trace and span identifiers supplied by your application. It does **not** generate backend traces, start trace spans, or perform distributed instrumentation on its own.
323
- - **Normalization:** `traceId` (32 hex characters) and `spanId` (16 hex characters) are validated and normalized to lowercase. A `spanId` cannot be supplied without an accompanying `traceId`.
324
- - **Fault Resilience:** If `getContext()` throws an error or rejects, incident export continues safely using available static context without failing the export or losing incident data.
325
-
326
- ---
327
-
328
- ## Inspecting Incidents (Viewer)
329
-
330
- Backtrack includes an offline visualizer with video replay, console logs, network inspection, and breadcrumbs.
331
-
332
- ### Launch the Viewer CLI
333
- Run the embedded CLI to launch the local visualizer:
334
-
335
- ```bash
336
- npx backtrack
337
- ```
338
-
339
- This starts a local server at `http://localhost:5173/` and opens your default browser. Drag and drop any exported incident `.json` file to replay it!
340
-
341
- ---
342
-
343
- ## API Reference
344
-
345
- ### `Backtrack`
346
-
347
- | Method | Return Type | Description |
348
- | --- | --- | --- |
349
- | `start()` | `Promise<void>` | Initializes storage and begins recording. |
350
- | `stop()` | `Promise<void>` | Stops recording, flushes pending batches to storage, and detaches listeners. |
351
- | `capture(reason?, windowSeconds?)` | `Promise<string>` | Manually captures and finalizes an incident. Returns incident ID. |
352
- | `captureException(error, context?)` | `Promise<string \| undefined> \| void` | Records a caught exception and triggers an incident. |
353
- | `listIncidents()` | `Promise<IncidentSummary[]>` | Lists all stored incidents. |
354
- | `getArtifact(incidentId)` | `Promise<FlightRecorderArtifact>` | Retrieves the full artifact for an incident (v1 or v2). |
355
- | `exportIncident(incidentId)` | `Promise<FlightRecorderArtifact>` | Exports the artifact and marks it finalized. |
356
- | `deleteIncident(incidentId)` | `Promise<void>` | Deletes an incident from local storage. |
357
- | `clear()` | `Promise<void>` | Clears all stored chunks, sessions, and incidents. |
358
- | `getHealth()` | `RecorderHealth` | Returns current buffer status, storage usage, and state. |
359
-
360
- ---
361
-
362
- ## License
363
-
364
- [Backtrack Free-Use License](LICENSE) — Free for personal and commercial integration. Redistribution, copying, or creating derivative/competing works is strictly prohibited.
1
+ # Backtrack
2
+
3
+ A flight recorder for your web app. Replay the exact moments before a bug, privately and offline.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@backtrack-js/browser.svg)](https://www.npmjs.com/package/@backtrack-js/browser)
6
+ [![license](https://img.shields.io/badge/license-Free_to_Use-blue.svg)](LICENSE)
7
+
8
+ <!-- TODO: add a GIF or screenshot of the Incident Viewer here (this is the highest-impact change) -->
9
+ <!-- ![Backtrack Incident Viewer](./docs/viewer.gif) -->
10
+
11
+ Your users hit a bug you can't reproduce. Error trackers tell you what broke, but not what the user did before it did.
12
+
13
+ Backtrack keeps the last few minutes of DOM changes, clicks, network calls, console logs and errors in a rolling local buffer. When something breaks (or the user asks for help), it freezes that buffer into a replayable incident file. Open it in the built-in viewer and press play.
14
+
15
+ - **Private by default.** Passwords, card numbers, CPFs and form inputs are masked. Request and response bodies are off unless you turn them on. Nothing leaves the browser unless you export an incident or configure a transport.
16
+ - **Light on the page.** Recording runs into a ring buffer in IndexedDB with automatic retention, so old events are dropped instead of piling up.
17
+ - **Framework-agnostic.** Pure TypeScript core, no React or other framework in your client bundle. ESM and CommonJS builds.
18
+ - **Replay offline.** An interactive viewer with DOM playback, a synchronized timeline, and console and network panels. No account, no server.
19
+ - **Readable production stack traces.** `backtrack symbolize` maps minified frames like `app.min.js:1:48213` back to your original TypeScript, with the surrounding source lines.
20
+ - **Zero-friction local debugging & AI agents.** Automatic dev-server export saves structured Markdown reports (`.backtrack/latest.md`) and full replays straight to your project disk upon error—no manual downloads or copy-pasting required.
21
+ - **Send it where you want.** `transport` and `beforeSend` let you forward incidents to your own backend or observability stack.
22
+
23
+ ---
24
+
25
+ ## Why Backtrack?
26
+
27
+ | Feature | Error trackers | Hosted session replay | Backtrack |
28
+ | :--- | :---: | :---: | :---: |
29
+ | Shows the error and stack trace | ✅ | ✅ | ✅ |
30
+ | Shows what the user did before it | ⚠️ limited breadcrumbs | ✅ | ✅ |
31
+ | Data stays in the browser by default | ❌ | ❌ | ✅ |
32
+ | Works fully offline | ❌ | ❌ | ✅ |
33
+ | Only records around incidents | n/a | ❌ usually continuous upload | ✅ rolling buffer |
34
+
35
+ Backtrack doesn't replace your error tracker. It gives you the context your error tracker is missing, and you can correlate the two with `traceId` and `release` (see [Release and trace correlation](#release-and-trace-correlation)).
36
+
37
+ ---
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ # npm
43
+ npm install @backtrack-js/browser
44
+
45
+ # yarn
46
+ yarn add @backtrack-js/browser
47
+
48
+ # pnpm
49
+ pnpm add @backtrack-js/browser
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Quick Start
55
+
56
+ ```typescript
57
+ import { Backtrack } from '@backtrack-js/browser';
58
+
59
+ const recorder = new Backtrack({
60
+ bufferMinutes: 5, // keep the last 5 minutes in the rolling buffer
61
+ afterErrorSeconds: 15, // keep recording 15s after an error
62
+ });
63
+
64
+ await recorder.start();
65
+
66
+ // Optional: capture manually, e.g. from a "Report a problem" button
67
+ const incidentId = await recorder.capture('User reported payment bug', 30);
68
+ ```
69
+
70
+ Then inspect it:
71
+
72
+ ```bash
73
+ npx backtrack
74
+ ```
75
+
76
+ Drag an exported incident `.json` into the viewer and hit play.
77
+
78
+ ---
79
+
80
+ ## Readable production stack traces (`backtrack symbolize`)
81
+
82
+ Production bundles are minified, so a stack frame like `app.3f9a1c.js:1:48213` tells you nothing. `backtrack symbolize` reads your source maps and rewrites the incident's errors with the original file, line, column and the surrounding source code.
83
+
84
+ ```bash
85
+ npx backtrack symbolize <incident> --sourcemaps <directory> [options]
86
+ ```
87
+
88
+ | Argument / option | Description |
89
+ | :--- | :--- |
90
+ | `<incident>` | Path to an exported incident: `.json`, `.ffr.json`, `.gz` or `.ffr.json.gz`. Gzip is detected automatically. |
91
+ | `--sourcemaps <directory>` | Directory containing your `.js.map`, `.mjs.map` and `.cjs.map` files. Searched recursively (e.g. `./dist`, `./build`). |
92
+ | `--output <file>` | Custom output path. Defaults to `<name>.symbolicated.ffr.json` next to the original. The output can never overwrite the input file. |
93
+ | `--release <id>` | Build or release identifier. Validates against the incident's recorded release (if present), warning on mismatch. |
94
+ | `--context-lines <number>` | Lines of original source shown before and after the failing line. Range 0–10, default 3. |
95
+ | `-h, --help` | Show help. |
96
+
97
+ ### Example
98
+
99
+ ```bash
100
+ npx backtrack symbolize ./incidents/checkout-bug.ffr.json --sourcemaps ./dist --context-lines 5
101
+ ```
102
+
103
+ ```text
104
+ [Backtrack] Symbolication concluída.
105
+
106
+ Incidente: inc_1727500000_3f2a
107
+ Erros encontrados: 1
108
+ Frames da exceção resolvidos: 4
109
+ Frames da exceção não resolvidos: 0
110
+ Frames React resolvidos: 2
111
+ Frames React não resolvidos: 0
112
+ Saída: /projects/app/incidents/checkout-bug.symbolicated.ffr.json
113
+ ```
114
+
115
+ If some frames have no matching source map, the command reports a partial symbolication with warnings instead of failing.
116
+
117
+ Open the `.symbolicated.ffr.json` file in the viewer (`npx backtrack`) and the error tab shows the resolved stack trace, with the failing line highlighted:
118
+
119
+ ```text
120
+ applyCoupon src/cart/coupon.ts:42:18
121
+
122
+ 40 function applyCoupon(cart, coupon) {
123
+ 41 // coupon discount might be missing from API
124
+ 42 const off = coupon.discount.percent; <-- highlighted
125
+ 43 return cart.total * (1 - off / 100);
126
+ 44 }
127
+ ```
128
+
129
+ ### What it does (and doesn't do)
130
+
131
+ - **Validates safely:** Reads the incident (up to 50 MB, gzip supported) and validates its schema with a strict validator. The original file is never modified.
132
+ - **Multi-browser stack parsing:** Supports V8 (Chrome/Edge), Firefox, and Safari stack formats, stripping URL hashes/query strings and skipping native frames (`[native code]`).
133
+ - **Resolves both exception location and React tree:** Simboliza tanto o ponto onde a exceção foi lançada (`resolvedStack`) quanto a árvore de componentes React (`resolvedComponentStack`), mantendo-os estritamente separados.
134
+ - **Sourcemap validation:** Checks the sourcemap `file` attribute against the minified bundle to prevent cross-bundle misattribution.
135
+ - **Extracts source context:** Extracts the original code from `sourcesContent`, so your source maps must include it.
136
+ - **Doesn't leak local paths:** File paths are normalized (e.g. `C:\Users\alice\...` or `/home/bob/...` become paths starting at `src/`).
137
+ - **Atomic write:** Writes the result atomically (to a `.tmp` file, then renames) and stores it in `resolvedStack` and `resolvedComponentStack`.
138
+
139
+ ### Limitations & Caveats
140
+
141
+ - **Frames without line/column:** Stack frames missing line or column numbers cannot be mapped to source locations.
142
+ - **Matching sourcemap release:** Source maps must correspond to the exact production build that generated the incident. Use `--release <id>` to ensure compatibility.
143
+ - **Privacy notice:** O artefato poderá incluir replay visual, URLs, logs, respostas de rede, anotações e trechos do código-fonte incorporados durante a symbolication.
144
+
145
+ ---
146
+
147
+ ## Privacy & Data Masking
148
+
149
+ Backtrack is designed with strict security defaults:
150
+
151
+ - **Inputs & Forms:** All `<input>`, `<textarea>`, and `<select>` values are masked by default (`maskAllInputs: true`).
152
+ - **Network Headers:** Authorization, Cookies, API keys, and Bearer tokens are automatically redacted.
153
+ - **Sensitive Selectors:** Use standard selectors or CSS classes to protect proprietary or personal data:
154
+
155
+ ```typescript
156
+ const recorder = new Backtrack({
157
+ privacy: {
158
+ maskAllInputs: true,
159
+ blockMedia: true,
160
+ blockSelector: '.backtrack-block, [data-private]', // Completely hides elements in replay
161
+ maskTextSelector: '.backtrack-mask, .customer-pII', // Scrambles text content
162
+ sanitizeUrl: (url) => {
163
+ // Remove sensitive query parameters
164
+ url.searchParams.delete('token');
165
+ return url.toString();
166
+ }
167
+ }
168
+ });
169
+ ```
170
+
171
+ ---
172
+
173
+ ## Network & Payload Capture
174
+
175
+ Network requests and responses can be configured using `network` options:
176
+
177
+ ```typescript
178
+ const recorder = new Backtrack({
179
+ network: {
180
+ capturePayloads: false, // Bodies are disabled by default for privacy
181
+ maxPayloadSize: 64 * 1024
182
+ }
183
+ });
184
+ ```
185
+
186
+ - **Bodies are not captured by default (`capturePayloads: false`):** Request and response bodies are disabled by default to prevent unintentional data leakage. HTTP method, URL, status, timing, and sanitized headers continue to be captured.
187
+ - **Enabling bodies increases the risk of PII:** Enabling `capturePayloads: true` should be done mindfully according to your compliance and privacy requirements.
188
+ - **Sanitization remains active:** Even when payload capture is explicitly enabled, Backtrack continues to sanitize and redact known sensitive keys (passwords, tokens, credentials, etc.) and patterns (Bearer tokens, emails, CPFs, cards).
189
+ - **`maxPayloadSize` is a limit per payload, not per incident:** It bounds each individual request or response body (default is `64 * 1024` bytes). Payloads exceeding this limit are truncated safely with a truncation marker.
190
+
191
+ ---
192
+
193
+ ## Capturing Incidents
194
+
195
+ ### Automatic Triggers
196
+ Backtrack automatically triggers and freezes an incident when:
197
+ 1. An unhandled window exception occurs (`window.onerror`).
198
+ 2. An unhandled promise rejection occurs (`unhandledrejection`).
199
+ 3. An HTTP request returns a matching error status (`captureHttpStatus`).
200
+
201
+ ### Manual Capture
202
+ You can trigger incident recording manually anywhere in your code (e.g. from an error boundary or user feedback modal):
203
+
204
+ ```typescript
205
+ // Capture the last 60 seconds of context
206
+ const incidentId = await recorder.capture('Checkout error', 60);
207
+
208
+ // Export the complete incident artifact as a JSON object
209
+ const artifact = await recorder.exportIncident(incidentId);
210
+
211
+ // Download or send artifact to your support/logging service
212
+ const jsonBlob = new Blob([JSON.stringify(artifact, null, 2)], { type: 'application/json' });
213
+ ```
214
+
215
+ ### Exception Capture
216
+
217
+ #### React Error Boundary
218
+ Em Error Boundaries (ex: `componentDidCatch` ou `react-error-boundary`), forneça `source: 'react'` e a árvore de componentes `componentStack`:
219
+
220
+ ```typescript
221
+ // React Error Boundary (captura renderização/lifecycle com árvore de componentes)
222
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
223
+ recorder.captureException(error, {
224
+ source: 'react',
225
+ componentStack: errorInfo.componentStack
226
+ });
227
+ }
228
+ ```
229
+
230
+ #### Captura manual (try / catch)
231
+ Para exceções capturadas imperativamente em handlers de eventos, chamadas assíncronas ou blocos `try/catch`, use `source: 'manual'`:
232
+
233
+ ```typescript
234
+ // Captura imperativa sem árvore de componentes React
235
+ try {
236
+ processCheckout();
237
+ } catch (err) {
238
+ recorder.captureException(err, {
239
+ source: 'manual'
240
+ });
241
+ }
242
+ ```
243
+
244
+ > **Atenção:** Use `source: 'react'` apenas quando o erro foi capturado por um Error Boundary com a árvore de renderização. O visualizador e o simbolizador tratam `source: 'react'` e `componentStack` como a árvore de componentes da interface, e a stack JavaScript comum como o local exato do disparo da exceção.
245
+
246
+ ---
247
+
248
+ ## Remote Transport & beforeSend
249
+
250
+ Backtrack allows applications to automatically dispatch finalized incidents to any remote endpoint or telemetry backend using `transport` and `beforeSend`:
251
+
252
+ ```typescript
253
+ const recorder = new Backtrack({
254
+ transport: {
255
+ async send(artifact) {
256
+ const response = await fetch('/api/backtrack/incidents', {
257
+ method: 'POST',
258
+ headers: {
259
+ 'content-type': 'application/json'
260
+ },
261
+ body: JSON.stringify(artifact)
262
+ });
263
+
264
+ if (!response.ok) {
265
+ throw new Error(`Upload failed: ${response.status}`);
266
+ }
267
+ }
268
+ },
269
+
270
+ beforeSend(artifact) {
271
+ if (artifact.environment.url.includes('/healthcheck')) {
272
+ return null;
273
+ }
274
+
275
+ return artifact;
276
+ }
277
+ });
278
+ ```
279
+
280
+ ### Dispatch Flow & Semantics
281
+
282
+ - **Sent Only When Finalized:** Incidents are dispatched only after they have been completely finalized and safely written to IndexedDB.
283
+ ```text
284
+ incident finalized
285
+ ↓
286
+ build artifact
287
+ ↓
288
+ beforeSend
289
+ ↓
290
+ null? cancel upload
291
+ ↓
292
+ transport.send
293
+ ```
294
+ - **Role of `beforeSend`:**
295
+ - Affects **only** the automated transmission via `transport.send`.
296
+ - Does **not** mutate the artifact stored in local IndexedDB.
297
+ - Does **not** affect `getArtifact()` or explicit file downloads via `exportIncident()`.
298
+ - Returning `null` cancels the upload.
299
+ - If `beforeSend` throws an error, the upload is aborted cleanly without affecting the application or the recorded incident.
300
+ - **Fault Isolation:** Network or transport errors will never break the host application, reject user captures, delete local incidents, prevent finalization, or trigger unhandled promise rejections.
301
+ - **Concurrency & Delivery Guarantees:**
302
+ - Each finalized incident triggers at most one upload attempt.
303
+ - Multiple incidents can upload concurrently.
304
+ - `stop()` does not wait for pending network uploads.
305
+ - There is currently no delivery guarantee (persistent retry queue, offline backoff, etc.); if transport fails, the incident remains saved in local IndexedDB and can be accessed or resent by the application.
306
+
307
+ ---
308
+
309
+ ## Automatic Local Incident Export (`@backtrack-js/browser/dev-server`)
310
+
311
+ During local development, Backtrack can save finalized incidents directly to disk in your project folder (JSON + Markdown report) without requiring manual downloads or clipboard copying.
312
+
313
+ This is ideal for both human developers and **AI coding assistants** (Cursor, Copilot, Antigravity, Claude Code), enabling instant root-cause diagnosis directly from `.backtrack/latest.md`.
314
+
315
+ ```text
316
+ .backtrack/
317
+ ├── latest.md <- Structured markdown report (<= 32 KiB) for quick inspection
318
+ └── incidents/
319
+ ├── <id>.json <- Full replay artifact with DOM, network & timeline
320
+ └── <id>.md <- Timestamped markdown report
321
+ ```
322
+
323
+ ### 1. Dev Server Setup
324
+
325
+ The middleware works with any Node-based development server (Vite, Webpack, CRA, Express, Fastify).
326
+
327
+ #### Vite (`vite.config.ts`)
328
+ ```typescript
329
+ import { defineConfig } from 'vite';
330
+ import path from 'path';
331
+ import { createBacktrackMiddleware } from '@backtrack-js/browser/dev-server';
332
+
333
+ export default defineConfig({
334
+ plugins: [
335
+ {
336
+ name: 'backtrack-dev-server',
337
+ configureServer(server) {
338
+ server.middlewares.use(
339
+ createBacktrackMiddleware({
340
+ outputDir: path.resolve(__dirname, '.backtrack'),
341
+ })
342
+ );
343
+ },
344
+ },
345
+ ],
346
+ });
347
+ ```
348
+
349
+ #### Create React App / Webpack Dev Server (`src/setupProxy.js`)
350
+ ```javascript
351
+ const path = require('path');
352
+ const { createBacktrackMiddleware } = require('@backtrack-js/browser/dev-server');
353
+
354
+ module.exports = function(app) {
355
+ if (process.env.NODE_ENV === 'development') {
356
+ app.use(
357
+ createBacktrackMiddleware({
358
+ outputDir: path.resolve(__dirname, '../.backtrack'),
359
+ allowedOrigins: ['http://localhost:3000', 'http://127.0.0.1:3000'],
360
+ })
361
+ );
362
+ }
363
+ };
364
+ ```
365
+
366
+ #### Express / Custom Node Dev Server
367
+ ```javascript
368
+ const path = require('path');
369
+ const { createBacktrackMiddleware } = require('@backtrack-js/browser/dev-server');
370
+
371
+ app.use(
372
+ createBacktrackMiddleware({
373
+ outputDir: path.resolve(__dirname, '.backtrack'),
374
+ maxIncidents: 20, // keeps disk clean automatically
375
+ })
376
+ );
377
+ ```
378
+
379
+ ### 2. Client Setup
380
+
381
+ In your client application initialization, configure `transport` to POST to `/__backtrack/incident`:
382
+
383
+ ```typescript
384
+ import { Backtrack } from '@backtrack-js/browser';
385
+
386
+ const isDev = process.env.NODE_ENV === 'development';
387
+
388
+ const recorder = await Backtrack.init({
389
+ ignoredUrls: ['/__backtrack/incident'],
390
+ transport: isDev
391
+ ? {
392
+ async send(artifact) {
393
+ await fetch('/__backtrack/incident', {
394
+ method: 'POST',
395
+ headers: { 'Content-Type': 'application/json' },
396
+ body: JSON.stringify(artifact),
397
+ });
398
+ },
399
+ }
400
+ : undefined,
401
+ });
402
+ ```
403
+
404
+ ### Middleware Options
405
+
406
+ | Option | Type | Default | Description |
407
+ | :--- | :--- | :--- | :--- |
408
+ | `outputDir` / `directory` | `string` | *(required)* | Absolute path to the directory where incidents are stored. |
409
+ | `allowedOrigins` | `string[]` | local origins | Allowed request origins. Defaults to loopback (`localhost`, `127.0.0.1`, `::1`). |
410
+ | `maxBodyBytes` | `number` | `52428800` (50 MiB) | Maximum allowed JSON request body size. Aborts stream early if exceeded. |
411
+ | `maxIncidents` | `number` | `20` | Maximum number of managed incidents retained on disk. |
412
+ | `maxStorageBytes` | `number` | `209715200` (200 MiB) | Aggregate disk space limit for managed incidents. Oldest files are pruned first. |
413
+
414
+ > **Security & Best Practices:**
415
+ > - Add `.backtrack/` to your `.gitignore`.
416
+ > - Never enable the local dev-server middleware in staging or production.
417
+ > - The middleware validates requests against local peer sockets and host headers to prevent external access. Writes are atomic via `.tmp` files.
418
+
419
+ ## Release and trace correlation
420
+
421
+ Backtrack artifacts (`formatVersion: 2`) provide an observability `context` to correlate client-side replays with backend traces, application releases, and environments.
422
+
423
+ ### Static Context
424
+
425
+ Configure static metadata when initializing Backtrack:
426
+
427
+ ```typescript
428
+ const recorder = new Backtrack({
429
+ context: {
430
+ release: 'checkout-web@2.14.0',
431
+ environment: 'production',
432
+ traceId: '4bf92f3577b34da6a3ce929d0e0e4736',
433
+ spanId: '00f067aa0ba902b7'
434
+ }
435
+ });
436
+ ```
437
+
438
+ ### Dynamic Context (`getContext`)
439
+
440
+ When trace IDs or active environments change dynamically during user navigation (e.g. from OpenTelemetry, Datadog, Sentry, or custom APM SDKs), use `getContext` to evaluate the latest context at incident export time:
441
+
442
+ ```typescript
443
+ const recorder = new Backtrack({
444
+ context: {
445
+ release: 'checkout-web@2.14.0',
446
+ environment: 'production'
447
+ },
448
+ getContext: async () => {
449
+ const activeSpan = tracer.getActiveSpan();
450
+ const spanContext = activeSpan?.spanContext();
451
+
452
+ return {
453
+ release: 'checkout-web@2.14.0',
454
+ environment: 'production',
455
+ traceId: spanContext?.traceId,
456
+ spanId: spanContext?.spanId
457
+ };
458
+ }
459
+ });
460
+ ```
461
+
462
+ ### Correlation Semantics
463
+
464
+ - **Precedence:** `getContext() > context (static) > environment metadata`.
465
+ - **Trace correlation scope:** Backtrack **only correlates** external trace and span identifiers supplied by your application. It does **not** generate backend traces, start trace spans, or perform distributed instrumentation on its own.
466
+ - **Normalization:** `traceId` (32 hex characters) and `spanId` (16 hex characters) are validated and normalized to lowercase. A `spanId` cannot be supplied without an accompanying `traceId`.
467
+ - **Fault Resilience:** If `getContext()` throws an error or rejects, incident export continues safely using available static context without failing the export or losing incident data.
468
+
469
+ ---
470
+
471
+ ## Inspecting Incidents (Viewer)
472
+
473
+ Backtrack includes an offline visualizer with video replay, console logs, network inspection, and breadcrumbs.
474
+
475
+ ### Launch the Viewer CLI
476
+ Run the embedded CLI to launch the local visualizer:
477
+
478
+ ```bash
479
+ npx backtrack
480
+ ```
481
+
482
+ This starts a local server at `http://localhost:5173/` and opens your default browser. Drag and drop any exported incident `.json` file to replay it!
483
+
484
+ ---
485
+
486
+ ## API Reference
487
+
488
+ ### `Backtrack`
489
+
490
+ | Method | Return Type | Description |
491
+ | --- | --- | --- |
492
+ | `start()` | `Promise<void>` | Initializes storage and begins recording. |
493
+ | `stop()` | `Promise<void>` | Stops recording, flushes pending batches to storage, and detaches listeners. |
494
+ | `capture(reason?, windowSeconds?)` | `Promise<string>` | Manually captures and finalizes an incident. Returns incident ID. |
495
+ | `captureException(error, context?)` | `Promise<string \| undefined> \| void` | Records a caught exception and triggers an incident. |
496
+ | `listIncidents()` | `Promise<IncidentSummary[]>` | Lists all stored incidents. |
497
+ | `getArtifact(incidentId)` | `Promise<FlightRecorderArtifact>` | Retrieves the full artifact for an incident (v1 or v2). |
498
+ | `exportIncident(incidentId)` | `Promise<FlightRecorderArtifact>` | Exports the artifact and marks it finalized. |
499
+ | `deleteIncident(incidentId)` | `Promise<void>` | Deletes an incident from local storage. |
500
+ | `clear()` | `Promise<void>` | Clears all stored chunks, sessions, and incidents. |
501
+ | `getHealth()` | `RecorderHealth` | Returns current buffer status, storage usage, and state. |
502
+
503
+ ---
504
+
505
+ ## License
506
+
507
+ [Backtrack Free-Use License](LICENSE) — Free for personal and commercial integration. Redistribution, copying, or creating derivative/competing works is strictly prohibited.