mixpanel-browser 2.55.0 → 2.55.1
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/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/dist/mixpanel-core.cjs.js +21 -6
- package/dist/mixpanel-recorder.js +55 -19
- package/dist/mixpanel-recorder.min.js +9 -9
- package/dist/mixpanel-with-async-recorder.cjs.js +21 -6
- package/dist/mixpanel.amd.js +56 -19
- package/dist/mixpanel.cjs.js +56 -19
- package/dist/mixpanel.globals.js +21 -6
- package/dist/mixpanel.min.js +98 -97
- package/dist/mixpanel.module.js +56 -19
- package/dist/mixpanel.umd.js +56 -19
- package/package.json +1 -1
- package/src/config.js +1 -1
- package/src/mixpanel-core.js +1 -0
- package/src/recorder/index.js +34 -14
- package/src/request-batcher.js +7 -2
- package/src/utils.js +26 -13
package/dist/mixpanel.module.js
CHANGED
|
@@ -4507,7 +4507,7 @@ var IncrementalSource = /* @__PURE__ */ ((IncrementalSource2) => {
|
|
|
4507
4507
|
|
|
4508
4508
|
var Config = {
|
|
4509
4509
|
DEBUG: false,
|
|
4510
|
-
LIB_VERSION: '2.55.
|
|
4510
|
+
LIB_VERSION: '2.55.1'
|
|
4511
4511
|
};
|
|
4512
4512
|
|
|
4513
4513
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
@@ -4519,7 +4519,7 @@ if (typeof(window) === 'undefined') {
|
|
|
4519
4519
|
hostname: ''
|
|
4520
4520
|
};
|
|
4521
4521
|
win = {
|
|
4522
|
-
navigator: { userAgent: '' },
|
|
4522
|
+
navigator: { userAgent: '', onLine: true },
|
|
4523
4523
|
document: {
|
|
4524
4524
|
location: loc,
|
|
4525
4525
|
referrer: ''
|
|
@@ -4533,6 +4533,8 @@ if (typeof(window) === 'undefined') {
|
|
|
4533
4533
|
|
|
4534
4534
|
// Maximum allowed session recording length
|
|
4535
4535
|
var MAX_RECORDING_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
4536
|
+
// Maximum allowed value for minimum session recording length
|
|
4537
|
+
var MAX_VALUE_FOR_MIN_RECORDING_MS = 8 * 1000; // 8 seconds
|
|
4536
4538
|
|
|
4537
4539
|
/*
|
|
4538
4540
|
* Saved references to long variable names, so that closure compiler can
|
|
@@ -5473,7 +5475,7 @@ _.HTTPBuildQuery = function(formdata, arg_separator) {
|
|
|
5473
5475
|
_.getQueryParam = function(url, param) {
|
|
5474
5476
|
// Expects a raw URL
|
|
5475
5477
|
|
|
5476
|
-
param = param.replace(/[[]
|
|
5478
|
+
param = param.replace(/[[]/g, '\\[').replace(/[\]]/g, '\\]');
|
|
5477
5479
|
var regexS = '[\\?&]' + param + '=([^&#]*)',
|
|
5478
5480
|
regex = new RegExp(regexS),
|
|
5479
5481
|
results = regex.exec(url);
|
|
@@ -5930,8 +5932,8 @@ _.dom_query = (function() {
|
|
|
5930
5932
|
};
|
|
5931
5933
|
})();
|
|
5932
5934
|
|
|
5933
|
-
var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
|
|
5934
|
-
var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'ttclid', 'twclid', 'wbraid'];
|
|
5935
|
+
var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term', 'utm_id', 'utm_source_platform','utm_campaign_id', 'utm_creative_format', 'utm_marketing_tactic'];
|
|
5936
|
+
var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'sccid', 'ttclid', 'twclid', 'wbraid'];
|
|
5935
5937
|
|
|
5936
5938
|
_.info = {
|
|
5937
5939
|
campaignParams: function(default_value) {
|
|
@@ -6213,6 +6215,15 @@ var extract_domain = function(hostname) {
|
|
|
6213
6215
|
return matches ? matches[0] : '';
|
|
6214
6216
|
};
|
|
6215
6217
|
|
|
6218
|
+
/**
|
|
6219
|
+
* Check whether we have network connection. default to true for browsers that don't support navigator.onLine (IE)
|
|
6220
|
+
* @returns {boolean}
|
|
6221
|
+
*/
|
|
6222
|
+
var isOnline = function() {
|
|
6223
|
+
var onLine = win.navigator['onLine'];
|
|
6224
|
+
return _.isUndefined(onLine) || onLine;
|
|
6225
|
+
};
|
|
6226
|
+
|
|
6216
6227
|
var JSONStringify = null, JSONParse = null;
|
|
6217
6228
|
if (typeof JSON !== 'undefined') {
|
|
6218
6229
|
JSONStringify = JSON.stringify;
|
|
@@ -7176,7 +7187,12 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
7176
7187
|
this.flush();
|
|
7177
7188
|
} else if (
|
|
7178
7189
|
_.isObject(res) &&
|
|
7179
|
-
(
|
|
7190
|
+
(
|
|
7191
|
+
res.httpStatusCode >= 500
|
|
7192
|
+
|| res.httpStatusCode === 429
|
|
7193
|
+
|| (res.httpStatusCode <= 0 && !isOnline())
|
|
7194
|
+
|| res.error === 'timeout'
|
|
7195
|
+
)
|
|
7180
7196
|
) {
|
|
7181
7197
|
// network or API error, or 429 Too Many Requests, retry
|
|
7182
7198
|
var retryMS = this.flushInterval * 2;
|
|
@@ -7327,6 +7343,7 @@ var MixpanelRecorder = function(mixpanelInstance) {
|
|
|
7327
7343
|
this.maxTimeoutId = null;
|
|
7328
7344
|
|
|
7329
7345
|
this.recordMaxMs = MAX_RECORDING_MS;
|
|
7346
|
+
this.recordMinMs = 0;
|
|
7330
7347
|
this._initBatcher();
|
|
7331
7348
|
};
|
|
7332
7349
|
|
|
@@ -7358,16 +7375,24 @@ MixpanelRecorder.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
7358
7375
|
logger.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
|
|
7359
7376
|
}
|
|
7360
7377
|
|
|
7378
|
+
this.recordMinMs = this.get_config('record_min_ms');
|
|
7379
|
+
if (this.recordMinMs > MAX_VALUE_FOR_MIN_RECORDING_MS) {
|
|
7380
|
+
this.recordMinMs = MAX_VALUE_FOR_MIN_RECORDING_MS;
|
|
7381
|
+
logger.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
|
|
7382
|
+
}
|
|
7383
|
+
|
|
7361
7384
|
this.recEvents = [];
|
|
7362
7385
|
this.seqNo = 0;
|
|
7363
|
-
this.replayStartTime =
|
|
7386
|
+
this.replayStartTime = new Date().getTime();
|
|
7364
7387
|
|
|
7365
7388
|
this.replayId = _.UUID();
|
|
7366
7389
|
|
|
7367
|
-
if (shouldStopBatcher) {
|
|
7368
|
-
//
|
|
7390
|
+
if (shouldStopBatcher || this.recordMinMs > 0) {
|
|
7391
|
+
// the primary case for shouldStopBatcher is when we're starting recording after a reset
|
|
7369
7392
|
// and don't want to send anything over the network until there's
|
|
7370
7393
|
// actual user activity
|
|
7394
|
+
// this also applies if the minimum recording length has not been hit yet
|
|
7395
|
+
// so that we don't send data until we know the recording will be long enough
|
|
7371
7396
|
this.batcher.stop();
|
|
7372
7397
|
} else {
|
|
7373
7398
|
this.batcher.start();
|
|
@@ -7381,11 +7406,16 @@ MixpanelRecorder.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
7381
7406
|
}, this), this.get_config('record_idle_timeout_ms'));
|
|
7382
7407
|
}, this);
|
|
7383
7408
|
|
|
7409
|
+
var blockSelector = this.get_config('record_block_selector');
|
|
7410
|
+
if (blockSelector === '' || blockSelector === null) {
|
|
7411
|
+
blockSelector = undefined;
|
|
7412
|
+
}
|
|
7413
|
+
|
|
7384
7414
|
this._stopRecording = record({
|
|
7385
7415
|
'emit': _.bind(function (ev) {
|
|
7386
7416
|
this.batcher.enqueue(ev);
|
|
7387
7417
|
if (isUserEvent(ev)) {
|
|
7388
|
-
if (this.batcher.stopped) {
|
|
7418
|
+
if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
|
|
7389
7419
|
// start flushing again after user activity
|
|
7390
7420
|
this.batcher.start();
|
|
7391
7421
|
}
|
|
@@ -7393,7 +7423,7 @@ MixpanelRecorder.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
7393
7423
|
}
|
|
7394
7424
|
}, this),
|
|
7395
7425
|
'blockClass': this.get_config('record_block_class'),
|
|
7396
|
-
'blockSelector':
|
|
7426
|
+
'blockSelector': blockSelector,
|
|
7397
7427
|
'collectFonts': this.get_config('record_collect_fonts'),
|
|
7398
7428
|
'inlineImages': this.get_config('record_inline_images'),
|
|
7399
7429
|
'maskAllInputs': true,
|
|
@@ -7447,14 +7477,14 @@ MixpanelRecorder.prototype._onOptOut = function (code) {
|
|
|
7447
7477
|
}
|
|
7448
7478
|
};
|
|
7449
7479
|
|
|
7450
|
-
MixpanelRecorder.prototype._sendRequest = function(reqParams, reqBody, callback) {
|
|
7480
|
+
MixpanelRecorder.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
|
|
7451
7481
|
var onSuccess = _.bind(function (response, responseBody) {
|
|
7452
7482
|
// Increment sequence counter only if the request was successful to guarantee ordering.
|
|
7453
7483
|
// RequestBatcher will always flush the next batch after the previous one succeeds.
|
|
7454
|
-
if
|
|
7484
|
+
// extra check to see if the replay ID has changed so that we don't increment the seqNo on the wrong replay
|
|
7485
|
+
if (response.status === 200 && this.replayId === currentReplayId) {
|
|
7455
7486
|
this.seqNo++;
|
|
7456
7487
|
}
|
|
7457
|
-
|
|
7458
7488
|
callback({
|
|
7459
7489
|
status: 0,
|
|
7460
7490
|
httpStatusCode: response.status,
|
|
@@ -7477,7 +7507,7 @@ MixpanelRecorder.prototype._sendRequest = function(reqParams, reqBody, callback)
|
|
|
7477
7507
|
callback({error: error});
|
|
7478
7508
|
});
|
|
7479
7509
|
}).catch(function (error) {
|
|
7480
|
-
callback({error: error});
|
|
7510
|
+
callback({error: error, httpStatusCode: 0});
|
|
7481
7511
|
});
|
|
7482
7512
|
};
|
|
7483
7513
|
|
|
@@ -7485,9 +7515,15 @@ MixpanelRecorder.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
|
|
|
7485
7515
|
const numEvents = data.length;
|
|
7486
7516
|
|
|
7487
7517
|
if (numEvents > 0) {
|
|
7518
|
+
var replayId = this.replayId;
|
|
7488
7519
|
// each rrweb event has a timestamp - leverage those to get time properties
|
|
7489
7520
|
var batchStartTime = data[0].timestamp;
|
|
7490
|
-
if (this.seqNo === 0) {
|
|
7521
|
+
if (this.seqNo === 0 || !this.replayStartTime) {
|
|
7522
|
+
// extra safety net so that we don't send a null replay start time
|
|
7523
|
+
if (this.seqNo !== 0) {
|
|
7524
|
+
this.reportError('Replay start time not set but seqNo is not 0. Using current batch start time as a fallback.');
|
|
7525
|
+
}
|
|
7526
|
+
|
|
7491
7527
|
this.replayStartTime = batchStartTime;
|
|
7492
7528
|
}
|
|
7493
7529
|
var replayLengthMs = data[numEvents - 1].timestamp - this.replayStartTime;
|
|
@@ -7496,7 +7532,7 @@ MixpanelRecorder.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
|
|
|
7496
7532
|
'distinct_id': String(this._mixpanel.get_distinct_id()),
|
|
7497
7533
|
'seq': this.seqNo,
|
|
7498
7534
|
'batch_start_time': batchStartTime / 1000,
|
|
7499
|
-
'replay_id':
|
|
7535
|
+
'replay_id': replayId,
|
|
7500
7536
|
'replay_length_ms': replayLengthMs,
|
|
7501
7537
|
'replay_start_time': this.replayStartTime / 1000
|
|
7502
7538
|
};
|
|
@@ -7519,11 +7555,11 @@ MixpanelRecorder.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
|
|
|
7519
7555
|
.blob()
|
|
7520
7556
|
.then(_.bind(function(compressedBlob) {
|
|
7521
7557
|
reqParams['format'] = 'gzip';
|
|
7522
|
-
this._sendRequest(reqParams, compressedBlob, callback);
|
|
7558
|
+
this._sendRequest(replayId, reqParams, compressedBlob, callback);
|
|
7523
7559
|
}, this));
|
|
7524
7560
|
} else {
|
|
7525
7561
|
reqParams['format'] = 'body';
|
|
7526
|
-
this._sendRequest(reqParams, eventsJson, callback);
|
|
7562
|
+
this._sendRequest(replayId, reqParams, eventsJson, callback);
|
|
7527
7563
|
}
|
|
7528
7564
|
}
|
|
7529
7565
|
});
|
|
@@ -9011,6 +9047,7 @@ var DEFAULT_CONFIG = {
|
|
|
9011
9047
|
'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
|
|
9012
9048
|
'record_mask_text_selector': '*',
|
|
9013
9049
|
'record_max_ms': MAX_RECORDING_MS,
|
|
9050
|
+
'record_min_ms': 0,
|
|
9014
9051
|
'record_sessions_percent': 0,
|
|
9015
9052
|
'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js'
|
|
9016
9053
|
};
|
package/dist/mixpanel.umd.js
CHANGED
|
@@ -4513,7 +4513,7 @@
|
|
|
4513
4513
|
|
|
4514
4514
|
var Config = {
|
|
4515
4515
|
DEBUG: false,
|
|
4516
|
-
LIB_VERSION: '2.55.
|
|
4516
|
+
LIB_VERSION: '2.55.1'
|
|
4517
4517
|
};
|
|
4518
4518
|
|
|
4519
4519
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
@@ -4525,7 +4525,7 @@
|
|
|
4525
4525
|
hostname: ''
|
|
4526
4526
|
};
|
|
4527
4527
|
win = {
|
|
4528
|
-
navigator: { userAgent: '' },
|
|
4528
|
+
navigator: { userAgent: '', onLine: true },
|
|
4529
4529
|
document: {
|
|
4530
4530
|
location: loc,
|
|
4531
4531
|
referrer: ''
|
|
@@ -4539,6 +4539,8 @@
|
|
|
4539
4539
|
|
|
4540
4540
|
// Maximum allowed session recording length
|
|
4541
4541
|
var MAX_RECORDING_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
4542
|
+
// Maximum allowed value for minimum session recording length
|
|
4543
|
+
var MAX_VALUE_FOR_MIN_RECORDING_MS = 8 * 1000; // 8 seconds
|
|
4542
4544
|
|
|
4543
4545
|
/*
|
|
4544
4546
|
* Saved references to long variable names, so that closure compiler can
|
|
@@ -5479,7 +5481,7 @@
|
|
|
5479
5481
|
_.getQueryParam = function(url, param) {
|
|
5480
5482
|
// Expects a raw URL
|
|
5481
5483
|
|
|
5482
|
-
param = param.replace(/[[]
|
|
5484
|
+
param = param.replace(/[[]/g, '\\[').replace(/[\]]/g, '\\]');
|
|
5483
5485
|
var regexS = '[\\?&]' + param + '=([^&#]*)',
|
|
5484
5486
|
regex = new RegExp(regexS),
|
|
5485
5487
|
results = regex.exec(url);
|
|
@@ -5936,8 +5938,8 @@
|
|
|
5936
5938
|
};
|
|
5937
5939
|
})();
|
|
5938
5940
|
|
|
5939
|
-
var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
|
|
5940
|
-
var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'ttclid', 'twclid', 'wbraid'];
|
|
5941
|
+
var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term', 'utm_id', 'utm_source_platform','utm_campaign_id', 'utm_creative_format', 'utm_marketing_tactic'];
|
|
5942
|
+
var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'sccid', 'ttclid', 'twclid', 'wbraid'];
|
|
5941
5943
|
|
|
5942
5944
|
_.info = {
|
|
5943
5945
|
campaignParams: function(default_value) {
|
|
@@ -6219,6 +6221,15 @@
|
|
|
6219
6221
|
return matches ? matches[0] : '';
|
|
6220
6222
|
};
|
|
6221
6223
|
|
|
6224
|
+
/**
|
|
6225
|
+
* Check whether we have network connection. default to true for browsers that don't support navigator.onLine (IE)
|
|
6226
|
+
* @returns {boolean}
|
|
6227
|
+
*/
|
|
6228
|
+
var isOnline = function() {
|
|
6229
|
+
var onLine = win.navigator['onLine'];
|
|
6230
|
+
return _.isUndefined(onLine) || onLine;
|
|
6231
|
+
};
|
|
6232
|
+
|
|
6222
6233
|
var JSONStringify = null, JSONParse = null;
|
|
6223
6234
|
if (typeof JSON !== 'undefined') {
|
|
6224
6235
|
JSONStringify = JSON.stringify;
|
|
@@ -7182,7 +7193,12 @@
|
|
|
7182
7193
|
this.flush();
|
|
7183
7194
|
} else if (
|
|
7184
7195
|
_.isObject(res) &&
|
|
7185
|
-
(
|
|
7196
|
+
(
|
|
7197
|
+
res.httpStatusCode >= 500
|
|
7198
|
+
|| res.httpStatusCode === 429
|
|
7199
|
+
|| (res.httpStatusCode <= 0 && !isOnline())
|
|
7200
|
+
|| res.error === 'timeout'
|
|
7201
|
+
)
|
|
7186
7202
|
) {
|
|
7187
7203
|
// network or API error, or 429 Too Many Requests, retry
|
|
7188
7204
|
var retryMS = this.flushInterval * 2;
|
|
@@ -7333,6 +7349,7 @@
|
|
|
7333
7349
|
this.maxTimeoutId = null;
|
|
7334
7350
|
|
|
7335
7351
|
this.recordMaxMs = MAX_RECORDING_MS;
|
|
7352
|
+
this.recordMinMs = 0;
|
|
7336
7353
|
this._initBatcher();
|
|
7337
7354
|
};
|
|
7338
7355
|
|
|
@@ -7364,16 +7381,24 @@
|
|
|
7364
7381
|
logger.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
|
|
7365
7382
|
}
|
|
7366
7383
|
|
|
7384
|
+
this.recordMinMs = this.get_config('record_min_ms');
|
|
7385
|
+
if (this.recordMinMs > MAX_VALUE_FOR_MIN_RECORDING_MS) {
|
|
7386
|
+
this.recordMinMs = MAX_VALUE_FOR_MIN_RECORDING_MS;
|
|
7387
|
+
logger.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
|
|
7388
|
+
}
|
|
7389
|
+
|
|
7367
7390
|
this.recEvents = [];
|
|
7368
7391
|
this.seqNo = 0;
|
|
7369
|
-
this.replayStartTime =
|
|
7392
|
+
this.replayStartTime = new Date().getTime();
|
|
7370
7393
|
|
|
7371
7394
|
this.replayId = _.UUID();
|
|
7372
7395
|
|
|
7373
|
-
if (shouldStopBatcher) {
|
|
7374
|
-
//
|
|
7396
|
+
if (shouldStopBatcher || this.recordMinMs > 0) {
|
|
7397
|
+
// the primary case for shouldStopBatcher is when we're starting recording after a reset
|
|
7375
7398
|
// and don't want to send anything over the network until there's
|
|
7376
7399
|
// actual user activity
|
|
7400
|
+
// this also applies if the minimum recording length has not been hit yet
|
|
7401
|
+
// so that we don't send data until we know the recording will be long enough
|
|
7377
7402
|
this.batcher.stop();
|
|
7378
7403
|
} else {
|
|
7379
7404
|
this.batcher.start();
|
|
@@ -7387,11 +7412,16 @@
|
|
|
7387
7412
|
}, this), this.get_config('record_idle_timeout_ms'));
|
|
7388
7413
|
}, this);
|
|
7389
7414
|
|
|
7415
|
+
var blockSelector = this.get_config('record_block_selector');
|
|
7416
|
+
if (blockSelector === '' || blockSelector === null) {
|
|
7417
|
+
blockSelector = undefined;
|
|
7418
|
+
}
|
|
7419
|
+
|
|
7390
7420
|
this._stopRecording = record({
|
|
7391
7421
|
'emit': _.bind(function (ev) {
|
|
7392
7422
|
this.batcher.enqueue(ev);
|
|
7393
7423
|
if (isUserEvent(ev)) {
|
|
7394
|
-
if (this.batcher.stopped) {
|
|
7424
|
+
if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
|
|
7395
7425
|
// start flushing again after user activity
|
|
7396
7426
|
this.batcher.start();
|
|
7397
7427
|
}
|
|
@@ -7399,7 +7429,7 @@
|
|
|
7399
7429
|
}
|
|
7400
7430
|
}, this),
|
|
7401
7431
|
'blockClass': this.get_config('record_block_class'),
|
|
7402
|
-
'blockSelector':
|
|
7432
|
+
'blockSelector': blockSelector,
|
|
7403
7433
|
'collectFonts': this.get_config('record_collect_fonts'),
|
|
7404
7434
|
'inlineImages': this.get_config('record_inline_images'),
|
|
7405
7435
|
'maskAllInputs': true,
|
|
@@ -7453,14 +7483,14 @@
|
|
|
7453
7483
|
}
|
|
7454
7484
|
};
|
|
7455
7485
|
|
|
7456
|
-
MixpanelRecorder.prototype._sendRequest = function(reqParams, reqBody, callback) {
|
|
7486
|
+
MixpanelRecorder.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
|
|
7457
7487
|
var onSuccess = _.bind(function (response, responseBody) {
|
|
7458
7488
|
// Increment sequence counter only if the request was successful to guarantee ordering.
|
|
7459
7489
|
// RequestBatcher will always flush the next batch after the previous one succeeds.
|
|
7460
|
-
if
|
|
7490
|
+
// extra check to see if the replay ID has changed so that we don't increment the seqNo on the wrong replay
|
|
7491
|
+
if (response.status === 200 && this.replayId === currentReplayId) {
|
|
7461
7492
|
this.seqNo++;
|
|
7462
7493
|
}
|
|
7463
|
-
|
|
7464
7494
|
callback({
|
|
7465
7495
|
status: 0,
|
|
7466
7496
|
httpStatusCode: response.status,
|
|
@@ -7483,7 +7513,7 @@
|
|
|
7483
7513
|
callback({error: error});
|
|
7484
7514
|
});
|
|
7485
7515
|
}).catch(function (error) {
|
|
7486
|
-
callback({error: error});
|
|
7516
|
+
callback({error: error, httpStatusCode: 0});
|
|
7487
7517
|
});
|
|
7488
7518
|
};
|
|
7489
7519
|
|
|
@@ -7491,9 +7521,15 @@
|
|
|
7491
7521
|
const numEvents = data.length;
|
|
7492
7522
|
|
|
7493
7523
|
if (numEvents > 0) {
|
|
7524
|
+
var replayId = this.replayId;
|
|
7494
7525
|
// each rrweb event has a timestamp - leverage those to get time properties
|
|
7495
7526
|
var batchStartTime = data[0].timestamp;
|
|
7496
|
-
if (this.seqNo === 0) {
|
|
7527
|
+
if (this.seqNo === 0 || !this.replayStartTime) {
|
|
7528
|
+
// extra safety net so that we don't send a null replay start time
|
|
7529
|
+
if (this.seqNo !== 0) {
|
|
7530
|
+
this.reportError('Replay start time not set but seqNo is not 0. Using current batch start time as a fallback.');
|
|
7531
|
+
}
|
|
7532
|
+
|
|
7497
7533
|
this.replayStartTime = batchStartTime;
|
|
7498
7534
|
}
|
|
7499
7535
|
var replayLengthMs = data[numEvents - 1].timestamp - this.replayStartTime;
|
|
@@ -7502,7 +7538,7 @@
|
|
|
7502
7538
|
'distinct_id': String(this._mixpanel.get_distinct_id()),
|
|
7503
7539
|
'seq': this.seqNo,
|
|
7504
7540
|
'batch_start_time': batchStartTime / 1000,
|
|
7505
|
-
'replay_id':
|
|
7541
|
+
'replay_id': replayId,
|
|
7506
7542
|
'replay_length_ms': replayLengthMs,
|
|
7507
7543
|
'replay_start_time': this.replayStartTime / 1000
|
|
7508
7544
|
};
|
|
@@ -7525,11 +7561,11 @@
|
|
|
7525
7561
|
.blob()
|
|
7526
7562
|
.then(_.bind(function(compressedBlob) {
|
|
7527
7563
|
reqParams['format'] = 'gzip';
|
|
7528
|
-
this._sendRequest(reqParams, compressedBlob, callback);
|
|
7564
|
+
this._sendRequest(replayId, reqParams, compressedBlob, callback);
|
|
7529
7565
|
}, this));
|
|
7530
7566
|
} else {
|
|
7531
7567
|
reqParams['format'] = 'body';
|
|
7532
|
-
this._sendRequest(reqParams, eventsJson, callback);
|
|
7568
|
+
this._sendRequest(replayId, reqParams, eventsJson, callback);
|
|
7533
7569
|
}
|
|
7534
7570
|
}
|
|
7535
7571
|
});
|
|
@@ -9017,6 +9053,7 @@
|
|
|
9017
9053
|
'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
|
|
9018
9054
|
'record_mask_text_selector': '*',
|
|
9019
9055
|
'record_max_ms': MAX_RECORDING_MS,
|
|
9056
|
+
'record_min_ms': 0,
|
|
9020
9057
|
'record_sessions_percent': 0,
|
|
9021
9058
|
'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js'
|
|
9022
9059
|
};
|
package/package.json
CHANGED
package/src/config.js
CHANGED
package/src/mixpanel-core.js
CHANGED
|
@@ -152,6 +152,7 @@ var DEFAULT_CONFIG = {
|
|
|
152
152
|
'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
|
|
153
153
|
'record_mask_text_selector': '*',
|
|
154
154
|
'record_max_ms': MAX_RECORDING_MS,
|
|
155
|
+
'record_min_ms': 0,
|
|
155
156
|
'record_sessions_percent': 0,
|
|
156
157
|
'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js'
|
|
157
158
|
};
|
package/src/recorder/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { record } from 'rrweb';
|
|
2
2
|
import { IncrementalSource, EventType } from '@rrweb/types';
|
|
3
3
|
|
|
4
|
-
import { MAX_RECORDING_MS, console_with_prefix, _, window} from '../utils'; // eslint-disable-line camelcase
|
|
4
|
+
import { MAX_RECORDING_MS, MAX_VALUE_FOR_MIN_RECORDING_MS, console_with_prefix, _, window} from '../utils'; // eslint-disable-line camelcase
|
|
5
5
|
import { addOptOutCheckMixpanelLib } from '../gdpr-utils';
|
|
6
6
|
import { RequestBatcher } from '../request-batcher';
|
|
7
7
|
|
|
@@ -47,6 +47,7 @@ var MixpanelRecorder = function(mixpanelInstance) {
|
|
|
47
47
|
this.maxTimeoutId = null;
|
|
48
48
|
|
|
49
49
|
this.recordMaxMs = MAX_RECORDING_MS;
|
|
50
|
+
this.recordMinMs = 0;
|
|
50
51
|
this._initBatcher();
|
|
51
52
|
};
|
|
52
53
|
|
|
@@ -78,16 +79,24 @@ MixpanelRecorder.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
78
79
|
logger.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
this.recordMinMs = this.get_config('record_min_ms');
|
|
83
|
+
if (this.recordMinMs > MAX_VALUE_FOR_MIN_RECORDING_MS) {
|
|
84
|
+
this.recordMinMs = MAX_VALUE_FOR_MIN_RECORDING_MS;
|
|
85
|
+
logger.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
|
|
86
|
+
}
|
|
87
|
+
|
|
81
88
|
this.recEvents = [];
|
|
82
89
|
this.seqNo = 0;
|
|
83
|
-
this.replayStartTime =
|
|
90
|
+
this.replayStartTime = new Date().getTime();
|
|
84
91
|
|
|
85
92
|
this.replayId = _.UUID();
|
|
86
93
|
|
|
87
|
-
if (shouldStopBatcher) {
|
|
88
|
-
//
|
|
94
|
+
if (shouldStopBatcher || this.recordMinMs > 0) {
|
|
95
|
+
// the primary case for shouldStopBatcher is when we're starting recording after a reset
|
|
89
96
|
// and don't want to send anything over the network until there's
|
|
90
97
|
// actual user activity
|
|
98
|
+
// this also applies if the minimum recording length has not been hit yet
|
|
99
|
+
// so that we don't send data until we know the recording will be long enough
|
|
91
100
|
this.batcher.stop();
|
|
92
101
|
} else {
|
|
93
102
|
this.batcher.start();
|
|
@@ -101,11 +110,16 @@ MixpanelRecorder.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
101
110
|
}, this), this.get_config('record_idle_timeout_ms'));
|
|
102
111
|
}, this);
|
|
103
112
|
|
|
113
|
+
var blockSelector = this.get_config('record_block_selector');
|
|
114
|
+
if (blockSelector === '' || blockSelector === null) {
|
|
115
|
+
blockSelector = undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
104
118
|
this._stopRecording = record({
|
|
105
119
|
'emit': _.bind(function (ev) {
|
|
106
120
|
this.batcher.enqueue(ev);
|
|
107
121
|
if (isUserEvent(ev)) {
|
|
108
|
-
if (this.batcher.stopped) {
|
|
122
|
+
if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
|
|
109
123
|
// start flushing again after user activity
|
|
110
124
|
this.batcher.start();
|
|
111
125
|
}
|
|
@@ -113,7 +127,7 @@ MixpanelRecorder.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
113
127
|
}
|
|
114
128
|
}, this),
|
|
115
129
|
'blockClass': this.get_config('record_block_class'),
|
|
116
|
-
'blockSelector':
|
|
130
|
+
'blockSelector': blockSelector,
|
|
117
131
|
'collectFonts': this.get_config('record_collect_fonts'),
|
|
118
132
|
'inlineImages': this.get_config('record_inline_images'),
|
|
119
133
|
'maskAllInputs': true,
|
|
@@ -167,14 +181,14 @@ MixpanelRecorder.prototype._onOptOut = function (code) {
|
|
|
167
181
|
}
|
|
168
182
|
};
|
|
169
183
|
|
|
170
|
-
MixpanelRecorder.prototype._sendRequest = function(reqParams, reqBody, callback) {
|
|
184
|
+
MixpanelRecorder.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
|
|
171
185
|
var onSuccess = _.bind(function (response, responseBody) {
|
|
172
186
|
// Increment sequence counter only if the request was successful to guarantee ordering.
|
|
173
187
|
// RequestBatcher will always flush the next batch after the previous one succeeds.
|
|
174
|
-
if
|
|
188
|
+
// extra check to see if the replay ID has changed so that we don't increment the seqNo on the wrong replay
|
|
189
|
+
if (response.status === 200 && this.replayId === currentReplayId) {
|
|
175
190
|
this.seqNo++;
|
|
176
191
|
}
|
|
177
|
-
|
|
178
192
|
callback({
|
|
179
193
|
status: 0,
|
|
180
194
|
httpStatusCode: response.status,
|
|
@@ -197,7 +211,7 @@ MixpanelRecorder.prototype._sendRequest = function(reqParams, reqBody, callback)
|
|
|
197
211
|
callback({error: error});
|
|
198
212
|
});
|
|
199
213
|
}).catch(function (error) {
|
|
200
|
-
callback({error: error});
|
|
214
|
+
callback({error: error, httpStatusCode: 0});
|
|
201
215
|
});
|
|
202
216
|
};
|
|
203
217
|
|
|
@@ -205,9 +219,15 @@ MixpanelRecorder.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
|
|
|
205
219
|
const numEvents = data.length;
|
|
206
220
|
|
|
207
221
|
if (numEvents > 0) {
|
|
222
|
+
var replayId = this.replayId;
|
|
208
223
|
// each rrweb event has a timestamp - leverage those to get time properties
|
|
209
224
|
var batchStartTime = data[0].timestamp;
|
|
210
|
-
if (this.seqNo === 0) {
|
|
225
|
+
if (this.seqNo === 0 || !this.replayStartTime) {
|
|
226
|
+
// extra safety net so that we don't send a null replay start time
|
|
227
|
+
if (this.seqNo !== 0) {
|
|
228
|
+
this.reportError('Replay start time not set but seqNo is not 0. Using current batch start time as a fallback.');
|
|
229
|
+
}
|
|
230
|
+
|
|
211
231
|
this.replayStartTime = batchStartTime;
|
|
212
232
|
}
|
|
213
233
|
var replayLengthMs = data[numEvents - 1].timestamp - this.replayStartTime;
|
|
@@ -216,7 +236,7 @@ MixpanelRecorder.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
|
|
|
216
236
|
'distinct_id': String(this._mixpanel.get_distinct_id()),
|
|
217
237
|
'seq': this.seqNo,
|
|
218
238
|
'batch_start_time': batchStartTime / 1000,
|
|
219
|
-
'replay_id':
|
|
239
|
+
'replay_id': replayId,
|
|
220
240
|
'replay_length_ms': replayLengthMs,
|
|
221
241
|
'replay_start_time': this.replayStartTime / 1000
|
|
222
242
|
};
|
|
@@ -239,11 +259,11 @@ MixpanelRecorder.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
|
|
|
239
259
|
.blob()
|
|
240
260
|
.then(_.bind(function(compressedBlob) {
|
|
241
261
|
reqParams['format'] = 'gzip';
|
|
242
|
-
this._sendRequest(reqParams, compressedBlob, callback);
|
|
262
|
+
this._sendRequest(replayId, reqParams, compressedBlob, callback);
|
|
243
263
|
}, this));
|
|
244
264
|
} else {
|
|
245
265
|
reqParams['format'] = 'body';
|
|
246
|
-
this._sendRequest(reqParams, eventsJson, callback);
|
|
266
|
+
this._sendRequest(replayId, reqParams, eventsJson, callback);
|
|
247
267
|
}
|
|
248
268
|
}
|
|
249
269
|
});
|
package/src/request-batcher.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Config from './config';
|
|
2
2
|
import { RequestQueue } from './request-queue';
|
|
3
|
-
import { console_with_prefix, _ } from './utils'; // eslint-disable-line camelcase
|
|
3
|
+
import { console_with_prefix, isOnline, _ } from './utils'; // eslint-disable-line camelcase
|
|
4
4
|
|
|
5
5
|
// maximum interval between request retries after exponential backoff
|
|
6
6
|
var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
|
|
@@ -198,7 +198,12 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
198
198
|
this.flush();
|
|
199
199
|
} else if (
|
|
200
200
|
_.isObject(res) &&
|
|
201
|
-
(
|
|
201
|
+
(
|
|
202
|
+
res.httpStatusCode >= 500
|
|
203
|
+
|| res.httpStatusCode === 429
|
|
204
|
+
|| (res.httpStatusCode <= 0 && !isOnline())
|
|
205
|
+
|| res.error === 'timeout'
|
|
206
|
+
)
|
|
202
207
|
) {
|
|
203
208
|
// network or API error, or 429 Too Many Requests, retry
|
|
204
209
|
var retryMS = this.flushInterval * 2;
|