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.
@@ -9,7 +9,8 @@ declare class RedactionManager {
9
9
  private excludeSelectors;
10
10
  constructor(options?: RedactionOptions);
11
11
  /**
12
- * Set specific fields to be redacted
12
+ * Set specific fields to be redacted using CSS selectors
13
+ * These selectors are used to configure rrweb's built-in masking
13
14
  * @param fields Array of CSS selectors for fields to redact
14
15
  */
15
16
  setFieldsToRedact(fields: string[]): void;
@@ -23,6 +24,8 @@ declare class RedactionManager {
23
24
  getSelectedFields(): string[];
24
25
  /**
25
26
  * Process an event and redact sensitive data if needed
27
+ * NOTE: This method is no longer used - events are handled directly by rrweb
28
+ * Kept for backward compatibility but not called in the current implementation
26
29
  */
27
30
  processEvent(event: any): any;
28
31
  /**
@@ -66,9 +69,20 @@ declare class RedactionManager {
66
69
  */
67
70
  private isFieldSelected;
68
71
  /**
69
- * Check if an element should be redacted based on user-selected fields
72
+ * Get CSS selectors for rrweb masking configuration
73
+ * Used to configure rrweb's maskTextSelector option
70
74
  */
71
- private shouldRedactElement;
75
+ getMaskTextSelector(): string | null;
76
+ /**
77
+ * Check if an element should be redacted (for rrweb maskTextFn/maskInputFn)
78
+ */
79
+ shouldRedactElement(element: HTMLElement): boolean;
80
+ /**
81
+ * Apply rrweb masking classes to DOM elements
82
+ * Adds 'rr-mask' class to elements that should be redacted
83
+ * This enables rrweb's built-in masking functionality
84
+ */
85
+ applyRedactionClasses(): void;
72
86
  /**
73
87
  * Get the original value of a redacted element (for debugging)
74
88
  */
@@ -88,7 +102,6 @@ declare global {
88
102
  }
89
103
  declare class HumanBehaviorTracker {
90
104
  private eventIngestionQueue;
91
- private queueSizeBytes;
92
105
  private sessionId;
93
106
  private userProperties;
94
107
  private isProcessing;
@@ -110,7 +123,6 @@ declare class HumanBehaviorTracker {
110
123
  private navigationListeners;
111
124
  private _connectionBlocked;
112
125
  private recordInstance;
113
- private frequencyUpdateInterval;
114
126
  private sessionStartTime;
115
127
  /**
116
128
  * Initialize the HumanBehavior tracker
@@ -140,13 +152,7 @@ declare class HumanBehaviorTracker {
140
152
  * Track navigation events and send custom events
141
153
  */
142
154
  trackNavigationEvent(type: string, fromUrl: string, toUrl: string): Promise<void>;
143
- /**
144
- * Track a page view event (PostHog-style)
145
- */
146
155
  trackPageView(url?: string): Promise<void>;
147
- /**
148
- * Track a custom event (PostHog-style)
149
- */
150
156
  customEvent(eventName: string, properties?: Record<string, any>): Promise<void>;
151
157
  /**
152
158
  * Setup automatic tracking for buttons, links, and forms
@@ -193,7 +199,7 @@ declare class HumanBehaviorTracker {
193
199
  * Add user identification information to the tracker
194
200
  * If userId is not provided, will use userProperties.email as the userId (if present)
195
201
  */
196
- addUserInfo({ userId, userProperties }: {
202
+ identifyUser({ userId, userProperties }: {
197
203
  userId?: string;
198
204
  userProperties: Record<string, any>;
199
205
  }): Promise<string>;
@@ -203,7 +209,15 @@ declare class HumanBehaviorTracker {
203
209
  getUserAttributes(): Record<string, any>;
204
210
  start(): Promise<void>;
205
211
  stop(): Promise<void>;
212
+ /**
213
+ * Add an event to the ingestion queue
214
+ * Events are sent directly without processing to avoid corruption
215
+ */
206
216
  addEvent(event: any): Promise<void>;
217
+ /**
218
+ * Flush events to the ingestion server
219
+ * Events are sent in chunks to handle large payloads efficiently
220
+ */
207
221
  private flush;
208
222
  private setCookie;
209
223
  getCookie(name: string): string | null;
@@ -225,9 +239,11 @@ declare class HumanBehaviorTracker {
225
239
  redact(options?: RedactionOptions): Promise<void>;
226
240
  /**
227
241
  * Set specific fields to be redacted during session recording
242
+ * Uses rrweb's built-in masking instead of custom redaction processing
228
243
  * @param fields Array of CSS selectors for fields to redact (e.g., ['input[type="password"]', '#email-field'])
229
244
  */
230
245
  setRedactedFields(fields: string[]): void;
246
+ private restartWithNewRedaction;
231
247
  /**
232
248
  * Check if redaction is currently active
233
249
  */
@@ -246,6 +262,7 @@ declare class HumanBehaviorTracker {
246
262
  getCurrentUrl(): string;
247
263
  /**
248
264
  * Get current snapshot frequency info
265
+ * Uses rrweb's sensible defaults (5 seconds, 100 events)
249
266
  */
250
267
  getSnapshotFrequencyInfo(): {
251
268
  sessionDuration: number;
@@ -20,7 +20,7 @@ declare const useRedaction: () => {
20
20
  getRedactedFields: () => string[];
21
21
  };
22
22
  declare const useUserTracking: () => {
23
- addUserInfo: ({ userId, userProperties }: {
23
+ identifyUser: ({ userId, userProperties }: {
24
24
  userId?: string;
25
25
  userProperties: Record<string, any>;
26
26
  }) => Promise<{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanbehavior-js",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "SDK for HumanBehavior session and event recording",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -31,10 +31,13 @@
31
31
  "dependencies": {
32
32
  "@types/react": "^19.0.12",
33
33
  "humanbehavior-js": "^0.0.9",
34
- "react": "^19.0.0",
35
34
  "rrweb": "^2.0.0-alpha.4",
36
35
  "uuid": "^11.1.0"
37
36
  },
37
+ "peerDependencies": {
38
+ "react": ">=16.8.0",
39
+ "react-dom": ">=16.8.0"
40
+ },
38
41
  "devDependencies": {
39
42
  "@rollup/plugin-commonjs": "^28.0.3",
40
43
  "@rollup/plugin-node-resolve": "^16.0.1",
package/rollup.config.js CHANGED
@@ -4,8 +4,8 @@ import commonjs from '@rollup/plugin-commonjs';
4
4
  import terser from '@rollup/plugin-terser';
5
5
  import dts from 'rollup-plugin-dts';
6
6
 
7
- // Only React should be external since it's typically provided by the host application
8
- const external = ['react', 'react-dom'];
7
+ // React should be external since it's typically provided by the host application
8
+ const external = ['react', 'react-dom', 'react/jsx-runtime'];
9
9
 
10
10
  // Global variables for UMD build
11
11
  const globals = {
@@ -81,7 +81,7 @@ export default [
81
81
  declaration: false
82
82
  })
83
83
  ],
84
- external: [...external, '..']
84
+ external: [...external, '..'] // Externalize React and the main SDK
85
85
  },
86
86
 
87
87
  // Type definition bundles - generate these separately
@@ -8,7 +8,7 @@ const isBrowser = () => typeof window !== 'undefined';
8
8
  // Define the public interface that components will interact with
9
9
  interface HumanBehaviorInterface {
10
10
  addEvent: (event: any) => void;
11
- addUserInfo: ({ userId, userProperties }: { userId?: string, userProperties: Record<string, any> }) => Promise<string>;
11
+ identifyUser: ({ userId, userProperties }: { userId?: string, userProperties: Record<string, any> }) => Promise<string>;
12
12
  start: () => void;
13
13
  stop: () => void;
14
14
  logout: () => void;
@@ -111,6 +111,17 @@ export const HumanBehaviorProvider = ({ apiKey, client, children, options }: Hum
111
111
  apiKeyRef.current.trim(),
112
112
  'https://ingest.humanbehavior.co'
113
113
  );
114
+
115
+ // ✅ APPLY LOGGING CONFIGURATION FROM OPTIONS
116
+ if (options?.logLevel) {
117
+ HumanBehaviorTracker.configureLogging({ level: options.logLevel });
118
+ }
119
+
120
+ // ✅ APPLY REDACTION FIELDS FROM OPTIONS
121
+ if (options?.redactFields && options.redactFields.length > 0) {
122
+ tracker.setRedactedFields(options.redactFields);
123
+ }
124
+
114
125
  setHumanBehavior(tracker);
115
126
 
116
127
  // Wait for initialization to complete
@@ -125,7 +136,7 @@ export const HumanBehaviorProvider = ({ apiKey, client, children, options }: Hum
125
136
  if (event.type === 'identify') {
126
137
  logDebug('Processing queued identify event', event);
127
138
  try {
128
- await tracker.addUserInfo({ userId: event.userId, userProperties: event.userProperties });
139
+ await tracker.identifyUser({ userId: event.userId, userProperties: event.userProperties });
129
140
  } catch (error) {
130
141
  logError('Failed to process queued user info:', error);
131
142
  }
@@ -169,7 +180,7 @@ export const HumanBehaviorProvider = ({ apiKey, client, children, options }: Hum
169
180
  // Default implementation for when tracker is not available
170
181
  const defaultImplementation: HumanBehaviorInterface = {
171
182
  addEvent: () => {},
172
- addUserInfo: async ({ userId, userProperties }: { userId?: string, userProperties: Record<string, any> }) => {
183
+ identifyUser: async ({ userId, userProperties }: { userId?: string, userProperties: Record<string, any> }) => {
173
184
  return userId || '';
174
185
  },
175
186
  start: () => {},
@@ -183,7 +194,7 @@ const createQueuingImplementation = (queueEvent: (event: any) => void): HumanBeh
183
194
  addEvent: (event: any) => {
184
195
  queueEvent(event);
185
196
  },
186
- addUserInfo: async ({ userId, userProperties }: { userId?: string, userProperties: Record<string, any> }) => {
197
+ identifyUser: async ({ userId, userProperties }: { userId?: string, userProperties: Record<string, any> }) => {
187
198
  queueEvent({
188
199
  type: 'identify',
189
200
  userId,
@@ -240,18 +251,18 @@ export const useRedaction = () => {
240
251
  export const useUserTracking = () => {
241
252
  const tracker = useHumanBehavior();
242
253
 
243
- const addUserInfo = useCallback(async ({ userId, userProperties }: { userId?: string, userProperties: Record<string, any> }) => {
254
+ const identifyUser = useCallback(async ({ userId, userProperties }: { userId?: string, userProperties: Record<string, any> }) => {
244
255
  try {
245
- await tracker.addUserInfo({ userId, userProperties });
256
+ await tracker.identifyUser({ userId, userProperties });
246
257
  return { success: true };
247
258
  } catch (error) {
248
- logError('Failed to add user info:', error);
259
+ logError('Failed to identify user:', error);
249
260
  return { success: false, error };
250
261
  }
251
262
  }, [tracker]);
252
263
 
253
264
  return {
254
- addUserInfo
265
+ identifyUser
255
266
  };
256
267
  };
257
268
 
package/src/redact.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // Redaction functionality for sensitive input fields
2
- // This module provides methods to redact sensitive input fields in event recordings
2
+ // This module provides methods to configure rrweb's built-in masking
3
+ // Uses CSS selectors and classes for reliable redaction without event corruption
3
4
 
4
5
  import { logDebug, logWarn } from './utils/logger';
5
6
 
@@ -33,7 +34,8 @@ export class RedactionManager {
33
34
  }
34
35
 
35
36
  /**
36
- * Set specific fields to be redacted
37
+ * Set specific fields to be redacted using CSS selectors
38
+ * These selectors are used to configure rrweb's built-in masking
37
39
  * @param fields Array of CSS selectors for fields to redact
38
40
  */
39
41
  public setFieldsToRedact(fields: string[]): void {
@@ -72,6 +74,8 @@ export class RedactionManager {
72
74
 
73
75
  /**
74
76
  * Process an event and redact sensitive data if needed
77
+ * NOTE: This method is no longer used - events are handled directly by rrweb
78
+ * Kept for backward compatibility but not called in the current implementation
75
79
  */
76
80
  public processEvent(event: any): any {
77
81
  // Only process if we have fields selected for redaction
@@ -431,26 +435,67 @@ export class RedactionManager {
431
435
  }
432
436
 
433
437
  /**
434
- * Check if an element should be redacted based on user-selected fields
438
+ * Get CSS selectors for rrweb masking configuration
439
+ * Used to configure rrweb's maskTextSelector option
435
440
  */
436
- private shouldRedactElement(element: HTMLElement): boolean {
437
- // Check if element is excluded from redaction
438
- for (const excludeSelector of this.excludeSelectors) {
439
- if (element.matches(excludeSelector) || element.closest(excludeSelector)) {
440
- return false;
441
- }
441
+ public getMaskTextSelector(): string | null {
442
+ if (this.userSelectedFields.size === 0) {
443
+ return null;
442
444
  }
445
+ return Array.from(this.userSelectedFields).join(',');
446
+ }
443
447
 
444
- // Check if element matches any of the user-selected fields
448
+ /**
449
+ * Check if an element should be redacted (for rrweb maskTextFn/maskInputFn)
450
+ */
451
+ public shouldRedactElement(element: HTMLElement): boolean {
452
+ if (this.userSelectedFields.size === 0) {
453
+ return false;
454
+ }
455
+
456
+ // Check if any selector matches this element
445
457
  for (const selector of this.userSelectedFields) {
446
- if (element.matches(selector)) {
447
- return true;
458
+ try {
459
+ if (element.matches(selector)) {
460
+ return true;
461
+ }
462
+ } catch (e) {
463
+ // Invalid selector, skip
464
+ logWarn(`Invalid selector: ${selector}`);
448
465
  }
449
466
  }
450
-
451
467
  return false;
452
468
  }
453
469
 
470
+ /**
471
+ * Apply rrweb masking classes to DOM elements
472
+ * Adds 'rr-mask' class to elements that should be redacted
473
+ * This enables rrweb's built-in masking functionality
474
+ */
475
+ public applyRedactionClasses(): void {
476
+ if (this.userSelectedFields.size === 0) {
477
+ return;
478
+ }
479
+
480
+ // Remove existing redaction classes
481
+ document.querySelectorAll('.rr-mask').forEach(element => {
482
+ element.classList.remove('rr-mask');
483
+ });
484
+
485
+ // Add redaction classes to matching elements
486
+ this.userSelectedFields.forEach(selector => {
487
+ try {
488
+ const elements = document.querySelectorAll(selector);
489
+ elements.forEach(element => {
490
+ element.classList.add('rr-mask');
491
+ });
492
+ logDebug(`Applied rr-mask class to ${elements.length} element(s) for selector: ${selector}`);
493
+ } catch (e) {
494
+ logWarn(`Invalid selector: ${selector}`);
495
+ }
496
+ });
497
+ }
498
+
454
499
  /**
455
500
  * Get the original value of a redacted element (for debugging)
456
501
  */
package/src/tracker.ts CHANGED
@@ -17,8 +17,6 @@ declare global {
17
17
 
18
18
  export class HumanBehaviorTracker {
19
19
  private eventIngestionQueue: any[] = [];
20
- private queueSizeBytes: number = 0;
21
-
22
20
  private sessionId!: string;
23
21
  private userProperties: Record<string, any> = {};
24
22
  private isProcessing: boolean = false;
@@ -48,7 +46,6 @@ export class HumanBehaviorTracker {
48
46
  private navigationListeners: Array<() => void> = [];
49
47
  private _connectionBlocked: boolean = false;
50
48
  private recordInstance: any = null;
51
- private frequencyUpdateInterval: any = null;
52
49
  private sessionStartTime: number = Date.now();
53
50
 
54
51
  /**
@@ -85,6 +82,8 @@ export class HumanBehaviorTracker {
85
82
  // Set redacted fields if specified
86
83
  if (options?.redactFields) {
87
84
  tracker.setRedactedFields(options.redactFields);
85
+ // ✅ Apply redaction classes to existing elements
86
+ tracker.redactionManager.applyRedactionClasses();
88
87
  }
89
88
 
90
89
  // Setup automatic tracking if enabled
@@ -304,9 +303,6 @@ export class HumanBehaviorTracker {
304
303
  }
305
304
  }
306
305
 
307
- /**
308
- * Track a page view event (PostHog-style)
309
- */
310
306
  public async trackPageView(url?: string): Promise<void> {
311
307
  if (!this.initialized) return;
312
308
 
@@ -338,9 +334,6 @@ export class HumanBehaviorTracker {
338
334
  }
339
335
  }
340
336
 
341
- /**
342
- * Track a custom event (PostHog-style)
343
- */
344
337
  public async customEvent(eventName: string, properties?: Record<string, any>): Promise<void> {
345
338
  if (!this.initialized) return;
346
339
 
@@ -724,7 +717,7 @@ export class HumanBehaviorTracker {
724
717
  * Add user identification information to the tracker
725
718
  * If userId is not provided, will use userProperties.email as the userId (if present)
726
719
  */
727
- public async addUserInfo(
720
+ public async identifyUser(
728
721
  { userId, userProperties }: { userId?: string, userProperties: Record<string, any> }
729
722
  ): Promise<string> {
730
723
  await this.ensureInitialized();
@@ -739,7 +732,6 @@ export class HumanBehaviorTracker {
739
732
  await this.api.sendUserData(originalEndUserId!, userProperties, this.sessionId);
740
733
 
741
734
  // Don't update endUserId - keep it as the original UUID
742
- // The posthogName will be updated on the server side with the email
743
735
 
744
736
  return originalEndUserId || '';
745
737
  }
@@ -762,70 +754,31 @@ export class HumanBehaviorTracker {
762
754
  // Enable console tracking
763
755
  this.enableConsoleTracking();
764
756
 
765
- // Adaptive snapshot configuration based on session duration
766
- const sessionStartTime = Date.now();
767
- let snapshotInterval = 5000; // Start with 5 seconds
768
- let eventThreshold = 100; // Start with 100 events
769
-
770
- // Function to update snapshot frequency based on session duration
771
- const updateSnapshotFrequency = () => {
772
- const sessionDuration = Date.now() - sessionStartTime;
773
- const thirtyMinutes = 30 * 60 * 1000;
774
- const twoHours = 2 * 60 * 60 * 1000;
775
- const fourHours = 4 * 60 * 60 * 1000;
757
+ // SIMPLIFIED RECORDING - Use rrweb's proven defaults
758
+ // No complex adaptive logic - rrweb's defaults work well for most use cases
759
+ const recordInstance = rrweb.record({
760
+ emit: (event) => {
761
+ // ✅ DIRECT EVENT HANDLING - Let rrweb handle events natively
762
+ this.addEvent(event);
763
+ },
764
+ inlineStylesheet: true,
765
+ recordCanvas: true,
766
+ collectFonts: true,
767
+ inlineImages: true,
768
+ blockClass: 'rr-block',
769
+ ignoreClass: 'rr-ignore',
770
+ maskTextClass: 'rr-mask',
771
+
772
+ // ✅ RRWEB BUILT-IN MASKING - More reliable than custom redaction
773
+ maskAllInputs: false, // Let users control this via selectors
774
+ maskTextSelector: this.redactionManager.getMaskTextSelector() || undefined,
775
+
776
+ // ✅ SELECTOR-BASED REDACTION - Users control via CSS selectors
777
+ // No custom masking functions needed - rrweb handles this natively
778
+ });
776
779
 
777
- if (sessionDuration > twoHours) {
778
- // After 2 hours, very infrequent snapshots
779
- snapshotInterval = 30000; // 30 seconds
780
- eventThreshold = 500; // 500 events
781
- logDebug('Reduced snapshot frequency: 30s/500 events (2+ hours)');
782
- } else if (sessionDuration > thirtyMinutes) {
783
- // After 30 minutes, moderate frequency
784
- snapshotInterval = 15000; // 15 seconds
785
- eventThreshold = 300; // 300 events
786
- logDebug('Reduced snapshot frequency: 15s/300 events (30+ minutes)');
787
- }
788
- // First 30 minutes: 5s/100 events (default)
789
- };
790
-
791
- // Update frequency every 5 minutes
792
- const frequencyUpdateInterval = setInterval(updateSnapshotFrequency, 5 * 60 * 1000);
793
-
794
- // Start recording with adaptive redaction enabled
795
- const recordInstance = rrweb.record({
796
- emit: (event) => {
797
- // Add additional validation for FullSnapshot events
798
- if (event.type === 2) { // FullSnapshot event
799
- if (!event.data || !event.data.node) {
800
- logWarn('rrweb generated malformed FullSnapshot event:', {
801
- hasData: !!event.data,
802
- hasNode: !!(event.data && event.data.node),
803
- dataType: typeof event.data,
804
- eventType: event.type,
805
- timestamp: event.timestamp
806
- });
807
- // Don't skip - let the addEvent method handle it
808
- } else {
809
- logDebug('Valid FullSnapshot event received from rrweb');
810
- }
811
- }
812
- this.addEvent(event);
813
- },
814
- inlineStylesheet: true,
815
- recordCanvas: true,
816
- collectFonts: true,
817
- inlineImages: true,
818
- blockClass: 'rr-block',
819
- ignoreClass: 'rr-ignore',
820
- maskTextClass: 'rr-ignore',
821
- // Adaptive configuration
822
- checkoutEveryNms: snapshotInterval,
823
- checkoutEveryNth: eventThreshold
824
- });
825
-
826
- // Store the record instance and interval for cleanup
827
- this.recordInstance = recordInstance;
828
- this.frequencyUpdateInterval = frequencyUpdateInterval;
780
+ // Store the record instance for cleanup
781
+ this.recordInstance = recordInstance;
829
782
  }
830
783
 
831
784
  public async stop() {
@@ -837,12 +790,6 @@ export class HumanBehaviorTracker {
837
790
  this.flushInterval = null;
838
791
  }
839
792
 
840
- // Cleanup adaptive snapshot intervals
841
- if (this.frequencyUpdateInterval) {
842
- clearInterval(this.frequencyUpdateInterval);
843
- this.frequencyUpdateInterval = null;
844
- }
845
-
846
793
  // Stop rrweb recording
847
794
  if (this.recordInstance) {
848
795
  this.recordInstance();
@@ -856,30 +803,30 @@ export class HumanBehaviorTracker {
856
803
  this.cleanupNavigationTracking();
857
804
  }
858
805
 
806
+ /**
807
+ * Add an event to the ingestion queue
808
+ * Events are sent directly without processing to avoid corruption
809
+ */
859
810
  public async addEvent(event: any) {
860
811
  await this.ensureInitialized();
861
812
 
862
- // Validate FullSnapshot events before processing
863
- if (event.type === 2) { // FullSnapshot event
864
- if (!event.data || !event.data.node) {
865
- logWarn('Malformed FullSnapshot event detected, skipping:', {
866
- hasData: !!event.data,
867
- hasNode: !!(event.data && event.data.node),
868
- dataType: typeof event.data,
869
- eventType: event.type
870
- });
871
- return; // Skip malformed FullSnapshot events
872
- }
873
- }
813
+ // DIRECT EVENT HANDLING - No custom processing to avoid corruption
814
+ // Events flow directly from rrweb to ingestion server
874
815
 
875
- // Process event through redaction manager if active
876
- const processedEvent = this.redactionManager.processEvent(event);
816
+ // LOG FULLSNAPSHOT STATUS FOR DEBUGGING
817
+ if (event.type === 2) { // FullSnapshot
818
+ const hasData = !!event.data;
819
+ const hasNode = !!(event.data && event.data.node);
820
+ logDebug(`[FIXED] FullSnapshot event: hasData=${hasData}, hasNode=${hasNode}, dataType=${event.data?.node?.type}`);
821
+ }
877
822
 
878
- const eventSize = new TextEncoder().encode(JSON.stringify(processedEvent)).length;
879
- this.eventIngestionQueue.push(processedEvent);
880
- this.queueSizeBytes += eventSize;
823
+ this.eventIngestionQueue.push(event); // Direct event handling
881
824
  }
882
825
 
826
+ /**
827
+ * Flush events to the ingestion server
828
+ * Events are sent in chunks to handle large payloads efficiently
829
+ */
883
830
  private async flush() {
884
831
  // Prevent concurrent flushes
885
832
  if (this.isProcessing || !this.initialized) {
@@ -891,10 +838,16 @@ export class HumanBehaviorTracker {
891
838
  // Swap the current queue with an empty one atomically
892
839
  const eventsToProcess = this.eventIngestionQueue;
893
840
  this.eventIngestionQueue = [];
894
- this.queueSizeBytes = 0;
895
841
 
896
842
  if (eventsToProcess.length > 0) {
897
843
  logDebug('Flushing events:', eventsToProcess);
844
+
845
+ // ✅ LOG FULLSNAPSHOT STATUS FOR MONITORING
846
+ const fullSnapshots = eventsToProcess.filter(e => e.type === 2);
847
+ if (fullSnapshots.length > 0) {
848
+ logDebug(`[FIXED] Sending ${fullSnapshots.length} FullSnapshot(s) with valid data`);
849
+ }
850
+
898
851
  try {
899
852
  // Use chunked sending to handle large payloads
900
853
  await this.api.sendEventsChunked(eventsToProcess, this.sessionId, this.endUserId!);
@@ -1058,15 +1011,26 @@ export class HumanBehaviorTracker {
1058
1011
 
1059
1012
  /**
1060
1013
  * Set specific fields to be redacted during session recording
1014
+ * Uses rrweb's built-in masking instead of custom redaction processing
1061
1015
  * @param fields Array of CSS selectors for fields to redact (e.g., ['input[type="password"]', '#email-field'])
1062
1016
  */
1063
1017
  public setRedactedFields(fields: string[]): void {
1064
- if (!isBrowser) {
1065
- logWarn('Redaction is only available in browser environments');
1066
- return;
1067
- }
1068
-
1069
1018
  this.redactionManager.setFieldsToRedact(fields);
1019
+
1020
+ // ✅ APPLY RRWEB MASKING CLASSES - More reliable than custom processing
1021
+ this.redactionManager.applyRedactionClasses();
1022
+
1023
+ // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures masking is applied
1024
+ if (this.recordInstance) {
1025
+ this.restartWithNewRedaction();
1026
+ }
1027
+ }
1028
+
1029
+ private restartWithNewRedaction(): void {
1030
+ if (this.recordInstance) {
1031
+ this.recordInstance(); // Stop current recording
1032
+ this.start(); // Restart with new redaction settings
1033
+ }
1070
1034
  }
1071
1035
 
1072
1036
  /**
@@ -1099,6 +1063,7 @@ export class HumanBehaviorTracker {
1099
1063
 
1100
1064
  /**
1101
1065
  * Get current snapshot frequency info
1066
+ * Uses rrweb's sensible defaults (5 seconds, 100 events)
1102
1067
  */
1103
1068
  public getSnapshotFrequencyInfo(): {
1104
1069
  sessionDuration: number;
@@ -1107,28 +1072,12 @@ export class HumanBehaviorTracker {
1107
1072
  phase: string;
1108
1073
  } {
1109
1074
  const sessionDuration = Date.now() - this.sessionStartTime;
1110
- const thirtyMinutes = 30 * 60 * 1000;
1111
- const twoHours = 2 * 60 * 60 * 1000;
1112
-
1113
- let phase = 'initial';
1114
- let interval = 5000;
1115
- let threshold = 100;
1116
-
1117
- if (sessionDuration > twoHours) {
1118
- phase = 'extended';
1119
- interval = 30000;
1120
- threshold = 500;
1121
- } else if (sessionDuration > thirtyMinutes) {
1122
- phase = 'moderate';
1123
- interval = 15000;
1124
- threshold = 300;
1125
- }
1126
1075
 
1127
1076
  return {
1128
1077
  sessionDuration,
1129
- currentInterval: interval,
1130
- currentThreshold: threshold,
1131
- phase
1078
+ currentInterval: 5000, // rrweb default - 5 seconds
1079
+ currentThreshold: 100, // rrweb default - 100 events
1080
+ phase: 'default' // Using rrweb's proven defaults
1132
1081
  };
1133
1082
  }
1134
1083