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/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 };