mixpanel-browser 2.78.0 → 2.80.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.
Files changed (67) hide show
  1. package/.eslintrc.json +12 -0
  2. package/.github/workflows/integration-tests.yml +1 -0
  3. package/.github/workflows/openfeature-provider-tests.yml +31 -0
  4. package/CHANGELOG.md +14 -1
  5. package/build.sh +2 -2
  6. package/dist/async-modules/{mixpanel-recorder-BjSlYaNJ.min.js → mixpanel-recorder-B61POiHc.min.js} +2 -2
  7. package/dist/async-modules/mixpanel-recorder-B61POiHc.min.js.map +1 -0
  8. package/dist/async-modules/{mixpanel-recorder-zMBXIyeG.js → mixpanel-recorder-C3AW7mPl.js} +63 -35
  9. package/dist/async-modules/{mixpanel-targeting-UHf4eBfC.js → mixpanel-targeting-CBwOQJZw.js} +24 -13
  10. package/dist/async-modules/mixpanel-targeting-kdl-eE-1.min.js +2 -0
  11. package/dist/async-modules/mixpanel-targeting-kdl-eE-1.min.js.map +1 -0
  12. package/dist/mixpanel-core.cjs.d.ts +45 -1
  13. package/dist/mixpanel-core.cjs.js +577 -209
  14. package/dist/mixpanel-recorder.js +63 -35
  15. package/dist/mixpanel-recorder.min.js +1 -1
  16. package/dist/mixpanel-recorder.min.js.map +1 -1
  17. package/dist/mixpanel-targeting.js +24 -13
  18. package/dist/mixpanel-targeting.min.js +1 -1
  19. package/dist/mixpanel-targeting.min.js.map +1 -1
  20. package/dist/mixpanel-with-async-modules.cjs.d.ts +45 -1
  21. package/dist/mixpanel-with-async-modules.cjs.js +579 -211
  22. package/dist/mixpanel-with-async-recorder.cjs.d.ts +45 -1
  23. package/dist/mixpanel-with-async-recorder.cjs.js +579 -211
  24. package/dist/mixpanel-with-recorder.d.ts +45 -1
  25. package/dist/mixpanel-with-recorder.js +508 -136
  26. package/dist/mixpanel-with-recorder.min.d.ts +45 -1
  27. package/dist/mixpanel-with-recorder.min.js +1 -1
  28. package/dist/mixpanel.amd.d.ts +45 -1
  29. package/dist/mixpanel.amd.js +508 -136
  30. package/dist/mixpanel.cjs.d.ts +45 -1
  31. package/dist/mixpanel.cjs.js +508 -136
  32. package/dist/mixpanel.globals.js +579 -211
  33. package/dist/mixpanel.min.js +200 -190
  34. package/dist/mixpanel.module.d.ts +45 -1
  35. package/dist/mixpanel.module.js +508 -136
  36. package/dist/mixpanel.umd.d.ts +45 -1
  37. package/dist/mixpanel.umd.js +508 -136
  38. package/package.json +1 -1
  39. package/packages/openfeature-web-provider/README.md +357 -0
  40. package/packages/openfeature-web-provider/package-lock.json +1636 -0
  41. package/packages/openfeature-web-provider/package.json +51 -0
  42. package/packages/openfeature-web-provider/rollup.config.browser.mjs +26 -0
  43. package/packages/openfeature-web-provider/src/MixpanelProvider.ts +302 -0
  44. package/packages/openfeature-web-provider/src/index.ts +1 -0
  45. package/packages/openfeature-web-provider/src/types.ts +72 -0
  46. package/packages/openfeature-web-provider/test/MixpanelProvider.spec.ts +484 -0
  47. package/packages/openfeature-web-provider/tsconfig.json +15 -0
  48. package/src/autocapture/index.js +17 -12
  49. package/src/config.js +1 -1
  50. package/src/flags/flags-persistence.js +176 -0
  51. package/src/flags/index.js +176 -25
  52. package/src/index.d.ts +45 -1
  53. package/src/mixpanel-core.js +24 -7
  54. package/src/recorder/idb-config.js +16 -0
  55. package/src/recorder/recording-registry.js +7 -2
  56. package/src/recorder/session-recording.js +15 -6
  57. package/src/recorder-manager.js +7 -2
  58. package/src/request-queue.js +1 -2
  59. package/src/shared-lock.js +2 -3
  60. package/src/storage/indexed-db.js +16 -15
  61. package/src/storage/local-storage.js +5 -3
  62. package/src/utils.js +25 -12
  63. package/tsconfig.base.json +9 -0
  64. package/.claude/settings.local.json +0 -16
  65. package/dist/async-modules/mixpanel-recorder-BjSlYaNJ.min.js.map +0 -1
  66. package/dist/async-modules/mixpanel-targeting-BSHal4N9.min.js +0 -2
  67. package/dist/async-modules/mixpanel-targeting-BSHal4N9.min.js.map +0 -1
@@ -28,7 +28,7 @@
28
28
 
29
29
  var Config = {
30
30
  DEBUG: false,
31
- LIB_VERSION: '2.78.0'
31
+ LIB_VERSION: '2.80.0'
32
32
  };
33
33
 
34
34
  // Window global names for async modules
@@ -19126,6 +19126,7 @@
19126
19126
  var console_with_prefix = function(prefix) {
19127
19127
  return {
19128
19128
  log: log_func_with_prefix(console$1.log, prefix),
19129
+ warn: log_func_with_prefix(console$1.warn, prefix),
19129
19130
  error: log_func_with_prefix(console$1.error, prefix),
19130
19131
  critical: log_func_with_prefix(console$1.critical, prefix)
19131
19132
  };
@@ -20072,7 +20073,8 @@
20072
20073
  if (_localStorageSupported !== null && !forceCheck) {
20073
20074
  return _localStorageSupported;
20074
20075
  }
20075
- return _localStorageSupported = _testStorageSupported(storage || win.localStorage);
20076
+
20077
+ return _localStorageSupported = _testStorageSupported(storage);
20076
20078
  };
20077
20079
 
20078
20080
  var _sessionStorageSupported = null;
@@ -20080,7 +20082,8 @@
20080
20082
  if (_sessionStorageSupported !== null && !forceCheck) {
20081
20083
  return _sessionStorageSupported;
20082
20084
  }
20083
- return _sessionStorageSupported = _testStorageSupported(storage || win.sessionStorage);
20085
+
20086
+ return _sessionStorageSupported = _testStorageSupported(storage);
20084
20087
  };
20085
20088
 
20086
20089
  function _storageWrapper(storage, name, is_supported_fn) {
@@ -20130,17 +20133,26 @@
20130
20133
  };
20131
20134
  }
20132
20135
 
20133
- // Safari errors out accessing localStorage/sessionStorage when cookies are disabled,
20134
- // so create dummy storage wrappers that silently fail as a fallback.
20135
- var windowLocalStorage = null, windowSessionStorage = null;
20136
- try {
20137
- windowLocalStorage = win.localStorage;
20138
- windowSessionStorage = win.sessionStorage;
20139
- // eslint-disable-next-line no-empty
20140
- } catch (_err) {}
20136
+ // Safari and other browsers may error out accessing localStorage/sessionStorage
20137
+ // when cookies are disabled, so wrap access in a try-catch.
20138
+ var getLocalStorage = function() {
20139
+ try {
20140
+ return win.localStorage; // eslint-disable-line no-restricted-properties
20141
+ } catch (_err) {
20142
+ return null;
20143
+ }
20144
+ };
20145
+
20146
+ var getSessionStorage = function() {
20147
+ try {
20148
+ return win.sessionStorage; // eslint-disable-line no-restricted-properties
20149
+ } catch (_err) {
20150
+ return null;
20151
+ }
20152
+ };
20141
20153
 
20142
- _.localStorage = _storageWrapper(windowLocalStorage, 'localStorage', localStorageSupported);
20143
- _.sessionStorage = _storageWrapper(windowSessionStorage, 'sessionStorage', sessionStorageSupported);
20154
+ _.localStorage = _storageWrapper(getLocalStorage(), 'localStorage', localStorageSupported);
20155
+ _.sessionStorage = _storageWrapper(getSessionStorage(), 'sessionStorage', sessionStorageSupported);
20144
20156
 
20145
20157
  _.register_event = (function() {
20146
20158
  // written by Dean Edwards, 2005
@@ -20809,29 +20821,26 @@
20809
20821
  _['toArray'] = _.toArray;
20810
20822
  _['NPO'] = NpoPromise;
20811
20823
 
20812
- var MIXPANEL_DB_NAME = 'mixpanelBrowserDb';
20813
-
20814
- var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
20815
- var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
20816
-
20817
- // note: increment the version number when adding new object stores
20818
- var DB_VERSION = 1;
20819
- var OBJECT_STORES = [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME];
20820
-
20821
20824
  /**
20822
20825
  * @type {import('./wrapper').StorageWrapper}
20823
20826
  */
20824
- var IDBStorageWrapper = function (storeName) {
20827
+ var IDBStorageWrapper = function (dbName, storeName, versionData) {
20828
+ this.dbName = dbName;
20829
+ this.storeName = storeName;
20830
+ this.version = versionData.version;
20831
+ this.storeNamesInDb = versionData.storeNames;
20825
20832
  /**
20826
20833
  * @type {Promise<IDBDatabase>|null}
20827
20834
  */
20828
20835
  this.dbPromise = null;
20829
- this.storeName = storeName;
20830
20836
  };
20831
20837
 
20832
20838
  IDBStorageWrapper.prototype._openDb = function () {
20839
+ var dbName = this.dbName;
20840
+ var version = this.version;
20841
+ var storeNamesInDb = this.storeNamesInDb;
20833
20842
  return new PromisePolyfill(function (resolve, reject) {
20834
- var openRequest = win.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
20843
+ var openRequest = win.indexedDB.open(dbName, version);
20835
20844
  openRequest['onerror'] = function () {
20836
20845
  reject(openRequest.error);
20837
20846
  };
@@ -20843,8 +20852,10 @@
20843
20852
  openRequest['onupgradeneeded'] = function (ev) {
20844
20853
  var db = ev.target.result;
20845
20854
 
20846
- OBJECT_STORES.forEach(function (storeName) {
20847
- db.createObjectStore(storeName);
20855
+ storeNamesInDb.forEach(function (storeName) {
20856
+ if (!db.objectStoreNames.contains(storeName)) {
20857
+ db.createObjectStore(storeName);
20858
+ }
20848
20859
  });
20849
20860
  };
20850
20861
  });
@@ -20936,6 +20947,16 @@
20936
20947
  });
20937
20948
  };
20938
20949
 
20950
+ var MIXPANEL_BROWSER_DB_NAME = 'mixpanelBrowserDb';
20951
+ var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
20952
+ var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
20953
+
20954
+ // Keeping these two properties closeby, as adding additional stores to a DB in IndexedDB requires a version increment
20955
+ var RECORDER_VERSION_DATA = {
20956
+ version: 1,
20957
+ storeNames: [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME]
20958
+ };
20959
+
20939
20960
  /**
20940
20961
  * GDPR utils
20941
20962
  *
@@ -21236,7 +21257,7 @@
21236
21257
  };
21237
21258
  }
21238
21259
 
21239
- var logger$8 = console_with_prefix('lock');
21260
+ var logger$9 = console_with_prefix('lock');
21240
21261
 
21241
21262
  /**
21242
21263
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -21262,7 +21283,7 @@
21262
21283
  options = options || {};
21263
21284
 
21264
21285
  this.storageKey = key;
21265
- this.storage = options.storage || win.localStorage;
21286
+ this.storage = options.storage || getLocalStorage();
21266
21287
  this.pollIntervalMS = options.pollIntervalMS || 100;
21267
21288
  this.timeoutMS = options.timeoutMS || 2000;
21268
21289
 
@@ -21288,7 +21309,7 @@
21288
21309
 
21289
21310
  var delay = function(cb) {
21290
21311
  if (new Date().getTime() - startTime > timeoutMS) {
21291
- logger$8.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21312
+ logger$9.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21292
21313
  storage.removeItem(keyZ);
21293
21314
  storage.removeItem(keyY);
21294
21315
  loop();
@@ -21390,10 +21411,13 @@
21390
21411
  * @type {import('./wrapper').StorageWrapper}
21391
21412
  */
21392
21413
  var LocalStorageWrapper = function (storageOverride) {
21393
- this.storage = storageOverride || win.localStorage;
21414
+ this.storage = storageOverride || getLocalStorage();
21394
21415
  };
21395
21416
 
21396
21417
  LocalStorageWrapper.prototype.init = function () {
21418
+ if (!this.storage) {
21419
+ return PromisePolyfill.reject(new Error('localStorage is not available'));
21420
+ }
21397
21421
  return PromisePolyfill.resolve();
21398
21422
  };
21399
21423
 
@@ -21435,7 +21459,7 @@
21435
21459
  }, this));
21436
21460
  };
21437
21461
 
21438
- var logger$7 = console_with_prefix('batch');
21462
+ var logger$8 = console_with_prefix('batch');
21439
21463
 
21440
21464
  /**
21441
21465
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -21460,11 +21484,11 @@
21460
21484
  if (this.usePersistence) {
21461
21485
  this.queueStorage = options.queueStorage || new LocalStorageWrapper();
21462
21486
  this.lock = new SharedLock(storageKey, {
21463
- storage: options.sharedLockStorage || win.localStorage,
21487
+ storage: options.sharedLockStorage,
21464
21488
  timeoutMS: options.sharedLockTimeoutMS,
21465
21489
  });
21466
21490
  }
21467
- this.reportError = options.errorReporter || _.bind(logger$7.error, logger$7);
21491
+ this.reportError = options.errorReporter || _.bind(logger$8.error, logger$8);
21468
21492
 
21469
21493
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
21470
21494
 
@@ -21797,7 +21821,7 @@
21797
21821
  // maximum interval between request retries after exponential backoff
21798
21822
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
21799
21823
 
21800
- var logger$6 = console_with_prefix('batch');
21824
+ var logger$7 = console_with_prefix('batch');
21801
21825
 
21802
21826
  /**
21803
21827
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -21925,7 +21949,7 @@
21925
21949
  */
21926
21950
  RequestBatcher.prototype.flush = function(options) {
21927
21951
  if (this.requestInProgress) {
21928
- logger$6.log('Flush: Request already in progress');
21952
+ logger$7.log('Flush: Request already in progress');
21929
21953
  return PromisePolyfill.resolve();
21930
21954
  }
21931
21955
 
@@ -22102,7 +22126,7 @@
22102
22126
  if (options.unloading) {
22103
22127
  requestOptions.transport = 'sendBeacon';
22104
22128
  }
22105
- logger$6.log('MIXPANEL REQUEST:', dataForRequest);
22129
+ logger$7.log('MIXPANEL REQUEST:', dataForRequest);
22106
22130
  return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
22107
22131
  }, this))
22108
22132
  .catch(_.bind(function(err) {
@@ -22115,7 +22139,7 @@
22115
22139
  * Log error to global logger and optional user-defined logger.
22116
22140
  */
22117
22141
  RequestBatcher.prototype.reportError = function(msg, err) {
22118
- logger$6.error.apply(logger$6.error, arguments);
22142
+ logger$7.error.apply(logger$7.error, arguments);
22119
22143
  if (this.errorReporter) {
22120
22144
  try {
22121
22145
  if (!(err instanceof Error)) {
@@ -22123,7 +22147,7 @@
22123
22147
  }
22124
22148
  this.errorReporter(msg, err);
22125
22149
  } catch(err) {
22126
- logger$6.error(err);
22150
+ logger$7.error(err);
22127
22151
  }
22128
22152
  }
22129
22153
  };
@@ -22268,7 +22292,7 @@
22268
22292
 
22269
22293
  var MAX_DEPTH = 5;
22270
22294
 
22271
- var logger$5 = console_with_prefix('autocapture');
22295
+ var logger$6 = console_with_prefix('autocapture');
22272
22296
 
22273
22297
 
22274
22298
  function getClasses(el) {
@@ -22532,7 +22556,7 @@
22532
22556
  return false;
22533
22557
  }
22534
22558
  } catch (err) {
22535
- logger$5.critical('Error while checking element in allowElementCallback', err);
22559
+ logger$6.critical('Error while checking element in allowElementCallback', err);
22536
22560
  return false;
22537
22561
  }
22538
22562
  }
@@ -22549,7 +22573,7 @@
22549
22573
  return true;
22550
22574
  }
22551
22575
  } catch (err) {
22552
- logger$5.critical('Error while checking selector: ' + sel, err);
22576
+ logger$6.critical('Error while checking selector: ' + sel, err);
22553
22577
  }
22554
22578
  }
22555
22579
  return false;
@@ -22564,7 +22588,7 @@
22564
22588
  return true;
22565
22589
  }
22566
22590
  } catch (err) {
22567
- logger$5.critical('Error while checking element in blockElementCallback', err);
22591
+ logger$6.critical('Error while checking element in blockElementCallback', err);
22568
22592
  return true;
22569
22593
  }
22570
22594
  }
@@ -22578,7 +22602,7 @@
22578
22602
  return true;
22579
22603
  }
22580
22604
  } catch (err) {
22581
- logger$5.critical('Error while checking selector: ' + sel, err);
22605
+ logger$6.critical('Error while checking selector: ' + sel, err);
22582
22606
  }
22583
22607
  }
22584
22608
  }
@@ -23134,7 +23158,7 @@
23134
23158
  *
23135
23159
  */
23136
23160
 
23137
- var logger$4 = console_with_prefix('network-plugin');
23161
+ var logger$5 = console_with_prefix('network-plugin');
23138
23162
 
23139
23163
  /**
23140
23164
  * Get the time origin for converting performance timestamps to absolute timestamps.
@@ -23286,7 +23310,7 @@
23286
23310
  return str;
23287
23311
  }
23288
23312
  if (str.length > MAX_BODY_SIZE) {
23289
- logger$4.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
23313
+ logger$5.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
23290
23314
  return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
23291
23315
  }
23292
23316
  return str;
@@ -23300,7 +23324,7 @@
23300
23324
  */
23301
23325
  function initPerformanceObserver(cb, win, options) {
23302
23326
  if (!win.PerformanceObserver) {
23303
- logger$4.error('PerformanceObserver not supported');
23327
+ logger$5.error('PerformanceObserver not supported');
23304
23328
  return function() {
23305
23329
  //
23306
23330
  };
@@ -23453,7 +23477,7 @@
23453
23477
  attempt = 0;
23454
23478
  }
23455
23479
  if (attempt > 10) {
23456
- logger$4.error('Cannot find performance entry');
23480
+ logger$5.error('Cannot find performance entry');
23457
23481
  return Promise.resolve(null);
23458
23482
  }
23459
23483
  var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
@@ -23574,7 +23598,7 @@
23574
23598
  )
23575
23599
  .then(function(entry) {
23576
23600
  if (!entry) {
23577
- logger$4.error('Failed to get performance entry for XHR request to ' + req.url);
23601
+ logger$5.error('Failed to get performance entry for XHR request to ' + req.url);
23578
23602
  return;
23579
23603
  }
23580
23604
  /** @type {NetworkRequest} */
@@ -23594,7 +23618,7 @@
23594
23618
  cb({ requests: [request] });
23595
23619
  })
23596
23620
  .catch(function(e) {
23597
- logger$4.error('Error recording XHR request to ' + req.url + ': ' + String(e));
23621
+ logger$5.error('Error recording XHR request to ' + req.url + ': ' + String(e));
23598
23622
  });
23599
23623
  });
23600
23624
 
@@ -23686,7 +23710,7 @@
23686
23710
  })
23687
23711
  .then(function(entry) {
23688
23712
  if (!entry) {
23689
- logger$4.error('Failed to get performance entry for fetch request to ' + req.url);
23713
+ logger$5.error('Failed to get performance entry for fetch request to ' + req.url);
23690
23714
  return;
23691
23715
  }
23692
23716
  /** @type {NetworkRequest} */
@@ -23706,7 +23730,7 @@
23706
23730
  cb({ requests: [request] });
23707
23731
  })
23708
23732
  .catch(function (e) {
23709
- logger$4.error('Error recording fetch request to ' + req.url + ': ' + String(e));
23733
+ logger$5.error('Error recording fetch request to ' + req.url + ': ' + String(e));
23710
23734
  });
23711
23735
 
23712
23736
  return originalFetchPromise;
@@ -23779,7 +23803,7 @@
23779
23803
  */
23780
23804
 
23781
23805
 
23782
- var logger$3 = console_with_prefix('recorder');
23806
+ var logger$4 = console_with_prefix('recorder');
23783
23807
  var CompressionStream = win['CompressionStream'];
23784
23808
 
23785
23809
  var RECORDER_BATCHER_LIB_CONFIG = {
@@ -23873,14 +23897,15 @@
23873
23897
 
23874
23898
  this.recordMaxMs = MAX_RECORDING_MS;
23875
23899
  this.recordMinMs = 0;
23900
+ this._recordMinMsCheckStart = null;
23876
23901
 
23877
23902
  // disable persistence if localStorage is not supported
23878
23903
  // request-queue will automatically disable persistence if indexedDB fails to initialize
23879
- var usePersistence = localStorageSupported(options.sharedLockStorage, true) && !this.getConfig('disable_persistence');
23904
+ var usePersistence = localStorageSupported(options.sharedLockStorage || getLocalStorage(), true) && !this.getConfig('disable_persistence');
23880
23905
 
23881
23906
  // each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
23882
23907
  this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
23883
- this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
23908
+ this.queueStorage = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_EVENTS_STORE_NAME, RECORDER_VERSION_DATA);
23884
23909
  this.batcher = new RequestBatcher(this.batcherKey, {
23885
23910
  errorReporter: this.reportError.bind(this),
23886
23911
  flushOnlyOnInterval: true,
@@ -23959,14 +23984,14 @@
23959
23984
  }
23960
23985
 
23961
23986
  if (this._stopRecording !== null) {
23962
- logger$3.log('Recording already in progress, skipping startRecording.');
23987
+ logger$4.log('Recording already in progress, skipping startRecording.');
23963
23988
  return;
23964
23989
  }
23965
23990
 
23966
23991
  this.recordMaxMs = this.getConfig('record_max_ms');
23967
23992
  if (this.recordMaxMs > MAX_RECORDING_MS) {
23968
23993
  this.recordMaxMs = MAX_RECORDING_MS;
23969
- logger$3.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
23994
+ logger$4.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
23970
23995
  }
23971
23996
 
23972
23997
  if (!this.maxExpires) {
@@ -23988,6 +24013,7 @@
23988
24013
  // this also applies if the minimum recording length has not been hit yet
23989
24014
  // so that we don't send data until we know the recording will be long enough
23990
24015
  this.batcher.stop();
24016
+ this._recordMinMsCheckStart = null;
23991
24017
  } else {
23992
24018
  this.batcher.start();
23993
24019
  }
@@ -24030,7 +24056,7 @@
24030
24056
  );
24031
24057
  }
24032
24058
 
24033
- var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$3);
24059
+ var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$4);
24034
24060
 
24035
24061
  try {
24036
24062
  this._stopRecording = this._rrwebRecord({
@@ -24039,9 +24065,11 @@
24039
24065
  this._onIdleTimeout();
24040
24066
  return;
24041
24067
  }
24068
+ if (this._recordMinMsCheckStart === null) {
24069
+ this._recordMinMsCheckStart = ev.timestamp;
24070
+ }
24042
24071
  if (isUserEvent(ev)) {
24043
- if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
24044
- // start flushing again after user activity
24072
+ if (this.batcher.stopped && ev.timestamp - this._recordMinMsCheckStart >= this.recordMinMs) {
24045
24073
  this.batcher.start();
24046
24074
  }
24047
24075
  resetIdleTimeout();
@@ -24292,14 +24320,14 @@
24292
24320
 
24293
24321
 
24294
24322
  SessionRecording.prototype.reportError = function(msg, err) {
24295
- logger$3.error.apply(logger$3.error, arguments);
24323
+ logger$4.error.apply(logger$4.error, arguments);
24296
24324
  try {
24297
24325
  if (!err && !(msg instanceof Error)) {
24298
24326
  msg = new Error(msg);
24299
24327
  }
24300
24328
  this.getConfig('error_reporter')(msg, err);
24301
24329
  } catch(err) {
24302
- logger$3.error(err);
24330
+ logger$4.error(err);
24303
24331
  }
24304
24332
  };
24305
24333
 
@@ -24328,7 +24356,7 @@
24328
24356
  var configValue = this.getConfig('record_min_ms');
24329
24357
 
24330
24358
  if (configValue > MAX_VALUE_FOR_MIN_RECORDING_MS) {
24331
- logger$3.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
24359
+ logger$4.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
24332
24360
  return MAX_VALUE_FOR_MIN_RECORDING_MS;
24333
24361
  }
24334
24362
 
@@ -24370,7 +24398,7 @@
24370
24398
  */
24371
24399
  var RecordingRegistry = function (options) {
24372
24400
  /** @type {IDBStorageWrapper} */
24373
- this.idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
24401
+ this.idb = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_REGISTRY_STORE_NAME, RECORDER_VERSION_DATA);
24374
24402
  this.errorReporter = options.errorReporter;
24375
24403
  this.mixpanelInstance = options.mixpanelInstance;
24376
24404
  this.sharedLockStorage = options.sharedLockStorage;
@@ -24491,7 +24519,7 @@
24491
24519
  .catch(this.handleError.bind(this));
24492
24520
  };
24493
24521
 
24494
- var logger$2 = console_with_prefix('recorder');
24522
+ var logger$3 = console_with_prefix('recorder');
24495
24523
 
24496
24524
  /**
24497
24525
  * Recorder API: bundles rrweb and and exposes methods to start and stop recordings.
@@ -24507,7 +24535,7 @@
24507
24535
  */
24508
24536
  this.recordingRegistry = new RecordingRegistry({
24509
24537
  mixpanelInstance: this.mixpanelInstance,
24510
- errorReporter: logger$2.error,
24538
+ errorReporter: logger$3.error,
24511
24539
  sharedLockStorage: sharedLockStorage
24512
24540
  });
24513
24541
  this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
@@ -24519,17 +24547,17 @@
24519
24547
  MixpanelRecorder.prototype.startRecording = function(options) {
24520
24548
  options = options || {};
24521
24549
  if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
24522
- logger$2.log('Recording already in progress, skipping startRecording.');
24550
+ logger$3.log('Recording already in progress, skipping startRecording.');
24523
24551
  return;
24524
24552
  }
24525
24553
 
24526
24554
  var onIdleTimeout = function () {
24527
- logger$2.log('Idle timeout reached, restarting recording.');
24555
+ logger$3.log('Idle timeout reached, restarting recording.');
24528
24556
  this.resetRecording();
24529
24557
  }.bind(this);
24530
24558
 
24531
24559
  var onMaxLengthReached = function () {
24532
- logger$2.log('Max recording length reached, stopping recording.');
24560
+ logger$3.log('Max recording length reached, stopping recording.');
24533
24561
  this.resetRecording();
24534
24562
  }.bind(this);
24535
24563
 
@@ -24599,7 +24627,7 @@
24599
24627
  } else if (startNewIfInactive) {
24600
24628
  return this.startRecording({shouldStopBatcher: false});
24601
24629
  } else {
24602
- logger$2.log('No resumable recording found.');
24630
+ logger$3.log('No resumable recording found.');
24603
24631
  return null;
24604
24632
  }
24605
24633
  }.bind(this));
@@ -24733,7 +24761,7 @@
24733
24761
  observer.observe(shadowRoot, this.observerConfig);
24734
24762
  this.shadowObservers.push(observer);
24735
24763
  } catch (e) {
24736
- logger$5.critical('Error while observing shadow root', e);
24764
+ logger$6.critical('Error while observing shadow root', e);
24737
24765
  }
24738
24766
  };
24739
24767
 
@@ -24744,7 +24772,7 @@
24744
24772
  }
24745
24773
 
24746
24774
  if (!weakSetSupported()) {
24747
- logger$5.critical('Shadow DOM observation unavailable: WeakSet not supported');
24775
+ logger$6.critical('Shadow DOM observation unavailable: WeakSet not supported');
24748
24776
  return;
24749
24777
  }
24750
24778
 
@@ -24760,7 +24788,7 @@
24760
24788
  try {
24761
24789
  this.shadowObservers[i].disconnect();
24762
24790
  } catch (e) {
24763
- logger$5.critical('Error while disconnecting shadow DOM observer', e);
24791
+ logger$6.critical('Error while disconnecting shadow DOM observer', e);
24764
24792
  }
24765
24793
  }
24766
24794
  this.shadowObservers = [];
@@ -24948,7 +24976,7 @@
24948
24976
 
24949
24977
  this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
24950
24978
  } catch (e) {
24951
- logger$5.critical('Error while setting up mutation observer', e);
24979
+ logger$6.critical('Error while setting up mutation observer', e);
24952
24980
  }
24953
24981
  }
24954
24982
 
@@ -24963,7 +24991,7 @@
24963
24991
  );
24964
24992
  this.shadowDOMObserver.start();
24965
24993
  } catch (e) {
24966
- logger$5.critical('Error while setting up shadow DOM observer', e);
24994
+ logger$6.critical('Error while setting up shadow DOM observer', e);
24967
24995
  this.shadowDOMObserver = null;
24968
24996
  }
24969
24997
  }
@@ -24990,7 +25018,7 @@
24990
25018
  try {
24991
25019
  listener.target.removeEventListener(listener.event, listener.handler, listener.options);
24992
25020
  } catch (e) {
24993
- logger$5.critical('Error while removing event listener', e);
25021
+ logger$6.critical('Error while removing event listener', e);
24994
25022
  }
24995
25023
  }
24996
25024
  this.eventListeners = [];
@@ -24999,7 +25027,7 @@
24999
25027
  try {
25000
25028
  this.mutationObserver.disconnect();
25001
25029
  } catch (e) {
25002
- logger$5.critical('Error while disconnecting mutation observer', e);
25030
+ logger$6.critical('Error while disconnecting mutation observer', e);
25003
25031
  }
25004
25032
  this.mutationObserver = null;
25005
25033
  }
@@ -25008,7 +25036,7 @@
25008
25036
  try {
25009
25037
  this.shadowDOMObserver.stop();
25010
25038
  } catch (e) {
25011
- logger$5.critical('Error while stopping shadow DOM observer', e);
25039
+ logger$6.critical('Error while stopping shadow DOM observer', e);
25012
25040
  }
25013
25041
  this.shadowDOMObserver = null;
25014
25042
  }
@@ -25086,7 +25114,7 @@
25086
25114
 
25087
25115
  Autocapture.prototype.init = function() {
25088
25116
  if (!minDOMApisSupported()) {
25089
- logger$5.critical('Autocapture unavailable: missing required DOM APIs');
25117
+ logger$6.critical('Autocapture unavailable: missing required DOM APIs');
25090
25118
  return;
25091
25119
  }
25092
25120
  this.initPageListeners();
@@ -25126,7 +25154,7 @@
25126
25154
  try {
25127
25155
  return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
25128
25156
  } catch (err) {
25129
- logger$5.critical('Error while checking block URL regexes: ', err);
25157
+ logger$6.critical('Error while checking block URL regexes: ', err);
25130
25158
  return true;
25131
25159
  }
25132
25160
  }
@@ -25139,7 +25167,7 @@
25139
25167
  try {
25140
25168
  return urlMatchesRegexList(currentUrl, blockUrlRegexes);
25141
25169
  } catch (err) {
25142
- logger$5.critical('Error while checking block URL regexes: ', err);
25170
+ logger$6.critical('Error while checking block URL regexes: ', err);
25143
25171
  return true;
25144
25172
  }
25145
25173
  };
@@ -25277,7 +25305,7 @@
25277
25305
  return;
25278
25306
  }
25279
25307
 
25280
- logger$5.log('Initializing scroll depth tracking');
25308
+ logger$6.log('Initializing scroll depth tracking');
25281
25309
 
25282
25310
  this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
25283
25311
 
@@ -25298,12 +25326,12 @@
25298
25326
  };
25299
25327
 
25300
25328
  Autocapture.prototype.initClickTracking = function() {
25301
- win.removeEventListener(EV_CLICK, this.listenerClick);
25329
+ win.removeEventListener(EV_CLICK, this.listenerClick, true);
25302
25330
 
25303
25331
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
25304
25332
  return;
25305
25333
  }
25306
- logger$5.log('Initializing click tracking');
25334
+ logger$6.log('Initializing click tracking');
25307
25335
 
25308
25336
  this.listenerClick = function(ev) {
25309
25337
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
@@ -25311,7 +25339,7 @@
25311
25339
  }
25312
25340
  this.trackDomEvent(ev, MP_EV_CLICK);
25313
25341
  }.bind(this);
25314
- win.addEventListener(EV_CLICK, this.listenerClick);
25342
+ win.addEventListener(EV_CLICK, this.listenerClick, true);
25315
25343
  };
25316
25344
 
25317
25345
  Autocapture.prototype.initDeadClickTracking = function() {
@@ -25322,7 +25350,7 @@
25322
25350
  return;
25323
25351
  }
25324
25352
 
25325
- logger$5.log('Initializing dead click tracking');
25353
+ logger$6.log('Initializing dead click tracking');
25326
25354
  if (!this._deadClickTracker) {
25327
25355
  this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
25328
25356
  this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
@@ -25346,17 +25374,17 @@
25346
25374
  }
25347
25375
  this._deadClickTracker.trackClick(ev, normalizedConfig);
25348
25376
  }.bind(this);
25349
- win.addEventListener(EV_CLICK, this.listenerDeadClick);
25377
+ win.addEventListener(EV_CLICK, this.listenerDeadClick, true);
25350
25378
  }
25351
25379
  };
25352
25380
 
25353
25381
  Autocapture.prototype.initInputTracking = function() {
25354
- win.removeEventListener(EV_CHANGE, this.listenerChange);
25382
+ win.removeEventListener(EV_CHANGE, this.listenerChange, true);
25355
25383
 
25356
25384
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
25357
25385
  return;
25358
25386
  }
25359
- logger$5.log('Initializing input tracking');
25387
+ logger$6.log('Initializing input tracking');
25360
25388
 
25361
25389
  this.listenerChange = function(ev) {
25362
25390
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
@@ -25364,20 +25392,21 @@
25364
25392
  }
25365
25393
  this.trackDomEvent(ev, MP_EV_INPUT);
25366
25394
  }.bind(this);
25367
- win.addEventListener(EV_CHANGE, this.listenerChange);
25395
+ win.addEventListener(EV_CHANGE, this.listenerChange, true);
25368
25396
  };
25369
25397
 
25370
25398
  Autocapture.prototype.initPageviewTracking = function() {
25371
25399
  win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
25372
25400
 
25373
- if (!this.pageviewTrackingConfig()) {
25401
+ if (!this.pageviewTrackingConfig() && !this.mp.get_config('record_heatmap_data')) {
25374
25402
  return;
25375
25403
  }
25376
- logger$5.log('Initializing pageview tracking');
25404
+ logger$6.log('Initializing pageview tracking');
25377
25405
 
25378
25406
  var previousTrackedUrl = '';
25379
25407
  var tracked = false;
25380
- if (!this.currentUrlBlocked()) {
25408
+ // Track initial pageview if pageview tracking enabled OR heatmap recording is active
25409
+ if ((this.pageviewTrackingConfig() || this.mp.is_recording_heatmap_data()) && !this.currentUrlBlocked()) {
25381
25410
  tracked = this.mp.track_pageview(DEFAULT_PROPS);
25382
25411
  }
25383
25412
  if (tracked) {
@@ -25393,6 +25422,10 @@
25393
25422
  var shouldTrack = false;
25394
25423
  var didPathChange = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
25395
25424
  var trackPageviewOption = this.pageviewTrackingConfig();
25425
+ if (!trackPageviewOption && this.mp.is_recording_heatmap_data()) {
25426
+ trackPageviewOption = PAGEVIEW_OPTION_FULL_URL;
25427
+ }
25428
+
25396
25429
  if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
25397
25430
  shouldTrack = currentUrl !== previousTrackedUrl;
25398
25431
  } else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
@@ -25408,7 +25441,7 @@
25408
25441
  }
25409
25442
  if (didPathChange) {
25410
25443
  this.lastScrollCheckpoint = 0;
25411
- logger$5.log('Path change: re-initializing scroll depth checkpoints');
25444
+ logger$6.log('Path change: re-initializing scroll depth checkpoints');
25412
25445
  }
25413
25446
  }
25414
25447
  }.bind(this));
@@ -25416,14 +25449,14 @@
25416
25449
  };
25417
25450
 
25418
25451
  Autocapture.prototype.initRageClickTracking = function() {
25419
- win.removeEventListener(EV_CLICK, this.listenerRageClick);
25452
+ win.removeEventListener(EV_CLICK, this.listenerRageClick, true);
25420
25453
 
25421
25454
  var rageClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK);
25422
25455
  if (!rageClickConfig && !this.mp.get_config('record_heatmap_data')) {
25423
25456
  return;
25424
25457
  }
25425
25458
 
25426
- logger$5.log('Initializing rage click tracking');
25459
+ logger$6.log('Initializing rage click tracking');
25427
25460
  if (!this._rageClickTracker) {
25428
25461
  this._rageClickTracker = new RageClickTracker();
25429
25462
  }
@@ -25442,7 +25475,7 @@
25442
25475
  this.trackDomEvent(ev, MP_EV_RAGE_CLICK);
25443
25476
  }
25444
25477
  }.bind(this);
25445
- win.addEventListener(EV_CLICK, this.listenerRageClick);
25478
+ win.addEventListener(EV_CLICK, this.listenerRageClick, true);
25446
25479
  };
25447
25480
 
25448
25481
  Autocapture.prototype.initScrollTracking = function() {
@@ -25453,7 +25486,7 @@
25453
25486
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
25454
25487
  return;
25455
25488
  }
25456
- logger$5.log('Initializing scroll tracking');
25489
+ logger$6.log('Initializing scroll tracking');
25457
25490
  this.lastScrollCheckpoint = 0;
25458
25491
 
25459
25492
  var scrollTrackFunction = function() {
@@ -25490,7 +25523,7 @@
25490
25523
  }
25491
25524
  }
25492
25525
  } catch (err) {
25493
- logger$5.critical('Error while calculating scroll percentage', err);
25526
+ logger$6.critical('Error while calculating scroll percentage', err);
25494
25527
  }
25495
25528
  if (shouldTrack) {
25496
25529
  this.mp.track(MP_EV_SCROLL, props);
@@ -25503,12 +25536,12 @@
25503
25536
  };
25504
25537
 
25505
25538
  Autocapture.prototype.initSubmitTracking = function() {
25506
- win.removeEventListener(EV_SUBMIT, this.listenerSubmit);
25539
+ win.removeEventListener(EV_SUBMIT, this.listenerSubmit, true);
25507
25540
 
25508
25541
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
25509
25542
  return;
25510
25543
  }
25511
- logger$5.log('Initializing submit tracking');
25544
+ logger$6.log('Initializing submit tracking');
25512
25545
 
25513
25546
  this.listenerSubmit = function(ev) {
25514
25547
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
@@ -25516,7 +25549,7 @@
25516
25549
  }
25517
25550
  this.trackDomEvent(ev, MP_EV_SUBMIT);
25518
25551
  }.bind(this);
25519
- win.addEventListener(EV_SUBMIT, this.listenerSubmit);
25552
+ win.addEventListener(EV_SUBMIT, this.listenerSubmit, true);
25520
25553
  };
25521
25554
 
25522
25555
  Autocapture.prototype.initPageLeaveTracking = function() {
@@ -25530,7 +25563,7 @@
25530
25563
  return;
25531
25564
  }
25532
25565
 
25533
- logger$5.log('Initializing page visibility tracking.');
25566
+ logger$6.log('Initializing page visibility tracking.');
25534
25567
  this._initScrollDepthTracking();
25535
25568
  var previousTrackedUrl = _.info.currentUrl();
25536
25569
 
@@ -25572,7 +25605,7 @@
25572
25605
 
25573
25606
  Autocapture.prototype.stopDeadClickTracking = function() {
25574
25607
  if (this.listenerDeadClick) {
25575
- win.removeEventListener(EV_CLICK, this.listenerDeadClick);
25608
+ win.removeEventListener(EV_CLICK, this.listenerDeadClick, true);
25576
25609
  this.listenerDeadClick = null;
25577
25610
  }
25578
25611
 
@@ -25615,10 +25648,183 @@
25615
25648
  return win[TARGETING_GLOBAL_NAME];
25616
25649
  };
25617
25650
 
25651
+ var logger$2 = console_with_prefix('flags');
25652
+
25653
+ var MIXPANEL_FLAGS_DB_NAME = 'mixpanelFlagsDb';
25654
+ var FLAGS_STORE_NAME = 'mixpanelFlags';
25655
+
25656
+ // Keeping these two properties closeby, as adding additional stores to a DB in IndexedDB requires a version increment
25657
+ var FLAGS_VERSION_DATA = { version: 1, storeNames: [FLAGS_STORE_NAME] };
25658
+
25659
+ var PERSISTED_VARIANTS_KEY_PREFIX = 'persisted_variants_for_';
25660
+ var DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
25661
+
25662
+ var VariantLookupPolicy = Object.freeze({
25663
+ NETWORK_ONLY: 'networkOnly',
25664
+ NETWORK_FIRST: 'networkFirst',
25665
+ PERSISTENCE_UNTIL_NETWORK_SUCCESS: 'persistenceUntilNetworkSuccess'
25666
+ });
25667
+
25668
+ var VALID_POLICIES = [
25669
+ VariantLookupPolicy.NETWORK_ONLY,
25670
+ VariantLookupPolicy.NETWORK_FIRST,
25671
+ VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS
25672
+ ];
25673
+
25674
+ /**
25675
+ * Module for handling the storage and retrieval of persisted feature flag variants.
25676
+ */
25677
+ var FeatureFlagPersistence = function(persistenceConfig, token, isGloballyDisabled) {
25678
+ this.idb = new IDBStorageWrapper(MIXPANEL_FLAGS_DB_NAME, FLAGS_STORE_NAME, FLAGS_VERSION_DATA);
25679
+ this.persistenceConfig = persistenceConfig;
25680
+ this.persistedVariantsKey = PERSISTED_VARIANTS_KEY_PREFIX + token;
25681
+ this.isGloballyDisabled = isGloballyDisabled || function() { return false; };
25682
+ };
25683
+
25684
+ FeatureFlagPersistence.prototype.getPolicy = function() {
25685
+ if (this.isGloballyDisabled() || !this._isConfigValid()) {
25686
+ return VariantLookupPolicy.NETWORK_ONLY;
25687
+ }
25688
+ return this.persistenceConfig['variantLookupPolicy'];
25689
+ };
25690
+
25691
+ FeatureFlagPersistence.prototype.getTtlMs = function() {
25692
+ if (!this._isConfigValid()) {
25693
+ return DEFAULT_TTL_MS;
25694
+ }
25695
+ var configuredTtl = this.persistenceConfig['persistenceTtlMs'];
25696
+ return (configuredTtl === undefined || configuredTtl === null) ? DEFAULT_TTL_MS : configuredTtl;
25697
+ };
25698
+
25699
+ FeatureFlagPersistence.prototype._isConfigValid = function() {
25700
+ var config = this.persistenceConfig;
25701
+ if (!config) {
25702
+ return false;
25703
+ }
25704
+
25705
+ if (VALID_POLICIES.indexOf(config['variantLookupPolicy']) === -1) {
25706
+ logger$2.error('Invalid variantLookupPolicy:', config['variantLookupPolicy']);
25707
+ return false;
25708
+ }
25709
+
25710
+ if (config['persistenceTtlMs'] !== undefined &&
25711
+ config['persistenceTtlMs'] !== null &&
25712
+ config['persistenceTtlMs'] <= 0) {
25713
+ logger$2.error('If provided, persistenceTtlMs must be a positive number. Provided value:', config['persistenceTtlMs']);
25714
+ return false;
25715
+ }
25716
+
25717
+ return true;
25718
+ };
25719
+
25720
+ FeatureFlagPersistence.prototype.loadFlagsFromStorage = function(context) {
25721
+ var clearAndReturnNull = _.bind(function() {
25722
+ return this.clear().then(function() { return null; }).catch(function() { return null; });
25723
+ }, this);
25724
+
25725
+ if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
25726
+ return clearAndReturnNull();
25727
+ }
25728
+
25729
+ var ttlMs = this.getTtlMs();
25730
+
25731
+ return this.idb.init().then(_.bind(function() {
25732
+ return this.idb.getItem(this.persistedVariantsKey);
25733
+ }, this)).then(_.bind(function(data) {
25734
+ if (!data) {
25735
+ logger$2.log('No persisted variants found in IndexedDB');
25736
+ return null;
25737
+ }
25738
+
25739
+ if (ttlMs && Date.now() - data['persistedAt'] >= ttlMs) {
25740
+ logger$2.log('Persisted variants are expiring');
25741
+ return null;
25742
+ }
25743
+
25744
+ if (!context || data['distinctId'] !== context['distinct_id']) {
25745
+ logger$2.log('Persisted variants found, but for a different distinct_id so clearing.');
25746
+ return clearAndReturnNull();
25747
+ }
25748
+
25749
+ var persistedFlags = new Map();
25750
+ _.each(data['flagVariants'], function(variantData, key) {
25751
+ persistedFlags.set(key, {
25752
+ 'key': variantData['variant_key'],
25753
+ 'value': variantData['variant_value'],
25754
+ 'experiment_id': variantData['experiment_id'],
25755
+ 'is_experiment_active': variantData['is_experiment_active'],
25756
+ 'is_qa_tester': variantData['is_qa_tester'],
25757
+ 'variant_source': 'persistence',
25758
+ 'persisted_at_in_ms': data['persistedAt'],
25759
+ 'ttl_in_ms': ttlMs
25760
+ });
25761
+ });
25762
+
25763
+ logger$2.log('Loaded', persistedFlags.size, 'variants from IndexedDB for distinct_id', data['distinctId']);
25764
+
25765
+ return {
25766
+ flags: persistedFlags,
25767
+ pendingFirstTimeEvents: data['pendingFirstTimeEvents'] || {},
25768
+ persistedAtMs: data['persistedAt'],
25769
+ ttlMs: ttlMs
25770
+ };
25771
+ }, this)).catch(_.bind(function(error) {
25772
+ logger$2.error('Failed to load persisted variants from IndexedDB, so clearing', error);
25773
+ return clearAndReturnNull();
25774
+ }, this));
25775
+ };
25776
+
25777
+ FeatureFlagPersistence.prototype.save = function(context, flagsMap, pendingFirstTimeEvents) {
25778
+ if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
25779
+ return Promise.resolve();
25780
+ }
25781
+
25782
+ var flagVariants = {};
25783
+ flagsMap.forEach(function(variant, key) {
25784
+ flagVariants[key] = {
25785
+ 'variant_key': variant['key'],
25786
+ 'variant_value': variant['value'],
25787
+ 'experiment_id': variant['experiment_id'],
25788
+ 'is_experiment_active': variant['is_experiment_active'],
25789
+ 'is_qa_tester': variant['is_qa_tester']
25790
+ };
25791
+ });
25792
+
25793
+ var data = {
25794
+ 'persistedAt': Date.now(),
25795
+ 'distinctId': context && context['distinct_id'],
25796
+ 'context': context,
25797
+ 'flagVariants': flagVariants,
25798
+ 'pendingFirstTimeEvents': pendingFirstTimeEvents || {}
25799
+ };
25800
+
25801
+ return this.idb.init().then(_.bind(function() {
25802
+ return this.idb.setItem(this.persistedVariantsKey, data);
25803
+ }, this)).then(function() {
25804
+ logger$2.log('Saved', flagsMap.size, 'variants to IndexedDB for distinct_id', data['distinctId']);
25805
+ }).catch(function(error) {
25806
+ logger$2.error('Failed to persist variants to IndexedDB:', error);
25807
+ });
25808
+ };
25809
+
25810
+ FeatureFlagPersistence.prototype.clear = function() {
25811
+ if (this.isGloballyDisabled()) {
25812
+ return Promise.resolve();
25813
+ }
25814
+ return this.idb.init().then(_.bind(function() {
25815
+ return this.idb.removeItem(this.persistedVariantsKey);
25816
+ }, this)).then(function() {
25817
+ logger$2.log('Cleared persisted variants from IndexedDB');
25818
+ }).catch(function(error) {
25819
+ logger$2.error('Failed to clear persisted variants from IndexedDB:', error);
25820
+ });
25821
+ };
25822
+
25618
25823
  var logger$1 = console_with_prefix('flags');
25619
25824
  var FLAGS_CONFIG_KEY = 'flags';
25620
25825
 
25621
25826
  var CONFIG_CONTEXT = 'context';
25827
+ var CONFIG_PERSISTENCE = 'persistence';
25622
25828
  var CONFIG_DEFAULTS = {};
25623
25829
  CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
25624
25830
 
@@ -25641,6 +25847,13 @@
25641
25847
  return eventKey.split(':')[0];
25642
25848
  };
25643
25849
 
25850
+ var withFallbackSource = function(fallback) {
25851
+ if (_.isObject(fallback)) {
25852
+ return _.extend({}, fallback, {'variant_source': 'fallback'});
25853
+ }
25854
+ return {'value': fallback, 'variant_source': 'fallback'};
25855
+ };
25856
+
25644
25857
  /**
25645
25858
  * FeatureFlagManager: support for Mixpanel's feature flagging product
25646
25859
  * @constructor
@@ -25663,13 +25876,63 @@
25663
25876
  }
25664
25877
 
25665
25878
  this.flags = null;
25666
- this.fetchFlags().catch(function() {
25667
- logger$1.error('Error fetching flags during init');
25668
- });
25669
-
25670
25879
  this.trackedFeatures = new Set();
25671
25880
  this.pendingFirstTimeEvents = {};
25672
25881
  this.activatedFirstTimeEvents = {};
25882
+ this._loadedPersistedAtMs = null;
25883
+ this._loadedTtlMs = null;
25884
+
25885
+ this.persistence = new FeatureFlagPersistence(
25886
+ this.getConfig(CONFIG_PERSISTENCE),
25887
+ this.getMpConfig('token'),
25888
+ _.bind(function() { return this.getMpConfig('disable_persistence'); }, this)
25889
+ );
25890
+
25891
+ this.persistenceLoadedPromise = this.persistence.loadFlagsFromStorage(this._buildContext())
25892
+ .then(_.bind(function(loaded) {
25893
+ if (loaded) {
25894
+ this.flags = loaded.flags;
25895
+ this.pendingFirstTimeEvents = loaded.pendingFirstTimeEvents;
25896
+ this._loadedPersistedAtMs = loaded.persistedAtMs;
25897
+ this._loadedTtlMs = loaded.ttlMs;
25898
+ }
25899
+ }, this));
25900
+
25901
+ return this.persistenceLoadedPromise
25902
+ .then(_.bind(function() {
25903
+ return this.fetchFlags();
25904
+ }, this))
25905
+ .catch(function() {
25906
+ logger$1.error('Error initializing feature flags');
25907
+ });
25908
+ };
25909
+
25910
+ FeatureFlagManager.prototype._buildContext = function() {
25911
+ return _.extend(
25912
+ {'distinct_id': this.getMpProperty('distinct_id'), 'device_id': this.getMpProperty('$device_id')},
25913
+ this.getConfig(CONFIG_CONTEXT)
25914
+ );
25915
+ };
25916
+
25917
+ FeatureFlagManager.prototype.reset = function() {
25918
+ if (!this.persistence) {
25919
+ return Promise.resolve();
25920
+ }
25921
+
25922
+ this.flags = null;
25923
+ this.pendingFirstTimeEvents = {};
25924
+ this.activatedFirstTimeEvents = {};
25925
+ this.trackedFeatures = new Set();
25926
+ this.fetchPromise = null;
25927
+ this._fetchInProgressStartTime = null;
25928
+ this._loadedPersistedAtMs = null;
25929
+ this._loadedTtlMs = null;
25930
+
25931
+ return this.persistence.clear().then(_.bind(function() {
25932
+ return this.fetchFlags();
25933
+ }, this)).catch(function() {
25934
+ logger$1.error('Error during flags reset');
25935
+ });
25673
25936
  };
25674
25937
 
25675
25938
  FeatureFlagManager.prototype.getFullConfig = function() {
@@ -25726,12 +25989,11 @@
25726
25989
  return Promise.resolve();
25727
25990
  }
25728
25991
 
25729
- var distinctId = this.getMpProperty('distinct_id');
25730
- var deviceId = this.getMpProperty('$device_id');
25992
+ var context = this._buildContext();
25993
+ var distinctId = context['distinct_id'];
25731
25994
  var traceparent = generateTraceparent();
25732
25995
  logger$1.log('Fetching flags for distinct ID: ' + distinctId);
25733
25996
 
25734
- var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
25735
25997
  var searchParams = new URLSearchParams();
25736
25998
  searchParams.set('context', JSON.stringify(context));
25737
25999
  searchParams.set('token', this.getMpConfig('token'));
@@ -25781,7 +26043,8 @@
25781
26043
  'value': data['variant_value'],
25782
26044
  'experiment_id': data['experiment_id'],
25783
26045
  'is_experiment_active': data['is_experiment_active'],
25784
- 'is_qa_tester': data['is_qa_tester']
26046
+ 'is_qa_tester': data['is_qa_tester'],
26047
+ 'variant_source': 'network'
25785
26048
  });
25786
26049
  }
25787
26050
  }, this);
@@ -25823,10 +26086,15 @@
25823
26086
  }
25824
26087
 
25825
26088
  this.flags = flags;
26089
+ this.trackedFeatures = new Set();
25826
26090
  this.pendingFirstTimeEvents = pendingFirstTimeEvents;
26091
+ this._loadedPersistedAtMs = null;
26092
+ this._loadedTtlMs = null;
25827
26093
  this._traceparent = traceparent;
25828
26094
 
25829
26095
  this._loadTargetingIfNeeded();
26096
+
26097
+ this.persistence.save(context, this.flags, this.pendingFirstTimeEvents);
25830
26098
  }.bind(this)).catch(function(error) {
25831
26099
  if (this._fetchInProgressStartTime) {
25832
26100
  this.markFetchComplete();
@@ -25986,6 +26254,7 @@
25986
26254
  };
25987
26255
 
25988
26256
  this.flags.set(flagKey, newVariant);
26257
+ this.trackedFeatures.delete(flagKey);
25989
26258
  this.activatedFirstTimeEvents[eventKey] = true;
25990
26259
 
25991
26260
  this.recordFirstTimeEvent(
@@ -25997,8 +26266,8 @@
25997
26266
  };
25998
26267
 
25999
26268
  FeatureFlagManager.prototype.getFirstTimeEventApiRoute = function(flagId) {
26000
- // Construct URL: {api_host}/flags/{flagId}/first-time-events
26001
- return this.getFullApiRoute() + '/' + flagId + '/first-time-events';
26269
+ var base = this.getFullApiRoute().replace(/\/$/, '');
26270
+ return base + '/' + flagId + '/first-time-events';
26002
26271
  };
26003
26272
 
26004
26273
  FeatureFlagManager.prototype.recordFirstTimeEvent = function(flagId, projectId, firstTimeEventHash) {
@@ -26035,35 +26304,106 @@
26035
26304
  };
26036
26305
 
26037
26306
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
26038
- if (!this.fetchPromise) {
26307
+ if (!this.persistenceLoadedPromise) {
26039
26308
  return new Promise(function(resolve) {
26040
26309
  logger$1.critical('Feature Flags not initialized');
26041
- resolve(fallback);
26310
+ resolve(withFallbackSource(fallback));
26042
26311
  });
26043
26312
  }
26044
26313
 
26045
- return this.fetchPromise.then(function() {
26046
- return this.getVariantSync(featureName, fallback);
26047
- }.bind(this)).catch(function(error) {
26048
- logger$1.error(error);
26049
- return fallback;
26050
- });
26314
+ var policy = this.persistence.getPolicy();
26315
+
26316
+ return this.persistenceLoadedPromise.then(_.bind(function() {
26317
+ // Serve from persistence until the network completes a successful fetch. If a non-expired cached value is available, return it without waiting on the in-flight fetch.
26318
+ if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
26319
+ if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
26320
+ return this.getVariantSync(featureName, fallback);
26321
+ }
26322
+ if (!this.fetchPromise) {
26323
+ return withFallbackSource(fallback);
26324
+ }
26325
+ return this.fetchPromise.then(_.bind(function() {
26326
+ return this.getVariantSync(featureName, fallback);
26327
+ }, this)).catch(function(error) {
26328
+ logger$1.error(error);
26329
+ return withFallbackSource(fallback);
26330
+ });
26331
+ }
26332
+
26333
+ var serve = _.bind(function() { return this.getVariantSync(featureName, fallback); }, this);
26334
+ if (!this.fetchPromise) {
26335
+ return withFallbackSource(fallback);
26336
+ }
26337
+ return this.fetchPromise.then(serve).catch(serve);
26338
+ }, this));
26339
+ };
26340
+
26341
+ FeatureFlagManager.prototype._loadedPersistenceIsStale = function() {
26342
+ if (!this._loadedPersistedAtMs || !this._loadedTtlMs) {
26343
+ return false;
26344
+ }
26345
+ return Date.now() - this._loadedPersistedAtMs >= this._loadedTtlMs;
26051
26346
  };
26052
26347
 
26053
26348
  FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
26349
+ if (this._loadedPersistenceIsStale()) {
26350
+ logger$1.log('Loaded persisted variants are past TTL so returning fallback for "' + featureName + '"');
26351
+ return withFallbackSource(fallback);
26352
+ }
26054
26353
  if (!this.areFlagsReady()) {
26055
26354
  logger$1.log('Flags not loaded yet');
26056
- return fallback;
26355
+ return withFallbackSource(fallback);
26057
26356
  }
26058
26357
  var feature = this.flags.get(featureName);
26059
26358
  if (!feature) {
26060
26359
  logger$1.log('No flag found: "' + featureName + '"');
26061
- return fallback;
26360
+ return withFallbackSource(fallback);
26062
26361
  }
26063
26362
  this.trackFeatureCheck(featureName, feature);
26064
26363
  return feature;
26065
26364
  };
26066
26365
 
26366
+ FeatureFlagManager.prototype.getAllVariants = function() {
26367
+ if (!this.persistenceLoadedPromise) {
26368
+ logger$1.critical('Feature Flags not initialized');
26369
+ return Promise.resolve(new Map());
26370
+ }
26371
+
26372
+ var policy = this.persistence.getPolicy();
26373
+
26374
+ return this.persistenceLoadedPromise.then(_.bind(function() {
26375
+ // Serve from persistence until the network completes a successful fetch. If a non-expired cached value is available, return it without waiting on the in-flight fetch.
26376
+ if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
26377
+ if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
26378
+ return this.getAllVariantsSync();
26379
+ }
26380
+ if (!this.fetchPromise) {
26381
+ return new Map();
26382
+ }
26383
+ return this.fetchPromise.then(_.bind(function() {
26384
+ return this.getAllVariantsSync();
26385
+ }, this)).catch(function(error) {
26386
+ logger$1.error(error);
26387
+ return new Map();
26388
+ });
26389
+ }
26390
+
26391
+ var serve = _.bind(this.getAllVariantsSync, this);
26392
+ if (!this.fetchPromise) {
26393
+ return new Map();
26394
+ }
26395
+ return this.fetchPromise.then(serve).catch(serve);
26396
+ }, this));
26397
+ };
26398
+
26399
+ FeatureFlagManager.prototype.getAllVariantsSync = function() {
26400
+ if (this._loadedPersistenceIsStale()) {
26401
+ logger$1.log('Loaded persisted variants are past TTL so returning empty Map');
26402
+ return new Map();
26403
+ }
26404
+ return this.flags || new Map();
26405
+ };
26406
+
26067
26407
  FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
26068
26408
  return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
26069
26409
  return feature['value'];
@@ -26102,6 +26442,10 @@
26102
26442
  return val;
26103
26443
  };
26104
26444
 
26445
+ function isPresent(v) {
26446
+ return v !== undefined && v !== null;
26447
+ }
26448
+
26105
26449
  FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
26106
26450
  if (this.trackedFeatures.has(featureName)) {
26107
26451
  return;
@@ -26112,21 +26456,30 @@
26112
26456
  'Experiment name': featureName,
26113
26457
  'Variant name': feature['key'],
26114
26458
  '$experiment_type': 'feature_flag',
26115
- 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
26116
- 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
26459
+ 'Variant fetch start time': isPresent(this._fetchStartTime) ? new Date(this._fetchStartTime).toISOString() : null,
26460
+ 'Variant fetch complete time': isPresent(this._fetchCompleteTime) ? new Date(this._fetchCompleteTime).toISOString() : null,
26117
26461
  'Variant fetch latency (ms)': this._fetchLatency,
26118
26462
  'Variant fetch traceparent': this._traceparent,
26119
26463
  };
26120
26464
 
26121
- if (feature['experiment_id'] !== 'undefined') {
26465
+ if (isPresent(feature['experiment_id'])) {
26122
26466
  trackingProperties['$experiment_id'] = feature['experiment_id'];
26123
26467
  }
26124
- if (feature['is_experiment_active'] !== 'undefined') {
26468
+ if (isPresent(feature['is_experiment_active'])) {
26125
26469
  trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
26126
26470
  }
26127
- if (feature['is_qa_tester'] !== 'undefined') {
26471
+ if (isPresent(feature['is_qa_tester'])) {
26128
26472
  trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
26129
26473
  }
26474
+ if (isPresent(feature['variant_source'])) {
26475
+ trackingProperties['$variant_source'] = feature['variant_source'];
26476
+ }
26477
+ if (isPresent(feature['persisted_at_in_ms'])) {
26478
+ trackingProperties['$persisted_at_in_ms'] = feature['persisted_at_in_ms'];
26479
+ }
26480
+ if (isPresent(feature['ttl_in_ms'])) {
26481
+ trackingProperties['$ttl_in_ms'] = feature['ttl_in_ms'];
26482
+ }
26130
26483
 
26131
26484
  this.track('$experiment_started', trackingProperties);
26132
26485
  };
@@ -26150,6 +26503,8 @@
26150
26503
  FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
26151
26504
  FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
26152
26505
  FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
26506
+ FeatureFlagManager.prototype['get_all_variants'] = FeatureFlagManager.prototype.getAllVariants;
26507
+ FeatureFlagManager.prototype['get_all_variants_sync'] = FeatureFlagManager.prototype.getAllVariantsSync;
26153
26508
  FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
26154
26509
  FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
26155
26510
  FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
@@ -26202,7 +26557,7 @@
26202
26557
  return PromisePolyfill.resolve(false);
26203
26558
  }
26204
26559
 
26205
- var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
26560
+ var recording_registry_idb = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_REGISTRY_STORE_NAME, RECORDER_VERSION_DATA);
26206
26561
  var tab_id = this.getTabId();
26207
26562
  return recording_registry_idb.init()
26208
26563
  .then(function () {
@@ -28129,6 +28484,7 @@
28129
28484
  'disable_all_events': false,
28130
28485
  'identify_called': false
28131
28486
  };
28487
+ this._remote_settings_strict_disabled = false;
28132
28488
 
28133
28489
  // set up request queueing/batching
28134
28490
  this.request_batchers = {};
@@ -28203,9 +28559,6 @@
28203
28559
  this.flags.init();
28204
28560
  this['flags'] = this.flags;
28205
28561
 
28206
- this.autocapture = new Autocapture(this);
28207
- this.autocapture.init();
28208
-
28209
28562
  this._init_tab_id();
28210
28563
 
28211
28564
  // Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
@@ -28217,6 +28570,9 @@
28217
28570
  } else {
28218
28571
  this.__session_recording_init_promise = this._check_and_start_session_recording();
28219
28572
  }
28573
+
28574
+ this.autocapture = new Autocapture(this);
28575
+ this.autocapture.init();
28220
28576
  };
28221
28577
 
28222
28578
  /**
@@ -28263,9 +28619,19 @@
28263
28619
  return this.recorderManager.checkAndStartSessionRecording(force_start);
28264
28620
  });
28265
28621
 
28266
- MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
28267
- return this.recorderManager.startRecordingOnEvent(event_name, properties);
28268
- };
28622
+ MixpanelLib.prototype._start_recording_on_event = safewrap(function(event_name, properties) {
28623
+ // Wait for recording init to complete before evaluating event triggers.
28624
+ // This ensures recording_event_triggers config is fully loaded when remote settings are used.
28625
+ if (this.__session_recording_init_promise) {
28626
+ this.__session_recording_init_promise.then(_.bind(function() {
28627
+ // In strict mode, skip recording if remote settings failed
28628
+ if (this._remote_settings_strict_disabled) {
28629
+ return;
28630
+ }
28631
+ return this.recorderManager.startRecordingOnEvent(event_name, properties);
28632
+ }, this));
28633
+ }
28634
+ });
28269
28635
 
28270
28636
  MixpanelLib.prototype.start_session_recording = function () {
28271
28637
  return this._check_and_start_session_recording(true);
@@ -28564,6 +28930,7 @@
28564
28930
  var disableRecordingIfStrict = function() {
28565
28931
  if (mode === 'strict') {
28566
28932
  self.set_config({'record_sessions_percent': 0});
28933
+ self._remote_settings_strict_disabled = true;
28567
28934
  }
28568
28935
  };
28569
28936
 
@@ -29189,6 +29556,10 @@
29189
29556
  properties
29190
29557
  );
29191
29558
 
29559
+ if (this.is_recording_heatmap_data()) {
29560
+ event_properties['$captured_for_heatmap'] = true;
29561
+ }
29562
+
29192
29563
  return this.track(event_name, event_properties);
29193
29564
  });
29194
29565
 
@@ -29532,6 +29903,7 @@
29532
29903
  '$device_id': uuid
29533
29904
  }, '');
29534
29905
  this._check_and_start_session_recording();
29906
+ this.flags.reset();
29535
29907
  };
29536
29908
 
29537
29909
  /**