mixpanel-browser 2.78.0 → 2.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.eslintrc.json +12 -0
  2. package/.github/workflows/integration-tests.yml +1 -0
  3. package/.github/workflows/openfeature-provider-tests.yml +31 -0
  4. package/CHANGELOG.md +14 -1
  5. package/build.sh +2 -2
  6. package/dist/async-modules/{mixpanel-recorder-BjSlYaNJ.min.js → mixpanel-recorder-B61POiHc.min.js} +2 -2
  7. package/dist/async-modules/mixpanel-recorder-B61POiHc.min.js.map +1 -0
  8. package/dist/async-modules/{mixpanel-recorder-zMBXIyeG.js → mixpanel-recorder-C3AW7mPl.js} +63 -35
  9. package/dist/async-modules/{mixpanel-targeting-UHf4eBfC.js → mixpanel-targeting-CBwOQJZw.js} +24 -13
  10. package/dist/async-modules/mixpanel-targeting-kdl-eE-1.min.js +2 -0
  11. package/dist/async-modules/mixpanel-targeting-kdl-eE-1.min.js.map +1 -0
  12. package/dist/mixpanel-core.cjs.d.ts +45 -1
  13. package/dist/mixpanel-core.cjs.js +577 -209
  14. package/dist/mixpanel-recorder.js +63 -35
  15. package/dist/mixpanel-recorder.min.js +1 -1
  16. package/dist/mixpanel-recorder.min.js.map +1 -1
  17. package/dist/mixpanel-targeting.js +24 -13
  18. package/dist/mixpanel-targeting.min.js +1 -1
  19. package/dist/mixpanel-targeting.min.js.map +1 -1
  20. package/dist/mixpanel-with-async-modules.cjs.d.ts +45 -1
  21. package/dist/mixpanel-with-async-modules.cjs.js +579 -211
  22. package/dist/mixpanel-with-async-recorder.cjs.d.ts +45 -1
  23. package/dist/mixpanel-with-async-recorder.cjs.js +579 -211
  24. package/dist/mixpanel-with-recorder.d.ts +45 -1
  25. package/dist/mixpanel-with-recorder.js +508 -136
  26. package/dist/mixpanel-with-recorder.min.d.ts +45 -1
  27. package/dist/mixpanel-with-recorder.min.js +1 -1
  28. package/dist/mixpanel.amd.d.ts +45 -1
  29. package/dist/mixpanel.amd.js +508 -136
  30. package/dist/mixpanel.cjs.d.ts +45 -1
  31. package/dist/mixpanel.cjs.js +508 -136
  32. package/dist/mixpanel.globals.js +579 -211
  33. package/dist/mixpanel.min.js +200 -190
  34. package/dist/mixpanel.module.d.ts +45 -1
  35. package/dist/mixpanel.module.js +508 -136
  36. package/dist/mixpanel.umd.d.ts +45 -1
  37. package/dist/mixpanel.umd.js +508 -136
  38. package/package.json +1 -1
  39. package/packages/openfeature-web-provider/README.md +357 -0
  40. package/packages/openfeature-web-provider/package-lock.json +1636 -0
  41. package/packages/openfeature-web-provider/package.json +51 -0
  42. package/packages/openfeature-web-provider/rollup.config.browser.mjs +26 -0
  43. package/packages/openfeature-web-provider/src/MixpanelProvider.ts +302 -0
  44. package/packages/openfeature-web-provider/src/index.ts +1 -0
  45. package/packages/openfeature-web-provider/src/types.ts +72 -0
  46. package/packages/openfeature-web-provider/test/MixpanelProvider.spec.ts +484 -0
  47. package/packages/openfeature-web-provider/tsconfig.json +15 -0
  48. package/src/autocapture/index.js +17 -12
  49. package/src/config.js +1 -1
  50. package/src/flags/flags-persistence.js +176 -0
  51. package/src/flags/index.js +176 -25
  52. package/src/index.d.ts +45 -1
  53. package/src/mixpanel-core.js +24 -7
  54. package/src/recorder/idb-config.js +16 -0
  55. package/src/recorder/recording-registry.js +7 -2
  56. package/src/recorder/session-recording.js +15 -6
  57. package/src/recorder-manager.js +7 -2
  58. package/src/request-queue.js +1 -2
  59. package/src/shared-lock.js +2 -3
  60. package/src/storage/indexed-db.js +16 -15
  61. package/src/storage/local-storage.js +5 -3
  62. package/src/utils.js +25 -12
  63. package/tsconfig.base.json +9 -0
  64. package/.claude/settings.local.json +0 -16
  65. package/dist/async-modules/mixpanel-recorder-BjSlYaNJ.min.js.map +0 -1
  66. package/dist/async-modules/mixpanel-targeting-BSHal4N9.min.js +0 -2
  67. package/dist/async-modules/mixpanel-targeting-BSHal4N9.min.js.map +0 -1
@@ -31,7 +31,7 @@
31
31
 
32
32
  var Config = {
33
33
  DEBUG: false,
34
- LIB_VERSION: '2.78.0'
34
+ LIB_VERSION: '2.80.0'
35
35
  };
36
36
 
37
37
  // Window global names for async modules
@@ -19129,6 +19129,7 @@
19129
19129
  var console_with_prefix = function(prefix) {
19130
19130
  return {
19131
19131
  log: log_func_with_prefix(console$1.log, prefix),
19132
+ warn: log_func_with_prefix(console$1.warn, prefix),
19132
19133
  error: log_func_with_prefix(console$1.error, prefix),
19133
19134
  critical: log_func_with_prefix(console$1.critical, prefix)
19134
19135
  };
@@ -20075,7 +20076,8 @@
20075
20076
  if (_localStorageSupported !== null && !forceCheck) {
20076
20077
  return _localStorageSupported;
20077
20078
  }
20078
- return _localStorageSupported = _testStorageSupported(storage || win.localStorage);
20079
+
20080
+ return _localStorageSupported = _testStorageSupported(storage);
20079
20081
  };
20080
20082
 
20081
20083
  var _sessionStorageSupported = null;
@@ -20083,7 +20085,8 @@
20083
20085
  if (_sessionStorageSupported !== null && !forceCheck) {
20084
20086
  return _sessionStorageSupported;
20085
20087
  }
20086
- return _sessionStorageSupported = _testStorageSupported(storage || win.sessionStorage);
20088
+
20089
+ return _sessionStorageSupported = _testStorageSupported(storage);
20087
20090
  };
20088
20091
 
20089
20092
  function _storageWrapper(storage, name, is_supported_fn) {
@@ -20133,17 +20136,26 @@
20133
20136
  };
20134
20137
  }
20135
20138
 
20136
- // Safari errors out accessing localStorage/sessionStorage when cookies are disabled,
20137
- // so create dummy storage wrappers that silently fail as a fallback.
20138
- var windowLocalStorage = null, windowSessionStorage = null;
20139
- try {
20140
- windowLocalStorage = win.localStorage;
20141
- windowSessionStorage = win.sessionStorage;
20142
- // eslint-disable-next-line no-empty
20143
- } catch (_err) {}
20139
+ // Safari and other browsers may error out accessing localStorage/sessionStorage
20140
+ // when cookies are disabled, so wrap access in a try-catch.
20141
+ var getLocalStorage = function() {
20142
+ try {
20143
+ return win.localStorage; // eslint-disable-line no-restricted-properties
20144
+ } catch (_err) {
20145
+ return null;
20146
+ }
20147
+ };
20148
+
20149
+ var getSessionStorage = function() {
20150
+ try {
20151
+ return win.sessionStorage; // eslint-disable-line no-restricted-properties
20152
+ } catch (_err) {
20153
+ return null;
20154
+ }
20155
+ };
20144
20156
 
20145
- _.localStorage = _storageWrapper(windowLocalStorage, 'localStorage', localStorageSupported);
20146
- _.sessionStorage = _storageWrapper(windowSessionStorage, 'sessionStorage', sessionStorageSupported);
20157
+ _.localStorage = _storageWrapper(getLocalStorage(), 'localStorage', localStorageSupported);
20158
+ _.sessionStorage = _storageWrapper(getSessionStorage(), 'sessionStorage', sessionStorageSupported);
20147
20159
 
20148
20160
  _.register_event = (function() {
20149
20161
  // written by Dean Edwards, 2005
@@ -20812,29 +20824,26 @@
20812
20824
  _['toArray'] = _.toArray;
20813
20825
  _['NPO'] = NpoPromise;
20814
20826
 
20815
- var MIXPANEL_DB_NAME = 'mixpanelBrowserDb';
20816
-
20817
- var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
20818
- var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
20819
-
20820
- // note: increment the version number when adding new object stores
20821
- var DB_VERSION = 1;
20822
- var OBJECT_STORES = [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME];
20823
-
20824
20827
  /**
20825
20828
  * @type {import('./wrapper').StorageWrapper}
20826
20829
  */
20827
- var IDBStorageWrapper = function (storeName) {
20830
+ var IDBStorageWrapper = function (dbName, storeName, versionData) {
20831
+ this.dbName = dbName;
20832
+ this.storeName = storeName;
20833
+ this.version = versionData.version;
20834
+ this.storeNamesInDb = versionData.storeNames;
20828
20835
  /**
20829
20836
  * @type {Promise<IDBDatabase>|null}
20830
20837
  */
20831
20838
  this.dbPromise = null;
20832
- this.storeName = storeName;
20833
20839
  };
20834
20840
 
20835
20841
  IDBStorageWrapper.prototype._openDb = function () {
20842
+ var dbName = this.dbName;
20843
+ var version = this.version;
20844
+ var storeNamesInDb = this.storeNamesInDb;
20836
20845
  return new PromisePolyfill(function (resolve, reject) {
20837
- var openRequest = win.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
20846
+ var openRequest = win.indexedDB.open(dbName, version);
20838
20847
  openRequest['onerror'] = function () {
20839
20848
  reject(openRequest.error);
20840
20849
  };
@@ -20846,8 +20855,10 @@
20846
20855
  openRequest['onupgradeneeded'] = function (ev) {
20847
20856
  var db = ev.target.result;
20848
20857
 
20849
- OBJECT_STORES.forEach(function (storeName) {
20850
- db.createObjectStore(storeName);
20858
+ storeNamesInDb.forEach(function (storeName) {
20859
+ if (!db.objectStoreNames.contains(storeName)) {
20860
+ db.createObjectStore(storeName);
20861
+ }
20851
20862
  });
20852
20863
  };
20853
20864
  });
@@ -20939,6 +20950,16 @@
20939
20950
  });
20940
20951
  };
20941
20952
 
20953
+ var MIXPANEL_BROWSER_DB_NAME = 'mixpanelBrowserDb';
20954
+ var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
20955
+ var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
20956
+
20957
+ // Keeping these two properties closeby, as adding additional stores to a DB in IndexedDB requires a version increment
20958
+ var RECORDER_VERSION_DATA = {
20959
+ version: 1,
20960
+ storeNames: [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME]
20961
+ };
20962
+
20942
20963
  /**
20943
20964
  * GDPR utils
20944
20965
  *
@@ -21239,7 +21260,7 @@
21239
21260
  };
21240
21261
  }
21241
21262
 
21242
- var logger$8 = console_with_prefix('lock');
21263
+ var logger$9 = console_with_prefix('lock');
21243
21264
 
21244
21265
  /**
21245
21266
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -21265,7 +21286,7 @@
21265
21286
  options = options || {};
21266
21287
 
21267
21288
  this.storageKey = key;
21268
- this.storage = options.storage || win.localStorage;
21289
+ this.storage = options.storage || getLocalStorage();
21269
21290
  this.pollIntervalMS = options.pollIntervalMS || 100;
21270
21291
  this.timeoutMS = options.timeoutMS || 2000;
21271
21292
 
@@ -21291,7 +21312,7 @@
21291
21312
 
21292
21313
  var delay = function(cb) {
21293
21314
  if (new Date().getTime() - startTime > timeoutMS) {
21294
- logger$8.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21315
+ logger$9.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21295
21316
  storage.removeItem(keyZ);
21296
21317
  storage.removeItem(keyY);
21297
21318
  loop();
@@ -21393,10 +21414,13 @@
21393
21414
  * @type {import('./wrapper').StorageWrapper}
21394
21415
  */
21395
21416
  var LocalStorageWrapper = function (storageOverride) {
21396
- this.storage = storageOverride || win.localStorage;
21417
+ this.storage = storageOverride || getLocalStorage();
21397
21418
  };
21398
21419
 
21399
21420
  LocalStorageWrapper.prototype.init = function () {
21421
+ if (!this.storage) {
21422
+ return PromisePolyfill.reject(new Error('localStorage is not available'));
21423
+ }
21400
21424
  return PromisePolyfill.resolve();
21401
21425
  };
21402
21426
 
@@ -21438,7 +21462,7 @@
21438
21462
  }, this));
21439
21463
  };
21440
21464
 
21441
- var logger$7 = console_with_prefix('batch');
21465
+ var logger$8 = console_with_prefix('batch');
21442
21466
 
21443
21467
  /**
21444
21468
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -21463,11 +21487,11 @@
21463
21487
  if (this.usePersistence) {
21464
21488
  this.queueStorage = options.queueStorage || new LocalStorageWrapper();
21465
21489
  this.lock = new SharedLock(storageKey, {
21466
- storage: options.sharedLockStorage || win.localStorage,
21490
+ storage: options.sharedLockStorage,
21467
21491
  timeoutMS: options.sharedLockTimeoutMS,
21468
21492
  });
21469
21493
  }
21470
- this.reportError = options.errorReporter || _.bind(logger$7.error, logger$7);
21494
+ this.reportError = options.errorReporter || _.bind(logger$8.error, logger$8);
21471
21495
 
21472
21496
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
21473
21497
 
@@ -21800,7 +21824,7 @@
21800
21824
  // maximum interval between request retries after exponential backoff
21801
21825
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
21802
21826
 
21803
- var logger$6 = console_with_prefix('batch');
21827
+ var logger$7 = console_with_prefix('batch');
21804
21828
 
21805
21829
  /**
21806
21830
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -21928,7 +21952,7 @@
21928
21952
  */
21929
21953
  RequestBatcher.prototype.flush = function(options) {
21930
21954
  if (this.requestInProgress) {
21931
- logger$6.log('Flush: Request already in progress');
21955
+ logger$7.log('Flush: Request already in progress');
21932
21956
  return PromisePolyfill.resolve();
21933
21957
  }
21934
21958
 
@@ -22105,7 +22129,7 @@
22105
22129
  if (options.unloading) {
22106
22130
  requestOptions.transport = 'sendBeacon';
22107
22131
  }
22108
- logger$6.log('MIXPANEL REQUEST:', dataForRequest);
22132
+ logger$7.log('MIXPANEL REQUEST:', dataForRequest);
22109
22133
  return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
22110
22134
  }, this))
22111
22135
  .catch(_.bind(function(err) {
@@ -22118,7 +22142,7 @@
22118
22142
  * Log error to global logger and optional user-defined logger.
22119
22143
  */
22120
22144
  RequestBatcher.prototype.reportError = function(msg, err) {
22121
- logger$6.error.apply(logger$6.error, arguments);
22145
+ logger$7.error.apply(logger$7.error, arguments);
22122
22146
  if (this.errorReporter) {
22123
22147
  try {
22124
22148
  if (!(err instanceof Error)) {
@@ -22126,7 +22150,7 @@
22126
22150
  }
22127
22151
  this.errorReporter(msg, err);
22128
22152
  } catch(err) {
22129
- logger$6.error(err);
22153
+ logger$7.error(err);
22130
22154
  }
22131
22155
  }
22132
22156
  };
@@ -22271,7 +22295,7 @@
22271
22295
 
22272
22296
  var MAX_DEPTH = 5;
22273
22297
 
22274
- var logger$5 = console_with_prefix('autocapture');
22298
+ var logger$6 = console_with_prefix('autocapture');
22275
22299
 
22276
22300
 
22277
22301
  function getClasses(el) {
@@ -22535,7 +22559,7 @@
22535
22559
  return false;
22536
22560
  }
22537
22561
  } catch (err) {
22538
- logger$5.critical('Error while checking element in allowElementCallback', err);
22562
+ logger$6.critical('Error while checking element in allowElementCallback', err);
22539
22563
  return false;
22540
22564
  }
22541
22565
  }
@@ -22552,7 +22576,7 @@
22552
22576
  return true;
22553
22577
  }
22554
22578
  } catch (err) {
22555
- logger$5.critical('Error while checking selector: ' + sel, err);
22579
+ logger$6.critical('Error while checking selector: ' + sel, err);
22556
22580
  }
22557
22581
  }
22558
22582
  return false;
@@ -22567,7 +22591,7 @@
22567
22591
  return true;
22568
22592
  }
22569
22593
  } catch (err) {
22570
- logger$5.critical('Error while checking element in blockElementCallback', err);
22594
+ logger$6.critical('Error while checking element in blockElementCallback', err);
22571
22595
  return true;
22572
22596
  }
22573
22597
  }
@@ -22581,7 +22605,7 @@
22581
22605
  return true;
22582
22606
  }
22583
22607
  } catch (err) {
22584
- logger$5.critical('Error while checking selector: ' + sel, err);
22608
+ logger$6.critical('Error while checking selector: ' + sel, err);
22585
22609
  }
22586
22610
  }
22587
22611
  }
@@ -23137,7 +23161,7 @@
23137
23161
  *
23138
23162
  */
23139
23163
 
23140
- var logger$4 = console_with_prefix('network-plugin');
23164
+ var logger$5 = console_with_prefix('network-plugin');
23141
23165
 
23142
23166
  /**
23143
23167
  * Get the time origin for converting performance timestamps to absolute timestamps.
@@ -23289,7 +23313,7 @@
23289
23313
  return str;
23290
23314
  }
23291
23315
  if (str.length > MAX_BODY_SIZE) {
23292
- logger$4.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
23316
+ logger$5.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
23293
23317
  return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
23294
23318
  }
23295
23319
  return str;
@@ -23303,7 +23327,7 @@
23303
23327
  */
23304
23328
  function initPerformanceObserver(cb, win, options) {
23305
23329
  if (!win.PerformanceObserver) {
23306
- logger$4.error('PerformanceObserver not supported');
23330
+ logger$5.error('PerformanceObserver not supported');
23307
23331
  return function() {
23308
23332
  //
23309
23333
  };
@@ -23456,7 +23480,7 @@
23456
23480
  attempt = 0;
23457
23481
  }
23458
23482
  if (attempt > 10) {
23459
- logger$4.error('Cannot find performance entry');
23483
+ logger$5.error('Cannot find performance entry');
23460
23484
  return Promise.resolve(null);
23461
23485
  }
23462
23486
  var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
@@ -23577,7 +23601,7 @@
23577
23601
  )
23578
23602
  .then(function(entry) {
23579
23603
  if (!entry) {
23580
- logger$4.error('Failed to get performance entry for XHR request to ' + req.url);
23604
+ logger$5.error('Failed to get performance entry for XHR request to ' + req.url);
23581
23605
  return;
23582
23606
  }
23583
23607
  /** @type {NetworkRequest} */
@@ -23597,7 +23621,7 @@
23597
23621
  cb({ requests: [request] });
23598
23622
  })
23599
23623
  .catch(function(e) {
23600
- logger$4.error('Error recording XHR request to ' + req.url + ': ' + String(e));
23624
+ logger$5.error('Error recording XHR request to ' + req.url + ': ' + String(e));
23601
23625
  });
23602
23626
  });
23603
23627
 
@@ -23689,7 +23713,7 @@
23689
23713
  })
23690
23714
  .then(function(entry) {
23691
23715
  if (!entry) {
23692
- logger$4.error('Failed to get performance entry for fetch request to ' + req.url);
23716
+ logger$5.error('Failed to get performance entry for fetch request to ' + req.url);
23693
23717
  return;
23694
23718
  }
23695
23719
  /** @type {NetworkRequest} */
@@ -23709,7 +23733,7 @@
23709
23733
  cb({ requests: [request] });
23710
23734
  })
23711
23735
  .catch(function (e) {
23712
- logger$4.error('Error recording fetch request to ' + req.url + ': ' + String(e));
23736
+ logger$5.error('Error recording fetch request to ' + req.url + ': ' + String(e));
23713
23737
  });
23714
23738
 
23715
23739
  return originalFetchPromise;
@@ -23782,7 +23806,7 @@
23782
23806
  */
23783
23807
 
23784
23808
 
23785
- var logger$3 = console_with_prefix('recorder');
23809
+ var logger$4 = console_with_prefix('recorder');
23786
23810
  var CompressionStream = win['CompressionStream'];
23787
23811
 
23788
23812
  var RECORDER_BATCHER_LIB_CONFIG = {
@@ -23876,14 +23900,15 @@
23876
23900
 
23877
23901
  this.recordMaxMs = MAX_RECORDING_MS;
23878
23902
  this.recordMinMs = 0;
23903
+ this._recordMinMsCheckStart = null;
23879
23904
 
23880
23905
  // disable persistence if localStorage is not supported
23881
23906
  // request-queue will automatically disable persistence if indexedDB fails to initialize
23882
- var usePersistence = localStorageSupported(options.sharedLockStorage, true) && !this.getConfig('disable_persistence');
23907
+ var usePersistence = localStorageSupported(options.sharedLockStorage || getLocalStorage(), true) && !this.getConfig('disable_persistence');
23883
23908
 
23884
23909
  // each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
23885
23910
  this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
23886
- this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
23911
+ this.queueStorage = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_EVENTS_STORE_NAME, RECORDER_VERSION_DATA);
23887
23912
  this.batcher = new RequestBatcher(this.batcherKey, {
23888
23913
  errorReporter: this.reportError.bind(this),
23889
23914
  flushOnlyOnInterval: true,
@@ -23962,14 +23987,14 @@
23962
23987
  }
23963
23988
 
23964
23989
  if (this._stopRecording !== null) {
23965
- logger$3.log('Recording already in progress, skipping startRecording.');
23990
+ logger$4.log('Recording already in progress, skipping startRecording.');
23966
23991
  return;
23967
23992
  }
23968
23993
 
23969
23994
  this.recordMaxMs = this.getConfig('record_max_ms');
23970
23995
  if (this.recordMaxMs > MAX_RECORDING_MS) {
23971
23996
  this.recordMaxMs = MAX_RECORDING_MS;
23972
- logger$3.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
23997
+ logger$4.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
23973
23998
  }
23974
23999
 
23975
24000
  if (!this.maxExpires) {
@@ -23991,6 +24016,7 @@
23991
24016
  // this also applies if the minimum recording length has not been hit yet
23992
24017
  // so that we don't send data until we know the recording will be long enough
23993
24018
  this.batcher.stop();
24019
+ this._recordMinMsCheckStart = null;
23994
24020
  } else {
23995
24021
  this.batcher.start();
23996
24022
  }
@@ -24033,7 +24059,7 @@
24033
24059
  );
24034
24060
  }
24035
24061
 
24036
- var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$3);
24062
+ var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$4);
24037
24063
 
24038
24064
  try {
24039
24065
  this._stopRecording = this._rrwebRecord({
@@ -24042,9 +24068,11 @@
24042
24068
  this._onIdleTimeout();
24043
24069
  return;
24044
24070
  }
24071
+ if (this._recordMinMsCheckStart === null) {
24072
+ this._recordMinMsCheckStart = ev.timestamp;
24073
+ }
24045
24074
  if (isUserEvent(ev)) {
24046
- if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
24047
- // start flushing again after user activity
24075
+ if (this.batcher.stopped && ev.timestamp - this._recordMinMsCheckStart >= this.recordMinMs) {
24048
24076
  this.batcher.start();
24049
24077
  }
24050
24078
  resetIdleTimeout();
@@ -24295,14 +24323,14 @@
24295
24323
 
24296
24324
 
24297
24325
  SessionRecording.prototype.reportError = function(msg, err) {
24298
- logger$3.error.apply(logger$3.error, arguments);
24326
+ logger$4.error.apply(logger$4.error, arguments);
24299
24327
  try {
24300
24328
  if (!err && !(msg instanceof Error)) {
24301
24329
  msg = new Error(msg);
24302
24330
  }
24303
24331
  this.getConfig('error_reporter')(msg, err);
24304
24332
  } catch(err) {
24305
- logger$3.error(err);
24333
+ logger$4.error(err);
24306
24334
  }
24307
24335
  };
24308
24336
 
@@ -24331,7 +24359,7 @@
24331
24359
  var configValue = this.getConfig('record_min_ms');
24332
24360
 
24333
24361
  if (configValue > MAX_VALUE_FOR_MIN_RECORDING_MS) {
24334
- logger$3.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
24362
+ logger$4.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
24335
24363
  return MAX_VALUE_FOR_MIN_RECORDING_MS;
24336
24364
  }
24337
24365
 
@@ -24373,7 +24401,7 @@
24373
24401
  */
24374
24402
  var RecordingRegistry = function (options) {
24375
24403
  /** @type {IDBStorageWrapper} */
24376
- this.idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
24404
+ this.idb = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_REGISTRY_STORE_NAME, RECORDER_VERSION_DATA);
24377
24405
  this.errorReporter = options.errorReporter;
24378
24406
  this.mixpanelInstance = options.mixpanelInstance;
24379
24407
  this.sharedLockStorage = options.sharedLockStorage;
@@ -24494,7 +24522,7 @@
24494
24522
  .catch(this.handleError.bind(this));
24495
24523
  };
24496
24524
 
24497
- var logger$2 = console_with_prefix('recorder');
24525
+ var logger$3 = console_with_prefix('recorder');
24498
24526
 
24499
24527
  /**
24500
24528
  * Recorder API: bundles rrweb and and exposes methods to start and stop recordings.
@@ -24510,7 +24538,7 @@
24510
24538
  */
24511
24539
  this.recordingRegistry = new RecordingRegistry({
24512
24540
  mixpanelInstance: this.mixpanelInstance,
24513
- errorReporter: logger$2.error,
24541
+ errorReporter: logger$3.error,
24514
24542
  sharedLockStorage: sharedLockStorage
24515
24543
  });
24516
24544
  this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
@@ -24522,17 +24550,17 @@
24522
24550
  MixpanelRecorder.prototype.startRecording = function(options) {
24523
24551
  options = options || {};
24524
24552
  if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
24525
- logger$2.log('Recording already in progress, skipping startRecording.');
24553
+ logger$3.log('Recording already in progress, skipping startRecording.');
24526
24554
  return;
24527
24555
  }
24528
24556
 
24529
24557
  var onIdleTimeout = function () {
24530
- logger$2.log('Idle timeout reached, restarting recording.');
24558
+ logger$3.log('Idle timeout reached, restarting recording.');
24531
24559
  this.resetRecording();
24532
24560
  }.bind(this);
24533
24561
 
24534
24562
  var onMaxLengthReached = function () {
24535
- logger$2.log('Max recording length reached, stopping recording.');
24563
+ logger$3.log('Max recording length reached, stopping recording.');
24536
24564
  this.resetRecording();
24537
24565
  }.bind(this);
24538
24566
 
@@ -24602,7 +24630,7 @@
24602
24630
  } else if (startNewIfInactive) {
24603
24631
  return this.startRecording({shouldStopBatcher: false});
24604
24632
  } else {
24605
- logger$2.log('No resumable recording found.');
24633
+ logger$3.log('No resumable recording found.');
24606
24634
  return null;
24607
24635
  }
24608
24636
  }.bind(this));
@@ -25267,7 +25295,7 @@
25267
25295
  observer.observe(shadowRoot, this.observerConfig);
25268
25296
  this.shadowObservers.push(observer);
25269
25297
  } catch (e) {
25270
- logger$5.critical('Error while observing shadow root', e);
25298
+ logger$6.critical('Error while observing shadow root', e);
25271
25299
  }
25272
25300
  };
25273
25301
 
@@ -25278,7 +25306,7 @@
25278
25306
  }
25279
25307
 
25280
25308
  if (!weakSetSupported()) {
25281
- logger$5.critical('Shadow DOM observation unavailable: WeakSet not supported');
25309
+ logger$6.critical('Shadow DOM observation unavailable: WeakSet not supported');
25282
25310
  return;
25283
25311
  }
25284
25312
 
@@ -25294,7 +25322,7 @@
25294
25322
  try {
25295
25323
  this.shadowObservers[i].disconnect();
25296
25324
  } catch (e) {
25297
- logger$5.critical('Error while disconnecting shadow DOM observer', e);
25325
+ logger$6.critical('Error while disconnecting shadow DOM observer', e);
25298
25326
  }
25299
25327
  }
25300
25328
  this.shadowObservers = [];
@@ -25482,7 +25510,7 @@
25482
25510
 
25483
25511
  this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
25484
25512
  } catch (e) {
25485
- logger$5.critical('Error while setting up mutation observer', e);
25513
+ logger$6.critical('Error while setting up mutation observer', e);
25486
25514
  }
25487
25515
  }
25488
25516
 
@@ -25497,7 +25525,7 @@
25497
25525
  );
25498
25526
  this.shadowDOMObserver.start();
25499
25527
  } catch (e) {
25500
- logger$5.critical('Error while setting up shadow DOM observer', e);
25528
+ logger$6.critical('Error while setting up shadow DOM observer', e);
25501
25529
  this.shadowDOMObserver = null;
25502
25530
  }
25503
25531
  }
@@ -25524,7 +25552,7 @@
25524
25552
  try {
25525
25553
  listener.target.removeEventListener(listener.event, listener.handler, listener.options);
25526
25554
  } catch (e) {
25527
- logger$5.critical('Error while removing event listener', e);
25555
+ logger$6.critical('Error while removing event listener', e);
25528
25556
  }
25529
25557
  }
25530
25558
  this.eventListeners = [];
@@ -25533,7 +25561,7 @@
25533
25561
  try {
25534
25562
  this.mutationObserver.disconnect();
25535
25563
  } catch (e) {
25536
- logger$5.critical('Error while disconnecting mutation observer', e);
25564
+ logger$6.critical('Error while disconnecting mutation observer', e);
25537
25565
  }
25538
25566
  this.mutationObserver = null;
25539
25567
  }
@@ -25542,7 +25570,7 @@
25542
25570
  try {
25543
25571
  this.shadowDOMObserver.stop();
25544
25572
  } catch (e) {
25545
- logger$5.critical('Error while stopping shadow DOM observer', e);
25573
+ logger$6.critical('Error while stopping shadow DOM observer', e);
25546
25574
  }
25547
25575
  this.shadowDOMObserver = null;
25548
25576
  }
@@ -25620,7 +25648,7 @@
25620
25648
 
25621
25649
  Autocapture.prototype.init = function() {
25622
25650
  if (!minDOMApisSupported()) {
25623
- logger$5.critical('Autocapture unavailable: missing required DOM APIs');
25651
+ logger$6.critical('Autocapture unavailable: missing required DOM APIs');
25624
25652
  return;
25625
25653
  }
25626
25654
  this.initPageListeners();
@@ -25660,7 +25688,7 @@
25660
25688
  try {
25661
25689
  return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
25662
25690
  } catch (err) {
25663
- logger$5.critical('Error while checking block URL regexes: ', err);
25691
+ logger$6.critical('Error while checking block URL regexes: ', err);
25664
25692
  return true;
25665
25693
  }
25666
25694
  }
@@ -25673,7 +25701,7 @@
25673
25701
  try {
25674
25702
  return urlMatchesRegexList(currentUrl, blockUrlRegexes);
25675
25703
  } catch (err) {
25676
- logger$5.critical('Error while checking block URL regexes: ', err);
25704
+ logger$6.critical('Error while checking block URL regexes: ', err);
25677
25705
  return true;
25678
25706
  }
25679
25707
  };
@@ -25811,7 +25839,7 @@
25811
25839
  return;
25812
25840
  }
25813
25841
 
25814
- logger$5.log('Initializing scroll depth tracking');
25842
+ logger$6.log('Initializing scroll depth tracking');
25815
25843
 
25816
25844
  this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
25817
25845
 
@@ -25832,12 +25860,12 @@
25832
25860
  };
25833
25861
 
25834
25862
  Autocapture.prototype.initClickTracking = function() {
25835
- win.removeEventListener(EV_CLICK, this.listenerClick);
25863
+ win.removeEventListener(EV_CLICK, this.listenerClick, true);
25836
25864
 
25837
25865
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
25838
25866
  return;
25839
25867
  }
25840
- logger$5.log('Initializing click tracking');
25868
+ logger$6.log('Initializing click tracking');
25841
25869
 
25842
25870
  this.listenerClick = function(ev) {
25843
25871
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
@@ -25845,7 +25873,7 @@
25845
25873
  }
25846
25874
  this.trackDomEvent(ev, MP_EV_CLICK);
25847
25875
  }.bind(this);
25848
- win.addEventListener(EV_CLICK, this.listenerClick);
25876
+ win.addEventListener(EV_CLICK, this.listenerClick, true);
25849
25877
  };
25850
25878
 
25851
25879
  Autocapture.prototype.initDeadClickTracking = function() {
@@ -25856,7 +25884,7 @@
25856
25884
  return;
25857
25885
  }
25858
25886
 
25859
- logger$5.log('Initializing dead click tracking');
25887
+ logger$6.log('Initializing dead click tracking');
25860
25888
  if (!this._deadClickTracker) {
25861
25889
  this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
25862
25890
  this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
@@ -25880,17 +25908,17 @@
25880
25908
  }
25881
25909
  this._deadClickTracker.trackClick(ev, normalizedConfig);
25882
25910
  }.bind(this);
25883
- win.addEventListener(EV_CLICK, this.listenerDeadClick);
25911
+ win.addEventListener(EV_CLICK, this.listenerDeadClick, true);
25884
25912
  }
25885
25913
  };
25886
25914
 
25887
25915
  Autocapture.prototype.initInputTracking = function() {
25888
- win.removeEventListener(EV_CHANGE, this.listenerChange);
25916
+ win.removeEventListener(EV_CHANGE, this.listenerChange, true);
25889
25917
 
25890
25918
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
25891
25919
  return;
25892
25920
  }
25893
- logger$5.log('Initializing input tracking');
25921
+ logger$6.log('Initializing input tracking');
25894
25922
 
25895
25923
  this.listenerChange = function(ev) {
25896
25924
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
@@ -25898,20 +25926,21 @@
25898
25926
  }
25899
25927
  this.trackDomEvent(ev, MP_EV_INPUT);
25900
25928
  }.bind(this);
25901
- win.addEventListener(EV_CHANGE, this.listenerChange);
25929
+ win.addEventListener(EV_CHANGE, this.listenerChange, true);
25902
25930
  };
25903
25931
 
25904
25932
  Autocapture.prototype.initPageviewTracking = function() {
25905
25933
  win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
25906
25934
 
25907
- if (!this.pageviewTrackingConfig()) {
25935
+ if (!this.pageviewTrackingConfig() && !this.mp.get_config('record_heatmap_data')) {
25908
25936
  return;
25909
25937
  }
25910
- logger$5.log('Initializing pageview tracking');
25938
+ logger$6.log('Initializing pageview tracking');
25911
25939
 
25912
25940
  var previousTrackedUrl = '';
25913
25941
  var tracked = false;
25914
- if (!this.currentUrlBlocked()) {
25942
+ // Track initial pageview if pageview tracking enabled OR heatmap recording is active
25943
+ if ((this.pageviewTrackingConfig() || this.mp.is_recording_heatmap_data()) && !this.currentUrlBlocked()) {
25915
25944
  tracked = this.mp.track_pageview(DEFAULT_PROPS);
25916
25945
  }
25917
25946
  if (tracked) {
@@ -25927,6 +25956,10 @@
25927
25956
  var shouldTrack = false;
25928
25957
  var didPathChange = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
25929
25958
  var trackPageviewOption = this.pageviewTrackingConfig();
25959
+ if (!trackPageviewOption && this.mp.is_recording_heatmap_data()) {
25960
+ trackPageviewOption = PAGEVIEW_OPTION_FULL_URL;
25961
+ }
25962
+
25930
25963
  if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
25931
25964
  shouldTrack = currentUrl !== previousTrackedUrl;
25932
25965
  } else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
@@ -25942,7 +25975,7 @@
25942
25975
  }
25943
25976
  if (didPathChange) {
25944
25977
  this.lastScrollCheckpoint = 0;
25945
- logger$5.log('Path change: re-initializing scroll depth checkpoints');
25978
+ logger$6.log('Path change: re-initializing scroll depth checkpoints');
25946
25979
  }
25947
25980
  }
25948
25981
  }.bind(this));
@@ -25950,14 +25983,14 @@
25950
25983
  };
25951
25984
 
25952
25985
  Autocapture.prototype.initRageClickTracking = function() {
25953
- win.removeEventListener(EV_CLICK, this.listenerRageClick);
25986
+ win.removeEventListener(EV_CLICK, this.listenerRageClick, true);
25954
25987
 
25955
25988
  var rageClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK);
25956
25989
  if (!rageClickConfig && !this.mp.get_config('record_heatmap_data')) {
25957
25990
  return;
25958
25991
  }
25959
25992
 
25960
- logger$5.log('Initializing rage click tracking');
25993
+ logger$6.log('Initializing rage click tracking');
25961
25994
  if (!this._rageClickTracker) {
25962
25995
  this._rageClickTracker = new RageClickTracker();
25963
25996
  }
@@ -25976,7 +26009,7 @@
25976
26009
  this.trackDomEvent(ev, MP_EV_RAGE_CLICK);
25977
26010
  }
25978
26011
  }.bind(this);
25979
- win.addEventListener(EV_CLICK, this.listenerRageClick);
26012
+ win.addEventListener(EV_CLICK, this.listenerRageClick, true);
25980
26013
  };
25981
26014
 
25982
26015
  Autocapture.prototype.initScrollTracking = function() {
@@ -25987,7 +26020,7 @@
25987
26020
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
25988
26021
  return;
25989
26022
  }
25990
- logger$5.log('Initializing scroll tracking');
26023
+ logger$6.log('Initializing scroll tracking');
25991
26024
  this.lastScrollCheckpoint = 0;
25992
26025
 
25993
26026
  var scrollTrackFunction = function() {
@@ -26024,7 +26057,7 @@
26024
26057
  }
26025
26058
  }
26026
26059
  } catch (err) {
26027
- logger$5.critical('Error while calculating scroll percentage', err);
26060
+ logger$6.critical('Error while calculating scroll percentage', err);
26028
26061
  }
26029
26062
  if (shouldTrack) {
26030
26063
  this.mp.track(MP_EV_SCROLL, props);
@@ -26037,12 +26070,12 @@
26037
26070
  };
26038
26071
 
26039
26072
  Autocapture.prototype.initSubmitTracking = function() {
26040
- win.removeEventListener(EV_SUBMIT, this.listenerSubmit);
26073
+ win.removeEventListener(EV_SUBMIT, this.listenerSubmit, true);
26041
26074
 
26042
26075
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
26043
26076
  return;
26044
26077
  }
26045
- logger$5.log('Initializing submit tracking');
26078
+ logger$6.log('Initializing submit tracking');
26046
26079
 
26047
26080
  this.listenerSubmit = function(ev) {
26048
26081
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
@@ -26050,7 +26083,7 @@
26050
26083
  }
26051
26084
  this.trackDomEvent(ev, MP_EV_SUBMIT);
26052
26085
  }.bind(this);
26053
- win.addEventListener(EV_SUBMIT, this.listenerSubmit);
26086
+ win.addEventListener(EV_SUBMIT, this.listenerSubmit, true);
26054
26087
  };
26055
26088
 
26056
26089
  Autocapture.prototype.initPageLeaveTracking = function() {
@@ -26064,7 +26097,7 @@
26064
26097
  return;
26065
26098
  }
26066
26099
 
26067
- logger$5.log('Initializing page visibility tracking.');
26100
+ logger$6.log('Initializing page visibility tracking.');
26068
26101
  this._initScrollDepthTracking();
26069
26102
  var previousTrackedUrl = _.info.currentUrl();
26070
26103
 
@@ -26106,7 +26139,7 @@
26106
26139
 
26107
26140
  Autocapture.prototype.stopDeadClickTracking = function() {
26108
26141
  if (this.listenerDeadClick) {
26109
- win.removeEventListener(EV_CLICK, this.listenerDeadClick);
26142
+ win.removeEventListener(EV_CLICK, this.listenerDeadClick, true);
26110
26143
  this.listenerDeadClick = null;
26111
26144
  }
26112
26145
 
@@ -26149,10 +26182,183 @@
26149
26182
  return win[TARGETING_GLOBAL_NAME];
26150
26183
  };
26151
26184
 
26185
+ var logger$2 = console_with_prefix('flags');
26186
+
26187
+ var MIXPANEL_FLAGS_DB_NAME = 'mixpanelFlagsDb';
26188
+ var FLAGS_STORE_NAME = 'mixpanelFlags';
26189
+
26190
+ // Keeping these two properties closeby, as adding additional stores to a DB in IndexedDB requires a version increment
26191
+ var FLAGS_VERSION_DATA = { version: 1, storeNames: [FLAGS_STORE_NAME] };
26192
+
26193
+ var PERSISTED_VARIANTS_KEY_PREFIX = 'persisted_variants_for_';
26194
+ var DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
26195
+
26196
+ var VariantLookupPolicy = Object.freeze({
26197
+ NETWORK_ONLY: 'networkOnly',
26198
+ NETWORK_FIRST: 'networkFirst',
26199
+ PERSISTENCE_UNTIL_NETWORK_SUCCESS: 'persistenceUntilNetworkSuccess'
26200
+ });
26201
+
26202
+ var VALID_POLICIES = [
26203
+ VariantLookupPolicy.NETWORK_ONLY,
26204
+ VariantLookupPolicy.NETWORK_FIRST,
26205
+ VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS
26206
+ ];
26207
+
26208
+ /**
26209
+ * Module for handling the storage and retrieval of persisted feature flag variants.
26210
+ */
26211
+ var FeatureFlagPersistence = function(persistenceConfig, token, isGloballyDisabled) {
26212
+ this.idb = new IDBStorageWrapper(MIXPANEL_FLAGS_DB_NAME, FLAGS_STORE_NAME, FLAGS_VERSION_DATA);
26213
+ this.persistenceConfig = persistenceConfig;
26214
+ this.persistedVariantsKey = PERSISTED_VARIANTS_KEY_PREFIX + token;
26215
+ this.isGloballyDisabled = isGloballyDisabled || function() { return false; };
26216
+ };
26217
+
26218
+ FeatureFlagPersistence.prototype.getPolicy = function() {
26219
+ if (this.isGloballyDisabled() || !this._isConfigValid()) {
26220
+ return VariantLookupPolicy.NETWORK_ONLY;
26221
+ }
26222
+ return this.persistenceConfig['variantLookupPolicy'];
26223
+ };
26224
+
26225
+ FeatureFlagPersistence.prototype.getTtlMs = function() {
26226
+ if (!this._isConfigValid()) {
26227
+ return DEFAULT_TTL_MS;
26228
+ }
26229
+ var configuredTtl = this.persistenceConfig['persistenceTtlMs'];
26230
+ return (configuredTtl === undefined || configuredTtl === null) ? DEFAULT_TTL_MS : configuredTtl;
26231
+ };
26232
+
26233
+ FeatureFlagPersistence.prototype._isConfigValid = function() {
26234
+ var config = this.persistenceConfig;
26235
+ if (!config) {
26236
+ return false;
26237
+ }
26238
+
26239
+ if (VALID_POLICIES.indexOf(config['variantLookupPolicy']) === -1) {
26240
+ logger$2.error('Invalid variantLookupPolicy:', config['variantLookupPolicy']);
26241
+ return false;
26242
+ }
26243
+
26244
+ if (config['persistenceTtlMs'] !== undefined &&
26245
+ config['persistenceTtlMs'] !== null &&
26246
+ config['persistenceTtlMs'] <= 0) {
26247
+ logger$2.error('If provided, persistenceTtlMs must be a positive number. Provided value:', config['persistenceTtlMs']);
26248
+ return false;
26249
+ }
26250
+
26251
+ return true;
26252
+ };
26253
+
26254
+ FeatureFlagPersistence.prototype.loadFlagsFromStorage = function(context) {
26255
+ var clearAndReturnNull = _.bind(function() {
26256
+ return this.clear().then(function() { return null; }).catch(function() { return null; });
26257
+ }, this);
26258
+
26259
+ if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
26260
+ return clearAndReturnNull();
26261
+ }
26262
+
26263
+ var ttlMs = this.getTtlMs();
26264
+
26265
+ return this.idb.init().then(_.bind(function() {
26266
+ return this.idb.getItem(this.persistedVariantsKey);
26267
+ }, this)).then(_.bind(function(data) {
26268
+ if (!data) {
26269
+ logger$2.log('No persisted variants found in IndexedDB');
26270
+ return null;
26271
+ }
26272
+
26273
+ if (ttlMs && Date.now() - data['persistedAt'] >= ttlMs) {
26274
+ logger$2.log('Persisted variants are expiring');
26275
+ return null;
26276
+ }
26277
+
26278
+ if (!context || data['distinctId'] !== context['distinct_id']) {
26279
+ logger$2.log('Persisted variants found, but for a different distinct_id so clearing.');
26280
+ return clearAndReturnNull();
26281
+ }
26282
+
26283
+ var persistedFlags = new Map();
26284
+ _.each(data['flagVariants'], function(variantData, key) {
26285
+ persistedFlags.set(key, {
26286
+ 'key': variantData['variant_key'],
26287
+ 'value': variantData['variant_value'],
26288
+ 'experiment_id': variantData['experiment_id'],
26289
+ 'is_experiment_active': variantData['is_experiment_active'],
26290
+ 'is_qa_tester': variantData['is_qa_tester'],
26291
+ 'variant_source': 'persistence',
26292
+ 'persisted_at_in_ms': data['persistedAt'],
26293
+ 'ttl_in_ms': ttlMs
26294
+ });
26295
+ });
26296
+
26297
+ logger$2.log('Loaded', persistedFlags.size, 'variants from IndexedDB for distinct_id', data['distinctId']);
26298
+
26299
+ return {
26300
+ flags: persistedFlags,
26301
+ pendingFirstTimeEvents: data['pendingFirstTimeEvents'] || {},
26302
+ persistedAtMs: data['persistedAt'],
26303
+ ttlMs: ttlMs
26304
+ };
26305
+ }, this)).catch(_.bind(function(error) {
26306
+ logger$2.error('Failed to load persisted variants from IndexedDB, so clearing', error);
26307
+ return clearAndReturnNull();
26308
+ }, this));
26309
+ };
26310
+
26311
+ FeatureFlagPersistence.prototype.save = function(context, flagsMap, pendingFirstTimeEvents) {
26312
+ if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
26313
+ return Promise.resolve();
26314
+ }
26315
+
26316
+ var flagVariants = {};
26317
+ flagsMap.forEach(function(variant, key) {
26318
+ flagVariants[key] = {
26319
+ 'variant_key': variant['key'],
26320
+ 'variant_value': variant['value'],
26321
+ 'experiment_id': variant['experiment_id'],
26322
+ 'is_experiment_active': variant['is_experiment_active'],
26323
+ 'is_qa_tester': variant['is_qa_tester']
26324
+ };
26325
+ });
26326
+
26327
+ var data = {
26328
+ 'persistedAt': Date.now(),
26329
+ 'distinctId': context && context['distinct_id'],
26330
+ 'context': context,
26331
+ 'flagVariants': flagVariants,
26332
+ 'pendingFirstTimeEvents': pendingFirstTimeEvents || {}
26333
+ };
26334
+
26335
+ return this.idb.init().then(_.bind(function() {
26336
+ return this.idb.setItem(this.persistedVariantsKey, data);
26337
+ }, this)).then(function() {
26338
+ logger$2.log('Saved', flagsMap.size, 'variants to IndexedDB for distinct_id', data['distinctId']);
26339
+ }).catch(function(error) {
26340
+ logger$2.error('Failed to persist variants to IndexedDB:', error);
26341
+ });
26342
+ };
26343
+
26344
+ FeatureFlagPersistence.prototype.clear = function() {
26345
+ if (this.isGloballyDisabled()) {
26346
+ return Promise.resolve();
26347
+ }
26348
+ return this.idb.init().then(_.bind(function() {
26349
+ return this.idb.removeItem(this.persistedVariantsKey);
26350
+ }, this)).then(function() {
26351
+ logger$2.log('Cleared persisted variants from IndexedDB');
26352
+ }).catch(function(error) {
26353
+ logger$2.error('Failed to clear persisted variants from IndexedDB:', error);
26354
+ });
26355
+ };
26356
+
26152
26357
  var logger$1 = console_with_prefix('flags');
26153
26358
  var FLAGS_CONFIG_KEY = 'flags';
26154
26359
 
26155
26360
  var CONFIG_CONTEXT = 'context';
26361
+ var CONFIG_PERSISTENCE = 'persistence';
26156
26362
  var CONFIG_DEFAULTS = {};
26157
26363
  CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
26158
26364
 
@@ -26175,6 +26381,13 @@
26175
26381
  return eventKey.split(':')[0];
26176
26382
  };
26177
26383
 
26384
+ var withFallbackSource = function(fallback) {
26385
+ if (_.isObject(fallback)) {
26386
+ return _.extend({}, fallback, {'variant_source': 'fallback'});
26387
+ }
26388
+ return {'value': fallback, 'variant_source': 'fallback'};
26389
+ };
26390
+
26178
26391
  /**
26179
26392
  * FeatureFlagManager: support for Mixpanel's feature flagging product
26180
26393
  * @constructor
@@ -26197,13 +26410,63 @@
26197
26410
  }
26198
26411
 
26199
26412
  this.flags = null;
26200
- this.fetchFlags().catch(function() {
26201
- logger$1.error('Error fetching flags during init');
26202
- });
26203
-
26204
26413
  this.trackedFeatures = new Set();
26205
26414
  this.pendingFirstTimeEvents = {};
26206
26415
  this.activatedFirstTimeEvents = {};
26416
+ this._loadedPersistedAtMs = null;
26417
+ this._loadedTtlMs = null;
26418
+
26419
+ this.persistence = new FeatureFlagPersistence(
26420
+ this.getConfig(CONFIG_PERSISTENCE),
26421
+ this.getMpConfig('token'),
26422
+ _.bind(function() { return this.getMpConfig('disable_persistence'); }, this)
26423
+ );
26424
+
26425
+ this.persistenceLoadedPromise = this.persistence.loadFlagsFromStorage(this._buildContext())
26426
+ .then(_.bind(function(loaded) {
26427
+ if (loaded) {
26428
+ this.flags = loaded.flags;
26429
+ this.pendingFirstTimeEvents = loaded.pendingFirstTimeEvents;
26430
+ this._loadedPersistedAtMs = loaded.persistedAtMs;
26431
+ this._loadedTtlMs = loaded.ttlMs;
26432
+ }
26433
+ }, this));
26434
+
26435
+ return this.persistenceLoadedPromise
26436
+ .then(_.bind(function() {
26437
+ return this.fetchFlags();
26438
+ }, this))
26439
+ .catch(function() {
26440
+ logger$1.error('Error initializing feature flags');
26441
+ });
26442
+ };
26443
+
26444
+ FeatureFlagManager.prototype._buildContext = function() {
26445
+ return _.extend(
26446
+ {'distinct_id': this.getMpProperty('distinct_id'), 'device_id': this.getMpProperty('$device_id')},
26447
+ this.getConfig(CONFIG_CONTEXT)
26448
+ );
26449
+ };
26450
+
26451
+ FeatureFlagManager.prototype.reset = function() {
26452
+ if (!this.persistence) {
26453
+ return Promise.resolve();
26454
+ }
26455
+
26456
+ this.flags = null;
26457
+ this.pendingFirstTimeEvents = {};
26458
+ this.activatedFirstTimeEvents = {};
26459
+ this.trackedFeatures = new Set();
26460
+ this.fetchPromise = null;
26461
+ this._fetchInProgressStartTime = null;
26462
+ this._loadedPersistedAtMs = null;
26463
+ this._loadedTtlMs = null;
26464
+
26465
+ return this.persistence.clear().then(_.bind(function() {
26466
+ return this.fetchFlags();
26467
+ }, this)).catch(function() {
26468
+ logger$1.error('Error during flags reset');
26469
+ });
26207
26470
  };
26208
26471
 
26209
26472
  FeatureFlagManager.prototype.getFullConfig = function() {
@@ -26260,12 +26523,11 @@
26260
26523
  return Promise.resolve();
26261
26524
  }
26262
26525
 
26263
- var distinctId = this.getMpProperty('distinct_id');
26264
- var deviceId = this.getMpProperty('$device_id');
26526
+ var context = this._buildContext();
26527
+ var distinctId = context['distinct_id'];
26265
26528
  var traceparent = generateTraceparent();
26266
26529
  logger$1.log('Fetching flags for distinct ID: ' + distinctId);
26267
26530
 
26268
- var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
26269
26531
  var searchParams = new URLSearchParams();
26270
26532
  searchParams.set('context', JSON.stringify(context));
26271
26533
  searchParams.set('token', this.getMpConfig('token'));
@@ -26315,7 +26577,8 @@
26315
26577
  'value': data['variant_value'],
26316
26578
  'experiment_id': data['experiment_id'],
26317
26579
  'is_experiment_active': data['is_experiment_active'],
26318
- 'is_qa_tester': data['is_qa_tester']
26580
+ 'is_qa_tester': data['is_qa_tester'],
26581
+ 'variant_source': 'network'
26319
26582
  });
26320
26583
  }
26321
26584
  }, this);
@@ -26357,10 +26620,15 @@
26357
26620
  }
26358
26621
 
26359
26622
  this.flags = flags;
26623
+ this.trackedFeatures = new Set();
26360
26624
  this.pendingFirstTimeEvents = pendingFirstTimeEvents;
26625
+ this._loadedPersistedAtMs = null;
26626
+ this._loadedTtlMs = null;
26361
26627
  this._traceparent = traceparent;
26362
26628
 
26363
26629
  this._loadTargetingIfNeeded();
26630
+
26631
+ this.persistence.save(context, this.flags, this.pendingFirstTimeEvents);
26364
26632
  }.bind(this)).catch(function(error) {
26365
26633
  if (this._fetchInProgressStartTime) {
26366
26634
  this.markFetchComplete();
@@ -26520,6 +26788,7 @@
26520
26788
  };
26521
26789
 
26522
26790
  this.flags.set(flagKey, newVariant);
26791
+ this.trackedFeatures.delete(flagKey);
26523
26792
  this.activatedFirstTimeEvents[eventKey] = true;
26524
26793
 
26525
26794
  this.recordFirstTimeEvent(
@@ -26531,8 +26800,8 @@
26531
26800
  };
26532
26801
 
26533
26802
  FeatureFlagManager.prototype.getFirstTimeEventApiRoute = function(flagId) {
26534
- // Construct URL: {api_host}/flags/{flagId}/first-time-events
26535
- return this.getFullApiRoute() + '/' + flagId + '/first-time-events';
26803
+ var base = this.getFullApiRoute().replace(/\/$/, '');
26804
+ return base + '/' + flagId + '/first-time-events';
26536
26805
  };
26537
26806
 
26538
26807
  FeatureFlagManager.prototype.recordFirstTimeEvent = function(flagId, projectId, firstTimeEventHash) {
@@ -26569,35 +26838,106 @@
26569
26838
  };
26570
26839
 
26571
26840
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
26572
- if (!this.fetchPromise) {
26841
+ if (!this.persistenceLoadedPromise) {
26573
26842
  return new Promise(function(resolve) {
26574
26843
  logger$1.critical('Feature Flags not initialized');
26575
- resolve(fallback);
26844
+ resolve(withFallbackSource(fallback));
26576
26845
  });
26577
26846
  }
26578
26847
 
26579
- return this.fetchPromise.then(function() {
26580
- return this.getVariantSync(featureName, fallback);
26581
- }.bind(this)).catch(function(error) {
26582
- logger$1.error(error);
26583
- return fallback;
26584
- });
26848
+ var policy = this.persistence.getPolicy();
26849
+
26850
+ return this.persistenceLoadedPromise.then(_.bind(function() {
26851
+ // 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.
26852
+ if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
26853
+ if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
26854
+ return this.getVariantSync(featureName, fallback);
26855
+ }
26856
+ if (!this.fetchPromise) {
26857
+ return withFallbackSource(fallback);
26858
+ }
26859
+ return this.fetchPromise.then(_.bind(function() {
26860
+ return this.getVariantSync(featureName, fallback);
26861
+ }, this)).catch(function(error) {
26862
+ logger$1.error(error);
26863
+ return withFallbackSource(fallback);
26864
+ });
26865
+ }
26866
+
26867
+ var serve = _.bind(function() { return this.getVariantSync(featureName, fallback); }, this);
26868
+ if (!this.fetchPromise) {
26869
+ return withFallbackSource(fallback);
26870
+ }
26871
+ return this.fetchPromise.then(serve).catch(serve);
26872
+ }, this));
26873
+ };
26874
+
26875
+ FeatureFlagManager.prototype._loadedPersistenceIsStale = function() {
26876
+ if (!this._loadedPersistedAtMs || !this._loadedTtlMs) {
26877
+ return false;
26878
+ }
26879
+ return Date.now() - this._loadedPersistedAtMs >= this._loadedTtlMs;
26585
26880
  };
26586
26881
 
26587
26882
  FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
26883
+ if (this._loadedPersistenceIsStale()) {
26884
+ logger$1.log('Loaded persisted variants are past TTL so returning fallback for "' + featureName + '"');
26885
+ return withFallbackSource(fallback);
26886
+ }
26588
26887
  if (!this.areFlagsReady()) {
26589
26888
  logger$1.log('Flags not loaded yet');
26590
- return fallback;
26889
+ return withFallbackSource(fallback);
26591
26890
  }
26592
26891
  var feature = this.flags.get(featureName);
26593
26892
  if (!feature) {
26594
26893
  logger$1.log('No flag found: "' + featureName + '"');
26595
- return fallback;
26894
+ return withFallbackSource(fallback);
26596
26895
  }
26597
26896
  this.trackFeatureCheck(featureName, feature);
26598
26897
  return feature;
26599
26898
  };
26600
26899
 
26900
+ FeatureFlagManager.prototype.getAllVariants = function() {
26901
+ if (!this.persistenceLoadedPromise) {
26902
+ logger$1.critical('Feature Flags not initialized');
26903
+ return Promise.resolve(new Map());
26904
+ }
26905
+
26906
+ var policy = this.persistence.getPolicy();
26907
+
26908
+ return this.persistenceLoadedPromise.then(_.bind(function() {
26909
+ // 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.
26910
+ if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
26911
+ if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
26912
+ return this.getAllVariantsSync();
26913
+ }
26914
+ if (!this.fetchPromise) {
26915
+ return new Map();
26916
+ }
26917
+ return this.fetchPromise.then(_.bind(function() {
26918
+ return this.getAllVariantsSync();
26919
+ }, this)).catch(function(error) {
26920
+ logger$1.error(error);
26921
+ return new Map();
26922
+ });
26923
+ }
26924
+
26925
+ var serve = _.bind(this.getAllVariantsSync, this);
26926
+ if (!this.fetchPromise) {
26927
+ return new Map();
26928
+ }
26929
+ return this.fetchPromise.then(serve).catch(serve);
26930
+ }, this));
26931
+ };
26932
+
26933
+ FeatureFlagManager.prototype.getAllVariantsSync = function() {
26934
+ if (this._loadedPersistenceIsStale()) {
26935
+ logger$1.log('Loaded persisted variants are past TTL so returning empty Map');
26936
+ return new Map();
26937
+ }
26938
+ return this.flags || new Map();
26939
+ };
26940
+
26601
26941
  FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
26602
26942
  return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
26603
26943
  return feature['value'];
@@ -26636,6 +26976,10 @@
26636
26976
  return val;
26637
26977
  };
26638
26978
 
26979
+ function isPresent(v) {
26980
+ return v !== undefined && v !== null;
26981
+ }
26982
+
26639
26983
  FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
26640
26984
  if (this.trackedFeatures.has(featureName)) {
26641
26985
  return;
@@ -26646,21 +26990,30 @@
26646
26990
  'Experiment name': featureName,
26647
26991
  'Variant name': feature['key'],
26648
26992
  '$experiment_type': 'feature_flag',
26649
- 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
26650
- 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
26993
+ 'Variant fetch start time': isPresent(this._fetchStartTime) ? new Date(this._fetchStartTime).toISOString() : null,
26994
+ 'Variant fetch complete time': isPresent(this._fetchCompleteTime) ? new Date(this._fetchCompleteTime).toISOString() : null,
26651
26995
  'Variant fetch latency (ms)': this._fetchLatency,
26652
26996
  'Variant fetch traceparent': this._traceparent,
26653
26997
  };
26654
26998
 
26655
- if (feature['experiment_id'] !== 'undefined') {
26999
+ if (isPresent(feature['experiment_id'])) {
26656
27000
  trackingProperties['$experiment_id'] = feature['experiment_id'];
26657
27001
  }
26658
- if (feature['is_experiment_active'] !== 'undefined') {
27002
+ if (isPresent(feature['is_experiment_active'])) {
26659
27003
  trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
26660
27004
  }
26661
- if (feature['is_qa_tester'] !== 'undefined') {
27005
+ if (isPresent(feature['is_qa_tester'])) {
26662
27006
  trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
26663
27007
  }
27008
+ if (isPresent(feature['variant_source'])) {
27009
+ trackingProperties['$variant_source'] = feature['variant_source'];
27010
+ }
27011
+ if (isPresent(feature['persisted_at_in_ms'])) {
27012
+ trackingProperties['$persisted_at_in_ms'] = feature['persisted_at_in_ms'];
27013
+ }
27014
+ if (isPresent(feature['ttl_in_ms'])) {
27015
+ trackingProperties['$ttl_in_ms'] = feature['ttl_in_ms'];
27016
+ }
26664
27017
 
26665
27018
  this.track('$experiment_started', trackingProperties);
26666
27019
  };
@@ -26684,6 +27037,8 @@
26684
27037
  FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
26685
27038
  FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
26686
27039
  FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
27040
+ FeatureFlagManager.prototype['get_all_variants'] = FeatureFlagManager.prototype.getAllVariants;
27041
+ FeatureFlagManager.prototype['get_all_variants_sync'] = FeatureFlagManager.prototype.getAllVariantsSync;
26687
27042
  FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
26688
27043
  FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
26689
27044
  FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
@@ -26736,7 +27091,7 @@
26736
27091
  return PromisePolyfill.resolve(false);
26737
27092
  }
26738
27093
 
26739
- var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
27094
+ var recording_registry_idb = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_REGISTRY_STORE_NAME, RECORDER_VERSION_DATA);
26740
27095
  var tab_id = this.getTabId();
26741
27096
  return recording_registry_idb.init()
26742
27097
  .then(function () {
@@ -28663,6 +29018,7 @@
28663
29018
  'disable_all_events': false,
28664
29019
  'identify_called': false
28665
29020
  };
29021
+ this._remote_settings_strict_disabled = false;
28666
29022
 
28667
29023
  // set up request queueing/batching
28668
29024
  this.request_batchers = {};
@@ -28737,9 +29093,6 @@
28737
29093
  this.flags.init();
28738
29094
  this['flags'] = this.flags;
28739
29095
 
28740
- this.autocapture = new Autocapture(this);
28741
- this.autocapture.init();
28742
-
28743
29096
  this._init_tab_id();
28744
29097
 
28745
29098
  // Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
@@ -28751,6 +29104,9 @@
28751
29104
  } else {
28752
29105
  this.__session_recording_init_promise = this._check_and_start_session_recording();
28753
29106
  }
29107
+
29108
+ this.autocapture = new Autocapture(this);
29109
+ this.autocapture.init();
28754
29110
  };
28755
29111
 
28756
29112
  /**
@@ -28797,9 +29153,19 @@
28797
29153
  return this.recorderManager.checkAndStartSessionRecording(force_start);
28798
29154
  });
28799
29155
 
28800
- MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
28801
- return this.recorderManager.startRecordingOnEvent(event_name, properties);
28802
- };
29156
+ MixpanelLib.prototype._start_recording_on_event = safewrap(function(event_name, properties) {
29157
+ // Wait for recording init to complete before evaluating event triggers.
29158
+ // This ensures recording_event_triggers config is fully loaded when remote settings are used.
29159
+ if (this.__session_recording_init_promise) {
29160
+ this.__session_recording_init_promise.then(_.bind(function() {
29161
+ // In strict mode, skip recording if remote settings failed
29162
+ if (this._remote_settings_strict_disabled) {
29163
+ return;
29164
+ }
29165
+ return this.recorderManager.startRecordingOnEvent(event_name, properties);
29166
+ }, this));
29167
+ }
29168
+ });
28803
29169
 
28804
29170
  MixpanelLib.prototype.start_session_recording = function () {
28805
29171
  return this._check_and_start_session_recording(true);
@@ -29098,6 +29464,7 @@
29098
29464
  var disableRecordingIfStrict = function() {
29099
29465
  if (mode === 'strict') {
29100
29466
  self.set_config({'record_sessions_percent': 0});
29467
+ self._remote_settings_strict_disabled = true;
29101
29468
  }
29102
29469
  };
29103
29470
 
@@ -29723,6 +30090,10 @@
29723
30090
  properties
29724
30091
  );
29725
30092
 
30093
+ if (this.is_recording_heatmap_data()) {
30094
+ event_properties['$captured_for_heatmap'] = true;
30095
+ }
30096
+
29726
30097
  return this.track(event_name, event_properties);
29727
30098
  });
29728
30099
 
@@ -30066,6 +30437,7 @@
30066
30437
  '$device_id': uuid
30067
30438
  }, '');
30068
30439
  this._check_and_start_session_recording();
30440
+ this.flags.reset();
30069
30441
  };
30070
30442
 
30071
30443
  /**