mixpanel-browser 2.43.0 → 2.45.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.
- package/CHANGELOG.md +6 -0
- package/dist/mixpanel-jslib-snippet.min.js +3 -3
- package/dist/mixpanel-jslib-snippet.min.test.js +3 -3
- package/dist/mixpanel.amd.js +908 -2782
- package/dist/mixpanel.cjs.js +908 -2782
- package/dist/mixpanel.globals.js +908 -2782
- package/dist/mixpanel.min.js +100 -150
- package/dist/mixpanel.umd.js +908 -2782
- package/mixpanel-jslib-snippet.js +2 -2
- package/package.json +1 -1
- package/src/config.js +1 -1
- package/src/mixpanel-core.js +31 -106
- package/src/mixpanel-persistence.js +0 -21
- package/src/request-batcher.js +45 -8
- package/src/request-queue.js +55 -18
- package/src/utils.js +0 -48
- package/src/mixpanel-notification.js +0 -1309
- package/src/property-filters.js +0 -508
|
@@ -68,8 +68,8 @@ var MIXPANEL_LIB_URL = '//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js';
|
|
|
68
68
|
|
|
69
69
|
function _set_and_defer_chained(fn_name) {
|
|
70
70
|
mock_group[fn_name] = function() {
|
|
71
|
-
call2_args = arguments;
|
|
72
|
-
call2 = [fn_name].concat(Array.prototype.slice.call(call2_args, 0));
|
|
71
|
+
var call2_args = arguments;
|
|
72
|
+
var call2 = [fn_name].concat(Array.prototype.slice.call(call2_args, 0));
|
|
73
73
|
target.push([call1, call2]);
|
|
74
74
|
};
|
|
75
75
|
}
|
package/package.json
CHANGED
package/src/config.js
CHANGED
package/src/mixpanel-core.js
CHANGED
|
@@ -4,7 +4,6 @@ import { _, console, userAgent, window, document, navigator, slice } from './uti
|
|
|
4
4
|
import { FormTracker, LinkTracker } from './dom-trackers';
|
|
5
5
|
import { RequestBatcher } from './request-batcher';
|
|
6
6
|
import { MixpanelGroup } from './mixpanel-group';
|
|
7
|
-
import { MixpanelNotification } from './mixpanel-notification';
|
|
8
7
|
import { MixpanelPeople } from './mixpanel-people';
|
|
9
8
|
import {
|
|
10
9
|
MixpanelPersistence,
|
|
@@ -93,6 +92,7 @@ var DEFAULT_CONFIG = {
|
|
|
93
92
|
'cdn': 'https://cdn.mxpnl.com',
|
|
94
93
|
'cross_site_cookie': false,
|
|
95
94
|
'cross_subdomain_cookie': true,
|
|
95
|
+
'error_reporter': NOOP_FUNC,
|
|
96
96
|
'persistence': 'cookie',
|
|
97
97
|
'persistence_name': '',
|
|
98
98
|
'cookie_domain': '',
|
|
@@ -117,8 +117,6 @@ var DEFAULT_CONFIG = {
|
|
|
117
117
|
'opt_out_tracking_cookie_prefix': null,
|
|
118
118
|
'property_blacklist': [],
|
|
119
119
|
'xhr_headers': {}, // { header: value, header2: value }
|
|
120
|
-
'inapp_protocol': '//',
|
|
121
|
-
'inapp_link_new_window': false,
|
|
122
120
|
'ignore_dnt': false,
|
|
123
121
|
'batch_requests': true,
|
|
124
122
|
'batch_size': 50,
|
|
@@ -160,8 +158,6 @@ var create_mplib = function(token, config, name) {
|
|
|
160
158
|
}
|
|
161
159
|
|
|
162
160
|
instance._cached_groups = {}; // cache groups in a pool
|
|
163
|
-
instance._user_decide_check_complete = false;
|
|
164
|
-
instance._events_tracked_before_user_decide_check_complete = [];
|
|
165
161
|
|
|
166
162
|
instance._init(token, config, name);
|
|
167
163
|
|
|
@@ -204,11 +200,11 @@ var create_mplib = function(token, config, name) {
|
|
|
204
200
|
*/
|
|
205
201
|
MixpanelLib.prototype.init = function (token, config, name) {
|
|
206
202
|
if (_.isUndefined(name)) {
|
|
207
|
-
|
|
203
|
+
this.report_error('You must name your new library: init(token, config, name)');
|
|
208
204
|
return;
|
|
209
205
|
}
|
|
210
206
|
if (name === PRIMARY_INSTANCE_NAME) {
|
|
211
|
-
|
|
207
|
+
this.report_error('You must initialize the main mixpanel object right after you include the Mixpanel js snippet');
|
|
212
208
|
return;
|
|
213
209
|
}
|
|
214
210
|
|
|
@@ -231,7 +227,6 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
231
227
|
|
|
232
228
|
this['__loaded'] = true;
|
|
233
229
|
this['config'] = {};
|
|
234
|
-
this['_triggered_notifs'] = [];
|
|
235
230
|
|
|
236
231
|
var variable_features = {};
|
|
237
232
|
|
|
@@ -350,7 +345,7 @@ MixpanelLib.prototype._dom_loaded = function() {
|
|
|
350
345
|
|
|
351
346
|
MixpanelLib.prototype._track_dom = function(DomClass, args) {
|
|
352
347
|
if (this.get_config('img')) {
|
|
353
|
-
|
|
348
|
+
this.report_error('You can\'t use DOM tracking functions with img = true.');
|
|
354
349
|
return false;
|
|
355
350
|
}
|
|
356
351
|
|
|
@@ -452,6 +447,7 @@ MixpanelLib.prototype._send_request = function(url, data, options, callback) {
|
|
|
452
447
|
|
|
453
448
|
url += '?' + _.HTTPBuildQuery(data);
|
|
454
449
|
|
|
450
|
+
var lib = this;
|
|
455
451
|
if ('img' in data) {
|
|
456
452
|
var img = document.createElement('img');
|
|
457
453
|
img.src = url;
|
|
@@ -460,7 +456,7 @@ MixpanelLib.prototype._send_request = function(url, data, options, callback) {
|
|
|
460
456
|
try {
|
|
461
457
|
succeeded = sendBeacon(url, body_data);
|
|
462
458
|
} catch (e) {
|
|
463
|
-
|
|
459
|
+
lib.report_error(e);
|
|
464
460
|
succeeded = false;
|
|
465
461
|
}
|
|
466
462
|
try {
|
|
@@ -468,7 +464,7 @@ MixpanelLib.prototype._send_request = function(url, data, options, callback) {
|
|
|
468
464
|
callback(succeeded ? 1 : 0);
|
|
469
465
|
}
|
|
470
466
|
} catch (e) {
|
|
471
|
-
|
|
467
|
+
lib.report_error(e);
|
|
472
468
|
}
|
|
473
469
|
} else if (USE_XHR) {
|
|
474
470
|
try {
|
|
@@ -500,7 +496,7 @@ MixpanelLib.prototype._send_request = function(url, data, options, callback) {
|
|
|
500
496
|
try {
|
|
501
497
|
response = _.JSONDecode(req.responseText);
|
|
502
498
|
} catch (e) {
|
|
503
|
-
|
|
499
|
+
lib.report_error(e);
|
|
504
500
|
if (options.ignore_json_errors) {
|
|
505
501
|
response = req.responseText;
|
|
506
502
|
} else {
|
|
@@ -523,7 +519,7 @@ MixpanelLib.prototype._send_request = function(url, data, options, callback) {
|
|
|
523
519
|
} else {
|
|
524
520
|
error = 'Bad HTTP status: ' + req.status + ' ' + req.statusText;
|
|
525
521
|
}
|
|
526
|
-
|
|
522
|
+
lib.report_error(error);
|
|
527
523
|
if (callback) {
|
|
528
524
|
if (verbose_mode) {
|
|
529
525
|
callback({status: 0, error: error, xhr_req: req});
|
|
@@ -536,7 +532,7 @@ MixpanelLib.prototype._send_request = function(url, data, options, callback) {
|
|
|
536
532
|
};
|
|
537
533
|
req.send(body_data);
|
|
538
534
|
} catch (e) {
|
|
539
|
-
|
|
535
|
+
lib.report_error(e);
|
|
540
536
|
succeeded = false;
|
|
541
537
|
}
|
|
542
538
|
} else {
|
|
@@ -626,7 +622,9 @@ MixpanelLib.prototype.init_batchers = function() {
|
|
|
626
622
|
}, this),
|
|
627
623
|
beforeSendHook: _.bind(function(item) {
|
|
628
624
|
return this._run_hook('before_send_' + attrs.type, item);
|
|
629
|
-
}, this)
|
|
625
|
+
}, this),
|
|
626
|
+
errorReporter: this.get_config('error_reporter'),
|
|
627
|
+
stopAllBatchingFunc: _.bind(this.stop_batch_senders, this)
|
|
630
628
|
}
|
|
631
629
|
);
|
|
632
630
|
}, this);
|
|
@@ -783,7 +781,7 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
|
|
|
783
781
|
}
|
|
784
782
|
|
|
785
783
|
if (_.isUndefined(event_name)) {
|
|
786
|
-
|
|
784
|
+
this.report_error('No event name provided to mixpanel.track');
|
|
787
785
|
return;
|
|
788
786
|
}
|
|
789
787
|
|
|
@@ -824,7 +822,7 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
|
|
|
824
822
|
delete properties[blacklisted_prop];
|
|
825
823
|
});
|
|
826
824
|
} else {
|
|
827
|
-
|
|
825
|
+
this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
|
|
828
826
|
}
|
|
829
827
|
|
|
830
828
|
var data = {
|
|
@@ -840,8 +838,6 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
|
|
|
840
838
|
send_request_options: options
|
|
841
839
|
}, callback);
|
|
842
840
|
|
|
843
|
-
this._check_and_handle_triggered_notifications(data);
|
|
844
|
-
|
|
845
841
|
return ret;
|
|
846
842
|
});
|
|
847
843
|
|
|
@@ -1069,7 +1065,7 @@ MixpanelLib.prototype.track_forms = function() {
|
|
|
1069
1065
|
*/
|
|
1070
1066
|
MixpanelLib.prototype.time_event = function(event_name) {
|
|
1071
1067
|
if (_.isUndefined(event_name)) {
|
|
1072
|
-
|
|
1068
|
+
this.report_error('No event name provided to mixpanel.time_event');
|
|
1073
1069
|
return;
|
|
1074
1070
|
}
|
|
1075
1071
|
|
|
@@ -1252,7 +1248,6 @@ MixpanelLib.prototype.identify = function(
|
|
|
1252
1248
|
this.unregister(ALIAS_ID_KEY);
|
|
1253
1249
|
this.register({'distinct_id': new_distinct_id});
|
|
1254
1250
|
}
|
|
1255
|
-
this._check_and_handle_notifications(this.get_distinct_id());
|
|
1256
1251
|
this._flags.identify_called = true;
|
|
1257
1252
|
// Flush any queued up people requests
|
|
1258
1253
|
this['people']._flush(_set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback, _remove_callback);
|
|
@@ -1342,7 +1337,7 @@ MixpanelLib.prototype.alias = function(alias, original) {
|
|
|
1342
1337
|
// mixpanel.people.identify() call made for this user. It is VERY BAD to make an alias with
|
|
1343
1338
|
// this ID, as it will duplicate users.
|
|
1344
1339
|
if (alias === this.get_property(PEOPLE_DISTINCT_ID_KEY)) {
|
|
1345
|
-
|
|
1340
|
+
this.report_error('Attempting to create alias for existing People user - aborting.');
|
|
1346
1341
|
return -2;
|
|
1347
1342
|
}
|
|
1348
1343
|
|
|
@@ -1362,7 +1357,7 @@ MixpanelLib.prototype.alias = function(alias, original) {
|
|
|
1362
1357
|
_this.identify(alias);
|
|
1363
1358
|
});
|
|
1364
1359
|
} else {
|
|
1365
|
-
|
|
1360
|
+
this.report_error('alias matches current distinct_id - skipping api call.');
|
|
1366
1361
|
this.identify(alias);
|
|
1367
1362
|
return -1;
|
|
1368
1363
|
}
|
|
@@ -1489,14 +1484,6 @@ MixpanelLib.prototype.name_tag = function(name_tag) {
|
|
|
1489
1484
|
* // the format {'Header-Name': value}
|
|
1490
1485
|
* xhr_headers: {}
|
|
1491
1486
|
*
|
|
1492
|
-
* // protocol for fetching in-app message resources, e.g.
|
|
1493
|
-
* // 'https://' or 'http://'; defaults to '//' (which defers to the
|
|
1494
|
-
* // current page's protocol)
|
|
1495
|
-
* inapp_protocol: '//'
|
|
1496
|
-
*
|
|
1497
|
-
* // whether to open in-app message link in new tab/window
|
|
1498
|
-
* inapp_link_new_window: false
|
|
1499
|
-
*
|
|
1500
1487
|
* // whether to ignore or respect the web browser's Do Not Track setting
|
|
1501
1488
|
* ignore_dnt: false
|
|
1502
1489
|
* }
|
|
@@ -1545,7 +1532,7 @@ MixpanelLib.prototype.get_config = function(prop_name) {
|
|
|
1545
1532
|
MixpanelLib.prototype._run_hook = function(hook_name) {
|
|
1546
1533
|
var ret = (this['config']['hooks'][hook_name] || IDENTITY_FUNC).apply(this, slice.call(arguments, 1));
|
|
1547
1534
|
if (typeof ret === 'undefined') {
|
|
1548
|
-
|
|
1535
|
+
this.report_error(hook_name + ' hook did not return a value');
|
|
1549
1536
|
ret = null;
|
|
1550
1537
|
}
|
|
1551
1538
|
return ret;
|
|
@@ -1587,75 +1574,6 @@ MixpanelLib.prototype._event_is_disabled = function(event_name) {
|
|
|
1587
1574
|
_.include(this.__disabled_events, event_name);
|
|
1588
1575
|
};
|
|
1589
1576
|
|
|
1590
|
-
MixpanelLib.prototype._check_and_handle_triggered_notifications = addOptOutCheckMixpanelLib(function(event_data) {
|
|
1591
|
-
if (!this._user_decide_check_complete) {
|
|
1592
|
-
this._events_tracked_before_user_decide_check_complete.push(event_data);
|
|
1593
|
-
} else {
|
|
1594
|
-
var arr = this['_triggered_notifs'];
|
|
1595
|
-
for (var i = 0; i < arr.length; i++) {
|
|
1596
|
-
var notif = new MixpanelNotification(arr[i], this);
|
|
1597
|
-
if (notif._matches_event_data(event_data)) {
|
|
1598
|
-
this._show_notification(arr[i]);
|
|
1599
|
-
return;
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
});
|
|
1604
|
-
|
|
1605
|
-
MixpanelLib.prototype._check_and_handle_notifications = addOptOutCheckMixpanelLib(function(distinct_id) {
|
|
1606
|
-
if (
|
|
1607
|
-
!distinct_id ||
|
|
1608
|
-
this._flags.identify_called ||
|
|
1609
|
-
this.get_config('disable_notifications')
|
|
1610
|
-
) {
|
|
1611
|
-
return;
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
console.log('MIXPANEL NOTIFICATION CHECK');
|
|
1615
|
-
|
|
1616
|
-
var data = {
|
|
1617
|
-
'verbose': true,
|
|
1618
|
-
'version': '3',
|
|
1619
|
-
'lib': 'web',
|
|
1620
|
-
'token': this.get_config('token'),
|
|
1621
|
-
'distinct_id': distinct_id
|
|
1622
|
-
};
|
|
1623
|
-
this._send_request(
|
|
1624
|
-
this.get_config('api_host') + '/decide/',
|
|
1625
|
-
data,
|
|
1626
|
-
{method: 'GET', transport: 'XHR'},
|
|
1627
|
-
this._prepare_callback(_.bind(function(result) {
|
|
1628
|
-
if (result['notifications'] && result['notifications'].length > 0) {
|
|
1629
|
-
this['_triggered_notifs'] = [];
|
|
1630
|
-
var notifications = [];
|
|
1631
|
-
_.each(result['notifications'], function(notif) {
|
|
1632
|
-
(notif['display_triggers'] && notif['display_triggers'].length > 0 ? this['_triggered_notifs'] : notifications).push(notif);
|
|
1633
|
-
}, this);
|
|
1634
|
-
if (notifications.length > 0) {
|
|
1635
|
-
this._show_notification.call(this, notifications[0]);
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
1638
|
-
this._handle_user_decide_check_complete();
|
|
1639
|
-
}, this))
|
|
1640
|
-
);
|
|
1641
|
-
});
|
|
1642
|
-
|
|
1643
|
-
MixpanelLib.prototype._handle_user_decide_check_complete = function() {
|
|
1644
|
-
this._user_decide_check_complete = true;
|
|
1645
|
-
|
|
1646
|
-
// check notifications against events that were tracked before decide call completed
|
|
1647
|
-
var events = this._events_tracked_before_user_decide_check_complete;
|
|
1648
|
-
while (events.length > 0) {
|
|
1649
|
-
var data = events.shift(); // replay in the same order they came in
|
|
1650
|
-
this._check_and_handle_triggered_notifications(data);
|
|
1651
|
-
}
|
|
1652
|
-
};
|
|
1653
|
-
|
|
1654
|
-
MixpanelLib.prototype._show_notification = function(notif_data) {
|
|
1655
|
-
var notification = new MixpanelNotification(notif_data, this);
|
|
1656
|
-
notification.show();
|
|
1657
|
-
};
|
|
1658
|
-
|
|
1659
1577
|
// perform some housekeeping around GDPR opt-in/out state
|
|
1660
1578
|
MixpanelLib.prototype._gdpr_init = function() {
|
|
1661
1579
|
var is_localStorage_requested = this.get_config('opt_out_tracking_persistence_type') === 'localStorage';
|
|
@@ -1901,6 +1819,18 @@ MixpanelLib.prototype.clear_opt_in_out_tracking = function(options) {
|
|
|
1901
1819
|
this._gdpr_update_persistence(options);
|
|
1902
1820
|
};
|
|
1903
1821
|
|
|
1822
|
+
MixpanelLib.prototype.report_error = function(msg, err) {
|
|
1823
|
+
console.error.apply(console.error, arguments);
|
|
1824
|
+
try {
|
|
1825
|
+
if (!err && !(msg instanceof Error)) {
|
|
1826
|
+
msg = new Error(msg);
|
|
1827
|
+
}
|
|
1828
|
+
this.get_config('error_reporter')(msg, err);
|
|
1829
|
+
} catch(err) {
|
|
1830
|
+
console.error(err);
|
|
1831
|
+
}
|
|
1832
|
+
};
|
|
1833
|
+
|
|
1904
1834
|
// EXPORTS (for closure compiler)
|
|
1905
1835
|
|
|
1906
1836
|
// MixpanelLib Exports
|
|
@@ -1923,9 +1853,6 @@ MixpanelLib.prototype['get_config'] = MixpanelLib.protot
|
|
|
1923
1853
|
MixpanelLib.prototype['get_property'] = MixpanelLib.prototype.get_property;
|
|
1924
1854
|
MixpanelLib.prototype['get_distinct_id'] = MixpanelLib.prototype.get_distinct_id;
|
|
1925
1855
|
MixpanelLib.prototype['toString'] = MixpanelLib.prototype.toString;
|
|
1926
|
-
MixpanelLib.prototype['_check_and_handle_notifications'] = MixpanelLib.prototype._check_and_handle_notifications;
|
|
1927
|
-
MixpanelLib.prototype['_handle_user_decide_check_complete'] = MixpanelLib.prototype._handle_user_decide_check_complete;
|
|
1928
|
-
MixpanelLib.prototype['_show_notification'] = MixpanelLib.prototype._show_notification;
|
|
1929
1856
|
MixpanelLib.prototype['opt_out_tracking'] = MixpanelLib.prototype.opt_out_tracking;
|
|
1930
1857
|
MixpanelLib.prototype['opt_in_tracking'] = MixpanelLib.prototype.opt_in_tracking;
|
|
1931
1858
|
MixpanelLib.prototype['has_opted_out_tracking'] = MixpanelLib.prototype.has_opted_out_tracking;
|
|
@@ -1946,8 +1873,6 @@ MixpanelPersistence.prototype['update_referrer_info'] = MixpanelPersistence.pro
|
|
|
1946
1873
|
MixpanelPersistence.prototype['get_cross_subdomain'] = MixpanelPersistence.prototype.get_cross_subdomain;
|
|
1947
1874
|
MixpanelPersistence.prototype['clear'] = MixpanelPersistence.prototype.clear;
|
|
1948
1875
|
|
|
1949
|
-
_.safewrap_class(MixpanelLib, ['identify', '_check_and_handle_notifications', '_show_notification']);
|
|
1950
|
-
|
|
1951
1876
|
|
|
1952
1877
|
var instances = {};
|
|
1953
1878
|
var extend_mp = function() {
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
REMOVE_ACTION,
|
|
10
10
|
UNION_ACTION
|
|
11
11
|
} from './api-actions';
|
|
12
|
-
import Config from './config';
|
|
13
12
|
import { _, console } from './utils';
|
|
14
13
|
|
|
15
14
|
/*
|
|
@@ -25,7 +24,6 @@ import { _, console } from './utils';
|
|
|
25
24
|
// This key is deprecated, but we want to check for it to see whether aliasing is allowed.
|
|
26
25
|
/** @const */ var PEOPLE_DISTINCT_ID_KEY = '$people_distinct_id';
|
|
27
26
|
/** @const */ var ALIAS_ID_KEY = '__alias';
|
|
28
|
-
/** @const */ var CAMPAIGN_IDS_KEY = '__cmpns';
|
|
29
27
|
/** @const */ var EVENT_TIMERS_KEY = '__timers';
|
|
30
28
|
/** @const */ var RESERVED_PROPERTIES = [
|
|
31
29
|
SET_QUEUE_KEY,
|
|
@@ -37,7 +35,6 @@ import { _, console } from './utils';
|
|
|
37
35
|
UNION_QUEUE_KEY,
|
|
38
36
|
PEOPLE_DISTINCT_ID_KEY,
|
|
39
37
|
ALIAS_ID_KEY,
|
|
40
|
-
CAMPAIGN_IDS_KEY,
|
|
41
38
|
EVENT_TIMERS_KEY
|
|
42
39
|
];
|
|
43
40
|
|
|
@@ -151,7 +148,6 @@ MixpanelPersistence.prototype.upgrade = function(config) {
|
|
|
151
148
|
|
|
152
149
|
MixpanelPersistence.prototype.save = function() {
|
|
153
150
|
if (this.disabled) { return; }
|
|
154
|
-
this._expire_notification_campaigns();
|
|
155
151
|
this.storage.set(
|
|
156
152
|
this.name,
|
|
157
153
|
_.JSONEncode(this['props']),
|
|
@@ -223,22 +219,6 @@ MixpanelPersistence.prototype.unregister = function(prop) {
|
|
|
223
219
|
}
|
|
224
220
|
};
|
|
225
221
|
|
|
226
|
-
MixpanelPersistence.prototype._expire_notification_campaigns = _.safewrap(function() {
|
|
227
|
-
var campaigns_shown = this['props'][CAMPAIGN_IDS_KEY],
|
|
228
|
-
EXPIRY_TIME = Config.DEBUG ? 60 * 1000 : 60 * 60 * 1000; // 1 minute (Config.DEBUG) / 1 hour (PDXN)
|
|
229
|
-
if (!campaigns_shown) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
for (var campaign_id in campaigns_shown) {
|
|
233
|
-
if (1 * new Date() - campaigns_shown[campaign_id] > EXPIRY_TIME) {
|
|
234
|
-
delete campaigns_shown[campaign_id];
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
if (_.isEmptyObject(campaigns_shown)) {
|
|
238
|
-
delete this['props'][CAMPAIGN_IDS_KEY];
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
|
|
242
222
|
MixpanelPersistence.prototype.update_campaign_params = function() {
|
|
243
223
|
if (!this.campaign_params_saved) {
|
|
244
224
|
this.register_once(_.info.campaignParams());
|
|
@@ -501,6 +481,5 @@ export {
|
|
|
501
481
|
UNION_QUEUE_KEY,
|
|
502
482
|
PEOPLE_DISTINCT_ID_KEY,
|
|
503
483
|
ALIAS_ID_KEY,
|
|
504
|
-
CAMPAIGN_IDS_KEY,
|
|
505
484
|
EVENT_TIMERS_KEY
|
|
506
485
|
};
|
package/src/request-batcher.js
CHANGED
|
@@ -13,17 +13,23 @@ var logger = console_with_prefix('batch');
|
|
|
13
13
|
* @constructor
|
|
14
14
|
*/
|
|
15
15
|
var RequestBatcher = function(storageKey, options) {
|
|
16
|
-
this.
|
|
16
|
+
this.errorReporter = options.errorReporter;
|
|
17
|
+
this.queue = new RequestQueue(storageKey, {
|
|
18
|
+
errorReporter: _.bind(this.reportError, this),
|
|
19
|
+
storage: options.storage
|
|
20
|
+
});
|
|
17
21
|
|
|
18
22
|
this.libConfig = options.libConfig;
|
|
19
23
|
this.sendRequest = options.sendRequestFunc;
|
|
20
24
|
this.beforeSendHook = options.beforeSendHook;
|
|
25
|
+
this.stopAllBatching = options.stopAllBatchingFunc;
|
|
21
26
|
|
|
22
27
|
// seed variable batch size + flush interval with configured values
|
|
23
28
|
this.batchSize = this.libConfig['batch_size'];
|
|
24
29
|
this.flushInterval = this.libConfig['batch_flush_interval_ms'];
|
|
25
30
|
|
|
26
31
|
this.stopped = !this.libConfig['batch_autostart'];
|
|
32
|
+
this.consecutiveRemovalFailures = 0;
|
|
27
33
|
};
|
|
28
34
|
|
|
29
35
|
/**
|
|
@@ -39,6 +45,7 @@ RequestBatcher.prototype.enqueue = function(item, cb) {
|
|
|
39
45
|
*/
|
|
40
46
|
RequestBatcher.prototype.start = function() {
|
|
41
47
|
this.stopped = false;
|
|
48
|
+
this.consecutiveRemovalFailures = 0;
|
|
42
49
|
this.flush();
|
|
43
50
|
};
|
|
44
51
|
|
|
@@ -143,7 +150,7 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
143
150
|
res.error === 'timeout' &&
|
|
144
151
|
new Date().getTime() - startTime >= timeoutMS
|
|
145
152
|
) {
|
|
146
|
-
|
|
153
|
+
this.reportError('Network timeout; retrying');
|
|
147
154
|
this.flush();
|
|
148
155
|
} else if (
|
|
149
156
|
_.isObject(res) &&
|
|
@@ -160,17 +167,17 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
160
167
|
}
|
|
161
168
|
}
|
|
162
169
|
retryMS = Math.min(MAX_RETRY_INTERVAL_MS, retryMS);
|
|
163
|
-
|
|
170
|
+
this.reportError('Error; retry in ' + retryMS + ' ms');
|
|
164
171
|
this.scheduleFlush(retryMS);
|
|
165
172
|
} else if (_.isObject(res) && res.xhr_req && res.xhr_req['status'] === 413) {
|
|
166
173
|
// 413 Payload Too Large
|
|
167
174
|
if (batch.length > 1) {
|
|
168
175
|
var halvedBatchSize = Math.max(1, Math.floor(currentBatchSize / 2));
|
|
169
176
|
this.batchSize = Math.min(this.batchSize, halvedBatchSize, batch.length - 1);
|
|
170
|
-
|
|
177
|
+
this.reportError('413 response; reducing batch size to ' + this.batchSize);
|
|
171
178
|
this.resetFlush();
|
|
172
179
|
} else {
|
|
173
|
-
|
|
180
|
+
this.reportError('Single-event request too large; dropping', batch);
|
|
174
181
|
this.resetBatchSize();
|
|
175
182
|
removeItemsFromQueue = true;
|
|
176
183
|
}
|
|
@@ -183,12 +190,25 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
183
190
|
if (removeItemsFromQueue) {
|
|
184
191
|
this.queue.removeItemsByID(
|
|
185
192
|
_.map(batch, function(item) { return item['id']; }),
|
|
186
|
-
_.bind(
|
|
193
|
+
_.bind(function(succeeded) {
|
|
194
|
+
if (succeeded) {
|
|
195
|
+
this.consecutiveRemovalFailures = 0;
|
|
196
|
+
this.flush(); // handle next batch if the queue isn't empty
|
|
197
|
+
} else {
|
|
198
|
+
this.reportError('Failed to remove items from queue');
|
|
199
|
+
if (++this.consecutiveRemovalFailures > 5) {
|
|
200
|
+
this.reportError('Too many queue failures; disabling batching system.');
|
|
201
|
+
this.stopAllBatching();
|
|
202
|
+
} else {
|
|
203
|
+
this.resetFlush();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}, this)
|
|
187
207
|
);
|
|
188
208
|
}
|
|
189
209
|
|
|
190
210
|
} catch(err) {
|
|
191
|
-
|
|
211
|
+
this.reportError('Error handling API response', err);
|
|
192
212
|
this.resetFlush();
|
|
193
213
|
}
|
|
194
214
|
}, this);
|
|
@@ -205,9 +225,26 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
205
225
|
this.sendRequest(dataForRequest, requestOptions, batchSendCallback);
|
|
206
226
|
|
|
207
227
|
} catch(err) {
|
|
208
|
-
|
|
228
|
+
this.reportError('Error flushing request queue', err);
|
|
209
229
|
this.resetFlush();
|
|
210
230
|
}
|
|
211
231
|
};
|
|
212
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Log error to global logger and optional user-defined logger.
|
|
235
|
+
*/
|
|
236
|
+
RequestBatcher.prototype.reportError = function(msg, err) {
|
|
237
|
+
logger.error.apply(logger.error, arguments);
|
|
238
|
+
if (this.errorReporter) {
|
|
239
|
+
try {
|
|
240
|
+
if (!(err instanceof Error)) {
|
|
241
|
+
err = new Error(msg);
|
|
242
|
+
}
|
|
243
|
+
this.errorReporter(msg, err);
|
|
244
|
+
} catch(err) {
|
|
245
|
+
logger.error(err);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
213
250
|
export { RequestBatcher };
|
package/src/request-queue.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SharedLock } from './shared-lock';
|
|
2
|
-
import { cheap_guid, console_with_prefix, JSONParse, JSONStringify, _ } from './utils'; // eslint-disable-line camelcase
|
|
2
|
+
import { cheap_guid, console_with_prefix, localStorageSupported, JSONParse, JSONStringify, _ } from './utils'; // eslint-disable-line camelcase
|
|
3
3
|
|
|
4
4
|
var logger = console_with_prefix('batch');
|
|
5
5
|
|
|
@@ -23,6 +23,7 @@ var RequestQueue = function(storageKey, options) {
|
|
|
23
23
|
options = options || {};
|
|
24
24
|
this.storageKey = storageKey;
|
|
25
25
|
this.storage = options.storage || window.localStorage;
|
|
26
|
+
this.reportError = options.errorReporter || _.bind(logger.error, logger);
|
|
26
27
|
this.lock = new SharedLock(storageKey, {storage: this.storage});
|
|
27
28
|
|
|
28
29
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
@@ -60,18 +61,18 @@ RequestQueue.prototype.enqueue = function(item, flushInterval, cb) {
|
|
|
60
61
|
this.memQueue.push(queueEntry);
|
|
61
62
|
}
|
|
62
63
|
} catch(err) {
|
|
63
|
-
|
|
64
|
+
this.reportError('Error enqueueing item', item);
|
|
64
65
|
succeeded = false;
|
|
65
66
|
}
|
|
66
67
|
if (cb) {
|
|
67
68
|
cb(succeeded);
|
|
68
69
|
}
|
|
69
|
-
}, this), function lockFailure(err) {
|
|
70
|
-
|
|
70
|
+
}, this), _.bind(function lockFailure(err) {
|
|
71
|
+
this.reportError('Error acquiring storage lock', err);
|
|
71
72
|
if (cb) {
|
|
72
73
|
cb(false);
|
|
73
74
|
}
|
|
74
|
-
}, this.pid);
|
|
75
|
+
}, this), this.pid);
|
|
75
76
|
};
|
|
76
77
|
|
|
77
78
|
/**
|
|
@@ -131,25 +132,61 @@ RequestQueue.prototype.removeItemsByID = function(ids, cb) {
|
|
|
131
132
|
_.each(ids, function(id) { idSet[id] = true; });
|
|
132
133
|
|
|
133
134
|
this.memQueue = filterOutIDsAndInvalid(this.memQueue, idSet);
|
|
134
|
-
|
|
135
|
+
|
|
136
|
+
var removeFromStorage = _.bind(function() {
|
|
135
137
|
var succeeded;
|
|
136
138
|
try {
|
|
137
139
|
var storedQueue = this.readFromStorage();
|
|
138
140
|
storedQueue = filterOutIDsAndInvalid(storedQueue, idSet);
|
|
139
141
|
succeeded = this.saveToStorage(storedQueue);
|
|
142
|
+
|
|
143
|
+
// an extra check: did storage report success but somehow
|
|
144
|
+
// the items are still there?
|
|
145
|
+
if (succeeded) {
|
|
146
|
+
storedQueue = this.readFromStorage();
|
|
147
|
+
for (var i = 0; i < storedQueue.length; i++) {
|
|
148
|
+
var item = storedQueue[i];
|
|
149
|
+
if (item['id'] && !!idSet[item['id']]) {
|
|
150
|
+
this.reportError('Item not removed from storage');
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
140
155
|
} catch(err) {
|
|
141
|
-
|
|
156
|
+
this.reportError('Error removing items', ids);
|
|
142
157
|
succeeded = false;
|
|
143
158
|
}
|
|
159
|
+
return succeeded;
|
|
160
|
+
}, this);
|
|
161
|
+
|
|
162
|
+
this.lock.withLock(function lockAcquired() {
|
|
163
|
+
var succeeded = removeFromStorage();
|
|
144
164
|
if (cb) {
|
|
145
165
|
cb(succeeded);
|
|
146
166
|
}
|
|
147
|
-
},
|
|
148
|
-
|
|
167
|
+
}, _.bind(function lockFailure(err) {
|
|
168
|
+
var succeeded = false;
|
|
169
|
+
this.reportError('Error acquiring storage lock', err);
|
|
170
|
+
if (!localStorageSupported(this.storage, true)) {
|
|
171
|
+
// Looks like localStorage writes have stopped working sometime after
|
|
172
|
+
// initialization (probably full), and so nobody can acquire locks
|
|
173
|
+
// anymore. Consider it temporarily safe to remove items without the
|
|
174
|
+
// lock, since nobody's writing successfully anyway.
|
|
175
|
+
succeeded = removeFromStorage();
|
|
176
|
+
if (!succeeded) {
|
|
177
|
+
// OK, we couldn't even write out the smaller queue. Try clearing it
|
|
178
|
+
// entirely.
|
|
179
|
+
try {
|
|
180
|
+
this.storage.removeItem(this.storageKey);
|
|
181
|
+
} catch(err) {
|
|
182
|
+
this.reportError('Error clearing queue', err);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
149
186
|
if (cb) {
|
|
150
|
-
cb(
|
|
187
|
+
cb(succeeded);
|
|
151
188
|
}
|
|
152
|
-
}, this.pid);
|
|
189
|
+
}, this), this.pid);
|
|
153
190
|
};
|
|
154
191
|
|
|
155
192
|
// internal helper for RequestQueue.updatePayloads
|
|
@@ -184,18 +221,18 @@ RequestQueue.prototype.updatePayloads = function(itemsToUpdate, cb) {
|
|
|
184
221
|
storedQueue = updatePayloads(storedQueue, itemsToUpdate);
|
|
185
222
|
succeeded = this.saveToStorage(storedQueue);
|
|
186
223
|
} catch(err) {
|
|
187
|
-
|
|
224
|
+
this.reportError('Error updating items', itemsToUpdate);
|
|
188
225
|
succeeded = false;
|
|
189
226
|
}
|
|
190
227
|
if (cb) {
|
|
191
228
|
cb(succeeded);
|
|
192
229
|
}
|
|
193
|
-
}, this), function lockFailure(err) {
|
|
194
|
-
|
|
230
|
+
}, this), _.bind(function lockFailure(err) {
|
|
231
|
+
this.reportError('Error acquiring storage lock', err);
|
|
195
232
|
if (cb) {
|
|
196
233
|
cb(false);
|
|
197
234
|
}
|
|
198
|
-
}, this.pid);
|
|
235
|
+
}, this), this.pid);
|
|
199
236
|
};
|
|
200
237
|
|
|
201
238
|
/**
|
|
@@ -209,12 +246,12 @@ RequestQueue.prototype.readFromStorage = function() {
|
|
|
209
246
|
if (storageEntry) {
|
|
210
247
|
storageEntry = JSONParse(storageEntry);
|
|
211
248
|
if (!_.isArray(storageEntry)) {
|
|
212
|
-
|
|
249
|
+
this.reportError('Invalid storage entry:', storageEntry);
|
|
213
250
|
storageEntry = null;
|
|
214
251
|
}
|
|
215
252
|
}
|
|
216
253
|
} catch (err) {
|
|
217
|
-
|
|
254
|
+
this.reportError('Error retrieving queue', err);
|
|
218
255
|
storageEntry = null;
|
|
219
256
|
}
|
|
220
257
|
return storageEntry || [];
|
|
@@ -228,7 +265,7 @@ RequestQueue.prototype.saveToStorage = function(queue) {
|
|
|
228
265
|
this.storage.setItem(this.storageKey, JSONStringify(queue));
|
|
229
266
|
return true;
|
|
230
267
|
} catch (err) {
|
|
231
|
-
|
|
268
|
+
this.reportError('Error saving queue', err);
|
|
232
269
|
return false;
|
|
233
270
|
}
|
|
234
271
|
};
|