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
@@ -27,7 +27,7 @@ if (typeof(window) === 'undefined') {
27
27
 
28
28
  var Config = {
29
29
  DEBUG: false,
30
- LIB_VERSION: '2.77.0'
30
+ LIB_VERSION: '2.79.0'
31
31
  };
32
32
 
33
33
  // Window global names for async modules
@@ -19125,6 +19125,7 @@ var log_func_with_prefix = function(func, prefix) {
19125
19125
  var console_with_prefix = function(prefix) {
19126
19126
  return {
19127
19127
  log: log_func_with_prefix(console$1.log, prefix),
19128
+ warn: log_func_with_prefix(console$1.warn, prefix),
19128
19129
  error: log_func_with_prefix(console$1.error, prefix),
19129
19130
  critical: log_func_with_prefix(console$1.critical, prefix)
19130
19131
  };
@@ -20071,7 +20072,8 @@ var localStorageSupported = function(storage, forceCheck) {
20071
20072
  if (_localStorageSupported !== null && !forceCheck) {
20072
20073
  return _localStorageSupported;
20073
20074
  }
20074
- return _localStorageSupported = _testStorageSupported(storage || win.localStorage);
20075
+
20076
+ return _localStorageSupported = _testStorageSupported(storage);
20075
20077
  };
20076
20078
 
20077
20079
  var _sessionStorageSupported = null;
@@ -20079,7 +20081,8 @@ var sessionStorageSupported = function(storage, forceCheck) {
20079
20081
  if (_sessionStorageSupported !== null && !forceCheck) {
20080
20082
  return _sessionStorageSupported;
20081
20083
  }
20082
- return _sessionStorageSupported = _testStorageSupported(storage || win.sessionStorage);
20084
+
20085
+ return _sessionStorageSupported = _testStorageSupported(storage);
20083
20086
  };
20084
20087
 
20085
20088
  function _storageWrapper(storage, name, is_supported_fn) {
@@ -20129,17 +20132,26 @@ function _storageWrapper(storage, name, is_supported_fn) {
20129
20132
  };
20130
20133
  }
20131
20134
 
20132
- // Safari errors out accessing localStorage/sessionStorage when cookies are disabled,
20133
- // so create dummy storage wrappers that silently fail as a fallback.
20134
- var windowLocalStorage = null, windowSessionStorage = null;
20135
- try {
20136
- windowLocalStorage = win.localStorage;
20137
- windowSessionStorage = win.sessionStorage;
20138
- // eslint-disable-next-line no-empty
20139
- } catch (_err) {}
20135
+ // Safari and other browsers may error out accessing localStorage/sessionStorage
20136
+ // when cookies are disabled, so wrap access in a try-catch.
20137
+ var getLocalStorage = function() {
20138
+ try {
20139
+ return win.localStorage; // eslint-disable-line no-restricted-properties
20140
+ } catch (_err) {
20141
+ return null;
20142
+ }
20143
+ };
20140
20144
 
20141
- _.localStorage = _storageWrapper(windowLocalStorage, 'localStorage', localStorageSupported);
20142
- _.sessionStorage = _storageWrapper(windowSessionStorage, 'sessionStorage', sessionStorageSupported);
20145
+ var getSessionStorage = function() {
20146
+ try {
20147
+ return win.sessionStorage; // eslint-disable-line no-restricted-properties
20148
+ } catch (_err) {
20149
+ return null;
20150
+ }
20151
+ };
20152
+
20153
+ _.localStorage = _storageWrapper(getLocalStorage(), 'localStorage', localStorageSupported);
20154
+ _.sessionStorage = _storageWrapper(getSessionStorage(), 'sessionStorage', sessionStorageSupported);
20143
20155
 
20144
20156
  _.register_event = (function() {
20145
20157
  // written by Dean Edwards, 2005
@@ -20808,29 +20820,26 @@ _['JSONEncode'] = _.JSONEncode;
20808
20820
  _['toArray'] = _.toArray;
20809
20821
  _['NPO'] = NpoPromise;
20810
20822
 
20811
- var MIXPANEL_DB_NAME = 'mixpanelBrowserDb';
20812
-
20813
- var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
20814
- var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
20815
-
20816
- // note: increment the version number when adding new object stores
20817
- var DB_VERSION = 1;
20818
- var OBJECT_STORES = [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME];
20819
-
20820
20823
  /**
20821
20824
  * @type {import('./wrapper').StorageWrapper}
20822
20825
  */
20823
- var IDBStorageWrapper = function (storeName) {
20826
+ var IDBStorageWrapper = function (dbName, storeName, versionData) {
20827
+ this.dbName = dbName;
20828
+ this.storeName = storeName;
20829
+ this.version = versionData.version;
20830
+ this.storeNamesInDb = versionData.storeNames;
20824
20831
  /**
20825
20832
  * @type {Promise<IDBDatabase>|null}
20826
20833
  */
20827
20834
  this.dbPromise = null;
20828
- this.storeName = storeName;
20829
20835
  };
20830
20836
 
20831
20837
  IDBStorageWrapper.prototype._openDb = function () {
20838
+ var dbName = this.dbName;
20839
+ var version = this.version;
20840
+ var storeNamesInDb = this.storeNamesInDb;
20832
20841
  return new PromisePolyfill(function (resolve, reject) {
20833
- var openRequest = win.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
20842
+ var openRequest = win.indexedDB.open(dbName, version);
20834
20843
  openRequest['onerror'] = function () {
20835
20844
  reject(openRequest.error);
20836
20845
  };
@@ -20842,8 +20851,10 @@ IDBStorageWrapper.prototype._openDb = function () {
20842
20851
  openRequest['onupgradeneeded'] = function (ev) {
20843
20852
  var db = ev.target.result;
20844
20853
 
20845
- OBJECT_STORES.forEach(function (storeName) {
20846
- db.createObjectStore(storeName);
20854
+ storeNamesInDb.forEach(function (storeName) {
20855
+ if (!db.objectStoreNames.contains(storeName)) {
20856
+ db.createObjectStore(storeName);
20857
+ }
20847
20858
  });
20848
20859
  };
20849
20860
  });
@@ -20935,6 +20946,16 @@ IDBStorageWrapper.prototype.getAll = function () {
20935
20946
  });
20936
20947
  };
20937
20948
 
20949
+ var MIXPANEL_BROWSER_DB_NAME = 'mixpanelBrowserDb';
20950
+ var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
20951
+ var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
20952
+
20953
+ // Keeping these two properties closeby, as adding additional stores to a DB in IndexedDB requires a version increment
20954
+ var RECORDER_VERSION_DATA = {
20955
+ version: 1,
20956
+ storeNames: [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME]
20957
+ };
20958
+
20938
20959
  /**
20939
20960
  * GDPR utils
20940
20961
  *
@@ -21235,7 +21256,7 @@ function _addOptOutCheck(method, getConfigValue) {
21235
21256
  };
21236
21257
  }
21237
21258
 
21238
- var logger$8 = console_with_prefix('lock');
21259
+ var logger$9 = console_with_prefix('lock');
21239
21260
 
21240
21261
  /**
21241
21262
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -21261,7 +21282,7 @@ var SharedLock = function(key, options) {
21261
21282
  options = options || {};
21262
21283
 
21263
21284
  this.storageKey = key;
21264
- this.storage = options.storage || win.localStorage;
21285
+ this.storage = options.storage || getLocalStorage();
21265
21286
  this.pollIntervalMS = options.pollIntervalMS || 100;
21266
21287
  this.timeoutMS = options.timeoutMS || 2000;
21267
21288
 
@@ -21287,7 +21308,7 @@ SharedLock.prototype.withLock = function(lockedCB, pid) {
21287
21308
 
21288
21309
  var delay = function(cb) {
21289
21310
  if (new Date().getTime() - startTime > timeoutMS) {
21290
- logger$8.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21311
+ logger$9.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21291
21312
  storage.removeItem(keyZ);
21292
21313
  storage.removeItem(keyY);
21293
21314
  loop();
@@ -21389,10 +21410,13 @@ SharedLock.prototype.withLock = function(lockedCB, pid) {
21389
21410
  * @type {import('./wrapper').StorageWrapper}
21390
21411
  */
21391
21412
  var LocalStorageWrapper = function (storageOverride) {
21392
- this.storage = storageOverride || win.localStorage;
21413
+ this.storage = storageOverride || getLocalStorage();
21393
21414
  };
21394
21415
 
21395
21416
  LocalStorageWrapper.prototype.init = function () {
21417
+ if (!this.storage) {
21418
+ return PromisePolyfill.reject(new Error('localStorage is not available'));
21419
+ }
21396
21420
  return PromisePolyfill.resolve();
21397
21421
  };
21398
21422
 
@@ -21434,7 +21458,7 @@ LocalStorageWrapper.prototype.removeItem = function (key) {
21434
21458
  }, this));
21435
21459
  };
21436
21460
 
21437
- var logger$7 = console_with_prefix('batch');
21461
+ var logger$8 = console_with_prefix('batch');
21438
21462
 
21439
21463
  /**
21440
21464
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -21459,11 +21483,11 @@ var RequestQueue = function (storageKey, options) {
21459
21483
  if (this.usePersistence) {
21460
21484
  this.queueStorage = options.queueStorage || new LocalStorageWrapper();
21461
21485
  this.lock = new SharedLock(storageKey, {
21462
- storage: options.sharedLockStorage || win.localStorage,
21486
+ storage: options.sharedLockStorage,
21463
21487
  timeoutMS: options.sharedLockTimeoutMS,
21464
21488
  });
21465
21489
  }
21466
- this.reportError = options.errorReporter || _.bind(logger$7.error, logger$7);
21490
+ this.reportError = options.errorReporter || _.bind(logger$8.error, logger$8);
21467
21491
 
21468
21492
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
21469
21493
 
@@ -21796,7 +21820,7 @@ RequestQueue.prototype.clear = function () {
21796
21820
  // maximum interval between request retries after exponential backoff
21797
21821
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
21798
21822
 
21799
- var logger$6 = console_with_prefix('batch');
21823
+ var logger$7 = console_with_prefix('batch');
21800
21824
 
21801
21825
  /**
21802
21826
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -21924,7 +21948,7 @@ RequestBatcher.prototype.sendRequestPromise = function(data, options) {
21924
21948
  */
21925
21949
  RequestBatcher.prototype.flush = function(options) {
21926
21950
  if (this.requestInProgress) {
21927
- logger$6.log('Flush: Request already in progress');
21951
+ logger$7.log('Flush: Request already in progress');
21928
21952
  return PromisePolyfill.resolve();
21929
21953
  }
21930
21954
 
@@ -22101,7 +22125,7 @@ RequestBatcher.prototype.flush = function(options) {
22101
22125
  if (options.unloading) {
22102
22126
  requestOptions.transport = 'sendBeacon';
22103
22127
  }
22104
- logger$6.log('MIXPANEL REQUEST:', dataForRequest);
22128
+ logger$7.log('MIXPANEL REQUEST:', dataForRequest);
22105
22129
  return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
22106
22130
  }, this))
22107
22131
  .catch(_.bind(function(err) {
@@ -22114,7 +22138,7 @@ RequestBatcher.prototype.flush = function(options) {
22114
22138
  * Log error to global logger and optional user-defined logger.
22115
22139
  */
22116
22140
  RequestBatcher.prototype.reportError = function(msg, err) {
22117
- logger$6.error.apply(logger$6.error, arguments);
22141
+ logger$7.error.apply(logger$7.error, arguments);
22118
22142
  if (this.errorReporter) {
22119
22143
  try {
22120
22144
  if (!(err instanceof Error)) {
@@ -22122,7 +22146,7 @@ RequestBatcher.prototype.reportError = function(msg, err) {
22122
22146
  }
22123
22147
  this.errorReporter(msg, err);
22124
22148
  } catch(err) {
22125
- logger$6.error(err);
22149
+ logger$7.error(err);
22126
22150
  }
22127
22151
  }
22128
22152
  };
@@ -22267,7 +22291,7 @@ var EVENT_HANDLER_ATTRIBUTES = [
22267
22291
 
22268
22292
  var MAX_DEPTH = 5;
22269
22293
 
22270
- var logger$5 = console_with_prefix('autocapture');
22294
+ var logger$6 = console_with_prefix('autocapture');
22271
22295
 
22272
22296
 
22273
22297
  function getClasses(el) {
@@ -22531,7 +22555,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
22531
22555
  return false;
22532
22556
  }
22533
22557
  } catch (err) {
22534
- logger$5.critical('Error while checking element in allowElementCallback', err);
22558
+ logger$6.critical('Error while checking element in allowElementCallback', err);
22535
22559
  return false;
22536
22560
  }
22537
22561
  }
@@ -22548,7 +22572,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
22548
22572
  return true;
22549
22573
  }
22550
22574
  } catch (err) {
22551
- logger$5.critical('Error while checking selector: ' + sel, err);
22575
+ logger$6.critical('Error while checking selector: ' + sel, err);
22552
22576
  }
22553
22577
  }
22554
22578
  return false;
@@ -22563,7 +22587,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
22563
22587
  return true;
22564
22588
  }
22565
22589
  } catch (err) {
22566
- logger$5.critical('Error while checking element in blockElementCallback', err);
22590
+ logger$6.critical('Error while checking element in blockElementCallback', err);
22567
22591
  return true;
22568
22592
  }
22569
22593
  }
@@ -22577,7 +22601,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
22577
22601
  return true;
22578
22602
  }
22579
22603
  } catch (err) {
22580
- logger$5.critical('Error while checking selector: ' + sel, err);
22604
+ logger$6.critical('Error while checking selector: ' + sel, err);
22581
22605
  }
22582
22606
  }
22583
22607
  }
@@ -23133,7 +23157,7 @@ function shouldMaskText(element, privacyConfig) {
23133
23157
  *
23134
23158
  */
23135
23159
 
23136
- var logger$4 = console_with_prefix('network-plugin');
23160
+ var logger$5 = console_with_prefix('network-plugin');
23137
23161
 
23138
23162
  /**
23139
23163
  * Get the time origin for converting performance timestamps to absolute timestamps.
@@ -23285,7 +23309,7 @@ function truncateBody(str) {
23285
23309
  return str;
23286
23310
  }
23287
23311
  if (str.length > MAX_BODY_SIZE) {
23288
- logger$4.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
23312
+ logger$5.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
23289
23313
  return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
23290
23314
  }
23291
23315
  return str;
@@ -23299,7 +23323,7 @@ function truncateBody(str) {
23299
23323
  */
23300
23324
  function initPerformanceObserver(cb, win, options) {
23301
23325
  if (!win.PerformanceObserver) {
23302
- logger$4.error('PerformanceObserver not supported');
23326
+ logger$5.error('PerformanceObserver not supported');
23303
23327
  return function() {
23304
23328
  //
23305
23329
  };
@@ -23452,7 +23476,7 @@ function getRequestPerformanceEntry(win, initiatorType, url, after, before, atte
23452
23476
  attempt = 0;
23453
23477
  }
23454
23478
  if (attempt > 10) {
23455
- logger$4.error('Cannot find performance entry');
23479
+ logger$5.error('Cannot find performance entry');
23456
23480
  return Promise.resolve(null);
23457
23481
  }
23458
23482
  var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
@@ -23573,7 +23597,7 @@ function initXhrObserver(cb, win, options) {
23573
23597
  )
23574
23598
  .then(function(entry) {
23575
23599
  if (!entry) {
23576
- logger$4.error('Failed to get performance entry for XHR request to ' + req.url);
23600
+ logger$5.error('Failed to get performance entry for XHR request to ' + req.url);
23577
23601
  return;
23578
23602
  }
23579
23603
  /** @type {NetworkRequest} */
@@ -23593,7 +23617,7 @@ function initXhrObserver(cb, win, options) {
23593
23617
  cb({ requests: [request] });
23594
23618
  })
23595
23619
  .catch(function(e) {
23596
- logger$4.error('Error recording XHR request to ' + req.url + ': ' + String(e));
23620
+ logger$5.error('Error recording XHR request to ' + req.url + ': ' + String(e));
23597
23621
  });
23598
23622
  });
23599
23623
 
@@ -23685,7 +23709,7 @@ function initFetchObserver(cb, win, options) {
23685
23709
  })
23686
23710
  .then(function(entry) {
23687
23711
  if (!entry) {
23688
- logger$4.error('Failed to get performance entry for fetch request to ' + req.url);
23712
+ logger$5.error('Failed to get performance entry for fetch request to ' + req.url);
23689
23713
  return;
23690
23714
  }
23691
23715
  /** @type {NetworkRequest} */
@@ -23705,7 +23729,7 @@ function initFetchObserver(cb, win, options) {
23705
23729
  cb({ requests: [request] });
23706
23730
  })
23707
23731
  .catch(function (e) {
23708
- logger$4.error('Error recording fetch request to ' + req.url + ': ' + String(e));
23732
+ logger$5.error('Error recording fetch request to ' + req.url + ': ' + String(e));
23709
23733
  });
23710
23734
 
23711
23735
  return originalFetchPromise;
@@ -23778,7 +23802,7 @@ var getRecordNetworkPlugin = function(options) {
23778
23802
  */
23779
23803
 
23780
23804
 
23781
- var logger$3 = console_with_prefix('recorder');
23805
+ var logger$4 = console_with_prefix('recorder');
23782
23806
  var CompressionStream = win['CompressionStream'];
23783
23807
 
23784
23808
  var RECORDER_BATCHER_LIB_CONFIG = {
@@ -23875,11 +23899,11 @@ var SessionRecording = function(options) {
23875
23899
 
23876
23900
  // disable persistence if localStorage is not supported
23877
23901
  // request-queue will automatically disable persistence if indexedDB fails to initialize
23878
- var usePersistence = localStorageSupported(options.sharedLockStorage, true) && !this.getConfig('disable_persistence');
23902
+ var usePersistence = localStorageSupported(options.sharedLockStorage || getLocalStorage(), true) && !this.getConfig('disable_persistence');
23879
23903
 
23880
23904
  // each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
23881
23905
  this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
23882
- this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
23906
+ this.queueStorage = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_EVENTS_STORE_NAME, RECORDER_VERSION_DATA);
23883
23907
  this.batcher = new RequestBatcher(this.batcherKey, {
23884
23908
  errorReporter: this.reportError.bind(this),
23885
23909
  flushOnlyOnInterval: true,
@@ -23958,14 +23982,14 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
23958
23982
  }
23959
23983
 
23960
23984
  if (this._stopRecording !== null) {
23961
- logger$3.log('Recording already in progress, skipping startRecording.');
23985
+ logger$4.log('Recording already in progress, skipping startRecording.');
23962
23986
  return;
23963
23987
  }
23964
23988
 
23965
23989
  this.recordMaxMs = this.getConfig('record_max_ms');
23966
23990
  if (this.recordMaxMs > MAX_RECORDING_MS) {
23967
23991
  this.recordMaxMs = MAX_RECORDING_MS;
23968
- logger$3.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
23992
+ logger$4.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
23969
23993
  }
23970
23994
 
23971
23995
  if (!this.maxExpires) {
@@ -24029,7 +24053,7 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
24029
24053
  );
24030
24054
  }
24031
24055
 
24032
- var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$3);
24056
+ var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$4);
24033
24057
 
24034
24058
  try {
24035
24059
  this._stopRecording = this._rrwebRecord({
@@ -24291,14 +24315,14 @@ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
24291
24315
 
24292
24316
 
24293
24317
  SessionRecording.prototype.reportError = function(msg, err) {
24294
- logger$3.error.apply(logger$3.error, arguments);
24318
+ logger$4.error.apply(logger$4.error, arguments);
24295
24319
  try {
24296
24320
  if (!err && !(msg instanceof Error)) {
24297
24321
  msg = new Error(msg);
24298
24322
  }
24299
24323
  this.getConfig('error_reporter')(msg, err);
24300
24324
  } catch(err) {
24301
- logger$3.error(err);
24325
+ logger$4.error(err);
24302
24326
  }
24303
24327
  };
24304
24328
 
@@ -24327,7 +24351,7 @@ SessionRecording.prototype._getRecordMinMs = function() {
24327
24351
  var configValue = this.getConfig('record_min_ms');
24328
24352
 
24329
24353
  if (configValue > MAX_VALUE_FOR_MIN_RECORDING_MS) {
24330
- logger$3.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
24354
+ logger$4.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
24331
24355
  return MAX_VALUE_FOR_MIN_RECORDING_MS;
24332
24356
  }
24333
24357
 
@@ -24369,7 +24393,7 @@ SessionRecording.prototype._getMaskFn = function(shouldMaskFn, privacyConfig) {
24369
24393
  */
24370
24394
  var RecordingRegistry = function (options) {
24371
24395
  /** @type {IDBStorageWrapper} */
24372
- this.idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
24396
+ this.idb = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_REGISTRY_STORE_NAME, RECORDER_VERSION_DATA);
24373
24397
  this.errorReporter = options.errorReporter;
24374
24398
  this.mixpanelInstance = options.mixpanelInstance;
24375
24399
  this.sharedLockStorage = options.sharedLockStorage;
@@ -24490,7 +24514,7 @@ RecordingRegistry.prototype.flushInactiveRecordings = function () {
24490
24514
  .catch(this.handleError.bind(this));
24491
24515
  };
24492
24516
 
24493
- var logger$2 = console_with_prefix('recorder');
24517
+ var logger$3 = console_with_prefix('recorder');
24494
24518
 
24495
24519
  /**
24496
24520
  * Recorder API: bundles rrweb and and exposes methods to start and stop recordings.
@@ -24506,7 +24530,7 @@ var MixpanelRecorder = function(mixpanelInstance, rrwebRecord, sharedLockStorage
24506
24530
  */
24507
24531
  this.recordingRegistry = new RecordingRegistry({
24508
24532
  mixpanelInstance: this.mixpanelInstance,
24509
- errorReporter: logger$2.error,
24533
+ errorReporter: logger$3.error,
24510
24534
  sharedLockStorage: sharedLockStorage
24511
24535
  });
24512
24536
  this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
@@ -24518,17 +24542,17 @@ var MixpanelRecorder = function(mixpanelInstance, rrwebRecord, sharedLockStorage
24518
24542
  MixpanelRecorder.prototype.startRecording = function(options) {
24519
24543
  options = options || {};
24520
24544
  if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
24521
- logger$2.log('Recording already in progress, skipping startRecording.');
24545
+ logger$3.log('Recording already in progress, skipping startRecording.');
24522
24546
  return;
24523
24547
  }
24524
24548
 
24525
24549
  var onIdleTimeout = function () {
24526
- logger$2.log('Idle timeout reached, restarting recording.');
24550
+ logger$3.log('Idle timeout reached, restarting recording.');
24527
24551
  this.resetRecording();
24528
24552
  }.bind(this);
24529
24553
 
24530
24554
  var onMaxLengthReached = function () {
24531
- logger$2.log('Max recording length reached, stopping recording.');
24555
+ logger$3.log('Max recording length reached, stopping recording.');
24532
24556
  this.resetRecording();
24533
24557
  }.bind(this);
24534
24558
 
@@ -24598,7 +24622,7 @@ MixpanelRecorder.prototype.resumeRecording = function (startNewIfInactive) {
24598
24622
  } else if (startNewIfInactive) {
24599
24623
  return this.startRecording({shouldStopBatcher: false});
24600
24624
  } else {
24601
- logger$2.log('No resumable recording found.');
24625
+ logger$3.log('No resumable recording found.');
24602
24626
  return null;
24603
24627
  }
24604
24628
  }.bind(this));
@@ -25263,7 +25287,7 @@ ShadowDOMObserver.prototype.observeShadowRoot = function(shadowRoot) {
25263
25287
  observer.observe(shadowRoot, this.observerConfig);
25264
25288
  this.shadowObservers.push(observer);
25265
25289
  } catch (e) {
25266
- logger$5.critical('Error while observing shadow root', e);
25290
+ logger$6.critical('Error while observing shadow root', e);
25267
25291
  }
25268
25292
  };
25269
25293
 
@@ -25274,7 +25298,7 @@ ShadowDOMObserver.prototype.start = function() {
25274
25298
  }
25275
25299
 
25276
25300
  if (!weakSetSupported()) {
25277
- logger$5.critical('Shadow DOM observation unavailable: WeakSet not supported');
25301
+ logger$6.critical('Shadow DOM observation unavailable: WeakSet not supported');
25278
25302
  return;
25279
25303
  }
25280
25304
 
@@ -25290,7 +25314,7 @@ ShadowDOMObserver.prototype.stop = function() {
25290
25314
  try {
25291
25315
  this.shadowObservers[i].disconnect();
25292
25316
  } catch (e) {
25293
- logger$5.critical('Error while disconnecting shadow DOM observer', e);
25317
+ logger$6.critical('Error while disconnecting shadow DOM observer', e);
25294
25318
  }
25295
25319
  }
25296
25320
  this.shadowObservers = [];
@@ -25478,7 +25502,7 @@ DeadClickTracker.prototype.startTracking = function() {
25478
25502
 
25479
25503
  this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
25480
25504
  } catch (e) {
25481
- logger$5.critical('Error while setting up mutation observer', e);
25505
+ logger$6.critical('Error while setting up mutation observer', e);
25482
25506
  }
25483
25507
  }
25484
25508
 
@@ -25493,7 +25517,7 @@ DeadClickTracker.prototype.startTracking = function() {
25493
25517
  );
25494
25518
  this.shadowDOMObserver.start();
25495
25519
  } catch (e) {
25496
- logger$5.critical('Error while setting up shadow DOM observer', e);
25520
+ logger$6.critical('Error while setting up shadow DOM observer', e);
25497
25521
  this.shadowDOMObserver = null;
25498
25522
  }
25499
25523
  }
@@ -25520,7 +25544,7 @@ DeadClickTracker.prototype.stopTracking = function() {
25520
25544
  try {
25521
25545
  listener.target.removeEventListener(listener.event, listener.handler, listener.options);
25522
25546
  } catch (e) {
25523
- logger$5.critical('Error while removing event listener', e);
25547
+ logger$6.critical('Error while removing event listener', e);
25524
25548
  }
25525
25549
  }
25526
25550
  this.eventListeners = [];
@@ -25529,7 +25553,7 @@ DeadClickTracker.prototype.stopTracking = function() {
25529
25553
  try {
25530
25554
  this.mutationObserver.disconnect();
25531
25555
  } catch (e) {
25532
- logger$5.critical('Error while disconnecting mutation observer', e);
25556
+ logger$6.critical('Error while disconnecting mutation observer', e);
25533
25557
  }
25534
25558
  this.mutationObserver = null;
25535
25559
  }
@@ -25538,7 +25562,7 @@ DeadClickTracker.prototype.stopTracking = function() {
25538
25562
  try {
25539
25563
  this.shadowDOMObserver.stop();
25540
25564
  } catch (e) {
25541
- logger$5.critical('Error while stopping shadow DOM observer', e);
25565
+ logger$6.critical('Error while stopping shadow DOM observer', e);
25542
25566
  }
25543
25567
  this.shadowDOMObserver = null;
25544
25568
  }
@@ -25616,7 +25640,7 @@ var Autocapture = function(mp) {
25616
25640
 
25617
25641
  Autocapture.prototype.init = function() {
25618
25642
  if (!minDOMApisSupported()) {
25619
- logger$5.critical('Autocapture unavailable: missing required DOM APIs');
25643
+ logger$6.critical('Autocapture unavailable: missing required DOM APIs');
25620
25644
  return;
25621
25645
  }
25622
25646
  this.initPageListeners();
@@ -25656,7 +25680,7 @@ Autocapture.prototype.currentUrlBlocked = function() {
25656
25680
  try {
25657
25681
  return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
25658
25682
  } catch (err) {
25659
- logger$5.critical('Error while checking block URL regexes: ', err);
25683
+ logger$6.critical('Error while checking block URL regexes: ', err);
25660
25684
  return true;
25661
25685
  }
25662
25686
  }
@@ -25669,7 +25693,7 @@ Autocapture.prototype.currentUrlBlocked = function() {
25669
25693
  try {
25670
25694
  return urlMatchesRegexList(currentUrl, blockUrlRegexes);
25671
25695
  } catch (err) {
25672
- logger$5.critical('Error while checking block URL regexes: ', err);
25696
+ logger$6.critical('Error while checking block URL regexes: ', err);
25673
25697
  return true;
25674
25698
  }
25675
25699
  };
@@ -25807,7 +25831,7 @@ Autocapture.prototype._initScrollDepthTracking = function() {
25807
25831
  return;
25808
25832
  }
25809
25833
 
25810
- logger$5.log('Initializing scroll depth tracking');
25834
+ logger$6.log('Initializing scroll depth tracking');
25811
25835
 
25812
25836
  this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
25813
25837
 
@@ -25833,7 +25857,7 @@ Autocapture.prototype.initClickTracking = function() {
25833
25857
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
25834
25858
  return;
25835
25859
  }
25836
- logger$5.log('Initializing click tracking');
25860
+ logger$6.log('Initializing click tracking');
25837
25861
 
25838
25862
  this.listenerClick = function(ev) {
25839
25863
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
@@ -25852,7 +25876,7 @@ Autocapture.prototype.initDeadClickTracking = function() {
25852
25876
  return;
25853
25877
  }
25854
25878
 
25855
- logger$5.log('Initializing dead click tracking');
25879
+ logger$6.log('Initializing dead click tracking');
25856
25880
  if (!this._deadClickTracker) {
25857
25881
  this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
25858
25882
  this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
@@ -25886,7 +25910,7 @@ Autocapture.prototype.initInputTracking = function() {
25886
25910
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
25887
25911
  return;
25888
25912
  }
25889
- logger$5.log('Initializing input tracking');
25913
+ logger$6.log('Initializing input tracking');
25890
25914
 
25891
25915
  this.listenerChange = function(ev) {
25892
25916
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
@@ -25900,14 +25924,15 @@ Autocapture.prototype.initInputTracking = function() {
25900
25924
  Autocapture.prototype.initPageviewTracking = function() {
25901
25925
  win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
25902
25926
 
25903
- if (!this.pageviewTrackingConfig()) {
25927
+ if (!this.pageviewTrackingConfig() && !this.mp.get_config('record_heatmap_data')) {
25904
25928
  return;
25905
25929
  }
25906
- logger$5.log('Initializing pageview tracking');
25930
+ logger$6.log('Initializing pageview tracking');
25907
25931
 
25908
25932
  var previousTrackedUrl = '';
25909
25933
  var tracked = false;
25910
- if (!this.currentUrlBlocked()) {
25934
+ // Track initial pageview if pageview tracking enabled OR heatmap recording is active
25935
+ if ((this.pageviewTrackingConfig() || this.mp.is_recording_heatmap_data()) && !this.currentUrlBlocked()) {
25911
25936
  tracked = this.mp.track_pageview(DEFAULT_PROPS);
25912
25937
  }
25913
25938
  if (tracked) {
@@ -25923,6 +25948,10 @@ Autocapture.prototype.initPageviewTracking = function() {
25923
25948
  var shouldTrack = false;
25924
25949
  var didPathChange = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
25925
25950
  var trackPageviewOption = this.pageviewTrackingConfig();
25951
+ if (!trackPageviewOption && this.mp.is_recording_heatmap_data()) {
25952
+ trackPageviewOption = PAGEVIEW_OPTION_FULL_URL;
25953
+ }
25954
+
25926
25955
  if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
25927
25956
  shouldTrack = currentUrl !== previousTrackedUrl;
25928
25957
  } else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
@@ -25938,7 +25967,7 @@ Autocapture.prototype.initPageviewTracking = function() {
25938
25967
  }
25939
25968
  if (didPathChange) {
25940
25969
  this.lastScrollCheckpoint = 0;
25941
- logger$5.log('Path change: re-initializing scroll depth checkpoints');
25970
+ logger$6.log('Path change: re-initializing scroll depth checkpoints');
25942
25971
  }
25943
25972
  }
25944
25973
  }.bind(this));
@@ -25953,7 +25982,7 @@ Autocapture.prototype.initRageClickTracking = function() {
25953
25982
  return;
25954
25983
  }
25955
25984
 
25956
- logger$5.log('Initializing rage click tracking');
25985
+ logger$6.log('Initializing rage click tracking');
25957
25986
  if (!this._rageClickTracker) {
25958
25987
  this._rageClickTracker = new RageClickTracker();
25959
25988
  }
@@ -25983,7 +26012,7 @@ Autocapture.prototype.initScrollTracking = function() {
25983
26012
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
25984
26013
  return;
25985
26014
  }
25986
- logger$5.log('Initializing scroll tracking');
26015
+ logger$6.log('Initializing scroll tracking');
25987
26016
  this.lastScrollCheckpoint = 0;
25988
26017
 
25989
26018
  var scrollTrackFunction = function() {
@@ -26020,7 +26049,7 @@ Autocapture.prototype.initScrollTracking = function() {
26020
26049
  }
26021
26050
  }
26022
26051
  } catch (err) {
26023
- logger$5.critical('Error while calculating scroll percentage', err);
26052
+ logger$6.critical('Error while calculating scroll percentage', err);
26024
26053
  }
26025
26054
  if (shouldTrack) {
26026
26055
  this.mp.track(MP_EV_SCROLL, props);
@@ -26038,7 +26067,7 @@ Autocapture.prototype.initSubmitTracking = function() {
26038
26067
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
26039
26068
  return;
26040
26069
  }
26041
- logger$5.log('Initializing submit tracking');
26070
+ logger$6.log('Initializing submit tracking');
26042
26071
 
26043
26072
  this.listenerSubmit = function(ev) {
26044
26073
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
@@ -26060,7 +26089,7 @@ Autocapture.prototype.initPageLeaveTracking = function() {
26060
26089
  return;
26061
26090
  }
26062
26091
 
26063
- logger$5.log('Initializing page visibility tracking.');
26092
+ logger$6.log('Initializing page visibility tracking.');
26064
26093
  this._initScrollDepthTracking();
26065
26094
  var previousTrackedUrl = _.info.currentUrl();
26066
26095
 
@@ -26145,10 +26174,183 @@ var getTargetingPromise = function(loadExtraBundle, targetingSrc) {
26145
26174
  return win[TARGETING_GLOBAL_NAME];
26146
26175
  };
26147
26176
 
26177
+ var logger$2 = console_with_prefix('flags');
26178
+
26179
+ var MIXPANEL_FLAGS_DB_NAME = 'mixpanelFlagsDb';
26180
+ var FLAGS_STORE_NAME = 'mixpanelFlags';
26181
+
26182
+ // Keeping these two properties closeby, as adding additional stores to a DB in IndexedDB requires a version increment
26183
+ var FLAGS_VERSION_DATA = { version: 1, storeNames: [FLAGS_STORE_NAME] };
26184
+
26185
+ var PERSISTED_VARIANTS_KEY_PREFIX = 'persisted_variants_for_';
26186
+ var DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
26187
+
26188
+ var VariantLookupPolicy = Object.freeze({
26189
+ NETWORK_ONLY: 'networkOnly',
26190
+ NETWORK_FIRST: 'networkFirst',
26191
+ PERSISTENCE_UNTIL_NETWORK_SUCCESS: 'persistenceUntilNetworkSuccess'
26192
+ });
26193
+
26194
+ var VALID_POLICIES = [
26195
+ VariantLookupPolicy.NETWORK_ONLY,
26196
+ VariantLookupPolicy.NETWORK_FIRST,
26197
+ VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS
26198
+ ];
26199
+
26200
+ /**
26201
+ * Module for handling the storage and retrieval of persisted feature flag variants.
26202
+ */
26203
+ var FeatureFlagPersistence = function(persistenceConfig, token, isGloballyDisabled) {
26204
+ this.idb = new IDBStorageWrapper(MIXPANEL_FLAGS_DB_NAME, FLAGS_STORE_NAME, FLAGS_VERSION_DATA);
26205
+ this.persistenceConfig = persistenceConfig;
26206
+ this.persistedVariantsKey = PERSISTED_VARIANTS_KEY_PREFIX + token;
26207
+ this.isGloballyDisabled = isGloballyDisabled || function() { return false; };
26208
+ };
26209
+
26210
+ FeatureFlagPersistence.prototype.getPolicy = function() {
26211
+ if (this.isGloballyDisabled() || !this._isConfigValid()) {
26212
+ return VariantLookupPolicy.NETWORK_ONLY;
26213
+ }
26214
+ return this.persistenceConfig['variantLookupPolicy'];
26215
+ };
26216
+
26217
+ FeatureFlagPersistence.prototype.getTtlMs = function() {
26218
+ if (!this._isConfigValid()) {
26219
+ return DEFAULT_TTL_MS;
26220
+ }
26221
+ var configuredTtl = this.persistenceConfig['persistenceTtlMs'];
26222
+ return (configuredTtl === undefined || configuredTtl === null) ? DEFAULT_TTL_MS : configuredTtl;
26223
+ };
26224
+
26225
+ FeatureFlagPersistence.prototype._isConfigValid = function() {
26226
+ var config = this.persistenceConfig;
26227
+ if (!config) {
26228
+ return false;
26229
+ }
26230
+
26231
+ if (VALID_POLICIES.indexOf(config['variantLookupPolicy']) === -1) {
26232
+ logger$2.error('Invalid variantLookupPolicy:', config['variantLookupPolicy']);
26233
+ return false;
26234
+ }
26235
+
26236
+ if (config['persistenceTtlMs'] !== undefined &&
26237
+ config['persistenceTtlMs'] !== null &&
26238
+ config['persistenceTtlMs'] <= 0) {
26239
+ logger$2.error('If provided, persistenceTtlMs must be a positive number. Provided value:', config['persistenceTtlMs']);
26240
+ return false;
26241
+ }
26242
+
26243
+ return true;
26244
+ };
26245
+
26246
+ FeatureFlagPersistence.prototype.loadFlagsFromStorage = function(context) {
26247
+ var clearAndReturnNull = _.bind(function() {
26248
+ return this.clear().then(function() { return null; }).catch(function() { return null; });
26249
+ }, this);
26250
+
26251
+ if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
26252
+ return clearAndReturnNull();
26253
+ }
26254
+
26255
+ var ttlMs = this.getTtlMs();
26256
+
26257
+ return this.idb.init().then(_.bind(function() {
26258
+ return this.idb.getItem(this.persistedVariantsKey);
26259
+ }, this)).then(_.bind(function(data) {
26260
+ if (!data) {
26261
+ logger$2.log('No persisted variants found in IndexedDB');
26262
+ return null;
26263
+ }
26264
+
26265
+ if (ttlMs && Date.now() - data['persistedAt'] >= ttlMs) {
26266
+ logger$2.log('Persisted variants are expiring');
26267
+ return null;
26268
+ }
26269
+
26270
+ if (!context || data['distinctId'] !== context['distinct_id']) {
26271
+ logger$2.log('Persisted variants found, but for a different distinct_id so clearing.');
26272
+ return clearAndReturnNull();
26273
+ }
26274
+
26275
+ var persistedFlags = new Map();
26276
+ _.each(data['flagVariants'], function(variantData, key) {
26277
+ persistedFlags.set(key, {
26278
+ 'key': variantData['variant_key'],
26279
+ 'value': variantData['variant_value'],
26280
+ 'experiment_id': variantData['experiment_id'],
26281
+ 'is_experiment_active': variantData['is_experiment_active'],
26282
+ 'is_qa_tester': variantData['is_qa_tester'],
26283
+ 'variant_source': 'persistence',
26284
+ 'persisted_at_in_ms': data['persistedAt'],
26285
+ 'ttl_in_ms': ttlMs
26286
+ });
26287
+ });
26288
+
26289
+ logger$2.log('Loaded', persistedFlags.size, 'variants from IndexedDB for distinct_id', data['distinctId']);
26290
+
26291
+ return {
26292
+ flags: persistedFlags,
26293
+ pendingFirstTimeEvents: data['pendingFirstTimeEvents'] || {},
26294
+ persistedAtMs: data['persistedAt'],
26295
+ ttlMs: ttlMs
26296
+ };
26297
+ }, this)).catch(_.bind(function(error) {
26298
+ logger$2.error('Failed to load persisted variants from IndexedDB, so clearing', error);
26299
+ return clearAndReturnNull();
26300
+ }, this));
26301
+ };
26302
+
26303
+ FeatureFlagPersistence.prototype.save = function(context, flagsMap, pendingFirstTimeEvents) {
26304
+ if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
26305
+ return Promise.resolve();
26306
+ }
26307
+
26308
+ var flagVariants = {};
26309
+ flagsMap.forEach(function(variant, key) {
26310
+ flagVariants[key] = {
26311
+ 'variant_key': variant['key'],
26312
+ 'variant_value': variant['value'],
26313
+ 'experiment_id': variant['experiment_id'],
26314
+ 'is_experiment_active': variant['is_experiment_active'],
26315
+ 'is_qa_tester': variant['is_qa_tester']
26316
+ };
26317
+ });
26318
+
26319
+ var data = {
26320
+ 'persistedAt': Date.now(),
26321
+ 'distinctId': context && context['distinct_id'],
26322
+ 'context': context,
26323
+ 'flagVariants': flagVariants,
26324
+ 'pendingFirstTimeEvents': pendingFirstTimeEvents || {}
26325
+ };
26326
+
26327
+ return this.idb.init().then(_.bind(function() {
26328
+ return this.idb.setItem(this.persistedVariantsKey, data);
26329
+ }, this)).then(function() {
26330
+ logger$2.log('Saved', flagsMap.size, 'variants to IndexedDB for distinct_id', data['distinctId']);
26331
+ }).catch(function(error) {
26332
+ logger$2.error('Failed to persist variants to IndexedDB:', error);
26333
+ });
26334
+ };
26335
+
26336
+ FeatureFlagPersistence.prototype.clear = function() {
26337
+ if (this.isGloballyDisabled()) {
26338
+ return Promise.resolve();
26339
+ }
26340
+ return this.idb.init().then(_.bind(function() {
26341
+ return this.idb.removeItem(this.persistedVariantsKey);
26342
+ }, this)).then(function() {
26343
+ logger$2.log('Cleared persisted variants from IndexedDB');
26344
+ }).catch(function(error) {
26345
+ logger$2.error('Failed to clear persisted variants from IndexedDB:', error);
26346
+ });
26347
+ };
26348
+
26148
26349
  var logger$1 = console_with_prefix('flags');
26149
26350
  var FLAGS_CONFIG_KEY = 'flags';
26150
26351
 
26151
26352
  var CONFIG_CONTEXT = 'context';
26353
+ var CONFIG_PERSISTENCE = 'persistence';
26152
26354
  var CONFIG_DEFAULTS = {};
26153
26355
  CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
26154
26356
 
@@ -26171,6 +26373,13 @@ var getFlagKeyFromPendingEventKey = function(eventKey) {
26171
26373
  return eventKey.split(':')[0];
26172
26374
  };
26173
26375
 
26376
+ var withFallbackSource = function(fallback) {
26377
+ if (_.isObject(fallback)) {
26378
+ return _.extend({}, fallback, {'variant_source': 'fallback'});
26379
+ }
26380
+ return {'value': fallback, 'variant_source': 'fallback'};
26381
+ };
26382
+
26174
26383
  /**
26175
26384
  * FeatureFlagManager: support for Mixpanel's feature flagging product
26176
26385
  * @constructor
@@ -26193,11 +26402,63 @@ FeatureFlagManager.prototype.init = function() {
26193
26402
  }
26194
26403
 
26195
26404
  this.flags = null;
26196
- this.fetchFlags();
26197
-
26198
26405
  this.trackedFeatures = new Set();
26199
26406
  this.pendingFirstTimeEvents = {};
26200
26407
  this.activatedFirstTimeEvents = {};
26408
+ this._loadedPersistedAtMs = null;
26409
+ this._loadedTtlMs = null;
26410
+
26411
+ this.persistence = new FeatureFlagPersistence(
26412
+ this.getConfig(CONFIG_PERSISTENCE),
26413
+ this.getMpConfig('token'),
26414
+ _.bind(function() { return this.getMpConfig('disable_persistence'); }, this)
26415
+ );
26416
+
26417
+ this.persistenceLoadedPromise = this.persistence.loadFlagsFromStorage(this._buildContext())
26418
+ .then(_.bind(function(loaded) {
26419
+ if (loaded) {
26420
+ this.flags = loaded.flags;
26421
+ this.pendingFirstTimeEvents = loaded.pendingFirstTimeEvents;
26422
+ this._loadedPersistedAtMs = loaded.persistedAtMs;
26423
+ this._loadedTtlMs = loaded.ttlMs;
26424
+ }
26425
+ }, this));
26426
+
26427
+ return this.persistenceLoadedPromise
26428
+ .then(_.bind(function() {
26429
+ return this.fetchFlags();
26430
+ }, this))
26431
+ .catch(function() {
26432
+ logger$1.error('Error initializing feature flags');
26433
+ });
26434
+ };
26435
+
26436
+ FeatureFlagManager.prototype._buildContext = function() {
26437
+ return _.extend(
26438
+ {'distinct_id': this.getMpProperty('distinct_id'), 'device_id': this.getMpProperty('$device_id')},
26439
+ this.getConfig(CONFIG_CONTEXT)
26440
+ );
26441
+ };
26442
+
26443
+ FeatureFlagManager.prototype.reset = function() {
26444
+ if (!this.persistence) {
26445
+ return Promise.resolve();
26446
+ }
26447
+
26448
+ this.flags = null;
26449
+ this.pendingFirstTimeEvents = {};
26450
+ this.activatedFirstTimeEvents = {};
26451
+ this.trackedFeatures = new Set();
26452
+ this.fetchPromise = null;
26453
+ this._fetchInProgressStartTime = null;
26454
+ this._loadedPersistedAtMs = null;
26455
+ this._loadedTtlMs = null;
26456
+
26457
+ return this.persistence.clear().then(_.bind(function() {
26458
+ return this.fetchFlags();
26459
+ }, this)).catch(function() {
26460
+ logger$1.error('Error during flags reset');
26461
+ });
26201
26462
  };
26202
26463
 
26203
26464
  FeatureFlagManager.prototype.getFullConfig = function() {
@@ -26234,8 +26495,12 @@ FeatureFlagManager.prototype.updateContext = function(newContext, options) {
26234
26495
  var oldContext = (options && options['replace']) ? {} : this.getConfig(CONFIG_CONTEXT);
26235
26496
  ffConfig[CONFIG_CONTEXT] = _.extend({}, oldContext, newContext);
26236
26497
 
26237
- this.setMpConfig(FLAGS_CONFIG_KEY, ffConfig);
26238
- return this.fetchFlags();
26498
+ var configUpdate = {};
26499
+ configUpdate[FLAGS_CONFIG_KEY] = ffConfig;
26500
+ this.setMpConfig(configUpdate);
26501
+ return this.fetchFlags().catch(function() {
26502
+ logger$1.error('Error fetching flags during updateContext');
26503
+ });
26239
26504
  };
26240
26505
 
26241
26506
  FeatureFlagManager.prototype.areFlagsReady = function() {
@@ -26250,12 +26515,11 @@ FeatureFlagManager.prototype.fetchFlags = function() {
26250
26515
  return Promise.resolve();
26251
26516
  }
26252
26517
 
26253
- var distinctId = this.getMpProperty('distinct_id');
26254
- var deviceId = this.getMpProperty('$device_id');
26518
+ var context = this._buildContext();
26519
+ var distinctId = context['distinct_id'];
26255
26520
  var traceparent = generateTraceparent();
26256
26521
  logger$1.log('Fetching flags for distinct ID: ' + distinctId);
26257
26522
 
26258
- var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
26259
26523
  var searchParams = new URLSearchParams();
26260
26524
  searchParams.set('context', JSON.stringify(context));
26261
26525
  searchParams.set('token', this.getMpConfig('token'));
@@ -26272,96 +26536,116 @@ FeatureFlagManager.prototype.fetchFlags = function() {
26272
26536
  }
26273
26537
  }).then(function(response) {
26274
26538
  this.markFetchComplete();
26275
- return response.json().then(function(responseBody) {
26276
- var responseFlags = responseBody['flags'];
26277
- if (!responseFlags) {
26278
- throw new Error('No flags in API response');
26279
- }
26280
- var flags = new Map();
26281
- var pendingFirstTimeEvents = {};
26282
-
26283
- // Process flags from response
26284
- _.each(responseFlags, function(data, key) {
26285
- // Check if this flag has any activated first-time events this session
26286
- var hasActivatedEvent = false;
26287
- var prefix = key + ':';
26288
- _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26289
- if (eventKey.startsWith(prefix)) {
26290
- hasActivatedEvent = true;
26291
- }
26292
- });
26539
+ return response.json();
26540
+ }.bind(this)).then(function(responseBody) {
26541
+ var responseFlags = responseBody['flags'];
26542
+ if (!responseFlags) {
26543
+ throw new Error('No flags in API response');
26544
+ }
26545
+ var flags = new Map();
26546
+ var pendingFirstTimeEvents = {};
26547
+
26548
+ // Process flags from response
26549
+ _.each(responseFlags, function(data, key) {
26550
+ // Check if this flag has any activated first-time events this session
26551
+ var hasActivatedEvent = false;
26552
+ var prefix = key + ':';
26553
+ _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26554
+ if (eventKey.startsWith(prefix)) {
26555
+ hasActivatedEvent = true;
26556
+ }
26557
+ });
26293
26558
 
26294
- if (hasActivatedEvent) {
26295
- // Preserve the activated variant, don't overwrite with server's current variant
26296
- var currentFlag = this.flags && this.flags.get(key);
26297
- if (currentFlag) {
26298
- flags.set(key, currentFlag);
26299
- }
26300
- } else {
26301
- // Use server's current variant
26302
- flags.set(key, {
26303
- 'key': data['variant_key'],
26304
- 'value': data['variant_value'],
26305
- 'experiment_id': data['experiment_id'],
26306
- 'is_experiment_active': data['is_experiment_active'],
26307
- 'is_qa_tester': data['is_qa_tester']
26308
- });
26559
+ if (hasActivatedEvent) {
26560
+ // Preserve the activated variant, don't overwrite with server's current variant
26561
+ var currentFlag = this.flags && this.flags.get(key);
26562
+ if (currentFlag) {
26563
+ flags.set(key, currentFlag);
26309
26564
  }
26310
- }, this);
26565
+ } else {
26566
+ // Use server's current variant
26567
+ flags.set(key, {
26568
+ 'key': data['variant_key'],
26569
+ 'value': data['variant_value'],
26570
+ 'experiment_id': data['experiment_id'],
26571
+ 'is_experiment_active': data['is_experiment_active'],
26572
+ 'is_qa_tester': data['is_qa_tester'],
26573
+ 'variant_source': 'network'
26574
+ });
26575
+ }
26576
+ }, this);
26311
26577
 
26312
- // Process top-level pending_first_time_events array
26313
- var topLevelDefinitions = responseBody['pending_first_time_events'];
26314
- if (topLevelDefinitions && topLevelDefinitions.length > 0) {
26315
- _.each(topLevelDefinitions, function(def) {
26316
- var flagKey = def['flag_key'];
26317
- var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
26578
+ // Process top-level pending_first_time_events array
26579
+ var topLevelDefinitions = responseBody['pending_first_time_events'];
26580
+ if (topLevelDefinitions && topLevelDefinitions.length > 0) {
26581
+ _.each(topLevelDefinitions, function(def) {
26582
+ var flagKey = def['flag_key'];
26583
+ var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
26318
26584
 
26319
- // Skip if this specific event has already been activated this session
26320
- if (this.activatedFirstTimeEvents[eventKey]) {
26321
- return;
26322
- }
26585
+ // Skip if this specific event has already been activated this session
26586
+ if (this.activatedFirstTimeEvents[eventKey]) {
26587
+ return;
26588
+ }
26323
26589
 
26324
- // Store pending event definition using composite key
26325
- pendingFirstTimeEvents[eventKey] = {
26326
- 'flag_key': flagKey,
26327
- 'flag_id': def['flag_id'],
26328
- 'project_id': def['project_id'],
26329
- 'first_time_event_hash': def['first_time_event_hash'],
26330
- 'event_name': def['event_name'],
26331
- 'property_filters': def['property_filters'],
26332
- 'pending_variant': def['pending_variant']
26333
- };
26334
- }, this);
26335
- }
26590
+ // Store pending event definition using composite key
26591
+ pendingFirstTimeEvents[eventKey] = {
26592
+ 'flag_key': flagKey,
26593
+ 'flag_id': def['flag_id'],
26594
+ 'project_id': def['project_id'],
26595
+ 'first_time_event_hash': def['first_time_event_hash'],
26596
+ 'event_name': def['event_name'],
26597
+ 'property_filters': def['property_filters'],
26598
+ 'pending_variant': def['pending_variant']
26599
+ };
26600
+ }, this);
26601
+ }
26336
26602
 
26337
- // Preserve any activated orphaned flags (flags that were activated but are no longer in response)
26338
- if (this.activatedFirstTimeEvents) {
26339
- _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26340
- var flagKey = getFlagKeyFromPendingEventKey(eventKey);
26341
- if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
26342
- // Keep the activated flag even though it's not in the new response
26343
- flags.set(flagKey, this.flags.get(flagKey));
26344
- }
26345
- }, this);
26346
- }
26603
+ // Preserve any activated orphaned flags (flags that were activated but are no longer in response)
26604
+ if (this.activatedFirstTimeEvents) {
26605
+ _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26606
+ var flagKey = getFlagKeyFromPendingEventKey(eventKey);
26607
+ if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
26608
+ // Keep the activated flag even though it's not in the new response
26609
+ flags.set(flagKey, this.flags.get(flagKey));
26610
+ }
26611
+ }, this);
26612
+ }
26347
26613
 
26348
- this.flags = flags;
26349
- this.pendingFirstTimeEvents = pendingFirstTimeEvents;
26350
- this._traceparent = traceparent;
26614
+ this.flags = flags;
26615
+ this.trackedFeatures = new Set();
26616
+ this.pendingFirstTimeEvents = pendingFirstTimeEvents;
26617
+ this._loadedPersistedAtMs = null;
26618
+ this._loadedTtlMs = null;
26619
+ this._traceparent = traceparent;
26351
26620
 
26352
- this._loadTargetingIfNeeded();
26353
- }.bind(this)).catch(function(error) {
26354
- this.markFetchComplete();
26355
- logger$1.error(error);
26356
- }.bind(this));
26621
+ this._loadTargetingIfNeeded();
26622
+
26623
+ this.persistence.save(context, this.flags, this.pendingFirstTimeEvents);
26357
26624
  }.bind(this)).catch(function(error) {
26358
- this.markFetchComplete();
26625
+ if (this._fetchInProgressStartTime) {
26626
+ this.markFetchComplete();
26627
+ }
26359
26628
  logger$1.error(error);
26629
+ throw error;
26360
26630
  }.bind(this));
26361
26631
 
26362
26632
  return this.fetchPromise;
26363
26633
  };
26364
26634
 
26635
+ FeatureFlagManager.prototype.loadFlags = function() {
26636
+ if (!this.isSystemEnabled()) {
26637
+ return Promise.resolve();
26638
+ }
26639
+ if (!this.trackedFeatures) {
26640
+ logger$1.error('loadFlags called before init');
26641
+ return Promise.resolve();
26642
+ }
26643
+ if (this._fetchInProgressStartTime) {
26644
+ return this.fetchPromise;
26645
+ }
26646
+ return this.fetchFlags();
26647
+ };
26648
+
26365
26649
  FeatureFlagManager.prototype.markFetchComplete = function() {
26366
26650
  if (!this._fetchInProgressStartTime) {
26367
26651
  logger$1.error('Fetch in progress started time not set, cannot mark fetch complete');
@@ -26496,6 +26780,7 @@ FeatureFlagManager.prototype._processFirstTimeEventCheck = function(eventName, p
26496
26780
  };
26497
26781
 
26498
26782
  this.flags.set(flagKey, newVariant);
26783
+ this.trackedFeatures.delete(flagKey);
26499
26784
  this.activatedFirstTimeEvents[eventKey] = true;
26500
26785
 
26501
26786
  this.recordFirstTimeEvent(
@@ -26545,35 +26830,106 @@ FeatureFlagManager.prototype.recordFirstTimeEvent = function(flagId, projectId,
26545
26830
  };
26546
26831
 
26547
26832
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
26548
- if (!this.fetchPromise) {
26833
+ if (!this.persistenceLoadedPromise) {
26549
26834
  return new Promise(function(resolve) {
26550
26835
  logger$1.critical('Feature Flags not initialized');
26551
- resolve(fallback);
26836
+ resolve(withFallbackSource(fallback));
26552
26837
  });
26553
26838
  }
26554
26839
 
26555
- return this.fetchPromise.then(function() {
26556
- return this.getVariantSync(featureName, fallback);
26557
- }.bind(this)).catch(function(error) {
26558
- logger$1.error(error);
26559
- return fallback;
26560
- });
26840
+ var policy = this.persistence.getPolicy();
26841
+
26842
+ return this.persistenceLoadedPromise.then(_.bind(function() {
26843
+ // 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.
26844
+ if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
26845
+ if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
26846
+ return this.getVariantSync(featureName, fallback);
26847
+ }
26848
+ if (!this.fetchPromise) {
26849
+ return withFallbackSource(fallback);
26850
+ }
26851
+ return this.fetchPromise.then(_.bind(function() {
26852
+ return this.getVariantSync(featureName, fallback);
26853
+ }, this)).catch(function(error) {
26854
+ logger$1.error(error);
26855
+ return withFallbackSource(fallback);
26856
+ });
26857
+ }
26858
+
26859
+ var serve = _.bind(function() { return this.getVariantSync(featureName, fallback); }, this);
26860
+ if (!this.fetchPromise) {
26861
+ return withFallbackSource(fallback);
26862
+ }
26863
+ return this.fetchPromise.then(serve).catch(serve);
26864
+ }, this));
26865
+ };
26866
+
26867
+ FeatureFlagManager.prototype._loadedPersistenceIsStale = function() {
26868
+ if (!this._loadedPersistedAtMs || !this._loadedTtlMs) {
26869
+ return false;
26870
+ }
26871
+ return Date.now() - this._loadedPersistedAtMs >= this._loadedTtlMs;
26561
26872
  };
26562
26873
 
26563
26874
  FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
26875
+ if (this._loadedPersistenceIsStale()) {
26876
+ logger$1.log('Loaded persisted variants are past TTL so returning fallback for "' + featureName + '"');
26877
+ return withFallbackSource(fallback);
26878
+ }
26564
26879
  if (!this.areFlagsReady()) {
26565
26880
  logger$1.log('Flags not loaded yet');
26566
- return fallback;
26881
+ return withFallbackSource(fallback);
26567
26882
  }
26568
26883
  var feature = this.flags.get(featureName);
26569
26884
  if (!feature) {
26570
26885
  logger$1.log('No flag found: "' + featureName + '"');
26571
- return fallback;
26886
+ return withFallbackSource(fallback);
26572
26887
  }
26573
26888
  this.trackFeatureCheck(featureName, feature);
26574
26889
  return feature;
26575
26890
  };
26576
26891
 
26892
+ FeatureFlagManager.prototype.getAllVariants = function() {
26893
+ if (!this.persistenceLoadedPromise) {
26894
+ logger$1.critical('Feature Flags not initialized');
26895
+ return Promise.resolve(new Map());
26896
+ }
26897
+
26898
+ var policy = this.persistence.getPolicy();
26899
+
26900
+ return this.persistenceLoadedPromise.then(_.bind(function() {
26901
+ // 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.
26902
+ if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
26903
+ if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
26904
+ return this.getAllVariantsSync();
26905
+ }
26906
+ if (!this.fetchPromise) {
26907
+ return new Map();
26908
+ }
26909
+ return this.fetchPromise.then(_.bind(function() {
26910
+ return this.getAllVariantsSync();
26911
+ }, this)).catch(function(error) {
26912
+ logger$1.error(error);
26913
+ return new Map();
26914
+ });
26915
+ }
26916
+
26917
+ var serve = _.bind(this.getAllVariantsSync, this);
26918
+ if (!this.fetchPromise) {
26919
+ return new Map();
26920
+ }
26921
+ return this.fetchPromise.then(serve).catch(serve);
26922
+ }, this));
26923
+ };
26924
+
26925
+ FeatureFlagManager.prototype.getAllVariantsSync = function() {
26926
+ if (this._loadedPersistenceIsStale()) {
26927
+ logger$1.log('Loaded persisted variants are past TTL so returning empty Map');
26928
+ return new Map();
26929
+ }
26930
+ return this.flags || new Map();
26931
+ };
26932
+
26577
26933
  FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
26578
26934
  return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
26579
26935
  return feature['value'];
@@ -26612,6 +26968,10 @@ FeatureFlagManager.prototype.isEnabledSync = function(featureName, fallbackValue
26612
26968
  return val;
26613
26969
  };
26614
26970
 
26971
+ function isPresent(v) {
26972
+ return v !== undefined && v !== null;
26973
+ }
26974
+
26615
26975
  FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
26616
26976
  if (this.trackedFeatures.has(featureName)) {
26617
26977
  return;
@@ -26622,25 +26982,41 @@ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature)
26622
26982
  'Experiment name': featureName,
26623
26983
  'Variant name': feature['key'],
26624
26984
  '$experiment_type': 'feature_flag',
26625
- 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
26626
- 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
26985
+ 'Variant fetch start time': isPresent(this._fetchStartTime) ? new Date(this._fetchStartTime).toISOString() : null,
26986
+ 'Variant fetch complete time': isPresent(this._fetchCompleteTime) ? new Date(this._fetchCompleteTime).toISOString() : null,
26627
26987
  'Variant fetch latency (ms)': this._fetchLatency,
26628
26988
  'Variant fetch traceparent': this._traceparent,
26629
26989
  };
26630
26990
 
26631
- if (feature['experiment_id'] !== 'undefined') {
26991
+ if (isPresent(feature['experiment_id'])) {
26632
26992
  trackingProperties['$experiment_id'] = feature['experiment_id'];
26633
26993
  }
26634
- if (feature['is_experiment_active'] !== 'undefined') {
26994
+ if (isPresent(feature['is_experiment_active'])) {
26635
26995
  trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
26636
26996
  }
26637
- if (feature['is_qa_tester'] !== 'undefined') {
26997
+ if (isPresent(feature['is_qa_tester'])) {
26638
26998
  trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
26639
26999
  }
27000
+ if (isPresent(feature['variant_source'])) {
27001
+ trackingProperties['$variant_source'] = feature['variant_source'];
27002
+ }
27003
+ if (isPresent(feature['persisted_at_in_ms'])) {
27004
+ trackingProperties['$persisted_at_in_ms'] = feature['persisted_at_in_ms'];
27005
+ }
27006
+ if (isPresent(feature['ttl_in_ms'])) {
27007
+ trackingProperties['$ttl_in_ms'] = feature['ttl_in_ms'];
27008
+ }
26640
27009
 
26641
27010
  this.track('$experiment_started', trackingProperties);
26642
27011
  };
26643
27012
 
27013
+ FeatureFlagManager.prototype.whenReady = function() {
27014
+ if (this.fetchPromise) {
27015
+ return this.fetchPromise;
27016
+ }
27017
+ return Promise.resolve();
27018
+ };
27019
+
26644
27020
  FeatureFlagManager.prototype.minApisSupported = function() {
26645
27021
  return !!this.fetch &&
26646
27022
  typeof Promise !== 'undefined' &&
@@ -26653,11 +27029,15 @@ safewrapClass(FeatureFlagManager);
26653
27029
  FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
26654
27030
  FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
26655
27031
  FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
27032
+ FeatureFlagManager.prototype['get_all_variants'] = FeatureFlagManager.prototype.getAllVariants;
27033
+ FeatureFlagManager.prototype['get_all_variants_sync'] = FeatureFlagManager.prototype.getAllVariantsSync;
26656
27034
  FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
26657
27035
  FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
26658
27036
  FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
26659
27037
  FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
27038
+ FeatureFlagManager.prototype['load_flags'] = FeatureFlagManager.prototype.loadFlags;
26660
27039
  FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
27040
+ FeatureFlagManager.prototype['when_ready'] = FeatureFlagManager.prototype.whenReady;
26661
27041
 
26662
27042
  // Deprecated method
26663
27043
  FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
@@ -26703,7 +27083,7 @@ RecorderManager.prototype.shouldLoadRecorder = function() {
26703
27083
  return PromisePolyfill.resolve(false);
26704
27084
  }
26705
27085
 
26706
- var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
27086
+ var recording_registry_idb = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_REGISTRY_STORE_NAME, RECORDER_VERSION_DATA);
26707
27087
  var tab_id = this.getTabId();
26708
27088
  return recording_registry_idb.init()
26709
27089
  .then(function () {
@@ -28630,6 +29010,7 @@ MixpanelLib.prototype._init = function(token, config, name) {
28630
29010
  'disable_all_events': false,
28631
29011
  'identify_called': false
28632
29012
  };
29013
+ this._remote_settings_strict_disabled = false;
28633
29014
 
28634
29015
  // set up request queueing/batching
28635
29016
  this.request_batchers = {};
@@ -28704,9 +29085,6 @@ MixpanelLib.prototype._init = function(token, config, name) {
28704
29085
  this.flags.init();
28705
29086
  this['flags'] = this.flags;
28706
29087
 
28707
- this.autocapture = new Autocapture(this);
28708
- this.autocapture.init();
28709
-
28710
29088
  this._init_tab_id();
28711
29089
 
28712
29090
  // Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
@@ -28718,6 +29096,9 @@ MixpanelLib.prototype._init = function(token, config, name) {
28718
29096
  } else {
28719
29097
  this.__session_recording_init_promise = this._check_and_start_session_recording();
28720
29098
  }
29099
+
29100
+ this.autocapture = new Autocapture(this);
29101
+ this.autocapture.init();
28721
29102
  };
28722
29103
 
28723
29104
  /**
@@ -28764,9 +29145,19 @@ MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpane
28764
29145
  return this.recorderManager.checkAndStartSessionRecording(force_start);
28765
29146
  });
28766
29147
 
28767
- MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
28768
- return this.recorderManager.startRecordingOnEvent(event_name, properties);
28769
- };
29148
+ MixpanelLib.prototype._start_recording_on_event = safewrap(function(event_name, properties) {
29149
+ // Wait for recording init to complete before evaluating event triggers.
29150
+ // This ensures recording_event_triggers config is fully loaded when remote settings are used.
29151
+ if (this.__session_recording_init_promise) {
29152
+ this.__session_recording_init_promise.then(_.bind(function() {
29153
+ // In strict mode, skip recording if remote settings failed
29154
+ if (this._remote_settings_strict_disabled) {
29155
+ return;
29156
+ }
29157
+ return this.recorderManager.startRecordingOnEvent(event_name, properties);
29158
+ }, this));
29159
+ }
29160
+ });
28770
29161
 
28771
29162
  MixpanelLib.prototype.start_session_recording = function () {
28772
29163
  return this._check_and_start_session_recording(true);
@@ -29065,6 +29456,7 @@ MixpanelLib.prototype._fetch_remote_settings = function(mode) {
29065
29456
  var disableRecordingIfStrict = function() {
29066
29457
  if (mode === 'strict') {
29067
29458
  self.set_config({'record_sessions_percent': 0});
29459
+ self._remote_settings_strict_disabled = true;
29068
29460
  }
29069
29461
  };
29070
29462
 
@@ -29690,6 +30082,10 @@ MixpanelLib.prototype.track_pageview = addOptOutCheckMixpanelLib(function(proper
29690
30082
  properties
29691
30083
  );
29692
30084
 
30085
+ if (this.is_recording_heatmap_data()) {
30086
+ event_properties['$captured_for_heatmap'] = true;
30087
+ }
30088
+
29693
30089
  return this.track(event_name, event_properties);
29694
30090
  });
29695
30091
 
@@ -30013,7 +30409,9 @@ MixpanelLib.prototype.identify = function(
30013
30409
 
30014
30410
  // check feature flags again if distinct id has changed
30015
30411
  if (new_distinct_id !== previous_distinct_id) {
30016
- this.flags.fetchFlags();
30412
+ this.flags.fetchFlags().catch(function() {
30413
+ console$1.error('[flags] Error fetching flags during identify');
30414
+ });
30017
30415
  }
30018
30416
  };
30019
30417
 
@@ -30031,6 +30429,7 @@ MixpanelLib.prototype.reset = function() {
30031
30429
  '$device_id': uuid
30032
30430
  }, '');
30033
30431
  this._check_and_start_session_recording();
30432
+ this.flags.reset();
30034
30433
  };
30035
30434
 
30036
30435
  /**