mixpanel-browser 2.63.0 → 2.64.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 @@ if (typeof Promise !== 'undefined' && Promise.toString().indexOf('[native code]'
13944
13944
 
13945
13945
  var Config = {
13946
13946
  DEBUG: false,
13947
- LIB_VERSION: '2.63.0'
13947
+ LIB_VERSION: '2.64.0'
13948
13948
  };
13949
13949
 
13950
13950
  /* eslint camelcase: "off", eqeqeq: "off" */
@@ -16103,7 +16103,7 @@ function _addOptOutCheck(method, getConfigValue) {
16103
16103
  };
16104
16104
  }
16105
16105
 
16106
- var logger$5 = console_with_prefix('lock');
16106
+ var logger$6 = console_with_prefix('lock');
16107
16107
 
16108
16108
  /**
16109
16109
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -16155,7 +16155,7 @@ SharedLock.prototype.withLock = function(lockedCB, pid) {
16155
16155
 
16156
16156
  var delay = function(cb) {
16157
16157
  if (new Date().getTime() - startTime > timeoutMS) {
16158
- logger$5.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
16158
+ logger$6.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
16159
16159
  storage.removeItem(keyZ);
16160
16160
  storage.removeItem(keyY);
16161
16161
  loop();
@@ -16298,7 +16298,7 @@ LocalStorageWrapper.prototype.removeItem = function (key) {
16298
16298
  }, this));
16299
16299
  };
16300
16300
 
16301
- var logger$4 = console_with_prefix('batch');
16301
+ var logger$5 = console_with_prefix('batch');
16302
16302
 
16303
16303
  /**
16304
16304
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -16327,7 +16327,7 @@ var RequestQueue = function (storageKey, options) {
16327
16327
  timeoutMS: options.sharedLockTimeoutMS,
16328
16328
  });
16329
16329
  }
16330
- this.reportError = options.errorReporter || _.bind(logger$4.error, logger$4);
16330
+ this.reportError = options.errorReporter || _.bind(logger$5.error, logger$5);
16331
16331
 
16332
16332
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
16333
16333
 
@@ -16660,7 +16660,7 @@ RequestQueue.prototype.clear = function () {
16660
16660
  // maximum interval between request retries after exponential backoff
16661
16661
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
16662
16662
 
16663
- var logger$3 = console_with_prefix('batch');
16663
+ var logger$4 = console_with_prefix('batch');
16664
16664
 
16665
16665
  /**
16666
16666
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -16788,7 +16788,7 @@ RequestBatcher.prototype.sendRequestPromise = function(data, options) {
16788
16788
  */
16789
16789
  RequestBatcher.prototype.flush = function(options) {
16790
16790
  if (this.requestInProgress) {
16791
- logger$3.log('Flush: Request already in progress');
16791
+ logger$4.log('Flush: Request already in progress');
16792
16792
  return PromisePolyfill.resolve();
16793
16793
  }
16794
16794
 
@@ -16965,7 +16965,7 @@ RequestBatcher.prototype.flush = function(options) {
16965
16965
  if (options.unloading) {
16966
16966
  requestOptions.transport = 'sendBeacon';
16967
16967
  }
16968
- logger$3.log('MIXPANEL REQUEST:', dataForRequest);
16968
+ logger$4.log('MIXPANEL REQUEST:', dataForRequest);
16969
16969
  return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
16970
16970
  }, this))
16971
16971
  .catch(_.bind(function(err) {
@@ -16978,7 +16978,7 @@ RequestBatcher.prototype.flush = function(options) {
16978
16978
  * Log error to global logger and optional user-defined logger.
16979
16979
  */
16980
16980
  RequestBatcher.prototype.reportError = function(msg, err) {
16981
- logger$3.error.apply(logger$3.error, arguments);
16981
+ logger$4.error.apply(logger$4.error, arguments);
16982
16982
  if (this.errorReporter) {
16983
16983
  try {
16984
16984
  if (!(err instanceof Error)) {
@@ -16986,7 +16986,7 @@ RequestBatcher.prototype.reportError = function(msg, err) {
16986
16986
  }
16987
16987
  this.errorReporter(msg, err);
16988
16988
  } catch(err) {
16989
- logger$3.error(err);
16989
+ logger$4.error(err);
16990
16990
  }
16991
16991
  }
16992
16992
  };
@@ -17002,7 +17002,7 @@ var isRecordingExpired = function(serializedRecording) {
17002
17002
 
17003
17003
  var RECORD_ENQUEUE_THROTTLE_MS = 250;
17004
17004
 
17005
- var logger$2 = console_with_prefix('recorder');
17005
+ var logger$3 = console_with_prefix('recorder');
17006
17006
  var CompressionStream = win['CompressionStream'];
17007
17007
 
17008
17008
  var RECORDER_BATCHER_LIB_CONFIG = {
@@ -17139,14 +17139,14 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
17139
17139
  }
17140
17140
 
17141
17141
  if (this._stopRecording !== null) {
17142
- logger$2.log('Recording already in progress, skipping startRecording.');
17142
+ logger$3.log('Recording already in progress, skipping startRecording.');
17143
17143
  return;
17144
17144
  }
17145
17145
 
17146
17146
  this.recordMaxMs = this.getConfig('record_max_ms');
17147
17147
  if (this.recordMaxMs > MAX_RECORDING_MS) {
17148
17148
  this.recordMaxMs = MAX_RECORDING_MS;
17149
- logger$2.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
17149
+ logger$3.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
17150
17150
  }
17151
17151
 
17152
17152
  if (!this.maxExpires) {
@@ -17156,7 +17156,7 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
17156
17156
  this.recordMinMs = this.getConfig('record_min_ms');
17157
17157
  if (this.recordMinMs > MAX_VALUE_FOR_MIN_RECORDING_MS) {
17158
17158
  this.recordMinMs = MAX_VALUE_FOR_MIN_RECORDING_MS;
17159
- logger$2.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
17159
+ logger$3.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
17160
17160
  }
17161
17161
 
17162
17162
  if (!this.replayStartTime) {
@@ -17440,14 +17440,14 @@ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
17440
17440
 
17441
17441
 
17442
17442
  SessionRecording.prototype.reportError = function(msg, err) {
17443
- logger$2.error.apply(logger$2.error, arguments);
17443
+ logger$3.error.apply(logger$3.error, arguments);
17444
17444
  try {
17445
17445
  if (!err && !(msg instanceof Error)) {
17446
17446
  msg = new Error(msg);
17447
17447
  }
17448
17448
  this.getConfig('error_reporter')(msg, err);
17449
17449
  } catch(err) {
17450
- logger$2.error(err);
17450
+ logger$3.error(err);
17451
17451
  }
17452
17452
  };
17453
17453
 
@@ -17543,7 +17543,7 @@ RecordingRegistry.prototype.flushInactiveRecordings = function () {
17543
17543
  .catch(this.handleError.bind(this));
17544
17544
  };
17545
17545
 
17546
- var logger$1 = console_with_prefix('recorder');
17546
+ var logger$2 = console_with_prefix('recorder');
17547
17547
 
17548
17548
  /**
17549
17549
  * Recorder API: bundles rrweb and and exposes methods to start and stop recordings.
@@ -17559,7 +17559,7 @@ var MixpanelRecorder = function(mixpanelInstance, rrwebRecord, sharedLockStorage
17559
17559
  */
17560
17560
  this.recordingRegistry = new RecordingRegistry({
17561
17561
  mixpanelInstance: this.mixpanelInstance,
17562
- errorReporter: logger$1.error,
17562
+ errorReporter: logger$2.error,
17563
17563
  sharedLockStorage: sharedLockStorage
17564
17564
  });
17565
17565
  this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
@@ -17570,17 +17570,17 @@ var MixpanelRecorder = function(mixpanelInstance, rrwebRecord, sharedLockStorage
17570
17570
  MixpanelRecorder.prototype.startRecording = function(options) {
17571
17571
  options = options || {};
17572
17572
  if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
17573
- logger$1.log('Recording already in progress, skipping startRecording.');
17573
+ logger$2.log('Recording already in progress, skipping startRecording.');
17574
17574
  return;
17575
17575
  }
17576
17576
 
17577
17577
  var onIdleTimeout = function () {
17578
- logger$1.log('Idle timeout reached, restarting recording.');
17578
+ logger$2.log('Idle timeout reached, restarting recording.');
17579
17579
  this.resetRecording();
17580
17580
  }.bind(this);
17581
17581
 
17582
17582
  var onMaxLengthReached = function () {
17583
- logger$1.log('Max recording length reached, stopping recording.');
17583
+ logger$2.log('Max recording length reached, stopping recording.');
17584
17584
  this.resetRecording();
17585
17585
  }.bind(this);
17586
17586
 
@@ -17643,7 +17643,7 @@ MixpanelRecorder.prototype.resumeRecording = function (startNewIfInactive) {
17643
17643
  } else if (startNewIfInactive) {
17644
17644
  return this.startRecording({shouldStopBatcher: false});
17645
17645
  } else {
17646
- logger$1.log('No resumable recording found.');
17646
+ logger$2.log('No resumable recording found.');
17647
17647
  return null;
17648
17648
  }
17649
17649
  }.bind(this));
@@ -17701,7 +17701,7 @@ var TRACKED_ATTRS = [
17701
17701
  'href', 'name', 'role', 'title', 'type'
17702
17702
  ];
17703
17703
 
17704
- var logger = console_with_prefix('autocapture');
17704
+ var logger$1 = console_with_prefix('autocapture');
17705
17705
 
17706
17706
 
17707
17707
  function getClasses(el) {
@@ -17787,6 +17787,7 @@ function getPropsForDOMEvent(ev, config) {
17787
17787
  var blockSelectors = config.blockSelectors || [];
17788
17788
  var captureTextContent = config.captureTextContent || false;
17789
17789
  var captureExtraAttrs = config.captureExtraAttrs || [];
17790
+ var capturedForHeatMap = config.capturedForHeatMap || false;
17790
17791
 
17791
17792
  // convert array to set every time, as the config may have changed
17792
17793
  var blockAttrsSet = {};
@@ -17865,6 +17866,9 @@ function getPropsForDOMEvent(ev, config) {
17865
17866
  props['$' + prop] = ev[prop];
17866
17867
  }
17867
17868
  });
17869
+ if (capturedForHeatMap) {
17870
+ props['$captured_for_heatmap'] = true;
17871
+ }
17868
17872
  target = guessRealClickTarget(ev);
17869
17873
  }
17870
17874
  // prioritize text content from "real" click target if different from original target
@@ -17959,7 +17963,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
17959
17963
  return false;
17960
17964
  }
17961
17965
  } catch (err) {
17962
- logger.critical('Error while checking element in allowElementCallback', err);
17966
+ logger$1.critical('Error while checking element in allowElementCallback', err);
17963
17967
  return false;
17964
17968
  }
17965
17969
  }
@@ -17976,7 +17980,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
17976
17980
  return true;
17977
17981
  }
17978
17982
  } catch (err) {
17979
- logger.critical('Error while checking selector: ' + sel, err);
17983
+ logger$1.critical('Error while checking selector: ' + sel, err);
17980
17984
  }
17981
17985
  }
17982
17986
  return false;
@@ -17991,7 +17995,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
17991
17995
  return true;
17992
17996
  }
17993
17997
  } catch (err) {
17994
- logger.critical('Error while checking element in blockElementCallback', err);
17998
+ logger$1.critical('Error while checking element in blockElementCallback', err);
17995
17999
  return true;
17996
18000
  }
17997
18001
  }
@@ -18005,7 +18009,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
18005
18009
  return true;
18006
18010
  }
18007
18011
  } catch (err) {
18008
- logger.critical('Error while checking selector: ' + sel, err);
18012
+ logger$1.critical('Error while checking selector: ' + sel, err);
18009
18013
  }
18010
18014
  }
18011
18015
  }
@@ -18211,22 +18215,22 @@ var CONFIG_TRACK_PAGEVIEW = 'pageview';
18211
18215
  var CONFIG_TRACK_SCROLL = 'scroll';
18212
18216
  var CONFIG_TRACK_SUBMIT = 'submit';
18213
18217
 
18214
- var CONFIG_DEFAULTS = {};
18215
- CONFIG_DEFAULTS[CONFIG_ALLOW_SELECTORS] = [];
18216
- CONFIG_DEFAULTS[CONFIG_ALLOW_URL_REGEXES] = [];
18217
- CONFIG_DEFAULTS[CONFIG_BLOCK_ATTRS] = [];
18218
- CONFIG_DEFAULTS[CONFIG_BLOCK_ELEMENT_CALLBACK] = null;
18219
- CONFIG_DEFAULTS[CONFIG_BLOCK_SELECTORS] = [];
18220
- CONFIG_DEFAULTS[CONFIG_BLOCK_URL_REGEXES] = [];
18221
- CONFIG_DEFAULTS[CONFIG_CAPTURE_EXTRA_ATTRS] = [];
18222
- CONFIG_DEFAULTS[CONFIG_CAPTURE_TEXT_CONTENT] = false;
18223
- CONFIG_DEFAULTS[CONFIG_SCROLL_CAPTURE_ALL] = false;
18224
- CONFIG_DEFAULTS[CONFIG_SCROLL_CHECKPOINTS] = [25, 50, 75, 100];
18225
- CONFIG_DEFAULTS[CONFIG_TRACK_CLICK] = true;
18226
- CONFIG_DEFAULTS[CONFIG_TRACK_INPUT] = true;
18227
- CONFIG_DEFAULTS[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
18228
- CONFIG_DEFAULTS[CONFIG_TRACK_SCROLL] = true;
18229
- CONFIG_DEFAULTS[CONFIG_TRACK_SUBMIT] = true;
18218
+ var CONFIG_DEFAULTS$1 = {};
18219
+ CONFIG_DEFAULTS$1[CONFIG_ALLOW_SELECTORS] = [];
18220
+ CONFIG_DEFAULTS$1[CONFIG_ALLOW_URL_REGEXES] = [];
18221
+ CONFIG_DEFAULTS$1[CONFIG_BLOCK_ATTRS] = [];
18222
+ CONFIG_DEFAULTS$1[CONFIG_BLOCK_ELEMENT_CALLBACK] = null;
18223
+ CONFIG_DEFAULTS$1[CONFIG_BLOCK_SELECTORS] = [];
18224
+ CONFIG_DEFAULTS$1[CONFIG_BLOCK_URL_REGEXES] = [];
18225
+ CONFIG_DEFAULTS$1[CONFIG_CAPTURE_EXTRA_ATTRS] = [];
18226
+ CONFIG_DEFAULTS$1[CONFIG_CAPTURE_TEXT_CONTENT] = false;
18227
+ CONFIG_DEFAULTS$1[CONFIG_SCROLL_CAPTURE_ALL] = false;
18228
+ CONFIG_DEFAULTS$1[CONFIG_SCROLL_CHECKPOINTS] = [25, 50, 75, 100];
18229
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_CLICK] = true;
18230
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_INPUT] = true;
18231
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
18232
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_SCROLL] = true;
18233
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_SUBMIT] = true;
18230
18234
 
18231
18235
  var DEFAULT_PROPS = {
18232
18236
  '$mp_autocapture': true
@@ -18247,7 +18251,7 @@ var Autocapture = function(mp) {
18247
18251
 
18248
18252
  Autocapture.prototype.init = function() {
18249
18253
  if (!minDOMApisSupported()) {
18250
- logger.critical('Autocapture unavailable: missing required DOM APIs');
18254
+ logger$1.critical('Autocapture unavailable: missing required DOM APIs');
18251
18255
  return;
18252
18256
  }
18253
18257
 
@@ -18264,10 +18268,10 @@ Autocapture.prototype.getFullConfig = function() {
18264
18268
  // Autocapture is completely off
18265
18269
  return {};
18266
18270
  } else if (_.isObject(autocaptureConfig)) {
18267
- return _.extend({}, CONFIG_DEFAULTS, autocaptureConfig);
18271
+ return _.extend({}, CONFIG_DEFAULTS$1, autocaptureConfig);
18268
18272
  } else {
18269
18273
  // Autocapture config is non-object truthy value, return default
18270
- return CONFIG_DEFAULTS;
18274
+ return CONFIG_DEFAULTS$1;
18271
18275
  }
18272
18276
  };
18273
18277
 
@@ -18291,7 +18295,7 @@ Autocapture.prototype.currentUrlBlocked = function() {
18291
18295
  break;
18292
18296
  }
18293
18297
  } catch (err) {
18294
- logger.critical('Error while checking block URL regex: ' + allowRegex, err);
18298
+ logger$1.critical('Error while checking block URL regex: ' + allowRegex, err);
18295
18299
  return true;
18296
18300
  }
18297
18301
  }
@@ -18312,7 +18316,7 @@ Autocapture.prototype.currentUrlBlocked = function() {
18312
18316
  return true;
18313
18317
  }
18314
18318
  } catch (err) {
18315
- logger.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
18319
+ logger$1.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
18316
18320
  return true;
18317
18321
  }
18318
18322
  }
@@ -18341,7 +18345,8 @@ Autocapture.prototype.trackDomEvent = function(ev, mpEventName) {
18341
18345
  blockElementCallback: this.getConfig(CONFIG_BLOCK_ELEMENT_CALLBACK),
18342
18346
  blockSelectors: this.getConfig(CONFIG_BLOCK_SELECTORS),
18343
18347
  captureExtraAttrs: this.getConfig(CONFIG_CAPTURE_EXTRA_ATTRS),
18344
- captureTextContent: this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT)
18348
+ captureTextContent: this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT),
18349
+ capturedForHeatMap: mpEventName === MP_EV_CLICK && !this.getConfig(CONFIG_TRACK_CLICK) && this.mp.is_recording_heatmap_data(),
18345
18350
  });
18346
18351
  if (props) {
18347
18352
  _.extend(props, DEFAULT_PROPS);
@@ -18352,13 +18357,13 @@ Autocapture.prototype.trackDomEvent = function(ev, mpEventName) {
18352
18357
  Autocapture.prototype.initClickTracking = function() {
18353
18358
  win.removeEventListener(EV_CLICK, this.listenerClick);
18354
18359
 
18355
- if (!this.getConfig(CONFIG_TRACK_CLICK)) {
18360
+ if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
18356
18361
  return;
18357
18362
  }
18358
- logger.log('Initializing click tracking');
18363
+ logger$1.log('Initializing click tracking');
18359
18364
 
18360
18365
  this.listenerClick = win.addEventListener(EV_CLICK, function(ev) {
18361
- if (!this.getConfig(CONFIG_TRACK_CLICK)) {
18366
+ if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
18362
18367
  return;
18363
18368
  }
18364
18369
  this.trackDomEvent(ev, MP_EV_CLICK);
@@ -18371,7 +18376,7 @@ Autocapture.prototype.initInputTracking = function() {
18371
18376
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
18372
18377
  return;
18373
18378
  }
18374
- logger.log('Initializing input tracking');
18379
+ logger$1.log('Initializing input tracking');
18375
18380
 
18376
18381
  this.listenerChange = win.addEventListener(EV_CHANGE, function(ev) {
18377
18382
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
@@ -18389,7 +18394,7 @@ Autocapture.prototype.initPageviewTracking = function() {
18389
18394
  if (!this.pageviewTrackingConfig()) {
18390
18395
  return;
18391
18396
  }
18392
- logger.log('Initializing pageview tracking');
18397
+ logger$1.log('Initializing pageview tracking');
18393
18398
 
18394
18399
  var previousTrackedUrl = '';
18395
18400
  var tracked = false;
@@ -18444,7 +18449,7 @@ Autocapture.prototype.initPageviewTracking = function() {
18444
18449
  }
18445
18450
  if (didPathChange) {
18446
18451
  this.lastScrollCheckpoint = 0;
18447
- logger.log('Path change: re-initializing scroll depth checkpoints');
18452
+ logger$1.log('Path change: re-initializing scroll depth checkpoints');
18448
18453
  }
18449
18454
  }
18450
18455
  }.bind(this)));
@@ -18456,7 +18461,7 @@ Autocapture.prototype.initScrollTracking = function() {
18456
18461
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
18457
18462
  return;
18458
18463
  }
18459
- logger.log('Initializing scroll tracking');
18464
+ logger$1.log('Initializing scroll tracking');
18460
18465
  this.lastScrollCheckpoint = 0;
18461
18466
 
18462
18467
  this.listenerScroll = win.addEventListener(EV_SCROLLEND, safewrap(function() {
@@ -18493,7 +18498,7 @@ Autocapture.prototype.initScrollTracking = function() {
18493
18498
  }
18494
18499
  }
18495
18500
  } catch (err) {
18496
- logger.critical('Error while calculating scroll percentage', err);
18501
+ logger$1.critical('Error while calculating scroll percentage', err);
18497
18502
  }
18498
18503
  if (shouldTrack) {
18499
18504
  this.mp.track(MP_EV_SCROLL, props);
@@ -18507,7 +18512,7 @@ Autocapture.prototype.initSubmitTracking = function() {
18507
18512
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
18508
18513
  return;
18509
18514
  }
18510
- logger.log('Initializing submit tracking');
18515
+ logger$1.log('Initializing submit tracking');
18511
18516
 
18512
18517
  this.listenerSubmit = win.addEventListener(EV_SUBMIT, function(ev) {
18513
18518
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
@@ -18520,6 +18525,193 @@ Autocapture.prototype.initSubmitTracking = function() {
18520
18525
  // TODO integrate error_reporter from mixpanel instance
18521
18526
  safewrapClass(Autocapture);
18522
18527
 
18528
+ var fetch = win['fetch'];
18529
+ var logger = console_with_prefix('flags');
18530
+
18531
+ var FLAGS_CONFIG_KEY = 'flags';
18532
+
18533
+ var CONFIG_CONTEXT = 'context';
18534
+ var CONFIG_DEFAULTS = {};
18535
+ CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
18536
+
18537
+ /**
18538
+ * FeatureFlagManager: support for Mixpanel's feature flagging product
18539
+ * @constructor
18540
+ */
18541
+ var FeatureFlagManager = function(initOptions) {
18542
+ this.getMpConfig = initOptions.getConfigFunc;
18543
+ this.getDistinctId = initOptions.getDistinctIdFunc;
18544
+ this.track = initOptions.trackingFunc;
18545
+ };
18546
+
18547
+ FeatureFlagManager.prototype.init = function() {
18548
+ if (!minApisSupported()) {
18549
+ logger.critical('Feature Flags unavailable: missing minimum required APIs');
18550
+ return;
18551
+ }
18552
+
18553
+ this.flags = null;
18554
+ this.fetchFlags();
18555
+
18556
+ this.trackedFeatures = new Set();
18557
+ };
18558
+
18559
+ FeatureFlagManager.prototype.getFullConfig = function() {
18560
+ var ffConfig = this.getMpConfig(FLAGS_CONFIG_KEY);
18561
+ if (!ffConfig) {
18562
+ // flags are completely off
18563
+ return {};
18564
+ } else if (_.isObject(ffConfig)) {
18565
+ return _.extend({}, CONFIG_DEFAULTS, ffConfig);
18566
+ } else {
18567
+ // config is non-object truthy value, return default
18568
+ return CONFIG_DEFAULTS;
18569
+ }
18570
+ };
18571
+
18572
+ FeatureFlagManager.prototype.getConfig = function(key) {
18573
+ return this.getFullConfig()[key];
18574
+ };
18575
+
18576
+ FeatureFlagManager.prototype.isEnabled = function() {
18577
+ return !!this.getMpConfig(FLAGS_CONFIG_KEY);
18578
+ };
18579
+
18580
+ FeatureFlagManager.prototype.areFeaturesReady = function() {
18581
+ if (!this.isEnabled()) {
18582
+ logger.error('Feature Flags not enabled');
18583
+ }
18584
+ return !!this.flags;
18585
+ };
18586
+
18587
+ FeatureFlagManager.prototype.fetchFlags = function() {
18588
+ if (!this.isEnabled()) {
18589
+ return;
18590
+ }
18591
+
18592
+ var distinctId = this.getDistinctId();
18593
+ logger.log('Fetching flags for distinct ID: ' + distinctId);
18594
+ var reqParams = {
18595
+ 'context': _.extend({'distinct_id': distinctId}, this.getConfig(CONFIG_CONTEXT))
18596
+ };
18597
+ this.fetchPromise = win['fetch'](this.getMpConfig('api_host') + '/' + this.getMpConfig('api_routes')['flags'], {
18598
+ 'method': 'POST',
18599
+ 'headers': {
18600
+ 'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
18601
+ 'Content-Type': 'application/octet-stream'
18602
+ },
18603
+ 'body': JSON.stringify(reqParams)
18604
+ }).then(function(response) {
18605
+ return response.json().then(function(responseBody) {
18606
+ var responseFlags = responseBody['flags'];
18607
+ if (!responseFlags) {
18608
+ throw new Error('No flags in API response');
18609
+ }
18610
+ var flags = new Map();
18611
+ _.each(responseFlags, function(data, key) {
18612
+ flags.set(key, {
18613
+ 'key': data['variant_key'],
18614
+ 'data': data['variant_value']
18615
+ });
18616
+ });
18617
+ this.flags = flags;
18618
+ }.bind(this)).catch(function(error) {
18619
+ logger.error(error);
18620
+ });
18621
+ }.bind(this)).catch(function() {});
18622
+ };
18623
+
18624
+ FeatureFlagManager.prototype.getFeature = function(featureName, fallback) {
18625
+ if (!this.fetchPromise) {
18626
+ return new Promise(function(resolve) {
18627
+ logger.critical('Feature Flags not initialized');
18628
+ resolve(fallback);
18629
+ });
18630
+ }
18631
+
18632
+ return this.fetchPromise.then(function() {
18633
+ return this.getFeatureSync(featureName, fallback);
18634
+ }.bind(this)).catch(function(error) {
18635
+ logger.error(error);
18636
+ return fallback;
18637
+ });
18638
+ };
18639
+
18640
+ FeatureFlagManager.prototype.getFeatureSync = function(featureName, fallback) {
18641
+ if (!this.areFeaturesReady()) {
18642
+ logger.log('Flags not loaded yet');
18643
+ return fallback;
18644
+ }
18645
+ var feature = this.flags.get(featureName);
18646
+ if (!feature) {
18647
+ logger.log('No flag found: "' + featureName + '"');
18648
+ return fallback;
18649
+ }
18650
+ this.trackFeatureCheck(featureName, feature);
18651
+ return feature;
18652
+ };
18653
+
18654
+ FeatureFlagManager.prototype.getFeatureData = function(featureName, fallbackValue) {
18655
+ return this.getFeature(featureName, {'data': fallbackValue}).then(function(feature) {
18656
+ return feature['data'];
18657
+ }).catch(function(error) {
18658
+ logger.error(error);
18659
+ return fallbackValue;
18660
+ });
18661
+ };
18662
+
18663
+ FeatureFlagManager.prototype.getFeatureDataSync = function(featureName, fallbackValue) {
18664
+ return this.getFeatureSync(featureName, {'data': fallbackValue})['data'];
18665
+ };
18666
+
18667
+ FeatureFlagManager.prototype.isFeatureEnabled = function(featureName, fallbackValue) {
18668
+ return this.getFeatureData(featureName).then(function() {
18669
+ return this.isFeatureEnabledSync(featureName, fallbackValue);
18670
+ }.bind(this)).catch(function(error) {
18671
+ logger.error(error);
18672
+ return fallbackValue;
18673
+ });
18674
+ };
18675
+
18676
+ FeatureFlagManager.prototype.isFeatureEnabledSync = function(featureName, fallbackValue) {
18677
+ fallbackValue = fallbackValue || false;
18678
+ var val = this.getFeatureDataSync(featureName, fallbackValue);
18679
+ if (val !== true && val !== false) {
18680
+ logger.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
18681
+ val = fallbackValue;
18682
+ }
18683
+ return val;
18684
+ };
18685
+
18686
+ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
18687
+ if (this.trackedFeatures.has(featureName)) {
18688
+ return;
18689
+ }
18690
+ this.trackedFeatures.add(featureName);
18691
+ this.track('$experiment_started', {
18692
+ 'Experiment name': featureName,
18693
+ 'Variant name': feature['key'],
18694
+ '$experiment_type': 'feature_flag'
18695
+ });
18696
+ };
18697
+
18698
+ function minApisSupported() {
18699
+ return !!fetch &&
18700
+ typeof Promise !== 'undefined' &&
18701
+ typeof Map !== 'undefined' &&
18702
+ typeof Set !== 'undefined';
18703
+ }
18704
+
18705
+ safewrapClass(FeatureFlagManager);
18706
+
18707
+ FeatureFlagManager.prototype['are_features_ready'] = FeatureFlagManager.prototype.areFeaturesReady;
18708
+ FeatureFlagManager.prototype['get_feature'] = FeatureFlagManager.prototype.getFeature;
18709
+ FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
18710
+ FeatureFlagManager.prototype['get_feature_data_sync'] = FeatureFlagManager.prototype.getFeatureDataSync;
18711
+ FeatureFlagManager.prototype['get_feature_sync'] = FeatureFlagManager.prototype.getFeatureSync;
18712
+ FeatureFlagManager.prototype['is_feature_enabled'] = FeatureFlagManager.prototype.isFeatureEnabled;
18713
+ FeatureFlagManager.prototype['is_feature_enabled_sync'] = FeatureFlagManager.prototype.isFeatureEnabledSync;
18714
+
18523
18715
  /* eslint camelcase: "off" */
18524
18716
 
18525
18717
 
@@ -19924,10 +20116,11 @@ if (navigator['sendBeacon']) {
19924
20116
  }
19925
20117
 
19926
20118
  var DEFAULT_API_ROUTES = {
19927
- 'track': 'track/',
20119
+ 'track': 'track/',
19928
20120
  'engage': 'engage/',
19929
20121
  'groups': 'groups/',
19930
- 'record': 'record/'
20122
+ 'record': 'record/',
20123
+ 'flags': 'flags/'
19931
20124
  };
19932
20125
 
19933
20126
  /*
@@ -19945,6 +20138,7 @@ var DEFAULT_CONFIG = {
19945
20138
  'cross_site_cookie': false,
19946
20139
  'cross_subdomain_cookie': true,
19947
20140
  'error_reporter': NOOP_FUNC,
20141
+ 'flags': false,
19948
20142
  'persistence': 'cookie',
19949
20143
  'persistence_name': '',
19950
20144
  'cookie_domain': '',
@@ -19985,6 +20179,7 @@ var DEFAULT_CONFIG = {
19985
20179
  'record_block_selector': 'img, video',
19986
20180
  'record_canvas': false,
19987
20181
  'record_collect_fonts': false,
20182
+ 'record_heatmap_data': false,
19988
20183
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
19989
20184
  'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
19990
20185
  'record_mask_text_selector': '*',
@@ -20200,6 +20395,14 @@ MixpanelLib.prototype._init = function(token, config, name) {
20200
20395
  }, '');
20201
20396
  }
20202
20397
 
20398
+ this.flags = new FeatureFlagManager({
20399
+ getConfigFunc: _.bind(this.get_config, this),
20400
+ getDistinctIdFunc: _.bind(this.get_distinct_id, this),
20401
+ trackingFunc: _.bind(this.track, this)
20402
+ });
20403
+ this.flags.init();
20404
+ this['flags'] = this.flags;
20405
+
20203
20406
  this.autocapture = new Autocapture(this);
20204
20407
  this.autocapture.init();
20205
20408
 
@@ -20325,6 +20528,10 @@ MixpanelLib.prototype.resume_session_recording = function () {
20325
20528
  }
20326
20529
  };
20327
20530
 
20531
+ MixpanelLib.prototype.is_recording_heatmap_data = function () {
20532
+ return this._get_session_replay_id() && this.get_config('record_heatmap_data');
20533
+ };
20534
+
20328
20535
  MixpanelLib.prototype.get_session_recording_properties = function () {
20329
20536
  var props = {};
20330
20537
  var replay_id = this._get_session_replay_id();
@@ -21406,6 +21613,11 @@ MixpanelLib.prototype.identify = function(
21406
21613
  '$anon_distinct_id': previous_distinct_id
21407
21614
  }, {skip_hooks: true});
21408
21615
  }
21616
+
21617
+ // check feature flags again if distinct id has changed
21618
+ if (new_distinct_id !== previous_distinct_id) {
21619
+ this.flags.fetchFlags();
21620
+ }
21409
21621
  };
21410
21622
 
21411
21623
  /**
@@ -21680,7 +21892,7 @@ MixpanelLib.prototype.set_config = function(config) {
21680
21892
  }
21681
21893
  Config.DEBUG = Config.DEBUG || this.get_config('debug');
21682
21894
 
21683
- if ('autocapture' in config && this.autocapture) {
21895
+ if (('autocapture' in config || 'record_heatmap_data' in config) && this.autocapture) {
21684
21896
  this.autocapture.init();
21685
21897
  }
21686
21898
  }