@sailfish-ai/recorder 1.7.49 → 1.8.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/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,15 +1,19 @@
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";
9
11
  import { getUrlAndStoredUuids, initializeConsolePlugin, initializeDomContentEvents, initializeRecording, } from "./recording";
10
12
  import { HAS_DOCUMENT, HAS_LOCAL_STORAGE, HAS_SESSION_STORAGE, HAS_WINDOW, } from "./runtimeEnv";
11
13
  import { getOrSetSessionId } from "./session";
14
+ import { withAppUrlMetadata } from "./utils";
12
15
  import { sendEvent, sendMessage } from "./websocket";
16
+ const DEBUG = readDebugFlag(); // A wrapper around fetch that suppresses connection refused errors
13
17
  // Default list of domains to ignore
14
18
  const DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT = [
15
19
  "t.co",
@@ -78,7 +82,12 @@ export const DEFAULT_CONSOLE_RECORDING_SETTINGS = {
78
82
  // recordBody: true,
79
83
  // recordInitialRequests: false,
80
84
  // };
81
- function trackDomainChanges() {
85
+ function trackDomainChangesOnce() {
86
+ const g = (window.__sailfish_recorder ||= {});
87
+ if (g.routeWatcherIntervalId) {
88
+ // already running
89
+ return;
90
+ }
82
91
  let lastDomain = window.location.href.split("?")[0];
83
92
  const checkDomainChange = (forceSend = false) => {
84
93
  const currentDomain = window.location.href.split("?")[0];
@@ -103,8 +112,8 @@ function trackDomainChanges() {
103
112
  const debouncedCheck = debounce(() => checkDomainChange(), 500);
104
113
  // Force the first check to send the initial URL on load
105
114
  checkDomainChange(true);
106
- // Set interval to periodically check if the domain or path has changed
107
- setInterval(debouncedCheck, 1000); // Adjust the interval as needed
115
+ // Start a single interval and remember it
116
+ g.routeWatcherIntervalId = window.setInterval(debouncedCheck, 1000);
108
117
  }
109
118
  // Debounce utility function
110
119
  function debounce(func, wait) {
@@ -557,37 +566,6 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo = []) {
557
566
  }
558
567
  }
559
568
  }
560
- // Note - if you update this, you must copy this function to the documentation in docs/docs/getting-started/updating-autoinstall-prs.md.
561
- function getCompileTimeGitSha() {
562
- // 1) Preferred: a hard-coded global replaced at build time (all bundlers support "define")
563
- // e.g. webpack DefinePlugin/esbuild/rollup/vite: __GIT_SHA__ => JSON.stringify('<sha>')
564
- try {
565
- const g = globalThis;
566
- if (g && typeof g.__GIT_SHA__ === "string" && g.__GIT_SHA__) {
567
- return g.__GIT_SHA__;
568
- }
569
- }
570
- catch { }
571
- // 2) Common pattern: process.env.GIT_SHA (webpack/rollup/esbuild/parcel)
572
- try {
573
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
574
- const p = typeof process !== "undefined" ? process : undefined;
575
- if (p?.env?.GIT_SHA)
576
- return String(p.env.GIT_SHA);
577
- }
578
- catch { }
579
- // 3) Vite (or other tools exposing import.meta.env)
580
- try {
581
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
582
- const im = import.meta;
583
- if (im?.env?.VITE_GIT_SHA)
584
- return String(im.env.VITE_GIT_SHA);
585
- if (im?.env?.GIT_SHA)
586
- return String(im.env.GIT_SHA);
587
- }
588
- catch { }
589
- return undefined;
590
- }
591
569
  // --- helper for mapUuid default from window ---
592
570
  function getMapUuidFromWindow() {
593
571
  try {
@@ -605,37 +583,77 @@ function getMapUuidFromWindow() {
605
583
  // it would be 1 serviceIdentifier per frontend user session,
606
584
  // which is very wasteful
607
585
  export async function startRecording({ apiKey, backendApi = "https://api-service.sailfishqa.com", domainsToPropagateHeaderTo = [], domainsToNotPropagateHeaderTo = [], serviceVersion, serviceIdentifier, gitSha, serviceAdditionalMetadata, }) {
608
- const effectiveGitSha = gitSha ?? getCompileTimeGitSha();
586
+ const effectiveGitSha = gitSha ?? readGitSha();
609
587
  const effectiveServiceIdentifier = serviceIdentifier ?? "";
610
588
  const effectiveServiceVersion = serviceVersion ?? "";
611
589
  const effectiveLibrary = "JS/TS"; // from Recorder.LIBRARY_CHOICES
612
590
  const effectiveMapUuid = getMapUuidFromWindow();
613
591
  const sessionId = getOrSetSessionId();
614
- initializeDomContentEvents(sessionId);
615
- 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
+ }
616
617
  storeCredentialsAndConnection({ apiKey, backendApi });
617
- trackDomainChanges();
618
- sessionStorage.setItem(SF_API_KEY_FOR_UPDATE, apiKey);
619
- sessionStorage.setItem(SF_BACKEND_API, backendApi);
620
- sendDomainsToNotPropagateHeaderTo(apiKey, [
621
- ...domainsToNotPropagateHeaderTo,
622
- ...DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT,
623
- ], backendApi).catch((error) => console.error("Failed to send domains to not propagate header to:", error));
624
- // Setup interceptors with custom ignore and propagate domains
625
- setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo);
626
- 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
+ }
627
637
  gatherAndCacheDeviceInfo();
628
638
  try {
629
639
  const captureSettingsResponse = await fetchCaptureSettings(apiKey, backendApi);
630
640
  const captureSettings = captureSettingsResponse.data?.captureSettingsFromApiKey ||
631
641
  DEFAULT_CAPTURE_SETTINGS;
632
- const sessionResponse = await startRecordingSession(apiKey, sessionId, backendApi, effectiveServiceIdentifier, effectiveServiceVersion, effectiveMapUuid, effectiveGitSha, effectiveLibrary, serviceAdditionalMetadata);
642
+ // If a socket is already open now, stop here.
643
+ if (g.ws && g.ws.readyState === 1) {
644
+ return;
645
+ }
646
+ const metadataWithAppUrl = withAppUrlMetadata(serviceAdditionalMetadata);
647
+ // Create/ensure a server-side recording session once per browser session
648
+ const sessionResponse = await startRecordingSession(apiKey, sessionId, backendApi, effectiveServiceIdentifier, effectiveServiceVersion, effectiveMapUuid, effectiveGitSha, effectiveLibrary, metadataWithAppUrl);
633
649
  if (sessionResponse.data?.startRecordingSession) {
634
- const websocket = await initializeRecording(captureSettings,
635
- // DEFAULT_NETWORK_CAPTURE_SETTINGS,
636
- backendApi, apiKey, sessionId);
637
- // Send parameters once before starting interval
638
- 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
+ }
639
657
  }
640
658
  else {
641
659
  console.error("Failed to start recording session:", sessionResponse.errors || sessionResponse);
@@ -646,21 +664,44 @@ export async function startRecording({ apiKey, backendApi = "https://api-service
646
664
  }
647
665
  }
648
666
  export const initRecorder = async (options) => {
649
- console.log("Initializing Sailfish Recorder with options:", options);
650
667
  // Only run on the client (browser) environment
651
- if (typeof window === "undefined") {
668
+ if (typeof window === "undefined")
669
+ return;
670
+ const g = (window.__sailfish_recorder ||= {});
671
+ const currentSessionId = getOrSetSessionId();
672
+ // If already initialized for this session and socket is open, do nothing.
673
+ if (g.initialized &&
674
+ g.sessionId === currentSessionId &&
675
+ g.ws &&
676
+ g.ws.readyState === 1) {
652
677
  return;
653
678
  }
654
- // Directly invoke the startRecording function from within the same package
655
- return startRecording(options).then(() => {
656
- setupIssueReporting({
657
- apiKey: options.apiKey,
658
- backendApi: options.backendApi ?? "https://api-service.sailfishqa.com",
659
- getSessionId: () => getOrSetSessionId(),
660
- shortcuts: options.reportIssueShortcuts,
661
- customBaseUrl: options.customBaseUrl,
679
+ // Coalesce concurrent calls into one promise
680
+ if (!g.initPromise) {
681
+ g.initPromise = (async () => {
682
+ if (!g.hasLoggedInitOnce) {
683
+ // Log only once per window lifecycle to avoid noisy logs when routes remount.
684
+ console.log("Initializing Sailfish Recorder (first run) …");
685
+ g.hasLoggedInitOnce = true;
686
+ }
687
+ await startRecording(options);
688
+ // Set up the issue reporting UI once
689
+ if (!g.issueReportingInit) {
690
+ setupIssueReporting({
691
+ apiKey: options.apiKey,
692
+ backendApi: options.backendApi ?? "https://api-service.sailfishqa.com",
693
+ getSessionId: () => getOrSetSessionId(),
694
+ shortcuts: options.reportIssueShortcuts,
695
+ customBaseUrl: options.customBaseUrl,
696
+ });
697
+ g.issueReportingInit = true;
698
+ }
699
+ })().finally(() => {
700
+ // Keep all state/flags, but clear the temp promise so a brand-new session can re-init later
701
+ delete g.initPromise;
662
702
  });
663
- });
703
+ }
704
+ return g.initPromise;
664
705
  };
665
706
  // Re-export from other modules
666
707
  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);