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/doc/build-docs.js
CHANGED
|
@@ -97,6 +97,22 @@ function doxToMD(items) {
|
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// Captures prototype bracket notation property assignment, e.g.:
|
|
101
|
+
// `MixpanelGroup.prototype['delete'] = addOptOutCheckMixpanelGroup(function(callback) {`
|
|
102
|
+
// Based on https://github.com/tj/dox/blob/9fe92e17dfcd31c9b6512f6e5bf0b52c2b6b84d4/lib/dox.js#L592
|
|
103
|
+
dox.contextPatternMatchers.push(function (str) {
|
|
104
|
+
if (/^\s*([\w$.]+)\s*\.\s*prototype\s*\['\s*([\w$]+)'\]\s*=\s*([^\n;]+)/.exec(str)) {
|
|
105
|
+
return {
|
|
106
|
+
type: 'property'
|
|
107
|
+
, constructor: RegExp.$1
|
|
108
|
+
, cons: RegExp.$1
|
|
109
|
+
, name: RegExp.$2
|
|
110
|
+
, value: RegExp.$3.trim()
|
|
111
|
+
, string: RegExp.$1 + '.prototype.' + RegExp.$2
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
100
116
|
const rawCode = fs.readFileSync(SOURCE_FILE).toString().trim();
|
|
101
117
|
const parsed = dox.parseComments(rawCode);
|
|
102
118
|
|
|
@@ -990,6 +990,24 @@ mixpanel.people.unset(['gender', 'Company']);
|
|
|
990
990
|
# mixpanel.group
|
|
991
991
|
|
|
992
992
|
|
|
993
|
+
___
|
|
994
|
+
## mixpanel.group.delete
|
|
995
|
+
Permanently delete a group.
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
### Usage:
|
|
999
|
+
|
|
1000
|
+
```javascript
|
|
1001
|
+
mixpanel.get_group('company', 'mixpanel').delete();
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
| Argument | Type | Description |
|
|
1007
|
+
| ------------- | ------------- | ----- |
|
|
1008
|
+
| **callback** | <span class="mp-arg-type">Function</span></br></span><span class="mp-arg-optional">optional</span> | If provided, the callback will be called after the tracking event |
|
|
1009
|
+
|
|
1010
|
+
|
|
993
1011
|
___
|
|
994
1012
|
## mixpanel.group.remove
|
|
995
1013
|
Remove a property from a group. The value will be ignored if doesn't exist.
|
|
@@ -9,25 +9,6 @@ var MIXPANEL_LIB_URL = '//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js';
|
|
|
9
9
|
(function(document, mixpanel) {
|
|
10
10
|
// Only stub out if this is the first time running the snippet.
|
|
11
11
|
if (!mixpanel['__SV']) {
|
|
12
|
-
var win = window;
|
|
13
|
-
|
|
14
|
-
// grab the hash params for ce editor immediately in case
|
|
15
|
-
// host website changes hash after init
|
|
16
|
-
try {
|
|
17
|
-
var getHashParam, matches, state, loc = win.location, hash = loc.hash;
|
|
18
|
-
getHashParam = function(hash, param) {
|
|
19
|
-
matches = hash.match(new RegExp(param + '=([^&]*)'));
|
|
20
|
-
return matches ? matches[1] : null;
|
|
21
|
-
};
|
|
22
|
-
if (hash && getHashParam(hash, 'state')) {
|
|
23
|
-
state = JSON.parse(decodeURIComponent(getHashParam(hash, 'state')));
|
|
24
|
-
if (state['action'] === 'mpeditor') {
|
|
25
|
-
win.sessionStorage.setItem('_mpcehash', hash);
|
|
26
|
-
history.replaceState(state['desiredHash'] || '', document.title, loc.pathname + loc.search); // remove ce editor hash
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
} catch (e) {}
|
|
30
|
-
|
|
31
12
|
var script, first_script, gen_fn, functions, i, lib_name = "mixpanel";
|
|
32
13
|
window[lib_name] = mixpanel;
|
|
33
14
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mixpanel-browser",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.43.0",
|
|
4
4
|
"description": "The official Mixpanel JavaScript browser client library",
|
|
5
5
|
"main": "dist/mixpanel.cjs.js",
|
|
6
6
|
"directories": {
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
"jsdom": "11.12.0",
|
|
47
47
|
"jsdom-global": "3.0.2",
|
|
48
48
|
"localStorage": "1.0.4",
|
|
49
|
-
"lodash": "4.17.
|
|
49
|
+
"lodash": "4.17.21",
|
|
50
50
|
"mocha": "7.1.1",
|
|
51
51
|
"morgan": "1.9.1",
|
|
52
|
-
"rdme": "
|
|
52
|
+
"rdme": "4.0.0",
|
|
53
53
|
"request": "2.88.0",
|
|
54
54
|
"rollup": "0.25.8",
|
|
55
55
|
"rollup-plugin-npm": "1.4.0",
|
package/src/config.js
CHANGED
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';
|
|
@@ -57,6 +56,8 @@ var IDENTITY_FUNC = function(x) {return x;};
|
|
|
57
56
|
var NOOP_FUNC = function() {};
|
|
58
57
|
|
|
59
58
|
/** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel';
|
|
59
|
+
/** @const */ var PAYLOAD_TYPE_BASE64 = 'base64';
|
|
60
|
+
/** @const */ var PAYLOAD_TYPE_JSON = 'json';
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
/*
|
|
@@ -87,8 +88,8 @@ var DEFAULT_CONFIG = {
|
|
|
87
88
|
'api_host': 'https://api-js.mixpanel.com',
|
|
88
89
|
'api_method': 'POST',
|
|
89
90
|
'api_transport': 'XHR',
|
|
91
|
+
'api_payload_format': PAYLOAD_TYPE_BASE64,
|
|
90
92
|
'app_host': 'https://mixpanel.com',
|
|
91
|
-
'autotrack': true,
|
|
92
93
|
'cdn': 'https://cdn.mxpnl.com',
|
|
93
94
|
'cross_site_cookie': false,
|
|
94
95
|
'cross_subdomain_cookie': true,
|
|
@@ -119,7 +120,7 @@ var DEFAULT_CONFIG = {
|
|
|
119
120
|
'inapp_protocol': '//',
|
|
120
121
|
'inapp_link_new_window': false,
|
|
121
122
|
'ignore_dnt': false,
|
|
122
|
-
'batch_requests':
|
|
123
|
+
'batch_requests': true,
|
|
123
124
|
'batch_size': 50,
|
|
124
125
|
'batch_flush_interval_ms': 5000,
|
|
125
126
|
'batch_request_timeout_ms': 90000,
|
|
@@ -171,21 +172,6 @@ var create_mplib = function(token, config, name) {
|
|
|
171
172
|
// global debug to be true
|
|
172
173
|
Config.DEBUG = Config.DEBUG || instance.get_config('debug');
|
|
173
174
|
|
|
174
|
-
instance['__autotrack_enabled'] = instance.get_config('autotrack');
|
|
175
|
-
if (instance.get_config('autotrack')) {
|
|
176
|
-
var num_buckets = 100;
|
|
177
|
-
var num_enabled_buckets = 100;
|
|
178
|
-
if (!autotrack.enabledForProject(instance.get_config('token'), num_buckets, num_enabled_buckets)) {
|
|
179
|
-
instance['__autotrack_enabled'] = false;
|
|
180
|
-
console.log('Not in active bucket: disabling Automatic Event Collection.');
|
|
181
|
-
} else if (!autotrack.isBrowserSupported()) {
|
|
182
|
-
instance['__autotrack_enabled'] = false;
|
|
183
|
-
console.log('Disabling Automatic Event Collection because this browser is not supported');
|
|
184
|
-
} else {
|
|
185
|
-
autotrack.init(instance);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
175
|
// if target is not defined, we called init after the lib already
|
|
190
176
|
// loaded, so there won't be an array of things to execute
|
|
191
177
|
if (!_.isUndefined(target) && _.isArray(target)) {
|
|
@@ -198,12 +184,6 @@ var create_mplib = function(token, config, name) {
|
|
|
198
184
|
return instance;
|
|
199
185
|
};
|
|
200
186
|
|
|
201
|
-
var encode_data_for_request = function(data) {
|
|
202
|
-
var json_data = _.JSONEncode(data);
|
|
203
|
-
var encoded_data = _.base64Encode(json_data);
|
|
204
|
-
return {'data': encoded_data};
|
|
205
|
-
};
|
|
206
|
-
|
|
207
187
|
// Initialization methods
|
|
208
188
|
|
|
209
189
|
/**
|
|
@@ -253,14 +233,14 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
253
233
|
this['config'] = {};
|
|
254
234
|
this['_triggered_notifs'] = [];
|
|
255
235
|
|
|
256
|
-
// rollout: enable batch_requests by default for 60% of projects
|
|
257
|
-
// (only if they have not specified a value in their init config
|
|
258
|
-
// and they aren't using a custom API host)
|
|
259
236
|
var variable_features = {};
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (!('
|
|
263
|
-
|
|
237
|
+
|
|
238
|
+
// default to JSON payload for standard mixpanel.com API hosts
|
|
239
|
+
if (!('api_payload_format' in config)) {
|
|
240
|
+
var api_host = config['api_host'] || DEFAULT_CONFIG['api_host'];
|
|
241
|
+
if (api_host.match(/\.mixpanel\.com$/)) {
|
|
242
|
+
variable_features['api_payload_format'] = PAYLOAD_TYPE_JSON;
|
|
243
|
+
}
|
|
264
244
|
}
|
|
265
245
|
|
|
266
246
|
this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, {
|
|
@@ -289,15 +269,32 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
289
269
|
} else {
|
|
290
270
|
this.init_batchers();
|
|
291
271
|
if (sendBeacon && window.addEventListener) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
272
|
+
// Before page closes or hides (user tabs away etc), attempt to flush any events
|
|
273
|
+
// queued up via navigator.sendBeacon. Since sendBeacon doesn't report success/failure,
|
|
274
|
+
// events will not be removed from the persistent store; if the site is loaded again,
|
|
275
|
+
// the events will be flushed again on startup and deduplicated on the Mixpanel server
|
|
276
|
+
// side.
|
|
277
|
+
// There is no reliable way to capture only page close events, so we lean on the
|
|
278
|
+
// visibilitychange and pagehide events as recommended at
|
|
279
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event#usage_notes.
|
|
280
|
+
// These events fire when the user clicks away from the current page/tab, so will occur
|
|
281
|
+
// more frequently than page unload, but are the only mechanism currently for capturing
|
|
282
|
+
// this scenario somewhat reliably.
|
|
283
|
+
var flush_on_unload = _.bind(function() {
|
|
297
284
|
if (!this.request_batchers.events.stopped) {
|
|
298
285
|
this.request_batchers.events.flush({unloading: true});
|
|
299
286
|
}
|
|
300
|
-
}, this)
|
|
287
|
+
}, this);
|
|
288
|
+
window.addEventListener('pagehide', function(ev) {
|
|
289
|
+
if (ev['persisted']) {
|
|
290
|
+
flush_on_unload();
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
window.addEventListener('visibilitychange', function() {
|
|
294
|
+
if (document['visibilityState'] === 'hidden') {
|
|
295
|
+
flush_on_unload();
|
|
296
|
+
}
|
|
297
|
+
});
|
|
301
298
|
}
|
|
302
299
|
}
|
|
303
300
|
}
|
|
@@ -622,7 +619,7 @@ MixpanelLib.prototype.init_batchers = function() {
|
|
|
622
619
|
sendRequestFunc: _.bind(function(data, options, cb) {
|
|
623
620
|
this._send_request(
|
|
624
621
|
this.get_config('api_host') + attrs.endpoint,
|
|
625
|
-
|
|
622
|
+
this._encode_data_for_request(data),
|
|
626
623
|
options,
|
|
627
624
|
this._prepare_callback(cb, data)
|
|
628
625
|
);
|
|
@@ -696,6 +693,14 @@ MixpanelLib.prototype.disable = function(events) {
|
|
|
696
693
|
}
|
|
697
694
|
};
|
|
698
695
|
|
|
696
|
+
MixpanelLib.prototype._encode_data_for_request = function(data) {
|
|
697
|
+
var encoded_data = _.JSONEncode(data);
|
|
698
|
+
if (this.get_config('api_payload_format') === PAYLOAD_TYPE_BASE64) {
|
|
699
|
+
encoded_data = _.base64Encode(encoded_data);
|
|
700
|
+
}
|
|
701
|
+
return {'data': encoded_data};
|
|
702
|
+
};
|
|
703
|
+
|
|
699
704
|
// internal method for handling track vs batch-enqueue logic
|
|
700
705
|
MixpanelLib.prototype._track_or_batch = function(options, callback) {
|
|
701
706
|
var truncated_data = _.truncate(options.data, 255);
|
|
@@ -715,7 +720,7 @@ MixpanelLib.prototype._track_or_batch = function(options, callback) {
|
|
|
715
720
|
console.log(truncated_data);
|
|
716
721
|
return this._send_request(
|
|
717
722
|
endpoint,
|
|
718
|
-
|
|
723
|
+
this._encode_data_for_request(truncated_data),
|
|
719
724
|
send_request_options,
|
|
720
725
|
this._prepare_callback(callback, truncated_data)
|
|
721
726
|
);
|
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
|
});
|
package/src/request-batcher.js
CHANGED
|
@@ -148,9 +148,9 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
148
148
|
} else if (
|
|
149
149
|
_.isObject(res) &&
|
|
150
150
|
res.xhr_req &&
|
|
151
|
-
(res.xhr_req['status'] >= 500 || res.xhr_req['status']
|
|
151
|
+
(res.xhr_req['status'] >= 500 || res.xhr_req['status'] === 429 || res.error === 'timeout')
|
|
152
152
|
) {
|
|
153
|
-
// network or API error, retry
|
|
153
|
+
// network or API error, or 429 Too Many Requests, retry
|
|
154
154
|
var retryMS = this.flushInterval * 2;
|
|
155
155
|
var headers = res.xhr_req['responseHeaders'];
|
|
156
156
|
if (headers) {
|
package/src/utils.js
CHANGED
|
@@ -279,10 +279,6 @@ _.values = function(obj) {
|
|
|
279
279
|
return results;
|
|
280
280
|
};
|
|
281
281
|
|
|
282
|
-
_.identity = function(value) {
|
|
283
|
-
return value;
|
|
284
|
-
};
|
|
285
|
-
|
|
286
282
|
_.include = function(obj, target) {
|
|
287
283
|
var found = false;
|
|
288
284
|
if (obj === null) {
|
|
@@ -946,10 +942,12 @@ _.UUID = (function() {
|
|
|
946
942
|
// This is to block various web spiders from executing our JS and
|
|
947
943
|
// sending false tracking data
|
|
948
944
|
var BLOCKED_UA_STRS = [
|
|
945
|
+
'ahrefsbot',
|
|
949
946
|
'baiduspider',
|
|
950
947
|
'bingbot',
|
|
951
948
|
'bingpreview',
|
|
952
949
|
'facebookexternal',
|
|
950
|
+
'petalbot',
|
|
953
951
|
'pinterest',
|
|
954
952
|
'screaming frog',
|
|
955
953
|
'yahoo! slurp',
|
|
@@ -1020,10 +1018,6 @@ _.getQueryParam = function(url, param) {
|
|
|
1020
1018
|
}
|
|
1021
1019
|
};
|
|
1022
1020
|
|
|
1023
|
-
_.getHashParam = function(hash, param) {
|
|
1024
|
-
var matches = hash.match(new RegExp(param + '=([^&]*)'));
|
|
1025
|
-
return matches ? matches[1] : null;
|
|
1026
|
-
};
|
|
1027
1021
|
|
|
1028
1022
|
// _.cookie
|
|
1029
1023
|
// Methods partially borrowed from quirksmode.org/js/cookies.html
|
|
@@ -1690,28 +1684,6 @@ var cheap_guid = function(maxlen) {
|
|
|
1690
1684
|
return maxlen ? guid.substring(0, maxlen) : guid;
|
|
1691
1685
|
};
|
|
1692
1686
|
|
|
1693
|
-
/**
|
|
1694
|
-
* Check deterministically whether to include or exclude from a feature rollout/test based on the
|
|
1695
|
-
* given string and the desired percentage to include.
|
|
1696
|
-
* @param {String} str - string to run the check against (for instance a project's token)
|
|
1697
|
-
* @param {String} feature - name of feature (for inclusion in hash, to ensure different results
|
|
1698
|
-
* for different features)
|
|
1699
|
-
* @param {Number} percent_allowed - percentage chance that a given string will be included
|
|
1700
|
-
* @returns {Boolean} whether the given string should be included
|
|
1701
|
-
*/
|
|
1702
|
-
var determine_eligibility = _.safewrap(function(str, feature, percent_allowed) {
|
|
1703
|
-
str = str + feature;
|
|
1704
|
-
|
|
1705
|
-
// Bernstein's hash: http://www.cse.yorku.ca/~oz/hash.html#djb2
|
|
1706
|
-
var hash = 5381;
|
|
1707
|
-
for (var i = 0; i < str.length; i++) {
|
|
1708
|
-
hash = ((hash << 5) + hash) + str.charCodeAt(i);
|
|
1709
|
-
hash = hash & hash;
|
|
1710
|
-
}
|
|
1711
|
-
var dart = (hash >>> 0) % 100;
|
|
1712
|
-
return dart < percent_allowed;
|
|
1713
|
-
});
|
|
1714
|
-
|
|
1715
1687
|
// naive way to extract domain name (example.com) from full hostname (my.sub.example.com)
|
|
1716
1688
|
var SIMPLE_DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]*\.[a-z]+$/i;
|
|
1717
1689
|
// this next one attempts to account for some ccSLDs, e.g. extracting oxford.ac.uk from www.oxford.ac.uk
|
|
@@ -1770,7 +1742,6 @@ export {
|
|
|
1770
1742
|
navigator,
|
|
1771
1743
|
cheap_guid,
|
|
1772
1744
|
console_with_prefix,
|
|
1773
|
-
determine_eligibility,
|
|
1774
1745
|
extract_domain,
|
|
1775
1746
|
localStorageSupported,
|
|
1776
1747
|
JSONStringify,
|
package/tunnel.log
ADDED
|
File without changes
|
package/.travis.yml
DELETED
package/src/autotrack-utils.js
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import { _ } from './utils';
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
* Get the className of an element, accounting for edge cases where element.className is an object
|
|
5
|
-
* @param {Element} el - element to get the className of
|
|
6
|
-
* @returns {string} the element's class
|
|
7
|
-
*/
|
|
8
|
-
export function getClassName(el) {
|
|
9
|
-
switch(typeof el.className) {
|
|
10
|
-
case 'string':
|
|
11
|
-
return el.className;
|
|
12
|
-
case 'object': // handle cases where className might be SVGAnimatedString or some other type
|
|
13
|
-
return el.className.baseVal || el.getAttribute('class') || '';
|
|
14
|
-
default: // future proof
|
|
15
|
-
return '';
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/*
|
|
20
|
-
* Get the direct text content of an element, protecting against sensitive data collection.
|
|
21
|
-
* Concats textContent of each of the element's text node children; this avoids potential
|
|
22
|
-
* collection of sensitive data that could happen if we used element.textContent and the
|
|
23
|
-
* element had sensitive child elements, since element.textContent includes child content.
|
|
24
|
-
* Scrubs values that look like they could be sensitive (i.e. cc or ssn number).
|
|
25
|
-
* @param {Element} el - element to get the text of
|
|
26
|
-
* @returns {string} the element's direct text content
|
|
27
|
-
*/
|
|
28
|
-
export function getSafeText(el) {
|
|
29
|
-
var elText = '';
|
|
30
|
-
|
|
31
|
-
if (shouldTrackElement(el) && el.childNodes && el.childNodes.length) {
|
|
32
|
-
_.each(el.childNodes, function(child) {
|
|
33
|
-
if (isTextNode(child) && child.textContent) {
|
|
34
|
-
elText += _.trim(child.textContent)
|
|
35
|
-
// scrub potentially sensitive values
|
|
36
|
-
.split(/(\s+)/).filter(shouldTrackValue).join('')
|
|
37
|
-
// normalize whitespace
|
|
38
|
-
.replace(/[\r\n]/g, ' ').replace(/[ ]+/g, ' ')
|
|
39
|
-
// truncate
|
|
40
|
-
.substring(0, 255);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return _.trim(elText);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/*
|
|
49
|
-
* Check whether an element has nodeType Node.ELEMENT_NODE
|
|
50
|
-
* @param {Element} el - element to check
|
|
51
|
-
* @returns {boolean} whether el is of the correct nodeType
|
|
52
|
-
*/
|
|
53
|
-
export function isElementNode(el) {
|
|
54
|
-
return el && el.nodeType === 1; // Node.ELEMENT_NODE - use integer constant for browser portability
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/*
|
|
58
|
-
* Check whether an element is of a given tag type.
|
|
59
|
-
* Due to potential reference discrepancies (such as the webcomponents.js polyfill),
|
|
60
|
-
* we want to match tagNames instead of specific references because something like
|
|
61
|
-
* element === document.body won't always work because element might not be a native
|
|
62
|
-
* element.
|
|
63
|
-
* @param {Element} el - element to check
|
|
64
|
-
* @param {string} tag - tag name (e.g., "div")
|
|
65
|
-
* @returns {boolean} whether el is of the given tag type
|
|
66
|
-
*/
|
|
67
|
-
export function isTag(el, tag) {
|
|
68
|
-
return el && el.tagName && el.tagName.toLowerCase() === tag.toLowerCase();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/*
|
|
72
|
-
* Check whether an element has nodeType Node.TEXT_NODE
|
|
73
|
-
* @param {Element} el - element to check
|
|
74
|
-
* @returns {boolean} whether el is of the correct nodeType
|
|
75
|
-
*/
|
|
76
|
-
export function isTextNode(el) {
|
|
77
|
-
return el && el.nodeType === 3; // Node.TEXT_NODE - use integer constant for browser portability
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/*
|
|
81
|
-
* Check whether a DOM event should be "tracked" or if it may contain sentitive data
|
|
82
|
-
* using a variety of heuristics.
|
|
83
|
-
* @param {Element} el - element to check
|
|
84
|
-
* @param {Event} event - event to check
|
|
85
|
-
* @returns {boolean} whether the event should be tracked
|
|
86
|
-
*/
|
|
87
|
-
export function shouldTrackDomEvent(el, event) {
|
|
88
|
-
if (!el || isTag(el, 'html') || !isElementNode(el)) {
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
var tag = el.tagName.toLowerCase();
|
|
92
|
-
switch (tag) {
|
|
93
|
-
case 'html':
|
|
94
|
-
return false;
|
|
95
|
-
case 'form':
|
|
96
|
-
return event.type === 'submit';
|
|
97
|
-
case 'input':
|
|
98
|
-
if (['button', 'submit'].indexOf(el.getAttribute('type')) === -1) {
|
|
99
|
-
return event.type === 'change';
|
|
100
|
-
} else {
|
|
101
|
-
return event.type === 'click';
|
|
102
|
-
}
|
|
103
|
-
case 'select':
|
|
104
|
-
case 'textarea':
|
|
105
|
-
return event.type === 'change';
|
|
106
|
-
default:
|
|
107
|
-
return event.type === 'click';
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/*
|
|
112
|
-
* Check whether a DOM element should be "tracked" or if it may contain sentitive data
|
|
113
|
-
* using a variety of heuristics.
|
|
114
|
-
* @param {Element} el - element to check
|
|
115
|
-
* @returns {boolean} whether the element should be tracked
|
|
116
|
-
*/
|
|
117
|
-
export function shouldTrackElement(el) {
|
|
118
|
-
for (var curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode) {
|
|
119
|
-
var classes = getClassName(curEl).split(' ');
|
|
120
|
-
if (_.includes(classes, 'mp-sensitive') || _.includes(classes, 'mp-no-track')) {
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (_.includes(getClassName(el).split(' '), 'mp-include')) {
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// don't send data from inputs or similar elements since there will always be
|
|
130
|
-
// a risk of clientside javascript placing sensitive data in attributes
|
|
131
|
-
if (
|
|
132
|
-
isTag(el, 'input') ||
|
|
133
|
-
isTag(el, 'select') ||
|
|
134
|
-
isTag(el, 'textarea') ||
|
|
135
|
-
el.getAttribute('contenteditable') === 'true'
|
|
136
|
-
) {
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// don't include hidden or password fields
|
|
141
|
-
var type = el.type || '';
|
|
142
|
-
if (typeof type === 'string') { // it's possible for el.type to be a DOM element if el is a form with a child input[name="type"]
|
|
143
|
-
switch(type.toLowerCase()) {
|
|
144
|
-
case 'hidden':
|
|
145
|
-
return false;
|
|
146
|
-
case 'password':
|
|
147
|
-
return false;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// filter out data from fields that look like sensitive fields
|
|
152
|
-
var name = el.name || el.id || '';
|
|
153
|
-
if (typeof name === 'string') { // it's possible for el.name or el.id to be a DOM element if el is a form with a child input[name="name"]
|
|
154
|
-
var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
|
|
155
|
-
if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/*
|
|
164
|
-
* Check whether a string value should be "tracked" or if it may contain sentitive data
|
|
165
|
-
* using a variety of heuristics.
|
|
166
|
-
* @param {string} value - string value to check
|
|
167
|
-
* @returns {boolean} whether the element should be tracked
|
|
168
|
-
*/
|
|
169
|
-
export function shouldTrackValue(value) {
|
|
170
|
-
if (value === null || _.isUndefined(value)) {
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (typeof value === 'string') {
|
|
175
|
-
value = _.trim(value);
|
|
176
|
-
|
|
177
|
-
// check to see if input value looks like a credit card number
|
|
178
|
-
// see: https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s20.html
|
|
179
|
-
var ccRegex = /^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))$/;
|
|
180
|
-
if (ccRegex.test((value || '').replace(/[- ]/g, ''))) {
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// check to see if input value looks like a social security number
|
|
185
|
-
var ssnRegex = /(^\d{3}-?\d{2}-?\d{4}$)/;
|
|
186
|
-
if (ssnRegex.test(value)) {
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return true;
|
|
192
|
-
}
|