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