@sphoro/bugcatch-web-sdk 0.13.1
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/LICENSE +21 -0
- package/README.md +717 -0
- package/dist/bugcatch.es.js +4393 -0
- package/dist/bugcatch.es.js.map +1 -0
- package/dist/bugcatch.umd.js +952 -0
- package/dist/bugcatch.umd.js.map +1 -0
- package/package.json +57 -0
- package/src/audio.js +191 -0
- package/src/capture.js +300 -0
- package/src/collectors.js +213 -0
- package/src/incoming-call.js +324 -0
- package/src/index.d.ts +684 -0
- package/src/index.js +1217 -0
- package/src/live-call.js +870 -0
- package/src/redact.js +177 -0
- package/src/session.js +189 -0
- package/src/updates.js +1062 -0
- package/src/voice.js +372 -0
- package/src/widget.js +2481 -0
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who is using your app. `email` is the one field with behaviour attached —
|
|
3
|
+
* see identify().
|
|
4
|
+
*/
|
|
5
|
+
export interface BugCatchUser { id?: string; email?: string; name?: string; }
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What a bug *report* may carry. Narrower than what may be uploaded: a report's
|
|
9
|
+
* attachments are typed SCREENSHOT or VIDEO, so a document has no honest place
|
|
10
|
+
* on one — it belongs on a message, where it keeps its own type.
|
|
11
|
+
*/
|
|
12
|
+
export type AttachmentMime =
|
|
13
|
+
| 'image/png' | 'image/jpeg' | 'image/webp' | 'video/webm' | 'video/mp4';
|
|
14
|
+
|
|
15
|
+
/** What a *message* may carry: the above, plus voice notes and documents. */
|
|
16
|
+
export type MessageMime =
|
|
17
|
+
| AttachmentMime | 'image/gif'
|
|
18
|
+
| 'audio/webm' | 'audio/ogg' | 'audio/mpeg' | 'audio/mp4' | 'audio/wav'
|
|
19
|
+
| 'application/pdf' | 'text/plain' | 'text/csv' | 'application/json'
|
|
20
|
+
| 'application/zip' | 'application/msword'
|
|
21
|
+
| 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
22
|
+
| 'application/vnd.ms-excel'
|
|
23
|
+
| 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A report this project has chosen to say is already known, as a reporter may
|
|
27
|
+
* read it.
|
|
28
|
+
*
|
|
29
|
+
* Four fields, and the absences are the design. No severity: how bad the team
|
|
30
|
+
* thinks something is, published to everyone who opens the widget, is an
|
|
31
|
+
* internal judgement that reads as a promise. No assignee, no description, no
|
|
32
|
+
* analysis. The question this answers is "is this the thing I came here about".
|
|
33
|
+
*/
|
|
34
|
+
export interface KnownIssue {
|
|
35
|
+
id: string;
|
|
36
|
+
title: string;
|
|
37
|
+
status: 'OPEN' | 'IN_PROGRESS';
|
|
38
|
+
/** How many people have said it is happening to them. Never who. */
|
|
39
|
+
affectedCount: number;
|
|
40
|
+
/** When it was first reported, ISO-8601. */
|
|
41
|
+
since: string;
|
|
42
|
+
/** Whether the person asking has already joined this one. */
|
|
43
|
+
joined: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** What came back from saying an issue is happening to you too. */
|
|
47
|
+
export interface JoinedIssue {
|
|
48
|
+
/** False when this browser had already joined — a second press, not a second person. */
|
|
49
|
+
joined: boolean;
|
|
50
|
+
affectedCount: number;
|
|
51
|
+
/** Held by the SDK, and what lets this browser hear that the bug was fixed. */
|
|
52
|
+
watchToken?: string | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface BugCatchConfig {
|
|
56
|
+
apiBase?: string;
|
|
57
|
+
apiKey: string;
|
|
58
|
+
projectId: string;
|
|
59
|
+
consoleLimit?: number;
|
|
60
|
+
networkLimit?: number;
|
|
61
|
+
captureConsole?: boolean;
|
|
62
|
+
captureNetwork?: boolean;
|
|
63
|
+
captureErrors?: boolean;
|
|
64
|
+
widget?: boolean;
|
|
65
|
+
widgetPosition?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
66
|
+
/** 'auto' follows the visitor's system setting. Default 'auto'. */
|
|
67
|
+
theme?: 'auto' | 'light' | 'dark';
|
|
68
|
+
/** Any CSS colour. Default '#4F46E5'. */
|
|
69
|
+
accent?: string;
|
|
70
|
+
/** Offer screenshot / recording / file attach in the widget. Default true. */
|
|
71
|
+
attachments?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Show what this project already knows is broken before the report form, and
|
|
74
|
+
* offer "this is happening to me too" instead of a second report. Default
|
|
75
|
+
* true — but the list is empty, and nothing is drawn, until the project turns
|
|
76
|
+
* the feature on and somebody lists an individual report.
|
|
77
|
+
*/
|
|
78
|
+
knownIssues?: boolean;
|
|
79
|
+
/** Default 5. */
|
|
80
|
+
maxAttachments?: number;
|
|
81
|
+
/** Default 25 MB. */
|
|
82
|
+
maxAttachmentBytes?: number;
|
|
83
|
+
/** Hard cap on a single recording. Default 120000 (2 minutes). */
|
|
84
|
+
maxRecordingMs?: number;
|
|
85
|
+
/** Return false to cancel the report, or a payload object to replace it. */
|
|
86
|
+
beforeSend?: (payload: Record<string, any>) => Record<string, any> | false | void;
|
|
87
|
+
user?: BugCatchUser | null;
|
|
88
|
+
/**
|
|
89
|
+
* Tell the reporter, live and in your app, when a bug they filed changes
|
|
90
|
+
* status. Only ever connects once this browser has actually filed one.
|
|
91
|
+
* Default true.
|
|
92
|
+
*/
|
|
93
|
+
updates?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Show the built-in notification on an update. Default true.
|
|
96
|
+
*
|
|
97
|
+
* It stays up until the reporter marks it read, across a reload included —
|
|
98
|
+
* nothing here is on a timer, because the point of telling somebody their bug
|
|
99
|
+
* was fixed is that they find out. Turn it off to render your own, and mark
|
|
100
|
+
* them read with `markUpdateRead`.
|
|
101
|
+
*/
|
|
102
|
+
updateToast?: boolean;
|
|
103
|
+
/** Called on every update, whether or not the built-in notification is shown. */
|
|
104
|
+
onUpdate?: ((update: ReportUpdate) => void) | null;
|
|
105
|
+
/**
|
|
106
|
+
* Calling, in both directions, where the workspace has sphoro.voice
|
|
107
|
+
* configured. Default true.
|
|
108
|
+
*
|
|
109
|
+
* Outgoing: a call is offered when a report is picked up. Incoming: a card
|
|
110
|
+
* appears when somebody on the team rings about a report filed from this
|
|
111
|
+
* browser. Neither dials anything and neither asks for a microphone until the
|
|
112
|
+
* person presses the button — see `startCall` and `onIncomingCall`.
|
|
113
|
+
*
|
|
114
|
+
* `false` switches off both. A host application that does not want its
|
|
115
|
+
* customers rung sets it, and no card is ever drawn.
|
|
116
|
+
*/
|
|
117
|
+
voice?: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Called as a call of either kind moves between states. `detail.live` marks
|
|
120
|
+
* the ones about a call to a person; their states come from LiveCallState.
|
|
121
|
+
*/
|
|
122
|
+
onCallState?: ((
|
|
123
|
+
state: CallState | LiveCallState,
|
|
124
|
+
detail: { bugId: string; live?: boolean; reason?: string },
|
|
125
|
+
) => void) | null;
|
|
126
|
+
/** Called with each line of a call, as it is recognised. */
|
|
127
|
+
onCallTranscript?: ((line: CallTranscriptLine) => void) | null;
|
|
128
|
+
/**
|
|
129
|
+
* Called when somebody on the team rings this page about a report filed from
|
|
130
|
+
* it — the one call this SDK does not start.
|
|
131
|
+
*
|
|
132
|
+
* The card is already on screen by the time this runs. It is for a host
|
|
133
|
+
* application that wants to duck its own audio or pause a video, not a hook
|
|
134
|
+
* that can suppress a call the person can see ringing. `voice: false`
|
|
135
|
+
* switches the whole thing off, card included.
|
|
136
|
+
*/
|
|
137
|
+
onIncomingCall?: ((call: IncomingCall) => void) | null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* A member of the team, ringing this page.
|
|
142
|
+
*
|
|
143
|
+
* Everything on it is what somebody has about five seconds to read before
|
|
144
|
+
* deciding whether to pick up. `recorded` says so before Answer rather than
|
|
145
|
+
* after, which is the only honest moment: on a peer-to-peer call the copy that
|
|
146
|
+
* gets kept is uplinked by this browser, so consent that arrives afterwards is
|
|
147
|
+
* consent to something that already happened.
|
|
148
|
+
*/
|
|
149
|
+
export interface IncomingCall {
|
|
150
|
+
bugId: string;
|
|
151
|
+
callId: string;
|
|
152
|
+
/** Who is calling, by name. */
|
|
153
|
+
from: string;
|
|
154
|
+
/** Which product the report was filed against. */
|
|
155
|
+
project: string;
|
|
156
|
+
/** The report's short reference. */
|
|
157
|
+
reference: string;
|
|
158
|
+
/** When the ringing stops, ISO-8601. */
|
|
159
|
+
ringsUntil: string;
|
|
160
|
+
/** Whether this call would be recorded or transcribed. */
|
|
161
|
+
recorded: boolean;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* What happened to a call the team placed to this page.
|
|
166
|
+
*
|
|
167
|
+
* `ringing` when the card appears, then exactly one of the rest. `missed` means
|
|
168
|
+
* nobody pressed anything before the deadline; `declined` means they did.
|
|
169
|
+
*/
|
|
170
|
+
export type IncomingCallState =
|
|
171
|
+
'ringing' | 'answered' | 'declined' | 'missed' | 'failed';
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Where a call is. `listening` means the reporter's turn, `speaking` the
|
|
175
|
+
* agent's; `ended` and `failed` are both final and release the microphone.
|
|
176
|
+
*/
|
|
177
|
+
export type CallState = 'connecting' | 'listening' | 'speaking' | 'ended' | 'failed';
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Where a *live* call is — one to a person on the team rather than to the
|
|
181
|
+
* agent. A separate vocabulary because the two have almost nothing in common:
|
|
182
|
+
* an agent call is never `ringing` because a model does not keep anybody
|
|
183
|
+
* waiting, and a live call is never `listening` or `speaking`, because nobody
|
|
184
|
+
* is transcribing it to know whose turn it is.
|
|
185
|
+
*
|
|
186
|
+
* `missed` is final and ordinary: the ringing stopped before anybody picked up.
|
|
187
|
+
*/
|
|
188
|
+
export type LiveCallState =
|
|
189
|
+
'ringing' | 'connecting' | 'connected' | 'ended' | 'missed' | 'failed';
|
|
190
|
+
|
|
191
|
+
export interface CallTranscriptLine {
|
|
192
|
+
bugId: string;
|
|
193
|
+
/** Who said it. `agent` lines arrive as deltas; `final` marks the end of a turn. */
|
|
194
|
+
role: 'reporter' | 'agent';
|
|
195
|
+
text: string;
|
|
196
|
+
final: boolean;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** A call in progress. */
|
|
200
|
+
/** Which kind of call a deployment offers. One agent has one mode, so never both. */
|
|
201
|
+
export type CallKind = 'agent' | 'live';
|
|
202
|
+
|
|
203
|
+
export interface CallHandle {
|
|
204
|
+
/** End it. Safe to call twice, and after it has already ended. */
|
|
205
|
+
hangUp(): void;
|
|
206
|
+
/** The report this call is about, whether it was named or filed on the way in. */
|
|
207
|
+
bugId: string;
|
|
208
|
+
/** What was filed, when the call is the reason the report exists. */
|
|
209
|
+
filed?: { id: string; watchToken?: string } | null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface LiveCallHandle {
|
|
213
|
+
/** End it. Safe to call twice, and after it has already ended. */
|
|
214
|
+
hangUp(): void;
|
|
215
|
+
/**
|
|
216
|
+
* Mute or unmute the microphone, returning what it now is.
|
|
217
|
+
*
|
|
218
|
+
* The track is disabled rather than the stream stopped: stopping it would
|
|
219
|
+
* drop the sender and renegotiate the call, and unmuting would then take a
|
|
220
|
+
* second and a half of silence to come back.
|
|
221
|
+
*/
|
|
222
|
+
setMuted(muted: boolean): boolean;
|
|
223
|
+
isMuted(): boolean;
|
|
224
|
+
/** The report this call is about. */
|
|
225
|
+
bugId: string;
|
|
226
|
+
/** sphoro.voice's id for the call, as the team's dashboard also knows it. */
|
|
227
|
+
callId: string;
|
|
228
|
+
live: true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export interface Attachment {
|
|
232
|
+
type: 'SCREENSHOT' | 'VIDEO';
|
|
233
|
+
storageKey: string;
|
|
234
|
+
mime: AttachmentMime;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface ReportInput {
|
|
238
|
+
title: string;
|
|
239
|
+
description?: string;
|
|
240
|
+
priority?: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
241
|
+
metadata?: Record<string, any>;
|
|
242
|
+
/** Already uploaded via uploadAttachment(). Entries without a key are dropped. */
|
|
243
|
+
attachments?: Attachment[];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* How long a report is likely to take, as a reporter may be shown it.
|
|
248
|
+
*
|
|
249
|
+
* A band and a sentence, never a single figure: "about two days" is heard as a
|
|
250
|
+
* promise about Thursday, and "two to five days" is heard as what it is. The
|
|
251
|
+
* basis, the sample size and the past bugs it was drawn from are all real and
|
|
252
|
+
* all deliberately absent — they are the workings of a triage queue the person
|
|
253
|
+
* reading this is not part of.
|
|
254
|
+
*
|
|
255
|
+
* Null whenever the project has switched estimates off, has decided its
|
|
256
|
+
* reporters do not see them, or has nothing closed to base one on. Draw nothing
|
|
257
|
+
* in that case: a confirmation that admits it cannot answer is worse than one
|
|
258
|
+
* that never raises the question.
|
|
259
|
+
*/
|
|
260
|
+
export interface ReportEstimate {
|
|
261
|
+
/** The optimistic end of the band, in hours from filing. */
|
|
262
|
+
lowHours: number;
|
|
263
|
+
highHours: number;
|
|
264
|
+
/** The band as a phrase — "2–5 days", "about 6 hours". Ready to print. */
|
|
265
|
+
label: string;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export interface ReportResult {
|
|
269
|
+
id: string;
|
|
270
|
+
redactionsApplied: number;
|
|
271
|
+
/**
|
|
272
|
+
* Returned only when identify() supplied an email, so the report could be
|
|
273
|
+
* attributed. The SDK stores it and uses it to watch for status changes —
|
|
274
|
+
* you do not need to handle it yourself.
|
|
275
|
+
*/
|
|
276
|
+
watchToken?: string | null;
|
|
277
|
+
/**
|
|
278
|
+
* Computed on this same request rather than a follow-up, so it can be shown
|
|
279
|
+
* on the confirmation the reporter is still looking at.
|
|
280
|
+
*/
|
|
281
|
+
estimate?: ReportEstimate | null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** One row of the reporter's own history. */
|
|
285
|
+
export interface MyReport {
|
|
286
|
+
id: string;
|
|
287
|
+
/** The short handle shown on screen and in their confirmation. */
|
|
288
|
+
reference: string;
|
|
289
|
+
title: string;
|
|
290
|
+
status: 'OPEN' | 'IN_PROGRESS' | 'RESOLVED' | 'CLOSED';
|
|
291
|
+
priority: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
292
|
+
createdAt: string;
|
|
293
|
+
updatedAt: string;
|
|
294
|
+
project: string;
|
|
295
|
+
workspace: string;
|
|
296
|
+
attachments: number;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** A report of this browser's changed status. */
|
|
300
|
+
export type ReportUpdateKind = 'status' | 'severity' | 'assignment' | 'reply';
|
|
301
|
+
|
|
302
|
+
export interface ReportUpdate {
|
|
303
|
+
/**
|
|
304
|
+
* Stable for the life of this update, and the same after a reload — what
|
|
305
|
+
* `markUpdateRead` takes, and what a host app keys its own list on.
|
|
306
|
+
*/
|
|
307
|
+
id: string;
|
|
308
|
+
bugId: string;
|
|
309
|
+
title: string;
|
|
310
|
+
/** What changed. Anything unrecognised should be treated as a generic update. */
|
|
311
|
+
kind: ReportUpdateKind;
|
|
312
|
+
/** A line written for the reporter, ready to show. Prefer this over building your own. */
|
|
313
|
+
summary: string | null;
|
|
314
|
+
status: 'OPEN' | 'IN_PROGRESS' | 'RESOLVED' | 'CLOSED' | null;
|
|
315
|
+
/** Present on a severity change. */
|
|
316
|
+
priority: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' | null;
|
|
317
|
+
/**
|
|
318
|
+
* Present on an assignment change: whether it now has an owner. Never who —
|
|
319
|
+
* a workspace's staff are not part of what was reported.
|
|
320
|
+
*/
|
|
321
|
+
assigned: boolean | null;
|
|
322
|
+
/** Present on a reply the team chose to share. Internal comments never appear. */
|
|
323
|
+
reply: ReportMessage | null;
|
|
324
|
+
/**
|
|
325
|
+
* Present on an assignment when the report may be talked through. An offer,
|
|
326
|
+
* never a call: `startCall` is what starts one.
|
|
327
|
+
*/
|
|
328
|
+
call: { offered: boolean } | null;
|
|
329
|
+
at: string;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** A file on a message: an image, a screen recording, a voice note, a document. */
|
|
333
|
+
export interface MessageAttachment {
|
|
334
|
+
id: string;
|
|
335
|
+
type: 'SCREENSHOT' | 'VIDEO' | 'AUDIO' | 'FILE';
|
|
336
|
+
mime: string;
|
|
337
|
+
/** As the sender's machine had it, when they had one. */
|
|
338
|
+
name: string | null;
|
|
339
|
+
/** Bytes, as reported at upload. Advisory. */
|
|
340
|
+
size: number | null;
|
|
341
|
+
url: string;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** What was said, and what came with it. */
|
|
345
|
+
export interface ReportMessage {
|
|
346
|
+
body: string;
|
|
347
|
+
/** Who signed it. Null on the reporter's own messages — that is "You". */
|
|
348
|
+
author: string | null;
|
|
349
|
+
attachments?: MessageAttachment[];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* One step in a report's history, in the same vocabulary as ReportUpdate.
|
|
354
|
+
*
|
|
355
|
+
* Entries carrying `from` are messages in the conversation; the rest are
|
|
356
|
+
* things that happened to the report.
|
|
357
|
+
*/
|
|
358
|
+
export interface ReportTimelineEntry {
|
|
359
|
+
id: string;
|
|
360
|
+
kind: 'filed' | 'message' | ReportUpdateKind;
|
|
361
|
+
summary: string;
|
|
362
|
+
at: string;
|
|
363
|
+
status?: string;
|
|
364
|
+
priority?: string;
|
|
365
|
+
assigned?: boolean;
|
|
366
|
+
reply?: ReportMessage;
|
|
367
|
+
/** Which side wrote it. Absent on entries that are not messages. */
|
|
368
|
+
from?: 'team' | 'reporter';
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export interface ReportDetail {
|
|
372
|
+
id: string;
|
|
373
|
+
reference: string;
|
|
374
|
+
title: string;
|
|
375
|
+
description: string | null;
|
|
376
|
+
status: 'OPEN' | 'IN_PROGRESS' | 'RESOLVED' | 'CLOSED';
|
|
377
|
+
priority: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
378
|
+
url: string | null;
|
|
379
|
+
tags: string[];
|
|
380
|
+
createdAt: string;
|
|
381
|
+
updatedAt: string;
|
|
382
|
+
project: string;
|
|
383
|
+
workspace: string;
|
|
384
|
+
attachments: { type: string; mime: string; createdAt: string; url: string | null }[];
|
|
385
|
+
/** Every step from filing to now, oldest first, both sides' messages included. */
|
|
386
|
+
timeline: ReportTimelineEntry[];
|
|
387
|
+
/**
|
|
388
|
+
* The same figure they were given when they filed it, so coming back to check
|
|
389
|
+
* is not a worse experience than not checking. Null on a settled report — the
|
|
390
|
+
* question stops being asked once it is answered.
|
|
391
|
+
*/
|
|
392
|
+
estimate?: ReportEstimate | null;
|
|
393
|
+
/**
|
|
394
|
+
* Whether this reader can write back — true for the SDK, which holds a watch
|
|
395
|
+
* token. The widget hides its composer when false rather than offering a box
|
|
396
|
+
* whose send button would fail.
|
|
397
|
+
*/
|
|
398
|
+
canReply: boolean;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* What a project asks of the person reporting.
|
|
403
|
+
*
|
|
404
|
+
* 'off' — no sign-in. An address only reaches a report because
|
|
405
|
+
* identify() supplied one.
|
|
406
|
+
* 'optional' — the widget offers a sign-in that may be skipped.
|
|
407
|
+
* 'required' — nothing can be filed until they have signed in.
|
|
408
|
+
*
|
|
409
|
+
* A project's own setting, changed in BugCatch under Project Settings →
|
|
410
|
+
* General. It is not a config option here: a widget that could opt out of a
|
|
411
|
+
* required sign-in would not be a requirement.
|
|
412
|
+
*/
|
|
413
|
+
export type SdkLoginMode = 'off' | 'optional' | 'required';
|
|
414
|
+
|
|
415
|
+
export interface SdkLoginState {
|
|
416
|
+
mode: SdkLoginMode;
|
|
417
|
+
/** Who this browser is signed in as on this project, or null. */
|
|
418
|
+
signedIn: { email: string; name: string | null } | null;
|
|
419
|
+
/**
|
|
420
|
+
* Why a session that was held is no longer good — expired, or ended by this
|
|
421
|
+
* address signing in somewhere else. Null in every ordinary case, including
|
|
422
|
+
* having never signed in.
|
|
423
|
+
*/
|
|
424
|
+
signedOutReason: string | null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export declare class BugCatchClient {
|
|
428
|
+
init(config: BugCatchConfig): this;
|
|
429
|
+
/**
|
|
430
|
+
* Listen for updates on this browser's reports. Returns an unsubscribe.
|
|
431
|
+
* Several listeners can coexist, unlike the single `onUpdate` config hook.
|
|
432
|
+
*/
|
|
433
|
+
on(event: 'update', handler: (update: ReportUpdate) => void): () => void;
|
|
434
|
+
on(
|
|
435
|
+
event: 'call',
|
|
436
|
+
handler: (detail: {
|
|
437
|
+
bugId: string; state: CallState | LiveCallState; live?: boolean; reason?: string;
|
|
438
|
+
}) => void,
|
|
439
|
+
): () => void;
|
|
440
|
+
on(event: 'call-transcript', handler: (line: CallTranscriptLine) => void): () => void;
|
|
441
|
+
/**
|
|
442
|
+
* A call the team placed to this page, as it moves. Separate from `call`,
|
|
443
|
+
* which is the conversation once one is connected — this is the telephone
|
|
444
|
+
* before anybody picks it up.
|
|
445
|
+
*/
|
|
446
|
+
on(
|
|
447
|
+
event: 'incoming-call',
|
|
448
|
+
handler: (detail: {
|
|
449
|
+
bugId: string; callId: string; state: IncomingCallState; error?: Error;
|
|
450
|
+
}) => void,
|
|
451
|
+
): () => void;
|
|
452
|
+
/**
|
|
453
|
+
* How much is waiting to be read, after every arrival and every mark-read.
|
|
454
|
+
* The number for your own bell, whether or not the built-in cards are on.
|
|
455
|
+
*/
|
|
456
|
+
on(
|
|
457
|
+
event: 'unread',
|
|
458
|
+
handler: (state: { count: number; updates: ReportUpdate[] }) => void,
|
|
459
|
+
): () => void;
|
|
460
|
+
/** Someone signed in through the widget, or through signIn()/confirmSignIn(). */
|
|
461
|
+
on(
|
|
462
|
+
event: 'signin',
|
|
463
|
+
handler: (who: { email: string; name: string | null }) => void,
|
|
464
|
+
): () => void;
|
|
465
|
+
/** Someone signed out. `email` is who it was, where this browser knew. */
|
|
466
|
+
on(event: 'signout', handler: (who: { email: string | null }) => void): () => void;
|
|
467
|
+
off(event: 'update', handler: (update: ReportUpdate) => void): this;
|
|
468
|
+
off(
|
|
469
|
+
event: 'call' | 'call-transcript' | 'unread' | 'incoming-call' | 'signin' | 'signout',
|
|
470
|
+
handler: (payload: any) => void,
|
|
471
|
+
): this;
|
|
472
|
+
/**
|
|
473
|
+
* What this project asks of the reporter, and who this browser is signed in
|
|
474
|
+
* as. The built-in widget asks this itself every time it opens; it is here
|
|
475
|
+
* for integrations running with `widget: false`.
|
|
476
|
+
*/
|
|
477
|
+
loginState(): Promise<SdkLoginState>;
|
|
478
|
+
/**
|
|
479
|
+
* Who this browser is signed in as, from storage alone — no request, and
|
|
480
|
+
* therefore only as current as the last one. A session ended elsewhere still
|
|
481
|
+
* reads as signed in here until something asks. Use loginState() where that
|
|
482
|
+
* matters.
|
|
483
|
+
*/
|
|
484
|
+
signedInAs(): { email: string; name: string | null } | null;
|
|
485
|
+
/**
|
|
486
|
+
* Step one of signing in: mail a code to any address. There is no list of
|
|
487
|
+
* people who may report a bug — the code is what proves the address, and
|
|
488
|
+
* proving it is all a reply-to needs.
|
|
489
|
+
*/
|
|
490
|
+
signIn(email: string, options?: { name?: string }): Promise<{ sent: true; email: string }>;
|
|
491
|
+
/**
|
|
492
|
+
* Step two: spend the code. Signs this address out of this project in every
|
|
493
|
+
* other browser — one live session per address per project.
|
|
494
|
+
*/
|
|
495
|
+
confirmSignIn(email: string, code: string): Promise<{ email: string; name: string | null }>;
|
|
496
|
+
/**
|
|
497
|
+
* Sign out of this project, here and on the server, so the token cannot be
|
|
498
|
+
* replayed from a copy of this browser's storage.
|
|
499
|
+
*/
|
|
500
|
+
signOut(): Promise<{ signedOut: boolean }>;
|
|
501
|
+
/**
|
|
502
|
+
* Updates nobody has read yet, oldest first. Survives a reload — the stream
|
|
503
|
+
* replays nothing when it reconnects, so an update that arrived while the tab
|
|
504
|
+
* was closed would otherwise be lost.
|
|
505
|
+
*/
|
|
506
|
+
unreadUpdates(): ReportUpdate[];
|
|
507
|
+
/**
|
|
508
|
+
* Mark one update read: it stops being shown, now and on the next load.
|
|
509
|
+
* Read is something the reporter says rather than something a timer decides,
|
|
510
|
+
* so an integration rendering its own notifications has to say when.
|
|
511
|
+
*/
|
|
512
|
+
markUpdateRead(id: string): this;
|
|
513
|
+
/**
|
|
514
|
+
* Mark everything about one report read. Opening the report is reading its
|
|
515
|
+
* updates — the built-in widget calls this when a thread opens.
|
|
516
|
+
*/
|
|
517
|
+
markReportRead(bugId: string): this;
|
|
518
|
+
/** Mark everything read. */
|
|
519
|
+
markUpdatesRead(): this;
|
|
520
|
+
/**
|
|
521
|
+
* Whether a report can be talked through right now: false without
|
|
522
|
+
* sphoro.voice configured, on a workspace that has not switched calling on,
|
|
523
|
+
* and on a report that has had its allowance of calls.
|
|
524
|
+
*/
|
|
525
|
+
canCall(bugId: string): Promise<boolean>;
|
|
526
|
+
/**
|
|
527
|
+
* Whether this visitor can report something by talking about it instead of
|
|
528
|
+
* filling the form in. False unless the project has calling configured *and*
|
|
529
|
+
* `identify()` has supplied an email — a call with nobody to attribute has no
|
|
530
|
+
* way to follow up afterwards.
|
|
531
|
+
*/
|
|
532
|
+
canCallNow(): Promise<boolean>;
|
|
533
|
+
/**
|
|
534
|
+
* Which kind of call this visitor would get, or null for none.
|
|
535
|
+
*
|
|
536
|
+
* `'agent'` is the triage agent: a model answers instantly and asks the
|
|
537
|
+
* questions the form did not. `'live'` rings the team and a colleague picks
|
|
538
|
+
* up. A deployment offers one or the other and never both — the agent behind
|
|
539
|
+
* it has exactly one mode — so this is what to word a button from.
|
|
540
|
+
*
|
|
541
|
+
* Remembered for the life of the page: it describes the deployment's
|
|
542
|
+
* configuration, not this visitor.
|
|
543
|
+
*/
|
|
544
|
+
callKind(): Promise<CallKind | null>;
|
|
545
|
+
/**
|
|
546
|
+
* Talk a report through. Asks for the microphone, places the call, and
|
|
547
|
+
* resolves once it is up.
|
|
548
|
+
*
|
|
549
|
+
* Lands on whichever kind of call this deployment offers, which is decided by
|
|
550
|
+
* the mode of its agent rather than by the method you called: against a
|
|
551
|
+
* person-to-person agent this rings the team, and the handle comes back with
|
|
552
|
+
* `live: true`. Ask `callKind()` first if the wording around your own button
|
|
553
|
+
* has to say which.
|
|
554
|
+
*
|
|
555
|
+
* Rejects if the microphone is refused, if calling is not available on this
|
|
556
|
+
* report, or if a call is already running — there is deliberately no way to
|
|
557
|
+
* start a second.
|
|
558
|
+
*/
|
|
559
|
+
startCall(bugId: string): Promise<CallHandle | LiveCallHandle>;
|
|
560
|
+
/**
|
|
561
|
+
* The same, with no report yet: files one from the captured context and calls
|
|
562
|
+
* about it. The handle carries the id it was filed as. A rejection with
|
|
563
|
+
* `.filed` set means the report was written and only the call failed.
|
|
564
|
+
*/
|
|
565
|
+
startCall(): Promise<CallHandle | LiveCallHandle>;
|
|
566
|
+
/**
|
|
567
|
+
* Whether this report can be talked through with a person on the team.
|
|
568
|
+
*
|
|
569
|
+
* Never true at the same time as `canCall`: one agent has one mode, so a
|
|
570
|
+
* deployment answers for the triage agent or for a live call, not both.
|
|
571
|
+
*/
|
|
572
|
+
canCallTeam(bugId: string): Promise<boolean>;
|
|
573
|
+
/**
|
|
574
|
+
* Ring the team about a report, and hand back the call.
|
|
575
|
+
*
|
|
576
|
+
* Resolves once it is *ringing*, not once somebody answers — follow the rest
|
|
577
|
+
* through `onCallState` or the `call` event. The audio goes directly between
|
|
578
|
+
* the two browsers over WebRTC, so a live call is not recorded and not
|
|
579
|
+
* transcribed; the report's thread gets a note saying it happened.
|
|
580
|
+
*
|
|
581
|
+
* A report id is required, unlike `startCall`: ringing a room about a report
|
|
582
|
+
* nobody can read would ask somebody to answer a call they know nothing
|
|
583
|
+
* about.
|
|
584
|
+
*
|
|
585
|
+
* Rejects if the microphone is refused, if live calling is not available on
|
|
586
|
+
* this report, or if a call of either kind is already running — this page has
|
|
587
|
+
* one microphone.
|
|
588
|
+
*/
|
|
589
|
+
startLiveCall(bugId: string): Promise<LiveCallHandle>;
|
|
590
|
+
/**
|
|
591
|
+
* End the call in progress, of either kind. Does nothing when there is not
|
|
592
|
+
* one — there is a single slot, because there is a single microphone.
|
|
593
|
+
*/
|
|
594
|
+
endCall(): this;
|
|
595
|
+
/** One report with its full timeline, or null if this browser cannot prove it filed it. */
|
|
596
|
+
myReport(bugId: string): Promise<ReportDetail | null>;
|
|
597
|
+
/**
|
|
598
|
+
* Write back on a report this browser filed. The widget's own conversation
|
|
599
|
+
* view uses this; it is public so an integration rendering its own can too.
|
|
600
|
+
*
|
|
601
|
+
* Body or attachments — at least one, or the API refuses it.
|
|
602
|
+
*/
|
|
603
|
+
replyToReport(
|
|
604
|
+
bugId: string,
|
|
605
|
+
message: {
|
|
606
|
+
body?: string;
|
|
607
|
+
attachments?: {
|
|
608
|
+
storageKey: string;
|
|
609
|
+
mime: MessageMime;
|
|
610
|
+
name?: string;
|
|
611
|
+
size?: number;
|
|
612
|
+
}[];
|
|
613
|
+
},
|
|
614
|
+
): Promise<{
|
|
615
|
+
id: string;
|
|
616
|
+
from: 'reporter';
|
|
617
|
+
body: string;
|
|
618
|
+
attachments: MessageAttachment[];
|
|
619
|
+
at: string;
|
|
620
|
+
}>;
|
|
621
|
+
/**
|
|
622
|
+
* Attach the signed-in user. `email` threads their reports together in the
|
|
623
|
+
* dashboard and is where the "your issue is resolved" email goes — your app
|
|
624
|
+
* has already authenticated them, so the address is treated as reachable.
|
|
625
|
+
* Pass null on sign-out.
|
|
626
|
+
*/
|
|
627
|
+
identify(user: BugCatchUser | null): this;
|
|
628
|
+
report(input: ReportInput): Promise<ReportResult | { cancelled: true }>;
|
|
629
|
+
/** Uploads straight to storage and resolves the key a report references. */
|
|
630
|
+
uploadAttachment(
|
|
631
|
+
blob: Blob,
|
|
632
|
+
mime?: string,
|
|
633
|
+
onProgress?: (fraction: number) => void,
|
|
634
|
+
): Promise<{ storageKey: string; mime: AttachmentMime }>;
|
|
635
|
+
/**
|
|
636
|
+
* What this project is already known to be having trouble with, most
|
|
637
|
+
* affected first.
|
|
638
|
+
*
|
|
639
|
+
* The built-in widget asks this itself when it opens; it is here for an
|
|
640
|
+
* integration running with `widget: false`. Resolves to an empty list on
|
|
641
|
+
* every project that has not switched the feature on, which is the default —
|
|
642
|
+
* draw nothing rather than an empty heading.
|
|
643
|
+
*/
|
|
644
|
+
knownIssues(): Promise<KnownIssue[]>;
|
|
645
|
+
/**
|
|
646
|
+
* The known issues that look like what somebody is describing.
|
|
647
|
+
*
|
|
648
|
+
* Pass an AbortSignal when calling this on a keystroke: without one a fast
|
|
649
|
+
* typist renders whichever response happens to land last, which is not
|
|
650
|
+
* necessarily the one matching what is now in the box.
|
|
651
|
+
*/
|
|
652
|
+
matchKnownIssues(query: string, options?: { signal?: AbortSignal }): Promise<KnownIssue[]>;
|
|
653
|
+
/**
|
|
654
|
+
* Say a known issue is happening to you too, instead of filing a second
|
|
655
|
+
* report about it.
|
|
656
|
+
*
|
|
657
|
+
* Sends this browser's console, network and environment, so the team gets one
|
|
658
|
+
* report carrying everybody's environment rather than fourteen reports of the
|
|
659
|
+
* same thing. Everyone who joins is told when it is resolved, exactly as the
|
|
660
|
+
* person who filed it is.
|
|
661
|
+
*
|
|
662
|
+
* Needs an address, which is what that promise rests on: a signed-in widget
|
|
663
|
+
* has one and `identify()` supplies one otherwise, so pass `email` only when
|
|
664
|
+
* neither did. Rejects when there is none.
|
|
665
|
+
*/
|
|
666
|
+
meToo(bugId: string, who?: { email?: string; name?: string }): Promise<JoinedIssue>;
|
|
667
|
+
snapshot(): { console: any[]; network: any[]; environment: Record<string, any> };
|
|
668
|
+
/**
|
|
669
|
+
* Everything this browser has reported, newest first, with each one's
|
|
670
|
+
* status. Resolves to an empty history when nothing has been filed here.
|
|
671
|
+
*/
|
|
672
|
+
myReports(): Promise<{ reporter: { email: string; name: string | null } | null; reports: MyReport[] }>;
|
|
673
|
+
/** True when this browser has filed something — for hiding an entry point. */
|
|
674
|
+
hasReports(): boolean;
|
|
675
|
+
/** Open the update stream by hand. Idempotent; init() and report() call it. */
|
|
676
|
+
startWatching(): this;
|
|
677
|
+
/** Forget this browser's reports — call on sign-out. */
|
|
678
|
+
forgetReports(): this;
|
|
679
|
+
clear(): void;
|
|
680
|
+
destroy(): void;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
declare const BugCatch: BugCatchClient & { Client: typeof BugCatchClient };
|
|
684
|
+
export default BugCatch;
|