humanbehavior-js 0.2.6 → 0.2.8

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.
@@ -109,6 +109,9 @@ declare class HumanBehaviorTracker {
109
109
  private originalReplaceState;
110
110
  private navigationListeners;
111
111
  private _connectionBlocked;
112
+ private recordInstance;
113
+ private frequencyUpdateInterval;
114
+ private sessionStartTime;
112
115
  /**
113
116
  * Initialize the HumanBehavior tracker
114
117
  * This is the main entry point - call this once per page
@@ -210,8 +213,9 @@ declare class HumanBehaviorTracker {
210
213
  */
211
214
  private deleteCookie;
212
215
  /**
213
- * Log out the current user by clearing all user-related data
214
- * This will delete the user ID cookie, clear localStorage, and reset user properties
216
+ * Clear user data and reset session when user signs out of the site
217
+ * This should be called when a user logs out of your application to prevent
218
+ * data contamination between different users
215
219
  */
216
220
  logout(): void;
217
221
  /**
@@ -240,6 +244,15 @@ declare class HumanBehaviorTracker {
240
244
  * Get the current URL being tracked
241
245
  */
242
246
  getCurrentUrl(): string;
247
+ /**
248
+ * Get current snapshot frequency info
249
+ */
250
+ getSnapshotFrequencyInfo(): {
251
+ sessionDuration: number;
252
+ currentInterval: number;
253
+ currentThreshold: number;
254
+ phase: string;
255
+ };
243
256
  /**
244
257
  * Test if the tracker can reach the ingestion server
245
258
  */
@@ -297,6 +310,25 @@ declare class HumanBehaviorAPI {
297
310
  }>): Promise<any>;
298
311
  }
299
312
 
313
+ /**
314
+ * Server-side utilities for HumanBehavior SDK
315
+ */
316
+ interface ServerSideUserData {
317
+ email: string;
318
+ name?: string;
319
+ image?: string;
320
+ provider?: string;
321
+ [key: string]: any;
322
+ }
323
+ /**
324
+ * Identify user from server-side (NextAuth, Firebase, etc.)
325
+ * Use this in your auth events to track users immediately on sign-in
326
+ */
327
+ declare function identifyUser(userData: ServerSideUserData, apiKey: string): Promise<{
328
+ success: boolean;
329
+ error?: string;
330
+ }>;
331
+
300
332
  declare enum LogLevel {
301
333
  NONE = 0,
302
334
  ERROR = 1,
@@ -330,5 +362,5 @@ declare const logWarn: (message: string, ...args: any[]) => void;
330
362
  declare const logInfo: (message: string, ...args: any[]) => void;
331
363
  declare const logDebug: (message: string, ...args: any[]) => void;
332
364
 
333
- export { HumanBehaviorAPI, HumanBehaviorTracker, LogLevel, MAX_CHUNK_SIZE_BYTES, RedactionManager, HumanBehaviorTracker as default, isChunkSizeExceeded, logDebug, logError, logInfo, logWarn, logger, redactionManager, splitLargeEvent, validateSingleEventSize };
334
- export type { LoggerConfig, RedactionOptions };
365
+ export { HumanBehaviorAPI, HumanBehaviorTracker, LogLevel, MAX_CHUNK_SIZE_BYTES, RedactionManager, HumanBehaviorTracker as default, identifyUser, isChunkSizeExceeded, logDebug, logError, logInfo, logWarn, logger, redactionManager, splitLargeEvent, validateSingleEventSize };
366
+ export type { LoggerConfig, RedactionOptions, ServerSideUserData };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanbehavior-js",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "SDK for HumanBehavior session and event recording",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
package/src/index.ts CHANGED
@@ -13,6 +13,9 @@ export * from './api';
13
13
  // Export redaction functionality
14
14
  export * from './redact';
15
15
 
16
+ // Export server-side utilities
17
+ export * from './server';
18
+
16
19
  // Export logger functionality
17
20
  export * from './utils/logger';
18
21
 
@@ -11,6 +11,7 @@ interface HumanBehaviorInterface {
11
11
  addUserInfo: ({ userId, userProperties }: { userId?: string, userProperties: Record<string, any> }) => Promise<string>;
12
12
  start: () => void;
13
13
  stop: () => void;
14
+ logout: () => void;
14
15
  viewLogs: () => void;
15
16
  }
16
17
 
@@ -173,6 +174,7 @@ const defaultImplementation: HumanBehaviorInterface = {
173
174
  },
174
175
  start: () => {},
175
176
  stop: () => {},
177
+ logout: () => {},
176
178
  viewLogs: () => {},
177
179
  };
178
180
 
@@ -195,6 +197,9 @@ const createQueuingImplementation = (queueEvent: (event: any) => void): HumanBeh
195
197
  stop: () => {
196
198
  // Stop is a no-op when not initialized
197
199
  },
200
+ logout: () => {
201
+ // Logout is a no-op when not initialized
202
+ },
198
203
  viewLogs: () => {
199
204
  logWarn('Logs are not available until HumanBehaviorTracker is initialized');
200
205
  }
package/src/server.ts ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Server-side utilities for HumanBehavior SDK
3
+ */
4
+
5
+ export interface ServerSideUserData {
6
+ email: string;
7
+ name?: string;
8
+ image?: string;
9
+ provider?: string;
10
+ [key: string]: any; // Allow additional properties
11
+ }
12
+
13
+ /**
14
+ * Identify user from server-side (NextAuth, Firebase, etc.)
15
+ * Use this in your auth events to track users immediately on sign-in
16
+ */
17
+ export async function identifyUser(
18
+ userData: ServerSideUserData,
19
+ apiKey: string
20
+ ): Promise<{ success: boolean; error?: string }> {
21
+ try {
22
+ const response = await fetch('https://ingest.humanbehavior.co/api/ingestion/user', {
23
+ method: 'POST',
24
+ headers: {
25
+ 'Content-Type': 'application/json',
26
+ 'Authorization': `Bearer ${apiKey}`
27
+ },
28
+ body: JSON.stringify({
29
+ userId: userData.email,
30
+ userAttributes: userData,
31
+ sessionId: null, // Will be set by client-side session recording
32
+ posthogName: userData.email || userData.name || null // Update posthogName with email
33
+ })
34
+ });
35
+
36
+ if (!response.ok) {
37
+ const errorText = await response.text();
38
+ return {
39
+ success: false,
40
+ error: `Failed to identify user: ${response.status} ${response.statusText} - ${errorText}`
41
+ };
42
+ }
43
+
44
+ return { success: true };
45
+ } catch (error) {
46
+ return {
47
+ success: false,
48
+ error: error instanceof Error ? error.message : 'Unknown error'
49
+ };
50
+ }
51
+ }
package/src/tracker.ts CHANGED
@@ -47,6 +47,9 @@ export class HumanBehaviorTracker {
47
47
  private originalReplaceState: typeof history.replaceState | null = null;
48
48
  private navigationListeners: Array<() => void> = [];
49
49
  private _connectionBlocked: boolean = false;
50
+ private recordInstance: any = null;
51
+ private frequencyUpdateInterval: any = null;
52
+ private sessionStartTime: number = Date.now();
50
53
 
51
54
  /**
52
55
  * Initialize the HumanBehavior tracker
@@ -759,37 +762,70 @@ export class HumanBehaviorTracker {
759
762
  // Enable console tracking
760
763
  this.enableConsoleTracking();
761
764
 
762
- // Start recording with redaction enabled
763
- rrweb.record({
764
- emit: (event) => {
765
- // Add additional validation for FullSnapshot events
766
- if (event.type === 2) { // FullSnapshot event
767
- if (!event.data || !event.data.node) {
768
- logWarn('rrweb generated malformed FullSnapshot event:', {
769
- hasData: !!event.data,
770
- hasNode: !!(event.data && event.data.node),
771
- dataType: typeof event.data,
772
- eventType: event.type,
773
- timestamp: event.timestamp
774
- });
775
- // Don't skip - let the addEvent method handle it
776
- } else {
777
- logDebug('Valid FullSnapshot event received from rrweb');
778
- }
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;
776
+
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');
779
810
  }
780
- this.addEvent(event);
781
- },
782
- inlineStylesheet: true,
783
- recordCanvas: true,
784
- collectFonts: true,
785
- inlineImages: true,
786
- blockClass: 'rr-block',
787
- ignoreClass: 'rr-ignore',
788
- maskTextClass: 'rr-ignore',
789
- // Add more robust configuration
790
- checkoutEveryNms: 5000, // Take full snapshot every 5 seconds
791
- checkoutEveryNth: 100 // Take full snapshot every 100 events
792
- });
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;
793
829
  }
794
830
 
795
831
  public async stop() {
@@ -801,6 +837,18 @@ export class HumanBehaviorTracker {
801
837
  this.flushInterval = null;
802
838
  }
803
839
 
840
+ // Cleanup adaptive snapshot intervals
841
+ if (this.frequencyUpdateInterval) {
842
+ clearInterval(this.frequencyUpdateInterval);
843
+ this.frequencyUpdateInterval = null;
844
+ }
845
+
846
+ // Stop rrweb recording
847
+ if (this.recordInstance) {
848
+ this.recordInstance();
849
+ this.recordInstance = null;
850
+ }
851
+
804
852
  // Disable console tracking
805
853
  this.disableConsoleTracking();
806
854
 
@@ -960,8 +1008,9 @@ export class HumanBehaviorTracker {
960
1008
  }
961
1009
 
962
1010
  /**
963
- * Log out the current user by clearing all user-related data
964
- * This will delete the user ID cookie, clear localStorage, and reset user properties
1011
+ * Clear user data and reset session when user signs out of the site
1012
+ * This should be called when a user logs out of your application to prevent
1013
+ * data contamination between different users
965
1014
  */
966
1015
  public logout(): void {
967
1016
  if (!isBrowser) return;
@@ -979,7 +1028,14 @@ export class HumanBehaviorTracker {
979
1028
  this.endUserId = null;
980
1029
  this.userProperties = {};
981
1030
 
982
- logInfo('User logged out - cleared all user data and cookies');
1031
+ // Generate a new session ID for the next user
1032
+ this.sessionId = uuidv1();
1033
+ if (isBrowser) {
1034
+ localStorage.setItem('human_behavior_session_id', this.sessionId);
1035
+ localStorage.setItem('human_behavior_last_activity', Date.now().toString());
1036
+ }
1037
+
1038
+ logInfo('User logged out - cleared all user data and started fresh session');
983
1039
  } catch (error) {
984
1040
  logError('Error during logout:', error);
985
1041
  }
@@ -1041,6 +1097,41 @@ export class HumanBehaviorTracker {
1041
1097
  return this.currentUrl;
1042
1098
  }
1043
1099
 
1100
+ /**
1101
+ * Get current snapshot frequency info
1102
+ */
1103
+ public getSnapshotFrequencyInfo(): {
1104
+ sessionDuration: number;
1105
+ currentInterval: number;
1106
+ currentThreshold: number;
1107
+ phase: string;
1108
+ } {
1109
+ 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
+
1127
+ return {
1128
+ sessionDuration,
1129
+ currentInterval: interval,
1130
+ currentThreshold: threshold,
1131
+ phase
1132
+ };
1133
+ }
1134
+
1044
1135
  /**
1045
1136
  * Test if the tracker can reach the ingestion server
1046
1137
  */