@customerhero/js 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -29,8 +29,11 @@ __export(index_exports, {
29
29
  captureScreenshot: () => captureScreenshot,
30
30
  createTranslator: () => createTranslator,
31
31
  detectLocale: () => detectLocale,
32
+ evaluate: () => evaluate,
32
33
  isRtlLocale: () => isRtlLocale,
33
- resolveLocale: () => resolveLocale
34
+ pickFire: () => pickFire,
35
+ resolveLocale: () => resolveLocale,
36
+ startTriggersRuntime: () => startTriggersRuntime
34
37
  });
35
38
  module.exports = __toCommonJS(index_exports);
36
39
 
@@ -549,6 +552,377 @@ ${line.slice(5).trim()}` : line.slice(5).trim();
549
552
  return { event, data };
550
553
  }
551
554
 
555
+ // src/triggers.ts
556
+ function evaluate(node, ctx) {
557
+ if (isGroup(node, "all")) {
558
+ if (node.all.length === 0) return false;
559
+ return node.all.every((child) => evaluate(child, ctx));
560
+ }
561
+ if (isGroup(node, "any")) {
562
+ if (node.any.length === 0) return false;
563
+ return node.any.some((child) => evaluate(child, ctx));
564
+ }
565
+ return evaluateLeaf(node, ctx);
566
+ }
567
+ function isGroup(node, key) {
568
+ return !!node && typeof node === "object" && Array.isArray(node[key]);
569
+ }
570
+ function evaluateLeaf(leaf, ctx) {
571
+ switch (leaf.kind) {
572
+ case "url_path": {
573
+ const path = parseUrlPath(ctx.url);
574
+ switch (leaf.op) {
575
+ case "equals":
576
+ return path === leaf.value;
577
+ case "contains":
578
+ return path.includes(leaf.value);
579
+ case "starts_with":
580
+ return path.startsWith(leaf.value);
581
+ case "regex":
582
+ return safeRegex(leaf.value).test(path);
583
+ default:
584
+ return false;
585
+ }
586
+ }
587
+ case "url_query": {
588
+ const has = Object.prototype.hasOwnProperty.call(
589
+ ctx.queryParams,
590
+ leaf.key
591
+ );
592
+ if (leaf.op === "exists") return has;
593
+ if (!has) return false;
594
+ const v = ctx.queryParams[leaf.key] ?? "";
595
+ if (leaf.op === "equals") return v === (leaf.value ?? "");
596
+ if (leaf.op === "contains") return v.includes(leaf.value ?? "");
597
+ return false;
598
+ }
599
+ case "referrer": {
600
+ const r = ctx.referrer;
601
+ if (leaf.op === "equals") return r === leaf.value;
602
+ if (leaf.op === "contains") return r.includes(leaf.value);
603
+ if (leaf.op === "regex") return safeRegex(leaf.value).test(r);
604
+ return false;
605
+ }
606
+ case "time_on_page":
607
+ return ctx.timeOnPageMs >= leaf.seconds * 1e3;
608
+ case "scroll_depth":
609
+ return ctx.scrollPercent >= leaf.percent;
610
+ case "exit_intent":
611
+ return ctx.exitIntentSeen;
612
+ case "device":
613
+ return ctx.device === leaf.value;
614
+ case "browser_language": {
615
+ const lang = (ctx.language || "").toLowerCase();
616
+ return leaf.values.some((v) => {
617
+ const target = v.toLowerCase();
618
+ return lang === target || lang.startsWith(`${target}-`);
619
+ });
620
+ }
621
+ case "visitor_trait": {
622
+ const has = Object.prototype.hasOwnProperty.call(ctx.traits, leaf.key);
623
+ if (leaf.op === "exists") return has;
624
+ if (!has) return false;
625
+ const v = ctx.traits[leaf.key];
626
+ if (leaf.op === "equals") return v === leaf.value;
627
+ if (leaf.op === "gt")
628
+ return typeof v === "number" && typeof leaf.value === "number" && v > leaf.value;
629
+ if (leaf.op === "lt")
630
+ return typeof v === "number" && typeof leaf.value === "number" && v < leaf.value;
631
+ return false;
632
+ }
633
+ case "return_visit": {
634
+ if (leaf.op === "gte") return ctx.returnVisitCount >= leaf.count;
635
+ if (leaf.op === "eq") return ctx.returnVisitCount === leaf.count;
636
+ return false;
637
+ }
638
+ default:
639
+ return false;
640
+ }
641
+ }
642
+ function parseUrlPath(url) {
643
+ try {
644
+ return new URL(url).pathname;
645
+ } catch {
646
+ return url.split("?")[0] ?? url;
647
+ }
648
+ }
649
+ function safeRegex(pattern) {
650
+ try {
651
+ return new RegExp(pattern);
652
+ } catch {
653
+ return /^\b$/;
654
+ }
655
+ }
656
+ function pickFire(triggers, ctx, firedSet) {
657
+ const sorted = triggers.slice().sort((a, b) => a.priority - b.priority);
658
+ for (const t of sorted) {
659
+ if (t.frequency !== "every_time" && firedSet.has(t.id)) continue;
660
+ if (evaluate(t.conditions, ctx)) return t;
661
+ }
662
+ return null;
663
+ }
664
+
665
+ // src/triggers-runtime.ts
666
+ var STORAGE_NS = "ch_trigger";
667
+ var RETURN_VISIT_KEY = "ch_visits";
668
+ var SCROLL_THROTTLE_MS = 250;
669
+ var EXIT_INTENT_GRACE_MS = 5e3;
670
+ function getLocalStorage() {
671
+ try {
672
+ return typeof window !== "undefined" ? window.localStorage : null;
673
+ } catch {
674
+ return null;
675
+ }
676
+ }
677
+ function getSessionStorage() {
678
+ try {
679
+ return typeof window !== "undefined" ? window.sessionStorage : null;
680
+ } catch {
681
+ return null;
682
+ }
683
+ }
684
+ function detectDevice() {
685
+ if (typeof window === "undefined") return "desktop";
686
+ const ua = navigator.userAgent || "";
687
+ if (/iPad|Tablet|PlayBook/i.test(ua)) return "tablet";
688
+ if (/Mobi|Android|iPhone|iPod/i.test(ua)) return "mobile";
689
+ if (window.matchMedia?.("(pointer: coarse)").matches && window.innerWidth < 1024) {
690
+ return window.innerWidth < 600 ? "mobile" : "tablet";
691
+ }
692
+ return "desktop";
693
+ }
694
+ function parseQueryParams(url) {
695
+ const out = {};
696
+ try {
697
+ const u = new URL(url);
698
+ u.searchParams.forEach((v, k) => {
699
+ out[k] = v;
700
+ });
701
+ } catch {
702
+ }
703
+ return out;
704
+ }
705
+ function readReturnVisitCount() {
706
+ const ls = getLocalStorage();
707
+ if (!ls) return 1;
708
+ try {
709
+ const raw = ls.getItem(RETURN_VISIT_KEY);
710
+ return raw ? Math.max(1, parseInt(raw, 10) || 1) : 1;
711
+ } catch {
712
+ return 1;
713
+ }
714
+ }
715
+ function bumpReturnVisitCount() {
716
+ const ls = getLocalStorage();
717
+ const ss = getSessionStorage();
718
+ if (!ls || !ss) return readReturnVisitCount();
719
+ try {
720
+ if (ss.getItem("ch_visit_seen") === "1") return readReturnVisitCount();
721
+ const current = parseInt(ls.getItem(RETURN_VISIT_KEY) ?? "0", 10) || 0;
722
+ const next = current + 1;
723
+ ls.setItem(RETURN_VISIT_KEY, String(next));
724
+ ss.setItem("ch_visit_seen", "1");
725
+ return next;
726
+ } catch {
727
+ return readReturnVisitCount();
728
+ }
729
+ }
730
+ function firedKey(chatbotId, triggerId) {
731
+ return `${STORAGE_NS}_${chatbotId}_${triggerId}_fired`;
732
+ }
733
+ function readFired(chatbotId, triggers) {
734
+ const ls = getLocalStorage();
735
+ const ss = getSessionStorage();
736
+ const out = /* @__PURE__ */ new Set();
737
+ for (const t of triggers) {
738
+ if (t.frequency === "every_time") continue;
739
+ try {
740
+ const key = firedKey(chatbotId, t.id);
741
+ if (t.frequency === "once_ever" && ls?.getItem(key) === "1") {
742
+ out.add(t.id);
743
+ } else if (t.frequency === "once_per_session" && ss?.getItem(key) === "1") {
744
+ out.add(t.id);
745
+ }
746
+ } catch {
747
+ }
748
+ }
749
+ return out;
750
+ }
751
+ function writeFired(chatbotId, triggerId, frequency) {
752
+ if (frequency === "every_time") return;
753
+ const key = firedKey(chatbotId, triggerId);
754
+ try {
755
+ if (frequency === "once_ever") {
756
+ getLocalStorage()?.setItem(key, "1");
757
+ } else if (frequency === "once_per_session") {
758
+ getSessionStorage()?.setItem(key, "1");
759
+ }
760
+ } catch {
761
+ }
762
+ }
763
+ function startTriggersRuntime(options) {
764
+ if (typeof window === "undefined") {
765
+ return {
766
+ stop() {
767
+ },
768
+ reevaluate() {
769
+ },
770
+ setTraits() {
771
+ },
772
+ markFired() {
773
+ }
774
+ };
775
+ }
776
+ const { chatbotId, triggers, onFire, isAllowedToFire } = options;
777
+ let stopped = false;
778
+ let exitIntentSeen = false;
779
+ let scrollPercent = 0;
780
+ let pageStartedAt = Date.now();
781
+ let timeOnPageMs = 0;
782
+ let lastVisibleAt = Date.now();
783
+ let traits = {
784
+ ...options.initialTraits ?? {}
785
+ };
786
+ const firedThisSession = /* @__PURE__ */ new Set();
787
+ for (const id of readFired(chatbotId, triggers)) firedThisSession.add(id);
788
+ const returnVisitCount = bumpReturnVisitCount();
789
+ function snapshot() {
790
+ const url = window.location.href;
791
+ return {
792
+ url,
793
+ queryParams: parseQueryParams(url),
794
+ referrer: document.referrer || "",
795
+ language: (navigator.language || "en").toLowerCase(),
796
+ device: detectDevice(),
797
+ timeOnPageMs,
798
+ scrollPercent,
799
+ exitIntentSeen,
800
+ returnVisitCount,
801
+ traits
802
+ };
803
+ }
804
+ function evaluate2() {
805
+ if (stopped) return;
806
+ if (!isAllowedToFire()) return;
807
+ if (triggers.length === 0) return;
808
+ const ctx = snapshot();
809
+ const trigger = pickFire(triggers, ctx, firedThisSession);
810
+ if (!trigger) return;
811
+ firedThisSession.add(trigger.id);
812
+ writeFired(chatbotId, trigger.id, trigger.frequency);
813
+ try {
814
+ onFire(trigger);
815
+ } catch (err) {
816
+ console.error("CustomerHero: trigger onFire handler threw", err);
817
+ }
818
+ }
819
+ const timerId = window.setInterval(() => {
820
+ if (document.hidden) return;
821
+ const now = Date.now();
822
+ timeOnPageMs += now - lastVisibleAt;
823
+ lastVisibleAt = now;
824
+ evaluate2();
825
+ }, 1e3);
826
+ function handleVisibility() {
827
+ const now = Date.now();
828
+ if (document.hidden) {
829
+ timeOnPageMs += now - lastVisibleAt;
830
+ if (now - pageStartedAt > EXIT_INTENT_GRACE_MS && !exitIntentSeen) {
831
+ exitIntentSeen = true;
832
+ evaluate2();
833
+ }
834
+ } else {
835
+ lastVisibleAt = now;
836
+ evaluate2();
837
+ }
838
+ }
839
+ document.addEventListener("visibilitychange", handleVisibility);
840
+ let scrollScheduled = false;
841
+ function handleScroll() {
842
+ if (scrollScheduled) return;
843
+ scrollScheduled = true;
844
+ window.setTimeout(() => {
845
+ scrollScheduled = false;
846
+ const doc = document.documentElement;
847
+ const scrollable = doc.scrollHeight - window.innerHeight;
848
+ if (scrollable <= 0) {
849
+ scrollPercent = Math.max(scrollPercent, 100);
850
+ } else {
851
+ const next = window.scrollY / scrollable * 100;
852
+ if (next > scrollPercent) {
853
+ scrollPercent = next;
854
+ evaluate2();
855
+ }
856
+ }
857
+ }, SCROLL_THROTTLE_MS);
858
+ }
859
+ window.addEventListener("scroll", handleScroll, { passive: true });
860
+ function handleMouseOut(e) {
861
+ if (exitIntentSeen) return;
862
+ if (e.relatedTarget !== null) return;
863
+ if (e.clientY > 0) return;
864
+ if (Date.now() - pageStartedAt < EXIT_INTENT_GRACE_MS) return;
865
+ exitIntentSeen = true;
866
+ evaluate2();
867
+ }
868
+ document.addEventListener("mouseout", handleMouseOut);
869
+ const origPush = history.pushState;
870
+ const origReplace = history.replaceState;
871
+ function onUrlChange() {
872
+ pageStartedAt = Date.now();
873
+ timeOnPageMs = 0;
874
+ lastVisibleAt = Date.now();
875
+ scrollPercent = 0;
876
+ exitIntentSeen = false;
877
+ evaluate2();
878
+ }
879
+ history.pushState = function patched(...args) {
880
+ const ret = origPush.apply(this, args);
881
+ try {
882
+ onUrlChange();
883
+ } catch {
884
+ }
885
+ return ret;
886
+ };
887
+ history.replaceState = function patched(...args) {
888
+ const ret = origReplace.apply(
889
+ this,
890
+ args
891
+ );
892
+ try {
893
+ onUrlChange();
894
+ } catch {
895
+ }
896
+ return ret;
897
+ };
898
+ window.addEventListener("popstate", onUrlChange);
899
+ const initialEval = window.setTimeout(evaluate2, 0);
900
+ return {
901
+ stop() {
902
+ stopped = true;
903
+ window.clearInterval(timerId);
904
+ window.clearTimeout(initialEval);
905
+ document.removeEventListener("visibilitychange", handleVisibility);
906
+ window.removeEventListener("scroll", handleScroll);
907
+ document.removeEventListener("mouseout", handleMouseOut);
908
+ window.removeEventListener("popstate", onUrlChange);
909
+ history.pushState = origPush;
910
+ history.replaceState = origReplace;
911
+ },
912
+ reevaluate() {
913
+ evaluate2();
914
+ },
915
+ setTraits(next) {
916
+ traits = { ...traits, ...next };
917
+ evaluate2();
918
+ },
919
+ markFired(triggerId, frequency) {
920
+ firedThisSession.add(triggerId);
921
+ writeFired(chatbotId, triggerId, frequency);
922
+ }
923
+ };
924
+ }
925
+
552
926
  // src/client.ts
553
927
  function resolveConfig(userConfig, fetched) {
554
928
  return {
@@ -603,9 +977,35 @@ var CustomerHeroChat = class {
603
977
  error: null,
604
978
  identity: null,
605
979
  locale,
606
- isRtl: isRtlLocale(locale)
980
+ isRtl: isRtlLocale(locale),
981
+ triggers: [],
982
+ preChatForm: null,
983
+ preChatFormVisible: false,
984
+ preChatSubmission: null,
985
+ consent: this.readStoredConsent(),
986
+ pendingTriggerId: null,
987
+ pendingPrefill: null
607
988
  };
608
989
  }
990
+ // ── Proactive engagement state ─────────────────────────────────────
991
+ triggersRuntime = null;
992
+ preChatFormSubmitted = false;
993
+ readStoredConsent() {
994
+ try {
995
+ const raw = this.storage?.getItem("ch_consent");
996
+ if (!raw) return { analytics: false };
997
+ const parsed = JSON.parse(raw);
998
+ return { analytics: parsed.analytics === true };
999
+ } catch {
1000
+ return { analytics: false };
1001
+ }
1002
+ }
1003
+ writeStoredConsent(consent) {
1004
+ try {
1005
+ this.storage?.setItem("ch_consent", JSON.stringify(consent));
1006
+ } catch {
1007
+ }
1008
+ }
609
1009
  // Switch the active locale at runtime. No-op when the resolved tag matches
610
1010
  // the current locale and `stringOverrides` is unchanged. Subscribers get a
611
1011
  // single state notification with the new `locale` / `isRtl`.
@@ -661,8 +1061,16 @@ var CustomerHeroChat = class {
661
1061
  }
662
1062
  const fetched = await response.json();
663
1063
  const resolved = resolveConfig(this.userConfig, fetched);
664
- this.setState({ config: resolved, configLoaded: true });
1064
+ const triggers = Array.isArray(fetched.triggers) ? fetched.triggers : [];
1065
+ const preChatForm = fetched.preChatForm ?? null;
1066
+ this.setState({
1067
+ config: resolved,
1068
+ configLoaded: true,
1069
+ triggers,
1070
+ preChatForm
1071
+ });
665
1072
  if (resolved.stringOverrides) this.rebuildTranslator();
1073
+ this.startTriggersRuntimeIfPossible();
666
1074
  } catch (error) {
667
1075
  const errorMsg = error instanceof Error ? error.message : "Failed to load widget config";
668
1076
  console.error("CustomerHero: Failed to fetch widget config", error);
@@ -720,6 +1128,14 @@ var CustomerHeroChat = class {
720
1128
  const trimmed = message.trim();
721
1129
  const attachmentTokens = options?.attachmentTokens ?? [];
722
1130
  if (!trimmed || this.state.isLoading) return;
1131
+ if (this.shouldShowPreChatForm()) {
1132
+ this.pendingMessageAfterPreChat = {
1133
+ message: trimmed,
1134
+ attachmentTokens
1135
+ };
1136
+ this.setState({ preChatFormVisible: true });
1137
+ return;
1138
+ }
723
1139
  const userMsg = {
724
1140
  role: "user",
725
1141
  content: trimmed,
@@ -752,7 +1168,13 @@ var CustomerHeroChat = class {
752
1168
  message: trimmed,
753
1169
  ...this.state.conversationId ? { conversationId: this.state.conversationId } : {},
754
1170
  ...this.identityData ? { identity: this.identityData } : {},
755
- ...attachmentTokens.length > 0 ? { attachmentTokens } : {}
1171
+ ...attachmentTokens.length > 0 ? { attachmentTokens } : {},
1172
+ // Trigger attribution + pre-chat submission only land on the very
1173
+ // first turn. We only consume them when there's no conversationId
1174
+ // yet — the server ignores them on subsequent turns anyway, but
1175
+ // sending them again would be misleading.
1176
+ ...!this.state.conversationId && this.state.pendingTriggerId ? { triggeredByTriggerId: this.state.pendingTriggerId } : {},
1177
+ ...!this.state.conversationId && this.state.preChatSubmission ? { prechatSubmission: this.state.preChatSubmission } : {}
756
1178
  })
757
1179
  });
758
1180
  if (!response.ok) {
@@ -774,7 +1196,11 @@ var CustomerHeroChat = class {
774
1196
  `ch_conv_${chatbotId}`,
775
1197
  meta.conversationId
776
1198
  );
777
- this.setState({ conversationId: meta.conversationId });
1199
+ this.setState({
1200
+ conversationId: meta.conversationId,
1201
+ pendingTriggerId: null,
1202
+ preChatSubmission: null
1203
+ });
778
1204
  }
779
1205
  this.patchMessageAt(userMsgIndex, { status: "sent" });
780
1206
  if (meta?.messageId) {
@@ -1047,6 +1473,119 @@ var CustomerHeroChat = class {
1047
1473
  error: null
1048
1474
  });
1049
1475
  }
1476
+ // ── Proactive engagement public API ────────────────────────────────
1477
+ /** Update visitor consent. Until `analytics: true` is set, only direct
1478
+ * launcher clicks fire — URL/time/scroll/exit-intent/trait conditions
1479
+ * stay dormant. The setting is persisted in localStorage so revisits
1480
+ * don't re-prompt. */
1481
+ setConsent(consent) {
1482
+ const next = {
1483
+ analytics: typeof consent.analytics === "boolean" ? consent.analytics : this.state.consent.analytics
1484
+ };
1485
+ this.writeStoredConsent(next);
1486
+ this.setState({ consent: next });
1487
+ this.triggersRuntime?.reevaluate();
1488
+ }
1489
+ /** Set or update visitor traits used by trait-based conditions. The trait
1490
+ * values are kept in memory (not persisted) so the integrator decides
1491
+ * the source of truth. */
1492
+ setTraits(traits) {
1493
+ this.triggersRuntime?.setTraits(traits);
1494
+ }
1495
+ /** Submit pre-chat form answers. Synthesizes a customer record server-side
1496
+ * on the next sendMessage. Resumes any pending message that was deferred
1497
+ * while the form was open. */
1498
+ async submitPreChatForm(submission) {
1499
+ this.preChatFormSubmitted = true;
1500
+ this.setState({
1501
+ preChatSubmission: submission,
1502
+ preChatFormVisible: false
1503
+ });
1504
+ const pending = this.pendingMessageAfterPreChat;
1505
+ this.pendingMessageAfterPreChat = null;
1506
+ if (pending) {
1507
+ await this.sendMessage(pending.message, {
1508
+ attachmentTokens: pending.attachmentTokens
1509
+ });
1510
+ }
1511
+ }
1512
+ /** Dismiss the pre-chat form without submitting. The form will reappear
1513
+ * on the next sendMessage attempt — call `setConsent` to acknowledge a
1514
+ * refusal, or `reset()` to clear pending state. */
1515
+ cancelPreChatForm() {
1516
+ this.pendingMessageAfterPreChat = null;
1517
+ this.setState({ preChatFormVisible: false });
1518
+ }
1519
+ /** Programmatically dispatch the action attached to a trigger. Used by
1520
+ * integrators who want to act on a custom button, e.g. an exit-intent
1521
+ * modal in their own UI. */
1522
+ fireTrigger(triggerId) {
1523
+ const trigger = this.state.triggers.find((t) => t.id === triggerId);
1524
+ if (!trigger) return;
1525
+ this.triggersRuntime?.markFired(trigger.id, trigger.frequency);
1526
+ this.handleTriggerAction(trigger);
1527
+ }
1528
+ // ── Internals ──────────────────────────────────────────────────────
1529
+ pendingMessageAfterPreChat = null;
1530
+ shouldShowPreChatForm() {
1531
+ const form = this.state.preChatForm;
1532
+ if (!form) return false;
1533
+ if (this.preChatFormSubmitted) return false;
1534
+ if (this.state.conversationId) return false;
1535
+ if (form.skipForIdentified && this.identityData?.userId) return false;
1536
+ return true;
1537
+ }
1538
+ startTriggersRuntimeIfPossible() {
1539
+ if (this.triggersRuntime) return;
1540
+ if (this.state.triggers.length === 0) return;
1541
+ this.triggersRuntime = startTriggersRuntime({
1542
+ chatbotId: this.state.config.chatbotId,
1543
+ triggers: this.state.triggers,
1544
+ isAllowedToFire: () => this.state.consent.analytics,
1545
+ onFire: (trigger) => this.handleTriggerAction(trigger)
1546
+ });
1547
+ }
1548
+ handleTriggerAction(trigger) {
1549
+ this.setState({ pendingTriggerId: trigger.id });
1550
+ const action = trigger.action;
1551
+ switch (action.kind) {
1552
+ case "open_widget":
1553
+ this.open();
1554
+ break;
1555
+ case "open_with_prefill":
1556
+ this.open();
1557
+ this.setState({ pendingPrefill: action.prefill });
1558
+ break;
1559
+ case "show_form":
1560
+ this.open();
1561
+ if (this.state.preChatForm) {
1562
+ this.setState({ preChatFormVisible: true });
1563
+ }
1564
+ break;
1565
+ case "send_message":
1566
+ this.open();
1567
+ if (this.state.messages.length === 0) {
1568
+ this.setState({
1569
+ messages: [{ role: "bot", content: action.message }]
1570
+ });
1571
+ }
1572
+ break;
1573
+ }
1574
+ }
1575
+ /** Read and clear the pending prefill (set by an `open_with_prefill`
1576
+ * trigger). The host calls this once when mounting the input and seeds
1577
+ * its controlled value with the result. */
1578
+ consumePendingPrefill() {
1579
+ const prefill = this.state.pendingPrefill;
1580
+ if (prefill !== null) this.setState({ pendingPrefill: null });
1581
+ return prefill;
1582
+ }
1583
+ /** Stop the triggers runtime and detach listeners. Safe to call multiple
1584
+ * times; safe to call before the runtime started. */
1585
+ destroy() {
1586
+ this.triggersRuntime?.stop();
1587
+ this.triggersRuntime = null;
1588
+ }
1050
1589
  identify(payload) {
1051
1590
  const { userId, email, name, phone, company, userHash, ...rest } = payload;
1052
1591
  const customProperties = {};
@@ -1089,9 +1628,7 @@ function stripSuggestions(message) {
1089
1628
  }
1090
1629
  function stripActionConfirmationBlocks(message) {
1091
1630
  if (!message.blocks?.length) return message;
1092
- const blocks = message.blocks.filter(
1093
- (b) => b.type !== "action_confirmation"
1094
- );
1631
+ const blocks = message.blocks.filter((b) => b.type !== "action_confirmation");
1095
1632
  if (blocks.length === message.blocks.length) return message;
1096
1633
  if (blocks.length === 0) {
1097
1634
  const { blocks: _b, ...rest } = message;
@@ -1255,6 +1792,9 @@ async function canvasToBlob(canvas, quality) {
1255
1792
  captureScreenshot,
1256
1793
  createTranslator,
1257
1794
  detectLocale,
1795
+ evaluate,
1258
1796
  isRtlLocale,
1259
- resolveLocale
1797
+ pickFire,
1798
+ resolveLocale,
1799
+ startTriggersRuntime
1260
1800
  });