@neroom/nevision 0.1.7 → 0.1.9

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/index.js CHANGED
@@ -37,7 +37,7 @@ __export(index_exports, {
37
37
  module.exports = __toCommonJS(index_exports);
38
38
  var import_react = require("react");
39
39
  var DEFAULT_API_URL = "https://api.ne-room.io";
40
- var CHUNK_INTERVAL = 1e4;
40
+ var CHUNK_INTERVAL = 2e3;
41
41
  var MAX_EVENTS_PER_CHUNK = 100;
42
42
  var DB_NAME = "nevision_recordings";
43
43
  var STORE_NAME = "pending_events";
@@ -173,7 +173,6 @@ function NevisionRecorder({
173
173
  const recordConfig = {
174
174
  emit: (event) => {
175
175
  eventsBufferRef.current.push(event);
176
- persistEventsToStorage();
177
176
  },
178
177
  sampling: currentSampling ? {
179
178
  mousemove: currentSampling.mousemove,
@@ -197,6 +196,36 @@ function NevisionRecorder({
197
196
  if (stopFn) {
198
197
  stopFnRef.current = stopFn;
199
198
  }
199
+ const emitPageLifecycleEvent = (eventName) => {
200
+ const customEvent = {
201
+ type: 5,
202
+ // Custom event type in rrweb
203
+ data: {
204
+ tag: "page_lifecycle",
205
+ payload: {
206
+ event: eventName
207
+ }
208
+ },
209
+ timestamp: Date.now()
210
+ };
211
+ eventsBufferRef.current.push(customEvent);
212
+ };
213
+ const handleVisibilityChangeForTimeline = () => {
214
+ if (document.visibilityState === "hidden") {
215
+ emitPageLifecycleEvent("page_hidden");
216
+ } else {
217
+ emitPageLifecycleEvent("page_visible");
218
+ }
219
+ };
220
+ const handleWindowFocusForTimeline = () => {
221
+ emitPageLifecycleEvent("page_focus");
222
+ };
223
+ const handleWindowBlurForTimeline = () => {
224
+ emitPageLifecycleEvent("page_blur");
225
+ };
226
+ document.addEventListener("visibilitychange", handleVisibilityChangeForTimeline);
227
+ window.addEventListener("focus", handleWindowFocusForTimeline);
228
+ window.addEventListener("blur", handleWindowBlurForTimeline);
200
229
  intervalId = setInterval(() => {
201
230
  sendChunk(apiUrl, sessionId);
202
231
  }, CHUNK_INTERVAL);
@@ -211,44 +240,30 @@ function NevisionRecorder({
211
240
  endSession(apiUrl, sessionIdRef.current);
212
241
  }
213
242
  };
214
- document.addEventListener("visibilitychange", () => {
243
+ const handleVisibilityChange = () => {
215
244
  if (document.visibilityState === "hidden") {
216
245
  handleFinalSync(false);
217
246
  }
218
- });
219
- window.addEventListener("beforeunload", () => {
247
+ };
248
+ const handleBeforeUnload = () => {
220
249
  handleFinalSync(true);
221
- });
222
- window.addEventListener("pagehide", (e) => {
250
+ };
251
+ const handlePageHide = (e) => {
223
252
  handleFinalSync(!e.persisted);
224
- });
253
+ };
254
+ const handleWindowBlur = () => {
255
+ if (eventsBufferRef.current.length > 0 && sessionIdRef.current) {
256
+ sendChunk(apiUrl, sessionIdRef.current, true);
257
+ }
258
+ };
259
+ document.addEventListener("visibilitychange", handleVisibilityChange);
260
+ window.addEventListener("beforeunload", handleBeforeUnload);
261
+ window.addEventListener("pagehide", handlePageHide);
262
+ window.addEventListener("blur", handleWindowBlur);
225
263
  } catch (error) {
226
264
  onErrorRef.current?.(error instanceof Error ? error : new Error(String(error)));
227
265
  }
228
266
  };
229
- let persistTimeout = null;
230
- const persistEventsToStorage = () => {
231
- if (persistTimeout) return;
232
- persistTimeout = setTimeout(async () => {
233
- persistTimeout = null;
234
- if (!sessionIdRef.current || !eventStoreRef.current) return;
235
- if (eventsBufferRef.current.length === 0) return;
236
- const events = [...eventsBufferRef.current];
237
- const chunkId = `${sessionIdRef.current}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
238
- try {
239
- await eventStoreRef.current.saveChunk({
240
- id: chunkId,
241
- sessionId: sessionIdRef.current,
242
- siteId,
243
- apiKey,
244
- events,
245
- chunkIndex: chunkIndexRef.current,
246
- timestamp: Date.now()
247
- });
248
- } catch {
249
- }
250
- }, 1e3);
251
- };
252
267
  const retrySendPendingChunks = async (url) => {
253
268
  if (!eventStoreRef.current) return;
254
269
  try {
@@ -277,23 +292,8 @@ function NevisionRecorder({
277
292
  };
278
293
  const sendChunk = async (url, sessionId, isBeacon = false) => {
279
294
  if (eventsBufferRef.current.length === 0) return;
280
- const events = eventsBufferRef.current.splice(0, MAX_EVENTS_PER_CHUNK);
295
+ const events = isBeacon ? eventsBufferRef.current.splice(0) : eventsBufferRef.current.splice(0, MAX_EVENTS_PER_CHUNK);
281
296
  const chunkIndex = chunkIndexRef.current++;
282
- const chunkId = `${sessionId}_${chunkIndex}_${Date.now()}`;
283
- if (eventStoreRef.current) {
284
- try {
285
- await eventStoreRef.current.saveChunk({
286
- id: chunkId,
287
- sessionId,
288
- siteId,
289
- apiKey,
290
- events,
291
- chunkIndex,
292
- timestamp: Date.now()
293
- });
294
- } catch {
295
- }
296
- }
297
297
  const payload = JSON.stringify({
298
298
  sessionId,
299
299
  siteId,
@@ -302,15 +302,26 @@ function NevisionRecorder({
302
302
  chunkIndex
303
303
  });
304
304
  if (isBeacon && navigator.sendBeacon) {
305
- const sent = navigator.sendBeacon(
305
+ navigator.sendBeacon(
306
306
  `${url}/public/recordings/chunk`,
307
307
  new Blob([payload], { type: "application/json" })
308
308
  );
309
- if (sent && eventStoreRef.current) {
310
- eventStoreRef.current.deleteChunk(chunkId).catch(() => {
311
- });
312
- }
313
309
  } else {
310
+ const chunkId = `${sessionId}_${chunkIndex}_${Date.now()}`;
311
+ if (eventStoreRef.current) {
312
+ try {
313
+ await eventStoreRef.current.saveChunk({
314
+ id: chunkId,
315
+ sessionId,
316
+ siteId,
317
+ apiKey,
318
+ events,
319
+ chunkIndex,
320
+ timestamp: Date.now()
321
+ });
322
+ } catch {
323
+ }
324
+ }
314
325
  try {
315
326
  const response = await fetch(`${url}/public/recordings/chunk`, {
316
327
  method: "POST",
@@ -339,10 +350,6 @@ function NevisionRecorder({
339
350
  isActive = false;
340
351
  clearInterval(intervalId);
341
352
  stopFnRef.current?.();
342
- if (sessionIdRef.current) {
343
- sendChunk(apiUrl, sessionIdRef.current, true);
344
- endSession(apiUrl, sessionIdRef.current);
345
- }
346
353
  sessionIdRef.current = null;
347
354
  chunkIndexRef.current = 0;
348
355
  eventsBufferRef.current = [];
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/index.tsx
4
4
  import { useEffect, useRef } from "react";
5
5
  var DEFAULT_API_URL = "https://api.ne-room.io";
6
- var CHUNK_INTERVAL = 1e4;
6
+ var CHUNK_INTERVAL = 2e3;
7
7
  var MAX_EVENTS_PER_CHUNK = 100;
8
8
  var DB_NAME = "nevision_recordings";
9
9
  var STORE_NAME = "pending_events";
@@ -139,7 +139,6 @@ function NevisionRecorder({
139
139
  const recordConfig = {
140
140
  emit: (event) => {
141
141
  eventsBufferRef.current.push(event);
142
- persistEventsToStorage();
143
142
  },
144
143
  sampling: currentSampling ? {
145
144
  mousemove: currentSampling.mousemove,
@@ -163,6 +162,36 @@ function NevisionRecorder({
163
162
  if (stopFn) {
164
163
  stopFnRef.current = stopFn;
165
164
  }
165
+ const emitPageLifecycleEvent = (eventName) => {
166
+ const customEvent = {
167
+ type: 5,
168
+ // Custom event type in rrweb
169
+ data: {
170
+ tag: "page_lifecycle",
171
+ payload: {
172
+ event: eventName
173
+ }
174
+ },
175
+ timestamp: Date.now()
176
+ };
177
+ eventsBufferRef.current.push(customEvent);
178
+ };
179
+ const handleVisibilityChangeForTimeline = () => {
180
+ if (document.visibilityState === "hidden") {
181
+ emitPageLifecycleEvent("page_hidden");
182
+ } else {
183
+ emitPageLifecycleEvent("page_visible");
184
+ }
185
+ };
186
+ const handleWindowFocusForTimeline = () => {
187
+ emitPageLifecycleEvent("page_focus");
188
+ };
189
+ const handleWindowBlurForTimeline = () => {
190
+ emitPageLifecycleEvent("page_blur");
191
+ };
192
+ document.addEventListener("visibilitychange", handleVisibilityChangeForTimeline);
193
+ window.addEventListener("focus", handleWindowFocusForTimeline);
194
+ window.addEventListener("blur", handleWindowBlurForTimeline);
166
195
  intervalId = setInterval(() => {
167
196
  sendChunk(apiUrl, sessionId);
168
197
  }, CHUNK_INTERVAL);
@@ -177,44 +206,30 @@ function NevisionRecorder({
177
206
  endSession(apiUrl, sessionIdRef.current);
178
207
  }
179
208
  };
180
- document.addEventListener("visibilitychange", () => {
209
+ const handleVisibilityChange = () => {
181
210
  if (document.visibilityState === "hidden") {
182
211
  handleFinalSync(false);
183
212
  }
184
- });
185
- window.addEventListener("beforeunload", () => {
213
+ };
214
+ const handleBeforeUnload = () => {
186
215
  handleFinalSync(true);
187
- });
188
- window.addEventListener("pagehide", (e) => {
216
+ };
217
+ const handlePageHide = (e) => {
189
218
  handleFinalSync(!e.persisted);
190
- });
219
+ };
220
+ const handleWindowBlur = () => {
221
+ if (eventsBufferRef.current.length > 0 && sessionIdRef.current) {
222
+ sendChunk(apiUrl, sessionIdRef.current, true);
223
+ }
224
+ };
225
+ document.addEventListener("visibilitychange", handleVisibilityChange);
226
+ window.addEventListener("beforeunload", handleBeforeUnload);
227
+ window.addEventListener("pagehide", handlePageHide);
228
+ window.addEventListener("blur", handleWindowBlur);
191
229
  } catch (error) {
192
230
  onErrorRef.current?.(error instanceof Error ? error : new Error(String(error)));
193
231
  }
194
232
  };
195
- let persistTimeout = null;
196
- const persistEventsToStorage = () => {
197
- if (persistTimeout) return;
198
- persistTimeout = setTimeout(async () => {
199
- persistTimeout = null;
200
- if (!sessionIdRef.current || !eventStoreRef.current) return;
201
- if (eventsBufferRef.current.length === 0) return;
202
- const events = [...eventsBufferRef.current];
203
- const chunkId = `${sessionIdRef.current}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
204
- try {
205
- await eventStoreRef.current.saveChunk({
206
- id: chunkId,
207
- sessionId: sessionIdRef.current,
208
- siteId,
209
- apiKey,
210
- events,
211
- chunkIndex: chunkIndexRef.current,
212
- timestamp: Date.now()
213
- });
214
- } catch {
215
- }
216
- }, 1e3);
217
- };
218
233
  const retrySendPendingChunks = async (url) => {
219
234
  if (!eventStoreRef.current) return;
220
235
  try {
@@ -243,23 +258,8 @@ function NevisionRecorder({
243
258
  };
244
259
  const sendChunk = async (url, sessionId, isBeacon = false) => {
245
260
  if (eventsBufferRef.current.length === 0) return;
246
- const events = eventsBufferRef.current.splice(0, MAX_EVENTS_PER_CHUNK);
261
+ const events = isBeacon ? eventsBufferRef.current.splice(0) : eventsBufferRef.current.splice(0, MAX_EVENTS_PER_CHUNK);
247
262
  const chunkIndex = chunkIndexRef.current++;
248
- const chunkId = `${sessionId}_${chunkIndex}_${Date.now()}`;
249
- if (eventStoreRef.current) {
250
- try {
251
- await eventStoreRef.current.saveChunk({
252
- id: chunkId,
253
- sessionId,
254
- siteId,
255
- apiKey,
256
- events,
257
- chunkIndex,
258
- timestamp: Date.now()
259
- });
260
- } catch {
261
- }
262
- }
263
263
  const payload = JSON.stringify({
264
264
  sessionId,
265
265
  siteId,
@@ -268,15 +268,26 @@ function NevisionRecorder({
268
268
  chunkIndex
269
269
  });
270
270
  if (isBeacon && navigator.sendBeacon) {
271
- const sent = navigator.sendBeacon(
271
+ navigator.sendBeacon(
272
272
  `${url}/public/recordings/chunk`,
273
273
  new Blob([payload], { type: "application/json" })
274
274
  );
275
- if (sent && eventStoreRef.current) {
276
- eventStoreRef.current.deleteChunk(chunkId).catch(() => {
277
- });
278
- }
279
275
  } else {
276
+ const chunkId = `${sessionId}_${chunkIndex}_${Date.now()}`;
277
+ if (eventStoreRef.current) {
278
+ try {
279
+ await eventStoreRef.current.saveChunk({
280
+ id: chunkId,
281
+ sessionId,
282
+ siteId,
283
+ apiKey,
284
+ events,
285
+ chunkIndex,
286
+ timestamp: Date.now()
287
+ });
288
+ } catch {
289
+ }
290
+ }
280
291
  try {
281
292
  const response = await fetch(`${url}/public/recordings/chunk`, {
282
293
  method: "POST",
@@ -305,10 +316,6 @@ function NevisionRecorder({
305
316
  isActive = false;
306
317
  clearInterval(intervalId);
307
318
  stopFnRef.current?.();
308
- if (sessionIdRef.current) {
309
- sendChunk(apiUrl, sessionIdRef.current, true);
310
- endSession(apiUrl, sessionIdRef.current);
311
- }
312
319
  sessionIdRef.current = null;
313
320
  chunkIndexRef.current = 0;
314
321
  eventsBufferRef.current = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neroom/nevision",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "React SDK for NEROOM/NEVISION session recording",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",