@skippr/live-agent-sdk 0.27.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.
@@ -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 useEffect12, useMemo as useMemo5, useRef as useRef6, useState as useState8 } from "react";
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({ agentId, authToken, appKey, userToken }) {
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
- try {
206
- screenStream = await navigator.mediaDevices.getDisplayMedia({
207
- video: { displaySurface: "browser" }
208
- });
209
- } catch {
210
- screenStream = null;
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 {
@@ -283,6 +299,15 @@ function useSession({ agentId, authToken, appKey, userToken }) {
283
299
  pendingScreenStream
284
300
  };
285
301
  }
302
+
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;
286
311
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/createLucideIcon.js
287
312
  import { forwardRef as forwardRef2, createElement as createElement3 } from "react";
288
313
 
@@ -513,7 +538,7 @@ var __iconNode16 = [
513
538
  ];
514
539
  var Send = createLucideIcon("send", __iconNode16);
515
540
  // src/hooks/useAgentVoiceState.ts
516
- import { useVoiceAssistant } from "@livekit/components-react";
541
+ import { useVoiceAssistant } from "@livekit/components-react/hooks";
517
542
  function useAgentVoiceState() {
518
543
  const { state } = useVoiceAssistant();
519
544
  return {
@@ -535,7 +560,7 @@ function useLiveAgent() {
535
560
  }
536
561
 
537
562
  // src/hooks/useMediaControls.ts
538
- import { useLocalParticipant } from "@livekit/components-react";
563
+ import { useLocalParticipant } from "@livekit/components-react/hooks";
539
564
  import { ScreenSharePresets } from "livekit-client";
540
565
  import { useCallback as useCallback3 } from "react";
541
566
  var SCREEN_SHARE_OPTIONS = {
@@ -682,7 +707,7 @@ function SpeakingBars() {
682
707
  }
683
708
 
684
709
  // src/components/AutoStartMedia.tsx
685
- import { useConnectionState, useLocalParticipant as useLocalParticipant2 } from "@livekit/components-react";
710
+ import { useConnectionState, useLocalParticipant as useLocalParticipant2 } from "@livekit/components-react/hooks";
686
711
  import { ConnectionState, Track } from "livekit-client";
687
712
  import { useEffect as useEffect3, useRef } from "react";
688
713
  function AutoStartMedia({ pendingScreenStream }) {
@@ -711,8 +736,853 @@ function AutoStartMedia({ pendingScreenStream }) {
711
736
  return null;
712
737
  }
713
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
+
714
1584
  // src/components/MinimizedBubble.tsx
715
- import { useEffect as useEffect4 } from "react";
1585
+ import { useEffect as useEffect5 } from "react";
716
1586
 
717
1587
  // src/lib/utils.ts
718
1588
  import { clsx } from "clsx";
@@ -828,9 +1698,10 @@ function Tooltip({ label, children, position = "top", align = "center" }) {
828
1698
  import { jsx as jsx4, jsxs as jsxs4, Fragment as Fragment2 } from "react/jsx-runtime";
829
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";
830
1700
  function ConnectedBubbleContent() {
831
- const { expandPanel, disconnect, position } = useLiveAgent();
1701
+ const { expandPanel, disconnect, position, captureMode } = useLiveAgent();
832
1702
  const { isMuted, toggleMute, isScreenSharing, toggleScreenShare } = useMediaControls();
833
1703
  const tooltipAlign = position === "right" ? "end" : "start";
1704
+ const showScreenShareToggle = captureMode === "screenshare";
834
1705
  return /* @__PURE__ */ jsxs4(Fragment2, {
835
1706
  children: [
836
1707
  /* @__PURE__ */ jsx4(Tooltip, {
@@ -848,7 +1719,7 @@ function ConnectedBubbleContent() {
848
1719
  })
849
1720
  })
850
1721
  }),
851
- /* @__PURE__ */ jsx4(Tooltip, {
1722
+ showScreenShareToggle && /* @__PURE__ */ jsx4(Tooltip, {
852
1723
  label: isScreenSharing ? "Stop sharing" : "Share screen",
853
1724
  align: tooltipAlign,
854
1725
  children: /* @__PURE__ */ jsx4("button", {
@@ -919,7 +1790,7 @@ function WelcomeBubble({
919
1790
  position,
920
1791
  onDismiss
921
1792
  }) {
922
- useEffect4(() => {
1793
+ useEffect5(() => {
923
1794
  const timer = setTimeout(onDismiss, 5000);
924
1795
  return () => clearTimeout(timer);
925
1796
  }, [onDismiss]);
@@ -957,13 +1828,13 @@ function MinimizedBubble({
957
1828
  }
958
1829
 
959
1830
  // src/components/Sidebar.tsx
960
- import { useEffect as useEffect11 } from "react";
1831
+ import { useEffect as useEffect12 } from "react";
961
1832
 
962
1833
  // src/hooks/useCombinedMessages.ts
963
1834
  import { useMemo as useMemo4 } from "react";
964
1835
 
965
1836
  // src/hooks/useChatMessages.ts
966
- import { useChat, useLocalParticipant as useLocalParticipant3 } from "@livekit/components-react";
1837
+ import { useChat, useLocalParticipant as useLocalParticipant4 } from "@livekit/components-react/hooks";
967
1838
  import { useMemo as useMemo2 } from "react";
968
1839
 
969
1840
  // src/lib/filterSystemMessages.ts
@@ -975,7 +1846,7 @@ function filterSystemMessages(messages) {
975
1846
  // src/hooks/useChatMessages.ts
976
1847
  function useChatMessages() {
977
1848
  const { chatMessages: rawMessages, send, isSending } = useChat();
978
- const { localParticipant } = useLocalParticipant3();
1849
+ const { localParticipant } = useLocalParticipant4();
979
1850
  const localIdentity = localParticipant.identity;
980
1851
  const chatMessages = useMemo2(() => {
981
1852
  const sortedMessages = rawMessages.map((msg) => ({
@@ -991,11 +1862,11 @@ function useChatMessages() {
991
1862
  }
992
1863
 
993
1864
  // src/hooks/useStreamingTranscript.ts
994
- import { useLocalParticipant as useLocalParticipant4, useTranscriptions } from "@livekit/components-react";
1865
+ import { useLocalParticipant as useLocalParticipant5, useTranscriptions } from "@livekit/components-react/hooks";
995
1866
  import { useMemo as useMemo3 } from "react";
996
1867
  function useStreamingTranscript() {
997
1868
  const transcriptions = useTranscriptions();
998
- const { localParticipant } = useLocalParticipant4();
1869
+ const { localParticipant } = useLocalParticipant5();
999
1870
  const localIdentity = localParticipant.identity;
1000
1871
  const transcriptMessages = useMemo3(() => filterSystemMessages(transcriptions.filter((stream) => stream.text.trim().length > 0).map((stream) => ({
1001
1872
  id: stream.streamInfo.id,
@@ -1042,12 +1913,12 @@ function useCombinedMessages() {
1042
1913
  import { useCallback as useCallback4 } from "react";
1043
1914
 
1044
1915
  // src/hooks/useAgentState.ts
1045
- import { useRemoteParticipants } from "@livekit/components-react";
1046
- import { useEffect as useEffect5, useState as useState3 } from "react";
1916
+ import { useRemoteParticipants } from "@livekit/components-react/hooks";
1917
+ import { useEffect as useEffect6, useState as useState3 } from "react";
1047
1918
  function useAgentState(attributeKey, parse, initial) {
1048
1919
  const [value, setValue] = useState3(initial);
1049
1920
  const remoteParticipants = useRemoteParticipants();
1050
- useEffect5(() => {
1921
+ useEffect6(() => {
1051
1922
  const agentParticipant = remoteParticipants.find((p) => p.attributes?.[attributeKey]);
1052
1923
  if (agentParticipant) {
1053
1924
  const attr = agentParticipant.attributes?.[attributeKey];
@@ -1099,7 +1970,7 @@ function usePhaseUpdates() {
1099
1970
  }
1100
1971
 
1101
1972
  // src/hooks/useSessionRemaining.ts
1102
- import { useEffect as useEffect6, useRef as useRef2, useState as useState4 } from "react";
1973
+ import { useEffect as useEffect7, useRef as useRef3, useState as useState4 } from "react";
1103
1974
 
1104
1975
  // src/lib/format.ts
1105
1976
  function formatTime(seconds) {
@@ -1115,9 +1986,9 @@ function parseNumber(s) {
1115
1986
  // src/hooks/useSessionRemaining.ts
1116
1987
  function useSessionRemaining() {
1117
1988
  const maxCallDuration = useAgentState("maxCallDuration", parseNumber, null);
1118
- const endTimeRef = useRef2(null);
1989
+ const endTimeRef = useRef3(null);
1119
1990
  const [remaining, setRemaining] = useState4(null);
1120
- useEffect6(() => {
1991
+ useEffect7(() => {
1121
1992
  if (maxCallDuration === null || endTimeRef.current !== null)
1122
1993
  return;
1123
1994
  const endTime = Date.now() + maxCallDuration * 1000;
@@ -1133,14 +2004,11 @@ function useSessionRemaining() {
1133
2004
  return remaining;
1134
2005
  }
1135
2006
 
1136
- // src/lib/constants.ts
1137
- var SIDEBAR_WIDTH = 360;
1138
-
1139
2007
  // src/hooks/useElapsedSeconds.ts
1140
- import { useEffect as useEffect7, useState as useState5 } from "react";
2008
+ import { useEffect as useEffect8, useState as useState5 } from "react";
1141
2009
  function useElapsedSeconds(isRunning) {
1142
2010
  const [elapsed, setElapsed] = useState5(0);
1143
- useEffect7(() => {
2011
+ useEffect8(() => {
1144
2012
  if (!isRunning) {
1145
2013
  setElapsed(0);
1146
2014
  return;
@@ -1238,7 +2106,7 @@ function LoadingDots({ label }) {
1238
2106
  }
1239
2107
 
1240
2108
  // src/components/LoginFlow.tsx
1241
- import { useCallback as useCallback5, useEffect as useEffect8, useRef as useRef3, useState as useState6 } from "react";
2109
+ import { useCallback as useCallback5, useEffect as useEffect9, useRef as useRef4, useState as useState6 } from "react";
1242
2110
 
1243
2111
  // src/components/ui/button.tsx
1244
2112
  import { forwardRef as forwardRef3 } from "react";
@@ -1366,16 +2234,16 @@ function EmailStep({ email, onEmailChange, onSubmit, error, isSubmitting }) {
1366
2234
  function OtpStep({ email, onSubmit, onResend, onBack, error, isSubmitting }) {
1367
2235
  const [digits, setDigits] = useState6(Array(OTP_LENGTH).fill(""));
1368
2236
  const [resendCooldown, setResendCooldown] = useState6(0);
1369
- const inputRefs = useRef3([]);
1370
- const submittedRef = useRef3(false);
1371
- useEffect8(() => {
2237
+ const inputRefs = useRef4([]);
2238
+ const submittedRef = useRef4(false);
2239
+ useEffect9(() => {
1372
2240
  inputRefs.current[0]?.focus();
1373
2241
  }, []);
1374
- useEffect8(() => {
2242
+ useEffect9(() => {
1375
2243
  if (error)
1376
2244
  submittedRef.current = false;
1377
2245
  }, [error]);
1378
- useEffect8(() => {
2246
+ useEffect9(() => {
1379
2247
  if (resendCooldown <= 0)
1380
2248
  return;
1381
2249
  const timer = setTimeout(() => setResendCooldown((c) => c - 1), 1000);
@@ -1518,7 +2386,7 @@ function OtpStep({ email, onSubmit, onResend, onBack, error, isSubmitting }) {
1518
2386
  // src/components/MeetingControls.tsx
1519
2387
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1520
2388
  var CONTROL_BUTTON = "skippr:flex skippr:size-11 skippr:cursor-pointer skippr:items-center skippr:justify-center skippr:rounded-full skippr:transition-colors";
1521
- function MeetingControls({ onHangUp }) {
2389
+ function MeetingControls({ onHangUp, showScreenShareToggle = true }) {
1522
2390
  const { isMuted, toggleMute, isScreenSharing, toggleScreenShare } = useMediaControls();
1523
2391
  return /* @__PURE__ */ jsxs8("div", {
1524
2392
  className: "skippr:shrink-0 skippr:border-t skippr:border-border skippr:bg-background skippr:px-4 skippr:py-4",
@@ -1537,7 +2405,7 @@ function MeetingControls({ onHangUp }) {
1537
2405
  className: "skippr:size-5"
1538
2406
  })
1539
2407
  }),
1540
- /* @__PURE__ */ jsx9("button", {
2408
+ showScreenShareToggle && /* @__PURE__ */ jsx9("button", {
1541
2409
  type: "button",
1542
2410
  onClick: toggleScreenShare,
1543
2411
  "aria-label": isScreenSharing ? "Stop sharing screen" : "Share screen",
@@ -1568,17 +2436,17 @@ function MeetingControls({ onHangUp }) {
1568
2436
  }
1569
2437
 
1570
2438
  // src/components/MessageList.tsx
1571
- import { useEffect as useEffect10, useRef as useRef5 } from "react";
2439
+ import { useEffect as useEffect11, useRef as useRef6 } from "react";
1572
2440
 
1573
2441
  // src/components/ChatInput.tsx
1574
- import { useEffect as useEffect9, useRef as useRef4, useState as useState7 } from "react";
2442
+ import { useEffect as useEffect10, useRef as useRef5, useState as useState7 } from "react";
1575
2443
  import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1576
2444
  var MAX_INPUT_HEIGHT = 60;
1577
2445
  function ChatInput({ sendChatMessage, isSendingChat, autoFocus = false }) {
1578
2446
  const [inputText, setInputText] = useState7("");
1579
- const textareaRef = useRef4(null);
2447
+ const textareaRef = useRef5(null);
1580
2448
  const canSend = inputText.trim().length > 0 && !isSendingChat;
1581
- useEffect9(() => {
2449
+ useEffect10(() => {
1582
2450
  if (autoFocus)
1583
2451
  textareaRef.current?.focus();
1584
2452
  }, [autoFocus]);
@@ -1723,9 +2591,9 @@ function MessageList({
1723
2591
  isSendingChat,
1724
2592
  autoFocus = false
1725
2593
  }) {
1726
- const scrollRef = useRef5(null);
2594
+ const scrollRef = useRef6(null);
1727
2595
  const lastMessage = messages.length > 0 ? messages[messages.length - 1] : undefined;
1728
- useEffect10(() => {
2596
+ useEffect11(() => {
1729
2597
  scrollRef.current?.scrollIntoView({ behavior: "smooth" });
1730
2598
  }, [messages.length, lastMessage?.content]);
1731
2599
  const showTyping = isStreaming && lastMessage?.role === "assistant" && lastMessage.content === "";
@@ -1878,11 +2746,12 @@ function Sidebar({
1878
2746
  isAuthSubmitting,
1879
2747
  sidebarTab: activeTab,
1880
2748
  setSidebarTab: setActiveTab,
1881
- autoFocusChat
2749
+ autoFocusChat,
2750
+ captureMode
1882
2751
  } = useLiveAgent();
1883
2752
  const isFloating = variant === "floating";
1884
2753
  const isSidebar = variant === "sidebar";
1885
- useEffect11(() => {
2754
+ useEffect12(() => {
1886
2755
  if (!isSidebar)
1887
2756
  return;
1888
2757
  const prop = position === "right" ? "marginRight" : "marginLeft";
@@ -1921,7 +2790,8 @@ function Sidebar({
1921
2790
  onTabChange: setActiveTab,
1922
2791
  hideControls,
1923
2792
  startSessionLabel,
1924
- autoFocusChat
2793
+ autoFocusChat,
2794
+ showScreenShareToggle: captureMode === "screenshare"
1925
2795
  })
1926
2796
  ]
1927
2797
  });
@@ -1936,7 +2806,8 @@ function AuthenticatedContent({
1936
2806
  onTabChange,
1937
2807
  hideControls,
1938
2808
  startSessionLabel,
1939
- autoFocusChat
2809
+ autoFocusChat,
2810
+ showScreenShareToggle
1940
2811
  }) {
1941
2812
  return /* @__PURE__ */ jsxs15(Fragment3, {
1942
2813
  children: [
@@ -1990,7 +2861,8 @@ function AuthenticatedContent({
1990
2861
  }, `${activeTab}-empty`)
1991
2862
  }),
1992
2863
  isConnected && !hideControls && /* @__PURE__ */ jsx17(MeetingControls, {
1993
- onHangUp: onDisconnect
2864
+ onHangUp: onDisconnect,
2865
+ showScreenShareToggle
1994
2866
  })
1995
2867
  ]
1996
2868
  });
@@ -2066,6 +2938,7 @@ function LiveAgent({
2066
2938
  startSessionLabel = "Talk to Skippr",
2067
2939
  autoFocusChat = true,
2068
2940
  showAgentStateBanner = true,
2941
+ captureMode = "screenshare",
2069
2942
  children
2070
2943
  }) {
2071
2944
  const auth = useAuth({ appKey });
@@ -2081,6 +2954,7 @@ function LiveAgent({
2081
2954
  pendingScreenStream
2082
2955
  } = useSession({
2083
2956
  agentId,
2957
+ captureMode,
2084
2958
  authToken: effectiveAuthToken,
2085
2959
  appKey,
2086
2960
  userToken
@@ -2119,8 +2993,8 @@ function LiveAgent({
2119
2993
  }, [minimizable]);
2120
2994
  const isConnected = connection !== null;
2121
2995
  const isAuthenticated = !!userToken || !!authTokenProp || auth.isAuthenticated;
2122
- const prevConnectionRef = useRef6(connection);
2123
- useEffect12(() => {
2996
+ const prevConnectionRef = useRef7(connection);
2997
+ useEffect13(() => {
2124
2998
  const connectionChanged = prevConnectionRef.current !== connection;
2125
2999
  prevConnectionRef.current = connection;
2126
3000
  if (connectionChanged && minimizable) {
@@ -2157,7 +3031,8 @@ function LiveAgent({
2157
3031
  isAuthSubmitting: auth.isSubmitting,
2158
3032
  sidebarTab,
2159
3033
  setSidebarTab,
2160
- autoFocusChat
3034
+ autoFocusChat,
3035
+ captureMode
2161
3036
  }), [
2162
3037
  connection,
2163
3038
  shouldConnect,
@@ -2186,7 +3061,8 @@ function LiveAgent({
2186
3061
  auth.logout,
2187
3062
  auth.isSubmitting,
2188
3063
  sidebarTab,
2189
- autoFocusChat
3064
+ autoFocusChat,
3065
+ captureMode
2190
3066
  ]);
2191
3067
  return /* @__PURE__ */ jsx19(LiveAgentContext.Provider, {
2192
3068
  value: ctx,
@@ -2198,20 +3074,26 @@ function LiveAgent({
2198
3074
  onDisconnected: disconnect,
2199
3075
  children: [
2200
3076
  connection && /* @__PURE__ */ jsx19(RoomAudioRenderer, {}),
2201
- showAgentStateBanner && /* @__PURE__ */ jsx19(AgentStateBanner, {}),
2202
- connection && /* @__PURE__ */ jsx19(AutoStartMedia, {
3077
+ connection && captureMode === "screenshare" && /* @__PURE__ */ jsx19(AutoStartMedia, {
2203
3078
  pendingScreenStream
2204
3079
  }),
2205
- isMinimized && /* @__PURE__ */ jsx19(MinimizedBubble, {
2206
- welcomeMessage,
2207
- welcomeDismissed,
2208
- onDismissWelcome: dismissWelcome
2209
- }),
2210
- /* @__PURE__ */ jsx19(SidebarTrigger, {}),
2211
- /* @__PURE__ */ jsx19(Sidebar, {
2212
- hideControls,
2213
- hideHeader,
2214
- startSessionLabel
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
+ ]
2215
3097
  }),
2216
3098
  children
2217
3099
  ]
@@ -2219,9 +3101,9 @@ function LiveAgent({
2219
3101
  });
2220
3102
  }
2221
3103
  // src/hooks/useIsLocalSpeaking.ts
2222
- import { useIsSpeaking, useLocalParticipant as useLocalParticipant5 } from "@livekit/components-react";
3104
+ import { useIsSpeaking, useLocalParticipant as useLocalParticipant6 } from "@livekit/components-react/hooks";
2223
3105
  function useIsLocalSpeaking() {
2224
- const { localParticipant } = useLocalParticipant5();
3106
+ const { localParticipant } = useLocalParticipant6();
2225
3107
  return useIsSpeaking(localParticipant);
2226
3108
  }
2227
3109
  export {