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