Skip to content

Commit f4ceda4

Browse files
authored
Merge pull request #217 from mixpanel/2.55.1-rc
2.55.1 rc
2 parents 93df8eb + cb938d7 commit f4ceda4

21 files changed

+889
-319
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
**2.55.1** (27 Aug 2024)
2+
- Adds a minimum recording length option for session recording
3+
- Fixes and improvements for session recording batcher to support offline queueing and retry
4+
15
**2.55.0** (2 Aug 2024)
26
- Added new build to support native JavaScript modules
37

dist/mixpanel-core.cjs.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
var Config = {
44
DEBUG: false,
5-
LIB_VERSION: '2.55.0'
5+
LIB_VERSION: '2.55.1'
66
};
77

88
/* eslint camelcase: "off", eqeqeq: "off" */
@@ -14,7 +14,7 @@ if (typeof(window) === 'undefined') {
1414
hostname: ''
1515
};
1616
win = {
17-
navigator: { userAgent: '' },
17+
navigator: { userAgent: '', onLine: true },
1818
document: {
1919
location: loc,
2020
referrer: ''
@@ -968,7 +968,7 @@ _.HTTPBuildQuery = function(formdata, arg_separator) {
968968
_.getQueryParam = function(url, param) {
969969
// Expects a raw URL
970970

971-
param = param.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
971+
param = param.replace(/[[]/g, '\\[').replace(/[\]]/g, '\\]');
972972
var regexS = '[\\?&]' + param + '=([^&#]*)',
973973
regex = new RegExp(regexS),
974974
results = regex.exec(url);
@@ -1425,8 +1425,8 @@ _.dom_query = (function() {
14251425
};
14261426
})();
14271427

1428-
var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
1429-
var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'ttclid', 'twclid', 'wbraid'];
1428+
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'];
1429+
var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'sccid', 'ttclid', 'twclid', 'wbraid'];
14301430

14311431
_.info = {
14321432
campaignParams: function(default_value) {
@@ -1708,6 +1708,15 @@ var extract_domain = function(hostname) {
17081708
return matches ? matches[0] : '';
17091709
};
17101710

1711+
/**
1712+
* Check whether we have network connection. default to true for browsers that don't support navigator.onLine (IE)
1713+
* @returns {boolean}
1714+
*/
1715+
var isOnline = function() {
1716+
var onLine = win.navigator['onLine'];
1717+
return _.isUndefined(onLine) || onLine;
1718+
};
1719+
17111720
var JSONStringify = null, JSONParse = null;
17121721
if (typeof JSON !== 'undefined') {
17131722
JSONStringify = JSON.stringify;
@@ -2523,7 +2532,12 @@ RequestBatcher.prototype.flush = function(options) {
25232532
this.flush();
25242533
} else if (
25252534
_.isObject(res) &&
2526-
(res.httpStatusCode >= 500 || res.httpStatusCode === 429 || res.error === 'timeout')
2535+
(
2536+
res.httpStatusCode >= 500
2537+
|| res.httpStatusCode === 429
2538+
|| (res.httpStatusCode <= 0 && !isOnline())
2539+
|| res.error === 'timeout'
2540+
)
25272541
) {
25282542
// network or API error, or 429 Too Many Requests, retry
25292543
var retryMS = this.flushInterval * 2;
@@ -4247,6 +4261,7 @@ var DEFAULT_CONFIG = {
42474261
'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
42484262
'record_mask_text_selector': '*',
42494263
'record_max_ms': MAX_RECORDING_MS,
4264+
'record_min_ms': 0,
42504265
'record_sessions_percent': 0,
42514266
'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js'
42524267
};

dist/mixpanel-recorder.js

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4510,7 +4510,7 @@
45104510

45114511
var Config = {
45124512
DEBUG: false,
4513-
LIB_VERSION: '2.55.0'
4513+
LIB_VERSION: '2.55.1'
45144514
};
45154515

45164516
/* eslint camelcase: "off", eqeqeq: "off" */
@@ -4522,7 +4522,7 @@
45224522
hostname: ''
45234523
};
45244524
win = {
4525-
navigator: { userAgent: '' },
4525+
navigator: { userAgent: '', onLine: true },
45264526
document: {
45274527
location: loc,
45284528
referrer: ''
@@ -4536,6 +4536,8 @@
45364536

45374537
// Maximum allowed session recording length
45384538
var MAX_RECORDING_MS = 24 * 60 * 60 * 1000; // 24 hours
4539+
// Maximum allowed value for minimum session recording length
4540+
var MAX_VALUE_FOR_MIN_RECORDING_MS = 8 * 1000; // 8 seconds
45394541

45404542
/*
45414543
* Saved references to long variable names, so that closure compiler can
@@ -5447,7 +5449,7 @@
54475449
_.getQueryParam = function(url, param) {
54485450
// Expects a raw URL
54495451

5450-
param = param.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
5452+
param = param.replace(/[[]/g, '\\[').replace(/[\]]/g, '\\]');
54515453
var regexS = '[\\?&]' + param + '=([^&#]*)',
54525454
regex = new RegExp(regexS),
54535455
results = regex.exec(url);
@@ -5904,8 +5906,8 @@
59045906
};
59055907
})();
59065908

5907-
var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
5908-
var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'ttclid', 'twclid', 'wbraid'];
5909+
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'];
5910+
var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'sccid', 'ttclid', 'twclid', 'wbraid'];
59095911

59105912
_.info = {
59115913
campaignParams: function(default_value) {
@@ -6187,6 +6189,15 @@
61876189
return matches ? matches[0] : '';
61886190
};
61896191

6192+
/**
6193+
* Check whether we have network connection. default to true for browsers that don't support navigator.onLine (IE)
6194+
* @returns {boolean}
6195+
*/
6196+
var isOnline = function() {
6197+
var onLine = win.navigator['onLine'];
6198+
return _.isUndefined(onLine) || onLine;
6199+
};
6200+
61906201
var JSONStringify = null, JSONParse = null;
61916202
if (typeof JSON !== 'undefined') {
61926203
JSONStringify = JSON.stringify;
@@ -7018,7 +7029,12 @@
70187029
this.flush();
70197030
} else if (
70207031
_.isObject(res) &&
7021-
(res.httpStatusCode >= 500 || res.httpStatusCode === 429 || res.error === 'timeout')
7032+
(
7033+
res.httpStatusCode >= 500
7034+
|| res.httpStatusCode === 429
7035+
|| (res.httpStatusCode <= 0 && !isOnline())
7036+
|| res.error === 'timeout'
7037+
)
70227038
) {
70237039
// network or API error, or 429 Too Many Requests, retry
70247040
var retryMS = this.flushInterval * 2;
@@ -7169,6 +7185,7 @@
71697185
this.maxTimeoutId = null;
71707186

71717187
this.recordMaxMs = MAX_RECORDING_MS;
7188+
this.recordMinMs = 0;
71727189
this._initBatcher();
71737190
};
71747191

@@ -7200,16 +7217,24 @@
72007217
logger.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
72017218
}
72027219

7220+
this.recordMinMs = this.get_config('record_min_ms');
7221+
if (this.recordMinMs > MAX_VALUE_FOR_MIN_RECORDING_MS) {
7222+
this.recordMinMs = MAX_VALUE_FOR_MIN_RECORDING_MS;
7223+
logger.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
7224+
}
7225+
72037226
this.recEvents = [];
72047227
this.seqNo = 0;
7205-
this.replayStartTime = null;
7228+
this.replayStartTime = new Date().getTime();
72067229

72077230
this.replayId = _.UUID();
72087231

7209-
if (shouldStopBatcher) {
7210-
// this is the case when we're starting recording after a reset
7232+
if (shouldStopBatcher || this.recordMinMs > 0) {
7233+
// the primary case for shouldStopBatcher is when we're starting recording after a reset
72117234
// and don't want to send anything over the network until there's
72127235
// actual user activity
7236+
// this also applies if the minimum recording length has not been hit yet
7237+
// so that we don't send data until we know the recording will be long enough
72137238
this.batcher.stop();
72147239
} else {
72157240
this.batcher.start();
@@ -7223,19 +7248,24 @@
72237248
}, this), this.get_config('record_idle_timeout_ms'));
72247249
}, this);
72257250

7251+
var blockSelector = this.get_config('record_block_selector');
7252+
if (blockSelector === '' || blockSelector === null) {
7253+
blockSelector = undefined;
7254+
}
7255+
72267256
this._stopRecording = record({
72277257
'emit': _.bind(function (ev) {
72287258
this.batcher.enqueue(ev);
72297259
if (isUserEvent(ev)) {
7230-
if (this.batcher.stopped) {
7260+
if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
72317261
// start flushing again after user activity
72327262
this.batcher.start();
72337263
}
72347264
resetIdleTimeout();
72357265
}
72367266
}, this),
72377267
'blockClass': this.get_config('record_block_class'),
7238-
'blockSelector': this.get_config('record_block_selector'),
7268+
'blockSelector': blockSelector,
72397269
'collectFonts': this.get_config('record_collect_fonts'),
72407270
'inlineImages': this.get_config('record_inline_images'),
72417271
'maskAllInputs': true,
@@ -7289,14 +7319,14 @@
72897319
}
72907320
};
72917321

7292-
MixpanelRecorder.prototype._sendRequest = function(reqParams, reqBody, callback) {
7322+
MixpanelRecorder.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
72937323
var onSuccess = _.bind(function (response, responseBody) {
72947324
// Increment sequence counter only if the request was successful to guarantee ordering.
72957325
// RequestBatcher will always flush the next batch after the previous one succeeds.
7296-
if (response.status === 200) {
7326+
// extra check to see if the replay ID has changed so that we don't increment the seqNo on the wrong replay
7327+
if (response.status === 200 && this.replayId === currentReplayId) {
72977328
this.seqNo++;
72987329
}
7299-
73007330
callback({
73017331
status: 0,
73027332
httpStatusCode: response.status,
@@ -7319,17 +7349,23 @@
73197349
callback({error: error});
73207350
});
73217351
}).catch(function (error) {
7322-
callback({error: error});
7352+
callback({error: error, httpStatusCode: 0});
73237353
});
73247354
};
73257355

73267356
MixpanelRecorder.prototype._flushEvents = addOptOutCheckMixpanelLib(function (data, options, callback) {
73277357
const numEvents = data.length;
73287358

73297359
if (numEvents > 0) {
7360+
var replayId = this.replayId;
73307361
// each rrweb event has a timestamp - leverage those to get time properties
73317362
var batchStartTime = data[0].timestamp;
7332-
if (this.seqNo === 0) {
7363+
if (this.seqNo === 0 || !this.replayStartTime) {
7364+
// extra safety net so that we don't send a null replay start time
7365+
if (this.seqNo !== 0) {
7366+
this.reportError('Replay start time not set but seqNo is not 0. Using current batch start time as a fallback.');
7367+
}
7368+
73337369
this.replayStartTime = batchStartTime;
73347370
}
73357371
var replayLengthMs = data[numEvents - 1].timestamp - this.replayStartTime;
@@ -7338,7 +7374,7 @@
73387374
'distinct_id': String(this._mixpanel.get_distinct_id()),
73397375
'seq': this.seqNo,
73407376
'batch_start_time': batchStartTime / 1000,
7341-
'replay_id': this.replayId,
7377+
'replay_id': replayId,
73427378
'replay_length_ms': replayLengthMs,
73437379
'replay_start_time': this.replayStartTime / 1000
73447380
};
@@ -7361,11 +7397,11 @@
73617397
.blob()
73627398
.then(_.bind(function(compressedBlob) {
73637399
reqParams['format'] = 'gzip';
7364-
this._sendRequest(reqParams, compressedBlob, callback);
7400+
this._sendRequest(replayId, reqParams, compressedBlob, callback);
73657401
}, this));
73667402
} else {
73677403
reqParams['format'] = 'body';
7368-
this._sendRequest(reqParams, eventsJson, callback);
7404+
this._sendRequest(replayId, reqParams, eventsJson, callback);
73697405
}
73707406
}
73717407
});

dist/mixpanel-recorder.min.js

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/mixpanel-with-async-recorder.cjs.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
var Config = {
44
DEBUG: false,
5-
LIB_VERSION: '2.55.0'
5+
LIB_VERSION: '2.55.1'
66
};
77

88
/* eslint camelcase: "off", eqeqeq: "off" */
@@ -14,7 +14,7 @@ if (typeof(window) === 'undefined') {
1414
hostname: ''
1515
};
1616
win = {
17-
navigator: { userAgent: '' },
17+
navigator: { userAgent: '', onLine: true },
1818
document: {
1919
location: loc,
2020
referrer: ''
@@ -968,7 +968,7 @@ _.HTTPBuildQuery = function(formdata, arg_separator) {
968968
_.getQueryParam = function(url, param) {
969969
// Expects a raw URL
970970

971-
param = param.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
971+
param = param.replace(/[[]/g, '\\[').replace(/[\]]/g, '\\]');
972972
var regexS = '[\\?&]' + param + '=([^&#]*)',
973973
regex = new RegExp(regexS),
974974
results = regex.exec(url);
@@ -1425,8 +1425,8 @@ _.dom_query = (function() {
14251425
};
14261426
})();
14271427

1428-
var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
1429-
var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'ttclid', 'twclid', 'wbraid'];
1428+
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'];
1429+
var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'sccid', 'ttclid', 'twclid', 'wbraid'];
14301430

14311431
_.info = {
14321432
campaignParams: function(default_value) {
@@ -1708,6 +1708,15 @@ var extract_domain = function(hostname) {
17081708
return matches ? matches[0] : '';
17091709
};
17101710

1711+
/**
1712+
* Check whether we have network connection. default to true for browsers that don't support navigator.onLine (IE)
1713+
* @returns {boolean}
1714+
*/
1715+
var isOnline = function() {
1716+
var onLine = win.navigator['onLine'];
1717+
return _.isUndefined(onLine) || onLine;
1718+
};
1719+
17111720
var JSONStringify = null, JSONParse = null;
17121721
if (typeof JSON !== 'undefined') {
17131722
JSONStringify = JSON.stringify;
@@ -2523,7 +2532,12 @@ RequestBatcher.prototype.flush = function(options) {
25232532
this.flush();
25242533
} else if (
25252534
_.isObject(res) &&
2526-
(res.httpStatusCode >= 500 || res.httpStatusCode === 429 || res.error === 'timeout')
2535+
(
2536+
res.httpStatusCode >= 500
2537+
|| res.httpStatusCode === 429
2538+
|| (res.httpStatusCode <= 0 && !isOnline())
2539+
|| res.error === 'timeout'
2540+
)
25272541
) {
25282542
// network or API error, or 429 Too Many Requests, retry
25292543
var retryMS = this.flushInterval * 2;
@@ -4247,6 +4261,7 @@ var DEFAULT_CONFIG = {
42474261
'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
42484262
'record_mask_text_selector': '*',
42494263
'record_max_ms': MAX_RECORDING_MS,
4264+
'record_min_ms': 0,
42504265
'record_sessions_percent': 0,
42514266
'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js'
42524267
};

0 commit comments

Comments
 (0)