@fixback/expo 0.1.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.
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Client-side backpressure for **automatic** error reports — a verbatim port of
3
+ * `packages/sdk/src/auto-report-backoff.ts` (spec 0003 §E/§H). When ingest
4
+ * sheds `source: auto` load it answers `429` with a `Retry-After`; the SDK
5
+ * honours it by holding a pause window during which further `source: auto`
6
+ * reports are dropped without touching the network. **Manual** reports — a
7
+ * human tapping Send — never consult this gate.
8
+ */
9
+ /** A source of the current time in epoch milliseconds — injectable for tests. */
10
+ export type Clock = () => number;
11
+ /** The hold window applied when a `429` carries no usable `Retry-After`. */
12
+ export declare const DEFAULT_RETRY_AFTER_SECONDS = 60;
13
+ /**
14
+ * Parse a `Retry-After` header into whole seconds to hold for. Handles both
15
+ * HTTP forms — a delta-seconds integer and an HTTP-date (measured from `now`,
16
+ * rounded up and clamped at zero) — and falls back to
17
+ * {@link DEFAULT_RETRY_AFTER_SECONDS} when the header is absent, blank, or
18
+ * unparseable.
19
+ */
20
+ export declare function parseRetryAfter(header: string | null | undefined, now: number): number;
21
+ /**
22
+ * A single pause window for `source: auto` reports. `hold` opens (or extends)
23
+ * it from a `429`'s `Retry-After`; `isPaused` reports whether it is still open.
24
+ * The default instance in `submit.ts` is shared across the app's reports so the
25
+ * hold persists across successive auto submissions.
26
+ */
27
+ export declare class AutoReportBackoff {
28
+ private readonly now;
29
+ /** Epoch ms until which `source: auto` reports are held; `0` when clear. */
30
+ private pausedUntil;
31
+ constructor(now?: Clock);
32
+ /** Is the `source: auto` pause window currently open? */
33
+ isPaused(): boolean;
34
+ /** Whole seconds remaining in the pause window (`0` when clear). */
35
+ retryAfterSeconds(): number;
36
+ /**
37
+ * Open (or extend) the window from a `429`'s `Retry-After` value, returning
38
+ * the seconds it will hold for. The window only ever grows — a shorter later
39
+ * hold never clips a longer one already in effect.
40
+ */
41
+ hold(retryAfterHeader: string | null | undefined): number;
42
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Client-side backpressure for **automatic** error reports — a verbatim port of
3
+ * `packages/sdk/src/auto-report-backoff.ts` (spec 0003 §E/§H). When ingest
4
+ * sheds `source: auto` load it answers `429` with a `Retry-After`; the SDK
5
+ * honours it by holding a pause window during which further `source: auto`
6
+ * reports are dropped without touching the network. **Manual** reports — a
7
+ * human tapping Send — never consult this gate.
8
+ */
9
+ /** The hold window applied when a `429` carries no usable `Retry-After`. */
10
+ export const DEFAULT_RETRY_AFTER_SECONDS = 60;
11
+ /**
12
+ * Parse a `Retry-After` header into whole seconds to hold for. Handles both
13
+ * HTTP forms — a delta-seconds integer and an HTTP-date (measured from `now`,
14
+ * rounded up and clamped at zero) — and falls back to
15
+ * {@link DEFAULT_RETRY_AFTER_SECONDS} when the header is absent, blank, or
16
+ * unparseable.
17
+ */
18
+ export function parseRetryAfter(header, now) {
19
+ if (header == null)
20
+ return DEFAULT_RETRY_AFTER_SECONDS;
21
+ const value = header.trim();
22
+ if (value === "")
23
+ return DEFAULT_RETRY_AFTER_SECONDS;
24
+ if (/^\d+$/.test(value)) {
25
+ return Number(value);
26
+ }
27
+ const when = Date.parse(value);
28
+ if (!Number.isNaN(when)) {
29
+ return Math.max(0, Math.ceil((when - now) / 1000));
30
+ }
31
+ return DEFAULT_RETRY_AFTER_SECONDS;
32
+ }
33
+ /**
34
+ * A single pause window for `source: auto` reports. `hold` opens (or extends)
35
+ * it from a `429`'s `Retry-After`; `isPaused` reports whether it is still open.
36
+ * The default instance in `submit.ts` is shared across the app's reports so the
37
+ * hold persists across successive auto submissions.
38
+ */
39
+ export class AutoReportBackoff {
40
+ now;
41
+ /** Epoch ms until which `source: auto` reports are held; `0` when clear. */
42
+ pausedUntil = 0;
43
+ constructor(now = Date.now) {
44
+ this.now = now;
45
+ }
46
+ /** Is the `source: auto` pause window currently open? */
47
+ isPaused() {
48
+ return this.now() < this.pausedUntil;
49
+ }
50
+ /** Whole seconds remaining in the pause window (`0` when clear). */
51
+ retryAfterSeconds() {
52
+ return Math.max(0, Math.ceil((this.pausedUntil - this.now()) / 1000));
53
+ }
54
+ /**
55
+ * Open (or extend) the window from a `429`'s `Retry-After` value, returning
56
+ * the seconds it will hold for. The window only ever grows — a shorter later
57
+ * hold never clips a longer one already in effect.
58
+ */
59
+ hold(retryAfterHeader) {
60
+ const now = this.now();
61
+ const seconds = parseRetryAfter(retryAfterHeader, now);
62
+ const until = now + seconds * 1000;
63
+ if (until > this.pausedUntil)
64
+ this.pausedUntil = until;
65
+ return seconds;
66
+ }
67
+ }
package/dist/boot.d.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The ingest **boot** wire-contract, vendored — kept file-parallel with
3
+ * `packages/sdk/src/boot.ts` (ADR-0021: a server contract change must be
4
+ * mirrored in both clients).
5
+ *
6
+ * The one mobile difference: a native HTTP stack attaches no `Origin` header,
7
+ * so the SDK sends the configured `origin` explicitly — the server reads it to
8
+ * decide `originAllowed` exactly as it does for a browser request (spec 0004 §A).
9
+ */
10
+ import { type FetchLike } from "./http";
11
+ /** A Project's Gate — who may submit. Mirrors the server's `ProjectGate`. */
12
+ export type ProjectGate = "open" | "invited" | "internal";
13
+ /** The trust tier a Reporter holds. Mirrors the server's `ReporterTier`. */
14
+ export type ReporterTier = "public" | "invited" | "internal";
15
+ /**
16
+ * Optional identity evidence the SDK forwards to boot. None of it is a tier: the
17
+ * server re-derives trust from this evidence and never honours a self-declared
18
+ * tier, so the SDK does not send one.
19
+ */
20
+ export interface IdentityInputs {
21
+ readonly signedIdentity?: string;
22
+ readonly reporterId?: string;
23
+ readonly anonymousId?: string;
24
+ }
25
+ /** The JSON body `POST /api/ingest/boot` accepts. */
26
+ export interface BootRequest extends IdentityInputs {
27
+ readonly key: string;
28
+ }
29
+ /**
30
+ * The Project's effective console/network capture config, served on the boot
31
+ * answer. Optional on the wire: an older server that does not send it (or a
32
+ * malformed value) is treated as **capture on**, and init options override
33
+ * whatever is served.
34
+ */
35
+ export interface CaptureConfig {
36
+ readonly console: boolean;
37
+ readonly network: boolean;
38
+ }
39
+ /**
40
+ * The boot answer: whether this origin is allowlisted, the Project's Gate, the
41
+ * caller's derived tier (`null` when a presented identity was refused), whether
42
+ * a submission would be accepted right now, and the Project's capture config.
43
+ * The shake gesture arms only when `canSubmit` is true.
44
+ */
45
+ export interface BootAnswer {
46
+ readonly originAllowed: boolean;
47
+ readonly gate: ProjectGate;
48
+ readonly tier: ReporterTier | null;
49
+ readonly canSubmit: boolean;
50
+ /** The Project's console/network capture config; absent ⇒ default-on. */
51
+ readonly capture?: CaptureConfig;
52
+ }
53
+ /** Join an API base URL with the boot path, tolerating a trailing slash. */
54
+ export declare function bootEndpoint(apiUrl: string): string;
55
+ /**
56
+ * Ask ingest whether a submission would be accepted for this key / origin /
57
+ * Gate. Resolves to the boot answer, or `null` when Fixback could not be
58
+ * reached, the key was refused, or the response was not a boot answer. It never
59
+ * throws: any non-answer is treated by the caller as "stay dormant", so a
60
+ * Fixback outage stays invisible to the host app.
61
+ *
62
+ * Unlike a browser, React Native attaches no `Origin` header of its own — the
63
+ * configured origin is sent explicitly so the server's allowlist check works.
64
+ */
65
+ export declare function requestBoot(apiUrl: string, request: BootRequest, origin: string, fetchImpl?: FetchLike): Promise<BootAnswer | null>;
package/dist/boot.js ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The ingest **boot** wire-contract, vendored — kept file-parallel with
3
+ * `packages/sdk/src/boot.ts` (ADR-0021: a server contract change must be
4
+ * mirrored in both clients).
5
+ *
6
+ * The one mobile difference: a native HTTP stack attaches no `Origin` header,
7
+ * so the SDK sends the configured `origin` explicitly — the server reads it to
8
+ * decide `originAllowed` exactly as it does for a browser request (spec 0004 §A).
9
+ */
10
+ import { globalFetch } from "./http";
11
+ /** Join an API base URL with the boot path, tolerating a trailing slash. */
12
+ export function bootEndpoint(apiUrl) {
13
+ return `${apiUrl.replace(/\/+$/, "")}/api/ingest/boot`;
14
+ }
15
+ /** Narrow an unknown JSON body to a `BootAnswer` before the SDK trusts it. */
16
+ function isBootAnswer(value) {
17
+ if (typeof value !== "object" || value === null)
18
+ return false;
19
+ const v = value;
20
+ return (typeof v.originAllowed === "boolean" &&
21
+ typeof v.canSubmit === "boolean" &&
22
+ typeof v.gate === "string" &&
23
+ (v.tier === null || typeof v.tier === "string"));
24
+ }
25
+ /**
26
+ * Ask ingest whether a submission would be accepted for this key / origin /
27
+ * Gate. Resolves to the boot answer, or `null` when Fixback could not be
28
+ * reached, the key was refused, or the response was not a boot answer. It never
29
+ * throws: any non-answer is treated by the caller as "stay dormant", so a
30
+ * Fixback outage stays invisible to the host app.
31
+ *
32
+ * Unlike a browser, React Native attaches no `Origin` header of its own — the
33
+ * configured origin is sent explicitly so the server's allowlist check works.
34
+ */
35
+ export async function requestBoot(apiUrl, request, origin, fetchImpl) {
36
+ const doFetch = fetchImpl ?? globalFetch();
37
+ if (!doFetch)
38
+ return null;
39
+ let response;
40
+ try {
41
+ response = await doFetch(bootEndpoint(apiUrl), {
42
+ method: "POST",
43
+ headers: { "content-type": "application/json", origin },
44
+ body: JSON.stringify(request),
45
+ });
46
+ }
47
+ catch {
48
+ return null; // network error / Fixback unreachable
49
+ }
50
+ if (!response.ok)
51
+ return null; // 401 unknown key, or any other refusal
52
+ let body;
53
+ try {
54
+ body = await response.json();
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ return isBootAnswer(body) ? body : null;
60
+ }
@@ -0,0 +1,276 @@
1
+ /**
2
+ * The trace **breadcrumb ring buffer** and its React Native capture
3
+ * instrumentation — a port of `packages/sdk/src/breadcrumbs.ts` kept
4
+ * file-parallel on purpose (ADR-0021): the entry shapes, stream budgets, caps,
5
+ * and eviction rules are the web SDK's exactly, so the server and dashboard
6
+ * cannot tell a mobile trace from a web one.
7
+ *
8
+ * Mobile mappings (spec 0004 §D): `console` wrapping is unchanged; network
9
+ * capture patches `XMLHttpRequest` only — React Native's `fetch` is a polyfill
10
+ * over XHR, so wrapping both would double-record; navigation crumbs come from
11
+ * the host app's `trackScreen` calls (there is no `history` to patch); the
12
+ * masked `ui.*` streams have no mobile source yet and simply never occur.
13
+ *
14
+ * Everything private is kept out **at the source**: network crumbs carry method
15
+ * + URL + status metadata only, **never** bodies; URLs are scrubbed as the
16
+ * crumb is built. The `beforeSend` choke point (`scrub.ts`) is the final gate.
17
+ * The SDK must never throw into the host app, so every instrumentation hook is
18
+ * wrapped: a capture failure is swallowed and the original behaviour always runs.
19
+ */
20
+ /** Console-style severity a `console` crumb records. */
21
+ export type BreadcrumbLevel = "log" | "info" | "warn" | "error" | "assert" | "debug";
22
+ /**
23
+ * The kind of activity a crumb records. The full web vocabulary is kept for
24
+ * wire parity — on mobile only `console`, `navigation`, `xhr`, and `error`
25
+ * are ever produced today.
26
+ */
27
+ export type BreadcrumbCategory = "console" | "navigation" | "fetch" | "xhr" | "beacon" | "ui.click" | "ui.input" | "error";
28
+ /** Which API issued a captured network request. */
29
+ export type NetworkApi = "fetch" | "xhr" | "beacon";
30
+ /** A network request's failure classification. */
31
+ export type NetworkOutcome = "ok" | "http-4xx" | "http-5xx" | "network-error" | "timeout" | "aborted" | "opaque-cors";
32
+ /**
33
+ * A crumb's structured detail. Deliberately narrow: there is **no** field for a
34
+ * request/response body or an input value, so those can never be recorded.
35
+ */
36
+ export interface BreadcrumbData {
37
+ readonly url?: string;
38
+ readonly method?: string;
39
+ readonly status?: number;
40
+ /** A masked target for a `ui.*` crumb — unused on mobile, kept for parity. */
41
+ readonly target?: string;
42
+ readonly from?: string;
43
+ readonly to?: string;
44
+ readonly errorType?: string;
45
+ }
46
+ /** The type tag on a structured console argument. */
47
+ export type ConsoleArgType = "string" | "number" | "bool" | "null" | "json" | "error";
48
+ /** One structured console argument: a type tag plus a JSON-safe value. */
49
+ export interface ConsoleArg {
50
+ readonly t: ConsoleArgType;
51
+ readonly v: unknown;
52
+ }
53
+ /** A `file:line` source location — where a console call was made. */
54
+ export interface SourceLocation {
55
+ readonly file: string;
56
+ readonly line: number;
57
+ }
58
+ /**
59
+ * One entry in a trace stream. Alongside its semantic fields every entry
60
+ * carries a stable {@link id} and a high-res monotonic {@link mono} timestamp —
61
+ * both stamped by the buffer on `add` — so entries from the three independent
62
+ * streams order and cross-link exactly. `timestamp` stays epoch ms.
63
+ */
64
+ export interface Breadcrumb {
65
+ readonly category: BreadcrumbCategory;
66
+ readonly message?: string;
67
+ readonly level?: BreadcrumbLevel;
68
+ /** Epoch milliseconds when the crumb was recorded. */
69
+ readonly timestamp: number;
70
+ /** A stable id, unique within the buffer — assigned on `add` when not set. */
71
+ readonly id?: string;
72
+ /** A high-res monotonic timestamp — assigned on `add`. */
73
+ readonly mono?: number;
74
+ readonly data?: BreadcrumbData;
75
+ /** For a `console` crumb: the call's structured, type-tagged arguments. */
76
+ readonly args?: readonly ConsoleArg[];
77
+ /** For a `console` crumb: the best-effort `file:line` the call was made from. */
78
+ readonly source?: SourceLocation;
79
+ /** For an auto-captured `error` crumb: ids of the entries preceding the throw. */
80
+ readonly causedBy?: readonly string[];
81
+ /** Network crumb metadata, lifted to the top level — never a body or header. */
82
+ readonly api?: NetworkApi;
83
+ readonly method?: string;
84
+ readonly url?: string;
85
+ readonly status?: number;
86
+ readonly statusText?: string;
87
+ readonly durationMs?: number;
88
+ readonly reqSize?: number;
89
+ readonly respSize?: number;
90
+ readonly contentType?: string;
91
+ readonly outcome?: NetworkOutcome;
92
+ }
93
+ /**
94
+ * The three independent trace streams. Each is its own FIFO ring with its own
95
+ * size budget, so a chatty stream can't evict another's lead-up.
96
+ */
97
+ export type TraceStream = "network" | "console" | "breadcrumbs";
98
+ /** Per-stream ring budgets — the web SDK's numbers, unchanged. */
99
+ export declare const DEFAULT_STREAM_BUDGETS: Readonly<Record<TraceStream, number>>;
100
+ /** The shared age cap: entries older than this are pruned from every stream. */
101
+ export declare const DEFAULT_MAX_AGE_MS: number;
102
+ /** Console levels captured by default: **all** of them. */
103
+ export declare const DEFAULT_CONSOLE_LEVELS: readonly BreadcrumbLevel[];
104
+ /**
105
+ * The console levels **pinned** against eviction: when the console stream
106
+ * overflows, `warn`/`error`/`assert` are retained ahead of `log`/`info`/`debug`.
107
+ */
108
+ export declare const PINNED_CONSOLE_LEVELS: readonly BreadcrumbLevel[];
109
+ /**
110
+ * The console levels for which `source` (`file:line`) is captured — reading a
111
+ * call site constructs a `new Error()` on every call, so only the levels that
112
+ * matter for debugging pay that cost. Mirrors {@link PINNED_CONSOLE_LEVELS}.
113
+ */
114
+ export declare const SOURCE_CAPTURE_LEVELS: readonly BreadcrumbLevel[];
115
+ /** Which stream a crumb's category belongs to. */
116
+ export declare function streamOf(category: string): TraceStream;
117
+ /** Filters or edits each crumb before it enters the buffer; `null` drops it. */
118
+ export type BeforeBreadcrumb = (crumb: Breadcrumb) => Breadcrumb | null;
119
+ /** Configuration for {@link createBreadcrumbBuffer}. All values are optional. */
120
+ export interface BreadcrumbBufferConfig {
121
+ /** Per-stream size budgets; any stream omitted falls back to the defaults. */
122
+ readonly budgets?: Partial<Record<TraceStream, number>>;
123
+ /** Shared age cap in ms; `0` (or non-positive) disables age pruning. */
124
+ readonly maxAgeMs?: number;
125
+ /** A per-crumb filter (mute a category, edit, or drop by returning `null`). */
126
+ readonly beforeBreadcrumb?: BeforeBreadcrumb | null;
127
+ /** Epoch clock source, injectable for tests. Defaults to `Date.now`. */
128
+ readonly now?: () => number;
129
+ /** High-res monotonic clock, injectable for tests. */
130
+ readonly mono?: () => number;
131
+ /** Stable id generator, injectable for tests. */
132
+ readonly nextId?: () => string;
133
+ }
134
+ /** A live trace buffer. */
135
+ export interface BreadcrumbBuffer {
136
+ /** Record a crumb (subject to filtering, stamping, size, and age trimming). */
137
+ add(crumb: Breadcrumb): void;
138
+ /** The current crumbs, merged across streams and ordered in time. */
139
+ snapshot(): Breadcrumb[];
140
+ /** Drop every crumb. */
141
+ clear(): void;
142
+ }
143
+ /**
144
+ * Create the per-stream trace buffer: **three independent FIFO rings** —
145
+ * `network`, `console`, `breadcrumbs` — each trimmed to its own budget so a
146
+ * chatty stream never evicts another's lead-up, plus a shared age cap.
147
+ * `snapshot` merges the three streams into one `mono`-ordered array for
148
+ * transport — the wire keeps its single `trace` field.
149
+ */
150
+ export declare function createBreadcrumbBuffer(config?: BreadcrumbBufferConfig): BreadcrumbBuffer;
151
+ /** Longest scrubbed URL kept on a crumb. */
152
+ export declare const MAX_URL_LENGTH = 2048;
153
+ /** Structured console argument caps, all applied at assembly. */
154
+ export declare const MAX_ARG_DEPTH = 4;
155
+ export declare const MAX_ARG_ITEMS = 100;
156
+ export declare const MAX_ARG_STRING_LENGTH = 1024;
157
+ export declare const MAX_CONSOLE_ARGS_BYTES = 4096;
158
+ /** Classify one console argument into a type-tagged {@link ConsoleArg}. */
159
+ export declare function toConsoleArg(value: unknown): ConsoleArg;
160
+ /**
161
+ * A `console` crumb from a captured call's level and arguments. The `message`
162
+ * is the one-line preview; `args` preserves each argument as a structured,
163
+ * type-tagged, size-capped value; `source` is the best-effort `file:line`.
164
+ */
165
+ export declare function consoleCrumb(level: BreadcrumbLevel, args: readonly unknown[], timestamp: number, source?: SourceLocation): Breadcrumb;
166
+ /**
167
+ * A `navigation` crumb; both sides are scrubbed and length-capped as the crumb
168
+ * is built. On mobile this is fed by `trackScreen` (spec 0004 §D) — the values
169
+ * are screen URLs derived from the configured origin.
170
+ */
171
+ export declare function navigationCrumb(from: string, to: string, timestamp: number): Breadcrumb;
172
+ /** Classify an HTTP status into a {@link NetworkOutcome}. */
173
+ export declare function outcomeFromStatus(status: number | undefined): NetworkOutcome;
174
+ /**
175
+ * The trivially-known byte size of a request body — a string's UTF-8 length, a
176
+ * `Blob`'s `.size`, or an `ArrayBuffer`/typed-array `.byteLength`. Anything
177
+ * that would require **reading** the body returns `undefined`.
178
+ */
179
+ export declare function trivialBodySize(body: unknown): number | undefined;
180
+ /** Parse a `content-length` header into a non-negative byte count, or `undefined`. */
181
+ export declare function parseContentLength(value: string | null | undefined): number | undefined;
182
+ /** The media type from a `content-type` header (before any `;` parameters). */
183
+ export declare function contentTypeOf(value: string | null | undefined): string | undefined;
184
+ /** The rich metadata a network crumb records — never a body or header. */
185
+ export interface NetworkCrumbInput {
186
+ readonly api: NetworkApi;
187
+ readonly method: string;
188
+ /** The raw request URL — scrubbed as the crumb is built. */
189
+ readonly url: string;
190
+ readonly status?: number;
191
+ readonly statusText?: string;
192
+ readonly durationMs?: number;
193
+ readonly reqSize?: number;
194
+ readonly respSize?: number;
195
+ readonly contentType?: string;
196
+ readonly outcome: NetworkOutcome;
197
+ }
198
+ /**
199
+ * A network crumb with the rich, metadata-only fields the Network tab renders.
200
+ * The URL is scrubbed and length-capped at assembly. The shape has **no field
201
+ * for a request/response body or an arbitrary header**.
202
+ */
203
+ export declare function networkCrumb(input: NetworkCrumbInput, timestamp: number): Breadcrumb;
204
+ /** A thin `xhr` crumb from method + URL + status (outcome derived from the status). */
205
+ export declare function xhrCrumb(method: string, url: string, status: number | undefined, timestamp: number): Breadcrumb;
206
+ /**
207
+ * An `error` crumb for the failing exception that ends the trace. When
208
+ * `causedBy` is given (an auto-captured error), it rides on the crumb as the
209
+ * causal pointer to the ids of the entries immediately preceding the throw.
210
+ */
211
+ export declare function errorCrumb(error: unknown, timestamp: number, causedBy?: readonly string[]): Breadcrumb;
212
+ /** Detaches an installed instrumentation, restoring the original behaviour. */
213
+ export type Teardown = () => void;
214
+ type AnyFn = (...args: unknown[]) => unknown;
215
+ /** The `console` surface the wrapper patches — injectable for tests. */
216
+ export type ConsoleLike = Partial<Record<BreadcrumbLevel, AnyFn>>;
217
+ /** React Native's global `console`, when present. */
218
+ export declare function globalConsole(): ConsoleLike | undefined;
219
+ /**
220
+ * The best-effort `file:line` a console call was made from. Parses the frames
221
+ * of a stack, skipping `skipFrames` leading (SDK-internal) frames so the
222
+ * source points at the host code that called `console.*`.
223
+ */
224
+ export declare function sourceFromStack(stack: string | undefined, skipFrames?: number): SourceLocation | undefined;
225
+ /** Wrap `console` methods so calls at the captured levels become crumbs. */
226
+ export declare function instrumentConsole(buffer: BreadcrumbBuffer, consoleObj: ConsoleLike, levels: readonly BreadcrumbLevel[], now: () => number): Teardown;
227
+ /** The XHR instance surface the network instrumentation touches. */
228
+ export interface XhrInstance {
229
+ status: number;
230
+ statusText?: string;
231
+ open(method: string, url: string | {
232
+ toString(): string;
233
+ }, ...rest: unknown[]): void;
234
+ send(body?: unknown): void;
235
+ getResponseHeader?(name: string): string | null;
236
+ addEventListener(type: string, listener: () => void): void;
237
+ removeEventListener(type: string, listener: () => void): void;
238
+ }
239
+ /** The XHR constructor whose prototype is patched — injectable for tests. */
240
+ export interface XhrConstructor {
241
+ new (): XhrInstance;
242
+ prototype: XhrInstance;
243
+ }
244
+ /** React Native's global `XMLHttpRequest`, when present. */
245
+ export declare function globalXhr(): XhrConstructor | undefined;
246
+ /**
247
+ * Patch `XMLHttpRequest` to record a rich network crumb when a request settles.
248
+ * On React Native this is the **single** network hook: the built-in `fetch` is
249
+ * a polyfill over XHR, so its traffic is captured here too (as `xhr` crumbs)
250
+ * and `fetch` itself is deliberately not wrapped — wrapping both would record
251
+ * every request twice (spec 0004 §D). The `send` body argument is **never read
252
+ * for content** — only its trivially-known size.
253
+ */
254
+ export declare function instrumentXhr(buffer: BreadcrumbBuffer, ctor: XhrConstructor | undefined, ignoreUrl: (url: string) => boolean, now: () => number, mono?: () => number): Teardown;
255
+ /** Options for {@link instrumentBreadcrumbs}. */
256
+ export interface InstrumentOptions {
257
+ readonly consoleObj?: ConsoleLike | null;
258
+ readonly xhr?: XhrConstructor | null;
259
+ readonly consoleLevels?: readonly BreadcrumbLevel[];
260
+ /** Skip URLs (the SDK's own ingest calls) so they never become crumbs. */
261
+ readonly ignoreUrl?: (url: string) => boolean;
262
+ readonly now?: () => number;
263
+ readonly mono?: () => number;
264
+ /** Instrument the console stream. Defaults to `true`. */
265
+ readonly captureConsole?: boolean;
266
+ /** Instrument the network stream (XHR). Defaults to `true`. */
267
+ readonly captureNetwork?: boolean;
268
+ }
269
+ /**
270
+ * Install the React Native capture hooks — the console wrapper and the XHR
271
+ * patch — and return a single teardown that removes them all. Each hook is
272
+ * independent and defensive: a failure in one never blocks the other, and none
273
+ * can throw into the host app. An off stream is never instrumented at all.
274
+ */
275
+ export declare function instrumentBreadcrumbs(buffer: BreadcrumbBuffer, options?: InstrumentOptions): Teardown;
276
+ export {};