humanbehavior-js 0.3.1 → 0.3.3
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/index.js +107 -113
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/index.js +13 -5
- package/dist/cjs/react/index.js.map +1 -1
- package/dist/esm/index.js +107 -113
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/index.js +13 -5
- package/dist/esm/react/index.js.map +1 -1
- package/dist/index.min.js +2 -2
- package/dist/index.min.js.map +1 -1
- package/dist/types/index.d.ts +29 -12
- package/dist/types/react/index.d.ts +1 -1
- package/package.json +5 -2
- package/rollup.config.js +3 -3
- package/src/react/index.tsx +19 -8
- package/src/redact.ts +58 -13
- package/src/tracker.ts +71 -122
package/dist/cjs/index.js
CHANGED
|
@@ -4486,7 +4486,8 @@ class HumanBehaviorAPI {
|
|
|
4486
4486
|
}
|
|
4487
4487
|
|
|
4488
4488
|
// Redaction functionality for sensitive input fields
|
|
4489
|
-
// This module provides methods to
|
|
4489
|
+
// This module provides methods to configure rrweb's built-in masking
|
|
4490
|
+
// Uses CSS selectors and classes for reliable redaction without event corruption
|
|
4490
4491
|
// Check if we're in a browser environment
|
|
4491
4492
|
const isBrowser$1 = typeof window !== 'undefined';
|
|
4492
4493
|
class RedactionManager {
|
|
@@ -4508,7 +4509,8 @@ class RedactionManager {
|
|
|
4508
4509
|
}
|
|
4509
4510
|
}
|
|
4510
4511
|
/**
|
|
4511
|
-
* Set specific fields to be redacted
|
|
4512
|
+
* Set specific fields to be redacted using CSS selectors
|
|
4513
|
+
* These selectors are used to configure rrweb's built-in masking
|
|
4512
4514
|
* @param fields Array of CSS selectors for fields to redact
|
|
4513
4515
|
*/
|
|
4514
4516
|
setFieldsToRedact(fields) {
|
|
@@ -4543,6 +4545,8 @@ class RedactionManager {
|
|
|
4543
4545
|
}
|
|
4544
4546
|
/**
|
|
4545
4547
|
* Process an event and redact sensitive data if needed
|
|
4548
|
+
* NOTE: This method is no longer used - events are handled directly by rrweb
|
|
4549
|
+
* Kept for backward compatibility but not called in the current implementation
|
|
4546
4550
|
*/
|
|
4547
4551
|
processEvent(event) {
|
|
4548
4552
|
// Only process if we have fields selected for redaction
|
|
@@ -4862,23 +4866,63 @@ class RedactionManager {
|
|
|
4862
4866
|
}
|
|
4863
4867
|
}
|
|
4864
4868
|
/**
|
|
4865
|
-
*
|
|
4869
|
+
* Get CSS selectors for rrweb masking configuration
|
|
4870
|
+
* Used to configure rrweb's maskTextSelector option
|
|
4871
|
+
*/
|
|
4872
|
+
getMaskTextSelector() {
|
|
4873
|
+
if (this.userSelectedFields.size === 0) {
|
|
4874
|
+
return null;
|
|
4875
|
+
}
|
|
4876
|
+
return Array.from(this.userSelectedFields).join(',');
|
|
4877
|
+
}
|
|
4878
|
+
/**
|
|
4879
|
+
* Check if an element should be redacted (for rrweb maskTextFn/maskInputFn)
|
|
4866
4880
|
*/
|
|
4867
4881
|
shouldRedactElement(element) {
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
if (element.matches(excludeSelector) || element.closest(excludeSelector)) {
|
|
4871
|
-
return false;
|
|
4872
|
-
}
|
|
4882
|
+
if (this.userSelectedFields.size === 0) {
|
|
4883
|
+
return false;
|
|
4873
4884
|
}
|
|
4874
|
-
// Check if
|
|
4885
|
+
// Check if any selector matches this element
|
|
4875
4886
|
for (const selector of this.userSelectedFields) {
|
|
4876
|
-
|
|
4877
|
-
|
|
4887
|
+
try {
|
|
4888
|
+
if (element.matches(selector)) {
|
|
4889
|
+
return true;
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4892
|
+
catch (e) {
|
|
4893
|
+
// Invalid selector, skip
|
|
4894
|
+
logWarn(`Invalid selector: ${selector}`);
|
|
4878
4895
|
}
|
|
4879
4896
|
}
|
|
4880
4897
|
return false;
|
|
4881
4898
|
}
|
|
4899
|
+
/**
|
|
4900
|
+
* Apply rrweb masking classes to DOM elements
|
|
4901
|
+
* Adds 'rr-mask' class to elements that should be redacted
|
|
4902
|
+
* This enables rrweb's built-in masking functionality
|
|
4903
|
+
*/
|
|
4904
|
+
applyRedactionClasses() {
|
|
4905
|
+
if (this.userSelectedFields.size === 0) {
|
|
4906
|
+
return;
|
|
4907
|
+
}
|
|
4908
|
+
// Remove existing redaction classes
|
|
4909
|
+
document.querySelectorAll('.rr-mask').forEach(element => {
|
|
4910
|
+
element.classList.remove('rr-mask');
|
|
4911
|
+
});
|
|
4912
|
+
// Add redaction classes to matching elements
|
|
4913
|
+
this.userSelectedFields.forEach(selector => {
|
|
4914
|
+
try {
|
|
4915
|
+
const elements = document.querySelectorAll(selector);
|
|
4916
|
+
elements.forEach(element => {
|
|
4917
|
+
element.classList.add('rr-mask');
|
|
4918
|
+
});
|
|
4919
|
+
logDebug(`Applied rr-mask class to ${elements.length} element(s) for selector: ${selector}`);
|
|
4920
|
+
}
|
|
4921
|
+
catch (e) {
|
|
4922
|
+
logWarn(`Invalid selector: ${selector}`);
|
|
4923
|
+
}
|
|
4924
|
+
});
|
|
4925
|
+
}
|
|
4882
4926
|
/**
|
|
4883
4927
|
* Get the original value of a redacted element (for debugging)
|
|
4884
4928
|
*/
|
|
@@ -4920,6 +4964,8 @@ class HumanBehaviorTracker {
|
|
|
4920
4964
|
// Set redacted fields if specified
|
|
4921
4965
|
if (options === null || options === void 0 ? void 0 : options.redactFields) {
|
|
4922
4966
|
tracker.setRedactedFields(options.redactFields);
|
|
4967
|
+
// ✅ Apply redaction classes to existing elements
|
|
4968
|
+
tracker.redactionManager.applyRedactionClasses();
|
|
4923
4969
|
}
|
|
4924
4970
|
// Setup automatic tracking if enabled
|
|
4925
4971
|
if ((options === null || options === void 0 ? void 0 : options.enableAutomaticTracking) !== false) {
|
|
@@ -4941,7 +4987,6 @@ class HumanBehaviorTracker {
|
|
|
4941
4987
|
}
|
|
4942
4988
|
constructor(apiKey, ingestionUrl) {
|
|
4943
4989
|
this.eventIngestionQueue = [];
|
|
4944
|
-
this.queueSizeBytes = 0;
|
|
4945
4990
|
this.userProperties = {};
|
|
4946
4991
|
this.isProcessing = false;
|
|
4947
4992
|
this.flushInterval = null;
|
|
@@ -4961,7 +5006,6 @@ class HumanBehaviorTracker {
|
|
|
4961
5006
|
this.navigationListeners = [];
|
|
4962
5007
|
this._connectionBlocked = false;
|
|
4963
5008
|
this.recordInstance = null;
|
|
4964
|
-
this.frequencyUpdateInterval = null;
|
|
4965
5009
|
this.sessionStartTime = Date.now();
|
|
4966
5010
|
if (!apiKey) {
|
|
4967
5011
|
throw new Error('Human Behavior API Key is required');
|
|
@@ -5136,9 +5180,6 @@ class HumanBehaviorTracker {
|
|
|
5136
5180
|
}
|
|
5137
5181
|
});
|
|
5138
5182
|
}
|
|
5139
|
-
/**
|
|
5140
|
-
* Track a page view event (PostHog-style)
|
|
5141
|
-
*/
|
|
5142
5183
|
trackPageView(url) {
|
|
5143
5184
|
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5144
5185
|
if (!this.initialized)
|
|
@@ -5167,9 +5208,6 @@ class HumanBehaviorTracker {
|
|
|
5167
5208
|
}
|
|
5168
5209
|
});
|
|
5169
5210
|
}
|
|
5170
|
-
/**
|
|
5171
|
-
* Track a custom event (PostHog-style)
|
|
5172
|
-
*/
|
|
5173
5211
|
customEvent(eventName, properties) {
|
|
5174
5212
|
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5175
5213
|
var _a, _b, _c, _d, _e;
|
|
@@ -5490,7 +5528,7 @@ class HumanBehaviorTracker {
|
|
|
5490
5528
|
* Add user identification information to the tracker
|
|
5491
5529
|
* If userId is not provided, will use userProperties.email as the userId (if present)
|
|
5492
5530
|
*/
|
|
5493
|
-
|
|
5531
|
+
identifyUser(_a) {
|
|
5494
5532
|
return __awaiter$1(this, arguments, void 0, function* ({ userId, userProperties }) {
|
|
5495
5533
|
yield this.ensureInitialized();
|
|
5496
5534
|
// Keep the original endUserId (UUID) - don't change it
|
|
@@ -5500,7 +5538,6 @@ class HumanBehaviorTracker {
|
|
|
5500
5538
|
// Send user data with the original endUserId
|
|
5501
5539
|
yield this.api.sendUserData(originalEndUserId, userProperties, this.sessionId);
|
|
5502
5540
|
// Don't update endUserId - keep it as the original UUID
|
|
5503
|
-
// The posthogName will be updated on the server side with the email
|
|
5504
5541
|
return originalEndUserId || '';
|
|
5505
5542
|
});
|
|
5506
5543
|
}
|
|
@@ -5521,50 +5558,11 @@ class HumanBehaviorTracker {
|
|
|
5521
5558
|
}, this.FLUSH_INTERVAL_MS);
|
|
5522
5559
|
// Enable console tracking
|
|
5523
5560
|
this.enableConsoleTracking();
|
|
5524
|
-
//
|
|
5525
|
-
|
|
5526
|
-
let snapshotInterval = 5000; // Start with 5 seconds
|
|
5527
|
-
let eventThreshold = 100; // Start with 100 events
|
|
5528
|
-
// Function to update snapshot frequency based on session duration
|
|
5529
|
-
const updateSnapshotFrequency = () => {
|
|
5530
|
-
const sessionDuration = Date.now() - sessionStartTime;
|
|
5531
|
-
const thirtyMinutes = 30 * 60 * 1000;
|
|
5532
|
-
const twoHours = 2 * 60 * 60 * 1000;
|
|
5533
|
-
if (sessionDuration > twoHours) {
|
|
5534
|
-
// After 2 hours, very infrequent snapshots
|
|
5535
|
-
snapshotInterval = 30000; // 30 seconds
|
|
5536
|
-
eventThreshold = 500; // 500 events
|
|
5537
|
-
logDebug('Reduced snapshot frequency: 30s/500 events (2+ hours)');
|
|
5538
|
-
}
|
|
5539
|
-
else if (sessionDuration > thirtyMinutes) {
|
|
5540
|
-
// After 30 minutes, moderate frequency
|
|
5541
|
-
snapshotInterval = 15000; // 15 seconds
|
|
5542
|
-
eventThreshold = 300; // 300 events
|
|
5543
|
-
logDebug('Reduced snapshot frequency: 15s/300 events (30+ minutes)');
|
|
5544
|
-
}
|
|
5545
|
-
// First 30 minutes: 5s/100 events (default)
|
|
5546
|
-
};
|
|
5547
|
-
// Update frequency every 5 minutes
|
|
5548
|
-
const frequencyUpdateInterval = setInterval(updateSnapshotFrequency, 5 * 60 * 1000);
|
|
5549
|
-
// Start recording with adaptive redaction enabled
|
|
5561
|
+
// ✅ SIMPLIFIED RECORDING - Use rrweb's proven defaults
|
|
5562
|
+
// No complex adaptive logic - rrweb's defaults work well for most use cases
|
|
5550
5563
|
const recordInstance = record({
|
|
5551
5564
|
emit: (event) => {
|
|
5552
|
-
//
|
|
5553
|
-
if (event.type === 2) { // FullSnapshot event
|
|
5554
|
-
if (!event.data || !event.data.node) {
|
|
5555
|
-
logWarn('rrweb generated malformed FullSnapshot event:', {
|
|
5556
|
-
hasData: !!event.data,
|
|
5557
|
-
hasNode: !!(event.data && event.data.node),
|
|
5558
|
-
dataType: typeof event.data,
|
|
5559
|
-
eventType: event.type,
|
|
5560
|
-
timestamp: event.timestamp
|
|
5561
|
-
});
|
|
5562
|
-
// Don't skip - let the addEvent method handle it
|
|
5563
|
-
}
|
|
5564
|
-
else {
|
|
5565
|
-
logDebug('Valid FullSnapshot event received from rrweb');
|
|
5566
|
-
}
|
|
5567
|
-
}
|
|
5565
|
+
// ✅ DIRECT EVENT HANDLING - Let rrweb handle events natively
|
|
5568
5566
|
this.addEvent(event);
|
|
5569
5567
|
},
|
|
5570
5568
|
inlineStylesheet: true,
|
|
@@ -5573,14 +5571,15 @@ class HumanBehaviorTracker {
|
|
|
5573
5571
|
inlineImages: true,
|
|
5574
5572
|
blockClass: 'rr-block',
|
|
5575
5573
|
ignoreClass: 'rr-ignore',
|
|
5576
|
-
maskTextClass: 'rr-
|
|
5577
|
-
//
|
|
5578
|
-
|
|
5579
|
-
|
|
5574
|
+
maskTextClass: 'rr-mask',
|
|
5575
|
+
// ✅ RRWEB BUILT-IN MASKING - More reliable than custom redaction
|
|
5576
|
+
maskAllInputs: false, // Let users control this via selectors
|
|
5577
|
+
maskTextSelector: this.redactionManager.getMaskTextSelector() || undefined,
|
|
5578
|
+
// ✅ SELECTOR-BASED REDACTION - Users control via CSS selectors
|
|
5579
|
+
// No custom masking functions needed - rrweb handles this natively
|
|
5580
5580
|
});
|
|
5581
|
-
// Store the record instance
|
|
5581
|
+
// Store the record instance for cleanup
|
|
5582
5582
|
this.recordInstance = recordInstance;
|
|
5583
|
-
this.frequencyUpdateInterval = frequencyUpdateInterval;
|
|
5584
5583
|
});
|
|
5585
5584
|
}
|
|
5586
5585
|
stop() {
|
|
@@ -5592,11 +5591,6 @@ class HumanBehaviorTracker {
|
|
|
5592
5591
|
clearInterval(this.flushInterval);
|
|
5593
5592
|
this.flushInterval = null;
|
|
5594
5593
|
}
|
|
5595
|
-
// Cleanup adaptive snapshot intervals
|
|
5596
|
-
if (this.frequencyUpdateInterval) {
|
|
5597
|
-
clearInterval(this.frequencyUpdateInterval);
|
|
5598
|
-
this.frequencyUpdateInterval = null;
|
|
5599
|
-
}
|
|
5600
5594
|
// Stop rrweb recording
|
|
5601
5595
|
if (this.recordInstance) {
|
|
5602
5596
|
this.recordInstance();
|
|
@@ -5608,28 +5602,29 @@ class HumanBehaviorTracker {
|
|
|
5608
5602
|
this.cleanupNavigationTracking();
|
|
5609
5603
|
});
|
|
5610
5604
|
}
|
|
5605
|
+
/**
|
|
5606
|
+
* Add an event to the ingestion queue
|
|
5607
|
+
* Events are sent directly without processing to avoid corruption
|
|
5608
|
+
*/
|
|
5611
5609
|
addEvent(event) {
|
|
5612
5610
|
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5611
|
+
var _a, _b;
|
|
5613
5612
|
yield this.ensureInitialized();
|
|
5614
|
-
//
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
eventType: event.type
|
|
5622
|
-
});
|
|
5623
|
-
return; // Skip malformed FullSnapshot events
|
|
5624
|
-
}
|
|
5613
|
+
// ✅ DIRECT EVENT HANDLING - No custom processing to avoid corruption
|
|
5614
|
+
// Events flow directly from rrweb to ingestion server
|
|
5615
|
+
// ✅ LOG FULLSNAPSHOT STATUS FOR DEBUGGING
|
|
5616
|
+
if (event.type === 2) { // FullSnapshot
|
|
5617
|
+
const hasData = !!event.data;
|
|
5618
|
+
const hasNode = !!(event.data && event.data.node);
|
|
5619
|
+
logDebug(`[FIXED] FullSnapshot event: hasData=${hasData}, hasNode=${hasNode}, dataType=${(_b = (_a = event.data) === null || _a === void 0 ? void 0 : _a.node) === null || _b === void 0 ? void 0 : _b.type}`);
|
|
5625
5620
|
}
|
|
5626
|
-
//
|
|
5627
|
-
const processedEvent = this.redactionManager.processEvent(event);
|
|
5628
|
-
const eventSize = new TextEncoder().encode(JSON.stringify(processedEvent)).length;
|
|
5629
|
-
this.eventIngestionQueue.push(processedEvent);
|
|
5630
|
-
this.queueSizeBytes += eventSize;
|
|
5621
|
+
this.eventIngestionQueue.push(event); // Direct event handling
|
|
5631
5622
|
});
|
|
5632
5623
|
}
|
|
5624
|
+
/**
|
|
5625
|
+
* Flush events to the ingestion server
|
|
5626
|
+
* Events are sent in chunks to handle large payloads efficiently
|
|
5627
|
+
*/
|
|
5633
5628
|
flush() {
|
|
5634
5629
|
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5635
5630
|
var _a, _b, _c, _d, _e, _f;
|
|
@@ -5642,9 +5637,13 @@ class HumanBehaviorTracker {
|
|
|
5642
5637
|
// Swap the current queue with an empty one atomically
|
|
5643
5638
|
const eventsToProcess = this.eventIngestionQueue;
|
|
5644
5639
|
this.eventIngestionQueue = [];
|
|
5645
|
-
this.queueSizeBytes = 0;
|
|
5646
5640
|
if (eventsToProcess.length > 0) {
|
|
5647
5641
|
logDebug('Flushing events:', eventsToProcess);
|
|
5642
|
+
// ✅ LOG FULLSNAPSHOT STATUS FOR MONITORING
|
|
5643
|
+
const fullSnapshots = eventsToProcess.filter(e => e.type === 2);
|
|
5644
|
+
if (fullSnapshots.length > 0) {
|
|
5645
|
+
logDebug(`[FIXED] Sending ${fullSnapshots.length} FullSnapshot(s) with valid data`);
|
|
5646
|
+
}
|
|
5648
5647
|
try {
|
|
5649
5648
|
// Use chunked sending to handle large payloads
|
|
5650
5649
|
yield this.api.sendEventsChunked(eventsToProcess, this.sessionId, this.endUserId);
|
|
@@ -5809,14 +5808,23 @@ class HumanBehaviorTracker {
|
|
|
5809
5808
|
}
|
|
5810
5809
|
/**
|
|
5811
5810
|
* Set specific fields to be redacted during session recording
|
|
5811
|
+
* Uses rrweb's built-in masking instead of custom redaction processing
|
|
5812
5812
|
* @param fields Array of CSS selectors for fields to redact (e.g., ['input[type="password"]', '#email-field'])
|
|
5813
5813
|
*/
|
|
5814
5814
|
setRedactedFields(fields) {
|
|
5815
|
-
if (!isBrowser) {
|
|
5816
|
-
logWarn('Redaction is only available in browser environments');
|
|
5817
|
-
return;
|
|
5818
|
-
}
|
|
5819
5815
|
this.redactionManager.setFieldsToRedact(fields);
|
|
5816
|
+
// ✅ APPLY RRWEB MASKING CLASSES - More reliable than custom processing
|
|
5817
|
+
this.redactionManager.applyRedactionClasses();
|
|
5818
|
+
// ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures masking is applied
|
|
5819
|
+
if (this.recordInstance) {
|
|
5820
|
+
this.restartWithNewRedaction();
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
restartWithNewRedaction() {
|
|
5824
|
+
if (this.recordInstance) {
|
|
5825
|
+
this.recordInstance(); // Stop current recording
|
|
5826
|
+
this.start(); // Restart with new redaction settings
|
|
5827
|
+
}
|
|
5820
5828
|
}
|
|
5821
5829
|
/**
|
|
5822
5830
|
* Check if redaction is currently active
|
|
@@ -5844,29 +5852,15 @@ class HumanBehaviorTracker {
|
|
|
5844
5852
|
}
|
|
5845
5853
|
/**
|
|
5846
5854
|
* Get current snapshot frequency info
|
|
5855
|
+
* Uses rrweb's sensible defaults (5 seconds, 100 events)
|
|
5847
5856
|
*/
|
|
5848
5857
|
getSnapshotFrequencyInfo() {
|
|
5849
5858
|
const sessionDuration = Date.now() - this.sessionStartTime;
|
|
5850
|
-
const thirtyMinutes = 30 * 60 * 1000;
|
|
5851
|
-
const twoHours = 2 * 60 * 60 * 1000;
|
|
5852
|
-
let phase = 'initial';
|
|
5853
|
-
let interval = 5000;
|
|
5854
|
-
let threshold = 100;
|
|
5855
|
-
if (sessionDuration > twoHours) {
|
|
5856
|
-
phase = 'extended';
|
|
5857
|
-
interval = 30000;
|
|
5858
|
-
threshold = 500;
|
|
5859
|
-
}
|
|
5860
|
-
else if (sessionDuration > thirtyMinutes) {
|
|
5861
|
-
phase = 'moderate';
|
|
5862
|
-
interval = 15000;
|
|
5863
|
-
threshold = 300;
|
|
5864
|
-
}
|
|
5865
5859
|
return {
|
|
5866
5860
|
sessionDuration,
|
|
5867
|
-
currentInterval:
|
|
5868
|
-
currentThreshold:
|
|
5869
|
-
phase
|
|
5861
|
+
currentInterval: 5000, // rrweb default - 5 seconds
|
|
5862
|
+
currentThreshold: 100, // rrweb default - 100 events
|
|
5863
|
+
phase: 'default' // Using rrweb's proven defaults
|
|
5870
5864
|
};
|
|
5871
5865
|
}
|
|
5872
5866
|
/**
|