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