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
@@ -3,7 +3,7 @@
3
3
 
4
4
  var Config = {
5
5
  DEBUG: false,
6
- LIB_VERSION: '2.78.0'
6
+ LIB_VERSION: '2.79.0'
7
7
  };
8
8
 
9
9
  // Window global names for async modules
@@ -11,8 +11,8 @@
11
11
  var RECORDER_GLOBAL_NAME = '__mp_recorder';
12
12
 
13
13
  // Constants that are injected at build-time for the names of async modules.
14
- var RECORDER_FILENAME = 'mixpanel-recorder-zMBXIyeG.js';
15
- var TARGETING_FILENAME = 'mixpanel-targeting-UHf4eBfC.js';
14
+ var RECORDER_FILENAME = 'mixpanel-recorder-P6SEnnPV.js';
15
+ var TARGETING_FILENAME = 'mixpanel-targeting-BBMVbgJF.js';
16
16
 
17
17
  // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
18
18
  var win;
@@ -502,6 +502,7 @@
502
502
  var console_with_prefix = function(prefix) {
503
503
  return {
504
504
  log: log_func_with_prefix(console.log, prefix),
505
+ warn: log_func_with_prefix(console.warn, prefix),
505
506
  error: log_func_with_prefix(console.error, prefix),
506
507
  critical: log_func_with_prefix(console.critical, prefix)
507
508
  };
@@ -1448,7 +1449,8 @@
1448
1449
  if (_localStorageSupported !== null && !forceCheck) {
1449
1450
  return _localStorageSupported;
1450
1451
  }
1451
- return _localStorageSupported = _testStorageSupported(storage || win.localStorage);
1452
+
1453
+ return _localStorageSupported = _testStorageSupported(storage);
1452
1454
  };
1453
1455
 
1454
1456
  var _sessionStorageSupported = null;
@@ -1456,7 +1458,8 @@
1456
1458
  if (_sessionStorageSupported !== null && !forceCheck) {
1457
1459
  return _sessionStorageSupported;
1458
1460
  }
1459
- return _sessionStorageSupported = _testStorageSupported(storage || win.sessionStorage);
1461
+
1462
+ return _sessionStorageSupported = _testStorageSupported(storage);
1460
1463
  };
1461
1464
 
1462
1465
  function _storageWrapper(storage, name, is_supported_fn) {
@@ -1506,17 +1509,26 @@
1506
1509
  };
1507
1510
  }
1508
1511
 
1509
- // Safari errors out accessing localStorage/sessionStorage when cookies are disabled,
1510
- // so create dummy storage wrappers that silently fail as a fallback.
1511
- var windowLocalStorage = null, windowSessionStorage = null;
1512
- try {
1513
- windowLocalStorage = win.localStorage;
1514
- windowSessionStorage = win.sessionStorage;
1515
- // eslint-disable-next-line no-empty
1516
- } catch (_err) {}
1512
+ // Safari and other browsers may error out accessing localStorage/sessionStorage
1513
+ // when cookies are disabled, so wrap access in a try-catch.
1514
+ var getLocalStorage = function() {
1515
+ try {
1516
+ return win.localStorage; // eslint-disable-line no-restricted-properties
1517
+ } catch (_err) {
1518
+ return null;
1519
+ }
1520
+ };
1517
1521
 
1518
- _.localStorage = _storageWrapper(windowLocalStorage, 'localStorage', localStorageSupported);
1519
- _.sessionStorage = _storageWrapper(windowSessionStorage, 'sessionStorage', sessionStorageSupported);
1522
+ var getSessionStorage = function() {
1523
+ try {
1524
+ return win.sessionStorage; // eslint-disable-line no-restricted-properties
1525
+ } catch (_err) {
1526
+ return null;
1527
+ }
1528
+ };
1529
+
1530
+ _.localStorage = _storageWrapper(getLocalStorage(), 'localStorage', localStorageSupported);
1531
+ _.sessionStorage = _storageWrapper(getSessionStorage(), 'sessionStorage', sessionStorageSupported);
1520
1532
 
1521
1533
  _.register_event = (function() {
1522
1534
  // written by Dean Edwards, 2005
@@ -2268,7 +2280,7 @@
2268
2280
 
2269
2281
  var MAX_DEPTH = 5;
2270
2282
 
2271
- var logger$5 = console_with_prefix('autocapture');
2283
+ var logger$6 = console_with_prefix('autocapture');
2272
2284
 
2273
2285
 
2274
2286
  function getClasses(el) {
@@ -2532,7 +2544,7 @@
2532
2544
  return false;
2533
2545
  }
2534
2546
  } catch (err) {
2535
- logger$5.critical('Error while checking element in allowElementCallback', err);
2547
+ logger$6.critical('Error while checking element in allowElementCallback', err);
2536
2548
  return false;
2537
2549
  }
2538
2550
  }
@@ -2549,7 +2561,7 @@
2549
2561
  return true;
2550
2562
  }
2551
2563
  } catch (err) {
2552
- logger$5.critical('Error while checking selector: ' + sel, err);
2564
+ logger$6.critical('Error while checking selector: ' + sel, err);
2553
2565
  }
2554
2566
  }
2555
2567
  return false;
@@ -2564,7 +2576,7 @@
2564
2576
  return true;
2565
2577
  }
2566
2578
  } catch (err) {
2567
- logger$5.critical('Error while checking element in blockElementCallback', err);
2579
+ logger$6.critical('Error while checking element in blockElementCallback', err);
2568
2580
  return true;
2569
2581
  }
2570
2582
  }
@@ -2578,7 +2590,7 @@
2578
2590
  return true;
2579
2591
  }
2580
2592
  } catch (err) {
2581
- logger$5.critical('Error while checking selector: ' + sel, err);
2593
+ logger$6.critical('Error while checking selector: ' + sel, err);
2582
2594
  }
2583
2595
  }
2584
2596
  }
@@ -3042,7 +3054,7 @@
3042
3054
  observer.observe(shadowRoot, this.observerConfig);
3043
3055
  this.shadowObservers.push(observer);
3044
3056
  } catch (e) {
3045
- logger$5.critical('Error while observing shadow root', e);
3057
+ logger$6.critical('Error while observing shadow root', e);
3046
3058
  }
3047
3059
  };
3048
3060
 
@@ -3053,7 +3065,7 @@
3053
3065
  }
3054
3066
 
3055
3067
  if (!weakSetSupported()) {
3056
- logger$5.critical('Shadow DOM observation unavailable: WeakSet not supported');
3068
+ logger$6.critical('Shadow DOM observation unavailable: WeakSet not supported');
3057
3069
  return;
3058
3070
  }
3059
3071
 
@@ -3069,7 +3081,7 @@
3069
3081
  try {
3070
3082
  this.shadowObservers[i].disconnect();
3071
3083
  } catch (e) {
3072
- logger$5.critical('Error while disconnecting shadow DOM observer', e);
3084
+ logger$6.critical('Error while disconnecting shadow DOM observer', e);
3073
3085
  }
3074
3086
  }
3075
3087
  this.shadowObservers = [];
@@ -3257,7 +3269,7 @@
3257
3269
 
3258
3270
  this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
3259
3271
  } catch (e) {
3260
- logger$5.critical('Error while setting up mutation observer', e);
3272
+ logger$6.critical('Error while setting up mutation observer', e);
3261
3273
  }
3262
3274
  }
3263
3275
 
@@ -3272,7 +3284,7 @@
3272
3284
  );
3273
3285
  this.shadowDOMObserver.start();
3274
3286
  } catch (e) {
3275
- logger$5.critical('Error while setting up shadow DOM observer', e);
3287
+ logger$6.critical('Error while setting up shadow DOM observer', e);
3276
3288
  this.shadowDOMObserver = null;
3277
3289
  }
3278
3290
  }
@@ -3299,7 +3311,7 @@
3299
3311
  try {
3300
3312
  listener.target.removeEventListener(listener.event, listener.handler, listener.options);
3301
3313
  } catch (e) {
3302
- logger$5.critical('Error while removing event listener', e);
3314
+ logger$6.critical('Error while removing event listener', e);
3303
3315
  }
3304
3316
  }
3305
3317
  this.eventListeners = [];
@@ -3308,7 +3320,7 @@
3308
3320
  try {
3309
3321
  this.mutationObserver.disconnect();
3310
3322
  } catch (e) {
3311
- logger$5.critical('Error while disconnecting mutation observer', e);
3323
+ logger$6.critical('Error while disconnecting mutation observer', e);
3312
3324
  }
3313
3325
  this.mutationObserver = null;
3314
3326
  }
@@ -3317,7 +3329,7 @@
3317
3329
  try {
3318
3330
  this.shadowDOMObserver.stop();
3319
3331
  } catch (e) {
3320
- logger$5.critical('Error while stopping shadow DOM observer', e);
3332
+ logger$6.critical('Error while stopping shadow DOM observer', e);
3321
3333
  }
3322
3334
  this.shadowDOMObserver = null;
3323
3335
  }
@@ -3395,7 +3407,7 @@
3395
3407
 
3396
3408
  Autocapture.prototype.init = function() {
3397
3409
  if (!minDOMApisSupported()) {
3398
- logger$5.critical('Autocapture unavailable: missing required DOM APIs');
3410
+ logger$6.critical('Autocapture unavailable: missing required DOM APIs');
3399
3411
  return;
3400
3412
  }
3401
3413
  this.initPageListeners();
@@ -3435,7 +3447,7 @@
3435
3447
  try {
3436
3448
  return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
3437
3449
  } catch (err) {
3438
- logger$5.critical('Error while checking block URL regexes: ', err);
3450
+ logger$6.critical('Error while checking block URL regexes: ', err);
3439
3451
  return true;
3440
3452
  }
3441
3453
  }
@@ -3448,7 +3460,7 @@
3448
3460
  try {
3449
3461
  return urlMatchesRegexList(currentUrl, blockUrlRegexes);
3450
3462
  } catch (err) {
3451
- logger$5.critical('Error while checking block URL regexes: ', err);
3463
+ logger$6.critical('Error while checking block URL regexes: ', err);
3452
3464
  return true;
3453
3465
  }
3454
3466
  };
@@ -3586,7 +3598,7 @@
3586
3598
  return;
3587
3599
  }
3588
3600
 
3589
- logger$5.log('Initializing scroll depth tracking');
3601
+ logger$6.log('Initializing scroll depth tracking');
3590
3602
 
3591
3603
  this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
3592
3604
 
@@ -3612,7 +3624,7 @@
3612
3624
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
3613
3625
  return;
3614
3626
  }
3615
- logger$5.log('Initializing click tracking');
3627
+ logger$6.log('Initializing click tracking');
3616
3628
 
3617
3629
  this.listenerClick = function(ev) {
3618
3630
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
@@ -3631,7 +3643,7 @@
3631
3643
  return;
3632
3644
  }
3633
3645
 
3634
- logger$5.log('Initializing dead click tracking');
3646
+ logger$6.log('Initializing dead click tracking');
3635
3647
  if (!this._deadClickTracker) {
3636
3648
  this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
3637
3649
  this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
@@ -3665,7 +3677,7 @@
3665
3677
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
3666
3678
  return;
3667
3679
  }
3668
- logger$5.log('Initializing input tracking');
3680
+ logger$6.log('Initializing input tracking');
3669
3681
 
3670
3682
  this.listenerChange = function(ev) {
3671
3683
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
@@ -3679,14 +3691,15 @@
3679
3691
  Autocapture.prototype.initPageviewTracking = function() {
3680
3692
  win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
3681
3693
 
3682
- if (!this.pageviewTrackingConfig()) {
3694
+ if (!this.pageviewTrackingConfig() && !this.mp.get_config('record_heatmap_data')) {
3683
3695
  return;
3684
3696
  }
3685
- logger$5.log('Initializing pageview tracking');
3697
+ logger$6.log('Initializing pageview tracking');
3686
3698
 
3687
3699
  var previousTrackedUrl = '';
3688
3700
  var tracked = false;
3689
- if (!this.currentUrlBlocked()) {
3701
+ // Track initial pageview if pageview tracking enabled OR heatmap recording is active
3702
+ if ((this.pageviewTrackingConfig() || this.mp.is_recording_heatmap_data()) && !this.currentUrlBlocked()) {
3690
3703
  tracked = this.mp.track_pageview(DEFAULT_PROPS);
3691
3704
  }
3692
3705
  if (tracked) {
@@ -3702,6 +3715,10 @@
3702
3715
  var shouldTrack = false;
3703
3716
  var didPathChange = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
3704
3717
  var trackPageviewOption = this.pageviewTrackingConfig();
3718
+ if (!trackPageviewOption && this.mp.is_recording_heatmap_data()) {
3719
+ trackPageviewOption = PAGEVIEW_OPTION_FULL_URL;
3720
+ }
3721
+
3705
3722
  if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
3706
3723
  shouldTrack = currentUrl !== previousTrackedUrl;
3707
3724
  } else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
@@ -3717,7 +3734,7 @@
3717
3734
  }
3718
3735
  if (didPathChange) {
3719
3736
  this.lastScrollCheckpoint = 0;
3720
- logger$5.log('Path change: re-initializing scroll depth checkpoints');
3737
+ logger$6.log('Path change: re-initializing scroll depth checkpoints');
3721
3738
  }
3722
3739
  }
3723
3740
  }.bind(this));
@@ -3732,7 +3749,7 @@
3732
3749
  return;
3733
3750
  }
3734
3751
 
3735
- logger$5.log('Initializing rage click tracking');
3752
+ logger$6.log('Initializing rage click tracking');
3736
3753
  if (!this._rageClickTracker) {
3737
3754
  this._rageClickTracker = new RageClickTracker();
3738
3755
  }
@@ -3762,7 +3779,7 @@
3762
3779
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
3763
3780
  return;
3764
3781
  }
3765
- logger$5.log('Initializing scroll tracking');
3782
+ logger$6.log('Initializing scroll tracking');
3766
3783
  this.lastScrollCheckpoint = 0;
3767
3784
 
3768
3785
  var scrollTrackFunction = function() {
@@ -3799,7 +3816,7 @@
3799
3816
  }
3800
3817
  }
3801
3818
  } catch (err) {
3802
- logger$5.critical('Error while calculating scroll percentage', err);
3819
+ logger$6.critical('Error while calculating scroll percentage', err);
3803
3820
  }
3804
3821
  if (shouldTrack) {
3805
3822
  this.mp.track(MP_EV_SCROLL, props);
@@ -3817,7 +3834,7 @@
3817
3834
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
3818
3835
  return;
3819
3836
  }
3820
- logger$5.log('Initializing submit tracking');
3837
+ logger$6.log('Initializing submit tracking');
3821
3838
 
3822
3839
  this.listenerSubmit = function(ev) {
3823
3840
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
@@ -3839,7 +3856,7 @@
3839
3856
  return;
3840
3857
  }
3841
3858
 
3842
- logger$5.log('Initializing page visibility tracking.');
3859
+ logger$6.log('Initializing page visibility tracking.');
3843
3860
  this._initScrollDepthTracking();
3844
3861
  var previousTrackedUrl = _.info.currentUrl();
3845
3862
 
@@ -3924,10 +3941,309 @@
3924
3941
  return win[TARGETING_GLOBAL_NAME];
3925
3942
  };
3926
3943
 
3944
+ /**
3945
+ * @type {import('./wrapper').StorageWrapper}
3946
+ */
3947
+ var IDBStorageWrapper = function (dbName, storeName, versionData) {
3948
+ this.dbName = dbName;
3949
+ this.storeName = storeName;
3950
+ this.version = versionData.version;
3951
+ this.storeNamesInDb = versionData.storeNames;
3952
+ /**
3953
+ * @type {Promise<IDBDatabase>|null}
3954
+ */
3955
+ this.dbPromise = null;
3956
+ };
3957
+
3958
+ IDBStorageWrapper.prototype._openDb = function () {
3959
+ var dbName = this.dbName;
3960
+ var version = this.version;
3961
+ var storeNamesInDb = this.storeNamesInDb;
3962
+ return new PromisePolyfill(function (resolve, reject) {
3963
+ var openRequest = win.indexedDB.open(dbName, version);
3964
+ openRequest['onerror'] = function () {
3965
+ reject(openRequest.error);
3966
+ };
3967
+
3968
+ openRequest['onsuccess'] = function () {
3969
+ resolve(openRequest.result);
3970
+ };
3971
+
3972
+ openRequest['onupgradeneeded'] = function (ev) {
3973
+ var db = ev.target.result;
3974
+
3975
+ storeNamesInDb.forEach(function (storeName) {
3976
+ if (!db.objectStoreNames.contains(storeName)) {
3977
+ db.createObjectStore(storeName);
3978
+ }
3979
+ });
3980
+ };
3981
+ });
3982
+ };
3983
+
3984
+ IDBStorageWrapper.prototype.init = function () {
3985
+ if (!win.indexedDB) {
3986
+ return PromisePolyfill.reject('indexedDB is not supported in this browser');
3987
+ }
3988
+
3989
+ if (!this.dbPromise) {
3990
+ this.dbPromise = this._openDb();
3991
+ }
3992
+
3993
+ return this.dbPromise
3994
+ .then(function (dbOrError) {
3995
+ if (dbOrError instanceof win['IDBDatabase']) {
3996
+ return PromisePolyfill.resolve();
3997
+ } else {
3998
+ return PromisePolyfill.reject(dbOrError);
3999
+ }
4000
+ });
4001
+ };
4002
+
4003
+ IDBStorageWrapper.prototype.isInitialized = function () {
4004
+ return !!this.dbPromise;
4005
+ };
4006
+
4007
+ /**
4008
+ * @param {IDBTransactionMode} mode
4009
+ * @param {function(IDBObjectStore): void} storeCb
4010
+ */
4011
+ IDBStorageWrapper.prototype.makeTransaction = function (mode, storeCb) {
4012
+ var storeName = this.storeName;
4013
+ var doTransaction = function (db) {
4014
+ return new PromisePolyfill(function (resolve, reject) {
4015
+ var transaction = db.transaction(storeName, mode);
4016
+ transaction.oncomplete = function () {
4017
+ resolve(transaction);
4018
+ };
4019
+ transaction.onabort = transaction.onerror = function () {
4020
+ reject(transaction.error);
4021
+ };
4022
+
4023
+ storeCb(transaction.objectStore(storeName));
4024
+ });
4025
+ };
4026
+
4027
+ return this.dbPromise
4028
+ .then(doTransaction)
4029
+ .catch(function (err) {
4030
+ if (err && err['name'] === 'InvalidStateError') {
4031
+ // try reopening the DB if the connection is closed
4032
+ this.dbPromise = this._openDb();
4033
+ return this.dbPromise.then(doTransaction);
4034
+ } else {
4035
+ return PromisePolyfill.reject(err);
4036
+ }
4037
+ }.bind(this));
4038
+ };
4039
+
4040
+ IDBStorageWrapper.prototype.setItem = function (key, value) {
4041
+ return this.makeTransaction('readwrite', function (objectStore) {
4042
+ objectStore.put(value, key);
4043
+ });
4044
+ };
4045
+
4046
+ IDBStorageWrapper.prototype.getItem = function (key) {
4047
+ var req;
4048
+ return this.makeTransaction('readonly', function (objectStore) {
4049
+ req = objectStore.get(key);
4050
+ }).then(function () {
4051
+ return req.result;
4052
+ });
4053
+ };
4054
+
4055
+ IDBStorageWrapper.prototype.removeItem = function (key) {
4056
+ return this.makeTransaction('readwrite', function (objectStore) {
4057
+ objectStore.delete(key);
4058
+ });
4059
+ };
4060
+
4061
+ IDBStorageWrapper.prototype.getAll = function () {
4062
+ var req;
4063
+ return this.makeTransaction('readonly', function (objectStore) {
4064
+ req = objectStore.getAll();
4065
+ }).then(function () {
4066
+ return req.result;
4067
+ });
4068
+ };
4069
+
4070
+ var logger$5 = console_with_prefix('flags');
4071
+
4072
+ var MIXPANEL_FLAGS_DB_NAME = 'mixpanelFlagsDb';
4073
+ var FLAGS_STORE_NAME = 'mixpanelFlags';
4074
+
4075
+ // Keeping these two properties closeby, as adding additional stores to a DB in IndexedDB requires a version increment
4076
+ var FLAGS_VERSION_DATA = { version: 1, storeNames: [FLAGS_STORE_NAME] };
4077
+
4078
+ var PERSISTED_VARIANTS_KEY_PREFIX = 'persisted_variants_for_';
4079
+ var DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
4080
+
4081
+ var VariantLookupPolicy = Object.freeze({
4082
+ NETWORK_ONLY: 'networkOnly',
4083
+ NETWORK_FIRST: 'networkFirst',
4084
+ PERSISTENCE_UNTIL_NETWORK_SUCCESS: 'persistenceUntilNetworkSuccess'
4085
+ });
4086
+
4087
+ var VALID_POLICIES = [
4088
+ VariantLookupPolicy.NETWORK_ONLY,
4089
+ VariantLookupPolicy.NETWORK_FIRST,
4090
+ VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS
4091
+ ];
4092
+
4093
+ /**
4094
+ * Module for handling the storage and retrieval of persisted feature flag variants.
4095
+ */
4096
+ var FeatureFlagPersistence = function(persistenceConfig, token, isGloballyDisabled) {
4097
+ this.idb = new IDBStorageWrapper(MIXPANEL_FLAGS_DB_NAME, FLAGS_STORE_NAME, FLAGS_VERSION_DATA);
4098
+ this.persistenceConfig = persistenceConfig;
4099
+ this.persistedVariantsKey = PERSISTED_VARIANTS_KEY_PREFIX + token;
4100
+ this.isGloballyDisabled = isGloballyDisabled || function() { return false; };
4101
+ };
4102
+
4103
+ FeatureFlagPersistence.prototype.getPolicy = function() {
4104
+ if (this.isGloballyDisabled() || !this._isConfigValid()) {
4105
+ return VariantLookupPolicy.NETWORK_ONLY;
4106
+ }
4107
+ return this.persistenceConfig['variantLookupPolicy'];
4108
+ };
4109
+
4110
+ FeatureFlagPersistence.prototype.getTtlMs = function() {
4111
+ if (!this._isConfigValid()) {
4112
+ return DEFAULT_TTL_MS;
4113
+ }
4114
+ var configuredTtl = this.persistenceConfig['persistenceTtlMs'];
4115
+ return (configuredTtl === undefined || configuredTtl === null) ? DEFAULT_TTL_MS : configuredTtl;
4116
+ };
4117
+
4118
+ FeatureFlagPersistence.prototype._isConfigValid = function() {
4119
+ var config = this.persistenceConfig;
4120
+ if (!config) {
4121
+ return false;
4122
+ }
4123
+
4124
+ if (VALID_POLICIES.indexOf(config['variantLookupPolicy']) === -1) {
4125
+ logger$5.error('Invalid variantLookupPolicy:', config['variantLookupPolicy']);
4126
+ return false;
4127
+ }
4128
+
4129
+ if (config['persistenceTtlMs'] !== undefined &&
4130
+ config['persistenceTtlMs'] !== null &&
4131
+ config['persistenceTtlMs'] <= 0) {
4132
+ logger$5.error('If provided, persistenceTtlMs must be a positive number. Provided value:', config['persistenceTtlMs']);
4133
+ return false;
4134
+ }
4135
+
4136
+ return true;
4137
+ };
4138
+
4139
+ FeatureFlagPersistence.prototype.loadFlagsFromStorage = function(context) {
4140
+ var clearAndReturnNull = _.bind(function() {
4141
+ return this.clear().then(function() { return null; }).catch(function() { return null; });
4142
+ }, this);
4143
+
4144
+ if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
4145
+ return clearAndReturnNull();
4146
+ }
4147
+
4148
+ var ttlMs = this.getTtlMs();
4149
+
4150
+ return this.idb.init().then(_.bind(function() {
4151
+ return this.idb.getItem(this.persistedVariantsKey);
4152
+ }, this)).then(_.bind(function(data) {
4153
+ if (!data) {
4154
+ logger$5.log('No persisted variants found in IndexedDB');
4155
+ return null;
4156
+ }
4157
+
4158
+ if (ttlMs && Date.now() - data['persistedAt'] >= ttlMs) {
4159
+ logger$5.log('Persisted variants are expiring');
4160
+ return null;
4161
+ }
4162
+
4163
+ if (!context || data['distinctId'] !== context['distinct_id']) {
4164
+ logger$5.log('Persisted variants found, but for a different distinct_id so clearing.');
4165
+ return clearAndReturnNull();
4166
+ }
4167
+
4168
+ var persistedFlags = new Map();
4169
+ _.each(data['flagVariants'], function(variantData, key) {
4170
+ persistedFlags.set(key, {
4171
+ 'key': variantData['variant_key'],
4172
+ 'value': variantData['variant_value'],
4173
+ 'experiment_id': variantData['experiment_id'],
4174
+ 'is_experiment_active': variantData['is_experiment_active'],
4175
+ 'is_qa_tester': variantData['is_qa_tester'],
4176
+ 'variant_source': 'persistence',
4177
+ 'persisted_at_in_ms': data['persistedAt'],
4178
+ 'ttl_in_ms': ttlMs
4179
+ });
4180
+ });
4181
+
4182
+ logger$5.log('Loaded', persistedFlags.size, 'variants from IndexedDB for distinct_id', data['distinctId']);
4183
+
4184
+ return {
4185
+ flags: persistedFlags,
4186
+ pendingFirstTimeEvents: data['pendingFirstTimeEvents'] || {},
4187
+ persistedAtMs: data['persistedAt'],
4188
+ ttlMs: ttlMs
4189
+ };
4190
+ }, this)).catch(_.bind(function(error) {
4191
+ logger$5.error('Failed to load persisted variants from IndexedDB, so clearing', error);
4192
+ return clearAndReturnNull();
4193
+ }, this));
4194
+ };
4195
+
4196
+ FeatureFlagPersistence.prototype.save = function(context, flagsMap, pendingFirstTimeEvents) {
4197
+ if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
4198
+ return Promise.resolve();
4199
+ }
4200
+
4201
+ var flagVariants = {};
4202
+ flagsMap.forEach(function(variant, key) {
4203
+ flagVariants[key] = {
4204
+ 'variant_key': variant['key'],
4205
+ 'variant_value': variant['value'],
4206
+ 'experiment_id': variant['experiment_id'],
4207
+ 'is_experiment_active': variant['is_experiment_active'],
4208
+ 'is_qa_tester': variant['is_qa_tester']
4209
+ };
4210
+ });
4211
+
4212
+ var data = {
4213
+ 'persistedAt': Date.now(),
4214
+ 'distinctId': context && context['distinct_id'],
4215
+ 'context': context,
4216
+ 'flagVariants': flagVariants,
4217
+ 'pendingFirstTimeEvents': pendingFirstTimeEvents || {}
4218
+ };
4219
+
4220
+ return this.idb.init().then(_.bind(function() {
4221
+ return this.idb.setItem(this.persistedVariantsKey, data);
4222
+ }, this)).then(function() {
4223
+ logger$5.log('Saved', flagsMap.size, 'variants to IndexedDB for distinct_id', data['distinctId']);
4224
+ }).catch(function(error) {
4225
+ logger$5.error('Failed to persist variants to IndexedDB:', error);
4226
+ });
4227
+ };
4228
+
4229
+ FeatureFlagPersistence.prototype.clear = function() {
4230
+ if (this.isGloballyDisabled()) {
4231
+ return Promise.resolve();
4232
+ }
4233
+ return this.idb.init().then(_.bind(function() {
4234
+ return this.idb.removeItem(this.persistedVariantsKey);
4235
+ }, this)).then(function() {
4236
+ logger$5.log('Cleared persisted variants from IndexedDB');
4237
+ }).catch(function(error) {
4238
+ logger$5.error('Failed to clear persisted variants from IndexedDB:', error);
4239
+ });
4240
+ };
4241
+
3927
4242
  var logger$4 = console_with_prefix('flags');
3928
4243
  var FLAGS_CONFIG_KEY = 'flags';
3929
4244
 
3930
4245
  var CONFIG_CONTEXT = 'context';
4246
+ var CONFIG_PERSISTENCE = 'persistence';
3931
4247
  var CONFIG_DEFAULTS = {};
3932
4248
  CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
3933
4249
 
@@ -3950,6 +4266,13 @@
3950
4266
  return eventKey.split(':')[0];
3951
4267
  };
3952
4268
 
4269
+ var withFallbackSource = function(fallback) {
4270
+ if (_.isObject(fallback)) {
4271
+ return _.extend({}, fallback, {'variant_source': 'fallback'});
4272
+ }
4273
+ return {'value': fallback, 'variant_source': 'fallback'};
4274
+ };
4275
+
3953
4276
  /**
3954
4277
  * FeatureFlagManager: support for Mixpanel's feature flagging product
3955
4278
  * @constructor
@@ -3972,13 +4295,63 @@
3972
4295
  }
3973
4296
 
3974
4297
  this.flags = null;
3975
- this.fetchFlags().catch(function() {
3976
- logger$4.error('Error fetching flags during init');
3977
- });
3978
-
3979
4298
  this.trackedFeatures = new Set();
3980
4299
  this.pendingFirstTimeEvents = {};
3981
4300
  this.activatedFirstTimeEvents = {};
4301
+ this._loadedPersistedAtMs = null;
4302
+ this._loadedTtlMs = null;
4303
+
4304
+ this.persistence = new FeatureFlagPersistence(
4305
+ this.getConfig(CONFIG_PERSISTENCE),
4306
+ this.getMpConfig('token'),
4307
+ _.bind(function() { return this.getMpConfig('disable_persistence'); }, this)
4308
+ );
4309
+
4310
+ this.persistenceLoadedPromise = this.persistence.loadFlagsFromStorage(this._buildContext())
4311
+ .then(_.bind(function(loaded) {
4312
+ if (loaded) {
4313
+ this.flags = loaded.flags;
4314
+ this.pendingFirstTimeEvents = loaded.pendingFirstTimeEvents;
4315
+ this._loadedPersistedAtMs = loaded.persistedAtMs;
4316
+ this._loadedTtlMs = loaded.ttlMs;
4317
+ }
4318
+ }, this));
4319
+
4320
+ return this.persistenceLoadedPromise
4321
+ .then(_.bind(function() {
4322
+ return this.fetchFlags();
4323
+ }, this))
4324
+ .catch(function() {
4325
+ logger$4.error('Error initializing feature flags');
4326
+ });
4327
+ };
4328
+
4329
+ FeatureFlagManager.prototype._buildContext = function() {
4330
+ return _.extend(
4331
+ {'distinct_id': this.getMpProperty('distinct_id'), 'device_id': this.getMpProperty('$device_id')},
4332
+ this.getConfig(CONFIG_CONTEXT)
4333
+ );
4334
+ };
4335
+
4336
+ FeatureFlagManager.prototype.reset = function() {
4337
+ if (!this.persistence) {
4338
+ return Promise.resolve();
4339
+ }
4340
+
4341
+ this.flags = null;
4342
+ this.pendingFirstTimeEvents = {};
4343
+ this.activatedFirstTimeEvents = {};
4344
+ this.trackedFeatures = new Set();
4345
+ this.fetchPromise = null;
4346
+ this._fetchInProgressStartTime = null;
4347
+ this._loadedPersistedAtMs = null;
4348
+ this._loadedTtlMs = null;
4349
+
4350
+ return this.persistence.clear().then(_.bind(function() {
4351
+ return this.fetchFlags();
4352
+ }, this)).catch(function() {
4353
+ logger$4.error('Error during flags reset');
4354
+ });
3982
4355
  };
3983
4356
 
3984
4357
  FeatureFlagManager.prototype.getFullConfig = function() {
@@ -4035,12 +4408,11 @@
4035
4408
  return Promise.resolve();
4036
4409
  }
4037
4410
 
4038
- var distinctId = this.getMpProperty('distinct_id');
4039
- var deviceId = this.getMpProperty('$device_id');
4411
+ var context = this._buildContext();
4412
+ var distinctId = context['distinct_id'];
4040
4413
  var traceparent = generateTraceparent();
4041
4414
  logger$4.log('Fetching flags for distinct ID: ' + distinctId);
4042
4415
 
4043
- var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
4044
4416
  var searchParams = new URLSearchParams();
4045
4417
  searchParams.set('context', JSON.stringify(context));
4046
4418
  searchParams.set('token', this.getMpConfig('token'));
@@ -4090,7 +4462,8 @@
4090
4462
  'value': data['variant_value'],
4091
4463
  'experiment_id': data['experiment_id'],
4092
4464
  'is_experiment_active': data['is_experiment_active'],
4093
- 'is_qa_tester': data['is_qa_tester']
4465
+ 'is_qa_tester': data['is_qa_tester'],
4466
+ 'variant_source': 'network'
4094
4467
  });
4095
4468
  }
4096
4469
  }, this);
@@ -4132,10 +4505,15 @@
4132
4505
  }
4133
4506
 
4134
4507
  this.flags = flags;
4508
+ this.trackedFeatures = new Set();
4135
4509
  this.pendingFirstTimeEvents = pendingFirstTimeEvents;
4510
+ this._loadedPersistedAtMs = null;
4511
+ this._loadedTtlMs = null;
4136
4512
  this._traceparent = traceparent;
4137
4513
 
4138
4514
  this._loadTargetingIfNeeded();
4515
+
4516
+ this.persistence.save(context, this.flags, this.pendingFirstTimeEvents);
4139
4517
  }.bind(this)).catch(function(error) {
4140
4518
  if (this._fetchInProgressStartTime) {
4141
4519
  this.markFetchComplete();
@@ -4295,6 +4673,7 @@
4295
4673
  };
4296
4674
 
4297
4675
  this.flags.set(flagKey, newVariant);
4676
+ this.trackedFeatures.delete(flagKey);
4298
4677
  this.activatedFirstTimeEvents[eventKey] = true;
4299
4678
 
4300
4679
  this.recordFirstTimeEvent(
@@ -4344,35 +4723,106 @@
4344
4723
  };
4345
4724
 
4346
4725
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
4347
- if (!this.fetchPromise) {
4726
+ if (!this.persistenceLoadedPromise) {
4348
4727
  return new Promise(function(resolve) {
4349
4728
  logger$4.critical('Feature Flags not initialized');
4350
- resolve(fallback);
4729
+ resolve(withFallbackSource(fallback));
4351
4730
  });
4352
4731
  }
4353
4732
 
4354
- return this.fetchPromise.then(function() {
4355
- return this.getVariantSync(featureName, fallback);
4356
- }.bind(this)).catch(function(error) {
4357
- logger$4.error(error);
4358
- return fallback;
4359
- });
4733
+ var policy = this.persistence.getPolicy();
4734
+
4735
+ return this.persistenceLoadedPromise.then(_.bind(function() {
4736
+ // 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.
4737
+ if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
4738
+ if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
4739
+ return this.getVariantSync(featureName, fallback);
4740
+ }
4741
+ if (!this.fetchPromise) {
4742
+ return withFallbackSource(fallback);
4743
+ }
4744
+ return this.fetchPromise.then(_.bind(function() {
4745
+ return this.getVariantSync(featureName, fallback);
4746
+ }, this)).catch(function(error) {
4747
+ logger$4.error(error);
4748
+ return withFallbackSource(fallback);
4749
+ });
4750
+ }
4751
+
4752
+ var serve = _.bind(function() { return this.getVariantSync(featureName, fallback); }, this);
4753
+ if (!this.fetchPromise) {
4754
+ return withFallbackSource(fallback);
4755
+ }
4756
+ return this.fetchPromise.then(serve).catch(serve);
4757
+ }, this));
4758
+ };
4759
+
4760
+ FeatureFlagManager.prototype._loadedPersistenceIsStale = function() {
4761
+ if (!this._loadedPersistedAtMs || !this._loadedTtlMs) {
4762
+ return false;
4763
+ }
4764
+ return Date.now() - this._loadedPersistedAtMs >= this._loadedTtlMs;
4360
4765
  };
4361
4766
 
4362
4767
  FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
4768
+ if (this._loadedPersistenceIsStale()) {
4769
+ logger$4.log('Loaded persisted variants are past TTL so returning fallback for "' + featureName + '"');
4770
+ return withFallbackSource(fallback);
4771
+ }
4363
4772
  if (!this.areFlagsReady()) {
4364
4773
  logger$4.log('Flags not loaded yet');
4365
- return fallback;
4774
+ return withFallbackSource(fallback);
4366
4775
  }
4367
4776
  var feature = this.flags.get(featureName);
4368
4777
  if (!feature) {
4369
4778
  logger$4.log('No flag found: "' + featureName + '"');
4370
- return fallback;
4779
+ return withFallbackSource(fallback);
4371
4780
  }
4372
4781
  this.trackFeatureCheck(featureName, feature);
4373
4782
  return feature;
4374
4783
  };
4375
4784
 
4785
+ FeatureFlagManager.prototype.getAllVariants = function() {
4786
+ if (!this.persistenceLoadedPromise) {
4787
+ logger$4.critical('Feature Flags not initialized');
4788
+ return Promise.resolve(new Map());
4789
+ }
4790
+
4791
+ var policy = this.persistence.getPolicy();
4792
+
4793
+ return this.persistenceLoadedPromise.then(_.bind(function() {
4794
+ // 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.
4795
+ if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
4796
+ if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
4797
+ return this.getAllVariantsSync();
4798
+ }
4799
+ if (!this.fetchPromise) {
4800
+ return new Map();
4801
+ }
4802
+ return this.fetchPromise.then(_.bind(function() {
4803
+ return this.getAllVariantsSync();
4804
+ }, this)).catch(function(error) {
4805
+ logger$4.error(error);
4806
+ return new Map();
4807
+ });
4808
+ }
4809
+
4810
+ var serve = _.bind(this.getAllVariantsSync, this);
4811
+ if (!this.fetchPromise) {
4812
+ return new Map();
4813
+ }
4814
+ return this.fetchPromise.then(serve).catch(serve);
4815
+ }, this));
4816
+ };
4817
+
4818
+ FeatureFlagManager.prototype.getAllVariantsSync = function() {
4819
+ if (this._loadedPersistenceIsStale()) {
4820
+ logger$4.log('Loaded persisted variants are past TTL so returning empty Map');
4821
+ return new Map();
4822
+ }
4823
+ return this.flags || new Map();
4824
+ };
4825
+
4376
4826
  FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
4377
4827
  return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
4378
4828
  return feature['value'];
@@ -4411,6 +4861,10 @@
4411
4861
  return val;
4412
4862
  };
4413
4863
 
4864
+ function isPresent(v) {
4865
+ return v !== undefined && v !== null;
4866
+ }
4867
+
4414
4868
  FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
4415
4869
  if (this.trackedFeatures.has(featureName)) {
4416
4870
  return;
@@ -4421,21 +4875,30 @@
4421
4875
  'Experiment name': featureName,
4422
4876
  'Variant name': feature['key'],
4423
4877
  '$experiment_type': 'feature_flag',
4424
- 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
4425
- 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
4878
+ 'Variant fetch start time': isPresent(this._fetchStartTime) ? new Date(this._fetchStartTime).toISOString() : null,
4879
+ 'Variant fetch complete time': isPresent(this._fetchCompleteTime) ? new Date(this._fetchCompleteTime).toISOString() : null,
4426
4880
  'Variant fetch latency (ms)': this._fetchLatency,
4427
4881
  'Variant fetch traceparent': this._traceparent,
4428
4882
  };
4429
4883
 
4430
- if (feature['experiment_id'] !== 'undefined') {
4884
+ if (isPresent(feature['experiment_id'])) {
4431
4885
  trackingProperties['$experiment_id'] = feature['experiment_id'];
4432
4886
  }
4433
- if (feature['is_experiment_active'] !== 'undefined') {
4887
+ if (isPresent(feature['is_experiment_active'])) {
4434
4888
  trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
4435
4889
  }
4436
- if (feature['is_qa_tester'] !== 'undefined') {
4890
+ if (isPresent(feature['is_qa_tester'])) {
4437
4891
  trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
4438
4892
  }
4893
+ if (isPresent(feature['variant_source'])) {
4894
+ trackingProperties['$variant_source'] = feature['variant_source'];
4895
+ }
4896
+ if (isPresent(feature['persisted_at_in_ms'])) {
4897
+ trackingProperties['$persisted_at_in_ms'] = feature['persisted_at_in_ms'];
4898
+ }
4899
+ if (isPresent(feature['ttl_in_ms'])) {
4900
+ trackingProperties['$ttl_in_ms'] = feature['ttl_in_ms'];
4901
+ }
4439
4902
 
4440
4903
  this.track('$experiment_started', trackingProperties);
4441
4904
  };
@@ -4459,6 +4922,8 @@
4459
4922
  FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
4460
4923
  FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
4461
4924
  FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
4925
+ FeatureFlagManager.prototype['get_all_variants'] = FeatureFlagManager.prototype.getAllVariants;
4926
+ FeatureFlagManager.prototype['get_all_variants_sync'] = FeatureFlagManager.prototype.getAllVariantsSync;
4462
4927
  FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
4463
4928
  FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
4464
4929
  FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
@@ -4473,131 +4938,14 @@
4473
4938
  // Exports intended only for testing
4474
4939
  FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
4475
4940
 
4476
- var MIXPANEL_DB_NAME = 'mixpanelBrowserDb';
4477
-
4941
+ var MIXPANEL_BROWSER_DB_NAME = 'mixpanelBrowserDb';
4478
4942
  var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
4479
4943
  var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
4480
4944
 
4481
- // note: increment the version number when adding new object stores
4482
- var DB_VERSION = 1;
4483
- var OBJECT_STORES = [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME];
4484
-
4485
- /**
4486
- * @type {import('./wrapper').StorageWrapper}
4487
- */
4488
- var IDBStorageWrapper = function (storeName) {
4489
- /**
4490
- * @type {Promise<IDBDatabase>|null}
4491
- */
4492
- this.dbPromise = null;
4493
- this.storeName = storeName;
4494
- };
4495
-
4496
- IDBStorageWrapper.prototype._openDb = function () {
4497
- return new PromisePolyfill(function (resolve, reject) {
4498
- var openRequest = win.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
4499
- openRequest['onerror'] = function () {
4500
- reject(openRequest.error);
4501
- };
4502
-
4503
- openRequest['onsuccess'] = function () {
4504
- resolve(openRequest.result);
4505
- };
4506
-
4507
- openRequest['onupgradeneeded'] = function (ev) {
4508
- var db = ev.target.result;
4509
-
4510
- OBJECT_STORES.forEach(function (storeName) {
4511
- db.createObjectStore(storeName);
4512
- });
4513
- };
4514
- });
4515
- };
4516
-
4517
- IDBStorageWrapper.prototype.init = function () {
4518
- if (!win.indexedDB) {
4519
- return PromisePolyfill.reject('indexedDB is not supported in this browser');
4520
- }
4521
-
4522
- if (!this.dbPromise) {
4523
- this.dbPromise = this._openDb();
4524
- }
4525
-
4526
- return this.dbPromise
4527
- .then(function (dbOrError) {
4528
- if (dbOrError instanceof win['IDBDatabase']) {
4529
- return PromisePolyfill.resolve();
4530
- } else {
4531
- return PromisePolyfill.reject(dbOrError);
4532
- }
4533
- });
4534
- };
4535
-
4536
- IDBStorageWrapper.prototype.isInitialized = function () {
4537
- return !!this.dbPromise;
4538
- };
4539
-
4540
- /**
4541
- * @param {IDBTransactionMode} mode
4542
- * @param {function(IDBObjectStore): void} storeCb
4543
- */
4544
- IDBStorageWrapper.prototype.makeTransaction = function (mode, storeCb) {
4545
- var storeName = this.storeName;
4546
- var doTransaction = function (db) {
4547
- return new PromisePolyfill(function (resolve, reject) {
4548
- var transaction = db.transaction(storeName, mode);
4549
- transaction.oncomplete = function () {
4550
- resolve(transaction);
4551
- };
4552
- transaction.onabort = transaction.onerror = function () {
4553
- reject(transaction.error);
4554
- };
4555
-
4556
- storeCb(transaction.objectStore(storeName));
4557
- });
4558
- };
4559
-
4560
- return this.dbPromise
4561
- .then(doTransaction)
4562
- .catch(function (err) {
4563
- if (err && err['name'] === 'InvalidStateError') {
4564
- // try reopening the DB if the connection is closed
4565
- this.dbPromise = this._openDb();
4566
- return this.dbPromise.then(doTransaction);
4567
- } else {
4568
- return PromisePolyfill.reject(err);
4569
- }
4570
- }.bind(this));
4571
- };
4572
-
4573
- IDBStorageWrapper.prototype.setItem = function (key, value) {
4574
- return this.makeTransaction('readwrite', function (objectStore) {
4575
- objectStore.put(value, key);
4576
- });
4577
- };
4578
-
4579
- IDBStorageWrapper.prototype.getItem = function (key) {
4580
- var req;
4581
- return this.makeTransaction('readonly', function (objectStore) {
4582
- req = objectStore.get(key);
4583
- }).then(function () {
4584
- return req.result;
4585
- });
4586
- };
4587
-
4588
- IDBStorageWrapper.prototype.removeItem = function (key) {
4589
- return this.makeTransaction('readwrite', function (objectStore) {
4590
- objectStore.delete(key);
4591
- });
4592
- };
4593
-
4594
- IDBStorageWrapper.prototype.getAll = function () {
4595
- var req;
4596
- return this.makeTransaction('readonly', function (objectStore) {
4597
- req = objectStore.getAll();
4598
- }).then(function () {
4599
- return req.result;
4600
- });
4945
+ // Keeping these two properties closeby, as adding additional stores to a DB in IndexedDB requires a version increment
4946
+ var RECORDER_VERSION_DATA = {
4947
+ version: 1,
4948
+ storeNames: [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME]
4601
4949
  };
4602
4950
 
4603
4951
  /**
@@ -4670,7 +5018,7 @@
4670
5018
  return PromisePolyfill.resolve(false);
4671
5019
  }
4672
5020
 
4673
- var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
5021
+ var recording_registry_idb = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_REGISTRY_STORE_NAME, RECORDER_VERSION_DATA);
4674
5022
  var tab_id = this.getTabId();
4675
5023
  return recording_registry_idb.init()
4676
5024
  .then(function () {
@@ -5126,7 +5474,7 @@
5126
5474
  options = options || {};
5127
5475
 
5128
5476
  this.storageKey = key;
5129
- this.storage = options.storage || win.localStorage;
5477
+ this.storage = options.storage || getLocalStorage();
5130
5478
  this.pollIntervalMS = options.pollIntervalMS || 100;
5131
5479
  this.timeoutMS = options.timeoutMS || 2000;
5132
5480
 
@@ -5254,10 +5602,13 @@
5254
5602
  * @type {import('./wrapper').StorageWrapper}
5255
5603
  */
5256
5604
  var LocalStorageWrapper = function (storageOverride) {
5257
- this.storage = storageOverride || win.localStorage;
5605
+ this.storage = storageOverride || getLocalStorage();
5258
5606
  };
5259
5607
 
5260
5608
  LocalStorageWrapper.prototype.init = function () {
5609
+ if (!this.storage) {
5610
+ return PromisePolyfill.reject(new Error('localStorage is not available'));
5611
+ }
5261
5612
  return PromisePolyfill.resolve();
5262
5613
  };
5263
5614
 
@@ -5324,7 +5675,7 @@
5324
5675
  if (this.usePersistence) {
5325
5676
  this.queueStorage = options.queueStorage || new LocalStorageWrapper();
5326
5677
  this.lock = new SharedLock(storageKey, {
5327
- storage: options.sharedLockStorage || win.localStorage,
5678
+ storage: options.sharedLockStorage,
5328
5679
  timeoutMS: options.sharedLockTimeoutMS,
5329
5680
  });
5330
5681
  }
@@ -7789,6 +8140,7 @@
7789
8140
  'disable_all_events': false,
7790
8141
  'identify_called': false
7791
8142
  };
8143
+ this._remote_settings_strict_disabled = false;
7792
8144
 
7793
8145
  // set up request queueing/batching
7794
8146
  this.request_batchers = {};
@@ -7863,9 +8215,6 @@
7863
8215
  this.flags.init();
7864
8216
  this['flags'] = this.flags;
7865
8217
 
7866
- this.autocapture = new Autocapture(this);
7867
- this.autocapture.init();
7868
-
7869
8218
  this._init_tab_id();
7870
8219
 
7871
8220
  // Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
@@ -7877,6 +8226,9 @@
7877
8226
  } else {
7878
8227
  this.__session_recording_init_promise = this._check_and_start_session_recording();
7879
8228
  }
8229
+
8230
+ this.autocapture = new Autocapture(this);
8231
+ this.autocapture.init();
7880
8232
  };
7881
8233
 
7882
8234
  /**
@@ -7923,9 +8275,19 @@
7923
8275
  return this.recorderManager.checkAndStartSessionRecording(force_start);
7924
8276
  });
7925
8277
 
7926
- MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
7927
- return this.recorderManager.startRecordingOnEvent(event_name, properties);
7928
- };
8278
+ MixpanelLib.prototype._start_recording_on_event = safewrap(function(event_name, properties) {
8279
+ // Wait for recording init to complete before evaluating event triggers.
8280
+ // This ensures recording_event_triggers config is fully loaded when remote settings are used.
8281
+ if (this.__session_recording_init_promise) {
8282
+ this.__session_recording_init_promise.then(_.bind(function() {
8283
+ // In strict mode, skip recording if remote settings failed
8284
+ if (this._remote_settings_strict_disabled) {
8285
+ return;
8286
+ }
8287
+ return this.recorderManager.startRecordingOnEvent(event_name, properties);
8288
+ }, this));
8289
+ }
8290
+ });
7929
8291
 
7930
8292
  MixpanelLib.prototype.start_session_recording = function () {
7931
8293
  return this._check_and_start_session_recording(true);
@@ -8224,6 +8586,7 @@
8224
8586
  var disableRecordingIfStrict = function() {
8225
8587
  if (mode === 'strict') {
8226
8588
  self.set_config({'record_sessions_percent': 0});
8589
+ self._remote_settings_strict_disabled = true;
8227
8590
  }
8228
8591
  };
8229
8592
 
@@ -8849,6 +9212,10 @@
8849
9212
  properties
8850
9213
  );
8851
9214
 
9215
+ if (this.is_recording_heatmap_data()) {
9216
+ event_properties['$captured_for_heatmap'] = true;
9217
+ }
9218
+
8852
9219
  return this.track(event_name, event_properties);
8853
9220
  });
8854
9221
 
@@ -9192,6 +9559,7 @@
9192
9559
  '$device_id': uuid
9193
9560
  }, '');
9194
9561
  this._check_and_start_session_recording();
9562
+ this.flags.reset();
9195
9563
  };
9196
9564
 
9197
9565
  /**