mixpanel-browser 2.71.1 → 2.73.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.
Files changed (40) hide show
  1. package/.github/workflows/tests.yml +1 -0
  2. package/CHANGELOG.md +12 -0
  3. package/dist/mixpanel-core.cjs.d.ts +84 -13
  4. package/dist/mixpanel-core.cjs.js +180 -28
  5. package/dist/mixpanel-recorder.js +684 -114
  6. package/dist/mixpanel-recorder.min.js +1 -1
  7. package/dist/mixpanel-recorder.min.js.map +1 -1
  8. package/dist/mixpanel-with-async-recorder.cjs.d.ts +84 -13
  9. package/dist/mixpanel-with-async-recorder.cjs.js +180 -28
  10. package/dist/mixpanel-with-recorder.d.ts +84 -13
  11. package/dist/mixpanel-with-recorder.js +860 -140
  12. package/dist/mixpanel-with-recorder.min.d.ts +84 -13
  13. package/dist/mixpanel-with-recorder.min.js +1 -1
  14. package/dist/mixpanel.amd.d.ts +84 -13
  15. package/dist/mixpanel.amd.js +860 -140
  16. package/dist/mixpanel.cjs.d.ts +84 -13
  17. package/dist/mixpanel.cjs.js +860 -140
  18. package/dist/mixpanel.globals.js +180 -28
  19. package/dist/mixpanel.min.js +172 -170
  20. package/dist/mixpanel.module.d.ts +84 -13
  21. package/dist/mixpanel.module.js +860 -140
  22. package/dist/mixpanel.umd.d.ts +84 -13
  23. package/dist/mixpanel.umd.js +860 -140
  24. package/dist/rrweb-bundled.js +12760 -0
  25. package/dist/rrweb-compiled.js +2496 -7176
  26. package/package.json +3 -2
  27. package/rollup.config.mjs +15 -4
  28. package/src/autocapture/index.js +1 -1
  29. package/src/autocapture/rageclick.js +20 -1
  30. package/src/autocapture/shadow-dom-observer.js +3 -15
  31. package/src/autocapture/utils.js +30 -0
  32. package/src/config.js +1 -1
  33. package/src/index.d.ts +84 -13
  34. package/src/mixpanel-core.js +127 -10
  35. package/src/recorder/recorder.js +1 -1
  36. package/src/recorder/rrweb-entrypoint.js +6 -0
  37. package/src/recorder/session-recording.js +69 -12
  38. package/src/utils.js +24 -0
  39. package/src/window.js +3 -1
  40. package/.claude/settings.local.json +0 -9
@@ -3,7 +3,7 @@
3
3
 
4
4
  var Config = {
5
5
  DEBUG: false,
6
- LIB_VERSION: '2.71.1'
6
+ LIB_VERSION: '2.73.0'
7
7
  };
8
8
 
9
9
  // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
@@ -23,7 +23,9 @@
23
23
  screen: { width: 0, height: 0 },
24
24
  location: loc,
25
25
  addEventListener: function() {},
26
- removeEventListener: function() {}
26
+ removeEventListener: function() {},
27
+ dispatchEvent: function() {},
28
+ CustomEvent: function () {}
27
29
  };
28
30
  } else {
29
31
  win = window;
@@ -2893,20 +2895,65 @@
2893
2895
  return false;
2894
2896
  }
2895
2897
 
2898
+ /**
2899
+ * Get the composed path of a click event for elements embedded in shadow DOM.
2900
+ * @param {Event} event - event to get the composed path from
2901
+ * @returns {Array} the composed path of the click event
2902
+ */
2903
+ function getClickEventComposedPath(event) {
2904
+ if ('composedPath' in event) {
2905
+ return event['composedPath']();
2906
+ }
2907
+
2908
+ return [];
2909
+ }
2910
+
2911
+ /**
2912
+ * Get the element from a click event, accounting for elements embedded in shadow DOM.
2913
+ * @param {Event} event - event to get the target from
2914
+ * @returns {Element | null} the element that was the target of the click event
2915
+ */
2916
+ function getClickEventTargetElement(event) {
2917
+ var path = getClickEventComposedPath(event);
2918
+
2919
+ if (path && path.length > 0) {
2920
+ return path[0];
2921
+ }
2922
+
2923
+ return event['target'] || event['srcElement'];
2924
+ }
2925
+
2896
2926
  /** @const */ var DEFAULT_RAGE_CLICK_THRESHOLD_PX = 30;
2897
2927
  /** @const */ var DEFAULT_RAGE_CLICK_TIMEOUT_MS = 1000;
2898
2928
  /** @const */ var DEFAULT_RAGE_CLICK_CLICK_COUNT = 4;
2929
+ /** @const */ var DEFAULT_RAGE_CLICK_INTERACTIVE_ELEMENTS_ONLY = false;
2899
2930
 
2900
2931
  function RageClickTracker() {
2901
2932
  this.clicks = [];
2902
2933
  }
2903
2934
 
2904
- RageClickTracker.prototype.isRageClick = function(x, y, options) {
2935
+ /**
2936
+ * Determines if a click event is part of a rage click sequence.
2937
+ * @param {Event} event - the original click event.
2938
+ * @param {import('../index.d.ts').RageClickConfig} options - configuration options for rage click detection.
2939
+ * @returns {boolean} - true if the click is considered a rage click, false otherwise.
2940
+ */
2941
+ RageClickTracker.prototype.isRageClick = function(event, options) {
2905
2942
  options = options || {};
2906
2943
  var thresholdPx = options['threshold_px'] || DEFAULT_RAGE_CLICK_THRESHOLD_PX;
2907
2944
  var timeoutMs = options['timeout_ms'] || DEFAULT_RAGE_CLICK_TIMEOUT_MS;
2908
2945
  var clickCount = options['click_count'] || DEFAULT_RAGE_CLICK_CLICK_COUNT;
2946
+ var interactiveElementsOnly = options['interactive_elements_only'] || DEFAULT_RAGE_CLICK_INTERACTIVE_ELEMENTS_ONLY;
2947
+
2948
+ if (interactiveElementsOnly) {
2949
+ var target = getClickEventTargetElement(event);
2950
+ if (!target || isDefinitelyNonInteractive(target)) {
2951
+ return false;
2952
+ }
2953
+ }
2954
+
2909
2955
  var timestamp = Date.now();
2956
+ var x = event['pageX'], y = event['pageY'];
2910
2957
 
2911
2958
  var lastClick = this.clicks[this.clicks.length - 1];
2912
2959
  if (
@@ -2937,28 +2984,16 @@
2937
2984
  if (!this.observedShadowRoots) {
2938
2985
  return;
2939
2986
  }
2940
- var path = this.getComposedPath(event);
2941
- if (path && path.length) {
2942
- return path[0];
2943
- }
2944
2987
 
2945
- return event['target'] || event['srcElement'];
2988
+ return getClickEventTargetElement(event);
2946
2989
  };
2947
2990
 
2948
-
2949
- ShadowDOMObserver.prototype.getComposedPath = function(event) {
2950
- if ('composedPath' in event) {
2951
- return event['composedPath']();
2952
- }
2953
-
2954
- return [];
2955
- };
2956
2991
  ShadowDOMObserver.prototype.observeFromEvent = function(event) {
2957
2992
  if (!this.observedShadowRoots) {
2958
2993
  return;
2959
2994
  }
2960
2995
 
2961
- var path = this.getComposedPath(event);
2996
+ var path = getClickEventComposedPath(event);
2962
2997
 
2963
2998
  // Check each element in path for shadow roots
2964
2999
  for (var i = 0; i < path.length; i++) {
@@ -3710,7 +3745,7 @@
3710
3745
  return;
3711
3746
  }
3712
3747
 
3713
- if (this._rageClickTracker.isRageClick(ev['pageX'], ev['pageY'], currentRageClickConfig)) {
3748
+ if (this._rageClickTracker.isRageClick(ev, currentRageClickConfig)) {
3714
3749
  this.trackDomEvent(ev, MP_EV_RAGE_CLICK);
3715
3750
  }
3716
3751
  }.bind(this);
@@ -6803,8 +6838,6 @@
6803
6838
  var INIT_MODULE = 0;
6804
6839
  var INIT_SNIPPET = 1;
6805
6840
 
6806
- var IDENTITY_FUNC = function(x) {return x;};
6807
-
6808
6841
  /** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel';
6809
6842
  /** @const */ var PAYLOAD_TYPE_BASE64 = 'base64';
6810
6843
  /** @const */ var PAYLOAD_TYPE_JSON = 'json';
@@ -6898,6 +6931,7 @@
6898
6931
  'record_block_selector': 'img, video, audio',
6899
6932
  'record_canvas': false,
6900
6933
  'record_collect_fonts': false,
6934
+ 'record_console': true,
6901
6935
  'record_heatmap_data': false,
6902
6936
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
6903
6937
  'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
@@ -6969,6 +7003,17 @@
6969
7003
  // global debug to be true
6970
7004
  Config.DEBUG = Config.DEBUG || instance.get_config('debug');
6971
7005
 
7006
+ var source = init_type === INIT_MODULE ? 'module' : 'snippet';
7007
+ win.dispatchEvent(new win.CustomEvent('$mp_sdk_to_extension_event', {
7008
+ 'detail': {
7009
+ 'instance': instance,
7010
+ 'source': source,
7011
+ 'token': token,
7012
+ 'name': name,
7013
+ 'info': _.info
7014
+ }
7015
+ }));
7016
+
6972
7017
  // if target is not defined, we called init after the lib already
6973
7018
  // loaded, so there won't be an array of things to execute
6974
7019
  if (!_.isUndefined(target) && _.isArray(target)) {
@@ -7039,6 +7084,8 @@
7039
7084
  }
7040
7085
  }
7041
7086
 
7087
+ this.hooks = {};
7088
+
7042
7089
  this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, {
7043
7090
  'name': name,
7044
7091
  'token': token,
@@ -7639,7 +7686,12 @@
7639
7686
  );
7640
7687
  }, this),
7641
7688
  beforeSendHook: _.bind(function(item) {
7642
- return this._run_hook('before_send_' + attrs.type, item);
7689
+ var ret = this._run_hook('before_send_' + attrs.type, item);
7690
+ if (ret) {
7691
+ return ret[0];
7692
+ } else {
7693
+ return null;
7694
+ }
7643
7695
  }, this),
7644
7696
  stopAllBatchingFunc: _.bind(this.stop_batch_senders, this),
7645
7697
  usePersistence: true,
@@ -7732,6 +7784,9 @@
7732
7784
  var send_request_immediately = _.bind(function() {
7733
7785
  if (!send_request_options.skip_hooks) {
7734
7786
  truncated_data = this._run_hook('before_send_' + options.type, truncated_data);
7787
+ if (truncated_data) {
7788
+ truncated_data = truncated_data[0];
7789
+ }
7735
7790
  }
7736
7791
  if (truncated_data) {
7737
7792
  console.log('MIXPANEL REQUEST:');
@@ -7786,6 +7841,17 @@
7786
7841
  * with the tracking payload sent to the API server is returned; otherwise false.
7787
7842
  */
7788
7843
  MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, properties, options, callback) {
7844
+ var ret;
7845
+ if (!(options && options.skip_hooks)) {
7846
+ ret = this._run_hook('before_track', event_name, properties);
7847
+ if (ret === null) {
7848
+ return;
7849
+ } else {
7850
+ event_name = ret[0];
7851
+ properties = ret[1];
7852
+ }
7853
+ }
7854
+
7789
7855
  if (!callback && typeof options === 'function') {
7790
7856
  callback = options;
7791
7857
  options = null;
@@ -7855,7 +7921,7 @@
7855
7921
  'event': event_name,
7856
7922
  'properties': properties
7857
7923
  };
7858
- var ret = this._track_or_batch({
7924
+ ret = this._track_or_batch({
7859
7925
  type: 'events',
7860
7926
  data: data,
7861
7927
  endpoint: this.get_api_host('events') + '/' + this.get_config('api_routes')['track'],
@@ -8201,6 +8267,14 @@
8201
8267
  * @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage)
8202
8268
  */
8203
8269
  MixpanelLib.prototype.register = function(props, days_or_options) {
8270
+ var ret = this._run_hook('before_register', props, days_or_options);
8271
+ if (ret === null) {
8272
+ return;
8273
+ } else {
8274
+ props = ret[0];
8275
+ days_or_options = ret[1];
8276
+ }
8277
+
8204
8278
  var options = options_for_register(days_or_options);
8205
8279
  if (options['persistent']) {
8206
8280
  this['persistence'].register(props, options['days']);
@@ -8237,6 +8311,15 @@
8237
8311
  * @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage)
8238
8312
  */
8239
8313
  MixpanelLib.prototype.register_once = function(props, default_value, days_or_options) {
8314
+ var ret = this._run_hook('before_register_once', props, default_value, days_or_options);
8315
+ if (ret === null) {
8316
+ return;
8317
+ } else {
8318
+ props = ret[0];
8319
+ default_value = ret[1];
8320
+ days_or_options = ret[2];
8321
+ }
8322
+
8240
8323
  var options = options_for_register(days_or_options);
8241
8324
  if (options['persistent']) {
8242
8325
  this['persistence'].register_once(props, default_value, options['days']);
@@ -8260,6 +8343,14 @@
8260
8343
  * @param {boolean} [options.persistent=true] - whether to look in persistent storage (cookie/localStorage)
8261
8344
  */
8262
8345
  MixpanelLib.prototype.unregister = function(property, options) {
8346
+ var ret = this._run_hook('before_unregister', property, options);
8347
+ if (ret === null) {
8348
+ return;
8349
+ } else {
8350
+ property = ret[0];
8351
+ options = ret[1];
8352
+ }
8353
+
8263
8354
  options = options_for_register(options);
8264
8355
  if (options['persistent']) {
8265
8356
  this['persistence'].unregister(property);
@@ -8308,6 +8399,13 @@
8308
8399
  // _set_once_callback:function A callback to be run if and when the People set_once queue is flushed
8309
8400
  // _union_callback:function A callback to be run if and when the People union queue is flushed
8310
8401
  // _unset_callback:function A callback to be run if and when the People unset queue is flushed
8402
+ var ret = this._run_hook('before_identify', new_distinct_id);
8403
+
8404
+ if (ret === null) {
8405
+ return -1;
8406
+ } else {
8407
+ new_distinct_id = ret[0];
8408
+ }
8311
8409
 
8312
8410
  var previous_distinct_id = this.get_distinct_id();
8313
8411
  if (new_distinct_id && previous_distinct_id !== new_distinct_id) {
@@ -8632,6 +8730,25 @@
8632
8730
  if (('autocapture' in config || 'record_heatmap_data' in config) && this.autocapture) {
8633
8731
  this.autocapture.init();
8634
8732
  }
8733
+
8734
+ if (_.isObject(config['hooks'])) {
8735
+ this.hooks = {};
8736
+ _.each(config['hooks'], function(hook_value, hook_name) {
8737
+ if (_.isFunction(hook_value)) {
8738
+ this.hooks[hook_name] = [hook_value];
8739
+ } else if (_.isArray(hook_value)) {
8740
+ this.hooks[hook_name] = [];
8741
+ for (var i = 0; i < hook_value.length; i++) {
8742
+ if (!_.isFunction(hook_value[i])) {
8743
+ console.critical('Invalid hook added. Hook is not a function');
8744
+ }
8745
+ this.hooks[hook_name].push(hook_value[i]);
8746
+ }
8747
+ } else {
8748
+ console.critical('Invalid hooks added. Ensure that the hook values passed into config.hooks are functions or arrays of functions.');
8749
+ }
8750
+ }, this);
8751
+ }
8635
8752
  }
8636
8753
  };
8637
8754
 
@@ -8649,12 +8766,26 @@
8649
8766
  * @returns {any|null} return value of user-provided hook, or null if nothing was returned
8650
8767
  */
8651
8768
  MixpanelLib.prototype._run_hook = function(hook_name) {
8652
- var ret = (this['config']['hooks'][hook_name] || IDENTITY_FUNC).apply(this, slice.call(arguments, 1));
8653
- if (typeof ret === 'undefined') {
8654
- this.report_error(hook_name + ' hook did not return a value');
8655
- ret = null;
8656
- }
8657
- return ret;
8769
+ var hook_data = slice.call(arguments, 1);
8770
+ _.each(this.hooks[hook_name], function(hook) {
8771
+ if (hook_data === null) {
8772
+ return null;
8773
+ }
8774
+
8775
+ var ret = hook.apply(this, hook_data);
8776
+
8777
+ if (typeof ret === 'undefined') {
8778
+ this.report_error(hook_name + ' hook did not return a valid value');
8779
+ hook_data = null;
8780
+ } else {
8781
+ if (!_.isArray(ret)) {
8782
+ ret = [ret];
8783
+ }
8784
+ hook_data.splice.apply(hook_data, [0, ret.length].concat(ret));
8785
+ }
8786
+ }, this);
8787
+
8788
+ return hook_data;
8658
8789
  };
8659
8790
 
8660
8791
  /**
@@ -8965,6 +9096,25 @@
8965
9096
  }
8966
9097
  };
8967
9098
 
9099
+ MixpanelLib.prototype.add_hook = function(hook_name, hook_fn) {
9100
+ if (!this.hooks[hook_name]) {
9101
+ this.hooks[hook_name] = [];
9102
+ }
9103
+ this.hooks[hook_name].push(hook_fn);
9104
+ };
9105
+
9106
+ MixpanelLib.prototype.remove_hook = function(hook_name, hook_fn) {
9107
+ var fn_index;
9108
+ if (this.hooks[hook_name]) {
9109
+ fn_index = this.hooks[hook_name].indexOf(hook_fn);
9110
+ if (fn_index !== -1) {
9111
+ this.hooks[hook_name].splice(fn_index, 1);
9112
+ } else {
9113
+ console.log('remove_hook failed. Matching hook was not found');
9114
+ }
9115
+ }
9116
+ };
9117
+
8968
9118
  // EXPORTS (for closure compiler)
8969
9119
 
8970
9120
  // MixpanelLib Exports
@@ -8997,6 +9147,8 @@
8997
9147
  MixpanelLib.prototype['set_group'] = MixpanelLib.prototype.set_group;
8998
9148
  MixpanelLib.prototype['add_group'] = MixpanelLib.prototype.add_group;
8999
9149
  MixpanelLib.prototype['remove_group'] = MixpanelLib.prototype.remove_group;
9150
+ MixpanelLib.prototype['add_hook'] = MixpanelLib.prototype.add_hook;
9151
+ MixpanelLib.prototype['remove_hook'] = MixpanelLib.prototype.remove_hook;
9000
9152
  MixpanelLib.prototype['track_with_groups'] = MixpanelLib.prototype.track_with_groups;
9001
9153
  MixpanelLib.prototype['start_batch_senders'] = MixpanelLib.prototype.start_batch_senders;
9002
9154
  MixpanelLib.prototype['stop_batch_senders'] = MixpanelLib.prototype.stop_batch_senders;