mixpanel-browser 2.58.0 → 2.60.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixpanel-browser",
3
- "version": "2.58.0",
3
+ "version": "2.60.0",
4
4
  "description": "The official Mixpanel JavaScript browser client library",
5
5
  "main": "dist/mixpanel.cjs.js",
6
6
  "module": "dist/mixpanel.module.js",
@@ -0,0 +1,342 @@
1
+ import { _, document, safewrap, safewrapClass } from '../utils';
2
+ import { window } from '../window';
3
+ import {
4
+ getPropsForDOMEvent, logger, minDOMApisSupported,
5
+ EV_CHANGE, EV_CLICK, EV_HASHCHANGE, EV_MP_LOCATION_CHANGE, EV_POPSTATE,
6
+ EV_SCROLLEND, EV_SUBMIT
7
+ } from './utils';
8
+
9
+ var AUTOCAPTURE_CONFIG_KEY = 'autocapture';
10
+ var LEGACY_PAGEVIEW_CONFIG_KEY = 'track_pageview';
11
+
12
+ var PAGEVIEW_OPTION_FULL_URL = 'full-url';
13
+ var PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING = 'url-with-path-and-query-string';
14
+ var PAGEVIEW_OPTION_URL_WITH_PATH = 'url-with-path';
15
+
16
+ var CONFIG_ALLOW_ELEMENT_CALLBACK = 'allow_element_callback';
17
+ var CONFIG_ALLOW_SELECTORS = 'allow_selectors';
18
+ var CONFIG_ALLOW_URL_REGEXES = 'allow_url_regexes';
19
+ var CONFIG_BLOCK_ATTRS = 'block_attrs';
20
+ var CONFIG_BLOCK_ELEMENT_CALLBACK = 'block_element_callback';
21
+ var CONFIG_BLOCK_SELECTORS = 'block_selectors';
22
+ var CONFIG_BLOCK_URL_REGEXES = 'block_url_regexes';
23
+ var CONFIG_CAPTURE_EXTRA_ATTRS = 'capture_extra_attrs';
24
+ var CONFIG_CAPTURE_TEXT_CONTENT = 'capture_text_content';
25
+ var CONFIG_SCROLL_CAPTURE_ALL = 'scroll_capture_all';
26
+ var CONFIG_SCROLL_CHECKPOINTS = 'scroll_depth_percent_checkpoints';
27
+ var CONFIG_TRACK_CLICK = 'click';
28
+ var CONFIG_TRACK_INPUT = 'input';
29
+ var CONFIG_TRACK_PAGEVIEW = 'pageview';
30
+ var CONFIG_TRACK_SCROLL = 'scroll';
31
+ var CONFIG_TRACK_SUBMIT = 'submit';
32
+
33
+ var CONFIG_DEFAULTS = {};
34
+ CONFIG_DEFAULTS[CONFIG_ALLOW_SELECTORS] = [];
35
+ CONFIG_DEFAULTS[CONFIG_ALLOW_URL_REGEXES] = [];
36
+ CONFIG_DEFAULTS[CONFIG_BLOCK_ATTRS] = [];
37
+ CONFIG_DEFAULTS[CONFIG_BLOCK_ELEMENT_CALLBACK] = null;
38
+ CONFIG_DEFAULTS[CONFIG_BLOCK_SELECTORS] = [];
39
+ CONFIG_DEFAULTS[CONFIG_BLOCK_URL_REGEXES] = [];
40
+ CONFIG_DEFAULTS[CONFIG_CAPTURE_EXTRA_ATTRS] = [];
41
+ CONFIG_DEFAULTS[CONFIG_CAPTURE_TEXT_CONTENT] = false;
42
+ CONFIG_DEFAULTS[CONFIG_SCROLL_CAPTURE_ALL] = false;
43
+ CONFIG_DEFAULTS[CONFIG_SCROLL_CHECKPOINTS] = [25, 50, 75, 100];
44
+ CONFIG_DEFAULTS[CONFIG_TRACK_CLICK] = true;
45
+ CONFIG_DEFAULTS[CONFIG_TRACK_INPUT] = true;
46
+ CONFIG_DEFAULTS[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
47
+ CONFIG_DEFAULTS[CONFIG_TRACK_SCROLL] = true;
48
+ CONFIG_DEFAULTS[CONFIG_TRACK_SUBMIT] = true;
49
+
50
+ var DEFAULT_PROPS = {
51
+ '$mp_autocapture': true
52
+ };
53
+
54
+ var MP_EV_CLICK = '$mp_click';
55
+ var MP_EV_INPUT = '$mp_input_change';
56
+ var MP_EV_SCROLL = '$mp_scroll';
57
+ var MP_EV_SUBMIT = '$mp_submit';
58
+
59
+ /**
60
+ * Autocapture: manages automatic event tracking
61
+ * @constructor
62
+ */
63
+ var Autocapture = function(mp) {
64
+ this.mp = mp;
65
+ };
66
+
67
+ Autocapture.prototype.init = function() {
68
+ if (!minDOMApisSupported()) {
69
+ logger.critical('Autocapture unavailable: missing required DOM APIs');
70
+ return;
71
+ }
72
+
73
+ this.initPageviewTracking();
74
+ this.initClickTracking();
75
+ this.initInputTracking();
76
+ this.initScrollTracking();
77
+ this.initSubmitTracking();
78
+ };
79
+
80
+ Autocapture.prototype.getFullConfig = function() {
81
+ var autocaptureConfig = this.mp.get_config(AUTOCAPTURE_CONFIG_KEY);
82
+ if (!autocaptureConfig) {
83
+ // Autocapture is completely off
84
+ return {};
85
+ } else if (_.isObject(autocaptureConfig)) {
86
+ return _.extend({}, CONFIG_DEFAULTS, autocaptureConfig);
87
+ } else {
88
+ // Autocapture config is non-object truthy value, return default
89
+ return CONFIG_DEFAULTS;
90
+ }
91
+ };
92
+
93
+ Autocapture.prototype.getConfig = function(key) {
94
+ return this.getFullConfig()[key];
95
+ };
96
+
97
+ Autocapture.prototype.currentUrlBlocked = function() {
98
+ var i;
99
+ var currentUrl = _.info.currentUrl();
100
+
101
+ var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
102
+ if (allowUrlRegexes.length) {
103
+ // we're using an allowlist, only track if current URL matches
104
+ var allowed = false;
105
+ for (i = 0; i < allowUrlRegexes.length; i++) {
106
+ var allowRegex = allowUrlRegexes[i];
107
+ try {
108
+ if (currentUrl.match(allowRegex)) {
109
+ allowed = true;
110
+ break;
111
+ }
112
+ } catch (err) {
113
+ logger.critical('Error while checking block URL regex: ' + allowRegex, err);
114
+ return true;
115
+ }
116
+ }
117
+ if (!allowed) {
118
+ // wasn't allowed by any regex
119
+ return true;
120
+ }
121
+ }
122
+
123
+ var blockUrlRegexes = this.getConfig(CONFIG_BLOCK_URL_REGEXES) || [];
124
+ if (!blockUrlRegexes || !blockUrlRegexes.length) {
125
+ return false;
126
+ }
127
+
128
+ for (i = 0; i < blockUrlRegexes.length; i++) {
129
+ try {
130
+ if (currentUrl.match(blockUrlRegexes[i])) {
131
+ return true;
132
+ }
133
+ } catch (err) {
134
+ logger.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
135
+ return true;
136
+ }
137
+ }
138
+ return false;
139
+ };
140
+
141
+ Autocapture.prototype.pageviewTrackingConfig = function() {
142
+ // supports both autocapture config and old track_pageview config
143
+ if (this.mp.get_config(AUTOCAPTURE_CONFIG_KEY)) {
144
+ return this.getConfig(CONFIG_TRACK_PAGEVIEW);
145
+ } else {
146
+ return this.mp.get_config(LEGACY_PAGEVIEW_CONFIG_KEY);
147
+ }
148
+ };
149
+
150
+ // helper for event handlers
151
+ Autocapture.prototype.trackDomEvent = function(ev, mpEventName) {
152
+ if (this.currentUrlBlocked()) {
153
+ return;
154
+ }
155
+
156
+ var props = getPropsForDOMEvent(ev, {
157
+ allowElementCallback: this.getConfig(CONFIG_ALLOW_ELEMENT_CALLBACK),
158
+ allowSelectors: this.getConfig(CONFIG_ALLOW_SELECTORS),
159
+ blockAttrs: this.getConfig(CONFIG_BLOCK_ATTRS),
160
+ blockElementCallback: this.getConfig(CONFIG_BLOCK_ELEMENT_CALLBACK),
161
+ blockSelectors: this.getConfig(CONFIG_BLOCK_SELECTORS),
162
+ captureExtraAttrs: this.getConfig(CONFIG_CAPTURE_EXTRA_ATTRS),
163
+ captureTextContent: this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT)
164
+ });
165
+ if (props) {
166
+ _.extend(props, DEFAULT_PROPS);
167
+ this.mp.track(mpEventName, props);
168
+ }
169
+ };
170
+
171
+ Autocapture.prototype.initClickTracking = function() {
172
+ window.removeEventListener(EV_CLICK, this.listenerClick);
173
+
174
+ if (!this.getConfig(CONFIG_TRACK_CLICK)) {
175
+ return;
176
+ }
177
+ logger.log('Initializing click tracking');
178
+
179
+ this.listenerClick = window.addEventListener(EV_CLICK, function(ev) {
180
+ if (!this.getConfig(CONFIG_TRACK_CLICK)) {
181
+ return;
182
+ }
183
+ this.trackDomEvent(ev, MP_EV_CLICK);
184
+ }.bind(this));
185
+ };
186
+
187
+ Autocapture.prototype.initInputTracking = function() {
188
+ window.removeEventListener(EV_CHANGE, this.listenerChange);
189
+
190
+ if (!this.getConfig(CONFIG_TRACK_INPUT)) {
191
+ return;
192
+ }
193
+ logger.log('Initializing input tracking');
194
+
195
+ this.listenerChange = window.addEventListener(EV_CHANGE, function(ev) {
196
+ if (!this.getConfig(CONFIG_TRACK_INPUT)) {
197
+ return;
198
+ }
199
+ this.trackDomEvent(ev, MP_EV_INPUT);
200
+ }.bind(this));
201
+ };
202
+
203
+ Autocapture.prototype.initPageviewTracking = function() {
204
+ window.removeEventListener(EV_POPSTATE, this.listenerPopstate);
205
+ window.removeEventListener(EV_HASHCHANGE, this.listenerHashchange);
206
+ window.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
207
+
208
+ if (!this.pageviewTrackingConfig()) {
209
+ return;
210
+ }
211
+ logger.log('Initializing pageview tracking');
212
+
213
+ var previousTrackedUrl = '';
214
+ var tracked = false;
215
+ if (!this.currentUrlBlocked()) {
216
+ tracked = this.mp.track_pageview(DEFAULT_PROPS);
217
+ }
218
+ if (tracked) {
219
+ previousTrackedUrl = _.info.currentUrl();
220
+ }
221
+
222
+ this.listenerPopstate = window.addEventListener(EV_POPSTATE, function() {
223
+ window.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
224
+ });
225
+ this.listenerHashchange = window.addEventListener(EV_HASHCHANGE, function() {
226
+ window.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
227
+ });
228
+ var nativePushState = window.history.pushState;
229
+ if (typeof nativePushState === 'function') {
230
+ window.history.pushState = function(state, unused, url) {
231
+ nativePushState.call(window.history, state, unused, url);
232
+ window.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
233
+ };
234
+ }
235
+ var nativeReplaceState = window.history.replaceState;
236
+ if (typeof nativeReplaceState === 'function') {
237
+ window.history.replaceState = function(state, unused, url) {
238
+ nativeReplaceState.call(window.history, state, unused, url);
239
+ window.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
240
+ };
241
+ }
242
+ this.listenerLocationchange = window.addEventListener(EV_MP_LOCATION_CHANGE, safewrap(function() {
243
+ if (this.currentUrlBlocked()) {
244
+ return;
245
+ }
246
+
247
+ var currentUrl = _.info.currentUrl();
248
+ var shouldTrack = false;
249
+ var didPathChange = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
250
+ var trackPageviewOption = this.pageviewTrackingConfig();
251
+ if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
252
+ shouldTrack = currentUrl !== previousTrackedUrl;
253
+ } else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
254
+ shouldTrack = currentUrl.split('#')[0] !== previousTrackedUrl.split('#')[0];
255
+ } else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH) {
256
+ shouldTrack = didPathChange;
257
+ }
258
+
259
+ if (shouldTrack) {
260
+ var tracked = this.mp.track_pageview(DEFAULT_PROPS);
261
+ if (tracked) {
262
+ previousTrackedUrl = currentUrl;
263
+ }
264
+ if (didPathChange) {
265
+ this.lastScrollCheckpoint = 0;
266
+ logger.log('Path change: re-initializing scroll depth checkpoints');
267
+ }
268
+ }
269
+ }.bind(this)));
270
+ };
271
+
272
+ Autocapture.prototype.initScrollTracking = function() {
273
+ window.removeEventListener(EV_SCROLLEND, this.listenerScroll);
274
+
275
+ if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
276
+ return;
277
+ }
278
+ logger.log('Initializing scroll tracking');
279
+ this.lastScrollCheckpoint = 0;
280
+
281
+ this.listenerScroll = window.addEventListener(EV_SCROLLEND, safewrap(function() {
282
+ if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
283
+ return;
284
+ }
285
+ if (this.currentUrlBlocked()) {
286
+ return;
287
+ }
288
+
289
+ var shouldTrack = this.getConfig(CONFIG_SCROLL_CAPTURE_ALL);
290
+ var scrollCheckpoints = (this.getConfig(CONFIG_SCROLL_CHECKPOINTS) || [])
291
+ .slice()
292
+ .sort(function(a, b) { return a - b; });
293
+
294
+ var scrollTop = window.scrollY;
295
+ var props = _.extend({'$scroll_top': scrollTop}, DEFAULT_PROPS);
296
+ try {
297
+ var scrollHeight = document.body.scrollHeight;
298
+ var scrollPercentage = Math.round((scrollTop / (scrollHeight - window.innerHeight)) * 100);
299
+ props['$scroll_height'] = scrollHeight;
300
+ props['$scroll_percentage'] = scrollPercentage;
301
+ if (scrollPercentage > this.lastScrollCheckpoint) {
302
+ for (var i = 0; i < scrollCheckpoints.length; i++) {
303
+ var checkpoint = scrollCheckpoints[i];
304
+ if (
305
+ scrollPercentage >= checkpoint &&
306
+ this.lastScrollCheckpoint < checkpoint
307
+ ) {
308
+ props['$scroll_checkpoint'] = checkpoint;
309
+ this.lastScrollCheckpoint = checkpoint;
310
+ shouldTrack = true;
311
+ }
312
+ }
313
+ }
314
+ } catch (err) {
315
+ logger.critical('Error while calculating scroll percentage', err);
316
+ }
317
+ if (shouldTrack) {
318
+ this.mp.track(MP_EV_SCROLL, props);
319
+ }
320
+ }.bind(this)));
321
+ };
322
+
323
+ Autocapture.prototype.initSubmitTracking = function() {
324
+ window.removeEventListener(EV_SUBMIT, this.listenerSubmit);
325
+
326
+ if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
327
+ return;
328
+ }
329
+ logger.log('Initializing submit tracking');
330
+
331
+ this.listenerSubmit = window.addEventListener(EV_SUBMIT, function(ev) {
332
+ if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
333
+ return;
334
+ }
335
+ this.trackDomEvent(ev, MP_EV_SUBMIT);
336
+ }.bind(this));
337
+ };
338
+
339
+ // TODO integrate error_reporter from mixpanel instance
340
+ safewrapClass(Autocapture);
341
+
342
+ export { Autocapture };