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.globals.js
CHANGED
|
@@ -3,9 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
var Config = {
|
|
5
5
|
DEBUG: false,
|
|
6
|
-
LIB_VERSION: '2.
|
|
6
|
+
LIB_VERSION: '2.76.0'
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
// Window global names for async modules
|
|
10
|
+
var TARGETING_GLOBAL_NAME = '__mp_targeting';
|
|
11
|
+
var RECORDER_GLOBAL_NAME = '__mp_recorder';
|
|
12
|
+
|
|
13
|
+
// Constants that are injected at build-time for the names of async modules.
|
|
14
|
+
var RECORDER_FILENAME = 'mixpanel-recorder-bIS4LMGd.js';
|
|
15
|
+
var TARGETING_FILENAME = 'mixpanel-targeting-BcAPS-Mz.js';
|
|
16
|
+
|
|
9
17
|
// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
|
|
10
18
|
var win;
|
|
11
19
|
if (typeof(window) === 'undefined') {
|
|
@@ -2122,6 +2130,17 @@
|
|
|
2122
2130
|
|
|
2123
2131
|
var NOOP_FUNC = function () {};
|
|
2124
2132
|
|
|
2133
|
+
var urlMatchesRegexList = function (url, regexList) {
|
|
2134
|
+
var matches = false;
|
|
2135
|
+
for (var i = 0; i < regexList.length; i++) {
|
|
2136
|
+
if (url.match(regexList[i])) {
|
|
2137
|
+
matches = true;
|
|
2138
|
+
break;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
return matches;
|
|
2142
|
+
};
|
|
2143
|
+
|
|
2125
2144
|
var JSONStringify = null, JSONParse = null;
|
|
2126
2145
|
if (typeof JSON !== 'undefined') {
|
|
2127
2146
|
JSONStringify = JSON.stringify;
|
|
@@ -2144,25 +2163,6 @@
|
|
|
2144
2163
|
_['toArray'] = _.toArray;
|
|
2145
2164
|
_['NPO'] = NpoPromise;
|
|
2146
2165
|
|
|
2147
|
-
/**
|
|
2148
|
-
* @param {import('./session-recording').SerializedRecording} serializedRecording
|
|
2149
|
-
* @returns {boolean}
|
|
2150
|
-
*/
|
|
2151
|
-
var isRecordingExpired = function(serializedRecording) {
|
|
2152
|
-
var now = Date.now();
|
|
2153
|
-
return !serializedRecording || now > serializedRecording['maxExpires'] || now > serializedRecording['idleExpires'];
|
|
2154
|
-
};
|
|
2155
|
-
|
|
2156
|
-
/**
|
|
2157
|
-
* Shared global window property names used across modules
|
|
2158
|
-
*/
|
|
2159
|
-
|
|
2160
|
-
// Targeting library global (used by flags and targeting modules)
|
|
2161
|
-
var TARGETING_GLOBAL_NAME = '__mp_targeting';
|
|
2162
|
-
|
|
2163
|
-
// Recorder library global (used by recorder and mixpanel-core)
|
|
2164
|
-
var RECORDER_GLOBAL_NAME = '__mp_recorder';
|
|
2165
|
-
|
|
2166
2166
|
// stateless utils
|
|
2167
2167
|
// mostly from https://github.com/mixpanel/mixpanel-js/blob/989ada50f518edab47b9c4fd9535f9fbd5ec5fc0/src/autotrack-utils.js
|
|
2168
2168
|
|
|
@@ -3427,27 +3427,15 @@
|
|
|
3427
3427
|
};
|
|
3428
3428
|
|
|
3429
3429
|
Autocapture.prototype.currentUrlBlocked = function() {
|
|
3430
|
-
var i;
|
|
3431
3430
|
var currentUrl = _.info.currentUrl();
|
|
3432
3431
|
|
|
3433
3432
|
var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
|
|
3434
3433
|
if (allowUrlRegexes.length) {
|
|
3435
3434
|
// we're using an allowlist, only track if current URL matches
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
if (currentUrl.match(allowRegex)) {
|
|
3441
|
-
allowed = true;
|
|
3442
|
-
break;
|
|
3443
|
-
}
|
|
3444
|
-
} catch (err) {
|
|
3445
|
-
logger$4.critical('Error while checking block URL regex: ' + allowRegex, err);
|
|
3446
|
-
return true;
|
|
3447
|
-
}
|
|
3448
|
-
}
|
|
3449
|
-
if (!allowed) {
|
|
3450
|
-
// wasn't allowed by any regex
|
|
3435
|
+
try {
|
|
3436
|
+
return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
|
|
3437
|
+
} catch (err) {
|
|
3438
|
+
logger$4.critical('Error while checking block URL regexes: ', err);
|
|
3451
3439
|
return true;
|
|
3452
3440
|
}
|
|
3453
3441
|
}
|
|
@@ -3457,17 +3445,12 @@
|
|
|
3457
3445
|
return false;
|
|
3458
3446
|
}
|
|
3459
3447
|
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
} catch (err) {
|
|
3466
|
-
logger$4.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
|
|
3467
|
-
return true;
|
|
3468
|
-
}
|
|
3448
|
+
try {
|
|
3449
|
+
return urlMatchesRegexList(currentUrl, blockUrlRegexes);
|
|
3450
|
+
} catch (err) {
|
|
3451
|
+
logger$4.critical('Error while checking block URL regexes: ', err);
|
|
3452
|
+
return true;
|
|
3469
3453
|
}
|
|
3470
|
-
return false;
|
|
3471
3454
|
};
|
|
3472
3455
|
|
|
3473
3456
|
Autocapture.prototype.pageviewTrackingConfig = function() {
|
|
@@ -4461,146 +4444,490 @@
|
|
|
4461
4444
|
// Exports intended only for testing
|
|
4462
4445
|
FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
|
|
4463
4446
|
|
|
4464
|
-
|
|
4447
|
+
var MIXPANEL_DB_NAME = 'mixpanelBrowserDb';
|
|
4448
|
+
|
|
4449
|
+
var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
|
|
4450
|
+
var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
|
|
4465
4451
|
|
|
4452
|
+
// note: increment the version number when adding new object stores
|
|
4453
|
+
var DB_VERSION = 1;
|
|
4454
|
+
var OBJECT_STORES = [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME];
|
|
4466
4455
|
|
|
4467
4456
|
/**
|
|
4468
|
-
*
|
|
4469
|
-
* @constructor
|
|
4457
|
+
* @type {import('./wrapper').StorageWrapper}
|
|
4470
4458
|
*/
|
|
4471
|
-
var
|
|
4459
|
+
var IDBStorageWrapper = function (storeName) {
|
|
4460
|
+
/**
|
|
4461
|
+
* @type {Promise<IDBDatabase>|null}
|
|
4462
|
+
*/
|
|
4463
|
+
this.dbPromise = null;
|
|
4464
|
+
this.storeName = storeName;
|
|
4465
|
+
};
|
|
4466
|
+
|
|
4467
|
+
IDBStorageWrapper.prototype._openDb = function () {
|
|
4468
|
+
return new PromisePolyfill(function (resolve, reject) {
|
|
4469
|
+
var openRequest = win.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
|
|
4470
|
+
openRequest['onerror'] = function () {
|
|
4471
|
+
reject(openRequest.error);
|
|
4472
|
+
};
|
|
4472
4473
|
|
|
4474
|
+
openRequest['onsuccess'] = function () {
|
|
4475
|
+
resolve(openRequest.result);
|
|
4476
|
+
};
|
|
4473
4477
|
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
DomTracker.prototype.event_handler = function() {};
|
|
4477
|
-
DomTracker.prototype.after_track_handler = function() {};
|
|
4478
|
+
openRequest['onupgradeneeded'] = function (ev) {
|
|
4479
|
+
var db = ev.target.result;
|
|
4478
4480
|
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4481
|
+
OBJECT_STORES.forEach(function (storeName) {
|
|
4482
|
+
db.createObjectStore(storeName);
|
|
4483
|
+
});
|
|
4484
|
+
};
|
|
4485
|
+
});
|
|
4482
4486
|
};
|
|
4483
4487
|
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
* @param {Object=} properties
|
|
4488
|
-
* @param {function=} user_callback
|
|
4489
|
-
*/
|
|
4490
|
-
DomTracker.prototype.track = function(query, event_name, properties, user_callback) {
|
|
4491
|
-
var that = this;
|
|
4492
|
-
var elements = _.dom_query(query);
|
|
4493
|
-
|
|
4494
|
-
if (elements.length === 0) {
|
|
4495
|
-
console.error('The DOM query (' + query + ') returned 0 elements');
|
|
4496
|
-
return;
|
|
4488
|
+
IDBStorageWrapper.prototype.init = function () {
|
|
4489
|
+
if (!win.indexedDB) {
|
|
4490
|
+
return PromisePolyfill.reject('indexedDB is not supported in this browser');
|
|
4497
4491
|
}
|
|
4498
4492
|
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
var props = that.create_properties(properties, this);
|
|
4503
|
-
var timeout = that.mp.get_config('track_links_timeout');
|
|
4504
|
-
|
|
4505
|
-
that.event_handler(e, this, options);
|
|
4506
|
-
|
|
4507
|
-
// in case the mixpanel servers don't get back to us in time
|
|
4508
|
-
window.setTimeout(that.track_callback(user_callback, props, options, true), timeout);
|
|
4493
|
+
if (!this.dbPromise) {
|
|
4494
|
+
this.dbPromise = this._openDb();
|
|
4495
|
+
}
|
|
4509
4496
|
|
|
4510
|
-
|
|
4511
|
-
|
|
4497
|
+
return this.dbPromise
|
|
4498
|
+
.then(function (dbOrError) {
|
|
4499
|
+
if (dbOrError instanceof win['IDBDatabase']) {
|
|
4500
|
+
return PromisePolyfill.resolve();
|
|
4501
|
+
} else {
|
|
4502
|
+
return PromisePolyfill.reject(dbOrError);
|
|
4503
|
+
}
|
|
4512
4504
|
});
|
|
4513
|
-
|
|
4505
|
+
};
|
|
4514
4506
|
|
|
4515
|
-
|
|
4507
|
+
IDBStorageWrapper.prototype.isInitialized = function () {
|
|
4508
|
+
return !!this.dbPromise;
|
|
4516
4509
|
};
|
|
4517
4510
|
|
|
4518
4511
|
/**
|
|
4519
|
-
* @param {
|
|
4520
|
-
* @param {
|
|
4521
|
-
* @param {boolean=} timeout_occured
|
|
4512
|
+
* @param {IDBTransactionMode} mode
|
|
4513
|
+
* @param {function(IDBObjectStore): void} storeCb
|
|
4522
4514
|
*/
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
var
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
// user can prevent the default functionality by
|
|
4535
|
-
// returning false from their callback
|
|
4536
|
-
return;
|
|
4537
|
-
}
|
|
4515
|
+
IDBStorageWrapper.prototype.makeTransaction = function (mode, storeCb) {
|
|
4516
|
+
var storeName = this.storeName;
|
|
4517
|
+
var doTransaction = function (db) {
|
|
4518
|
+
return new PromisePolyfill(function (resolve, reject) {
|
|
4519
|
+
var transaction = db.transaction(storeName, mode);
|
|
4520
|
+
transaction.oncomplete = function () {
|
|
4521
|
+
resolve(transaction);
|
|
4522
|
+
};
|
|
4523
|
+
transaction.onabort = transaction.onerror = function () {
|
|
4524
|
+
reject(transaction.error);
|
|
4525
|
+
};
|
|
4538
4526
|
|
|
4539
|
-
|
|
4527
|
+
storeCb(transaction.objectStore(storeName));
|
|
4528
|
+
});
|
|
4540
4529
|
};
|
|
4541
|
-
};
|
|
4542
|
-
|
|
4543
|
-
DomTracker.prototype.create_properties = function(properties, element) {
|
|
4544
|
-
var props;
|
|
4545
4530
|
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4531
|
+
return this.dbPromise
|
|
4532
|
+
.then(doTransaction)
|
|
4533
|
+
.catch(function (err) {
|
|
4534
|
+
if (err && err['name'] === 'InvalidStateError') {
|
|
4535
|
+
// try reopening the DB if the connection is closed
|
|
4536
|
+
this.dbPromise = this._openDb();
|
|
4537
|
+
return this.dbPromise.then(doTransaction);
|
|
4538
|
+
} else {
|
|
4539
|
+
return PromisePolyfill.reject(err);
|
|
4540
|
+
}
|
|
4541
|
+
}.bind(this));
|
|
4553
4542
|
};
|
|
4554
4543
|
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
*/
|
|
4560
|
-
var LinkTracker = function() {
|
|
4561
|
-
this.override_event = 'click';
|
|
4544
|
+
IDBStorageWrapper.prototype.setItem = function (key, value) {
|
|
4545
|
+
return this.makeTransaction('readwrite', function (objectStore) {
|
|
4546
|
+
objectStore.put(value, key);
|
|
4547
|
+
});
|
|
4562
4548
|
};
|
|
4563
|
-
_.inherit(LinkTracker, DomTracker);
|
|
4564
|
-
|
|
4565
|
-
LinkTracker.prototype.create_properties = function(properties, element) {
|
|
4566
|
-
var props = LinkTracker.superclass.create_properties.apply(this, arguments);
|
|
4567
4549
|
|
|
4568
|
-
|
|
4550
|
+
IDBStorageWrapper.prototype.getItem = function (key) {
|
|
4551
|
+
var req;
|
|
4552
|
+
return this.makeTransaction('readonly', function (objectStore) {
|
|
4553
|
+
req = objectStore.get(key);
|
|
4554
|
+
}).then(function () {
|
|
4555
|
+
return req.result;
|
|
4556
|
+
});
|
|
4557
|
+
};
|
|
4569
4558
|
|
|
4570
|
-
|
|
4559
|
+
IDBStorageWrapper.prototype.removeItem = function (key) {
|
|
4560
|
+
return this.makeTransaction('readwrite', function (objectStore) {
|
|
4561
|
+
objectStore.delete(key);
|
|
4562
|
+
});
|
|
4571
4563
|
};
|
|
4572
4564
|
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
);
|
|
4580
|
-
|
|
4565
|
+
IDBStorageWrapper.prototype.getAll = function () {
|
|
4566
|
+
var req;
|
|
4567
|
+
return this.makeTransaction('readonly', function (objectStore) {
|
|
4568
|
+
req = objectStore.getAll();
|
|
4569
|
+
}).then(function () {
|
|
4570
|
+
return req.result;
|
|
4571
|
+
});
|
|
4572
|
+
};
|
|
4581
4573
|
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4574
|
+
/**
|
|
4575
|
+
* @param {import('./session-recording').SerializedRecording} serializedRecording
|
|
4576
|
+
* @returns {boolean}
|
|
4577
|
+
*/
|
|
4578
|
+
var isRecordingExpired = function(serializedRecording) {
|
|
4579
|
+
var now = Date.now();
|
|
4580
|
+
return !serializedRecording || now > serializedRecording['maxExpires'] || now > serializedRecording['idleExpires'];
|
|
4585
4581
|
};
|
|
4586
4582
|
|
|
4587
|
-
|
|
4588
|
-
if (options.new_tab) { return; }
|
|
4583
|
+
/* eslint camelcase: "off" */
|
|
4589
4584
|
|
|
4590
|
-
setTimeout(function() {
|
|
4591
|
-
window.location = options.href;
|
|
4592
|
-
}, 0);
|
|
4593
|
-
};
|
|
4594
4585
|
|
|
4595
4586
|
/**
|
|
4596
|
-
*
|
|
4587
|
+
* RecorderManager: manages session recording initialization, lifecycle and state
|
|
4597
4588
|
* @constructor
|
|
4598
|
-
* @extends DomTracker
|
|
4599
4589
|
*/
|
|
4600
|
-
var
|
|
4601
|
-
|
|
4590
|
+
var RecorderManager = function(initOptions) {
|
|
4591
|
+
// TODO - Passing in mixpanel instance as it is still needed for recorder creation
|
|
4592
|
+
// but ideally we should be able to remove this dependency.
|
|
4593
|
+
this.mixpanelInstance = initOptions.mixpanelInstance;
|
|
4594
|
+
|
|
4595
|
+
this.getMpConfig = initOptions.getConfigFunc;
|
|
4596
|
+
this.getTabId = initOptions.getTabIdFunc;
|
|
4597
|
+
this.reportError = initOptions.reportErrorFunc;
|
|
4598
|
+
this.getDistinctId = initOptions.getDistinctIdFunc;
|
|
4599
|
+
this.loadExtraBundle = initOptions.loadExtraBundle;
|
|
4600
|
+
this.recorderSrc = initOptions.recorderSrc;
|
|
4601
|
+
this.targetingSrc = initOptions.targetingSrc;
|
|
4602
|
+
this.libBasePath = initOptions.libBasePath;
|
|
4603
|
+
|
|
4604
|
+
this._recorder = null;
|
|
4602
4605
|
};
|
|
4603
|
-
|
|
4606
|
+
|
|
4607
|
+
RecorderManager.prototype.shouldLoadRecorder = function() {
|
|
4608
|
+
if (this.getMpConfig('disable_persistence')) {
|
|
4609
|
+
console.log('Load recorder check skipped due to disable_persistence config');
|
|
4610
|
+
return PromisePolyfill.resolve(false);
|
|
4611
|
+
}
|
|
4612
|
+
|
|
4613
|
+
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
4614
|
+
var tab_id = this.getTabId();
|
|
4615
|
+
return recording_registry_idb.init()
|
|
4616
|
+
.then(function () {
|
|
4617
|
+
return recording_registry_idb.getAll();
|
|
4618
|
+
})
|
|
4619
|
+
.then(function (recordings) {
|
|
4620
|
+
for (var i = 0; i < recordings.length; i++) {
|
|
4621
|
+
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
4622
|
+
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
4623
|
+
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
4624
|
+
return true;
|
|
4625
|
+
}
|
|
4626
|
+
}
|
|
4627
|
+
return false;
|
|
4628
|
+
})
|
|
4629
|
+
.catch(_.bind(function (err) {
|
|
4630
|
+
this.reportError('Error checking recording registry', err);
|
|
4631
|
+
return false;
|
|
4632
|
+
}, this));
|
|
4633
|
+
};
|
|
4634
|
+
|
|
4635
|
+
RecorderManager.prototype.checkAndStartSessionRecording = function(force_start, rate) {
|
|
4636
|
+
if (!win['MutationObserver']) {
|
|
4637
|
+
console.critical('Browser does not support MutationObserver; skipping session recording');
|
|
4638
|
+
return PromisePolyfill.resolve();
|
|
4639
|
+
}
|
|
4640
|
+
|
|
4641
|
+
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
4642
|
+
return new PromisePolyfill(_.bind(function(resolve) {
|
|
4643
|
+
var handleLoadedRecorder = safewrap(_.bind(function() {
|
|
4644
|
+
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this.mixpanelInstance);
|
|
4645
|
+
this._recorder['resumeRecording'](startNewIfInactive);
|
|
4646
|
+
resolve();
|
|
4647
|
+
}, this));
|
|
4648
|
+
|
|
4649
|
+
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
4650
|
+
var recorderSrc = this.recorderSrc || (this.libBasePath + RECORDER_FILENAME);
|
|
4651
|
+
this.loadExtraBundle(recorderSrc, handleLoadedRecorder);
|
|
4652
|
+
} else {
|
|
4653
|
+
handleLoadedRecorder();
|
|
4654
|
+
}
|
|
4655
|
+
}, this));
|
|
4656
|
+
}, this);
|
|
4657
|
+
|
|
4658
|
+
/**
|
|
4659
|
+
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
4660
|
+
* 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.
|
|
4661
|
+
*/
|
|
4662
|
+
var effective_rate = _.isUndefined(rate) ? this.getMpConfig('record_sessions_percent') : rate;
|
|
4663
|
+
var is_sampled = effective_rate > 0 && Math.random() * 100 <= effective_rate;
|
|
4664
|
+
if (force_start || is_sampled) {
|
|
4665
|
+
return loadRecorder(true);
|
|
4666
|
+
} else {
|
|
4667
|
+
return this.shouldLoadRecorder()
|
|
4668
|
+
.then(_.bind(function (shouldLoad) {
|
|
4669
|
+
if (shouldLoad) {
|
|
4670
|
+
return loadRecorder(false);
|
|
4671
|
+
}
|
|
4672
|
+
return PromisePolyfill.resolve();
|
|
4673
|
+
}, this));
|
|
4674
|
+
}
|
|
4675
|
+
};
|
|
4676
|
+
|
|
4677
|
+
RecorderManager.prototype.isRecording = function() {
|
|
4678
|
+
// Safety check: ensure isRecording method exists (older CDN builds may not have it)
|
|
4679
|
+
if (!this._recorder || !_.isFunction(this._recorder['isRecording'])) {
|
|
4680
|
+
return false;
|
|
4681
|
+
}
|
|
4682
|
+
try {
|
|
4683
|
+
return this._recorder['isRecording']();
|
|
4684
|
+
} catch (e) {
|
|
4685
|
+
this.reportError('Error checking if recording is active', e);
|
|
4686
|
+
return false;
|
|
4687
|
+
}
|
|
4688
|
+
};
|
|
4689
|
+
|
|
4690
|
+
RecorderManager.prototype.startRecordingOnEvent = function(event_name, properties) {
|
|
4691
|
+
var isRecording = this.isRecording();
|
|
4692
|
+
var recordingTriggerEvents = this.getMpConfig('recording_event_triggers');
|
|
4693
|
+
|
|
4694
|
+
if (!isRecording && recordingTriggerEvents) {
|
|
4695
|
+
var trigger = recordingTriggerEvents[event_name];
|
|
4696
|
+
if (trigger && typeof trigger['percentage'] === 'number') {
|
|
4697
|
+
var newRate = trigger['percentage'];
|
|
4698
|
+
var propertyFilters = trigger['property_filters'];
|
|
4699
|
+
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
4700
|
+
var targetingSrc = this.targetingSrc || (this.libBasePath + TARGETING_FILENAME);
|
|
4701
|
+
getTargetingPromise(this.loadExtraBundle, targetingSrc)
|
|
4702
|
+
.then(function(targeting) {
|
|
4703
|
+
try {
|
|
4704
|
+
var result = targeting['eventMatchesCriteria'](
|
|
4705
|
+
event_name,
|
|
4706
|
+
properties,
|
|
4707
|
+
{
|
|
4708
|
+
'event_name': event_name,
|
|
4709
|
+
'property_filters': propertyFilters
|
|
4710
|
+
}
|
|
4711
|
+
);
|
|
4712
|
+
if (result['matches']) {
|
|
4713
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
4714
|
+
}
|
|
4715
|
+
} catch (err) {
|
|
4716
|
+
console.critical('Could not parse recording event trigger properties logic:', err);
|
|
4717
|
+
}
|
|
4718
|
+
}.bind(this)).catch(function(err) {
|
|
4719
|
+
console.critical('Failed to load targeting library:', err);
|
|
4720
|
+
});
|
|
4721
|
+
} else {
|
|
4722
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
};
|
|
4727
|
+
|
|
4728
|
+
RecorderManager.prototype.stopSessionRecording = function() {
|
|
4729
|
+
if (this._recorder) {
|
|
4730
|
+
return this._recorder['stopRecording']();
|
|
4731
|
+
}
|
|
4732
|
+
return PromisePolyfill.resolve();
|
|
4733
|
+
};
|
|
4734
|
+
|
|
4735
|
+
RecorderManager.prototype.pauseSessionRecording = function() {
|
|
4736
|
+
if (this._recorder) {
|
|
4737
|
+
return this._recorder['pauseRecording']();
|
|
4738
|
+
}
|
|
4739
|
+
return PromisePolyfill.resolve();
|
|
4740
|
+
};
|
|
4741
|
+
|
|
4742
|
+
RecorderManager.prototype.resumeSessionRecording = function() {
|
|
4743
|
+
if (this._recorder) {
|
|
4744
|
+
return this._recorder['resumeRecording']();
|
|
4745
|
+
}
|
|
4746
|
+
return PromisePolyfill.resolve();
|
|
4747
|
+
};
|
|
4748
|
+
|
|
4749
|
+
RecorderManager.prototype.isRecordingHeatmapData = function() {
|
|
4750
|
+
return this.getSessionReplayId() && this.getMpConfig('record_heatmap_data');
|
|
4751
|
+
};
|
|
4752
|
+
|
|
4753
|
+
RecorderManager.prototype.getSessionRecordingProperties = function() {
|
|
4754
|
+
var props = {};
|
|
4755
|
+
var replay_id = this.getSessionReplayId();
|
|
4756
|
+
if (replay_id) {
|
|
4757
|
+
props['$mp_replay_id'] = replay_id;
|
|
4758
|
+
}
|
|
4759
|
+
return props;
|
|
4760
|
+
};
|
|
4761
|
+
|
|
4762
|
+
RecorderManager.prototype.getSessionReplayUrl = function() {
|
|
4763
|
+
var replay_url = null;
|
|
4764
|
+
var replay_id = this.getSessionReplayId();
|
|
4765
|
+
if (replay_id) {
|
|
4766
|
+
var query_params = _.HTTPBuildQuery({
|
|
4767
|
+
'replay_id': replay_id,
|
|
4768
|
+
'distinct_id': this.getDistinctId(),
|
|
4769
|
+
'token': this.getMpConfig('token')
|
|
4770
|
+
});
|
|
4771
|
+
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
4772
|
+
}
|
|
4773
|
+
return replay_url;
|
|
4774
|
+
};
|
|
4775
|
+
|
|
4776
|
+
RecorderManager.prototype.getSessionReplayId = function() {
|
|
4777
|
+
var replay_id = null;
|
|
4778
|
+
if (this._recorder) {
|
|
4779
|
+
replay_id = this._recorder['replayId'];
|
|
4780
|
+
}
|
|
4781
|
+
return replay_id || null;
|
|
4782
|
+
};
|
|
4783
|
+
|
|
4784
|
+
// "private" public method to reach into the recorder in test cases
|
|
4785
|
+
RecorderManager.prototype.getRecorder = function() {
|
|
4786
|
+
return this._recorder;
|
|
4787
|
+
};
|
|
4788
|
+
|
|
4789
|
+
safewrapClass(RecorderManager);
|
|
4790
|
+
|
|
4791
|
+
/* eslint camelcase: "off" */
|
|
4792
|
+
|
|
4793
|
+
|
|
4794
|
+
/**
|
|
4795
|
+
* DomTracker Object
|
|
4796
|
+
* @constructor
|
|
4797
|
+
*/
|
|
4798
|
+
var DomTracker = function() {};
|
|
4799
|
+
|
|
4800
|
+
|
|
4801
|
+
// interface
|
|
4802
|
+
DomTracker.prototype.create_properties = function() {};
|
|
4803
|
+
DomTracker.prototype.event_handler = function() {};
|
|
4804
|
+
DomTracker.prototype.after_track_handler = function() {};
|
|
4805
|
+
|
|
4806
|
+
DomTracker.prototype.init = function(mixpanel_instance) {
|
|
4807
|
+
this.mp = mixpanel_instance;
|
|
4808
|
+
return this;
|
|
4809
|
+
};
|
|
4810
|
+
|
|
4811
|
+
/**
|
|
4812
|
+
* @param {Object|string} query
|
|
4813
|
+
* @param {string} event_name
|
|
4814
|
+
* @param {Object=} properties
|
|
4815
|
+
* @param {function=} user_callback
|
|
4816
|
+
*/
|
|
4817
|
+
DomTracker.prototype.track = function(query, event_name, properties, user_callback) {
|
|
4818
|
+
var that = this;
|
|
4819
|
+
var elements = _.dom_query(query);
|
|
4820
|
+
|
|
4821
|
+
if (elements.length === 0) {
|
|
4822
|
+
console.error('The DOM query (' + query + ') returned 0 elements');
|
|
4823
|
+
return;
|
|
4824
|
+
}
|
|
4825
|
+
|
|
4826
|
+
_.each(elements, function(element) {
|
|
4827
|
+
_.register_event(element, this.override_event, function(e) {
|
|
4828
|
+
var options = {};
|
|
4829
|
+
var props = that.create_properties(properties, this);
|
|
4830
|
+
var timeout = that.mp.get_config('track_links_timeout');
|
|
4831
|
+
|
|
4832
|
+
that.event_handler(e, this, options);
|
|
4833
|
+
|
|
4834
|
+
// in case the mixpanel servers don't get back to us in time
|
|
4835
|
+
window.setTimeout(that.track_callback(user_callback, props, options, true), timeout);
|
|
4836
|
+
|
|
4837
|
+
// fire the tracking event
|
|
4838
|
+
that.mp.track(event_name, props, that.track_callback(user_callback, props, options));
|
|
4839
|
+
});
|
|
4840
|
+
}, this);
|
|
4841
|
+
|
|
4842
|
+
return true;
|
|
4843
|
+
};
|
|
4844
|
+
|
|
4845
|
+
/**
|
|
4846
|
+
* @param {function} user_callback
|
|
4847
|
+
* @param {Object} props
|
|
4848
|
+
* @param {boolean=} timeout_occured
|
|
4849
|
+
*/
|
|
4850
|
+
DomTracker.prototype.track_callback = function(user_callback, props, options, timeout_occured) {
|
|
4851
|
+
timeout_occured = timeout_occured || false;
|
|
4852
|
+
var that = this;
|
|
4853
|
+
|
|
4854
|
+
return function() {
|
|
4855
|
+
// options is referenced from both callbacks, so we can have
|
|
4856
|
+
// a 'lock' of sorts to ensure only one fires
|
|
4857
|
+
if (options.callback_fired) { return; }
|
|
4858
|
+
options.callback_fired = true;
|
|
4859
|
+
|
|
4860
|
+
if (user_callback && user_callback(timeout_occured, props) === false) {
|
|
4861
|
+
// user can prevent the default functionality by
|
|
4862
|
+
// returning false from their callback
|
|
4863
|
+
return;
|
|
4864
|
+
}
|
|
4865
|
+
|
|
4866
|
+
that.after_track_handler(props, options, timeout_occured);
|
|
4867
|
+
};
|
|
4868
|
+
};
|
|
4869
|
+
|
|
4870
|
+
DomTracker.prototype.create_properties = function(properties, element) {
|
|
4871
|
+
var props;
|
|
4872
|
+
|
|
4873
|
+
if (typeof(properties) === 'function') {
|
|
4874
|
+
props = properties(element);
|
|
4875
|
+
} else {
|
|
4876
|
+
props = _.extend({}, properties);
|
|
4877
|
+
}
|
|
4878
|
+
|
|
4879
|
+
return props;
|
|
4880
|
+
};
|
|
4881
|
+
|
|
4882
|
+
/**
|
|
4883
|
+
* LinkTracker Object
|
|
4884
|
+
* @constructor
|
|
4885
|
+
* @extends DomTracker
|
|
4886
|
+
*/
|
|
4887
|
+
var LinkTracker = function() {
|
|
4888
|
+
this.override_event = 'click';
|
|
4889
|
+
};
|
|
4890
|
+
_.inherit(LinkTracker, DomTracker);
|
|
4891
|
+
|
|
4892
|
+
LinkTracker.prototype.create_properties = function(properties, element) {
|
|
4893
|
+
var props = LinkTracker.superclass.create_properties.apply(this, arguments);
|
|
4894
|
+
|
|
4895
|
+
if (element.href) { props['url'] = element.href; }
|
|
4896
|
+
|
|
4897
|
+
return props;
|
|
4898
|
+
};
|
|
4899
|
+
|
|
4900
|
+
LinkTracker.prototype.event_handler = function(evt, element, options) {
|
|
4901
|
+
options.new_tab = (
|
|
4902
|
+
evt.which === 2 ||
|
|
4903
|
+
evt.metaKey ||
|
|
4904
|
+
evt.ctrlKey ||
|
|
4905
|
+
element.target === '_blank'
|
|
4906
|
+
);
|
|
4907
|
+
options.href = element.href;
|
|
4908
|
+
|
|
4909
|
+
if (!options.new_tab) {
|
|
4910
|
+
evt.preventDefault();
|
|
4911
|
+
}
|
|
4912
|
+
};
|
|
4913
|
+
|
|
4914
|
+
LinkTracker.prototype.after_track_handler = function(props, options) {
|
|
4915
|
+
if (options.new_tab) { return; }
|
|
4916
|
+
|
|
4917
|
+
setTimeout(function() {
|
|
4918
|
+
window.location = options.href;
|
|
4919
|
+
}, 0);
|
|
4920
|
+
};
|
|
4921
|
+
|
|
4922
|
+
/**
|
|
4923
|
+
* FormTracker Object
|
|
4924
|
+
* @constructor
|
|
4925
|
+
* @extends DomTracker
|
|
4926
|
+
*/
|
|
4927
|
+
var FormTracker = function() {
|
|
4928
|
+
this.override_event = 'submit';
|
|
4929
|
+
};
|
|
4930
|
+
_.inherit(FormTracker, DomTracker);
|
|
4604
4931
|
|
|
4605
4932
|
FormTracker.prototype.event_handler = function(evt, element, options) {
|
|
4606
4933
|
options.element = element;
|
|
@@ -6982,133 +7309,6 @@
|
|
|
6982
7309
|
return timestamp;
|
|
6983
7310
|
};
|
|
6984
7311
|
|
|
6985
|
-
var MIXPANEL_DB_NAME = 'mixpanelBrowserDb';
|
|
6986
|
-
|
|
6987
|
-
var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
|
|
6988
|
-
var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
|
|
6989
|
-
|
|
6990
|
-
// note: increment the version number when adding new object stores
|
|
6991
|
-
var DB_VERSION = 1;
|
|
6992
|
-
var OBJECT_STORES = [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME];
|
|
6993
|
-
|
|
6994
|
-
/**
|
|
6995
|
-
* @type {import('./wrapper').StorageWrapper}
|
|
6996
|
-
*/
|
|
6997
|
-
var IDBStorageWrapper = function (storeName) {
|
|
6998
|
-
/**
|
|
6999
|
-
* @type {Promise<IDBDatabase>|null}
|
|
7000
|
-
*/
|
|
7001
|
-
this.dbPromise = null;
|
|
7002
|
-
this.storeName = storeName;
|
|
7003
|
-
};
|
|
7004
|
-
|
|
7005
|
-
IDBStorageWrapper.prototype._openDb = function () {
|
|
7006
|
-
return new PromisePolyfill(function (resolve, reject) {
|
|
7007
|
-
var openRequest = win.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
|
|
7008
|
-
openRequest['onerror'] = function () {
|
|
7009
|
-
reject(openRequest.error);
|
|
7010
|
-
};
|
|
7011
|
-
|
|
7012
|
-
openRequest['onsuccess'] = function () {
|
|
7013
|
-
resolve(openRequest.result);
|
|
7014
|
-
};
|
|
7015
|
-
|
|
7016
|
-
openRequest['onupgradeneeded'] = function (ev) {
|
|
7017
|
-
var db = ev.target.result;
|
|
7018
|
-
|
|
7019
|
-
OBJECT_STORES.forEach(function (storeName) {
|
|
7020
|
-
db.createObjectStore(storeName);
|
|
7021
|
-
});
|
|
7022
|
-
};
|
|
7023
|
-
});
|
|
7024
|
-
};
|
|
7025
|
-
|
|
7026
|
-
IDBStorageWrapper.prototype.init = function () {
|
|
7027
|
-
if (!win.indexedDB) {
|
|
7028
|
-
return PromisePolyfill.reject('indexedDB is not supported in this browser');
|
|
7029
|
-
}
|
|
7030
|
-
|
|
7031
|
-
if (!this.dbPromise) {
|
|
7032
|
-
this.dbPromise = this._openDb();
|
|
7033
|
-
}
|
|
7034
|
-
|
|
7035
|
-
return this.dbPromise
|
|
7036
|
-
.then(function (dbOrError) {
|
|
7037
|
-
if (dbOrError instanceof win['IDBDatabase']) {
|
|
7038
|
-
return PromisePolyfill.resolve();
|
|
7039
|
-
} else {
|
|
7040
|
-
return PromisePolyfill.reject(dbOrError);
|
|
7041
|
-
}
|
|
7042
|
-
});
|
|
7043
|
-
};
|
|
7044
|
-
|
|
7045
|
-
IDBStorageWrapper.prototype.isInitialized = function () {
|
|
7046
|
-
return !!this.dbPromise;
|
|
7047
|
-
};
|
|
7048
|
-
|
|
7049
|
-
/**
|
|
7050
|
-
* @param {IDBTransactionMode} mode
|
|
7051
|
-
* @param {function(IDBObjectStore): void} storeCb
|
|
7052
|
-
*/
|
|
7053
|
-
IDBStorageWrapper.prototype.makeTransaction = function (mode, storeCb) {
|
|
7054
|
-
var storeName = this.storeName;
|
|
7055
|
-
var doTransaction = function (db) {
|
|
7056
|
-
return new PromisePolyfill(function (resolve, reject) {
|
|
7057
|
-
var transaction = db.transaction(storeName, mode);
|
|
7058
|
-
transaction.oncomplete = function () {
|
|
7059
|
-
resolve(transaction);
|
|
7060
|
-
};
|
|
7061
|
-
transaction.onabort = transaction.onerror = function () {
|
|
7062
|
-
reject(transaction.error);
|
|
7063
|
-
};
|
|
7064
|
-
|
|
7065
|
-
storeCb(transaction.objectStore(storeName));
|
|
7066
|
-
});
|
|
7067
|
-
};
|
|
7068
|
-
|
|
7069
|
-
return this.dbPromise
|
|
7070
|
-
.then(doTransaction)
|
|
7071
|
-
.catch(function (err) {
|
|
7072
|
-
if (err && err['name'] === 'InvalidStateError') {
|
|
7073
|
-
// try reopening the DB if the connection is closed
|
|
7074
|
-
this.dbPromise = this._openDb();
|
|
7075
|
-
return this.dbPromise.then(doTransaction);
|
|
7076
|
-
} else {
|
|
7077
|
-
return PromisePolyfill.reject(err);
|
|
7078
|
-
}
|
|
7079
|
-
}.bind(this));
|
|
7080
|
-
};
|
|
7081
|
-
|
|
7082
|
-
IDBStorageWrapper.prototype.setItem = function (key, value) {
|
|
7083
|
-
return this.makeTransaction('readwrite', function (objectStore) {
|
|
7084
|
-
objectStore.put(value, key);
|
|
7085
|
-
});
|
|
7086
|
-
};
|
|
7087
|
-
|
|
7088
|
-
IDBStorageWrapper.prototype.getItem = function (key) {
|
|
7089
|
-
var req;
|
|
7090
|
-
return this.makeTransaction('readonly', function (objectStore) {
|
|
7091
|
-
req = objectStore.get(key);
|
|
7092
|
-
}).then(function () {
|
|
7093
|
-
return req.result;
|
|
7094
|
-
});
|
|
7095
|
-
};
|
|
7096
|
-
|
|
7097
|
-
IDBStorageWrapper.prototype.removeItem = function (key) {
|
|
7098
|
-
return this.makeTransaction('readwrite', function (objectStore) {
|
|
7099
|
-
objectStore.delete(key);
|
|
7100
|
-
});
|
|
7101
|
-
};
|
|
7102
|
-
|
|
7103
|
-
IDBStorageWrapper.prototype.getAll = function () {
|
|
7104
|
-
var req;
|
|
7105
|
-
return this.makeTransaction('readonly', function (objectStore) {
|
|
7106
|
-
req = objectStore.getAll();
|
|
7107
|
-
}).then(function () {
|
|
7108
|
-
return req.result;
|
|
7109
|
-
});
|
|
7110
|
-
};
|
|
7111
|
-
|
|
7112
7312
|
/* eslint camelcase: "off" */
|
|
7113
7313
|
|
|
7114
7314
|
/*
|
|
@@ -7243,13 +7443,17 @@
|
|
|
7243
7443
|
'record_collect_fonts': false,
|
|
7244
7444
|
'record_console': true,
|
|
7245
7445
|
'record_heatmap_data': false,
|
|
7446
|
+
'recording_event_triggers': {},
|
|
7246
7447
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
7247
7448
|
'record_mask_inputs': true,
|
|
7248
7449
|
'record_max_ms': MAX_RECORDING_MS,
|
|
7249
7450
|
'record_min_ms': 0,
|
|
7451
|
+
'record_network': false,
|
|
7452
|
+
'record_network_options': {},
|
|
7250
7453
|
'record_sessions_percent': 0,
|
|
7251
|
-
'recorder_src':
|
|
7252
|
-
'targeting_src':
|
|
7454
|
+
'recorder_src': null,
|
|
7455
|
+
'targeting_src': null,
|
|
7456
|
+
'lib_base_path': 'https://cdn.mxpnl.com/libs/',
|
|
7253
7457
|
'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
|
|
7254
7458
|
};
|
|
7255
7459
|
|
|
@@ -7403,6 +7607,19 @@
|
|
|
7403
7607
|
'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
|
|
7404
7608
|
}));
|
|
7405
7609
|
|
|
7610
|
+
this.recorderManager = new RecorderManager({
|
|
7611
|
+
mixpanelInstance: this,
|
|
7612
|
+
getConfigFunc: _.bind(this.get_config, this),
|
|
7613
|
+
setConfigFunc: _.bind(this.set_config, this),
|
|
7614
|
+
getTabIdFunc: _.bind(this.get_tab_id, this),
|
|
7615
|
+
reportErrorFunc: _.bind(this.report_error, this),
|
|
7616
|
+
getDistinctIdFunc: _.bind(this.get_distinct_id, this),
|
|
7617
|
+
recorderSrc: this.get_config('recorder_src'),
|
|
7618
|
+
targetingSrc: this.get_config('targeting_src'),
|
|
7619
|
+
libBasePath: this.get_config('lib_base_path'),
|
|
7620
|
+
loadExtraBundle: load_extra_bundle
|
|
7621
|
+
});
|
|
7622
|
+
|
|
7406
7623
|
this['_jsc'] = NOOP_FUNC;
|
|
7407
7624
|
|
|
7408
7625
|
this.__dom_loaded_queue = [];
|
|
@@ -7481,7 +7698,7 @@
|
|
|
7481
7698
|
getPropertyFunc: _.bind(this.get_property, this),
|
|
7482
7699
|
trackingFunc: _.bind(this.track, this),
|
|
7483
7700
|
loadExtraBundle: load_extra_bundle,
|
|
7484
|
-
targetingSrc: this.get_config('targeting_src')
|
|
7701
|
+
targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
|
|
7485
7702
|
});
|
|
7486
7703
|
this.flags.init();
|
|
7487
7704
|
this['flags'] = this.flags;
|
|
@@ -7494,11 +7711,11 @@
|
|
|
7494
7711
|
// Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
|
|
7495
7712
|
var mode = this.get_config('remote_settings_mode');
|
|
7496
7713
|
if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
|
|
7497
|
-
this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
7498
|
-
this._check_and_start_session_recording();
|
|
7714
|
+
this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
7715
|
+
return this._check_and_start_session_recording();
|
|
7499
7716
|
}, this));
|
|
7500
7717
|
} else {
|
|
7501
|
-
this._check_and_start_session_recording();
|
|
7718
|
+
this.__session_recording_init_promise = this._check_and_start_session_recording();
|
|
7502
7719
|
}
|
|
7503
7720
|
};
|
|
7504
7721
|
|
|
@@ -7542,132 +7759,50 @@
|
|
|
7542
7759
|
return this.tab_id || null;
|
|
7543
7760
|
};
|
|
7544
7761
|
|
|
7545
|
-
MixpanelLib.prototype._should_load_recorder = function () {
|
|
7546
|
-
if (this.get_config('disable_persistence')) {
|
|
7547
|
-
console.log('Load recorder check skipped due to disable_persistence config');
|
|
7548
|
-
return Promise.resolve(false);
|
|
7549
|
-
}
|
|
7550
|
-
|
|
7551
|
-
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
7552
|
-
var tab_id = this.get_tab_id();
|
|
7553
|
-
return recording_registry_idb.init()
|
|
7554
|
-
.then(function () {
|
|
7555
|
-
return recording_registry_idb.getAll();
|
|
7556
|
-
})
|
|
7557
|
-
.then(function (recordings) {
|
|
7558
|
-
for (var i = 0; i < recordings.length; i++) {
|
|
7559
|
-
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
7560
|
-
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
7561
|
-
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
7562
|
-
return true;
|
|
7563
|
-
}
|
|
7564
|
-
}
|
|
7565
|
-
return false;
|
|
7566
|
-
})
|
|
7567
|
-
.catch(_.bind(function (err) {
|
|
7568
|
-
this.report_error('Error checking recording registry', err);
|
|
7569
|
-
}, this));
|
|
7570
|
-
};
|
|
7571
|
-
|
|
7572
7762
|
MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
|
|
7573
|
-
|
|
7574
|
-
console.critical('Browser does not support MutationObserver; skipping session recording');
|
|
7575
|
-
return;
|
|
7576
|
-
}
|
|
7577
|
-
|
|
7578
|
-
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
7579
|
-
var handleLoadedRecorder = _.bind(function() {
|
|
7580
|
-
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this);
|
|
7581
|
-
this._recorder['resumeRecording'](startNewIfInactive);
|
|
7582
|
-
}, this);
|
|
7583
|
-
|
|
7584
|
-
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
7585
|
-
load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
|
|
7586
|
-
} else {
|
|
7587
|
-
handleLoadedRecorder();
|
|
7588
|
-
}
|
|
7589
|
-
}, this);
|
|
7590
|
-
|
|
7591
|
-
/**
|
|
7592
|
-
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
7593
|
-
* Otherwise, if the recording registry has any records then it's likely there's a recording in progress or orphaned data that needs to be flushed.
|
|
7594
|
-
*/
|
|
7595
|
-
var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
|
|
7596
|
-
if (force_start || is_sampled) {
|
|
7597
|
-
loadRecorder(true);
|
|
7598
|
-
} else {
|
|
7599
|
-
this._should_load_recorder()
|
|
7600
|
-
.then(function (shouldLoad) {
|
|
7601
|
-
if (shouldLoad) {
|
|
7602
|
-
loadRecorder(false);
|
|
7603
|
-
}
|
|
7604
|
-
});
|
|
7605
|
-
}
|
|
7763
|
+
return this.recorderManager.checkAndStartSessionRecording(force_start);
|
|
7606
7764
|
});
|
|
7607
7765
|
|
|
7766
|
+
MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
|
|
7767
|
+
return this.recorderManager.startRecordingOnEvent(event_name, properties);
|
|
7768
|
+
};
|
|
7769
|
+
|
|
7608
7770
|
MixpanelLib.prototype.start_session_recording = function () {
|
|
7609
|
-
this._check_and_start_session_recording(true);
|
|
7771
|
+
return this._check_and_start_session_recording(true);
|
|
7610
7772
|
};
|
|
7611
7773
|
|
|
7612
7774
|
MixpanelLib.prototype.stop_session_recording = function () {
|
|
7613
|
-
|
|
7614
|
-
return this._recorder['stopRecording']();
|
|
7615
|
-
}
|
|
7616
|
-
return Promise.resolve();
|
|
7775
|
+
return this.recorderManager.stopSessionRecording();
|
|
7617
7776
|
};
|
|
7618
7777
|
|
|
7619
7778
|
MixpanelLib.prototype.pause_session_recording = function () {
|
|
7620
|
-
|
|
7621
|
-
return this._recorder['pauseRecording']();
|
|
7622
|
-
}
|
|
7623
|
-
return Promise.resolve();
|
|
7779
|
+
return this.recorderManager.pauseSessionRecording();
|
|
7624
7780
|
};
|
|
7625
7781
|
|
|
7626
7782
|
MixpanelLib.prototype.resume_session_recording = function () {
|
|
7627
|
-
|
|
7628
|
-
return this._recorder['resumeRecording']();
|
|
7629
|
-
}
|
|
7630
|
-
return Promise.resolve();
|
|
7783
|
+
return this.recorderManager.resumeSessionRecording();
|
|
7631
7784
|
};
|
|
7632
7785
|
|
|
7633
7786
|
MixpanelLib.prototype.is_recording_heatmap_data = function () {
|
|
7634
|
-
return this.
|
|
7787
|
+
return this.recorderManager.isRecordingHeatmapData();
|
|
7635
7788
|
};
|
|
7636
7789
|
|
|
7637
7790
|
MixpanelLib.prototype.get_session_recording_properties = function () {
|
|
7638
|
-
|
|
7639
|
-
var replay_id = this._get_session_replay_id();
|
|
7640
|
-
if (replay_id) {
|
|
7641
|
-
props['$mp_replay_id'] = replay_id;
|
|
7642
|
-
}
|
|
7643
|
-
return props;
|
|
7791
|
+
return this.recorderManager.getSessionRecordingProperties();
|
|
7644
7792
|
};
|
|
7645
7793
|
|
|
7646
7794
|
MixpanelLib.prototype.get_session_replay_url = function () {
|
|
7647
|
-
|
|
7648
|
-
var replay_id = this._get_session_replay_id();
|
|
7649
|
-
if (replay_id) {
|
|
7650
|
-
var query_params = _.HTTPBuildQuery({
|
|
7651
|
-
'replay_id': replay_id,
|
|
7652
|
-
'distinct_id': this.get_distinct_id(),
|
|
7653
|
-
'token': this.get_config('token')
|
|
7654
|
-
});
|
|
7655
|
-
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
7656
|
-
}
|
|
7657
|
-
return replay_url;
|
|
7658
|
-
};
|
|
7659
|
-
|
|
7660
|
-
MixpanelLib.prototype._get_session_replay_id = function () {
|
|
7661
|
-
var replay_id = null;
|
|
7662
|
-
if (this._recorder) {
|
|
7663
|
-
replay_id = this._recorder['replayId'];
|
|
7664
|
-
}
|
|
7665
|
-
return replay_id || null;
|
|
7795
|
+
return this.recorderManager.getSessionReplayUrl();
|
|
7666
7796
|
};
|
|
7667
7797
|
|
|
7668
7798
|
// "private" public method to reach into the recorder in test cases
|
|
7669
7799
|
MixpanelLib.prototype.__get_recorder = function () {
|
|
7670
|
-
return this.
|
|
7800
|
+
return this.recorderManager.getRecorder();
|
|
7801
|
+
};
|
|
7802
|
+
|
|
7803
|
+
// "private" public method to get session recording init promise in test cases
|
|
7804
|
+
MixpanelLib.prototype.__get_recording_init_promise = function () {
|
|
7805
|
+
return this.__session_recording_init_promise;
|
|
7671
7806
|
};
|
|
7672
7807
|
|
|
7673
7808
|
// Private methods
|
|
@@ -7925,6 +8060,7 @@
|
|
|
7925
8060
|
};
|
|
7926
8061
|
|
|
7927
8062
|
MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
8063
|
+
var self = this;
|
|
7928
8064
|
var disableRecordingIfStrict = function() {
|
|
7929
8065
|
if (mode === 'strict') {
|
|
7930
8066
|
self.set_config({'record_sessions_percent': 0});
|
|
@@ -7945,7 +8081,6 @@
|
|
|
7945
8081
|
};
|
|
7946
8082
|
var query_string = _.HTTPBuildQuery(request_params);
|
|
7947
8083
|
var full_url = settings_endpoint + '?' + query_string;
|
|
7948
|
-
var self = this;
|
|
7949
8084
|
|
|
7950
8085
|
var abortController = new AbortController();
|
|
7951
8086
|
var timeout_id = setTimeout(function() {
|
|
@@ -8137,6 +8272,34 @@
|
|
|
8137
8272
|
this._execute_array([item]);
|
|
8138
8273
|
};
|
|
8139
8274
|
|
|
8275
|
+
/**
|
|
8276
|
+
* Enables events on the Mixpanel object. If passed no arguments,
|
|
8277
|
+
* this function enable tracking of all events. If passed an
|
|
8278
|
+
* array of event names, those events will be enabled, but other
|
|
8279
|
+
* existing disabled events will continue to be not tracked.
|
|
8280
|
+
*
|
|
8281
|
+
* @param {Array} [events] An array of event names to enable
|
|
8282
|
+
*/
|
|
8283
|
+
MixpanelLib.prototype.enable = function(events) {
|
|
8284
|
+
var keys, new_disabled_events, i, j;
|
|
8285
|
+
|
|
8286
|
+
if (typeof(events) === 'undefined') {
|
|
8287
|
+
this._flags.disable_all_events = false;
|
|
8288
|
+
} else {
|
|
8289
|
+
keys = {};
|
|
8290
|
+
new_disabled_events = [];
|
|
8291
|
+
for (i = 0; i < events.length; i++) {
|
|
8292
|
+
keys[events[i]] = true;
|
|
8293
|
+
}
|
|
8294
|
+
for (j = 0; j < this.__disabled_events.length; j++) {
|
|
8295
|
+
if (!keys[this.__disabled_events[j]]) {
|
|
8296
|
+
new_disabled_events.push(this.__disabled_events[j]);
|
|
8297
|
+
}
|
|
8298
|
+
}
|
|
8299
|
+
this.__disabled_events = new_disabled_events;
|
|
8300
|
+
}
|
|
8301
|
+
};
|
|
8302
|
+
|
|
8140
8303
|
/**
|
|
8141
8304
|
* Disable events on the Mixpanel object. If passed no arguments,
|
|
8142
8305
|
* this function disables tracking of any event. If passed an
|
|
@@ -8310,6 +8473,8 @@
|
|
|
8310
8473
|
this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
|
|
8311
8474
|
}
|
|
8312
8475
|
|
|
8476
|
+
this._start_recording_on_event(event_name, properties);
|
|
8477
|
+
|
|
8313
8478
|
var data = {
|
|
8314
8479
|
'event': event_name,
|
|
8315
8480
|
'properties': properties
|
|
@@ -9518,6 +9683,7 @@
|
|
|
9518
9683
|
// MixpanelLib Exports
|
|
9519
9684
|
MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
|
|
9520
9685
|
MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
|
|
9686
|
+
MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
|
|
9521
9687
|
MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
|
|
9522
9688
|
MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
|
|
9523
9689
|
MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
|
|
@@ -9561,6 +9727,7 @@
|
|
|
9561
9727
|
|
|
9562
9728
|
// Exports intended only for testing
|
|
9563
9729
|
MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
|
|
9730
|
+
MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
|
|
9564
9731
|
|
|
9565
9732
|
// MixpanelPersistence Exports
|
|
9566
9733
|
MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
|