@sailfish-ai/recorder 1.8.2 → 1.8.8
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/README.md +4 -0
- package/dist/errorInterceptor.js +74 -56
- package/dist/inAppReportIssueModal.js +18 -2
- package/dist/index.js +209 -12
- package/dist/recorder.cjs +954 -804
- package/dist/recorder.js +1040 -886
- package/dist/recorder.js.br +0 -0
- package/dist/recorder.js.gz +0 -0
- package/dist/recorder.umd.cjs +955 -805
- package/dist/recording.js +2 -0
- package/dist/types/errorInterceptor.d.ts +0 -5
- package/dist/types/recording.d.ts +2 -0
- package/dist/types/websocket.d.ts +23 -0
- package/dist/websocket.js +244 -7
- package/package.json +2 -1
package/dist/recording.js
CHANGED
|
@@ -85,6 +85,8 @@ export const getUrlAndStoredUuids = () => ({
|
|
|
85
85
|
page_visit_uuid: sessionStorage.getItem("pageVisitUUID"),
|
|
86
86
|
prev_page_visit_uuid: sessionStorage.getItem("prevPageVisitUUID"),
|
|
87
87
|
href: location.origin + location.pathname,
|
|
88
|
+
tabVisibilityChanged: sessionStorage.getItem("tabVisibilityChanged"),
|
|
89
|
+
tabVisibilityState: sessionStorage.getItem("tabVisibilityState"),
|
|
88
90
|
});
|
|
89
91
|
export function initializeDomContentEvents(sessionId) {
|
|
90
92
|
document.addEventListener("readystatechange", () => {
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolves stack traces using source maps.
|
|
3
|
-
* @param stackTrace - The minified stack trace.
|
|
4
|
-
* @returns The mapped stack trace with original file/line/column.
|
|
5
|
-
*/
|
|
6
1
|
export declare function resolveStackTrace(stackTrace?: string | string[]): Promise<string[]>;
|
|
7
2
|
/**
|
|
8
3
|
* Initializes the error interceptor globally.
|
|
@@ -4,6 +4,8 @@ export declare const getUrlAndStoredUuids: () => {
|
|
|
4
4
|
page_visit_uuid: string;
|
|
5
5
|
prev_page_visit_uuid: string;
|
|
6
6
|
href: string;
|
|
7
|
+
tabVisibilityChanged: string;
|
|
8
|
+
tabVisibilityState: string;
|
|
7
9
|
};
|
|
8
10
|
export declare function initializeDomContentEvents(sessionId: string): void;
|
|
9
11
|
export declare function initializeConsolePlugin(consoleRecordSettings: LogRecordOptions, sessionId: string): void;
|
|
@@ -3,3 +3,26 @@ export declare function flushBufferedEvents(): Promise<void>;
|
|
|
3
3
|
export declare function sendEvent(event: any): void;
|
|
4
4
|
export declare function initializeWebSocket(backendApi: string, apiKey: string, sessionId: string): ReconnectingWebSocket;
|
|
5
5
|
export declare function sendMessage(message: Record<string, any>): void;
|
|
6
|
+
/**
|
|
7
|
+
* Enable function span tracking for this session (e.g., when report issue recording starts)
|
|
8
|
+
* This is LOCAL tracking mode - only this session, not global
|
|
9
|
+
*/
|
|
10
|
+
export declare function enableFunctionSpanTracking(): void;
|
|
11
|
+
/**
|
|
12
|
+
* Disable function span tracking for this session (e.g., when report issue recording stops)
|
|
13
|
+
* Only disables if we're in LOCAL tracking mode
|
|
14
|
+
*/
|
|
15
|
+
export declare function disableFunctionSpanTracking(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Check if function span tracking is currently enabled
|
|
18
|
+
*/
|
|
19
|
+
export declare function isFunctionSpanTrackingEnabled(): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Get the current funcspan header name and value
|
|
22
|
+
* Header constants are defined here but used by HTTP interceptors in index.tsx
|
|
23
|
+
* Checks both the enabled flag AND the expiration time before returning the header
|
|
24
|
+
*/
|
|
25
|
+
export declare function getFuncSpanHeader(): {
|
|
26
|
+
name: string;
|
|
27
|
+
value: string;
|
|
28
|
+
} | null;
|
package/dist/websocket.js
CHANGED
|
@@ -8,10 +8,21 @@ import version from "./version";
|
|
|
8
8
|
const DEBUG = readDebugFlag(); // A wrapper around fetch that suppresses connection refused errors
|
|
9
9
|
const MAX_MESSAGE_SIZE_MB = 50;
|
|
10
10
|
const MAX_MESSAGE_SIZE_BYTES = MAX_MESSAGE_SIZE_MB * 1024 * 1024;
|
|
11
|
+
// Function span tracking header constants
|
|
12
|
+
const FUNCSPAN_HEADER_NAME = "X-Sf3-FunctionSpanCaptureOverride";
|
|
13
|
+
const FUNCSPAN_HEADER_VALUE = "1-0-5-5-0-1.0";
|
|
14
|
+
// Tracking configuration type constants (must match backend TrackingConfigurationType enum)
|
|
15
|
+
const TRACKING_CONFIG_GLOBAL = "global";
|
|
16
|
+
const TRACKING_CONFIG_PER_SESSION = "per_session";
|
|
11
17
|
let webSocket = null;
|
|
12
18
|
let isDraining = false;
|
|
13
19
|
let inFlightFlush = null;
|
|
14
20
|
let flushIntervalId = null;
|
|
21
|
+
// Function span tracking state (only manages enabled/disabled)
|
|
22
|
+
let funcSpanTrackingEnabled = false;
|
|
23
|
+
let funcSpanTimeoutId = null;
|
|
24
|
+
let funcSpanExpirationTime = null; // Timestamp when tracking should expire (milliseconds)
|
|
25
|
+
let isLocalTrackingMode = false; // True when tracking is enabled locally (Report Issue), not globally
|
|
15
26
|
function isWebSocketOpen(ws) {
|
|
16
27
|
return ws?.readyState === WebSocket.OPEN;
|
|
17
28
|
}
|
|
@@ -49,10 +60,15 @@ export async function flushBufferedEvents() {
|
|
|
49
60
|
for (const batch of idbBatches) {
|
|
50
61
|
if (!isWebSocketOpen(webSocket))
|
|
51
62
|
break;
|
|
52
|
-
const eventsToSend = batch.map((e) =>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
const eventsToSend = batch.map((e) => {
|
|
64
|
+
const event = {
|
|
65
|
+
...e.data,
|
|
66
|
+
appUrl: e.data?.appUrl ?? window?.location?.href,
|
|
67
|
+
};
|
|
68
|
+
// Note: We do NOT add funcspan header to websocket events
|
|
69
|
+
// The header is only added to HTTP network requests
|
|
70
|
+
return event;
|
|
71
|
+
});
|
|
56
72
|
const idsToDelete = batch
|
|
57
73
|
.map((e) => e.id)
|
|
58
74
|
.filter((id) => id != null);
|
|
@@ -81,6 +97,9 @@ export function sendEvent(event) {
|
|
|
81
97
|
...event,
|
|
82
98
|
app_url: event?.app_url ?? window?.location?.href,
|
|
83
99
|
};
|
|
100
|
+
// Note: We do NOT add funcspan header to websocket events
|
|
101
|
+
// The header is only added to HTTP network requests (fetch/XMLHttpRequest)
|
|
102
|
+
// This is handled in index.tsx injectHeader function
|
|
84
103
|
if (isDraining || !isWebSocketOpen(webSocket)) {
|
|
85
104
|
saveEventToIDB(enrichedEvent);
|
|
86
105
|
return;
|
|
@@ -107,8 +126,10 @@ export function initializeWebSocket(backendApi, apiKey, sessionId) {
|
|
|
107
126
|
};
|
|
108
127
|
webSocket = new ReconnectingWebSocket(wsUrl, [], options);
|
|
109
128
|
webSocket.addEventListener("open", () => {
|
|
110
|
-
if (DEBUG)
|
|
111
|
-
console.log("WebSocket opened
|
|
129
|
+
if (DEBUG) {
|
|
130
|
+
console.log("[Sailfish] WebSocket connection opened");
|
|
131
|
+
console.log(`[Sailfish] Function span tracking state: ${funcSpanTrackingEnabled ? 'ENABLED' : 'DISABLED'}`);
|
|
132
|
+
}
|
|
112
133
|
(async () => {
|
|
113
134
|
try {
|
|
114
135
|
isDraining = true; // begin drain (blocks live sends)
|
|
@@ -128,7 +149,109 @@ export function initializeWebSocket(backendApi, apiKey, sessionId) {
|
|
|
128
149
|
})();
|
|
129
150
|
});
|
|
130
151
|
webSocket.addEventListener("close", () => {
|
|
131
|
-
|
|
152
|
+
if (DEBUG)
|
|
153
|
+
console.log("[Sailfish] WebSocket closed");
|
|
154
|
+
});
|
|
155
|
+
webSocket.addEventListener("message", (event) => {
|
|
156
|
+
try {
|
|
157
|
+
const data = JSON.parse(event.data);
|
|
158
|
+
// Handle function span tracking control messages from backend
|
|
159
|
+
if (data.type === "funcSpanTrackingControl") {
|
|
160
|
+
if (DEBUG) {
|
|
161
|
+
console.log(`[Sailfish] Received funcSpanTrackingControl message:`, {
|
|
162
|
+
enabled: data.enabled,
|
|
163
|
+
timeoutSeconds: data.timeoutSeconds,
|
|
164
|
+
expirationTimestampMs: data.expirationTimestampMs
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
// Clear any existing timeout
|
|
168
|
+
if (funcSpanTimeoutId !== null) {
|
|
169
|
+
window.clearTimeout(funcSpanTimeoutId);
|
|
170
|
+
funcSpanTimeoutId = null;
|
|
171
|
+
}
|
|
172
|
+
// This is a GLOBAL tracking control message
|
|
173
|
+
funcSpanTrackingEnabled = data.enabled;
|
|
174
|
+
isLocalTrackingMode = false; // Mark as global tracking, not local
|
|
175
|
+
if (DEBUG) {
|
|
176
|
+
console.log(`[Sailfish] Function span tracking ${data.enabled ? 'ENABLED (GLOBAL)' : 'DISABLED (GLOBAL)'}`);
|
|
177
|
+
}
|
|
178
|
+
if (data.enabled) {
|
|
179
|
+
// Use server-provided expiration timestamp for synchronization across all clients/pods
|
|
180
|
+
// This ensures all clients expire at the same time, even if they receive the message at different times
|
|
181
|
+
if (data.expirationTimestampMs) {
|
|
182
|
+
funcSpanExpirationTime = data.expirationTimestampMs;
|
|
183
|
+
// Calculate how many milliseconds until expiration
|
|
184
|
+
const now = Date.now();
|
|
185
|
+
const msUntilExpiration = funcSpanExpirationTime - now;
|
|
186
|
+
if (DEBUG) {
|
|
187
|
+
console.log(`[Sailfish] Server expiration timestamp: ${funcSpanExpirationTime}, ms until expiration: ${msUntilExpiration}`);
|
|
188
|
+
}
|
|
189
|
+
if (msUntilExpiration > 0) {
|
|
190
|
+
funcSpanTimeoutId = window.setTimeout(() => {
|
|
191
|
+
// Only auto-disable if still in global mode
|
|
192
|
+
if (!isLocalTrackingMode) {
|
|
193
|
+
funcSpanTrackingEnabled = false;
|
|
194
|
+
funcSpanExpirationTime = null;
|
|
195
|
+
if (DEBUG) {
|
|
196
|
+
console.log(`[Sailfish] GLOBAL function span tracking auto-disabled at server expiration time`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}, msUntilExpiration);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
// Already expired
|
|
203
|
+
funcSpanTrackingEnabled = false;
|
|
204
|
+
funcSpanExpirationTime = null;
|
|
205
|
+
if (DEBUG) {
|
|
206
|
+
console.log(`[Sailfish] Tracking already expired, not enabling`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
// Fallback: no server timestamp provided, use local calculation (legacy behavior)
|
|
212
|
+
const timeoutSeconds = data.timeoutSeconds || 3600; // Default 1 hour
|
|
213
|
+
if (timeoutSeconds > 0) {
|
|
214
|
+
funcSpanExpirationTime = Date.now() + (timeoutSeconds * 1000);
|
|
215
|
+
funcSpanTimeoutId = window.setTimeout(() => {
|
|
216
|
+
if (!isLocalTrackingMode) {
|
|
217
|
+
funcSpanTrackingEnabled = false;
|
|
218
|
+
funcSpanExpirationTime = null;
|
|
219
|
+
if (DEBUG) {
|
|
220
|
+
console.log(`[Sailfish] GLOBAL function span tracking auto-disabled after ${timeoutSeconds}s (legacy)`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}, timeoutSeconds * 1000);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Immediately report this session ID back to backend (non-blocking)
|
|
227
|
+
// This allows backend to track which sessions are active during tracking
|
|
228
|
+
try {
|
|
229
|
+
const sessionId = getOrSetSessionId();
|
|
230
|
+
webSocket.send(JSON.stringify({
|
|
231
|
+
type: "funcSpanTrackingSessionReport",
|
|
232
|
+
sessionId: sessionId,
|
|
233
|
+
enabled: true,
|
|
234
|
+
configurationType: TRACKING_CONFIG_GLOBAL
|
|
235
|
+
}));
|
|
236
|
+
if (DEBUG) {
|
|
237
|
+
console.log(`[Sailfish] GLOBAL tracking session report sent for session: ${sessionId}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch (e) {
|
|
241
|
+
if (DEBUG) {
|
|
242
|
+
console.warn(`[Sailfish] Failed to send GLOBAL tracking session report:`, e);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
// When disabled, clear expiration time
|
|
248
|
+
funcSpanExpirationTime = null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch (e) {
|
|
253
|
+
// Ignore parse errors for non-JSON messages
|
|
254
|
+
}
|
|
132
255
|
});
|
|
133
256
|
return webSocket;
|
|
134
257
|
}
|
|
@@ -157,3 +280,117 @@ function getWebSocketHost(url) {
|
|
|
157
280
|
parser.href = url;
|
|
158
281
|
return `${parser.hostname}${parser.port ? `:${parser.port}` : ""}`;
|
|
159
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Enable function span tracking for this session (e.g., when report issue recording starts)
|
|
285
|
+
* This is LOCAL tracking mode - only this session, not global
|
|
286
|
+
*/
|
|
287
|
+
export function enableFunctionSpanTracking() {
|
|
288
|
+
if (DEBUG) {
|
|
289
|
+
console.log("[Sailfish] enableFunctionSpanTracking() called - Report Issue recording started (LOCAL MODE)");
|
|
290
|
+
}
|
|
291
|
+
funcSpanTrackingEnabled = true;
|
|
292
|
+
isLocalTrackingMode = true; // Mark as local tracking
|
|
293
|
+
funcSpanExpirationTime = null; // Local mode has no expiration
|
|
294
|
+
// Clear any existing timeout
|
|
295
|
+
if (funcSpanTimeoutId !== null) {
|
|
296
|
+
window.clearTimeout(funcSpanTimeoutId);
|
|
297
|
+
funcSpanTimeoutId = null;
|
|
298
|
+
}
|
|
299
|
+
// Report this session to backend for tracking with per_session configuration type
|
|
300
|
+
if (isWebSocketOpen(webSocket)) {
|
|
301
|
+
try {
|
|
302
|
+
const sessionId = getOrSetSessionId();
|
|
303
|
+
const message = {
|
|
304
|
+
type: "funcSpanTrackingSessionReport",
|
|
305
|
+
sessionId: sessionId,
|
|
306
|
+
enabled: true,
|
|
307
|
+
configurationType: TRACKING_CONFIG_PER_SESSION,
|
|
308
|
+
};
|
|
309
|
+
webSocket.send(JSON.stringify(message));
|
|
310
|
+
}
|
|
311
|
+
catch (e) {
|
|
312
|
+
console.error(`[FUNCSPAN START] ✗ Failed to send tracking session report:`, e);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
if (DEBUG) {
|
|
317
|
+
console.warn(`[Sailfish] WebSocket not open, cannot report LOCAL tracking session`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Disable function span tracking for this session (e.g., when report issue recording stops)
|
|
323
|
+
* Only disables if we're in LOCAL tracking mode
|
|
324
|
+
*/
|
|
325
|
+
export function disableFunctionSpanTracking() {
|
|
326
|
+
if (DEBUG) {
|
|
327
|
+
console.log("[Sailfish] disableFunctionSpanTracking() called - Report Issue recording stopped");
|
|
328
|
+
}
|
|
329
|
+
// Always send the disable message for per-session tracking
|
|
330
|
+
// This will only disable tracking for THIS specific recording session
|
|
331
|
+
// Global tracking (if active) will remain active
|
|
332
|
+
if (isWebSocketOpen(webSocket)) {
|
|
333
|
+
try {
|
|
334
|
+
const sessionId = getOrSetSessionId();
|
|
335
|
+
const message = {
|
|
336
|
+
type: "funcSpanTrackingSessionReport",
|
|
337
|
+
sessionId: sessionId,
|
|
338
|
+
enabled: false,
|
|
339
|
+
configurationType: TRACKING_CONFIG_PER_SESSION,
|
|
340
|
+
};
|
|
341
|
+
webSocket.send(JSON.stringify(message));
|
|
342
|
+
}
|
|
343
|
+
catch (e) {
|
|
344
|
+
console.error(`[FUNCSPAN STOP] ✗ Failed to send tracking stop report:`, e);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
console.warn(`[FUNCSPAN STOP] ✗ WebSocket not open, cannot notify tracking end`);
|
|
349
|
+
}
|
|
350
|
+
if (isLocalTrackingMode) {
|
|
351
|
+
funcSpanTrackingEnabled = false;
|
|
352
|
+
isLocalTrackingMode = false;
|
|
353
|
+
funcSpanExpirationTime = null;
|
|
354
|
+
if (DEBUG) {
|
|
355
|
+
console.log("[Sailfish] LOCAL tracking mode disabled");
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Clear any existing timeout
|
|
359
|
+
if (funcSpanTimeoutId !== null) {
|
|
360
|
+
window.clearTimeout(funcSpanTimeoutId);
|
|
361
|
+
funcSpanTimeoutId = null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Check if function span tracking is currently enabled
|
|
366
|
+
*/
|
|
367
|
+
export function isFunctionSpanTrackingEnabled() {
|
|
368
|
+
return funcSpanTrackingEnabled;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Get the current funcspan header name and value
|
|
372
|
+
* Header constants are defined here but used by HTTP interceptors in index.tsx
|
|
373
|
+
* Checks both the enabled flag AND the expiration time before returning the header
|
|
374
|
+
*/
|
|
375
|
+
export function getFuncSpanHeader() {
|
|
376
|
+
if (!funcSpanTrackingEnabled) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
// Check if tracking has expired (for global mode with timeout)
|
|
380
|
+
if (funcSpanExpirationTime !== null) {
|
|
381
|
+
const now = Date.now();
|
|
382
|
+
if (now >= funcSpanExpirationTime) {
|
|
383
|
+
// Tracking has expired, disable it immediately
|
|
384
|
+
funcSpanTrackingEnabled = false;
|
|
385
|
+
funcSpanExpirationTime = null;
|
|
386
|
+
if (DEBUG) {
|
|
387
|
+
console.log("[Sailfish] Function span tracking expired on header check - disabling now");
|
|
388
|
+
}
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
name: FUNCSPAN_HEADER_NAME,
|
|
394
|
+
value: FUNCSPAN_HEADER_VALUE
|
|
395
|
+
};
|
|
396
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sailfish-ai/recorder",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.8",
|
|
4
4
|
"publishPublicly": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/recorder.cjs",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"dist"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
+
"@sailfish-ai/sf-map-utils": "0.4.3",
|
|
40
41
|
"@sailfish-rrweb/rrweb-plugin-console-record": "0.5.2",
|
|
41
42
|
"@sailfish-rrweb/rrweb-record-only": "0.5.2",
|
|
42
43
|
"@sailfish-rrweb/types": "0.5.2",
|