@burdenoff/website-sdk 2026.1102.3 → 2026.1104.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/README.md +22 -6
- package/dist/avatar-live-view/index.d.mts +17 -2
- package/dist/avatar-live-view/index.d.ts +17 -2
- package/dist/avatar-live-view/index.js +64 -21
- package/dist/avatar-live-view/index.js.map +1 -1
- package/dist/avatar-live-view/index.mjs +64 -21
- package/dist/avatar-live-view/index.mjs.map +1 -1
- package/dist/{chunk-DZLUZKOX.js → chunk-22OT6E26.js} +44 -12
- package/dist/chunk-22OT6E26.js.map +1 -0
- package/dist/{chunk-ZWEEBHXJ.mjs → chunk-5JQGE3XI.mjs} +44 -12
- package/dist/chunk-5JQGE3XI.mjs.map +1 -0
- package/dist/components/explore.d.mts +1 -1
- package/dist/components/explore.d.ts +1 -1
- package/dist/components/explore.js +13 -13
- package/dist/components/explore.mjs +1 -1
- package/dist/{explore-DtSPdlEy.d.ts → explore-BV4dRwoC.d.ts} +7 -0
- package/dist/{explore-DaSNdm4O.d.mts → explore-BgEeGPvY.d.mts} +7 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +49 -41
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-DZLUZKOX.js.map +0 -1
- package/dist/chunk-ZWEEBHXJ.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -420,10 +420,24 @@ publishes audio, video or data, and it sends the avatar nothing to say.
|
|
|
420
420
|
limits, region, language, paused, a question the avatar cannot read) is one
|
|
421
421
|
short sentence and "Continue in Chat". A network or CORS failure, or a route
|
|
422
422
|
that is not there, shows the view as it was before.
|
|
423
|
-
- **Ending.** Stop,
|
|
424
|
-
|
|
423
|
+
- **Ending.** Stop, closing the panel, leaving the page (`pagehide`), and the
|
|
424
|
+
live avatar going off under a session (the panel's locale changing to one
|
|
425
425
|
whose strings have not been reviewed) all end the session with a `keepalive`
|
|
426
|
-
request and leave the room.
|
|
426
|
+
request and leave the room. Once the create has been sent (connecting,
|
|
427
|
+
joining, speaking), switching to the Chat tab (or pressing Continue in Chat)
|
|
428
|
+
and hiding the page (another browser tab, another app) do **not** end it: the
|
|
429
|
+
Avatar pane is only hidden, the room's video and audio stay attached, and the
|
|
430
|
+
avatar speaks to the end the server planned for it, which a hidden page
|
|
431
|
+
cannot extend. Coming back to the Avatar tab shows the session as it is, with
|
|
432
|
+
the countdown to the server's end, and asks nothing again. Before the create,
|
|
433
|
+
nothing paid exists, so the Chat tab still calls off an availability check, a
|
|
434
|
+
"Start when ready" and a bot check, and a hidden page still calls off a
|
|
435
|
+
"Start when ready" and returns a bot check to the consent card, all with
|
|
436
|
+
nothing created. While a session is created, joined or played, Chat's own
|
|
437
|
+
read-aloud and dictation are held (`ExplorePage`'s `voiceHold`): a reading
|
|
438
|
+
under way stops, and both controls stay where they are, `aria-disabled`, with
|
|
439
|
+
the avatar's status line as their title, until the session ends; nothing in
|
|
440
|
+
Chat ends the session. The end is a simple CORS request: its body is a
|
|
427
441
|
string and the SDK sets no `content-type`, so the browser sends it as
|
|
428
442
|
`text/plain;charset=UTF-8` (which the gateway accepts for this route), and on
|
|
429
443
|
production nothing else is added, so no preflight has to complete while the
|
|
@@ -510,9 +524,11 @@ publishes audio, video or data, and it sends the avatar nothing to say.
|
|
|
510
524
|
of the same host for the validation check.
|
|
511
525
|
- **Not verified in this package.** Every test here uses fakes: nothing reaches
|
|
512
526
|
Turnstile, the gateway, the avatar service or the media room. Left to the
|
|
513
|
-
alpha test: real playback on iPhone Safari and Android Chrome; that
|
|
514
|
-
|
|
515
|
-
|
|
527
|
+
alpha test: real playback on iPhone Safari and Android Chrome; that the voice
|
|
528
|
+
keeps playing behind the Chat tab and in a background browser tab there (a
|
|
529
|
+
browser may suspend it; the server still ends the session at its planned
|
|
530
|
+
end); that `page_hide` ends actually arrive from those browsers; and that the
|
|
531
|
+
receive-only token has `canPublish=false` and `canPublishData=false`, and a worker RPC
|
|
516
532
|
addressed to the viewer is dropped without disconnecting it (the media client
|
|
517
533
|
answers incoming RPC requests on its own, so "never publishes" holds only as
|
|
518
534
|
far as the token's permissions do).
|
|
@@ -9,6 +9,11 @@ interface AvatarLiveViewProps {
|
|
|
9
9
|
live: ExplorePanelAvatarLive;
|
|
10
10
|
/** The Avatar view is showing: the panel is open and its Avatar tab is selected. */
|
|
11
11
|
active: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* The panel is open, whichever tab it shows. With `active` it tells the Chat tab (open, not active: the pane is only
|
|
14
|
+
* hidden, and a session under way keeps playing) from the panel closing (not open: what is under way ends).
|
|
15
|
+
*/
|
|
16
|
+
open: boolean;
|
|
12
17
|
locale: ExploreLocale;
|
|
13
18
|
/** The product the panel speaks for, when it is one product's own; null on the portfolio's. */
|
|
14
19
|
productName: string | null;
|
|
@@ -19,8 +24,18 @@ interface AvatarLiveViewProps {
|
|
|
19
24
|
};
|
|
20
25
|
/** The Avatar tab as it was before the live avatar: shown whenever the live avatar is not offered or not reachable. */
|
|
21
26
|
fallback: ReactNode;
|
|
22
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Back to the Chat tab. Like the Chat tab itself, it calls off a start that has not been sent yet, and leaves a
|
|
29
|
+
* session under way speaking.
|
|
30
|
+
*/
|
|
23
31
|
onContinueInChat: () => void;
|
|
32
|
+
/**
|
|
33
|
+
* Told whenever the avatar's voice starts or stops being the page's: its status line while a session is being
|
|
34
|
+
* created, joined or played ("Connecting to the live avatar…", "The avatar is speaking."), and null otherwise (and
|
|
35
|
+
* when the view goes). The panel holds Chat's own read-aloud and dictation meanwhile, so the page never speaks in two
|
|
36
|
+
* voices and the microphone never takes down the avatar's.
|
|
37
|
+
*/
|
|
38
|
+
onVoiceHold?: (line: string | null) => void;
|
|
24
39
|
/** "Continue in Chat" in the panel's language: the panel's own word for it, so this chunk carries none of the panel's. */
|
|
25
40
|
continueInChat: string;
|
|
26
41
|
/**
|
|
@@ -51,6 +66,6 @@ interface AvatarLiveViewProps {
|
|
|
51
66
|
/** The panel's conversation, null when there is no provider. */
|
|
52
67
|
conversation: AvatarConversation | null;
|
|
53
68
|
}
|
|
54
|
-
declare function AvatarLiveView({ live, active, locale, productName, still, fallback, onContinueInChat, continueInChat, newChat, send, tryAsking, prompts, touchFirst, currentPath, off, base, headers, conversation, }: AvatarLiveViewProps): react.JSX.Element;
|
|
69
|
+
declare function AvatarLiveView({ live, active, open, locale, productName, still, fallback, onContinueInChat, onVoiceHold, continueInChat, newChat, send, tryAsking, prompts, touchFirst, currentPath, off, base, headers, conversation, }: AvatarLiveViewProps): react.JSX.Element;
|
|
55
70
|
|
|
56
71
|
export { AvatarLiveView, type AvatarLiveViewProps };
|
|
@@ -9,6 +9,11 @@ interface AvatarLiveViewProps {
|
|
|
9
9
|
live: ExplorePanelAvatarLive;
|
|
10
10
|
/** The Avatar view is showing: the panel is open and its Avatar tab is selected. */
|
|
11
11
|
active: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* The panel is open, whichever tab it shows. With `active` it tells the Chat tab (open, not active: the pane is only
|
|
14
|
+
* hidden, and a session under way keeps playing) from the panel closing (not open: what is under way ends).
|
|
15
|
+
*/
|
|
16
|
+
open: boolean;
|
|
12
17
|
locale: ExploreLocale;
|
|
13
18
|
/** The product the panel speaks for, when it is one product's own; null on the portfolio's. */
|
|
14
19
|
productName: string | null;
|
|
@@ -19,8 +24,18 @@ interface AvatarLiveViewProps {
|
|
|
19
24
|
};
|
|
20
25
|
/** The Avatar tab as it was before the live avatar: shown whenever the live avatar is not offered or not reachable. */
|
|
21
26
|
fallback: ReactNode;
|
|
22
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Back to the Chat tab. Like the Chat tab itself, it calls off a start that has not been sent yet, and leaves a
|
|
29
|
+
* session under way speaking.
|
|
30
|
+
*/
|
|
23
31
|
onContinueInChat: () => void;
|
|
32
|
+
/**
|
|
33
|
+
* Told whenever the avatar's voice starts or stops being the page's: its status line while a session is being
|
|
34
|
+
* created, joined or played ("Connecting to the live avatar…", "The avatar is speaking."), and null otherwise (and
|
|
35
|
+
* when the view goes). The panel holds Chat's own read-aloud and dictation meanwhile, so the page never speaks in two
|
|
36
|
+
* voices and the microphone never takes down the avatar's.
|
|
37
|
+
*/
|
|
38
|
+
onVoiceHold?: (line: string | null) => void;
|
|
24
39
|
/** "Continue in Chat" in the panel's language: the panel's own word for it, so this chunk carries none of the panel's. */
|
|
25
40
|
continueInChat: string;
|
|
26
41
|
/**
|
|
@@ -51,6 +66,6 @@ interface AvatarLiveViewProps {
|
|
|
51
66
|
/** The panel's conversation, null when there is no provider. */
|
|
52
67
|
conversation: AvatarConversation | null;
|
|
53
68
|
}
|
|
54
|
-
declare function AvatarLiveView({ live, active, locale, productName, still, fallback, onContinueInChat, continueInChat, newChat, send, tryAsking, prompts, touchFirst, currentPath, off, base, headers, conversation, }: AvatarLiveViewProps): react.JSX.Element;
|
|
69
|
+
declare function AvatarLiveView({ live, active, open, locale, productName, still, fallback, onContinueInChat, onVoiceHold, continueInChat, newChat, send, tryAsking, prompts, touchFirst, currentPath, off, base, headers, conversation, }: AvatarLiveViewProps): react.JSX.Element;
|
|
55
70
|
|
|
56
71
|
export { AvatarLiveView, type AvatarLiveViewProps };
|
|
@@ -1026,11 +1026,13 @@ function clock(seconds) {
|
|
|
1026
1026
|
function AvatarLiveView({
|
|
1027
1027
|
live,
|
|
1028
1028
|
active,
|
|
1029
|
+
open,
|
|
1029
1030
|
locale,
|
|
1030
1031
|
productName,
|
|
1031
1032
|
still,
|
|
1032
1033
|
fallback,
|
|
1033
1034
|
onContinueInChat,
|
|
1035
|
+
onVoiceHold,
|
|
1034
1036
|
continueInChat,
|
|
1035
1037
|
newChat,
|
|
1036
1038
|
send,
|
|
@@ -1078,9 +1080,19 @@ function AvatarLiveView({
|
|
|
1078
1080
|
const askMaxLength = chatMaxLength !== void 0 && Number.isFinite(chatMaxLength) && chatMaxLength >= 1 ? Math.min(ASK_MAX_LENGTH, Math.floor(chatMaxLength)) : ASK_MAX_LENGTH;
|
|
1079
1081
|
const [note, setNote] = react.useState(null);
|
|
1080
1082
|
const [usedToday, setUsedToday] = react.useState(false);
|
|
1083
|
+
const showing = active && !off;
|
|
1084
|
+
const behindChat = open && !active && !off;
|
|
1081
1085
|
const phaseRef = react.useRef(IDLE);
|
|
1082
|
-
const latest = react.useRef({
|
|
1083
|
-
|
|
1086
|
+
const latest = react.useRef({
|
|
1087
|
+
live,
|
|
1088
|
+
locale,
|
|
1089
|
+
base,
|
|
1090
|
+
headers,
|
|
1091
|
+
off,
|
|
1092
|
+
currentPath,
|
|
1093
|
+
showing
|
|
1094
|
+
});
|
|
1095
|
+
latest.current = { live, locale, base, headers, off, currentPath, showing };
|
|
1084
1096
|
const conversationRef = react.useRef(conversation);
|
|
1085
1097
|
conversationRef.current = conversation;
|
|
1086
1098
|
const waitRef = react.useRef(null);
|
|
@@ -1099,6 +1111,10 @@ function AvatarLiveView({
|
|
|
1099
1111
|
phaseRef.current = next;
|
|
1100
1112
|
setPhase(next);
|
|
1101
1113
|
}, []);
|
|
1114
|
+
const inSight = react.useCallback(
|
|
1115
|
+
() => latest.current.showing && (typeof document === "undefined" || document.visibilityState !== "hidden"),
|
|
1116
|
+
[]
|
|
1117
|
+
);
|
|
1102
1118
|
const setMode = react.useCallback((next) => {
|
|
1103
1119
|
questionsRef.current = next;
|
|
1104
1120
|
setQuestions(next);
|
|
@@ -1153,6 +1169,17 @@ function AvatarLiveView({
|
|
|
1153
1169
|
},
|
|
1154
1170
|
[go]
|
|
1155
1171
|
);
|
|
1172
|
+
const goLive = react.useCallback(() => {
|
|
1173
|
+
const current = phaseRef.current;
|
|
1174
|
+
const handle = sessionRef.current;
|
|
1175
|
+
if (current.name !== "pending" || !handle) return;
|
|
1176
|
+
handle.speakerArrived = true;
|
|
1177
|
+
if (questionsRef.current === "any") {
|
|
1178
|
+
noteUsedToday();
|
|
1179
|
+
setUsedToday(true);
|
|
1180
|
+
}
|
|
1181
|
+
go({ name: "live", session: current.session });
|
|
1182
|
+
}, [go]);
|
|
1156
1183
|
const cancelStart = react.useCallback((reason) => {
|
|
1157
1184
|
const run = startRef.current;
|
|
1158
1185
|
if (!run) return;
|
|
@@ -1489,9 +1516,11 @@ function AvatarLiveView({
|
|
|
1489
1516
|
}
|
|
1490
1517
|
}, [cancelStart, cancelWaiting, finish, go, reoffer]);
|
|
1491
1518
|
const leave = react.useCallback(
|
|
1492
|
-
(
|
|
1519
|
+
(event) => {
|
|
1493
1520
|
const current = phaseRef.current;
|
|
1521
|
+
const reason = event === "user" || event === "chat" ? "user" : "page_hide";
|
|
1494
1522
|
const how = reason === "user" ? "stopped" : "interrupted";
|
|
1523
|
+
const keepsSession = event === "chat" || event === "hidden";
|
|
1495
1524
|
switch (current.name) {
|
|
1496
1525
|
case "checking": {
|
|
1497
1526
|
if (reason !== "user") return;
|
|
@@ -1509,26 +1538,36 @@ function AvatarLiveView({
|
|
|
1509
1538
|
go(reason === "user" ? IDLE : reoffer(current.offer));
|
|
1510
1539
|
return;
|
|
1511
1540
|
case "creating":
|
|
1541
|
+
if (keepsSession) return;
|
|
1512
1542
|
cancelStart(reason);
|
|
1513
1543
|
go({ name: "ended", how });
|
|
1514
1544
|
return;
|
|
1515
1545
|
case "pending":
|
|
1516
1546
|
case "live":
|
|
1547
|
+
if (keepsSession) {
|
|
1548
|
+
if (current.name === "pending" && sessionRef.current?.speakerArrived) {
|
|
1549
|
+
goLive();
|
|
1550
|
+
}
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1517
1553
|
finish(how, reason);
|
|
1518
1554
|
return;
|
|
1519
1555
|
default:
|
|
1520
1556
|
return;
|
|
1521
1557
|
}
|
|
1522
1558
|
},
|
|
1523
|
-
[cancelStart, cancelWaiting, finish, go, reoffer]
|
|
1559
|
+
[cancelStart, cancelWaiting, finish, go, goLive, reoffer]
|
|
1524
1560
|
);
|
|
1525
|
-
const showing = active && !off;
|
|
1526
1561
|
react.useEffect(() => {
|
|
1527
1562
|
if (!showing) {
|
|
1528
|
-
leave("user");
|
|
1563
|
+
leave(behindChat ? "chat" : "user");
|
|
1529
1564
|
return;
|
|
1530
1565
|
}
|
|
1531
1566
|
const current = phaseRef.current;
|
|
1567
|
+
if (current.name === "creating" || current.name === "pending" || current.name === "live") {
|
|
1568
|
+
setNow(Date.now());
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1532
1571
|
const offered = offeredNow();
|
|
1533
1572
|
const used = offered !== null && claimedRef.current.has(offered);
|
|
1534
1573
|
if (current.name === "ended" && used) return;
|
|
@@ -1541,7 +1580,7 @@ function AvatarLiveView({
|
|
|
1541
1580
|
current.name === "checking" && checkRef.current === null) {
|
|
1542
1581
|
check();
|
|
1543
1582
|
}
|
|
1544
|
-
}, [showing, check, leave, offeredNow]);
|
|
1583
|
+
}, [showing, behindChat, check, leave, offeredNow]);
|
|
1545
1584
|
react.useEffect(() => {
|
|
1546
1585
|
if (askedLocale.current === locale) return;
|
|
1547
1586
|
askedLocale.current = locale;
|
|
@@ -1628,7 +1667,9 @@ function AvatarLiveView({
|
|
|
1628
1667
|
const wasLive = phaseRef.current.name === "live";
|
|
1629
1668
|
switch (event.type) {
|
|
1630
1669
|
case "speaker-track":
|
|
1631
|
-
if (event.kind
|
|
1670
|
+
if (event.kind !== "video") return;
|
|
1671
|
+
handle.speakerArrived = true;
|
|
1672
|
+
if (!inSight()) goLive();
|
|
1632
1673
|
return;
|
|
1633
1674
|
case "playback-blocked":
|
|
1634
1675
|
setBlocked(event.blocked);
|
|
@@ -1645,7 +1686,7 @@ function AvatarLiveView({
|
|
|
1645
1686
|
return;
|
|
1646
1687
|
}
|
|
1647
1688
|
},
|
|
1648
|
-
[finish]
|
|
1689
|
+
[finish, goLive, inSight]
|
|
1649
1690
|
);
|
|
1650
1691
|
react.useEffect(() => {
|
|
1651
1692
|
if (phase.name !== "pending") return;
|
|
@@ -1719,11 +1760,23 @@ function AvatarLiveView({
|
|
|
1719
1760
|
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
1720
1761
|
return () => clearInterval(timer);
|
|
1721
1762
|
}, [ticking]);
|
|
1763
|
+
const voiceLine = phase.name === "live" ? S.speaking : phase.name === "creating" || phase.name === "pending" ? S.connecting : null;
|
|
1764
|
+
const onVoiceHoldRef = react.useRef(onVoiceHold);
|
|
1765
|
+
onVoiceHoldRef.current = onVoiceHold;
|
|
1766
|
+
react.useEffect(() => {
|
|
1767
|
+
onVoiceHoldRef.current?.(voiceLine);
|
|
1768
|
+
}, [voiceLine]);
|
|
1769
|
+
react.useEffect(() => () => onVoiceHoldRef.current?.(null), []);
|
|
1722
1770
|
react.useEffect(() => {
|
|
1723
1771
|
if (typeof window === "undefined") return;
|
|
1724
1772
|
const onPageHide = () => leave("page_hide");
|
|
1725
1773
|
const onVisibility = () => {
|
|
1726
|
-
if (document.visibilityState === "hidden")
|
|
1774
|
+
if (document.visibilityState === "hidden") {
|
|
1775
|
+
leave("hidden");
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
const current = phaseRef.current.name;
|
|
1779
|
+
if (current === "pending" || current === "live") setNow(Date.now());
|
|
1727
1780
|
};
|
|
1728
1781
|
window.addEventListener("pagehide", onPageHide);
|
|
1729
1782
|
document.addEventListener("visibilitychange", onVisibility);
|
|
@@ -1829,17 +1882,7 @@ function AvatarLiveView({
|
|
|
1829
1882
|
if (focused && focused !== document.body && focused.isConnected) return;
|
|
1830
1883
|
rootRef.current?.querySelector(PRIMARY)?.focus();
|
|
1831
1884
|
}, [active, phase.name, askState, shown.pendingAnswerId, blocked]);
|
|
1832
|
-
const onPlaying =
|
|
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]);
|
|
1885
|
+
const onPlaying = goLive;
|
|
1843
1886
|
const enablePlayback = react.useCallback(() => {
|
|
1844
1887
|
void sessionRef.current?.joined?.enablePlayback().catch(() => void 0);
|
|
1845
1888
|
}, []);
|