mixpanel-browser 2.75.0 → 2.77.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 +14 -0
- package/.github/dependabot.yml +8 -0
- package/.github/workflows/integration-tests.yml +4 -4
- package/.github/workflows/unit-tests.yml +4 -4
- package/CHANGELOG.md +14 -0
- package/build.sh +10 -8
- package/dist/async-modules/mixpanel-recorder-DLKbUIEE.js +23669 -0
- package/dist/async-modules/mixpanel-recorder-wIWnMDLA.min.js +2 -0
- package/dist/async-modules/mixpanel-recorder-wIWnMDLA.min.js.map +1 -0
- package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js +2 -0
- package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js.map +1 -0
- package/dist/async-modules/mixpanel-targeting-CmVvUyFM.js +2520 -0
- package/dist/mixpanel-core.cjs.d.ts +70 -1
- package/dist/mixpanel-core.cjs.js +724 -426
- package/dist/mixpanel-recorder.js +791 -41
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-targeting.js +6 -62
- 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 +70 -1
- package/dist/mixpanel-with-async-modules.cjs.js +724 -426
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +70 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +724 -426
- package/dist/mixpanel-with-recorder.d.ts +70 -1
- package/dist/mixpanel-with-recorder.js +1471 -450
- package/dist/mixpanel-with-recorder.min.d.ts +70 -1
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +70 -1
- package/dist/mixpanel.amd.js +1473 -504
- package/dist/mixpanel.cjs.d.ts +70 -1
- package/dist/mixpanel.cjs.js +1473 -504
- package/dist/mixpanel.globals.js +724 -426
- package/dist/mixpanel.min.js +189 -182
- package/dist/mixpanel.module.d.ts +70 -1
- package/dist/mixpanel.module.js +1473 -504
- package/dist/mixpanel.umd.d.ts +70 -1
- package/dist/mixpanel.umd.js +1473 -504
- package/dist/rrweb-bundled.js +61 -9
- package/dist/rrweb-compiled.js +56 -9
- package/logo.svg +5 -0
- package/package.json +6 -4
- package/rollup.config.mjs +163 -46
- package/src/autocapture/index.js +10 -27
- package/src/config.js +9 -3
- package/src/flags/index.js +1 -2
- package/src/index.d.ts +70 -1
- package/src/mixpanel-core.js +77 -112
- package/src/recorder/index.js +1 -1
- package/src/recorder/recorder.js +5 -1
- package/src/recorder/rrweb-network-plugin.js +649 -0
- package/src/recorder/session-recording.js +36 -12
- package/src/recorder/utils.js +27 -1
- package/src/recorder-manager.js +324 -0
- package/src/request-batcher.js +1 -1
- package/src/targeting/event-matcher.js +2 -57
- package/src/targeting/index.js +1 -1
- package/src/targeting/loader.js +1 -1
- package/src/utils.js +13 -1
- package/testServer.js +69 -1
- package/src/globals.js +0 -14
- /package/src/loaders/{loader-module-with-async-recorder.d.ts → loader-module-with-async-modules.d.ts} +0 -0
package/dist/mixpanel.globals.js
CHANGED
|
@@ -3,9 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
var Config = {
|
|
5
5
|
DEBUG: false,
|
|
6
|
-
LIB_VERSION: '2.
|
|
6
|
+
LIB_VERSION: '2.77.0'
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
// Window global names for async modules
|
|
10
|
+
var TARGETING_GLOBAL_NAME = '__mp_targeting';
|
|
11
|
+
var RECORDER_GLOBAL_NAME = '__mp_recorder';
|
|
12
|
+
|
|
13
|
+
// Constants that are injected at build-time for the names of async modules.
|
|
14
|
+
var RECORDER_FILENAME = 'mixpanel-recorder-DLKbUIEE.js';
|
|
15
|
+
var TARGETING_FILENAME = 'mixpanel-targeting-CmVvUyFM.js';
|
|
16
|
+
|
|
9
17
|
// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
|
|
10
18
|
var win;
|
|
11
19
|
if (typeof(window) === 'undefined') {
|
|
@@ -2122,6 +2130,17 @@
|
|
|
2122
2130
|
|
|
2123
2131
|
var NOOP_FUNC = function () {};
|
|
2124
2132
|
|
|
2133
|
+
var urlMatchesRegexList = function (url, regexList) {
|
|
2134
|
+
var matches = false;
|
|
2135
|
+
for (var i = 0; i < regexList.length; i++) {
|
|
2136
|
+
if (url.match(regexList[i])) {
|
|
2137
|
+
matches = true;
|
|
2138
|
+
break;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
return matches;
|
|
2142
|
+
};
|
|
2143
|
+
|
|
2125
2144
|
var JSONStringify = null, JSONParse = null;
|
|
2126
2145
|
if (typeof JSON !== 'undefined') {
|
|
2127
2146
|
JSONStringify = JSON.stringify;
|
|
@@ -2144,25 +2163,6 @@
|
|
|
2144
2163
|
_['toArray'] = _.toArray;
|
|
2145
2164
|
_['NPO'] = NpoPromise;
|
|
2146
2165
|
|
|
2147
|
-
/**
|
|
2148
|
-
* @param {import('./session-recording').SerializedRecording} serializedRecording
|
|
2149
|
-
* @returns {boolean}
|
|
2150
|
-
*/
|
|
2151
|
-
var isRecordingExpired = function(serializedRecording) {
|
|
2152
|
-
var now = Date.now();
|
|
2153
|
-
return !serializedRecording || now > serializedRecording['maxExpires'] || now > serializedRecording['idleExpires'];
|
|
2154
|
-
};
|
|
2155
|
-
|
|
2156
|
-
/**
|
|
2157
|
-
* Shared global window property names used across modules
|
|
2158
|
-
*/
|
|
2159
|
-
|
|
2160
|
-
// Targeting library global (used by flags and targeting modules)
|
|
2161
|
-
var TARGETING_GLOBAL_NAME = '__mp_targeting';
|
|
2162
|
-
|
|
2163
|
-
// Recorder library global (used by recorder and mixpanel-core)
|
|
2164
|
-
var RECORDER_GLOBAL_NAME = '__mp_recorder';
|
|
2165
|
-
|
|
2166
2166
|
// stateless utils
|
|
2167
2167
|
// mostly from https://github.com/mixpanel/mixpanel-js/blob/989ada50f518edab47b9c4fd9535f9fbd5ec5fc0/src/autotrack-utils.js
|
|
2168
2168
|
|
|
@@ -2268,7 +2268,7 @@
|
|
|
2268
2268
|
|
|
2269
2269
|
var MAX_DEPTH = 5;
|
|
2270
2270
|
|
|
2271
|
-
var logger$
|
|
2271
|
+
var logger$5 = console_with_prefix('autocapture');
|
|
2272
2272
|
|
|
2273
2273
|
|
|
2274
2274
|
function getClasses(el) {
|
|
@@ -2532,7 +2532,7 @@
|
|
|
2532
2532
|
return false;
|
|
2533
2533
|
}
|
|
2534
2534
|
} catch (err) {
|
|
2535
|
-
logger$
|
|
2535
|
+
logger$5.critical('Error while checking element in allowElementCallback', err);
|
|
2536
2536
|
return false;
|
|
2537
2537
|
}
|
|
2538
2538
|
}
|
|
@@ -2549,7 +2549,7 @@
|
|
|
2549
2549
|
return true;
|
|
2550
2550
|
}
|
|
2551
2551
|
} catch (err) {
|
|
2552
|
-
logger$
|
|
2552
|
+
logger$5.critical('Error while checking selector: ' + sel, err);
|
|
2553
2553
|
}
|
|
2554
2554
|
}
|
|
2555
2555
|
return false;
|
|
@@ -2564,7 +2564,7 @@
|
|
|
2564
2564
|
return true;
|
|
2565
2565
|
}
|
|
2566
2566
|
} catch (err) {
|
|
2567
|
-
logger$
|
|
2567
|
+
logger$5.critical('Error while checking element in blockElementCallback', err);
|
|
2568
2568
|
return true;
|
|
2569
2569
|
}
|
|
2570
2570
|
}
|
|
@@ -2578,7 +2578,7 @@
|
|
|
2578
2578
|
return true;
|
|
2579
2579
|
}
|
|
2580
2580
|
} catch (err) {
|
|
2581
|
-
logger$
|
|
2581
|
+
logger$5.critical('Error while checking selector: ' + sel, err);
|
|
2582
2582
|
}
|
|
2583
2583
|
}
|
|
2584
2584
|
}
|
|
@@ -3042,7 +3042,7 @@
|
|
|
3042
3042
|
observer.observe(shadowRoot, this.observerConfig);
|
|
3043
3043
|
this.shadowObservers.push(observer);
|
|
3044
3044
|
} catch (e) {
|
|
3045
|
-
logger$
|
|
3045
|
+
logger$5.critical('Error while observing shadow root', e);
|
|
3046
3046
|
}
|
|
3047
3047
|
};
|
|
3048
3048
|
|
|
@@ -3053,7 +3053,7 @@
|
|
|
3053
3053
|
}
|
|
3054
3054
|
|
|
3055
3055
|
if (!weakSetSupported()) {
|
|
3056
|
-
logger$
|
|
3056
|
+
logger$5.critical('Shadow DOM observation unavailable: WeakSet not supported');
|
|
3057
3057
|
return;
|
|
3058
3058
|
}
|
|
3059
3059
|
|
|
@@ -3069,7 +3069,7 @@
|
|
|
3069
3069
|
try {
|
|
3070
3070
|
this.shadowObservers[i].disconnect();
|
|
3071
3071
|
} catch (e) {
|
|
3072
|
-
logger$
|
|
3072
|
+
logger$5.critical('Error while disconnecting shadow DOM observer', e);
|
|
3073
3073
|
}
|
|
3074
3074
|
}
|
|
3075
3075
|
this.shadowObservers = [];
|
|
@@ -3257,7 +3257,7 @@
|
|
|
3257
3257
|
|
|
3258
3258
|
this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
|
|
3259
3259
|
} catch (e) {
|
|
3260
|
-
logger$
|
|
3260
|
+
logger$5.critical('Error while setting up mutation observer', e);
|
|
3261
3261
|
}
|
|
3262
3262
|
}
|
|
3263
3263
|
|
|
@@ -3272,7 +3272,7 @@
|
|
|
3272
3272
|
);
|
|
3273
3273
|
this.shadowDOMObserver.start();
|
|
3274
3274
|
} catch (e) {
|
|
3275
|
-
logger$
|
|
3275
|
+
logger$5.critical('Error while setting up shadow DOM observer', e);
|
|
3276
3276
|
this.shadowDOMObserver = null;
|
|
3277
3277
|
}
|
|
3278
3278
|
}
|
|
@@ -3299,7 +3299,7 @@
|
|
|
3299
3299
|
try {
|
|
3300
3300
|
listener.target.removeEventListener(listener.event, listener.handler, listener.options);
|
|
3301
3301
|
} catch (e) {
|
|
3302
|
-
logger$
|
|
3302
|
+
logger$5.critical('Error while removing event listener', e);
|
|
3303
3303
|
}
|
|
3304
3304
|
}
|
|
3305
3305
|
this.eventListeners = [];
|
|
@@ -3308,7 +3308,7 @@
|
|
|
3308
3308
|
try {
|
|
3309
3309
|
this.mutationObserver.disconnect();
|
|
3310
3310
|
} catch (e) {
|
|
3311
|
-
logger$
|
|
3311
|
+
logger$5.critical('Error while disconnecting mutation observer', e);
|
|
3312
3312
|
}
|
|
3313
3313
|
this.mutationObserver = null;
|
|
3314
3314
|
}
|
|
@@ -3317,7 +3317,7 @@
|
|
|
3317
3317
|
try {
|
|
3318
3318
|
this.shadowDOMObserver.stop();
|
|
3319
3319
|
} catch (e) {
|
|
3320
|
-
logger$
|
|
3320
|
+
logger$5.critical('Error while stopping shadow DOM observer', e);
|
|
3321
3321
|
}
|
|
3322
3322
|
this.shadowDOMObserver = null;
|
|
3323
3323
|
}
|
|
@@ -3395,7 +3395,7 @@
|
|
|
3395
3395
|
|
|
3396
3396
|
Autocapture.prototype.init = function() {
|
|
3397
3397
|
if (!minDOMApisSupported()) {
|
|
3398
|
-
logger$
|
|
3398
|
+
logger$5.critical('Autocapture unavailable: missing required DOM APIs');
|
|
3399
3399
|
return;
|
|
3400
3400
|
}
|
|
3401
3401
|
this.initPageListeners();
|
|
@@ -3427,27 +3427,15 @@
|
|
|
3427
3427
|
};
|
|
3428
3428
|
|
|
3429
3429
|
Autocapture.prototype.currentUrlBlocked = function() {
|
|
3430
|
-
var i;
|
|
3431
3430
|
var currentUrl = _.info.currentUrl();
|
|
3432
3431
|
|
|
3433
3432
|
var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
|
|
3434
3433
|
if (allowUrlRegexes.length) {
|
|
3435
3434
|
// we're using an allowlist, only track if current URL matches
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
if (currentUrl.match(allowRegex)) {
|
|
3441
|
-
allowed = true;
|
|
3442
|
-
break;
|
|
3443
|
-
}
|
|
3444
|
-
} catch (err) {
|
|
3445
|
-
logger$4.critical('Error while checking block URL regex: ' + allowRegex, err);
|
|
3446
|
-
return true;
|
|
3447
|
-
}
|
|
3448
|
-
}
|
|
3449
|
-
if (!allowed) {
|
|
3450
|
-
// wasn't allowed by any regex
|
|
3435
|
+
try {
|
|
3436
|
+
return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
|
|
3437
|
+
} catch (err) {
|
|
3438
|
+
logger$5.critical('Error while checking block URL regexes: ', err);
|
|
3451
3439
|
return true;
|
|
3452
3440
|
}
|
|
3453
3441
|
}
|
|
@@ -3457,17 +3445,12 @@
|
|
|
3457
3445
|
return false;
|
|
3458
3446
|
}
|
|
3459
3447
|
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
} catch (err) {
|
|
3466
|
-
logger$4.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
|
|
3467
|
-
return true;
|
|
3468
|
-
}
|
|
3448
|
+
try {
|
|
3449
|
+
return urlMatchesRegexList(currentUrl, blockUrlRegexes);
|
|
3450
|
+
} catch (err) {
|
|
3451
|
+
logger$5.critical('Error while checking block URL regexes: ', err);
|
|
3452
|
+
return true;
|
|
3469
3453
|
}
|
|
3470
|
-
return false;
|
|
3471
3454
|
};
|
|
3472
3455
|
|
|
3473
3456
|
Autocapture.prototype.pageviewTrackingConfig = function() {
|
|
@@ -3603,7 +3586,7 @@
|
|
|
3603
3586
|
return;
|
|
3604
3587
|
}
|
|
3605
3588
|
|
|
3606
|
-
logger$
|
|
3589
|
+
logger$5.log('Initializing scroll depth tracking');
|
|
3607
3590
|
|
|
3608
3591
|
this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
|
|
3609
3592
|
|
|
@@ -3629,7 +3612,7 @@
|
|
|
3629
3612
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
|
|
3630
3613
|
return;
|
|
3631
3614
|
}
|
|
3632
|
-
logger$
|
|
3615
|
+
logger$5.log('Initializing click tracking');
|
|
3633
3616
|
|
|
3634
3617
|
this.listenerClick = function(ev) {
|
|
3635
3618
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
|
|
@@ -3648,7 +3631,7 @@
|
|
|
3648
3631
|
return;
|
|
3649
3632
|
}
|
|
3650
3633
|
|
|
3651
|
-
logger$
|
|
3634
|
+
logger$5.log('Initializing dead click tracking');
|
|
3652
3635
|
if (!this._deadClickTracker) {
|
|
3653
3636
|
this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
|
|
3654
3637
|
this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
|
|
@@ -3682,7 +3665,7 @@
|
|
|
3682
3665
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
3683
3666
|
return;
|
|
3684
3667
|
}
|
|
3685
|
-
logger$
|
|
3668
|
+
logger$5.log('Initializing input tracking');
|
|
3686
3669
|
|
|
3687
3670
|
this.listenerChange = function(ev) {
|
|
3688
3671
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
@@ -3699,7 +3682,7 @@
|
|
|
3699
3682
|
if (!this.pageviewTrackingConfig()) {
|
|
3700
3683
|
return;
|
|
3701
3684
|
}
|
|
3702
|
-
logger$
|
|
3685
|
+
logger$5.log('Initializing pageview tracking');
|
|
3703
3686
|
|
|
3704
3687
|
var previousTrackedUrl = '';
|
|
3705
3688
|
var tracked = false;
|
|
@@ -3734,7 +3717,7 @@
|
|
|
3734
3717
|
}
|
|
3735
3718
|
if (didPathChange) {
|
|
3736
3719
|
this.lastScrollCheckpoint = 0;
|
|
3737
|
-
logger$
|
|
3720
|
+
logger$5.log('Path change: re-initializing scroll depth checkpoints');
|
|
3738
3721
|
}
|
|
3739
3722
|
}
|
|
3740
3723
|
}.bind(this));
|
|
@@ -3749,7 +3732,7 @@
|
|
|
3749
3732
|
return;
|
|
3750
3733
|
}
|
|
3751
3734
|
|
|
3752
|
-
logger$
|
|
3735
|
+
logger$5.log('Initializing rage click tracking');
|
|
3753
3736
|
if (!this._rageClickTracker) {
|
|
3754
3737
|
this._rageClickTracker = new RageClickTracker();
|
|
3755
3738
|
}
|
|
@@ -3779,7 +3762,7 @@
|
|
|
3779
3762
|
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
3780
3763
|
return;
|
|
3781
3764
|
}
|
|
3782
|
-
logger$
|
|
3765
|
+
logger$5.log('Initializing scroll tracking');
|
|
3783
3766
|
this.lastScrollCheckpoint = 0;
|
|
3784
3767
|
|
|
3785
3768
|
var scrollTrackFunction = function() {
|
|
@@ -3816,7 +3799,7 @@
|
|
|
3816
3799
|
}
|
|
3817
3800
|
}
|
|
3818
3801
|
} catch (err) {
|
|
3819
|
-
logger$
|
|
3802
|
+
logger$5.critical('Error while calculating scroll percentage', err);
|
|
3820
3803
|
}
|
|
3821
3804
|
if (shouldTrack) {
|
|
3822
3805
|
this.mp.track(MP_EV_SCROLL, props);
|
|
@@ -3834,7 +3817,7 @@
|
|
|
3834
3817
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
3835
3818
|
return;
|
|
3836
3819
|
}
|
|
3837
|
-
logger$
|
|
3820
|
+
logger$5.log('Initializing submit tracking');
|
|
3838
3821
|
|
|
3839
3822
|
this.listenerSubmit = function(ev) {
|
|
3840
3823
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
@@ -3856,7 +3839,7 @@
|
|
|
3856
3839
|
return;
|
|
3857
3840
|
}
|
|
3858
3841
|
|
|
3859
|
-
logger$
|
|
3842
|
+
logger$5.log('Initializing page visibility tracking.');
|
|
3860
3843
|
this._initScrollDepthTracking();
|
|
3861
3844
|
var previousTrackedUrl = _.info.currentUrl();
|
|
3862
3845
|
|
|
@@ -3941,7 +3924,7 @@
|
|
|
3941
3924
|
return win[TARGETING_GLOBAL_NAME];
|
|
3942
3925
|
};
|
|
3943
3926
|
|
|
3944
|
-
var logger$
|
|
3927
|
+
var logger$4 = console_with_prefix('flags');
|
|
3945
3928
|
var FLAGS_CONFIG_KEY = 'flags';
|
|
3946
3929
|
|
|
3947
3930
|
var CONFIG_CONTEXT = 'context';
|
|
@@ -3984,7 +3967,7 @@
|
|
|
3984
3967
|
|
|
3985
3968
|
FeatureFlagManager.prototype.init = function() {
|
|
3986
3969
|
if (!this.minApisSupported()) {
|
|
3987
|
-
logger$
|
|
3970
|
+
logger$4.critical('Feature Flags unavailable: missing minimum required APIs');
|
|
3988
3971
|
return;
|
|
3989
3972
|
}
|
|
3990
3973
|
|
|
@@ -4019,7 +4002,7 @@
|
|
|
4019
4002
|
|
|
4020
4003
|
FeatureFlagManager.prototype.updateContext = function(newContext, options) {
|
|
4021
4004
|
if (!this.isSystemEnabled()) {
|
|
4022
|
-
logger$
|
|
4005
|
+
logger$4.critical('Feature Flags not enabled, cannot update context');
|
|
4023
4006
|
return Promise.resolve();
|
|
4024
4007
|
}
|
|
4025
4008
|
|
|
@@ -4036,7 +4019,7 @@
|
|
|
4036
4019
|
|
|
4037
4020
|
FeatureFlagManager.prototype.areFlagsReady = function() {
|
|
4038
4021
|
if (!this.isSystemEnabled()) {
|
|
4039
|
-
logger$
|
|
4022
|
+
logger$4.error('Feature Flags not enabled');
|
|
4040
4023
|
}
|
|
4041
4024
|
return !!this.flags;
|
|
4042
4025
|
};
|
|
@@ -4049,7 +4032,7 @@
|
|
|
4049
4032
|
var distinctId = this.getMpProperty('distinct_id');
|
|
4050
4033
|
var deviceId = this.getMpProperty('$device_id');
|
|
4051
4034
|
var traceparent = generateTraceparent();
|
|
4052
|
-
logger$
|
|
4035
|
+
logger$4.log('Fetching flags for distinct ID: ' + distinctId);
|
|
4053
4036
|
|
|
4054
4037
|
var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
|
|
4055
4038
|
var searchParams = new URLSearchParams();
|
|
@@ -4148,11 +4131,11 @@
|
|
|
4148
4131
|
this._loadTargetingIfNeeded();
|
|
4149
4132
|
}.bind(this)).catch(function(error) {
|
|
4150
4133
|
this.markFetchComplete();
|
|
4151
|
-
logger$
|
|
4134
|
+
logger$4.error(error);
|
|
4152
4135
|
}.bind(this));
|
|
4153
4136
|
}.bind(this)).catch(function(error) {
|
|
4154
4137
|
this.markFetchComplete();
|
|
4155
|
-
logger$
|
|
4138
|
+
logger$4.error(error);
|
|
4156
4139
|
}.bind(this));
|
|
4157
4140
|
|
|
4158
4141
|
return this.fetchPromise;
|
|
@@ -4160,7 +4143,7 @@
|
|
|
4160
4143
|
|
|
4161
4144
|
FeatureFlagManager.prototype.markFetchComplete = function() {
|
|
4162
4145
|
if (!this._fetchInProgressStartTime) {
|
|
4163
|
-
logger$
|
|
4146
|
+
logger$4.error('Fetch in progress started time not set, cannot mark fetch complete');
|
|
4164
4147
|
return;
|
|
4165
4148
|
}
|
|
4166
4149
|
this._fetchStartTime = this._fetchInProgressStartTime;
|
|
@@ -4182,7 +4165,7 @@
|
|
|
4182
4165
|
|
|
4183
4166
|
if (hasPropertyFilters) {
|
|
4184
4167
|
this.getTargeting().then(function() {
|
|
4185
|
-
logger$
|
|
4168
|
+
logger$4.log('targeting loaded for property filter evaluation');
|
|
4186
4169
|
});
|
|
4187
4170
|
}
|
|
4188
4171
|
};
|
|
@@ -4197,7 +4180,7 @@
|
|
|
4197
4180
|
this.loadExtraBundle.bind(this),
|
|
4198
4181
|
this.targetingSrc
|
|
4199
4182
|
).catch(function(error) {
|
|
4200
|
-
logger$
|
|
4183
|
+
logger$4.error('Failed to load targeting: ' + error);
|
|
4201
4184
|
}.bind(this));
|
|
4202
4185
|
};
|
|
4203
4186
|
|
|
@@ -4251,7 +4234,7 @@
|
|
|
4251
4234
|
|
|
4252
4235
|
// If no targeting library and event has property filters, skip it
|
|
4253
4236
|
if (!targeting && pendingEvent['property_filters'] && !_.isEmptyObject(pendingEvent['property_filters'])) {
|
|
4254
|
-
logger$
|
|
4237
|
+
logger$4.warn('Skipping event check for "' + flagKey + '" - property filters require targeting library');
|
|
4255
4238
|
return;
|
|
4256
4239
|
}
|
|
4257
4240
|
|
|
@@ -4274,7 +4257,7 @@
|
|
|
4274
4257
|
}
|
|
4275
4258
|
|
|
4276
4259
|
if (matchResult.error) {
|
|
4277
|
-
logger$
|
|
4260
|
+
logger$4.error('Error checking first-time event for flag "' + flagKey + '": ' + matchResult.error);
|
|
4278
4261
|
return;
|
|
4279
4262
|
}
|
|
4280
4263
|
|
|
@@ -4282,7 +4265,7 @@
|
|
|
4282
4265
|
return;
|
|
4283
4266
|
}
|
|
4284
4267
|
|
|
4285
|
-
logger$
|
|
4268
|
+
logger$4.log('First-time event matched for flag "' + flagKey + '": ' + eventName);
|
|
4286
4269
|
|
|
4287
4270
|
var newVariant = {
|
|
4288
4271
|
'key': pendingEvent['pending_variant']['variant_key'],
|
|
@@ -4323,7 +4306,7 @@
|
|
|
4323
4306
|
'first_time_event_hash': firstTimeEventHash
|
|
4324
4307
|
};
|
|
4325
4308
|
|
|
4326
|
-
logger$
|
|
4309
|
+
logger$4.log('Recording first-time event for flag: ' + flagId);
|
|
4327
4310
|
|
|
4328
4311
|
// Fire-and-forget POST request
|
|
4329
4312
|
this.fetch.call(win, url, {
|
|
@@ -4336,14 +4319,14 @@
|
|
|
4336
4319
|
'body': JSON.stringify(payload)
|
|
4337
4320
|
}).catch(function(error) {
|
|
4338
4321
|
// Silent failure - cohort sync will catch up
|
|
4339
|
-
logger$
|
|
4322
|
+
logger$4.error('Failed to record first-time event for flag ' + flagId + ': ' + error);
|
|
4340
4323
|
});
|
|
4341
4324
|
};
|
|
4342
4325
|
|
|
4343
4326
|
FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
|
|
4344
4327
|
if (!this.fetchPromise) {
|
|
4345
4328
|
return new Promise(function(resolve) {
|
|
4346
|
-
logger$
|
|
4329
|
+
logger$4.critical('Feature Flags not initialized');
|
|
4347
4330
|
resolve(fallback);
|
|
4348
4331
|
});
|
|
4349
4332
|
}
|
|
@@ -4351,19 +4334,19 @@
|
|
|
4351
4334
|
return this.fetchPromise.then(function() {
|
|
4352
4335
|
return this.getVariantSync(featureName, fallback);
|
|
4353
4336
|
}.bind(this)).catch(function(error) {
|
|
4354
|
-
logger$
|
|
4337
|
+
logger$4.error(error);
|
|
4355
4338
|
return fallback;
|
|
4356
4339
|
});
|
|
4357
4340
|
};
|
|
4358
4341
|
|
|
4359
4342
|
FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
|
|
4360
4343
|
if (!this.areFlagsReady()) {
|
|
4361
|
-
logger$
|
|
4344
|
+
logger$4.log('Flags not loaded yet');
|
|
4362
4345
|
return fallback;
|
|
4363
4346
|
}
|
|
4364
4347
|
var feature = this.flags.get(featureName);
|
|
4365
4348
|
if (!feature) {
|
|
4366
|
-
logger$
|
|
4349
|
+
logger$4.log('No flag found: "' + featureName + '"');
|
|
4367
4350
|
return fallback;
|
|
4368
4351
|
}
|
|
4369
4352
|
this.trackFeatureCheck(featureName, feature);
|
|
@@ -4374,14 +4357,14 @@
|
|
|
4374
4357
|
return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
|
|
4375
4358
|
return feature['value'];
|
|
4376
4359
|
}).catch(function(error) {
|
|
4377
|
-
logger$
|
|
4360
|
+
logger$4.error(error);
|
|
4378
4361
|
return fallbackValue;
|
|
4379
4362
|
});
|
|
4380
4363
|
};
|
|
4381
4364
|
|
|
4382
4365
|
// TODO remove deprecated method
|
|
4383
4366
|
FeatureFlagManager.prototype.getFeatureData = function(featureName, fallbackValue) {
|
|
4384
|
-
logger$
|
|
4367
|
+
logger$4.critical('mixpanel.flags.get_feature_data() is deprecated and will be removed in a future release. Use mixpanel.flags.get_variant_value() instead.');
|
|
4385
4368
|
return this.getVariantValue(featureName, fallbackValue);
|
|
4386
4369
|
};
|
|
4387
4370
|
|
|
@@ -4393,7 +4376,7 @@
|
|
|
4393
4376
|
return this.getVariantValue(featureName).then(function() {
|
|
4394
4377
|
return this.isEnabledSync(featureName, fallbackValue);
|
|
4395
4378
|
}.bind(this)).catch(function(error) {
|
|
4396
|
-
logger$
|
|
4379
|
+
logger$4.error(error);
|
|
4397
4380
|
return fallbackValue;
|
|
4398
4381
|
});
|
|
4399
4382
|
};
|
|
@@ -4402,7 +4385,7 @@
|
|
|
4402
4385
|
fallbackValue = fallbackValue || false;
|
|
4403
4386
|
var val = this.getVariantValueSync(featureName, fallbackValue);
|
|
4404
4387
|
if (val !== true && val !== false) {
|
|
4405
|
-
logger$
|
|
4388
|
+
logger$4.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
|
|
4406
4389
|
val = fallbackValue;
|
|
4407
4390
|
}
|
|
4408
4391
|
return val;
|
|
@@ -4461,143 +4444,618 @@
|
|
|
4461
4444
|
// Exports intended only for testing
|
|
4462
4445
|
FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
|
|
4463
4446
|
|
|
4464
|
-
|
|
4447
|
+
var MIXPANEL_DB_NAME = 'mixpanelBrowserDb';
|
|
4448
|
+
|
|
4449
|
+
var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
|
|
4450
|
+
var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
|
|
4465
4451
|
|
|
4452
|
+
// note: increment the version number when adding new object stores
|
|
4453
|
+
var DB_VERSION = 1;
|
|
4454
|
+
var OBJECT_STORES = [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME];
|
|
4466
4455
|
|
|
4467
4456
|
/**
|
|
4468
|
-
*
|
|
4469
|
-
* @constructor
|
|
4457
|
+
* @type {import('./wrapper').StorageWrapper}
|
|
4470
4458
|
*/
|
|
4471
|
-
var
|
|
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
|
+
};
|
|
4472
4473
|
|
|
4474
|
+
openRequest['onsuccess'] = function () {
|
|
4475
|
+
resolve(openRequest.result);
|
|
4476
|
+
};
|
|
4473
4477
|
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
DomTracker.prototype.event_handler = function() {};
|
|
4477
|
-
DomTracker.prototype.after_track_handler = function() {};
|
|
4478
|
+
openRequest['onupgradeneeded'] = function (ev) {
|
|
4479
|
+
var db = ev.target.result;
|
|
4478
4480
|
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4481
|
+
OBJECT_STORES.forEach(function (storeName) {
|
|
4482
|
+
db.createObjectStore(storeName);
|
|
4483
|
+
});
|
|
4484
|
+
};
|
|
4485
|
+
});
|
|
4482
4486
|
};
|
|
4483
4487
|
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
* @param {Object=} properties
|
|
4488
|
-
* @param {function=} user_callback
|
|
4489
|
-
*/
|
|
4490
|
-
DomTracker.prototype.track = function(query, event_name, properties, user_callback) {
|
|
4491
|
-
var that = this;
|
|
4492
|
-
var elements = _.dom_query(query);
|
|
4493
|
-
|
|
4494
|
-
if (elements.length === 0) {
|
|
4495
|
-
console.error('The DOM query (' + query + ') returned 0 elements');
|
|
4496
|
-
return;
|
|
4488
|
+
IDBStorageWrapper.prototype.init = function () {
|
|
4489
|
+
if (!win.indexedDB) {
|
|
4490
|
+
return PromisePolyfill.reject('indexedDB is not supported in this browser');
|
|
4497
4491
|
}
|
|
4498
4492
|
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
var props = that.create_properties(properties, this);
|
|
4503
|
-
var timeout = that.mp.get_config('track_links_timeout');
|
|
4504
|
-
|
|
4505
|
-
that.event_handler(e, this, options);
|
|
4506
|
-
|
|
4507
|
-
// in case the mixpanel servers don't get back to us in time
|
|
4508
|
-
window.setTimeout(that.track_callback(user_callback, props, options, true), timeout);
|
|
4493
|
+
if (!this.dbPromise) {
|
|
4494
|
+
this.dbPromise = this._openDb();
|
|
4495
|
+
}
|
|
4509
4496
|
|
|
4510
|
-
|
|
4511
|
-
|
|
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
|
+
}
|
|
4512
4504
|
});
|
|
4513
|
-
|
|
4505
|
+
};
|
|
4514
4506
|
|
|
4515
|
-
|
|
4507
|
+
IDBStorageWrapper.prototype.isInitialized = function () {
|
|
4508
|
+
return !!this.dbPromise;
|
|
4516
4509
|
};
|
|
4517
4510
|
|
|
4518
4511
|
/**
|
|
4519
|
-
* @param {
|
|
4520
|
-
* @param {
|
|
4521
|
-
* @param {boolean=} timeout_occured
|
|
4512
|
+
* @param {IDBTransactionMode} mode
|
|
4513
|
+
* @param {function(IDBObjectStore): void} storeCb
|
|
4522
4514
|
*/
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
var
|
|
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
4526
|
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
if (options.callback_fired) { return; }
|
|
4531
|
-
options.callback_fired = true;
|
|
4527
|
+
storeCb(transaction.objectStore(storeName));
|
|
4528
|
+
});
|
|
4529
|
+
};
|
|
4532
4530
|
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
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
|
+
};
|
|
4538
4543
|
|
|
4539
|
-
|
|
4540
|
-
|
|
4544
|
+
IDBStorageWrapper.prototype.setItem = function (key, value) {
|
|
4545
|
+
return this.makeTransaction('readwrite', function (objectStore) {
|
|
4546
|
+
objectStore.put(value, key);
|
|
4547
|
+
});
|
|
4541
4548
|
};
|
|
4542
4549
|
|
|
4543
|
-
|
|
4544
|
-
var
|
|
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
|
+
};
|
|
4545
4558
|
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4559
|
+
IDBStorageWrapper.prototype.removeItem = function (key) {
|
|
4560
|
+
return this.makeTransaction('readwrite', function (objectStore) {
|
|
4561
|
+
objectStore.delete(key);
|
|
4562
|
+
});
|
|
4563
|
+
};
|
|
4551
4564
|
|
|
4552
|
-
|
|
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
|
+
});
|
|
4553
4572
|
};
|
|
4554
4573
|
|
|
4555
4574
|
/**
|
|
4556
|
-
*
|
|
4557
|
-
* @
|
|
4558
|
-
* @extends DomTracker
|
|
4575
|
+
* @param {import('./session-recording').SerializedRecording} serializedRecording
|
|
4576
|
+
* @returns {boolean}
|
|
4559
4577
|
*/
|
|
4560
|
-
var
|
|
4561
|
-
|
|
4578
|
+
var isRecordingExpired = function(serializedRecording) {
|
|
4579
|
+
var now = Date.now();
|
|
4580
|
+
return !serializedRecording || now > serializedRecording['maxExpires'] || now > serializedRecording['idleExpires'];
|
|
4562
4581
|
};
|
|
4563
|
-
_.inherit(LinkTracker, DomTracker);
|
|
4564
|
-
|
|
4565
|
-
LinkTracker.prototype.create_properties = function(properties, element) {
|
|
4566
|
-
var props = LinkTracker.superclass.create_properties.apply(this, arguments);
|
|
4567
|
-
|
|
4568
|
-
if (element.href) { props['url'] = element.href; }
|
|
4569
4582
|
|
|
4570
|
-
|
|
4583
|
+
var validateAllowedOrigins = function(origins, logger) {
|
|
4584
|
+
if (!_.isArray(origins)) {
|
|
4585
|
+
if (origins) {
|
|
4586
|
+
logger.critical('record_allowed_iframe_origins must be an array of origin strings, cross-origin recording will be disabled.');
|
|
4587
|
+
}
|
|
4588
|
+
return [];
|
|
4589
|
+
}
|
|
4590
|
+
var valid = [];
|
|
4591
|
+
for (var i = 0; i < origins.length; i++) {
|
|
4592
|
+
try {
|
|
4593
|
+
var origin = new URL(origins[i]).origin;
|
|
4594
|
+
if (origin === 'null') {
|
|
4595
|
+
logger.critical(origins[i] + ' has an opaque origin. Skipping this entry.');
|
|
4596
|
+
continue;
|
|
4597
|
+
}
|
|
4598
|
+
valid.push(origin);
|
|
4599
|
+
} catch (e) {
|
|
4600
|
+
logger.critical(origins[i] + ' is not a valid origin URL. Skipping this entry.');
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
return valid;
|
|
4571
4604
|
};
|
|
4572
4605
|
|
|
4573
|
-
|
|
4574
|
-
options.new_tab = (
|
|
4575
|
-
evt.which === 2 ||
|
|
4576
|
-
evt.metaKey ||
|
|
4577
|
-
evt.ctrlKey ||
|
|
4578
|
-
element.target === '_blank'
|
|
4579
|
-
);
|
|
4580
|
-
options.href = element.href;
|
|
4606
|
+
/* eslint camelcase: "off" */
|
|
4581
4607
|
|
|
4582
|
-
if (!options.new_tab) {
|
|
4583
|
-
evt.preventDefault();
|
|
4584
|
-
}
|
|
4585
|
-
};
|
|
4586
4608
|
|
|
4587
|
-
|
|
4588
|
-
|
|
4609
|
+
var logger$3 = console_with_prefix('recorder');
|
|
4610
|
+
|
|
4611
|
+
var IFRAME_HANDSHAKE_REQUEST = 'mp_iframe_handshake_request';
|
|
4612
|
+
var IFRAME_HANDSHAKE_RESPONSE = 'mp_iframe_handshake_response';
|
|
4589
4613
|
|
|
4590
|
-
setTimeout(function() {
|
|
4591
|
-
window.location = options.href;
|
|
4592
|
-
}, 0);
|
|
4593
|
-
};
|
|
4594
4614
|
|
|
4595
4615
|
/**
|
|
4596
|
-
*
|
|
4616
|
+
* RecorderManager: manages session recording initialization, lifecycle and state
|
|
4597
4617
|
* @constructor
|
|
4598
|
-
* @extends DomTracker
|
|
4599
4618
|
*/
|
|
4600
|
-
var
|
|
4619
|
+
var RecorderManager = function(initOptions) {
|
|
4620
|
+
// TODO - Passing in mixpanel instance as it is still needed for recorder creation
|
|
4621
|
+
// but ideally we should be able to remove this dependency.
|
|
4622
|
+
this.mixpanelInstance = initOptions.mixpanelInstance;
|
|
4623
|
+
|
|
4624
|
+
this.getMpConfig = initOptions.getConfigFunc;
|
|
4625
|
+
this.getTabId = initOptions.getTabIdFunc;
|
|
4626
|
+
this.reportError = initOptions.reportErrorFunc;
|
|
4627
|
+
this.getDistinctId = initOptions.getDistinctIdFunc;
|
|
4628
|
+
this.loadExtraBundle = initOptions.loadExtraBundle;
|
|
4629
|
+
this.recorderSrc = initOptions.recorderSrc;
|
|
4630
|
+
this.targetingSrc = initOptions.targetingSrc;
|
|
4631
|
+
this.libBasePath = initOptions.libBasePath;
|
|
4632
|
+
|
|
4633
|
+
this._recorder = null;
|
|
4634
|
+
this._parentReplayId = null;
|
|
4635
|
+
this._parentFrameRetryInterval = null;
|
|
4636
|
+
};
|
|
4637
|
+
|
|
4638
|
+
RecorderManager.prototype.shouldLoadRecorder = function() {
|
|
4639
|
+
if (this.getMpConfig('disable_persistence')) {
|
|
4640
|
+
console.log('Load recorder check skipped due to disable_persistence config');
|
|
4641
|
+
return PromisePolyfill.resolve(false);
|
|
4642
|
+
}
|
|
4643
|
+
|
|
4644
|
+
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
4645
|
+
var tab_id = this.getTabId();
|
|
4646
|
+
return recording_registry_idb.init()
|
|
4647
|
+
.then(function () {
|
|
4648
|
+
return recording_registry_idb.getAll();
|
|
4649
|
+
})
|
|
4650
|
+
.then(function (recordings) {
|
|
4651
|
+
for (var i = 0; i < recordings.length; i++) {
|
|
4652
|
+
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
4653
|
+
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
4654
|
+
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
4655
|
+
return true;
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4658
|
+
return false;
|
|
4659
|
+
})
|
|
4660
|
+
.catch(_.bind(function (err) {
|
|
4661
|
+
this.reportError('Error checking recording registry', err);
|
|
4662
|
+
return false;
|
|
4663
|
+
}, this));
|
|
4664
|
+
};
|
|
4665
|
+
|
|
4666
|
+
RecorderManager.prototype.checkAndStartSessionRecording = function(force_start, rate) {
|
|
4667
|
+
if (!win['MutationObserver']) {
|
|
4668
|
+
console.critical('Browser does not support MutationObserver; skipping session recording');
|
|
4669
|
+
return PromisePolyfill.resolve();
|
|
4670
|
+
}
|
|
4671
|
+
|
|
4672
|
+
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
4673
|
+
return new PromisePolyfill(_.bind(function(resolve) {
|
|
4674
|
+
var handleLoadedRecorder = safewrap(_.bind(function() {
|
|
4675
|
+
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this.mixpanelInstance);
|
|
4676
|
+
this._recorder['resumeRecording'](startNewIfInactive);
|
|
4677
|
+
resolve();
|
|
4678
|
+
}, this));
|
|
4679
|
+
|
|
4680
|
+
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
4681
|
+
var recorderSrc = this.recorderSrc || (this.libBasePath + RECORDER_FILENAME);
|
|
4682
|
+
this.loadExtraBundle(recorderSrc, handleLoadedRecorder);
|
|
4683
|
+
} else {
|
|
4684
|
+
handleLoadedRecorder();
|
|
4685
|
+
}
|
|
4686
|
+
}, this));
|
|
4687
|
+
}, this);
|
|
4688
|
+
|
|
4689
|
+
// Cross-origin iframe handling
|
|
4690
|
+
var allowedOrigins = validateAllowedOrigins(this.getMpConfig('record_allowed_iframe_origins'), logger$3);
|
|
4691
|
+
var isCrossOriginRecordingEnabled = allowedOrigins.length > 0;
|
|
4692
|
+
|
|
4693
|
+
if (isCrossOriginRecordingEnabled) {
|
|
4694
|
+
// listen for handshake requests from their own child iframes (including nested)
|
|
4695
|
+
this._setupParentFrameListener(allowedOrigins);
|
|
4696
|
+
|
|
4697
|
+
if (win.parent !== win) {
|
|
4698
|
+
// also wait for parent's replay ID
|
|
4699
|
+
this._setupChildFrameListener(allowedOrigins, loadRecorder);
|
|
4700
|
+
this._sendParentFrameRequestWithRetry(allowedOrigins);
|
|
4701
|
+
return PromisePolyfill.resolve();
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
|
|
4705
|
+
/**
|
|
4706
|
+
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
4707
|
+
* Otherwise, if the recording registry has any records then it's likely there's a recording in progress or orphaned data that needs to be flushed.
|
|
4708
|
+
*/
|
|
4709
|
+
var effective_rate = _.isUndefined(rate) ? this.getMpConfig('record_sessions_percent') : rate;
|
|
4710
|
+
var is_sampled = effective_rate > 0 && Math.random() * 100 <= effective_rate;
|
|
4711
|
+
if (force_start || is_sampled) {
|
|
4712
|
+
return loadRecorder(true);
|
|
4713
|
+
} else {
|
|
4714
|
+
return this.shouldLoadRecorder()
|
|
4715
|
+
.then(_.bind(function (shouldLoad) {
|
|
4716
|
+
if (shouldLoad) {
|
|
4717
|
+
return loadRecorder(false);
|
|
4718
|
+
}
|
|
4719
|
+
return PromisePolyfill.resolve();
|
|
4720
|
+
}, this));
|
|
4721
|
+
}
|
|
4722
|
+
};
|
|
4723
|
+
|
|
4724
|
+
RecorderManager.prototype.isRecording = function() {
|
|
4725
|
+
// Safety check: ensure isRecording method exists (older CDN builds may not have it)
|
|
4726
|
+
if (!this._recorder || !_.isFunction(this._recorder['isRecording'])) {
|
|
4727
|
+
return false;
|
|
4728
|
+
}
|
|
4729
|
+
try {
|
|
4730
|
+
return this._recorder['isRecording']();
|
|
4731
|
+
} catch (e) {
|
|
4732
|
+
this.reportError('Error checking if recording is active', e);
|
|
4733
|
+
return false;
|
|
4734
|
+
}
|
|
4735
|
+
};
|
|
4736
|
+
|
|
4737
|
+
RecorderManager.prototype.startRecordingOnEvent = function(event_name, properties) {
|
|
4738
|
+
var isRecording = this.isRecording();
|
|
4739
|
+
var recordingTriggerEvents = this.getMpConfig('recording_event_triggers');
|
|
4740
|
+
|
|
4741
|
+
if (!isRecording && recordingTriggerEvents) {
|
|
4742
|
+
var trigger = recordingTriggerEvents[event_name];
|
|
4743
|
+
if (trigger && typeof trigger['percentage'] === 'number') {
|
|
4744
|
+
var newRate = trigger['percentage'];
|
|
4745
|
+
var propertyFilters = trigger['property_filters'];
|
|
4746
|
+
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
4747
|
+
var targetingSrc = this.targetingSrc || (this.libBasePath + TARGETING_FILENAME);
|
|
4748
|
+
getTargetingPromise(this.loadExtraBundle, targetingSrc)
|
|
4749
|
+
.then(function(targeting) {
|
|
4750
|
+
try {
|
|
4751
|
+
var result = targeting['eventMatchesCriteria'](
|
|
4752
|
+
event_name,
|
|
4753
|
+
properties,
|
|
4754
|
+
{
|
|
4755
|
+
'event_name': event_name,
|
|
4756
|
+
'property_filters': propertyFilters
|
|
4757
|
+
}
|
|
4758
|
+
);
|
|
4759
|
+
if (result['matches']) {
|
|
4760
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
4761
|
+
}
|
|
4762
|
+
} catch (err) {
|
|
4763
|
+
console.critical('Could not parse recording event trigger properties logic:', err);
|
|
4764
|
+
}
|
|
4765
|
+
}.bind(this)).catch(function(err) {
|
|
4766
|
+
console.critical('Failed to load targeting library:', err);
|
|
4767
|
+
});
|
|
4768
|
+
} else {
|
|
4769
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4773
|
+
};
|
|
4774
|
+
|
|
4775
|
+
RecorderManager.prototype.stopSessionRecording = function() {
|
|
4776
|
+
if (this._recorder) {
|
|
4777
|
+
return this._recorder['stopRecording']();
|
|
4778
|
+
}
|
|
4779
|
+
return PromisePolyfill.resolve();
|
|
4780
|
+
};
|
|
4781
|
+
|
|
4782
|
+
RecorderManager.prototype.pauseSessionRecording = function() {
|
|
4783
|
+
if (this._recorder) {
|
|
4784
|
+
return this._recorder['pauseRecording']();
|
|
4785
|
+
}
|
|
4786
|
+
return PromisePolyfill.resolve();
|
|
4787
|
+
};
|
|
4788
|
+
|
|
4789
|
+
RecorderManager.prototype.resumeSessionRecording = function() {
|
|
4790
|
+
if (this._recorder) {
|
|
4791
|
+
return this._recorder['resumeRecording']();
|
|
4792
|
+
}
|
|
4793
|
+
return PromisePolyfill.resolve();
|
|
4794
|
+
};
|
|
4795
|
+
|
|
4796
|
+
RecorderManager.prototype.isRecordingHeatmapData = function() {
|
|
4797
|
+
return this.getSessionReplayId() && this.getMpConfig('record_heatmap_data');
|
|
4798
|
+
};
|
|
4799
|
+
|
|
4800
|
+
RecorderManager.prototype.getSessionRecordingProperties = function() {
|
|
4801
|
+
var props = {};
|
|
4802
|
+
var replay_id = this.getSessionReplayId();
|
|
4803
|
+
if (replay_id) {
|
|
4804
|
+
props['$mp_replay_id'] = replay_id;
|
|
4805
|
+
}
|
|
4806
|
+
return props;
|
|
4807
|
+
};
|
|
4808
|
+
|
|
4809
|
+
RecorderManager.prototype.getSessionReplayUrl = function() {
|
|
4810
|
+
var replay_url = null;
|
|
4811
|
+
var replay_id = this.getSessionReplayId();
|
|
4812
|
+
if (replay_id) {
|
|
4813
|
+
var query_params = _.HTTPBuildQuery({
|
|
4814
|
+
'replay_id': replay_id,
|
|
4815
|
+
'distinct_id': this.getDistinctId(),
|
|
4816
|
+
'token': this.getMpConfig('token')
|
|
4817
|
+
});
|
|
4818
|
+
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
4819
|
+
}
|
|
4820
|
+
return replay_url;
|
|
4821
|
+
};
|
|
4822
|
+
|
|
4823
|
+
RecorderManager.prototype.getSessionReplayId = function() {
|
|
4824
|
+
// Child iframe uses parent's replay ID
|
|
4825
|
+
if (this._parentReplayId) {
|
|
4826
|
+
return this._parentReplayId;
|
|
4827
|
+
}
|
|
4828
|
+
var replay_id = null;
|
|
4829
|
+
if (this._recorder) {
|
|
4830
|
+
replay_id = this._recorder['replayId'];
|
|
4831
|
+
}
|
|
4832
|
+
return replay_id || null;
|
|
4833
|
+
};
|
|
4834
|
+
|
|
4835
|
+
// "private" public method to reach into the recorder in test cases
|
|
4836
|
+
RecorderManager.prototype.getRecorder = function() {
|
|
4837
|
+
return this._recorder;
|
|
4838
|
+
};
|
|
4839
|
+
|
|
4840
|
+
RecorderManager.prototype._setupChildFrameListener = function(allowedOrigins, loadRecorder) {
|
|
4841
|
+
if (this._childFrameMessageHandler) {
|
|
4842
|
+
return;
|
|
4843
|
+
}
|
|
4844
|
+
var self = this;
|
|
4845
|
+
this._childFrameMessageHandler = function(event) {
|
|
4846
|
+
if (allowedOrigins.indexOf(event.origin) === -1) return;
|
|
4847
|
+
var data = event.data;
|
|
4848
|
+
if (data && data['type'] === IFRAME_HANDSHAKE_RESPONSE && data['token'] === self.getMpConfig('token') && data['replayId']) {
|
|
4849
|
+
self._parentReplayId = data['replayId'];
|
|
4850
|
+
if (data['distinctId']) {
|
|
4851
|
+
self.mixpanelInstance['identify'](data['distinctId']);
|
|
4852
|
+
}
|
|
4853
|
+
self._parentFrameRetryActive = false;
|
|
4854
|
+
win.removeEventListener('message', self._childFrameMessageHandler);
|
|
4855
|
+
self._childFrameMessageHandler = null;
|
|
4856
|
+
loadRecorder(true);
|
|
4857
|
+
}
|
|
4858
|
+
};
|
|
4859
|
+
win.addEventListener('message', this._childFrameMessageHandler);
|
|
4860
|
+
};
|
|
4861
|
+
|
|
4862
|
+
RecorderManager.prototype._sendParentFrameRequest = function(allowedOrigins) {
|
|
4863
|
+
var message = {};
|
|
4864
|
+
message['type'] = IFRAME_HANDSHAKE_REQUEST;
|
|
4865
|
+
message['token'] = this.getMpConfig('token');
|
|
4866
|
+
for (var i = 0; i < allowedOrigins.length; i++) {
|
|
4867
|
+
try {
|
|
4868
|
+
win.parent.postMessage(message, allowedOrigins[i]);
|
|
4869
|
+
} catch (e) {
|
|
4870
|
+
// origin mismatch - ignore
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4873
|
+
};
|
|
4874
|
+
|
|
4875
|
+
RecorderManager.prototype._sendParentFrameRequestWithRetry = function(allowedOrigins) {
|
|
4876
|
+
var self = this;
|
|
4877
|
+
var maxRetries = 10;
|
|
4878
|
+
var retryCount = 0;
|
|
4879
|
+
var delay = 50;
|
|
4880
|
+
this._parentFrameRetryActive = true;
|
|
4881
|
+
|
|
4882
|
+
this._sendParentFrameRequest(allowedOrigins);
|
|
4883
|
+
|
|
4884
|
+
function scheduleRetry() {
|
|
4885
|
+
setTimeout(function() {
|
|
4886
|
+
if (!self._parentFrameRetryActive || self._parentReplayId || ++retryCount >= maxRetries) {
|
|
4887
|
+
return;
|
|
4888
|
+
}
|
|
4889
|
+
self._sendParentFrameRequest(allowedOrigins);
|
|
4890
|
+
delay *= 2;
|
|
4891
|
+
scheduleRetry();
|
|
4892
|
+
}, delay);
|
|
4893
|
+
}
|
|
4894
|
+
scheduleRetry();
|
|
4895
|
+
};
|
|
4896
|
+
|
|
4897
|
+
RecorderManager.prototype._setupParentFrameListener = function(allowedOrigins) {
|
|
4898
|
+
if (this._parentFrameMessageHandler) {
|
|
4899
|
+
return;
|
|
4900
|
+
}
|
|
4901
|
+
var self = this;
|
|
4902
|
+
this._parentFrameMessageHandler = function(event) {
|
|
4903
|
+
if (allowedOrigins.indexOf(event.origin) === -1) return;
|
|
4904
|
+
var data = event.data;
|
|
4905
|
+
if (data && data['type'] === IFRAME_HANDSHAKE_REQUEST && data['token'] === self.getMpConfig('token')) {
|
|
4906
|
+
var replayId = self.getSessionReplayId();
|
|
4907
|
+
if (replayId) {
|
|
4908
|
+
var response = {};
|
|
4909
|
+
response['type'] = IFRAME_HANDSHAKE_RESPONSE;
|
|
4910
|
+
response['token'] = self.getMpConfig('token');
|
|
4911
|
+
response['replayId'] = replayId;
|
|
4912
|
+
response['distinctId'] = self.getDistinctId();
|
|
4913
|
+
event.source.postMessage(response, event.origin);
|
|
4914
|
+
}
|
|
4915
|
+
}
|
|
4916
|
+
};
|
|
4917
|
+
win.addEventListener('message', this._parentFrameMessageHandler);
|
|
4918
|
+
};
|
|
4919
|
+
|
|
4920
|
+
safewrapClass(RecorderManager);
|
|
4921
|
+
|
|
4922
|
+
/* eslint camelcase: "off" */
|
|
4923
|
+
|
|
4924
|
+
|
|
4925
|
+
/**
|
|
4926
|
+
* DomTracker Object
|
|
4927
|
+
* @constructor
|
|
4928
|
+
*/
|
|
4929
|
+
var DomTracker = function() {};
|
|
4930
|
+
|
|
4931
|
+
|
|
4932
|
+
// interface
|
|
4933
|
+
DomTracker.prototype.create_properties = function() {};
|
|
4934
|
+
DomTracker.prototype.event_handler = function() {};
|
|
4935
|
+
DomTracker.prototype.after_track_handler = function() {};
|
|
4936
|
+
|
|
4937
|
+
DomTracker.prototype.init = function(mixpanel_instance) {
|
|
4938
|
+
this.mp = mixpanel_instance;
|
|
4939
|
+
return this;
|
|
4940
|
+
};
|
|
4941
|
+
|
|
4942
|
+
/**
|
|
4943
|
+
* @param {Object|string} query
|
|
4944
|
+
* @param {string} event_name
|
|
4945
|
+
* @param {Object=} properties
|
|
4946
|
+
* @param {function=} user_callback
|
|
4947
|
+
*/
|
|
4948
|
+
DomTracker.prototype.track = function(query, event_name, properties, user_callback) {
|
|
4949
|
+
var that = this;
|
|
4950
|
+
var elements = _.dom_query(query);
|
|
4951
|
+
|
|
4952
|
+
if (elements.length === 0) {
|
|
4953
|
+
console.error('The DOM query (' + query + ') returned 0 elements');
|
|
4954
|
+
return;
|
|
4955
|
+
}
|
|
4956
|
+
|
|
4957
|
+
_.each(elements, function(element) {
|
|
4958
|
+
_.register_event(element, this.override_event, function(e) {
|
|
4959
|
+
var options = {};
|
|
4960
|
+
var props = that.create_properties(properties, this);
|
|
4961
|
+
var timeout = that.mp.get_config('track_links_timeout');
|
|
4962
|
+
|
|
4963
|
+
that.event_handler(e, this, options);
|
|
4964
|
+
|
|
4965
|
+
// in case the mixpanel servers don't get back to us in time
|
|
4966
|
+
window.setTimeout(that.track_callback(user_callback, props, options, true), timeout);
|
|
4967
|
+
|
|
4968
|
+
// fire the tracking event
|
|
4969
|
+
that.mp.track(event_name, props, that.track_callback(user_callback, props, options));
|
|
4970
|
+
});
|
|
4971
|
+
}, this);
|
|
4972
|
+
|
|
4973
|
+
return true;
|
|
4974
|
+
};
|
|
4975
|
+
|
|
4976
|
+
/**
|
|
4977
|
+
* @param {function} user_callback
|
|
4978
|
+
* @param {Object} props
|
|
4979
|
+
* @param {boolean=} timeout_occured
|
|
4980
|
+
*/
|
|
4981
|
+
DomTracker.prototype.track_callback = function(user_callback, props, options, timeout_occured) {
|
|
4982
|
+
timeout_occured = timeout_occured || false;
|
|
4983
|
+
var that = this;
|
|
4984
|
+
|
|
4985
|
+
return function() {
|
|
4986
|
+
// options is referenced from both callbacks, so we can have
|
|
4987
|
+
// a 'lock' of sorts to ensure only one fires
|
|
4988
|
+
if (options.callback_fired) { return; }
|
|
4989
|
+
options.callback_fired = true;
|
|
4990
|
+
|
|
4991
|
+
if (user_callback && user_callback(timeout_occured, props) === false) {
|
|
4992
|
+
// user can prevent the default functionality by
|
|
4993
|
+
// returning false from their callback
|
|
4994
|
+
return;
|
|
4995
|
+
}
|
|
4996
|
+
|
|
4997
|
+
that.after_track_handler(props, options, timeout_occured);
|
|
4998
|
+
};
|
|
4999
|
+
};
|
|
5000
|
+
|
|
5001
|
+
DomTracker.prototype.create_properties = function(properties, element) {
|
|
5002
|
+
var props;
|
|
5003
|
+
|
|
5004
|
+
if (typeof(properties) === 'function') {
|
|
5005
|
+
props = properties(element);
|
|
5006
|
+
} else {
|
|
5007
|
+
props = _.extend({}, properties);
|
|
5008
|
+
}
|
|
5009
|
+
|
|
5010
|
+
return props;
|
|
5011
|
+
};
|
|
5012
|
+
|
|
5013
|
+
/**
|
|
5014
|
+
* LinkTracker Object
|
|
5015
|
+
* @constructor
|
|
5016
|
+
* @extends DomTracker
|
|
5017
|
+
*/
|
|
5018
|
+
var LinkTracker = function() {
|
|
5019
|
+
this.override_event = 'click';
|
|
5020
|
+
};
|
|
5021
|
+
_.inherit(LinkTracker, DomTracker);
|
|
5022
|
+
|
|
5023
|
+
LinkTracker.prototype.create_properties = function(properties, element) {
|
|
5024
|
+
var props = LinkTracker.superclass.create_properties.apply(this, arguments);
|
|
5025
|
+
|
|
5026
|
+
if (element.href) { props['url'] = element.href; }
|
|
5027
|
+
|
|
5028
|
+
return props;
|
|
5029
|
+
};
|
|
5030
|
+
|
|
5031
|
+
LinkTracker.prototype.event_handler = function(evt, element, options) {
|
|
5032
|
+
options.new_tab = (
|
|
5033
|
+
evt.which === 2 ||
|
|
5034
|
+
evt.metaKey ||
|
|
5035
|
+
evt.ctrlKey ||
|
|
5036
|
+
element.target === '_blank'
|
|
5037
|
+
);
|
|
5038
|
+
options.href = element.href;
|
|
5039
|
+
|
|
5040
|
+
if (!options.new_tab) {
|
|
5041
|
+
evt.preventDefault();
|
|
5042
|
+
}
|
|
5043
|
+
};
|
|
5044
|
+
|
|
5045
|
+
LinkTracker.prototype.after_track_handler = function(props, options) {
|
|
5046
|
+
if (options.new_tab) { return; }
|
|
5047
|
+
|
|
5048
|
+
setTimeout(function() {
|
|
5049
|
+
window.location = options.href;
|
|
5050
|
+
}, 0);
|
|
5051
|
+
};
|
|
5052
|
+
|
|
5053
|
+
/**
|
|
5054
|
+
* FormTracker Object
|
|
5055
|
+
* @constructor
|
|
5056
|
+
* @extends DomTracker
|
|
5057
|
+
*/
|
|
5058
|
+
var FormTracker = function() {
|
|
4601
5059
|
this.override_event = 'submit';
|
|
4602
5060
|
};
|
|
4603
5061
|
_.inherit(FormTracker, DomTracker);
|
|
@@ -6982,133 +7440,6 @@
|
|
|
6982
7440
|
return timestamp;
|
|
6983
7441
|
};
|
|
6984
7442
|
|
|
6985
|
-
var MIXPANEL_DB_NAME = 'mixpanelBrowserDb';
|
|
6986
|
-
|
|
6987
|
-
var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
|
|
6988
|
-
var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
|
|
6989
|
-
|
|
6990
|
-
// note: increment the version number when adding new object stores
|
|
6991
|
-
var DB_VERSION = 1;
|
|
6992
|
-
var OBJECT_STORES = [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME];
|
|
6993
|
-
|
|
6994
|
-
/**
|
|
6995
|
-
* @type {import('./wrapper').StorageWrapper}
|
|
6996
|
-
*/
|
|
6997
|
-
var IDBStorageWrapper = function (storeName) {
|
|
6998
|
-
/**
|
|
6999
|
-
* @type {Promise<IDBDatabase>|null}
|
|
7000
|
-
*/
|
|
7001
|
-
this.dbPromise = null;
|
|
7002
|
-
this.storeName = storeName;
|
|
7003
|
-
};
|
|
7004
|
-
|
|
7005
|
-
IDBStorageWrapper.prototype._openDb = function () {
|
|
7006
|
-
return new PromisePolyfill(function (resolve, reject) {
|
|
7007
|
-
var openRequest = win.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
|
|
7008
|
-
openRequest['onerror'] = function () {
|
|
7009
|
-
reject(openRequest.error);
|
|
7010
|
-
};
|
|
7011
|
-
|
|
7012
|
-
openRequest['onsuccess'] = function () {
|
|
7013
|
-
resolve(openRequest.result);
|
|
7014
|
-
};
|
|
7015
|
-
|
|
7016
|
-
openRequest['onupgradeneeded'] = function (ev) {
|
|
7017
|
-
var db = ev.target.result;
|
|
7018
|
-
|
|
7019
|
-
OBJECT_STORES.forEach(function (storeName) {
|
|
7020
|
-
db.createObjectStore(storeName);
|
|
7021
|
-
});
|
|
7022
|
-
};
|
|
7023
|
-
});
|
|
7024
|
-
};
|
|
7025
|
-
|
|
7026
|
-
IDBStorageWrapper.prototype.init = function () {
|
|
7027
|
-
if (!win.indexedDB) {
|
|
7028
|
-
return PromisePolyfill.reject('indexedDB is not supported in this browser');
|
|
7029
|
-
}
|
|
7030
|
-
|
|
7031
|
-
if (!this.dbPromise) {
|
|
7032
|
-
this.dbPromise = this._openDb();
|
|
7033
|
-
}
|
|
7034
|
-
|
|
7035
|
-
return this.dbPromise
|
|
7036
|
-
.then(function (dbOrError) {
|
|
7037
|
-
if (dbOrError instanceof win['IDBDatabase']) {
|
|
7038
|
-
return PromisePolyfill.resolve();
|
|
7039
|
-
} else {
|
|
7040
|
-
return PromisePolyfill.reject(dbOrError);
|
|
7041
|
-
}
|
|
7042
|
-
});
|
|
7043
|
-
};
|
|
7044
|
-
|
|
7045
|
-
IDBStorageWrapper.prototype.isInitialized = function () {
|
|
7046
|
-
return !!this.dbPromise;
|
|
7047
|
-
};
|
|
7048
|
-
|
|
7049
|
-
/**
|
|
7050
|
-
* @param {IDBTransactionMode} mode
|
|
7051
|
-
* @param {function(IDBObjectStore): void} storeCb
|
|
7052
|
-
*/
|
|
7053
|
-
IDBStorageWrapper.prototype.makeTransaction = function (mode, storeCb) {
|
|
7054
|
-
var storeName = this.storeName;
|
|
7055
|
-
var doTransaction = function (db) {
|
|
7056
|
-
return new PromisePolyfill(function (resolve, reject) {
|
|
7057
|
-
var transaction = db.transaction(storeName, mode);
|
|
7058
|
-
transaction.oncomplete = function () {
|
|
7059
|
-
resolve(transaction);
|
|
7060
|
-
};
|
|
7061
|
-
transaction.onabort = transaction.onerror = function () {
|
|
7062
|
-
reject(transaction.error);
|
|
7063
|
-
};
|
|
7064
|
-
|
|
7065
|
-
storeCb(transaction.objectStore(storeName));
|
|
7066
|
-
});
|
|
7067
|
-
};
|
|
7068
|
-
|
|
7069
|
-
return this.dbPromise
|
|
7070
|
-
.then(doTransaction)
|
|
7071
|
-
.catch(function (err) {
|
|
7072
|
-
if (err && err['name'] === 'InvalidStateError') {
|
|
7073
|
-
// try reopening the DB if the connection is closed
|
|
7074
|
-
this.dbPromise = this._openDb();
|
|
7075
|
-
return this.dbPromise.then(doTransaction);
|
|
7076
|
-
} else {
|
|
7077
|
-
return PromisePolyfill.reject(err);
|
|
7078
|
-
}
|
|
7079
|
-
}.bind(this));
|
|
7080
|
-
};
|
|
7081
|
-
|
|
7082
|
-
IDBStorageWrapper.prototype.setItem = function (key, value) {
|
|
7083
|
-
return this.makeTransaction('readwrite', function (objectStore) {
|
|
7084
|
-
objectStore.put(value, key);
|
|
7085
|
-
});
|
|
7086
|
-
};
|
|
7087
|
-
|
|
7088
|
-
IDBStorageWrapper.prototype.getItem = function (key) {
|
|
7089
|
-
var req;
|
|
7090
|
-
return this.makeTransaction('readonly', function (objectStore) {
|
|
7091
|
-
req = objectStore.get(key);
|
|
7092
|
-
}).then(function () {
|
|
7093
|
-
return req.result;
|
|
7094
|
-
});
|
|
7095
|
-
};
|
|
7096
|
-
|
|
7097
|
-
IDBStorageWrapper.prototype.removeItem = function (key) {
|
|
7098
|
-
return this.makeTransaction('readwrite', function (objectStore) {
|
|
7099
|
-
objectStore.delete(key);
|
|
7100
|
-
});
|
|
7101
|
-
};
|
|
7102
|
-
|
|
7103
|
-
IDBStorageWrapper.prototype.getAll = function () {
|
|
7104
|
-
var req;
|
|
7105
|
-
return this.makeTransaction('readonly', function (objectStore) {
|
|
7106
|
-
req = objectStore.getAll();
|
|
7107
|
-
}).then(function () {
|
|
7108
|
-
return req.result;
|
|
7109
|
-
});
|
|
7110
|
-
};
|
|
7111
|
-
|
|
7112
7443
|
/* eslint camelcase: "off" */
|
|
7113
7444
|
|
|
7114
7445
|
/*
|
|
@@ -7152,7 +7483,6 @@
|
|
|
7152
7483
|
/** @const */ var SETTING_FALLBACK = 'fallback';
|
|
7153
7484
|
/** @const */ var SETTING_DISABLED = 'disabled';
|
|
7154
7485
|
|
|
7155
|
-
|
|
7156
7486
|
/*
|
|
7157
7487
|
* Dynamic... constants? Is that an oxymoron?
|
|
7158
7488
|
*/
|
|
@@ -7237,19 +7567,24 @@
|
|
|
7237
7567
|
'batch_request_timeout_ms': 90000,
|
|
7238
7568
|
'batch_autostart': true,
|
|
7239
7569
|
'hooks': {},
|
|
7570
|
+
'record_allowed_iframe_origins': [],
|
|
7240
7571
|
'record_block_class': new RegExp('^(mp-block|fs-exclude|amp-block|rr-block|ph-no-capture)$'),
|
|
7241
7572
|
'record_block_selector': 'img, video, audio',
|
|
7242
7573
|
'record_canvas': false,
|
|
7243
7574
|
'record_collect_fonts': false,
|
|
7244
7575
|
'record_console': true,
|
|
7245
7576
|
'record_heatmap_data': false,
|
|
7577
|
+
'recording_event_triggers': {},
|
|
7246
7578
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
7247
7579
|
'record_mask_inputs': true,
|
|
7248
7580
|
'record_max_ms': MAX_RECORDING_MS,
|
|
7249
7581
|
'record_min_ms': 0,
|
|
7582
|
+
'record_network': false,
|
|
7583
|
+
'record_network_options': {},
|
|
7250
7584
|
'record_sessions_percent': 0,
|
|
7251
|
-
'recorder_src':
|
|
7252
|
-
'targeting_src':
|
|
7585
|
+
'recorder_src': null,
|
|
7586
|
+
'targeting_src': null,
|
|
7587
|
+
'lib_base_path': 'https://cdn.mxpnl.com/libs/',
|
|
7253
7588
|
'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
|
|
7254
7589
|
};
|
|
7255
7590
|
|
|
@@ -7403,6 +7738,19 @@
|
|
|
7403
7738
|
'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
|
|
7404
7739
|
}));
|
|
7405
7740
|
|
|
7741
|
+
this.recorderManager = new RecorderManager({
|
|
7742
|
+
mixpanelInstance: this,
|
|
7743
|
+
getConfigFunc: _.bind(this.get_config, this),
|
|
7744
|
+
setConfigFunc: _.bind(this.set_config, this),
|
|
7745
|
+
getTabIdFunc: _.bind(this.get_tab_id, this),
|
|
7746
|
+
reportErrorFunc: _.bind(this.report_error, this),
|
|
7747
|
+
getDistinctIdFunc: _.bind(this.get_distinct_id, this),
|
|
7748
|
+
recorderSrc: this.get_config('recorder_src'),
|
|
7749
|
+
targetingSrc: this.get_config('targeting_src'),
|
|
7750
|
+
libBasePath: this.get_config('lib_base_path'),
|
|
7751
|
+
loadExtraBundle: load_extra_bundle
|
|
7752
|
+
});
|
|
7753
|
+
|
|
7406
7754
|
this['_jsc'] = NOOP_FUNC;
|
|
7407
7755
|
|
|
7408
7756
|
this.__dom_loaded_queue = [];
|
|
@@ -7481,7 +7829,7 @@
|
|
|
7481
7829
|
getPropertyFunc: _.bind(this.get_property, this),
|
|
7482
7830
|
trackingFunc: _.bind(this.track, this),
|
|
7483
7831
|
loadExtraBundle: load_extra_bundle,
|
|
7484
|
-
targetingSrc: this.get_config('targeting_src')
|
|
7832
|
+
targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
|
|
7485
7833
|
});
|
|
7486
7834
|
this.flags.init();
|
|
7487
7835
|
this['flags'] = this.flags;
|
|
@@ -7494,11 +7842,11 @@
|
|
|
7494
7842
|
// Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
|
|
7495
7843
|
var mode = this.get_config('remote_settings_mode');
|
|
7496
7844
|
if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
|
|
7497
|
-
this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
7498
|
-
this._check_and_start_session_recording();
|
|
7845
|
+
this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
7846
|
+
return this._check_and_start_session_recording();
|
|
7499
7847
|
}, this));
|
|
7500
7848
|
} else {
|
|
7501
|
-
this._check_and_start_session_recording();
|
|
7849
|
+
this.__session_recording_init_promise = this._check_and_start_session_recording();
|
|
7502
7850
|
}
|
|
7503
7851
|
};
|
|
7504
7852
|
|
|
@@ -7542,132 +7890,50 @@
|
|
|
7542
7890
|
return this.tab_id || null;
|
|
7543
7891
|
};
|
|
7544
7892
|
|
|
7545
|
-
MixpanelLib.prototype._should_load_recorder = function () {
|
|
7546
|
-
if (this.get_config('disable_persistence')) {
|
|
7547
|
-
console.log('Load recorder check skipped due to disable_persistence config');
|
|
7548
|
-
return Promise.resolve(false);
|
|
7549
|
-
}
|
|
7550
|
-
|
|
7551
|
-
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
7552
|
-
var tab_id = this.get_tab_id();
|
|
7553
|
-
return recording_registry_idb.init()
|
|
7554
|
-
.then(function () {
|
|
7555
|
-
return recording_registry_idb.getAll();
|
|
7556
|
-
})
|
|
7557
|
-
.then(function (recordings) {
|
|
7558
|
-
for (var i = 0; i < recordings.length; i++) {
|
|
7559
|
-
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
7560
|
-
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
7561
|
-
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
7562
|
-
return true;
|
|
7563
|
-
}
|
|
7564
|
-
}
|
|
7565
|
-
return false;
|
|
7566
|
-
})
|
|
7567
|
-
.catch(_.bind(function (err) {
|
|
7568
|
-
this.report_error('Error checking recording registry', err);
|
|
7569
|
-
}, this));
|
|
7570
|
-
};
|
|
7571
|
-
|
|
7572
7893
|
MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
|
|
7573
|
-
|
|
7574
|
-
console.critical('Browser does not support MutationObserver; skipping session recording');
|
|
7575
|
-
return;
|
|
7576
|
-
}
|
|
7577
|
-
|
|
7578
|
-
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
7579
|
-
var handleLoadedRecorder = _.bind(function() {
|
|
7580
|
-
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this);
|
|
7581
|
-
this._recorder['resumeRecording'](startNewIfInactive);
|
|
7582
|
-
}, this);
|
|
7583
|
-
|
|
7584
|
-
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
7585
|
-
load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
|
|
7586
|
-
} else {
|
|
7587
|
-
handleLoadedRecorder();
|
|
7588
|
-
}
|
|
7589
|
-
}, this);
|
|
7590
|
-
|
|
7591
|
-
/**
|
|
7592
|
-
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
7593
|
-
* Otherwise, if the recording registry has any records then it's likely there's a recording in progress or orphaned data that needs to be flushed.
|
|
7594
|
-
*/
|
|
7595
|
-
var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
|
|
7596
|
-
if (force_start || is_sampled) {
|
|
7597
|
-
loadRecorder(true);
|
|
7598
|
-
} else {
|
|
7599
|
-
this._should_load_recorder()
|
|
7600
|
-
.then(function (shouldLoad) {
|
|
7601
|
-
if (shouldLoad) {
|
|
7602
|
-
loadRecorder(false);
|
|
7603
|
-
}
|
|
7604
|
-
});
|
|
7605
|
-
}
|
|
7894
|
+
return this.recorderManager.checkAndStartSessionRecording(force_start);
|
|
7606
7895
|
});
|
|
7607
7896
|
|
|
7897
|
+
MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
|
|
7898
|
+
return this.recorderManager.startRecordingOnEvent(event_name, properties);
|
|
7899
|
+
};
|
|
7900
|
+
|
|
7608
7901
|
MixpanelLib.prototype.start_session_recording = function () {
|
|
7609
|
-
this._check_and_start_session_recording(true);
|
|
7902
|
+
return this._check_and_start_session_recording(true);
|
|
7610
7903
|
};
|
|
7611
7904
|
|
|
7612
7905
|
MixpanelLib.prototype.stop_session_recording = function () {
|
|
7613
|
-
|
|
7614
|
-
return this._recorder['stopRecording']();
|
|
7615
|
-
}
|
|
7616
|
-
return Promise.resolve();
|
|
7906
|
+
return this.recorderManager.stopSessionRecording();
|
|
7617
7907
|
};
|
|
7618
7908
|
|
|
7619
7909
|
MixpanelLib.prototype.pause_session_recording = function () {
|
|
7620
|
-
|
|
7621
|
-
return this._recorder['pauseRecording']();
|
|
7622
|
-
}
|
|
7623
|
-
return Promise.resolve();
|
|
7910
|
+
return this.recorderManager.pauseSessionRecording();
|
|
7624
7911
|
};
|
|
7625
7912
|
|
|
7626
7913
|
MixpanelLib.prototype.resume_session_recording = function () {
|
|
7627
|
-
|
|
7628
|
-
return this._recorder['resumeRecording']();
|
|
7629
|
-
}
|
|
7630
|
-
return Promise.resolve();
|
|
7914
|
+
return this.recorderManager.resumeSessionRecording();
|
|
7631
7915
|
};
|
|
7632
7916
|
|
|
7633
7917
|
MixpanelLib.prototype.is_recording_heatmap_data = function () {
|
|
7634
|
-
return this.
|
|
7918
|
+
return this.recorderManager.isRecordingHeatmapData();
|
|
7635
7919
|
};
|
|
7636
7920
|
|
|
7637
7921
|
MixpanelLib.prototype.get_session_recording_properties = function () {
|
|
7638
|
-
|
|
7639
|
-
var replay_id = this._get_session_replay_id();
|
|
7640
|
-
if (replay_id) {
|
|
7641
|
-
props['$mp_replay_id'] = replay_id;
|
|
7642
|
-
}
|
|
7643
|
-
return props;
|
|
7922
|
+
return this.recorderManager.getSessionRecordingProperties();
|
|
7644
7923
|
};
|
|
7645
7924
|
|
|
7646
7925
|
MixpanelLib.prototype.get_session_replay_url = function () {
|
|
7647
|
-
|
|
7648
|
-
var replay_id = this._get_session_replay_id();
|
|
7649
|
-
if (replay_id) {
|
|
7650
|
-
var query_params = _.HTTPBuildQuery({
|
|
7651
|
-
'replay_id': replay_id,
|
|
7652
|
-
'distinct_id': this.get_distinct_id(),
|
|
7653
|
-
'token': this.get_config('token')
|
|
7654
|
-
});
|
|
7655
|
-
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
7656
|
-
}
|
|
7657
|
-
return replay_url;
|
|
7658
|
-
};
|
|
7659
|
-
|
|
7660
|
-
MixpanelLib.prototype._get_session_replay_id = function () {
|
|
7661
|
-
var replay_id = null;
|
|
7662
|
-
if (this._recorder) {
|
|
7663
|
-
replay_id = this._recorder['replayId'];
|
|
7664
|
-
}
|
|
7665
|
-
return replay_id || null;
|
|
7926
|
+
return this.recorderManager.getSessionReplayUrl();
|
|
7666
7927
|
};
|
|
7667
7928
|
|
|
7668
7929
|
// "private" public method to reach into the recorder in test cases
|
|
7669
7930
|
MixpanelLib.prototype.__get_recorder = function () {
|
|
7670
|
-
return this.
|
|
7931
|
+
return this.recorderManager.getRecorder();
|
|
7932
|
+
};
|
|
7933
|
+
|
|
7934
|
+
// "private" public method to get session recording init promise in test cases
|
|
7935
|
+
MixpanelLib.prototype.__get_recording_init_promise = function () {
|
|
7936
|
+
return this.__session_recording_init_promise;
|
|
7671
7937
|
};
|
|
7672
7938
|
|
|
7673
7939
|
// Private methods
|
|
@@ -7925,6 +8191,7 @@
|
|
|
7925
8191
|
};
|
|
7926
8192
|
|
|
7927
8193
|
MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
8194
|
+
var self = this;
|
|
7928
8195
|
var disableRecordingIfStrict = function() {
|
|
7929
8196
|
if (mode === 'strict') {
|
|
7930
8197
|
self.set_config({'record_sessions_percent': 0});
|
|
@@ -7945,7 +8212,6 @@
|
|
|
7945
8212
|
};
|
|
7946
8213
|
var query_string = _.HTTPBuildQuery(request_params);
|
|
7947
8214
|
var full_url = settings_endpoint + '?' + query_string;
|
|
7948
|
-
var self = this;
|
|
7949
8215
|
|
|
7950
8216
|
var abortController = new AbortController();
|
|
7951
8217
|
var timeout_id = setTimeout(function() {
|
|
@@ -8137,6 +8403,34 @@
|
|
|
8137
8403
|
this._execute_array([item]);
|
|
8138
8404
|
};
|
|
8139
8405
|
|
|
8406
|
+
/**
|
|
8407
|
+
* Enables events on the Mixpanel object. If passed no arguments,
|
|
8408
|
+
* this function enable tracking of all events. If passed an
|
|
8409
|
+
* array of event names, those events will be enabled, but other
|
|
8410
|
+
* existing disabled events will continue to be not tracked.
|
|
8411
|
+
*
|
|
8412
|
+
* @param {Array} [events] An array of event names to enable
|
|
8413
|
+
*/
|
|
8414
|
+
MixpanelLib.prototype.enable = function(events) {
|
|
8415
|
+
var keys, new_disabled_events, i, j;
|
|
8416
|
+
|
|
8417
|
+
if (typeof(events) === 'undefined') {
|
|
8418
|
+
this._flags.disable_all_events = false;
|
|
8419
|
+
} else {
|
|
8420
|
+
keys = {};
|
|
8421
|
+
new_disabled_events = [];
|
|
8422
|
+
for (i = 0; i < events.length; i++) {
|
|
8423
|
+
keys[events[i]] = true;
|
|
8424
|
+
}
|
|
8425
|
+
for (j = 0; j < this.__disabled_events.length; j++) {
|
|
8426
|
+
if (!keys[this.__disabled_events[j]]) {
|
|
8427
|
+
new_disabled_events.push(this.__disabled_events[j]);
|
|
8428
|
+
}
|
|
8429
|
+
}
|
|
8430
|
+
this.__disabled_events = new_disabled_events;
|
|
8431
|
+
}
|
|
8432
|
+
};
|
|
8433
|
+
|
|
8140
8434
|
/**
|
|
8141
8435
|
* Disable events on the Mixpanel object. If passed no arguments,
|
|
8142
8436
|
* this function disables tracking of any event. If passed an
|
|
@@ -8310,6 +8604,8 @@
|
|
|
8310
8604
|
this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
|
|
8311
8605
|
}
|
|
8312
8606
|
|
|
8607
|
+
this._start_recording_on_event(event_name, properties);
|
|
8608
|
+
|
|
8313
8609
|
var data = {
|
|
8314
8610
|
'event': event_name,
|
|
8315
8611
|
'properties': properties
|
|
@@ -9518,6 +9814,7 @@
|
|
|
9518
9814
|
// MixpanelLib Exports
|
|
9519
9815
|
MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
|
|
9520
9816
|
MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
|
|
9817
|
+
MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
|
|
9521
9818
|
MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
|
|
9522
9819
|
MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
|
|
9523
9820
|
MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
|
|
@@ -9561,6 +9858,7 @@
|
|
|
9561
9858
|
|
|
9562
9859
|
// Exports intended only for testing
|
|
9563
9860
|
MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
|
|
9861
|
+
MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
|
|
9564
9862
|
|
|
9565
9863
|
// MixpanelPersistence Exports
|
|
9566
9864
|
MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
|