arcy.js 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- /* arcy.js https://arcyai.com */
1
+ import { DESIGN_MODE_PROTOCOL, DESIGN_MODE_EXIT, DESIGN_MODE_SCREENSHOT, DESIGN_MODE_PICK, DESIGN_MODE_READY } from './chunk-7VVKR3AO.js';
2
+ import { FALLBACK_CHROME, resolveImageUrl, FONT_KEY_PROPERTY, isRenderableImageUrl, readViewport, POLICY_LINK_CLASS, BAR_TRAY_CLASS, ATTACHABLE_TYPES, TOOLTIP_CSS, surfaceWidth, clampOffsetX, dragTransform, carriesFiles, droppedFiles, MOBILE_MARGIN, CHAT_CONTRACT, CHAT_GLOBAL } from './chunk-DW3GUQ6J.js';
3
+ import { createShellState, safeStore, browserSessionStorage, writeJson, browserLocalStorage, readJson, hasPendingPreview, createFlowRunState, FLOW_CONTRACT, FLOW_GLOBAL } from './chunk-FR6SJSDU.js';
4
+ import { PICKER_CONTRACT, PICKER_GLOBAL } from './chunk-DKYMIAYF.js';
5
+ import { warn, reportCspViolations, releaseHostFocus, Z_INDEX, createShell, injectStyles } from './chunk-LGWYJSYX.js';
6
+ import { captureEventFingerprint, containsPii } from './chunk-NY3NXM2V.js';
2
7
 
3
- // src/warn.ts
4
- function warn(message) {
5
- if (typeof console !== "undefined" && console.warn) {
6
- console.warn(`[arcy] ${message}`);
7
- }
8
- }
8
+ /* arcy.js — https://arcyai.com */
9
9
 
10
10
  // src/emitter.ts
11
11
  function createEmitter() {
@@ -40,149 +40,6 @@ function createEmitter() {
40
40
  return { on, emit };
41
41
  }
42
42
 
43
- // src/fingerprint/model.ts
44
- var FINGERPRINT_TEXT_MAX = 80;
45
-
46
- // src/fingerprint/shadow.ts
47
- function parentOrHost(el) {
48
- if (el.parentElement !== null) return el.parentElement;
49
- const parentNode = el.parentNode;
50
- if (parentNode !== null && "host" in parentNode) {
51
- return parentNode.host;
52
- }
53
- return null;
54
- }
55
-
56
- // src/fingerprint/capture.ts
57
- var PII_PATTERNS = [
58
- // Email
59
- /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
60
- // US SSN: 123-45-6789
61
- /\b\d{3}-\d{2}-\d{4}\b/g,
62
- // Card shapes: 13-19 digits, optionally grouped. A denylist, not a Luhn check.
63
- /\b\d(?:[ -]?\d){12,18}\b/g
64
- ];
65
- var MASK = "[masked]";
66
- function containsPii(raw) {
67
- return PII_PATTERNS.some((pattern) => {
68
- pattern.lastIndex = 0;
69
- return pattern.test(raw);
70
- });
71
- }
72
- var NORMALIZE_RAW_MAX = 4096;
73
- var NORMALIZE_RAW_MARGIN = 100;
74
- var ROLE_MAX = 64;
75
- function normalizeText(raw) {
76
- if (!raw) return void 0;
77
- let bounded = raw.slice(0, NORMALIZE_RAW_MAX);
78
- if (raw.length > NORMALIZE_RAW_MAX) {
79
- const lastSpace = bounded.search(/\s\S*$/);
80
- bounded = lastSpace === -1 ? bounded.slice(0, NORMALIZE_RAW_MAX - NORMALIZE_RAW_MARGIN) : bounded.slice(0, lastSpace);
81
- }
82
- let text = bounded.replace(/\s+/g, " ").trim();
83
- if (!text) return void 0;
84
- for (const pattern of PII_PATTERNS) {
85
- text = text.replace(pattern, MASK);
86
- }
87
- return text.slice(0, FINGERPRINT_TEXT_MAX);
88
- }
89
- function isStableId(id) {
90
- if (id.length === 0 || id.length > 64) return false;
91
- if (id.includes(":")) return false;
92
- if (id.startsWith("radix-")) return false;
93
- if (/^\d+$/.test(id)) return false;
94
- if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) return false;
95
- return true;
96
- }
97
- var ANCESTOR_ATTRS = ["data-testid", "data-test", "data-cy", "id", "name", "aria-label"];
98
- var TEST_ATTRS = ["data-testid", "data-test", "data-cy"];
99
- var TEST_ID_MAX = 64;
100
- function firstTestId(el) {
101
- for (const attr of TEST_ATTRS) {
102
- const raw = el.getAttribute(attr);
103
- if (raw === null) continue;
104
- const trimmed = raw.trim();
105
- if (trimmed === "" || trimmed.length > TEST_ID_MAX) continue;
106
- if (containsPii(trimmed)) continue;
107
- return trimmed;
108
- }
109
- return void 0;
110
- }
111
- var LANDMARK_ROLES = /* @__PURE__ */ new Set([
112
- "navigation",
113
- "main",
114
- "banner",
115
- "contentinfo",
116
- "search",
117
- "form",
118
- "region",
119
- "dialog"
120
- ]);
121
- function findSemanticAncestor(el) {
122
- let current = parentOrHost(el);
123
- while (current !== null) {
124
- const tag = current.tagName.toLowerCase();
125
- if (tag === "body" || tag === "html") return void 0;
126
- for (const attr of ANCESTOR_ATTRS) {
127
- const raw = current.getAttribute(attr);
128
- if (raw === null) continue;
129
- if (attr === "id" && !isStableId(raw)) continue;
130
- const value = normalizeText(raw);
131
- if (value !== void 0) return { tag, attr, value };
132
- }
133
- const role = current.getAttribute("role");
134
- if (role !== null && LANDMARK_ROLES.has(role)) {
135
- return { tag, attr: "role", value: role };
136
- }
137
- current = parentOrHost(current);
138
- }
139
- return void 0;
140
- }
141
- function siblingIndexOf(el) {
142
- let index = 0;
143
- let sibling = el.previousElementSibling;
144
- while (sibling !== null) {
145
- if (sibling.tagName === el.tagName) index += 1;
146
- sibling = sibling.previousElementSibling;
147
- }
148
- return index;
149
- }
150
- function captureEventFingerprint(el) {
151
- const fingerprint = {
152
- tag: el.tagName.toLowerCase(),
153
- siblingIndex: siblingIndexOf(el)
154
- };
155
- const text = normalizeText(el.textContent);
156
- if (text !== void 0) fingerprint.text = text;
157
- const ariaLabel = normalizeText(el.getAttribute("aria-label"));
158
- if (ariaLabel !== void 0) fingerprint.ariaLabel = ariaLabel;
159
- const placeholder = normalizeText(el.getAttribute("placeholder"));
160
- if (placeholder !== void 0) fingerprint.placeholder = placeholder;
161
- const id = el.getAttribute("id");
162
- if (id !== null && isStableId(id) && !containsPii(id)) fingerprint.id = id;
163
- const role = el.getAttribute("role");
164
- if (role !== null) {
165
- const trimmed = role.trim();
166
- if (trimmed !== "" && trimmed.length <= ROLE_MAX && !containsPii(trimmed)) {
167
- fingerprint.role = trimmed;
168
- }
169
- }
170
- const testId = firstTestId(el);
171
- if (testId !== void 0) fingerprint.testId = testId;
172
- const ancestor = findSemanticAncestor(el);
173
- if (ancestor !== void 0) {
174
- fingerprint.ancestorTag = ancestor.tag;
175
- fingerprint.ancestorAttr = ancestor.attr;
176
- fingerprint.ancestorValue = ancestor.value;
177
- }
178
- return fingerprint;
179
- }
180
-
181
- // src/generated/build-hashes.ts
182
- var PICKER_CHUNK_FILE = "arcy.picker.c598a581d964.js";
183
- var CHAT_CHUNK_FILE = "arcy.chat.ab181cf5a84b.js";
184
- var FLOW_CHUNK_FILE = "arcy.flow.fef131718e42.js";
185
-
186
43
  // src/telemetry/route.ts
187
44
  var MAX_ROUTE_LENGTH = 2048;
188
45
  var OPAQUE = /^(?:\d+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-f]{12,})$/i;
@@ -246,192 +103,6 @@ function normalizeRoute(href, base) {
246
103
  return path.length + query.length > MAX_ROUTE_LENGTH ? path : path + query;
247
104
  }
248
105
 
249
- // src/picker/contract.ts
250
- var PICKER_GLOBAL = "__arcyPicker";
251
- var PICKER_CONTRACT = 2;
252
-
253
- // src/picker/message.ts
254
- var DESIGN_MODE_PROTOCOL = 2;
255
- var DESIGN_MODE_READY = "arcy:design-mode:ready";
256
- var DESIGN_MODE_PICK = "arcy:design-mode:pick";
257
- var DESIGN_MODE_EXIT = "arcy:design-mode:exit";
258
- var DESIGN_MODE_SCREENSHOT = "arcy:design-mode:screenshot";
259
-
260
- // src/shell/host.ts
261
- var HOST_ID = "arcy-widget";
262
- var HOST_ATTRIBUTE = "data-arcy";
263
- var Z_INDEX = "2147483000";
264
- var CRITICAL_STYLE = [
265
- ["position", "fixed"],
266
- ["inset", "0"],
267
- ["z-index", Z_INDEX],
268
- // The layer spans the viewport so the launcher (1.4) and the chat panel (1.5)
269
- // share one coordinate space. It must therefore be transparent to the
270
- // pointer; each surface inside re-enables its own.
271
- ["pointer-events", "none"],
272
- // The layer must also be invisible, and that is not the shadow root's job
273
- // (D512). The host is in the customer's light DOM whatever happens inside
274
- // it, so their `div { background: ... !important }` paints it, and a painted
275
- // full-viewport layer is their whole site replaced by a coloured sheet with
276
- // no error anywhere. These four are every property that fills or edges a
277
- // box, and none of them is something a customer wants on an invisible layer.
278
- ["background", "transparent"],
279
- ["border", "0"],
280
- ["box-shadow", "none"],
281
- ["outline", "0"]
282
- ];
283
- var RESET_STYLE = [
284
- ["margin", "0"],
285
- ["padding", "0"]
286
- ];
287
- var STYLE_MARKER = "data-arcy-style";
288
- var shells = /* @__PURE__ */ new Map();
289
- function isMounted(id = HOST_ID) {
290
- const shell = shells.get(id);
291
- return shell !== void 0 && shell.host.isConnected;
292
- }
293
- function createShell(options = {}) {
294
- if (typeof document === "undefined") return null;
295
- const id = options.id ?? HOST_ID;
296
- const container = options.container;
297
- if (isMounted(id)) {
298
- warn("This ARCY surface is already on this page. Ignoring the second mount.");
299
- return null;
300
- }
301
- shells.delete(id);
302
- const parent = container ?? document.body ?? document.documentElement;
303
- if (!parent) {
304
- warn("The page has no <body> to mount into yet. The widget will not render.");
305
- return null;
306
- }
307
- if (!container) {
308
- const hosts = document.querySelectorAll(`[${HOST_ATTRIBUTE}]`);
309
- for (let i = 0; i < hosts.length; i += 1) {
310
- if (hosts[i]?.id === id) {
311
- warn("Another copy of ARCY is already on this page. Ignoring this one.");
312
- return null;
313
- }
314
- }
315
- }
316
- try {
317
- const host = document.createElement("div");
318
- host.id = id;
319
- host.setAttribute(HOST_ATTRIBUTE, "");
320
- for (const [property, value] of CRITICAL_STYLE) {
321
- host.style.setProperty(property, value, "important");
322
- }
323
- for (const [property, value] of RESET_STYLE) {
324
- host.style.setProperty(property, value);
325
- }
326
- let root = host;
327
- let isolated = false;
328
- try {
329
- if (host.attachShadow) {
330
- root = host.attachShadow({ mode: "closed" });
331
- isolated = true;
332
- }
333
- } catch {
334
- }
335
- parent.appendChild(host);
336
- isolateInteractions(host);
337
- const shell = {
338
- host,
339
- root,
340
- isolated,
341
- destroy() {
342
- try {
343
- if (host.parentNode) host.parentNode.removeChild(host);
344
- } catch {
345
- }
346
- if (shells.get(id) === shell) shells.delete(id);
347
- }
348
- };
349
- shells.set(id, shell);
350
- return shell;
351
- } catch (error) {
352
- warn(`The widget could not mount. ${String(error)}`);
353
- return null;
354
- }
355
- }
356
- var ISOLATED_EVENTS = [
357
- "pointerdown",
358
- "pointerup",
359
- "pointercancel",
360
- "mousedown",
361
- "mouseup",
362
- "click",
363
- "dblclick",
364
- "auxclick",
365
- "contextmenu",
366
- "touchstart",
367
- "touchend",
368
- "touchcancel",
369
- "focusin",
370
- "focusout",
371
- "keydown",
372
- "keyup",
373
- "keypress",
374
- "beforeinput",
375
- "input",
376
- "change",
377
- "compositionstart",
378
- "compositionupdate",
379
- "compositionend",
380
- "cut",
381
- "copy",
382
- "paste"
383
- ];
384
- function isolateInteractions(host) {
385
- const stop = (event) => {
386
- try {
387
- event.stopPropagation();
388
- } catch {
389
- }
390
- };
391
- for (const type of ISOLATED_EVENTS) {
392
- try {
393
- host.addEventListener(type, stop);
394
- } catch {
395
- }
396
- }
397
- try {
398
- host.addEventListener("mousedown", (event) => {
399
- if (event.defaultPrevented) return;
400
- releaseHostFocus(host.ownerDocument, host);
401
- });
402
- } catch {
403
- }
404
- }
405
- function releaseHostFocus(doc, host) {
406
- try {
407
- const active = doc.activeElement;
408
- if (!active || active === host || host.contains(active)) return;
409
- if (active === doc.body || active === doc.documentElement) return;
410
- const blur = active.blur;
411
- if (typeof blur === "function") blur.call(active);
412
- } catch {
413
- }
414
- }
415
- function injectStyles(shell, css) {
416
- if (!shell.isolated) return false;
417
- try {
418
- const existing = shell.root.querySelector(`[${STYLE_MARKER}]`);
419
- if (existing) {
420
- existing.textContent = `${existing.textContent ?? ""}
421
- ${css}`;
422
- return true;
423
- }
424
- const style2 = shell.host.ownerDocument.createElement("style");
425
- style2.setAttribute(STYLE_MARKER, "");
426
- style2.textContent = css;
427
- shell.root.appendChild(style2);
428
- return true;
429
- } catch (error) {
430
- warn(`The widget could not apply its styles. ${String(error)}`);
431
- return false;
432
- }
433
- }
434
-
435
106
  // src/picker/notice.ts
436
107
  var NOTICE_HOST_ID = "arcy-picker-notice";
437
108
  var CARD_OFFSET = [
@@ -523,7 +194,6 @@ function refuse(message) {
523
194
  }
524
195
  var PICKER_PARAM = "arcy_picker";
525
196
  var PICKER_VERIFY_PATH = "/api/v1/sdk/tagging/verify";
526
- var PICKER_CDN_BASE = "https://cdn.arcyai.com";
527
197
  var PICKER_LOAD_TIMEOUT_MS = 15e3;
528
198
  var VERIFY_TIMEOUT_MS = 1e4;
529
199
  function detectPickerNonce(search, hash) {
@@ -608,115 +278,6 @@ function normalizeOrigin(value) {
608
278
  return null;
609
279
  }
610
280
  }
611
- function loadPickerChunk(win, cdnBase, timeoutMs, setTimeoutImpl) {
612
- return new Promise((resolve) => {
613
- let settled = false;
614
- let script = null;
615
- const settle = (value) => {
616
- if (settled) return;
617
- settled = true;
618
- if (script) {
619
- script.onload = null;
620
- script.onerror = null;
621
- }
622
- resolve(value);
623
- };
624
- let warned = false;
625
- const complain = (message) => {
626
- if (warned) return;
627
- warned = true;
628
- refuse(message);
629
- };
630
- const read = () => {
631
- let registration;
632
- try {
633
- registration = win[PICKER_GLOBAL];
634
- } catch {
635
- complain(
636
- "The element picker registration could not be read. Design mode is off."
637
- );
638
- return null;
639
- }
640
- if (!registration) return null;
641
- if (typeof registration.mount !== "function") {
642
- complain(
643
- "The element picker registered something unusable. Design mode is off."
644
- );
645
- return null;
646
- }
647
- let contract;
648
- try {
649
- contract = registration.contract;
650
- } catch {
651
- contract = void 0;
652
- }
653
- if (contract !== PICKER_CONTRACT) {
654
- complain(
655
- `The element picker on the CDN speaks contract ${typeof contract === "number" ? contract : "unknown"}, this arcy.js speaks ${PICKER_CONTRACT}. Update arcy.js to use design mode.`
656
- );
657
- return null;
658
- }
659
- return registration;
660
- };
661
- const readAndSettle = (whenAbsent) => {
662
- try {
663
- const registration = read();
664
- if (!registration && !warned) whenAbsent();
665
- settle(registration);
666
- } catch {
667
- settle(null);
668
- }
669
- };
670
- let present = false;
671
- try {
672
- present = win[PICKER_GLOBAL] !== void 0;
673
- } catch {
674
- present = true;
675
- }
676
- if (present) {
677
- readAndSettle(() => {
678
- });
679
- return;
680
- }
681
- try {
682
- const doc = win.document;
683
- script = doc.createElement("script");
684
- script.async = true;
685
- script.src = `${cdnBase}/${PICKER_CHUNK_FILE}`;
686
- const src = script.src;
687
- script.onload = () => readAndSettle(
688
- () => refuse(
689
- "The element picker loaded but did not register. Design mode is off."
690
- )
691
- );
692
- script.onerror = () => {
693
- try {
694
- refuse(
695
- `Failed to load ${src}. Design mode is off. If this page sets a Content-Security-Policy, it must allow scripts from ${cdnBase}.`
696
- );
697
- } catch {
698
- }
699
- settle(null);
700
- };
701
- (doc.head || doc.documentElement).appendChild(script);
702
- } catch (error) {
703
- refuse(`The element picker could not load. ${String(error)}`);
704
- settle(null);
705
- return;
706
- }
707
- try {
708
- setTimeoutImpl(() => {
709
- if (!settled) {
710
- refuse(
711
- "The element picker did not load in time. Design mode is off."
712
- );
713
- }
714
- settle(null);
715
- }, timeoutMs);
716
- } catch {
717
- }
718
- });
719
- }
720
281
  function currentPath(win) {
721
282
  try {
722
283
  const path = win.location.pathname;
@@ -754,7 +315,7 @@ async function maybeActivateDesignMode(options) {
754
315
  apiBase,
755
316
  fetchImpl,
756
317
  onVerified,
757
- cdnBase = PICKER_CDN_BASE,
318
+ loadChunk,
758
319
  loadTimeoutMs = PICKER_LOAD_TIMEOUT_MS,
759
320
  setTimeoutImpl = (fn, ms) => setTimeout(fn, ms)
760
321
  } = options;
@@ -784,12 +345,7 @@ async function maybeActivateDesignMode(options) {
784
345
  onVerified();
785
346
  } catch {
786
347
  }
787
- const registration = await loadPickerChunk(
788
- win,
789
- cdnBase,
790
- loadTimeoutMs,
791
- setTimeoutImpl
792
- );
348
+ const registration = await loadChunk(win, loadTimeoutMs, setTimeoutImpl);
793
349
  if (!registration) return "load_failed";
794
350
  const { returnOrigin, screenshotsEnabled } = verified;
795
351
  const handle = registration.mount({
@@ -843,84 +399,6 @@ async function maybeActivateDesignMode(options) {
843
399
  }
844
400
  }
845
401
 
846
- // src/telemetry/storage.ts
847
- var NULL_STORE = {
848
- getItem: () => null,
849
- setItem: () => {
850
- },
851
- removeItem: () => {
852
- }
853
- };
854
- function safeStore(raw) {
855
- if (!raw) return NULL_STORE;
856
- return {
857
- getItem(key) {
858
- try {
859
- return raw.getItem(key);
860
- } catch {
861
- return null;
862
- }
863
- },
864
- setItem(key, value) {
865
- try {
866
- raw.setItem(key, value);
867
- } catch {
868
- }
869
- },
870
- removeItem(key) {
871
- try {
872
- raw.removeItem(key);
873
- } catch {
874
- }
875
- }
876
- };
877
- }
878
- function browserLocalStorage() {
879
- try {
880
- const candidate = globalThis.localStorage;
881
- return safeStore(candidate);
882
- } catch {
883
- return NULL_STORE;
884
- }
885
- }
886
- function browserSessionStorage() {
887
- try {
888
- const candidate = globalThis.sessionStorage;
889
- return safeStore(candidate);
890
- } catch {
891
- return NULL_STORE;
892
- }
893
- }
894
- function readJson(store, key) {
895
- const raw = store.getItem(key);
896
- if (!raw) return null;
897
- try {
898
- const parsed = JSON.parse(raw);
899
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
900
- return null;
901
- }
902
- return parsed;
903
- } catch {
904
- return null;
905
- }
906
- }
907
- function writeJson(store, key, value) {
908
- try {
909
- store.setItem(key, JSON.stringify(value));
910
- } catch {
911
- }
912
- }
913
-
914
- // src/flow/preview-state.ts
915
- var PREVIEW_KEY_PREFIX = "arcy.preview.";
916
- function hasPendingPreview(token, store = browserSessionStorage()) {
917
- try {
918
- return safeStore(store).getItem(PREVIEW_KEY_PREFIX + token) !== null;
919
- } catch {
920
- return false;
921
- }
922
- }
923
-
924
402
  // src/flow/preview.ts
925
403
  var PREVIEW_PARAM = "arcy_preview";
926
404
  var PREVIEW_PROTOCOL = 1;
@@ -1108,6 +586,8 @@ function createSessionBootstrap(options) {
1108
586
  if (payload.viewerLocale !== void 0)
1109
587
  out.viewerLocale = payload.viewerLocale;
1110
588
  if (payload.route !== void 0) out.route = payload.route;
589
+ if (payload.sessionContext !== void 0)
590
+ out.sessionContext = payload.sessionContext;
1111
591
  return JSON.stringify(out);
1112
592
  }
1113
593
  async function attempt(json) {
@@ -1191,190 +671,14 @@ function createSessionBootstrap(options) {
1191
671
  };
1192
672
  }
1193
673
 
1194
- // src/shell/chat-contract.ts
1195
- var CHAT_GLOBAL = "__arcyChat";
1196
- var CHAT_CONTRACT = 4;
1197
- var ATTACHABLE_TYPES = [
1198
- "image/png",
1199
- "image/jpeg",
1200
- "image/webp"
1201
- ];
1202
- var FONT_KEY_PROPERTY = "--_arcy-font-key";
1203
- var POLICY_LINK_CLASS = "arcy-chat-policy";
1204
- var FALLBACK_CHROME = {
1205
- historyTitle: "Recent chats",
1206
- historyEmpty: "No conversations yet.",
1207
- newChat: "New chat",
1208
- flowsTitle: "What I can do",
1209
- flowsEmpty: "Nothing to run yet.",
1210
- collapse: "Collapse",
1211
- back: "Back",
1212
- closeSheet: "Close",
1213
- settingsTitle: "Settings",
1214
- dockChat: "Dock to the corner",
1215
- dockChatHint: "Shrink ARCY to a button in the bottom-right corner.",
1216
- language: "Language",
1217
- openChat: "Open chat",
1218
- moveBar: "Move the chat bar",
1219
- send: "Send",
1220
- flowRunning: "Running",
1221
- flowStep: "Step {step} of {total}",
1222
- flowStop: "Stop",
1223
- flowFailed: "Sorry, I could not finish that for you this time. You can ask me here and I will help you do it.",
1224
- attachMenu: "Add",
1225
- attachImage: "Attach an image",
1226
- removeImage: "Remove image",
1227
- imageTooLarge: "That image is too large. Each image must be under 5 MB.",
1228
- imageWrongType: "That file type is not supported. Use PNG, JPEG or WEBP.",
1229
- imageTooMany: "You can attach up to 3 images.",
1230
- imageFailed: "That image could not be uploaded. Please try again.",
1231
- likeAnswer: "Good answer",
1232
- dislikeAnswer: "Bad answer",
1233
- answering: "Answering",
1234
- aiDisclaimer: "{app} is AI and can make mistakes",
1235
- consentNotice: "Your messages are processed to answer your question.",
1236
- consentWithPolicy: "{policy}",
1237
- disclaimerJoin: " \xB7 ",
1238
- privacyPolicy: "Privacy Policy",
1239
- poweredBy: "Powered by ARCY",
1240
- genericError: "Something went wrong. Please try again.",
1241
- userCapReached: "You've reached today's limit for questions. Please try again tomorrow.",
1242
- imageConversation: "Image",
1243
- searchedSourcesLabel: "Searched {count} sources",
1244
- sourcesLabel: "Sources:",
1245
- flowOfferHint: "You can do this in one click with the Quick Flow below.",
1246
- fillConfirm: "Confirm",
1247
- fillYes: "Yes",
1248
- fillNo: "No",
1249
- fillRequired: "This field needs an answer.",
1250
- fillInvalidNumber: "Please enter a number.",
1251
- fillNumberTooSmall: "Please enter {min} or more.",
1252
- fillNumberTooLarge: "Please enter {max} or less.",
1253
- fillInvalidEmail: "Please enter a valid email address.",
1254
- fillInvalidUrl: "Please enter a valid link.",
1255
- fillInvalidDate: "Please enter a valid date.",
1256
- fillInvalidTime: "Please enter a valid time.",
1257
- fillDateTooEarly: "Please pick {min} or later.",
1258
- fillDateTooLate: "Please pick {max} or earlier.",
1259
- fillTooLong: "Please keep it under {max} characters.",
1260
- fillInvalidOption: "Please pick one of the options.",
1261
- fillInvalid: "That does not look right. Please try again."
1262
- };
1263
-
1264
674
  // src/shell/chat-loader.ts
1265
- var CHAT_CDN_BASE = "https://cdn.arcyai.com";
1266
675
  var CHAT_LOAD_TIMEOUT_MS = 15e3;
1267
- function loadChatChunk(win, cdnBase, timeoutMs, setTimeoutImpl) {
1268
- return new Promise((resolve) => {
1269
- let settled = false;
1270
- let script = null;
1271
- const settle = (value) => {
1272
- if (settled) return;
1273
- settled = true;
1274
- if (script) {
1275
- script.onload = null;
1276
- script.onerror = null;
1277
- }
1278
- resolve(value);
1279
- };
1280
- let warned = false;
1281
- const complain = (message) => {
1282
- if (warned) return;
1283
- warned = true;
1284
- warn(message);
1285
- };
1286
- const read = () => {
1287
- let registration;
1288
- try {
1289
- registration = win[CHAT_GLOBAL];
1290
- } catch {
1291
- complain(
1292
- "The chat panel registration could not be read. The panel will not open."
1293
- );
1294
- return null;
1295
- }
1296
- if (!registration) return null;
1297
- if (typeof registration.mount !== "function") {
1298
- complain(
1299
- "The chat panel registered something unusable. The panel will not open."
1300
- );
1301
- return null;
1302
- }
1303
- let contract;
1304
- try {
1305
- contract = registration.contract;
1306
- } catch {
1307
- contract = void 0;
1308
- }
1309
- if (contract !== CHAT_CONTRACT) {
1310
- complain(
1311
- `The chat panel on the CDN speaks contract ${typeof contract === "number" ? contract : "unknown"}, this arcy.js speaks ${CHAT_CONTRACT}. Update arcy.js to open the chat panel.`
1312
- );
1313
- return null;
1314
- }
1315
- return registration;
1316
- };
1317
- const readAndSettle = (whenAbsent) => {
1318
- try {
1319
- const registration = read();
1320
- if (!registration && !warned) whenAbsent();
1321
- settle(registration);
1322
- } catch {
1323
- settle(null);
1324
- }
1325
- };
1326
- let present = false;
1327
- try {
1328
- present = win[CHAT_GLOBAL] !== void 0;
1329
- } catch {
1330
- present = true;
1331
- }
1332
- if (present) {
1333
- readAndSettle(() => {
1334
- });
1335
- return;
1336
- }
1337
- try {
1338
- const doc = win.document;
1339
- script = doc.createElement("script");
1340
- script.async = true;
1341
- script.src = `${cdnBase}/${CHAT_CHUNK_FILE}`;
1342
- const src = script.src;
1343
- script.onload = () => readAndSettle(
1344
- () => warn("The chat panel loaded but did not register. The panel will not open.")
1345
- );
1346
- script.onerror = () => {
1347
- try {
1348
- warn(
1349
- `Failed to load ${src}. The chat panel will not open. If this page sets a Content-Security-Policy, it must allow scripts from ${cdnBase}.`
1350
- );
1351
- } catch {
1352
- }
1353
- settle(null);
1354
- };
1355
- (doc.head || doc.documentElement).appendChild(script);
1356
- } catch (error) {
1357
- warn(`The chat panel could not load. ${String(error)}`);
1358
- settle(null);
1359
- return;
1360
- }
1361
- try {
1362
- setTimeoutImpl(() => {
1363
- if (!settled) {
1364
- warn("The chat panel did not load in time.");
1365
- }
1366
- settle(null);
1367
- }, timeoutMs);
1368
- } catch {
1369
- }
1370
- });
1371
- }
1372
676
  function createChatController(options) {
1373
677
  const {
1374
678
  root,
1375
679
  host,
1376
680
  win = typeof window !== "undefined" ? window : void 0,
1377
- cdnBase = CHAT_CDN_BASE,
681
+ loadChunk,
1378
682
  loadTimeoutMs = CHAT_LOAD_TIMEOUT_MS,
1379
683
  setTimeoutImpl = (fn, ms) => setTimeout(fn, ms),
1380
684
  onRequestClose,
@@ -1407,12 +711,7 @@ function createChatController(options) {
1407
711
  loading = (async () => {
1408
712
  if (!win) return;
1409
713
  try {
1410
- const registration = await loadChatChunk(
1411
- win,
1412
- cdnBase,
1413
- loadTimeoutMs,
1414
- setTimeoutImpl
1415
- );
714
+ const registration = await loadChunk(win, loadTimeoutMs, setTimeoutImpl);
1416
715
  if (!registration || destroyed) return;
1417
716
  const context = {
1418
717
  root,
@@ -1524,144 +823,12 @@ function createChatController(options) {
1524
823
  };
1525
824
  }
1526
825
 
1527
- // src/flow/flow-contract.ts
1528
- var FLOW_GLOBAL = "__arcyFlow";
1529
- var FLOW_CONTRACT = 2;
1530
-
1531
- // src/flow/state.ts
1532
- var FLOW_KEY_PREFIX = "arcy.flow.";
1533
- function isValid(stored) {
1534
- return typeof stored.flowId === "string" && stored.flowId.length > 0 && typeof stored.currentStepCvid === "string" && stored.currentStepCvid.length > 0 && typeof stored.flowSessionId === "string" && stored.flowSessionId.length > 0 && typeof stored.startedAt === "number";
1535
- }
1536
- function createFlowRunState(token, { store: raw = browserLocalStorage() } = {}) {
1537
- const store = safeStore(raw);
1538
- const key = FLOW_KEY_PREFIX + token;
1539
- return {
1540
- read() {
1541
- const stored = readJson(store, key);
1542
- if (!stored || !isValid(stored)) return null;
1543
- return {
1544
- flowId: stored.flowId,
1545
- currentStepCvid: stored.currentStepCvid,
1546
- flowSessionId: stored.flowSessionId,
1547
- startedAt: stored.startedAt
1548
- };
1549
- },
1550
- save(run) {
1551
- writeJson(store, key, run);
1552
- },
1553
- clear() {
1554
- store.removeItem(key);
1555
- }
1556
- };
1557
- }
1558
-
1559
826
  // src/flow/flow-loader.ts
1560
- var FLOW_CDN_BASE = "https://cdn.arcyai.com";
1561
827
  var FLOW_LOAD_TIMEOUT_MS = 15e3;
1562
- function loadFlowChunk(win, cdnBase, timeoutMs, setTimeoutImpl) {
1563
- return new Promise((resolve) => {
1564
- let settled = false;
1565
- let script = null;
1566
- const settle = (value) => {
1567
- if (settled) return;
1568
- settled = true;
1569
- if (script) {
1570
- script.onload = null;
1571
- script.onerror = null;
1572
- }
1573
- resolve(value);
1574
- };
1575
- let warned = false;
1576
- const complain = (message) => {
1577
- if (warned) return;
1578
- warned = true;
1579
- warn(message);
1580
- };
1581
- const read = () => {
1582
- let registration;
1583
- try {
1584
- registration = win[FLOW_GLOBAL];
1585
- } catch {
1586
- complain("The flow engine registration could not be read. The flow will not start.");
1587
- return null;
1588
- }
1589
- if (!registration) return null;
1590
- if (typeof registration.mount !== "function") {
1591
- complain("The flow engine registered something unusable. The flow will not start.");
1592
- return null;
1593
- }
1594
- let contract;
1595
- try {
1596
- contract = registration.contract;
1597
- } catch {
1598
- contract = void 0;
1599
- }
1600
- if (contract !== FLOW_CONTRACT) {
1601
- complain(
1602
- `The flow engine on the CDN speaks contract ${typeof contract === "number" ? contract : "unknown"}, this arcy.js speaks ${FLOW_CONTRACT}. Update arcy.js to run flows.`
1603
- );
1604
- return null;
1605
- }
1606
- return registration;
1607
- };
1608
- const readAndSettle = (whenAbsent) => {
1609
- try {
1610
- const registration = read();
1611
- if (!registration && !warned) whenAbsent();
1612
- settle(registration);
1613
- } catch {
1614
- settle(null);
1615
- }
1616
- };
1617
- let present = false;
1618
- try {
1619
- present = win[FLOW_GLOBAL] !== void 0;
1620
- } catch {
1621
- present = true;
1622
- }
1623
- if (present) {
1624
- readAndSettle(() => {
1625
- });
1626
- return;
1627
- }
1628
- try {
1629
- const doc = win.document;
1630
- script = doc.createElement("script");
1631
- script.async = true;
1632
- script.src = `${cdnBase}/${FLOW_CHUNK_FILE}`;
1633
- const src = script.src;
1634
- script.onload = () => readAndSettle(
1635
- () => warn("The flow engine loaded but did not register. The flow will not start.")
1636
- );
1637
- script.onerror = () => {
1638
- try {
1639
- warn(
1640
- `Failed to load ${src}. The flow will not start. If this page sets a Content-Security-Policy, it must allow scripts from ${cdnBase}.`
1641
- );
1642
- } catch {
1643
- }
1644
- settle(null);
1645
- };
1646
- (doc.head || doc.documentElement).appendChild(script);
1647
- } catch (error) {
1648
- warn(`The flow engine could not load. ${String(error)}`);
1649
- settle(null);
1650
- return;
1651
- }
1652
- try {
1653
- setTimeoutImpl(() => {
1654
- if (!settled) warn("The flow engine did not load in time.");
1655
- settle(null);
1656
- }, timeoutMs);
1657
- } catch {
1658
- }
1659
- });
1660
- }
1661
828
  function createFlowController(options) {
1662
829
  const {
1663
830
  win = typeof window !== "undefined" ? window : void 0,
1664
- cdnBase = FLOW_CDN_BASE,
831
+ loadChunk,
1665
832
  loadTimeoutMs = FLOW_LOAD_TIMEOUT_MS,
1666
833
  setTimeoutImpl = (fn, ms) => setTimeout(fn, ms),
1667
834
  context
@@ -1675,12 +842,7 @@ function createFlowController(options) {
1675
842
  loading = (async () => {
1676
843
  if (!win) return;
1677
844
  try {
1678
- const registration = await loadFlowChunk(
1679
- win,
1680
- cdnBase,
1681
- loadTimeoutMs,
1682
- setTimeoutImpl
1683
- );
845
+ const registration = await loadChunk(win, loadTimeoutMs, setTimeoutImpl);
1684
846
  if (!registration || destroyed) return;
1685
847
  handle = registration.mount({ ...context, doc: win.document, win });
1686
848
  if (!handle) warn("The flow engine could not start.");
@@ -1759,90 +921,6 @@ function createFlowController(options) {
1759
921
  };
1760
922
  }
1761
923
 
1762
- // src/shell/drag.ts
1763
- function carriesFiles(event) {
1764
- const transfer = event.dataTransfer;
1765
- if (!transfer) return false;
1766
- const types = transfer.types;
1767
- if (!types) return false;
1768
- for (let index = 0; index < types.length; index += 1) {
1769
- if (types[index] === "Files") return true;
1770
- }
1771
- return false;
1772
- }
1773
- function droppedFiles(event) {
1774
- const transfer = event.dataTransfer;
1775
- return Array.from(transfer?.files ?? []);
1776
- }
1777
-
1778
- // src/shell/tooltip.ts
1779
- var TOOLTIP_CSS = `
1780
- [data-tip] {
1781
- position: relative;
1782
- }
1783
-
1784
- [data-tip]::after {
1785
- content: attr(data-tip);
1786
- position: absolute;
1787
- left: 50%;
1788
- bottom: calc(100% + 6px);
1789
- transform: translateX(-50%);
1790
- z-index: 2;
1791
- padding: 4px 8px;
1792
- border-radius: 6px;
1793
- background: var(--_arcy-main-text, #101828);
1794
- color: var(--_arcy-main-bg, #ffffff);
1795
- font-size: 11px;
1796
- line-height: 1.3;
1797
- white-space: nowrap;
1798
- pointer-events: none;
1799
- opacity: 0;
1800
- visibility: hidden;
1801
- transition: opacity 120ms ease;
1802
- }
1803
-
1804
- [data-tip][data-tip-placement="below"]::after {
1805
- bottom: auto;
1806
- top: calc(100% + 6px);
1807
- }
1808
-
1809
- /* Alignment, for a control near an edge the panel clips (D895). Centring a
1810
- bubble on the leftmost header icon puts half of it outside the panel, and
1811
- the panel clips its overflow, so the half outside is simply gone. No
1812
- z-index can fix that: the bubble is not behind anything, it is cut off.
1813
- These pin the bubble to the control's own edge instead. */
1814
- [data-tip][data-tip-align="start"]::after {
1815
- left: 0;
1816
- transform: none;
1817
- }
1818
-
1819
- [data-tip][data-tip-align="end"]::after {
1820
- left: auto;
1821
- right: 0;
1822
- transform: none;
1823
- }
1824
-
1825
- [data-tip]:hover::after,
1826
- [data-tip]:focus-visible::after {
1827
- opacity: 1;
1828
- visibility: visible;
1829
- }
1830
-
1831
- /* A coarse pointer has no hover: the tooltip would latch open on tap and
1832
- sit over the thing the visitor just pressed. */
1833
- @media (hover: none) {
1834
- [data-tip]::after {
1835
- display: none;
1836
- }
1837
- }
1838
-
1839
- @media (prefers-reduced-motion: reduce) {
1840
- [data-tip]::after {
1841
- transition: none;
1842
- }
1843
- }
1844
- `;
1845
-
1846
924
  // src/shell/styles.ts
1847
925
  var BAR_CSS = TOOLTIP_CSS + `
1848
926
  .arcy-bar-wrap {
@@ -2209,20 +1287,20 @@ var BAR_CSS = TOOLTIP_CSS + `
2209
1287
  outline-offset: 1px;
2210
1288
  }
2211
1289
 
2212
- /* The attachment count, so a visitor can see what is about to be sent
2213
- without the bar owning the upload. */
2214
- .arcy-bar-badge {
2215
- display: flex;
1290
+ /* The attachment tray. See bar.ts's renderTray(). */
1291
+ .arcy-bar-tray {
1292
+ display: none;
1293
+ flex-wrap: wrap;
2216
1294
  align-items: center;
2217
- justify-content: center;
2218
- flex: 0 0 auto;
2219
- min-width: 20px;
2220
- height: 20px;
2221
- padding: 0 6px;
2222
- border-radius: 999px;
2223
- font-size: 12px;
2224
- background: var(--_arcy-brand-bg, #101828);
2225
- color: var(--_arcy-brand-text, #ffffff);
1295
+ gap: 6px;
1296
+ padding: 10px 4px 2px 4px;
1297
+ min-width: 0;
1298
+ }
1299
+
1300
+ /* A composer holding pictures is two rows, not one. */
1301
+ .arcy-bar[data-attachments="true"] {
1302
+ flex-direction: column;
1303
+ align-items: stretch;
2226
1304
  }
2227
1305
 
2228
1306
  /* \u2500\u2500 Flow-running mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
@@ -2342,59 +1420,7 @@ var BAR_CSS = TOOLTIP_CSS + `
2342
1420
  animation: none;
2343
1421
  }
2344
1422
  }
2345
- `;
2346
-
2347
- // src/shell/viewport.ts
2348
- var MOBILE_BREAKPOINT = 640;
2349
- var MOBILE_MARGIN = 12;
2350
- var PANEL_MARGIN = 16;
2351
- var positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
2352
- function readSize(win) {
2353
- const el = win?.document?.documentElement;
2354
- return {
2355
- width: positive(el?.clientWidth) || positive(win?.innerWidth),
2356
- height: positive(el?.clientHeight) || positive(win?.innerHeight)
2357
- };
2358
- }
2359
- function keyboardOffset(win) {
2360
- const vv = win?.visualViewport;
2361
- if (!vv) return 0;
2362
- const layoutHeight = readSize(win).height;
2363
- const visualHeight = positive(vv.height);
2364
- if (!layoutHeight || !visualHeight) return 0;
2365
- const top = typeof vv.offsetTop === "number" && Number.isFinite(vv.offsetTop) ? vv.offsetTop : 0;
2366
- const covered = layoutHeight - (visualHeight + top);
2367
- if (!Number.isFinite(covered) || covered < 24) return 0;
2368
- return Math.min(covered, layoutHeight);
2369
- }
2370
- function readViewport(win) {
2371
- const size2 = readSize(win);
2372
- return {
2373
- ...size2,
2374
- isMobile: size2.width > 0 && size2.width < MOBILE_BREAKPOINT,
2375
- keyboardOffset: keyboardOffset(win)
2376
- };
2377
- }
2378
- var FALLBACK_WIDTH_PX = 560;
2379
- function surfaceWidth(widthPercent, viewport) {
2380
- const available = positive(viewport.width);
2381
- if (!available) return FALLBACK_WIDTH_PX;
2382
- if (available < MOBILE_BREAKPOINT) {
2383
- return Math.max(0, available - MOBILE_MARGIN * 2);
2384
- }
2385
- const target = Math.round(widthPercent / 100 * available);
2386
- return Math.min(target, available - PANEL_MARGIN * 2);
2387
- }
2388
- function dragTransform(offset) {
2389
- if (offset.x === 0 && offset.y === 0) return "none";
2390
- return `translate(${offset.x}px, ${offset.y}px)`;
2391
- }
2392
- function clampOffsetX(x, width, viewportWidth) {
2393
- const available = positive(viewportWidth);
2394
- if (!available) return x;
2395
- const maxX = Math.max(0, (available - positive(width)) / 2);
2396
- return Math.min(maxX, Math.max(-maxX, x));
2397
- }
1423
+ `;
2398
1424
 
2399
1425
  // src/shell/bar.ts
2400
1426
  var MAX_INPUT_LENGTH = 1e4;
@@ -2508,7 +1534,7 @@ function createBar(options = {}) {
2508
1534
  let statusStep = null;
2509
1535
  let stop = null;
2510
1536
  let disclaimer = null;
2511
- let badge = null;
1537
+ let tray = null;
2512
1538
  let mode = "idle";
2513
1539
  let flow = null;
2514
1540
  let awaitingInput = false;
@@ -2631,6 +1657,15 @@ function createBar(options = {}) {
2631
1657
  leading.tabIndex = hasMenu ? 0 : -1;
2632
1658
  if (!hasMenu) closeMenu();
2633
1659
  }
1660
+ function renderTray() {
1661
+ if (!tray || !bar) return;
1662
+ const visible = attachEnabled && attachmentCount > 0 && !isCollapsed();
1663
+ const was = bar.getAttribute("data-attachments") === "true";
1664
+ tray.style.setProperty("display", visible ? "flex" : "none");
1665
+ bar.setAttribute("data-attachments", visible ? "true" : "false");
1666
+ if (was === visible) return;
1667
+ safe("onGeometryChange", () => options.onGeometryChange?.())();
1668
+ }
2634
1669
  function setMenuOpen(open) {
2635
1670
  menuOpen = open;
2636
1671
  if (menu) menu.style.setProperty("display", open ? "flex" : "none");
@@ -2689,6 +1724,7 @@ function createBar(options = {}) {
2689
1724
  );
2690
1725
  bar.setAttribute("data-collapsed", isCollapsed() ? "true" : "false");
2691
1726
  renderLeading();
1727
+ renderTray();
2692
1728
  render();
2693
1729
  form.style.setProperty("display", effective === "flow" ? "none" : "flex");
2694
1730
  status.style.setProperty(
@@ -2894,9 +1930,9 @@ function createBar(options = {}) {
2894
1930
  input.maxLength = MAX_INPUT_LENGTH;
2895
1931
  input.setAttribute("aria-label", labels.openChat);
2896
1932
  input.autocomplete = "off";
2897
- badge = doc.createElement("span");
2898
- badge.className = "arcy-bar-badge";
2899
- badge.style.setProperty("display", "none");
1933
+ tray = doc.createElement("div");
1934
+ tray.className = BAR_TRAY_CLASS;
1935
+ tray.style.setProperty("display", "none");
2900
1936
  grip = doc.createElement("button");
2901
1937
  grip.type = "button";
2902
1938
  grip.className = "arcy-bar-grip";
@@ -2937,7 +1973,6 @@ function createBar(options = {}) {
2937
1973
  form.appendChild(grip);
2938
1974
  form.appendChild(leading);
2939
1975
  form.appendChild(input);
2940
- form.appendChild(badge);
2941
1976
  form.appendChild(submit);
2942
1977
  status = doc.createElement("div");
2943
1978
  status.className = "arcy-bar-status";
@@ -2958,6 +1993,7 @@ function createBar(options = {}) {
2958
1993
  status.appendChild(statusName);
2959
1994
  status.appendChild(statusStep);
2960
1995
  status.appendChild(stop);
1996
+ bar.appendChild(tray);
2961
1997
  bar.appendChild(form);
2962
1998
  bar.appendChild(status);
2963
1999
  bar.appendChild(menu);
@@ -3045,7 +2081,7 @@ function createBar(options = {}) {
3045
2081
  dock = null;
3046
2082
  dockIcon = null;
3047
2083
  menu = null;
3048
- badge = null;
2084
+ tray = null;
3049
2085
  menuOpen = false;
3050
2086
  flow = null;
3051
2087
  mode = "idle";
@@ -3201,13 +2237,11 @@ function createBar(options = {}) {
3201
2237
  setAttachmentCount(count) {
3202
2238
  attachmentCount = count;
3203
2239
  try {
3204
- if (!badge) return;
3205
- const visible = attachEnabled && count > 0;
3206
- badge.textContent = visible ? String(count) : "";
3207
- badge.style.setProperty("display", visible ? "flex" : "none");
2240
+ renderTray();
3208
2241
  } catch {
3209
2242
  }
3210
2243
  },
2244
+ attachmentSlot: () => tray,
3211
2245
  focusInput() {
3212
2246
  try {
3213
2247
  if (shell) releaseHostFocus(shell.host.ownerDocument, shell.host);
@@ -3395,13 +2429,12 @@ function applyTokens(host, tokens) {
3395
2429
  var TOKEN_KEY_PREFIX = "arcy.tokens.";
3396
2430
  var MAX_STORED = 4096;
3397
2431
  var MAX_NAME = 100;
3398
- var MAX_URL = 2048;
3399
2432
  function readIdentity(value) {
3400
2433
  const raw = object(value);
3401
2434
  if (!raw) return null;
3402
2435
  const name = typeof raw.name === "string" ? raw.name.slice(0, MAX_NAME) : "";
3403
2436
  const url = raw.logoUrl;
3404
- const logoUrl = typeof url === "string" && url.length <= MAX_URL && /^https:\/\//i.test(url) ? url : null;
2437
+ const logoUrl = isRenderableImageUrl(url) ? url : null;
3405
2438
  return { name, logoUrl };
3406
2439
  }
3407
2440
  function createTokenCache(token, raw = browserLocalStorage()) {
@@ -3432,93 +2465,6 @@ function createTokenCache(token, raw = browserLocalStorage()) {
3432
2465
  };
3433
2466
  }
3434
2467
 
3435
- // src/shell/state.ts
3436
- var SHELL_KEY_PREFIX = "arcy.shell.";
3437
- var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
3438
- var LOCALE_PATTERN = /^[a-z]{2}-[A-Z]{2}$/;
3439
- function readPosition(value) {
3440
- if (!value || typeof value !== "object") return null;
3441
- const { x, y } = value;
3442
- if (typeof x !== "number" || typeof y !== "number") return null;
3443
- if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
3444
- return { x, y };
3445
- }
3446
- function uuidV4(fill) {
3447
- const source = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
3448
- if (!fill && source && typeof source.randomUUID === "function") {
3449
- try {
3450
- const native = source.randomUUID();
3451
- if (UUID_PATTERN.test(native)) return native;
3452
- } catch {
3453
- }
3454
- }
3455
- const bytes = new Uint8Array(16);
3456
- if (fill) {
3457
- fill(bytes);
3458
- } else if (source && typeof source.getRandomValues === "function") {
3459
- source.getRandomValues(bytes);
3460
- } else {
3461
- for (let i = 0; i < bytes.length; i++) {
3462
- bytes[i] = Math.floor(Math.random() * 256);
3463
- }
3464
- }
3465
- bytes[6] = (bytes[6] ?? 0) & 15 | 64;
3466
- bytes[8] = (bytes[8] ?? 0) & 63 | 128;
3467
- let hex = "";
3468
- for (let i = 0; i < bytes.length; i++) {
3469
- hex += (bytes[i] ?? 0).toString(16).padStart(2, "0");
3470
- }
3471
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
3472
- }
3473
- function createShellState(token, { store: raw = browserLocalStorage(), fill } = {}) {
3474
- const store = safeStore(raw);
3475
- const key = SHELL_KEY_PREFIX + token;
3476
- function readRaw() {
3477
- return readJson(store, key) ?? {};
3478
- }
3479
- function patch(next) {
3480
- writeJson(store, key, { ...readRaw(), ...next });
3481
- }
3482
- function read() {
3483
- const stored = readRaw();
3484
- const id = stored.anonymousId;
3485
- return {
3486
- anonymousId: typeof id === "string" && UUID_PATTERN.test(id) ? id : null,
3487
- position: readPosition(stored.position),
3488
- // Anything but a literal `true` means not docked. The value is in the
3489
- // visitor's own storage, so a truthy string written by an older build
3490
- // must not be able to hide the widget.
3491
- docked: stored.docked === true,
3492
- locale: typeof stored.locale === "string" && LOCALE_PATTERN.test(stored.locale) ? stored.locale : null
3493
- };
3494
- }
3495
- return {
3496
- read,
3497
- anonymousId() {
3498
- const existing = read().anonymousId;
3499
- if (existing) return existing;
3500
- patch({ anonymousId: uuidV4(fill) });
3501
- return read().anonymousId;
3502
- },
3503
- rememberPosition(position) {
3504
- patch({ position });
3505
- },
3506
- rememberDocked(docked) {
3507
- patch({ docked });
3508
- },
3509
- rememberLocale(locale) {
3510
- patch({ locale });
3511
- },
3512
- forgetAnonymousId() {
3513
- const current = readRaw();
3514
- if (current.anonymousId === void 0) return;
3515
- delete current.anonymousId;
3516
- if (Object.keys(current).length === 0) store.removeItem(key);
3517
- else writeJson(store, key, current);
3518
- }
3519
- };
3520
- }
3521
-
3522
2468
  // src/session/visit.ts
3523
2469
  var VISIT_KEY_PREFIX = "arcy.visit.";
3524
2470
  var FIRST_VISIT_VALUE = "first";
@@ -3610,6 +2556,97 @@ function createConversationStore(token, { store: raw = browserLocalStorage(), fi
3610
2556
  };
3611
2557
  }
3612
2558
 
2559
+ // src/session/context.ts
2560
+ var CAMPAIGN_KEYS = [
2561
+ ["utm_source", "utmSource"],
2562
+ ["utm_medium", "utmMedium"],
2563
+ ["utm_campaign", "utmCampaign"],
2564
+ ["utm_content", "utmContent"],
2565
+ ["utm_term", "utmTerm"]
2566
+ ];
2567
+ var MAX_CAMPAIGN_LENGTH = 255;
2568
+ var MAX_REFERRER_LENGTH = 512;
2569
+ var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/g;
2570
+ function clean(value, max) {
2571
+ const trimmed = value.replace(CONTROL_CHARACTERS, "").trim();
2572
+ if (trimmed.length === 0) return void 0;
2573
+ return trimmed.slice(0, max);
2574
+ }
2575
+ function scrubReferrer(referrer) {
2576
+ if (typeof referrer !== "string" || referrer.length === 0) return void 0;
2577
+ try {
2578
+ const url = new URL(referrer);
2579
+ const host = url.host;
2580
+ if (!host) return void 0;
2581
+ const path = scrubPath(url.pathname);
2582
+ return clean(path === "/" ? host : host + path, MAX_REFERRER_LENGTH);
2583
+ } catch {
2584
+ return void 0;
2585
+ }
2586
+ }
2587
+ function readCampaign(search) {
2588
+ const out = {};
2589
+ if (typeof search !== "string" || search.length === 0) return out;
2590
+ const query = search.charAt(0) === "?" ? search.slice(1) : search;
2591
+ for (const pair of query.split("&")) {
2592
+ const eq = pair.indexOf("=");
2593
+ if (eq <= 0) continue;
2594
+ const rawKey = pair.slice(0, eq).toLowerCase();
2595
+ const match = CAMPAIGN_KEYS.find((entry) => entry[0] === rawKey);
2596
+ if (!match) continue;
2597
+ let value = pair.slice(eq + 1).replace(/\+/g, " ");
2598
+ try {
2599
+ value = decodeURIComponent(value);
2600
+ } catch {
2601
+ }
2602
+ const cleaned = clean(value, MAX_CAMPAIGN_LENGTH);
2603
+ if (cleaned) out[match[1]] = cleaned;
2604
+ }
2605
+ return out;
2606
+ }
2607
+ function readBrowser(userAgent) {
2608
+ const ua = typeof userAgent === "string" ? userAgent : "";
2609
+ if (ua.length === 0) return void 0;
2610
+ if (/Edg[A-Z]?\//.test(ua)) return "Edge";
2611
+ if (/OPR\/|Opera/.test(ua)) return "Opera";
2612
+ if (/SamsungBrowser\//.test(ua)) return "Samsung Internet";
2613
+ if (/Firefox\/|FxiOS\//.test(ua)) return "Firefox";
2614
+ if (/Chrome\/|CriOS\//.test(ua)) return "Chrome";
2615
+ if (/Safari\//.test(ua)) return "Safari";
2616
+ return void 0;
2617
+ }
2618
+ function readOs(userAgent) {
2619
+ const ua = typeof userAgent === "string" ? userAgent : "";
2620
+ if (ua.length === 0) return void 0;
2621
+ if (/iPhone|iPad|iPod/.test(ua)) return "iOS";
2622
+ if (/Android/.test(ua)) return "Android";
2623
+ if (/CrOS/.test(ua)) return "ChromeOS";
2624
+ if (/Windows/.test(ua)) return "Windows";
2625
+ if (/Mac OS X|Macintosh/.test(ua)) return "macOS";
2626
+ if (/Linux/.test(ua)) return "Linux";
2627
+ return void 0;
2628
+ }
2629
+ function viewportBucket(width) {
2630
+ if (typeof width !== "number" || !(width > 0)) return void 0;
2631
+ if (width < 480) return "xs";
2632
+ if (width < 768) return "sm";
2633
+ if (width < 1024) return "md";
2634
+ if (width < 1280) return "lg";
2635
+ return "xl";
2636
+ }
2637
+ function captureSessionContext(input) {
2638
+ const context = { ...readCampaign(input.search) };
2639
+ const referrer = scrubReferrer(input.referrer);
2640
+ if (referrer) context.referrer = referrer;
2641
+ const browser = readBrowser(input.userAgent);
2642
+ if (browser) context.browser = browser;
2643
+ const os = readOs(input.userAgent);
2644
+ if (os) context.os = os;
2645
+ const viewport = viewportBucket(input.viewportWidth);
2646
+ if (viewport) context.viewport = viewport;
2647
+ return Object.keys(context).length > 0 ? context : void 0;
2648
+ }
2649
+
3613
2650
  // src/telemetry/consent.ts
3614
2651
  var STATES = ["ON", "REQUIRE_CONSENT", "OFF"];
3615
2652
  function createConsentCache(store, key) {
@@ -3666,8 +2703,14 @@ function deviceClass(nav) {
3666
2703
 
3667
2704
  // src/telemetry/describe.ts
3668
2705
  var MAX_TEXT_LENGTH = 100;
2706
+ var PII_SCAN_MAX = 4096;
3669
2707
  var VALUE_BEARING = ["INPUT", "TEXTAREA", "SELECT"];
3670
- function describeClick(element, describeElement) {
2708
+ function readableCopy(raw) {
2709
+ if (raw.length === 0) return void 0;
2710
+ if (containsPii(raw.slice(0, PII_SCAN_MAX))) return void 0;
2711
+ return raw.slice(0, MAX_TEXT_LENGTH);
2712
+ }
2713
+ function describeElementPayload(element, describeElement) {
3671
2714
  const tag = (element.tagName ?? "").toUpperCase();
3672
2715
  const payload = { tag: tag.toLowerCase() };
3673
2716
  const role = element.getAttribute("role");
@@ -3676,10 +2719,12 @@ function describeClick(element, describeElement) {
3676
2719
  if (type) payload.type = type.slice(0, 32);
3677
2720
  if (VALUE_BEARING.indexOf(tag) === -1) {
3678
2721
  const text = (element.textContent ?? "").replace(/\s+/g, " ").trim();
3679
- if (text) payload.text = text.slice(0, MAX_TEXT_LENGTH);
2722
+ const readable = readableCopy(text);
2723
+ if (readable) payload.text = readable;
3680
2724
  } else {
3681
2725
  const label = element.getAttribute("aria-label") ?? element.getAttribute("name");
3682
- if (label) payload.label = label.slice(0, MAX_TEXT_LENGTH);
2726
+ const readable = readableCopy(label ?? "");
2727
+ if (readable) payload.label = readable;
3683
2728
  }
3684
2729
  if (describeElement) {
3685
2730
  const fingerprint = describeElement(element);
@@ -3689,8 +2734,17 @@ function describeClick(element, describeElement) {
3689
2734
  }
3690
2735
  return payload;
3691
2736
  }
3692
- function describeSubmit(form) {
3693
- const payload = {};
2737
+ function describeClick(element, describeElement) {
2738
+ return describeElementPayload(element, describeElement);
2739
+ }
2740
+ function describeField(element, describeElement) {
2741
+ const payload = describeElementPayload(element, describeElement);
2742
+ const valid = element.validity?.valid;
2743
+ if (typeof valid === "boolean") payload.valid = valid;
2744
+ return payload;
2745
+ }
2746
+ function describeSubmit(form, describeElement) {
2747
+ const payload = describeElementPayload(form, describeElement);
3694
2748
  const name = form.getAttribute("name") ?? form.getAttribute("id");
3695
2749
  if (name) payload.form = name.slice(0, MAX_TEXT_LENGTH);
3696
2750
  const count = form.elements?.length;
@@ -3698,6 +2752,169 @@ function describeSubmit(form) {
3698
2752
  return payload;
3699
2753
  }
3700
2754
 
2755
+ // src/telemetry/errors.ts
2756
+ var REQUEST_METHODS = [
2757
+ "GET",
2758
+ "POST",
2759
+ "PUT",
2760
+ "PATCH",
2761
+ "DELETE",
2762
+ "HEAD",
2763
+ "OPTIONS"
2764
+ ];
2765
+ var OTHER_METHOD = "OTHER";
2766
+ var FAILED_STATUS_MIN = 400;
2767
+ var ERROR_MESSAGE_MAX = 200;
2768
+ var CONTROL_CHARACTERS2 = /[\u0000-\u001f\u007f]+/g;
2769
+ function readErrorMessage(raw) {
2770
+ let text = null;
2771
+ if (typeof raw === "string") text = raw;
2772
+ else if (raw && typeof raw === "object") {
2773
+ const message = raw.message;
2774
+ if (typeof message === "string") text = message;
2775
+ }
2776
+ if (text === null) return null;
2777
+ const flattened = text.replace(CONTROL_CHARACTERS2, " ").trim();
2778
+ if (flattened.length === 0) return null;
2779
+ return flattened.length > ERROR_MESSAGE_MAX ? flattened.slice(0, ERROR_MESSAGE_MAX) : flattened;
2780
+ }
2781
+ function normalizeMethod(raw) {
2782
+ if (typeof raw !== "string") return "GET";
2783
+ const upper = raw.toUpperCase();
2784
+ return REQUEST_METHODS.indexOf(upper) === -1 ? OTHER_METHOD : upper;
2785
+ }
2786
+ function isFailedStatus(status) {
2787
+ return typeof status === "number" && status >= FAILED_STATUS_MIN;
2788
+ }
2789
+ function installNetworkWatch(options) {
2790
+ const { target, report, ignore } = options;
2791
+ const restores = [];
2792
+ function emit(method, status, raw) {
2793
+ try {
2794
+ if (!isFailedStatus(status)) return;
2795
+ if (raw !== null && ignore?.(raw)) return;
2796
+ report({
2797
+ method,
2798
+ status,
2799
+ url: raw === null ? null : normalizeRoute(raw, target.location?.href)
2800
+ });
2801
+ } catch {
2802
+ }
2803
+ }
2804
+ function installFetch() {
2805
+ const original = target.fetch;
2806
+ if (typeof original !== "function") return;
2807
+ const wrapper = function(...args) {
2808
+ const result = original.apply(this, args);
2809
+ try {
2810
+ const promise = result;
2811
+ if (typeof promise?.then !== "function") return result;
2812
+ promise.then(
2813
+ (response) => {
2814
+ const status = response?.status;
2815
+ emit(fetchMethod(args), status, fetchUrl(args));
2816
+ },
2817
+ () => {
2818
+ }
2819
+ );
2820
+ } catch {
2821
+ }
2822
+ return result;
2823
+ };
2824
+ target.fetch = wrapper;
2825
+ restores.push(() => {
2826
+ if (target.fetch === wrapper) target.fetch = original;
2827
+ });
2828
+ }
2829
+ function installXhr() {
2830
+ const proto = target.XMLHttpRequest?.prototype;
2831
+ const originalOpen = proto?.open;
2832
+ const originalSend = proto?.send;
2833
+ if (!proto || typeof originalOpen !== "function" || typeof originalSend !== "function") {
2834
+ return;
2835
+ }
2836
+ if (typeof WeakMap !== "function") return;
2837
+ const pending = /* @__PURE__ */ new WeakMap();
2838
+ const open = function(...args) {
2839
+ const result = originalOpen.apply(this, args);
2840
+ try {
2841
+ pending.set(this, {
2842
+ method: normalizeMethod(args[0]),
2843
+ url: typeof args[1] === "string" ? args[1] : null,
2844
+ listening: pending.get(this)?.listening ?? false
2845
+ });
2846
+ } catch {
2847
+ }
2848
+ return result;
2849
+ };
2850
+ const send = function(...args) {
2851
+ try {
2852
+ const xhr = this;
2853
+ const record = pending.get(this);
2854
+ if (typeof xhr.addEventListener === "function" && record?.listening !== true) {
2855
+ if (record) record.listening = true;
2856
+ xhr.addEventListener("loadend", () => {
2857
+ const current = pending.get(this);
2858
+ emit(current?.method ?? "GET", xhr.status, current?.url ?? null);
2859
+ });
2860
+ }
2861
+ } catch {
2862
+ }
2863
+ return originalSend.apply(this, args);
2864
+ };
2865
+ proto.open = open;
2866
+ restores.push(() => {
2867
+ if (proto.open === open) proto.open = originalOpen;
2868
+ });
2869
+ proto.send = send;
2870
+ restores.push(() => {
2871
+ if (proto.send === send) proto.send = originalSend;
2872
+ });
2873
+ }
2874
+ try {
2875
+ installFetch();
2876
+ installXhr();
2877
+ } catch {
2878
+ for (const restore of restores) {
2879
+ try {
2880
+ restore();
2881
+ } catch {
2882
+ }
2883
+ }
2884
+ return null;
2885
+ }
2886
+ if (restores.length === 0) return null;
2887
+ return () => {
2888
+ for (const restore of restores) {
2889
+ try {
2890
+ restore();
2891
+ } catch {
2892
+ }
2893
+ }
2894
+ restores.length = 0;
2895
+ };
2896
+ }
2897
+ function fetchMethod(args) {
2898
+ const init = args[1];
2899
+ if (init && typeof init === "object" && typeof init.method === "string") {
2900
+ return normalizeMethod(init.method);
2901
+ }
2902
+ const input = args[0];
2903
+ if (input && typeof input === "object" && typeof input.method === "string") {
2904
+ return normalizeMethod(input.method);
2905
+ }
2906
+ return "GET";
2907
+ }
2908
+ function fetchUrl(args) {
2909
+ const input = args[0];
2910
+ if (typeof input === "string") return input;
2911
+ if (!input || typeof input !== "object") return null;
2912
+ const url = input.url;
2913
+ if (typeof url === "string") return url;
2914
+ const href = input.href;
2915
+ return typeof href === "string" ? href : null;
2916
+ }
2917
+
3701
2918
  // src/telemetry/events.ts
3702
2919
  function buildEvent(id, name, occurredAt, context, payload) {
3703
2920
  const event = {
@@ -3716,6 +2933,63 @@ function buildEvent(id, name, occurredAt, context, payload) {
3716
2933
  return event;
3717
2934
  }
3718
2935
 
2936
+ // src/telemetry/fields.ts
2937
+ var NON_FIELD_TYPES = [
2938
+ "button",
2939
+ "submit",
2940
+ "reset",
2941
+ "image",
2942
+ "hidden",
2943
+ "checkbox",
2944
+ "radio"
2945
+ ];
2946
+ var FIELD_TAGS = ["INPUT", "TEXTAREA", "SELECT"];
2947
+ function isFieldElement(element) {
2948
+ if (!element || typeof element.getAttribute !== "function") return false;
2949
+ const tag = (element.tagName ?? "").toUpperCase();
2950
+ if (FIELD_TAGS.indexOf(tag) === -1) return false;
2951
+ if (tag !== "INPUT") return true;
2952
+ const type = (element.getAttribute("type") ?? "text").toLowerCase();
2953
+ return NON_FIELD_TYPES.indexOf(type) === -1;
2954
+ }
2955
+ function isEmpty(element) {
2956
+ const value = element.value;
2957
+ return typeof value === "string" ? value.length === 0 : value == null;
2958
+ }
2959
+ function createFieldObserver(options) {
2960
+ let active = null;
2961
+ return {
2962
+ focus(element) {
2963
+ if (!isFieldElement(element)) return;
2964
+ active = {
2965
+ element,
2966
+ empty: isEmpty(element),
2967
+ filled: false,
2968
+ cleared: false
2969
+ };
2970
+ options.emit("field_focused", element);
2971
+ },
2972
+ change(element) {
2973
+ if (!active || active.element !== element) return;
2974
+ const empty = isEmpty(element);
2975
+ if (empty === active.empty) return;
2976
+ active.empty = empty;
2977
+ if (!empty) {
2978
+ if (active.filled) return;
2979
+ active.filled = true;
2980
+ options.emit("input_filled", element);
2981
+ return;
2982
+ }
2983
+ if (active.cleared) return;
2984
+ active.cleared = true;
2985
+ options.emit("input_cleared", element);
2986
+ },
2987
+ blur(element) {
2988
+ if (active && active.element === element) active = null;
2989
+ }
2990
+ };
2991
+ }
2992
+
3719
2993
  // src/telemetry/interactive.ts
3720
2994
  var INTERACTIVE_TAGS = [
3721
2995
  "A",
@@ -3766,6 +3040,78 @@ function findInteractive(target, cursorOf) {
3766
3040
  return null;
3767
3041
  }
3768
3042
 
3043
+ // src/telemetry/scroll.ts
3044
+ var SCROLL_MILESTONES = [25, 50, 75, 100];
3045
+ var SCROLL_THROTTLE_MS = 250;
3046
+ var FINAL_MILESTONE = 100;
3047
+ function createScrollObserver(options) {
3048
+ const {
3049
+ readDepth,
3050
+ emit,
3051
+ setTimeoutImpl = (fn, ms) => setTimeout(fn, ms),
3052
+ clearTimeoutImpl = (handle) => clearTimeout(handle),
3053
+ throttleMs = SCROLL_THROTTLE_MS
3054
+ } = options;
3055
+ let reported = 0;
3056
+ let pending = null;
3057
+ function evaluate() {
3058
+ pending = null;
3059
+ let depth = null;
3060
+ try {
3061
+ depth = readDepth();
3062
+ } catch {
3063
+ return;
3064
+ }
3065
+ if (depth === null) return;
3066
+ let reached = 0;
3067
+ for (const milestone of SCROLL_MILESTONES) {
3068
+ if (depth >= milestone) reached = milestone;
3069
+ }
3070
+ if (reached <= reported) return;
3071
+ reported = reached;
3072
+ emit(reached);
3073
+ }
3074
+ return {
3075
+ scrolled() {
3076
+ if (pending !== null) return;
3077
+ if (reported >= FINAL_MILESTONE) return;
3078
+ try {
3079
+ pending = setTimeoutImpl(evaluate, throttleMs);
3080
+ } catch {
3081
+ pending = null;
3082
+ }
3083
+ },
3084
+ reset() {
3085
+ reported = 0;
3086
+ if (pending !== null) {
3087
+ try {
3088
+ clearTimeoutImpl(pending);
3089
+ } catch {
3090
+ }
3091
+ pending = null;
3092
+ }
3093
+ },
3094
+ dispose() {
3095
+ if (pending === null) return;
3096
+ try {
3097
+ clearTimeoutImpl(pending);
3098
+ } catch {
3099
+ }
3100
+ pending = null;
3101
+ }
3102
+ };
3103
+ }
3104
+ function scrollDepthPercent(metrics) {
3105
+ const viewport = metrics.innerHeight ?? 0;
3106
+ const total = metrics.documentHeight ?? 0;
3107
+ const scrollable = total - viewport;
3108
+ if (!(scrollable > 0) || !(viewport > 0)) return null;
3109
+ const scrolled = (metrics.scrollY ?? 0) + viewport;
3110
+ const percent = scrolled / total * 100;
3111
+ if (!(percent > 0)) return 0;
3112
+ return percent > 100 ? 100 : percent;
3113
+ }
3114
+
3769
3115
  // src/telemetry/session.ts
3770
3116
  var SESSION_WINDOW_MS = 30 * 60 * 1e3;
3771
3117
  var TOUCH_WRITE_INTERVAL_MS = 60 * 1e3;
@@ -3776,22 +3122,34 @@ function createSessionStore({
3776
3122
  fill,
3777
3123
  windowMs = SESSION_WINDOW_MS
3778
3124
  }) {
3779
- function touch() {
3780
- const at = now();
3125
+ function alive(at) {
3781
3126
  const existing = readJson(store, key);
3782
- const alive = existing && typeof existing.id === "string" && existing.id.length > 0 && typeof existing.seen === "number" && at - existing.seen < windowMs;
3783
- if (alive) {
3784
- if (at - existing.seen >= TOUCH_WRITE_INTERVAL_MS) {
3785
- writeJson(store, key, { id: existing.id, seen: at });
3786
- }
3787
- return existing.id;
3127
+ if (existing && typeof existing.id === "string" && existing.id.length > 0 && typeof existing.seen === "number" && at - existing.seen < windowMs) {
3128
+ return existing;
3788
3129
  }
3130
+ return null;
3131
+ }
3132
+ function mint(at) {
3789
3133
  const id = ulid(at, fill);
3790
3134
  writeJson(store, key, { id, seen: at });
3791
3135
  return id;
3792
3136
  }
3137
+ function touch() {
3138
+ const at = now();
3139
+ const existing = alive(at);
3140
+ if (!existing) return mint(at);
3141
+ if (at - existing.seen >= TOUCH_WRITE_INTERVAL_MS) {
3142
+ writeJson(store, key, { id: existing.id, seen: at });
3143
+ }
3144
+ return existing.id;
3145
+ }
3146
+ function current() {
3147
+ const at = now();
3148
+ return alive(at)?.id ?? mint(at);
3149
+ }
3793
3150
  return {
3794
3151
  touch,
3152
+ current,
3795
3153
  peek: () => readJson(store, key),
3796
3154
  clear() {
3797
3155
  store.removeItem(key);
@@ -4069,7 +3427,7 @@ function createTelemetry(options) {
4069
3427
  function currentRoute() {
4070
3428
  return normalizeRoute(win?.location?.href) ?? void 0;
4071
3429
  }
4072
- function capture(name, payload, fields) {
3430
+ function capture(name, payload, fields2) {
4073
3431
  if (halted) return false;
4074
3432
  if (!consent.shouldCapture()) return false;
4075
3433
  if (transport.stopped() !== null) return false;
@@ -4086,13 +3444,13 @@ function createTelemetry(options) {
4086
3444
  route: currentRoute(),
4087
3445
  localeCode,
4088
3446
  deviceClass: resolvedDeviceClass,
4089
- flowSessionId: fields?.flowSessionId ?? getFlowSessionId?.() ?? void 0
3447
+ flowSessionId: fields2?.flowSessionId ?? getFlowSessionId?.() ?? void 0
4090
3448
  },
4091
3449
  payload
4092
3450
  );
4093
- if (fields?.flowId) event.flowId = fields.flowId;
4094
- if (fields?.flowVersion) event.flowVersion = fields.flowVersion;
4095
- if (fields?.stepCvid) event.stepCvid = fields.stepCvid;
3451
+ if (fields2?.flowId) event.flowId = fields2.flowId;
3452
+ if (fields2?.flowVersion) event.flowVersion = fields2.flowVersion;
3453
+ if (fields2?.stepCvid) event.stepCvid = fields2.stepCvid;
4096
3454
  transport.enqueue(event);
4097
3455
  return true;
4098
3456
  }
@@ -4118,7 +3476,7 @@ function createTelemetry(options) {
4118
3476
  const raw = event;
4119
3477
  if (isSynthetic(raw)) return;
4120
3478
  if (!raw.target || typeof raw.target.getAttribute !== "function") return;
4121
- capture("form_submit", describeSubmit(raw.target));
3479
+ capture("form_submit", describeSubmit(raw.target, describeElement));
4122
3480
  } catch {
4123
3481
  }
4124
3482
  };
@@ -4128,16 +3486,118 @@ function createTelemetry(options) {
4128
3486
  if (route === lastRoute) return;
4129
3487
  const from = lastRoute;
4130
3488
  lastRoute = route;
3489
+ scroll.reset();
4131
3490
  capture("navigation", from ? { from } : void 0);
4132
3491
  } catch {
4133
3492
  }
4134
3493
  };
3494
+ const fields = createFieldObserver({
3495
+ emit: (name, element) => {
3496
+ capture(name, describeField(element, describeElement));
3497
+ }
3498
+ });
3499
+ const scroll = createScrollObserver({
3500
+ readDepth: () => scrollDepthPercent({
3501
+ scrollY: win?.scrollY,
3502
+ innerHeight: win?.innerHeight,
3503
+ documentHeight: doc?.documentElement?.scrollHeight
3504
+ }),
3505
+ emit: (percent) => {
3506
+ capture("scroll_depth", { percent });
3507
+ },
3508
+ setTimeoutImpl: options.setTimeoutImpl,
3509
+ clearTimeoutImpl: options.clearTimeoutImpl
3510
+ });
3511
+ let sessionEnded = false;
3512
+ const onFocusIn = (event) => {
3513
+ try {
3514
+ const raw = event;
3515
+ if (isSynthetic(raw) || !raw.target) return;
3516
+ fields.focus(raw.target);
3517
+ } catch {
3518
+ }
3519
+ };
3520
+ const onFieldChange = (event) => {
3521
+ try {
3522
+ const raw = event;
3523
+ if (isSynthetic(raw) || !raw.target) return;
3524
+ fields.change(raw.target);
3525
+ } catch {
3526
+ }
3527
+ };
3528
+ const onFocusOut = (event) => {
3529
+ try {
3530
+ const raw = event;
3531
+ if (!raw.target) return;
3532
+ fields.blur(raw.target);
3533
+ } catch {
3534
+ }
3535
+ };
3536
+ const onScroll = () => {
3537
+ try {
3538
+ scroll.scrolled();
3539
+ } catch {
3540
+ }
3541
+ };
3542
+ const onVisibilityChange = () => {
3543
+ try {
3544
+ if (doc?.visibilityState === "visible") {
3545
+ capture("page_visible");
3546
+ return;
3547
+ }
3548
+ capture("page_hidden");
3549
+ void transport.flush(true);
3550
+ } catch {
3551
+ }
3552
+ };
4135
3553
  const onPageHide = () => {
4136
3554
  try {
3555
+ if (!sessionEnded) {
3556
+ sessionEnded = true;
3557
+ capture("session_end");
3558
+ }
4137
3559
  void transport.flush(true);
4138
3560
  } catch {
4139
3561
  }
4140
3562
  };
3563
+ const onError = (event) => {
3564
+ try {
3565
+ const raw = event;
3566
+ captureError({
3567
+ message: readErrorMessage(raw.message) ?? "Uncaught error",
3568
+ kind: "error",
3569
+ source: scrubSource(raw.filename)
3570
+ });
3571
+ } catch {
3572
+ }
3573
+ };
3574
+ const onRejection = (event) => {
3575
+ try {
3576
+ const raw = event;
3577
+ captureError({
3578
+ // The kind alone when the reason is neither a string nor an `Error`.
3579
+ // Serializing an unknown object from the customer's application is the
3580
+ // one thing this event must never do.
3581
+ message: readErrorMessage(raw.reason) ?? "Unhandled promise rejection",
3582
+ kind: "unhandledrejection"
3583
+ });
3584
+ } catch {
3585
+ }
3586
+ };
3587
+ function captureError(encounter) {
3588
+ const payload = {
3589
+ message: encounter.message,
3590
+ kind: encounter.kind
3591
+ };
3592
+ if (encounter.source) payload.source = encounter.source;
3593
+ capture("js_error", payload);
3594
+ }
3595
+ function scrubSource(raw) {
3596
+ if (typeof raw !== "string") return void 0;
3597
+ return normalizeRoute(raw, win?.location?.href) ?? void 0;
3598
+ }
3599
+ const ownOrigin = (apiBase ?? DEFAULT_API_BASE2).replace(/\/+$/, "");
3600
+ let uninstallNetworkWatch = null;
4141
3601
  return {
4142
3602
  start() {
4143
3603
  if (started || halted) return;
@@ -4145,10 +3605,31 @@ function createTelemetry(options) {
4145
3605
  lastRoute = currentRoute() ?? null;
4146
3606
  doc?.addEventListener("click", onClick, true);
4147
3607
  doc?.addEventListener("submit", onSubmit, true);
3608
+ doc?.addEventListener("focusin", onFocusIn, true);
3609
+ doc?.addEventListener("focusout", onFocusOut, true);
3610
+ doc?.addEventListener("input", onFieldChange, true);
3611
+ doc?.addEventListener("change", onFieldChange, true);
4148
3612
  win?.addEventListener(ROUTE_CHANGE_EVENT, onNavigate);
4149
3613
  win?.addEventListener("popstate", onNavigate);
3614
+ win?.addEventListener("scroll", onScroll, { passive: true });
4150
3615
  win?.addEventListener("pagehide", onPageHide);
4151
- doc?.addEventListener("visibilitychange", onPageHide);
3616
+ doc?.addEventListener("visibilitychange", onVisibilityChange);
3617
+ win?.addEventListener("error", onError);
3618
+ win?.addEventListener("unhandledrejection", onRejection);
3619
+ if (win && uninstallNetworkWatch === null) {
3620
+ uninstallNetworkWatch = installNetworkWatch({
3621
+ target: win,
3622
+ report: (failure) => {
3623
+ const payload = {
3624
+ method: failure.method,
3625
+ status: failure.status
3626
+ };
3627
+ if (failure.url) payload.url = failure.url;
3628
+ capture("request_failed", payload);
3629
+ },
3630
+ ignore: (url) => url.indexOf(ownOrigin) === 0
3631
+ });
3632
+ }
4152
3633
  loadNavigationPending = !capture("navigation") && consent.state() === null;
4153
3634
  },
4154
3635
  stop(options2) {
@@ -4157,10 +3638,22 @@ function createTelemetry(options) {
4157
3638
  started = false;
4158
3639
  doc?.removeEventListener("click", onClick, true);
4159
3640
  doc?.removeEventListener("submit", onSubmit, true);
3641
+ doc?.removeEventListener("focusin", onFocusIn, true);
3642
+ doc?.removeEventListener("focusout", onFocusOut, true);
3643
+ doc?.removeEventListener("input", onFieldChange, true);
3644
+ doc?.removeEventListener("change", onFieldChange, true);
4160
3645
  win?.removeEventListener(ROUTE_CHANGE_EVENT, onNavigate);
4161
3646
  win?.removeEventListener("popstate", onNavigate);
3647
+ win?.removeEventListener("scroll", onScroll);
4162
3648
  win?.removeEventListener("pagehide", onPageHide);
4163
- doc?.removeEventListener("visibilitychange", onPageHide);
3649
+ doc?.removeEventListener("visibilitychange", onVisibilityChange);
3650
+ win?.removeEventListener("error", onError);
3651
+ win?.removeEventListener("unhandledrejection", onRejection);
3652
+ if (uninstallNetworkWatch) {
3653
+ uninstallNetworkWatch();
3654
+ uninstallNetworkWatch = null;
3655
+ }
3656
+ scroll.dispose();
4164
3657
  if (options2?.discard) transport.discard();
4165
3658
  else void transport.flush(false);
4166
3659
  transport.dispose();
@@ -4176,11 +3669,11 @@ function createTelemetry(options) {
4176
3669
  consent.setConsent(granted);
4177
3670
  if (!granted) transport.discard();
4178
3671
  },
4179
- track(name, payload, fields) {
4180
- capture(name, payload, fields);
3672
+ track(name, payload, fields2) {
3673
+ capture(name, payload, fields2);
4181
3674
  },
4182
3675
  flush: () => transport.flush(false),
4183
- sessionId: () => session.touch(),
3676
+ sessionId: () => session.current(),
4184
3677
  resetSession: () => session.clear(),
4185
3678
  stopped: () => transport.stopped(),
4186
3679
  state: () => consent.state()
@@ -4188,14 +3681,15 @@ function createTelemetry(options) {
4188
3681
  }
4189
3682
 
4190
3683
  // src/version.ts
4191
- var VERSION = "0.1.0";
3684
+ var VERSION = "0.1.1";
4192
3685
 
4193
3686
  // src/api.ts
4194
3687
  var OPTION_KEYS = [
4195
3688
  "telemetry",
4196
3689
  "locale",
4197
3690
  "defaultOpen",
4198
- "contentLocale"
3691
+ "contentLocale",
3692
+ "apiBase"
4199
3693
  ];
4200
3694
  function emptyIdentity() {
4201
3695
  return { userId: null, isAnonymous: false, attributes: {} };
@@ -4217,6 +3711,7 @@ function sanitizeAttributes(attributes, method) {
4217
3711
  }
4218
3712
  const out = {};
4219
3713
  for (const [key, value] of Object.entries(attributes)) {
3714
+ if (value === void 0) continue;
4220
3715
  if (value === null) {
4221
3716
  out[key] = null;
4222
3717
  continue;
@@ -4236,11 +3731,25 @@ function sanitizeAttributes(attributes, method) {
4236
3731
  }
4237
3732
  return out;
4238
3733
  }
4239
- function createArcy() {
4240
- return createArcyInternals().api;
3734
+ var unavailableChunks = {
3735
+ cdnBase: null,
3736
+ loadChat: () => Promise.resolve(null),
3737
+ loadFlow: () => Promise.resolve(null),
3738
+ loadPicker: () => Promise.resolve(null)
3739
+ };
3740
+ function createArcy(chunks) {
3741
+ return createArcyInternals({ chunks }).api;
4241
3742
  }
4242
3743
  function createArcyInternals(internalOptions = {}) {
3744
+ const chunks = internalOptions.chunks ?? unavailableChunks;
4243
3745
  const emitter = createEmitter();
3746
+ function resolveApiBase() {
3747
+ const fromOptions = options.apiBase;
3748
+ if (typeof fromOptions === "string" && fromOptions.length > 0) {
3749
+ return fromOptions.replace(/\/+$/, "");
3750
+ }
3751
+ return internalOptions.apiBase ?? DEFAULT_API_BASE;
3752
+ }
4244
3753
  let token = null;
4245
3754
  let options = {};
4246
3755
  let identity = emptyIdentity();
@@ -4254,6 +3763,8 @@ function createArcyInternals(internalOptions = {}) {
4254
3763
  let bootstrap = null;
4255
3764
  let sendSessionSequence = 0;
4256
3765
  let entryRouteValue;
3766
+ let sessionContextValue;
3767
+ let sessionContextResolved = false;
4257
3768
  let shellState = null;
4258
3769
  let visitState = null;
4259
3770
  let conversationStore = null;
@@ -4292,7 +3803,7 @@ function createArcyInternals(internalOptions = {}) {
4292
3803
  for (const [key, value] of Object.entries(nextOptions)) {
4293
3804
  if (!OPTION_KEYS.includes(key)) {
4294
3805
  warn(
4295
- `init() ignored unknown option "${key}". Only telemetry, locale, defaultOpen, and contentLocale can be set in code; everything else is configured in the dashboard.`
3806
+ `init() ignored unknown option "${key}". Only telemetry, locale, defaultOpen, contentLocale, and apiBase can be set in code; everything else is configured in the dashboard.`
4296
3807
  );
4297
3808
  continue;
4298
3809
  }
@@ -4300,6 +3811,12 @@ function createArcyInternals(internalOptions = {}) {
4300
3811
  }
4301
3812
  }
4302
3813
  }
3814
+ if (typeof window !== "undefined") {
3815
+ reportCspViolations(window, {
3816
+ apiBase: resolveApiBase(),
3817
+ cdnBase: chunks.cdnBase
3818
+ });
3819
+ }
4303
3820
  start(nextToken);
4304
3821
  startDesignMode(nextToken);
4305
3822
  startPreviewMode(nextToken);
@@ -4343,7 +3860,7 @@ function createArcyInternals(internalOptions = {}) {
4343
3860
  // The same seam the bootstrap gets. Without it the collector's batches
4344
3861
  // leave through the real `fetch` even under test, so nothing in this
4345
3862
  // package could ever assert what a captured event actually carries.
4346
- apiBase: internalOptions.apiBase,
3863
+ apiBase: resolveApiBase(),
4347
3864
  fetchImpl: internalOptions.fetchImpl,
4348
3865
  // 15.1 plugs in here. This is what makes a click on the customer's
4349
3866
  // page resolvable to the element the dashboard picked: both sides
@@ -4368,7 +3885,7 @@ function createArcyInternals(internalOptions = {}) {
4368
3885
  try {
4369
3886
  bootstrap = createSessionBootstrap({
4370
3887
  token: activeToken,
4371
- apiBase: internalOptions.apiBase,
3888
+ apiBase: resolveApiBase(),
4372
3889
  fetchImpl: internalOptions.fetchImpl,
4373
3890
  setTimeoutImpl: internalOptions.setTimeoutImpl
4374
3891
  });
@@ -4383,7 +3900,8 @@ function createArcyInternals(internalOptions = {}) {
4383
3900
  void maybeActivateDesignMode({
4384
3901
  win: window,
4385
3902
  token: activeToken,
4386
- apiBase: internalOptions.apiBase ?? DEFAULT_API_BASE,
3903
+ loadChunk: chunks.loadPicker,
3904
+ apiBase: resolveApiBase(),
4387
3905
  fetchImpl: internalOptions.fetchImpl !== void 0 ? internalOptions.fetchImpl : typeof fetch !== "undefined" ? fetch.bind(globalThis) : null,
4388
3906
  setTimeoutImpl: internalOptions.setTimeoutImpl,
4389
3907
  // Discard, not flush (D490, D492): the queue at this moment is the
@@ -4408,7 +3926,7 @@ function createArcyInternals(internalOptions = {}) {
4408
3926
  }
4409
3927
  function verifyPreview(nonce) {
4410
3928
  return verifyPreviewNonce({
4411
- apiBase: internalOptions.apiBase ?? DEFAULT_API_BASE,
3929
+ apiBase: resolveApiBase(),
4412
3930
  token: token ?? "",
4413
3931
  nonce,
4414
3932
  sessionToken: bootstrap?.config()?.sessionToken ?? null,
@@ -4471,16 +3989,12 @@ function createArcyInternals(internalOptions = {}) {
4471
3989
  const chrome = raw?.chrome && typeof raw.chrome === "object" && !Array.isArray(raw.chrome) ? { ...FALLBACK_CHROME, ...raw.chrome } : FALLBACK_CHROME;
4472
3990
  const welcomeIconUrl = raw?.welcomeIconUrl;
4473
3991
  const chatBarIconUrl = raw?.chatBarIconUrl;
3992
+ const apiBase = resolveApiBase();
4474
3993
  return {
4475
3994
  name: identity2.name,
4476
- logoUrl: identity2.logoUrl,
4477
- // Re-checked here rather than trusted, same as privacyPolicyUrl below:
4478
- // a rewritten bootstrap must not be able to point this at a
4479
- // non-`https` URL (ADR 0050).
4480
- welcomeIconUrl: typeof welcomeIconUrl === "string" && /^https:\/\//i.test(welcomeIconUrl) ? welcomeIconUrl : null,
4481
- // Same re-check, same reason: this renders into an `<img src>` on a
4482
- // page ARCY does not control (ADR 0050).
4483
- chatBarIconUrl: typeof chatBarIconUrl === "string" && /^https:\/\//i.test(chatBarIconUrl) ? chatBarIconUrl : null,
3995
+ logoUrl: resolveImageUrl(identity2.logoUrl, apiBase),
3996
+ welcomeIconUrl: resolveImageUrl(welcomeIconUrl, apiBase),
3997
+ chatBarIconUrl: resolveImageUrl(chatBarIconUrl, apiBase),
4484
3998
  welcomeMessage: text(raw?.welcomeMessage),
4485
3999
  inputPlaceholder: text(raw?.inputPlaceholder),
4486
4000
  welcomeHeadline: text(raw?.welcomeHeadline),
@@ -4710,6 +4224,7 @@ function createArcyInternals(internalOptions = {}) {
4710
4224
  chatController = createChatController({
4711
4225
  root,
4712
4226
  host,
4227
+ loadChunk: chunks.loadChat,
4713
4228
  // The panel's collapse control reaches the public close() through
4714
4229
  // here, so isOpen() has exactly one writer. There is no close, only
4715
4230
  // collapse: the bar never leaves.
@@ -4742,6 +4257,10 @@ function createArcyInternals(internalOptions = {}) {
4742
4257
  setDisclaimer: (text) => mountedBar.setDisclaimer(text),
4743
4258
  setAttachEnabled: (enabled) => mountedBar.setAttachEnabled(enabled),
4744
4259
  setAttachmentCount: (count) => mountedBar.setAttachmentCount(count),
4260
+ // Where the panel's image tiles render: inside the composer, above
4261
+ // its input. The tiles stay the panel's (it owns the upload they
4262
+ // stand for); only the place is the bar's.
4263
+ attachmentSlot: () => mountedBar.attachmentSlot(),
4745
4264
  focusInput: () => mountedBar.focusInput(),
4746
4265
  getMetrics: () => mountedBar.getMetrics(),
4747
4266
  // The panel's Settings sheet asked for the widget to become a
@@ -4752,7 +4271,7 @@ function createArcyInternals(internalOptions = {}) {
4752
4271
  // The flows catalog fetch inputs; read live rather than captured,
4753
4272
  // same reasoning as everything else in this options object, since
4754
4273
  // the panel can mount before the bootstrap resolves.
4755
- apiBase: internalOptions.apiBase,
4274
+ apiBase: resolveApiBase(),
4756
4275
  getSessionToken: () => bootstrap?.config()?.sessionToken ?? null,
4757
4276
  getSessionId: () => telemetry?.sessionId(),
4758
4277
  getRoute: () => typeof window !== "undefined" ? window.location.pathname : "",
@@ -4806,7 +4325,7 @@ function createArcyInternals(internalOptions = {}) {
4806
4325
  if (!sessionToken) return null;
4807
4326
  const visits = visitState;
4808
4327
  return {
4809
- apiBase: internalOptions.apiBase ?? DEFAULT_API_BASE,
4328
+ apiBase: resolveApiBase(),
4810
4329
  token,
4811
4330
  sessionToken,
4812
4331
  sessionId: telemetry?.sessionId(),
@@ -4882,7 +4401,7 @@ function createArcyInternals(internalOptions = {}) {
4882
4401
  try {
4883
4402
  const context = buildFlowContext();
4884
4403
  if (!context) return;
4885
- flowController = createFlowController({ context });
4404
+ flowController = createFlowController({ context, loadChunk: chunks.loadFlow });
4886
4405
  } catch (error) {
4887
4406
  warn(`The flow engine could not be prepared. ${String(error)}`);
4888
4407
  }
@@ -4939,6 +4458,22 @@ function createArcyInternals(internalOptions = {}) {
4939
4458
  return void 0;
4940
4459
  }
4941
4460
  }
4461
+ function sessionContext() {
4462
+ if (sessionContextResolved) return sessionContextValue;
4463
+ sessionContextResolved = true;
4464
+ try {
4465
+ if (typeof window === "undefined") return void 0;
4466
+ sessionContextValue = captureSessionContext({
4467
+ referrer: typeof document !== "undefined" ? document.referrer : null,
4468
+ search: window.location?.search ?? null,
4469
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : null,
4470
+ viewportWidth: window.innerWidth
4471
+ });
4472
+ return sessionContextValue;
4473
+ } catch {
4474
+ return void 0;
4475
+ }
4476
+ }
4942
4477
  async function sendSession() {
4943
4478
  if (!bootstrap || !telemetry) return;
4944
4479
  const mySequence = ++sendSessionSequence;
@@ -4951,6 +4486,10 @@ function createArcyInternals(internalOptions = {}) {
4951
4486
  // identify() per route change from making every navigation a new
4952
4487
  // payload, which is the re-send storm D338's skip exists to prevent.
4953
4488
  route: entryRoute(),
4489
+ // Frozen with the entry route and for the same reason. Segmentation
4490
+ // dimensions, not behavior: they land on the session row rather than on
4491
+ // four hundred events that would each repeat them (#790).
4492
+ sessionContext: sessionContext(),
4954
4493
  // Sent whether or not this browser has identified (D517). It is the
4955
4494
  // Lift Subject for anyone who never will, and the anonymous half of the
4956
4495
  // pairing ADR 0055 discovers for everyone who does.
@@ -5004,15 +4543,15 @@ function createArcyInternals(internalOptions = {}) {
5004
4543
  warn("identify() needs a user id. Ignoring the call.");
5005
4544
  return;
5006
4545
  }
5007
- const clean = sanitizeAttributes(attributes, "identify");
5008
- if (clean === null) return;
4546
+ const clean2 = sanitizeAttributes(attributes, "identify");
4547
+ if (clean2 === null) return;
5009
4548
  const hash = readUserHash((options2 ?? {}).userHash, "identify");
5010
4549
  if (!hash.ok) return;
5011
4550
  const sameUser = identity.userId === userId && !identity.isAnonymous;
5012
4551
  identity = {
5013
4552
  userId,
5014
4553
  isAnonymous: false,
5015
- attributes: sameUser ? { ...identity.attributes, ...clean } : clean,
4554
+ attributes: sameUser ? { ...identity.attributes, ...clean2 } : clean2,
5016
4555
  // **The proof carries over for the same id and never across ids.** An
5017
4556
  // SPA re-identifying the same user on every route change passes the hash
5018
4557
  // once at login and nothing afterwards, and dropping it there would
@@ -5026,9 +4565,9 @@ function createArcyInternals(internalOptions = {}) {
5026
4565
  }
5027
4566
  async function identifyAnonymous(attributes) {
5028
4567
  if (!requireInit("identifyAnonymous")) return;
5029
- const clean = sanitizeAttributes(attributes, "identifyAnonymous");
5030
- if (clean === null) return;
5031
- identity = { userId: null, isAnonymous: true, attributes: clean };
4568
+ const clean2 = sanitizeAttributes(attributes, "identifyAnonymous");
4569
+ if (clean2 === null) return;
4570
+ identity = { userId: null, isAnonymous: true, attributes: clean2 };
5032
4571
  await syncSession();
5033
4572
  }
5034
4573
  async function updateUser(attributes) {
@@ -5039,9 +4578,9 @@ function createArcyInternals(internalOptions = {}) {
5039
4578
  );
5040
4579
  return;
5041
4580
  }
5042
- const clean = sanitizeAttributes(attributes, "updateUser");
5043
- if (clean === null) return;
5044
- identity = { ...identity, attributes: { ...identity.attributes, ...clean } };
4581
+ const clean2 = sanitizeAttributes(attributes, "updateUser");
4582
+ if (clean2 === null) return;
4583
+ identity = { ...identity, attributes: { ...identity.attributes, ...clean2 } };
5045
4584
  await syncSession();
5046
4585
  }
5047
4586
  function reset() {
@@ -5098,12 +4637,115 @@ function createArcyInternals(internalOptions = {}) {
5098
4637
  getConfig: () => bootstrap?.config() ?? null,
5099
4638
  selectLocale,
5100
4639
  getShellState: () => shellState,
5101
- getTokens: () => tokens
4640
+ getTokens: () => tokens,
4641
+ getWidgetChrome: () => readWidgetChrome()
5102
4642
  };
5103
4643
  }
5104
4644
 
4645
+ // src/chunks/bundled.ts
4646
+ async function importChunk(win, importer, globalKey, contract, surface, timeoutMs, setTimeoutImpl) {
4647
+ const holder = win;
4648
+ let warned = false;
4649
+ const complain = (message) => {
4650
+ if (warned) return;
4651
+ warned = true;
4652
+ warn(message);
4653
+ };
4654
+ const read = () => {
4655
+ let registration2;
4656
+ try {
4657
+ registration2 = holder[globalKey];
4658
+ } catch {
4659
+ complain(`The ${surface} registration could not be read. It will not start.`);
4660
+ return null;
4661
+ }
4662
+ if (!registration2) return null;
4663
+ if (typeof registration2.mount !== "function") {
4664
+ complain(`The ${surface} registered something unusable. It will not start.`);
4665
+ return null;
4666
+ }
4667
+ let declared;
4668
+ try {
4669
+ declared = registration2.contract;
4670
+ } catch {
4671
+ declared = void 0;
4672
+ }
4673
+ if (declared !== contract) {
4674
+ complain(
4675
+ `The bundled ${surface} speaks contract ${typeof declared === "number" ? declared : "unknown"}, this arcy.js speaks ${contract}. Reinstall arcy.js so both halves come from one version.`
4676
+ );
4677
+ return null;
4678
+ }
4679
+ return registration2;
4680
+ };
4681
+ let present = false;
4682
+ try {
4683
+ present = holder[globalKey] !== void 0;
4684
+ } catch {
4685
+ present = true;
4686
+ }
4687
+ if (present) return read();
4688
+ const arrived = await Promise.race([
4689
+ importer().then(
4690
+ () => "loaded",
4691
+ (error) => {
4692
+ warn(
4693
+ `The ${surface} chunk could not be loaded from your own build. ${String(error)}`
4694
+ );
4695
+ return "failed";
4696
+ }
4697
+ ),
4698
+ new Promise((resolve) => {
4699
+ try {
4700
+ setTimeoutImpl(() => resolve("timeout"), timeoutMs);
4701
+ } catch {
4702
+ }
4703
+ })
4704
+ ]);
4705
+ if (arrived === "failed") return null;
4706
+ if (arrived === "timeout") {
4707
+ warn(`The ${surface} did not load in time.`);
4708
+ return null;
4709
+ }
4710
+ const registration = read();
4711
+ if (!registration && !warned) {
4712
+ complain(`The ${surface} loaded but did not register. It will not start.`);
4713
+ }
4714
+ return registration;
4715
+ }
4716
+ var bundledChunks = {
4717
+ cdnBase: null,
4718
+ loadChat: (win, timeoutMs, setTimeoutImpl) => importChunk(
4719
+ win,
4720
+ () => import('./chat-IU4IJMGO.js'),
4721
+ CHAT_GLOBAL,
4722
+ CHAT_CONTRACT,
4723
+ "chat panel",
4724
+ timeoutMs,
4725
+ setTimeoutImpl
4726
+ ),
4727
+ loadFlow: (win, timeoutMs, setTimeoutImpl) => importChunk(
4728
+ win,
4729
+ () => import('./flow-M7W3H3C5.js'),
4730
+ FLOW_GLOBAL,
4731
+ FLOW_CONTRACT,
4732
+ "flow engine",
4733
+ timeoutMs,
4734
+ setTimeoutImpl
4735
+ ),
4736
+ loadPicker: (win, timeoutMs, setTimeoutImpl) => importChunk(
4737
+ win,
4738
+ () => import('./picker-JPESE6JJ.js'),
4739
+ PICKER_GLOBAL,
4740
+ PICKER_CONTRACT,
4741
+ "design-mode picker",
4742
+ timeoutMs,
4743
+ setTimeoutImpl
4744
+ )
4745
+ };
4746
+
5105
4747
  // src/index.ts
5106
- var arcy = createArcy();
4748
+ var arcy = createArcy(bundledChunks);
5107
4749
  var src_default = arcy;
5108
4750
 
5109
4751
  export { VERSION, src_default as default };