@sailfish-ai/recorder 1.7.50 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/env.js ADDED
@@ -0,0 +1,55 @@
1
+ // Avoid referencing `import.meta` in distributed code.
2
+ // Priority: explicit global → process.env → default false.
3
+ export function readDebugFlag() {
4
+ // 1) Global (works in any bundler and vanilla <script> usage)
5
+ try {
6
+ const g = globalThis;
7
+ if (typeof g.__SAILFISH_DEBUG__ === "boolean")
8
+ return g.__SAILFISH_DEBUG__;
9
+ }
10
+ catch { }
11
+ // 2) process.env (webpack/rollup/esbuild all replace at build-time)
12
+ try {
13
+ const p = typeof process !== "undefined" ? process : undefined;
14
+ if (p?.env?.SAILFISH_DEBUG != null) {
15
+ const v = String(p.env.SAILFISH_DEBUG).toLowerCase();
16
+ return v === "1" || v === "true" || v === "yes";
17
+ }
18
+ }
19
+ catch { }
20
+ // 3) Build-time define fallback (we'll inject __SAILFISH_DEBUG__ in vite.config)
21
+ try {
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ const D = globalThis.__SAILFISH_DEBUG_DEFINE__;
24
+ if (typeof D === "boolean")
25
+ return D;
26
+ }
27
+ catch { }
28
+ return false;
29
+ }
30
+ // Build-time injected Git SHA (without using `import.meta`).
31
+ export function readGitSha() {
32
+ // 1) global
33
+ try {
34
+ const g = globalThis;
35
+ if (typeof g.__GIT_SHA__ === "string" && g.__GIT_SHA__)
36
+ return g.__GIT_SHA__;
37
+ }
38
+ catch { }
39
+ // 2) process.env
40
+ try {
41
+ const p = typeof process !== "undefined" ? process : undefined;
42
+ if (p?.env?.GIT_SHA)
43
+ return String(p.env.GIT_SHA);
44
+ }
45
+ catch { }
46
+ // 3) build-time define fallback (see vite.config define)
47
+ try {
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ const S = globalThis.__GIT_SHA_DEFINE__;
50
+ if (typeof S === "string" && S)
51
+ return S;
52
+ }
53
+ catch { }
54
+ return undefined;
55
+ }
@@ -8,7 +8,9 @@ import { sendMessage } from "./websocket";
8
8
  export async function resolveStackTrace(stackTrace) {
9
9
  if (!stackTrace)
10
10
  return ["No stack trace available"];
11
- const traceLines = Array.isArray(stackTrace) ? stackTrace : stackTrace.split("\n");
11
+ const traceLines = Array.isArray(stackTrace)
12
+ ? stackTrace
13
+ : stackTrace.split("\n");
12
14
  const mappedStack = [];
13
15
  for (const line of traceLines) {
14
16
  const match = line.match(/\/assets\/([^\/]+\.js):(\d+):(\d+)/);
@@ -24,7 +26,7 @@ export async function resolveStackTrace(stackTrace) {
24
26
  if (!response.ok) {
25
27
  break;
26
28
  }
27
- const rawSourceMap = await response.json();
29
+ const rawSourceMap = (await response.json());
28
30
  if (!rawSourceMap.sources || !Array.isArray(rawSourceMap.sources)) {
29
31
  mappedStack.push(line);
30
32
  continue;
@@ -50,7 +52,9 @@ export async function resolveStackTrace(stackTrace) {
50
52
  contextSnippet = lines.slice(start, end).map((line, i) => {
51
53
  const lineNumber = start + i + 1;
52
54
  const prefix = lineNumber === errorLine ? "👉" : " ";
53
- return `${prefix} ${lineNumber.toString().padStart(4)} | ${line.trim()}`;
55
+ return `${prefix} ${lineNumber
56
+ .toString()
57
+ .padStart(4)} | ${line.trim()}`;
54
58
  });
55
59
  }
56
60
  mappedStack.push(`${original.source}:${original.line}:${original.column} (${original.name || "anonymous"})`);
@@ -96,8 +100,13 @@ async function captureError(error, isPromiseRejection = false) {
96
100
  };
97
101
  // Sends the mapped error details to the backend.
98
102
  sendMessage({
99
- type: "frontendError",
100
- data: errorDetails,
103
+ type: "event",
104
+ event: {
105
+ type: 6,
106
+ data: {
107
+ payload: errorDetails,
108
+ },
109
+ },
101
110
  });
102
111
  }
103
112
  /**
@@ -1,5 +1,5 @@
1
- const DEBUG = import.meta.env.VITE_DEBUG ? import.meta.env.VITE_DEBUG : false;
2
- // A wrapper around fetch that suppresses connection refused errors
1
+ import { readDebugFlag } from "./env";
2
+ const DEBUG = readDebugFlag(); // A wrapper around fetch that suppresses connection refused errors
3
3
  export function silentFetch(input, init) {
4
4
  return new Promise((resolve, reject) => {
5
5
  fetch(input, init)
package/dist/graphql.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { exponentialBackoff } from "./exponentialBackoff"; // Import your custom backoff function
2
- const DEBUG = import.meta.env.VITE_DEBUG ? import.meta.env.VITE_DEBUG : false;
2
+ import { readDebugFlag } from "./env";
3
+ const DEBUG = readDebugFlag(); // A wrapper around fetch that suppresses connection refused errors
3
4
  export function sendGraphQLRequest(operationName, query, variables, retries = 5, // Number of retries before giving up
4
5
  initialBackoff = 2000, // Initial backoff in milliseconds
5
6
  backoffFactor = 2) {
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
- const DEBUG = import.meta.env.VITE_DEBUG ? import.meta.env.VITE_DEBUG : false;
1
+ import { readDebugFlag } from "./env";
2
2
  // import { NetworkRecordOptions } from "@sailfish-rrweb/rrweb-plugin-network-record";
3
3
  import { v4 as uuidv4 } from "uuid";
4
4
  import { NetworkRequestEventId, STATIC_EXTENSIONS, xSf3RidHeader, } from "./constants";
5
5
  import { gatherAndCacheDeviceInfo } from "./deviceInfo";
6
+ import { readGitSha } from "./env";
7
+ import { initializeErrorInterceptor } from "./errorInterceptor";
6
8
  import { fetchCaptureSettings, sendDomainsToNotPropagateHeaderTo, startRecordingSession, } from "./graphql";
7
9
  import { setupIssueReporting } from "./inAppReportIssueModal";
8
10
  import { sendMapUuidIfAvailable } from "./mapUuid";
@@ -11,6 +13,7 @@ import { HAS_DOCUMENT, HAS_LOCAL_STORAGE, HAS_SESSION_STORAGE, HAS_WINDOW, } fro
11
13
  import { getOrSetSessionId } from "./session";
12
14
  import { withAppUrlMetadata } from "./utils";
13
15
  import { sendEvent, sendMessage } from "./websocket";
16
+ const DEBUG = readDebugFlag(); // A wrapper around fetch that suppresses connection refused errors
14
17
  // Default list of domains to ignore
15
18
  const DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT = [
16
19
  "t.co",
@@ -79,7 +82,12 @@ export const DEFAULT_CONSOLE_RECORDING_SETTINGS = {
79
82
  // recordBody: true,
80
83
  // recordInitialRequests: false,
81
84
  // };
82
- function trackDomainChanges() {
85
+ function trackDomainChangesOnce() {
86
+ const g = (window.__sailfish_recorder ||= {});
87
+ if (g.routeWatcherIntervalId) {
88
+ // already running
89
+ return;
90
+ }
83
91
  let lastDomain = window.location.href.split("?")[0];
84
92
  const checkDomainChange = (forceSend = false) => {
85
93
  const currentDomain = window.location.href.split("?")[0];
@@ -104,8 +112,8 @@ function trackDomainChanges() {
104
112
  const debouncedCheck = debounce(() => checkDomainChange(), 500);
105
113
  // Force the first check to send the initial URL on load
106
114
  checkDomainChange(true);
107
- // Set interval to periodically check if the domain or path has changed
108
- setInterval(debouncedCheck, 1000); // Adjust the interval as needed
115
+ // Start a single interval and remember it
116
+ g.routeWatcherIntervalId = window.setInterval(debouncedCheck, 1000);
109
117
  }
110
118
  // Debounce utility function
111
119
  function debounce(func, wait) {
@@ -558,37 +566,6 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo = []) {
558
566
  }
559
567
  }
560
568
  }
561
- // Note - if you update this, you must copy this function to the documentation in docs/docs/getting-started/updating-autoinstall-prs.md.
562
- function getCompileTimeGitSha() {
563
- // 1) Preferred: a hard-coded global replaced at build time (all bundlers support "define")
564
- // e.g. webpack DefinePlugin/esbuild/rollup/vite: __GIT_SHA__ => JSON.stringify('<sha>')
565
- try {
566
- const g = globalThis;
567
- if (g && typeof g.__GIT_SHA__ === "string" && g.__GIT_SHA__) {
568
- return g.__GIT_SHA__;
569
- }
570
- }
571
- catch { }
572
- // 2) Common pattern: process.env.GIT_SHA (webpack/rollup/esbuild/parcel)
573
- try {
574
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
575
- const p = typeof process !== "undefined" ? process : undefined;
576
- if (p?.env?.GIT_SHA)
577
- return String(p.env.GIT_SHA);
578
- }
579
- catch { }
580
- // 3) Vite (or other tools exposing import.meta.env)
581
- try {
582
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
583
- const im = import.meta;
584
- if (im?.env?.VITE_GIT_SHA)
585
- return String(im.env.VITE_GIT_SHA);
586
- if (im?.env?.GIT_SHA)
587
- return String(im.env.GIT_SHA);
588
- }
589
- catch { }
590
- return undefined;
591
- }
592
569
  // --- helper for mapUuid default from window ---
593
570
  function getMapUuidFromWindow() {
594
571
  try {
@@ -606,38 +583,77 @@ function getMapUuidFromWindow() {
606
583
  // it would be 1 serviceIdentifier per frontend user session,
607
584
  // which is very wasteful
608
585
  export async function startRecording({ apiKey, backendApi = "https://api-service.sailfishqa.com", domainsToPropagateHeaderTo = [], domainsToNotPropagateHeaderTo = [], serviceVersion, serviceIdentifier, gitSha, serviceAdditionalMetadata, }) {
609
- const effectiveGitSha = gitSha ?? getCompileTimeGitSha();
586
+ const effectiveGitSha = gitSha ?? readGitSha();
610
587
  const effectiveServiceIdentifier = serviceIdentifier ?? "";
611
588
  const effectiveServiceVersion = serviceVersion ?? "";
612
589
  const effectiveLibrary = "JS/TS"; // from Recorder.LIBRARY_CHOICES
613
590
  const effectiveMapUuid = getMapUuidFromWindow();
614
591
  const sessionId = getOrSetSessionId();
615
- initializeDomContentEvents(sessionId);
616
- initializeConsolePlugin(DEFAULT_CONSOLE_RECORDING_SETTINGS, sessionId);
592
+ const g = (window.__sailfish_recorder ||= {});
593
+ g.sessionId = sessionId;
594
+ g.apiKey = apiKey;
595
+ g.backendApi = backendApi;
596
+ // Already fully initialized for this session with an open socket? No-op.
597
+ if (g.initialized &&
598
+ g.sessionId === sessionId &&
599
+ g.ws &&
600
+ g.ws.readyState === 1) {
601
+ trackDomainChangesOnce(); // ensure single watcher alive
602
+ return;
603
+ }
604
+ // One-time installs (per window)
605
+ if (!g.domEventsInit) {
606
+ initializeDomContentEvents(sessionId);
607
+ g.domEventsInit = true;
608
+ }
609
+ if (!g.consoleInit) {
610
+ initializeConsolePlugin(DEFAULT_CONSOLE_RECORDING_SETTINGS, sessionId);
611
+ g.consoleInit = true;
612
+ }
613
+ if (!g.errorInit) {
614
+ initializeErrorInterceptor();
615
+ g.errorInit = true;
616
+ }
617
617
  storeCredentialsAndConnection({ apiKey, backendApi });
618
- trackDomainChanges();
619
- sessionStorage.setItem(SF_API_KEY_FOR_UPDATE, apiKey);
620
- sessionStorage.setItem(SF_BACKEND_API, backendApi);
621
- sendDomainsToNotPropagateHeaderTo(apiKey, [
622
- ...domainsToNotPropagateHeaderTo,
623
- ...DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT,
624
- ], backendApi).catch((error) => console.error("Failed to send domains to not propagate header to:", error));
625
- // Setup interceptors with custom ignore and propagate domains
626
- setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo);
627
- setupFetchInterceptor(domainsToNotPropagateHeaderTo);
618
+ trackDomainChangesOnce();
619
+ sessionStorage.setItem("sailfishApiKey", apiKey);
620
+ sessionStorage.setItem("sailfishBackendApi", backendApi);
621
+ if (!g.sentDoNotPropagateOnce) {
622
+ sendDomainsToNotPropagateHeaderTo(apiKey, [
623
+ ...domainsToNotPropagateHeaderTo,
624
+ ...DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT,
625
+ ], backendApi).catch((error) => console.error("Failed to send domains to not propagate header to:", error));
626
+ g.sentDoNotPropagateOnce = true;
627
+ }
628
+ // Patch XHR/fetch once per window
629
+ if (!g.xhrPatched) {
630
+ setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo);
631
+ g.xhrPatched = true;
632
+ }
633
+ if (!g.fetchPatched) {
634
+ setupFetchInterceptor(domainsToNotPropagateHeaderTo);
635
+ g.fetchPatched = true;
636
+ }
628
637
  gatherAndCacheDeviceInfo();
629
638
  try {
630
639
  const captureSettingsResponse = await fetchCaptureSettings(apiKey, backendApi);
631
640
  const captureSettings = captureSettingsResponse.data?.captureSettingsFromApiKey ||
632
641
  DEFAULT_CAPTURE_SETTINGS;
642
+ // If a socket is already open now, stop here.
643
+ if (g.ws && g.ws.readyState === 1) {
644
+ return;
645
+ }
633
646
  const metadataWithAppUrl = withAppUrlMetadata(serviceAdditionalMetadata);
647
+ // Create/ensure a server-side recording session once per browser session
634
648
  const sessionResponse = await startRecordingSession(apiKey, sessionId, backendApi, effectiveServiceIdentifier, effectiveServiceVersion, effectiveMapUuid, effectiveGitSha, effectiveLibrary, metadataWithAppUrl);
635
649
  if (sessionResponse.data?.startRecordingSession) {
636
- const websocket = await initializeRecording(captureSettings,
637
- // DEFAULT_NETWORK_CAPTURE_SETTINGS,
638
- backendApi, apiKey, sessionId);
639
- // Send parameters once before starting interval
640
- sendMapUuidIfAvailable(serviceIdentifier, serviceVersion);
650
+ const websocket = await initializeRecording(captureSettings, backendApi, apiKey, sessionId);
651
+ g.ws = websocket;
652
+ g.initialized = true;
653
+ if (!g.sentMapUuidOnce) {
654
+ sendMapUuidIfAvailable(serviceIdentifier, serviceVersion);
655
+ g.sentMapUuidOnce = true;
656
+ }
641
657
  }
642
658
  else {
643
659
  console.error("Failed to start recording session:", sessionResponse.errors || sessionResponse);
@@ -647,22 +663,49 @@ export async function startRecording({ apiKey, backendApi = "https://api-service
647
663
  console.error("Error starting recording:", error);
648
664
  }
649
665
  }
666
+ /*
667
+ If there are any interface changes, you MUST change the following file(s):
668
+ => prefect/workspace/flows/fix_code_issues/tasks/autoinstall/js_ts_frontend/create_sailfish_instrumentation_wrapper_jsx_tsx_component.py
669
+ */
650
670
  export const initRecorder = async (options) => {
651
- console.log("Initializing Sailfish Recorder with options:", options);
652
671
  // Only run on the client (browser) environment
653
- if (typeof window === "undefined") {
672
+ if (typeof window === "undefined")
673
+ return;
674
+ const g = (window.__sailfish_recorder ||= {});
675
+ const currentSessionId = getOrSetSessionId();
676
+ // If already initialized for this session and socket is open, do nothing.
677
+ if (g.initialized &&
678
+ g.sessionId === currentSessionId &&
679
+ g.ws &&
680
+ g.ws.readyState === 1) {
654
681
  return;
655
682
  }
656
- // Directly invoke the startRecording function from within the same package
657
- return startRecording(options).then(() => {
658
- setupIssueReporting({
659
- apiKey: options.apiKey,
660
- backendApi: options.backendApi ?? "https://api-service.sailfishqa.com",
661
- getSessionId: () => getOrSetSessionId(),
662
- shortcuts: options.reportIssueShortcuts,
663
- customBaseUrl: options.customBaseUrl,
683
+ // Coalesce concurrent calls into one promise
684
+ if (!g.initPromise) {
685
+ g.initPromise = (async () => {
686
+ if (!g.hasLoggedInitOnce) {
687
+ // Log only once per window lifecycle to avoid noisy logs when routes remount.
688
+ console.log("Initializing Sailfish Recorder (first run) …");
689
+ g.hasLoggedInitOnce = true;
690
+ }
691
+ await startRecording(options);
692
+ // Set up the issue reporting UI once
693
+ if (!g.issueReportingInit) {
694
+ setupIssueReporting({
695
+ apiKey: options.apiKey,
696
+ backendApi: options.backendApi ?? "https://api-service.sailfishqa.com",
697
+ getSessionId: () => getOrSetSessionId(),
698
+ shortcuts: options.reportIssueShortcuts,
699
+ customBaseUrl: options.customBaseUrl,
700
+ });
701
+ g.issueReportingInit = true;
702
+ }
703
+ })().finally(() => {
704
+ // Keep all state/flags, but clear the temp promise so a brand-new session can re-init later
705
+ delete g.initPromise;
664
706
  });
665
- });
707
+ }
708
+ return g.initPromise;
666
709
  };
667
710
  // Re-export from other modules
668
711
  export * from "./graphql";
package/dist/recording.js CHANGED
@@ -2,7 +2,6 @@ import { record } from "@sailfish-rrweb/rrweb-record-only";
2
2
  import { getRecordConsolePlugin, } from "@sailfish-rrweb/rrweb-plugin-console-record";
3
3
  // import { NetworkRecordOptions } from "@sailfish-rrweb/rrweb-plugin-network-record";
4
4
  import { EventType } from "@sailfish-rrweb/types";
5
- import { ZendeskAPI } from "react-zendesk";
6
5
  import { Complete, DomContentEventId, DomContentSource, Loading, } from "./constants";
7
6
  import suppressConsoleLogsDuringCall from "./suppressConsoleLogsDuringCall";
8
7
  import { initializeWebSocket, sendEvent } from "./websocket";
@@ -10,6 +9,53 @@ const MASK_CLASS = "sailfishSanitize";
10
9
  const ZENDESK_ELEMENT_ID = "zendesk_chat";
11
10
  const ZENDESK_PROVIDER = "Zendesk";
12
11
  const ZENDESK_TAG_PREFIX = "sailfish-session-";
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+ // Zendesk (Messaging) optional helpers
14
+ // Docs (Messaging widget show/open + core API):
15
+ // - https://developer.zendesk.com/documentation/zendesk-web-widget-sdks/sdks/web/messaging-show-web-widget/
16
+ // - https://developer.zendesk.com/api-reference/widget-messaging/web/core/
17
+ // Conversation tags via Messaging:
18
+ // - zE('messenger:set', 'conversationTags', [...])
19
+ // ─────────────────────────────────────────────────────────────────────────────
20
+ /** Is the Zendesk Messaging API available? */
21
+ function hasZendesk() {
22
+ return typeof window !== "undefined" && typeof window.zE === "function";
23
+ }
24
+ /**
25
+ * Queue-safe "ready" hook. If Zendesk is present, `zE(callback)` registers `callback` to run
26
+ * when the widget is ready (or queues it if loading). If absent, do nothing.
27
+ */
28
+ function whenZendeskReady(ballback) {
29
+ if (typeof window === "undefined")
30
+ return;
31
+ const zE = window.zE;
32
+ if (typeof zE === "function") {
33
+ try {
34
+ // Per Zendesk snippet semantics, zE(ballback) queues the callback until ready.
35
+ zE(ballback);
36
+ }
37
+ catch {
38
+ // ignore
39
+ }
40
+ }
41
+ }
42
+ /** Call zE safely. Returns true if the call executed. */
43
+ function zE_safe(...args) {
44
+ try {
45
+ if (hasZendesk()) {
46
+ // Non-null assertion ok: guarded by hasZendesk()
47
+ window.zE(...args);
48
+ return true;
49
+ }
50
+ }
51
+ catch {
52
+ // ignore
53
+ }
54
+ return false;
55
+ }
56
+ // ─────────────────────────────────────────────────────────────────────────────
57
+ // Recorder logic
58
+ // ─────────────────────────────────────────────────────────────────────────────
13
59
  function maskInputFn(text, node) {
14
60
  if (node.type === "hidden") {
15
61
  return "";
@@ -122,37 +168,53 @@ backendApi, apiKey, sessionId) {
122
168
  maskTextClass: MASK_CLASS,
123
169
  ...captureSettings,
124
170
  });
125
- suppressConsoleLogsDuringCall(() => {
126
- ZendeskAPI("messenger:set", "conversationTags", [
127
- `${ZENDESK_TAG_PREFIX}${sessionId}`,
128
- ]);
129
- });
130
- const handleWidgetOpen = () => {
131
- record.addSailfishEvent(EventType.SailfishCustom, {
132
- action: "customer support chat opened",
133
- element_id: ZENDESK_ELEMENT_ID,
134
- provider: ZENDESK_PROVIDER,
171
+ // ───────────────────────────────────────────────────────────────────────
172
+ // Zendesk (Messaging) OPTIONAL
173
+ // If a site uses Zendesk Messaging (and includes the snippet), wire tags
174
+ // and events once the widget is ready. Otherwise, this is a no-op.
175
+ //
176
+ // APIs used (Messaging):
177
+ // zE('messenger:set', 'conversationTags', string[])
178
+ // zE('messenger:on', 'open' | 'close' | 'unreadMessages', callback)
179
+ // Docs: show/open guidance + core API.
180
+ // ───────────────────────────────────────────────────────────────────────
181
+ whenZendeskReady(() => {
182
+ // Tag the conversation with our session id (if supported).
183
+ suppressConsoleLogsDuringCall(() => {
184
+ zE_safe("messenger:set", "conversationTags", [
185
+ `${ZENDESK_TAG_PREFIX}${sessionId}`,
186
+ ]);
135
187
  });
136
- };
137
- const handleWidgetClose = () => {
138
- record.addSailfishEvent(EventType.SailfishCustom, {
139
- action: "customer support chat closed",
140
- element_id: ZENDESK_ELEMENT_ID,
141
- provider: ZENDESK_PROVIDER,
188
+ const handleWidgetOpen = () => {
189
+ record.addSailfishEvent(EventType.SailfishCustom, {
190
+ action: "customer support chat opened",
191
+ element_id: ZENDESK_ELEMENT_ID,
192
+ provider: ZENDESK_PROVIDER,
193
+ });
194
+ };
195
+ const handleWidgetClose = () => {
196
+ record.addSailfishEvent(EventType.SailfishCustom, {
197
+ action: "customer support chat closed",
198
+ element_id: ZENDESK_ELEMENT_ID,
199
+ provider: ZENDESK_PROVIDER,
200
+ });
201
+ };
202
+ const handleUnreadMessages = (count) => {
203
+ record.addSailfishEvent(EventType.SailfishCustom, {
204
+ action: "zendesk unreadmessages",
205
+ element_id: ZENDESK_ELEMENT_ID,
206
+ provider: ZENDESK_PROVIDER,
207
+ });
208
+ };
209
+ suppressConsoleLogsDuringCall(() => {
210
+ zE_safe("messenger:on", "open", handleWidgetOpen);
211
+ zE_safe("messenger:on", "close", handleWidgetClose);
212
+ zE_safe("messenger:on", "unreadMessages", handleUnreadMessages);
142
213
  });
143
- };
144
- const handleUnreadMessages = (count) => {
145
- record.addSailfishEvent(EventType.SailfishCustom, {
146
- action: "zendesk unreadmessages",
147
- element_id: ZENDESK_ELEMENT_ID,
148
- provider: ZENDESK_PROVIDER,
149
- });
150
- };
151
- suppressConsoleLogsDuringCall(() => {
152
- ZendeskAPI("messenger:on", "open", handleWidgetOpen);
153
- ZendeskAPI("messenger:on", "close", handleWidgetClose);
154
- ZendeskAPI("messenger:on", "unreadMessages", handleUnreadMessages);
155
214
  });
215
+ // ───────────────────────────────────────────────────────────────────────
216
+ // End Zendesk (optional)
217
+ // ───────────────────────────────────────────────────────────────────────
156
218
  }
157
219
  catch (error) {
158
220
  console.error("Error importing plugins!", error);