@sailfish-ai/recorder 1.7.2 → 1.7.3
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/constants.js +12 -0
- package/dist/index.js +98 -26
- package/dist/recording.js +52 -0
- 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/constants.d.ts +12 -0
- package/dist/types/recording.d.ts +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const DomContentEventId = 24;
|
|
2
|
+
export const NetworkRequestEventId = 27;
|
|
3
|
+
export const xSf3RidHeader = "X-Sf3-Rid";
|
|
4
|
+
export const DomContentSource = {
|
|
5
|
+
loading: 0,
|
|
6
|
+
contentLoaded: 1,
|
|
7
|
+
complete: 2,
|
|
8
|
+
beforeunload: 3,
|
|
9
|
+
unload: 4,
|
|
10
|
+
};
|
|
11
|
+
export const Loading = "loading";
|
|
12
|
+
export const Complete = "complete";
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// import { NetworkRecordOptions } from "@sailfish-rrweb/rrweb-plugin-network-record";
|
|
2
2
|
import { v4 as uuidv4 } from "uuid";
|
|
3
3
|
import { gatherAndCacheDeviceInfo } from "./deviceInfo";
|
|
4
|
-
import { sendRecordingEvents } from "./eventCache";
|
|
4
|
+
import { cacheEvents, sendRecordingEvents } from "./eventCache";
|
|
5
5
|
import { fetchCaptureSettings, sendDomainsToNotPropagateHeaderTo, startRecordingSession, } from "./graphql";
|
|
6
6
|
import { sendMapUuidIfAvailable } from "./mapUuid";
|
|
7
|
-
import { initializeConsolePlugin, initializeRecording } from "./recording";
|
|
7
|
+
import { initializeDomContentEvents, initializeConsolePlugin, initializeRecording, getUrlAndStoredUuids, } from "./recording";
|
|
8
8
|
import { sendMessage } from "./websocket";
|
|
9
|
+
import { NetworkRequestEventId, xSf3RidHeader } from "./constants";
|
|
9
10
|
// Default list of domains to ignore
|
|
10
11
|
const DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT = [
|
|
11
12
|
"t.co",
|
|
@@ -57,18 +58,23 @@ export const DEFAULT_CONSOLE_RECORDING_SETTINGS = {
|
|
|
57
58
|
// recordInitialRequests: false,
|
|
58
59
|
// };
|
|
59
60
|
function trackDomainChanges() {
|
|
60
|
-
let lastDomain = window.location.href;
|
|
61
|
+
let lastDomain = window.location.href.split("?")[0];
|
|
61
62
|
const checkDomainChange = (forceSend = false) => {
|
|
62
|
-
const currentDomain = window.location.href;
|
|
63
|
+
const currentDomain = window.location.href.split("?")[0];
|
|
63
64
|
if (forceSend || currentDomain !== lastDomain) {
|
|
64
|
-
// console.log(`Domain changed from ${lastDomain} to ${currentDomain}`);
|
|
65
65
|
lastDomain = currentDomain;
|
|
66
|
+
const pageVisitUUID = uuidv4();
|
|
67
|
+
const prevPageVisitUUID = sessionStorage.getItem("pageVisitUUID");
|
|
68
|
+
sessionStorage.setItem("pageVisitUUID", pageVisitUUID);
|
|
69
|
+
sessionStorage.setItem("prevPageVisitUUID", prevPageVisitUUID);
|
|
66
70
|
const timestamp = Date.now();
|
|
67
71
|
sendMessage({
|
|
68
72
|
type: "routeChange",
|
|
69
73
|
data: {
|
|
70
74
|
url: currentDomain,
|
|
71
75
|
timestamp,
|
|
76
|
+
page_visit_uuid: pageVisitUUID,
|
|
77
|
+
prev_page_visit_uuid: prevPageVisitUUID,
|
|
72
78
|
},
|
|
73
79
|
});
|
|
74
80
|
}
|
|
@@ -130,10 +136,17 @@ function handleVisibilityChange() {
|
|
|
130
136
|
getOrSetSessionId(); // Restore sessionId when the user returns to the page
|
|
131
137
|
}
|
|
132
138
|
}
|
|
139
|
+
function clearPageVisitUuid() {
|
|
140
|
+
sessionStorage.removeItem("pageVisitUUID");
|
|
141
|
+
sessionStorage.removeItem("prevPageVisitUUID");
|
|
142
|
+
}
|
|
133
143
|
// Initialize event listeners for visibility change and page unload
|
|
134
144
|
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
135
145
|
// Clearing window.name on refresh to generate and retain a new sessionId
|
|
136
|
-
window.addEventListener("beforeunload", () =>
|
|
146
|
+
window.addEventListener("beforeunload", () => {
|
|
147
|
+
window.name = "";
|
|
148
|
+
clearPageVisitUuid();
|
|
149
|
+
});
|
|
137
150
|
function storeCredentialsAndConnection({ apiKey, backendApi, }) {
|
|
138
151
|
sessionStorage.setItem("sailfishApiKey", apiKey);
|
|
139
152
|
sessionStorage.setItem("sailfishBackendApi", backendApi);
|
|
@@ -232,7 +245,9 @@ function setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo, domainsTo
|
|
|
232
245
|
matchUrlWithWildcard(url, domainsToPropagateHeadersTo);
|
|
233
246
|
if (sessionId && shouldPropagateHeader) {
|
|
234
247
|
try {
|
|
235
|
-
|
|
248
|
+
const pageVisitUUID = sessionStorage.getItem("pageVisitUUID");
|
|
249
|
+
const networkUUID = uuidv4();
|
|
250
|
+
this.setRequestHeader(xSf3RidHeader, `${sessionId}/${pageVisitUUID}/${networkUUID}`);
|
|
236
251
|
}
|
|
237
252
|
catch (e) {
|
|
238
253
|
console.warn(`Could not set X-Sf3-Rid header for ${url}`, e);
|
|
@@ -274,7 +289,7 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo, domainsToPropagat
|
|
|
274
289
|
return target.apply(thisArg, args);
|
|
275
290
|
}
|
|
276
291
|
if (cachedResult === "propagate") {
|
|
277
|
-
return
|
|
292
|
+
return injectHeaderWrapper(target, thisArg, args, input, init, sessionId, url);
|
|
278
293
|
}
|
|
279
294
|
}
|
|
280
295
|
// Check domain exclusion
|
|
@@ -290,38 +305,95 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo, domainsToPropagat
|
|
|
290
305
|
return target.apply(thisArg, args);
|
|
291
306
|
}
|
|
292
307
|
cache.set(url, "propagate");
|
|
293
|
-
return
|
|
308
|
+
return injectHeaderWrapper(target, thisArg, args, input, init, sessionId, url);
|
|
294
309
|
},
|
|
295
310
|
});
|
|
296
|
-
//
|
|
297
|
-
function
|
|
311
|
+
// Wrapper function to emit 'networkRequest' event
|
|
312
|
+
async function injectHeaderWrapper(target, thisArg, args, input, init, sessionId, url) {
|
|
298
313
|
if (sessionId) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
|
|
314
|
+
const networkUUID = uuidv4();
|
|
315
|
+
const urlAndStoredUuids = getUrlAndStoredUuids();
|
|
316
|
+
const method = init.method || "GET";
|
|
317
|
+
const startTime = Date.now();
|
|
318
|
+
try {
|
|
319
|
+
const response = await injectHeader(target, thisArg, input, init, sessionId, urlAndStoredUuids.page_visit_uuid, networkUUID);
|
|
320
|
+
const endTime = Date.now();
|
|
321
|
+
const status = response.status;
|
|
322
|
+
const success = response.ok;
|
|
323
|
+
const error = success ? '' : `Request Error: ${response.statusText}`;
|
|
324
|
+
// Emit 'networkRequestFinished' event
|
|
325
|
+
cacheEvents({
|
|
326
|
+
type: NetworkRequestEventId,
|
|
327
|
+
timestamp: endTime,
|
|
328
|
+
data: {
|
|
329
|
+
request_id: networkUUID,
|
|
330
|
+
session_id: sessionId,
|
|
331
|
+
timestamp_start: startTime,
|
|
332
|
+
timestamp_end: endTime,
|
|
333
|
+
response_code: status,
|
|
334
|
+
success,
|
|
335
|
+
error,
|
|
336
|
+
method,
|
|
337
|
+
url,
|
|
338
|
+
},
|
|
339
|
+
...urlAndStoredUuids,
|
|
306
340
|
});
|
|
307
|
-
return
|
|
341
|
+
return response;
|
|
308
342
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
343
|
+
catch (error) {
|
|
344
|
+
const endTime = Date.now();
|
|
345
|
+
const success = false;
|
|
346
|
+
const responseCode = error.response?.status || 500;
|
|
347
|
+
const errorMessage = error.message || "Fetch request failed";
|
|
348
|
+
// Emit 'networkRequestFinished' event with error
|
|
349
|
+
cacheEvents({
|
|
350
|
+
type: NetworkRequestEventId,
|
|
351
|
+
timestamp: endTime,
|
|
352
|
+
data: {
|
|
353
|
+
request_id: networkUUID,
|
|
354
|
+
session_id: sessionId,
|
|
355
|
+
timestamp_start: startTime,
|
|
356
|
+
timestamp_end: endTime,
|
|
357
|
+
response_code: responseCode,
|
|
358
|
+
success,
|
|
359
|
+
error: errorMessage,
|
|
360
|
+
method,
|
|
361
|
+
url,
|
|
362
|
+
},
|
|
363
|
+
...urlAndStoredUuids
|
|
364
|
+
});
|
|
365
|
+
throw error;
|
|
316
366
|
}
|
|
317
367
|
}
|
|
318
368
|
else {
|
|
319
369
|
return target.apply(thisArg, args);
|
|
320
370
|
}
|
|
321
371
|
}
|
|
372
|
+
// Helper function to inject the X-Sf3-Rid header
|
|
373
|
+
async function injectHeader(target, thisArg, input, init, sessionId, pageVisitUUID, networkUUID) {
|
|
374
|
+
if (input instanceof Request) {
|
|
375
|
+
// Clone the Request and modify headers
|
|
376
|
+
const clonedRequest = input.clone();
|
|
377
|
+
const newHeaders = new Headers(clonedRequest.headers);
|
|
378
|
+
newHeaders.set(xSf3RidHeader, `${sessionId}/${pageVisitUUID}/${networkUUID}`);
|
|
379
|
+
const modifiedRequest = new Request(clonedRequest, {
|
|
380
|
+
headers: newHeaders,
|
|
381
|
+
});
|
|
382
|
+
return await target.call(thisArg, modifiedRequest, init);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
// For string or URL input, modify init to add headers
|
|
386
|
+
const modifiedInit = { ...init };
|
|
387
|
+
const newHeaders = new Headers(init.headers || {});
|
|
388
|
+
newHeaders.set(xSf3RidHeader, `${sessionId}/${pageVisitUUID}/${networkUUID}`);
|
|
389
|
+
modifiedInit.headers = newHeaders;
|
|
390
|
+
return await target.call(thisArg, input, modifiedInit);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
322
393
|
}
|
|
323
394
|
// Main Recording Function
|
|
324
395
|
export async function startRecording({ apiKey, backendApi = "https://api-service.sailfishqa.com", domainsToPropagateHeaderTo = [], domainsToNotPropagateHeaderTo = [], serviceVersion = "", serviceIdentifier = "", }) {
|
|
396
|
+
initializeDomContentEvents();
|
|
325
397
|
initializeConsolePlugin(DEFAULT_CONSOLE_RECORDING_SETTINGS);
|
|
326
398
|
let sessionId = getOrSetSessionId();
|
|
327
399
|
storeCredentialsAndConnection({ apiKey, backendApi });
|
package/dist/recording.js
CHANGED
|
@@ -5,6 +5,7 @@ import { EventType } from "@sailfish-rrweb/types";
|
|
|
5
5
|
import { ZendeskAPI } from "react-zendesk";
|
|
6
6
|
import { cacheEvents, sendRecordingEvents } from "./eventCache";
|
|
7
7
|
import { initializeWebSocket } from "./websocket";
|
|
8
|
+
import { DomContentEventId, DomContentSource, Loading, Complete } from "./constants";
|
|
8
9
|
const MASK_CLASS = "sailfishSanitize";
|
|
9
10
|
const ZENDESK_ELEMENT_ID = "zendesk_chat";
|
|
10
11
|
const ZENDESK_PROVIDER = "Zendesk";
|
|
@@ -34,6 +35,56 @@ function maskInputFn(text, node) {
|
|
|
34
35
|
}
|
|
35
36
|
return text;
|
|
36
37
|
}
|
|
38
|
+
export const getUrlAndStoredUuids = () => ({
|
|
39
|
+
page_visit_uuid: sessionStorage.getItem("pageVisitUUID"),
|
|
40
|
+
prev_page_visit_uuid: sessionStorage.getItem("prevPageVisitUUID"),
|
|
41
|
+
href: location.origin + location.pathname,
|
|
42
|
+
});
|
|
43
|
+
export function initializeDomContentEvents() {
|
|
44
|
+
document.addEventListener("readystatechange", () => {
|
|
45
|
+
const timestamp = Date.now();
|
|
46
|
+
const newEvent = {
|
|
47
|
+
type: DomContentEventId,
|
|
48
|
+
data: { source: 0, info: "" },
|
|
49
|
+
timestamp,
|
|
50
|
+
...getUrlAndStoredUuids(),
|
|
51
|
+
};
|
|
52
|
+
switch (document.readyState) {
|
|
53
|
+
case Loading:
|
|
54
|
+
newEvent.data.source = DomContentSource.loading;
|
|
55
|
+
break;
|
|
56
|
+
case Complete:
|
|
57
|
+
newEvent.data.source = DomContentSource.complete;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
if (newEvent.data.info)
|
|
61
|
+
cacheEvents(newEvent);
|
|
62
|
+
});
|
|
63
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
64
|
+
cacheEvents({
|
|
65
|
+
type: DomContentEventId,
|
|
66
|
+
data: { source: DomContentSource.contentLoaded },
|
|
67
|
+
timestamp: Date.now(),
|
|
68
|
+
...getUrlAndStoredUuids(),
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
window.addEventListener("beforeunload", () => {
|
|
72
|
+
cacheEvents({
|
|
73
|
+
type: DomContentEventId,
|
|
74
|
+
data: { source: DomContentSource.beforeunload },
|
|
75
|
+
timestamp: Date.now(),
|
|
76
|
+
...getUrlAndStoredUuids(),
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
window.addEventListener("unload", () => {
|
|
80
|
+
cacheEvents({
|
|
81
|
+
type: DomContentEventId,
|
|
82
|
+
data: { source: DomContentSource.unload },
|
|
83
|
+
timestamp: Date.now(),
|
|
84
|
+
...getUrlAndStoredUuids(),
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
37
88
|
export function initializeConsolePlugin(consoleRecordSettings) {
|
|
38
89
|
const { name, observer } = getRecordConsolePlugin(consoleRecordSettings);
|
|
39
90
|
observer((payload) => cacheEvents({
|
|
@@ -52,6 +103,7 @@ backendApi, apiKey, sessionId) {
|
|
|
52
103
|
try {
|
|
53
104
|
record({
|
|
54
105
|
emit(event) {
|
|
106
|
+
Object.assign(event, getUrlAndStoredUuids());
|
|
55
107
|
// Attach sessionId to each event
|
|
56
108
|
event.sessionId = sessionId;
|
|
57
109
|
cacheEvents(event);
|