humanbehavior-js 0.4.22 → 0.4.24

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 (52) hide show
  1. package/dist/cjs/angular/index.cjs +276 -387
  2. package/dist/cjs/angular/index.cjs.map +1 -1
  3. package/dist/cjs/index.cjs +272 -383
  4. package/dist/cjs/index.cjs.map +1 -1
  5. package/dist/cjs/install-wizard.cjs +5 -5
  6. package/dist/cjs/install-wizard.cjs.map +1 -1
  7. package/dist/cjs/react/index.cjs +282 -393
  8. package/dist/cjs/react/index.cjs.map +1 -1
  9. package/dist/cjs/remix/index.cjs +272 -383
  10. package/dist/cjs/remix/index.cjs.map +1 -1
  11. package/dist/cjs/svelte/index.cjs +272 -383
  12. package/dist/cjs/svelte/index.cjs.map +1 -1
  13. package/dist/cjs/vue/index.cjs +272 -383
  14. package/dist/cjs/vue/index.cjs.map +1 -1
  15. package/dist/cjs/wizard/index.cjs +5 -5
  16. package/dist/cjs/wizard/index.cjs.map +1 -1
  17. package/dist/cli/ai-auto-install.js +5 -5
  18. package/dist/cli/ai-auto-install.js.map +1 -1
  19. package/dist/cli/auto-install.js +5 -5
  20. package/dist/cli/auto-install.js.map +1 -1
  21. package/dist/esm/angular/index.js +276 -387
  22. package/dist/esm/angular/index.js.map +1 -1
  23. package/dist/esm/index.js +272 -383
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/install-wizard.js +5 -5
  26. package/dist/esm/install-wizard.js.map +1 -1
  27. package/dist/esm/react/index.js +282 -393
  28. package/dist/esm/react/index.js.map +1 -1
  29. package/dist/esm/remix/index.js +272 -383
  30. package/dist/esm/remix/index.js.map +1 -1
  31. package/dist/esm/svelte/index.js +272 -383
  32. package/dist/esm/svelte/index.js.map +1 -1
  33. package/dist/esm/vue/index.js +272 -383
  34. package/dist/esm/vue/index.js.map +1 -1
  35. package/dist/esm/wizard/index.js +5 -5
  36. package/dist/esm/wizard/index.js.map +1 -1
  37. package/dist/index.min.js +1 -1
  38. package/dist/index.min.js.map +1 -1
  39. package/dist/types/angular/index.d.ts +39 -9
  40. package/dist/types/index.d.ts +74 -59
  41. package/dist/types/install-wizard.d.ts +1 -1
  42. package/dist/types/react/index.d.ts +40 -10
  43. package/dist/types/remix/index.d.ts +37 -7
  44. package/dist/types/svelte/index.d.ts +37 -7
  45. package/dist/types/wizard/index.d.ts +1 -1
  46. package/package.json +1 -1
  47. package/readme.md +59 -5
  48. package/src/angular/index.ts +4 -4
  49. package/src/react/index.tsx +10 -10
  50. package/src/redact.ts +205 -399
  51. package/src/tracker.ts +103 -19
  52. package/src/wizard/core/install-wizard.ts +5 -5
@@ -12886,15 +12886,15 @@ class HumanBehaviorAPI {
12886
12886
  }
12887
12887
  }
12888
12888
 
12889
- // Redaction functionality for sensitive input fields
12890
- // This module provides methods to configure rrweb's built-in masking
12891
- // Uses CSS selectors and classes for reliable redaction without event corruption
12892
- // Check if we're in a browser environment
12893
- const isBrowser$3 = typeof window !== 'undefined';
12889
+ // Simplified redaction functionality for HumanBehavior SDK
12890
+ // Since rrweb auto-redacts all input fields by default, this module only handles
12891
+ // selectively unredacting specific fields (except passwords which remain protected)
12894
12892
  class RedactionManager {
12895
12893
  constructor(options) {
12896
12894
  this.redactedText = '[REDACTED]';
12897
- this.userSelectedFields = new Set(); // User-selected fields to redact
12895
+ this.unredactedFields = new Set(); // Fields that user wants to unredact
12896
+ this.redactedFields = new Set(); // Fields that user wants to redact
12897
+ this.redactionMode = 'privacy-first';
12898
12898
  this.excludeSelectors = [
12899
12899
  '[data-no-redact="true"]',
12900
12900
  '.human-behavior-no-redact'
@@ -12905,419 +12905,180 @@ class RedactionManager {
12905
12905
  if (options === null || options === void 0 ? void 0 : options.excludeSelectors) {
12906
12906
  this.excludeSelectors = [...this.excludeSelectors, ...options.excludeSelectors];
12907
12907
  }
12908
+ // Handle new redaction strategy
12909
+ if (options === null || options === void 0 ? void 0 : options.redactionStrategy) {
12910
+ this.redactionMode = options.redactionStrategy.mode;
12911
+ if (this.redactionMode === 'privacy-first') {
12912
+ // Privacy-first: everything redacted by default, unredact specific fields
12913
+ if (options.redactionStrategy.unredactFields) {
12914
+ this.setFieldsToUnredact(options.redactionStrategy.unredactFields);
12915
+ }
12916
+ }
12917
+ else {
12918
+ // Visibility-first: everything visible by default, redact specific fields
12919
+ if (options.redactionStrategy.redactFields) {
12920
+ this.setFieldsToRedact(options.redactionStrategy.redactFields);
12921
+ }
12922
+ }
12923
+ }
12924
+ // Handle legacy redactFields (backward compatibility)
12925
+ if (options === null || options === void 0 ? void 0 : options.legacyRedactFields) {
12926
+ this.setFieldsToUnredact(options.legacyRedactFields);
12927
+ }
12928
+ // Handle legacy userFields
12908
12929
  if (options === null || options === void 0 ? void 0 : options.userFields) {
12909
- this.setFieldsToRedact(options.userFields);
12930
+ this.setFieldsToUnredact(options.userFields);
12910
12931
  }
12911
12932
  }
12912
12933
  /**
12913
- * Set specific fields to be redacted using CSS selectors
12914
- * These selectors are used to configure rrweb's built-in masking
12934
+ * Set specific fields to be redacted (for visibility-first mode)
12915
12935
  * @param fields Array of CSS selectors for fields to redact
12916
12936
  */
12917
12937
  setFieldsToRedact(fields) {
12918
- this.userSelectedFields.clear();
12919
- fields.forEach(field => this.userSelectedFields.add(field));
12920
- if (fields.length > 0) {
12921
- logDebug(`Redaction: Active for ${fields.length} field(s):`, fields);
12922
- // Debug: Check if elements exist
12923
- fields.forEach(selector => {
12924
- const elements = document.querySelectorAll(selector);
12925
- logDebug(`Redaction: Found ${elements.length} element(s) for selector '${selector}'`);
12926
- elements.forEach((el, index) => {
12927
- logDebug(`Redaction: Element ${index} for '${selector}':`, el);
12928
- });
12929
- });
12938
+ this.redactedFields.clear();
12939
+ // Always include password fields in redacted list
12940
+ const passwordFields = [
12941
+ 'input[type="password"]',
12942
+ 'input[type="password" i]',
12943
+ '[type="password"]',
12944
+ '[type="password" i]'
12945
+ ];
12946
+ // Add password fields and user-specified fields
12947
+ [...passwordFields, ...fields].forEach(field => {
12948
+ this.redactedFields.add(field);
12949
+ });
12950
+ if (this.redactedFields.size > 0) {
12951
+ logDebug(`Redaction: Active for ${this.redactedFields.size} field(s):`, Array.from(this.redactedFields));
12930
12952
  }
12931
12953
  else {
12932
- logDebug('Redaction: Disabled - no fields selected');
12933
- }
12934
- }
12935
- /**
12936
- * Check if redaction is currently active (has fields selected)
12937
- */
12938
- isActive() {
12939
- return this.userSelectedFields.size > 0;
12940
- }
12941
- /**
12942
- * Get the currently selected fields for redaction
12943
- */
12944
- getSelectedFields() {
12945
- return Array.from(this.userSelectedFields);
12946
- }
12947
- /**
12948
- * Process an event and redact sensitive data if needed
12949
- * NOTE: This method is no longer used - events are handled directly by rrweb
12950
- * Kept for backward compatibility but not called in the current implementation
12951
- */
12952
- processEvent(event) {
12953
- // Only process if we have fields selected for redaction
12954
- if (this.userSelectedFields.size === 0) {
12955
- return event;
12956
- }
12957
- // Clone the event to avoid modifying the original
12958
- const processedEvent = JSON.parse(JSON.stringify(event));
12959
- // Handle different event types
12960
- if (processedEvent.type === 3) { // IncrementalSnapshot
12961
- if (processedEvent.data.source === 5) { // Input event
12962
- const shouldRedact = this.isFieldSelected(processedEvent.data);
12963
- if (shouldRedact) {
12964
- logDebug('Redaction: Processing input event for redaction');
12965
- this.redactInputEvent(processedEvent.data);
12966
- }
12967
- }
12968
- // Also check for other sources that might contain text changes
12969
- else if (processedEvent.data.source === 0) { // DOM mutations
12970
- this.redactDOMEvent(processedEvent.data);
12971
- }
12972
- // Handle other sources that might contain text
12973
- else if (processedEvent.data.source === 2) { // Mouse/Touch interaction
12974
- this.redactMouseEvent(processedEvent.data);
12975
- }
12976
- }
12977
- else if (processedEvent.type === 2) { // FullSnapshot
12978
- this.redactFullSnapshot(processedEvent.data);
12954
+ logDebug('Redaction: No fields to redact');
12979
12955
  }
12980
- return processedEvent;
12956
+ this.applyRedactionClasses();
12981
12957
  }
12982
12958
  /**
12983
- * Redact sensitive data in input events
12959
+ * Set specific fields to be unredacted (everything else stays redacted by rrweb)
12960
+ * @param fields Array of CSS selectors for fields to unredact
12984
12961
  */
12985
- redactInputEvent(inputData) {
12986
- // Check if this input event is from a field we want to redact
12987
- if (!this.isFieldSelected(inputData)) {
12988
- return;
12989
- }
12990
- logDebug('Redaction: Redacting input event with text:', inputData.text);
12991
- // Redact all text-related properties that could contain input data
12992
- const textProperties = ['text', 'value', 'content', 'data', 'input', 'textContent'];
12993
- textProperties.forEach(prop => {
12994
- if (inputData[prop] !== undefined && typeof inputData[prop] === 'string') {
12995
- inputData[prop] = this.redactedText;
12996
- logDebug(`Redaction: Redacted property '${prop}'`);
12997
- }
12998
- });
12999
- // Also check for any other string properties that might contain input data
13000
- Object.keys(inputData).forEach(key => {
13001
- if (typeof inputData[key] === 'string' && inputData[key].length > 0) {
13002
- inputData[key] = this.redactedText;
13003
- logDebug(`Redaction: Redacted additional property '${key}'`);
13004
- }
12962
+ setFieldsToUnredact(fields) {
12963
+ this.unredactedFields.clear();
12964
+ // Filter out password fields (they cannot be unredacted)
12965
+ const validFields = fields.filter(field => {
12966
+ const isPasswordField = this.isPasswordSelector(field);
12967
+ if (isPasswordField) {
12968
+ logWarn(`Cannot unredact password field: ${field} - Password fields are always protected`);
12969
+ return false;
12970
+ }
12971
+ return true;
13005
12972
  });
13006
- // Handle nested objects that might contain text data
13007
- if (inputData.attributes && typeof inputData.attributes === 'object') {
13008
- if (inputData.attributes.value && typeof inputData.attributes.value === 'string') {
13009
- inputData.attributes.value = this.redactedText;
13010
- logDebug('Redaction: Redacted nested value attribute');
13011
- }
13012
- }
13013
- logDebug('Redaction: Input event redaction complete');
13014
- }
13015
- /**
13016
- * Redact sensitive data in DOM mutation events
13017
- */
13018
- redactDOMEvent(domData) {
13019
- // Check for text changes in DOM mutations
13020
- if (domData.texts && Array.isArray(domData.texts)) {
13021
- domData.texts.forEach((textChange) => {
13022
- if (textChange.text && typeof textChange.text === 'string' &&
13023
- this.shouldRedactDOMChange(textChange)) {
13024
- textChange.text = this.redactedText;
13025
- }
13026
- });
13027
- }
13028
- // Also check for attribute changes that might contain input data
13029
- if (domData.attributes && Array.isArray(domData.attributes)) {
13030
- domData.attributes.forEach((attrChange) => {
13031
- if (attrChange.attributes && attrChange.attributes.value &&
13032
- typeof attrChange.attributes.value === 'string' &&
13033
- this.shouldRedactDOMChange(attrChange)) {
13034
- attrChange.attributes.value = this.redactedText;
13035
- }
13036
- });
12973
+ validFields.forEach(field => this.unredactedFields.add(field));
12974
+ if (validFields.length > 0) {
12975
+ logDebug(`Unredaction: Active for ${validFields.length} field(s):`, validFields);
13037
12976
  }
13038
- // Check for any other properties that might contain text data
13039
- if (domData.adds && Array.isArray(domData.adds)) {
13040
- domData.adds.forEach((add) => {
13041
- if (add.node && add.node.textContent && typeof add.node.textContent === 'string' &&
13042
- this.shouldRedactDOMChange(add)) {
13043
- add.node.textContent = this.redactedText;
13044
- }
13045
- });
12977
+ else {
12978
+ logDebug('Unredaction: No valid fields to unredact');
13046
12979
  }
12980
+ this.applyUnredactionClasses();
13047
12981
  }
13048
12982
  /**
13049
- * Check if a DOM change should be redacted based on its ID
12983
+ * Remove specific fields from unredaction (they become redacted again)
12984
+ * @param fields Array of CSS selectors for fields to redact
13050
12985
  */
13051
- shouldRedactDOMChange(changeData) {
13052
- if (!isBrowser$3)
13053
- return false;
13054
- try {
13055
- // Check if this change has an ID that we can use to find the element
13056
- const elementId = changeData.id;
13057
- if (elementId !== undefined) {
13058
- // Try to find the element by data-rrweb-id attribute
13059
- let element = document.querySelector(`[data-rrweb-id="${elementId}"]`);
13060
- if (element) {
13061
- return this.shouldRedactElement(element);
13062
- }
13063
- }
13064
- // Also check for nodeId which is another way rrweb identifies elements
13065
- const nodeId = changeData.nodeId;
13066
- if (nodeId !== undefined) {
13067
- const element = document.querySelector(`[data-rrweb-id="${nodeId}"]`);
13068
- if (element) {
13069
- return this.shouldRedactElement(element);
13070
- }
13071
- }
13072
- return false;
12986
+ redactFields(fields) {
12987
+ fields.forEach(field => {
12988
+ this.unredactedFields.delete(field);
12989
+ });
12990
+ if (this.unredactedFields.size > 0) {
12991
+ logDebug(`Unredaction: Removed ${fields.length} field(s), ${this.unredactedFields.size} remaining:`, Array.from(this.unredactedFields));
13073
12992
  }
13074
- catch (e) {
13075
- logWarn('Error checking if DOM change should be redacted:', e);
13076
- return false;
12993
+ else {
12994
+ logDebug('Unredaction: All fields redacted');
13077
12995
  }
12996
+ this.applyUnredactionClasses();
13078
12997
  }
13079
12998
  /**
13080
- * Redact sensitive data in mouse/touch interaction events
12999
+ * Clear all unredacted fields (everything becomes redacted again)
13081
13000
  */
13082
- redactMouseEvent(mouseData) {
13083
- // Mouse events typically don't contain text data, but check for any text properties
13084
- if (mouseData.text && typeof mouseData.text === 'string' &&
13085
- this.isFieldSelected(mouseData)) {
13086
- mouseData.text = this.redactedText;
13087
- }
13001
+ clearUnredactedFields() {
13002
+ this.unredactedFields.clear();
13003
+ logDebug('Unredaction: All fields cleared, everything redacted');
13004
+ this.removeUnredactionClasses();
13088
13005
  }
13089
13006
  /**
13090
- * Redact sensitive data in full snapshot events
13007
+ * Check if any fields are currently unredacted
13091
13008
  */
13092
- redactFullSnapshot(snapshotData) {
13093
- if (snapshotData.node && snapshotData.node.type === 2) { // Element node
13094
- this.redactNode(snapshotData.node);
13095
- }
13009
+ hasUnredactedFields() {
13010
+ return this.unredactedFields.size > 0;
13096
13011
  }
13097
13012
  /**
13098
- * Recursively redact sensitive data in DOM nodes
13013
+ * Get the current redaction mode
13099
13014
  */
13100
- redactNode(node) {
13101
- if (!node)
13102
- return;
13103
- // Check if this node should be redacted
13104
- if (node.type === 2 && node.tagName &&
13105
- (node.tagName.toLowerCase() === 'input' || node.tagName.toLowerCase() === 'textarea')) {
13106
- // Check if this input/textarea should be redacted
13107
- if (this.shouldRedactNode(node)) {
13108
- // Redact value attribute
13109
- if (node.attributes && node.attributes.value) {
13110
- node.attributes.value = this.redactedText;
13111
- }
13112
- // Redact text content
13113
- if (node.textContent) {
13114
- node.textContent = this.redactedText;
13115
- }
13116
- }
13117
- }
13118
- // Recursively process child nodes
13119
- if (node.childNodes && Array.isArray(node.childNodes)) {
13120
- node.childNodes.forEach((childNode) => {
13121
- this.redactNode(childNode);
13122
- });
13123
- }
13015
+ getRedactionMode() {
13016
+ return this.redactionMode;
13124
13017
  }
13125
13018
  /**
13126
- * Check if a node should be redacted based on its attributes
13019
+ * Get the currently unredacted fields
13127
13020
  */
13128
- shouldRedactNode(node) {
13129
- if (!node.attributes)
13130
- return false;
13131
- // Check if any of our selectors would match this node
13132
- for (const selector of this.userSelectedFields) {
13133
- if (this.selectorMatchesNode(selector, node)) {
13134
- return true;
13135
- }
13136
- }
13137
- return false;
13021
+ getUnredactedFields() {
13022
+ return Array.from(this.unredactedFields);
13138
13023
  }
13139
13024
  /**
13140
- * Check if a CSS selector would match a node based on its attributes
13141
- */
13142
- selectorMatchesNode(selector, node) {
13143
- if (!node.attributes)
13144
- return false;
13145
- // Create a temporary element to test the selector
13146
- try {
13147
- const tempElement = document.createElement(node.tagName || 'div');
13148
- // Copy attributes from the node to the temp element
13149
- if (node.attributes) {
13150
- Object.keys(node.attributes).forEach(key => {
13151
- tempElement.setAttribute(key, node.attributes[key]);
13152
- });
13153
- }
13154
- // Test if the selector matches this element
13155
- return tempElement.matches(selector);
13156
- }
13157
- catch (e) {
13158
- // If matches() is not supported or fails, fall back to basic attribute checking
13159
- return this.basicSelectorMatch(selector, node);
13160
- }
13161
- }
13162
- /**
13163
- * Basic selector matching for environments where matches() is not available
13025
+ * Get CSS selectors for rrweb masking configuration
13026
+ * Returns null if no fields are unredacted (everything stays redacted)
13164
13027
  */
13165
- basicSelectorMatch(selector, node) {
13166
- if (!node.attributes)
13167
- return false;
13168
- // Handle simple selectors like 'input[type="password"]'
13169
- if (selector.includes('input[type=')) {
13170
- const typeMatch = selector.match(/input\[type="([^"]+)"\]/);
13171
- if (typeMatch && node.tagName === 'input' && node.attributes.type === typeMatch[1]) {
13172
- return true;
13028
+ getMaskTextSelector() {
13029
+ if (this.redactionMode === 'privacy-first') {
13030
+ // Privacy-first: mask everything except unredacted fields
13031
+ if (this.unredactedFields.size === 0) {
13032
+ return null; // Everything stays redacted
13173
13033
  }
13034
+ return Array.from(this.unredactedFields).join(',');
13174
13035
  }
13175
- // Handle ID selectors like '#email'
13176
- if (selector.startsWith('#')) {
13177
- const id = selector.substring(1);
13178
- return node.attributes.id === id;
13179
- }
13180
- // Handle class selectors like '.sensitive-field'
13181
- if (selector.startsWith('.')) {
13182
- const className = selector.substring(1);
13183
- return node.attributes.class && node.attributes.class.includes(className);
13184
- }
13185
- // Handle tag selectors like 'input'
13186
- if (!selector.includes('[') && !selector.includes('.')) {
13187
- return node.tagName && node.tagName.toLowerCase() === selector.toLowerCase();
13188
- }
13189
- return false;
13190
- }
13191
- /**
13192
- * Check if an event is from a field that should be redacted
13193
- */
13194
- isFieldSelected(eventData) {
13195
- if (!isBrowser$3)
13196
- return false;
13197
- try {
13198
- // For input events (source 5), we need to determine if this is a sensitive field
13199
- if (eventData.source === 5) { // Input event
13200
- const elementId = eventData.id;
13201
- if (elementId !== undefined) {
13202
- // Try to find the element by data-rrweb-id attribute
13203
- let element = document.querySelector(`[data-rrweb-id="${elementId}"]`);
13204
- if (element) {
13205
- return this.shouldRedactElement(element);
13206
- }
13207
- // Fallback: Try to find by nodeId if available
13208
- if (eventData.nodeId !== undefined) {
13209
- element = document.querySelector(`[data-rrweb-id="${eventData.nodeId}"]`);
13210
- if (element) {
13211
- return this.shouldRedactElement(element);
13212
- }
13213
- }
13214
- // More aggressive approach: Check all elements that match our selectors
13215
- // and see if any of them are currently focused or have the same ID
13216
- for (const selector of this.userSelectedFields) {
13217
- const matchingElements = document.querySelectorAll(selector);
13218
- if (matchingElements.length > 0) {
13219
- // Check if any of these elements are currently focused
13220
- for (const el of matchingElements) {
13221
- if (el === document.activeElement) {
13222
- logDebug('Redaction: Found focused element matching selector:', selector);
13223
- return true;
13224
- }
13225
- }
13226
- }
13227
- }
13228
- // If we still can't find it, try a more direct approach
13229
- // Look for any input element that might be the active one
13230
- const activeElement = document.activeElement;
13231
- if (activeElement && this.shouldRedactElement(activeElement)) {
13232
- logDebug('Redaction: Active element should be redacted');
13233
- return true;
13234
- }
13235
- return false;
13236
- }
13237
- }
13238
- // For other event types, try to find the element
13239
- const elementId = eventData.id;
13240
- if (elementId !== undefined) {
13241
- // First try to find by data-rrweb-id attribute
13242
- let element = document.querySelector(`[data-rrweb-id="${elementId}"]`);
13243
- if (element) {
13244
- return this.shouldRedactElement(element);
13245
- }
13246
- }
13247
- // Also check for nodeId which is another way rrweb identifies elements
13248
- const nodeId = eventData.nodeId;
13249
- if (nodeId !== undefined) {
13250
- const element = document.querySelector(`[data-rrweb-id="${nodeId}"]`);
13251
- if (element) {
13252
- return this.shouldRedactElement(element);
13253
- }
13254
- }
13255
- // For DOM mutations, check if the target element should be redacted
13256
- if (eventData.target && eventData.target.id) {
13257
- const element = document.querySelector(`[data-rrweb-id="${eventData.target.id}"]`);
13258
- if (element) {
13259
- return this.shouldRedactElement(element);
13260
- }
13036
+ else {
13037
+ // Visibility-first: mask only redacted fields
13038
+ if (this.redactedFields.size === 0) {
13039
+ return null; // Nothing to redact
13261
13040
  }
13262
- return false;
13263
- }
13264
- catch (e) {
13265
- logWarn('Error checking if field should be redacted:', e);
13266
- return false;
13267
- }
13268
- }
13269
- /**
13270
- * Get CSS selectors for rrweb masking configuration
13271
- * Used to configure rrweb's maskTextSelector option
13272
- */
13273
- getMaskTextSelector() {
13274
- if (this.userSelectedFields.size === 0) {
13275
- return null;
13041
+ return Array.from(this.redactedFields).join(',');
13276
13042
  }
13277
- return Array.from(this.userSelectedFields).join(',');
13278
13043
  }
13279
13044
  /**
13280
- * Check if an element should be redacted (for rrweb maskTextFn/maskInputFn)
13045
+ * Apply redaction classes to DOM elements (for visibility-first mode)
13046
+ * Adds 'rr-mask' class to elements that should be redacted
13281
13047
  */
13282
- shouldRedactElement(element) {
13283
- if (this.userSelectedFields.size === 0) {
13284
- return false;
13048
+ applyRedactionClasses() {
13049
+ if (this.redactedFields.size === 0) {
13050
+ return;
13285
13051
  }
13286
- // Check if any selector matches this element
13287
- for (const selector of this.userSelectedFields) {
13052
+ // Add 'rr-mask' class to redacted elements
13053
+ this.redactedFields.forEach(selector => {
13288
13054
  try {
13289
- if (element.matches(selector)) {
13290
- return true;
13291
- }
13055
+ const elements = document.querySelectorAll(selector);
13056
+ elements.forEach(element => {
13057
+ element.classList.add('rr-mask');
13058
+ });
13059
+ logDebug(`Added rr-mask class to ${elements.length} element(s) for selector: ${selector}`);
13292
13060
  }
13293
13061
  catch (e) {
13294
- // Invalid selector, skip
13295
13062
  logWarn(`Invalid selector: ${selector}`);
13296
13063
  }
13297
- }
13298
- return false;
13064
+ });
13299
13065
  }
13300
13066
  /**
13301
- * Apply rrweb masking classes to DOM elements
13302
- * Adds 'rr-mask' class to elements that should be redacted
13303
- * This enables rrweb's built-in masking functionality
13067
+ * Apply unredaction classes to DOM elements
13068
+ * Removes 'rr-mask' class from elements that should be unredacted
13304
13069
  */
13305
- applyRedactionClasses() {
13306
- if (this.userSelectedFields.size === 0) {
13070
+ applyUnredactionClasses() {
13071
+ if (this.unredactedFields.size === 0) {
13307
13072
  return;
13308
13073
  }
13309
- // Remove existing redaction classes
13310
- document.querySelectorAll('.rr-mask').forEach(element => {
13311
- element.classList.remove('rr-mask');
13312
- });
13313
- // Add redaction classes to matching elements
13314
- this.userSelectedFields.forEach(selector => {
13074
+ // Remove 'rr-mask' class from unredacted elements
13075
+ this.unredactedFields.forEach(selector => {
13315
13076
  try {
13316
13077
  const elements = document.querySelectorAll(selector);
13317
13078
  elements.forEach(element => {
13318
- element.classList.add('rr-mask');
13079
+ element.classList.remove('rr-mask');
13319
13080
  });
13320
- logDebug(`Applied rr-mask class to ${elements.length} element(s) for selector: ${selector}`);
13081
+ logDebug(`Removed rr-mask class from ${elements.length} element(s) for selector: ${selector}`);
13321
13082
  }
13322
13083
  catch (e) {
13323
13084
  logWarn(`Invalid selector: ${selector}`);
@@ -13325,7 +13086,26 @@ class RedactionManager {
13325
13086
  });
13326
13087
  }
13327
13088
  /**
13328
- * Get the original value of a redacted element (for debugging)
13089
+ * Remove all unredaction classes from DOM elements
13090
+ */
13091
+ removeUnredactionClasses() {
13092
+ // Note: This doesn't add 'rr-mask' classes back - rrweb handles that automatically
13093
+ logDebug('Unredaction classes removed');
13094
+ }
13095
+ /**
13096
+ * Check if a selector represents a password field
13097
+ */
13098
+ isPasswordSelector(selector) {
13099
+ const passwordPatterns = [
13100
+ 'input[type="password"]',
13101
+ 'input[type="password" i]',
13102
+ '[type="password"]',
13103
+ '[type="password" i]'
13104
+ ];
13105
+ return passwordPatterns.some(pattern => selector.toLowerCase().includes(pattern.toLowerCase().replace(/[\[\]]/g, '')));
13106
+ }
13107
+ /**
13108
+ * Get the original value of an element (for debugging)
13329
13109
  */
13330
13110
  getOriginalValue(element) {
13331
13111
  if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
@@ -13334,10 +13114,51 @@ class RedactionManager {
13334
13114
  return undefined;
13335
13115
  }
13336
13116
  /**
13337
- * Check if an element is currently being redacted
13117
+ * Check if an element is currently unredacted
13338
13118
  */
13339
- isElementRedacted(element) {
13340
- return this.shouldRedactElement(element);
13119
+ isElementUnredacted(element) {
13120
+ return this.shouldUnredactElement(element);
13121
+ }
13122
+ /**
13123
+ * Check if an element should be unredacted
13124
+ */
13125
+ shouldUnredactElement(element) {
13126
+ if (this.redactionMode === 'privacy-first') {
13127
+ // Privacy-first: check if element is in unredacted fields
13128
+ if (this.unredactedFields.size === 0) {
13129
+ return false; // Nothing unredacted
13130
+ }
13131
+ // Check if any selector matches this element
13132
+ for (const selector of this.unredactedFields) {
13133
+ try {
13134
+ if (element.matches(selector)) {
13135
+ return true;
13136
+ }
13137
+ }
13138
+ catch (e) {
13139
+ logWarn(`Invalid selector: ${selector}`);
13140
+ }
13141
+ }
13142
+ return false;
13143
+ }
13144
+ else {
13145
+ // Visibility-first: check if element is NOT in redacted fields
13146
+ if (this.redactedFields.size === 0) {
13147
+ return true; // Nothing redacted, everything visible
13148
+ }
13149
+ // Check if any selector matches this element
13150
+ for (const selector of this.redactedFields) {
13151
+ try {
13152
+ if (element.matches(selector)) {
13153
+ return false; // Element is redacted
13154
+ }
13155
+ }
13156
+ catch (e) {
13157
+ logWarn(`Invalid selector: ${selector}`);
13158
+ }
13159
+ }
13160
+ return true; // Element is not redacted
13161
+ }
13341
13162
  }
13342
13163
  }
13343
13164
  // Export a default instance
@@ -13906,11 +13727,22 @@ class HumanBehaviorTracker {
13906
13727
  });
13907
13728
  // Store canvas recording preference
13908
13729
  tracker.recordCanvas = (_a = options === null || options === void 0 ? void 0 : options.recordCanvas) !== null && _a !== void 0 ? _a : false;
13909
- // Set redacted fields if specified
13730
+ // Set unredacted fields if specified (legacy support)
13910
13731
  if (options === null || options === void 0 ? void 0 : options.redactFields) {
13911
- tracker.setRedactedFields(options.redactFields);
13912
- // ✅ Apply redaction classes to existing elements
13913
- tracker.redactionManager.applyRedactionClasses();
13732
+ tracker.setUnredactedFields(options.redactFields);
13733
+ }
13734
+ // Handle new redaction strategy
13735
+ if (options === null || options === void 0 ? void 0 : options.redactionStrategy) {
13736
+ if (options.redactionStrategy.mode === 'privacy-first') {
13737
+ if (options.redactionStrategy.unredactFields) {
13738
+ tracker.setUnredactedFields(options.redactionStrategy.unredactFields);
13739
+ }
13740
+ }
13741
+ else {
13742
+ if (options.redactionStrategy.redactFields) {
13743
+ tracker.setRedactedFields(options.redactionStrategy.redactFields);
13744
+ }
13745
+ }
13914
13746
  }
13915
13747
  // Setup automatic tracking if enabled
13916
13748
  if ((options === null || options === void 0 ? void 0 : options.enableAutomaticTracking) !== false) {
@@ -13957,7 +13789,10 @@ class HumanBehaviorTracker {
13957
13789
  ingestionUrl: ingestionUrl || defaultIngestionUrl
13958
13790
  });
13959
13791
  this.apiKey = apiKey;
13960
- this.redactionManager = new RedactionManager();
13792
+ this.redactionManager = new RedactionManager({
13793
+ redactionStrategy: options === null || options === void 0 ? void 0 : options.redactionStrategy,
13794
+ legacyRedactFields: options === null || options === void 0 ? void 0 : options.redactFields // For backward compatibility
13795
+ });
13961
13796
  // Initialize property manager
13962
13797
  this.propertyManager = new PropertyManager({
13963
13798
  enableAutomaticProperties: (options === null || options === void 0 ? void 0 : options.enableAutomaticProperties) !== false,
@@ -14535,6 +14370,31 @@ class HumanBehaviorTracker {
14535
14370
  if (!userResponse.ok) {
14536
14371
  throw new Error(`Failed to identify user: ${userResponse.statusText}`);
14537
14372
  }
14373
+ // Get IP info and GeoIP data
14374
+ try {
14375
+ const ipResponse = yield fetch(`${this.api['baseUrl']}/api/ingestion/ip-info`, {
14376
+ method: 'POST',
14377
+ headers: {
14378
+ 'Content-Type': 'application/json',
14379
+ 'Authorization': `Bearer ${this.apiKey}`
14380
+ },
14381
+ body: JSON.stringify({
14382
+ sessionId: this.sessionId,
14383
+ clientIP: null, // Let server detect from headers
14384
+ ipDetectionMethod: 'headers',
14385
+ timestamp: new Date().toISOString()
14386
+ })
14387
+ });
14388
+ if (ipResponse.ok) {
14389
+ logDebug('✅ IP info and GeoIP data retrieved successfully');
14390
+ }
14391
+ else {
14392
+ logDebug(`⚠️ IP info request failed: ${ipResponse.statusText}`);
14393
+ }
14394
+ }
14395
+ catch (error) {
14396
+ logDebug(`⚠️ IP info request error: ${error}`);
14397
+ }
14538
14398
  // Don't update endUserId - keep it as the original UUID
14539
14399
  return originalEndUserId || '';
14540
14400
  });
@@ -14574,7 +14434,7 @@ class HumanBehaviorTracker {
14574
14434
  // ✅ HUMANBEHAVIOR'S CUSTOM SETTINGS
14575
14435
  maskTextSelector: this.redactionManager.getMaskTextSelector() || undefined,
14576
14436
  maskTextFn: undefined,
14577
- maskAllInputs: true, // HumanBehavior default
14437
+ maskAllInputs: this.redactionManager.getRedactionMode() === 'privacy-first', // Configurable based on strategy
14578
14438
  maskInputOptions: { password: true }, // HumanBehavior default
14579
14439
  maskInputFn: undefined,
14580
14440
  slimDOMOptions: {},
@@ -14884,15 +14744,23 @@ class HumanBehaviorTracker {
14884
14744
  });
14885
14745
  }
14886
14746
  /**
14887
- * Set specific fields to be redacted during session recording
14888
- * Uses rrweb's built-in masking instead of custom redaction processing
14889
- * @param fields Array of CSS selectors for fields to redact (e.g., ['input[type="password"]', '#email-field'])
14747
+ * Set specific fields to be redacted (for visibility-first mode)
14748
+ * @param fields Array of CSS selectors for fields to redact
14890
14749
  */
14891
14750
  setRedactedFields(fields) {
14892
14751
  this.redactionManager.setFieldsToRedact(fields);
14893
- // ✅ APPLY RRWEB MASKING CLASSES - More reliable than custom processing
14894
- this.redactionManager.applyRedactionClasses();
14895
- // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures masking is applied
14752
+ // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures redaction is applied
14753
+ if (this.recordInstance) {
14754
+ this.restartWithNewRedaction();
14755
+ }
14756
+ }
14757
+ /**
14758
+ * Set specific fields to be unredacted (everything else stays redacted by rrweb)
14759
+ * @param fields Array of CSS selectors for fields to unredact (e.g., ['#username', '#comment'])
14760
+ */
14761
+ setUnredactedFields(fields) {
14762
+ this.redactionManager.setFieldsToUnredact(fields);
14763
+ // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures unredaction is applied
14896
14764
  if (this.recordInstance) {
14897
14765
  this.restartWithNewRedaction();
14898
14766
  }
@@ -14904,16 +14772,37 @@ class HumanBehaviorTracker {
14904
14772
  }
14905
14773
  }
14906
14774
  /**
14907
- * Check if redaction is currently active
14775
+ * Check if any fields are currently unredacted
14908
14776
  */
14909
- isRedactionActive() {
14910
- return this.redactionManager.isActive();
14777
+ hasUnredactedFields() {
14778
+ return this.redactionManager.hasUnredactedFields();
14911
14779
  }
14912
14780
  /**
14913
- * Get the currently selected fields for redaction
14781
+ * Get the currently unredacted fields
14914
14782
  */
14915
- getRedactedFields() {
14916
- return this.redactionManager.getSelectedFields();
14783
+ getUnredactedFields() {
14784
+ return this.redactionManager.getUnredactedFields();
14785
+ }
14786
+ /**
14787
+ * Remove specific fields from unredaction (they become redacted again)
14788
+ * @param fields Array of CSS selectors for fields to redact
14789
+ */
14790
+ redactFields(fields) {
14791
+ this.redactionManager.redactFields(fields);
14792
+ // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures redaction is updated
14793
+ if (this.recordInstance) {
14794
+ this.restartWithNewRedaction();
14795
+ }
14796
+ }
14797
+ /**
14798
+ * Clear all unredacted fields (everything becomes redacted again)
14799
+ */
14800
+ clearUnredactedFields() {
14801
+ this.redactionManager.clearUnredactedFields();
14802
+ // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures redaction is updated
14803
+ if (this.recordInstance) {
14804
+ this.restartWithNewRedaction();
14805
+ }
14917
14806
  }
14918
14807
  /**
14919
14808
  * Get the current session ID