@sailfish-ai/recorder 1.7.8 → 1.7.11
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/eventStore.js +42 -0
- package/dist/index.js +12 -10
- package/dist/notifyEventStore.js +26 -0
- package/dist/recording.js +25 -18
- 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/eventStore.d.ts +10 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/notifyEventStore.d.ts +7 -0
- package/dist/types/recording.d.ts +2 -2
- package/dist/types/utils.d.ts +6 -0
- package/dist/types/websocket.d.ts +2 -0
- package/dist/utils.js +41 -0
- package/dist/websocket.js +77 -13
- package/package.json +2 -1
- package/dist/eventCache.js +0 -43
- package/dist/types/eventCache.d.ts +0 -2
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface IndexedEvent {
|
|
2
|
+
id?: number;
|
|
3
|
+
timestamp: number;
|
|
4
|
+
data: any;
|
|
5
|
+
}
|
|
6
|
+
export declare function saveEventToIDB(event: any): Promise<void>;
|
|
7
|
+
export declare function saveEventsToIDB(events: any[]): Promise<void>;
|
|
8
|
+
export declare function getAllIndexedEvents(): Promise<IndexedEvent[]>;
|
|
9
|
+
export declare function deleteEventById(id: number): Promise<void>;
|
|
10
|
+
export declare function deleteEventsByIds(ids: number[]): Promise<void>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export declare const initRecorder: (options: {
|
|
|
19
19
|
serviceVersion?: string;
|
|
20
20
|
serviceIdentifier?: string;
|
|
21
21
|
}) => Promise<void>;
|
|
22
|
-
export * from "./
|
|
22
|
+
export * from "./utils";
|
|
23
23
|
export * from "./graphql";
|
|
24
24
|
export * from "./recording";
|
|
25
25
|
export * from "./sendSailfishMessages";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface NotifyMessageRecord {
|
|
2
|
+
id?: number;
|
|
3
|
+
value: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function saveNotifyMessageToIDB(message: string): Promise<void>;
|
|
6
|
+
export declare function getAllNotifyMessages(): Promise<NotifyMessageRecord[]>;
|
|
7
|
+
export declare function deleteNotifyMessageById(id: number): Promise<void>;
|
|
@@ -5,7 +5,7 @@ export declare const getUrlAndStoredUuids: () => {
|
|
|
5
5
|
prev_page_visit_uuid: string;
|
|
6
6
|
href: string;
|
|
7
7
|
};
|
|
8
|
-
export declare function initializeDomContentEvents(): void;
|
|
9
|
-
export declare function initializeConsolePlugin(consoleRecordSettings: LogRecordOptions): void;
|
|
8
|
+
export declare function initializeDomContentEvents(sessionId: string): void;
|
|
9
|
+
export declare function initializeConsolePlugin(consoleRecordSettings: LogRecordOptions, sessionId: string): void;
|
|
10
10
|
export declare function initializeRecording(captureSettings: any, // TODO - Sibyl post-launch - replace type
|
|
11
11
|
backendApi: string, apiKey: string, sessionId: string): Promise<ReconnectingWebSocket>;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import ReconnectingWebSocket from "reconnecting-websocket";
|
|
2
|
+
export declare function flushBufferedEvents(): Promise<void>;
|
|
3
|
+
export declare function sendEvent(event: any): void;
|
|
2
4
|
export declare function initializeWebSocket(backendApi: string, apiKey: string, sessionId: string): ReconnectingWebSocket;
|
|
3
5
|
export declare function sendMessage(message: Record<string, any>): void;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export function buildBatches(queue, getSize, maxBytes) {
|
|
2
|
+
const grouped = {};
|
|
3
|
+
// Step 1: group by sessionId
|
|
4
|
+
for (const item of queue) {
|
|
5
|
+
const sessionId = item.data.sessionId || "unknown";
|
|
6
|
+
if (!grouped[sessionId]) {
|
|
7
|
+
grouped[sessionId] = [];
|
|
8
|
+
}
|
|
9
|
+
grouped[sessionId].push(item);
|
|
10
|
+
}
|
|
11
|
+
// Step 2: build batches per session
|
|
12
|
+
const batches = [];
|
|
13
|
+
for (const sessionId in grouped) {
|
|
14
|
+
const sessionQueue = grouped[sessionId];
|
|
15
|
+
let batch = [];
|
|
16
|
+
let batchSize = 0;
|
|
17
|
+
for (const item of sessionQueue) {
|
|
18
|
+
const size = getSize(item);
|
|
19
|
+
if (batchSize + size > maxBytes) {
|
|
20
|
+
if (batch.length > 0) {
|
|
21
|
+
batches.push(batch);
|
|
22
|
+
batch = [];
|
|
23
|
+
batchSize = 0;
|
|
24
|
+
}
|
|
25
|
+
if (size > maxBytes) {
|
|
26
|
+
// Skip this oversized item
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
batch.push(item);
|
|
31
|
+
batchSize += size;
|
|
32
|
+
}
|
|
33
|
+
if (batch.length > 0) {
|
|
34
|
+
batches.push(batch);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return batches;
|
|
38
|
+
}
|
|
39
|
+
export function eventSize(event) {
|
|
40
|
+
return new Blob([JSON.stringify(event)]).size;
|
|
41
|
+
}
|
package/dist/websocket.js
CHANGED
|
@@ -1,18 +1,74 @@
|
|
|
1
1
|
import ReconnectingWebSocket from "reconnecting-websocket";
|
|
2
2
|
import version from "./version";
|
|
3
|
-
|
|
3
|
+
import { getAllNotifyMessages, deleteNotifyMessageById, saveNotifyMessageToIDB, } from "./notifyEventStore";
|
|
4
|
+
import { deleteEventsByIds, getAllIndexedEvents, saveEventToIDB, } from './eventStore';
|
|
5
|
+
import { buildBatches, eventSize } from "./utils";
|
|
6
|
+
const MAX_MESSAGE_SIZE_MB = 50;
|
|
7
|
+
const MAX_MESSAGE_SIZE_BYTES = MAX_MESSAGE_SIZE_MB * 1024 * 1024;
|
|
4
8
|
let webSocket = null;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
function isWebSocketOpen(ws) {
|
|
10
|
+
return ws?.readyState === WebSocket.OPEN;
|
|
11
|
+
}
|
|
12
|
+
async function flushNotifyQueue() {
|
|
13
|
+
const stored = await getAllNotifyMessages();
|
|
14
|
+
if (!isWebSocketOpen(webSocket))
|
|
15
|
+
return;
|
|
16
|
+
try {
|
|
17
|
+
for (const event of stored) {
|
|
18
|
+
webSocket.send(event.value);
|
|
19
|
+
await deleteNotifyMessageById(event.id);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (e) { }
|
|
23
|
+
}
|
|
24
|
+
export async function flushBufferedEvents() {
|
|
25
|
+
if (!isWebSocketOpen(webSocket))
|
|
26
|
+
return;
|
|
27
|
+
const persisted = await getAllIndexedEvents();
|
|
28
|
+
const groupedBySession = {};
|
|
29
|
+
for (const event of persisted) {
|
|
30
|
+
const sessionId = event?.data?.data?.sessionId ?? 'unknown-session';
|
|
31
|
+
if (!groupedBySession[sessionId])
|
|
32
|
+
groupedBySession[sessionId] = [];
|
|
33
|
+
groupedBySession[sessionId].push(event);
|
|
34
|
+
}
|
|
35
|
+
for (const groupedEvents of Object.values(groupedBySession)) {
|
|
36
|
+
const idbBatches = buildBatches(groupedEvents, (e) => eventSize(e.data), MAX_MESSAGE_SIZE_BYTES);
|
|
37
|
+
for (const batch of idbBatches) {
|
|
38
|
+
if (!isWebSocketOpen(webSocket))
|
|
39
|
+
break;
|
|
40
|
+
const eventsToSend = batch.map(e => e.data);
|
|
41
|
+
const idsToDelete = batch.map(e => e.id).filter((id) => id != null);
|
|
42
|
+
try {
|
|
43
|
+
const message = JSON.stringify({
|
|
44
|
+
type: 'events',
|
|
45
|
+
events: eventsToSend,
|
|
46
|
+
mapUuid: window.sfMapUuid,
|
|
47
|
+
});
|
|
12
48
|
webSocket.send(message);
|
|
49
|
+
await deleteEventsByIds(idsToDelete);
|
|
13
50
|
}
|
|
51
|
+
catch (err) { }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function sendEvent(event) {
|
|
56
|
+
const msg = JSON.stringify({
|
|
57
|
+
type: 'event',
|
|
58
|
+
event,
|
|
59
|
+
mapUuid: window.sfMapUuid,
|
|
60
|
+
});
|
|
61
|
+
if (isWebSocketOpen(webSocket)) {
|
|
62
|
+
try {
|
|
63
|
+
webSocket.send(msg);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
saveEventToIDB(event);
|
|
14
67
|
}
|
|
15
68
|
}
|
|
69
|
+
else {
|
|
70
|
+
saveEventToIDB(event);
|
|
71
|
+
}
|
|
16
72
|
}
|
|
17
73
|
export function initializeWebSocket(backendApi, apiKey, sessionId) {
|
|
18
74
|
const wsHost = getWebSocketHost(backendApi);
|
|
@@ -24,7 +80,10 @@ export function initializeWebSocket(backendApi, apiKey, sessionId) {
|
|
|
24
80
|
};
|
|
25
81
|
webSocket = new ReconnectingWebSocket(wsUrl, [], options);
|
|
26
82
|
webSocket.addEventListener("open", () => {
|
|
27
|
-
|
|
83
|
+
(async () => {
|
|
84
|
+
await flushNotifyQueue();
|
|
85
|
+
setInterval(() => flushBufferedEvents(), 10000);
|
|
86
|
+
})();
|
|
28
87
|
});
|
|
29
88
|
webSocket.addEventListener("close", () => {
|
|
30
89
|
// Handle reconnect logic or cleanup here if needed
|
|
@@ -33,12 +92,17 @@ export function initializeWebSocket(backendApi, apiKey, sessionId) {
|
|
|
33
92
|
return webSocket;
|
|
34
93
|
}
|
|
35
94
|
export function sendMessage(message) {
|
|
36
|
-
const
|
|
37
|
-
if (webSocket
|
|
38
|
-
|
|
95
|
+
const msg = JSON.stringify(message);
|
|
96
|
+
if (isWebSocketOpen(webSocket)) {
|
|
97
|
+
try {
|
|
98
|
+
webSocket.send(msg);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
saveNotifyMessageToIDB(msg);
|
|
102
|
+
}
|
|
39
103
|
}
|
|
40
104
|
else {
|
|
41
|
-
|
|
105
|
+
saveNotifyMessageToIDB(msg);
|
|
42
106
|
}
|
|
43
107
|
}
|
|
44
108
|
function getWebSocketHost(url) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sailfish-ai/recorder",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.11",
|
|
4
4
|
"publishPublicly": true,
|
|
5
5
|
"main": "dist/sailfish-recorder.umd.js",
|
|
6
6
|
"types": "dist/types/index.d.ts",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"@sailfish-rrweb/rrweb-record-only": "https://us-npm.pkg.dev/sailfish-ai/sailfishai-npm-public/@sailfish-rrweb/rrweb-record-only/-/rrweb-record-only-0.5.2.tgz",
|
|
29
29
|
"@sailfish-rrweb/types": "https://us-npm.pkg.dev/sailfish-ai/sailfishai-npm-public/@sailfish-rrweb/types/-/types-0.5.2.tgz",
|
|
30
30
|
"async-mutex": "^0.5.0",
|
|
31
|
+
"idb": "^8.0.3",
|
|
31
32
|
"react-zendesk": "^0.1.13",
|
|
32
33
|
"reconnecting-websocket": "^4.4.0",
|
|
33
34
|
"source-map-js": "^1.2.1",
|
package/dist/eventCache.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
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;
|
|
4
|
-
let eventCache = [];
|
|
5
|
-
const mutex = new Mutex();
|
|
6
|
-
export function cacheEvents(event) {
|
|
7
|
-
mutex.runExclusive(() => {
|
|
8
|
-
eventCache.push(event);
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
export function sendRecordingEvents(webSocket) {
|
|
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, mapUuid: window.sfMapUuid });
|
|
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
|
-
}
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
}
|