mixpanel-browser 2.78.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 (66) hide show
  1. package/.claude/settings.local.json +6 -11
  2. package/.eslintrc.json +12 -0
  3. package/.github/workflows/openfeature-provider-tests.yml +31 -0
  4. package/CHANGELOG.md +8 -1
  5. package/build.sh +2 -2
  6. package/dist/async-modules/{mixpanel-recorder-BjSlYaNJ.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-zMBXIyeG.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-UHf4eBfC.js → mixpanel-targeting-BBMVbgJF.js} +24 -13
  12. package/dist/mixpanel-core.cjs.d.ts +45 -1
  13. package/dist/mixpanel-core.cjs.js +565 -197
  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 +45 -1
  21. package/dist/mixpanel-with-async-modules.cjs.js +567 -199
  22. package/dist/mixpanel-with-async-recorder.cjs.d.ts +45 -1
  23. package/dist/mixpanel-with-async-recorder.cjs.js +567 -199
  24. package/dist/mixpanel-with-recorder.d.ts +45 -1
  25. package/dist/mixpanel-with-recorder.js +490 -122
  26. package/dist/mixpanel-with-recorder.min.d.ts +45 -1
  27. package/dist/mixpanel-with-recorder.min.js +1 -1
  28. package/dist/mixpanel.amd.d.ts +45 -1
  29. package/dist/mixpanel.amd.js +490 -122
  30. package/dist/mixpanel.cjs.d.ts +45 -1
  31. package/dist/mixpanel.cjs.js +490 -122
  32. package/dist/mixpanel.globals.js +567 -199
  33. package/dist/mixpanel.min.js +199 -189
  34. package/dist/mixpanel.module.d.ts +45 -1
  35. package/dist/mixpanel.module.js +490 -122
  36. package/dist/mixpanel.umd.d.ts +45 -1
  37. package/dist/mixpanel.umd.js +490 -122
  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/flags-persistence.js +176 -0
  51. package/src/flags/index.js +174 -23
  52. package/src/index.d.ts +45 -1
  53. package/src/mixpanel-core.js +24 -7
  54. package/src/recorder/idb-config.js +16 -0
  55. package/src/recorder/recording-registry.js +7 -2
  56. package/src/recorder/session-recording.js +9 -4
  57. package/src/recorder-manager.js +7 -2
  58. package/src/request-queue.js +1 -2
  59. package/src/shared-lock.js +2 -3
  60. package/src/storage/indexed-db.js +16 -15
  61. package/src/storage/local-storage.js +5 -3
  62. package/src/utils.js +25 -12
  63. package/tsconfig.base.json +9 -0
  64. package/dist/async-modules/mixpanel-recorder-BjSlYaNJ.min.js.map +0 -1
  65. package/dist/async-modules/mixpanel-targeting-BSHal4N9.min.js +0 -2
  66. package/dist/async-modules/mixpanel-targeting-BSHal4N9.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.78.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
+ };
20142
+
20143
+ var getSessionStorage = function() {
20144
+ try {
20145
+ return win.sessionStorage; // eslint-disable-line no-restricted-properties
20146
+ } catch (_err) {
20147
+ return null;
20148
+ }
20149
+ };
20138
20150
 
20139
- _.localStorage = _storageWrapper(windowLocalStorage, 'localStorage', localStorageSupported);
20140
- _.sessionStorage = _storageWrapper(windowSessionStorage, 'sessionStorage', sessionStorageSupported);
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,13 +26400,63 @@ FeatureFlagManager.prototype.init = function() {
26191
26400
  }
26192
26401
 
26193
26402
  this.flags = null;
26194
- this.fetchFlags().catch(function() {
26195
- logger$1.error('Error fetching flags during init');
26196
- });
26197
-
26198
26403
  this.trackedFeatures = new Set();
26199
26404
  this.pendingFirstTimeEvents = {};
26200
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
+ });
26201
26460
  };
26202
26461
 
26203
26462
  FeatureFlagManager.prototype.getFullConfig = function() {
@@ -26254,12 +26513,11 @@ FeatureFlagManager.prototype.fetchFlags = function() {
26254
26513
  return Promise.resolve();
26255
26514
  }
26256
26515
 
26257
- var distinctId = this.getMpProperty('distinct_id');
26258
- var deviceId = this.getMpProperty('$device_id');
26516
+ var context = this._buildContext();
26517
+ var distinctId = context['distinct_id'];
26259
26518
  var traceparent = generateTraceparent();
26260
26519
  logger$1.log('Fetching flags for distinct ID: ' + distinctId);
26261
26520
 
26262
- var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
26263
26521
  var searchParams = new URLSearchParams();
26264
26522
  searchParams.set('context', JSON.stringify(context));
26265
26523
  searchParams.set('token', this.getMpConfig('token'));
@@ -26309,7 +26567,8 @@ FeatureFlagManager.prototype.fetchFlags = function() {
26309
26567
  'value': data['variant_value'],
26310
26568
  'experiment_id': data['experiment_id'],
26311
26569
  'is_experiment_active': data['is_experiment_active'],
26312
- 'is_qa_tester': data['is_qa_tester']
26570
+ 'is_qa_tester': data['is_qa_tester'],
26571
+ 'variant_source': 'network'
26313
26572
  });
26314
26573
  }
26315
26574
  }, this);
@@ -26351,10 +26610,15 @@ FeatureFlagManager.prototype.fetchFlags = function() {
26351
26610
  }
26352
26611
 
26353
26612
  this.flags = flags;
26613
+ this.trackedFeatures = new Set();
26354
26614
  this.pendingFirstTimeEvents = pendingFirstTimeEvents;
26615
+ this._loadedPersistedAtMs = null;
26616
+ this._loadedTtlMs = null;
26355
26617
  this._traceparent = traceparent;
26356
26618
 
26357
26619
  this._loadTargetingIfNeeded();
26620
+
26621
+ this.persistence.save(context, this.flags, this.pendingFirstTimeEvents);
26358
26622
  }.bind(this)).catch(function(error) {
26359
26623
  if (this._fetchInProgressStartTime) {
26360
26624
  this.markFetchComplete();
@@ -26514,6 +26778,7 @@ FeatureFlagManager.prototype._processFirstTimeEventCheck = function(eventName, p
26514
26778
  };
26515
26779
 
26516
26780
  this.flags.set(flagKey, newVariant);
26781
+ this.trackedFeatures.delete(flagKey);
26517
26782
  this.activatedFirstTimeEvents[eventKey] = true;
26518
26783
 
26519
26784
  this.recordFirstTimeEvent(
@@ -26563,35 +26828,106 @@ FeatureFlagManager.prototype.recordFirstTimeEvent = function(flagId, projectId,
26563
26828
  };
26564
26829
 
26565
26830
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
26566
- if (!this.fetchPromise) {
26831
+ if (!this.persistenceLoadedPromise) {
26567
26832
  return new Promise(function(resolve) {
26568
26833
  logger$1.critical('Feature Flags not initialized');
26569
- resolve(fallback);
26834
+ resolve(withFallbackSource(fallback));
26570
26835
  });
26571
26836
  }
26572
26837
 
26573
- return this.fetchPromise.then(function() {
26574
- return this.getVariantSync(featureName, fallback);
26575
- }.bind(this)).catch(function(error) {
26576
- logger$1.error(error);
26577
- return fallback;
26578
- });
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;
26579
26870
  };
26580
26871
 
26581
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
+ }
26582
26877
  if (!this.areFlagsReady()) {
26583
26878
  logger$1.log('Flags not loaded yet');
26584
- return fallback;
26879
+ return withFallbackSource(fallback);
26585
26880
  }
26586
26881
  var feature = this.flags.get(featureName);
26587
26882
  if (!feature) {
26588
26883
  logger$1.log('No flag found: "' + featureName + '"');
26589
- return fallback;
26884
+ return withFallbackSource(fallback);
26590
26885
  }
26591
26886
  this.trackFeatureCheck(featureName, feature);
26592
26887
  return feature;
26593
26888
  };
26594
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
+
26595
26931
  FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
26596
26932
  return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
26597
26933
  return feature['value'];
@@ -26630,6 +26966,10 @@ FeatureFlagManager.prototype.isEnabledSync = function(featureName, fallbackValue
26630
26966
  return val;
26631
26967
  };
26632
26968
 
26969
+ function isPresent(v) {
26970
+ return v !== undefined && v !== null;
26971
+ }
26972
+
26633
26973
  FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
26634
26974
  if (this.trackedFeatures.has(featureName)) {
26635
26975
  return;
@@ -26640,21 +26980,30 @@ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature)
26640
26980
  'Experiment name': featureName,
26641
26981
  'Variant name': feature['key'],
26642
26982
  '$experiment_type': 'feature_flag',
26643
- 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
26644
- '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,
26645
26985
  'Variant fetch latency (ms)': this._fetchLatency,
26646
26986
  'Variant fetch traceparent': this._traceparent,
26647
26987
  };
26648
26988
 
26649
- if (feature['experiment_id'] !== 'undefined') {
26989
+ if (isPresent(feature['experiment_id'])) {
26650
26990
  trackingProperties['$experiment_id'] = feature['experiment_id'];
26651
26991
  }
26652
- if (feature['is_experiment_active'] !== 'undefined') {
26992
+ if (isPresent(feature['is_experiment_active'])) {
26653
26993
  trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
26654
26994
  }
26655
- if (feature['is_qa_tester'] !== 'undefined') {
26995
+ if (isPresent(feature['is_qa_tester'])) {
26656
26996
  trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
26657
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
+ }
26658
27007
 
26659
27008
  this.track('$experiment_started', trackingProperties);
26660
27009
  };
@@ -26678,6 +27027,8 @@ safewrapClass(FeatureFlagManager);
26678
27027
  FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
26679
27028
  FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
26680
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;
26681
27032
  FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
26682
27033
  FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
26683
27034
  FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
@@ -26730,7 +27081,7 @@ RecorderManager.prototype.shouldLoadRecorder = function() {
26730
27081
  return PromisePolyfill.resolve(false);
26731
27082
  }
26732
27083
 
26733
- 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);
26734
27085
  var tab_id = this.getTabId();
26735
27086
  return recording_registry_idb.init()
26736
27087
  .then(function () {
@@ -28657,6 +29008,7 @@ MixpanelLib.prototype._init = function(token, config, name) {
28657
29008
  'disable_all_events': false,
28658
29009
  'identify_called': false
28659
29010
  };
29011
+ this._remote_settings_strict_disabled = false;
28660
29012
 
28661
29013
  // set up request queueing/batching
28662
29014
  this.request_batchers = {};
@@ -28731,9 +29083,6 @@ MixpanelLib.prototype._init = function(token, config, name) {
28731
29083
  this.flags.init();
28732
29084
  this['flags'] = this.flags;
28733
29085
 
28734
- this.autocapture = new Autocapture(this);
28735
- this.autocapture.init();
28736
-
28737
29086
  this._init_tab_id();
28738
29087
 
28739
29088
  // Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
@@ -28745,6 +29094,9 @@ MixpanelLib.prototype._init = function(token, config, name) {
28745
29094
  } else {
28746
29095
  this.__session_recording_init_promise = this._check_and_start_session_recording();
28747
29096
  }
29097
+
29098
+ this.autocapture = new Autocapture(this);
29099
+ this.autocapture.init();
28748
29100
  };
28749
29101
 
28750
29102
  /**
@@ -28791,9 +29143,19 @@ MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpane
28791
29143
  return this.recorderManager.checkAndStartSessionRecording(force_start);
28792
29144
  });
28793
29145
 
28794
- MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
28795
- return this.recorderManager.startRecordingOnEvent(event_name, properties);
28796
- };
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
+ });
28797
29159
 
28798
29160
  MixpanelLib.prototype.start_session_recording = function () {
28799
29161
  return this._check_and_start_session_recording(true);
@@ -29092,6 +29454,7 @@ MixpanelLib.prototype._fetch_remote_settings = function(mode) {
29092
29454
  var disableRecordingIfStrict = function() {
29093
29455
  if (mode === 'strict') {
29094
29456
  self.set_config({'record_sessions_percent': 0});
29457
+ self._remote_settings_strict_disabled = true;
29095
29458
  }
29096
29459
  };
29097
29460
 
@@ -29717,6 +30080,10 @@ MixpanelLib.prototype.track_pageview = addOptOutCheckMixpanelLib(function(proper
29717
30080
  properties
29718
30081
  );
29719
30082
 
30083
+ if (this.is_recording_heatmap_data()) {
30084
+ event_properties['$captured_for_heatmap'] = true;
30085
+ }
30086
+
29720
30087
  return this.track(event_name, event_properties);
29721
30088
  });
29722
30089
 
@@ -30060,6 +30427,7 @@ MixpanelLib.prototype.reset = function() {
30060
30427
  '$device_id': uuid
30061
30428
  }, '');
30062
30429
  this._check_and_start_session_recording();
30430
+ this.flags.reset();
30063
30431
  };
30064
30432
 
30065
30433
  /**