@sphoro/bugcatch-web-sdk 0.13.1 → 0.14.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/src/updates.js CHANGED
@@ -297,32 +297,51 @@ export async function callAvailableForProject({ apiBase, apiKey, projectId }) {
297
297
  }
298
298
 
299
299
  /**
300
- * Which kind of call this project can take: 'agent', 'live', or null for none.
300
+ * Which kind of call this project can take: 'live', or null for none.
301
301
  *
302
302
  * The same round trip as above, answering the question that actually matters.
303
- * A deployment offers one or the other and never both — a sphoro.voice agent has
304
- * exactly one mode, and it is either a model that answers or a rendezvous that
305
- * rings the team. Asking only "can it call" is what drew a triage button on a
306
- * deployment whose agent had been switched to person-to-person: pressing it filed
307
- * the report and then failed the call, so the reporter got a ticket they had not
308
- * asked for and nobody's dashboard rang.
303
+ * Asking only "can it call" is what drew a call button on a deployment that did
304
+ * not have one: pressing it filed the report and then failed the call, so the
305
+ * reporter got a ticket they had not asked for and nobody's dashboard rang.
309
306
  *
310
- * Null for every failure, and for an older API that answers without `kind` but
311
- * says it is available — that deployment is on the agent, because live calling
312
- * did not exist before the field did.
307
+ * There is one kind of call now — the team's dashboards ring and a colleague
308
+ * picks up — so this has one non-null answer. The shape is kept because callers
309
+ * are written against it and because a deployment answering something else is a
310
+ * deployment this SDK must not place a call on.
313
311
  */
314
312
  export async function callKindForProject({ apiBase, apiKey, projectId }) {
313
+ const kinds = await callKindsForProject({ apiBase, apiKey, projectId });
314
+ return kinds.live ? 'live' : null;
315
+ }
316
+
317
+ /**
318
+ * Which kinds of call this project can take, as booleans.
319
+ *
320
+ * `agent` is always false and is still here because the API still sends it: it
321
+ * was a triage agent that answered instantly instead of a person, and it is
322
+ * gone. Reading it rather than dropping it keeps this honest about what the
323
+ * response actually said.
324
+ *
325
+ * An older API answers `available` and a bare `kind` with no `kinds` object.
326
+ * Anything that is not 'live' there is a kind this SDK can no longer place, so
327
+ * it reads as no call at all rather than as a call that would fail on connect.
328
+ */
329
+ export async function callKindsForProject({ apiBase, apiKey, projectId }) {
330
+ const none = { agent: false, live: false };
315
331
  try {
316
332
  const res = await fetch(
317
333
  `${apiBase}/ingest/calls?projectId=${encodeURIComponent(projectId)}`,
318
334
  { headers: { 'X-BugCatch-Key': apiKey } },
319
335
  );
320
- if (!res.ok) return null;
336
+ if (!res.ok) return none;
321
337
  const body = await res.json();
322
- if (!body?.available) return null;
323
- return body.kind === 'live' || body.kind === 'agent' ? body.kind : 'agent';
338
+ if (!body?.available) return none;
339
+ if (body.kinds && typeof body.kinds === 'object') {
340
+ return { agent: false, live: Boolean(body.kinds.live) };
341
+ }
342
+ return { agent: false, live: body.kind === 'live' };
324
343
  } catch {
325
- return null;
344
+ return none;
326
345
  }
327
346
  }
328
347
 
@@ -369,7 +388,7 @@ export async function reportByCall({ apiBase, apiKey, token, payload }) {
369
388
  }
370
389
 
371
390
  /**
372
- * Whether this report can be talked through with the triage agent.
391
+ * Whether this report can be talked through with a person on the team.
373
392
  *
374
393
  * Asked before anything is rendered, so a button never appears on a deployment
375
394
  * with no sphoro.voice configured, on a workspace that has not switched calling
@@ -377,65 +396,6 @@ export async function reportByCall({ apiBase, apiKey, token, payload }) {
377
396
  * failure too: a call is an extra, and an extra that cannot be offered quietly
378
397
  * is better than an error about a feature nobody asked for.
379
398
  */
380
- export async function callAvailable({ apiBase, bugId }) {
381
- const token = readToken();
382
- if (!token) return false;
383
-
384
- try {
385
- const res = await fetch(
386
- `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/voice-call`,
387
- { headers: { Authorization: `Bearer ${token}` } },
388
- );
389
- if (!res.ok) return false;
390
- const body = await res.json();
391
- return Boolean(body?.offered);
392
- } catch {
393
- return false;
394
- }
395
- }
396
-
397
- /**
398
- * Ask for a call, and get back the socket to hold it on.
399
- *
400
- * The response carries a URL with a short-lived credential in it and nothing
401
- * else — no key ever reaches this page. Which is also why the URL is passed
402
- * straight to the caller and never logged or stored.
403
- */
404
- export async function requestCall({ apiBase, bugId }) {
405
- const token = readToken();
406
- if (!token) throw new Error('[BugCatch] nothing filed from this browser yet.');
407
-
408
- const res = await fetch(
409
- `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/voice-call`,
410
- { method: 'POST', headers: { Authorization: `Bearer ${token}` } },
411
- );
412
-
413
- if (res.status === 401) {
414
- clearToken();
415
- throw new Error('[BugCatch] this browser can no longer prove it filed that report.');
416
- }
417
- if (!res.ok) {
418
- // The API's own message is the one worth showing: "you have already talked
419
- // this report through" is something the reader can act on, and a status
420
- // code is not.
421
- let detail = '';
422
- try {
423
- const problem = await res.json();
424
- detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';
425
- } catch { /* non-JSON error */ }
426
- throw new Error(detail || `[BugCatch] the call could not be started (HTTP ${res.status}).`);
427
- }
428
- return res.json();
429
- }
430
-
431
- /**
432
- * Whether this report can be talked through with a *person*.
433
- *
434
- * A separate question from `callAvailable` and separately switched on, so the
435
- * widget cannot infer either answer from the other: a workspace may run the
436
- * triage agent, or a live line to its team, or both, or neither — and neither
437
- * is the common case.
438
- */
439
399
  export async function liveCallAvailable({ apiBase, bugId }) {
440
400
  const token = readToken();
441
401
  if (!token) return false;
@@ -1060,3 +1020,14 @@ async function readJoinError(res) {
1060
1020
  } catch { /* non-JSON error */ }
1061
1021
  return `Could not add you to this issue (HTTP ${res.status})`;
1062
1022
  }
1023
+
1024
+ export async function verifyFix({ apiBase, bugId, ...feedback }) {
1025
+ const token = readToken();
1026
+ if (!token) throw new Error('This browser cannot access that report.');
1027
+ const res = await fetch(`${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/verification`, {
1028
+ method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify(feedback),
1029
+ });
1030
+ const data = await res.json().catch(() => ({}));
1031
+ if (!res.ok) throw new Error(data.message || 'Could not record feedback. Please refresh and try again.');
1032
+ return data;
1033
+ }