UNPKG

humanbehavior-js

Version:

SDK for HumanBehavior session and event recording

509 lines 16.2 kB
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