mixpanel-browser 2.66.0 → 2.67.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.
@@ -0,0 +1,7 @@
1
+ # .github/dependabot.yml
2
+ version: 2
3
+ updates:
4
+ - package-ecosystem: "npm" # Or "yarn"
5
+ directory: "/" # Tells Dependabot to scan root directory only
6
+ schedule:
7
+ interval: "daily"
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ **2.67.0** (17 Jul 2025)
2
+ - Use `get_api_host()` consistently across the SDK
3
+ - Include `device_id` in default Feature Flag context
4
+ - Track latency props in `$experiment_started` event
5
+ - Fix async behavior in `mixpanel.reset()` when a session recording is active
6
+ - Fix recorder integration test race conditions
7
+
1
8
  **2.66.0** (8 Jul 2025)
2
9
  - Add `api_host` configuration option to support different hosts/proxies for different endpoints (thanks @chrisknu)
3
10
  - Add types.d.ts from existing public repo
@@ -2,7 +2,7 @@
2
2
 
3
3
  var Config = {
4
4
  DEBUG: false,
5
- LIB_VERSION: '2.66.0'
5
+ LIB_VERSION: '2.67.0'
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
@@ -3002,8 +3002,9 @@ CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
3002
3002
  * @constructor
3003
3003
  */
3004
3004
  var FeatureFlagManager = function(initOptions) {
3005
+ this.getFullApiRoute = initOptions.getFullApiRoute;
3005
3006
  this.getMpConfig = initOptions.getConfigFunc;
3006
- this.getDistinctId = initOptions.getDistinctIdFunc;
3007
+ this.getMpProperty = initOptions.getPropertyFunc;
3007
3008
  this.track = initOptions.trackingFunc;
3008
3009
  };
3009
3010
 
@@ -3052,12 +3053,14 @@ FeatureFlagManager.prototype.fetchFlags = function() {
3052
3053
  return;
3053
3054
  }
3054
3055
 
3055
- var distinctId = this.getDistinctId();
3056
+ var distinctId = this.getMpProperty('distinct_id');
3057
+ var deviceId = this.getMpProperty('$device_id');
3056
3058
  logger$3.log('Fetching flags for distinct ID: ' + distinctId);
3057
3059
  var reqParams = {
3058
- 'context': _.extend({'distinct_id': distinctId}, this.getConfig(CONFIG_CONTEXT))
3060
+ 'context': _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT))
3059
3061
  };
3060
- this.fetchPromise = win['fetch'](this.getMpConfig('api_host') + '/' + this.getMpConfig('api_routes')['flags'], {
3062
+ this._fetchInProgressStartTime = Date.now();
3063
+ this.fetchPromise = win['fetch'](this.getFullApiRoute(), {
3061
3064
  'method': 'POST',
3062
3065
  'headers': {
3063
3066
  'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
@@ -3065,6 +3068,7 @@ FeatureFlagManager.prototype.fetchFlags = function() {
3065
3068
  },
3066
3069
  'body': JSON.stringify(reqParams)
3067
3070
  }).then(function(response) {
3071
+ this.markFetchComplete();
3068
3072
  return response.json().then(function(responseBody) {
3069
3073
  var responseFlags = responseBody['flags'];
3070
3074
  if (!responseFlags) {
@@ -3079,9 +3083,24 @@ FeatureFlagManager.prototype.fetchFlags = function() {
3079
3083
  });
3080
3084
  this.flags = flags;
3081
3085
  }.bind(this)).catch(function(error) {
3086
+ this.markFetchComplete();
3082
3087
  logger$3.error(error);
3083
- });
3084
- }.bind(this)).catch(function() {});
3088
+ }.bind(this));
3089
+ }.bind(this)).catch(function(error) {
3090
+ this.markFetchComplete();
3091
+ logger$3.error(error);
3092
+ }.bind(this));
3093
+ };
3094
+
3095
+ FeatureFlagManager.prototype.markFetchComplete = function() {
3096
+ if (!this._fetchInProgressStartTime) {
3097
+ logger$3.error('Fetch in progress started time not set, cannot mark fetch complete');
3098
+ return;
3099
+ }
3100
+ this._fetchStartTime = this._fetchInProgressStartTime;
3101
+ this._fetchCompleteTime = Date.now();
3102
+ this._fetchLatency = this._fetchCompleteTime - this._fetchStartTime;
3103
+ this._fetchInProgressStartTime = null;
3085
3104
  };
3086
3105
 
3087
3106
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
@@ -3160,7 +3179,10 @@ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature)
3160
3179
  this.track('$experiment_started', {
3161
3180
  'Experiment name': featureName,
3162
3181
  'Variant name': feature['key'],
3163
- '$experiment_type': 'feature_flag'
3182
+ '$experiment_type': 'feature_flag',
3183
+ 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
3184
+ 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
3185
+ 'Variant fetch latency (ms)': this._fetchLatency
3164
3186
  });
3165
3187
  };
3166
3188
 
@@ -6171,8 +6193,11 @@ MixpanelLib.prototype._init = function(token, config, name) {
6171
6193
  }
6172
6194
 
6173
6195
  this.flags = new FeatureFlagManager({
6196
+ getFullApiRoute: _.bind(function() {
6197
+ return this.get_api_host('flags') + '/' + this.get_config('api_routes')['flags'];
6198
+ }, this),
6174
6199
  getConfigFunc: _.bind(this.get_config, this),
6175
- getDistinctIdFunc: _.bind(this.get_distinct_id, this),
6200
+ getPropertyFunc: _.bind(this.get_property, this),
6176
6201
  trackingFunc: _.bind(this.track, this)
6177
6202
  });
6178
6203
  this.flags.init();
@@ -6658,11 +6683,10 @@ MixpanelLib.prototype.are_batchers_initialized = function() {
6658
6683
 
6659
6684
  MixpanelLib.prototype.get_batcher_configs = function() {
6660
6685
  var queue_prefix = '__mpq_' + this.get_config('token');
6661
- var api_routes = this.get_config('api_routes');
6662
6686
  this._batcher_configs = this._batcher_configs || {
6663
- events: {type: 'events', endpoint: '/' + api_routes['track'], queue_key: queue_prefix + '_ev'},
6664
- people: {type: 'people', endpoint: '/' + api_routes['engage'], queue_key: queue_prefix + '_pp'},
6665
- groups: {type: 'groups', endpoint: '/' + api_routes['groups'], queue_key: queue_prefix + '_gr'}
6687
+ events: {type: 'events', api_name: 'track', queue_key: queue_prefix + '_ev'},
6688
+ people: {type: 'people', api_name: 'engage', queue_key: queue_prefix + '_pp'},
6689
+ groups: {type: 'groups', api_name: 'groups', queue_key: queue_prefix + '_gr'}
6666
6690
  };
6667
6691
  return this._batcher_configs;
6668
6692
  };
@@ -6676,8 +6700,9 @@ MixpanelLib.prototype.init_batchers = function() {
6676
6700
  libConfig: this['config'],
6677
6701
  errorReporter: this.get_config('error_reporter'),
6678
6702
  sendRequestFunc: _.bind(function(data, options, cb) {
6703
+ var api_routes = this.get_config('api_routes');
6679
6704
  this._send_request(
6680
- this.get_config('api_host') + attrs.endpoint,
6705
+ this.get_api_host(attrs.api_name) + '/' + api_routes[attrs.api_name],
6681
6706
  this._encode_data_for_request(data),
6682
6707
  options,
6683
6708
  this._prepare_callback(cb, data)
@@ -7405,31 +7430,15 @@ MixpanelLib.prototype.identify = function(
7405
7430
  * Useful for clearing data when a user logs out.
7406
7431
  */
7407
7432
  MixpanelLib.prototype.reset = function() {
7408
- var self = this;
7409
-
7410
- var reset = function () {
7411
- self['persistence'].clear();
7412
- self._flags.identify_called = false;
7413
- var uuid = _.UUID();
7414
- self.register_once({
7415
- 'distinct_id': DEVICE_ID_PREFIX + uuid,
7416
- '$device_id': uuid
7417
- }, '');
7418
- };
7419
-
7420
- if (self._recorder) {
7421
- self.stop_session_recording()
7422
- .then(function () {
7423
- reset();
7424
- self._check_and_start_session_recording();
7425
- })
7426
- .catch(_.bind(function (err) {
7427
- reset();
7428
- this.report_error('Error restarting recording session', err);
7429
- }, this));
7430
- } else {
7431
- reset();
7432
- }
7433
+ this.stop_session_recording();
7434
+ this['persistence'].clear();
7435
+ this._flags.identify_called = false;
7436
+ var uuid = _.UUID();
7437
+ this.register_once({
7438
+ 'distinct_id': DEVICE_ID_PREFIX + uuid,
7439
+ '$device_id': uuid
7440
+ }, '');
7441
+ this._check_and_start_session_recording();
7433
7442
  };
7434
7443
 
7435
7444
  /**
@@ -13944,7 +13944,7 @@
13944
13944
  }
13945
13945
 
13946
13946
  var Config = {
13947
- LIB_VERSION: '2.66.0'
13947
+ LIB_VERSION: '2.67.0'
13948
13948
  };
13949
13949
 
13950
13950
  /* eslint camelcase: "off", eqeqeq: "off" */
@@ -16878,6 +16878,13 @@
16878
16878
  * @property {string} replayStartUrl
16879
16879
  */
16880
16880
 
16881
+ /**
16882
+ * @typedef {Object} UserIdInfo
16883
+ * @property {string} distinct_id
16884
+ * @property {string} user_id
16885
+ * @property {string} device_id
16886
+ */
16887
+
16881
16888
 
16882
16889
  /**
16883
16890
  * This class encapsulates a single session recording and its lifecycle.
@@ -16933,6 +16940,30 @@
16933
16940
  });
16934
16941
  };
16935
16942
 
16943
+ /**
16944
+ * @returns {UserIdInfo}
16945
+ */
16946
+ SessionRecording.prototype.getUserIdInfo = function () {
16947
+ if (this.finalFlushUserIdInfo) {
16948
+ return this.finalFlushUserIdInfo;
16949
+ }
16950
+
16951
+ var userIdInfo = {
16952
+ 'distinct_id': String(this._mixpanel.get_distinct_id()),
16953
+ };
16954
+
16955
+ // send ID management props if they exist
16956
+ var deviceId = this._mixpanel.get_property('$device_id');
16957
+ if (deviceId) {
16958
+ userIdInfo['$device_id'] = deviceId;
16959
+ }
16960
+ var userId = this._mixpanel.get_property('$user_id');
16961
+ if (userId) {
16962
+ userIdInfo['$user_id'] = userId;
16963
+ }
16964
+ return userIdInfo;
16965
+ };
16966
+
16936
16967
  SessionRecording.prototype.unloadPersistedData = function () {
16937
16968
  this.batcher.stop();
16938
16969
  return this.batcher.flush()
@@ -17057,6 +17088,9 @@
17057
17088
  };
17058
17089
 
17059
17090
  SessionRecording.prototype.stopRecording = function (skipFlush) {
17091
+ // store the user ID info in case this is getting called in mixpanel.reset()
17092
+ this.finalFlushUserIdInfo = this.getUserIdInfo();
17093
+
17060
17094
  if (!this.isRrwebStopped()) {
17061
17095
  try {
17062
17096
  this._stopRecording();
@@ -17222,7 +17256,6 @@
17222
17256
  '$current_url': this.batchStartUrl,
17223
17257
  '$lib_version': Config.LIB_VERSION,
17224
17258
  'batch_start_time': batchStartTime / 1000,
17225
- 'distinct_id': String(this._mixpanel.get_distinct_id()),
17226
17259
  'mp_lib': 'web',
17227
17260
  'replay_id': replayId,
17228
17261
  'replay_length_ms': replayLengthMs,
@@ -17231,16 +17264,7 @@
17231
17264
  'seq': this.seqNo
17232
17265
  };
17233
17266
  var eventsJson = JSON.stringify(data);
17234
-
17235
- // send ID management props if they exist
17236
- var deviceId = this._mixpanel.get_property('$device_id');
17237
- if (deviceId) {
17238
- reqParams['$device_id'] = deviceId;
17239
- }
17240
- var userId = this._mixpanel.get_property('$user_id');
17241
- if (userId) {
17242
- reqParams['$user_id'] = userId;
17243
- }
17267
+ Object.assign(reqParams, this.getUserIdInfo());
17244
17268
 
17245
17269
  if (CompressionStream) {
17246
17270
  var jsonStream = new Blob([eventsJson], {type: 'application/json'}).stream();