@fixback/sdk 0.5.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.
Files changed (41) hide show
  1. package/README.md +12 -2
  2. package/dist/capture/extract-dom.d.ts +33 -0
  3. package/dist/connect.d.ts +47 -0
  4. package/dist/dom.d.ts +7 -19
  5. package/dist/element-picker.d.ts +1 -1
  6. package/dist/error-capture.d.ts +21 -64
  7. package/dist/fixback.umd.js +36 -202
  8. package/dist/fixback.umd.js.map +1 -1
  9. package/dist/identity.d.ts +6 -1
  10. package/dist/index.d.ts +17 -15
  11. package/dist/index.mjs +3577 -3683
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/init.d.ts +62 -96
  14. package/dist/mount.d.ts +46 -0
  15. package/dist/overlay/dom.d.ts +16 -0
  16. package/dist/overlay/icons.d.ts +23 -0
  17. package/dist/overlay/marking.d.ts +64 -0
  18. package/dist/overlay/send.d.ts +65 -0
  19. package/dist/overlay/view.d.ts +78 -0
  20. package/dist/overlay-styles.d.ts +1 -1
  21. package/dist/overlay.d.ts +40 -41
  22. package/dist/package.json +3 -0
  23. package/dist/report.d.ts +9 -13
  24. package/dist/reporter-session-store.d.ts +17 -0
  25. package/dist/session.d.ts +27 -0
  26. package/dist/submit.d.ts +51 -8
  27. package/dist/trace/instrument/beacon.d.ts +16 -0
  28. package/dist/trace/instrument/console.d.ts +11 -0
  29. package/dist/trace/instrument/fetch.d.ts +16 -0
  30. package/dist/trace/instrument/index.d.ts +64 -0
  31. package/dist/trace/instrument/navigation.d.ts +12 -0
  32. package/dist/trace/instrument/ui.d.ts +23 -0
  33. package/dist/trace/instrument/window.d.ts +66 -0
  34. package/dist/trace/instrument/xhr.d.ts +15 -0
  35. package/dist/version.d.ts +1 -1
  36. package/package.json +8 -5
  37. package/dist/boot.d.ts +0 -74
  38. package/dist/breadcrumbs.d.ts +0 -327
  39. package/dist/invite.d.ts +0 -104
  40. package/dist/onboarding-styles.d.ts +0 -8
  41. package/dist/onboarding.d.ts +0 -44
@@ -1,327 +0,0 @@
1
- /**
2
- * The thin trace **breadcrumb ring buffer** and its capture instrumentation
3
- * (spec 0003 §C; research `sentry-error-capture-findings.md` §7.5).
4
- *
5
- * A fixed-size FIFO buffer of the most recent activity — console, navigation,
6
- * network metadata, masked user actions, and the failing error — that rides on a
7
- * report as Evidence. It is trimmed exactly like Sentry (`crumbs.slice(-N)`),
8
- * defaults to a deliberately thin `N ≈ 30` because it ships on **every** payload,
9
- * and takes an optional age cap. A `beforeBreadcrumb(crumb) => crumb | null`
10
- * filter lets a Project mute or edit crumbs before they enter the buffer.
11
- *
12
- * Everything private is kept out **at the source**: `ui.input` records that an
13
- * input changed, never its value; `fetch`/`xhr` crumbs carry method + URL +
14
- * status only, **never** bodies; URLs are scrubbed as the crumb is built. The
15
- * `beforeSend` choke point (`scrub.ts`) is the final gate over the whole report.
16
- *
17
- * The SDK stays dependency-free and must never throw into the host page, so every
18
- * instrumentation hook is wrapped: a capture failure is swallowed and the original
19
- * behaviour (the real `console`, `fetch`, navigation) always runs.
20
- */
21
- import { type Breadcrumb, type BreadcrumbCategory, type BreadcrumbData, type BreadcrumbLevel, type ConsoleArg, type ConsoleArgType, type NetworkApi, type NetworkOutcome, type SourceLocation } from "@fixback/sdk-core";
22
- export type { Breadcrumb, BreadcrumbCategory, BreadcrumbData, BreadcrumbLevel, ConsoleArg, ConsoleArgType, NetworkApi, NetworkOutcome, SourceLocation, };
23
- /**
24
- * The three independent trace streams (spec #122 §A, decision D6). Each is its own
25
- * FIFO ring with its own size budget, so a chatty stream can't evict another's
26
- * lead-up. Every {@link BreadcrumbCategory} maps to exactly one stream via
27
- * {@link streamOf}.
28
- */
29
- export type TraceStream = "network" | "console" | "breadcrumbs";
30
- /**
31
- * Per-stream ring budgets — starting points from the grill (D6): network is the
32
- * chattiest, breadcrumbs the sparsest. Per-project tunable, never frozen.
33
- */
34
- export declare const DEFAULT_STREAM_BUDGETS: Readonly<Record<TraceStream, number>>;
35
- /**
36
- * The shared age cap (spec #122 §A): entries older than this are pruned from every
37
- * stream, whichever trims first. ~3 minutes and — unlike the thin buffer's
38
- * off-by-default cap — **on** by default, so an idle tab never ships stale context.
39
- */
40
- export declare const DEFAULT_MAX_AGE_MS: number;
41
- /**
42
- * Console levels captured by default (spec #122 §C, decision D7): **all** of them, so
43
- * the Console tab is a real console and not just an error log. Eviction priority (not
44
- * capture) is what keeps a chatty app's `log`/`info`/`debug` from burying the signal —
45
- * see {@link PINNED_CONSOLE_LEVELS}.
46
- */
47
- export declare const DEFAULT_CONSOLE_LEVELS: readonly BreadcrumbLevel[];
48
- /**
49
- * The console levels **pinned** against eviction (spec #122 §C, decision D7). When the
50
- * console stream overflows its budget, `warn`/`error`/`assert` are retained ahead of
51
- * `log`/`info`/`debug`, so an error is never evicted by a burst of chatter — the
52
- * low-priority levels fill the remainder and are dropped first.
53
- */
54
- export declare const PINNED_CONSOLE_LEVELS: readonly BreadcrumbLevel[];
55
- /**
56
- * The console levels for which `source` (`file:line`) is captured (ticket #159).
57
- *
58
- * Capturing a call site constructs a `new Error()` to read its stack on **every** console
59
- * call — a measurable hot-page cost when a chatty app logs in a tight loop (spec #122's
60
- * "hot-page performance budget" open question, handed onward from the grill). The
61
- * spec-sanctioned mitigation (decision D7) narrows source capture to the **levels that
62
- * matter for debugging** — the same `warn`/`error`/`assert` that are pinned against
63
- * eviction — so `log`/`info`/`debug` pay no per-call stack cost and simply omit `source`.
64
- * Full-fidelity `source` is preserved exactly where it earns its keep: on warnings,
65
- * errors, and failed assertions. Per-project tunability of this set is future work; today
66
- * it deliberately mirrors {@link PINNED_CONSOLE_LEVELS} so "what survives eviction" and
67
- * "what carries a call site" stay one idea.
68
- */
69
- export declare const SOURCE_CAPTURE_LEVELS: readonly BreadcrumbLevel[];
70
- /**
71
- * Which stream a crumb's category belongs to (spec #122 §A). Network APIs
72
- * (`fetch` / `xhr` / `beacon`) form the network stream, `console` its own, and
73
- * everything else (navigation, masked UI events, the failing error) breadcrumbs.
74
- */
75
- export declare function streamOf(category: string): TraceStream;
76
- /** Filters or edits each crumb before it enters the buffer; `null` drops it. */
77
- export type BeforeBreadcrumb = (crumb: Breadcrumb) => Breadcrumb | null;
78
- /** Configuration for {@link createBreadcrumbBuffer}. All values are optional. */
79
- export interface BreadcrumbBufferConfig {
80
- /**
81
- * Per-stream size budgets (oldest drop, independently per stream). Any stream
82
- * omitted falls back to {@link DEFAULT_STREAM_BUDGETS}.
83
- */
84
- readonly budgets?: Partial<Record<TraceStream, number>>;
85
- /**
86
- * Shared age cap in ms: entries older than this are dropped from every stream.
87
- * Defaults to {@link DEFAULT_MAX_AGE_MS} (on); pass `0` (or a non-positive value)
88
- * to disable age pruning.
89
- */
90
- readonly maxAgeMs?: number;
91
- /** A per-crumb filter (mute a category, edit, or drop by returning `null`). */
92
- readonly beforeBreadcrumb?: BeforeBreadcrumb | null;
93
- /** Epoch clock source, injectable for tests. Defaults to `Date.now`. */
94
- readonly now?: () => number;
95
- /** High-res monotonic clock, injectable for tests. Defaults to `performance.now`. */
96
- readonly mono?: () => number;
97
- /** Stable id generator, injectable for tests. Defaults to a per-buffer sequence. */
98
- readonly nextId?: () => string;
99
- }
100
- /** A live trace buffer. */
101
- export interface BreadcrumbBuffer {
102
- /** Record a crumb (subject to `beforeBreadcrumb`, id/mono stamping, size, and age trimming). */
103
- add(crumb: Breadcrumb): void;
104
- /** The current crumbs, merged across streams and ordered in time — a fresh array. */
105
- snapshot(): Breadcrumb[];
106
- /** Drop every crumb. */
107
- clear(): void;
108
- }
109
- /**
110
- * Create the per-stream trace buffer (spec #122 §A/§B). It holds **three
111
- * independent FIFO rings** — `network`, `console`, `breadcrumbs` — each trimmed to
112
- * its own budget so a chatty stream never evicts another's lead-up, plus a shared
113
- * age cap. On `add`, a crumb passes through `beforeBreadcrumb`, is stamped with a
114
- * stable `id` and a high-res `mono` timestamp (and an epoch `timestamp` if it has
115
- * none), routed to its stream, then that stream is pruned by age and trimmed to its
116
- * budget. `snapshot` prunes by age again, then merges the three streams into one
117
- * `mono`-ordered array for transport — so the wire keeps its single `trace` field.
118
- */
119
- export declare function createBreadcrumbBuffer(config?: BreadcrumbBufferConfig): BreadcrumbBuffer;
120
- /**
121
- * Longest scrubbed URL kept on a crumb (spec #122 §F): capped at assembly so an
122
- * over-long URL is truncated, never dropped and never allowed to bloat a report.
123
- */
124
- export declare const MAX_URL_LENGTH = 2048;
125
- /**
126
- * Structured console argument caps (spec #122 §C/§F), all applied **at assembly** so a
127
- * single console crumb can never bloat a report:
128
- * - {@link MAX_ARG_DEPTH} bounds how deep a `json` argument is cloned (deeper nodes
129
- * collapse to an `[Object]`/`[Array]` marker);
130
- * - {@link MAX_ARG_ITEMS} bounds how many keys/elements are kept at each level;
131
- * - {@link MAX_ARG_STRING_LENGTH} truncates a single over-long string value;
132
- * - {@link MAX_CONSOLE_ARGS_BYTES} bounds the serialized size of the whole args array
133
- * (trailing args are dropped to fit), kept well under ingest's per-entry byte cap.
134
- */
135
- export declare const MAX_ARG_DEPTH = 4;
136
- export declare const MAX_ARG_ITEMS = 100;
137
- export declare const MAX_ARG_STRING_LENGTH = 1024;
138
- export declare const MAX_CONSOLE_ARGS_BYTES = 4096;
139
- /** Classify one console argument into a type-tagged {@link ConsoleArg} (spec #122 §C). */
140
- export declare function toConsoleArg(value: unknown): ConsoleArg;
141
- /**
142
- * A `console` crumb from a captured call's level and arguments (spec #122 §C). The
143
- * `message` is the one-line preview (flattened, truncated); `args` preserves each
144
- * argument as a structured, type-tagged, size-capped value so the Console tab can
145
- * render objects/errors expandably; `source` is the best-effort `file:line`, attached
146
- * only when the stack yielded one. Args are omitted entirely for a no-argument call.
147
- */
148
- export declare function consoleCrumb(level: BreadcrumbLevel, args: readonly unknown[], timestamp: number, source?: SourceLocation): Breadcrumb;
149
- /** A `navigation` crumb; both URLs are scrubbed and length-capped as the crumb is built. */
150
- export declare function navigationCrumb(from: string, to: string, timestamp: number): Breadcrumb;
151
- /**
152
- * Classify an HTTP status into a {@link NetworkOutcome} (spec #122 §D). A missing or
153
- * `0` status is a `network-error` (a request that never got a response); otherwise
154
- * the 4xx/5xx classes flag failures and everything else is `ok`. Used both as the
155
- * fallback outcome and by the thin {@link fetchCrumb} / {@link xhrCrumb} builders.
156
- */
157
- export declare function outcomeFromStatus(status: number | undefined): NetworkOutcome;
158
- /**
159
- * The trivially-known byte size of a request body (spec #122 §D) — a string's UTF-8
160
- * length, a `Blob`'s `.size`, or an `ArrayBuffer`/typed-array `.byteLength`. Anything
161
- * that would require **reading** the body (a `ReadableStream`, `FormData`, a `Request`
162
- * with a stream body) returns `undefined` — the "never read the body" line holds.
163
- */
164
- export declare function trivialBodySize(body: unknown): number | undefined;
165
- /** Parse a `content-length` header into a non-negative byte count, or `undefined`. */
166
- export declare function parseContentLength(value: string | null | undefined): number | undefined;
167
- /** The media type from a `content-type` header (the part before any `;` parameters). */
168
- export declare function contentTypeOf(value: string | null | undefined): string | undefined;
169
- /** The rich metadata a network crumb records (spec #122 §D) — never a body or header. */
170
- export interface NetworkCrumbInput {
171
- readonly api: NetworkApi;
172
- readonly method: string;
173
- /** The raw request URL — scrubbed (query dropped, path PII redacted) as the crumb is built. */
174
- readonly url: string;
175
- readonly status?: number;
176
- readonly statusText?: string;
177
- readonly durationMs?: number;
178
- readonly reqSize?: number;
179
- readonly respSize?: number;
180
- readonly contentType?: string;
181
- readonly outcome: NetworkOutcome;
182
- }
183
- /**
184
- * A network crumb (`fetch` / `xhr` / `beacon`) with the rich, metadata-only fields
185
- * the Network tab renders (spec #122 §D, ticket #139): api, method, scrubbed URL,
186
- * status + statusText, duration, request/response sizes, content type, and a failure
187
- * outcome. The URL is scrubbed and length-capped at assembly. The shape has **no field
188
- * for a request/response body or an arbitrary header**, so neither can ever be
189
- * recorded; only a positive status, and fields that were actually supplied, ride along.
190
- */
191
- export declare function networkCrumb(input: NetworkCrumbInput, timestamp: number): Breadcrumb;
192
- /**
193
- * A thin `fetch` crumb from method + URL + status (outcome derived from the status).
194
- * The instrumentation uses {@link networkCrumb} directly to carry the full metadata;
195
- * this convenience builder seeds a network crumb from a status alone.
196
- */
197
- export declare function fetchCrumb(method: string, url: string, status: number | undefined, timestamp: number): Breadcrumb;
198
- /** A thin `xhr` crumb from method + URL + status (outcome derived from the status). */
199
- export declare function xhrCrumb(method: string, url: string, status: number | undefined, timestamp: number): Breadcrumb;
200
- /** A `ui.click` crumb: a masked target selector only — no text or value. */
201
- export declare function clickCrumb(target: Element, timestamp: number): Breadcrumb;
202
- /**
203
- * A `ui.input` crumb: records **that** an input changed and which field, never
204
- * the value typed into it. The `target` element's `.value` is never read.
205
- */
206
- export declare function inputCrumb(target: Element, timestamp: number): Breadcrumb;
207
- /**
208
- * An `error` crumb for the failing exception or rejection that ends the trace. When
209
- * `causedBy` is given (an auto-captured error), it rides on the crumb as the causal
210
- * pointer to the ids of the entries immediately preceding the throw (spec #122 §F).
211
- */
212
- export declare function errorCrumb(error: unknown, timestamp: number, causedBy?: readonly string[]): Breadcrumb;
213
- /** Detaches an installed instrumentation, restoring the original behaviour. */
214
- export type Teardown = () => void;
215
- type AnyFn = (...args: unknown[]) => unknown;
216
- type ConsoleLike = Partial<Record<BreadcrumbLevel, AnyFn>>;
217
- type FetchFn = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
218
- interface HistoryLike {
219
- pushState(data: unknown, unused: string, url?: string | URL | null): void;
220
- replaceState(data: unknown, unused: string, url?: string | URL | null): void;
221
- }
222
- interface XhrInstance {
223
- status: number;
224
- statusText?: string;
225
- open(method: string, url: string | URL, ...rest: unknown[]): void;
226
- send(body?: unknown): void;
227
- getResponseHeader?(name: string): string | null;
228
- addEventListener(type: string, listener: () => void): void;
229
- removeEventListener(type: string, listener: () => void): void;
230
- }
231
- interface XhrConstructor {
232
- new (): XhrInstance;
233
- prototype: XhrInstance;
234
- }
235
- /** The `navigator.sendBeacon` surface the beacon instrumentation wraps. */
236
- export interface BeaconNavigator {
237
- sendBeacon?: (url: string | URL, data?: BodyInit | null) => boolean;
238
- }
239
- /** The structural window surface the instrumentation reaches into. */
240
- export interface InstrumentWindow {
241
- fetch?: FetchFn;
242
- history?: HistoryLike;
243
- location?: {
244
- href: string;
245
- };
246
- XMLHttpRequest?: XhrConstructor;
247
- navigator?: BeaconNavigator;
248
- addEventListener(type: string, listener: (event: Event) => void, options?: boolean | AddEventListenerOptions): void;
249
- removeEventListener(type: string, listener: (event: Event) => void, options?: boolean | EventListenerOptions): void;
250
- }
251
- /** Options for {@link instrumentBreadcrumbs} and the individual installers. */
252
- export interface InstrumentOptions {
253
- readonly win?: InstrumentWindow;
254
- readonly doc?: Document;
255
- readonly consoleObj?: ConsoleLike;
256
- readonly consoleLevels?: readonly BreadcrumbLevel[];
257
- /** Skip URLs (e.g. the SDK's own ingest calls) so they never become crumbs. */
258
- readonly ignoreUrl?: (url: string) => boolean;
259
- readonly now?: () => number;
260
- /**
261
- * High-res monotonic clock (spec #122 §D) — used to measure a request's duration as
262
- * a delta around the wrapped call. Defaults to `performance.now`; injectable for tests.
263
- */
264
- readonly mono?: () => number;
265
- /**
266
- * Whether to instrument the **console** stream (spec #122 §L; ticket #138).
267
- * Defaults to `true`; pass `false` and `console` is never wrapped, so no console
268
- * entry is ever recorded — the per-project toggle stops the stream at its source.
269
- */
270
- readonly captureConsole?: boolean;
271
- /**
272
- * Whether to instrument the **network** stream — `fetch` and `XHR` (spec #122 §L;
273
- * ticket #138). Defaults to `true`; pass `false` and neither is wrapped, so no
274
- * network entry is ever recorded. Navigation and masked UI events are unaffected.
275
- */
276
- readonly captureNetwork?: boolean;
277
- }
278
- /**
279
- * The best-effort `file:line` a console call was made from (spec #122 §C). Parses the
280
- * frames of a stack, skipping `skipFrames` leading (SDK-internal) frames so the source
281
- * points at the host code that called `console.*`. Returns `undefined` when no frame
282
- * yields a usable location — the crumb then simply omits `source`.
283
- */
284
- export declare function sourceFromStack(stack: string | undefined, skipFrames?: number): SourceLocation | undefined;
285
- /** Wrap `console` methods so calls at the captured levels become crumbs. */
286
- export declare function instrumentConsole(buffer: BreadcrumbBuffer, consoleObj: ConsoleLike, levels: readonly BreadcrumbLevel[], now: () => number): Teardown;
287
- /**
288
- * Wrap `fetch` to record a rich network crumb on settlement (spec #122 §D, ticket
289
- * #139): method, scrubbed URL, status + statusText, a `performance.now()` duration,
290
- * request size (only when the body is trivially sized), response size (from the
291
- * `content-length` header only), content type, and a failure outcome. The **original**
292
- * outcome is returned untouched — the response is passed through **without its body
293
- * being read** (only two metadata headers are inspected), and a rejection is re-thrown
294
- * so the caller's `unhandledrejection` semantics are preserved.
295
- */
296
- export declare function instrumentFetch(buffer: BreadcrumbBuffer, win: InstrumentWindow, ignoreUrl: (url: string) => boolean, now: () => number, mono?: () => number): Teardown;
297
- /**
298
- * Patch `XMLHttpRequest` to record a rich network crumb when a request settles (spec
299
- * #122 §D, ticket #139): method, scrubbed URL, status + statusText, a `performance.now()`
300
- * duration, request size (only when the body is trivially sized), response size (from
301
- * the `content-length` header only), content type, and a failure outcome distinguished
302
- * by which terminal event fired (`error` / `timeout` / `abort` / `load`). The `send`
303
- * body argument is **never read for content** — only its trivially-known size — so a body
304
- * can never reach the buffer.
305
- */
306
- export declare function instrumentXhr(buffer: BreadcrumbBuffer, win: InstrumentWindow, ignoreUrl: (url: string) => boolean, now: () => number, mono?: () => number): Teardown;
307
- /**
308
- * Wrap `navigator.sendBeacon` to record a network crumb for each beacon (spec #122 §D,
309
- * ticket #139). `sendBeacon` is fire-and-forget with a **synchronous boolean** return —
310
- * `true` when the user agent queued the beacon, `false` when it declined — so wrapping
311
- * must return that boolean unchanged: the original is called first and its result both
312
- * classifies the crumb (`ok` / `network-error`) and is returned to the caller. Only the
313
- * URL and the trivially-known request size are recorded (no response exists to read);
314
- * capture failure is swallowed and the boolean still returned, and if the original itself
315
- * throws we record best-effort and re-throw so the contract is preserved exactly.
316
- */
317
- export declare function instrumentBeacon(buffer: BreadcrumbBuffer, win: InstrumentWindow, ignoreUrl: (url: string) => boolean, now: () => number): Teardown;
318
- /** Record a `navigation` crumb on `pushState`/`replaceState`/pop/hash changes. */
319
- export declare function instrumentNavigation(buffer: BreadcrumbBuffer, win: InstrumentWindow, now: () => number): Teardown;
320
- /** Listen (capture-phase) for clicks and input changes as masked crumbs. */
321
- export declare function instrumentUiEvents(buffer: BreadcrumbBuffer, doc: Document, now: () => number): Teardown;
322
- /**
323
- * Install every capture hook onto a window/document and return a single teardown
324
- * that removes them all. Each hook is independent and defensive: a failure in one
325
- * never blocks the others, and none can throw into the host page.
326
- */
327
- export declare function instrumentBreadcrumbs(buffer: BreadcrumbBuffer, options?: InstrumentOptions): Teardown;
package/dist/invite.d.ts DELETED
@@ -1,104 +0,0 @@
1
- /**
2
- * Invite redemption & reporter identity (spec §F).
3
- *
4
- * The zero-integration path for an invited tester: detect an invite token on the
5
- * page (`?fixback_invite=<token>`) or an explicit `redeem()` call, read the
6
- * Invite's public status, and — on confirmation — redeem it into a persisted
7
- * `reporterId` the SDK thereafter presents as an identity input.
8
- *
9
- * This module is the wire + storage half (pure enough to unit-test directly): the
10
- * two invite endpoints, the localStorage persistence scoped by publishable key,
11
- * and the URL token detection / one-time consumption. The onboarding modal is
12
- * `onboarding.ts`; `init` wires the two together.
13
- *
14
- * Like `boot.ts`, the wire types are a **vendored** slice of the server contract
15
- * (`apps/api/src/invites/invite-redemption.controller.ts`) — the SDK never imports
16
- * the private server package. Keep them in lock-step with that controller.
17
- */
18
- import type { ReporterTier } from "./boot";
19
- /** The Invite's shape. Mirrors the server's `InviteKind`. */
20
- export type InviteKind = "targeted" | "shared";
21
- /** The dead/unknown states a status or redeem read can resolve to. */
22
- export type InviteDeadStatus = "revoked" | "expired" | "exhausted" | "not_found";
23
- /**
24
- * The public status of an Invite (`GET /api/invites/:token`). A live (`pending`)
25
- * Invite reveals the minimal facts the onboarding modal needs — the Project, the
26
- * Invite's shape, and the **server-derived** tier the redemption would grant; a
27
- * dead or unknown token reveals only its state.
28
- */
29
- export type InviteStatusAnswer = {
30
- readonly status: "pending";
31
- readonly projectId: string;
32
- readonly kind: InviteKind;
33
- readonly tier: ReporterTier;
34
- } | {
35
- readonly status: InviteDeadStatus;
36
- };
37
- /**
38
- * The result of redeeming an Invite (`POST /api/invites/:token/redeem`): the
39
- * minted Reporter (its handle + server-derived tier + Project), or why it was
40
- * refused.
41
- */
42
- export type RedeemAnswer = {
43
- readonly status: "redeemed";
44
- readonly reporterId: string;
45
- readonly tier: ReporterTier;
46
- readonly projectId: string;
47
- } | {
48
- readonly status: InviteDeadStatus;
49
- };
50
- /**
51
- * The self-provided display fields captured in the onboarding modal. They ride
52
- * along as a Reporter's chosen name / email — **never** a trust signal (the tier
53
- * is always server-derived, spec §F).
54
- */
55
- export interface ReporterDisplay {
56
- readonly name?: string;
57
- readonly email?: string;
58
- }
59
- /** A persisted redeemed Reporter: the server handle plus the display fields. */
60
- export interface StoredReporter extends ReporterDisplay {
61
- readonly reporterId: string;
62
- }
63
- /** The URL query parameter that carries an invite token. */
64
- export declare const INVITE_QUERY_PARAM = "fixback_invite";
65
- /**
66
- * Read the invite token from a page URL's `?fixback_invite=` param. Returns the
67
- * token, or `null` when absent, empty, or the URL cannot be parsed — never throws.
68
- */
69
- export declare function readInviteToken(href: string): string | null;
70
- /**
71
- * Strip the invite token from the address bar (one-time consumption, spec §F) via
72
- * `history.replaceState`, so a reload or a shared link cannot re-trigger — or
73
- * re-consume — an already-redeemed Invite. Other params, the path, and the hash
74
- * are preserved. Best-effort: it never throws into the host page.
75
- */
76
- export declare function stripInviteToken(win: Window): void;
77
- /**
78
- * Persist a redeemed Reporter for `key`. The `reporterId` is the identity the SDK
79
- * presents on later boots; the display name / email ride along as chosen fields.
80
- * Best-effort — storage being unavailable (private mode) is never fatal.
81
- */
82
- export declare function persistReporter(key: string, reporter: StoredReporter, store?: Storage | null): void;
83
- /**
84
- * Read the redeemed Reporter persisted for `key`, or `null` when none is stored,
85
- * the record is malformed, or it carries no `reporterId`. Never throws.
86
- */
87
- export declare function readStoredReporter(key: string, store?: Storage | null): StoredReporter | null;
88
- /** Join an API base URL with the invite status path, tolerating a trailing slash. */
89
- export declare function inviteStatusEndpoint(apiUrl: string, token: string): string;
90
- /** Join an API base URL with the redeem path for a token. */
91
- export declare function redeemEndpoint(apiUrl: string, token: string): string;
92
- /**
93
- * Read an Invite's public status. Resolves to the answer, or `null` when Fixback
94
- * could not be reached or the body was not a recognised answer. The endpoint
95
- * answers 200 for a live or dead Invite and 404 (with a JSON body) for an unknown
96
- * token, so the body — not the HTTP status — is what the caller narrows on. Never
97
- * throws: an outage stays invisible to the host page.
98
- */
99
- export declare function fetchInviteStatus(apiUrl: string, token: string, fetchImpl?: typeof fetch): Promise<InviteStatusAnswer | null>;
100
- /**
101
- * Redeem an Invite by token, minting and returning a Reporter. Resolves to the
102
- * answer, or `null` on an unreachable API or an unrecognised body. Never throws.
103
- */
104
- export declare function redeemInviteToken(apiUrl: string, token: string, fetchImpl?: typeof fetch): Promise<RedeemAnswer | null>;
@@ -1,8 +0,0 @@
1
- /**
2
- * Styles for the invite onboarding modal (spec §F) — the Signal look of the
3
- * frozen Reporter prototype (`docs/design/Fixback Reporter.dc.html`), scoped to
4
- * the modal's own Shadow DOM so the host page is never touched and never touches
5
- * it. Tokens mirror `overlay-styles.ts` so launcher, overlay, and modal read as
6
- * one system.
7
- */
8
- export declare const ONBOARDING_STYLES = "\n:host {\n --fb-color-accent: #2f6fed;\n --fb-color-accent-hover: #245fd0;\n --fb-color-on-emphasis: #ffffff;\n --fb-color-ink: #0f1720;\n --fb-color-text: #1a2530;\n --fb-color-muted: #5a6875;\n --fb-color-faint: #9aa7b2;\n --fb-color-hint: #b7c1cb;\n --fb-color-border: #dce3ea;\n --fb-color-border-soft: #e6ebf0;\n --fb-color-surface: #ffffff;\n --fb-color-card: #f6f8fa;\n --fb-color-accent-bg: #eaf1fe;\n --fb-color-success: #2f9e5b;\n --fb-font-sans: \"IBM Plex Sans\", system-ui, -apple-system, \"Segoe UI\", Roboto,\n Helvetica, Arial, sans-serif;\n --fb-font-mono: \"IBM Plex Mono\", ui-monospace, \"SFMono-Regular\", Menlo, Consolas,\n monospace;\n all: initial;\n}\n\n*, *::before, *::after { box-sizing: border-box; }\n\n.fb-ob-backdrop {\n position: fixed;\n inset: 0;\n z-index: 2147483010;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(15, 23, 32, 0.55);\n backdrop-filter: blur(3px);\n font-family: var(--fb-font-sans);\n color: var(--fb-color-text);\n animation: fb-ob-fade 0.25s ease both;\n}\n\n.fb-ob-panel {\n width: 400px;\n max-width: 100%;\n background: var(--fb-color-surface);\n border-radius: 16px;\n box-shadow: 0 30px 70px rgba(15, 40, 70, 0.4);\n overflow: hidden;\n animation: fb-ob-pop 0.32s cubic-bezier(0.2, 0.8, 0.3, 1) both;\n}\n\n.fb-ob-head {\n padding: 22px 24px 0;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.fb-ob-mark {\n width: 22px;\n height: 22px;\n border-radius: 6px;\n background: var(--fb-color-accent);\n flex: none;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.fb-ob-mark::after {\n content: \"\";\n width: 7px;\n height: 7px;\n border-radius: 2px;\n background: #fff;\n}\n.fb-ob-brand { font-size: 15px; font-weight: 600; color: var(--fb-color-ink); }\n.fb-ob-chip {\n margin-left: auto;\n font-family: var(--fb-font-mono);\n font-size: 10px;\n color: #8a97a3;\n border: 1px solid var(--fb-color-border-soft);\n border-radius: 6px;\n padding: 2px 7px;\n}\n\n.fb-ob-body { padding: 16px 24px 8px; }\n.fb-ob-title {\n font-size: 19px;\n font-weight: 700;\n color: var(--fb-color-ink);\n letter-spacing: -0.01em;\n}\n.fb-ob-lede {\n font-size: 13.5px;\n color: var(--fb-color-muted);\n line-height: 1.5;\n margin: 7px 0 0;\n}\n.fb-ob-lede strong { color: var(--fb-color-text); }\n\n.fb-ob-card {\n margin: 16px 0;\n padding: 13px 14px;\n background: var(--fb-color-card);\n border: 1px solid var(--fb-color-border-soft);\n border-radius: 10px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.fb-ob-row { display: flex; align-items: center; gap: 10px; }\n.fb-ob-row + .fb-ob-row {\n border-top: 1px solid #eef2f6;\n padding-top: 10px;\n}\n.fb-ob-rowlabel {\n font-family: var(--fb-font-mono);\n font-size: 10px;\n color: var(--fb-color-faint);\n width: 70px;\n flex: none;\n}\n.fb-ob-site { font-size: 13px; color: var(--fb-color-text); font-weight: 500; }\n.fb-ob-tier {\n font-size: 11px;\n font-weight: 600;\n color: var(--fb-color-accent);\n background: var(--fb-color-accent-bg);\n padding: 3px 9px;\n border-radius: 6px;\n}\n.fb-ob-tiernote { font-size: 11.5px; color: #8a97a3; }\n\n.fb-ob-fieldlabel {\n display: block;\n font-family: var(--fb-font-mono);\n font-size: 10px;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n color: var(--fb-color-faint);\n margin-bottom: 6px;\n}\n.fb-ob-fields { display: flex; gap: 8px; }\n.fb-ob-input {\n height: 38px;\n padding: 0 12px;\n border: 1px solid var(--fb-color-border);\n border-radius: 9px;\n font-family: inherit;\n font-size: 13px;\n color: var(--fb-color-text);\n outline: none;\n min-width: 0;\n}\n.fb-ob-input:focus { border-color: var(--fb-color-accent); }\n.fb-ob-name { flex: 1; }\n.fb-ob-email { flex: 1.3; font-family: var(--fb-font-mono); font-size: 12.5px; color: var(--fb-color-muted); }\n\n.fb-ob-privacy {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n margin-top: 14px;\n font-size: 11.5px;\n color: #8a97a3;\n line-height: 1.5;\n}\n.fb-ob-privacy svg { flex: none; margin-top: 1px; }\n.fb-ob-privacy strong { color: var(--fb-color-muted); }\n\n.fb-ob-foot { padding: 16px 24px 22px; }\n.fb-ob-confirm {\n width: 100%;\n height: 44px;\n border: 0;\n border-radius: 11px;\n background: var(--fb-color-accent);\n color: var(--fb-color-on-emphasis);\n font-family: inherit;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n}\n.fb-ob-confirm:hover { background: var(--fb-color-accent-hover); }\n.fb-ob-confirm:disabled { opacity: 0.6; cursor: default; }\n\n@keyframes fb-ob-fade { from { opacity: 0; } to { opacity: 1; } }\n@keyframes fb-ob-pop {\n from { opacity: 0; transform: translateY(8px) scale(0.98); }\n to { opacity: 1; transform: none; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fb-ob-backdrop, .fb-ob-panel { animation: none; }\n}\n";
@@ -1,44 +0,0 @@
1
- /**
2
- * The invite onboarding modal (spec §F) — the SDK-rendered redemption screen an
3
- * invited tester sees when a page carries an invite token. It shows the site
4
- * they were invited to, the **server-derived** access tier the redemption grants,
5
- * a private-by-default note, and a "Continue as" name / email, then confirms.
6
- *
7
- * Built to the frozen Signal Reporter prototype
8
- * (`docs/design/Fixback Reporter.dc.html`). It mounts lazily inside its own open
9
- * Shadow DOM so the host page is fully isolated, and — like the launcher and
10
- * overlay — never throws into the host page. It renders only data the SDK
11
- * truthfully holds: the site's own origin and the server-derived tier. The name /
12
- * email are **self-provided display fields**, never a trust signal (§F).
13
- */
14
- import type { ReporterTier } from "./boot";
15
- import type { ReporterDisplay } from "./invite";
16
- /** Marks the modal's host element in the light DOM, so it is findable and unique. */
17
- export declare const ONBOARDING_ATTRIBUTE = "data-fixback-onboard";
18
- /** Configuration for {@link createOnboardingModal}. */
19
- export interface OnboardingConfig {
20
- /** The site the tester was invited to — its origin (`window.location.host`). */
21
- readonly origin: string;
22
- /** The **server-derived** tier the redemption grants (from the invite status). */
23
- readonly tier: ReporterTier;
24
- /** Prefill the Continue-as fields from a previously stored display identity. */
25
- readonly defaults?: ReporterDisplay;
26
- /** Called with the self-provided display fields when the tester confirms. */
27
- readonly onConfirm: (display: ReporterDisplay) => void;
28
- /** Where to mount the modal host. Defaults to `document.body`. */
29
- readonly target?: HTMLElement;
30
- /** The document to build in. Defaults to the target's owner document. */
31
- readonly doc?: Document;
32
- }
33
- /** A mounted onboarding modal. */
34
- export interface OnboardingModal {
35
- destroy(): void;
36
- readonly host: HTMLElement;
37
- }
38
- /**
39
- * Render the onboarding modal into `target` (default `document.body`) and return a
40
- * handle to remove it. Confirming reads the name / email, fires `onConfirm` once
41
- * (further clicks are ignored while the caller redeems), and leaves teardown to
42
- * the caller so it can strip the token and mount the launcher first.
43
- */
44
- export declare function createOnboardingModal(config: OnboardingConfig): OnboardingModal;