@monoscopetech/browser 0.5.5 → 0.5.6
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/monoscope.min.js +3 -3
- package/dist/monoscope.min.js.map +1 -1
- package/dist/monoscope.umd.js +3 -3
- package/dist/monoscope.umd.js.map +1 -1
- package/dist/replay.d.ts +18 -4
- package/dist/replay.js +183 -50
- package/package.json +1 -1
package/dist/replay.d.ts
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import { MonoscopeConfig } from "./types";
|
|
2
2
|
export declare class MonoscopeReplay {
|
|
3
|
-
events
|
|
4
|
-
config
|
|
5
|
-
sessionId
|
|
3
|
+
private events;
|
|
4
|
+
private config;
|
|
5
|
+
private sessionId;
|
|
6
|
+
private stopRecording;
|
|
7
|
+
private saveInterval;
|
|
8
|
+
private isSaving;
|
|
9
|
+
private isConfigured;
|
|
6
10
|
constructor(config: MonoscopeConfig, sessionId: string);
|
|
11
|
+
private setupEventListeners;
|
|
12
|
+
private handleUnload;
|
|
13
|
+
private handleVisibilityChange;
|
|
7
14
|
configure(): void;
|
|
8
|
-
save(): void
|
|
15
|
+
save(forceSynchronous?: boolean): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Stop recording and clean up
|
|
18
|
+
*/
|
|
19
|
+
stop(): void;
|
|
20
|
+
getEventCount(): number;
|
|
21
|
+
getSessionId(): string;
|
|
22
|
+
isRecording(): boolean;
|
|
9
23
|
}
|
package/dist/replay.js
CHANGED
|
@@ -1,72 +1,205 @@
|
|
|
1
1
|
import { getRecordConsolePlugin } from "@rrweb/rrweb-plugin-console-record";
|
|
2
2
|
import * as rrweb from "rrweb";
|
|
3
|
-
const MAX_EVENT_BATCH =
|
|
3
|
+
const MAX_EVENT_BATCH = 50;
|
|
4
|
+
const SAVE_INTERVAL = 10000;
|
|
5
|
+
const MAX_RETRY_EVENTS = 1000;
|
|
4
6
|
export class MonoscopeReplay {
|
|
5
7
|
constructor(config, sessionId) {
|
|
6
8
|
this.events = [];
|
|
9
|
+
this.stopRecording = undefined;
|
|
10
|
+
this.saveInterval = null;
|
|
11
|
+
this.isSaving = false;
|
|
12
|
+
this.isConfigured = false;
|
|
7
13
|
this.sessionId = sessionId;
|
|
8
14
|
this.config = config;
|
|
9
|
-
this.save = this.save.bind(this);
|
|
10
15
|
this.events = [];
|
|
16
|
+
// Bind methods
|
|
17
|
+
this.save = this.save.bind(this);
|
|
11
18
|
this.configure = this.configure.bind(this);
|
|
12
|
-
|
|
19
|
+
this.handleUnload = this.handleUnload.bind(this);
|
|
20
|
+
this.handleVisibilityChange = this.handleVisibilityChange.bind(this);
|
|
21
|
+
// Setup event listeners
|
|
22
|
+
this.setupEventListeners();
|
|
23
|
+
}
|
|
24
|
+
setupEventListeners() {
|
|
25
|
+
window.addEventListener("beforeunload", this.handleUnload);
|
|
26
|
+
document.addEventListener("visibilitychange", this.handleVisibilityChange);
|
|
27
|
+
window.addEventListener("pagehide", this.handleUnload);
|
|
28
|
+
}
|
|
29
|
+
handleUnload() {
|
|
30
|
+
this.save(true); // Force synchronous save on unload
|
|
31
|
+
}
|
|
32
|
+
handleVisibilityChange() {
|
|
33
|
+
if (document.visibilityState === "hidden") {
|
|
34
|
+
this.save();
|
|
35
|
+
}
|
|
13
36
|
}
|
|
14
37
|
configure() {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
if (this.isConfigured) {
|
|
39
|
+
console.warn("MonoscopeReplay already configured");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
this.stopRecording = rrweb.record({
|
|
44
|
+
emit: (event) => {
|
|
45
|
+
// Don't record if we're in a weird state
|
|
46
|
+
if (!event || typeof event !== "object") {
|
|
47
|
+
console.warn("Invalid event received:", event);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
this.events.push(event);
|
|
51
|
+
// Auto-save when batch size reached
|
|
52
|
+
if (this.events.length >= MAX_EVENT_BATCH) {
|
|
53
|
+
this.save();
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
// Prevent recording replay UI elements
|
|
57
|
+
blockClass: "rr-block",
|
|
58
|
+
blockSelector: "#replay-container, .replay-ui, [data-rrweb-ignore]",
|
|
59
|
+
// Privacy settings
|
|
60
|
+
maskAllInputs: true,
|
|
61
|
+
maskInputOptions: {
|
|
62
|
+
password: true,
|
|
63
|
+
email: true,
|
|
64
|
+
tel: true,
|
|
65
|
+
},
|
|
66
|
+
maskTextClass: "rr-mask",
|
|
67
|
+
// Performance settings
|
|
68
|
+
checkoutEveryNms: 15 * 1000, // Full snapshot every 15s
|
|
69
|
+
sampling: {
|
|
70
|
+
mouseInteraction: {
|
|
71
|
+
MouseUp: false,
|
|
72
|
+
MouseDown: false,
|
|
73
|
+
Click: true,
|
|
74
|
+
ContextMenu: false,
|
|
75
|
+
DblClick: true,
|
|
76
|
+
Focus: false,
|
|
77
|
+
Blur: false,
|
|
78
|
+
TouchStart: true,
|
|
79
|
+
TouchEnd: false,
|
|
38
80
|
},
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
81
|
+
mousemove: true,
|
|
82
|
+
scroll: 150, // Throttle scroll events
|
|
83
|
+
media: 800,
|
|
84
|
+
input: "last", // Only capture final input value
|
|
85
|
+
},
|
|
86
|
+
// Console plugin
|
|
87
|
+
plugins: [
|
|
88
|
+
getRecordConsolePlugin({
|
|
89
|
+
level: ["info", "log", "warn", "error"],
|
|
90
|
+
lengthThreshold: 10000,
|
|
91
|
+
stringifyOptions: {
|
|
92
|
+
stringLengthLimit: 1000,
|
|
93
|
+
numOfKeysLimit: 100,
|
|
94
|
+
depthOfLimit: 2, // Increased from 1 for better context
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
// Setup periodic save
|
|
100
|
+
this.saveInterval = setInterval(() => {
|
|
101
|
+
this.save();
|
|
102
|
+
}, SAVE_INTERVAL);
|
|
103
|
+
this.isConfigured = true;
|
|
104
|
+
console.log("MonoscopeReplay configured successfully");
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.error("Failed to configure MonoscopeReplay:", error);
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
43
110
|
}
|
|
44
|
-
save() {
|
|
45
|
-
|
|
111
|
+
async save(forceSynchronous = false) {
|
|
112
|
+
// Prevent concurrent saves
|
|
113
|
+
if (this.isSaving && !forceSynchronous) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Nothing to save
|
|
117
|
+
if (this.events.length === 0) {
|
|
46
118
|
return;
|
|
47
|
-
let { replayEventsBaseUrl, projectId } = this.config;
|
|
48
|
-
if (!replayEventsBaseUrl) {
|
|
49
|
-
replayEventsBaseUrl = `https://app.apitoolkit.io/rrweb/${projectId}`;
|
|
50
119
|
}
|
|
51
|
-
|
|
52
|
-
|
|
120
|
+
// Prevent event array from growing too large during failed saves
|
|
121
|
+
if (this.events.length > MAX_RETRY_EVENTS) {
|
|
122
|
+
console.warn(`Event queue exceeded ${MAX_RETRY_EVENTS}, dropping oldest events`);
|
|
123
|
+
this.events = this.events.slice(-MAX_RETRY_EVENTS);
|
|
53
124
|
}
|
|
54
|
-
|
|
125
|
+
this.isSaving = true;
|
|
126
|
+
const { replayEventsBaseUrl, projectId } = this.config;
|
|
127
|
+
// Construct base URL
|
|
128
|
+
let baseUrl = replayEventsBaseUrl || "https://app.monoscope.tech";
|
|
129
|
+
baseUrl = `${baseUrl}/rrweb/${projectId}`;
|
|
130
|
+
// Get events to send and clear buffer
|
|
131
|
+
const eventsToSend = [...this.events];
|
|
55
132
|
this.events = [];
|
|
56
|
-
const
|
|
57
|
-
events,
|
|
133
|
+
const payload = {
|
|
134
|
+
events: eventsToSend,
|
|
58
135
|
sessionId: this.sessionId,
|
|
59
136
|
timestamp: new Date().toISOString(),
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
137
|
+
eventCount: eventsToSend.length,
|
|
138
|
+
};
|
|
139
|
+
try {
|
|
140
|
+
if (forceSynchronous && navigator.sendBeacon) {
|
|
141
|
+
// Use sendBeacon for unload events (more reliable)
|
|
142
|
+
const blob = new Blob([JSON.stringify(payload)], {
|
|
143
|
+
type: "application/json",
|
|
144
|
+
});
|
|
145
|
+
const sent = navigator.sendBeacon(baseUrl, blob);
|
|
146
|
+
if (!sent) {
|
|
147
|
+
console.warn("sendBeacon failed, events may be lost");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Regular fetch with keepalive
|
|
152
|
+
const response = await fetch(baseUrl, {
|
|
153
|
+
method: "POST",
|
|
154
|
+
headers: {
|
|
155
|
+
"Content-Type": "application/json",
|
|
156
|
+
},
|
|
157
|
+
body: JSON.stringify(payload),
|
|
158
|
+
keepalive: true, // Important for unload scenarios
|
|
159
|
+
});
|
|
160
|
+
if (!response.ok) {
|
|
161
|
+
throw new Error(`Failed to save replay events: ${response.status} ${response.statusText}`);
|
|
162
|
+
}
|
|
163
|
+
console.log(`Successfully saved ${eventsToSend.length} replay events`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
68
167
|
console.error("Failed to save replay events:", error);
|
|
69
|
-
this.events = [...
|
|
70
|
-
|
|
168
|
+
this.events = [...eventsToSend, ...this.events];
|
|
169
|
+
if (this.events.length > MAX_RETRY_EVENTS) {
|
|
170
|
+
this.events = this.events.slice(-MAX_RETRY_EVENTS);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
this.isSaving = false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Stop recording and clean up
|
|
179
|
+
*/
|
|
180
|
+
stop() {
|
|
181
|
+
this.save(true);
|
|
182
|
+
if (this.stopRecording) {
|
|
183
|
+
this.stopRecording();
|
|
184
|
+
this.stopRecording = undefined;
|
|
185
|
+
}
|
|
186
|
+
if (this.saveInterval) {
|
|
187
|
+
clearInterval(this.saveInterval);
|
|
188
|
+
this.saveInterval = null;
|
|
189
|
+
}
|
|
190
|
+
window.removeEventListener("beforeunload", this.handleUnload);
|
|
191
|
+
window.removeEventListener("pagehide", this.handleUnload);
|
|
192
|
+
document.removeEventListener("visibilitychange", this.handleVisibilityChange);
|
|
193
|
+
this.isConfigured = false;
|
|
194
|
+
console.log("MonoscopeReplay stopped");
|
|
195
|
+
}
|
|
196
|
+
getEventCount() {
|
|
197
|
+
return this.events.length;
|
|
198
|
+
}
|
|
199
|
+
getSessionId() {
|
|
200
|
+
return this.sessionId;
|
|
201
|
+
}
|
|
202
|
+
isRecording() {
|
|
203
|
+
return this.isConfigured && this.stopRecording !== null;
|
|
71
204
|
}
|
|
72
205
|
}
|