mixpanel-browser 2.40.1 → 2.43.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 +15 -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 +96 -647
- package/dist/mixpanel.cjs.js +96 -647
- package/dist/mixpanel.globals.js +99 -650
- package/dist/mixpanel.min.js +151 -163
- package/dist/mixpanel.umd.js +96 -647
- package/doc/build-docs.js +16 -0
- package/doc/readme.io/javascript-full-api-reference.md +18 -0
- package/mixpanel-jslib-snippet.js +0 -19
- package/package.json +3 -3
- package/src/config.js +1 -1
- package/src/mixpanel-core.js +45 -40
- package/src/mixpanel-group.js +4 -0
- package/src/request-batcher.js +2 -2
- package/src/utils.js +2 -31
- package/tunnel.log +0 -0
- package/.travis.yml +0 -6
- package/src/autotrack-utils.js +0 -192
- package/src/autotrack.js +0 -355
package/src/autotrack.js
DELETED
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
import { _ } from './utils';
|
|
2
|
-
import {
|
|
3
|
-
getClassName,
|
|
4
|
-
getSafeText,
|
|
5
|
-
isElementNode,
|
|
6
|
-
isTag,
|
|
7
|
-
isTextNode,
|
|
8
|
-
shouldTrackDomEvent,
|
|
9
|
-
shouldTrackElement,
|
|
10
|
-
shouldTrackValue
|
|
11
|
-
} from './autotrack-utils';
|
|
12
|
-
|
|
13
|
-
var autotrack = {
|
|
14
|
-
_initializedTokens: [],
|
|
15
|
-
|
|
16
|
-
_previousElementSibling: function(el) {
|
|
17
|
-
if (el.previousElementSibling) {
|
|
18
|
-
return el.previousElementSibling;
|
|
19
|
-
} else {
|
|
20
|
-
do {
|
|
21
|
-
el = el.previousSibling;
|
|
22
|
-
} while (el && !isElementNode(el));
|
|
23
|
-
return el;
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
_loadScript: function(scriptUrlToLoad, callback) {
|
|
28
|
-
var scriptTag = document.createElement('script');
|
|
29
|
-
scriptTag.type = 'text/javascript';
|
|
30
|
-
scriptTag.src = scriptUrlToLoad;
|
|
31
|
-
scriptTag.onload = callback;
|
|
32
|
-
|
|
33
|
-
var scripts = document.getElementsByTagName('script');
|
|
34
|
-
if (scripts.length > 0) {
|
|
35
|
-
scripts[0].parentNode.insertBefore(scriptTag, scripts[0]);
|
|
36
|
-
} else {
|
|
37
|
-
document.body.appendChild(scriptTag);
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
_getPropertiesFromElement: function(elem) {
|
|
42
|
-
var props = {
|
|
43
|
-
'classes': getClassName(elem).split(' '),
|
|
44
|
-
'tag_name': elem.tagName.toLowerCase()
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
if (shouldTrackElement(elem)) {
|
|
48
|
-
_.each(elem.attributes, function(attr) {
|
|
49
|
-
if (shouldTrackValue(attr.value)) {
|
|
50
|
-
props['attr__' + attr.name] = attr.value;
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
var nthChild = 1;
|
|
56
|
-
var nthOfType = 1;
|
|
57
|
-
var currentElem = elem;
|
|
58
|
-
while (currentElem = this._previousElementSibling(currentElem)) { // eslint-disable-line no-cond-assign
|
|
59
|
-
nthChild++;
|
|
60
|
-
if (currentElem.tagName === elem.tagName) {
|
|
61
|
-
nthOfType++;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
props['nth_child'] = nthChild;
|
|
65
|
-
props['nth_of_type'] = nthOfType;
|
|
66
|
-
|
|
67
|
-
return props;
|
|
68
|
-
},
|
|
69
|
-
|
|
70
|
-
_getDefaultProperties: function(eventType) {
|
|
71
|
-
return {
|
|
72
|
-
'$event_type': eventType,
|
|
73
|
-
'$ce_version': 1,
|
|
74
|
-
'$host': window.location.host,
|
|
75
|
-
'$pathname': window.location.pathname
|
|
76
|
-
};
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
_extractCustomPropertyValue: function(customProperty) {
|
|
80
|
-
var propValues = [];
|
|
81
|
-
_.each(document.querySelectorAll(customProperty['css_selector']), function(matchedElem) {
|
|
82
|
-
var value;
|
|
83
|
-
|
|
84
|
-
if (['input', 'select'].indexOf(matchedElem.tagName.toLowerCase()) > -1) {
|
|
85
|
-
value = matchedElem['value'];
|
|
86
|
-
} else if (matchedElem['textContent']) {
|
|
87
|
-
value = matchedElem['textContent'];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (shouldTrackValue(value)) {
|
|
91
|
-
propValues.push(value);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
return propValues.join(', ');
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
_getCustomProperties: function(targetElementList) {
|
|
98
|
-
var props = {};
|
|
99
|
-
_.each(this._customProperties, function(customProperty) {
|
|
100
|
-
_.each(customProperty['event_selectors'], function(eventSelector) {
|
|
101
|
-
var eventElements = document.querySelectorAll(eventSelector);
|
|
102
|
-
_.each(eventElements, function(eventElement) {
|
|
103
|
-
if (_.includes(targetElementList, eventElement) && shouldTrackElement(eventElement)) {
|
|
104
|
-
props[customProperty['name']] = this._extractCustomPropertyValue(customProperty);
|
|
105
|
-
}
|
|
106
|
-
}, this);
|
|
107
|
-
}, this);
|
|
108
|
-
}, this);
|
|
109
|
-
return props;
|
|
110
|
-
},
|
|
111
|
-
|
|
112
|
-
_getEventTarget: function(e) {
|
|
113
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/Event/target#Compatibility_notes
|
|
114
|
-
if (typeof e.target === 'undefined') {
|
|
115
|
-
return e.srcElement;
|
|
116
|
-
} else {
|
|
117
|
-
return e.target;
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
|
|
121
|
-
_trackEvent: function(e, instance) {
|
|
122
|
-
/*** Don't mess with this code without running IE8 tests on it ***/
|
|
123
|
-
var target = this._getEventTarget(e);
|
|
124
|
-
if (isTextNode(target)) { // defeat Safari bug (see: http://www.quirksmode.org/js/events_properties.html)
|
|
125
|
-
target = target.parentNode;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (shouldTrackDomEvent(target, e)) {
|
|
129
|
-
var targetElementList = [target];
|
|
130
|
-
var curEl = target;
|
|
131
|
-
while (curEl.parentNode && !isTag(curEl, 'body')) {
|
|
132
|
-
targetElementList.push(curEl.parentNode);
|
|
133
|
-
curEl = curEl.parentNode;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
var elementsJson = [];
|
|
137
|
-
var href, explicitNoTrack = false;
|
|
138
|
-
_.each(targetElementList, function(el) {
|
|
139
|
-
var shouldTrackEl = shouldTrackElement(el);
|
|
140
|
-
|
|
141
|
-
// if the element or a parent element is an anchor tag
|
|
142
|
-
// include the href as a property
|
|
143
|
-
if (el.tagName.toLowerCase() === 'a') {
|
|
144
|
-
href = el.getAttribute('href');
|
|
145
|
-
href = shouldTrackEl && shouldTrackValue(href) && href;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// allow users to programatically prevent tracking of elements by adding class 'mp-no-track'
|
|
149
|
-
var classes = getClassName(el).split(' ');
|
|
150
|
-
if (_.includes(classes, 'mp-no-track')) {
|
|
151
|
-
explicitNoTrack = true;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
elementsJson.push(this._getPropertiesFromElement(el));
|
|
155
|
-
}, this);
|
|
156
|
-
|
|
157
|
-
if (explicitNoTrack) {
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// only populate text content from target element (not parents)
|
|
162
|
-
// to prevent text within a sensitive element from being collected
|
|
163
|
-
// as part of a parent's el.textContent
|
|
164
|
-
var elementText;
|
|
165
|
-
var safeElementText = getSafeText(target);
|
|
166
|
-
if (safeElementText && safeElementText.length) {
|
|
167
|
-
elementText = safeElementText;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
var props = _.extend(
|
|
171
|
-
this._getDefaultProperties(e.type),
|
|
172
|
-
{
|
|
173
|
-
'$elements': elementsJson,
|
|
174
|
-
'$el_attr__href': href,
|
|
175
|
-
'$el_text': elementText
|
|
176
|
-
},
|
|
177
|
-
this._getCustomProperties(targetElementList)
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
instance.track('$web_event', props);
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
// only reason is to stub for unit tests
|
|
186
|
-
// since you can't override window.location props
|
|
187
|
-
_navigate: function(href) {
|
|
188
|
-
window.location.href = href;
|
|
189
|
-
},
|
|
190
|
-
|
|
191
|
-
_addDomEventHandlers: function(instance) {
|
|
192
|
-
var handler = _.bind(function(e) {
|
|
193
|
-
e = e || window.event;
|
|
194
|
-
this._trackEvent(e, instance);
|
|
195
|
-
}, this);
|
|
196
|
-
_.register_event(document, 'submit', handler, false, true);
|
|
197
|
-
_.register_event(document, 'change', handler, false, true);
|
|
198
|
-
_.register_event(document, 'click', handler, false, true);
|
|
199
|
-
},
|
|
200
|
-
|
|
201
|
-
_customProperties: {},
|
|
202
|
-
init: function(instance) {
|
|
203
|
-
if (!(document && document.body)) {
|
|
204
|
-
console.log('document not ready yet, trying again in 500 milliseconds...');
|
|
205
|
-
var that = this;
|
|
206
|
-
setTimeout(function() { that.init(instance); }, 500);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
var token = instance.get_config('token');
|
|
211
|
-
if (this._initializedTokens.indexOf(token) > -1) {
|
|
212
|
-
console.log('autotrack already initialized for token "' + token + '"');
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
this._initializedTokens.push(token);
|
|
216
|
-
|
|
217
|
-
if (!this._maybeLoadEditor(instance)) { // don't autotrack actions when the editor is enabled
|
|
218
|
-
var parseDecideResponse = _.bind(function(response) {
|
|
219
|
-
if (response && response['config'] && response['config']['enable_collect_everything'] === true) {
|
|
220
|
-
|
|
221
|
-
if (response['custom_properties']) {
|
|
222
|
-
this._customProperties = response['custom_properties'];
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
instance.track('$web_event', _.extend({
|
|
226
|
-
'$title': document.title
|
|
227
|
-
}, this._getDefaultProperties('pageview')));
|
|
228
|
-
|
|
229
|
-
this._addDomEventHandlers(instance);
|
|
230
|
-
|
|
231
|
-
} else {
|
|
232
|
-
instance['__autotrack_enabled'] = false;
|
|
233
|
-
}
|
|
234
|
-
}, this);
|
|
235
|
-
|
|
236
|
-
instance._send_request(
|
|
237
|
-
instance.get_config('api_host') + '/decide/', {
|
|
238
|
-
'verbose': true,
|
|
239
|
-
'version': '1',
|
|
240
|
-
'lib': 'web',
|
|
241
|
-
'token': token
|
|
242
|
-
},
|
|
243
|
-
{method: 'GET', transport: 'XHR'},
|
|
244
|
-
instance._prepare_callback(parseDecideResponse)
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
},
|
|
248
|
-
|
|
249
|
-
_editorParamsFromHash: function(instance, hash) {
|
|
250
|
-
var editorParams;
|
|
251
|
-
try {
|
|
252
|
-
var state = _.getHashParam(hash, 'state');
|
|
253
|
-
state = JSON.parse(decodeURIComponent(state));
|
|
254
|
-
var expiresInSeconds = _.getHashParam(hash, 'expires_in');
|
|
255
|
-
editorParams = {
|
|
256
|
-
'accessToken': _.getHashParam(hash, 'access_token'),
|
|
257
|
-
'accessTokenExpiresAt': (new Date()).getTime() + (Number(expiresInSeconds) * 1000),
|
|
258
|
-
'bookmarkletMode': !!state['bookmarkletMode'],
|
|
259
|
-
'projectId': state['projectId'],
|
|
260
|
-
'projectOwnerId': state['projectOwnerId'],
|
|
261
|
-
'projectToken': state['token'],
|
|
262
|
-
'readOnly': state['readOnly'],
|
|
263
|
-
'userFlags': state['userFlags'],
|
|
264
|
-
'userId': state['userId']
|
|
265
|
-
};
|
|
266
|
-
window.sessionStorage.setItem('editorParams', JSON.stringify(editorParams));
|
|
267
|
-
|
|
268
|
-
if (state['desiredHash']) {
|
|
269
|
-
window.location.hash = state['desiredHash'];
|
|
270
|
-
} else if (window.history) {
|
|
271
|
-
history.replaceState('', document.title, window.location.pathname + window.location.search); // completely remove hash
|
|
272
|
-
} else {
|
|
273
|
-
window.location.hash = ''; // clear hash (but leaves # unfortunately)
|
|
274
|
-
}
|
|
275
|
-
} catch (e) {
|
|
276
|
-
console.error('Unable to parse data from hash', e);
|
|
277
|
-
}
|
|
278
|
-
return editorParams;
|
|
279
|
-
},
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* To load the visual editor, we need an access token and other state. That state comes from one of three places:
|
|
283
|
-
* 1. In the URL hash params if the customer is using an old snippet
|
|
284
|
-
* 2. From session storage under the key `_mpcehash` if the snippet already parsed the hash
|
|
285
|
-
* 3. From session storage under the key `editorParams` if the editor was initialized on a previous page
|
|
286
|
-
*/
|
|
287
|
-
_maybeLoadEditor: function(instance) {
|
|
288
|
-
try {
|
|
289
|
-
var parseFromUrl = false;
|
|
290
|
-
if (_.getHashParam(window.location.hash, 'state')) {
|
|
291
|
-
var state = _.getHashParam(window.location.hash, 'state');
|
|
292
|
-
state = JSON.parse(decodeURIComponent(state));
|
|
293
|
-
parseFromUrl = state['action'] === 'mpeditor';
|
|
294
|
-
}
|
|
295
|
-
var parseFromStorage = !!window.sessionStorage.getItem('_mpcehash');
|
|
296
|
-
var editorParams;
|
|
297
|
-
|
|
298
|
-
if (parseFromUrl) { // happens if they are initializing the editor using an old snippet
|
|
299
|
-
editorParams = this._editorParamsFromHash(instance, window.location.hash);
|
|
300
|
-
} else if (parseFromStorage) { // happens if they are initialized the editor and using the new snippet
|
|
301
|
-
editorParams = this._editorParamsFromHash(instance, window.sessionStorage.getItem('_mpcehash'));
|
|
302
|
-
window.sessionStorage.removeItem('_mpcehash');
|
|
303
|
-
} else { // get credentials from sessionStorage from a previous initialzation
|
|
304
|
-
editorParams = JSON.parse(window.sessionStorage.getItem('editorParams') || '{}');
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (editorParams['projectToken'] && instance.get_config('token') === editorParams['projectToken']) {
|
|
308
|
-
this._loadEditor(instance, editorParams);
|
|
309
|
-
return true;
|
|
310
|
-
} else {
|
|
311
|
-
return false;
|
|
312
|
-
}
|
|
313
|
-
} catch (e) {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
},
|
|
317
|
-
|
|
318
|
-
_loadEditor: function(instance, editorParams) {
|
|
319
|
-
if (!window['_mpEditorLoaded']) { // only load the codeless event editor once, even if there are multiple instances of MixpanelLib
|
|
320
|
-
window['_mpEditorLoaded'] = true;
|
|
321
|
-
var editorUrl = instance.get_config('app_host')
|
|
322
|
-
+ '/js-bundle/reports/collect-everything/editor.js?_ts='
|
|
323
|
-
+ (new Date()).getTime();
|
|
324
|
-
this._loadScript(editorUrl, function() {
|
|
325
|
-
window['mp_load_editor'](editorParams);
|
|
326
|
-
});
|
|
327
|
-
return true;
|
|
328
|
-
}
|
|
329
|
-
return false;
|
|
330
|
-
},
|
|
331
|
-
|
|
332
|
-
// this is a mechanism to ramp up CE with no server-side interaction.
|
|
333
|
-
// when CE is active, every page load results in a decide request. we
|
|
334
|
-
// need to gently ramp this up so we don't overload decide. this decides
|
|
335
|
-
// deterministically if CE is enabled for this project by modding the char
|
|
336
|
-
// value of the project token.
|
|
337
|
-
enabledForProject: function(token, numBuckets, numEnabledBuckets) {
|
|
338
|
-
numBuckets = !_.isUndefined(numBuckets) ? numBuckets : 10;
|
|
339
|
-
numEnabledBuckets = !_.isUndefined(numEnabledBuckets) ? numEnabledBuckets : 10;
|
|
340
|
-
var charCodeSum = 0;
|
|
341
|
-
for (var i = 0; i < token.length; i++) {
|
|
342
|
-
charCodeSum += token.charCodeAt(i);
|
|
343
|
-
}
|
|
344
|
-
return (charCodeSum % numBuckets) < numEnabledBuckets;
|
|
345
|
-
},
|
|
346
|
-
|
|
347
|
-
isBrowserSupported: function() {
|
|
348
|
-
return _.isFunction(document.querySelectorAll);
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
_.bind_instance_methods(autotrack);
|
|
353
|
-
_.safewrap_instance_methods(autotrack);
|
|
354
|
-
|
|
355
|
-
export { autotrack };
|