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