@sailfish-ai/recorder 1.6.0 → 1.6.2

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.
@@ -1,19 +1,43 @@
1
+ import { Mutex } from "async-mutex";
2
+ const MAX_MESSAGE_SIZE_MB = 50;
3
+ const MAX_MESSAGE_SIZE_BYTES = MAX_MESSAGE_SIZE_MB * 1024 * 1024;
1
4
  let eventCache = [];
5
+ const mutex = new Mutex();
2
6
  export function cacheEvents(event) {
3
- eventCache.push(event);
7
+ mutex.runExclusive(() => {
8
+ eventCache.push(event);
9
+ });
4
10
  }
5
11
  export function sendRecordingEvents(webSocket) {
6
- if (webSocket && webSocket.readyState === WebSocket.OPEN) {
7
- if (eventCache.length > 0) {
8
- const message = {
9
- type: "events",
10
- events: eventCache,
11
- };
12
- webSocket.send(JSON.stringify(message));
13
- eventCache = [];
12
+ mutex.runExclusive(() => {
13
+ if (webSocket && webSocket.readyState === WebSocket.OPEN) {
14
+ if (eventCache.length > 0) {
15
+ let batch = [];
16
+ let batchSize = 0;
17
+ const sendBatch = () => {
18
+ if (batch.length > 0) {
19
+ const message = JSON.stringify({ type: "events", events: batch });
20
+ webSocket.send(message);
21
+ batch = [];
22
+ batchSize = 0;
23
+ }
24
+ };
25
+ for (const event of eventCache) {
26
+ const eventSize = new Blob([JSON.stringify(event)]).size;
27
+ if (eventSize > MAX_MESSAGE_SIZE_BYTES) {
28
+ console.error(`Event Type: ${event.type || "unknown"} exceeds ${MAX_MESSAGE_SIZE_MB}MB limit! It may be rejected by the backend.`);
29
+ webSocket.send(JSON.stringify({ type: "events", events: [event] }));
30
+ continue;
31
+ }
32
+ if (batchSize + eventSize > MAX_MESSAGE_SIZE_BYTES) {
33
+ sendBatch();
34
+ }
35
+ batch.push(event);
36
+ batchSize += eventSize;
37
+ }
38
+ sendBatch();
39
+ eventCache = [];
40
+ }
14
41
  }
15
- }
16
- else {
17
- eventCache.push(...eventCache);
18
- }
42
+ });
19
43
  }
package/dist/recording.js CHANGED
@@ -1,11 +1,15 @@
1
+ import { record } from "@sailfish-rrweb/rrweb-record-only";
1
2
  import { getRecordConsolePlugin, } from "@sailfish-rrweb/rrweb-plugin-console-record";
2
- import record from "@sailfish-rrweb/rrweb-record-only";
3
+ // import { NetworkRecordOptions } from "@sailfish-rrweb/rrweb-plugin-network-record";
3
4
  import { EventType } from "@sailfish-rrweb/types";
5
+ import { ZendeskAPI } from "react-zendesk";
4
6
  import { cacheEvents, sendRecordingEvents } from "./eventCache";
5
7
  import { initializeWebSocket } from "./websocket";
6
8
  const MASK_CLASS = "sailfishSanitize";
9
+ const ZENDESK_ELEMENT_ID = "zendesk_chat";
10
+ const ZENDESK_PROVIDER = "Zendesk";
11
+ const ZENDESK_TAG_PREFIX = "sailfish-session-";
7
12
  function maskInputFn(text, node) {
8
- // Exclude input[type=hidden] fields
9
13
  if (node.type === "hidden") {
10
14
  return "";
11
15
  }
@@ -13,28 +17,21 @@ function maskInputFn(text, node) {
13
17
  creditCard: /\b(?:\d[ -]*?){13,16}\b/,
14
18
  ssn: /\b\d{3}-\d{2}-\d{4}\b/,
15
19
  };
16
- const MASK_CLASS = "mask"; // Assume this is a known constant
17
- // Check for data attributes indicating sensitive information
18
- // Check if element or parents have MASK_CLASS in their className
20
+ const MASK_CLASS = "mask";
19
21
  if (node.closest(`.${MASK_CLASS}`)) {
20
- // Mask the input and retain the length of the input
21
22
  return "*".repeat(text.length);
22
23
  }
23
24
  else if (node.hasAttribute("data-cc") ||
24
25
  (node.getAttribute("autocomplete")?.startsWith("cc-") ?? false) ||
25
26
  patterns.creditCard.test(text)) {
26
- // Mask all but the last 4 digits of a credit card number
27
27
  return "**** **** **** " + text.slice(-4);
28
28
  }
29
29
  else if (node.hasAttribute("data-ssn") || patterns.ssn.test(text)) {
30
- // Mask the first 5 digits of an SSN
31
30
  return "***-**-" + text.slice(-4);
32
31
  }
33
32
  else if (node.hasAttribute("data-dob")) {
34
- // Mask the day and month of a date of birth, revealing only the year
35
33
  return "**/**/" + text.slice(-4);
36
34
  }
37
- // Default to returning the original text
38
35
  return text;
39
36
  }
40
37
  export function initializeConsolePlugin(consoleRecordSettings) {
@@ -55,6 +52,8 @@ backendApi, apiKey, sessionId) {
55
52
  try {
56
53
  record({
57
54
  emit(event) {
55
+ // Attach sessionId to each event
56
+ event.sessionId = sessionId;
58
57
  cacheEvents(event);
59
58
  },
60
59
  maskInputOptions: { text: true }, // Fix the incorrect property name
@@ -62,6 +61,33 @@ backendApi, apiKey, sessionId) {
62
61
  maskTextClass: MASK_CLASS,
63
62
  ...captureSettings,
64
63
  });
64
+ ZendeskAPI("messenger:set", "conversationTags", [
65
+ `${ZENDESK_TAG_PREFIX}${sessionId}`,
66
+ ]);
67
+ const handleWidgetOpen = () => {
68
+ record.addSailfishEvent(EventType.SailfishCustom, {
69
+ action: "customer support chat opened",
70
+ element_id: ZENDESK_ELEMENT_ID,
71
+ provider: ZENDESK_PROVIDER,
72
+ });
73
+ };
74
+ const handleWidgetClose = () => {
75
+ record.addSailfishEvent(EventType.SailfishCustom, {
76
+ action: "customer support chat closed",
77
+ element_id: ZENDESK_ELEMENT_ID,
78
+ provider: ZENDESK_PROVIDER,
79
+ });
80
+ };
81
+ const handleUnreadMessages = (count) => {
82
+ record.addSailfishEvent(EventType.SailfishCustom, {
83
+ action: "zendesk unreadmessages",
84
+ element_id: ZENDESK_ELEMENT_ID,
85
+ provider: ZENDESK_PROVIDER,
86
+ });
87
+ };
88
+ ZendeskAPI("messenger:on", "open", handleWidgetOpen);
89
+ ZendeskAPI("messenger:on", "close", handleWidgetClose);
90
+ ZendeskAPI("messenger:on", "unreadMessages", handleUnreadMessages);
65
91
  setInterval(() => sendRecordingEvents(webSocket), 10000);
66
92
  }
67
93
  catch (error) {