mixpanel-browser 2.63.0 → 2.64.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/CHANGELOG.md +4 -0
- package/dist/mixpanel-core.cjs.js +252 -40
- package/dist/mixpanel-recorder.js +1 -1
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +252 -40
- package/dist/mixpanel-with-recorder.js +273 -61
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.js +273 -61
- package/dist/mixpanel.cjs.js +273 -61
- package/dist/mixpanel.globals.js +252 -40
- package/dist/mixpanel.min.js +149 -143
- package/dist/mixpanel.module.js +273 -61
- package/dist/mixpanel.umd.js +273 -61
- package/package.json +1 -1
- package/src/autocapture/index.js +4 -3
- package/src/autocapture/utils.js +4 -0
- package/src/config.js +1 -1
- package/src/flags/index.js +191 -0
- package/src/mixpanel-core.js +24 -3
package/src/autocapture/index.js
CHANGED
|
@@ -160,7 +160,8 @@ Autocapture.prototype.trackDomEvent = function(ev, mpEventName) {
|
|
|
160
160
|
blockElementCallback: this.getConfig(CONFIG_BLOCK_ELEMENT_CALLBACK),
|
|
161
161
|
blockSelectors: this.getConfig(CONFIG_BLOCK_SELECTORS),
|
|
162
162
|
captureExtraAttrs: this.getConfig(CONFIG_CAPTURE_EXTRA_ATTRS),
|
|
163
|
-
captureTextContent: this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT)
|
|
163
|
+
captureTextContent: this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT),
|
|
164
|
+
capturedForHeatMap: mpEventName === MP_EV_CLICK && !this.getConfig(CONFIG_TRACK_CLICK) && this.mp.is_recording_heatmap_data(),
|
|
164
165
|
});
|
|
165
166
|
if (props) {
|
|
166
167
|
_.extend(props, DEFAULT_PROPS);
|
|
@@ -171,13 +172,13 @@ Autocapture.prototype.trackDomEvent = function(ev, mpEventName) {
|
|
|
171
172
|
Autocapture.prototype.initClickTracking = function() {
|
|
172
173
|
window.removeEventListener(EV_CLICK, this.listenerClick);
|
|
173
174
|
|
|
174
|
-
if (!this.getConfig(CONFIG_TRACK_CLICK)) {
|
|
175
|
+
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
|
|
175
176
|
return;
|
|
176
177
|
}
|
|
177
178
|
logger.log('Initializing click tracking');
|
|
178
179
|
|
|
179
180
|
this.listenerClick = window.addEventListener(EV_CLICK, function(ev) {
|
|
180
|
-
if (!this.getConfig(CONFIG_TRACK_CLICK)) {
|
|
181
|
+
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
|
|
181
182
|
return;
|
|
182
183
|
}
|
|
183
184
|
this.trackDomEvent(ev, MP_EV_CLICK);
|
package/src/autocapture/utils.js
CHANGED
|
@@ -114,6 +114,7 @@ function getPropsForDOMEvent(ev, config) {
|
|
|
114
114
|
var blockSelectors = config.blockSelectors || [];
|
|
115
115
|
var captureTextContent = config.captureTextContent || false;
|
|
116
116
|
var captureExtraAttrs = config.captureExtraAttrs || [];
|
|
117
|
+
var capturedForHeatMap = config.capturedForHeatMap || false;
|
|
117
118
|
|
|
118
119
|
// convert array to set every time, as the config may have changed
|
|
119
120
|
var blockAttrsSet = {};
|
|
@@ -192,6 +193,9 @@ function getPropsForDOMEvent(ev, config) {
|
|
|
192
193
|
props['$' + prop] = ev[prop];
|
|
193
194
|
}
|
|
194
195
|
});
|
|
196
|
+
if (capturedForHeatMap) {
|
|
197
|
+
props['$captured_for_heatmap'] = true;
|
|
198
|
+
}
|
|
195
199
|
target = guessRealClickTarget(ev);
|
|
196
200
|
}
|
|
197
201
|
// prioritize text content from "real" click target if different from original target
|
package/src/config.js
CHANGED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { _, console_with_prefix, safewrapClass } from '../utils'; // eslint-disable-line camelcase
|
|
2
|
+
import { window } from '../window';
|
|
3
|
+
|
|
4
|
+
var fetch = window['fetch'];
|
|
5
|
+
var logger = console_with_prefix('flags');
|
|
6
|
+
|
|
7
|
+
var FLAGS_CONFIG_KEY = 'flags';
|
|
8
|
+
|
|
9
|
+
var CONFIG_CONTEXT = 'context';
|
|
10
|
+
var CONFIG_DEFAULTS = {};
|
|
11
|
+
CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* FeatureFlagManager: support for Mixpanel's feature flagging product
|
|
15
|
+
* @constructor
|
|
16
|
+
*/
|
|
17
|
+
var FeatureFlagManager = function(initOptions) {
|
|
18
|
+
this.getMpConfig = initOptions.getConfigFunc;
|
|
19
|
+
this.getDistinctId = initOptions.getDistinctIdFunc;
|
|
20
|
+
this.track = initOptions.trackingFunc;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
FeatureFlagManager.prototype.init = function() {
|
|
24
|
+
if (!minApisSupported()) {
|
|
25
|
+
logger.critical('Feature Flags unavailable: missing minimum required APIs');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.flags = null;
|
|
30
|
+
this.fetchFlags();
|
|
31
|
+
|
|
32
|
+
this.trackedFeatures = new Set();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
FeatureFlagManager.prototype.getFullConfig = function() {
|
|
36
|
+
var ffConfig = this.getMpConfig(FLAGS_CONFIG_KEY);
|
|
37
|
+
if (!ffConfig) {
|
|
38
|
+
// flags are completely off
|
|
39
|
+
return {};
|
|
40
|
+
} else if (_.isObject(ffConfig)) {
|
|
41
|
+
return _.extend({}, CONFIG_DEFAULTS, ffConfig);
|
|
42
|
+
} else {
|
|
43
|
+
// config is non-object truthy value, return default
|
|
44
|
+
return CONFIG_DEFAULTS;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
FeatureFlagManager.prototype.getConfig = function(key) {
|
|
49
|
+
return this.getFullConfig()[key];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
FeatureFlagManager.prototype.isEnabled = function() {
|
|
53
|
+
return !!this.getMpConfig(FLAGS_CONFIG_KEY);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
FeatureFlagManager.prototype.areFeaturesReady = function() {
|
|
57
|
+
if (!this.isEnabled()) {
|
|
58
|
+
logger.error('Feature Flags not enabled');
|
|
59
|
+
}
|
|
60
|
+
return !!this.flags;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
FeatureFlagManager.prototype.fetchFlags = function() {
|
|
64
|
+
if (!this.isEnabled()) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
var distinctId = this.getDistinctId();
|
|
69
|
+
logger.log('Fetching flags for distinct ID: ' + distinctId);
|
|
70
|
+
var reqParams = {
|
|
71
|
+
'context': _.extend({'distinct_id': distinctId}, this.getConfig(CONFIG_CONTEXT))
|
|
72
|
+
};
|
|
73
|
+
this.fetchPromise = window['fetch'](this.getMpConfig('api_host') + '/' + this.getMpConfig('api_routes')['flags'], {
|
|
74
|
+
'method': 'POST',
|
|
75
|
+
'headers': {
|
|
76
|
+
'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
|
|
77
|
+
'Content-Type': 'application/octet-stream'
|
|
78
|
+
},
|
|
79
|
+
'body': JSON.stringify(reqParams)
|
|
80
|
+
}).then(function(response) {
|
|
81
|
+
return response.json().then(function(responseBody) {
|
|
82
|
+
var responseFlags = responseBody['flags'];
|
|
83
|
+
if (!responseFlags) {
|
|
84
|
+
throw new Error('No flags in API response');
|
|
85
|
+
}
|
|
86
|
+
var flags = new Map();
|
|
87
|
+
_.each(responseFlags, function(data, key) {
|
|
88
|
+
flags.set(key, {
|
|
89
|
+
'key': data['variant_key'],
|
|
90
|
+
'data': data['variant_value']
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
this.flags = flags;
|
|
94
|
+
}.bind(this)).catch(function(error) {
|
|
95
|
+
logger.error(error);
|
|
96
|
+
});
|
|
97
|
+
}.bind(this)).catch(function() {});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
FeatureFlagManager.prototype.getFeature = function(featureName, fallback) {
|
|
101
|
+
if (!this.fetchPromise) {
|
|
102
|
+
return new Promise(function(resolve) {
|
|
103
|
+
logger.critical('Feature Flags not initialized');
|
|
104
|
+
resolve(fallback);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return this.fetchPromise.then(function() {
|
|
109
|
+
return this.getFeatureSync(featureName, fallback);
|
|
110
|
+
}.bind(this)).catch(function(error) {
|
|
111
|
+
logger.error(error);
|
|
112
|
+
return fallback;
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
FeatureFlagManager.prototype.getFeatureSync = function(featureName, fallback) {
|
|
117
|
+
if (!this.areFeaturesReady()) {
|
|
118
|
+
logger.log('Flags not loaded yet');
|
|
119
|
+
return fallback;
|
|
120
|
+
}
|
|
121
|
+
var feature = this.flags.get(featureName);
|
|
122
|
+
if (!feature) {
|
|
123
|
+
logger.log('No flag found: "' + featureName + '"');
|
|
124
|
+
return fallback;
|
|
125
|
+
}
|
|
126
|
+
this.trackFeatureCheck(featureName, feature);
|
|
127
|
+
return feature;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
FeatureFlagManager.prototype.getFeatureData = function(featureName, fallbackValue) {
|
|
131
|
+
return this.getFeature(featureName, {'data': fallbackValue}).then(function(feature) {
|
|
132
|
+
return feature['data'];
|
|
133
|
+
}).catch(function(error) {
|
|
134
|
+
logger.error(error);
|
|
135
|
+
return fallbackValue;
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
FeatureFlagManager.prototype.getFeatureDataSync = function(featureName, fallbackValue) {
|
|
140
|
+
return this.getFeatureSync(featureName, {'data': fallbackValue})['data'];
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
FeatureFlagManager.prototype.isFeatureEnabled = function(featureName, fallbackValue) {
|
|
144
|
+
return this.getFeatureData(featureName).then(function() {
|
|
145
|
+
return this.isFeatureEnabledSync(featureName, fallbackValue);
|
|
146
|
+
}.bind(this)).catch(function(error) {
|
|
147
|
+
logger.error(error);
|
|
148
|
+
return fallbackValue;
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
FeatureFlagManager.prototype.isFeatureEnabledSync = function(featureName, fallbackValue) {
|
|
153
|
+
fallbackValue = fallbackValue || false;
|
|
154
|
+
var val = this.getFeatureDataSync(featureName, fallbackValue);
|
|
155
|
+
if (val !== true && val !== false) {
|
|
156
|
+
logger.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
|
|
157
|
+
val = fallbackValue;
|
|
158
|
+
}
|
|
159
|
+
return val;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
|
|
163
|
+
if (this.trackedFeatures.has(featureName)) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
this.trackedFeatures.add(featureName);
|
|
167
|
+
this.track('$experiment_started', {
|
|
168
|
+
'Experiment name': featureName,
|
|
169
|
+
'Variant name': feature['key'],
|
|
170
|
+
'$experiment_type': 'feature_flag'
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
function minApisSupported() {
|
|
175
|
+
return !!fetch &&
|
|
176
|
+
typeof Promise !== 'undefined' &&
|
|
177
|
+
typeof Map !== 'undefined' &&
|
|
178
|
+
typeof Set !== 'undefined';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
safewrapClass(FeatureFlagManager);
|
|
182
|
+
|
|
183
|
+
FeatureFlagManager.prototype['are_features_ready'] = FeatureFlagManager.prototype.areFeaturesReady;
|
|
184
|
+
FeatureFlagManager.prototype['get_feature'] = FeatureFlagManager.prototype.getFeature;
|
|
185
|
+
FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
|
|
186
|
+
FeatureFlagManager.prototype['get_feature_data_sync'] = FeatureFlagManager.prototype.getFeatureDataSync;
|
|
187
|
+
FeatureFlagManager.prototype['get_feature_sync'] = FeatureFlagManager.prototype.getFeatureSync;
|
|
188
|
+
FeatureFlagManager.prototype['is_feature_enabled'] = FeatureFlagManager.prototype.isFeatureEnabled;
|
|
189
|
+
FeatureFlagManager.prototype['is_feature_enabled_sync'] = FeatureFlagManager.prototype.isFeatureEnabledSync;
|
|
190
|
+
|
|
191
|
+
export { FeatureFlagManager };
|
package/src/mixpanel-core.js
CHANGED
|
@@ -4,6 +4,7 @@ import { MAX_RECORDING_MS, _, console, userAgent, document, navigator, slice, NO
|
|
|
4
4
|
import { isRecordingExpired } from './recorder/utils';
|
|
5
5
|
import { window } from './window';
|
|
6
6
|
import { Autocapture } from './autocapture';
|
|
7
|
+
import { FeatureFlagManager } from './flags';
|
|
7
8
|
import { FormTracker, LinkTracker } from './dom-trackers';
|
|
8
9
|
import { RequestBatcher } from './request-batcher';
|
|
9
10
|
import { MixpanelGroup } from './mixpanel-group';
|
|
@@ -86,10 +87,11 @@ if (navigator['sendBeacon']) {
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
var DEFAULT_API_ROUTES = {
|
|
89
|
-
'track':
|
|
90
|
+
'track': 'track/',
|
|
90
91
|
'engage': 'engage/',
|
|
91
92
|
'groups': 'groups/',
|
|
92
|
-
'record': 'record/'
|
|
93
|
+
'record': 'record/',
|
|
94
|
+
'flags': 'flags/'
|
|
93
95
|
};
|
|
94
96
|
|
|
95
97
|
/*
|
|
@@ -107,6 +109,7 @@ var DEFAULT_CONFIG = {
|
|
|
107
109
|
'cross_site_cookie': false,
|
|
108
110
|
'cross_subdomain_cookie': true,
|
|
109
111
|
'error_reporter': NOOP_FUNC,
|
|
112
|
+
'flags': false,
|
|
110
113
|
'persistence': 'cookie',
|
|
111
114
|
'persistence_name': '',
|
|
112
115
|
'cookie_domain': '',
|
|
@@ -147,6 +150,7 @@ var DEFAULT_CONFIG = {
|
|
|
147
150
|
'record_block_selector': 'img, video',
|
|
148
151
|
'record_canvas': false,
|
|
149
152
|
'record_collect_fonts': false,
|
|
153
|
+
'record_heatmap_data': false,
|
|
150
154
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
151
155
|
'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
|
|
152
156
|
'record_mask_text_selector': '*',
|
|
@@ -362,6 +366,14 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
362
366
|
}, '');
|
|
363
367
|
}
|
|
364
368
|
|
|
369
|
+
this.flags = new FeatureFlagManager({
|
|
370
|
+
getConfigFunc: _.bind(this.get_config, this),
|
|
371
|
+
getDistinctIdFunc: _.bind(this.get_distinct_id, this),
|
|
372
|
+
trackingFunc: _.bind(this.track, this)
|
|
373
|
+
});
|
|
374
|
+
this.flags.init();
|
|
375
|
+
this['flags'] = this.flags;
|
|
376
|
+
|
|
365
377
|
this.autocapture = new Autocapture(this);
|
|
366
378
|
this.autocapture.init();
|
|
367
379
|
|
|
@@ -487,6 +499,10 @@ MixpanelLib.prototype.resume_session_recording = function () {
|
|
|
487
499
|
}
|
|
488
500
|
};
|
|
489
501
|
|
|
502
|
+
MixpanelLib.prototype.is_recording_heatmap_data = function () {
|
|
503
|
+
return this._get_session_replay_id() && this.get_config('record_heatmap_data');
|
|
504
|
+
};
|
|
505
|
+
|
|
490
506
|
MixpanelLib.prototype.get_session_recording_properties = function () {
|
|
491
507
|
var props = {};
|
|
492
508
|
var replay_id = this._get_session_replay_id();
|
|
@@ -1568,6 +1584,11 @@ MixpanelLib.prototype.identify = function(
|
|
|
1568
1584
|
'$anon_distinct_id': previous_distinct_id
|
|
1569
1585
|
}, {skip_hooks: true});
|
|
1570
1586
|
}
|
|
1587
|
+
|
|
1588
|
+
// check feature flags again if distinct id has changed
|
|
1589
|
+
if (new_distinct_id !== previous_distinct_id) {
|
|
1590
|
+
this.flags.fetchFlags();
|
|
1591
|
+
}
|
|
1571
1592
|
};
|
|
1572
1593
|
|
|
1573
1594
|
/**
|
|
@@ -1842,7 +1863,7 @@ MixpanelLib.prototype.set_config = function(config) {
|
|
|
1842
1863
|
}
|
|
1843
1864
|
Config.DEBUG = Config.DEBUG || this.get_config('debug');
|
|
1844
1865
|
|
|
1845
|
-
if ('autocapture' in config && this.autocapture) {
|
|
1866
|
+
if (('autocapture' in config || 'record_heatmap_data' in config) && this.autocapture) {
|
|
1846
1867
|
this.autocapture.init();
|
|
1847
1868
|
}
|
|
1848
1869
|
}
|