mixpanel-browser 2.71.1 → 2.73.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/.github/workflows/tests.yml +1 -0
- package/CHANGELOG.md +12 -0
- package/dist/mixpanel-core.cjs.d.ts +84 -13
- package/dist/mixpanel-core.cjs.js +180 -28
- package/dist/mixpanel-recorder.js +684 -114
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +84 -13
- package/dist/mixpanel-with-async-recorder.cjs.js +180 -28
- package/dist/mixpanel-with-recorder.d.ts +84 -13
- package/dist/mixpanel-with-recorder.js +860 -140
- package/dist/mixpanel-with-recorder.min.d.ts +84 -13
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +84 -13
- package/dist/mixpanel.amd.js +860 -140
- package/dist/mixpanel.cjs.d.ts +84 -13
- package/dist/mixpanel.cjs.js +860 -140
- package/dist/mixpanel.globals.js +180 -28
- package/dist/mixpanel.min.js +172 -170
- package/dist/mixpanel.module.d.ts +84 -13
- package/dist/mixpanel.module.js +860 -140
- package/dist/mixpanel.umd.d.ts +84 -13
- package/dist/mixpanel.umd.js +860 -140
- package/dist/rrweb-bundled.js +12760 -0
- package/dist/rrweb-compiled.js +2496 -7176
- package/package.json +3 -2
- package/rollup.config.mjs +15 -4
- package/src/autocapture/index.js +1 -1
- package/src/autocapture/rageclick.js +20 -1
- package/src/autocapture/shadow-dom-observer.js +3 -15
- package/src/autocapture/utils.js +30 -0
- package/src/config.js +1 -1
- package/src/index.d.ts +84 -13
- package/src/mixpanel-core.js +127 -10
- package/src/recorder/recorder.js +1 -1
- package/src/recorder/rrweb-entrypoint.js +6 -0
- package/src/recorder/session-recording.js +69 -12
- package/src/utils.js +24 -0
- package/src/window.js +3 -1
- package/.claude/settings.local.json +0 -9
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { window } from '../window';
|
|
2
|
-
import { IncrementalSource, EventType } from '
|
|
3
|
-
import { MAX_RECORDING_MS, MAX_VALUE_FOR_MIN_RECORDING_MS, console_with_prefix, NOOP_FUNC, _, localStorageSupported} from '../utils'; // eslint-disable-line camelcase
|
|
2
|
+
import { IncrementalSource, EventType, getRecordConsolePlugin } from './rrweb-entrypoint';
|
|
3
|
+
import { MAX_RECORDING_MS, MAX_VALUE_FOR_MIN_RECORDING_MS, console_with_prefix, NOOP_FUNC, _, localStorageSupported, canUseCompressionStream, navigator, userAgent, windowOpera} from '../utils'; // eslint-disable-line camelcase
|
|
4
4
|
import { IDBStorageWrapper, RECORDING_EVENTS_STORE_NAME } from '../storage/indexed-db';
|
|
5
5
|
import { addOptOutCheckMixpanelLib } from '../gdpr-utils';
|
|
6
6
|
import { RequestBatcher } from '../request-batcher';
|
|
@@ -38,6 +38,7 @@ function isUserEvent(ev) {
|
|
|
38
38
|
* @property {number} idleExpires
|
|
39
39
|
* @property {number} maxExpires
|
|
40
40
|
* @property {number} replayStartTime
|
|
41
|
+
* @property {number} lastEventTimestamp
|
|
41
42
|
* @property {number} seqNo
|
|
42
43
|
* @property {string} batchStartUrl
|
|
43
44
|
* @property {string} replayId
|
|
@@ -58,6 +59,7 @@ function isUserEvent(ev) {
|
|
|
58
59
|
* @property {number} idleExpires
|
|
59
60
|
* @property {number} maxExpires
|
|
60
61
|
* @property {number} replayStartTime
|
|
62
|
+
* @property {number} lastEventTimestamp - the unix timestamp of the last recorded event from rrweb
|
|
61
63
|
* @property {number} seqNo
|
|
62
64
|
* @property {string} batchStartUrl
|
|
63
65
|
* @property {string} replayStartUrl
|
|
@@ -91,6 +93,7 @@ var SessionRecording = function(options) {
|
|
|
91
93
|
this.idleExpires = options.idleExpires || null;
|
|
92
94
|
this.maxExpires = options.maxExpires || null;
|
|
93
95
|
this.replayStartTime = options.replayStartTime || null;
|
|
96
|
+
this.lastEventTimestamp = options.lastEventTimestamp || null;
|
|
94
97
|
this.seqNo = options.seqNo || 0;
|
|
95
98
|
|
|
96
99
|
this.idleTimeoutId = null;
|
|
@@ -150,10 +153,20 @@ SessionRecording.prototype.getUserIdInfo = function () {
|
|
|
150
153
|
|
|
151
154
|
SessionRecording.prototype.unloadPersistedData = function () {
|
|
152
155
|
this.batcher.stop();
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
|
|
157
|
+
return this.queueStorage.init().catch(function () {
|
|
158
|
+
this.reportError('Error initializing IndexedDB storage for unloading persisted data.');
|
|
159
|
+
}.bind(this)).then(function () {
|
|
160
|
+
// if the recording is too short, just delete any stored events without flushing
|
|
161
|
+
if (this.getDurationMs() < this._getRecordMinMs()) {
|
|
155
162
|
return this.queueStorage.removeItem(this.batcherKey);
|
|
156
|
-
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return this.batcher.flush()
|
|
166
|
+
.then(function () {
|
|
167
|
+
return this.queueStorage.removeItem(this.batcherKey);
|
|
168
|
+
}.bind(this));
|
|
169
|
+
}.bind(this));
|
|
157
170
|
};
|
|
158
171
|
|
|
159
172
|
SessionRecording.prototype.getConfig = function(configVar) {
|
|
@@ -188,11 +201,7 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
188
201
|
this.maxExpires = new Date().getTime() + this.recordMaxMs;
|
|
189
202
|
}
|
|
190
203
|
|
|
191
|
-
this.recordMinMs = this.
|
|
192
|
-
if (this.recordMinMs > MAX_VALUE_FOR_MIN_RECORDING_MS) {
|
|
193
|
-
this.recordMinMs = MAX_VALUE_FOR_MIN_RECORDING_MS;
|
|
194
|
-
logger.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
|
|
195
|
-
}
|
|
204
|
+
this.recordMinMs = this._getRecordMinMs();
|
|
196
205
|
|
|
197
206
|
if (!this.replayStartTime) {
|
|
198
207
|
this.replayStartTime = new Date().getTime();
|
|
@@ -240,6 +249,11 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
240
249
|
}
|
|
241
250
|
// promise only used to await during tests
|
|
242
251
|
this.__enqueuePromise = this.batcher.enqueue(ev);
|
|
252
|
+
|
|
253
|
+
// Capture the timestamp of the last event for duration calculation.
|
|
254
|
+
if (this.lastEventTimestamp === null || ev.timestamp > this.lastEventTimestamp) {
|
|
255
|
+
this.lastEventTimestamp = ev.timestamp;
|
|
256
|
+
}
|
|
243
257
|
}.bind(this),
|
|
244
258
|
'blockClass': this.getConfig('record_block_class'),
|
|
245
259
|
'blockSelector': blockSelector,
|
|
@@ -254,7 +268,16 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
254
268
|
'recordCanvas': this.getConfig('record_canvas'),
|
|
255
269
|
'sampling': {
|
|
256
270
|
'canvas': 15
|
|
257
|
-
}
|
|
271
|
+
},
|
|
272
|
+
'plugins': this.getConfig('record_console') ? [
|
|
273
|
+
getRecordConsolePlugin({
|
|
274
|
+
stringifyOptions: {
|
|
275
|
+
stringLengthLimit: 1000,
|
|
276
|
+
numOfKeysLimit: 50,
|
|
277
|
+
depthOfLimit: 2
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
] : []
|
|
258
281
|
});
|
|
259
282
|
} catch (err) {
|
|
260
283
|
this.reportError('Unexpected error when starting rrweb recording.', err);
|
|
@@ -339,6 +362,7 @@ SessionRecording.prototype.serialize = function () {
|
|
|
339
362
|
'replayStartTime': this.replayStartTime,
|
|
340
363
|
'batchStartUrl': this.batchStartUrl,
|
|
341
364
|
'replayStartUrl': this.replayStartUrl,
|
|
365
|
+
'lastEventTimestamp': this.lastEventTimestamp,
|
|
342
366
|
'idleExpires': this.idleExpires,
|
|
343
367
|
'maxExpires': this.maxExpires,
|
|
344
368
|
'tabId': tabId,
|
|
@@ -360,6 +384,7 @@ SessionRecording.deserialize = function (serializedRecording, options) {
|
|
|
360
384
|
idleExpires: serializedRecording['idleExpires'],
|
|
361
385
|
maxExpires: serializedRecording['maxExpires'],
|
|
362
386
|
replayStartTime: serializedRecording['replayStartTime'],
|
|
387
|
+
lastEventTimestamp: serializedRecording['lastEventTimestamp'],
|
|
363
388
|
seqNo: serializedRecording['seqNo'],
|
|
364
389
|
sharedLockStorage: options.sharedLockStorage,
|
|
365
390
|
}));
|
|
@@ -450,7 +475,7 @@ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
|
|
|
450
475
|
var eventsJson = JSON.stringify(data);
|
|
451
476
|
Object.assign(reqParams, this.getUserIdInfo());
|
|
452
477
|
|
|
453
|
-
if (
|
|
478
|
+
if (canUseCompressionStream(userAgent, navigator.vendor, windowOpera)) {
|
|
454
479
|
var jsonStream = new Blob([eventsJson], {type: 'application/json'}).stream();
|
|
455
480
|
var gzipStream = jsonStream.pipeThrough(new CompressionStream('gzip'));
|
|
456
481
|
new Response(gzipStream)
|
|
@@ -479,4 +504,36 @@ SessionRecording.prototype.reportError = function(msg, err) {
|
|
|
479
504
|
}
|
|
480
505
|
};
|
|
481
506
|
|
|
507
|
+
/**
|
|
508
|
+
* Calculates the duration of the recording in milliseconds, based on the start time and time of last recorded event.
|
|
509
|
+
* @returns {number} The duration of the recording in milliseconds. Returns 0 if recording hasn't started.
|
|
510
|
+
*/
|
|
511
|
+
SessionRecording.prototype.getDurationMs = function() {
|
|
512
|
+
if (this.replayStartTime === null) {
|
|
513
|
+
return 0;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// If the recording has no events, assume it is in progress and use the current time as the end time.
|
|
517
|
+
if (this.lastEventTimestamp === null) {
|
|
518
|
+
return new Date().getTime() - this.replayStartTime;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return this.lastEventTimestamp - this.replayStartTime;
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Lazily loads the minimum recording length config in milliseconds, respecting the maximum limit.
|
|
526
|
+
* @returns {number} The minimum recording length in milliseconds.
|
|
527
|
+
*/
|
|
528
|
+
SessionRecording.prototype._getRecordMinMs = function() {
|
|
529
|
+
var configValue = this.getConfig('record_min_ms');
|
|
530
|
+
|
|
531
|
+
if (configValue > MAX_VALUE_FOR_MIN_RECORDING_MS) {
|
|
532
|
+
logger.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
|
|
533
|
+
return MAX_VALUE_FOR_MIN_RECORDING_MS;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return configValue;
|
|
537
|
+
};
|
|
538
|
+
|
|
482
539
|
export { SessionRecording };
|
package/src/utils.js
CHANGED
|
@@ -1738,6 +1738,28 @@ if (typeof JSON !== 'undefined') {
|
|
|
1738
1738
|
JSONStringify = JSONStringify || _.JSONEncode;
|
|
1739
1739
|
JSONParse = JSONParse || _.JSONDecode;
|
|
1740
1740
|
|
|
1741
|
+
/**
|
|
1742
|
+
* Determines if CompressionStream API should be used.
|
|
1743
|
+
* Returns false for Safari 16.4 and 16.5 which have breaking CompressionStream bugs.
|
|
1744
|
+
* https://bugs.webkit.org/show_bug.cgi?id=254021
|
|
1745
|
+
* fixed in 16.6 https://developer.apple.com/documentation/safari-release-notes/safari-16_6-release-notes
|
|
1746
|
+
*/
|
|
1747
|
+
var canUseCompressionStream = function(userAgent, vendor, opera) {
|
|
1748
|
+
if (!window.CompressionStream) {
|
|
1749
|
+
return false;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
var browser = _.info.browser(userAgent, vendor, opera);
|
|
1753
|
+
var version = _.info.browserVersion(userAgent, vendor, opera);
|
|
1754
|
+
if (browser === 'Safari' || browser === 'Mobile Safari') {
|
|
1755
|
+
if (version >= 16.4 && version < 16.6) {
|
|
1756
|
+
return false;
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
return true;
|
|
1761
|
+
};
|
|
1762
|
+
|
|
1741
1763
|
// UNMINIFIED EXPORTS (for closure compiler)
|
|
1742
1764
|
_['info'] = _.info;
|
|
1743
1765
|
_['info']['browser'] = _.info.browser;
|
|
@@ -1755,6 +1777,7 @@ _['NPO'] = NpoPromise;
|
|
|
1755
1777
|
export {
|
|
1756
1778
|
_,
|
|
1757
1779
|
batchedThrottle,
|
|
1780
|
+
canUseCompressionStream,
|
|
1758
1781
|
cheap_guid,
|
|
1759
1782
|
console_with_prefix,
|
|
1760
1783
|
console,
|
|
@@ -1773,4 +1796,5 @@ export {
|
|
|
1773
1796
|
safewrapClass,
|
|
1774
1797
|
slice,
|
|
1775
1798
|
userAgent,
|
|
1799
|
+
windowOpera,
|
|
1776
1800
|
};
|
package/src/window.js
CHANGED
|
@@ -15,7 +15,9 @@ if (typeof(window) === 'undefined') {
|
|
|
15
15
|
screen: { width: 0, height: 0 },
|
|
16
16
|
location: loc,
|
|
17
17
|
addEventListener: function() {},
|
|
18
|
-
removeEventListener: function() {}
|
|
18
|
+
removeEventListener: function() {},
|
|
19
|
+
dispatchEvent: function() {},
|
|
20
|
+
CustomEvent: function () {}
|
|
19
21
|
};
|
|
20
22
|
} else {
|
|
21
23
|
win = window;
|