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.
@@ -13942,7 +13942,7 @@ if (typeof Promise !== 'undefined' && Promise.toString().indexOf('[native code]'
13942
13942
 
13943
13943
  var Config = {
13944
13944
  DEBUG: false,
13945
- LIB_VERSION: '2.66.0'
13945
+ LIB_VERSION: '2.68.0'
13946
13946
  };
13947
13947
 
13948
13948
  /* eslint camelcase: "off", eqeqeq: "off" */
@@ -17060,6 +17060,13 @@ function isUserEvent(ev) {
17060
17060
  * @property {string} replayStartUrl
17061
17061
  */
17062
17062
 
17063
+ /**
17064
+ * @typedef {Object} UserIdInfo
17065
+ * @property {string} distinct_id
17066
+ * @property {string} user_id
17067
+ * @property {string} device_id
17068
+ */
17069
+
17063
17070
 
17064
17071
  /**
17065
17072
  * This class encapsulates a single session recording and its lifecycle.
@@ -17115,6 +17122,30 @@ var SessionRecording = function(options) {
17115
17122
  });
17116
17123
  };
17117
17124
 
17125
+ /**
17126
+ * @returns {UserIdInfo}
17127
+ */
17128
+ SessionRecording.prototype.getUserIdInfo = function () {
17129
+ if (this.finalFlushUserIdInfo) {
17130
+ return this.finalFlushUserIdInfo;
17131
+ }
17132
+
17133
+ var userIdInfo = {
17134
+ 'distinct_id': String(this._mixpanel.get_distinct_id()),
17135
+ };
17136
+
17137
+ // send ID management props if they exist
17138
+ var deviceId = this._mixpanel.get_property('$device_id');
17139
+ if (deviceId) {
17140
+ userIdInfo['$device_id'] = deviceId;
17141
+ }
17142
+ var userId = this._mixpanel.get_property('$user_id');
17143
+ if (userId) {
17144
+ userIdInfo['$user_id'] = userId;
17145
+ }
17146
+ return userIdInfo;
17147
+ };
17148
+
17118
17149
  SessionRecording.prototype.unloadPersistedData = function () {
17119
17150
  this.batcher.stop();
17120
17151
  return this.batcher.flush()
@@ -17239,6 +17270,9 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
17239
17270
  };
17240
17271
 
17241
17272
  SessionRecording.prototype.stopRecording = function (skipFlush) {
17273
+ // store the user ID info in case this is getting called in mixpanel.reset()
17274
+ this.finalFlushUserIdInfo = this.getUserIdInfo();
17275
+
17242
17276
  if (!this.isRrwebStopped()) {
17243
17277
  try {
17244
17278
  this._stopRecording();
@@ -17404,7 +17438,6 @@ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
17404
17438
  '$current_url': this.batchStartUrl,
17405
17439
  '$lib_version': Config.LIB_VERSION,
17406
17440
  'batch_start_time': batchStartTime / 1000,
17407
- 'distinct_id': String(this._mixpanel.get_distinct_id()),
17408
17441
  'mp_lib': 'web',
17409
17442
  'replay_id': replayId,
17410
17443
  'replay_length_ms': replayLengthMs,
@@ -17413,16 +17446,7 @@ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
17413
17446
  'seq': this.seqNo
17414
17447
  };
17415
17448
  var eventsJson = JSON.stringify(data);
17416
-
17417
- // send ID management props if they exist
17418
- var deviceId = this._mixpanel.get_property('$device_id');
17419
- if (deviceId) {
17420
- reqParams['$device_id'] = deviceId;
17421
- }
17422
- var userId = this._mixpanel.get_property('$user_id');
17423
- if (userId) {
17424
- reqParams['$user_id'] = userId;
17425
- }
17449
+ Object.assign(reqParams, this.getUserIdInfo());
17426
17450
 
17427
17451
  if (CompressionStream) {
17428
17452
  var jsonStream = new Blob([eventsJson], {type: 'application/json'}).stream();
@@ -18203,6 +18227,38 @@ function shouldTrackValue(value) {
18203
18227
  return true;
18204
18228
  }
18205
18229
 
18230
+ /** @const */ var DEFAULT_RAGE_CLICK_THRESHOLD_PX = 30;
18231
+ /** @const */ var DEFAULT_RAGE_CLICK_TIMEOUT_MS = 1000;
18232
+ /** @const */ var DEFAULT_RAGE_CLICK_CLICK_COUNT = 4;
18233
+
18234
+ function RageClickTracker() {
18235
+ this.clicks = [];
18236
+ }
18237
+
18238
+ RageClickTracker.prototype.isRageClick = function(x, y, options) {
18239
+ options = options || {};
18240
+ var thresholdPx = options['threshold_px'] || DEFAULT_RAGE_CLICK_THRESHOLD_PX;
18241
+ var timeoutMs = options['timeout_ms'] || DEFAULT_RAGE_CLICK_TIMEOUT_MS;
18242
+ var clickCount = options['click_count'] || DEFAULT_RAGE_CLICK_CLICK_COUNT;
18243
+ var timestamp = Date.now();
18244
+
18245
+ var lastClick = this.clicks[this.clicks.length - 1];
18246
+ if (
18247
+ lastClick &&
18248
+ timestamp - lastClick.timestamp < timeoutMs &&
18249
+ Math.sqrt(Math.pow(x - lastClick.x, 2) + Math.pow(y - lastClick.y, 2)) < thresholdPx
18250
+ ) {
18251
+ this.clicks.push({ x: x, y: y, timestamp: timestamp });
18252
+ if (this.clicks.length >= clickCount) {
18253
+ this.clicks = [];
18254
+ return true;
18255
+ }
18256
+ } else {
18257
+ this.clicks = [{ x: x, y: y, timestamp: timestamp }];
18258
+ }
18259
+ return false;
18260
+ };
18261
+
18206
18262
  var AUTOCAPTURE_CONFIG_KEY = 'autocapture';
18207
18263
  var LEGACY_PAGEVIEW_CONFIG_KEY = 'track_pageview';
18208
18264
 
@@ -18224,6 +18280,7 @@ var CONFIG_SCROLL_CHECKPOINTS = 'scroll_depth_percent_checkpoints';
18224
18280
  var CONFIG_TRACK_CLICK = 'click';
18225
18281
  var CONFIG_TRACK_INPUT = 'input';
18226
18282
  var CONFIG_TRACK_PAGEVIEW = 'pageview';
18283
+ var CONFIG_TRACK_RAGE_CLICK = 'rage_click';
18227
18284
  var CONFIG_TRACK_SCROLL = 'scroll';
18228
18285
  var CONFIG_TRACK_SUBMIT = 'submit';
18229
18286
 
@@ -18241,6 +18298,7 @@ CONFIG_DEFAULTS$1[CONFIG_SCROLL_CHECKPOINTS] = [25, 50, 75, 100];
18241
18298
  CONFIG_DEFAULTS$1[CONFIG_TRACK_CLICK] = true;
18242
18299
  CONFIG_DEFAULTS$1[CONFIG_TRACK_INPUT] = true;
18243
18300
  CONFIG_DEFAULTS$1[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
18301
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_RAGE_CLICK] = true;
18244
18302
  CONFIG_DEFAULTS$1[CONFIG_TRACK_SCROLL] = true;
18245
18303
  CONFIG_DEFAULTS$1[CONFIG_TRACK_SUBMIT] = true;
18246
18304
 
@@ -18250,6 +18308,7 @@ var DEFAULT_PROPS = {
18250
18308
 
18251
18309
  var MP_EV_CLICK = '$mp_click';
18252
18310
  var MP_EV_INPUT = '$mp_input_change';
18311
+ var MP_EV_RAGE_CLICK = '$mp_rage_click';
18253
18312
  var MP_EV_SCROLL = '$mp_scroll';
18254
18313
  var MP_EV_SUBMIT = '$mp_submit';
18255
18314
 
@@ -18272,6 +18331,7 @@ Autocapture.prototype.init = function() {
18272
18331
  this.initInputTracking();
18273
18332
  this.initScrollTracking();
18274
18333
  this.initSubmitTracking();
18334
+ this.initRageClickTracking();
18275
18335
  };
18276
18336
 
18277
18337
  Autocapture.prototype.getFullConfig = function() {
@@ -18350,6 +18410,11 @@ Autocapture.prototype.trackDomEvent = function(ev, mpEventName) {
18350
18410
  return;
18351
18411
  }
18352
18412
 
18413
+ var isCapturedForHeatMap = this.mp.is_recording_heatmap_data() && (
18414
+ (mpEventName === MP_EV_CLICK && !this.getConfig(CONFIG_TRACK_CLICK)) ||
18415
+ (mpEventName === MP_EV_RAGE_CLICK && !this._getRageClickConfig())
18416
+ );
18417
+
18353
18418
  var props = getPropsForDOMEvent(ev, {
18354
18419
  allowElementCallback: this.getConfig(CONFIG_ALLOW_ELEMENT_CALLBACK),
18355
18420
  allowSelectors: this.getConfig(CONFIG_ALLOW_SELECTORS),
@@ -18358,7 +18423,7 @@ Autocapture.prototype.trackDomEvent = function(ev, mpEventName) {
18358
18423
  blockSelectors: this.getConfig(CONFIG_BLOCK_SELECTORS),
18359
18424
  captureExtraAttrs: this.getConfig(CONFIG_CAPTURE_EXTRA_ATTRS),
18360
18425
  captureTextContent: this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT),
18361
- capturedForHeatMap: mpEventName === MP_EV_CLICK && !this.getConfig(CONFIG_TRACK_CLICK) && this.mp.is_recording_heatmap_data(),
18426
+ capturedForHeatMap: isCapturedForHeatMap,
18362
18427
  });
18363
18428
  if (props) {
18364
18429
  _.extend(props, DEFAULT_PROPS);
@@ -18366,6 +18431,24 @@ Autocapture.prototype.trackDomEvent = function(ev, mpEventName) {
18366
18431
  }
18367
18432
  };
18368
18433
 
18434
+ Autocapture.prototype._getRageClickConfig = function() {
18435
+ var config = this.getConfig(CONFIG_TRACK_RAGE_CLICK);
18436
+
18437
+ if (!config) {
18438
+ return null; // rage click tracking disabled
18439
+ }
18440
+
18441
+ if (config === true) {
18442
+ return {}; // use defaults
18443
+ }
18444
+
18445
+ if (typeof config === 'object') {
18446
+ return config; // use custom configuration
18447
+ }
18448
+
18449
+ return {}; // fallback to defaults for any other truthy value
18450
+ };
18451
+
18369
18452
  Autocapture.prototype.initClickTracking = function() {
18370
18453
  win.removeEventListener(EV_CLICK, this.listenerClick);
18371
18454
 
@@ -18467,6 +18550,36 @@ Autocapture.prototype.initPageviewTracking = function() {
18467
18550
  }.bind(this)));
18468
18551
  };
18469
18552
 
18553
+ Autocapture.prototype.initRageClickTracking = function() {
18554
+ win.removeEventListener(EV_CLICK, this.listenerRageClick);
18555
+
18556
+ var rageClickConfig = this._getRageClickConfig();
18557
+ if (!rageClickConfig && !this.mp.get_config('record_heatmap_data')) {
18558
+ return;
18559
+ }
18560
+
18561
+ logger$1.log('Initializing rage click tracking');
18562
+ if (!this._rageClickTracker) {
18563
+ this._rageClickTracker = new RageClickTracker();
18564
+ }
18565
+
18566
+ this.listenerRageClick = function(ev) {
18567
+ var currentRageClickConfig = this._getRageClickConfig();
18568
+ if (!currentRageClickConfig && !this.mp.is_recording_heatmap_data()) {
18569
+ return;
18570
+ }
18571
+
18572
+ if (this.currentUrlBlocked()) {
18573
+ return;
18574
+ }
18575
+
18576
+ if (this._rageClickTracker.isRageClick(ev['pageX'], ev['pageY'], currentRageClickConfig)) {
18577
+ this.trackDomEvent(ev, MP_EV_RAGE_CLICK);
18578
+ }
18579
+ }.bind(this);
18580
+ win.addEventListener(EV_CLICK, this.listenerRageClick);
18581
+ };
18582
+
18470
18583
  Autocapture.prototype.initScrollTracking = function() {
18471
18584
  win.removeEventListener(EV_SCROLLEND, this.listenerScroll);
18472
18585
 
@@ -18551,8 +18664,10 @@ CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
18551
18664
  * @constructor
18552
18665
  */
18553
18666
  var FeatureFlagManager = function(initOptions) {
18667
+ this.getFullApiRoute = initOptions.getFullApiRoute;
18554
18668
  this.getMpConfig = initOptions.getConfigFunc;
18555
- this.getDistinctId = initOptions.getDistinctIdFunc;
18669
+ this.setMpConfig = initOptions.setConfigFunc;
18670
+ this.getMpProperty = initOptions.getPropertyFunc;
18556
18671
  this.track = initOptions.trackingFunc;
18557
18672
  };
18558
18673
 
@@ -18589,6 +18704,23 @@ FeatureFlagManager.prototype.isSystemEnabled = function() {
18589
18704
  return !!this.getMpConfig(FLAGS_CONFIG_KEY);
18590
18705
  };
18591
18706
 
18707
+ FeatureFlagManager.prototype.updateContext = function(newContext, options) {
18708
+ if (!this.isSystemEnabled()) {
18709
+ logger.critical('Feature Flags not enabled, cannot update context');
18710
+ return Promise.resolve();
18711
+ }
18712
+
18713
+ var ffConfig = this.getMpConfig(FLAGS_CONFIG_KEY);
18714
+ if (!_.isObject(ffConfig)) {
18715
+ ffConfig = {};
18716
+ }
18717
+ var oldContext = (options && options['replace']) ? {} : this.getConfig(CONFIG_CONTEXT);
18718
+ ffConfig[CONFIG_CONTEXT] = _.extend({}, oldContext, newContext);
18719
+
18720
+ this.setMpConfig(FLAGS_CONFIG_KEY, ffConfig);
18721
+ return this.fetchFlags();
18722
+ };
18723
+
18592
18724
  FeatureFlagManager.prototype.areFlagsReady = function() {
18593
18725
  if (!this.isSystemEnabled()) {
18594
18726
  logger.error('Feature Flags not enabled');
@@ -18598,15 +18730,17 @@ FeatureFlagManager.prototype.areFlagsReady = function() {
18598
18730
 
18599
18731
  FeatureFlagManager.prototype.fetchFlags = function() {
18600
18732
  if (!this.isSystemEnabled()) {
18601
- return;
18733
+ return Promise.resolve();
18602
18734
  }
18603
18735
 
18604
- var distinctId = this.getDistinctId();
18736
+ var distinctId = this.getMpProperty('distinct_id');
18737
+ var deviceId = this.getMpProperty('$device_id');
18605
18738
  logger.log('Fetching flags for distinct ID: ' + distinctId);
18606
18739
  var reqParams = {
18607
- 'context': _.extend({'distinct_id': distinctId}, this.getConfig(CONFIG_CONTEXT))
18740
+ 'context': _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT))
18608
18741
  };
18609
- this.fetchPromise = win['fetch'](this.getMpConfig('api_host') + '/' + this.getMpConfig('api_routes')['flags'], {
18742
+ this._fetchInProgressStartTime = Date.now();
18743
+ this.fetchPromise = win['fetch'](this.getFullApiRoute(), {
18610
18744
  'method': 'POST',
18611
18745
  'headers': {
18612
18746
  'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
@@ -18614,6 +18748,7 @@ FeatureFlagManager.prototype.fetchFlags = function() {
18614
18748
  },
18615
18749
  'body': JSON.stringify(reqParams)
18616
18750
  }).then(function(response) {
18751
+ this.markFetchComplete();
18617
18752
  return response.json().then(function(responseBody) {
18618
18753
  var responseFlags = responseBody['flags'];
18619
18754
  if (!responseFlags) {
@@ -18628,9 +18763,26 @@ FeatureFlagManager.prototype.fetchFlags = function() {
18628
18763
  });
18629
18764
  this.flags = flags;
18630
18765
  }.bind(this)).catch(function(error) {
18766
+ this.markFetchComplete();
18631
18767
  logger.error(error);
18632
- });
18633
- }.bind(this)).catch(function() {});
18768
+ }.bind(this));
18769
+ }.bind(this)).catch(function(error) {
18770
+ this.markFetchComplete();
18771
+ logger.error(error);
18772
+ }.bind(this));
18773
+
18774
+ return this.fetchPromise;
18775
+ };
18776
+
18777
+ FeatureFlagManager.prototype.markFetchComplete = function() {
18778
+ if (!this._fetchInProgressStartTime) {
18779
+ logger.error('Fetch in progress started time not set, cannot mark fetch complete');
18780
+ return;
18781
+ }
18782
+ this._fetchStartTime = this._fetchInProgressStartTime;
18783
+ this._fetchCompleteTime = Date.now();
18784
+ this._fetchLatency = this._fetchCompleteTime - this._fetchStartTime;
18785
+ this._fetchInProgressStartTime = null;
18634
18786
  };
18635
18787
 
18636
18788
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
@@ -18709,7 +18861,10 @@ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature)
18709
18861
  this.track('$experiment_started', {
18710
18862
  'Experiment name': featureName,
18711
18863
  'Variant name': feature['key'],
18712
- '$experiment_type': 'feature_flag'
18864
+ '$experiment_type': 'feature_flag',
18865
+ 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
18866
+ 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
18867
+ 'Variant fetch latency (ms)': this._fetchLatency
18713
18868
  });
18714
18869
  };
18715
18870
 
@@ -18729,6 +18884,7 @@ FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype
18729
18884
  FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
18730
18885
  FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
18731
18886
  FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
18887
+ FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
18732
18888
 
18733
18889
  // Deprecated method
18734
18890
  FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
@@ -20189,7 +20345,7 @@ var DEFAULT_CONFIG = {
20189
20345
  'batch_autostart': true,
20190
20346
  'hooks': {},
20191
20347
  'record_block_class': new RegExp('^(mp-block|fs-exclude|amp-block|rr-block|ph-no-capture)$'),
20192
- 'record_block_selector': 'img, video',
20348
+ 'record_block_selector': 'img, video, audio',
20193
20349
  'record_canvas': false,
20194
20350
  'record_collect_fonts': false,
20195
20351
  'record_heatmap_data': false,
@@ -20409,8 +20565,12 @@ MixpanelLib.prototype._init = function(token, config, name) {
20409
20565
  }
20410
20566
 
20411
20567
  this.flags = new FeatureFlagManager({
20568
+ getFullApiRoute: _.bind(function() {
20569
+ return this.get_api_host('flags') + '/' + this.get_config('api_routes')['flags'];
20570
+ }, this),
20412
20571
  getConfigFunc: _.bind(this.get_config, this),
20413
- getDistinctIdFunc: _.bind(this.get_distinct_id, this),
20572
+ setConfigFunc: _.bind(this.set_config, this),
20573
+ getPropertyFunc: _.bind(this.get_property, this),
20414
20574
  trackingFunc: _.bind(this.track, this)
20415
20575
  });
20416
20576
  this.flags.init();
@@ -20896,11 +21056,10 @@ MixpanelLib.prototype.are_batchers_initialized = function() {
20896
21056
 
20897
21057
  MixpanelLib.prototype.get_batcher_configs = function() {
20898
21058
  var queue_prefix = '__mpq_' + this.get_config('token');
20899
- var api_routes = this.get_config('api_routes');
20900
21059
  this._batcher_configs = this._batcher_configs || {
20901
- events: {type: 'events', endpoint: '/' + api_routes['track'], queue_key: queue_prefix + '_ev'},
20902
- people: {type: 'people', endpoint: '/' + api_routes['engage'], queue_key: queue_prefix + '_pp'},
20903
- groups: {type: 'groups', endpoint: '/' + api_routes['groups'], queue_key: queue_prefix + '_gr'}
21060
+ events: {type: 'events', api_name: 'track', queue_key: queue_prefix + '_ev'},
21061
+ people: {type: 'people', api_name: 'engage', queue_key: queue_prefix + '_pp'},
21062
+ groups: {type: 'groups', api_name: 'groups', queue_key: queue_prefix + '_gr'}
20904
21063
  };
20905
21064
  return this._batcher_configs;
20906
21065
  };
@@ -20914,8 +21073,9 @@ MixpanelLib.prototype.init_batchers = function() {
20914
21073
  libConfig: this['config'],
20915
21074
  errorReporter: this.get_config('error_reporter'),
20916
21075
  sendRequestFunc: _.bind(function(data, options, cb) {
21076
+ var api_routes = this.get_config('api_routes');
20917
21077
  this._send_request(
20918
- this.get_config('api_host') + attrs.endpoint,
21078
+ this.get_api_host(attrs.api_name) + '/' + api_routes[attrs.api_name],
20919
21079
  this._encode_data_for_request(data),
20920
21080
  options,
20921
21081
  this._prepare_callback(cb, data)
@@ -21643,31 +21803,15 @@ MixpanelLib.prototype.identify = function(
21643
21803
  * Useful for clearing data when a user logs out.
21644
21804
  */
21645
21805
  MixpanelLib.prototype.reset = function() {
21646
- var self = this;
21647
-
21648
- var reset = function () {
21649
- self['persistence'].clear();
21650
- self._flags.identify_called = false;
21651
- var uuid = _.UUID();
21652
- self.register_once({
21653
- 'distinct_id': DEVICE_ID_PREFIX + uuid,
21654
- '$device_id': uuid
21655
- }, '');
21656
- };
21657
-
21658
- if (self._recorder) {
21659
- self.stop_session_recording()
21660
- .then(function () {
21661
- reset();
21662
- self._check_and_start_session_recording();
21663
- })
21664
- .catch(_.bind(function (err) {
21665
- reset();
21666
- this.report_error('Error restarting recording session', err);
21667
- }, this));
21668
- } else {
21669
- reset();
21670
- }
21806
+ this.stop_session_recording();
21807
+ this['persistence'].clear();
21808
+ this._flags.identify_called = false;
21809
+ var uuid = _.UUID();
21810
+ this.register_once({
21811
+ 'distinct_id': DEVICE_ID_PREFIX + uuid,
21812
+ '$device_id': uuid
21813
+ }, '');
21814
+ this._check_and_start_session_recording();
21671
21815
  };
21672
21816
 
21673
21817
  /**