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
@@ -31,7 +31,7 @@
31
31
 
32
32
  var Config = {
33
33
  DEBUG: false,
34
- LIB_VERSION: '2.78.0'
34
+ LIB_VERSION: '2.79.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 = {
@@ -23879,11 +23903,11 @@
23879
23903
 
23880
23904
  // disable persistence if localStorage is not supported
23881
23905
  // request-queue will automatically disable persistence if indexedDB fails to initialize
23882
- var usePersistence = localStorageSupported(options.sharedLockStorage, true) && !this.getConfig('disable_persistence');
23906
+ var usePersistence = localStorageSupported(options.sharedLockStorage || getLocalStorage(), true) && !this.getConfig('disable_persistence');
23883
23907
 
23884
23908
  // each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
23885
23909
  this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
23886
- this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
23910
+ this.queueStorage = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_EVENTS_STORE_NAME, RECORDER_VERSION_DATA);
23887
23911
  this.batcher = new RequestBatcher(this.batcherKey, {
23888
23912
  errorReporter: this.reportError.bind(this),
23889
23913
  flushOnlyOnInterval: true,
@@ -23962,14 +23986,14 @@
23962
23986
  }
23963
23987
 
23964
23988
  if (this._stopRecording !== null) {
23965
- logger$3.log('Recording already in progress, skipping startRecording.');
23989
+ logger$4.log('Recording already in progress, skipping startRecording.');
23966
23990
  return;
23967
23991
  }
23968
23992
 
23969
23993
  this.recordMaxMs = this.getConfig('record_max_ms');
23970
23994
  if (this.recordMaxMs > MAX_RECORDING_MS) {
23971
23995
  this.recordMaxMs = MAX_RECORDING_MS;
23972
- logger$3.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
23996
+ logger$4.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
23973
23997
  }
23974
23998
 
23975
23999
  if (!this.maxExpires) {
@@ -24033,7 +24057,7 @@
24033
24057
  );
24034
24058
  }
24035
24059
 
24036
- var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$3);
24060
+ var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$4);
24037
24061
 
24038
24062
  try {
24039
24063
  this._stopRecording = this._rrwebRecord({
@@ -24295,14 +24319,14 @@
24295
24319
 
24296
24320
 
24297
24321
  SessionRecording.prototype.reportError = function(msg, err) {
24298
- logger$3.error.apply(logger$3.error, arguments);
24322
+ logger$4.error.apply(logger$4.error, arguments);
24299
24323
  try {
24300
24324
  if (!err && !(msg instanceof Error)) {
24301
24325
  msg = new Error(msg);
24302
24326
  }
24303
24327
  this.getConfig('error_reporter')(msg, err);
24304
24328
  } catch(err) {
24305
- logger$3.error(err);
24329
+ logger$4.error(err);
24306
24330
  }
24307
24331
  };
24308
24332
 
@@ -24331,7 +24355,7 @@
24331
24355
  var configValue = this.getConfig('record_min_ms');
24332
24356
 
24333
24357
  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.');
24358
+ logger$4.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
24335
24359
  return MAX_VALUE_FOR_MIN_RECORDING_MS;
24336
24360
  }
24337
24361
 
@@ -24373,7 +24397,7 @@
24373
24397
  */
24374
24398
  var RecordingRegistry = function (options) {
24375
24399
  /** @type {IDBStorageWrapper} */
24376
- this.idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
24400
+ this.idb = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_REGISTRY_STORE_NAME, RECORDER_VERSION_DATA);
24377
24401
  this.errorReporter = options.errorReporter;
24378
24402
  this.mixpanelInstance = options.mixpanelInstance;
24379
24403
  this.sharedLockStorage = options.sharedLockStorage;
@@ -24494,7 +24518,7 @@
24494
24518
  .catch(this.handleError.bind(this));
24495
24519
  };
24496
24520
 
24497
- var logger$2 = console_with_prefix('recorder');
24521
+ var logger$3 = console_with_prefix('recorder');
24498
24522
 
24499
24523
  /**
24500
24524
  * Recorder API: bundles rrweb and and exposes methods to start and stop recordings.
@@ -24510,7 +24534,7 @@
24510
24534
  */
24511
24535
  this.recordingRegistry = new RecordingRegistry({
24512
24536
  mixpanelInstance: this.mixpanelInstance,
24513
- errorReporter: logger$2.error,
24537
+ errorReporter: logger$3.error,
24514
24538
  sharedLockStorage: sharedLockStorage
24515
24539
  });
24516
24540
  this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
@@ -24522,17 +24546,17 @@
24522
24546
  MixpanelRecorder.prototype.startRecording = function(options) {
24523
24547
  options = options || {};
24524
24548
  if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
24525
- logger$2.log('Recording already in progress, skipping startRecording.');
24549
+ logger$3.log('Recording already in progress, skipping startRecording.');
24526
24550
  return;
24527
24551
  }
24528
24552
 
24529
24553
  var onIdleTimeout = function () {
24530
- logger$2.log('Idle timeout reached, restarting recording.');
24554
+ logger$3.log('Idle timeout reached, restarting recording.');
24531
24555
  this.resetRecording();
24532
24556
  }.bind(this);
24533
24557
 
24534
24558
  var onMaxLengthReached = function () {
24535
- logger$2.log('Max recording length reached, stopping recording.');
24559
+ logger$3.log('Max recording length reached, stopping recording.');
24536
24560
  this.resetRecording();
24537
24561
  }.bind(this);
24538
24562
 
@@ -24602,7 +24626,7 @@
24602
24626
  } else if (startNewIfInactive) {
24603
24627
  return this.startRecording({shouldStopBatcher: false});
24604
24628
  } else {
24605
- logger$2.log('No resumable recording found.');
24629
+ logger$3.log('No resumable recording found.');
24606
24630
  return null;
24607
24631
  }
24608
24632
  }.bind(this));
@@ -25267,7 +25291,7 @@
25267
25291
  observer.observe(shadowRoot, this.observerConfig);
25268
25292
  this.shadowObservers.push(observer);
25269
25293
  } catch (e) {
25270
- logger$5.critical('Error while observing shadow root', e);
25294
+ logger$6.critical('Error while observing shadow root', e);
25271
25295
  }
25272
25296
  };
25273
25297
 
@@ -25278,7 +25302,7 @@
25278
25302
  }
25279
25303
 
25280
25304
  if (!weakSetSupported()) {
25281
- logger$5.critical('Shadow DOM observation unavailable: WeakSet not supported');
25305
+ logger$6.critical('Shadow DOM observation unavailable: WeakSet not supported');
25282
25306
  return;
25283
25307
  }
25284
25308
 
@@ -25294,7 +25318,7 @@
25294
25318
  try {
25295
25319
  this.shadowObservers[i].disconnect();
25296
25320
  } catch (e) {
25297
- logger$5.critical('Error while disconnecting shadow DOM observer', e);
25321
+ logger$6.critical('Error while disconnecting shadow DOM observer', e);
25298
25322
  }
25299
25323
  }
25300
25324
  this.shadowObservers = [];
@@ -25482,7 +25506,7 @@
25482
25506
 
25483
25507
  this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
25484
25508
  } catch (e) {
25485
- logger$5.critical('Error while setting up mutation observer', e);
25509
+ logger$6.critical('Error while setting up mutation observer', e);
25486
25510
  }
25487
25511
  }
25488
25512
 
@@ -25497,7 +25521,7 @@
25497
25521
  );
25498
25522
  this.shadowDOMObserver.start();
25499
25523
  } catch (e) {
25500
- logger$5.critical('Error while setting up shadow DOM observer', e);
25524
+ logger$6.critical('Error while setting up shadow DOM observer', e);
25501
25525
  this.shadowDOMObserver = null;
25502
25526
  }
25503
25527
  }
@@ -25524,7 +25548,7 @@
25524
25548
  try {
25525
25549
  listener.target.removeEventListener(listener.event, listener.handler, listener.options);
25526
25550
  } catch (e) {
25527
- logger$5.critical('Error while removing event listener', e);
25551
+ logger$6.critical('Error while removing event listener', e);
25528
25552
  }
25529
25553
  }
25530
25554
  this.eventListeners = [];
@@ -25533,7 +25557,7 @@
25533
25557
  try {
25534
25558
  this.mutationObserver.disconnect();
25535
25559
  } catch (e) {
25536
- logger$5.critical('Error while disconnecting mutation observer', e);
25560
+ logger$6.critical('Error while disconnecting mutation observer', e);
25537
25561
  }
25538
25562
  this.mutationObserver = null;
25539
25563
  }
@@ -25542,7 +25566,7 @@
25542
25566
  try {
25543
25567
  this.shadowDOMObserver.stop();
25544
25568
  } catch (e) {
25545
- logger$5.critical('Error while stopping shadow DOM observer', e);
25569
+ logger$6.critical('Error while stopping shadow DOM observer', e);
25546
25570
  }
25547
25571
  this.shadowDOMObserver = null;
25548
25572
  }
@@ -25620,7 +25644,7 @@
25620
25644
 
25621
25645
  Autocapture.prototype.init = function() {
25622
25646
  if (!minDOMApisSupported()) {
25623
- logger$5.critical('Autocapture unavailable: missing required DOM APIs');
25647
+ logger$6.critical('Autocapture unavailable: missing required DOM APIs');
25624
25648
  return;
25625
25649
  }
25626
25650
  this.initPageListeners();
@@ -25660,7 +25684,7 @@
25660
25684
  try {
25661
25685
  return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
25662
25686
  } catch (err) {
25663
- logger$5.critical('Error while checking block URL regexes: ', err);
25687
+ logger$6.critical('Error while checking block URL regexes: ', err);
25664
25688
  return true;
25665
25689
  }
25666
25690
  }
@@ -25673,7 +25697,7 @@
25673
25697
  try {
25674
25698
  return urlMatchesRegexList(currentUrl, blockUrlRegexes);
25675
25699
  } catch (err) {
25676
- logger$5.critical('Error while checking block URL regexes: ', err);
25700
+ logger$6.critical('Error while checking block URL regexes: ', err);
25677
25701
  return true;
25678
25702
  }
25679
25703
  };
@@ -25811,7 +25835,7 @@
25811
25835
  return;
25812
25836
  }
25813
25837
 
25814
- logger$5.log('Initializing scroll depth tracking');
25838
+ logger$6.log('Initializing scroll depth tracking');
25815
25839
 
25816
25840
  this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
25817
25841
 
@@ -25837,7 +25861,7 @@
25837
25861
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
25838
25862
  return;
25839
25863
  }
25840
- logger$5.log('Initializing click tracking');
25864
+ logger$6.log('Initializing click tracking');
25841
25865
 
25842
25866
  this.listenerClick = function(ev) {
25843
25867
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
@@ -25856,7 +25880,7 @@
25856
25880
  return;
25857
25881
  }
25858
25882
 
25859
- logger$5.log('Initializing dead click tracking');
25883
+ logger$6.log('Initializing dead click tracking');
25860
25884
  if (!this._deadClickTracker) {
25861
25885
  this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
25862
25886
  this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
@@ -25890,7 +25914,7 @@
25890
25914
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
25891
25915
  return;
25892
25916
  }
25893
- logger$5.log('Initializing input tracking');
25917
+ logger$6.log('Initializing input tracking');
25894
25918
 
25895
25919
  this.listenerChange = function(ev) {
25896
25920
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
@@ -25904,14 +25928,15 @@
25904
25928
  Autocapture.prototype.initPageviewTracking = function() {
25905
25929
  win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
25906
25930
 
25907
- if (!this.pageviewTrackingConfig()) {
25931
+ if (!this.pageviewTrackingConfig() && !this.mp.get_config('record_heatmap_data')) {
25908
25932
  return;
25909
25933
  }
25910
- logger$5.log('Initializing pageview tracking');
25934
+ logger$6.log('Initializing pageview tracking');
25911
25935
 
25912
25936
  var previousTrackedUrl = '';
25913
25937
  var tracked = false;
25914
- if (!this.currentUrlBlocked()) {
25938
+ // Track initial pageview if pageview tracking enabled OR heatmap recording is active
25939
+ if ((this.pageviewTrackingConfig() || this.mp.is_recording_heatmap_data()) && !this.currentUrlBlocked()) {
25915
25940
  tracked = this.mp.track_pageview(DEFAULT_PROPS);
25916
25941
  }
25917
25942
  if (tracked) {
@@ -25927,6 +25952,10 @@
25927
25952
  var shouldTrack = false;
25928
25953
  var didPathChange = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
25929
25954
  var trackPageviewOption = this.pageviewTrackingConfig();
25955
+ if (!trackPageviewOption && this.mp.is_recording_heatmap_data()) {
25956
+ trackPageviewOption = PAGEVIEW_OPTION_FULL_URL;
25957
+ }
25958
+
25930
25959
  if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
25931
25960
  shouldTrack = currentUrl !== previousTrackedUrl;
25932
25961
  } else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
@@ -25942,7 +25971,7 @@
25942
25971
  }
25943
25972
  if (didPathChange) {
25944
25973
  this.lastScrollCheckpoint = 0;
25945
- logger$5.log('Path change: re-initializing scroll depth checkpoints');
25974
+ logger$6.log('Path change: re-initializing scroll depth checkpoints');
25946
25975
  }
25947
25976
  }
25948
25977
  }.bind(this));
@@ -25957,7 +25986,7 @@
25957
25986
  return;
25958
25987
  }
25959
25988
 
25960
- logger$5.log('Initializing rage click tracking');
25989
+ logger$6.log('Initializing rage click tracking');
25961
25990
  if (!this._rageClickTracker) {
25962
25991
  this._rageClickTracker = new RageClickTracker();
25963
25992
  }
@@ -25987,7 +26016,7 @@
25987
26016
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
25988
26017
  return;
25989
26018
  }
25990
- logger$5.log('Initializing scroll tracking');
26019
+ logger$6.log('Initializing scroll tracking');
25991
26020
  this.lastScrollCheckpoint = 0;
25992
26021
 
25993
26022
  var scrollTrackFunction = function() {
@@ -26024,7 +26053,7 @@
26024
26053
  }
26025
26054
  }
26026
26055
  } catch (err) {
26027
- logger$5.critical('Error while calculating scroll percentage', err);
26056
+ logger$6.critical('Error while calculating scroll percentage', err);
26028
26057
  }
26029
26058
  if (shouldTrack) {
26030
26059
  this.mp.track(MP_EV_SCROLL, props);
@@ -26042,7 +26071,7 @@
26042
26071
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
26043
26072
  return;
26044
26073
  }
26045
- logger$5.log('Initializing submit tracking');
26074
+ logger$6.log('Initializing submit tracking');
26046
26075
 
26047
26076
  this.listenerSubmit = function(ev) {
26048
26077
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
@@ -26064,7 +26093,7 @@
26064
26093
  return;
26065
26094
  }
26066
26095
 
26067
- logger$5.log('Initializing page visibility tracking.');
26096
+ logger$6.log('Initializing page visibility tracking.');
26068
26097
  this._initScrollDepthTracking();
26069
26098
  var previousTrackedUrl = _.info.currentUrl();
26070
26099
 
@@ -26149,10 +26178,183 @@
26149
26178
  return win[TARGETING_GLOBAL_NAME];
26150
26179
  };
26151
26180
 
26181
+ var logger$2 = console_with_prefix('flags');
26182
+
26183
+ var MIXPANEL_FLAGS_DB_NAME = 'mixpanelFlagsDb';
26184
+ var FLAGS_STORE_NAME = 'mixpanelFlags';
26185
+
26186
+ // Keeping these two properties closeby, as adding additional stores to a DB in IndexedDB requires a version increment
26187
+ var FLAGS_VERSION_DATA = { version: 1, storeNames: [FLAGS_STORE_NAME] };
26188
+
26189
+ var PERSISTED_VARIANTS_KEY_PREFIX = 'persisted_variants_for_';
26190
+ var DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
26191
+
26192
+ var VariantLookupPolicy = Object.freeze({
26193
+ NETWORK_ONLY: 'networkOnly',
26194
+ NETWORK_FIRST: 'networkFirst',
26195
+ PERSISTENCE_UNTIL_NETWORK_SUCCESS: 'persistenceUntilNetworkSuccess'
26196
+ });
26197
+
26198
+ var VALID_POLICIES = [
26199
+ VariantLookupPolicy.NETWORK_ONLY,
26200
+ VariantLookupPolicy.NETWORK_FIRST,
26201
+ VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS
26202
+ ];
26203
+
26204
+ /**
26205
+ * Module for handling the storage and retrieval of persisted feature flag variants.
26206
+ */
26207
+ var FeatureFlagPersistence = function(persistenceConfig, token, isGloballyDisabled) {
26208
+ this.idb = new IDBStorageWrapper(MIXPANEL_FLAGS_DB_NAME, FLAGS_STORE_NAME, FLAGS_VERSION_DATA);
26209
+ this.persistenceConfig = persistenceConfig;
26210
+ this.persistedVariantsKey = PERSISTED_VARIANTS_KEY_PREFIX + token;
26211
+ this.isGloballyDisabled = isGloballyDisabled || function() { return false; };
26212
+ };
26213
+
26214
+ FeatureFlagPersistence.prototype.getPolicy = function() {
26215
+ if (this.isGloballyDisabled() || !this._isConfigValid()) {
26216
+ return VariantLookupPolicy.NETWORK_ONLY;
26217
+ }
26218
+ return this.persistenceConfig['variantLookupPolicy'];
26219
+ };
26220
+
26221
+ FeatureFlagPersistence.prototype.getTtlMs = function() {
26222
+ if (!this._isConfigValid()) {
26223
+ return DEFAULT_TTL_MS;
26224
+ }
26225
+ var configuredTtl = this.persistenceConfig['persistenceTtlMs'];
26226
+ return (configuredTtl === undefined || configuredTtl === null) ? DEFAULT_TTL_MS : configuredTtl;
26227
+ };
26228
+
26229
+ FeatureFlagPersistence.prototype._isConfigValid = function() {
26230
+ var config = this.persistenceConfig;
26231
+ if (!config) {
26232
+ return false;
26233
+ }
26234
+
26235
+ if (VALID_POLICIES.indexOf(config['variantLookupPolicy']) === -1) {
26236
+ logger$2.error('Invalid variantLookupPolicy:', config['variantLookupPolicy']);
26237
+ return false;
26238
+ }
26239
+
26240
+ if (config['persistenceTtlMs'] !== undefined &&
26241
+ config['persistenceTtlMs'] !== null &&
26242
+ config['persistenceTtlMs'] <= 0) {
26243
+ logger$2.error('If provided, persistenceTtlMs must be a positive number. Provided value:', config['persistenceTtlMs']);
26244
+ return false;
26245
+ }
26246
+
26247
+ return true;
26248
+ };
26249
+
26250
+ FeatureFlagPersistence.prototype.loadFlagsFromStorage = function(context) {
26251
+ var clearAndReturnNull = _.bind(function() {
26252
+ return this.clear().then(function() { return null; }).catch(function() { return null; });
26253
+ }, this);
26254
+
26255
+ if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
26256
+ return clearAndReturnNull();
26257
+ }
26258
+
26259
+ var ttlMs = this.getTtlMs();
26260
+
26261
+ return this.idb.init().then(_.bind(function() {
26262
+ return this.idb.getItem(this.persistedVariantsKey);
26263
+ }, this)).then(_.bind(function(data) {
26264
+ if (!data) {
26265
+ logger$2.log('No persisted variants found in IndexedDB');
26266
+ return null;
26267
+ }
26268
+
26269
+ if (ttlMs && Date.now() - data['persistedAt'] >= ttlMs) {
26270
+ logger$2.log('Persisted variants are expiring');
26271
+ return null;
26272
+ }
26273
+
26274
+ if (!context || data['distinctId'] !== context['distinct_id']) {
26275
+ logger$2.log('Persisted variants found, but for a different distinct_id so clearing.');
26276
+ return clearAndReturnNull();
26277
+ }
26278
+
26279
+ var persistedFlags = new Map();
26280
+ _.each(data['flagVariants'], function(variantData, key) {
26281
+ persistedFlags.set(key, {
26282
+ 'key': variantData['variant_key'],
26283
+ 'value': variantData['variant_value'],
26284
+ 'experiment_id': variantData['experiment_id'],
26285
+ 'is_experiment_active': variantData['is_experiment_active'],
26286
+ 'is_qa_tester': variantData['is_qa_tester'],
26287
+ 'variant_source': 'persistence',
26288
+ 'persisted_at_in_ms': data['persistedAt'],
26289
+ 'ttl_in_ms': ttlMs
26290
+ });
26291
+ });
26292
+
26293
+ logger$2.log('Loaded', persistedFlags.size, 'variants from IndexedDB for distinct_id', data['distinctId']);
26294
+
26295
+ return {
26296
+ flags: persistedFlags,
26297
+ pendingFirstTimeEvents: data['pendingFirstTimeEvents'] || {},
26298
+ persistedAtMs: data['persistedAt'],
26299
+ ttlMs: ttlMs
26300
+ };
26301
+ }, this)).catch(_.bind(function(error) {
26302
+ logger$2.error('Failed to load persisted variants from IndexedDB, so clearing', error);
26303
+ return clearAndReturnNull();
26304
+ }, this));
26305
+ };
26306
+
26307
+ FeatureFlagPersistence.prototype.save = function(context, flagsMap, pendingFirstTimeEvents) {
26308
+ if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
26309
+ return Promise.resolve();
26310
+ }
26311
+
26312
+ var flagVariants = {};
26313
+ flagsMap.forEach(function(variant, key) {
26314
+ flagVariants[key] = {
26315
+ 'variant_key': variant['key'],
26316
+ 'variant_value': variant['value'],
26317
+ 'experiment_id': variant['experiment_id'],
26318
+ 'is_experiment_active': variant['is_experiment_active'],
26319
+ 'is_qa_tester': variant['is_qa_tester']
26320
+ };
26321
+ });
26322
+
26323
+ var data = {
26324
+ 'persistedAt': Date.now(),
26325
+ 'distinctId': context && context['distinct_id'],
26326
+ 'context': context,
26327
+ 'flagVariants': flagVariants,
26328
+ 'pendingFirstTimeEvents': pendingFirstTimeEvents || {}
26329
+ };
26330
+
26331
+ return this.idb.init().then(_.bind(function() {
26332
+ return this.idb.setItem(this.persistedVariantsKey, data);
26333
+ }, this)).then(function() {
26334
+ logger$2.log('Saved', flagsMap.size, 'variants to IndexedDB for distinct_id', data['distinctId']);
26335
+ }).catch(function(error) {
26336
+ logger$2.error('Failed to persist variants to IndexedDB:', error);
26337
+ });
26338
+ };
26339
+
26340
+ FeatureFlagPersistence.prototype.clear = function() {
26341
+ if (this.isGloballyDisabled()) {
26342
+ return Promise.resolve();
26343
+ }
26344
+ return this.idb.init().then(_.bind(function() {
26345
+ return this.idb.removeItem(this.persistedVariantsKey);
26346
+ }, this)).then(function() {
26347
+ logger$2.log('Cleared persisted variants from IndexedDB');
26348
+ }).catch(function(error) {
26349
+ logger$2.error('Failed to clear persisted variants from IndexedDB:', error);
26350
+ });
26351
+ };
26352
+
26152
26353
  var logger$1 = console_with_prefix('flags');
26153
26354
  var FLAGS_CONFIG_KEY = 'flags';
26154
26355
 
26155
26356
  var CONFIG_CONTEXT = 'context';
26357
+ var CONFIG_PERSISTENCE = 'persistence';
26156
26358
  var CONFIG_DEFAULTS = {};
26157
26359
  CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
26158
26360
 
@@ -26175,6 +26377,13 @@
26175
26377
  return eventKey.split(':')[0];
26176
26378
  };
26177
26379
 
26380
+ var withFallbackSource = function(fallback) {
26381
+ if (_.isObject(fallback)) {
26382
+ return _.extend({}, fallback, {'variant_source': 'fallback'});
26383
+ }
26384
+ return {'value': fallback, 'variant_source': 'fallback'};
26385
+ };
26386
+
26178
26387
  /**
26179
26388
  * FeatureFlagManager: support for Mixpanel's feature flagging product
26180
26389
  * @constructor
@@ -26197,13 +26406,63 @@
26197
26406
  }
26198
26407
 
26199
26408
  this.flags = null;
26200
- this.fetchFlags().catch(function() {
26201
- logger$1.error('Error fetching flags during init');
26202
- });
26203
-
26204
26409
  this.trackedFeatures = new Set();
26205
26410
  this.pendingFirstTimeEvents = {};
26206
26411
  this.activatedFirstTimeEvents = {};
26412
+ this._loadedPersistedAtMs = null;
26413
+ this._loadedTtlMs = null;
26414
+
26415
+ this.persistence = new FeatureFlagPersistence(
26416
+ this.getConfig(CONFIG_PERSISTENCE),
26417
+ this.getMpConfig('token'),
26418
+ _.bind(function() { return this.getMpConfig('disable_persistence'); }, this)
26419
+ );
26420
+
26421
+ this.persistenceLoadedPromise = this.persistence.loadFlagsFromStorage(this._buildContext())
26422
+ .then(_.bind(function(loaded) {
26423
+ if (loaded) {
26424
+ this.flags = loaded.flags;
26425
+ this.pendingFirstTimeEvents = loaded.pendingFirstTimeEvents;
26426
+ this._loadedPersistedAtMs = loaded.persistedAtMs;
26427
+ this._loadedTtlMs = loaded.ttlMs;
26428
+ }
26429
+ }, this));
26430
+
26431
+ return this.persistenceLoadedPromise
26432
+ .then(_.bind(function() {
26433
+ return this.fetchFlags();
26434
+ }, this))
26435
+ .catch(function() {
26436
+ logger$1.error('Error initializing feature flags');
26437
+ });
26438
+ };
26439
+
26440
+ FeatureFlagManager.prototype._buildContext = function() {
26441
+ return _.extend(
26442
+ {'distinct_id': this.getMpProperty('distinct_id'), 'device_id': this.getMpProperty('$device_id')},
26443
+ this.getConfig(CONFIG_CONTEXT)
26444
+ );
26445
+ };
26446
+
26447
+ FeatureFlagManager.prototype.reset = function() {
26448
+ if (!this.persistence) {
26449
+ return Promise.resolve();
26450
+ }
26451
+
26452
+ this.flags = null;
26453
+ this.pendingFirstTimeEvents = {};
26454
+ this.activatedFirstTimeEvents = {};
26455
+ this.trackedFeatures = new Set();
26456
+ this.fetchPromise = null;
26457
+ this._fetchInProgressStartTime = null;
26458
+ this._loadedPersistedAtMs = null;
26459
+ this._loadedTtlMs = null;
26460
+
26461
+ return this.persistence.clear().then(_.bind(function() {
26462
+ return this.fetchFlags();
26463
+ }, this)).catch(function() {
26464
+ logger$1.error('Error during flags reset');
26465
+ });
26207
26466
  };
26208
26467
 
26209
26468
  FeatureFlagManager.prototype.getFullConfig = function() {
@@ -26260,12 +26519,11 @@
26260
26519
  return Promise.resolve();
26261
26520
  }
26262
26521
 
26263
- var distinctId = this.getMpProperty('distinct_id');
26264
- var deviceId = this.getMpProperty('$device_id');
26522
+ var context = this._buildContext();
26523
+ var distinctId = context['distinct_id'];
26265
26524
  var traceparent = generateTraceparent();
26266
26525
  logger$1.log('Fetching flags for distinct ID: ' + distinctId);
26267
26526
 
26268
- var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
26269
26527
  var searchParams = new URLSearchParams();
26270
26528
  searchParams.set('context', JSON.stringify(context));
26271
26529
  searchParams.set('token', this.getMpConfig('token'));
@@ -26315,7 +26573,8 @@
26315
26573
  'value': data['variant_value'],
26316
26574
  'experiment_id': data['experiment_id'],
26317
26575
  'is_experiment_active': data['is_experiment_active'],
26318
- 'is_qa_tester': data['is_qa_tester']
26576
+ 'is_qa_tester': data['is_qa_tester'],
26577
+ 'variant_source': 'network'
26319
26578
  });
26320
26579
  }
26321
26580
  }, this);
@@ -26357,10 +26616,15 @@
26357
26616
  }
26358
26617
 
26359
26618
  this.flags = flags;
26619
+ this.trackedFeatures = new Set();
26360
26620
  this.pendingFirstTimeEvents = pendingFirstTimeEvents;
26621
+ this._loadedPersistedAtMs = null;
26622
+ this._loadedTtlMs = null;
26361
26623
  this._traceparent = traceparent;
26362
26624
 
26363
26625
  this._loadTargetingIfNeeded();
26626
+
26627
+ this.persistence.save(context, this.flags, this.pendingFirstTimeEvents);
26364
26628
  }.bind(this)).catch(function(error) {
26365
26629
  if (this._fetchInProgressStartTime) {
26366
26630
  this.markFetchComplete();
@@ -26520,6 +26784,7 @@
26520
26784
  };
26521
26785
 
26522
26786
  this.flags.set(flagKey, newVariant);
26787
+ this.trackedFeatures.delete(flagKey);
26523
26788
  this.activatedFirstTimeEvents[eventKey] = true;
26524
26789
 
26525
26790
  this.recordFirstTimeEvent(
@@ -26569,35 +26834,106 @@
26569
26834
  };
26570
26835
 
26571
26836
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
26572
- if (!this.fetchPromise) {
26837
+ if (!this.persistenceLoadedPromise) {
26573
26838
  return new Promise(function(resolve) {
26574
26839
  logger$1.critical('Feature Flags not initialized');
26575
- resolve(fallback);
26840
+ resolve(withFallbackSource(fallback));
26576
26841
  });
26577
26842
  }
26578
26843
 
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
- });
26844
+ var policy = this.persistence.getPolicy();
26845
+
26846
+ return this.persistenceLoadedPromise.then(_.bind(function() {
26847
+ // 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.
26848
+ if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
26849
+ if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
26850
+ return this.getVariantSync(featureName, fallback);
26851
+ }
26852
+ if (!this.fetchPromise) {
26853
+ return withFallbackSource(fallback);
26854
+ }
26855
+ return this.fetchPromise.then(_.bind(function() {
26856
+ return this.getVariantSync(featureName, fallback);
26857
+ }, this)).catch(function(error) {
26858
+ logger$1.error(error);
26859
+ return withFallbackSource(fallback);
26860
+ });
26861
+ }
26862
+
26863
+ var serve = _.bind(function() { return this.getVariantSync(featureName, fallback); }, this);
26864
+ if (!this.fetchPromise) {
26865
+ return withFallbackSource(fallback);
26866
+ }
26867
+ return this.fetchPromise.then(serve).catch(serve);
26868
+ }, this));
26869
+ };
26870
+
26871
+ FeatureFlagManager.prototype._loadedPersistenceIsStale = function() {
26872
+ if (!this._loadedPersistedAtMs || !this._loadedTtlMs) {
26873
+ return false;
26874
+ }
26875
+ return Date.now() - this._loadedPersistedAtMs >= this._loadedTtlMs;
26585
26876
  };
26586
26877
 
26587
26878
  FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
26879
+ if (this._loadedPersistenceIsStale()) {
26880
+ logger$1.log('Loaded persisted variants are past TTL so returning fallback for "' + featureName + '"');
26881
+ return withFallbackSource(fallback);
26882
+ }
26588
26883
  if (!this.areFlagsReady()) {
26589
26884
  logger$1.log('Flags not loaded yet');
26590
- return fallback;
26885
+ return withFallbackSource(fallback);
26591
26886
  }
26592
26887
  var feature = this.flags.get(featureName);
26593
26888
  if (!feature) {
26594
26889
  logger$1.log('No flag found: "' + featureName + '"');
26595
- return fallback;
26890
+ return withFallbackSource(fallback);
26596
26891
  }
26597
26892
  this.trackFeatureCheck(featureName, feature);
26598
26893
  return feature;
26599
26894
  };
26600
26895
 
26896
+ FeatureFlagManager.prototype.getAllVariants = function() {
26897
+ if (!this.persistenceLoadedPromise) {
26898
+ logger$1.critical('Feature Flags not initialized');
26899
+ return Promise.resolve(new Map());
26900
+ }
26901
+
26902
+ var policy = this.persistence.getPolicy();
26903
+
26904
+ return this.persistenceLoadedPromise.then(_.bind(function() {
26905
+ // 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.
26906
+ if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
26907
+ if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
26908
+ return this.getAllVariantsSync();
26909
+ }
26910
+ if (!this.fetchPromise) {
26911
+ return new Map();
26912
+ }
26913
+ return this.fetchPromise.then(_.bind(function() {
26914
+ return this.getAllVariantsSync();
26915
+ }, this)).catch(function(error) {
26916
+ logger$1.error(error);
26917
+ return new Map();
26918
+ });
26919
+ }
26920
+
26921
+ var serve = _.bind(this.getAllVariantsSync, this);
26922
+ if (!this.fetchPromise) {
26923
+ return new Map();
26924
+ }
26925
+ return this.fetchPromise.then(serve).catch(serve);
26926
+ }, this));
26927
+ };
26928
+
26929
+ FeatureFlagManager.prototype.getAllVariantsSync = function() {
26930
+ if (this._loadedPersistenceIsStale()) {
26931
+ logger$1.log('Loaded persisted variants are past TTL so returning empty Map');
26932
+ return new Map();
26933
+ }
26934
+ return this.flags || new Map();
26935
+ };
26936
+
26601
26937
  FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
26602
26938
  return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
26603
26939
  return feature['value'];
@@ -26636,6 +26972,10 @@
26636
26972
  return val;
26637
26973
  };
26638
26974
 
26975
+ function isPresent(v) {
26976
+ return v !== undefined && v !== null;
26977
+ }
26978
+
26639
26979
  FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
26640
26980
  if (this.trackedFeatures.has(featureName)) {
26641
26981
  return;
@@ -26646,21 +26986,30 @@
26646
26986
  'Experiment name': featureName,
26647
26987
  'Variant name': feature['key'],
26648
26988
  '$experiment_type': 'feature_flag',
26649
- 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
26650
- 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
26989
+ 'Variant fetch start time': isPresent(this._fetchStartTime) ? new Date(this._fetchStartTime).toISOString() : null,
26990
+ 'Variant fetch complete time': isPresent(this._fetchCompleteTime) ? new Date(this._fetchCompleteTime).toISOString() : null,
26651
26991
  'Variant fetch latency (ms)': this._fetchLatency,
26652
26992
  'Variant fetch traceparent': this._traceparent,
26653
26993
  };
26654
26994
 
26655
- if (feature['experiment_id'] !== 'undefined') {
26995
+ if (isPresent(feature['experiment_id'])) {
26656
26996
  trackingProperties['$experiment_id'] = feature['experiment_id'];
26657
26997
  }
26658
- if (feature['is_experiment_active'] !== 'undefined') {
26998
+ if (isPresent(feature['is_experiment_active'])) {
26659
26999
  trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
26660
27000
  }
26661
- if (feature['is_qa_tester'] !== 'undefined') {
27001
+ if (isPresent(feature['is_qa_tester'])) {
26662
27002
  trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
26663
27003
  }
27004
+ if (isPresent(feature['variant_source'])) {
27005
+ trackingProperties['$variant_source'] = feature['variant_source'];
27006
+ }
27007
+ if (isPresent(feature['persisted_at_in_ms'])) {
27008
+ trackingProperties['$persisted_at_in_ms'] = feature['persisted_at_in_ms'];
27009
+ }
27010
+ if (isPresent(feature['ttl_in_ms'])) {
27011
+ trackingProperties['$ttl_in_ms'] = feature['ttl_in_ms'];
27012
+ }
26664
27013
 
26665
27014
  this.track('$experiment_started', trackingProperties);
26666
27015
  };
@@ -26684,6 +27033,8 @@
26684
27033
  FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
26685
27034
  FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
26686
27035
  FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
27036
+ FeatureFlagManager.prototype['get_all_variants'] = FeatureFlagManager.prototype.getAllVariants;
27037
+ FeatureFlagManager.prototype['get_all_variants_sync'] = FeatureFlagManager.prototype.getAllVariantsSync;
26687
27038
  FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
26688
27039
  FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
26689
27040
  FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
@@ -26736,7 +27087,7 @@
26736
27087
  return PromisePolyfill.resolve(false);
26737
27088
  }
26738
27089
 
26739
- var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
27090
+ var recording_registry_idb = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_REGISTRY_STORE_NAME, RECORDER_VERSION_DATA);
26740
27091
  var tab_id = this.getTabId();
26741
27092
  return recording_registry_idb.init()
26742
27093
  .then(function () {
@@ -28663,6 +29014,7 @@
28663
29014
  'disable_all_events': false,
28664
29015
  'identify_called': false
28665
29016
  };
29017
+ this._remote_settings_strict_disabled = false;
28666
29018
 
28667
29019
  // set up request queueing/batching
28668
29020
  this.request_batchers = {};
@@ -28737,9 +29089,6 @@
28737
29089
  this.flags.init();
28738
29090
  this['flags'] = this.flags;
28739
29091
 
28740
- this.autocapture = new Autocapture(this);
28741
- this.autocapture.init();
28742
-
28743
29092
  this._init_tab_id();
28744
29093
 
28745
29094
  // Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
@@ -28751,6 +29100,9 @@
28751
29100
  } else {
28752
29101
  this.__session_recording_init_promise = this._check_and_start_session_recording();
28753
29102
  }
29103
+
29104
+ this.autocapture = new Autocapture(this);
29105
+ this.autocapture.init();
28754
29106
  };
28755
29107
 
28756
29108
  /**
@@ -28797,9 +29149,19 @@
28797
29149
  return this.recorderManager.checkAndStartSessionRecording(force_start);
28798
29150
  });
28799
29151
 
28800
- MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
28801
- return this.recorderManager.startRecordingOnEvent(event_name, properties);
28802
- };
29152
+ MixpanelLib.prototype._start_recording_on_event = safewrap(function(event_name, properties) {
29153
+ // Wait for recording init to complete before evaluating event triggers.
29154
+ // This ensures recording_event_triggers config is fully loaded when remote settings are used.
29155
+ if (this.__session_recording_init_promise) {
29156
+ this.__session_recording_init_promise.then(_.bind(function() {
29157
+ // In strict mode, skip recording if remote settings failed
29158
+ if (this._remote_settings_strict_disabled) {
29159
+ return;
29160
+ }
29161
+ return this.recorderManager.startRecordingOnEvent(event_name, properties);
29162
+ }, this));
29163
+ }
29164
+ });
28803
29165
 
28804
29166
  MixpanelLib.prototype.start_session_recording = function () {
28805
29167
  return this._check_and_start_session_recording(true);
@@ -29098,6 +29460,7 @@
29098
29460
  var disableRecordingIfStrict = function() {
29099
29461
  if (mode === 'strict') {
29100
29462
  self.set_config({'record_sessions_percent': 0});
29463
+ self._remote_settings_strict_disabled = true;
29101
29464
  }
29102
29465
  };
29103
29466
 
@@ -29723,6 +30086,10 @@
29723
30086
  properties
29724
30087
  );
29725
30088
 
30089
+ if (this.is_recording_heatmap_data()) {
30090
+ event_properties['$captured_for_heatmap'] = true;
30091
+ }
30092
+
29726
30093
  return this.track(event_name, event_properties);
29727
30094
  });
29728
30095
 
@@ -30066,6 +30433,7 @@
30066
30433
  '$device_id': uuid
30067
30434
  }, '');
30068
30435
  this._check_and_start_session_recording();
30436
+ this.flags.reset();
30069
30437
  };
30070
30438
 
30071
30439
  /**