mixpanel-browser 2.77.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 -9
- package/.eslintrc.json +12 -0
- package/.github/workflows/openfeature-provider-tests.yml +31 -0
- package/CHANGELOG.md +11 -0
- package/build.sh +2 -2
- package/dist/async-modules/{mixpanel-recorder-wIWnMDLA.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-DLKbUIEE.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-CmVvUyFM.js → mixpanel-targeting-BBMVbgJF.js} +24 -13
- package/dist/mixpanel-core.cjs.d.ts +46 -1
- package/dist/mixpanel-core.cjs.js +671 -272
- 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 +46 -1
- package/dist/mixpanel-with-async-modules.cjs.js +673 -274
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +46 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +673 -274
- package/dist/mixpanel-with-recorder.d.ts +46 -1
- package/dist/mixpanel-with-recorder.js +596 -197
- package/dist/mixpanel-with-recorder.min.d.ts +46 -1
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +46 -1
- package/dist/mixpanel.amd.js +596 -197
- package/dist/mixpanel.cjs.d.ts +46 -1
- package/dist/mixpanel.cjs.js +596 -197
- package/dist/mixpanel.globals.js +673 -274
- package/dist/mixpanel.min.js +200 -189
- package/dist/mixpanel.module.d.ts +46 -1
- package/dist/mixpanel.module.js +596 -197
- package/dist/mixpanel.umd.d.ts +46 -1
- package/dist/mixpanel.umd.js +596 -197
- 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/CLAUDE.md +24 -0
- package/src/flags/flags-persistence.js +176 -0
- package/src/flags/index.js +278 -98
- package/src/index.d.ts +46 -1
- package/src/mixpanel-core.js +27 -8
- 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/testServer.js +2 -0
- package/tsconfig.base.json +9 -0
- package/dist/async-modules/mixpanel-recorder-wIWnMDLA.min.js.map +0 -1
- package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js +0 -2
- package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js.map +0 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var Config = {
|
|
4
4
|
DEBUG: false,
|
|
5
|
-
LIB_VERSION: '2.
|
|
5
|
+
LIB_VERSION: '2.79.0'
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// Window global names for async modules
|
|
@@ -501,6 +501,7 @@ var log_func_with_prefix = function(func, prefix) {
|
|
|
501
501
|
var console_with_prefix = function(prefix) {
|
|
502
502
|
return {
|
|
503
503
|
log: log_func_with_prefix(console.log, prefix),
|
|
504
|
+
warn: log_func_with_prefix(console.warn, prefix),
|
|
504
505
|
error: log_func_with_prefix(console.error, prefix),
|
|
505
506
|
critical: log_func_with_prefix(console.critical, prefix)
|
|
506
507
|
};
|
|
@@ -1447,7 +1448,8 @@ var localStorageSupported = function(storage, forceCheck) {
|
|
|
1447
1448
|
if (_localStorageSupported !== null && !forceCheck) {
|
|
1448
1449
|
return _localStorageSupported;
|
|
1449
1450
|
}
|
|
1450
|
-
|
|
1451
|
+
|
|
1452
|
+
return _localStorageSupported = _testStorageSupported(storage);
|
|
1451
1453
|
};
|
|
1452
1454
|
|
|
1453
1455
|
var _sessionStorageSupported = null;
|
|
@@ -1455,7 +1457,8 @@ var sessionStorageSupported = function(storage, forceCheck) {
|
|
|
1455
1457
|
if (_sessionStorageSupported !== null && !forceCheck) {
|
|
1456
1458
|
return _sessionStorageSupported;
|
|
1457
1459
|
}
|
|
1458
|
-
|
|
1460
|
+
|
|
1461
|
+
return _sessionStorageSupported = _testStorageSupported(storage);
|
|
1459
1462
|
};
|
|
1460
1463
|
|
|
1461
1464
|
function _storageWrapper(storage, name, is_supported_fn) {
|
|
@@ -1505,17 +1508,26 @@ function _storageWrapper(storage, name, is_supported_fn) {
|
|
|
1505
1508
|
};
|
|
1506
1509
|
}
|
|
1507
1510
|
|
|
1508
|
-
// Safari
|
|
1509
|
-
//
|
|
1510
|
-
var
|
|
1511
|
-
try {
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
}
|
|
1511
|
+
// Safari and other browsers may error out accessing localStorage/sessionStorage
|
|
1512
|
+
// when cookies are disabled, so wrap access in a try-catch.
|
|
1513
|
+
var getLocalStorage = function() {
|
|
1514
|
+
try {
|
|
1515
|
+
return win.localStorage; // eslint-disable-line no-restricted-properties
|
|
1516
|
+
} catch (_err) {
|
|
1517
|
+
return null;
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1516
1520
|
|
|
1517
|
-
|
|
1518
|
-
|
|
1521
|
+
var getSessionStorage = function() {
|
|
1522
|
+
try {
|
|
1523
|
+
return win.sessionStorage; // eslint-disable-line no-restricted-properties
|
|
1524
|
+
} catch (_err) {
|
|
1525
|
+
return null;
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
|
|
1529
|
+
_.localStorage = _storageWrapper(getLocalStorage(), 'localStorage', localStorageSupported);
|
|
1530
|
+
_.sessionStorage = _storageWrapper(getSessionStorage(), 'sessionStorage', sessionStorageSupported);
|
|
1519
1531
|
|
|
1520
1532
|
_.register_event = (function() {
|
|
1521
1533
|
// written by Dean Edwards, 2005
|
|
@@ -2267,7 +2279,7 @@ var EVENT_HANDLER_ATTRIBUTES = [
|
|
|
2267
2279
|
|
|
2268
2280
|
var MAX_DEPTH = 5;
|
|
2269
2281
|
|
|
2270
|
-
var logger$
|
|
2282
|
+
var logger$6 = console_with_prefix('autocapture');
|
|
2271
2283
|
|
|
2272
2284
|
|
|
2273
2285
|
function getClasses(el) {
|
|
@@ -2531,7 +2543,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
|
|
|
2531
2543
|
return false;
|
|
2532
2544
|
}
|
|
2533
2545
|
} catch (err) {
|
|
2534
|
-
logger$
|
|
2546
|
+
logger$6.critical('Error while checking element in allowElementCallback', err);
|
|
2535
2547
|
return false;
|
|
2536
2548
|
}
|
|
2537
2549
|
}
|
|
@@ -2548,7 +2560,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
|
|
|
2548
2560
|
return true;
|
|
2549
2561
|
}
|
|
2550
2562
|
} catch (err) {
|
|
2551
|
-
logger$
|
|
2563
|
+
logger$6.critical('Error while checking selector: ' + sel, err);
|
|
2552
2564
|
}
|
|
2553
2565
|
}
|
|
2554
2566
|
return false;
|
|
@@ -2563,7 +2575,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
|
|
|
2563
2575
|
return true;
|
|
2564
2576
|
}
|
|
2565
2577
|
} catch (err) {
|
|
2566
|
-
logger$
|
|
2578
|
+
logger$6.critical('Error while checking element in blockElementCallback', err);
|
|
2567
2579
|
return true;
|
|
2568
2580
|
}
|
|
2569
2581
|
}
|
|
@@ -2577,7 +2589,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
|
|
|
2577
2589
|
return true;
|
|
2578
2590
|
}
|
|
2579
2591
|
} catch (err) {
|
|
2580
|
-
logger$
|
|
2592
|
+
logger$6.critical('Error while checking selector: ' + sel, err);
|
|
2581
2593
|
}
|
|
2582
2594
|
}
|
|
2583
2595
|
}
|
|
@@ -3041,7 +3053,7 @@ ShadowDOMObserver.prototype.observeShadowRoot = function(shadowRoot) {
|
|
|
3041
3053
|
observer.observe(shadowRoot, this.observerConfig);
|
|
3042
3054
|
this.shadowObservers.push(observer);
|
|
3043
3055
|
} catch (e) {
|
|
3044
|
-
logger$
|
|
3056
|
+
logger$6.critical('Error while observing shadow root', e);
|
|
3045
3057
|
}
|
|
3046
3058
|
};
|
|
3047
3059
|
|
|
@@ -3052,7 +3064,7 @@ ShadowDOMObserver.prototype.start = function() {
|
|
|
3052
3064
|
}
|
|
3053
3065
|
|
|
3054
3066
|
if (!weakSetSupported()) {
|
|
3055
|
-
logger$
|
|
3067
|
+
logger$6.critical('Shadow DOM observation unavailable: WeakSet not supported');
|
|
3056
3068
|
return;
|
|
3057
3069
|
}
|
|
3058
3070
|
|
|
@@ -3068,7 +3080,7 @@ ShadowDOMObserver.prototype.stop = function() {
|
|
|
3068
3080
|
try {
|
|
3069
3081
|
this.shadowObservers[i].disconnect();
|
|
3070
3082
|
} catch (e) {
|
|
3071
|
-
logger$
|
|
3083
|
+
logger$6.critical('Error while disconnecting shadow DOM observer', e);
|
|
3072
3084
|
}
|
|
3073
3085
|
}
|
|
3074
3086
|
this.shadowObservers = [];
|
|
@@ -3256,7 +3268,7 @@ DeadClickTracker.prototype.startTracking = function() {
|
|
|
3256
3268
|
|
|
3257
3269
|
this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
|
|
3258
3270
|
} catch (e) {
|
|
3259
|
-
logger$
|
|
3271
|
+
logger$6.critical('Error while setting up mutation observer', e);
|
|
3260
3272
|
}
|
|
3261
3273
|
}
|
|
3262
3274
|
|
|
@@ -3271,7 +3283,7 @@ DeadClickTracker.prototype.startTracking = function() {
|
|
|
3271
3283
|
);
|
|
3272
3284
|
this.shadowDOMObserver.start();
|
|
3273
3285
|
} catch (e) {
|
|
3274
|
-
logger$
|
|
3286
|
+
logger$6.critical('Error while setting up shadow DOM observer', e);
|
|
3275
3287
|
this.shadowDOMObserver = null;
|
|
3276
3288
|
}
|
|
3277
3289
|
}
|
|
@@ -3298,7 +3310,7 @@ DeadClickTracker.prototype.stopTracking = function() {
|
|
|
3298
3310
|
try {
|
|
3299
3311
|
listener.target.removeEventListener(listener.event, listener.handler, listener.options);
|
|
3300
3312
|
} catch (e) {
|
|
3301
|
-
logger$
|
|
3313
|
+
logger$6.critical('Error while removing event listener', e);
|
|
3302
3314
|
}
|
|
3303
3315
|
}
|
|
3304
3316
|
this.eventListeners = [];
|
|
@@ -3307,7 +3319,7 @@ DeadClickTracker.prototype.stopTracking = function() {
|
|
|
3307
3319
|
try {
|
|
3308
3320
|
this.mutationObserver.disconnect();
|
|
3309
3321
|
} catch (e) {
|
|
3310
|
-
logger$
|
|
3322
|
+
logger$6.critical('Error while disconnecting mutation observer', e);
|
|
3311
3323
|
}
|
|
3312
3324
|
this.mutationObserver = null;
|
|
3313
3325
|
}
|
|
@@ -3316,7 +3328,7 @@ DeadClickTracker.prototype.stopTracking = function() {
|
|
|
3316
3328
|
try {
|
|
3317
3329
|
this.shadowDOMObserver.stop();
|
|
3318
3330
|
} catch (e) {
|
|
3319
|
-
logger$
|
|
3331
|
+
logger$6.critical('Error while stopping shadow DOM observer', e);
|
|
3320
3332
|
}
|
|
3321
3333
|
this.shadowDOMObserver = null;
|
|
3322
3334
|
}
|
|
@@ -3394,7 +3406,7 @@ var Autocapture = function(mp) {
|
|
|
3394
3406
|
|
|
3395
3407
|
Autocapture.prototype.init = function() {
|
|
3396
3408
|
if (!minDOMApisSupported()) {
|
|
3397
|
-
logger$
|
|
3409
|
+
logger$6.critical('Autocapture unavailable: missing required DOM APIs');
|
|
3398
3410
|
return;
|
|
3399
3411
|
}
|
|
3400
3412
|
this.initPageListeners();
|
|
@@ -3434,7 +3446,7 @@ Autocapture.prototype.currentUrlBlocked = function() {
|
|
|
3434
3446
|
try {
|
|
3435
3447
|
return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
|
|
3436
3448
|
} catch (err) {
|
|
3437
|
-
logger$
|
|
3449
|
+
logger$6.critical('Error while checking block URL regexes: ', err);
|
|
3438
3450
|
return true;
|
|
3439
3451
|
}
|
|
3440
3452
|
}
|
|
@@ -3447,7 +3459,7 @@ Autocapture.prototype.currentUrlBlocked = function() {
|
|
|
3447
3459
|
try {
|
|
3448
3460
|
return urlMatchesRegexList(currentUrl, blockUrlRegexes);
|
|
3449
3461
|
} catch (err) {
|
|
3450
|
-
logger$
|
|
3462
|
+
logger$6.critical('Error while checking block URL regexes: ', err);
|
|
3451
3463
|
return true;
|
|
3452
3464
|
}
|
|
3453
3465
|
};
|
|
@@ -3585,7 +3597,7 @@ Autocapture.prototype._initScrollDepthTracking = function() {
|
|
|
3585
3597
|
return;
|
|
3586
3598
|
}
|
|
3587
3599
|
|
|
3588
|
-
logger$
|
|
3600
|
+
logger$6.log('Initializing scroll depth tracking');
|
|
3589
3601
|
|
|
3590
3602
|
this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
|
|
3591
3603
|
|
|
@@ -3611,7 +3623,7 @@ Autocapture.prototype.initClickTracking = function() {
|
|
|
3611
3623
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
|
|
3612
3624
|
return;
|
|
3613
3625
|
}
|
|
3614
|
-
logger$
|
|
3626
|
+
logger$6.log('Initializing click tracking');
|
|
3615
3627
|
|
|
3616
3628
|
this.listenerClick = function(ev) {
|
|
3617
3629
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
|
|
@@ -3630,7 +3642,7 @@ Autocapture.prototype.initDeadClickTracking = function() {
|
|
|
3630
3642
|
return;
|
|
3631
3643
|
}
|
|
3632
3644
|
|
|
3633
|
-
logger$
|
|
3645
|
+
logger$6.log('Initializing dead click tracking');
|
|
3634
3646
|
if (!this._deadClickTracker) {
|
|
3635
3647
|
this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
|
|
3636
3648
|
this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
|
|
@@ -3664,7 +3676,7 @@ Autocapture.prototype.initInputTracking = function() {
|
|
|
3664
3676
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
3665
3677
|
return;
|
|
3666
3678
|
}
|
|
3667
|
-
logger$
|
|
3679
|
+
logger$6.log('Initializing input tracking');
|
|
3668
3680
|
|
|
3669
3681
|
this.listenerChange = function(ev) {
|
|
3670
3682
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
@@ -3678,14 +3690,15 @@ Autocapture.prototype.initInputTracking = function() {
|
|
|
3678
3690
|
Autocapture.prototype.initPageviewTracking = function() {
|
|
3679
3691
|
win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
|
|
3680
3692
|
|
|
3681
|
-
if (!this.pageviewTrackingConfig()) {
|
|
3693
|
+
if (!this.pageviewTrackingConfig() && !this.mp.get_config('record_heatmap_data')) {
|
|
3682
3694
|
return;
|
|
3683
3695
|
}
|
|
3684
|
-
logger$
|
|
3696
|
+
logger$6.log('Initializing pageview tracking');
|
|
3685
3697
|
|
|
3686
3698
|
var previousTrackedUrl = '';
|
|
3687
3699
|
var tracked = false;
|
|
3688
|
-
if
|
|
3700
|
+
// Track initial pageview if pageview tracking enabled OR heatmap recording is active
|
|
3701
|
+
if ((this.pageviewTrackingConfig() || this.mp.is_recording_heatmap_data()) && !this.currentUrlBlocked()) {
|
|
3689
3702
|
tracked = this.mp.track_pageview(DEFAULT_PROPS);
|
|
3690
3703
|
}
|
|
3691
3704
|
if (tracked) {
|
|
@@ -3701,6 +3714,10 @@ Autocapture.prototype.initPageviewTracking = function() {
|
|
|
3701
3714
|
var shouldTrack = false;
|
|
3702
3715
|
var didPathChange = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
|
|
3703
3716
|
var trackPageviewOption = this.pageviewTrackingConfig();
|
|
3717
|
+
if (!trackPageviewOption && this.mp.is_recording_heatmap_data()) {
|
|
3718
|
+
trackPageviewOption = PAGEVIEW_OPTION_FULL_URL;
|
|
3719
|
+
}
|
|
3720
|
+
|
|
3704
3721
|
if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
|
|
3705
3722
|
shouldTrack = currentUrl !== previousTrackedUrl;
|
|
3706
3723
|
} else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
|
|
@@ -3716,7 +3733,7 @@ Autocapture.prototype.initPageviewTracking = function() {
|
|
|
3716
3733
|
}
|
|
3717
3734
|
if (didPathChange) {
|
|
3718
3735
|
this.lastScrollCheckpoint = 0;
|
|
3719
|
-
logger$
|
|
3736
|
+
logger$6.log('Path change: re-initializing scroll depth checkpoints');
|
|
3720
3737
|
}
|
|
3721
3738
|
}
|
|
3722
3739
|
}.bind(this));
|
|
@@ -3731,7 +3748,7 @@ Autocapture.prototype.initRageClickTracking = function() {
|
|
|
3731
3748
|
return;
|
|
3732
3749
|
}
|
|
3733
3750
|
|
|
3734
|
-
logger$
|
|
3751
|
+
logger$6.log('Initializing rage click tracking');
|
|
3735
3752
|
if (!this._rageClickTracker) {
|
|
3736
3753
|
this._rageClickTracker = new RageClickTracker();
|
|
3737
3754
|
}
|
|
@@ -3761,7 +3778,7 @@ Autocapture.prototype.initScrollTracking = function() {
|
|
|
3761
3778
|
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
3762
3779
|
return;
|
|
3763
3780
|
}
|
|
3764
|
-
logger$
|
|
3781
|
+
logger$6.log('Initializing scroll tracking');
|
|
3765
3782
|
this.lastScrollCheckpoint = 0;
|
|
3766
3783
|
|
|
3767
3784
|
var scrollTrackFunction = function() {
|
|
@@ -3798,7 +3815,7 @@ Autocapture.prototype.initScrollTracking = function() {
|
|
|
3798
3815
|
}
|
|
3799
3816
|
}
|
|
3800
3817
|
} catch (err) {
|
|
3801
|
-
logger$
|
|
3818
|
+
logger$6.critical('Error while calculating scroll percentage', err);
|
|
3802
3819
|
}
|
|
3803
3820
|
if (shouldTrack) {
|
|
3804
3821
|
this.mp.track(MP_EV_SCROLL, props);
|
|
@@ -3816,7 +3833,7 @@ Autocapture.prototype.initSubmitTracking = function() {
|
|
|
3816
3833
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
3817
3834
|
return;
|
|
3818
3835
|
}
|
|
3819
|
-
logger$
|
|
3836
|
+
logger$6.log('Initializing submit tracking');
|
|
3820
3837
|
|
|
3821
3838
|
this.listenerSubmit = function(ev) {
|
|
3822
3839
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
@@ -3838,7 +3855,7 @@ Autocapture.prototype.initPageLeaveTracking = function() {
|
|
|
3838
3855
|
return;
|
|
3839
3856
|
}
|
|
3840
3857
|
|
|
3841
|
-
logger$
|
|
3858
|
+
logger$6.log('Initializing page visibility tracking.');
|
|
3842
3859
|
this._initScrollDepthTracking();
|
|
3843
3860
|
var previousTrackedUrl = _.info.currentUrl();
|
|
3844
3861
|
|
|
@@ -3923,10 +3940,309 @@ var getTargetingPromise = function(loadExtraBundle, targetingSrc) {
|
|
|
3923
3940
|
return win[TARGETING_GLOBAL_NAME];
|
|
3924
3941
|
};
|
|
3925
3942
|
|
|
3943
|
+
/**
|
|
3944
|
+
* @type {import('./wrapper').StorageWrapper}
|
|
3945
|
+
*/
|
|
3946
|
+
var IDBStorageWrapper = function (dbName, storeName, versionData) {
|
|
3947
|
+
this.dbName = dbName;
|
|
3948
|
+
this.storeName = storeName;
|
|
3949
|
+
this.version = versionData.version;
|
|
3950
|
+
this.storeNamesInDb = versionData.storeNames;
|
|
3951
|
+
/**
|
|
3952
|
+
* @type {Promise<IDBDatabase>|null}
|
|
3953
|
+
*/
|
|
3954
|
+
this.dbPromise = null;
|
|
3955
|
+
};
|
|
3956
|
+
|
|
3957
|
+
IDBStorageWrapper.prototype._openDb = function () {
|
|
3958
|
+
var dbName = this.dbName;
|
|
3959
|
+
var version = this.version;
|
|
3960
|
+
var storeNamesInDb = this.storeNamesInDb;
|
|
3961
|
+
return new PromisePolyfill(function (resolve, reject) {
|
|
3962
|
+
var openRequest = win.indexedDB.open(dbName, version);
|
|
3963
|
+
openRequest['onerror'] = function () {
|
|
3964
|
+
reject(openRequest.error);
|
|
3965
|
+
};
|
|
3966
|
+
|
|
3967
|
+
openRequest['onsuccess'] = function () {
|
|
3968
|
+
resolve(openRequest.result);
|
|
3969
|
+
};
|
|
3970
|
+
|
|
3971
|
+
openRequest['onupgradeneeded'] = function (ev) {
|
|
3972
|
+
var db = ev.target.result;
|
|
3973
|
+
|
|
3974
|
+
storeNamesInDb.forEach(function (storeName) {
|
|
3975
|
+
if (!db.objectStoreNames.contains(storeName)) {
|
|
3976
|
+
db.createObjectStore(storeName);
|
|
3977
|
+
}
|
|
3978
|
+
});
|
|
3979
|
+
};
|
|
3980
|
+
});
|
|
3981
|
+
};
|
|
3982
|
+
|
|
3983
|
+
IDBStorageWrapper.prototype.init = function () {
|
|
3984
|
+
if (!win.indexedDB) {
|
|
3985
|
+
return PromisePolyfill.reject('indexedDB is not supported in this browser');
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3988
|
+
if (!this.dbPromise) {
|
|
3989
|
+
this.dbPromise = this._openDb();
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
return this.dbPromise
|
|
3993
|
+
.then(function (dbOrError) {
|
|
3994
|
+
if (dbOrError instanceof win['IDBDatabase']) {
|
|
3995
|
+
return PromisePolyfill.resolve();
|
|
3996
|
+
} else {
|
|
3997
|
+
return PromisePolyfill.reject(dbOrError);
|
|
3998
|
+
}
|
|
3999
|
+
});
|
|
4000
|
+
};
|
|
4001
|
+
|
|
4002
|
+
IDBStorageWrapper.prototype.isInitialized = function () {
|
|
4003
|
+
return !!this.dbPromise;
|
|
4004
|
+
};
|
|
4005
|
+
|
|
4006
|
+
/**
|
|
4007
|
+
* @param {IDBTransactionMode} mode
|
|
4008
|
+
* @param {function(IDBObjectStore): void} storeCb
|
|
4009
|
+
*/
|
|
4010
|
+
IDBStorageWrapper.prototype.makeTransaction = function (mode, storeCb) {
|
|
4011
|
+
var storeName = this.storeName;
|
|
4012
|
+
var doTransaction = function (db) {
|
|
4013
|
+
return new PromisePolyfill(function (resolve, reject) {
|
|
4014
|
+
var transaction = db.transaction(storeName, mode);
|
|
4015
|
+
transaction.oncomplete = function () {
|
|
4016
|
+
resolve(transaction);
|
|
4017
|
+
};
|
|
4018
|
+
transaction.onabort = transaction.onerror = function () {
|
|
4019
|
+
reject(transaction.error);
|
|
4020
|
+
};
|
|
4021
|
+
|
|
4022
|
+
storeCb(transaction.objectStore(storeName));
|
|
4023
|
+
});
|
|
4024
|
+
};
|
|
4025
|
+
|
|
4026
|
+
return this.dbPromise
|
|
4027
|
+
.then(doTransaction)
|
|
4028
|
+
.catch(function (err) {
|
|
4029
|
+
if (err && err['name'] === 'InvalidStateError') {
|
|
4030
|
+
// try reopening the DB if the connection is closed
|
|
4031
|
+
this.dbPromise = this._openDb();
|
|
4032
|
+
return this.dbPromise.then(doTransaction);
|
|
4033
|
+
} else {
|
|
4034
|
+
return PromisePolyfill.reject(err);
|
|
4035
|
+
}
|
|
4036
|
+
}.bind(this));
|
|
4037
|
+
};
|
|
4038
|
+
|
|
4039
|
+
IDBStorageWrapper.prototype.setItem = function (key, value) {
|
|
4040
|
+
return this.makeTransaction('readwrite', function (objectStore) {
|
|
4041
|
+
objectStore.put(value, key);
|
|
4042
|
+
});
|
|
4043
|
+
};
|
|
4044
|
+
|
|
4045
|
+
IDBStorageWrapper.prototype.getItem = function (key) {
|
|
4046
|
+
var req;
|
|
4047
|
+
return this.makeTransaction('readonly', function (objectStore) {
|
|
4048
|
+
req = objectStore.get(key);
|
|
4049
|
+
}).then(function () {
|
|
4050
|
+
return req.result;
|
|
4051
|
+
});
|
|
4052
|
+
};
|
|
4053
|
+
|
|
4054
|
+
IDBStorageWrapper.prototype.removeItem = function (key) {
|
|
4055
|
+
return this.makeTransaction('readwrite', function (objectStore) {
|
|
4056
|
+
objectStore.delete(key);
|
|
4057
|
+
});
|
|
4058
|
+
};
|
|
4059
|
+
|
|
4060
|
+
IDBStorageWrapper.prototype.getAll = function () {
|
|
4061
|
+
var req;
|
|
4062
|
+
return this.makeTransaction('readonly', function (objectStore) {
|
|
4063
|
+
req = objectStore.getAll();
|
|
4064
|
+
}).then(function () {
|
|
4065
|
+
return req.result;
|
|
4066
|
+
});
|
|
4067
|
+
};
|
|
4068
|
+
|
|
4069
|
+
var logger$5 = console_with_prefix('flags');
|
|
4070
|
+
|
|
4071
|
+
var MIXPANEL_FLAGS_DB_NAME = 'mixpanelFlagsDb';
|
|
4072
|
+
var FLAGS_STORE_NAME = 'mixpanelFlags';
|
|
4073
|
+
|
|
4074
|
+
// Keeping these two properties closeby, as adding additional stores to a DB in IndexedDB requires a version increment
|
|
4075
|
+
var FLAGS_VERSION_DATA = { version: 1, storeNames: [FLAGS_STORE_NAME] };
|
|
4076
|
+
|
|
4077
|
+
var PERSISTED_VARIANTS_KEY_PREFIX = 'persisted_variants_for_';
|
|
4078
|
+
var DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
4079
|
+
|
|
4080
|
+
var VariantLookupPolicy = Object.freeze({
|
|
4081
|
+
NETWORK_ONLY: 'networkOnly',
|
|
4082
|
+
NETWORK_FIRST: 'networkFirst',
|
|
4083
|
+
PERSISTENCE_UNTIL_NETWORK_SUCCESS: 'persistenceUntilNetworkSuccess'
|
|
4084
|
+
});
|
|
4085
|
+
|
|
4086
|
+
var VALID_POLICIES = [
|
|
4087
|
+
VariantLookupPolicy.NETWORK_ONLY,
|
|
4088
|
+
VariantLookupPolicy.NETWORK_FIRST,
|
|
4089
|
+
VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS
|
|
4090
|
+
];
|
|
4091
|
+
|
|
4092
|
+
/**
|
|
4093
|
+
* Module for handling the storage and retrieval of persisted feature flag variants.
|
|
4094
|
+
*/
|
|
4095
|
+
var FeatureFlagPersistence = function(persistenceConfig, token, isGloballyDisabled) {
|
|
4096
|
+
this.idb = new IDBStorageWrapper(MIXPANEL_FLAGS_DB_NAME, FLAGS_STORE_NAME, FLAGS_VERSION_DATA);
|
|
4097
|
+
this.persistenceConfig = persistenceConfig;
|
|
4098
|
+
this.persistedVariantsKey = PERSISTED_VARIANTS_KEY_PREFIX + token;
|
|
4099
|
+
this.isGloballyDisabled = isGloballyDisabled || function() { return false; };
|
|
4100
|
+
};
|
|
4101
|
+
|
|
4102
|
+
FeatureFlagPersistence.prototype.getPolicy = function() {
|
|
4103
|
+
if (this.isGloballyDisabled() || !this._isConfigValid()) {
|
|
4104
|
+
return VariantLookupPolicy.NETWORK_ONLY;
|
|
4105
|
+
}
|
|
4106
|
+
return this.persistenceConfig['variantLookupPolicy'];
|
|
4107
|
+
};
|
|
4108
|
+
|
|
4109
|
+
FeatureFlagPersistence.prototype.getTtlMs = function() {
|
|
4110
|
+
if (!this._isConfigValid()) {
|
|
4111
|
+
return DEFAULT_TTL_MS;
|
|
4112
|
+
}
|
|
4113
|
+
var configuredTtl = this.persistenceConfig['persistenceTtlMs'];
|
|
4114
|
+
return (configuredTtl === undefined || configuredTtl === null) ? DEFAULT_TTL_MS : configuredTtl;
|
|
4115
|
+
};
|
|
4116
|
+
|
|
4117
|
+
FeatureFlagPersistence.prototype._isConfigValid = function() {
|
|
4118
|
+
var config = this.persistenceConfig;
|
|
4119
|
+
if (!config) {
|
|
4120
|
+
return false;
|
|
4121
|
+
}
|
|
4122
|
+
|
|
4123
|
+
if (VALID_POLICIES.indexOf(config['variantLookupPolicy']) === -1) {
|
|
4124
|
+
logger$5.error('Invalid variantLookupPolicy:', config['variantLookupPolicy']);
|
|
4125
|
+
return false;
|
|
4126
|
+
}
|
|
4127
|
+
|
|
4128
|
+
if (config['persistenceTtlMs'] !== undefined &&
|
|
4129
|
+
config['persistenceTtlMs'] !== null &&
|
|
4130
|
+
config['persistenceTtlMs'] <= 0) {
|
|
4131
|
+
logger$5.error('If provided, persistenceTtlMs must be a positive number. Provided value:', config['persistenceTtlMs']);
|
|
4132
|
+
return false;
|
|
4133
|
+
}
|
|
4134
|
+
|
|
4135
|
+
return true;
|
|
4136
|
+
};
|
|
4137
|
+
|
|
4138
|
+
FeatureFlagPersistence.prototype.loadFlagsFromStorage = function(context) {
|
|
4139
|
+
var clearAndReturnNull = _.bind(function() {
|
|
4140
|
+
return this.clear().then(function() { return null; }).catch(function() { return null; });
|
|
4141
|
+
}, this);
|
|
4142
|
+
|
|
4143
|
+
if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
|
|
4144
|
+
return clearAndReturnNull();
|
|
4145
|
+
}
|
|
4146
|
+
|
|
4147
|
+
var ttlMs = this.getTtlMs();
|
|
4148
|
+
|
|
4149
|
+
return this.idb.init().then(_.bind(function() {
|
|
4150
|
+
return this.idb.getItem(this.persistedVariantsKey);
|
|
4151
|
+
}, this)).then(_.bind(function(data) {
|
|
4152
|
+
if (!data) {
|
|
4153
|
+
logger$5.log('No persisted variants found in IndexedDB');
|
|
4154
|
+
return null;
|
|
4155
|
+
}
|
|
4156
|
+
|
|
4157
|
+
if (ttlMs && Date.now() - data['persistedAt'] >= ttlMs) {
|
|
4158
|
+
logger$5.log('Persisted variants are expiring');
|
|
4159
|
+
return null;
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4162
|
+
if (!context || data['distinctId'] !== context['distinct_id']) {
|
|
4163
|
+
logger$5.log('Persisted variants found, but for a different distinct_id so clearing.');
|
|
4164
|
+
return clearAndReturnNull();
|
|
4165
|
+
}
|
|
4166
|
+
|
|
4167
|
+
var persistedFlags = new Map();
|
|
4168
|
+
_.each(data['flagVariants'], function(variantData, key) {
|
|
4169
|
+
persistedFlags.set(key, {
|
|
4170
|
+
'key': variantData['variant_key'],
|
|
4171
|
+
'value': variantData['variant_value'],
|
|
4172
|
+
'experiment_id': variantData['experiment_id'],
|
|
4173
|
+
'is_experiment_active': variantData['is_experiment_active'],
|
|
4174
|
+
'is_qa_tester': variantData['is_qa_tester'],
|
|
4175
|
+
'variant_source': 'persistence',
|
|
4176
|
+
'persisted_at_in_ms': data['persistedAt'],
|
|
4177
|
+
'ttl_in_ms': ttlMs
|
|
4178
|
+
});
|
|
4179
|
+
});
|
|
4180
|
+
|
|
4181
|
+
logger$5.log('Loaded', persistedFlags.size, 'variants from IndexedDB for distinct_id', data['distinctId']);
|
|
4182
|
+
|
|
4183
|
+
return {
|
|
4184
|
+
flags: persistedFlags,
|
|
4185
|
+
pendingFirstTimeEvents: data['pendingFirstTimeEvents'] || {},
|
|
4186
|
+
persistedAtMs: data['persistedAt'],
|
|
4187
|
+
ttlMs: ttlMs
|
|
4188
|
+
};
|
|
4189
|
+
}, this)).catch(_.bind(function(error) {
|
|
4190
|
+
logger$5.error('Failed to load persisted variants from IndexedDB, so clearing', error);
|
|
4191
|
+
return clearAndReturnNull();
|
|
4192
|
+
}, this));
|
|
4193
|
+
};
|
|
4194
|
+
|
|
4195
|
+
FeatureFlagPersistence.prototype.save = function(context, flagsMap, pendingFirstTimeEvents) {
|
|
4196
|
+
if (this.getPolicy() === VariantLookupPolicy.NETWORK_ONLY) {
|
|
4197
|
+
return Promise.resolve();
|
|
4198
|
+
}
|
|
4199
|
+
|
|
4200
|
+
var flagVariants = {};
|
|
4201
|
+
flagsMap.forEach(function(variant, key) {
|
|
4202
|
+
flagVariants[key] = {
|
|
4203
|
+
'variant_key': variant['key'],
|
|
4204
|
+
'variant_value': variant['value'],
|
|
4205
|
+
'experiment_id': variant['experiment_id'],
|
|
4206
|
+
'is_experiment_active': variant['is_experiment_active'],
|
|
4207
|
+
'is_qa_tester': variant['is_qa_tester']
|
|
4208
|
+
};
|
|
4209
|
+
});
|
|
4210
|
+
|
|
4211
|
+
var data = {
|
|
4212
|
+
'persistedAt': Date.now(),
|
|
4213
|
+
'distinctId': context && context['distinct_id'],
|
|
4214
|
+
'context': context,
|
|
4215
|
+
'flagVariants': flagVariants,
|
|
4216
|
+
'pendingFirstTimeEvents': pendingFirstTimeEvents || {}
|
|
4217
|
+
};
|
|
4218
|
+
|
|
4219
|
+
return this.idb.init().then(_.bind(function() {
|
|
4220
|
+
return this.idb.setItem(this.persistedVariantsKey, data);
|
|
4221
|
+
}, this)).then(function() {
|
|
4222
|
+
logger$5.log('Saved', flagsMap.size, 'variants to IndexedDB for distinct_id', data['distinctId']);
|
|
4223
|
+
}).catch(function(error) {
|
|
4224
|
+
logger$5.error('Failed to persist variants to IndexedDB:', error);
|
|
4225
|
+
});
|
|
4226
|
+
};
|
|
4227
|
+
|
|
4228
|
+
FeatureFlagPersistence.prototype.clear = function() {
|
|
4229
|
+
if (this.isGloballyDisabled()) {
|
|
4230
|
+
return Promise.resolve();
|
|
4231
|
+
}
|
|
4232
|
+
return this.idb.init().then(_.bind(function() {
|
|
4233
|
+
return this.idb.removeItem(this.persistedVariantsKey);
|
|
4234
|
+
}, this)).then(function() {
|
|
4235
|
+
logger$5.log('Cleared persisted variants from IndexedDB');
|
|
4236
|
+
}).catch(function(error) {
|
|
4237
|
+
logger$5.error('Failed to clear persisted variants from IndexedDB:', error);
|
|
4238
|
+
});
|
|
4239
|
+
};
|
|
4240
|
+
|
|
3926
4241
|
var logger$4 = console_with_prefix('flags');
|
|
3927
4242
|
var FLAGS_CONFIG_KEY = 'flags';
|
|
3928
4243
|
|
|
3929
4244
|
var CONFIG_CONTEXT = 'context';
|
|
4245
|
+
var CONFIG_PERSISTENCE = 'persistence';
|
|
3930
4246
|
var CONFIG_DEFAULTS = {};
|
|
3931
4247
|
CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
|
|
3932
4248
|
|
|
@@ -3949,6 +4265,13 @@ var getFlagKeyFromPendingEventKey = function(eventKey) {
|
|
|
3949
4265
|
return eventKey.split(':')[0];
|
|
3950
4266
|
};
|
|
3951
4267
|
|
|
4268
|
+
var withFallbackSource = function(fallback) {
|
|
4269
|
+
if (_.isObject(fallback)) {
|
|
4270
|
+
return _.extend({}, fallback, {'variant_source': 'fallback'});
|
|
4271
|
+
}
|
|
4272
|
+
return {'value': fallback, 'variant_source': 'fallback'};
|
|
4273
|
+
};
|
|
4274
|
+
|
|
3952
4275
|
/**
|
|
3953
4276
|
* FeatureFlagManager: support for Mixpanel's feature flagging product
|
|
3954
4277
|
* @constructor
|
|
@@ -3971,11 +4294,63 @@ FeatureFlagManager.prototype.init = function() {
|
|
|
3971
4294
|
}
|
|
3972
4295
|
|
|
3973
4296
|
this.flags = null;
|
|
3974
|
-
this.fetchFlags();
|
|
3975
|
-
|
|
3976
4297
|
this.trackedFeatures = new Set();
|
|
3977
4298
|
this.pendingFirstTimeEvents = {};
|
|
3978
4299
|
this.activatedFirstTimeEvents = {};
|
|
4300
|
+
this._loadedPersistedAtMs = null;
|
|
4301
|
+
this._loadedTtlMs = null;
|
|
4302
|
+
|
|
4303
|
+
this.persistence = new FeatureFlagPersistence(
|
|
4304
|
+
this.getConfig(CONFIG_PERSISTENCE),
|
|
4305
|
+
this.getMpConfig('token'),
|
|
4306
|
+
_.bind(function() { return this.getMpConfig('disable_persistence'); }, this)
|
|
4307
|
+
);
|
|
4308
|
+
|
|
4309
|
+
this.persistenceLoadedPromise = this.persistence.loadFlagsFromStorage(this._buildContext())
|
|
4310
|
+
.then(_.bind(function(loaded) {
|
|
4311
|
+
if (loaded) {
|
|
4312
|
+
this.flags = loaded.flags;
|
|
4313
|
+
this.pendingFirstTimeEvents = loaded.pendingFirstTimeEvents;
|
|
4314
|
+
this._loadedPersistedAtMs = loaded.persistedAtMs;
|
|
4315
|
+
this._loadedTtlMs = loaded.ttlMs;
|
|
4316
|
+
}
|
|
4317
|
+
}, this));
|
|
4318
|
+
|
|
4319
|
+
return this.persistenceLoadedPromise
|
|
4320
|
+
.then(_.bind(function() {
|
|
4321
|
+
return this.fetchFlags();
|
|
4322
|
+
}, this))
|
|
4323
|
+
.catch(function() {
|
|
4324
|
+
logger$4.error('Error initializing feature flags');
|
|
4325
|
+
});
|
|
4326
|
+
};
|
|
4327
|
+
|
|
4328
|
+
FeatureFlagManager.prototype._buildContext = function() {
|
|
4329
|
+
return _.extend(
|
|
4330
|
+
{'distinct_id': this.getMpProperty('distinct_id'), 'device_id': this.getMpProperty('$device_id')},
|
|
4331
|
+
this.getConfig(CONFIG_CONTEXT)
|
|
4332
|
+
);
|
|
4333
|
+
};
|
|
4334
|
+
|
|
4335
|
+
FeatureFlagManager.prototype.reset = function() {
|
|
4336
|
+
if (!this.persistence) {
|
|
4337
|
+
return Promise.resolve();
|
|
4338
|
+
}
|
|
4339
|
+
|
|
4340
|
+
this.flags = null;
|
|
4341
|
+
this.pendingFirstTimeEvents = {};
|
|
4342
|
+
this.activatedFirstTimeEvents = {};
|
|
4343
|
+
this.trackedFeatures = new Set();
|
|
4344
|
+
this.fetchPromise = null;
|
|
4345
|
+
this._fetchInProgressStartTime = null;
|
|
4346
|
+
this._loadedPersistedAtMs = null;
|
|
4347
|
+
this._loadedTtlMs = null;
|
|
4348
|
+
|
|
4349
|
+
return this.persistence.clear().then(_.bind(function() {
|
|
4350
|
+
return this.fetchFlags();
|
|
4351
|
+
}, this)).catch(function() {
|
|
4352
|
+
logger$4.error('Error during flags reset');
|
|
4353
|
+
});
|
|
3979
4354
|
};
|
|
3980
4355
|
|
|
3981
4356
|
FeatureFlagManager.prototype.getFullConfig = function() {
|
|
@@ -4012,8 +4387,12 @@ FeatureFlagManager.prototype.updateContext = function(newContext, options) {
|
|
|
4012
4387
|
var oldContext = (options && options['replace']) ? {} : this.getConfig(CONFIG_CONTEXT);
|
|
4013
4388
|
ffConfig[CONFIG_CONTEXT] = _.extend({}, oldContext, newContext);
|
|
4014
4389
|
|
|
4015
|
-
|
|
4016
|
-
|
|
4390
|
+
var configUpdate = {};
|
|
4391
|
+
configUpdate[FLAGS_CONFIG_KEY] = ffConfig;
|
|
4392
|
+
this.setMpConfig(configUpdate);
|
|
4393
|
+
return this.fetchFlags().catch(function() {
|
|
4394
|
+
logger$4.error('Error fetching flags during updateContext');
|
|
4395
|
+
});
|
|
4017
4396
|
};
|
|
4018
4397
|
|
|
4019
4398
|
FeatureFlagManager.prototype.areFlagsReady = function() {
|
|
@@ -4028,12 +4407,11 @@ FeatureFlagManager.prototype.fetchFlags = function() {
|
|
|
4028
4407
|
return Promise.resolve();
|
|
4029
4408
|
}
|
|
4030
4409
|
|
|
4031
|
-
var
|
|
4032
|
-
var
|
|
4410
|
+
var context = this._buildContext();
|
|
4411
|
+
var distinctId = context['distinct_id'];
|
|
4033
4412
|
var traceparent = generateTraceparent();
|
|
4034
4413
|
logger$4.log('Fetching flags for distinct ID: ' + distinctId);
|
|
4035
4414
|
|
|
4036
|
-
var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
|
|
4037
4415
|
var searchParams = new URLSearchParams();
|
|
4038
4416
|
searchParams.set('context', JSON.stringify(context));
|
|
4039
4417
|
searchParams.set('token', this.getMpConfig('token'));
|
|
@@ -4050,96 +4428,116 @@ FeatureFlagManager.prototype.fetchFlags = function() {
|
|
|
4050
4428
|
}
|
|
4051
4429
|
}).then(function(response) {
|
|
4052
4430
|
this.markFetchComplete();
|
|
4053
|
-
return response.json()
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
}
|
|
4431
|
+
return response.json();
|
|
4432
|
+
}.bind(this)).then(function(responseBody) {
|
|
4433
|
+
var responseFlags = responseBody['flags'];
|
|
4434
|
+
if (!responseFlags) {
|
|
4435
|
+
throw new Error('No flags in API response');
|
|
4436
|
+
}
|
|
4437
|
+
var flags = new Map();
|
|
4438
|
+
var pendingFirstTimeEvents = {};
|
|
4439
|
+
|
|
4440
|
+
// Process flags from response
|
|
4441
|
+
_.each(responseFlags, function(data, key) {
|
|
4442
|
+
// Check if this flag has any activated first-time events this session
|
|
4443
|
+
var hasActivatedEvent = false;
|
|
4444
|
+
var prefix = key + ':';
|
|
4445
|
+
_.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
|
|
4446
|
+
if (eventKey.startsWith(prefix)) {
|
|
4447
|
+
hasActivatedEvent = true;
|
|
4448
|
+
}
|
|
4449
|
+
});
|
|
4071
4450
|
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
}
|
|
4078
|
-
} else {
|
|
4079
|
-
// Use server's current variant
|
|
4080
|
-
flags.set(key, {
|
|
4081
|
-
'key': data['variant_key'],
|
|
4082
|
-
'value': data['variant_value'],
|
|
4083
|
-
'experiment_id': data['experiment_id'],
|
|
4084
|
-
'is_experiment_active': data['is_experiment_active'],
|
|
4085
|
-
'is_qa_tester': data['is_qa_tester']
|
|
4086
|
-
});
|
|
4451
|
+
if (hasActivatedEvent) {
|
|
4452
|
+
// Preserve the activated variant, don't overwrite with server's current variant
|
|
4453
|
+
var currentFlag = this.flags && this.flags.get(key);
|
|
4454
|
+
if (currentFlag) {
|
|
4455
|
+
flags.set(key, currentFlag);
|
|
4087
4456
|
}
|
|
4088
|
-
}
|
|
4457
|
+
} else {
|
|
4458
|
+
// Use server's current variant
|
|
4459
|
+
flags.set(key, {
|
|
4460
|
+
'key': data['variant_key'],
|
|
4461
|
+
'value': data['variant_value'],
|
|
4462
|
+
'experiment_id': data['experiment_id'],
|
|
4463
|
+
'is_experiment_active': data['is_experiment_active'],
|
|
4464
|
+
'is_qa_tester': data['is_qa_tester'],
|
|
4465
|
+
'variant_source': 'network'
|
|
4466
|
+
});
|
|
4467
|
+
}
|
|
4468
|
+
}, this);
|
|
4089
4469
|
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4470
|
+
// Process top-level pending_first_time_events array
|
|
4471
|
+
var topLevelDefinitions = responseBody['pending_first_time_events'];
|
|
4472
|
+
if (topLevelDefinitions && topLevelDefinitions.length > 0) {
|
|
4473
|
+
_.each(topLevelDefinitions, function(def) {
|
|
4474
|
+
var flagKey = def['flag_key'];
|
|
4475
|
+
var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
|
|
4096
4476
|
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4477
|
+
// Skip if this specific event has already been activated this session
|
|
4478
|
+
if (this.activatedFirstTimeEvents[eventKey]) {
|
|
4479
|
+
return;
|
|
4480
|
+
}
|
|
4101
4481
|
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4482
|
+
// Store pending event definition using composite key
|
|
4483
|
+
pendingFirstTimeEvents[eventKey] = {
|
|
4484
|
+
'flag_key': flagKey,
|
|
4485
|
+
'flag_id': def['flag_id'],
|
|
4486
|
+
'project_id': def['project_id'],
|
|
4487
|
+
'first_time_event_hash': def['first_time_event_hash'],
|
|
4488
|
+
'event_name': def['event_name'],
|
|
4489
|
+
'property_filters': def['property_filters'],
|
|
4490
|
+
'pending_variant': def['pending_variant']
|
|
4491
|
+
};
|
|
4492
|
+
}, this);
|
|
4493
|
+
}
|
|
4114
4494
|
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4495
|
+
// Preserve any activated orphaned flags (flags that were activated but are no longer in response)
|
|
4496
|
+
if (this.activatedFirstTimeEvents) {
|
|
4497
|
+
_.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
|
|
4498
|
+
var flagKey = getFlagKeyFromPendingEventKey(eventKey);
|
|
4499
|
+
if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
|
|
4500
|
+
// Keep the activated flag even though it's not in the new response
|
|
4501
|
+
flags.set(flagKey, this.flags.get(flagKey));
|
|
4502
|
+
}
|
|
4503
|
+
}, this);
|
|
4504
|
+
}
|
|
4125
4505
|
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4506
|
+
this.flags = flags;
|
|
4507
|
+
this.trackedFeatures = new Set();
|
|
4508
|
+
this.pendingFirstTimeEvents = pendingFirstTimeEvents;
|
|
4509
|
+
this._loadedPersistedAtMs = null;
|
|
4510
|
+
this._loadedTtlMs = null;
|
|
4511
|
+
this._traceparent = traceparent;
|
|
4129
4512
|
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
logger$4.error(error);
|
|
4134
|
-
}.bind(this));
|
|
4513
|
+
this._loadTargetingIfNeeded();
|
|
4514
|
+
|
|
4515
|
+
this.persistence.save(context, this.flags, this.pendingFirstTimeEvents);
|
|
4135
4516
|
}.bind(this)).catch(function(error) {
|
|
4136
|
-
this.
|
|
4517
|
+
if (this._fetchInProgressStartTime) {
|
|
4518
|
+
this.markFetchComplete();
|
|
4519
|
+
}
|
|
4137
4520
|
logger$4.error(error);
|
|
4521
|
+
throw error;
|
|
4138
4522
|
}.bind(this));
|
|
4139
4523
|
|
|
4140
4524
|
return this.fetchPromise;
|
|
4141
4525
|
};
|
|
4142
4526
|
|
|
4527
|
+
FeatureFlagManager.prototype.loadFlags = function() {
|
|
4528
|
+
if (!this.isSystemEnabled()) {
|
|
4529
|
+
return Promise.resolve();
|
|
4530
|
+
}
|
|
4531
|
+
if (!this.trackedFeatures) {
|
|
4532
|
+
logger$4.error('loadFlags called before init');
|
|
4533
|
+
return Promise.resolve();
|
|
4534
|
+
}
|
|
4535
|
+
if (this._fetchInProgressStartTime) {
|
|
4536
|
+
return this.fetchPromise;
|
|
4537
|
+
}
|
|
4538
|
+
return this.fetchFlags();
|
|
4539
|
+
};
|
|
4540
|
+
|
|
4143
4541
|
FeatureFlagManager.prototype.markFetchComplete = function() {
|
|
4144
4542
|
if (!this._fetchInProgressStartTime) {
|
|
4145
4543
|
logger$4.error('Fetch in progress started time not set, cannot mark fetch complete');
|
|
@@ -4274,6 +4672,7 @@ FeatureFlagManager.prototype._processFirstTimeEventCheck = function(eventName, p
|
|
|
4274
4672
|
};
|
|
4275
4673
|
|
|
4276
4674
|
this.flags.set(flagKey, newVariant);
|
|
4675
|
+
this.trackedFeatures.delete(flagKey);
|
|
4277
4676
|
this.activatedFirstTimeEvents[eventKey] = true;
|
|
4278
4677
|
|
|
4279
4678
|
this.recordFirstTimeEvent(
|
|
@@ -4323,35 +4722,106 @@ FeatureFlagManager.prototype.recordFirstTimeEvent = function(flagId, projectId,
|
|
|
4323
4722
|
};
|
|
4324
4723
|
|
|
4325
4724
|
FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
|
|
4326
|
-
if (!this.
|
|
4725
|
+
if (!this.persistenceLoadedPromise) {
|
|
4327
4726
|
return new Promise(function(resolve) {
|
|
4328
4727
|
logger$4.critical('Feature Flags not initialized');
|
|
4329
|
-
resolve(fallback);
|
|
4728
|
+
resolve(withFallbackSource(fallback));
|
|
4330
4729
|
});
|
|
4331
4730
|
}
|
|
4332
4731
|
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4732
|
+
var policy = this.persistence.getPolicy();
|
|
4733
|
+
|
|
4734
|
+
return this.persistenceLoadedPromise.then(_.bind(function() {
|
|
4735
|
+
// 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.
|
|
4736
|
+
if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
|
|
4737
|
+
if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
|
|
4738
|
+
return this.getVariantSync(featureName, fallback);
|
|
4739
|
+
}
|
|
4740
|
+
if (!this.fetchPromise) {
|
|
4741
|
+
return withFallbackSource(fallback);
|
|
4742
|
+
}
|
|
4743
|
+
return this.fetchPromise.then(_.bind(function() {
|
|
4744
|
+
return this.getVariantSync(featureName, fallback);
|
|
4745
|
+
}, this)).catch(function(error) {
|
|
4746
|
+
logger$4.error(error);
|
|
4747
|
+
return withFallbackSource(fallback);
|
|
4748
|
+
});
|
|
4749
|
+
}
|
|
4750
|
+
|
|
4751
|
+
var serve = _.bind(function() { return this.getVariantSync(featureName, fallback); }, this);
|
|
4752
|
+
if (!this.fetchPromise) {
|
|
4753
|
+
return withFallbackSource(fallback);
|
|
4754
|
+
}
|
|
4755
|
+
return this.fetchPromise.then(serve).catch(serve);
|
|
4756
|
+
}, this));
|
|
4757
|
+
};
|
|
4758
|
+
|
|
4759
|
+
FeatureFlagManager.prototype._loadedPersistenceIsStale = function() {
|
|
4760
|
+
if (!this._loadedPersistedAtMs || !this._loadedTtlMs) {
|
|
4761
|
+
return false;
|
|
4762
|
+
}
|
|
4763
|
+
return Date.now() - this._loadedPersistedAtMs >= this._loadedTtlMs;
|
|
4339
4764
|
};
|
|
4340
4765
|
|
|
4341
4766
|
FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
|
|
4767
|
+
if (this._loadedPersistenceIsStale()) {
|
|
4768
|
+
logger$4.log('Loaded persisted variants are past TTL so returning fallback for "' + featureName + '"');
|
|
4769
|
+
return withFallbackSource(fallback);
|
|
4770
|
+
}
|
|
4342
4771
|
if (!this.areFlagsReady()) {
|
|
4343
4772
|
logger$4.log('Flags not loaded yet');
|
|
4344
|
-
return fallback;
|
|
4773
|
+
return withFallbackSource(fallback);
|
|
4345
4774
|
}
|
|
4346
4775
|
var feature = this.flags.get(featureName);
|
|
4347
4776
|
if (!feature) {
|
|
4348
4777
|
logger$4.log('No flag found: "' + featureName + '"');
|
|
4349
|
-
return fallback;
|
|
4778
|
+
return withFallbackSource(fallback);
|
|
4350
4779
|
}
|
|
4351
4780
|
this.trackFeatureCheck(featureName, feature);
|
|
4352
4781
|
return feature;
|
|
4353
4782
|
};
|
|
4354
4783
|
|
|
4784
|
+
FeatureFlagManager.prototype.getAllVariants = function() {
|
|
4785
|
+
if (!this.persistenceLoadedPromise) {
|
|
4786
|
+
logger$4.critical('Feature Flags not initialized');
|
|
4787
|
+
return Promise.resolve(new Map());
|
|
4788
|
+
}
|
|
4789
|
+
|
|
4790
|
+
var policy = this.persistence.getPolicy();
|
|
4791
|
+
|
|
4792
|
+
return this.persistenceLoadedPromise.then(_.bind(function() {
|
|
4793
|
+
// 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.
|
|
4794
|
+
if (policy === VariantLookupPolicy.PERSISTENCE_UNTIL_NETWORK_SUCCESS) {
|
|
4795
|
+
if (this.areFlagsReady() && !this._loadedPersistenceIsStale()) {
|
|
4796
|
+
return this.getAllVariantsSync();
|
|
4797
|
+
}
|
|
4798
|
+
if (!this.fetchPromise) {
|
|
4799
|
+
return new Map();
|
|
4800
|
+
}
|
|
4801
|
+
return this.fetchPromise.then(_.bind(function() {
|
|
4802
|
+
return this.getAllVariantsSync();
|
|
4803
|
+
}, this)).catch(function(error) {
|
|
4804
|
+
logger$4.error(error);
|
|
4805
|
+
return new Map();
|
|
4806
|
+
});
|
|
4807
|
+
}
|
|
4808
|
+
|
|
4809
|
+
var serve = _.bind(this.getAllVariantsSync, this);
|
|
4810
|
+
if (!this.fetchPromise) {
|
|
4811
|
+
return new Map();
|
|
4812
|
+
}
|
|
4813
|
+
return this.fetchPromise.then(serve).catch(serve);
|
|
4814
|
+
}, this));
|
|
4815
|
+
};
|
|
4816
|
+
|
|
4817
|
+
FeatureFlagManager.prototype.getAllVariantsSync = function() {
|
|
4818
|
+
if (this._loadedPersistenceIsStale()) {
|
|
4819
|
+
logger$4.log('Loaded persisted variants are past TTL so returning empty Map');
|
|
4820
|
+
return new Map();
|
|
4821
|
+
}
|
|
4822
|
+
return this.flags || new Map();
|
|
4823
|
+
};
|
|
4824
|
+
|
|
4355
4825
|
FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
|
|
4356
4826
|
return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
|
|
4357
4827
|
return feature['value'];
|
|
@@ -4390,6 +4860,10 @@ FeatureFlagManager.prototype.isEnabledSync = function(featureName, fallbackValue
|
|
|
4390
4860
|
return val;
|
|
4391
4861
|
};
|
|
4392
4862
|
|
|
4863
|
+
function isPresent(v) {
|
|
4864
|
+
return v !== undefined && v !== null;
|
|
4865
|
+
}
|
|
4866
|
+
|
|
4393
4867
|
FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
|
|
4394
4868
|
if (this.trackedFeatures.has(featureName)) {
|
|
4395
4869
|
return;
|
|
@@ -4400,25 +4874,41 @@ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature)
|
|
|
4400
4874
|
'Experiment name': featureName,
|
|
4401
4875
|
'Variant name': feature['key'],
|
|
4402
4876
|
'$experiment_type': 'feature_flag',
|
|
4403
|
-
'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
|
|
4404
|
-
'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
|
|
4877
|
+
'Variant fetch start time': isPresent(this._fetchStartTime) ? new Date(this._fetchStartTime).toISOString() : null,
|
|
4878
|
+
'Variant fetch complete time': isPresent(this._fetchCompleteTime) ? new Date(this._fetchCompleteTime).toISOString() : null,
|
|
4405
4879
|
'Variant fetch latency (ms)': this._fetchLatency,
|
|
4406
4880
|
'Variant fetch traceparent': this._traceparent,
|
|
4407
4881
|
};
|
|
4408
4882
|
|
|
4409
|
-
if (feature['experiment_id']
|
|
4883
|
+
if (isPresent(feature['experiment_id'])) {
|
|
4410
4884
|
trackingProperties['$experiment_id'] = feature['experiment_id'];
|
|
4411
4885
|
}
|
|
4412
|
-
if (feature['is_experiment_active']
|
|
4886
|
+
if (isPresent(feature['is_experiment_active'])) {
|
|
4413
4887
|
trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
|
|
4414
4888
|
}
|
|
4415
|
-
if (feature['is_qa_tester']
|
|
4889
|
+
if (isPresent(feature['is_qa_tester'])) {
|
|
4416
4890
|
trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
|
|
4417
4891
|
}
|
|
4892
|
+
if (isPresent(feature['variant_source'])) {
|
|
4893
|
+
trackingProperties['$variant_source'] = feature['variant_source'];
|
|
4894
|
+
}
|
|
4895
|
+
if (isPresent(feature['persisted_at_in_ms'])) {
|
|
4896
|
+
trackingProperties['$persisted_at_in_ms'] = feature['persisted_at_in_ms'];
|
|
4897
|
+
}
|
|
4898
|
+
if (isPresent(feature['ttl_in_ms'])) {
|
|
4899
|
+
trackingProperties['$ttl_in_ms'] = feature['ttl_in_ms'];
|
|
4900
|
+
}
|
|
4418
4901
|
|
|
4419
4902
|
this.track('$experiment_started', trackingProperties);
|
|
4420
4903
|
};
|
|
4421
4904
|
|
|
4905
|
+
FeatureFlagManager.prototype.whenReady = function() {
|
|
4906
|
+
if (this.fetchPromise) {
|
|
4907
|
+
return this.fetchPromise;
|
|
4908
|
+
}
|
|
4909
|
+
return Promise.resolve();
|
|
4910
|
+
};
|
|
4911
|
+
|
|
4422
4912
|
FeatureFlagManager.prototype.minApisSupported = function() {
|
|
4423
4913
|
return !!this.fetch &&
|
|
4424
4914
|
typeof Promise !== 'undefined' &&
|
|
@@ -4431,11 +4921,15 @@ safewrapClass(FeatureFlagManager);
|
|
|
4431
4921
|
FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
|
|
4432
4922
|
FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
|
|
4433
4923
|
FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
|
|
4924
|
+
FeatureFlagManager.prototype['get_all_variants'] = FeatureFlagManager.prototype.getAllVariants;
|
|
4925
|
+
FeatureFlagManager.prototype['get_all_variants_sync'] = FeatureFlagManager.prototype.getAllVariantsSync;
|
|
4434
4926
|
FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
|
|
4435
4927
|
FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
|
|
4436
4928
|
FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
|
|
4437
4929
|
FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
|
|
4930
|
+
FeatureFlagManager.prototype['load_flags'] = FeatureFlagManager.prototype.loadFlags;
|
|
4438
4931
|
FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
|
|
4932
|
+
FeatureFlagManager.prototype['when_ready'] = FeatureFlagManager.prototype.whenReady;
|
|
4439
4933
|
|
|
4440
4934
|
// Deprecated method
|
|
4441
4935
|
FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
|
|
@@ -4443,131 +4937,14 @@ FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.
|
|
|
4443
4937
|
// Exports intended only for testing
|
|
4444
4938
|
FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
|
|
4445
4939
|
|
|
4446
|
-
var
|
|
4447
|
-
|
|
4940
|
+
var MIXPANEL_BROWSER_DB_NAME = 'mixpanelBrowserDb';
|
|
4448
4941
|
var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
|
|
4449
4942
|
var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
|
|
4450
4943
|
|
|
4451
|
-
//
|
|
4452
|
-
var
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
/**
|
|
4456
|
-
* @type {import('./wrapper').StorageWrapper}
|
|
4457
|
-
*/
|
|
4458
|
-
var IDBStorageWrapper = function (storeName) {
|
|
4459
|
-
/**
|
|
4460
|
-
* @type {Promise<IDBDatabase>|null}
|
|
4461
|
-
*/
|
|
4462
|
-
this.dbPromise = null;
|
|
4463
|
-
this.storeName = storeName;
|
|
4464
|
-
};
|
|
4465
|
-
|
|
4466
|
-
IDBStorageWrapper.prototype._openDb = function () {
|
|
4467
|
-
return new PromisePolyfill(function (resolve, reject) {
|
|
4468
|
-
var openRequest = win.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
|
|
4469
|
-
openRequest['onerror'] = function () {
|
|
4470
|
-
reject(openRequest.error);
|
|
4471
|
-
};
|
|
4472
|
-
|
|
4473
|
-
openRequest['onsuccess'] = function () {
|
|
4474
|
-
resolve(openRequest.result);
|
|
4475
|
-
};
|
|
4476
|
-
|
|
4477
|
-
openRequest['onupgradeneeded'] = function (ev) {
|
|
4478
|
-
var db = ev.target.result;
|
|
4479
|
-
|
|
4480
|
-
OBJECT_STORES.forEach(function (storeName) {
|
|
4481
|
-
db.createObjectStore(storeName);
|
|
4482
|
-
});
|
|
4483
|
-
};
|
|
4484
|
-
});
|
|
4485
|
-
};
|
|
4486
|
-
|
|
4487
|
-
IDBStorageWrapper.prototype.init = function () {
|
|
4488
|
-
if (!win.indexedDB) {
|
|
4489
|
-
return PromisePolyfill.reject('indexedDB is not supported in this browser');
|
|
4490
|
-
}
|
|
4491
|
-
|
|
4492
|
-
if (!this.dbPromise) {
|
|
4493
|
-
this.dbPromise = this._openDb();
|
|
4494
|
-
}
|
|
4495
|
-
|
|
4496
|
-
return this.dbPromise
|
|
4497
|
-
.then(function (dbOrError) {
|
|
4498
|
-
if (dbOrError instanceof win['IDBDatabase']) {
|
|
4499
|
-
return PromisePolyfill.resolve();
|
|
4500
|
-
} else {
|
|
4501
|
-
return PromisePolyfill.reject(dbOrError);
|
|
4502
|
-
}
|
|
4503
|
-
});
|
|
4504
|
-
};
|
|
4505
|
-
|
|
4506
|
-
IDBStorageWrapper.prototype.isInitialized = function () {
|
|
4507
|
-
return !!this.dbPromise;
|
|
4508
|
-
};
|
|
4509
|
-
|
|
4510
|
-
/**
|
|
4511
|
-
* @param {IDBTransactionMode} mode
|
|
4512
|
-
* @param {function(IDBObjectStore): void} storeCb
|
|
4513
|
-
*/
|
|
4514
|
-
IDBStorageWrapper.prototype.makeTransaction = function (mode, storeCb) {
|
|
4515
|
-
var storeName = this.storeName;
|
|
4516
|
-
var doTransaction = function (db) {
|
|
4517
|
-
return new PromisePolyfill(function (resolve, reject) {
|
|
4518
|
-
var transaction = db.transaction(storeName, mode);
|
|
4519
|
-
transaction.oncomplete = function () {
|
|
4520
|
-
resolve(transaction);
|
|
4521
|
-
};
|
|
4522
|
-
transaction.onabort = transaction.onerror = function () {
|
|
4523
|
-
reject(transaction.error);
|
|
4524
|
-
};
|
|
4525
|
-
|
|
4526
|
-
storeCb(transaction.objectStore(storeName));
|
|
4527
|
-
});
|
|
4528
|
-
};
|
|
4529
|
-
|
|
4530
|
-
return this.dbPromise
|
|
4531
|
-
.then(doTransaction)
|
|
4532
|
-
.catch(function (err) {
|
|
4533
|
-
if (err && err['name'] === 'InvalidStateError') {
|
|
4534
|
-
// try reopening the DB if the connection is closed
|
|
4535
|
-
this.dbPromise = this._openDb();
|
|
4536
|
-
return this.dbPromise.then(doTransaction);
|
|
4537
|
-
} else {
|
|
4538
|
-
return PromisePolyfill.reject(err);
|
|
4539
|
-
}
|
|
4540
|
-
}.bind(this));
|
|
4541
|
-
};
|
|
4542
|
-
|
|
4543
|
-
IDBStorageWrapper.prototype.setItem = function (key, value) {
|
|
4544
|
-
return this.makeTransaction('readwrite', function (objectStore) {
|
|
4545
|
-
objectStore.put(value, key);
|
|
4546
|
-
});
|
|
4547
|
-
};
|
|
4548
|
-
|
|
4549
|
-
IDBStorageWrapper.prototype.getItem = function (key) {
|
|
4550
|
-
var req;
|
|
4551
|
-
return this.makeTransaction('readonly', function (objectStore) {
|
|
4552
|
-
req = objectStore.get(key);
|
|
4553
|
-
}).then(function () {
|
|
4554
|
-
return req.result;
|
|
4555
|
-
});
|
|
4556
|
-
};
|
|
4557
|
-
|
|
4558
|
-
IDBStorageWrapper.prototype.removeItem = function (key) {
|
|
4559
|
-
return this.makeTransaction('readwrite', function (objectStore) {
|
|
4560
|
-
objectStore.delete(key);
|
|
4561
|
-
});
|
|
4562
|
-
};
|
|
4563
|
-
|
|
4564
|
-
IDBStorageWrapper.prototype.getAll = function () {
|
|
4565
|
-
var req;
|
|
4566
|
-
return this.makeTransaction('readonly', function (objectStore) {
|
|
4567
|
-
req = objectStore.getAll();
|
|
4568
|
-
}).then(function () {
|
|
4569
|
-
return req.result;
|
|
4570
|
-
});
|
|
4944
|
+
// Keeping these two properties closeby, as adding additional stores to a DB in IndexedDB requires a version increment
|
|
4945
|
+
var RECORDER_VERSION_DATA = {
|
|
4946
|
+
version: 1,
|
|
4947
|
+
storeNames: [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME]
|
|
4571
4948
|
};
|
|
4572
4949
|
|
|
4573
4950
|
/**
|
|
@@ -4640,7 +5017,7 @@ RecorderManager.prototype.shouldLoadRecorder = function() {
|
|
|
4640
5017
|
return PromisePolyfill.resolve(false);
|
|
4641
5018
|
}
|
|
4642
5019
|
|
|
4643
|
-
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
5020
|
+
var recording_registry_idb = new IDBStorageWrapper(MIXPANEL_BROWSER_DB_NAME, RECORDING_REGISTRY_STORE_NAME, RECORDER_VERSION_DATA);
|
|
4644
5021
|
var tab_id = this.getTabId();
|
|
4645
5022
|
return recording_registry_idb.init()
|
|
4646
5023
|
.then(function () {
|
|
@@ -5096,7 +5473,7 @@ var SharedLock = function(key, options) {
|
|
|
5096
5473
|
options = options || {};
|
|
5097
5474
|
|
|
5098
5475
|
this.storageKey = key;
|
|
5099
|
-
this.storage = options.storage ||
|
|
5476
|
+
this.storage = options.storage || getLocalStorage();
|
|
5100
5477
|
this.pollIntervalMS = options.pollIntervalMS || 100;
|
|
5101
5478
|
this.timeoutMS = options.timeoutMS || 2000;
|
|
5102
5479
|
|
|
@@ -5224,10 +5601,13 @@ SharedLock.prototype.withLock = function(lockedCB, pid) {
|
|
|
5224
5601
|
* @type {import('./wrapper').StorageWrapper}
|
|
5225
5602
|
*/
|
|
5226
5603
|
var LocalStorageWrapper = function (storageOverride) {
|
|
5227
|
-
this.storage = storageOverride ||
|
|
5604
|
+
this.storage = storageOverride || getLocalStorage();
|
|
5228
5605
|
};
|
|
5229
5606
|
|
|
5230
5607
|
LocalStorageWrapper.prototype.init = function () {
|
|
5608
|
+
if (!this.storage) {
|
|
5609
|
+
return PromisePolyfill.reject(new Error('localStorage is not available'));
|
|
5610
|
+
}
|
|
5231
5611
|
return PromisePolyfill.resolve();
|
|
5232
5612
|
};
|
|
5233
5613
|
|
|
@@ -5294,7 +5674,7 @@ var RequestQueue = function (storageKey, options) {
|
|
|
5294
5674
|
if (this.usePersistence) {
|
|
5295
5675
|
this.queueStorage = options.queueStorage || new LocalStorageWrapper();
|
|
5296
5676
|
this.lock = new SharedLock(storageKey, {
|
|
5297
|
-
storage: options.sharedLockStorage
|
|
5677
|
+
storage: options.sharedLockStorage,
|
|
5298
5678
|
timeoutMS: options.sharedLockTimeoutMS,
|
|
5299
5679
|
});
|
|
5300
5680
|
}
|
|
@@ -7759,6 +8139,7 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
7759
8139
|
'disable_all_events': false,
|
|
7760
8140
|
'identify_called': false
|
|
7761
8141
|
};
|
|
8142
|
+
this._remote_settings_strict_disabled = false;
|
|
7762
8143
|
|
|
7763
8144
|
// set up request queueing/batching
|
|
7764
8145
|
this.request_batchers = {};
|
|
@@ -7833,9 +8214,6 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
7833
8214
|
this.flags.init();
|
|
7834
8215
|
this['flags'] = this.flags;
|
|
7835
8216
|
|
|
7836
|
-
this.autocapture = new Autocapture(this);
|
|
7837
|
-
this.autocapture.init();
|
|
7838
|
-
|
|
7839
8217
|
this._init_tab_id();
|
|
7840
8218
|
|
|
7841
8219
|
// Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
|
|
@@ -7847,6 +8225,9 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
7847
8225
|
} else {
|
|
7848
8226
|
this.__session_recording_init_promise = this._check_and_start_session_recording();
|
|
7849
8227
|
}
|
|
8228
|
+
|
|
8229
|
+
this.autocapture = new Autocapture(this);
|
|
8230
|
+
this.autocapture.init();
|
|
7850
8231
|
};
|
|
7851
8232
|
|
|
7852
8233
|
/**
|
|
@@ -7893,9 +8274,19 @@ MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpane
|
|
|
7893
8274
|
return this.recorderManager.checkAndStartSessionRecording(force_start);
|
|
7894
8275
|
});
|
|
7895
8276
|
|
|
7896
|
-
MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
|
|
7897
|
-
|
|
7898
|
-
|
|
8277
|
+
MixpanelLib.prototype._start_recording_on_event = safewrap(function(event_name, properties) {
|
|
8278
|
+
// Wait for recording init to complete before evaluating event triggers.
|
|
8279
|
+
// This ensures recording_event_triggers config is fully loaded when remote settings are used.
|
|
8280
|
+
if (this.__session_recording_init_promise) {
|
|
8281
|
+
this.__session_recording_init_promise.then(_.bind(function() {
|
|
8282
|
+
// In strict mode, skip recording if remote settings failed
|
|
8283
|
+
if (this._remote_settings_strict_disabled) {
|
|
8284
|
+
return;
|
|
8285
|
+
}
|
|
8286
|
+
return this.recorderManager.startRecordingOnEvent(event_name, properties);
|
|
8287
|
+
}, this));
|
|
8288
|
+
}
|
|
8289
|
+
});
|
|
7899
8290
|
|
|
7900
8291
|
MixpanelLib.prototype.start_session_recording = function () {
|
|
7901
8292
|
return this._check_and_start_session_recording(true);
|
|
@@ -8194,6 +8585,7 @@ MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
|
8194
8585
|
var disableRecordingIfStrict = function() {
|
|
8195
8586
|
if (mode === 'strict') {
|
|
8196
8587
|
self.set_config({'record_sessions_percent': 0});
|
|
8588
|
+
self._remote_settings_strict_disabled = true;
|
|
8197
8589
|
}
|
|
8198
8590
|
};
|
|
8199
8591
|
|
|
@@ -8819,6 +9211,10 @@ MixpanelLib.prototype.track_pageview = addOptOutCheckMixpanelLib(function(proper
|
|
|
8819
9211
|
properties
|
|
8820
9212
|
);
|
|
8821
9213
|
|
|
9214
|
+
if (this.is_recording_heatmap_data()) {
|
|
9215
|
+
event_properties['$captured_for_heatmap'] = true;
|
|
9216
|
+
}
|
|
9217
|
+
|
|
8822
9218
|
return this.track(event_name, event_properties);
|
|
8823
9219
|
});
|
|
8824
9220
|
|
|
@@ -9142,7 +9538,9 @@ MixpanelLib.prototype.identify = function(
|
|
|
9142
9538
|
|
|
9143
9539
|
// check feature flags again if distinct id has changed
|
|
9144
9540
|
if (new_distinct_id !== previous_distinct_id) {
|
|
9145
|
-
this.flags.fetchFlags()
|
|
9541
|
+
this.flags.fetchFlags().catch(function() {
|
|
9542
|
+
console.error('[flags] Error fetching flags during identify');
|
|
9543
|
+
});
|
|
9146
9544
|
}
|
|
9147
9545
|
};
|
|
9148
9546
|
|
|
@@ -9160,6 +9558,7 @@ MixpanelLib.prototype.reset = function() {
|
|
|
9160
9558
|
'$device_id': uuid
|
|
9161
9559
|
}, '');
|
|
9162
9560
|
this._check_and_start_session_recording();
|
|
9561
|
+
this.flags.reset();
|
|
9163
9562
|
};
|
|
9164
9563
|
|
|
9165
9564
|
/**
|