mixpanel-browser 2.75.0 → 2.76.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/workflows/integration-tests.yml +2 -2
- package/.github/workflows/unit-tests.yml +2 -2
- package/CHANGELOG.md +10 -0
- package/build.sh +10 -8
- package/dist/async-modules/mixpanel-recorder-bIS4LMGd.js +23595 -0
- package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js +2 -0
- package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js.map +1 -0
- package/dist/async-modules/mixpanel-targeting-BcAPS-Mz.js +2520 -0
- package/dist/async-modules/mixpanel-targeting-VOeN7RWY.min.js +2 -0
- package/dist/async-modules/mixpanel-targeting-VOeN7RWY.min.js.map +1 -0
- package/dist/mixpanel-core.cjs.d.ts +68 -0
- package/dist/mixpanel-core.cjs.js +550 -383
- package/dist/mixpanel-recorder.js +708 -32
- 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 +68 -0
- package/dist/mixpanel-with-async-modules.cjs.js +550 -383
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +68 -0
- package/dist/mixpanel-with-async-recorder.cjs.js +550 -383
- package/dist/mixpanel-with-recorder.d.ts +68 -0
- package/dist/mixpanel-with-recorder.js +1036 -197
- package/dist/mixpanel-with-recorder.min.d.ts +68 -0
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +68 -0
- package/dist/mixpanel.amd.js +1038 -251
- package/dist/mixpanel.cjs.d.ts +68 -0
- package/dist/mixpanel.cjs.js +1038 -251
- package/dist/mixpanel.globals.js +550 -383
- package/dist/mixpanel.min.js +184 -181
- package/dist/mixpanel.module.d.ts +68 -0
- package/dist/mixpanel.module.js +1038 -251
- package/dist/mixpanel.umd.d.ts +68 -0
- package/dist/mixpanel.umd.js +1038 -251
- package/logo.svg +5 -0
- package/package.json +2 -1
- 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 +68 -0
- package/src/mixpanel-core.js +76 -111
- 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 +31 -11
- package/src/recorder-manager.js +216 -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 +55 -0
- package/src/globals.js +0 -14
package/dist/mixpanel.amd.js
CHANGED
|
@@ -25,16 +25,19 @@ define((function () { 'use strict';
|
|
|
25
25
|
win = window;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
var Config = {
|
|
29
|
+
DEBUG: false,
|
|
30
|
+
LIB_VERSION: '2.76.0'
|
|
31
|
+
};
|
|
31
32
|
|
|
32
|
-
//
|
|
33
|
+
// Window global names for async modules
|
|
33
34
|
var TARGETING_GLOBAL_NAME = '__mp_targeting';
|
|
34
|
-
|
|
35
|
-
// Recorder library global (used by recorder and mixpanel-core)
|
|
36
35
|
var RECORDER_GLOBAL_NAME = '__mp_recorder';
|
|
37
36
|
|
|
37
|
+
// Constants that are injected at build-time for the names of async modules.
|
|
38
|
+
var RECORDER_FILENAME = '__MP_RECORDER_FILENAME__';
|
|
39
|
+
var TARGETING_FILENAME = '__MP_TARGETING_FILENAME__';
|
|
40
|
+
|
|
38
41
|
function _array_like_to_array(arr, len) {
|
|
39
42
|
if (len == null || len > arr.length) len = arr.length;
|
|
40
43
|
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
@@ -18134,7 +18137,7 @@ define((function () { 'use strict';
|
|
|
18134
18137
|
var __publicField = function(obj, key, value) {
|
|
18135
18138
|
return __defNormalProp(obj, (typeof key === "undefined" ? "undefined" : _type_of(key)) !== "symbol" ? key + "" : key, value);
|
|
18136
18139
|
};
|
|
18137
|
-
function patch(source, name, replacement) {
|
|
18140
|
+
function patch$3(source, name, replacement) {
|
|
18138
18141
|
try {
|
|
18139
18142
|
if (!(name in source)) {
|
|
18140
18143
|
return function() {};
|
|
@@ -18551,7 +18554,7 @@ define((function () { 'use strict';
|
|
|
18551
18554
|
if (!_logger[level]) {
|
|
18552
18555
|
return function() {};
|
|
18553
18556
|
}
|
|
18554
|
-
return patch(_logger, level, function(original) {
|
|
18557
|
+
return patch$3(_logger, level, function(original) {
|
|
18555
18558
|
var _this1 = _this;
|
|
18556
18559
|
return function() {
|
|
18557
18560
|
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
@@ -18972,11 +18975,6 @@ define((function () { 'use strict';
|
|
|
18972
18975
|
PromisePolyfill = NpoPromise;
|
|
18973
18976
|
}
|
|
18974
18977
|
|
|
18975
|
-
var Config = {
|
|
18976
|
-
DEBUG: false,
|
|
18977
|
-
LIB_VERSION: '2.75.0'
|
|
18978
|
-
};
|
|
18979
|
-
|
|
18980
18978
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
18981
18979
|
|
|
18982
18980
|
// Maximum allowed session recording length
|
|
@@ -20708,6 +20706,17 @@ define((function () { 'use strict';
|
|
|
20708
20706
|
|
|
20709
20707
|
var NOOP_FUNC = function () {};
|
|
20710
20708
|
|
|
20709
|
+
var urlMatchesRegexList = function (url, regexList) {
|
|
20710
|
+
var matches = false;
|
|
20711
|
+
for (var i = 0; i < regexList.length; i++) {
|
|
20712
|
+
if (url.match(regexList[i])) {
|
|
20713
|
+
matches = true;
|
|
20714
|
+
break;
|
|
20715
|
+
}
|
|
20716
|
+
}
|
|
20717
|
+
return matches;
|
|
20718
|
+
};
|
|
20719
|
+
|
|
20711
20720
|
var JSONStringify = null, JSONParse = null;
|
|
20712
20721
|
if (typeof JSON !== 'undefined') {
|
|
20713
20722
|
JSONStringify = JSON.stringify;
|
|
@@ -21179,7 +21188,7 @@ define((function () { 'use strict';
|
|
|
21179
21188
|
};
|
|
21180
21189
|
}
|
|
21181
21190
|
|
|
21182
|
-
var logger$
|
|
21191
|
+
var logger$7 = console_with_prefix('lock');
|
|
21183
21192
|
|
|
21184
21193
|
/**
|
|
21185
21194
|
* SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
|
|
@@ -21231,7 +21240,7 @@ define((function () { 'use strict';
|
|
|
21231
21240
|
|
|
21232
21241
|
var delay = function(cb) {
|
|
21233
21242
|
if (new Date().getTime() - startTime > timeoutMS) {
|
|
21234
|
-
logger$
|
|
21243
|
+
logger$7.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
|
|
21235
21244
|
storage.removeItem(keyZ);
|
|
21236
21245
|
storage.removeItem(keyY);
|
|
21237
21246
|
loop();
|
|
@@ -21378,7 +21387,7 @@ define((function () { 'use strict';
|
|
|
21378
21387
|
}, this));
|
|
21379
21388
|
};
|
|
21380
21389
|
|
|
21381
|
-
var logger$
|
|
21390
|
+
var logger$6 = console_with_prefix('batch');
|
|
21382
21391
|
|
|
21383
21392
|
/**
|
|
21384
21393
|
* RequestQueue: queue for batching API requests with localStorage backup for retries.
|
|
@@ -21407,7 +21416,7 @@ define((function () { 'use strict';
|
|
|
21407
21416
|
timeoutMS: options.sharedLockTimeoutMS,
|
|
21408
21417
|
});
|
|
21409
21418
|
}
|
|
21410
|
-
this.reportError = options.errorReporter || _.bind(logger$
|
|
21419
|
+
this.reportError = options.errorReporter || _.bind(logger$6.error, logger$6);
|
|
21411
21420
|
|
|
21412
21421
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
21413
21422
|
|
|
@@ -21740,7 +21749,7 @@ define((function () { 'use strict';
|
|
|
21740
21749
|
// maximum interval between request retries after exponential backoff
|
|
21741
21750
|
var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
|
|
21742
21751
|
|
|
21743
|
-
var logger$
|
|
21752
|
+
var logger$5 = console_with_prefix('batch');
|
|
21744
21753
|
|
|
21745
21754
|
/**
|
|
21746
21755
|
* RequestBatcher: manages the queueing, flushing, retry etc of requests of one
|
|
@@ -21868,7 +21877,7 @@ define((function () { 'use strict';
|
|
|
21868
21877
|
*/
|
|
21869
21878
|
RequestBatcher.prototype.flush = function(options) {
|
|
21870
21879
|
if (this.requestInProgress) {
|
|
21871
|
-
logger$
|
|
21880
|
+
logger$5.log('Flush: Request already in progress');
|
|
21872
21881
|
return PromisePolyfill.resolve();
|
|
21873
21882
|
}
|
|
21874
21883
|
|
|
@@ -22045,7 +22054,7 @@ define((function () { 'use strict';
|
|
|
22045
22054
|
if (options.unloading) {
|
|
22046
22055
|
requestOptions.transport = 'sendBeacon';
|
|
22047
22056
|
}
|
|
22048
|
-
logger$
|
|
22057
|
+
logger$5.log('MIXPANEL REQUEST:', dataForRequest);
|
|
22049
22058
|
return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
|
|
22050
22059
|
}, this))
|
|
22051
22060
|
.catch(_.bind(function(err) {
|
|
@@ -22058,7 +22067,7 @@ define((function () { 'use strict';
|
|
|
22058
22067
|
* Log error to global logger and optional user-defined logger.
|
|
22059
22068
|
*/
|
|
22060
22069
|
RequestBatcher.prototype.reportError = function(msg, err) {
|
|
22061
|
-
logger$
|
|
22070
|
+
logger$5.error.apply(logger$5.error, arguments);
|
|
22062
22071
|
if (this.errorReporter) {
|
|
22063
22072
|
try {
|
|
22064
22073
|
if (!(err instanceof Error)) {
|
|
@@ -22066,7 +22075,7 @@ define((function () { 'use strict';
|
|
|
22066
22075
|
}
|
|
22067
22076
|
this.errorReporter(msg, err);
|
|
22068
22077
|
} catch(err) {
|
|
22069
|
-
logger$
|
|
22078
|
+
logger$5.error(err);
|
|
22070
22079
|
}
|
|
22071
22080
|
}
|
|
22072
22081
|
};
|
|
@@ -22188,7 +22197,7 @@ define((function () { 'use strict';
|
|
|
22188
22197
|
|
|
22189
22198
|
var MAX_DEPTH = 5;
|
|
22190
22199
|
|
|
22191
|
-
var logger$
|
|
22200
|
+
var logger$4 = console_with_prefix('autocapture');
|
|
22192
22201
|
|
|
22193
22202
|
|
|
22194
22203
|
function getClasses(el) {
|
|
@@ -22452,7 +22461,7 @@ define((function () { 'use strict';
|
|
|
22452
22461
|
return false;
|
|
22453
22462
|
}
|
|
22454
22463
|
} catch (err) {
|
|
22455
|
-
logger$
|
|
22464
|
+
logger$4.critical('Error while checking element in allowElementCallback', err);
|
|
22456
22465
|
return false;
|
|
22457
22466
|
}
|
|
22458
22467
|
}
|
|
@@ -22469,7 +22478,7 @@ define((function () { 'use strict';
|
|
|
22469
22478
|
return true;
|
|
22470
22479
|
}
|
|
22471
22480
|
} catch (err) {
|
|
22472
|
-
logger$
|
|
22481
|
+
logger$4.critical('Error while checking selector: ' + sel, err);
|
|
22473
22482
|
}
|
|
22474
22483
|
}
|
|
22475
22484
|
return false;
|
|
@@ -22484,7 +22493,7 @@ define((function () { 'use strict';
|
|
|
22484
22493
|
return true;
|
|
22485
22494
|
}
|
|
22486
22495
|
} catch (err) {
|
|
22487
|
-
logger$
|
|
22496
|
+
logger$4.critical('Error while checking element in blockElementCallback', err);
|
|
22488
22497
|
return true;
|
|
22489
22498
|
}
|
|
22490
22499
|
}
|
|
@@ -22498,7 +22507,7 @@ define((function () { 'use strict';
|
|
|
22498
22507
|
return true;
|
|
22499
22508
|
}
|
|
22500
22509
|
} catch (err) {
|
|
22501
|
-
logger$
|
|
22510
|
+
logger$4.critical('Error while checking selector: ' + sel, err);
|
|
22502
22511
|
}
|
|
22503
22512
|
}
|
|
22504
22513
|
}
|
|
@@ -23045,6 +23054,655 @@ define((function () { 'use strict';
|
|
|
23045
23054
|
}
|
|
23046
23055
|
}
|
|
23047
23056
|
|
|
23057
|
+
/**
|
|
23058
|
+
* This is a port of the open rrweb network plugin in this PR https://github.com/rrweb-io/rrweb/pull/1105
|
|
23059
|
+
* the hope is that eventually this can be replaced with the official plugin once it's published (and we sync the mixpanel rrweb fork)
|
|
23060
|
+
*
|
|
23061
|
+
* This plugin incorporates some important fixes for fetch/XHR body recording that are not yet in the main rrweb repo, as well as makes
|
|
23062
|
+
* header and body recording more restrictive by requiring an allowlist instead of content type / blocklist.
|
|
23063
|
+
*
|
|
23064
|
+
*/
|
|
23065
|
+
|
|
23066
|
+
var logger$3 = console_with_prefix('network-plugin');
|
|
23067
|
+
|
|
23068
|
+
/**
|
|
23069
|
+
* Get the time origin for converting performance timestamps to absolute timestamps.
|
|
23070
|
+
* Uses Date.now() - performance.now() instead of performance.timeOrigin because
|
|
23071
|
+
* browsers can report timeOrigin values that are skewed from actual time, and some
|
|
23072
|
+
* browsers (notably older Safari versions) don't implement timeOrigin at all.
|
|
23073
|
+
* See: https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L49-L70
|
|
23074
|
+
* @param {Window} win
|
|
23075
|
+
* @returns {number}
|
|
23076
|
+
*/
|
|
23077
|
+
function getTimeOrigin(win) {
|
|
23078
|
+
return Math.round(Date.now() - win.performance.now());
|
|
23079
|
+
}
|
|
23080
|
+
|
|
23081
|
+
/**
|
|
23082
|
+
* @typedef {import('../index.d.ts').InitiatorType} InitiatorType
|
|
23083
|
+
* @typedef {import('../index.d.ts').NetworkRequest} NetworkRequest
|
|
23084
|
+
* @typedef {import('../index.d.ts').NetworkRecordOptions} NetworkRecordOptions
|
|
23085
|
+
* @typedef {import('../index.d.ts').NetworkData} NetworkData
|
|
23086
|
+
*/
|
|
23087
|
+
|
|
23088
|
+
/**
|
|
23089
|
+
* @typedef {Record<string, string>} Headers
|
|
23090
|
+
*/
|
|
23091
|
+
|
|
23092
|
+
/**
|
|
23093
|
+
* @typedef {string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | null} Body
|
|
23094
|
+
*/
|
|
23095
|
+
|
|
23096
|
+
/**
|
|
23097
|
+
* @callback networkCallback
|
|
23098
|
+
* @param {NetworkData} data
|
|
23099
|
+
* @returns {void}
|
|
23100
|
+
*/
|
|
23101
|
+
|
|
23102
|
+
/**
|
|
23103
|
+
* @callback listenerHandler
|
|
23104
|
+
* @returns {void}
|
|
23105
|
+
*/
|
|
23106
|
+
|
|
23107
|
+
/**
|
|
23108
|
+
* @typedef {(PerformanceNavigationTiming | PerformanceResourceTiming) & { responseStatus?: number }} ObservedPerformanceEntry
|
|
23109
|
+
*/
|
|
23110
|
+
|
|
23111
|
+
/**
|
|
23112
|
+
* @typedef {Object} RecordPlugin
|
|
23113
|
+
* @property {string} name
|
|
23114
|
+
* @property {(callback: networkCallback, win: Window, options: NetworkRecordOptions) => listenerHandler} observer
|
|
23115
|
+
* @property {NetworkRecordOptions} [options]
|
|
23116
|
+
*/
|
|
23117
|
+
|
|
23118
|
+
/** @type {Required<NetworkRecordOptions>} */
|
|
23119
|
+
var defaultNetworkOptions = {
|
|
23120
|
+
initiatorTypes: [
|
|
23121
|
+
'audio',
|
|
23122
|
+
'beacon',
|
|
23123
|
+
'body',
|
|
23124
|
+
'css',
|
|
23125
|
+
'early-hint',
|
|
23126
|
+
'embed',
|
|
23127
|
+
'fetch',
|
|
23128
|
+
'frame',
|
|
23129
|
+
'iframe',
|
|
23130
|
+
'icon',
|
|
23131
|
+
'image',
|
|
23132
|
+
'img',
|
|
23133
|
+
'input',
|
|
23134
|
+
'link',
|
|
23135
|
+
'navigation',
|
|
23136
|
+
'object',
|
|
23137
|
+
'ping',
|
|
23138
|
+
'script',
|
|
23139
|
+
'track',
|
|
23140
|
+
'video',
|
|
23141
|
+
'xmlhttprequest',
|
|
23142
|
+
],
|
|
23143
|
+
ignoreRequestFn: function() { return false; },
|
|
23144
|
+
recordHeaders: {
|
|
23145
|
+
request: [],
|
|
23146
|
+
response: [],
|
|
23147
|
+
},
|
|
23148
|
+
recordBodyUrls: {
|
|
23149
|
+
request: [],
|
|
23150
|
+
response: [],
|
|
23151
|
+
},
|
|
23152
|
+
recordInitialRequests: false,
|
|
23153
|
+
};
|
|
23154
|
+
|
|
23155
|
+
/**
|
|
23156
|
+
* @param {PerformanceEntry} entry
|
|
23157
|
+
* @returns {entry is PerformanceNavigationTiming}
|
|
23158
|
+
*/
|
|
23159
|
+
function isNavigationTiming(entry) {
|
|
23160
|
+
return entry.entryType === 'navigation';
|
|
23161
|
+
}
|
|
23162
|
+
|
|
23163
|
+
/**
|
|
23164
|
+
* @param {PerformanceEntry} entry
|
|
23165
|
+
* @returns {entry is PerformanceResourceTiming}
|
|
23166
|
+
*/
|
|
23167
|
+
function isResourceTiming (entry) {
|
|
23168
|
+
return entry.entryType === 'resource';
|
|
23169
|
+
}
|
|
23170
|
+
|
|
23171
|
+
function findLast(array, predicate) {
|
|
23172
|
+
var length = array.length;
|
|
23173
|
+
for (var i = length - 1; i >= 0; i -= 1) {
|
|
23174
|
+
if (predicate(array[i])) {
|
|
23175
|
+
return array[i];
|
|
23176
|
+
}
|
|
23177
|
+
}
|
|
23178
|
+
}
|
|
23179
|
+
|
|
23180
|
+
/**
|
|
23181
|
+
* Monkey-patches a method on an object with a wrapped version, returning a function that restores the original.
|
|
23182
|
+
* Adapted from Sentry's `fill` utility:
|
|
23183
|
+
* https://github.com/getsentry/sentry-javascript/blob/de5c5cbe177b4334386e747857225eec36a91ea1/packages/core/src/utils/object.ts#L67-L95
|
|
23184
|
+
*
|
|
23185
|
+
* @param {object} source - The object containing the method to patch
|
|
23186
|
+
* @param {string} name - The method name to patch
|
|
23187
|
+
* @param {function} replacementFactory - A function that receives the original method and returns the replacement
|
|
23188
|
+
* @returns {function} A function that restores the original method
|
|
23189
|
+
*/
|
|
23190
|
+
function patch(source, name, replacementFactory) {
|
|
23191
|
+
if (!(name in source) || typeof source[name] !== 'function') {
|
|
23192
|
+
return function() {};
|
|
23193
|
+
}
|
|
23194
|
+
var original = source[name];
|
|
23195
|
+
var wrapped = replacementFactory(original);
|
|
23196
|
+
source[name] = wrapped;
|
|
23197
|
+
return function() {
|
|
23198
|
+
source[name] = original;
|
|
23199
|
+
};
|
|
23200
|
+
}
|
|
23201
|
+
|
|
23202
|
+
|
|
23203
|
+
/**
|
|
23204
|
+
* Maximum body size to record (1MB)
|
|
23205
|
+
*/
|
|
23206
|
+
var MAX_BODY_SIZE = 1024 * 1024;
|
|
23207
|
+
|
|
23208
|
+
/**
|
|
23209
|
+
* Truncate string if it exceeds max size
|
|
23210
|
+
* @param {string} str
|
|
23211
|
+
* @returns {string}
|
|
23212
|
+
*/
|
|
23213
|
+
function truncateBody(str) {
|
|
23214
|
+
if (!str || typeof str !== 'string') {
|
|
23215
|
+
return str;
|
|
23216
|
+
}
|
|
23217
|
+
if (str.length > MAX_BODY_SIZE) {
|
|
23218
|
+
logger$3.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
|
|
23219
|
+
return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
|
|
23220
|
+
}
|
|
23221
|
+
return str;
|
|
23222
|
+
}
|
|
23223
|
+
|
|
23224
|
+
/**
|
|
23225
|
+
* @param {networkCallback} cb
|
|
23226
|
+
* @param {Window} win
|
|
23227
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23228
|
+
* @returns {listenerHandler}
|
|
23229
|
+
*/
|
|
23230
|
+
function initPerformanceObserver(cb, win, options) {
|
|
23231
|
+
if (!win.PerformanceObserver) {
|
|
23232
|
+
logger$3.error('PerformanceObserver not supported');
|
|
23233
|
+
return function() {
|
|
23234
|
+
//
|
|
23235
|
+
};
|
|
23236
|
+
}
|
|
23237
|
+
if (options.recordInitialRequests) {
|
|
23238
|
+
var initialPerformanceEntries = win.performance
|
|
23239
|
+
.getEntries()
|
|
23240
|
+
.filter(function(entry) {
|
|
23241
|
+
return isNavigationTiming(entry) ||
|
|
23242
|
+
(isResourceTiming(entry) &&
|
|
23243
|
+
options.initiatorTypes.includes(entry.initiatorType));
|
|
23244
|
+
});
|
|
23245
|
+
cb({
|
|
23246
|
+
requests: initialPerformanceEntries.map(function(entry) {
|
|
23247
|
+
return {
|
|
23248
|
+
url: entry.name,
|
|
23249
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23250
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23251
|
+
startTime: Math.round(entry.startTime),
|
|
23252
|
+
endTime: Math.round(entry.responseEnd),
|
|
23253
|
+
timeOrigin: getTimeOrigin(win),
|
|
23254
|
+
};
|
|
23255
|
+
}),
|
|
23256
|
+
isInitial: true,
|
|
23257
|
+
});
|
|
23258
|
+
}
|
|
23259
|
+
var observer = new win.PerformanceObserver(function(entries) {
|
|
23260
|
+
var performanceEntries = entries
|
|
23261
|
+
.getEntries()
|
|
23262
|
+
.filter(function(entry) {
|
|
23263
|
+
return isResourceTiming(entry) &&
|
|
23264
|
+
options.initiatorTypes.includes(entry.initiatorType) &&
|
|
23265
|
+
entry.initiatorType !== 'xmlhttprequest' &&
|
|
23266
|
+
entry.initiatorType !== 'fetch';
|
|
23267
|
+
});
|
|
23268
|
+
cb({
|
|
23269
|
+
requests: performanceEntries.map(function(entry) {
|
|
23270
|
+
return {
|
|
23271
|
+
url: entry.name,
|
|
23272
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23273
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23274
|
+
startTime: Math.round(entry.startTime),
|
|
23275
|
+
endTime: Math.round(entry.responseEnd),
|
|
23276
|
+
timeOrigin: getTimeOrigin(win),
|
|
23277
|
+
};
|
|
23278
|
+
}),
|
|
23279
|
+
});
|
|
23280
|
+
});
|
|
23281
|
+
observer.observe({ entryTypes: ['navigation', 'resource'] });
|
|
23282
|
+
return function() {
|
|
23283
|
+
observer.disconnect();
|
|
23284
|
+
};
|
|
23285
|
+
}
|
|
23286
|
+
|
|
23287
|
+
/**
|
|
23288
|
+
* Variation of the original rrweb function that requires an allowlist for headers instead of supporting boolean options
|
|
23289
|
+
* @param {'request' | 'response'} type
|
|
23290
|
+
* @param {NetworkRecordOptions['recordHeaders']} recordHeaders
|
|
23291
|
+
* @param {string} headerName
|
|
23292
|
+
* @returns {boolean}
|
|
23293
|
+
*/
|
|
23294
|
+
function shouldRecordHeader(type, recordHeaders, headerName) {
|
|
23295
|
+
if (!recordHeaders[type] || recordHeaders[type].length === 0) {
|
|
23296
|
+
return false;
|
|
23297
|
+
}
|
|
23298
|
+
|
|
23299
|
+
return recordHeaders[type].includes(headerName.toLowerCase());
|
|
23300
|
+
}
|
|
23301
|
+
|
|
23302
|
+
/**
|
|
23303
|
+
* Variation of the original rrweb function that requires an allowlist for URLs instead of supporting boolean options or by content type
|
|
23304
|
+
* @param {'request' | 'response'} type
|
|
23305
|
+
* @param {NetworkRecordOptions['recordBodyUrls']} recordBodyUrls
|
|
23306
|
+
* @param {string} url
|
|
23307
|
+
* @returns {boolean}
|
|
23308
|
+
*/
|
|
23309
|
+
function shouldRecordBody(type, recordBodyUrls, url) {
|
|
23310
|
+
if (!recordBodyUrls[type] || recordBodyUrls[type].length === 0) {
|
|
23311
|
+
return false;
|
|
23312
|
+
}
|
|
23313
|
+
|
|
23314
|
+
return urlMatchesRegexList(url, recordBodyUrls[type]);
|
|
23315
|
+
}
|
|
23316
|
+
|
|
23317
|
+
function tryReadXHRBody(body) {
|
|
23318
|
+
if (body === null || body === undefined) {
|
|
23319
|
+
return null;
|
|
23320
|
+
}
|
|
23321
|
+
|
|
23322
|
+
var result;
|
|
23323
|
+
if (typeof body === 'string') {
|
|
23324
|
+
result = body;
|
|
23325
|
+
} else if (body instanceof Document) {
|
|
23326
|
+
result = body.textContent;
|
|
23327
|
+
} else if (body instanceof FormData) {
|
|
23328
|
+
result = _.HTTPBuildQuery(body);
|
|
23329
|
+
} else if (_.isObject(body)) {
|
|
23330
|
+
try {
|
|
23331
|
+
result = JSON.stringify(body);
|
|
23332
|
+
} catch (e) {
|
|
23333
|
+
return 'Failed to stringify response object';
|
|
23334
|
+
}
|
|
23335
|
+
} else {
|
|
23336
|
+
return 'Cannot read body of type ' + typeof body;
|
|
23337
|
+
}
|
|
23338
|
+
|
|
23339
|
+
return truncateBody(result);
|
|
23340
|
+
}
|
|
23341
|
+
|
|
23342
|
+
/**
|
|
23343
|
+
* @param {Request | Response} r
|
|
23344
|
+
* @returns {Promise<string>}
|
|
23345
|
+
*/
|
|
23346
|
+
function tryReadFetchBody(r) {
|
|
23347
|
+
return new Promise(function(resolve) {
|
|
23348
|
+
var timeout = setTimeout(function() {
|
|
23349
|
+
resolve('Timeout while trying to read body');
|
|
23350
|
+
}, 500);
|
|
23351
|
+
try {
|
|
23352
|
+
r.clone()
|
|
23353
|
+
.text()
|
|
23354
|
+
.then(
|
|
23355
|
+
function(txt) {
|
|
23356
|
+
clearTimeout(timeout);
|
|
23357
|
+
resolve(truncateBody(txt));
|
|
23358
|
+
},
|
|
23359
|
+
function(reason) {
|
|
23360
|
+
clearTimeout(timeout);
|
|
23361
|
+
resolve('Failed to read body: ' + String(reason));
|
|
23362
|
+
}
|
|
23363
|
+
);
|
|
23364
|
+
} catch (e) {
|
|
23365
|
+
clearTimeout(timeout);
|
|
23366
|
+
resolve('Failed to read body: ' + String(e));
|
|
23367
|
+
}
|
|
23368
|
+
});
|
|
23369
|
+
}
|
|
23370
|
+
|
|
23371
|
+
/**
|
|
23372
|
+
* @param {Window} win
|
|
23373
|
+
* @param {string} initiatorType
|
|
23374
|
+
* @param {string} url
|
|
23375
|
+
* @param {number} [after]
|
|
23376
|
+
* @param {number} [before]
|
|
23377
|
+
* @param {number} [attempt]
|
|
23378
|
+
* @returns {Promise<PerformanceResourceTiming>}
|
|
23379
|
+
*/
|
|
23380
|
+
function getRequestPerformanceEntry(win, initiatorType, url, after, before, attempt) {
|
|
23381
|
+
if (attempt === undefined) {
|
|
23382
|
+
attempt = 0;
|
|
23383
|
+
}
|
|
23384
|
+
if (attempt > 10) {
|
|
23385
|
+
logger$3.error('Cannot find performance entry');
|
|
23386
|
+
return Promise.resolve(null);
|
|
23387
|
+
}
|
|
23388
|
+
var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
|
|
23389
|
+
win.performance.getEntriesByName(url)
|
|
23390
|
+
);
|
|
23391
|
+
var performanceEntry = findLast(
|
|
23392
|
+
urlPerformanceEntries,
|
|
23393
|
+
function(entry) {
|
|
23394
|
+
return isResourceTiming(entry) &&
|
|
23395
|
+
entry.initiatorType === initiatorType &&
|
|
23396
|
+
(!after || entry.startTime >= after) &&
|
|
23397
|
+
(!before || entry.startTime <= before);
|
|
23398
|
+
}
|
|
23399
|
+
);
|
|
23400
|
+
if (!performanceEntry) {
|
|
23401
|
+
return new Promise(function(resolve) {
|
|
23402
|
+
setTimeout(resolve, 50 * attempt);
|
|
23403
|
+
}).then(function() {
|
|
23404
|
+
return getRequestPerformanceEntry(
|
|
23405
|
+
win,
|
|
23406
|
+
initiatorType,
|
|
23407
|
+
url,
|
|
23408
|
+
after,
|
|
23409
|
+
before,
|
|
23410
|
+
attempt + 1
|
|
23411
|
+
);
|
|
23412
|
+
});
|
|
23413
|
+
}
|
|
23414
|
+
return Promise.resolve(performanceEntry);
|
|
23415
|
+
}
|
|
23416
|
+
|
|
23417
|
+
/**
|
|
23418
|
+
* @param {networkCallback} cb
|
|
23419
|
+
* @param {Window} win
|
|
23420
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23421
|
+
* @returns {listenerHandler}
|
|
23422
|
+
*/
|
|
23423
|
+
function initXhrObserver(cb, win, options) {
|
|
23424
|
+
if (!options.initiatorTypes.includes('xmlhttprequest')) {
|
|
23425
|
+
return function() {
|
|
23426
|
+
//
|
|
23427
|
+
};
|
|
23428
|
+
}
|
|
23429
|
+
var restorePatch = patch(
|
|
23430
|
+
win.XMLHttpRequest.prototype,
|
|
23431
|
+
'open',
|
|
23432
|
+
function(/** @type {typeof XMLHttpRequest.prototype.open} */ originalOpen) {
|
|
23433
|
+
return function(
|
|
23434
|
+
/** @type {string} */ method,
|
|
23435
|
+
/** @type {string | URL} */ url,
|
|
23436
|
+
/** @type {boolean} */ async,
|
|
23437
|
+
username, password
|
|
23438
|
+
) {
|
|
23439
|
+
if (async === undefined) {
|
|
23440
|
+
async = true;
|
|
23441
|
+
}
|
|
23442
|
+
var xhr = /** @type {XMLHttpRequest} */ (this);
|
|
23443
|
+
var req = new Request(url, { method: method });
|
|
23444
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23445
|
+
var networkRequest = {};
|
|
23446
|
+
/** @type {number | undefined} */
|
|
23447
|
+
var after;
|
|
23448
|
+
/** @type {number | undefined} */
|
|
23449
|
+
var before;
|
|
23450
|
+
|
|
23451
|
+
/** @type {Headers} */
|
|
23452
|
+
var requestHeaders = {};
|
|
23453
|
+
var originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
|
|
23454
|
+
xhr.setRequestHeader = function(/** @type {string} */ header, /** @type {string} */ value) {
|
|
23455
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23456
|
+
requestHeaders[header] = value;
|
|
23457
|
+
}
|
|
23458
|
+
return originalSetRequestHeader(header, value);
|
|
23459
|
+
};
|
|
23460
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23461
|
+
|
|
23462
|
+
var originalSend = xhr.send.bind(xhr);
|
|
23463
|
+
xhr.send = function(/** @type {Body} */ body) {
|
|
23464
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23465
|
+
networkRequest.requestBody = tryReadXHRBody(body);
|
|
23466
|
+
}
|
|
23467
|
+
after = win.performance.now();
|
|
23468
|
+
return originalSend(body);
|
|
23469
|
+
};
|
|
23470
|
+
xhr.addEventListener('readystatechange', function() {
|
|
23471
|
+
if (xhr.readyState !== xhr.DONE) {
|
|
23472
|
+
return;
|
|
23473
|
+
}
|
|
23474
|
+
before = win.performance.now();
|
|
23475
|
+
/** @type {Headers} */
|
|
23476
|
+
var responseHeaders = {};
|
|
23477
|
+
var rawHeaders = xhr.getAllResponseHeaders();
|
|
23478
|
+
if (rawHeaders) {
|
|
23479
|
+
var headers = rawHeaders.trim().split(/[\r\n]+/);
|
|
23480
|
+
headers.forEach(function(line) {
|
|
23481
|
+
if (!line) return;
|
|
23482
|
+
var colonIndex = line.indexOf(': ');
|
|
23483
|
+
if (colonIndex === -1) return;
|
|
23484
|
+
var header = line.substring(0, colonIndex);
|
|
23485
|
+
var value = line.substring(colonIndex + 2);
|
|
23486
|
+
if (header && shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23487
|
+
responseHeaders[header] = value;
|
|
23488
|
+
}
|
|
23489
|
+
});
|
|
23490
|
+
}
|
|
23491
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23492
|
+
if (
|
|
23493
|
+
shouldRecordBody('response', options.recordBodyUrls, req.url)
|
|
23494
|
+
) {
|
|
23495
|
+
networkRequest.responseBody = tryReadXHRBody(xhr.response);
|
|
23496
|
+
}
|
|
23497
|
+
getRequestPerformanceEntry(
|
|
23498
|
+
win,
|
|
23499
|
+
'xmlhttprequest',
|
|
23500
|
+
req.url,
|
|
23501
|
+
after,
|
|
23502
|
+
before
|
|
23503
|
+
)
|
|
23504
|
+
.then(function(entry) {
|
|
23505
|
+
if (!entry) {
|
|
23506
|
+
logger$3.error('Failed to get performance entry for XHR request to ' + req.url);
|
|
23507
|
+
return;
|
|
23508
|
+
}
|
|
23509
|
+
/** @type {NetworkRequest} */
|
|
23510
|
+
var request = {
|
|
23511
|
+
url: entry.name,
|
|
23512
|
+
method: req.method,
|
|
23513
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23514
|
+
status: xhr.status,
|
|
23515
|
+
startTime: Math.round(entry.startTime),
|
|
23516
|
+
endTime: Math.round(entry.responseEnd),
|
|
23517
|
+
timeOrigin: getTimeOrigin(win),
|
|
23518
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23519
|
+
requestBody: networkRequest.requestBody,
|
|
23520
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23521
|
+
responseBody: networkRequest.responseBody,
|
|
23522
|
+
};
|
|
23523
|
+
cb({ requests: [request] });
|
|
23524
|
+
})
|
|
23525
|
+
.catch(function(e) {
|
|
23526
|
+
logger$3.error('Error recording XHR request to ' + req.url + ': ' + String(e));
|
|
23527
|
+
});
|
|
23528
|
+
});
|
|
23529
|
+
|
|
23530
|
+
originalOpen.call(xhr, method, url, async, username, password);
|
|
23531
|
+
};
|
|
23532
|
+
}
|
|
23533
|
+
);
|
|
23534
|
+
return function() {
|
|
23535
|
+
restorePatch();
|
|
23536
|
+
};
|
|
23537
|
+
}
|
|
23538
|
+
|
|
23539
|
+
/**
|
|
23540
|
+
* @param {networkCallback} cb
|
|
23541
|
+
* @param {Window} win
|
|
23542
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23543
|
+
* @returns {listenerHandler}
|
|
23544
|
+
*/
|
|
23545
|
+
function initFetchObserver(cb, win, options) {
|
|
23546
|
+
if (!options.initiatorTypes.includes('fetch')) {
|
|
23547
|
+
return function() {
|
|
23548
|
+
//
|
|
23549
|
+
};
|
|
23550
|
+
}
|
|
23551
|
+
|
|
23552
|
+
var restorePatch = patch(win, 'fetch', function(/** @type {typeof fetch} */ originalFetch) {
|
|
23553
|
+
return function() {
|
|
23554
|
+
var req = new Request(arguments[0], arguments[1]);
|
|
23555
|
+
/** @type {Response | undefined} */
|
|
23556
|
+
var res;
|
|
23557
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23558
|
+
var networkRequest = {};
|
|
23559
|
+
/** @type {number | undefined} */
|
|
23560
|
+
var after;
|
|
23561
|
+
/** @type {number | undefined} */
|
|
23562
|
+
var before;
|
|
23563
|
+
|
|
23564
|
+
var originalFetchPromise;
|
|
23565
|
+
var requestBodyPromise = Promise.resolve(undefined);
|
|
23566
|
+
var responseBodyPromise = Promise.resolve(undefined);
|
|
23567
|
+
try {
|
|
23568
|
+
/** @type {Headers} */
|
|
23569
|
+
var requestHeaders = {};
|
|
23570
|
+
req.headers.forEach(function(value, header) {
|
|
23571
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23572
|
+
requestHeaders[header] = value;
|
|
23573
|
+
}
|
|
23574
|
+
});
|
|
23575
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23576
|
+
|
|
23577
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23578
|
+
requestBodyPromise = tryReadFetchBody(req)
|
|
23579
|
+
.then(function(body) {
|
|
23580
|
+
networkRequest.requestBody = body;
|
|
23581
|
+
});
|
|
23582
|
+
}
|
|
23583
|
+
|
|
23584
|
+
after = win.performance.now();
|
|
23585
|
+
originalFetchPromise = originalFetch.apply(win, arguments).then(function(response) {
|
|
23586
|
+
res = response;
|
|
23587
|
+
before = win.performance.now();
|
|
23588
|
+
|
|
23589
|
+
/** @type {Headers} */
|
|
23590
|
+
var responseHeaders = {};
|
|
23591
|
+
res.headers.forEach(function(value, header) {
|
|
23592
|
+
if (shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23593
|
+
responseHeaders[header] = value;
|
|
23594
|
+
}
|
|
23595
|
+
});
|
|
23596
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23597
|
+
|
|
23598
|
+
if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
|
|
23599
|
+
responseBodyPromise = tryReadFetchBody(res)
|
|
23600
|
+
.then(function(body) {
|
|
23601
|
+
networkRequest.responseBody = body;
|
|
23602
|
+
});
|
|
23603
|
+
}
|
|
23604
|
+
|
|
23605
|
+
return res;
|
|
23606
|
+
});
|
|
23607
|
+
} catch (e) {
|
|
23608
|
+
originalFetchPromise = Promise.reject(e);
|
|
23609
|
+
}
|
|
23610
|
+
|
|
23611
|
+
// await concurrently so we don't delay the fetch response
|
|
23612
|
+
Promise.all([requestBodyPromise, responseBodyPromise, originalFetchPromise])
|
|
23613
|
+
.then(function () {
|
|
23614
|
+
return getRequestPerformanceEntry(win, 'fetch', req.url, after, before);
|
|
23615
|
+
})
|
|
23616
|
+
.then(function(entry) {
|
|
23617
|
+
if (!entry) {
|
|
23618
|
+
logger$3.error('Failed to get performance entry for fetch request to ' + req.url);
|
|
23619
|
+
return;
|
|
23620
|
+
}
|
|
23621
|
+
/** @type {NetworkRequest} */
|
|
23622
|
+
var request = {
|
|
23623
|
+
url: entry.name,
|
|
23624
|
+
method: req.method,
|
|
23625
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23626
|
+
status: res ? res.status : undefined,
|
|
23627
|
+
startTime: Math.round(entry.startTime),
|
|
23628
|
+
endTime: Math.round(entry.responseEnd),
|
|
23629
|
+
timeOrigin: getTimeOrigin(win),
|
|
23630
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23631
|
+
requestBody: networkRequest.requestBody,
|
|
23632
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23633
|
+
responseBody: networkRequest.responseBody,
|
|
23634
|
+
};
|
|
23635
|
+
cb({ requests: [request] });
|
|
23636
|
+
})
|
|
23637
|
+
.catch(function (e) {
|
|
23638
|
+
logger$3.error('Error recording fetch request to ' + req.url + ': ' + String(e));
|
|
23639
|
+
});
|
|
23640
|
+
|
|
23641
|
+
return originalFetchPromise;
|
|
23642
|
+
};
|
|
23643
|
+
});
|
|
23644
|
+
return function() {
|
|
23645
|
+
restorePatch();
|
|
23646
|
+
};
|
|
23647
|
+
}
|
|
23648
|
+
|
|
23649
|
+
/**
|
|
23650
|
+
* @param {networkCallback} callback
|
|
23651
|
+
* @param {Window} win
|
|
23652
|
+
* @param {NetworkRecordOptions} options
|
|
23653
|
+
* @returns {listenerHandler}
|
|
23654
|
+
*/
|
|
23655
|
+
function initNetworkObserver(callback, win, options) {
|
|
23656
|
+
if (!('performance' in win)) {
|
|
23657
|
+
return function() {
|
|
23658
|
+
//
|
|
23659
|
+
};
|
|
23660
|
+
}
|
|
23661
|
+
|
|
23662
|
+
var recordHeaders = Object.assign({}, defaultNetworkOptions.recordHeaders, options.recordHeaders || {});
|
|
23663
|
+
var recordBodyUrls = Object.assign({}, defaultNetworkOptions.recordBodyUrls, options.recordBodyUrls || {});
|
|
23664
|
+
options = Object.assign({}, options, {
|
|
23665
|
+
recordHeaders: recordHeaders,
|
|
23666
|
+
recordBodyUrls: recordBodyUrls,
|
|
23667
|
+
});
|
|
23668
|
+
var networkOptions = /** @type {Required<NetworkRecordOptions>} */ Object.assign({}, defaultNetworkOptions, options);
|
|
23669
|
+
|
|
23670
|
+
/** @type {networkCallback} */
|
|
23671
|
+
var cb = function(data) {
|
|
23672
|
+
var requests = data.requests.filter(function(request) {
|
|
23673
|
+
var shouldIgnoreUrl = urlMatchesRegexList(request.url, networkOptions.ignoreRequestUrls || []);
|
|
23674
|
+
return !shouldIgnoreUrl && !networkOptions.ignoreRequestFn(request);
|
|
23675
|
+
});
|
|
23676
|
+
if (requests.length > 0 || data.isInitial) {
|
|
23677
|
+
callback(Object.assign({}, data, { requests: requests }));
|
|
23678
|
+
}
|
|
23679
|
+
};
|
|
23680
|
+
var performanceObserver = initPerformanceObserver(cb, win, networkOptions);
|
|
23681
|
+
var xhrObserver = initXhrObserver(cb, win, networkOptions);
|
|
23682
|
+
var fetchObserver = initFetchObserver(cb, win, networkOptions);
|
|
23683
|
+
return function() {
|
|
23684
|
+
performanceObserver();
|
|
23685
|
+
xhrObserver();
|
|
23686
|
+
fetchObserver();
|
|
23687
|
+
};
|
|
23688
|
+
}
|
|
23689
|
+
|
|
23690
|
+
// arbitrary .mp suffix in case rrweb does publish this plugin later and we use it but need to handle
|
|
23691
|
+
// a changed format in the mixpanel product.
|
|
23692
|
+
var NETWORK_PLUGIN_NAME = 'rrweb/network@1.mp';
|
|
23693
|
+
|
|
23694
|
+
/**
|
|
23695
|
+
* @param {NetworkRecordOptions} [options]
|
|
23696
|
+
* @returns {RecordPlugin}
|
|
23697
|
+
*/
|
|
23698
|
+
var getRecordNetworkPlugin = function(options) {
|
|
23699
|
+
return {
|
|
23700
|
+
name: NETWORK_PLUGIN_NAME,
|
|
23701
|
+
observer: initNetworkObserver,
|
|
23702
|
+
options: options,
|
|
23703
|
+
};
|
|
23704
|
+
};
|
|
23705
|
+
|
|
23048
23706
|
/**
|
|
23049
23707
|
* @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
|
|
23050
23708
|
*/
|
|
@@ -23278,12 +23936,35 @@ define((function () { 'use strict';
|
|
|
23278
23936
|
|
|
23279
23937
|
var privacyConfig = getPrivacyConfig(this._mixpanel);
|
|
23280
23938
|
|
|
23281
|
-
|
|
23282
|
-
|
|
23283
|
-
|
|
23284
|
-
|
|
23285
|
-
|
|
23286
|
-
|
|
23939
|
+
var plugins = [];
|
|
23940
|
+
if (this.getConfig('record_network')) {
|
|
23941
|
+
var options = this.getConfig('record_network_options') || {};
|
|
23942
|
+
// don't track requests to Mixpanel /record API
|
|
23943
|
+
var ignoreRequestUrls = (options.ignoreRequestUrls || []).slice();
|
|
23944
|
+
ignoreRequestUrls.push(this._getApiRoute());
|
|
23945
|
+
options.ignoreRequestUrls = ignoreRequestUrls;
|
|
23946
|
+
|
|
23947
|
+
plugins.push(getRecordNetworkPlugin(options));
|
|
23948
|
+
}
|
|
23949
|
+
|
|
23950
|
+
if (this.getConfig('record_console')) {
|
|
23951
|
+
plugins.push(
|
|
23952
|
+
getRecordConsolePlugin({
|
|
23953
|
+
stringifyOptions: {
|
|
23954
|
+
stringLengthLimit: 1000,
|
|
23955
|
+
numOfKeysLimit: 50,
|
|
23956
|
+
depthOfLimit: 2
|
|
23957
|
+
}
|
|
23958
|
+
})
|
|
23959
|
+
);
|
|
23960
|
+
}
|
|
23961
|
+
|
|
23962
|
+
try {
|
|
23963
|
+
this._stopRecording = this._rrwebRecord({
|
|
23964
|
+
'emit': function (ev) {
|
|
23965
|
+
if (this.idleExpires && this.idleExpires < ev.timestamp) {
|
|
23966
|
+
this._onIdleTimeout();
|
|
23967
|
+
return;
|
|
23287
23968
|
}
|
|
23288
23969
|
if (isUserEvent(ev)) {
|
|
23289
23970
|
if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
|
|
@@ -23316,15 +23997,7 @@ define((function () { 'use strict';
|
|
|
23316
23997
|
'sampling': {
|
|
23317
23998
|
'canvas': 15
|
|
23318
23999
|
},
|
|
23319
|
-
'plugins':
|
|
23320
|
-
getRecordConsolePlugin({
|
|
23321
|
-
stringifyOptions: {
|
|
23322
|
-
stringLengthLimit: 1000,
|
|
23323
|
-
numOfKeysLimit: 50,
|
|
23324
|
-
depthOfLimit: 2
|
|
23325
|
-
}
|
|
23326
|
-
})
|
|
23327
|
-
] : []
|
|
24000
|
+
'plugins': plugins,
|
|
23328
24001
|
});
|
|
23329
24002
|
} catch (err) {
|
|
23330
24003
|
this.reportError('Unexpected error when starting rrweb recording.', err);
|
|
@@ -23439,6 +24112,10 @@ define((function () { 'use strict';
|
|
|
23439
24112
|
return recording;
|
|
23440
24113
|
};
|
|
23441
24114
|
|
|
24115
|
+
SessionRecording.prototype._getApiRoute = function () {
|
|
24116
|
+
return this.getConfig('api_routes')['record'];
|
|
24117
|
+
};
|
|
24118
|
+
|
|
23442
24119
|
SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
|
|
23443
24120
|
var onSuccess = function (response, responseBody) {
|
|
23444
24121
|
// Update batch specific props only if the request was successful to guarantee ordering.
|
|
@@ -23458,7 +24135,7 @@ define((function () { 'use strict';
|
|
|
23458
24135
|
});
|
|
23459
24136
|
}.bind(this);
|
|
23460
24137
|
var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
|
|
23461
|
-
win['fetch'](apiHost + '/' + this.
|
|
24138
|
+
win['fetch'](apiHost + '/' + this._getApiRoute() + '?' + new URLSearchParams(reqParams), {
|
|
23462
24139
|
'method': 'POST',
|
|
23463
24140
|
'headers': {
|
|
23464
24141
|
'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
|
|
@@ -23859,8 +24536,12 @@ define((function () { 'use strict';
|
|
|
23859
24536
|
this.startRecording({shouldStopBatcher: true});
|
|
23860
24537
|
};
|
|
23861
24538
|
|
|
24539
|
+
MixpanelRecorder.prototype.isRecording = function () {
|
|
24540
|
+
return this.activeRecording && !this.activeRecording.isRrwebStopped();
|
|
24541
|
+
};
|
|
24542
|
+
|
|
23862
24543
|
MixpanelRecorder.prototype.getActiveReplayId = function () {
|
|
23863
|
-
if (this.
|
|
24544
|
+
if (this.isRecording()) {
|
|
23864
24545
|
return this.activeRecording.replayId;
|
|
23865
24546
|
} else {
|
|
23866
24547
|
return null;
|
|
@@ -24364,53 +25045,6 @@ define((function () { 'use strict';
|
|
|
24364
25045
|
var logicExports = requireLogic();
|
|
24365
25046
|
var jsonLogic = /*@__PURE__*/getDefaultExportFromCjs(logicExports);
|
|
24366
25047
|
|
|
24367
|
-
/**
|
|
24368
|
-
* Shared helper to recursively lowercase strings in nested structures
|
|
24369
|
-
* @param {*} obj - Value to process
|
|
24370
|
-
* @param {boolean} lowercaseKeys - Whether to lowercase object keys
|
|
24371
|
-
* @returns {*} Processed value with lowercased strings
|
|
24372
|
-
*/
|
|
24373
|
-
var lowercaseJson = function(obj, lowercaseKeys) {
|
|
24374
|
-
if (obj === null || obj === undefined) {
|
|
24375
|
-
return obj;
|
|
24376
|
-
} else if (typeof obj === 'string') {
|
|
24377
|
-
return obj.toLowerCase();
|
|
24378
|
-
} else if (Array.isArray(obj)) {
|
|
24379
|
-
return obj.map(function(item) {
|
|
24380
|
-
return lowercaseJson(item, lowercaseKeys);
|
|
24381
|
-
});
|
|
24382
|
-
} else if (obj === Object(obj)) {
|
|
24383
|
-
var result = {};
|
|
24384
|
-
for (var key in obj) {
|
|
24385
|
-
if (obj.hasOwnProperty(key)) {
|
|
24386
|
-
var newKey = lowercaseKeys && typeof key === 'string' ? key.toLowerCase() : key;
|
|
24387
|
-
result[newKey] = lowercaseJson(obj[key], lowercaseKeys);
|
|
24388
|
-
}
|
|
24389
|
-
}
|
|
24390
|
-
return result;
|
|
24391
|
-
} else {
|
|
24392
|
-
return obj;
|
|
24393
|
-
}
|
|
24394
|
-
};
|
|
24395
|
-
|
|
24396
|
-
/**
|
|
24397
|
-
* Lowercase all string keys and values in a nested structure
|
|
24398
|
-
* @param {*} val - Value to process
|
|
24399
|
-
* @returns {*} Processed value with lowercased strings
|
|
24400
|
-
*/
|
|
24401
|
-
var lowercaseKeysAndValues = function(val) {
|
|
24402
|
-
return lowercaseJson(val, true);
|
|
24403
|
-
};
|
|
24404
|
-
|
|
24405
|
-
/**
|
|
24406
|
-
* Lowercase only leaf node string values in a nested structure (keys unchanged)
|
|
24407
|
-
* @param {*} val - Value to process
|
|
24408
|
-
* @returns {*} Processed value with lowercased leaf strings
|
|
24409
|
-
*/
|
|
24410
|
-
var lowercaseOnlyLeafNodes = function(val) {
|
|
24411
|
-
return lowercaseJson(val, false);
|
|
24412
|
-
};
|
|
24413
|
-
|
|
24414
25048
|
/**
|
|
24415
25049
|
* Check if an event matches the given criteria
|
|
24416
25050
|
* @param {string} eventName - The name of the event being checked
|
|
@@ -24434,13 +25068,8 @@ define((function () { 'use strict';
|
|
|
24434
25068
|
|
|
24435
25069
|
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
24436
25070
|
try {
|
|
24437
|
-
//
|
|
24438
|
-
|
|
24439
|
-
|
|
24440
|
-
// Lowercase only leaf nodes in JsonLogic filters (keep operators intact)
|
|
24441
|
-
var lowercasedFilters = lowercaseOnlyLeafNodes(propertyFilters);
|
|
24442
|
-
|
|
24443
|
-
filtersMatch = jsonLogic.apply(lowercasedFilters, lowercasedProperties);
|
|
25071
|
+
// Use properties as-is for case-sensitive matching
|
|
25072
|
+
filtersMatch = jsonLogic.apply(propertyFilters, properties || {});
|
|
24444
25073
|
} catch (error) {
|
|
24445
25074
|
return {
|
|
24446
25075
|
matches: false,
|
|
@@ -24560,7 +25189,7 @@ define((function () { 'use strict';
|
|
|
24560
25189
|
observer.observe(shadowRoot, this.observerConfig);
|
|
24561
25190
|
this.shadowObservers.push(observer);
|
|
24562
25191
|
} catch (e) {
|
|
24563
|
-
logger$
|
|
25192
|
+
logger$4.critical('Error while observing shadow root', e);
|
|
24564
25193
|
}
|
|
24565
25194
|
};
|
|
24566
25195
|
|
|
@@ -24571,7 +25200,7 @@ define((function () { 'use strict';
|
|
|
24571
25200
|
}
|
|
24572
25201
|
|
|
24573
25202
|
if (!weakSetSupported()) {
|
|
24574
|
-
logger$
|
|
25203
|
+
logger$4.critical('Shadow DOM observation unavailable: WeakSet not supported');
|
|
24575
25204
|
return;
|
|
24576
25205
|
}
|
|
24577
25206
|
|
|
@@ -24587,7 +25216,7 @@ define((function () { 'use strict';
|
|
|
24587
25216
|
try {
|
|
24588
25217
|
this.shadowObservers[i].disconnect();
|
|
24589
25218
|
} catch (e) {
|
|
24590
|
-
logger$
|
|
25219
|
+
logger$4.critical('Error while disconnecting shadow DOM observer', e);
|
|
24591
25220
|
}
|
|
24592
25221
|
}
|
|
24593
25222
|
this.shadowObservers = [];
|
|
@@ -24775,7 +25404,7 @@ define((function () { 'use strict';
|
|
|
24775
25404
|
|
|
24776
25405
|
this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
|
|
24777
25406
|
} catch (e) {
|
|
24778
|
-
logger$
|
|
25407
|
+
logger$4.critical('Error while setting up mutation observer', e);
|
|
24779
25408
|
}
|
|
24780
25409
|
}
|
|
24781
25410
|
|
|
@@ -24790,7 +25419,7 @@ define((function () { 'use strict';
|
|
|
24790
25419
|
);
|
|
24791
25420
|
this.shadowDOMObserver.start();
|
|
24792
25421
|
} catch (e) {
|
|
24793
|
-
logger$
|
|
25422
|
+
logger$4.critical('Error while setting up shadow DOM observer', e);
|
|
24794
25423
|
this.shadowDOMObserver = null;
|
|
24795
25424
|
}
|
|
24796
25425
|
}
|
|
@@ -24817,7 +25446,7 @@ define((function () { 'use strict';
|
|
|
24817
25446
|
try {
|
|
24818
25447
|
listener.target.removeEventListener(listener.event, listener.handler, listener.options);
|
|
24819
25448
|
} catch (e) {
|
|
24820
|
-
logger$
|
|
25449
|
+
logger$4.critical('Error while removing event listener', e);
|
|
24821
25450
|
}
|
|
24822
25451
|
}
|
|
24823
25452
|
this.eventListeners = [];
|
|
@@ -24826,7 +25455,7 @@ define((function () { 'use strict';
|
|
|
24826
25455
|
try {
|
|
24827
25456
|
this.mutationObserver.disconnect();
|
|
24828
25457
|
} catch (e) {
|
|
24829
|
-
logger$
|
|
25458
|
+
logger$4.critical('Error while disconnecting mutation observer', e);
|
|
24830
25459
|
}
|
|
24831
25460
|
this.mutationObserver = null;
|
|
24832
25461
|
}
|
|
@@ -24835,7 +25464,7 @@ define((function () { 'use strict';
|
|
|
24835
25464
|
try {
|
|
24836
25465
|
this.shadowDOMObserver.stop();
|
|
24837
25466
|
} catch (e) {
|
|
24838
|
-
logger$
|
|
25467
|
+
logger$4.critical('Error while stopping shadow DOM observer', e);
|
|
24839
25468
|
}
|
|
24840
25469
|
this.shadowDOMObserver = null;
|
|
24841
25470
|
}
|
|
@@ -24913,7 +25542,7 @@ define((function () { 'use strict';
|
|
|
24913
25542
|
|
|
24914
25543
|
Autocapture.prototype.init = function() {
|
|
24915
25544
|
if (!minDOMApisSupported()) {
|
|
24916
|
-
logger$
|
|
25545
|
+
logger$4.critical('Autocapture unavailable: missing required DOM APIs');
|
|
24917
25546
|
return;
|
|
24918
25547
|
}
|
|
24919
25548
|
this.initPageListeners();
|
|
@@ -24945,27 +25574,15 @@ define((function () { 'use strict';
|
|
|
24945
25574
|
};
|
|
24946
25575
|
|
|
24947
25576
|
Autocapture.prototype.currentUrlBlocked = function() {
|
|
24948
|
-
var i;
|
|
24949
25577
|
var currentUrl = _.info.currentUrl();
|
|
24950
25578
|
|
|
24951
25579
|
var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
|
|
24952
25580
|
if (allowUrlRegexes.length) {
|
|
24953
25581
|
// we're using an allowlist, only track if current URL matches
|
|
24954
|
-
|
|
24955
|
-
|
|
24956
|
-
|
|
24957
|
-
|
|
24958
|
-
if (currentUrl.match(allowRegex)) {
|
|
24959
|
-
allowed = true;
|
|
24960
|
-
break;
|
|
24961
|
-
}
|
|
24962
|
-
} catch (err) {
|
|
24963
|
-
logger$3.critical('Error while checking block URL regex: ' + allowRegex, err);
|
|
24964
|
-
return true;
|
|
24965
|
-
}
|
|
24966
|
-
}
|
|
24967
|
-
if (!allowed) {
|
|
24968
|
-
// wasn't allowed by any regex
|
|
25582
|
+
try {
|
|
25583
|
+
return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
|
|
25584
|
+
} catch (err) {
|
|
25585
|
+
logger$4.critical('Error while checking block URL regexes: ', err);
|
|
24969
25586
|
return true;
|
|
24970
25587
|
}
|
|
24971
25588
|
}
|
|
@@ -24975,17 +25592,12 @@ define((function () { 'use strict';
|
|
|
24975
25592
|
return false;
|
|
24976
25593
|
}
|
|
24977
25594
|
|
|
24978
|
-
|
|
24979
|
-
|
|
24980
|
-
|
|
24981
|
-
|
|
24982
|
-
|
|
24983
|
-
} catch (err) {
|
|
24984
|
-
logger$3.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
|
|
24985
|
-
return true;
|
|
24986
|
-
}
|
|
25595
|
+
try {
|
|
25596
|
+
return urlMatchesRegexList(currentUrl, blockUrlRegexes);
|
|
25597
|
+
} catch (err) {
|
|
25598
|
+
logger$4.critical('Error while checking block URL regexes: ', err);
|
|
25599
|
+
return true;
|
|
24987
25600
|
}
|
|
24988
|
-
return false;
|
|
24989
25601
|
};
|
|
24990
25602
|
|
|
24991
25603
|
Autocapture.prototype.pageviewTrackingConfig = function() {
|
|
@@ -25121,7 +25733,7 @@ define((function () { 'use strict';
|
|
|
25121
25733
|
return;
|
|
25122
25734
|
}
|
|
25123
25735
|
|
|
25124
|
-
logger$
|
|
25736
|
+
logger$4.log('Initializing scroll depth tracking');
|
|
25125
25737
|
|
|
25126
25738
|
this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
|
|
25127
25739
|
|
|
@@ -25147,7 +25759,7 @@ define((function () { 'use strict';
|
|
|
25147
25759
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
|
|
25148
25760
|
return;
|
|
25149
25761
|
}
|
|
25150
|
-
logger$
|
|
25762
|
+
logger$4.log('Initializing click tracking');
|
|
25151
25763
|
|
|
25152
25764
|
this.listenerClick = function(ev) {
|
|
25153
25765
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
|
|
@@ -25166,7 +25778,7 @@ define((function () { 'use strict';
|
|
|
25166
25778
|
return;
|
|
25167
25779
|
}
|
|
25168
25780
|
|
|
25169
|
-
logger$
|
|
25781
|
+
logger$4.log('Initializing dead click tracking');
|
|
25170
25782
|
if (!this._deadClickTracker) {
|
|
25171
25783
|
this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
|
|
25172
25784
|
this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
|
|
@@ -25200,7 +25812,7 @@ define((function () { 'use strict';
|
|
|
25200
25812
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
25201
25813
|
return;
|
|
25202
25814
|
}
|
|
25203
|
-
logger$
|
|
25815
|
+
logger$4.log('Initializing input tracking');
|
|
25204
25816
|
|
|
25205
25817
|
this.listenerChange = function(ev) {
|
|
25206
25818
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
@@ -25217,7 +25829,7 @@ define((function () { 'use strict';
|
|
|
25217
25829
|
if (!this.pageviewTrackingConfig()) {
|
|
25218
25830
|
return;
|
|
25219
25831
|
}
|
|
25220
|
-
logger$
|
|
25832
|
+
logger$4.log('Initializing pageview tracking');
|
|
25221
25833
|
|
|
25222
25834
|
var previousTrackedUrl = '';
|
|
25223
25835
|
var tracked = false;
|
|
@@ -25252,7 +25864,7 @@ define((function () { 'use strict';
|
|
|
25252
25864
|
}
|
|
25253
25865
|
if (didPathChange) {
|
|
25254
25866
|
this.lastScrollCheckpoint = 0;
|
|
25255
|
-
logger$
|
|
25867
|
+
logger$4.log('Path change: re-initializing scroll depth checkpoints');
|
|
25256
25868
|
}
|
|
25257
25869
|
}
|
|
25258
25870
|
}.bind(this));
|
|
@@ -25267,7 +25879,7 @@ define((function () { 'use strict';
|
|
|
25267
25879
|
return;
|
|
25268
25880
|
}
|
|
25269
25881
|
|
|
25270
|
-
logger$
|
|
25882
|
+
logger$4.log('Initializing rage click tracking');
|
|
25271
25883
|
if (!this._rageClickTracker) {
|
|
25272
25884
|
this._rageClickTracker = new RageClickTracker();
|
|
25273
25885
|
}
|
|
@@ -25297,7 +25909,7 @@ define((function () { 'use strict';
|
|
|
25297
25909
|
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
25298
25910
|
return;
|
|
25299
25911
|
}
|
|
25300
|
-
logger$
|
|
25912
|
+
logger$4.log('Initializing scroll tracking');
|
|
25301
25913
|
this.lastScrollCheckpoint = 0;
|
|
25302
25914
|
|
|
25303
25915
|
var scrollTrackFunction = function() {
|
|
@@ -25334,7 +25946,7 @@ define((function () { 'use strict';
|
|
|
25334
25946
|
}
|
|
25335
25947
|
}
|
|
25336
25948
|
} catch (err) {
|
|
25337
|
-
logger$
|
|
25949
|
+
logger$4.critical('Error while calculating scroll percentage', err);
|
|
25338
25950
|
}
|
|
25339
25951
|
if (shouldTrack) {
|
|
25340
25952
|
this.mp.track(MP_EV_SCROLL, props);
|
|
@@ -25352,7 +25964,7 @@ define((function () { 'use strict';
|
|
|
25352
25964
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
25353
25965
|
return;
|
|
25354
25966
|
}
|
|
25355
|
-
logger$
|
|
25967
|
+
logger$4.log('Initializing submit tracking');
|
|
25356
25968
|
|
|
25357
25969
|
this.listenerSubmit = function(ev) {
|
|
25358
25970
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
@@ -25374,7 +25986,7 @@ define((function () { 'use strict';
|
|
|
25374
25986
|
return;
|
|
25375
25987
|
}
|
|
25376
25988
|
|
|
25377
|
-
logger$
|
|
25989
|
+
logger$4.log('Initializing page visibility tracking.');
|
|
25378
25990
|
this._initScrollDepthTracking();
|
|
25379
25991
|
var previousTrackedUrl = _.info.currentUrl();
|
|
25380
25992
|
|
|
@@ -25982,6 +26594,214 @@ define((function () { 'use strict';
|
|
|
25982
26594
|
/* eslint camelcase: "off" */
|
|
25983
26595
|
|
|
25984
26596
|
|
|
26597
|
+
/**
|
|
26598
|
+
* RecorderManager: manages session recording initialization, lifecycle and state
|
|
26599
|
+
* @constructor
|
|
26600
|
+
*/
|
|
26601
|
+
var RecorderManager = function(initOptions) {
|
|
26602
|
+
// TODO - Passing in mixpanel instance as it is still needed for recorder creation
|
|
26603
|
+
// but ideally we should be able to remove this dependency.
|
|
26604
|
+
this.mixpanelInstance = initOptions.mixpanelInstance;
|
|
26605
|
+
|
|
26606
|
+
this.getMpConfig = initOptions.getConfigFunc;
|
|
26607
|
+
this.getTabId = initOptions.getTabIdFunc;
|
|
26608
|
+
this.reportError = initOptions.reportErrorFunc;
|
|
26609
|
+
this.getDistinctId = initOptions.getDistinctIdFunc;
|
|
26610
|
+
this.loadExtraBundle = initOptions.loadExtraBundle;
|
|
26611
|
+
this.recorderSrc = initOptions.recorderSrc;
|
|
26612
|
+
this.targetingSrc = initOptions.targetingSrc;
|
|
26613
|
+
this.libBasePath = initOptions.libBasePath;
|
|
26614
|
+
|
|
26615
|
+
this._recorder = null;
|
|
26616
|
+
};
|
|
26617
|
+
|
|
26618
|
+
RecorderManager.prototype.shouldLoadRecorder = function() {
|
|
26619
|
+
if (this.getMpConfig('disable_persistence')) {
|
|
26620
|
+
console$1.log('Load recorder check skipped due to disable_persistence config');
|
|
26621
|
+
return PromisePolyfill.resolve(false);
|
|
26622
|
+
}
|
|
26623
|
+
|
|
26624
|
+
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
26625
|
+
var tab_id = this.getTabId();
|
|
26626
|
+
return recording_registry_idb.init()
|
|
26627
|
+
.then(function () {
|
|
26628
|
+
return recording_registry_idb.getAll();
|
|
26629
|
+
})
|
|
26630
|
+
.then(function (recordings) {
|
|
26631
|
+
for (var i = 0; i < recordings.length; i++) {
|
|
26632
|
+
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
26633
|
+
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
26634
|
+
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
26635
|
+
return true;
|
|
26636
|
+
}
|
|
26637
|
+
}
|
|
26638
|
+
return false;
|
|
26639
|
+
})
|
|
26640
|
+
.catch(_.bind(function (err) {
|
|
26641
|
+
this.reportError('Error checking recording registry', err);
|
|
26642
|
+
return false;
|
|
26643
|
+
}, this));
|
|
26644
|
+
};
|
|
26645
|
+
|
|
26646
|
+
RecorderManager.prototype.checkAndStartSessionRecording = function(force_start, rate) {
|
|
26647
|
+
if (!win['MutationObserver']) {
|
|
26648
|
+
console$1.critical('Browser does not support MutationObserver; skipping session recording');
|
|
26649
|
+
return PromisePolyfill.resolve();
|
|
26650
|
+
}
|
|
26651
|
+
|
|
26652
|
+
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
26653
|
+
return new PromisePolyfill(_.bind(function(resolve) {
|
|
26654
|
+
var handleLoadedRecorder = safewrap(_.bind(function() {
|
|
26655
|
+
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this.mixpanelInstance);
|
|
26656
|
+
this._recorder['resumeRecording'](startNewIfInactive);
|
|
26657
|
+
resolve();
|
|
26658
|
+
}, this));
|
|
26659
|
+
|
|
26660
|
+
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
26661
|
+
var recorderSrc = this.recorderSrc || (this.libBasePath + RECORDER_FILENAME);
|
|
26662
|
+
this.loadExtraBundle(recorderSrc, handleLoadedRecorder);
|
|
26663
|
+
} else {
|
|
26664
|
+
handleLoadedRecorder();
|
|
26665
|
+
}
|
|
26666
|
+
}, this));
|
|
26667
|
+
}, this);
|
|
26668
|
+
|
|
26669
|
+
/**
|
|
26670
|
+
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
26671
|
+
* 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.
|
|
26672
|
+
*/
|
|
26673
|
+
var effective_rate = _.isUndefined(rate) ? this.getMpConfig('record_sessions_percent') : rate;
|
|
26674
|
+
var is_sampled = effective_rate > 0 && Math.random() * 100 <= effective_rate;
|
|
26675
|
+
if (force_start || is_sampled) {
|
|
26676
|
+
return loadRecorder(true);
|
|
26677
|
+
} else {
|
|
26678
|
+
return this.shouldLoadRecorder()
|
|
26679
|
+
.then(_.bind(function (shouldLoad) {
|
|
26680
|
+
if (shouldLoad) {
|
|
26681
|
+
return loadRecorder(false);
|
|
26682
|
+
}
|
|
26683
|
+
return PromisePolyfill.resolve();
|
|
26684
|
+
}, this));
|
|
26685
|
+
}
|
|
26686
|
+
};
|
|
26687
|
+
|
|
26688
|
+
RecorderManager.prototype.isRecording = function() {
|
|
26689
|
+
// Safety check: ensure isRecording method exists (older CDN builds may not have it)
|
|
26690
|
+
if (!this._recorder || !_.isFunction(this._recorder['isRecording'])) {
|
|
26691
|
+
return false;
|
|
26692
|
+
}
|
|
26693
|
+
try {
|
|
26694
|
+
return this._recorder['isRecording']();
|
|
26695
|
+
} catch (e) {
|
|
26696
|
+
this.reportError('Error checking if recording is active', e);
|
|
26697
|
+
return false;
|
|
26698
|
+
}
|
|
26699
|
+
};
|
|
26700
|
+
|
|
26701
|
+
RecorderManager.prototype.startRecordingOnEvent = function(event_name, properties) {
|
|
26702
|
+
var isRecording = this.isRecording();
|
|
26703
|
+
var recordingTriggerEvents = this.getMpConfig('recording_event_triggers');
|
|
26704
|
+
|
|
26705
|
+
if (!isRecording && recordingTriggerEvents) {
|
|
26706
|
+
var trigger = recordingTriggerEvents[event_name];
|
|
26707
|
+
if (trigger && typeof trigger['percentage'] === 'number') {
|
|
26708
|
+
var newRate = trigger['percentage'];
|
|
26709
|
+
var propertyFilters = trigger['property_filters'];
|
|
26710
|
+
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
26711
|
+
var targetingSrc = this.targetingSrc || (this.libBasePath + TARGETING_FILENAME);
|
|
26712
|
+
getTargetingPromise(this.loadExtraBundle, targetingSrc)
|
|
26713
|
+
.then(function(targeting) {
|
|
26714
|
+
try {
|
|
26715
|
+
var result = targeting['eventMatchesCriteria'](
|
|
26716
|
+
event_name,
|
|
26717
|
+
properties,
|
|
26718
|
+
{
|
|
26719
|
+
'event_name': event_name,
|
|
26720
|
+
'property_filters': propertyFilters
|
|
26721
|
+
}
|
|
26722
|
+
);
|
|
26723
|
+
if (result['matches']) {
|
|
26724
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26725
|
+
}
|
|
26726
|
+
} catch (err) {
|
|
26727
|
+
console$1.critical('Could not parse recording event trigger properties logic:', err);
|
|
26728
|
+
}
|
|
26729
|
+
}.bind(this)).catch(function(err) {
|
|
26730
|
+
console$1.critical('Failed to load targeting library:', err);
|
|
26731
|
+
});
|
|
26732
|
+
} else {
|
|
26733
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26734
|
+
}
|
|
26735
|
+
}
|
|
26736
|
+
}
|
|
26737
|
+
};
|
|
26738
|
+
|
|
26739
|
+
RecorderManager.prototype.stopSessionRecording = function() {
|
|
26740
|
+
if (this._recorder) {
|
|
26741
|
+
return this._recorder['stopRecording']();
|
|
26742
|
+
}
|
|
26743
|
+
return PromisePolyfill.resolve();
|
|
26744
|
+
};
|
|
26745
|
+
|
|
26746
|
+
RecorderManager.prototype.pauseSessionRecording = function() {
|
|
26747
|
+
if (this._recorder) {
|
|
26748
|
+
return this._recorder['pauseRecording']();
|
|
26749
|
+
}
|
|
26750
|
+
return PromisePolyfill.resolve();
|
|
26751
|
+
};
|
|
26752
|
+
|
|
26753
|
+
RecorderManager.prototype.resumeSessionRecording = function() {
|
|
26754
|
+
if (this._recorder) {
|
|
26755
|
+
return this._recorder['resumeRecording']();
|
|
26756
|
+
}
|
|
26757
|
+
return PromisePolyfill.resolve();
|
|
26758
|
+
};
|
|
26759
|
+
|
|
26760
|
+
RecorderManager.prototype.isRecordingHeatmapData = function() {
|
|
26761
|
+
return this.getSessionReplayId() && this.getMpConfig('record_heatmap_data');
|
|
26762
|
+
};
|
|
26763
|
+
|
|
26764
|
+
RecorderManager.prototype.getSessionRecordingProperties = function() {
|
|
26765
|
+
var props = {};
|
|
26766
|
+
var replay_id = this.getSessionReplayId();
|
|
26767
|
+
if (replay_id) {
|
|
26768
|
+
props['$mp_replay_id'] = replay_id;
|
|
26769
|
+
}
|
|
26770
|
+
return props;
|
|
26771
|
+
};
|
|
26772
|
+
|
|
26773
|
+
RecorderManager.prototype.getSessionReplayUrl = function() {
|
|
26774
|
+
var replay_url = null;
|
|
26775
|
+
var replay_id = this.getSessionReplayId();
|
|
26776
|
+
if (replay_id) {
|
|
26777
|
+
var query_params = _.HTTPBuildQuery({
|
|
26778
|
+
'replay_id': replay_id,
|
|
26779
|
+
'distinct_id': this.getDistinctId(),
|
|
26780
|
+
'token': this.getMpConfig('token')
|
|
26781
|
+
});
|
|
26782
|
+
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
26783
|
+
}
|
|
26784
|
+
return replay_url;
|
|
26785
|
+
};
|
|
26786
|
+
|
|
26787
|
+
RecorderManager.prototype.getSessionReplayId = function() {
|
|
26788
|
+
var replay_id = null;
|
|
26789
|
+
if (this._recorder) {
|
|
26790
|
+
replay_id = this._recorder['replayId'];
|
|
26791
|
+
}
|
|
26792
|
+
return replay_id || null;
|
|
26793
|
+
};
|
|
26794
|
+
|
|
26795
|
+
// "private" public method to reach into the recorder in test cases
|
|
26796
|
+
RecorderManager.prototype.getRecorder = function() {
|
|
26797
|
+
return this._recorder;
|
|
26798
|
+
};
|
|
26799
|
+
|
|
26800
|
+
safewrapClass(RecorderManager);
|
|
26801
|
+
|
|
26802
|
+
/* eslint camelcase: "off" */
|
|
26803
|
+
|
|
26804
|
+
|
|
25985
26805
|
/**
|
|
25986
26806
|
* DomTracker Object
|
|
25987
26807
|
* @constructor
|
|
@@ -27442,13 +28262,17 @@ define((function () { 'use strict';
|
|
|
27442
28262
|
'record_collect_fonts': false,
|
|
27443
28263
|
'record_console': true,
|
|
27444
28264
|
'record_heatmap_data': false,
|
|
28265
|
+
'recording_event_triggers': {},
|
|
27445
28266
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
27446
28267
|
'record_mask_inputs': true,
|
|
27447
28268
|
'record_max_ms': MAX_RECORDING_MS,
|
|
27448
28269
|
'record_min_ms': 0,
|
|
28270
|
+
'record_network': false,
|
|
28271
|
+
'record_network_options': {},
|
|
27449
28272
|
'record_sessions_percent': 0,
|
|
27450
|
-
'recorder_src':
|
|
27451
|
-
'targeting_src':
|
|
28273
|
+
'recorder_src': null,
|
|
28274
|
+
'targeting_src': null,
|
|
28275
|
+
'lib_base_path': 'https://cdn.mxpnl.com/libs/',
|
|
27452
28276
|
'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
|
|
27453
28277
|
};
|
|
27454
28278
|
|
|
@@ -27602,6 +28426,19 @@ define((function () { 'use strict';
|
|
|
27602
28426
|
'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
|
|
27603
28427
|
}));
|
|
27604
28428
|
|
|
28429
|
+
this.recorderManager = new RecorderManager({
|
|
28430
|
+
mixpanelInstance: this,
|
|
28431
|
+
getConfigFunc: _.bind(this.get_config, this),
|
|
28432
|
+
setConfigFunc: _.bind(this.set_config, this),
|
|
28433
|
+
getTabIdFunc: _.bind(this.get_tab_id, this),
|
|
28434
|
+
reportErrorFunc: _.bind(this.report_error, this),
|
|
28435
|
+
getDistinctIdFunc: _.bind(this.get_distinct_id, this),
|
|
28436
|
+
recorderSrc: this.get_config('recorder_src'),
|
|
28437
|
+
targetingSrc: this.get_config('targeting_src'),
|
|
28438
|
+
libBasePath: this.get_config('lib_base_path'),
|
|
28439
|
+
loadExtraBundle: load_extra_bundle
|
|
28440
|
+
});
|
|
28441
|
+
|
|
27605
28442
|
this['_jsc'] = NOOP_FUNC;
|
|
27606
28443
|
|
|
27607
28444
|
this.__dom_loaded_queue = [];
|
|
@@ -27680,7 +28517,7 @@ define((function () { 'use strict';
|
|
|
27680
28517
|
getPropertyFunc: _.bind(this.get_property, this),
|
|
27681
28518
|
trackingFunc: _.bind(this.track, this),
|
|
27682
28519
|
loadExtraBundle: load_extra_bundle,
|
|
27683
|
-
targetingSrc: this.get_config('targeting_src')
|
|
28520
|
+
targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
|
|
27684
28521
|
});
|
|
27685
28522
|
this.flags.init();
|
|
27686
28523
|
this['flags'] = this.flags;
|
|
@@ -27693,11 +28530,11 @@ define((function () { 'use strict';
|
|
|
27693
28530
|
// Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
|
|
27694
28531
|
var mode = this.get_config('remote_settings_mode');
|
|
27695
28532
|
if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
|
|
27696
|
-
this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
27697
|
-
this._check_and_start_session_recording();
|
|
28533
|
+
this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
28534
|
+
return this._check_and_start_session_recording();
|
|
27698
28535
|
}, this));
|
|
27699
28536
|
} else {
|
|
27700
|
-
this._check_and_start_session_recording();
|
|
28537
|
+
this.__session_recording_init_promise = this._check_and_start_session_recording();
|
|
27701
28538
|
}
|
|
27702
28539
|
};
|
|
27703
28540
|
|
|
@@ -27741,132 +28578,50 @@ define((function () { 'use strict';
|
|
|
27741
28578
|
return this.tab_id || null;
|
|
27742
28579
|
};
|
|
27743
28580
|
|
|
27744
|
-
MixpanelLib.prototype._should_load_recorder = function () {
|
|
27745
|
-
if (this.get_config('disable_persistence')) {
|
|
27746
|
-
console$1.log('Load recorder check skipped due to disable_persistence config');
|
|
27747
|
-
return Promise.resolve(false);
|
|
27748
|
-
}
|
|
27749
|
-
|
|
27750
|
-
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
27751
|
-
var tab_id = this.get_tab_id();
|
|
27752
|
-
return recording_registry_idb.init()
|
|
27753
|
-
.then(function () {
|
|
27754
|
-
return recording_registry_idb.getAll();
|
|
27755
|
-
})
|
|
27756
|
-
.then(function (recordings) {
|
|
27757
|
-
for (var i = 0; i < recordings.length; i++) {
|
|
27758
|
-
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
27759
|
-
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
27760
|
-
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
27761
|
-
return true;
|
|
27762
|
-
}
|
|
27763
|
-
}
|
|
27764
|
-
return false;
|
|
27765
|
-
})
|
|
27766
|
-
.catch(_.bind(function (err) {
|
|
27767
|
-
this.report_error('Error checking recording registry', err);
|
|
27768
|
-
}, this));
|
|
27769
|
-
};
|
|
27770
|
-
|
|
27771
28581
|
MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
|
|
27772
|
-
|
|
27773
|
-
console$1.critical('Browser does not support MutationObserver; skipping session recording');
|
|
27774
|
-
return;
|
|
27775
|
-
}
|
|
27776
|
-
|
|
27777
|
-
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
27778
|
-
var handleLoadedRecorder = _.bind(function() {
|
|
27779
|
-
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this);
|
|
27780
|
-
this._recorder['resumeRecording'](startNewIfInactive);
|
|
27781
|
-
}, this);
|
|
27782
|
-
|
|
27783
|
-
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
27784
|
-
load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
|
|
27785
|
-
} else {
|
|
27786
|
-
handleLoadedRecorder();
|
|
27787
|
-
}
|
|
27788
|
-
}, this);
|
|
27789
|
-
|
|
27790
|
-
/**
|
|
27791
|
-
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
27792
|
-
* 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.
|
|
27793
|
-
*/
|
|
27794
|
-
var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
|
|
27795
|
-
if (force_start || is_sampled) {
|
|
27796
|
-
loadRecorder(true);
|
|
27797
|
-
} else {
|
|
27798
|
-
this._should_load_recorder()
|
|
27799
|
-
.then(function (shouldLoad) {
|
|
27800
|
-
if (shouldLoad) {
|
|
27801
|
-
loadRecorder(false);
|
|
27802
|
-
}
|
|
27803
|
-
});
|
|
27804
|
-
}
|
|
28582
|
+
return this.recorderManager.checkAndStartSessionRecording(force_start);
|
|
27805
28583
|
});
|
|
27806
28584
|
|
|
28585
|
+
MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
|
|
28586
|
+
return this.recorderManager.startRecordingOnEvent(event_name, properties);
|
|
28587
|
+
};
|
|
28588
|
+
|
|
27807
28589
|
MixpanelLib.prototype.start_session_recording = function () {
|
|
27808
|
-
this._check_and_start_session_recording(true);
|
|
28590
|
+
return this._check_and_start_session_recording(true);
|
|
27809
28591
|
};
|
|
27810
28592
|
|
|
27811
28593
|
MixpanelLib.prototype.stop_session_recording = function () {
|
|
27812
|
-
|
|
27813
|
-
return this._recorder['stopRecording']();
|
|
27814
|
-
}
|
|
27815
|
-
return Promise.resolve();
|
|
28594
|
+
return this.recorderManager.stopSessionRecording();
|
|
27816
28595
|
};
|
|
27817
28596
|
|
|
27818
28597
|
MixpanelLib.prototype.pause_session_recording = function () {
|
|
27819
|
-
|
|
27820
|
-
return this._recorder['pauseRecording']();
|
|
27821
|
-
}
|
|
27822
|
-
return Promise.resolve();
|
|
28598
|
+
return this.recorderManager.pauseSessionRecording();
|
|
27823
28599
|
};
|
|
27824
28600
|
|
|
27825
28601
|
MixpanelLib.prototype.resume_session_recording = function () {
|
|
27826
|
-
|
|
27827
|
-
return this._recorder['resumeRecording']();
|
|
27828
|
-
}
|
|
27829
|
-
return Promise.resolve();
|
|
28602
|
+
return this.recorderManager.resumeSessionRecording();
|
|
27830
28603
|
};
|
|
27831
28604
|
|
|
27832
28605
|
MixpanelLib.prototype.is_recording_heatmap_data = function () {
|
|
27833
|
-
return this.
|
|
28606
|
+
return this.recorderManager.isRecordingHeatmapData();
|
|
27834
28607
|
};
|
|
27835
28608
|
|
|
27836
28609
|
MixpanelLib.prototype.get_session_recording_properties = function () {
|
|
27837
|
-
|
|
27838
|
-
var replay_id = this._get_session_replay_id();
|
|
27839
|
-
if (replay_id) {
|
|
27840
|
-
props['$mp_replay_id'] = replay_id;
|
|
27841
|
-
}
|
|
27842
|
-
return props;
|
|
28610
|
+
return this.recorderManager.getSessionRecordingProperties();
|
|
27843
28611
|
};
|
|
27844
28612
|
|
|
27845
28613
|
MixpanelLib.prototype.get_session_replay_url = function () {
|
|
27846
|
-
|
|
27847
|
-
var replay_id = this._get_session_replay_id();
|
|
27848
|
-
if (replay_id) {
|
|
27849
|
-
var query_params = _.HTTPBuildQuery({
|
|
27850
|
-
'replay_id': replay_id,
|
|
27851
|
-
'distinct_id': this.get_distinct_id(),
|
|
27852
|
-
'token': this.get_config('token')
|
|
27853
|
-
});
|
|
27854
|
-
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
27855
|
-
}
|
|
27856
|
-
return replay_url;
|
|
27857
|
-
};
|
|
27858
|
-
|
|
27859
|
-
MixpanelLib.prototype._get_session_replay_id = function () {
|
|
27860
|
-
var replay_id = null;
|
|
27861
|
-
if (this._recorder) {
|
|
27862
|
-
replay_id = this._recorder['replayId'];
|
|
27863
|
-
}
|
|
27864
|
-
return replay_id || null;
|
|
28614
|
+
return this.recorderManager.getSessionReplayUrl();
|
|
27865
28615
|
};
|
|
27866
28616
|
|
|
27867
28617
|
// "private" public method to reach into the recorder in test cases
|
|
27868
28618
|
MixpanelLib.prototype.__get_recorder = function () {
|
|
27869
|
-
return this.
|
|
28619
|
+
return this.recorderManager.getRecorder();
|
|
28620
|
+
};
|
|
28621
|
+
|
|
28622
|
+
// "private" public method to get session recording init promise in test cases
|
|
28623
|
+
MixpanelLib.prototype.__get_recording_init_promise = function () {
|
|
28624
|
+
return this.__session_recording_init_promise;
|
|
27870
28625
|
};
|
|
27871
28626
|
|
|
27872
28627
|
// Private methods
|
|
@@ -28124,6 +28879,7 @@ define((function () { 'use strict';
|
|
|
28124
28879
|
};
|
|
28125
28880
|
|
|
28126
28881
|
MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
28882
|
+
var self = this;
|
|
28127
28883
|
var disableRecordingIfStrict = function() {
|
|
28128
28884
|
if (mode === 'strict') {
|
|
28129
28885
|
self.set_config({'record_sessions_percent': 0});
|
|
@@ -28144,7 +28900,6 @@ define((function () { 'use strict';
|
|
|
28144
28900
|
};
|
|
28145
28901
|
var query_string = _.HTTPBuildQuery(request_params);
|
|
28146
28902
|
var full_url = settings_endpoint + '?' + query_string;
|
|
28147
|
-
var self = this;
|
|
28148
28903
|
|
|
28149
28904
|
var abortController = new AbortController();
|
|
28150
28905
|
var timeout_id = setTimeout(function() {
|
|
@@ -28336,6 +29091,34 @@ define((function () { 'use strict';
|
|
|
28336
29091
|
this._execute_array([item]);
|
|
28337
29092
|
};
|
|
28338
29093
|
|
|
29094
|
+
/**
|
|
29095
|
+
* Enables events on the Mixpanel object. If passed no arguments,
|
|
29096
|
+
* this function enable tracking of all events. If passed an
|
|
29097
|
+
* array of event names, those events will be enabled, but other
|
|
29098
|
+
* existing disabled events will continue to be not tracked.
|
|
29099
|
+
*
|
|
29100
|
+
* @param {Array} [events] An array of event names to enable
|
|
29101
|
+
*/
|
|
29102
|
+
MixpanelLib.prototype.enable = function(events) {
|
|
29103
|
+
var keys, new_disabled_events, i, j;
|
|
29104
|
+
|
|
29105
|
+
if (typeof(events) === 'undefined') {
|
|
29106
|
+
this._flags.disable_all_events = false;
|
|
29107
|
+
} else {
|
|
29108
|
+
keys = {};
|
|
29109
|
+
new_disabled_events = [];
|
|
29110
|
+
for (i = 0; i < events.length; i++) {
|
|
29111
|
+
keys[events[i]] = true;
|
|
29112
|
+
}
|
|
29113
|
+
for (j = 0; j < this.__disabled_events.length; j++) {
|
|
29114
|
+
if (!keys[this.__disabled_events[j]]) {
|
|
29115
|
+
new_disabled_events.push(this.__disabled_events[j]);
|
|
29116
|
+
}
|
|
29117
|
+
}
|
|
29118
|
+
this.__disabled_events = new_disabled_events;
|
|
29119
|
+
}
|
|
29120
|
+
};
|
|
29121
|
+
|
|
28339
29122
|
/**
|
|
28340
29123
|
* Disable events on the Mixpanel object. If passed no arguments,
|
|
28341
29124
|
* this function disables tracking of any event. If passed an
|
|
@@ -28509,6 +29292,8 @@ define((function () { 'use strict';
|
|
|
28509
29292
|
this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
|
|
28510
29293
|
}
|
|
28511
29294
|
|
|
29295
|
+
this._start_recording_on_event(event_name, properties);
|
|
29296
|
+
|
|
28512
29297
|
var data = {
|
|
28513
29298
|
'event': event_name,
|
|
28514
29299
|
'properties': properties
|
|
@@ -29717,6 +30502,7 @@ define((function () { 'use strict';
|
|
|
29717
30502
|
// MixpanelLib Exports
|
|
29718
30503
|
MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
|
|
29719
30504
|
MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
|
|
30505
|
+
MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
|
|
29720
30506
|
MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
|
|
29721
30507
|
MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
|
|
29722
30508
|
MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
|
|
@@ -29760,6 +30546,7 @@ define((function () { 'use strict';
|
|
|
29760
30546
|
|
|
29761
30547
|
// Exports intended only for testing
|
|
29762
30548
|
MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
|
|
30549
|
+
MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
|
|
29763
30550
|
|
|
29764
30551
|
// MixpanelPersistence Exports
|
|
29765
30552
|
MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
|