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.
@@ -13948,7 +13948,7 @@
13948
13948
 
13949
13949
  var Config = {
13950
13950
  DEBUG: false,
13951
- LIB_VERSION: '2.65.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();
@@ -17355,8 +17389,8 @@
17355
17389
  retryAfter: response.headers.get('Retry-After')
17356
17390
  });
17357
17391
  }.bind(this);
17358
-
17359
- win['fetch'](this.getConfig('api_host') + '/' + this.getConfig('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
17392
+ var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
17393
+ win['fetch'](apiHost + '/' + this.getConfig('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
17360
17394
  'method': 'POST',
17361
17395
  'headers': {
17362
17396
  'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
@@ -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();
@@ -17573,6 +17597,7 @@
17573
17597
  this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
17574
17598
 
17575
17599
  this.activeRecording = null;
17600
+ this.stopRecordingInProgress = false;
17576
17601
  };
17577
17602
 
17578
17603
  MixpanelRecorder.prototype.startRecording = function(options) {
@@ -17621,19 +17646,26 @@
17621
17646
  };
17622
17647
 
17623
17648
  MixpanelRecorder.prototype.stopRecording = function() {
17624
- var stopPromise = this._stopCurrentRecording(false);
17625
- this.recordingRegistry.clearActiveRecording();
17626
- this.activeRecording = null;
17627
- return stopPromise;
17649
+ // Prevents activeSerializedRecording from being reused when stopping the recording.
17650
+ this.stopRecordingInProgress = true;
17651
+ return this._stopCurrentRecording(false, true).then(function() {
17652
+ return this.recordingRegistry.clearActiveRecording();
17653
+ }.bind(this)).then(function() {
17654
+ this.stopRecordingInProgress = false;
17655
+ }.bind(this));
17628
17656
  };
17629
17657
 
17630
17658
  MixpanelRecorder.prototype.pauseRecording = function() {
17631
17659
  return this._stopCurrentRecording(false);
17632
17660
  };
17633
17661
 
17634
- MixpanelRecorder.prototype._stopCurrentRecording = function(skipFlush) {
17662
+ MixpanelRecorder.prototype._stopCurrentRecording = function(skipFlush, disableActiveRecording) {
17635
17663
  if (this.activeRecording) {
17636
- return this.activeRecording.stopRecording(skipFlush);
17664
+ var stopRecordingPromise = this.activeRecording.stopRecording(skipFlush);
17665
+ if (disableActiveRecording) {
17666
+ this.activeRecording = null;
17667
+ }
17668
+ return stopRecordingPromise;
17637
17669
  }
17638
17670
  return PromisePolyfill.resolve();
17639
17671
  };
@@ -17646,7 +17678,7 @@
17646
17678
 
17647
17679
  return this.recordingRegistry.getActiveRecording()
17648
17680
  .then(function (activeSerializedRecording) {
17649
- if (activeSerializedRecording) {
17681
+ if (activeSerializedRecording && !this.stopRecordingInProgress) {
17650
17682
  return this.startRecording({activeSerializedRecording: activeSerializedRecording});
17651
17683
  } else if (startNewIfInactive) {
17652
17684
  return this.startRecording({shouldStopBatcher: false});
@@ -18549,8 +18581,9 @@
18549
18581
  * @constructor
18550
18582
  */
18551
18583
  var FeatureFlagManager = function(initOptions) {
18584
+ this.getFullApiRoute = initOptions.getFullApiRoute;
18552
18585
  this.getMpConfig = initOptions.getConfigFunc;
18553
- this.getDistinctId = initOptions.getDistinctIdFunc;
18586
+ this.getMpProperty = initOptions.getPropertyFunc;
18554
18587
  this.track = initOptions.trackingFunc;
18555
18588
  };
18556
18589
 
@@ -18599,12 +18632,14 @@
18599
18632
  return;
18600
18633
  }
18601
18634
 
18602
- var distinctId = this.getDistinctId();
18635
+ var distinctId = this.getMpProperty('distinct_id');
18636
+ var deviceId = this.getMpProperty('$device_id');
18603
18637
  logger.log('Fetching flags for distinct ID: ' + distinctId);
18604
18638
  var reqParams = {
18605
- 'context': _.extend({'distinct_id': distinctId}, this.getConfig(CONFIG_CONTEXT))
18639
+ 'context': _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT))
18606
18640
  };
18607
- 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(), {
18608
18643
  'method': 'POST',
18609
18644
  'headers': {
18610
18645
  'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
@@ -18612,6 +18647,7 @@
18612
18647
  },
18613
18648
  'body': JSON.stringify(reqParams)
18614
18649
  }).then(function(response) {
18650
+ this.markFetchComplete();
18615
18651
  return response.json().then(function(responseBody) {
18616
18652
  var responseFlags = responseBody['flags'];
18617
18653
  if (!responseFlags) {
@@ -18626,9 +18662,24 @@
18626
18662
  });
18627
18663
  this.flags = flags;
18628
18664
  }.bind(this)).catch(function(error) {
18665
+ this.markFetchComplete();
18629
18666
  logger.error(error);
18630
- });
18631
- }.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;
18632
18683
  };
18633
18684
 
18634
18685
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
@@ -18707,7 +18758,10 @@
18707
18758
  this.track('$experiment_started', {
18708
18759
  'Experiment name': featureName,
18709
18760
  'Variant name': feature['key'],
18710
- '$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
18711
18765
  });
18712
18766
  };
18713
18767
 
@@ -19148,7 +19202,7 @@
19148
19202
  return this._mixpanel._track_or_batch({
19149
19203
  type: 'groups',
19150
19204
  data: date_encoded_data,
19151
- endpoint: this._get_config('api_host') + '/' + this._get_config('api_routes')['groups'],
19205
+ endpoint: this._mixpanel.get_api_host('groups') + '/' + this._get_config('api_routes')['groups'],
19152
19206
  batcher: this._mixpanel.request_batchers.groups
19153
19207
  }, callback);
19154
19208
  };
@@ -19500,7 +19554,7 @@
19500
19554
  return this._mixpanel._track_or_batch({
19501
19555
  type: 'people',
19502
19556
  data: date_encoded_data,
19503
- endpoint: this._get_config('api_host') + '/' + this._get_config('api_routes')['engage'],
19557
+ endpoint: this._mixpanel.get_api_host('people') + '/' + this._get_config('api_routes')['engage'],
19504
19558
  batcher: this._mixpanel.request_batchers.people
19505
19559
  }, callback);
19506
19560
  };
@@ -20137,6 +20191,7 @@
20137
20191
  */
20138
20192
  var DEFAULT_CONFIG = {
20139
20193
  'api_host': 'https://api-js.mixpanel.com',
20194
+ 'api_hosts': {},
20140
20195
  'api_routes': DEFAULT_API_ROUTES,
20141
20196
  'api_extra_query_params': {},
20142
20197
  'api_method': 'POST',
@@ -20406,8 +20461,11 @@
20406
20461
  }
20407
20462
 
20408
20463
  this.flags = new FeatureFlagManager({
20464
+ getFullApiRoute: _.bind(function() {
20465
+ return this.get_api_host('flags') + '/' + this.get_config('api_routes')['flags'];
20466
+ }, this),
20409
20467
  getConfigFunc: _.bind(this.get_config, this),
20410
- getDistinctIdFunc: _.bind(this.get_distinct_id, this),
20468
+ getPropertyFunc: _.bind(this.get_property, this),
20411
20469
  trackingFunc: _.bind(this.track, this)
20412
20470
  });
20413
20471
  this.flags.init();
@@ -20522,20 +20580,23 @@
20522
20580
 
20523
20581
  MixpanelLib.prototype.stop_session_recording = function () {
20524
20582
  if (this._recorder) {
20525
- this._recorder['stopRecording']();
20583
+ return this._recorder['stopRecording']();
20526
20584
  }
20585
+ return Promise.resolve();
20527
20586
  };
20528
20587
 
20529
20588
  MixpanelLib.prototype.pause_session_recording = function () {
20530
20589
  if (this._recorder) {
20531
- this._recorder['pauseRecording']();
20590
+ return this._recorder['pauseRecording']();
20532
20591
  }
20592
+ return Promise.resolve();
20533
20593
  };
20534
20594
 
20535
20595
  MixpanelLib.prototype.resume_session_recording = function () {
20536
20596
  if (this._recorder) {
20537
- this._recorder['resumeRecording']();
20597
+ return this._recorder['resumeRecording']();
20538
20598
  }
20599
+ return Promise.resolve();
20539
20600
  };
20540
20601
 
20541
20602
  MixpanelLib.prototype.is_recording_heatmap_data = function () {
@@ -20890,11 +20951,10 @@
20890
20951
 
20891
20952
  MixpanelLib.prototype.get_batcher_configs = function() {
20892
20953
  var queue_prefix = '__mpq_' + this.get_config('token');
20893
- var api_routes = this.get_config('api_routes');
20894
20954
  this._batcher_configs = this._batcher_configs || {
20895
- events: {type: 'events', endpoint: '/' + api_routes['track'], queue_key: queue_prefix + '_ev'},
20896
- people: {type: 'people', endpoint: '/' + api_routes['engage'], queue_key: queue_prefix + '_pp'},
20897
- 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'}
20898
20958
  };
20899
20959
  return this._batcher_configs;
20900
20960
  };
@@ -20908,8 +20968,9 @@
20908
20968
  libConfig: this['config'],
20909
20969
  errorReporter: this.get_config('error_reporter'),
20910
20970
  sendRequestFunc: _.bind(function(data, options, cb) {
20971
+ var api_routes = this.get_config('api_routes');
20911
20972
  this._send_request(
20912
- this.get_config('api_host') + attrs.endpoint,
20973
+ this.get_api_host(attrs.api_name) + '/' + api_routes[attrs.api_name],
20913
20974
  this._encode_data_for_request(data),
20914
20975
  options,
20915
20976
  this._prepare_callback(cb, data)
@@ -21135,7 +21196,7 @@
21135
21196
  var ret = this._track_or_batch({
21136
21197
  type: 'events',
21137
21198
  data: data,
21138
- endpoint: this.get_config('api_host') + '/' + this.get_config('api_routes')['track'],
21199
+ endpoint: this.get_api_host('events') + '/' + this.get_config('api_routes')['track'],
21139
21200
  batcher: this.request_batchers.events,
21140
21201
  should_send_immediately: should_send_immediately,
21141
21202
  send_request_options: options
@@ -21637,6 +21698,7 @@
21637
21698
  * Useful for clearing data when a user logs out.
21638
21699
  */
21639
21700
  MixpanelLib.prototype.reset = function() {
21701
+ this.stop_session_recording();
21640
21702
  this['persistence'].clear();
21641
21703
  this._flags.identify_called = false;
21642
21704
  var uuid = _.UUID();
@@ -21644,7 +21706,6 @@
21644
21706
  'distinct_id': DEVICE_ID_PREFIX + uuid,
21645
21707
  '$device_id': uuid
21646
21708
  }, '');
21647
- this.stop_session_recording();
21648
21709
  this._check_and_start_session_recording();
21649
21710
  };
21650
21711
 
@@ -21956,6 +22017,16 @@
21956
22017
  return this['persistence'].load_prop([property_name]);
21957
22018
  };
21958
22019
 
22020
+ /**
22021
+ * Get the API host for a specific endpoint type, falling back to the default api_host if not specified
22022
+ *
22023
+ * @param {String} endpoint_type The type of endpoint (e.g., "events", "people", "groups")
22024
+ * @returns {String} The API host to use for this endpoint
22025
+ */
22026
+ MixpanelLib.prototype.get_api_host = function(endpoint_type) {
22027
+ return this.get_config('api_hosts')[endpoint_type] || this.get_config('api_host');
22028
+ };
22029
+
21959
22030
  MixpanelLib.prototype.toString = function() {
21960
22031
  var name = this.get_config('name');
21961
22032
  if (name !== PRIMARY_INSTANCE_NAME) {
@@ -22251,6 +22322,7 @@
22251
22322
  MixpanelLib.prototype['name_tag'] = MixpanelLib.prototype.name_tag;
22252
22323
  MixpanelLib.prototype['set_config'] = MixpanelLib.prototype.set_config;
22253
22324
  MixpanelLib.prototype['get_config'] = MixpanelLib.prototype.get_config;
22325
+ MixpanelLib.prototype['get_api_host'] = MixpanelLib.prototype.get_api_host;
22254
22326
  MixpanelLib.prototype['get_property'] = MixpanelLib.prototype.get_property;
22255
22327
  MixpanelLib.prototype['get_distinct_id'] = MixpanelLib.prototype.get_distinct_id;
22256
22328
  MixpanelLib.prototype['toString'] = MixpanelLib.prototype.toString;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixpanel-browser",
3
- "version": "2.65.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",
@@ -22,6 +22,7 @@
22
22
  "unit-test": "BABEL_ENV=test mocha --require babel-core/register tests/unit/*.js",
23
23
  "validate": "npm ls"
24
24
  },
25
+ "types": "./src/index.d.ts",
25
26
  "repository": {
26
27
  "type": "git",
27
28
  "url": "https://github.com/mixpanel/mixpanel-js.git"
package/src/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  var Config = {
2
2
  DEBUG: false,
3
- LIB_VERSION: '2.65.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