humanbehavior-js 0.5.33 → 0.5.34
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.
- package/package.json +1 -1
- package/packages/browser/dist/cjs/index.js +1 -1
- package/packages/browser/dist/cjs/index.js.map +1 -1
- package/packages/browser/dist/esm/index.js +1 -1
- package/packages/browser/dist/esm/index.js.map +1 -1
- package/packages/browser/dist/index.min.js +1 -1
- package/packages/browser/dist/index.min.js.map +1 -1
- package/packages/core/dist/index.js +1 -1
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/dist/index.mjs +1 -1
- package/packages/core/dist/index.mjs.map +1 -1
- package/packages/core/dist/tracker.d.ts.map +1 -1
- package/packages/react/dist/index.js +1 -1
- package/packages/react/dist/index.js.map +1 -1
- package/packages/react/dist/index.mjs +1 -1
- package/packages/react/dist/index.mjs.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../src/utils/logger.ts","../src/api.ts","../src/redact.ts","../src/utils/property-detector.ts","../src/utils/property-manager.ts","../src/tracker.ts","../src/utils/global-tracker.ts"],"sourcesContent":["export enum LogLevel {\n NONE = 0,\n ERROR = 1,\n WARN = 2,\n INFO = 3,\n DEBUG = 4\n}\n\nexport interface LoggerConfig {\n level: LogLevel;\n enableConsole: boolean;\n enableStorage: boolean;\n}\n\nclass Logger {\n private config: LoggerConfig = {\n level: LogLevel.ERROR, // Default to only errors in production\n enableConsole: true,\n enableStorage: false\n };\n\n private isBrowser = typeof window !== 'undefined';\n\n constructor(config?: Partial<LoggerConfig>) {\n if (config) {\n this.config = { ...this.config, ...config };\n }\n }\n\n setConfig(config: Partial<LoggerConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n private shouldLog(level: LogLevel): boolean {\n return level <= this.config.level;\n }\n\n private formatMessage(level: string, message: string, ...args: any[]): string {\n const timestamp = new Date().toISOString();\n return `[HumanBehavior ${level}] ${timestamp}: ${message}`;\n }\n\n error(message: string, ...args: any[]): void {\n if (!this.shouldLog(LogLevel.ERROR)) return;\n \n const formattedMessage = this.formatMessage('ERROR', message);\n \n if (this.config.enableConsole) {\n console.error(formattedMessage, ...args);\n }\n \n if (this.config.enableStorage && this.isBrowser) {\n this.logToStorage(formattedMessage, args);\n }\n }\n\n warn(message: string, ...args: any[]): void {\n if (!this.shouldLog(LogLevel.WARN)) return;\n \n const formattedMessage = this.formatMessage('WARN', message);\n \n if (this.config.enableConsole) {\n console.warn(formattedMessage, ...args);\n }\n \n if (this.config.enableStorage && this.isBrowser) {\n this.logToStorage(formattedMessage, args);\n }\n }\n\n info(message: string, ...args: any[]): void {\n if (!this.shouldLog(LogLevel.INFO)) return;\n \n const formattedMessage = this.formatMessage('INFO', message);\n \n if (this.config.enableConsole) {\n console.log(formattedMessage, ...args);\n }\n \n if (this.config.enableStorage && this.isBrowser) {\n this.logToStorage(formattedMessage, args);\n }\n }\n\n debug(message: string, ...args: any[]): void {\n if (!this.shouldLog(LogLevel.DEBUG)) return;\n \n const formattedMessage = this.formatMessage('DEBUG', message);\n \n if (this.config.enableConsole) {\n console.log(formattedMessage, ...args);\n }\n \n if (this.config.enableStorage && this.isBrowser) {\n this.logToStorage(formattedMessage, args);\n }\n }\n\n private logToStorage(message: string, args: any[]): void {\n try {\n const logs = JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');\n const logEntry = {\n message,\n args: args.length > 0 ? args : undefined,\n timestamp: Date.now()\n };\n logs.push(logEntry);\n \n // Keep only last 1000 logs to prevent storage bloat\n if (logs.length > 1000) {\n logs.splice(0, logs.length - 1000);\n }\n \n localStorage.setItem('human_behavior_logs', JSON.stringify(logs));\n } catch (e) {\n // Silently fail if storage is not available\n }\n }\n\n getLogs(): any[] {\n if (!this.isBrowser) return [];\n \n try {\n return JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');\n } catch (e) {\n return [];\n }\n }\n\n clearLogs(): void {\n if (this.isBrowser) {\n localStorage.removeItem('human_behavior_logs');\n }\n }\n}\n\n// Create singleton instance\nexport const logger = new Logger();\n\n// Export convenience methods\nexport const logError = (message: string, ...args: any[]) => logger.error(message, ...args);\nexport const logWarn = (message: string, ...args: any[]) => logger.warn(message, ...args);\nexport const logInfo = (message: string, ...args: any[]) => logger.info(message, ...args);\nexport const logDebug = (message: string, ...args: any[]) => logger.debug(message, ...args); ","import { logError, logInfo, logDebug, logWarn } from './utils/logger';\n\nexport const MAX_CHUNK_SIZE_BYTES = 1024 * 1024; // 1MB chunk size - more conservative\n\nexport function isChunkSizeExceeded(currentChunk: any[], newEvent: any, sessionId: string): boolean {\n const nextChunkSize = new TextEncoder().encode(JSON.stringify({\n sessionId,\n events: [...currentChunk, newEvent]\n })).length;\n \n return nextChunkSize > MAX_CHUNK_SIZE_BYTES;\n}\n\nexport function validateSingleEventSize(event: any, sessionId: string): void {\n const singleEventSize = new TextEncoder().encode(JSON.stringify({\n sessionId,\n events: [event]\n })).length;\n\n if (singleEventSize > MAX_CHUNK_SIZE_BYTES) {\n // Instead of throwing, log a warning and suggest reducing event size\n logWarn(`Single event size (${singleEventSize} bytes) exceeds maximum chunk size (${MAX_CHUNK_SIZE_BYTES} bytes). Consider reducing event data size.`);\n }\n}\n\n\n\n\n\nexport function splitLargeEvent(event: any, sessionId: string): any[] {\n // ✅ SIMPLE VALIDATION\n if (!event || typeof event !== 'object') {\n return [];\n }\n \n const eventSize = new TextEncoder().encode(JSON.stringify({\n sessionId,\n events: [event]\n })).length;\n\n if (eventSize <= MAX_CHUNK_SIZE_BYTES) {\n return [event];\n }\n\n // If event is too large, try to split it by removing large properties\n const simplifiedEvent = { ...event };\n \n // Remove potentially large properties\n const largeProperties = ['screenshot', 'html', 'dom', 'fullText', 'innerHTML', 'outerHTML'];\n largeProperties.forEach(prop => {\n if (simplifiedEvent[prop]) {\n delete simplifiedEvent[prop];\n }\n });\n\n // Check if simplified event is now small enough\n const simplifiedSize = new TextEncoder().encode(JSON.stringify({\n sessionId,\n events: [simplifiedEvent]\n })).length;\n\n if (simplifiedSize <= MAX_CHUNK_SIZE_BYTES) {\n return [simplifiedEvent];\n }\n\n // If still too large, create a minimal event\n const minimalEvent = {\n type: event.type,\n timestamp: event.timestamp,\n url: event.url,\n pathname: event.pathname,\n // Keep only essential properties\n ...Object.fromEntries(\n Object.entries(event).filter(([key, value]) => \n !largeProperties.includes(key) && \n typeof value !== 'object' && \n typeof value !== 'string' || \n (typeof value === 'string' && value.length < 1000)\n )\n )\n };\n\n return [minimalEvent];\n}\n\nexport class HumanBehaviorAPI {\n private apiKey: string;\n private baseUrl: string;\n private monthlyLimitReached: boolean = false;\n\n constructor({ apiKey, ingestionUrl }: { apiKey: string, ingestionUrl: string }) {\n this.apiKey = apiKey;\n this.baseUrl = ingestionUrl;\n }\n\n private checkMonthlyLimit(): boolean {\n if (this.monthlyLimitReached) {\n return false;\n }\n return true;\n }\n\n public async init(sessionId: string, userId: string | null) {\n // Check if monthly limit is already reached - silently skip if so\n if (!this.checkMonthlyLimit()) {\n // Silently return success to avoid any errors\n return {\n sessionId: sessionId,\n endUserId: userId\n };\n }\n\n // Get current page URL and referrer if in browser environment\n let entryURL = null;\n let referrer = null;\n \n if (typeof window !== 'undefined') {\n entryURL = window.location.href;\n referrer = document.referrer;\n }\n\n logInfo('API init called with:', { sessionId, userId, entryURL, referrer, baseUrl: this.baseUrl });\n\n try {\n const response = await fetch(`${this.baseUrl}/api/ingestion/init`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Referer': referrer || ''\n },\n body: JSON.stringify({\n sessionId: sessionId,\n endUserId: userId,\n entryURL: entryURL,\n referrer: referrer\n })\n });\n\n logInfo('API init response status:', response.status);\n\n if (!response.ok) {\n if (response.status === 429) {\n this.monthlyLimitReached = true;\n // Silently return success to avoid any errors\n return {\n sessionId: sessionId,\n endUserId: userId\n };\n }\n const errorText = await response.text();\n logError('API init failed:', response.status, errorText);\n throw new Error(`Failed to initialize ingestion: ${response.statusText} - ${errorText}`);\n } \n\n const responseJson = await response.json();\n \n // Check for monthly limit flag in successful response\n if (responseJson.monthlyLimitReached === true) {\n this.monthlyLimitReached = true;\n logInfo('Monthly limit reached detected from server response');\n }\n \n logInfo('API init success:', responseJson);\n return {\n sessionId: responseJson.sessionId,\n endUserId: responseJson.endUserId\n }\n } catch (error) {\n logError('API init error:', error);\n throw error;\n }\n }\n\n /**\n * Server detects IP from HTTP requests automatically\n */\n\n async sendEvents(events: any[], sessionId: string, userId: string) {\n // ✅ SIMPLE VALIDATION FOR ALL EVENTS\n const validEvents = events.filter(event => event && typeof event === 'object');\n \n const response = await fetch(`${this.baseUrl}/api/ingestion/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify({\n sessionId,\n events: validEvents,\n endUserId: userId\n })\n });\n \n if (!response.ok) {\n if (response.status === 429) {\n this.monthlyLimitReached = true;\n throw new Error(`429: Monthly video processing limit reached`);\n }\n throw new Error(`Failed to send events: ${response.statusText}`);\n }\n \n // Check for monthly limit flag in successful response\n const responseJson = await response.json();\n if (responseJson.monthlyLimitReached === true) {\n this.monthlyLimitReached = true;\n logInfo('Monthly limit reached detected from events response');\n }\n }\n \n async sendEventsChunked(events: any[], sessionId: string, userId?: string) {\n // Check if monthly limit is already reached - silently skip if so\n if (!this.checkMonthlyLimit()) {\n // Silently return success to avoid any errors\n return [];\n }\n try {\n const results = [];\n let currentChunk: any[] = [];\n \n for (const event of events) {\n // ✅ SIMPLE VALIDATION FOR ALL EVENTS\n if (!event || typeof event !== 'object') {\n continue;\n }\n \n if (isChunkSizeExceeded(currentChunk, event, sessionId)) {\n // If current chunk is not empty, send it first\n if (currentChunk.length > 0) {\n logDebug(`[SDK] Sending chunk with ${currentChunk.length} events`);\n const response = await fetch(`${this.baseUrl}/api/ingestion/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify({\n sessionId,\n events: currentChunk,\n endUserId: userId\n })\n });\n \n if (!response.ok) {\n if (response.status === 429) {\n this.monthlyLimitReached = true;\n // Silently skip this chunk\n return results.flat();\n }\n throw new Error(`Failed to send events: ${response.statusText}`);\n }\n \n const responseJson = await response.json();\n \n // Check for monthly limit flag in successful response\n if (responseJson.monthlyLimitReached === true) {\n this.monthlyLimitReached = true;\n logInfo('Monthly limit reached detected from chunked events response');\n }\n \n results.push(responseJson);\n currentChunk = [];\n }\n\n // Handle large events by splitting them\n const splitEvents = splitLargeEvent(event, sessionId);\n \n // Start new chunk with the split events\n currentChunk = splitEvents;\n } else {\n // Add event to current chunk\n currentChunk.push(event);\n }\n }\n \n // Send any remaining events\n if (currentChunk.length > 0) {\n logDebug(`[SDK] Sending final chunk with ${currentChunk.length} events`);\n const response = await fetch(`${this.baseUrl}/api/ingestion/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify({\n sessionId,\n events: currentChunk,\n endUserId: userId\n })\n });\n \n if (!response.ok) {\n if (response.status === 429) {\n this.monthlyLimitReached = true;\n // Silently skip this chunk\n return results.flat();\n }\n throw new Error(`Failed to send events: ${response.statusText}`);\n }\n \n const responseJson = await response.json();\n \n // Check for monthly limit flag in successful response\n if (responseJson.monthlyLimitReached === true) {\n this.monthlyLimitReached = true;\n logInfo('Monthly limit reached detected from final chunked events response');\n }\n \n results.push(responseJson);\n }\n \n return results.flat();\n } catch (error) {\n logError('Error sending events:', error);\n throw error;\n }\n }\n\n async sendUserData(userId: string, userData: Record<string, any>, sessionId: string) {\n try {\n const payload = {\n userId: userId,\n userAttributes: userData,\n sessionId: sessionId,\n posthogName: userData.email || userData.name || null // Update user name with email\n };\n \n logDebug('Sending user data to server:', payload);\n \n const response = await fetch(`${this.baseUrl}/api/ingestion/user`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify(payload)\n });\n \n if (!response.ok) {\n throw new Error(`Failed to send user data: ${response.statusText} with API key: ${this.apiKey}`);\n }\n \n const result = await response.json();\n logDebug('Server response:', result);\n return result;\n } catch (error) {\n logError('Error sending user data:', error);\n throw error;\n }\n }\n\n async sendUserAuth(userId: string, userData: Record<string, any>, sessionId: string, authFields: string[]) {\n try {\n const response = await fetch(`${this.baseUrl}/api/ingestion/user/auth`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify({\n userId: userId,\n userAttributes: userData,\n sessionId: sessionId,\n authFields: authFields\n })\n });\n \n if (!response.ok) {\n throw new Error(`Failed to authenticate user: ${response.statusText} with API key: ${this.apiKey}`);\n }\n // Returns: { success: true, message: '...', userId: '...' }\n return await response.json();\n } catch (error) {\n logError('Error authenticating user:', error);\n throw error;\n }\n }\n\n public sendBeaconEvents(events: any[], sessionId: string) {\n // Create JSON payload that matches the server's expected format\n const payload = {\n sessionId: sessionId,\n events: events,\n endUserId: null, // Beacon doesn't have user context\n apiKey: this.apiKey // Include API key in body since beacon can't use headers\n };\n\n // Convert to Blob for sendBeacon\n const blob = new Blob([JSON.stringify(payload)], {\n type: 'application/json'\n });\n\n const success = navigator.sendBeacon(\n `${this.baseUrl}/api/ingestion/events`, \n blob\n );\n\n return success;\n }\n\n async sendCustomEvent(sessionId: string, eventName: string, eventProperties?: Record<string, any>, endUserId?: string | null) {\n logInfo('[SDK] Sending custom event', { sessionId, eventName, eventProperties, endUserId });\n try {\n const response = await fetch(`${this.baseUrl}/api/ingestion/customEvent`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify({\n sessionId: sessionId,\n eventName: eventName,\n eventProperties: eventProperties || {},\n endUserId: endUserId || null\n })\n });\n \n logInfo('[SDK] Custom event response', { status: response.status, statusText: response.statusText });\n \n if (!response.ok) {\n const errorText = await response.text();\n logError('[SDK] Failed to send custom event', { status: response.status, statusText: response.statusText, errorText });\n throw new Error(`Failed to send custom event: ${response.status} ${response.statusText} - ${errorText}`);\n }\n \n const json = await response.json();\n logDebug('[SDK] Custom event success', json);\n return json;\n } catch (error) {\n logError('[SDK] Error sending custom event', error, { sessionId, eventName, eventProperties });\n throw error;\n }\n }\n\n async sendCustomEventBatch(sessionId: string, events: Array<{ eventName: string; eventProperties?: Record<string, any> }>, endUserId?: string | null) {\n try {\n const response = await fetch(`${this.baseUrl}/api/ingestion/customEvent/batch`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify({\n sessionId: sessionId,\n events: events,\n endUserId: endUserId || null\n })\n });\n \n if (!response.ok) {\n throw new Error(`Failed to send custom event batch: ${response.statusText}`);\n }\n \n return await response.json();\n } catch (error) {\n logError('Error sending custom event batch:', error);\n throw error;\n }\n }\n}\n","// Simplified redaction functionality for HumanBehavior SDK\n// Since rrweb auto-redacts all input fields by default, this module only handles\n// selectively unredacting specific fields (except passwords which remain protected)\n\nimport { logDebug, logWarn } from './utils/logger';\n\n// Check if we're in a browser environment\nconst isBrowser = typeof window !== 'undefined';\n\nexport interface RedactionOptions {\n redactedText?: string;\n excludeSelectors?: string[];\n userFields?: string[]; // Fields that the user wants to unredact (legacy)\n redactionStrategy?: {\n mode: 'privacy-first' | 'visibility-first';\n unredactFields?: string[]; // Fields to make visible (when mode: 'privacy-first')\n redactFields?: string[]; // Fields to hide (when mode: 'visibility-first')\n };\n legacyRedactFields?: string[]; // For backward compatibility\n}\n\nexport class RedactionManager {\n private redactedText: string = '[REDACTED]';\n private unredactedFields: Set<string> = new Set(); // Fields that user wants to unredact\n private redactedFields: Set<string> = new Set(); // Fields that user wants to redact\n private redactionMode: 'privacy-first' | 'visibility-first' = 'privacy-first';\n private excludeSelectors: string[] = [\n '[data-no-redact=\"true\"]',\n '.human-behavior-no-redact'\n ];\n\n constructor(options?: RedactionOptions) {\n if (options?.redactedText) {\n this.redactedText = options.redactedText;\n }\n if (options?.excludeSelectors) {\n this.excludeSelectors = [...this.excludeSelectors, ...options.excludeSelectors];\n }\n \n // Handle new redaction strategy\n if (options?.redactionStrategy) {\n this.redactionMode = options.redactionStrategy.mode;\n // debug removed\n \n if (this.redactionMode === 'privacy-first') {\n // Privacy-first: everything redacted by default, unredact specific fields\n if (options.redactionStrategy.unredactFields) {\n this.setFieldsToUnredact(options.redactionStrategy.unredactFields);\n }\n } else {\n // Visibility-first: everything visible by default, redact specific fields\n // Default to only redacting passwords if no specific fields provided\n // Support zero-config authoring: allow data-hb-redact=\"true\" marks\n const defaultMarks = ['input[type=\"password\"]', '[data-hb-redact=\"true\"]'];\n const fieldsToRedact = options.redactionStrategy.redactFields && options.redactionStrategy.redactFields.length > 0\n ? options.redactionStrategy.redactFields\n : defaultMarks;\n this.setFieldsToRedact(fieldsToRedact);\n }\n }\n \n // Handle legacy redactFields (backward compatibility)\n if (options?.legacyRedactFields) {\n this.setFieldsToUnredact(options.legacyRedactFields);\n }\n \n // Handle legacy userFields\n if (options?.userFields) {\n this.setFieldsToUnredact(options.userFields);\n }\n }\n\n /**\n * Set specific fields to be redacted (for visibility-first mode)\n * @param fields Array of CSS selectors for fields to redact\n */\n public setFieldsToRedact(fields: string[]): void {\n this.redactedFields.clear();\n \n // Always include password fields in redacted list\n const passwordFields = [\n 'input[type=\"password\"]',\n 'input[type=\"password\" i]',\n '[type=\"password\"]',\n '[type=\"password\" i]'\n ];\n \n // Add password fields and user-specified fields\n [...passwordFields, ...fields].forEach(field => {\n this.redactedFields.add(field);\n });\n \n if (this.redactedFields.size > 0) {\n logDebug(`Redaction: Active for ${this.redactedFields.size} field(s):`, Array.from(this.redactedFields));\n } else {\n logDebug('Redaction: No fields to redact');\n }\n \n this.applyRedactionClasses();\n }\n\n /**\n * Set specific fields to be unredacted (everything else stays redacted by rrweb)\n * @param fields Array of CSS selectors for fields to unredact\n */\n public setFieldsToUnredact(fields: string[]): void {\n this.unredactedFields.clear();\n \n // Filter out password fields (they cannot be unredacted)\n const validFields = fields.filter(field => {\n const isPasswordField = this.isPasswordSelector(field);\n if (isPasswordField) {\n logWarn(`Cannot unredact password field: ${field} - Password fields are always protected`);\n return false;\n }\n return true;\n });\n \n validFields.forEach(field => this.unredactedFields.add(field));\n \n if (validFields.length > 0) {\n logDebug(`Unredaction: Active for ${validFields.length} field(s):`, validFields);\n } else {\n logDebug('Unredaction: No valid fields to unredact');\n }\n \n this.applyUnredactionClasses();\n }\n\n /**\n * Remove specific fields from unredaction (they become redacted again)\n * @param fields Array of CSS selectors for fields to redact\n */\n public redactFields(fields: string[]): void {\n fields.forEach(field => {\n this.unredactedFields.delete(field);\n });\n \n if (this.unredactedFields.size > 0) {\n logDebug(`Unredaction: Removed ${fields.length} field(s), ${this.unredactedFields.size} remaining:`, Array.from(this.unredactedFields));\n } else {\n logDebug('Unredaction: All fields redacted');\n }\n \n this.applyUnredactionClasses();\n }\n\n /**\n * Clear all unredacted fields (everything becomes redacted again)\n */\n public clearUnredactedFields(): void {\n this.unredactedFields.clear();\n logDebug('Unredaction: All fields cleared, everything redacted');\n \n this.removeUnredactionClasses();\n }\n\n /**\n * Check if any fields are currently unredacted\n */\n public hasUnredactedFields(): boolean {\n return this.unredactedFields.size > 0;\n }\n\n /**\n * Get the current redaction mode\n */\n public getRedactionMode(): 'privacy-first' | 'visibility-first' {\n return this.redactionMode;\n }\n\n /**\n * Get the currently unredacted fields\n */\n public getUnredactedFields(): string[] {\n return Array.from(this.unredactedFields);\n }\n\n /**\n * Get CSS selectors for rrweb masking configuration\n * Returns null if no fields are unredacted (everything stays redacted)\n */\n public getMaskTextSelector(): string | null {\n if (this.redactionMode === 'privacy-first') {\n // Privacy-first: mask everything except unredacted fields\n if (this.unredactedFields.size === 0) {\n return null; // Everything stays redacted\n }\n return Array.from(this.unredactedFields).join(',');\n } else {\n // Visibility-first: mask only redacted fields\n if (this.redactedFields.size === 0) {\n return null; // Nothing to redact\n }\n return Array.from(this.redactedFields).join(',');\n }\n }\n\n /**\n * Apply redaction classes to DOM elements (for visibility-first mode)\n * Adds 'rr-mask' class to elements that should be redacted\n */\n public applyRedactionClasses(): void {\n if (this.redactedFields.size === 0) {\n return;\n }\n\n // Check if DOM is ready\n if (typeof document === 'undefined' || document.readyState === 'loading') {\n logDebug('DOM not ready, deferring redaction class application');\n return;\n }\n\n //console.log('🔍 Applying redaction classes to fields:', Array.from(this.redactedFields));\n\n // Add 'rr-mask' class to redacted elements\n this.redactedFields.forEach(selector => {\n try {\n const elements = document.querySelectorAll(selector);\n elements.forEach(element => {\n if (element && element.classList) {\n element.classList.add('rr-mask');\n }\n });\n logDebug(`Added rr-mask class to ${elements.length} element(s) for selector: ${selector}`);\n } catch (e) {\n logWarn(`Invalid selector: ${selector}`);\n }\n });\n }\n\n /**\n * Apply unredaction classes to DOM elements\n * Removes 'rr-mask' class from elements that should be unredacted\n */\n public applyUnredactionClasses(): void {\n if (this.unredactedFields.size === 0) {\n return;\n }\n\n // Check if DOM is ready\n if (typeof document === 'undefined' || document.readyState === 'loading') {\n logDebug('DOM not ready, deferring unredaction class application');\n return;\n }\n\n // Remove 'rr-mask' class from unredacted elements\n this.unredactedFields.forEach(selector => {\n try {\n const elements = document.querySelectorAll(selector);\n elements.forEach(element => {\n if (element && element.classList) {\n element.classList.remove('rr-mask');\n }\n });\n logDebug(`Removed rr-mask class from ${elements.length} element(s) for selector: ${selector}`);\n } catch (e) {\n logWarn(`Invalid selector: ${selector}`);\n }\n });\n }\n\n /**\n * Remove all unredaction classes from DOM elements\n */\n public removeUnredactionClasses(): void {\n // Note: This doesn't add 'rr-mask' classes back - rrweb handles that automatically\n logDebug('Unredaction classes removed');\n }\n\n /**\n * Check if a selector represents a password field\n */\n private isPasswordSelector(selector: string): boolean {\n const passwordPatterns = [\n 'input[type=\"password\"]',\n 'input[type=\"password\" i]',\n '[type=\"password\"]',\n '[type=\"password\" i]'\n ];\n \n return passwordPatterns.some(pattern => \n selector.toLowerCase().includes(pattern.toLowerCase().replace(/[\\[\\]]/g, ''))\n );\n }\n\n /**\n * Get the original value of an element (for debugging)\n */\n public getOriginalValue(element: HTMLElement): string | undefined {\n if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {\n return element.value;\n }\n return undefined;\n }\n\n /**\n * Check if an element is currently unredacted\n */\n public isElementUnredacted(element: HTMLElement): boolean {\n return this.shouldUnredactElement(element);\n }\n\n /**\n * Check if an element should be unredacted\n */\n public shouldUnredactElement(element: HTMLElement): boolean {\n if (this.redactionMode === 'privacy-first') {\n // Privacy-first: check if element is in unredacted fields\n if (this.unredactedFields.size === 0) {\n return false; // Nothing unredacted\n }\n\n // Check if any selector matches this element\n for (const selector of this.unredactedFields) {\n try {\n if (element.matches(selector)) {\n return true;\n }\n } catch (e) {\n logWarn(`Invalid selector: ${selector}`);\n }\n }\n return false;\n } else {\n // Visibility-first: check if element is NOT in redacted fields\n if (this.redactedFields.size === 0) {\n return true; // Nothing redacted, everything visible\n }\n\n // Check if any selector matches this element\n for (const selector of this.redactedFields) {\n try {\n if (element.matches(selector)) {\n return false; // Element is redacted\n }\n } catch (e) {\n logWarn(`Invalid selector: ${selector}`);\n }\n }\n return true; // Element is not redacted\n }\n }\n}\n\n// Export a default instance\nexport const redactionManager = new RedactionManager();\n\n// Export the class for custom instances\nexport default RedactionManager; ","/**\n * Automatic Property Detection for HumanBehavior SDK\n * Captures device type, location, and initial referrer information\n */\n\n// Check if we're in a browser environment\nconst isBrowser = typeof window !== 'undefined';\n\nexport interface DeviceInfo {\n device_type: string;\n browser: string;\n browser_version: string;\n os: string;\n os_version: string;\n device_model?: string;\n screen_resolution: string;\n viewport_size: string;\n color_depth: number;\n timezone: string;\n language: string;\n languages: string[];\n raw_user_agent?: string;\n}\n\nexport interface LocationInfo {\n current_url: string;\n pathname: string;\n search: string;\n hash: string;\n title: string;\n referrer: string;\n referrer_domain: string;\n initial_referrer: string;\n initial_referrer_domain: string;\n initial_host?: string;\n utm_source?: string;\n utm_medium?: string;\n utm_campaign?: string;\n utm_term?: string;\n utm_content?: string;\n}\n\nexport interface AutomaticProperties extends DeviceInfo, LocationInfo {}\n\n/**\n * Detect device type based on user agent and screen size\n */\nfunction detectDeviceType(): string {\n if (!isBrowser) return 'unknown';\n \n const userAgent = navigator.userAgent.toLowerCase();\n const screenWidth = window.screen.width;\n const screenHeight = window.screen.height;\n \n // Mobile detection\n if (/mobile|android|iphone|ipad|ipod|blackberry|windows phone/i.test(userAgent)) {\n if (/ipad/i.test(userAgent) || (screenWidth >= 768 && screenHeight >= 1024)) {\n return 'tablet';\n }\n return 'mobile';\n }\n \n // Desktop detection\n if (/windows|macintosh|linux/i.test(userAgent)) {\n return 'desktop';\n }\n \n return 'unknown';\n}\n\n/**\n * Extract browser information from user agent\n */\nfunction detectBrowser(): { browser: string; browser_version: string } {\n if (!isBrowser) return { browser: 'unknown', browser_version: 'unknown' };\n \n const userAgent = navigator.userAgent;\n \n // Chrome\n if (/chrome/i.test(userAgent) && !/edge/i.test(userAgent)) {\n const match = userAgent.match(/chrome\\/(\\d+)/i);\n return {\n browser: 'chrome',\n browser_version: match ? match[1] : 'unknown'\n };\n }\n \n // Firefox\n if (/firefox/i.test(userAgent)) {\n const match = userAgent.match(/firefox\\/(\\d+)/i);\n return {\n browser: 'firefox',\n browser_version: match ? match[1] : 'unknown'\n };\n }\n \n // Safari\n if (/safari/i.test(userAgent) && !/chrome/i.test(userAgent)) {\n const match = userAgent.match(/version\\/(\\d+)/i);\n return {\n browser: 'safari',\n browser_version: match ? match[1] : 'unknown'\n };\n }\n \n // Edge\n if (/edge/i.test(userAgent)) {\n const match = userAgent.match(/edge\\/(\\d+)/i);\n return {\n browser: 'edge',\n browser_version: match ? match[1] : 'unknown'\n };\n }\n \n // Internet Explorer\n if (/msie|trident/i.test(userAgent)) {\n const match = userAgent.match(/msie (\\d+)/i) || userAgent.match(/rv:(\\d+)/i);\n return {\n browser: 'ie',\n browser_version: match ? match[1] : 'unknown'\n };\n }\n \n return { browser: 'unknown', browser_version: 'unknown' };\n}\n\n/**\n * Extract operating system information from user agent\n */\nfunction detectOS(): { os: string; os_version: string } {\n if (!isBrowser) return { os: 'unknown', os_version: 'unknown' };\n \n const userAgent = navigator.userAgent;\n \n // Windows\n if (/windows/i.test(userAgent)) {\n const match = userAgent.match(/windows nt (\\d+\\.\\d+)/i);\n let version = 'unknown';\n if (match) {\n const versionNum = parseFloat(match[1]);\n if (versionNum === 10.0) version = '10';\n else if (versionNum === 6.3) version = '8.1';\n else if (versionNum === 6.2) version = '8';\n else if (versionNum === 6.1) version = '7';\n else version = match[1];\n }\n return { os: 'windows', os_version: version };\n }\n \n // macOS\n if (/macintosh|mac os x/i.test(userAgent)) {\n const match = userAgent.match(/mac os x (\\d+[._]\\d+)/i);\n return {\n os: 'macos',\n os_version: match ? match[1].replace('_', '.') : 'unknown'\n };\n }\n \n // iOS\n if (/iphone|ipad|ipod/i.test(userAgent)) {\n const match = userAgent.match(/os (\\d+[._]\\d+)/i);\n return {\n os: 'ios',\n os_version: match ? match[1].replace('_', '.') : 'unknown'\n };\n }\n \n // Android\n if (/android/i.test(userAgent)) {\n const match = userAgent.match(/android (\\d+\\.\\d+)/i);\n return {\n os: 'android',\n os_version: match ? match[1] : 'unknown'\n };\n }\n \n // Linux\n if (/linux/i.test(userAgent)) {\n return { os: 'linux', os_version: 'unknown' };\n }\n \n return { os: 'unknown', os_version: 'unknown' };\n}\n\n/**\n * Extract UTM parameters from URL\n */\nfunction extractUTMParams(url: string): Record<string, string> {\n const urlObj = new URL(url);\n const utmParams: Record<string, string> = {};\n \n const utmKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];\n \n utmKeys.forEach(key => {\n const value = urlObj.searchParams.get(key);\n if (value) {\n utmParams[key] = value;\n }\n });\n \n return utmParams;\n}\n\n/**\n * Extract domain from URL\n */\nfunction extractDomain(url: string): string {\n try {\n const urlObj = new URL(url);\n return urlObj.hostname;\n } catch {\n return '';\n }\n}\n\n/**\n * Get device information\n */\nexport function getDeviceInfo(): DeviceInfo {\n if (!isBrowser) {\n return {\n device_type: 'unknown',\n browser: 'unknown',\n browser_version: 'unknown',\n os: 'unknown',\n os_version: 'unknown',\n screen_resolution: 'unknown',\n viewport_size: 'unknown',\n color_depth: 0,\n timezone: 'unknown',\n language: 'unknown',\n languages: []\n };\n }\n \n const { browser, browser_version } = detectBrowser();\n const { os, os_version } = detectOS();\n \n return {\n device_type: detectDeviceType(),\n browser,\n browser_version,\n os,\n os_version,\n screen_resolution: `${window.screen.width}x${window.screen.height}`,\n viewport_size: `${window.innerWidth}x${window.innerHeight}`,\n color_depth: window.screen.colorDepth,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n language: navigator.language,\n languages: [...(navigator.languages || [navigator.language])],\n raw_user_agent: navigator.userAgent\n };\n}\n\n/**\n * Get location information\n */\nexport function getLocationInfo(): LocationInfo {\n if (!isBrowser) {\n return {\n current_url: '',\n pathname: '',\n search: '',\n hash: '',\n title: '',\n referrer: '',\n referrer_domain: '',\n initial_referrer: '',\n initial_referrer_domain: ''\n };\n }\n \n const currentUrl = window.location.href;\n const referrer = document.referrer;\n const utmParams = extractUTMParams(currentUrl);\n \n return {\n current_url: currentUrl,\n pathname: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n title: document.title,\n referrer,\n referrer_domain: extractDomain(referrer),\n initial_referrer: referrer,\n initial_referrer_domain: extractDomain(referrer),\n initial_host: window.location.hostname,\n ...utmParams\n };\n}\n\n/**\n * Get all automatic properties\n */\nexport function getAutomaticProperties(): AutomaticProperties {\n return {\n ...getDeviceInfo(),\n ...getLocationInfo()\n };\n}\n\n/**\n * Get initial properties that should be captured once per session\n */\nexport function getInitialProperties(): Record<string, any> {\n if (!isBrowser) return {};\n \n const locationInfo = getLocationInfo();\n \n return {\n initial_referrer: locationInfo.initial_referrer,\n initial_referrer_domain: locationInfo.initial_referrer_domain,\n initial_url: locationInfo.current_url,\n initial_pathname: locationInfo.pathname,\n initial_utm_source: locationInfo.utm_source,\n initial_utm_medium: locationInfo.utm_medium,\n initial_utm_campaign: locationInfo.utm_campaign,\n initial_utm_term: locationInfo.utm_term,\n initial_utm_content: locationInfo.utm_content\n };\n}\n\n/**\n * Get current page properties (changes with navigation)\n */\nexport function getCurrentPageProperties(): Record<string, any> {\n if (!isBrowser) return {};\n \n const locationInfo = getLocationInfo();\n \n return {\n current_url: locationInfo.current_url,\n pathname: locationInfo.pathname,\n search: locationInfo.search,\n hash: locationInfo.hash,\n title: locationInfo.title,\n referrer: locationInfo.referrer,\n referrer_domain: locationInfo.referrer_domain,\n utm_source: locationInfo.utm_source,\n utm_medium: locationInfo.utm_medium,\n utm_campaign: locationInfo.utm_campaign,\n utm_term: locationInfo.utm_term,\n utm_content: locationInfo.utm_content\n };\n}\n","/**\n * Property Manager for HumanBehavior SDK\n * Handles automatic properties, session properties, and user properties\n */\n\nimport { getAutomaticProperties, getInitialProperties, getCurrentPageProperties, AutomaticProperties } from './property-detector';\n\nexport interface Properties {\n [key: string]: any;\n}\n\nexport interface PropertyManagerConfig {\n enableAutomaticProperties?: boolean;\n enableSessionProperties?: boolean;\n enableUserProperties?: boolean;\n propertyDenylist?: string[];\n}\n\nexport class PropertyManager {\n private config: PropertyManagerConfig;\n private automaticProperties: AutomaticProperties;\n private sessionProperties: Properties = {};\n private userProperties: Properties = {};\n private initialProperties: Properties = {};\n private isInitialized: boolean = false;\n\n constructor(config: PropertyManagerConfig = {}) {\n this.config = {\n enableAutomaticProperties: true,\n enableSessionProperties: true,\n enableUserProperties: true,\n propertyDenylist: [],\n ...config\n };\n \n this.automaticProperties = getAutomaticProperties();\n this.initialize();\n }\n\n /**\n * Initialize the property manager\n */\n private initialize(): void {\n if (this.isInitialized) return;\n \n // Capture initial properties once\n this.initialProperties = getInitialProperties();\n \n // Load session properties from sessionStorage\n this.loadSessionProperties();\n \n this.isInitialized = true;\n }\n\n /**\n * Get all properties for an event\n */\n public getEventProperties(eventProperties: Properties = {}): Properties {\n const properties: Properties = { ...eventProperties };\n\n // Add automatic properties\n if (this.config.enableAutomaticProperties) {\n Object.assign(properties, this.getAutomaticProperties());\n }\n\n // Add session properties\n if (this.config.enableSessionProperties) {\n Object.assign(properties, this.sessionProperties);\n }\n\n // Add user properties\n if (this.config.enableUserProperties) {\n Object.assign(properties, this.userProperties);\n }\n\n // Add initial properties (only once per session)\n if (!this.sessionProperties['$initial_properties_captured']) {\n Object.assign(properties, this.initialProperties);\n this.setSessionProperty('$initial_properties_captured', true);\n }\n\n // Apply denylist\n this.applyDenylist(properties);\n\n return properties;\n }\n\n /**\n * Get automatic properties\n */\n public getAutomaticProperties(): Properties {\n return {\n ...this.automaticProperties,\n ...getCurrentPageProperties() // Always get fresh page properties\n };\n }\n\n /**\n * Get automatic properties with GeoIP data merged in\n */\n public getAutomaticPropertiesWithGeoIP(geoIPProperties: Record<string, any> = {}): Properties {\n return {\n ...this.automaticProperties,\n ...getCurrentPageProperties(), // Always get fresh page properties\n ...geoIPProperties\n };\n }\n\n /**\n * Set a session property\n */\n public setSessionProperty(key: string, value: any): void {\n this.sessionProperties[key] = value;\n this.saveSessionProperties();\n }\n\n /**\n * Set multiple session properties\n */\n public setSessionProperties(properties: Properties): void {\n Object.assign(this.sessionProperties, properties);\n this.saveSessionProperties();\n }\n\n /**\n * Get a session property\n */\n public getSessionProperty(key: string): any {\n return this.sessionProperties[key];\n }\n\n /**\n * Remove a session property\n */\n public removeSessionProperty(key: string): void {\n delete this.sessionProperties[key];\n this.saveSessionProperties();\n }\n\n /**\n * Set a user property\n */\n public setUserProperty(key: string, value: any): void {\n this.userProperties[key] = value;\n }\n\n /**\n * Set multiple user properties\n */\n public setUserProperties(properties: Properties): void {\n Object.assign(this.userProperties, properties);\n }\n\n /**\n * Get a user property\n */\n public getUserProperty(key: string): any {\n return this.userProperties[key];\n }\n\n /**\n * Remove a user property\n */\n public removeUserProperty(key: string): void {\n delete this.userProperties[key];\n }\n\n /**\n * Set a property only if it hasn't been set before\n */\n public setOnce(key: string, value: any, scope: 'session' | 'user' = 'user'): void {\n if (scope === 'session') {\n if (!(key in this.sessionProperties)) {\n this.setSessionProperty(key, value);\n }\n } else {\n if (!(key in this.userProperties)) {\n this.setUserProperty(key, value);\n }\n }\n }\n\n /**\n * Clear all session properties\n */\n public clearSessionProperties(): void {\n this.sessionProperties = {};\n this.saveSessionProperties();\n }\n\n /**\n * Clear all user properties\n */\n public clearUserProperties(): void {\n this.userProperties = {};\n }\n\n /**\n * Reset all properties\n */\n public reset(): void {\n this.clearSessionProperties();\n this.clearUserProperties();\n this.initialProperties = {};\n this.isInitialized = false;\n this.initialize();\n }\n\n /**\n * Load session properties from sessionStorage\n */\n private loadSessionProperties(): void {\n if (typeof sessionStorage === 'undefined') return;\n \n try {\n const stored = sessionStorage.getItem('hb_session_properties');\n if (stored) {\n this.sessionProperties = JSON.parse(stored);\n }\n } catch (error) {\n console.warn('Failed to load session properties:', error);\n }\n }\n\n /**\n * Save session properties to sessionStorage\n */\n private saveSessionProperties(): void {\n if (typeof sessionStorage === 'undefined') return;\n \n try {\n sessionStorage.setItem('hb_session_properties', JSON.stringify(this.sessionProperties));\n } catch (error) {\n console.warn('Failed to save session properties:', error);\n }\n }\n\n /**\n * Apply property denylist\n */\n private applyDenylist(properties: Properties): void {\n if (!this.config.propertyDenylist || this.config.propertyDenylist.length === 0) {\n return;\n }\n\n this.config.propertyDenylist.forEach(deniedKey => {\n delete properties[deniedKey];\n });\n }\n\n /**\n * Update automatic properties (call when page changes)\n */\n public updateAutomaticProperties(): void {\n this.automaticProperties = getAutomaticProperties();\n }\n\n /**\n * Get all properties for debugging\n */\n public getAllProperties(): {\n automatic: Properties;\n session: Properties;\n user: Properties;\n initial: Properties;\n } {\n return {\n automatic: this.getAutomaticProperties(),\n session: { ...this.sessionProperties },\n user: { ...this.userProperties },\n initial: { ...this.initialProperties }\n };\n }\n}\n","import { record } from '@rrweb/record';\nimport type { listenerHandler } from '@rrweb/types';\nimport { v1 as uuidv1 } from 'uuid';\nimport { HumanBehaviorAPI } from './api';\nimport { RedactionManager, RedactionOptions } from './redact';\nimport { logger, logError, logWarn, logInfo, logDebug } from './utils/logger';\nimport { PropertyManager, Properties } from './utils/property-manager';\n\n// Check if we're in a browser environment\nconst isBrowser = typeof window !== 'undefined';\n\n// Global declarations moved to browser-tracker.ts to avoid conflicts\n\nexport class HumanBehaviorTracker {\n private eventQueue: any[] = []; // Unified queue for all events (regular + recordings)\n \n private sessionId!: string;\n private userProperties: Record<string, any> = {};\n private isProcessing: boolean = false;\n \n private flushInterval: number | null = null;\n private readonly FLUSH_INTERVAL_MS = 3000; // Unified flush interval\n private readonly MAX_QUEUE_SIZE: number; // Configurable queue size\n \n private api!: HumanBehaviorAPI;\n private endUserId: string | null = null;\n private apiKey!: string;\n private initialized: boolean = false;\n public initializationPromise: Promise<void> | null = null;\n private monthlyLimitReached: boolean = false;\n private redactionManager!: RedactionManager;\n private propertyManager!: PropertyManager;\n \n private isDomReady: boolean = false;\n private requestQueue: any[] = [];\n private domReadyHandlers: Array<() => void> = [];\n \n // Console tracking properties\n private originalConsole: {\n log: typeof console.log;\n warn: typeof console.warn;\n error: typeof console.error;\n } | null = null;\n private consoleTrackingEnabled: boolean = false;\n\n // Navigation tracking properties\n public navigationTrackingEnabled: boolean = false;\n private currentUrl: string = '';\n private previousUrl: string = '';\n private originalPushState: typeof history.pushState | null = null;\n private originalReplaceState: typeof history.replaceState | null = null;\n private navigationListeners: Array<() => void> = [];\n private _connectionBlocked: boolean = false;\n private recordInstance: listenerHandler | null = null;\n private sessionStartTime: number = Date.now();\n private rrwebRecord: any = null;\n private fullSnapshotTimeout: number | null = null;\n private recordCanvas: boolean = false; // Store canvas recording preference\n private isStarted: boolean = false; // Guard against multiple start() calls\n \n /**\n * Check if the tracker has been started\n */\n public get isTrackerStarted(): boolean {\n return this.isStarted;\n }\n\n /**\n * DOM ready detection - more aggressive\n */\n private setupDomReadyHandler(): void {\n if (!isBrowser) {\n this.onDomReady();\n return;\n }\n\n // More aggressive DOM ready detection\n if (document.readyState === 'complete' || document.readyState === 'interactive') {\n // DOM is ready enough\n this.onDomReady();\n } else if (document.addEventListener) {\n // Wait for DOMContentLoaded, but also check periodically\n const checkDomReady = () => {\n if (document.readyState === 'interactive' || document.readyState === 'complete') {\n this.onDomReady();\n }\n };\n \n document.addEventListener('DOMContentLoaded', () => this.onDomReady(), { capture: false });\n \n // Also check periodically for faster response\n const interval = setInterval(() => {\n if (document.readyState === 'interactive' || document.readyState === 'complete') {\n clearInterval(interval);\n this.onDomReady();\n }\n }, 10); // Check every 10ms\n \n // Clear interval after 5 seconds to avoid infinite checking\n setTimeout(() => clearInterval(interval), 5000);\n } else {\n // Fallback for older browsers\n this.onDomReady();\n }\n }\n\n /**\n * Called when DOM is ready - processes queued requests\n */\n private onDomReady(): void {\n if (this.isDomReady) return; // Prevent multiple calls\n \n this.isDomReady = true;\n logDebug('🎯 DOM is ready, processing queued requests');\n \n // Process queued requests\n this.requestQueue.forEach(request => {\n this.processRequest(request);\n });\n this.requestQueue = [];\n \n // Call registered handlers\n this.domReadyHandlers.forEach(handler => handler());\n this.domReadyHandlers = [];\n }\n\n /**\n * Queue a request until DOM is ready\n */\n private queueRequest(request: any): void {\n if (this.isDomReady) {\n this.processRequest(request);\n } else {\n this.requestQueue.push(request);\n }\n }\n\n /**\n * Process a request (called after DOM is ready)\n */\n private async processRequest(request: any): Promise<void> {\n logDebug('Processing queued request:', request);\n \n switch (request.type) {\n case 'addEvent':\n await this.addEvent(request.event);\n break;\n case 'identifyUser':\n await this.identifyUser(request.userProperties);\n break;\n case 'trackPageView':\n this.trackPageView();\n break;\n default:\n logWarn('Unknown request type:', request.type);\n }\n }\n\n /**\n * Register a handler to be called when DOM is ready\n */\n private registerDomReadyHandler(handler: () => void): void {\n if (this.isDomReady) {\n handler();\n } else {\n this.domReadyHandlers.push(handler);\n }\n }\n\n /**\n * Initialize the HumanBehavior tracker\n * This is the main entry point - call this once per page\n */\n public static init(apiKey: string, options?: {\n ingestionUrl?: string;\n logLevel?: 'none' | 'error' | 'warn' | 'info' | 'debug';\n redactFields?: string[]; // DEPRECATED: Use redactionStrategy instead\n redactionStrategy?: {\n mode: 'privacy-first' | 'visibility-first'; // Default: 'privacy-first'\n unredactFields?: string[]; // Fields to make visible (when mode: 'privacy-first')\n redactFields?: string[]; // Fields to hide (when mode: 'visibility-first')\n };\n enableAutomaticTracking?: boolean;\n suppressConsoleErrors?: boolean; // New option to control error suppression\n recordCanvas?: boolean; // Enable canvas recording with protection\n enableAutomaticProperties?: boolean; // Enable automatic property detection\n propertyDenylist?: string[]; // Properties to exclude from tracking\n automaticTrackingOptions?: {\n trackButtons?: boolean;\n trackLinks?: boolean;\n trackForms?: boolean;\n includeText?: boolean;\n includeClasses?: boolean;\n };\n maxQueueSize?: number; // Configurable queue size\n }): HumanBehaviorTracker {\n // ✅ SUPPRESS COMMON RRWEB ERRORS FOR CLEAN CONSOLE\n if (isBrowser && options?.suppressConsoleErrors !== false) {\n // Suppress canvas security errors and network errors\n const originalConsoleError = console.error;\n console.error = (...args: any[]) => {\n const message = args.join(' ');\n if (\n message.includes('SecurityError: Failed to execute \\'toDataURL\\'') ||\n message.includes('Tainted canvases may not be exported') ||\n message.includes('Cannot inline img src=') ||\n message.includes('Cross-Origin') ||\n message.includes('CORS') ||\n message.includes('Access-Control-Allow-Origin') ||\n message.includes('Failed to load resource') ||\n message.includes('net::ERR_BLOCKED_BY_CLIENT') ||\n message.includes('NetworkError when attempting to fetch resource') ||\n message.includes('Failed to fetch') ||\n message.includes('TypeError: NetworkError') ||\n message.includes('HumanBehavior ERROR') ||\n message.includes('Failed to track custom event') ||\n message.includes('Error sending custom event')\n ) {\n // Silently suppress these common errors\n return;\n }\n originalConsoleError.apply(console, args);\n };\n\n // Suppress console.warn for similar issues\n const originalConsoleWarn = console.warn;\n console.warn = (...args: any[]) => {\n const message = args.join(' ');\n if (\n message.includes('Cannot inline img src=') ||\n message.includes('Cross-Origin') ||\n message.includes('CORS') ||\n message.includes('Access-Control-Allow-Origin') ||\n message.includes('Failed to load resource') ||\n message.includes('net::ERR_BLOCKED_BY_CLIENT') ||\n message.includes('NetworkError when attempting to fetch resource') ||\n message.includes('Failed to fetch') ||\n message.includes('Custom event network error') ||\n message.includes('Request blocked by ad blocker')\n ) {\n // Silently suppress these common warnings\n return;\n }\n originalConsoleWarn.apply(console, args);\n };\n\n // Add global error handler for any remaining rrweb errors\n window.addEventListener('error', (event) => {\n const message = event.message || '';\n if (\n message.includes('SecurityError') ||\n message.includes('Tainted canvases') ||\n message.includes('toDataURL') ||\n message.includes('Cross-Origin') ||\n message.includes('CORS') ||\n message.includes('NetworkError') ||\n message.includes('Failed to fetch')\n ) {\n event.preventDefault();\n return false;\n }\n });\n }\n // Return existing instance if already initialized\n if (isBrowser && (window as any).__humanBehaviorGlobalTracker) {\n logDebug('Tracker already initialized, returning existing instance');\n return (window as any).__humanBehaviorGlobalTracker;\n }\n\n // Configure logging if specified\n if (options?.logLevel) {\n this.configureLogging({ level: options.logLevel });\n }\n\n // Create new tracker instance\n const tracker = new HumanBehaviorTracker(apiKey, options?.ingestionUrl, {\n enableAutomaticProperties: options?.enableAutomaticProperties,\n propertyDenylist: options?.propertyDenylist,\n redactionStrategy: options?.redactionStrategy,\n redactFields: options?.redactFields,\n maxQueueSize: options?.maxQueueSize\n });\n \n // Store canvas recording preference\n tracker.recordCanvas = options?.recordCanvas ?? false;\n \n // Set unredacted fields if specified (legacy support)\n if (options?.redactFields) {\n tracker.setUnredactedFields(options.redactFields);\n }\n\n // Handle new redaction strategy - this is now handled in the constructor\n // The redactionManager is created with the correct redactionStrategy in the constructor\n\n // Setup automatic tracking if enabled\n if (options?.enableAutomaticTracking !== false) {\n tracker.setupAutomaticTracking(options?.automaticTrackingOptions);\n }\n\n // Start tracking\n tracker.start();\n \n return tracker;\n }\n\n constructor(apiKey: string | undefined, ingestionUrl?: string, options?: {\n enableAutomaticProperties?: boolean;\n propertyDenylist?: string[];\n redactionStrategy?: {\n mode: 'privacy-first' | 'visibility-first';\n unredactFields?: string[];\n redactFields?: string[];\n };\n redactFields?: string[]; // Legacy support\n maxQueueSize?: number; // Configurable queue size\n }) {\n if (!apiKey) {\n throw new Error('Human Behavior API Key is required');\n }\n \n // Initialize API\n //const defaultIngestionUrl = 'http://3.137.217.33:3000'; // AWS Development Server\n //const defaultIngestionUrl = 'http://ingestion-server-alb-1823866402.us-east-2.elb.amazonaws.com'; // ALB\n const defaultIngestionUrl = 'https://ingest.humanbehavior.co'; // HTTPS ALB\n this.api = new HumanBehaviorAPI({ \n apiKey: apiKey,\n ingestionUrl: ingestionUrl || defaultIngestionUrl\n });\n this.apiKey = apiKey;\n \n // Initialize queue size (default 1000)\n this.MAX_QUEUE_SIZE = options?.maxQueueSize ?? 1000;\n \n // debug removed\n this.redactionManager = new RedactionManager({\n redactionStrategy: options?.redactionStrategy,\n legacyRedactFields: options?.redactFields // For backward compatibility\n });\n // debug removed\n \n // Initialize property manager\n this.propertyManager = new PropertyManager({\n enableAutomaticProperties: options?.enableAutomaticProperties !== false,\n propertyDenylist: options?.propertyDenylist || []\n });\n \n // DOM ready handling removed - using simpler approach\n\n // Handle session restoration with improved continuity\n if (isBrowser) {\n const existingSessionId = localStorage.getItem(`human_behavior_session_id_${this.apiKey}`);\n const lastActivity = localStorage.getItem(`human_behavior_last_activity_${this.apiKey}`);\n const fifteenMinutesAgo = Date.now() - (15 * 60 * 1000);\n \n // Check if we have an existing session that's still within the activity window\n if (existingSessionId && lastActivity && parseInt(lastActivity) > fifteenMinutesAgo) {\n this.sessionId = existingSessionId;\n logDebug(`Reusing existing session: ${this.sessionId}`);\n // Update activity timestamp to extend the session window\n localStorage.setItem(`human_behavior_last_activity_${this.apiKey}`, Date.now().toString());\n } else {\n // Clear old session data if it's expired\n if (existingSessionId) {\n logDebug(`Session expired, clearing old session: ${existingSessionId}`);\n localStorage.removeItem(`human_behavior_session_id_${this.apiKey}`);\n localStorage.removeItem(`human_behavior_last_activity_${this.apiKey}`);\n }\n this.sessionId = uuidv1();\n logDebug(`Creating new session: ${this.sessionId}`);\n localStorage.setItem(`human_behavior_session_id_${this.apiKey}`, this.sessionId);\n localStorage.setItem(`human_behavior_last_activity_${this.apiKey}`, Date.now().toString());\n }\n \n this.currentUrl = window.location.href;\n (window as any).__humanBehaviorGlobalTracker = this;\n } else {\n this.sessionId = uuidv1();\n }\n\n // ✅ FIXED: Wait for Kafka-based initialization to complete\n this.initializationPromise = this.init().catch(error => {\n logError('Background initialization failed:', error);\n });\n }\n\n private async init(): Promise<void> {\n try {\n \n // Get userId only in browser environment\n const userId = isBrowser ? this.getCookie(`human_behavior_end_user_id_${this.apiKey}`) : null;\n logDebug(`Initializing with sessionId: ${this.sessionId}, userId: ${userId}`);\n \n // Get automatic properties for init\n const automaticProperties = this.propertyManager.getAutomaticProperties();\n \n // Server will detect IP from HTTP requests automatically\n \n if (isBrowser) {\n this.setupPageUnloadHandler();\n this.setupNavigationTracking();\n } else {\n logInfo('HumanBehaviorTracker initialized in server environment. Session tracking is disabled.');\n }\n\n // ✅ FIXED: Wait for Kafka server initialization to complete\n await this.initServerAsync(userId, automaticProperties);\n \n // ✅ FIXED: Set initialized ONLY after server init succeeds\n this.initialized = true;\n \n logInfo(`HumanBehaviorTracker initialized with sessionId: ${this.sessionId}`);\n } catch (error: any) {\n // Handle initialization errors gracefully - don't throw (v2)\n logError('Failed to initialize HumanBehaviorTracker:', error);\n this.initialized = true; // Don't throw - allow tracker to work locally\n }\n }\n\n private async initServerAsync(userId: string | null, automaticProperties: any): Promise<void> {\n try {\n logDebug('🚀 Attempting to call /init endpoint...');\n logDebug('📡 URL:', `${this.api['baseUrl']}/api/ingestion/init`);\n logDebug('🔑 API Key:', this.apiKey);\n \n // Get browser-specific data only if in browser environment\n let entryURL = null;\n let referrer = null;\n \n if (isBrowser) {\n entryURL = window.location.href;\n referrer = document.referrer;\n }\n \n logDebug('📦 Payload:', {\n sessionId: this.sessionId,\n endUserId: userId,\n entryURL: entryURL,\n referrer: referrer,\n automaticProperties: automaticProperties\n });\n \n // Create a custom init request with automatic properties\n const initResponse = await fetch(`${this.api['baseUrl']}/api/ingestion/init`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Referer': referrer || ''\n },\n body: JSON.stringify({\n sessionId: this.sessionId,\n endUserId: userId,\n entryURL: entryURL,\n referrer: referrer,\n automaticProperties: automaticProperties\n })\n });\n\n if (!initResponse.ok) {\n throw new Error(`Failed to initialize: ${initResponse.statusText}`);\n }\n\n const { sessionId, endUserId } = await initResponse.json();\n \n // Check if server returned a different session ID (for session continuity)\n if (sessionId !== this.sessionId) {\n logDebug(`Server returned different sessionId: ${sessionId} (client had: ${this.sessionId})`);\n this.sessionId = sessionId;\n // Update localStorage with server's session ID for continuity\n if (isBrowser) {\n localStorage.setItem(`human_behavior_session_id_${this.apiKey}`, this.sessionId);\n }\n }\n \n this.endUserId = endUserId;\n this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, endUserId, 365);\n\n logInfo(`Server initialization completed with endUserId: ${endUserId}`);\n } catch (error: any) {\n logError('❌ Server initialization failed:', error);\n logError('🔍 Error details:', {\n message: error?.message || 'Unknown error',\n stack: error?.stack || 'No stack trace',\n type: error?.constructor?.name || 'Unknown type'\n });\n logWarn('Server initialization failed, continuing with local session:', error);\n // Don't throw - allow the tracker to continue working locally\n }\n }\n\n /**\n * ✅ FIXED: Wait for Kafka-based initialization to complete\n */\n private async ensureInitialized(): Promise<void> {\n if (this.initializationPromise) {\n await this.initializationPromise;\n }\n }\n\n /**\n * Setup navigation event tracking for SPA navigation\n */\n private setupNavigationTracking(): void {\n if (!isBrowser || this.navigationTrackingEnabled) return;\n \n this.navigationTrackingEnabled = true;\n logDebug('Setting up navigation tracking');\n\n // Store original history methods\n this.originalPushState = history.pushState;\n this.originalReplaceState = history.replaceState;\n\n // Override pushState to capture programmatic navigation\n history.pushState = (...args) => {\n this.previousUrl = this.currentUrl;\n this.currentUrl = window.location.href;\n \n // Call original method\n this.originalPushState!.apply(history, args);\n \n // Track navigation event\n this.trackNavigationEvent('pushState', this.previousUrl, this.currentUrl);\n \n // Take FullSnapshot on navigation\n this.takeFullSnapshot();\n };\n\n // Override replaceState to capture programmatic navigation\n history.replaceState = (...args) => {\n this.previousUrl = this.currentUrl;\n this.currentUrl = window.location.href;\n \n // Call original method\n this.originalReplaceState!.apply(history, args);\n \n // Track navigation event\n this.trackNavigationEvent('replaceState', this.previousUrl, this.currentUrl);\n \n // Take FullSnapshot on navigation\n this.takeFullSnapshot();\n };\n\n // Listen for popstate events (back/forward navigation)\n const popstateListener = () => {\n this.previousUrl = this.currentUrl;\n this.currentUrl = window.location.href;\n this.trackNavigationEvent('popstate', this.previousUrl, this.currentUrl);\n \n // Take FullSnapshot on navigation\n this.takeFullSnapshot();\n };\n \n window.addEventListener('popstate', popstateListener);\n this.navigationListeners.push(() => {\n window.removeEventListener('popstate', popstateListener);\n });\n\n // Listen for hashchange events\n const hashchangeListener = () => {\n this.previousUrl = this.currentUrl;\n this.currentUrl = window.location.href;\n this.trackNavigationEvent('hashchange', this.previousUrl, this.currentUrl);\n };\n \n window.addEventListener('hashchange', hashchangeListener);\n this.navigationListeners.push(() => {\n window.removeEventListener('hashchange', hashchangeListener);\n });\n\n // Track initial page load\n this.trackNavigationEvent('pageLoad', '', this.currentUrl);\n }\n\n /**\n * Track navigation events and send custom events\n */\n public async trackNavigationEvent(type: string, fromUrl: string, toUrl: string): Promise<void> {\n if (!this.initialized) return;\n\n try {\n const navigationData = {\n type: type,\n from: fromUrl,\n to: toUrl,\n timestamp: new Date().toISOString(),\n pathname: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n referrer: document.referrer\n };\n\n // Add navigation event to the main event stream\n await this.addEvent({\n type: 5, // Custom event type\n data: {\n payload: {\n eventType: 'navigation',\n ...navigationData\n }\n },\n timestamp: Date.now()\n });\n\n // Send $page_viewed custom event for page loads and navigation\n if (type === 'pageLoad' || type === 'pushState' || type === 'replaceState' || type === 'popstate' || type === 'hashchange') {\n const pageViewProperties = {\n url: window.location.href, // Full current URL including protocol, domain, path, query, and hash\n fromUrl: fromUrl,\n navigationType: type,\n pathname: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n referrer: document.referrer,\n timestamp: Date.now()\n };\n\n await this.customEvent('$page_viewed', pageViewProperties);\n }\n \n logDebug(`Navigation tracked: ${type} from ${fromUrl} to ${toUrl}`);\n } catch (error) {\n logError('Failed to track navigation event:', error);\n }\n }\n\n public async trackPageView(url?: string): Promise<void> {\n if (!this.initialized) return;\n\n // Update automatic properties for new page\n this.propertyManager.updateAutomaticProperties();\n\n try {\n const pageViewData = {\n url: url || window.location.href,\n pathname: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n referrer: document.referrer,\n timestamp: new Date().toISOString()\n };\n\n // Get enhanced properties with automatic properties\n const enhancedProperties = this.propertyManager.getEventProperties(pageViewData);\n\n // Add pageview event to the main event stream\n await this.addEvent({\n type: 5, // Custom event type\n data: {\n payload: {\n eventType: 'pageview',\n ...enhancedProperties\n }\n },\n timestamp: Date.now()\n });\n \n logDebug(`Pageview tracked: ${pageViewData.url}`);\n } catch (error) {\n logError('Failed to track pageview event:', error);\n }\n }\n\n public async customEvent(eventName: string, properties?: Record<string, any>): Promise<void> {\n if (!this.initialized || !this.endUserId) {\n logDebug(`Waiting for initialization/endUserId before tracking custom event: ${eventName}. Initialized: ${this.initialized}, endUserId: ${this.endUserId ? 'available' : 'null'}`);\n return;\n }\n\n // Get enhanced properties with automatic properties\n const enhancedProperties = this.propertyManager.getEventProperties(properties);\n\n try {\n // Send custom event directly to the API\n await this.api.sendCustomEvent(this.sessionId, eventName, enhancedProperties, this.endUserId);\n \n logDebug(`Custom event tracked: ${eventName}`, enhancedProperties);\n } catch (error: any) {\n logError('Failed to track custom event:', error);\n \n // Handle specific error types - check for any custom event failure\n if (error.message?.includes('500') || \n error.message?.includes('Internal Server Error') ||\n error.message?.includes('Failed to send custom event')) {\n logWarn('Custom event endpoint failed, using fallback');\n } else if (error.message?.includes('ERR_BLOCKED_BY_CLIENT')) {\n logWarn('Custom event request blocked by ad blocker, using fallback');\n } else if (error.message?.includes('Failed to fetch')) {\n logWarn('Custom event network error, using fallback');\n }\n \n // Always try fallback for any custom event error\n try {\n const customEventData = {\n eventName: eventName,\n properties: enhancedProperties || {},\n timestamp: new Date().toISOString(),\n url: window.location.href,\n pathname: window.location.pathname\n };\n\n await this.addEvent({\n type: 5, // Custom event type\n data: {\n payload: {\n eventType: 'custom',\n ...customEventData\n }\n },\n timestamp: Date.now()\n });\n \n logDebug(`Custom event added to event stream as fallback: ${eventName}`);\n } catch (fallbackError) {\n logError('Failed to add custom event to event stream as fallback:', fallbackError);\n }\n }\n }\n\n /**\n * Setup automatic tracking for buttons, links, and forms\n */\n private setupAutomaticTracking(options?: {\n trackButtons?: boolean;\n trackLinks?: boolean;\n trackForms?: boolean;\n includeText?: boolean;\n includeClasses?: boolean;\n }): void {\n if (!isBrowser) return;\n\n const config = {\n trackButtons: options?.trackButtons !== false,\n trackLinks: false, // Always disabled - only buttons and forms\n trackForms: options?.trackForms !== false,\n includeText: options?.includeText !== false,\n includeClasses: options?.includeClasses || false\n };\n\n logDebug('Setting up automatic tracking with config:', config);\n\n // Setup button tracking\n if (config.trackButtons) {\n this.setupAutomaticButtonTracking(config);\n }\n\n // Setup form tracking\n if (config.trackForms) {\n this.setupAutomaticFormTracking(config);\n }\n }\n\n /**\n * Setup automatic button tracking\n */\n private setupAutomaticButtonTracking(config: {\n includeText?: boolean;\n includeClasses?: boolean;\n }): void {\n document.addEventListener('click', async (event) => {\n const target = event.target as HTMLElement;\n \n // Track button clicks\n if (target.tagName === 'BUTTON' || target.closest('button')) {\n const button = target.tagName === 'BUTTON'\n ? target as HTMLButtonElement\n : target.closest('button') as HTMLButtonElement;\n \n const properties: Record<string, any> = {\n buttonId: button.id || null,\n buttonType: button.type || 'button',\n page: window.location.pathname,\n timestamp: Date.now()\n };\n\n if (config.includeText) {\n properties.buttonText = button.textContent?.trim() || null;\n }\n\n if (config.includeClasses) {\n properties.buttonClass = button.className || null;\n }\n\n // Remove null values\n Object.keys(properties).forEach(key => {\n if (properties[key] === null) {\n delete properties[key];\n }\n });\n\n await this.customEvent('$button_clicked', properties);\n }\n });\n }\n\n /**\n * Setup automatic link tracking\n * TEMPORARILY DISABLED: Automatic custom event tracking\n */\n private setupAutomaticLinkTracking(config: {\n includeText?: boolean;\n includeClasses?: boolean;\n }): void {\n // TEMPORARILY DISABLED: Automatic custom event tracking\n return;\n \n // document.addEventListener('click', async (event) => {\n // const target = event.target as HTMLElement;\n \n // // Track link clicks\n // if (target.tagName === 'A' || target.closest('a')) {\n // const link = target.tagName === 'A'\n // ? target as HTMLAnchorElement\n // : target.closest('a') as HTMLAnchorElement;\n \n // const properties: Record<string, any> = {\n // linkUrl: link.href || null,\n // linkId: link.id || null,\n // linkTarget: link.target || null,\n // page: window.location.pathname,\n // timestamp: Date.now()\n // };\n\n // if (config.includeText) {\n // properties.linkText = link.textContent?.trim() || null;\n // }\n\n // if (config.includeClasses) {\n // properties.linkClass = link.className || null;\n // }\n\n // // Remove null values\n // Object.keys(properties).forEach(key => {\n // if (properties[key] === null) {\n // delete properties[key];\n // }\n // });\n\n // await this.customEvent('link_clicked', properties);\n // }\n // });\n }\n\n /**\n * Setup automatic form tracking\n */\n private setupAutomaticFormTracking(config: {\n includeText?: boolean;\n includeClasses?: boolean;\n }): void {\n document.addEventListener('submit', async (event) => {\n const form = event.target as HTMLFormElement;\n const formData = new FormData(form);\n \n const properties: Record<string, any> = {\n formId: form.id || null,\n formAction: form.action || null,\n formMethod: form.method || 'get',\n fields: Array.from(formData.keys()),\n page: window.location.pathname,\n timestamp: Date.now()\n };\n\n if (config.includeClasses) {\n properties.formClass = form.className || null;\n }\n\n // Remove null values\n Object.keys(properties).forEach(key => {\n if (properties[key] === null) {\n delete properties[key];\n }\n });\n\n await this.customEvent('$form_submitted', properties);\n });\n }\n\n /**\n * Cleanup navigation tracking\n */\n private cleanupNavigationTracking(): void {\n if (!this.navigationTrackingEnabled) return;\n\n // Restore original history methods\n if (this.originalPushState) {\n history.pushState = this.originalPushState;\n }\n if (this.originalReplaceState) {\n history.replaceState = this.originalReplaceState;\n }\n\n // Remove event listeners\n this.navigationListeners.forEach(cleanup => cleanup());\n this.navigationListeners = [];\n\n this.navigationTrackingEnabled = false;\n logDebug('Navigation tracking cleaned up');\n }\n\n public static logToStorage(message: string) {\n logInfo(message);\n }\n\n /**\n * Configure logging behavior for the SDK\n * @param config Logger configuration options\n */\n public static configureLogging(config: { level?: 'none' | 'error' | 'warn' | 'info' | 'debug', enableConsole?: boolean, enableStorage?: boolean }) {\n const levelMap = {\n 'none': 0,\n 'error': 1,\n 'warn': 2,\n 'info': 3,\n 'debug': 4\n };\n \n logger.setConfig({\n level: levelMap[config.level || 'error'],\n enableConsole: config.enableConsole !== false,\n enableStorage: config.enableStorage || false\n });\n }\n\n /**\n * Enable console event tracking\n */\n public enableConsoleTracking(): void {\n if (!isBrowser || this.consoleTrackingEnabled) return;\n \n // Store original console methods\n this.originalConsole = {\n log: console.log,\n warn: console.warn,\n error: console.error\n };\n\n // Override console methods to capture ALL console output (including logger output)\n console.log = (...args) => {\n this.trackConsoleEvent('log', args);\n this.originalConsole!.log(...args);\n };\n\n console.warn = (...args) => {\n this.trackConsoleEvent('warn', args);\n this.originalConsole!.warn(...args);\n };\n\n console.error = (...args) => {\n this.trackConsoleEvent('error', args);\n this.originalConsole!.error(...args);\n };\n\n this.consoleTrackingEnabled = true;\n logDebug('Console tracking enabled');\n }\n\n /**\n * Disable console event tracking\n */\n public disableConsoleTracking(): void {\n if (!isBrowser || !this.consoleTrackingEnabled) return;\n\n // Restore original console methods\n if (this.originalConsole) {\n console.log = this.originalConsole.log;\n console.warn = this.originalConsole.warn;\n console.error = this.originalConsole.error;\n }\n\n this.consoleTrackingEnabled = false;\n logDebug('Console tracking disabled');\n }\n\n private trackConsoleEvent(level: 'log' | 'warn' | 'error', args: any[]): void {\n if (!this.initialized) return;\n\n try {\n const consoleData = {\n level: level,\n message: args.map(arg => \n typeof arg === 'object' ? JSON.stringify(arg) : String(arg)\n ).join(' '),\n timestamp: new Date().toISOString(),\n url: window.location.href\n };\n\n // Add console event to the main event stream\n this.addEvent({\n type: 5, // Custom event type\n data: {\n payload: {\n eventType: 'console',\n ...consoleData\n }\n },\n timestamp: Date.now()\n }).catch(error => {\n logError('Failed to track console event:', error);\n });\n } catch (error) {\n logError('Error in trackConsoleEvent:', error);\n }\n }\n\n private setupPageUnloadHandler() {\n if (!isBrowser) return;\n \n logDebug('Setting up page unload handler');\n \n // Handle visibility changes for sending events\n window.addEventListener('visibilitychange', () => {\n // Only send events when page becomes hidden\n if (document.visibilityState === 'hidden') {\n logDebug('Page hidden - sending pending events');\n // Flush unified event queue\n this.flushEvents();\n }\n });\n\n // Handle actual page unload/close\n window.addEventListener('beforeunload', () => {\n // Send final events from unified queue\n this.flushEvents();\n });\n\n // Update activity timestamp on user interaction (not on page load)\n const updateActivity = () => {\n localStorage.setItem(`human_behavior_last_activity_${this.apiKey}`, Date.now().toString());\n };\n\n // Listen for user interactions to update activity timestamp\n window.addEventListener('click', updateActivity);\n window.addEventListener('keydown', updateActivity);\n window.addEventListener('scroll', updateActivity);\n window.addEventListener('mousemove', updateActivity);\n }\n\n public viewLogs() {\n try {\n const logs = logger.getLogs();\n logInfo('HumanBehavior Logs:', logs);\n logger.clearLogs(); // Clear logs after viewing\n } catch (e) {\n logError('Failed to read logs:', e);\n }\n }\n\n /**\n * Add user identification information to the tracker\n * If userId is not provided, will use userProperties.email as the userId (if present)\n */\n public async identifyUser(\n { userProperties }: { userProperties: Record<string, any> }\n ): Promise<string> {\n await this.ensureInitialized();\n \n // Keep the original endUserId (UUID) - don't change it\n const originalEndUserId = this.endUserId;\n \n // Store user properties\n this.userProperties = userProperties;\n \n logDebug('Identifying user:', { userProperties, originalEndUserId, sessionId: this.sessionId });\n \n // Get automatic properties and send with user data (only in browser)\n const automaticProperties = isBrowser ? this.propertyManager.getAutomaticProperties() : {};\n \n // Use the API class method which properly handles posthogName\n const userResponse = await this.api.sendUserData(\n originalEndUserId || '',\n userProperties,\n this.sessionId\n );\n\n // sendUserData throws an error if the request fails, so if we get here, it succeeded\n \n // ✅ REMOVED: IP info is now handled during /init with automatic properties\n // No need for separate /ip-info call since GeoIP data is captured during initialization\n \n // Don't update endUserId - keep it as the original UUID\n \n return originalEndUserId || '';\n }\n /**\n * Get current user attributes\n */\n public getUserAttributes(): Record<string, any> {\n return { ...this.userProperties };\n }\n\n public async start() {\n await this.ensureInitialized();\n if (!isBrowser) return;\n \n // Prevent multiple start() calls\n if (this.isStarted) {\n logDebug('HumanBehaviorTracker already started, skipping start() call.');\n return;\n }\n this.isStarted = true;\n\n // Start periodic flushing (unified queue)\n this.flushInterval = window.setInterval(() => {\n this.flushEvents();\n }, this.FLUSH_INTERVAL_MS);\n\n // Disable console tracking to reduce event pollution\n // this.enableConsoleTracking();\n\n // ✅ DOM READY DETECTION\n // Wait for DOM to be ready before starting recording\n const startRecording = () => {\n // Prevent multiple recording instances\n if (this.recordInstance) {\n logDebug('🎯 Recording already started, skipping duplicate start');\n return;\n }\n \n logDebug('🎯 DOM ready, starting session recording');\n \n // ✅ HUMANBEHAVIOR RRWEB CONFIGURATION\n this.rrwebRecord = record;\n // debug removed\n const recordInstance = record({\n emit: (event) => {\n this.addRecordingEvent(event);\n \n // ✅ DEBUG FULLSNAPSHOT GENERATION\n if (event.type === 2) { // FullSnapshot\n logDebug(`🎯 FullSnapshot generated at ${new Date().toISOString()}`);\n }\n },\n // ✅ HUMANBEHAVIOR'S CUSTOM SETTINGS\n maskTextSelector: this.redactionManager.getMaskTextSelector() || undefined,\n maskTextFn: undefined,\n maskAllInputs: this.redactionManager.getRedactionMode() === 'privacy-first', // Configurable based on strategy\n maskInputOptions: {\n // Enable rrweb input masking callbacks for all common types\n password: true,\n text: true,\n textarea: true,\n email: true,\n number: true,\n tel: true,\n url: true,\n search: true,\n date: true,\n time: true,\n month: true,\n week: true\n },\n // In visibility-first, selectively mask inputs that should be redacted\n maskInputFn: (text, element) => {\n try {\n const mode = this.redactionManager.getRedactionMode();\n // Only evaluate HTML elements\n if (!(element instanceof HTMLElement)) return text;\n // privacy-first: always mask input values\n if (mode === 'privacy-first') return '*'.repeat(text.length || 1);\n // visibility-first: mask if element is NOT unredacted (i.e., is in redacted set)\n const shouldShow = this.redactionManager.shouldUnredactElement(element);\n const decision = shouldShow ? 'show' : 'mask';\n const id = (element as HTMLElement).id;\n const name = (element as HTMLInputElement).name;\n const type = (element as HTMLInputElement).type;\n return shouldShow ? text : '*'.repeat(text.length || 1);\n } catch {\n return text;\n }\n },\n slimDOMOptions: {},\n // ✅ ERROR SUPPRESSION SETTINGS - Disabled to prevent console noise\n collectFonts: false, // Disable font collection to reduce errors\n inlineStylesheet: true, // Keep styles for proper session replay\n recordCrossOriginIframes: false, // Prevent cross-origin iframe errors\n \n // ✅ CANVAS RECORDING - protection against overwhelm\n recordCanvas: this.recordCanvas, // Opt-in only\n sampling: this.recordCanvas ? { canvas: 4 } : undefined, // 4 FPS throttle\n dataURLOptions: this.recordCanvas ? { \n type: 'image/webp', \n quality: 0.4 \n } : undefined, // WebP with 40% quality\n \n // ✅ FULLSNAPSHOT GENERATION - No periodic snapshots to avoid animation issues\n // Rely on initial FullSnapshot + navigation-triggered ones only\n hooks: {\n // Extra safety: mask input events selectively using rrweb hook\n input: (event) => {\n try {\n const mode = this.redactionManager.getRedactionMode();\n // In privacy-first everything is masked already by maskAllInputs\n if (mode === 'privacy-first') return;\n const node = typeof document !== 'undefined'\n ? document.querySelector(`[data-rrweb-id=\"${(event as any).id}\"]`)\n : null;\n if (node && node instanceof HTMLElement) {\n const shouldShow = this.redactionManager.shouldUnredactElement(node);\n if (!shouldShow) {\n // Mask text payloads in input event\n if (typeof (event as any).text !== 'undefined') {\n (event as any).text = '*'.repeat((event as any).text?.length || 1);\n }\n if (typeof (event as any).value !== 'undefined') {\n (event as any).value = '*'.repeat((event as any).value?.length || 1);\n }\n }\n }\n } catch {}\n }\n }\n });\n \n // Store the record instance for cleanup \n this.recordInstance = recordInstance || null;\n };\n\n // ✅ DOM READY DETECTION - More aggressive like previous version\n logDebug(`🎯 DOM ready state: ${document.readyState}`);\n if (document.readyState === 'complete' || document.readyState === 'interactive') {\n // DOM is ready enough, start immediately\n logDebug(`🎯 DOM ready (${document.readyState}), starting recording immediately`);\n startRecording();\n } else {\n // Wait for DOM to be ready, but also check periodically\n logDebug('🎯 DOM not ready, waiting for DOMContentLoaded event');\n \n const checkDomReady = () => {\n if (document.readyState === 'interactive' || document.readyState === 'complete') {\n logDebug(`🎯 DOM ready (${document.readyState}), starting recording`);\n startRecording();\n return true;\n }\n return false;\n };\n \n // Check immediately in case it changed\n if (checkDomReady()) return;\n \n // Listen for DOMContentLoaded\n document.addEventListener('DOMContentLoaded', () => {\n logDebug('🎯 DOMContentLoaded fired, starting recording');\n startRecording();\n }, { once: true });\n \n // Also check periodically for faster response\n const interval = setInterval(() => {\n if (checkDomReady()) {\n clearInterval(interval);\n }\n }, 10); // Check every 10ms\n \n // Clear interval after 5 seconds to avoid infinite checking\n setTimeout(() => clearInterval(interval), 5000);\n }\n }\n\n /**\n * Manually trigger a FullSnapshot (for navigation events)\n * Delays snapshot to avoid capturing mid-animation states\n */\n private takeFullSnapshot(): void {\n // Clear any existing timeout to avoid multiple snapshots\n if (this.fullSnapshotTimeout) {\n clearTimeout(this.fullSnapshotTimeout);\n }\n\n // Delay FullSnapshot to let animations settle\n this.fullSnapshotTimeout = window.setTimeout(() => {\n try {\n // Wait for any pending animations/transitions to complete\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n // Access takeFullSnapshot from the rrweb record function\n if (this.rrwebRecord && typeof this.rrwebRecord.takeFullSnapshot === 'function') {\n this.rrwebRecord.takeFullSnapshot();\n logDebug('✅ FullSnapshot taken for navigation (delayed for animations)');\n } else {\n logWarn('⚠️ takeFullSnapshot not available on record function');\n }\n });\n });\n } catch (error) {\n logError('❌ Failed to take FullSnapshot for navigation:', error);\n }\n }, 1000); // Wait 1 second for animations to settle\n }\n\n public async stop() {\n await this.ensureInitialized();\n if (!isBrowser) return;\n \n if (this.flushInterval) {\n clearInterval(this.flushInterval);\n this.flushInterval = null;\n }\n\n // Stop rrweb recording\n if (this.recordInstance) {\n this.recordInstance();\n this.recordInstance = null;\n }\n \n // Clear any pending FullSnapshot timeouts\n if (this.fullSnapshotTimeout) {\n clearTimeout(this.fullSnapshotTimeout);\n this.fullSnapshotTimeout = null;\n }\n \n this.rrwebRecord = null;\n\n // Disable console tracking\n this.disableConsoleTracking();\n\n // Cleanup navigation tracking\n this.cleanupNavigationTracking();\n }\n\n /**\n * Add an event to the ingestion queue\n * Events are sent directly without processing to avoid corruption\n */\n public async addEvent(event: any) {\n await this.ensureInitialized();\n \n // ✅ DIRECT EVENT HANDLING - No custom processing to avoid corruption\n // Events flow directly from rrweb to ingestion server\n \n // ✅ EVENT VALIDATION\n if (!event || typeof event !== 'object') {\n logDebug('⚠️ Skipping invalid event:', event);\n return;\n }\n \n // ✅ LOG FULLSNAPSHOT STATUS FOR DEBUGGING\n if (event.type === 2) { // FullSnapshot\n const hasData = !!event.data;\n const hasNode = !!(event.data && event.data.node);\n \n if (!hasData || !hasNode) {\n logDebug(`⚠️ Empty FullSnapshot detected: hasData=${hasData}, hasNode=${hasNode} - continuing session`);\n } else {\n logDebug(`✅ Valid FullSnapshot: hasData=${hasData}, hasNode=${hasNode}, dataType=${event.data?.node?.type}`);\n }\n }\n \n // Queue size management with immediate flushing\n if (this.eventQueue.length >= this.MAX_QUEUE_SIZE) {\n // Drop oldest event when queue is full\n this.eventQueue.shift();\n logDebug('Queue is full, the oldest event is dropped.');\n }\n \n this.eventQueue.push(event); // Direct event handling\n \n // Immediate flush for FullSnapshots (important events)\n if (event.type === 2) { // FullSnapshot\n logDebug('FullSnapshot added, triggering immediate flush');\n this.flushEvents();\n }\n // Immediate flush if queue is getting large\n else if (this.eventQueue.length >= this.MAX_QUEUE_SIZE * 0.8) {\n logDebug(`Queue at ${this.eventQueue.length}/${this.MAX_QUEUE_SIZE}, triggering immediate flush`);\n this.flushEvents();\n }\n }\n\n /**\n * Flush events to the ingestion server\n * Events are sent in chunks to handle large payloads efficiently\n */\n private async flushEvents() {\n // Prevent concurrent flushes\n if (this.isProcessing || !this.initialized) {\n return;\n }\n\n // Don't make requests if monthly limit is reached - silently skip\n if (this.monthlyLimitReached) {\n return; // Silently skip without logging\n }\n\n this.isProcessing = true;\n try {\n // Swap the current queue with an empty one atomically\n const eventsToProcess = this.eventQueue;\n this.eventQueue = [];\n\n if (eventsToProcess.length > 0) {\n logDebug('Flushing events:', eventsToProcess);\n \n // ✅ LOG FULLSNAPSHOT STATUS FOR MONITORING\n const fullSnapshots = eventsToProcess.filter(e => e.type === 2);\n if (fullSnapshots.length > 0) {\n logDebug(`[FIXED] Sending ${fullSnapshots.length} FullSnapshot(s) with valid data`);\n }\n \n try {\n // Use chunked sending to handle large payloads\n await this.api.sendEventsChunked(eventsToProcess, this.sessionId, this.endUserId!);\n } catch (error: any) {\n // Handle specific error types with graceful degradation\n if (error.message?.includes('ERROR: Session already completed')) {\n logWarn('Session expired, events will be lost');\n } else if (error.message?.includes('413') || error.message?.includes('Content Too Large')) {\n logWarn('Payload too large, events will be lost');\n } else if (error.message?.includes('ERR_BLOCKED_BY_CLIENT') || \n error.message?.includes('Failed to fetch') ||\n error.message?.includes('NetworkError')) {\n logWarn('Request blocked by ad blocker or network issue, events will be lost');\n } else {\n throw error;\n }\n }\n }\n } finally {\n this.isProcessing = false;\n }\n }\n\n /**\n * Add an event to the session recording queue\n * These are typically FullSnapshots or IncrementalSnapshots\n */\n public async addRecordingEvent(event: any) {\n await this.ensureInitialized();\n \n // ✅ DIRECT EVENT HANDLING - No custom processing to avoid corruption\n // Events flow directly from rrweb to ingestion server\n \n // ✅ EVENT VALIDATION\n if (!event || typeof event !== 'object') {\n logDebug('⚠️ Skipping invalid recording event:', event);\n return;\n }\n \n // ✅ LOG FULLSNAPSHOT STATUS FOR DEBUGGING\n if (event.type === 2) { // FullSnapshot\n const hasData = !!event.data;\n const hasNode = !!(event.data && event.data.node);\n \n if (!hasData || !hasNode) {\n logDebug(`⚠️ Empty FullSnapshot detected: hasData=${hasData}, hasNode=${hasNode} - continuing session`);\n } else {\n logDebug(`✅ Valid FullSnapshot: hasData=${hasData}, hasNode=${hasNode}, dataType=${event.data?.node?.type}`);\n }\n }\n \n // Use the same unified queue for all events\n // Queue size management with immediate flushing\n if (this.eventQueue.length >= this.MAX_QUEUE_SIZE) {\n // Drop oldest event when queue is full\n this.eventQueue.shift();\n logDebug('Queue is full, the oldest event is dropped.');\n }\n \n this.eventQueue.push(event); // Direct event handling\n \n // Immediate flush for FullSnapshots (important events)\n if (event.type === 2) { // FullSnapshot\n logDebug('FullSnapshot added, triggering immediate flush');\n this.flushEvents();\n }\n // Immediate flush if queue is getting large\n else if (this.eventQueue.length >= this.MAX_QUEUE_SIZE * 0.8) {\n logDebug(`Queue at ${this.eventQueue.length}/${this.MAX_QUEUE_SIZE}, triggering immediate flush`);\n this.flushEvents();\n }\n }\n\n\n\n // Add helper methods for cookie management with localStorage fallback\n private setCookie(name: string, value: string, daysToExpire: number) {\n if (!isBrowser) return;\n \n try {\n // Try to set cookie first\n const date = new Date();\n date.setTime(date.getTime() + (daysToExpire * 24 * 60 * 60 * 1000));\n const expires = `expires=${date.toUTCString()}`;\n document.cookie = `${name}=${value};${expires};path=/;SameSite=Lax`;\n \n // Also store in localStorage as backup\n localStorage.setItem(name, value);\n logDebug(`Set cookie and localStorage: ${name}`);\n } catch (error) {\n // If cookie fails, use localStorage only\n try {\n localStorage.setItem(name, value);\n logDebug(`Cookie blocked, using localStorage: ${name}`);\n } catch (localStorageError) {\n logError('Failed to store user ID in both cookie and localStorage:', localStorageError);\n }\n }\n }\n\n public getCookie(name: string): string | null {\n if (!isBrowser) return null;\n \n try {\n // Try to get from cookie first\n const nameEQ = name + \"=\";\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) === ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) === 0) {\n const cookieValue = c.substring(nameEQ.length, c.length);\n logDebug(`Found cookie: ${name}`);\n return cookieValue;\n }\n }\n \n // If cookie not found, try localStorage\n const localStorageValue = localStorage.getItem(name);\n if (localStorageValue) {\n logDebug(`Cookie not found, using localStorage: ${name}`);\n return localStorageValue;\n }\n \n return null;\n } catch (error) {\n // If cookie access fails, try localStorage\n try {\n const localStorageValue = localStorage.getItem(name);\n if (localStorageValue) {\n logDebug(`Cookie access failed, using localStorage: ${name}`);\n return localStorageValue;\n }\n } catch (localStorageError) {\n logError('Failed to access both cookie and localStorage:', localStorageError);\n }\n return null;\n }\n }\n\n /**\n * Delete a cookie by setting its expiration date to the past\n * @param name The name of the cookie to delete\n */\n private deleteCookie(name: string) {\n if (!isBrowser) return;\n \n try {\n // Delete cookie by setting expiration to past\n document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=Lax`;\n logDebug(`Deleted cookie: ${name}`);\n } catch (error) {\n logError(`Failed to delete cookie: ${name}`, error);\n }\n \n // Also remove from localStorage\n try {\n localStorage.removeItem(name);\n logDebug(`Removed from localStorage: ${name}`);\n } catch (error) {\n logError(`Failed to remove from localStorage: ${name}`, error);\n }\n }\n\n /**\n * Clear user data and reset session when user signs out of the site\n * This should be called when a user logs out of your application to prevent\n * data contamination between different users\n */\n public logout(): void {\n if (!isBrowser) return;\n \n try { \n // Clear user ID cookie and localStorage\n const userIdCookieName = `human_behavior_end_user_id_${this.apiKey}`;\n this.deleteCookie(userIdCookieName);\n \n // Clear session data from localStorage\n localStorage.removeItem(`human_behavior_session_id_${this.apiKey}`);\n localStorage.removeItem(`human_behavior_last_activity_${this.apiKey}`);\n \n // Reset user-related properties\n this.endUserId = null;\n this.userProperties = {};\n \n // Generate a new session ID for the next user\n this.sessionId = uuidv1();\n if (isBrowser) {\n localStorage.setItem(`human_behavior_session_id_${this.apiKey}`, this.sessionId);\n localStorage.setItem(`human_behavior_last_activity_${this.apiKey}`, Date.now().toString());\n }\n \n logInfo('User logged out - cleared all user data and started fresh session');\n } catch (error) {\n logError('Error during logout:', error);\n }\n }\n\n /**\n * Start redaction functionality for sensitive input fields\n * @param options Optional configuration for redaction behavior\n */\n public async redact(options?: RedactionOptions): Promise<void> {\n await this.ensureInitialized();\n if (!isBrowser) {\n logWarn('Redaction is only available in browser environments');\n return;\n }\n \n // Create a new redaction manager with the provided options\n this.redactionManager = new RedactionManager(options);\n }\n\n /**\n * Set specific fields to be redacted (for visibility-first mode)\n * @param fields Array of CSS selectors for fields to redact\n */\n public setRedactedFields(fields: string[]): void {\n this.redactionManager.setFieldsToRedact(fields);\n \n // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures redaction is applied\n if (this.recordInstance) {\n this.restartWithNewRedaction();\n }\n }\n\n /**\n * Set specific fields to be unredacted (everything else stays redacted by rrweb)\n * @param fields Array of CSS selectors for fields to unredact (e.g., ['#username', '#comment'])\n */\n public setUnredactedFields(fields: string[]): void {\n this.redactionManager.setFieldsToUnredact(fields);\n \n // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures unredaction is applied\n if (this.recordInstance) {\n this.restartWithNewRedaction();\n }\n }\n\n private restartWithNewRedaction(): void {\n if (this.recordInstance) {\n this.recordInstance(); // Stop current recording\n this.start(); // Restart with new redaction settings\n }\n }\n\n /**\n * Check if any fields are currently unredacted\n */\n public hasUnredactedFields(): boolean {\n return this.redactionManager.hasUnredactedFields();\n }\n\n /**\n * Get the currently unredacted fields\n */\n public getUnredactedFields(): string[] {\n return this.redactionManager.getUnredactedFields();\n }\n\n /**\n * Remove specific fields from unredaction (they become redacted again)\n * @param fields Array of CSS selectors for fields to redact\n */\n public redactFields(fields: string[]): void {\n this.redactionManager.redactFields(fields);\n \n // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures redaction is updated\n if (this.recordInstance) {\n this.restartWithNewRedaction();\n }\n }\n\n /**\n * Clear all unredacted fields (everything becomes redacted again)\n */\n public clearUnredactedFields(): void {\n this.redactionManager.clearUnredactedFields();\n \n // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures redaction is updated\n if (this.recordInstance) {\n this.restartWithNewRedaction();\n }\n }\n\n /**\n * Get the current session ID\n */\n public getSessionId(): string {\n return this.sessionId;\n }\n\n /**\n * Get the current URL being tracked\n */\n public getCurrentUrl(): string {\n return this.currentUrl;\n }\n\n /**\n * Get current snapshot frequency info\n * Uses configured values (5 minutes, 1000 events)\n */\n public getSnapshotFrequencyInfo(): {\n sessionDuration: number;\n currentInterval: number;\n currentThreshold: number;\n phase: string;\n } {\n const sessionDuration = Date.now() - this.sessionStartTime;\n \n return {\n sessionDuration,\n currentInterval: 300000, // Configured - 5 minutes\n currentThreshold: 1000, // Configured - 1000 events\n phase: 'configured' // Using explicit configuration\n };\n }\n\n /**\n * Test if the tracker can reach the ingestion server\n */\n public async testConnection(): Promise<{ success: boolean; error?: string }> {\n try {\n await this.api.init(this.sessionId, this.endUserId);\n return { success: true };\n } catch (error: any) {\n return { \n success: false, \n error: error.message || 'Unknown error' \n };\n }\n }\n\n /**\n * Get connection status and recommendations\n */\n public getConnectionStatus(): { \n blocked: boolean; \n recommendations: string[] \n } {\n const recommendations: string[] = [];\n let blocked = false;\n\n // Check if we have queued events (might indicate blocking)\n if (this.eventQueue.length > 0) {\n blocked = true;\n recommendations.push('Some requests may be blocked by ad blockers');\n }\n\n // Check if connection was blocked during initialization\n if (this._connectionBlocked) {\n blocked = true;\n recommendations.push('Initial connection test failed - ad blocker may be active');\n }\n\n // Check if we're in a browser environment\n if (typeof window === 'undefined') {\n recommendations.push('Not running in browser environment');\n }\n\n // Check if navigator.sendBeacon is available\n if (typeof navigator.sendBeacon === 'undefined') {\n recommendations.push('sendBeacon not available, using fetch fallback');\n }\n\n return { blocked, recommendations };\n }\n\n /**\n * Check if the current user is a preexisting user\n * Returns true if the user has an existing endUserId cookie from a previous session\n */\n public isPreexistingUser(): boolean {\n if (!isBrowser) {\n return false;\n }\n \n // Check if there's an existing endUserId cookie for this API key\n const existingEndUserId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);\n return existingEndUserId !== null && existingEndUserId !== this.endUserId;\n }\n\n /**\n * Get user information including whether they are preexisting\n */\n public getUserInfo(): {\n endUserId: string | null;\n sessionId: string;\n isPreexistingUser: boolean;\n initialized: boolean;\n } {\n return {\n endUserId: this.endUserId,\n sessionId: this.sessionId,\n isPreexistingUser: this.isPreexistingUser(),\n initialized: this.initialized\n };\n }\n\n // ===== PROPERTY MANAGEMENT METHODS =====\n\n /**\n * Set a session property that will be included in all events for this session\n */\n public setSessionProperty(key: string, value: any): void {\n this.propertyManager.setSessionProperty(key, value);\n }\n\n /**\n * Set multiple session properties\n */\n public setSessionProperties(properties: Record<string, any>): void {\n this.propertyManager.setSessionProperties(properties);\n }\n\n /**\n * Get a session property\n */\n public getSessionProperty(key: string): any {\n return this.propertyManager.getSessionProperty(key);\n }\n\n /**\n * Remove a session property\n */\n public removeSessionProperty(key: string): void {\n this.propertyManager.removeSessionProperty(key);\n }\n\n /**\n * Set a user property that will be included in all events\n */\n public setUserProperty(key: string, value: any): void {\n this.propertyManager.setUserProperty(key, value);\n }\n\n /**\n * Set multiple user properties\n */\n public setUserProperties(properties: Record<string, any>): void {\n this.propertyManager.setUserProperties(properties);\n }\n\n /**\n * Get a user property\n */\n public getUserProperty(key: string): any {\n return this.propertyManager.getUserProperty(key);\n }\n\n /**\n * Remove a user property\n */\n public removeUserProperty(key: string): void {\n this.propertyManager.removeUserProperty(key);\n }\n\n /**\n * Set a property only if it hasn't been set before\n */\n public setOnce(key: string, value: any, scope: 'session' | 'user' = 'user'): void {\n this.propertyManager.setOnce(key, value, scope);\n }\n\n /**\n * Clear all session properties\n */\n public clearSessionProperties(): void {\n this.propertyManager.clearSessionProperties();\n }\n\n /**\n * Clear all user properties\n */\n public clearUserProperties(): void {\n this.propertyManager.clearUserProperties();\n }\n\n /**\n * Get all properties for debugging\n */\n public getAllProperties(): {\n automatic: Record<string, any>;\n session: Record<string, any>;\n user: Record<string, any>;\n initial: Record<string, any>;\n } {\n return this.propertyManager.getAllProperties();\n }\n}\n\n// Only expose to window object in browser environments\nif (isBrowser) {\n (window as any).HumanBehaviorTracker = HumanBehaviorTracker;\n}\n\nexport default HumanBehaviorTracker;\n","/**\n * Global tracker utility functions\n * Provides helper functions for accessing the global HumanBehavior tracker instance\n */\n\n/**\n * Identifies a user using the global HumanBehavior tracker\n * @param userProperties - User properties to identify with\n * @returns Promise<string> - The endUserId if successful, null if tracker not found\n */\nexport function identifyUserGlobally(userProperties: Record<string, any>): Promise<string> | null {\n const globalTracker = (globalThis as any).__humanBehaviorGlobalTracker;\n \n if (globalTracker?.identifyUser) {\n return globalTracker.identifyUser({ userProperties });\n } else {\n console.warn('HumanBehavior tracker not found. Make sure the SDK is initialized.');\n return null;\n }\n}\n\n/**\n * Sends an event using the global HumanBehavior tracker\n * @param eventName - Name of the event\n * @param properties - Event properties\n * @returns Promise<boolean> - True if successful, false if tracker not found\n */\nexport function sendEventGlobally(eventName: string, properties?: Record<string, any>): Promise<boolean> | null {\n const globalTracker = (globalThis as any).__humanBehaviorGlobalTracker;\n \n if (globalTracker?.track) {\n return globalTracker.track(eventName, properties);\n } else {\n console.warn('HumanBehavior tracker not found. Make sure the SDK is initialized.');\n return null;\n }\n}\n\n/**\n * Checks if the global HumanBehavior tracker is available\n * @returns boolean - True if tracker is available\n */\nexport function isGlobalTrackerAvailable(): boolean {\n const globalTracker = (globalThis as any).__humanBehaviorGlobalTracker;\n return !!(globalTracker?.identifyUser);\n}\n"],"names":["LogLevel","logger","constructor","config","this","level","ERROR","enableConsole","enableStorage","isBrowser","window","setConfig","shouldLog","formatMessage","message","args","Date","toISOString","error","formattedMessage","console","logToStorage","warn","WARN","info","INFO","log","debug","DEBUG","logs","JSON","parse","localStorage","getItem","logEntry","length","undefined","timestamp","now","push","splice","setItem","stringify","e","getLogs","clearLogs","removeItem","logError","logWarn","logInfo","logDebug","MAX_CHUNK_SIZE_BYTES","isChunkSizeExceeded","currentChunk","newEvent","sessionId","TextEncoder","encode","events","splitLargeEvent","event","simplifiedEvent","largeProperties","forEach","prop","type","url","pathname","Object","fromEntries","entries","filter","key","value","includes","HumanBehaviorAPI","apiKey","ingestionUrl","monthlyLimitReached","baseUrl","checkMonthlyLimit","init","userId","endUserId","entryURL","referrer","location","href","document","response","fetch","method","headers","Authorization","Referer","body","status","ok","errorText","text","Error","statusText","responseJson","json","sendEvents","validEvents","sendEventsChunked","results","flat","sendUserData","userData","payload","userAttributes","posthogName","email","name","result","sendUserAuth","authFields","sendBeaconEvents","blob","Blob","navigator","sendBeacon","sendCustomEvent","eventName","eventProperties","sendCustomEventBatch","RedactionManager","options","redactedText","unredactedFields","Set","redactedFields","redactionMode","excludeSelectors","redactionStrategy","mode","unredactFields","setFieldsToUnredact","defaultMarks","fieldsToRedact","redactFields","setFieldsToRedact","legacyRedactFields","userFields","fields","clear","field","add","size","Array","from","applyRedactionClasses","validFields","isPasswordSelector","applyUnredactionClasses","delete","clearUnredactedFields","removeUnredactionClasses","hasUnredactedFields","getRedactionMode","getUnredactedFields","getMaskTextSelector","join","readyState","selector","elements","querySelectorAll","element","classList","remove","some","pattern","toLowerCase","replace","getOriginalValue","HTMLInputElement","HTMLTextAreaElement","isElementUnredacted","shouldUnredactElement","matches","detectDeviceType","userAgent","screenWidth","screen","width","screenHeight","height","test","extractDomain","URL","hostname","getDeviceInfo","device_type","browser","browser_version","os","os_version","screen_resolution","viewport_size","color_depth","timezone","language","languages","match","detectBrowser","version","versionNum","parseFloat","detectOS","innerWidth","innerHeight","colorDepth","Intl","DateTimeFormat","resolvedOptions","timeZone","raw_user_agent","getLocationInfo","current_url","search","hash","title","referrer_domain","initial_referrer","initial_referrer_domain","currentUrl","utmParams","urlObj","searchParams","get","extractUTMParams","initial_host","getAutomaticProperties","getInitialProperties","locationInfo","initial_url","initial_pathname","initial_utm_source","utm_source","initial_utm_medium","utm_medium","initial_utm_campaign","utm_campaign","initial_utm_term","utm_term","initial_utm_content","utm_content","getCurrentPageProperties","PropertyManager","sessionProperties","userProperties","initialProperties","isInitialized","enableAutomaticProperties","enableSessionProperties","enableUserProperties","propertyDenylist","automaticProperties","initialize","loadSessionProperties","getEventProperties","properties","assign","setSessionProperty","applyDenylist","getAutomaticPropertiesWithGeoIP","geoIPProperties","saveSessionProperties","setSessionProperties","getSessionProperty","removeSessionProperty","setUserProperty","setUserProperties","getUserProperty","removeUserProperty","setOnce","scope","clearSessionProperties","clearUserProperties","reset","sessionStorage","stored","deniedKey","updateAutomaticProperties","getAllProperties","automatic","session","user","initial","HumanBehaviorTracker","isTrackerStarted","isStarted","setupDomReadyHandler","onDomReady","addEventListener","capture","interval","setInterval","clearInterval","setTimeout","isDomReady","requestQueue","request","processRequest","domReadyHandlers","handler","queueRequest","addEvent","identifyUser","trackPageView","registerDomReadyHandler","suppressConsoleErrors","originalConsoleError","apply","originalConsoleWarn","preventDefault","__humanBehaviorGlobalTracker","logLevel","configureLogging","tracker","maxQueueSize","recordCanvas","setUnredactedFields","enableAutomaticTracking","setupAutomaticTracking","automaticTrackingOptions","start","eventQueue","isProcessing","flushInterval","FLUSH_INTERVAL_MS","initialized","initializationPromise","originalConsole","consoleTrackingEnabled","navigationTrackingEnabled","previousUrl","originalPushState","originalReplaceState","navigationListeners","_connectionBlocked","recordInstance","sessionStartTime","rrwebRecord","fullSnapshotTimeout","api","MAX_QUEUE_SIZE","redactionManager","propertyManager","existingSessionId","lastActivity","fifteenMinutesAgo","parseInt","toString","uuidv1","catch","getCookie","setupPageUnloadHandler","setupNavigationTracking","initServerAsync","initResponse","setCookie","stack","ensureInitialized","history","pushState","replaceState","trackNavigationEvent","takeFullSnapshot","popstateListener","removeEventListener","hashchangeListener","fromUrl","toUrl","navigationData","to","data","eventType","pageViewProperties","navigationType","customEvent","pageViewData","enhancedProperties","customEventData","fallbackError","trackButtons","trackLinks","trackForms","includeText","includeClasses","setupAutomaticButtonTracking","setupAutomaticFormTracking","async","target","tagName","closest","button","buttonId","id","buttonType","page","buttonText","textContent","trim","buttonClass","className","keys","setupAutomaticLinkTracking","form","formData","FormData","formId","formAction","action","formMethod","formClass","cleanupNavigationTracking","cleanup","none","enableConsoleTracking","trackConsoleEvent","disableConsoleTracking","consoleData","map","arg","String","visibilityState","flushEvents","updateActivity","viewLogs","originalEndUserId","getUserAttributes","startRecording","record","emit","addRecordingEvent","maskTextSelector","maskTextFn","maskAllInputs","maskInputOptions","password","textarea","number","tel","date","time","month","week","maskInputFn","HTMLElement","repeat","shouldShow","slimDOMOptions","collectFonts","inlineStylesheet","recordCrossOriginIframes","sampling","canvas","dataURLOptions","quality","hooks","input","node","querySelector","checkDomReady","once","clearTimeout","requestAnimationFrame","stop","hasData","hasNode","shift","eventsToProcess","fullSnapshots","daysToExpire","setTime","getTime","expires","toUTCString","cookie","localStorageError","nameEQ","ca","split","i","c","charAt","substring","indexOf","cookieValue","localStorageValue","deleteCookie","logout","userIdCookieName","redact","setRedactedFields","restartWithNewRedaction","getSessionId","getCurrentUrl","getSnapshotFrequencyInfo","sessionDuration","currentInterval","currentThreshold","phase","testConnection","success","getConnectionStatus","recommendations","blocked","isPreexistingUser","existingEndUserId","getUserInfo","identifyUserGlobally","globalTracker","globalThis","sendEventGlobally","track","isGlobalTrackerAvailable"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/utils/logger.ts","../src/api.ts","../src/redact.ts","../src/utils/property-detector.ts","../src/utils/property-manager.ts","../src/tracker.ts","../src/utils/global-tracker.ts"],"sourcesContent":["export enum LogLevel {\n NONE = 0,\n ERROR = 1,\n WARN = 2,\n INFO = 3,\n DEBUG = 4\n}\n\nexport interface LoggerConfig {\n level: LogLevel;\n enableConsole: boolean;\n enableStorage: boolean;\n}\n\nclass Logger {\n private config: LoggerConfig = {\n level: LogLevel.ERROR, // Default to only errors in production\n enableConsole: true,\n enableStorage: false\n };\n\n private isBrowser = typeof window !== 'undefined';\n\n constructor(config?: Partial<LoggerConfig>) {\n if (config) {\n this.config = { ...this.config, ...config };\n }\n }\n\n setConfig(config: Partial<LoggerConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n private shouldLog(level: LogLevel): boolean {\n return level <= this.config.level;\n }\n\n private formatMessage(level: string, message: string, ...args: any[]): string {\n const timestamp = new Date().toISOString();\n return `[HumanBehavior ${level}] ${timestamp}: ${message}`;\n }\n\n error(message: string, ...args: any[]): void {\n if (!this.shouldLog(LogLevel.ERROR)) return;\n \n const formattedMessage = this.formatMessage('ERROR', message);\n \n if (this.config.enableConsole) {\n console.error(formattedMessage, ...args);\n }\n \n if (this.config.enableStorage && this.isBrowser) {\n this.logToStorage(formattedMessage, args);\n }\n }\n\n warn(message: string, ...args: any[]): void {\n if (!this.shouldLog(LogLevel.WARN)) return;\n \n const formattedMessage = this.formatMessage('WARN', message);\n \n if (this.config.enableConsole) {\n console.warn(formattedMessage, ...args);\n }\n \n if (this.config.enableStorage && this.isBrowser) {\n this.logToStorage(formattedMessage, args);\n }\n }\n\n info(message: string, ...args: any[]): void {\n if (!this.shouldLog(LogLevel.INFO)) return;\n \n const formattedMessage = this.formatMessage('INFO', message);\n \n if (this.config.enableConsole) {\n console.log(formattedMessage, ...args);\n }\n \n if (this.config.enableStorage && this.isBrowser) {\n this.logToStorage(formattedMessage, args);\n }\n }\n\n debug(message: string, ...args: any[]): void {\n if (!this.shouldLog(LogLevel.DEBUG)) return;\n \n const formattedMessage = this.formatMessage('DEBUG', message);\n \n if (this.config.enableConsole) {\n console.log(formattedMessage, ...args);\n }\n \n if (this.config.enableStorage && this.isBrowser) {\n this.logToStorage(formattedMessage, args);\n }\n }\n\n private logToStorage(message: string, args: any[]): void {\n try {\n const logs = JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');\n const logEntry = {\n message,\n args: args.length > 0 ? args : undefined,\n timestamp: Date.now()\n };\n logs.push(logEntry);\n \n // Keep only last 1000 logs to prevent storage bloat\n if (logs.length > 1000) {\n logs.splice(0, logs.length - 1000);\n }\n \n localStorage.setItem('human_behavior_logs', JSON.stringify(logs));\n } catch (e) {\n // Silently fail if storage is not available\n }\n }\n\n getLogs(): any[] {\n if (!this.isBrowser) return [];\n \n try {\n return JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');\n } catch (e) {\n return [];\n }\n }\n\n clearLogs(): void {\n if (this.isBrowser) {\n localStorage.removeItem('human_behavior_logs');\n }\n }\n}\n\n// Create singleton instance\nexport const logger = new Logger();\n\n// Export convenience methods\nexport const logError = (message: string, ...args: any[]) => logger.error(message, ...args);\nexport const logWarn = (message: string, ...args: any[]) => logger.warn(message, ...args);\nexport const logInfo = (message: string, ...args: any[]) => logger.info(message, ...args);\nexport const logDebug = (message: string, ...args: any[]) => logger.debug(message, ...args); ","import { logError, logInfo, logDebug, logWarn } from './utils/logger';\n\nexport const MAX_CHUNK_SIZE_BYTES = 1024 * 1024; // 1MB chunk size - more conservative\n\nexport function isChunkSizeExceeded(currentChunk: any[], newEvent: any, sessionId: string): boolean {\n const nextChunkSize = new TextEncoder().encode(JSON.stringify({\n sessionId,\n events: [...currentChunk, newEvent]\n })).length;\n \n return nextChunkSize > MAX_CHUNK_SIZE_BYTES;\n}\n\nexport function validateSingleEventSize(event: any, sessionId: string): void {\n const singleEventSize = new TextEncoder().encode(JSON.stringify({\n sessionId,\n events: [event]\n })).length;\n\n if (singleEventSize > MAX_CHUNK_SIZE_BYTES) {\n // Instead of throwing, log a warning and suggest reducing event size\n logWarn(`Single event size (${singleEventSize} bytes) exceeds maximum chunk size (${MAX_CHUNK_SIZE_BYTES} bytes). Consider reducing event data size.`);\n }\n}\n\n\n\n\n\nexport function splitLargeEvent(event: any, sessionId: string): any[] {\n // ✅ SIMPLE VALIDATION\n if (!event || typeof event !== 'object') {\n return [];\n }\n \n const eventSize = new TextEncoder().encode(JSON.stringify({\n sessionId,\n events: [event]\n })).length;\n\n if (eventSize <= MAX_CHUNK_SIZE_BYTES) {\n return [event];\n }\n\n // If event is too large, try to split it by removing large properties\n const simplifiedEvent = { ...event };\n \n // Remove potentially large properties\n const largeProperties = ['screenshot', 'html', 'dom', 'fullText', 'innerHTML', 'outerHTML'];\n largeProperties.forEach(prop => {\n if (simplifiedEvent[prop]) {\n delete simplifiedEvent[prop];\n }\n });\n\n // Check if simplified event is now small enough\n const simplifiedSize = new TextEncoder().encode(JSON.stringify({\n sessionId,\n events: [simplifiedEvent]\n })).length;\n\n if (simplifiedSize <= MAX_CHUNK_SIZE_BYTES) {\n return [simplifiedEvent];\n }\n\n // If still too large, create a minimal event\n const minimalEvent = {\n type: event.type,\n timestamp: event.timestamp,\n url: event.url,\n pathname: event.pathname,\n // Keep only essential properties\n ...Object.fromEntries(\n Object.entries(event).filter(([key, value]) => \n !largeProperties.includes(key) && \n typeof value !== 'object' && \n typeof value !== 'string' || \n (typeof value === 'string' && value.length < 1000)\n )\n )\n };\n\n return [minimalEvent];\n}\n\nexport class HumanBehaviorAPI {\n private apiKey: string;\n private baseUrl: string;\n private monthlyLimitReached: boolean = false;\n\n constructor({ apiKey, ingestionUrl }: { apiKey: string, ingestionUrl: string }) {\n this.apiKey = apiKey;\n this.baseUrl = ingestionUrl;\n }\n\n private checkMonthlyLimit(): boolean {\n if (this.monthlyLimitReached) {\n return false;\n }\n return true;\n }\n\n public async init(sessionId: string, userId: string | null) {\n // Check if monthly limit is already reached - silently skip if so\n if (!this.checkMonthlyLimit()) {\n // Silently return success to avoid any errors\n return {\n sessionId: sessionId,\n endUserId: userId\n };\n }\n\n // Get current page URL and referrer if in browser environment\n let entryURL = null;\n let referrer = null;\n \n if (typeof window !== 'undefined') {\n entryURL = window.location.href;\n referrer = document.referrer;\n }\n\n logInfo('API init called with:', { sessionId, userId, entryURL, referrer, baseUrl: this.baseUrl });\n\n try {\n const response = await fetch(`${this.baseUrl}/api/ingestion/init`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Referer': referrer || ''\n },\n body: JSON.stringify({\n sessionId: sessionId,\n endUserId: userId,\n entryURL: entryURL,\n referrer: referrer\n })\n });\n\n logInfo('API init response status:', response.status);\n\n if (!response.ok) {\n if (response.status === 429) {\n this.monthlyLimitReached = true;\n // Silently return success to avoid any errors\n return {\n sessionId: sessionId,\n endUserId: userId\n };\n }\n const errorText = await response.text();\n logError('API init failed:', response.status, errorText);\n throw new Error(`Failed to initialize ingestion: ${response.statusText} - ${errorText}`);\n } \n\n const responseJson = await response.json();\n \n // Check for monthly limit flag in successful response\n if (responseJson.monthlyLimitReached === true) {\n this.monthlyLimitReached = true;\n logInfo('Monthly limit reached detected from server response');\n }\n \n logInfo('API init success:', responseJson);\n return {\n sessionId: responseJson.sessionId,\n endUserId: responseJson.endUserId\n }\n } catch (error) {\n logError('API init error:', error);\n throw error;\n }\n }\n\n /**\n * Server detects IP from HTTP requests automatically\n */\n\n async sendEvents(events: any[], sessionId: string, userId: string) {\n // ✅ SIMPLE VALIDATION FOR ALL EVENTS\n const validEvents = events.filter(event => event && typeof event === 'object');\n \n const response = await fetch(`${this.baseUrl}/api/ingestion/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify({\n sessionId,\n events: validEvents,\n endUserId: userId\n })\n });\n \n if (!response.ok) {\n if (response.status === 429) {\n this.monthlyLimitReached = true;\n throw new Error(`429: Monthly video processing limit reached`);\n }\n throw new Error(`Failed to send events: ${response.statusText}`);\n }\n \n // Check for monthly limit flag in successful response\n const responseJson = await response.json();\n if (responseJson.monthlyLimitReached === true) {\n this.monthlyLimitReached = true;\n logInfo('Monthly limit reached detected from events response');\n }\n }\n \n async sendEventsChunked(events: any[], sessionId: string, userId?: string) {\n // Check if monthly limit is already reached - silently skip if so\n if (!this.checkMonthlyLimit()) {\n // Silently return success to avoid any errors\n return [];\n }\n try {\n const results = [];\n let currentChunk: any[] = [];\n \n for (const event of events) {\n // ✅ SIMPLE VALIDATION FOR ALL EVENTS\n if (!event || typeof event !== 'object') {\n continue;\n }\n \n if (isChunkSizeExceeded(currentChunk, event, sessionId)) {\n // If current chunk is not empty, send it first\n if (currentChunk.length > 0) {\n logDebug(`[SDK] Sending chunk with ${currentChunk.length} events`);\n const response = await fetch(`${this.baseUrl}/api/ingestion/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify({\n sessionId,\n events: currentChunk,\n endUserId: userId\n })\n });\n \n if (!response.ok) {\n if (response.status === 429) {\n this.monthlyLimitReached = true;\n // Silently skip this chunk\n return results.flat();\n }\n throw new Error(`Failed to send events: ${response.statusText}`);\n }\n \n const responseJson = await response.json();\n \n // Check for monthly limit flag in successful response\n if (responseJson.monthlyLimitReached === true) {\n this.monthlyLimitReached = true;\n logInfo('Monthly limit reached detected from chunked events response');\n }\n \n results.push(responseJson);\n currentChunk = [];\n }\n\n // Handle large events by splitting them\n const splitEvents = splitLargeEvent(event, sessionId);\n \n // Start new chunk with the split events\n currentChunk = splitEvents;\n } else {\n // Add event to current chunk\n currentChunk.push(event);\n }\n }\n \n // Send any remaining events\n if (currentChunk.length > 0) {\n logDebug(`[SDK] Sending final chunk with ${currentChunk.length} events`);\n const response = await fetch(`${this.baseUrl}/api/ingestion/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify({\n sessionId,\n events: currentChunk,\n endUserId: userId\n })\n });\n \n if (!response.ok) {\n if (response.status === 429) {\n this.monthlyLimitReached = true;\n // Silently skip this chunk\n return results.flat();\n }\n throw new Error(`Failed to send events: ${response.statusText}`);\n }\n \n const responseJson = await response.json();\n \n // Check for monthly limit flag in successful response\n if (responseJson.monthlyLimitReached === true) {\n this.monthlyLimitReached = true;\n logInfo('Monthly limit reached detected from final chunked events response');\n }\n \n results.push(responseJson);\n }\n \n return results.flat();\n } catch (error) {\n logError('Error sending events:', error);\n throw error;\n }\n }\n\n async sendUserData(userId: string, userData: Record<string, any>, sessionId: string) {\n try {\n const payload = {\n userId: userId,\n userAttributes: userData,\n sessionId: sessionId,\n posthogName: userData.email || userData.name || null // Update user name with email\n };\n \n logDebug('Sending user data to server:', payload);\n \n const response = await fetch(`${this.baseUrl}/api/ingestion/user`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify(payload)\n });\n \n if (!response.ok) {\n throw new Error(`Failed to send user data: ${response.statusText} with API key: ${this.apiKey}`);\n }\n \n const result = await response.json();\n logDebug('Server response:', result);\n return result;\n } catch (error) {\n logError('Error sending user data:', error);\n throw error;\n }\n }\n\n async sendUserAuth(userId: string, userData: Record<string, any>, sessionId: string, authFields: string[]) {\n try {\n const response = await fetch(`${this.baseUrl}/api/ingestion/user/auth`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify({\n userId: userId,\n userAttributes: userData,\n sessionId: sessionId,\n authFields: authFields\n })\n });\n \n if (!response.ok) {\n throw new Error(`Failed to authenticate user: ${response.statusText} with API key: ${this.apiKey}`);\n }\n // Returns: { success: true, message: '...', userId: '...' }\n return await response.json();\n } catch (error) {\n logError('Error authenticating user:', error);\n throw error;\n }\n }\n\n public sendBeaconEvents(events: any[], sessionId: string) {\n // Create JSON payload that matches the server's expected format\n const payload = {\n sessionId: sessionId,\n events: events,\n endUserId: null, // Beacon doesn't have user context\n apiKey: this.apiKey // Include API key in body since beacon can't use headers\n };\n\n // Convert to Blob for sendBeacon\n const blob = new Blob([JSON.stringify(payload)], {\n type: 'application/json'\n });\n\n const success = navigator.sendBeacon(\n `${this.baseUrl}/api/ingestion/events`, \n blob\n );\n\n return success;\n }\n\n async sendCustomEvent(sessionId: string, eventName: string, eventProperties?: Record<string, any>, endUserId?: string | null) {\n logInfo('[SDK] Sending custom event', { sessionId, eventName, eventProperties, endUserId });\n try {\n const response = await fetch(`${this.baseUrl}/api/ingestion/customEvent`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify({\n sessionId: sessionId,\n eventName: eventName,\n eventProperties: eventProperties || {},\n endUserId: endUserId || null\n })\n });\n \n logInfo('[SDK] Custom event response', { status: response.status, statusText: response.statusText });\n \n if (!response.ok) {\n const errorText = await response.text();\n logError('[SDK] Failed to send custom event', { status: response.status, statusText: response.statusText, errorText });\n throw new Error(`Failed to send custom event: ${response.status} ${response.statusText} - ${errorText}`);\n }\n \n const json = await response.json();\n logDebug('[SDK] Custom event success', json);\n return json;\n } catch (error) {\n logError('[SDK] Error sending custom event', error, { sessionId, eventName, eventProperties });\n throw error;\n }\n }\n\n async sendCustomEventBatch(sessionId: string, events: Array<{ eventName: string; eventProperties?: Record<string, any> }>, endUserId?: string | null) {\n try {\n const response = await fetch(`${this.baseUrl}/api/ingestion/customEvent/batch`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify({\n sessionId: sessionId,\n events: events,\n endUserId: endUserId || null\n })\n });\n \n if (!response.ok) {\n throw new Error(`Failed to send custom event batch: ${response.statusText}`);\n }\n \n return await response.json();\n } catch (error) {\n logError('Error sending custom event batch:', error);\n throw error;\n }\n }\n}\n","// Simplified redaction functionality for HumanBehavior SDK\n// Since rrweb auto-redacts all input fields by default, this module only handles\n// selectively unredacting specific fields (except passwords which remain protected)\n\nimport { logDebug, logWarn } from './utils/logger';\n\n// Check if we're in a browser environment\nconst isBrowser = typeof window !== 'undefined';\n\nexport interface RedactionOptions {\n redactedText?: string;\n excludeSelectors?: string[];\n userFields?: string[]; // Fields that the user wants to unredact (legacy)\n redactionStrategy?: {\n mode: 'privacy-first' | 'visibility-first';\n unredactFields?: string[]; // Fields to make visible (when mode: 'privacy-first')\n redactFields?: string[]; // Fields to hide (when mode: 'visibility-first')\n };\n legacyRedactFields?: string[]; // For backward compatibility\n}\n\nexport class RedactionManager {\n private redactedText: string = '[REDACTED]';\n private unredactedFields: Set<string> = new Set(); // Fields that user wants to unredact\n private redactedFields: Set<string> = new Set(); // Fields that user wants to redact\n private redactionMode: 'privacy-first' | 'visibility-first' = 'privacy-first';\n private excludeSelectors: string[] = [\n '[data-no-redact=\"true\"]',\n '.human-behavior-no-redact'\n ];\n\n constructor(options?: RedactionOptions) {\n if (options?.redactedText) {\n this.redactedText = options.redactedText;\n }\n if (options?.excludeSelectors) {\n this.excludeSelectors = [...this.excludeSelectors, ...options.excludeSelectors];\n }\n \n // Handle new redaction strategy\n if (options?.redactionStrategy) {\n this.redactionMode = options.redactionStrategy.mode;\n // debug removed\n \n if (this.redactionMode === 'privacy-first') {\n // Privacy-first: everything redacted by default, unredact specific fields\n if (options.redactionStrategy.unredactFields) {\n this.setFieldsToUnredact(options.redactionStrategy.unredactFields);\n }\n } else {\n // Visibility-first: everything visible by default, redact specific fields\n // Default to only redacting passwords if no specific fields provided\n // Support zero-config authoring: allow data-hb-redact=\"true\" marks\n const defaultMarks = ['input[type=\"password\"]', '[data-hb-redact=\"true\"]'];\n const fieldsToRedact = options.redactionStrategy.redactFields && options.redactionStrategy.redactFields.length > 0\n ? options.redactionStrategy.redactFields\n : defaultMarks;\n this.setFieldsToRedact(fieldsToRedact);\n }\n }\n \n // Handle legacy redactFields (backward compatibility)\n if (options?.legacyRedactFields) {\n this.setFieldsToUnredact(options.legacyRedactFields);\n }\n \n // Handle legacy userFields\n if (options?.userFields) {\n this.setFieldsToUnredact(options.userFields);\n }\n }\n\n /**\n * Set specific fields to be redacted (for visibility-first mode)\n * @param fields Array of CSS selectors for fields to redact\n */\n public setFieldsToRedact(fields: string[]): void {\n this.redactedFields.clear();\n \n // Always include password fields in redacted list\n const passwordFields = [\n 'input[type=\"password\"]',\n 'input[type=\"password\" i]',\n '[type=\"password\"]',\n '[type=\"password\" i]'\n ];\n \n // Add password fields and user-specified fields\n [...passwordFields, ...fields].forEach(field => {\n this.redactedFields.add(field);\n });\n \n if (this.redactedFields.size > 0) {\n logDebug(`Redaction: Active for ${this.redactedFields.size} field(s):`, Array.from(this.redactedFields));\n } else {\n logDebug('Redaction: No fields to redact');\n }\n \n this.applyRedactionClasses();\n }\n\n /**\n * Set specific fields to be unredacted (everything else stays redacted by rrweb)\n * @param fields Array of CSS selectors for fields to unredact\n */\n public setFieldsToUnredact(fields: string[]): void {\n this.unredactedFields.clear();\n \n // Filter out password fields (they cannot be unredacted)\n const validFields = fields.filter(field => {\n const isPasswordField = this.isPasswordSelector(field);\n if (isPasswordField) {\n logWarn(`Cannot unredact password field: ${field} - Password fields are always protected`);\n return false;\n }\n return true;\n });\n \n validFields.forEach(field => this.unredactedFields.add(field));\n \n if (validFields.length > 0) {\n logDebug(`Unredaction: Active for ${validFields.length} field(s):`, validFields);\n } else {\n logDebug('Unredaction: No valid fields to unredact');\n }\n \n this.applyUnredactionClasses();\n }\n\n /**\n * Remove specific fields from unredaction (they become redacted again)\n * @param fields Array of CSS selectors for fields to redact\n */\n public redactFields(fields: string[]): void {\n fields.forEach(field => {\n this.unredactedFields.delete(field);\n });\n \n if (this.unredactedFields.size > 0) {\n logDebug(`Unredaction: Removed ${fields.length} field(s), ${this.unredactedFields.size} remaining:`, Array.from(this.unredactedFields));\n } else {\n logDebug('Unredaction: All fields redacted');\n }\n \n this.applyUnredactionClasses();\n }\n\n /**\n * Clear all unredacted fields (everything becomes redacted again)\n */\n public clearUnredactedFields(): void {\n this.unredactedFields.clear();\n logDebug('Unredaction: All fields cleared, everything redacted');\n \n this.removeUnredactionClasses();\n }\n\n /**\n * Check if any fields are currently unredacted\n */\n public hasUnredactedFields(): boolean {\n return this.unredactedFields.size > 0;\n }\n\n /**\n * Get the current redaction mode\n */\n public getRedactionMode(): 'privacy-first' | 'visibility-first' {\n return this.redactionMode;\n }\n\n /**\n * Get the currently unredacted fields\n */\n public getUnredactedFields(): string[] {\n return Array.from(this.unredactedFields);\n }\n\n /**\n * Get CSS selectors for rrweb masking configuration\n * Returns null if no fields are unredacted (everything stays redacted)\n */\n public getMaskTextSelector(): string | null {\n if (this.redactionMode === 'privacy-first') {\n // Privacy-first: mask everything except unredacted fields\n if (this.unredactedFields.size === 0) {\n return null; // Everything stays redacted\n }\n return Array.from(this.unredactedFields).join(',');\n } else {\n // Visibility-first: mask only redacted fields\n if (this.redactedFields.size === 0) {\n return null; // Nothing to redact\n }\n return Array.from(this.redactedFields).join(',');\n }\n }\n\n /**\n * Apply redaction classes to DOM elements (for visibility-first mode)\n * Adds 'rr-mask' class to elements that should be redacted\n */\n public applyRedactionClasses(): void {\n if (this.redactedFields.size === 0) {\n return;\n }\n\n // Check if DOM is ready\n if (typeof document === 'undefined' || document.readyState === 'loading') {\n logDebug('DOM not ready, deferring redaction class application');\n return;\n }\n\n //console.log('🔍 Applying redaction classes to fields:', Array.from(this.redactedFields));\n\n // Add 'rr-mask' class to redacted elements\n this.redactedFields.forEach(selector => {\n try {\n const elements = document.querySelectorAll(selector);\n elements.forEach(element => {\n if (element && element.classList) {\n element.classList.add('rr-mask');\n }\n });\n logDebug(`Added rr-mask class to ${elements.length} element(s) for selector: ${selector}`);\n } catch (e) {\n logWarn(`Invalid selector: ${selector}`);\n }\n });\n }\n\n /**\n * Apply unredaction classes to DOM elements\n * Removes 'rr-mask' class from elements that should be unredacted\n */\n public applyUnredactionClasses(): void {\n if (this.unredactedFields.size === 0) {\n return;\n }\n\n // Check if DOM is ready\n if (typeof document === 'undefined' || document.readyState === 'loading') {\n logDebug('DOM not ready, deferring unredaction class application');\n return;\n }\n\n // Remove 'rr-mask' class from unredacted elements\n this.unredactedFields.forEach(selector => {\n try {\n const elements = document.querySelectorAll(selector);\n elements.forEach(element => {\n if (element && element.classList) {\n element.classList.remove('rr-mask');\n }\n });\n logDebug(`Removed rr-mask class from ${elements.length} element(s) for selector: ${selector}`);\n } catch (e) {\n logWarn(`Invalid selector: ${selector}`);\n }\n });\n }\n\n /**\n * Remove all unredaction classes from DOM elements\n */\n public removeUnredactionClasses(): void {\n // Note: This doesn't add 'rr-mask' classes back - rrweb handles that automatically\n logDebug('Unredaction classes removed');\n }\n\n /**\n * Check if a selector represents a password field\n */\n private isPasswordSelector(selector: string): boolean {\n const passwordPatterns = [\n 'input[type=\"password\"]',\n 'input[type=\"password\" i]',\n '[type=\"password\"]',\n '[type=\"password\" i]'\n ];\n \n return passwordPatterns.some(pattern => \n selector.toLowerCase().includes(pattern.toLowerCase().replace(/[\\[\\]]/g, ''))\n );\n }\n\n /**\n * Get the original value of an element (for debugging)\n */\n public getOriginalValue(element: HTMLElement): string | undefined {\n if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {\n return element.value;\n }\n return undefined;\n }\n\n /**\n * Check if an element is currently unredacted\n */\n public isElementUnredacted(element: HTMLElement): boolean {\n return this.shouldUnredactElement(element);\n }\n\n /**\n * Check if an element should be unredacted\n */\n public shouldUnredactElement(element: HTMLElement): boolean {\n if (this.redactionMode === 'privacy-first') {\n // Privacy-first: check if element is in unredacted fields\n if (this.unredactedFields.size === 0) {\n return false; // Nothing unredacted\n }\n\n // Check if any selector matches this element\n for (const selector of this.unredactedFields) {\n try {\n if (element.matches(selector)) {\n return true;\n }\n } catch (e) {\n logWarn(`Invalid selector: ${selector}`);\n }\n }\n return false;\n } else {\n // Visibility-first: check if element is NOT in redacted fields\n if (this.redactedFields.size === 0) {\n return true; // Nothing redacted, everything visible\n }\n\n // Check if any selector matches this element\n for (const selector of this.redactedFields) {\n try {\n if (element.matches(selector)) {\n return false; // Element is redacted\n }\n } catch (e) {\n logWarn(`Invalid selector: ${selector}`);\n }\n }\n return true; // Element is not redacted\n }\n }\n}\n\n// Export a default instance\nexport const redactionManager = new RedactionManager();\n\n// Export the class for custom instances\nexport default RedactionManager; ","/**\n * Automatic Property Detection for HumanBehavior SDK\n * Captures device type, location, and initial referrer information\n */\n\n// Check if we're in a browser environment\nconst isBrowser = typeof window !== 'undefined';\n\nexport interface DeviceInfo {\n device_type: string;\n browser: string;\n browser_version: string;\n os: string;\n os_version: string;\n device_model?: string;\n screen_resolution: string;\n viewport_size: string;\n color_depth: number;\n timezone: string;\n language: string;\n languages: string[];\n raw_user_agent?: string;\n}\n\nexport interface LocationInfo {\n current_url: string;\n pathname: string;\n search: string;\n hash: string;\n title: string;\n referrer: string;\n referrer_domain: string;\n initial_referrer: string;\n initial_referrer_domain: string;\n initial_host?: string;\n utm_source?: string;\n utm_medium?: string;\n utm_campaign?: string;\n utm_term?: string;\n utm_content?: string;\n}\n\nexport interface AutomaticProperties extends DeviceInfo, LocationInfo {}\n\n/**\n * Detect device type based on user agent and screen size\n */\nfunction detectDeviceType(): string {\n if (!isBrowser) return 'unknown';\n \n const userAgent = navigator.userAgent.toLowerCase();\n const screenWidth = window.screen.width;\n const screenHeight = window.screen.height;\n \n // Mobile detection\n if (/mobile|android|iphone|ipad|ipod|blackberry|windows phone/i.test(userAgent)) {\n if (/ipad/i.test(userAgent) || (screenWidth >= 768 && screenHeight >= 1024)) {\n return 'tablet';\n }\n return 'mobile';\n }\n \n // Desktop detection\n if (/windows|macintosh|linux/i.test(userAgent)) {\n return 'desktop';\n }\n \n return 'unknown';\n}\n\n/**\n * Extract browser information from user agent\n */\nfunction detectBrowser(): { browser: string; browser_version: string } {\n if (!isBrowser) return { browser: 'unknown', browser_version: 'unknown' };\n \n const userAgent = navigator.userAgent;\n \n // Chrome\n if (/chrome/i.test(userAgent) && !/edge/i.test(userAgent)) {\n const match = userAgent.match(/chrome\\/(\\d+)/i);\n return {\n browser: 'chrome',\n browser_version: match ? match[1] : 'unknown'\n };\n }\n \n // Firefox\n if (/firefox/i.test(userAgent)) {\n const match = userAgent.match(/firefox\\/(\\d+)/i);\n return {\n browser: 'firefox',\n browser_version: match ? match[1] : 'unknown'\n };\n }\n \n // Safari\n if (/safari/i.test(userAgent) && !/chrome/i.test(userAgent)) {\n const match = userAgent.match(/version\\/(\\d+)/i);\n return {\n browser: 'safari',\n browser_version: match ? match[1] : 'unknown'\n };\n }\n \n // Edge\n if (/edge/i.test(userAgent)) {\n const match = userAgent.match(/edge\\/(\\d+)/i);\n return {\n browser: 'edge',\n browser_version: match ? match[1] : 'unknown'\n };\n }\n \n // Internet Explorer\n if (/msie|trident/i.test(userAgent)) {\n const match = userAgent.match(/msie (\\d+)/i) || userAgent.match(/rv:(\\d+)/i);\n return {\n browser: 'ie',\n browser_version: match ? match[1] : 'unknown'\n };\n }\n \n return { browser: 'unknown', browser_version: 'unknown' };\n}\n\n/**\n * Extract operating system information from user agent\n */\nfunction detectOS(): { os: string; os_version: string } {\n if (!isBrowser) return { os: 'unknown', os_version: 'unknown' };\n \n const userAgent = navigator.userAgent;\n \n // Windows\n if (/windows/i.test(userAgent)) {\n const match = userAgent.match(/windows nt (\\d+\\.\\d+)/i);\n let version = 'unknown';\n if (match) {\n const versionNum = parseFloat(match[1]);\n if (versionNum === 10.0) version = '10';\n else if (versionNum === 6.3) version = '8.1';\n else if (versionNum === 6.2) version = '8';\n else if (versionNum === 6.1) version = '7';\n else version = match[1];\n }\n return { os: 'windows', os_version: version };\n }\n \n // macOS\n if (/macintosh|mac os x/i.test(userAgent)) {\n const match = userAgent.match(/mac os x (\\d+[._]\\d+)/i);\n return {\n os: 'macos',\n os_version: match ? match[1].replace('_', '.') : 'unknown'\n };\n }\n \n // iOS\n if (/iphone|ipad|ipod/i.test(userAgent)) {\n const match = userAgent.match(/os (\\d+[._]\\d+)/i);\n return {\n os: 'ios',\n os_version: match ? match[1].replace('_', '.') : 'unknown'\n };\n }\n \n // Android\n if (/android/i.test(userAgent)) {\n const match = userAgent.match(/android (\\d+\\.\\d+)/i);\n return {\n os: 'android',\n os_version: match ? match[1] : 'unknown'\n };\n }\n \n // Linux\n if (/linux/i.test(userAgent)) {\n return { os: 'linux', os_version: 'unknown' };\n }\n \n return { os: 'unknown', os_version: 'unknown' };\n}\n\n/**\n * Extract UTM parameters from URL\n */\nfunction extractUTMParams(url: string): Record<string, string> {\n const urlObj = new URL(url);\n const utmParams: Record<string, string> = {};\n \n const utmKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];\n \n utmKeys.forEach(key => {\n const value = urlObj.searchParams.get(key);\n if (value) {\n utmParams[key] = value;\n }\n });\n \n return utmParams;\n}\n\n/**\n * Extract domain from URL\n */\nfunction extractDomain(url: string): string {\n try {\n const urlObj = new URL(url);\n return urlObj.hostname;\n } catch {\n return '';\n }\n}\n\n/**\n * Get device information\n */\nexport function getDeviceInfo(): DeviceInfo {\n if (!isBrowser) {\n return {\n device_type: 'unknown',\n browser: 'unknown',\n browser_version: 'unknown',\n os: 'unknown',\n os_version: 'unknown',\n screen_resolution: 'unknown',\n viewport_size: 'unknown',\n color_depth: 0,\n timezone: 'unknown',\n language: 'unknown',\n languages: []\n };\n }\n \n const { browser, browser_version } = detectBrowser();\n const { os, os_version } = detectOS();\n \n return {\n device_type: detectDeviceType(),\n browser,\n browser_version,\n os,\n os_version,\n screen_resolution: `${window.screen.width}x${window.screen.height}`,\n viewport_size: `${window.innerWidth}x${window.innerHeight}`,\n color_depth: window.screen.colorDepth,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n language: navigator.language,\n languages: [...(navigator.languages || [navigator.language])],\n raw_user_agent: navigator.userAgent\n };\n}\n\n/**\n * Get location information\n */\nexport function getLocationInfo(): LocationInfo {\n if (!isBrowser) {\n return {\n current_url: '',\n pathname: '',\n search: '',\n hash: '',\n title: '',\n referrer: '',\n referrer_domain: '',\n initial_referrer: '',\n initial_referrer_domain: ''\n };\n }\n \n const currentUrl = window.location.href;\n const referrer = document.referrer;\n const utmParams = extractUTMParams(currentUrl);\n \n return {\n current_url: currentUrl,\n pathname: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n title: document.title,\n referrer,\n referrer_domain: extractDomain(referrer),\n initial_referrer: referrer,\n initial_referrer_domain: extractDomain(referrer),\n initial_host: window.location.hostname,\n ...utmParams\n };\n}\n\n/**\n * Get all automatic properties\n */\nexport function getAutomaticProperties(): AutomaticProperties {\n return {\n ...getDeviceInfo(),\n ...getLocationInfo()\n };\n}\n\n/**\n * Get initial properties that should be captured once per session\n */\nexport function getInitialProperties(): Record<string, any> {\n if (!isBrowser) return {};\n \n const locationInfo = getLocationInfo();\n \n return {\n initial_referrer: locationInfo.initial_referrer,\n initial_referrer_domain: locationInfo.initial_referrer_domain,\n initial_url: locationInfo.current_url,\n initial_pathname: locationInfo.pathname,\n initial_utm_source: locationInfo.utm_source,\n initial_utm_medium: locationInfo.utm_medium,\n initial_utm_campaign: locationInfo.utm_campaign,\n initial_utm_term: locationInfo.utm_term,\n initial_utm_content: locationInfo.utm_content\n };\n}\n\n/**\n * Get current page properties (changes with navigation)\n */\nexport function getCurrentPageProperties(): Record<string, any> {\n if (!isBrowser) return {};\n \n const locationInfo = getLocationInfo();\n \n return {\n current_url: locationInfo.current_url,\n pathname: locationInfo.pathname,\n search: locationInfo.search,\n hash: locationInfo.hash,\n title: locationInfo.title,\n referrer: locationInfo.referrer,\n referrer_domain: locationInfo.referrer_domain,\n utm_source: locationInfo.utm_source,\n utm_medium: locationInfo.utm_medium,\n utm_campaign: locationInfo.utm_campaign,\n utm_term: locationInfo.utm_term,\n utm_content: locationInfo.utm_content\n };\n}\n","/**\n * Property Manager for HumanBehavior SDK\n * Handles automatic properties, session properties, and user properties\n */\n\nimport { getAutomaticProperties, getInitialProperties, getCurrentPageProperties, AutomaticProperties } from './property-detector';\n\nexport interface Properties {\n [key: string]: any;\n}\n\nexport interface PropertyManagerConfig {\n enableAutomaticProperties?: boolean;\n enableSessionProperties?: boolean;\n enableUserProperties?: boolean;\n propertyDenylist?: string[];\n}\n\nexport class PropertyManager {\n private config: PropertyManagerConfig;\n private automaticProperties: AutomaticProperties;\n private sessionProperties: Properties = {};\n private userProperties: Properties = {};\n private initialProperties: Properties = {};\n private isInitialized: boolean = false;\n\n constructor(config: PropertyManagerConfig = {}) {\n this.config = {\n enableAutomaticProperties: true,\n enableSessionProperties: true,\n enableUserProperties: true,\n propertyDenylist: [],\n ...config\n };\n \n this.automaticProperties = getAutomaticProperties();\n this.initialize();\n }\n\n /**\n * Initialize the property manager\n */\n private initialize(): void {\n if (this.isInitialized) return;\n \n // Capture initial properties once\n this.initialProperties = getInitialProperties();\n \n // Load session properties from sessionStorage\n this.loadSessionProperties();\n \n this.isInitialized = true;\n }\n\n /**\n * Get all properties for an event\n */\n public getEventProperties(eventProperties: Properties = {}): Properties {\n const properties: Properties = { ...eventProperties };\n\n // Add automatic properties\n if (this.config.enableAutomaticProperties) {\n Object.assign(properties, this.getAutomaticProperties());\n }\n\n // Add session properties\n if (this.config.enableSessionProperties) {\n Object.assign(properties, this.sessionProperties);\n }\n\n // Add user properties\n if (this.config.enableUserProperties) {\n Object.assign(properties, this.userProperties);\n }\n\n // Add initial properties (only once per session)\n if (!this.sessionProperties['$initial_properties_captured']) {\n Object.assign(properties, this.initialProperties);\n this.setSessionProperty('$initial_properties_captured', true);\n }\n\n // Apply denylist\n this.applyDenylist(properties);\n\n return properties;\n }\n\n /**\n * Get automatic properties\n */\n public getAutomaticProperties(): Properties {\n return {\n ...this.automaticProperties,\n ...getCurrentPageProperties() // Always get fresh page properties\n };\n }\n\n /**\n * Get automatic properties with GeoIP data merged in\n */\n public getAutomaticPropertiesWithGeoIP(geoIPProperties: Record<string, any> = {}): Properties {\n return {\n ...this.automaticProperties,\n ...getCurrentPageProperties(), // Always get fresh page properties\n ...geoIPProperties\n };\n }\n\n /**\n * Set a session property\n */\n public setSessionProperty(key: string, value: any): void {\n this.sessionProperties[key] = value;\n this.saveSessionProperties();\n }\n\n /**\n * Set multiple session properties\n */\n public setSessionProperties(properties: Properties): void {\n Object.assign(this.sessionProperties, properties);\n this.saveSessionProperties();\n }\n\n /**\n * Get a session property\n */\n public getSessionProperty(key: string): any {\n return this.sessionProperties[key];\n }\n\n /**\n * Remove a session property\n */\n public removeSessionProperty(key: string): void {\n delete this.sessionProperties[key];\n this.saveSessionProperties();\n }\n\n /**\n * Set a user property\n */\n public setUserProperty(key: string, value: any): void {\n this.userProperties[key] = value;\n }\n\n /**\n * Set multiple user properties\n */\n public setUserProperties(properties: Properties): void {\n Object.assign(this.userProperties, properties);\n }\n\n /**\n * Get a user property\n */\n public getUserProperty(key: string): any {\n return this.userProperties[key];\n }\n\n /**\n * Remove a user property\n */\n public removeUserProperty(key: string): void {\n delete this.userProperties[key];\n }\n\n /**\n * Set a property only if it hasn't been set before\n */\n public setOnce(key: string, value: any, scope: 'session' | 'user' = 'user'): void {\n if (scope === 'session') {\n if (!(key in this.sessionProperties)) {\n this.setSessionProperty(key, value);\n }\n } else {\n if (!(key in this.userProperties)) {\n this.setUserProperty(key, value);\n }\n }\n }\n\n /**\n * Clear all session properties\n */\n public clearSessionProperties(): void {\n this.sessionProperties = {};\n this.saveSessionProperties();\n }\n\n /**\n * Clear all user properties\n */\n public clearUserProperties(): void {\n this.userProperties = {};\n }\n\n /**\n * Reset all properties\n */\n public reset(): void {\n this.clearSessionProperties();\n this.clearUserProperties();\n this.initialProperties = {};\n this.isInitialized = false;\n this.initialize();\n }\n\n /**\n * Load session properties from sessionStorage\n */\n private loadSessionProperties(): void {\n if (typeof sessionStorage === 'undefined') return;\n \n try {\n const stored = sessionStorage.getItem('hb_session_properties');\n if (stored) {\n this.sessionProperties = JSON.parse(stored);\n }\n } catch (error) {\n console.warn('Failed to load session properties:', error);\n }\n }\n\n /**\n * Save session properties to sessionStorage\n */\n private saveSessionProperties(): void {\n if (typeof sessionStorage === 'undefined') return;\n \n try {\n sessionStorage.setItem('hb_session_properties', JSON.stringify(this.sessionProperties));\n } catch (error) {\n console.warn('Failed to save session properties:', error);\n }\n }\n\n /**\n * Apply property denylist\n */\n private applyDenylist(properties: Properties): void {\n if (!this.config.propertyDenylist || this.config.propertyDenylist.length === 0) {\n return;\n }\n\n this.config.propertyDenylist.forEach(deniedKey => {\n delete properties[deniedKey];\n });\n }\n\n /**\n * Update automatic properties (call when page changes)\n */\n public updateAutomaticProperties(): void {\n this.automaticProperties = getAutomaticProperties();\n }\n\n /**\n * Get all properties for debugging\n */\n public getAllProperties(): {\n automatic: Properties;\n session: Properties;\n user: Properties;\n initial: Properties;\n } {\n return {\n automatic: this.getAutomaticProperties(),\n session: { ...this.sessionProperties },\n user: { ...this.userProperties },\n initial: { ...this.initialProperties }\n };\n }\n}\n","import { record } from '@rrweb/record';\nimport type { listenerHandler } from '@rrweb/types';\nimport { v1 as uuidv1 } from 'uuid';\nimport { HumanBehaviorAPI } from './api';\nimport { RedactionManager, RedactionOptions } from './redact';\nimport { logger, logError, logWarn, logInfo, logDebug } from './utils/logger';\nimport { PropertyManager, Properties } from './utils/property-manager';\n\n// Check if we're in a browser environment\nconst isBrowser = typeof window !== 'undefined';\n\n// Global declarations moved to browser-tracker.ts to avoid conflicts\n\nexport class HumanBehaviorTracker {\n private eventQueue: any[] = []; // Unified queue for all events (regular + recordings)\n \n private sessionId!: string;\n private userProperties: Record<string, any> = {};\n private isProcessing: boolean = false;\n \n private flushInterval: number | null = null;\n private readonly FLUSH_INTERVAL_MS = 3000; // Unified flush interval\n private readonly MAX_QUEUE_SIZE: number; // Configurable queue size\n \n private api!: HumanBehaviorAPI;\n private endUserId: string | null = null;\n private apiKey!: string;\n private initialized: boolean = false;\n public initializationPromise: Promise<void> | null = null;\n private monthlyLimitReached: boolean = false;\n private redactionManager!: RedactionManager;\n private propertyManager!: PropertyManager;\n \n private isDomReady: boolean = false;\n private requestQueue: any[] = [];\n private domReadyHandlers: Array<() => void> = [];\n \n // Console tracking properties\n private originalConsole: {\n log: typeof console.log;\n warn: typeof console.warn;\n error: typeof console.error;\n } | null = null;\n private consoleTrackingEnabled: boolean = false;\n\n // Navigation tracking properties\n public navigationTrackingEnabled: boolean = false;\n private currentUrl: string = '';\n private previousUrl: string = '';\n private originalPushState: typeof history.pushState | null = null;\n private originalReplaceState: typeof history.replaceState | null = null;\n private navigationListeners: Array<() => void> = [];\n private _connectionBlocked: boolean = false;\n private recordInstance: listenerHandler | null = null;\n private sessionStartTime: number = Date.now();\n private rrwebRecord: any = null;\n private fullSnapshotTimeout: number | null = null;\n private recordCanvas: boolean = false; // Store canvas recording preference\n private isStarted: boolean = false; // Guard against multiple start() calls\n \n /**\n * Check if the tracker has been started\n */\n public get isTrackerStarted(): boolean {\n return this.isStarted;\n }\n\n /**\n * DOM ready detection - more aggressive\n */\n private setupDomReadyHandler(): void {\n if (!isBrowser) {\n this.onDomReady();\n return;\n }\n\n // More aggressive DOM ready detection\n if (document.readyState === 'complete' || document.readyState === 'interactive') {\n // DOM is ready enough\n this.onDomReady();\n } else if (document.addEventListener) {\n // Wait for DOMContentLoaded, but also check periodically\n const checkDomReady = () => {\n if (document.readyState === 'interactive' || document.readyState === 'complete') {\n this.onDomReady();\n }\n };\n \n document.addEventListener('DOMContentLoaded', () => this.onDomReady(), { capture: false });\n \n // Also check periodically for faster response\n const interval = setInterval(() => {\n if (document.readyState === 'interactive' || document.readyState === 'complete') {\n clearInterval(interval);\n this.onDomReady();\n }\n }, 10); // Check every 10ms\n \n // Clear interval after 5 seconds to avoid infinite checking\n setTimeout(() => clearInterval(interval), 5000);\n } else {\n // Fallback for older browsers\n this.onDomReady();\n }\n }\n\n /**\n * Called when DOM is ready - processes queued requests\n */\n private onDomReady(): void {\n if (this.isDomReady) return; // Prevent multiple calls\n \n this.isDomReady = true;\n logDebug('🎯 DOM is ready, processing queued requests');\n \n // Process queued requests\n this.requestQueue.forEach(request => {\n this.processRequest(request);\n });\n this.requestQueue = [];\n \n // Call registered handlers\n this.domReadyHandlers.forEach(handler => handler());\n this.domReadyHandlers = [];\n }\n\n /**\n * Queue a request until DOM is ready\n */\n private queueRequest(request: any): void {\n if (this.isDomReady) {\n this.processRequest(request);\n } else {\n this.requestQueue.push(request);\n }\n }\n\n /**\n * Process a request (called after DOM is ready)\n */\n private async processRequest(request: any): Promise<void> {\n logDebug('Processing queued request:', request);\n \n switch (request.type) {\n case 'addEvent':\n await this.addEvent(request.event);\n break;\n case 'identifyUser':\n await this.identifyUser(request.userProperties);\n break;\n case 'trackPageView':\n this.trackPageView();\n break;\n default:\n logWarn('Unknown request type:', request.type);\n }\n }\n\n /**\n * Register a handler to be called when DOM is ready\n */\n private registerDomReadyHandler(handler: () => void): void {\n if (this.isDomReady) {\n handler();\n } else {\n this.domReadyHandlers.push(handler);\n }\n }\n\n /**\n * Initialize the HumanBehavior tracker\n * This is the main entry point - call this once per page\n */\n public static init(apiKey: string, options?: {\n ingestionUrl?: string;\n logLevel?: 'none' | 'error' | 'warn' | 'info' | 'debug';\n redactFields?: string[]; // DEPRECATED: Use redactionStrategy instead\n redactionStrategy?: {\n mode: 'privacy-first' | 'visibility-first'; // Default: 'privacy-first'\n unredactFields?: string[]; // Fields to make visible (when mode: 'privacy-first')\n redactFields?: string[]; // Fields to hide (when mode: 'visibility-first')\n };\n enableAutomaticTracking?: boolean;\n suppressConsoleErrors?: boolean; // New option to control error suppression\n recordCanvas?: boolean; // Enable canvas recording with protection\n enableAutomaticProperties?: boolean; // Enable automatic property detection\n propertyDenylist?: string[]; // Properties to exclude from tracking\n automaticTrackingOptions?: {\n trackButtons?: boolean;\n trackLinks?: boolean;\n trackForms?: boolean;\n includeText?: boolean;\n includeClasses?: boolean;\n };\n maxQueueSize?: number; // Configurable queue size\n }): HumanBehaviorTracker {\n // ✅ SUPPRESS COMMON RRWEB ERRORS FOR CLEAN CONSOLE\n if (isBrowser && options?.suppressConsoleErrors !== false) {\n // Suppress canvas security errors and network errors\n const originalConsoleError = console.error;\n console.error = (...args: any[]) => {\n const message = args.join(' ');\n if (\n message.includes('SecurityError: Failed to execute \\'toDataURL\\'') ||\n message.includes('Tainted canvases may not be exported') ||\n message.includes('Cannot inline img src=') ||\n message.includes('Cross-Origin') ||\n message.includes('CORS') ||\n message.includes('Access-Control-Allow-Origin') ||\n message.includes('Failed to load resource') ||\n message.includes('net::ERR_BLOCKED_BY_CLIENT') ||\n message.includes('NetworkError when attempting to fetch resource') ||\n message.includes('Failed to fetch') ||\n message.includes('TypeError: NetworkError') ||\n message.includes('HumanBehavior ERROR') ||\n message.includes('Failed to track custom event') ||\n message.includes('Error sending custom event')\n ) {\n // Silently suppress these common errors\n return;\n }\n originalConsoleError.apply(console, args);\n };\n\n // Suppress console.warn for similar issues\n const originalConsoleWarn = console.warn;\n console.warn = (...args: any[]) => {\n const message = args.join(' ');\n if (\n message.includes('Cannot inline img src=') ||\n message.includes('Cross-Origin') ||\n message.includes('CORS') ||\n message.includes('Access-Control-Allow-Origin') ||\n message.includes('Failed to load resource') ||\n message.includes('net::ERR_BLOCKED_BY_CLIENT') ||\n message.includes('NetworkError when attempting to fetch resource') ||\n message.includes('Failed to fetch') ||\n message.includes('Custom event network error') ||\n message.includes('Request blocked by ad blocker')\n ) {\n // Silently suppress these common warnings\n return;\n }\n originalConsoleWarn.apply(console, args);\n };\n\n // Add global error handler for any remaining rrweb errors\n window.addEventListener('error', (event) => {\n const message = event.message || '';\n if (\n message.includes('SecurityError') ||\n message.includes('Tainted canvases') ||\n message.includes('toDataURL') ||\n message.includes('Cross-Origin') ||\n message.includes('CORS') ||\n message.includes('NetworkError') ||\n message.includes('Failed to fetch')\n ) {\n event.preventDefault();\n return false;\n }\n });\n }\n // Return existing instance if already initialized\n if (isBrowser && (window as any).__humanBehaviorGlobalTracker) {\n logDebug('Tracker already initialized, returning existing instance');\n return (window as any).__humanBehaviorGlobalTracker;\n }\n\n // Configure logging if specified\n if (options?.logLevel) {\n this.configureLogging({ level: options.logLevel });\n }\n\n // Create new tracker instance\n const tracker = new HumanBehaviorTracker(apiKey, options?.ingestionUrl, {\n enableAutomaticProperties: options?.enableAutomaticProperties,\n propertyDenylist: options?.propertyDenylist,\n redactionStrategy: options?.redactionStrategy,\n redactFields: options?.redactFields,\n maxQueueSize: options?.maxQueueSize\n });\n \n // Store canvas recording preference\n tracker.recordCanvas = options?.recordCanvas ?? false;\n \n // Set unredacted fields if specified (legacy support)\n if (options?.redactFields) {\n tracker.setUnredactedFields(options.redactFields);\n }\n\n // Handle new redaction strategy - this is now handled in the constructor\n // The redactionManager is created with the correct redactionStrategy in the constructor\n\n // Setup automatic tracking if enabled\n if (options?.enableAutomaticTracking !== false) {\n tracker.setupAutomaticTracking(options?.automaticTrackingOptions);\n }\n\n // Start tracking\n tracker.start();\n \n return tracker;\n }\n\n constructor(apiKey: string | undefined, ingestionUrl?: string, options?: {\n enableAutomaticProperties?: boolean;\n propertyDenylist?: string[];\n redactionStrategy?: {\n mode: 'privacy-first' | 'visibility-first';\n unredactFields?: string[];\n redactFields?: string[];\n };\n redactFields?: string[]; // Legacy support\n maxQueueSize?: number; // Configurable queue size\n }) {\n if (!apiKey) {\n throw new Error('Human Behavior API Key is required');\n }\n \n // Initialize API\n //const defaultIngestionUrl = 'http://3.137.217.33:3000'; // AWS Development Server\n //const defaultIngestionUrl = 'http://ingestion-server-alb-1823866402.us-east-2.elb.amazonaws.com'; // ALB\n const defaultIngestionUrl = 'https://ingest.humanbehavior.co'; // HTTPS ALB\n this.api = new HumanBehaviorAPI({ \n apiKey: apiKey,\n ingestionUrl: ingestionUrl || defaultIngestionUrl\n });\n this.apiKey = apiKey;\n \n // Initialize queue size (default 1000)\n this.MAX_QUEUE_SIZE = options?.maxQueueSize ?? 1000;\n \n // debug removed\n this.redactionManager = new RedactionManager({\n redactionStrategy: options?.redactionStrategy,\n legacyRedactFields: options?.redactFields // For backward compatibility\n });\n // debug removed\n \n // Initialize property manager\n this.propertyManager = new PropertyManager({\n enableAutomaticProperties: options?.enableAutomaticProperties !== false,\n propertyDenylist: options?.propertyDenylist || []\n });\n \n // DOM ready handling removed - using simpler approach\n\n // Handle session restoration with improved continuity\n if (isBrowser) {\n const existingSessionId = localStorage.getItem(`human_behavior_session_id_${this.apiKey}`);\n const lastActivity = localStorage.getItem(`human_behavior_last_activity_${this.apiKey}`);\n const fifteenMinutesAgo = Date.now() - (15 * 60 * 1000);\n \n // Check if we have an existing session that's still within the activity window\n if (existingSessionId && lastActivity && parseInt(lastActivity) > fifteenMinutesAgo) {\n this.sessionId = existingSessionId;\n logDebug(`Reusing existing session: ${this.sessionId}`);\n // Update activity timestamp to extend the session window\n localStorage.setItem(`human_behavior_last_activity_${this.apiKey}`, Date.now().toString());\n } else {\n // Clear old session data if it's expired\n if (existingSessionId) {\n logDebug(`Session expired, clearing old session: ${existingSessionId}`);\n localStorage.removeItem(`human_behavior_session_id_${this.apiKey}`);\n localStorage.removeItem(`human_behavior_last_activity_${this.apiKey}`);\n }\n this.sessionId = uuidv1();\n logDebug(`Creating new session: ${this.sessionId}`);\n localStorage.setItem(`human_behavior_session_id_${this.apiKey}`, this.sessionId);\n localStorage.setItem(`human_behavior_last_activity_${this.apiKey}`, Date.now().toString());\n }\n \n this.currentUrl = window.location.href;\n (window as any).__humanBehaviorGlobalTracker = this;\n } else {\n this.sessionId = uuidv1();\n }\n\n // ✅ FIXED: Wait for Kafka-based initialization to complete\n this.initializationPromise = this.init().catch(error => {\n logError('Background initialization failed:', error);\n });\n }\n\n private async init(): Promise<void> {\n try {\n \n // Get userId only in browser environment\n const userId = isBrowser ? this.getCookie(`human_behavior_end_user_id_${this.apiKey}`) : null;\n logDebug(`Initializing with sessionId: ${this.sessionId}, userId: ${userId}`);\n \n // Get automatic properties for init\n const automaticProperties = this.propertyManager.getAutomaticProperties();\n \n // Server will detect IP from HTTP requests automatically\n \n if (isBrowser) {\n this.setupPageUnloadHandler();\n this.setupNavigationTracking();\n } else {\n logInfo('HumanBehaviorTracker initialized in server environment. Session tracking is disabled.');\n }\n\n // ✅ FIXED: Wait for Kafka server initialization to complete\n await this.initServerAsync(userId, automaticProperties);\n \n // ✅ FIXED: Set initialized ONLY after server init succeeds\n this.initialized = true;\n \n logInfo(`HumanBehaviorTracker initialized with sessionId: ${this.sessionId}`);\n } catch (error: any) {\n // Handle initialization errors gracefully - don't throw (v2)\n if (error.message?.includes('429') || error.message?.includes('Monthly video processing limit reached')) {\n // Silently set the flag without logging\n this.monthlyLimitReached = true;\n this.initialized = true; // Mark as initialized so the tracker can still work locally\n } else {\n logError('Failed to initialize HumanBehaviorTracker:', error);\n this.initialized = true; // Don't throw - allow tracker to work locally\n }\n }\n }\n\n private async initServerAsync(userId: string | null, automaticProperties: any): Promise<void> {\n try {\n logDebug('🚀 Attempting to call /init endpoint...');\n logDebug('📡 URL:', `${this.api['baseUrl']}/api/ingestion/init`);\n logDebug('🔑 API Key:', this.apiKey);\n \n // Get browser-specific data only if in browser environment\n let entryURL = null;\n let referrer = null;\n \n if (isBrowser) {\n entryURL = window.location.href;\n referrer = document.referrer;\n }\n \n logDebug('📦 Payload:', {\n sessionId: this.sessionId,\n endUserId: userId,\n entryURL: entryURL,\n referrer: referrer,\n automaticProperties: automaticProperties\n });\n \n // Create a custom init request with automatic properties\n const initResponse = await fetch(`${this.api['baseUrl']}/api/ingestion/init`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Referer': referrer || ''\n },\n body: JSON.stringify({\n sessionId: this.sessionId,\n endUserId: userId,\n entryURL: entryURL,\n referrer: referrer,\n automaticProperties: automaticProperties\n })\n });\n\n if (!initResponse.ok) {\n throw new Error(`Failed to initialize: ${initResponse.statusText}`);\n }\n\n const { sessionId, endUserId } = await initResponse.json();\n \n // Check if server returned a different session ID (for session continuity)\n if (sessionId !== this.sessionId) {\n logDebug(`Server returned different sessionId: ${sessionId} (client had: ${this.sessionId})`);\n this.sessionId = sessionId;\n // Update localStorage with server's session ID for continuity\n if (isBrowser) {\n localStorage.setItem(`human_behavior_session_id_${this.apiKey}`, this.sessionId);\n }\n }\n \n this.endUserId = endUserId;\n this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, endUserId, 365);\n\n logInfo(`Server initialization completed with endUserId: ${endUserId}`);\n } catch (error: any) {\n logError('❌ Server initialization failed:', error);\n logError('🔍 Error details:', {\n message: error?.message || 'Unknown error',\n stack: error?.stack || 'No stack trace',\n type: error?.constructor?.name || 'Unknown type'\n });\n logWarn('Server initialization failed, continuing with local session:', error);\n // Don't throw - allow the tracker to continue working locally\n }\n }\n\n /**\n * ✅ FIXED: Wait for Kafka-based initialization to complete\n */\n private async ensureInitialized(): Promise<void> {\n if (this.initializationPromise) {\n await this.initializationPromise;\n }\n }\n\n /**\n * Setup navigation event tracking for SPA navigation\n */\n private setupNavigationTracking(): void {\n if (!isBrowser || this.navigationTrackingEnabled) return;\n \n this.navigationTrackingEnabled = true;\n logDebug('Setting up navigation tracking');\n\n // Store original history methods\n this.originalPushState = history.pushState;\n this.originalReplaceState = history.replaceState;\n\n // Override pushState to capture programmatic navigation\n history.pushState = (...args) => {\n this.previousUrl = this.currentUrl;\n this.currentUrl = window.location.href;\n \n // Call original method\n this.originalPushState!.apply(history, args);\n \n // Track navigation event\n this.trackNavigationEvent('pushState', this.previousUrl, this.currentUrl);\n \n // Take FullSnapshot on navigation\n this.takeFullSnapshot();\n };\n\n // Override replaceState to capture programmatic navigation\n history.replaceState = (...args) => {\n this.previousUrl = this.currentUrl;\n this.currentUrl = window.location.href;\n \n // Call original method\n this.originalReplaceState!.apply(history, args);\n \n // Track navigation event\n this.trackNavigationEvent('replaceState', this.previousUrl, this.currentUrl);\n \n // Take FullSnapshot on navigation\n this.takeFullSnapshot();\n };\n\n // Listen for popstate events (back/forward navigation)\n const popstateListener = () => {\n this.previousUrl = this.currentUrl;\n this.currentUrl = window.location.href;\n this.trackNavigationEvent('popstate', this.previousUrl, this.currentUrl);\n \n // Take FullSnapshot on navigation\n this.takeFullSnapshot();\n };\n \n window.addEventListener('popstate', popstateListener);\n this.navigationListeners.push(() => {\n window.removeEventListener('popstate', popstateListener);\n });\n\n // Listen for hashchange events\n const hashchangeListener = () => {\n this.previousUrl = this.currentUrl;\n this.currentUrl = window.location.href;\n this.trackNavigationEvent('hashchange', this.previousUrl, this.currentUrl);\n };\n \n window.addEventListener('hashchange', hashchangeListener);\n this.navigationListeners.push(() => {\n window.removeEventListener('hashchange', hashchangeListener);\n });\n\n // Track initial page load\n this.trackNavigationEvent('pageLoad', '', this.currentUrl);\n }\n\n /**\n * Track navigation events and send custom events\n */\n public async trackNavigationEvent(type: string, fromUrl: string, toUrl: string): Promise<void> {\n if (!this.initialized) return;\n\n try {\n const navigationData = {\n type: type,\n from: fromUrl,\n to: toUrl,\n timestamp: new Date().toISOString(),\n pathname: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n referrer: document.referrer\n };\n\n // Add navigation event to the main event stream\n await this.addEvent({\n type: 5, // Custom event type\n data: {\n payload: {\n eventType: 'navigation',\n ...navigationData\n }\n },\n timestamp: Date.now()\n });\n\n // Send $page_viewed custom event for page loads and navigation\n if (type === 'pageLoad' || type === 'pushState' || type === 'replaceState' || type === 'popstate' || type === 'hashchange') {\n const pageViewProperties = {\n url: window.location.href, // Full current URL including protocol, domain, path, query, and hash\n fromUrl: fromUrl,\n navigationType: type,\n pathname: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n referrer: document.referrer,\n timestamp: Date.now()\n };\n\n await this.customEvent('$page_viewed', pageViewProperties);\n }\n \n logDebug(`Navigation tracked: ${type} from ${fromUrl} to ${toUrl}`);\n } catch (error) {\n logError('Failed to track navigation event:', error);\n }\n }\n\n public async trackPageView(url?: string): Promise<void> {\n if (!this.initialized) return;\n\n // Update automatic properties for new page\n this.propertyManager.updateAutomaticProperties();\n\n try {\n const pageViewData = {\n url: url || window.location.href,\n pathname: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n referrer: document.referrer,\n timestamp: new Date().toISOString()\n };\n\n // Get enhanced properties with automatic properties\n const enhancedProperties = this.propertyManager.getEventProperties(pageViewData);\n\n // Add pageview event to the main event stream\n await this.addEvent({\n type: 5, // Custom event type\n data: {\n payload: {\n eventType: 'pageview',\n ...enhancedProperties\n }\n },\n timestamp: Date.now()\n });\n \n logDebug(`Pageview tracked: ${pageViewData.url}`);\n } catch (error) {\n logError('Failed to track pageview event:', error);\n }\n }\n\n public async customEvent(eventName: string, properties?: Record<string, any>): Promise<void> {\n if (!this.initialized || !this.endUserId) {\n logDebug(`Waiting for initialization/endUserId before tracking custom event: ${eventName}. Initialized: ${this.initialized}, endUserId: ${this.endUserId ? 'available' : 'null'}`);\n return;\n }\n\n // Get enhanced properties with automatic properties\n const enhancedProperties = this.propertyManager.getEventProperties(properties);\n\n try {\n // Send custom event directly to the API\n await this.api.sendCustomEvent(this.sessionId, eventName, enhancedProperties, this.endUserId);\n \n logDebug(`Custom event tracked: ${eventName}`, enhancedProperties);\n } catch (error: any) {\n logError('Failed to track custom event:', error);\n \n // Handle specific error types - check for any custom event failure\n if (error.message?.includes('500') || \n error.message?.includes('Internal Server Error') ||\n error.message?.includes('Failed to send custom event')) {\n logWarn('Custom event endpoint failed, using fallback');\n } else if (error.message?.includes('ERR_BLOCKED_BY_CLIENT')) {\n logWarn('Custom event request blocked by ad blocker, using fallback');\n } else if (error.message?.includes('Failed to fetch')) {\n logWarn('Custom event network error, using fallback');\n }\n \n // Always try fallback for any custom event error\n try {\n const customEventData = {\n eventName: eventName,\n properties: enhancedProperties || {},\n timestamp: new Date().toISOString(),\n url: window.location.href,\n pathname: window.location.pathname\n };\n\n await this.addEvent({\n type: 5, // Custom event type\n data: {\n payload: {\n eventType: 'custom',\n ...customEventData\n }\n },\n timestamp: Date.now()\n });\n \n logDebug(`Custom event added to event stream as fallback: ${eventName}`);\n } catch (fallbackError) {\n logError('Failed to add custom event to event stream as fallback:', fallbackError);\n }\n }\n }\n\n /**\n * Setup automatic tracking for buttons, links, and forms\n */\n private setupAutomaticTracking(options?: {\n trackButtons?: boolean;\n trackLinks?: boolean;\n trackForms?: boolean;\n includeText?: boolean;\n includeClasses?: boolean;\n }): void {\n if (!isBrowser) return;\n\n const config = {\n trackButtons: options?.trackButtons !== false,\n trackLinks: false, // Always disabled - only buttons and forms\n trackForms: options?.trackForms !== false,\n includeText: options?.includeText !== false,\n includeClasses: options?.includeClasses || false\n };\n\n logDebug('Setting up automatic tracking with config:', config);\n\n // Setup button tracking\n if (config.trackButtons) {\n this.setupAutomaticButtonTracking(config);\n }\n\n // Setup form tracking\n if (config.trackForms) {\n this.setupAutomaticFormTracking(config);\n }\n }\n\n /**\n * Setup automatic button tracking\n */\n private setupAutomaticButtonTracking(config: {\n includeText?: boolean;\n includeClasses?: boolean;\n }): void {\n document.addEventListener('click', async (event) => {\n const target = event.target as HTMLElement;\n \n // Track button clicks\n if (target.tagName === 'BUTTON' || target.closest('button')) {\n const button = target.tagName === 'BUTTON'\n ? target as HTMLButtonElement\n : target.closest('button') as HTMLButtonElement;\n \n const properties: Record<string, any> = {\n buttonId: button.id || null,\n buttonType: button.type || 'button',\n page: window.location.pathname,\n timestamp: Date.now()\n };\n\n if (config.includeText) {\n properties.buttonText = button.textContent?.trim() || null;\n }\n\n if (config.includeClasses) {\n properties.buttonClass = button.className || null;\n }\n\n // Remove null values\n Object.keys(properties).forEach(key => {\n if (properties[key] === null) {\n delete properties[key];\n }\n });\n\n await this.customEvent('$button_clicked', properties);\n }\n });\n }\n\n /**\n * Setup automatic link tracking\n * TEMPORARILY DISABLED: Automatic custom event tracking\n */\n private setupAutomaticLinkTracking(config: {\n includeText?: boolean;\n includeClasses?: boolean;\n }): void {\n // TEMPORARILY DISABLED: Automatic custom event tracking\n return;\n \n // document.addEventListener('click', async (event) => {\n // const target = event.target as HTMLElement;\n \n // // Track link clicks\n // if (target.tagName === 'A' || target.closest('a')) {\n // const link = target.tagName === 'A'\n // ? target as HTMLAnchorElement\n // : target.closest('a') as HTMLAnchorElement;\n \n // const properties: Record<string, any> = {\n // linkUrl: link.href || null,\n // linkId: link.id || null,\n // linkTarget: link.target || null,\n // page: window.location.pathname,\n // timestamp: Date.now()\n // };\n\n // if (config.includeText) {\n // properties.linkText = link.textContent?.trim() || null;\n // }\n\n // if (config.includeClasses) {\n // properties.linkClass = link.className || null;\n // }\n\n // // Remove null values\n // Object.keys(properties).forEach(key => {\n // if (properties[key] === null) {\n // delete properties[key];\n // }\n // });\n\n // await this.customEvent('link_clicked', properties);\n // }\n // });\n }\n\n /**\n * Setup automatic form tracking\n */\n private setupAutomaticFormTracking(config: {\n includeText?: boolean;\n includeClasses?: boolean;\n }): void {\n document.addEventListener('submit', async (event) => {\n const form = event.target as HTMLFormElement;\n const formData = new FormData(form);\n \n const properties: Record<string, any> = {\n formId: form.id || null,\n formAction: form.action || null,\n formMethod: form.method || 'get',\n fields: Array.from(formData.keys()),\n page: window.location.pathname,\n timestamp: Date.now()\n };\n\n if (config.includeClasses) {\n properties.formClass = form.className || null;\n }\n\n // Remove null values\n Object.keys(properties).forEach(key => {\n if (properties[key] === null) {\n delete properties[key];\n }\n });\n\n await this.customEvent('$form_submitted', properties);\n });\n }\n\n /**\n * Cleanup navigation tracking\n */\n private cleanupNavigationTracking(): void {\n if (!this.navigationTrackingEnabled) return;\n\n // Restore original history methods\n if (this.originalPushState) {\n history.pushState = this.originalPushState;\n }\n if (this.originalReplaceState) {\n history.replaceState = this.originalReplaceState;\n }\n\n // Remove event listeners\n this.navigationListeners.forEach(cleanup => cleanup());\n this.navigationListeners = [];\n\n this.navigationTrackingEnabled = false;\n logDebug('Navigation tracking cleaned up');\n }\n\n public static logToStorage(message: string) {\n logInfo(message);\n }\n\n /**\n * Configure logging behavior for the SDK\n * @param config Logger configuration options\n */\n public static configureLogging(config: { level?: 'none' | 'error' | 'warn' | 'info' | 'debug', enableConsole?: boolean, enableStorage?: boolean }) {\n const levelMap = {\n 'none': 0,\n 'error': 1,\n 'warn': 2,\n 'info': 3,\n 'debug': 4\n };\n \n logger.setConfig({\n level: levelMap[config.level || 'error'],\n enableConsole: config.enableConsole !== false,\n enableStorage: config.enableStorage || false\n });\n }\n\n /**\n * Enable console event tracking\n */\n public enableConsoleTracking(): void {\n if (!isBrowser || this.consoleTrackingEnabled) return;\n \n // Store original console methods\n this.originalConsole = {\n log: console.log,\n warn: console.warn,\n error: console.error\n };\n\n // Override console methods to capture ALL console output (including logger output)\n console.log = (...args) => {\n this.trackConsoleEvent('log', args);\n this.originalConsole!.log(...args);\n };\n\n console.warn = (...args) => {\n this.trackConsoleEvent('warn', args);\n this.originalConsole!.warn(...args);\n };\n\n console.error = (...args) => {\n this.trackConsoleEvent('error', args);\n this.originalConsole!.error(...args);\n };\n\n this.consoleTrackingEnabled = true;\n logDebug('Console tracking enabled');\n }\n\n /**\n * Disable console event tracking\n */\n public disableConsoleTracking(): void {\n if (!isBrowser || !this.consoleTrackingEnabled) return;\n\n // Restore original console methods\n if (this.originalConsole) {\n console.log = this.originalConsole.log;\n console.warn = this.originalConsole.warn;\n console.error = this.originalConsole.error;\n }\n\n this.consoleTrackingEnabled = false;\n logDebug('Console tracking disabled');\n }\n\n private trackConsoleEvent(level: 'log' | 'warn' | 'error', args: any[]): void {\n if (!this.initialized) return;\n\n try {\n const consoleData = {\n level: level,\n message: args.map(arg => \n typeof arg === 'object' ? JSON.stringify(arg) : String(arg)\n ).join(' '),\n timestamp: new Date().toISOString(),\n url: window.location.href\n };\n\n // Add console event to the main event stream\n this.addEvent({\n type: 5, // Custom event type\n data: {\n payload: {\n eventType: 'console',\n ...consoleData\n }\n },\n timestamp: Date.now()\n }).catch(error => {\n logError('Failed to track console event:', error);\n });\n } catch (error) {\n logError('Error in trackConsoleEvent:', error);\n }\n }\n\n private setupPageUnloadHandler() {\n if (!isBrowser) return;\n \n logDebug('Setting up page unload handler');\n \n // Handle visibility changes for sending events\n window.addEventListener('visibilitychange', () => {\n // Only send events when page becomes hidden\n if (document.visibilityState === 'hidden') {\n logDebug('Page hidden - sending pending events');\n // Flush unified event queue\n this.flushEvents();\n }\n });\n\n // Handle actual page unload/close\n window.addEventListener('beforeunload', () => {\n // Send final events from unified queue\n this.flushEvents();\n });\n\n // Update activity timestamp on user interaction (not on page load)\n const updateActivity = () => {\n localStorage.setItem(`human_behavior_last_activity_${this.apiKey}`, Date.now().toString());\n };\n\n // Listen for user interactions to update activity timestamp\n window.addEventListener('click', updateActivity);\n window.addEventListener('keydown', updateActivity);\n window.addEventListener('scroll', updateActivity);\n window.addEventListener('mousemove', updateActivity);\n }\n\n public viewLogs() {\n try {\n const logs = logger.getLogs();\n logInfo('HumanBehavior Logs:', logs);\n logger.clearLogs(); // Clear logs after viewing\n } catch (e) {\n logError('Failed to read logs:', e);\n }\n }\n\n /**\n * Add user identification information to the tracker\n * If userId is not provided, will use userProperties.email as the userId (if present)\n */\n public async identifyUser(\n { userProperties }: { userProperties: Record<string, any> }\n ): Promise<string> {\n await this.ensureInitialized();\n \n // Keep the original endUserId (UUID) - don't change it\n const originalEndUserId = this.endUserId;\n \n // Store user properties\n this.userProperties = userProperties;\n \n logDebug('Identifying user:', { userProperties, originalEndUserId, sessionId: this.sessionId });\n \n // Get automatic properties and send with user data (only in browser)\n const automaticProperties = isBrowser ? this.propertyManager.getAutomaticProperties() : {};\n \n // Use the API class method which properly handles posthogName\n const userResponse = await this.api.sendUserData(\n originalEndUserId || '',\n userProperties,\n this.sessionId\n );\n\n // sendUserData throws an error if the request fails, so if we get here, it succeeded\n \n // ✅ REMOVED: IP info is now handled during /init with automatic properties\n // No need for separate /ip-info call since GeoIP data is captured during initialization\n \n // Don't update endUserId - keep it as the original UUID\n \n return originalEndUserId || '';\n }\n /**\n * Get current user attributes\n */\n public getUserAttributes(): Record<string, any> {\n return { ...this.userProperties };\n }\n\n public async start() {\n await this.ensureInitialized();\n if (!isBrowser) return;\n \n // Prevent multiple start() calls\n if (this.isStarted) {\n logDebug('HumanBehaviorTracker already started, skipping start() call.');\n return;\n }\n this.isStarted = true;\n\n // Start periodic flushing (unified queue)\n this.flushInterval = window.setInterval(() => {\n this.flushEvents();\n }, this.FLUSH_INTERVAL_MS);\n\n // Disable console tracking to reduce event pollution\n // this.enableConsoleTracking();\n\n // ✅ DOM READY DETECTION\n // Wait for DOM to be ready before starting recording\n const startRecording = () => {\n // Prevent multiple recording instances\n if (this.recordInstance) {\n logDebug('🎯 Recording already started, skipping duplicate start');\n return;\n }\n \n logDebug('🎯 DOM ready, starting session recording');\n \n // ✅ HUMANBEHAVIOR RRWEB CONFIGURATION\n this.rrwebRecord = record;\n // debug removed\n const recordInstance = record({\n emit: (event) => {\n this.addRecordingEvent(event);\n \n // ✅ DEBUG FULLSNAPSHOT GENERATION\n if (event.type === 2) { // FullSnapshot\n logDebug(`🎯 FullSnapshot generated at ${new Date().toISOString()}`);\n }\n },\n // ✅ HUMANBEHAVIOR'S CUSTOM SETTINGS\n maskTextSelector: this.redactionManager.getMaskTextSelector() || undefined,\n maskTextFn: undefined,\n maskAllInputs: this.redactionManager.getRedactionMode() === 'privacy-first', // Configurable based on strategy\n maskInputOptions: {\n // Enable rrweb input masking callbacks for all common types\n password: true,\n text: true,\n textarea: true,\n email: true,\n number: true,\n tel: true,\n url: true,\n search: true,\n date: true,\n time: true,\n month: true,\n week: true\n },\n // In visibility-first, selectively mask inputs that should be redacted\n maskInputFn: (text, element) => {\n try {\n const mode = this.redactionManager.getRedactionMode();\n // Only evaluate HTML elements\n if (!(element instanceof HTMLElement)) return text;\n // privacy-first: always mask input values\n if (mode === 'privacy-first') return '*'.repeat(text.length || 1);\n // visibility-first: mask if element is NOT unredacted (i.e., is in redacted set)\n const shouldShow = this.redactionManager.shouldUnredactElement(element);\n const decision = shouldShow ? 'show' : 'mask';\n const id = (element as HTMLElement).id;\n const name = (element as HTMLInputElement).name;\n const type = (element as HTMLInputElement).type;\n return shouldShow ? text : '*'.repeat(text.length || 1);\n } catch {\n return text;\n }\n },\n slimDOMOptions: {},\n // ✅ ERROR SUPPRESSION SETTINGS - Disabled to prevent console noise\n collectFonts: false, // Disable font collection to reduce errors\n inlineStylesheet: true, // Keep styles for proper session replay\n recordCrossOriginIframes: false, // Prevent cross-origin iframe errors\n \n // ✅ CANVAS RECORDING - protection against overwhelm\n recordCanvas: this.recordCanvas, // Opt-in only\n sampling: this.recordCanvas ? { canvas: 4 } : undefined, // 4 FPS throttle\n dataURLOptions: this.recordCanvas ? { \n type: 'image/webp', \n quality: 0.4 \n } : undefined, // WebP with 40% quality\n \n // ✅ FULLSNAPSHOT GENERATION - No periodic snapshots to avoid animation issues\n // Rely on initial FullSnapshot + navigation-triggered ones only\n hooks: {\n // Extra safety: mask input events selectively using rrweb hook\n input: (event) => {\n try {\n const mode = this.redactionManager.getRedactionMode();\n // In privacy-first everything is masked already by maskAllInputs\n if (mode === 'privacy-first') return;\n const node = typeof document !== 'undefined'\n ? document.querySelector(`[data-rrweb-id=\"${(event as any).id}\"]`)\n : null;\n if (node && node instanceof HTMLElement) {\n const shouldShow = this.redactionManager.shouldUnredactElement(node);\n if (!shouldShow) {\n // Mask text payloads in input event\n if (typeof (event as any).text !== 'undefined') {\n (event as any).text = '*'.repeat((event as any).text?.length || 1);\n }\n if (typeof (event as any).value !== 'undefined') {\n (event as any).value = '*'.repeat((event as any).value?.length || 1);\n }\n }\n }\n } catch {}\n }\n }\n });\n \n // Store the record instance for cleanup \n this.recordInstance = recordInstance || null;\n };\n\n // ✅ DOM READY DETECTION - More aggressive like previous version\n logDebug(`🎯 DOM ready state: ${document.readyState}`);\n if (document.readyState === 'complete' || document.readyState === 'interactive') {\n // DOM is ready enough, start immediately\n logDebug(`🎯 DOM ready (${document.readyState}), starting recording immediately`);\n startRecording();\n } else {\n // Wait for DOM to be ready, but also check periodically\n logDebug('🎯 DOM not ready, waiting for DOMContentLoaded event');\n \n const checkDomReady = () => {\n if (document.readyState === 'interactive' || document.readyState === 'complete') {\n logDebug(`🎯 DOM ready (${document.readyState}), starting recording`);\n startRecording();\n return true;\n }\n return false;\n };\n \n // Check immediately in case it changed\n if (checkDomReady()) return;\n \n // Listen for DOMContentLoaded\n document.addEventListener('DOMContentLoaded', () => {\n logDebug('🎯 DOMContentLoaded fired, starting recording');\n startRecording();\n }, { once: true });\n \n // Also check periodically for faster response\n const interval = setInterval(() => {\n if (checkDomReady()) {\n clearInterval(interval);\n }\n }, 10); // Check every 10ms\n \n // Clear interval after 5 seconds to avoid infinite checking\n setTimeout(() => clearInterval(interval), 5000);\n }\n }\n\n /**\n * Manually trigger a FullSnapshot (for navigation events)\n * Delays snapshot to avoid capturing mid-animation states\n */\n private takeFullSnapshot(): void {\n // Clear any existing timeout to avoid multiple snapshots\n if (this.fullSnapshotTimeout) {\n clearTimeout(this.fullSnapshotTimeout);\n }\n\n // Delay FullSnapshot to let animations settle\n this.fullSnapshotTimeout = window.setTimeout(() => {\n try {\n // Wait for any pending animations/transitions to complete\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n // Access takeFullSnapshot from the rrweb record function\n if (this.rrwebRecord && typeof this.rrwebRecord.takeFullSnapshot === 'function') {\n this.rrwebRecord.takeFullSnapshot();\n logDebug('✅ FullSnapshot taken for navigation (delayed for animations)');\n } else {\n logWarn('⚠️ takeFullSnapshot not available on record function');\n }\n });\n });\n } catch (error) {\n logError('❌ Failed to take FullSnapshot for navigation:', error);\n }\n }, 1000); // Wait 1 second for animations to settle\n }\n\n public async stop() {\n await this.ensureInitialized();\n if (!isBrowser) return;\n \n if (this.flushInterval) {\n clearInterval(this.flushInterval);\n this.flushInterval = null;\n }\n\n // Stop rrweb recording\n if (this.recordInstance) {\n this.recordInstance();\n this.recordInstance = null;\n }\n \n // Clear any pending FullSnapshot timeouts\n if (this.fullSnapshotTimeout) {\n clearTimeout(this.fullSnapshotTimeout);\n this.fullSnapshotTimeout = null;\n }\n \n this.rrwebRecord = null;\n\n // Disable console tracking\n this.disableConsoleTracking();\n\n // Cleanup navigation tracking\n this.cleanupNavigationTracking();\n }\n\n /**\n * Add an event to the ingestion queue\n * Events are sent directly without processing to avoid corruption\n */\n public async addEvent(event: any) {\n await this.ensureInitialized();\n \n // ✅ DIRECT EVENT HANDLING - No custom processing to avoid corruption\n // Events flow directly from rrweb to ingestion server\n \n // ✅ EVENT VALIDATION\n if (!event || typeof event !== 'object') {\n logDebug('⚠️ Skipping invalid event:', event);\n return;\n }\n \n // ✅ LOG FULLSNAPSHOT STATUS FOR DEBUGGING\n if (event.type === 2) { // FullSnapshot\n const hasData = !!event.data;\n const hasNode = !!(event.data && event.data.node);\n \n if (!hasData || !hasNode) {\n logDebug(`⚠️ Empty FullSnapshot detected: hasData=${hasData}, hasNode=${hasNode} - continuing session`);\n } else {\n logDebug(`✅ Valid FullSnapshot: hasData=${hasData}, hasNode=${hasNode}, dataType=${event.data?.node?.type}`);\n }\n }\n \n // Queue size management with immediate flushing\n if (this.eventQueue.length >= this.MAX_QUEUE_SIZE) {\n // Drop oldest event when queue is full\n this.eventQueue.shift();\n logDebug('Queue is full, the oldest event is dropped.');\n }\n \n this.eventQueue.push(event); // Direct event handling\n \n // Immediate flush for FullSnapshots (important events)\n if (event.type === 2) { // FullSnapshot\n logDebug('FullSnapshot added, triggering immediate flush');\n this.flushEvents();\n }\n // Immediate flush if queue is getting large\n else if (this.eventQueue.length >= this.MAX_QUEUE_SIZE * 0.8) {\n logDebug(`Queue at ${this.eventQueue.length}/${this.MAX_QUEUE_SIZE}, triggering immediate flush`);\n this.flushEvents();\n }\n }\n\n /**\n * Flush events to the ingestion server\n * Events are sent in chunks to handle large payloads efficiently\n */\n private async flushEvents() {\n // Prevent concurrent flushes\n if (this.isProcessing || !this.initialized) {\n return;\n }\n\n // Don't make requests if monthly limit is reached - silently skip\n if (this.monthlyLimitReached) {\n return; // Silently skip without logging\n }\n\n this.isProcessing = true;\n try {\n // Swap the current queue with an empty one atomically\n const eventsToProcess = this.eventQueue;\n this.eventQueue = [];\n\n if (eventsToProcess.length > 0) {\n logDebug('Flushing events:', eventsToProcess);\n \n // ✅ LOG FULLSNAPSHOT STATUS FOR MONITORING\n const fullSnapshots = eventsToProcess.filter(e => e.type === 2);\n if (fullSnapshots.length > 0) {\n logDebug(`[FIXED] Sending ${fullSnapshots.length} FullSnapshot(s) with valid data`);\n }\n \n try {\n // Use chunked sending to handle large payloads\n await this.api.sendEventsChunked(eventsToProcess, this.sessionId, this.endUserId!);\n } catch (error: any) {\n // Handle specific error types with graceful degradation\n if (error.message?.includes('ERROR: Session already completed')) {\n logWarn('Session expired, events will be lost');\n } else if (error.message?.includes('429') || error.message?.includes('Monthly video processing limit reached')) {\n // Silently set the flag without logging\n this.monthlyLimitReached = true;\n } else if (error.message?.includes('413') || error.message?.includes('Content Too Large')) {\n logWarn('Payload too large, events will be lost');\n } else if (error.message?.includes('ERR_BLOCKED_BY_CLIENT') || \n error.message?.includes('Failed to fetch') ||\n error.message?.includes('NetworkError')) {\n logWarn('Request blocked by ad blocker or network issue, events will be lost');\n } else {\n throw error;\n }\n }\n }\n } finally {\n this.isProcessing = false;\n }\n }\n\n /**\n * Add an event to the session recording queue\n * These are typically FullSnapshots or IncrementalSnapshots\n */\n public async addRecordingEvent(event: any) {\n await this.ensureInitialized();\n \n // ✅ DIRECT EVENT HANDLING - No custom processing to avoid corruption\n // Events flow directly from rrweb to ingestion server\n \n // ✅ EVENT VALIDATION\n if (!event || typeof event !== 'object') {\n logDebug('⚠️ Skipping invalid recording event:', event);\n return;\n }\n \n // ✅ LOG FULLSNAPSHOT STATUS FOR DEBUGGING\n if (event.type === 2) { // FullSnapshot\n const hasData = !!event.data;\n const hasNode = !!(event.data && event.data.node);\n \n if (!hasData || !hasNode) {\n logDebug(`⚠️ Empty FullSnapshot detected: hasData=${hasData}, hasNode=${hasNode} - continuing session`);\n } else {\n logDebug(`✅ Valid FullSnapshot: hasData=${hasData}, hasNode=${hasNode}, dataType=${event.data?.node?.type}`);\n }\n }\n \n // Use the same unified queue for all events\n // Queue size management with immediate flushing\n if (this.eventQueue.length >= this.MAX_QUEUE_SIZE) {\n // Drop oldest event when queue is full\n this.eventQueue.shift();\n logDebug('Queue is full, the oldest event is dropped.');\n }\n \n this.eventQueue.push(event); // Direct event handling\n \n // Immediate flush for FullSnapshots (important events)\n if (event.type === 2) { // FullSnapshot\n logDebug('FullSnapshot added, triggering immediate flush');\n this.flushEvents();\n }\n // Immediate flush if queue is getting large\n else if (this.eventQueue.length >= this.MAX_QUEUE_SIZE * 0.8) {\n logDebug(`Queue at ${this.eventQueue.length}/${this.MAX_QUEUE_SIZE}, triggering immediate flush`);\n this.flushEvents();\n }\n }\n\n\n\n // Add helper methods for cookie management with localStorage fallback\n private setCookie(name: string, value: string, daysToExpire: number) {\n if (!isBrowser) return;\n \n try {\n // Try to set cookie first\n const date = new Date();\n date.setTime(date.getTime() + (daysToExpire * 24 * 60 * 60 * 1000));\n const expires = `expires=${date.toUTCString()}`;\n document.cookie = `${name}=${value};${expires};path=/;SameSite=Lax`;\n \n // Also store in localStorage as backup\n localStorage.setItem(name, value);\n logDebug(`Set cookie and localStorage: ${name}`);\n } catch (error) {\n // If cookie fails, use localStorage only\n try {\n localStorage.setItem(name, value);\n logDebug(`Cookie blocked, using localStorage: ${name}`);\n } catch (localStorageError) {\n logError('Failed to store user ID in both cookie and localStorage:', localStorageError);\n }\n }\n }\n\n public getCookie(name: string): string | null {\n if (!isBrowser) return null;\n \n try {\n // Try to get from cookie first\n const nameEQ = name + \"=\";\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) === ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) === 0) {\n const cookieValue = c.substring(nameEQ.length, c.length);\n logDebug(`Found cookie: ${name}`);\n return cookieValue;\n }\n }\n \n // If cookie not found, try localStorage\n const localStorageValue = localStorage.getItem(name);\n if (localStorageValue) {\n logDebug(`Cookie not found, using localStorage: ${name}`);\n return localStorageValue;\n }\n \n return null;\n } catch (error) {\n // If cookie access fails, try localStorage\n try {\n const localStorageValue = localStorage.getItem(name);\n if (localStorageValue) {\n logDebug(`Cookie access failed, using localStorage: ${name}`);\n return localStorageValue;\n }\n } catch (localStorageError) {\n logError('Failed to access both cookie and localStorage:', localStorageError);\n }\n return null;\n }\n }\n\n /**\n * Delete a cookie by setting its expiration date to the past\n * @param name The name of the cookie to delete\n */\n private deleteCookie(name: string) {\n if (!isBrowser) return;\n \n try {\n // Delete cookie by setting expiration to past\n document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=Lax`;\n logDebug(`Deleted cookie: ${name}`);\n } catch (error) {\n logError(`Failed to delete cookie: ${name}`, error);\n }\n \n // Also remove from localStorage\n try {\n localStorage.removeItem(name);\n logDebug(`Removed from localStorage: ${name}`);\n } catch (error) {\n logError(`Failed to remove from localStorage: ${name}`, error);\n }\n }\n\n /**\n * Clear user data and reset session when user signs out of the site\n * This should be called when a user logs out of your application to prevent\n * data contamination between different users\n */\n public logout(): void {\n if (!isBrowser) return;\n \n try { \n // Clear user ID cookie and localStorage\n const userIdCookieName = `human_behavior_end_user_id_${this.apiKey}`;\n this.deleteCookie(userIdCookieName);\n \n // Clear session data from localStorage\n localStorage.removeItem(`human_behavior_session_id_${this.apiKey}`);\n localStorage.removeItem(`human_behavior_last_activity_${this.apiKey}`);\n \n // Reset user-related properties\n this.endUserId = null;\n this.userProperties = {};\n \n // Generate a new session ID for the next user\n this.sessionId = uuidv1();\n if (isBrowser) {\n localStorage.setItem(`human_behavior_session_id_${this.apiKey}`, this.sessionId);\n localStorage.setItem(`human_behavior_last_activity_${this.apiKey}`, Date.now().toString());\n }\n \n logInfo('User logged out - cleared all user data and started fresh session');\n } catch (error) {\n logError('Error during logout:', error);\n }\n }\n\n /**\n * Start redaction functionality for sensitive input fields\n * @param options Optional configuration for redaction behavior\n */\n public async redact(options?: RedactionOptions): Promise<void> {\n await this.ensureInitialized();\n if (!isBrowser) {\n logWarn('Redaction is only available in browser environments');\n return;\n }\n \n // Create a new redaction manager with the provided options\n this.redactionManager = new RedactionManager(options);\n }\n\n /**\n * Set specific fields to be redacted (for visibility-first mode)\n * @param fields Array of CSS selectors for fields to redact\n */\n public setRedactedFields(fields: string[]): void {\n this.redactionManager.setFieldsToRedact(fields);\n \n // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures redaction is applied\n if (this.recordInstance) {\n this.restartWithNewRedaction();\n }\n }\n\n /**\n * Set specific fields to be unredacted (everything else stays redacted by rrweb)\n * @param fields Array of CSS selectors for fields to unredact (e.g., ['#username', '#comment'])\n */\n public setUnredactedFields(fields: string[]): void {\n this.redactionManager.setFieldsToUnredact(fields);\n \n // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures unredaction is applied\n if (this.recordInstance) {\n this.restartWithNewRedaction();\n }\n }\n\n private restartWithNewRedaction(): void {\n if (this.recordInstance) {\n this.recordInstance(); // Stop current recording\n this.start(); // Restart with new redaction settings\n }\n }\n\n /**\n * Check if any fields are currently unredacted\n */\n public hasUnredactedFields(): boolean {\n return this.redactionManager.hasUnredactedFields();\n }\n\n /**\n * Get the currently unredacted fields\n */\n public getUnredactedFields(): string[] {\n return this.redactionManager.getUnredactedFields();\n }\n\n /**\n * Remove specific fields from unredaction (they become redacted again)\n * @param fields Array of CSS selectors for fields to redact\n */\n public redactFields(fields: string[]): void {\n this.redactionManager.redactFields(fields);\n \n // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures redaction is updated\n if (this.recordInstance) {\n this.restartWithNewRedaction();\n }\n }\n\n /**\n * Clear all unredacted fields (everything becomes redacted again)\n */\n public clearUnredactedFields(): void {\n this.redactionManager.clearUnredactedFields();\n \n // ✅ RESTART RECORDING WITH NEW SETTINGS - Ensures redaction is updated\n if (this.recordInstance) {\n this.restartWithNewRedaction();\n }\n }\n\n /**\n * Get the current session ID\n */\n public getSessionId(): string {\n return this.sessionId;\n }\n\n /**\n * Get the current URL being tracked\n */\n public getCurrentUrl(): string {\n return this.currentUrl;\n }\n\n /**\n * Get current snapshot frequency info\n * Uses configured values (5 minutes, 1000 events)\n */\n public getSnapshotFrequencyInfo(): {\n sessionDuration: number;\n currentInterval: number;\n currentThreshold: number;\n phase: string;\n } {\n const sessionDuration = Date.now() - this.sessionStartTime;\n \n return {\n sessionDuration,\n currentInterval: 300000, // Configured - 5 minutes\n currentThreshold: 1000, // Configured - 1000 events\n phase: 'configured' // Using explicit configuration\n };\n }\n\n /**\n * Test if the tracker can reach the ingestion server\n */\n public async testConnection(): Promise<{ success: boolean; error?: string }> {\n try {\n await this.api.init(this.sessionId, this.endUserId);\n return { success: true };\n } catch (error: any) {\n return { \n success: false, \n error: error.message || 'Unknown error' \n };\n }\n }\n\n /**\n * Get connection status and recommendations\n */\n public getConnectionStatus(): { \n blocked: boolean; \n recommendations: string[] \n } {\n const recommendations: string[] = [];\n let blocked = false;\n\n // Check if we have queued events (might indicate blocking)\n if (this.eventQueue.length > 0) {\n blocked = true;\n recommendations.push('Some requests may be blocked by ad blockers');\n }\n\n // Check if connection was blocked during initialization\n if (this._connectionBlocked) {\n blocked = true;\n recommendations.push('Initial connection test failed - ad blocker may be active');\n }\n\n // Check if we're in a browser environment\n if (typeof window === 'undefined') {\n recommendations.push('Not running in browser environment');\n }\n\n // Check if navigator.sendBeacon is available\n if (typeof navigator.sendBeacon === 'undefined') {\n recommendations.push('sendBeacon not available, using fetch fallback');\n }\n\n return { blocked, recommendations };\n }\n\n /**\n * Check if the current user is a preexisting user\n * Returns true if the user has an existing endUserId cookie from a previous session\n */\n public isPreexistingUser(): boolean {\n if (!isBrowser) {\n return false;\n }\n \n // Check if there's an existing endUserId cookie for this API key\n const existingEndUserId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);\n return existingEndUserId !== null && existingEndUserId !== this.endUserId;\n }\n\n /**\n * Get user information including whether they are preexisting\n */\n public getUserInfo(): {\n endUserId: string | null;\n sessionId: string;\n isPreexistingUser: boolean;\n initialized: boolean;\n } {\n return {\n endUserId: this.endUserId,\n sessionId: this.sessionId,\n isPreexistingUser: this.isPreexistingUser(),\n initialized: this.initialized\n };\n }\n\n // ===== PROPERTY MANAGEMENT METHODS =====\n\n /**\n * Set a session property that will be included in all events for this session\n */\n public setSessionProperty(key: string, value: any): void {\n this.propertyManager.setSessionProperty(key, value);\n }\n\n /**\n * Set multiple session properties\n */\n public setSessionProperties(properties: Record<string, any>): void {\n this.propertyManager.setSessionProperties(properties);\n }\n\n /**\n * Get a session property\n */\n public getSessionProperty(key: string): any {\n return this.propertyManager.getSessionProperty(key);\n }\n\n /**\n * Remove a session property\n */\n public removeSessionProperty(key: string): void {\n this.propertyManager.removeSessionProperty(key);\n }\n\n /**\n * Set a user property that will be included in all events\n */\n public setUserProperty(key: string, value: any): void {\n this.propertyManager.setUserProperty(key, value);\n }\n\n /**\n * Set multiple user properties\n */\n public setUserProperties(properties: Record<string, any>): void {\n this.propertyManager.setUserProperties(properties);\n }\n\n /**\n * Get a user property\n */\n public getUserProperty(key: string): any {\n return this.propertyManager.getUserProperty(key);\n }\n\n /**\n * Remove a user property\n */\n public removeUserProperty(key: string): void {\n this.propertyManager.removeUserProperty(key);\n }\n\n /**\n * Set a property only if it hasn't been set before\n */\n public setOnce(key: string, value: any, scope: 'session' | 'user' = 'user'): void {\n this.propertyManager.setOnce(key, value, scope);\n }\n\n /**\n * Clear all session properties\n */\n public clearSessionProperties(): void {\n this.propertyManager.clearSessionProperties();\n }\n\n /**\n * Clear all user properties\n */\n public clearUserProperties(): void {\n this.propertyManager.clearUserProperties();\n }\n\n /**\n * Get all properties for debugging\n */\n public getAllProperties(): {\n automatic: Record<string, any>;\n session: Record<string, any>;\n user: Record<string, any>;\n initial: Record<string, any>;\n } {\n return this.propertyManager.getAllProperties();\n }\n}\n\n// Only expose to window object in browser environments\nif (isBrowser) {\n (window as any).HumanBehaviorTracker = HumanBehaviorTracker;\n}\n\nexport default HumanBehaviorTracker;\n","/**\n * Global tracker utility functions\n * Provides helper functions for accessing the global HumanBehavior tracker instance\n */\n\n/**\n * Identifies a user using the global HumanBehavior tracker\n * @param userProperties - User properties to identify with\n * @returns Promise<string> - The endUserId if successful, null if tracker not found\n */\nexport function identifyUserGlobally(userProperties: Record<string, any>): Promise<string> | null {\n const globalTracker = (globalThis as any).__humanBehaviorGlobalTracker;\n \n if (globalTracker?.identifyUser) {\n return globalTracker.identifyUser({ userProperties });\n } else {\n console.warn('HumanBehavior tracker not found. Make sure the SDK is initialized.');\n return null;\n }\n}\n\n/**\n * Sends an event using the global HumanBehavior tracker\n * @param eventName - Name of the event\n * @param properties - Event properties\n * @returns Promise<boolean> - True if successful, false if tracker not found\n */\nexport function sendEventGlobally(eventName: string, properties?: Record<string, any>): Promise<boolean> | null {\n const globalTracker = (globalThis as any).__humanBehaviorGlobalTracker;\n \n if (globalTracker?.track) {\n return globalTracker.track(eventName, properties);\n } else {\n console.warn('HumanBehavior tracker not found. Make sure the SDK is initialized.');\n return null;\n }\n}\n\n/**\n * Checks if the global HumanBehavior tracker is available\n * @returns boolean - True if tracker is available\n */\nexport function isGlobalTrackerAvailable(): boolean {\n const globalTracker = (globalThis as any).__humanBehaviorGlobalTracker;\n return !!(globalTracker?.identifyUser);\n}\n"],"names":["LogLevel","logger","constructor","config","this","level","ERROR","enableConsole","enableStorage","isBrowser","window","setConfig","shouldLog","formatMessage","message","args","Date","toISOString","error","formattedMessage","console","logToStorage","warn","WARN","info","INFO","log","debug","DEBUG","logs","JSON","parse","localStorage","getItem","logEntry","length","undefined","timestamp","now","push","splice","setItem","stringify","e","getLogs","clearLogs","removeItem","logError","logWarn","logInfo","logDebug","MAX_CHUNK_SIZE_BYTES","isChunkSizeExceeded","currentChunk","newEvent","sessionId","TextEncoder","encode","events","splitLargeEvent","event","simplifiedEvent","largeProperties","forEach","prop","type","url","pathname","Object","fromEntries","entries","filter","key","value","includes","HumanBehaviorAPI","apiKey","ingestionUrl","monthlyLimitReached","baseUrl","checkMonthlyLimit","init","userId","endUserId","entryURL","referrer","location","href","document","response","fetch","method","headers","Authorization","Referer","body","status","ok","errorText","text","Error","statusText","responseJson","json","sendEvents","validEvents","sendEventsChunked","results","flat","sendUserData","userData","payload","userAttributes","posthogName","email","name","result","sendUserAuth","authFields","sendBeaconEvents","blob","Blob","navigator","sendBeacon","sendCustomEvent","eventName","eventProperties","sendCustomEventBatch","RedactionManager","options","redactedText","unredactedFields","Set","redactedFields","redactionMode","excludeSelectors","redactionStrategy","mode","unredactFields","setFieldsToUnredact","defaultMarks","fieldsToRedact","redactFields","setFieldsToRedact","legacyRedactFields","userFields","fields","clear","field","add","size","Array","from","applyRedactionClasses","validFields","isPasswordSelector","applyUnredactionClasses","delete","clearUnredactedFields","removeUnredactionClasses","hasUnredactedFields","getRedactionMode","getUnredactedFields","getMaskTextSelector","join","readyState","selector","elements","querySelectorAll","element","classList","remove","some","pattern","toLowerCase","replace","getOriginalValue","HTMLInputElement","HTMLTextAreaElement","isElementUnredacted","shouldUnredactElement","matches","detectDeviceType","userAgent","screenWidth","screen","width","screenHeight","height","test","extractDomain","URL","hostname","getDeviceInfo","device_type","browser","browser_version","os","os_version","screen_resolution","viewport_size","color_depth","timezone","language","languages","match","detectBrowser","version","versionNum","parseFloat","detectOS","innerWidth","innerHeight","colorDepth","Intl","DateTimeFormat","resolvedOptions","timeZone","raw_user_agent","getLocationInfo","current_url","search","hash","title","referrer_domain","initial_referrer","initial_referrer_domain","currentUrl","utmParams","urlObj","searchParams","get","extractUTMParams","initial_host","getAutomaticProperties","getInitialProperties","locationInfo","initial_url","initial_pathname","initial_utm_source","utm_source","initial_utm_medium","utm_medium","initial_utm_campaign","utm_campaign","initial_utm_term","utm_term","initial_utm_content","utm_content","getCurrentPageProperties","PropertyManager","sessionProperties","userProperties","initialProperties","isInitialized","enableAutomaticProperties","enableSessionProperties","enableUserProperties","propertyDenylist","automaticProperties","initialize","loadSessionProperties","getEventProperties","properties","assign","setSessionProperty","applyDenylist","getAutomaticPropertiesWithGeoIP","geoIPProperties","saveSessionProperties","setSessionProperties","getSessionProperty","removeSessionProperty","setUserProperty","setUserProperties","getUserProperty","removeUserProperty","setOnce","scope","clearSessionProperties","clearUserProperties","reset","sessionStorage","stored","deniedKey","updateAutomaticProperties","getAllProperties","automatic","session","user","initial","HumanBehaviorTracker","isTrackerStarted","isStarted","setupDomReadyHandler","onDomReady","addEventListener","capture","interval","setInterval","clearInterval","setTimeout","isDomReady","requestQueue","request","processRequest","domReadyHandlers","handler","queueRequest","addEvent","identifyUser","trackPageView","registerDomReadyHandler","suppressConsoleErrors","originalConsoleError","apply","originalConsoleWarn","preventDefault","__humanBehaviorGlobalTracker","logLevel","configureLogging","tracker","maxQueueSize","recordCanvas","setUnredactedFields","enableAutomaticTracking","setupAutomaticTracking","automaticTrackingOptions","start","eventQueue","isProcessing","flushInterval","FLUSH_INTERVAL_MS","initialized","initializationPromise","originalConsole","consoleTrackingEnabled","navigationTrackingEnabled","previousUrl","originalPushState","originalReplaceState","navigationListeners","_connectionBlocked","recordInstance","sessionStartTime","rrwebRecord","fullSnapshotTimeout","api","MAX_QUEUE_SIZE","redactionManager","propertyManager","existingSessionId","lastActivity","fifteenMinutesAgo","parseInt","toString","uuidv1","catch","getCookie","setupPageUnloadHandler","setupNavigationTracking","initServerAsync","initResponse","setCookie","stack","ensureInitialized","history","pushState","replaceState","trackNavigationEvent","takeFullSnapshot","popstateListener","removeEventListener","hashchangeListener","fromUrl","toUrl","navigationData","to","data","eventType","pageViewProperties","navigationType","customEvent","pageViewData","enhancedProperties","customEventData","fallbackError","trackButtons","trackLinks","trackForms","includeText","includeClasses","setupAutomaticButtonTracking","setupAutomaticFormTracking","async","target","tagName","closest","button","buttonId","id","buttonType","page","buttonText","textContent","trim","buttonClass","className","keys","setupAutomaticLinkTracking","form","formData","FormData","formId","formAction","action","formMethod","formClass","cleanupNavigationTracking","cleanup","none","enableConsoleTracking","trackConsoleEvent","disableConsoleTracking","consoleData","map","arg","String","visibilityState","flushEvents","updateActivity","viewLogs","originalEndUserId","getUserAttributes","startRecording","record","emit","addRecordingEvent","maskTextSelector","maskTextFn","maskAllInputs","maskInputOptions","password","textarea","number","tel","date","time","month","week","maskInputFn","HTMLElement","repeat","shouldShow","slimDOMOptions","collectFonts","inlineStylesheet","recordCrossOriginIframes","sampling","canvas","dataURLOptions","quality","hooks","input","node","querySelector","checkDomReady","once","clearTimeout","requestAnimationFrame","stop","hasData","hasNode","shift","eventsToProcess","fullSnapshots","daysToExpire","setTime","getTime","expires","toUTCString","cookie","localStorageError","nameEQ","ca","split","i","c","charAt","substring","indexOf","cookieValue","localStorageValue","deleteCookie","logout","userIdCookieName","redact","setRedactedFields","restartWithNewRedaction","getSessionId","getCurrentUrl","getSnapshotFrequencyInfo","sessionDuration","currentInterval","currentThreshold","phase","testConnection","success","getConnectionStatus","recommendations","blocked","isPreexistingUser","existingEndUserId","getUserInfo","identifyUserGlobally","globalTracker","globalThis","sendEventGlobally","track","isGlobalTrackerAvailable"],"mappings":"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"}
|