@fixback/sdk 0.4.0 → 0.5.0

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/dist/init.d.ts CHANGED
@@ -1,7 +1,7 @@
1
+ import type { BeforeSend } from "@fixback/sdk-core";
1
2
  import { type IdentityInputs } from "./boot";
2
3
  import { type BeforeBreadcrumb, type BreadcrumbLevel, type TraceStream } from "./breadcrumbs";
3
4
  import { type ReplayOptions } from "./replay";
4
- import type { BeforeSend } from "./scrub";
5
5
  /**
6
6
  * The hosted Fixback API origin the SDK talks to by default. A self-hosted or
7
7
  * local deployment overrides it with `apiUrl` (the dashboard's install snippet
@@ -92,7 +92,7 @@ export interface InitOptions extends IdentityInputs {
92
92
  };
93
93
  /**
94
94
  * Automatic error capture — the SDK files uncaught exceptions / unhandled
95
- * rejections as `source: auto` Feedback with no prompt (spec §E, ADR-0011).
95
+ * rejections as `source: error` Feedback with no prompt (spec §E, ADR-0011).
96
96
  * **Default-on across all Gates**; set `false` for the per-project toggle that
97
97
  * turns it off. It is gated by boot's `canSubmit` either way, so an auto-error is
98
98
  * never filed where a manual report would be refused.
package/dist/overlay.d.ts CHANGED
@@ -1,19 +1,16 @@
1
+ import { type BeforeSend } from "@fixback/sdk-core";
1
2
  import type { IdentityInputs, ProjectGate, ReporterTier } from "./boot";
2
3
  import type { Breadcrumb } from "./breadcrumbs";
3
4
  import type { ReporterDisplay } from "./invite";
4
5
  import { type ElementPicker, type ElementPickerOptions } from "./element-picker";
5
6
  import type { ReplaySource } from "./replay";
6
- import { type BeforeSend } from "./scrub";
7
- import { type Capture, type CaptureOptions } from "./screenshot";
8
7
  import { type SubmitInput, type SubmitResult } from "./submit";
9
8
  /** Marks the overlay's host element in the light DOM (skipped by capture/picker). */
10
9
  export declare const OVERLAY_ATTRIBUTE = "data-fixback-overlay";
11
- type CaptureViewFn = (options?: CaptureOptions) => Promise<Capture | null>;
12
10
  type SubmitReportFn = (apiUrl: string, input: SubmitInput, fetchImpl?: typeof fetch) => Promise<SubmitResult>;
13
11
  type StartPickerFn = (options: ElementPickerOptions) => ElementPicker;
14
12
  /** Injectable collaborators, defaulted to the real implementations. */
15
13
  export interface OverlayDeps {
16
- readonly captureView: CaptureViewFn;
17
14
  readonly submitReport: SubmitReportFn;
18
15
  readonly startElementPicker: StartPickerFn;
19
16
  }
@@ -73,14 +70,15 @@ export interface OverlayController {
73
70
  * Create the report overlay — the on-page panel a Reporter files a report from,
74
71
  * built to the frozen Signal Reporter prototype (`docs/design/Fixback Reporter.dc.html`).
75
72
  * It mounts lazily inside its own Shadow DOM (isolated from the host page, and
76
- * marked so the screenshot and element-picker skip it), opens on the launcher's
73
+ * marked so the replay recorder and element-picker skip it), opens on the launcher's
77
74
  * `fixback:launch` seam, and offers three **composable, optional** marking layers
78
- * over one full masked screenshot (spec §B): **element-pick**, **region-capture**
79
- * (drag), and **draw** (arrow / box / pen / text, with undo / cancel / attach). On
80
- * Send it captures the masked screenshot, assembles the structured Annotation
81
- * (`{ element?, region?, marks? }`, spec §D marks stay vector, never baked into
82
- * the PNG), and submits to ingest showing a confirmation on success and failing
83
- * quietly otherwise.
75
+ * over the current view (spec §B): **element-pick**, **region-capture** (drag), and
76
+ * **draw** (arrow / box / pen / text, with undo / cancel / attach). The marks are
77
+ * viewport-space vectors composited at view time (spec §D), never baked into an
78
+ * image; the still they annotate is a frame of the buffered rrweb window
79
+ * (reconstructed downstream since ADR-0027), so the SDK rasterises nothing. On Send
80
+ * it assembles the structured Annotation (`{ element?, region?, marks? }`) and
81
+ * submits to ingest — showing a confirmation on success and failing quietly otherwise.
84
82
  */
85
83
  export declare function createOverlay(config: OverlayConfig): OverlayController;
86
84
  export {};
package/dist/report.d.ts CHANGED
@@ -1,65 +1,20 @@
1
1
  /**
2
- * The ingest **feedback** content wire-contract, vendored, plus the helpers that
3
- * assemble it from what a Reporter composed in the overlay.
2
+ * Assembling the ingest **feedback** content from what a Reporter composed in the
3
+ * overlay, plus the browser-side environment capture and Release validation.
4
4
  *
5
- * Like `boot.ts`, the SDK does not import `@fixback/shared`; the slice of the
6
- * submission shape it needs is copied here. Keep it in lock-step with the server:
7
- * the JSON `payload` part accepted by `POST /api/ingest/feedback`
8
- * (`apps/api/src/ingest/ingest.controller.ts` `feedbackContentBody`) and the
9
- * `SubmissionContent` it parses (`apps/api/src/ingest/reporter-identity.ts`).
10
- */
11
- import { type Annotation, type Mark, type Rect } from "./annotation";
5
+ * The shared wire shapes it produces `ReportContent`, `CaptureEnvironment`,
6
+ * `CapturedFrame`, `SelectedElement`, `ElementRect`, `FeedbackSource` live in
7
+ * `@fixback/sdk-core` (ADR-0028), so the browser and backend SDKs speak the same
8
+ * shape; this module re-exports them for the SDK's public surface and holds only
9
+ * the browser-specific assembly (`assembleContent`, `collectEnvironment`) and the
10
+ * `release` validation. Keep the shapes in lock-step with the server: the JSON
11
+ * `payload` accepted by `POST /api/ingest/feedback`
12
+ * (`apps/api/src/ingest/ingest.controller.ts`).
13
+ */
14
+ import { type CaptureEnvironment, type CapturedFrame, type ElementRect, type FeedbackSource, type ReportContent, type SelectedElement } from "@fixback/sdk-core";
15
+ import { type Mark, type Rect } from "./annotation";
12
16
  import type { Breadcrumb } from "./breadcrumbs";
13
- /**
14
- * Where a Feedback came from — a human in the overlay (`reporter`, the default) or
15
- * the SDK's automatic error capture (`auto`). Mirrors the server's `FEEDBACK_SOURCES`;
16
- * the server derives trust independently and ignores anything else the client claims.
17
- */
18
- export type FeedbackSource = "reporter" | "auto";
19
- /** The picked element's viewport rectangle, as the server's annotation `rect`. */
20
- export interface ElementRect {
21
- readonly x: number;
22
- readonly y: number;
23
- readonly width: number;
24
- readonly height: number;
25
- }
26
- /**
27
- * The picked element — the `element` layer of an {@link Annotation}: a stable CSS
28
- * selector, a readable DOM path, the tag, and the bounding rect. Exactly the
29
- * server's annotation `element` object shape.
30
- */
31
- export interface SelectedElement {
32
- readonly selector: string;
33
- readonly domPath: string;
34
- readonly tag: string;
35
- readonly rect: ElementRect;
36
- }
37
- /** The capture environment recorded alongside a report. */
38
- export interface CaptureEnvironment {
39
- readonly viewportWidth?: number;
40
- readonly viewportHeight?: number;
41
- readonly browser?: string;
42
- readonly sdkVersion?: string;
43
- /**
44
- * The host app's **Release** — the build identifier the builder configured at
45
- * `init` (#117, ADR-0024). Uploaded sourcemaps are keyed by it, so the server
46
- * can symbolicate this session's stack traces against the exact build that
47
- * produced them. Omitted when the Project doesn't set one.
48
- */
49
- readonly release?: string;
50
- }
51
- /**
52
- * One structured frame of a captured stack trace (#117, ADR-0024), top of stack
53
- * first — the server's `errorFrames` wire shape (mirrors `@fixback/shared`'s
54
- * `CapturedStackFrame` by value, per this file's vendoring rule). `file` is the
55
- * scrubbed script URL; `line`/`column` are 1-based as browsers report them.
56
- */
57
- export interface CapturedFrame {
58
- readonly file: string;
59
- readonly line: number;
60
- readonly column: number | null;
61
- readonly function: string | null;
62
- }
17
+ export type { CaptureEnvironment, CapturedFrame, ElementRect, FeedbackSource, ReportContent, SelectedElement, };
63
18
  /**
64
19
  * Validate + trim a builder-supplied release (#117, ADR-0024), mirroring the
65
20
  * server's rules by value: 1–100 visible characters, no whitespace, control
@@ -68,46 +23,6 @@ export interface CapturedFrame {
68
23
  * shipping a value the server would discard.
69
24
  */
70
25
  export declare function normaliseRelease(value: string): string | undefined;
71
- /**
72
- * The JSON content of a feedback submission — the object serialised into the
73
- * multipart `payload` part next to the `key` and identity evidence. Every field
74
- * is optional: none of it feeds the server's trust decision, so a submission may
75
- * carry any subset. `annotation` is the structured `{ element?, region?, marks? }`
76
- * (spec §D); the screenshot is a separate binary part, never part of this JSON.
77
- */
78
- export interface ReportContent {
79
- readonly comment?: string;
80
- /**
81
- * The Reporter's self-provided display **name / email** (spec §F), captured in the
82
- * invite onboarding step and riding along with every submission so a Member sees
83
- * who reported an Issue. **Display only, never a trust signal** — the server derives
84
- * the tier from identity evidence alone and ignores these. Omitted when the Reporter
85
- * gave none (a Public/anonymous visitor).
86
- */
87
- readonly reporterName?: string;
88
- readonly reporterEmail?: string;
89
- readonly url?: string;
90
- readonly environment?: CaptureEnvironment;
91
- readonly annotation?: Annotation;
92
- /** The masked breadcrumb trace buffer that rode on this report (spec §C). */
93
- readonly trace?: readonly Breadcrumb[];
94
- /**
95
- * Provenance (spec §D/§E). Omitted for a manual report — the transport stamps the
96
- * `reporter` default on the wire; set to `auto` by the SDK's error capture.
97
- */
98
- readonly source?: FeedbackSource;
99
- /** For `source: auto` only — the SDK's per-session error fingerprint (spec §E). */
100
- readonly errorSignature?: string;
101
- /** For `source: auto` only — the running occurrence count within the session (spec §E). */
102
- readonly occurrences?: number;
103
- /**
104
- * For `source: auto` only — the captured error's parsed stack frames (#117,
105
- * ADR-0024), top of stack first, capped client-side. The analysis worker
106
- * matches them against the release's uploaded sourcemaps to write the Issue's
107
- * Code-area pointer.
108
- */
109
- readonly errorFrames?: readonly CapturedFrame[];
110
- }
111
26
  /**
112
27
  * What the overlay hands to {@link assembleContent} when the Reporter sends. The
113
28
  * three marking layers arrive flat (`element` / `region` / `marks`); `assembleContent`
package/dist/submit.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AutoReportBackoff } from "./auto-report-backoff";
1
+ import { AutoReportBackoff } from "@fixback/sdk-core";
2
2
  import type { IdentityInputs, ReporterTier } from "./boot";
3
3
  import type { ReportContent } from "./report";
4
4
  /** What ingest returns for an accepted submission (vendored server shape). */
@@ -8,11 +8,6 @@ export interface RecordedFeedback {
8
8
  readonly reporterId: string;
9
9
  readonly tier: ReporterTier;
10
10
  }
11
- /** The captured screenshot to attach, if any. */
12
- export interface SubmitScreenshot {
13
- readonly blob: Blob;
14
- readonly filename?: string;
15
- }
16
11
  /**
17
12
  * The buffered replay window to attach, if any — the rrweb events a
18
13
  * `ReplaySnapshot` holds (replay.ts). Serialized into its own binary part.
@@ -25,7 +20,6 @@ export interface SubmitInput {
25
20
  readonly key: string;
26
21
  readonly identity?: IdentityInputs;
27
22
  readonly content: ReportContent;
28
- readonly screenshot?: SubmitScreenshot | null;
29
23
  readonly replay?: SubmitReplay | null;
30
24
  }
31
25
  /** The outcome of a submission — never an exception. */
@@ -38,7 +32,7 @@ export type SubmitResult = {
38
32
  readonly status?: number;
39
33
  } | {
40
34
  /**
41
- * A `source: auto` report shed under ingest backpressure (spec §E/§H) — either
35
+ * A `source: error` report shed under ingest backpressure (spec §E/§H) — either
42
36
  * held locally because the window is still open, or answered `429` by ingest,
43
37
  * which opened/extended the window. Carries the seconds left to wait. Manual
44
38
  * reports never produce this.
@@ -51,11 +45,11 @@ export type SubmitResult = {
51
45
  export declare function feedbackEndpoint(apiUrl: string): string;
52
46
  /**
53
47
  * Submit a report to ingest. Assembles the `payload` JSON (key + identity +
54
- * content, with an explicit `source` stamped) and the optional full masked
55
- * `screenshot` file into a `FormData`, posts it, and resolves to the recorded
56
- * Feedback on success or a named failure otherwise.
48
+ * content, with an explicit `source` stamped) and the optional `replay` window
49
+ * into a `FormData`, posts it, and resolves to the recorded Feedback on success
50
+ * or a named failure otherwise.
57
51
  *
58
- * A `source: auto` report first consults the shared backpressure window: while it
52
+ * A `source: error` report first consults the shared backpressure window: while it
59
53
  * is open the report is dropped without a request (spec §E/§H). Ingest's `429` +
60
54
  * `Retry-After` opens/extends that window (default 60 s if the header is absent).
61
55
  * Manual reports (`source: reporter`, the default) never consult the window and a
package/dist/version.d.ts CHANGED
@@ -10,4 +10,4 @@
10
10
  * can't slip through. Do not hand-edit this line to a value other than
11
11
  * `package.json`'s version.
12
12
  */
13
- export declare const SDK_VERSION = "0.4.0";
13
+ export declare const SDK_VERSION = "0.5.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fixback/sdk",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "The Fixback capture SDK — a boot-gated, self-isolating on-page feedback launcher.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -43,7 +43,7 @@
43
43
  "dependencies": {
44
44
  "@rrweb/record": "^2.1.1",
45
45
  "@rrweb/types": "^2.1.1",
46
- "modern-screenshot": "^4.7.0"
46
+ "@fixback/sdk-core": "0.2.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "jsdom": "^30.0.1",
@@ -1,47 +0,0 @@
1
- /**
2
- * Client-side backpressure for **automatic** error reports (spec 0003 §E/§H,
3
- * ticket #89). When ingest sheds `source: auto` load it answers `429` with a
4
- * `Retry-After`; the SDK honours it by holding a pause window during which further
5
- * `source: auto` reports are dropped without touching the network. **Manual**
6
- * reports — a human clicking Send — never consult this gate.
7
- *
8
- * This is the transport's counterpart to the server's per-Project token bucket
9
- * (`apps/api/src/ingest/auto-report-rate-limiter.ts`): one shared window per page,
10
- * so a 429 from one auto-report shed applies to the auto-reports that follow it.
11
- * The clock is injectable so the window is unit-tested deterministically, never on
12
- * wall time — mirroring the server limiter's `Clock`.
13
- */
14
- /** A source of the current time in epoch milliseconds — injectable for tests. */
15
- export type Clock = () => number;
16
- /** The hold window applied when a `429` carries no usable `Retry-After` (spec §E). */
17
- export declare const DEFAULT_RETRY_AFTER_SECONDS = 60;
18
- /**
19
- * Parse a `Retry-After` header into whole seconds to hold for. Handles both HTTP
20
- * forms — a delta-seconds integer and an HTTP-date (measured from `now`, rounded up
21
- * and clamped at zero) — and falls back to {@link DEFAULT_RETRY_AFTER_SECONDS} when
22
- * the header is absent, blank, or unparseable. Ingest sends the delta-seconds form;
23
- * the date form is handled for spec-completeness.
24
- */
25
- export declare function parseRetryAfter(header: string | null | undefined, now: number): number;
26
- /**
27
- * A single pause window for `source: auto` reports. `hold` opens (or extends) it
28
- * from a `429`'s `Retry-After`; `isPaused` reports whether it is still open. The
29
- * default instance in `submit.ts` is shared across a page's reports so the hold
30
- * persists across successive auto submissions.
31
- */
32
- export declare class AutoReportBackoff {
33
- private readonly now;
34
- /** Epoch ms until which `source: auto` reports are held; `0` when clear. */
35
- private pausedUntil;
36
- constructor(now?: Clock);
37
- /** Is the `source: auto` pause window currently open? */
38
- isPaused(): boolean;
39
- /** Whole seconds remaining in the pause window (`0` when clear). */
40
- retryAfterSeconds(): number;
41
- /**
42
- * Open (or extend) the window from a `429`'s `Retry-After` value, returning the
43
- * seconds it will hold for. The window only ever grows — a shorter later hold
44
- * never clips a longer one already in effect.
45
- */
46
- hold(retryAfterHeader: string | null | undefined): number;
47
- }
@@ -1,73 +0,0 @@
1
- /**
2
- * Client-side masked screenshot capture (spec MVP §E; ticket #54; ADR-0014).
3
- *
4
- * The capture is **private-by-default**: input values are masked and the SDK's own
5
- * UI is excluded from a *clone* of the view **before** anything is rasterised, so no
6
- * unmasked text and none of Fixback's chrome ever reaches the image.
7
- *
8
- * The rasterisation itself is delegated to `modern-screenshot` (ADR-0014): it clones
9
- * the target, inlines the page's real styles **and** its fonts and images as data
10
- * URIs, then draws the result through an SVG `<foreignObject>` onto a `<canvas>`.
11
- * That closes the fidelity gap a hand-rolled `<foreignObject>` left open — external
12
- * stylesheets, web fonts, and same-origin images now render — so the shot matches
13
- * what the Reporter saw and lines up with the annotation's viewport-space marks.
14
- *
15
- * We keep the privacy guarantee by driving it through two of its hooks: `filter`
16
- * drops Fixback's own host elements, and `onCloneNode` masks the clone's inputs —
17
- * both run on the library's internal clone, before it embeds or serialises anything,
18
- * so the live page is never touched and no real value reaches the raster. The backend
19
- * is injectable so the pipeline (and the masking-before-capture guarantee) is testable
20
- * without a real canvas, and it fails quietly: any problem resolves to `null` and the
21
- * report is simply sent without a screenshot.
22
- */
23
- /** The character private input content is replaced with. */
24
- export declare const MASK_CHAR = "\u2022";
25
- /**
26
- * Mask the private, user-entered content in a subtree: text `input` values,
27
- * `textarea` content, and `contenteditable` text. Developer-authored text
28
- * (placeholders, button labels, non-text controls) is left untouched. Operates in
29
- * place — call it on a *clone* of the view, never the live page.
30
- */
31
- export declare function maskInputs(root: ParentNode): void;
32
- /** A captured screenshot: the image bytes plus its dimensions and content type. */
33
- export interface Capture {
34
- readonly blob: Blob;
35
- readonly width: number;
36
- readonly height: number;
37
- readonly type: string;
38
- }
39
- /** The screenshot filename extension for a {@link Capture}'s content type (defaults to `png`). */
40
- export declare function extensionFor(type: string): string;
41
- /** How the SDK prepares a capture: exclude Fixback's UI, mask the clone's inputs. */
42
- interface CaptureHooks {
43
- /** Keep a node in the shot? Returns `false` for Fixback's own host elements. */
44
- readonly filter: (node: Node) => boolean;
45
- /** Mask private input content on the library's internal clone, pre-raster. */
46
- readonly onCloneNode: (clone: Node) => void;
47
- }
48
- /** Turns a DOM subtree into image bytes at a given size (injectable for tests). */
49
- export type CaptureBackend = (target: Element, meta: {
50
- width: number;
51
- height: number;
52
- type: string;
53
- } & CaptureHooks) => Promise<Blob | null>;
54
- /** Options for {@link captureView}. */
55
- export interface CaptureOptions {
56
- /** The element to capture. Defaults to the document element (the full view). */
57
- readonly target?: Element;
58
- readonly doc?: Document;
59
- readonly win?: Window;
60
- /** Output content type. Defaults to `image/png`. */
61
- readonly type?: string;
62
- /** Override the raster backend (the default is `modern-screenshot`). */
63
- readonly capture?: CaptureBackend;
64
- }
65
- /**
66
- * Capture the current view as a masked screenshot. Delegates the raster to the
67
- * backend ({@link captureViaModernScreenshot} by default), which excludes the SDK's
68
- * own UI and masks input values on its internal clone — **before** it embeds or
69
- * serialises anything — then returns the image bytes, or `null` if capture wasn't
70
- * possible.
71
- */
72
- export declare function captureView(options?: CaptureOptions): Promise<Capture | null>;
73
- export {};
package/dist/scrub.d.ts DELETED
@@ -1,61 +0,0 @@
1
- /**
2
- * The single client-side **scrub choke point** every report passes through
3
- * before transport (spec 0003 §C; research `sentry-error-capture-findings.md`
4
- * §7.4) — the SDK's `beforeSend` equivalent.
5
- *
6
- * Masking is the SDK's job, done in the browser before anything leaves the page.
7
- * The screenshot is masked at capture, and breadcrumbs never record a value or a
8
- * body at the source; `runBeforeSend` is the **last** gate over the assembled
9
- * report. Its default scrubbers are **on**: they strip credentials, query
10
- * strings, and bearer tokens from URLs, and redact obvious PII (emails, long
11
- * digit runs, bearer tokens) from crumb and error text. The result is then handed
12
- * to an optional per-project hook that can mutate it further or drop the whole
13
- * report by returning `null`.
14
- *
15
- * The hook is **synchronous and network-free** by contract, and both manual
16
- * (overlay) and automatic (error-capture) reports run through the very same
17
- * choke point. A project relaxes the defaults with `scrub: false`, or reshapes
18
- * the draft in its own hook — never a silent raw send.
19
- */
20
- import type { ReportContent } from "./report";
21
- /** The per-project client scrub hook. Return `null` to drop the whole report. */
22
- export type BeforeSend = (draft: ReportContent) => ReportContent | null;
23
- /** Options for {@link runBeforeSend}. */
24
- export interface BeforeSendOptions {
25
- /** The per-project hook, run **after** the default scrubbers. */
26
- readonly hook?: BeforeSend | null;
27
- /** Run the built-in default scrubbers first. Defaults to `true`. */
28
- readonly scrub?: boolean;
29
- }
30
- /**
31
- * Redact obvious PII from free text: email addresses, `Bearer <token>` /
32
- * `token <value>` pairs, and long digit runs. Conservative by design — it keeps
33
- * the shape of the message readable while removing the sensitive spans.
34
- */
35
- export declare function redactPii(text: string): string;
36
- /**
37
- * Strip the sensitive parts of a URL: userinfo credentials
38
- * (`scheme://user:pass@host`), the entire query string, a token-bearing
39
- * fragment (one that carries `key=value`), and PII (emails, long digit runs) in
40
- * the **path segments** (#139). Plain hash routes (`#/checkout`) are kept. Works
41
- * on absolute and relative URLs alike, with no dependency and no throw. The host
42
- * (authority) is never redacted — only the path and any surviving fragment route.
43
- */
44
- export declare function scrubUrl(url: string): string;
45
- /**
46
- * Apply the built-in default scrubbers to a report draft: strip the page URL,
47
- * and scrub every crumb's URLs and redact PII from its text. The Reporter's own
48
- * `comment` is intentionally left untouched — it is authored on purpose, not
49
- * scraped. The screenshot and input values are masked elsewhere (at capture and
50
- * at crumb creation); this is the final URL/PII sweep.
51
- */
52
- export declare function applyDefaultScrub(draft: ReportContent): ReportContent;
53
- /**
54
- * Run the report draft through the client scrub choke point: the default
55
- * scrubbers first (unless `scrub` is `false`), then the optional per-project
56
- * hook. Returns the scrubbed (and possibly hook-mutated) draft, or `null` when
57
- * the hook drops the report. A hook that throws is treated as a no-op — the
58
- * already-scrubbed draft is kept, so a buggy hook never breaks the report path
59
- * nor leaks unscrubbed data.
60
- */
61
- export declare function runBeforeSend(draft: ReportContent, options?: BeforeSendOptions): ReportContent | null;