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.
@@ -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.66.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();
@@ -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();
@@ -18551,8 +18575,9 @@ CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
18551
18575
  * @constructor
18552
18576
  */
18553
18577
  var FeatureFlagManager = function(initOptions) {
18578
+ this.getFullApiRoute = initOptions.getFullApiRoute;
18554
18579
  this.getMpConfig = initOptions.getConfigFunc;
18555
- this.getDistinctId = initOptions.getDistinctIdFunc;
18580
+ this.getMpProperty = initOptions.getPropertyFunc;
18556
18581
  this.track = initOptions.trackingFunc;
18557
18582
  };
18558
18583
 
@@ -18601,12 +18626,14 @@ FeatureFlagManager.prototype.fetchFlags = function() {
18601
18626
  return;
18602
18627
  }
18603
18628
 
18604
- var distinctId = this.getDistinctId();
18629
+ var distinctId = this.getMpProperty('distinct_id');
18630
+ var deviceId = this.getMpProperty('$device_id');
18605
18631
  logger.log('Fetching flags for distinct ID: ' + distinctId);
18606
18632
  var reqParams = {
18607
- 'context': _.extend({'distinct_id': distinctId}, this.getConfig(CONFIG_CONTEXT))
18633
+ 'context': _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT))
18608
18634
  };
18609
- 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(), {
18610
18637
  'method': 'POST',
18611
18638
  'headers': {
18612
18639
  'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
@@ -18614,6 +18641,7 @@ FeatureFlagManager.prototype.fetchFlags = function() {
18614
18641
  },
18615
18642
  'body': JSON.stringify(reqParams)
18616
18643
  }).then(function(response) {
18644
+ this.markFetchComplete();
18617
18645
  return response.json().then(function(responseBody) {
18618
18646
  var responseFlags = responseBody['flags'];
18619
18647
  if (!responseFlags) {
@@ -18628,9 +18656,24 @@ FeatureFlagManager.prototype.fetchFlags = function() {
18628
18656
  });
18629
18657
  this.flags = flags;
18630
18658
  }.bind(this)).catch(function(error) {
18659
+ this.markFetchComplete();
18631
18660
  logger.error(error);
18632
- });
18633
- }.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;
18634
18677
  };
18635
18678
 
18636
18679
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
@@ -18709,7 +18752,10 @@ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature)
18709
18752
  this.track('$experiment_started', {
18710
18753
  'Experiment name': featureName,
18711
18754
  'Variant name': feature['key'],
18712
- '$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
18713
18759
  });
18714
18760
  };
18715
18761
 
@@ -20409,8 +20455,11 @@ MixpanelLib.prototype._init = function(token, config, name) {
20409
20455
  }
20410
20456
 
20411
20457
  this.flags = new FeatureFlagManager({
20458
+ getFullApiRoute: _.bind(function() {
20459
+ return this.get_api_host('flags') + '/' + this.get_config('api_routes')['flags'];
20460
+ }, this),
20412
20461
  getConfigFunc: _.bind(this.get_config, this),
20413
- getDistinctIdFunc: _.bind(this.get_distinct_id, this),
20462
+ getPropertyFunc: _.bind(this.get_property, this),
20414
20463
  trackingFunc: _.bind(this.track, this)
20415
20464
  });
20416
20465
  this.flags.init();
@@ -20896,11 +20945,10 @@ MixpanelLib.prototype.are_batchers_initialized = function() {
20896
20945
 
20897
20946
  MixpanelLib.prototype.get_batcher_configs = function() {
20898
20947
  var queue_prefix = '__mpq_' + this.get_config('token');
20899
- var api_routes = this.get_config('api_routes');
20900
20948
  this._batcher_configs = this._batcher_configs || {
20901
- events: {type: 'events', endpoint: '/' + api_routes['track'], queue_key: queue_prefix + '_ev'},
20902
- people: {type: 'people', endpoint: '/' + api_routes['engage'], queue_key: queue_prefix + '_pp'},
20903
- 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'}
20904
20952
  };
20905
20953
  return this._batcher_configs;
20906
20954
  };
@@ -20914,8 +20962,9 @@ MixpanelLib.prototype.init_batchers = function() {
20914
20962
  libConfig: this['config'],
20915
20963
  errorReporter: this.get_config('error_reporter'),
20916
20964
  sendRequestFunc: _.bind(function(data, options, cb) {
20965
+ var api_routes = this.get_config('api_routes');
20917
20966
  this._send_request(
20918
- this.get_config('api_host') + attrs.endpoint,
20967
+ this.get_api_host(attrs.api_name) + '/' + api_routes[attrs.api_name],
20919
20968
  this._encode_data_for_request(data),
20920
20969
  options,
20921
20970
  this._prepare_callback(cb, data)
@@ -21643,31 +21692,15 @@ MixpanelLib.prototype.identify = function(
21643
21692
  * Useful for clearing data when a user logs out.
21644
21693
  */
21645
21694
  MixpanelLib.prototype.reset = function() {
21646
- var self = this;
21647
-
21648
- var reset = function () {
21649
- self['persistence'].clear();
21650
- self._flags.identify_called = false;
21651
- var uuid = _.UUID();
21652
- self.register_once({
21653
- 'distinct_id': DEVICE_ID_PREFIX + uuid,
21654
- '$device_id': uuid
21655
- }, '');
21656
- };
21657
-
21658
- if (self._recorder) {
21659
- self.stop_session_recording()
21660
- .then(function () {
21661
- reset();
21662
- self._check_and_start_session_recording();
21663
- })
21664
- .catch(_.bind(function (err) {
21665
- reset();
21666
- this.report_error('Error restarting recording session', err);
21667
- }, this));
21668
- } else {
21669
- reset();
21670
- }
21695
+ this.stop_session_recording();
21696
+ this['persistence'].clear();
21697
+ this._flags.identify_called = false;
21698
+ var uuid = _.UUID();
21699
+ this.register_once({
21700
+ 'distinct_id': DEVICE_ID_PREFIX + uuid,
21701
+ '$device_id': uuid
21702
+ }, '');
21703
+ this._check_and_start_session_recording();
21671
21704
  };
21672
21705
 
21673
21706
  /**
@@ -13948,7 +13948,7 @@
13948
13948
 
13949
13949
  var Config = {
13950
13950
  DEBUG: false,
13951
- LIB_VERSION: '2.66.0'
13951
+ LIB_VERSION: '2.67.0'
13952
13952
  };
13953
13953
 
13954
13954
  /* eslint camelcase: "off", eqeqeq: "off" */
@@ -17066,6 +17066,13 @@
17066
17066
  * @property {string} replayStartUrl
17067
17067
  */
17068
17068
 
17069
+ /**
17070
+ * @typedef {Object} UserIdInfo
17071
+ * @property {string} distinct_id
17072
+ * @property {string} user_id
17073
+ * @property {string} device_id
17074
+ */
17075
+
17069
17076
 
17070
17077
  /**
17071
17078
  * This class encapsulates a single session recording and its lifecycle.
@@ -17121,6 +17128,30 @@
17121
17128
  });
17122
17129
  };
17123
17130
 
17131
+ /**
17132
+ * @returns {UserIdInfo}
17133
+ */
17134
+ SessionRecording.prototype.getUserIdInfo = function () {
17135
+ if (this.finalFlushUserIdInfo) {
17136
+ return this.finalFlushUserIdInfo;
17137
+ }
17138
+
17139
+ var userIdInfo = {
17140
+ 'distinct_id': String(this._mixpanel.get_distinct_id()),
17141
+ };
17142
+
17143
+ // send ID management props if they exist
17144
+ var deviceId = this._mixpanel.get_property('$device_id');
17145
+ if (deviceId) {
17146
+ userIdInfo['$device_id'] = deviceId;
17147
+ }
17148
+ var userId = this._mixpanel.get_property('$user_id');
17149
+ if (userId) {
17150
+ userIdInfo['$user_id'] = userId;
17151
+ }
17152
+ return userIdInfo;
17153
+ };
17154
+
17124
17155
  SessionRecording.prototype.unloadPersistedData = function () {
17125
17156
  this.batcher.stop();
17126
17157
  return this.batcher.flush()
@@ -17245,6 +17276,9 @@
17245
17276
  };
17246
17277
 
17247
17278
  SessionRecording.prototype.stopRecording = function (skipFlush) {
17279
+ // store the user ID info in case this is getting called in mixpanel.reset()
17280
+ this.finalFlushUserIdInfo = this.getUserIdInfo();
17281
+
17248
17282
  if (!this.isRrwebStopped()) {
17249
17283
  try {
17250
17284
  this._stopRecording();
@@ -17410,7 +17444,6 @@
17410
17444
  '$current_url': this.batchStartUrl,
17411
17445
  '$lib_version': Config.LIB_VERSION,
17412
17446
  'batch_start_time': batchStartTime / 1000,
17413
- 'distinct_id': String(this._mixpanel.get_distinct_id()),
17414
17447
  'mp_lib': 'web',
17415
17448
  'replay_id': replayId,
17416
17449
  'replay_length_ms': replayLengthMs,
@@ -17419,16 +17452,7 @@
17419
17452
  'seq': this.seqNo
17420
17453
  };
17421
17454
  var eventsJson = JSON.stringify(data);
17422
-
17423
- // send ID management props if they exist
17424
- var deviceId = this._mixpanel.get_property('$device_id');
17425
- if (deviceId) {
17426
- reqParams['$device_id'] = deviceId;
17427
- }
17428
- var userId = this._mixpanel.get_property('$user_id');
17429
- if (userId) {
17430
- reqParams['$user_id'] = userId;
17431
- }
17455
+ Object.assign(reqParams, this.getUserIdInfo());
17432
17456
 
17433
17457
  if (CompressionStream) {
17434
17458
  var jsonStream = new Blob([eventsJson], {type: 'application/json'}).stream();
@@ -18557,8 +18581,9 @@
18557
18581
  * @constructor
18558
18582
  */
18559
18583
  var FeatureFlagManager = function(initOptions) {
18584
+ this.getFullApiRoute = initOptions.getFullApiRoute;
18560
18585
  this.getMpConfig = initOptions.getConfigFunc;
18561
- this.getDistinctId = initOptions.getDistinctIdFunc;
18586
+ this.getMpProperty = initOptions.getPropertyFunc;
18562
18587
  this.track = initOptions.trackingFunc;
18563
18588
  };
18564
18589
 
@@ -18607,12 +18632,14 @@
18607
18632
  return;
18608
18633
  }
18609
18634
 
18610
- var distinctId = this.getDistinctId();
18635
+ var distinctId = this.getMpProperty('distinct_id');
18636
+ var deviceId = this.getMpProperty('$device_id');
18611
18637
  logger.log('Fetching flags for distinct ID: ' + distinctId);
18612
18638
  var reqParams = {
18613
- 'context': _.extend({'distinct_id': distinctId}, this.getConfig(CONFIG_CONTEXT))
18639
+ 'context': _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT))
18614
18640
  };
18615
- this.fetchPromise = win['fetch'](this.getMpConfig('api_host') + '/' + this.getMpConfig('api_routes')['flags'], {
18641
+ this._fetchInProgressStartTime = Date.now();
18642
+ this.fetchPromise = win['fetch'](this.getFullApiRoute(), {
18616
18643
  'method': 'POST',
18617
18644
  'headers': {
18618
18645
  'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
@@ -18620,6 +18647,7 @@
18620
18647
  },
18621
18648
  'body': JSON.stringify(reqParams)
18622
18649
  }).then(function(response) {
18650
+ this.markFetchComplete();
18623
18651
  return response.json().then(function(responseBody) {
18624
18652
  var responseFlags = responseBody['flags'];
18625
18653
  if (!responseFlags) {
@@ -18634,9 +18662,24 @@
18634
18662
  });
18635
18663
  this.flags = flags;
18636
18664
  }.bind(this)).catch(function(error) {
18665
+ this.markFetchComplete();
18637
18666
  logger.error(error);
18638
- });
18639
- }.bind(this)).catch(function() {});
18667
+ }.bind(this));
18668
+ }.bind(this)).catch(function(error) {
18669
+ this.markFetchComplete();
18670
+ logger.error(error);
18671
+ }.bind(this));
18672
+ };
18673
+
18674
+ FeatureFlagManager.prototype.markFetchComplete = function() {
18675
+ if (!this._fetchInProgressStartTime) {
18676
+ logger.error('Fetch in progress started time not set, cannot mark fetch complete');
18677
+ return;
18678
+ }
18679
+ this._fetchStartTime = this._fetchInProgressStartTime;
18680
+ this._fetchCompleteTime = Date.now();
18681
+ this._fetchLatency = this._fetchCompleteTime - this._fetchStartTime;
18682
+ this._fetchInProgressStartTime = null;
18640
18683
  };
18641
18684
 
18642
18685
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
@@ -18715,7 +18758,10 @@
18715
18758
  this.track('$experiment_started', {
18716
18759
  'Experiment name': featureName,
18717
18760
  'Variant name': feature['key'],
18718
- '$experiment_type': 'feature_flag'
18761
+ '$experiment_type': 'feature_flag',
18762
+ 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
18763
+ 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
18764
+ 'Variant fetch latency (ms)': this._fetchLatency
18719
18765
  });
18720
18766
  };
18721
18767
 
@@ -20415,8 +20461,11 @@
20415
20461
  }
20416
20462
 
20417
20463
  this.flags = new FeatureFlagManager({
20464
+ getFullApiRoute: _.bind(function() {
20465
+ return this.get_api_host('flags') + '/' + this.get_config('api_routes')['flags'];
20466
+ }, this),
20418
20467
  getConfigFunc: _.bind(this.get_config, this),
20419
- getDistinctIdFunc: _.bind(this.get_distinct_id, this),
20468
+ getPropertyFunc: _.bind(this.get_property, this),
20420
20469
  trackingFunc: _.bind(this.track, this)
20421
20470
  });
20422
20471
  this.flags.init();
@@ -20902,11 +20951,10 @@
20902
20951
 
20903
20952
  MixpanelLib.prototype.get_batcher_configs = function() {
20904
20953
  var queue_prefix = '__mpq_' + this.get_config('token');
20905
- var api_routes = this.get_config('api_routes');
20906
20954
  this._batcher_configs = this._batcher_configs || {
20907
- events: {type: 'events', endpoint: '/' + api_routes['track'], queue_key: queue_prefix + '_ev'},
20908
- people: {type: 'people', endpoint: '/' + api_routes['engage'], queue_key: queue_prefix + '_pp'},
20909
- groups: {type: 'groups', endpoint: '/' + api_routes['groups'], queue_key: queue_prefix + '_gr'}
20955
+ events: {type: 'events', api_name: 'track', queue_key: queue_prefix + '_ev'},
20956
+ people: {type: 'people', api_name: 'engage', queue_key: queue_prefix + '_pp'},
20957
+ groups: {type: 'groups', api_name: 'groups', queue_key: queue_prefix + '_gr'}
20910
20958
  };
20911
20959
  return this._batcher_configs;
20912
20960
  };
@@ -20920,8 +20968,9 @@
20920
20968
  libConfig: this['config'],
20921
20969
  errorReporter: this.get_config('error_reporter'),
20922
20970
  sendRequestFunc: _.bind(function(data, options, cb) {
20971
+ var api_routes = this.get_config('api_routes');
20923
20972
  this._send_request(
20924
- this.get_config('api_host') + attrs.endpoint,
20973
+ this.get_api_host(attrs.api_name) + '/' + api_routes[attrs.api_name],
20925
20974
  this._encode_data_for_request(data),
20926
20975
  options,
20927
20976
  this._prepare_callback(cb, data)
@@ -21649,31 +21698,15 @@
21649
21698
  * Useful for clearing data when a user logs out.
21650
21699
  */
21651
21700
  MixpanelLib.prototype.reset = function() {
21652
- var self = this;
21653
-
21654
- var reset = function () {
21655
- self['persistence'].clear();
21656
- self._flags.identify_called = false;
21657
- var uuid = _.UUID();
21658
- self.register_once({
21659
- 'distinct_id': DEVICE_ID_PREFIX + uuid,
21660
- '$device_id': uuid
21661
- }, '');
21662
- };
21663
-
21664
- if (self._recorder) {
21665
- self.stop_session_recording()
21666
- .then(function () {
21667
- reset();
21668
- self._check_and_start_session_recording();
21669
- })
21670
- .catch(_.bind(function (err) {
21671
- reset();
21672
- this.report_error('Error restarting recording session', err);
21673
- }, this));
21674
- } else {
21675
- reset();
21676
- }
21701
+ this.stop_session_recording();
21702
+ this['persistence'].clear();
21703
+ this._flags.identify_called = false;
21704
+ var uuid = _.UUID();
21705
+ this.register_once({
21706
+ 'distinct_id': DEVICE_ID_PREFIX + uuid,
21707
+ '$device_id': uuid
21708
+ }, '');
21709
+ this._check_and_start_session_recording();
21677
21710
  };
21678
21711
 
21679
21712
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixpanel-browser",
3
- "version": "2.66.0",
3
+ "version": "2.67.0",
4
4
  "description": "The official Mixpanel JavaScript browser client library",
5
5
  "main": "dist/mixpanel.cjs.js",
6
6
  "module": "dist/mixpanel.module.js",
package/src/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  var Config = {
2
2
  DEBUG: false,
3
- LIB_VERSION: '2.66.0'
3
+ LIB_VERSION: '2.67.0'
4
4
  };
5
5
 
6
6
  export default Config;
@@ -15,8 +15,9 @@ CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
15
15
  * @constructor
16
16
  */
17
17
  var FeatureFlagManager = function(initOptions) {
18
+ this.getFullApiRoute = initOptions.getFullApiRoute;
18
19
  this.getMpConfig = initOptions.getConfigFunc;
19
- this.getDistinctId = initOptions.getDistinctIdFunc;
20
+ this.getMpProperty = initOptions.getPropertyFunc;
20
21
  this.track = initOptions.trackingFunc;
21
22
  };
22
23
 
@@ -65,12 +66,14 @@ FeatureFlagManager.prototype.fetchFlags = function() {
65
66
  return;
66
67
  }
67
68
 
68
- var distinctId = this.getDistinctId();
69
+ var distinctId = this.getMpProperty('distinct_id');
70
+ var deviceId = this.getMpProperty('$device_id');
69
71
  logger.log('Fetching flags for distinct ID: ' + distinctId);
70
72
  var reqParams = {
71
- 'context': _.extend({'distinct_id': distinctId}, this.getConfig(CONFIG_CONTEXT))
73
+ 'context': _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT))
72
74
  };
73
- this.fetchPromise = window['fetch'](this.getMpConfig('api_host') + '/' + this.getMpConfig('api_routes')['flags'], {
75
+ this._fetchInProgressStartTime = Date.now();
76
+ this.fetchPromise = window['fetch'](this.getFullApiRoute(), {
74
77
  'method': 'POST',
75
78
  'headers': {
76
79
  'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
@@ -78,6 +81,7 @@ FeatureFlagManager.prototype.fetchFlags = function() {
78
81
  },
79
82
  'body': JSON.stringify(reqParams)
80
83
  }).then(function(response) {
84
+ this.markFetchComplete();
81
85
  return response.json().then(function(responseBody) {
82
86
  var responseFlags = responseBody['flags'];
83
87
  if (!responseFlags) {
@@ -92,9 +96,24 @@ FeatureFlagManager.prototype.fetchFlags = function() {
92
96
  });
93
97
  this.flags = flags;
94
98
  }.bind(this)).catch(function(error) {
99
+ this.markFetchComplete();
95
100
  logger.error(error);
96
- });
97
- }.bind(this)).catch(function() {});
101
+ }.bind(this));
102
+ }.bind(this)).catch(function(error) {
103
+ this.markFetchComplete();
104
+ logger.error(error);
105
+ }.bind(this));
106
+ };
107
+
108
+ FeatureFlagManager.prototype.markFetchComplete = function() {
109
+ if (!this._fetchInProgressStartTime) {
110
+ logger.error('Fetch in progress started time not set, cannot mark fetch complete');
111
+ return;
112
+ }
113
+ this._fetchStartTime = this._fetchInProgressStartTime;
114
+ this._fetchCompleteTime = Date.now();
115
+ this._fetchLatency = this._fetchCompleteTime - this._fetchStartTime;
116
+ this._fetchInProgressStartTime = null;
98
117
  };
99
118
 
100
119
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
@@ -173,7 +192,10 @@ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature)
173
192
  this.track('$experiment_started', {
174
193
  'Experiment name': featureName,
175
194
  'Variant name': feature['key'],
176
- '$experiment_type': 'feature_flag'
195
+ '$experiment_type': 'feature_flag',
196
+ 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
197
+ 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
198
+ 'Variant fetch latency (ms)': this._fetchLatency
177
199
  });
178
200
  };
179
201
 
@@ -369,8 +369,11 @@ MixpanelLib.prototype._init = function(token, config, name) {
369
369
  }
370
370
 
371
371
  this.flags = new FeatureFlagManager({
372
+ getFullApiRoute: _.bind(function() {
373
+ return this.get_api_host('flags') + '/' + this.get_config('api_routes')['flags'];
374
+ }, this),
372
375
  getConfigFunc: _.bind(this.get_config, this),
373
- getDistinctIdFunc: _.bind(this.get_distinct_id, this),
376
+ getPropertyFunc: _.bind(this.get_property, this),
374
377
  trackingFunc: _.bind(this.track, this)
375
378
  });
376
379
  this.flags.init();
@@ -856,11 +859,10 @@ MixpanelLib.prototype.are_batchers_initialized = function() {
856
859
 
857
860
  MixpanelLib.prototype.get_batcher_configs = function() {
858
861
  var queue_prefix = '__mpq_' + this.get_config('token');
859
- var api_routes = this.get_config('api_routes');
860
862
  this._batcher_configs = this._batcher_configs || {
861
- events: {type: 'events', endpoint: '/' + api_routes['track'], queue_key: queue_prefix + '_ev'},
862
- people: {type: 'people', endpoint: '/' + api_routes['engage'], queue_key: queue_prefix + '_pp'},
863
- groups: {type: 'groups', endpoint: '/' + api_routes['groups'], queue_key: queue_prefix + '_gr'}
863
+ events: {type: 'events', api_name: 'track', queue_key: queue_prefix + '_ev'},
864
+ people: {type: 'people', api_name: 'engage', queue_key: queue_prefix + '_pp'},
865
+ groups: {type: 'groups', api_name: 'groups', queue_key: queue_prefix + '_gr'}
864
866
  };
865
867
  return this._batcher_configs;
866
868
  };
@@ -874,8 +876,9 @@ MixpanelLib.prototype.init_batchers = function() {
874
876
  libConfig: this['config'],
875
877
  errorReporter: this.get_config('error_reporter'),
876
878
  sendRequestFunc: _.bind(function(data, options, cb) {
879
+ var api_routes = this.get_config('api_routes');
877
880
  this._send_request(
878
- this.get_config('api_host') + attrs.endpoint,
881
+ this.get_api_host(attrs.api_name) + '/' + api_routes[attrs.api_name],
879
882
  this._encode_data_for_request(data),
880
883
  options,
881
884
  this._prepare_callback(cb, data)
@@ -1603,31 +1606,15 @@ MixpanelLib.prototype.identify = function(
1603
1606
  * Useful for clearing data when a user logs out.
1604
1607
  */
1605
1608
  MixpanelLib.prototype.reset = function() {
1606
- var self = this;
1607
-
1608
- var reset = function () {
1609
- self['persistence'].clear();
1610
- self._flags.identify_called = false;
1611
- var uuid = _.UUID();
1612
- self.register_once({
1613
- 'distinct_id': DEVICE_ID_PREFIX + uuid,
1614
- '$device_id': uuid
1615
- }, '');
1616
- };
1617
-
1618
- if (self._recorder) {
1619
- self.stop_session_recording()
1620
- .then(function () {
1621
- reset();
1622
- self._check_and_start_session_recording();
1623
- })
1624
- .catch(_.bind(function (err) {
1625
- reset();
1626
- this.report_error('Error restarting recording session', err);
1627
- }, this));
1628
- } else {
1629
- reset();
1630
- }
1609
+ this.stop_session_recording();
1610
+ this['persistence'].clear();
1611
+ this._flags.identify_called = false;
1612
+ var uuid = _.UUID();
1613
+ this.register_once({
1614
+ 'distinct_id': DEVICE_ID_PREFIX + uuid,
1615
+ '$device_id': uuid
1616
+ }, '');
1617
+ this._check_and_start_session_recording();
1631
1618
  };
1632
1619
 
1633
1620
  /**