mixpanel-browser 2.41.0 → 2.42.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.
@@ -0,0 +1,25 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ build:
11
+
12
+ runs-on: ubuntu-latest
13
+
14
+ strategy:
15
+ matrix:
16
+ node-version: [12.x, 14.x]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v2
20
+ - name: Use Node.js ${{ matrix.node-version }}
21
+ uses: actions/setup-node@v1
22
+ with:
23
+ node-version: ${{ matrix.node-version }}
24
+ - run: npm ci
25
+ - run: npm test
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ **2.42.0** (9 Nov 2021)
2
+ - Make `batch_requests` default-on for all remaining projects
3
+ - Replace `unload` event listener with modern alternatives (thanks @JoaoGomesTW)
4
+ - Don't retry requests blocked by client (adblockers)
5
+ - Retry with backoff after 429
6
+
1
7
  **2.41.0** (28 Jan 2021)
2
8
  - Remove all code related to Autotrack feature
3
9
 
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
+
1
2
  # Mixpanel JavaScript Library
2
- [![Build Status](https://travis-ci.org/mixpanel/mixpanel-js.svg?branch=master)](https://travis-ci.org/mixpanel/mixpanel-js)
3
+ ![Build Status](https://github.com/mixpanel/mixpanel-js/actions/workflows/tests.yml/badge.svg)
4
+ [![](http://img.badgesize.io/https://unpkg.com/mixpanel-browser/dist/mixpanel.min.js?compression=gzip)](https://unpkg.com/mixpanel-browser/dist/mixpanel.min.js)
3
5
 
4
6
  The Mixpanel JavaScript Library is a set of methods attached to a global `mixpanel` object
5
7
  intended to be used by websites wishing to send data to Mixpanel projects. A full reference
@@ -73,4 +75,4 @@ Mixpanel production releases are tested against a large matrix of browsers and o
73
75
  - Publish to readme.io via the [rdme](https://www.npmjs.com/package/rdme) util: `RDME_API_KEY=<API_KEY> npm run dox-publish`
74
76
 
75
77
  ## Thanks
76
- For patches and support: @bohanyang, @dehau, @drubin, @D1plo1d, @feychenie, @mogstad, @pfhayes, @sandorfr, @stefansedich, @gfx, @pkaminski, @austince, @danielbaker, @mkdai, @wolever, @dpraul, @chriszamierowski
78
+ For patches and support: @bohanyang, @dehau, @drubin, @D1plo1d, @feychenie, @mogstad, @pfhayes, @sandorfr, @stefansedich, @gfx, @pkaminski, @austince, @danielbaker, @mkdai, @wolever, @dpraul, @chriszamierowski, @JoaoGomesTW
@@ -2,7 +2,7 @@ define(function () { 'use strict';
2
2
 
3
3
  var Config = {
4
4
  DEBUG: false,
5
- LIB_VERSION: '2.41.0'
5
+ LIB_VERSION: '2.42.0'
6
6
  };
7
7
 
8
8
  // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
@@ -1684,28 +1684,6 @@ define(function () { 'use strict';
1684
1684
  return maxlen ? guid.substring(0, maxlen) : guid;
1685
1685
  };
1686
1686
 
1687
- /**
1688
- * Check deterministically whether to include or exclude from a feature rollout/test based on the
1689
- * given string and the desired percentage to include.
1690
- * @param {String} str - string to run the check against (for instance a project's token)
1691
- * @param {String} feature - name of feature (for inclusion in hash, to ensure different results
1692
- * for different features)
1693
- * @param {Number} percent_allowed - percentage chance that a given string will be included
1694
- * @returns {Boolean} whether the given string should be included
1695
- */
1696
- var determine_eligibility = _.safewrap(function(str, feature, percent_allowed) {
1697
- str = str + feature;
1698
-
1699
- // Bernstein's hash: http://www.cse.yorku.ca/~oz/hash.html#djb2
1700
- var hash = 5381;
1701
- for (var i = 0; i < str.length; i++) {
1702
- hash = ((hash << 5) + hash) + str.charCodeAt(i);
1703
- hash = hash & hash;
1704
- }
1705
- var dart = (hash >>> 0) % 100;
1706
- return dart < percent_allowed;
1707
- });
1708
-
1709
1687
  // naive way to extract domain name (example.com) from full hostname (my.sub.example.com)
1710
1688
  var SIMPLE_DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]*\.[a-z]+$/i;
1711
1689
  // this next one attempts to account for some ccSLDs, e.g. extracting oxford.ac.uk from www.oxford.ac.uk
@@ -2444,9 +2422,9 @@ define(function () { 'use strict';
2444
2422
  } else if (
2445
2423
  _.isObject(res) &&
2446
2424
  res.xhr_req &&
2447
- (res.xhr_req['status'] >= 500 || res.xhr_req['status'] <= 0)
2425
+ (res.xhr_req['status'] >= 500 || res.xhr_req['status'] === 429 || res.error === 'timeout')
2448
2426
  ) {
2449
- // network or API error, retry
2427
+ // network or API error, or 429 Too Many Requests, retry
2450
2428
  var retryMS = this.flushInterval * 2;
2451
2429
  var headers = res.xhr_req['responseHeaders'];
2452
2430
  if (headers) {
@@ -3016,9 +2994,13 @@ define(function () { 'use strict';
3016
2994
  * Permanently delete a group.
3017
2995
  *
3018
2996
  * ### Usage:
2997
+ *
3019
2998
  * mixpanel.get_group('company', 'mixpanel').delete();
2999
+ *
3000
+ * @param {Function} [callback] If provided, the callback will be called after the tracking event
3020
3001
  */
3021
3002
  MixpanelGroup.prototype['delete'] = addOptOutCheckMixpanelGroup(function(callback) {
3003
+ // bracket notation above prevents a minification error related to reserved words
3022
3004
  var data = this.delete_action();
3023
3005
  return this._send_request(data, callback);
3024
3006
  });
@@ -5920,7 +5902,7 @@ define(function () { 'use strict';
5920
5902
  'inapp_protocol': '//',
5921
5903
  'inapp_link_new_window': false,
5922
5904
  'ignore_dnt': false,
5923
- 'batch_requests': false, // for now
5905
+ 'batch_requests': true,
5924
5906
  'batch_size': 50,
5925
5907
  'batch_flush_interval_ms': 5000,
5926
5908
  'batch_request_timeout_ms': 90000,
@@ -6039,17 +6021,7 @@ define(function () { 'use strict';
6039
6021
  this['config'] = {};
6040
6022
  this['_triggered_notifs'] = [];
6041
6023
 
6042
- // rollout: enable batch_requests by default for 60% of projects
6043
- // (only if they have not specified a value in their init config
6044
- // and they aren't using a custom API host)
6045
- var variable_features = {};
6046
- var api_host = config['api_host'];
6047
- var is_custom_api = !!api_host && !api_host.match(/\.mixpanel\.com$/);
6048
- if (!('batch_requests' in config) && !is_custom_api && determine_eligibility(token, 'batch', 60)) {
6049
- variable_features['batch_requests'] = true;
6050
- }
6051
-
6052
- this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, {
6024
+ this.set_config(_.extend({}, DEFAULT_CONFIG, config, {
6053
6025
  'name': name,
6054
6026
  'token': token,
6055
6027
  'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
@@ -6075,15 +6047,32 @@ define(function () { 'use strict';
6075
6047
  } else {
6076
6048
  this.init_batchers();
6077
6049
  if (sendBeacon && window$1.addEventListener) {
6078
- window$1.addEventListener('unload', _.bind(function() {
6079
- // Before page closes, attempt to flush any events queued up via navigator.sendBeacon.
6080
- // Since sendBeacon doesn't report success/failure, events will not be removed from
6081
- // the persistent store; if the site is loaded again, the events will be flushed again
6082
- // on startup and deduplicated on the Mixpanel server side.
6050
+ // Before page closes or hides (user tabs away etc), attempt to flush any events
6051
+ // queued up via navigator.sendBeacon. Since sendBeacon doesn't report success/failure,
6052
+ // events will not be removed from the persistent store; if the site is loaded again,
6053
+ // the events will be flushed again on startup and deduplicated on the Mixpanel server
6054
+ // side.
6055
+ // There is no reliable way to capture only page close events, so we lean on the
6056
+ // visibilitychange and pagehide events as recommended at
6057
+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event#usage_notes.
6058
+ // These events fire when the user clicks away from the current page/tab, so will occur
6059
+ // more frequently than page unload, but are the only mechanism currently for capturing
6060
+ // this scenario somewhat reliably.
6061
+ var flush_on_unload = _.bind(function() {
6083
6062
  if (!this.request_batchers.events.stopped) {
6084
6063
  this.request_batchers.events.flush({unloading: true});
6085
6064
  }
6086
- }, this));
6065
+ }, this);
6066
+ window$1.addEventListener('pagehide', function(ev) {
6067
+ if (ev['persisted']) {
6068
+ flush_on_unload();
6069
+ }
6070
+ });
6071
+ window$1.addEventListener('visibilitychange', function() {
6072
+ if (document$1['visibilityState'] === 'hidden') {
6073
+ flush_on_unload();
6074
+ }
6075
+ });
6087
6076
  }
6088
6077
  }
6089
6078
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  var Config = {
4
4
  DEBUG: false,
5
- LIB_VERSION: '2.41.0'
5
+ LIB_VERSION: '2.42.0'
6
6
  };
7
7
 
8
8
  // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
@@ -1684,28 +1684,6 @@ var cheap_guid = function(maxlen) {
1684
1684
  return maxlen ? guid.substring(0, maxlen) : guid;
1685
1685
  };
1686
1686
 
1687
- /**
1688
- * Check deterministically whether to include or exclude from a feature rollout/test based on the
1689
- * given string and the desired percentage to include.
1690
- * @param {String} str - string to run the check against (for instance a project's token)
1691
- * @param {String} feature - name of feature (for inclusion in hash, to ensure different results
1692
- * for different features)
1693
- * @param {Number} percent_allowed - percentage chance that a given string will be included
1694
- * @returns {Boolean} whether the given string should be included
1695
- */
1696
- var determine_eligibility = _.safewrap(function(str, feature, percent_allowed) {
1697
- str = str + feature;
1698
-
1699
- // Bernstein's hash: http://www.cse.yorku.ca/~oz/hash.html#djb2
1700
- var hash = 5381;
1701
- for (var i = 0; i < str.length; i++) {
1702
- hash = ((hash << 5) + hash) + str.charCodeAt(i);
1703
- hash = hash & hash;
1704
- }
1705
- var dart = (hash >>> 0) % 100;
1706
- return dart < percent_allowed;
1707
- });
1708
-
1709
1687
  // naive way to extract domain name (example.com) from full hostname (my.sub.example.com)
1710
1688
  var SIMPLE_DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]*\.[a-z]+$/i;
1711
1689
  // this next one attempts to account for some ccSLDs, e.g. extracting oxford.ac.uk from www.oxford.ac.uk
@@ -2444,9 +2422,9 @@ RequestBatcher.prototype.flush = function(options) {
2444
2422
  } else if (
2445
2423
  _.isObject(res) &&
2446
2424
  res.xhr_req &&
2447
- (res.xhr_req['status'] >= 500 || res.xhr_req['status'] <= 0)
2425
+ (res.xhr_req['status'] >= 500 || res.xhr_req['status'] === 429 || res.error === 'timeout')
2448
2426
  ) {
2449
- // network or API error, retry
2427
+ // network or API error, or 429 Too Many Requests, retry
2450
2428
  var retryMS = this.flushInterval * 2;
2451
2429
  var headers = res.xhr_req['responseHeaders'];
2452
2430
  if (headers) {
@@ -3016,9 +2994,13 @@ MixpanelGroup.prototype.union = addOptOutCheckMixpanelGroup(function(list_name,
3016
2994
  * Permanently delete a group.
3017
2995
  *
3018
2996
  * ### Usage:
2997
+ *
3019
2998
  * mixpanel.get_group('company', 'mixpanel').delete();
2999
+ *
3000
+ * @param {Function} [callback] If provided, the callback will be called after the tracking event
3020
3001
  */
3021
3002
  MixpanelGroup.prototype['delete'] = addOptOutCheckMixpanelGroup(function(callback) {
3003
+ // bracket notation above prevents a minification error related to reserved words
3022
3004
  var data = this.delete_action();
3023
3005
  return this._send_request(data, callback);
3024
3006
  });
@@ -5920,7 +5902,7 @@ var DEFAULT_CONFIG = {
5920
5902
  'inapp_protocol': '//',
5921
5903
  'inapp_link_new_window': false,
5922
5904
  'ignore_dnt': false,
5923
- 'batch_requests': false, // for now
5905
+ 'batch_requests': true,
5924
5906
  'batch_size': 50,
5925
5907
  'batch_flush_interval_ms': 5000,
5926
5908
  'batch_request_timeout_ms': 90000,
@@ -6039,17 +6021,7 @@ MixpanelLib.prototype._init = function(token, config, name) {
6039
6021
  this['config'] = {};
6040
6022
  this['_triggered_notifs'] = [];
6041
6023
 
6042
- // rollout: enable batch_requests by default for 60% of projects
6043
- // (only if they have not specified a value in their init config
6044
- // and they aren't using a custom API host)
6045
- var variable_features = {};
6046
- var api_host = config['api_host'];
6047
- var is_custom_api = !!api_host && !api_host.match(/\.mixpanel\.com$/);
6048
- if (!('batch_requests' in config) && !is_custom_api && determine_eligibility(token, 'batch', 60)) {
6049
- variable_features['batch_requests'] = true;
6050
- }
6051
-
6052
- this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, {
6024
+ this.set_config(_.extend({}, DEFAULT_CONFIG, config, {
6053
6025
  'name': name,
6054
6026
  'token': token,
6055
6027
  'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
@@ -6075,15 +6047,32 @@ MixpanelLib.prototype._init = function(token, config, name) {
6075
6047
  } else {
6076
6048
  this.init_batchers();
6077
6049
  if (sendBeacon && window$1.addEventListener) {
6078
- window$1.addEventListener('unload', _.bind(function() {
6079
- // Before page closes, attempt to flush any events queued up via navigator.sendBeacon.
6080
- // Since sendBeacon doesn't report success/failure, events will not be removed from
6081
- // the persistent store; if the site is loaded again, the events will be flushed again
6082
- // on startup and deduplicated on the Mixpanel server side.
6050
+ // Before page closes or hides (user tabs away etc), attempt to flush any events
6051
+ // queued up via navigator.sendBeacon. Since sendBeacon doesn't report success/failure,
6052
+ // events will not be removed from the persistent store; if the site is loaded again,
6053
+ // the events will be flushed again on startup and deduplicated on the Mixpanel server
6054
+ // side.
6055
+ // There is no reliable way to capture only page close events, so we lean on the
6056
+ // visibilitychange and pagehide events as recommended at
6057
+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event#usage_notes.
6058
+ // These events fire when the user clicks away from the current page/tab, so will occur
6059
+ // more frequently than page unload, but are the only mechanism currently for capturing
6060
+ // this scenario somewhat reliably.
6061
+ var flush_on_unload = _.bind(function() {
6083
6062
  if (!this.request_batchers.events.stopped) {
6084
6063
  this.request_batchers.events.flush({unloading: true});
6085
6064
  }
6086
- }, this));
6065
+ }, this);
6066
+ window$1.addEventListener('pagehide', function(ev) {
6067
+ if (ev['persisted']) {
6068
+ flush_on_unload();
6069
+ }
6070
+ });
6071
+ window$1.addEventListener('visibilitychange', function() {
6072
+ if (document$1['visibilityState'] === 'hidden') {
6073
+ flush_on_unload();
6074
+ }
6075
+ });
6087
6076
  }
6088
6077
  }
6089
6078
  }
@@ -3,7 +3,7 @@
3
3
 
4
4
  var Config = {
5
5
  DEBUG: false,
6
- LIB_VERSION: '2.41.0'
6
+ LIB_VERSION: '2.42.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
@@ -1685,28 +1685,6 @@
1685
1685
  return maxlen ? guid.substring(0, maxlen) : guid;
1686
1686
  };
1687
1687
 
1688
- /**
1689
- * Check deterministically whether to include or exclude from a feature rollout/test based on the
1690
- * given string and the desired percentage to include.
1691
- * @param {String} str - string to run the check against (for instance a project's token)
1692
- * @param {String} feature - name of feature (for inclusion in hash, to ensure different results
1693
- * for different features)
1694
- * @param {Number} percent_allowed - percentage chance that a given string will be included
1695
- * @returns {Boolean} whether the given string should be included
1696
- */
1697
- var determine_eligibility = _.safewrap(function(str, feature, percent_allowed) {
1698
- str = str + feature;
1699
-
1700
- // Bernstein's hash: http://www.cse.yorku.ca/~oz/hash.html#djb2
1701
- var hash = 5381;
1702
- for (var i = 0; i < str.length; i++) {
1703
- hash = ((hash << 5) + hash) + str.charCodeAt(i);
1704
- hash = hash & hash;
1705
- }
1706
- var dart = (hash >>> 0) % 100;
1707
- return dart < percent_allowed;
1708
- });
1709
-
1710
1688
  // naive way to extract domain name (example.com) from full hostname (my.sub.example.com)
1711
1689
  var SIMPLE_DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]*\.[a-z]+$/i;
1712
1690
  // this next one attempts to account for some ccSLDs, e.g. extracting oxford.ac.uk from www.oxford.ac.uk
@@ -2445,9 +2423,9 @@
2445
2423
  } else if (
2446
2424
  _.isObject(res) &&
2447
2425
  res.xhr_req &&
2448
- (res.xhr_req['status'] >= 500 || res.xhr_req['status'] <= 0)
2426
+ (res.xhr_req['status'] >= 500 || res.xhr_req['status'] === 429 || res.error === 'timeout')
2449
2427
  ) {
2450
- // network or API error, retry
2428
+ // network or API error, or 429 Too Many Requests, retry
2451
2429
  var retryMS = this.flushInterval * 2;
2452
2430
  var headers = res.xhr_req['responseHeaders'];
2453
2431
  if (headers) {
@@ -3017,9 +2995,13 @@
3017
2995
  * Permanently delete a group.
3018
2996
  *
3019
2997
  * ### Usage:
2998
+ *
3020
2999
  * mixpanel.get_group('company', 'mixpanel').delete();
3000
+ *
3001
+ * @param {Function} [callback] If provided, the callback will be called after the tracking event
3021
3002
  */
3022
3003
  MixpanelGroup.prototype['delete'] = addOptOutCheckMixpanelGroup(function(callback) {
3004
+ // bracket notation above prevents a minification error related to reserved words
3023
3005
  var data = this.delete_action();
3024
3006
  return this._send_request(data, callback);
3025
3007
  });
@@ -5921,7 +5903,7 @@
5921
5903
  'inapp_protocol': '//',
5922
5904
  'inapp_link_new_window': false,
5923
5905
  'ignore_dnt': false,
5924
- 'batch_requests': false, // for now
5906
+ 'batch_requests': true,
5925
5907
  'batch_size': 50,
5926
5908
  'batch_flush_interval_ms': 5000,
5927
5909
  'batch_request_timeout_ms': 90000,
@@ -6040,17 +6022,7 @@
6040
6022
  this['config'] = {};
6041
6023
  this['_triggered_notifs'] = [];
6042
6024
 
6043
- // rollout: enable batch_requests by default for 60% of projects
6044
- // (only if they have not specified a value in their init config
6045
- // and they aren't using a custom API host)
6046
- var variable_features = {};
6047
- var api_host = config['api_host'];
6048
- var is_custom_api = !!api_host && !api_host.match(/\.mixpanel\.com$/);
6049
- if (!('batch_requests' in config) && !is_custom_api && determine_eligibility(token, 'batch', 60)) {
6050
- variable_features['batch_requests'] = true;
6051
- }
6052
-
6053
- this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, {
6025
+ this.set_config(_.extend({}, DEFAULT_CONFIG, config, {
6054
6026
  'name': name,
6055
6027
  'token': token,
6056
6028
  'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
@@ -6076,15 +6048,32 @@
6076
6048
  } else {
6077
6049
  this.init_batchers();
6078
6050
  if (sendBeacon && window$1.addEventListener) {
6079
- window$1.addEventListener('unload', _.bind(function() {
6080
- // Before page closes, attempt to flush any events queued up via navigator.sendBeacon.
6081
- // Since sendBeacon doesn't report success/failure, events will not be removed from
6082
- // the persistent store; if the site is loaded again, the events will be flushed again
6083
- // on startup and deduplicated on the Mixpanel server side.
6051
+ // Before page closes or hides (user tabs away etc), attempt to flush any events
6052
+ // queued up via navigator.sendBeacon. Since sendBeacon doesn't report success/failure,
6053
+ // events will not be removed from the persistent store; if the site is loaded again,
6054
+ // the events will be flushed again on startup and deduplicated on the Mixpanel server
6055
+ // side.
6056
+ // There is no reliable way to capture only page close events, so we lean on the
6057
+ // visibilitychange and pagehide events as recommended at
6058
+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event#usage_notes.
6059
+ // These events fire when the user clicks away from the current page/tab, so will occur
6060
+ // more frequently than page unload, but are the only mechanism currently for capturing
6061
+ // this scenario somewhat reliably.
6062
+ var flush_on_unload = _.bind(function() {
6084
6063
  if (!this.request_batchers.events.stopped) {
6085
6064
  this.request_batchers.events.flush({unloading: true});
6086
6065
  }
6087
- }, this));
6066
+ }, this);
6067
+ window$1.addEventListener('pagehide', function(ev) {
6068
+ if (ev['persisted']) {
6069
+ flush_on_unload();
6070
+ }
6071
+ });
6072
+ window$1.addEventListener('visibilitychange', function() {
6073
+ if (document$1['visibilityState'] === 'hidden') {
6074
+ flush_on_unload();
6075
+ }
6076
+ });
6088
6077
  }
6089
6078
  }
6090
6079
  }