mixpanel-browser 2.61.0 → 2.61.2

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 CHANGED
@@ -1,3 +1,14 @@
1
+ **2.61.2** (14 Mar 2025)
2
+ - Revert 10ms throttle on enqueueing events to improve tracking reliability on page unload
3
+
4
+ **2.61.1** (11 Mar 2025)
5
+ - Session recording stops if initial DOM snapshot fails
6
+ - Errors triggered by rrweb's record function are now caught
7
+ - Fix for issue causing opt-out check error messages in `debug` mode
8
+
9
+ **2.61.0** (6 Mar 2025)
10
+ - Session recordings now continue across page loads within the same tab, using IndexedDB for persistence
11
+
1
12
  **2.60.0** (31 Jan 2025)
2
13
  - Expanded Autocapture configs
3
14
  - Prevent duplicate values in persistence when using people.union (thanks @chrisdeely)
@@ -2,7 +2,7 @@
2
2
 
3
3
  var Config = {
4
4
  DEBUG: false,
5
- LIB_VERSION: '2.61.0'
5
+ LIB_VERSION: '2.61.2'
6
6
  };
7
7
 
8
8
  // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
@@ -5624,7 +5624,7 @@ IDBStorageWrapper.prototype.makeTransaction = function (mode, storeCb) {
5624
5624
  return this.dbPromise
5625
5625
  .then(doTransaction)
5626
5626
  .catch(function (err) {
5627
- if (err['name'] === 'InvalidStateError') {
5627
+ if (err && err['name'] === 'InvalidStateError') {
5628
5628
  // try reopening the DB if the connection is closed
5629
5629
  this.dbPromise = this._openDb();
5630
5630
  return this.dbPromise.then(doTransaction);
@@ -6506,7 +6506,6 @@ MixpanelLib.prototype.init_batchers = function() {
6506
6506
  }, this),
6507
6507
  stopAllBatchingFunc: _.bind(this.stop_batch_senders, this),
6508
6508
  usePersistence: true,
6509
- enqueueThrottleMs: 10,
6510
6509
  }
6511
6510
  );
6512
6511
  }, this);
@@ -4894,7 +4894,7 @@
4894
4894
 
4895
4895
  var Config = {
4896
4896
  DEBUG: false,
4897
- LIB_VERSION: '2.61.0'
4897
+ LIB_VERSION: '2.61.2'
4898
4898
  };
4899
4899
 
4900
4900
  /* eslint camelcase: "off", eqeqeq: "off" */
@@ -6705,7 +6705,7 @@
6705
6705
  return this.dbPromise
6706
6706
  .then(doTransaction)
6707
6707
  .catch(function (err) {
6708
- if (err['name'] === 'InvalidStateError') {
6708
+ if (err && err['name'] === 'InvalidStateError') {
6709
6709
  // try reopening the DB if the connection is closed
6710
6710
  this.dbPromise = this._openDb();
6711
6711
  return this.dbPromise.then(doTransaction);
@@ -7997,34 +7997,37 @@
7997
7997
  blockSelector = undefined;
7998
7998
  }
7999
7999
 
8000
- this._stopRecording = this._rrwebRecord({
8001
- 'emit': addOptOutCheckMixpanelLib(function (ev) {
8002
- if (isUserEvent(ev)) {
8003
- if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
8004
- // start flushing again after user activity
8005
- this.batcher.start();
8000
+ try {
8001
+ this._stopRecording = this._rrwebRecord({
8002
+ 'emit': function (ev) {
8003
+ if (isUserEvent(ev)) {
8004
+ if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
8005
+ // start flushing again after user activity
8006
+ this.batcher.start();
8007
+ }
8008
+ resetIdleTimeout();
8006
8009
  }
8007
- resetIdleTimeout();
8010
+ // promise only used to await during tests
8011
+ this.__enqueuePromise = this.batcher.enqueue(ev);
8012
+ }.bind(this),
8013
+ 'blockClass': this.getConfig('record_block_class'),
8014
+ 'blockSelector': blockSelector,
8015
+ 'collectFonts': this.getConfig('record_collect_fonts'),
8016
+ 'dataURLOptions': { // canvas image options (https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL)
8017
+ 'type': 'image/webp',
8018
+ 'quality': 0.6
8019
+ },
8020
+ 'maskAllInputs': true,
8021
+ 'maskTextClass': this.getConfig('record_mask_text_class'),
8022
+ 'maskTextSelector': this.getConfig('record_mask_text_selector'),
8023
+ 'recordCanvas': this.getConfig('record_canvas'),
8024
+ 'sampling': {
8025
+ 'canvas': 15
8008
8026
  }
8009
-
8010
- // promise only used to await during tests
8011
- this.__enqueuePromise = this.batcher.enqueue(ev);
8012
- }.bind(this)),
8013
- 'blockClass': this.getConfig('record_block_class'),
8014
- 'blockSelector': blockSelector,
8015
- 'collectFonts': this.getConfig('record_collect_fonts'),
8016
- 'dataURLOptions': { // canvas image options (https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL)
8017
- 'type': 'image/webp',
8018
- 'quality': 0.6
8019
- },
8020
- 'maskAllInputs': true,
8021
- 'maskTextClass': this.getConfig('record_mask_text_class'),
8022
- 'maskTextSelector': this.getConfig('record_mask_text_selector'),
8023
- 'recordCanvas': this.getConfig('record_canvas'),
8024
- 'sampling': {
8025
- 'canvas': 15
8026
- }
8027
- });
8027
+ });
8028
+ } catch (err) {
8029
+ this.reportError('Unexpected error when starting rrweb recording.', err);
8030
+ }
8028
8031
 
8029
8032
  if (typeof this._stopRecording !== 'function') {
8030
8033
  this.reportError('rrweb failed to start, skipping this recording.');
@@ -8068,12 +8071,21 @@
8068
8071
  return this._stopRecording === null;
8069
8072
  };
8070
8073
 
8074
+
8071
8075
  /**
8072
8076
  * Flushes the current batch of events to the server, but passes an opt-out callback to make sure
8073
8077
  * we stop recording and dump any queued events if the user has opted out.
8074
8078
  */
8075
8079
  SessionRecording.prototype.flushEventsWithOptOut = function (data, options, cb) {
8076
- this._flushEvents(data, options, cb, this._onOptOut.bind(this));
8080
+ var onOptOut = function (code) {
8081
+ // addOptOutCheckMixpanelLib invokes this function with code=0 when the user has opted out
8082
+ if (code === 0) {
8083
+ this.stopRecording();
8084
+ cb({error: 'Tracking has been opted out, stopping recording.'});
8085
+ }
8086
+ }.bind(this);
8087
+
8088
+ this._flushEvents(data, options, cb, onOptOut);
8077
8089
  };
8078
8090
 
8079
8091
  /**
@@ -8123,13 +8135,6 @@
8123
8135
  return recording;
8124
8136
  };
8125
8137
 
8126
- SessionRecording.prototype._onOptOut = function (code) {
8127
- // addOptOutCheckMixpanelLib invokes this function with code=0 when the user has opted out
8128
- if (code === 0) {
8129
- this.stopRecording();
8130
- }
8131
- };
8132
-
8133
8138
  SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
8134
8139
  var onSuccess = function (response, responseBody) {
8135
8140
  // Update batch specific props only if the request was successful to guarantee ordering.
@@ -8172,17 +8177,32 @@
8172
8177
 
8173
8178
  if (numEvents > 0) {
8174
8179
  var replayId = this.replayId;
8180
+
8175
8181
  // each rrweb event has a timestamp - leverage those to get time properties
8176
- var batchStartTime = data[0].timestamp;
8177
- if (this.seqNo === 0 || !this.replayStartTime) {
8178
- // extra safety net so that we don't send a null replay start time
8179
- if (this.seqNo !== 0) {
8180
- this.reportError('Replay start time not set but seqNo is not 0. Using current batch start time as a fallback.');
8182
+ var batchStartTime = Infinity;
8183
+ var batchEndTime = -Infinity;
8184
+ var hasFullSnapshot = false;
8185
+ for (var i = 0; i < numEvents; i++) {
8186
+ batchStartTime = Math.min(batchStartTime, data[i].timestamp);
8187
+ batchEndTime = Math.max(batchEndTime, data[i].timestamp);
8188
+ if (data[i].type === EventType.FullSnapshot) {
8189
+ hasFullSnapshot = true;
8181
8190
  }
8191
+ }
8182
8192
 
8193
+ if (this.seqNo === 0) {
8194
+ if (!hasFullSnapshot) {
8195
+ callback({error: 'First batch does not contain a full snapshot. Aborting recording.'});
8196
+ this.stopRecording(true);
8197
+ return;
8198
+ }
8199
+ this.replayStartTime = batchStartTime;
8200
+ } else if (!this.replayStartTime) {
8201
+ this.reportError('Replay start time not set but seqNo is not 0. Using current batch start time as a fallback.');
8183
8202
  this.replayStartTime = batchStartTime;
8184
8203
  }
8185
- var replayLengthMs = data[numEvents - 1].timestamp - this.replayStartTime;
8204
+
8205
+ var replayLengthMs = batchEndTime - this.replayStartTime;
8186
8206
 
8187
8207
  var reqParams = {
8188
8208
  '$current_url': this.batchStartUrl,