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.
- package/.claude/settings.local.json +6 -11
- package/.eslintrc.json +12 -0
- package/.github/workflows/openfeature-provider-tests.yml +31 -0
- package/CHANGELOG.md +8 -1
- package/build.sh +2 -2
- package/dist/async-modules/{mixpanel-recorder-BjSlYaNJ.min.js → mixpanel-recorder-D5HJyV2E.min.js} +2 -2
- package/dist/async-modules/mixpanel-recorder-D5HJyV2E.min.js.map +1 -0
- package/dist/async-modules/{mixpanel-recorder-zMBXIyeG.js → mixpanel-recorder-P6SEnnPV.js} +57 -33
- package/dist/async-modules/mixpanel-targeting-1L9FyetZ.min.js +2 -0
- package/dist/async-modules/mixpanel-targeting-1L9FyetZ.min.js.map +1 -0
- package/dist/async-modules/{mixpanel-targeting-UHf4eBfC.js → mixpanel-targeting-BBMVbgJF.js} +24 -13
- package/dist/mixpanel-core.cjs.d.ts +45 -1
- package/dist/mixpanel-core.cjs.js +565 -197
- package/dist/mixpanel-recorder.js +57 -33
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-targeting.js +24 -13
- package/dist/mixpanel-targeting.min.js +1 -1
- package/dist/mixpanel-targeting.min.js.map +1 -1
- package/dist/mixpanel-with-async-modules.cjs.d.ts +45 -1
- package/dist/mixpanel-with-async-modules.cjs.js +567 -199
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +45 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +567 -199
- package/dist/mixpanel-with-recorder.d.ts +45 -1
- package/dist/mixpanel-with-recorder.js +490 -122
- package/dist/mixpanel-with-recorder.min.d.ts +45 -1
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +45 -1
- package/dist/mixpanel.amd.js +490 -122
- package/dist/mixpanel.cjs.d.ts +45 -1
- package/dist/mixpanel.cjs.js +490 -122
- package/dist/mixpanel.globals.js +567 -199
- package/dist/mixpanel.min.js +199 -189
- package/dist/mixpanel.module.d.ts +45 -1
- package/dist/mixpanel.module.js +490 -122
- package/dist/mixpanel.umd.d.ts +45 -1
- package/dist/mixpanel.umd.js +490 -122
- package/package.json +1 -1
- package/packages/openfeature-web-provider/README.md +357 -0
- package/packages/openfeature-web-provider/package-lock.json +1636 -0
- package/packages/openfeature-web-provider/package.json +51 -0
- package/packages/openfeature-web-provider/rollup.config.browser.mjs +26 -0
- package/packages/openfeature-web-provider/src/MixpanelProvider.ts +302 -0
- package/packages/openfeature-web-provider/src/index.ts +1 -0
- package/packages/openfeature-web-provider/src/types.ts +72 -0
- package/packages/openfeature-web-provider/test/MixpanelProvider.spec.ts +484 -0
- package/packages/openfeature-web-provider/tsconfig.json +15 -0
- package/src/autocapture/index.js +7 -2
- package/src/config.js +1 -1
- package/src/flags/flags-persistence.js +176 -0
- package/src/flags/index.js +174 -23
- package/src/index.d.ts +45 -1
- package/src/mixpanel-core.js +24 -7
- package/src/recorder/idb-config.js +16 -0
- package/src/recorder/recording-registry.js +7 -2
- package/src/recorder/session-recording.js +9 -4
- package/src/recorder-manager.js +7 -2
- package/src/request-queue.js +1 -2
- package/src/shared-lock.js +2 -3
- package/src/storage/indexed-db.js +16 -15
- package/src/storage/local-storage.js +5 -3
- package/src/utils.js +25 -12
- package/tsconfig.base.json +9 -0
- package/dist/async-modules/mixpanel-recorder-BjSlYaNJ.min.js.map +0 -1
- package/dist/async-modules/mixpanel-targeting-BSHal4N9.min.js +0 -2
- package/dist/async-modules/mixpanel-targeting-BSHal4N9.min.js.map +0 -1
package/dist/mixpanel.globals.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
var Config = {
|
|
5
5
|
DEBUG: false,
|
|
6
|
-
LIB_VERSION: '2.
|
|
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-
|
|
15
|
-
var TARGETING_FILENAME = 'mixpanel-targeting-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1510
|
-
//
|
|
1511
|
-
var
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
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
|
-
|
|
1519
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
3697
|
+
logger$6.log('Initializing pageview tracking');
|
|
3686
3698
|
|
|
3687
3699
|
var previousTrackedUrl = '';
|
|
3688
3700
|
var tracked = false;
|
|
3689
|
-
if
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
4039
|
-
var
|
|
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.
|
|
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
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
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']
|
|
4884
|
+
if (isPresent(feature['experiment_id'])) {
|
|
4431
4885
|
trackingProperties['$experiment_id'] = feature['experiment_id'];
|
|
4432
4886
|
}
|
|
4433
|
-
if (feature['is_experiment_active']
|
|
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']
|
|
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
|
|
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
|
-
//
|
|
4482
|
-
var
|
|
4483
|
-
|
|
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 ||
|
|
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 ||
|
|
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
|
|
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
|
-
|
|
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
|
/**
|