mixpanel-browser 2.73.0 → 2.74.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/.claude/settings.local.json +12 -0
- package/.eslintrc.json +7 -4
- package/.github/workflows/integration-tests.yml +52 -0
- package/.github/workflows/unit-tests.yml +40 -0
- package/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/build.sh +1 -5
- package/dist/mixpanel-core.cjs.d.ts +12 -1
- package/dist/mixpanel-core.cjs.js +115 -15
- package/dist/mixpanel-recorder.js +5255 -687
- 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 +12 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +115 -15
- package/dist/mixpanel-with-recorder.d.ts +12 -1
- package/dist/mixpanel-with-recorder.js +6720 -2079
- package/dist/mixpanel-with-recorder.min.d.ts +12 -1
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +12 -1
- package/dist/mixpanel.amd.js +6720 -2079
- package/dist/mixpanel.cjs.d.ts +12 -1
- package/dist/mixpanel.cjs.js +6720 -2079
- package/dist/mixpanel.globals.js +115 -15
- package/dist/mixpanel.min.js +174 -172
- package/dist/mixpanel.module.d.ts +12 -1
- package/dist/mixpanel.module.js +6720 -2079
- package/dist/mixpanel.umd.d.ts +12 -1
- package/dist/mixpanel.umd.js +6720 -2079
- package/dist/rrweb-bundled.js +4315 -591
- package/dist/rrweb-compiled.js +4962 -641
- package/package.json +30 -5
- package/rollup.config.mjs +254 -224
- package/src/autocapture/utils.js +15 -7
- package/src/config.js +1 -1
- package/src/index.d.ts +12 -1
- package/src/mixpanel-core.js +89 -5
- package/src/recorder/masking.js +197 -0
- package/src/recorder/rrweb-entrypoint.js +2 -1
- package/src/recorder/session-recording.js +43 -4
- package/src/recorder/utils.js +5 -1
- package/src/utils.js +11 -2
- package/testServer.js +51 -7
- package/.github/workflows/tests.yml +0 -25
|
@@ -6,6 +6,8 @@ export type PushItem = Array<string | Dict | ((this: Mixpanel) => void)>;
|
|
|
6
6
|
|
|
7
7
|
export type Query = string | Element | Element[];
|
|
8
8
|
|
|
9
|
+
export type RemoteSettingType = "disabled" | "fallback" | "strict";
|
|
10
|
+
|
|
9
11
|
export interface Dict {
|
|
10
12
|
[key: string]: any;
|
|
11
13
|
}
|
|
@@ -166,6 +168,8 @@ export interface Config {
|
|
|
166
168
|
track?: string;
|
|
167
169
|
engage?: string;
|
|
168
170
|
groups?: string;
|
|
171
|
+
record?: string;
|
|
172
|
+
flags?: string;
|
|
169
173
|
};
|
|
170
174
|
api_method: string;
|
|
171
175
|
api_transport: string;
|
|
@@ -225,12 +229,18 @@ export interface Config {
|
|
|
225
229
|
record_idle_timeout_ms: number;
|
|
226
230
|
record_inline_images: boolean;
|
|
227
231
|
record_mask_text_class: string | RegExp;
|
|
228
|
-
record_mask_text_selector: string;
|
|
232
|
+
record_mask_text_selector: string | string[];
|
|
233
|
+
record_unmask_text_selector: string | string[];
|
|
234
|
+
record_mask_all_text: boolean;
|
|
235
|
+
record_mask_input_selector: string | string[];
|
|
236
|
+
record_unmask_input_selector: string | string[];
|
|
237
|
+
record_mask_all_inputs: boolean;
|
|
229
238
|
record_min_ms: number;
|
|
230
239
|
record_max_ms: number;
|
|
231
240
|
record_sessions_percent: number;
|
|
232
241
|
record_canvas: boolean;
|
|
233
242
|
record_heatmap_data: boolean;
|
|
243
|
+
remote_settings_mode: RemoteSettingType;
|
|
234
244
|
hooks: {
|
|
235
245
|
before_identify?: (new_distinct_id: string) => string | null;
|
|
236
246
|
before_register?: (
|
|
@@ -256,6 +266,7 @@ export interface Config {
|
|
|
256
266
|
};
|
|
257
267
|
}
|
|
258
268
|
|
|
269
|
+
|
|
259
270
|
export type VerboseResponse =
|
|
260
271
|
| {
|
|
261
272
|
status: 1;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var Config = {
|
|
4
4
|
DEBUG: false,
|
|
5
|
-
LIB_VERSION: '2.
|
|
5
|
+
LIB_VERSION: '2.74.0'
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
|
|
@@ -1504,8 +1504,17 @@ function _storageWrapper(storage, name, is_supported_fn) {
|
|
|
1504
1504
|
};
|
|
1505
1505
|
}
|
|
1506
1506
|
|
|
1507
|
-
|
|
1508
|
-
|
|
1507
|
+
// Safari errors out accessing localStorage/sessionStorage when cookies are disabled,
|
|
1508
|
+
// so create dummy storage wrappers that silently fail as a fallback.
|
|
1509
|
+
var windowLocalStorage = null, windowSessionStorage = null;
|
|
1510
|
+
try {
|
|
1511
|
+
windowLocalStorage = win.localStorage;
|
|
1512
|
+
windowSessionStorage = win.sessionStorage;
|
|
1513
|
+
// eslint-disable-next-line no-empty
|
|
1514
|
+
} catch (_err) {}
|
|
1515
|
+
|
|
1516
|
+
_.localStorage = _storageWrapper(windowLocalStorage, 'localStorage', localStorageSupported);
|
|
1517
|
+
_.sessionStorage = _storageWrapper(windowSessionStorage, 'sessionStorage', sessionStorageSupported);
|
|
1509
1518
|
|
|
1510
1519
|
_.register_event = (function() {
|
|
1511
1520
|
// written by Dean Edwards, 2005
|
|
@@ -2655,6 +2664,18 @@ function shouldTrackDomEvent(el, ev) {
|
|
|
2655
2664
|
}
|
|
2656
2665
|
}
|
|
2657
2666
|
|
|
2667
|
+
function elementLooksSensitive(el) {
|
|
2668
|
+
var name = (el.name || el.id || '').toString().toLowerCase();
|
|
2669
|
+
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"]
|
|
2670
|
+
var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
|
|
2671
|
+
if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
|
|
2672
|
+
return true;
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
return false;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2658
2679
|
/*
|
|
2659
2680
|
* Check whether a DOM element should be "tracked" or if it may contain sensitive data
|
|
2660
2681
|
* using a variety of heuristics.
|
|
@@ -2707,13 +2728,8 @@ function shouldTrackElementDetails(el, ev, allowElementCallback, allowSelectors)
|
|
|
2707
2728
|
}
|
|
2708
2729
|
}
|
|
2709
2730
|
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
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"]
|
|
2713
|
-
var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
|
|
2714
|
-
if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
|
|
2715
|
-
return false;
|
|
2716
|
-
}
|
|
2731
|
+
if (elementLooksSensitive(el)) {
|
|
2732
|
+
return false;
|
|
2717
2733
|
}
|
|
2718
2734
|
|
|
2719
2735
|
return true;
|
|
@@ -6841,6 +6857,9 @@ var INIT_SNIPPET = 1;
|
|
|
6841
6857
|
/** @const */ var PAYLOAD_TYPE_BASE64 = 'base64';
|
|
6842
6858
|
/** @const */ var PAYLOAD_TYPE_JSON = 'json';
|
|
6843
6859
|
/** @const */ var DEVICE_ID_PREFIX = '$device:';
|
|
6860
|
+
/** @const */ var SETTING_STRICT = 'strict';
|
|
6861
|
+
/** @const */ var SETTING_FALLBACK = 'fallback';
|
|
6862
|
+
/** @const */ var SETTING_DISABLED = 'disabled';
|
|
6844
6863
|
|
|
6845
6864
|
|
|
6846
6865
|
/*
|
|
@@ -6869,7 +6888,8 @@ var DEFAULT_API_ROUTES = {
|
|
|
6869
6888
|
'engage': 'engage/',
|
|
6870
6889
|
'groups': 'groups/',
|
|
6871
6890
|
'record': 'record/',
|
|
6872
|
-
'flags': 'flags/'
|
|
6891
|
+
'flags': 'flags/',
|
|
6892
|
+
'settings': 'settings/'
|
|
6873
6893
|
};
|
|
6874
6894
|
|
|
6875
6895
|
/*
|
|
@@ -6933,12 +6953,12 @@ var DEFAULT_CONFIG = {
|
|
|
6933
6953
|
'record_console': true,
|
|
6934
6954
|
'record_heatmap_data': false,
|
|
6935
6955
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
6936
|
-
'
|
|
6937
|
-
'record_mask_text_selector': '*',
|
|
6956
|
+
'record_mask_inputs': true,
|
|
6938
6957
|
'record_max_ms': MAX_RECORDING_MS,
|
|
6939
6958
|
'record_min_ms': 0,
|
|
6940
6959
|
'record_sessions_percent': 0,
|
|
6941
|
-
'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js'
|
|
6960
|
+
'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js',
|
|
6961
|
+
'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
|
|
6942
6962
|
};
|
|
6943
6963
|
|
|
6944
6964
|
var DOM_LOADED = false;
|
|
@@ -7176,7 +7196,16 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
7176
7196
|
this.autocapture.init();
|
|
7177
7197
|
|
|
7178
7198
|
this._init_tab_id();
|
|
7179
|
-
|
|
7199
|
+
|
|
7200
|
+
// Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
|
|
7201
|
+
var mode = this.get_config('remote_settings_mode');
|
|
7202
|
+
if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
|
|
7203
|
+
this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
7204
|
+
this._check_and_start_session_recording();
|
|
7205
|
+
}, this));
|
|
7206
|
+
} else {
|
|
7207
|
+
this._check_and_start_session_recording();
|
|
7208
|
+
}
|
|
7180
7209
|
};
|
|
7181
7210
|
|
|
7182
7211
|
/**
|
|
@@ -7601,6 +7630,77 @@ MixpanelLib.prototype._send_request = function(url, data, options, callback) {
|
|
|
7601
7630
|
return succeeded;
|
|
7602
7631
|
};
|
|
7603
7632
|
|
|
7633
|
+
MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
7634
|
+
var disableRecordingIfStrict = function() {
|
|
7635
|
+
if (mode === 'strict') {
|
|
7636
|
+
self.set_config({'record_sessions_percent': 0});
|
|
7637
|
+
}
|
|
7638
|
+
};
|
|
7639
|
+
|
|
7640
|
+
if (!win['AbortController']) {
|
|
7641
|
+
console.critical('Remote settings unavailable: missing minimum required APIs');
|
|
7642
|
+
disableRecordingIfStrict();
|
|
7643
|
+
return Promise.resolve();
|
|
7644
|
+
}
|
|
7645
|
+
|
|
7646
|
+
var settings_endpoint = this.get_api_host('settings') + '/' + this.get_config('api_routes')['settings'];
|
|
7647
|
+
var request_params = {
|
|
7648
|
+
'$lib_version': Config.LIB_VERSION,
|
|
7649
|
+
'mp_lib': 'web',
|
|
7650
|
+
'sdk_config': '1',
|
|
7651
|
+
};
|
|
7652
|
+
var query_string = _.HTTPBuildQuery(request_params);
|
|
7653
|
+
var full_url = settings_endpoint + '?' + query_string;
|
|
7654
|
+
var self = this;
|
|
7655
|
+
|
|
7656
|
+
var abortController = new AbortController();
|
|
7657
|
+
var timeout_id = setTimeout(function() {
|
|
7658
|
+
abortController.abort();
|
|
7659
|
+
}, 500);
|
|
7660
|
+
var fetchOptions = {
|
|
7661
|
+
'method': 'GET',
|
|
7662
|
+
'headers': {
|
|
7663
|
+
'Authorization': 'Basic ' + btoa(self.get_config('token') + ':'),
|
|
7664
|
+
},
|
|
7665
|
+
'signal': abortController.signal
|
|
7666
|
+
};
|
|
7667
|
+
|
|
7668
|
+
return win['fetch'](full_url, fetchOptions).then(function(response) {
|
|
7669
|
+
clearTimeout(timeout_id);
|
|
7670
|
+
if (!response['ok']) {
|
|
7671
|
+
console.critical('Network response was not ok');
|
|
7672
|
+
disableRecordingIfStrict();
|
|
7673
|
+
return;
|
|
7674
|
+
}
|
|
7675
|
+
return response.json();
|
|
7676
|
+
}).then(function(result) {
|
|
7677
|
+
if (result && result['sdk_config'] && result['sdk_config']['config']) {
|
|
7678
|
+
var remote_config = result['sdk_config']['config'];
|
|
7679
|
+
|
|
7680
|
+
// Verify that remote config contains only valid keys from DEFAULT_CONFIG
|
|
7681
|
+
var valid_config = {};
|
|
7682
|
+
_.each(remote_config, function(value, key) {
|
|
7683
|
+
if (DEFAULT_CONFIG.hasOwnProperty(key)) {
|
|
7684
|
+
valid_config[key] = value;
|
|
7685
|
+
}
|
|
7686
|
+
});
|
|
7687
|
+
|
|
7688
|
+
if (_.isEmptyObject(valid_config)) {
|
|
7689
|
+
console.critical('No valid config keys found in remote settings.');
|
|
7690
|
+
disableRecordingIfStrict();
|
|
7691
|
+
} else {
|
|
7692
|
+
self.set_config(valid_config);
|
|
7693
|
+
}
|
|
7694
|
+
} else {
|
|
7695
|
+
disableRecordingIfStrict();
|
|
7696
|
+
}
|
|
7697
|
+
}).catch(function(err) {
|
|
7698
|
+
clearTimeout(timeout_id);
|
|
7699
|
+
console.critical('Failed to fetch remote settings', err);
|
|
7700
|
+
disableRecordingIfStrict();
|
|
7701
|
+
});
|
|
7702
|
+
};
|
|
7703
|
+
|
|
7604
7704
|
/**
|
|
7605
7705
|
* _execute_array() deals with processing any mixpanel function
|
|
7606
7706
|
* calls that were called before the Mixpanel library were loaded
|
|
@@ -6,6 +6,8 @@ export type PushItem = Array<string | Dict | ((this: Mixpanel) => void)>;
|
|
|
6
6
|
|
|
7
7
|
export type Query = string | Element | Element[];
|
|
8
8
|
|
|
9
|
+
export type RemoteSettingType = "disabled" | "fallback" | "strict";
|
|
10
|
+
|
|
9
11
|
export interface Dict {
|
|
10
12
|
[key: string]: any;
|
|
11
13
|
}
|
|
@@ -166,6 +168,8 @@ export interface Config {
|
|
|
166
168
|
track?: string;
|
|
167
169
|
engage?: string;
|
|
168
170
|
groups?: string;
|
|
171
|
+
record?: string;
|
|
172
|
+
flags?: string;
|
|
169
173
|
};
|
|
170
174
|
api_method: string;
|
|
171
175
|
api_transport: string;
|
|
@@ -225,12 +229,18 @@ export interface Config {
|
|
|
225
229
|
record_idle_timeout_ms: number;
|
|
226
230
|
record_inline_images: boolean;
|
|
227
231
|
record_mask_text_class: string | RegExp;
|
|
228
|
-
record_mask_text_selector: string;
|
|
232
|
+
record_mask_text_selector: string | string[];
|
|
233
|
+
record_unmask_text_selector: string | string[];
|
|
234
|
+
record_mask_all_text: boolean;
|
|
235
|
+
record_mask_input_selector: string | string[];
|
|
236
|
+
record_unmask_input_selector: string | string[];
|
|
237
|
+
record_mask_all_inputs: boolean;
|
|
229
238
|
record_min_ms: number;
|
|
230
239
|
record_max_ms: number;
|
|
231
240
|
record_sessions_percent: number;
|
|
232
241
|
record_canvas: boolean;
|
|
233
242
|
record_heatmap_data: boolean;
|
|
243
|
+
remote_settings_mode: RemoteSettingType;
|
|
234
244
|
hooks: {
|
|
235
245
|
before_identify?: (new_distinct_id: string) => string | null;
|
|
236
246
|
before_register?: (
|
|
@@ -256,6 +266,7 @@ export interface Config {
|
|
|
256
266
|
};
|
|
257
267
|
}
|
|
258
268
|
|
|
269
|
+
|
|
259
270
|
export type VerboseResponse =
|
|
260
271
|
| {
|
|
261
272
|
status: 1;
|