@fixback/sdk 0.4.0 → 0.6.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/README.md +12 -2
- package/dist/annotation.d.ts +10 -73
- package/dist/capture/extract-dom.d.ts +33 -0
- package/dist/connect.d.ts +47 -0
- package/dist/dom.d.ts +7 -19
- package/dist/element-picker.d.ts +1 -1
- package/dist/error-capture.d.ts +21 -93
- package/dist/fixback.umd.js +36 -215
- package/dist/fixback.umd.js.map +1 -1
- package/dist/identity.d.ts +6 -1
- package/dist/index.d.ts +17 -15
- package/dist/index.mjs +3598 -4681
- package/dist/index.mjs.map +1 -1
- package/dist/init.d.ts +63 -97
- package/dist/mount.d.ts +46 -0
- package/dist/overlay/dom.d.ts +16 -0
- package/dist/overlay/icons.d.ts +23 -0
- package/dist/overlay/marking.d.ts +64 -0
- package/dist/overlay/send.d.ts +65 -0
- package/dist/overlay/view.d.ts +78 -0
- package/dist/overlay-styles.d.ts +1 -1
- package/dist/overlay.d.ts +40 -43
- package/dist/package.json +3 -0
- package/dist/report.d.ts +22 -111
- package/dist/reporter-session-store.d.ts +17 -0
- package/dist/session.d.ts +27 -0
- package/dist/submit.d.ts +56 -19
- package/dist/trace/instrument/beacon.d.ts +16 -0
- package/dist/trace/instrument/console.d.ts +11 -0
- package/dist/trace/instrument/fetch.d.ts +16 -0
- package/dist/trace/instrument/index.d.ts +64 -0
- package/dist/trace/instrument/navigation.d.ts +12 -0
- package/dist/trace/instrument/ui.d.ts +23 -0
- package/dist/trace/instrument/window.d.ts +66 -0
- package/dist/trace/instrument/xhr.d.ts +15 -0
- package/dist/version.d.ts +1 -1
- package/package.json +8 -5
- package/dist/auto-report-backoff.d.ts +0 -47
- package/dist/boot.d.ts +0 -74
- package/dist/breadcrumbs.d.ts +0 -448
- package/dist/invite.d.ts +0 -104
- package/dist/onboarding-styles.d.ts +0 -8
- package/dist/onboarding.d.ts +0 -44
- package/dist/screenshot.d.ts +0 -73
- package/dist/scrub.d.ts +0 -61
package/dist/overlay.d.ts
CHANGED
|
@@ -1,31 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The report **overlay** — the on-page panel a Reporter files a report from, built
|
|
3
|
+
* to the frozen Signal Reporter prototype (`docs/design/Fixback Reporter.dc.html`).
|
|
4
|
+
*
|
|
5
|
+
* This module is the wiring: it mounts the view lazily inside its own Shadow DOM
|
|
6
|
+
* (isolated from the host page, and marked so the replay recorder and
|
|
7
|
+
* element-picker skip it), opens on the launcher's `fixback:launch` seam, and joins
|
|
8
|
+
* the three parts that do the work:
|
|
9
|
+
*
|
|
10
|
+
* - `./overlay/view` builds the markup and hands back typed refs;
|
|
11
|
+
* - `./overlay/marking` runs the three composable marking layers (element-pick,
|
|
12
|
+
* region-capture, draw) and owns what has been marked;
|
|
13
|
+
* - `./overlay/send` assembles, scrubs, and submits.
|
|
14
|
+
*
|
|
15
|
+
* What is left here is the panel's own state: open/closed, the compose → sending →
|
|
16
|
+
* sent phase, the status line, and the confirmation's auto-close. The marks are
|
|
17
|
+
* viewport-space vectors composited at view time (spec §D), never baked into an
|
|
18
|
+
* image; the still they annotate is a frame of the buffered rrweb window
|
|
19
|
+
* (reconstructed downstream since ADR-0027), so the SDK rasterises nothing.
|
|
20
|
+
*/
|
|
21
|
+
import type { BeforeSend, IdentityInputs, ProjectGate, ReporterIdentity } from "@fixback/sdk-core";
|
|
22
|
+
import { type StartPickerFn } from "./overlay/marking";
|
|
23
|
+
import { type BreadcrumbSource, type SubmitReportFn } from "./overlay/send";
|
|
5
24
|
import type { ReplaySource } from "./replay";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import { type SubmitInput, type SubmitResult } from "./submit";
|
|
9
|
-
/** Marks the overlay's host element in the light DOM (skipped by capture/picker). */
|
|
10
|
-
export declare const OVERLAY_ATTRIBUTE = "data-fixback-overlay";
|
|
11
|
-
type CaptureViewFn = (options?: CaptureOptions) => Promise<Capture | null>;
|
|
12
|
-
type SubmitReportFn = (apiUrl: string, input: SubmitInput, fetchImpl?: typeof fetch) => Promise<SubmitResult>;
|
|
13
|
-
type StartPickerFn = (options: ElementPickerOptions) => ElementPicker;
|
|
25
|
+
export { OVERLAY_ATTRIBUTE } from "./overlay/dom";
|
|
26
|
+
export type { BreadcrumbSource } from "./overlay/send";
|
|
14
27
|
/** Injectable collaborators, defaulted to the real implementations. */
|
|
15
28
|
export interface OverlayDeps {
|
|
16
|
-
readonly captureView: CaptureViewFn;
|
|
17
29
|
readonly submitReport: SubmitReportFn;
|
|
18
30
|
readonly startElementPicker: StartPickerFn;
|
|
19
31
|
}
|
|
20
|
-
/** A read-only view of the trace buffer the overlay attaches to a report. */
|
|
21
|
-
export interface BreadcrumbSource {
|
|
22
|
-
snapshot(): readonly Breadcrumb[];
|
|
23
|
-
}
|
|
24
32
|
/** Configuration for {@link createOverlay}. */
|
|
25
33
|
export interface OverlayConfig {
|
|
26
34
|
readonly apiUrl: string;
|
|
27
35
|
readonly key: string;
|
|
28
|
-
|
|
36
|
+
/** Identity evidence, read **live** at send time (ADR-0032) — see {@link MountContext}. */
|
|
37
|
+
readonly getIdentity?: () => IdentityInputs;
|
|
29
38
|
/**
|
|
30
39
|
* The Project's Gate, forwarded verbatim from the boot answer. Reserved for
|
|
31
40
|
* Gate-aware launcher / redemption behaviour (spec §A/§F); the overlay itself
|
|
@@ -33,21 +42,20 @@ export interface OverlayConfig {
|
|
|
33
42
|
*/
|
|
34
43
|
readonly gate?: ProjectGate;
|
|
35
44
|
/**
|
|
36
|
-
* The Reporter
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* and never sent back to the server. When `null`/absent no tier is asserted.
|
|
45
|
+
* The current Reporter identity — anonymous, or a connected Account with its
|
|
46
|
+
* server-derived tier — rendered in the header's identity chip (ADR-0032). Updated in
|
|
47
|
+
* place via {@link OverlayController.setIdentity} after a Connect or sign-out.
|
|
40
48
|
*/
|
|
41
|
-
readonly
|
|
49
|
+
readonly reporterIdentity: ReporterIdentity;
|
|
50
|
+
/** Start a Connect from the chip's **Sign in** (ADR-0032). */
|
|
51
|
+
readonly onSignIn: () => void;
|
|
52
|
+
/** Clear the Reporter session from the chip's **Sign out** (ADR-0032). */
|
|
53
|
+
readonly onSignOut: () => void;
|
|
42
54
|
readonly sdkVersion?: string;
|
|
43
55
|
/** The host app's Release (#117, ADR-0024), validated by `init`; rides in the environment. */
|
|
44
56
|
readonly release?: string;
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
* them (from a redemption in this session or a prior one). They ride along with the
|
|
48
|
-
* report so a Member sees who filed it — display only, never a trust signal.
|
|
49
|
-
*/
|
|
50
|
-
readonly display?: ReporterDisplay;
|
|
57
|
+
/** The deploy environment (`production` / `staging` / …), stamped on every report. */
|
|
58
|
+
readonly deployEnvironment?: string;
|
|
51
59
|
/** Where to mount the overlay host. Defaults to `document.body`. */
|
|
52
60
|
readonly target?: HTMLElement;
|
|
53
61
|
readonly doc?: Document;
|
|
@@ -67,20 +75,9 @@ export interface OverlayController {
|
|
|
67
75
|
open(): void;
|
|
68
76
|
close(): void;
|
|
69
77
|
destroy(): void;
|
|
78
|
+
/** Update the identity chip in place after a Connect or sign-out (ADR-0032). */
|
|
79
|
+
setIdentity(identity: ReporterIdentity): void;
|
|
70
80
|
readonly isOpen: boolean;
|
|
71
81
|
}
|
|
72
|
-
/**
|
|
73
|
-
* Create the report overlay — the on-page panel a Reporter files a report from,
|
|
74
|
-
* built to the frozen Signal Reporter prototype (`docs/design/Fixback Reporter.dc.html`).
|
|
75
|
-
* 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
|
|
77
|
-
* `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.
|
|
84
|
-
*/
|
|
82
|
+
/** Create the report overlay. See the module doc. */
|
|
85
83
|
export declare function createOverlay(config: OverlayConfig): OverlayController;
|
|
86
|
-
export {};
|
package/dist/report.d.ts
CHANGED
|
@@ -1,113 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Validate + trim a builder-supplied release (#117, ADR-0024), mirroring the
|
|
65
|
-
* server's rules by value: 1–100 visible characters, no whitespace, control
|
|
66
|
-
* characters, or path separators, and not a reserved name. Returns `undefined`
|
|
67
|
-
* for an invalid value — the SDK drops it (with a warning at `init`) rather than
|
|
68
|
-
* shipping a value the server would discard.
|
|
69
|
-
*/
|
|
70
|
-
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
|
-
}
|
|
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 Breadcrumb, type CaptureEnvironment, type CapturedFrame, type ElementRect, type FeedbackSource, normaliseRelease, type ReportContent, type SelectedElement } from "@fixback/sdk-core";
|
|
15
|
+
import { type Mark, type Rect } from "./annotation";
|
|
16
|
+
export type { CaptureEnvironment, CapturedFrame, ElementRect, FeedbackSource, ReportContent, SelectedElement, };
|
|
17
|
+
export { normaliseRelease };
|
|
111
18
|
/**
|
|
112
19
|
* What the overlay hands to {@link assembleContent} when the Reporter sends. The
|
|
113
20
|
* three marking layers arrive flat (`element` / `region` / `marks`); `assembleContent`
|
|
@@ -121,9 +28,13 @@ export interface ReportDraft {
|
|
|
121
28
|
readonly url?: string;
|
|
122
29
|
readonly environment?: CaptureEnvironment;
|
|
123
30
|
readonly trace?: readonly Breadcrumb[];
|
|
124
|
-
/**
|
|
125
|
-
|
|
126
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The deploy **Environment** the SDK was configured with (ADR-0025) —
|
|
33
|
+
* `production` / `staging` / … — distinct from the build `release` in
|
|
34
|
+
* {@link CaptureEnvironment}. Named apart from `environment` (the capture bag)
|
|
35
|
+
* to avoid the collision, exactly as the wire does.
|
|
36
|
+
*/
|
|
37
|
+
readonly deployEnvironment?: string;
|
|
127
38
|
}
|
|
128
39
|
/**
|
|
129
40
|
* Read the capture environment off a window: the viewport size, the browser's
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser's **Reporter session** store (ADR-0032) — the storage binding only.
|
|
3
|
+
*
|
|
4
|
+
* After a Connect the SDK holds a compact signed token per origin and forwards it on
|
|
5
|
+
* every boot; the boot answer hands back a refreshed one (30-day sliding). The token
|
|
6
|
+
* lives in `localStorage` under a key **scoped by publishable key** (so two Projects on
|
|
7
|
+
* one origin keep separate sessions), the key format shared with every SDK
|
|
8
|
+
* (`@fixback/sdk-core`). Persistence is best-effort: a blocked or absent `localStorage`
|
|
9
|
+
* (private mode) simply means no stored session — the SDK falls back to anonymous
|
|
10
|
+
* rather than throwing, so it never disturbs the host page.
|
|
11
|
+
*/
|
|
12
|
+
/** Read the stored Reporter session token for this Project, or `null`. */
|
|
13
|
+
export declare function readReporterSession(publishableKey: string): string | null;
|
|
14
|
+
/** Persist a Reporter session token for this Project. Best-effort. */
|
|
15
|
+
export declare function writeReporterSession(publishableKey: string, token: string): void;
|
|
16
|
+
/** Clear the stored Reporter session token for this Project (sign out). Best-effort. */
|
|
17
|
+
export declare function clearReporterSession(publishableKey: string): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The **session flow** behind `init` (ADR-0032): recognise the Reporter through the
|
|
3
|
+
* Reporter session token it holds, ask ingest what it may do, and mount only when a
|
|
4
|
+
* submission would be accepted — while exposing `signIn` / `signOut` / `identity`.
|
|
5
|
+
*
|
|
6
|
+
* On boot the SDK forwards its stored **Reporter session** token (from a prior Connect,
|
|
7
|
+
* scoped per publishable key) and swaps in the refreshed one boot hands back (30-day
|
|
8
|
+
* sliding). It also detects a one-time code arriving on the `?fixback=` query parameter
|
|
9
|
+
* — the connect page's redirect fallback, and Fixback-issued links (ticket #250) — and
|
|
10
|
+
* exchanges it for a session before booting.
|
|
11
|
+
*
|
|
12
|
+
* **Connect** binds an Account to the site: `signIn` opens the platform's connect page
|
|
13
|
+
* (popup, or a full redirect when it is blocked), exchanges the returned code, and
|
|
14
|
+
* re-boots — so an Open-gated visitor's launcher gains the Account, and a gated
|
|
15
|
+
* Project's launcher **mounts** once the Account is eligible. The launcher mounts only
|
|
16
|
+
* when boot returns `canSubmit`, so a gated Project shows nothing to a signed-out
|
|
17
|
+
* visitor. Everything is wrapped so a Fixback problem resolves to a no-op and never
|
|
18
|
+
* surfaces on the host page.
|
|
19
|
+
*/
|
|
20
|
+
import type { FixbackInstance, InitOptions } from "./init";
|
|
21
|
+
/** A running SDK that mounted nothing — every failure path resolves to this. */
|
|
22
|
+
export declare const NOOP_INSTANCE: FixbackInstance;
|
|
23
|
+
/**
|
|
24
|
+
* Run one session: recognise the Reporter, boot, and mount when accepted — exchanging a
|
|
25
|
+
* `?fixback=` return code first, and exposing Connect (`signIn` / `signOut` / `identity`).
|
|
26
|
+
*/
|
|
27
|
+
export declare function runSession(options: InitOptions, apiUrl: string): Promise<FixbackInstance>;
|
package/dist/submit.d.ts
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The multipart feedback submission — the transport half of the capture loop.
|
|
3
|
+
*
|
|
4
|
+
* `POST /api/ingest/feedback` (spec MVP §C; tickets #50/#53) is a multipart
|
|
5
|
+
* request: a single text part `payload` carrying the JSON (publishable key +
|
|
6
|
+
* identity evidence + content) and an optional binary `replay` part — the buffered
|
|
7
|
+
* rrweb window (ADR-0024), from which the annotated still is reconstructed downstream
|
|
8
|
+
* (ADR-0027), so the browser SDK rasterises and sends no screenshot. The content-type
|
|
9
|
+
* header is deliberately not set, so the runtime writes the `multipart/form-data`
|
|
10
|
+
* boundary itself. Like `requestBoot`, this never throws: an unreachable Fixback or a
|
|
11
|
+
* refusal is a quiet result, so a Fixback problem never breaks the host page.
|
|
12
|
+
*
|
|
13
|
+
* The endpoint, the identity compaction, and the backpressure window are the shared
|
|
14
|
+
* core's (ADR-0028); what lives here is the browser envelope — a `FormData` with a
|
|
15
|
+
* `Blob` replay part.
|
|
16
|
+
*
|
|
17
|
+
* The `payload` carries the full report content — the structured `annotation`
|
|
18
|
+
* (element + region + marks, kept vector), the masked `trace` buffer, and the
|
|
19
|
+
* `source` provenance (spec §D). Automatic reports (`source: error`) additionally
|
|
20
|
+
* honour ingest's `429` + `Retry-After` backpressure: while the hold window is open
|
|
21
|
+
* they are dropped without touching the network. Manual reports are never gated
|
|
22
|
+
* (spec §E/§H).
|
|
23
|
+
*/
|
|
24
|
+
import { AutoReportBackoff, feedbackEndpoint, type FetchLike, type IdentityInputs, type ReportContent, type ReporterTier } from "@fixback/sdk-core";
|
|
25
|
+
export { feedbackEndpoint };
|
|
26
|
+
/**
|
|
27
|
+
* How long a submission may stay in flight before it is reported as
|
|
28
|
+
* `unreachable`. A hung connection — a captive portal, a proxy that never
|
|
29
|
+
* answers — would otherwise leave a report pending forever, and with it the
|
|
30
|
+
* overlay's "Sending…" state. The abandoned request keeps running in the
|
|
31
|
+
* background harmlessly. Matches `@fixback/expo`'s and `@fixback/node`'s window.
|
|
32
|
+
*/
|
|
33
|
+
export declare const DEFAULT_SUBMIT_TIMEOUT_MS = 30000;
|
|
4
34
|
/** What ingest returns for an accepted submission (vendored server shape). */
|
|
5
35
|
export interface RecordedFeedback {
|
|
6
36
|
readonly feedbackId: string;
|
|
@@ -8,11 +38,6 @@ export interface RecordedFeedback {
|
|
|
8
38
|
readonly reporterId: string;
|
|
9
39
|
readonly tier: ReporterTier;
|
|
10
40
|
}
|
|
11
|
-
/** The captured screenshot to attach, if any. */
|
|
12
|
-
export interface SubmitScreenshot {
|
|
13
|
-
readonly blob: Blob;
|
|
14
|
-
readonly filename?: string;
|
|
15
|
-
}
|
|
16
41
|
/**
|
|
17
42
|
* The buffered replay window to attach, if any — the rrweb events a
|
|
18
43
|
* `ReplaySnapshot` holds (replay.ts). Serialized into its own binary part.
|
|
@@ -25,7 +50,6 @@ export interface SubmitInput {
|
|
|
25
50
|
readonly key: string;
|
|
26
51
|
readonly identity?: IdentityInputs;
|
|
27
52
|
readonly content: ReportContent;
|
|
28
|
-
readonly screenshot?: SubmitScreenshot | null;
|
|
29
53
|
readonly replay?: SubmitReplay | null;
|
|
30
54
|
}
|
|
31
55
|
/** The outcome of a submission — never an exception. */
|
|
@@ -38,7 +62,7 @@ export type SubmitResult = {
|
|
|
38
62
|
readonly status?: number;
|
|
39
63
|
} | {
|
|
40
64
|
/**
|
|
41
|
-
* A `source:
|
|
65
|
+
* A `source: error` report shed under ingest backpressure (spec §E/§H) — either
|
|
42
66
|
* held locally because the window is still open, or answered `429` by ingest,
|
|
43
67
|
* which opened/extended the window. Carries the seconds left to wait. Manual
|
|
44
68
|
* reports never produce this.
|
|
@@ -47,19 +71,32 @@ export type SubmitResult = {
|
|
|
47
71
|
readonly reason: "backpressure";
|
|
48
72
|
readonly retryAfterSeconds: number;
|
|
49
73
|
};
|
|
50
|
-
/**
|
|
51
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Injectable transport collaborators, defaulted to the real implementations — the
|
|
76
|
+
* same deps-object shape `requestBoot` and `@fixback/expo`'s `submitReport` take.
|
|
77
|
+
*/
|
|
78
|
+
export interface SubmitDeps {
|
|
79
|
+
/** The `fetch` to call. Defaults to the page's global. */
|
|
80
|
+
readonly fetch?: FetchLike;
|
|
81
|
+
/**
|
|
82
|
+
* The shared `source: error` backpressure window. Defaults to the page-wide
|
|
83
|
+
* instance; tests inject their own to isolate the clock.
|
|
84
|
+
*/
|
|
85
|
+
readonly backoff?: AutoReportBackoff;
|
|
86
|
+
/** Overall request timeout in ms; `0` disables. Defaults to {@link DEFAULT_SUBMIT_TIMEOUT_MS}. */
|
|
87
|
+
readonly timeoutMs?: number;
|
|
88
|
+
}
|
|
52
89
|
/**
|
|
53
90
|
* Submit a report to ingest. Assembles the `payload` JSON (key + identity +
|
|
54
|
-
* content, with an explicit `source` stamped) and the optional
|
|
55
|
-
*
|
|
56
|
-
*
|
|
91
|
+
* content, with an explicit `source` stamped) and the optional `replay` window
|
|
92
|
+
* into a `FormData`, posts it, and resolves to the recorded Feedback on success
|
|
93
|
+
* or a named failure otherwise.
|
|
57
94
|
*
|
|
58
|
-
* A `source:
|
|
95
|
+
* A `source: error` report first consults the shared backpressure window: while it
|
|
59
96
|
* is open the report is dropped without a request (spec §E/§H). Ingest's `429` +
|
|
60
97
|
* `Retry-After` opens/extends that window (default 60 s if the header is absent).
|
|
61
98
|
* Manual reports (`source: reporter`, the default) never consult the window and a
|
|
62
|
-
* non-2xx for them stays a plain refusal. The `
|
|
63
|
-
*
|
|
99
|
+
* non-2xx for them stays a plain refusal. The `deps` seams exist purely so the call
|
|
100
|
+
* is testable.
|
|
64
101
|
*/
|
|
65
|
-
export declare function submitReport(apiUrl: string, input: SubmitInput,
|
|
102
|
+
export declare function submitReport(apiUrl: string, input: SubmitInput, deps?: SubmitDeps): Promise<SubmitResult>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `navigator.sendBeacon` network hook (spec #122 §D, ticket #139).
|
|
3
|
+
*
|
|
4
|
+
* `sendBeacon` is fire-and-forget with a **synchronous boolean** return — `true`
|
|
5
|
+
* when the user agent queued the beacon, `false` when it declined — so wrapping
|
|
6
|
+
* must return that boolean unchanged: the original is called first and its result
|
|
7
|
+
* both classifies the crumb (`ok` / `network-error`) and is returned to the caller.
|
|
8
|
+
* Only the URL and the trivially-known request size are recorded (no response
|
|
9
|
+
* exists to read); capture failure is swallowed and the boolean still returned, and
|
|
10
|
+
* if the original itself throws we record best-effort and re-throw so the contract
|
|
11
|
+
* is preserved exactly.
|
|
12
|
+
*/
|
|
13
|
+
import { type BreadcrumbBuffer, type Teardown } from "@fixback/sdk-core";
|
|
14
|
+
import { type InstrumentWindow } from "./window";
|
|
15
|
+
/** Wrap `navigator.sendBeacon` to record a network crumb per beacon. See the module doc. */
|
|
16
|
+
export declare function instrumentBeacon(buffer: BreadcrumbBuffer, win: InstrumentWindow, ignoreUrl: (url: string) => boolean, now: () => number): Teardown;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser's binding for the shared **console** wrapper.
|
|
3
|
+
*
|
|
4
|
+
* `instrumentConsole` itself is `@fixback/sdk-core`'s (ADR-0028) — the browser and
|
|
5
|
+
* React Native wrap `console` identically, and the wrapper takes its target as an
|
|
6
|
+
* argument rather than reading a global. This module exists so `./trace/instrument`
|
|
7
|
+
* names every stream it installs, and so the page's `console` is resolved in one
|
|
8
|
+
* place.
|
|
9
|
+
*/
|
|
10
|
+
export { instrumentConsole, type ConsoleLike } from "@fixback/sdk-core";
|
|
11
|
+
export { globalConsole } from "./window";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `fetch` network hook (spec #122 §D, ticket #139).
|
|
3
|
+
*
|
|
4
|
+
* Wraps the page's `fetch` to record a rich network crumb on settlement: method,
|
|
5
|
+
* scrubbed URL, status + statusText, a `performance.now()` duration, request size
|
|
6
|
+
* (only when the body is trivially sized), response size (from the
|
|
7
|
+
* `content-length` header only), content type, and a failure outcome. The
|
|
8
|
+
* **original** outcome is returned untouched — the response is passed through
|
|
9
|
+
* **without its body being read** (only two metadata headers are inspected), and a
|
|
10
|
+
* rejection is re-thrown so the caller's `unhandledrejection` semantics are
|
|
11
|
+
* preserved.
|
|
12
|
+
*/
|
|
13
|
+
import { type BreadcrumbBuffer, type Teardown } from "@fixback/sdk-core";
|
|
14
|
+
import { type InstrumentWindow } from "./window";
|
|
15
|
+
/** Wrap `fetch` to record a rich network crumb on settlement. See the module doc. */
|
|
16
|
+
export declare function instrumentFetch(buffer: BreadcrumbBuffer, win: InstrumentWindow, ignoreUrl: (url: string) => boolean, now: () => number, mono?: () => number): Teardown;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser SDK's **capture instrumentation** — one hook per stream, plus the
|
|
3
|
+
* composite that installs them all (spec 0003 §C; spec #122 §A/§D).
|
|
4
|
+
*
|
|
5
|
+
* Each hook lives in its own module and takes exactly the surface it patches, so
|
|
6
|
+
* none of them needs a whole `Window`: `console` (the shared core's wrapper),
|
|
7
|
+
* `fetch`, `XMLHttpRequest`, `sendBeacon`, `history` + hash navigation, and the
|
|
8
|
+
* masked `click`/`input` listeners.
|
|
9
|
+
*
|
|
10
|
+
* Everything private is kept out **at the source**: `ui.input` records that an
|
|
11
|
+
* input changed, never its value; network crumbs carry method + URL + status only,
|
|
12
|
+
* **never** bodies; URLs are scrubbed as the crumb is built. The `beforeSend` choke
|
|
13
|
+
* point is the final gate over the whole report.
|
|
14
|
+
*
|
|
15
|
+
* The SDK stays dependency-free and must never throw into the host page, so every
|
|
16
|
+
* hook is wrapped: a capture failure is swallowed and the original behaviour (the
|
|
17
|
+
* real `console`, `fetch`, navigation) always runs.
|
|
18
|
+
*/
|
|
19
|
+
import { type BreadcrumbBuffer, type BreadcrumbLevel, type ConsoleLike, type Teardown } from "@fixback/sdk-core";
|
|
20
|
+
import { type InstrumentWindow } from "./window";
|
|
21
|
+
export { instrumentBeacon } from "./beacon";
|
|
22
|
+
export { instrumentConsole } from "./console";
|
|
23
|
+
export { instrumentFetch } from "./fetch";
|
|
24
|
+
export { instrumentNavigation } from "./navigation";
|
|
25
|
+
export { clickCrumb, inputCrumb, instrumentUiEvents } from "./ui";
|
|
26
|
+
export { instrumentXhr } from "./xhr";
|
|
27
|
+
export { type BeaconNavigator, type FetchFn, globalConsole, globalDocument, globalWindow, type HistoryLike, type InstrumentWindow, type XhrConstructor, type XhrInstance, } from "./window";
|
|
28
|
+
/** Options for {@link instrumentBreadcrumbs} and the individual installers. */
|
|
29
|
+
export interface InstrumentOptions {
|
|
30
|
+
/** The window to instrument. Defaults to the page's, when there is one. */
|
|
31
|
+
readonly win?: InstrumentWindow;
|
|
32
|
+
/** The document whose masked UI events are listened for. Defaults to the page's. */
|
|
33
|
+
readonly doc?: Document;
|
|
34
|
+
/** The console to wrap. Defaults to the page's. */
|
|
35
|
+
readonly consoleObj?: ConsoleLike;
|
|
36
|
+
readonly consoleLevels?: readonly BreadcrumbLevel[];
|
|
37
|
+
/** Skip URLs (e.g. the SDK's own ingest calls) so they never become crumbs. */
|
|
38
|
+
readonly ignoreUrl?: (url: string) => boolean;
|
|
39
|
+
readonly now?: () => number;
|
|
40
|
+
/**
|
|
41
|
+
* High-res monotonic clock (spec #122 §D) — used to measure a request's duration as
|
|
42
|
+
* a delta around the wrapped call. Defaults to `performance.now`; injectable for tests.
|
|
43
|
+
*/
|
|
44
|
+
readonly mono?: () => number;
|
|
45
|
+
/**
|
|
46
|
+
* Whether to instrument the **console** stream (spec #122 §L; ticket #138).
|
|
47
|
+
* Defaults to `true`; pass `false` and `console` is never wrapped, so no console
|
|
48
|
+
* entry is ever recorded — the per-project toggle stops the stream at its source.
|
|
49
|
+
*/
|
|
50
|
+
readonly captureConsole?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Whether to instrument the **network** stream — `fetch`, `XHR`, and `sendBeacon`
|
|
53
|
+
* (spec #122 §L; ticket #138). Defaults to `true`; pass `false` and none is
|
|
54
|
+
* wrapped, so no network entry is ever recorded. Navigation and masked UI events
|
|
55
|
+
* are unaffected.
|
|
56
|
+
*/
|
|
57
|
+
readonly captureNetwork?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Install every capture hook onto a window/document and return a single teardown
|
|
61
|
+
* that removes them all. Each hook is independent and defensive: a failure in one
|
|
62
|
+
* never blocks the others, and none can throw into the host page.
|
|
63
|
+
*/
|
|
64
|
+
export declare function instrumentBreadcrumbs(buffer: BreadcrumbBuffer, options?: InstrumentOptions): Teardown;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The navigation hook (spec #122 §A): a `navigation` crumb on every SPA route
|
|
3
|
+
* change — `pushState`, `replaceState`, `popstate`, and `hashchange`.
|
|
4
|
+
*
|
|
5
|
+
* Both `history` methods are patched rather than listened for, because neither
|
|
6
|
+
* fires an event of its own; the original is always called first and its result
|
|
7
|
+
* returned, so the host page's routing is untouched.
|
|
8
|
+
*/
|
|
9
|
+
import { type BreadcrumbBuffer, type Teardown } from "@fixback/sdk-core";
|
|
10
|
+
import { type InstrumentWindow } from "./window";
|
|
11
|
+
/** Record a `navigation` crumb on `pushState`/`replaceState`/pop/hash changes. */
|
|
12
|
+
export declare function instrumentNavigation(buffer: BreadcrumbBuffer, win: InstrumentWindow, now: () => number): Teardown;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The masked **user-action** hook (spec #122 §A) and its two crumb builders.
|
|
3
|
+
*
|
|
4
|
+
* These are the browser-only crumbs: they need an `Element` to derive a masked CSS
|
|
5
|
+
* selector from, which is why they stay here rather than in the shared core with
|
|
6
|
+
* the rest of the builders. The masking is at the source and structural — a
|
|
7
|
+
* `ui.input` crumb records **that** a field changed and which one, and the shape
|
|
8
|
+
* has no slot for a value, so the typed text can never be recorded.
|
|
9
|
+
*
|
|
10
|
+
* Listeners are capture-phase and passive observers: they never call
|
|
11
|
+
* `preventDefault` or `stopPropagation`, and the SDK's own chrome (launcher,
|
|
12
|
+
* overlay, and anything inside their shadow roots) is skipped.
|
|
13
|
+
*/
|
|
14
|
+
import type { Breadcrumb, BreadcrumbBuffer, Teardown } from "@fixback/sdk-core";
|
|
15
|
+
/** A `ui.click` crumb: a masked target selector only — no text or value. */
|
|
16
|
+
export declare function clickCrumb(target: Element, timestamp: number): Breadcrumb;
|
|
17
|
+
/**
|
|
18
|
+
* A `ui.input` crumb: records **that** an input changed and which field, never
|
|
19
|
+
* the value typed into it. The `target` element's `.value` is never read.
|
|
20
|
+
*/
|
|
21
|
+
export declare function inputCrumb(target: Element, timestamp: number): Breadcrumb;
|
|
22
|
+
/** Listen (capture-phase) for clicks and input changes as masked crumbs. */
|
|
23
|
+
export declare function instrumentUiEvents(buffer: BreadcrumbBuffer, doc: Document, now: () => number): Teardown;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The **structural browser surfaces** the trace instrumentation reaches into, and
|
|
3
|
+
* the resolvers that find them on the page.
|
|
4
|
+
*
|
|
5
|
+
* Everything the hooks touch is described as a narrow interface rather than taken
|
|
6
|
+
* from the DOM lib wholesale, so a test can build a plain object (`fakeWindow()`)
|
|
7
|
+
* instead of a whole `Window`. The shapes are deliberately written so the **real**
|
|
8
|
+
* `window` and `console` satisfy them by assignment — no `as unknown as` escape
|
|
9
|
+
* anywhere, in the SDK or in its tests.
|
|
10
|
+
*/
|
|
11
|
+
import type { ConsoleLike } from "@fixback/sdk-core";
|
|
12
|
+
/** The `fetch` signature the network hook wraps. */
|
|
13
|
+
export type FetchFn = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
14
|
+
/** The `history` slice the navigation hook patches. */
|
|
15
|
+
export interface HistoryLike {
|
|
16
|
+
pushState(data: unknown, unused: string, url?: string | URL | null): void;
|
|
17
|
+
replaceState(data: unknown, unused: string, url?: string | URL | null): void;
|
|
18
|
+
}
|
|
19
|
+
/** The XHR instance surface the network hook touches. */
|
|
20
|
+
export interface XhrInstance {
|
|
21
|
+
status: number;
|
|
22
|
+
statusText?: string;
|
|
23
|
+
open(method: string, url: string | URL, ...rest: unknown[]): void;
|
|
24
|
+
send(body?: unknown): void;
|
|
25
|
+
getResponseHeader?(name: string): string | null;
|
|
26
|
+
addEventListener(type: string, listener: () => void): void;
|
|
27
|
+
removeEventListener(type: string, listener: () => void): void;
|
|
28
|
+
}
|
|
29
|
+
/** The XHR constructor whose prototype the network hook patches. */
|
|
30
|
+
export interface XhrConstructor {
|
|
31
|
+
new (): XhrInstance;
|
|
32
|
+
prototype: XhrInstance;
|
|
33
|
+
}
|
|
34
|
+
/** The `navigator.sendBeacon` surface the beacon hook wraps. */
|
|
35
|
+
export interface BeaconNavigator {
|
|
36
|
+
sendBeacon?: (url: string | URL, data?: BodyInit | null) => boolean;
|
|
37
|
+
}
|
|
38
|
+
/** The structural window surface the instrumentation reaches into. */
|
|
39
|
+
export interface InstrumentWindow {
|
|
40
|
+
fetch?: FetchFn;
|
|
41
|
+
history?: HistoryLike;
|
|
42
|
+
location?: {
|
|
43
|
+
href: string;
|
|
44
|
+
};
|
|
45
|
+
XMLHttpRequest?: XhrConstructor;
|
|
46
|
+
navigator?: BeaconNavigator;
|
|
47
|
+
addEventListener(type: string, listener: (event: Event) => void, options?: boolean | AddEventListenerOptions): void;
|
|
48
|
+
removeEventListener(type: string, listener: (event: Event) => void, options?: boolean | EventListenerOptions): void;
|
|
49
|
+
}
|
|
50
|
+
/** An installed hook's detacher — nothing installed is the no-op below. */
|
|
51
|
+
export declare function noop(): void;
|
|
52
|
+
/**
|
|
53
|
+
* The page's `window`, when there is one (a worker or an SSR pass has none).
|
|
54
|
+
* `Window` satisfies {@link InstrumentWindow} structurally, so this is a plain
|
|
55
|
+
* assignment rather than a cast.
|
|
56
|
+
*/
|
|
57
|
+
export declare function globalWindow(): InstrumentWindow | undefined;
|
|
58
|
+
/** The page's `document`, when there is one. */
|
|
59
|
+
export declare function globalDocument(): Document | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* The page's `console`, when there is one. A real `Console` satisfies
|
|
62
|
+
* {@link ConsoleLike} (its optional-method shape is bivariant in its parameters,
|
|
63
|
+
* which is what makes `console.assert`'s overloads fit), so this too is an
|
|
64
|
+
* assignment, not a cast.
|
|
65
|
+
*/
|
|
66
|
+
export declare function globalConsole(): ConsoleLike | undefined;
|