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
@@ -28,7 +28,7 @@
28
28
 
29
29
  var Config = {
30
30
  DEBUG: false,
31
- LIB_VERSION: '2.78.0'
31
+ LIB_VERSION: '2.79.0'
32
32
  };
33
33
 
34
34
  // Window global names for async modules
@@ -19126,6 +19126,7 @@
19126
19126
  var console_with_prefix = function(prefix) {
19127
19127
  return {
19128
19128
  log: log_func_with_prefix(console$1.log, prefix),
19129
+ warn: log_func_with_prefix(console$1.warn, prefix),
19129
19130
  error: log_func_with_prefix(console$1.error, prefix),
19130
19131
  critical: log_func_with_prefix(console$1.critical, prefix)
19131
19132
  };
@@ -20072,7 +20073,8 @@
20072
20073
  if (_localStorageSupported !== null && !forceCheck) {
20073
20074
  return _localStorageSupported;
20074
20075
  }
20075
- return _localStorageSupported = _testStorageSupported(storage || win.localStorage);
20076
+
20077
+ return _localStorageSupported = _testStorageSupported(storage);
20076
20078
  };
20077
20079
 
20078
20080
  var _sessionStorageSupported = null;
@@ -20080,7 +20082,8 @@
20080
20082
  if (_sessionStorageSupported !== null && !forceCheck) {
20081
20083
  return _sessionStorageSupported;
20082
20084
  }
20083
- return _sessionStorageSupported = _testStorageSupported(storage || win.sessionStorage);
20085
+
20086
+ return _sessionStorageSupported = _testStorageSupported(storage);
20084
20087
  };
20085
20088
 
20086
20089
  function _storageWrapper(storage, name, is_supported_fn) {
@@ -20130,17 +20133,26 @@
20130
20133
  };
20131
20134
  }
20132
20135
 
20133
- // Safari errors out accessing localStorage/sessionStorage when cookies are disabled,
20134
- // so create dummy storage wrappers that silently fail as a fallback.
20135
- var windowLocalStorage = null, windowSessionStorage = null;
20136
- try {
20137
- windowLocalStorage = win.localStorage;
20138
- windowSessionStorage = win.sessionStorage;
20139
- // eslint-disable-next-line no-empty
20140
- } catch (_err) {}
20136
+ // Safari and other browsers may error out accessing localStorage/sessionStorage
20137
+ // when cookies are disabled, so wrap access in a try-catch.
20138
+ var getLocalStorage = function() {
20139
+ try {
20140
+ return win.localStorage; // eslint-disable-line no-restricted-properties
20141
+ } catch (_err) {
20142
+ return null;
20143
+ }
20144
+ };
20145
+
20146
+ var getSessionStorage = function() {
20147
+ try {
20148
+ return win.sessionStorage; // eslint-disable-line no-restricted-properties
20149
+ } catch (_err) {
20150
+ return null;
20151
+ }
20152
+ };
20141
20153
 
20142
- _.localStorage = _storageWrapper(windowLocalStorage, 'localStorage', localStorageSupported);
20143
- _.sessionStorage = _storageWrapper(windowSessionStorage, 'sessionStorage', sessionStorageSupported);
20154
+ _.localStorage = _storageWrapper(getLocalStorage(), 'localStorage', localStorageSupported);
20155
+ _.sessionStorage = _storageWrapper(getSessionStorage(), 'sessionStorage', sessionStorageSupported);
20144
20156
 
20145
20157
  _.register_event = (function() {
20146
20158
  // written by Dean Edwards, 2005
@@ -20809,29 +20821,26 @@
20809
20821
  _['toArray'] = _.toArray;
20810
20822
  _['NPO'] = NpoPromise;
20811
20823
 
20812
- var MIXPANEL_DB_NAME = 'mixpanelBrowserDb';
20813
-
20814
- var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
20815
- var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
20816
-
20817
- // note: increment the version number when adding new object stores
20818
- var DB_VERSION = 1;
20819
- var OBJECT_STORES = [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME];
20820
-
20821
20824
  /**
20822
20825
  * @type {import('./wrapper').StorageWrapper}
20823
20826
  */
20824
- var IDBStorageWrapper = function (storeName) {
20827
+ var IDBStorageWrapper = function (dbName, storeName, versionData) {
20828
+ this.dbName = dbName;
20829
+ this.storeName = storeName;
20830
+ this.version = versionData.version;
20831
+ this.storeNamesInDb = versionData.storeNames;
20825
20832
  /**
20826
20833
  * @type {Promise<IDBDatabase>|null}
20827
20834
  */
20828
20835
  this.dbPromise = null;
20829
- this.storeName = storeName;
20830
20836
  };
20831
20837
 
20832
20838
  IDBStorageWrapper.prototype._openDb = function () {
20839
+ var dbName = this.dbName;
20840
+ var version = this.version;
20841
+ var storeNamesInDb = this.storeNamesInDb;
20833
20842
  return new PromisePolyfill(function (resolve, reject) {
20834
- var openRequest = win.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
20843
+ var openRequest = win.indexedDB.open(dbName, version);
20835
20844
  openRequest['onerror'] = function () {
20836
20845
  reject(openRequest.error);
20837
20846
  };
@@ -20843,8 +20852,10 @@
20843
20852
  openRequest['onupgradeneeded'] = function (ev) {
20844
20853
  var db = ev.target.result;
20845
20854
 
20846
- OBJECT_STORES.forEach(function (storeName) {
20847
- db.createObjectStore(storeName);
20855
+ storeNamesInDb.forEach(function (storeName) {
20856
+ if (!db.objectStoreNames.contains(storeName)) {
20857
+ db.createObjectStore(storeName);
20858
+ }
20848
20859
  });
20849
20860
  };
20850
20861
  });
@@ -20936,6 +20947,16 @@
20936
20947
  });
20937
20948
  };
20938
20949
 
20950
+ var MIXPANEL_BROWSER_DB_NAME = 'mixpanelBrowserDb';
20951
+ var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
20952
+ var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
20953
+
20954
+ // Keeping these two properties closeby, as adding additional stores to a DB in IndexedDB requires a version increment
20955
+ var RECORDER_VERSION_DATA = {
20956
+ version: 1,
20957
+ storeNames: [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME]
20958
+ };
20959
+
20939
20960
  /**
20940
20961
  * GDPR utils
20941
20962
  *
@@ -21236,7 +21257,7 @@
21236
21257
  };
21237
21258
  }
21238
21259
 
21239
- var logger$8 = console_with_prefix('lock');
21260
+ var logger$9 = console_with_prefix('lock');
21240
21261
 
21241
21262
  /**
21242
21263
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -21262,7 +21283,7 @@
21262
21283
  options = options || {};
21263
21284
 
21264
21285
  this.storageKey = key;
21265
- this.storage = options.storage || win.localStorage;
21286
+ this.storage = options.storage || getLocalStorage();
21266
21287
  this.pollIntervalMS = options.pollIntervalMS || 100;
21267
21288
  this.timeoutMS = options.timeoutMS || 2000;
21268
21289
 
@@ -21288,7 +21309,7 @@
21288
21309
 
21289
21310
  var delay = function(cb) {
21290
21311
  if (new Date().getTime() - startTime > timeoutMS) {
21291
- logger$8.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21312
+ logger$9.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21292
21313
  storage.removeItem(keyZ);
21293
21314
  storage.removeItem(keyY);
21294
21315
  loop();
@@ -21390,10 +21411,13 @@
21390
21411
  * @type {import('./wrapper').StorageWrapper}
21391
21412
  */
21392
21413
  var LocalStorageWrapper = function (storageOverride) {
21393
- this.storage = storageOverride || win.localStorage;
21414
+ this.storage = storageOverride || getLocalStorage();
21394
21415
  };
21395
21416
 
21396
21417
  LocalStorageWrapper.prototype.init = function () {
21418
+ if (!this.storage) {
21419
+ return PromisePolyfill.reject(new Error('localStorage is not available'));
21420
+ }
21397
21421
  return PromisePolyfill.resolve();
21398
21422
  };
21399
21423
 
@@ -21435,7 +21459,7 @@
21435
21459
  }, this));
21436
21460
  };
21437
21461
 
21438
- var logger$7 = console_with_prefix('batch');
21462
+ var logger$8 = console_with_prefix('batch');
21439
21463
 
21440
21464
  /**
21441
21465
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -21460,11 +21484,11 @@
21460
21484
  if (this.usePersistence) {
21461
21485
  this.queueStorage = options.queueStorage || new LocalStorageWrapper();
21462
21486
  this.lock = new SharedLock(storageKey, {
21463
- storage: options.sharedLockStorage || win.localStorage,
21487
+ storage: options.sharedLockStorage,
21464
21488
  timeoutMS: options.sharedLockTimeoutMS,
21465
21489
  });
21466
21490
  }
21467
- this.reportError = options.errorReporter || _.bind(logger$7.error, logger$7);
21491
+ this.reportError = options.errorReporter || _.bind(logger$8.error, logger$8);
21468
21492
 
21469
21493
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
21470
21494
 
@@ -21797,7 +21821,7 @@
21797
21821
  // maximum interval between request retries after exponential backoff
21798
21822
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
21799
21823
 
21800
- var logger$6 = console_with_prefix('batch');
21824
+ var logger$7 = console_with_prefix('batch');
21801
21825
 
21802
21826
  /**
21803
21827
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -21925,7 +21949,7 @@
21925
21949
  */
21926
21950
  RequestBatcher.prototype.flush = function(options) {
21927
21951
  if (this.requestInProgress) {
21928
- logger$6.log('Flush: Request already in progress');
21952
+ logger$7.log('Flush: Request already in progress');
21929
21953
  return PromisePolyfill.resolve();
21930
21954
  }
21931
21955
 
@@ -22102,7 +22126,7 @@
22102
22126
  if (options.unloading) {
22103
22127
  requestOptions.transport = 'sendBeacon';
22104
22128
  }
22105
- logger$6.log('MIXPANEL REQUEST:', dataForRequest);
22129
+ logger$7.log('MIXPANEL REQUEST:', dataForRequest);
22106
22130
  return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
22107
22131
  }, this))
22108
22132
  .catch(_.bind(function(err) {
@@ -22115,7 +22139,7 @@
22115
22139
  * Log error to global logger and optional user-defined logger.
22116
22140
  */
22117
22141
  RequestBatcher.prototype.reportError = function(msg, err) {
22118
- logger$6.error.apply(logger$6.error, arguments);
22142
+ logger$7.error.apply(logger$7.error, arguments);
22119
22143
  if (this.errorReporter) {
22120
22144
  try {
22121
22145
  if (!(err instanceof Error)) {
@@ -22123,7 +22147,7 @@
22123
22147
  }
22124
22148
  this.errorReporter(msg, err);
22125
22149
  } catch(err) {
22126
- logger$6.error(err);
22150
+ logger$7.error(err);
22127
22151
  }
22128
22152
  }
22129
22153
  };
@@ -22268,7 +22292,7 @@
22268
22292
 
22269
22293
  var MAX_DEPTH = 5;
22270
22294
 
22271
- var logger$5 = console_with_prefix('autocapture');
22295
+ var logger$6 = console_with_prefix('autocapture');
22272
22296
 
22273
22297
 
22274
22298
  function getClasses(el) {
@@ -22532,7 +22556,7 @@
22532
22556
  return false;
22533
22557
  }
22534
22558
  } catch (err) {
22535
- logger$5.critical('Error while checking element in allowElementCallback', err);
22559
+ logger$6.critical('Error while checking element in allowElementCallback', err);
22536
22560
  return false;
22537
22561
  }
22538
22562
  }
@@ -22549,7 +22573,7 @@
22549
22573
  return true;
22550
22574
  }
22551
22575
  } catch (err) {
22552
- logger$5.critical('Error while checking selector: ' + sel, err);
22576
+ logger$6.critical('Error while checking selector: ' + sel, err);
22553
22577
  }
22554
22578
  }
22555
22579
  return false;
@@ -22564,7 +22588,7 @@
22564
22588
  return true;
22565
22589
  }
22566
22590
  } catch (err) {
22567
- logger$5.critical('Error while checking element in blockElementCallback', err);
22591
+ logger$6.critical('Error while checking element in blockElementCallback', err);
22568
22592
  return true;
22569
22593
  }
22570
22594
  }
@@ -22578,7 +22602,7 @@
22578
22602
  return true;
22579
22603
  }
22580
22604
  } catch (err) {
22581
- logger$5.critical('Error while checking selector: ' + sel, err);
22605
+ logger$6.critical('Error while checking selector: ' + sel, err);
22582
22606
  }
22583
22607
  }
22584
22608
  }
@@ -23134,7 +23158,7 @@
23134
23158
  *
23135
23159
  */
23136
23160
 
23137
- var logger$4 = console_with_prefix('network-plugin');
23161
+ var logger$5 = console_with_prefix('network-plugin');
23138
23162
 
23139
23163
  /**
23140
23164
  * Get the time origin for converting performance timestamps to absolute timestamps.
@@ -23286,7 +23310,7 @@
23286
23310
  return str;
23287
23311
  }
23288
23312
  if (str.length > MAX_BODY_SIZE) {
23289
- logger$4.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
23313
+ logger$5.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
23290
23314
  return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
23291
23315
  }
23292
23316
  return str;
@@ -23300,7 +23324,7 @@
23300
23324
  */
23301
23325
  function initPerformanceObserver(cb, win, options) {
23302
23326
  if (!win.PerformanceObserver) {
23303
- logger$4.error('PerformanceObserver not supported');
23327
+ logger$5.error('PerformanceObserver not supported');
23304
23328
  return function() {
23305
23329
  //
23306
23330
  };
@@ -23453,7 +23477,7 @@
23453
23477
  attempt = 0;
23454
23478
  }
23455
23479
  if (attempt > 10) {
23456
- logger$4.error('Cannot find performance entry');
23480
+ logger$5.error('Cannot find performance entry');
23457
23481
  return Promise.resolve(null);
23458
23482
  }
23459
23483
  var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
@@ -23574,7 +23598,7 @@
23574
23598
  )
23575
23599
  .then(function(entry) {
23576
23600
  if (!entry) {
23577
- logger$4.error('Failed to get performance entry for XHR request to ' + req.url);
23601
+ logger$5.error('Failed to get performance entry for XHR request to ' + req.url);
23578
23602
  return;
23579
23603
  }
23580
23604
  /** @type {NetworkRequest} */
@@ -23594,7 +23618,7 @@
23594
23618
  cb({ requests: [request] });
23595
23619
  })
23596
23620
  .catch(function(e) {
23597
- logger$4.error('Error recording XHR request to ' + req.url + ': ' + String(e));
23621
+ logger$5.error('Error recording XHR request to ' + req.url + ': ' + String(e));
23598
23622
  });
23599
23623
  });
23600
23624
 
@@ -23686,7 +23710,7 @@
23686
23710
  })
23687
23711
  .then(function(entry) {
23688
23712
  if (!entry) {
23689
- logger$4.error('Failed to get performance entry for fetch request to ' + req.url);
23713
+ logger$5.error('Failed to get performance entry for fetch request to ' + req.url);
23690
23714
  return;
23691
23715
  }
23692
23716
  /** @type {NetworkRequest} */
@@ -23706,7 +23730,7 @@
23706
23730
  cb({ requests: [request] });
23707
23731
  })
23708
23732
  .catch(function (e) {
23709
- logger$4.error('Error recording fetch request to ' + req.url + ': ' + String(e));
23733
+ logger$5.error('Error recording fetch request to ' + req.url + ': ' + String(e));
23710
23734
  });
23711
23735
 
23712
23736
  return originalFetchPromise;
@@ -23779,7 +23803,7 @@
23779
23803
  */
23780
23804
 
23781
23805
 
23782
- var logger$3 = console_with_prefix('recorder');
23806
+ var logger$4 = console_with_prefix('recorder');
23783
23807
  var CompressionStream = win['CompressionStream'];
23784
23808
 
23785
23809
  var RECORDER_BATCHER_LIB_CONFIG = {
@@ -23876,11 +23900,11 @@
23876
23900
 
23877
23901
  // disable persistence if localStorage is not supported
23878
23902
  // request-queue will automatically disable persistence if indexedDB fails to initialize
23879
- var usePersistence = localStorageSupported(options.sharedLockStorage, true) && !this.getConfig('disable_persistence');
23903
+ var usePersistence = localStorageSupported(options.sharedLockStorage || getLocalStorage(), true) && !this.getConfig('disable_persistence');
23880
23904
 
23881
23905
  // each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
23882
23906
  this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
23883
- this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
23907
+ this.queueStorage = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_EVENTS_STORE_NAME, RECORDER_VERSION_DATA);
23884
23908
  this.batcher = new RequestBatcher(this.batcherKey, {
23885
23909
  errorReporter: this.reportError.bind(this),
23886
23910
  flushOnlyOnInterval: true,
@@ -23959,14 +23983,14 @@
23959
23983
  }
23960
23984
 
23961
23985
  if (this._stopRecording !== null) {
23962
- logger$3.log('Recording already in progress, skipping startRecording.');
23986
+ logger$4.log('Recording already in progress, skipping startRecording.');
23963
23987
  return;
23964
23988
  }
23965
23989
 
23966
23990
  this.recordMaxMs = this.getConfig('record_max_ms');
23967
23991
  if (this.recordMaxMs > MAX_RECORDING_MS) {
23968
23992
  this.recordMaxMs = MAX_RECORDING_MS;
23969
- logger$3.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
23993
+ logger$4.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
23970
23994
  }
23971
23995
 
23972
23996
  if (!this.maxExpires) {
@@ -24030,7 +24054,7 @@
24030
24054
  );
24031
24055
  }
24032
24056
 
24033
- var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$3);
24057
+ var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$4);
24034
24058
 
24035
24059
  try {
24036
24060
  this._stopRecording = this._rrwebRecord({
@@ -24292,14 +24316,14 @@
24292
24316
 
24293
24317
 
24294
24318
  SessionRecording.prototype.reportError = function(msg, err) {
24295
- logger$3.error.apply(logger$3.error, arguments);
24319
+ logger$4.error.apply(logger$4.error, arguments);
24296
24320
  try {
24297
24321
  if (!err && !(msg instanceof Error)) {
24298
24322
  msg = new Error(msg);
24299
24323
  }
24300
24324
  this.getConfig('error_reporter')(msg, err);
24301
24325
  } catch(err) {
24302
- logger$3.error(err);
24326
+ logger$4.error(err);
24303
24327
  }
24304
24328
  };
24305
24329
 
@@ -24328,7 +24352,7 @@
24328
24352
  var configValue = this.getConfig('record_min_ms');
24329
24353
 
24330
24354
  if (configValue > MAX_VALUE_FOR_MIN_RECORDING_MS) {
24331
- logger$3.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
24355
+ logger$4.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
24332
24356
  return MAX_VALUE_FOR_MIN_RECORDING_MS;
24333
24357
  }
24334
24358
 
@@ -24370,7 +24394,7 @@
24370
24394
  */
24371
24395
  var RecordingRegistry = function (options) {
24372
24396
  /** @type {IDBStorageWrapper} */
24373
- this.idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
24397
+ this.idb = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_REGISTRY_STORE_NAME, RECORDER_VERSION_DATA);
24374
24398
  this.errorReporter = options.errorReporter;
24375
24399
  this.mixpanelInstance = options.mixpanelInstance;
24376
24400
  this.sharedLockStorage = options.sharedLockStorage;
@@ -24491,7 +24515,7 @@
24491
24515
  .catch(this.handleError.bind(this));
24492
24516
  };
24493
24517
 
24494
- var logger$2 = console_with_prefix('recorder');
24518
+ var logger$3 = console_with_prefix('recorder');
24495
24519
 
24496
24520
  /**
24497
24521
  * Recorder API: bundles rrweb and and exposes methods to start and stop recordings.
@@ -24507,7 +24531,7 @@
24507
24531
  */
24508
24532
  this.recordingRegistry = new RecordingRegistry({
24509
24533
  mixpanelInstance: this.mixpanelInstance,
24510
- errorReporter: logger$2.error,
24534
+ errorReporter: logger$3.error,
24511
24535
  sharedLockStorage: sharedLockStorage
24512
24536
  });
24513
24537
  this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
@@ -24519,17 +24543,17 @@
24519
24543
  MixpanelRecorder.prototype.startRecording = function(options) {
24520
24544
  options = options || {};
24521
24545
  if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
24522
- logger$2.log('Recording already in progress, skipping startRecording.');
24546
+ logger$3.log('Recording already in progress, skipping startRecording.');
24523
24547
  return;
24524
24548
  }
24525
24549
 
24526
24550
  var onIdleTimeout = function () {
24527
- logger$2.log('Idle timeout reached, restarting recording.');
24551
+ logger$3.log('Idle timeout reached, restarting recording.');
24528
24552
  this.resetRecording();
24529
24553
  }.bind(this);
24530
24554
 
24531
24555
  var onMaxLengthReached = function () {
24532
- logger$2.log('Max recording length reached, stopping recording.');
24556
+ logger$3.log('Max recording length reached, stopping recording.');
24533
24557
  this.resetRecording();
24534
24558
  }.bind(this);
24535
24559
 
@@ -24599,7 +24623,7 @@
24599
24623
  } else if (startNewIfInactive) {
24600
24624
  return this.startRecording({shouldStopBatcher: false});
24601
24625
  } else {
24602
- logger$2.log('No resumable recording found.');
24626
+ logger$3.log('No resumable recording found.');
24603
24627
  return null;
24604
24628
  }
24605
24629
  }.bind(this));
@@ -24733,7 +24757,7 @@
24733
24757
  observer.observe(shadowRoot, this.observerConfig);
24734
24758
  this.shadowObservers.push(observer);
24735
24759
  } catch (e) {
24736
- logger$5.critical('Error while observing shadow root', e);
24760
+ logger$6.critical('Error while observing shadow root', e);
24737
24761
  }
24738
24762
  };
24739
24763
 
@@ -24744,7 +24768,7 @@
24744
24768
  }
24745
24769
 
24746
24770
  if (!weakSetSupported()) {
24747
- logger$5.critical('Shadow DOM observation unavailable: WeakSet not supported');
24771
+ logger$6.critical('Shadow DOM observation unavailable: WeakSet not supported');
24748
24772
  return;
24749
24773
  }
24750
24774
 
@@ -24760,7 +24784,7 @@
24760
24784
  try {
24761
24785
  this.shadowObservers[i].disconnect();
24762
24786
  } catch (e) {
24763
- logger$5.critical('Error while disconnecting shadow DOM observer', e);
24787
+ logger$6.critical('Error while disconnecting shadow DOM observer', e);
24764
24788
  }
24765
24789
  }
24766
24790
  this.shadowObservers = [];
@@ -24948,7 +24972,7 @@
24948
24972
 
24949
24973
  this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
24950
24974
  } catch (e) {
24951
- logger$5.critical('Error while setting up mutation observer', e);
24975
+ logger$6.critical('Error while setting up mutation observer', e);
24952
24976
  }
24953
24977
  }
24954
24978
 
@@ -24963,7 +24987,7 @@
24963
24987
  );
24964
24988
  this.shadowDOMObserver.start();
24965
24989
  } catch (e) {
24966
- logger$5.critical('Error while setting up shadow DOM observer', e);
24990
+ logger$6.critical('Error while setting up shadow DOM observer', e);
24967
24991
  this.shadowDOMObserver = null;
24968
24992
  }
24969
24993
  }
@@ -24990,7 +25014,7 @@
24990
25014
  try {
24991
25015
  listener.target.removeEventListener(listener.event, listener.handler, listener.options);
24992
25016
  } catch (e) {
24993
- logger$5.critical('Error while removing event listener', e);
25017
+ logger$6.critical('Error while removing event listener', e);
24994
25018
  }
24995
25019
  }
24996
25020
  this.eventListeners = [];
@@ -24999,7 +25023,7 @@
24999
25023
  try {
25000
25024
  this.mutationObserver.disconnect();
25001
25025
  } catch (e) {
25002
- logger$5.critical('Error while disconnecting mutation observer', e);
25026
+ logger$6.critical('Error while disconnecting mutation observer', e);
25003
25027
  }
25004
25028
  this.mutationObserver = null;
25005
25029
  }
@@ -25008,7 +25032,7 @@
25008
25032
  try {
25009
25033
  this.shadowDOMObserver.stop();
25010
25034
  } catch (e) {
25011
- logger$5.critical('Error while stopping shadow DOM observer', e);
25035
+ logger$6.critical('Error while stopping shadow DOM observer', e);
25012
25036
  }
25013
25037
  this.shadowDOMObserver = null;
25014
25038
  }
@@ -25086,7 +25110,7 @@
25086
25110
 
25087
25111
  Autocapture.prototype.init = function() {
25088
25112
  if (!minDOMApisSupported()) {
25089
- logger$5.critical('Autocapture unavailable: missing required DOM APIs');
25113
+ logger$6.critical('Autocapture unavailable: missing required DOM APIs');
25090
25114
  return;
25091
25115
  }
25092
25116
  this.initPageListeners();
@@ -25126,7 +25150,7 @@
25126
25150
  try {
25127
25151
  return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
25128
25152
  } catch (err) {
25129
- logger$5.critical('Error while checking block URL regexes: ', err);
25153
+ logger$6.critical('Error while checking block URL regexes: ', err);
25130
25154
  return true;
25131
25155
  }
25132
25156
  }
@@ -25139,7 +25163,7 @@
25139
25163
  try {
25140
25164
  return urlMatchesRegexList(currentUrl, blockUrlRegexes);
25141
25165
  } catch (err) {
25142
- logger$5.critical('Error while checking block URL regexes: ', err);
25166
+ logger$6.critical('Error while checking block URL regexes: ', err);
25143
25167
  return true;
25144
25168
  }
25145
25169
  };
@@ -25277,7 +25301,7 @@
25277
25301
  return;
25278
25302
  }
25279
25303
 
25280
- logger$5.log('Initializing scroll depth tracking');
25304
+ logger$6.log('Initializing scroll depth tracking');
25281
25305
 
25282
25306
  this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
25283
25307
 
@@ -25303,7 +25327,7 @@
25303
25327
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
25304
25328
  return;
25305
25329
  }
25306
- logger$5.log('Initializing click tracking');
25330
+ logger$6.log('Initializing click tracking');
25307
25331
 
25308
25332
  this.listenerClick = function(ev) {
25309
25333
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
@@ -25322,7 +25346,7 @@
25322
25346
  return;
25323
25347
  }
25324
25348
 
25325
- logger$5.log('Initializing dead click tracking');
25349
+ logger$6.log('Initializing dead click tracking');
25326
25350
  if (!this._deadClickTracker) {
25327
25351
  this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
25328
25352
  this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
@@ -25356,7 +25380,7 @@
25356
25380
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
25357
25381
  return;
25358
25382
  }
25359
- logger$5.log('Initializing input tracking');
25383
+ logger$6.log('Initializing input tracking');
25360
25384
 
25361
25385
  this.listenerChange = function(ev) {
25362
25386
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
@@ -25370,14 +25394,15 @@
25370
25394
  Autocapture.prototype.initPageviewTracking = function() {
25371
25395
  win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
25372
25396
 
25373
- if (!this.pageviewTrackingConfig()) {
25397
+ if (!this.pageviewTrackingConfig() && !this.mp.get_config('record_heatmap_data')) {
25374
25398
  return;
25375
25399
  }
25376
- logger$5.log('Initializing pageview tracking');
25400
+ logger$6.log('Initializing pageview tracking');
25377
25401
 
25378
25402
  var previousTrackedUrl = '';
25379
25403
  var tracked = false;
25380
- if (!this.currentUrlBlocked()) {
25404
+ // Track initial pageview if pageview tracking enabled OR heatmap recording is active
25405
+ if ((this.pageviewTrackingConfig() || this.mp.is_recording_heatmap_data()) && !this.currentUrlBlocked()) {
25381
25406
  tracked = this.mp.track_pageview(DEFAULT_PROPS);
25382
25407
  }
25383
25408
  if (tracked) {
@@ -25393,6 +25418,10 @@
25393
25418
  var shouldTrack = false;
25394
25419
  var didPathChange = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
25395
25420
  var trackPageviewOption = this.pageviewTrackingConfig();
25421
+ if (!trackPageviewOption && this.mp.is_recording_heatmap_data()) {
25422
+ trackPageviewOption = PAGEVIEW_OPTION_FULL_URL;
25423
+ }
25424
+
25396
25425
  if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
25397
25426
  shouldTrack = currentUrl !== previousTrackedUrl;
25398
25427
  } else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
@@ -25408,7 +25437,7 @@
25408
25437
  }
25409
25438
  if (didPathChange) {
25410
25439
  this.lastScrollCheckpoint = 0;
25411
- logger$5.log('Path change: re-initializing scroll depth checkpoints');
25440
+ logger$6.log('Path change: re-initializing scroll depth checkpoints');
25412
25441
  }
25413
25442
  }
25414
25443
  }.bind(this));
@@ -25423,7 +25452,7 @@
25423
25452
  return;
25424
25453
  }
25425
25454
 
25426
- logger$5.log('Initializing rage click tracking');
25455
+ logger$6.log('Initializing rage click tracking');
25427
25456
  if (!this._rageClickTracker) {
25428
25457
  this._rageClickTracker = new RageClickTracker();
25429
25458
  }
@@ -25453,7 +25482,7 @@
25453
25482
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
25454
25483
  return;
25455
25484
  }
25456
- logger$5.log('Initializing scroll tracking');
25485
+ logger$6.log('Initializing scroll tracking');
25457
25486
  this.lastScrollCheckpoint = 0;
25458
25487
 
25459
25488
  var scrollTrackFunction = function() {
@@ -25490,7 +25519,7 @@
25490
25519
  }
25491
25520
  }
25492
25521
  } catch (err) {
25493
- logger$5.critical('Error while calculating scroll percentage', err);
25522
+ logger$6.critical('Error while calculating scroll percentage', err);
25494
25523
  }
25495
25524
  if (shouldTrack) {
25496
25525
  this.mp.track(MP_EV_SCROLL, props);
@@ -25508,7 +25537,7 @@
25508
25537
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
25509
25538
  return;
25510
25539
  }
25511
- logger$5.log('Initializing submit tracking');
25540
+ logger$6.log('Initializing submit tracking');
25512
25541
 
25513
25542
  this.listenerSubmit = function(ev) {
25514
25543
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
@@ -25530,7 +25559,7 @@
25530
25559
  return;
25531
25560
  }
25532
25561
 
25533
- logger$5.log('Initializing page visibility tracking.');
25562
+ logger$6.log('Initializing page visibility tracking.');
25534
25563
  this._initScrollDepthTracking();
25535
25564
  var previousTrackedUrl = _.info.currentUrl();
25536
25565
 
@@ -25615,10 +25644,183 @@
25615
25644
  return win[TARGETING_GLOBAL_NAME];
25616
25645
  };
25617
25646
 
25647
+ var logger$2 = console_with_prefix('flags');
25648
+
25649
+ var MIXPANEL_FLAGS_DB_NAME = 'mixpanelFlagsDb';
25650
+ var FLAGS_STORE_NAME = 'mixpanelFlags';
25651
+
25652
+ // Keeping these two properties closeby, as adding additional stores to a DB in IndexedDB requires a version increment
25653
+ var FLAGS_VERSION_DATA = { version: 1, storeNames: [FLAGS_STORE_NAME] };
25654
+
25655
+ var PERSISTED_VARIANTS_KEY_PREFIX = 'persisted_variants_for_';
25656
+ var DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
25657
+
25658
+ var VariantLookupPolicy = Object.freeze({
25659
+ NETWORK_ONLY: 'networkOnly',
25660
+ NETWORK_FIRST: 'networkFirst',
25661
+ PERSISTENCE_UNTIL_NETWORK_SUCCESS: 'persistenceUntilNetworkSuccess'
25662
+ });
25663
+
25664
+ var VALID_POLICIES = [
25665
+ VariantLookupPolicy.NETWORK_ONLY,
25666
+ VariantLookupPolicy.NETWORK_FIRST,
25667
+ VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS
25668
+ ];
25669
+
25670
+ /**
25671
+ * Module for handling the storage and retrieval of persisted feature flag variants.
25672
+ */
25673
+ var FeatureFlagPersistence = function(persistenceConfig, token, isGloballyDisabled) {
25674
+ this.idb = new IDBStorageWrapper(MIXPANEL_FLAGS_DB_NAME, FLAGS_STORE_NAME, FLAGS_VERSION_DATA);
25675
+ this.persistenceConfig = persistenceConfig;
25676
+ this.persistedVariantsKey = PERSISTED_VARIANTS_KEY_PREFIX + token;
25677
+ this.isGloballyDisabled = isGloballyDisabled || function() { return false; };
25678
+ };
25679
+
25680
+ FeatureFlagPersistence.prototype.getPolicy = function() {
25681
+ if (this.isGloballyDisabled() || !this._isConfigValid()) {
25682
+ return VariantLookupPolicy.NETWORK_ONLY;
25683
+ }
25684
+ return this.persistenceConfig['variantLookupPolicy'];
25685
+ };
25686
+
25687
+ FeatureFlagPersistence.prototype.getTtlMs = function() {
25688
+ if (!this._isConfigValid()) {
25689
+ return DEFAULT_TTL_MS;
25690
+ }
25691
+ var configuredTtl = this.persistenceConfig['persistenceTtlMs'];
25692
+ return (configuredTtl === undefined || configuredTtl === null) ? DEFAULT_TTL_MS : configuredTtl;
25693
+ };
25694
+
25695
+ FeatureFlagPersistence.prototype._isConfigValid = function() {
25696
+ var config = this.persistenceConfig;
25697
+ if (!config) {
25698
+ return false;
25699
+ }
25700
+
25701
+ if (VALID_POLICIES.indexOf(config['variantLookupPolicy']) === -1) {
25702
+ logger$2.error('Invalid variantLookupPolicy:', config['variantLookupPolicy']);
25703
+ return false;
25704
+ }
25705
+
25706
+ if (config['persistenceTtlMs'] !== undefined &&
25707
+ config['persistenceTtlMs'] !== null &&
25708
+ config['persistenceTtlMs'] <= 0) {
25709
+ logger$2.error('If provided, persistenceTtlMs must be a positive number. Provided value:', config['persistenceTtlMs']);
25710
+ return false;
25711
+ }
25712
+
25713
+ return true;
25714
+ };
25715
+
25716
+ FeatureFlagPersistence.prototype.loadFlagsFromStorage = function(context) {
25717
+ var clearAndReturnNull = _.bind(function() {
25718
+ return this.clear().then(function() { return null; }).catch(function() { return null; });
25719
+ }, this);
25720
+
25721
+ if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
25722
+ return clearAndReturnNull();
25723
+ }
25724
+
25725
+ var ttlMs = this.getTtlMs();
25726
+
25727
+ return this.idb.init().then(_.bind(function() {
25728
+ return this.idb.getItem(this.persistedVariantsKey);
25729
+ }, this)).then(_.bind(function(data) {
25730
+ if (!data) {
25731
+ logger$2.log('No persisted variants found in IndexedDB');
25732
+ return null;
25733
+ }
25734
+
25735
+ if (ttlMs && Date.now() - data['persistedAt'] >= ttlMs) {
25736
+ logger$2.log('Persisted variants are expiring');
25737
+ return null;
25738
+ }
25739
+
25740
+ if (!context || data['distinctId'] !== context['distinct_id']) {
25741
+ logger$2.log('Persisted variants found, but for a different distinct_id so clearing.');
25742
+ return clearAndReturnNull();
25743
+ }
25744
+
25745
+ var persistedFlags = new Map();
25746
+ _.each(data['flagVariants'], function(variantData, key) {
25747
+ persistedFlags.set(key, {
25748
+ 'key': variantData['variant_key'],
25749
+ 'value': variantData['variant_value'],
25750
+ 'experiment_id': variantData['experiment_id'],
25751
+ 'is_experiment_active': variantData['is_experiment_active'],
25752
+ 'is_qa_tester': variantData['is_qa_tester'],
25753
+ 'variant_source': 'persistence',
25754
+ 'persisted_at_in_ms': data['persistedAt'],
25755
+ 'ttl_in_ms': ttlMs
25756
+ });
25757
+ });
25758
+
25759
+ logger$2.log('Loaded', persistedFlags.size, 'variants from IndexedDB for distinct_id', data['distinctId']);
25760
+
25761
+ return {
25762
+ flags: persistedFlags,
25763
+ pendingFirstTimeEvents: data['pendingFirstTimeEvents'] || {},
25764
+ persistedAtMs: data['persistedAt'],
25765
+ ttlMs: ttlMs
25766
+ };
25767
+ }, this)).catch(_.bind(function(error) {
25768
+ logger$2.error('Failed to load persisted variants from IndexedDB, so clearing', error);
25769
+ return clearAndReturnNull();
25770
+ }, this));
25771
+ };
25772
+
25773
+ FeatureFlagPersistence.prototype.save = function(context, flagsMap, pendingFirstTimeEvents) {
25774
+ if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
25775
+ return Promise.resolve();
25776
+ }
25777
+
25778
+ var flagVariants = {};
25779
+ flagsMap.forEach(function(variant, key) {
25780
+ flagVariants[key] = {
25781
+ 'variant_key': variant['key'],
25782
+ 'variant_value': variant['value'],
25783
+ 'experiment_id': variant['experiment_id'],
25784
+ 'is_experiment_active': variant['is_experiment_active'],
25785
+ 'is_qa_tester': variant['is_qa_tester']
25786
+ };
25787
+ });
25788
+
25789
+ var data = {
25790
+ 'persistedAt': Date.now(),
25791
+ 'distinctId': context && context['distinct_id'],
25792
+ 'context': context,
25793
+ 'flagVariants': flagVariants,
25794
+ 'pendingFirstTimeEvents': pendingFirstTimeEvents || {}
25795
+ };
25796
+
25797
+ return this.idb.init().then(_.bind(function() {
25798
+ return this.idb.setItem(this.persistedVariantsKey, data);
25799
+ }, this)).then(function() {
25800
+ logger$2.log('Saved', flagsMap.size, 'variants to IndexedDB for distinct_id', data['distinctId']);
25801
+ }).catch(function(error) {
25802
+ logger$2.error('Failed to persist variants to IndexedDB:', error);
25803
+ });
25804
+ };
25805
+
25806
+ FeatureFlagPersistence.prototype.clear = function() {
25807
+ if (this.isGloballyDisabled()) {
25808
+ return Promise.resolve();
25809
+ }
25810
+ return this.idb.init().then(_.bind(function() {
25811
+ return this.idb.removeItem(this.persistedVariantsKey);
25812
+ }, this)).then(function() {
25813
+ logger$2.log('Cleared persisted variants from IndexedDB');
25814
+ }).catch(function(error) {
25815
+ logger$2.error('Failed to clear persisted variants from IndexedDB:', error);
25816
+ });
25817
+ };
25818
+
25618
25819
  var logger$1 = console_with_prefix('flags');
25619
25820
  var FLAGS_CONFIG_KEY = 'flags';
25620
25821
 
25621
25822
  var CONFIG_CONTEXT = 'context';
25823
+ var CONFIG_PERSISTENCE = 'persistence';
25622
25824
  var CONFIG_DEFAULTS = {};
25623
25825
  CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
25624
25826
 
@@ -25641,6 +25843,13 @@
25641
25843
  return eventKey.split(':')[0];
25642
25844
  };
25643
25845
 
25846
+ var withFallbackSource = function(fallback) {
25847
+ if (_.isObject(fallback)) {
25848
+ return _.extend({}, fallback, {'variant_source': 'fallback'});
25849
+ }
25850
+ return {'value': fallback, 'variant_source': 'fallback'};
25851
+ };
25852
+
25644
25853
  /**
25645
25854
  * FeatureFlagManager: support for Mixpanel's feature flagging product
25646
25855
  * @constructor
@@ -25663,13 +25872,63 @@
25663
25872
  }
25664
25873
 
25665
25874
  this.flags = null;
25666
- this.fetchFlags().catch(function() {
25667
- logger$1.error('Error fetching flags during init');
25668
- });
25669
-
25670
25875
  this.trackedFeatures = new Set();
25671
25876
  this.pendingFirstTimeEvents = {};
25672
25877
  this.activatedFirstTimeEvents = {};
25878
+ this._loadedPersistedAtMs = null;
25879
+ this._loadedTtlMs = null;
25880
+
25881
+ this.persistence = new FeatureFlagPersistence(
25882
+ this.getConfig(CONFIG_PERSISTENCE),
25883
+ this.getMpConfig('token'),
25884
+ _.bind(function() { return this.getMpConfig('disable_persistence'); }, this)
25885
+ );
25886
+
25887
+ this.persistenceLoadedPromise = this.persistence.loadFlagsFromStorage(this._buildContext())
25888
+ .then(_.bind(function(loaded) {
25889
+ if (loaded) {
25890
+ this.flags = loaded.flags;
25891
+ this.pendingFirstTimeEvents = loaded.pendingFirstTimeEvents;
25892
+ this._loadedPersistedAtMs = loaded.persistedAtMs;
25893
+ this._loadedTtlMs = loaded.ttlMs;
25894
+ }
25895
+ }, this));
25896
+
25897
+ return this.persistenceLoadedPromise
25898
+ .then(_.bind(function() {
25899
+ return this.fetchFlags();
25900
+ }, this))
25901
+ .catch(function() {
25902
+ logger$1.error('Error initializing feature flags');
25903
+ });
25904
+ };
25905
+
25906
+ FeatureFlagManager.prototype._buildContext = function() {
25907
+ return _.extend(
25908
+ {'distinct_id': this.getMpProperty('distinct_id'), 'device_id': this.getMpProperty('$device_id')},
25909
+ this.getConfig(CONFIG_CONTEXT)
25910
+ );
25911
+ };
25912
+
25913
+ FeatureFlagManager.prototype.reset = function() {
25914
+ if (!this.persistence) {
25915
+ return Promise.resolve();
25916
+ }
25917
+
25918
+ this.flags = null;
25919
+ this.pendingFirstTimeEvents = {};
25920
+ this.activatedFirstTimeEvents = {};
25921
+ this.trackedFeatures = new Set();
25922
+ this.fetchPromise = null;
25923
+ this._fetchInProgressStartTime = null;
25924
+ this._loadedPersistedAtMs = null;
25925
+ this._loadedTtlMs = null;
25926
+
25927
+ return this.persistence.clear().then(_.bind(function() {
25928
+ return this.fetchFlags();
25929
+ }, this)).catch(function() {
25930
+ logger$1.error('Error during flags reset');
25931
+ });
25673
25932
  };
25674
25933
 
25675
25934
  FeatureFlagManager.prototype.getFullConfig = function() {
@@ -25726,12 +25985,11 @@
25726
25985
  return Promise.resolve();
25727
25986
  }
25728
25987
 
25729
- var distinctId = this.getMpProperty('distinct_id');
25730
- var deviceId = this.getMpProperty('$device_id');
25988
+ var context = this._buildContext();
25989
+ var distinctId = context['distinct_id'];
25731
25990
  var traceparent = generateTraceparent();
25732
25991
  logger$1.log('Fetching flags for distinct ID: ' + distinctId);
25733
25992
 
25734
- var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
25735
25993
  var searchParams = new URLSearchParams();
25736
25994
  searchParams.set('context', JSON.stringify(context));
25737
25995
  searchParams.set('token', this.getMpConfig('token'));
@@ -25781,7 +26039,8 @@
25781
26039
  'value': data['variant_value'],
25782
26040
  'experiment_id': data['experiment_id'],
25783
26041
  'is_experiment_active': data['is_experiment_active'],
25784
- 'is_qa_tester': data['is_qa_tester']
26042
+ 'is_qa_tester': data['is_qa_tester'],
26043
+ 'variant_source': 'network'
25785
26044
  });
25786
26045
  }
25787
26046
  }, this);
@@ -25823,10 +26082,15 @@
25823
26082
  }
25824
26083
 
25825
26084
  this.flags = flags;
26085
+ this.trackedFeatures = new Set();
25826
26086
  this.pendingFirstTimeEvents = pendingFirstTimeEvents;
26087
+ this._loadedPersistedAtMs = null;
26088
+ this._loadedTtlMs = null;
25827
26089
  this._traceparent = traceparent;
25828
26090
 
25829
26091
  this._loadTargetingIfNeeded();
26092
+
26093
+ this.persistence.save(context, this.flags, this.pendingFirstTimeEvents);
25830
26094
  }.bind(this)).catch(function(error) {
25831
26095
  if (this._fetchInProgressStartTime) {
25832
26096
  this.markFetchComplete();
@@ -25986,6 +26250,7 @@
25986
26250
  };
25987
26251
 
25988
26252
  this.flags.set(flagKey, newVariant);
26253
+ this.trackedFeatures.delete(flagKey);
25989
26254
  this.activatedFirstTimeEvents[eventKey] = true;
25990
26255
 
25991
26256
  this.recordFirstTimeEvent(
@@ -26035,35 +26300,106 @@
26035
26300
  };
26036
26301
 
26037
26302
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
26038
- if (!this.fetchPromise) {
26303
+ if (!this.persistenceLoadedPromise) {
26039
26304
  return new Promise(function(resolve) {
26040
26305
  logger$1.critical('Feature Flags not initialized');
26041
- resolve(fallback);
26306
+ resolve(withFallbackSource(fallback));
26042
26307
  });
26043
26308
  }
26044
26309
 
26045
- return this.fetchPromise.then(function() {
26046
- return this.getVariantSync(featureName, fallback);
26047
- }.bind(this)).catch(function(error) {
26048
- logger$1.error(error);
26049
- return fallback;
26050
- });
26310
+ var policy = this.persistence.getPolicy();
26311
+
26312
+ return this.persistenceLoadedPromise.then(_.bind(function() {
26313
+ // 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.
26314
+ if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
26315
+ if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
26316
+ return this.getVariantSync(featureName, fallback);
26317
+ }
26318
+ if (!this.fetchPromise) {
26319
+ return withFallbackSource(fallback);
26320
+ }
26321
+ return this.fetchPromise.then(_.bind(function() {
26322
+ return this.getVariantSync(featureName, fallback);
26323
+ }, this)).catch(function(error) {
26324
+ logger$1.error(error);
26325
+ return withFallbackSource(fallback);
26326
+ });
26327
+ }
26328
+
26329
+ var serve = _.bind(function() { return this.getVariantSync(featureName, fallback); }, this);
26330
+ if (!this.fetchPromise) {
26331
+ return withFallbackSource(fallback);
26332
+ }
26333
+ return this.fetchPromise.then(serve).catch(serve);
26334
+ }, this));
26335
+ };
26336
+
26337
+ FeatureFlagManager.prototype._loadedPersistenceIsStale = function() {
26338
+ if (!this._loadedPersistedAtMs || !this._loadedTtlMs) {
26339
+ return false;
26340
+ }
26341
+ return Date.now() - this._loadedPersistedAtMs >= this._loadedTtlMs;
26051
26342
  };
26052
26343
 
26053
26344
  FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
26345
+ if (this._loadedPersistenceIsStale()) {
26346
+ logger$1.log('Loaded persisted variants are past TTL so returning fallback for "' + featureName + '"');
26347
+ return withFallbackSource(fallback);
26348
+ }
26054
26349
  if (!this.areFlagsReady()) {
26055
26350
  logger$1.log('Flags not loaded yet');
26056
- return fallback;
26351
+ return withFallbackSource(fallback);
26057
26352
  }
26058
26353
  var feature = this.flags.get(featureName);
26059
26354
  if (!feature) {
26060
26355
  logger$1.log('No flag found: "' + featureName + '"');
26061
- return fallback;
26356
+ return withFallbackSource(fallback);
26062
26357
  }
26063
26358
  this.trackFeatureCheck(featureName, feature);
26064
26359
  return feature;
26065
26360
  };
26066
26361
 
26362
+ FeatureFlagManager.prototype.getAllVariants = function() {
26363
+ if (!this.persistenceLoadedPromise) {
26364
+ logger$1.critical('Feature Flags not initialized');
26365
+ return Promise.resolve(new Map());
26366
+ }
26367
+
26368
+ var policy = this.persistence.getPolicy();
26369
+
26370
+ return this.persistenceLoadedPromise.then(_.bind(function() {
26371
+ // 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.
26372
+ if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
26373
+ if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
26374
+ return this.getAllVariantsSync();
26375
+ }
26376
+ if (!this.fetchPromise) {
26377
+ return new Map();
26378
+ }
26379
+ return this.fetchPromise.then(_.bind(function() {
26380
+ return this.getAllVariantsSync();
26381
+ }, this)).catch(function(error) {
26382
+ logger$1.error(error);
26383
+ return new Map();
26384
+ });
26385
+ }
26386
+
26387
+ var serve = _.bind(this.getAllVariantsSync, this);
26388
+ if (!this.fetchPromise) {
26389
+ return new Map();
26390
+ }
26391
+ return this.fetchPromise.then(serve).catch(serve);
26392
+ }, this));
26393
+ };
26394
+
26395
+ FeatureFlagManager.prototype.getAllVariantsSync = function() {
26396
+ if (this._loadedPersistenceIsStale()) {
26397
+ logger$1.log('Loaded persisted variants are past TTL so returning empty Map');
26398
+ return new Map();
26399
+ }
26400
+ return this.flags || new Map();
26401
+ };
26402
+
26067
26403
  FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
26068
26404
  return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
26069
26405
  return feature['value'];
@@ -26102,6 +26438,10 @@
26102
26438
  return val;
26103
26439
  };
26104
26440
 
26441
+ function isPresent(v) {
26442
+ return v !== undefined && v !== null;
26443
+ }
26444
+
26105
26445
  FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
26106
26446
  if (this.trackedFeatures.has(featureName)) {
26107
26447
  return;
@@ -26112,21 +26452,30 @@
26112
26452
  'Experiment name': featureName,
26113
26453
  'Variant name': feature['key'],
26114
26454
  '$experiment_type': 'feature_flag',
26115
- 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
26116
- 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
26455
+ 'Variant fetch start time': isPresent(this._fetchStartTime) ? new Date(this._fetchStartTime).toISOString() : null,
26456
+ 'Variant fetch complete time': isPresent(this._fetchCompleteTime) ? new Date(this._fetchCompleteTime).toISOString() : null,
26117
26457
  'Variant fetch latency (ms)': this._fetchLatency,
26118
26458
  'Variant fetch traceparent': this._traceparent,
26119
26459
  };
26120
26460
 
26121
- if (feature['experiment_id'] !== 'undefined') {
26461
+ if (isPresent(feature['experiment_id'])) {
26122
26462
  trackingProperties['$experiment_id'] = feature['experiment_id'];
26123
26463
  }
26124
- if (feature['is_experiment_active'] !== 'undefined') {
26464
+ if (isPresent(feature['is_experiment_active'])) {
26125
26465
  trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
26126
26466
  }
26127
- if (feature['is_qa_tester'] !== 'undefined') {
26467
+ if (isPresent(feature['is_qa_tester'])) {
26128
26468
  trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
26129
26469
  }
26470
+ if (isPresent(feature['variant_source'])) {
26471
+ trackingProperties['$variant_source'] = feature['variant_source'];
26472
+ }
26473
+ if (isPresent(feature['persisted_at_in_ms'])) {
26474
+ trackingProperties['$persisted_at_in_ms'] = feature['persisted_at_in_ms'];
26475
+ }
26476
+ if (isPresent(feature['ttl_in_ms'])) {
26477
+ trackingProperties['$ttl_in_ms'] = feature['ttl_in_ms'];
26478
+ }
26130
26479
 
26131
26480
  this.track('$experiment_started', trackingProperties);
26132
26481
  };
@@ -26150,6 +26499,8 @@
26150
26499
  FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
26151
26500
  FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
26152
26501
  FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
26502
+ FeatureFlagManager.prototype['get_all_variants'] = FeatureFlagManager.prototype.getAllVariants;
26503
+ FeatureFlagManager.prototype['get_all_variants_sync'] = FeatureFlagManager.prototype.getAllVariantsSync;
26153
26504
  FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
26154
26505
  FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
26155
26506
  FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
@@ -26202,7 +26553,7 @@
26202
26553
  return PromisePolyfill.resolve(false);
26203
26554
  }
26204
26555
 
26205
- var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
26556
+ var recording_registry_idb = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_REGISTRY_STORE_NAME, RECORDER_VERSION_DATA);
26206
26557
  var tab_id = this.getTabId();
26207
26558
  return recording_registry_idb.init()
26208
26559
  .then(function () {
@@ -28129,6 +28480,7 @@
28129
28480
  'disable_all_events': false,
28130
28481
  'identify_called': false
28131
28482
  };
28483
+ this._remote_settings_strict_disabled = false;
28132
28484
 
28133
28485
  // set up request queueing/batching
28134
28486
  this.request_batchers = {};
@@ -28203,9 +28555,6 @@
28203
28555
  this.flags.init();
28204
28556
  this['flags'] = this.flags;
28205
28557
 
28206
- this.autocapture = new Autocapture(this);
28207
- this.autocapture.init();
28208
-
28209
28558
  this._init_tab_id();
28210
28559
 
28211
28560
  // Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
@@ -28217,6 +28566,9 @@
28217
28566
  } else {
28218
28567
  this.__session_recording_init_promise = this._check_and_start_session_recording();
28219
28568
  }
28569
+
28570
+ this.autocapture = new Autocapture(this);
28571
+ this.autocapture.init();
28220
28572
  };
28221
28573
 
28222
28574
  /**
@@ -28263,9 +28615,19 @@
28263
28615
  return this.recorderManager.checkAndStartSessionRecording(force_start);
28264
28616
  });
28265
28617
 
28266
- MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
28267
- return this.recorderManager.startRecordingOnEvent(event_name, properties);
28268
- };
28618
+ MixpanelLib.prototype._start_recording_on_event = safewrap(function(event_name, properties) {
28619
+ // Wait for recording init to complete before evaluating event triggers.
28620
+ // This ensures recording_event_triggers config is fully loaded when remote settings are used.
28621
+ if (this.__session_recording_init_promise) {
28622
+ this.__session_recording_init_promise.then(_.bind(function() {
28623
+ // In strict mode, skip recording if remote settings failed
28624
+ if (this._remote_settings_strict_disabled) {
28625
+ return;
28626
+ }
28627
+ return this.recorderManager.startRecordingOnEvent(event_name, properties);
28628
+ }, this));
28629
+ }
28630
+ });
28269
28631
 
28270
28632
  MixpanelLib.prototype.start_session_recording = function () {
28271
28633
  return this._check_and_start_session_recording(true);
@@ -28564,6 +28926,7 @@
28564
28926
  var disableRecordingIfStrict = function() {
28565
28927
  if (mode === 'strict') {
28566
28928
  self.set_config({'record_sessions_percent': 0});
28929
+ self._remote_settings_strict_disabled = true;
28567
28930
  }
28568
28931
  };
28569
28932
 
@@ -29189,6 +29552,10 @@
29189
29552
  properties
29190
29553
  );
29191
29554
 
29555
+ if (this.is_recording_heatmap_data()) {
29556
+ event_properties['$captured_for_heatmap'] = true;
29557
+ }
29558
+
29192
29559
  return this.track(event_name, event_properties);
29193
29560
  });
29194
29561
 
@@ -29532,6 +29899,7 @@
29532
29899
  '$device_id': uuid
29533
29900
  }, '');
29534
29901
  this._check_and_start_session_recording();
29902
+ this.flags.reset();
29535
29903
  };
29536
29904
 
29537
29905
  /**