mixpanel-browser 2.65.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.
@@ -13942,7 +13942,7 @@ if (typeof Promise !== 'undefined' && Promise.toString().indexOf('[native code]'
13942
13942
 
13943
13943
  var Config = {
13944
13944
  DEBUG: false,
13945
- LIB_VERSION: '2.65.0'
13945
+ LIB_VERSION: '2.67.0'
13946
13946
  };
13947
13947
 
13948
13948
  /* eslint camelcase: "off", eqeqeq: "off" */
@@ -17060,6 +17060,13 @@ function isUserEvent(ev) {
17060
17060
  * @property {string} replayStartUrl
17061
17061
  */
17062
17062
 
17063
+ /**
17064
+ * @typedef {Object} UserIdInfo
17065
+ * @property {string} distinct_id
17066
+ * @property {string} user_id
17067
+ * @property {string} device_id
17068
+ */
17069
+
17063
17070
 
17064
17071
  /**
17065
17072
  * This class encapsulates a single session recording and its lifecycle.
@@ -17115,6 +17122,30 @@ var SessionRecording = function(options) {
17115
17122
  });
17116
17123
  };
17117
17124
 
17125
+ /**
17126
+ * @returns {UserIdInfo}
17127
+ */
17128
+ SessionRecording.prototype.getUserIdInfo = function () {
17129
+ if (this.finalFlushUserIdInfo) {
17130
+ return this.finalFlushUserIdInfo;
17131
+ }
17132
+
17133
+ var userIdInfo = {
17134
+ 'distinct_id': String(this._mixpanel.get_distinct_id()),
17135
+ };
17136
+
17137
+ // send ID management props if they exist
17138
+ var deviceId = this._mixpanel.get_property('$device_id');
17139
+ if (deviceId) {
17140
+ userIdInfo['$device_id'] = deviceId;
17141
+ }
17142
+ var userId = this._mixpanel.get_property('$user_id');
17143
+ if (userId) {
17144
+ userIdInfo['$user_id'] = userId;
17145
+ }
17146
+ return userIdInfo;
17147
+ };
17148
+
17118
17149
  SessionRecording.prototype.unloadPersistedData = function () {
17119
17150
  this.batcher.stop();
17120
17151
  return this.batcher.flush()
@@ -17239,6 +17270,9 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
17239
17270
  };
17240
17271
 
17241
17272
  SessionRecording.prototype.stopRecording = function (skipFlush) {
17273
+ // store the user ID info in case this is getting called in mixpanel.reset()
17274
+ this.finalFlushUserIdInfo = this.getUserIdInfo();
17275
+
17242
17276
  if (!this.isRrwebStopped()) {
17243
17277
  try {
17244
17278
  this._stopRecording();
@@ -17349,8 +17383,8 @@ SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, r
17349
17383
  retryAfter: response.headers.get('Retry-After')
17350
17384
  });
17351
17385
  }.bind(this);
17352
-
17353
- win['fetch'](this.getConfig('api_host') + '/' + this.getConfig('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
17386
+ var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
17387
+ win['fetch'](apiHost + '/' + this.getConfig('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
17354
17388
  'method': 'POST',
17355
17389
  'headers': {
17356
17390
  'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
@@ -17404,7 +17438,6 @@ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
17404
17438
  '$current_url': this.batchStartUrl,
17405
17439
  '$lib_version': Config.LIB_VERSION,
17406
17440
  'batch_start_time': batchStartTime / 1000,
17407
- 'distinct_id': String(this._mixpanel.get_distinct_id()),
17408
17441
  'mp_lib': 'web',
17409
17442
  'replay_id': replayId,
17410
17443
  'replay_length_ms': replayLengthMs,
@@ -17413,16 +17446,7 @@ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
17413
17446
  'seq': this.seqNo
17414
17447
  };
17415
17448
  var eventsJson = JSON.stringify(data);
17416
-
17417
- // send ID management props if they exist
17418
- var deviceId = this._mixpanel.get_property('$device_id');
17419
- if (deviceId) {
17420
- reqParams['$device_id'] = deviceId;
17421
- }
17422
- var userId = this._mixpanel.get_property('$user_id');
17423
- if (userId) {
17424
- reqParams['$user_id'] = userId;
17425
- }
17449
+ Object.assign(reqParams, this.getUserIdInfo());
17426
17450
 
17427
17451
  if (CompressionStream) {
17428
17452
  var jsonStream = new Blob([eventsJson], {type: 'application/json'}).stream();
@@ -17567,6 +17591,7 @@ var MixpanelRecorder = function(mixpanelInstance, rrwebRecord, sharedLockStorage
17567
17591
  this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
17568
17592
 
17569
17593
  this.activeRecording = null;
17594
+ this.stopRecordingInProgress = false;
17570
17595
  };
17571
17596
 
17572
17597
  MixpanelRecorder.prototype.startRecording = function(options) {
@@ -17615,19 +17640,26 @@ MixpanelRecorder.prototype.startRecording = function(options) {
17615
17640
  };
17616
17641
 
17617
17642
  MixpanelRecorder.prototype.stopRecording = function() {
17618
- var stopPromise = this._stopCurrentRecording(false);
17619
- this.recordingRegistry.clearActiveRecording();
17620
- this.activeRecording = null;
17621
- return stopPromise;
17643
+ // Prevents activeSerializedRecording from being reused when stopping the recording.
17644
+ this.stopRecordingInProgress = true;
17645
+ return this._stopCurrentRecording(false, true).then(function() {
17646
+ return this.recordingRegistry.clearActiveRecording();
17647
+ }.bind(this)).then(function() {
17648
+ this.stopRecordingInProgress = false;
17649
+ }.bind(this));
17622
17650
  };
17623
17651
 
17624
17652
  MixpanelRecorder.prototype.pauseRecording = function() {
17625
17653
  return this._stopCurrentRecording(false);
17626
17654
  };
17627
17655
 
17628
- MixpanelRecorder.prototype._stopCurrentRecording = function(skipFlush) {
17656
+ MixpanelRecorder.prototype._stopCurrentRecording = function(skipFlush, disableActiveRecording) {
17629
17657
  if (this.activeRecording) {
17630
- return this.activeRecording.stopRecording(skipFlush);
17658
+ var stopRecordingPromise = this.activeRecording.stopRecording(skipFlush);
17659
+ if (disableActiveRecording) {
17660
+ this.activeRecording = null;
17661
+ }
17662
+ return stopRecordingPromise;
17631
17663
  }
17632
17664
  return PromisePolyfill.resolve();
17633
17665
  };
@@ -17640,7 +17672,7 @@ MixpanelRecorder.prototype.resumeRecording = function (startNewIfInactive) {
17640
17672
 
17641
17673
  return this.recordingRegistry.getActiveRecording()
17642
17674
  .then(function (activeSerializedRecording) {
17643
- if (activeSerializedRecording) {
17675
+ if (activeSerializedRecording && !this.stopRecordingInProgress) {
17644
17676
  return this.startRecording({activeSerializedRecording: activeSerializedRecording});
17645
17677
  } else if (startNewIfInactive) {
17646
17678
  return this.startRecording({shouldStopBatcher: false});
@@ -18543,8 +18575,9 @@ CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
18543
18575
  * @constructor
18544
18576
  */
18545
18577
  var FeatureFlagManager = function(initOptions) {
18578
+ this.getFullApiRoute = initOptions.getFullApiRoute;
18546
18579
  this.getMpConfig = initOptions.getConfigFunc;
18547
- this.getDistinctId = initOptions.getDistinctIdFunc;
18580
+ this.getMpProperty = initOptions.getPropertyFunc;
18548
18581
  this.track = initOptions.trackingFunc;
18549
18582
  };
18550
18583
 
@@ -18593,12 +18626,14 @@ FeatureFlagManager.prototype.fetchFlags = function() {
18593
18626
  return;
18594
18627
  }
18595
18628
 
18596
- var distinctId = this.getDistinctId();
18629
+ var distinctId = this.getMpProperty('distinct_id');
18630
+ var deviceId = this.getMpProperty('$device_id');
18597
18631
  logger.log('Fetching flags for distinct ID: ' + distinctId);
18598
18632
  var reqParams = {
18599
- 'context': _.extend({'distinct_id': distinctId}, this.getConfig(CONFIG_CONTEXT))
18633
+ 'context': _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT))
18600
18634
  };
18601
- this.fetchPromise = win['fetch'](this.getMpConfig('api_host') + '/' + this.getMpConfig('api_routes')['flags'], {
18635
+ this._fetchInProgressStartTime = Date.now();
18636
+ this.fetchPromise = win['fetch'](this.getFullApiRoute(), {
18602
18637
  'method': 'POST',
18603
18638
  'headers': {
18604
18639
  'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
@@ -18606,6 +18641,7 @@ FeatureFlagManager.prototype.fetchFlags = function() {
18606
18641
  },
18607
18642
  'body': JSON.stringify(reqParams)
18608
18643
  }).then(function(response) {
18644
+ this.markFetchComplete();
18609
18645
  return response.json().then(function(responseBody) {
18610
18646
  var responseFlags = responseBody['flags'];
18611
18647
  if (!responseFlags) {
@@ -18620,9 +18656,24 @@ FeatureFlagManager.prototype.fetchFlags = function() {
18620
18656
  });
18621
18657
  this.flags = flags;
18622
18658
  }.bind(this)).catch(function(error) {
18659
+ this.markFetchComplete();
18623
18660
  logger.error(error);
18624
- });
18625
- }.bind(this)).catch(function() {});
18661
+ }.bind(this));
18662
+ }.bind(this)).catch(function(error) {
18663
+ this.markFetchComplete();
18664
+ logger.error(error);
18665
+ }.bind(this));
18666
+ };
18667
+
18668
+ FeatureFlagManager.prototype.markFetchComplete = function() {
18669
+ if (!this._fetchInProgressStartTime) {
18670
+ logger.error('Fetch in progress started time not set, cannot mark fetch complete');
18671
+ return;
18672
+ }
18673
+ this._fetchStartTime = this._fetchInProgressStartTime;
18674
+ this._fetchCompleteTime = Date.now();
18675
+ this._fetchLatency = this._fetchCompleteTime - this._fetchStartTime;
18676
+ this._fetchInProgressStartTime = null;
18626
18677
  };
18627
18678
 
18628
18679
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
@@ -18701,7 +18752,10 @@ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature)
18701
18752
  this.track('$experiment_started', {
18702
18753
  'Experiment name': featureName,
18703
18754
  'Variant name': feature['key'],
18704
- '$experiment_type': 'feature_flag'
18755
+ '$experiment_type': 'feature_flag',
18756
+ 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
18757
+ 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
18758
+ 'Variant fetch latency (ms)': this._fetchLatency
18705
18759
  });
18706
18760
  };
18707
18761
 
@@ -19142,7 +19196,7 @@ MixpanelGroup.prototype._send_request = function(data, callback) {
19142
19196
  return this._mixpanel._track_or_batch({
19143
19197
  type: 'groups',
19144
19198
  data: date_encoded_data,
19145
- endpoint: this._get_config('api_host') + '/' + this._get_config('api_routes')['groups'],
19199
+ endpoint: this._mixpanel.get_api_host('groups') + '/' + this._get_config('api_routes')['groups'],
19146
19200
  batcher: this._mixpanel.request_batchers.groups
19147
19201
  }, callback);
19148
19202
  };
@@ -19494,7 +19548,7 @@ MixpanelPeople.prototype._send_request = function(data, callback) {
19494
19548
  return this._mixpanel._track_or_batch({
19495
19549
  type: 'people',
19496
19550
  data: date_encoded_data,
19497
- endpoint: this._get_config('api_host') + '/' + this._get_config('api_routes')['engage'],
19551
+ endpoint: this._mixpanel.get_api_host('people') + '/' + this._get_config('api_routes')['engage'],
19498
19552
  batcher: this._mixpanel.request_batchers.people
19499
19553
  }, callback);
19500
19554
  };
@@ -20131,6 +20185,7 @@ var DEFAULT_API_ROUTES = {
20131
20185
  */
20132
20186
  var DEFAULT_CONFIG = {
20133
20187
  'api_host': 'https://api-js.mixpanel.com',
20188
+ 'api_hosts': {},
20134
20189
  'api_routes': DEFAULT_API_ROUTES,
20135
20190
  'api_extra_query_params': {},
20136
20191
  'api_method': 'POST',
@@ -20400,8 +20455,11 @@ MixpanelLib.prototype._init = function(token, config, name) {
20400
20455
  }
20401
20456
 
20402
20457
  this.flags = new FeatureFlagManager({
20458
+ getFullApiRoute: _.bind(function() {
20459
+ return this.get_api_host('flags') + '/' + this.get_config('api_routes')['flags'];
20460
+ }, this),
20403
20461
  getConfigFunc: _.bind(this.get_config, this),
20404
- getDistinctIdFunc: _.bind(this.get_distinct_id, this),
20462
+ getPropertyFunc: _.bind(this.get_property, this),
20405
20463
  trackingFunc: _.bind(this.track, this)
20406
20464
  });
20407
20465
  this.flags.init();
@@ -20516,20 +20574,23 @@ MixpanelLib.prototype.start_session_recording = function () {
20516
20574
 
20517
20575
  MixpanelLib.prototype.stop_session_recording = function () {
20518
20576
  if (this._recorder) {
20519
- this._recorder['stopRecording']();
20577
+ return this._recorder['stopRecording']();
20520
20578
  }
20579
+ return Promise.resolve();
20521
20580
  };
20522
20581
 
20523
20582
  MixpanelLib.prototype.pause_session_recording = function () {
20524
20583
  if (this._recorder) {
20525
- this._recorder['pauseRecording']();
20584
+ return this._recorder['pauseRecording']();
20526
20585
  }
20586
+ return Promise.resolve();
20527
20587
  };
20528
20588
 
20529
20589
  MixpanelLib.prototype.resume_session_recording = function () {
20530
20590
  if (this._recorder) {
20531
- this._recorder['resumeRecording']();
20591
+ return this._recorder['resumeRecording']();
20532
20592
  }
20593
+ return Promise.resolve();
20533
20594
  };
20534
20595
 
20535
20596
  MixpanelLib.prototype.is_recording_heatmap_data = function () {
@@ -20884,11 +20945,10 @@ MixpanelLib.prototype.are_batchers_initialized = function() {
20884
20945
 
20885
20946
  MixpanelLib.prototype.get_batcher_configs = function() {
20886
20947
  var queue_prefix = '__mpq_' + this.get_config('token');
20887
- var api_routes = this.get_config('api_routes');
20888
20948
  this._batcher_configs = this._batcher_configs || {
20889
- events: {type: 'events', endpoint: '/' + api_routes['track'], queue_key: queue_prefix + '_ev'},
20890
- people: {type: 'people', endpoint: '/' + api_routes['engage'], queue_key: queue_prefix + '_pp'},
20891
- groups: {type: 'groups', endpoint: '/' + api_routes['groups'], queue_key: queue_prefix + '_gr'}
20949
+ events: {type: 'events', api_name: 'track', queue_key: queue_prefix + '_ev'},
20950
+ people: {type: 'people', api_name: 'engage', queue_key: queue_prefix + '_pp'},
20951
+ groups: {type: 'groups', api_name: 'groups', queue_key: queue_prefix + '_gr'}
20892
20952
  };
20893
20953
  return this._batcher_configs;
20894
20954
  };
@@ -20902,8 +20962,9 @@ MixpanelLib.prototype.init_batchers = function() {
20902
20962
  libConfig: this['config'],
20903
20963
  errorReporter: this.get_config('error_reporter'),
20904
20964
  sendRequestFunc: _.bind(function(data, options, cb) {
20965
+ var api_routes = this.get_config('api_routes');
20905
20966
  this._send_request(
20906
- this.get_config('api_host') + attrs.endpoint,
20967
+ this.get_api_host(attrs.api_name) + '/' + api_routes[attrs.api_name],
20907
20968
  this._encode_data_for_request(data),
20908
20969
  options,
20909
20970
  this._prepare_callback(cb, data)
@@ -21129,7 +21190,7 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
21129
21190
  var ret = this._track_or_batch({
21130
21191
  type: 'events',
21131
21192
  data: data,
21132
- endpoint: this.get_config('api_host') + '/' + this.get_config('api_routes')['track'],
21193
+ endpoint: this.get_api_host('events') + '/' + this.get_config('api_routes')['track'],
21133
21194
  batcher: this.request_batchers.events,
21134
21195
  should_send_immediately: should_send_immediately,
21135
21196
  send_request_options: options
@@ -21631,6 +21692,7 @@ MixpanelLib.prototype.identify = function(
21631
21692
  * Useful for clearing data when a user logs out.
21632
21693
  */
21633
21694
  MixpanelLib.prototype.reset = function() {
21695
+ this.stop_session_recording();
21634
21696
  this['persistence'].clear();
21635
21697
  this._flags.identify_called = false;
21636
21698
  var uuid = _.UUID();
@@ -21638,7 +21700,6 @@ MixpanelLib.prototype.reset = function() {
21638
21700
  'distinct_id': DEVICE_ID_PREFIX + uuid,
21639
21701
  '$device_id': uuid
21640
21702
  }, '');
21641
- this.stop_session_recording();
21642
21703
  this._check_and_start_session_recording();
21643
21704
  };
21644
21705
 
@@ -21950,6 +22011,16 @@ MixpanelLib.prototype.get_property = function(property_name) {
21950
22011
  return this['persistence'].load_prop([property_name]);
21951
22012
  };
21952
22013
 
22014
+ /**
22015
+ * Get the API host for a specific endpoint type, falling back to the default api_host if not specified
22016
+ *
22017
+ * @param {String} endpoint_type The type of endpoint (e.g., "events", "people", "groups")
22018
+ * @returns {String} The API host to use for this endpoint
22019
+ */
22020
+ MixpanelLib.prototype.get_api_host = function(endpoint_type) {
22021
+ return this.get_config('api_hosts')[endpoint_type] || this.get_config('api_host');
22022
+ };
22023
+
21953
22024
  MixpanelLib.prototype.toString = function() {
21954
22025
  var name = this.get_config('name');
21955
22026
  if (name !== PRIMARY_INSTANCE_NAME) {
@@ -22245,6 +22316,7 @@ MixpanelLib.prototype['alias'] = MixpanelLib.protot
22245
22316
  MixpanelLib.prototype['name_tag'] = MixpanelLib.prototype.name_tag;
22246
22317
  MixpanelLib.prototype['set_config'] = MixpanelLib.prototype.set_config;
22247
22318
  MixpanelLib.prototype['get_config'] = MixpanelLib.prototype.get_config;
22319
+ MixpanelLib.prototype['get_api_host'] = MixpanelLib.prototype.get_api_host;
22248
22320
  MixpanelLib.prototype['get_property'] = MixpanelLib.prototype.get_property;
22249
22321
  MixpanelLib.prototype['get_distinct_id'] = MixpanelLib.prototype.get_distinct_id;
22250
22322
  MixpanelLib.prototype['toString'] = MixpanelLib.prototype.toString;