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
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,11 +4295,63 @@
|
|
|
3972
4295
|
}
|
|
3973
4296
|
|
|
3974
4297
|
this.flags = null;
|
|
3975
|
-
this.fetchFlags();
|
|
3976
|
-
|
|
3977
4298
|
this.trackedFeatures = new Set();
|
|
3978
4299
|
this.pendingFirstTimeEvents = {};
|
|
3979
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
|
+
});
|
|
3980
4355
|
};
|
|
3981
4356
|
|
|
3982
4357
|
FeatureFlagManager.prototype.getFullConfig = function() {
|
|
@@ -4013,8 +4388,12 @@
|
|
|
4013
4388
|
var oldContext = (options && options['replace']) ? {} : this.getConfig(CONFIG_CONTEXT);
|
|
4014
4389
|
ffConfig[CONFIG_CONTEXT] = _.extend({}, oldContext, newContext);
|
|
4015
4390
|
|
|
4016
|
-
|
|
4017
|
-
|
|
4391
|
+
var configUpdate = {};
|
|
4392
|
+
configUpdate[FLAGS_CONFIG_KEY] = ffConfig;
|
|
4393
|
+
this.setMpConfig(configUpdate);
|
|
4394
|
+
return this.fetchFlags().catch(function() {
|
|
4395
|
+
logger$4.error('Error fetching flags during updateContext');
|
|
4396
|
+
});
|
|
4018
4397
|
};
|
|
4019
4398
|
|
|
4020
4399
|
FeatureFlagManager.prototype.areFlagsReady = function() {
|
|
@@ -4029,12 +4408,11 @@
|
|
|
4029
4408
|
return Promise.resolve();
|
|
4030
4409
|
}
|
|
4031
4410
|
|
|
4032
|
-
var
|
|
4033
|
-
var
|
|
4411
|
+
var context = this._buildContext();
|
|
4412
|
+
var distinctId = context['distinct_id'];
|
|
4034
4413
|
var traceparent = generateTraceparent();
|
|
4035
4414
|
logger$4.log('Fetching flags for distinct ID: ' + distinctId);
|
|
4036
4415
|
|
|
4037
|
-
var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
|
|
4038
4416
|
var searchParams = new URLSearchParams();
|
|
4039
4417
|
searchParams.set('context', JSON.stringify(context));
|
|
4040
4418
|
searchParams.set('token', this.getMpConfig('token'));
|
|
@@ -4051,96 +4429,116 @@
|
|
|
4051
4429
|
}
|
|
4052
4430
|
}).then(function(response) {
|
|
4053
4431
|
this.markFetchComplete();
|
|
4054
|
-
return response.json()
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
}
|
|
4432
|
+
return response.json();
|
|
4433
|
+
}.bind(this)).then(function(responseBody) {
|
|
4434
|
+
var responseFlags = responseBody['flags'];
|
|
4435
|
+
if (!responseFlags) {
|
|
4436
|
+
throw new Error('No flags in API response');
|
|
4437
|
+
}
|
|
4438
|
+
var flags = new Map();
|
|
4439
|
+
var pendingFirstTimeEvents = {};
|
|
4440
|
+
|
|
4441
|
+
// Process flags from response
|
|
4442
|
+
_.each(responseFlags, function(data, key) {
|
|
4443
|
+
// Check if this flag has any activated first-time events this session
|
|
4444
|
+
var hasActivatedEvent = false;
|
|
4445
|
+
var prefix = key + ':';
|
|
4446
|
+
_.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
|
|
4447
|
+
if (eventKey.startsWith(prefix)) {
|
|
4448
|
+
hasActivatedEvent = true;
|
|
4449
|
+
}
|
|
4450
|
+
});
|
|
4072
4451
|
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
}
|
|
4079
|
-
} else {
|
|
4080
|
-
// Use server's current variant
|
|
4081
|
-
flags.set(key, {
|
|
4082
|
-
'key': data['variant_key'],
|
|
4083
|
-
'value': data['variant_value'],
|
|
4084
|
-
'experiment_id': data['experiment_id'],
|
|
4085
|
-
'is_experiment_active': data['is_experiment_active'],
|
|
4086
|
-
'is_qa_tester': data['is_qa_tester']
|
|
4087
|
-
});
|
|
4452
|
+
if (hasActivatedEvent) {
|
|
4453
|
+
// Preserve the activated variant, don't overwrite with server's current variant
|
|
4454
|
+
var currentFlag = this.flags && this.flags.get(key);
|
|
4455
|
+
if (currentFlag) {
|
|
4456
|
+
flags.set(key, currentFlag);
|
|
4088
4457
|
}
|
|
4089
|
-
}
|
|
4458
|
+
} else {
|
|
4459
|
+
// Use server's current variant
|
|
4460
|
+
flags.set(key, {
|
|
4461
|
+
'key': data['variant_key'],
|
|
4462
|
+
'value': data['variant_value'],
|
|
4463
|
+
'experiment_id': data['experiment_id'],
|
|
4464
|
+
'is_experiment_active': data['is_experiment_active'],
|
|
4465
|
+
'is_qa_tester': data['is_qa_tester'],
|
|
4466
|
+
'variant_source': 'network'
|
|
4467
|
+
});
|
|
4468
|
+
}
|
|
4469
|
+
}, this);
|
|
4090
4470
|
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4471
|
+
// Process top-level pending_first_time_events array
|
|
4472
|
+
var topLevelDefinitions = responseBody['pending_first_time_events'];
|
|
4473
|
+
if (topLevelDefinitions && topLevelDefinitions.length > 0) {
|
|
4474
|
+
_.each(topLevelDefinitions, function(def) {
|
|
4475
|
+
var flagKey = def['flag_key'];
|
|
4476
|
+
var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
|
|
4097
4477
|
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4478
|
+
// Skip if this specific event has already been activated this session
|
|
4479
|
+
if (this.activatedFirstTimeEvents[eventKey]) {
|
|
4480
|
+
return;
|
|
4481
|
+
}
|
|
4102
4482
|
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4483
|
+
// Store pending event definition using composite key
|
|
4484
|
+
pendingFirstTimeEvents[eventKey] = {
|
|
4485
|
+
'flag_key': flagKey,
|
|
4486
|
+
'flag_id': def['flag_id'],
|
|
4487
|
+
'project_id': def['project_id'],
|
|
4488
|
+
'first_time_event_hash': def['first_time_event_hash'],
|
|
4489
|
+
'event_name': def['event_name'],
|
|
4490
|
+
'property_filters': def['property_filters'],
|
|
4491
|
+
'pending_variant': def['pending_variant']
|
|
4492
|
+
};
|
|
4493
|
+
}, this);
|
|
4494
|
+
}
|
|
4115
4495
|
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4496
|
+
// Preserve any activated orphaned flags (flags that were activated but are no longer in response)
|
|
4497
|
+
if (this.activatedFirstTimeEvents) {
|
|
4498
|
+
_.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
|
|
4499
|
+
var flagKey = getFlagKeyFromPendingEventKey(eventKey);
|
|
4500
|
+
if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
|
|
4501
|
+
// Keep the activated flag even though it's not in the new response
|
|
4502
|
+
flags.set(flagKey, this.flags.get(flagKey));
|
|
4503
|
+
}
|
|
4504
|
+
}, this);
|
|
4505
|
+
}
|
|
4126
4506
|
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4507
|
+
this.flags = flags;
|
|
4508
|
+
this.trackedFeatures = new Set();
|
|
4509
|
+
this.pendingFirstTimeEvents = pendingFirstTimeEvents;
|
|
4510
|
+
this._loadedPersistedAtMs = null;
|
|
4511
|
+
this._loadedTtlMs = null;
|
|
4512
|
+
this._traceparent = traceparent;
|
|
4130
4513
|
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
logger$4.error(error);
|
|
4135
|
-
}.bind(this));
|
|
4514
|
+
this._loadTargetingIfNeeded();
|
|
4515
|
+
|
|
4516
|
+
this.persistence.save(context, this.flags, this.pendingFirstTimeEvents);
|
|
4136
4517
|
}.bind(this)).catch(function(error) {
|
|
4137
|
-
this.
|
|
4518
|
+
if (this._fetchInProgressStartTime) {
|
|
4519
|
+
this.markFetchComplete();
|
|
4520
|
+
}
|
|
4138
4521
|
logger$4.error(error);
|
|
4522
|
+
throw error;
|
|
4139
4523
|
}.bind(this));
|
|
4140
4524
|
|
|
4141
4525
|
return this.fetchPromise;
|
|
4142
4526
|
};
|
|
4143
4527
|
|
|
4528
|
+
FeatureFlagManager.prototype.loadFlags = function() {
|
|
4529
|
+
if (!this.isSystemEnabled()) {
|
|
4530
|
+
return Promise.resolve();
|
|
4531
|
+
}
|
|
4532
|
+
if (!this.trackedFeatures) {
|
|
4533
|
+
logger$4.error('loadFlags called before init');
|
|
4534
|
+
return Promise.resolve();
|
|
4535
|
+
}
|
|
4536
|
+
if (this._fetchInProgressStartTime) {
|
|
4537
|
+
return this.fetchPromise;
|
|
4538
|
+
}
|
|
4539
|
+
return this.fetchFlags();
|
|
4540
|
+
};
|
|
4541
|
+
|
|
4144
4542
|
FeatureFlagManager.prototype.markFetchComplete = function() {
|
|
4145
4543
|
if (!this._fetchInProgressStartTime) {
|
|
4146
4544
|
logger$4.error('Fetch in progress started time not set, cannot mark fetch complete');
|
|
@@ -4275,6 +4673,7 @@
|
|
|
4275
4673
|
};
|
|
4276
4674
|
|
|
4277
4675
|
this.flags.set(flagKey, newVariant);
|
|
4676
|
+
this.trackedFeatures.delete(flagKey);
|
|
4278
4677
|
this.activatedFirstTimeEvents[eventKey] = true;
|
|
4279
4678
|
|
|
4280
4679
|
this.recordFirstTimeEvent(
|
|
@@ -4324,35 +4723,106 @@
|
|
|
4324
4723
|
};
|
|
4325
4724
|
|
|
4326
4725
|
FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
|
|
4327
|
-
if (!this.
|
|
4726
|
+
if (!this.persistenceLoadedPromise) {
|
|
4328
4727
|
return new Promise(function(resolve) {
|
|
4329
4728
|
logger$4.critical('Feature Flags not initialized');
|
|
4330
|
-
resolve(fallback);
|
|
4729
|
+
resolve(withFallbackSource(fallback));
|
|
4331
4730
|
});
|
|
4332
4731
|
}
|
|
4333
4732
|
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
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;
|
|
4340
4765
|
};
|
|
4341
4766
|
|
|
4342
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
|
+
}
|
|
4343
4772
|
if (!this.areFlagsReady()) {
|
|
4344
4773
|
logger$4.log('Flags not loaded yet');
|
|
4345
|
-
return fallback;
|
|
4774
|
+
return withFallbackSource(fallback);
|
|
4346
4775
|
}
|
|
4347
4776
|
var feature = this.flags.get(featureName);
|
|
4348
4777
|
if (!feature) {
|
|
4349
4778
|
logger$4.log('No flag found: "' + featureName + '"');
|
|
4350
|
-
return fallback;
|
|
4779
|
+
return withFallbackSource(fallback);
|
|
4351
4780
|
}
|
|
4352
4781
|
this.trackFeatureCheck(featureName, feature);
|
|
4353
4782
|
return feature;
|
|
4354
4783
|
};
|
|
4355
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
|
+
|
|
4356
4826
|
FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
|
|
4357
4827
|
return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
|
|
4358
4828
|
return feature['value'];
|
|
@@ -4391,6 +4861,10 @@
|
|
|
4391
4861
|
return val;
|
|
4392
4862
|
};
|
|
4393
4863
|
|
|
4864
|
+
function isPresent(v) {
|
|
4865
|
+
return v !== undefined && v !== null;
|
|
4866
|
+
}
|
|
4867
|
+
|
|
4394
4868
|
FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
|
|
4395
4869
|
if (this.trackedFeatures.has(featureName)) {
|
|
4396
4870
|
return;
|
|
@@ -4401,25 +4875,41 @@
|
|
|
4401
4875
|
'Experiment name': featureName,
|
|
4402
4876
|
'Variant name': feature['key'],
|
|
4403
4877
|
'$experiment_type': 'feature_flag',
|
|
4404
|
-
'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
|
|
4405
|
-
'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,
|
|
4406
4880
|
'Variant fetch latency (ms)': this._fetchLatency,
|
|
4407
4881
|
'Variant fetch traceparent': this._traceparent,
|
|
4408
4882
|
};
|
|
4409
4883
|
|
|
4410
|
-
if (feature['experiment_id']
|
|
4884
|
+
if (isPresent(feature['experiment_id'])) {
|
|
4411
4885
|
trackingProperties['$experiment_id'] = feature['experiment_id'];
|
|
4412
4886
|
}
|
|
4413
|
-
if (feature['is_experiment_active']
|
|
4887
|
+
if (isPresent(feature['is_experiment_active'])) {
|
|
4414
4888
|
trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
|
|
4415
4889
|
}
|
|
4416
|
-
if (feature['is_qa_tester']
|
|
4890
|
+
if (isPresent(feature['is_qa_tester'])) {
|
|
4417
4891
|
trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
|
|
4418
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
|
+
}
|
|
4419
4902
|
|
|
4420
4903
|
this.track('$experiment_started', trackingProperties);
|
|
4421
4904
|
};
|
|
4422
4905
|
|
|
4906
|
+
FeatureFlagManager.prototype.whenReady = function() {
|
|
4907
|
+
if (this.fetchPromise) {
|
|
4908
|
+
return this.fetchPromise;
|
|
4909
|
+
}
|
|
4910
|
+
return Promise.resolve();
|
|
4911
|
+
};
|
|
4912
|
+
|
|
4423
4913
|
FeatureFlagManager.prototype.minApisSupported = function() {
|
|
4424
4914
|
return !!this.fetch &&
|
|
4425
4915
|
typeof Promise !== 'undefined' &&
|
|
@@ -4432,11 +4922,15 @@
|
|
|
4432
4922
|
FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
|
|
4433
4923
|
FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
|
|
4434
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;
|
|
4435
4927
|
FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
|
|
4436
4928
|
FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
|
|
4437
4929
|
FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
|
|
4438
4930
|
FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
|
|
4931
|
+
FeatureFlagManager.prototype['load_flags'] = FeatureFlagManager.prototype.loadFlags;
|
|
4439
4932
|
FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
|
|
4933
|
+
FeatureFlagManager.prototype['when_ready'] = FeatureFlagManager.prototype.whenReady;
|
|
4440
4934
|
|
|
4441
4935
|
// Deprecated method
|
|
4442
4936
|
FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
|
|
@@ -4444,131 +4938,14 @@
|
|
|
4444
4938
|
// Exports intended only for testing
|
|
4445
4939
|
FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
|
|
4446
4940
|
|
|
4447
|
-
var
|
|
4448
|
-
|
|
4941
|
+
var MIXPANEL_BROWSER_DB_NAME = 'mixpanelBrowserDb';
|
|
4449
4942
|
var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
|
|
4450
4943
|
var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
|
|
4451
4944
|
|
|
4452
|
-
//
|
|
4453
|
-
var
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
/**
|
|
4457
|
-
* @type {import('./wrapper').StorageWrapper}
|
|
4458
|
-
*/
|
|
4459
|
-
var IDBStorageWrapper = function (storeName) {
|
|
4460
|
-
/**
|
|
4461
|
-
* @type {Promise<IDBDatabase>|null}
|
|
4462
|
-
*/
|
|
4463
|
-
this.dbPromise = null;
|
|
4464
|
-
this.storeName = storeName;
|
|
4465
|
-
};
|
|
4466
|
-
|
|
4467
|
-
IDBStorageWrapper.prototype._openDb = function () {
|
|
4468
|
-
return new PromisePolyfill(function (resolve, reject) {
|
|
4469
|
-
var openRequest = win.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
|
|
4470
|
-
openRequest['onerror'] = function () {
|
|
4471
|
-
reject(openRequest.error);
|
|
4472
|
-
};
|
|
4473
|
-
|
|
4474
|
-
openRequest['onsuccess'] = function () {
|
|
4475
|
-
resolve(openRequest.result);
|
|
4476
|
-
};
|
|
4477
|
-
|
|
4478
|
-
openRequest['onupgradeneeded'] = function (ev) {
|
|
4479
|
-
var db = ev.target.result;
|
|
4480
|
-
|
|
4481
|
-
OBJECT_STORES.forEach(function (storeName) {
|
|
4482
|
-
db.createObjectStore(storeName);
|
|
4483
|
-
});
|
|
4484
|
-
};
|
|
4485
|
-
});
|
|
4486
|
-
};
|
|
4487
|
-
|
|
4488
|
-
IDBStorageWrapper.prototype.init = function () {
|
|
4489
|
-
if (!win.indexedDB) {
|
|
4490
|
-
return PromisePolyfill.reject('indexedDB is not supported in this browser');
|
|
4491
|
-
}
|
|
4492
|
-
|
|
4493
|
-
if (!this.dbPromise) {
|
|
4494
|
-
this.dbPromise = this._openDb();
|
|
4495
|
-
}
|
|
4496
|
-
|
|
4497
|
-
return this.dbPromise
|
|
4498
|
-
.then(function (dbOrError) {
|
|
4499
|
-
if (dbOrError instanceof win['IDBDatabase']) {
|
|
4500
|
-
return PromisePolyfill.resolve();
|
|
4501
|
-
} else {
|
|
4502
|
-
return PromisePolyfill.reject(dbOrError);
|
|
4503
|
-
}
|
|
4504
|
-
});
|
|
4505
|
-
};
|
|
4506
|
-
|
|
4507
|
-
IDBStorageWrapper.prototype.isInitialized = function () {
|
|
4508
|
-
return !!this.dbPromise;
|
|
4509
|
-
};
|
|
4510
|
-
|
|
4511
|
-
/**
|
|
4512
|
-
* @param {IDBTransactionMode} mode
|
|
4513
|
-
* @param {function(IDBObjectStore): void} storeCb
|
|
4514
|
-
*/
|
|
4515
|
-
IDBStorageWrapper.prototype.makeTransaction = function (mode, storeCb) {
|
|
4516
|
-
var storeName = this.storeName;
|
|
4517
|
-
var doTransaction = function (db) {
|
|
4518
|
-
return new PromisePolyfill(function (resolve, reject) {
|
|
4519
|
-
var transaction = db.transaction(storeName, mode);
|
|
4520
|
-
transaction.oncomplete = function () {
|
|
4521
|
-
resolve(transaction);
|
|
4522
|
-
};
|
|
4523
|
-
transaction.onabort = transaction.onerror = function () {
|
|
4524
|
-
reject(transaction.error);
|
|
4525
|
-
};
|
|
4526
|
-
|
|
4527
|
-
storeCb(transaction.objectStore(storeName));
|
|
4528
|
-
});
|
|
4529
|
-
};
|
|
4530
|
-
|
|
4531
|
-
return this.dbPromise
|
|
4532
|
-
.then(doTransaction)
|
|
4533
|
-
.catch(function (err) {
|
|
4534
|
-
if (err && err['name'] === 'InvalidStateError') {
|
|
4535
|
-
// try reopening the DB if the connection is closed
|
|
4536
|
-
this.dbPromise = this._openDb();
|
|
4537
|
-
return this.dbPromise.then(doTransaction);
|
|
4538
|
-
} else {
|
|
4539
|
-
return PromisePolyfill.reject(err);
|
|
4540
|
-
}
|
|
4541
|
-
}.bind(this));
|
|
4542
|
-
};
|
|
4543
|
-
|
|
4544
|
-
IDBStorageWrapper.prototype.setItem = function (key, value) {
|
|
4545
|
-
return this.makeTransaction('readwrite', function (objectStore) {
|
|
4546
|
-
objectStore.put(value, key);
|
|
4547
|
-
});
|
|
4548
|
-
};
|
|
4549
|
-
|
|
4550
|
-
IDBStorageWrapper.prototype.getItem = function (key) {
|
|
4551
|
-
var req;
|
|
4552
|
-
return this.makeTransaction('readonly', function (objectStore) {
|
|
4553
|
-
req = objectStore.get(key);
|
|
4554
|
-
}).then(function () {
|
|
4555
|
-
return req.result;
|
|
4556
|
-
});
|
|
4557
|
-
};
|
|
4558
|
-
|
|
4559
|
-
IDBStorageWrapper.prototype.removeItem = function (key) {
|
|
4560
|
-
return this.makeTransaction('readwrite', function (objectStore) {
|
|
4561
|
-
objectStore.delete(key);
|
|
4562
|
-
});
|
|
4563
|
-
};
|
|
4564
|
-
|
|
4565
|
-
IDBStorageWrapper.prototype.getAll = function () {
|
|
4566
|
-
var req;
|
|
4567
|
-
return this.makeTransaction('readonly', function (objectStore) {
|
|
4568
|
-
req = objectStore.getAll();
|
|
4569
|
-
}).then(function () {
|
|
4570
|
-
return req.result;
|
|
4571
|
-
});
|
|
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]
|
|
4572
4949
|
};
|
|
4573
4950
|
|
|
4574
4951
|
/**
|
|
@@ -4641,7 +5018,7 @@
|
|
|
4641
5018
|
return PromisePolyfill.resolve(false);
|
|
4642
5019
|
}
|
|
4643
5020
|
|
|
4644
|
-
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);
|
|
4645
5022
|
var tab_id = this.getTabId();
|
|
4646
5023
|
return recording_registry_idb.init()
|
|
4647
5024
|
.then(function () {
|
|
@@ -5097,7 +5474,7 @@
|
|
|
5097
5474
|
options = options || {};
|
|
5098
5475
|
|
|
5099
5476
|
this.storageKey = key;
|
|
5100
|
-
this.storage = options.storage ||
|
|
5477
|
+
this.storage = options.storage || getLocalStorage();
|
|
5101
5478
|
this.pollIntervalMS = options.pollIntervalMS || 100;
|
|
5102
5479
|
this.timeoutMS = options.timeoutMS || 2000;
|
|
5103
5480
|
|
|
@@ -5225,10 +5602,13 @@
|
|
|
5225
5602
|
* @type {import('./wrapper').StorageWrapper}
|
|
5226
5603
|
*/
|
|
5227
5604
|
var LocalStorageWrapper = function (storageOverride) {
|
|
5228
|
-
this.storage = storageOverride ||
|
|
5605
|
+
this.storage = storageOverride || getLocalStorage();
|
|
5229
5606
|
};
|
|
5230
5607
|
|
|
5231
5608
|
LocalStorageWrapper.prototype.init = function () {
|
|
5609
|
+
if (!this.storage) {
|
|
5610
|
+
return PromisePolyfill.reject(new Error('localStorage is not available'));
|
|
5611
|
+
}
|
|
5232
5612
|
return PromisePolyfill.resolve();
|
|
5233
5613
|
};
|
|
5234
5614
|
|
|
@@ -5295,7 +5675,7 @@
|
|
|
5295
5675
|
if (this.usePersistence) {
|
|
5296
5676
|
this.queueStorage = options.queueStorage || new LocalStorageWrapper();
|
|
5297
5677
|
this.lock = new SharedLock(storageKey, {
|
|
5298
|
-
storage: options.sharedLockStorage
|
|
5678
|
+
storage: options.sharedLockStorage,
|
|
5299
5679
|
timeoutMS: options.sharedLockTimeoutMS,
|
|
5300
5680
|
});
|
|
5301
5681
|
}
|
|
@@ -7760,6 +8140,7 @@
|
|
|
7760
8140
|
'disable_all_events': false,
|
|
7761
8141
|
'identify_called': false
|
|
7762
8142
|
};
|
|
8143
|
+
this._remote_settings_strict_disabled = false;
|
|
7763
8144
|
|
|
7764
8145
|
// set up request queueing/batching
|
|
7765
8146
|
this.request_batchers = {};
|
|
@@ -7834,9 +8215,6 @@
|
|
|
7834
8215
|
this.flags.init();
|
|
7835
8216
|
this['flags'] = this.flags;
|
|
7836
8217
|
|
|
7837
|
-
this.autocapture = new Autocapture(this);
|
|
7838
|
-
this.autocapture.init();
|
|
7839
|
-
|
|
7840
8218
|
this._init_tab_id();
|
|
7841
8219
|
|
|
7842
8220
|
// Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
|
|
@@ -7848,6 +8226,9 @@
|
|
|
7848
8226
|
} else {
|
|
7849
8227
|
this.__session_recording_init_promise = this._check_and_start_session_recording();
|
|
7850
8228
|
}
|
|
8229
|
+
|
|
8230
|
+
this.autocapture = new Autocapture(this);
|
|
8231
|
+
this.autocapture.init();
|
|
7851
8232
|
};
|
|
7852
8233
|
|
|
7853
8234
|
/**
|
|
@@ -7894,9 +8275,19 @@
|
|
|
7894
8275
|
return this.recorderManager.checkAndStartSessionRecording(force_start);
|
|
7895
8276
|
});
|
|
7896
8277
|
|
|
7897
|
-
MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
|
|
7898
|
-
|
|
7899
|
-
|
|
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
|
+
});
|
|
7900
8291
|
|
|
7901
8292
|
MixpanelLib.prototype.start_session_recording = function () {
|
|
7902
8293
|
return this._check_and_start_session_recording(true);
|
|
@@ -8195,6 +8586,7 @@
|
|
|
8195
8586
|
var disableRecordingIfStrict = function() {
|
|
8196
8587
|
if (mode === 'strict') {
|
|
8197
8588
|
self.set_config({'record_sessions_percent': 0});
|
|
8589
|
+
self._remote_settings_strict_disabled = true;
|
|
8198
8590
|
}
|
|
8199
8591
|
};
|
|
8200
8592
|
|
|
@@ -8820,6 +9212,10 @@
|
|
|
8820
9212
|
properties
|
|
8821
9213
|
);
|
|
8822
9214
|
|
|
9215
|
+
if (this.is_recording_heatmap_data()) {
|
|
9216
|
+
event_properties['$captured_for_heatmap'] = true;
|
|
9217
|
+
}
|
|
9218
|
+
|
|
8823
9219
|
return this.track(event_name, event_properties);
|
|
8824
9220
|
});
|
|
8825
9221
|
|
|
@@ -9143,7 +9539,9 @@
|
|
|
9143
9539
|
|
|
9144
9540
|
// check feature flags again if distinct id has changed
|
|
9145
9541
|
if (new_distinct_id !== previous_distinct_id) {
|
|
9146
|
-
this.flags.fetchFlags()
|
|
9542
|
+
this.flags.fetchFlags().catch(function() {
|
|
9543
|
+
console.error('[flags] Error fetching flags during identify');
|
|
9544
|
+
});
|
|
9147
9545
|
}
|
|
9148
9546
|
};
|
|
9149
9547
|
|
|
@@ -9161,6 +9559,7 @@
|
|
|
9161
9559
|
'$device_id': uuid
|
|
9162
9560
|
}, '');
|
|
9163
9561
|
this._check_and_start_session_recording();
|
|
9562
|
+
this.flags.reset();
|
|
9164
9563
|
};
|
|
9165
9564
|
|
|
9166
9565
|
/**
|