@monoscopetech/browser 0.5.5 → 0.5.7

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