@sailfish-ai/recorder 1.0.0-alpha-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/eventCache.js +26 -0
- package/dist/graphql.js +38 -0
- package/dist/index.js +85 -0
- package/dist/recording.js +34 -0
- package/dist/sailfish-recorder.es.js +21147 -0
- package/dist/sailfish-recorder.umd.js +21151 -0
- package/dist/types/eventCache.d.ts +4 -0
- package/dist/types/graphql.d.ts +4 -0
- package/dist/types/index.d.ts +16 -0
- package/dist/types/recording.d.ts +4 -0
- package/dist/types/types.d.ts +53 -0
- package/dist/types/websocket.d.ts +2 -0
- package/dist/types.js +1 -0
- package/dist/websocket.js +33 -0
- package/package.json +43 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
let eventCache = [];
|
|
2
|
+
export function cacheEvents(event) {
|
|
3
|
+
eventCache.push(event);
|
|
4
|
+
}
|
|
5
|
+
export function sendCachedEvents(webSocket) {
|
|
6
|
+
if (eventCache.length > 0 && webSocket.readyState === WebSocket.OPEN) {
|
|
7
|
+
const message = {
|
|
8
|
+
type: "events",
|
|
9
|
+
events: eventCache,
|
|
10
|
+
};
|
|
11
|
+
webSocket.send(JSON.stringify(message));
|
|
12
|
+
eventCache = [];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function sendRecordingEvents(webSocket, sessionId) {
|
|
16
|
+
if (webSocket && webSocket.readyState === WebSocket.OPEN && sessionId) {
|
|
17
|
+
if (eventCache.length > 0) {
|
|
18
|
+
const message = {
|
|
19
|
+
type: "events",
|
|
20
|
+
events: eventCache,
|
|
21
|
+
};
|
|
22
|
+
webSocket.send(JSON.stringify(message));
|
|
23
|
+
eventCache = [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/graphql.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function sendGraphQLRequest(operationName, query, variables) {
|
|
2
|
+
return fetch(`${variables["backendApi"]}/graphql/?apiKey=${variables["apiKey"]}`, {
|
|
3
|
+
method: "POST",
|
|
4
|
+
headers: {
|
|
5
|
+
"Content-Type": "application/json",
|
|
6
|
+
},
|
|
7
|
+
body: JSON.stringify({ operationName, query, variables }),
|
|
8
|
+
}).then((response) => response.json());
|
|
9
|
+
}
|
|
10
|
+
export function fetchCaptureSettings(apiKey, backendApi) {
|
|
11
|
+
return sendGraphQLRequest("GetCaptureSettingsFromApiKey", `
|
|
12
|
+
query GetCaptureSettingsFromApiKey($apiKey: String!) {
|
|
13
|
+
captureSettingsFromApiKey(apiKey: $apiKey) {
|
|
14
|
+
recordCanvas
|
|
15
|
+
recordCrossOriginIframes
|
|
16
|
+
collectFonts
|
|
17
|
+
inlineImages
|
|
18
|
+
recordPassword
|
|
19
|
+
recordRealName
|
|
20
|
+
recordCreditCardInfo
|
|
21
|
+
recordSsn
|
|
22
|
+
recordDob
|
|
23
|
+
sampling
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
`, { apiKey, backendApi });
|
|
27
|
+
}
|
|
28
|
+
export function startRecordingSession(apiKey, recordingId, backendApi) {
|
|
29
|
+
return sendGraphQLRequest("StartSession", `mutation StartSession($apiKey: UUID!, $recordingSessionId: UUID!) {
|
|
30
|
+
startRecordingSession(companyApiKey: $apiKey, sessionId: $recordingSessionId) {
|
|
31
|
+
id
|
|
32
|
+
}
|
|
33
|
+
}`, {
|
|
34
|
+
apiKey,
|
|
35
|
+
recordingSessionId: recordingId,
|
|
36
|
+
backendApi,
|
|
37
|
+
});
|
|
38
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2
|
+
import { sendRecordingEvents } from "./eventCache";
|
|
3
|
+
import { fetchCaptureSettings, startRecordingSession } from "./graphql";
|
|
4
|
+
import { initializeRecording } from "./recording";
|
|
5
|
+
import { initializeWebSocket } from "./websocket";
|
|
6
|
+
// Default Settings
|
|
7
|
+
export const DEFAULT_DOMAINS_TO_IGNORE = [];
|
|
8
|
+
export const DEFAULT_CAPTURE_SETTINGS = {
|
|
9
|
+
recordCanvas: false,
|
|
10
|
+
recordCrossOriginIframes: false,
|
|
11
|
+
collectFonts: false,
|
|
12
|
+
inlineImages: false,
|
|
13
|
+
recordPassword: false,
|
|
14
|
+
recordRealName: true,
|
|
15
|
+
recordCreditCardInfo: false,
|
|
16
|
+
recordSsn: false,
|
|
17
|
+
recordDob: false,
|
|
18
|
+
sampling: {},
|
|
19
|
+
};
|
|
20
|
+
export const DEFAULT_CONSOLE_RECORDING_SETTINGS = {
|
|
21
|
+
level: ["info", "log", "warn", "error"],
|
|
22
|
+
lengthThreshold: 10000,
|
|
23
|
+
stringifyOptions: {
|
|
24
|
+
stringLengthLimit: 1000,
|
|
25
|
+
numOfKeysLimit: 20,
|
|
26
|
+
depthOfLimit: 1,
|
|
27
|
+
},
|
|
28
|
+
logger: "console",
|
|
29
|
+
};
|
|
30
|
+
export const DEFAULT_NETWORK_CAPTURE_SETTINGS = {
|
|
31
|
+
initiatorTypes: ["fetch", "xmlhttprequest"],
|
|
32
|
+
ignoreRequestFn: (request) => {
|
|
33
|
+
const domain = extractHostname(request.url);
|
|
34
|
+
return DEFAULT_DOMAINS_TO_IGNORE.includes(domain);
|
|
35
|
+
},
|
|
36
|
+
recordHeaders: true,
|
|
37
|
+
ignoreHeaders: {
|
|
38
|
+
request: ["authorization"],
|
|
39
|
+
response: [],
|
|
40
|
+
},
|
|
41
|
+
ignoreBodyParts: {
|
|
42
|
+
request: ["variables.token"],
|
|
43
|
+
response: ["data.token"],
|
|
44
|
+
},
|
|
45
|
+
recordBody: true,
|
|
46
|
+
recordInitialRequests: false,
|
|
47
|
+
};
|
|
48
|
+
// Functions
|
|
49
|
+
export async function startRecording({ apiKey, backendApi, }) {
|
|
50
|
+
const sessionId = uuidv4(); // Use the same value for recordingId and sessionId
|
|
51
|
+
try {
|
|
52
|
+
const captureSettingsResponse = await fetchCaptureSettings(apiKey, backendApi);
|
|
53
|
+
const captureSettings = captureSettingsResponse.data?.captureSettingsFromApiKey ||
|
|
54
|
+
DEFAULT_CAPTURE_SETTINGS;
|
|
55
|
+
const sessionResponse = await startRecordingSession(apiKey, sessionId, backendApi);
|
|
56
|
+
if (sessionResponse.data?.startRecordingSession) {
|
|
57
|
+
const webSocket = initializeWebSocket(backendApi, apiKey, sessionId);
|
|
58
|
+
initializeRecording(captureSettings, DEFAULT_CONSOLE_RECORDING_SETTINGS, DEFAULT_NETWORK_CAPTURE_SETTINGS, backendApi, apiKey, sessionId);
|
|
59
|
+
setInterval(() => sendRecordingEvents(webSocket, sessionId), 10000);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
console.error("Failed to start recording session:", sessionResponse.errors || sessionResponse);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.error("Error starting recording:", error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function extractHostname(url) {
|
|
70
|
+
let hostname = url.indexOf("//") > -1 ? url.split("/")[2] : url.split("/")[0];
|
|
71
|
+
hostname = hostname.split(":")[0];
|
|
72
|
+
hostname = hostname.split("?")[0];
|
|
73
|
+
return hostname;
|
|
74
|
+
}
|
|
75
|
+
function getWebSocketHost(url) {
|
|
76
|
+
const parser = document.createElement("a");
|
|
77
|
+
parser.href = url;
|
|
78
|
+
return `${parser.hostname}${parser.port ? `:${parser.port}` : ""}`;
|
|
79
|
+
}
|
|
80
|
+
// Re-export from other modules
|
|
81
|
+
export * from "./eventCache";
|
|
82
|
+
export * from "./graphql";
|
|
83
|
+
export * from "./recording";
|
|
84
|
+
export * from "./types";
|
|
85
|
+
export * from "./websocket";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { record } from "@sailfish-rrweb/record";
|
|
2
|
+
import { getRecordConsolePlugin, } from "@sailfish-rrweb/rrweb-plugin-console-record";
|
|
3
|
+
import { getRecordNetworkPlugin, } from "@sailfish-rrweb/rrweb-plugin-network-record";
|
|
4
|
+
import { cacheEvents, sendRecordingEvents } from "./eventCache";
|
|
5
|
+
import { initializeWebSocket } from "./websocket";
|
|
6
|
+
const MASK_CLASS = "sailfishSanitize";
|
|
7
|
+
const DEFAULT_DOMAINS_TO_IGNORE = [];
|
|
8
|
+
function maskInputFn(text, node) {
|
|
9
|
+
// The maskInputFn logic here
|
|
10
|
+
return text; // Placeholder return
|
|
11
|
+
}
|
|
12
|
+
export async function initializeRecording(captureSettings, // TODO - Sibyl launch - replace type
|
|
13
|
+
consoleRecordSettings, networkRecordSettings, backendApi, apiKey, sessionId) {
|
|
14
|
+
try {
|
|
15
|
+
record({
|
|
16
|
+
emit(event) {
|
|
17
|
+
cacheEvents(event);
|
|
18
|
+
},
|
|
19
|
+
plugins: [
|
|
20
|
+
getRecordConsolePlugin(consoleRecordSettings),
|
|
21
|
+
getRecordNetworkPlugin(networkRecordSettings),
|
|
22
|
+
],
|
|
23
|
+
maskInputOptions: { text: true }, // Fix the incorrect property name
|
|
24
|
+
maskInputFn,
|
|
25
|
+
maskTextClass: MASK_CLASS,
|
|
26
|
+
...captureSettings,
|
|
27
|
+
});
|
|
28
|
+
const webSocket = initializeWebSocket(backendApi, apiKey, sessionId);
|
|
29
|
+
setInterval(() => sendRecordingEvents(webSocket, sessionId), 10000);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error("Error importing plugins!", error);
|
|
33
|
+
}
|
|
34
|
+
}
|