@skippr/live-agent-sdk 0.26.0 → 0.28.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 +4 -3
- package/dist/esm/lib-exports.js +1149 -227
- package/dist/skippr-sdk.css +1 -1
- package/dist/skippr-sdk.js +153 -144
- package/dist/types/capture/a11yTree.d.ts +11 -0
- package/dist/types/capture/a11yUtils.d.ts +6 -0
- package/dist/types/capture/domEvents.d.ts +31 -0
- package/dist/types/capture/snapdom.d.ts +2 -0
- package/dist/types/components/AgentStateBanner.d.ts +1 -0
- package/dist/types/components/DomCapture.d.ts +1 -0
- package/dist/types/components/LiveAgent.d.ts +4 -2
- package/dist/types/components/MeetingControls.d.ts +2 -1
- package/dist/types/components/SessionAgenda.d.ts +2 -1
- package/dist/types/context/LiveAgentContext.d.ts +2 -0
- package/dist/types/hooks/useAgentVoiceState.d.ts +2 -3
- package/dist/types/hooks/useSession.d.ts +3 -1
- package/dist/types/lib/constants.d.ts +6 -0
- package/package.json +2 -1
- package/dist/types/components/ObservingBanner.d.ts +0 -1
package/dist/esm/lib-exports.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined")
|
|
5
|
+
return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
1
9
|
// src/components/LiveAgent.tsx
|
|
2
10
|
import { LiveKitRoom, RoomAudioRenderer } from "@livekit/components-react";
|
|
3
|
-
import { useCallback as useCallback6, useEffect as
|
|
11
|
+
import { useCallback as useCallback6, useEffect as useEffect13, useMemo as useMemo5, useRef as useRef7, useState as useState8 } from "react";
|
|
4
12
|
|
|
5
13
|
// src/context/LiveAgentContext.tsx
|
|
6
14
|
import { createContext } from "react";
|
|
@@ -165,7 +173,13 @@ async function exchangeForBearerToken(appKey, userToken) {
|
|
|
165
173
|
const { token } = await resp.json();
|
|
166
174
|
return token;
|
|
167
175
|
}
|
|
168
|
-
function useSession({
|
|
176
|
+
function useSession({
|
|
177
|
+
agentId,
|
|
178
|
+
captureMode = "screenshare",
|
|
179
|
+
authToken,
|
|
180
|
+
appKey,
|
|
181
|
+
userToken
|
|
182
|
+
}) {
|
|
169
183
|
const [connection, setConnection] = useState2(null);
|
|
170
184
|
const [shouldConnect, setShouldConnect] = useState2(false);
|
|
171
185
|
const [isStarting, setIsStarting] = useState2(false);
|
|
@@ -202,12 +216,14 @@ function useSession({ agentId, authToken, appKey, userToken }) {
|
|
|
202
216
|
setError("");
|
|
203
217
|
setErrorCode(null);
|
|
204
218
|
let screenStream = null;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
219
|
+
if (captureMode === "screenshare") {
|
|
220
|
+
try {
|
|
221
|
+
screenStream = await navigator.mediaDevices.getDisplayMedia({
|
|
222
|
+
video: { displaySurface: "browser" }
|
|
223
|
+
});
|
|
224
|
+
} catch {
|
|
225
|
+
screenStream = null;
|
|
226
|
+
}
|
|
211
227
|
}
|
|
212
228
|
const headers = { Authorization: `Bearer ${bearerToken}` };
|
|
213
229
|
try {
|
|
@@ -215,7 +231,7 @@ function useSession({ agentId, authToken, appKey, userToken }) {
|
|
|
215
231
|
method: "POST",
|
|
216
232
|
credentials: "omit",
|
|
217
233
|
headers: { "Content-Type": "application/json", ...headers },
|
|
218
|
-
body: JSON.stringify({ agentId })
|
|
234
|
+
body: JSON.stringify({ agentId, captureMode })
|
|
219
235
|
});
|
|
220
236
|
if (!createResp.ok) {
|
|
221
237
|
const body = await createResp.json().catch(() => null);
|
|
@@ -250,7 +266,7 @@ function useSession({ agentId, authToken, appKey, userToken }) {
|
|
|
250
266
|
} finally {
|
|
251
267
|
setIsStarting(false);
|
|
252
268
|
}
|
|
253
|
-
}, [agentId, bearerToken]);
|
|
269
|
+
}, [agentId, captureMode, bearerToken]);
|
|
254
270
|
const disconnect = useCallback2(async () => {
|
|
255
271
|
if (sessionId && bearerToken) {
|
|
256
272
|
try {
|
|
@@ -284,35 +300,14 @@ function useSession({ agentId, authToken, appKey, userToken }) {
|
|
|
284
300
|
};
|
|
285
301
|
}
|
|
286
302
|
|
|
287
|
-
// src/
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
useEffect3(() => {
|
|
296
|
-
if (didStartRef.current)
|
|
297
|
-
return;
|
|
298
|
-
if (connectionState !== ConnectionState.Connected)
|
|
299
|
-
return;
|
|
300
|
-
didStartRef.current = true;
|
|
301
|
-
localParticipant.setMicrophoneEnabled(true).catch((error) => console.error("Failed to enable microphone:", error));
|
|
302
|
-
if (pendingScreenStream) {
|
|
303
|
-
const videoTrack = pendingScreenStream.getVideoTracks()[0];
|
|
304
|
-
if (videoTrack) {
|
|
305
|
-
videoTrack.contentHint = "detail";
|
|
306
|
-
localParticipant.publishTrack(videoTrack, { source: Track.Source.ScreenShare }).catch((error) => {
|
|
307
|
-
console.error("Failed to publish screen share track:", error);
|
|
308
|
-
for (const track of pendingScreenStream.getTracks())
|
|
309
|
-
track.stop();
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}, [connectionState, localParticipant, pendingScreenStream]);
|
|
314
|
-
return null;
|
|
315
|
-
}
|
|
303
|
+
// src/lib/constants.ts
|
|
304
|
+
var SIDEBAR_WIDTH = 360;
|
|
305
|
+
var WIDGET_ROOT_ID = "skippr-sdk-root";
|
|
306
|
+
var REF_ATTR = "data-skippr-ref";
|
|
307
|
+
var PRIVATE_ATTR = "data-skippr-private";
|
|
308
|
+
var DOM_SNAPSHOT_TOPIC = "skippr.dom-snapshot";
|
|
309
|
+
var DOM_EVENTS_TOPIC = "skippr.dom-events";
|
|
310
|
+
var NAME_MAX_CHARS = 80;
|
|
316
311
|
// ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/createLucideIcon.js
|
|
317
312
|
import { forwardRef as forwardRef2, createElement as createElement3 } from "react";
|
|
318
313
|
|
|
@@ -542,11 +537,8 @@ var __iconNode16 = [
|
|
|
542
537
|
["path", { d: "m21.854 2.147-10.94 10.939", key: "12cjpa" }]
|
|
543
538
|
];
|
|
544
539
|
var Send = createLucideIcon("send", __iconNode16);
|
|
545
|
-
// src/components/MinimizedBubble.tsx
|
|
546
|
-
import { useEffect as useEffect4 } from "react";
|
|
547
|
-
|
|
548
540
|
// src/hooks/useAgentVoiceState.ts
|
|
549
|
-
import { useVoiceAssistant } from "@livekit/components-react";
|
|
541
|
+
import { useVoiceAssistant } from "@livekit/components-react/hooks";
|
|
550
542
|
function useAgentVoiceState() {
|
|
551
543
|
const { state } = useVoiceAssistant();
|
|
552
544
|
return {
|
|
@@ -568,7 +560,7 @@ function useLiveAgent() {
|
|
|
568
560
|
}
|
|
569
561
|
|
|
570
562
|
// src/hooks/useMediaControls.ts
|
|
571
|
-
import { useLocalParticipant
|
|
563
|
+
import { useLocalParticipant } from "@livekit/components-react/hooks";
|
|
572
564
|
import { ScreenSharePresets } from "livekit-client";
|
|
573
565
|
import { useCallback as useCallback3 } from "react";
|
|
574
566
|
var SCREEN_SHARE_OPTIONS = {
|
|
@@ -577,7 +569,7 @@ var SCREEN_SHARE_OPTIONS = {
|
|
|
577
569
|
contentHint: "detail"
|
|
578
570
|
};
|
|
579
571
|
function useMediaControls() {
|
|
580
|
-
const { localParticipant } =
|
|
572
|
+
const { localParticipant } = useLocalParticipant();
|
|
581
573
|
const isMuted = !localParticipant.isMicrophoneEnabled;
|
|
582
574
|
const isScreenSharing = localParticipant.isScreenShareEnabled;
|
|
583
575
|
const toggleMute = useCallback3(async () => {
|
|
@@ -597,6 +589,1001 @@ function useMediaControls() {
|
|
|
597
589
|
return { isMuted, toggleMute, isScreenSharing, toggleScreenShare };
|
|
598
590
|
}
|
|
599
591
|
|
|
592
|
+
// src/components/AgentStateBanner.tsx
|
|
593
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
594
|
+
function AgentStateBanner() {
|
|
595
|
+
const { isConnected } = useLiveAgent();
|
|
596
|
+
const { isScreenSharing } = useMediaControls();
|
|
597
|
+
const { state } = useAgentVoiceState();
|
|
598
|
+
if (!isConnected)
|
|
599
|
+
return null;
|
|
600
|
+
return /* @__PURE__ */ jsx("div", {
|
|
601
|
+
className: "skippr:fixed skippr:top-0 skippr:left-0 skippr:right-0 skippr:z-[2147483647] skippr:flex skippr:justify-center skippr:pointer-events-none",
|
|
602
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
603
|
+
className: "skippr:pointer-events-auto skippr:flex skippr:items-center skippr:gap-2 skippr:bg-indigo-500/95 skippr:backdrop-blur-sm skippr:text-white skippr:text-xs skippr:font-medium skippr:px-4 skippr:py-1.5 skippr:rounded-b-lg skippr:shadow-lg",
|
|
604
|
+
children: /* @__PURE__ */ jsx(BannerContent, {
|
|
605
|
+
state,
|
|
606
|
+
isScreenSharing
|
|
607
|
+
})
|
|
608
|
+
})
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
function BannerContent({
|
|
612
|
+
state,
|
|
613
|
+
isScreenSharing
|
|
614
|
+
}) {
|
|
615
|
+
if (state === "speaking") {
|
|
616
|
+
return /* @__PURE__ */ jsxs(Fragment, {
|
|
617
|
+
children: [
|
|
618
|
+
/* @__PURE__ */ jsx(SpeakingBars, {}),
|
|
619
|
+
/* @__PURE__ */ jsx("span", {
|
|
620
|
+
children: "Skippr is talking"
|
|
621
|
+
})
|
|
622
|
+
]
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
if (state === "thinking") {
|
|
626
|
+
return /* @__PURE__ */ jsxs(Fragment, {
|
|
627
|
+
children: [
|
|
628
|
+
/* @__PURE__ */ jsx(ThinkingDots, {}),
|
|
629
|
+
/* @__PURE__ */ jsx("span", {
|
|
630
|
+
children: "Skippr is thinking"
|
|
631
|
+
})
|
|
632
|
+
]
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
if (state === "listening" && isScreenSharing) {
|
|
636
|
+
return /* @__PURE__ */ jsxs(Fragment, {
|
|
637
|
+
children: [
|
|
638
|
+
/* @__PURE__ */ jsx(ObservingDot, {}),
|
|
639
|
+
/* @__PURE__ */ jsx(Eye, {
|
|
640
|
+
className: "skippr:size-3.5"
|
|
641
|
+
}),
|
|
642
|
+
/* @__PURE__ */ jsx("span", {
|
|
643
|
+
children: "Skippr is observing this page"
|
|
644
|
+
})
|
|
645
|
+
]
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
return /* @__PURE__ */ jsxs(Fragment, {
|
|
649
|
+
children: [
|
|
650
|
+
/* @__PURE__ */ jsx(ObservingDot, {}),
|
|
651
|
+
/* @__PURE__ */ jsx("span", {
|
|
652
|
+
children: "Skippr is connected"
|
|
653
|
+
})
|
|
654
|
+
]
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
function ObservingDot() {
|
|
658
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
659
|
+
className: "skippr:relative skippr:flex skippr:size-1.5",
|
|
660
|
+
children: [
|
|
661
|
+
/* @__PURE__ */ jsx("span", {
|
|
662
|
+
className: "skippr:absolute skippr:inline-flex skippr:size-full skippr:animate-ping skippr:rounded-full skippr:bg-emerald-400 skippr:opacity-75"
|
|
663
|
+
}),
|
|
664
|
+
/* @__PURE__ */ jsx("span", {
|
|
665
|
+
className: "skippr:relative skippr:inline-flex skippr:size-1.5 skippr:rounded-full skippr:bg-emerald-400"
|
|
666
|
+
})
|
|
667
|
+
]
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
function ThinkingDots() {
|
|
671
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
672
|
+
className: "skippr:flex skippr:items-center skippr:gap-[3px]",
|
|
673
|
+
children: [
|
|
674
|
+
/* @__PURE__ */ jsx("span", {
|
|
675
|
+
className: "skippr:size-1 skippr:rounded-full skippr:bg-white skippr:animate-bounce skippr:[animation-delay:0ms]"
|
|
676
|
+
}),
|
|
677
|
+
/* @__PURE__ */ jsx("span", {
|
|
678
|
+
className: "skippr:size-1 skippr:rounded-full skippr:bg-white skippr:animate-bounce skippr:[animation-delay:150ms]"
|
|
679
|
+
}),
|
|
680
|
+
/* @__PURE__ */ jsx("span", {
|
|
681
|
+
className: "skippr:size-1 skippr:rounded-full skippr:bg-white skippr:animate-bounce skippr:[animation-delay:300ms]"
|
|
682
|
+
})
|
|
683
|
+
]
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
function SpeakingBars() {
|
|
687
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
688
|
+
className: "skippr:flex skippr:items-center skippr:gap-[2px] skippr:h-3.5",
|
|
689
|
+
children: [
|
|
690
|
+
/* @__PURE__ */ jsx("span", {
|
|
691
|
+
className: "skippr:w-[2px] skippr:rounded-sm skippr:bg-white skippr:animate-[skippr-speak_1.1s_ease-in-out_infinite] skippr:h-1.5"
|
|
692
|
+
}),
|
|
693
|
+
/* @__PURE__ */ jsx("span", {
|
|
694
|
+
className: "skippr:w-[2px] skippr:rounded-sm skippr:bg-white skippr:animate-[skippr-speak_1.1s_ease-in-out_0.15s_infinite] skippr:h-3"
|
|
695
|
+
}),
|
|
696
|
+
/* @__PURE__ */ jsx("span", {
|
|
697
|
+
className: "skippr:w-[2px] skippr:rounded-sm skippr:bg-white skippr:animate-[skippr-speak_1.1s_ease-in-out_0.3s_infinite] skippr:h-2"
|
|
698
|
+
}),
|
|
699
|
+
/* @__PURE__ */ jsx("span", {
|
|
700
|
+
className: "skippr:w-[2px] skippr:rounded-sm skippr:bg-white skippr:animate-[skippr-speak_1.1s_ease-in-out_0.45s_infinite] skippr:h-3.5"
|
|
701
|
+
}),
|
|
702
|
+
/* @__PURE__ */ jsx("span", {
|
|
703
|
+
className: "skippr:w-[2px] skippr:rounded-sm skippr:bg-white skippr:animate-[skippr-speak_1.1s_ease-in-out_0.6s_infinite] skippr:h-1"
|
|
704
|
+
})
|
|
705
|
+
]
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// src/components/AutoStartMedia.tsx
|
|
710
|
+
import { useConnectionState, useLocalParticipant as useLocalParticipant2 } from "@livekit/components-react/hooks";
|
|
711
|
+
import { ConnectionState, Track } from "livekit-client";
|
|
712
|
+
import { useEffect as useEffect3, useRef } from "react";
|
|
713
|
+
function AutoStartMedia({ pendingScreenStream }) {
|
|
714
|
+
const { localParticipant } = useLocalParticipant2();
|
|
715
|
+
const connectionState = useConnectionState();
|
|
716
|
+
const didStartRef = useRef(false);
|
|
717
|
+
useEffect3(() => {
|
|
718
|
+
if (didStartRef.current)
|
|
719
|
+
return;
|
|
720
|
+
if (connectionState !== ConnectionState.Connected)
|
|
721
|
+
return;
|
|
722
|
+
didStartRef.current = true;
|
|
723
|
+
localParticipant.setMicrophoneEnabled(true).catch((error) => console.error("Failed to enable microphone:", error));
|
|
724
|
+
if (pendingScreenStream) {
|
|
725
|
+
const videoTrack = pendingScreenStream.getVideoTracks()[0];
|
|
726
|
+
if (videoTrack) {
|
|
727
|
+
videoTrack.contentHint = "detail";
|
|
728
|
+
localParticipant.publishTrack(videoTrack, { source: Track.Source.ScreenShare }).catch((error) => {
|
|
729
|
+
console.error("Failed to publish screen share track:", error);
|
|
730
|
+
for (const track of pendingScreenStream.getTracks())
|
|
731
|
+
track.stop();
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}, [connectionState, localParticipant, pendingScreenStream]);
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// src/components/DomCapture.tsx
|
|
740
|
+
import { useConnectionState as useConnectionState2, useLocalParticipant as useLocalParticipant3 } from "@livekit/components-react/hooks";
|
|
741
|
+
import { ConnectionState as ConnectionState2, ScreenSharePresets as ScreenSharePresets2, Track as Track2 } from "livekit-client";
|
|
742
|
+
import { useEffect as useEffect4, useRef as useRef2 } from "react";
|
|
743
|
+
|
|
744
|
+
// src/capture/a11yUtils.ts
|
|
745
|
+
var ROLE_BY_TAG = {
|
|
746
|
+
a: "link",
|
|
747
|
+
button: "button",
|
|
748
|
+
input: "input",
|
|
749
|
+
select: "select",
|
|
750
|
+
textarea: "textarea",
|
|
751
|
+
label: "label",
|
|
752
|
+
nav: "navigation",
|
|
753
|
+
main: "main",
|
|
754
|
+
header: "header",
|
|
755
|
+
footer: "footer",
|
|
756
|
+
aside: "complementary",
|
|
757
|
+
form: "form",
|
|
758
|
+
h1: "heading",
|
|
759
|
+
h2: "heading",
|
|
760
|
+
h3: "heading",
|
|
761
|
+
h4: "heading",
|
|
762
|
+
h5: "heading",
|
|
763
|
+
h6: "heading",
|
|
764
|
+
img: "image",
|
|
765
|
+
ul: "list",
|
|
766
|
+
ol: "list",
|
|
767
|
+
li: "listitem",
|
|
768
|
+
table: "table",
|
|
769
|
+
tr: "row",
|
|
770
|
+
td: "cell",
|
|
771
|
+
th: "columnheader",
|
|
772
|
+
video: "video",
|
|
773
|
+
iframe: "iframe",
|
|
774
|
+
canvas: "canvas",
|
|
775
|
+
audio: "audio",
|
|
776
|
+
embed: "embed",
|
|
777
|
+
object: "object"
|
|
778
|
+
};
|
|
779
|
+
function inferRole(element) {
|
|
780
|
+
const explicitRole = element.getAttribute("role");
|
|
781
|
+
if (explicitRole)
|
|
782
|
+
return explicitRole;
|
|
783
|
+
const tagName = element.tagName.toLowerCase();
|
|
784
|
+
if (tagName === "input") {
|
|
785
|
+
const inputType = element.type;
|
|
786
|
+
if (inputType === "checkbox")
|
|
787
|
+
return "checkbox";
|
|
788
|
+
if (inputType === "radio")
|
|
789
|
+
return "radio";
|
|
790
|
+
if (inputType === "submit" || inputType === "button")
|
|
791
|
+
return "button";
|
|
792
|
+
return "input";
|
|
793
|
+
}
|
|
794
|
+
return ROLE_BY_TAG[tagName] ?? null;
|
|
795
|
+
}
|
|
796
|
+
function accessibleName(element, options = {}) {
|
|
797
|
+
const ariaLabel = element.getAttribute("aria-label");
|
|
798
|
+
if (ariaLabel)
|
|
799
|
+
return ariaLabel.trim().slice(0, NAME_MAX_CHARS);
|
|
800
|
+
if (!options.quick) {
|
|
801
|
+
const labelledBy = element.getAttribute("aria-labelledby");
|
|
802
|
+
if (labelledBy) {
|
|
803
|
+
const labelElement = document.getElementById(labelledBy);
|
|
804
|
+
if (labelElement)
|
|
805
|
+
return (labelElement.textContent || "").trim().slice(0, NAME_MAX_CHARS);
|
|
806
|
+
}
|
|
807
|
+
if (element.tagName === "INPUT" || element.tagName === "SELECT" || element.tagName === "TEXTAREA") {
|
|
808
|
+
const elementId = element.id;
|
|
809
|
+
if (elementId) {
|
|
810
|
+
const associatedLabel = document.querySelector(`label[for="${CSS.escape(elementId)}"]`);
|
|
811
|
+
if (associatedLabel?.textContent) {
|
|
812
|
+
return associatedLabel.textContent.trim().slice(0, NAME_MAX_CHARS);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
const placeholder = element.getAttribute("placeholder");
|
|
816
|
+
if (placeholder)
|
|
817
|
+
return placeholder.trim().slice(0, NAME_MAX_CHARS);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
if (element.tagName === "IMG") {
|
|
821
|
+
const altText = element.getAttribute("alt");
|
|
822
|
+
if (altText)
|
|
823
|
+
return altText.trim().slice(0, NAME_MAX_CHARS);
|
|
824
|
+
}
|
|
825
|
+
const titleAttr = element.getAttribute("title");
|
|
826
|
+
if (titleAttr)
|
|
827
|
+
return titleAttr.trim().slice(0, NAME_MAX_CHARS);
|
|
828
|
+
const textContent = (element.textContent || "").replace(/\s+/g, " ").trim();
|
|
829
|
+
if (textContent)
|
|
830
|
+
return textContent.slice(0, NAME_MAX_CHARS);
|
|
831
|
+
return "";
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// src/capture/a11yTree.ts
|
|
835
|
+
var REF_PREFIX = "r-";
|
|
836
|
+
var VALUE_MAX_CHARS = 80;
|
|
837
|
+
var HREF_MAX_CHARS = 60;
|
|
838
|
+
var PAGE_CONTENT_MAX_BYTES = 14000;
|
|
839
|
+
var NODE_LIMIT = 1500;
|
|
840
|
+
var SENSITIVE_IFRAME_HOSTS = [
|
|
841
|
+
"js.stripe.com",
|
|
842
|
+
"checkout.stripe.com",
|
|
843
|
+
"connect.stripe.com",
|
|
844
|
+
"plaid.com",
|
|
845
|
+
"production.plaid.com",
|
|
846
|
+
"cdn.plaid.com",
|
|
847
|
+
"auth0.com",
|
|
848
|
+
"login.auth0.com",
|
|
849
|
+
"recaptcha.net",
|
|
850
|
+
"www.google.com",
|
|
851
|
+
"hcaptcha.com",
|
|
852
|
+
"newassets.hcaptcha.com",
|
|
853
|
+
"challenges.cloudflare.com"
|
|
854
|
+
];
|
|
855
|
+
var MASK_AUTOCOMPLETE_VALUES = new Set([
|
|
856
|
+
"cc-number",
|
|
857
|
+
"cc-exp",
|
|
858
|
+
"cc-exp-month",
|
|
859
|
+
"cc-exp-year",
|
|
860
|
+
"cc-csc",
|
|
861
|
+
"cc-name",
|
|
862
|
+
"cc-given-name",
|
|
863
|
+
"cc-family-name",
|
|
864
|
+
"current-password",
|
|
865
|
+
"new-password",
|
|
866
|
+
"one-time-code"
|
|
867
|
+
]);
|
|
868
|
+
var MASK_INPUT_TYPES = new Set(["password", "hidden"]);
|
|
869
|
+
var MASK_NAME_PATTERN = /password|secret|token|api[_-]?key|ssn|tax[_-]?id|social[_-]?security|cvv|cvc|pin/i;
|
|
870
|
+
var refCounter = 0;
|
|
871
|
+
function generateNextRef() {
|
|
872
|
+
refCounter += 1;
|
|
873
|
+
return `${REF_PREFIX}${refCounter}`;
|
|
874
|
+
}
|
|
875
|
+
function advanceCounterPastExisting(ref) {
|
|
876
|
+
const parsedRefNumber = Number.parseInt(ref.slice(REF_PREFIX.length), 10);
|
|
877
|
+
if (Number.isFinite(parsedRefNumber) && parsedRefNumber > refCounter) {
|
|
878
|
+
refCounter = parsedRefNumber;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
function intersectsViewport(rect, viewportWidth, viewportHeight) {
|
|
882
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
883
|
+
return false;
|
|
884
|
+
if (rect.bottom <= 0 || rect.top >= viewportHeight)
|
|
885
|
+
return false;
|
|
886
|
+
if (rect.right <= 0 || rect.left >= viewportWidth)
|
|
887
|
+
return false;
|
|
888
|
+
return true;
|
|
889
|
+
}
|
|
890
|
+
function isVisible(element, rect) {
|
|
891
|
+
if (!intersectsViewport(rect, window.innerWidth, window.innerHeight))
|
|
892
|
+
return false;
|
|
893
|
+
const style = window.getComputedStyle(element);
|
|
894
|
+
if (style.visibility === "hidden" || style.display === "none")
|
|
895
|
+
return false;
|
|
896
|
+
if (Number.parseFloat(style.opacity || "1") <= 0.01)
|
|
897
|
+
return false;
|
|
898
|
+
return true;
|
|
899
|
+
}
|
|
900
|
+
function isInteractive(element, role) {
|
|
901
|
+
if (role && role !== "heading" && role !== "image" && role !== "list" && role !== "listitem") {
|
|
902
|
+
const interactiveRoles = [
|
|
903
|
+
"button",
|
|
904
|
+
"link",
|
|
905
|
+
"input",
|
|
906
|
+
"select",
|
|
907
|
+
"textarea",
|
|
908
|
+
"checkbox",
|
|
909
|
+
"radio",
|
|
910
|
+
"tab",
|
|
911
|
+
"menuitem",
|
|
912
|
+
"option",
|
|
913
|
+
"switch",
|
|
914
|
+
"slider"
|
|
915
|
+
];
|
|
916
|
+
if (interactiveRoles.includes(role))
|
|
917
|
+
return true;
|
|
918
|
+
}
|
|
919
|
+
if (element.tabIndex >= 0)
|
|
920
|
+
return true;
|
|
921
|
+
if (element.hasAttribute("onclick") || element.hasAttribute("contenteditable"))
|
|
922
|
+
return true;
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
function isBlindRegion(role) {
|
|
926
|
+
return role === "video" || role === "iframe" || role === "canvas" || role === "audio" || role === "embed" || role === "object";
|
|
927
|
+
}
|
|
928
|
+
function getIframeHost(element) {
|
|
929
|
+
const src = element.getAttribute("src");
|
|
930
|
+
if (!src)
|
|
931
|
+
return null;
|
|
932
|
+
try {
|
|
933
|
+
return new URL(src, window.location.href).host;
|
|
934
|
+
} catch {
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
function isSensitiveIframe(element) {
|
|
939
|
+
const host = getIframeHost(element);
|
|
940
|
+
if (!host)
|
|
941
|
+
return false;
|
|
942
|
+
return SENSITIVE_IFRAME_HOSTS.some((sensitiveHost) => host === sensitiveHost || host.endsWith(`.${sensitiveHost}`));
|
|
943
|
+
}
|
|
944
|
+
function isSameOriginIframe(element) {
|
|
945
|
+
const src = element.getAttribute("src");
|
|
946
|
+
if (!src)
|
|
947
|
+
return true;
|
|
948
|
+
try {
|
|
949
|
+
return new URL(src, window.location.href).origin === window.location.origin;
|
|
950
|
+
} catch {
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
function nearestHeadingText(element) {
|
|
955
|
+
let currentNode = element;
|
|
956
|
+
while (currentNode && currentNode !== document.body) {
|
|
957
|
+
let previousSibling = currentNode.previousElementSibling;
|
|
958
|
+
while (previousSibling) {
|
|
959
|
+
if (/^H[1-6]$/.test(previousSibling.tagName) && previousSibling.textContent) {
|
|
960
|
+
return previousSibling.textContent.trim().slice(0, NAME_MAX_CHARS);
|
|
961
|
+
}
|
|
962
|
+
previousSibling = previousSibling.previousElementSibling;
|
|
963
|
+
}
|
|
964
|
+
currentNode = currentNode.parentElement;
|
|
965
|
+
}
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
function shouldMaskValue(input) {
|
|
969
|
+
if (input.tagName === "INPUT") {
|
|
970
|
+
const inputElement = input;
|
|
971
|
+
if (MASK_INPUT_TYPES.has(inputElement.type))
|
|
972
|
+
return true;
|
|
973
|
+
const autocompleteAttr = (inputElement.getAttribute("autocomplete") || "").toLowerCase().trim();
|
|
974
|
+
if (MASK_AUTOCOMPLETE_VALUES.has(autocompleteAttr))
|
|
975
|
+
return true;
|
|
976
|
+
}
|
|
977
|
+
const fieldName = (input.getAttribute("name") || "").trim();
|
|
978
|
+
const fieldId = (input.getAttribute("id") || "").trim();
|
|
979
|
+
if (MASK_NAME_PATTERN.test(fieldName) || MASK_NAME_PATTERN.test(fieldId))
|
|
980
|
+
return true;
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
983
|
+
function escapeAttributeValue(value) {
|
|
984
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/[\r\n]+/g, " ");
|
|
985
|
+
}
|
|
986
|
+
function extractAttrs(element, role) {
|
|
987
|
+
const attributes = [];
|
|
988
|
+
if (role === "link") {
|
|
989
|
+
const href = element.getAttribute("href");
|
|
990
|
+
if (href)
|
|
991
|
+
attributes.push(`href="${escapeAttributeValue(href.slice(0, HREF_MAX_CHARS))}"`);
|
|
992
|
+
}
|
|
993
|
+
if (role === "input" || role === "checkbox" || role === "radio") {
|
|
994
|
+
const input = element;
|
|
995
|
+
if (input.type && input.type !== "text")
|
|
996
|
+
attributes.push(`type="${input.type}"`);
|
|
997
|
+
if (shouldMaskValue(input)) {
|
|
998
|
+
attributes.push("valueMasked");
|
|
999
|
+
} else if (input.value && role === "input") {
|
|
1000
|
+
attributes.push(`value="${escapeAttributeValue(input.value.slice(0, VALUE_MAX_CHARS))}"`);
|
|
1001
|
+
}
|
|
1002
|
+
if (input.checked)
|
|
1003
|
+
attributes.push("checked");
|
|
1004
|
+
}
|
|
1005
|
+
if (role === "textarea") {
|
|
1006
|
+
const textarea = element;
|
|
1007
|
+
if (shouldMaskValue(textarea)) {
|
|
1008
|
+
attributes.push("valueMasked");
|
|
1009
|
+
} else if (textarea.value) {
|
|
1010
|
+
attributes.push(`value="${escapeAttributeValue(textarea.value.slice(0, VALUE_MAX_CHARS))}"`);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
if (role === "select") {
|
|
1014
|
+
const select = element;
|
|
1015
|
+
const selectedOption = select.options[select.selectedIndex];
|
|
1016
|
+
if (selectedOption?.value?.trim()) {
|
|
1017
|
+
attributes.push(`selected="${escapeAttributeValue(selectedOption.value.slice(0, VALUE_MAX_CHARS))}"`);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (role === "heading") {
|
|
1021
|
+
const headingLevel = element.tagName.match(/^H([1-6])$/)?.[1];
|
|
1022
|
+
if (headingLevel)
|
|
1023
|
+
attributes.push(`level="${headingLevel}"`);
|
|
1024
|
+
}
|
|
1025
|
+
if (role === "video" || role === "audio") {
|
|
1026
|
+
const mediaElement = element;
|
|
1027
|
+
try {
|
|
1028
|
+
if (Number.isFinite(mediaElement.currentTime)) {
|
|
1029
|
+
attributes.push(`currentTime="${mediaElement.currentTime.toFixed(1)}"`);
|
|
1030
|
+
}
|
|
1031
|
+
if (Number.isFinite(mediaElement.duration) && mediaElement.duration > 0) {
|
|
1032
|
+
attributes.push(`duration="${mediaElement.duration.toFixed(1)}"`);
|
|
1033
|
+
}
|
|
1034
|
+
attributes.push(mediaElement.paused ? "paused" : "playing");
|
|
1035
|
+
if (mediaElement.muted)
|
|
1036
|
+
attributes.push("muted");
|
|
1037
|
+
const mediaSrc = mediaElement.currentSrc || mediaElement.getAttribute("src");
|
|
1038
|
+
if (mediaSrc) {
|
|
1039
|
+
try {
|
|
1040
|
+
attributes.push(`srcHost="${new URL(mediaSrc, window.location.href).host}"`);
|
|
1041
|
+
} catch {}
|
|
1042
|
+
}
|
|
1043
|
+
} catch {}
|
|
1044
|
+
}
|
|
1045
|
+
if (role === "iframe") {
|
|
1046
|
+
const host = getIframeHost(element);
|
|
1047
|
+
if (host)
|
|
1048
|
+
attributes.push(`srcHost="${host}"`);
|
|
1049
|
+
if (isSensitiveIframe(element)) {
|
|
1050
|
+
attributes.push("sensitive");
|
|
1051
|
+
} else if (!isSameOriginIframe(element)) {
|
|
1052
|
+
attributes.push("cross-origin");
|
|
1053
|
+
}
|
|
1054
|
+
const iframeRect = element.getBoundingClientRect();
|
|
1055
|
+
attributes.push(`size="${Math.round(iframeRect.width)}x${Math.round(iframeRect.height)}"`);
|
|
1056
|
+
}
|
|
1057
|
+
if (role === "canvas") {
|
|
1058
|
+
const canvas = element;
|
|
1059
|
+
attributes.push(`size="${canvas.width}x${canvas.height}"`);
|
|
1060
|
+
const heading = nearestHeadingText(element);
|
|
1061
|
+
if (heading)
|
|
1062
|
+
attributes.push(`nearestHeading="${escapeAttributeValue(heading)}"`);
|
|
1063
|
+
}
|
|
1064
|
+
if (role === "image") {
|
|
1065
|
+
const image = element;
|
|
1066
|
+
const imageSrc = image.getAttribute("src") || "";
|
|
1067
|
+
if (/\.gif(\?|$)/i.test(imageSrc))
|
|
1068
|
+
attributes.push("animated");
|
|
1069
|
+
}
|
|
1070
|
+
const isNativelyDisabled = "disabled" in element && element.disabled === true;
|
|
1071
|
+
if (isNativelyDisabled || element.getAttribute("aria-disabled") === "true") {
|
|
1072
|
+
attributes.push("disabled");
|
|
1073
|
+
}
|
|
1074
|
+
return attributes;
|
|
1075
|
+
}
|
|
1076
|
+
function ensureStableRef(element) {
|
|
1077
|
+
const existingRef = element.getAttribute(REF_ATTR);
|
|
1078
|
+
if (existingRef) {
|
|
1079
|
+
advanceCounterPastExisting(existingRef);
|
|
1080
|
+
return existingRef;
|
|
1081
|
+
}
|
|
1082
|
+
const newRef = generateNextRef();
|
|
1083
|
+
element.setAttribute(REF_ATTR, newRef);
|
|
1084
|
+
return newRef;
|
|
1085
|
+
}
|
|
1086
|
+
function formatNodeAsLine(entry) {
|
|
1087
|
+
const indent = " ".repeat(entry.depth);
|
|
1088
|
+
const namePart = entry.name ? ` "${escapeAttributeValue(entry.name)}"` : "";
|
|
1089
|
+
const attrsPart = entry.attrs.length > 0 ? ` ${entry.attrs.join(" ")}` : "";
|
|
1090
|
+
return `${indent}${entry.role}${namePart} ${entry.ref}${attrsPart}`;
|
|
1091
|
+
}
|
|
1092
|
+
function shouldSkipSubtree(element) {
|
|
1093
|
+
if (element.id === WIDGET_ROOT_ID)
|
|
1094
|
+
return true;
|
|
1095
|
+
if (element.hasAttribute(PRIVATE_ATTR))
|
|
1096
|
+
return true;
|
|
1097
|
+
const tagName = element.tagName;
|
|
1098
|
+
if (tagName === "SCRIPT" || tagName === "STYLE" || tagName === "NOSCRIPT")
|
|
1099
|
+
return true;
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
function shouldEmitElement(element, role) {
|
|
1103
|
+
if (role === null)
|
|
1104
|
+
return false;
|
|
1105
|
+
return isInteractive(element, role) || role === "heading" || role === "image" || role === "label" || isBlindRegion(role);
|
|
1106
|
+
}
|
|
1107
|
+
function getSameOriginIframeBody(element) {
|
|
1108
|
+
if (element.tagName !== "IFRAME")
|
|
1109
|
+
return null;
|
|
1110
|
+
if (!isSameOriginIframe(element) || isSensitiveIframe(element))
|
|
1111
|
+
return null;
|
|
1112
|
+
try {
|
|
1113
|
+
return element.contentDocument?.body ?? null;
|
|
1114
|
+
} catch {
|
|
1115
|
+
return null;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
function walkAndCollect(element, depth, entries) {
|
|
1119
|
+
if (shouldSkipSubtree(element))
|
|
1120
|
+
return;
|
|
1121
|
+
const rect = element.getBoundingClientRect();
|
|
1122
|
+
const role = inferRole(element);
|
|
1123
|
+
const isVisibleAndEmittable = role !== null && isVisible(element, rect) && shouldEmitElement(element, role);
|
|
1124
|
+
let childDepth = depth;
|
|
1125
|
+
if (isVisibleAndEmittable && role) {
|
|
1126
|
+
const ref = ensureStableRef(element);
|
|
1127
|
+
entries.push({
|
|
1128
|
+
depth,
|
|
1129
|
+
role,
|
|
1130
|
+
name: accessibleName(element),
|
|
1131
|
+
ref,
|
|
1132
|
+
attrs: extractAttrs(element, role),
|
|
1133
|
+
area: rect.width * rect.height
|
|
1134
|
+
});
|
|
1135
|
+
childDepth = depth + 1;
|
|
1136
|
+
}
|
|
1137
|
+
if (role === "iframe") {
|
|
1138
|
+
const iframeBody = getSameOriginIframeBody(element);
|
|
1139
|
+
if (iframeBody) {
|
|
1140
|
+
for (const child of Array.from(iframeBody.children)) {
|
|
1141
|
+
walkAndCollect(child, childDepth, entries);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
if (role && isBlindRegion(role))
|
|
1147
|
+
return;
|
|
1148
|
+
for (const child of Array.from(element.children)) {
|
|
1149
|
+
walkAndCollect(child, childDepth, entries);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
function dropSmallestAreaEntriesUntilUnderLimit(entries) {
|
|
1153
|
+
if (entries.length <= NODE_LIMIT)
|
|
1154
|
+
return entries;
|
|
1155
|
+
const indexedEntries = entries.map((entry, index2) => ({ entry, index: index2 }));
|
|
1156
|
+
const entriesByAreaAscending = indexedEntries.slice(1).sort((a, b) => a.entry.area - b.entry.area);
|
|
1157
|
+
const indicesToDrop = new Set(entriesByAreaAscending.slice(0, entries.length - NODE_LIMIT).map((indexed) => indexed.index));
|
|
1158
|
+
const keptEntries = [];
|
|
1159
|
+
for (let i = 0;i < entries.length; i++) {
|
|
1160
|
+
if (!indicesToDrop.has(i))
|
|
1161
|
+
keptEntries.push(entries[i]);
|
|
1162
|
+
}
|
|
1163
|
+
return keptEntries;
|
|
1164
|
+
}
|
|
1165
|
+
function buildAccessibilityTree() {
|
|
1166
|
+
const collectedEntries = [];
|
|
1167
|
+
const pageTitle = document.title || window.location.pathname;
|
|
1168
|
+
collectedEntries.push({
|
|
1169
|
+
depth: 0,
|
|
1170
|
+
role: "page",
|
|
1171
|
+
name: pageTitle.slice(0, NAME_MAX_CHARS),
|
|
1172
|
+
ref: `${REF_PREFIX}0`,
|
|
1173
|
+
attrs: [],
|
|
1174
|
+
area: window.innerWidth * window.innerHeight
|
|
1175
|
+
});
|
|
1176
|
+
if (document.body) {
|
|
1177
|
+
for (const child of Array.from(document.body.children)) {
|
|
1178
|
+
walkAndCollect(child, 1, collectedEntries);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
const exceededNodeLimit = collectedEntries.length > NODE_LIMIT;
|
|
1182
|
+
const finalEntries = exceededNodeLimit ? dropSmallestAreaEntriesUntilUnderLimit(collectedEntries) : collectedEntries;
|
|
1183
|
+
const treeLines = finalEntries.map(formatNodeAsLine);
|
|
1184
|
+
let pageContent = treeLines.join(`
|
|
1185
|
+
`);
|
|
1186
|
+
const exceededByteLimit = pageContent.length > PAGE_CONTENT_MAX_BYTES;
|
|
1187
|
+
if (exceededByteLimit) {
|
|
1188
|
+
pageContent = `${pageContent.slice(0, PAGE_CONTENT_MAX_BYTES)}
|
|
1189
|
+
<!-- truncated -->`;
|
|
1190
|
+
}
|
|
1191
|
+
return {
|
|
1192
|
+
pageContent,
|
|
1193
|
+
viewport: { width: window.innerWidth, height: window.innerHeight },
|
|
1194
|
+
refCount: finalEntries.length - 1,
|
|
1195
|
+
truncated: exceededNodeLimit || exceededByteLimit
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// src/capture/domEvents.ts
|
|
1200
|
+
var MUTATION_DEBOUNCE_MS = 250;
|
|
1201
|
+
var textEncoder = new TextEncoder;
|
|
1202
|
+
function isInsideWidget(target) {
|
|
1203
|
+
if (!(target instanceof Node))
|
|
1204
|
+
return false;
|
|
1205
|
+
let node = target;
|
|
1206
|
+
while (node) {
|
|
1207
|
+
if (node instanceof Element && node.id === WIDGET_ROOT_ID)
|
|
1208
|
+
return true;
|
|
1209
|
+
node = node.parentNode;
|
|
1210
|
+
}
|
|
1211
|
+
return false;
|
|
1212
|
+
}
|
|
1213
|
+
function findNearestRef(element) {
|
|
1214
|
+
let currentNode = element;
|
|
1215
|
+
while (currentNode) {
|
|
1216
|
+
const ref = currentNode.getAttribute(REF_ATTR);
|
|
1217
|
+
if (ref)
|
|
1218
|
+
return { ref, node: currentNode };
|
|
1219
|
+
currentNode = currentNode.parentElement;
|
|
1220
|
+
}
|
|
1221
|
+
return null;
|
|
1222
|
+
}
|
|
1223
|
+
function installDomEventListeners(localParticipant, options = {}) {
|
|
1224
|
+
const { onTriggerEvent } = options;
|
|
1225
|
+
function publishDomEvent(event) {
|
|
1226
|
+
try {
|
|
1227
|
+
localParticipant.publishData(textEncoder.encode(JSON.stringify(event)), {
|
|
1228
|
+
reliable: true,
|
|
1229
|
+
topic: DOM_EVENTS_TOPIC
|
|
1230
|
+
}).catch(() => {
|
|
1231
|
+
return;
|
|
1232
|
+
});
|
|
1233
|
+
} catch {}
|
|
1234
|
+
if (onTriggerEvent) {
|
|
1235
|
+
try {
|
|
1236
|
+
onTriggerEvent();
|
|
1237
|
+
} catch {}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
const onClick = (event) => {
|
|
1241
|
+
if (isInsideWidget(event.target))
|
|
1242
|
+
return;
|
|
1243
|
+
if (!(event.target instanceof Element))
|
|
1244
|
+
return;
|
|
1245
|
+
const matchedRef = findNearestRef(event.target);
|
|
1246
|
+
const targetElement = matchedRef?.node ?? event.target;
|
|
1247
|
+
publishDomEvent({
|
|
1248
|
+
type: "click",
|
|
1249
|
+
ref: matchedRef?.ref ?? null,
|
|
1250
|
+
role: inferRole(targetElement) ?? targetElement.tagName.toLowerCase(),
|
|
1251
|
+
name: accessibleName(targetElement, { quick: true }),
|
|
1252
|
+
timestamp: Date.now()
|
|
1253
|
+
});
|
|
1254
|
+
};
|
|
1255
|
+
document.addEventListener("click", onClick, true);
|
|
1256
|
+
const onSubmit = (event) => {
|
|
1257
|
+
if (isInsideWidget(event.target))
|
|
1258
|
+
return;
|
|
1259
|
+
if (!(event.target instanceof HTMLFormElement))
|
|
1260
|
+
return;
|
|
1261
|
+
const submittedForm = event.target;
|
|
1262
|
+
publishDomEvent({
|
|
1263
|
+
type: "submit",
|
|
1264
|
+
ref: submittedForm.getAttribute(REF_ATTR),
|
|
1265
|
+
formName: (submittedForm.name || submittedForm.id || "").slice(0, NAME_MAX_CHARS),
|
|
1266
|
+
action: (submittedForm.action || "").slice(0, NAME_MAX_CHARS),
|
|
1267
|
+
timestamp: Date.now()
|
|
1268
|
+
});
|
|
1269
|
+
};
|
|
1270
|
+
document.addEventListener("submit", onSubmit, true);
|
|
1271
|
+
const originalPushState = history.pushState;
|
|
1272
|
+
const originalReplaceState = history.replaceState;
|
|
1273
|
+
let previousUrl = window.location.href;
|
|
1274
|
+
const emitNavigationIfChanged = () => {
|
|
1275
|
+
const nextUrl = window.location.href;
|
|
1276
|
+
if (nextUrl === previousUrl)
|
|
1277
|
+
return;
|
|
1278
|
+
publishDomEvent({
|
|
1279
|
+
type: "navigation",
|
|
1280
|
+
from: previousUrl,
|
|
1281
|
+
to: nextUrl,
|
|
1282
|
+
title: document.title.slice(0, NAME_MAX_CHARS),
|
|
1283
|
+
timestamp: Date.now()
|
|
1284
|
+
});
|
|
1285
|
+
previousUrl = nextUrl;
|
|
1286
|
+
};
|
|
1287
|
+
history.pushState = function patchedPushState(...args) {
|
|
1288
|
+
originalPushState.apply(this, args);
|
|
1289
|
+
queueMicrotask(emitNavigationIfChanged);
|
|
1290
|
+
};
|
|
1291
|
+
history.replaceState = function patchedReplaceState(...args) {
|
|
1292
|
+
originalReplaceState.apply(this, args);
|
|
1293
|
+
queueMicrotask(emitNavigationIfChanged);
|
|
1294
|
+
};
|
|
1295
|
+
const onPopState = () => emitNavigationIfChanged();
|
|
1296
|
+
const onHashChange = () => emitNavigationIfChanged();
|
|
1297
|
+
window.addEventListener("popstate", onPopState);
|
|
1298
|
+
window.addEventListener("hashchange", onHashChange);
|
|
1299
|
+
let pendingAddedRefs = new Set;
|
|
1300
|
+
let pendingRemovedRefs = new Set;
|
|
1301
|
+
let pendingMutationCount = 0;
|
|
1302
|
+
let mutationFlushTimer = null;
|
|
1303
|
+
const flushPendingMutations = () => {
|
|
1304
|
+
mutationFlushTimer = null;
|
|
1305
|
+
if (pendingMutationCount === 0)
|
|
1306
|
+
return;
|
|
1307
|
+
publishDomEvent({
|
|
1308
|
+
type: "mutation",
|
|
1309
|
+
summary: `${pendingMutationCount} subtree changes`,
|
|
1310
|
+
addedRefs: Array.from(pendingAddedRefs),
|
|
1311
|
+
removedRefs: Array.from(pendingRemovedRefs),
|
|
1312
|
+
timestamp: Date.now()
|
|
1313
|
+
});
|
|
1314
|
+
pendingAddedRefs = new Set;
|
|
1315
|
+
pendingRemovedRefs = new Set;
|
|
1316
|
+
pendingMutationCount = 0;
|
|
1317
|
+
};
|
|
1318
|
+
const collectDescendantRefs = (node, targetSet) => {
|
|
1319
|
+
if (!(node instanceof Element))
|
|
1320
|
+
return;
|
|
1321
|
+
if (node.id === WIDGET_ROOT_ID)
|
|
1322
|
+
return;
|
|
1323
|
+
const ref = node.getAttribute(REF_ATTR);
|
|
1324
|
+
if (ref)
|
|
1325
|
+
targetSet.add(ref);
|
|
1326
|
+
for (const child of Array.from(node.children))
|
|
1327
|
+
collectDescendantRefs(child, targetSet);
|
|
1328
|
+
};
|
|
1329
|
+
const observer = new MutationObserver((mutations) => {
|
|
1330
|
+
let observedCount = 0;
|
|
1331
|
+
for (const mutation of mutations) {
|
|
1332
|
+
if (mutation.type !== "childList")
|
|
1333
|
+
continue;
|
|
1334
|
+
if (mutation.target instanceof Element && isInsideWidget(mutation.target))
|
|
1335
|
+
continue;
|
|
1336
|
+
if (mutation.addedNodes.length === 0 && mutation.removedNodes.length === 0)
|
|
1337
|
+
continue;
|
|
1338
|
+
for (const added of Array.from(mutation.addedNodes)) {
|
|
1339
|
+
collectDescendantRefs(added, pendingAddedRefs);
|
|
1340
|
+
}
|
|
1341
|
+
for (const removed of Array.from(mutation.removedNodes)) {
|
|
1342
|
+
collectDescendantRefs(removed, pendingRemovedRefs);
|
|
1343
|
+
}
|
|
1344
|
+
observedCount += 1;
|
|
1345
|
+
}
|
|
1346
|
+
if (observedCount === 0)
|
|
1347
|
+
return;
|
|
1348
|
+
pendingMutationCount += observedCount;
|
|
1349
|
+
if (mutationFlushTimer)
|
|
1350
|
+
clearTimeout(mutationFlushTimer);
|
|
1351
|
+
mutationFlushTimer = setTimeout(flushPendingMutations, MUTATION_DEBOUNCE_MS);
|
|
1352
|
+
});
|
|
1353
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
1354
|
+
return () => {
|
|
1355
|
+
document.removeEventListener("click", onClick, true);
|
|
1356
|
+
document.removeEventListener("submit", onSubmit, true);
|
|
1357
|
+
window.removeEventListener("popstate", onPopState);
|
|
1358
|
+
window.removeEventListener("hashchange", onHashChange);
|
|
1359
|
+
history.pushState = originalPushState;
|
|
1360
|
+
history.replaceState = originalReplaceState;
|
|
1361
|
+
if (mutationFlushTimer)
|
|
1362
|
+
clearTimeout(mutationFlushTimer);
|
|
1363
|
+
observer.disconnect();
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// src/capture/snapdom.ts
|
|
1368
|
+
var cachedSnapdomModule = null;
|
|
1369
|
+
function loadSnapdom() {
|
|
1370
|
+
if (!cachedSnapdomModule)
|
|
1371
|
+
cachedSnapdomModule = import("@zumer/snapdom");
|
|
1372
|
+
return cachedSnapdomModule;
|
|
1373
|
+
}
|
|
1374
|
+
async function snapToCanvas(element, options = {}) {
|
|
1375
|
+
const snapdomModule = await loadSnapdom();
|
|
1376
|
+
return snapdomModule.snapdom.toCanvas(element, options);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// src/components/DomCapture.tsx
|
|
1380
|
+
var SNAPSHOT_INTERVAL_MS = 2000;
|
|
1381
|
+
var A11Y_PUBLISH_INTERVAL_MS = 2000;
|
|
1382
|
+
var CAPTURE_PRESET = ScreenSharePresets2.h1080fps30;
|
|
1383
|
+
var CAPTURE_FPS = CAPTURE_PRESET.encoding.maxFramerate ?? 30;
|
|
1384
|
+
var CAPTURE_BITRATE = 4000000;
|
|
1385
|
+
var DOM_SNAPSHOT_GZIP_THRESHOLD_BYTES = 14000;
|
|
1386
|
+
var MAX_CANVAS_DPR = 1.5;
|
|
1387
|
+
var CONSECUTIVE_FAILURES_BEFORE_REPORT = 3;
|
|
1388
|
+
var textEncoder2 = new TextEncoder;
|
|
1389
|
+
function shouldIncludeInSnapshot(element) {
|
|
1390
|
+
if (!(element instanceof Element))
|
|
1391
|
+
return true;
|
|
1392
|
+
if (element.id === WIDGET_ROOT_ID)
|
|
1393
|
+
return false;
|
|
1394
|
+
if (element.hasAttribute?.(PRIVATE_ATTR))
|
|
1395
|
+
return false;
|
|
1396
|
+
return true;
|
|
1397
|
+
}
|
|
1398
|
+
function createPublishingCanvas() {
|
|
1399
|
+
const dprBoost = Math.min(window.devicePixelRatio || 1, MAX_CANVAS_DPR);
|
|
1400
|
+
const canvas = document.createElement("canvas");
|
|
1401
|
+
canvas.width = Math.round(CAPTURE_PRESET.width * dprBoost);
|
|
1402
|
+
canvas.height = Math.round(CAPTURE_PRESET.height * dprBoost);
|
|
1403
|
+
const ctx = canvas.getContext("2d");
|
|
1404
|
+
if (!ctx)
|
|
1405
|
+
return null;
|
|
1406
|
+
ctx.fillStyle = "#ffffff";
|
|
1407
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
1408
|
+
return { canvas, ctx };
|
|
1409
|
+
}
|
|
1410
|
+
function getCanvasCaptureStream(canvas, fps) {
|
|
1411
|
+
const captureStreamFn = canvas.captureStream;
|
|
1412
|
+
if (typeof captureStreamFn !== "function") {
|
|
1413
|
+
console.error("canvas.captureStream is not supported in this browser");
|
|
1414
|
+
return null;
|
|
1415
|
+
}
|
|
1416
|
+
return captureStreamFn.call(canvas, fps);
|
|
1417
|
+
}
|
|
1418
|
+
async function paintViewportSnapshot(canvas, ctx) {
|
|
1419
|
+
const dpr = window.devicePixelRatio || 1;
|
|
1420
|
+
const snapshotCanvas = await snapToCanvas(document.documentElement, {
|
|
1421
|
+
filter: shouldIncludeInSnapshot,
|
|
1422
|
+
filterMode: "remove",
|
|
1423
|
+
backgroundColor: "#ffffff",
|
|
1424
|
+
fast: true,
|
|
1425
|
+
dpr
|
|
1426
|
+
});
|
|
1427
|
+
const sourceX = window.scrollX * dpr;
|
|
1428
|
+
const sourceY = window.scrollY * dpr;
|
|
1429
|
+
const sourceWidth = window.innerWidth * dpr;
|
|
1430
|
+
const sourceHeight = window.innerHeight * dpr;
|
|
1431
|
+
const fitScale = Math.min(canvas.width / sourceWidth, canvas.height / sourceHeight);
|
|
1432
|
+
const destWidth = sourceWidth * fitScale;
|
|
1433
|
+
const destHeight = sourceHeight * fitScale;
|
|
1434
|
+
const destX = (canvas.width - destWidth) / 2;
|
|
1435
|
+
const destY = (canvas.height - destHeight) / 2;
|
|
1436
|
+
ctx.fillStyle = "#ffffff";
|
|
1437
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
1438
|
+
ctx.drawImage(snapshotCanvas, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight);
|
|
1439
|
+
}
|
|
1440
|
+
async function gzipIfBeneficial(snapshotBytes) {
|
|
1441
|
+
if (typeof CompressionStream === "undefined" || snapshotBytes.byteLength <= DOM_SNAPSHOT_GZIP_THRESHOLD_BYTES) {
|
|
1442
|
+
return { bytes: snapshotBytes, compressionFlag: 0 };
|
|
1443
|
+
}
|
|
1444
|
+
try {
|
|
1445
|
+
const blob = new Blob([new Uint8Array(snapshotBytes)]);
|
|
1446
|
+
const stream = blob.stream().pipeThrough(new CompressionStream("gzip"));
|
|
1447
|
+
const compressedBytes = new Uint8Array(await new Response(stream).arrayBuffer());
|
|
1448
|
+
return { bytes: compressedBytes, compressionFlag: 1 };
|
|
1449
|
+
} catch {
|
|
1450
|
+
return { bytes: snapshotBytes, compressionFlag: 0 };
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
async function buildDomSnapshotFrame() {
|
|
1454
|
+
const a11yTree = buildAccessibilityTree();
|
|
1455
|
+
const snapshotJson = JSON.stringify({
|
|
1456
|
+
pageContent: a11yTree.pageContent,
|
|
1457
|
+
viewport: a11yTree.viewport,
|
|
1458
|
+
truncated: a11yTree.truncated,
|
|
1459
|
+
url: window.location.href,
|
|
1460
|
+
title: document.title,
|
|
1461
|
+
capturedAt: new Date().toISOString()
|
|
1462
|
+
});
|
|
1463
|
+
const snapshotBytes = textEncoder2.encode(snapshotJson);
|
|
1464
|
+
const { bytes, compressionFlag } = await gzipIfBeneficial(snapshotBytes);
|
|
1465
|
+
const frame = new Uint8Array(bytes.byteLength + 1);
|
|
1466
|
+
frame[0] = compressionFlag;
|
|
1467
|
+
frame.set(bytes, 1);
|
|
1468
|
+
return frame;
|
|
1469
|
+
}
|
|
1470
|
+
function reportCaptureError(localParticipant) {
|
|
1471
|
+
try {
|
|
1472
|
+
localParticipant.publishData(textEncoder2.encode(JSON.stringify({ type: "capture_error" })), {
|
|
1473
|
+
reliable: true,
|
|
1474
|
+
topic: DOM_SNAPSHOT_TOPIC
|
|
1475
|
+
}).catch(() => {
|
|
1476
|
+
return;
|
|
1477
|
+
});
|
|
1478
|
+
} catch {}
|
|
1479
|
+
}
|
|
1480
|
+
async function unpublishAndStopTrack(localParticipant, videoTrack) {
|
|
1481
|
+
try {
|
|
1482
|
+
await localParticipant.unpublishTrack(videoTrack);
|
|
1483
|
+
} catch {}
|
|
1484
|
+
videoTrack.stop();
|
|
1485
|
+
}
|
|
1486
|
+
function DomCapture() {
|
|
1487
|
+
const { localParticipant } = useLocalParticipant3();
|
|
1488
|
+
const connectionState = useConnectionState2();
|
|
1489
|
+
const didStartRef = useRef2(false);
|
|
1490
|
+
useEffect4(() => {
|
|
1491
|
+
if (didStartRef.current)
|
|
1492
|
+
return;
|
|
1493
|
+
if (connectionState !== ConnectionState2.Connected)
|
|
1494
|
+
return;
|
|
1495
|
+
const canvasAndCtx = createPublishingCanvas();
|
|
1496
|
+
if (!canvasAndCtx)
|
|
1497
|
+
return;
|
|
1498
|
+
const { canvas, ctx } = canvasAndCtx;
|
|
1499
|
+
const canvasStream = getCanvasCaptureStream(canvas, CAPTURE_FPS);
|
|
1500
|
+
if (!canvasStream)
|
|
1501
|
+
return;
|
|
1502
|
+
const videoTrack = canvasStream.getVideoTracks()[0] ?? null;
|
|
1503
|
+
if (!videoTrack)
|
|
1504
|
+
return;
|
|
1505
|
+
videoTrack.contentHint = "text";
|
|
1506
|
+
didStartRef.current = true;
|
|
1507
|
+
let cancelled = false;
|
|
1508
|
+
let snapshotInFlight = false;
|
|
1509
|
+
let a11yPublishInFlight = false;
|
|
1510
|
+
let consecutiveCaptureFailures = 0;
|
|
1511
|
+
localParticipant.setMicrophoneEnabled(true).catch((error) => console.error("Failed to enable microphone:", error));
|
|
1512
|
+
localParticipant.publishTrack(videoTrack, {
|
|
1513
|
+
source: Track2.Source.ScreenShare,
|
|
1514
|
+
videoEncoding: {
|
|
1515
|
+
maxBitrate: CAPTURE_BITRATE,
|
|
1516
|
+
maxFramerate: CAPTURE_FPS
|
|
1517
|
+
},
|
|
1518
|
+
simulcast: false
|
|
1519
|
+
}).catch((error) => {
|
|
1520
|
+
console.error("Failed to publish DOM capture track:", error);
|
|
1521
|
+
for (const track of canvasStream.getTracks())
|
|
1522
|
+
track.stop();
|
|
1523
|
+
});
|
|
1524
|
+
const tickSnapshot = async () => {
|
|
1525
|
+
if (cancelled || snapshotInFlight)
|
|
1526
|
+
return;
|
|
1527
|
+
snapshotInFlight = true;
|
|
1528
|
+
try {
|
|
1529
|
+
await paintViewportSnapshot(canvas, ctx);
|
|
1530
|
+
if (cancelled)
|
|
1531
|
+
return;
|
|
1532
|
+
consecutiveCaptureFailures = 0;
|
|
1533
|
+
} catch {
|
|
1534
|
+
if (cancelled)
|
|
1535
|
+
return;
|
|
1536
|
+
consecutiveCaptureFailures += 1;
|
|
1537
|
+
if (consecutiveCaptureFailures === CONSECUTIVE_FAILURES_BEFORE_REPORT) {
|
|
1538
|
+
reportCaptureError(localParticipant);
|
|
1539
|
+
}
|
|
1540
|
+
} finally {
|
|
1541
|
+
snapshotInFlight = false;
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
const tickA11yPublish = async () => {
|
|
1545
|
+
if (cancelled || a11yPublishInFlight)
|
|
1546
|
+
return;
|
|
1547
|
+
a11yPublishInFlight = true;
|
|
1548
|
+
try {
|
|
1549
|
+
const frame = await buildDomSnapshotFrame();
|
|
1550
|
+
if (cancelled)
|
|
1551
|
+
return;
|
|
1552
|
+
await localParticipant.publishData(frame, {
|
|
1553
|
+
reliable: true,
|
|
1554
|
+
topic: DOM_SNAPSHOT_TOPIC
|
|
1555
|
+
});
|
|
1556
|
+
} catch {} finally {
|
|
1557
|
+
a11yPublishInFlight = false;
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
const cleanupDomEventListeners = installDomEventListeners(localParticipant, {
|
|
1561
|
+
onTriggerEvent: () => {
|
|
1562
|
+
tickA11yPublish();
|
|
1563
|
+
tickSnapshot();
|
|
1564
|
+
}
|
|
1565
|
+
});
|
|
1566
|
+
tickSnapshot();
|
|
1567
|
+
tickA11yPublish();
|
|
1568
|
+
const snapshotTimer = setInterval(tickSnapshot, SNAPSHOT_INTERVAL_MS);
|
|
1569
|
+
const a11yPublishTimer = setInterval(tickA11yPublish, A11Y_PUBLISH_INTERVAL_MS);
|
|
1570
|
+
return () => {
|
|
1571
|
+
cancelled = true;
|
|
1572
|
+
didStartRef.current = false;
|
|
1573
|
+
clearInterval(snapshotTimer);
|
|
1574
|
+
clearInterval(a11yPublishTimer);
|
|
1575
|
+
cleanupDomEventListeners();
|
|
1576
|
+
unpublishAndStopTrack(localParticipant, videoTrack);
|
|
1577
|
+
for (const track of canvasStream.getTracks())
|
|
1578
|
+
track.stop();
|
|
1579
|
+
};
|
|
1580
|
+
}, [connectionState, localParticipant]);
|
|
1581
|
+
return null;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
// src/components/MinimizedBubble.tsx
|
|
1585
|
+
import { useEffect as useEffect5 } from "react";
|
|
1586
|
+
|
|
600
1587
|
// src/lib/utils.ts
|
|
601
1588
|
import { clsx } from "clsx";
|
|
602
1589
|
import { twMerge } from "tailwind-merge";
|
|
@@ -606,12 +1593,12 @@ function cn(...inputs) {
|
|
|
606
1593
|
|
|
607
1594
|
// src/components/Logo.tsx
|
|
608
1595
|
import { useId } from "react";
|
|
609
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1596
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
610
1597
|
function Logo({ className }) {
|
|
611
1598
|
const reactId = useId().replace(/:/g, "");
|
|
612
1599
|
const clipId = `skippr-logo-clip-${reactId}`;
|
|
613
1600
|
const gradientId = `skippr-logo-gradient-${reactId}`;
|
|
614
|
-
return /* @__PURE__ */
|
|
1601
|
+
return /* @__PURE__ */ jsxs2("svg", {
|
|
615
1602
|
width: "1em",
|
|
616
1603
|
height: "1em",
|
|
617
1604
|
viewBox: "0 0 30 30",
|
|
@@ -621,14 +1608,14 @@ function Logo({ className }) {
|
|
|
621
1608
|
"aria-label": "Skippr",
|
|
622
1609
|
className,
|
|
623
1610
|
children: [
|
|
624
|
-
/* @__PURE__ */
|
|
1611
|
+
/* @__PURE__ */ jsxs2("g", {
|
|
625
1612
|
clipPath: `url(#${clipId})`,
|
|
626
1613
|
children: [
|
|
627
|
-
/* @__PURE__ */
|
|
1614
|
+
/* @__PURE__ */ jsx2("path", {
|
|
628
1615
|
d: "M0 10C0 4.47715 4.47715 0 10 0H20C25.5228 0 30 4.47715 30 10V20C30 25.5228 25.5228 30 20 30H10C4.47715 30 0 25.5228 0 20V10Z",
|
|
629
1616
|
fill: "#2D2D3F"
|
|
630
1617
|
}),
|
|
631
|
-
/* @__PURE__ */
|
|
1618
|
+
/* @__PURE__ */ jsx2("rect", {
|
|
632
1619
|
x: "7.83325",
|
|
633
1620
|
y: "14.9404",
|
|
634
1621
|
width: "12.4083",
|
|
@@ -637,11 +1624,11 @@ function Logo({ className }) {
|
|
|
637
1624
|
transform: "rotate(-45 7.83325 14.9404)",
|
|
638
1625
|
fill: "#52FFF9"
|
|
639
1626
|
}),
|
|
640
|
-
/* @__PURE__ */
|
|
1627
|
+
/* @__PURE__ */ jsx2("path", {
|
|
641
1628
|
d: "M18.8975 12.5928C20.2728 12.5928 21.3877 13.647 21.3877 14.9474C21.3877 16.2479 20.2728 17.3021 18.8975 17.3021L11.4269 17.3021C10.0516 17.3021 8.93665 16.2479 8.93665 14.9474C8.93665 13.647 10.0516 12.5928 11.4269 12.5928L18.8975 12.5928Z",
|
|
642
1629
|
fill: `url(#${gradientId})`
|
|
643
1630
|
}),
|
|
644
|
-
/* @__PURE__ */
|
|
1631
|
+
/* @__PURE__ */ jsx2("rect", {
|
|
645
1632
|
x: "10.1665",
|
|
646
1633
|
y: "20.4404",
|
|
647
1634
|
width: "12.4083",
|
|
@@ -652,9 +1639,9 @@ function Logo({ className }) {
|
|
|
652
1639
|
})
|
|
653
1640
|
]
|
|
654
1641
|
}),
|
|
655
|
-
/* @__PURE__ */
|
|
1642
|
+
/* @__PURE__ */ jsxs2("defs", {
|
|
656
1643
|
children: [
|
|
657
|
-
/* @__PURE__ */
|
|
1644
|
+
/* @__PURE__ */ jsxs2("linearGradient", {
|
|
658
1645
|
id: gradientId,
|
|
659
1646
|
x1: "18.9237",
|
|
660
1647
|
y1: "16.9997",
|
|
@@ -662,19 +1649,19 @@ function Logo({ className }) {
|
|
|
662
1649
|
y2: "14.1904",
|
|
663
1650
|
gradientUnits: "userSpaceOnUse",
|
|
664
1651
|
children: [
|
|
665
|
-
/* @__PURE__ */
|
|
1652
|
+
/* @__PURE__ */ jsx2("stop", {
|
|
666
1653
|
offset: "0.473958",
|
|
667
1654
|
stopColor: "white"
|
|
668
1655
|
}),
|
|
669
|
-
/* @__PURE__ */
|
|
1656
|
+
/* @__PURE__ */ jsx2("stop", {
|
|
670
1657
|
offset: "1",
|
|
671
1658
|
stopColor: "#52FFF9"
|
|
672
1659
|
})
|
|
673
1660
|
]
|
|
674
1661
|
}),
|
|
675
|
-
/* @__PURE__ */
|
|
1662
|
+
/* @__PURE__ */ jsx2("clipPath", {
|
|
676
1663
|
id: clipId,
|
|
677
|
-
children: /* @__PURE__ */
|
|
1664
|
+
children: /* @__PURE__ */ jsx2("rect", {
|
|
678
1665
|
width: "30",
|
|
679
1666
|
height: "30",
|
|
680
1667
|
fill: "white"
|
|
@@ -687,18 +1674,18 @@ function Logo({ className }) {
|
|
|
687
1674
|
}
|
|
688
1675
|
|
|
689
1676
|
// src/components/ui/tooltip.tsx
|
|
690
|
-
import { jsx as
|
|
1677
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
691
1678
|
var ALIGN_CLASSES = {
|
|
692
1679
|
center: "skippr:left-1/2 skippr:-translate-x-1/2",
|
|
693
1680
|
start: "skippr:left-0",
|
|
694
1681
|
end: "skippr:right-0"
|
|
695
1682
|
};
|
|
696
1683
|
function Tooltip({ label, children, position = "top", align = "center" }) {
|
|
697
|
-
return /* @__PURE__ */
|
|
1684
|
+
return /* @__PURE__ */ jsxs3("span", {
|
|
698
1685
|
className: "skippr:relative skippr:inline-flex skippr:group",
|
|
699
1686
|
children: [
|
|
700
1687
|
children,
|
|
701
|
-
/* @__PURE__ */
|
|
1688
|
+
/* @__PURE__ */ jsx3("span", {
|
|
702
1689
|
className: cn("skippr:pointer-events-none skippr:absolute skippr:z-10", "skippr:whitespace-nowrap skippr:rounded-md skippr:bg-foreground skippr:px-2 skippr:py-1", "skippr:text-[11px] skippr:text-background skippr:font-medium", "skippr:opacity-0 skippr:scale-95 skippr:transition-all skippr:duration-150", "skippr:group-hover:opacity-100 skippr:group-hover:scale-100", "skippr:group-focus-within:opacity-100 skippr:group-focus-within:scale-100", ALIGN_CLASSES[align], position === "top" && "skippr:bottom-full skippr:mb-1.5", position === "bottom" && "skippr:top-full skippr:mt-1.5"),
|
|
703
1690
|
"aria-hidden": "true",
|
|
704
1691
|
children: label
|
|
@@ -708,111 +1695,68 @@ function Tooltip({ label, children, position = "top", align = "center" }) {
|
|
|
708
1695
|
}
|
|
709
1696
|
|
|
710
1697
|
// src/components/MinimizedBubble.tsx
|
|
711
|
-
import { jsx as
|
|
1698
|
+
import { jsx as jsx4, jsxs as jsxs4, Fragment as Fragment2 } from "react/jsx-runtime";
|
|
712
1699
|
var BUBBLE_BUTTON = "skippr:flex skippr:size-12 skippr:cursor-pointer skippr:items-center skippr:justify-center skippr:rounded-[14px] skippr:shadow-[0_4px_16px_rgba(45,43,61,0.45),0_2px_4px_rgba(0,0,0,0.1)] skippr:transition-all skippr:hover:-translate-y-0.5 skippr:active:translate-y-0";
|
|
713
|
-
function AgentBubbleContent({ agentState }) {
|
|
714
|
-
if (agentState === "speaking") {
|
|
715
|
-
return /* @__PURE__ */ jsxs3("div", {
|
|
716
|
-
className: "skippr:flex skippr:h-5 skippr:items-center skippr:justify-center skippr:gap-[3px]",
|
|
717
|
-
children: [
|
|
718
|
-
/* @__PURE__ */ jsx3("span", {
|
|
719
|
-
className: "skippr:w-[3px] skippr:h-2 skippr:rounded-sm skippr:bg-white skippr:animate-[skippr-speak_1.1s_ease-in-out_infinite]"
|
|
720
|
-
}),
|
|
721
|
-
/* @__PURE__ */ jsx3("span", {
|
|
722
|
-
className: "skippr:w-[3px] skippr:h-3.5 skippr:rounded-sm skippr:bg-white skippr:animate-[skippr-speak_1.1s_ease-in-out_0.15s_infinite]"
|
|
723
|
-
}),
|
|
724
|
-
/* @__PURE__ */ jsx3("span", {
|
|
725
|
-
className: "skippr:w-[3px] skippr:h-2.5 skippr:rounded-sm skippr:bg-white skippr:animate-[skippr-speak_1.1s_ease-in-out_0.3s_infinite]"
|
|
726
|
-
}),
|
|
727
|
-
/* @__PURE__ */ jsx3("span", {
|
|
728
|
-
className: "skippr:w-[3px] skippr:h-4 skippr:rounded-sm skippr:bg-white skippr:animate-[skippr-speak_1.1s_ease-in-out_0.45s_infinite]"
|
|
729
|
-
}),
|
|
730
|
-
/* @__PURE__ */ jsx3("span", {
|
|
731
|
-
className: "skippr:w-[3px] skippr:h-1.5 skippr:rounded-sm skippr:bg-white skippr:animate-[skippr-speak_1.1s_ease-in-out_0.6s_infinite]"
|
|
732
|
-
})
|
|
733
|
-
]
|
|
734
|
-
});
|
|
735
|
-
}
|
|
736
|
-
if (agentState === "thinking") {
|
|
737
|
-
return /* @__PURE__ */ jsxs3("div", {
|
|
738
|
-
className: "skippr:flex skippr:items-center skippr:justify-center skippr:gap-[4px]",
|
|
739
|
-
children: [
|
|
740
|
-
/* @__PURE__ */ jsx3("span", {
|
|
741
|
-
className: "skippr:size-1.5 skippr:rounded-full skippr:bg-white skippr:animate-bounce skippr:[animation-delay:0ms]"
|
|
742
|
-
}),
|
|
743
|
-
/* @__PURE__ */ jsx3("span", {
|
|
744
|
-
className: "skippr:size-1.5 skippr:rounded-full skippr:bg-white skippr:animate-bounce skippr:[animation-delay:150ms]"
|
|
745
|
-
}),
|
|
746
|
-
/* @__PURE__ */ jsx3("span", {
|
|
747
|
-
className: "skippr:size-1.5 skippr:rounded-full skippr:bg-white skippr:animate-bounce skippr:[animation-delay:300ms]"
|
|
748
|
-
})
|
|
749
|
-
]
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
return /* @__PURE__ */ jsx3(Logo, {
|
|
753
|
-
className: "skippr:size-7"
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
1700
|
function ConnectedBubbleContent() {
|
|
757
|
-
const { expandPanel, disconnect, position } = useLiveAgent();
|
|
758
|
-
const { state: agentState } = useAgentVoiceState();
|
|
1701
|
+
const { expandPanel, disconnect, position, captureMode } = useLiveAgent();
|
|
759
1702
|
const { isMuted, toggleMute, isScreenSharing, toggleScreenShare } = useMediaControls();
|
|
760
1703
|
const tooltipAlign = position === "right" ? "end" : "start";
|
|
761
|
-
|
|
1704
|
+
const showScreenShareToggle = captureMode === "screenshare";
|
|
1705
|
+
return /* @__PURE__ */ jsxs4(Fragment2, {
|
|
762
1706
|
children: [
|
|
763
|
-
/* @__PURE__ */
|
|
1707
|
+
/* @__PURE__ */ jsx4(Tooltip, {
|
|
764
1708
|
label: isMuted ? "Unmute" : "Mute",
|
|
765
1709
|
align: tooltipAlign,
|
|
766
|
-
children: /* @__PURE__ */
|
|
1710
|
+
children: /* @__PURE__ */ jsx4("button", {
|
|
767
1711
|
type: "button",
|
|
768
1712
|
onClick: toggleMute,
|
|
769
1713
|
"aria-label": isMuted ? "Unmute" : "Mute",
|
|
770
1714
|
className: cn(BUBBLE_BUTTON, isMuted ? "skippr:bg-destructive skippr:text-destructive-foreground skippr:hover:bg-destructive/90" : "skippr:bg-white skippr:text-foreground skippr:hover:bg-muted"),
|
|
771
|
-
children: isMuted ? /* @__PURE__ */
|
|
1715
|
+
children: isMuted ? /* @__PURE__ */ jsx4(MicOff, {
|
|
772
1716
|
className: "skippr:size-5"
|
|
773
|
-
}) : /* @__PURE__ */
|
|
1717
|
+
}) : /* @__PURE__ */ jsx4(Mic, {
|
|
774
1718
|
className: "skippr:size-5"
|
|
775
1719
|
})
|
|
776
1720
|
})
|
|
777
1721
|
}),
|
|
778
|
-
/* @__PURE__ */
|
|
1722
|
+
showScreenShareToggle && /* @__PURE__ */ jsx4(Tooltip, {
|
|
779
1723
|
label: isScreenSharing ? "Stop sharing" : "Share screen",
|
|
780
1724
|
align: tooltipAlign,
|
|
781
|
-
children: /* @__PURE__ */
|
|
1725
|
+
children: /* @__PURE__ */ jsx4("button", {
|
|
782
1726
|
type: "button",
|
|
783
1727
|
onClick: toggleScreenShare,
|
|
784
1728
|
"aria-label": isScreenSharing ? "Stop sharing screen" : "Share screen",
|
|
785
1729
|
className: cn(BUBBLE_BUTTON, isScreenSharing ? "skippr:bg-bubble skippr:text-white skippr:hover:brightness-110" : "skippr:bg-white skippr:text-foreground skippr:hover:bg-muted"),
|
|
786
|
-
children: isScreenSharing ? /* @__PURE__ */
|
|
1730
|
+
children: isScreenSharing ? /* @__PURE__ */ jsx4(MonitorOff, {
|
|
787
1731
|
className: "skippr:size-5"
|
|
788
|
-
}) : /* @__PURE__ */
|
|
1732
|
+
}) : /* @__PURE__ */ jsx4(Monitor, {
|
|
789
1733
|
className: "skippr:size-5"
|
|
790
1734
|
})
|
|
791
1735
|
})
|
|
792
1736
|
}),
|
|
793
|
-
/* @__PURE__ */
|
|
1737
|
+
/* @__PURE__ */ jsx4(Tooltip, {
|
|
794
1738
|
label: "End session",
|
|
795
1739
|
align: tooltipAlign,
|
|
796
|
-
children: /* @__PURE__ */
|
|
1740
|
+
children: /* @__PURE__ */ jsx4("button", {
|
|
797
1741
|
type: "button",
|
|
798
1742
|
onClick: () => disconnect(),
|
|
799
1743
|
"aria-label": "End session",
|
|
800
1744
|
className: cn(BUBBLE_BUTTON, "skippr:bg-destructive skippr:text-destructive-foreground skippr:hover:bg-destructive/90"),
|
|
801
|
-
children: /* @__PURE__ */
|
|
1745
|
+
children: /* @__PURE__ */ jsx4(PhoneOff, {
|
|
802
1746
|
className: "skippr:size-5"
|
|
803
1747
|
})
|
|
804
1748
|
})
|
|
805
1749
|
}),
|
|
806
|
-
/* @__PURE__ */
|
|
1750
|
+
/* @__PURE__ */ jsx4(Tooltip, {
|
|
807
1751
|
label: "Open chat & transcript",
|
|
808
1752
|
align: tooltipAlign,
|
|
809
|
-
children: /* @__PURE__ */
|
|
1753
|
+
children: /* @__PURE__ */ jsx4("button", {
|
|
810
1754
|
type: "button",
|
|
811
1755
|
onClick: expandPanel,
|
|
812
1756
|
"aria-label": "Open chat & transcript",
|
|
813
1757
|
className: cn(BUBBLE_BUTTON, "skippr:bg-bubble skippr:hover:brightness-110"),
|
|
814
|
-
children: /* @__PURE__ */
|
|
815
|
-
|
|
1758
|
+
children: /* @__PURE__ */ jsx4(Logo, {
|
|
1759
|
+
className: "skippr:size-7"
|
|
816
1760
|
})
|
|
817
1761
|
})
|
|
818
1762
|
})
|
|
@@ -822,19 +1766,19 @@ function ConnectedBubbleContent() {
|
|
|
822
1766
|
function IdleBubbleContent() {
|
|
823
1767
|
const { expandPanel, position } = useLiveAgent();
|
|
824
1768
|
const tooltipAlign = position === "right" ? "end" : "start";
|
|
825
|
-
return /* @__PURE__ */
|
|
1769
|
+
return /* @__PURE__ */ jsx4(Tooltip, {
|
|
826
1770
|
label: "Open Skippr assistant",
|
|
827
1771
|
align: tooltipAlign,
|
|
828
|
-
children: /* @__PURE__ */
|
|
1772
|
+
children: /* @__PURE__ */ jsxs4("button", {
|
|
829
1773
|
type: "button",
|
|
830
1774
|
onClick: expandPanel,
|
|
831
1775
|
"aria-label": "Skippr assistant",
|
|
832
1776
|
className: cn(BUBBLE_BUTTON, "skippr:relative skippr:bg-bubble skippr:hover:brightness-110"),
|
|
833
1777
|
children: [
|
|
834
|
-
/* @__PURE__ */
|
|
1778
|
+
/* @__PURE__ */ jsx4(Logo, {
|
|
835
1779
|
className: "skippr:relative skippr:z-10 skippr:size-7"
|
|
836
1780
|
}),
|
|
837
|
-
/* @__PURE__ */
|
|
1781
|
+
/* @__PURE__ */ jsx4("span", {
|
|
838
1782
|
className: "skippr:absolute skippr:-inset-[3px] skippr:animate-pulse skippr:rounded-[17px] skippr:border-2 skippr:border-bubble/50"
|
|
839
1783
|
})
|
|
840
1784
|
]
|
|
@@ -846,18 +1790,18 @@ function WelcomeBubble({
|
|
|
846
1790
|
position,
|
|
847
1791
|
onDismiss
|
|
848
1792
|
}) {
|
|
849
|
-
|
|
1793
|
+
useEffect5(() => {
|
|
850
1794
|
const timer = setTimeout(onDismiss, 5000);
|
|
851
1795
|
return () => clearTimeout(timer);
|
|
852
1796
|
}, [onDismiss]);
|
|
853
|
-
return /* @__PURE__ */
|
|
1797
|
+
return /* @__PURE__ */ jsxs4("button", {
|
|
854
1798
|
type: "button",
|
|
855
1799
|
className: cn("skippr:absolute skippr:bottom-full skippr:mb-3", "skippr:max-w-64 skippr:rounded-xl skippr:bg-card skippr:shadow-lg", "skippr:border skippr:border-border skippr:px-4 skippr:py-3", "skippr:text-sm skippr:text-foreground skippr:leading-relaxed skippr:text-left", "skippr:animate-[skippr-fade-in_0.3s_ease-out]", "skippr:cursor-pointer", position === "right" ? "skippr:right-0" : "skippr:left-0"),
|
|
856
1800
|
onClick: onDismiss,
|
|
857
1801
|
"aria-label": "Dismiss",
|
|
858
1802
|
children: [
|
|
859
1803
|
message,
|
|
860
|
-
/* @__PURE__ */
|
|
1804
|
+
/* @__PURE__ */ jsx4("span", {
|
|
861
1805
|
className: cn("skippr:absolute skippr:top-full skippr:size-2.5", "skippr:border-l skippr:border-t skippr:border-border skippr:bg-card", "skippr:rotate-[225deg]", position === "right" ? "skippr:right-5" : "skippr:left-5", "skippr:-mt-[5px]")
|
|
862
1806
|
})
|
|
863
1807
|
]
|
|
@@ -870,61 +1814,27 @@ function MinimizedBubble({
|
|
|
870
1814
|
}) {
|
|
871
1815
|
const { isConnected, isStarting, position } = useLiveAgent();
|
|
872
1816
|
const inSession = isConnected || isStarting;
|
|
873
|
-
return /* @__PURE__ */
|
|
1817
|
+
return /* @__PURE__ */ jsxs4("div", {
|
|
874
1818
|
className: cn("skippr:fixed skippr:bottom-6 skippr:z-[9999]", "skippr:flex skippr:items-center skippr:gap-2", position === "right" ? "skippr:right-6" : "skippr:left-6"),
|
|
875
1819
|
children: [
|
|
876
|
-
welcomeMessage && !inSession && !welcomeDismissed && /* @__PURE__ */
|
|
1820
|
+
welcomeMessage && !inSession && !welcomeDismissed && /* @__PURE__ */ jsx4(WelcomeBubble, {
|
|
877
1821
|
message: welcomeMessage,
|
|
878
1822
|
position,
|
|
879
1823
|
onDismiss: onDismissWelcome
|
|
880
1824
|
}),
|
|
881
|
-
inSession ? /* @__PURE__ */
|
|
1825
|
+
inSession ? /* @__PURE__ */ jsx4(ConnectedBubbleContent, {}) : /* @__PURE__ */ jsx4(IdleBubbleContent, {})
|
|
882
1826
|
]
|
|
883
1827
|
});
|
|
884
1828
|
}
|
|
885
1829
|
|
|
886
|
-
// src/components/ObservingBanner.tsx
|
|
887
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
888
|
-
function ObservingBanner() {
|
|
889
|
-
const { isConnected } = useLiveAgent();
|
|
890
|
-
const { isScreenSharing } = useMediaControls();
|
|
891
|
-
if (!isConnected || !isScreenSharing)
|
|
892
|
-
return null;
|
|
893
|
-
return /* @__PURE__ */ jsx4("div", {
|
|
894
|
-
className: "skippr:fixed skippr:top-0 skippr:left-0 skippr:right-0 skippr:z-[2147483647] skippr:flex skippr:justify-center skippr:pointer-events-none",
|
|
895
|
-
children: /* @__PURE__ */ jsxs4("div", {
|
|
896
|
-
className: "skippr:pointer-events-auto skippr:flex skippr:items-center skippr:gap-2 skippr:bg-indigo-500/95 skippr:backdrop-blur-sm skippr:text-white skippr:text-xs skippr:font-medium skippr:px-4 skippr:py-1.5 skippr:rounded-b-lg skippr:shadow-lg",
|
|
897
|
-
children: [
|
|
898
|
-
/* @__PURE__ */ jsxs4("span", {
|
|
899
|
-
className: "skippr:relative skippr:flex skippr:size-1.5",
|
|
900
|
-
children: [
|
|
901
|
-
/* @__PURE__ */ jsx4("span", {
|
|
902
|
-
className: "skippr:absolute skippr:inline-flex skippr:size-full skippr:animate-ping skippr:rounded-full skippr:bg-emerald-400 skippr:opacity-75"
|
|
903
|
-
}),
|
|
904
|
-
/* @__PURE__ */ jsx4("span", {
|
|
905
|
-
className: "skippr:relative skippr:inline-flex skippr:size-1.5 skippr:rounded-full skippr:bg-emerald-400"
|
|
906
|
-
})
|
|
907
|
-
]
|
|
908
|
-
}),
|
|
909
|
-
/* @__PURE__ */ jsx4(Eye, {
|
|
910
|
-
className: "skippr:size-3.5"
|
|
911
|
-
}),
|
|
912
|
-
/* @__PURE__ */ jsx4("span", {
|
|
913
|
-
children: "Skippr is observing this page"
|
|
914
|
-
})
|
|
915
|
-
]
|
|
916
|
-
})
|
|
917
|
-
});
|
|
918
|
-
}
|
|
919
|
-
|
|
920
1830
|
// src/components/Sidebar.tsx
|
|
921
|
-
import { useEffect as
|
|
1831
|
+
import { useEffect as useEffect12 } from "react";
|
|
922
1832
|
|
|
923
1833
|
// src/hooks/useCombinedMessages.ts
|
|
924
1834
|
import { useMemo as useMemo4 } from "react";
|
|
925
1835
|
|
|
926
1836
|
// src/hooks/useChatMessages.ts
|
|
927
|
-
import { useChat, useLocalParticipant as
|
|
1837
|
+
import { useChat, useLocalParticipant as useLocalParticipant4 } from "@livekit/components-react/hooks";
|
|
928
1838
|
import { useMemo as useMemo2 } from "react";
|
|
929
1839
|
|
|
930
1840
|
// src/lib/filterSystemMessages.ts
|
|
@@ -936,7 +1846,7 @@ function filterSystemMessages(messages) {
|
|
|
936
1846
|
// src/hooks/useChatMessages.ts
|
|
937
1847
|
function useChatMessages() {
|
|
938
1848
|
const { chatMessages: rawMessages, send, isSending } = useChat();
|
|
939
|
-
const { localParticipant } =
|
|
1849
|
+
const { localParticipant } = useLocalParticipant4();
|
|
940
1850
|
const localIdentity = localParticipant.identity;
|
|
941
1851
|
const chatMessages = useMemo2(() => {
|
|
942
1852
|
const sortedMessages = rawMessages.map((msg) => ({
|
|
@@ -952,11 +1862,11 @@ function useChatMessages() {
|
|
|
952
1862
|
}
|
|
953
1863
|
|
|
954
1864
|
// src/hooks/useStreamingTranscript.ts
|
|
955
|
-
import { useLocalParticipant as
|
|
1865
|
+
import { useLocalParticipant as useLocalParticipant5, useTranscriptions } from "@livekit/components-react/hooks";
|
|
956
1866
|
import { useMemo as useMemo3 } from "react";
|
|
957
1867
|
function useStreamingTranscript() {
|
|
958
1868
|
const transcriptions = useTranscriptions();
|
|
959
|
-
const { localParticipant } =
|
|
1869
|
+
const { localParticipant } = useLocalParticipant5();
|
|
960
1870
|
const localIdentity = localParticipant.identity;
|
|
961
1871
|
const transcriptMessages = useMemo3(() => filterSystemMessages(transcriptions.filter((stream) => stream.text.trim().length > 0).map((stream) => ({
|
|
962
1872
|
id: stream.streamInfo.id,
|
|
@@ -1003,12 +1913,12 @@ function useCombinedMessages() {
|
|
|
1003
1913
|
import { useCallback as useCallback4 } from "react";
|
|
1004
1914
|
|
|
1005
1915
|
// src/hooks/useAgentState.ts
|
|
1006
|
-
import { useRemoteParticipants } from "@livekit/components-react";
|
|
1007
|
-
import { useEffect as
|
|
1916
|
+
import { useRemoteParticipants } from "@livekit/components-react/hooks";
|
|
1917
|
+
import { useEffect as useEffect6, useState as useState3 } from "react";
|
|
1008
1918
|
function useAgentState(attributeKey, parse, initial) {
|
|
1009
1919
|
const [value, setValue] = useState3(initial);
|
|
1010
1920
|
const remoteParticipants = useRemoteParticipants();
|
|
1011
|
-
|
|
1921
|
+
useEffect6(() => {
|
|
1012
1922
|
const agentParticipant = remoteParticipants.find((p) => p.attributes?.[attributeKey]);
|
|
1013
1923
|
if (agentParticipant) {
|
|
1014
1924
|
const attr = agentParticipant.attributes?.[attributeKey];
|
|
@@ -1060,7 +1970,7 @@ function usePhaseUpdates() {
|
|
|
1060
1970
|
}
|
|
1061
1971
|
|
|
1062
1972
|
// src/hooks/useSessionRemaining.ts
|
|
1063
|
-
import { useEffect as
|
|
1973
|
+
import { useEffect as useEffect7, useRef as useRef3, useState as useState4 } from "react";
|
|
1064
1974
|
|
|
1065
1975
|
// src/lib/format.ts
|
|
1066
1976
|
function formatTime(seconds) {
|
|
@@ -1076,9 +1986,9 @@ function parseNumber(s) {
|
|
|
1076
1986
|
// src/hooks/useSessionRemaining.ts
|
|
1077
1987
|
function useSessionRemaining() {
|
|
1078
1988
|
const maxCallDuration = useAgentState("maxCallDuration", parseNumber, null);
|
|
1079
|
-
const endTimeRef =
|
|
1989
|
+
const endTimeRef = useRef3(null);
|
|
1080
1990
|
const [remaining, setRemaining] = useState4(null);
|
|
1081
|
-
|
|
1991
|
+
useEffect7(() => {
|
|
1082
1992
|
if (maxCallDuration === null || endTimeRef.current !== null)
|
|
1083
1993
|
return;
|
|
1084
1994
|
const endTime = Date.now() + maxCallDuration * 1000;
|
|
@@ -1094,14 +2004,11 @@ function useSessionRemaining() {
|
|
|
1094
2004
|
return remaining;
|
|
1095
2005
|
}
|
|
1096
2006
|
|
|
1097
|
-
// src/lib/constants.ts
|
|
1098
|
-
var SIDEBAR_WIDTH = 360;
|
|
1099
|
-
|
|
1100
2007
|
// src/hooks/useElapsedSeconds.ts
|
|
1101
|
-
import { useEffect as
|
|
2008
|
+
import { useEffect as useEffect8, useState as useState5 } from "react";
|
|
1102
2009
|
function useElapsedSeconds(isRunning) {
|
|
1103
2010
|
const [elapsed, setElapsed] = useState5(0);
|
|
1104
|
-
|
|
2011
|
+
useEffect8(() => {
|
|
1105
2012
|
if (!isRunning) {
|
|
1106
2013
|
setElapsed(0);
|
|
1107
2014
|
return;
|
|
@@ -1199,7 +2106,7 @@ function LoadingDots({ label }) {
|
|
|
1199
2106
|
}
|
|
1200
2107
|
|
|
1201
2108
|
// src/components/LoginFlow.tsx
|
|
1202
|
-
import { useCallback as useCallback5, useEffect as
|
|
2109
|
+
import { useCallback as useCallback5, useEffect as useEffect9, useRef as useRef4, useState as useState6 } from "react";
|
|
1203
2110
|
|
|
1204
2111
|
// src/components/ui/button.tsx
|
|
1205
2112
|
import { forwardRef as forwardRef3 } from "react";
|
|
@@ -1327,16 +2234,16 @@ function EmailStep({ email, onEmailChange, onSubmit, error, isSubmitting }) {
|
|
|
1327
2234
|
function OtpStep({ email, onSubmit, onResend, onBack, error, isSubmitting }) {
|
|
1328
2235
|
const [digits, setDigits] = useState6(Array(OTP_LENGTH).fill(""));
|
|
1329
2236
|
const [resendCooldown, setResendCooldown] = useState6(0);
|
|
1330
|
-
const inputRefs =
|
|
1331
|
-
const submittedRef =
|
|
1332
|
-
|
|
2237
|
+
const inputRefs = useRef4([]);
|
|
2238
|
+
const submittedRef = useRef4(false);
|
|
2239
|
+
useEffect9(() => {
|
|
1333
2240
|
inputRefs.current[0]?.focus();
|
|
1334
2241
|
}, []);
|
|
1335
|
-
|
|
2242
|
+
useEffect9(() => {
|
|
1336
2243
|
if (error)
|
|
1337
2244
|
submittedRef.current = false;
|
|
1338
2245
|
}, [error]);
|
|
1339
|
-
|
|
2246
|
+
useEffect9(() => {
|
|
1340
2247
|
if (resendCooldown <= 0)
|
|
1341
2248
|
return;
|
|
1342
2249
|
const timer = setTimeout(() => setResendCooldown((c) => c - 1), 1000);
|
|
@@ -1479,7 +2386,7 @@ function OtpStep({ email, onSubmit, onResend, onBack, error, isSubmitting }) {
|
|
|
1479
2386
|
// src/components/MeetingControls.tsx
|
|
1480
2387
|
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1481
2388
|
var CONTROL_BUTTON = "skippr:flex skippr:size-11 skippr:cursor-pointer skippr:items-center skippr:justify-center skippr:rounded-full skippr:transition-colors";
|
|
1482
|
-
function MeetingControls({ onHangUp }) {
|
|
2389
|
+
function MeetingControls({ onHangUp, showScreenShareToggle = true }) {
|
|
1483
2390
|
const { isMuted, toggleMute, isScreenSharing, toggleScreenShare } = useMediaControls();
|
|
1484
2391
|
return /* @__PURE__ */ jsxs8("div", {
|
|
1485
2392
|
className: "skippr:shrink-0 skippr:border-t skippr:border-border skippr:bg-background skippr:px-4 skippr:py-4",
|
|
@@ -1498,7 +2405,7 @@ function MeetingControls({ onHangUp }) {
|
|
|
1498
2405
|
className: "skippr:size-5"
|
|
1499
2406
|
})
|
|
1500
2407
|
}),
|
|
1501
|
-
/* @__PURE__ */ jsx9("button", {
|
|
2408
|
+
showScreenShareToggle && /* @__PURE__ */ jsx9("button", {
|
|
1502
2409
|
type: "button",
|
|
1503
2410
|
onClick: toggleScreenShare,
|
|
1504
2411
|
"aria-label": isScreenSharing ? "Stop sharing screen" : "Share screen",
|
|
@@ -1529,17 +2436,17 @@ function MeetingControls({ onHangUp }) {
|
|
|
1529
2436
|
}
|
|
1530
2437
|
|
|
1531
2438
|
// src/components/MessageList.tsx
|
|
1532
|
-
import { useEffect as
|
|
2439
|
+
import { useEffect as useEffect11, useRef as useRef6 } from "react";
|
|
1533
2440
|
|
|
1534
2441
|
// src/components/ChatInput.tsx
|
|
1535
|
-
import { useEffect as
|
|
2442
|
+
import { useEffect as useEffect10, useRef as useRef5, useState as useState7 } from "react";
|
|
1536
2443
|
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1537
2444
|
var MAX_INPUT_HEIGHT = 60;
|
|
1538
2445
|
function ChatInput({ sendChatMessage, isSendingChat, autoFocus = false }) {
|
|
1539
2446
|
const [inputText, setInputText] = useState7("");
|
|
1540
|
-
const textareaRef =
|
|
2447
|
+
const textareaRef = useRef5(null);
|
|
1541
2448
|
const canSend = inputText.trim().length > 0 && !isSendingChat;
|
|
1542
|
-
|
|
2449
|
+
useEffect10(() => {
|
|
1543
2450
|
if (autoFocus)
|
|
1544
2451
|
textareaRef.current?.focus();
|
|
1545
2452
|
}, [autoFocus]);
|
|
@@ -1684,9 +2591,9 @@ function MessageList({
|
|
|
1684
2591
|
isSendingChat,
|
|
1685
2592
|
autoFocus = false
|
|
1686
2593
|
}) {
|
|
1687
|
-
const scrollRef =
|
|
2594
|
+
const scrollRef = useRef6(null);
|
|
1688
2595
|
const lastMessage = messages.length > 0 ? messages[messages.length - 1] : undefined;
|
|
1689
|
-
|
|
2596
|
+
useEffect11(() => {
|
|
1690
2597
|
scrollRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
1691
2598
|
}, [messages.length, lastMessage?.content]);
|
|
1692
2599
|
const showTyping = isStreaming && lastMessage?.role === "assistant" && lastMessage.content === "";
|
|
@@ -1719,8 +2626,8 @@ function MessageList({
|
|
|
1719
2626
|
|
|
1720
2627
|
// src/components/SessionAgenda.tsx
|
|
1721
2628
|
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1722
|
-
function SessionAgenda({ phases }) {
|
|
1723
|
-
if (phases.length === 0) {
|
|
2629
|
+
function SessionAgenda({ phases, hasStarted }) {
|
|
2630
|
+
if (phases.length === 0 || !hasStarted) {
|
|
1724
2631
|
return /* @__PURE__ */ jsx14("div", {
|
|
1725
2632
|
className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center",
|
|
1726
2633
|
children: /* @__PURE__ */ jsx14(LoadingDots, {
|
|
@@ -1816,7 +2723,7 @@ function StartSessionPrompt({
|
|
|
1816
2723
|
}
|
|
1817
2724
|
|
|
1818
2725
|
// src/components/Sidebar.tsx
|
|
1819
|
-
import { jsx as jsx17, jsxs as jsxs15, Fragment as
|
|
2726
|
+
import { jsx as jsx17, jsxs as jsxs15, Fragment as Fragment3 } from "react/jsx-runtime";
|
|
1820
2727
|
function Sidebar({
|
|
1821
2728
|
hideControls = false,
|
|
1822
2729
|
hideHeader = false,
|
|
@@ -1839,11 +2746,12 @@ function Sidebar({
|
|
|
1839
2746
|
isAuthSubmitting,
|
|
1840
2747
|
sidebarTab: activeTab,
|
|
1841
2748
|
setSidebarTab: setActiveTab,
|
|
1842
|
-
autoFocusChat
|
|
2749
|
+
autoFocusChat,
|
|
2750
|
+
captureMode
|
|
1843
2751
|
} = useLiveAgent();
|
|
1844
2752
|
const isFloating = variant === "floating";
|
|
1845
2753
|
const isSidebar = variant === "sidebar";
|
|
1846
|
-
|
|
2754
|
+
useEffect12(() => {
|
|
1847
2755
|
if (!isSidebar)
|
|
1848
2756
|
return;
|
|
1849
2757
|
const prop = position === "right" ? "marginRight" : "marginLeft";
|
|
@@ -1882,7 +2790,8 @@ function Sidebar({
|
|
|
1882
2790
|
onTabChange: setActiveTab,
|
|
1883
2791
|
hideControls,
|
|
1884
2792
|
startSessionLabel,
|
|
1885
|
-
autoFocusChat
|
|
2793
|
+
autoFocusChat,
|
|
2794
|
+
showScreenShareToggle: captureMode === "screenshare"
|
|
1886
2795
|
})
|
|
1887
2796
|
]
|
|
1888
2797
|
});
|
|
@@ -1897,9 +2806,10 @@ function AuthenticatedContent({
|
|
|
1897
2806
|
onTabChange,
|
|
1898
2807
|
hideControls,
|
|
1899
2808
|
startSessionLabel,
|
|
1900
|
-
autoFocusChat
|
|
2809
|
+
autoFocusChat,
|
|
2810
|
+
showScreenShareToggle
|
|
1901
2811
|
}) {
|
|
1902
|
-
return /* @__PURE__ */ jsxs15(
|
|
2812
|
+
return /* @__PURE__ */ jsxs15(Fragment3, {
|
|
1903
2813
|
children: [
|
|
1904
2814
|
isConnected && /* @__PURE__ */ jsx17(ConnectedBanner, {}),
|
|
1905
2815
|
/* @__PURE__ */ jsxs15("div", {
|
|
@@ -1937,7 +2847,7 @@ function AuthenticatedContent({
|
|
|
1937
2847
|
}),
|
|
1938
2848
|
/* @__PURE__ */ jsx17("div", {
|
|
1939
2849
|
className: "skippr:flex skippr:min-h-0 skippr:flex-1 skippr:flex-col",
|
|
1940
|
-
children: isConnected ? /* @__PURE__ */ jsx17(ConnectedBody, {
|
|
2850
|
+
children: isConnected || isStarting ? /* @__PURE__ */ jsx17(ConnectedBody, {
|
|
1941
2851
|
activeTab,
|
|
1942
2852
|
autoFocusChat
|
|
1943
2853
|
}) : /* @__PURE__ */ jsx17("div", {
|
|
@@ -1951,7 +2861,8 @@ function AuthenticatedContent({
|
|
|
1951
2861
|
}, `${activeTab}-empty`)
|
|
1952
2862
|
}),
|
|
1953
2863
|
isConnected && !hideControls && /* @__PURE__ */ jsx17(MeetingControls, {
|
|
1954
|
-
onHangUp: onDisconnect
|
|
2864
|
+
onHangUp: onDisconnect,
|
|
2865
|
+
showScreenShareToggle
|
|
1955
2866
|
})
|
|
1956
2867
|
]
|
|
1957
2868
|
});
|
|
@@ -1972,7 +2883,8 @@ function ConnectedBody({
|
|
|
1972
2883
|
return /* @__PURE__ */ jsx17("div", {
|
|
1973
2884
|
className: "skippr:min-h-0 skippr:flex-1 skippr:overflow-y-auto skippr:animate-skippr-tab-fade",
|
|
1974
2885
|
children: /* @__PURE__ */ jsx17(SessionAgenda, {
|
|
1975
|
-
phases
|
|
2886
|
+
phases,
|
|
2887
|
+
hasStarted: allMessages.length > 0 || agentState === "speaking"
|
|
1976
2888
|
})
|
|
1977
2889
|
}, "agenda");
|
|
1978
2890
|
}
|
|
@@ -2025,7 +2937,8 @@ function LiveAgent({
|
|
|
2025
2937
|
hideHeader = false,
|
|
2026
2938
|
startSessionLabel = "Talk to Skippr",
|
|
2027
2939
|
autoFocusChat = true,
|
|
2028
|
-
|
|
2940
|
+
showAgentStateBanner = true,
|
|
2941
|
+
captureMode = "screenshare",
|
|
2029
2942
|
children
|
|
2030
2943
|
}) {
|
|
2031
2944
|
const auth = useAuth({ appKey });
|
|
@@ -2041,6 +2954,7 @@ function LiveAgent({
|
|
|
2041
2954
|
pendingScreenStream
|
|
2042
2955
|
} = useSession({
|
|
2043
2956
|
agentId,
|
|
2957
|
+
captureMode,
|
|
2044
2958
|
authToken: effectiveAuthToken,
|
|
2045
2959
|
appKey,
|
|
2046
2960
|
userToken
|
|
@@ -2079,8 +2993,8 @@ function LiveAgent({
|
|
|
2079
2993
|
}, [minimizable]);
|
|
2080
2994
|
const isConnected = connection !== null;
|
|
2081
2995
|
const isAuthenticated = !!userToken || !!authTokenProp || auth.isAuthenticated;
|
|
2082
|
-
const prevConnectionRef =
|
|
2083
|
-
|
|
2996
|
+
const prevConnectionRef = useRef7(connection);
|
|
2997
|
+
useEffect13(() => {
|
|
2084
2998
|
const connectionChanged = prevConnectionRef.current !== connection;
|
|
2085
2999
|
prevConnectionRef.current = connection;
|
|
2086
3000
|
if (connectionChanged && minimizable) {
|
|
@@ -2117,7 +3031,8 @@ function LiveAgent({
|
|
|
2117
3031
|
isAuthSubmitting: auth.isSubmitting,
|
|
2118
3032
|
sidebarTab,
|
|
2119
3033
|
setSidebarTab,
|
|
2120
|
-
autoFocusChat
|
|
3034
|
+
autoFocusChat,
|
|
3035
|
+
captureMode
|
|
2121
3036
|
}), [
|
|
2122
3037
|
connection,
|
|
2123
3038
|
shouldConnect,
|
|
@@ -2146,7 +3061,8 @@ function LiveAgent({
|
|
|
2146
3061
|
auth.logout,
|
|
2147
3062
|
auth.isSubmitting,
|
|
2148
3063
|
sidebarTab,
|
|
2149
|
-
autoFocusChat
|
|
3064
|
+
autoFocusChat,
|
|
3065
|
+
captureMode
|
|
2150
3066
|
]);
|
|
2151
3067
|
return /* @__PURE__ */ jsx19(LiveAgentContext.Provider, {
|
|
2152
3068
|
value: ctx,
|
|
@@ -2158,20 +3074,26 @@ function LiveAgent({
|
|
|
2158
3074
|
onDisconnected: disconnect,
|
|
2159
3075
|
children: [
|
|
2160
3076
|
connection && /* @__PURE__ */ jsx19(RoomAudioRenderer, {}),
|
|
2161
|
-
|
|
2162
|
-
connection && /* @__PURE__ */ jsx19(AutoStartMedia, {
|
|
3077
|
+
connection && captureMode === "screenshare" && /* @__PURE__ */ jsx19(AutoStartMedia, {
|
|
2163
3078
|
pendingScreenStream
|
|
2164
3079
|
}),
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
3080
|
+
connection && captureMode === "auto" && /* @__PURE__ */ jsx19(DomCapture, {}),
|
|
3081
|
+
/* @__PURE__ */ jsxs16("div", {
|
|
3082
|
+
id: WIDGET_ROOT_ID,
|
|
3083
|
+
children: [
|
|
3084
|
+
showAgentStateBanner && /* @__PURE__ */ jsx19(AgentStateBanner, {}),
|
|
3085
|
+
isMinimized && /* @__PURE__ */ jsx19(MinimizedBubble, {
|
|
3086
|
+
welcomeMessage,
|
|
3087
|
+
welcomeDismissed,
|
|
3088
|
+
onDismissWelcome: dismissWelcome
|
|
3089
|
+
}),
|
|
3090
|
+
/* @__PURE__ */ jsx19(SidebarTrigger, {}),
|
|
3091
|
+
/* @__PURE__ */ jsx19(Sidebar, {
|
|
3092
|
+
hideControls,
|
|
3093
|
+
hideHeader,
|
|
3094
|
+
startSessionLabel
|
|
3095
|
+
})
|
|
3096
|
+
]
|
|
2175
3097
|
}),
|
|
2176
3098
|
children
|
|
2177
3099
|
]
|
|
@@ -2179,9 +3101,9 @@ function LiveAgent({
|
|
|
2179
3101
|
});
|
|
2180
3102
|
}
|
|
2181
3103
|
// src/hooks/useIsLocalSpeaking.ts
|
|
2182
|
-
import { useIsSpeaking, useLocalParticipant as
|
|
3104
|
+
import { useIsSpeaking, useLocalParticipant as useLocalParticipant6 } from "@livekit/components-react/hooks";
|
|
2183
3105
|
function useIsLocalSpeaking() {
|
|
2184
|
-
const { localParticipant } =
|
|
3106
|
+
const { localParticipant } = useLocalParticipant6();
|
|
2185
3107
|
return useIsSpeaking(localParticipant);
|
|
2186
3108
|
}
|
|
2187
3109
|
export {
|