Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ Read this first; then `docs/design/sdk-design.md` (Draft v3) for the full archit
- `createUiBreadcrumbSource` / `createUiBreadcrumbProvider` (**UI breadcrumbs**, 2026-08-31): the state-change half of the DOM interaction split — capture-phase/passive listeners for `change`/`submit`/`focusin` → `client.addBreadcrumb` (so the app's `breadcrumbFilter` runs) in Android `BreadcrumbInputGesture`'s shape: `{type:'user', category:'ui.change'|'ui.submit'|'ui.focus', level:'info', data:{'view.id','view.class','view.tag'}, timestamp: the DOM event's own moment (`performance.timeOrigin + Event.timeStamp`, clamped to now)}`. `type:'user'` is Android's literal for a user-ORIGINATED breadcrumb — **not** the `*.user` stream (these are `breadcrumbs` entries). Why not `input`: `InputEvent.type` is Android's `InputEventStage` (`unknown|begin|move|end|scroll|keydown|keyup`), which a DOM-only value cannot join, and Android already splits its input dispatcher (→ `input.json`) from its gesture dispatcher (→ breadcrumbs). Reuses `describeTarget`; a sensitive/app-masked target **drops the breadcrumb outright** (the focus/change rhythm on a masked field is itself a side channel). Gated by `captureInteractions` (the option means "do not watch what I click and type", whatever stream it lands on). Wired into `@bugsee/browser` **and** `@bugsee/webview` (which also now declares `breadcrumbs` in `hello.caps`).
- `createDomSnapshot` / `createViewtreeSnapshotSource` (view hierarchy: an at-report DOM-tree snapshot → `viewtree`, via the core `reportSnapshots` pull-seam) — reuses `describeTarget` per node + rounded `getBoundingClientRect`; masked subtrees collapse to `{tag, masked, rect}`; bounded (maxNodes/maxDepth) + per-node throw-isolated. Gated by `captureViewHierarchy`. See §7.
- **`client.showReportDialog(options?)`** (2026-10-11 → `docs/design/report-dialog.md`): the web counterpart of the mobile `showReportDialog()` — a built-in bug-report form shown as a modal over a shaded page, sent as a `code_dialog` report through the new core **`client.upload(options)`** (Android `Bugsee.upload`: not deduped or rate-limited, still runs the report handler). Same fields/order/strings/colours/validation as mobile (summary, description, severity, labels, email, `v<sdk>` footer); the reporting-UI launch options are Android's canonical `com.bugsee.option.reporting.ui.*` keys with the same defaults (`reportSummaryRequired`, `reportDescriptionRequired`, `reportEmailRequired`, `reportLabelsEnabled`, `reportLabelsRequired`, `reportPrioritySelectorEnabled`, `reportDefaultBugPriority`). Built with DOM APIs only inside ONE shadow root on ONE host element (page CSS cannot reach it; user text is never parsed as HTML) and shown with `<dialog>.showModal()` so it sits in the TOP LAYER — above any z-index and above a modal the page already has open (verified in real Chromium); a fixed max-z-index overlay is the fallback. Esc / a click on the shading is a cancel with a discard prompt for an edited form; focus and scroll are restored on close; a changed email is remembered as the user identifier. **No screenshot in v1** (the report already carries the DOM snapshot). Resolves `null` on cancel / already open / not running / no DOM. Tested under jsdom (`report-dialog.test.ts`, mutation-checked) and end to end through a real `launch()` (`report-dialog.launch.test.ts`).
- **Mobile-parity options (T6, `docs/design/mobile-api-alignment.md`):** `captureLogsLevel`, `captureBreadcrumbs` (JS default stays `true`), `detectHttpErrors` (`createHttpErrorDetectionProvider` in `@bugsee/capture`, mechanism `http-error`, own rate budget), `captureNetworkDefaultSanitizer`, `reportDefaultCrashPriority` / `reportDefaultErrorPriority` (applied in the client before the report handler, only to severities that were derived), and `getDefaultNetworkSanitizer()` for composing with a custom network filter.
- **`launch(appToken, options)`** — the browser composition root (fetch/DOM analog of node's): fetch transport (internal-tagged), api/uploader/upload pipeline, browser env, in-memory store (or IndexedDB-backed when `persist:true`), gated capture providers (console→log; network umbrella, NO `node:http`; system traces; system events; user-interaction input) + detection providers, `client.launch()`. **No `process.exit` path** (the browser flushes via the pipeline/`pagehide`; `stop()` only clears the carrier). `persist:true` builds an IndexedDB durable bundle queue (crash recovery across reload — `recover()` deferred to the store's `whenReady`) + the durable IndexedDB chunk capture store (`createIdbChunkCaptureStore` in its own `bugsee-capture` db). `maxDataSize` defaults to 10 MB. **Session replay is ON by default** (2026-08-27) — parity with the iOS/Android SDKs, which record by default; `replay: false` is the opt-out and is the ONLY value on which `import('@bugsee/replay')` is never evaluated, so an errors-only integration still pays nothing for rrweb (the lazy chunk + the opt-out carry the bundle-size budget, not the default). Masking stays fail-closed on the default path (mask all text/inputs, block all media); `replay.canvas` stays opt-in. Returns the started `BugseeClient`.

### Integration shims — `@bugsee/integration-shims` (tier-3 leaf, slice #13)
Expand Down
25 changes: 25 additions & 0 deletions docs/design/mobile-api-alignment.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,33 @@ Each task is test-first with the mutator loop, keeps the coverage gate, and land
| T8 | `logException` options: `domain`; check what `mergingRules` does on iOS | Android `ExceptionOptions.Domain`, iOS `BugseeExceptionLoggingOptions` | |
| T9 | Browser: persist the user identifier across page loads, as iOS does | iOS `setUserIdentifier` | storage-failure tolerant |

## Options added by T6

Launch options (all runtimes through `COMMON_OPTION_DEFINITIONS` in `@bugsee/core`; sent in `sdk.options`
dots-to-colons like every other option). `detectHttpErrors` is wired in `@bugsee/browser` and `@bugsee/node`.

| Friendly | Canonical (`com.bugsee.option.` + ...) | Default | Notes |
|---|---|---|---|
| `captureLogsLevel` | `capture.logs.level` | `'verbose'` | Console and `client.log()` records less severe than the level are dropped before the log filter. |
| `captureBreadcrumbs` | `capture.breadcrumbs` | `true` (mobile: `false`) | `false` drops every breadcrumb: `addBreadcrumb`, the UI ones and adapter ones. See Kept differences. |
| `detectHttpErrors` | `detect.http-errors` | `false` | fetch / XHR / `node:http` responses with status >= 400, or a request with no response, file an `error` report with mechanism `http-error`. Own rate budget (20 per minute). The report text carries the method, status and URL without query string or credentials. |
| `captureNetworkDefaultSanitizer` | `capture.network.default-sanitizer` | `true` | Friendly name for the existing key. A network filter still replaces the default; see `getDefaultNetworkSanitizer()`. |
| `reportDefaultCrashPriority` | `reporting.defaults.crash-priority` | `'blocker'` | Severity of a crash report that was given none. |
| `reportDefaultErrorPriority` | `reporting.defaults.error-priority` | `'high'` | Same for error reports (`logException`, unhandled rejections, http errors). |

An explicit per-call severity (`logException(e, { severity })`) and a report handler's edits win over the default.
Native-crash recovery (Electron) builds its report outside the client and keeps `blocker`.
Invalid values warn through `onError` and fall back to the default.

`getDefaultNetworkSanitizer()` (exported from `@bugsee/capture` and the `@bugsee/bugsee` entries) returns
`{ sanitize, sanitizeUrl, sanitizeErrorMessage }`, the same object the built-in sanitizer uses. Call it from inside a
`setNetworkEventFilter` filter to keep the default redaction.

## Kept differences (documented, not changed)

- `captureBreadcrumbs` defaults to `true`, not mobile's `false`: UI-interaction breadcrumbs (click / change / submit /
focus) are a core web signal and were already always on. The option can now switch them off.

- `launch` is async in the browser (lazy-loaded extensions) and synchronous on Node.
- JS filters return a value; mobile filters take a callback.
- No native triggers (shake, screenshot, notification bar, broadcast), no video options, `Appearance` or `setWrapper`.
Expand Down
21 changes: 21 additions & 0 deletions packages/browser/src/launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '@bugsee/browser-utils';
import {
createConsoleInterceptor,
createHttpErrorDetectionProvider,
createInputProvider,
createLogCaptureProvider,
createSystemEventsProvider,
Expand Down Expand Up @@ -178,10 +179,22 @@ export interface BugseeLaunchOptions {

/** Capture console output as logs. Default true. */
captureLogs?: boolean;
/** Minimum level of a log record to capture: `error | warning | info | debug | verbose`. Default `verbose`. */
captureLogsLevel?: 'error' | 'warning' | 'info' | 'debug' | 'verbose';
/**
* Capture breadcrumbs (`addBreadcrumb` and the automatic UI-interaction ones). Default true. Mobile SDKs
* default this to false; on the web UI breadcrumbs are a core signal, so JS keeps them on.
*/
captureBreadcrumbs?: boolean;
/** Capture network (fetch/xhr/ws/sse/webtransport). Default true. */
captureNetwork?: boolean;
/** Capture request/response bodies (bounded read). Default true. */
captureNetworkBodies?: boolean;
/**
* Apply the built-in network PII sanitizer (URL, headers, bodies, error text). Default true. A
* `setNetworkEventFilter` filter REPLACES it; compose with `getDefaultNetworkSanitizer()`.
*/
captureNetworkDefaultSanitizer?: boolean;
/** Max captured request/response body size in bytes. Default 20480. */
maxNetworkBodySize?: number;
/** Capture a body even when its Content-Type is missing/blank. Default false. */
Expand Down Expand Up @@ -214,6 +227,12 @@ export interface BugseeLaunchOptions {
reportDefaultBugPriority?: SeverityName;
/** Detect window errors + unhandled rejections. Default true. */
detectCrashes?: boolean;
/** Report failed fetch/XHR exchanges (status >= 400, or no response) as `error` reports. Default false. */
detectHttpErrors?: boolean;
/** Severity a crash report starts with. Default `blocker`. */
reportDefaultCrashPriority?: SeverityName;
/** Severity an error report starts with. Default `high`. */
reportDefaultErrorPriority?: SeverityName;
/**
* Session replay (rrweb). **On by default** — parity with the iOS/Android SDKs, which record by default.
* An options object customises it; `false` opts out entirely. Masking is FAIL-CLOSED (mask all
Expand Down Expand Up @@ -641,6 +660,8 @@ export async function launchCore(
// Detection providers: window error → crash, unhandledrejection → error.
client.addDetectionProvider(createWindowErrorProvider(win));
client.addDetectionProvider(createUnhandledRejectionProvider(win));
// Failed fetch/xhr exchanges → `error` reports (mechanism http-error). Off unless `detectHttpErrors`.
client.addDetectionProvider(createHttpErrorDetectionProvider(network.interceptor));

client.launch();

Expand Down
Loading
Loading