@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 +55 -0
- package/dist/errorInterceptor.js +14 -5
- package/dist/exponentialBackoff.js +2 -2
- package/dist/graphql.js +2 -1
- package/dist/index.js +107 -64
- package/dist/recording.js +91 -29
- package/dist/sailfish-recorder.cjs.js +1 -1
- package/dist/sailfish-recorder.cjs.js.br +0 -0
- package/dist/sailfish-recorder.cjs.js.gz +0 -0
- package/dist/sailfish-recorder.es.js +1 -1
- package/dist/sailfish-recorder.es.js.br +0 -0
- package/dist/sailfish-recorder.es.js.gz +0 -0
- package/dist/sailfish-recorder.umd.js +1 -1
- package/dist/sailfish-recorder.umd.js.br +0 -0
- package/dist/sailfish-recorder.umd.js.gz +0 -0
- package/dist/types/env.d.ts +2 -0
- package/dist/websocket.js +2 -1
- package/package.json +11 -12
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
|
+
}
|
package/dist/errorInterceptor.js
CHANGED
|
@@ -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)
|
|
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
|
|
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: "
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
108
|
-
setInterval(debouncedCheck, 1000);
|
|
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 ??
|
|
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
|
-
|
|
616
|
-
|
|
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
|
-
|
|
619
|
-
sessionStorage.setItem(
|
|
620
|
-
sessionStorage.setItem(
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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
|
-
//
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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);
|