mixpanel-browser 2.63.0 → 2.65.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.
@@ -13948,7 +13948,7 @@
13948
13948
 
13949
13949
  var Config = {
13950
13950
  DEBUG: false,
13951
- LIB_VERSION: '2.63.0'
13951
+ LIB_VERSION: '2.65.0'
13952
13952
  };
13953
13953
 
13954
13954
  /* eslint camelcase: "off", eqeqeq: "off" */
@@ -15433,6 +15433,9 @@
15433
15433
  return 'Microsoft Edge';
15434
15434
  } else if (_.includes(user_agent, 'FBIOS')) {
15435
15435
  return 'Facebook Mobile';
15436
+ } else if (_.includes(user_agent, 'Whale/')) {
15437
+ // https://user-agents.net/browsers/whale-browser
15438
+ return 'Whale Browser';
15436
15439
  } else if (_.includes(user_agent, 'Chrome')) {
15437
15440
  return 'Chrome';
15438
15441
  } else if (_.includes(user_agent, 'CriOS')) {
@@ -15484,7 +15487,8 @@
15484
15487
  'Android Mobile': /android\s(\d+(\.\d+)?)/,
15485
15488
  'Samsung Internet': /SamsungBrowser\/(\d+(\.\d+)?)/,
15486
15489
  'Internet Explorer': /(rv:|MSIE )(\d+(\.\d+)?)/,
15487
- 'Mozilla': /rv:(\d+(\.\d+)?)/
15490
+ 'Mozilla': /rv:(\d+(\.\d+)?)/,
15491
+ 'Whale Browser': /Whale\/(\d+(\.\d+)?)/
15488
15492
  };
15489
15493
  var regex = versionRegexs[browser];
15490
15494
  if (regex === undefined) {
@@ -16107,7 +16111,7 @@
16107
16111
  };
16108
16112
  }
16109
16113
 
16110
- var logger$5 = console_with_prefix('lock');
16114
+ var logger$6 = console_with_prefix('lock');
16111
16115
 
16112
16116
  /**
16113
16117
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -16159,7 +16163,7 @@
16159
16163
 
16160
16164
  var delay = function(cb) {
16161
16165
  if (new Date().getTime() - startTime > timeoutMS) {
16162
- logger$5.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
16166
+ logger$6.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
16163
16167
  storage.removeItem(keyZ);
16164
16168
  storage.removeItem(keyY);
16165
16169
  loop();
@@ -16302,7 +16306,7 @@
16302
16306
  }, this));
16303
16307
  };
16304
16308
 
16305
- var logger$4 = console_with_prefix('batch');
16309
+ var logger$5 = console_with_prefix('batch');
16306
16310
 
16307
16311
  /**
16308
16312
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -16331,7 +16335,7 @@
16331
16335
  timeoutMS: options.sharedLockTimeoutMS,
16332
16336
  });
16333
16337
  }
16334
- this.reportError = options.errorReporter || _.bind(logger$4.error, logger$4);
16338
+ this.reportError = options.errorReporter || _.bind(logger$5.error, logger$5);
16335
16339
 
16336
16340
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
16337
16341
 
@@ -16664,7 +16668,7 @@
16664
16668
  // maximum interval between request retries after exponential backoff
16665
16669
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
16666
16670
 
16667
- var logger$3 = console_with_prefix('batch');
16671
+ var logger$4 = console_with_prefix('batch');
16668
16672
 
16669
16673
  /**
16670
16674
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -16792,7 +16796,7 @@
16792
16796
  */
16793
16797
  RequestBatcher.prototype.flush = function(options) {
16794
16798
  if (this.requestInProgress) {
16795
- logger$3.log('Flush: Request already in progress');
16799
+ logger$4.log('Flush: Request already in progress');
16796
16800
  return PromisePolyfill.resolve();
16797
16801
  }
16798
16802
 
@@ -16969,7 +16973,7 @@
16969
16973
  if (options.unloading) {
16970
16974
  requestOptions.transport = 'sendBeacon';
16971
16975
  }
16972
- logger$3.log('MIXPANEL REQUEST:', dataForRequest);
16976
+ logger$4.log('MIXPANEL REQUEST:', dataForRequest);
16973
16977
  return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
16974
16978
  }, this))
16975
16979
  .catch(_.bind(function(err) {
@@ -16982,7 +16986,7 @@
16982
16986
  * Log error to global logger and optional user-defined logger.
16983
16987
  */
16984
16988
  RequestBatcher.prototype.reportError = function(msg, err) {
16985
- logger$3.error.apply(logger$3.error, arguments);
16989
+ logger$4.error.apply(logger$4.error, arguments);
16986
16990
  if (this.errorReporter) {
16987
16991
  try {
16988
16992
  if (!(err instanceof Error)) {
@@ -16990,7 +16994,7 @@
16990
16994
  }
16991
16995
  this.errorReporter(msg, err);
16992
16996
  } catch(err) {
16993
- logger$3.error(err);
16997
+ logger$4.error(err);
16994
16998
  }
16995
16999
  }
16996
17000
  };
@@ -17006,7 +17010,7 @@
17006
17010
 
17007
17011
  var RECORD_ENQUEUE_THROTTLE_MS = 250;
17008
17012
 
17009
- var logger$2 = console_with_prefix('recorder');
17013
+ var logger$3 = console_with_prefix('recorder');
17010
17014
  var CompressionStream = win['CompressionStream'];
17011
17015
 
17012
17016
  var RECORDER_BATCHER_LIB_CONFIG = {
@@ -17143,14 +17147,14 @@
17143
17147
  }
17144
17148
 
17145
17149
  if (this._stopRecording !== null) {
17146
- logger$2.log('Recording already in progress, skipping startRecording.');
17150
+ logger$3.log('Recording already in progress, skipping startRecording.');
17147
17151
  return;
17148
17152
  }
17149
17153
 
17150
17154
  this.recordMaxMs = this.getConfig('record_max_ms');
17151
17155
  if (this.recordMaxMs > MAX_RECORDING_MS) {
17152
17156
  this.recordMaxMs = MAX_RECORDING_MS;
17153
- logger$2.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
17157
+ logger$3.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
17154
17158
  }
17155
17159
 
17156
17160
  if (!this.maxExpires) {
@@ -17160,7 +17164,7 @@
17160
17164
  this.recordMinMs = this.getConfig('record_min_ms');
17161
17165
  if (this.recordMinMs > MAX_VALUE_FOR_MIN_RECORDING_MS) {
17162
17166
  this.recordMinMs = MAX_VALUE_FOR_MIN_RECORDING_MS;
17163
- logger$2.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
17167
+ logger$3.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
17164
17168
  }
17165
17169
 
17166
17170
  if (!this.replayStartTime) {
@@ -17444,14 +17448,14 @@
17444
17448
 
17445
17449
 
17446
17450
  SessionRecording.prototype.reportError = function(msg, err) {
17447
- logger$2.error.apply(logger$2.error, arguments);
17451
+ logger$3.error.apply(logger$3.error, arguments);
17448
17452
  try {
17449
17453
  if (!err && !(msg instanceof Error)) {
17450
17454
  msg = new Error(msg);
17451
17455
  }
17452
17456
  this.getConfig('error_reporter')(msg, err);
17453
17457
  } catch(err) {
17454
- logger$2.error(err);
17458
+ logger$3.error(err);
17455
17459
  }
17456
17460
  };
17457
17461
 
@@ -17547,7 +17551,7 @@
17547
17551
  .catch(this.handleError.bind(this));
17548
17552
  };
17549
17553
 
17550
- var logger$1 = console_with_prefix('recorder');
17554
+ var logger$2 = console_with_prefix('recorder');
17551
17555
 
17552
17556
  /**
17553
17557
  * Recorder API: bundles rrweb and and exposes methods to start and stop recordings.
@@ -17563,7 +17567,7 @@
17563
17567
  */
17564
17568
  this.recordingRegistry = new RecordingRegistry({
17565
17569
  mixpanelInstance: this.mixpanelInstance,
17566
- errorReporter: logger$1.error,
17570
+ errorReporter: logger$2.error,
17567
17571
  sharedLockStorage: sharedLockStorage
17568
17572
  });
17569
17573
  this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
@@ -17574,17 +17578,17 @@
17574
17578
  MixpanelRecorder.prototype.startRecording = function(options) {
17575
17579
  options = options || {};
17576
17580
  if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
17577
- logger$1.log('Recording already in progress, skipping startRecording.');
17581
+ logger$2.log('Recording already in progress, skipping startRecording.');
17578
17582
  return;
17579
17583
  }
17580
17584
 
17581
17585
  var onIdleTimeout = function () {
17582
- logger$1.log('Idle timeout reached, restarting recording.');
17586
+ logger$2.log('Idle timeout reached, restarting recording.');
17583
17587
  this.resetRecording();
17584
17588
  }.bind(this);
17585
17589
 
17586
17590
  var onMaxLengthReached = function () {
17587
- logger$1.log('Max recording length reached, stopping recording.');
17591
+ logger$2.log('Max recording length reached, stopping recording.');
17588
17592
  this.resetRecording();
17589
17593
  }.bind(this);
17590
17594
 
@@ -17647,7 +17651,7 @@
17647
17651
  } else if (startNewIfInactive) {
17648
17652
  return this.startRecording({shouldStopBatcher: false});
17649
17653
  } else {
17650
- logger$1.log('No resumable recording found.');
17654
+ logger$2.log('No resumable recording found.');
17651
17655
  return null;
17652
17656
  }
17653
17657
  }.bind(this));
@@ -17705,7 +17709,7 @@
17705
17709
  'href', 'name', 'role', 'title', 'type'
17706
17710
  ];
17707
17711
 
17708
- var logger = console_with_prefix('autocapture');
17712
+ var logger$1 = console_with_prefix('autocapture');
17709
17713
 
17710
17714
 
17711
17715
  function getClasses(el) {
@@ -17791,6 +17795,7 @@
17791
17795
  var blockSelectors = config.blockSelectors || [];
17792
17796
  var captureTextContent = config.captureTextContent || false;
17793
17797
  var captureExtraAttrs = config.captureExtraAttrs || [];
17798
+ var capturedForHeatMap = config.capturedForHeatMap || false;
17794
17799
 
17795
17800
  // convert array to set every time, as the config may have changed
17796
17801
  var blockAttrsSet = {};
@@ -17845,7 +17850,9 @@
17845
17850
  '$elements': elementsJson,
17846
17851
  '$el_attr__href': href,
17847
17852
  '$viewportHeight': Math.max(docElement['clientHeight'], win['innerHeight'] || 0),
17848
- '$viewportWidth': Math.max(docElement['clientWidth'], win['innerWidth'] || 0)
17853
+ '$viewportWidth': Math.max(docElement['clientWidth'], win['innerWidth'] || 0),
17854
+ '$pageHeight': document$1['body']['offsetHeight'] || 0,
17855
+ '$pageWidth': document$1['body']['offsetWidth'] || 0,
17849
17856
  };
17850
17857
  _.each(captureExtraAttrs, function(attr) {
17851
17858
  if (!blockAttrsSet[attr] && target.hasAttribute(attr)) {
@@ -17869,6 +17876,9 @@
17869
17876
  props['$' + prop] = ev[prop];
17870
17877
  }
17871
17878
  });
17879
+ if (capturedForHeatMap) {
17880
+ props['$captured_for_heatmap'] = true;
17881
+ }
17872
17882
  target = guessRealClickTarget(ev);
17873
17883
  }
17874
17884
  // prioritize text content from "real" click target if different from original target
@@ -17963,7 +17973,7 @@
17963
17973
  return false;
17964
17974
  }
17965
17975
  } catch (err) {
17966
- logger.critical('Error while checking element in allowElementCallback', err);
17976
+ logger$1.critical('Error while checking element in allowElementCallback', err);
17967
17977
  return false;
17968
17978
  }
17969
17979
  }
@@ -17980,7 +17990,7 @@
17980
17990
  return true;
17981
17991
  }
17982
17992
  } catch (err) {
17983
- logger.critical('Error while checking selector: ' + sel, err);
17993
+ logger$1.critical('Error while checking selector: ' + sel, err);
17984
17994
  }
17985
17995
  }
17986
17996
  return false;
@@ -17995,7 +18005,7 @@
17995
18005
  return true;
17996
18006
  }
17997
18007
  } catch (err) {
17998
- logger.critical('Error while checking element in blockElementCallback', err);
18008
+ logger$1.critical('Error while checking element in blockElementCallback', err);
17999
18009
  return true;
18000
18010
  }
18001
18011
  }
@@ -18009,7 +18019,7 @@
18009
18019
  return true;
18010
18020
  }
18011
18021
  } catch (err) {
18012
- logger.critical('Error while checking selector: ' + sel, err);
18022
+ logger$1.critical('Error while checking selector: ' + sel, err);
18013
18023
  }
18014
18024
  }
18015
18025
  }
@@ -18215,22 +18225,22 @@
18215
18225
  var CONFIG_TRACK_SCROLL = 'scroll';
18216
18226
  var CONFIG_TRACK_SUBMIT = 'submit';
18217
18227
 
18218
- var CONFIG_DEFAULTS = {};
18219
- CONFIG_DEFAULTS[CONFIG_ALLOW_SELECTORS] = [];
18220
- CONFIG_DEFAULTS[CONFIG_ALLOW_URL_REGEXES] = [];
18221
- CONFIG_DEFAULTS[CONFIG_BLOCK_ATTRS] = [];
18222
- CONFIG_DEFAULTS[CONFIG_BLOCK_ELEMENT_CALLBACK] = null;
18223
- CONFIG_DEFAULTS[CONFIG_BLOCK_SELECTORS] = [];
18224
- CONFIG_DEFAULTS[CONFIG_BLOCK_URL_REGEXES] = [];
18225
- CONFIG_DEFAULTS[CONFIG_CAPTURE_EXTRA_ATTRS] = [];
18226
- CONFIG_DEFAULTS[CONFIG_CAPTURE_TEXT_CONTENT] = false;
18227
- CONFIG_DEFAULTS[CONFIG_SCROLL_CAPTURE_ALL] = false;
18228
- CONFIG_DEFAULTS[CONFIG_SCROLL_CHECKPOINTS] = [25, 50, 75, 100];
18229
- CONFIG_DEFAULTS[CONFIG_TRACK_CLICK] = true;
18230
- CONFIG_DEFAULTS[CONFIG_TRACK_INPUT] = true;
18231
- CONFIG_DEFAULTS[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
18232
- CONFIG_DEFAULTS[CONFIG_TRACK_SCROLL] = true;
18233
- CONFIG_DEFAULTS[CONFIG_TRACK_SUBMIT] = true;
18228
+ var CONFIG_DEFAULTS$1 = {};
18229
+ CONFIG_DEFAULTS$1[CONFIG_ALLOW_SELECTORS] = [];
18230
+ CONFIG_DEFAULTS$1[CONFIG_ALLOW_URL_REGEXES] = [];
18231
+ CONFIG_DEFAULTS$1[CONFIG_BLOCK_ATTRS] = [];
18232
+ CONFIG_DEFAULTS$1[CONFIG_BLOCK_ELEMENT_CALLBACK] = null;
18233
+ CONFIG_DEFAULTS$1[CONFIG_BLOCK_SELECTORS] = [];
18234
+ CONFIG_DEFAULTS$1[CONFIG_BLOCK_URL_REGEXES] = [];
18235
+ CONFIG_DEFAULTS$1[CONFIG_CAPTURE_EXTRA_ATTRS] = [];
18236
+ CONFIG_DEFAULTS$1[CONFIG_CAPTURE_TEXT_CONTENT] = false;
18237
+ CONFIG_DEFAULTS$1[CONFIG_SCROLL_CAPTURE_ALL] = false;
18238
+ CONFIG_DEFAULTS$1[CONFIG_SCROLL_CHECKPOINTS] = [25, 50, 75, 100];
18239
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_CLICK] = true;
18240
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_INPUT] = true;
18241
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
18242
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_SCROLL] = true;
18243
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_SUBMIT] = true;
18234
18244
 
18235
18245
  var DEFAULT_PROPS = {
18236
18246
  '$mp_autocapture': true
@@ -18251,7 +18261,7 @@
18251
18261
 
18252
18262
  Autocapture.prototype.init = function() {
18253
18263
  if (!minDOMApisSupported()) {
18254
- logger.critical('Autocapture unavailable: missing required DOM APIs');
18264
+ logger$1.critical('Autocapture unavailable: missing required DOM APIs');
18255
18265
  return;
18256
18266
  }
18257
18267
 
@@ -18268,10 +18278,10 @@
18268
18278
  // Autocapture is completely off
18269
18279
  return {};
18270
18280
  } else if (_.isObject(autocaptureConfig)) {
18271
- return _.extend({}, CONFIG_DEFAULTS, autocaptureConfig);
18281
+ return _.extend({}, CONFIG_DEFAULTS$1, autocaptureConfig);
18272
18282
  } else {
18273
18283
  // Autocapture config is non-object truthy value, return default
18274
- return CONFIG_DEFAULTS;
18284
+ return CONFIG_DEFAULTS$1;
18275
18285
  }
18276
18286
  };
18277
18287
 
@@ -18295,7 +18305,7 @@
18295
18305
  break;
18296
18306
  }
18297
18307
  } catch (err) {
18298
- logger.critical('Error while checking block URL regex: ' + allowRegex, err);
18308
+ logger$1.critical('Error while checking block URL regex: ' + allowRegex, err);
18299
18309
  return true;
18300
18310
  }
18301
18311
  }
@@ -18316,7 +18326,7 @@
18316
18326
  return true;
18317
18327
  }
18318
18328
  } catch (err) {
18319
- logger.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
18329
+ logger$1.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
18320
18330
  return true;
18321
18331
  }
18322
18332
  }
@@ -18345,7 +18355,8 @@
18345
18355
  blockElementCallback: this.getConfig(CONFIG_BLOCK_ELEMENT_CALLBACK),
18346
18356
  blockSelectors: this.getConfig(CONFIG_BLOCK_SELECTORS),
18347
18357
  captureExtraAttrs: this.getConfig(CONFIG_CAPTURE_EXTRA_ATTRS),
18348
- captureTextContent: this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT)
18358
+ captureTextContent: this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT),
18359
+ capturedForHeatMap: mpEventName === MP_EV_CLICK && !this.getConfig(CONFIG_TRACK_CLICK) && this.mp.is_recording_heatmap_data(),
18349
18360
  });
18350
18361
  if (props) {
18351
18362
  _.extend(props, DEFAULT_PROPS);
@@ -18356,13 +18367,13 @@
18356
18367
  Autocapture.prototype.initClickTracking = function() {
18357
18368
  win.removeEventListener(EV_CLICK, this.listenerClick);
18358
18369
 
18359
- if (!this.getConfig(CONFIG_TRACK_CLICK)) {
18370
+ if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
18360
18371
  return;
18361
18372
  }
18362
- logger.log('Initializing click tracking');
18373
+ logger$1.log('Initializing click tracking');
18363
18374
 
18364
18375
  this.listenerClick = win.addEventListener(EV_CLICK, function(ev) {
18365
- if (!this.getConfig(CONFIG_TRACK_CLICK)) {
18376
+ if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
18366
18377
  return;
18367
18378
  }
18368
18379
  this.trackDomEvent(ev, MP_EV_CLICK);
@@ -18375,7 +18386,7 @@
18375
18386
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
18376
18387
  return;
18377
18388
  }
18378
- logger.log('Initializing input tracking');
18389
+ logger$1.log('Initializing input tracking');
18379
18390
 
18380
18391
  this.listenerChange = win.addEventListener(EV_CHANGE, function(ev) {
18381
18392
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
@@ -18393,7 +18404,7 @@
18393
18404
  if (!this.pageviewTrackingConfig()) {
18394
18405
  return;
18395
18406
  }
18396
- logger.log('Initializing pageview tracking');
18407
+ logger$1.log('Initializing pageview tracking');
18397
18408
 
18398
18409
  var previousTrackedUrl = '';
18399
18410
  var tracked = false;
@@ -18448,7 +18459,7 @@
18448
18459
  }
18449
18460
  if (didPathChange) {
18450
18461
  this.lastScrollCheckpoint = 0;
18451
- logger.log('Path change: re-initializing scroll depth checkpoints');
18462
+ logger$1.log('Path change: re-initializing scroll depth checkpoints');
18452
18463
  }
18453
18464
  }
18454
18465
  }.bind(this)));
@@ -18460,7 +18471,7 @@
18460
18471
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
18461
18472
  return;
18462
18473
  }
18463
- logger.log('Initializing scroll tracking');
18474
+ logger$1.log('Initializing scroll tracking');
18464
18475
  this.lastScrollCheckpoint = 0;
18465
18476
 
18466
18477
  this.listenerScroll = win.addEventListener(EV_SCROLLEND, safewrap(function() {
@@ -18497,7 +18508,7 @@
18497
18508
  }
18498
18509
  }
18499
18510
  } catch (err) {
18500
- logger.critical('Error while calculating scroll percentage', err);
18511
+ logger$1.critical('Error while calculating scroll percentage', err);
18501
18512
  }
18502
18513
  if (shouldTrack) {
18503
18514
  this.mp.track(MP_EV_SCROLL, props);
@@ -18511,7 +18522,7 @@
18511
18522
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
18512
18523
  return;
18513
18524
  }
18514
- logger.log('Initializing submit tracking');
18525
+ logger$1.log('Initializing submit tracking');
18515
18526
 
18516
18527
  this.listenerSubmit = win.addEventListener(EV_SUBMIT, function(ev) {
18517
18528
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
@@ -18524,6 +18535,202 @@
18524
18535
  // TODO integrate error_reporter from mixpanel instance
18525
18536
  safewrapClass(Autocapture);
18526
18537
 
18538
+ var fetch = win['fetch'];
18539
+ var logger = console_with_prefix('flags');
18540
+
18541
+ var FLAGS_CONFIG_KEY = 'flags';
18542
+
18543
+ var CONFIG_CONTEXT = 'context';
18544
+ var CONFIG_DEFAULTS = {};
18545
+ CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
18546
+
18547
+ /**
18548
+ * FeatureFlagManager: support for Mixpanel's feature flagging product
18549
+ * @constructor
18550
+ */
18551
+ var FeatureFlagManager = function(initOptions) {
18552
+ this.getMpConfig = initOptions.getConfigFunc;
18553
+ this.getDistinctId = initOptions.getDistinctIdFunc;
18554
+ this.track = initOptions.trackingFunc;
18555
+ };
18556
+
18557
+ FeatureFlagManager.prototype.init = function() {
18558
+ if (!minApisSupported()) {
18559
+ logger.critical('Feature Flags unavailable: missing minimum required APIs');
18560
+ return;
18561
+ }
18562
+
18563
+ this.flags = null;
18564
+ this.fetchFlags();
18565
+
18566
+ this.trackedFeatures = new Set();
18567
+ };
18568
+
18569
+ FeatureFlagManager.prototype.getFullConfig = function() {
18570
+ var ffConfig = this.getMpConfig(FLAGS_CONFIG_KEY);
18571
+ if (!ffConfig) {
18572
+ // flags are completely off
18573
+ return {};
18574
+ } else if (_.isObject(ffConfig)) {
18575
+ return _.extend({}, CONFIG_DEFAULTS, ffConfig);
18576
+ } else {
18577
+ // config is non-object truthy value, return default
18578
+ return CONFIG_DEFAULTS;
18579
+ }
18580
+ };
18581
+
18582
+ FeatureFlagManager.prototype.getConfig = function(key) {
18583
+ return this.getFullConfig()[key];
18584
+ };
18585
+
18586
+ FeatureFlagManager.prototype.isSystemEnabled = function() {
18587
+ return !!this.getMpConfig(FLAGS_CONFIG_KEY);
18588
+ };
18589
+
18590
+ FeatureFlagManager.prototype.areFlagsReady = function() {
18591
+ if (!this.isSystemEnabled()) {
18592
+ logger.error('Feature Flags not enabled');
18593
+ }
18594
+ return !!this.flags;
18595
+ };
18596
+
18597
+ FeatureFlagManager.prototype.fetchFlags = function() {
18598
+ if (!this.isSystemEnabled()) {
18599
+ return;
18600
+ }
18601
+
18602
+ var distinctId = this.getDistinctId();
18603
+ logger.log('Fetching flags for distinct ID: ' + distinctId);
18604
+ var reqParams = {
18605
+ 'context': _.extend({'distinct_id': distinctId}, this.getConfig(CONFIG_CONTEXT))
18606
+ };
18607
+ this.fetchPromise = win['fetch'](this.getMpConfig('api_host') + '/' + this.getMpConfig('api_routes')['flags'], {
18608
+ 'method': 'POST',
18609
+ 'headers': {
18610
+ 'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
18611
+ 'Content-Type': 'application/octet-stream'
18612
+ },
18613
+ 'body': JSON.stringify(reqParams)
18614
+ }).then(function(response) {
18615
+ return response.json().then(function(responseBody) {
18616
+ var responseFlags = responseBody['flags'];
18617
+ if (!responseFlags) {
18618
+ throw new Error('No flags in API response');
18619
+ }
18620
+ var flags = new Map();
18621
+ _.each(responseFlags, function(data, key) {
18622
+ flags.set(key, {
18623
+ 'key': data['variant_key'],
18624
+ 'value': data['variant_value']
18625
+ });
18626
+ });
18627
+ this.flags = flags;
18628
+ }.bind(this)).catch(function(error) {
18629
+ logger.error(error);
18630
+ });
18631
+ }.bind(this)).catch(function() {});
18632
+ };
18633
+
18634
+ FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
18635
+ if (!this.fetchPromise) {
18636
+ return new Promise(function(resolve) {
18637
+ logger.critical('Feature Flags not initialized');
18638
+ resolve(fallback);
18639
+ });
18640
+ }
18641
+
18642
+ return this.fetchPromise.then(function() {
18643
+ return this.getVariantSync(featureName, fallback);
18644
+ }.bind(this)).catch(function(error) {
18645
+ logger.error(error);
18646
+ return fallback;
18647
+ });
18648
+ };
18649
+
18650
+ FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
18651
+ if (!this.areFlagsReady()) {
18652
+ logger.log('Flags not loaded yet');
18653
+ return fallback;
18654
+ }
18655
+ var feature = this.flags.get(featureName);
18656
+ if (!feature) {
18657
+ logger.log('No flag found: "' + featureName + '"');
18658
+ return fallback;
18659
+ }
18660
+ this.trackFeatureCheck(featureName, feature);
18661
+ return feature;
18662
+ };
18663
+
18664
+ FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
18665
+ return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
18666
+ return feature['value'];
18667
+ }).catch(function(error) {
18668
+ logger.error(error);
18669
+ return fallbackValue;
18670
+ });
18671
+ };
18672
+
18673
+ // TODO remove deprecated method
18674
+ FeatureFlagManager.prototype.getFeatureData = function(featureName, fallbackValue) {
18675
+ logger.critical('mixpanel.flags.get_feature_data() is deprecated and will be removed in a future release. Use mixpanel.flags.get_variant_value() instead.');
18676
+ return this.getVariantValue(featureName, fallbackValue);
18677
+ };
18678
+
18679
+ FeatureFlagManager.prototype.getVariantValueSync = function(featureName, fallbackValue) {
18680
+ return this.getVariantSync(featureName, {'value': fallbackValue})['value'];
18681
+ };
18682
+
18683
+ FeatureFlagManager.prototype.isEnabled = function(featureName, fallbackValue) {
18684
+ return this.getVariantValue(featureName).then(function() {
18685
+ return this.isEnabledSync(featureName, fallbackValue);
18686
+ }.bind(this)).catch(function(error) {
18687
+ logger.error(error);
18688
+ return fallbackValue;
18689
+ });
18690
+ };
18691
+
18692
+ FeatureFlagManager.prototype.isEnabledSync = function(featureName, fallbackValue) {
18693
+ fallbackValue = fallbackValue || false;
18694
+ var val = this.getVariantValueSync(featureName, fallbackValue);
18695
+ if (val !== true && val !== false) {
18696
+ logger.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
18697
+ val = fallbackValue;
18698
+ }
18699
+ return val;
18700
+ };
18701
+
18702
+ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
18703
+ if (this.trackedFeatures.has(featureName)) {
18704
+ return;
18705
+ }
18706
+ this.trackedFeatures.add(featureName);
18707
+ this.track('$experiment_started', {
18708
+ 'Experiment name': featureName,
18709
+ 'Variant name': feature['key'],
18710
+ '$experiment_type': 'feature_flag'
18711
+ });
18712
+ };
18713
+
18714
+ function minApisSupported() {
18715
+ return !!fetch &&
18716
+ typeof Promise !== 'undefined' &&
18717
+ typeof Map !== 'undefined' &&
18718
+ typeof Set !== 'undefined';
18719
+ }
18720
+
18721
+ safewrapClass(FeatureFlagManager);
18722
+
18723
+ FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
18724
+ FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
18725
+ FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
18726
+ FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
18727
+ FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
18728
+ FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
18729
+ FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
18730
+
18731
+ // Deprecated method
18732
+ FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
18733
+
18527
18734
  /* eslint camelcase: "off" */
18528
18735
 
18529
18736
 
@@ -19218,18 +19425,8 @@
19218
19425
  * @param {Function} [callback] If provided, the callback will be called when the server responds
19219
19426
  * @deprecated
19220
19427
  */
19221
- MixpanelPeople.prototype.track_charge = addOptOutCheckMixpanelPeople(function(amount, properties, callback) {
19222
- if (!_.isNumber(amount)) {
19223
- amount = parseFloat(amount);
19224
- if (isNaN(amount)) {
19225
- console$1.error('Invalid value passed to mixpanel.people.track_charge - must be a number');
19226
- return;
19227
- }
19228
- }
19229
-
19230
- return this.append('$transactions', _.extend({
19231
- '$amount': amount
19232
- }, properties), callback);
19428
+ MixpanelPeople.prototype.track_charge = addOptOutCheckMixpanelPeople(function() {
19429
+ console$1.error('mixpanel.people.track_charge() is deprecated and no longer has any effect.');
19233
19430
  });
19234
19431
 
19235
19432
  /*
@@ -19928,10 +20125,11 @@
19928
20125
  }
19929
20126
 
19930
20127
  var DEFAULT_API_ROUTES = {
19931
- 'track': 'track/',
20128
+ 'track': 'track/',
19932
20129
  'engage': 'engage/',
19933
20130
  'groups': 'groups/',
19934
- 'record': 'record/'
20131
+ 'record': 'record/',
20132
+ 'flags': 'flags/'
19935
20133
  };
19936
20134
 
19937
20135
  /*
@@ -19940,6 +20138,7 @@
19940
20138
  var DEFAULT_CONFIG = {
19941
20139
  'api_host': 'https://api-js.mixpanel.com',
19942
20140
  'api_routes': DEFAULT_API_ROUTES,
20141
+ 'api_extra_query_params': {},
19943
20142
  'api_method': 'POST',
19944
20143
  'api_transport': 'XHR',
19945
20144
  'api_payload_format': PAYLOAD_TYPE_BASE64,
@@ -19949,6 +20148,7 @@
19949
20148
  'cross_site_cookie': false,
19950
20149
  'cross_subdomain_cookie': true,
19951
20150
  'error_reporter': NOOP_FUNC,
20151
+ 'flags': false,
19952
20152
  'persistence': 'cookie',
19953
20153
  'persistence_name': '',
19954
20154
  'cookie_domain': '',
@@ -19989,6 +20189,7 @@
19989
20189
  'record_block_selector': 'img, video',
19990
20190
  'record_canvas': false,
19991
20191
  'record_collect_fonts': false,
20192
+ 'record_heatmap_data': false,
19992
20193
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
19993
20194
  'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
19994
20195
  'record_mask_text_selector': '*',
@@ -20204,6 +20405,14 @@
20204
20405
  }, '');
20205
20406
  }
20206
20407
 
20408
+ this.flags = new FeatureFlagManager({
20409
+ getConfigFunc: _.bind(this.get_config, this),
20410
+ getDistinctIdFunc: _.bind(this.get_distinct_id, this),
20411
+ trackingFunc: _.bind(this.track, this)
20412
+ });
20413
+ this.flags.init();
20414
+ this['flags'] = this.flags;
20415
+
20207
20416
  this.autocapture = new Autocapture(this);
20208
20417
  this.autocapture.init();
20209
20418
 
@@ -20329,6 +20538,10 @@
20329
20538
  }
20330
20539
  };
20331
20540
 
20541
+ MixpanelLib.prototype.is_recording_heatmap_data = function () {
20542
+ return this._get_session_replay_id() && this.get_config('record_heatmap_data');
20543
+ };
20544
+
20332
20545
  MixpanelLib.prototype.get_session_recording_properties = function () {
20333
20546
  var props = {};
20334
20547
  var replay_id = this._get_session_replay_id();
@@ -20513,6 +20726,8 @@
20513
20726
  delete data['data'];
20514
20727
  }
20515
20728
 
20729
+ _.extend(data, this.get_config('api_extra_query_params'));
20730
+
20516
20731
  url += '?' + _.HTTPBuildQuery(data);
20517
20732
 
20518
20733
  var lib = this;
@@ -21410,6 +21625,11 @@
21410
21625
  '$anon_distinct_id': previous_distinct_id
21411
21626
  }, {skip_hooks: true});
21412
21627
  }
21628
+
21629
+ // check feature flags again if distinct id has changed
21630
+ if (new_distinct_id !== previous_distinct_id) {
21631
+ this.flags.fetchFlags();
21632
+ }
21413
21633
  };
21414
21634
 
21415
21635
  /**
@@ -21424,6 +21644,8 @@
21424
21644
  'distinct_id': DEVICE_ID_PREFIX + uuid,
21425
21645
  '$device_id': uuid
21426
21646
  }, '');
21647
+ this.stop_session_recording();
21648
+ this._check_and_start_session_recording();
21427
21649
  };
21428
21650
 
21429
21651
  /**
@@ -21684,7 +21906,7 @@
21684
21906
  }
21685
21907
  Config.DEBUG = Config.DEBUG || this.get_config('debug');
21686
21908
 
21687
- if ('autocapture' in config && this.autocapture) {
21909
+ if (('autocapture' in config || 'record_heatmap_data' in config) && this.autocapture) {
21688
21910
  this.autocapture.init();
21689
21911
  }
21690
21912
  }