mixpanel-browser 2.63.0 → 2.65.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 +12 -0
- package/README.md +1 -1
- package/dist/mixpanel-core.cjs.js +276 -54
- package/dist/mixpanel-recorder.js +6 -2
- 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 +276 -54
- package/dist/mixpanel-with-recorder.js +297 -75
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.js +297 -75
- package/dist/mixpanel.cjs.js +297 -75
- package/dist/mixpanel.globals.js +276 -54
- package/dist/mixpanel.min.js +151 -144
- package/dist/mixpanel.module.js +297 -75
- package/dist/mixpanel.umd.js +297 -75
- package/package.json +1 -1
- package/src/autocapture/index.js +4 -3
- package/src/autocapture/utils.js +7 -1
- package/src/config.js +1 -1
- package/src/flags/index.js +200 -0
- package/src/mixpanel-core.js +29 -3
- package/src/mixpanel-people.js +2 -12
- package/src/utils.js +5 -1
package/package.json
CHANGED
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 = {};
|
|
@@ -168,7 +169,9 @@ function getPropsForDOMEvent(ev, config) {
|
|
|
168
169
|
'$elements': elementsJson,
|
|
169
170
|
'$el_attr__href': href,
|
|
170
171
|
'$viewportHeight': Math.max(docElement['clientHeight'], window['innerHeight'] || 0),
|
|
171
|
-
'$viewportWidth': Math.max(docElement['clientWidth'], window['innerWidth'] || 0)
|
|
172
|
+
'$viewportWidth': Math.max(docElement['clientWidth'], window['innerWidth'] || 0),
|
|
173
|
+
'$pageHeight': document['body']['offsetHeight'] || 0,
|
|
174
|
+
'$pageWidth': document['body']['offsetWidth'] || 0,
|
|
172
175
|
};
|
|
173
176
|
_.each(captureExtraAttrs, function(attr) {
|
|
174
177
|
if (!blockAttrsSet[attr] && target.hasAttribute(attr)) {
|
|
@@ -192,6 +195,9 @@ function getPropsForDOMEvent(ev, config) {
|
|
|
192
195
|
props['$' + prop] = ev[prop];
|
|
193
196
|
}
|
|
194
197
|
});
|
|
198
|
+
if (capturedForHeatMap) {
|
|
199
|
+
props['$captured_for_heatmap'] = true;
|
|
200
|
+
}
|
|
195
201
|
target = guessRealClickTarget(ev);
|
|
196
202
|
}
|
|
197
203
|
// prioritize text content from "real" click target if different from original target
|
package/src/config.js
CHANGED
|
@@ -0,0 +1,200 @@
|
|
|
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.isSystemEnabled = function() {
|
|
53
|
+
return !!this.getMpConfig(FLAGS_CONFIG_KEY);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
FeatureFlagManager.prototype.areFlagsReady = function() {
|
|
57
|
+
if (!this.isSystemEnabled()) {
|
|
58
|
+
logger.error('Feature Flags not enabled');
|
|
59
|
+
}
|
|
60
|
+
return !!this.flags;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
FeatureFlagManager.prototype.fetchFlags = function() {
|
|
64
|
+
if (!this.isSystemEnabled()) {
|
|
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
|
+
'value': 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.getVariant = 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.getVariantSync(featureName, fallback);
|
|
110
|
+
}.bind(this)).catch(function(error) {
|
|
111
|
+
logger.error(error);
|
|
112
|
+
return fallback;
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
|
|
117
|
+
if (!this.areFlagsReady()) {
|
|
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.getVariantValue = function(featureName, fallbackValue) {
|
|
131
|
+
return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
|
|
132
|
+
return feature['value'];
|
|
133
|
+
}).catch(function(error) {
|
|
134
|
+
logger.error(error);
|
|
135
|
+
return fallbackValue;
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// TODO remove deprecated method
|
|
140
|
+
FeatureFlagManager.prototype.getFeatureData = function(featureName, fallbackValue) {
|
|
141
|
+
logger.critical('mixpanel.flags.get_feature_data() is deprecated and will be removed in a future release. Use mixpanel.flags.get_variant_value() instead.');
|
|
142
|
+
return this.getVariantValue(featureName, fallbackValue);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
FeatureFlagManager.prototype.getVariantValueSync = function(featureName, fallbackValue) {
|
|
146
|
+
return this.getVariantSync(featureName, {'value': fallbackValue})['value'];
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
FeatureFlagManager.prototype.isEnabled = function(featureName, fallbackValue) {
|
|
150
|
+
return this.getVariantValue(featureName).then(function() {
|
|
151
|
+
return this.isEnabledSync(featureName, fallbackValue);
|
|
152
|
+
}.bind(this)).catch(function(error) {
|
|
153
|
+
logger.error(error);
|
|
154
|
+
return fallbackValue;
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
FeatureFlagManager.prototype.isEnabledSync = function(featureName, fallbackValue) {
|
|
159
|
+
fallbackValue = fallbackValue || false;
|
|
160
|
+
var val = this.getVariantValueSync(featureName, fallbackValue);
|
|
161
|
+
if (val !== true && val !== false) {
|
|
162
|
+
logger.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
|
|
163
|
+
val = fallbackValue;
|
|
164
|
+
}
|
|
165
|
+
return val;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
|
|
169
|
+
if (this.trackedFeatures.has(featureName)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
this.trackedFeatures.add(featureName);
|
|
173
|
+
this.track('$experiment_started', {
|
|
174
|
+
'Experiment name': featureName,
|
|
175
|
+
'Variant name': feature['key'],
|
|
176
|
+
'$experiment_type': 'feature_flag'
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
function minApisSupported() {
|
|
181
|
+
return !!fetch &&
|
|
182
|
+
typeof Promise !== 'undefined' &&
|
|
183
|
+
typeof Map !== 'undefined' &&
|
|
184
|
+
typeof Set !== 'undefined';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
safewrapClass(FeatureFlagManager);
|
|
188
|
+
|
|
189
|
+
FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
|
|
190
|
+
FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
|
|
191
|
+
FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
|
|
192
|
+
FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
|
|
193
|
+
FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
|
|
194
|
+
FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
|
|
195
|
+
FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
|
|
196
|
+
|
|
197
|
+
// Deprecated method
|
|
198
|
+
FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
|
|
199
|
+
|
|
200
|
+
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
|
/*
|
|
@@ -98,6 +100,7 @@ var DEFAULT_API_ROUTES = {
|
|
|
98
100
|
var DEFAULT_CONFIG = {
|
|
99
101
|
'api_host': 'https://api-js.mixpanel.com',
|
|
100
102
|
'api_routes': DEFAULT_API_ROUTES,
|
|
103
|
+
'api_extra_query_params': {},
|
|
101
104
|
'api_method': 'POST',
|
|
102
105
|
'api_transport': 'XHR',
|
|
103
106
|
'api_payload_format': PAYLOAD_TYPE_BASE64,
|
|
@@ -107,6 +110,7 @@ var DEFAULT_CONFIG = {
|
|
|
107
110
|
'cross_site_cookie': false,
|
|
108
111
|
'cross_subdomain_cookie': true,
|
|
109
112
|
'error_reporter': NOOP_FUNC,
|
|
113
|
+
'flags': false,
|
|
110
114
|
'persistence': 'cookie',
|
|
111
115
|
'persistence_name': '',
|
|
112
116
|
'cookie_domain': '',
|
|
@@ -147,6 +151,7 @@ var DEFAULT_CONFIG = {
|
|
|
147
151
|
'record_block_selector': 'img, video',
|
|
148
152
|
'record_canvas': false,
|
|
149
153
|
'record_collect_fonts': false,
|
|
154
|
+
'record_heatmap_data': false,
|
|
150
155
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
151
156
|
'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
|
|
152
157
|
'record_mask_text_selector': '*',
|
|
@@ -362,6 +367,14 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
362
367
|
}, '');
|
|
363
368
|
}
|
|
364
369
|
|
|
370
|
+
this.flags = new FeatureFlagManager({
|
|
371
|
+
getConfigFunc: _.bind(this.get_config, this),
|
|
372
|
+
getDistinctIdFunc: _.bind(this.get_distinct_id, this),
|
|
373
|
+
trackingFunc: _.bind(this.track, this)
|
|
374
|
+
});
|
|
375
|
+
this.flags.init();
|
|
376
|
+
this['flags'] = this.flags;
|
|
377
|
+
|
|
365
378
|
this.autocapture = new Autocapture(this);
|
|
366
379
|
this.autocapture.init();
|
|
367
380
|
|
|
@@ -487,6 +500,10 @@ MixpanelLib.prototype.resume_session_recording = function () {
|
|
|
487
500
|
}
|
|
488
501
|
};
|
|
489
502
|
|
|
503
|
+
MixpanelLib.prototype.is_recording_heatmap_data = function () {
|
|
504
|
+
return this._get_session_replay_id() && this.get_config('record_heatmap_data');
|
|
505
|
+
};
|
|
506
|
+
|
|
490
507
|
MixpanelLib.prototype.get_session_recording_properties = function () {
|
|
491
508
|
var props = {};
|
|
492
509
|
var replay_id = this._get_session_replay_id();
|
|
@@ -671,6 +688,8 @@ MixpanelLib.prototype._send_request = function(url, data, options, callback) {
|
|
|
671
688
|
delete data['data'];
|
|
672
689
|
}
|
|
673
690
|
|
|
691
|
+
_.extend(data, this.get_config('api_extra_query_params'));
|
|
692
|
+
|
|
674
693
|
url += '?' + _.HTTPBuildQuery(data);
|
|
675
694
|
|
|
676
695
|
var lib = this;
|
|
@@ -1568,6 +1587,11 @@ MixpanelLib.prototype.identify = function(
|
|
|
1568
1587
|
'$anon_distinct_id': previous_distinct_id
|
|
1569
1588
|
}, {skip_hooks: true});
|
|
1570
1589
|
}
|
|
1590
|
+
|
|
1591
|
+
// check feature flags again if distinct id has changed
|
|
1592
|
+
if (new_distinct_id !== previous_distinct_id) {
|
|
1593
|
+
this.flags.fetchFlags();
|
|
1594
|
+
}
|
|
1571
1595
|
};
|
|
1572
1596
|
|
|
1573
1597
|
/**
|
|
@@ -1582,6 +1606,8 @@ MixpanelLib.prototype.reset = function() {
|
|
|
1582
1606
|
'distinct_id': DEVICE_ID_PREFIX + uuid,
|
|
1583
1607
|
'$device_id': uuid
|
|
1584
1608
|
}, '');
|
|
1609
|
+
this.stop_session_recording();
|
|
1610
|
+
this._check_and_start_session_recording();
|
|
1585
1611
|
};
|
|
1586
1612
|
|
|
1587
1613
|
/**
|
|
@@ -1842,7 +1868,7 @@ MixpanelLib.prototype.set_config = function(config) {
|
|
|
1842
1868
|
}
|
|
1843
1869
|
Config.DEBUG = Config.DEBUG || this.get_config('debug');
|
|
1844
1870
|
|
|
1845
|
-
if ('autocapture' in config && this.autocapture) {
|
|
1871
|
+
if (('autocapture' in config || 'record_heatmap_data' in config) && this.autocapture) {
|
|
1846
1872
|
this.autocapture.init();
|
|
1847
1873
|
}
|
|
1848
1874
|
}
|
package/src/mixpanel-people.js
CHANGED
|
@@ -262,18 +262,8 @@ MixpanelPeople.prototype.union = addOptOutCheckMixpanelPeople(function(list_name
|
|
|
262
262
|
* @param {Function} [callback] If provided, the callback will be called when the server responds
|
|
263
263
|
* @deprecated
|
|
264
264
|
*/
|
|
265
|
-
MixpanelPeople.prototype.track_charge = addOptOutCheckMixpanelPeople(function(
|
|
266
|
-
|
|
267
|
-
amount = parseFloat(amount);
|
|
268
|
-
if (isNaN(amount)) {
|
|
269
|
-
console.error('Invalid value passed to mixpanel.people.track_charge - must be a number');
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return this.append('$transactions', _.extend({
|
|
275
|
-
'$amount': amount
|
|
276
|
-
}, properties), callback);
|
|
265
|
+
MixpanelPeople.prototype.track_charge = addOptOutCheckMixpanelPeople(function() {
|
|
266
|
+
console.error('mixpanel.people.track_charge() is deprecated and no longer has any effect.');
|
|
277
267
|
});
|
|
278
268
|
|
|
279
269
|
/*
|
package/src/utils.js
CHANGED
|
@@ -1483,6 +1483,9 @@ _.info = {
|
|
|
1483
1483
|
return 'Microsoft Edge';
|
|
1484
1484
|
} else if (_.includes(user_agent, 'FBIOS')) {
|
|
1485
1485
|
return 'Facebook Mobile';
|
|
1486
|
+
} else if (_.includes(user_agent, 'Whale/')) {
|
|
1487
|
+
// https://user-agents.net/browsers/whale-browser
|
|
1488
|
+
return 'Whale Browser';
|
|
1486
1489
|
} else if (_.includes(user_agent, 'Chrome')) {
|
|
1487
1490
|
return 'Chrome';
|
|
1488
1491
|
} else if (_.includes(user_agent, 'CriOS')) {
|
|
@@ -1534,7 +1537,8 @@ _.info = {
|
|
|
1534
1537
|
'Android Mobile': /android\s(\d+(\.\d+)?)/,
|
|
1535
1538
|
'Samsung Internet': /SamsungBrowser\/(\d+(\.\d+)?)/,
|
|
1536
1539
|
'Internet Explorer': /(rv:|MSIE )(\d+(\.\d+)?)/,
|
|
1537
|
-
'Mozilla': /rv:(\d+(\.\d+)?)
|
|
1540
|
+
'Mozilla': /rv:(\d+(\.\d+)?)/,
|
|
1541
|
+
'Whale Browser': /Whale\/(\d+(\.\d+)?)/
|
|
1538
1542
|
};
|
|
1539
1543
|
var regex = versionRegexs[browser];
|
|
1540
1544
|
if (regex === undefined) {
|