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.
Files changed (40) hide show
  1. package/.github/workflows/tests.yml +1 -0
  2. package/CHANGELOG.md +12 -0
  3. package/dist/mixpanel-core.cjs.d.ts +84 -13
  4. package/dist/mixpanel-core.cjs.js +180 -28
  5. package/dist/mixpanel-recorder.js +684 -114
  6. package/dist/mixpanel-recorder.min.js +1 -1
  7. package/dist/mixpanel-recorder.min.js.map +1 -1
  8. package/dist/mixpanel-with-async-recorder.cjs.d.ts +84 -13
  9. package/dist/mixpanel-with-async-recorder.cjs.js +180 -28
  10. package/dist/mixpanel-with-recorder.d.ts +84 -13
  11. package/dist/mixpanel-with-recorder.js +860 -140
  12. package/dist/mixpanel-with-recorder.min.d.ts +84 -13
  13. package/dist/mixpanel-with-recorder.min.js +1 -1
  14. package/dist/mixpanel.amd.d.ts +84 -13
  15. package/dist/mixpanel.amd.js +860 -140
  16. package/dist/mixpanel.cjs.d.ts +84 -13
  17. package/dist/mixpanel.cjs.js +860 -140
  18. package/dist/mixpanel.globals.js +180 -28
  19. package/dist/mixpanel.min.js +172 -170
  20. package/dist/mixpanel.module.d.ts +84 -13
  21. package/dist/mixpanel.module.js +860 -140
  22. package/dist/mixpanel.umd.d.ts +84 -13
  23. package/dist/mixpanel.umd.js +860 -140
  24. package/dist/rrweb-bundled.js +12760 -0
  25. package/dist/rrweb-compiled.js +2496 -7176
  26. package/package.json +3 -2
  27. package/rollup.config.mjs +15 -4
  28. package/src/autocapture/index.js +1 -1
  29. package/src/autocapture/rageclick.js +20 -1
  30. package/src/autocapture/shadow-dom-observer.js +3 -15
  31. package/src/autocapture/utils.js +30 -0
  32. package/src/config.js +1 -1
  33. package/src/index.d.ts +84 -13
  34. package/src/mixpanel-core.js +127 -10
  35. package/src/recorder/recorder.js +1 -1
  36. package/src/recorder/rrweb-entrypoint.js +6 -0
  37. package/src/recorder/session-recording.js +69 -12
  38. package/src/utils.js +24 -0
  39. package/src/window.js +3 -1
  40. package/.claude/settings.local.json +0 -9
@@ -1,6 +1,6 @@
1
1
  import { window } from '../window';
2
- import { IncrementalSource, EventType } from '@mixpanel/rrweb';
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
- return this.batcher.flush()
154
- .then(function () {
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
- }.bind(this));
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.getConfig('record_min_ms');
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 (CompressionStream) {
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;
@@ -1,9 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(mkdir:*)"
5
- ],
6
- "deny": [],
7
- "ask": []
8
- }
9
- }