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