userlens-session-recorder 2.0.1 → 2.1.0

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,25 +1,49 @@
1
+ import { eventWithTime } from 'rrweb';
2
+
1
3
  type MaskingOption = "passwords" | "all";
4
+ type RecorderMode = "auto" | "manual";
2
5
  interface SessionRecordingOptions {
3
6
  TIMEOUT?: number;
4
7
  BUFFER_SIZE?: number;
5
8
  maskingOptions?: MaskingOption[];
6
9
  recordCrossOriginIframes?: boolean;
7
10
  }
8
- interface SessionRecorderConfig {
11
+ interface EventBatch {
12
+ session_uuid: string;
13
+ chunk_seq: number;
14
+ chunk_start_ts: string;
15
+ chunk_end_ts: string;
16
+ initial_url?: string;
17
+ events: eventWithTime[];
18
+ keepalive?: boolean;
19
+ }
20
+ type OnEventsCallback = (batch: EventBatch) => void | Promise<void>;
21
+ interface AutoModeConfig {
22
+ mode?: "auto";
9
23
  WRITE_CODE: string;
10
24
  userId: string;
11
25
  recordingOptions?: SessionRecordingOptions;
12
26
  debug?: boolean;
13
27
  }
28
+ interface ManualModeConfig {
29
+ mode: "manual";
30
+ onEvents: OnEventsCallback;
31
+ recordingOptions?: SessionRecordingOptions;
32
+ debug?: boolean;
33
+ }
34
+ type SessionRecorderConfig = AutoModeConfig | ManualModeConfig;
14
35
 
15
36
  declare class SessionRecorder {
16
37
  #private;
38
+ private mode;
17
39
  private userId?;
40
+ private onEvents?;
18
41
  private TIMEOUT;
19
42
  private BUFFER_SIZE;
20
43
  private maskingOptions;
21
44
  private sessionUuid;
22
45
  private sessionEvents;
46
+ private bufferBytes;
23
47
  private rrwebStop;
24
48
  private debug;
25
49
  private recordCrossOriginIframes;
@@ -29,4 +53,4 @@ declare class SessionRecorder {
29
53
  }
30
54
 
31
55
  export { SessionRecorder as default };
32
- export type { MaskingOption, SessionRecorderConfig };
56
+ export type { AutoModeConfig, EventBatch, ManualModeConfig, MaskingOption, OnEventsCallback, RecorderMode, SessionRecorderConfig };
@@ -20675,66 +20675,81 @@
20675
20675
  return "ok";
20676
20676
  };
20677
20677
 
20678
- var _SessionRecorder_instances, _SessionRecorder_trackEventsThrottled, _SessionRecorder_uploading, _SessionRecorder_uploadingMaxTs, _SessionRecorder_log, _SessionRecorder_initRecorder, _SessionRecorder_isUserInteraction, _SessionRecorder_handleEvent, _SessionRecorder_resetSession, _SessionRecorder_createSession, _SessionRecorder_handlePageHide, _SessionRecorder_initListeners, _SessionRecorder_throttle, _SessionRecorder_trackEvents, _SessionRecorder_clearEvents;
20678
+ var _SessionRecorder_instances, _SessionRecorder_uploading, _SessionRecorder_uploadingMaxTs, _SessionRecorder_bufferTimer, _SessionRecorder_log, _SessionRecorder_initRecorder, _SessionRecorder_isUserInteraction, _SessionRecorder_handleEvent, _SessionRecorder_armBufferTimer, _SessionRecorder_flushBuffer, _SessionRecorder_resetSession, _SessionRecorder_createSession, _SessionRecorder_handlePageHide, _SessionRecorder_initListeners, _SessionRecorder_trackEvents, _SessionRecorder_clearEvents;
20679
+ const BUFFER_TIMEOUT_MS = 2000;
20680
+ const MAX_BUFFER_BYTES = 900 * 1024;
20681
+ function estimateEventSize(event) {
20682
+ try {
20683
+ return JSON.stringify(event).length;
20684
+ }
20685
+ catch {
20686
+ return 0;
20687
+ }
20688
+ }
20679
20689
  class SessionRecorder {
20680
20690
  constructor(config) {
20681
20691
  var _a, _b, _c;
20682
20692
  _SessionRecorder_instances.add(this);
20683
20693
  this.sessionEvents = [];
20694
+ this.bufferBytes = 0;
20684
20695
  this.rrwebStop = null;
20685
20696
  this.debug = false;
20686
- _SessionRecorder_trackEventsThrottled.set(this, void 0);
20687
20697
  _SessionRecorder_uploading.set(this, false);
20688
20698
  _SessionRecorder_uploadingMaxTs.set(this, 0);
20699
+ _SessionRecorder_bufferTimer.set(this, null);
20689
20700
  _SessionRecorder_handlePageHide.set(this, () => {
20690
20701
  try {
20691
20702
  if (this.sessionEvents.length === 0)
20692
20703
  return;
20693
- let toUpload;
20704
+ let events;
20694
20705
  if (__classPrivateFieldGet(this, _SessionRecorder_uploading, "f") && __classPrivateFieldGet(this, _SessionRecorder_uploadingMaxTs, "f") > 0) {
20695
- toUpload = this.sessionEvents.filter((e) => e.timestamp > __classPrivateFieldGet(this, _SessionRecorder_uploadingMaxTs, "f"));
20706
+ events = this.sessionEvents.filter((e) => e.timestamp > __classPrivateFieldGet(this, _SessionRecorder_uploadingMaxTs, "f"));
20696
20707
  }
20697
20708
  else {
20698
- toUpload = [...this.sessionEvents];
20709
+ events = [...this.sessionEvents];
20699
20710
  }
20700
- if (toUpload.length === 0)
20711
+ if (events.length === 0)
20701
20712
  return;
20702
- let events;
20703
- if (toUpload[0].type !== 4) {
20704
- let snapshotPair = [];
20705
- for (let i = this.sessionEvents.length - 2; i >= 0; i--) {
20706
- if (this.sessionEvents[i].type === 4 &&
20707
- this.sessionEvents[i + 1].type === 2) {
20708
- snapshotPair = [
20709
- this.sessionEvents[i],
20710
- this.sessionEvents[i + 1],
20711
- ];
20712
- break;
20713
- }
20714
- }
20715
- events = [...snapshotPair, ...toUpload];
20716
- }
20717
- else {
20718
- events = toUpload;
20719
- }
20720
20713
  const state = readSessionState();
20721
20714
  if (!state)
20722
20715
  return;
20723
20716
  const chunk_seq = __classPrivateFieldGet(this, _SessionRecorder_uploading, "f") ? state.chunk_seq + 1 : state.chunk_seq;
20724
20717
  writeSessionState({ ...state, chunk_seq: chunk_seq + 1 });
20725
- const start_ts_ms = toUpload[0].timestamp;
20726
- const end_ts_ms = toUpload[toUpload.length - 1].timestamp;
20727
- uploadSessionEvents({
20728
- user_id: this.userId,
20729
- session_uuid: this.sessionUuid,
20730
- chunk_seq,
20731
- chunk_start_ts: new Date(start_ts_ms).toISOString(),
20732
- chunk_end_ts: new Date(end_ts_ms).toISOString(),
20733
- initial_url: chunk_seq === 0 ? state.initial_url : undefined,
20734
- events,
20735
- keepalive: true,
20736
- }).catch(() => { });
20718
+ const start_ts_ms = events[0].timestamp;
20719
+ const end_ts_ms = events[events.length - 1].timestamp;
20720
+ const initial_url = chunk_seq === 0 ? state.initial_url : undefined;
20721
+ const chunk_start_ts = new Date(start_ts_ms).toISOString();
20722
+ const chunk_end_ts = new Date(end_ts_ms).toISOString();
20723
+ if (this.mode === "manual") {
20724
+ try {
20725
+ void this.onEvents({
20726
+ session_uuid: this.sessionUuid,
20727
+ chunk_seq,
20728
+ chunk_start_ts,
20729
+ chunk_end_ts,
20730
+ initial_url,
20731
+ events,
20732
+ keepalive: true,
20733
+ });
20734
+ }
20735
+ catch {
20736
+ // ignore
20737
+ }
20738
+ }
20739
+ else {
20740
+ uploadSessionEvents({
20741
+ user_id: this.userId,
20742
+ session_uuid: this.sessionUuid,
20743
+ chunk_seq,
20744
+ chunk_start_ts,
20745
+ chunk_end_ts,
20746
+ initial_url,
20747
+ events,
20748
+ keepalive: true,
20749
+ }).catch(() => { });
20750
+ }
20737
20751
  this.sessionEvents = [];
20752
+ this.bufferBytes = 0;
20738
20753
  }
20739
20754
  catch (err) {
20740
20755
  __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Page hide handling failed", err);
@@ -20761,21 +20776,29 @@
20761
20776
  sessionStorage.setItem(testKey, "1");
20762
20777
  sessionStorage.removeItem(testKey);
20763
20778
  this.debug = (_a = config.debug) !== null && _a !== void 0 ? _a : false;
20764
- if (!((_b = config.WRITE_CODE) === null || _b === void 0 ? void 0 : _b.trim()) || !((_c = config.userId) === null || _c === void 0 ? void 0 : _c.trim())) {
20765
- return;
20779
+ if (config.mode === "manual") {
20780
+ this.mode = "manual";
20781
+ if (!config.onEvents || typeof config.onEvents !== "function") {
20782
+ __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "onEvents callback is required in manual mode");
20783
+ return;
20784
+ }
20785
+ this.onEvents = config.onEvents;
20786
+ }
20787
+ else {
20788
+ this.mode = "auto";
20789
+ if (!((_b = config.WRITE_CODE) === null || _b === void 0 ? void 0 : _b.trim()) || !((_c = config.userId) === null || _c === void 0 ? void 0 : _c.trim())) {
20790
+ return;
20791
+ }
20792
+ saveWriteCode(config.WRITE_CODE);
20793
+ this.userId = config.userId;
20766
20794
  }
20767
- saveWriteCode(config.WRITE_CODE);
20768
- this.userId = config.userId;
20769
20795
  const { recordingOptions = {} } = config;
20770
- const { TIMEOUT = 30 * 60 * 1000, BUFFER_SIZE = 10, maskingOptions = ["passwords"], recordCrossOriginIframes = false, } = recordingOptions;
20796
+ const { TIMEOUT = 30 * 60 * 1000, BUFFER_SIZE = 30, maskingOptions = ["passwords"], recordCrossOriginIframes = false, } = recordingOptions;
20771
20797
  this.TIMEOUT = TIMEOUT;
20772
20798
  this.BUFFER_SIZE = BUFFER_SIZE;
20773
20799
  this.maskingOptions = maskingOptions;
20774
20800
  this.recordCrossOriginIframes = recordCrossOriginIframes;
20775
20801
  this.sessionEvents = [];
20776
- __classPrivateFieldSet(this, _SessionRecorder_trackEventsThrottled, __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_throttle).call(this, () => {
20777
- __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_trackEvents).call(this);
20778
- }, 5000), "f");
20779
20802
  __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_initRecorder).call(this);
20780
20803
  }
20781
20804
  catch (err) {
@@ -20802,7 +20825,7 @@
20802
20825
  }
20803
20826
  }
20804
20827
  }
20805
- _SessionRecorder_trackEventsThrottled = new WeakMap(), _SessionRecorder_uploading = new WeakMap(), _SessionRecorder_uploadingMaxTs = new WeakMap(), _SessionRecorder_handlePageHide = new WeakMap(), _SessionRecorder_instances = new WeakSet(), _SessionRecorder_log = function _SessionRecorder_log(message, error) {
20828
+ _SessionRecorder_uploading = new WeakMap(), _SessionRecorder_uploadingMaxTs = new WeakMap(), _SessionRecorder_bufferTimer = new WeakMap(), _SessionRecorder_handlePageHide = new WeakMap(), _SessionRecorder_instances = new WeakSet(), _SessionRecorder_log = function _SessionRecorder_log(message, error) {
20806
20829
  if (!this.debug)
20807
20830
  return;
20808
20831
  if (error) {
@@ -20825,6 +20848,7 @@
20825
20848
  },
20826
20849
  recordCrossOriginIframes: this.recordCrossOriginIframes,
20827
20850
  plugins: [getRecordConsolePlugin()],
20851
+ checkoutEveryNth: 100,
20828
20852
  });
20829
20853
  __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_initListeners).call(this);
20830
20854
  }, _SessionRecorder_isUserInteraction = function _SessionRecorder_isUserInteraction(event) {
@@ -20835,7 +20859,6 @@
20835
20859
  }
20836
20860
  return false;
20837
20861
  }, _SessionRecorder_handleEvent = function _SessionRecorder_handleEvent(event, _isCheckout) {
20838
- var _a;
20839
20862
  try {
20840
20863
  const now = Date.now();
20841
20864
  const state = readSessionState();
@@ -20850,13 +20873,29 @@
20850
20873
  }
20851
20874
  }
20852
20875
  this.sessionEvents.push(event);
20853
- if (this.sessionEvents.length >= this.BUFFER_SIZE) {
20854
- (_a = __classPrivateFieldGet(this, _SessionRecorder_trackEventsThrottled, "f")) === null || _a === void 0 ? void 0 : _a.call(this);
20876
+ this.bufferBytes += estimateEventSize(event);
20877
+ __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_armBufferTimer).call(this);
20878
+ if (this.bufferBytes >= MAX_BUFFER_BYTES) {
20879
+ __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_flushBuffer).call(this);
20855
20880
  }
20856
20881
  }
20857
20882
  catch (err) {
20858
20883
  __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Event handling failed", err);
20859
20884
  }
20885
+ }, _SessionRecorder_armBufferTimer = function _SessionRecorder_armBufferTimer() {
20886
+ if (__classPrivateFieldGet(this, _SessionRecorder_bufferTimer, "f") !== null) {
20887
+ clearTimeout(__classPrivateFieldGet(this, _SessionRecorder_bufferTimer, "f"));
20888
+ }
20889
+ __classPrivateFieldSet(this, _SessionRecorder_bufferTimer, setTimeout(() => {
20890
+ __classPrivateFieldSet(this, _SessionRecorder_bufferTimer, null, "f");
20891
+ __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_flushBuffer).call(this);
20892
+ }, BUFFER_TIMEOUT_MS), "f");
20893
+ }, _SessionRecorder_flushBuffer = function _SessionRecorder_flushBuffer() {
20894
+ if (__classPrivateFieldGet(this, _SessionRecorder_bufferTimer, "f") !== null) {
20895
+ clearTimeout(__classPrivateFieldGet(this, _SessionRecorder_bufferTimer, "f"));
20896
+ __classPrivateFieldSet(this, _SessionRecorder_bufferTimer, null, "f");
20897
+ }
20898
+ void __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_trackEvents).call(this);
20860
20899
  }, _SessionRecorder_resetSession = function _SessionRecorder_resetSession() {
20861
20900
  clearSessionState();
20862
20901
  __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_clearEvents).call(this);
@@ -20881,15 +20920,6 @@
20881
20920
  }
20882
20921
  }, _SessionRecorder_initListeners = function _SessionRecorder_initListeners() {
20883
20922
  window.addEventListener("pagehide", __classPrivateFieldGet(this, _SessionRecorder_handlePageHide, "f"));
20884
- }, _SessionRecorder_throttle = function _SessionRecorder_throttle(func, delay) {
20885
- let lastCall = 0;
20886
- return (...args) => {
20887
- const now = Date.now();
20888
- if (now - lastCall >= delay) {
20889
- lastCall = now;
20890
- func.apply(this, args);
20891
- }
20892
- };
20893
20923
  }, _SessionRecorder_trackEvents = async function _SessionRecorder_trackEvents() {
20894
20924
  if (__classPrivateFieldGet(this, _SessionRecorder_uploading, "f"))
20895
20925
  return;
@@ -20908,26 +20938,33 @@
20908
20938
  return;
20909
20939
  }
20910
20940
  const chunk_seq = state.chunk_seq;
20911
- await uploadSessionEvents({
20912
- user_id: this.userId,
20913
- session_uuid: this.sessionUuid,
20914
- chunk_seq,
20915
- chunk_start_ts: new Date(start_ts_ms).toISOString(),
20916
- chunk_end_ts: new Date(end_ts_ms).toISOString(),
20917
- initial_url: chunk_seq === 0 ? state.initial_url : undefined,
20918
- events,
20919
- });
20920
- const remaining = this.sessionEvents.slice(snapshot_count);
20921
- this.sessionEvents = [];
20922
- try {
20923
- takeFullSnapshot(true);
20924
- }
20925
- catch (err) {
20926
- __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "takeFullSnapshot failed", err);
20941
+ const initial_url = chunk_seq === 0 ? state.initial_url : undefined;
20942
+ const chunk_start_ts = new Date(start_ts_ms).toISOString();
20943
+ const chunk_end_ts = new Date(end_ts_ms).toISOString();
20944
+ if (this.mode === "manual") {
20945
+ await this.onEvents({
20946
+ session_uuid: this.sessionUuid,
20947
+ chunk_seq,
20948
+ chunk_start_ts,
20949
+ chunk_end_ts,
20950
+ initial_url,
20951
+ events,
20952
+ });
20927
20953
  }
20928
- if (remaining.length > 0) {
20929
- this.sessionEvents.push(...remaining);
20954
+ else {
20955
+ await uploadSessionEvents({
20956
+ user_id: this.userId,
20957
+ session_uuid: this.sessionUuid,
20958
+ chunk_seq,
20959
+ chunk_start_ts,
20960
+ chunk_end_ts,
20961
+ initial_url,
20962
+ events,
20963
+ });
20930
20964
  }
20965
+ const removedBytes = events.reduce((sum, e) => sum + estimateEventSize(e), 0);
20966
+ this.sessionEvents = this.sessionEvents.slice(snapshot_count);
20967
+ this.bufferBytes = Math.max(0, this.bufferBytes - removedBytes);
20931
20968
  const after = readSessionState();
20932
20969
  if (after &&
20933
20970
  after.session_uuid === state.session_uuid &&
@@ -20944,6 +20981,11 @@
20944
20981
  }
20945
20982
  }, _SessionRecorder_clearEvents = function _SessionRecorder_clearEvents() {
20946
20983
  this.sessionEvents = [];
20984
+ this.bufferBytes = 0;
20985
+ if (__classPrivateFieldGet(this, _SessionRecorder_bufferTimer, "f") !== null) {
20986
+ clearTimeout(__classPrivateFieldGet(this, _SessionRecorder_bufferTimer, "f"));
20987
+ __classPrivateFieldSet(this, _SessionRecorder_bufferTimer, null, "f");
20988
+ }
20947
20989
  };
20948
20990
 
20949
20991
  exports.default = SessionRecorder;