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