mixpanel-browser 2.71.1 → 2.73.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 +1 -0
- package/CHANGELOG.md +12 -0
- package/dist/mixpanel-core.cjs.d.ts +84 -13
- package/dist/mixpanel-core.cjs.js +180 -28
- package/dist/mixpanel-recorder.js +684 -114
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +84 -13
- package/dist/mixpanel-with-async-recorder.cjs.js +180 -28
- package/dist/mixpanel-with-recorder.d.ts +84 -13
- package/dist/mixpanel-with-recorder.js +860 -140
- package/dist/mixpanel-with-recorder.min.d.ts +84 -13
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +84 -13
- package/dist/mixpanel.amd.js +860 -140
- package/dist/mixpanel.cjs.d.ts +84 -13
- package/dist/mixpanel.cjs.js +860 -140
- package/dist/mixpanel.globals.js +180 -28
- package/dist/mixpanel.min.js +172 -170
- package/dist/mixpanel.module.d.ts +84 -13
- package/dist/mixpanel.module.js +860 -140
- package/dist/mixpanel.umd.d.ts +84 -13
- package/dist/mixpanel.umd.js +860 -140
- package/dist/rrweb-bundled.js +12760 -0
- package/dist/rrweb-compiled.js +2496 -7176
- package/package.json +3 -2
- package/rollup.config.mjs +15 -4
- package/src/autocapture/index.js +1 -1
- package/src/autocapture/rageclick.js +20 -1
- package/src/autocapture/shadow-dom-observer.js +3 -15
- package/src/autocapture/utils.js +30 -0
- package/src/config.js +1 -1
- package/src/index.d.ts +84 -13
- package/src/mixpanel-core.js +127 -10
- package/src/recorder/recorder.js +1 -1
- package/src/recorder/rrweb-entrypoint.js +6 -0
- package/src/recorder/session-recording.js +69 -12
- package/src/utils.js +24 -0
- package/src/window.js +3 -1
- package/.claude/settings.local.json +0 -9
package/dist/mixpanel.globals.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
var Config = {
|
|
5
5
|
DEBUG: false,
|
|
6
|
-
LIB_VERSION: '2.
|
|
6
|
+
LIB_VERSION: '2.73.0'
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
screen: { width: 0, height: 0 },
|
|
24
24
|
location: loc,
|
|
25
25
|
addEventListener: function() {},
|
|
26
|
-
removeEventListener: function() {}
|
|
26
|
+
removeEventListener: function() {},
|
|
27
|
+
dispatchEvent: function() {},
|
|
28
|
+
CustomEvent: function () {}
|
|
27
29
|
};
|
|
28
30
|
} else {
|
|
29
31
|
win = window;
|
|
@@ -2893,20 +2895,65 @@
|
|
|
2893
2895
|
return false;
|
|
2894
2896
|
}
|
|
2895
2897
|
|
|
2898
|
+
/**
|
|
2899
|
+
* Get the composed path of a click event for elements embedded in shadow DOM.
|
|
2900
|
+
* @param {Event} event - event to get the composed path from
|
|
2901
|
+
* @returns {Array} the composed path of the click event
|
|
2902
|
+
*/
|
|
2903
|
+
function getClickEventComposedPath(event) {
|
|
2904
|
+
if ('composedPath' in event) {
|
|
2905
|
+
return event['composedPath']();
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
return [];
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
/**
|
|
2912
|
+
* Get the element from a click event, accounting for elements embedded in shadow DOM.
|
|
2913
|
+
* @param {Event} event - event to get the target from
|
|
2914
|
+
* @returns {Element | null} the element that was the target of the click event
|
|
2915
|
+
*/
|
|
2916
|
+
function getClickEventTargetElement(event) {
|
|
2917
|
+
var path = getClickEventComposedPath(event);
|
|
2918
|
+
|
|
2919
|
+
if (path && path.length > 0) {
|
|
2920
|
+
return path[0];
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
return event['target'] || event['srcElement'];
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2896
2926
|
/** @const */ var DEFAULT_RAGE_CLICK_THRESHOLD_PX = 30;
|
|
2897
2927
|
/** @const */ var DEFAULT_RAGE_CLICK_TIMEOUT_MS = 1000;
|
|
2898
2928
|
/** @const */ var DEFAULT_RAGE_CLICK_CLICK_COUNT = 4;
|
|
2929
|
+
/** @const */ var DEFAULT_RAGE_CLICK_INTERACTIVE_ELEMENTS_ONLY = false;
|
|
2899
2930
|
|
|
2900
2931
|
function RageClickTracker() {
|
|
2901
2932
|
this.clicks = [];
|
|
2902
2933
|
}
|
|
2903
2934
|
|
|
2904
|
-
|
|
2935
|
+
/**
|
|
2936
|
+
* Determines if a click event is part of a rage click sequence.
|
|
2937
|
+
* @param {Event} event - the original click event.
|
|
2938
|
+
* @param {import('../index.d.ts').RageClickConfig} options - configuration options for rage click detection.
|
|
2939
|
+
* @returns {boolean} - true if the click is considered a rage click, false otherwise.
|
|
2940
|
+
*/
|
|
2941
|
+
RageClickTracker.prototype.isRageClick = function(event, options) {
|
|
2905
2942
|
options = options || {};
|
|
2906
2943
|
var thresholdPx = options['threshold_px'] || DEFAULT_RAGE_CLICK_THRESHOLD_PX;
|
|
2907
2944
|
var timeoutMs = options['timeout_ms'] || DEFAULT_RAGE_CLICK_TIMEOUT_MS;
|
|
2908
2945
|
var clickCount = options['click_count'] || DEFAULT_RAGE_CLICK_CLICK_COUNT;
|
|
2946
|
+
var interactiveElementsOnly = options['interactive_elements_only'] || DEFAULT_RAGE_CLICK_INTERACTIVE_ELEMENTS_ONLY;
|
|
2947
|
+
|
|
2948
|
+
if (interactiveElementsOnly) {
|
|
2949
|
+
var target = getClickEventTargetElement(event);
|
|
2950
|
+
if (!target || isDefinitelyNonInteractive(target)) {
|
|
2951
|
+
return false;
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2909
2955
|
var timestamp = Date.now();
|
|
2956
|
+
var x = event['pageX'], y = event['pageY'];
|
|
2910
2957
|
|
|
2911
2958
|
var lastClick = this.clicks[this.clicks.length - 1];
|
|
2912
2959
|
if (
|
|
@@ -2937,28 +2984,16 @@
|
|
|
2937
2984
|
if (!this.observedShadowRoots) {
|
|
2938
2985
|
return;
|
|
2939
2986
|
}
|
|
2940
|
-
var path = this.getComposedPath(event);
|
|
2941
|
-
if (path && path.length) {
|
|
2942
|
-
return path[0];
|
|
2943
|
-
}
|
|
2944
2987
|
|
|
2945
|
-
return event
|
|
2988
|
+
return getClickEventTargetElement(event);
|
|
2946
2989
|
};
|
|
2947
2990
|
|
|
2948
|
-
|
|
2949
|
-
ShadowDOMObserver.prototype.getComposedPath = function(event) {
|
|
2950
|
-
if ('composedPath' in event) {
|
|
2951
|
-
return event['composedPath']();
|
|
2952
|
-
}
|
|
2953
|
-
|
|
2954
|
-
return [];
|
|
2955
|
-
};
|
|
2956
2991
|
ShadowDOMObserver.prototype.observeFromEvent = function(event) {
|
|
2957
2992
|
if (!this.observedShadowRoots) {
|
|
2958
2993
|
return;
|
|
2959
2994
|
}
|
|
2960
2995
|
|
|
2961
|
-
var path =
|
|
2996
|
+
var path = getClickEventComposedPath(event);
|
|
2962
2997
|
|
|
2963
2998
|
// Check each element in path for shadow roots
|
|
2964
2999
|
for (var i = 0; i < path.length; i++) {
|
|
@@ -3710,7 +3745,7 @@
|
|
|
3710
3745
|
return;
|
|
3711
3746
|
}
|
|
3712
3747
|
|
|
3713
|
-
if (this._rageClickTracker.isRageClick(ev
|
|
3748
|
+
if (this._rageClickTracker.isRageClick(ev, currentRageClickConfig)) {
|
|
3714
3749
|
this.trackDomEvent(ev, MP_EV_RAGE_CLICK);
|
|
3715
3750
|
}
|
|
3716
3751
|
}.bind(this);
|
|
@@ -6803,8 +6838,6 @@
|
|
|
6803
6838
|
var INIT_MODULE = 0;
|
|
6804
6839
|
var INIT_SNIPPET = 1;
|
|
6805
6840
|
|
|
6806
|
-
var IDENTITY_FUNC = function(x) {return x;};
|
|
6807
|
-
|
|
6808
6841
|
/** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel';
|
|
6809
6842
|
/** @const */ var PAYLOAD_TYPE_BASE64 = 'base64';
|
|
6810
6843
|
/** @const */ var PAYLOAD_TYPE_JSON = 'json';
|
|
@@ -6898,6 +6931,7 @@
|
|
|
6898
6931
|
'record_block_selector': 'img, video, audio',
|
|
6899
6932
|
'record_canvas': false,
|
|
6900
6933
|
'record_collect_fonts': false,
|
|
6934
|
+
'record_console': true,
|
|
6901
6935
|
'record_heatmap_data': false,
|
|
6902
6936
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
6903
6937
|
'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
|
|
@@ -6969,6 +7003,17 @@
|
|
|
6969
7003
|
// global debug to be true
|
|
6970
7004
|
Config.DEBUG = Config.DEBUG || instance.get_config('debug');
|
|
6971
7005
|
|
|
7006
|
+
var source = init_type === INIT_MODULE ? 'module' : 'snippet';
|
|
7007
|
+
win.dispatchEvent(new win.CustomEvent('$mp_sdk_to_extension_event', {
|
|
7008
|
+
'detail': {
|
|
7009
|
+
'instance': instance,
|
|
7010
|
+
'source': source,
|
|
7011
|
+
'token': token,
|
|
7012
|
+
'name': name,
|
|
7013
|
+
'info': _.info
|
|
7014
|
+
}
|
|
7015
|
+
}));
|
|
7016
|
+
|
|
6972
7017
|
// if target is not defined, we called init after the lib already
|
|
6973
7018
|
// loaded, so there won't be an array of things to execute
|
|
6974
7019
|
if (!_.isUndefined(target) && _.isArray(target)) {
|
|
@@ -7039,6 +7084,8 @@
|
|
|
7039
7084
|
}
|
|
7040
7085
|
}
|
|
7041
7086
|
|
|
7087
|
+
this.hooks = {};
|
|
7088
|
+
|
|
7042
7089
|
this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, {
|
|
7043
7090
|
'name': name,
|
|
7044
7091
|
'token': token,
|
|
@@ -7639,7 +7686,12 @@
|
|
|
7639
7686
|
);
|
|
7640
7687
|
}, this),
|
|
7641
7688
|
beforeSendHook: _.bind(function(item) {
|
|
7642
|
-
|
|
7689
|
+
var ret = this._run_hook('before_send_' + attrs.type, item);
|
|
7690
|
+
if (ret) {
|
|
7691
|
+
return ret[0];
|
|
7692
|
+
} else {
|
|
7693
|
+
return null;
|
|
7694
|
+
}
|
|
7643
7695
|
}, this),
|
|
7644
7696
|
stopAllBatchingFunc: _.bind(this.stop_batch_senders, this),
|
|
7645
7697
|
usePersistence: true,
|
|
@@ -7732,6 +7784,9 @@
|
|
|
7732
7784
|
var send_request_immediately = _.bind(function() {
|
|
7733
7785
|
if (!send_request_options.skip_hooks) {
|
|
7734
7786
|
truncated_data = this._run_hook('before_send_' + options.type, truncated_data);
|
|
7787
|
+
if (truncated_data) {
|
|
7788
|
+
truncated_data = truncated_data[0];
|
|
7789
|
+
}
|
|
7735
7790
|
}
|
|
7736
7791
|
if (truncated_data) {
|
|
7737
7792
|
console.log('MIXPANEL REQUEST:');
|
|
@@ -7786,6 +7841,17 @@
|
|
|
7786
7841
|
* with the tracking payload sent to the API server is returned; otherwise false.
|
|
7787
7842
|
*/
|
|
7788
7843
|
MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, properties, options, callback) {
|
|
7844
|
+
var ret;
|
|
7845
|
+
if (!(options && options.skip_hooks)) {
|
|
7846
|
+
ret = this._run_hook('before_track', event_name, properties);
|
|
7847
|
+
if (ret === null) {
|
|
7848
|
+
return;
|
|
7849
|
+
} else {
|
|
7850
|
+
event_name = ret[0];
|
|
7851
|
+
properties = ret[1];
|
|
7852
|
+
}
|
|
7853
|
+
}
|
|
7854
|
+
|
|
7789
7855
|
if (!callback && typeof options === 'function') {
|
|
7790
7856
|
callback = options;
|
|
7791
7857
|
options = null;
|
|
@@ -7855,7 +7921,7 @@
|
|
|
7855
7921
|
'event': event_name,
|
|
7856
7922
|
'properties': properties
|
|
7857
7923
|
};
|
|
7858
|
-
|
|
7924
|
+
ret = this._track_or_batch({
|
|
7859
7925
|
type: 'events',
|
|
7860
7926
|
data: data,
|
|
7861
7927
|
endpoint: this.get_api_host('events') + '/' + this.get_config('api_routes')['track'],
|
|
@@ -8201,6 +8267,14 @@
|
|
|
8201
8267
|
* @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage)
|
|
8202
8268
|
*/
|
|
8203
8269
|
MixpanelLib.prototype.register = function(props, days_or_options) {
|
|
8270
|
+
var ret = this._run_hook('before_register', props, days_or_options);
|
|
8271
|
+
if (ret === null) {
|
|
8272
|
+
return;
|
|
8273
|
+
} else {
|
|
8274
|
+
props = ret[0];
|
|
8275
|
+
days_or_options = ret[1];
|
|
8276
|
+
}
|
|
8277
|
+
|
|
8204
8278
|
var options = options_for_register(days_or_options);
|
|
8205
8279
|
if (options['persistent']) {
|
|
8206
8280
|
this['persistence'].register(props, options['days']);
|
|
@@ -8237,6 +8311,15 @@
|
|
|
8237
8311
|
* @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage)
|
|
8238
8312
|
*/
|
|
8239
8313
|
MixpanelLib.prototype.register_once = function(props, default_value, days_or_options) {
|
|
8314
|
+
var ret = this._run_hook('before_register_once', props, default_value, days_or_options);
|
|
8315
|
+
if (ret === null) {
|
|
8316
|
+
return;
|
|
8317
|
+
} else {
|
|
8318
|
+
props = ret[0];
|
|
8319
|
+
default_value = ret[1];
|
|
8320
|
+
days_or_options = ret[2];
|
|
8321
|
+
}
|
|
8322
|
+
|
|
8240
8323
|
var options = options_for_register(days_or_options);
|
|
8241
8324
|
if (options['persistent']) {
|
|
8242
8325
|
this['persistence'].register_once(props, default_value, options['days']);
|
|
@@ -8260,6 +8343,14 @@
|
|
|
8260
8343
|
* @param {boolean} [options.persistent=true] - whether to look in persistent storage (cookie/localStorage)
|
|
8261
8344
|
*/
|
|
8262
8345
|
MixpanelLib.prototype.unregister = function(property, options) {
|
|
8346
|
+
var ret = this._run_hook('before_unregister', property, options);
|
|
8347
|
+
if (ret === null) {
|
|
8348
|
+
return;
|
|
8349
|
+
} else {
|
|
8350
|
+
property = ret[0];
|
|
8351
|
+
options = ret[1];
|
|
8352
|
+
}
|
|
8353
|
+
|
|
8263
8354
|
options = options_for_register(options);
|
|
8264
8355
|
if (options['persistent']) {
|
|
8265
8356
|
this['persistence'].unregister(property);
|
|
@@ -8308,6 +8399,13 @@
|
|
|
8308
8399
|
// _set_once_callback:function A callback to be run if and when the People set_once queue is flushed
|
|
8309
8400
|
// _union_callback:function A callback to be run if and when the People union queue is flushed
|
|
8310
8401
|
// _unset_callback:function A callback to be run if and when the People unset queue is flushed
|
|
8402
|
+
var ret = this._run_hook('before_identify', new_distinct_id);
|
|
8403
|
+
|
|
8404
|
+
if (ret === null) {
|
|
8405
|
+
return -1;
|
|
8406
|
+
} else {
|
|
8407
|
+
new_distinct_id = ret[0];
|
|
8408
|
+
}
|
|
8311
8409
|
|
|
8312
8410
|
var previous_distinct_id = this.get_distinct_id();
|
|
8313
8411
|
if (new_distinct_id && previous_distinct_id !== new_distinct_id) {
|
|
@@ -8632,6 +8730,25 @@
|
|
|
8632
8730
|
if (('autocapture' in config || 'record_heatmap_data' in config) && this.autocapture) {
|
|
8633
8731
|
this.autocapture.init();
|
|
8634
8732
|
}
|
|
8733
|
+
|
|
8734
|
+
if (_.isObject(config['hooks'])) {
|
|
8735
|
+
this.hooks = {};
|
|
8736
|
+
_.each(config['hooks'], function(hook_value, hook_name) {
|
|
8737
|
+
if (_.isFunction(hook_value)) {
|
|
8738
|
+
this.hooks[hook_name] = [hook_value];
|
|
8739
|
+
} else if (_.isArray(hook_value)) {
|
|
8740
|
+
this.hooks[hook_name] = [];
|
|
8741
|
+
for (var i = 0; i < hook_value.length; i++) {
|
|
8742
|
+
if (!_.isFunction(hook_value[i])) {
|
|
8743
|
+
console.critical('Invalid hook added. Hook is not a function');
|
|
8744
|
+
}
|
|
8745
|
+
this.hooks[hook_name].push(hook_value[i]);
|
|
8746
|
+
}
|
|
8747
|
+
} else {
|
|
8748
|
+
console.critical('Invalid hooks added. Ensure that the hook values passed into config.hooks are functions or arrays of functions.');
|
|
8749
|
+
}
|
|
8750
|
+
}, this);
|
|
8751
|
+
}
|
|
8635
8752
|
}
|
|
8636
8753
|
};
|
|
8637
8754
|
|
|
@@ -8649,12 +8766,26 @@
|
|
|
8649
8766
|
* @returns {any|null} return value of user-provided hook, or null if nothing was returned
|
|
8650
8767
|
*/
|
|
8651
8768
|
MixpanelLib.prototype._run_hook = function(hook_name) {
|
|
8652
|
-
var
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8769
|
+
var hook_data = slice.call(arguments, 1);
|
|
8770
|
+
_.each(this.hooks[hook_name], function(hook) {
|
|
8771
|
+
if (hook_data === null) {
|
|
8772
|
+
return null;
|
|
8773
|
+
}
|
|
8774
|
+
|
|
8775
|
+
var ret = hook.apply(this, hook_data);
|
|
8776
|
+
|
|
8777
|
+
if (typeof ret === 'undefined') {
|
|
8778
|
+
this.report_error(hook_name + ' hook did not return a valid value');
|
|
8779
|
+
hook_data = null;
|
|
8780
|
+
} else {
|
|
8781
|
+
if (!_.isArray(ret)) {
|
|
8782
|
+
ret = [ret];
|
|
8783
|
+
}
|
|
8784
|
+
hook_data.splice.apply(hook_data, [0, ret.length].concat(ret));
|
|
8785
|
+
}
|
|
8786
|
+
}, this);
|
|
8787
|
+
|
|
8788
|
+
return hook_data;
|
|
8658
8789
|
};
|
|
8659
8790
|
|
|
8660
8791
|
/**
|
|
@@ -8965,6 +9096,25 @@
|
|
|
8965
9096
|
}
|
|
8966
9097
|
};
|
|
8967
9098
|
|
|
9099
|
+
MixpanelLib.prototype.add_hook = function(hook_name, hook_fn) {
|
|
9100
|
+
if (!this.hooks[hook_name]) {
|
|
9101
|
+
this.hooks[hook_name] = [];
|
|
9102
|
+
}
|
|
9103
|
+
this.hooks[hook_name].push(hook_fn);
|
|
9104
|
+
};
|
|
9105
|
+
|
|
9106
|
+
MixpanelLib.prototype.remove_hook = function(hook_name, hook_fn) {
|
|
9107
|
+
var fn_index;
|
|
9108
|
+
if (this.hooks[hook_name]) {
|
|
9109
|
+
fn_index = this.hooks[hook_name].indexOf(hook_fn);
|
|
9110
|
+
if (fn_index !== -1) {
|
|
9111
|
+
this.hooks[hook_name].splice(fn_index, 1);
|
|
9112
|
+
} else {
|
|
9113
|
+
console.log('remove_hook failed. Matching hook was not found');
|
|
9114
|
+
}
|
|
9115
|
+
}
|
|
9116
|
+
};
|
|
9117
|
+
|
|
8968
9118
|
// EXPORTS (for closure compiler)
|
|
8969
9119
|
|
|
8970
9120
|
// MixpanelLib Exports
|
|
@@ -8997,6 +9147,8 @@
|
|
|
8997
9147
|
MixpanelLib.prototype['set_group'] = MixpanelLib.prototype.set_group;
|
|
8998
9148
|
MixpanelLib.prototype['add_group'] = MixpanelLib.prototype.add_group;
|
|
8999
9149
|
MixpanelLib.prototype['remove_group'] = MixpanelLib.prototype.remove_group;
|
|
9150
|
+
MixpanelLib.prototype['add_hook'] = MixpanelLib.prototype.add_hook;
|
|
9151
|
+
MixpanelLib.prototype['remove_hook'] = MixpanelLib.prototype.remove_hook;
|
|
9000
9152
|
MixpanelLib.prototype['track_with_groups'] = MixpanelLib.prototype.track_with_groups;
|
|
9001
9153
|
MixpanelLib.prototype['start_batch_senders'] = MixpanelLib.prototype.start_batch_senders;
|
|
9002
9154
|
MixpanelLib.prototype['stop_batch_senders'] = MixpanelLib.prototype.stop_batch_senders;
|