mixpanel-browser 2.45.0 → 2.47.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.
@@ -3,7 +3,7 @@
3
3
 
4
4
  var Config = {
5
5
  DEBUG: false,
6
- LIB_VERSION: '2.45.0'
6
+ LIB_VERSION: '2.47.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
@@ -833,20 +833,24 @@
833
833
 
834
834
  _.UUID = (function() {
835
835
 
836
- // Time/ticks information
837
- // 1*new Date() is a cross browser version of Date.now()
836
+ // Time-based entropy
838
837
  var T = function() {
839
- var d = 1 * new Date(),
840
- i = 0;
841
-
842
- // this while loop figures how many browser ticks go by
843
- // before 1*new Date() returns a new number, ie the amount
844
- // of ticks that go by per millisecond
845
- while (d == 1 * new Date()) {
846
- i++;
838
+ var time = 1 * new Date(); // cross-browser version of Date.now()
839
+ var ticks;
840
+ if (window$1.performance && window$1.performance.now) {
841
+ ticks = window$1.performance.now();
842
+ } else {
843
+ // fall back to busy loop
844
+ ticks = 0;
845
+
846
+ // this while loop figures how many browser ticks go by
847
+ // before 1*new Date() returns a new number, ie the amount
848
+ // of ticks that go by per millisecond
849
+ while (time == 1 * new Date()) {
850
+ ticks++;
851
+ }
847
852
  }
848
-
849
- return d.toString(16) + i.toString(16);
853
+ return time.toString(16) + Math.floor(ticks).toString(16);
850
854
  };
851
855
 
852
856
  // Math.Random entropy
@@ -1413,21 +1417,42 @@
1413
1417
  };
1414
1418
  })();
1415
1419
 
1420
+ var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
1421
+ var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'ttclid', 'twclid', 'wbraid'];
1422
+
1416
1423
  _.info = {
1417
- campaignParams: function() {
1418
- var campaign_keywords = 'utm_source utm_medium utm_campaign utm_content utm_term'.split(' '),
1419
- kw = '',
1424
+ campaignParams: function(default_value) {
1425
+ var kw = '',
1420
1426
  params = {};
1421
- _.each(campaign_keywords, function(kwkey) {
1427
+ _.each(CAMPAIGN_KEYWORDS, function(kwkey) {
1422
1428
  kw = _.getQueryParam(document$1.URL, kwkey);
1423
1429
  if (kw.length) {
1424
1430
  params[kwkey] = kw;
1431
+ } else if (default_value !== undefined) {
1432
+ params[kwkey] = default_value;
1433
+ }
1434
+ });
1435
+
1436
+ return params;
1437
+ },
1438
+
1439
+ clickParams: function() {
1440
+ var id = '',
1441
+ params = {};
1442
+ _.each(CLICK_IDS, function(idkey) {
1443
+ id = _.getQueryParam(document$1.URL, idkey);
1444
+ if (id.length) {
1445
+ params[idkey] = id;
1425
1446
  }
1426
1447
  });
1427
1448
 
1428
1449
  return params;
1429
1450
  },
1430
1451
 
1452
+ marketingParams: function() {
1453
+ return _.extend(_.info.campaignParams(), _.info.clickParams());
1454
+ },
1455
+
1431
1456
  searchEngine: function(referrer) {
1432
1457
  if (referrer.search('https?://(.*)google.([^/?]*)') === 0) {
1433
1458
  return 'google';
@@ -1624,12 +1649,13 @@
1624
1649
  });
1625
1650
  },
1626
1651
 
1627
- pageviewInfo: function(page) {
1652
+ mpPageViewProperties: function() {
1628
1653
  return _.strip_empty_properties({
1629
- 'mp_page': page,
1630
- 'mp_referrer': document$1.referrer,
1631
- 'mp_browser': _.info.browser(userAgent, navigator.vendor, windowOpera),
1632
- 'mp_platform': _.info.os()
1654
+ 'current_page_title': document$1.title,
1655
+ 'current_domain': window$1.location.hostname,
1656
+ 'current_url_path': window$1.location.pathname,
1657
+ 'current_url_protocol': window$1.location.protocol,
1658
+ 'current_url_search': window$1.location.search
1633
1659
  });
1634
1660
  }
1635
1661
  };
@@ -2296,6 +2322,9 @@
2296
2322
 
2297
2323
  this.stopped = !this.libConfig['batch_autostart'];
2298
2324
  this.consecutiveRemovalFailures = 0;
2325
+
2326
+ // extra client-side dedupe
2327
+ this.itemIdsSentSuccessfully = {};
2299
2328
  };
2300
2329
 
2301
2330
  /**
@@ -2388,7 +2417,34 @@
2388
2417
  payload = this.beforeSendHook(payload);
2389
2418
  }
2390
2419
  if (payload) {
2391
- dataForRequest.push(payload);
2420
+ // mp_sent_by_lib_version prop captures which lib version actually
2421
+ // sends each event (regardless of which version originally queued
2422
+ // it for sending)
2423
+ if (payload['event'] && payload['properties']) {
2424
+ payload['properties'] = _.extend(
2425
+ {},
2426
+ payload['properties'],
2427
+ {'mp_sent_by_lib_version': Config.LIB_VERSION}
2428
+ );
2429
+ }
2430
+ var addPayload = true;
2431
+ var itemId = item['id'];
2432
+ if (itemId) {
2433
+ if ((this.itemIdsSentSuccessfully[itemId] || 0) > 5) {
2434
+ this.reportError('[dupe] item ID sent too many times, not sending', {
2435
+ item: item,
2436
+ batchSize: batch.length,
2437
+ timesSent: this.itemIdsSentSuccessfully[itemId]
2438
+ });
2439
+ addPayload = false;
2440
+ }
2441
+ } else {
2442
+ this.reportError('[dupe] found item with no ID', {item: item});
2443
+ }
2444
+
2445
+ if (addPayload) {
2446
+ dataForRequest.push(payload);
2447
+ }
2392
2448
  }
2393
2449
  transformedItems[item['id']] = payload;
2394
2450
  }, this);
@@ -2471,6 +2527,24 @@
2471
2527
  }
2472
2528
  }, this)
2473
2529
  );
2530
+
2531
+ // client-side dedupe
2532
+ _.each(batch, _.bind(function(item) {
2533
+ var itemId = item['id'];
2534
+ if (itemId) {
2535
+ this.itemIdsSentSuccessfully[itemId] = this.itemIdsSentSuccessfully[itemId] || 0;
2536
+ this.itemIdsSentSuccessfully[itemId]++;
2537
+ if (this.itemIdsSentSuccessfully[itemId] > 5) {
2538
+ this.reportError('[dupe] item ID sent too many times', {
2539
+ item: item,
2540
+ batchSize: batch.length,
2541
+ timesSent: this.itemIdsSentSuccessfully[itemId]
2542
+ });
2543
+ }
2544
+ } else {
2545
+ this.reportError('[dupe] found item with no ID while removing', {item: item});
2546
+ }
2547
+ }, this));
2474
2548
  }
2475
2549
 
2476
2550
  } catch(err) {
@@ -3316,24 +3390,25 @@
3316
3390
  });
3317
3391
 
3318
3392
  /*
3319
- * Record that you have charged the current user a certain amount
3320
- * of money. Charges recorded with track_charge() will appear in the
3321
- * Mixpanel revenue report.
3322
- *
3323
- * ### Usage:
3324
- *
3325
- * // charge a user $50
3326
- * mixpanel.people.track_charge(50);
3327
- *
3328
- * // charge a user $30.50 on the 2nd of january
3329
- * mixpanel.people.track_charge(30.50, {
3330
- * '$time': new Date('jan 1 2012')
3331
- * });
3332
- *
3333
- * @param {Number} amount The amount of money charged to the current user
3334
- * @param {Object} [properties] An associative array of properties associated with the charge
3335
- * @param {Function} [callback] If provided, the callback will be called when the server responds
3336
- */
3393
+ * Record that you have charged the current user a certain amount
3394
+ * of money. Charges recorded with track_charge() will appear in the
3395
+ * Mixpanel revenue report.
3396
+ *
3397
+ * ### Usage:
3398
+ *
3399
+ * // charge a user $50
3400
+ * mixpanel.people.track_charge(50);
3401
+ *
3402
+ * // charge a user $30.50 on the 2nd of january
3403
+ * mixpanel.people.track_charge(30.50, {
3404
+ * '$time': new Date('jan 1 2012')
3405
+ * });
3406
+ *
3407
+ * @param {Number} amount The amount of money charged to the current user
3408
+ * @param {Object} [properties] An associative array of properties associated with the charge
3409
+ * @param {Function} [callback] If provided, the callback will be called when the server responds
3410
+ * @deprecated
3411
+ */
3337
3412
  MixpanelPeople.prototype.track_charge = addOptOutCheckMixpanelPeople(function(amount, properties, callback) {
3338
3413
  if (!_.isNumber(amount)) {
3339
3414
  amount = parseFloat(amount);
@@ -3349,15 +3424,16 @@
3349
3424
  });
3350
3425
 
3351
3426
  /*
3352
- * Permanently clear all revenue report transactions from the
3353
- * current user's people analytics profile.
3354
- *
3355
- * ### Usage:
3356
- *
3357
- * mixpanel.people.clear_charges();
3358
- *
3359
- * @param {Function} [callback] If provided, the callback will be called after tracking the event.
3360
- */
3427
+ * Permanently clear all revenue report transactions from the
3428
+ * current user's people analytics profile.
3429
+ *
3430
+ * ### Usage:
3431
+ *
3432
+ * mixpanel.people.clear_charges();
3433
+ *
3434
+ * @param {Function} [callback] If provided, the callback will be called after tracking the event.
3435
+ * @deprecated
3436
+ */
3361
3437
  MixpanelPeople.prototype.clear_charges = function(callback) {
3362
3438
  return this.set('$transactions', [], callback);
3363
3439
  };
@@ -3757,13 +3833,6 @@
3757
3833
  }
3758
3834
  };
3759
3835
 
3760
- MixpanelPersistence.prototype.update_campaign_params = function() {
3761
- if (!this.campaign_params_saved) {
3762
- this.register_once(_.info.campaignParams());
3763
- this.campaign_params_saved = true;
3764
- }
3765
- };
3766
-
3767
3836
  MixpanelPersistence.prototype.update_search_keyword = function(referrer) {
3768
3837
  this.register(_.info.searchInfo(referrer));
3769
3838
  };
@@ -4046,6 +4115,7 @@
4046
4115
  /** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel';
4047
4116
  /** @const */ var PAYLOAD_TYPE_BASE64 = 'base64';
4048
4117
  /** @const */ var PAYLOAD_TYPE_JSON = 'json';
4118
+ /** @const */ var DEVICE_ID_PREFIX = '$device:';
4049
4119
 
4050
4120
 
4051
4121
  /*
@@ -4087,6 +4157,9 @@
4087
4157
  'cookie_domain': '',
4088
4158
  'cookie_name': '',
4089
4159
  'loaded': NOOP_FUNC,
4160
+ 'track_marketing': true,
4161
+ 'track_pageview': false,
4162
+ 'skip_first_touch_marketing': false,
4090
4163
  'store_google': true,
4091
4164
  'save_referrer': true,
4092
4165
  'test': false,
@@ -4153,6 +4226,25 @@
4153
4226
  instance['people'] = new MixpanelPeople();
4154
4227
  instance['people']._init(instance);
4155
4228
 
4229
+ if (!instance.get_config('skip_first_touch_marketing')) {
4230
+ // We need null UTM params in the object because
4231
+ // UTM parameters act as a tuple. If any UTM param
4232
+ // is present, then we set all UTM params including
4233
+ // empty ones together
4234
+ var utm_params = _.info.campaignParams(null);
4235
+ var initial_utm_params = {};
4236
+ var has_utm = false;
4237
+ _.each(utm_params, function(utm_value, utm_key) {
4238
+ initial_utm_params['initial_' + utm_key] = utm_value;
4239
+ if (utm_value) {
4240
+ has_utm = true;
4241
+ }
4242
+ });
4243
+ if (has_utm) {
4244
+ instance['people'].set_once(initial_utm_params);
4245
+ }
4246
+ }
4247
+
4156
4248
  // if any instance on the page has debug = true, we set the
4157
4249
  // global debug to be true
4158
4250
  Config.DEBUG = Config.DEBUG || instance.get_config('debug');
@@ -4184,7 +4276,7 @@
4184
4276
  * mixpanel.library_name.track(...);
4185
4277
  *
4186
4278
  * @param {String} token Your Mixpanel API token
4187
- * @param {Object} [config] A dictionary of config options to override. <a href="https://github.com/mixpanel/mixpanel-js/blob/8b2e1f7b/src/mixpanel-core.js#L87-L110">See a list of default config options</a>.
4279
+ * @param {Object} [config] A dictionary of config options to override. <a href="https://github.com/mixpanel/mixpanel-js/blob/v2.46.0/src/mixpanel-core.js#L88-L127">See a list of default config options</a>.
4188
4280
  * @param {String} [name] The name for the new mixpanel instance that you want created
4189
4281
  */
4190
4282
  MixpanelLib.prototype.init = function (token, config, name) {
@@ -4222,7 +4314,7 @@
4222
4314
  // default to JSON payload for standard mixpanel.com API hosts
4223
4315
  if (!('api_payload_format' in config)) {
4224
4316
  var api_host = config['api_host'] || DEFAULT_CONFIG['api_host'];
4225
- if (api_host.match(/\.mixpanel\.com$/)) {
4317
+ if (api_host.match(/\.mixpanel\.com/)) {
4226
4318
  variable_features['api_payload_format'] = PAYLOAD_TYPE_JSON;
4227
4319
  }
4228
4320
  }
@@ -4293,10 +4385,14 @@
4293
4385
  // or the device id if something was already stored
4294
4386
  // in the persitence
4295
4387
  this.register_once({
4296
- 'distinct_id': uuid,
4388
+ 'distinct_id': DEVICE_ID_PREFIX + uuid,
4297
4389
  '$device_id': uuid
4298
4390
  }, '');
4299
4391
  }
4392
+
4393
+ if (this.get_config('track_pageview')) {
4394
+ this.track_pageview();
4395
+ }
4300
4396
  };
4301
4397
 
4302
4398
  // Private methods
@@ -4310,7 +4406,7 @@
4310
4406
  MixpanelLib.prototype._set_default_superprops = function() {
4311
4407
  this['persistence'].update_search_keyword(document$1.referrer);
4312
4408
  if (this.get_config('store_google')) {
4313
- this['persistence'].update_campaign_params();
4409
+ this.register(_.info.campaignParams(), {persistent: false});
4314
4410
  }
4315
4411
  if (this.get_config('save_referrer')) {
4316
4412
  this['persistence'].update_referrer_info(document$1.referrer);
@@ -4792,6 +4888,10 @@
4792
4888
 
4793
4889
  this._set_default_superprops();
4794
4890
 
4891
+ var marketing_properties = this.get_config('track_marketing')
4892
+ ? _.info.marketingParams()
4893
+ : {};
4894
+
4795
4895
  // note: extend writes to the first object, so lets make sure we
4796
4896
  // don't write to the persistence properties object and info
4797
4897
  // properties object by passing in a new object
@@ -4800,6 +4900,7 @@
4800
4900
  properties = _.extend(
4801
4901
  {},
4802
4902
  _.info.properties(),
4903
+ marketing_properties,
4803
4904
  this['persistence'].properties(),
4804
4905
  this.unpersisted_superprops,
4805
4906
  properties
@@ -4960,17 +5061,54 @@
4960
5061
  };
4961
5062
 
4962
5063
  /**
4963
- * Track mp_page_view event. This is now ignored by the server.
5064
+ * Track a default Mixpanel page view event, which includes extra default event properties to
5065
+ * improve page view data. The `config.track_pageview` option for <a href="#mixpanelinit">mixpanel.init()</a>
5066
+ * may be turned on for tracking page loads automatically.
4964
5067
  *
4965
- * @param {String} [page] The url of the page to record. If you don't include this, it defaults to the current url.
4966
- * @deprecated
5068
+ * ### Usage
5069
+ *
5070
+ * // track a default $mp_web_page_view event
5071
+ * mixpanel.track_pageview();
5072
+ *
5073
+ * // track a page view event with additional event properties
5074
+ * mixpanel.track_pageview({'ab_test_variant': 'card-layout-b'});
5075
+ *
5076
+ * // example approach to track page views on different page types as event properties
5077
+ * mixpanel.track_pageview({'page': 'pricing'});
5078
+ * mixpanel.track_pageview({'page': 'homepage'});
5079
+ *
5080
+ * // UNCOMMON: Tracking a page view event with a custom event_name option. NOT expected to be used for
5081
+ * // individual pages on the same site or product. Use cases for custom event_name may be page
5082
+ * // views on different products or internal applications that are considered completely separate
5083
+ * mixpanel.track_pageview({'page': 'customer-search'}, {'event_name': '[internal] Admin Page View'});
5084
+ *
5085
+ * @param {Object} [properties] An optional set of additional properties to send with the page view event
5086
+ * @param {Object} [options] Page view tracking options
5087
+ * @param {String} [options.event_name] - Alternate name for the tracking event
5088
+ * @returns {Boolean|Object} If the tracking request was successfully initiated/queued, an object
5089
+ * with the tracking payload sent to the API server is returned; otherwise false.
4967
5090
  */
4968
- MixpanelLib.prototype.track_pageview = function(page) {
4969
- if (_.isUndefined(page)) {
4970
- page = document$1.location.href;
5091
+ MixpanelLib.prototype.track_pageview = addOptOutCheckMixpanelLib(function(properties, options) {
5092
+ if (typeof properties !== 'object') {
5093
+ properties = {};
4971
5094
  }
4972
- this.track('mp_page_view', _.info.pageviewInfo(page));
4973
- };
5095
+ options = options || {};
5096
+ var event_name = options['event_name'] || '$mp_web_page_view';
5097
+
5098
+ var default_page_properties = _.extend(
5099
+ _.info.mpPageViewProperties(),
5100
+ _.info.campaignParams(),
5101
+ _.info.clickParams()
5102
+ );
5103
+
5104
+ var event_properties = _.extend(
5105
+ {},
5106
+ default_page_properties,
5107
+ properties
5108
+ );
5109
+
5110
+ return this.track(event_name, event_properties);
5111
+ });
4974
5112
 
4975
5113
  /**
4976
5114
  * Track clicks on a set of document elements. Selector must be a
@@ -5219,7 +5357,15 @@
5219
5357
  // _unset_callback:function A callback to be run if and when the People unset queue is flushed
5220
5358
 
5221
5359
  var previous_distinct_id = this.get_distinct_id();
5222
- this.register({'$user_id': new_distinct_id});
5360
+ if (new_distinct_id && previous_distinct_id !== new_distinct_id) {
5361
+ // we allow the following condition if previous distinct_id is same as new_distinct_id
5362
+ // so that you can force flush people updates for anonymous profiles.
5363
+ if (typeof new_distinct_id === 'string' && new_distinct_id.indexOf(DEVICE_ID_PREFIX) === 0) {
5364
+ this.report_error('distinct_id cannot have $device: prefix');
5365
+ return -1;
5366
+ }
5367
+ this.register({'$user_id': new_distinct_id});
5368
+ }
5223
5369
 
5224
5370
  if (!this.get_property('$device_id')) {
5225
5371
  // The persisted distinct id might not actually be a device id at all
@@ -5260,7 +5406,7 @@
5260
5406
  this._flags.identify_called = false;
5261
5407
  var uuid = _.UUID();
5262
5408
  this.register_once({
5263
- 'distinct_id': uuid,
5409
+ 'distinct_id': DEVICE_ID_PREFIX + uuid,
5264
5410
  '$device_id': uuid
5265
5411
  }, '');
5266
5412
  };
@@ -5385,8 +5531,8 @@
5385
5531
  * // batching or retry mechanisms.
5386
5532
  * api_transport: 'XHR'
5387
5533
  *
5388
- * // turn on request-batching/queueing/retry
5389
- * batch_requests: false,
5534
+ * // request-batching/queueing/retry
5535
+ * batch_requests: true,
5390
5536
  *
5391
5537
  * // maximum number of events/updates to send in a single
5392
5538
  * // network request
@@ -5458,10 +5604,20 @@
5458
5604
  * // secure, meaning they will only be transmitted over https
5459
5605
  * secure_cookie: false
5460
5606
  *
5607
+ * // disables enriching user profiles with first touch marketing data
5608
+ * skip_first_touch_marketing: false
5609
+ *
5461
5610
  * // the amount of time track_links will
5462
5611
  * // wait for Mixpanel's servers to respond
5463
5612
  * track_links_timeout: 300
5464
5613
  *
5614
+ * // adds any UTM parameters and click IDs present on the page to any events fired
5615
+ * track_marketing: true
5616
+ *
5617
+ * // enables automatic page view tracking using default page view events through
5618
+ * // the track_pageview() method
5619
+ * track_pageview: false
5620
+ *
5465
5621
  * // if you set upgrade to be true, the library will check for
5466
5622
  * // a cookie from our old js library and import super
5467
5623
  * // properties from it, then the old cookie is deleted