mixpanel-browser 2.66.0 → 2.68.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/dependabot.yml +7 -0
- package/CHANGELOG.md +12 -0
- package/dist/mixpanel-core.cjs.js +162 -42
- package/dist/mixpanel-recorder.js +36 -12
- 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 +162 -42
- package/dist/mixpanel-with-recorder.js +197 -53
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.js +197 -53
- package/dist/mixpanel.cjs.js +197 -53
- package/dist/mixpanel.globals.js +162 -42
- package/dist/mixpanel.min.js +150 -148
- package/dist/mixpanel.module.js +197 -53
- package/dist/mixpanel.umd.js +197 -53
- package/package.json +1 -1
- package/src/autocapture/index.js +59 -1
- package/src/autocapture/rageclick.js +38 -0
- package/src/config.js +1 -1
- package/src/flags/index.js +51 -8
- package/src/index.d.ts +18 -0
- package/src/mixpanel-core.js +20 -32
- package/src/recorder/session-recording.js +35 -11
package/src/flags/index.js
CHANGED
|
@@ -15,8 +15,10 @@ CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
|
|
|
15
15
|
* @constructor
|
|
16
16
|
*/
|
|
17
17
|
var FeatureFlagManager = function(initOptions) {
|
|
18
|
+
this.getFullApiRoute = initOptions.getFullApiRoute;
|
|
18
19
|
this.getMpConfig = initOptions.getConfigFunc;
|
|
19
|
-
this.
|
|
20
|
+
this.setMpConfig = initOptions.setConfigFunc;
|
|
21
|
+
this.getMpProperty = initOptions.getPropertyFunc;
|
|
20
22
|
this.track = initOptions.trackingFunc;
|
|
21
23
|
};
|
|
22
24
|
|
|
@@ -53,6 +55,23 @@ FeatureFlagManager.prototype.isSystemEnabled = function() {
|
|
|
53
55
|
return !!this.getMpConfig(FLAGS_CONFIG_KEY);
|
|
54
56
|
};
|
|
55
57
|
|
|
58
|
+
FeatureFlagManager.prototype.updateContext = function(newContext, options) {
|
|
59
|
+
if (!this.isSystemEnabled()) {
|
|
60
|
+
logger.critical('Feature Flags not enabled, cannot update context');
|
|
61
|
+
return Promise.resolve();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
var ffConfig = this.getMpConfig(FLAGS_CONFIG_KEY);
|
|
65
|
+
if (!_.isObject(ffConfig)) {
|
|
66
|
+
ffConfig = {};
|
|
67
|
+
}
|
|
68
|
+
var oldContext = (options && options['replace']) ? {} : this.getConfig(CONFIG_CONTEXT);
|
|
69
|
+
ffConfig[CONFIG_CONTEXT] = _.extend({}, oldContext, newContext);
|
|
70
|
+
|
|
71
|
+
this.setMpConfig(FLAGS_CONFIG_KEY, ffConfig);
|
|
72
|
+
return this.fetchFlags();
|
|
73
|
+
};
|
|
74
|
+
|
|
56
75
|
FeatureFlagManager.prototype.areFlagsReady = function() {
|
|
57
76
|
if (!this.isSystemEnabled()) {
|
|
58
77
|
logger.error('Feature Flags not enabled');
|
|
@@ -62,15 +81,17 @@ FeatureFlagManager.prototype.areFlagsReady = function() {
|
|
|
62
81
|
|
|
63
82
|
FeatureFlagManager.prototype.fetchFlags = function() {
|
|
64
83
|
if (!this.isSystemEnabled()) {
|
|
65
|
-
return;
|
|
84
|
+
return Promise.resolve();
|
|
66
85
|
}
|
|
67
86
|
|
|
68
|
-
var distinctId = this.
|
|
87
|
+
var distinctId = this.getMpProperty('distinct_id');
|
|
88
|
+
var deviceId = this.getMpProperty('$device_id');
|
|
69
89
|
logger.log('Fetching flags for distinct ID: ' + distinctId);
|
|
70
90
|
var reqParams = {
|
|
71
|
-
'context': _.extend({'distinct_id': distinctId}, this.getConfig(CONFIG_CONTEXT))
|
|
91
|
+
'context': _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT))
|
|
72
92
|
};
|
|
73
|
-
this.
|
|
93
|
+
this._fetchInProgressStartTime = Date.now();
|
|
94
|
+
this.fetchPromise = window['fetch'](this.getFullApiRoute(), {
|
|
74
95
|
'method': 'POST',
|
|
75
96
|
'headers': {
|
|
76
97
|
'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
|
|
@@ -78,6 +99,7 @@ FeatureFlagManager.prototype.fetchFlags = function() {
|
|
|
78
99
|
},
|
|
79
100
|
'body': JSON.stringify(reqParams)
|
|
80
101
|
}).then(function(response) {
|
|
102
|
+
this.markFetchComplete();
|
|
81
103
|
return response.json().then(function(responseBody) {
|
|
82
104
|
var responseFlags = responseBody['flags'];
|
|
83
105
|
if (!responseFlags) {
|
|
@@ -92,9 +114,26 @@ FeatureFlagManager.prototype.fetchFlags = function() {
|
|
|
92
114
|
});
|
|
93
115
|
this.flags = flags;
|
|
94
116
|
}.bind(this)).catch(function(error) {
|
|
117
|
+
this.markFetchComplete();
|
|
95
118
|
logger.error(error);
|
|
96
|
-
});
|
|
97
|
-
}.bind(this)).catch(function() {
|
|
119
|
+
}.bind(this));
|
|
120
|
+
}.bind(this)).catch(function(error) {
|
|
121
|
+
this.markFetchComplete();
|
|
122
|
+
logger.error(error);
|
|
123
|
+
}.bind(this));
|
|
124
|
+
|
|
125
|
+
return this.fetchPromise;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
FeatureFlagManager.prototype.markFetchComplete = function() {
|
|
129
|
+
if (!this._fetchInProgressStartTime) {
|
|
130
|
+
logger.error('Fetch in progress started time not set, cannot mark fetch complete');
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this._fetchStartTime = this._fetchInProgressStartTime;
|
|
134
|
+
this._fetchCompleteTime = Date.now();
|
|
135
|
+
this._fetchLatency = this._fetchCompleteTime - this._fetchStartTime;
|
|
136
|
+
this._fetchInProgressStartTime = null;
|
|
98
137
|
};
|
|
99
138
|
|
|
100
139
|
FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
|
|
@@ -173,7 +212,10 @@ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature)
|
|
|
173
212
|
this.track('$experiment_started', {
|
|
174
213
|
'Experiment name': featureName,
|
|
175
214
|
'Variant name': feature['key'],
|
|
176
|
-
'$experiment_type': 'feature_flag'
|
|
215
|
+
'$experiment_type': 'feature_flag',
|
|
216
|
+
'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
|
|
217
|
+
'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
|
|
218
|
+
'Variant fetch latency (ms)': this._fetchLatency
|
|
177
219
|
});
|
|
178
220
|
};
|
|
179
221
|
|
|
@@ -193,6 +235,7 @@ FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype
|
|
|
193
235
|
FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
|
|
194
236
|
FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
|
|
195
237
|
FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
|
|
238
|
+
FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
|
|
196
239
|
|
|
197
240
|
// Deprecated method
|
|
198
241
|
FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
|
package/src/index.d.ts
CHANGED
|
@@ -40,6 +40,17 @@ export interface OutTrackingOptions extends ClearOptOutInOutOptions {
|
|
|
40
40
|
delete_user: boolean;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export type RageClickConfig =
|
|
44
|
+
| boolean
|
|
45
|
+
| {
|
|
46
|
+
/** Distance threshold in pixels for clicks to be considered within the same area (default: 30) */
|
|
47
|
+
threshold_px?: number;
|
|
48
|
+
/** Time window in milliseconds for clicks to be considered rapid (default: 1000) */
|
|
49
|
+
timeout_ms?: number;
|
|
50
|
+
/** Number of clicks required to trigger a rage click event (default: 3) */
|
|
51
|
+
click_count?: number;
|
|
52
|
+
};
|
|
53
|
+
|
|
43
54
|
export interface RegisterOptions {
|
|
44
55
|
persistent: boolean;
|
|
45
56
|
}
|
|
@@ -66,6 +77,12 @@ export interface AutocaptureConfig {
|
|
|
66
77
|
* @default 'full-url'
|
|
67
78
|
*/
|
|
68
79
|
pageview?: TrackPageView;
|
|
80
|
+
/**
|
|
81
|
+
* When set to `true`, Mixpanel will track rage clicks (multiple clicks in a short time on the same element).
|
|
82
|
+
* Can also be configured as an object to customize rage click detection parameters.
|
|
83
|
+
* @default true
|
|
84
|
+
*/
|
|
85
|
+
rage_click?: RageClickConfig;
|
|
69
86
|
/**
|
|
70
87
|
* When set, Mixpanel will collect page scrolls at specified scroll intervals.
|
|
71
88
|
* @default true
|
|
@@ -184,6 +201,7 @@ export interface Config {
|
|
|
184
201
|
record_max_ms: number;
|
|
185
202
|
record_sessions_percent: number;
|
|
186
203
|
record_canvas: boolean;
|
|
204
|
+
record_heatmap_data: boolean;
|
|
187
205
|
}
|
|
188
206
|
|
|
189
207
|
export type VerboseResponse =
|
package/src/mixpanel-core.js
CHANGED
|
@@ -149,7 +149,7 @@ var DEFAULT_CONFIG = {
|
|
|
149
149
|
'batch_autostart': true,
|
|
150
150
|
'hooks': {},
|
|
151
151
|
'record_block_class': new RegExp('^(mp-block|fs-exclude|amp-block|rr-block|ph-no-capture)$'),
|
|
152
|
-
'record_block_selector': 'img, video',
|
|
152
|
+
'record_block_selector': 'img, video, audio',
|
|
153
153
|
'record_canvas': false,
|
|
154
154
|
'record_collect_fonts': false,
|
|
155
155
|
'record_heatmap_data': false,
|
|
@@ -369,8 +369,12 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
this.flags = new FeatureFlagManager({
|
|
372
|
+
getFullApiRoute: _.bind(function() {
|
|
373
|
+
return this.get_api_host('flags') + '/' + this.get_config('api_routes')['flags'];
|
|
374
|
+
}, this),
|
|
372
375
|
getConfigFunc: _.bind(this.get_config, this),
|
|
373
|
-
|
|
376
|
+
setConfigFunc: _.bind(this.set_config, this),
|
|
377
|
+
getPropertyFunc: _.bind(this.get_property, this),
|
|
374
378
|
trackingFunc: _.bind(this.track, this)
|
|
375
379
|
});
|
|
376
380
|
this.flags.init();
|
|
@@ -856,11 +860,10 @@ MixpanelLib.prototype.are_batchers_initialized = function() {
|
|
|
856
860
|
|
|
857
861
|
MixpanelLib.prototype.get_batcher_configs = function() {
|
|
858
862
|
var queue_prefix = '__mpq_' + this.get_config('token');
|
|
859
|
-
var api_routes = this.get_config('api_routes');
|
|
860
863
|
this._batcher_configs = this._batcher_configs || {
|
|
861
|
-
events: {type: 'events',
|
|
862
|
-
people: {type: 'people',
|
|
863
|
-
groups: {type: 'groups',
|
|
864
|
+
events: {type: 'events', api_name: 'track', queue_key: queue_prefix + '_ev'},
|
|
865
|
+
people: {type: 'people', api_name: 'engage', queue_key: queue_prefix + '_pp'},
|
|
866
|
+
groups: {type: 'groups', api_name: 'groups', queue_key: queue_prefix + '_gr'}
|
|
864
867
|
};
|
|
865
868
|
return this._batcher_configs;
|
|
866
869
|
};
|
|
@@ -874,8 +877,9 @@ MixpanelLib.prototype.init_batchers = function() {
|
|
|
874
877
|
libConfig: this['config'],
|
|
875
878
|
errorReporter: this.get_config('error_reporter'),
|
|
876
879
|
sendRequestFunc: _.bind(function(data, options, cb) {
|
|
880
|
+
var api_routes = this.get_config('api_routes');
|
|
877
881
|
this._send_request(
|
|
878
|
-
this.
|
|
882
|
+
this.get_api_host(attrs.api_name) + '/' + api_routes[attrs.api_name],
|
|
879
883
|
this._encode_data_for_request(data),
|
|
880
884
|
options,
|
|
881
885
|
this._prepare_callback(cb, data)
|
|
@@ -1603,31 +1607,15 @@ MixpanelLib.prototype.identify = function(
|
|
|
1603
1607
|
* Useful for clearing data when a user logs out.
|
|
1604
1608
|
*/
|
|
1605
1609
|
MixpanelLib.prototype.reset = function() {
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
}, '');
|
|
1616
|
-
};
|
|
1617
|
-
|
|
1618
|
-
if (self._recorder) {
|
|
1619
|
-
self.stop_session_recording()
|
|
1620
|
-
.then(function () {
|
|
1621
|
-
reset();
|
|
1622
|
-
self._check_and_start_session_recording();
|
|
1623
|
-
})
|
|
1624
|
-
.catch(_.bind(function (err) {
|
|
1625
|
-
reset();
|
|
1626
|
-
this.report_error('Error restarting recording session', err);
|
|
1627
|
-
}, this));
|
|
1628
|
-
} else {
|
|
1629
|
-
reset();
|
|
1630
|
-
}
|
|
1610
|
+
this.stop_session_recording();
|
|
1611
|
+
this['persistence'].clear();
|
|
1612
|
+
this._flags.identify_called = false;
|
|
1613
|
+
var uuid = _.UUID();
|
|
1614
|
+
this.register_once({
|
|
1615
|
+
'distinct_id': DEVICE_ID_PREFIX + uuid,
|
|
1616
|
+
'$device_id': uuid
|
|
1617
|
+
}, '');
|
|
1618
|
+
this._check_and_start_session_recording();
|
|
1631
1619
|
};
|
|
1632
1620
|
|
|
1633
1621
|
/**
|
|
@@ -63,6 +63,13 @@ function isUserEvent(ev) {
|
|
|
63
63
|
* @property {string} replayStartUrl
|
|
64
64
|
*/
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* @typedef {Object} UserIdInfo
|
|
68
|
+
* @property {string} distinct_id
|
|
69
|
+
* @property {string} user_id
|
|
70
|
+
* @property {string} device_id
|
|
71
|
+
*/
|
|
72
|
+
|
|
66
73
|
|
|
67
74
|
/**
|
|
68
75
|
* This class encapsulates a single session recording and its lifecycle.
|
|
@@ -118,6 +125,30 @@ var SessionRecording = function(options) {
|
|
|
118
125
|
});
|
|
119
126
|
};
|
|
120
127
|
|
|
128
|
+
/**
|
|
129
|
+
* @returns {UserIdInfo}
|
|
130
|
+
*/
|
|
131
|
+
SessionRecording.prototype.getUserIdInfo = function () {
|
|
132
|
+
if (this.finalFlushUserIdInfo) {
|
|
133
|
+
return this.finalFlushUserIdInfo;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
var userIdInfo = {
|
|
137
|
+
'distinct_id': String(this._mixpanel.get_distinct_id()),
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// send ID management props if they exist
|
|
141
|
+
var deviceId = this._mixpanel.get_property('$device_id');
|
|
142
|
+
if (deviceId) {
|
|
143
|
+
userIdInfo['$device_id'] = deviceId;
|
|
144
|
+
}
|
|
145
|
+
var userId = this._mixpanel.get_property('$user_id');
|
|
146
|
+
if (userId) {
|
|
147
|
+
userIdInfo['$user_id'] = userId;
|
|
148
|
+
}
|
|
149
|
+
return userIdInfo;
|
|
150
|
+
};
|
|
151
|
+
|
|
121
152
|
SessionRecording.prototype.unloadPersistedData = function () {
|
|
122
153
|
this.batcher.stop();
|
|
123
154
|
return this.batcher.flush()
|
|
@@ -242,6 +273,9 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
242
273
|
};
|
|
243
274
|
|
|
244
275
|
SessionRecording.prototype.stopRecording = function (skipFlush) {
|
|
276
|
+
// store the user ID info in case this is getting called in mixpanel.reset()
|
|
277
|
+
this.finalFlushUserIdInfo = this.getUserIdInfo();
|
|
278
|
+
|
|
245
279
|
if (!this.isRrwebStopped()) {
|
|
246
280
|
try {
|
|
247
281
|
this._stopRecording();
|
|
@@ -407,7 +441,6 @@ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
|
|
|
407
441
|
'$current_url': this.batchStartUrl,
|
|
408
442
|
'$lib_version': Config.LIB_VERSION,
|
|
409
443
|
'batch_start_time': batchStartTime / 1000,
|
|
410
|
-
'distinct_id': String(this._mixpanel.get_distinct_id()),
|
|
411
444
|
'mp_lib': 'web',
|
|
412
445
|
'replay_id': replayId,
|
|
413
446
|
'replay_length_ms': replayLengthMs,
|
|
@@ -416,16 +449,7 @@ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
|
|
|
416
449
|
'seq': this.seqNo
|
|
417
450
|
};
|
|
418
451
|
var eventsJson = JSON.stringify(data);
|
|
419
|
-
|
|
420
|
-
// send ID management props if they exist
|
|
421
|
-
var deviceId = this._mixpanel.get_property('$device_id');
|
|
422
|
-
if (deviceId) {
|
|
423
|
-
reqParams['$device_id'] = deviceId;
|
|
424
|
-
}
|
|
425
|
-
var userId = this._mixpanel.get_property('$user_id');
|
|
426
|
-
if (userId) {
|
|
427
|
-
reqParams['$user_id'] = userId;
|
|
428
|
-
}
|
|
452
|
+
Object.assign(reqParams, this.getUserIdInfo());
|
|
429
453
|
|
|
430
454
|
if (CompressionStream) {
|
|
431
455
|
var jsonStream = new Blob([eventsJson], {type: 'application/json'}).stream();
|