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
@@ -12901,15 +12901,15 @@ class HumanBehaviorAPI {
12901
12901
  }
12902
12902
  }
12903
12903
 
12904
- // Redaction functionality for sensitive input fields
12905
- // This module provides methods to configure rrweb's built-in masking
12906
- // Uses CSS selectors and classes for reliable redaction without event corruption
12907
- // Check if we're in a browser environment
12908
- const isBrowser$2 = typeof window !== 'undefined';
12904
+ // Simplified redaction functionality for HumanBehavior SDK
12905
+ // Since rrweb auto-redacts all input fields by default, this module only handles
12906
+ // selectively unredacting specific fields (except passwords which remain protected)
12909
12907
  class RedactionManager {
12910
12908
  constructor(options) {
12911
12909
  this.redactedText = '[REDACTED]';
12912
- this.userSelectedFields = new Set(); // User-selected fields to redact
12910
+ this.unredactedFields = new Set(); // Fields that user wants to unredact
12911
+ this.redactedFields = new Set(); // Fields that user wants to redact
12912
+ this.redactionMode = 'privacy-first';
12913
12913
  this.excludeSelectors = [
12914
12914
  '[data-no-redact="true"]',
12915
12915
  '.human-behavior-no-redact'
@@ -12920,419 +12920,180 @@ class RedactionManager {
12920
12920
  if (options === null || options === void 0 ? void 0 : options.excludeSelectors) {
12921
12921
  this.excludeSelectors = [...this.excludeSelectors, ...options.excludeSelectors];
12922
12922
  }
12923
+ // Handle new redaction strategy
12924
+ if (options === null || options === void 0 ? void 0 : options.redactionStrategy) {
12925
+ this.redactionMode = options.redactionStrategy.mode;
12926
+ if (this.redactionMode === 'privacy-first') {
12927
+ // Privacy-first: everything redacted by default, unredact specific fields
12928
+ if (options.redactionStrategy.unredactFields) {
12929
+ this.setFieldsToUnredact(options.redactionStrategy.unredactFields);
12930
+ }
12931
+ }
12932
+ else {
12933
+ // Visibility-first: everything visible by default, redact specific fields
12934
+ if (options.redactionStrategy.redactFields) {
12935
+ this.setFieldsToRedact(options.redactionStrategy.redactFields);
12936
+ }
12937
+ }
12938
+ }
12939
+ // Handle legacy redactFields (backward compatibility)
12940
+ if (options === null || options === void 0 ? void 0 : options.legacyRedactFields) {
12941
+ this.setFieldsToUnredact(options.legacyRedactFields);
12942
+ }
12943
+ // Handle legacy userFields
12923
12944
  if (options === null || options === void 0 ? void 0 : options.userFields) {
12924
- this.setFieldsToRedact(options.userFields);
12945
+ this.setFieldsToUnredact(options.userFields);
12925
12946
  }
12926
12947
  }
12927
12948
  /**
12928
- * Set specific fields to be redacted using CSS selectors
12929
- * These selectors are used to configure rrweb's built-in masking
12949
+ * Set specific fields to be redacted (for visibility-first mode)
12930
12950
  * @param fields Array of CSS selectors for fields to redact
12931
12951
  */
12932
12952
  setFieldsToRedact(fields) {
12933
- this.userSelectedFields.clear();
12934
- fields.forEach(field => this.userSelectedFields.add(field));
12935
- if (fields.length > 0) {
12936
- logDebug(`Redaction: Active for ${fields.length} field(s):`, fields);
12937
- // Debug: Check if elements exist
12938
- fields.forEach(selector => {
12939
- const elements = document.querySelectorAll(selector);
12940
- logDebug(`Redaction: Found ${elements.length} element(s) for selector '${selector}'`);
12941
- elements.forEach((el, index) => {
12942
- logDebug(`Redaction: Element ${index} for '${selector}':`, el);
12943
- });
12944
- });
12953
+ this.redactedFields.clear();
12954
+ // Always include password fields in redacted list
12955
+ const passwordFields = [
12956
+ 'input[type="password"]',
12957
+ 'input[type="password" i]',
12958
+ '[type="password"]',
12959
+ '[type="password" i]'
12960
+ ];
12961
+ // Add password fields and user-specified fields
12962
+ [...passwordFields, ...fields].forEach(field => {
12963
+ this.redactedFields.add(field);
12964
+ });
12965
+ if (this.redactedFields.size > 0) {
12966
+ logDebug(`Redaction: Active for ${this.redactedFields.size} field(s):`, Array.from(this.redactedFields));
12945
12967
  }
12946
12968
  else {
12947
- logDebug('Redaction: Disabled - no fields selected');
12948
- }
12949
- }
12950
- /**
12951
- * Check if redaction is currently active (has fields selected)
12952
- */
12953
- isActive() {
12954
- return this.userSelectedFields.size > 0;
12955
- }
12956
- /**
12957
- * Get the currently selected fields for redaction
12958
- */
12959
- getSelectedFields() {
12960
- return Array.from(this.userSelectedFields);
12961
- }
12962
- /**
12963
- * Process an event and redact sensitive data if needed
12964
- * NOTE: This method is no longer used - events are handled directly by rrweb
12965
- * Kept for backward compatibility but not called in the current implementation
12966
- */
12967
- processEvent(event) {
12968
- // Only process if we have fields selected for redaction
12969
- if (this.userSelectedFields.size === 0) {
12970
- return event;
12971
- }
12972
- // Clone the event to avoid modifying the original
12973
- const processedEvent = JSON.parse(JSON.stringify(event));
12974
- // Handle different event types
12975
- if (processedEvent.type === 3) { // IncrementalSnapshot
12976
- if (processedEvent.data.source === 5) { // Input event
12977
- const shouldRedact = this.isFieldSelected(processedEvent.data);
12978
- if (shouldRedact) {
12979
- logDebug('Redaction: Processing input event for redaction');
12980
- this.redactInputEvent(processedEvent.data);
12981
- }
12982
- }
12983
- // Also check for other sources that might contain text changes
12984
- else if (processedEvent.data.source === 0) { // DOM mutations
12985
- this.redactDOMEvent(processedEvent.data);
12986
- }
12987
- // Handle other sources that might contain text
12988
- else if (processedEvent.data.source === 2) { // Mouse/Touch interaction
12989
- this.redactMouseEvent(processedEvent.data);
12990
- }
12991
- }
12992
- else if (processedEvent.type === 2) { // FullSnapshot
12993
- this.redactFullSnapshot(processedEvent.data);
12969
+ logDebug('Redaction: No fields to redact');
12994
12970
  }
12995
- return processedEvent;
12971
+ this.applyRedactionClasses();
12996
12972
  }
12997
12973
  /**
12998
- * Redact sensitive data in input events
12974
+ * Set specific fields to be unredacted (everything else stays redacted by rrweb)
12975
+ * @param fields Array of CSS selectors for fields to unredact
12999
12976
  */
13000
- redactInputEvent(inputData) {
13001
- // Check if this input event is from a field we want to redact
13002
- if (!this.isFieldSelected(inputData)) {
13003
- return;
13004
- }
13005
- logDebug('Redaction: Redacting input event with text:', inputData.text);
13006
- // Redact all text-related properties that could contain input data
13007
- const textProperties = ['text', 'value', 'content', 'data', 'input', 'textContent'];
13008
- textProperties.forEach(prop => {
13009
- if (inputData[prop] !== undefined && typeof inputData[prop] === 'string') {
13010
- inputData[prop] = this.redactedText;
13011
- logDebug(`Redaction: Redacted property '${prop}'`);
13012
- }
13013
- });
13014
- // Also check for any other string properties that might contain input data
13015
- Object.keys(inputData).forEach(key => {
13016
- if (typeof inputData[key] === 'string' && inputData[key].length > 0) {
13017
- inputData[key] = this.redactedText;
13018
- logDebug(`Redaction: Redacted additional property '${key}'`);
13019
- }
12977
+ setFieldsToUnredact(fields) {
12978
+ this.unredactedFields.clear();
12979
+ // Filter out password fields (they cannot be unredacted)
12980
+ const validFields = fields.filter(field => {
12981
+ const isPasswordField = this.isPasswordSelector(field);
12982
+ if (isPasswordField) {
12983
+ logWarn(`Cannot unredact password field: ${field} - Password fields are always protected`);
12984
+ return false;
12985
+ }
12986
+ return true;
13020
12987
  });
13021
- // Handle nested objects that might contain text data
13022
- if (inputData.attributes && typeof inputData.attributes === 'object') {
13023
- if (inputData.attributes.value && typeof inputData.attributes.value === 'string') {
13024
- inputData.attributes.value = this.redactedText;
13025
- logDebug('Redaction: Redacted nested value attribute');
13026
- }
13027
- }
13028
- logDebug('Redaction: Input event redaction complete');
13029
- }
13030
- /**
13031
- * Redact sensitive data in DOM mutation events
13032
- */
13033
- redactDOMEvent(domData) {
13034
- // Check for text changes in DOM mutations
13035
- if (domData.texts && Array.isArray(domData.texts)) {
13036
- domData.texts.forEach((textChange) => {
13037
- if (textChange.text && typeof textChange.text === 'string' &&
13038
- this.shouldRedactDOMChange(textChange)) {
13039
- textChange.text = this.redactedText;
13040
- }
13041
- });
13042
- }
13043
- // Also check for attribute changes that might contain input data
13044
- if (domData.attributes && Array.isArray(domData.attributes)) {
13045
- domData.attributes.forEach((attrChange) => {
13046
- if (attrChange.attributes && attrChange.attributes.value &&
13047
- typeof attrChange.attributes.value === 'string' &&
13048
- this.shouldRedactDOMChange(attrChange)) {
13049
- attrChange.attributes.value = this.redactedText;
13050
- }
13051
- });
12988
+ validFields.forEach(field => this.unredactedFields.add(field));
12989
+ if (validFields.length > 0) {
12990
+ logDebug(`Unredaction: Active for ${validFields.length} field(s):`, validFields);
13052
12991
  }
13053
- // Check for any other properties that might contain text data
13054
- if (domData.adds && Array.isArray(domData.adds)) {
13055
- domData.adds.forEach((add) => {
13056
- if (add.node && add.node.textContent && typeof add.node.textContent === 'string' &&
13057
- this.shouldRedactDOMChange(add)) {
13058
- add.node.textContent = this.redactedText;
13059
- }
13060
- });
12992
+ else {
12993
+ logDebug('Unredaction: No valid fields to unredact');
13061
12994
  }
12995
+ this.applyUnredactionClasses();
13062
12996
  }
13063
12997
  /**
13064
- * Check if a DOM change should be redacted based on its ID
12998
+ * Remove specific fields from unredaction (they become redacted again)
12999
+ * @param fields Array of CSS selectors for fields to redact
13065
13000
  */
13066
- shouldRedactDOMChange(changeData) {
13067
- if (!isBrowser$2)
13068
- return false;
13069
- try {
13070
- // Check if this change has an ID that we can use to find the element
13071
- const elementId = changeData.id;
13072
- if (elementId !== undefined) {
13073
- // Try to find the element by data-rrweb-id attribute
13074
- let element = document.querySelector(`[data-rrweb-id="${elementId}"]`);
13075
- if (element) {
13076
- return this.shouldRedactElement(element);
13077
- }
13078
- }
13079
- // Also check for nodeId which is another way rrweb identifies elements
13080
- const nodeId = changeData.nodeId;
13081
- if (nodeId !== undefined) {
13082
- const element = document.querySelector(`[data-rrweb-id="${nodeId}"]`);
13083
- if (element) {
13084
- return this.shouldRedactElement(element);
13085
- }
13086
- }
13087
- return false;
13001
+ redactFields(fields) {
13002
+ fields.forEach(field => {
13003
+ this.unredactedFields.delete(field);
13004
+ });
13005
+ if (this.unredactedFields.size > 0) {
13006
+ logDebug(`Unredaction: Removed ${fields.length} field(s), ${this.unredactedFields.size} remaining:`, Array.from(this.unredactedFields));
13088
13007
  }
13089
- catch (e) {
13090
- logWarn('Error checking if DOM change should be redacted:', e);
13091
- return false;
13008
+ else {
13009
+ logDebug('Unredaction: All fields redacted');
13092
13010
  }
13011
+ this.applyUnredactionClasses();
13093
13012
  }
13094
13013
  /**
13095
- * Redact sensitive data in mouse/touch interaction events
13014
+ * Clear all unredacted fields (everything becomes redacted again)
13096
13015
  */
13097
- redactMouseEvent(mouseData) {
13098
- // Mouse events typically don't contain text data, but check for any text properties
13099
- if (mouseData.text && typeof mouseData.text === 'string' &&
13100
- this.isFieldSelected(mouseData)) {
13101
- mouseData.text = this.redactedText;
13102
- }
13016
+ clearUnredactedFields() {
13017
+ this.unredactedFields.clear();
13018
+ logDebug('Unredaction: All fields cleared, everything redacted');
13019
+ this.removeUnredactionClasses();
13103
13020
  }
13104
13021
  /**
13105
- * Redact sensitive data in full snapshot events
13022
+ * Check if any fields are currently unredacted
13106
13023
  */
13107
- redactFullSnapshot(snapshotData) {
13108
- if (snapshotData.node && snapshotData.node.type === 2) { // Element node
13109
- this.redactNode(snapshotData.node);
13110
- }
13024
+ hasUnredactedFields() {
13025
+ return this.unredactedFields.size > 0;
13111
13026
  }
13112
13027
  /**
13113
- * Recursively redact sensitive data in DOM nodes
13028
+ * Get the current redaction mode
13114
13029
  */
13115
- redactNode(node) {
13116
- if (!node)
13117
- return;
13118
- // Check if this node should be redacted
13119
- if (node.type === 2 && node.tagName &&
13120
- (node.tagName.toLowerCase() === 'input' || node.tagName.toLowerCase() === 'textarea')) {
13121
- // Check if this input/textarea should be redacted
13122
- if (this.shouldRedactNode(node)) {
13123
- // Redact value attribute
13124
- if (node.attributes && node.attributes.value) {
13125
- node.attributes.value = this.redactedText;
13126
- }
13127
- // Redact text content
13128
- if (node.textContent) {
13129
- node.textContent = this.redactedText;
13130
- }
13131
- }
13132
- }
13133
- // Recursively process child nodes
13134
- if (node.childNodes && Array.isArray(node.childNodes)) {
13135
- node.childNodes.forEach((childNode) => {
13136
- this.redactNode(childNode);
13137
- });
13138
- }
13030
+ getRedactionMode() {
13031
+ return this.redactionMode;
13139
13032
  }
13140
13033
  /**
13141
- * Check if a node should be redacted based on its attributes
13034
+ * Get the currently unredacted fields
13142
13035
  */
13143
- shouldRedactNode(node) {
13144
- if (!node.attributes)
13145
- return false;
13146
- // Check if any of our selectors would match this node
13147
- for (const selector of this.userSelectedFields) {
13148
- if (this.selectorMatchesNode(selector, node)) {
13149
- return true;
13150
- }
13151
- }
13152
- return false;
13036
+ getUnredactedFields() {
13037
+ return Array.from(this.unredactedFields);
13153
13038
  }
13154
13039
  /**
13155
- * Check if a CSS selector would match a node based on its attributes
13156
- */
13157
- selectorMatchesNode(selector, node) {
13158
- if (!node.attributes)
13159
- return false;
13160
- // Create a temporary element to test the selector
13161
- try {
13162
- const tempElement = document.createElement(node.tagName || 'div');
13163
- // Copy attributes from the node to the temp element
13164
- if (node.attributes) {
13165
- Object.keys(node.attributes).forEach(key => {
13166
- tempElement.setAttribute(key, node.attributes[key]);
13167
- });
13168
- }
13169
- // Test if the selector matches this element
13170
- return tempElement.matches(selector);
13171
- }
13172
- catch (e) {
13173
- // If matches() is not supported or fails, fall back to basic attribute checking
13174
- return this.basicSelectorMatch(selector, node);
13175
- }
13176
- }
13177
- /**
13178
- * Basic selector matching for environments where matches() is not available
13040
+ * Get CSS selectors for rrweb masking configuration
13041
+ * Returns null if no fields are unredacted (everything stays redacted)
13179
13042
  */
13180
- basicSelectorMatch(selector, node) {
13181
- if (!node.attributes)
13182
- return false;
13183
- // Handle simple selectors like 'input[type="password"]'
13184
- if (selector.includes('input[type=')) {
13185
- const typeMatch = selector.match(/input\[type="([^"]+)"\]/);
13186
- if (typeMatch && node.tagName === 'input' && node.attributes.type === typeMatch[1]) {
13187
- return true;
13043
+ getMaskTextSelector() {
13044
+ if (this.redactionMode === 'privacy-first') {
13045
+ // Privacy-first: mask everything except unredacted fields
13046
+ if (this.unredactedFields.size === 0) {
13047
+ return null; // Everything stays redacted
13188
13048
  }
13049
+ return Array.from(this.unredactedFields).join(',');
13189
13050
  }
13190
- // Handle ID selectors like '#email'
13191
- if (selector.startsWith('#')) {
13192
- const id = selector.substring(1);
13193
- return node.attributes.id === id;
13194
- }
13195
- // Handle class selectors like '.sensitive-field'
13196
- if (selector.startsWith('.')) {
13197
- const className = selector.substring(1);
13198
- return node.attributes.class && node.attributes.class.includes(className);
13199
- }
13200
- // Handle tag selectors like 'input'
13201
- if (!selector.includes('[') && !selector.includes('.')) {
13202
- return node.tagName && node.tagName.toLowerCase() === selector.toLowerCase();
13203
- }
13204
- return false;
13205
- }
13206
- /**
13207
- * Check if an event is from a field that should be redacted
13208
- */
13209
- isFieldSelected(eventData) {
13210
- if (!isBrowser$2)
13211
- return false;
13212
- try {
13213
- // For input events (source 5), we need to determine if this is a sensitive field
13214
- if (eventData.source === 5) { // Input event
13215
- const elementId = eventData.id;
13216
- if (elementId !== undefined) {
13217
- // Try to find the element by data-rrweb-id attribute
13218
- let element = document.querySelector(`[data-rrweb-id="${elementId}"]`);
13219
- if (element) {
13220
- return this.shouldRedactElement(element);
13221
- }
13222
- // Fallback: Try to find by nodeId if available
13223
- if (eventData.nodeId !== undefined) {
13224
- element = document.querySelector(`[data-rrweb-id="${eventData.nodeId}"]`);
13225
- if (element) {
13226
- return this.shouldRedactElement(element);
13227
- }
13228
- }
13229
- // More aggressive approach: Check all elements that match our selectors
13230
- // and see if any of them are currently focused or have the same ID
13231
- for (const selector of this.userSelectedFields) {
13232
- const matchingElements = document.querySelectorAll(selector);
13233
- if (matchingElements.length > 0) {
13234
- // Check if any of these elements are currently focused
13235
- for (const el of matchingElements) {
13236
- if (el === document.activeElement) {
13237
- logDebug('Redaction: Found focused element matching selector:', selector);
13238
- return true;
13239
- }
13240
- }
13241
- }
13242
- }
13243
- // If we still can't find it, try a more direct approach
13244
- // Look for any input element that might be the active one
13245
- const activeElement = document.activeElement;
13246
- if (activeElement && this.shouldRedactElement(activeElement)) {
13247
- logDebug('Redaction: Active element should be redacted');
13248
- return true;
13249
- }
13250
- return false;
13251
- }
13252
- }
13253
- // For other event types, try to find the element
13254
- const elementId = eventData.id;
13255
- if (elementId !== undefined) {
13256
- // First try to find by data-rrweb-id attribute
13257
- let element = document.querySelector(`[data-rrweb-id="${elementId}"]`);
13258
- if (element) {
13259
- return this.shouldRedactElement(element);
13260
- }
13261
- }
13262
- // Also check for nodeId which is another way rrweb identifies elements
13263
- const nodeId = eventData.nodeId;
13264
- if (nodeId !== undefined) {
13265
- const element = document.querySelector(`[data-rrweb-id="${nodeId}"]`);
13266
- if (element) {
13267
- return this.shouldRedactElement(element);
13268
- }
13269
- }
13270
- // For DOM mutations, check if the target element should be redacted
13271
- if (eventData.target && eventData.target.id) {
13272
- const element = document.querySelector(`[data-rrweb-id="${eventData.target.id}"]`);
13273
- if (element) {
13274
- return this.shouldRedactElement(element);
13275
- }
13051
+ else {
13052
+ // Visibility-first: mask only redacted fields
13053
+ if (this.redactedFields.size === 0) {
13054
+ return null; // Nothing to redact
13276
13055
  }
13277
- return false;
13278
- }
13279
- catch (e) {
13280
- logWarn('Error checking if field should be redacted:', e);
13281
- return false;
13282
- }
13283
- }
13284
- /**
13285
- * Get CSS selectors for rrweb masking configuration
13286
- * Used to configure rrweb's maskTextSelector option
13287
- */
13288
- getMaskTextSelector() {
13289
- if (this.userSelectedFields.size === 0) {
13290
- return null;
13056
+ return Array.from(this.redactedFields).join(',');
13291
13057
  }
13292
- return Array.from(this.userSelectedFields).join(',');
13293
13058
  }
13294
13059
  /**
13295
- * Check if an element should be redacted (for rrweb maskTextFn/maskInputFn)
13060
+ * Apply redaction classes to DOM elements (for visibility-first mode)
13061
+ * Adds 'rr-mask' class to elements that should be redacted
13296
13062
  */
13297
- shouldRedactElement(element) {
13298
- if (this.userSelectedFields.size === 0) {
13299
- return false;
13063
+ applyRedactionClasses() {
13064
+ if (this.redactedFields.size === 0) {
13065
+ return;
13300
13066
  }
13301
- // Check if any selector matches this element
13302
- for (const selector of this.userSelectedFields) {
13067
+ // Add 'rr-mask' class to redacted elements
13068
+ this.redactedFields.forEach(selector => {
13303
13069
  try {
13304
- if (element.matches(selector)) {
13305
- return true;
13306
- }
13070
+ const elements = document.querySelectorAll(selector);
13071
+ elements.forEach(element => {
13072
+ element.classList.add('rr-mask');
13073
+ });
13074
+ logDebug(`Added rr-mask class to ${elements.length} element(s) for selector: ${selector}`);
13307
13075
  }
13308
13076
  catch (e) {
13309
- // Invalid selector, skip
13310
13077
  logWarn(`Invalid selector: ${selector}`);
13311
13078
  }
13312
- }
13313
- return false;
13079
+ });
13314
13080
  }
13315
13081
  /**
13316
- * Apply rrweb masking classes to DOM elements
13317
- * Adds 'rr-mask' class to elements that should be redacted
13318
- * This enables rrweb's built-in masking functionality
13082
+ * Apply unredaction classes to DOM elements
13083
+ * Removes 'rr-mask' class from elements that should be unredacted
13319
13084
  */
13320
- applyRedactionClasses() {
13321
- if (this.userSelectedFields.size === 0) {
13085
+ applyUnredactionClasses() {
13086
+ if (this.unredactedFields.size === 0) {
13322
13087
  return;
13323
13088
  }
13324
- // Remove existing redaction classes
13325
- document.querySelectorAll('.rr-mask').forEach(element => {
13326
- element.classList.remove('rr-mask');
13327
- });
13328
- // Add redaction classes to matching elements
13329
- this.userSelectedFields.forEach(selector => {
13089
+ // Remove 'rr-mask' class from unredacted elements
13090
+ this.unredactedFields.forEach(selector => {
13330
13091
  try {
13331
13092
  const elements = document.querySelectorAll(selector);
13332
13093
  elements.forEach(element => {
13333
- element.classList.add('rr-mask');
13094
+ element.classList.remove('rr-mask');
13334
13095
  });
13335
- logDebug(`Applied rr-mask class to ${elements.length} element(s) for selector: ${selector}`);
13096
+ logDebug(`Removed rr-mask class from ${elements.length} element(s) for selector: ${selector}`);
13336
13097
  }
13337
13098
  catch (e) {
13338
13099
  logWarn(`Invalid selector: ${selector}`);
@@ -13340,7 +13101,26 @@ class RedactionManager {
13340
13101
  });
13341
13102
  }
13342
13103
  /**
13343
- * Get the original value of a redacted element (for debugging)
13104
+ * Remove all unredaction classes from DOM elements
13105
+ */
13106
+ removeUnredactionClasses() {
13107
+ // Note: This doesn't add 'rr-mask' classes back - rrweb handles that automatically
13108
+ logDebug('Unredaction classes removed');
13109
+ }
13110
+ /**
13111
+ * Check if a selector represents a password field
13112
+ */
13113
+ isPasswordSelector(selector) {
13114
+ const passwordPatterns = [
13115
+ 'input[type="password"]',
13116
+ 'input[type="password" i]',
13117
+ '[type="password"]',
13118
+ '[type="password" i]'
13119
+ ];
13120
+ return passwordPatterns.some(pattern => selector.toLowerCase().includes(pattern.toLowerCase().replace(/[\[\]]/g, '')));
13121
+ }
13122
+ /**
13123
+ * Get the original value of an element (for debugging)
13344
13124
  */
13345
13125
  getOriginalValue(element) {
13346
13126
  if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
@@ -13349,10 +13129,51 @@ class RedactionManager {
13349
13129
  return undefined;
13350
13130
  }
13351
13131
  /**
13352
- * Check if an element is currently being redacted
13132
+ * Check if an element is currently unredacted
13353
13133
  */
13354
- isElementRedacted(element) {
13355
- return this.shouldRedactElement(element);
13134
+ isElementUnredacted(element) {
13135
+ return this.shouldUnredactElement(element);
13136
+ }
13137
+ /**
13138
+ * Check if an element should be unredacted
13139
+ */
13140
+ shouldUnredactElement(element) {
13141
+ if (this.redactionMode === 'privacy-first') {
13142
+ // Privacy-first: check if element is in unredacted fields
13143
+ if (this.unredactedFields.size === 0) {
13144
+ return false; // Nothing unredacted
13145
+ }
13146
+ // Check if any selector matches this element
13147
+ for (const selector of this.unredactedFields) {
13148
+ try {
13149
+ if (element.matches(selector)) {
13150
+ return true;
13151
+ }
13152
+ }
13153
+ catch (e) {
13154
+ logWarn(`Invalid selector: ${selector}`);
13155
+ }
13156
+ }
13157
+ return false;
13158
+ }
13159
+ else {
13160
+ // Visibility-first: check if element is NOT in redacted fields
13161
+ if (this.redactedFields.size === 0) {
13162
+ return true; // Nothing redacted, everything visible
13163
+ }
13164
+ // Check if any selector matches this element
13165
+ for (const selector of this.redactedFields) {
13166
+ try {
13167
+ if (element.matches(selector)) {
13168
+ return false; // Element is redacted
13169
+ }
13170
+ }
13171
+ catch (e) {
13172
+ logWarn(`Invalid selector: ${selector}`);
13173
+ }
13174
+ }
13175
+ return true; // Element is not redacted
13176
+ }
13356
13177
  }
13357
13178
  }
13358
13179
  // Export a default instance
@@ -13921,11 +13742,22 @@ class HumanBehaviorTracker {
13921
13742
  });
13922
13743
  // Store canvas recording preference
13923
13744
  tracker.recordCanvas = (_a = options === null || options === void 0 ? void 0 : options.recordCanvas) !== null && _a !== void 0 ? _a : false;
13924
- // Set redacted fields if specified
13745
+ // Set unredacted fields if specified (legacy support)
13925
13746
  if (options === null || options === void 0 ? void 0 : options.redactFields) {
13926
- tracker.setRedactedFields(options.redactFields);
13927
- // ✅ Apply redaction classes to existing elements
13928
- tracker.redactionManager.applyRedactionClasses();
13747
+ tracker.setUnredactedFields(options.redactFields);
13748
+ }
13749
+ // Handle new redaction strategy
13750
+ if (options === null || options === void 0 ? void 0 : options.redactionStrategy) {
13751
+ if (options.redactionStrategy.mode === 'privacy-first') {
13752
+ if (options.redactionStrategy.unredactFields) {
13753
+ tracker.setUnredactedFields(options.redactionStrategy.unredactFields);
13754
+ }
13755
+ }
13756
+ else {
13757
+ if (options.redactionStrategy.redactFields) {
13758
+ tracker.setRedactedFields(options.redactionStrategy.redactFields);
13759
+ }
13760
+ }
13929
13761
  }
13930
13762
  // Setup automatic tracking if enabled
13931
13763
  if ((options === null || options === void 0 ? void 0 : options.enableAutomaticTracking) !== false) {
@@ -13972,7 +13804,10 @@ class HumanBehaviorTracker {
13972
13804
  ingestionUrl: ingestionUrl || defaultIngestionUrl
13973
13805
  });
13974
13806
  this.apiKey = apiKey;
13975
- this.redactionManager = new RedactionManager();
13807
+ this.redactionManager = new RedactionManager({
13808
+ redactionStrategy: options === null || options === void 0 ? void 0 : options.redactionStrategy,
13809
+ legacyRedactFields: options === null || options === void 0 ? void 0 : options.redactFields // For backward compatibility
13810
+ });
13976
13811
  // Initialize property manager
13977
13812
  this.propertyManager = new PropertyManager({
13978
13813
  enableAutomaticProperties: (options === null || options === void 0 ? void 0 : options.enableAutomaticProperties) !== false,
@@ -14550,6 +14385,31 @@ class HumanBehaviorTracker {
14550
14385
  if (!userResponse.ok) {
14551
14386
  throw new Error(`Failed to identify user: ${userResponse.statusText}`);
14552
14387
  }
14388
+ // Get IP info and GeoIP data
14389
+ try {
14390
+ const ipResponse = yield fetch(`${this.api['baseUrl']}/api/ingestion/ip-info`, {
14391
+ method: 'POST',
14392
+ headers: {
14393
+ 'Content-Type': 'application/json',
14394
+ 'Authorization': `Bearer ${this.apiKey}`
14395
+ },
14396
+ body: JSON.stringify({
14397
+ sessionId: this.sessionId,
14398
+ clientIP: null, // Let server detect from headers
14399
+ ipDetectionMethod: 'headers',
14400
+ timestamp: new Date().toISOString()
14401
+ })
14402
+ });
14403
+ if (ipResponse.ok) {
14404
+ logDebug('✅ IP info and GeoIP data retrieved successfully');
14405
+ }
14406
+ else {
14407
+ logDebug(`⚠️ IP info request failed: ${ipResponse.statusText}`);
14408
+ }
14409
+ }
14410
+ catch (error) {
14411
+ logDebug(`⚠️ IP info request error: ${error}`);
14412
+ }
14553
14413
  // Don't update endUserId - keep it as the original UUID
14554
14414
  return originalEndUserId || '';
14555
14415
  });
@@ -14589,7 +14449,7 @@ class HumanBehaviorTracker {
14589
14449
  // ✅ HUMANBEHAVIOR'S CUSTOM SETTINGS
14590
14450
  maskTextSelector: this.redactionManager.getMaskTextSelector() || undefined,
14591
14451
  maskTextFn: undefined,
14592
- maskAllInputs: true, // HumanBehavior default
14452
+ maskAllInputs: this.redactionManager.getRedactionMode() === 'privacy-first', // Configurable based on strategy
14593
14453
  maskInputOptions: { password: true }, // HumanBehavior default
14594
14454
  maskInputFn: undefined,
14595
14455
  slimDOMOptions: {},
@@ -14899,15 +14759,23 @@ class HumanBehaviorTracker {
14899
14759
  });
14900
14760
  }
14901
14761
  /**
14902
- * Set specific fields to be redacted during session recording
14903
- * Uses rrweb's built-in masking instead of custom redaction processing
14904
- * @param fields Array of CSS selectors for fields to redact (e.g., ['input[type="password"]', '#email-field'])
14762
+ * Set specific fields to be redacted (for visibility-first mode)
14763
+ * @param fields Array of CSS selectors for fields to redact
14905
14764
  */
14906
14765
  setRedactedFields(fields) {
14907
14766
  this.redactionManager.setFieldsToRedact(fields);
14908
- // ✅ APPLY RRWEB MASKING CLASSES - More reliable than custom processing
14909
- this.redactionManager.applyRedactionClasses();
14910
- // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures masking is applied
14767
+ // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures redaction is applied
14768
+ if (this.recordInstance) {
14769
+ this.restartWithNewRedaction();
14770
+ }
14771
+ }
14772
+ /**
14773
+ * Set specific fields to be unredacted (everything else stays redacted by rrweb)
14774
+ * @param fields Array of CSS selectors for fields to unredact (e.g., ['#username', '#comment'])
14775
+ */
14776
+ setUnredactedFields(fields) {
14777
+ this.redactionManager.setFieldsToUnredact(fields);
14778
+ // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures unredaction is applied
14911
14779
  if (this.recordInstance) {
14912
14780
  this.restartWithNewRedaction();
14913
14781
  }
@@ -14919,16 +14787,37 @@ class HumanBehaviorTracker {
14919
14787
  }
14920
14788
  }
14921
14789
  /**
14922
- * Check if redaction is currently active
14790
+ * Check if any fields are currently unredacted
14923
14791
  */
14924
- isRedactionActive() {
14925
- return this.redactionManager.isActive();
14792
+ hasUnredactedFields() {
14793
+ return this.redactionManager.hasUnredactedFields();
14926
14794
  }
14927
14795
  /**
14928
- * Get the currently selected fields for redaction
14796
+ * Get the currently unredacted fields
14929
14797
  */
14930
- getRedactedFields() {
14931
- return this.redactionManager.getSelectedFields();
14798
+ getUnredactedFields() {
14799
+ return this.redactionManager.getUnredactedFields();
14800
+ }
14801
+ /**
14802
+ * Remove specific fields from unredaction (they become redacted again)
14803
+ * @param fields Array of CSS selectors for fields to redact
14804
+ */
14805
+ redactFields(fields) {
14806
+ this.redactionManager.redactFields(fields);
14807
+ // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures redaction is updated
14808
+ if (this.recordInstance) {
14809
+ this.restartWithNewRedaction();
14810
+ }
14811
+ }
14812
+ /**
14813
+ * Clear all unredacted fields (everything becomes redacted again)
14814
+ */
14815
+ clearUnredactedFields() {
14816
+ this.redactionManager.clearUnredactedFields();
14817
+ // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures redaction is updated
14818
+ if (this.recordInstance) {
14819
+ this.restartWithNewRedaction();
14820
+ }
14932
14821
  }
14933
14822
  /**
14934
14823
  * Get the current session ID