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.
@@ -13944,7 +13944,7 @@ if (typeof Promise !== 'undefined' && Promise.toString().indexOf('[native code]'
13944
13944
 
13945
13945
  var Config = {
13946
13946
  DEBUG: false,
13947
- LIB_VERSION: '2.65.0'
13947
+ LIB_VERSION: '2.67.0'
13948
13948
  };
13949
13949
 
13950
13950
  /* eslint camelcase: "off", eqeqeq: "off" */
@@ -17062,6 +17062,13 @@ function isUserEvent(ev) {
17062
17062
  * @property {string} replayStartUrl
17063
17063
  */
17064
17064
 
17065
+ /**
17066
+ * @typedef {Object} UserIdInfo
17067
+ * @property {string} distinct_id
17068
+ * @property {string} user_id
17069
+ * @property {string} device_id
17070
+ */
17071
+
17065
17072
 
17066
17073
  /**
17067
17074
  * This class encapsulates a single session recording and its lifecycle.
@@ -17117,6 +17124,30 @@ var SessionRecording = function(options) {
17117
17124
  });
17118
17125
  };
17119
17126
 
17127
+ /**
17128
+ * @returns {UserIdInfo}
17129
+ */
17130
+ SessionRecording.prototype.getUserIdInfo = function () {
17131
+ if (this.finalFlushUserIdInfo) {
17132
+ return this.finalFlushUserIdInfo;
17133
+ }
17134
+
17135
+ var userIdInfo = {
17136
+ 'distinct_id': String(this._mixpanel.get_distinct_id()),
17137
+ };
17138
+
17139
+ // send ID management props if they exist
17140
+ var deviceId = this._mixpanel.get_property('$device_id');
17141
+ if (deviceId) {
17142
+ userIdInfo['$device_id'] = deviceId;
17143
+ }
17144
+ var userId = this._mixpanel.get_property('$user_id');
17145
+ if (userId) {
17146
+ userIdInfo['$user_id'] = userId;
17147
+ }
17148
+ return userIdInfo;
17149
+ };
17150
+
17120
17151
  SessionRecording.prototype.unloadPersistedData = function () {
17121
17152
  this.batcher.stop();
17122
17153
  return this.batcher.flush()
@@ -17241,6 +17272,9 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
17241
17272
  };
17242
17273
 
17243
17274
  SessionRecording.prototype.stopRecording = function (skipFlush) {
17275
+ // store the user ID info in case this is getting called in mixpanel.reset()
17276
+ this.finalFlushUserIdInfo = this.getUserIdInfo();
17277
+
17244
17278
  if (!this.isRrwebStopped()) {
17245
17279
  try {
17246
17280
  this._stopRecording();
@@ -17351,8 +17385,8 @@ SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, r
17351
17385
  retryAfter: response.headers.get('Retry-After')
17352
17386
  });
17353
17387
  }.bind(this);
17354
-
17355
- win['fetch'](this.getConfig('api_host') + '/' + this.getConfig('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
17388
+ var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
17389
+ win['fetch'](apiHost + '/' + this.getConfig('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
17356
17390
  'method': 'POST',
17357
17391
  'headers': {
17358
17392
  'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
@@ -17406,7 +17440,6 @@ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
17406
17440
  '$current_url': this.batchStartUrl,
17407
17441
  '$lib_version': Config.LIB_VERSION,
17408
17442
  'batch_start_time': batchStartTime / 1000,
17409
- 'distinct_id': String(this._mixpanel.get_distinct_id()),
17410
17443
  'mp_lib': 'web',
17411
17444
  'replay_id': replayId,
17412
17445
  'replay_length_ms': replayLengthMs,
@@ -17415,16 +17448,7 @@ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
17415
17448
  'seq': this.seqNo
17416
17449
  };
17417
17450
  var eventsJson = JSON.stringify(data);
17418
-
17419
- // send ID management props if they exist
17420
- var deviceId = this._mixpanel.get_property('$device_id');
17421
- if (deviceId) {
17422
- reqParams['$device_id'] = deviceId;
17423
- }
17424
- var userId = this._mixpanel.get_property('$user_id');
17425
- if (userId) {
17426
- reqParams['$user_id'] = userId;
17427
- }
17451
+ Object.assign(reqParams, this.getUserIdInfo());
17428
17452
 
17429
17453
  if (CompressionStream) {
17430
17454
  var jsonStream = new Blob([eventsJson], {type: 'application/json'}).stream();
@@ -17569,6 +17593,7 @@ var MixpanelRecorder = function(mixpanelInstance, rrwebRecord, sharedLockStorage
17569
17593
  this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
17570
17594
 
17571
17595
  this.activeRecording = null;
17596
+ this.stopRecordingInProgress = false;
17572
17597
  };
17573
17598
 
17574
17599
  MixpanelRecorder.prototype.startRecording = function(options) {
@@ -17617,19 +17642,26 @@ MixpanelRecorder.prototype.startRecording = function(options) {
17617
17642
  };
17618
17643
 
17619
17644
  MixpanelRecorder.prototype.stopRecording = function() {
17620
- var stopPromise = this._stopCurrentRecording(false);
17621
- this.recordingRegistry.clearActiveRecording();
17622
- this.activeRecording = null;
17623
- return stopPromise;
17645
+ // Prevents activeSerializedRecording from being reused when stopping the recording.
17646
+ this.stopRecordingInProgress = true;
17647
+ return this._stopCurrentRecording(false, true).then(function() {
17648
+ return this.recordingRegistry.clearActiveRecording();
17649
+ }.bind(this)).then(function() {
17650
+ this.stopRecordingInProgress = false;
17651
+ }.bind(this));
17624
17652
  };
17625
17653
 
17626
17654
  MixpanelRecorder.prototype.pauseRecording = function() {
17627
17655
  return this._stopCurrentRecording(false);
17628
17656
  };
17629
17657
 
17630
- MixpanelRecorder.prototype._stopCurrentRecording = function(skipFlush) {
17658
+ MixpanelRecorder.prototype._stopCurrentRecording = function(skipFlush, disableActiveRecording) {
17631
17659
  if (this.activeRecording) {
17632
- return this.activeRecording.stopRecording(skipFlush);
17660
+ var stopRecordingPromise = this.activeRecording.stopRecording(skipFlush);
17661
+ if (disableActiveRecording) {
17662
+ this.activeRecording = null;
17663
+ }
17664
+ return stopRecordingPromise;
17633
17665
  }
17634
17666
  return PromisePolyfill.resolve();
17635
17667
  };
@@ -17642,7 +17674,7 @@ MixpanelRecorder.prototype.resumeRecording = function (startNewIfInactive) {
17642
17674
 
17643
17675
  return this.recordingRegistry.getActiveRecording()
17644
17676
  .then(function (activeSerializedRecording) {
17645
- if (activeSerializedRecording) {
17677
+ if (activeSerializedRecording && !this.stopRecordingInProgress) {
17646
17678
  return this.startRecording({activeSerializedRecording: activeSerializedRecording});
17647
17679
  } else if (startNewIfInactive) {
17648
17680
  return this.startRecording({shouldStopBatcher: false});
@@ -18545,8 +18577,9 @@ CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
18545
18577
  * @constructor
18546
18578
  */
18547
18579
  var FeatureFlagManager = function(initOptions) {
18580
+ this.getFullApiRoute = initOptions.getFullApiRoute;
18548
18581
  this.getMpConfig = initOptions.getConfigFunc;
18549
- this.getDistinctId = initOptions.getDistinctIdFunc;
18582
+ this.getMpProperty = initOptions.getPropertyFunc;
18550
18583
  this.track = initOptions.trackingFunc;
18551
18584
  };
18552
18585
 
@@ -18595,12 +18628,14 @@ FeatureFlagManager.prototype.fetchFlags = function() {
18595
18628
  return;
18596
18629
  }
18597
18630
 
18598
- var distinctId = this.getDistinctId();
18631
+ var distinctId = this.getMpProperty('distinct_id');
18632
+ var deviceId = this.getMpProperty('$device_id');
18599
18633
  logger.log('Fetching flags for distinct ID: ' + distinctId);
18600
18634
  var reqParams = {
18601
- 'context': _.extend({'distinct_id': distinctId}, this.getConfig(CONFIG_CONTEXT))
18635
+ 'context': _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT))
18602
18636
  };
18603
- this.fetchPromise = win['fetch'](this.getMpConfig('api_host') + '/' + this.getMpConfig('api_routes')['flags'], {
18637
+ this._fetchInProgressStartTime = Date.now();
18638
+ this.fetchPromise = win['fetch'](this.getFullApiRoute(), {
18604
18639
  'method': 'POST',
18605
18640
  'headers': {
18606
18641
  'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
@@ -18608,6 +18643,7 @@ FeatureFlagManager.prototype.fetchFlags = function() {
18608
18643
  },
18609
18644
  'body': JSON.stringify(reqParams)
18610
18645
  }).then(function(response) {
18646
+ this.markFetchComplete();
18611
18647
  return response.json().then(function(responseBody) {
18612
18648
  var responseFlags = responseBody['flags'];
18613
18649
  if (!responseFlags) {
@@ -18622,9 +18658,24 @@ FeatureFlagManager.prototype.fetchFlags = function() {
18622
18658
  });
18623
18659
  this.flags = flags;
18624
18660
  }.bind(this)).catch(function(error) {
18661
+ this.markFetchComplete();
18625
18662
  logger.error(error);
18626
- });
18627
- }.bind(this)).catch(function() {});
18663
+ }.bind(this));
18664
+ }.bind(this)).catch(function(error) {
18665
+ this.markFetchComplete();
18666
+ logger.error(error);
18667
+ }.bind(this));
18668
+ };
18669
+
18670
+ FeatureFlagManager.prototype.markFetchComplete = function() {
18671
+ if (!this._fetchInProgressStartTime) {
18672
+ logger.error('Fetch in progress started time not set, cannot mark fetch complete');
18673
+ return;
18674
+ }
18675
+ this._fetchStartTime = this._fetchInProgressStartTime;
18676
+ this._fetchCompleteTime = Date.now();
18677
+ this._fetchLatency = this._fetchCompleteTime - this._fetchStartTime;
18678
+ this._fetchInProgressStartTime = null;
18628
18679
  };
18629
18680
 
18630
18681
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
@@ -18703,7 +18754,10 @@ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature)
18703
18754
  this.track('$experiment_started', {
18704
18755
  'Experiment name': featureName,
18705
18756
  'Variant name': feature['key'],
18706
- '$experiment_type': 'feature_flag'
18757
+ '$experiment_type': 'feature_flag',
18758
+ 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
18759
+ 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
18760
+ 'Variant fetch latency (ms)': this._fetchLatency
18707
18761
  });
18708
18762
  };
18709
18763
 
@@ -19144,7 +19198,7 @@ MixpanelGroup.prototype._send_request = function(data, callback) {
19144
19198
  return this._mixpanel._track_or_batch({
19145
19199
  type: 'groups',
19146
19200
  data: date_encoded_data,
19147
- endpoint: this._get_config('api_host') + '/' + this._get_config('api_routes')['groups'],
19201
+ endpoint: this._mixpanel.get_api_host('groups') + '/' + this._get_config('api_routes')['groups'],
19148
19202
  batcher: this._mixpanel.request_batchers.groups
19149
19203
  }, callback);
19150
19204
  };
@@ -19496,7 +19550,7 @@ MixpanelPeople.prototype._send_request = function(data, callback) {
19496
19550
  return this._mixpanel._track_or_batch({
19497
19551
  type: 'people',
19498
19552
  data: date_encoded_data,
19499
- endpoint: this._get_config('api_host') + '/' + this._get_config('api_routes')['engage'],
19553
+ endpoint: this._mixpanel.get_api_host('people') + '/' + this._get_config('api_routes')['engage'],
19500
19554
  batcher: this._mixpanel.request_batchers.people
19501
19555
  }, callback);
19502
19556
  };
@@ -20133,6 +20187,7 @@ var DEFAULT_API_ROUTES = {
20133
20187
  */
20134
20188
  var DEFAULT_CONFIG = {
20135
20189
  'api_host': 'https://api-js.mixpanel.com',
20190
+ 'api_hosts': {},
20136
20191
  'api_routes': DEFAULT_API_ROUTES,
20137
20192
  'api_extra_query_params': {},
20138
20193
  'api_method': 'POST',
@@ -20402,8 +20457,11 @@ MixpanelLib.prototype._init = function(token, config, name) {
20402
20457
  }
20403
20458
 
20404
20459
  this.flags = new FeatureFlagManager({
20460
+ getFullApiRoute: _.bind(function() {
20461
+ return this.get_api_host('flags') + '/' + this.get_config('api_routes')['flags'];
20462
+ }, this),
20405
20463
  getConfigFunc: _.bind(this.get_config, this),
20406
- getDistinctIdFunc: _.bind(this.get_distinct_id, this),
20464
+ getPropertyFunc: _.bind(this.get_property, this),
20407
20465
  trackingFunc: _.bind(this.track, this)
20408
20466
  });
20409
20467
  this.flags.init();
@@ -20518,20 +20576,23 @@ MixpanelLib.prototype.start_session_recording = function () {
20518
20576
 
20519
20577
  MixpanelLib.prototype.stop_session_recording = function () {
20520
20578
  if (this._recorder) {
20521
- this._recorder['stopRecording']();
20579
+ return this._recorder['stopRecording']();
20522
20580
  }
20581
+ return Promise.resolve();
20523
20582
  };
20524
20583
 
20525
20584
  MixpanelLib.prototype.pause_session_recording = function () {
20526
20585
  if (this._recorder) {
20527
- this._recorder['pauseRecording']();
20586
+ return this._recorder['pauseRecording']();
20528
20587
  }
20588
+ return Promise.resolve();
20529
20589
  };
20530
20590
 
20531
20591
  MixpanelLib.prototype.resume_session_recording = function () {
20532
20592
  if (this._recorder) {
20533
- this._recorder['resumeRecording']();
20593
+ return this._recorder['resumeRecording']();
20534
20594
  }
20595
+ return Promise.resolve();
20535
20596
  };
20536
20597
 
20537
20598
  MixpanelLib.prototype.is_recording_heatmap_data = function () {
@@ -20886,11 +20947,10 @@ MixpanelLib.prototype.are_batchers_initialized = function() {
20886
20947
 
20887
20948
  MixpanelLib.prototype.get_batcher_configs = function() {
20888
20949
  var queue_prefix = '__mpq_' + this.get_config('token');
20889
- var api_routes = this.get_config('api_routes');
20890
20950
  this._batcher_configs = this._batcher_configs || {
20891
- events: {type: 'events', endpoint: '/' + api_routes['track'], queue_key: queue_prefix + '_ev'},
20892
- people: {type: 'people', endpoint: '/' + api_routes['engage'], queue_key: queue_prefix + '_pp'},
20893
- groups: {type: 'groups', endpoint: '/' + api_routes['groups'], queue_key: queue_prefix + '_gr'}
20951
+ events: {type: 'events', api_name: 'track', queue_key: queue_prefix + '_ev'},
20952
+ people: {type: 'people', api_name: 'engage', queue_key: queue_prefix + '_pp'},
20953
+ groups: {type: 'groups', api_name: 'groups', queue_key: queue_prefix + '_gr'}
20894
20954
  };
20895
20955
  return this._batcher_configs;
20896
20956
  };
@@ -20904,8 +20964,9 @@ MixpanelLib.prototype.init_batchers = function() {
20904
20964
  libConfig: this['config'],
20905
20965
  errorReporter: this.get_config('error_reporter'),
20906
20966
  sendRequestFunc: _.bind(function(data, options, cb) {
20967
+ var api_routes = this.get_config('api_routes');
20907
20968
  this._send_request(
20908
- this.get_config('api_host') + attrs.endpoint,
20969
+ this.get_api_host(attrs.api_name) + '/' + api_routes[attrs.api_name],
20909
20970
  this._encode_data_for_request(data),
20910
20971
  options,
20911
20972
  this._prepare_callback(cb, data)
@@ -21131,7 +21192,7 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
21131
21192
  var ret = this._track_or_batch({
21132
21193
  type: 'events',
21133
21194
  data: data,
21134
- endpoint: this.get_config('api_host') + '/' + this.get_config('api_routes')['track'],
21195
+ endpoint: this.get_api_host('events') + '/' + this.get_config('api_routes')['track'],
21135
21196
  batcher: this.request_batchers.events,
21136
21197
  should_send_immediately: should_send_immediately,
21137
21198
  send_request_options: options
@@ -21633,6 +21694,7 @@ MixpanelLib.prototype.identify = function(
21633
21694
  * Useful for clearing data when a user logs out.
21634
21695
  */
21635
21696
  MixpanelLib.prototype.reset = function() {
21697
+ this.stop_session_recording();
21636
21698
  this['persistence'].clear();
21637
21699
  this._flags.identify_called = false;
21638
21700
  var uuid = _.UUID();
@@ -21640,7 +21702,6 @@ MixpanelLib.prototype.reset = function() {
21640
21702
  'distinct_id': DEVICE_ID_PREFIX + uuid,
21641
21703
  '$device_id': uuid
21642
21704
  }, '');
21643
- this.stop_session_recording();
21644
21705
  this._check_and_start_session_recording();
21645
21706
  };
21646
21707
 
@@ -21952,6 +22013,16 @@ MixpanelLib.prototype.get_property = function(property_name) {
21952
22013
  return this['persistence'].load_prop([property_name]);
21953
22014
  };
21954
22015
 
22016
+ /**
22017
+ * Get the API host for a specific endpoint type, falling back to the default api_host if not specified
22018
+ *
22019
+ * @param {String} endpoint_type The type of endpoint (e.g., "events", "people", "groups")
22020
+ * @returns {String} The API host to use for this endpoint
22021
+ */
22022
+ MixpanelLib.prototype.get_api_host = function(endpoint_type) {
22023
+ return this.get_config('api_hosts')[endpoint_type] || this.get_config('api_host');
22024
+ };
22025
+
21955
22026
  MixpanelLib.prototype.toString = function() {
21956
22027
  var name = this.get_config('name');
21957
22028
  if (name !== PRIMARY_INSTANCE_NAME) {
@@ -22247,6 +22318,7 @@ MixpanelLib.prototype['alias'] = MixpanelLib.protot
22247
22318
  MixpanelLib.prototype['name_tag'] = MixpanelLib.prototype.name_tag;
22248
22319
  MixpanelLib.prototype['set_config'] = MixpanelLib.prototype.set_config;
22249
22320
  MixpanelLib.prototype['get_config'] = MixpanelLib.prototype.get_config;
22321
+ MixpanelLib.prototype['get_api_host'] = MixpanelLib.prototype.get_api_host;
22250
22322
  MixpanelLib.prototype['get_property'] = MixpanelLib.prototype.get_property;
22251
22323
  MixpanelLib.prototype['get_distinct_id'] = MixpanelLib.prototype.get_distinct_id;
22252
22324
  MixpanelLib.prototype['toString'] = MixpanelLib.prototype.toString;
@@ -3,7 +3,7 @@
3
3
 
4
4
  var Config = {
5
5
  DEBUG: false,
6
- LIB_VERSION: '2.65.0'
6
+ LIB_VERSION: '2.67.0'
7
7
  };
8
8
 
9
9
  // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
@@ -3003,8 +3003,9 @@
3003
3003
  * @constructor
3004
3004
  */
3005
3005
  var FeatureFlagManager = function(initOptions) {
3006
+ this.getFullApiRoute = initOptions.getFullApiRoute;
3006
3007
  this.getMpConfig = initOptions.getConfigFunc;
3007
- this.getDistinctId = initOptions.getDistinctIdFunc;
3008
+ this.getMpProperty = initOptions.getPropertyFunc;
3008
3009
  this.track = initOptions.trackingFunc;
3009
3010
  };
3010
3011
 
@@ -3053,12 +3054,14 @@
3053
3054
  return;
3054
3055
  }
3055
3056
 
3056
- var distinctId = this.getDistinctId();
3057
+ var distinctId = this.getMpProperty('distinct_id');
3058
+ var deviceId = this.getMpProperty('$device_id');
3057
3059
  logger$3.log('Fetching flags for distinct ID: ' + distinctId);
3058
3060
  var reqParams = {
3059
- 'context': _.extend({'distinct_id': distinctId}, this.getConfig(CONFIG_CONTEXT))
3061
+ 'context': _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT))
3060
3062
  };
3061
- this.fetchPromise = win['fetch'](this.getMpConfig('api_host') + '/' + this.getMpConfig('api_routes')['flags'], {
3063
+ this._fetchInProgressStartTime = Date.now();
3064
+ this.fetchPromise = win['fetch'](this.getFullApiRoute(), {
3062
3065
  'method': 'POST',
3063
3066
  'headers': {
3064
3067
  'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
@@ -3066,6 +3069,7 @@
3066
3069
  },
3067
3070
  'body': JSON.stringify(reqParams)
3068
3071
  }).then(function(response) {
3072
+ this.markFetchComplete();
3069
3073
  return response.json().then(function(responseBody) {
3070
3074
  var responseFlags = responseBody['flags'];
3071
3075
  if (!responseFlags) {
@@ -3080,9 +3084,24 @@
3080
3084
  });
3081
3085
  this.flags = flags;
3082
3086
  }.bind(this)).catch(function(error) {
3087
+ this.markFetchComplete();
3083
3088
  logger$3.error(error);
3084
- });
3085
- }.bind(this)).catch(function() {});
3089
+ }.bind(this));
3090
+ }.bind(this)).catch(function(error) {
3091
+ this.markFetchComplete();
3092
+ logger$3.error(error);
3093
+ }.bind(this));
3094
+ };
3095
+
3096
+ FeatureFlagManager.prototype.markFetchComplete = function() {
3097
+ if (!this._fetchInProgressStartTime) {
3098
+ logger$3.error('Fetch in progress started time not set, cannot mark fetch complete');
3099
+ return;
3100
+ }
3101
+ this._fetchStartTime = this._fetchInProgressStartTime;
3102
+ this._fetchCompleteTime = Date.now();
3103
+ this._fetchLatency = this._fetchCompleteTime - this._fetchStartTime;
3104
+ this._fetchInProgressStartTime = null;
3086
3105
  };
3087
3106
 
3088
3107
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
@@ -3161,7 +3180,10 @@
3161
3180
  this.track('$experiment_started', {
3162
3181
  'Experiment name': featureName,
3163
3182
  'Variant name': feature['key'],
3164
- '$experiment_type': 'feature_flag'
3183
+ '$experiment_type': 'feature_flag',
3184
+ 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
3185
+ 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
3186
+ 'Variant fetch latency (ms)': this._fetchLatency
3165
3187
  });
3166
3188
  };
3167
3189
 
@@ -4790,7 +4812,7 @@
4790
4812
  return this._mixpanel._track_or_batch({
4791
4813
  type: 'groups',
4792
4814
  data: date_encoded_data,
4793
- endpoint: this._get_config('api_host') + '/' + this._get_config('api_routes')['groups'],
4815
+ endpoint: this._mixpanel.get_api_host('groups') + '/' + this._get_config('api_routes')['groups'],
4794
4816
  batcher: this._mixpanel.request_batchers.groups
4795
4817
  }, callback);
4796
4818
  };
@@ -5142,7 +5164,7 @@
5142
5164
  return this._mixpanel._track_or_batch({
5143
5165
  type: 'people',
5144
5166
  data: date_encoded_data,
5145
- endpoint: this._get_config('api_host') + '/' + this._get_config('api_routes')['engage'],
5167
+ endpoint: this._mixpanel.get_api_host('people') + '/' + this._get_config('api_routes')['engage'],
5146
5168
  batcher: this._mixpanel.request_batchers.people
5147
5169
  }, callback);
5148
5170
  };
@@ -5902,6 +5924,7 @@
5902
5924
  */
5903
5925
  var DEFAULT_CONFIG = {
5904
5926
  'api_host': 'https://api-js.mixpanel.com',
5927
+ 'api_hosts': {},
5905
5928
  'api_routes': DEFAULT_API_ROUTES,
5906
5929
  'api_extra_query_params': {},
5907
5930
  'api_method': 'POST',
@@ -6171,8 +6194,11 @@
6171
6194
  }
6172
6195
 
6173
6196
  this.flags = new FeatureFlagManager({
6197
+ getFullApiRoute: _.bind(function() {
6198
+ return this.get_api_host('flags') + '/' + this.get_config('api_routes')['flags'];
6199
+ }, this),
6174
6200
  getConfigFunc: _.bind(this.get_config, this),
6175
- getDistinctIdFunc: _.bind(this.get_distinct_id, this),
6201
+ getPropertyFunc: _.bind(this.get_property, this),
6176
6202
  trackingFunc: _.bind(this.track, this)
6177
6203
  });
6178
6204
  this.flags.init();
@@ -6287,20 +6313,23 @@
6287
6313
 
6288
6314
  MixpanelLib.prototype.stop_session_recording = function () {
6289
6315
  if (this._recorder) {
6290
- this._recorder['stopRecording']();
6316
+ return this._recorder['stopRecording']();
6291
6317
  }
6318
+ return Promise.resolve();
6292
6319
  };
6293
6320
 
6294
6321
  MixpanelLib.prototype.pause_session_recording = function () {
6295
6322
  if (this._recorder) {
6296
- this._recorder['pauseRecording']();
6323
+ return this._recorder['pauseRecording']();
6297
6324
  }
6325
+ return Promise.resolve();
6298
6326
  };
6299
6327
 
6300
6328
  MixpanelLib.prototype.resume_session_recording = function () {
6301
6329
  if (this._recorder) {
6302
- this._recorder['resumeRecording']();
6330
+ return this._recorder['resumeRecording']();
6303
6331
  }
6332
+ return Promise.resolve();
6304
6333
  };
6305
6334
 
6306
6335
  MixpanelLib.prototype.is_recording_heatmap_data = function () {
@@ -6655,11 +6684,10 @@
6655
6684
 
6656
6685
  MixpanelLib.prototype.get_batcher_configs = function() {
6657
6686
  var queue_prefix = '__mpq_' + this.get_config('token');
6658
- var api_routes = this.get_config('api_routes');
6659
6687
  this._batcher_configs = this._batcher_configs || {
6660
- events: {type: 'events', endpoint: '/' + api_routes['track'], queue_key: queue_prefix + '_ev'},
6661
- people: {type: 'people', endpoint: '/' + api_routes['engage'], queue_key: queue_prefix + '_pp'},
6662
- groups: {type: 'groups', endpoint: '/' + api_routes['groups'], queue_key: queue_prefix + '_gr'}
6688
+ events: {type: 'events', api_name: 'track', queue_key: queue_prefix + '_ev'},
6689
+ people: {type: 'people', api_name: 'engage', queue_key: queue_prefix + '_pp'},
6690
+ groups: {type: 'groups', api_name: 'groups', queue_key: queue_prefix + '_gr'}
6663
6691
  };
6664
6692
  return this._batcher_configs;
6665
6693
  };
@@ -6673,8 +6701,9 @@
6673
6701
  libConfig: this['config'],
6674
6702
  errorReporter: this.get_config('error_reporter'),
6675
6703
  sendRequestFunc: _.bind(function(data, options, cb) {
6704
+ var api_routes = this.get_config('api_routes');
6676
6705
  this._send_request(
6677
- this.get_config('api_host') + attrs.endpoint,
6706
+ this.get_api_host(attrs.api_name) + '/' + api_routes[attrs.api_name],
6678
6707
  this._encode_data_for_request(data),
6679
6708
  options,
6680
6709
  this._prepare_callback(cb, data)
@@ -6900,7 +6929,7 @@
6900
6929
  var ret = this._track_or_batch({
6901
6930
  type: 'events',
6902
6931
  data: data,
6903
- endpoint: this.get_config('api_host') + '/' + this.get_config('api_routes')['track'],
6932
+ endpoint: this.get_api_host('events') + '/' + this.get_config('api_routes')['track'],
6904
6933
  batcher: this.request_batchers.events,
6905
6934
  should_send_immediately: should_send_immediately,
6906
6935
  send_request_options: options
@@ -7402,6 +7431,7 @@
7402
7431
  * Useful for clearing data when a user logs out.
7403
7432
  */
7404
7433
  MixpanelLib.prototype.reset = function() {
7434
+ this.stop_session_recording();
7405
7435
  this['persistence'].clear();
7406
7436
  this._flags.identify_called = false;
7407
7437
  var uuid = _.UUID();
@@ -7409,7 +7439,6 @@
7409
7439
  'distinct_id': DEVICE_ID_PREFIX + uuid,
7410
7440
  '$device_id': uuid
7411
7441
  }, '');
7412
- this.stop_session_recording();
7413
7442
  this._check_and_start_session_recording();
7414
7443
  };
7415
7444
 
@@ -7721,6 +7750,16 @@
7721
7750
  return this['persistence'].load_prop([property_name]);
7722
7751
  };
7723
7752
 
7753
+ /**
7754
+ * Get the API host for a specific endpoint type, falling back to the default api_host if not specified
7755
+ *
7756
+ * @param {String} endpoint_type The type of endpoint (e.g., "events", "people", "groups")
7757
+ * @returns {String} The API host to use for this endpoint
7758
+ */
7759
+ MixpanelLib.prototype.get_api_host = function(endpoint_type) {
7760
+ return this.get_config('api_hosts')[endpoint_type] || this.get_config('api_host');
7761
+ };
7762
+
7724
7763
  MixpanelLib.prototype.toString = function() {
7725
7764
  var name = this.get_config('name');
7726
7765
  if (name !== PRIMARY_INSTANCE_NAME) {
@@ -8016,6 +8055,7 @@
8016
8055
  MixpanelLib.prototype['name_tag'] = MixpanelLib.prototype.name_tag;
8017
8056
  MixpanelLib.prototype['set_config'] = MixpanelLib.prototype.set_config;
8018
8057
  MixpanelLib.prototype['get_config'] = MixpanelLib.prototype.get_config;
8058
+ MixpanelLib.prototype['get_api_host'] = MixpanelLib.prototype.get_api_host;
8019
8059
  MixpanelLib.prototype['get_property'] = MixpanelLib.prototype.get_property;
8020
8060
  MixpanelLib.prototype['get_distinct_id'] = MixpanelLib.prototype.get_distinct_id;
8021
8061
  MixpanelLib.prototype['toString'] = MixpanelLib.prototype.toString;