mixpanel-browser 2.67.0 → 2.69.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.
package/src/index.d.ts CHANGED
@@ -40,6 +40,17 @@ export interface OutTrackingOptions extends ClearOptOutInOutOptions {
40
40
  delete_user: boolean;
41
41
  }
42
42
 
43
+ export type RageClickConfig =
44
+ | boolean
45
+ | {
46
+ /** Distance threshold in pixels for clicks to be considered within the same area (default: 30) */
47
+ threshold_px?: number;
48
+ /** Time window in milliseconds for clicks to be considered rapid (default: 1000) */
49
+ timeout_ms?: number;
50
+ /** Number of clicks required to trigger a rage click event (default: 3) */
51
+ click_count?: number;
52
+ };
53
+
43
54
  export interface RegisterOptions {
44
55
  persistent: boolean;
45
56
  }
@@ -66,6 +77,12 @@ export interface AutocaptureConfig {
66
77
  * @default 'full-url'
67
78
  */
68
79
  pageview?: TrackPageView;
80
+ /**
81
+ * When set to `true`, Mixpanel will track rage clicks (multiple clicks in a short time on the same element).
82
+ * Can also be configured as an object to customize rage click detection parameters.
83
+ * @default true
84
+ */
85
+ rage_click?: RageClickConfig;
69
86
  /**
70
87
  * When set, Mixpanel will collect page scrolls at specified scroll intervals.
71
88
  * @default true
@@ -181,9 +198,11 @@ export interface Config {
181
198
  record_inline_images: boolean;
182
199
  record_mask_text_class: string;
183
200
  record_mask_text_selector: string;
201
+ record_min_ms: number;
184
202
  record_max_ms: number;
185
203
  record_sessions_percent: number;
186
204
  record_canvas: boolean;
205
+ record_heatmap_data: boolean;
187
206
  }
188
207
 
189
208
  export type VerboseResponse =
@@ -149,7 +149,7 @@ var DEFAULT_CONFIG = {
149
149
  'batch_autostart': true,
150
150
  'hooks': {},
151
151
  'record_block_class': new RegExp('^(mp-block|fs-exclude|amp-block|rr-block|ph-no-capture)$'),
152
- 'record_block_selector': 'img, video',
152
+ 'record_block_selector': 'img, video, audio',
153
153
  'record_canvas': false,
154
154
  'record_collect_fonts': false,
155
155
  'record_heatmap_data': false,
@@ -373,6 +373,7 @@ MixpanelLib.prototype._init = function(token, config, name) {
373
373
  return this.get_api_host('flags') + '/' + this.get_config('api_routes')['flags'];
374
374
  }, this),
375
375
  getConfigFunc: _.bind(this.get_config, this),
376
+ setConfigFunc: _.bind(this.set_config, this),
376
377
  getPropertyFunc: _.bind(this.get_property, this),
377
378
  trackingFunc: _.bind(this.track, this)
378
379
  });
@@ -391,7 +392,9 @@ MixpanelLib.prototype._init = function(token, config, name) {
391
392
  * This is primarily used for session recording, where data must be isolated to the current tab.
392
393
  */
393
394
  MixpanelLib.prototype._init_tab_id = function() {
394
- if (_.sessionStorage.is_supported()) {
395
+ if (this.get_config('disable_persistence')) {
396
+ console.log('Tab ID initialization skipped due to disable_persistence config');
397
+ } else if (_.sessionStorage.is_supported()) {
395
398
  try {
396
399
  var key_suffix = this.get_config('name') + '_' + this.get_config('token');
397
400
  var tab_id_key = 'mp_tab_id_' + key_suffix;
@@ -425,6 +428,11 @@ MixpanelLib.prototype.get_tab_id = function () {
425
428
  };
426
429
 
427
430
  MixpanelLib.prototype._should_load_recorder = function () {
431
+ if (this.get_config('disable_persistence')) {
432
+ console.log('Load recorder check skipped due to disable_persistence config');
433
+ return Promise.resolve(false);
434
+ }
435
+
428
436
  var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
429
437
  var tab_id = this.get_tab_id();
430
438
  return recording_registry_idb.init()
@@ -1,4 +1,4 @@
1
- import { record } from 'rrweb';
1
+ import { record } from '@mixpanel/rrweb';
2
2
  import { Promise } from '../promise-polyfill';
3
3
  import { SessionRecording } from './session-recording';
4
4
  import { RecordingRegistry } from './recording-registry';
@@ -8,12 +8,17 @@ import { isRecordingExpired } from './utils';
8
8
  * Makes sure that only one tab can be recording at a time.
9
9
  */
10
10
  var RecordingRegistry = function (options) {
11
+ /** @type {IDBStorageWrapper} */
11
12
  this.idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
12
13
  this.errorReporter = options.errorReporter;
13
14
  this.mixpanelInstance = options.mixpanelInstance;
14
15
  this.sharedLockStorage = options.sharedLockStorage;
15
16
  };
16
17
 
18
+ RecordingRegistry.prototype.isPersistenceEnabled = function() {
19
+ return !this.mixpanelInstance.get_config('disable_persistence');
20
+ };
21
+
17
22
  RecordingRegistry.prototype.handleError = function (err) {
18
23
  this.errorReporter('IndexedDB error: ', err);
19
24
  };
@@ -22,6 +27,10 @@ RecordingRegistry.prototype.handleError = function (err) {
22
27
  * @param {import('./session-recording').SerializedRecording} serializedRecording
23
28
  */
24
29
  RecordingRegistry.prototype.setActiveRecording = function (serializedRecording) {
30
+ if (!this.isPersistenceEnabled()) {
31
+ return Promise.resolve();
32
+ }
33
+
25
34
  var tabId = serializedRecording['tabId'];
26
35
  if (!tabId) {
27
36
  console.warn('No tab ID is set, cannot persist recording metadata.');
@@ -39,6 +48,10 @@ RecordingRegistry.prototype.setActiveRecording = function (serializedRecording)
39
48
  * @returns {Promise<import('./session-recording').SerializedRecording>}
40
49
  */
41
50
  RecordingRegistry.prototype.getActiveRecording = function () {
51
+ if (!this.isPersistenceEnabled()) {
52
+ return Promise.resolve(null);
53
+ }
54
+
42
55
  return this.idb.init()
43
56
  .then(function () {
44
57
  return this.idb.getItem(this.mixpanelInstance.get_tab_id());
@@ -50,8 +63,16 @@ RecordingRegistry.prototype.getActiveRecording = function () {
50
63
  };
51
64
 
52
65
  RecordingRegistry.prototype.clearActiveRecording = function () {
53
- // mark recording as expired instead of deleting it in case the page unloads mid-flush and doesn't make it to ingestion.
54
- // this will ensure the next pageload will flush the remaining events, but not try to continue the recording.
66
+ if (this.isPersistenceEnabled()) {
67
+ // mark recording as expired instead of deleting it in case the page unloads mid-flush and doesn't make it to ingestion.
68
+ // this will ensure the next pageload will flush the remaining events, but not try to continue the recording.
69
+ return this.markActiveRecordingExpired();
70
+ } else {
71
+ return this.deleteActiveRecording();
72
+ }
73
+ };
74
+
75
+ RecordingRegistry.prototype.markActiveRecordingExpired = function () {
55
76
  return this.getActiveRecording()
56
77
  .then(function (serializedRecording) {
57
78
  if (serializedRecording) {
@@ -62,11 +83,25 @@ RecordingRegistry.prototype.clearActiveRecording = function () {
62
83
  .catch(this.handleError.bind(this));
63
84
  };
64
85
 
86
+ RecordingRegistry.prototype.deleteActiveRecording = function () {
87
+ // avoid initializing IDB if this registry instance hasn't already written a recording
88
+ if (this.idb.isInitialized()) {
89
+ return this.idb.removeItem(this.mixpanelInstance.get_tab_id())
90
+ .catch(this.handleError.bind(this));
91
+ } else {
92
+ return Promise.resolve();
93
+ }
94
+ };
95
+
65
96
  /**
66
97
  * Flush any inactive recordings from the registry to minimize data loss.
67
98
  * The main idea here is that we can flush remaining rrweb events on the next page load if a tab is closed mid-batch.
68
99
  */
69
100
  RecordingRegistry.prototype.flushInactiveRecordings = function () {
101
+ if (!this.isPersistenceEnabled()) {
102
+ return Promise.resolve([]);
103
+ }
104
+
70
105
  return this.idb.init()
71
106
  .then(function() {
72
107
  return this.idb.getAll();
@@ -1,5 +1,5 @@
1
1
  import { window } from '../window';
2
- import { IncrementalSource, EventType } from 'rrweb';
2
+ import { IncrementalSource, EventType } from '@mixpanel/rrweb';
3
3
  import { MAX_RECORDING_MS, MAX_VALUE_FOR_MIN_RECORDING_MS, console_with_prefix, NOOP_FUNC, _, localStorageSupported} from '../utils'; // eslint-disable-line camelcase
4
4
  import { IDBStorageWrapper, RECORDING_EVENTS_STORE_NAME } from '../storage/indexed-db';
5
5
  import { addOptOutCheckMixpanelLib } from '../gdpr-utils';
@@ -101,10 +101,9 @@ var SessionRecording = function(options) {
101
101
 
102
102
  // disable persistence if localStorage is not supported
103
103
  // request-queue will automatically disable persistence if indexedDB fails to initialize
104
- var usePersistence = localStorageSupported(options.sharedLockStorage, true);
104
+ var usePersistence = localStorageSupported(options.sharedLockStorage, true) && !this.getConfig('disable_persistence');
105
105
 
106
106
  // each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
107
- // this will be important when persistence is introduced
108
107
  this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
109
108
  this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
110
109
  this.batcher = new RequestBatcher(this.batcherKey, {
@@ -50,7 +50,7 @@ var RequestQueue = function (storageKey, options) {
50
50
  };
51
51
 
52
52
  RequestQueue.prototype.ensureInit = function () {
53
- if (this.initialized) {
53
+ if (this.initialized || !this.usePersistence) {
54
54
  return Promise.resolve();
55
55
  }
56
56
 
@@ -61,6 +61,10 @@ IDBStorageWrapper.prototype.init = function () {
61
61
  });
62
62
  };
63
63
 
64
+ IDBStorageWrapper.prototype.isInitialized = function () {
65
+ return !!this.dbPromise;
66
+ };
67
+
64
68
  /**
65
69
  * @param {IDBTransactionMode} mode
66
70
  * @param {function(IDBObjectStore): void} storeCb
@@ -13,6 +13,10 @@ LocalStorageWrapper.prototype.init = function () {
13
13
  return Promise.resolve();
14
14
  };
15
15
 
16
+ LocalStorageWrapper.prototype.isInitialized = function () {
17
+ return true;
18
+ };
19
+
16
20
  LocalStorageWrapper.prototype.setItem = function (key, value) {
17
21
  return new Promise(_.bind(function (resolve, reject) {
18
22
  try {
@@ -6,6 +6,7 @@
6
6
  /**
7
7
  * @typedef {Object} StorageWrapper
8
8
  * @property {function():Promise<void>} init - Initializes the wrapper, async storage like IDB needs to create a table and upgrade if needed.
9
+ * @property {function():boolean} isInitialized - Checks if the storage is initialized.
9
10
  * @property {function(string, any):Promise<void>} setItem - Sets an item in storage.
10
11
  * @property {function(string):Promise<any>} getItem - Retrieves an item from storage.
11
12
  * @property {function(string):Promise<void>} removeItem - Removes an item from storage.