humanbehavior-js
Version:
SDK for HumanBehavior session and event recording
509 lines • 16.2 kB
TypeScript
import { RedactionOptions } from './redact';
export declare class HumanBehaviorTracker {
private eventQueue;
private pendingCustomEvents;
private pendingLogs;
private pendingNetworkErrors;
private sessionId;
private windowId;
private _sessionActivityTimestamp;
private _sessionStartTimestamp;
private userProperties;
private isProcessing;
private flushInterval;
private readonly FLUSH_INTERVAL_MS;
private readonly MAX_QUEUE_SIZE;
private api;
private endUserId;
private apiKey;
private ingestionUrl;
private initialized;
initializationPromise: Promise<void> | null;
private monthlyLimitReached;
private redactionManager;
private propertyManager;
private isDomReady;
private requestQueue;
private domReadyHandlers;
private originalConsole;
private consoleTrackingEnabled;
private originalFetch;
private networkTrackingEnabled;
private enableConsoleTrackingFlag;
private enableNetworkTrackingFlag;
navigationTrackingEnabled: boolean;
private currentUrl;
private previousUrl;
private originalPushState;
private originalReplaceState;
private navigationListeners;
private _connectionBlocked;
private recordInstance;
private sessionStartTime;
private rrwebRecord;
private fullSnapshotTimeout;
private recordCanvas;
private isStarted;
private readonly minimumDurationMilliseconds;
private readonly _window_id_storage_key;
private readonly _primary_window_exists_storage_key;
private _isIdle;
private _lastActivityTimestamp;
private readonly IDLE_THRESHOLD_MS;
private rageClickTracker;
private readonly RAGE_CLICK_THRESHOLD_PX;
private readonly RAGE_CLICK_TIMEOUT_MS;
private readonly RAGE_CLICK_CLICK_COUNT;
private deadClickTracker;
private readonly DEAD_CLICK_SCROLL_THRESHOLD_MS;
private readonly DEAD_CLICK_SELECTION_THRESHOLD_MS;
private readonly DEAD_CLICK_MUTATION_THRESHOLD_MS;
private readonly DEAD_CLICK_ABSOLUTE_TIMEOUT_MS;
/**
* Check if the tracker has been started
*/
get isTrackerStarted(): boolean;
/**
* DOM ready detection - more aggressive
*/
private setupDomReadyHandler;
/**
* Called when DOM is ready - processes queued requests
*/
private onDomReady;
/**
* Queue a request until DOM is ready
*/
private queueRequest;
/**
* Process a request (called after DOM is ready)
*/
private processRequest;
/**
* Register a handler to be called when DOM is ready
*/
private registerDomReadyHandler;
/**
* Initialize the HumanBehavior tracker
* This is the main entry point - call this once per page
*/
static init(apiKey: string, options?: {
ingestionUrl?: string;
logLevel?: 'none' | 'error' | 'warn' | 'info' | 'debug';
redactFields?: string[];
redactionStrategy?: {
mode: 'privacy-first' | 'visibility-first';
unredactFields?: string[];
redactFields?: string[];
};
enableAutomaticTracking?: boolean;
suppressConsoleErrors?: boolean;
recordCanvas?: boolean;
enableAutomaticProperties?: boolean;
propertyDenylist?: string[];
automaticTrackingOptions?: {
trackButtons?: boolean;
trackLinks?: boolean;
trackForms?: boolean;
includeText?: boolean;
includeClasses?: boolean;
};
maxQueueSize?: number;
enableConsoleTracking?: boolean;
enableNetworkTracking?: boolean;
}): HumanBehaviorTracker;
constructor(apiKey: string | undefined, ingestionUrl?: string, options?: {
enableAutomaticProperties?: boolean;
propertyDenylist?: string[];
redactionStrategy?: {
mode: 'privacy-first' | 'visibility-first';
unredactFields?: string[];
redactFields?: string[];
};
redactFields?: string[];
maxQueueSize?: number;
enableConsoleTracking?: boolean;
enableNetworkTracking?: boolean;
});
private init;
/**
* ✅ FIXED: Wait for Kafka-based initialization to complete
*/
private ensureInitialized;
/**
* Setup navigation event tracking for SPA navigation
*/
private setupNavigationTracking;
/**
* Track navigation events and send custom events
*/
trackNavigationEvent(type: string, fromUrl: string, toUrl: string): Promise<void>;
trackPageView(url?: string): Promise<void>;
customEvent(eventName: string, properties?: Record<string, any>): Promise<void>;
/**
* Setup automatic tracking for buttons, links, and forms
*/
private setupAutomaticTracking;
/**
* Setup automatic button tracking
*/
private setupAutomaticButtonTracking;
/**
* Setup rage click detection
* Detects when user clicks the same area 3+ times within 1 second (within 30px radius)
* Similar to rage click detection
*/
private setupRageClickDetection;
/**
* Check if a click is part of a rage click pattern
* Returns true if 3+ clicks within 1 second and within 30px of each other
*/
private isRageClick;
/**
* Setup dead click detection
* Detects when user clicks on interactive elements that don't trigger any action
* Observes entire document for mutations, accepts false negatives on animated pages
*/
/**
* Check if an element is interactive (should respond to clicks)
*/
private isInteractiveElement;
/**
* Setup automatic link tracking
* TEMPORARILY DISABLED: Automatic custom event tracking
*/
private setupAutomaticLinkTracking;
/**
* Setup automatic form tracking
*/
private setupAutomaticFormTracking;
/**
* Cleanup navigation tracking
*/
private cleanupNavigationTracking;
static logToStorage(message: string): void;
/**
* Configure logging behavior for the SDK
* @param config Logger configuration options
*/
static configureLogging(config: {
level?: 'none' | 'error' | 'warn' | 'info' | 'debug';
enableConsole?: boolean;
enableStorage?: boolean;
}): void;
/**
* Enable console event tracking
*/
enableConsoleTracking(): void;
/**
* Enable network error tracking by intercepting the global fetch API
*/
enableNetworkTracking(): void;
/**
* Flush pending custom events (queued before 5 seconds)
*/
private flushPendingCustomEvents;
/**
* Flush pending logs (queued before 5 seconds)
*/
private flushPendingLogs;
/**
* Flush pending network errors (queued before 5 seconds)
*/
private flushPendingNetworkErrors;
/**
* Enable page load tracking - detects heavy page loads (>3 seconds)
*/
enablePageLoadTracking(): void;
/**
* Track heavy page loads using Performance API
*/
private trackPageLoad;
/**
* Check if network request should be skipped (SDK's own requests)
*/
private shouldSkipNetworkTracking;
/**
* Classify HTTP error status codes
*/
private classifyHttpError;
/**
* Classify network errors (CORS, timeouts, blocked requests, etc.)
*/
private classifyNetworkError;
/**
* Disable console event tracking
*/
disableConsoleTracking(): void;
private trackConsoleEvent;
/**
* Check if the actual caller (not SDK wrapper) is from SDK code
* Since we intercept console methods, the stack will always include SDK frames.
* We need to look at the actual caller frame (the one that called console.error/warn from user code).
*/
private isSDKStackFrame;
private setupPageUnloadHandler;
viewLogs(): void;
/**
* Add user identification information to the tracker
* If userId is not provided, will use userProperties.email as the userId (if present)
*/
identifyUser({ userProperties }: {
userProperties: Record<string, any>;
}): Promise<string>;
/**
* Get current user attributes
*/
getUserAttributes(): Record<string, any>;
start(): Promise<void>;
/**
* Manually trigger a FullSnapshot (for navigation events)
* Delays snapshot to avoid capturing mid-animation states
*/
private takeFullSnapshot;
stop(): Promise<void>;
/**
* Add an event to the ingestion queue
* Events are sent directly without processing to avoid corruption
*/
addEvent(event: any): Promise<void>;
/**
* Calculate session duration
* Uses most recent event timestamp minus session start timestamp
*/
private getSessionDuration;
/**
* Check if session duration is below minimum threshold
* Returns true if we should skip sending (session too short)
*/
private shouldSkipDueToMinimumDuration;
/**
* Flush events to the ingestion server
* Events are sent in chunks to handle large payloads efficiently
*/
private flushEvents;
/**
* Check if an event represents user interaction (not background DOM mutations)
*/
private isInteractiveEvent;
/**
* Update idle state based on event activity
* Also updates session activity timestamp to prevent premature session expiration
*/
private updateIdleState;
/**
* Add an event to the session recording queue
* These are typically FullSnapshots or IncrementalSnapshots
*/
addRecordingEvent(event: any): Promise<void>;
/**
* Check if sessionStorage is available and can be used
*/
private _canUseSessionStorage;
/**
* Get windowId from sessionStorage
* SessionStorage persists across page reloads but is unique per window/tab
*/
private _getWindowIdFromStorage;
/**
* Set windowId in sessionStorage
*/
private _setWindowIdInStorage;
/**
* Remove windowId from sessionStorage
*/
private _removeWindowIdFromStorage;
/**
* Check if primary_window_exists flag is set in sessionStorage
* This flag indicates if a window was opened as a new tab/window (not a reload)
*/
private _getPrimaryWindowExists;
/**
* Set primary_window_exists flag in sessionStorage
* This flag is set when DOM loads and cleared on beforeunload
*/
private _setPrimaryWindowExists;
/**
* Get or create windowId with multi-window detection
* - Reuses windowId on page reload (same tab)
* - Creates new windowId for new windows/tabs
*/
private getOrCreateWindowId;
/**
* Setup beforeunload listener to clear primary_window_exists flag
* This allows us to distinguish page reloads from new windows/tabs
*/
private setupWindowUnloadListener;
private setCookie;
getCookie(name: string): string | null;
/**
* Delete a cookie by setting its expiration date to the past
* @param name The name of the cookie to delete
*/
private deleteCookie;
/**
* Clear user data and reset session when user signs out of the site
* This should be called when a user logs out of your application to prevent
* data contamination between different users
*/
logout(): void;
/**
* Start redaction functionality for sensitive input fields
* @param options Optional configuration for redaction behavior
*/
redact(options?: RedactionOptions): Promise<void>;
/**
* Set specific fields to be redacted (for visibility-first mode)
* @param fields Array of CSS selectors for fields to redact
*/
setRedactedFields(fields: string[]): void;
/**
* Set specific fields to be unredacted (everything else stays redacted by rrweb)
* @param fields Array of CSS selectors for fields to unredact (e.g., ['#username', '#comment'])
*/
setUnredactedFields(fields: string[]): void;
private restartWithNewRedaction;
/**
* Check if any fields are currently unredacted
*/
hasUnredactedFields(): boolean;
/**
* Get the currently unredacted fields
*/
getUnredactedFields(): string[];
/**
* Remove specific fields from unredaction (they become redacted again)
* @param fields Array of CSS selectors for fields to redact
*/
redactFields(fields: string[]): void;
/**
* Clear all unredacted fields (everything becomes redacted again)
*/
clearUnredactedFields(): void;
/**
* Check and refresh session if expired (called before adding events)
* Uses in-memory state as source of truth
*/
private checkAndRefreshSession;
/**
* Get or create session ID with timeout checking
* Called once during initialization
* Uses in-memory state as source of truth
*/
private getOrCreateSessionId;
/**
* Get session data (check memory first, then localStorage)
*/
private getStoredSession;
/**
* Create a new session (update memory first, then persistence)
*/
private createNewSession;
/**
* Update session activity timestamp (update memory first, then persistence)
* Note: This is called by checkAndRefreshSession() for any event, not just user interactions
* For user interactions, updateIdleState() also updates this, keeping them in sync
*/
private updateSessionActivity;
/**
* Get the current session ID
*/
getSessionId(): string;
/**
* Get the current URL being tracked
*/
getCurrentUrl(): string;
/**
* Get current snapshot frequency info
* Uses configured values (5 minutes, 1000 events)
*/
getSnapshotFrequencyInfo(): {
sessionDuration: number;
currentInterval: number;
currentThreshold: number;
phase: string;
};
/**
* Test if the tracker can reach the ingestion server
*/
testConnection(): Promise<{
success: boolean;
error?: string;
}>;
/**
* Get connection status and recommendations
*/
getConnectionStatus(): {
blocked: boolean;
recommendations: string[];
};
/**
* Check if the current user is a preexisting user
* Returns true if the user has an existing endUserId cookie from a previous session
*/
isPreexistingUser(): boolean;
/**
* Get user information including whether they are preexisting
*/
getUserInfo(): {
endUserId: string | null;
sessionId: string;
isPreexistingUser: boolean;
initialized: boolean;
};
/**
* Set a session property that will be included in all events for this session
*/
setSessionProperty(key: string, value: any): void;
/**
* Set multiple session properties
*/
setSessionProperties(properties: Record<string, any>): void;
/**
* Get a session property
*/
getSessionProperty(key: string): any;
/**
* Remove a session property
*/
removeSessionProperty(key: string): void;
/**
* Set a user property that will be included in all events
*/
setUserProperty(key: string, value: any): void;
/**
* Set multiple user properties
*/
setUserProperties(properties: Record<string, any>): void;
/**
* Get a user property
*/
getUserProperty(key: string): any;
/**
* Remove a user property
*/
removeUserProperty(key: string): void;
/**
* Set a property only if it hasn't been set before
*/
setOnce(key: string, value: any, scope?: 'session' | 'user'): void;
/**
* Clear all session properties
*/
clearSessionProperties(): void;
/**
* Clear all user properties
*/
clearUserProperties(): void;
/**
* Get all properties for debugging
*/
getAllProperties(): {
automatic: Record<string, any>;
session: Record<string, any>;
user: Record<string, any>;
initial: Record<string, any>;
};
}
export default HumanBehaviorTracker;
//# sourceMappingURL=tracker.d.ts.map