@burdenoff/website-sdk 2026.1102.3 → 2026.1104.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +46 -6
  2. package/dist/avatar-live/index.d.mts +2 -2
  3. package/dist/avatar-live/index.d.ts +2 -2
  4. package/dist/avatar-live/index.js +29 -4
  5. package/dist/avatar-live/index.js.map +1 -1
  6. package/dist/avatar-live/index.mjs +29 -4
  7. package/dist/avatar-live/index.mjs.map +1 -1
  8. package/dist/avatar-live-view/index.d.mts +19 -4
  9. package/dist/avatar-live-view/index.d.ts +19 -4
  10. package/dist/avatar-live-view/index.js +135 -28
  11. package/dist/avatar-live-view/index.js.map +1 -1
  12. package/dist/avatar-live-view/index.mjs +130 -23
  13. package/dist/avatar-live-view/index.mjs.map +1 -1
  14. package/dist/{chunk-DZLUZKOX.js → chunk-22OT6E26.js} +44 -12
  15. package/dist/chunk-22OT6E26.js.map +1 -0
  16. package/dist/{chunk-ZWEEBHXJ.mjs → chunk-5JQGE3XI.mjs} +44 -12
  17. package/dist/chunk-5JQGE3XI.mjs.map +1 -0
  18. package/dist/{chunk-K3QZGJEH.mjs → chunk-7MSB3EUI.mjs} +4 -3
  19. package/dist/chunk-7MSB3EUI.mjs.map +1 -0
  20. package/dist/{chunk-7O2I32ZN.js → chunk-WSEQ2I6I.js} +4 -3
  21. package/dist/chunk-WSEQ2I6I.js.map +1 -0
  22. package/dist/components/explore.d.mts +1 -1
  23. package/dist/components/explore.d.ts +1 -1
  24. package/dist/components/explore.js +13 -13
  25. package/dist/components/explore.mjs +1 -1
  26. package/dist/{explore-DtSPdlEy.d.ts → explore-BV4dRwoC.d.ts} +7 -0
  27. package/dist/{explore-DaSNdm4O.d.mts → explore-BgEeGPvY.d.mts} +7 -0
  28. package/dist/{explore-avatar-live-config-N4WhJzgS.d.mts → explore-avatar-live-config-B6_p0q1c.d.mts} +14 -1
  29. package/dist/{explore-avatar-live-config-CAfN5eAX.d.ts → explore-avatar-live-config-DRpS-mqQ.d.ts} +14 -1
  30. package/dist/index.d.mts +13 -7
  31. package/dist/index.d.ts +13 -7
  32. package/dist/index.js +59 -48
  33. package/dist/index.js.map +1 -1
  34. package/dist/index.mjs +16 -5
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/{types-CndbmBK-.d.mts → types-CXqaLlNB.d.mts} +14 -1
  37. package/dist/{types-CndbmBK-.d.ts → types-CXqaLlNB.d.ts} +14 -1
  38. package/package.json +1 -1
  39. package/dist/chunk-7O2I32ZN.js.map +0 -1
  40. package/dist/chunk-DZLUZKOX.js.map +0 -1
  41. package/dist/chunk-K3QZGJEH.mjs.map +0 -1
  42. package/dist/chunk-ZWEEBHXJ.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunk7O2I32ZN_js = require('../chunk-7O2I32ZN.js');
3
+ var chunkWSEQ2I6I_js = require('../chunk-WSEQ2I6I.js');
4
4
  var chunkO4GS3XPZ_js = require('../chunk-O4GS3XPZ.js');
5
5
  var chunkC6OJIHNO_js = require('../chunk-C6OJIHNO.js');
6
6
  require('../chunk-ZSTFX7FV.js');
@@ -68,7 +68,7 @@ function readAvailability(status, body) {
68
68
  const { consentVersion, maxSessionSeconds } = body;
69
69
  const botCheck = readBotCheck(body.botCheck);
70
70
  const questions = readQuestions(body.questions);
71
- if (available !== true || typeof consentVersion !== "string" || !chunk7O2I32ZN_js.isAvatarVersion(consentVersion) || typeof maxSessionSeconds !== "number" || !Number.isInteger(maxSessionSeconds) || maxSessionSeconds < 1 || botCheck === null || questions === null) {
71
+ if (available !== true || typeof consentVersion !== "string" || !chunkWSEQ2I6I_js.isAvatarVersion(consentVersion) || typeof maxSessionSeconds !== "number" || !Number.isInteger(maxSessionSeconds) || maxSessionSeconds < 1 || botCheck === null || questions === null) {
72
72
  return INERT;
73
73
  }
74
74
  return {
@@ -160,7 +160,7 @@ function deadline(timeoutMs, outer) {
160
160
  };
161
161
  }
162
162
  async function requestAvailability(target) {
163
- if (!chunk7O2I32ZN_js.isAvatarSite(target.site) || !LOCALE.test(target.locale)) return INERT;
163
+ if (!chunkWSEQ2I6I_js.isAvatarSite(target.site) || !LOCALE.test(target.locale)) return INERT;
164
164
  const query = new URLSearchParams({
165
165
  site: target.site,
166
166
  locale: target.locale
@@ -737,7 +737,7 @@ var CONSENT = /* @__PURE__ */ new Map([
737
737
  ]
738
738
  ]);
739
739
  function avatarConsentCopy(version, locale) {
740
- if (!chunk7O2I32ZN_js.isAvatarLiveLocaleReviewed(locale)) return null;
740
+ if (!chunkWSEQ2I6I_js.isAvatarLiveLocaleReviewed(locale)) return null;
741
741
  return CONSENT.get(version)?.[locale] ?? null;
742
742
  }
743
743
 
@@ -859,6 +859,8 @@ var PRIVACY_HREF = "/privacy#live-avatar";
859
859
  var SPEAKER_WAIT_MS = 3e4;
860
860
  var END_GRACE_MS = 1e4;
861
861
  var CLOCK_TOLERANCE_MS = 5e3;
862
+ var UNHEARD_GRACE_MS = 5e3;
863
+ var PLAYOUT_TAIL_MS = 5e3;
862
864
  var SERVICE_MAX_AGE_MS = 15 * 6e4;
863
865
  var ANSWER_OFFER_MS = SERVICE_MAX_AGE_MS - 6e4;
864
866
  var MAX_TIMER_MS = 2147483647;
@@ -868,6 +870,12 @@ var USED_ON_KEY = "boff.explore.avatarUsedOn";
868
870
  var PRIMARY = '[data-avatar-primary], [data-boff-explore="panel-continue-chat"]';
869
871
  var IDLE = { name: "idle" };
870
872
  var INERT2 = { name: "inert" };
873
+ function stopAttending(handle) {
874
+ if (handle.unheard !== null) clearTimeout(handle.unheard);
875
+ if (handle.tail !== null) clearTimeout(handle.tail);
876
+ handle.unheard = null;
877
+ handle.tail = null;
878
+ }
871
879
  var REFUSAL_TEXT = {
872
880
  busy: "busy",
873
881
  limits: "limits",
@@ -1026,11 +1034,13 @@ function clock(seconds) {
1026
1034
  function AvatarLiveView({
1027
1035
  live,
1028
1036
  active,
1037
+ open,
1029
1038
  locale,
1030
1039
  productName,
1031
1040
  still,
1032
1041
  fallback,
1033
1042
  onContinueInChat,
1043
+ onVoiceHold,
1034
1044
  continueInChat,
1035
1045
  newChat,
1036
1046
  send,
@@ -1078,9 +1088,19 @@ function AvatarLiveView({
1078
1088
  const askMaxLength = chatMaxLength !== void 0 && Number.isFinite(chatMaxLength) && chatMaxLength >= 1 ? Math.min(ASK_MAX_LENGTH, Math.floor(chatMaxLength)) : ASK_MAX_LENGTH;
1079
1089
  const [note, setNote] = react.useState(null);
1080
1090
  const [usedToday, setUsedToday] = react.useState(false);
1091
+ const showing = active && !off;
1092
+ const behindChat = open && !active && !off;
1081
1093
  const phaseRef = react.useRef(IDLE);
1082
- const latest = react.useRef({ live, locale, base, headers, off, currentPath });
1083
- latest.current = { live, locale, base, headers, off, currentPath };
1094
+ const latest = react.useRef({
1095
+ live,
1096
+ locale,
1097
+ base,
1098
+ headers,
1099
+ off,
1100
+ currentPath,
1101
+ showing
1102
+ });
1103
+ latest.current = { live, locale, base, headers, off, currentPath, showing };
1084
1104
  const conversationRef = react.useRef(conversation);
1085
1105
  conversationRef.current = conversation;
1086
1106
  const waitRef = react.useRef(null);
@@ -1099,6 +1119,10 @@ function AvatarLiveView({
1099
1119
  phaseRef.current = next;
1100
1120
  setPhase(next);
1101
1121
  }, []);
1122
+ const inSight = react.useCallback(
1123
+ () => latest.current.showing && (typeof document === "undefined" || document.visibilityState !== "hidden"),
1124
+ []
1125
+ );
1102
1126
  const setMode = react.useCallback((next) => {
1103
1127
  questionsRef.current = next;
1104
1128
  setQuestions(next);
@@ -1135,6 +1159,7 @@ function AvatarLiveView({
1135
1159
  const handle = sessionRef.current;
1136
1160
  if (!handle || handle.done) return;
1137
1161
  handle.done = true;
1162
+ stopAttending(handle);
1138
1163
  sessionRef.current = null;
1139
1164
  if (reason !== null) {
1140
1165
  sendEnd({
@@ -1153,6 +1178,44 @@ function AvatarLiveView({
1153
1178
  },
1154
1179
  [go]
1155
1180
  );
1181
+ const goLive = react.useCallback(() => {
1182
+ const current = phaseRef.current;
1183
+ const handle = sessionRef.current;
1184
+ if (current.name !== "pending" || !handle) return;
1185
+ handle.speakerArrived = true;
1186
+ if (questionsRef.current === "any") {
1187
+ noteUsedToday();
1188
+ setUsedToday(true);
1189
+ }
1190
+ go({ name: "live", session: current.session });
1191
+ }, [go]);
1192
+ const attend = react.useCallback(() => {
1193
+ const handle = sessionRef.current;
1194
+ if (!handle || handle.done || latest.current.live.endUnattended !== true) {
1195
+ return;
1196
+ }
1197
+ const current = phaseRef.current.name;
1198
+ if (current !== "pending" && current !== "live") return;
1199
+ const away = !inSight();
1200
+ if (away && handle.playedOut && current === "live") {
1201
+ finish("finished", "user");
1202
+ return;
1203
+ }
1204
+ if (!(away && handle.soundHeld && handle.voiceArrived)) {
1205
+ if (handle.unheard !== null) {
1206
+ clearTimeout(handle.unheard);
1207
+ handle.unheard = null;
1208
+ }
1209
+ return;
1210
+ }
1211
+ if (handle.unheard !== null) return;
1212
+ handle.unheard = setTimeout(() => {
1213
+ handle.unheard = null;
1214
+ if (sessionRef.current === handle && !handle.done && latest.current.live.endUnattended === true && handle.soundHeld && handle.voiceArrived && !inSight()) {
1215
+ finish("interrupted", "user");
1216
+ }
1217
+ }, UNHEARD_GRACE_MS);
1218
+ }, [finish, inSight]);
1156
1219
  const cancelStart = react.useCallback((reason) => {
1157
1220
  const run = startRef.current;
1158
1221
  if (!run) return;
@@ -1180,7 +1243,7 @@ function AvatarLiveView({
1180
1243
  go(INERT2);
1181
1244
  return;
1182
1245
  }
1183
- if (result.offer.botCheck === "turnstile" && chunk7O2I32ZN_js.turnstileSiteKeyOf(live2) === null) {
1246
+ if (result.offer.botCheck === "turnstile" && chunkWSEQ2I6I_js.turnstileSiteKeyOf(live2) === null) {
1184
1247
  setMode("suggested");
1185
1248
  go(INERT2);
1186
1249
  return;
@@ -1229,6 +1292,11 @@ function AvatarLiveView({
1229
1292
  joined: null,
1230
1293
  endsAt,
1231
1294
  speakerArrived: false,
1295
+ voiceArrived: false,
1296
+ soundHeld: false,
1297
+ playedOut: false,
1298
+ unheard: null,
1299
+ tail: null,
1232
1300
  done: false
1233
1301
  };
1234
1302
  setBlocked(false);
@@ -1308,7 +1376,7 @@ function AvatarLiveView({
1308
1376
  return;
1309
1377
  }
1310
1378
  const refusedFrom = exchangeAtStart.offerUntil - ANSWER_OFFER_MS + SERVICE_MAX_AGE_MS;
1311
- const siteKey = offer.botCheck === "turnstile" ? chunk7O2I32ZN_js.turnstileSiteKeyOf(live2) : null;
1379
+ const siteKey = offer.botCheck === "turnstile" ? chunkWSEQ2I6I_js.turnstileSiteKeyOf(live2) : null;
1312
1380
  if (offer.botCheck === "turnstile" && (siteKey === null || !canBindBotCheck())) {
1313
1381
  go(INERT2);
1314
1382
  return;
@@ -1489,9 +1557,11 @@ function AvatarLiveView({
1489
1557
  }
1490
1558
  }, [cancelStart, cancelWaiting, finish, go, reoffer]);
1491
1559
  const leave = react.useCallback(
1492
- (reason) => {
1560
+ (event) => {
1493
1561
  const current = phaseRef.current;
1562
+ const reason = event === "user" || event === "chat" ? "user" : "page_hide";
1494
1563
  const how = reason === "user" ? "stopped" : "interrupted";
1564
+ const keepsSession = event === "chat" || event === "hidden";
1495
1565
  switch (current.name) {
1496
1566
  case "checking": {
1497
1567
  if (reason !== "user") return;
@@ -1509,26 +1579,36 @@ function AvatarLiveView({
1509
1579
  go(reason === "user" ? IDLE : reoffer(current.offer));
1510
1580
  return;
1511
1581
  case "creating":
1582
+ if (keepsSession) return;
1512
1583
  cancelStart(reason);
1513
1584
  go({ name: "ended", how });
1514
1585
  return;
1515
1586
  case "pending":
1516
1587
  case "live":
1588
+ if (keepsSession) {
1589
+ if (current.name === "pending" && sessionRef.current?.speakerArrived) {
1590
+ goLive();
1591
+ }
1592
+ return;
1593
+ }
1517
1594
  finish(how, reason);
1518
1595
  return;
1519
1596
  default:
1520
1597
  return;
1521
1598
  }
1522
1599
  },
1523
- [cancelStart, cancelWaiting, finish, go, reoffer]
1600
+ [cancelStart, cancelWaiting, finish, go, goLive, reoffer]
1524
1601
  );
1525
- const showing = active && !off;
1526
1602
  react.useEffect(() => {
1527
1603
  if (!showing) {
1528
- leave("user");
1604
+ leave(behindChat ? "chat" : "user");
1529
1605
  return;
1530
1606
  }
1531
1607
  const current = phaseRef.current;
1608
+ if (current.name === "creating" || current.name === "pending" || current.name === "live") {
1609
+ setNow(Date.now());
1610
+ return;
1611
+ }
1532
1612
  const offered = offeredNow();
1533
1613
  const used = offered !== null && claimedRef.current.has(offered);
1534
1614
  if (current.name === "ended" && used) return;
@@ -1541,7 +1621,10 @@ function AvatarLiveView({
1541
1621
  current.name === "checking" && checkRef.current === null) {
1542
1622
  check();
1543
1623
  }
1544
- }, [showing, check, leave, offeredNow]);
1624
+ }, [showing, behindChat, check, leave, offeredNow]);
1625
+ react.useEffect(() => {
1626
+ attend();
1627
+ }, [showing, attend]);
1545
1628
  react.useEffect(() => {
1546
1629
  if (askedLocale.current === locale) return;
1547
1630
  askedLocale.current = locale;
@@ -1628,10 +1711,29 @@ function AvatarLiveView({
1628
1711
  const wasLive = phaseRef.current.name === "live";
1629
1712
  switch (event.type) {
1630
1713
  case "speaker-track":
1631
- if (event.kind === "video") handle.speakerArrived = true;
1714
+ if (event.kind !== "video") {
1715
+ handle.voiceArrived = true;
1716
+ attend();
1717
+ return;
1718
+ }
1719
+ handle.speakerArrived = true;
1720
+ if (!inSight()) goLive();
1632
1721
  return;
1633
1722
  case "playback-blocked":
1634
1723
  setBlocked(event.blocked);
1724
+ handle.soundHeld = event.sound ?? event.blocked;
1725
+ attend();
1726
+ return;
1727
+ case "speech-ended":
1728
+ if (latest.current.live.endUnattended !== true || handle.tail !== null || handle.playedOut) {
1729
+ return;
1730
+ }
1731
+ handle.tail = setTimeout(() => {
1732
+ handle.tail = null;
1733
+ if (sessionRef.current !== handle || handle.done) return;
1734
+ handle.playedOut = true;
1735
+ attend();
1736
+ }, PLAYOUT_TAIL_MS);
1635
1737
  return;
1636
1738
  case "speaker-left":
1637
1739
  finish(
@@ -1645,7 +1747,7 @@ function AvatarLiveView({
1645
1747
  return;
1646
1748
  }
1647
1749
  },
1648
- [finish]
1750
+ [attend, finish, goLive, inSight]
1649
1751
  );
1650
1752
  react.useEffect(() => {
1651
1753
  if (phase.name !== "pending") return;
@@ -1719,11 +1821,25 @@ function AvatarLiveView({
1719
1821
  const timer = setInterval(() => setNow(Date.now()), 1e3);
1720
1822
  return () => clearInterval(timer);
1721
1823
  }, [ticking]);
1824
+ const voiceLine = phase.name === "live" ? S.speaking : phase.name === "creating" || phase.name === "pending" ? S.connecting : null;
1825
+ const onVoiceHoldRef = react.useRef(onVoiceHold);
1826
+ onVoiceHoldRef.current = onVoiceHold;
1827
+ react.useEffect(() => {
1828
+ onVoiceHoldRef.current?.(voiceLine);
1829
+ }, [voiceLine]);
1830
+ react.useEffect(() => () => onVoiceHoldRef.current?.(null), []);
1722
1831
  react.useEffect(() => {
1723
1832
  if (typeof window === "undefined") return;
1724
1833
  const onPageHide = () => leave("page_hide");
1725
1834
  const onVisibility = () => {
1726
- if (document.visibilityState === "hidden") leave("page_hide");
1835
+ if (document.visibilityState === "hidden") {
1836
+ leave("hidden");
1837
+ attend();
1838
+ return;
1839
+ }
1840
+ const current = phaseRef.current.name;
1841
+ if (current === "pending" || current === "live") setNow(Date.now());
1842
+ attend();
1727
1843
  };
1728
1844
  window.addEventListener("pagehide", onPageHide);
1729
1845
  document.addEventListener("visibilitychange", onVisibility);
@@ -1731,7 +1847,7 @@ function AvatarLiveView({
1731
1847
  window.removeEventListener("pagehide", onPageHide);
1732
1848
  document.removeEventListener("visibilitychange", onVisibility);
1733
1849
  };
1734
- }, [leave]);
1850
+ }, [attend, leave]);
1735
1851
  react.useEffect(
1736
1852
  () => () => {
1737
1853
  const controller = checkRef.current;
@@ -1750,6 +1866,7 @@ function AvatarLiveView({
1750
1866
  sessionRef.current = null;
1751
1867
  if (handle && !handle.done) {
1752
1868
  handle.done = true;
1869
+ stopAttending(handle);
1753
1870
  sendEnd({
1754
1871
  base: handle.base,
1755
1872
  headers: handle.headers,
@@ -1829,17 +1946,7 @@ function AvatarLiveView({
1829
1946
  if (focused && focused !== document.body && focused.isConnected) return;
1830
1947
  rootRef.current?.querySelector(PRIMARY)?.focus();
1831
1948
  }, [active, phase.name, askState, shown.pendingAnswerId, blocked]);
1832
- const onPlaying = react.useCallback(() => {
1833
- const current = phaseRef.current;
1834
- const handle = sessionRef.current;
1835
- if (current.name !== "pending" || !handle) return;
1836
- handle.speakerArrived = true;
1837
- if (questionsRef.current === "any") {
1838
- noteUsedToday();
1839
- setUsedToday(true);
1840
- }
1841
- go({ name: "live", session: current.session });
1842
- }, [go]);
1949
+ const onPlaying = goLive;
1843
1950
  const enablePlayback = react.useCallback(() => {
1844
1951
  void sessionRef.current?.joined?.enablePlayback().catch(() => void 0);
1845
1952
  }, []);