mixpanel-browser 2.66.0 → 2.68.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.
@@ -3,7 +3,7 @@
3
3
 
4
4
  var Config = {
5
5
  DEBUG: false,
6
- LIB_VERSION: '2.66.0'
6
+ LIB_VERSION: '2.68.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
@@ -2655,6 +2655,38 @@
2655
2655
  return true;
2656
2656
  }
2657
2657
 
2658
+ /** @const */ var DEFAULT_RAGE_CLICK_THRESHOLD_PX = 30;
2659
+ /** @const */ var DEFAULT_RAGE_CLICK_TIMEOUT_MS = 1000;
2660
+ /** @const */ var DEFAULT_RAGE_CLICK_CLICK_COUNT = 4;
2661
+
2662
+ function RageClickTracker() {
2663
+ this.clicks = [];
2664
+ }
2665
+
2666
+ RageClickTracker.prototype.isRageClick = function(x, y, options) {
2667
+ options = options || {};
2668
+ var thresholdPx = options['threshold_px'] || DEFAULT_RAGE_CLICK_THRESHOLD_PX;
2669
+ var timeoutMs = options['timeout_ms'] || DEFAULT_RAGE_CLICK_TIMEOUT_MS;
2670
+ var clickCount = options['click_count'] || DEFAULT_RAGE_CLICK_CLICK_COUNT;
2671
+ var timestamp = Date.now();
2672
+
2673
+ var lastClick = this.clicks[this.clicks.length - 1];
2674
+ if (
2675
+ lastClick &&
2676
+ timestamp - lastClick.timestamp < timeoutMs &&
2677
+ Math.sqrt(Math.pow(x - lastClick.x, 2) + Math.pow(y - lastClick.y, 2)) < thresholdPx
2678
+ ) {
2679
+ this.clicks.push({ x: x, y: y, timestamp: timestamp });
2680
+ if (this.clicks.length >= clickCount) {
2681
+ this.clicks = [];
2682
+ return true;
2683
+ }
2684
+ } else {
2685
+ this.clicks = [{ x: x, y: y, timestamp: timestamp }];
2686
+ }
2687
+ return false;
2688
+ };
2689
+
2658
2690
  var AUTOCAPTURE_CONFIG_KEY = 'autocapture';
2659
2691
  var LEGACY_PAGEVIEW_CONFIG_KEY = 'track_pageview';
2660
2692
 
@@ -2676,6 +2708,7 @@
2676
2708
  var CONFIG_TRACK_CLICK = 'click';
2677
2709
  var CONFIG_TRACK_INPUT = 'input';
2678
2710
  var CONFIG_TRACK_PAGEVIEW = 'pageview';
2711
+ var CONFIG_TRACK_RAGE_CLICK = 'rage_click';
2679
2712
  var CONFIG_TRACK_SCROLL = 'scroll';
2680
2713
  var CONFIG_TRACK_SUBMIT = 'submit';
2681
2714
 
@@ -2693,6 +2726,7 @@
2693
2726
  CONFIG_DEFAULTS$1[CONFIG_TRACK_CLICK] = true;
2694
2727
  CONFIG_DEFAULTS$1[CONFIG_TRACK_INPUT] = true;
2695
2728
  CONFIG_DEFAULTS$1[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
2729
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_RAGE_CLICK] = true;
2696
2730
  CONFIG_DEFAULTS$1[CONFIG_TRACK_SCROLL] = true;
2697
2731
  CONFIG_DEFAULTS$1[CONFIG_TRACK_SUBMIT] = true;
2698
2732
 
@@ -2702,6 +2736,7 @@
2702
2736
 
2703
2737
  var MP_EV_CLICK = '$mp_click';
2704
2738
  var MP_EV_INPUT = '$mp_input_change';
2739
+ var MP_EV_RAGE_CLICK = '$mp_rage_click';
2705
2740
  var MP_EV_SCROLL = '$mp_scroll';
2706
2741
  var MP_EV_SUBMIT = '$mp_submit';
2707
2742
 
@@ -2724,6 +2759,7 @@
2724
2759
  this.initInputTracking();
2725
2760
  this.initScrollTracking();
2726
2761
  this.initSubmitTracking();
2762
+ this.initRageClickTracking();
2727
2763
  };
2728
2764
 
2729
2765
  Autocapture.prototype.getFullConfig = function() {
@@ -2802,6 +2838,11 @@
2802
2838
  return;
2803
2839
  }
2804
2840
 
2841
+ var isCapturedForHeatMap = this.mp.is_recording_heatmap_data() && (
2842
+ (mpEventName === MP_EV_CLICK && !this.getConfig(CONFIG_TRACK_CLICK)) ||
2843
+ (mpEventName === MP_EV_RAGE_CLICK && !this._getRageClickConfig())
2844
+ );
2845
+
2805
2846
  var props = getPropsForDOMEvent(ev, {
2806
2847
  allowElementCallback: this.getConfig(CONFIG_ALLOW_ELEMENT_CALLBACK),
2807
2848
  allowSelectors: this.getConfig(CONFIG_ALLOW_SELECTORS),
@@ -2810,7 +2851,7 @@
2810
2851
  blockSelectors: this.getConfig(CONFIG_BLOCK_SELECTORS),
2811
2852
  captureExtraAttrs: this.getConfig(CONFIG_CAPTURE_EXTRA_ATTRS),
2812
2853
  captureTextContent: this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT),
2813
- capturedForHeatMap: mpEventName === MP_EV_CLICK && !this.getConfig(CONFIG_TRACK_CLICK) && this.mp.is_recording_heatmap_data(),
2854
+ capturedForHeatMap: isCapturedForHeatMap,
2814
2855
  });
2815
2856
  if (props) {
2816
2857
  _.extend(props, DEFAULT_PROPS);
@@ -2818,6 +2859,24 @@
2818
2859
  }
2819
2860
  };
2820
2861
 
2862
+ Autocapture.prototype._getRageClickConfig = function() {
2863
+ var config = this.getConfig(CONFIG_TRACK_RAGE_CLICK);
2864
+
2865
+ if (!config) {
2866
+ return null; // rage click tracking disabled
2867
+ }
2868
+
2869
+ if (config === true) {
2870
+ return {}; // use defaults
2871
+ }
2872
+
2873
+ if (typeof config === 'object') {
2874
+ return config; // use custom configuration
2875
+ }
2876
+
2877
+ return {}; // fallback to defaults for any other truthy value
2878
+ };
2879
+
2821
2880
  Autocapture.prototype.initClickTracking = function() {
2822
2881
  win.removeEventListener(EV_CLICK, this.listenerClick);
2823
2882
 
@@ -2919,6 +2978,36 @@
2919
2978
  }.bind(this)));
2920
2979
  };
2921
2980
 
2981
+ Autocapture.prototype.initRageClickTracking = function() {
2982
+ win.removeEventListener(EV_CLICK, this.listenerRageClick);
2983
+
2984
+ var rageClickConfig = this._getRageClickConfig();
2985
+ if (!rageClickConfig && !this.mp.get_config('record_heatmap_data')) {
2986
+ return;
2987
+ }
2988
+
2989
+ logger$4.log('Initializing rage click tracking');
2990
+ if (!this._rageClickTracker) {
2991
+ this._rageClickTracker = new RageClickTracker();
2992
+ }
2993
+
2994
+ this.listenerRageClick = function(ev) {
2995
+ var currentRageClickConfig = this._getRageClickConfig();
2996
+ if (!currentRageClickConfig && !this.mp.is_recording_heatmap_data()) {
2997
+ return;
2998
+ }
2999
+
3000
+ if (this.currentUrlBlocked()) {
3001
+ return;
3002
+ }
3003
+
3004
+ if (this._rageClickTracker.isRageClick(ev['pageX'], ev['pageY'], currentRageClickConfig)) {
3005
+ this.trackDomEvent(ev, MP_EV_RAGE_CLICK);
3006
+ }
3007
+ }.bind(this);
3008
+ win.addEventListener(EV_CLICK, this.listenerRageClick);
3009
+ };
3010
+
2922
3011
  Autocapture.prototype.initScrollTracking = function() {
2923
3012
  win.removeEventListener(EV_SCROLLEND, this.listenerScroll);
2924
3013
 
@@ -3003,8 +3092,10 @@
3003
3092
  * @constructor
3004
3093
  */
3005
3094
  var FeatureFlagManager = function(initOptions) {
3095
+ this.getFullApiRoute = initOptions.getFullApiRoute;
3006
3096
  this.getMpConfig = initOptions.getConfigFunc;
3007
- this.getDistinctId = initOptions.getDistinctIdFunc;
3097
+ this.setMpConfig = initOptions.setConfigFunc;
3098
+ this.getMpProperty = initOptions.getPropertyFunc;
3008
3099
  this.track = initOptions.trackingFunc;
3009
3100
  };
3010
3101
 
@@ -3041,6 +3132,23 @@
3041
3132
  return !!this.getMpConfig(FLAGS_CONFIG_KEY);
3042
3133
  };
3043
3134
 
3135
+ FeatureFlagManager.prototype.updateContext = function(newContext, options) {
3136
+ if (!this.isSystemEnabled()) {
3137
+ logger$3.critical('Feature Flags not enabled, cannot update context');
3138
+ return Promise.resolve();
3139
+ }
3140
+
3141
+ var ffConfig = this.getMpConfig(FLAGS_CONFIG_KEY);
3142
+ if (!_.isObject(ffConfig)) {
3143
+ ffConfig = {};
3144
+ }
3145
+ var oldContext = (options && options['replace']) ? {} : this.getConfig(CONFIG_CONTEXT);
3146
+ ffConfig[CONFIG_CONTEXT] = _.extend({}, oldContext, newContext);
3147
+
3148
+ this.setMpConfig(FLAGS_CONFIG_KEY, ffConfig);
3149
+ return this.fetchFlags();
3150
+ };
3151
+
3044
3152
  FeatureFlagManager.prototype.areFlagsReady = function() {
3045
3153
  if (!this.isSystemEnabled()) {
3046
3154
  logger$3.error('Feature Flags not enabled');
@@ -3050,15 +3158,17 @@
3050
3158
 
3051
3159
  FeatureFlagManager.prototype.fetchFlags = function() {
3052
3160
  if (!this.isSystemEnabled()) {
3053
- return;
3161
+ return Promise.resolve();
3054
3162
  }
3055
3163
 
3056
- var distinctId = this.getDistinctId();
3164
+ var distinctId = this.getMpProperty('distinct_id');
3165
+ var deviceId = this.getMpProperty('$device_id');
3057
3166
  logger$3.log('Fetching flags for distinct ID: ' + distinctId);
3058
3167
  var reqParams = {
3059
- 'context': _.extend({'distinct_id': distinctId}, this.getConfig(CONFIG_CONTEXT))
3168
+ 'context': _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT))
3060
3169
  };
3061
- this.fetchPromise = win['fetch'](this.getMpConfig('api_host') + '/' + this.getMpConfig('api_routes')['flags'], {
3170
+ this._fetchInProgressStartTime = Date.now();
3171
+ this.fetchPromise = win['fetch'](this.getFullApiRoute(), {
3062
3172
  'method': 'POST',
3063
3173
  'headers': {
3064
3174
  'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
@@ -3066,6 +3176,7 @@
3066
3176
  },
3067
3177
  'body': JSON.stringify(reqParams)
3068
3178
  }).then(function(response) {
3179
+ this.markFetchComplete();
3069
3180
  return response.json().then(function(responseBody) {
3070
3181
  var responseFlags = responseBody['flags'];
3071
3182
  if (!responseFlags) {
@@ -3080,9 +3191,26 @@
3080
3191
  });
3081
3192
  this.flags = flags;
3082
3193
  }.bind(this)).catch(function(error) {
3194
+ this.markFetchComplete();
3083
3195
  logger$3.error(error);
3084
- });
3085
- }.bind(this)).catch(function() {});
3196
+ }.bind(this));
3197
+ }.bind(this)).catch(function(error) {
3198
+ this.markFetchComplete();
3199
+ logger$3.error(error);
3200
+ }.bind(this));
3201
+
3202
+ return this.fetchPromise;
3203
+ };
3204
+
3205
+ FeatureFlagManager.prototype.markFetchComplete = function() {
3206
+ if (!this._fetchInProgressStartTime) {
3207
+ logger$3.error('Fetch in progress started time not set, cannot mark fetch complete');
3208
+ return;
3209
+ }
3210
+ this._fetchStartTime = this._fetchInProgressStartTime;
3211
+ this._fetchCompleteTime = Date.now();
3212
+ this._fetchLatency = this._fetchCompleteTime - this._fetchStartTime;
3213
+ this._fetchInProgressStartTime = null;
3086
3214
  };
3087
3215
 
3088
3216
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
@@ -3161,7 +3289,10 @@
3161
3289
  this.track('$experiment_started', {
3162
3290
  'Experiment name': featureName,
3163
3291
  'Variant name': feature['key'],
3164
- '$experiment_type': 'feature_flag'
3292
+ '$experiment_type': 'feature_flag',
3293
+ 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
3294
+ 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
3295
+ 'Variant fetch latency (ms)': this._fetchLatency
3165
3296
  });
3166
3297
  };
3167
3298
 
@@ -3181,6 +3312,7 @@
3181
3312
  FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
3182
3313
  FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
3183
3314
  FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
3315
+ FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
3184
3316
 
3185
3317
  // Deprecated method
3186
3318
  FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
@@ -5952,7 +6084,7 @@
5952
6084
  'batch_autostart': true,
5953
6085
  'hooks': {},
5954
6086
  'record_block_class': new RegExp('^(mp-block|fs-exclude|amp-block|rr-block|ph-no-capture)$'),
5955
- 'record_block_selector': 'img, video',
6087
+ 'record_block_selector': 'img, video, audio',
5956
6088
  'record_canvas': false,
5957
6089
  'record_collect_fonts': false,
5958
6090
  'record_heatmap_data': false,
@@ -6172,8 +6304,12 @@
6172
6304
  }
6173
6305
 
6174
6306
  this.flags = new FeatureFlagManager({
6307
+ getFullApiRoute: _.bind(function() {
6308
+ return this.get_api_host('flags') + '/' + this.get_config('api_routes')['flags'];
6309
+ }, this),
6175
6310
  getConfigFunc: _.bind(this.get_config, this),
6176
- getDistinctIdFunc: _.bind(this.get_distinct_id, this),
6311
+ setConfigFunc: _.bind(this.set_config, this),
6312
+ getPropertyFunc: _.bind(this.get_property, this),
6177
6313
  trackingFunc: _.bind(this.track, this)
6178
6314
  });
6179
6315
  this.flags.init();
@@ -6659,11 +6795,10 @@
6659
6795
 
6660
6796
  MixpanelLib.prototype.get_batcher_configs = function() {
6661
6797
  var queue_prefix = '__mpq_' + this.get_config('token');
6662
- var api_routes = this.get_config('api_routes');
6663
6798
  this._batcher_configs = this._batcher_configs || {
6664
- events: {type: 'events', endpoint: '/' + api_routes['track'], queue_key: queue_prefix + '_ev'},
6665
- people: {type: 'people', endpoint: '/' + api_routes['engage'], queue_key: queue_prefix + '_pp'},
6666
- groups: {type: 'groups', endpoint: '/' + api_routes['groups'], queue_key: queue_prefix + '_gr'}
6799
+ events: {type: 'events', api_name: 'track', queue_key: queue_prefix + '_ev'},
6800
+ people: {type: 'people', api_name: 'engage', queue_key: queue_prefix + '_pp'},
6801
+ groups: {type: 'groups', api_name: 'groups', queue_key: queue_prefix + '_gr'}
6667
6802
  };
6668
6803
  return this._batcher_configs;
6669
6804
  };
@@ -6677,8 +6812,9 @@
6677
6812
  libConfig: this['config'],
6678
6813
  errorReporter: this.get_config('error_reporter'),
6679
6814
  sendRequestFunc: _.bind(function(data, options, cb) {
6815
+ var api_routes = this.get_config('api_routes');
6680
6816
  this._send_request(
6681
- this.get_config('api_host') + attrs.endpoint,
6817
+ this.get_api_host(attrs.api_name) + '/' + api_routes[attrs.api_name],
6682
6818
  this._encode_data_for_request(data),
6683
6819
  options,
6684
6820
  this._prepare_callback(cb, data)
@@ -7406,31 +7542,15 @@
7406
7542
  * Useful for clearing data when a user logs out.
7407
7543
  */
7408
7544
  MixpanelLib.prototype.reset = function() {
7409
- var self = this;
7410
-
7411
- var reset = function () {
7412
- self['persistence'].clear();
7413
- self._flags.identify_called = false;
7414
- var uuid = _.UUID();
7415
- self.register_once({
7416
- 'distinct_id': DEVICE_ID_PREFIX + uuid,
7417
- '$device_id': uuid
7418
- }, '');
7419
- };
7420
-
7421
- if (self._recorder) {
7422
- self.stop_session_recording()
7423
- .then(function () {
7424
- reset();
7425
- self._check_and_start_session_recording();
7426
- })
7427
- .catch(_.bind(function (err) {
7428
- reset();
7429
- this.report_error('Error restarting recording session', err);
7430
- }, this));
7431
- } else {
7432
- reset();
7433
- }
7545
+ this.stop_session_recording();
7546
+ this['persistence'].clear();
7547
+ this._flags.identify_called = false;
7548
+ var uuid = _.UUID();
7549
+ this.register_once({
7550
+ 'distinct_id': DEVICE_ID_PREFIX + uuid,
7551
+ '$device_id': uuid
7552
+ }, '');
7553
+ this._check_and_start_session_recording();
7434
7554
  };
7435
7555
 
7436
7556
  /**