humanbehavior-js 0.1.8 → 0.2.0

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.
@@ -89,8 +89,6 @@ declare global {
89
89
  declare class HumanBehaviorTracker {
90
90
  private eventIngestionQueue;
91
91
  private queueSizeBytes;
92
- private rejectedEvents;
93
- private isProcessingRejectedEvents;
94
92
  private sessionId;
95
93
  private userProperties;
96
94
  private isProcessing;
@@ -188,19 +186,31 @@ declare class HumanBehaviorTracker {
188
186
  private trackConsoleEvent;
189
187
  private setupPageUnloadHandler;
190
188
  viewLogs(): void;
191
- addUserInfo(userProperties: Record<string, any>): Promise<void>;
189
+ addUserInfo({ userId, userProperties }: {
190
+ userId: string;
191
+ userProperties: Record<string, any>;
192
+ }): Promise<void>;
192
193
  /**
193
- * Authenticate user using existing userInfo data
194
+ * Add user identification information to the tracker
194
195
  * @param authFields Array of field names to check for existing users (e.g., ['email', 'phoneNumber'])
195
196
  */
196
- auth(authFields: string[]): Promise<void>;
197
+ private auth;
197
198
  start(): Promise<void>;
198
199
  stop(): Promise<void>;
199
200
  addEvent(event: any): Promise<void>;
200
- private processRejectedEvents;
201
201
  private flush;
202
202
  private setCookie;
203
203
  getCookie(name: string): string | null;
204
+ /**
205
+ * Delete a cookie by setting its expiration date to the past
206
+ * @param name The name of the cookie to delete
207
+ */
208
+ private deleteCookie;
209
+ /**
210
+ * Log out the current user by clearing all user-related data
211
+ * This will delete the user ID cookie, clear localStorage, and reset user properties
212
+ */
213
+ logout(): void;
204
214
  /**
205
215
  * Start redaction functionality for sensitive input fields
206
216
  * @param options Optional configuration for redaction behavior
@@ -260,6 +270,7 @@ declare class HumanBehaviorTracker {
260
270
  declare const MAX_CHUNK_SIZE_BYTES: number;
261
271
  declare function isChunkSizeExceeded(currentChunk: any[], newEvent: any, sessionId: string): boolean;
262
272
  declare function validateSingleEventSize(event: any, sessionId: string): void;
273
+ declare function splitLargeEvent(event: any, sessionId: string): any[];
263
274
  declare class HumanBehaviorAPI {
264
275
  private apiKey;
265
276
  private baseUrl;
@@ -272,7 +283,7 @@ declare class HumanBehaviorAPI {
272
283
  endUserId: any;
273
284
  }>;
274
285
  sendEvents(events: any[], sessionId: string, userId: string): Promise<void>;
275
- sendEventsChunked(events: any[], sessionId: string): Promise<any[]>;
286
+ sendEventsChunked(events: any[], sessionId: string, userId?: string): Promise<any[]>;
276
287
  sendUserData(userId: string, userData: Record<string, any>, sessionId: string): Promise<any>;
277
288
  sendUserAuth(userId: string, userData: Record<string, any>, sessionId: string, authFields: string[]): Promise<any>;
278
289
  sendBeaconEvents(events: any[], sessionId: string): boolean;
@@ -316,5 +327,9 @@ declare const logWarn: (message: string, ...args: any[]) => void;
316
327
  declare const logInfo: (message: string, ...args: any[]) => void;
317
328
  declare const logDebug: (message: string, ...args: any[]) => void;
318
329
 
319
- export { HumanBehaviorAPI, HumanBehaviorTracker, LogLevel, MAX_CHUNK_SIZE_BYTES, RedactionManager, HumanBehaviorTracker as default, isChunkSizeExceeded, logDebug, logError, logInfo, logWarn, logger, redactionManager, validateSingleEventSize };
330
+ /**
331
+ * Main entry point for the HumanBehavior SDK
332
+ */
333
+
334
+ export { HumanBehaviorAPI, HumanBehaviorTracker, LogLevel, MAX_CHUNK_SIZE_BYTES, RedactionManager, HumanBehaviorTracker as default, isChunkSizeExceeded, logDebug, logError, logInfo, logWarn, logger, redactionManager, splitLargeEvent, validateSingleEventSize };
320
335
  export type { LoggerConfig, RedactionOptions };
@@ -1,4 +1,5 @@
1
- import React, { ReactNode } from 'react';
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
2
3
  import { HumanBehaviorTracker } from '..';
3
4
  export { HumanBehaviorTracker } from '..';
4
5
 
@@ -12,7 +13,21 @@ interface HumanBehaviorProviderProps {
12
13
  redactFields?: string[];
13
14
  };
14
15
  }
15
- declare const HumanBehaviorProvider: ({ apiKey, client, children, options }: HumanBehaviorProviderProps) => React.JSX.Element;
16
+ declare const HumanBehaviorProvider: ({ apiKey, client, children, options }: HumanBehaviorProviderProps) => react_jsx_runtime.JSX.Element;
16
17
  declare const useHumanBehavior: () => HumanBehaviorTracker;
18
+ declare const useRedaction: () => {
19
+ setRedactedFields: (fields: string[]) => void;
20
+ isRedactionActive: () => boolean;
21
+ getRedactedFields: () => string[];
22
+ };
23
+ declare const useUserTracking: () => {
24
+ addUserInfo: (userId: string, userProperties: Record<string, any>) => Promise<{
25
+ success: boolean;
26
+ error?: undefined;
27
+ } | {
28
+ success: boolean;
29
+ error: unknown;
30
+ }>;
31
+ };
17
32
 
18
- export { HumanBehaviorProvider, useHumanBehavior };
33
+ export { HumanBehaviorProvider, useHumanBehavior, useRedaction, useUserTracking };
package/package.json CHANGED
@@ -1,36 +1,50 @@
1
1
  {
2
2
  "name": "humanbehavior-js",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "SDK for HumanBehavior session and event recording",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
7
7
  "module": "./dist/esm/index.js",
8
8
  "types": "./dist/types/index.d.ts",
9
9
  "unpkg": "dist/index.min.js",
10
+ "jsdelivr": "dist/index.min.js",
11
+ "homepage": "https://documentation.humanbehavior.co",
10
12
  "exports": {
11
13
  ".": {
12
14
  "import": "./dist/esm/index.js",
13
15
  "require": "./dist/cjs/index.js",
14
- "types": "./dist/types/index.d.ts"
16
+ "types": "./dist/types/index.d.ts",
17
+ "default": "./dist/esm/index.js"
15
18
  },
16
19
  "./react": {
17
20
  "import": "./dist/esm/react/index.js",
18
21
  "require": "./dist/cjs/react/index.js",
19
- "types": "./dist/types/react/index.d.ts"
20
- }
22
+ "types": "./dist/types/react/index.d.ts",
23
+ "default": "./dist/esm/react/index.js"
24
+ },
25
+ "./package.json": "./package.json"
21
26
  },
27
+ "files": [
28
+ "dist/**/*",
29
+ "src/**/*",
30
+ "*.d.ts"
31
+ ],
22
32
  "scripts": {
23
33
  "build": "rollup -c",
24
34
  "build:dev": "rollup -c --environment NODE_ENV:development",
25
35
  "prepare": "npm run build",
26
- "test": "echo \"Error: no test specified\" && exit 1"
36
+ "test": "node scripts/test-bundlers.js",
37
+ "test:bundlers": "node scripts/test-bundlers.js",
38
+ "test:framework": "node scripts/test-framework-compatibility.js",
39
+ "test:all": "npm run test:bundlers && npm run test:framework"
27
40
  },
28
41
  "author": "Chirag Kawediya, Skyler Ji, Amogh Chaturvedi",
29
42
  "license": "ISC",
43
+ "peerDependencies": {
44
+ "react": ">=16.8.0",
45
+ "react-dom": ">=16.8.0"
46
+ },
30
47
  "dependencies": {
31
- "@types/react": "^19.0.12",
32
- "humanbehavior-js": "^0.0.9",
33
- "react": "^19.0.0",
34
48
  "rrweb": "^2.0.0-alpha.4",
35
49
  "uuid": "^11.1.0"
36
50
  },
@@ -40,6 +54,8 @@
40
54
  "@rollup/plugin-terser": "^0.4.4",
41
55
  "@rollup/plugin-typescript": "^12.1.2",
42
56
  "@types/node": "^24.0.3",
57
+ "@types/react": "^19.0.12",
58
+ "@types/react-dom": "^19.0.0",
43
59
  "rollup": "^4.36.0",
44
60
  "rollup-plugin-dts": "^6.2.1",
45
61
  "tslib": "^2.8.1",
@@ -56,6 +72,13 @@
56
72
  "session-recording",
57
73
  "analytics",
58
74
  "rrweb",
59
- "koalaware"
60
- ]
75
+ "koalaware",
76
+ "webpack",
77
+ "esbuild",
78
+ "vite",
79
+ "rollup"
80
+ ],
81
+ "engines": {
82
+ "node": ">=14.0.0"
83
+ }
61
84
  }
package/readme.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  A simplified session recording and analytics SDK that maintains session continuity across page navigations.
4
4
 
5
+ 📖 **[Full Documentation](https://documentation.humanbehavior.co)** - Complete API reference, examples, and integration guides
6
+
5
7
  ## Quick Start
6
8
 
7
9
  ### Single Page Application (Recommended)
package/src/api.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { logError, logInfo } from './utils/logger';
1
+ import { logError, logInfo, logDebug } from './utils/logger';
2
2
 
3
- export const MAX_CHUNK_SIZE_BYTES = 1024 * 1024 * 10; // 10MB chunk size
3
+ export const MAX_CHUNK_SIZE_BYTES = 1024 * 1024; // 1MB chunk size - more conservative
4
4
 
5
5
  export function isChunkSizeExceeded(currentChunk: any[], newEvent: any, sessionId: string): boolean {
6
6
  const nextChunkSize = new TextEncoder().encode(JSON.stringify({
@@ -18,10 +18,62 @@ export function validateSingleEventSize(event: any, sessionId: string): void {
18
18
  })).length;
19
19
 
20
20
  if (singleEventSize > MAX_CHUNK_SIZE_BYTES) {
21
- throw new Error(`Single event size (${singleEventSize} bytes) exceeds maximum chunk size (${MAX_CHUNK_SIZE_BYTES} bytes)`);
21
+ // Instead of throwing, log a warning and suggest reducing event size
22
+ console.warn(`Single event size (${singleEventSize} bytes) exceeds maximum chunk size (${MAX_CHUNK_SIZE_BYTES} bytes). Consider reducing event data size.`);
22
23
  }
23
24
  }
24
25
 
26
+ export function splitLargeEvent(event: any, sessionId: string): any[] {
27
+ const eventSize = new TextEncoder().encode(JSON.stringify({
28
+ sessionId,
29
+ events: [event]
30
+ })).length;
31
+
32
+ if (eventSize <= MAX_CHUNK_SIZE_BYTES) {
33
+ return [event];
34
+ }
35
+
36
+ // If event is too large, try to split it by removing large properties
37
+ const simplifiedEvent = { ...event };
38
+
39
+ // Remove potentially large properties
40
+ const largeProperties = ['screenshot', 'html', 'dom', 'fullText', 'innerHTML', 'outerHTML'];
41
+ largeProperties.forEach(prop => {
42
+ if (simplifiedEvent[prop]) {
43
+ delete simplifiedEvent[prop];
44
+ }
45
+ });
46
+
47
+ // Check if simplified event is now small enough
48
+ const simplifiedSize = new TextEncoder().encode(JSON.stringify({
49
+ sessionId,
50
+ events: [simplifiedEvent]
51
+ })).length;
52
+
53
+ if (simplifiedSize <= MAX_CHUNK_SIZE_BYTES) {
54
+ return [simplifiedEvent];
55
+ }
56
+
57
+ // If still too large, create a minimal event
58
+ const minimalEvent = {
59
+ type: event.type,
60
+ timestamp: event.timestamp,
61
+ url: event.url,
62
+ pathname: event.pathname,
63
+ // Keep only essential properties
64
+ ...Object.fromEntries(
65
+ Object.entries(event).filter(([key, value]) =>
66
+ !largeProperties.includes(key) &&
67
+ typeof value !== 'object' &&
68
+ typeof value !== 'string' ||
69
+ (typeof value === 'string' && value.length < 1000)
70
+ )
71
+ )
72
+ };
73
+
74
+ return [minimalEvent];
75
+ }
76
+
25
77
  export class HumanBehaviorAPI {
26
78
  private apiKey: string;
27
79
  private baseUrl: string;
@@ -98,7 +150,7 @@ export class HumanBehaviorAPI {
98
150
  }
99
151
  }
100
152
 
101
- async sendEventsChunked(events: any[], sessionId: string) {
153
+ async sendEventsChunked(events: any[], sessionId: string, userId?: string) {
102
154
  try {
103
155
  const results = [];
104
156
  let currentChunk: any[] = [];
@@ -115,7 +167,8 @@ export class HumanBehaviorAPI {
115
167
  },
116
168
  body: JSON.stringify({
117
169
  sessionId,
118
- events: currentChunk
170
+ events: currentChunk,
171
+ endUserId: userId
119
172
  })
120
173
  });
121
174
 
@@ -127,11 +180,11 @@ export class HumanBehaviorAPI {
127
180
  currentChunk = [];
128
181
  }
129
182
 
130
- // Validate single event size
131
- validateSingleEventSize(event, sessionId);
183
+ // Handle large events by splitting them
184
+ const splitEvents = splitLargeEvent(event, sessionId);
132
185
 
133
- // Start new chunk with this event
134
- currentChunk = [event];
186
+ // Start new chunk with the split events
187
+ currentChunk = splitEvents;
135
188
  } else {
136
189
  // Add event to current chunk
137
190
  currentChunk.push(event);
@@ -148,7 +201,8 @@ export class HumanBehaviorAPI {
148
201
  },
149
202
  body: JSON.stringify({
150
203
  sessionId,
151
- events: currentChunk
204
+ events: currentChunk,
205
+ endUserId: userId
152
206
  })
153
207
  });
154
208
 
@@ -224,7 +278,8 @@ export class HumanBehaviorAPI {
224
278
  const payload = {
225
279
  sessionId: sessionId,
226
280
  events: events,
227
- endUserId: null // Beacon doesn't have user context
281
+ endUserId: null, // Beacon doesn't have user context
282
+ apiKey: this.apiKey // Include API key in body since beacon can't use headers
228
283
  };
229
284
 
230
285
  // Convert to Blob for sendBeacon
@@ -241,6 +296,7 @@ export class HumanBehaviorAPI {
241
296
  }
242
297
 
243
298
  async sendCustomEvent(sessionId: string, eventName: string, eventProperties?: Record<string, any>) {
299
+ logInfo('[SDK] Sending custom event', { sessionId, eventName, eventProperties });
244
300
  try {
245
301
  const response = await fetch(`${this.baseUrl}/api/ingestion/customEvent`, {
246
302
  method: 'POST',
@@ -254,14 +310,17 @@ export class HumanBehaviorAPI {
254
310
  eventProperties: eventProperties || {}
255
311
  })
256
312
  });
257
-
313
+ logInfo('[SDK] Custom event response', { status: response.status, statusText: response.statusText });
258
314
  if (!response.ok) {
259
- throw new Error(`Failed to send custom event: ${response.statusText}`);
315
+ const errorText = await response.text();
316
+ logError('[SDK] Failed to send custom event', { status: response.status, statusText: response.statusText, errorText });
317
+ throw new Error(`Failed to send custom event: ${response.status} ${response.statusText} - ${errorText}`);
260
318
  }
261
-
262
- return await response.json();
319
+ const json = await response.json();
320
+ logDebug('[SDK] Custom event success', json);
321
+ return json;
263
322
  } catch (error) {
264
- logError('Error sending custom event:', error);
323
+ logError('[SDK] Error sending custom event', error, { sessionId, eventName, eventProperties });
265
324
  throw error;
266
325
  }
267
326
  }
package/src/index.ts CHANGED
@@ -16,8 +16,8 @@ export * from './redact';
16
16
  // Export logger functionality
17
17
  export * from './utils/logger';
18
18
 
19
- // Also export the tracker as the default export
20
- export { HumanBehaviorTracker as default } from './tracker';
19
+ // Export the tracker as the default export
20
+ export default HumanBehaviorTracker;
21
21
 
22
22
  // For UMD builds, expose the main class globally
23
23
  if (typeof window !== 'undefined') {
@@ -114,21 +114,24 @@ export const HumanBehaviorProvider = ({ apiKey, client, children, options }: Hum
114
114
 
115
115
  // Wait for initialization to complete
116
116
  tracker.initializationPromise?.then(async () => {
117
- // Start the tracker to enable event flushing and recording
118
117
  await tracker.start();
119
118
  setIsInitialized(true);
120
119
 
121
120
  // Process any queued events
122
121
  const currentQueue = eventQueueRef.current;
123
122
  if (currentQueue.length > 0) {
124
- currentQueue.forEach(event => {
123
+ for (const event of currentQueue) {
125
124
  if (event.type === 'identify') {
126
125
  logDebug('Processing queued identify event', event.userProperties);
127
- tracker.addUserInfo(event.userProperties);
126
+ try {
127
+ await tracker.addUserInfo(event.userProperties);
128
+ } catch (error) {
129
+ logError('Failed to process queued user info:', error);
130
+ }
128
131
  } else {
129
132
  tracker.addEvent(event);
130
133
  }
131
- });
134
+ }
132
135
  setEventQueue([]); // Clear the queue
133
136
  }
134
137
  }).catch(error => {
@@ -201,6 +204,48 @@ export const useHumanBehavior = () => {
201
204
  return tracker;
202
205
  };
203
206
 
207
+ // Custom hook for managing redaction fields dynamically
208
+ export const useRedaction = () => {
209
+ const tracker = useHumanBehavior();
210
+
211
+ const setRedactedFields = useCallback((fields: string[]) => {
212
+ tracker.setRedactedFields(fields);
213
+ }, [tracker]);
214
+
215
+ const isRedactionActive = useCallback(() => {
216
+ return tracker.isRedactionActive();
217
+ }, [tracker]);
218
+
219
+ const getRedactedFields = useCallback(() => {
220
+ return tracker.getRedactedFields();
221
+ }, [tracker]);
222
+
223
+ return {
224
+ setRedactedFields,
225
+ isRedactionActive,
226
+ getRedactedFields
227
+ };
228
+ };
229
+
230
+ // Custom hook for managing user info
231
+ export const useUserTracking = () => {
232
+ const tracker = useHumanBehavior();
233
+
234
+ const addUserInfo = useCallback(async (userId: string, userProperties: Record<string, any>) => {
235
+ try {
236
+ await tracker.addUserInfo({ userId, userProperties });
237
+ return { success: true };
238
+ } catch (error) {
239
+ logError('Failed to add user info:', error);
240
+ return { success: false, error };
241
+ }
242
+ }, [tracker]);
243
+
244
+ return {
245
+ addUserInfo
246
+ };
247
+ };
248
+
204
249
  // Automatic page tracking component (similar to PostHog's PostHogPageView)
205
250
  function HumanBehaviorPageView() {
206
251
  const tracker = useContext(HumanBehaviorContext);
package/src/tracker.ts CHANGED
@@ -18,8 +18,6 @@ declare global {
18
18
  export class HumanBehaviorTracker {
19
19
  private eventIngestionQueue: any[] = [];
20
20
  private queueSizeBytes: number = 0;
21
- private rejectedEvents: any[] = [];
22
- private isProcessingRejectedEvents: boolean = false;
23
21
 
24
22
  private sessionId!: string;
25
23
  private userProperties: Record<string, any> = {};
@@ -183,7 +181,6 @@ export class HumanBehaviorTracker {
183
181
  if (isBrowser) {
184
182
  this.setupPageUnloadHandler();
185
183
  this.setupNavigationTracking();
186
- this.processRejectedEvents();
187
184
  } else {
188
185
  logWarn('HumanBehaviorTracker initialized in a non-browser environment. Session tracking is disabled.');
189
186
  }
@@ -349,10 +346,21 @@ export class HumanBehaviorTracker {
349
346
  await this.api.sendCustomEvent(this.sessionId, eventName, properties);
350
347
 
351
348
  logDebug(`Custom event tracked: ${eventName}`, properties);
352
- } catch (error) {
349
+ } catch (error: any) {
353
350
  logError('Failed to track custom event:', error);
354
351
 
355
- // Fallback: add to event stream if direct API call fails
352
+ // Handle specific error types - check for any custom event failure
353
+ if (error.message?.includes('500') ||
354
+ error.message?.includes('Internal Server Error') ||
355
+ error.message?.includes('Failed to send custom event')) {
356
+ logWarn('Custom event endpoint failed, using fallback');
357
+ } else if (error.message?.includes('ERR_BLOCKED_BY_CLIENT')) {
358
+ logWarn('Custom event request blocked by ad blocker, using fallback');
359
+ } else if (error.message?.includes('Failed to fetch')) {
360
+ logWarn('Custom event network error, using fallback');
361
+ }
362
+
363
+ // Always try fallback for any custom event error
356
364
  try {
357
365
  const customEventData = {
358
366
  eventName: eventName,
@@ -709,30 +717,31 @@ export class HumanBehaviorTracker {
709
717
  }
710
718
  }
711
719
 
712
- public async addUserInfo(userProperties: Record<string, any>) {
720
+ public async addUserInfo(
721
+ { userId, userProperties }: { userId: string, userProperties: Record<string, any> }
722
+ ) {
713
723
  await this.ensureInitialized();
714
724
  if (!this.endUserId) {
715
725
  throw new Error('Cannot add user info before tracker initialization');
716
726
  }
717
727
  this.userProperties = userProperties;
718
728
  await this.api.sendUserData(this.endUserId, userProperties, this.sessionId);
729
+ if (userId) {
730
+ this.endUserId = userId;
731
+ }
719
732
  }
720
733
 
721
734
  /**
722
- * Authenticate user using existing userInfo data
735
+ * Add user identification information to the tracker
723
736
  * @param authFields Array of field names to check for existing users (e.g., ['email', 'phoneNumber'])
724
737
  */
725
- public async auth(authFields: string[]) {
738
+ private async auth(id: string) {
726
739
  await this.ensureInitialized();
727
740
  if (!this.endUserId) {
728
741
  throw new Error('Cannot authenticate before tracker initialization');
729
742
  }
730
- if (!this.userProperties || Object.keys(this.userProperties).length === 0) {
731
- throw new Error('No user info available. Call addUserInfo() first.');
732
- }
733
- const response = await this.api.sendUserAuth(this.endUserId, this.userProperties, this.sessionId, authFields);
743
+ const response = await this.api.sendUserAuth(this.endUserId, { id }, this.sessionId, ["id"]);
734
744
  if (response && response.userId && response.userId !== this.endUserId) {
735
- // Update endUserId and cookie if backend returns a new userId
736
745
  this.endUserId = response.userId;
737
746
  this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, response.userId, 365);
738
747
  }
@@ -792,33 +801,6 @@ export class HumanBehaviorTracker {
792
801
  this.queueSizeBytes += eventSize;
793
802
  }
794
803
 
795
- private async processRejectedEvents() {
796
- if (this.isProcessingRejectedEvents || this.rejectedEvents.length === 0) return;
797
-
798
- this.isProcessingRejectedEvents = true;
799
- try {
800
- // Create a new session ID for rejected events
801
- const newSessionId = uuidv1();
802
- if (isBrowser) {
803
- localStorage.setItem('human_behavior_session_id', newSessionId);
804
- }
805
-
806
- // Try to send rejected events with new session ID using beacon
807
- // sendBeacon returns true if the request was queued successfully
808
- this.api.sendBeaconEvents(this.rejectedEvents, newSessionId);
809
-
810
- // Clear rejected events and update session ID
811
- // Note: We can't verify if the beacon data was actually sent,
812
- // but we clear the events to prevent duplicate sending attempts
813
- this.rejectedEvents = [];
814
- this.sessionId = newSessionId;
815
- } catch (error) {
816
- logError('Failed to process rejected events:', error);
817
- } finally {
818
- this.isProcessingRejectedEvents = false;
819
- }
820
- }
821
-
822
804
  private async flush() {
823
805
  // Prevent concurrent flushes
824
806
  if (this.isProcessing || !this.initialized) {
@@ -835,20 +817,18 @@ export class HumanBehaviorTracker {
835
817
  if (eventsToProcess.length > 0) {
836
818
  logDebug('Flushing events:', eventsToProcess);
837
819
  try {
838
- await this.api.sendEvents(eventsToProcess, this.sessionId, this.endUserId!);
820
+ // Use chunked sending to handle large payloads
821
+ await this.api.sendEventsChunked(eventsToProcess, this.sessionId, this.endUserId!);
839
822
  } catch (error: any) {
840
- // If we get a 400 error, store events for retry
823
+ // Handle specific error types with graceful degradation
841
824
  if (error.message?.includes('ERROR: Session already completed')) {
842
- logInfo('Session expired, storing events for retry');
843
- this.rejectedEvents.push(...eventsToProcess);
844
- this.processRejectedEvents();
825
+ logWarn('Session expired, events will be lost');
826
+ } else if (error.message?.includes('413') || error.message?.includes('Content Too Large')) {
827
+ logWarn('Payload too large, events will be lost');
845
828
  } else if (error.message?.includes('ERR_BLOCKED_BY_CLIENT') ||
846
829
  error.message?.includes('Failed to fetch') ||
847
830
  error.message?.includes('NetworkError')) {
848
- // Handle ad blocker or network issues gracefully
849
- logWarn('Request blocked by ad blocker or network issue, storing events for retry');
850
- this.rejectedEvents.push(...eventsToProcess);
851
- // Don't process rejected events immediately to avoid spam
831
+ logWarn('Request blocked by ad blocker or network issue, events will be lost');
852
832
  } else {
853
833
  throw error;
854
834
  }
@@ -924,6 +904,56 @@ export class HumanBehaviorTracker {
924
904
  }
925
905
  }
926
906
 
907
+ /**
908
+ * Delete a cookie by setting its expiration date to the past
909
+ * @param name The name of the cookie to delete
910
+ */
911
+ private deleteCookie(name: string) {
912
+ if (!isBrowser) return;
913
+
914
+ try {
915
+ // Delete cookie by setting expiration to past
916
+ document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=Lax`;
917
+ logDebug(`Deleted cookie: ${name}`);
918
+ } catch (error) {
919
+ logError(`Failed to delete cookie: ${name}`, error);
920
+ }
921
+
922
+ // Also remove from localStorage
923
+ try {
924
+ localStorage.removeItem(name);
925
+ logDebug(`Removed from localStorage: ${name}`);
926
+ } catch (error) {
927
+ logError(`Failed to remove from localStorage: ${name}`, error);
928
+ }
929
+ }
930
+
931
+ /**
932
+ * Log out the current user by clearing all user-related data
933
+ * This will delete the user ID cookie, clear localStorage, and reset user properties
934
+ */
935
+ public logout(): void {
936
+ if (!isBrowser) return;
937
+
938
+ try {
939
+ // Clear user ID cookie and localStorage
940
+ const userIdCookieName = `human_behavior_end_user_id_${this.apiKey}`;
941
+ this.deleteCookie(userIdCookieName);
942
+
943
+ // Clear session data from localStorage
944
+ localStorage.removeItem('human_behavior_session_id');
945
+ localStorage.removeItem('human_behavior_last_activity');
946
+
947
+ // Reset user-related properties
948
+ this.endUserId = null;
949
+ this.userProperties = {};
950
+
951
+ logInfo('User logged out - cleared all user data and cookies');
952
+ } catch (error) {
953
+ logError('Error during logout:', error);
954
+ }
955
+ }
956
+
927
957
  /**
928
958
  * Start redaction functionality for sensitive input fields
929
959
  * @param options Optional configuration for redaction behavior
@@ -1005,8 +1035,8 @@ export class HumanBehaviorTracker {
1005
1035
  const recommendations: string[] = [];
1006
1036
  let blocked = false;
1007
1037
 
1008
- // Check if we have rejected events (might indicate blocking)
1009
- if (this.rejectedEvents.length > 0) {
1038
+ // Check if we have queued events (might indicate blocking)
1039
+ if (this.eventIngestionQueue.length > 0) {
1010
1040
  blocked = true;
1011
1041
  recommendations.push('Some requests may be blocked by ad blockers');
1012
1042
  }