@sphoro/bugcatch-web-sdk 0.14.4 → 0.15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sphoro/bugcatch-web-sdk",
3
- "version": "0.14.4",
3
+ "version": "0.15.0",
4
4
  "description": "Drop-in browser SDK — report bugs with console, network and environment context, without the extension.",
5
5
  "type": "module",
6
6
  "main": "dist/bugcatch.es.js",
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Who is reporting, what they are looking at, and whether init() was given
3
+ * something that can work — shaped here, before anything is sent.
4
+ *
5
+ * Pure and import-free so the unit tests load it directly. Every function takes
6
+ * a `warn(message)` callback instead of calling console itself: the client
7
+ * decides whether (and how often) a warning is printed, and the tests collect
8
+ * them.
9
+ *
10
+ * WHY THE BUSINESS IDS HAVE NAMES. A lending app knows exactly which customer is
11
+ * signed in and which loan is on screen. Passed as `loanId`, `applicationId` or
12
+ * `customerNumber`, the API reads them as identifiers the app itself asserted
13
+ * (reference source `identify`), which is what lets bugcatch look the customer
14
+ * up in the lender's own system over MCP without asking the reporter — and lets
15
+ * that lookup count as a trusted join. Passed under any other name they are
16
+ * still sent, but only as metadata a person can read.
17
+ *
18
+ * WHERE THEY GO. The signed-in user's fields travel as `metadata.user`, which is
19
+ * where the API has always read `id` and where the ownership check reads `phone`
20
+ * and `email`. The current screen travels as `metadata.context`. Both shapes are
21
+ * mirrored by bugcatch-api/src/settings/reference-extract.ts identifyReferences.
22
+ */
23
+
24
+ /** Longest identifier the API keeps; anything longer is dropped there too. */
25
+ export const MAX_ID_CHARS = 120;
26
+ /** Longest free-form string value kept on user or context. */
27
+ export const MAX_TEXT_CHARS = 500;
28
+ /** Most extra (non-standard) keys kept on user or context. */
29
+ export const MAX_EXTRA_KEYS = 30;
30
+ /** Largest nested object or array kept, as JSON. */
31
+ const MAX_NESTED_JSON = 1000;
32
+
33
+ /**
34
+ * The identifier fields bugcatch understands, and the names each accepts.
35
+ * The first name is canonical; the rest are accepted and rewritten to it so the
36
+ * API sees one spelling.
37
+ */
38
+ export const ID_FIELDS = {
39
+ loanId: ['loanId', 'loanNumber'],
40
+ applicationId: ['applicationId'],
41
+ customerNumber: ['customerNumber', 'customerId'],
42
+ };
43
+
44
+ const CONTROL = /[\u0000-\u001f\u007f]/;
45
+
46
+ /** An identifier: a non-empty string or finite number, as a trimmed string, or null. */
47
+ export function idValue(raw) {
48
+ if (typeof raw === 'number') return Number.isFinite(raw) ? String(raw) : null;
49
+ if (typeof raw !== 'string') return null;
50
+ const v = raw.trim();
51
+ if (!v || v.length > MAX_ID_CHARS || CONTROL.test(v)) return null;
52
+ return v;
53
+ }
54
+
55
+ function describe(v) {
56
+ if (v === null) return 'null';
57
+ if (Array.isArray(v)) return 'an array';
58
+ return typeof v === 'object' ? 'an object' : `a ${typeof v}`;
59
+ }
60
+
61
+ /** Read one identifier field (under any of its names) off `src`, warning on a bad value. */
62
+ function readId(src, canonical, names, where, warn) {
63
+ for (const name of names) {
64
+ if (!(name in src) || src[name] === undefined || src[name] === null || src[name] === '') continue;
65
+ const v = idValue(src[name]);
66
+ if (v) return v;
67
+ const raw = src[name];
68
+ warn(typeof raw === 'string' && raw.trim().length > MAX_ID_CHARS
69
+ ? `${where}.${name} is longer than ${MAX_ID_CHARS} characters and was dropped — pass the identifier itself, not a sentence or a URL.`
70
+ : `${where}.${name} should be a string or number, got ${describe(raw)} — it was dropped.`);
71
+ }
72
+ return null;
73
+ }
74
+
75
+ /** An extra field worth keeping: a primitive, or a small plain object / array. */
76
+ function extraValue(v) {
77
+ if (v === null || typeof v === 'boolean') return { ok: true, value: v };
78
+ if (typeof v === 'number') return Number.isFinite(v) ? { ok: true, value: v } : { ok: false };
79
+ if (typeof v === 'string') return { ok: true, value: v.slice(0, MAX_TEXT_CHARS) };
80
+ if (typeof v === 'object') {
81
+ try {
82
+ const json = JSON.stringify(v);
83
+ if (json && json.length <= MAX_NESTED_JSON) return { ok: true, value: JSON.parse(json) };
84
+ } catch { /* cyclic or otherwise unserialisable */ }
85
+ }
86
+ return { ok: false };
87
+ }
88
+
89
+ const USER_TEXT = ['email', 'name', 'phone'];
90
+ const RESERVED_USER = new Set(['id', 'userId', ...USER_TEXT, ...Object.values(ID_FIELDS).flat()]);
91
+
92
+ /**
93
+ * identify()'s argument, as it will be sent under `metadata.user`.
94
+ *
95
+ * Returns null for null/undefined (sign-out) and for anything that is not a
96
+ * plain object, with a warning for the latter. Never throws.
97
+ *
98
+ * id string | number the customer's own account id in your system
99
+ * email string threads their reports; where "resolved" mail goes
100
+ * name string
101
+ * phone string | number used, with email, to confirm the reporter owns
102
+ * the record bugcatch looked up
103
+ * loanId string | number (alias loanNumber)
104
+ * applicationId string | number
105
+ * customerNumber string | number (alias customerId)
106
+ *
107
+ * Anything else is kept as readable metadata, within limits.
108
+ */
109
+ export function normalizeUser(user, warn = () => {}) {
110
+ if (user === null || user === undefined) return null;
111
+ if (typeof user !== 'object' || Array.isArray(user)) {
112
+ warn(`identify() takes an object like { id, email, name } or null, got ${describe(user)} — ignored.`);
113
+ return null;
114
+ }
115
+ const out = {};
116
+ try {
117
+ const id = readId(user, 'id', ['id', 'userId'], 'identify()', warn);
118
+ if (id) out.id = id;
119
+ for (const field of USER_TEXT) {
120
+ const raw = user[field];
121
+ if (raw === undefined || raw === null || raw === '') continue;
122
+ if (field === 'phone' && typeof raw === 'number' && Number.isFinite(raw)) { out.phone = String(raw); continue; }
123
+ if (typeof raw !== 'string') {
124
+ warn(`identify().${field} should be a string, got ${describe(raw)} — it was dropped.`);
125
+ continue;
126
+ }
127
+ const v = raw.trim().slice(0, field === 'name' ? 200 : MAX_ID_CHARS);
128
+ if (!v) continue;
129
+ if (field === 'email' && !v.includes('@')) {
130
+ warn(`identify().email "${v}" does not look like an email address — reports cannot be threaded or followed up without one.`);
131
+ }
132
+ out[field] = v;
133
+ }
134
+ for (const [canonical, names] of Object.entries(ID_FIELDS)) {
135
+ const v = readId(user, canonical, names, 'identify()', warn);
136
+ if (v) out[canonical] = v;
137
+ }
138
+ let extras = 0;
139
+ for (const [key, raw] of Object.entries(user)) {
140
+ if (RESERVED_USER.has(key) || raw === undefined || typeof raw === 'function') continue;
141
+ if (extras >= MAX_EXTRA_KEYS) { warn(`identify() was given more than ${MAX_EXTRA_KEYS} extra fields; the rest were dropped.`); break; }
142
+ const kept = extraValue(raw);
143
+ if (!kept.ok) { warn(`identify().${key} could not be sent (${describe(raw)} too large or not serialisable) — dropped.`); continue; }
144
+ out[key] = kept.value;
145
+ extras += 1;
146
+ }
147
+ } catch {
148
+ // A getter that throws, a proxy — whatever it was, the report goes out with
149
+ // what was read before it.
150
+ }
151
+ if (!out.id && !out.email) {
152
+ warn('identify() was called without an id or email — reports will not be attributed to anyone, and nothing can be looked up by user.');
153
+ }
154
+ return out;
155
+ }
156
+
157
+ /**
158
+ * setContext()'s argument merged over what was already set, as it will be sent
159
+ * under `metadata.context`.
160
+ *
161
+ * `null` for the patch clears everything; a key set to null/undefined removes
162
+ * that key. Identifier fields are validated exactly as identify()'s are and
163
+ * rewritten to their canonical names; everything else (screen, route, feature
164
+ * flag…) is kept within limits. Never throws.
165
+ */
166
+ export function mergeContext(prev, patch, warn = () => {}) {
167
+ if (patch === null) return {};
168
+ const out = { ...(prev || {}) };
169
+ if (patch === undefined) return out;
170
+ if (typeof patch !== 'object' || Array.isArray(patch)) {
171
+ warn(`setContext() takes an object like { loanId, screen } or null, got ${describe(patch)} — ignored.`);
172
+ return out;
173
+ }
174
+ try {
175
+ const aliases = new Map();
176
+ for (const [canonical, names] of Object.entries(ID_FIELDS)) for (const n of names) aliases.set(n, canonical);
177
+ for (const [key, raw] of Object.entries(patch)) {
178
+ const canonical = aliases.get(key);
179
+ if (raw === null || raw === undefined) { delete out[canonical || key]; continue; }
180
+ if (canonical) {
181
+ const v = readId({ [key]: raw }, canonical, [key], 'setContext()', warn);
182
+ if (v) out[canonical] = v; else delete out[canonical];
183
+ continue;
184
+ }
185
+ if (typeof raw === 'function') continue;
186
+ if (!(key in out) && Object.keys(out).length >= MAX_EXTRA_KEYS) {
187
+ warn(`setContext() holds at most ${MAX_EXTRA_KEYS} keys; "${key}" was dropped.`);
188
+ continue;
189
+ }
190
+ const kept = extraValue(raw);
191
+ if (!kept.ok) { warn(`setContext().${key} could not be sent (${describe(raw)} too large or not serialisable) — dropped.`); continue; }
192
+ out[key] = kept.value;
193
+ }
194
+ } catch { /* keep what was merged so far */ }
195
+ return out;
196
+ }
197
+
198
+ /** Every init() option this SDK reads. Anything else is probably a typo. */
199
+ export const KNOWN_OPTIONS = [
200
+ 'apiBase', 'apiKey', 'projectId', 'consoleLimit', 'networkLimit', 'captureConsole',
201
+ 'captureNetwork', 'captureErrors', 'widget', 'widgetPosition', 'theme', 'accent',
202
+ 'attachments', 'maxAttachments', 'maxAttachmentBytes', 'maxRecordingMs', 'beforeSend',
203
+ 'user', 'context', 'updates', 'updateToast', 'onUpdate', 'voice', 'onCallState',
204
+ 'onIncomingCall', 'onReportSubmitted', 'onAssistantPhase', 'onError', 'debug',
205
+ 'drafts', 'knownIssues', 'release', 'environment', 'buildId',
206
+ ];
207
+
208
+ const CALLBACK_OPTIONS = [
209
+ 'beforeSend', 'onUpdate', 'onCallState', 'onIncomingCall', 'onReportSubmitted',
210
+ 'onAssistantPhase', 'onError',
211
+ ];
212
+
213
+ /** The closest known option to a misspelt one, when it is close enough to suggest. */
214
+ function suggest(name) {
215
+ const lower = name.toLowerCase().replace(/[-_]/g, '');
216
+ return KNOWN_OPTIONS.find((k) => k.toLowerCase() === lower) || null;
217
+ }
218
+
219
+ const PLACEHOLDER = /^(your[_-]|<|xxx|todo|changeme)/i;
220
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
221
+
222
+ /**
223
+ * What is wrong with init()'s options, as two lists: `errors` stop init (it
224
+ * throws the first), `warnings` are printed and init carries on.
225
+ *
226
+ * `pageHost` is the page's hostname, passed in so this stays pure; it is what
227
+ * tells a production page still pointed at the localhost default apart from a
228
+ * developer's machine.
229
+ */
230
+ export function checkOptions(options, { pageHost = '' } = {}) {
231
+ const errors = [];
232
+ const warnings = [];
233
+ if (!options || typeof options !== 'object') {
234
+ errors.push('init() takes an options object: BugCatch.init({ apiBase, apiKey, projectId }).');
235
+ return { errors, warnings };
236
+ }
237
+ const { apiKey, projectId, apiBase } = options;
238
+
239
+ if (!apiKey) errors.push('apiKey is required — create one under Workspace Settings → API Keys.');
240
+ else if (typeof apiKey !== 'string') errors.push(`apiKey must be a string, got ${describe(apiKey)}.`);
241
+ else if (PLACEHOLDER.test(apiKey)) errors.push(`apiKey is still the placeholder "${apiKey}" — paste the key you saved when you created it.`);
242
+ else if (UUID.test(apiKey)) warnings.push('apiKey looks like a project id — did you swap apiKey and projectId?');
243
+ else if (!apiKey.startsWith('bc_')) warnings.push('apiKey does not look like a BugCatch key (they start with "bc_live_") — reports will be refused with HTTP 401.');
244
+
245
+ if (!projectId) errors.push('projectId is required — copy it from Project Settings → Web SDK.');
246
+ else if (typeof projectId !== 'string') errors.push(`projectId must be a string, got ${describe(projectId)}.`);
247
+ else if (PLACEHOLDER.test(projectId)) errors.push(`projectId is still the placeholder "${projectId}".`);
248
+ else if (projectId.startsWith('bc_')) warnings.push('projectId looks like an API key — did you swap apiKey and projectId?');
249
+ else if (/\s/.test(projectId)) warnings.push('projectId contains whitespace — check it was copied cleanly.');
250
+
251
+ if (apiBase === undefined || apiBase === null) {
252
+ const local = !pageHost || /^(localhost|127\.|0\.0\.0\.0|\[::1\])/.test(pageHost);
253
+ if (!local) warnings.push('apiBase is not set, so reports go to http://localhost:3000 — set apiBase to your BugCatch API URL.');
254
+ } else if (typeof apiBase !== 'string' || !/^https?:\/\//.test(apiBase)) {
255
+ warnings.push(`apiBase should be an absolute http(s) URL, got ${JSON.stringify(apiBase)}.`);
256
+ }
257
+
258
+ for (const key of Object.keys(options)) {
259
+ if (KNOWN_OPTIONS.includes(key)) continue;
260
+ const near = suggest(key);
261
+ warnings.push(near
262
+ ? `unknown option "${key}" — did you mean "${near}"?`
263
+ : `unknown option "${key}" is ignored.`);
264
+ }
265
+ for (const key of CALLBACK_OPTIONS) {
266
+ const v = options[key];
267
+ if (v !== undefined && v !== null && typeof v !== 'function') {
268
+ warnings.push(`${key} should be a function, got ${describe(v)} — it will not be called.`);
269
+ }
270
+ }
271
+ return { errors, warnings };
272
+ }
273
+
274
+ /** Drop a trailing slash so `${apiBase}/ingest/bugs` never has two. */
275
+ export function cleanApiBase(apiBase) {
276
+ return typeof apiBase === 'string' ? apiBase.replace(/\/+$/, '') : apiBase;
277
+ }
package/src/index.d.ts CHANGED
@@ -1,8 +1,73 @@
1
1
  /**
2
- * Who is using your app. `email` is the one field with behaviour attached —
3
- * see identify().
2
+ * Who is using your app, as identify() takes it.
3
+ *
4
+ * `id`, `email` and `phone` have behaviour attached; so do the three business
5
+ * identifiers, which bugcatch reads as identifiers *your app* asserted — trusted
6
+ * join keys it uses to look the customer up in your own system (over MCP)
7
+ * without asking the reporter. Anything else is kept as readable metadata.
8
+ */
9
+ export interface BugCatchUser {
10
+ /** The customer's own account id in your system — never a staff or agent id. */
11
+ id?: string | number;
12
+ /** Threads their reports together; where "your issue is resolved" mail goes. */
13
+ email?: string;
14
+ name?: string;
15
+ /**
16
+ * With `email`, what bugcatch compares against the record it looked up to
17
+ * confirm the reporter owns it. Include the country code.
18
+ */
19
+ phone?: string | number;
20
+ /** A loan this customer holds. Looked up as a loan. Alias: `loanNumber`. */
21
+ loanId?: string | number;
22
+ /** A loan application. Looked up as a loan. */
23
+ applicationId?: string | number;
24
+ /** Your customer / client number. Looked up as a customer. Alias: `customerId`. */
25
+ customerNumber?: string | number;
26
+ /** @deprecated alias of `customerNumber`. */
27
+ customerId?: string | number;
28
+ /** @deprecated alias of `loanId`. */
29
+ loanNumber?: string | number;
30
+ /** Extra fields: strings, numbers, booleans or small objects, sent as metadata. */
31
+ [extra: string]: unknown;
32
+ }
33
+
34
+ /**
35
+ * What the reporter is looking at, as setContext() takes it. The three
36
+ * identifier fields are read exactly as on identify() and outrank them; set one
37
+ * to null to remove it.
4
38
  */
5
- export interface BugCatchUser { id?: string; email?: string; name?: string; }
39
+ export interface BugCatchContext {
40
+ loanId?: string | number | null;
41
+ applicationId?: string | number | null;
42
+ customerNumber?: string | number | null;
43
+ /** Anything else — screen, route, step, a feature flag. Readable metadata only. */
44
+ [extra: string]: unknown;
45
+ }
46
+
47
+ /** Where the AI assistant working a report has got to. */
48
+ export type AssistantPhase =
49
+ | 'analyzing' | 'needs_info' | 'confirming' | 'reviewing' | 'resolving' | 'resolved';
50
+
51
+ export interface AssistantPhaseEvent {
52
+ bugId: string;
53
+ phase: AssistantPhase;
54
+ /** A short heading written for the reporter, e.g. "Root cause found". */
55
+ title: string | null;
56
+ detail: string | null;
57
+ }
58
+
59
+ export interface ReportSubmittedEvent {
60
+ bugId: string;
61
+ /** Null for a report filed by pressing Call — it is titled from the call. */
62
+ title: string | null;
63
+ via: 'form' | 'call';
64
+ result: ReportResult;
65
+ }
66
+
67
+ export interface BugCatchErrorEvent {
68
+ action: 'report' | 'call';
69
+ error: Error & { status?: number };
70
+ }
6
71
 
7
72
  /**
8
73
  * What a bug *report* may carry. Narrower than what may be uploaded: a report's
@@ -53,13 +118,39 @@ export interface JoinedIssue {
53
118
  }
54
119
 
55
120
  export interface BugCatchConfig {
121
+ /**
122
+ * Log what the SDK does — identify, context, each report's shape — to the
123
+ * console. Default false. Misconfiguration warnings print either way.
124
+ */
125
+ debug?: boolean;
126
+ /** Context for the first screen; the same as calling setContext() after init. */
127
+ context?: BugCatchContext | null;
128
+ /** A report was accepted (widget, report() or Call). Also the 'report' event. */
129
+ onReportSubmitted?: ((event: ReportSubmittedEvent) => void) | null;
130
+ /**
131
+ * The AI assistant working a report moved phase. Seen whenever the report is
132
+ * fetched: by the widget while its thread is open, or by myReport(). Also the
133
+ * 'assistant' event.
134
+ */
135
+ onAssistantPhase?: ((event: AssistantPhaseEvent) => void) | null;
136
+ /** A report or call the SDK filed failed. Also the 'error' event. */
137
+ onError?: ((event: BugCatchErrorEvent) => void) | null;
56
138
  /** Recover unfinished widget reports on this device for seven days. Default true. */
57
139
  drafts?: boolean;
140
+ /** Your app's version, attached to every report. */
58
141
  release?: string;
142
+ /** e.g. 'production' / 'staging', attached to every report. */
59
143
  environment?: string;
60
144
  buildId?: string;
145
+ /**
146
+ * Your BugCatch API URL, e.g. 'https://api.bugcatch.example'. Default
147
+ * 'http://localhost:3000' — set it anywhere but local development. A trailing
148
+ * slash is removed.
149
+ */
61
150
  apiBase?: string;
151
+ /** A workspace API key (bc_live_…), from Workspace Settings → API Keys. */
62
152
  apiKey: string;
153
+ /** The project reports land in, from Project Settings → Web SDK. */
63
154
  projectId: string;
64
155
  consoleLimit?: number;
65
156
  networkLimit?: number;
@@ -235,6 +326,11 @@ export interface ReportInput {
235
326
  * form and must ask their own version of that question.
236
327
  */
237
328
  references?: ReportReference[];
329
+ /**
330
+ * Context for this report only, merged over setContext()'s without changing
331
+ * it — e.g. the loan a "report a problem with this loan" button belongs to.
332
+ */
333
+ context?: BugCatchContext;
238
334
  }
239
335
 
240
336
  export interface ReportReference {
@@ -426,6 +522,17 @@ export interface ReportDetail {
426
522
  * whose send button would fail.
427
523
  */
428
524
  canReply: boolean;
525
+ /**
526
+ * Where the AI assistant is on this report, written for the reporter. Null
527
+ * when no assistant is working it. Fetching a report also fires
528
+ * onAssistantPhase / the 'assistant' event when the phase has changed.
529
+ */
530
+ assistant?: {
531
+ phase: AssistantPhase;
532
+ title: string;
533
+ detail: string;
534
+ steps: { label: string; state: string }[];
535
+ } | null;
429
536
  }
430
537
 
431
538
  /**
@@ -461,6 +568,12 @@ export declare class BugCatchClient {
461
568
  * Several listeners can coexist, unlike the single `onUpdate` config hook.
462
569
  */
463
570
  on(event: 'update', handler: (update: ReportUpdate) => void): () => void;
571
+ /** A report was accepted — from the widget, report(), or pressing Call. */
572
+ on(event: 'report', handler: (event: ReportSubmittedEvent) => void): () => void;
573
+ /** The AI assistant working a report moved phase (seen when the report is fetched). */
574
+ on(event: 'assistant', handler: (event: AssistantPhaseEvent) => void): () => void;
575
+ /** A report or call the SDK filed failed. */
576
+ on(event: 'error', handler: (event: BugCatchErrorEvent) => void): () => void;
464
577
  on(
465
578
  event: 'call',
466
579
  handler: (detail: {
@@ -495,7 +608,7 @@ export declare class BugCatchClient {
495
608
  on(event: 'signout', handler: (who: { email: string | null }) => void): () => void;
496
609
  off(event: 'update', handler: (update: ReportUpdate) => void): this;
497
610
  off(
498
- event: 'call' | 'unread' | 'incoming-call' | 'signin' | 'signout',
611
+ event: 'call' | 'unread' | 'incoming-call' | 'signin' | 'signout' | 'report' | 'assistant' | 'error',
499
612
  handler: (payload: any) => void,
500
613
  ): this;
501
614
  /**
@@ -630,8 +743,9 @@ export declare class BugCatchClient {
630
743
  * single slot, because there is a single microphone.
631
744
  */
632
745
  endCall(): this;
633
- /** One report with its full timeline, or null if this browser cannot prove it filed it. */
746
+ /** Tell the team whether their fix worked. STILL_HAPPENING re-sends this browser's context. */
634
747
  verifyFix(bugId: string, feedback: { result: 'CONFIRMED' | 'STILL_HAPPENING'; resolvedAt: string; note?: string }): Promise<{ verification: string; status: string }>;
748
+ /** One report with its full timeline, or null if this browser cannot prove it filed it. */
635
749
  myReport(bugId: string): Promise<ReportDetail | null>;
636
750
  /**
637
751
  * Write back on a report this browser filed. The widget's own conversation
@@ -664,6 +778,24 @@ export declare class BugCatchClient {
664
778
  * Pass null on sign-out.
665
779
  */
666
780
  identify(user: BugCatchUser | null): this;
781
+ /**
782
+ * Say what the reporter is looking at — the loan, application or customer on
783
+ * this screen. Merged over what was set before and kept for the life of the
784
+ * page; a key set to null is removed, and `setContext(null)` clears it all.
785
+ */
786
+ setContext(context: BugCatchContext | null): this;
787
+ /** A copy of what setContext() holds. */
788
+ getContext(): BugCatchContext;
789
+ /** Open the built-in report panel (needs `widget: true`). */
790
+ open(): this;
791
+ /** Close the report panel if it is open. */
792
+ close(): this;
793
+ isOpen(): boolean;
794
+ /**
795
+ * File a report with everything captured so far. Rejects with an Error
796
+ * (carrying `.status` for an HTTP refusal) and emits 'error'; resolves to
797
+ * `{ cancelled: true }` when beforeSend returned false.
798
+ */
667
799
  report(input: ReportInput): Promise<ReportResult | { cancelled: true }>;
668
800
  /** Uploads straight to storage and resolves the key a report references. */
669
801
  uploadAttachment(
@@ -703,7 +835,13 @@ export declare class BugCatchClient {
703
835
  * neither did. Rejects when there is none.
704
836
  */
705
837
  meToo(bugId: string, who?: { email?: string; name?: string }): Promise<JoinedIssue>;
706
- snapshot(): { console: any[]; network: any[]; environment: Record<string, any> };
838
+ snapshot(): {
839
+ user: BugCatchUser | null;
840
+ context: BugCatchContext;
841
+ console: any[];
842
+ network: any[];
843
+ environment: Record<string, any>;
844
+ };
707
845
  /**
708
846
  * Everything this browser has reported, newest first, with each one's
709
847
  * status. Resolves to an empty history when nothing has been filed here.
@@ -719,5 +857,5 @@ export declare class BugCatchClient {
719
857
  destroy(): void;
720
858
  }
721
859
 
722
- declare const BugCatch: BugCatchClient & { Client: typeof BugCatchClient };
860
+ declare const BugCatch: BugCatchClient & { Client: typeof BugCatchClient; version: string };
723
861
  export default BugCatch;