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.
Files changed (43) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/.eslintrc.json +7 -4
  3. package/.github/workflows/integration-tests.yml +52 -0
  4. package/.github/workflows/unit-tests.yml +40 -0
  5. package/CHANGELOG.md +7 -0
  6. package/README.md +1 -1
  7. package/build.sh +1 -5
  8. package/dist/mixpanel-core.cjs.d.ts +12 -1
  9. package/dist/mixpanel-core.cjs.js +115 -15
  10. package/dist/mixpanel-recorder.js +5255 -687
  11. package/dist/mixpanel-recorder.min.js +1 -1
  12. package/dist/mixpanel-recorder.min.js.map +1 -1
  13. package/dist/mixpanel-with-async-recorder.cjs.d.ts +12 -1
  14. package/dist/mixpanel-with-async-recorder.cjs.js +115 -15
  15. package/dist/mixpanel-with-recorder.d.ts +12 -1
  16. package/dist/mixpanel-with-recorder.js +6720 -2079
  17. package/dist/mixpanel-with-recorder.min.d.ts +12 -1
  18. package/dist/mixpanel-with-recorder.min.js +1 -1
  19. package/dist/mixpanel.amd.d.ts +12 -1
  20. package/dist/mixpanel.amd.js +6720 -2079
  21. package/dist/mixpanel.cjs.d.ts +12 -1
  22. package/dist/mixpanel.cjs.js +6720 -2079
  23. package/dist/mixpanel.globals.js +115 -15
  24. package/dist/mixpanel.min.js +174 -172
  25. package/dist/mixpanel.module.d.ts +12 -1
  26. package/dist/mixpanel.module.js +6720 -2079
  27. package/dist/mixpanel.umd.d.ts +12 -1
  28. package/dist/mixpanel.umd.js +6720 -2079
  29. package/dist/rrweb-bundled.js +4315 -591
  30. package/dist/rrweb-compiled.js +4962 -641
  31. package/package.json +30 -5
  32. package/rollup.config.mjs +254 -224
  33. package/src/autocapture/utils.js +15 -7
  34. package/src/config.js +1 -1
  35. package/src/index.d.ts +12 -1
  36. package/src/mixpanel-core.js +89 -5
  37. package/src/recorder/masking.js +197 -0
  38. package/src/recorder/rrweb-entrypoint.js +2 -1
  39. package/src/recorder/session-recording.js +43 -4
  40. package/src/recorder/utils.js +5 -1
  41. package/src/utils.js +11 -2
  42. package/testServer.js +51 -7
  43. package/.github/workflows/tests.yml +0 -25
@@ -3,7 +3,7 @@
3
3
 
4
4
  var Config = {
5
5
  DEBUG: false,
6
- LIB_VERSION: '2.73.0'
6
+ LIB_VERSION: '2.74.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
@@ -1505,8 +1505,17 @@
1505
1505
  };
1506
1506
  }
1507
1507
 
1508
- _.localStorage = _storageWrapper(win.localStorage, 'localStorage', localStorageSupported);
1509
- _.sessionStorage = _storageWrapper(win.sessionStorage, 'sessionStorage', sessionStorageSupported);
1508
+ // Safari errors out accessing localStorage/sessionStorage when cookies are disabled,
1509
+ // so create dummy storage wrappers that silently fail as a fallback.
1510
+ var windowLocalStorage = null, windowSessionStorage = null;
1511
+ try {
1512
+ windowLocalStorage = win.localStorage;
1513
+ windowSessionStorage = win.sessionStorage;
1514
+ // eslint-disable-next-line no-empty
1515
+ } catch (_err) {}
1516
+
1517
+ _.localStorage = _storageWrapper(windowLocalStorage, 'localStorage', localStorageSupported);
1518
+ _.sessionStorage = _storageWrapper(windowSessionStorage, 'sessionStorage', sessionStorageSupported);
1510
1519
 
1511
1520
  _.register_event = (function() {
1512
1521
  // written by Dean Edwards, 2005
@@ -2656,6 +2665,18 @@
2656
2665
  }
2657
2666
  }
2658
2667
 
2668
+ function elementLooksSensitive(el) {
2669
+ var name = (el.name || el.id || '').toString().toLowerCase();
2670
+ 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"]
2671
+ var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
2672
+ if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
2673
+ return true;
2674
+ }
2675
+ }
2676
+
2677
+ return false;
2678
+ }
2679
+
2659
2680
  /*
2660
2681
  * Check whether a DOM element should be "tracked" or if it may contain sensitive data
2661
2682
  * using a variety of heuristics.
@@ -2708,13 +2729,8 @@
2708
2729
  }
2709
2730
  }
2710
2731
 
2711
- // filter out data from fields that look like sensitive fields
2712
- var name = el.name || el.id || '';
2713
- 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"]
2714
- var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
2715
- if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
2716
- return false;
2717
- }
2732
+ if (elementLooksSensitive(el)) {
2733
+ return false;
2718
2734
  }
2719
2735
 
2720
2736
  return true;
@@ -6842,6 +6858,9 @@
6842
6858
  /** @const */ var PAYLOAD_TYPE_BASE64 = 'base64';
6843
6859
  /** @const */ var PAYLOAD_TYPE_JSON = 'json';
6844
6860
  /** @const */ var DEVICE_ID_PREFIX = '$device:';
6861
+ /** @const */ var SETTING_STRICT = 'strict';
6862
+ /** @const */ var SETTING_FALLBACK = 'fallback';
6863
+ /** @const */ var SETTING_DISABLED = 'disabled';
6845
6864
 
6846
6865
 
6847
6866
  /*
@@ -6870,7 +6889,8 @@
6870
6889
  'engage': 'engage/',
6871
6890
  'groups': 'groups/',
6872
6891
  'record': 'record/',
6873
- 'flags': 'flags/'
6892
+ 'flags': 'flags/',
6893
+ 'settings': 'settings/'
6874
6894
  };
6875
6895
 
6876
6896
  /*
@@ -6934,12 +6954,12 @@
6934
6954
  'record_console': true,
6935
6955
  'record_heatmap_data': false,
6936
6956
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
6937
- 'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
6938
- 'record_mask_text_selector': '*',
6957
+ 'record_mask_inputs': true,
6939
6958
  'record_max_ms': MAX_RECORDING_MS,
6940
6959
  'record_min_ms': 0,
6941
6960
  'record_sessions_percent': 0,
6942
- 'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js'
6961
+ 'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js',
6962
+ 'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
6943
6963
  };
6944
6964
 
6945
6965
  var DOM_LOADED = false;
@@ -7177,7 +7197,16 @@
7177
7197
  this.autocapture.init();
7178
7198
 
7179
7199
  this._init_tab_id();
7180
- this._check_and_start_session_recording();
7200
+
7201
+ // Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
7202
+ var mode = this.get_config('remote_settings_mode');
7203
+ if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
7204
+ this._fetch_remote_settings(mode).then(_.bind(function() {
7205
+ this._check_and_start_session_recording();
7206
+ }, this));
7207
+ } else {
7208
+ this._check_and_start_session_recording();
7209
+ }
7181
7210
  };
7182
7211
 
7183
7212
  /**
@@ -7602,6 +7631,77 @@
7602
7631
  return succeeded;
7603
7632
  };
7604
7633
 
7634
+ MixpanelLib.prototype._fetch_remote_settings = function(mode) {
7635
+ var disableRecordingIfStrict = function() {
7636
+ if (mode === 'strict') {
7637
+ self.set_config({'record_sessions_percent': 0});
7638
+ }
7639
+ };
7640
+
7641
+ if (!win['AbortController']) {
7642
+ console.critical('Remote settings unavailable: missing minimum required APIs');
7643
+ disableRecordingIfStrict();
7644
+ return Promise.resolve();
7645
+ }
7646
+
7647
+ var settings_endpoint = this.get_api_host('settings') + '/' + this.get_config('api_routes')['settings'];
7648
+ var request_params = {
7649
+ '$lib_version': Config.LIB_VERSION,
7650
+ 'mp_lib': 'web',
7651
+ 'sdk_config': '1',
7652
+ };
7653
+ var query_string = _.HTTPBuildQuery(request_params);
7654
+ var full_url = settings_endpoint + '?' + query_string;
7655
+ var self = this;
7656
+
7657
+ var abortController = new AbortController();
7658
+ var timeout_id = setTimeout(function() {
7659
+ abortController.abort();
7660
+ }, 500);
7661
+ var fetchOptions = {
7662
+ 'method': 'GET',
7663
+ 'headers': {
7664
+ 'Authorization': 'Basic ' + btoa(self.get_config('token') + ':'),
7665
+ },
7666
+ 'signal': abortController.signal
7667
+ };
7668
+
7669
+ return win['fetch'](full_url, fetchOptions).then(function(response) {
7670
+ clearTimeout(timeout_id);
7671
+ if (!response['ok']) {
7672
+ console.critical('Network response was not ok');
7673
+ disableRecordingIfStrict();
7674
+ return;
7675
+ }
7676
+ return response.json();
7677
+ }).then(function(result) {
7678
+ if (result && result['sdk_config'] && result['sdk_config']['config']) {
7679
+ var remote_config = result['sdk_config']['config'];
7680
+
7681
+ // Verify that remote config contains only valid keys from DEFAULT_CONFIG
7682
+ var valid_config = {};
7683
+ _.each(remote_config, function(value, key) {
7684
+ if (DEFAULT_CONFIG.hasOwnProperty(key)) {
7685
+ valid_config[key] = value;
7686
+ }
7687
+ });
7688
+
7689
+ if (_.isEmptyObject(valid_config)) {
7690
+ console.critical('No valid config keys found in remote settings.');
7691
+ disableRecordingIfStrict();
7692
+ } else {
7693
+ self.set_config(valid_config);
7694
+ }
7695
+ } else {
7696
+ disableRecordingIfStrict();
7697
+ }
7698
+ }).catch(function(err) {
7699
+ clearTimeout(timeout_id);
7700
+ console.critical('Failed to fetch remote settings', err);
7701
+ disableRecordingIfStrict();
7702
+ });
7703
+ };
7704
+
7605
7705
  /**
7606
7706
  * _execute_array() deals with processing any mixpanel function
7607
7707
  * calls that were called before the Mixpanel library were loaded