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