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.
@@ -13945,7 +13945,7 @@
13945
13945
 
13946
13946
  var Config = {
13947
13947
  DEBUG: false,
13948
- LIB_VERSION: '2.63.0'
13948
+ LIB_VERSION: '2.65.0'
13949
13949
  };
13950
13950
 
13951
13951
  /* eslint camelcase: "off", eqeqeq: "off" */
@@ -15430,6 +15430,9 @@
15430
15430
  return 'Microsoft Edge';
15431
15431
  } else if (_.includes(user_agent, 'FBIOS')) {
15432
15432
  return 'Facebook Mobile';
15433
+ } else if (_.includes(user_agent, 'Whale/')) {
15434
+ // https://user-agents.net/browsers/whale-browser
15435
+ return 'Whale Browser';
15433
15436
  } else if (_.includes(user_agent, 'Chrome')) {
15434
15437
  return 'Chrome';
15435
15438
  } else if (_.includes(user_agent, 'CriOS')) {
@@ -15481,7 +15484,8 @@
15481
15484
  'Android Mobile': /android\s(\d+(\.\d+)?)/,
15482
15485
  'Samsung Internet': /SamsungBrowser\/(\d+(\.\d+)?)/,
15483
15486
  'Internet Explorer': /(rv:|MSIE )(\d+(\.\d+)?)/,
15484
- 'Mozilla': /rv:(\d+(\.\d+)?)/
15487
+ 'Mozilla': /rv:(\d+(\.\d+)?)/,
15488
+ 'Whale Browser': /Whale\/(\d+(\.\d+)?)/
15485
15489
  };
15486
15490
  var regex = versionRegexs[browser];
15487
15491
  if (regex === undefined) {
@@ -16104,7 +16108,7 @@
16104
16108
  };
16105
16109
  }
16106
16110
 
16107
- var logger$5 = console_with_prefix('lock');
16111
+ var logger$6 = console_with_prefix('lock');
16108
16112
 
16109
16113
  /**
16110
16114
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -16156,7 +16160,7 @@
16156
16160
 
16157
16161
  var delay = function(cb) {
16158
16162
  if (new Date().getTime() - startTime > timeoutMS) {
16159
- logger$5.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
16163
+ logger$6.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
16160
16164
  storage.removeItem(keyZ);
16161
16165
  storage.removeItem(keyY);
16162
16166
  loop();
@@ -16299,7 +16303,7 @@
16299
16303
  }, this));
16300
16304
  };
16301
16305
 
16302
- var logger$4 = console_with_prefix('batch');
16306
+ var logger$5 = console_with_prefix('batch');
16303
16307
 
16304
16308
  /**
16305
16309
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -16328,7 +16332,7 @@
16328
16332
  timeoutMS: options.sharedLockTimeoutMS,
16329
16333
  });
16330
16334
  }
16331
- this.reportError = options.errorReporter || _.bind(logger$4.error, logger$4);
16335
+ this.reportError = options.errorReporter || _.bind(logger$5.error, logger$5);
16332
16336
 
16333
16337
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
16334
16338
 
@@ -16661,7 +16665,7 @@
16661
16665
  // maximum interval between request retries after exponential backoff
16662
16666
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
16663
16667
 
16664
- var logger$3 = console_with_prefix('batch');
16668
+ var logger$4 = console_with_prefix('batch');
16665
16669
 
16666
16670
  /**
16667
16671
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -16789,7 +16793,7 @@
16789
16793
  */
16790
16794
  RequestBatcher.prototype.flush = function(options) {
16791
16795
  if (this.requestInProgress) {
16792
- logger$3.log('Flush: Request already in progress');
16796
+ logger$4.log('Flush: Request already in progress');
16793
16797
  return PromisePolyfill.resolve();
16794
16798
  }
16795
16799
 
@@ -16966,7 +16970,7 @@
16966
16970
  if (options.unloading) {
16967
16971
  requestOptions.transport = 'sendBeacon';
16968
16972
  }
16969
- logger$3.log('MIXPANEL REQUEST:', dataForRequest);
16973
+ logger$4.log('MIXPANEL REQUEST:', dataForRequest);
16970
16974
  return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
16971
16975
  }, this))
16972
16976
  .catch(_.bind(function(err) {
@@ -16979,7 +16983,7 @@
16979
16983
  * Log error to global logger and optional user-defined logger.
16980
16984
  */
16981
16985
  RequestBatcher.prototype.reportError = function(msg, err) {
16982
- logger$3.error.apply(logger$3.error, arguments);
16986
+ logger$4.error.apply(logger$4.error, arguments);
16983
16987
  if (this.errorReporter) {
16984
16988
  try {
16985
16989
  if (!(err instanceof Error)) {
@@ -16987,7 +16991,7 @@
16987
16991
  }
16988
16992
  this.errorReporter(msg, err);
16989
16993
  } catch(err) {
16990
- logger$3.error(err);
16994
+ logger$4.error(err);
16991
16995
  }
16992
16996
  }
16993
16997
  };
@@ -17003,7 +17007,7 @@
17003
17007
 
17004
17008
  var RECORD_ENQUEUE_THROTTLE_MS = 250;
17005
17009
 
17006
- var logger$2 = console_with_prefix('recorder');
17010
+ var logger$3 = console_with_prefix('recorder');
17007
17011
  var CompressionStream = win['CompressionStream'];
17008
17012
 
17009
17013
  var RECORDER_BATCHER_LIB_CONFIG = {
@@ -17140,14 +17144,14 @@
17140
17144
  }
17141
17145
 
17142
17146
  if (this._stopRecording !== null) {
17143
- logger$2.log('Recording already in progress, skipping startRecording.');
17147
+ logger$3.log('Recording already in progress, skipping startRecording.');
17144
17148
  return;
17145
17149
  }
17146
17150
 
17147
17151
  this.recordMaxMs = this.getConfig('record_max_ms');
17148
17152
  if (this.recordMaxMs > MAX_RECORDING_MS) {
17149
17153
  this.recordMaxMs = MAX_RECORDING_MS;
17150
- logger$2.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
17154
+ logger$3.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
17151
17155
  }
17152
17156
 
17153
17157
  if (!this.maxExpires) {
@@ -17157,7 +17161,7 @@
17157
17161
  this.recordMinMs = this.getConfig('record_min_ms');
17158
17162
  if (this.recordMinMs > MAX_VALUE_FOR_MIN_RECORDING_MS) {
17159
17163
  this.recordMinMs = MAX_VALUE_FOR_MIN_RECORDING_MS;
17160
- logger$2.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
17164
+ logger$3.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
17161
17165
  }
17162
17166
 
17163
17167
  if (!this.replayStartTime) {
@@ -17441,14 +17445,14 @@
17441
17445
 
17442
17446
 
17443
17447
  SessionRecording.prototype.reportError = function(msg, err) {
17444
- logger$2.error.apply(logger$2.error, arguments);
17448
+ logger$3.error.apply(logger$3.error, arguments);
17445
17449
  try {
17446
17450
  if (!err && !(msg instanceof Error)) {
17447
17451
  msg = new Error(msg);
17448
17452
  }
17449
17453
  this.getConfig('error_reporter')(msg, err);
17450
17454
  } catch(err) {
17451
- logger$2.error(err);
17455
+ logger$3.error(err);
17452
17456
  }
17453
17457
  };
17454
17458
 
@@ -17544,7 +17548,7 @@
17544
17548
  .catch(this.handleError.bind(this));
17545
17549
  };
17546
17550
 
17547
- var logger$1 = console_with_prefix('recorder');
17551
+ var logger$2 = console_with_prefix('recorder');
17548
17552
 
17549
17553
  /**
17550
17554
  * Recorder API: bundles rrweb and and exposes methods to start and stop recordings.
@@ -17560,7 +17564,7 @@
17560
17564
  */
17561
17565
  this.recordingRegistry = new RecordingRegistry({
17562
17566
  mixpanelInstance: this.mixpanelInstance,
17563
- errorReporter: logger$1.error,
17567
+ errorReporter: logger$2.error,
17564
17568
  sharedLockStorage: sharedLockStorage
17565
17569
  });
17566
17570
  this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
@@ -17571,17 +17575,17 @@
17571
17575
  MixpanelRecorder.prototype.startRecording = function(options) {
17572
17576
  options = options || {};
17573
17577
  if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
17574
- logger$1.log('Recording already in progress, skipping startRecording.');
17578
+ logger$2.log('Recording already in progress, skipping startRecording.');
17575
17579
  return;
17576
17580
  }
17577
17581
 
17578
17582
  var onIdleTimeout = function () {
17579
- logger$1.log('Idle timeout reached, restarting recording.');
17583
+ logger$2.log('Idle timeout reached, restarting recording.');
17580
17584
  this.resetRecording();
17581
17585
  }.bind(this);
17582
17586
 
17583
17587
  var onMaxLengthReached = function () {
17584
- logger$1.log('Max recording length reached, stopping recording.');
17588
+ logger$2.log('Max recording length reached, stopping recording.');
17585
17589
  this.resetRecording();
17586
17590
  }.bind(this);
17587
17591
 
@@ -17644,7 +17648,7 @@
17644
17648
  } else if (startNewIfInactive) {
17645
17649
  return this.startRecording({shouldStopBatcher: false});
17646
17650
  } else {
17647
- logger$1.log('No resumable recording found.');
17651
+ logger$2.log('No resumable recording found.');
17648
17652
  return null;
17649
17653
  }
17650
17654
  }.bind(this));
@@ -17702,7 +17706,7 @@
17702
17706
  'href', 'name', 'role', 'title', 'type'
17703
17707
  ];
17704
17708
 
17705
- var logger = console_with_prefix('autocapture');
17709
+ var logger$1 = console_with_prefix('autocapture');
17706
17710
 
17707
17711
 
17708
17712
  function getClasses(el) {
@@ -17788,6 +17792,7 @@
17788
17792
  var blockSelectors = config.blockSelectors || [];
17789
17793
  var captureTextContent = config.captureTextContent || false;
17790
17794
  var captureExtraAttrs = config.captureExtraAttrs || [];
17795
+ var capturedForHeatMap = config.capturedForHeatMap || false;
17791
17796
 
17792
17797
  // convert array to set every time, as the config may have changed
17793
17798
  var blockAttrsSet = {};
@@ -17842,7 +17847,9 @@
17842
17847
  '$elements': elementsJson,
17843
17848
  '$el_attr__href': href,
17844
17849
  '$viewportHeight': Math.max(docElement['clientHeight'], win['innerHeight'] || 0),
17845
- '$viewportWidth': Math.max(docElement['clientWidth'], win['innerWidth'] || 0)
17850
+ '$viewportWidth': Math.max(docElement['clientWidth'], win['innerWidth'] || 0),
17851
+ '$pageHeight': document$1['body']['offsetHeight'] || 0,
17852
+ '$pageWidth': document$1['body']['offsetWidth'] || 0,
17846
17853
  };
17847
17854
  _.each(captureExtraAttrs, function(attr) {
17848
17855
  if (!blockAttrsSet[attr] && target.hasAttribute(attr)) {
@@ -17866,6 +17873,9 @@
17866
17873
  props['$' + prop] = ev[prop];
17867
17874
  }
17868
17875
  });
17876
+ if (capturedForHeatMap) {
17877
+ props['$captured_for_heatmap'] = true;
17878
+ }
17869
17879
  target = guessRealClickTarget(ev);
17870
17880
  }
17871
17881
  // prioritize text content from "real" click target if different from original target
@@ -17960,7 +17970,7 @@
17960
17970
  return false;
17961
17971
  }
17962
17972
  } catch (err) {
17963
- logger.critical('Error while checking element in allowElementCallback', err);
17973
+ logger$1.critical('Error while checking element in allowElementCallback', err);
17964
17974
  return false;
17965
17975
  }
17966
17976
  }
@@ -17977,7 +17987,7 @@
17977
17987
  return true;
17978
17988
  }
17979
17989
  } catch (err) {
17980
- logger.critical('Error while checking selector: ' + sel, err);
17990
+ logger$1.critical('Error while checking selector: ' + sel, err);
17981
17991
  }
17982
17992
  }
17983
17993
  return false;
@@ -17992,7 +18002,7 @@
17992
18002
  return true;
17993
18003
  }
17994
18004
  } catch (err) {
17995
- logger.critical('Error while checking element in blockElementCallback', err);
18005
+ logger$1.critical('Error while checking element in blockElementCallback', err);
17996
18006
  return true;
17997
18007
  }
17998
18008
  }
@@ -18006,7 +18016,7 @@
18006
18016
  return true;
18007
18017
  }
18008
18018
  } catch (err) {
18009
- logger.critical('Error while checking selector: ' + sel, err);
18019
+ logger$1.critical('Error while checking selector: ' + sel, err);
18010
18020
  }
18011
18021
  }
18012
18022
  }
@@ -18212,22 +18222,22 @@
18212
18222
  var CONFIG_TRACK_SCROLL = 'scroll';
18213
18223
  var CONFIG_TRACK_SUBMIT = 'submit';
18214
18224
 
18215
- var CONFIG_DEFAULTS = {};
18216
- CONFIG_DEFAULTS[CONFIG_ALLOW_SELECTORS] = [];
18217
- CONFIG_DEFAULTS[CONFIG_ALLOW_URL_REGEXES] = [];
18218
- CONFIG_DEFAULTS[CONFIG_BLOCK_ATTRS] = [];
18219
- CONFIG_DEFAULTS[CONFIG_BLOCK_ELEMENT_CALLBACK] = null;
18220
- CONFIG_DEFAULTS[CONFIG_BLOCK_SELECTORS] = [];
18221
- CONFIG_DEFAULTS[CONFIG_BLOCK_URL_REGEXES] = [];
18222
- CONFIG_DEFAULTS[CONFIG_CAPTURE_EXTRA_ATTRS] = [];
18223
- CONFIG_DEFAULTS[CONFIG_CAPTURE_TEXT_CONTENT] = false;
18224
- CONFIG_DEFAULTS[CONFIG_SCROLL_CAPTURE_ALL] = false;
18225
- CONFIG_DEFAULTS[CONFIG_SCROLL_CHECKPOINTS] = [25, 50, 75, 100];
18226
- CONFIG_DEFAULTS[CONFIG_TRACK_CLICK] = true;
18227
- CONFIG_DEFAULTS[CONFIG_TRACK_INPUT] = true;
18228
- CONFIG_DEFAULTS[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
18229
- CONFIG_DEFAULTS[CONFIG_TRACK_SCROLL] = true;
18230
- CONFIG_DEFAULTS[CONFIG_TRACK_SUBMIT] = true;
18225
+ var CONFIG_DEFAULTS$1 = {};
18226
+ CONFIG_DEFAULTS$1[CONFIG_ALLOW_SELECTORS] = [];
18227
+ CONFIG_DEFAULTS$1[CONFIG_ALLOW_URL_REGEXES] = [];
18228
+ CONFIG_DEFAULTS$1[CONFIG_BLOCK_ATTRS] = [];
18229
+ CONFIG_DEFAULTS$1[CONFIG_BLOCK_ELEMENT_CALLBACK] = null;
18230
+ CONFIG_DEFAULTS$1[CONFIG_BLOCK_SELECTORS] = [];
18231
+ CONFIG_DEFAULTS$1[CONFIG_BLOCK_URL_REGEXES] = [];
18232
+ CONFIG_DEFAULTS$1[CONFIG_CAPTURE_EXTRA_ATTRS] = [];
18233
+ CONFIG_DEFAULTS$1[CONFIG_CAPTURE_TEXT_CONTENT] = false;
18234
+ CONFIG_DEFAULTS$1[CONFIG_SCROLL_CAPTURE_ALL] = false;
18235
+ CONFIG_DEFAULTS$1[CONFIG_SCROLL_CHECKPOINTS] = [25, 50, 75, 100];
18236
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_CLICK] = true;
18237
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_INPUT] = true;
18238
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
18239
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_SCROLL] = true;
18240
+ CONFIG_DEFAULTS$1[CONFIG_TRACK_SUBMIT] = true;
18231
18241
 
18232
18242
  var DEFAULT_PROPS = {
18233
18243
  '$mp_autocapture': true
@@ -18248,7 +18258,7 @@
18248
18258
 
18249
18259
  Autocapture.prototype.init = function() {
18250
18260
  if (!minDOMApisSupported()) {
18251
- logger.critical('Autocapture unavailable: missing required DOM APIs');
18261
+ logger$1.critical('Autocapture unavailable: missing required DOM APIs');
18252
18262
  return;
18253
18263
  }
18254
18264
 
@@ -18265,10 +18275,10 @@
18265
18275
  // Autocapture is completely off
18266
18276
  return {};
18267
18277
  } else if (_.isObject(autocaptureConfig)) {
18268
- return _.extend({}, CONFIG_DEFAULTS, autocaptureConfig);
18278
+ return _.extend({}, CONFIG_DEFAULTS$1, autocaptureConfig);
18269
18279
  } else {
18270
18280
  // Autocapture config is non-object truthy value, return default
18271
- return CONFIG_DEFAULTS;
18281
+ return CONFIG_DEFAULTS$1;
18272
18282
  }
18273
18283
  };
18274
18284
 
@@ -18292,7 +18302,7 @@
18292
18302
  break;
18293
18303
  }
18294
18304
  } catch (err) {
18295
- logger.critical('Error while checking block URL regex: ' + allowRegex, err);
18305
+ logger$1.critical('Error while checking block URL regex: ' + allowRegex, err);
18296
18306
  return true;
18297
18307
  }
18298
18308
  }
@@ -18313,7 +18323,7 @@
18313
18323
  return true;
18314
18324
  }
18315
18325
  } catch (err) {
18316
- logger.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
18326
+ logger$1.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
18317
18327
  return true;
18318
18328
  }
18319
18329
  }
@@ -18342,7 +18352,8 @@
18342
18352
  blockElementCallback: this.getConfig(CONFIG_BLOCK_ELEMENT_CALLBACK),
18343
18353
  blockSelectors: this.getConfig(CONFIG_BLOCK_SELECTORS),
18344
18354
  captureExtraAttrs: this.getConfig(CONFIG_CAPTURE_EXTRA_ATTRS),
18345
- captureTextContent: this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT)
18355
+ captureTextContent: this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT),
18356
+ capturedForHeatMap: mpEventName === MP_EV_CLICK && !this.getConfig(CONFIG_TRACK_CLICK) && this.mp.is_recording_heatmap_data(),
18346
18357
  });
18347
18358
  if (props) {
18348
18359
  _.extend(props, DEFAULT_PROPS);
@@ -18353,13 +18364,13 @@
18353
18364
  Autocapture.prototype.initClickTracking = function() {
18354
18365
  win.removeEventListener(EV_CLICK, this.listenerClick);
18355
18366
 
18356
- if (!this.getConfig(CONFIG_TRACK_CLICK)) {
18367
+ if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
18357
18368
  return;
18358
18369
  }
18359
- logger.log('Initializing click tracking');
18370
+ logger$1.log('Initializing click tracking');
18360
18371
 
18361
18372
  this.listenerClick = win.addEventListener(EV_CLICK, function(ev) {
18362
- if (!this.getConfig(CONFIG_TRACK_CLICK)) {
18373
+ if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
18363
18374
  return;
18364
18375
  }
18365
18376
  this.trackDomEvent(ev, MP_EV_CLICK);
@@ -18372,7 +18383,7 @@
18372
18383
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
18373
18384
  return;
18374
18385
  }
18375
- logger.log('Initializing input tracking');
18386
+ logger$1.log('Initializing input tracking');
18376
18387
 
18377
18388
  this.listenerChange = win.addEventListener(EV_CHANGE, function(ev) {
18378
18389
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
@@ -18390,7 +18401,7 @@
18390
18401
  if (!this.pageviewTrackingConfig()) {
18391
18402
  return;
18392
18403
  }
18393
- logger.log('Initializing pageview tracking');
18404
+ logger$1.log('Initializing pageview tracking');
18394
18405
 
18395
18406
  var previousTrackedUrl = '';
18396
18407
  var tracked = false;
@@ -18445,7 +18456,7 @@
18445
18456
  }
18446
18457
  if (didPathChange) {
18447
18458
  this.lastScrollCheckpoint = 0;
18448
- logger.log('Path change: re-initializing scroll depth checkpoints');
18459
+ logger$1.log('Path change: re-initializing scroll depth checkpoints');
18449
18460
  }
18450
18461
  }
18451
18462
  }.bind(this)));
@@ -18457,7 +18468,7 @@
18457
18468
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
18458
18469
  return;
18459
18470
  }
18460
- logger.log('Initializing scroll tracking');
18471
+ logger$1.log('Initializing scroll tracking');
18461
18472
  this.lastScrollCheckpoint = 0;
18462
18473
 
18463
18474
  this.listenerScroll = win.addEventListener(EV_SCROLLEND, safewrap(function() {
@@ -18494,7 +18505,7 @@
18494
18505
  }
18495
18506
  }
18496
18507
  } catch (err) {
18497
- logger.critical('Error while calculating scroll percentage', err);
18508
+ logger$1.critical('Error while calculating scroll percentage', err);
18498
18509
  }
18499
18510
  if (shouldTrack) {
18500
18511
  this.mp.track(MP_EV_SCROLL, props);
@@ -18508,7 +18519,7 @@
18508
18519
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
18509
18520
  return;
18510
18521
  }
18511
- logger.log('Initializing submit tracking');
18522
+ logger$1.log('Initializing submit tracking');
18512
18523
 
18513
18524
  this.listenerSubmit = win.addEventListener(EV_SUBMIT, function(ev) {
18514
18525
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
@@ -18521,6 +18532,202 @@
18521
18532
  // TODO integrate error_reporter from mixpanel instance
18522
18533
  safewrapClass(Autocapture);
18523
18534
 
18535
+ var fetch = win['fetch'];
18536
+ var logger = console_with_prefix('flags');
18537
+
18538
+ var FLAGS_CONFIG_KEY = 'flags';
18539
+
18540
+ var CONFIG_CONTEXT = 'context';
18541
+ var CONFIG_DEFAULTS = {};
18542
+ CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
18543
+
18544
+ /**
18545
+ * FeatureFlagManager: support for Mixpanel's feature flagging product
18546
+ * @constructor
18547
+ */
18548
+ var FeatureFlagManager = function(initOptions) {
18549
+ this.getMpConfig = initOptions.getConfigFunc;
18550
+ this.getDistinctId = initOptions.getDistinctIdFunc;
18551
+ this.track = initOptions.trackingFunc;
18552
+ };
18553
+
18554
+ FeatureFlagManager.prototype.init = function() {
18555
+ if (!minApisSupported()) {
18556
+ logger.critical('Feature Flags unavailable: missing minimum required APIs');
18557
+ return;
18558
+ }
18559
+
18560
+ this.flags = null;
18561
+ this.fetchFlags();
18562
+
18563
+ this.trackedFeatures = new Set();
18564
+ };
18565
+
18566
+ FeatureFlagManager.prototype.getFullConfig = function() {
18567
+ var ffConfig = this.getMpConfig(FLAGS_CONFIG_KEY);
18568
+ if (!ffConfig) {
18569
+ // flags are completely off
18570
+ return {};
18571
+ } else if (_.isObject(ffConfig)) {
18572
+ return _.extend({}, CONFIG_DEFAULTS, ffConfig);
18573
+ } else {
18574
+ // config is non-object truthy value, return default
18575
+ return CONFIG_DEFAULTS;
18576
+ }
18577
+ };
18578
+
18579
+ FeatureFlagManager.prototype.getConfig = function(key) {
18580
+ return this.getFullConfig()[key];
18581
+ };
18582
+
18583
+ FeatureFlagManager.prototype.isSystemEnabled = function() {
18584
+ return !!this.getMpConfig(FLAGS_CONFIG_KEY);
18585
+ };
18586
+
18587
+ FeatureFlagManager.prototype.areFlagsReady = function() {
18588
+ if (!this.isSystemEnabled()) {
18589
+ logger.error('Feature Flags not enabled');
18590
+ }
18591
+ return !!this.flags;
18592
+ };
18593
+
18594
+ FeatureFlagManager.prototype.fetchFlags = function() {
18595
+ if (!this.isSystemEnabled()) {
18596
+ return;
18597
+ }
18598
+
18599
+ var distinctId = this.getDistinctId();
18600
+ logger.log('Fetching flags for distinct ID: ' + distinctId);
18601
+ var reqParams = {
18602
+ 'context': _.extend({'distinct_id': distinctId}, this.getConfig(CONFIG_CONTEXT))
18603
+ };
18604
+ this.fetchPromise = win['fetch'](this.getMpConfig('api_host') + '/' + this.getMpConfig('api_routes')['flags'], {
18605
+ 'method': 'POST',
18606
+ 'headers': {
18607
+ 'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
18608
+ 'Content-Type': 'application/octet-stream'
18609
+ },
18610
+ 'body': JSON.stringify(reqParams)
18611
+ }).then(function(response) {
18612
+ return response.json().then(function(responseBody) {
18613
+ var responseFlags = responseBody['flags'];
18614
+ if (!responseFlags) {
18615
+ throw new Error('No flags in API response');
18616
+ }
18617
+ var flags = new Map();
18618
+ _.each(responseFlags, function(data, key) {
18619
+ flags.set(key, {
18620
+ 'key': data['variant_key'],
18621
+ 'value': data['variant_value']
18622
+ });
18623
+ });
18624
+ this.flags = flags;
18625
+ }.bind(this)).catch(function(error) {
18626
+ logger.error(error);
18627
+ });
18628
+ }.bind(this)).catch(function() {});
18629
+ };
18630
+
18631
+ FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
18632
+ if (!this.fetchPromise) {
18633
+ return new Promise(function(resolve) {
18634
+ logger.critical('Feature Flags not initialized');
18635
+ resolve(fallback);
18636
+ });
18637
+ }
18638
+
18639
+ return this.fetchPromise.then(function() {
18640
+ return this.getVariantSync(featureName, fallback);
18641
+ }.bind(this)).catch(function(error) {
18642
+ logger.error(error);
18643
+ return fallback;
18644
+ });
18645
+ };
18646
+
18647
+ FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
18648
+ if (!this.areFlagsReady()) {
18649
+ logger.log('Flags not loaded yet');
18650
+ return fallback;
18651
+ }
18652
+ var feature = this.flags.get(featureName);
18653
+ if (!feature) {
18654
+ logger.log('No flag found: "' + featureName + '"');
18655
+ return fallback;
18656
+ }
18657
+ this.trackFeatureCheck(featureName, feature);
18658
+ return feature;
18659
+ };
18660
+
18661
+ FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
18662
+ return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
18663
+ return feature['value'];
18664
+ }).catch(function(error) {
18665
+ logger.error(error);
18666
+ return fallbackValue;
18667
+ });
18668
+ };
18669
+
18670
+ // TODO remove deprecated method
18671
+ FeatureFlagManager.prototype.getFeatureData = function(featureName, fallbackValue) {
18672
+ logger.critical('mixpanel.flags.get_feature_data() is deprecated and will be removed in a future release. Use mixpanel.flags.get_variant_value() instead.');
18673
+ return this.getVariantValue(featureName, fallbackValue);
18674
+ };
18675
+
18676
+ FeatureFlagManager.prototype.getVariantValueSync = function(featureName, fallbackValue) {
18677
+ return this.getVariantSync(featureName, {'value': fallbackValue})['value'];
18678
+ };
18679
+
18680
+ FeatureFlagManager.prototype.isEnabled = function(featureName, fallbackValue) {
18681
+ return this.getVariantValue(featureName).then(function() {
18682
+ return this.isEnabledSync(featureName, fallbackValue);
18683
+ }.bind(this)).catch(function(error) {
18684
+ logger.error(error);
18685
+ return fallbackValue;
18686
+ });
18687
+ };
18688
+
18689
+ FeatureFlagManager.prototype.isEnabledSync = function(featureName, fallbackValue) {
18690
+ fallbackValue = fallbackValue || false;
18691
+ var val = this.getVariantValueSync(featureName, fallbackValue);
18692
+ if (val !== true && val !== false) {
18693
+ logger.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
18694
+ val = fallbackValue;
18695
+ }
18696
+ return val;
18697
+ };
18698
+
18699
+ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
18700
+ if (this.trackedFeatures.has(featureName)) {
18701
+ return;
18702
+ }
18703
+ this.trackedFeatures.add(featureName);
18704
+ this.track('$experiment_started', {
18705
+ 'Experiment name': featureName,
18706
+ 'Variant name': feature['key'],
18707
+ '$experiment_type': 'feature_flag'
18708
+ });
18709
+ };
18710
+
18711
+ function minApisSupported() {
18712
+ return !!fetch &&
18713
+ typeof Promise !== 'undefined' &&
18714
+ typeof Map !== 'undefined' &&
18715
+ typeof Set !== 'undefined';
18716
+ }
18717
+
18718
+ safewrapClass(FeatureFlagManager);
18719
+
18720
+ FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
18721
+ FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
18722
+ FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
18723
+ FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
18724
+ FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
18725
+ FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
18726
+ FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
18727
+
18728
+ // Deprecated method
18729
+ FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
18730
+
18524
18731
  /* eslint camelcase: "off" */
18525
18732
 
18526
18733
 
@@ -19215,18 +19422,8 @@
19215
19422
  * @param {Function} [callback] If provided, the callback will be called when the server responds
19216
19423
  * @deprecated
19217
19424
  */
19218
- MixpanelPeople.prototype.track_charge = addOptOutCheckMixpanelPeople(function(amount, properties, callback) {
19219
- if (!_.isNumber(amount)) {
19220
- amount = parseFloat(amount);
19221
- if (isNaN(amount)) {
19222
- console$1.error('Invalid value passed to mixpanel.people.track_charge - must be a number');
19223
- return;
19224
- }
19225
- }
19226
-
19227
- return this.append('$transactions', _.extend({
19228
- '$amount': amount
19229
- }, properties), callback);
19425
+ MixpanelPeople.prototype.track_charge = addOptOutCheckMixpanelPeople(function() {
19426
+ console$1.error('mixpanel.people.track_charge() is deprecated and no longer has any effect.');
19230
19427
  });
19231
19428
 
19232
19429
  /*
@@ -19925,10 +20122,11 @@
19925
20122
  }
19926
20123
 
19927
20124
  var DEFAULT_API_ROUTES = {
19928
- 'track': 'track/',
20125
+ 'track': 'track/',
19929
20126
  'engage': 'engage/',
19930
20127
  'groups': 'groups/',
19931
- 'record': 'record/'
20128
+ 'record': 'record/',
20129
+ 'flags': 'flags/'
19932
20130
  };
19933
20131
 
19934
20132
  /*
@@ -19937,6 +20135,7 @@
19937
20135
  var DEFAULT_CONFIG = {
19938
20136
  'api_host': 'https://api-js.mixpanel.com',
19939
20137
  'api_routes': DEFAULT_API_ROUTES,
20138
+ 'api_extra_query_params': {},
19940
20139
  'api_method': 'POST',
19941
20140
  'api_transport': 'XHR',
19942
20141
  'api_payload_format': PAYLOAD_TYPE_BASE64,
@@ -19946,6 +20145,7 @@
19946
20145
  'cross_site_cookie': false,
19947
20146
  'cross_subdomain_cookie': true,
19948
20147
  'error_reporter': NOOP_FUNC,
20148
+ 'flags': false,
19949
20149
  'persistence': 'cookie',
19950
20150
  'persistence_name': '',
19951
20151
  'cookie_domain': '',
@@ -19986,6 +20186,7 @@
19986
20186
  'record_block_selector': 'img, video',
19987
20187
  'record_canvas': false,
19988
20188
  'record_collect_fonts': false,
20189
+ 'record_heatmap_data': false,
19989
20190
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
19990
20191
  'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
19991
20192
  'record_mask_text_selector': '*',
@@ -20201,6 +20402,14 @@
20201
20402
  }, '');
20202
20403
  }
20203
20404
 
20405
+ this.flags = new FeatureFlagManager({
20406
+ getConfigFunc: _.bind(this.get_config, this),
20407
+ getDistinctIdFunc: _.bind(this.get_distinct_id, this),
20408
+ trackingFunc: _.bind(this.track, this)
20409
+ });
20410
+ this.flags.init();
20411
+ this['flags'] = this.flags;
20412
+
20204
20413
  this.autocapture = new Autocapture(this);
20205
20414
  this.autocapture.init();
20206
20415
 
@@ -20326,6 +20535,10 @@
20326
20535
  }
20327
20536
  };
20328
20537
 
20538
+ MixpanelLib.prototype.is_recording_heatmap_data = function () {
20539
+ return this._get_session_replay_id() && this.get_config('record_heatmap_data');
20540
+ };
20541
+
20329
20542
  MixpanelLib.prototype.get_session_recording_properties = function () {
20330
20543
  var props = {};
20331
20544
  var replay_id = this._get_session_replay_id();
@@ -20510,6 +20723,8 @@
20510
20723
  delete data['data'];
20511
20724
  }
20512
20725
 
20726
+ _.extend(data, this.get_config('api_extra_query_params'));
20727
+
20513
20728
  url += '?' + _.HTTPBuildQuery(data);
20514
20729
 
20515
20730
  var lib = this;
@@ -21407,6 +21622,11 @@
21407
21622
  '$anon_distinct_id': previous_distinct_id
21408
21623
  }, {skip_hooks: true});
21409
21624
  }
21625
+
21626
+ // check feature flags again if distinct id has changed
21627
+ if (new_distinct_id !== previous_distinct_id) {
21628
+ this.flags.fetchFlags();
21629
+ }
21410
21630
  };
21411
21631
 
21412
21632
  /**
@@ -21421,6 +21641,8 @@
21421
21641
  'distinct_id': DEVICE_ID_PREFIX + uuid,
21422
21642
  '$device_id': uuid
21423
21643
  }, '');
21644
+ this.stop_session_recording();
21645
+ this._check_and_start_session_recording();
21424
21646
  };
21425
21647
 
21426
21648
  /**
@@ -21681,7 +21903,7 @@
21681
21903
  }
21682
21904
  Config.DEBUG = Config.DEBUG || this.get_config('debug');
21683
21905
 
21684
- if ('autocapture' in config && this.autocapture) {
21906
+ if (('autocapture' in config || 'record_heatmap_data' in config) && this.autocapture) {
21685
21907
  this.autocapture.init();
21686
21908
  }
21687
21909
  }