@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/README.md +390 -115
- package/dist/bugcatch.es.js +2514 -1722
- package/dist/bugcatch.es.js.map +1 -1
- package/dist/bugcatch.umd.js +781 -450
- package/dist/bugcatch.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/identity.js +277 -0
- package/src/index.d.ts +145 -7
- package/src/index.js +243 -28
- package/src/widget.js +980 -425
package/README.md
CHANGED
|
@@ -1,11 +1,38 @@
|
|
|
1
1
|
# @sphoro/bugcatch-web-sdk
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
have to ask for: console output, network
|
|
3
|
+
A browser SDK that lets your customers report a problem from inside your app, and
|
|
4
|
+
attaches what you'd otherwise have to ask them for: console output, network
|
|
5
|
+
activity, unhandled errors, the environment, and — when you tell it — who the
|
|
6
|
+
customer is and which loan, application or account they were looking at.
|
|
7
|
+
|
|
8
|
+
No extension, no dependencies. The widget renders in a shadow root, so its CSS and
|
|
9
|
+
yours never collide.
|
|
10
|
+
|
|
11
|
+
- [Quick start](#quick-start)
|
|
12
|
+
- [Tell BugCatch who the customer is](#tell-bugcatch-who-the-customer-is) — with loan / customer IDs
|
|
13
|
+
- [Context for the current screen](#context-for-the-current-screen)
|
|
14
|
+
- [A lending app, end to end](#a-lending-app-end-to-end)
|
|
15
|
+
- [Events](#events)
|
|
16
|
+
- [Reporting and opening from your own UI](#reporting-and-opening-from-your-own-ui)
|
|
17
|
+
- [The AI assistant](#the-ai-assistant)
|
|
18
|
+
- [Privacy: what is captured and what is not](#privacy-what-is-captured-and-what-is-not)
|
|
19
|
+
- [Options](#options)
|
|
20
|
+
- [API at a glance](#api-at-a-glance)
|
|
21
|
+
- [Troubleshooting](#troubleshooting)
|
|
22
|
+
- [Reference](#reference) — attachments, sign-in, live updates, replies, known issues, calls
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
You need two values from BugCatch:
|
|
27
|
+
|
|
28
|
+
| Value | Where to find it |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `apiKey` | Workspace Settings → API Keys (`bc_live_…`; shown once when created) |
|
|
31
|
+
| `projectId` | Project Settings → Web SDK (the snippet there is pre-filled) |
|
|
5
32
|
|
|
6
|
-
|
|
33
|
+
and the URL of your BugCatch API (`apiBase`).
|
|
7
34
|
|
|
8
|
-
|
|
35
|
+
**npm / bundler**
|
|
9
36
|
|
|
10
37
|
```bash
|
|
11
38
|
npm install @sphoro/bugcatch-web-sdk
|
|
@@ -15,55 +42,380 @@ npm install @sphoro/bugcatch-web-sdk
|
|
|
15
42
|
import BugCatch from '@sphoro/bugcatch-web-sdk';
|
|
16
43
|
|
|
17
44
|
BugCatch.init({
|
|
18
|
-
apiBase: 'https://api.yourdomain.com',
|
|
19
|
-
apiKey: 'bc_live_…',
|
|
20
|
-
projectId: '…',
|
|
45
|
+
apiBase: 'https://api.bugcatch.yourdomain.com',
|
|
46
|
+
apiKey: 'bc_live_…',
|
|
47
|
+
projectId: '…',
|
|
48
|
+
release: '4.12.0', // optional, shown on every report
|
|
49
|
+
environment: 'production', // optional
|
|
21
50
|
});
|
|
22
51
|
```
|
|
23
52
|
|
|
24
|
-
|
|
53
|
+
**Script tag**
|
|
25
54
|
|
|
26
55
|
```html
|
|
27
56
|
<!-- Pin the version. Without one the CDN serves whatever is latest, so a
|
|
28
57
|
future release changes behaviour on a site nobody redeployed. -->
|
|
29
|
-
<script src="https://unpkg.com/@sphoro/bugcatch-web-sdk@0.
|
|
58
|
+
<script src="https://unpkg.com/@sphoro/bugcatch-web-sdk@0.14.4/dist/bugcatch.umd.js"></script>
|
|
30
59
|
<script>
|
|
31
|
-
BugCatch.init({ apiBase: '…', apiKey: '…', projectId: '…' });
|
|
60
|
+
BugCatch.init({ apiBase: '…', apiKey: 'bc_live_…', projectId: '…' });
|
|
32
61
|
</script>
|
|
33
62
|
```
|
|
34
63
|
|
|
35
|
-
|
|
36
|
-
|
|
64
|
+
A floating **Report a bug** button appears, and whatever the page was doing is
|
|
65
|
+
attached to each report. Types ship with the package (`src/index.d.ts`), so
|
|
66
|
+
editors autocomplete every option and method below.
|
|
67
|
+
|
|
68
|
+
Call `init()` once, as early as you can — console and network capture start at
|
|
69
|
+
`init()`, so anything before it is not recorded. `identify()` and `setContext()`
|
|
70
|
+
may be called before or after it.
|
|
71
|
+
|
|
72
|
+
## Tell BugCatch who the customer is
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
BugCatch.identify({
|
|
76
|
+
id: customer.id, // your customer's own account id
|
|
77
|
+
email: customer.email,
|
|
78
|
+
name: customer.fullName,
|
|
79
|
+
phone: customer.mobile, // include the country code: '+919876543210'
|
|
80
|
+
customerNumber: customer.number, // your customer / client number
|
|
81
|
+
loanId: customer.activeLoanId, // a loan they hold
|
|
82
|
+
applicationId: customer.appId, // a loan application
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// On sign-out
|
|
86
|
+
BugCatch.identify(null);
|
|
87
|
+
BugCatch.setContext(null);
|
|
88
|
+
BugCatch.forgetReports(); // so the next person on this device doesn't see their updates
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Every field is optional; send what you have. What each one does:
|
|
92
|
+
|
|
93
|
+
| Field | Type | What BugCatch does with it |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `id` | string \| number | The customer's account id in **your** system. Used to look them up in your connected data source (as `userId`). Never pass a staff or agent id here. |
|
|
96
|
+
| `email` | string | Threads their reports together in the dashboard, and is where the "your issue is resolved" email goes. Treated as reachable because your app authenticated them. Needed for live updates and calls. |
|
|
97
|
+
| `name` | string | Shown instead of a bare address. |
|
|
98
|
+
| `phone` | string \| number | With `email`, compared against the record BugCatch looked up, to confirm the reporter really is that customer before anything is done on their behalf. |
|
|
99
|
+
| `loanId` | string \| number | Looked up as a **loan** (`loanId`). Alias: `loanNumber`. |
|
|
100
|
+
| `applicationId` | string \| number | Looked up as a **loan application** (`loanId`). |
|
|
101
|
+
| `customerNumber` | string \| number | Looked up as a **customer** (`customerNumber`). Alias: `customerId`. |
|
|
102
|
+
| anything else | primitive or small object | Kept as readable metadata on the report. Not used for lookups. |
|
|
103
|
+
|
|
104
|
+
**Why the IDs matter.** When your project has a data source connected (MCP), the
|
|
105
|
+
AI assistant looks the customer up *before* it reasons about the report. IDs your
|
|
106
|
+
app passes are **trusted identifiers**: your application asserted them, so the
|
|
107
|
+
assistant uses them directly instead of asking the customer "what is your loan
|
|
108
|
+
number?", and a record found by one of them counts as belonging to the reporter.
|
|
109
|
+
IDs a customer types into the report are still read — but as hints, not facts.
|
|
110
|
+
|
|
111
|
+
Rules the SDK enforces (with a console warning, never an exception):
|
|
112
|
+
|
|
113
|
+
- Identifiers must be strings or numbers, at most 120 characters, with no control
|
|
114
|
+
characters. Anything else is dropped — a truncated loan number is a different loan.
|
|
115
|
+
- An object, array or function passed as an identifier is dropped.
|
|
116
|
+
- `identify('u_42')` (not an object) is ignored and keeps the previous user.
|
|
117
|
+
Only `identify(null)` signs out.
|
|
118
|
+
- Extra fields: up to 30, strings capped at 500 characters, nested objects at 1 kB of JSON.
|
|
119
|
+
|
|
120
|
+
## Context for the current screen
|
|
121
|
+
|
|
122
|
+
`identify()` says who the customer is. `setContext()` says what they are looking
|
|
123
|
+
at — so a report filed from the repayment screen of one loan carries that loan,
|
|
124
|
+
even when the customer holds three.
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
// Entering a loan's screen
|
|
128
|
+
BugCatch.setContext({ loanId: loan.id, screen: 'repayment' });
|
|
129
|
+
|
|
130
|
+
// Leaving it — null removes one key
|
|
131
|
+
BugCatch.setContext({ loanId: null });
|
|
132
|
+
|
|
133
|
+
// Clear everything
|
|
134
|
+
BugCatch.setContext(null);
|
|
135
|
+
|
|
136
|
+
BugCatch.getContext(); // { screen: 'repayment' }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- Merged over what was set before, and kept until changed, for the life of the
|
|
140
|
+
page (not across reloads — call it again when the screen mounts).
|
|
141
|
+
- `loanId`, `applicationId` and `customerNumber` are validated and trusted exactly
|
|
142
|
+
as on `identify()`, and **outrank** the same field there: if the customer holds
|
|
143
|
+
loan A but files from loan B's screen, B is what gets looked up.
|
|
144
|
+
- Anything else (`screen`, `step`, `route`, a feature flag) is readable metadata.
|
|
145
|
+
- For a single report, pass `context` to `report()` instead — it is merged for
|
|
146
|
+
that report only and does not change what `setContext()` holds.
|
|
147
|
+
|
|
148
|
+
In a single-page app, the natural place is your router:
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
router.afterEach((to) => {
|
|
152
|
+
BugCatch.setContext({
|
|
153
|
+
screen: to.name,
|
|
154
|
+
loanId: to.params.loanId ?? null,
|
|
155
|
+
applicationId: to.params.applicationId ?? null,
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## A lending app, end to end
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
import BugCatch from '@sphoro/bugcatch-web-sdk';
|
|
164
|
+
|
|
165
|
+
BugCatch.init({
|
|
166
|
+
apiBase: 'https://bugcatch-api.example.com',
|
|
167
|
+
apiKey: 'bc_live_…',
|
|
168
|
+
projectId: '…',
|
|
169
|
+
release: APP_VERSION,
|
|
170
|
+
environment: 'production',
|
|
171
|
+
onReportSubmitted: ({ bugId }) => analytics.track('bug_reported', { bugId }),
|
|
172
|
+
onAssistantPhase: ({ phase }) => {
|
|
173
|
+
if (phase === 'needs_info') showBadge('We need one more detail from you');
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
auth.onSignIn((customer) => {
|
|
178
|
+
BugCatch.identify({
|
|
179
|
+
id: customer.userId, // e.g. 'dc046b84-3314-46ae-9b8f-526d3e50bc50'
|
|
180
|
+
email: customer.email,
|
|
181
|
+
name: customer.name,
|
|
182
|
+
phone: customer.mobile, // '+919876543210'
|
|
183
|
+
customerNumber: customer.customerNumber, // e.g. 'C1234567'
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
auth.onSignOut(() => {
|
|
188
|
+
BugCatch.identify(null);
|
|
189
|
+
BugCatch.setContext(null);
|
|
190
|
+
BugCatch.forgetReports();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Loan details / repayment / EMI screens
|
|
194
|
+
function onLoanScreen(loan) {
|
|
195
|
+
BugCatch.setContext({ loanId: loan.formattedLoanId, screen: 'loan-details' }); // 'L26090028459'
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**How the IDs travel.**
|
|
200
|
+
|
|
201
|
+
1. The SDK sends `identify()` fields as `metadata.user` and `setContext()` as
|
|
202
|
+
`metadata.context` on every report (and on "me too" joins).
|
|
203
|
+
2. The API reads them — only on the SDK's authenticated route, never from a public
|
|
204
|
+
recording link — and stores them as references with source `identify`:
|
|
205
|
+
`id` → `identify_user_id`, `loanId` → `auto_loan`, `applicationId` →
|
|
206
|
+
`auto_application`, `customerNumber` → `auto_customer`.
|
|
207
|
+
3. The analysis looks each one up as what it is (`loanId`, `customerNumber`,
|
|
208
|
+
`userId`, loans first) through your data source's tools, and marks the join
|
|
209
|
+
trusted only when the record it found came from one of these IDs.
|
|
210
|
+
4. `phone` and `email` from `identify()` are what the ownership check compares
|
|
211
|
+
with the record, before the assistant acts for the customer.
|
|
212
|
+
|
|
213
|
+
## Events
|
|
214
|
+
|
|
215
|
+
Every event is available two ways: as a single callback in `init()`, or through
|
|
216
|
+
`on()`, which several parts of your app can use independently. `on()` returns an
|
|
217
|
+
unsubscribe function. A handler that throws is logged and never breaks the SDK.
|
|
218
|
+
|
|
219
|
+
| `on(…)` event | `init` option | Payload | When |
|
|
220
|
+
|---|---|---|---|
|
|
221
|
+
| `'report'` | `onReportSubmitted` | `{ bugId, title, via: 'form' \| 'call', result }` | A report was accepted — from the widget, `report()`, or pressing Call. |
|
|
222
|
+
| `'assistant'` | `onAssistantPhase` | `{ bugId, phase, title, detail }` | The AI assistant working a report changed phase — see [The AI assistant](#the-ai-assistant). |
|
|
223
|
+
| `'error'` | `onError` | `{ action: 'report' \| 'call', error }` | A report or call failed. `error.status` is set for an HTTP refusal. |
|
|
224
|
+
| `'update'` | `onUpdate` | `ReportUpdate` | A report filed from this browser changed (status, reply, …). |
|
|
225
|
+
| `'unread'` | — | `{ count, updates }` | The unread count changed — for your own bell. |
|
|
226
|
+
| `'call'` | `onCallState` | `{ bugId, state, … }` | A call moved: `ringing`, `connecting`, `connected`, `missed`, `ended`, `failed`. |
|
|
227
|
+
| `'incoming-call'` | `onIncomingCall` | `{ bugId, callId, state }` | The team is ringing this page. |
|
|
228
|
+
| `'signin'` / `'signout'` | — | `{ email, name }` | The reporter signed in or out through the widget. |
|
|
229
|
+
|
|
230
|
+
```js
|
|
231
|
+
const off = BugCatch.on('report', ({ bugId, via }) => {
|
|
232
|
+
console.log(`filed ${bugId} via ${via}`);
|
|
233
|
+
});
|
|
234
|
+
off(); // stop listening
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Reporting and opening from your own UI
|
|
238
|
+
|
|
239
|
+
Open the built-in panel from your own menu item:
|
|
240
|
+
|
|
241
|
+
```js
|
|
242
|
+
helpMenu.onReportProblem = () => BugCatch.open();
|
|
243
|
+
BugCatch.close();
|
|
244
|
+
BugCatch.isOpen();
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Or turn the widget off and file reports yourself — everything captured is still
|
|
248
|
+
attached:
|
|
249
|
+
|
|
250
|
+
```js
|
|
251
|
+
BugCatch.init({ apiKey, projectId, apiBase, widget: false });
|
|
252
|
+
|
|
253
|
+
const result = await BugCatch.report({
|
|
254
|
+
title: 'EMI paid but still showing due', // required
|
|
255
|
+
description: 'Paid ₹4,210 by UPI this morning',
|
|
256
|
+
priority: 'HIGH', // LOW | MEDIUM | HIGH | CRITICAL
|
|
257
|
+
context: { loanId: 'L26090028459' }, // this report only
|
|
258
|
+
references: [{ key: 'utr', value: '4321…' }], // what the customer typed, if you asked
|
|
259
|
+
metadata: { paymentMethod: 'upi' },
|
|
260
|
+
});
|
|
261
|
+
// → { id, redactionsApplied, watchToken?, estimate? } or { cancelled: true } from beforeSend
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
`report()` rejects with an `Error` whose message says what went wrong
|
|
265
|
+
(`[BugCatch] report failed (HTTP 401): the apiKey was not accepted`), and emits
|
|
266
|
+
`'error'`. Malformed `references` entries are dropped rather than failing the report.
|
|
267
|
+
|
|
268
|
+
## The AI assistant
|
|
269
|
+
|
|
270
|
+
When your workspace has AI analysis on, every report is investigated as soon as
|
|
271
|
+
it arrives: the assistant reads the report, the captured logs and — with a
|
|
272
|
+
connected data source — the customer's own records, looked up by the IDs above.
|
|
273
|
+
After a report is filed, the widget opens its conversation, where the reporter
|
|
274
|
+
watches progress and answers questions.
|
|
275
|
+
|
|
276
|
+
| Phase | What the reporter sees |
|
|
277
|
+
|---|---|
|
|
278
|
+
| `analyzing` | "Analyzing the issue…" — reading the report, the account and the logs. |
|
|
279
|
+
| `needs_info` | It asked a question (often: which loan?) and is waiting for the reporter's reply. |
|
|
280
|
+
| `confirming` | It found the likely cause and asks the reporter to confirm. |
|
|
281
|
+
| `resolving` | The cause is confirmed, or it is being handled. |
|
|
282
|
+
| `reviewing` | Handed to your team, who will reply in the thread. |
|
|
283
|
+
| `resolved` | Settled. |
|
|
284
|
+
|
|
285
|
+
The phase is known when a report is fetched: the widget does this every few
|
|
286
|
+
seconds while its thread is open, and `BugCatch.myReport(bugId)` does it for your
|
|
287
|
+
own view (it also returns `report.assistant`). Each change fires
|
|
288
|
+
`onAssistantPhase` / `'assistant'` once.
|
|
289
|
+
|
|
290
|
+
**Passing IDs is the biggest thing you can do for it.** Without them, the first
|
|
291
|
+
thing the assistant usually has to do is ask the customer which account or loan
|
|
292
|
+
they mean — the `needs_info` step — and wait for an answer.
|
|
293
|
+
|
|
294
|
+
## Privacy: what is captured and what is not
|
|
295
|
+
|
|
296
|
+
Captured, and only after `init()`:
|
|
297
|
+
|
|
298
|
+
- **Console** — the last 100 entries (level, message, time), plus uncaught errors
|
|
299
|
+
and unhandled promise rejections. Whatever your app logs is captured, so do not
|
|
300
|
+
log tokens or personal data — or drop those entries in `beforeSend`.
|
|
301
|
+
- **Network** — the last 50 `fetch`/XHR calls: URL, method, status, duration.
|
|
302
|
+
**Request and response bodies are never read.** Credentials in URLs (API keys,
|
|
303
|
+
tokens, signatures, JWTs, `user:password@`) are replaced with `[REDACTED]` before
|
|
304
|
+
the entry is even buffered.
|
|
305
|
+
- **Environment** — browser, OS, language, viewport and screen, timezone,
|
|
306
|
+
connection type, page title and referrer.
|
|
307
|
+
- **The page URL** when the report is filed (credential-scrubbed the same way).
|
|
308
|
+
- **Only what you pass** to `identify()`, `setContext()` and `report({ metadata })`.
|
|
309
|
+
The SDK never reads your cookies, your app's storage or what is typed into your forms.
|
|
310
|
+
- **Screenshots and recordings** only when the reporter chooses to take one; the
|
|
311
|
+
browser always shows its own permission prompt.
|
|
312
|
+
|
|
313
|
+
On top of that:
|
|
314
|
+
|
|
315
|
+
- `beforeSend(payload)` runs in the browser before anything leaves it — mutate the
|
|
316
|
+
payload or return `false` to cancel.
|
|
317
|
+
- Your workspace's redaction rules (Workspace Settings → Security) run again on the
|
|
318
|
+
server before anything is stored; encrypted workspaces seal it at rest.
|
|
319
|
+
- `captureConsole`, `captureNetwork` and `captureErrors` can each be switched off.
|
|
320
|
+
- The SDK stores in the reporter's own browser only: a watch token for their reports,
|
|
321
|
+
unread update notices, unfinished drafts (7 days), and a widget sign-in if used.
|
|
322
|
+
`forgetReports()` clears the first two.
|
|
37
323
|
|
|
38
324
|
## Options
|
|
39
325
|
|
|
40
326
|
| Option | Default | What it does |
|
|
41
327
|
|---|---|---|
|
|
42
|
-
| `apiKey` | *required* | Workspace API key. |
|
|
43
|
-
| `projectId` | *required* | Project the
|
|
44
|
-
| `apiBase` | `http://localhost:3000` |
|
|
45
|
-
| `
|
|
328
|
+
| `apiKey` | *required* | Workspace API key (`bc_live_…`). |
|
|
329
|
+
| `projectId` | *required* | Project the reports land in. |
|
|
330
|
+
| `apiBase` | `http://localhost:3000` | Your BugCatch API URL. **Set it everywhere except local development.** A trailing slash is removed. |
|
|
331
|
+
| `user` | `null` | Same as calling `identify()` right after `init()`. |
|
|
332
|
+
| `context` | `null` | Same as calling `setContext()` right after `init()`. |
|
|
333
|
+
| `release` / `environment` / `buildId` | — | Attached to every report. |
|
|
334
|
+
| `debug` | `false` | Log identify, context and each report's shape to the console. |
|
|
335
|
+
| `onReportSubmitted` | — | See [Events](#events). |
|
|
336
|
+
| `onAssistantPhase` | — | See [Events](#events). |
|
|
337
|
+
| `onError` | — | See [Events](#events). |
|
|
338
|
+
| `widget` | `true` | Floating launcher and panel. `false` to drive everything from your own UI. |
|
|
46
339
|
| `widgetPosition` | `'bottom-right'` | `bottom-left`, `top-right`, `top-left`. |
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `captureErrors` | `true` |
|
|
50
|
-
| `consoleLimit` | `100` |
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `knownIssues` | `true` | Show what this project already knows is broken above the form, and offer "this is happening to me too" instead of a second report. Draws nothing until the project switches the feature on and somebody lists an individual report — see [What is already known](#what-is-already-known). |
|
|
56
|
-
| `maxAttachments` | `5` | Per report. |
|
|
57
|
-
| `maxAttachmentBytes` | `25 MB` | Per file. |
|
|
58
|
-
| `maxRecordingMs` | `120000` | Hard cap on a single recording. |
|
|
340
|
+
| `theme` | `'auto'` | `auto` follows the system; `light` / `dark` pin it. A visitor's own choice in the widget wins on later visits. |
|
|
341
|
+
| `accent` | `'#4F46E5'` | Any CSS colour. |
|
|
342
|
+
| `captureConsole` / `captureNetwork` / `captureErrors` | `true` | Each capture can be switched off. |
|
|
343
|
+
| `consoleLimit` / `networkLimit` | `100` / `50` | Entries retained. |
|
|
344
|
+
| `attachments` | `true` | Offer screenshot, recording and file attach. |
|
|
345
|
+
| `maxAttachments` / `maxAttachmentBytes` / `maxRecordingMs` | `5` / `25 MB` / `120000` | Attachment limits. |
|
|
346
|
+
| `drafts` | `true` | Recover an unfinished report on this device for seven days. |
|
|
347
|
+
| `knownIssues` | `true` | Show what this project already knows is broken, with "me too". Draws nothing until the project turns it on. |
|
|
59
348
|
| `beforeSend` | — | Mutate the payload, or return `false` to cancel. |
|
|
60
|
-
| `updates` | `true` | Tell the reporter
|
|
61
|
-
| `updateToast` | `true` | Show the built-in notification
|
|
62
|
-
| `onUpdate` | — |
|
|
63
|
-
| `voice` | `true` | Offer
|
|
64
|
-
| `onCallState` | — |
|
|
349
|
+
| `updates` | `true` | Tell the reporter live when a report they filed moves. |
|
|
350
|
+
| `updateToast` | `true` | Show the built-in notification; `false` to render your own from `'update'` / `'unread'`. |
|
|
351
|
+
| `onUpdate` | — | Called on every update. |
|
|
352
|
+
| `voice` | `true` | Offer calls where the workspace has calling configured. `false` switches calls off in both directions. |
|
|
353
|
+
| `onCallState` / `onIncomingCall` | — | See [Talking a report through](#talking-a-report-through). |
|
|
354
|
+
|
|
355
|
+
An option the SDK does not know is reported in the console with a suggestion
|
|
356
|
+
(`unknown option "projectID" — did you mean "projectId"?`).
|
|
357
|
+
|
|
358
|
+
## API at a glance
|
|
359
|
+
|
|
360
|
+
```js
|
|
361
|
+
BugCatch.init(options); // once
|
|
362
|
+
BugCatch.identify({ id, email, name, phone, loanId, applicationId, customerNumber });
|
|
363
|
+
BugCatch.identify(null); // sign-out
|
|
364
|
+
BugCatch.setContext({ loanId, screen }); // merge; null value removes a key
|
|
365
|
+
BugCatch.setContext(null); // clear
|
|
366
|
+
BugCatch.getContext();
|
|
367
|
+
|
|
368
|
+
BugCatch.open(); BugCatch.close(); BugCatch.isOpen();
|
|
369
|
+
await BugCatch.report({ title, description, priority, context, references, metadata, attachments });
|
|
370
|
+
await BugCatch.uploadAttachment(blob, mime, onProgress);
|
|
371
|
+
|
|
372
|
+
const off = BugCatch.on('report' | 'assistant' | 'error' | 'update' | 'unread' | 'call'
|
|
373
|
+
| 'incoming-call' | 'signin' | 'signout', handler);
|
|
374
|
+
BugCatch.off(event, handler);
|
|
375
|
+
|
|
376
|
+
await BugCatch.myReports(); // everything filed from this browser
|
|
377
|
+
await BugCatch.myReport(bugId); // one report, with timeline and assistant phase
|
|
378
|
+
await BugCatch.replyToReport(bugId, { body, attachments });
|
|
379
|
+
BugCatch.unreadUpdates(); BugCatch.markUpdateRead(id); BugCatch.markReportRead(bugId); BugCatch.markUpdatesRead();
|
|
380
|
+
BugCatch.hasReports(); BugCatch.forgetReports();
|
|
381
|
+
|
|
382
|
+
await BugCatch.loginState(); BugCatch.signedInAs();
|
|
383
|
+
await BugCatch.signIn(email); await BugCatch.confirmSignIn(email, code); await BugCatch.signOut();
|
|
384
|
+
await BugCatch.referenceFields(); // the identifier question this project asks
|
|
385
|
+
|
|
386
|
+
await BugCatch.knownIssues(); await BugCatch.matchKnownIssues(q, { signal }); await BugCatch.meToo(bugId);
|
|
387
|
+
|
|
388
|
+
await BugCatch.canCallNow(); await BugCatch.startCall(bugId?); BugCatch.endCall();
|
|
65
389
|
|
|
66
|
-
|
|
390
|
+
BugCatch.snapshot(); // what would be sent right now (user, context, logs)
|
|
391
|
+
BugCatch.clear(); // empty the capture buffers
|
|
392
|
+
BugCatch.destroy(); // restore patched globals, remove the widget
|
|
393
|
+
BugCatch.version; // the SDK version, for support tickets
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Troubleshooting
|
|
397
|
+
|
|
398
|
+
Turn on `debug: true` first: the console then shows what `init()` received, every
|
|
399
|
+
`identify()` / `setContext()` and the shape of each report as it is sent.
|
|
400
|
+
|
|
401
|
+
| Symptom | Likely cause |
|
|
402
|
+
|---|---|
|
|
403
|
+
| `[BugCatch] apiKey is required` / `projectId is required` thrown | The option is missing or still the snippet placeholder. |
|
|
404
|
+
| Warning: *apiBase is not set, so reports go to http://localhost:3000* | Set `apiBase` to your BugCatch API URL. |
|
|
405
|
+
| Warning: *apiKey looks like a project id* | `apiKey` and `projectId` are swapped. |
|
|
406
|
+
| `report failed (HTTP 401): the apiKey was not accepted` | Revoked or mistyped key — keys are shown once; create a new one. |
|
|
407
|
+
| `report failed (HTTP 403)` | The key belongs to a different workspace than `projectId`. |
|
|
408
|
+
| `could not reach https://…` | Wrong `apiBase`, the API is down, or the API's CORS does not allow your site's origin. |
|
|
409
|
+
| `This project needs something to look the report up by — …` | The project requires an identifier. Pass `loanId` / `customerNumber` via `identify()` or `setContext()`, or let the widget ask. |
|
|
410
|
+
| The assistant keeps asking customers for their loan number | Pass the IDs through `identify()` / `setContext()`; check `BugCatch.snapshot().user` and `.context` show them. |
|
|
411
|
+
| `identify()` "did nothing" | It was called with a non-object (ignored with a warning), or an identifier was an object/array/too long. See the console. |
|
|
412
|
+
| Reports are not attributed / no resolution email / no live updates / no Call button | `identify()` has no `email`. |
|
|
413
|
+
| The panel will not open with `open()` | `widget: false` — there is no built-in panel; use `report()`. |
|
|
414
|
+
| Nothing is captured from before a certain point | Capture starts at `init()`. Call it as early as possible. |
|
|
415
|
+
|
|
416
|
+
## Reference
|
|
417
|
+
|
|
418
|
+
### Screenshots, recordings and files
|
|
67
419
|
|
|
68
420
|
The widget can attach a screenshot, a screen recording, or a file the reporter
|
|
69
421
|
picks. Capture goes through `getDisplayMedia`, so **the browser always shows its
|
|
@@ -98,68 +450,6 @@ off.
|
|
|
98
450
|
> "MaxAgeSeconds":86400}]}'
|
|
99
451
|
> ```
|
|
100
452
|
|
|
101
|
-
## API
|
|
102
|
-
|
|
103
|
-
```js
|
|
104
|
-
BugCatch.identify({ id, email, name }); // attach the signed-in user
|
|
105
|
-
await BugCatch.report({ title, description, priority, metadata, attachments });
|
|
106
|
-
await BugCatch.uploadAttachment(blob, mime, onProgress);
|
|
107
|
-
BugCatch.snapshot(); // what would be sent right now
|
|
108
|
-
BugCatch.clear(); // empty the buffers
|
|
109
|
-
BugCatch.destroy(); // restore every patched global
|
|
110
|
-
|
|
111
|
-
const off = BugCatch.on('update', fn); // live updates on this browser's reports
|
|
112
|
-
await BugCatch.myReports(); // everything filed here
|
|
113
|
-
await BugCatch.myReport(bugId); // one report, with its full timeline
|
|
114
|
-
await BugCatch.replyToReport(bugId, { body, attachments }); // write back
|
|
115
|
-
BugCatch.hasReports(); // has this browser filed anything?
|
|
116
|
-
BugCatch.forgetReports(); // drop the subscription (call on sign-out)
|
|
117
|
-
|
|
118
|
-
await BugCatch.loginState(); // what this project asks, and who is signed in
|
|
119
|
-
BugCatch.signedInAs(); // { email, name } | null, from storage
|
|
120
|
-
await BugCatch.signIn(email); // step one: mail a code to any address
|
|
121
|
-
await BugCatch.confirmSignIn(email, code);// step two: spend it
|
|
122
|
-
await BugCatch.signOut(); // end the session, here and on the server
|
|
123
|
-
|
|
124
|
-
await BugCatch.knownIssues(); // what this project says is already broken
|
|
125
|
-
await BugCatch.matchKnownIssues(q, { signal }); // the ones matching what they are typing
|
|
126
|
-
await BugCatch.meToo(bugId, { email }); // join a report instead of filing a second
|
|
127
|
-
|
|
128
|
-
await BugCatch.canCallNow(); // may this visitor call instead of typing?
|
|
129
|
-
await BugCatch.startCall(); // file a report and ring the team about it
|
|
130
|
-
await BugCatch.canCall(bugId); // may this report be talked through?
|
|
131
|
-
await BugCatch.startCall(bugId); // ring the team about an existing report
|
|
132
|
-
await BugCatch.canCallTeam(bugId); // the same question, under the widget's name
|
|
133
|
-
await BugCatch.startLiveCall(bugId); // the same call, under its older name
|
|
134
|
-
BugCatch.on('incoming-call', fn); // the team ringing *them* — the SDK draws the card
|
|
135
|
-
BugCatch.endCall(); // hang up
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Telling BugCatch who is reporting
|
|
139
|
-
|
|
140
|
-
```js
|
|
141
|
-
BugCatch.identify({ id: 'u_42', email: 'alex@example.com', name: 'Alex' });
|
|
142
|
-
|
|
143
|
-
// On sign-out
|
|
144
|
-
BugCatch.identify(null);
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
`email` is the field with behaviour attached. It does three things:
|
|
148
|
-
|
|
149
|
-
- **Threads their reports together.** Every report from that address belongs to
|
|
150
|
-
one person in the dashboard, with a count and a last-seen — so "this user
|
|
151
|
-
keeps hitting things" is visible rather than something you have to notice.
|
|
152
|
-
- **Closes the loop.** When someone marks the bug resolved, that person gets
|
|
153
|
-
the project's resolution email. Nobody has to remember to tell them.
|
|
154
|
-
- **Is trusted, because you authenticated them.** An address typed into a
|
|
155
|
-
public recording-link form is unusable for outbound mail until the reporter
|
|
156
|
-
answers a confirmation code. One that arrives through `identify()` is not,
|
|
157
|
-
because your app already knows who they are. Only send addresses you have
|
|
158
|
-
actually signed in.
|
|
159
|
-
|
|
160
|
-
Calling `identify()` is optional; without it reports are filed anonymously and
|
|
161
|
-
nobody gets a resolution email.
|
|
162
|
-
|
|
163
453
|
### Letting them sign in themselves
|
|
164
454
|
|
|
165
455
|
`identify()` is your application vouching for its own user. Some projects want
|
|
@@ -457,7 +747,7 @@ parameters — `?page=2`, `?sort=created_at`, `?code=US` — are left alone.
|
|
|
457
747
|
The API applies the same scrubbing on intake and again when a report is read back, so
|
|
458
748
|
reports captured by older SDK versions are covered too.
|
|
459
749
|
|
|
460
|
-
|
|
750
|
+
### What is already known
|
|
461
751
|
|
|
462
752
|
The widget opens with the reports this project has chosen to say are already
|
|
463
753
|
broken, above the form rather than after it, and offers **Me too** beside each
|
|
@@ -503,7 +793,7 @@ this on, which is the default — draw nothing rather than an empty heading. Wha
|
|
|
503
793
|
comes back is a title, a state and a count, and never a severity, an assignee or
|
|
504
794
|
a description: the question it answers is "is this the thing I came here about".
|
|
505
795
|
|
|
506
|
-
|
|
796
|
+
### Talking a report through
|
|
507
797
|
|
|
508
798
|
Where the workspace has sphoro.voice configured, a reporter can talk to the team
|
|
509
799
|
instead of typing. There are two ways in.
|
|
@@ -576,7 +866,7 @@ Four things are worth being explicit about, because they are what people ask:
|
|
|
576
866
|
- **One call at a time**, and `destroy()` ends it. Two microphones on one page
|
|
577
867
|
talk over each other.
|
|
578
868
|
|
|
579
|
-
|
|
869
|
+
#### Being called
|
|
580
870
|
|
|
581
871
|
The same live call, placed from the other end. Somebody on the team reads a
|
|
582
872
|
report an hour after it was filed, has one question whose answer is thirty
|
|
@@ -652,22 +942,7 @@ supplied an email: a call with nobody to attribute has no way to tell the
|
|
|
652
942
|
reporter what happened next. Set `voice: false` to opt out entirely; the code is
|
|
653
943
|
still in the bundle, but nothing runs.
|
|
654
944
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
- **Console** — last N entries with level and timestamp, plus uncaught errors and
|
|
658
|
-
unhandled promise rejections.
|
|
659
|
-
- **Network** — last N `fetch`/XHR calls: URL, method, status, duration, and flags for
|
|
660
|
-
failed or slow. **Request and response bodies are never read**, and **credentials are
|
|
661
|
-
stripped out of the URL before it is buffered** — see below.
|
|
662
|
-
- **Environment** — user agent, platform, language(s), viewport, screen (size, available
|
|
663
|
-
size, colour depth, orientation), DPR, timezone and offset, connection type, CPU cores,
|
|
664
|
-
device memory, touch points, battery level and charging state, colour-scheme and
|
|
665
|
-
reduced-motion preferences, Do Not Track, online state, referrer, page title.
|
|
666
|
-
- **Page URL** at the moment of reporting.
|
|
667
|
-
|
|
668
|
-
Both buffers are ring buffers, so a long-lived page won't grow memory without bound.
|
|
669
|
-
|
|
670
|
-
## Notes
|
|
945
|
+
### Notes
|
|
671
946
|
|
|
672
947
|
- The widget renders in a shadow root, so your CSS and its CSS can't collide.
|
|
673
948
|
- Capture never throws into your page: every hook is wrapped, and originals are always
|