mixpanel-browser 2.77.0 → 2.79.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 (68) hide show
  1. package/.claude/settings.local.json +6 -9
  2. package/.eslintrc.json +12 -0
  3. package/.github/workflows/openfeature-provider-tests.yml +31 -0
  4. package/CHANGELOG.md +11 -0
  5. package/build.sh +2 -2
  6. package/dist/async-modules/{mixpanel-recorder-wIWnMDLA.min.js → mixpanel-recorder-D5HJyV2E.min.js} +2 -2
  7. package/dist/async-modules/mixpanel-recorder-D5HJyV2E.min.js.map +1 -0
  8. package/dist/async-modules/{mixpanel-recorder-DLKbUIEE.js → mixpanel-recorder-P6SEnnPV.js} +57 -33
  9. package/dist/async-modules/mixpanel-targeting-1L9FyetZ.min.js +2 -0
  10. package/dist/async-modules/mixpanel-targeting-1L9FyetZ.min.js.map +1 -0
  11. package/dist/async-modules/{mixpanel-targeting-CmVvUyFM.js → mixpanel-targeting-BBMVbgJF.js} +24 -13
  12. package/dist/mixpanel-core.cjs.d.ts +46 -1
  13. package/dist/mixpanel-core.cjs.js +671 -272
  14. package/dist/mixpanel-recorder.js +57 -33
  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 +46 -1
  21. package/dist/mixpanel-with-async-modules.cjs.js +673 -274
  22. package/dist/mixpanel-with-async-recorder.cjs.d.ts +46 -1
  23. package/dist/mixpanel-with-async-recorder.cjs.js +673 -274
  24. package/dist/mixpanel-with-recorder.d.ts +46 -1
  25. package/dist/mixpanel-with-recorder.js +596 -197
  26. package/dist/mixpanel-with-recorder.min.d.ts +46 -1
  27. package/dist/mixpanel-with-recorder.min.js +1 -1
  28. package/dist/mixpanel.amd.d.ts +46 -1
  29. package/dist/mixpanel.amd.js +596 -197
  30. package/dist/mixpanel.cjs.d.ts +46 -1
  31. package/dist/mixpanel.cjs.js +596 -197
  32. package/dist/mixpanel.globals.js +673 -274
  33. package/dist/mixpanel.min.js +200 -189
  34. package/dist/mixpanel.module.d.ts +46 -1
  35. package/dist/mixpanel.module.js +596 -197
  36. package/dist/mixpanel.umd.d.ts +46 -1
  37. package/dist/mixpanel.umd.js +596 -197
  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 +7 -2
  49. package/src/config.js +1 -1
  50. package/src/flags/CLAUDE.md +24 -0
  51. package/src/flags/flags-persistence.js +176 -0
  52. package/src/flags/index.js +278 -98
  53. package/src/index.d.ts +46 -1
  54. package/src/mixpanel-core.js +27 -8
  55. package/src/recorder/idb-config.js +16 -0
  56. package/src/recorder/recording-registry.js +7 -2
  57. package/src/recorder/session-recording.js +9 -4
  58. package/src/recorder-manager.js +7 -2
  59. package/src/request-queue.js +1 -2
  60. package/src/shared-lock.js +2 -3
  61. package/src/storage/indexed-db.js +16 -15
  62. package/src/storage/local-storage.js +5 -3
  63. package/src/utils.js +25 -12
  64. package/testServer.js +2 -0
  65. package/tsconfig.base.json +9 -0
  66. package/dist/async-modules/mixpanel-recorder-wIWnMDLA.min.js.map +0 -1
  67. package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js +0 -2
  68. package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js.map +0 -1
@@ -25,7 +25,7 @@ if (typeof(window) === 'undefined') {
25
25
 
26
26
  var Config = {
27
27
  DEBUG: false,
28
- LIB_VERSION: '2.77.0'
28
+ LIB_VERSION: '2.79.0'
29
29
  };
30
30
 
31
31
  // Window global names for async modules
@@ -19123,6 +19123,7 @@ var log_func_with_prefix = function(func, prefix) {
19123
19123
  var console_with_prefix = function(prefix) {
19124
19124
  return {
19125
19125
  log: log_func_with_prefix(console$1.log, prefix),
19126
+ warn: log_func_with_prefix(console$1.warn, prefix),
19126
19127
  error: log_func_with_prefix(console$1.error, prefix),
19127
19128
  critical: log_func_with_prefix(console$1.critical, prefix)
19128
19129
  };
@@ -20069,7 +20070,8 @@ var localStorageSupported = function(storage, forceCheck) {
20069
20070
  if (_localStorageSupported !== null && !forceCheck) {
20070
20071
  return _localStorageSupported;
20071
20072
  }
20072
- return _localStorageSupported = _testStorageSupported(storage || win.localStorage);
20073
+
20074
+ return _localStorageSupported = _testStorageSupported(storage);
20073
20075
  };
20074
20076
 
20075
20077
  var _sessionStorageSupported = null;
@@ -20077,7 +20079,8 @@ var sessionStorageSupported = function(storage, forceCheck) {
20077
20079
  if (_sessionStorageSupported !== null && !forceCheck) {
20078
20080
  return _sessionStorageSupported;
20079
20081
  }
20080
- return _sessionStorageSupported = _testStorageSupported(storage || win.sessionStorage);
20082
+
20083
+ return _sessionStorageSupported = _testStorageSupported(storage);
20081
20084
  };
20082
20085
 
20083
20086
  function _storageWrapper(storage, name, is_supported_fn) {
@@ -20127,17 +20130,26 @@ function _storageWrapper(storage, name, is_supported_fn) {
20127
20130
  };
20128
20131
  }
20129
20132
 
20130
- // Safari errors out accessing localStorage/sessionStorage when cookies are disabled,
20131
- // so create dummy storage wrappers that silently fail as a fallback.
20132
- var windowLocalStorage = null, windowSessionStorage = null;
20133
- try {
20134
- windowLocalStorage = win.localStorage;
20135
- windowSessionStorage = win.sessionStorage;
20136
- // eslint-disable-next-line no-empty
20137
- } catch (_err) {}
20133
+ // Safari and other browsers may error out accessing localStorage/sessionStorage
20134
+ // when cookies are disabled, so wrap access in a try-catch.
20135
+ var getLocalStorage = function() {
20136
+ try {
20137
+ return win.localStorage; // eslint-disable-line no-restricted-properties
20138
+ } catch (_err) {
20139
+ return null;
20140
+ }
20141
+ };
20138
20142
 
20139
- _.localStorage = _storageWrapper(windowLocalStorage, 'localStorage', localStorageSupported);
20140
- _.sessionStorage = _storageWrapper(windowSessionStorage, 'sessionStorage', sessionStorageSupported);
20143
+ var getSessionStorage = function() {
20144
+ try {
20145
+ return win.sessionStorage; // eslint-disable-line no-restricted-properties
20146
+ } catch (_err) {
20147
+ return null;
20148
+ }
20149
+ };
20150
+
20151
+ _.localStorage = _storageWrapper(getLocalStorage(), 'localStorage', localStorageSupported);
20152
+ _.sessionStorage = _storageWrapper(getSessionStorage(), 'sessionStorage', sessionStorageSupported);
20141
20153
 
20142
20154
  _.register_event = (function() {
20143
20155
  // written by Dean Edwards, 2005
@@ -20806,29 +20818,26 @@ _['JSONEncode'] = _.JSONEncode;
20806
20818
  _['toArray'] = _.toArray;
20807
20819
  _['NPO'] = NpoPromise;
20808
20820
 
20809
- var MIXPANEL_DB_NAME = 'mixpanelBrowserDb';
20810
-
20811
- var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
20812
- var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
20813
-
20814
- // note: increment the version number when adding new object stores
20815
- var DB_VERSION = 1;
20816
- var OBJECT_STORES = [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME];
20817
-
20818
20821
  /**
20819
20822
  * @type {import('./wrapper').StorageWrapper}
20820
20823
  */
20821
- var IDBStorageWrapper = function (storeName) {
20824
+ var IDBStorageWrapper = function (dbName, storeName, versionData) {
20825
+ this.dbName = dbName;
20826
+ this.storeName = storeName;
20827
+ this.version = versionData.version;
20828
+ this.storeNamesInDb = versionData.storeNames;
20822
20829
  /**
20823
20830
  * @type {Promise<IDBDatabase>|null}
20824
20831
  */
20825
20832
  this.dbPromise = null;
20826
- this.storeName = storeName;
20827
20833
  };
20828
20834
 
20829
20835
  IDBStorageWrapper.prototype._openDb = function () {
20836
+ var dbName = this.dbName;
20837
+ var version = this.version;
20838
+ var storeNamesInDb = this.storeNamesInDb;
20830
20839
  return new PromisePolyfill(function (resolve, reject) {
20831
- var openRequest = win.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
20840
+ var openRequest = win.indexedDB.open(dbName, version);
20832
20841
  openRequest['onerror'] = function () {
20833
20842
  reject(openRequest.error);
20834
20843
  };
@@ -20840,8 +20849,10 @@ IDBStorageWrapper.prototype._openDb = function () {
20840
20849
  openRequest['onupgradeneeded'] = function (ev) {
20841
20850
  var db = ev.target.result;
20842
20851
 
20843
- OBJECT_STORES.forEach(function (storeName) {
20844
- db.createObjectStore(storeName);
20852
+ storeNamesInDb.forEach(function (storeName) {
20853
+ if (!db.objectStoreNames.contains(storeName)) {
20854
+ db.createObjectStore(storeName);
20855
+ }
20845
20856
  });
20846
20857
  };
20847
20858
  });
@@ -20933,6 +20944,16 @@ IDBStorageWrapper.prototype.getAll = function () {
20933
20944
  });
20934
20945
  };
20935
20946
 
20947
+ var MIXPANEL_BROWSER_DB_NAME = 'mixpanelBrowserDb';
20948
+ var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
20949
+ var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
20950
+
20951
+ // Keeping these two properties closeby, as adding additional stores to a DB in IndexedDB requires a version increment
20952
+ var RECORDER_VERSION_DATA = {
20953
+ version: 1,
20954
+ storeNames: [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME]
20955
+ };
20956
+
20936
20957
  /**
20937
20958
  * GDPR utils
20938
20959
  *
@@ -21233,7 +21254,7 @@ function _addOptOutCheck(method, getConfigValue) {
21233
21254
  };
21234
21255
  }
21235
21256
 
21236
- var logger$8 = console_with_prefix('lock');
21257
+ var logger$9 = console_with_prefix('lock');
21237
21258
 
21238
21259
  /**
21239
21260
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -21259,7 +21280,7 @@ var SharedLock = function(key, options) {
21259
21280
  options = options || {};
21260
21281
 
21261
21282
  this.storageKey = key;
21262
- this.storage = options.storage || win.localStorage;
21283
+ this.storage = options.storage || getLocalStorage();
21263
21284
  this.pollIntervalMS = options.pollIntervalMS || 100;
21264
21285
  this.timeoutMS = options.timeoutMS || 2000;
21265
21286
 
@@ -21285,7 +21306,7 @@ SharedLock.prototype.withLock = function(lockedCB, pid) {
21285
21306
 
21286
21307
  var delay = function(cb) {
21287
21308
  if (new Date().getTime() - startTime > timeoutMS) {
21288
- logger$8.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21309
+ logger$9.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21289
21310
  storage.removeItem(keyZ);
21290
21311
  storage.removeItem(keyY);
21291
21312
  loop();
@@ -21387,10 +21408,13 @@ SharedLock.prototype.withLock = function(lockedCB, pid) {
21387
21408
  * @type {import('./wrapper').StorageWrapper}
21388
21409
  */
21389
21410
  var LocalStorageWrapper = function (storageOverride) {
21390
- this.storage = storageOverride || win.localStorage;
21411
+ this.storage = storageOverride || getLocalStorage();
21391
21412
  };
21392
21413
 
21393
21414
  LocalStorageWrapper.prototype.init = function () {
21415
+ if (!this.storage) {
21416
+ return PromisePolyfill.reject(new Error('localStorage is not available'));
21417
+ }
21394
21418
  return PromisePolyfill.resolve();
21395
21419
  };
21396
21420
 
@@ -21432,7 +21456,7 @@ LocalStorageWrapper.prototype.removeItem = function (key) {
21432
21456
  }, this));
21433
21457
  };
21434
21458
 
21435
- var logger$7 = console_with_prefix('batch');
21459
+ var logger$8 = console_with_prefix('batch');
21436
21460
 
21437
21461
  /**
21438
21462
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -21457,11 +21481,11 @@ var RequestQueue = function (storageKey, options) {
21457
21481
  if (this.usePersistence) {
21458
21482
  this.queueStorage = options.queueStorage || new LocalStorageWrapper();
21459
21483
  this.lock = new SharedLock(storageKey, {
21460
- storage: options.sharedLockStorage || win.localStorage,
21484
+ storage: options.sharedLockStorage,
21461
21485
  timeoutMS: options.sharedLockTimeoutMS,
21462
21486
  });
21463
21487
  }
21464
- this.reportError = options.errorReporter || _.bind(logger$7.error, logger$7);
21488
+ this.reportError = options.errorReporter || _.bind(logger$8.error, logger$8);
21465
21489
 
21466
21490
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
21467
21491
 
@@ -21794,7 +21818,7 @@ RequestQueue.prototype.clear = function () {
21794
21818
  // maximum interval between request retries after exponential backoff
21795
21819
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
21796
21820
 
21797
- var logger$6 = console_with_prefix('batch');
21821
+ var logger$7 = console_with_prefix('batch');
21798
21822
 
21799
21823
  /**
21800
21824
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -21922,7 +21946,7 @@ RequestBatcher.prototype.sendRequestPromise = function(data, options) {
21922
21946
  */
21923
21947
  RequestBatcher.prototype.flush = function(options) {
21924
21948
  if (this.requestInProgress) {
21925
- logger$6.log('Flush: Request already in progress');
21949
+ logger$7.log('Flush: Request already in progress');
21926
21950
  return PromisePolyfill.resolve();
21927
21951
  }
21928
21952
 
@@ -22099,7 +22123,7 @@ RequestBatcher.prototype.flush = function(options) {
22099
22123
  if (options.unloading) {
22100
22124
  requestOptions.transport = 'sendBeacon';
22101
22125
  }
22102
- logger$6.log('MIXPANEL REQUEST:', dataForRequest);
22126
+ logger$7.log('MIXPANEL REQUEST:', dataForRequest);
22103
22127
  return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
22104
22128
  }, this))
22105
22129
  .catch(_.bind(function(err) {
@@ -22112,7 +22136,7 @@ RequestBatcher.prototype.flush = function(options) {
22112
22136
  * Log error to global logger and optional user-defined logger.
22113
22137
  */
22114
22138
  RequestBatcher.prototype.reportError = function(msg, err) {
22115
- logger$6.error.apply(logger$6.error, arguments);
22139
+ logger$7.error.apply(logger$7.error, arguments);
22116
22140
  if (this.errorReporter) {
22117
22141
  try {
22118
22142
  if (!(err instanceof Error)) {
@@ -22120,7 +22144,7 @@ RequestBatcher.prototype.reportError = function(msg, err) {
22120
22144
  }
22121
22145
  this.errorReporter(msg, err);
22122
22146
  } catch(err) {
22123
- logger$6.error(err);
22147
+ logger$7.error(err);
22124
22148
  }
22125
22149
  }
22126
22150
  };
@@ -22265,7 +22289,7 @@ var EVENT_HANDLER_ATTRIBUTES = [
22265
22289
 
22266
22290
  var MAX_DEPTH = 5;
22267
22291
 
22268
- var logger$5 = console_with_prefix('autocapture');
22292
+ var logger$6 = console_with_prefix('autocapture');
22269
22293
 
22270
22294
 
22271
22295
  function getClasses(el) {
@@ -22529,7 +22553,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
22529
22553
  return false;
22530
22554
  }
22531
22555
  } catch (err) {
22532
- logger$5.critical('Error while checking element in allowElementCallback', err);
22556
+ logger$6.critical('Error while checking element in allowElementCallback', err);
22533
22557
  return false;
22534
22558
  }
22535
22559
  }
@@ -22546,7 +22570,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
22546
22570
  return true;
22547
22571
  }
22548
22572
  } catch (err) {
22549
- logger$5.critical('Error while checking selector: ' + sel, err);
22573
+ logger$6.critical('Error while checking selector: ' + sel, err);
22550
22574
  }
22551
22575
  }
22552
22576
  return false;
@@ -22561,7 +22585,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
22561
22585
  return true;
22562
22586
  }
22563
22587
  } catch (err) {
22564
- logger$5.critical('Error while checking element in blockElementCallback', err);
22588
+ logger$6.critical('Error while checking element in blockElementCallback', err);
22565
22589
  return true;
22566
22590
  }
22567
22591
  }
@@ -22575,7 +22599,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
22575
22599
  return true;
22576
22600
  }
22577
22601
  } catch (err) {
22578
- logger$5.critical('Error while checking selector: ' + sel, err);
22602
+ logger$6.critical('Error while checking selector: ' + sel, err);
22579
22603
  }
22580
22604
  }
22581
22605
  }
@@ -23131,7 +23155,7 @@ function shouldMaskText(element, privacyConfig) {
23131
23155
  *
23132
23156
  */
23133
23157
 
23134
- var logger$4 = console_with_prefix('network-plugin');
23158
+ var logger$5 = console_with_prefix('network-plugin');
23135
23159
 
23136
23160
  /**
23137
23161
  * Get the time origin for converting performance timestamps to absolute timestamps.
@@ -23283,7 +23307,7 @@ function truncateBody(str) {
23283
23307
  return str;
23284
23308
  }
23285
23309
  if (str.length > MAX_BODY_SIZE) {
23286
- logger$4.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
23310
+ logger$5.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
23287
23311
  return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
23288
23312
  }
23289
23313
  return str;
@@ -23297,7 +23321,7 @@ function truncateBody(str) {
23297
23321
  */
23298
23322
  function initPerformanceObserver(cb, win, options) {
23299
23323
  if (!win.PerformanceObserver) {
23300
- logger$4.error('PerformanceObserver not supported');
23324
+ logger$5.error('PerformanceObserver not supported');
23301
23325
  return function() {
23302
23326
  //
23303
23327
  };
@@ -23450,7 +23474,7 @@ function getRequestPerformanceEntry(win, initiatorType, url, after, before, atte
23450
23474
  attempt = 0;
23451
23475
  }
23452
23476
  if (attempt > 10) {
23453
- logger$4.error('Cannot find performance entry');
23477
+ logger$5.error('Cannot find performance entry');
23454
23478
  return Promise.resolve(null);
23455
23479
  }
23456
23480
  var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
@@ -23571,7 +23595,7 @@ function initXhrObserver(cb, win, options) {
23571
23595
  )
23572
23596
  .then(function(entry) {
23573
23597
  if (!entry) {
23574
- logger$4.error('Failed to get performance entry for XHR request to ' + req.url);
23598
+ logger$5.error('Failed to get performance entry for XHR request to ' + req.url);
23575
23599
  return;
23576
23600
  }
23577
23601
  /** @type {NetworkRequest} */
@@ -23591,7 +23615,7 @@ function initXhrObserver(cb, win, options) {
23591
23615
  cb({ requests: [request] });
23592
23616
  })
23593
23617
  .catch(function(e) {
23594
- logger$4.error('Error recording XHR request to ' + req.url + ': ' + String(e));
23618
+ logger$5.error('Error recording XHR request to ' + req.url + ': ' + String(e));
23595
23619
  });
23596
23620
  });
23597
23621
 
@@ -23683,7 +23707,7 @@ function initFetchObserver(cb, win, options) {
23683
23707
  })
23684
23708
  .then(function(entry) {
23685
23709
  if (!entry) {
23686
- logger$4.error('Failed to get performance entry for fetch request to ' + req.url);
23710
+ logger$5.error('Failed to get performance entry for fetch request to ' + req.url);
23687
23711
  return;
23688
23712
  }
23689
23713
  /** @type {NetworkRequest} */
@@ -23703,7 +23727,7 @@ function initFetchObserver(cb, win, options) {
23703
23727
  cb({ requests: [request] });
23704
23728
  })
23705
23729
  .catch(function (e) {
23706
- logger$4.error('Error recording fetch request to ' + req.url + ': ' + String(e));
23730
+ logger$5.error('Error recording fetch request to ' + req.url + ': ' + String(e));
23707
23731
  });
23708
23732
 
23709
23733
  return originalFetchPromise;
@@ -23776,7 +23800,7 @@ var getRecordNetworkPlugin = function(options) {
23776
23800
  */
23777
23801
 
23778
23802
 
23779
- var logger$3 = console_with_prefix('recorder');
23803
+ var logger$4 = console_with_prefix('recorder');
23780
23804
  var CompressionStream = win['CompressionStream'];
23781
23805
 
23782
23806
  var RECORDER_BATCHER_LIB_CONFIG = {
@@ -23873,11 +23897,11 @@ var SessionRecording = function(options) {
23873
23897
 
23874
23898
  // disable persistence if localStorage is not supported
23875
23899
  // request-queue will automatically disable persistence if indexedDB fails to initialize
23876
- var usePersistence = localStorageSupported(options.sharedLockStorage, true) && !this.getConfig('disable_persistence');
23900
+ var usePersistence = localStorageSupported(options.sharedLockStorage || getLocalStorage(), true) && !this.getConfig('disable_persistence');
23877
23901
 
23878
23902
  // each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
23879
23903
  this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
23880
- this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
23904
+ this.queueStorage = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_EVENTS_STORE_NAME, RECORDER_VERSION_DATA);
23881
23905
  this.batcher = new RequestBatcher(this.batcherKey, {
23882
23906
  errorReporter: this.reportError.bind(this),
23883
23907
  flushOnlyOnInterval: true,
@@ -23956,14 +23980,14 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
23956
23980
  }
23957
23981
 
23958
23982
  if (this._stopRecording !== null) {
23959
- logger$3.log('Recording already in progress, skipping startRecording.');
23983
+ logger$4.log('Recording already in progress, skipping startRecording.');
23960
23984
  return;
23961
23985
  }
23962
23986
 
23963
23987
  this.recordMaxMs = this.getConfig('record_max_ms');
23964
23988
  if (this.recordMaxMs > MAX_RECORDING_MS) {
23965
23989
  this.recordMaxMs = MAX_RECORDING_MS;
23966
- logger$3.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
23990
+ logger$4.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
23967
23991
  }
23968
23992
 
23969
23993
  if (!this.maxExpires) {
@@ -24027,7 +24051,7 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
24027
24051
  );
24028
24052
  }
24029
24053
 
24030
- var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$3);
24054
+ var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$4);
24031
24055
 
24032
24056
  try {
24033
24057
  this._stopRecording = this._rrwebRecord({
@@ -24289,14 +24313,14 @@ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
24289
24313
 
24290
24314
 
24291
24315
  SessionRecording.prototype.reportError = function(msg, err) {
24292
- logger$3.error.apply(logger$3.error, arguments);
24316
+ logger$4.error.apply(logger$4.error, arguments);
24293
24317
  try {
24294
24318
  if (!err && !(msg instanceof Error)) {
24295
24319
  msg = new Error(msg);
24296
24320
  }
24297
24321
  this.getConfig('error_reporter')(msg, err);
24298
24322
  } catch(err) {
24299
- logger$3.error(err);
24323
+ logger$4.error(err);
24300
24324
  }
24301
24325
  };
24302
24326
 
@@ -24325,7 +24349,7 @@ SessionRecording.prototype._getRecordMinMs = function() {
24325
24349
  var configValue = this.getConfig('record_min_ms');
24326
24350
 
24327
24351
  if (configValue > MAX_VALUE_FOR_MIN_RECORDING_MS) {
24328
- logger$3.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
24352
+ logger$4.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
24329
24353
  return MAX_VALUE_FOR_MIN_RECORDING_MS;
24330
24354
  }
24331
24355
 
@@ -24367,7 +24391,7 @@ SessionRecording.prototype._getMaskFn = function(shouldMaskFn, privacyConfig) {
24367
24391
  */
24368
24392
  var RecordingRegistry = function (options) {
24369
24393
  /** @type {IDBStorageWrapper} */
24370
- this.idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
24394
+ this.idb = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_REGISTRY_STORE_NAME, RECORDER_VERSION_DATA);
24371
24395
  this.errorReporter = options.errorReporter;
24372
24396
  this.mixpanelInstance = options.mixpanelInstance;
24373
24397
  this.sharedLockStorage = options.sharedLockStorage;
@@ -24488,7 +24512,7 @@ RecordingRegistry.prototype.flushInactiveRecordings = function () {
24488
24512
  .catch(this.handleError.bind(this));
24489
24513
  };
24490
24514
 
24491
- var logger$2 = console_with_prefix('recorder');
24515
+ var logger$3 = console_with_prefix('recorder');
24492
24516
 
24493
24517
  /**
24494
24518
  * Recorder API: bundles rrweb and and exposes methods to start and stop recordings.
@@ -24504,7 +24528,7 @@ var MixpanelRecorder = function(mixpanelInstance, rrwebRecord, sharedLockStorage
24504
24528
  */
24505
24529
  this.recordingRegistry = new RecordingRegistry({
24506
24530
  mixpanelInstance: this.mixpanelInstance,
24507
- errorReporter: logger$2.error,
24531
+ errorReporter: logger$3.error,
24508
24532
  sharedLockStorage: sharedLockStorage
24509
24533
  });
24510
24534
  this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
@@ -24516,17 +24540,17 @@ var MixpanelRecorder = function(mixpanelInstance, rrwebRecord, sharedLockStorage
24516
24540
  MixpanelRecorder.prototype.startRecording = function(options) {
24517
24541
  options = options || {};
24518
24542
  if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
24519
- logger$2.log('Recording already in progress, skipping startRecording.');
24543
+ logger$3.log('Recording already in progress, skipping startRecording.');
24520
24544
  return;
24521
24545
  }
24522
24546
 
24523
24547
  var onIdleTimeout = function () {
24524
- logger$2.log('Idle timeout reached, restarting recording.');
24548
+ logger$3.log('Idle timeout reached, restarting recording.');
24525
24549
  this.resetRecording();
24526
24550
  }.bind(this);
24527
24551
 
24528
24552
  var onMaxLengthReached = function () {
24529
- logger$2.log('Max recording length reached, stopping recording.');
24553
+ logger$3.log('Max recording length reached, stopping recording.');
24530
24554
  this.resetRecording();
24531
24555
  }.bind(this);
24532
24556
 
@@ -24596,7 +24620,7 @@ MixpanelRecorder.prototype.resumeRecording = function (startNewIfInactive) {
24596
24620
  } else if (startNewIfInactive) {
24597
24621
  return this.startRecording({shouldStopBatcher: false});
24598
24622
  } else {
24599
- logger$2.log('No resumable recording found.');
24623
+ logger$3.log('No resumable recording found.');
24600
24624
  return null;
24601
24625
  }
24602
24626
  }.bind(this));
@@ -25261,7 +25285,7 @@ ShadowDOMObserver.prototype.observeShadowRoot = function(shadowRoot) {
25261
25285
  observer.observe(shadowRoot, this.observerConfig);
25262
25286
  this.shadowObservers.push(observer);
25263
25287
  } catch (e) {
25264
- logger$5.critical('Error while observing shadow root', e);
25288
+ logger$6.critical('Error while observing shadow root', e);
25265
25289
  }
25266
25290
  };
25267
25291
 
@@ -25272,7 +25296,7 @@ ShadowDOMObserver.prototype.start = function() {
25272
25296
  }
25273
25297
 
25274
25298
  if (!weakSetSupported()) {
25275
- logger$5.critical('Shadow DOM observation unavailable: WeakSet not supported');
25299
+ logger$6.critical('Shadow DOM observation unavailable: WeakSet not supported');
25276
25300
  return;
25277
25301
  }
25278
25302
 
@@ -25288,7 +25312,7 @@ ShadowDOMObserver.prototype.stop = function() {
25288
25312
  try {
25289
25313
  this.shadowObservers[i].disconnect();
25290
25314
  } catch (e) {
25291
- logger$5.critical('Error while disconnecting shadow DOM observer', e);
25315
+ logger$6.critical('Error while disconnecting shadow DOM observer', e);
25292
25316
  }
25293
25317
  }
25294
25318
  this.shadowObservers = [];
@@ -25476,7 +25500,7 @@ DeadClickTracker.prototype.startTracking = function() {
25476
25500
 
25477
25501
  this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
25478
25502
  } catch (e) {
25479
- logger$5.critical('Error while setting up mutation observer', e);
25503
+ logger$6.critical('Error while setting up mutation observer', e);
25480
25504
  }
25481
25505
  }
25482
25506
 
@@ -25491,7 +25515,7 @@ DeadClickTracker.prototype.startTracking = function() {
25491
25515
  );
25492
25516
  this.shadowDOMObserver.start();
25493
25517
  } catch (e) {
25494
- logger$5.critical('Error while setting up shadow DOM observer', e);
25518
+ logger$6.critical('Error while setting up shadow DOM observer', e);
25495
25519
  this.shadowDOMObserver = null;
25496
25520
  }
25497
25521
  }
@@ -25518,7 +25542,7 @@ DeadClickTracker.prototype.stopTracking = function() {
25518
25542
  try {
25519
25543
  listener.target.removeEventListener(listener.event, listener.handler, listener.options);
25520
25544
  } catch (e) {
25521
- logger$5.critical('Error while removing event listener', e);
25545
+ logger$6.critical('Error while removing event listener', e);
25522
25546
  }
25523
25547
  }
25524
25548
  this.eventListeners = [];
@@ -25527,7 +25551,7 @@ DeadClickTracker.prototype.stopTracking = function() {
25527
25551
  try {
25528
25552
  this.mutationObserver.disconnect();
25529
25553
  } catch (e) {
25530
- logger$5.critical('Error while disconnecting mutation observer', e);
25554
+ logger$6.critical('Error while disconnecting mutation observer', e);
25531
25555
  }
25532
25556
  this.mutationObserver = null;
25533
25557
  }
@@ -25536,7 +25560,7 @@ DeadClickTracker.prototype.stopTracking = function() {
25536
25560
  try {
25537
25561
  this.shadowDOMObserver.stop();
25538
25562
  } catch (e) {
25539
- logger$5.critical('Error while stopping shadow DOM observer', e);
25563
+ logger$6.critical('Error while stopping shadow DOM observer', e);
25540
25564
  }
25541
25565
  this.shadowDOMObserver = null;
25542
25566
  }
@@ -25614,7 +25638,7 @@ var Autocapture = function(mp) {
25614
25638
 
25615
25639
  Autocapture.prototype.init = function() {
25616
25640
  if (!minDOMApisSupported()) {
25617
- logger$5.critical('Autocapture unavailable: missing required DOM APIs');
25641
+ logger$6.critical('Autocapture unavailable: missing required DOM APIs');
25618
25642
  return;
25619
25643
  }
25620
25644
  this.initPageListeners();
@@ -25654,7 +25678,7 @@ Autocapture.prototype.currentUrlBlocked = function() {
25654
25678
  try {
25655
25679
  return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
25656
25680
  } catch (err) {
25657
- logger$5.critical('Error while checking block URL regexes: ', err);
25681
+ logger$6.critical('Error while checking block URL regexes: ', err);
25658
25682
  return true;
25659
25683
  }
25660
25684
  }
@@ -25667,7 +25691,7 @@ Autocapture.prototype.currentUrlBlocked = function() {
25667
25691
  try {
25668
25692
  return urlMatchesRegexList(currentUrl, blockUrlRegexes);
25669
25693
  } catch (err) {
25670
- logger$5.critical('Error while checking block URL regexes: ', err);
25694
+ logger$6.critical('Error while checking block URL regexes: ', err);
25671
25695
  return true;
25672
25696
  }
25673
25697
  };
@@ -25805,7 +25829,7 @@ Autocapture.prototype._initScrollDepthTracking = function() {
25805
25829
  return;
25806
25830
  }
25807
25831
 
25808
- logger$5.log('Initializing scroll depth tracking');
25832
+ logger$6.log('Initializing scroll depth tracking');
25809
25833
 
25810
25834
  this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
25811
25835
 
@@ -25831,7 +25855,7 @@ Autocapture.prototype.initClickTracking = function() {
25831
25855
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
25832
25856
  return;
25833
25857
  }
25834
- logger$5.log('Initializing click tracking');
25858
+ logger$6.log('Initializing click tracking');
25835
25859
 
25836
25860
  this.listenerClick = function(ev) {
25837
25861
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
@@ -25850,7 +25874,7 @@ Autocapture.prototype.initDeadClickTracking = function() {
25850
25874
  return;
25851
25875
  }
25852
25876
 
25853
- logger$5.log('Initializing dead click tracking');
25877
+ logger$6.log('Initializing dead click tracking');
25854
25878
  if (!this._deadClickTracker) {
25855
25879
  this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
25856
25880
  this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
@@ -25884,7 +25908,7 @@ Autocapture.prototype.initInputTracking = function() {
25884
25908
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
25885
25909
  return;
25886
25910
  }
25887
- logger$5.log('Initializing input tracking');
25911
+ logger$6.log('Initializing input tracking');
25888
25912
 
25889
25913
  this.listenerChange = function(ev) {
25890
25914
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
@@ -25898,14 +25922,15 @@ Autocapture.prototype.initInputTracking = function() {
25898
25922
  Autocapture.prototype.initPageviewTracking = function() {
25899
25923
  win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
25900
25924
 
25901
- if (!this.pageviewTrackingConfig()) {
25925
+ if (!this.pageviewTrackingConfig() && !this.mp.get_config('record_heatmap_data')) {
25902
25926
  return;
25903
25927
  }
25904
- logger$5.log('Initializing pageview tracking');
25928
+ logger$6.log('Initializing pageview tracking');
25905
25929
 
25906
25930
  var previousTrackedUrl = '';
25907
25931
  var tracked = false;
25908
- if (!this.currentUrlBlocked()) {
25932
+ // Track initial pageview if pageview tracking enabled OR heatmap recording is active
25933
+ if ((this.pageviewTrackingConfig() || this.mp.is_recording_heatmap_data()) && !this.currentUrlBlocked()) {
25909
25934
  tracked = this.mp.track_pageview(DEFAULT_PROPS);
25910
25935
  }
25911
25936
  if (tracked) {
@@ -25921,6 +25946,10 @@ Autocapture.prototype.initPageviewTracking = function() {
25921
25946
  var shouldTrack = false;
25922
25947
  var didPathChange = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
25923
25948
  var trackPageviewOption = this.pageviewTrackingConfig();
25949
+ if (!trackPageviewOption && this.mp.is_recording_heatmap_data()) {
25950
+ trackPageviewOption = PAGEVIEW_OPTION_FULL_URL;
25951
+ }
25952
+
25924
25953
  if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
25925
25954
  shouldTrack = currentUrl !== previousTrackedUrl;
25926
25955
  } else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
@@ -25936,7 +25965,7 @@ Autocapture.prototype.initPageviewTracking = function() {
25936
25965
  }
25937
25966
  if (didPathChange) {
25938
25967
  this.lastScrollCheckpoint = 0;
25939
- logger$5.log('Path change: re-initializing scroll depth checkpoints');
25968
+ logger$6.log('Path change: re-initializing scroll depth checkpoints');
25940
25969
  }
25941
25970
  }
25942
25971
  }.bind(this));
@@ -25951,7 +25980,7 @@ Autocapture.prototype.initRageClickTracking = function() {
25951
25980
  return;
25952
25981
  }
25953
25982
 
25954
- logger$5.log('Initializing rage click tracking');
25983
+ logger$6.log('Initializing rage click tracking');
25955
25984
  if (!this._rageClickTracker) {
25956
25985
  this._rageClickTracker = new RageClickTracker();
25957
25986
  }
@@ -25981,7 +26010,7 @@ Autocapture.prototype.initScrollTracking = function() {
25981
26010
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
25982
26011
  return;
25983
26012
  }
25984
- logger$5.log('Initializing scroll tracking');
26013
+ logger$6.log('Initializing scroll tracking');
25985
26014
  this.lastScrollCheckpoint = 0;
25986
26015
 
25987
26016
  var scrollTrackFunction = function() {
@@ -26018,7 +26047,7 @@ Autocapture.prototype.initScrollTracking = function() {
26018
26047
  }
26019
26048
  }
26020
26049
  } catch (err) {
26021
- logger$5.critical('Error while calculating scroll percentage', err);
26050
+ logger$6.critical('Error while calculating scroll percentage', err);
26022
26051
  }
26023
26052
  if (shouldTrack) {
26024
26053
  this.mp.track(MP_EV_SCROLL, props);
@@ -26036,7 +26065,7 @@ Autocapture.prototype.initSubmitTracking = function() {
26036
26065
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
26037
26066
  return;
26038
26067
  }
26039
- logger$5.log('Initializing submit tracking');
26068
+ logger$6.log('Initializing submit tracking');
26040
26069
 
26041
26070
  this.listenerSubmit = function(ev) {
26042
26071
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
@@ -26058,7 +26087,7 @@ Autocapture.prototype.initPageLeaveTracking = function() {
26058
26087
  return;
26059
26088
  }
26060
26089
 
26061
- logger$5.log('Initializing page visibility tracking.');
26090
+ logger$6.log('Initializing page visibility tracking.');
26062
26091
  this._initScrollDepthTracking();
26063
26092
  var previousTrackedUrl = _.info.currentUrl();
26064
26093
 
@@ -26143,10 +26172,183 @@ var getTargetingPromise = function(loadExtraBundle, targetingSrc) {
26143
26172
  return win[TARGETING_GLOBAL_NAME];
26144
26173
  };
26145
26174
 
26175
+ var logger$2 = console_with_prefix('flags');
26176
+
26177
+ var MIXPANEL_FLAGS_DB_NAME = 'mixpanelFlagsDb';
26178
+ var FLAGS_STORE_NAME = 'mixpanelFlags';
26179
+
26180
+ // Keeping these two properties closeby, as adding additional stores to a DB in IndexedDB requires a version increment
26181
+ var FLAGS_VERSION_DATA = { version: 1, storeNames: [FLAGS_STORE_NAME] };
26182
+
26183
+ var PERSISTED_VARIANTS_KEY_PREFIX = 'persisted_variants_for_';
26184
+ var DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
26185
+
26186
+ var VariantLookupPolicy = Object.freeze({
26187
+ NETWORK_ONLY: 'networkOnly',
26188
+ NETWORK_FIRST: 'networkFirst',
26189
+ PERSISTENCE_UNTIL_NETWORK_SUCCESS: 'persistenceUntilNetworkSuccess'
26190
+ });
26191
+
26192
+ var VALID_POLICIES = [
26193
+ VariantLookupPolicy.NETWORK_ONLY,
26194
+ VariantLookupPolicy.NETWORK_FIRST,
26195
+ VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS
26196
+ ];
26197
+
26198
+ /**
26199
+ * Module for handling the storage and retrieval of persisted feature flag variants.
26200
+ */
26201
+ var FeatureFlagPersistence = function(persistenceConfig, token, isGloballyDisabled) {
26202
+ this.idb = new IDBStorageWrapper(MIXPANEL_FLAGS_DB_NAME, FLAGS_STORE_NAME, FLAGS_VERSION_DATA);
26203
+ this.persistenceConfig = persistenceConfig;
26204
+ this.persistedVariantsKey = PERSISTED_VARIANTS_KEY_PREFIX + token;
26205
+ this.isGloballyDisabled = isGloballyDisabled || function() { return false; };
26206
+ };
26207
+
26208
+ FeatureFlagPersistence.prototype.getPolicy = function() {
26209
+ if (this.isGloballyDisabled() || !this._isConfigValid()) {
26210
+ return VariantLookupPolicy.NETWORK_ONLY;
26211
+ }
26212
+ return this.persistenceConfig['variantLookupPolicy'];
26213
+ };
26214
+
26215
+ FeatureFlagPersistence.prototype.getTtlMs = function() {
26216
+ if (!this._isConfigValid()) {
26217
+ return DEFAULT_TTL_MS;
26218
+ }
26219
+ var configuredTtl = this.persistenceConfig['persistenceTtlMs'];
26220
+ return (configuredTtl === undefined || configuredTtl === null) ? DEFAULT_TTL_MS : configuredTtl;
26221
+ };
26222
+
26223
+ FeatureFlagPersistence.prototype._isConfigValid = function() {
26224
+ var config = this.persistenceConfig;
26225
+ if (!config) {
26226
+ return false;
26227
+ }
26228
+
26229
+ if (VALID_POLICIES.indexOf(config['variantLookupPolicy']) === -1) {
26230
+ logger$2.error('Invalid variantLookupPolicy:', config['variantLookupPolicy']);
26231
+ return false;
26232
+ }
26233
+
26234
+ if (config['persistenceTtlMs'] !== undefined &&
26235
+ config['persistenceTtlMs'] !== null &&
26236
+ config['persistenceTtlMs'] <= 0) {
26237
+ logger$2.error('If provided, persistenceTtlMs must be a positive number. Provided value:', config['persistenceTtlMs']);
26238
+ return false;
26239
+ }
26240
+
26241
+ return true;
26242
+ };
26243
+
26244
+ FeatureFlagPersistence.prototype.loadFlagsFromStorage = function(context) {
26245
+ var clearAndReturnNull = _.bind(function() {
26246
+ return this.clear().then(function() { return null; }).catch(function() { return null; });
26247
+ }, this);
26248
+
26249
+ if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
26250
+ return clearAndReturnNull();
26251
+ }
26252
+
26253
+ var ttlMs = this.getTtlMs();
26254
+
26255
+ return this.idb.init().then(_.bind(function() {
26256
+ return this.idb.getItem(this.persistedVariantsKey);
26257
+ }, this)).then(_.bind(function(data) {
26258
+ if (!data) {
26259
+ logger$2.log('No persisted variants found in IndexedDB');
26260
+ return null;
26261
+ }
26262
+
26263
+ if (ttlMs && Date.now() - data['persistedAt'] >= ttlMs) {
26264
+ logger$2.log('Persisted variants are expiring');
26265
+ return null;
26266
+ }
26267
+
26268
+ if (!context || data['distinctId'] !== context['distinct_id']) {
26269
+ logger$2.log('Persisted variants found, but for a different distinct_id so clearing.');
26270
+ return clearAndReturnNull();
26271
+ }
26272
+
26273
+ var persistedFlags = new Map();
26274
+ _.each(data['flagVariants'], function(variantData, key) {
26275
+ persistedFlags.set(key, {
26276
+ 'key': variantData['variant_key'],
26277
+ 'value': variantData['variant_value'],
26278
+ 'experiment_id': variantData['experiment_id'],
26279
+ 'is_experiment_active': variantData['is_experiment_active'],
26280
+ 'is_qa_tester': variantData['is_qa_tester'],
26281
+ 'variant_source': 'persistence',
26282
+ 'persisted_at_in_ms': data['persistedAt'],
26283
+ 'ttl_in_ms': ttlMs
26284
+ });
26285
+ });
26286
+
26287
+ logger$2.log('Loaded', persistedFlags.size, 'variants from IndexedDB for distinct_id', data['distinctId']);
26288
+
26289
+ return {
26290
+ flags: persistedFlags,
26291
+ pendingFirstTimeEvents: data['pendingFirstTimeEvents'] || {},
26292
+ persistedAtMs: data['persistedAt'],
26293
+ ttlMs: ttlMs
26294
+ };
26295
+ }, this)).catch(_.bind(function(error) {
26296
+ logger$2.error('Failed to load persisted variants from IndexedDB, so clearing', error);
26297
+ return clearAndReturnNull();
26298
+ }, this));
26299
+ };
26300
+
26301
+ FeatureFlagPersistence.prototype.save = function(context, flagsMap, pendingFirstTimeEvents) {
26302
+ if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
26303
+ return Promise.resolve();
26304
+ }
26305
+
26306
+ var flagVariants = {};
26307
+ flagsMap.forEach(function(variant, key) {
26308
+ flagVariants[key] = {
26309
+ 'variant_key': variant['key'],
26310
+ 'variant_value': variant['value'],
26311
+ 'experiment_id': variant['experiment_id'],
26312
+ 'is_experiment_active': variant['is_experiment_active'],
26313
+ 'is_qa_tester': variant['is_qa_tester']
26314
+ };
26315
+ });
26316
+
26317
+ var data = {
26318
+ 'persistedAt': Date.now(),
26319
+ 'distinctId': context && context['distinct_id'],
26320
+ 'context': context,
26321
+ 'flagVariants': flagVariants,
26322
+ 'pendingFirstTimeEvents': pendingFirstTimeEvents || {}
26323
+ };
26324
+
26325
+ return this.idb.init().then(_.bind(function() {
26326
+ return this.idb.setItem(this.persistedVariantsKey, data);
26327
+ }, this)).then(function() {
26328
+ logger$2.log('Saved', flagsMap.size, 'variants to IndexedDB for distinct_id', data['distinctId']);
26329
+ }).catch(function(error) {
26330
+ logger$2.error('Failed to persist variants to IndexedDB:', error);
26331
+ });
26332
+ };
26333
+
26334
+ FeatureFlagPersistence.prototype.clear = function() {
26335
+ if (this.isGloballyDisabled()) {
26336
+ return Promise.resolve();
26337
+ }
26338
+ return this.idb.init().then(_.bind(function() {
26339
+ return this.idb.removeItem(this.persistedVariantsKey);
26340
+ }, this)).then(function() {
26341
+ logger$2.log('Cleared persisted variants from IndexedDB');
26342
+ }).catch(function(error) {
26343
+ logger$2.error('Failed to clear persisted variants from IndexedDB:', error);
26344
+ });
26345
+ };
26346
+
26146
26347
  var logger$1 = console_with_prefix('flags');
26147
26348
  var FLAGS_CONFIG_KEY = 'flags';
26148
26349
 
26149
26350
  var CONFIG_CONTEXT = 'context';
26351
+ var CONFIG_PERSISTENCE = 'persistence';
26150
26352
  var CONFIG_DEFAULTS = {};
26151
26353
  CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
26152
26354
 
@@ -26169,6 +26371,13 @@ var getFlagKeyFromPendingEventKey = function(eventKey) {
26169
26371
  return eventKey.split(':')[0];
26170
26372
  };
26171
26373
 
26374
+ var withFallbackSource = function(fallback) {
26375
+ if (_.isObject(fallback)) {
26376
+ return _.extend({}, fallback, {'variant_source': 'fallback'});
26377
+ }
26378
+ return {'value': fallback, 'variant_source': 'fallback'};
26379
+ };
26380
+
26172
26381
  /**
26173
26382
  * FeatureFlagManager: support for Mixpanel's feature flagging product
26174
26383
  * @constructor
@@ -26191,11 +26400,63 @@ FeatureFlagManager.prototype.init = function() {
26191
26400
  }
26192
26401
 
26193
26402
  this.flags = null;
26194
- this.fetchFlags();
26195
-
26196
26403
  this.trackedFeatures = new Set();
26197
26404
  this.pendingFirstTimeEvents = {};
26198
26405
  this.activatedFirstTimeEvents = {};
26406
+ this._loadedPersistedAtMs = null;
26407
+ this._loadedTtlMs = null;
26408
+
26409
+ this.persistence = new FeatureFlagPersistence(
26410
+ this.getConfig(CONFIG_PERSISTENCE),
26411
+ this.getMpConfig('token'),
26412
+ _.bind(function() { return this.getMpConfig('disable_persistence'); }, this)
26413
+ );
26414
+
26415
+ this.persistenceLoadedPromise = this.persistence.loadFlagsFromStorage(this._buildContext())
26416
+ .then(_.bind(function(loaded) {
26417
+ if (loaded) {
26418
+ this.flags = loaded.flags;
26419
+ this.pendingFirstTimeEvents = loaded.pendingFirstTimeEvents;
26420
+ this._loadedPersistedAtMs = loaded.persistedAtMs;
26421
+ this._loadedTtlMs = loaded.ttlMs;
26422
+ }
26423
+ }, this));
26424
+
26425
+ return this.persistenceLoadedPromise
26426
+ .then(_.bind(function() {
26427
+ return this.fetchFlags();
26428
+ }, this))
26429
+ .catch(function() {
26430
+ logger$1.error('Error initializing feature flags');
26431
+ });
26432
+ };
26433
+
26434
+ FeatureFlagManager.prototype._buildContext = function() {
26435
+ return _.extend(
26436
+ {'distinct_id': this.getMpProperty('distinct_id'), 'device_id': this.getMpProperty('$device_id')},
26437
+ this.getConfig(CONFIG_CONTEXT)
26438
+ );
26439
+ };
26440
+
26441
+ FeatureFlagManager.prototype.reset = function() {
26442
+ if (!this.persistence) {
26443
+ return Promise.resolve();
26444
+ }
26445
+
26446
+ this.flags = null;
26447
+ this.pendingFirstTimeEvents = {};
26448
+ this.activatedFirstTimeEvents = {};
26449
+ this.trackedFeatures = new Set();
26450
+ this.fetchPromise = null;
26451
+ this._fetchInProgressStartTime = null;
26452
+ this._loadedPersistedAtMs = null;
26453
+ this._loadedTtlMs = null;
26454
+
26455
+ return this.persistence.clear().then(_.bind(function() {
26456
+ return this.fetchFlags();
26457
+ }, this)).catch(function() {
26458
+ logger$1.error('Error during flags reset');
26459
+ });
26199
26460
  };
26200
26461
 
26201
26462
  FeatureFlagManager.prototype.getFullConfig = function() {
@@ -26232,8 +26493,12 @@ FeatureFlagManager.prototype.updateContext = function(newContext, options) {
26232
26493
  var oldContext = (options && options['replace']) ? {} : this.getConfig(CONFIG_CONTEXT);
26233
26494
  ffConfig[CONFIG_CONTEXT] = _.extend({}, oldContext, newContext);
26234
26495
 
26235
- this.setMpConfig(FLAGS_CONFIG_KEY, ffConfig);
26236
- return this.fetchFlags();
26496
+ var configUpdate = {};
26497
+ configUpdate[FLAGS_CONFIG_KEY] = ffConfig;
26498
+ this.setMpConfig(configUpdate);
26499
+ return this.fetchFlags().catch(function() {
26500
+ logger$1.error('Error fetching flags during updateContext');
26501
+ });
26237
26502
  };
26238
26503
 
26239
26504
  FeatureFlagManager.prototype.areFlagsReady = function() {
@@ -26248,12 +26513,11 @@ FeatureFlagManager.prototype.fetchFlags = function() {
26248
26513
  return Promise.resolve();
26249
26514
  }
26250
26515
 
26251
- var distinctId = this.getMpProperty('distinct_id');
26252
- var deviceId = this.getMpProperty('$device_id');
26516
+ var context = this._buildContext();
26517
+ var distinctId = context['distinct_id'];
26253
26518
  var traceparent = generateTraceparent();
26254
26519
  logger$1.log('Fetching flags for distinct ID: ' + distinctId);
26255
26520
 
26256
- var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
26257
26521
  var searchParams = new URLSearchParams();
26258
26522
  searchParams.set('context', JSON.stringify(context));
26259
26523
  searchParams.set('token', this.getMpConfig('token'));
@@ -26270,96 +26534,116 @@ FeatureFlagManager.prototype.fetchFlags = function() {
26270
26534
  }
26271
26535
  }).then(function(response) {
26272
26536
  this.markFetchComplete();
26273
- return response.json().then(function(responseBody) {
26274
- var responseFlags = responseBody['flags'];
26275
- if (!responseFlags) {
26276
- throw new Error('No flags in API response');
26277
- }
26278
- var flags = new Map();
26279
- var pendingFirstTimeEvents = {};
26280
-
26281
- // Process flags from response
26282
- _.each(responseFlags, function(data, key) {
26283
- // Check if this flag has any activated first-time events this session
26284
- var hasActivatedEvent = false;
26285
- var prefix = key + ':';
26286
- _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26287
- if (eventKey.startsWith(prefix)) {
26288
- hasActivatedEvent = true;
26289
- }
26290
- });
26537
+ return response.json();
26538
+ }.bind(this)).then(function(responseBody) {
26539
+ var responseFlags = responseBody['flags'];
26540
+ if (!responseFlags) {
26541
+ throw new Error('No flags in API response');
26542
+ }
26543
+ var flags = new Map();
26544
+ var pendingFirstTimeEvents = {};
26545
+
26546
+ // Process flags from response
26547
+ _.each(responseFlags, function(data, key) {
26548
+ // Check if this flag has any activated first-time events this session
26549
+ var hasActivatedEvent = false;
26550
+ var prefix = key + ':';
26551
+ _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26552
+ if (eventKey.startsWith(prefix)) {
26553
+ hasActivatedEvent = true;
26554
+ }
26555
+ });
26291
26556
 
26292
- if (hasActivatedEvent) {
26293
- // Preserve the activated variant, don't overwrite with server's current variant
26294
- var currentFlag = this.flags && this.flags.get(key);
26295
- if (currentFlag) {
26296
- flags.set(key, currentFlag);
26297
- }
26298
- } else {
26299
- // Use server's current variant
26300
- flags.set(key, {
26301
- 'key': data['variant_key'],
26302
- 'value': data['variant_value'],
26303
- 'experiment_id': data['experiment_id'],
26304
- 'is_experiment_active': data['is_experiment_active'],
26305
- 'is_qa_tester': data['is_qa_tester']
26306
- });
26557
+ if (hasActivatedEvent) {
26558
+ // Preserve the activated variant, don't overwrite with server's current variant
26559
+ var currentFlag = this.flags && this.flags.get(key);
26560
+ if (currentFlag) {
26561
+ flags.set(key, currentFlag);
26307
26562
  }
26308
- }, this);
26563
+ } else {
26564
+ // Use server's current variant
26565
+ flags.set(key, {
26566
+ 'key': data['variant_key'],
26567
+ 'value': data['variant_value'],
26568
+ 'experiment_id': data['experiment_id'],
26569
+ 'is_experiment_active': data['is_experiment_active'],
26570
+ 'is_qa_tester': data['is_qa_tester'],
26571
+ 'variant_source': 'network'
26572
+ });
26573
+ }
26574
+ }, this);
26309
26575
 
26310
- // Process top-level pending_first_time_events array
26311
- var topLevelDefinitions = responseBody['pending_first_time_events'];
26312
- if (topLevelDefinitions && topLevelDefinitions.length > 0) {
26313
- _.each(topLevelDefinitions, function(def) {
26314
- var flagKey = def['flag_key'];
26315
- var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
26576
+ // Process top-level pending_first_time_events array
26577
+ var topLevelDefinitions = responseBody['pending_first_time_events'];
26578
+ if (topLevelDefinitions && topLevelDefinitions.length > 0) {
26579
+ _.each(topLevelDefinitions, function(def) {
26580
+ var flagKey = def['flag_key'];
26581
+ var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
26316
26582
 
26317
- // Skip if this specific event has already been activated this session
26318
- if (this.activatedFirstTimeEvents[eventKey]) {
26319
- return;
26320
- }
26583
+ // Skip if this specific event has already been activated this session
26584
+ if (this.activatedFirstTimeEvents[eventKey]) {
26585
+ return;
26586
+ }
26321
26587
 
26322
- // Store pending event definition using composite key
26323
- pendingFirstTimeEvents[eventKey] = {
26324
- 'flag_key': flagKey,
26325
- 'flag_id': def['flag_id'],
26326
- 'project_id': def['project_id'],
26327
- 'first_time_event_hash': def['first_time_event_hash'],
26328
- 'event_name': def['event_name'],
26329
- 'property_filters': def['property_filters'],
26330
- 'pending_variant': def['pending_variant']
26331
- };
26332
- }, this);
26333
- }
26588
+ // Store pending event definition using composite key
26589
+ pendingFirstTimeEvents[eventKey] = {
26590
+ 'flag_key': flagKey,
26591
+ 'flag_id': def['flag_id'],
26592
+ 'project_id': def['project_id'],
26593
+ 'first_time_event_hash': def['first_time_event_hash'],
26594
+ 'event_name': def['event_name'],
26595
+ 'property_filters': def['property_filters'],
26596
+ 'pending_variant': def['pending_variant']
26597
+ };
26598
+ }, this);
26599
+ }
26334
26600
 
26335
- // Preserve any activated orphaned flags (flags that were activated but are no longer in response)
26336
- if (this.activatedFirstTimeEvents) {
26337
- _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26338
- var flagKey = getFlagKeyFromPendingEventKey(eventKey);
26339
- if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
26340
- // Keep the activated flag even though it's not in the new response
26341
- flags.set(flagKey, this.flags.get(flagKey));
26342
- }
26343
- }, this);
26344
- }
26601
+ // Preserve any activated orphaned flags (flags that were activated but are no longer in response)
26602
+ if (this.activatedFirstTimeEvents) {
26603
+ _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26604
+ var flagKey = getFlagKeyFromPendingEventKey(eventKey);
26605
+ if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
26606
+ // Keep the activated flag even though it's not in the new response
26607
+ flags.set(flagKey, this.flags.get(flagKey));
26608
+ }
26609
+ }, this);
26610
+ }
26345
26611
 
26346
- this.flags = flags;
26347
- this.pendingFirstTimeEvents = pendingFirstTimeEvents;
26348
- this._traceparent = traceparent;
26612
+ this.flags = flags;
26613
+ this.trackedFeatures = new Set();
26614
+ this.pendingFirstTimeEvents = pendingFirstTimeEvents;
26615
+ this._loadedPersistedAtMs = null;
26616
+ this._loadedTtlMs = null;
26617
+ this._traceparent = traceparent;
26349
26618
 
26350
- this._loadTargetingIfNeeded();
26351
- }.bind(this)).catch(function(error) {
26352
- this.markFetchComplete();
26353
- logger$1.error(error);
26354
- }.bind(this));
26619
+ this._loadTargetingIfNeeded();
26620
+
26621
+ this.persistence.save(context, this.flags, this.pendingFirstTimeEvents);
26355
26622
  }.bind(this)).catch(function(error) {
26356
- this.markFetchComplete();
26623
+ if (this._fetchInProgressStartTime) {
26624
+ this.markFetchComplete();
26625
+ }
26357
26626
  logger$1.error(error);
26627
+ throw error;
26358
26628
  }.bind(this));
26359
26629
 
26360
26630
  return this.fetchPromise;
26361
26631
  };
26362
26632
 
26633
+ FeatureFlagManager.prototype.loadFlags = function() {
26634
+ if (!this.isSystemEnabled()) {
26635
+ return Promise.resolve();
26636
+ }
26637
+ if (!this.trackedFeatures) {
26638
+ logger$1.error('loadFlags called before init');
26639
+ return Promise.resolve();
26640
+ }
26641
+ if (this._fetchInProgressStartTime) {
26642
+ return this.fetchPromise;
26643
+ }
26644
+ return this.fetchFlags();
26645
+ };
26646
+
26363
26647
  FeatureFlagManager.prototype.markFetchComplete = function() {
26364
26648
  if (!this._fetchInProgressStartTime) {
26365
26649
  logger$1.error('Fetch in progress started time not set, cannot mark fetch complete');
@@ -26494,6 +26778,7 @@ FeatureFlagManager.prototype._processFirstTimeEventCheck = function(eventName, p
26494
26778
  };
26495
26779
 
26496
26780
  this.flags.set(flagKey, newVariant);
26781
+ this.trackedFeatures.delete(flagKey);
26497
26782
  this.activatedFirstTimeEvents[eventKey] = true;
26498
26783
 
26499
26784
  this.recordFirstTimeEvent(
@@ -26543,35 +26828,106 @@ FeatureFlagManager.prototype.recordFirstTimeEvent = function(flagId, projectId,
26543
26828
  };
26544
26829
 
26545
26830
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
26546
- if (!this.fetchPromise) {
26831
+ if (!this.persistenceLoadedPromise) {
26547
26832
  return new Promise(function(resolve) {
26548
26833
  logger$1.critical('Feature Flags not initialized');
26549
- resolve(fallback);
26834
+ resolve(withFallbackSource(fallback));
26550
26835
  });
26551
26836
  }
26552
26837
 
26553
- return this.fetchPromise.then(function() {
26554
- return this.getVariantSync(featureName, fallback);
26555
- }.bind(this)).catch(function(error) {
26556
- logger$1.error(error);
26557
- return fallback;
26558
- });
26838
+ var policy = this.persistence.getPolicy();
26839
+
26840
+ return this.persistenceLoadedPromise.then(_.bind(function() {
26841
+ // 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.
26842
+ if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
26843
+ if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
26844
+ return this.getVariantSync(featureName, fallback);
26845
+ }
26846
+ if (!this.fetchPromise) {
26847
+ return withFallbackSource(fallback);
26848
+ }
26849
+ return this.fetchPromise.then(_.bind(function() {
26850
+ return this.getVariantSync(featureName, fallback);
26851
+ }, this)).catch(function(error) {
26852
+ logger$1.error(error);
26853
+ return withFallbackSource(fallback);
26854
+ });
26855
+ }
26856
+
26857
+ var serve = _.bind(function() { return this.getVariantSync(featureName, fallback); }, this);
26858
+ if (!this.fetchPromise) {
26859
+ return withFallbackSource(fallback);
26860
+ }
26861
+ return this.fetchPromise.then(serve).catch(serve);
26862
+ }, this));
26863
+ };
26864
+
26865
+ FeatureFlagManager.prototype._loadedPersistenceIsStale = function() {
26866
+ if (!this._loadedPersistedAtMs || !this._loadedTtlMs) {
26867
+ return false;
26868
+ }
26869
+ return Date.now() - this._loadedPersistedAtMs >= this._loadedTtlMs;
26559
26870
  };
26560
26871
 
26561
26872
  FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
26873
+ if (this._loadedPersistenceIsStale()) {
26874
+ logger$1.log('Loaded persisted variants are past TTL so returning fallback for "' + featureName + '"');
26875
+ return withFallbackSource(fallback);
26876
+ }
26562
26877
  if (!this.areFlagsReady()) {
26563
26878
  logger$1.log('Flags not loaded yet');
26564
- return fallback;
26879
+ return withFallbackSource(fallback);
26565
26880
  }
26566
26881
  var feature = this.flags.get(featureName);
26567
26882
  if (!feature) {
26568
26883
  logger$1.log('No flag found: "' + featureName + '"');
26569
- return fallback;
26884
+ return withFallbackSource(fallback);
26570
26885
  }
26571
26886
  this.trackFeatureCheck(featureName, feature);
26572
26887
  return feature;
26573
26888
  };
26574
26889
 
26890
+ FeatureFlagManager.prototype.getAllVariants = function() {
26891
+ if (!this.persistenceLoadedPromise) {
26892
+ logger$1.critical('Feature Flags not initialized');
26893
+ return Promise.resolve(new Map());
26894
+ }
26895
+
26896
+ var policy = this.persistence.getPolicy();
26897
+
26898
+ return this.persistenceLoadedPromise.then(_.bind(function() {
26899
+ // 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.
26900
+ if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
26901
+ if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
26902
+ return this.getAllVariantsSync();
26903
+ }
26904
+ if (!this.fetchPromise) {
26905
+ return new Map();
26906
+ }
26907
+ return this.fetchPromise.then(_.bind(function() {
26908
+ return this.getAllVariantsSync();
26909
+ }, this)).catch(function(error) {
26910
+ logger$1.error(error);
26911
+ return new Map();
26912
+ });
26913
+ }
26914
+
26915
+ var serve = _.bind(this.getAllVariantsSync, this);
26916
+ if (!this.fetchPromise) {
26917
+ return new Map();
26918
+ }
26919
+ return this.fetchPromise.then(serve).catch(serve);
26920
+ }, this));
26921
+ };
26922
+
26923
+ FeatureFlagManager.prototype.getAllVariantsSync = function() {
26924
+ if (this._loadedPersistenceIsStale()) {
26925
+ logger$1.log('Loaded persisted variants are past TTL so returning empty Map');
26926
+ return new Map();
26927
+ }
26928
+ return this.flags || new Map();
26929
+ };
26930
+
26575
26931
  FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
26576
26932
  return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
26577
26933
  return feature['value'];
@@ -26610,6 +26966,10 @@ FeatureFlagManager.prototype.isEnabledSync = function(featureName, fallbackValue
26610
26966
  return val;
26611
26967
  };
26612
26968
 
26969
+ function isPresent(v) {
26970
+ return v !== undefined && v !== null;
26971
+ }
26972
+
26613
26973
  FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
26614
26974
  if (this.trackedFeatures.has(featureName)) {
26615
26975
  return;
@@ -26620,25 +26980,41 @@ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature)
26620
26980
  'Experiment name': featureName,
26621
26981
  'Variant name': feature['key'],
26622
26982
  '$experiment_type': 'feature_flag',
26623
- 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
26624
- 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
26983
+ 'Variant fetch start time': isPresent(this._fetchStartTime) ? new Date(this._fetchStartTime).toISOString() : null,
26984
+ 'Variant fetch complete time': isPresent(this._fetchCompleteTime) ? new Date(this._fetchCompleteTime).toISOString() : null,
26625
26985
  'Variant fetch latency (ms)': this._fetchLatency,
26626
26986
  'Variant fetch traceparent': this._traceparent,
26627
26987
  };
26628
26988
 
26629
- if (feature['experiment_id'] !== 'undefined') {
26989
+ if (isPresent(feature['experiment_id'])) {
26630
26990
  trackingProperties['$experiment_id'] = feature['experiment_id'];
26631
26991
  }
26632
- if (feature['is_experiment_active'] !== 'undefined') {
26992
+ if (isPresent(feature['is_experiment_active'])) {
26633
26993
  trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
26634
26994
  }
26635
- if (feature['is_qa_tester'] !== 'undefined') {
26995
+ if (isPresent(feature['is_qa_tester'])) {
26636
26996
  trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
26637
26997
  }
26998
+ if (isPresent(feature['variant_source'])) {
26999
+ trackingProperties['$variant_source'] = feature['variant_source'];
27000
+ }
27001
+ if (isPresent(feature['persisted_at_in_ms'])) {
27002
+ trackingProperties['$persisted_at_in_ms'] = feature['persisted_at_in_ms'];
27003
+ }
27004
+ if (isPresent(feature['ttl_in_ms'])) {
27005
+ trackingProperties['$ttl_in_ms'] = feature['ttl_in_ms'];
27006
+ }
26638
27007
 
26639
27008
  this.track('$experiment_started', trackingProperties);
26640
27009
  };
26641
27010
 
27011
+ FeatureFlagManager.prototype.whenReady = function() {
27012
+ if (this.fetchPromise) {
27013
+ return this.fetchPromise;
27014
+ }
27015
+ return Promise.resolve();
27016
+ };
27017
+
26642
27018
  FeatureFlagManager.prototype.minApisSupported = function() {
26643
27019
  return !!this.fetch &&
26644
27020
  typeof Promise !== 'undefined' &&
@@ -26651,11 +27027,15 @@ safewrapClass(FeatureFlagManager);
26651
27027
  FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
26652
27028
  FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
26653
27029
  FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
27030
+ FeatureFlagManager.prototype['get_all_variants'] = FeatureFlagManager.prototype.getAllVariants;
27031
+ FeatureFlagManager.prototype['get_all_variants_sync'] = FeatureFlagManager.prototype.getAllVariantsSync;
26654
27032
  FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
26655
27033
  FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
26656
27034
  FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
26657
27035
  FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
27036
+ FeatureFlagManager.prototype['load_flags'] = FeatureFlagManager.prototype.loadFlags;
26658
27037
  FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
27038
+ FeatureFlagManager.prototype['when_ready'] = FeatureFlagManager.prototype.whenReady;
26659
27039
 
26660
27040
  // Deprecated method
26661
27041
  FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
@@ -26701,7 +27081,7 @@ RecorderManager.prototype.shouldLoadRecorder = function() {
26701
27081
  return PromisePolyfill.resolve(false);
26702
27082
  }
26703
27083
 
26704
- var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
27084
+ var recording_registry_idb = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_REGISTRY_STORE_NAME, RECORDER_VERSION_DATA);
26705
27085
  var tab_id = this.getTabId();
26706
27086
  return recording_registry_idb.init()
26707
27087
  .then(function () {
@@ -28628,6 +29008,7 @@ MixpanelLib.prototype._init = function(token, config, name) {
28628
29008
  'disable_all_events': false,
28629
29009
  'identify_called': false
28630
29010
  };
29011
+ this._remote_settings_strict_disabled = false;
28631
29012
 
28632
29013
  // set up request queueing/batching
28633
29014
  this.request_batchers = {};
@@ -28702,9 +29083,6 @@ MixpanelLib.prototype._init = function(token, config, name) {
28702
29083
  this.flags.init();
28703
29084
  this['flags'] = this.flags;
28704
29085
 
28705
- this.autocapture = new Autocapture(this);
28706
- this.autocapture.init();
28707
-
28708
29086
  this._init_tab_id();
28709
29087
 
28710
29088
  // Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
@@ -28716,6 +29094,9 @@ MixpanelLib.prototype._init = function(token, config, name) {
28716
29094
  } else {
28717
29095
  this.__session_recording_init_promise = this._check_and_start_session_recording();
28718
29096
  }
29097
+
29098
+ this.autocapture = new Autocapture(this);
29099
+ this.autocapture.init();
28719
29100
  };
28720
29101
 
28721
29102
  /**
@@ -28762,9 +29143,19 @@ MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpane
28762
29143
  return this.recorderManager.checkAndStartSessionRecording(force_start);
28763
29144
  });
28764
29145
 
28765
- MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
28766
- return this.recorderManager.startRecordingOnEvent(event_name, properties);
28767
- };
29146
+ MixpanelLib.prototype._start_recording_on_event = safewrap(function(event_name, properties) {
29147
+ // Wait for recording init to complete before evaluating event triggers.
29148
+ // This ensures recording_event_triggers config is fully loaded when remote settings are used.
29149
+ if (this.__session_recording_init_promise) {
29150
+ this.__session_recording_init_promise.then(_.bind(function() {
29151
+ // In strict mode, skip recording if remote settings failed
29152
+ if (this._remote_settings_strict_disabled) {
29153
+ return;
29154
+ }
29155
+ return this.recorderManager.startRecordingOnEvent(event_name, properties);
29156
+ }, this));
29157
+ }
29158
+ });
28768
29159
 
28769
29160
  MixpanelLib.prototype.start_session_recording = function () {
28770
29161
  return this._check_and_start_session_recording(true);
@@ -29063,6 +29454,7 @@ MixpanelLib.prototype._fetch_remote_settings = function(mode) {
29063
29454
  var disableRecordingIfStrict = function() {
29064
29455
  if (mode === 'strict') {
29065
29456
  self.set_config({'record_sessions_percent': 0});
29457
+ self._remote_settings_strict_disabled = true;
29066
29458
  }
29067
29459
  };
29068
29460
 
@@ -29688,6 +30080,10 @@ MixpanelLib.prototype.track_pageview = addOptOutCheckMixpanelLib(function(proper
29688
30080
  properties
29689
30081
  );
29690
30082
 
30083
+ if (this.is_recording_heatmap_data()) {
30084
+ event_properties['$captured_for_heatmap'] = true;
30085
+ }
30086
+
29691
30087
  return this.track(event_name, event_properties);
29692
30088
  });
29693
30089
 
@@ -30011,7 +30407,9 @@ MixpanelLib.prototype.identify = function(
30011
30407
 
30012
30408
  // check feature flags again if distinct id has changed
30013
30409
  if (new_distinct_id !== previous_distinct_id) {
30014
- this.flags.fetchFlags();
30410
+ this.flags.fetchFlags().catch(function() {
30411
+ console$1.error('[flags] Error fetching flags during identify');
30412
+ });
30015
30413
  }
30016
30414
  };
30017
30415
 
@@ -30029,6 +30427,7 @@ MixpanelLib.prototype.reset = function() {
30029
30427
  '$device_id': uuid
30030
30428
  }, '');
30031
30429
  this._check_and_start_session_recording();
30430
+ this.flags.reset();
30032
30431
  };
30033
30432
 
30034
30433
  /**