@sailfish-ai/recorder 1.10.5 → 1.10.7
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/chunkSerializer.js +210 -0
- package/dist/chunks/chunkSerializer-BuEZW3N4.js +95 -0
- package/dist/chunks/chunkSerializer-BuEZW3N4.js.br +0 -0
- package/dist/chunks/chunkSerializer-BuEZW3N4.js.gz +0 -0
- package/dist/chunks/chunkSerializer-DM9muyGb.js +94 -0
- package/dist/chunks/chunkSerializer-DM9muyGb.js.br +0 -0
- package/dist/chunks/chunkSerializer-DM9muyGb.js.gz +0 -0
- package/dist/chunks/index-BlPJWz9y.js +2259 -0
- package/dist/chunks/index-BlPJWz9y.js.br +0 -0
- package/dist/chunks/index-BlPJWz9y.js.gz +0 -0
- package/dist/chunks/index-soHaKXF4.js +2287 -0
- package/dist/chunks/index-soHaKXF4.js.br +0 -0
- package/dist/chunks/index-soHaKXF4.js.gz +0 -0
- package/dist/inAppReportIssueModal/state.js +5 -2
- package/dist/index.js +55 -35
- package/dist/recorder.cjs +2 -2208
- package/dist/recorder.js +44 -2230
- package/dist/recorder.js.br +0 -0
- package/dist/recorder.js.gz +0 -0
- package/dist/recording.js +127 -18
- package/dist/scheduler.js +9 -0
- package/dist/types/chunkSerializer.d.ts +31 -0
- package/dist/types/index.d.ts +32 -1
- package/dist/types/recording.d.ts +3 -1
- package/dist/types/scheduler.d.ts +1 -0
- package/dist/types/websocket.d.ts +6 -0
- package/dist/utils.js +3 -1
- package/dist/websocket.js +83 -27
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { STORAGE_KEYS } from "./types";
|
|
2
|
+
import { HAS_LOCAL_STORAGE } from "../runtimeEnv";
|
|
2
3
|
// Helper function to load user preferences from localStorage
|
|
3
4
|
function loadUserPreferences() {
|
|
4
5
|
return {
|
|
5
|
-
createIssue:
|
|
6
|
-
|
|
6
|
+
createIssue: HAS_LOCAL_STORAGE &&
|
|
7
|
+
localStorage.getItem(STORAGE_KEYS.CREATE_ISSUE) === "true",
|
|
8
|
+
createEngTicket: HAS_LOCAL_STORAGE &&
|
|
9
|
+
localStorage.getItem(STORAGE_KEYS.CREATE_ENG_TICKET) === "true",
|
|
7
10
|
};
|
|
8
11
|
}
|
|
9
12
|
// Helper function to get initial state
|
package/dist/index.js
CHANGED
|
@@ -8,11 +8,12 @@ import { readGitSha } from "./env";
|
|
|
8
8
|
import { initializeErrorInterceptor } from "./errorInterceptor";
|
|
9
9
|
import { fetchCaptureSettings, fetchFunctionSpanTrackingEnabled, sendDomainsToNotPropagateHeaderTo, startRecordingSession, } from "./graphql";
|
|
10
10
|
import { sendMapUuidIfAvailable } from "./mapUuid";
|
|
11
|
-
import { getUrlAndStoredUuids, initializeConsolePlugin, initializeDomContentEvents, initializeRecording, } from "./recording";
|
|
11
|
+
import { getUrlAndStoredUuids, initializeConsolePlugin, initializeDomContentEvents, initializeRecording, invalidateUrlCache, } from "./recording";
|
|
12
12
|
import { HAS_DOCUMENT, HAS_LOCAL_STORAGE, HAS_SESSION_STORAGE, HAS_WINDOW, } from "./runtimeEnv";
|
|
13
13
|
import { ensureSessionListeners, getOrSetSessionId } from "./session";
|
|
14
14
|
import { withAppUrlMetadata } from "./utils";
|
|
15
15
|
import { clearStaleFuncSpanState, getFuncSpanHeader, isFunctionSpanTrackingEnabled, restoreFuncSpanState, sendEvent, sendMessage, } from "./websocket";
|
|
16
|
+
import { yieldToMain } from "./scheduler";
|
|
16
17
|
const DEBUG = readDebugFlag(); // A wrapper around fetch that suppresses connection refused errors
|
|
17
18
|
// Regex cache for matchUrlWithWildcard() — avoids recompiling on every network request
|
|
18
19
|
const _regexCache = new Map();
|
|
@@ -148,6 +149,7 @@ function trackDomainChangesOnce() {
|
|
|
148
149
|
const prevPageVisitUUID = sessionStorage.getItem("pageVisitUUID");
|
|
149
150
|
sessionStorage.setItem("pageVisitUUID", pageVisitUUID);
|
|
150
151
|
sessionStorage.setItem("prevPageVisitUUID", prevPageVisitUUID);
|
|
152
|
+
invalidateUrlCache();
|
|
151
153
|
const timestamp = Date.now();
|
|
152
154
|
sendMessage({
|
|
153
155
|
type: "routeChange",
|
|
@@ -242,6 +244,7 @@ function handleVisibilityChange() {
|
|
|
242
244
|
// Store visibility state in sessionStorage
|
|
243
245
|
sessionStorage.setItem("tabVisibilityChanged", timestamp.toString());
|
|
244
246
|
sessionStorage.setItem("tabVisibilityState", visibilityState);
|
|
247
|
+
invalidateUrlCache();
|
|
245
248
|
}
|
|
246
249
|
function clearPageVisitDataFromSessionStorage() {
|
|
247
250
|
if (!HAS_SESSION_STORAGE)
|
|
@@ -250,6 +253,7 @@ function clearPageVisitDataFromSessionStorage() {
|
|
|
250
253
|
sessionStorage.removeItem("prevPageVisitUUID");
|
|
251
254
|
sessionStorage.removeItem("tabVisibilityChanged");
|
|
252
255
|
sessionStorage.removeItem("tabVisibilityState");
|
|
256
|
+
invalidateUrlCache();
|
|
253
257
|
}
|
|
254
258
|
// Visibility/beforeunload listeners are deferred — see _ensureModuleSideEffects()
|
|
255
259
|
// One-time deferred side effects that were previously at module top-level.
|
|
@@ -1013,14 +1017,9 @@ function getMapUuidFromWindow() {
|
|
|
1013
1017
|
// Note - we do NOT send serviceIdentifier because
|
|
1014
1018
|
// it would be 1 serviceIdentifier per frontend user session,
|
|
1015
1019
|
// which is very wasteful
|
|
1016
|
-
export async function startRecording({ apiKey, backendApi = "https://api-service.sailfishqa.com", domainsToPropagateHeaderTo = [], domainsToNotPropagateHeaderTo = [], serviceVersion, serviceIdentifier, gitSha, serviceAdditionalMetadata, enableIpTracking, captureStreamingResponseBody = true, captureResponseBodyMaxMb = 10, captureStreamPrefixKb = 64, captureStreamTimeoutMs = 10000, enableFiberTracking = false, deferRecordingStart
|
|
1017
|
-
//
|
|
1018
|
-
|
|
1019
|
-
const effectiveGitSha = gitSha ?? readGitSha();
|
|
1020
|
-
const effectiveServiceIdentifier = serviceIdentifier ?? "";
|
|
1021
|
-
const effectiveServiceVersion = serviceVersion ?? "";
|
|
1022
|
-
const effectiveLibrary = "JS/TS"; // from Recorder.LIBRARY_CHOICES
|
|
1023
|
-
const effectiveMapUuid = getMapUuidFromWindow();
|
|
1020
|
+
export async function startRecording({ apiKey, backendApi = "https://api-service.sailfishqa.com", domainsToPropagateHeaderTo = [], domainsToNotPropagateHeaderTo = [], serviceVersion, serviceIdentifier, gitSha, serviceAdditionalMetadata, enableIpTracking, captureStreamingResponseBody = true, captureResponseBodyMaxMb = 10, captureStreamPrefixKb = 64, captureStreamTimeoutMs = 10000, enableFiberTracking = false, deferRecording, deferRecordingStart, chunkSnapshot, useWsWorker = true, }) {
|
|
1021
|
+
// deferRecording takes precedence; fall back to deprecated deferRecordingStart; default true
|
|
1022
|
+
const effectiveDeferRecording = deferRecording ?? deferRecordingStart ?? true;
|
|
1024
1023
|
const sessionId = getOrSetSessionId();
|
|
1025
1024
|
const g = (window.__sailfish_recorder ||= {});
|
|
1026
1025
|
g.sessionId = sessionId;
|
|
@@ -1035,32 +1034,60 @@ export async function startRecording({ apiKey, backendApi = "https://api-service
|
|
|
1035
1034
|
trackDomainChangesOnce(); // ensure single watcher alive
|
|
1036
1035
|
return;
|
|
1037
1036
|
}
|
|
1038
|
-
//
|
|
1037
|
+
// ── Phase 1: Critical interceptor installs ───────────────────────────────
|
|
1038
|
+
// These interceptors MUST be installed before app code makes network
|
|
1039
|
+
// requests or logs to the console. Yield between each setup to ensure
|
|
1040
|
+
// no single task exceeds 50ms (especially on mobile with CPU throttle).
|
|
1041
|
+
const bodyCaptureConfig = {
|
|
1042
|
+
captureStreamingResponseBody,
|
|
1043
|
+
captureResponseBodyMaxMb,
|
|
1044
|
+
captureStreamPrefixKb,
|
|
1045
|
+
captureStreamTimeoutMs,
|
|
1046
|
+
};
|
|
1047
|
+
// 1a. XHR interceptor
|
|
1048
|
+
if (!g.xhrPatched) {
|
|
1049
|
+
setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo, bodyCaptureConfig);
|
|
1050
|
+
g.xhrPatched = true;
|
|
1051
|
+
}
|
|
1052
|
+
await yieldToMain();
|
|
1053
|
+
// 1b. Fetch interceptor
|
|
1054
|
+
if (!g.fetchPatched) {
|
|
1055
|
+
setupFetchInterceptor(domainsToNotPropagateHeaderTo, bodyCaptureConfig);
|
|
1056
|
+
g.fetchPatched = true;
|
|
1057
|
+
}
|
|
1058
|
+
await yieldToMain();
|
|
1059
|
+
// 1c. DOM content events
|
|
1039
1060
|
if (!g.domEventsInit) {
|
|
1040
1061
|
initializeDomContentEvents(sessionId);
|
|
1041
1062
|
g.domEventsInit = true;
|
|
1042
1063
|
}
|
|
1064
|
+
await yieldToMain();
|
|
1065
|
+
// 1d. Console plugin (fire-and-forget dynamic import)
|
|
1043
1066
|
if (!g.consoleInit) {
|
|
1044
1067
|
initializeConsolePlugin(DEFAULT_CONSOLE_RECORDING_SETTINGS, sessionId);
|
|
1045
1068
|
g.consoleInit = true;
|
|
1046
1069
|
}
|
|
1070
|
+
await yieldToMain();
|
|
1071
|
+
// 1e. Error interceptor
|
|
1047
1072
|
if (!g.errorInit) {
|
|
1048
1073
|
initializeErrorInterceptor();
|
|
1049
1074
|
g.errorInit = true;
|
|
1050
1075
|
}
|
|
1076
|
+
await yieldToMain();
|
|
1077
|
+
// ── Phase 2: Deferred async work ────────────────────────────────────────
|
|
1078
|
+
// Everything else — GraphQL calls, WebSocket init, device info, domain
|
|
1079
|
+
// reporting, visibility listeners, funcspan state restore, etc.
|
|
1080
|
+
// None of this needs to happen before app code runs.
|
|
1081
|
+
// Deferred module-level side effects (funcspan restore, visibility listeners, etc.)
|
|
1082
|
+
_ensureModuleSideEffects();
|
|
1051
1083
|
storeCredentialsAndConnection({ apiKey, backendApi });
|
|
1052
|
-
|
|
1053
|
-
sessionStorage.setItem("sailfishApiKey", apiKey);
|
|
1054
|
-
sessionStorage.setItem("sailfishBackendApi", backendApi);
|
|
1055
|
-
// Function span tracking state is loaded from localStorage on module init (websocket.tsx)
|
|
1056
|
-
// Validate localStorage state with backend if tracking appears to be enabled but WebSocket not connected
|
|
1084
|
+
// Function span tracking state validation (fire-and-forget)
|
|
1057
1085
|
if (isFunctionSpanTrackingEnabled() && (!g.ws || g.ws.readyState !== 1)) {
|
|
1058
1086
|
fetchFunctionSpanTrackingEnabled(apiKey, backendApi)
|
|
1059
1087
|
.then((funcSpanResponse) => {
|
|
1060
1088
|
const isEnabled = funcSpanResponse.data?.isFunctionSpanTrackingEnabledFromApiKey ??
|
|
1061
1089
|
false;
|
|
1062
1090
|
if (!isEnabled) {
|
|
1063
|
-
// Backend says tracking is NOT active, clear stale localStorage data
|
|
1064
1091
|
clearStaleFuncSpanState();
|
|
1065
1092
|
if (DEBUG) {
|
|
1066
1093
|
console.log("[Sailfish] Cleared stale function span tracking state - backend validation shows tracking is not active");
|
|
@@ -1076,7 +1103,6 @@ export async function startRecording({ apiKey, backendApi = "https://api-service
|
|
|
1076
1103
|
if (DEBUG) {
|
|
1077
1104
|
console.warn("[Sailfish] Failed to validate function span tracking status with backend:", error);
|
|
1078
1105
|
}
|
|
1079
|
-
// On error, keep the localStorage state and let WebSocket correct it when it connects
|
|
1080
1106
|
});
|
|
1081
1107
|
}
|
|
1082
1108
|
if (!g.sentDoNotPropagateOnce) {
|
|
@@ -1086,28 +1112,19 @@ export async function startRecording({ apiKey, backendApi = "https://api-service
|
|
|
1086
1112
|
], backendApi).catch((error) => console.error("Failed to send domains to not propagate header to:", error));
|
|
1087
1113
|
g.sentDoNotPropagateOnce = true;
|
|
1088
1114
|
}
|
|
1089
|
-
// Network body capture config
|
|
1090
|
-
const bodyCaptureConfig = {
|
|
1091
|
-
captureStreamingResponseBody,
|
|
1092
|
-
captureResponseBodyMaxMb,
|
|
1093
|
-
captureStreamPrefixKb,
|
|
1094
|
-
captureStreamTimeoutMs,
|
|
1095
|
-
};
|
|
1096
|
-
// Patch XHR/fetch once per window
|
|
1097
|
-
if (!g.xhrPatched) {
|
|
1098
|
-
setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo, bodyCaptureConfig);
|
|
1099
|
-
g.xhrPatched = true;
|
|
1100
|
-
}
|
|
1101
|
-
if (!g.fetchPatched) {
|
|
1102
|
-
setupFetchInterceptor(domainsToNotPropagateHeaderTo, bodyCaptureConfig);
|
|
1103
|
-
g.fetchPatched = true;
|
|
1104
|
-
}
|
|
1105
1115
|
gatherAndCacheDeviceInfo();
|
|
1106
1116
|
if (enableIpTracking) {
|
|
1107
1117
|
fetchAndSendIp(sessionId);
|
|
1108
1118
|
}
|
|
1109
1119
|
try {
|
|
1120
|
+
const effectiveGitSha = gitSha ?? readGitSha();
|
|
1121
|
+
const effectiveServiceIdentifier = serviceIdentifier ?? "";
|
|
1122
|
+
const effectiveServiceVersion = serviceVersion ?? "";
|
|
1123
|
+
const effectiveLibrary = "JS/TS";
|
|
1124
|
+
const effectiveMapUuid = getMapUuidFromWindow();
|
|
1110
1125
|
const metadataWithAppUrl = withAppUrlMetadata(serviceAdditionalMetadata);
|
|
1126
|
+
// Yield before GraphQL calls
|
|
1127
|
+
await yieldToMain();
|
|
1111
1128
|
// Fire both GraphQL calls in parallel — they are independent
|
|
1112
1129
|
const [captureSettingsResponse, sessionResponse] = await Promise.all([
|
|
1113
1130
|
fetchCaptureSettings(apiKey, backendApi),
|
|
@@ -1123,12 +1140,15 @@ export async function startRecording({ apiKey, backendApi = "https://api-service
|
|
|
1123
1140
|
return;
|
|
1124
1141
|
}
|
|
1125
1142
|
if (sessionResponse.data?.startRecordingSession) {
|
|
1126
|
-
// Extract env value from serviceAdditionalMetadata
|
|
1127
1143
|
const envValue = serviceAdditionalMetadata?.env ||
|
|
1128
1144
|
serviceAdditionalMetadata?.environment;
|
|
1129
|
-
|
|
1145
|
+
// Yield before WebSocket initialization
|
|
1146
|
+
await yieldToMain();
|
|
1147
|
+
const websocket = await initializeRecording(captureSettings, backendApi, apiKey, sessionId, envValue, effectiveDeferRecording, useWsWorker, chunkSnapshot ?? false);
|
|
1130
1148
|
g.ws = websocket;
|
|
1131
1149
|
g.initialized = true;
|
|
1150
|
+
// Route watcher and mapUuid run after WS is connected
|
|
1151
|
+
trackDomainChangesOnce();
|
|
1132
1152
|
if (!g.sentMapUuidOnce) {
|
|
1133
1153
|
sendMapUuidIfAvailable(serviceIdentifier, serviceVersion);
|
|
1134
1154
|
g.sentMapUuidOnce = true;
|