mixpanel-browser 2.39.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.
- package/.github/workflows/tests.yml +25 -0
- package/CHANGELOG.md +21 -0
- package/README.md +4 -2
- package/dist/mixpanel-jslib-snippet.min.js +3 -4
- package/dist/mixpanel-jslib-snippet.min.test.js +3 -4
- package/dist/mixpanel.amd.js +370 -704
- package/dist/mixpanel.cjs.js +370 -704
- package/dist/mixpanel.globals.js +373 -707
- package/dist/mixpanel.min.js +150 -158
- package/dist/mixpanel.umd.js +370 -704
- package/doc/build-docs.js +16 -0
- package/doc/readme.io/javascript-full-api-reference.md +44 -2
- package/mixpanel-jslib-snippet.js +1 -20
- package/package.json +3 -3
- package/src/config.js +1 -1
- package/src/gdpr-utils.js +7 -2
- package/src/mixpanel-core.js +201 -85
- package/src/mixpanel-group.js +6 -1
- package/src/mixpanel-people.js +3 -3
- package/src/request-batcher.js +27 -13
- package/src/request-queue.js +47 -0
- package/src/utils.js +48 -37
- package/tunnel.log +0 -0
- package/.travis.yml +0 -8
- package/src/autotrack-utils.js +0 -192
- package/src/autotrack.js +0 -355
package/src/mixpanel-core.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/* eslint camelcase: "off" */
|
|
2
2
|
import Config from './config';
|
|
3
|
-
import { _, console, userAgent, window, document, navigator,
|
|
4
|
-
import { autotrack } from './autotrack';
|
|
3
|
+
import { _, console, userAgent, window, document, navigator, slice } from './utils';
|
|
5
4
|
import { FormTracker, LinkTracker } from './dom-trackers';
|
|
6
5
|
import { RequestBatcher } from './request-batcher';
|
|
7
6
|
import { MixpanelGroup } from './mixpanel-group';
|
|
@@ -53,6 +52,9 @@ var mixpanel_master; // main mixpanel instance / object
|
|
|
53
52
|
var INIT_MODULE = 0;
|
|
54
53
|
var INIT_SNIPPET = 1;
|
|
55
54
|
|
|
55
|
+
var IDENTITY_FUNC = function(x) {return x;};
|
|
56
|
+
var NOOP_FUNC = function() {};
|
|
57
|
+
|
|
56
58
|
/** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel';
|
|
57
59
|
|
|
58
60
|
|
|
@@ -85,7 +87,6 @@ var DEFAULT_CONFIG = {
|
|
|
85
87
|
'api_method': 'POST',
|
|
86
88
|
'api_transport': 'XHR',
|
|
87
89
|
'app_host': 'https://mixpanel.com',
|
|
88
|
-
'autotrack': true,
|
|
89
90
|
'cdn': 'https://cdn.mxpnl.com',
|
|
90
91
|
'cross_site_cookie': false,
|
|
91
92
|
'cross_subdomain_cookie': true,
|
|
@@ -93,7 +94,7 @@ var DEFAULT_CONFIG = {
|
|
|
93
94
|
'persistence_name': '',
|
|
94
95
|
'cookie_domain': '',
|
|
95
96
|
'cookie_name': '',
|
|
96
|
-
'loaded':
|
|
97
|
+
'loaded': NOOP_FUNC,
|
|
97
98
|
'store_google': true,
|
|
98
99
|
'save_referrer': true,
|
|
99
100
|
'test': false,
|
|
@@ -116,10 +117,12 @@ var DEFAULT_CONFIG = {
|
|
|
116
117
|
'inapp_protocol': '//',
|
|
117
118
|
'inapp_link_new_window': false,
|
|
118
119
|
'ignore_dnt': false,
|
|
119
|
-
'batch_requests':
|
|
120
|
+
'batch_requests': true,
|
|
120
121
|
'batch_size': 50,
|
|
121
122
|
'batch_flush_interval_ms': 5000,
|
|
122
|
-
'batch_request_timeout_ms': 90000
|
|
123
|
+
'batch_request_timeout_ms': 90000,
|
|
124
|
+
'batch_autostart': true,
|
|
125
|
+
'hooks': {}
|
|
123
126
|
};
|
|
124
127
|
|
|
125
128
|
var DOM_LOADED = false;
|
|
@@ -166,21 +169,6 @@ var create_mplib = function(token, config, name) {
|
|
|
166
169
|
// global debug to be true
|
|
167
170
|
Config.DEBUG = Config.DEBUG || instance.get_config('debug');
|
|
168
171
|
|
|
169
|
-
instance['__autotrack_enabled'] = instance.get_config('autotrack');
|
|
170
|
-
if (instance.get_config('autotrack')) {
|
|
171
|
-
var num_buckets = 100;
|
|
172
|
-
var num_enabled_buckets = 100;
|
|
173
|
-
if (!autotrack.enabledForProject(instance.get_config('token'), num_buckets, num_enabled_buckets)) {
|
|
174
|
-
instance['__autotrack_enabled'] = false;
|
|
175
|
-
console.log('Not in active bucket: disabling Automatic Event Collection.');
|
|
176
|
-
} else if (!autotrack.isBrowserSupported()) {
|
|
177
|
-
instance['__autotrack_enabled'] = false;
|
|
178
|
-
console.log('Disabling Automatic Event Collection because this browser is not supported');
|
|
179
|
-
} else {
|
|
180
|
-
autotrack.init(instance);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
172
|
// if target is not defined, we called init after the lib already
|
|
185
173
|
// loaded, so there won't be an array of things to execute
|
|
186
174
|
if (!_.isUndefined(target) && _.isArray(target)) {
|
|
@@ -248,23 +236,13 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
248
236
|
this['config'] = {};
|
|
249
237
|
this['_triggered_notifs'] = [];
|
|
250
238
|
|
|
251
|
-
|
|
252
|
-
// (only if they have not specified a value in their init config
|
|
253
|
-
// and they aren't using a custom API host)
|
|
254
|
-
var variable_features = {};
|
|
255
|
-
var api_host = config['api_host'];
|
|
256
|
-
var is_custom_api = !!api_host && !api_host.match(/\.mixpanel\.com$/);
|
|
257
|
-
if (!('batch_requests' in config) && !is_custom_api && determine_eligibility(token, 'batch', 30)) {
|
|
258
|
-
variable_features['batch_requests'] = true;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, {
|
|
239
|
+
this.set_config(_.extend({}, DEFAULT_CONFIG, config, {
|
|
262
240
|
'name': name,
|
|
263
241
|
'token': token,
|
|
264
242
|
'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
|
|
265
243
|
}));
|
|
266
244
|
|
|
267
|
-
this['_jsc'] =
|
|
245
|
+
this['_jsc'] = NOOP_FUNC;
|
|
268
246
|
|
|
269
247
|
this.__dom_loaded_queue = [];
|
|
270
248
|
this.__request_queue = [];
|
|
@@ -282,20 +260,40 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
282
260
|
this._batch_requests = false;
|
|
283
261
|
console.log('Turning off Mixpanel request-queueing; needs XHR and localStorage support');
|
|
284
262
|
} else {
|
|
285
|
-
this.
|
|
263
|
+
this.init_batchers();
|
|
286
264
|
if (sendBeacon && window.addEventListener) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
265
|
+
// Before page closes or hides (user tabs away etc), attempt to flush any events
|
|
266
|
+
// queued up via navigator.sendBeacon. Since sendBeacon doesn't report success/failure,
|
|
267
|
+
// events will not be removed from the persistent store; if the site is loaded again,
|
|
268
|
+
// the events will be flushed again on startup and deduplicated on the Mixpanel server
|
|
269
|
+
// side.
|
|
270
|
+
// There is no reliable way to capture only page close events, so we lean on the
|
|
271
|
+
// visibilitychange and pagehide events as recommended at
|
|
272
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event#usage_notes.
|
|
273
|
+
// These events fire when the user clicks away from the current page/tab, so will occur
|
|
274
|
+
// more frequently than page unload, but are the only mechanism currently for capturing
|
|
275
|
+
// this scenario somewhat reliably.
|
|
276
|
+
var flush_on_unload = _.bind(function() {
|
|
277
|
+
if (!this.request_batchers.events.stopped) {
|
|
278
|
+
this.request_batchers.events.flush({unloading: true});
|
|
279
|
+
}
|
|
280
|
+
}, this);
|
|
281
|
+
window.addEventListener('pagehide', function(ev) {
|
|
282
|
+
if (ev['persisted']) {
|
|
283
|
+
flush_on_unload();
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
window.addEventListener('visibilitychange', function() {
|
|
287
|
+
if (document['visibilityState'] === 'hidden') {
|
|
288
|
+
flush_on_unload();
|
|
289
|
+
}
|
|
290
|
+
});
|
|
294
291
|
}
|
|
295
292
|
}
|
|
296
293
|
}
|
|
297
294
|
|
|
298
295
|
this['persistence'] = this['cookie'] = new MixpanelPersistence(this['config']);
|
|
296
|
+
this.unpersisted_superprops = {};
|
|
299
297
|
this._gdpr_init();
|
|
300
298
|
|
|
301
299
|
var uuid = _.UUID();
|
|
@@ -458,6 +456,13 @@ MixpanelLib.prototype._send_request = function(url, data, options, callback) {
|
|
|
458
456
|
console.error(e);
|
|
459
457
|
succeeded = false;
|
|
460
458
|
}
|
|
459
|
+
try {
|
|
460
|
+
if (callback) {
|
|
461
|
+
callback(succeeded ? 1 : 0);
|
|
462
|
+
}
|
|
463
|
+
} catch (e) {
|
|
464
|
+
console.error(e);
|
|
465
|
+
}
|
|
461
466
|
} else if (USE_XHR) {
|
|
462
467
|
try {
|
|
463
468
|
var req = new XMLHttpRequest();
|
|
@@ -592,32 +597,53 @@ MixpanelLib.prototype._execute_array = function(array) {
|
|
|
592
597
|
|
|
593
598
|
// request queueing utils
|
|
594
599
|
|
|
595
|
-
MixpanelLib.prototype.
|
|
600
|
+
MixpanelLib.prototype.are_batchers_initialized = function() {
|
|
601
|
+
return !!this.request_batchers.events;
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
MixpanelLib.prototype.init_batchers = function() {
|
|
596
605
|
var token = this.get_config('token');
|
|
597
|
-
if (!this.
|
|
598
|
-
var
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
this
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
606
|
+
if (!this.are_batchers_initialized()) {
|
|
607
|
+
var batcher_for = _.bind(function(attrs) {
|
|
608
|
+
return new RequestBatcher(
|
|
609
|
+
'__mpq_' + token + attrs.queue_suffix,
|
|
610
|
+
{
|
|
611
|
+
libConfig: this['config'],
|
|
612
|
+
sendRequestFunc: _.bind(function(data, options, cb) {
|
|
613
|
+
this._send_request(
|
|
614
|
+
this.get_config('api_host') + attrs.endpoint,
|
|
615
|
+
encode_data_for_request(data),
|
|
616
|
+
options,
|
|
617
|
+
this._prepare_callback(cb, data)
|
|
618
|
+
);
|
|
619
|
+
}, this),
|
|
620
|
+
beforeSendHook: _.bind(function(item) {
|
|
621
|
+
return this._run_hook('before_send_' + attrs.type, item);
|
|
622
|
+
}, this)
|
|
623
|
+
}
|
|
624
|
+
);
|
|
625
|
+
}, this);
|
|
609
626
|
this.request_batchers = {
|
|
610
|
-
events:
|
|
611
|
-
people:
|
|
612
|
-
groups:
|
|
627
|
+
events: batcher_for({type: 'events', endpoint: '/track/', queue_suffix: '_ev'}),
|
|
628
|
+
people: batcher_for({type: 'people', endpoint: '/engage/', queue_suffix: '_pp'}),
|
|
629
|
+
groups: batcher_for({type: 'groups', endpoint: '/groups/', queue_suffix: '_gr'})
|
|
613
630
|
};
|
|
614
631
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
}
|
|
632
|
+
if (this.get_config('batch_autostart')) {
|
|
633
|
+
this.start_batch_senders();
|
|
634
|
+
}
|
|
618
635
|
};
|
|
619
636
|
|
|
620
|
-
MixpanelLib.prototype.
|
|
637
|
+
MixpanelLib.prototype.start_batch_senders = function() {
|
|
638
|
+
if (this.are_batchers_initialized()) {
|
|
639
|
+
this._batch_requests = true;
|
|
640
|
+
_.each(this.request_batchers, function(batcher) {
|
|
641
|
+
batcher.start();
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
MixpanelLib.prototype.stop_batch_senders = function() {
|
|
621
647
|
this._batch_requests = false;
|
|
622
648
|
_.each(this.request_batchers, function(batcher) {
|
|
623
649
|
batcher.stop();
|
|
@@ -662,23 +688,30 @@ MixpanelLib.prototype.disable = function(events) {
|
|
|
662
688
|
|
|
663
689
|
// internal method for handling track vs batch-enqueue logic
|
|
664
690
|
MixpanelLib.prototype._track_or_batch = function(options, callback) {
|
|
665
|
-
var truncated_data = options.
|
|
691
|
+
var truncated_data = _.truncate(options.data, 255);
|
|
666
692
|
var endpoint = options.endpoint;
|
|
667
693
|
var batcher = options.batcher;
|
|
668
694
|
var should_send_immediately = options.should_send_immediately;
|
|
669
695
|
var send_request_options = options.send_request_options || {};
|
|
670
|
-
callback = callback ||
|
|
696
|
+
callback = callback || NOOP_FUNC;
|
|
671
697
|
|
|
672
698
|
var request_enqueued_or_initiated = true;
|
|
673
699
|
var send_request_immediately = _.bind(function() {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
this.
|
|
681
|
-
|
|
700
|
+
if (!send_request_options.skip_hooks) {
|
|
701
|
+
truncated_data = this._run_hook('before_send_' + options.type, truncated_data);
|
|
702
|
+
}
|
|
703
|
+
if (truncated_data) {
|
|
704
|
+
console.log('MIXPANEL REQUEST:');
|
|
705
|
+
console.log(truncated_data);
|
|
706
|
+
return this._send_request(
|
|
707
|
+
endpoint,
|
|
708
|
+
encode_data_for_request(truncated_data),
|
|
709
|
+
send_request_options,
|
|
710
|
+
this._prepare_callback(callback, truncated_data)
|
|
711
|
+
);
|
|
712
|
+
} else {
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
682
715
|
}, this);
|
|
683
716
|
|
|
684
717
|
if (this._batch_requests && !should_send_immediately) {
|
|
@@ -731,7 +764,7 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
|
|
|
731
764
|
}
|
|
732
765
|
var should_send_immediately = options['send_immediately'];
|
|
733
766
|
if (typeof callback !== 'function') {
|
|
734
|
-
callback =
|
|
767
|
+
callback = NOOP_FUNC;
|
|
735
768
|
}
|
|
736
769
|
|
|
737
770
|
if (_.isUndefined(event_name)) {
|
|
@@ -766,6 +799,7 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
|
|
|
766
799
|
{},
|
|
767
800
|
_.info.properties(),
|
|
768
801
|
this['persistence'].properties(),
|
|
802
|
+
this.unpersisted_superprops,
|
|
769
803
|
properties
|
|
770
804
|
);
|
|
771
805
|
|
|
@@ -783,7 +817,8 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
|
|
|
783
817
|
'properties': properties
|
|
784
818
|
};
|
|
785
819
|
var ret = this._track_or_batch({
|
|
786
|
-
|
|
820
|
+
type: 'events',
|
|
821
|
+
data: data,
|
|
787
822
|
endpoint: this.get_config('api_host') + '/track/',
|
|
788
823
|
batcher: this.request_batchers.events,
|
|
789
824
|
should_send_immediately: should_send_immediately,
|
|
@@ -1030,6 +1065,27 @@ MixpanelLib.prototype.time_event = function(event_name) {
|
|
|
1030
1065
|
this['persistence'].set_event_timer(event_name, new Date().getTime());
|
|
1031
1066
|
};
|
|
1032
1067
|
|
|
1068
|
+
var REGISTER_DEFAULTS = {
|
|
1069
|
+
'persistent': true
|
|
1070
|
+
};
|
|
1071
|
+
/**
|
|
1072
|
+
* Helper to parse options param for register methods, maintaining
|
|
1073
|
+
* legacy support for plain "days" param instead of options object
|
|
1074
|
+
* @param {Number|Object} [days_or_options] 'days' option (Number), or Options object for register methods
|
|
1075
|
+
* @returns {Object} options object
|
|
1076
|
+
*/
|
|
1077
|
+
var options_for_register = function(days_or_options) {
|
|
1078
|
+
var options;
|
|
1079
|
+
if (_.isObject(days_or_options)) {
|
|
1080
|
+
options = days_or_options;
|
|
1081
|
+
} else if (!_.isUndefined(days_or_options)) {
|
|
1082
|
+
options = {'days': days_or_options};
|
|
1083
|
+
} else {
|
|
1084
|
+
options = {};
|
|
1085
|
+
}
|
|
1086
|
+
return _.extend({}, REGISTER_DEFAULTS, options);
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1033
1089
|
/**
|
|
1034
1090
|
* Register a set of super properties, which are included with all
|
|
1035
1091
|
* events. This will overwrite previous super property values.
|
|
@@ -1045,11 +1101,21 @@ MixpanelLib.prototype.time_event = function(event_name) {
|
|
|
1045
1101
|
* 'Account Type': 'Free'
|
|
1046
1102
|
* });
|
|
1047
1103
|
*
|
|
1104
|
+
* // register only for the current pageload
|
|
1105
|
+
* mixpanel.register({'Name': 'Pat'}, {persistent: false});
|
|
1106
|
+
*
|
|
1048
1107
|
* @param {Object} properties An associative array of properties to store about the user
|
|
1049
|
-
* @param {Number} [
|
|
1108
|
+
* @param {Number|Object} [days_or_options] Options object or number of days since the user's last visit to store the super properties (only valid for persisted props)
|
|
1109
|
+
* @param {boolean} [days_or_options.days] - number of days since the user's last visit to store the super properties (only valid for persisted props)
|
|
1110
|
+
* @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage)
|
|
1050
1111
|
*/
|
|
1051
|
-
MixpanelLib.prototype.register = function(props,
|
|
1052
|
-
|
|
1112
|
+
MixpanelLib.prototype.register = function(props, days_or_options) {
|
|
1113
|
+
var options = options_for_register(days_or_options);
|
|
1114
|
+
if (options['persistent']) {
|
|
1115
|
+
this['persistence'].register(props, options['days']);
|
|
1116
|
+
} else {
|
|
1117
|
+
_.extend(this.unpersisted_superprops, props);
|
|
1118
|
+
}
|
|
1053
1119
|
};
|
|
1054
1120
|
|
|
1055
1121
|
/**
|
|
@@ -1063,6 +1129,11 @@ MixpanelLib.prototype.register = function(props, days) {
|
|
|
1063
1129
|
* 'First Login Date': new Date().toISOString()
|
|
1064
1130
|
* });
|
|
1065
1131
|
*
|
|
1132
|
+
* // register once, only for the current pageload
|
|
1133
|
+
* mixpanel.register_once({
|
|
1134
|
+
* 'First interaction time': new Date().toISOString()
|
|
1135
|
+
* }, 'None', {persistent: false});
|
|
1136
|
+
*
|
|
1066
1137
|
* ### Notes:
|
|
1067
1138
|
*
|
|
1068
1139
|
* If default_value is specified, current super properties
|
|
@@ -1070,19 +1141,40 @@ MixpanelLib.prototype.register = function(props, days) {
|
|
|
1070
1141
|
*
|
|
1071
1142
|
* @param {Object} properties An associative array of properties to store about the user
|
|
1072
1143
|
* @param {*} [default_value] Value to override if already set in super properties (ex: 'False') Default: 'None'
|
|
1073
|
-
* @param {Number} [
|
|
1144
|
+
* @param {Number|Object} [days_or_options] Options object or number of days since the user's last visit to store the super properties (only valid for persisted props)
|
|
1145
|
+
* @param {boolean} [days_or_options.days] - number of days since the user's last visit to store the super properties (only valid for persisted props)
|
|
1146
|
+
* @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage)
|
|
1074
1147
|
*/
|
|
1075
|
-
MixpanelLib.prototype.register_once = function(props, default_value,
|
|
1076
|
-
|
|
1148
|
+
MixpanelLib.prototype.register_once = function(props, default_value, days_or_options) {
|
|
1149
|
+
var options = options_for_register(days_or_options);
|
|
1150
|
+
if (options['persistent']) {
|
|
1151
|
+
this['persistence'].register_once(props, default_value, options['days']);
|
|
1152
|
+
} else {
|
|
1153
|
+
if (typeof(default_value) === 'undefined') {
|
|
1154
|
+
default_value = 'None';
|
|
1155
|
+
}
|
|
1156
|
+
_.each(props, function(val, prop) {
|
|
1157
|
+
if (!this.unpersisted_superprops.hasOwnProperty(prop) || this.unpersisted_superprops[prop] === default_value) {
|
|
1158
|
+
this.unpersisted_superprops[prop] = val;
|
|
1159
|
+
}
|
|
1160
|
+
}, this);
|
|
1161
|
+
}
|
|
1077
1162
|
};
|
|
1078
1163
|
|
|
1079
1164
|
/**
|
|
1080
1165
|
* Delete a super property stored with the current user.
|
|
1081
1166
|
*
|
|
1082
1167
|
* @param {String} property The name of the super property to remove
|
|
1168
|
+
* @param {Object} [options]
|
|
1169
|
+
* @param {boolean} [options.persistent=true] - whether to look in persistent storage (cookie/localStorage)
|
|
1083
1170
|
*/
|
|
1084
|
-
MixpanelLib.prototype.unregister = function(property) {
|
|
1085
|
-
|
|
1171
|
+
MixpanelLib.prototype.unregister = function(property, options) {
|
|
1172
|
+
options = options_for_register(options);
|
|
1173
|
+
if (options['persistent']) {
|
|
1174
|
+
this['persistence'].unregister(property);
|
|
1175
|
+
} else {
|
|
1176
|
+
delete this.unpersisted_superprops[property];
|
|
1177
|
+
}
|
|
1086
1178
|
};
|
|
1087
1179
|
|
|
1088
1180
|
MixpanelLib.prototype._register_single = function(prop, value) {
|
|
@@ -1153,7 +1245,10 @@ MixpanelLib.prototype.identify = function(
|
|
|
1153
1245
|
// send an $identify event any time the distinct_id is changing - logic on the server
|
|
1154
1246
|
// will determine whether or not to do anything with it.
|
|
1155
1247
|
if (new_distinct_id !== previous_distinct_id) {
|
|
1156
|
-
this.track('$identify', {
|
|
1248
|
+
this.track('$identify', {
|
|
1249
|
+
'distinct_id': new_distinct_id,
|
|
1250
|
+
'$anon_distinct_id': previous_distinct_id
|
|
1251
|
+
}, {skip_hooks: true});
|
|
1157
1252
|
}
|
|
1158
1253
|
};
|
|
1159
1254
|
|
|
@@ -1242,7 +1337,12 @@ MixpanelLib.prototype.alias = function(alias, original) {
|
|
|
1242
1337
|
}
|
|
1243
1338
|
if (alias !== original) {
|
|
1244
1339
|
this._register_single(ALIAS_ID_KEY, alias);
|
|
1245
|
-
return this.track('$create_alias', {
|
|
1340
|
+
return this.track('$create_alias', {
|
|
1341
|
+
'alias': alias,
|
|
1342
|
+
'distinct_id': original
|
|
1343
|
+
}, {
|
|
1344
|
+
skip_hooks: true
|
|
1345
|
+
}, function() {
|
|
1246
1346
|
// Flush the people queue
|
|
1247
1347
|
_this.identify(alias);
|
|
1248
1348
|
});
|
|
@@ -1421,6 +1521,21 @@ MixpanelLib.prototype.get_config = function(prop_name) {
|
|
|
1421
1521
|
return this['config'][prop_name];
|
|
1422
1522
|
};
|
|
1423
1523
|
|
|
1524
|
+
/**
|
|
1525
|
+
* Fetch a hook function from config, with safe default, and run it
|
|
1526
|
+
* against the given arguments
|
|
1527
|
+
* @param {string} hook_name which hook to retrieve
|
|
1528
|
+
* @returns {any|null} return value of user-provided hook, or null if nothing was returned
|
|
1529
|
+
*/
|
|
1530
|
+
MixpanelLib.prototype._run_hook = function(hook_name) {
|
|
1531
|
+
var ret = (this['config']['hooks'][hook_name] || IDENTITY_FUNC).apply(this, slice.call(arguments, 1));
|
|
1532
|
+
if (typeof ret === 'undefined') {
|
|
1533
|
+
console.error(hook_name + ' hook did not return a value');
|
|
1534
|
+
ret = null;
|
|
1535
|
+
}
|
|
1536
|
+
return ret;
|
|
1537
|
+
};
|
|
1538
|
+
|
|
1424
1539
|
/**
|
|
1425
1540
|
* Returns the value of the super property named property_name. If no such
|
|
1426
1541
|
* property is set, get_property() will return the undefined value.
|
|
@@ -1806,7 +1921,8 @@ MixpanelLib.prototype['set_group'] = MixpanelLib.protot
|
|
|
1806
1921
|
MixpanelLib.prototype['add_group'] = MixpanelLib.prototype.add_group;
|
|
1807
1922
|
MixpanelLib.prototype['remove_group'] = MixpanelLib.prototype.remove_group;
|
|
1808
1923
|
MixpanelLib.prototype['track_with_groups'] = MixpanelLib.prototype.track_with_groups;
|
|
1809
|
-
MixpanelLib.prototype['
|
|
1924
|
+
MixpanelLib.prototype['start_batch_senders'] = MixpanelLib.prototype.start_batch_senders;
|
|
1925
|
+
MixpanelLib.prototype['stop_batch_senders'] = MixpanelLib.prototype.stop_batch_senders;
|
|
1810
1926
|
|
|
1811
1927
|
// MixpanelPersistence Exports
|
|
1812
1928
|
MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
|
|
@@ -1931,7 +2047,7 @@ export function init_from_snippet() {
|
|
|
1931
2047
|
}
|
|
1932
2048
|
if (mixpanel_master['__loaded'] || (mixpanel_master['config'] && mixpanel_master['persistence'])) {
|
|
1933
2049
|
// lib has already been loaded at least once; we don't want to override the global object this time so bomb early
|
|
1934
|
-
console.
|
|
2050
|
+
console.critical('The Mixpanel library has already been downloaded at least once. Ensure that the Mixpanel code snippet only appears once on the page (and is not double-loaded by a tag manager) in order to avoid errors.');
|
|
1935
2051
|
return;
|
|
1936
2052
|
}
|
|
1937
2053
|
var snippet_version = mixpanel_master['__SV'] || 0;
|
package/src/mixpanel-group.js
CHANGED
|
@@ -110,9 +110,13 @@ MixpanelGroup.prototype.union = addOptOutCheckMixpanelGroup(function(list_name,
|
|
|
110
110
|
* Permanently delete a group.
|
|
111
111
|
*
|
|
112
112
|
* ### Usage:
|
|
113
|
+
*
|
|
113
114
|
* mixpanel.get_group('company', 'mixpanel').delete();
|
|
115
|
+
*
|
|
116
|
+
* @param {Function} [callback] If provided, the callback will be called after the tracking event
|
|
114
117
|
*/
|
|
115
118
|
MixpanelGroup.prototype['delete'] = addOptOutCheckMixpanelGroup(function(callback) {
|
|
119
|
+
// bracket notation above prevents a minification error related to reserved words
|
|
116
120
|
var data = this.delete_action();
|
|
117
121
|
return this._send_request(data, callback);
|
|
118
122
|
});
|
|
@@ -140,7 +144,8 @@ MixpanelGroup.prototype._send_request = function(data, callback) {
|
|
|
140
144
|
|
|
141
145
|
var date_encoded_data = _.encodeDates(data);
|
|
142
146
|
return this._mixpanel._track_or_batch({
|
|
143
|
-
|
|
147
|
+
type: 'groups',
|
|
148
|
+
data: date_encoded_data,
|
|
144
149
|
endpoint: this._get_config('api_host') + '/groups/',
|
|
145
150
|
batcher: this._mixpanel.request_batchers.groups
|
|
146
151
|
}, callback);
|
package/src/mixpanel-people.js
CHANGED
|
@@ -330,7 +330,6 @@ MixpanelPeople.prototype._send_request = function(data, callback) {
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
var date_encoded_data = _.encodeDates(data);
|
|
333
|
-
var truncated_data = _.truncate(date_encoded_data, 255);
|
|
334
333
|
|
|
335
334
|
if (!this._identify_called()) {
|
|
336
335
|
this._enqueue(data);
|
|
@@ -341,11 +340,12 @@ MixpanelPeople.prototype._send_request = function(data, callback) {
|
|
|
341
340
|
callback(-1);
|
|
342
341
|
}
|
|
343
342
|
}
|
|
344
|
-
return
|
|
343
|
+
return _.truncate(date_encoded_data, 255);
|
|
345
344
|
}
|
|
346
345
|
|
|
347
346
|
return this._mixpanel._track_or_batch({
|
|
348
|
-
|
|
347
|
+
type: 'people',
|
|
348
|
+
data: date_encoded_data,
|
|
349
349
|
endpoint: this._get_config('api_host') + '/engage/',
|
|
350
350
|
batcher: this._mixpanel.request_batchers.people
|
|
351
351
|
}, callback);
|
package/src/request-batcher.js
CHANGED
|
@@ -12,18 +12,18 @@ var logger = console_with_prefix('batch');
|
|
|
12
12
|
* Uses RequestQueue to manage the backing store.
|
|
13
13
|
* @constructor
|
|
14
14
|
*/
|
|
15
|
-
var RequestBatcher = function(storageKey,
|
|
15
|
+
var RequestBatcher = function(storageKey, options) {
|
|
16
16
|
this.queue = new RequestQueue(storageKey, {storage: options.storage});
|
|
17
|
-
this.endpoint = endpoint;
|
|
18
17
|
|
|
19
18
|
this.libConfig = options.libConfig;
|
|
20
19
|
this.sendRequest = options.sendRequestFunc;
|
|
20
|
+
this.beforeSendHook = options.beforeSendHook;
|
|
21
21
|
|
|
22
22
|
// seed variable batch size + flush interval with configured values
|
|
23
23
|
this.batchSize = this.libConfig['batch_size'];
|
|
24
24
|
this.flushInterval = this.libConfig['batch_flush_interval_ms'];
|
|
25
25
|
|
|
26
|
-
this.stopped =
|
|
26
|
+
this.stopped = !this.libConfig['batch_autostart'];
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -103,18 +103,29 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
options = options || {};
|
|
106
|
+
var timeoutMS = this.libConfig['batch_request_timeout_ms'];
|
|
107
|
+
var startTime = new Date().getTime();
|
|
106
108
|
var currentBatchSize = this.batchSize;
|
|
107
109
|
var batch = this.queue.fillBatch(currentBatchSize);
|
|
108
|
-
|
|
110
|
+
var dataForRequest = [];
|
|
111
|
+
var transformedItems = {};
|
|
112
|
+
_.each(batch, function(item) {
|
|
113
|
+
var payload = item['payload'];
|
|
114
|
+
if (this.beforeSendHook && !item.orphaned) {
|
|
115
|
+
payload = this.beforeSendHook(payload);
|
|
116
|
+
}
|
|
117
|
+
if (payload) {
|
|
118
|
+
dataForRequest.push(payload);
|
|
119
|
+
}
|
|
120
|
+
transformedItems[item['id']] = payload;
|
|
121
|
+
}, this);
|
|
122
|
+
if (dataForRequest.length < 1) {
|
|
109
123
|
this.resetFlush();
|
|
110
124
|
return; // nothing to do
|
|
111
125
|
}
|
|
112
126
|
|
|
113
127
|
this.requestInProgress = true;
|
|
114
128
|
|
|
115
|
-
var timeoutMS = this.libConfig['batch_request_timeout_ms'];
|
|
116
|
-
var startTime = new Date().getTime();
|
|
117
|
-
var dataForRequest = _.map(batch, function(item) { return item['payload']; });
|
|
118
129
|
var batchSendCallback = _.bind(function(res) {
|
|
119
130
|
this.requestInProgress = false;
|
|
120
131
|
|
|
@@ -124,7 +135,10 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
124
135
|
// flush operation if something goes wrong
|
|
125
136
|
|
|
126
137
|
var removeItemsFromQueue = false;
|
|
127
|
-
if (
|
|
138
|
+
if (options.unloading) {
|
|
139
|
+
// update persisted data to include hook transformations
|
|
140
|
+
this.queue.updatePayloads(transformedItems);
|
|
141
|
+
} else if (
|
|
128
142
|
_.isObject(res) &&
|
|
129
143
|
res.error === 'timeout' &&
|
|
130
144
|
new Date().getTime() - startTime >= timeoutMS
|
|
@@ -134,9 +148,9 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
134
148
|
} else if (
|
|
135
149
|
_.isObject(res) &&
|
|
136
150
|
res.xhr_req &&
|
|
137
|
-
(res.xhr_req['status'] >= 500 || res.xhr_req['status']
|
|
151
|
+
(res.xhr_req['status'] >= 500 || res.xhr_req['status'] === 429 || res.error === 'timeout')
|
|
138
152
|
) {
|
|
139
|
-
// network or API error, retry
|
|
153
|
+
// network or API error, or 429 Too Many Requests, retry
|
|
140
154
|
var retryMS = this.flushInterval * 2;
|
|
141
155
|
var headers = res.xhr_req['responseHeaders'];
|
|
142
156
|
if (headers) {
|
|
@@ -184,11 +198,11 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
184
198
|
ignore_json_errors: true, // eslint-disable-line camelcase
|
|
185
199
|
timeout_ms: timeoutMS // eslint-disable-line camelcase
|
|
186
200
|
};
|
|
187
|
-
if (options.
|
|
201
|
+
if (options.unloading) {
|
|
188
202
|
requestOptions.transport = 'sendBeacon';
|
|
189
203
|
}
|
|
190
|
-
logger.log('MIXPANEL REQUEST:',
|
|
191
|
-
this.sendRequest(
|
|
204
|
+
logger.log('MIXPANEL REQUEST:', dataForRequest);
|
|
205
|
+
this.sendRequest(dataForRequest, requestOptions, batchSendCallback);
|
|
192
206
|
|
|
193
207
|
} catch(err) {
|
|
194
208
|
logger.error('Error flushing request queue', err);
|