humanbehavior-js 0.5.71 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/browser/dist/cjs/index.js +347 -149
- package/packages/browser/dist/cjs/index.js.map +1 -1
- package/packages/browser/dist/esm/index.js +342 -144
- 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/api.d.ts +8 -0
- package/packages/core/dist/api.d.ts.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 +14 -0
- 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/retry-queue.ts","../src/persistence.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// Global flag to track if SDK is currently logging (prevents self-tracking)\nlet sdkLoggingInProgress = false;\n\n// Export getter for tracker to check\nexport const isSDKLogging = (): boolean => sdkLoggingInProgress;\n\n// Export convenience methods with SDK logging flag\nexport const logError = (message: string, ...args: any[]) => {\n sdkLoggingInProgress = true;\n try {\n logger.error(message, ...args);\n } finally {\n sdkLoggingInProgress = false;\n }\n};\n\nexport const logWarn = (message: string, ...args: any[]) => {\n sdkLoggingInProgress = true;\n try {\n logger.warn(message, ...args);\n } finally {\n sdkLoggingInProgress = false;\n }\n};\n\nexport const logInfo = (message: string, ...args: any[]) => {\n sdkLoggingInProgress = true;\n try {\n logger.info(message, ...args);\n } finally {\n sdkLoggingInProgress = false;\n }\n};\n\nexport const logDebug = (message: string, ...args: any[]) => {\n sdkLoggingInProgress = true;\n try {\n logger.debug(message, ...args);\n } finally {\n sdkLoggingInProgress = false;\n }\n}; ","import { logWarn, logError, logDebug } from './utils/logger';\n\nconst THIRTY_MINUTES = 30 * 60 * 1000;\nconst KEEP_ALIVE_THRESHOLD = 64 * 1024 * 0.8; // 64KB * 0.8 for safety margin\n\n/**\n * Generates a jittered exponential backoff delay in milliseconds\n * \n * The base value is 3 seconds, which is doubled with each retry\n * up to the maximum of 30 minutes\n * \n * Each value then has +/- 50% jitter\n * \n * Giving a range of 3 seconds up to 45 minutes\n */\nexport function pickNextRetryDelay(retriesPerformedSoFar: number): number {\n const rawBackoffTime = 3000 * 2 ** retriesPerformedSoFar;\n const minBackoff = rawBackoffTime / 2;\n const cappedBackoffTime = Math.min(THIRTY_MINUTES, rawBackoffTime);\n const jitterFraction = Math.random() - 0.5; // A random number between -0.5 and 0.5\n const jitter = jitterFraction * (cappedBackoffTime - minBackoff);\n return Math.ceil(cappedBackoffTime + jitter);\n}\n\nexport interface RetriableRequestOptions {\n url: string;\n method?: string;\n headers?: Record<string, string>;\n body?: string | Blob;\n retriesPerformedSoFar?: number;\n estimatedSize?: number;\n callback?: (response: { statusCode: number; text: string; json?: any }) => void;\n}\n\ninterface RetryQueueElement {\n retryAt: number;\n requestOptions: RetriableRequestOptions;\n}\n\nexport class RetryQueue {\n private _isPolling: boolean = false;\n private _poller: ReturnType<typeof setTimeout> | undefined;\n private _pollIntervalMs: number = 3000;\n private _queue: RetryQueueElement[] = [];\n private _areWeOnline: boolean;\n private _sendRequest: (options: RetriableRequestOptions) => Promise<void>;\n\n constructor(sendRequest: (options: RetriableRequestOptions) => Promise<void>) {\n this._queue = [];\n this._areWeOnline = true;\n this._sendRequest = sendRequest;\n\n if (typeof window !== 'undefined' && 'onLine' in window.navigator) {\n this._areWeOnline = window.navigator.onLine;\n\n window.addEventListener('online', () => {\n this._areWeOnline = true;\n this._flush();\n });\n\n window.addEventListener('offline', () => {\n this._areWeOnline = false;\n });\n }\n }\n\n get length(): number {\n return this._queue.length;\n }\n\n async retriableRequest(options: RetriableRequestOptions): Promise<void> {\n const retriesPerformedSoFar = options.retriesPerformedSoFar || 0;\n \n // Add retry count to URL if retrying\n if (retriesPerformedSoFar > 0) {\n const url = new URL(options.url);\n url.searchParams.set('retry_count', retriesPerformedSoFar.toString());\n options.url = url.toString();\n }\n\n try {\n await this._sendRequest(options);\n } catch (error: any) {\n // Check if we should retry\n const shouldRetry = this._shouldRetry(error, retriesPerformedSoFar);\n \n if (shouldRetry && retriesPerformedSoFar < 10) {\n this._enqueue(options);\n return;\n }\n\n // Call callback with error if provided\n if (options.callback) {\n options.callback({\n statusCode: error.status || 0,\n text: error.message || 'Request failed'\n });\n }\n }\n }\n\n private _shouldRetry(error: any, retriesPerformedSoFar: number): boolean {\n // Don't retry on client errors (4xx) except for 408, 429\n if (error.status >= 400 && error.status < 500) {\n return error.status === 408 || error.status === 429;\n }\n \n // Retry on server errors (5xx) and network errors\n return error.status >= 500 || !error.status;\n }\n\n private _enqueue(requestOptions: RetriableRequestOptions): void {\n const retriesPerformedSoFar = requestOptions.retriesPerformedSoFar || 0;\n requestOptions.retriesPerformedSoFar = retriesPerformedSoFar + 1;\n\n const msToNextRetry = pickNextRetryDelay(retriesPerformedSoFar);\n const retryAt = Date.now() + msToNextRetry;\n\n this._queue.push({ retryAt, requestOptions });\n\n let logMessage = `Enqueued failed request for retry in ${Math.round(msToNextRetry / 1000)}s`;\n if (typeof navigator !== 'undefined' && !navigator.onLine) {\n logMessage += ' (Browser is offline)';\n }\n logWarn(logMessage);\n\n if (!this._isPolling) {\n this._isPolling = true;\n this._poll();\n }\n }\n\n private _poll(): void {\n if (this._poller) {\n clearTimeout(this._poller);\n }\n this._poller = setTimeout(() => {\n if (this._areWeOnline && this._queue.length > 0) {\n this._flush();\n }\n this._poll();\n }, this._pollIntervalMs);\n }\n\n private _flush(): void {\n const now = Date.now();\n const notToFlush: RetryQueueElement[] = [];\n const toFlush = this._queue.filter((item) => {\n if (item.retryAt < now) {\n return true;\n }\n notToFlush.push(item);\n return false;\n });\n\n this._queue = notToFlush;\n\n if (toFlush.length > 0) {\n for (const { requestOptions } of toFlush) {\n this.retriableRequest(requestOptions).catch((error) => {\n logError('Failed to retry request:', error);\n });\n }\n }\n }\n\n unload(): void {\n if (this._poller) {\n clearTimeout(this._poller);\n this._poller = undefined;\n }\n\n for (const { requestOptions } of this._queue) {\n try {\n // Use sendBeacon for unload to ensure requests are sent\n this._sendBeaconRequest(requestOptions);\n } catch (e) {\n logError('Failed to send request via sendBeacon on unload:', e);\n }\n }\n this._queue = [];\n }\n\n private _sendBeaconRequest(options: RetriableRequestOptions): void {\n if (typeof navigator === 'undefined' || !navigator.sendBeacon) {\n return;\n }\n\n try {\n const url = new URL(options.url);\n url.searchParams.set('beacon', '1');\n\n let body: Blob | null = null;\n if (options.body) {\n if (typeof options.body === 'string') {\n body = new Blob([options.body], { type: 'application/json' });\n } else if (options.body instanceof Blob) {\n body = options.body;\n }\n }\n\n const success = navigator.sendBeacon(url.toString(), body);\n if (!success) {\n logWarn('sendBeacon returned false for unload request');\n }\n } catch (error) {\n logError('Error sending beacon request:', error);\n }\n }\n}\n\n","import { logDebug, logWarn } from './utils/logger';\n\nconst STORAGE_KEY_PREFIX = 'human_behavior_';\n\nexport interface QueuedEvent {\n sessionId: string;\n events: any[];\n endUserId?: string | null;\n windowId?: string;\n automaticProperties?: any;\n timestamp: number;\n}\n\nexport class EventPersistence {\n private storageKey: string;\n private maxQueueSize: number;\n\n constructor(apiKey: string, maxQueueSize: number = 1000) {\n this.storageKey = `${STORAGE_KEY_PREFIX}queue`;\n this.maxQueueSize = maxQueueSize;\n }\n\n /**\n * Get persisted events from storage\n */\n getQueue(): QueuedEvent[] {\n if (typeof window === 'undefined' || !window.localStorage) {\n return [];\n }\n\n try {\n const stored = window.localStorage.getItem(this.storageKey);\n if (!stored) {\n return [];\n }\n\n const queue = JSON.parse(stored);\n if (!Array.isArray(queue)) {\n return [];\n }\n\n return queue;\n } catch (error) {\n logWarn('Failed to read persisted queue:', error);\n return [];\n }\n }\n\n /**\n * Save events to storage\n */\n setQueue(queue: QueuedEvent[]): void {\n if (typeof window === 'undefined' || !window.localStorage) {\n return;\n }\n\n try {\n // Limit queue size\n const limitedQueue = queue.slice(-this.maxQueueSize);\n window.localStorage.setItem(this.storageKey, JSON.stringify(limitedQueue));\n logDebug(`Persisted ${limitedQueue.length} events to storage`);\n } catch (error: any) {\n // Handle quota exceeded errors gracefully\n if (error.name === 'QuotaExceededError' || error.code === 22) {\n logWarn('Storage quota exceeded, clearing old events');\n try {\n // Try to save a smaller queue\n const smallerQueue = queue.slice(-Math.floor(this.maxQueueSize / 2));\n window.localStorage.setItem(this.storageKey, JSON.stringify(smallerQueue));\n } catch (e) {\n logWarn('Failed to save smaller queue, clearing storage');\n this.clearQueue();\n }\n } else {\n logWarn('Failed to persist queue:', error);\n }\n }\n }\n\n /**\n * Add event to persisted queue\n */\n addToQueue(event: QueuedEvent): void {\n const queue = this.getQueue();\n queue.push(event);\n\n // Remove oldest events if queue is too large\n if (queue.length > this.maxQueueSize) {\n queue.shift();\n logDebug('Queue is full, the oldest event is dropped.');\n }\n\n this.setQueue(queue);\n }\n\n /**\n * Remove events from queue (after successful send)\n */\n removeFromQueue(count: number): void {\n const queue = this.getQueue();\n queue.splice(0, count);\n this.setQueue(queue);\n }\n\n /**\n * Clear persisted queue\n */\n clearQueue(): void {\n if (typeof window === 'undefined' || !window.localStorage) {\n return;\n }\n\n try {\n window.localStorage.removeItem(this.storageKey);\n } catch (error) {\n logWarn('Failed to clear persisted queue:', error);\n }\n }\n\n /**\n * Get queue length\n */\n getQueueLength(): number {\n return this.getQueue().length;\n }\n}\n\n","import { logError, logInfo, logDebug, logWarn } from './utils/logger';\nimport { v1 as uuidv1 } from 'uuid';\nimport { RetryQueue, RetriableRequestOptions } from './retry-queue';\nimport { EventPersistence, QueuedEvent } from './persistence';\n\n// SDK version will be replaced at build time by Rollup replace plugin\nconst SDK_VERSION = '__SDK_VERSION__';\n\nexport const MAX_CHUNK_SIZE_BYTES = 1024 * 1024; // 1MB chunk size - more conservative\nconst KEEP_ALIVE_THRESHOLD = 64 * 1024 * 0.8; // 64KB * 0.8 for safety margin\nconst REQUEST_TIMEOUT_MS = 10000; // 10 seconds default timeout\n\nexport function isChunkSizeExceeded(currentChunk: any[], newEvent: any, sessionId: string): boolean {\n const nextChunkSize = new TextEncoder().encode(safeJsonStringify({\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(safeJsonStringify({\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\n/**\n * Safe JSON stringify that handles BigInt values\n */\nfunction safeJsonStringify(data: any): string {\n return JSON.stringify(data, (_, value) => {\n if (typeof value === 'bigint') {\n return value.toString();\n }\n return value;\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(safeJsonStringify({\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(safeJsonStringify({\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 private sessionId: string = '';\n private endUserId: string | null = null;\n private cspBlocked: boolean = false; // Track if CSP is blocking requests\n private retryQueue: RetryQueue;\n private persistence: EventPersistence;\n private requestTimeout: number = REQUEST_TIMEOUT_MS;\n private currentBatchSize: number = 100; // Dynamic batch size for 413 handling\n\n constructor({ apiKey, ingestionUrl }: { apiKey: string, ingestionUrl: string }) {\n this.apiKey = apiKey;\n this.baseUrl = ingestionUrl;\n this.persistence = new EventPersistence(apiKey);\n this.retryQueue = new RetryQueue((options) => this._sendRequestInternal(options));\n \n // Load persisted events on initialization\n this._loadPersistedEvents();\n }\n\n /**\n * Set session and user IDs for tracking context\n */\n public setTrackingContext(sessionId: string, endUserId: string | null): void {\n this.sessionId = sessionId;\n this.endUserId = endUserId;\n }\n\n /**\n * Load persisted events from storage and send them\n */\n private async _loadPersistedEvents(): Promise<void> {\n const persistedQueue = this.persistence.getQueue();\n if (persistedQueue.length === 0) {\n return;\n }\n\n logDebug(`Loading ${persistedQueue.length} persisted events from storage`);\n \n for (const queuedEvent of persistedQueue) {\n try {\n await this.sendEventsChunked(\n queuedEvent.events,\n queuedEvent.sessionId,\n queuedEvent.endUserId || undefined,\n queuedEvent.windowId,\n queuedEvent.automaticProperties\n );\n // Remove from persistence after successful send\n this.persistence.removeFromQueue(1);\n } catch (error) {\n logWarn('Failed to send persisted event, will retry later:', error);\n // Keep in persistence for retry\n }\n }\n }\n\n /**\n * Internal method to send request (used by retry queue)\n */\n private async _sendRequestInternal(options: RetriableRequestOptions): Promise<void> {\n const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n if (controller) {\n timeoutId = setTimeout(() => {\n controller!.abort();\n }, this.requestTimeout);\n }\n\n try {\n const estimatedSize = options.estimatedSize || 0;\n const useKeepalive = options.method === 'POST' && estimatedSize < KEEP_ALIVE_THRESHOLD;\n\n const response = await fetch(options.url, {\n method: options.method || 'GET',\n headers: options.headers || {},\n body: options.body,\n signal: controller?.signal,\n keepalive: useKeepalive\n });\n\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n\n const responseText = await response.text();\n let responseJson: any = null;\n \n try {\n responseJson = JSON.parse(responseText);\n } catch {\n // Not JSON, ignore\n }\n\n if (options.callback) {\n options.callback({\n statusCode: response.status,\n text: responseText,\n json: responseJson\n });\n }\n\n if (!response.ok) {\n throw { status: response.status, message: responseText };\n }\n } catch (error: any) {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n\n if (error.name === 'AbortError') {\n throw { status: 0, message: 'Request timeout' };\n }\n throw error;\n }\n }\n\n /**\n * Handle unload - send pending retries via sendBeacon\n */\n public unload(): void {\n this.retryQueue.unload();\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 this.trackedFetch(`${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: safeJsonStringify({\n sessionId: sessionId,\n endUserId: userId,\n entryURL: entryURL,\n referrer: referrer,\n sdkVersion: SDK_VERSION // Include SDK version for tracking\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 this.trackedFetch(`${this.baseUrl}/api/ingestion/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify({\n sessionId,\n events: validEvents,\n endUserId: userId,\n sdkVersion: SDK_VERSION // Include SDK version for tracking\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, windowId?: string, automaticProperties?: any) {\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 this.trackedFetch(`${this.baseUrl}/api/ingestion/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify({\n sessionId,\n events: currentChunk,\n endUserId: userId,\n windowId: windowId,\n automaticProperties: automaticProperties, // Include automatic properties for user creation\n sdkVersion: SDK_VERSION // Include SDK version for tracking\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 const result = await this._sendChunkWithRetry(\n currentChunk,\n sessionId,\n userId,\n windowId,\n automaticProperties || currentChunk[0]?.automaticProperties\n );\n if (result) {\n results.push(result);\n }\n }\n \n return results.flat();\n } catch (error) {\n logError('Error sending events:', error);\n // Persist failed events for retry\n this._persistEvents(events, sessionId, userId, windowId, automaticProperties);\n throw error;\n }\n }\n\n /**\n * Send a chunk of events with retry logic and 413 handling\n */\n private async _sendChunkWithRetry(\n chunk: any[],\n sessionId: string,\n userId?: string,\n windowId?: string,\n automaticProperties?: any\n ): Promise<any | null> {\n let batchSize = Math.min(this.currentBatchSize, chunk.length);\n let startIndex = 0;\n\n while (startIndex < chunk.length) {\n const batch = chunk.slice(startIndex, startIndex + batchSize);\n const payload = {\n sessionId,\n events: batch,\n endUserId: userId,\n windowId: windowId,\n automaticProperties: automaticProperties,\n sdkVersion: SDK_VERSION // Include SDK version for tracking\n };\n\n const bodyString = safeJsonStringify(payload);\n const estimatedSize = new TextEncoder().encode(bodyString).length;\n\n try {\n const response = await this.trackedFetch(`${this.baseUrl}/api/ingestion/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: bodyString\n }, estimatedSize);\n \n if (!response.ok) {\n if (response.status === 429) {\n this.monthlyLimitReached = true;\n // Persist remaining events\n this._persistEvents(chunk.slice(startIndex), sessionId, userId, windowId, automaticProperties);\n return null;\n }\n \n if (response.status === 413) {\n // Content too large - reduce batch size and retry\n logWarn(`413 error: reducing batch size from ${batchSize} to ${Math.max(1, Math.floor(batchSize / 2))}`);\n this.currentBatchSize = Math.max(1, Math.floor(batchSize / 2));\n batchSize = this.currentBatchSize;\n // Retry with smaller batch\n continue;\n }\n\n // For other errors, use retry queue\n await this.retryQueue.retriableRequest({\n url: `${this.baseUrl}/api/ingestion/events`,\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: bodyString,\n estimatedSize: estimatedSize,\n callback: (response) => {\n if (response.statusCode === 200 && response.json) {\n if (response.json.monthlyLimitReached === true) {\n this.monthlyLimitReached = true;\n }\n }\n }\n });\n \n // Persist for retry\n this._persistEvents(chunk.slice(startIndex), sessionId, userId, windowId, automaticProperties);\n return null;\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 startIndex += batchSize;\n \n // If we successfully sent a batch, return the result\n if (startIndex >= chunk.length) {\n return responseJson;\n }\n } catch (error: any) {\n // Network error - use retry queue\n logWarn('Network error sending chunk, adding to retry queue:', error);\n await this.retryQueue.retriableRequest({\n url: `${this.baseUrl}/api/ingestion/events`,\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: bodyString,\n estimatedSize: estimatedSize,\n callback: (response) => {\n if (response.statusCode === 200 && response.json) {\n if (response.json.monthlyLimitReached === true) {\n this.monthlyLimitReached = true;\n }\n }\n }\n });\n \n // Persist for retry\n this._persistEvents(chunk.slice(startIndex), sessionId, userId, windowId, automaticProperties);\n return null;\n }\n }\n\n return null;\n }\n\n /**\n * Persist events to storage for retry\n */\n private _persistEvents(\n events: any[],\n sessionId: string,\n userId?: string,\n windowId?: string,\n automaticProperties?: any\n ): void {\n if (events.length === 0) {\n return;\n }\n\n this.persistence.addToQueue({\n sessionId,\n events,\n endUserId: userId,\n windowId,\n automaticProperties,\n timestamp: Date.now()\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 this.trackedFetch(`${this.baseUrl}/api/ingestion/user`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify(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 this.trackedFetch(`${this.baseUrl}/api/ingestion/user/auth`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify({\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, userId?: string, windowId?: string, automaticProperties?: any) {\n // Create JSON payload that matches the server's expected format\n // ✅ FIX: Include all fields that sendEventsChunked includes\n // This ensures sendBeacon requests are processed identically to regular HTTP requests\n const payload = {\n sessionId: sessionId,\n events: events,\n endUserId: userId || null, // ✅ FIX: Use actual userId instead of hardcoded null\n windowId: windowId, // ✅ FIX: Include windowId if available\n automaticProperties: automaticProperties, // ✅ FIX: Include automatic properties for user creation\n sdkVersion: SDK_VERSION, // ✅ FIX: Include SDK version for tracking\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([safeJsonStringify(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 this.trackedFetch(`${this.baseUrl}/api/ingestion/customEvent`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify({\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 this.trackedFetch(`${this.baseUrl}/api/ingestion/customEvent/batch`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify({\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 /**\n * Send console log (warn/error) to ingestion server\n */\n async sendLog(logData: {\n level: 'warn' | 'error';\n message: string;\n stack?: string;\n url: string;\n timestampMs: number;\n sessionId: string;\n endUserId: string | null;\n }): Promise<void> {\n try {\n logDebug('[SDK] Sending log to server:', { level: logData.level, message: logData.message.substring(0, 50), sessionId: logData.sessionId });\n \n if (!this.baseUrl) {\n return;\n }\n \n if (!logData.sessionId) {\n return;\n }\n \n // Use regular fetch (not trackedFetch) since this is SDK's own request\n const response = await fetch(`${this.baseUrl}/api/ingestion/logs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify(logData)\n });\n \n if (!response.ok) {\n logWarn('[SDK] Failed to send log to server:', response.status, response.statusText);\n } else {\n logDebug('[SDK] Log sent successfully');\n }\n } catch (error) {\n // Silent fail - don't break app if logging fails\n logWarn('[SDK] Failed to send log to server:', error);\n }\n }\n\n /**\n * Send network error to ingestion server\n */\n async sendNetworkError(errorData: {\n requestId: string;\n url: string;\n method: string;\n status: number | null;\n statusText: string | null;\n duration: number;\n timestampMs: number;\n sessionId: string;\n endUserId: string | null;\n errorType: string;\n errorMessage: string | null;\n errorName?: string | null;\n // New span fields\n startTimeMs?: number;\n spanName?: string;\n spanStatus?: 'error' | 'success' | 'slow';\n attributes?: Record<string, any>;\n }): Promise<void> {\n try {\n logDebug('[SDK] Sending network error to server:', { errorType: errorData.errorType, url: errorData.url.substring(0, 50), sessionId: errorData.sessionId });\n \n if (!this.baseUrl) {\n return;\n }\n \n if (!errorData.sessionId) {\n return;\n }\n \n // Use regular fetch (not trackedFetch) since this is SDK's own request\n const response = await fetch(`${this.baseUrl}/api/ingestion/network`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify(errorData)\n });\n \n if (!response.ok) {\n logWarn('[SDK] Failed to send network error to server:', response.status, response.statusText);\n } else {\n logDebug('[SDK] Network error sent successfully');\n }\n } catch (error) {\n // Silent fail - don't break app if tracking fails\n logWarn('[SDK] Failed to send network error to server:', error);\n }\n }\n\n /**\n * Wrapper for fetch that tracks network errors and falls back to sendBeacon on CSP violations\n * Skips tracking for SDK's own requests to ingestion server\n */\n private async trackedFetch(url: string, options: RequestInit, estimatedSize?: number): Promise<Response> {\n const requestStartTime = Date.now();\n const requestId = uuidv1();\n \n // ✅ SKIP TRACKING: Don't track SDK's own requests to ingestion server\n const shouldSkipTracking = this.shouldSkipNetworkTracking(url);\n \n // If CSP is already known to be blocking, use sendBeacon directly for POST requests\n if (this.cspBlocked && options.method === 'POST' && typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n return this.trackedFetchWithBeaconFallback(url, options, shouldSkipTracking);\n }\n \n try {\n const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n if (controller) {\n timeoutId = setTimeout(() => {\n controller!.abort();\n }, this.requestTimeout);\n }\n\n const useKeepalive = options.method === 'POST' && estimatedSize !== undefined && estimatedSize < KEEP_ALIVE_THRESHOLD;\n\n const response = await fetch(url, {\n ...options,\n signal: controller?.signal,\n keepalive: useKeepalive\n });\n\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n const requestDuration = Date.now() - requestStartTime;\n \n // Track failed requests (4xx, 5xx) AND skip SDK requests\n if (!response.ok && !shouldSkipTracking) {\n await this.sendNetworkError({\n requestId,\n url,\n method: options.method || 'GET',\n status: response.status,\n statusText: response.statusText,\n duration: requestDuration,\n timestampMs: Date.now(),\n sessionId: this.sessionId,\n endUserId: this.endUserId,\n errorType: this.classifyHttpError(response.status),\n errorMessage: response.statusText,\n // New span fields\n startTimeMs: requestStartTime,\n spanName: `${options.method || 'GET'} ${url}`,\n spanStatus: 'error',\n attributes: {\n 'http.status_code': response.status,\n 'http.status_text': response.statusText,\n }\n }).catch(() => {}); // Non-blocking\n }\n \n return response;\n } catch (error: any) {\n const requestDuration = Date.now() - requestStartTime;\n \n // Handle timeout errors\n if (error.name === 'AbortError') {\n const timeoutError: any = new Error('Request timeout');\n timeoutError.name = 'TimeoutError';\n error = timeoutError;\n }\n \n // Check if this is a CSP violation\n const isCSPViolation = this.isCSPViolation(error);\n \n if (isCSPViolation && options.method === 'POST' && typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n // Mark CSP as blocked for future requests\n this.cspBlocked = true;\n logWarn('[SDK] CSP violation detected, falling back to sendBeacon for future requests');\n \n // Try sendBeacon as fallback\n return this.trackedFetchWithBeaconFallback(url, options, shouldSkipTracking);\n }\n \n // Track network errors BUT skip SDK requests\n if (!shouldSkipTracking) {\n await this.sendNetworkError({\n requestId,\n url,\n method: options.method || 'GET',\n status: null,\n statusText: null,\n duration: requestDuration,\n timestampMs: Date.now(),\n sessionId: this.sessionId,\n endUserId: this.endUserId,\n errorType: this.classifyNetworkError(error),\n errorMessage: error.message,\n errorName: error.name,\n // New span fields\n startTimeMs: requestStartTime,\n spanName: `${options.method || 'GET'} ${url}`,\n spanStatus: 'error',\n attributes: {\n 'error.name': error.name,\n 'error.message': error.message,\n }\n }).catch(() => {}); // Non-blocking\n }\n \n throw error; // Re-throw to maintain existing error handling\n }\n }\n\n /**\n * Fallback to sendBeacon when CSP blocks fetch\n * sendBeacon bypasses CSP connect-src restrictions\n * Note: sendBeacon is synchronous and fire-and-forget, so we can't await it\n */\n private async trackedFetchWithBeaconFallback(url: string, options: RequestInit, shouldSkipTracking: boolean): Promise<Response> {\n try {\n // Extract and parse body synchronously (sendBeacon is synchronous)\n let bodyJson: any = null;\n let bodyString: string = '';\n \n if (options.body) {\n if (typeof options.body === 'string') {\n try {\n bodyJson = JSON.parse(options.body);\n bodyString = options.body;\n } catch {\n // If not JSON, use as-is\n bodyString = options.body;\n }\n } else if (options.body instanceof Blob) {\n // Can't read Blob synchronously, so we'll need to handle this differently\n // For now, we'll add apiKey to URL and send the blob as-is\n logWarn('[SDK] Cannot extract apiKey from Blob body for sendBeacon, using URL param');\n url = `${url}${url.includes('?') ? '&' : '?'}apiKey=${encodeURIComponent(this.apiKey)}`;\n const success = navigator.sendBeacon(url, options.body);\n return success ? new Response(null, { status: 200, statusText: 'OK', headers: new Headers() })\n : new Response(null, { status: 500, statusText: 'sendBeacon failed', headers: new Headers() });\n } else {\n // For other types, try to stringify\n bodyString = safeJsonStringify(options.body);\n bodyJson = options.body;\n }\n }\n \n // sendBeacon doesn't support headers, so we need to include auth in body or URL\n // For ingestion endpoints, include apiKey in the body JSON\n if (url.includes('/api/ingestion/')) {\n if (bodyJson && typeof bodyJson === 'object') {\n // Add API key to body JSON\n bodyJson.apiKey = this.apiKey;\n bodyString = safeJsonStringify(bodyJson);\n } else if (bodyString) {\n // Try to parse and add apiKey\n try {\n const parsed = JSON.parse(bodyString);\n parsed.apiKey = this.apiKey;\n bodyString = safeJsonStringify(parsed);\n } catch {\n // If parsing fails, append apiKey as query param\n url = `${url}${url.includes('?') ? '&' : '?'}apiKey=${encodeURIComponent(this.apiKey)}`;\n }\n } else {\n // No body, add apiKey to URL\n url = `${url}${url.includes('?') ? '&' : '?'}apiKey=${encodeURIComponent(this.apiKey)}`;\n }\n } else {\n // For non-ingestion endpoints, add apiKey to URL\n url = `${url}${url.includes('?') ? '&' : '?'}apiKey=${encodeURIComponent(this.apiKey)}`;\n }\n \n // Create Blob with proper Content-Type for sendBeacon\n const blob = bodyString \n ? new Blob([bodyString], { type: 'application/json' })\n : null;\n \n // Use sendBeacon (synchronous, fire-and-forget)\n const success = navigator.sendBeacon(url, blob);\n \n if (success) {\n logDebug('[SDK] Successfully sent request via sendBeacon (CSP fallback)');\n // Return a mock Response object for compatibility\n return new Response(null, {\n status: 200,\n statusText: 'OK',\n headers: new Headers()\n });\n } else {\n logWarn('[SDK] sendBeacon returned false - browser may be throttling');\n // Return success anyway since sendBeacon is best-effort\n return new Response(null, {\n status: 200,\n statusText: 'OK (sendBeacon best-effort)',\n headers: new Headers()\n });\n }\n } catch (error: any) {\n logError('[SDK] sendBeacon fallback failed:', error);\n // Return a mock error response\n return new Response(null, {\n status: 500,\n statusText: 'Failed to send via sendBeacon',\n headers: new Headers()\n });\n }\n }\n\n /**\n * Detect if an error is a CSP violation\n */\n private isCSPViolation(error: any): boolean {\n const errorMessage = (error?.message || '').toLowerCase();\n const errorName = (error?.name || '').toLowerCase();\n \n // CSP violations typically manifest as:\n // 1. TypeError with \"Failed to fetch\" and CSP-related text\n // 2. Network errors that mention CSP or Content Security Policy\n // 3. Errors that mention \"violates\" and \"Content Security Policy\"\n \n return (\n (errorName === 'typeerror' && errorMessage.includes('failed to fetch')) ||\n errorMessage.includes('content security policy') ||\n errorMessage.includes('csp') ||\n errorMessage.includes('violates') ||\n // Check for common CSP violation patterns\n (errorMessage.includes('refused to connect') && errorMessage.includes('violates'))\n );\n }\n\n /**\n * Check if network request should be skipped (SDK's own requests)\n */\n private shouldSkipNetworkTracking(url: string): boolean {\n // Skip tracking if URL matches ingestion server base URL\n if (!url || !this.baseUrl) {\n return false;\n }\n \n try {\n const urlObj = new URL(url);\n const baseUrlObj = new URL(this.baseUrl);\n \n // Skip if same origin (same protocol, host, port)\n if (urlObj.origin === baseUrlObj.origin) {\n // Also check if it's an ingestion endpoint\n if (urlObj.pathname.startsWith('/api/ingestion/')) {\n return true;\n }\n }\n \n // Also check string matching as fallback\n if (url.includes(this.baseUrl)) {\n return true;\n }\n \n return false;\n } catch (error) {\n // If URL parsing fails, do simple string check\n return url.includes(this.baseUrl);\n }\n }\n\n private classifyHttpError(status: number): string {\n if (status >= 400 && status < 500) {\n return 'client_error';\n }\n if (status >= 500) {\n return 'server_error';\n }\n return 'unknown_error';\n }\n\n private classifyNetworkError(error: any): string {\n const errorMessage = error.message || '';\n const errorName = error.name || '';\n \n // Check for CSP violations first\n if (this.isCSPViolation(error)) {\n return 'csp_violation';\n }\n \n // Check for blocked requests (ad blockers, browser extensions, etc.)\n // This includes ERR_BLOCKED_BY_CLIENT, ERR_BLOCKED_BY_RESPONSE, and other blocked variants\n if (errorMessage.includes('ERR_BLOCKED_BY_CLIENT') || \n errorMessage.includes('ERR_BLOCKED_BY_RESPONSE') ||\n errorMessage.includes('blocked:other') ||\n errorMessage.includes('net::ERR_BLOCKED_BY_CLIENT') ||\n errorMessage.includes('net::ERR_BLOCKED_BY_RESPONSE') ||\n (errorName === 'TypeError' && errorMessage.includes('Failed to fetch') && \n (errorMessage.includes('blocked') || errorMessage.includes('ERR_BLOCKED')))) {\n return 'blocked_by_client';\n }\n if (errorMessage.includes('CORS') || errorMessage.includes('Access-Control')) {\n return 'cors_error';\n }\n if (errorMessage.includes('timeout') || errorName === 'TimeoutError') {\n return 'timeout_error';\n }\n if (errorMessage.includes('Failed to fetch') || errorMessage.includes('NetworkError')) {\n return 'network_error';\n }\n return 'unknown_error';\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, isSDKLogging } 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 private pendingCustomEvents: Array<{ eventName: string; properties: any; timestamp: number }> = [];\n private pendingLogs: Array<{ logData: any; timestamp: number }> = [];\n private pendingNetworkErrors: Array<{ errorData: any; timestamp: number }> = [];\n \n private sessionId!: string;\n private windowId!: string; // Window ID for multi-window tracking\n // In-memory session state (source of truth during session)\n private _sessionActivityTimestamp: number | null = null;\n private _sessionStartTimestamp: number | null = null;\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 ingestionUrl!: 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 // Network tracking properties\n private originalFetch: typeof fetch | null = null;\n private networkTrackingEnabled: boolean = false;\n \n // Tracking configuration flags\n private enableConsoleTrackingFlag: boolean = true; // Default: enabled (opt-out)\n private enableNetworkTrackingFlag: boolean = true; // Default: enabled (opt-out)\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 private readonly minimumDurationMilliseconds: number = 5000; // Default: 5 seconds minimum\n \n // WindowId tracking properties\n private readonly _window_id_storage_key: string;\n private readonly _primary_window_exists_storage_key: string;\n \n // Idle detection properties\n private _isIdle: boolean | 'unknown' = 'unknown'; // Idle state: true, false, or 'unknown' (initial)\n private _lastActivityTimestamp: number = Date.now(); // Last user interaction timestamp\n private readonly IDLE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes\n \n // Rage click tracking properties\n private rageClickTracker: {\n clicks: Array<{ x: number; y: number; timestamp: number; element: HTMLElement }>;\n } = { clicks: [] };\n private readonly RAGE_CLICK_THRESHOLD_PX = 30; // 30px radius\n private readonly RAGE_CLICK_TIMEOUT_MS = 1000; // 1 second\n private readonly RAGE_CLICK_CLICK_COUNT = 3; // 3 clicks required\n \n // Dead click tracking properties\n private deadClickTracker: {\n pendingClicks: Map<number, {\n element: HTMLElement;\n originalEvent: MouseEvent;\n timestamp: number;\n timer: number;\n cancelled: boolean;\n lastMutationTimeAtClick?: number; // Store mutation time at click to detect new mutations\n }>;\n mutationObserver?: MutationObserver;\n lastMutationTime?: number;\n lastSelectionChangedTime?: number;\n } = {\n pendingClicks: new Map()\n };\n private readonly DEAD_CLICK_SCROLL_THRESHOLD_MS = 100; // Scroll within 100ms counts as action\n private readonly DEAD_CLICK_SELECTION_THRESHOLD_MS = 100; // Selection change within 100ms counts\n private readonly DEAD_CLICK_MUTATION_THRESHOLD_MS = 2000; // DOM mutation within 2s counts (reduced from 2.5s)\n private readonly DEAD_CLICK_ABSOLUTE_TIMEOUT_MS = 1400; // Minimum wait time before considering dead\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 enableConsoleTracking?: boolean; // Enable console warn/error tracking (default: true, set to false to opt-out)\n enableNetworkTracking?: boolean; // Enable network error tracking (default: true, set to false to opt-out)\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 enableConsoleTracking: options?.enableConsoleTracking,\n enableNetworkTracking: options?.enableNetworkTracking\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 enableConsoleTracking?: boolean; // Enable console warn/error tracking (default: true, set to false to opt-out)\n enableNetworkTracking?: boolean; // Enable network error tracking (default: true, set to false to opt-out)\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 const finalIngestionUrl = ingestionUrl || defaultIngestionUrl;\n this.api = new HumanBehaviorAPI({ \n apiKey: apiKey,\n ingestionUrl: finalIngestionUrl\n });\n this.apiKey = apiKey;\n this.ingestionUrl = finalIngestionUrl;\n \n // Initialize queue size (default 1000)\n this.MAX_QUEUE_SIZE = options?.maxQueueSize ?? 1000;\n \n // Store tracking configuration flags (default: enabled, opt-out by setting to false)\n this.enableConsoleTrackingFlag = options?.enableConsoleTracking !== false; // Default: true (opt-out)\n this.enableNetworkTrackingFlag = options?.enableNetworkTracking !== false; // Default: true (opt-out)\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 // ✅ CLIENT-SIDE ID GENERATION\n // Generate endUserId locally (no server dependency)\n if (isBrowser) {\n const endUserIdKey = `human_behavior_end_user_id`;\n const existingEndUserId = this.getCookie(endUserIdKey);\n this.endUserId = existingEndUserId || uuidv1();\n if (!existingEndUserId) {\n this.setCookie(endUserIdKey, this.endUserId, 365);\n logDebug(`Generated new endUserId: ${this.endUserId}`);\n } else {\n logDebug(`Reusing existing endUserId: ${this.endUserId}`);\n }\n } else {\n this.endUserId = uuidv1();\n }\n\n // ✅ CLIENT-SIDE SESSION MANAGEMENT\n // Generate sessionId with timeout checking\n if (isBrowser) {\n // Initialize windowId storage keys\n const persistenceName = this.apiKey || 'default';\n this._window_id_storage_key = `human_behavior_${persistenceName}_window_id`;\n this._primary_window_exists_storage_key = `human_behavior_${persistenceName}_primary_window_exists`;\n \n this.sessionId = this.getOrCreateSessionId();\n this.windowId = this.getOrCreateWindowId(); // Multi-window tracking\n this.currentUrl = window.location.href;\n (window as any).__humanBehaviorGlobalTracker = this;\n \n // Setup beforeunload listener to clear primary_window_exists flag\n this.setupWindowUnloadListener();\n } else {\n this._window_id_storage_key = '';\n this._primary_window_exists_storage_key = '';\n this.sessionId = uuidv1();\n this.windowId = uuidv1();\n }\n\n // ✅ SET TRACKING CONTEXT: Set session and user IDs for network error tracking\n this.api.setTrackingContext(this.sessionId, this.endUserId);\n\n // ✅ INITIALIZATION: Setup handlers immediately (no server call needed)\n this.initializationPromise = this.init().catch(error => {\n logError('Initialization failed:', error);\n });\n }\n\n private async init(): Promise<void> {\n try {\n // Setup handlers immediately\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 // Mark as initialized\n this.initialized = true;\n \n logInfo(`HumanBehaviorTracker initialized with sessionId: ${this.sessionId}, endUserId: ${this.endUserId}`);\n } catch (error: any) {\n // Handle initialization errors gracefully - don't throw\n logError('Failed to initialize HumanBehaviorTracker:', error);\n this.initialized = true; // Allow tracker to work locally even if init fails\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 // ✅ NON-BLOCKING: endUserId is always available (generated locally)\n // No need to wait for server initialization\n if (!this.endUserId) {\n // This should never happen, but fallback to anonymous if it does\n logWarn(`endUserId not available, using anonymous ID for event: ${eventName}`);\n this.endUserId = uuidv1();\n }\n\n // ✅ CHECK SESSION TIMEOUT before sending custom event (creates new session if expired)\n if (isBrowser) {\n this.checkAndRefreshSession();\n }\n\n // Get enhanced properties with automatic properties\n const enhancedProperties = this.propertyManager.getEventProperties(properties);\n\n // ✅ Check minimum duration - queue if below, send if above\n if (this.shouldSkipDueToMinimumDuration()) {\n logDebug(`Custom event '${eventName}' queued due to session duration below minimum`);\n this.pendingCustomEvents.push({\n eventName,\n properties: enhancedProperties,\n timestamp: Date.now()\n });\n return;\n }\n\n // ✅ Flush any pending custom events first (everything before 5 seconds)\n await this.flushPendingCustomEvents();\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 this.setupRageClickDetection();\n // TEMPORARILY DISABLED: Dead click detection (MutationObserver performance impact)\n // this.setupDeadClickDetection();\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 rage click detection\n * Detects when user clicks the same area 3+ times within 1 second (within 30px radius)\n * Similar to rage click detection\n */\n private setupRageClickDetection(): void {\n if (!isBrowser) return;\n\n document.addEventListener('click', async (event: MouseEvent) => {\n const target = event.target as HTMLElement;\n const x = event.clientX;\n const y = event.clientY;\n const timestamp = Date.now();\n \n // Check if this is a rage click\n if (this.isRageClick(x, y, timestamp, target)) {\n // Get element information\n const element = target.closest('button, a, [role=\"button\"], [role=\"link\"]') || target;\n \n const properties: Record<string, any> = {\n x: x,\n y: y,\n page: window.location.pathname,\n element: element.tagName.toLowerCase(),\n clickCount: this.RAGE_CLICK_CLICK_COUNT,\n timestamp: timestamp\n };\n\n // Add element details if available\n if (element.id) {\n properties.elementId = element.id;\n }\n if (element.className) {\n properties.elementClass = element.className;\n }\n if (element.textContent) {\n properties.elementText = element.textContent.trim().substring(0, 100);\n }\n\n // Remove null values\n Object.keys(properties).forEach(key => {\n if (properties[key] === null || properties[key] === undefined) {\n delete properties[key];\n }\n });\n await this.customEvent('$rageclick', properties);\n \n // Reset tracker after detecting rage click\n this.rageClickTracker.clicks = [];\n }\n });\n }\n\n /**\n * Check if a click is part of a rage click pattern\n * Returns true if 3+ clicks within 1 second and within 30px of each other\n */\n private isRageClick(x: number, y: number, timestamp: number, element: HTMLElement): boolean {\n const clicks = this.rageClickTracker.clicks;\n const lastClick = clicks[clicks.length - 1];\n \n if (\n lastClick &&\n Math.abs(x - lastClick.x) + Math.abs(y - lastClick.y) < this.RAGE_CLICK_THRESHOLD_PX &&\n timestamp - lastClick.timestamp < this.RAGE_CLICK_TIMEOUT_MS\n ) {\n // Continuation of previous clicks - add to array\n clicks.push({ x, y, timestamp, element });\n \n // Check if we've reached the threshold\n if (clicks.length >= this.RAGE_CLICK_CLICK_COUNT) {\n return true; // Rage click detected!\n }\n } else {\n // Not a continuation - reset and start new sequence\n this.rageClickTracker.clicks = [{ x, y, timestamp, element }];\n }\n \n return false;\n }\n\n /**\n * Setup dead click detection\n * Detects when user clicks on interactive elements that don't trigger any action\n * Observes entire document for mutations, accepts false negatives on animated pages\n */\n // TEMPORARILY DISABLED: Dead click detection (MutationObserver performance impact)\n /*\n private setupDeadClickDetection(): void {\n if (!isBrowser) return;\n\n // Set up global mutation observer (observes entire document)\n this.setupDeadClickMutationObserver();\n\n // Set up scroll listener\n this.setupDeadClickScrollObserver();\n\n // Set up selection change listener\n this.setupDeadClickSelectionObserver();\n\n // Hook into navigation tracking\n this.setupDeadClickNavigationTracking();\n\n // Set up click listener\n document.addEventListener('click', (event: MouseEvent) => {\n const target = event.target as HTMLElement;\n if (!target) return;\n\n // Only track clicks on interactive elements\n if (!this.isInteractiveElement(target)) {\n return;\n }\n\n // Check if this click should be ignored\n if (this.ignoreClickForDeadDetection(target)) {\n return;\n }\n\n // Create unique ID for this click\n const clickId = Date.now() + Math.random();\n const clickTimestamp = Date.now();\n const mutationTimeAtClick = this.deadClickTracker.lastMutationTime;\n\n // Set timer for this click\n const timeoutValue = this.DEAD_CLICK_ABSOLUTE_TIMEOUT_MS;\n const timer = window.setTimeout(() => {\n this.handleDeadClickTimeout(clickId);\n }, timeoutValue);\n\n // Store click data - capture mutation time at click to detect new mutations\n this.deadClickTracker.pendingClicks.set(clickId, {\n element: target,\n originalEvent: event,\n timestamp: clickTimestamp,\n timer: timer,\n cancelled: false,\n lastMutationTimeAtClick: mutationTimeAtClick\n });\n });\n }\n */\n\n // TEMPORARILY DISABLED: Dead click detection (MutationObserver performance impact)\n /*\n // Set up global MutationObserver to track ALL DOM changes\n // Observe entire document, just record timestamp of any mutation\n private setupDeadClickMutationObserver(): void {\n if (!isBrowser) return;\n\n // Only set up once\n if (this.deadClickTracker.mutationObserver) {\n return;\n }\n\n this.deadClickTracker.mutationObserver = new MutationObserver(() => {\n // We don't care about the content of mutations - just record that one happened\n const now = Date.now();\n this.deadClickTracker.lastMutationTime = now;\n \n // Immediately cancel any pending clicks that happened recently\n // This catches mutations from dropdowns/menus that open right after click\n // Also catch mutations that happened just before the click (within 50ms) - React timing issue\n this.deadClickTracker.pendingClicks.forEach((click, clickId) => {\n if (click.cancelled) return;\n \n const timeSinceClick = now - click.timestamp;\n const mutationTimeAtClick = click.lastMutationTimeAtClick || 0;\n const isNewMutation = now > mutationTimeAtClick;\n // Allow mutations within threshold OR mutations that happened just before click (React timing)\n const withinThreshold = timeSinceClick >= 0 && timeSinceClick < this.DEAD_CLICK_MUTATION_THRESHOLD_MS;\n const mutationJustBeforeClick = timeSinceClick < 0 && Math.abs(timeSinceClick) < 50; // Mutation within 50ms before click\n \n // If mutation happened within the mutation threshold OR just before click, cancel\n if ((withinThreshold && isNewMutation) || mutationJustBeforeClick) {\n this.cancelPendingClick(clickId);\n }\n });\n });\n\n // Observe entire document (accepts false negatives on animated pages)\n this.deadClickTracker.mutationObserver.observe(document, {\n attributes: true,\n characterData: true,\n childList: true,\n subtree: true\n });\n }\n */\n\n // TEMPORARILY DISABLED: Dead click detection (MutationObserver performance impact)\n /*\n // Set up scroll observer to track scroll events\n private setupDeadClickScrollObserver(): void {\n if (!isBrowser) return;\n\n window.addEventListener('scroll', () => {\n const now = Date.now();\n // Cancel clicks that happened within the scroll threshold\n this.deadClickTracker.pendingClicks.forEach((click, clickId) => {\n const timeSinceClick = now - click.timestamp;\n if (timeSinceClick < this.DEAD_CLICK_SCROLL_THRESHOLD_MS) {\n this.cancelPendingClick(clickId);\n }\n });\n }, { capture: true, passive: true });\n }\n */\n\n // TEMPORARILY DISABLED: Dead click detection (MutationObserver performance impact)\n /*\n // Set up selection change observer\n private setupDeadClickSelectionObserver(): void {\n if (!isBrowser) return;\n\n document.addEventListener('selectionchange', () => {\n const now = Date.now();\n this.deadClickTracker.lastSelectionChangedTime = now;\n // Cancel clicks that happened within the selection threshold\n this.deadClickTracker.pendingClicks.forEach((click, clickId) => {\n const timeSinceClick = now - click.timestamp;\n if (timeSinceClick < this.DEAD_CLICK_SELECTION_THRESHOLD_MS) {\n this.cancelPendingClick(clickId);\n }\n });\n });\n }\n */\n\n // TEMPORARILY DISABLED: Dead click detection (MutationObserver performance impact)\n /*\n // Hook into navigation tracking to detect page changes\n private setupDeadClickNavigationTracking(): void {\n if (!isBrowser) return;\n\n // Track current URL to detect changes\n let lastUrl = window.location.href;\n \n // Hook into existing trackNavigationEvent method\n // Navigation will cancel all pending clicks\n const originalTrackNavigationEvent = this.trackNavigationEvent.bind(this);\n this.trackNavigationEvent = async (type: string, fromUrl: string, toUrl: string) => {\n // Cancel all pending clicks when navigation happens\n this.cancelAllPendingClicks();\n // Update last URL\n lastUrl = window.location.href;\n // Call original method\n return originalTrackNavigationEvent(type, fromUrl, toUrl);\n };\n \n // Also monitor URL changes directly as a backup\n const checkUrlChange = () => {\n const currentUrl = window.location.href;\n if (currentUrl !== lastUrl) {\n this.cancelAllPendingClicks();\n lastUrl = currentUrl;\n }\n };\n \n // Listen to all navigation events\n window.addEventListener('popstate', checkUrlChange);\n window.addEventListener('hashchange', checkUrlChange);\n window.addEventListener('beforeunload', () => {\n this.cancelAllPendingClicks();\n });\n \n // Also check URL periodically (catches any navigation we might miss)\n setInterval(() => {\n checkUrlChange();\n }, 100);\n }\n */\n\n /**\n * Check if an element is interactive (should respond to clicks)\n */\n private isInteractiveElement(element: HTMLElement): boolean {\n const tagName = element.tagName.toLowerCase();\n\n // Buttons and links\n if (tagName === 'button' || tagName === 'a') {\n return true;\n }\n\n // Form elements\n if (['input', 'select', 'textarea'].includes(tagName)) {\n return true;\n }\n\n // Elements with interactive roles\n const role = element.getAttribute('role');\n if (role && ['button', 'link', 'tab', 'menuitem', 'checkbox', 'radio'].includes(role)) {\n return true;\n }\n\n // Elements with click handlers\n if ((element as any).onclick || element.getAttribute('onclick')) {\n return true;\n }\n\n // Elements with cursor pointer (often indicates clickable)\n try {\n const style = window.getComputedStyle(element);\n if (style.cursor === 'pointer') {\n return true;\n }\n } catch (e) {\n // Ignore errors\n }\n\n // Check if parent is an interactive element (click might be on child like span inside button)\n const interactiveParent = element.closest('button, a, [role=\"button\"], [role=\"link\"], [role=\"tab\"], [role=\"menuitem\"]');\n if (interactiveParent) {\n return true;\n }\n\n return false;\n }\n\n // TEMPORARILY DISABLED: Dead click detection (MutationObserver performance impact)\n /*\n // Check if a click should be ignored for dead click detection\n private ignoreClickForDeadDetection(element: HTMLElement): boolean {\n // Ignore clicks on html tag\n if (element.tagName.toLowerCase() === 'html') {\n return true;\n }\n\n // Ignore if same element was clicked within last second (avoid duplicates)\n const now = Date.now();\n for (const click of this.deadClickTracker.pendingClicks.values()) {\n if (click.element === element && Math.abs(now - click.timestamp) < 1000) {\n return true;\n }\n }\n\n return false;\n }\n\n // Cancel a pending click (action happened, so it's not dead)\n private cancelPendingClick(clickId: number): void {\n const click = this.deadClickTracker.pendingClicks.get(clickId);\n if (click && !click.cancelled) {\n clearTimeout(click.timer);\n click.cancelled = true;\n this.deadClickTracker.pendingClicks.delete(clickId);\n }\n }\n\n // Cancel all pending clicks (e.g., on navigation)\n private cancelAllPendingClicks(): void {\n this.deadClickTracker.pendingClicks.forEach((click, clickId) => {\n if (!click.cancelled) {\n clearTimeout(click.timer);\n this.deadClickTracker.pendingClicks.delete(clickId);\n }\n });\n }\n\n // Handle when a dead click timer expires\n private async handleDeadClickTimeout(clickId: number): Promise<void> {\n const click = this.deadClickTracker.pendingClicks.get(clickId);\n if (!click || click.cancelled) {\n return;\n }\n\n const now = Date.now();\n const absoluteDelayMs = now - click.timestamp;\n\n // Check if any action happened after the minimum timeout\n // Keep only selection and navigation checks (mutation removed)\n let hadAction = false;\n let reason: string | null = null;\n\n // Calculate delays\n // Check if a NEW mutation happened after the click OR just before (React timing issue)\n let mutationDelayMs: number | undefined;\n const mutationTimeAtClick = click.lastMutationTimeAtClick || 0;\n const currentMutationTime = this.deadClickTracker.lastMutationTime || 0;\n \n // Count mutations that happened AFTER the click OR just before (within 50ms - React timing)\n const mutationAfterClick = currentMutationTime > mutationTimeAtClick && currentMutationTime >= click.timestamp;\n const mutationJustBeforeClick = mutationTimeAtClick > 0 && \n mutationTimeAtClick < click.timestamp && \n (click.timestamp - mutationTimeAtClick) < 50; // Mutation within 50ms before click\n \n if (mutationAfterClick) {\n mutationDelayMs = currentMutationTime - click.timestamp;\n } else if (mutationJustBeforeClick) {\n // Treat mutation just before click as if it happened at click time (0ms delay)\n mutationDelayMs = 0;\n }\n\n let selectionChangedDelayMs: number | undefined;\n if (this.deadClickTracker.lastSelectionChangedTime && click.timestamp <= this.deadClickTracker.lastSelectionChangedTime) {\n selectionChangedDelayMs = this.deadClickTracker.lastSelectionChangedTime - click.timestamp;\n }\n\n // Check if any action happened within thresholds\n const hadScroll = false; // Scroll is handled by immediate cancellation\n const hadMutation = mutationDelayMs !== undefined && mutationDelayMs < this.DEAD_CLICK_MUTATION_THRESHOLD_MS;\n const hadSelectionChange = selectionChangedDelayMs !== undefined && selectionChangedDelayMs < this.DEAD_CLICK_SELECTION_THRESHOLD_MS;\n\n if (hadScroll || hadMutation || hadSelectionChange) {\n this.deadClickTracker.pendingClicks.delete(clickId);\n return;\n }\n\n // No action happened - it's a dead click\n await this.fireDeadClickEvent(click, absoluteDelayMs, mutationDelayMs, selectionChangedDelayMs);\n\n // Remove from pending clicks\n this.deadClickTracker.pendingClicks.delete(clickId);\n }\n\n // Fire dead click event\n private async fireDeadClickEvent(\n click: {\n element: HTMLElement;\n originalEvent: MouseEvent;\n timestamp: number;\n },\n absoluteDelayMs: number,\n mutationDelayMs?: number,\n selectionChangedDelayMs?: number\n ): Promise<void> {\n const element = click.element.closest('button, a, [role=\"button\"], [role=\"link\"]') || click.element;\n\n const properties: Record<string, any> = {\n x: click.originalEvent.clientX,\n y: click.originalEvent.clientY,\n page: window.location.pathname,\n element: element.tagName.toLowerCase(),\n absoluteDelayMs: absoluteDelayMs,\n timestamp: click.timestamp\n };\n\n // Add delay information\n if (mutationDelayMs !== undefined) {\n properties.mutationDelayMs = mutationDelayMs;\n }\n if (selectionChangedDelayMs !== undefined) {\n properties.selectionChangedDelayMs = selectionChangedDelayMs;\n }\n\n // Add element details if available\n if (element.id) {\n properties.elementId = element.id;\n }\n if (element.className) {\n properties.elementClass = element.className;\n }\n if (element.textContent) {\n properties.elementText = element.textContent.trim().substring(0, 100);\n }\n\n // Remove null/undefined values\n Object.keys(properties).forEach(key => {\n if (properties[key] === null || properties[key] === undefined) {\n delete properties[key];\n }\n });\n\n // Send dead click custom event\n await this.customEvent('$deadclick', properties);\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 * Enable network error tracking by intercepting the global fetch API\n */\n public enableNetworkTracking(): void {\n if (!isBrowser || this.networkTrackingEnabled || typeof fetch === 'undefined') return;\n \n // Store original fetch\n this.originalFetch = window.fetch.bind(window);\n \n // Override global fetch to track network errors\n window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const requestStartTime = Date.now();\n const requestId = uuidv1();\n const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;\n const method = (init?.method || (typeof input === 'object' && 'method' in input ? input.method : undefined) || 'GET').toUpperCase();\n \n // Check if we should skip tracking (SDK's own requests)\n const shouldSkipTracking = this.shouldSkipNetworkTracking(url);\n \n // Track long-loading requests (>10 seconds)\n const LONG_LOADING_THRESHOLD_MS = 10000; // 10 seconds\n let longLoadingTimeoutId: ReturnType<typeof setTimeout> | null = null;\n let longLoadingTracked = false;\n \n // Set up timeout to track long-loading requests\n if (!shouldSkipTracking) {\n longLoadingTimeoutId = setTimeout(() => {\n const elapsedTime = Date.now() - requestStartTime;\n if (!longLoadingTracked) {\n longLoadingTracked = true;\n const errorData = {\n requestId,\n url,\n method,\n status: null, // Request still in progress\n statusText: null,\n duration: elapsedTime,\n timestampMs: Date.now(),\n sessionId: this.sessionId,\n endUserId: this.endUserId,\n errorType: 'long_loading',\n errorMessage: `Request took longer than ${LONG_LOADING_THRESHOLD_MS}ms (${elapsedTime}ms elapsed)`,\n // New span fields\n startTimeMs: requestStartTime,\n spanName: `${method} ${url}`,\n spanStatus: 'slow' as const,\n attributes: {\n 'http.method': method,\n 'http.url': url,\n 'request.duration_ms': elapsedTime,\n 'request.long_loading_threshold_ms': LONG_LOADING_THRESHOLD_MS,\n }\n };\n // ✅ Check minimum duration - queue if below, send if above\n if (this.shouldSkipDueToMinimumDuration()) {\n logDebug('Long-loading network error queued due to session duration below minimum');\n this.pendingNetworkErrors.push({\n errorData,\n timestamp: Date.now()\n });\n return;\n }\n // ✅ Flush any pending network errors first (everything before 5 seconds)\n this.flushPendingNetworkErrors();\n this.api.sendNetworkError(errorData).catch(() => {}); // Non-blocking\n return;\n }\n }, LONG_LOADING_THRESHOLD_MS);\n }\n \n try {\n const response = await this.originalFetch!(input, init);\n const requestDuration = Date.now() - requestStartTime;\n \n // Clear long-loading timeout if request completed\n if (longLoadingTimeoutId) {\n clearTimeout(longLoadingTimeoutId);\n }\n \n // Track failed requests (4xx, 5xx) AND skip SDK requests\n if (!response.ok && !shouldSkipTracking) {\n const errorData = {\n requestId,\n url,\n method,\n status: response.status,\n statusText: response.statusText,\n duration: requestDuration,\n timestampMs: Date.now(),\n sessionId: this.sessionId,\n endUserId: this.endUserId,\n errorType: this.classifyHttpError(response.status),\n errorMessage: response.statusText,\n // New span fields\n startTimeMs: requestStartTime,\n spanName: `${method} ${url}`,\n spanStatus: 'error' as const,\n attributes: {\n 'http.status_code': response.status,\n 'http.status_text': response.statusText,\n }\n };\n // ✅ Check minimum duration - queue if below, send if above\n if (this.shouldSkipDueToMinimumDuration()) {\n logDebug('Failed request network error queued due to session duration below minimum');\n this.pendingNetworkErrors.push({\n errorData,\n timestamp: Date.now()\n });\n return response;\n }\n // ✅ Flush any pending network errors first (everything before 5 seconds)\n this.flushPendingNetworkErrors();\n this.api.sendNetworkError(errorData).catch(() => {}); // Non-blocking\n }\n \n return response;\n } catch (error: any) {\n const requestDuration = Date.now() - requestStartTime;\n \n // Clear long-loading timeout if request failed\n if (longLoadingTimeoutId) {\n clearTimeout(longLoadingTimeoutId);\n }\n \n // Track network errors BUT skip SDK requests\n if (!shouldSkipTracking) {\n const errorData = {\n requestId,\n url,\n method,\n status: null,\n statusText: null,\n duration: requestDuration,\n timestampMs: Date.now(),\n sessionId: this.sessionId,\n endUserId: this.endUserId,\n errorType: this.classifyNetworkError(error),\n errorMessage: error.message,\n errorName: error.name,\n // New span fields\n startTimeMs: requestStartTime,\n spanName: `${method} ${url}`,\n spanStatus: 'error' as const,\n attributes: {\n 'error.name': error.name,\n 'error.message': error.message,\n }\n };\n // ✅ Check minimum duration - queue if below, send if above\n if (this.shouldSkipDueToMinimumDuration()) {\n logDebug('Network error queued due to session duration below minimum');\n this.pendingNetworkErrors.push({\n errorData,\n timestamp: Date.now()\n });\n throw error; // Re-throw to maintain error propagation\n }\n // ✅ Flush any pending network errors first (everything before 5 seconds)\n this.flushPendingNetworkErrors();\n this.api.sendNetworkError(errorData).catch(() => {}); // Non-blocking\n }\n \n throw error;\n }\n };\n \n this.networkTrackingEnabled = true;\n logDebug('Network tracking enabled');\n }\n\n /**\n * Flush pending custom events (queued before 5 seconds)\n */\n private async flushPendingCustomEvents(): Promise<void> {\n if (this.pendingCustomEvents.length === 0) {\n return;\n }\n\n const eventsToFlush = [...this.pendingCustomEvents];\n this.pendingCustomEvents = [];\n\n logDebug(`Flushing ${eventsToFlush.length} pending custom events`);\n\n for (const { eventName, properties } of eventsToFlush) {\n try {\n await this.api.sendCustomEvent(this.sessionId, eventName, properties, this.endUserId);\n } catch (error) {\n logError('Failed to flush pending custom event:', error);\n }\n }\n }\n\n /**\n * Flush pending logs (queued before 5 seconds)\n */\n private async flushPendingLogs(): Promise<void> {\n if (this.pendingLogs.length === 0) {\n return;\n }\n\n const logsToFlush = [...this.pendingLogs];\n this.pendingLogs = [];\n\n logDebug(`Flushing ${logsToFlush.length} pending logs`);\n\n for (const { logData } of logsToFlush) {\n try {\n await this.api.sendLog(logData);\n } catch (error) {\n logError('Failed to flush pending log:', error);\n }\n }\n }\n\n /**\n * Flush pending network errors (queued before 5 seconds)\n */\n private async flushPendingNetworkErrors(): Promise<void> {\n if (this.pendingNetworkErrors.length === 0) {\n return;\n }\n\n const errorsToFlush = [...this.pendingNetworkErrors];\n this.pendingNetworkErrors = [];\n\n logDebug(`Flushing ${errorsToFlush.length} pending network errors`);\n\n for (const { errorData } of errorsToFlush) {\n try {\n await this.api.sendNetworkError(errorData);\n } catch (error) {\n logError('Failed to flush pending network error:', error);\n }\n }\n }\n\n /**\n * Enable page load tracking - detects heavy page loads (>3 seconds)\n */\n public enablePageLoadTracking(): void {\n if (!isBrowser || typeof window === 'undefined') return;\n \n // Track initial page load\n if (document.readyState === 'complete') {\n // Page already loaded, check immediately\n this.trackPageLoad();\n } else {\n // Wait for page load\n window.addEventListener('load', () => {\n this.trackPageLoad();\n });\n }\n \n logDebug('Page load tracking enabled');\n }\n\n /**\n * Track heavy page loads using Performance API\n */\n private trackPageLoad(): void {\n if (!isBrowser || typeof performance === 'undefined') return;\n \n try {\n const perfEntry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;\n if (!perfEntry) return;\n \n const loadDuration = perfEntry.loadEventEnd - perfEntry.fetchStart;\n const HEAVY_LOAD_THRESHOLD_MS = 3000; // 3 seconds\n \n // Only track if heavy (>3 seconds)\n if (loadDuration > HEAVY_LOAD_THRESHOLD_MS) {\n const requestId = uuidv1();\n const domContentLoaded = perfEntry.domContentLoadedEventEnd - perfEntry.fetchStart;\n const domComplete = perfEntry.domComplete - perfEntry.fetchStart;\n \n const errorData = {\n requestId,\n url: window.location.href,\n method: 'GET',\n status: 200, // Page loads are typically successful\n statusText: 'OK',\n duration: loadDuration,\n timestampMs: perfEntry.loadEventEnd + performance.timeOrigin,\n sessionId: this.sessionId,\n endUserId: this.endUserId,\n errorType: 'slow_page_load',\n errorMessage: `Page load took ${loadDuration}ms`,\n // New span fields\n startTimeMs: perfEntry.fetchStart + performance.timeOrigin,\n spanName: 'page_load',\n spanStatus: 'slow' as const,\n attributes: {\n 'page.url': window.location.href,\n 'page.load_time': loadDuration,\n 'page.dom_content_loaded': domContentLoaded,\n 'page.dom_complete': domComplete,\n }\n };\n // ✅ Check minimum duration - queue if below, send if above\n if (this.shouldSkipDueToMinimumDuration()) {\n logDebug('Slow page load network error queued due to session duration below minimum');\n this.pendingNetworkErrors.push({\n errorData,\n timestamp: Date.now()\n });\n return;\n }\n // ✅ Flush any pending network errors first (everything before 5 seconds)\n this.flushPendingNetworkErrors();\n this.api.sendNetworkError(errorData).catch(() => {}); // Non-blocking\n }\n } catch (error) {\n logWarn('Failed to track page load:', error);\n }\n }\n\n /**\n * Check if network request should be skipped (SDK's own requests)\n */\n private shouldSkipNetworkTracking(url: string): boolean {\n if (!url || !this.ingestionUrl) {\n return false;\n }\n \n try {\n const urlObj = new URL(url);\n const baseUrlObj = new URL(this.ingestionUrl);\n \n // Skip if same origin (same protocol, host, port)\n if (urlObj.origin === baseUrlObj.origin) {\n // Also check if it's an ingestion endpoint\n if (urlObj.pathname.startsWith('/api/ingestion/')) {\n return true;\n }\n }\n \n // Also check string matching as fallback\n if (url.includes(this.ingestionUrl)) {\n return true;\n }\n \n return false;\n } catch (error) {\n // If URL parsing fails, do simple string check\n return url.includes(this.ingestionUrl);\n }\n }\n\n /**\n * Classify HTTP error status codes\n */\n private classifyHttpError(status: number): string {\n if (status >= 400 && status < 500) {\n return 'client_error';\n }\n if (status >= 500) {\n return 'server_error';\n }\n return 'unknown_error';\n }\n\n /**\n * Classify network errors (CORS, timeouts, blocked requests, etc.)\n */\n private classifyNetworkError(error: any): string {\n const errorMessage = error.message || '';\n const errorName = error.name || '';\n \n // Check for blocked requests (ad blockers, browser extensions, etc.)\n if (\n errorMessage.includes('blocked') ||\n errorMessage.includes('ERR_BLOCKED_BY_CLIENT') ||\n errorMessage.includes('net::ERR_BLOCKED_BY_CLIENT') ||\n errorName === 'TypeError' && errorMessage.includes('Failed to fetch')\n ) {\n return 'blocked_by_client';\n }\n \n // Check for CORS errors\n if (\n errorMessage.includes('CORS') ||\n errorMessage.includes('Cross-Origin') ||\n errorMessage.includes('Access-Control-Allow-Origin') ||\n errorName === 'TypeError' && errorMessage.includes('CORS')\n ) {\n return 'cors_error';\n }\n \n // Check for network/timeout errors\n if (\n errorMessage.includes('timeout') ||\n errorMessage.includes('TIMEOUT') ||\n errorMessage.includes('NetworkError') ||\n errorName === 'NetworkError'\n ) {\n return 'network_error';\n }\n \n // Check for abort errors\n if (\n errorMessage.includes('abort') ||\n errorName === 'AbortError'\n ) {\n return 'aborted';\n }\n \n return 'unknown_error';\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) {\n return;\n }\n\n // Only track warn and error, skip log\n if (level === 'log') {\n // Just call original console.log, don't track\n if (this.originalConsole) {\n this.originalConsole.log(...args);\n }\n return;\n }\n\n try {\n // ✅ SKIP TRACKING: If SDK logger is currently active, don't track\n if (isSDKLogging()) {\n if (this.originalConsole) {\n this.originalConsole[level](...args);\n }\n return;\n }\n\n // ✅ SKIP TRACKING: Check if log originates from SDK code\n const stack = new Error().stack || '';\n if (this.isSDKStackFrame(stack)) {\n // This log came from SDK code, don't track it\n if (this.originalConsole) {\n this.originalConsole[level](...args);\n }\n return;\n }\n\n const consoleData = {\n level: level, // 'warn' or 'error'\n message: args.map(arg => \n typeof arg === 'object' ? JSON.stringify(arg) : String(arg)\n ).join(' '),\n timestampMs: Date.now(),\n url: isBrowser ? window.location.href : '',\n userAgent: isBrowser ? navigator.userAgent : '',\n stack: stack,\n sessionId: this.sessionId,\n endUserId: this.endUserId\n };\n\n // ✅ Check minimum duration - queue if below, send if above\n if (this.shouldSkipDueToMinimumDuration()) {\n logDebug(`Console ${level} queued due to session duration below minimum`);\n this.pendingLogs.push({\n logData: consoleData,\n timestamp: Date.now()\n });\n return;\n }\n\n // ✅ Flush any pending logs first (everything before 5 seconds)\n this.flushPendingLogs();\n\n // Send to dedicated endpoint for ClickHouse\n this.api.sendLog(consoleData).catch(err => {\n // Fallback to event stream if dedicated endpoint fails\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(() => {}); // Silent fail\n });\n } catch (error) {\n logError('Error in trackConsoleEvent:', error);\n }\n }\n\n /**\n * Check if the actual caller (not SDK wrapper) is from SDK code\n * Since we intercept console methods, the stack will always include SDK frames.\n * We need to look at the actual caller frame (the one that called console.error/warn from user code).\n */\n private isSDKStackFrame(stack: string): boolean {\n if (!stack) return false;\n \n // SDK file path patterns to check for\n const sdkPatterns = [\n 'humanbehavior-js',\n '@humanbehavior/core',\n '@humanbehavior/browser',\n 'tracker.ts',\n 'api.ts',\n 'logger.ts',\n 'utils/logger',\n 'packages/core',\n 'packages/browser',\n 'index.mjs', // Built SDK bundle\n 'index.js' // Built SDK bundle\n ];\n \n // Parse stack into lines\n const stackLines = stack.split('\\n');\n const stackLower = stack.toLowerCase();\n \n // If the ENTIRE stack only contains SDK patterns, it's an SDK-originated log\n // Otherwise, if there's ANY non-SDK frame, it's user code\n \n // Check if ALL frames (excluding the first \"Error\" line) are SDK frames\n let foundNonSDKFrame = false;\n \n for (let i = 0; i < stackLines.length; i++) {\n const line = stackLines[i].trim().toLowerCase();\n \n // Skip empty lines and the first \"Error:\" line\n if (!line || line === 'error' || line.startsWith('error:')) {\n continue;\n }\n \n // Check if this line contains SDK patterns\n const isSDKFrame = sdkPatterns.some(pattern => line.includes(pattern.toLowerCase()));\n \n if (!isSDKFrame) {\n // Found a non-SDK frame - this is user code calling console.error/warn\n foundNonSDKFrame = true;\n break;\n }\n }\n \n // If we found a non-SDK frame, it's user code - don't skip\n // If all frames are SDK frames, skip tracking\n return !foundNonSDKFrame;\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 if (document.visibilityState === 'hidden') {\n logDebug('Page hidden - sending pending events');\n // Flush unified event queue\n this.flushEvents();\n } else if (document.visibilityState === 'visible') {\n logDebug('Page visible - taking full snapshot for multi-window replay');\n this.takeFullSnapshot();\n }\n });\n\n // Use pagehide if available (more reliable than beforeunload)\n // pagehide fires in more cases (navigation, tab close, etc.)\n const unloadEvent = 'onpagehide' in window ? 'pagehide' : 'beforeunload';\n \n window.addEventListener(unloadEvent, () => {\n // ✅ SYNCHRONOUS UNLOAD HANDLER\n // Prepare data synchronously (no await) and send via sendBeacon\n logDebug('Page unloading - sending final events via sendBeacon');\n \n // ✅ Check minimum duration before sending on unload (default: 5 seconds)\n const minimumDuration = this.minimumDurationMilliseconds;\n const sessionDuration = this.getSessionDuration();\n const isPositiveSessionDuration = sessionDuration !== null && sessionDuration >= 0;\n const isBelowMinimumDuration = \n isPositiveSessionDuration && \n sessionDuration < minimumDuration;\n \n if (isBelowMinimumDuration) {\n // Don't send - buffer stays\n logDebug(`Session duration (${sessionDuration}ms) below minimum (${minimumDuration}ms), not sending on unload`);\n return;\n }\n \n // 1. Prepare events synchronously (copy queue immediately)\n const eventsToSend = [...this.eventQueue];\n \n // 2. Include pending snapshots if available (for very short sessions)\n // This handles cases where a flush is in progress but hasn't completed\n if (isBrowser && (window as any).__hb_pending_snapshots) {\n const pendingSnapshots = (window as any).__hb_pending_snapshots;\n if (Array.isArray(pendingSnapshots) && pendingSnapshots.length > 0) {\n logDebug('Including pending FullSnapshot(s) in sendBeacon for short session');\n eventsToSend.unshift(...pendingSnapshots); // Add at beginning so snapshot comes first\n delete (window as any).__hb_pending_snapshots;\n }\n }\n \n // 3. Send via sendBeacon synchronously\n if (eventsToSend.length > 0 && this.api) {\n try {\n // Get automatic properties synchronously\n const automaticProperties = this.propertyManager.getAutomaticProperties();\n \n // Send via sendBeacon (synchronous API - completes before page closes)\n this.api.sendBeaconEvents(\n eventsToSend, \n this.sessionId, \n this.endUserId || undefined,\n this.windowId,\n automaticProperties\n );\n \n // Clear queue after sending\n this.eventQueue = [];\n } catch (error) {\n // sendBeacon is best-effort, log but don't throw\n logWarn('Failed to send events via sendBeacon on unload:', error);\n }\n }\n \n // 4. Also handle retry queue (for any failed requests)\n if (this.api) {\n this.api.unload();\n }\n });\n\n // Update activity timestamp on user interaction (not on page load)\n const updateActivity = () => {\n localStorage.setItem(`human_behavior_last_activity`, 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 // ✅ NON-BLOCKING: Don't wait for init, endUserId is already available locally\n // await this.ensureInitialized(); // Removed - no longer needed\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 user name\n const userResponse = await this.api.sendUserData(\n originalEndUserId || '',\n userProperties,\n this.sessionId\n );\n\n // If server found a preexisting user, persist the canonical ID for future sessions\n // but keep using the original anon ID for the current session\n if (userResponse.actualUserId || userResponse.wasExistingUser) {\n const canonicalEndUserId = userResponse.actualUserId || originalEndUserId;\n if (canonicalEndUserId && canonicalEndUserId !== originalEndUserId) {\n // Persist canonical ID to cookie/localStorage for future sessions\n const cookieName = `human_behavior_end_user_id`;\n this.setCookie(cookieName, canonicalEndUserId, 365);\n // Explicitly set localStorage as well to ensure it's persisted\n if (isBrowser) {\n try {\n localStorage.setItem(cookieName, canonicalEndUserId);\n } catch (error) {\n logDebug('Failed to set canonical endUserId in localStorage:', error);\n }\n }\n logDebug(`🔗 Preexisting user detected. Future sessions will use canonical ID: ${canonicalEndUserId} (current session stays: ${originalEndUserId})`);\n }\n }\n\n // Keep original endUserId for the entire session (no mid-session switch)\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 // ✅ NON-BLOCKING: Start immediately, don't wait for init\n // Init continues in background but doesn't block recording\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 // ✅ Initialize idle detection\n // Sync with session activity timestamp if it exists (from session creation)\n // Otherwise use current time\n this._lastActivityTimestamp = this._sessionActivityTimestamp !== null \n ? this._sessionActivityTimestamp \n : Date.now();\n this._isIdle = 'unknown'; // Start as unknown until first interaction\n\n // Start periodic flushing (unified queue)\n this.flushInterval = window.setInterval(() => {\n this.flushEvents();\n }, this.FLUSH_INTERVAL_MS);\n\n // ✅ Enable console tracking for warn/error logging (if enabled)\n if (this.enableConsoleTrackingFlag) {\n this.enableConsoleTracking();\n }\n \n // ✅ Enable network error tracking (if enabled)\n if (this.enableNetworkTrackingFlag) {\n this.enableNetworkTracking();\n }\n \n // Enable page load tracking\n this.enablePageLoadTracking();\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 // Wait for any pending animations/transitions to complete\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n try {\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 (delayed for animations)');\n } else {\n logWarn('⚠️ takeFullSnapshot not available on record function');\n }\n } catch (error) {\n logError('❌ Failed to take FullSnapshot:', error);\n }\n });\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 // TEMPORARILY DISABLED: Dead click detection (MutationObserver performance impact)\n // Cleanup dead click tracking\n // if (this.deadClickTracker.mutationObserver) {\n // this.deadClickTracker.mutationObserver.disconnect();\n // this.deadClickTracker.mutationObserver = undefined;\n // }\n // // Cancel all pending clicks\n // this.deadClickTracker.pendingClicks.forEach((click, clickId) => {\n // if (!click.cancelled) {\n // clearTimeout(click.timer);\n // }\n // });\n // this.deadClickTracker.pendingClicks.clear();\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 // ✅ NON-BLOCKING: Events work immediately, no waiting for init\n // endUserId and sessionId are already available locally\n \n // ✅ CHECK SESSION TIMEOUT before adding event (creates new session if expired)\n if (isBrowser) {\n this.checkAndRefreshSession();\n }\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 * Calculate session duration\n * Uses most recent event timestamp minus session start timestamp\n */\n private getSessionDuration(): number | null {\n // Use session start timestamp if available, otherwise fall back to sessionStartTime\n const sessionStart = this._sessionStartTimestamp ?? this.sessionStartTime;\n if (!sessionStart) {\n return null;\n }\n \n // Get most recent event timestamp from queue\n const eventsWithTimestamps = this.eventQueue.filter((e: any) => e && e.timestamp);\n if (eventsWithTimestamps.length === 0) {\n return null;\n }\n \n const mostRecentEvent = eventsWithTimestamps.reduce((latest: any, current: any) => {\n return (!latest || (current.timestamp && current.timestamp > latest.timestamp)) ? current : latest;\n }, null);\n \n if (!mostRecentEvent || !mostRecentEvent.timestamp) {\n return null;\n }\n \n // Calculate duration (both timestamps should be in milliseconds)\n const duration = mostRecentEvent.timestamp - sessionStart;\n return duration >= 0 ? duration : null;\n }\n\n /**\n * Check if session duration is below minimum threshold\n * Returns true if we should skip sending (session too short)\n */\n private shouldSkipDueToMinimumDuration(): boolean {\n const minimumDuration = this.minimumDurationMilliseconds;\n const sessionDuration = this.getSessionDuration();\n const isPositiveSessionDuration = sessionDuration !== null && sessionDuration >= 0;\n const isBelowMinimumDuration =\n isPositiveSessionDuration &&\n sessionDuration < minimumDuration;\n\n if (isBelowMinimumDuration) {\n logDebug(`Session duration (${sessionDuration}ms) below minimum (${minimumDuration}ms), skipping send`);\n return true;\n }\n\n return false;\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 // ✅ NON-BLOCKING: Don't check initialized - events work immediately\n if (this.isProcessing) {\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 // ✅ IDLE STATE: Don't flush when idle and queue is empty (events are being skipped anyway)\n // But allow flush if we have events queued (e.g., from before going idle or FullSnapshots)\n if (this._isIdle === true && this.eventQueue.length === 0) {\n return;\n }\n \n // ✅ Check minimum duration before flushing (default: 5 seconds)\n const minimumDuration = this.minimumDurationMilliseconds;\n const sessionDuration = this.getSessionDuration();\n const isPositiveSessionDuration = sessionDuration !== null && sessionDuration >= 0;\n const isBelowMinimumDuration = \n isPositiveSessionDuration && \n sessionDuration < minimumDuration;\n \n if (isBelowMinimumDuration) {\n // Don't flush - schedule retry\n logDebug(`Session duration (${sessionDuration}ms) below minimum (${minimumDuration}ms), buffering`);\n // Schedule retry after buffer timeout (2 seconds)\n setTimeout(() => {\n this.flushEvents();\n }, 2000);\n return;\n }\n\n this.isProcessing = true;\n try {\n // ✅ CRITICAL FIX FOR SHORT SESSIONS:\n // Swap the current queue with an empty one atomically\n // BUT: Store a reference to FullSnapshots so they can be re-sent on unload if needed\n const eventsToProcess = this.eventQueue;\n const fullSnapshotsInFlush = eventsToProcess.filter(e => e && e.type === 2);\n this.eventQueue = [];\n \n // Store FullSnapshots that are being flushed so they can be sent via sendBeacon on unload\n // if the HTTP request doesn't complete in time (for very short sessions)\n if (fullSnapshotsInFlush.length > 0 && isBrowser) {\n // Store in a way that's accessible during unload\n (window as any).__hb_pending_snapshots = fullSnapshotsInFlush;\n // Clear after a delay (long enough for HTTP to complete, short enough to not waste memory)\n setTimeout(() => {\n delete (window as any).__hb_pending_snapshots;\n }, 5000); // 5 seconds should be enough for HTTP to complete\n }\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 // ✅ Include all IDs in payload (endUserId, sessionId, windowId)\n // ✅ Include automatic properties for user creation on first event\n // Server will create user/session on first event if needed\n const automaticProperties = this.propertyManager.getAutomaticProperties();\n await this.api.sendEventsChunked(\n eventsToProcess, \n this.sessionId, \n this.endUserId!,\n this.windowId,\n automaticProperties\n );\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\n // ✅ Once we hit 5 seconds, flush all pending events (custom events, logs, network errors)\n // This ensures everything that happened before 5 seconds gets sent\n // Flush AFTER eventQueue to prioritize session recording events\n await this.flushPendingCustomEvents();\n await this.flushPendingLogs();\n await this.flushPendingNetworkErrors();\n } finally {\n this.isProcessing = false;\n }\n }\n\n /**\n * Check if an event represents user interaction (not background DOM mutations)\n */\n private isInteractiveEvent(event: any): boolean {\n // Event type 3 = IncrementalSnapshot\n if (event.type !== 3) {\n return false;\n }\n \n // Active sources that indicate user interaction\n // Source values from rrweb: 0=DomContentLoaded, 1=MouseMove, 2=MouseInteraction, 3=Scroll, \n // 4=ViewportResize, 5=Input, 6=MediaInteraction, 7=StyleSheetRule, 8=CanvasMutation, \n // 9=Font, 10=Log, 11=Drag, 12=StyleDeclaration, 13=Selection, 14=AdoptedStyleSheet, 15=Mutation\n const ACTIVE_SOURCES = [1, 2, 3, 4, 5, 6, 11]; // MouseMove, MouseInteraction, Scroll, ViewportResize, Input, MediaInteraction, Drag\n \n const source = event.data?.source;\n return ACTIVE_SOURCES.includes(source);\n }\n \n /**\n * Update idle state based on event activity\n * Also updates session activity timestamp to prevent premature session expiration\n */\n private updateIdleState(event: any): void {\n const isUserInteraction = this.isInteractiveEvent(event);\n const currentTime = event.timestamp || Date.now();\n \n // Update activity timestamp on user interaction\n if (isUserInteraction) {\n const wasIdle = this._isIdle === true;\n this._lastActivityTimestamp = currentTime;\n \n // ✅ CRITICAL: Also update session activity timestamp to prevent session expiration\n // This ensures the 15-minute session timeout is extended on user interaction\n // (checkAndRefreshSession will handle persistence, but we update memory here)\n if (this._sessionActivityTimestamp !== null) {\n this._sessionActivityTimestamp = currentTime;\n }\n \n // If we were idle and user interacts, exit idle state\n if (wasIdle) {\n logDebug('✅ User activity detected, exiting idle state');\n this._isIdle = false;\n \n // Take full snapshot when returning from idle to capture current state\n if (this.rrwebRecord && typeof this.rrwebRecord.takeFullSnapshot === 'function') {\n this.rrwebRecord.takeFullSnapshot();\n logDebug('✅ FullSnapshot taken after returning from idle');\n }\n } else if (this._isIdle === 'unknown') {\n // First interaction, mark as active\n this._isIdle = false;\n }\n } else if (this._isIdle !== true) {\n // Check if we should go idle (no user interaction for threshold time)\n // Note: This uses 5-minute threshold for idle detection (stops recording)\n // Session timeout uses 15-minute threshold (ends session completely)\n const timeSinceLastActivity = currentTime - this._lastActivityTimestamp;\n if (timeSinceLastActivity > this.IDLE_THRESHOLD_MS) {\n logDebug(`⏸️ Session idle detected (${Math.round(timeSinceLastActivity / 1000)}s since last activity) - stopping background event recording`);\n logDebug(`ℹ️ Session will expire after 15 minutes of inactivity (${Math.round((15 * 60 * 1000 - timeSinceLastActivity) / 1000)}s remaining)`);\n this._isIdle = true;\n \n // Flush buffer when going idle to save what we have\n this.flushEvents();\n }\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 // ✅ NON-BLOCKING: Recording events work immediately\n // endUserId and sessionId are already available locally\n \n // ✅ CHECK SESSION TIMEOUT before adding event (creates new session if expired)\n if (isBrowser) {\n this.checkAndRefreshSession();\n }\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 // ✅ IDLE DETECTION: Update idle state based on event\n this.updateIdleState(event);\n \n // ✅ IDLE STATE: Skip non-interactive events when idle (save bandwidth)\n // Always record FullSnapshots and user interactions, but skip background mutations\n if (this._isIdle === true && event.type === 3 && !this.isInteractiveEvent(event)) {\n // Skip background DOM mutations while idle\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 (but not when idle)\n else if (this._isIdle !== true && 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 /**\n * Check if sessionStorage is available and can be used\n */\n private _canUseSessionStorage(): boolean {\n if (!isBrowser) return false;\n try {\n const test = '__sessionStorage_test__';\n sessionStorage.setItem(test, test);\n sessionStorage.removeItem(test);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get windowId from sessionStorage\n * SessionStorage persists across page reloads but is unique per window/tab\n */\n private _getWindowIdFromStorage(): string | null {\n if (!this._canUseSessionStorage()) {\n return null;\n }\n try {\n return sessionStorage.getItem(this._window_id_storage_key);\n } catch {\n return null;\n }\n }\n\n /**\n * Set windowId in sessionStorage\n */\n private _setWindowIdInStorage(windowId: string): void {\n if (!this._canUseSessionStorage()) {\n return;\n }\n try {\n sessionStorage.setItem(this._window_id_storage_key, windowId);\n logDebug(`Stored windowId in sessionStorage: ${windowId}`);\n } catch (error) {\n logWarn('Failed to store windowId in sessionStorage:', error);\n }\n }\n\n /**\n * Remove windowId from sessionStorage\n */\n private _removeWindowIdFromStorage(): void {\n if (!this._canUseSessionStorage()) {\n return;\n }\n try {\n sessionStorage.removeItem(this._window_id_storage_key);\n } catch (error) {\n logWarn('Failed to remove windowId from sessionStorage:', error);\n }\n }\n\n /**\n * Check if primary_window_exists flag is set in sessionStorage\n * This flag indicates if a window was opened as a new tab/window (not a reload)\n */\n private _getPrimaryWindowExists(): boolean {\n if (!this._canUseSessionStorage()) {\n return false;\n }\n try {\n return sessionStorage.getItem(this._primary_window_exists_storage_key) === 'true';\n } catch {\n return false;\n }\n }\n\n /**\n * Set primary_window_exists flag in sessionStorage\n * This flag is set when DOM loads and cleared on beforeunload\n */\n private _setPrimaryWindowExists(value: boolean): void {\n if (!this._canUseSessionStorage()) {\n return;\n }\n try {\n if (value) {\n sessionStorage.setItem(this._primary_window_exists_storage_key, 'true');\n } else {\n sessionStorage.removeItem(this._primary_window_exists_storage_key);\n }\n } catch (error) {\n logWarn('Failed to set primary_window_exists flag:', error);\n }\n }\n\n /**\n * Get or create windowId with multi-window detection\n * - Reuses windowId on page reload (same tab)\n * - Creates new windowId for new windows/tabs\n */\n private getOrCreateWindowId(): string {\n if (!isBrowser) {\n return uuidv1();\n }\n\n const lastWindowId = this._getWindowIdFromStorage();\n const primaryWindowExists = this._getPrimaryWindowExists();\n\n if (lastWindowId && !primaryWindowExists) {\n // Page reload: primary_window_exists was cleared on beforeunload\n // Reuse the windowId from sessionStorage\n logDebug(`Reusing windowId from previous page load: ${lastWindowId}`);\n this._setWindowIdInStorage(lastWindowId);\n this._setPrimaryWindowExists(true);\n return lastWindowId;\n } else {\n // New window/tab: primary_window_exists exists (copied from original window)\n // OR no previous windowId exists\n // Create a new windowId\n const newWindowId = uuidv1();\n logDebug(`Creating new windowId: ${newWindowId} (new window/tab detected)`);\n this._setWindowIdInStorage(newWindowId);\n this._setPrimaryWindowExists(true);\n return newWindowId;\n }\n }\n\n /**\n * Setup beforeunload listener to clear primary_window_exists flag\n * This allows us to distinguish page reloads from new windows/tabs\n */\n private setupWindowUnloadListener(): void {\n if (!isBrowser) {\n return;\n }\n\n // Use beforeunload to clear the flag before page unloads\n window.addEventListener('beforeunload', () => {\n if (this._canUseSessionStorage()) {\n this._setPrimaryWindowExists(false);\n logDebug('Cleared primary_window_exists flag on beforeunload');\n }\n }, { capture: false });\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`;\n this.deleteCookie(userIdCookieName);\n \n // Clear session data from localStorage\n const sessionKey = `human_behavior_session`;\n localStorage.removeItem(sessionKey);\n \n // Reset user-related properties\n this.endUserId = null;\n this.userProperties = {};\n \n // Generate new IDs for the next user\n this.endUserId = uuidv1();\n this.setCookie(`human_behavior_end_user_id`, this.endUserId, 365);\n this.sessionId = this.createNewSession(sessionKey);\n // Create new windowId for new session (logout = new session)\n this.windowId = uuidv1();\n this._setWindowIdInStorage(this.windowId);\n this.api.setTrackingContext(this.sessionId, this.endUserId);\n // Emit FullSnapshot so the player can replay this new window\n this.takeFullSnapshot();\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 * Check and refresh session if expired (called before adding events)\n * Uses in-memory state as source of truth\n */\n private checkAndRefreshSession(): void {\n if (!isBrowser) return;\n \n const sessionKey = `human_behavior_session`;\n const now = Date.now();\n\n // Get session data (checks memory first, then localStorage)\n // getStoredSession() now handles session expiration and creation atomically\n const stored = this.getStoredSession(sessionKey);\n\n if (!stored || !stored.sessionId) {\n // No stored session - create new one\n this.createNewSession(sessionKey);\n // New session = new windowId\n this.windowId = uuidv1();\n this._setWindowIdInStorage(this.windowId);\n this.api.setTrackingContext(this.sessionId, this.endUserId);\n // Emit FullSnapshot so the player can replay this new window\n this.takeFullSnapshot();\n logDebug(`Created new session (no stored session): ${this.sessionId}`);\n return;\n }\n\n // Session is valid (getStoredSession() already handled expiration)\n // Update activity timestamp to extend the session\n this.updateSessionActivity(sessionKey, now, stored.sessionId, stored.sessionStartTimestamp);\n }\n\n /**\n * Get or create session ID with timeout checking\n * Called once during initialization\n * Uses in-memory state as source of truth\n */\n private getOrCreateSessionId(): string {\n if (!isBrowser) {\n return uuidv1();\n }\n\n const sessionKey = `human_behavior_session`;\n const now = Date.now();\n\n // Get session data (checks memory first, then localStorage)\n const stored = this.getStoredSession(sessionKey);\n\n if (!stored || !stored.sessionId) {\n const newSessionId = this.createNewSession(sessionKey);\n this.api.setTrackingContext(newSessionId, this.endUserId);\n return newSessionId;\n }\n\n // Check idle timeout (15 minutes)\n const SESSION_IDLE_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes\n const SESSION_MAX_LENGTH_MS = 24 * 60 * 60 * 1000; // 24 hours\n\n const timeSinceActivity = now - stored.lastActivityTimestamp;\n const sessionAge = now - stored.sessionStartTimestamp;\n\n if (timeSinceActivity > SESSION_IDLE_TIMEOUT_MS || sessionAge > SESSION_MAX_LENGTH_MS) {\n logDebug(`Session expired: idle=${timeSinceActivity}ms, age=${sessionAge}ms`);\n const newSessionId = this.createNewSession(sessionKey);\n this.api.setTrackingContext(newSessionId, this.endUserId);\n return newSessionId;\n }\n\n // Update activity timestamp (extends session)\n // Memory is already updated by getStoredSession() if it read from localStorage\n this.updateSessionActivity(sessionKey, now, stored.sessionId, stored.sessionStartTimestamp);\n return stored.sessionId;\n }\n\n /**\n * Get session data (check memory first, then localStorage)\n */\n private getStoredSession(key: string): { sessionId: string; lastActivityTimestamp: number; sessionStartTimestamp: number } | null {\n const now = Date.now();\n const SESSION_IDLE_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes\n const SESSION_MAX_LENGTH_MS = 24 * 60 * 60 * 1000; // 24 hours\n \n // Check in-memory state first (source of truth during session)\n // BUT: Always validate expiration before returning from memory\n if (this.sessionId && this._sessionActivityTimestamp !== null && this._sessionStartTimestamp !== null) {\n const timeSinceActivity = now - this._sessionActivityTimestamp;\n const sessionAge = now - this._sessionStartTimestamp;\n \n // If expired, immediately create new session (atomic operation)\n if (timeSinceActivity > SESSION_IDLE_TIMEOUT_MS || sessionAge > SESSION_MAX_LENGTH_MS) {\n logDebug(`Session in memory expired: creating new session immediately`);\n const oldSessionId = this.sessionId;\n this.createNewSession(key);\n // New session = new windowId\n this.windowId = uuidv1();\n this._setWindowIdInStorage(this.windowId);\n this.api.setTrackingContext(this.sessionId, this.endUserId);\n // Emit FullSnapshot so the player can replay this new window\n this.takeFullSnapshot();\n logInfo(`🔄 Session timeout (memory): Created new session ${this.sessionId} (previous: ${oldSessionId})`);\n // Return the new session (createNewSession ensures these are not null)\n if (this._sessionActivityTimestamp !== null && this._sessionStartTimestamp !== null) {\n return {\n sessionId: this.sessionId,\n lastActivityTimestamp: this._sessionActivityTimestamp,\n sessionStartTimestamp: this._sessionStartTimestamp\n };\n }\n } else {\n // Session in memory is valid\n return {\n sessionId: this.sessionId,\n lastActivityTimestamp: this._sessionActivityTimestamp,\n sessionStartTimestamp: this._sessionStartTimestamp\n };\n }\n }\n \n // Only read from localStorage if memory is empty (initialization or after page reload)\n try {\n const stored = localStorage.getItem(key);\n if (!stored) return null;\n const parsed = JSON.parse(stored);\n \n // Check expiration BEFORE setting in memory\n const timeSinceActivity = now - parsed.lastActivityTimestamp;\n const sessionAge = now - parsed.sessionStartTimestamp;\n \n if (timeSinceActivity > SESSION_IDLE_TIMEOUT_MS || sessionAge > SESSION_MAX_LENGTH_MS) {\n // Session expired - immediately create new session (atomic operation)\n logDebug(`Session in localStorage expired: idle=${Math.round(timeSinceActivity / 1000 / 60)}min, age=${Math.round(sessionAge / 1000 / 60 / 60)}hrs`);\n const oldSessionId = parsed.sessionId;\n this.createNewSession(key);\n // New session = new windowId\n this.windowId = uuidv1();\n this._setWindowIdInStorage(this.windowId);\n this.api.setTrackingContext(this.sessionId, this.endUserId);\n // Emit FullSnapshot so the player can replay this new window\n this.takeFullSnapshot();\n logInfo(`🔄 Session timeout (localStorage): Created new session ${this.sessionId} (previous: ${oldSessionId})`);\n // Return the new session (createNewSession ensures these are not null)\n if (this._sessionActivityTimestamp !== null && this._sessionStartTimestamp !== null) {\n return {\n sessionId: this.sessionId,\n lastActivityTimestamp: this._sessionActivityTimestamp,\n sessionStartTimestamp: this._sessionStartTimestamp\n };\n }\n }\n \n // Session is valid - update memory from storage (for next time)\n if (parsed.sessionId) {\n this.sessionId = parsed.sessionId;\n this._sessionActivityTimestamp = parsed.lastActivityTimestamp;\n this._sessionStartTimestamp = parsed.sessionStartTimestamp;\n }\n \n return parsed;\n } catch {\n return null;\n }\n }\n\n /**\n * Create a new session (update memory first, then persistence)\n */\n private createNewSession(key: string): string {\n const sessionId = uuidv1();\n const now = Date.now();\n \n // Update memory immediately (source of truth)\n this.sessionId = sessionId;\n this._sessionActivityTimestamp = now;\n this._sessionStartTimestamp = now;\n \n // ✅ Sync idle detection timestamp with session activity timestamp\n // This ensures idle detection (5 min) and session timeout (15 min) are aligned\n this._lastActivityTimestamp = now;\n \n // Then write to persistence\n const session = {\n sessionId,\n lastActivityTimestamp: now,\n sessionStartTimestamp: now\n };\n try {\n localStorage.setItem(key, JSON.stringify(session));\n } catch (e) {\n logWarn(`Failed to save session to localStorage: ${e}`);\n }\n \n logDebug(`Created new session: ${sessionId}`);\n return sessionId;\n }\n\n /**\n * Update session activity timestamp (update memory first, then persistence)\n * Note: This is called by checkAndRefreshSession() for any event, not just user interactions\n * For user interactions, updateIdleState() also updates this, keeping them in sync\n */\n private updateSessionActivity(key: string, timestamp: number, sessionId: string, sessionStartTimestamp: number): void {\n // Update memory immediately (source of truth)\n this.sessionId = sessionId;\n this._sessionActivityTimestamp = timestamp;\n this._sessionStartTimestamp = sessionStartTimestamp;\n \n // ✅ Note: We don't update _lastActivityTimestamp here because:\n // - updateIdleState() handles it for user interactions\n // - Non-interactive events shouldn't reset idle detection (5 min threshold)\n // - Session timeout (15 min) is extended by any event via checkAndRefreshSession()\n \n // Then write to persistence\n const session = {\n sessionId,\n lastActivityTimestamp: timestamp,\n sessionStartTimestamp\n };\n try {\n localStorage.setItem(key, JSON.stringify(session));\n } catch (e) {\n logWarn(`Failed to update session in localStorage: ${e}`);\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`);\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","sdkLoggingInProgress","isSDKLogging","logError","logWarn","logInfo","logDebug","RetryQueue","sendRequest","_isPolling","_pollIntervalMs","_queue","_areWeOnline","_sendRequest","navigator","onLine","addEventListener","_flush","retriableRequest","options","retriesPerformedSoFar","url","URL","searchParams","set","toString","_shouldRetry","_enqueue","callback","statusCode","status","text","requestOptions","msToNextRetry","rawBackoffTime","minBackoff","cappedBackoffTime","Math","min","jitter","random","ceil","pickNextRetryDelay","retryAt","logMessage","round","_poll","_poller","clearTimeout","setTimeout","notToFlush","toFlush","filter","item","catch","unload","_sendBeaconRequest","sendBeacon","body","Blob","type","EventPersistence","apiKey","maxQueueSize","storageKey","getQueue","stored","queue","Array","isArray","setQueue","limitedQueue","slice","name","code","smallerQueue","floor","clearQueue","addToQueue","event","shift","removeFromQueue","count","getQueueLength","SDK_VERSION","MAX_CHUNK_SIZE_BYTES","KEEP_ALIVE_THRESHOLD","isChunkSizeExceeded","currentChunk","newEvent","sessionId","TextEncoder","encode","safeJsonStringify","events","data","_","value","splitLargeEvent","simplifiedEvent","largeProperties","forEach","prop","pathname","Object","fromEntries","entries","key","includes","HumanBehaviorAPI","ingestionUrl","monthlyLimitReached","endUserId","cspBlocked","requestTimeout","currentBatchSize","baseUrl","persistence","retryQueue","_sendRequestInternal","_loadPersistedEvents","setTrackingContext","persistedQueue","queuedEvent","sendEventsChunked","windowId","automaticProperties","controller","AbortController","timeoutId","abort","estimatedSize","useKeepalive","method","response","fetch","headers","signal","keepalive","responseText","responseJson","json","ok","checkMonthlyLimit","init","userId","entryURL","referrer","location","href","document","trackedFetch","Authorization","Referer","sdkVersion","errorText","Error","statusText","sendEvents","validEvents","results","flat","result","_sendChunkWithRetry","_persistEvents","chunk","batchSize","startIndex","bodyString","max","sendUserData","userData","payload","userAttributes","posthogName","email","sendUserAuth","authFields","sendBeaconEvents","blob","sendCustomEvent","eventName","eventProperties","sendCustomEventBatch","sendLog","logData","substring","sendNetworkError","errorData","errorType","requestStartTime","requestId","uuidv1","shouldSkipTracking","shouldSkipNetworkTracking","trackedFetchWithBeaconFallback","requestDuration","duration","timestampMs","classifyHttpError","errorMessage","startTimeMs","spanName","spanStatus","attributes","timeoutError","isCSPViolation","classifyNetworkError","errorName","bodyJson","encodeURIComponent","Response","Headers","parsed","toLowerCase","urlObj","baseUrlObj","origin","startsWith","RedactionManager","redactedText","unredactedFields","Set","redactedFields","redactionMode","excludeSelectors","redactionStrategy","mode","unredactFields","setFieldsToUnredact","defaultMarks","fieldsToRedact","redactFields","setFieldsToRedact","legacyRedactFields","userFields","fields","clear","field","add","size","from","applyRedactionClasses","validFields","isPasswordSelector","applyUnredactionClasses","delete","clearUnredactedFields","removeUnredactionClasses","hasUnredactedFields","getRedactionMode","getUnredactedFields","getMaskTextSelector","join","readyState","selector","elements","querySelectorAll","element","classList","remove","some","pattern","replace","getOriginalValue","HTMLInputElement","HTMLTextAreaElement","isElementUnredacted","shouldUnredactElement","matches","detectDeviceType","userAgent","screenWidth","screen","width","screenHeight","height","test","extractDomain","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","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","initialize","loadSessionProperties","getEventProperties","properties","assign","setSessionProperty","applyDenylist","getAutomaticPropertiesWithGeoIP","geoIPProperties","saveSessionProperties","setSessionProperties","getSessionProperty","removeSessionProperty","setUserProperty","setUserProperties","getUserProperty","removeUserProperty","setOnce","scope","clearSessionProperties","clearUserProperties","reset","sessionStorage","deniedKey","updateAutomaticProperties","getAllProperties","automatic","session","user","initial","HumanBehaviorTracker","isTrackerStarted","isStarted","setupDomReadyHandler","onDomReady","capture","interval","setInterval","clearInterval","isDomReady","requestQueue","request","processRequest","domReadyHandlers","handler","queueRequest","addEvent","identifyUser","trackPageView","registerDomReadyHandler","suppressConsoleErrors","originalConsoleError","apply","originalConsoleWarn","preventDefault","__humanBehaviorGlobalTracker","logLevel","configureLogging","tracker","enableConsoleTracking","enableNetworkTracking","recordCanvas","setUnredactedFields","enableAutomaticTracking","setupAutomaticTracking","automaticTrackingOptions","start","eventQueue","pendingCustomEvents","pendingLogs","pendingNetworkErrors","_sessionActivityTimestamp","_sessionStartTimestamp","isProcessing","flushInterval","FLUSH_INTERVAL_MS","initialized","initializationPromise","originalConsole","consoleTrackingEnabled","originalFetch","networkTrackingEnabled","enableConsoleTrackingFlag","enableNetworkTrackingFlag","navigationTrackingEnabled","previousUrl","originalPushState","originalReplaceState","navigationListeners","_connectionBlocked","recordInstance","sessionStartTime","rrwebRecord","fullSnapshotTimeout","minimumDurationMilliseconds","_isIdle","_lastActivityTimestamp","IDLE_THRESHOLD_MS","rageClickTracker","clicks","RAGE_CLICK_THRESHOLD_PX","RAGE_CLICK_TIMEOUT_MS","RAGE_CLICK_CLICK_COUNT","deadClickTracker","pendingClicks","Map","DEAD_CLICK_SCROLL_THRESHOLD_MS","DEAD_CLICK_SELECTION_THRESHOLD_MS","DEAD_CLICK_MUTATION_THRESHOLD_MS","DEAD_CLICK_ABSOLUTE_TIMEOUT_MS","finalIngestionUrl","api","MAX_QUEUE_SIZE","redactionManager","propertyManager","endUserIdKey","existingEndUserId","getCookie","setCookie","persistenceName","_window_id_storage_key","_primary_window_exists_storage_key","getOrCreateSessionId","getOrCreateWindowId","setupWindowUnloadListener","setupPageUnloadHandler","setupNavigationTracking","ensureInitialized","history","pushState","replaceState","trackNavigationEvent","takeFullSnapshot","popstateListener","removeEventListener","hashchangeListener","fromUrl","toUrl","navigationData","to","eventType","pageViewProperties","navigationType","customEvent","pageViewData","enhancedProperties","checkAndRefreshSession","shouldSkipDueToMinimumDuration","flushPendingCustomEvents","customEventData","fallbackError","trackButtons","trackLinks","trackForms","includeText","includeClasses","setupAutomaticButtonTracking","setupAutomaticFormTracking","setupRageClickDetection","async","target","tagName","closest","button","buttonId","id","buttonType","page","buttonText","textContent","trim","buttonClass","className","keys","x","clientX","y","clientY","isRageClick","clickCount","elementId","elementClass","elementText","lastClick","abs","isInteractiveElement","role","getAttribute","onclick","getComputedStyle","cursor","setupAutomaticLinkTracking","form","formData","FormData","formId","formAction","action","formMethod","formClass","cleanupNavigationTracking","cleanup","none","trackConsoleEvent","bind","input","toUpperCase","LONG_LOADING_THRESHOLD_MS","longLoadingTimeoutId","longLoadingTracked","elapsedTime","flushPendingNetworkErrors","eventsToFlush","flushPendingLogs","logsToFlush","errorsToFlush","enablePageLoadTracking","trackPageLoad","performance","perfEntry","getEntriesByType","loadDuration","loadEventEnd","fetchStart","domContentLoaded","domContentLoadedEventEnd","domComplete","timeOrigin","disableConsoleTracking","stack","isSDKStackFrame","consoleData","map","arg","String","err","sdkPatterns","stackLines","split","foundNonSDKFrame","i","line","visibilityState","flushEvents","unloadEvent","minimumDuration","sessionDuration","getSessionDuration","eventsToSend","__hb_pending_snapshots","pendingSnapshots","unshift","updateActivity","viewLogs","originalEndUserId","userResponse","actualUserId","wasExistingUser","canonicalEndUserId","cookieName","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","node","querySelector","checkDomReady","once","requestAnimationFrame","stop","hasData","hasNode","sessionStart","eventsWithTimestamps","mostRecentEvent","reduce","latest","current","eventsToProcess","fullSnapshotsInFlush","fullSnapshots","isInteractiveEvent","source","updateIdleState","isUserInteraction","currentTime","wasIdle","timeSinceLastActivity","_canUseSessionStorage","_getWindowIdFromStorage","_setWindowIdInStorage","_removeWindowIdFromStorage","_getPrimaryWindowExists","_setPrimaryWindowExists","lastWindowId","primaryWindowExists","newWindowId","daysToExpire","setTime","getTime","expires","toUTCString","cookie","localStorageError","nameEQ","ca","c","charAt","indexOf","cookieValue","localStorageValue","deleteCookie","logout","userIdCookieName","sessionKey","createNewSession","redact","setRedactedFields","restartWithNewRedaction","getStoredSession","updateSessionActivity","sessionStartTimestamp","newSessionId","timeSinceActivity","lastActivityTimestamp","sessionAge","SESSION_IDLE_TIMEOUT_MS","SESSION_MAX_LENGTH_MS","oldSessionId","getSessionId","getCurrentUrl","getSnapshotFrequencyInfo","currentInterval","currentThreshold","phase","testConnection","success","getConnectionStatus","recommendations","blocked","isPreexistingUser","getUserInfo","identifyUserGlobally","globalTracker","globalThis","sendEventGlobally","track","isGlobalTrackerAvailable"],"mappings":"qEAAYA,GAAZ,SAAYA,GACVA,EAAAA,EAAA,KAAA,GAAA,OACAA,EAAAA,EAAA,MAAA,GAAA,QACAA,EAAAA,EAAA,KAAA,GAAA,OACAA,EAAAA,EAAA,KAAA,GAAA,OACAA,EAAAA,EAAA,MAAA,GAAA,OACD,CAND,CAAYA,IAAAA,EAAQ,CAAA,IAyIb,MAAMC,EAAS,IA3HtB,MASE,WAAAC,CAAYC,GARJC,KAAAD,OAAuB,CAC7BE,MAAOL,EAASM,MAChBC,eAAe,EACfC,eAAe,GAGTJ,KAAAK,UAA8B,oBAAXC,OAGrBP,IACFC,KAAKD,OAAS,IAAKC,KAAKD,UAAWA,GAEvC,CAEA,SAAAQ,CAAUR,GACRC,KAAKD,OAAS,IAAKC,KAAKD,UAAWA,EACrC,CAEQ,SAAAS,CAAUP,GAChB,OAAOA,GAASD,KAAKD,OAAOE,KAC9B,CAEQ,aAAAQ,CAAcR,EAAeS,KAAoBC,GAEvD,MAAO,kBAAkBV,OADP,IAAIW,MAAOC,kBACoBH,GACnD,CAEA,KAAAI,CAAMJ,KAAoBC,GACxB,IAAKX,KAAKQ,UAAUZ,EAASM,OAAQ,OAErC,MAAMa,EAAmBf,KAAKS,cAAc,QAASC,GAEjDV,KAAKD,OAAOI,eACda,QAAQF,MAAMC,KAAqBJ,GAGjCX,KAAKD,OAAOK,eAAiBJ,KAAKK,WACpCL,KAAKiB,aAAaF,EAAkBJ,EAExC,CAEA,IAAAO,CAAKR,KAAoBC,GACvB,IAAKX,KAAKQ,UAAUZ,EAASuB,MAAO,OAEpC,MAAMJ,EAAmBf,KAAKS,cAAc,OAAQC,GAEhDV,KAAKD,OAAOI,eACda,QAAQE,KAAKH,KAAqBJ,GAGhCX,KAAKD,OAAOK,eAAiBJ,KAAKK,WACpCL,KAAKiB,aAAaF,EAAkBJ,EAExC,CAEA,IAAAS,CAAKV,KAAoBC,GACvB,IAAKX,KAAKQ,UAAUZ,EAASyB,MAAO,OAEpC,MAAMN,EAAmBf,KAAKS,cAAc,OAAQC,GAEhDV,KAAKD,OAAOI,eACda,QAAQM,IAAIP,KAAqBJ,GAG/BX,KAAKD,OAAOK,eAAiBJ,KAAKK,WACpCL,KAAKiB,aAAaF,EAAkBJ,EAExC,CAEA,KAAAY,CAAMb,KAAoBC,GACxB,IAAKX,KAAKQ,UAAUZ,EAAS4B,OAAQ,OAErC,MAAMT,EAAmBf,KAAKS,cAAc,QAASC,GAEjDV,KAAKD,OAAOI,eACda,QAAQM,IAAIP,KAAqBJ,GAG/BX,KAAKD,OAAOK,eAAiBJ,KAAKK,WACpCL,KAAKiB,aAAaF,EAAkBJ,EAExC,CAEQ,YAAAM,CAAaP,EAAiBC,GACpC,IACE,MAAMc,EAAOC,KAAKC,MAAMC,aAAaC,QAAQ,wBAA0B,MACjEC,EAAW,CACfpB,UACAC,KAAMA,EAAKoB,OAAS,EAAIpB,OAAOqB,EAC/BC,UAAWrB,KAAKsB,OAElBT,EAAKU,KAAKL,GAGNL,EAAKM,OAAS,KAChBN,EAAKW,OAAO,EAAGX,EAAKM,OAAS,KAG/BH,aAAaS,QAAQ,sBAAuBX,KAAKY,UAAUb,GAC7D,CAAE,MAAOc,GAET,CACF,CAEA,OAAAC,GACE,IAAKxC,KAAKK,UAAW,MAAO,GAE5B,IACE,OAAOqB,KAAKC,MAAMC,aAAaC,QAAQ,wBAA0B,KACnE,CAAE,MAAOU,GACP,MAAO,EACT,CACF,CAEA,SAAAE,GACMzC,KAAKK,WACPuB,aAAac,WAAW,sBAE5B,GAOF,IAAIC,GAAuB,QAGdC,EAAe,IAAeD,EAG9BE,EAAW,CAACnC,KAAoBC,KACzCgC,GAAuB,EACvB,IACI9C,EAAOiB,MAAMJ,KAAYC,EAC7B,SACIgC,GAAuB,CAC3B,GAGSG,EAAU,CAACpC,KAAoBC,KACxCgC,GAAuB,EACvB,IACI9C,EAAOqB,KAAKR,KAAYC,EAC5B,SACIgC,GAAuB,CAC3B,GAGSI,EAAU,CAACrC,KAAoBC,KACxCgC,GAAuB,EACvB,IACI9C,EAAOuB,KAAKV,KAAYC,EAC5B,SACIgC,GAAuB,CAC3B,GAGSK,EAAW,CAACtC,KAAoBC,KACzCgC,GAAuB,EACvB,IACI9C,EAAO0B,MAAMb,KAAYC,EAC7B,SACIgC,GAAuB,CAC3B,SC5ISM,EAQT,WAAAnD,CAAYoD,GAPJlD,KAAAmD,YAAsB,EAEtBnD,KAAAoD,gBAA0B,IAC1BpD,KAAAqD,OAA8B,GAKlCrD,KAAKqD,OAAS,GACdrD,KAAKsD,cAAe,EACpBtD,KAAKuD,aAAeL,EAEE,oBAAX5C,QAA0B,WAAYA,OAAOkD,YACpDxD,KAAKsD,aAAehD,OAAOkD,UAAUC,OAErCnD,OAAOoD,iBAAiB,SAAU,KAC9B1D,KAAKsD,cAAe,EACpBtD,KAAK2D,WAGTrD,OAAOoD,iBAAiB,UAAW,KAC/B1D,KAAKsD,cAAe,IAGhC,CAEA,UAAIvB,GACA,OAAO/B,KAAKqD,OAAOtB,MACvB,CAEA,sBAAM6B,CAAiBC,GACnB,MAAMC,EAAwBD,EAAQC,uBAAyB,EAG/D,GAAIA,EAAwB,EAAG,CAC3B,MAAMC,EAAM,IAAIC,IAAIH,EAAQE,KAC5BA,EAAIE,aAAaC,IAAI,cAAeJ,EAAsBK,YAC1DN,EAAQE,IAAMA,EAAII,UACtB,CAEA,UACUnE,KAAKuD,aAAaM,EAC5B,CAAE,MAAO/C,GAIL,GAFoBd,KAAKoE,aAAatD,EAAOgD,IAE1BA,EAAwB,GAEvC,YADA9D,KAAKqE,SAASR,GAKdA,EAAQS,UACRT,EAAQS,SAAS,CACbC,WAAYzD,EAAM0D,QAAU,EAC5BC,KAAM3D,EAAMJ,SAAW,kBAGnC,CACJ,CAEQ,YAAA0D,CAAatD,EAAYgD,GAE7B,OAAIhD,EAAM0D,QAAU,KAAO1D,EAAM0D,OAAS,IACd,MAAjB1D,EAAM0D,QAAmC,MAAjB1D,EAAM0D,OAIlC1D,EAAM0D,QAAU,MAAQ1D,EAAM0D,MACzC,CAEQ,QAAAH,CAASK,GACb,MAAMZ,EAAwBY,EAAeZ,uBAAyB,EACtEY,EAAeZ,sBAAwBA,EAAwB,EAE/D,MAAMa,EApGR,SAA6Bb,GAC/B,MAAMc,EAAiB,IAAO,GAAKd,EAC7Be,EAAaD,EAAiB,EAC9BE,EAAoBC,KAAKC,IAhBZ,KAgBgCJ,GAE7CK,GADiBF,KAAKG,SAAW,KACNJ,EAAoBD,GACrD,OAAOE,KAAKI,KAAKL,EAAoBG,EACzC,CA6F8BG,CAAmBtB,GACnCuB,EAAUzE,KAAKsB,MAAQyC,EAE7B3E,KAAKqD,OAAOlB,KAAK,CAAEkD,UAASX,mBAE5B,IAAIY,EAAa,wCAAwCP,KAAKQ,MAAMZ,EAAgB,QAC3D,oBAAdnB,WAA8BA,UAAUC,SAC/C6B,GAAc,yBAElBxC,EAAQwC,GAEHtF,KAAKmD,aACNnD,KAAKmD,YAAa,EAClBnD,KAAKwF,QAEb,CAEQ,KAAAA,GACAxF,KAAKyF,SACLC,aAAa1F,KAAKyF,SAEtBzF,KAAKyF,QAAUE,WAAW,KAClB3F,KAAKsD,cAAgBtD,KAAKqD,OAAOtB,OAAS,GAC1C/B,KAAK2D,SAET3D,KAAKwF,SACNxF,KAAKoD,gBACZ,CAEQ,MAAAO,GACJ,MAAMzB,EAAMtB,KAAKsB,MACX0D,EAAkC,GAClCC,EAAU7F,KAAKqD,OAAOyC,OAAQC,GAC5BA,EAAKV,QAAUnD,IAGnB0D,EAAWzD,KAAK4D,IACT,IAKX,GAFA/F,KAAKqD,OAASuC,EAEVC,EAAQ9D,OAAS,EACjB,IAAK,MAAM2C,eAAEA,KAAoBmB,EAC7B7F,KAAK4D,iBAAiBc,GAAgBsB,MAAOlF,IACzC+B,EAAS,2BAA4B/B,IAIrD,CAEA,MAAAmF,GACQjG,KAAKyF,UACLC,aAAa1F,KAAKyF,SAClBzF,KAAKyF,aAAUzD,GAGnB,IAAK,MAAM0C,eAAEA,KAAoB1E,KAAKqD,OAClC,IAEIrD,KAAKkG,mBAAmBxB,EAC5B,CAAE,MAAOnC,GACLM,EAAS,mDAAoDN,EACjE,CAEJvC,KAAKqD,OAAS,EAClB,CAEQ,kBAAA6C,CAAmBrC,GACvB,GAAyB,oBAAdL,WAA8BA,UAAU2C,WAInD,IACI,MAAMpC,EAAM,IAAIC,IAAIH,EAAQE,KAC5BA,EAAIE,aAAaC,IAAI,SAAU,KAE/B,IAAIkC,EAAoB,KACpBvC,EAAQuC,OACoB,iBAAjBvC,EAAQuC,KACfA,EAAO,IAAIC,KAAK,CAACxC,EAAQuC,MAAO,CAAEE,KAAM,qBACjCzC,EAAQuC,gBAAgBC,OAC/BD,EAAOvC,EAAQuC,OAIP5C,UAAU2C,WAAWpC,EAAII,WAAYiC,IAEjDtD,EAAQ,+CAEhB,CAAE,MAAOhC,GACL+B,EAAS,gCAAiC/B,EAC9C,CACJ,QCnMSyF,EAIT,WAAAzG,CAAY0G,EAAgBC,EAAuB,KAC/CzG,KAAK0G,WAAa,uBAClB1G,KAAKyG,aAAeA,CACxB,CAKA,QAAAE,GACI,GAAsB,oBAAXrG,SAA2BA,OAAOsB,aACzC,MAAO,GAGX,IACI,MAAMgF,EAAStG,OAAOsB,aAAaC,QAAQ7B,KAAK0G,YAChD,IAAKE,EACD,MAAO,GAGX,MAAMC,EAAQnF,KAAKC,MAAMiF,GACzB,OAAKE,MAAMC,QAAQF,GAIZA,EAHI,EAIf,CAAE,MAAO/F,GAEL,OADAgC,EAAQ,kCAAmChC,GACpC,EACX,CACJ,CAKA,QAAAkG,CAASH,GACL,GAAsB,oBAAXvG,QAA2BA,OAAOsB,aAI7C,IAEI,MAAMqF,EAAeJ,EAAMK,OAAOlH,KAAKyG,cACvCnG,OAAOsB,aAAaS,QAAQrC,KAAK0G,WAAYhF,KAAKY,UAAU2E,IAC5DjE,EAAS,aAAaiE,EAAalF,2BACvC,CAAE,MAAOjB,GAEL,GAAmB,uBAAfA,EAAMqG,MAAgD,KAAfrG,EAAMsG,KAAa,CAC1DtE,EAAQ,+CACR,IAEI,MAAMuE,EAAeR,EAAMK,OAAOnC,KAAKuC,MAAMtH,KAAKyG,aAAe,IACjEnG,OAAOsB,aAAaS,QAAQrC,KAAK0G,WAAYhF,KAAKY,UAAU+E,GAChE,CAAE,MAAO9E,GACLO,EAAQ,kDACR9C,KAAKuH,YACT,CACJ,MACIzE,EAAQ,2BAA4BhC,EAE5C,CACJ,CAKA,UAAA0G,CAAWC,GACP,MAAMZ,EAAQ7G,KAAK2G,WACnBE,EAAM1E,KAAKsF,GAGPZ,EAAM9E,OAAS/B,KAAKyG,eACpBI,EAAMa,QACN1E,EAAS,gDAGbhD,KAAKgH,SAASH,EAClB,CAKA,eAAAc,CAAgBC,GACZ,MAAMf,EAAQ7G,KAAK2G,WACnBE,EAAMzE,OAAO,EAAGwF,GAChB5H,KAAKgH,SAASH,EAClB,CAKA,UAAAU,GACI,GAAsB,oBAAXjH,QAA2BA,OAAOsB,aAI7C,IACItB,OAAOsB,aAAac,WAAW1C,KAAK0G,WACxC,CAAE,MAAO5F,GACLgC,EAAQ,mCAAoChC,EAChD,CACJ,CAKA,cAAA+G,GACI,OAAO7H,KAAK2G,WAAW5E,MAC3B,ECtHJ,MAAM+F,EAAc,SAEPC,EAAuB,QAC9BC,EAAuB,iBAGbC,EAAoBC,EAAqBC,EAAeC,GAMpE,OALsB,IAAIC,aAAcC,OAAOC,EAAkB,CAC7DH,YACAI,OAAQ,IAAIN,EAAcC,MAC1BpG,OAEmBgG,CAC3B,CAqBA,SAASQ,EAAkBE,GACvB,OAAO/G,KAAKY,UAAUmG,EAAM,CAACC,EAAGC,IACP,iBAAVA,EACAA,EAAMxE,WAEVwE,EAEf,CAEM,SAAUC,EAAgBnB,EAAYW,GAExC,IAAKX,GAA0B,iBAAVA,EACjB,MAAO,GAQX,IALkB,IAAIY,aAAcC,OAAOC,EAAkB,CACzDH,YACAI,OAAQ,CAACf,MACT1F,QAEagG,EACb,MAAO,CAACN,GAIZ,MAAMoB,EAAkB,IAAKpB,GAGvBqB,EAAkB,CAAC,aAAc,OAAQ,MAAO,WAAY,YAAa,aAC/EA,EAAgBC,QAAQC,IAChBH,EAAgBG,WACTH,EAAgBG,KAU/B,IALuB,IAAIX,aAAcC,OAAOC,EAAkB,CAC9DH,YACAI,OAAQ,CAACK,MACT9G,QAEkBgG,EAClB,MAAO,CAACc,GAoBZ,MAAO,CAhBc,CACjBvC,KAAMmB,EAAMnB,KACZrE,UAAWwF,EAAMxF,UACjB8B,IAAK0D,EAAM1D,IACXkF,SAAUxB,EAAMwB,YAEbC,OAAOC,YACND,OAAOE,QAAQ3B,GAAO3B,OAAO,EAAEuD,EAAKV,MAC/BG,EAAgBQ,SAASD,IACT,iBAAVV,GACU,iBAAVA,GACW,iBAAVA,GAAsBA,EAAM5G,OAAS,OAM7D,OAEawH,EAYT,WAAAzJ,EAAY0G,OAAEA,EAAMgD,aAAEA,IATdxJ,KAAAyJ,qBAA+B,EAC/BzJ,KAAAoI,UAAoB,GACpBpI,KAAA0J,UAA2B,KAC3B1J,KAAA2J,YAAsB,EAGtB3J,KAAA4J,eAxGe,IAyGf5J,KAAA6J,iBAA2B,IAG/B7J,KAAKwG,OAASA,EACdxG,KAAK8J,QAAUN,EACfxJ,KAAK+J,YAAc,IAAIxD,EAAiBC,GACxCxG,KAAKgK,WAAa,IAAI/G,EAAYY,GAAY7D,KAAKiK,qBAAqBpG,IAGxE7D,KAAKkK,sBACT,CAKO,kBAAAC,CAAmB/B,EAAmBsB,GACzC1J,KAAKoI,UAAYA,EACjBpI,KAAK0J,UAAYA,CACrB,CAKQ,0BAAMQ,GACV,MAAME,EAAiBpK,KAAK+J,YAAYpD,WACxC,GAA8B,IAA1ByD,EAAerI,OAAnB,CAIAiB,EAAS,WAAWoH,EAAerI,wCAEnC,IAAK,MAAMsI,KAAeD,EACtB,UACUpK,KAAKsK,kBACPD,EAAY7B,OACZ6B,EAAYjC,UACZiC,EAAYX,gBAAa1H,EACzBqI,EAAYE,SACZF,EAAYG,qBAGhBxK,KAAK+J,YAAYpC,gBAAgB,EACrC,CAAE,MAAO7G,GACLgC,EAAQ,oDAAqDhC,EAEjE,CAlBJ,CAoBJ,CAKQ,0BAAMmJ,CAAqBpG,GAC/B,MAAM4G,EAAwC,oBAApBC,gBAAkC,IAAIA,gBAAoB,KACpF,IAAIC,EAAkD,KAElDF,IACAE,EAAYhF,WAAW,KACnB8E,EAAYG,SACb5K,KAAK4J,iBAGZ,IACI,MAAMiB,EAAgBhH,EAAQgH,eAAiB,EACzCC,EAAkC,SAAnBjH,EAAQkH,QAAqBF,EAAgB7C,EAE5DgD,QAAiBC,MAAMpH,EAAQE,IAAK,CACtCgH,OAAQlH,EAAQkH,QAAU,MAC1BG,QAASrH,EAAQqH,SAAW,CAAA,EAC5B9E,KAAMvC,EAAQuC,KACd+E,OAAQV,GAAYU,OACpBC,UAAWN,IAGXH,GACAjF,aAAaiF,GAGjB,MAAMU,QAAqBL,EAASvG,OACpC,IAAI6G,EAAoB,KAExB,IACIA,EAAe5J,KAAKC,MAAM0J,EAC9B,CAAE,MAEF,CAUA,GARIxH,EAAQS,UACRT,EAAQS,SAAS,CACbC,WAAYyG,EAASxG,OACrBC,KAAM4G,EACNE,KAAMD,KAITN,EAASQ,GACV,KAAM,CAAEhH,OAAQwG,EAASxG,OAAQ9D,QAAS2K,EAElD,CAAE,MAAOvK,GAKL,GAJI6J,GACAjF,aAAaiF,GAGE,eAAf7J,EAAMqG,KACN,KAAM,CAAE3C,OAAQ,EAAG9D,QAAS,mBAEhC,MAAMI,CACV,CACJ,CAKO,MAAAmF,GACHjG,KAAKgK,WAAW/D,QACpB,CAEQ,iBAAAwF,GACJ,OAAIzL,KAAKyJ,mBAIb,CAEO,UAAMiC,CAAKtD,EAAmBuD,GAEjC,IAAK3L,KAAKyL,oBAEN,MAAO,CACHrD,UAAWA,EACXsB,UAAWiC,GAKnB,IAAIC,EAAW,KACXC,EAAW,KAEO,oBAAXvL,SACPsL,EAAWtL,OAAOwL,SAASC,KAC3BF,EAAWG,SAASH,UAGxB9I,EAAQ,wBAAyB,CAAEqF,YAAWuD,SAAQC,WAAUC,WAAU/B,QAAS9J,KAAK8J,UAExF,IACA,MAAMkB,QAAiBhL,KAAKiM,aAAa,GAAGjM,KAAK8J,6BAA8B,CAC3EiB,OAAQ,OACRG,QAAS,CACL,eAAgB,mBAChBgB,cAAiB,UAAUlM,KAAKwG,SAChC2F,QAAWN,GAAY,IAE3BzF,KAAMmC,EAAkB,CACpBH,UAAWA,EACXsB,UAAWiC,EACXC,SAAUA,EACVC,SAAUA,EACVO,WAAYtE,MAMpB,GAFI/E,EAAQ,4BAA6BiI,EAASxG,SAE7CwG,EAASQ,GAAI,CACd,GAAwB,MAApBR,EAASxG,OAGT,OAFAxE,KAAKyJ,qBAAsB,EAEpB,CACHrB,UAAWA,EACXsB,UAAWiC,GAGnB,MAAMU,QAAkBrB,EAASvG,OAEjC,MADA5B,EAAS,mBAAoBmI,EAASxG,OAAQ6H,GACxC,IAAIC,MAAM,mCAAmCtB,EAASuB,gBAAgBF,IAChF,CAEA,MAAMf,QAAqBN,EAASO,OASpC,OANyC,IAArCD,EAAa7B,sBACbzJ,KAAKyJ,qBAAsB,EAC3B1G,EAAQ,wDAGZA,EAAQ,oBAAqBuI,GACtB,CACHlD,UAAWkD,EAAalD,UACxBsB,UAAW4B,EAAa5B,UAE5B,CAAE,MAAO5I,GAEL,MADA+B,EAAS,kBAAmB/B,GACtBA,CACV,CACJ,CAMA,gBAAM0L,CAAWhE,EAAeJ,EAAmBuD,GAE/C,MAAMc,EAAcjE,EAAO1C,OAAO2B,GAASA,GAA0B,iBAAVA,GAErDuD,QAAiBhL,KAAKiM,aAAa,GAAGjM,KAAK8J,+BAAgC,CAC7EiB,OAAQ,OACRG,QAAS,CACL,eAAgB,mBAChBgB,cAAiB,UAAUlM,KAAKwG,UAEpCJ,KAAMmC,EAAkB,CACpBH,YACAI,OAAQiE,EACR/C,UAAWiC,EACXS,WAAYtE,MAIpB,IAAKkD,EAASQ,GAAI,CACd,GAAwB,MAApBR,EAASxG,OAET,MADAxE,KAAKyJ,qBAAsB,EACrB,IAAI6C,MAAM,+CAEpB,MAAM,IAAIA,MAAM,0BAA0BtB,EAASuB,aACvD,EAIyC,WADdvB,EAASO,QACnB9B,sBACbzJ,KAAKyJ,qBAAsB,EAC3B1G,EAAQ,uDAEhB,CAEA,uBAAMuH,CAAkB9B,EAAeJ,EAAmBuD,EAAiBpB,EAAmBC,GAE1F,IAAKxK,KAAKyL,oBAEN,MAAO,GAEX,IACI,MAAMiB,EAAU,GAChB,IAAIxE,EAAsB,GAE1B,IAAK,MAAMT,KAASe,EAEhB,GAAKf,GAA0B,iBAAVA,EAIrB,GAAIQ,EAAoBC,EAAcT,EAAOW,GAAY,CAErD,GAAIF,EAAanG,OAAS,EAAG,CACzBiB,EAAS,4BAA4BkF,EAAanG,iBAClD,MAAMiJ,QAAiBhL,KAAKiM,aAAa,GAAGjM,KAAK8J,+BAAgC,CAC7EiB,OAAQ,OACRG,QAAS,CACL,eAAgB,mBAChBgB,cAAiB,UAAUlM,KAAKwG,UAEpCJ,KAAMmC,EAAkB,CACpBH,YACAI,OAAQN,EACRwB,UAAWiC,EACXpB,SAAUA,EACVC,oBAAqBA,EACrB4B,WAAYtE,MAIpB,IAAKkD,EAASQ,GAAI,CACd,GAAwB,MAApBR,EAASxG,OAGT,OAFAxE,KAAKyJ,qBAAsB,EAEpBiD,EAAQC,OAEnB,MAAM,IAAIL,MAAM,0BAA0BtB,EAASuB,aACvD,CAEA,MAAMjB,QAAqBN,EAASO,QAGK,IAArCD,EAAa7B,sBACbzJ,KAAKyJ,qBAAsB,EAC3B1G,EAAQ,gEAGZ2J,EAAQvK,KAAKmJ,GACbpD,EAAe,EACnB,CAMAA,EAHoBU,EAAgBnB,EAAOW,EAI/C,MAEIF,EAAa/F,KAAKsF,GAK1B,GAAIS,EAAanG,OAAS,EAAG,CACzB,MAAM6K,QAAe5M,KAAK6M,oBACtB3E,EACAE,EACAuD,EACApB,EACAC,GAAuBtC,EAAa,IAAIsC,qBAExCoC,GACAF,EAAQvK,KAAKyK,EAErB,CAEA,OAAOF,EAAQC,MACnB,CAAE,MAAO7L,GAIL,MAHA+B,EAAS,wBAAyB/B,GAElCd,KAAK8M,eAAetE,EAAQJ,EAAWuD,EAAQpB,EAAUC,GACnD1J,CACV,CACJ,CAKQ,yBAAM+L,CACVE,EACA3E,EACAuD,EACApB,EACAC,GAEA,IAAIwC,EAAYjI,KAAKC,IAAIhF,KAAK6J,iBAAkBkD,EAAMhL,QAClDkL,EAAa,EAEjB,KAAOA,EAAaF,EAAMhL,QAAQ,CAC9B,MAUMmL,EAAa3E,EATH,CACZH,YACAI,OAHUuE,EAAM7F,MAAM+F,EAAYA,EAAaD,GAI/CtD,UAAWiC,EACXpB,SAAUA,EACVC,oBAAqBA,EACrB4B,WAAYtE,IAIV+C,GAAgB,IAAIxC,aAAcC,OAAO4E,GAAYnL,OAE3D,IACI,MAAMiJ,QAAiBhL,KAAKiM,aAAa,GAAGjM,KAAK8J,+BAAgC,CAC7EiB,OAAQ,OACRG,QAAS,CACL,eAAgB,mBAChBgB,cAAiB,UAAUlM,KAAKwG,UAEpCJ,KAAM8G,GACPrC,GAEH,IAAKG,EAASQ,GAAI,CACd,GAAwB,MAApBR,EAASxG,OAIT,OAHAxE,KAAKyJ,qBAAsB,EAE3BzJ,KAAK8M,eAAeC,EAAM7F,MAAM+F,GAAa7E,EAAWuD,EAAQpB,EAAUC,GACnE,KAGX,GAAwB,MAApBQ,EAASxG,OAAgB,CAEzB1B,EAAQ,uCAAuCkK,QAAgBjI,KAAKoI,IAAI,EAAGpI,KAAKuC,MAAM0F,EAAY,OAClGhN,KAAK6J,iBAAmB9E,KAAKoI,IAAI,EAAGpI,KAAKuC,MAAM0F,EAAY,IAC3DA,EAAYhN,KAAK6J,iBAEjB,QACJ,CAuBA,aApBM7J,KAAKgK,WAAWpG,iBAAiB,CACnCG,IAAK,GAAG/D,KAAK8J,+BACbiB,OAAQ,OACRG,QAAS,CACL,eAAgB,mBAChBgB,cAAiB,UAAUlM,KAAKwG,UAEpCJ,KAAM8G,EACNrC,cAAeA,EACfvG,SAAW0G,IACqB,MAAxBA,EAASzG,YAAsByG,EAASO,OACE,IAAtCP,EAASO,KAAK9B,sBACdzJ,KAAKyJ,qBAAsB,MAO3CzJ,KAAK8M,eAAeC,EAAM7F,MAAM+F,GAAa7E,EAAWuD,EAAQpB,EAAUC,GACnE,IACX,CAEA,MAAMc,QAAqBN,EAASO,OAWpC,IARyC,IAArCD,EAAa7B,sBACbzJ,KAAKyJ,qBAAsB,EAC3B1G,EAAQ,gEAGZkK,GAAcD,EAGVC,GAAcF,EAAMhL,OACpB,OAAOuJ,CAEf,CAAE,MAAOxK,GAuBL,OArBAgC,EAAQ,sDAAuDhC,SACzDd,KAAKgK,WAAWpG,iBAAiB,CACnCG,IAAK,GAAG/D,KAAK8J,+BACbiB,OAAQ,OACRG,QAAS,CACL,eAAgB,mBAChBgB,cAAiB,UAAUlM,KAAKwG,UAEpCJ,KAAM8G,EACNrC,cAAeA,EACfvG,SAAW0G,IACqB,MAAxBA,EAASzG,YAAsByG,EAASO,OACE,IAAtCP,EAASO,KAAK9B,sBACdzJ,KAAKyJ,qBAAsB,MAO3CzJ,KAAK8M,eAAeC,EAAM7F,MAAM+F,GAAa7E,EAAWuD,EAAQpB,EAAUC,GACnE,IACX,CACJ,CAEA,OAAO,IACX,CAKQ,cAAAsC,CACJtE,EACAJ,EACAuD,EACApB,EACAC,GAEsB,IAAlBhC,EAAOzG,QAIX/B,KAAK+J,YAAYvC,WAAW,CACxBY,YACAI,SACAkB,UAAWiC,EACXpB,WACAC,sBACAvI,UAAWrB,KAAKsB,OAExB,CAEA,kBAAMkL,CAAazB,EAAgB0B,EAA+BjF,GAC9D,IACI,MAAMkF,EAAU,CACZ3B,OAAQA,EACR4B,eAAgBF,EAChBjF,UAAWA,EACXoF,YAAaH,EAASI,OAASJ,EAASlG,MAAQ,MAGpDnE,EAAS,+BAAgCsK,GAEzC,MAAMtC,QAAiBhL,KAAKiM,aAAa,GAAGjM,KAAK8J,6BAA8B,CAC3EiB,OAAQ,OACRG,QAAS,CACL,eAAgB,mBAChBgB,cAAiB,UAAUlM,KAAKwG,UAEpCJ,KAAMmC,EAAkB+E,KAG5B,IAAKtC,EAASQ,GACV,MAAM,IAAIc,MAAM,6BAA6BtB,EAASuB,4BAA4BvM,KAAKwG,UAG3F,MAAMoG,QAAe5B,EAASO,OAE9B,OADAvI,EAAS,mBAAoB4J,GACtBA,CACX,CAAE,MAAO9L,GAEL,MADA+B,EAAS,2BAA4B/B,GAC/BA,CACV,CACJ,CAEA,kBAAM4M,CAAa/B,EAAgB0B,EAA+BjF,EAAmBuF,GACjF,IACI,MAAM3C,QAAiBhL,KAAKiM,aAAa,GAAGjM,KAAK8J,kCAAmC,CAChFiB,OAAQ,OACRG,QAAS,CACL,eAAgB,mBAChBgB,cAAiB,UAAUlM,KAAKwG,UAEpCJ,KAAMmC,EAAkB,CACpBoD,OAAQA,EACR4B,eAAgBF,EAChBjF,UAAWA,EACXuF,WAAYA,MAIpB,IAAK3C,EAASQ,GACV,MAAM,IAAIc,MAAM,gCAAgCtB,EAASuB,4BAA4BvM,KAAKwG,UAG9F,aAAawE,EAASO,MAC1B,CAAE,MAAOzK,GAEL,MADA+B,EAAS,6BAA8B/B,GACjCA,CACV,CACJ,CAEO,gBAAA8M,CAAiBpF,EAAeJ,EAAmBuD,EAAiBpB,EAAmBC,GAI1F,MAAM8C,EAAU,CACZlF,UAAWA,EACXI,OAAQA,EACRkB,UAAWiC,GAAU,KACrBpB,SAAUA,EACVC,oBAAqBA,EACrB4B,WAAYtE,EACZtB,OAAQxG,KAAKwG,QAIPqH,EAAO,IAAIxH,KAAK,CAACkC,EAAkB+E,IAAW,CACpDhH,KAAM,qBAQV,OALgB9C,UAAU2C,WACtB,GAAGnG,KAAK8J,+BACR+D,EAIR,CAEA,qBAAMC,CAAgB1F,EAAmB2F,EAAmBC,EAAuCtE,GAC/F3G,EAAQ,6BAA8B,CAAEqF,YAAW2F,YAAWC,kBAAiBtE,cAC/E,IACI,MAAMsB,QAAiBhL,KAAKiM,aAAa,GAAGjM,KAAK8J,oCAAqC,CAClFiB,OAAQ,OACRG,QAAS,CACL,eAAgB,mBAChBgB,cAAiB,UAAUlM,KAAKwG,UAEpCJ,KAAMmC,EAAkB,CACpBH,UAAWA,EACX2F,UAAWA,EACXC,gBAAiBA,GAAmB,CAAA,EACpCtE,UAAWA,GAAa,SAMhC,GAFA3G,EAAQ,8BAA+B,CAAEyB,OAAQwG,EAASxG,OAAQ+H,WAAYvB,EAASuB,cAElFvB,EAASQ,GAAI,CACd,MAAMa,QAAkBrB,EAASvG,OAEjC,MADA5B,EAAS,oCAAqC,CAAE2B,OAAQwG,EAASxG,OAAQ+H,WAAYvB,EAASuB,WAAYF,cACpG,IAAIC,MAAM,gCAAgCtB,EAASxG,UAAUwG,EAASuB,gBAAgBF,IAChG,CAEA,MAAMd,QAAaP,EAASO,OAE5B,OADAvI,EAAS,6BAA8BuI,GAChCA,CACX,CAAE,MAAOzK,GAEL,MADA+B,EAAS,mCAAoC/B,EAAO,CAAEsH,YAAW2F,YAAWC,oBACtElN,CACV,CACJ,CAEA,0BAAMmN,CAAqB7F,EAAmBI,EAA6EkB,GACvH,IACI,MAAMsB,QAAiBhL,KAAKiM,aAAa,GAAGjM,KAAK8J,0CAA2C,CACxFiB,OAAQ,OACRG,QAAS,CACL,eAAgB,mBAChBgB,cAAiB,UAAUlM,KAAKwG,UAEpCJ,KAAMmC,EAAkB,CACpBH,UAAWA,EACXI,OAAQA,EACRkB,UAAWA,GAAa,SAIhC,IAAKsB,EAASQ,GACV,MAAM,IAAIc,MAAM,sCAAsCtB,EAASuB,cAGnE,aAAavB,EAASO,MAC1B,CAAE,MAAOzK,GAEL,MADA+B,EAAS,oCAAqC/B,GACxCA,CACV,CACJ,CAKA,aAAMoN,CAAQC,GASV,IAGI,GAFAnL,EAAS,+BAAgC,CAAE/C,MAAOkO,EAAQlO,MAAOS,QAASyN,EAAQzN,QAAQ0N,UAAU,EAAG,IAAKhG,UAAW+F,EAAQ/F,aAE1HpI,KAAK8J,QACN,OAGJ,IAAKqE,EAAQ/F,UACT,OAIJ,MAAM4C,QAAiBC,MAAM,GAAGjL,KAAK8J,6BAA8B,CAC/DiB,OAAQ,OACRG,QAAS,CACL,eAAgB,mBAChBgB,cAAiB,UAAUlM,KAAKwG,UAEpCJ,KAAMmC,EAAkB4F,KAGvBnD,EAASQ,GAGVxI,EAAS,+BAFTF,EAAQ,sCAAuCkI,EAASxG,OAAQwG,EAASuB,WAIjF,CAAE,MAAOzL,GAELgC,EAAQ,sCAAuChC,EACnD,CACJ,CAKA,sBAAMuN,CAAiBC,GAmBnB,IAGI,GAFAtL,EAAS,yCAA0C,CAAEuL,UAAWD,EAAUC,UAAWxK,IAAKuK,EAAUvK,IAAIqK,UAAU,EAAG,IAAKhG,UAAWkG,EAAUlG,aAE1IpI,KAAK8J,QACN,OAGJ,IAAKwE,EAAUlG,UACX,OAIJ,MAAM4C,QAAiBC,MAAM,GAAGjL,KAAK8J,gCAAiC,CAClEiB,OAAQ,OACRG,QAAS,CACL,eAAgB,mBAChBgB,cAAiB,UAAUlM,KAAKwG,UAEpCJ,KAAMmC,EAAkB+F,KAGvBtD,EAASQ,GAGVxI,EAAS,yCAFTF,EAAQ,gDAAiDkI,EAASxG,OAAQwG,EAASuB,WAI3F,CAAE,MAAOzL,GAELgC,EAAQ,gDAAiDhC,EAC7D,CACJ,CAMQ,kBAAMmL,CAAalI,EAAaF,EAAsBgH,GAC1D,MAAM2D,EAAmB5N,KAAKsB,MACxBuM,EAAYC,IAGZC,EAAqB3O,KAAK4O,0BAA0B7K,GAG1D,GAAI/D,KAAK2J,YAAiC,SAAnB9F,EAAQkH,QAA0C,oBAAdvH,WAA6D,mBAAzBA,UAAU2C,WACrG,OAAOnG,KAAK6O,+BAA+B9K,EAAKF,EAAS8K,GAG7D,IACI,MAAMlE,EAAwC,oBAApBC,gBAAkC,IAAIA,gBAAoB,KACpF,IAAIC,EAAkD,KAElDF,IACAE,EAAYhF,WAAW,KACnB8E,EAAYG,SACb5K,KAAK4J,iBAGZ,MAAMkB,EAAkC,SAAnBjH,EAAQkH,aAAuC/I,IAAlB6I,GAA+BA,EAAgB7C,EAE3FgD,QAAiBC,MAAMlH,EAAK,IAC3BF,EACHsH,OAAQV,GAAYU,OACpBC,UAAWN,IAGXH,GACAjF,aAAaiF,GAEjB,MAAMmE,EAAkBlO,KAAKsB,MAAQsM,EA2BrC,OAxBKxD,EAASQ,IAAOmD,SACX3O,KAAKqO,iBAAiB,CACxBI,YACA1K,MACAgH,OAAQlH,EAAQkH,QAAU,MAC1BvG,OAAQwG,EAASxG,OACjB+H,WAAYvB,EAASuB,WACrBwC,SAAUD,EACVE,YAAapO,KAAKsB,MAClBkG,UAAWpI,KAAKoI,UAChBsB,UAAW1J,KAAK0J,UAChB6E,UAAWvO,KAAKiP,kBAAkBjE,EAASxG,QAC3C0K,aAAclE,EAASuB,WAEvB4C,YAAaX,EACbY,SAAU,GAAGvL,EAAQkH,QAAU,SAAShH,IACxCsL,WAAY,QACZC,WAAY,CACR,mBAAoBtE,EAASxG,OAC7B,mBAAoBwG,EAASuB,cAElCvG,MAAM,QAGNgF,CACX,CAAE,MAAOlK,GACL,MAAMgO,EAAkBlO,KAAKsB,MAAQsM,EAGrC,GAAmB,eAAf1N,EAAMqG,KAAuB,CAC7B,MAAMoI,EAAoB,IAAIjD,MAAM,mBACpCiD,EAAapI,KAAO,eACpBrG,EAAQyO,CACZ,CAKA,GAFuBvP,KAAKwP,eAAe1O,IAEF,SAAnB+C,EAAQkH,QAA0C,oBAAdvH,WAA6D,mBAAzBA,UAAU2C,WAMpG,OAJAnG,KAAK2J,YAAa,EAClB7G,EAAQ,gFAGD9C,KAAK6O,+BAA+B9K,EAAKF,EAAS8K,GA6B7D,MAzBKA,SACK3O,KAAKqO,iBAAiB,CACxBI,YACA1K,MACAgH,OAAQlH,EAAQkH,QAAU,MAC1BvG,OAAQ,KACR+H,WAAY,KACZwC,SAAUD,EACVE,YAAapO,KAAKsB,MAClBkG,UAAWpI,KAAKoI,UAChBsB,UAAW1J,KAAK0J,UAChB6E,UAAWvO,KAAKyP,qBAAqB3O,GACrCoO,aAAcpO,EAAMJ,QACpBgP,UAAW5O,EAAMqG,KAEjBgI,YAAaX,EACbY,SAAU,GAAGvL,EAAQkH,QAAU,SAAShH,IACxCsL,WAAY,QACZC,WAAY,CACR,aAAcxO,EAAMqG,KACpB,gBAAiBrG,EAAMJ,WAE5BsF,MAAM,QAGPlF,CACV,CACJ,CAOQ,oCAAM+N,CAA+B9K,EAAaF,EAAsB8K,GAC5E,IAEI,IAAIgB,EAAgB,KAChBzC,EAAqB,GAEzB,GAAIrJ,EAAQuC,KACR,GAA4B,iBAAjBvC,EAAQuC,KACf,IACIuJ,EAAWjO,KAAKC,MAAMkC,EAAQuC,MAC9B8G,EAAarJ,EAAQuC,IACzB,CAAE,MAEE8G,EAAarJ,EAAQuC,IACzB,KACG,IAAIvC,EAAQuC,gBAAgBC,KAAM,CAGrCvD,EAAQ,8EACRiB,EAAM,GAAGA,IAAMA,EAAIuF,SAAS,KAAO,IAAM,aAAasG,mBAAmB5P,KAAKwG,UAE9E,OADgBhD,UAAU2C,WAAWpC,EAAKF,EAAQuC,MACjC,IAAIyJ,SAAS,KAAM,CAAErL,OAAQ,IAAK+H,WAAY,KAAMrB,QAAS,IAAI4E,UACjE,IAAID,SAAS,KAAM,CAAErL,OAAQ,IAAK+H,WAAY,oBAAqBrB,QAAS,IAAI4E,SACrG,CAEI5C,EAAa3E,EAAkB1E,EAAQuC,MACvCuJ,EAAW9L,EAAQuC,IACvB,CAKJ,GAAIrC,EAAIuF,SAAS,mBACb,GAAIqG,GAAgC,iBAAbA,EAEnBA,EAASnJ,OAASxG,KAAKwG,OACvB0G,EAAa3E,EAAkBoH,QAC5B,GAAIzC,EAEP,IACI,MAAM6C,EAASrO,KAAKC,MAAMuL,GAC1B6C,EAAOvJ,OAASxG,KAAKwG,OACrB0G,EAAa3E,EAAkBwH,EACnC,CAAE,MAEEhM,EAAM,GAAGA,IAAMA,EAAIuF,SAAS,KAAO,IAAM,aAAasG,mBAAmB5P,KAAKwG,SAClF,MAGAzC,EAAM,GAAGA,IAAMA,EAAIuF,SAAS,KAAO,IAAM,aAAasG,mBAAmB5P,KAAKwG,eAIlFzC,EAAM,GAAGA,IAAMA,EAAIuF,SAAS,KAAO,IAAM,aAAasG,mBAAmB5P,KAAKwG,UAIlF,MAAMqH,EAAOX,EACP,IAAI7G,KAAK,CAAC6G,GAAa,CAAE5G,KAAM,qBAC/B,KAKN,OAFgB9C,UAAU2C,WAAWpC,EAAK8J,IAGtC7K,EAAS,iEAEF,IAAI6M,SAAS,KAAM,CACtBrL,OAAQ,IACR+H,WAAY,KACZrB,QAAS,IAAI4E,YAGjBhN,EAAQ,+DAED,IAAI+M,SAAS,KAAM,CACtBrL,OAAQ,IACR+H,WAAY,8BACZrB,QAAS,IAAI4E,UAGzB,CAAE,MAAOhP,GAGL,OAFA+B,EAAS,oCAAqC/B,GAEvC,IAAI+O,SAAS,KAAM,CACtBrL,OAAQ,IACR+H,WAAY,gCACZrB,QAAS,IAAI4E,SAErB,CACJ,CAKQ,cAAAN,CAAe1O,GACnB,MAAMoO,GAAgBpO,GAAOJ,SAAW,IAAIsP,cAQ5C,MACmB,eARAlP,GAAOqG,MAAQ,IAAI6I,eAQJd,EAAa5F,SAAS,oBACpD4F,EAAa5F,SAAS,4BACtB4F,EAAa5F,SAAS,QACtB4F,EAAa5F,SAAS,aAErB4F,EAAa5F,SAAS,uBAAyB4F,EAAa5F,SAAS,WAE9E,CAKQ,yBAAAsF,CAA0B7K,GAE9B,IAAKA,IAAQ/D,KAAK8J,QACd,OAAO,EAGX,IACI,MAAMmG,EAAS,IAAIjM,IAAID,GACjBmM,EAAa,IAAIlM,IAAIhE,KAAK8J,SAGhC,QAAImG,EAAOE,SAAWD,EAAWC,SAEzBF,EAAOhH,SAASmH,WAAW,uBAM/BrM,EAAIuF,SAAStJ,KAAK8J,QAK1B,CAAE,MAAOhJ,GAEL,OAAOiD,EAAIuF,SAAStJ,KAAK8J,QAC7B,CACJ,CAEQ,iBAAAmF,CAAkBzK,GACtB,OAAIA,GAAU,KAAOA,EAAS,IACnB,eAEPA,GAAU,IACH,eAEJ,eACX,CAEQ,oBAAAiL,CAAqB3O,GACzB,MAAMoO,EAAepO,EAAMJ,SAAW,GAChCgP,EAAY5O,EAAMqG,MAAQ,GAGhC,OAAInH,KAAKwP,eAAe1O,GACb,gBAKPoO,EAAa5F,SAAS,0BACtB4F,EAAa5F,SAAS,4BACtB4F,EAAa5F,SAAS,kBACtB4F,EAAa5F,SAAS,+BACtB4F,EAAa5F,SAAS,iCACP,cAAdoG,GAA6BR,EAAa5F,SAAS,qBAClD4F,EAAa5F,SAAS,YAAc4F,EAAa5F,SAAS,gBACrD,oBAEP4F,EAAa5F,SAAS,SAAW4F,EAAa5F,SAAS,kBAChD,aAEP4F,EAAa5F,SAAS,YAA4B,iBAAdoG,EAC7B,gBAEPR,EAAa5F,SAAS,oBAAsB4F,EAAa5F,SAAS,gBAC3D,gBAEJ,eACX,QChmCS+G,EAUT,WAAAvQ,CAAY+D,GASR,GAlBI7D,KAAAsQ,aAAuB,aACvBtQ,KAAAuQ,iBAAgC,IAAIC,IACpCxQ,KAAAyQ,eAA8B,IAAID,IAClCxQ,KAAA0Q,cAAsD,gBACtD1Q,KAAA2Q,iBAA6B,CACjC,0BACA,6BAII9M,GAASyM,eACTtQ,KAAKsQ,aAAezM,EAAQyM,cAE5BzM,GAAS8M,mBACT3Q,KAAK2Q,iBAAmB,IAAI3Q,KAAK2Q,oBAAqB9M,EAAQ8M,mBAI9D9M,GAAS+M,kBAIT,GAHA5Q,KAAK0Q,cAAgB7M,EAAQ+M,kBAAkBC,KAGpB,kBAAvB7Q,KAAK0Q,cAED7M,EAAQ+M,kBAAkBE,gBAC1B9Q,KAAK+Q,oBAAoBlN,EAAQ+M,kBAAkBE,oBAEpD,CAIH,MAAME,EAAe,CAAC,yBAA0B,2BAC1CC,EAAiBpN,EAAQ+M,kBAAkBM,cAAgBrN,EAAQ+M,kBAAkBM,aAAanP,OAAS,EAC3G8B,EAAQ+M,kBAAkBM,aAC1BF,EACNhR,KAAKmR,kBAAkBF,EAC3B,CAIApN,GAASuN,oBACTpR,KAAK+Q,oBAAoBlN,EAAQuN,oBAIjCvN,GAASwN,YACTrR,KAAK+Q,oBAAoBlN,EAAQwN,WAEzC,CAMO,iBAAAF,CAAkBG,GACrBtR,KAAKyQ,eAAec,QAWpB,CAPI,yBACA,2BACA,oBACA,yBAImBD,GAAQvI,QAAQyI,IACnCxR,KAAKyQ,eAAegB,IAAID,KAGxBxR,KAAKyQ,eAAeiB,KAAO,EAC3B1O,EAAS,yBAAyBhD,KAAKyQ,eAAeiB,iBAAkB5K,MAAM6K,KAAK3R,KAAKyQ,iBAExFzN,EAAS,kCAGbhD,KAAK4R,uBACT,CAMO,mBAAAb,CAAoBO,GACvBtR,KAAKuQ,iBAAiBgB,QAGtB,MAAMM,EAAcP,EAAOxL,OAAO0L,IACNxR,KAAK8R,mBAAmBN,KAE5C1O,EAAQ,mCAAmC0O,6CACpC,IAKfK,EAAY9I,QAAQyI,GAASxR,KAAKuQ,iBAAiBkB,IAAID,IAEnDK,EAAY9P,OAAS,EACrBiB,EAAS,2BAA2B6O,EAAY9P,mBAAoB8P,GAEpE7O,EAAS,4CAGbhD,KAAK+R,yBACT,CAMO,YAAAb,CAAaI,GAChBA,EAAOvI,QAAQyI,IACXxR,KAAKuQ,iBAAiByB,OAAOR,KAG7BxR,KAAKuQ,iBAAiBmB,KAAO,EAC7B1O,EAAS,wBAAwBsO,EAAOvP,oBAAoB/B,KAAKuQ,iBAAiBmB,kBAAmB5K,MAAM6K,KAAK3R,KAAKuQ,mBAErHvN,EAAS,oCAGbhD,KAAK+R,yBACT,CAKO,qBAAAE,GACHjS,KAAKuQ,iBAAiBgB,QACtBvO,EAAS,wDAEThD,KAAKkS,0BACT,CAKO,mBAAAC,GACH,OAAOnS,KAAKuQ,iBAAiBmB,KAAO,CACxC,CAKO,gBAAAU,GACH,OAAOpS,KAAK0Q,aAChB,CAKO,mBAAA2B,GACH,OAAOvL,MAAM6K,KAAK3R,KAAKuQ,iBAC3B,CAMO,mBAAA+B,GACH,MAA2B,kBAAvBtS,KAAK0Q,cAE8B,IAA/B1Q,KAAKuQ,iBAAiBmB,KACf,KAEJ5K,MAAM6K,KAAK3R,KAAKuQ,kBAAkBgC,KAAK,KAGb,IAA7BvS,KAAKyQ,eAAeiB,KACb,KAEJ5K,MAAM6K,KAAK3R,KAAKyQ,gBAAgB8B,KAAK,IAEpD,CAMO,qBAAAX,GAC8B,IAA7B5R,KAAKyQ,eAAeiB,OAKA,oBAAb1F,UAAoD,YAAxBA,SAASwG,WAQhDxS,KAAKyQ,eAAe1H,QAAQ0J,IACxB,IACI,MAAMC,EAAW1G,SAAS2G,iBAAiBF,GAC3CC,EAAS3J,QAAQ6J,IACTA,GAAWA,EAAQC,WACnBD,EAAQC,UAAUpB,IAAI,aAG9BzO,EAAS,0BAA0B0P,EAAS3Q,mCAAmC0Q,IACnF,CAAE,MAAOlQ,GACLO,EAAQ,qBAAqB2P,IACjC,IAlBAzP,EAAS,wDAoBjB,CAMO,uBAAA+O,GACgC,IAA/B/R,KAAKuQ,iBAAiBmB,OAKF,oBAAb1F,UAAoD,YAAxBA,SAASwG,WAMhDxS,KAAKuQ,iBAAiBxH,QAAQ0J,IAC1B,IACI,MAAMC,EAAW1G,SAAS2G,iBAAiBF,GAC3CC,EAAS3J,QAAQ6J,IACTA,GAAWA,EAAQC,WACnBD,EAAQC,UAAUC,OAAO,aAGjC9P,EAAS,8BAA8B0P,EAAS3Q,mCAAmC0Q,IACvF,CAAE,MAAOlQ,GACLO,EAAQ,qBAAqB2P,IACjC,IAhBAzP,EAAS,0DAkBjB,CAKO,wBAAAkP,GAEHlP,EAAS,8BACb,CAKQ,kBAAA8O,CAAmBW,GAQvB,MAPyB,CACrB,yBACA,2BACA,oBACA,uBAGoBM,KAAKC,GACzBP,EAASzC,cAAc1G,SAAS0J,EAAQhD,cAAciD,QAAQ,UAAW,KAEjF,CAKO,gBAAAC,CAAiBN,GACpB,GAAIA,aAAmBO,kBAAoBP,aAAmBQ,oBAC1D,OAAOR,EAAQjK,KAGvB,CAKO,mBAAA0K,CAAoBT,GACvB,OAAO5S,KAAKsT,sBAAsBV,EACtC,CAKO,qBAAAU,CAAsBV,GACzB,GAA2B,kBAAvB5S,KAAK0Q,cAAmC,CAExC,GAAmC,IAA/B1Q,KAAKuQ,iBAAiBmB,KACtB,OAAO,EAIX,IAAK,MAAMe,KAAYzS,KAAKuQ,iBACxB,IACI,GAAIqC,EAAQW,QAAQd,GAChB,OAAO,CAEf,CAAE,MAAOlQ,GACLO,EAAQ,qBAAqB2P,IACjC,CAEJ,OAAO,CACX,CAEI,GAAiC,IAA7BzS,KAAKyQ,eAAeiB,KACpB,OAAO,EAIX,IAAK,MAAMe,KAAYzS,KAAKyQ,eACxB,IACI,GAAImC,EAAQW,QAAQd,GAChB,OAAO,CAEf,CAAE,MAAOlQ,GACLO,EAAQ,qBAAqB2P,IACjC,CAEJ,OAAO,CAEf,EAI4B,IAAIpC,ECpVpC,MAAMhQ,EAA8B,oBAAXC,OAyCzB,SAASkT,IACL,IAAKnT,EAAW,MAAO,UAEvB,MAAMoT,EAAYjQ,UAAUiQ,UAAUzD,cAChC0D,EAAcpT,OAAOqT,OAAOC,MAC5BC,EAAevT,OAAOqT,OAAOG,OAGnC,MAAI,4DAA4DC,KAAKN,GAC7D,QAAQM,KAAKN,IAAeC,GAAe,KAAOG,GAAgB,KAC3D,SAEJ,SAIP,2BAA2BE,KAAKN,GACzB,UAGJ,SACX,CA0IA,SAASO,EAAcjQ,GACnB,IAEI,OADe,IAAIC,IAAID,GACTkQ,QAClB,CAAE,MACE,MAAO,EACX,CACJ,UAKgBC,IACZ,IAAK7T,EACD,MAAO,CACH8T,YAAa,UACbC,QAAS,UACTC,gBAAiB,UACjBC,GAAI,UACJC,WAAY,UACZC,kBAAmB,UACnBC,cAAe,UACfC,YAAa,EACbC,SAAU,UACVC,SAAU,UACVC,UAAW,IAInB,MAAMT,QAAEA,EAAOC,gBAAEA,GAlKrB,WACI,IAAKhU,EAAW,MAAO,CAAE+T,QAAS,UAAWC,gBAAiB,WAE9D,MAAMZ,EAAYjQ,UAAUiQ,UAG5B,GAAI,UAAUM,KAAKN,KAAe,QAAQM,KAAKN,GAAY,CACvD,MAAMqB,EAAQrB,EAAUqB,MAAM,kBAC9B,MAAO,CACHV,QAAS,SACTC,gBAAiBS,EAAQA,EAAM,GAAK,UAE5C,CAGA,GAAI,WAAWf,KAAKN,GAAY,CAC5B,MAAMqB,EAAQrB,EAAUqB,MAAM,mBAC9B,MAAO,CACHV,QAAS,UACTC,gBAAiBS,EAAQA,EAAM,GAAK,UAE5C,CAGA,GAAI,UAAUf,KAAKN,KAAe,UAAUM,KAAKN,GAAY,CACzD,MAAMqB,EAAQrB,EAAUqB,MAAM,mBAC9B,MAAO,CACHV,QAAS,SACTC,gBAAiBS,EAAQA,EAAM,GAAK,UAE5C,CAGA,GAAI,QAAQf,KAAKN,GAAY,CACzB,MAAMqB,EAAQrB,EAAUqB,MAAM,gBAC9B,MAAO,CACHV,QAAS,OACTC,gBAAiBS,EAAQA,EAAM,GAAK,UAE5C,CAGA,GAAI,gBAAgBf,KAAKN,GAAY,CACjC,MAAMqB,EAAQrB,EAAUqB,MAAM,gBAAkBrB,EAAUqB,MAAM,aAChE,MAAO,CACHV,QAAS,KACTC,gBAAiBS,EAAQA,EAAM,GAAK,UAE5C,CAEA,MAAO,CAAEV,QAAS,UAAWC,gBAAiB,UAClD,CA+GyCU,IAC/BT,GAAEA,EAAEC,WAAEA,GA3GhB,WACI,IAAKlU,EAAW,MAAO,CAAEiU,GAAI,UAAWC,WAAY,WAEpD,MAAMd,EAAYjQ,UAAUiQ,UAG5B,GAAI,WAAWM,KAAKN,GAAY,CAC5B,MAAMqB,EAAQrB,EAAUqB,MAAM,0BAC9B,IAAIE,EAAU,UACd,GAAIF,EAAO,CACP,MAAMG,EAAaC,WAAWJ,EAAM,IACXE,EAAN,KAAfC,EAA+B,KACX,MAAfA,EAA8B,MACf,MAAfA,EAA8B,IACf,MAAfA,EAA8B,IACxBH,EAAM,EACzB,CACA,MAAO,CAAER,GAAI,UAAWC,WAAYS,EACxC,CAGA,GAAI,sBAAsBjB,KAAKN,GAAY,CACvC,MAAMqB,EAAQrB,EAAUqB,MAAM,0BAC9B,MAAO,CACHR,GAAI,QACJC,WAAYO,EAAQA,EAAM,GAAG7B,QAAQ,IAAK,KAAO,UAEzD,CAGA,GAAI,oBAAoBc,KAAKN,GAAY,CACrC,MAAMqB,EAAQrB,EAAUqB,MAAM,oBAC9B,MAAO,CACHR,GAAI,MACJC,WAAYO,EAAQA,EAAM,GAAG7B,QAAQ,IAAK,KAAO,UAEzD,CAGA,GAAI,WAAWc,KAAKN,GAAY,CAC5B,MAAMqB,EAAQrB,EAAUqB,MAAM,uBAC9B,MAAO,CACHR,GAAI,UACJC,WAAYO,EAAQA,EAAM,GAAK,UAEvC,CAGA,MAAI,SAASf,KAAKN,GACP,CAAEa,GAAI,QAASC,WAAY,WAG/B,CAAED,GAAI,UAAWC,WAAY,UACxC,CAsD+BY,GAE3B,MAAO,CACHhB,YAAaX,IACbY,UACAC,kBACAC,KACAC,aACAC,kBAAmB,GAAGlU,OAAOqT,OAAOC,SAAStT,OAAOqT,OAAOG,SAC3DW,cAAe,GAAGnU,OAAO8U,cAAc9U,OAAO+U,cAC9CX,YAAapU,OAAOqT,OAAO2B,WAC3BX,SAAUY,KAAKC,iBAAiBC,kBAAkBC,SAClDd,SAAUpR,UAAUoR,SACpBC,UAAW,IAAKrR,UAAUqR,WAAa,CAACrR,UAAUoR,WAClDe,eAAgBnS,UAAUiQ,UAElC,UAKgBmC,IACZ,IAAKvV,EACD,MAAO,CACHwV,YAAa,GACb5M,SAAU,GACV6M,OAAQ,GACRC,KAAM,GACNC,MAAO,GACPnK,SAAU,GACVoK,gBAAiB,GACjBC,iBAAkB,GAClBC,wBAAyB,IAIjC,MAAMC,EAAa9V,OAAOwL,SAASC,KAC7BF,EAAWG,SAASH,SACpBwK,EAvFV,SAA0BtS,GACtB,MAAMkM,EAAS,IAAIjM,IAAID,GACjBsS,EAAoC,CAAA,EAW1C,MATgB,CAAC,aAAc,aAAc,eAAgB,WAAY,eAEjEtN,QAAQM,IACZ,MAAMV,EAAQsH,EAAOhM,aAAaqS,IAAIjN,GAClCV,IACA0N,EAAUhN,GAAOV,KAIlB0N,CACX,CAyEsBE,CAAiBH,GAEnC,MAAO,CACHP,YAAaO,EACbnN,SAAU3I,OAAOwL,SAAS7C,SAC1B6M,OAAQxV,OAAOwL,SAASgK,OACxBC,KAAMzV,OAAOwL,SAASiK,KACtBC,MAAOhK,SAASgK,MAChBnK,WACAoK,gBAAiBjC,EAAcnI,GAC/BqK,iBAAkBrK,EAClBsK,wBAAyBnC,EAAcnI,GACvC2K,aAAclW,OAAOwL,SAASmI,YAC3BoC,EAEX,UAKgBI,IACZ,MAAO,IACAvC,OACA0B,IAEX,UAKgBc,IACZ,IAAKrW,EAAW,MAAO,CAAA,EAEvB,MAAMsW,EAAef,IAErB,MAAO,CACHM,iBAAkBS,EAAaT,iBAC/BC,wBAAyBQ,EAAaR,wBACtCS,YAAaD,EAAad,YAC1BgB,iBAAkBF,EAAa1N,SAC/B6N,mBAAoBH,EAAaI,WACjCC,mBAAoBL,EAAaM,WACjCC,qBAAsBP,EAAaQ,aACnCC,iBAAkBT,EAAaU,SAC/BC,oBAAqBX,EAAaY,YAE1C,UAKgBC,IACZ,IAAKnX,EAAW,MAAO,CAAA,EAEvB,MAAMsW,EAAef,IAErB,MAAO,CACHC,YAAac,EAAad,YAC1B5M,SAAU0N,EAAa1N,SACvB6M,OAAQa,EAAab,OACrBC,KAAMY,EAAaZ,KACnBC,MAAOW,EAAaX,MACpBnK,SAAU8K,EAAa9K,SACvBoK,gBAAiBU,EAAaV,gBAC9Bc,WAAYJ,EAAaI,WACzBE,WAAYN,EAAaM,WACzBE,aAAcR,EAAaQ,aAC3BE,SAAUV,EAAaU,SACvBE,YAAaZ,EAAaY,YAElC,OCtUaE,EAQT,WAAA3X,CAAYC,EAAgC,IALpCC,KAAA0X,kBAAgC,CAAA,EAChC1X,KAAA2X,eAA6B,CAAA,EAC7B3X,KAAA4X,kBAAgC,CAAA,EAChC5X,KAAA6X,eAAyB,EAG7B7X,KAAKD,OAAS,CACV+X,2BAA2B,EAC3BC,yBAAyB,EACzBC,sBAAsB,EACtBC,iBAAkB,MACflY,GAGPC,KAAKwK,oBAAsBiM,IAC3BzW,KAAKkY,YACT,CAKQ,UAAAA,GACAlY,KAAK6X,gBAGT7X,KAAK4X,kBAAoBlB,IAGzB1W,KAAKmY,wBAELnY,KAAK6X,eAAgB,EACzB,CAKO,kBAAAO,CAAmBpK,EAA8B,IACpD,MAAMqK,EAAyB,IAAKrK,GA0BpC,OAvBIhO,KAAKD,OAAO+X,2BACZ5O,OAAOoP,OAAOD,EAAYrY,KAAKyW,0BAI/BzW,KAAKD,OAAOgY,yBACZ7O,OAAOoP,OAAOD,EAAYrY,KAAK0X,mBAI/B1X,KAAKD,OAAOiY,sBACZ9O,OAAOoP,OAAOD,EAAYrY,KAAK2X,gBAI9B3X,KAAK0X,kBAAgD,+BACtDxO,OAAOoP,OAAOD,EAAYrY,KAAK4X,mBAC/B5X,KAAKuY,mBAAmB,gCAAgC,IAI5DvY,KAAKwY,cAAcH,GAEZA,CACX,CAKO,sBAAA5B,GACH,MAAO,IACAzW,KAAKwK,uBACLgN,IAEX,CAKO,+BAAAiB,CAAgCC,EAAuC,IAC1E,MAAO,IACA1Y,KAAKwK,uBACLgN,OACAkB,EAEX,CAKO,kBAAAH,CAAmBlP,EAAaV,GACnC3I,KAAK0X,kBAAkBrO,GAAOV,EAC9B3I,KAAK2Y,uBACT,CAKO,oBAAAC,CAAqBP,GACxBnP,OAAOoP,OAAOtY,KAAK0X,kBAAmBW,GACtCrY,KAAK2Y,uBACT,CAKO,kBAAAE,CAAmBxP,GACtB,OAAOrJ,KAAK0X,kBAAkBrO,EAClC,CAKO,qBAAAyP,CAAsBzP,UAClBrJ,KAAK0X,kBAAkBrO,GAC9BrJ,KAAK2Y,uBACT,CAKO,eAAAI,CAAgB1P,EAAaV,GAChC3I,KAAK2X,eAAetO,GAAOV,CAC/B,CAKO,iBAAAqQ,CAAkBX,GACrBnP,OAAOoP,OAAOtY,KAAK2X,eAAgBU,EACvC,CAKO,eAAAY,CAAgB5P,GACnB,OAAOrJ,KAAK2X,eAAetO,EAC/B,CAKO,kBAAA6P,CAAmB7P,UACfrJ,KAAK2X,eAAetO,EAC/B,CAKO,OAAA8P,CAAQ9P,EAAaV,EAAYyQ,EAA4B,QAClD,YAAVA,EACM/P,KAAOrJ,KAAK0X,mBACd1X,KAAKuY,mBAAmBlP,EAAKV,GAG3BU,KAAOrJ,KAAK2X,gBACd3X,KAAK+Y,gBAAgB1P,EAAKV,EAGtC,CAKO,sBAAA0Q,GACHrZ,KAAK0X,kBAAoB,CAAA,EACzB1X,KAAK2Y,uBACT,CAKO,mBAAAW,GACHtZ,KAAK2X,eAAiB,CAAA,CAC1B,CAKO,KAAA4B,GACHvZ,KAAKqZ,yBACLrZ,KAAKsZ,sBACLtZ,KAAK4X,kBAAoB,CAAA,EACzB5X,KAAK6X,eAAgB,EACrB7X,KAAKkY,YACT,CAKQ,qBAAAC,GACJ,GAA8B,oBAAnBqB,eAEX,IACI,MAAM5S,EAAS4S,eAAe3X,QAAQ,yBAClC+E,IACA5G,KAAK0X,kBAAoBhW,KAAKC,MAAMiF,GAE5C,CAAE,MAAO9F,GACLE,QAAQE,KAAK,qCAAsCJ,EACvD,CACJ,CAKQ,qBAAA6X,GACJ,GAA8B,oBAAnBa,eAEX,IACIA,eAAenX,QAAQ,wBAAyBX,KAAKY,UAAUtC,KAAK0X,mBACxE,CAAE,MAAO5W,GACLE,QAAQE,KAAK,qCAAsCJ,EACvD,CACJ,CAKQ,aAAA0X,CAAcH,GACbrY,KAAKD,OAAOkY,kBAA4D,IAAxCjY,KAAKD,OAAOkY,iBAAiBlW,QAIlE/B,KAAKD,OAAOkY,iBAAiBlP,QAAQ0Q,WAC1BpB,EAAWoB,IAE1B,CAKO,yBAAAC,GACH1Z,KAAKwK,oBAAsBiM,GAC/B,CAKO,gBAAAkD,GAMH,MAAO,CACHC,UAAW5Z,KAAKyW,yBAChBoD,QAAS,IAAK7Z,KAAK0X,mBACnBoC,KAAM,IAAK9Z,KAAK2X,gBAChBoC,QAAS,IAAK/Z,KAAK4X,mBAE3B,ECvQJ,MAAMvX,EAA8B,oBAAXC,aAIZ0Z,EAyGT,oBAAWC,GACP,OAAOja,KAAKka,SAChB,CAKQ,oBAAAC,GACJ,GAAK9Z,EAML,GAA4B,aAAxB2L,SAASwG,YAAqD,gBAAxBxG,SAASwG,WAE/CxS,KAAKoa,kBACF,GAAIpO,SAAStI,iBAAkB,CAQlCsI,SAAStI,iBAAiB,mBAAoB,IAAM1D,KAAKoa,aAAc,CAAEC,SAAS,IAGlF,MAAMC,EAAWC,YAAY,KACG,gBAAxBvO,SAASwG,YAAwD,aAAxBxG,SAASwG,aAClDgI,cAAcF,GACdta,KAAKoa,eAEV,IAGHzU,WAAW,IAAM6U,cAAcF,GAAW,IAC9C,MAEIta,KAAKoa,kBA9BLpa,KAAKoa,YAgCb,CAKQ,UAAAA,GACApa,KAAKya,aAETza,KAAKya,YAAa,EAClBzX,EAAS,+CAGThD,KAAK0a,aAAa3R,QAAQ4R,IACtB3a,KAAK4a,eAAeD,KAExB3a,KAAK0a,aAAe,GAGpB1a,KAAK6a,iBAAiB9R,QAAQ+R,GAAWA,KACzC9a,KAAK6a,iBAAmB,GAC5B,CAKQ,YAAAE,CAAaJ,GACb3a,KAAKya,WACLza,KAAK4a,eAAeD,GAEpB3a,KAAK0a,aAAavY,KAAKwY,EAE/B,CAKQ,oBAAMC,CAAeD,GAGzB,OAFA3X,EAAS,6BAA8B2X,GAE/BA,EAAQrU,MACZ,IAAK,iBACKtG,KAAKgb,SAASL,EAAQlT,OAC5B,MACJ,IAAK,qBACKzH,KAAKib,aAAaN,EAAQhD,gBAChC,MACJ,IAAK,gBACD3X,KAAKkb,gBACL,MACJ,QACIpY,EAAQ,wBAAyB6X,EAAQrU,MAErD,CAKQ,uBAAA6U,CAAwBL,GACxB9a,KAAKya,WACLK,IAEA9a,KAAK6a,iBAAiB1Y,KAAK2Y,EAEnC,CAMO,WAAOpP,CAAKlF,EAAgB3C,GA0B/B,GAAIxD,IAAgD,IAAnCwD,GAASuX,sBAAiC,CAEvD,MAAMC,EAAuBra,QAAQF,MACrCE,QAAQF,MAAQ,IAAIH,KAChB,MAAMD,EAAUC,EAAK4R,KAAK,KAEtB7R,EAAQ4I,SAAS,iDACjB5I,EAAQ4I,SAAS,yCACjB5I,EAAQ4I,SAAS,2BACjB5I,EAAQ4I,SAAS,iBACjB5I,EAAQ4I,SAAS,SACjB5I,EAAQ4I,SAAS,gCACjB5I,EAAQ4I,SAAS,4BACjB5I,EAAQ4I,SAAS,+BACjB5I,EAAQ4I,SAAS,mDACjB5I,EAAQ4I,SAAS,oBACjB5I,EAAQ4I,SAAS,4BACjB5I,EAAQ4I,SAAS,wBACjB5I,EAAQ4I,SAAS,iCACjB5I,EAAQ4I,SAAS,+BAKrB+R,EAAqBC,MAAMta,QAASL,IAIxC,MAAM4a,EAAsBva,QAAQE,KACpCF,QAAQE,KAAO,IAAIP,KACf,MAAMD,EAAUC,EAAK4R,KAAK,KAEtB7R,EAAQ4I,SAAS,2BACjB5I,EAAQ4I,SAAS,iBACjB5I,EAAQ4I,SAAS,SACjB5I,EAAQ4I,SAAS,gCACjB5I,EAAQ4I,SAAS,4BACjB5I,EAAQ4I,SAAS,+BACjB5I,EAAQ4I,SAAS,mDACjB5I,EAAQ4I,SAAS,oBACjB5I,EAAQ4I,SAAS,+BACjB5I,EAAQ4I,SAAS,kCAKrBiS,EAAoBD,MAAMta,QAASL,IAIvCL,OAAOoD,iBAAiB,QAAU+D,IAC9B,MAAM/G,EAAU+G,EAAM/G,SAAW,GACjC,GACIA,EAAQ4I,SAAS,kBACjB5I,EAAQ4I,SAAS,qBACjB5I,EAAQ4I,SAAS,cACjB5I,EAAQ4I,SAAS,iBACjB5I,EAAQ4I,SAAS,SACjB5I,EAAQ4I,SAAS,iBACjB5I,EAAQ4I,SAAS,mBAGjB,OADA7B,EAAM+T,kBACC,GAGnB,CAEA,GAAInb,GAAcC,OAAemb,6BAE7B,OADAzY,EAAS,4DACD1C,OAAemb,6BAIvB5X,GAAS6X,UACT1b,KAAK2b,iBAAiB,CAAE1b,MAAO4D,EAAQ6X,WAI3C,MAAME,EAAU,IAAI5B,EAAqBxT,EAAQ3C,GAAS2F,aAAc,CACpEsO,0BAA2BjU,GAASiU,0BACpCG,iBAAkBpU,GAASoU,iBAC3BrH,kBAAmB/M,GAAS+M,kBAC5BM,aAAcrN,GAASqN,aACvBzK,aAAc5C,GAAS4C,aACvBoV,sBAAuBhY,GAASgY,sBAChCC,sBAAuBjY,GAASiY,wBAsBpC,OAlBAF,EAAQG,aAAelY,GAASkY,eAAgB,EAG5ClY,GAASqN,cACT0K,EAAQI,oBAAoBnY,EAAQqN,eAOC,IAArCrN,GAASoY,yBACTL,EAAQM,uBAAuBrY,GAASsY,0BAI5CP,EAAQQ,QAEDR,CACX,CAEA,WAAA9b,CAAY0G,EAA4BgD,EAAuB3F,GAa3D,GA3WI7D,KAAAqc,WAAoB,GACpBrc,KAAAsc,oBAAwF,GACxFtc,KAAAuc,YAA0D,GAC1Dvc,KAAAwc,qBAAqE,GAKrExc,KAAAyc,0BAA2C,KAC3Czc,KAAA0c,uBAAwC,KACxC1c,KAAA2X,eAAsC,CAAA,EACtC3X,KAAA2c,cAAwB,EAExB3c,KAAA4c,cAA+B,KACtB5c,KAAA6c,kBAAoB,IAI7B7c,KAAA0J,UAA2B,KAG3B1J,KAAA8c,aAAuB,EACxB9c,KAAA+c,sBAA8C,KAC7C/c,KAAAyJ,qBAA+B,EAI/BzJ,KAAAya,YAAsB,EACtBza,KAAA0a,aAAsB,GACtB1a,KAAA6a,iBAAsC,GAGtC7a,KAAAgd,gBAIG,KACHhd,KAAAid,wBAAkC,EAGlCjd,KAAAkd,cAAqC,KACrCld,KAAAmd,wBAAkC,EAGlCnd,KAAAod,2BAAqC,EACrCpd,KAAAqd,2BAAqC,EAGtCrd,KAAAsd,2BAAqC,EACpCtd,KAAAoW,WAAqB,GACrBpW,KAAAud,YAAsB,GACtBvd,KAAAwd,kBAAqD,KACrDxd,KAAAyd,qBAA2D,KAC3Dzd,KAAA0d,oBAAyC,GACzC1d,KAAA2d,oBAA8B,EAC9B3d,KAAA4d,eAAyC,KACzC5d,KAAA6d,iBAA2Bjd,KAAKsB,MAChClC,KAAA8d,YAAmB,KACnB9d,KAAA+d,oBAAqC,KACrC/d,KAAA+b,cAAwB,EACxB/b,KAAAka,WAAqB,EACZla,KAAAge,4BAAsC,IAO/Che,KAAAie,QAA+B,UAC/Bje,KAAAke,uBAAiCtd,KAAKsB,MAC7BlC,KAAAme,kBAAoB,IAG7Bne,KAAAoe,iBAEJ,CAAEC,OAAQ,IACGre,KAAAse,wBAA0B,GAC1Bte,KAAAue,sBAAwB,IACxBve,KAAAwe,uBAAyB,EAGlCxe,KAAAye,iBAYJ,CACAC,cAAe,IAAIC,KAEN3e,KAAA4e,+BAAiC,IACjC5e,KAAA6e,kCAAoC,IACpC7e,KAAA8e,iCAAmC,IACnC9e,KAAA+e,+BAAiC,MAwQzCvY,EACD,MAAM,IAAI8F,MAAM,sCAMpB,MACM0S,EAAoBxV,GADE,kCAiC5B,GA/BAxJ,KAAKif,IAAM,IAAI1V,EAAiB,CAC5B/C,OAAQA,EACRgD,aAAcwV,IAElBhf,KAAKwG,OAASA,EACdxG,KAAKwJ,aAAewV,EAGpBhf,KAAKkf,eAAiBrb,GAAS4C,cAAgB,IAG/CzG,KAAKod,2BAA+D,IAAnCvZ,GAASgY,sBAC1C7b,KAAKqd,2BAA+D,IAAnCxZ,GAASiY,sBAG1C9b,KAAKmf,iBAAmB,IAAI9O,EAAiB,CACzCO,kBAAmB/M,GAAS+M,kBAC5BQ,mBAAoBvN,GAASqN,eAKjClR,KAAKof,gBAAkB,IAAI3H,EAAgB,CACvCK,2BAAkE,IAAvCjU,GAASiU,0BACpCG,iBAAkBpU,GAASoU,kBAAoB,KAO/C5X,EAAW,CACX,MAAMgf,EAAe,6BACfC,EAAoBtf,KAAKuf,UAAUF,GACzCrf,KAAK0J,UAAY4V,GAAqB5Q,IACjC4Q,EAIDtc,EAAS,+BAA+BhD,KAAK0J,cAH7C1J,KAAKwf,UAAUH,EAAcrf,KAAK0J,UAAW,KAC7C1G,EAAS,4BAA4BhD,KAAK0J,aAIlD,MACI1J,KAAK0J,UAAYgF,IAKrB,GAAIrO,EAAW,CAEX,MAAMof,EAAkBzf,KAAKwG,QAAU,UACvCxG,KAAK0f,uBAAyB,kBAAkBD,cAChDzf,KAAK2f,mCAAqC,kBAAkBF,0BAE5Dzf,KAAKoI,UAAYpI,KAAK4f,uBACtB5f,KAAKuK,SAAWvK,KAAK6f,sBACrB7f,KAAKoW,WAAa9V,OAAOwL,SAASC,KACjCzL,OAAemb,6BAA+Bzb,KAG/CA,KAAK8f,2BACT,MACI9f,KAAK0f,uBAAyB,GAC9B1f,KAAK2f,mCAAqC,GAC1C3f,KAAKoI,UAAYsG,IACjB1O,KAAKuK,SAAWmE,IAIpB1O,KAAKif,IAAI9U,mBAAmBnK,KAAKoI,UAAWpI,KAAK0J,WAGjD1J,KAAK+c,sBAAwB/c,KAAK0L,OAAO1F,MAAMlF,IAC3C+B,EAAS,yBAA0B/B,IAE3C,CAEQ,UAAM4K,GACV,IAEQrL,GACAL,KAAK+f,yBACL/f,KAAKggB,2BAELjd,EAAQ,yFAIZ/C,KAAK8c,aAAc,EAEnB/Z,EAAQ,oDAAoD/C,KAAKoI,yBAAyBpI,KAAK0J,YACnG,CAAE,MAAO5I,GAEL+B,EAAS,6CAA8C/B,GACvDd,KAAK8c,aAAc,CACvB,CACJ,CAKQ,uBAAMmD,GACNjgB,KAAK+c,6BACC/c,KAAK+c,qBAEnB,CAKQ,uBAAAiD,GACJ,IAAK3f,GAAaL,KAAKsd,0BAA2B,OAElDtd,KAAKsd,2BAA4B,EACjCta,EAAS,kCAGThD,KAAKwd,kBAAoB0C,QAAQC,UACjCngB,KAAKyd,qBAAuByC,QAAQE,aAGpCF,QAAQC,UAAY,IAAIxf,KACpBX,KAAKud,YAAcvd,KAAKoW,WACxBpW,KAAKoW,WAAa9V,OAAOwL,SAASC,KAGlC/L,KAAKwd,kBAAmBlC,MAAM4E,QAASvf,GAGvCX,KAAKqgB,qBAAqB,YAAargB,KAAKud,YAAavd,KAAKoW,YAG9DpW,KAAKsgB,oBAITJ,QAAQE,aAAe,IAAIzf,KACvBX,KAAKud,YAAcvd,KAAKoW,WACxBpW,KAAKoW,WAAa9V,OAAOwL,SAASC,KAGlC/L,KAAKyd,qBAAsBnC,MAAM4E,QAASvf,GAG1CX,KAAKqgB,qBAAqB,eAAgBrgB,KAAKud,YAAavd,KAAKoW,YAGjEpW,KAAKsgB,oBAIT,MAAMC,EAAmB,KACrBvgB,KAAKud,YAAcvd,KAAKoW,WACxBpW,KAAKoW,WAAa9V,OAAOwL,SAASC,KAClC/L,KAAKqgB,qBAAqB,WAAYrgB,KAAKud,YAAavd,KAAKoW,YAG7DpW,KAAKsgB,oBAGThgB,OAAOoD,iBAAiB,WAAY6c,GACpCvgB,KAAK0d,oBAAoBvb,KAAK,KAC1B7B,OAAOkgB,oBAAoB,WAAYD,KAI3C,MAAME,EAAqB,KACvBzgB,KAAKud,YAAcvd,KAAKoW,WACxBpW,KAAKoW,WAAa9V,OAAOwL,SAASC,KAClC/L,KAAKqgB,qBAAqB,aAAcrgB,KAAKud,YAAavd,KAAKoW,aAGnE9V,OAAOoD,iBAAiB,aAAc+c,GACtCzgB,KAAK0d,oBAAoBvb,KAAK,KAC1B7B,OAAOkgB,oBAAoB,aAAcC,KAI7CzgB,KAAKqgB,qBAAqB,WAAY,GAAIrgB,KAAKoW,WACnD,CAKO,0BAAMiK,CAAqB/Z,EAAcoa,EAAiBC,GAC7D,GAAK3gB,KAAK8c,YAEV,IACI,MAAM8D,EAAiB,CACnBta,KAAMA,EACNqL,KAAM+O,EACNG,GAAIF,EACJ1e,WAAW,IAAIrB,MAAOC,cACtBoI,SAAU3I,OAAOwL,SAAS7C,SAC1B6M,OAAQxV,OAAOwL,SAASgK,OACxBC,KAAMzV,OAAOwL,SAASiK,KACtBlK,SAAUG,SAASH,UAgBvB,SAZM7L,KAAKgb,SAAS,CAChB1U,KAAM,EACNmC,KAAM,CACF6E,QAAS,CACLwT,UAAW,gBACRF,IAGX3e,UAAWrB,KAAKsB,QAIP,aAAToE,GAAgC,cAATA,GAAiC,iBAATA,GAAoC,aAATA,GAAgC,eAATA,EAAuB,CACxH,MAAMya,EAAqB,CACvBhd,IAAKzD,OAAOwL,SAASC,KACrB2U,QAASA,EACTM,eAAgB1a,EAChB2C,SAAU3I,OAAOwL,SAAS7C,SAC1B6M,OAAQxV,OAAOwL,SAASgK,OACxBC,KAAMzV,OAAOwL,SAASiK,KACtBlK,SAAUG,SAASH,SACnB5J,UAAWrB,KAAKsB,aAGdlC,KAAKihB,YAAY,eAAgBF,EAC3C,CAEA/d,EAAS,uBAAuBsD,UAAaoa,QAAcC,IAC/D,CAAE,MAAO7f,GACL+B,EAAS,oCAAqC/B,EAClD,CACJ,CAEO,mBAAMoa,CAAcnX,GACvB,GAAK/D,KAAK8c,YAAV,CAGA9c,KAAKof,gBAAgB1F,4BAErB,IACI,MAAMwH,EAAe,CACjBnd,IAAKA,GAAOzD,OAAOwL,SAASC,KAC5B9C,SAAU3I,OAAOwL,SAAS7C,SAC1B6M,OAAQxV,OAAOwL,SAASgK,OACxBC,KAAMzV,OAAOwL,SAASiK,KACtBlK,SAAUG,SAASH,SACnB5J,WAAW,IAAIrB,MAAOC,eAIpBsgB,EAAqBnhB,KAAKof,gBAAgBhH,mBAAmB8I,SAG7DlhB,KAAKgb,SAAS,CAChB1U,KAAM,EACNmC,KAAM,CACF6E,QAAS,CACLwT,UAAW,cACRK,IAGXlf,UAAWrB,KAAKsB,QAGpBc,EAAS,qBAAqBke,EAAand,MAC/C,CAAE,MAAOjD,GACL+B,EAAS,kCAAmC/B,EAChD,CAjCuB,CAkC3B,CAEO,iBAAMmgB,CAAYlT,EAAmBsK,GAGnCrY,KAAK0J,YAEN5G,EAAQ,0DAA0DiL,KAClE/N,KAAK0J,UAAYgF,KAIjBrO,GACAL,KAAKohB,yBAIT,MAAMD,EAAqBnhB,KAAKof,gBAAgBhH,mBAAmBC,GAGnE,GAAIrY,KAAKqhB,iCAOL,OANAre,EAAS,iBAAiB+K,wDAC1B/N,KAAKsc,oBAAoBna,KAAK,CAC1B4L,YACAsK,WAAY8I,EACZlf,UAAWrB,KAAKsB,cAMlBlC,KAAKshB,2BAEX,UAEUthB,KAAKif,IAAInR,gBAAgB9N,KAAKoI,UAAW2F,EAAWoT,EAAoBnhB,KAAK0J,WAEnF1G,EAAS,yBAAyB+K,IAAaoT,EACnD,CAAE,MAAOrgB,GACL+B,EAAS,gCAAiC/B,GAGtCA,EAAMJ,SAAS4I,SAAS,QACxBxI,EAAMJ,SAAS4I,SAAS,0BACxBxI,EAAMJ,SAAS4I,SAAS,+BACxBxG,EAAQ,gDACDhC,EAAMJ,SAAS4I,SAAS,yBAC/BxG,EAAQ,8DACDhC,EAAMJ,SAAS4I,SAAS,oBAC/BxG,EAAQ,8CAIZ,IACI,MAAMye,EAAkB,CACpBxT,UAAWA,EACXsK,WAAY8I,GAAsB,CAAA,EAClClf,WAAW,IAAIrB,MAAOC,cACtBkD,IAAKzD,OAAOwL,SAASC,KACrB9C,SAAU3I,OAAOwL,SAAS7C,gBAGxBjJ,KAAKgb,SAAS,CAChB1U,KAAM,EACNmC,KAAM,CACF6E,QAAS,CACLwT,UAAW,YACRS,IAGXtf,UAAWrB,KAAKsB,QAGpBc,EAAS,mDAAmD+K,IAChE,CAAE,MAAOyT,GACL3e,EAAS,0DAA2D2e,EACxE,CACJ,CACJ,CAKQ,sBAAAtF,CAAuBrY,GAO3B,IAAKxD,EAAW,OAEhB,MAAMN,EAAS,CACX0hB,cAAwC,IAA1B5d,GAAS4d,aACvBC,YAAY,EACZC,YAAoC,IAAxB9d,GAAS8d,WACrBC,aAAsC,IAAzB/d,GAAS+d,YACtBC,eAAgBhe,GAASge,iBAAkB,GAG/C7e,EAAS,6CAA8CjD,GAGnDA,EAAO0hB,cACPzhB,KAAK8hB,6BAA6B/hB,GAIlCA,EAAO4hB,YACP3hB,KAAK+hB,2BAA2BhiB,GAGpCC,KAAKgiB,yBAGT,CAKQ,4BAAAF,CAA6B/hB,GAIjCiM,SAAStI,iBAAiB,QAASue,MAAOxa,IACtC,MAAMya,EAASza,EAAMya,OAGrB,GAAuB,WAAnBA,EAAOC,SAAwBD,EAAOE,QAAQ,UAAW,CACzD,MAAMC,EAA4B,WAAnBH,EAAOC,QAChBD,EACAA,EAAOE,QAAQ,UAEf/J,EAAkC,CACpCiK,SAAUD,EAAOE,IAAM,KACvBC,WAAYH,EAAO/b,MAAQ,SAC3Bmc,KAAMniB,OAAOwL,SAAS7C,SACtBhH,UAAWrB,KAAKsB,OAGhBnC,EAAO6hB,cACPvJ,EAAWqK,WAAaL,EAAOM,aAAaC,QAAU,MAGtD7iB,EAAO8hB,iBACPxJ,EAAWwK,YAAcR,EAAOS,WAAa,MAIjD5Z,OAAO6Z,KAAK1K,GAAYtP,QAAQM,IACJ,OAApBgP,EAAWhP,WACJgP,EAAWhP,WAIpBrJ,KAAKihB,YAAY,kBAAmB5I,EAC9C,GAER,CAOQ,uBAAA2J,GACC3hB,GAEL2L,SAAStI,iBAAiB,QAASue,MAAOxa,IACtC,MAAMya,EAASza,EAAMya,OACfc,EAAIvb,EAAMwb,QACVC,EAAIzb,EAAM0b,QACVlhB,EAAYrB,KAAKsB,MAGvB,GAAIlC,KAAKojB,YAAYJ,EAAGE,EAAGjhB,EAAWigB,GAAS,CAE3C,MAAMtP,EAAUsP,EAAOE,QAAQ,8CAAgDF,EAEzE7J,EAAkC,CACpC2K,EAAGA,EACHE,EAAGA,EACHT,KAAMniB,OAAOwL,SAAS7C,SACtB2J,QAASA,EAAQuP,QAAQnS,cACzBqT,WAAYrjB,KAAKwe,uBACjBvc,UAAWA,GAIX2Q,EAAQ2P,KACRlK,EAAWiL,UAAY1Q,EAAQ2P,IAE/B3P,EAAQkQ,YACRzK,EAAWkL,aAAe3Q,EAAQkQ,WAElClQ,EAAQ+P,cACRtK,EAAWmL,YAAc5Q,EAAQ+P,YAAYC,OAAOxU,UAAU,EAAG,MAIrElF,OAAO6Z,KAAK1K,GAAYtP,QAAQM,IACJ,OAApBgP,EAAWhP,SAAqCrH,IAApBqW,EAAWhP,WAChCgP,EAAWhP,WAGpBrJ,KAAKihB,YAAY,aAAc5I,GAGrCrY,KAAKoe,iBAAiBC,OAAS,EACnC,GAER,CAMQ,WAAA+E,CAAYJ,EAAWE,EAAWjhB,EAAmB2Q,GACzD,MAAMyL,EAASre,KAAKoe,iBAAiBC,OAC/BoF,EAAYpF,EAAOA,EAAOtc,OAAS,GAEzC,GACI0hB,GACA1e,KAAK2e,IAAIV,EAAIS,EAAUT,GAAKje,KAAK2e,IAAIR,EAAIO,EAAUP,GAAKljB,KAAKse,yBAC7Drc,EAAYwhB,EAAUxhB,UAAYjC,KAAKue,uBAMvC,GAHAF,EAAOlc,KAAK,CAAE6gB,IAAGE,IAAGjhB,YAAW2Q,YAG3ByL,EAAOtc,QAAU/B,KAAKwe,uBACtB,OAAO,OAIXxe,KAAKoe,iBAAiBC,OAAS,CAAC,CAAE2E,IAAGE,IAAGjhB,YAAW2Q,YAGvD,OAAO,CACX,CAoMQ,oBAAA+Q,CAAqB/Q,GACzB,MAAMuP,EAAUvP,EAAQuP,QAAQnS,cAGhC,GAAgB,WAAZmS,GAAoC,MAAZA,EACxB,OAAO,EAIX,GAAI,CAAC,QAAS,SAAU,YAAY7Y,SAAS6Y,GACzC,OAAO,EAIX,MAAMyB,EAAOhR,EAAQiR,aAAa,QAClC,GAAID,GAAQ,CAAC,SAAU,OAAQ,MAAO,WAAY,WAAY,SAASta,SAASsa,GAC5E,OAAO,EAIX,GAAKhR,EAAgBkR,SAAWlR,EAAQiR,aAAa,WACjD,OAAO,EAIX,IAEI,GAAqB,YADPvjB,OAAOyjB,iBAAiBnR,GAC5BoR,OACN,OAAO,CAEf,CAAE,MAAOzhB,GAET,CAIA,QAD0BqQ,EAAQwP,QAAQ,6EAM9C,CA2JQ,0BAAA6B,CAA2BlkB,GA0CnC,CAKQ,0BAAAgiB,CAA2BhiB,GAI/BiM,SAAStI,iBAAiB,SAAUue,MAAOxa,IACvC,MAAMyc,EAAOzc,EAAMya,OACbiC,EAAW,IAAIC,SAASF,GAExB7L,EAAkC,CACpCgM,OAAQH,EAAK3B,IAAM,KACnB+B,WAAYJ,EAAKK,QAAU,KAC3BC,WAAYN,EAAKnZ,QAAU,MAC3BuG,OAAQxK,MAAM6K,KAAKwS,EAASpB,QAC5BN,KAAMniB,OAAOwL,SAAS7C,SACtBhH,UAAWrB,KAAKsB,OAGhBnC,EAAO8hB,iBACPxJ,EAAWoM,UAAYP,EAAKpB,WAAa,MAI7C5Z,OAAO6Z,KAAK1K,GAAYtP,QAAQM,IACJ,OAApBgP,EAAWhP,WACJgP,EAAWhP,WAIpBrJ,KAAKihB,YAAY,kBAAmB5I,IAElD,CAKQ,yBAAAqM,GACC1kB,KAAKsd,4BAGNtd,KAAKwd,oBACL0C,QAAQC,UAAYngB,KAAKwd,mBAEzBxd,KAAKyd,uBACLyC,QAAQE,aAAepgB,KAAKyd,sBAIhCzd,KAAK0d,oBAAoB3U,QAAQ4b,GAAWA,KAC5C3kB,KAAK0d,oBAAsB,GAE3B1d,KAAKsd,2BAA4B,EACjCta,EAAS,kCACb,CAEO,mBAAO/B,CAAaP,GACvBqC,EAAQrC,EACZ,CAMO,uBAAOib,CAAiB5b,GAS3BF,EAAOU,UAAU,CACbN,MATa,CACb2kB,KAAQ,EACR9jB,MAAS,EACTI,KAAQ,EACRE,KAAQ,EACRG,MAAS,GAIOxB,EAAOE,OAAS,SAChCE,eAAwC,IAAzBJ,EAAOI,cACtBC,cAAeL,EAAOK,gBAAiB,GAE/C,CAKO,qBAAAyb,GACExb,IAAaL,KAAKid,yBAGvBjd,KAAKgd,gBAAkB,CACnB1b,IAAKN,QAAQM,IACbJ,KAAMF,QAAQE,KACdJ,MAAOE,QAAQF,OAInBE,QAAQM,IAAM,IAAIX,KACdX,KAAK6kB,kBAAkB,MAAOlkB,GAC9BX,KAAKgd,gBAAiB1b,OAAOX,IAGjCK,QAAQE,KAAO,IAAIP,KACfX,KAAK6kB,kBAAkB,OAAQlkB,GAC/BX,KAAKgd,gBAAiB9b,QAAQP,IAGlCK,QAAQF,MAAQ,IAAIH,KAChBX,KAAK6kB,kBAAkB,QAASlkB,GAChCX,KAAKgd,gBAAiBlc,SAASH,IAGnCX,KAAKid,wBAAyB,EAC9Bja,EAAS,4BACb,CAKO,qBAAA8Y,GACEzb,IAAaL,KAAKmd,wBAA2C,oBAAVlS,QAGxDjL,KAAKkd,cAAgB5c,OAAO2K,MAAM6Z,KAAKxkB,QAGvCA,OAAO2K,MAAQgX,MAAO8C,EAA0BrZ,KAC5C,MAAM8C,EAAmB5N,KAAKsB,MACxBuM,EAAYC,IACZ3K,EAAuB,iBAAVghB,EAAqBA,EAAQA,aAAiB/gB,IAAM+gB,EAAM5gB,WAAa4gB,EAAMhhB,IAC1FgH,GAAUW,GAAMX,SAA4B,iBAAVga,GAAsB,WAAYA,EAAQA,EAAMha,YAAS/I,IAAc,OAAOgjB,cAGhHrW,EAAqB3O,KAAK4O,0BAA0B7K,GAGpDkhB,EAA4B,IAClC,IAAIC,EAA6D,KAC7DC,GAAqB,EAGpBxW,IACDuW,EAAuBvf,WAAW,KAC9B,MAAMyf,EAAcxkB,KAAKsB,MAAQsM,EACjC,IAAK2W,EAAoB,CACrBA,GAAqB,EACrB,MAAM7W,EAAY,CACdG,YACA1K,MACAgH,SACAvG,OAAQ,KACR+H,WAAY,KACZwC,SAAUqW,EACVpW,YAAapO,KAAKsB,MAClBkG,UAAWpI,KAAKoI,UAChBsB,UAAW1J,KAAK0J,UAChB6E,UAAW,eACXW,aAAc,qCAA4DkW,eAE1EjW,YAAaX,EACbY,SAAU,GAAGrE,KAAUhH,IACvBsL,WAAY,OACZC,WAAY,CACR,cAAevE,EACf,WAAYhH,EACZ,sBAAuBqhB,EACvB,oCAAqCH,IAI7C,OAAIjlB,KAAKqhB,kCACLre,EAAS,gFACThD,KAAKwc,qBAAqBra,KAAK,CAC3BmM,YACArM,UAAWrB,KAAKsB,UAKxBlC,KAAKqlB,iCACLrlB,KAAKif,IAAI5Q,iBAAiBC,GAAWtI,MAAM,QAE/C,GACDif,IAGP,IACI,MAAMja,QAAiBhL,KAAKkd,cAAe6H,EAAOrZ,GAC5CoD,EAAkBlO,KAAKsB,MAAQsM,EAQrC,GALI0W,GACAxf,aAAawf,IAIZla,EAASQ,KAAOmD,EAAoB,CACrC,MAAML,EAAY,CACdG,YACA1K,MACAgH,SACAvG,OAAQwG,EAASxG,OACjB+H,WAAYvB,EAASuB,WACrBwC,SAAUD,EACVE,YAAapO,KAAKsB,MAClBkG,UAAWpI,KAAKoI,UAChBsB,UAAW1J,KAAK0J,UAChB6E,UAAWvO,KAAKiP,kBAAkBjE,EAASxG,QAC3C0K,aAAclE,EAASuB,WAEvB4C,YAAaX,EACbY,SAAU,GAAGrE,KAAUhH,IACvBsL,WAAY,QACZC,WAAY,CACR,mBAAoBtE,EAASxG,OAC7B,mBAAoBwG,EAASuB,aAIrC,GAAIvM,KAAKqhB,iCAML,OALAre,EAAS,6EACThD,KAAKwc,qBAAqBra,KAAK,CAC3BmM,YACArM,UAAWrB,KAAKsB,QAEb8I,EAGXhL,KAAKqlB,4BACLrlB,KAAKif,IAAI5Q,iBAAiBC,GAAWtI,MAAM,OAC/C,CAEA,OAAOgF,CACX,CAAE,MAAOlK,GACL,MAAMgO,EAAkBlO,KAAKsB,MAAQsM,EAQrC,GALI0W,GACAxf,aAAawf,IAIZvW,EAAoB,CACrB,MAAML,EAAY,CACdG,YACA1K,MACAgH,SACAvG,OAAQ,KACR+H,WAAY,KACZwC,SAAUD,EACVE,YAAapO,KAAKsB,MAClBkG,UAAWpI,KAAKoI,UAChBsB,UAAW1J,KAAK0J,UAChB6E,UAAWvO,KAAKyP,qBAAqB3O,GACrCoO,aAAcpO,EAAMJ,QACpBgP,UAAW5O,EAAMqG,KAEjBgI,YAAaX,EACbY,SAAU,GAAGrE,KAAUhH,IACvBsL,WAAY,QACZC,WAAY,CACR,aAAcxO,EAAMqG,KACpB,gBAAiBrG,EAAMJ,UAI/B,GAAIV,KAAKqhB,iCAML,MALAre,EAAS,8DACThD,KAAKwc,qBAAqBra,KAAK,CAC3BmM,YACArM,UAAWrB,KAAKsB,QAEdpB,EAGVd,KAAKqlB,4BACLrlB,KAAKif,IAAI5Q,iBAAiBC,GAAWtI,MAAM,OAC/C,CAEA,MAAMlF,CACV,GAGJd,KAAKmd,wBAAyB,EAC9Bna,EAAS,4BACb,CAKQ,8BAAMse,GACV,GAAwC,IAApCthB,KAAKsc,oBAAoBva,OACzB,OAGJ,MAAMujB,EAAgB,IAAItlB,KAAKsc,qBAC/Btc,KAAKsc,oBAAsB,GAE3BtZ,EAAS,YAAYsiB,EAAcvjB,gCAEnC,IAAK,MAAMgM,UAAEA,EAASsK,WAAEA,KAAgBiN,EACpC,UACUtlB,KAAKif,IAAInR,gBAAgB9N,KAAKoI,UAAW2F,EAAWsK,EAAYrY,KAAK0J,UAC/E,CAAE,MAAO5I,GACL+B,EAAS,wCAAyC/B,EACtD,CAER,CAKQ,sBAAMykB,GACV,GAAgC,IAA5BvlB,KAAKuc,YAAYxa,OACjB,OAGJ,MAAMyjB,EAAc,IAAIxlB,KAAKuc,aAC7Bvc,KAAKuc,YAAc,GAEnBvZ,EAAS,YAAYwiB,EAAYzjB,uBAEjC,IAAK,MAAMoM,QAAEA,KAAaqX,EACtB,UACUxlB,KAAKif,IAAI/Q,QAAQC,EAC3B,CAAE,MAAOrN,GACL+B,EAAS,+BAAgC/B,EAC7C,CAER,CAKQ,+BAAMukB,GACV,GAAyC,IAArCrlB,KAAKwc,qBAAqBza,OAC1B,OAGJ,MAAM0jB,EAAgB,IAAIzlB,KAAKwc,sBAC/Bxc,KAAKwc,qBAAuB,GAE5BxZ,EAAS,YAAYyiB,EAAc1jB,iCAEnC,IAAK,MAAMuM,UAAEA,KAAemX,EACxB,UACUzlB,KAAKif,IAAI5Q,iBAAiBC,EACpC,CAAE,MAAOxN,GACL+B,EAAS,yCAA0C/B,EACvD,CAER,CAKO,sBAAA4kB,GACErlB,GAA+B,oBAAXC,SAGG,aAAxB0L,SAASwG,WAETxS,KAAK2lB,gBAGLrlB,OAAOoD,iBAAiB,OAAQ,KAC5B1D,KAAK2lB,kBAIb3iB,EAAS,8BACb,CAKQ,aAAA2iB,GACJ,GAAKtlB,GAAoC,oBAAhBulB,YAEzB,IACI,MAAMC,EAAYD,YAAYE,iBAAiB,cAAc,GAC7D,IAAKD,EAAW,OAEhB,MAAME,EAAeF,EAAUG,aAAeH,EAAUI,WAIxD,GAAIF,EAH4B,IAGY,CACxC,MAAMtX,EAAYC,IACZwX,EAAmBL,EAAUM,yBAA2BN,EAAUI,WAClEG,EAAcP,EAAUO,YAAcP,EAAUI,WAEhD3X,EAAY,CACdG,YACA1K,IAAKzD,OAAOwL,SAASC,KACrBhB,OAAQ,MACRvG,OAAQ,IACR+H,WAAY,KACZwC,SAAUgX,EACV/W,YAAa6W,EAAUG,aAAeJ,YAAYS,WAClDje,UAAWpI,KAAKoI,UAChBsB,UAAW1J,KAAK0J,UAChB6E,UAAW,iBACXW,aAAc,kBAAkB6W,MAEhC5W,YAAa0W,EAAUI,WAAaL,YAAYS,WAChDjX,SAAU,YACVC,WAAY,OACZC,WAAY,CACR,WAAYhP,OAAOwL,SAASC,KAC5B,iBAAkBga,EAClB,0BAA2BG,EAC3B,oBAAqBE,IAI7B,GAAIpmB,KAAKqhB,iCAML,OALAre,EAAS,kFACThD,KAAKwc,qBAAqBra,KAAK,CAC3BmM,YACArM,UAAWrB,KAAKsB,QAKxBlC,KAAKqlB,4BACLrlB,KAAKif,IAAI5Q,iBAAiBC,GAAWtI,MAAM,OAC/C,CACJ,CAAE,MAAOlF,GACLgC,EAAQ,6BAA8BhC,EAC1C,CACJ,CAKQ,yBAAA8N,CAA0B7K,GAC9B,IAAKA,IAAQ/D,KAAKwJ,aACd,OAAO,EAGX,IACI,MAAMyG,EAAS,IAAIjM,IAAID,GACjBmM,EAAa,IAAIlM,IAAIhE,KAAKwJ,cAGhC,QAAIyG,EAAOE,SAAWD,EAAWC,SAEzBF,EAAOhH,SAASmH,WAAW,uBAM/BrM,EAAIuF,SAAStJ,KAAKwJ,aAK1B,CAAE,MAAO1I,GAEL,OAAOiD,EAAIuF,SAAStJ,KAAKwJ,aAC7B,CACJ,CAKQ,iBAAAyF,CAAkBzK,GACtB,OAAIA,GAAU,KAAOA,EAAS,IACnB,eAEPA,GAAU,IACH,eAEJ,eACX,CAKQ,oBAAAiL,CAAqB3O,GACzB,MAAMoO,EAAepO,EAAMJ,SAAW,GAChCgP,EAAY5O,EAAMqG,MAAQ,GAGhC,OACI+H,EAAa5F,SAAS,YACtB4F,EAAa5F,SAAS,0BACtB4F,EAAa5F,SAAS,+BACR,cAAdoG,GAA6BR,EAAa5F,SAAS,mBAE5C,oBAKP4F,EAAa5F,SAAS,SACtB4F,EAAa5F,SAAS,iBACtB4F,EAAa5F,SAAS,gCACR,cAAdoG,GAA6BR,EAAa5F,SAAS,QAE5C,aAKP4F,EAAa5F,SAAS,YACtB4F,EAAa5F,SAAS,YACtB4F,EAAa5F,SAAS,iBACR,iBAAdoG,EAEO,gBAKPR,EAAa5F,SAAS,UACR,eAAdoG,EAEO,UAGJ,eACX,CAKO,sBAAA4W,GACEjmB,GAAcL,KAAKid,yBAGpBjd,KAAKgd,kBACLhc,QAAQM,IAAMtB,KAAKgd,gBAAgB1b,IACnCN,QAAQE,KAAOlB,KAAKgd,gBAAgB9b,KACpCF,QAAQF,MAAQd,KAAKgd,gBAAgBlc,OAGzCd,KAAKid,wBAAyB,EAC9Bja,EAAS,6BACb,CAEQ,iBAAA6hB,CAAkB5kB,EAAiCU,GACvD,GAAKX,KAAK8c,YAKV,GAAc,QAAV7c,EAQJ,IAEI,GAAI2C,IAIA,YAHI5C,KAAKgd,iBACLhd,KAAKgd,gBAAgB/c,MAAUU,IAMvC,MAAM4lB,GAAQ,IAAIja,OAAQia,OAAS,GACnC,GAAIvmB,KAAKwmB,gBAAgBD,GAKrB,YAHIvmB,KAAKgd,iBACLhd,KAAKgd,gBAAgB/c,MAAUU,IAKvC,MAAM8lB,EAAc,CAChBxmB,MAAOA,EACPS,QAASC,EAAK+lB,IAAIC,GACC,iBAARA,EAAmBjlB,KAAKY,UAAUqkB,GAAOC,OAAOD,IACzDpU,KAAK,KACPvD,YAAapO,KAAKsB,MAClB6B,IAAK1D,EAAYC,OAAOwL,SAASC,KAAO,GACxC0H,UAAWpT,EAAYmD,UAAUiQ,UAAY,GAC7C8S,MAAOA,EACPne,UAAWpI,KAAKoI,UAChBsB,UAAW1J,KAAK0J,WAIpB,GAAI1J,KAAKqhB,iCAML,OALAre,EAAS,WAAW/C,uDACpBD,KAAKuc,YAAYpa,KAAK,CAClBgM,QAASsY,EACTxkB,UAAWrB,KAAKsB,QAMxBlC,KAAKulB,mBAGLvlB,KAAKif,IAAI/Q,QAAQuY,GAAazgB,MAAM6gB,IAEpC7mB,KAAKgb,SAAS,CACV1U,KAAM,EACNmC,KAAM,CACF6E,QAAS,CACLwT,UAAW,aACR2F,IAGXxkB,UAAWrB,KAAKsB,QACb8D,MAAM,SAEjB,CAAE,MAAOlF,GACL+B,EAAS,8BAA+B/B,EAC5C,MAnEQd,KAAKgd,iBACLhd,KAAKgd,gBAAgB1b,OAAOX,EAmExC,CAOQ,eAAA6lB,CAAgBD,GACpB,IAAKA,EAAO,OAAO,EAGnB,MAAMO,EAAc,CAChB,mBACA,sBACA,yBACA,aACA,SACA,YACA,eACA,gBACA,mBACA,YACA,YAIEC,EAAaR,EAAMS,MAAM,MACZT,EAAMvW,cAMzB,IAAIiX,GAAmB,EAEvB,IAAK,IAAIC,EAAI,EAAGA,EAAIH,EAAWhlB,OAAQmlB,IAAK,CACxC,MAAMC,EAAOJ,EAAWG,GAAGtE,OAAO5S,cAGlC,IAAKmX,GAAiB,UAATA,GAAoBA,EAAK/W,WAAW,UAC7C,SAMJ,IAFmB0W,EAAY/T,KAAKC,GAAWmU,EAAK7d,SAAS0J,EAAQhD,gBAEpD,CAEbiX,GAAmB,EACnB,KACJ,CACJ,CAIA,OAAQA,CACZ,CAEQ,sBAAAlH,GACJ,IAAK1f,EAAW,OAEhB2C,EAAS,kCAGT1C,OAAOoD,iBAAiB,mBAAoB,KACP,WAA7BsI,SAASob,iBACTpkB,EAAS,wCAEThD,KAAKqnB,eAC+B,YAA7Brb,SAASob,kBAChBpkB,EAAS,+DACThD,KAAKsgB,sBAMb,MAAMgH,EAAc,eAAgBhnB,OAAS,WAAa,eAE1DA,OAAOoD,iBAAiB4jB,EAAa,KAGjCtkB,EAAS,wDAGT,MAAMukB,EAAkBvnB,KAAKge,4BACvBwJ,EAAkBxnB,KAAKynB,qBAM7B,GALsD,OAApBD,GAA4BA,GAAmB,GAG7EA,EAAkBD,EAKlB,YADAvkB,EAAS,qBAAqBwkB,uBAAqCD,+BAKvE,MAAMG,EAAe,IAAI1nB,KAAKqc,YAI9B,GAAIhc,GAAcC,OAAeqnB,uBAAwB,CACrD,MAAMC,EAAoBtnB,OAAeqnB,uBACrC7gB,MAAMC,QAAQ6gB,IAAqBA,EAAiB7lB,OAAS,IAC7DiB,EAAS,qEACT0kB,EAAaG,WAAWD,UAChBtnB,OAAeqnB,uBAE/B,CAGA,GAAID,EAAa3lB,OAAS,GAAK/B,KAAKif,IAChC,IAEI,MAAMzU,EAAsBxK,KAAKof,gBAAgB3I,yBAGjDzW,KAAKif,IAAIrR,iBACL8Z,EACA1nB,KAAKoI,UACLpI,KAAK0J,gBAAa1H,EAClBhC,KAAKuK,SACLC,GAIJxK,KAAKqc,WAAa,EACtB,CAAE,MAAOvb,GAELgC,EAAQ,kDAAmDhC,EAC/D,CAIAd,KAAKif,KACLjf,KAAKif,IAAIhZ,WAKjB,MAAM6hB,EAAiB,KACnBlmB,aAAaS,QAAQ,+BAAgCzB,KAAKsB,MAAMiC,aAIpE7D,OAAOoD,iBAAiB,QAASokB,GACjCxnB,OAAOoD,iBAAiB,UAAWokB,GACnCxnB,OAAOoD,iBAAiB,SAAUokB,GAClCxnB,OAAOoD,iBAAiB,YAAaokB,EACzC,CAEO,QAAAC,GACH,IACI,MAAMtmB,EAAO5B,EAAO2C,UACpBO,EAAQ,sBAAuBtB,GAC/B5B,EAAO4C,WACX,CAAE,MAAOF,GACLM,EAAS,uBAAwBN,EACrC,CACJ,CAMO,kBAAM0Y,EACTtD,eAAEA,IAMF,MAAMqQ,EAAoBhoB,KAAK0J,UAG/B1J,KAAK2X,eAAiBA,EAEtB3U,EAAS,oBAAqB,CAAE2U,iBAAgBqQ,oBAAmB5f,UAAWpI,KAAKoI,aAGvD/H,GAAYL,KAAKof,gBAAgB3I,yBAG7D,MAAMwR,QAAqBjoB,KAAKif,IAAI7R,aAChC4a,GAAqB,GACrBrQ,EACA3X,KAAKoI,WAKT,GAAI6f,EAAaC,cAAgBD,EAAaE,gBAAiB,CAC3D,MAAMC,EAAqBH,EAAaC,cAAgBF,EACxD,GAAII,GAAsBA,IAAuBJ,EAAmB,CAEhE,MAAMK,EAAa,6BAGnB,GAFAroB,KAAKwf,UAAU6I,EAAYD,EAAoB,KAE3C/nB,EACA,IACIuB,aAAaS,QAAQgmB,EAAYD,EACrC,CAAE,MAAOtnB,GACLkC,EAAS,qDAAsDlC,EACnE,CAEJkC,EAAS,wEAAwEolB,6BAA8CJ,KACnI,CACJ,CAGA,OAAOA,GAAqB,EAChC,CAIO,iBAAAM,GACH,MAAO,IAAKtoB,KAAK2X,eACrB,CAEO,WAAMyE,GAGT,IAAK/b,EAAW,OAGhB,GAAIL,KAAKka,UAEL,YADAlX,EAAS,gEAGbhD,KAAKka,WAAY,EAKjBla,KAAKke,uBAA4D,OAAnCle,KAAKyc,0BAC7Bzc,KAAKyc,0BACL7b,KAAKsB,MACXlC,KAAKie,QAAU,UAGfje,KAAK4c,cAAgBtc,OAAOia,YAAY,KACpCva,KAAKqnB,eACNrnB,KAAK6c,mBAGJ7c,KAAKod,2BACLpd,KAAK6b,wBAIL7b,KAAKqd,2BACLrd,KAAK8b,wBAIT9b,KAAK0lB,yBAIL,MAAM6C,EAAiB,KAEnB,GAAIvoB,KAAK4d,eAEL,YADA5a,EAAS,0DAIbA,EAAS,4CAGThD,KAAK8d,YAAc0K,EAEnB,MAAM5K,EAAiB4K,EAAO,CAC9BC,KAAOhhB,IACHzH,KAAK0oB,kBAAkBjhB,GAGJ,IAAfA,EAAMnB,MACNtD,EAAS,iCAAgC,IAAIpC,MAAOC,kBAI5D8nB,iBAAkB3oB,KAAKmf,iBAAiB7M,4BAAyBtQ,EACjE4mB,gBAAY5mB,EACZ6mB,cAA4D,kBAA7C7oB,KAAKmf,iBAAiB/M,mBACrC0W,iBAAkB,CAEdC,UAAU,EACVtkB,MAAM,EACNukB,UAAU,EACVvb,OAAO,EACPwb,QAAQ,EACRC,KAAK,EACLnlB,KAAK,EACL+R,QAAQ,EACRqT,MAAM,EACNC,MAAM,EACNC,OAAO,EACPC,MAAM,GAGVC,YAAa,CAAC9kB,EAAMmO,KAChB,IACI,MAAM/B,EAAO7Q,KAAKmf,iBAAiB/M,mBAEnC,KAAMQ,aAAmB4W,aAAc,OAAO/kB,EAE9C,GAAa,kBAAToM,EAA0B,MAAO,IAAI4Y,OAAOhlB,EAAK1C,QAAU,GAE/D,MAAM2nB,EAAa1pB,KAAKmf,iBAAiB7L,sBAAsBV,GAEnDA,EAAwB2P,GACtB3P,EAA6BzL,KAC7ByL,EAA6BtM,KAC3C,OAAOojB,EAAajlB,EAAO,IAAIglB,OAAOhlB,EAAK1C,QAAU,EACzD,CAAE,MACE,OAAO0C,CACX,GAEJklB,eAAgB,CAAA,EAEhBC,cAAc,EACdC,kBAAkB,EAClBC,0BAA0B,EAG1B/N,aAAc/b,KAAK+b,aACnBgO,SAAU/pB,KAAK+b,aAAe,CAAEiO,OAAQ,QAAMhoB,EAC9CioB,eAAgBjqB,KAAK+b,aAAe,CAChCzV,KAAM,aACN4jB,QAAS,SACTloB,EAIJmoB,MAAO,CAEHpF,MAAQtd,IACJ,IAGI,GAAa,kBAFAzH,KAAKmf,iBAAiB/M,mBAEL,OAC9B,MAAMgY,EAA2B,oBAAbpe,SAChBA,SAASqe,cAAc,mBAAoB5iB,EAAc8a,QACzD,KACJ,GAAI6H,GAAQA,aAAgBZ,YAAa,CAClBxpB,KAAKmf,iBAAiB7L,sBAAsB8W,UAGxB,IAAvB3iB,EAAchD,OACrBgD,EAAchD,KAAO,IAAIglB,OAAQhiB,EAAchD,MAAM1C,QAAU,SAEhC,IAAxB0F,EAAckB,QACrBlB,EAAckB,MAAQ,IAAI8gB,OAAQhiB,EAAckB,OAAO5G,QAAU,IAG9E,CACJ,CAAE,MAAO,MAMrB/B,KAAK4d,eAAiBA,GAAkB,MAKxC,GADA5a,EAAS,uBAAuBgJ,SAASwG,cACb,aAAxBxG,SAASwG,YAAqD,gBAAxBxG,SAASwG,WAE/CxP,EAAS,iBAAiBgJ,SAASwG,+CACnC+V,QACG,CAEHvlB,EAAS,wDAET,MAAMsnB,EAAgB,KACU,gBAAxBte,SAASwG,YAAwD,aAAxBxG,SAASwG,cAClDxP,EAAS,iBAAiBgJ,SAASwG,mCACnC+V,KACO,GAMf,GAAI+B,IAAiB,OAGrBte,SAAStI,iBAAiB,mBAAoB,KAC1CV,EAAS,iDACTulB,KACD,CAAEgC,MAAM,IAGX,MAAMjQ,EAAWC,YAAY,KACrB+P,KACA9P,cAAcF,IAEnB,IAGH3U,WAAW,IAAM6U,cAAcF,GAAW,IAC9C,CACJ,CAMQ,gBAAAgG,GAEAtgB,KAAK+d,qBACLrY,aAAa1F,KAAK+d,qBAItB/d,KAAK+d,oBAAsBzd,OAAOqF,WAAW,KAEzC6kB,sBAAsB,KAClBA,sBAAsB,KAClB,IAEQxqB,KAAK8d,aAA4D,mBAAtC9d,KAAK8d,YAAYwC,kBAC5CtgB,KAAK8d,YAAYwC,mBACjBtd,EAAS,kDAETF,EAAQ,uDAEhB,CAAE,MAAOhC,GACL+B,EAAS,iCAAkC/B,EAC/C,OAGT,IACP,CAEO,UAAM2pB,SACHzqB,KAAKigB,oBACN5f,IAEDL,KAAK4c,gBACLpC,cAAcxa,KAAK4c,eACnB5c,KAAK4c,cAAgB,MAIrB5c,KAAK4d,iBACL5d,KAAK4d,iBACL5d,KAAK4d,eAAiB,MAItB5d,KAAK+d,sBACLrY,aAAa1F,KAAK+d,qBAClB/d,KAAK+d,oBAAsB,MAG/B/d,KAAK8d,YAAc,KAGnB9d,KAAKsmB,yBAGLtmB,KAAK0kB,4BAeT,CAMO,cAAM1J,CAASvT,GAalB,GARIpH,GACAL,KAAKohB,yBAOJ3Z,GAA0B,iBAAVA,EAArB,CAMA,GAAmB,IAAfA,EAAMnB,KAAY,CAClB,MAAMokB,IAAYjjB,EAAMgB,KAClBkiB,KAAaljB,EAAMgB,OAAQhB,EAAMgB,KAAK2hB,MAKxCpnB,EAHC0nB,GAAYC,EAGJ,iCAAiCD,cAAoBC,eAAqBljB,EAAMgB,MAAM2hB,MAAM9jB,OAF5F,2CAA2CokB,cAAoBC,yBAIhF,CAGI3qB,KAAKqc,WAAWta,QAAU/B,KAAKkf,iBAE/Blf,KAAKqc,WAAW3U,QAChB1E,EAAS,gDAGbhD,KAAKqc,WAAWla,KAAKsF,GAGF,IAAfA,EAAMnB,MACNtD,EAAS,kDACThD,KAAKqnB,eAGArnB,KAAKqc,WAAWta,QAAgC,GAAtB/B,KAAKkf,iBACpClc,EAAS,YAAYhD,KAAKqc,WAAWta,UAAU/B,KAAKkf,8CACpDlf,KAAKqnB,cA/BT,MAFIrkB,EAAS,6BAA8ByE,EAmC/C,CAMQ,kBAAAggB,GAEJ,MAAMmD,EAAe5qB,KAAK0c,wBAA0B1c,KAAK6d,iBACzD,IAAK+M,EACD,OAAO,KAIX,MAAMC,EAAuB7qB,KAAKqc,WAAWvW,OAAQvD,GAAWA,GAAKA,EAAEN,WACvE,GAAoC,IAAhC4oB,EAAqB9oB,OACrB,OAAO,KAGX,MAAM+oB,EAAkBD,EAAqBE,OAAO,CAACC,EAAaC,KACrDD,GAAWC,EAAQhpB,WAAagpB,EAAQhpB,UAAY+oB,EAAO/oB,UAAcgpB,EAAUD,EAC7F,MAEH,IAAKF,IAAoBA,EAAgB7oB,UACrC,OAAO,KAIX,MAAM8M,EAAW+b,EAAgB7oB,UAAY2oB,EAC7C,OAAO7b,GAAY,EAAIA,EAAW,IACtC,CAMQ,8BAAAsS,GACJ,MAAMkG,EAAkBvnB,KAAKge,4BACvBwJ,EAAkBxnB,KAAKynB,qBAM7B,SALsD,OAApBD,GAA4BA,GAAmB,GAG7EA,EAAkBD,KAGlBvkB,EAAS,qBAAqBwkB,uBAAqCD,wBAC5D,EAIf,CAMQ,iBAAMF,GAGV,GAAIrnB,KAAK2c,aACL,OAIJ,GAAI3c,KAAKyJ,oBACL,OAKJ,IAAqB,IAAjBzJ,KAAKie,SAA+C,IAA3Bje,KAAKqc,WAAWta,OACzC,OAIJ,MAAMwlB,EAAkBvnB,KAAKge,4BACvBwJ,EAAkBxnB,KAAKynB,qBAM7B,GALsD,OAApBD,GAA4BA,GAAmB,GAG7EA,EAAkBD,EASlB,OALAvkB,EAAS,qBAAqBwkB,uBAAqCD,wBAEnE5hB,WAAW,KACP3F,KAAKqnB,eACN,KAIPrnB,KAAK2c,cAAe,EACpB,IAII,MAAMuO,EAAkBlrB,KAAKqc,WACvB8O,EAAuBD,EAAgBplB,OAAOvD,GAAKA,GAAgB,IAAXA,EAAE+D,MAchE,GAbAtG,KAAKqc,WAAa,GAId8O,EAAqBppB,OAAS,GAAK1B,IAElCC,OAAeqnB,uBAAyBwD,EAEzCxlB,WAAW,YACCrF,OAAeqnB,wBACxB,MAGHuD,EAAgBnpB,OAAS,EAAG,CAC5BiB,EAAS,mBAAoBkoB,GAG7B,MAAME,EAAgBF,EAAgBplB,OAAOvD,GAAgB,IAAXA,EAAE+D,MAChD8kB,EAAcrpB,OAAS,GACvBiB,EAAS,mBAAmBooB,EAAcrpB,0CAG9C,IAII,MAAMyI,EAAsBxK,KAAKof,gBAAgB3I,+BAC3CzW,KAAKif,IAAI3U,kBACX4gB,EACAlrB,KAAKoI,UACLpI,KAAK0J,UACL1J,KAAKuK,SACLC,EAER,CAAE,MAAO1J,GAEL,GAAIA,EAAMJ,SAAS4I,SAAS,oCACxBxG,EAAQ,6CACL,GAAIhC,EAAMJ,SAAS4I,SAAS,QAAUxI,EAAMJ,SAAS4I,SAAS,qBACjExG,EAAQ,8CACL,MAAIhC,EAAMJ,SAAS4I,SAAS,0BACxBxI,EAAMJ,SAAS4I,SAAS,oBACxBxI,EAAMJ,SAAS4I,SAAS,iBAG/B,MAAMxI,EAFNgC,EAAQ,sEAGZ,CACJ,CACJ,OAKM9C,KAAKshB,iCACLthB,KAAKulB,yBACLvlB,KAAKqlB,2BACf,SACIrlB,KAAK2c,cAAe,CACxB,CACJ,CAKQ,kBAAA0O,CAAmB5jB,GAEvB,GAAmB,IAAfA,EAAMnB,KACN,OAAO,EAOX,MAEMglB,EAAS7jB,EAAMgB,MAAM6iB,OAC3B,MAHuB,CAAC,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,IAGpBhiB,SAASgiB,EACnC,CAMQ,eAAAC,CAAgB9jB,GACpB,MAAM+jB,EAAoBxrB,KAAKqrB,mBAAmB5jB,GAC5CgkB,EAAchkB,EAAMxF,WAAarB,KAAKsB,MAG5C,GAAIspB,EAAmB,CACnB,MAAME,GAA2B,IAAjB1rB,KAAKie,QACrBje,KAAKke,uBAAyBuN,EAKS,OAAnCzrB,KAAKyc,4BACLzc,KAAKyc,0BAA4BgP,GAIjCC,GACA1oB,EAAS,gDACThD,KAAKie,SAAU,EAGXje,KAAK8d,aAA4D,mBAAtC9d,KAAK8d,YAAYwC,mBAC5CtgB,KAAK8d,YAAYwC,mBACjBtd,EAAS,oDAEW,YAAjBhD,KAAKie,UAEZje,KAAKie,SAAU,EAEvB,MAAO,IAAqB,IAAjBje,KAAKie,QAAkB,CAI9B,MAAM0N,EAAwBF,EAAczrB,KAAKke,uBAC7CyN,EAAwB3rB,KAAKme,oBAC7Bnb,EAAS,6BAA6B+B,KAAKQ,MAAMomB,EAAwB,oEACzE3oB,EAAS,0DAA0D+B,KAAKQ,OAAO,IAAiBomB,GAAyB,oBACzH3rB,KAAKie,SAAU,EAGfje,KAAKqnB,cAEb,CACJ,CAMO,uBAAMqB,CAAkBjhB,GAa3B,GARIpH,GACAL,KAAKohB,yBAOJ3Z,GAA0B,iBAAVA,GAUrB,GAJAzH,KAAKurB,gBAAgB9jB,IAIA,IAAjBzH,KAAKie,SAAmC,IAAfxW,EAAMnB,MAAetG,KAAKqrB,mBAAmB5jB,GAA1E,CAMA,GAAmB,IAAfA,EAAMnB,KAAY,CAClB,MAAMokB,IAAYjjB,EAAMgB,KAClBkiB,KAAaljB,EAAMgB,OAAQhB,EAAMgB,KAAK2hB,MAKxCpnB,EAHC0nB,GAAYC,EAGJ,iCAAiCD,cAAoBC,eAAqBljB,EAAMgB,MAAM2hB,MAAM9jB,OAF5F,2CAA2CokB,cAAoBC,yBAIhF,CAII3qB,KAAKqc,WAAWta,QAAU/B,KAAKkf,iBAE/Blf,KAAKqc,WAAW3U,QAChB1E,EAAS,gDAGbhD,KAAKqc,WAAWla,KAAKsF,GAGF,IAAfA,EAAMnB,MACNtD,EAAS,kDACThD,KAAKqnB,gBAGiB,IAAjBrnB,KAAKie,SAAoBje,KAAKqc,WAAWta,QAAgC,GAAtB/B,KAAKkf,iBAC7Dlc,EAAS,YAAYhD,KAAKqc,WAAWta,UAAU/B,KAAKkf,8CACpDlf,KAAKqnB,cAhCT,OAZIrkB,EAAS,uCAAwCyE,EA8CzD,CAOQ,qBAAAmkB,GACJ,IAAKvrB,EAAW,OAAO,EACvB,IACI,MAAM0T,EAAO,0BAGb,OAFAyF,eAAenX,QAAQ0R,EAAMA,GAC7ByF,eAAe9W,WAAWqR,IACnB,CACX,CAAE,MACE,OAAO,CACX,CACJ,CAMQ,uBAAA8X,GACJ,IAAK7rB,KAAK4rB,wBACN,OAAO,KAEX,IACI,OAAOpS,eAAe3X,QAAQ7B,KAAK0f,uBACvC,CAAE,MACE,OAAO,IACX,CACJ,CAKQ,qBAAAoM,CAAsBvhB,GAC1B,GAAKvK,KAAK4rB,wBAGV,IACIpS,eAAenX,QAAQrC,KAAK0f,uBAAwBnV,GACpDvH,EAAS,sCAAsCuH,IACnD,CAAE,MAAOzJ,GACLgC,EAAQ,8CAA+ChC,EAC3D,CACJ,CAKQ,0BAAAirB,GACJ,GAAK/rB,KAAK4rB,wBAGV,IACIpS,eAAe9W,WAAW1C,KAAK0f,uBACnC,CAAE,MAAO5e,GACLgC,EAAQ,iDAAkDhC,EAC9D,CACJ,CAMQ,uBAAAkrB,GACJ,IAAKhsB,KAAK4rB,wBACN,OAAO,EAEX,IACI,MAA2E,SAApEpS,eAAe3X,QAAQ7B,KAAK2f,mCACvC,CAAE,MACE,OAAO,CACX,CACJ,CAMQ,uBAAAsM,CAAwBtjB,GAC5B,GAAK3I,KAAK4rB,wBAGV,IACQjjB,EACA6Q,eAAenX,QAAQrC,KAAK2f,mCAAoC,QAEhEnG,eAAe9W,WAAW1C,KAAK2f,mCAEvC,CAAE,MAAO7e,GACLgC,EAAQ,4CAA6ChC,EACzD,CACJ,CAOQ,mBAAA+e,GACJ,IAAKxf,EACD,OAAOqO,IAGX,MAAMwd,EAAelsB,KAAK6rB,0BACpBM,EAAsBnsB,KAAKgsB,0BAEjC,GAAIE,IAAiBC,EAMjB,OAHAnpB,EAAS,6CAA6CkpB,KACtDlsB,KAAK8rB,sBAAsBI,GAC3BlsB,KAAKisB,yBAAwB,GACtBC,EACJ,CAIH,MAAME,EAAc1d,IAIpB,OAHA1L,EAAS,0BAA0BopB,+BACnCpsB,KAAK8rB,sBAAsBM,GAC3BpsB,KAAKisB,yBAAwB,GACtBG,CACX,CACJ,CAMQ,yBAAAtM,GACCzf,GAKLC,OAAOoD,iBAAiB,eAAgB,KAChC1D,KAAK4rB,0BACL5rB,KAAKisB,yBAAwB,GAC7BjpB,EAAS,wDAEd,CAAEqX,SAAS,GAClB,CAGQ,SAAAmF,CAAUrY,EAAcwB,EAAe0jB,GAC3C,GAAKhsB,EAEL,IAEI,MAAM8oB,EAAO,IAAIvoB,KACjBuoB,EAAKmD,QAAQnD,EAAKoD,UAA4B,GAAfF,EAAoB,GAAK,GAAK,KAC7D,MAAMG,EAAU,WAAWrD,EAAKsD,gBAChCzgB,SAAS0gB,OAAS,GAAGvlB,KAAQwB,KAAS6jB,wBAGtC5qB,aAAaS,QAAQ8E,EAAMwB,GAC3B3F,EAAS,gCAAgCmE,IAC7C,CAAE,MAAOrG,GAEL,IACIc,aAAaS,QAAQ8E,EAAMwB,GAC3B3F,EAAS,uCAAuCmE,IACpD,CAAE,MAAOwlB,GACL9pB,EAAS,2DAA4D8pB,EACzE,CACJ,CACJ,CAEO,SAAApN,CAAUpY,GACb,IAAK9G,EAAW,OAAO,KAEvB,IAEI,MAAMusB,EAASzlB,EAAO,IAChB0lB,EAAK7gB,SAAS0gB,OAAO1F,MAAM,KACjC,IAAK,IAAIE,EAAI,EAAGA,EAAI2F,EAAG9qB,OAAQmlB,IAAK,CAChC,IAAI4F,EAAID,EAAG3F,GACX,KAAuB,MAAhB4F,EAAEC,OAAO,IAAYD,EAAIA,EAAE1e,UAAU,EAAG0e,EAAE/qB,QACjD,GAA0B,IAAtB+qB,EAAEE,QAAQJ,GAAe,CACzB,MAAMK,EAAcH,EAAE1e,UAAUwe,EAAO7qB,OAAQ+qB,EAAE/qB,QAEjD,OADAiB,EAAS,iBAAiBmE,KACnB8lB,CACX,CACJ,CAGA,MAAMC,EAAoBtrB,aAAaC,QAAQsF,GAC/C,OAAI+lB,GACAlqB,EAAS,yCAAyCmE,KAC3C+lB,GAGJ,IACX,CAAE,MAAOpsB,GAEL,IACI,MAAMosB,EAAoBtrB,aAAaC,QAAQsF,GAC/C,GAAI+lB,EAEA,OADAlqB,EAAS,6CAA6CmE,KAC/C+lB,CAEf,CAAE,MAAOP,GACL9pB,EAAS,iDAAkD8pB,EAC/D,CACA,OAAO,IACX,CACJ,CAMQ,YAAAQ,CAAahmB,GACjB,GAAK9G,EAAL,CAEA,IAEI2L,SAAS0gB,OAAS,GAAGvlB,kEACrBnE,EAAS,mBAAmBmE,IAChC,CAAE,MAAOrG,GACL+B,EAAS,4BAA4BsE,IAAQrG,EACjD,CAGA,IACIc,aAAac,WAAWyE,GACxBnE,EAAS,8BAA8BmE,IAC3C,CAAE,MAAOrG,GACL+B,EAAS,uCAAuCsE,IAAQrG,EAC5D,CAhBgB,CAiBpB,CAOO,MAAAssB,GACH,GAAK/sB,EAEL,IAEI,MAAMgtB,EAAmB,6BACzBrtB,KAAKmtB,aAAaE,GAGlB,MAAMC,EAAa,yBACnB1rB,aAAac,WAAW4qB,GAGxBttB,KAAK0J,UAAY,KACjB1J,KAAK2X,eAAiB,CAAA,EAGtB3X,KAAK0J,UAAYgF,IACjB1O,KAAKwf,UAAU,6BAA8Bxf,KAAK0J,UAAW,KAC7D1J,KAAKoI,UAAYpI,KAAKutB,iBAAiBD,GAEvCttB,KAAKuK,SAAWmE,IAChB1O,KAAK8rB,sBAAsB9rB,KAAKuK,UAChCvK,KAAKif,IAAI9U,mBAAmBnK,KAAKoI,UAAWpI,KAAK0J,WAEjD1J,KAAKsgB,mBAELvd,EAAQ,oEACZ,CAAE,MAAOjC,GACL+B,EAAS,uBAAwB/B,EACrC,CACJ,CAMO,YAAM0sB,CAAO3pB,SACV7D,KAAKigB,oBACN5f,EAMLL,KAAKmf,iBAAmB,IAAI9O,EAAiBxM,GALzCf,EAAQ,sDAMhB,CAMO,iBAAA2qB,CAAkBnc,GACrBtR,KAAKmf,iBAAiBhO,kBAAkBG,GAGpCtR,KAAK4d,gBACL5d,KAAK0tB,yBAEb,CAMO,mBAAA1R,CAAoB1K,GACvBtR,KAAKmf,iBAAiBpO,oBAAoBO,GAGtCtR,KAAK4d,gBACL5d,KAAK0tB,yBAEb,CAEQ,uBAAAA,GACA1tB,KAAK4d,iBACL5d,KAAK4d,iBACL5d,KAAKoc,QAEb,CAKO,mBAAAjK,GACH,OAAOnS,KAAKmf,iBAAiBhN,qBACjC,CAKO,mBAAAE,GACH,OAAOrS,KAAKmf,iBAAiB9M,qBACjC,CAMO,YAAAnB,CAAaI,GAChBtR,KAAKmf,iBAAiBjO,aAAaI,GAG/BtR,KAAK4d,gBACL5d,KAAK0tB,yBAEb,CAKO,qBAAAzb,GACHjS,KAAKmf,iBAAiBlN,wBAGlBjS,KAAK4d,gBACL5d,KAAK0tB,yBAEb,CAMQ,sBAAAtM,GACJ,IAAK/gB,EAAW,OAEhB,MAAMitB,EAAa,yBACbprB,EAAMtB,KAAKsB,MAIX0E,EAAS5G,KAAK2tB,iBAAiBL,GAErC,IAAK1mB,IAAWA,EAAOwB,UAUnB,OARApI,KAAKutB,iBAAiBD,GAEtBttB,KAAKuK,SAAWmE,IAChB1O,KAAK8rB,sBAAsB9rB,KAAKuK,UAChCvK,KAAKif,IAAI9U,mBAAmBnK,KAAKoI,UAAWpI,KAAK0J,WAEjD1J,KAAKsgB,wBACLtd,EAAS,4CAA4ChD,KAAKoI,aAM9DpI,KAAK4tB,sBAAsBN,EAAYprB,EAAK0E,EAAOwB,UAAWxB,EAAOinB,sBACzE,CAOQ,oBAAAjO,GACJ,IAAKvf,EACD,OAAOqO,IAGX,MAAM4e,EAAa,yBACbprB,EAAMtB,KAAKsB,MAGX0E,EAAS5G,KAAK2tB,iBAAiBL,GAErC,IAAK1mB,IAAWA,EAAOwB,UAAW,CAC9B,MAAM0lB,EAAe9tB,KAAKutB,iBAAiBD,GAE3C,OADAttB,KAAKif,IAAI9U,mBAAmB2jB,EAAc9tB,KAAK0J,WACxCokB,CACX,CAGA,MAGMC,EAAoB7rB,EAAM0E,EAAOonB,sBACjCC,EAAa/rB,EAAM0E,EAAOinB,sBAEhC,GAAIE,EAN4B,KAMmBE,EALrB,MAKyD,CACnFjrB,EAAS,yBAAyB+qB,YAA4BE,OAC9D,MAAMH,EAAe9tB,KAAKutB,iBAAiBD,GAE3C,OADAttB,KAAKif,IAAI9U,mBAAmB2jB,EAAc9tB,KAAK0J,WACxCokB,CACX,CAKA,OADA9tB,KAAK4tB,sBAAsBN,EAAYprB,EAAK0E,EAAOwB,UAAWxB,EAAOinB,uBAC9DjnB,EAAOwB,SAClB,CAKQ,gBAAAulB,CAAiBtkB,GACrB,MAAMnH,EAAMtB,KAAKsB,MACXgsB,EAA0B,IAC1BC,EAAwB,MAI9B,GAAInuB,KAAKoI,WAAgD,OAAnCpI,KAAKyc,2BAAsE,OAAhCzc,KAAK0c,uBAAiC,CACnG,MAAMqR,EAAoB7rB,EAAMlC,KAAKyc,0BAC/BwR,EAAa/rB,EAAMlC,KAAK0c,uBAG9B,KAAIqR,EAAoBG,GAA2BD,EAAaE,GAqB5D,MAAO,CACH/lB,UAAWpI,KAAKoI,UAChB4lB,sBAAuBhuB,KAAKyc,0BAC5BoR,sBAAuB7tB,KAAK0c,wBAxBmD,CACnF1Z,EAAS,+DACT,MAAMorB,EAAepuB,KAAKoI,UAU1B,GATApI,KAAKutB,iBAAiBlkB,GAEtBrJ,KAAKuK,SAAWmE,IAChB1O,KAAK8rB,sBAAsB9rB,KAAKuK,UAChCvK,KAAKif,IAAI9U,mBAAmBnK,KAAKoI,UAAWpI,KAAK0J,WAEjD1J,KAAKsgB,mBACLvd,EAAQ,oDAAoD/C,KAAKoI,wBAAwBgmB,MAElD,OAAnCpuB,KAAKyc,2BAAsE,OAAhCzc,KAAK0c,uBAChD,MAAO,CACHtU,UAAWpI,KAAKoI,UAChB4lB,sBAAuBhuB,KAAKyc,0BAC5BoR,sBAAuB7tB,KAAK0c,uBAGxC,CAQJ,CAGA,IACI,MAAM9V,EAAShF,aAAaC,QAAQwH,GACpC,IAAKzC,EAAQ,OAAO,KACpB,MAAMmJ,EAASrO,KAAKC,MAAMiF,GAGpBmnB,EAAoB7rB,EAAM6N,EAAOie,sBACjCC,EAAa/rB,EAAM6N,EAAO8d,sBAEhC,GAAIE,EAAoBG,GAA2BD,EAAaE,EAAuB,CAEnFnrB,EAAS,yCAAyC+B,KAAKQ,MAAMwoB,EAAoB,IAAO,eAAehpB,KAAKQ,MAAM0oB,EAAa,IAAO,GAAK,UAC3I,MAAMG,EAAere,EAAO3H,UAU5B,GATApI,KAAKutB,iBAAiBlkB,GAEtBrJ,KAAKuK,SAAWmE,IAChB1O,KAAK8rB,sBAAsB9rB,KAAKuK,UAChCvK,KAAKif,IAAI9U,mBAAmBnK,KAAKoI,UAAWpI,KAAK0J,WAEjD1J,KAAKsgB,mBACLvd,EAAQ,0DAA0D/C,KAAKoI,wBAAwBgmB,MAExD,OAAnCpuB,KAAKyc,2BAAsE,OAAhCzc,KAAK0c,uBAChD,MAAO,CACHtU,UAAWpI,KAAKoI,UAChB4lB,sBAAuBhuB,KAAKyc,0BAC5BoR,sBAAuB7tB,KAAK0c,uBAGxC,CASA,OANI3M,EAAO3H,YACPpI,KAAKoI,UAAY2H,EAAO3H,UACxBpI,KAAKyc,0BAA4B1M,EAAOie,sBACxChuB,KAAK0c,uBAAyB3M,EAAO8d,uBAGlC9d,CACX,CAAE,MACE,OAAO,IACX,CACJ,CAKQ,gBAAAwd,CAAiBlkB,GACrB,MAAMjB,EAAYsG,IACZxM,EAAMtB,KAAKsB,MAGjBlC,KAAKoI,UAAYA,EACjBpI,KAAKyc,0BAA4Bva,EACjClC,KAAK0c,uBAAyBxa,EAI9BlC,KAAKke,uBAAyBhc,EAG9B,MAAM2X,EAAU,CACZzR,YACA4lB,sBAAuB9rB,EACvB2rB,sBAAuB3rB,GAE3B,IACAN,aAAaS,QAAQgH,EAAK3H,KAAKY,UAAUuX,GACzC,CAAE,MAAOtX,GACLO,EAAQ,2CAA2CP,IACvD,CAGA,OADAS,EAAS,wBAAwBoF,KAC1BA,CACX,CAOQ,qBAAAwlB,CAAsBvkB,EAAapH,EAAmBmG,EAAmBylB,GAE7E7tB,KAAKoI,UAAYA,EACjBpI,KAAKyc,0BAA4Bxa,EACjCjC,KAAK0c,uBAAyBmR,EAQ9B,MAAMhU,EAAU,CACZzR,YACA4lB,sBAAuB/rB,EACvB4rB,yBAEJ,IACAjsB,aAAaS,QAAQgH,EAAK3H,KAAKY,UAAUuX,GACzC,CAAE,MAAOtX,GACLO,EAAQ,6CAA6CP,IACzD,CACJ,CAKO,YAAA8rB,GACH,OAAOruB,KAAKoI,SAChB,CAKO,aAAAkmB,GACH,OAAOtuB,KAAKoW,UAChB,CAMO,wBAAAmY,GAQH,MAAO,CACH/G,gBAHoB5mB,KAAKsB,MAAQlC,KAAK6d,iBAItC2Q,gBAAiB,IACjBC,iBAAkB,IAClBC,MAAO,aAEf,CAKO,oBAAMC,GACT,IAEI,aADM3uB,KAAKif,IAAIvT,KAAK1L,KAAKoI,UAAWpI,KAAK0J,WAClC,CAAEklB,SAAS,EACtB,CAAE,MAAO9tB,GACL,MAAO,CACH8tB,SAAS,EACT9tB,MAAOA,EAAMJ,SAAW,gBAEhC,CACJ,CAKO,mBAAAmuB,GAIH,MAAMC,EAA4B,GAClC,IAAIC,GAAU,EAwBd,OArBI/uB,KAAKqc,WAAWta,OAAS,IACzBgtB,GAAU,EACVD,EAAgB3sB,KAAK,gDAIrBnC,KAAK2d,qBACLoR,GAAU,EACVD,EAAgB3sB,KAAK,8DAIH,oBAAX7B,QACPwuB,EAAgB3sB,KAAK,2CAIW,IAAzBqB,UAAU2C,YACjB2oB,EAAgB3sB,KAAK,kDAGlB,CAAE4sB,UAASD,kBACtB,CAMO,iBAAAE,GACH,IAAK3uB,EACD,OAAO,EAIX,MAAMif,EAAoBtf,KAAKuf,UAAU,8BACzC,OAA6B,OAAtBD,GAA8BA,IAAsBtf,KAAK0J,SACpE,CAKO,WAAAulB,GAMH,MAAO,CACHvlB,UAAW1J,KAAK0J,UAChBtB,UAAWpI,KAAKoI,UAChB4mB,kBAAmBhvB,KAAKgvB,oBACxBlS,YAAa9c,KAAK8c,YAE1B,CAOO,kBAAAvE,CAAmBlP,EAAaV,GACnC3I,KAAKof,gBAAgB7G,mBAAmBlP,EAAKV,EACjD,CAKO,oBAAAiQ,CAAqBP,GACxBrY,KAAKof,gBAAgBxG,qBAAqBP,EAC9C,CAKO,kBAAAQ,CAAmBxP,GACtB,OAAOrJ,KAAKof,gBAAgBvG,mBAAmBxP,EACnD,CAKO,qBAAAyP,CAAsBzP,GACzBrJ,KAAKof,gBAAgBtG,sBAAsBzP,EAC/C,CAKO,eAAA0P,CAAgB1P,EAAaV,GAChC3I,KAAKof,gBAAgBrG,gBAAgB1P,EAAKV,EAC9C,CAKO,iBAAAqQ,CAAkBX,GACrBrY,KAAKof,gBAAgBpG,kBAAkBX,EAC3C,CAKO,eAAAY,CAAgB5P,GACnB,OAAOrJ,KAAKof,gBAAgBnG,gBAAgB5P,EAChD,CAKO,kBAAA6P,CAAmB7P,GACtBrJ,KAAKof,gBAAgBlG,mBAAmB7P,EAC5C,CAKO,OAAA8P,CAAQ9P,EAAaV,EAAYyQ,EAA4B,QAChEpZ,KAAKof,gBAAgBjG,QAAQ9P,EAAKV,EAAOyQ,EAC7C,CAKO,sBAAAC,GACHrZ,KAAKof,gBAAgB/F,wBACzB,CAKO,mBAAAC,GACHtZ,KAAKof,gBAAgB9F,qBACzB,CAKO,gBAAAK,GAMH,OAAO3Z,KAAKof,gBAAgBzF,kBAChC,ECh+GE,SAAUuV,EAAqBvX,GACnC,MAAMwX,EAAiBC,WAAmB3T,6BAE1C,OAAI0T,GAAelU,aACVkU,EAAclU,aAAa,CAAEtD,oBAEpC3W,QAAQE,KAAK,sEACN,KAEX,CAQM,SAAUmuB,EAAkBthB,EAAmBsK,GACnD,MAAM8W,EAAiBC,WAAmB3T,6BAE1C,OAAI0T,GAAeG,MACVH,EAAcG,MAAMvhB,EAAWsK,IAEtCrX,QAAQE,KAAK,sEACN,KAEX,UAMgBquB,IACd,MAAMJ,EAAiBC,WAAmB3T,6BAC1C,QAAU0T,GAAelU,YAC3B,CDi8GI5a,IACCC,OAAe0Z,qBAAuBA"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/utils/logger.ts","../src/retry-queue.ts","../src/persistence.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// Global flag to track if SDK is currently logging (prevents self-tracking)\nlet sdkLoggingInProgress = false;\n\n// Export getter for tracker to check\nexport const isSDKLogging = (): boolean => sdkLoggingInProgress;\n\n// Export convenience methods with SDK logging flag\nexport const logError = (message: string, ...args: any[]) => {\n sdkLoggingInProgress = true;\n try {\n logger.error(message, ...args);\n } finally {\n sdkLoggingInProgress = false;\n }\n};\n\nexport const logWarn = (message: string, ...args: any[]) => {\n sdkLoggingInProgress = true;\n try {\n logger.warn(message, ...args);\n } finally {\n sdkLoggingInProgress = false;\n }\n};\n\nexport const logInfo = (message: string, ...args: any[]) => {\n sdkLoggingInProgress = true;\n try {\n logger.info(message, ...args);\n } finally {\n sdkLoggingInProgress = false;\n }\n};\n\nexport const logDebug = (message: string, ...args: any[]) => {\n sdkLoggingInProgress = true;\n try {\n logger.debug(message, ...args);\n } finally {\n sdkLoggingInProgress = false;\n }\n}; ","import { logWarn, logError, logDebug } from './utils/logger';\n\nconst THIRTY_MINUTES = 30 * 60 * 1000;\nconst KEEP_ALIVE_THRESHOLD = 64 * 1024 * 0.8; // 64KB * 0.8 for safety margin\n\n/**\n * Generates a jittered exponential backoff delay in milliseconds\n * \n * The base value is 3 seconds, which is doubled with each retry\n * up to the maximum of 30 minutes\n * \n * Each value then has +/- 50% jitter\n * \n * Giving a range of 3 seconds up to 45 minutes\n */\nexport function pickNextRetryDelay(retriesPerformedSoFar: number): number {\n const rawBackoffTime = 3000 * 2 ** retriesPerformedSoFar;\n const minBackoff = rawBackoffTime / 2;\n const cappedBackoffTime = Math.min(THIRTY_MINUTES, rawBackoffTime);\n const jitterFraction = Math.random() - 0.5; // A random number between -0.5 and 0.5\n const jitter = jitterFraction * (cappedBackoffTime - minBackoff);\n return Math.ceil(cappedBackoffTime + jitter);\n}\n\nexport interface RetriableRequestOptions {\n url: string;\n method?: string;\n headers?: Record<string, string>;\n body?: string | Blob;\n retriesPerformedSoFar?: number;\n estimatedSize?: number;\n callback?: (response: { statusCode: number; text: string; json?: any }) => void;\n}\n\ninterface RetryQueueElement {\n retryAt: number;\n requestOptions: RetriableRequestOptions;\n}\n\nexport class RetryQueue {\n private _isPolling: boolean = false;\n private _poller: ReturnType<typeof setTimeout> | undefined;\n private _pollIntervalMs: number = 3000;\n private _queue: RetryQueueElement[] = [];\n private _areWeOnline: boolean;\n private _sendRequest: (options: RetriableRequestOptions) => Promise<void>;\n\n constructor(sendRequest: (options: RetriableRequestOptions) => Promise<void>) {\n this._queue = [];\n this._areWeOnline = true;\n this._sendRequest = sendRequest;\n\n if (typeof window !== 'undefined' && 'onLine' in window.navigator) {\n this._areWeOnline = window.navigator.onLine;\n\n window.addEventListener('online', () => {\n this._areWeOnline = true;\n this._flush();\n });\n\n window.addEventListener('offline', () => {\n this._areWeOnline = false;\n });\n }\n }\n\n get length(): number {\n return this._queue.length;\n }\n\n async retriableRequest(options: RetriableRequestOptions): Promise<void> {\n const retriesPerformedSoFar = options.retriesPerformedSoFar || 0;\n \n // Add retry count to URL if retrying\n if (retriesPerformedSoFar > 0) {\n const url = new URL(options.url);\n url.searchParams.set('retry_count', retriesPerformedSoFar.toString());\n options.url = url.toString();\n }\n\n try {\n await this._sendRequest(options);\n } catch (error: any) {\n // Check if we should retry\n const shouldRetry = this._shouldRetry(error, retriesPerformedSoFar);\n \n if (shouldRetry && retriesPerformedSoFar < 10) {\n this._enqueue(options);\n return;\n }\n\n // Call callback with error if provided\n if (options.callback) {\n options.callback({\n statusCode: error.status || 0,\n text: error.message || 'Request failed'\n });\n }\n }\n }\n\n private _shouldRetry(error: any, retriesPerformedSoFar: number): boolean {\n // Don't retry on client errors (4xx) except for 408, 429\n if (error.status >= 400 && error.status < 500) {\n return error.status === 408 || error.status === 429;\n }\n \n // Retry on server errors (5xx) and network errors\n return error.status >= 500 || !error.status;\n }\n\n private _enqueue(requestOptions: RetriableRequestOptions): void {\n const retriesPerformedSoFar = requestOptions.retriesPerformedSoFar || 0;\n requestOptions.retriesPerformedSoFar = retriesPerformedSoFar + 1;\n\n const msToNextRetry = pickNextRetryDelay(retriesPerformedSoFar);\n const retryAt = Date.now() + msToNextRetry;\n\n this._queue.push({ retryAt, requestOptions });\n\n let logMessage = `Enqueued failed request for retry in ${Math.round(msToNextRetry / 1000)}s`;\n if (typeof navigator !== 'undefined' && !navigator.onLine) {\n logMessage += ' (Browser is offline)';\n }\n logWarn(logMessage);\n\n if (!this._isPolling) {\n this._isPolling = true;\n this._poll();\n }\n }\n\n private _poll(): void {\n if (this._poller) {\n clearTimeout(this._poller);\n }\n this._poller = setTimeout(() => {\n if (this._areWeOnline && this._queue.length > 0) {\n this._flush();\n }\n this._poll();\n }, this._pollIntervalMs);\n }\n\n private _flush(): void {\n const now = Date.now();\n const notToFlush: RetryQueueElement[] = [];\n const toFlush = this._queue.filter((item) => {\n if (item.retryAt < now) {\n return true;\n }\n notToFlush.push(item);\n return false;\n });\n\n this._queue = notToFlush;\n\n if (toFlush.length > 0) {\n for (const { requestOptions } of toFlush) {\n this.retriableRequest(requestOptions).catch((error) => {\n logError('Failed to retry request:', error);\n });\n }\n }\n }\n\n unload(): void {\n if (this._poller) {\n clearTimeout(this._poller);\n this._poller = undefined;\n }\n\n for (const { requestOptions } of this._queue) {\n try {\n // Use sendBeacon for unload to ensure requests are sent\n this._sendBeaconRequest(requestOptions);\n } catch (e) {\n logError('Failed to send request via sendBeacon on unload:', e);\n }\n }\n this._queue = [];\n }\n\n private _sendBeaconRequest(options: RetriableRequestOptions): void {\n if (typeof navigator === 'undefined' || !navigator.sendBeacon) {\n return;\n }\n\n try {\n const url = new URL(options.url);\n url.searchParams.set('beacon', '1');\n\n let body: Blob | null = null;\n if (options.body) {\n if (typeof options.body === 'string') {\n body = new Blob([options.body], { type: 'application/json' });\n } else if (options.body instanceof Blob) {\n body = options.body;\n }\n }\n\n const success = navigator.sendBeacon(url.toString(), body);\n if (!success) {\n logWarn('sendBeacon returned false for unload request');\n }\n } catch (error) {\n logError('Error sending beacon request:', error);\n }\n }\n}\n\n","import { logDebug, logWarn } from './utils/logger';\n\nconst STORAGE_KEY_PREFIX = 'human_behavior_';\n\nexport interface QueuedEvent {\n sessionId: string;\n events: any[];\n endUserId?: string | null;\n windowId?: string;\n automaticProperties?: any;\n timestamp: number;\n}\n\nexport class EventPersistence {\n private storageKey: string;\n private maxQueueSize: number;\n\n constructor(apiKey: string, maxQueueSize: number = 1000) {\n this.storageKey = `${STORAGE_KEY_PREFIX}queue`;\n this.maxQueueSize = maxQueueSize;\n }\n\n /**\n * Get persisted events from storage\n */\n getQueue(): QueuedEvent[] {\n if (typeof window === 'undefined' || !window.localStorage) {\n return [];\n }\n\n try {\n const stored = window.localStorage.getItem(this.storageKey);\n if (!stored) {\n return [];\n }\n\n const queue = JSON.parse(stored);\n if (!Array.isArray(queue)) {\n return [];\n }\n\n return queue;\n } catch (error) {\n logWarn('Failed to read persisted queue:', error);\n return [];\n }\n }\n\n /**\n * Save events to storage\n */\n setQueue(queue: QueuedEvent[]): void {\n if (typeof window === 'undefined' || !window.localStorage) {\n return;\n }\n\n try {\n // Limit queue size\n const limitedQueue = queue.slice(-this.maxQueueSize);\n window.localStorage.setItem(this.storageKey, JSON.stringify(limitedQueue));\n logDebug(`Persisted ${limitedQueue.length} events to storage`);\n } catch (error: any) {\n // Handle quota exceeded errors gracefully\n if (error.name === 'QuotaExceededError' || error.code === 22) {\n logWarn('Storage quota exceeded, clearing old events');\n try {\n // Try to save a smaller queue\n const smallerQueue = queue.slice(-Math.floor(this.maxQueueSize / 2));\n window.localStorage.setItem(this.storageKey, JSON.stringify(smallerQueue));\n } catch (e) {\n logWarn('Failed to save smaller queue, clearing storage');\n this.clearQueue();\n }\n } else {\n logWarn('Failed to persist queue:', error);\n }\n }\n }\n\n /**\n * Add event to persisted queue\n */\n addToQueue(event: QueuedEvent): void {\n const queue = this.getQueue();\n queue.push(event);\n\n // Remove oldest events if queue is too large\n if (queue.length > this.maxQueueSize) {\n queue.shift();\n logDebug('Queue is full, the oldest event is dropped.');\n }\n\n this.setQueue(queue);\n }\n\n /**\n * Remove events from queue (after successful send)\n */\n removeFromQueue(count: number): void {\n const queue = this.getQueue();\n queue.splice(0, count);\n this.setQueue(queue);\n }\n\n /**\n * Clear persisted queue\n */\n clearQueue(): void {\n if (typeof window === 'undefined' || !window.localStorage) {\n return;\n }\n\n try {\n window.localStorage.removeItem(this.storageKey);\n } catch (error) {\n logWarn('Failed to clear persisted queue:', error);\n }\n }\n\n /**\n * Get queue length\n */\n getQueueLength(): number {\n return this.getQueue().length;\n }\n}\n\n","import { logError, logInfo, logDebug, logWarn } from './utils/logger';\nimport { v1 as uuidv1 } from 'uuid';\nimport { RetryQueue, RetriableRequestOptions } from './retry-queue';\nimport { EventPersistence, QueuedEvent } from './persistence';\n\n// SDK version will be replaced at build time by Rollup replace plugin\nconst SDK_VERSION = '__SDK_VERSION__';\n\nexport const MAX_CHUNK_SIZE_BYTES = 1024 * 1024; // 1MB chunk size - more conservative\nconst KEEP_ALIVE_THRESHOLD = 64 * 1024 * 0.8; // 64KB * 0.8 for safety margin\nconst REQUEST_TIMEOUT_MS = 10000; // 10 seconds default timeout\n\nexport function isChunkSizeExceeded(currentChunk: any[], newEvent: any, sessionId: string): boolean {\n const nextChunkSize = new TextEncoder().encode(safeJsonStringify({\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(safeJsonStringify({\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\n/**\n * Safe JSON stringify that handles BigInt values\n */\nfunction safeJsonStringify(data: any): string {\n return JSON.stringify(data, (_, value) => {\n if (typeof value === 'bigint') {\n return value.toString();\n }\n return value;\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(safeJsonStringify({\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(safeJsonStringify({\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 private sessionId: string = '';\n private endUserId: string | null = null;\n private cspBlocked: boolean = false; // Track if CSP is blocking requests\n private retryQueue: RetryQueue;\n private persistence: EventPersistence;\n private requestTimeout: number = REQUEST_TIMEOUT_MS;\n private currentBatchSize: number = 100; // Dynamic batch size for 413 handling\n\n constructor({ apiKey, ingestionUrl }: { apiKey: string, ingestionUrl: string }) {\n this.apiKey = apiKey;\n this.baseUrl = ingestionUrl;\n this.persistence = new EventPersistence(apiKey);\n this.retryQueue = new RetryQueue((options) => this._sendRequestInternal(options));\n \n // Load persisted events on initialization\n this._loadPersistedEvents();\n }\n\n /**\n * Set session and user IDs for tracking context\n */\n public setTrackingContext(sessionId: string, endUserId: string | null): void {\n this.sessionId = sessionId;\n this.endUserId = endUserId;\n }\n\n /**\n * Load persisted events from storage and send them\n */\n private async _loadPersistedEvents(): Promise<void> {\n const persistedQueue = this.persistence.getQueue();\n if (persistedQueue.length === 0) {\n return;\n }\n\n logDebug(`Loading ${persistedQueue.length} persisted events from storage`);\n \n for (const queuedEvent of persistedQueue) {\n try {\n await this.sendEventsChunked(\n queuedEvent.events,\n queuedEvent.sessionId,\n queuedEvent.endUserId || undefined,\n queuedEvent.windowId,\n queuedEvent.automaticProperties\n );\n // Remove from persistence after successful send\n this.persistence.removeFromQueue(1);\n } catch (error) {\n logWarn('Failed to send persisted event, will retry later:', error);\n // Keep in persistence for retry\n }\n }\n }\n\n /**\n * Internal method to send request (used by retry queue)\n */\n private async _sendRequestInternal(options: RetriableRequestOptions): Promise<void> {\n const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n if (controller) {\n timeoutId = setTimeout(() => {\n controller!.abort();\n }, this.requestTimeout);\n }\n\n try {\n const estimatedSize = options.estimatedSize || 0;\n const useKeepalive = options.method === 'POST' && estimatedSize < KEEP_ALIVE_THRESHOLD;\n\n const response = await fetch(options.url, {\n method: options.method || 'GET',\n headers: options.headers || {},\n body: options.body,\n signal: controller?.signal,\n keepalive: useKeepalive\n });\n\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n\n const responseText = await response.text();\n let responseJson: any = null;\n \n try {\n responseJson = JSON.parse(responseText);\n } catch {\n // Not JSON, ignore\n }\n\n if (options.callback) {\n options.callback({\n statusCode: response.status,\n text: responseText,\n json: responseJson\n });\n }\n\n if (!response.ok) {\n throw { status: response.status, message: responseText };\n }\n } catch (error: any) {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n\n if (error.name === 'AbortError') {\n throw { status: 0, message: 'Request timeout' };\n }\n throw error;\n }\n }\n\n /**\n * Handle unload - send pending retries via sendBeacon\n */\n public unload(): void {\n this.retryQueue.unload();\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 this.trackedFetch(`${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: safeJsonStringify({\n sessionId: sessionId,\n endUserId: userId,\n entryURL: entryURL,\n referrer: referrer,\n sdkVersion: SDK_VERSION // Include SDK version for tracking\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 this.trackedFetch(`${this.baseUrl}/api/ingestion/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify({\n sessionId,\n events: validEvents,\n endUserId: userId,\n sdkVersion: SDK_VERSION // Include SDK version for tracking\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, windowId?: string, automaticProperties?: any) {\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 this.trackedFetch(`${this.baseUrl}/api/ingestion/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify({\n sessionId,\n events: currentChunk,\n endUserId: userId,\n windowId: windowId,\n automaticProperties: automaticProperties, // Include automatic properties for user creation\n sdkVersion: SDK_VERSION // Include SDK version for tracking\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 const result = await this._sendChunkWithRetry(\n currentChunk,\n sessionId,\n userId,\n windowId,\n automaticProperties || currentChunk[0]?.automaticProperties\n );\n if (result) {\n results.push(result);\n }\n }\n \n return results.flat();\n } catch (error) {\n logError('Error sending events:', error);\n // Persist failed events for retry\n this._persistEvents(events, sessionId, userId, windowId, automaticProperties);\n throw error;\n }\n }\n\n /**\n * Send a chunk of events with retry logic and 413 handling\n */\n private async _sendChunkWithRetry(\n chunk: any[],\n sessionId: string,\n userId?: string,\n windowId?: string,\n automaticProperties?: any\n ): Promise<any | null> {\n let batchSize = Math.min(this.currentBatchSize, chunk.length);\n let startIndex = 0;\n\n while (startIndex < chunk.length) {\n const batch = chunk.slice(startIndex, startIndex + batchSize);\n const payload = {\n sessionId,\n events: batch,\n endUserId: userId,\n windowId: windowId,\n automaticProperties: automaticProperties,\n sdkVersion: SDK_VERSION // Include SDK version for tracking\n };\n\n const bodyString = safeJsonStringify(payload);\n const estimatedSize = new TextEncoder().encode(bodyString).length;\n\n try {\n const response = await this.trackedFetch(`${this.baseUrl}/api/ingestion/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: bodyString\n }, estimatedSize);\n \n if (!response.ok) {\n if (response.status === 429) {\n this.monthlyLimitReached = true;\n // Persist remaining events\n this._persistEvents(chunk.slice(startIndex), sessionId, userId, windowId, automaticProperties);\n return null;\n }\n \n if (response.status === 413) {\n // Content too large - reduce batch size and retry\n logWarn(`413 error: reducing batch size from ${batchSize} to ${Math.max(1, Math.floor(batchSize / 2))}`);\n this.currentBatchSize = Math.max(1, Math.floor(batchSize / 2));\n batchSize = this.currentBatchSize;\n // Retry with smaller batch\n continue;\n }\n\n // For other errors, use retry queue\n await this.retryQueue.retriableRequest({\n url: `${this.baseUrl}/api/ingestion/events`,\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: bodyString,\n estimatedSize: estimatedSize,\n callback: (response) => {\n if (response.statusCode === 200 && response.json) {\n if (response.json.monthlyLimitReached === true) {\n this.monthlyLimitReached = true;\n }\n }\n }\n });\n \n // Persist for retry\n this._persistEvents(chunk.slice(startIndex), sessionId, userId, windowId, automaticProperties);\n return null;\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 startIndex += batchSize;\n \n // If we successfully sent a batch, return the result\n if (startIndex >= chunk.length) {\n return responseJson;\n }\n } catch (error: any) {\n // Network error - use retry queue\n logWarn('Network error sending chunk, adding to retry queue:', error);\n await this.retryQueue.retriableRequest({\n url: `${this.baseUrl}/api/ingestion/events`,\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: bodyString,\n estimatedSize: estimatedSize,\n callback: (response) => {\n if (response.statusCode === 200 && response.json) {\n if (response.json.monthlyLimitReached === true) {\n this.monthlyLimitReached = true;\n }\n }\n }\n });\n \n // Persist for retry\n this._persistEvents(chunk.slice(startIndex), sessionId, userId, windowId, automaticProperties);\n return null;\n }\n }\n\n return null;\n }\n\n /**\n * Persist events to storage for retry\n */\n private _persistEvents(\n events: any[],\n sessionId: string,\n userId?: string,\n windowId?: string,\n automaticProperties?: any\n ): void {\n if (events.length === 0) {\n return;\n }\n\n this.persistence.addToQueue({\n sessionId,\n events,\n endUserId: userId,\n windowId,\n automaticProperties,\n timestamp: Date.now()\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 this.trackedFetch(`${this.baseUrl}/api/ingestion/user`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify(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 this.trackedFetch(`${this.baseUrl}/api/ingestion/user/auth`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify({\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, userId?: string, windowId?: string, automaticProperties?: any) {\n // Create JSON payload that matches the server's expected format\n // ✅ FIX: Include all fields that sendEventsChunked includes\n // This ensures sendBeacon requests are processed identically to regular HTTP requests\n const payload = {\n sessionId: sessionId,\n events: events,\n endUserId: userId || null, // ✅ FIX: Use actual userId instead of hardcoded null\n windowId: windowId, // ✅ FIX: Include windowId if available\n automaticProperties: automaticProperties, // ✅ FIX: Include automatic properties for user creation\n sdkVersion: SDK_VERSION, // ✅ FIX: Include SDK version for tracking\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([safeJsonStringify(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 this.trackedFetch(`${this.baseUrl}/api/ingestion/customEvent`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify({\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 this.trackedFetch(`${this.baseUrl}/api/ingestion/customEvent/batch`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify({\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 /**\n * Send console log (warn/error) to ingestion server\n */\n async sendLog(logData: {\n level: 'warn' | 'error';\n message: string;\n stack?: string;\n url: string;\n timestampMs: number;\n sessionId: string;\n endUserId: string | null;\n automaticProperties?: Record<string, unknown>;\n }): Promise<void> {\n try {\n logDebug('[SDK] Sending log to server:', { level: logData.level, message: logData.message.substring(0, 50), sessionId: logData.sessionId });\n \n if (!this.baseUrl) {\n return;\n }\n \n if (!logData.sessionId) {\n return;\n }\n \n // Use regular fetch (not trackedFetch) since this is SDK's own request\n const response = await fetch(`${this.baseUrl}/api/ingestion/logs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify(logData)\n });\n \n if (!response.ok) {\n logWarn('[SDK] Failed to send log to server:', response.status, response.statusText);\n } else {\n logDebug('[SDK] Log sent successfully');\n }\n } catch (error) {\n // Silent fail - don't break app if logging fails\n logWarn('[SDK] Failed to send log to server:', error);\n }\n }\n\n /**\n * Send network error to ingestion server\n */\n async sendNetworkError(errorData: {\n requestId: string;\n url: string;\n method: string;\n status: number | null;\n statusText: string | null;\n duration: number;\n timestampMs: number;\n sessionId: string;\n endUserId: string | null;\n errorType: string;\n errorMessage: string | null;\n errorName?: string | null;\n // New span fields\n startTimeMs?: number;\n spanName?: string;\n spanStatus?: 'error' | 'success' | 'slow';\n attributes?: Record<string, any>;\n automaticProperties?: Record<string, unknown>;\n }): Promise<void> {\n try {\n logDebug('[SDK] Sending network error to server:', { errorType: errorData.errorType, url: errorData.url.substring(0, 50), sessionId: errorData.sessionId });\n \n if (!this.baseUrl) {\n return;\n }\n \n if (!errorData.sessionId) {\n return;\n }\n \n // Use regular fetch (not trackedFetch) since this is SDK's own request\n const response = await fetch(`${this.baseUrl}/api/ingestion/network`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify(errorData)\n });\n \n if (!response.ok) {\n logWarn('[SDK] Failed to send network error to server:', response.status, response.statusText);\n } else {\n logDebug('[SDK] Network error sent successfully');\n }\n } catch (error) {\n // Silent fail - don't break app if tracking fails\n logWarn('[SDK] Failed to send network error to server:', error);\n }\n }\n\n /**\n * Trigger server-side GeoIP enrichment. Server resolves the IP from request\n * headers; result is published as a $geoip analytics event that updates\n * raw_sessions.country/city/region in ClickHouse.\n */\n async sendIpInfo(sessionId: string, endUserId: string | null): Promise<void> {\n try {\n if (!this.baseUrl || !sessionId || !endUserId) return;\n\n const response = await fetch(`${this.baseUrl}/api/ingestion/ip-info`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: safeJsonStringify({\n sessionId,\n endUserId,\n ipDetectionMethod: 'server_header'\n })\n });\n\n if (!response.ok) {\n logWarn('[SDK] Failed to send ip-info:', response.status, response.statusText);\n }\n } catch (error) {\n logWarn('[SDK] Failed to send ip-info:', error);\n }\n }\n\n /**\n * Wrapper for fetch that tracks network errors and falls back to sendBeacon on CSP violations\n * Skips tracking for SDK's own requests to ingestion server\n */\n private async trackedFetch(url: string, options: RequestInit, estimatedSize?: number): Promise<Response> {\n const requestStartTime = Date.now();\n const requestId = uuidv1();\n \n // ✅ SKIP TRACKING: Don't track SDK's own requests to ingestion server\n const shouldSkipTracking = this.shouldSkipNetworkTracking(url);\n \n // If CSP is already known to be blocking, use sendBeacon directly for POST requests\n if (this.cspBlocked && options.method === 'POST' && typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n return this.trackedFetchWithBeaconFallback(url, options, shouldSkipTracking);\n }\n \n try {\n const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n if (controller) {\n timeoutId = setTimeout(() => {\n controller!.abort();\n }, this.requestTimeout);\n }\n\n const useKeepalive = options.method === 'POST' && estimatedSize !== undefined && estimatedSize < KEEP_ALIVE_THRESHOLD;\n\n const response = await fetch(url, {\n ...options,\n signal: controller?.signal,\n keepalive: useKeepalive\n });\n\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n const requestDuration = Date.now() - requestStartTime;\n \n // Track failed requests (4xx, 5xx) AND skip SDK requests\n if (!response.ok && !shouldSkipTracking) {\n await this.sendNetworkError({\n requestId,\n url,\n method: options.method || 'GET',\n status: response.status,\n statusText: response.statusText,\n duration: requestDuration,\n timestampMs: Date.now(),\n sessionId: this.sessionId,\n endUserId: this.endUserId,\n errorType: this.classifyHttpError(response.status),\n errorMessage: response.statusText,\n // New span fields\n startTimeMs: requestStartTime,\n spanName: `${options.method || 'GET'} ${url}`,\n spanStatus: 'error',\n attributes: {\n 'http.status_code': response.status,\n 'http.status_text': response.statusText,\n }\n }).catch(() => {}); // Non-blocking\n }\n \n return response;\n } catch (error: any) {\n const requestDuration = Date.now() - requestStartTime;\n \n // Handle timeout errors\n if (error.name === 'AbortError') {\n const timeoutError: any = new Error('Request timeout');\n timeoutError.name = 'TimeoutError';\n error = timeoutError;\n }\n \n // Check if this is a CSP violation\n const isCSPViolation = this.isCSPViolation(error);\n \n if (isCSPViolation && options.method === 'POST' && typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n // Mark CSP as blocked for future requests\n this.cspBlocked = true;\n logWarn('[SDK] CSP violation detected, falling back to sendBeacon for future requests');\n \n // Try sendBeacon as fallback\n return this.trackedFetchWithBeaconFallback(url, options, shouldSkipTracking);\n }\n \n // Track network errors BUT skip SDK requests\n if (!shouldSkipTracking) {\n await this.sendNetworkError({\n requestId,\n url,\n method: options.method || 'GET',\n status: null,\n statusText: null,\n duration: requestDuration,\n timestampMs: Date.now(),\n sessionId: this.sessionId,\n endUserId: this.endUserId,\n errorType: this.classifyNetworkError(error),\n errorMessage: error.message,\n errorName: error.name,\n // New span fields\n startTimeMs: requestStartTime,\n spanName: `${options.method || 'GET'} ${url}`,\n spanStatus: 'error',\n attributes: {\n 'error.name': error.name,\n 'error.message': error.message,\n }\n }).catch(() => {}); // Non-blocking\n }\n \n throw error; // Re-throw to maintain existing error handling\n }\n }\n\n /**\n * Fallback to sendBeacon when CSP blocks fetch\n * sendBeacon bypasses CSP connect-src restrictions\n * Note: sendBeacon is synchronous and fire-and-forget, so we can't await it\n */\n private async trackedFetchWithBeaconFallback(url: string, options: RequestInit, shouldSkipTracking: boolean): Promise<Response> {\n try {\n // Extract and parse body synchronously (sendBeacon is synchronous)\n let bodyJson: any = null;\n let bodyString: string = '';\n \n if (options.body) {\n if (typeof options.body === 'string') {\n try {\n bodyJson = JSON.parse(options.body);\n bodyString = options.body;\n } catch {\n // If not JSON, use as-is\n bodyString = options.body;\n }\n } else if (options.body instanceof Blob) {\n // Can't read Blob synchronously, so we'll need to handle this differently\n // For now, we'll add apiKey to URL and send the blob as-is\n logWarn('[SDK] Cannot extract apiKey from Blob body for sendBeacon, using URL param');\n url = `${url}${url.includes('?') ? '&' : '?'}apiKey=${encodeURIComponent(this.apiKey)}`;\n const success = navigator.sendBeacon(url, options.body);\n return success ? new Response(null, { status: 200, statusText: 'OK', headers: new Headers() })\n : new Response(null, { status: 500, statusText: 'sendBeacon failed', headers: new Headers() });\n } else {\n // For other types, try to stringify\n bodyString = safeJsonStringify(options.body);\n bodyJson = options.body;\n }\n }\n \n // sendBeacon doesn't support headers, so we need to include auth in body or URL\n // For ingestion endpoints, include apiKey in the body JSON\n if (url.includes('/api/ingestion/')) {\n if (bodyJson && typeof bodyJson === 'object') {\n // Add API key to body JSON\n bodyJson.apiKey = this.apiKey;\n bodyString = safeJsonStringify(bodyJson);\n } else if (bodyString) {\n // Try to parse and add apiKey\n try {\n const parsed = JSON.parse(bodyString);\n parsed.apiKey = this.apiKey;\n bodyString = safeJsonStringify(parsed);\n } catch {\n // If parsing fails, append apiKey as query param\n url = `${url}${url.includes('?') ? '&' : '?'}apiKey=${encodeURIComponent(this.apiKey)}`;\n }\n } else {\n // No body, add apiKey to URL\n url = `${url}${url.includes('?') ? '&' : '?'}apiKey=${encodeURIComponent(this.apiKey)}`;\n }\n } else {\n // For non-ingestion endpoints, add apiKey to URL\n url = `${url}${url.includes('?') ? '&' : '?'}apiKey=${encodeURIComponent(this.apiKey)}`;\n }\n \n // Create Blob with proper Content-Type for sendBeacon\n const blob = bodyString \n ? new Blob([bodyString], { type: 'application/json' })\n : null;\n \n // Use sendBeacon (synchronous, fire-and-forget)\n const success = navigator.sendBeacon(url, blob);\n \n if (success) {\n logDebug('[SDK] Successfully sent request via sendBeacon (CSP fallback)');\n // Return a mock Response object for compatibility\n return new Response(null, {\n status: 200,\n statusText: 'OK',\n headers: new Headers()\n });\n } else {\n logWarn('[SDK] sendBeacon returned false - browser may be throttling');\n // Return success anyway since sendBeacon is best-effort\n return new Response(null, {\n status: 200,\n statusText: 'OK (sendBeacon best-effort)',\n headers: new Headers()\n });\n }\n } catch (error: any) {\n logError('[SDK] sendBeacon fallback failed:', error);\n // Return a mock error response\n return new Response(null, {\n status: 500,\n statusText: 'Failed to send via sendBeacon',\n headers: new Headers()\n });\n }\n }\n\n /**\n * Detect if an error is a CSP violation\n */\n private isCSPViolation(error: any): boolean {\n const errorMessage = (error?.message || '').toLowerCase();\n const errorName = (error?.name || '').toLowerCase();\n \n // CSP violations typically manifest as:\n // 1. TypeError with \"Failed to fetch\" and CSP-related text\n // 2. Network errors that mention CSP or Content Security Policy\n // 3. Errors that mention \"violates\" and \"Content Security Policy\"\n \n return (\n (errorName === 'typeerror' && errorMessage.includes('failed to fetch')) ||\n errorMessage.includes('content security policy') ||\n errorMessage.includes('csp') ||\n errorMessage.includes('violates') ||\n // Check for common CSP violation patterns\n (errorMessage.includes('refused to connect') && errorMessage.includes('violates'))\n );\n }\n\n /**\n * Check if network request should be skipped (SDK's own requests)\n */\n private shouldSkipNetworkTracking(url: string): boolean {\n // Skip tracking if URL matches ingestion server base URL\n if (!url || !this.baseUrl) {\n return false;\n }\n \n try {\n const urlObj = new URL(url);\n const baseUrlObj = new URL(this.baseUrl);\n \n // Skip if same origin (same protocol, host, port)\n if (urlObj.origin === baseUrlObj.origin) {\n // Also check if it's an ingestion endpoint\n if (urlObj.pathname.startsWith('/api/ingestion/')) {\n return true;\n }\n }\n \n // Also check string matching as fallback\n if (url.includes(this.baseUrl)) {\n return true;\n }\n \n return false;\n } catch (error) {\n // If URL parsing fails, do simple string check\n return url.includes(this.baseUrl);\n }\n }\n\n private classifyHttpError(status: number): string {\n if (status >= 400 && status < 500) {\n return 'client_error';\n }\n if (status >= 500) {\n return 'server_error';\n }\n return 'unknown_error';\n }\n\n private classifyNetworkError(error: any): string {\n const errorMessage = error.message || '';\n const errorName = error.name || '';\n \n // Check for CSP violations first\n if (this.isCSPViolation(error)) {\n return 'csp_violation';\n }\n \n // Check for blocked requests (ad blockers, browser extensions, etc.)\n // This includes ERR_BLOCKED_BY_CLIENT, ERR_BLOCKED_BY_RESPONSE, and other blocked variants\n if (errorMessage.includes('ERR_BLOCKED_BY_CLIENT') || \n errorMessage.includes('ERR_BLOCKED_BY_RESPONSE') ||\n errorMessage.includes('blocked:other') ||\n errorMessage.includes('net::ERR_BLOCKED_BY_CLIENT') ||\n errorMessage.includes('net::ERR_BLOCKED_BY_RESPONSE') ||\n (errorName === 'TypeError' && errorMessage.includes('Failed to fetch') && \n (errorMessage.includes('blocked') || errorMessage.includes('ERR_BLOCKED')))) {\n return 'blocked_by_client';\n }\n if (errorMessage.includes('CORS') || errorMessage.includes('Access-Control')) {\n return 'cors_error';\n }\n if (errorMessage.includes('timeout') || errorName === 'TimeoutError') {\n return 'timeout_error';\n }\n if (errorMessage.includes('Failed to fetch') || errorMessage.includes('NetworkError')) {\n return 'network_error';\n }\n return 'unknown_error';\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, isSDKLogging } 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 private pendingCustomEvents: Array<{ eventName: string; properties: any; timestamp: number }> = [];\n private pendingLogs: Array<{ logData: any; timestamp: number }> = [];\n private pendingNetworkErrors: Array<{ errorData: any; timestamp: number }> = [];\n \n private sessionId!: string;\n private windowId!: string; // Window ID for multi-window tracking\n // In-memory session state (source of truth during session)\n private _sessionActivityTimestamp: number | null = null;\n private _sessionStartTimestamp: number | null = null;\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 ingestionUrl!: 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 // Network tracking properties\n private originalFetch: typeof fetch | null = null;\n private networkTrackingEnabled: boolean = false;\n \n // Tracking configuration flags\n private enableConsoleTrackingFlag: boolean = true; // Default: enabled (opt-out)\n private enableNetworkTrackingFlag: boolean = true; // Default: enabled (opt-out)\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 private readonly minimumDurationMilliseconds: number = 5000; // Default: 5 seconds minimum\n \n // WindowId tracking properties\n private readonly _window_id_storage_key: string;\n private readonly _primary_window_exists_storage_key: string;\n \n // Idle detection properties\n private _isIdle: boolean | 'unknown' = 'unknown'; // Idle state: true, false, or 'unknown' (initial)\n private _lastActivityTimestamp: number = Date.now(); // Last user interaction timestamp\n private readonly IDLE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes\n \n // Rage click tracking properties\n private rageClickTracker: {\n clicks: Array<{ x: number; y: number; timestamp: number; element: HTMLElement }>;\n } = { clicks: [] };\n private readonly RAGE_CLICK_THRESHOLD_PX = 30; // 30px radius\n private readonly RAGE_CLICK_TIMEOUT_MS = 1000; // 1 second\n private readonly RAGE_CLICK_CLICK_COUNT = 3; // 3 clicks required\n \n // Dead click tracking properties\n private deadClickTracker: {\n pendingClicks: Map<number, {\n element: HTMLElement;\n originalEvent: MouseEvent;\n timestamp: number;\n timer: number;\n cancelled: boolean;\n lastMutationTimeAtClick?: number; // Store mutation time at click to detect new mutations\n }>;\n mutationObserver?: MutationObserver;\n lastMutationTime?: number;\n lastSelectionChangedTime?: number;\n } = {\n pendingClicks: new Map()\n };\n private readonly DEAD_CLICK_SCROLL_THRESHOLD_MS = 100; // Scroll within 100ms counts as action\n private readonly DEAD_CLICK_SELECTION_THRESHOLD_MS = 100; // Selection change within 100ms counts\n private readonly DEAD_CLICK_MUTATION_THRESHOLD_MS = 2000; // DOM mutation within 2s counts (reduced from 2.5s)\n private readonly DEAD_CLICK_ABSOLUTE_TIMEOUT_MS = 1400; // Minimum wait time before considering dead\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 enableConsoleTracking?: boolean; // Enable console warn/error tracking (default: true, set to false to opt-out)\n enableNetworkTracking?: boolean; // Enable network error tracking (default: true, set to false to opt-out)\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 enableConsoleTracking: options?.enableConsoleTracking,\n enableNetworkTracking: options?.enableNetworkTracking\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 enableConsoleTracking?: boolean; // Enable console warn/error tracking (default: true, set to false to opt-out)\n enableNetworkTracking?: boolean; // Enable network error tracking (default: true, set to false to opt-out)\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 const finalIngestionUrl = ingestionUrl || defaultIngestionUrl;\n this.api = new HumanBehaviorAPI({ \n apiKey: apiKey,\n ingestionUrl: finalIngestionUrl\n });\n this.apiKey = apiKey;\n this.ingestionUrl = finalIngestionUrl;\n \n // Initialize queue size (default 1000)\n this.MAX_QUEUE_SIZE = options?.maxQueueSize ?? 1000;\n \n // Store tracking configuration flags (default: enabled, opt-out by setting to false)\n this.enableConsoleTrackingFlag = options?.enableConsoleTracking !== false; // Default: true (opt-out)\n this.enableNetworkTrackingFlag = options?.enableNetworkTracking !== false; // Default: true (opt-out)\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 // ✅ CLIENT-SIDE ID GENERATION\n // Generate endUserId locally (no server dependency)\n if (isBrowser) {\n const endUserIdKey = `human_behavior_end_user_id`;\n const existingEndUserId = this.getCookie(endUserIdKey);\n this.endUserId = existingEndUserId || uuidv1();\n if (!existingEndUserId) {\n this.setCookie(endUserIdKey, this.endUserId, 365);\n logDebug(`Generated new endUserId: ${this.endUserId}`);\n } else {\n logDebug(`Reusing existing endUserId: ${this.endUserId}`);\n }\n } else {\n this.endUserId = uuidv1();\n }\n\n // ✅ CLIENT-SIDE SESSION MANAGEMENT\n // Generate sessionId with timeout checking\n if (isBrowser) {\n // Initialize windowId storage keys\n const persistenceName = this.apiKey || 'default';\n this._window_id_storage_key = `human_behavior_${persistenceName}_window_id`;\n this._primary_window_exists_storage_key = `human_behavior_${persistenceName}_primary_window_exists`;\n \n this.sessionId = this.getOrCreateSessionId();\n this.windowId = this.getOrCreateWindowId(); // Multi-window tracking\n this.currentUrl = window.location.href;\n (window as any).__humanBehaviorGlobalTracker = this;\n \n // Setup beforeunload listener to clear primary_window_exists flag\n this.setupWindowUnloadListener();\n } else {\n this._window_id_storage_key = '';\n this._primary_window_exists_storage_key = '';\n this.sessionId = uuidv1();\n this.windowId = uuidv1();\n }\n\n // ✅ SET TRACKING CONTEXT: Set session and user IDs for network error tracking\n this.api.setTrackingContext(this.sessionId, this.endUserId);\n\n // ✅ INITIALIZATION: Setup handlers immediately (no server call needed)\n this.initializationPromise = this.init().catch(error => {\n logError('Initialization failed:', error);\n });\n }\n\n private async init(): Promise<void> {\n try {\n // Setup handlers immediately\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 // Mark as initialized\n this.initialized = true;\n \n logInfo(`HumanBehaviorTracker initialized with sessionId: ${this.sessionId}, endUserId: ${this.endUserId}`);\n } catch (error: any) {\n // Handle initialization errors gracefully - don't throw\n logError('Failed to initialize HumanBehaviorTracker:', error);\n this.initialized = true; // Allow tracker to work locally even if init fails\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 // ✅ NON-BLOCKING: endUserId is always available (generated locally)\n // No need to wait for server initialization\n if (!this.endUserId) {\n // This should never happen, but fallback to anonymous if it does\n logWarn(`endUserId not available, using anonymous ID for event: ${eventName}`);\n this.endUserId = uuidv1();\n }\n\n // ✅ CHECK SESSION TIMEOUT before sending custom event (creates new session if expired)\n if (isBrowser) {\n this.checkAndRefreshSession();\n }\n\n // Get enhanced properties with automatic properties\n const enhancedProperties = this.propertyManager.getEventProperties(properties);\n\n // ✅ Check minimum duration - queue if below, send if above\n if (this.shouldSkipDueToMinimumDuration()) {\n logDebug(`Custom event '${eventName}' queued due to session duration below minimum`);\n this.pendingCustomEvents.push({\n eventName,\n properties: enhancedProperties,\n timestamp: Date.now()\n });\n return;\n }\n\n // ✅ Flush any pending custom events first (everything before 5 seconds)\n await this.flushPendingCustomEvents();\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 general click tracking (captures all clicks)\n this.setupClickTracking(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 this.setupRageClickDetection();\n this.setupDeadClickDetection();\n }\n\n /**\n * Setup general click tracking - captures ALL clicks on the document\n */\n private setupClickTracking(config: {\n includeText?: boolean;\n includeClasses?: boolean;\n }): void {\n document.addEventListener('click', async (event) => {\n const target = event.target as HTMLElement;\n if (!target || !target.tagName) return;\n\n const interactiveParent = target.closest('button, a, [role=\"button\"], [role=\"link\"], [role=\"tab\"], [role=\"menuitem\"]');\n const element = interactiveParent || target;\n\n const properties: Record<string, any> = {\n tag: element.tagName.toLowerCase(),\n x: event.clientX,\n y: event.clientY,\n page: window.location.pathname,\n timestamp: Date.now()\n };\n\n if (element.id) {\n properties.elementId = element.id;\n }\n\n if ((element as HTMLAnchorElement).href) {\n properties.href = (element as HTMLAnchorElement).href;\n }\n\n if (config.includeText) {\n const text = element.textContent?.trim();\n if (text) {\n properties.elementText = text.substring(0, 100);\n }\n }\n\n if (config.includeClasses && element.className) {\n properties.elementClass = element.className;\n }\n\n await this.customEvent('$click', properties);\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 rage click detection\n * Detects when user clicks the same area 3+ times within 1 second (within 30px radius)\n * Similar to rage click detection\n */\n private setupRageClickDetection(): void {\n if (!isBrowser) return;\n\n document.addEventListener('click', async (event: MouseEvent) => {\n const target = event.target as HTMLElement;\n const x = event.clientX;\n const y = event.clientY;\n const timestamp = Date.now();\n \n // Check if this is a rage click\n if (this.isRageClick(x, y, timestamp, target)) {\n // Get element information\n const element = target.closest('button, a, [role=\"button\"], [role=\"link\"]') || target;\n \n const properties: Record<string, any> = {\n x: x,\n y: y,\n page: window.location.pathname,\n element: element.tagName.toLowerCase(),\n clickCount: this.RAGE_CLICK_CLICK_COUNT,\n timestamp: timestamp\n };\n\n // Add element details if available\n if (element.id) {\n properties.elementId = element.id;\n }\n if (element.className) {\n properties.elementClass = element.className;\n }\n if (element.textContent) {\n properties.elementText = element.textContent.trim().substring(0, 100);\n }\n\n // Remove null values\n Object.keys(properties).forEach(key => {\n if (properties[key] === null || properties[key] === undefined) {\n delete properties[key];\n }\n });\n await this.customEvent('$rageclick', properties);\n \n // Reset tracker after detecting rage click\n this.rageClickTracker.clicks = [];\n }\n });\n }\n\n /**\n * Check if a click is part of a rage click pattern\n * Returns true if 3+ clicks within 1 second and within 30px of each other\n */\n private isRageClick(x: number, y: number, timestamp: number, element: HTMLElement): boolean {\n const clicks = this.rageClickTracker.clicks;\n const lastClick = clicks[clicks.length - 1];\n \n if (\n lastClick &&\n Math.abs(x - lastClick.x) + Math.abs(y - lastClick.y) < this.RAGE_CLICK_THRESHOLD_PX &&\n timestamp - lastClick.timestamp < this.RAGE_CLICK_TIMEOUT_MS\n ) {\n // Continuation of previous clicks - add to array\n clicks.push({ x, y, timestamp, element });\n \n // Check if we've reached the threshold\n if (clicks.length >= this.RAGE_CLICK_CLICK_COUNT) {\n return true; // Rage click detected!\n }\n } else {\n // Not a continuation - reset and start new sequence\n this.rageClickTracker.clicks = [{ x, y, timestamp, element }];\n }\n \n return false;\n }\n\n /**\n * Setup dead click detection\n * Detects when user clicks on interactive elements that don't trigger any action\n * Observes entire document for mutations, accepts false negatives on animated pages\n */\n private setupDeadClickDetection(): void {\n if (!isBrowser) return;\n\n // Set up global mutation observer (observes entire document)\n this.setupDeadClickMutationObserver();\n\n // Set up scroll listener\n this.setupDeadClickScrollObserver();\n\n // Set up selection change listener\n this.setupDeadClickSelectionObserver();\n\n // Hook into navigation tracking\n this.setupDeadClickNavigationTracking();\n\n // Set up click listener\n document.addEventListener('click', (event: MouseEvent) => {\n const target = event.target as HTMLElement;\n if (!target) return;\n\n // Only track clicks on interactive elements\n if (!this.isInteractiveElement(target)) {\n return;\n }\n\n // Check if this click should be ignored\n if (this.ignoreClickForDeadDetection(target)) {\n return;\n }\n\n // Create unique ID for this click\n const clickId = Date.now() + Math.random();\n const clickTimestamp = Date.now();\n const mutationTimeAtClick = this.deadClickTracker.lastMutationTime;\n\n // Set timer for this click\n const timeoutValue = this.DEAD_CLICK_ABSOLUTE_TIMEOUT_MS;\n const timer = window.setTimeout(() => {\n this.handleDeadClickTimeout(clickId);\n }, timeoutValue);\n\n // Store click data - capture mutation time at click to detect new mutations\n this.deadClickTracker.pendingClicks.set(clickId, {\n element: target,\n originalEvent: event,\n timestamp: clickTimestamp,\n timer: timer,\n cancelled: false,\n lastMutationTimeAtClick: mutationTimeAtClick\n });\n });\n }\n\n // Set up global MutationObserver to track ALL DOM changes\n // Observe entire document, just record timestamp of any mutation\n private setupDeadClickMutationObserver(): void {\n if (!isBrowser) return;\n\n // Only set up once\n if (this.deadClickTracker.mutationObserver) {\n return;\n }\n\n this.deadClickTracker.mutationObserver = new MutationObserver(() => {\n // We don't care about the content of mutations - just record that one happened\n const now = Date.now();\n this.deadClickTracker.lastMutationTime = now;\n \n // Immediately cancel any pending clicks that happened recently\n // This catches mutations from dropdowns/menus that open right after click\n // Also catch mutations that happened just before the click (within 50ms) - React timing issue\n this.deadClickTracker.pendingClicks.forEach((click, clickId) => {\n if (click.cancelled) return;\n \n const timeSinceClick = now - click.timestamp;\n const mutationTimeAtClick = click.lastMutationTimeAtClick || 0;\n const isNewMutation = now > mutationTimeAtClick;\n // Allow mutations within threshold OR mutations that happened just before click (React timing)\n const withinThreshold = timeSinceClick >= 0 && timeSinceClick < this.DEAD_CLICK_MUTATION_THRESHOLD_MS;\n const mutationJustBeforeClick = timeSinceClick < 0 && Math.abs(timeSinceClick) < 50; // Mutation within 50ms before click\n \n // If mutation happened within the mutation threshold OR just before click, cancel\n if ((withinThreshold && isNewMutation) || mutationJustBeforeClick) {\n this.cancelPendingClick(clickId);\n }\n });\n });\n\n // Observe entire document (accepts false negatives on animated pages)\n this.deadClickTracker.mutationObserver.observe(document, {\n attributes: true,\n characterData: true,\n childList: true,\n subtree: true\n });\n }\n\n // Set up scroll observer to track scroll events\n private setupDeadClickScrollObserver(): void {\n if (!isBrowser) return;\n\n window.addEventListener('scroll', () => {\n const now = Date.now();\n // Cancel clicks that happened within the scroll threshold\n this.deadClickTracker.pendingClicks.forEach((click, clickId) => {\n const timeSinceClick = now - click.timestamp;\n if (timeSinceClick < this.DEAD_CLICK_SCROLL_THRESHOLD_MS) {\n this.cancelPendingClick(clickId);\n }\n });\n }, { capture: true, passive: true });\n }\n\n // Set up selection change observer\n private setupDeadClickSelectionObserver(): void {\n if (!isBrowser) return;\n\n document.addEventListener('selectionchange', () => {\n const now = Date.now();\n this.deadClickTracker.lastSelectionChangedTime = now;\n // Cancel clicks that happened within the selection threshold\n this.deadClickTracker.pendingClicks.forEach((click, clickId) => {\n const timeSinceClick = now - click.timestamp;\n if (timeSinceClick < this.DEAD_CLICK_SELECTION_THRESHOLD_MS) {\n this.cancelPendingClick(clickId);\n }\n });\n });\n }\n\n // Hook into navigation tracking to detect page changes\n private setupDeadClickNavigationTracking(): void {\n if (!isBrowser) return;\n\n // Track current URL to detect changes\n let lastUrl = window.location.href;\n \n // Hook into existing trackNavigationEvent method\n // Navigation will cancel all pending clicks\n const originalTrackNavigationEvent = this.trackNavigationEvent.bind(this);\n this.trackNavigationEvent = async (type: string, fromUrl: string, toUrl: string) => {\n // Cancel all pending clicks when navigation happens\n this.cancelAllPendingClicks();\n // Update last URL\n lastUrl = window.location.href;\n // Call original method\n return originalTrackNavigationEvent(type, fromUrl, toUrl);\n };\n \n // Also monitor URL changes directly as a backup\n const checkUrlChange = () => {\n const currentUrl = window.location.href;\n if (currentUrl !== lastUrl) {\n this.cancelAllPendingClicks();\n lastUrl = currentUrl;\n }\n };\n \n // Listen to all navigation events\n window.addEventListener('popstate', checkUrlChange);\n window.addEventListener('hashchange', checkUrlChange);\n window.addEventListener('beforeunload', () => {\n this.cancelAllPendingClicks();\n });\n \n // Also check URL periodically (catches any navigation we might miss)\n setInterval(() => {\n checkUrlChange();\n }, 100);\n }\n\n /**\n * Check if an element is interactive (should respond to clicks)\n */\n private isInteractiveElement(element: HTMLElement): boolean {\n const tagName = element.tagName.toLowerCase();\n\n // Buttons and links\n if (tagName === 'button' || tagName === 'a') {\n return true;\n }\n\n // Form elements\n if (['input', 'select', 'textarea'].includes(tagName)) {\n return true;\n }\n\n // Elements with interactive roles\n const role = element.getAttribute('role');\n if (role && ['button', 'link', 'tab', 'menuitem', 'checkbox', 'radio'].includes(role)) {\n return true;\n }\n\n // Elements with click handlers\n if ((element as any).onclick || element.getAttribute('onclick')) {\n return true;\n }\n\n // Elements with cursor pointer (often indicates clickable)\n try {\n const style = window.getComputedStyle(element);\n if (style.cursor === 'pointer') {\n return true;\n }\n } catch (e) {\n // Ignore errors\n }\n\n // Check if parent is an interactive element (click might be on child like span inside button)\n const interactiveParent = element.closest('button, a, [role=\"button\"], [role=\"link\"], [role=\"tab\"], [role=\"menuitem\"]');\n if (interactiveParent) {\n return true;\n }\n\n return false;\n }\n\n // Check if a click should be ignored for dead click detection\n private ignoreClickForDeadDetection(element: HTMLElement): boolean {\n // Ignore clicks on html tag\n if (element.tagName.toLowerCase() === 'html') {\n return true;\n }\n\n // Ignore if same element was clicked within last second (avoid duplicates)\n const now = Date.now();\n for (const click of this.deadClickTracker.pendingClicks.values()) {\n if (click.element === element && Math.abs(now - click.timestamp) < 1000) {\n return true;\n }\n }\n\n return false;\n }\n\n // Cancel a pending click (action happened, so it's not dead)\n private cancelPendingClick(clickId: number): void {\n const click = this.deadClickTracker.pendingClicks.get(clickId);\n if (click && !click.cancelled) {\n clearTimeout(click.timer);\n click.cancelled = true;\n this.deadClickTracker.pendingClicks.delete(clickId);\n }\n }\n\n // Cancel all pending clicks (e.g., on navigation)\n private cancelAllPendingClicks(): void {\n this.deadClickTracker.pendingClicks.forEach((click, clickId) => {\n if (!click.cancelled) {\n clearTimeout(click.timer);\n this.deadClickTracker.pendingClicks.delete(clickId);\n }\n });\n }\n\n // Handle when a dead click timer expires\n private async handleDeadClickTimeout(clickId: number): Promise<void> {\n const click = this.deadClickTracker.pendingClicks.get(clickId);\n if (!click || click.cancelled) {\n return;\n }\n\n const now = Date.now();\n const absoluteDelayMs = now - click.timestamp;\n\n // Check if any action happened after the minimum timeout\n // Keep only selection and navigation checks (mutation removed)\n let hadAction = false;\n let reason: string | null = null;\n\n // Calculate delays\n // Check if a NEW mutation happened after the click OR just before (React timing issue)\n let mutationDelayMs: number | undefined;\n const mutationTimeAtClick = click.lastMutationTimeAtClick || 0;\n const currentMutationTime = this.deadClickTracker.lastMutationTime || 0;\n \n // Count mutations that happened AFTER the click OR just before (within 50ms - React timing)\n const mutationAfterClick = currentMutationTime > mutationTimeAtClick && currentMutationTime >= click.timestamp;\n const mutationJustBeforeClick = mutationTimeAtClick > 0 && \n mutationTimeAtClick < click.timestamp && \n (click.timestamp - mutationTimeAtClick) < 50; // Mutation within 50ms before click\n \n if (mutationAfterClick) {\n mutationDelayMs = currentMutationTime - click.timestamp;\n } else if (mutationJustBeforeClick) {\n // Treat mutation just before click as if it happened at click time (0ms delay)\n mutationDelayMs = 0;\n }\n\n let selectionChangedDelayMs: number | undefined;\n if (this.deadClickTracker.lastSelectionChangedTime && click.timestamp <= this.deadClickTracker.lastSelectionChangedTime) {\n selectionChangedDelayMs = this.deadClickTracker.lastSelectionChangedTime - click.timestamp;\n }\n\n // Check if any action happened within thresholds\n const hadScroll = false; // Scroll is handled by immediate cancellation\n const hadMutation = mutationDelayMs !== undefined && mutationDelayMs < this.DEAD_CLICK_MUTATION_THRESHOLD_MS;\n const hadSelectionChange = selectionChangedDelayMs !== undefined && selectionChangedDelayMs < this.DEAD_CLICK_SELECTION_THRESHOLD_MS;\n\n if (hadScroll || hadMutation || hadSelectionChange) {\n this.deadClickTracker.pendingClicks.delete(clickId);\n return;\n }\n\n // No action happened - it's a dead click\n await this.fireDeadClickEvent(click, absoluteDelayMs, mutationDelayMs, selectionChangedDelayMs);\n\n // Remove from pending clicks\n this.deadClickTracker.pendingClicks.delete(clickId);\n }\n\n // Fire dead click event\n private async fireDeadClickEvent(\n click: {\n element: HTMLElement;\n originalEvent: MouseEvent;\n timestamp: number;\n },\n absoluteDelayMs: number,\n mutationDelayMs?: number,\n selectionChangedDelayMs?: number\n ): Promise<void> {\n const element = click.element.closest('button, a, [role=\"button\"], [role=\"link\"]') || click.element;\n\n const properties: Record<string, any> = {\n x: click.originalEvent.clientX,\n y: click.originalEvent.clientY,\n page: window.location.pathname,\n element: element.tagName.toLowerCase(),\n absoluteDelayMs: absoluteDelayMs,\n timestamp: click.timestamp\n };\n\n // Add delay information\n if (mutationDelayMs !== undefined) {\n properties.mutationDelayMs = mutationDelayMs;\n }\n if (selectionChangedDelayMs !== undefined) {\n properties.selectionChangedDelayMs = selectionChangedDelayMs;\n }\n\n // Add element details if available\n if (element.id) {\n properties.elementId = element.id;\n }\n if (element.className) {\n properties.elementClass = element.className;\n }\n if (element.textContent) {\n properties.elementText = element.textContent.trim().substring(0, 100);\n }\n\n // Remove null/undefined values\n Object.keys(properties).forEach(key => {\n if (properties[key] === null || properties[key] === undefined) {\n delete properties[key];\n }\n });\n\n // Send dead click custom event\n await this.customEvent('$deadclick', properties);\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 * Enable network error tracking by intercepting the global fetch API\n */\n public enableNetworkTracking(): void {\n if (!isBrowser || this.networkTrackingEnabled || typeof fetch === 'undefined') return;\n \n // Store original fetch\n this.originalFetch = window.fetch.bind(window);\n \n // Override global fetch to track network errors\n window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const requestStartTime = Date.now();\n const requestId = uuidv1();\n const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;\n const method = (init?.method || (typeof input === 'object' && 'method' in input ? input.method : undefined) || 'GET').toUpperCase();\n \n // Check if we should skip tracking (SDK's own requests)\n const shouldSkipTracking = this.shouldSkipNetworkTracking(url);\n \n // Track long-loading requests (>10 seconds)\n const LONG_LOADING_THRESHOLD_MS = 10000; // 10 seconds\n let longLoadingTimeoutId: ReturnType<typeof setTimeout> | null = null;\n let longLoadingTracked = false;\n \n // Set up timeout to track long-loading requests\n if (!shouldSkipTracking) {\n longLoadingTimeoutId = setTimeout(() => {\n const elapsedTime = Date.now() - requestStartTime;\n if (!longLoadingTracked) {\n longLoadingTracked = true;\n const errorData = {\n requestId,\n url,\n method,\n status: null, // Request still in progress\n statusText: null,\n duration: elapsedTime,\n timestampMs: Date.now(),\n sessionId: this.sessionId,\n endUserId: this.endUserId,\n errorType: 'long_loading',\n errorMessage: `Request took longer than ${LONG_LOADING_THRESHOLD_MS}ms (${elapsedTime}ms elapsed)`,\n // New span fields\n startTimeMs: requestStartTime,\n spanName: `${method} ${url}`,\n spanStatus: 'slow' as const,\n attributes: {\n 'http.method': method,\n 'http.url': url,\n 'request.duration_ms': elapsedTime,\n 'request.long_loading_threshold_ms': LONG_LOADING_THRESHOLD_MS,\n },\n automaticProperties: this.propertyManager.getAutomaticProperties()\n };\n // ✅ Check minimum duration - queue if below, send if above\n if (this.shouldSkipDueToMinimumDuration()) {\n logDebug('Long-loading network error queued due to session duration below minimum');\n this.pendingNetworkErrors.push({\n errorData,\n timestamp: Date.now()\n });\n return;\n }\n // ✅ Flush any pending network errors first (everything before 5 seconds)\n this.flushPendingNetworkErrors();\n this.api.sendNetworkError(errorData).catch(() => {}); // Non-blocking\n return;\n }\n }, LONG_LOADING_THRESHOLD_MS);\n }\n \n try {\n const response = await this.originalFetch!(input, init);\n const requestDuration = Date.now() - requestStartTime;\n \n // Clear long-loading timeout if request completed\n if (longLoadingTimeoutId) {\n clearTimeout(longLoadingTimeoutId);\n }\n \n // Track failed requests (4xx, 5xx) AND skip SDK requests\n if (!response.ok && !shouldSkipTracking) {\n const errorData = {\n requestId,\n url,\n method,\n status: response.status,\n statusText: response.statusText,\n duration: requestDuration,\n timestampMs: Date.now(),\n sessionId: this.sessionId,\n endUserId: this.endUserId,\n errorType: this.classifyHttpError(response.status),\n errorMessage: response.statusText,\n // New span fields\n startTimeMs: requestStartTime,\n spanName: `${method} ${url}`,\n spanStatus: 'error' as const,\n attributes: {\n 'http.status_code': response.status,\n 'http.status_text': response.statusText,\n },\n automaticProperties: this.propertyManager.getAutomaticProperties()\n };\n // ✅ Check minimum duration - queue if below, send if above\n if (this.shouldSkipDueToMinimumDuration()) {\n logDebug('Failed request network error queued due to session duration below minimum');\n this.pendingNetworkErrors.push({\n errorData,\n timestamp: Date.now()\n });\n return response;\n }\n // ✅ Flush any pending network errors first (everything before 5 seconds)\n this.flushPendingNetworkErrors();\n this.api.sendNetworkError(errorData).catch(() => {}); // Non-blocking\n }\n \n return response;\n } catch (error: any) {\n const requestDuration = Date.now() - requestStartTime;\n \n // Clear long-loading timeout if request failed\n if (longLoadingTimeoutId) {\n clearTimeout(longLoadingTimeoutId);\n }\n \n // Track network errors BUT skip SDK requests\n if (!shouldSkipTracking) {\n const errorData = {\n requestId,\n url,\n method,\n status: null,\n statusText: null,\n duration: requestDuration,\n timestampMs: Date.now(),\n sessionId: this.sessionId,\n endUserId: this.endUserId,\n errorType: this.classifyNetworkError(error),\n errorMessage: error.message,\n errorName: error.name,\n // New span fields\n startTimeMs: requestStartTime,\n spanName: `${method} ${url}`,\n spanStatus: 'error' as const,\n attributes: {\n 'error.name': error.name,\n 'error.message': error.message,\n },\n automaticProperties: this.propertyManager.getAutomaticProperties()\n };\n // ✅ Check minimum duration - queue if below, send if above\n if (this.shouldSkipDueToMinimumDuration()) {\n logDebug('Network error queued due to session duration below minimum');\n this.pendingNetworkErrors.push({\n errorData,\n timestamp: Date.now()\n });\n throw error; // Re-throw to maintain error propagation\n }\n // ✅ Flush any pending network errors first (everything before 5 seconds)\n this.flushPendingNetworkErrors();\n this.api.sendNetworkError(errorData).catch(() => {}); // Non-blocking\n }\n \n throw error;\n }\n };\n \n this.networkTrackingEnabled = true;\n logDebug('Network tracking enabled');\n }\n\n /**\n * Flush pending custom events (queued before 5 seconds)\n */\n private async flushPendingCustomEvents(): Promise<void> {\n if (this.pendingCustomEvents.length === 0) {\n return;\n }\n\n const eventsToFlush = [...this.pendingCustomEvents];\n this.pendingCustomEvents = [];\n\n logDebug(`Flushing ${eventsToFlush.length} pending custom events`);\n\n for (const { eventName, properties } of eventsToFlush) {\n try {\n await this.api.sendCustomEvent(this.sessionId, eventName, properties, this.endUserId);\n } catch (error) {\n logError('Failed to flush pending custom event:', error);\n }\n }\n }\n\n /**\n * Flush pending logs (queued before 5 seconds)\n */\n private async flushPendingLogs(): Promise<void> {\n if (this.pendingLogs.length === 0) {\n return;\n }\n\n const logsToFlush = [...this.pendingLogs];\n this.pendingLogs = [];\n\n logDebug(`Flushing ${logsToFlush.length} pending logs`);\n\n for (const { logData } of logsToFlush) {\n try {\n await this.api.sendLog(logData);\n } catch (error) {\n logError('Failed to flush pending log:', error);\n }\n }\n }\n\n /**\n * Flush pending network errors (queued before 5 seconds)\n */\n private async flushPendingNetworkErrors(): Promise<void> {\n if (this.pendingNetworkErrors.length === 0) {\n return;\n }\n\n const errorsToFlush = [...this.pendingNetworkErrors];\n this.pendingNetworkErrors = [];\n\n logDebug(`Flushing ${errorsToFlush.length} pending network errors`);\n\n for (const { errorData } of errorsToFlush) {\n try {\n await this.api.sendNetworkError(errorData);\n } catch (error) {\n logError('Failed to flush pending network error:', error);\n }\n }\n }\n\n /**\n * Enable page load tracking - detects heavy page loads (>3 seconds)\n */\n public enablePageLoadTracking(): void {\n if (!isBrowser || typeof window === 'undefined') return;\n \n // Track initial page load\n if (document.readyState === 'complete') {\n // Page already loaded, check immediately\n this.trackPageLoad();\n } else {\n // Wait for page load\n window.addEventListener('load', () => {\n this.trackPageLoad();\n });\n }\n \n logDebug('Page load tracking enabled');\n }\n\n /**\n * Track heavy page loads using Performance API\n */\n private trackPageLoad(): void {\n if (!isBrowser || typeof performance === 'undefined') return;\n \n try {\n const perfEntry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;\n if (!perfEntry) return;\n \n const loadDuration = perfEntry.loadEventEnd - perfEntry.fetchStart;\n const HEAVY_LOAD_THRESHOLD_MS = 3000; // 3 seconds\n \n // Only track if heavy (>3 seconds)\n if (loadDuration > HEAVY_LOAD_THRESHOLD_MS) {\n const requestId = uuidv1();\n const domContentLoaded = perfEntry.domContentLoadedEventEnd - perfEntry.fetchStart;\n const domComplete = perfEntry.domComplete - perfEntry.fetchStart;\n \n const errorData = {\n requestId,\n url: window.location.href,\n method: 'GET',\n status: 200, // Page loads are typically successful\n statusText: 'OK',\n duration: loadDuration,\n timestampMs: perfEntry.loadEventEnd + performance.timeOrigin,\n sessionId: this.sessionId,\n endUserId: this.endUserId,\n errorType: 'slow_page_load',\n errorMessage: `Page load took ${loadDuration}ms`,\n // New span fields\n startTimeMs: perfEntry.fetchStart + performance.timeOrigin,\n spanName: 'page_load',\n spanStatus: 'slow' as const,\n attributes: {\n 'page.url': window.location.href,\n 'page.load_time': loadDuration,\n 'page.dom_content_loaded': domContentLoaded,\n 'page.dom_complete': domComplete,\n }\n };\n // ✅ Check minimum duration - queue if below, send if above\n if (this.shouldSkipDueToMinimumDuration()) {\n logDebug('Slow page load network error queued due to session duration below minimum');\n this.pendingNetworkErrors.push({\n errorData,\n timestamp: Date.now()\n });\n return;\n }\n // ✅ Flush any pending network errors first (everything before 5 seconds)\n this.flushPendingNetworkErrors();\n this.api.sendNetworkError(errorData).catch(() => {}); // Non-blocking\n }\n } catch (error) {\n logWarn('Failed to track page load:', error);\n }\n }\n\n /**\n * Check if network request should be skipped (SDK's own requests)\n */\n private shouldSkipNetworkTracking(url: string): boolean {\n if (!url || !this.ingestionUrl) {\n return false;\n }\n \n try {\n const urlObj = new URL(url);\n const baseUrlObj = new URL(this.ingestionUrl);\n \n // Skip if same origin (same protocol, host, port)\n if (urlObj.origin === baseUrlObj.origin) {\n // Also check if it's an ingestion endpoint\n if (urlObj.pathname.startsWith('/api/ingestion/')) {\n return true;\n }\n }\n \n // Also check string matching as fallback\n if (url.includes(this.ingestionUrl)) {\n return true;\n }\n \n return false;\n } catch (error) {\n // If URL parsing fails, do simple string check\n return url.includes(this.ingestionUrl);\n }\n }\n\n /**\n * Classify HTTP error status codes\n */\n private classifyHttpError(status: number): string {\n if (status >= 400 && status < 500) {\n return 'client_error';\n }\n if (status >= 500) {\n return 'server_error';\n }\n return 'unknown_error';\n }\n\n /**\n * Classify network errors (CORS, timeouts, blocked requests, etc.)\n */\n private classifyNetworkError(error: any): string {\n const errorMessage = error.message || '';\n const errorName = error.name || '';\n \n // Check for blocked requests (ad blockers, browser extensions, etc.)\n if (\n errorMessage.includes('blocked') ||\n errorMessage.includes('ERR_BLOCKED_BY_CLIENT') ||\n errorMessage.includes('net::ERR_BLOCKED_BY_CLIENT') ||\n errorName === 'TypeError' && errorMessage.includes('Failed to fetch')\n ) {\n return 'blocked_by_client';\n }\n \n // Check for CORS errors\n if (\n errorMessage.includes('CORS') ||\n errorMessage.includes('Cross-Origin') ||\n errorMessage.includes('Access-Control-Allow-Origin') ||\n errorName === 'TypeError' && errorMessage.includes('CORS')\n ) {\n return 'cors_error';\n }\n \n // Check for network/timeout errors\n if (\n errorMessage.includes('timeout') ||\n errorMessage.includes('TIMEOUT') ||\n errorMessage.includes('NetworkError') ||\n errorName === 'NetworkError'\n ) {\n return 'network_error';\n }\n \n // Check for abort errors\n if (\n errorMessage.includes('abort') ||\n errorName === 'AbortError'\n ) {\n return 'aborted';\n }\n \n return 'unknown_error';\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) {\n return;\n }\n\n // Only track warn and error, skip log\n if (level === 'log') {\n // Just call original console.log, don't track\n if (this.originalConsole) {\n this.originalConsole.log(...args);\n }\n return;\n }\n\n try {\n // ✅ SKIP TRACKING: If SDK logger is currently active, don't track\n if (isSDKLogging()) {\n if (this.originalConsole) {\n this.originalConsole[level](...args);\n }\n return;\n }\n\n // ✅ SKIP TRACKING: Check if log originates from SDK code\n const stack = new Error().stack || '';\n if (this.isSDKStackFrame(stack)) {\n // This log came from SDK code, don't track it\n if (this.originalConsole) {\n this.originalConsole[level](...args);\n }\n return;\n }\n\n const consoleData = {\n level: level, // 'warn' or 'error'\n message: args.map(arg =>\n typeof arg === 'object' ? JSON.stringify(arg) : String(arg)\n ).join(' '),\n timestampMs: Date.now(),\n url: isBrowser ? window.location.href : '',\n userAgent: isBrowser ? navigator.userAgent : '',\n stack: stack,\n sessionId: this.sessionId,\n endUserId: this.endUserId,\n automaticProperties: this.propertyManager.getAutomaticProperties()\n };\n\n // ✅ Check minimum duration - queue if below, send if above\n if (this.shouldSkipDueToMinimumDuration()) {\n logDebug(`Console ${level} queued due to session duration below minimum`);\n this.pendingLogs.push({\n logData: consoleData,\n timestamp: Date.now()\n });\n return;\n }\n\n // ✅ Flush any pending logs first (everything before 5 seconds)\n this.flushPendingLogs();\n\n // Send to dedicated endpoint for ClickHouse\n this.api.sendLog(consoleData).catch(err => {\n // Fallback to event stream if dedicated endpoint fails\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(() => {}); // Silent fail\n });\n } catch (error) {\n logError('Error in trackConsoleEvent:', error);\n }\n }\n\n /**\n * Check if the actual caller (not SDK wrapper) is from SDK code\n * Since we intercept console methods, the stack will always include SDK frames.\n * We need to look at the actual caller frame (the one that called console.error/warn from user code).\n */\n private isSDKStackFrame(stack: string): boolean {\n if (!stack) return false;\n \n // SDK file path patterns to check for\n const sdkPatterns = [\n 'humanbehavior-js',\n '@humanbehavior/core',\n '@humanbehavior/browser',\n 'tracker.ts',\n 'api.ts',\n 'logger.ts',\n 'utils/logger',\n 'packages/core',\n 'packages/browser',\n 'index.mjs', // Built SDK bundle\n 'index.js' // Built SDK bundle\n ];\n \n // Parse stack into lines\n const stackLines = stack.split('\\n');\n const stackLower = stack.toLowerCase();\n \n // If the ENTIRE stack only contains SDK patterns, it's an SDK-originated log\n // Otherwise, if there's ANY non-SDK frame, it's user code\n \n // Check if ALL frames (excluding the first \"Error\" line) are SDK frames\n let foundNonSDKFrame = false;\n \n for (let i = 0; i < stackLines.length; i++) {\n const line = stackLines[i].trim().toLowerCase();\n \n // Skip empty lines and the first \"Error:\" line\n if (!line || line === 'error' || line.startsWith('error:')) {\n continue;\n }\n \n // Check if this line contains SDK patterns\n const isSDKFrame = sdkPatterns.some(pattern => line.includes(pattern.toLowerCase()));\n \n if (!isSDKFrame) {\n // Found a non-SDK frame - this is user code calling console.error/warn\n foundNonSDKFrame = true;\n break;\n }\n }\n \n // If we found a non-SDK frame, it's user code - don't skip\n // If all frames are SDK frames, skip tracking\n return !foundNonSDKFrame;\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 if (document.visibilityState === 'hidden') {\n logDebug('Page hidden - sending pending events');\n // Flush unified event queue\n this.flushEvents();\n } else if (document.visibilityState === 'visible') {\n logDebug('Page visible - taking full snapshot for multi-window replay');\n this.takeFullSnapshot();\n }\n });\n\n // Use pagehide if available (more reliable than beforeunload)\n // pagehide fires in more cases (navigation, tab close, etc.)\n const unloadEvent = 'onpagehide' in window ? 'pagehide' : 'beforeunload';\n \n window.addEventListener(unloadEvent, () => {\n // ✅ SYNCHRONOUS UNLOAD HANDLER\n // Prepare data synchronously (no await) and send via sendBeacon\n logDebug('Page unloading - sending final events via sendBeacon');\n \n // ✅ Check minimum duration before sending on unload (default: 5 seconds)\n const minimumDuration = this.minimumDurationMilliseconds;\n const sessionDuration = this.getSessionDuration();\n const isPositiveSessionDuration = sessionDuration !== null && sessionDuration >= 0;\n const isBelowMinimumDuration = \n isPositiveSessionDuration && \n sessionDuration < minimumDuration;\n \n if (isBelowMinimumDuration) {\n // Don't send - buffer stays\n logDebug(`Session duration (${sessionDuration}ms) below minimum (${minimumDuration}ms), not sending on unload`);\n return;\n }\n \n // 1. Prepare events synchronously (copy queue immediately)\n const eventsToSend = [...this.eventQueue];\n \n // 2. Include pending snapshots if available (for very short sessions)\n // This handles cases where a flush is in progress but hasn't completed\n if (isBrowser && (window as any).__hb_pending_snapshots) {\n const pendingSnapshots = (window as any).__hb_pending_snapshots;\n if (Array.isArray(pendingSnapshots) && pendingSnapshots.length > 0) {\n logDebug('Including pending FullSnapshot(s) in sendBeacon for short session');\n eventsToSend.unshift(...pendingSnapshots); // Add at beginning so snapshot comes first\n delete (window as any).__hb_pending_snapshots;\n }\n }\n \n // 3. Send via sendBeacon synchronously\n if (eventsToSend.length > 0 && this.api) {\n try {\n // Get automatic properties synchronously\n const automaticProperties = this.propertyManager.getAutomaticProperties();\n \n // Send via sendBeacon (synchronous API - completes before page closes)\n this.api.sendBeaconEvents(\n eventsToSend, \n this.sessionId, \n this.endUserId || undefined,\n this.windowId,\n automaticProperties\n );\n \n // Clear queue after sending\n this.eventQueue = [];\n } catch (error) {\n // sendBeacon is best-effort, log but don't throw\n logWarn('Failed to send events via sendBeacon on unload:', error);\n }\n }\n \n // 4. Also handle retry queue (for any failed requests)\n if (this.api) {\n this.api.unload();\n }\n });\n\n // Update activity timestamp on user interaction (not on page load)\n const updateActivity = () => {\n localStorage.setItem(`human_behavior_last_activity`, 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 // ✅ NON-BLOCKING: Don't wait for init, endUserId is already available locally\n // await this.ensureInitialized(); // Removed - no longer needed\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 user name\n const userResponse = await this.api.sendUserData(\n originalEndUserId || '',\n userProperties,\n this.sessionId\n );\n\n // If server found a preexisting user, persist the canonical ID for future sessions\n // but keep using the original anon ID for the current session\n if (userResponse.actualUserId || userResponse.wasExistingUser) {\n const canonicalEndUserId = userResponse.actualUserId || originalEndUserId;\n if (canonicalEndUserId && canonicalEndUserId !== originalEndUserId) {\n // Persist canonical ID to cookie/localStorage for future sessions\n const cookieName = `human_behavior_end_user_id`;\n this.setCookie(cookieName, canonicalEndUserId, 365);\n // Explicitly set localStorage as well to ensure it's persisted\n if (isBrowser) {\n try {\n localStorage.setItem(cookieName, canonicalEndUserId);\n } catch (error) {\n logDebug('Failed to set canonical endUserId in localStorage:', error);\n }\n }\n logDebug(`🔗 Preexisting user detected. Future sessions will use canonical ID: ${canonicalEndUserId} (current session stays: ${originalEndUserId})`);\n }\n }\n\n // Keep original endUserId for the entire session (no mid-session switch)\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 // ✅ NON-BLOCKING: Start immediately, don't wait for init\n // Init continues in background but doesn't block recording\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 // ✅ Initialize idle detection\n // Sync with session activity timestamp if it exists (from session creation)\n // Otherwise use current time\n this._lastActivityTimestamp = this._sessionActivityTimestamp !== null \n ? this._sessionActivityTimestamp \n : Date.now();\n this._isIdle = 'unknown'; // Start as unknown until first interaction\n\n // Start periodic flushing (unified queue)\n this.flushInterval = window.setInterval(() => {\n this.flushEvents();\n }, this.FLUSH_INTERVAL_MS);\n\n // ✅ Enable console tracking for warn/error logging (if enabled)\n if (this.enableConsoleTrackingFlag) {\n this.enableConsoleTracking();\n }\n \n // ✅ Enable network error tracking (if enabled)\n if (this.enableNetworkTrackingFlag) {\n this.enableNetworkTracking();\n }\n \n // Enable page load tracking\n this.enablePageLoadTracking();\n\n // Trigger server-side GeoIP enrichment once per session (fire-and-forget).\n // Server resolves IP from request headers and publishes a $geoip event.\n this.api.sendIpInfo(this.sessionId, this.endUserId);\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 // Wait for any pending animations/transitions to complete\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n try {\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 (delayed for animations)');\n } else {\n logWarn('⚠️ takeFullSnapshot not available on record function');\n }\n } catch (error) {\n logError('❌ Failed to take FullSnapshot:', error);\n }\n });\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 // Cleanup dead click tracking\n if (this.deadClickTracker.mutationObserver) {\n this.deadClickTracker.mutationObserver.disconnect();\n this.deadClickTracker.mutationObserver = undefined;\n }\n // Cancel all pending clicks\n this.deadClickTracker.pendingClicks.forEach((click, clickId) => {\n if (!click.cancelled) {\n clearTimeout(click.timer);\n }\n });\n this.deadClickTracker.pendingClicks.clear();\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 // ✅ NON-BLOCKING: Events work immediately, no waiting for init\n // endUserId and sessionId are already available locally\n \n // ✅ CHECK SESSION TIMEOUT before adding event (creates new session if expired)\n if (isBrowser) {\n this.checkAndRefreshSession();\n }\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 * Calculate session duration\n * Uses most recent event timestamp minus session start timestamp\n */\n private getSessionDuration(): number | null {\n // Use session start timestamp if available, otherwise fall back to sessionStartTime\n const sessionStart = this._sessionStartTimestamp ?? this.sessionStartTime;\n if (!sessionStart) {\n return null;\n }\n \n // Get most recent event timestamp from queue\n const eventsWithTimestamps = this.eventQueue.filter((e: any) => e && e.timestamp);\n if (eventsWithTimestamps.length === 0) {\n return null;\n }\n \n const mostRecentEvent = eventsWithTimestamps.reduce((latest: any, current: any) => {\n return (!latest || (current.timestamp && current.timestamp > latest.timestamp)) ? current : latest;\n }, null);\n \n if (!mostRecentEvent || !mostRecentEvent.timestamp) {\n return null;\n }\n \n // Calculate duration (both timestamps should be in milliseconds)\n const duration = mostRecentEvent.timestamp - sessionStart;\n return duration >= 0 ? duration : null;\n }\n\n /**\n * Check if session duration is below minimum threshold\n * Returns true if we should skip sending (session too short)\n */\n private shouldSkipDueToMinimumDuration(): boolean {\n const minimumDuration = this.minimumDurationMilliseconds;\n const sessionDuration = this.getSessionDuration();\n const isPositiveSessionDuration = sessionDuration !== null && sessionDuration >= 0;\n const isBelowMinimumDuration =\n isPositiveSessionDuration &&\n sessionDuration < minimumDuration;\n\n if (isBelowMinimumDuration) {\n logDebug(`Session duration (${sessionDuration}ms) below minimum (${minimumDuration}ms), skipping send`);\n return true;\n }\n\n return false;\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 // ✅ NON-BLOCKING: Don't check initialized - events work immediately\n if (this.isProcessing) {\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 // ✅ IDLE STATE: Don't flush when idle and queue is empty (events are being skipped anyway)\n // But allow flush if we have events queued (e.g., from before going idle or FullSnapshots)\n if (this._isIdle === true && this.eventQueue.length === 0) {\n return;\n }\n \n // ✅ Check minimum duration before flushing (default: 5 seconds)\n const minimumDuration = this.minimumDurationMilliseconds;\n const sessionDuration = this.getSessionDuration();\n const isPositiveSessionDuration = sessionDuration !== null && sessionDuration >= 0;\n const isBelowMinimumDuration = \n isPositiveSessionDuration && \n sessionDuration < minimumDuration;\n \n if (isBelowMinimumDuration) {\n // Don't flush - schedule retry\n logDebug(`Session duration (${sessionDuration}ms) below minimum (${minimumDuration}ms), buffering`);\n // Schedule retry after buffer timeout (2 seconds)\n setTimeout(() => {\n this.flushEvents();\n }, 2000);\n return;\n }\n\n this.isProcessing = true;\n try {\n // ✅ CRITICAL FIX FOR SHORT SESSIONS:\n // Swap the current queue with an empty one atomically\n // BUT: Store a reference to FullSnapshots so they can be re-sent on unload if needed\n const eventsToProcess = this.eventQueue;\n const fullSnapshotsInFlush = eventsToProcess.filter(e => e && e.type === 2);\n this.eventQueue = [];\n \n // Store FullSnapshots that are being flushed so they can be sent via sendBeacon on unload\n // if the HTTP request doesn't complete in time (for very short sessions)\n if (fullSnapshotsInFlush.length > 0 && isBrowser) {\n // Store in a way that's accessible during unload\n (window as any).__hb_pending_snapshots = fullSnapshotsInFlush;\n // Clear after a delay (long enough for HTTP to complete, short enough to not waste memory)\n setTimeout(() => {\n delete (window as any).__hb_pending_snapshots;\n }, 5000); // 5 seconds should be enough for HTTP to complete\n }\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 // ✅ Include all IDs in payload (endUserId, sessionId, windowId)\n // ✅ Include automatic properties for user creation on first event\n // Server will create user/session on first event if needed\n const automaticProperties = this.propertyManager.getAutomaticProperties();\n await this.api.sendEventsChunked(\n eventsToProcess, \n this.sessionId, \n this.endUserId!,\n this.windowId,\n automaticProperties\n );\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\n // ✅ Once we hit 5 seconds, flush all pending events (custom events, logs, network errors)\n // This ensures everything that happened before 5 seconds gets sent\n // Flush AFTER eventQueue to prioritize session recording events\n await this.flushPendingCustomEvents();\n await this.flushPendingLogs();\n await this.flushPendingNetworkErrors();\n } finally {\n this.isProcessing = false;\n }\n }\n\n /**\n * Check if an event represents user interaction (not background DOM mutations)\n */\n private isInteractiveEvent(event: any): boolean {\n // Event type 3 = IncrementalSnapshot\n if (event.type !== 3) {\n return false;\n }\n \n // Active sources that indicate user interaction\n // Source values from rrweb: 0=DomContentLoaded, 1=MouseMove, 2=MouseInteraction, 3=Scroll, \n // 4=ViewportResize, 5=Input, 6=MediaInteraction, 7=StyleSheetRule, 8=CanvasMutation, \n // 9=Font, 10=Log, 11=Drag, 12=StyleDeclaration, 13=Selection, 14=AdoptedStyleSheet, 15=Mutation\n const ACTIVE_SOURCES = [1, 2, 3, 4, 5, 6, 11]; // MouseMove, MouseInteraction, Scroll, ViewportResize, Input, MediaInteraction, Drag\n \n const source = event.data?.source;\n return ACTIVE_SOURCES.includes(source);\n }\n \n /**\n * Update idle state based on event activity\n * Also updates session activity timestamp to prevent premature session expiration\n */\n private updateIdleState(event: any): void {\n const isUserInteraction = this.isInteractiveEvent(event);\n const currentTime = event.timestamp || Date.now();\n \n // Update activity timestamp on user interaction\n if (isUserInteraction) {\n const wasIdle = this._isIdle === true;\n this._lastActivityTimestamp = currentTime;\n \n // ✅ CRITICAL: Also update session activity timestamp to prevent session expiration\n // This ensures the 15-minute session timeout is extended on user interaction\n // (checkAndRefreshSession will handle persistence, but we update memory here)\n if (this._sessionActivityTimestamp !== null) {\n this._sessionActivityTimestamp = currentTime;\n }\n \n // If we were idle and user interacts, exit idle state\n if (wasIdle) {\n logDebug('✅ User activity detected, exiting idle state');\n this._isIdle = false;\n \n // Take full snapshot when returning from idle to capture current state\n if (this.rrwebRecord && typeof this.rrwebRecord.takeFullSnapshot === 'function') {\n this.rrwebRecord.takeFullSnapshot();\n logDebug('✅ FullSnapshot taken after returning from idle');\n }\n } else if (this._isIdle === 'unknown') {\n // First interaction, mark as active\n this._isIdle = false;\n }\n } else if (this._isIdle !== true) {\n // Check if we should go idle (no user interaction for threshold time)\n // Note: This uses 5-minute threshold for idle detection (stops recording)\n // Session timeout uses 15-minute threshold (ends session completely)\n const timeSinceLastActivity = currentTime - this._lastActivityTimestamp;\n if (timeSinceLastActivity > this.IDLE_THRESHOLD_MS) {\n logDebug(`⏸️ Session idle detected (${Math.round(timeSinceLastActivity / 1000)}s since last activity) - stopping background event recording`);\n logDebug(`ℹ️ Session will expire after 15 minutes of inactivity (${Math.round((15 * 60 * 1000 - timeSinceLastActivity) / 1000)}s remaining)`);\n this._isIdle = true;\n \n // Flush buffer when going idle to save what we have\n this.flushEvents();\n }\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 // ✅ NON-BLOCKING: Recording events work immediately\n // endUserId and sessionId are already available locally\n \n // ✅ CHECK SESSION TIMEOUT before adding event (creates new session if expired)\n if (isBrowser) {\n this.checkAndRefreshSession();\n }\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 // ✅ IDLE DETECTION: Update idle state based on event\n this.updateIdleState(event);\n \n // ✅ IDLE STATE: Skip non-interactive events when idle (save bandwidth)\n // Always record FullSnapshots and user interactions, but skip background mutations\n if (this._isIdle === true && event.type === 3 && !this.isInteractiveEvent(event)) {\n // Skip background DOM mutations while idle\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 (but not when idle)\n else if (this._isIdle !== true && 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 /**\n * Check if sessionStorage is available and can be used\n */\n private _canUseSessionStorage(): boolean {\n if (!isBrowser) return false;\n try {\n const test = '__sessionStorage_test__';\n sessionStorage.setItem(test, test);\n sessionStorage.removeItem(test);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get windowId from sessionStorage\n * SessionStorage persists across page reloads but is unique per window/tab\n */\n private _getWindowIdFromStorage(): string | null {\n if (!this._canUseSessionStorage()) {\n return null;\n }\n try {\n return sessionStorage.getItem(this._window_id_storage_key);\n } catch {\n return null;\n }\n }\n\n /**\n * Set windowId in sessionStorage\n */\n private _setWindowIdInStorage(windowId: string): void {\n if (!this._canUseSessionStorage()) {\n return;\n }\n try {\n sessionStorage.setItem(this._window_id_storage_key, windowId);\n logDebug(`Stored windowId in sessionStorage: ${windowId}`);\n } catch (error) {\n logWarn('Failed to store windowId in sessionStorage:', error);\n }\n }\n\n /**\n * Remove windowId from sessionStorage\n */\n private _removeWindowIdFromStorage(): void {\n if (!this._canUseSessionStorage()) {\n return;\n }\n try {\n sessionStorage.removeItem(this._window_id_storage_key);\n } catch (error) {\n logWarn('Failed to remove windowId from sessionStorage:', error);\n }\n }\n\n /**\n * Check if primary_window_exists flag is set in sessionStorage\n * This flag indicates if a window was opened as a new tab/window (not a reload)\n */\n private _getPrimaryWindowExists(): boolean {\n if (!this._canUseSessionStorage()) {\n return false;\n }\n try {\n return sessionStorage.getItem(this._primary_window_exists_storage_key) === 'true';\n } catch {\n return false;\n }\n }\n\n /**\n * Set primary_window_exists flag in sessionStorage\n * This flag is set when DOM loads and cleared on beforeunload\n */\n private _setPrimaryWindowExists(value: boolean): void {\n if (!this._canUseSessionStorage()) {\n return;\n }\n try {\n if (value) {\n sessionStorage.setItem(this._primary_window_exists_storage_key, 'true');\n } else {\n sessionStorage.removeItem(this._primary_window_exists_storage_key);\n }\n } catch (error) {\n logWarn('Failed to set primary_window_exists flag:', error);\n }\n }\n\n /**\n * Get or create windowId with multi-window detection\n * - Reuses windowId on page reload (same tab)\n * - Creates new windowId for new windows/tabs\n */\n private getOrCreateWindowId(): string {\n if (!isBrowser) {\n return uuidv1();\n }\n\n const lastWindowId = this._getWindowIdFromStorage();\n const primaryWindowExists = this._getPrimaryWindowExists();\n\n if (lastWindowId && !primaryWindowExists) {\n // Page reload: primary_window_exists was cleared on beforeunload\n // Reuse the windowId from sessionStorage\n logDebug(`Reusing windowId from previous page load: ${lastWindowId}`);\n this._setWindowIdInStorage(lastWindowId);\n this._setPrimaryWindowExists(true);\n return lastWindowId;\n } else {\n // New window/tab: primary_window_exists exists (copied from original window)\n // OR no previous windowId exists\n // Create a new windowId\n const newWindowId = uuidv1();\n logDebug(`Creating new windowId: ${newWindowId} (new window/tab detected)`);\n this._setWindowIdInStorage(newWindowId);\n this._setPrimaryWindowExists(true);\n return newWindowId;\n }\n }\n\n /**\n * Setup beforeunload listener to clear primary_window_exists flag\n * This allows us to distinguish page reloads from new windows/tabs\n */\n private setupWindowUnloadListener(): void {\n if (!isBrowser) {\n return;\n }\n\n // Use beforeunload to clear the flag before page unloads\n window.addEventListener('beforeunload', () => {\n if (this._canUseSessionStorage()) {\n this._setPrimaryWindowExists(false);\n logDebug('Cleared primary_window_exists flag on beforeunload');\n }\n }, { capture: false });\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`;\n this.deleteCookie(userIdCookieName);\n \n // Clear session data from localStorage\n const sessionKey = `human_behavior_session`;\n localStorage.removeItem(sessionKey);\n \n // Reset user-related properties\n this.endUserId = null;\n this.userProperties = {};\n \n // Generate new IDs for the next user\n this.endUserId = uuidv1();\n this.setCookie(`human_behavior_end_user_id`, this.endUserId, 365);\n this.sessionId = this.createNewSession(sessionKey);\n // Create new windowId for new session (logout = new session)\n this.windowId = uuidv1();\n this._setWindowIdInStorage(this.windowId);\n this.api.setTrackingContext(this.sessionId, this.endUserId);\n // Emit FullSnapshot so the player can replay this new window\n this.takeFullSnapshot();\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 * Check and refresh session if expired (called before adding events)\n * Uses in-memory state as source of truth\n */\n private checkAndRefreshSession(): void {\n if (!isBrowser) return;\n \n const sessionKey = `human_behavior_session`;\n const now = Date.now();\n\n // Get session data (checks memory first, then localStorage)\n // getStoredSession() now handles session expiration and creation atomically\n const stored = this.getStoredSession(sessionKey);\n\n if (!stored || !stored.sessionId) {\n // No stored session - create new one\n this.createNewSession(sessionKey);\n // New session = new windowId\n this.windowId = uuidv1();\n this._setWindowIdInStorage(this.windowId);\n this.api.setTrackingContext(this.sessionId, this.endUserId);\n // Emit FullSnapshot so the player can replay this new window\n this.takeFullSnapshot();\n logDebug(`Created new session (no stored session): ${this.sessionId}`);\n return;\n }\n\n // Session is valid (getStoredSession() already handled expiration)\n // Update activity timestamp to extend the session\n this.updateSessionActivity(sessionKey, now, stored.sessionId, stored.sessionStartTimestamp);\n }\n\n /**\n * Get or create session ID with timeout checking\n * Called once during initialization\n * Uses in-memory state as source of truth\n */\n private getOrCreateSessionId(): string {\n if (!isBrowser) {\n return uuidv1();\n }\n\n const sessionKey = `human_behavior_session`;\n const now = Date.now();\n\n // Get session data (checks memory first, then localStorage)\n const stored = this.getStoredSession(sessionKey);\n\n if (!stored || !stored.sessionId) {\n const newSessionId = this.createNewSession(sessionKey);\n this.api.setTrackingContext(newSessionId, this.endUserId);\n return newSessionId;\n }\n\n // Check idle timeout (15 minutes)\n const SESSION_IDLE_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes\n const SESSION_MAX_LENGTH_MS = 24 * 60 * 60 * 1000; // 24 hours\n\n const timeSinceActivity = now - stored.lastActivityTimestamp;\n const sessionAge = now - stored.sessionStartTimestamp;\n\n if (timeSinceActivity > SESSION_IDLE_TIMEOUT_MS || sessionAge > SESSION_MAX_LENGTH_MS) {\n logDebug(`Session expired: idle=${timeSinceActivity}ms, age=${sessionAge}ms`);\n const newSessionId = this.createNewSession(sessionKey);\n this.api.setTrackingContext(newSessionId, this.endUserId);\n return newSessionId;\n }\n\n // Update activity timestamp (extends session)\n // Memory is already updated by getStoredSession() if it read from localStorage\n this.updateSessionActivity(sessionKey, now, stored.sessionId, stored.sessionStartTimestamp);\n return stored.sessionId;\n }\n\n /**\n * Get session data (check memory first, then localStorage)\n */\n private getStoredSession(key: string): { sessionId: string; lastActivityTimestamp: number; sessionStartTimestamp: number } | null {\n const now = Date.now();\n const SESSION_IDLE_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes\n const SESSION_MAX_LENGTH_MS = 24 * 60 * 60 * 1000; // 24 hours\n \n // Check in-memory state first (source of truth during session)\n // BUT: Always validate expiration before returning from memory\n if (this.sessionId && this._sessionActivityTimestamp !== null && this._sessionStartTimestamp !== null) {\n const timeSinceActivity = now - this._sessionActivityTimestamp;\n const sessionAge = now - this._sessionStartTimestamp;\n \n // If expired, immediately create new session (atomic operation)\n if (timeSinceActivity > SESSION_IDLE_TIMEOUT_MS || sessionAge > SESSION_MAX_LENGTH_MS) {\n logDebug(`Session in memory expired: creating new session immediately`);\n const oldSessionId = this.sessionId;\n this.createNewSession(key);\n // New session = new windowId\n this.windowId = uuidv1();\n this._setWindowIdInStorage(this.windowId);\n this.api.setTrackingContext(this.sessionId, this.endUserId);\n // Emit FullSnapshot so the player can replay this new window\n this.takeFullSnapshot();\n logInfo(`🔄 Session timeout (memory): Created new session ${this.sessionId} (previous: ${oldSessionId})`);\n // Return the new session (createNewSession ensures these are not null)\n if (this._sessionActivityTimestamp !== null && this._sessionStartTimestamp !== null) {\n return {\n sessionId: this.sessionId,\n lastActivityTimestamp: this._sessionActivityTimestamp,\n sessionStartTimestamp: this._sessionStartTimestamp\n };\n }\n } else {\n // Session in memory is valid\n return {\n sessionId: this.sessionId,\n lastActivityTimestamp: this._sessionActivityTimestamp,\n sessionStartTimestamp: this._sessionStartTimestamp\n };\n }\n }\n \n // Only read from localStorage if memory is empty (initialization or after page reload)\n try {\n const stored = localStorage.getItem(key);\n if (!stored) return null;\n const parsed = JSON.parse(stored);\n \n // Check expiration BEFORE setting in memory\n const timeSinceActivity = now - parsed.lastActivityTimestamp;\n const sessionAge = now - parsed.sessionStartTimestamp;\n \n if (timeSinceActivity > SESSION_IDLE_TIMEOUT_MS || sessionAge > SESSION_MAX_LENGTH_MS) {\n // Session expired - immediately create new session (atomic operation)\n logDebug(`Session in localStorage expired: idle=${Math.round(timeSinceActivity / 1000 / 60)}min, age=${Math.round(sessionAge / 1000 / 60 / 60)}hrs`);\n const oldSessionId = parsed.sessionId;\n this.createNewSession(key);\n // New session = new windowId\n this.windowId = uuidv1();\n this._setWindowIdInStorage(this.windowId);\n this.api.setTrackingContext(this.sessionId, this.endUserId);\n // Emit FullSnapshot so the player can replay this new window\n this.takeFullSnapshot();\n logInfo(`🔄 Session timeout (localStorage): Created new session ${this.sessionId} (previous: ${oldSessionId})`);\n // Return the new session (createNewSession ensures these are not null)\n if (this._sessionActivityTimestamp !== null && this._sessionStartTimestamp !== null) {\n return {\n sessionId: this.sessionId,\n lastActivityTimestamp: this._sessionActivityTimestamp,\n sessionStartTimestamp: this._sessionStartTimestamp\n };\n }\n }\n \n // Session is valid - update memory from storage (for next time)\n if (parsed.sessionId) {\n this.sessionId = parsed.sessionId;\n this._sessionActivityTimestamp = parsed.lastActivityTimestamp;\n this._sessionStartTimestamp = parsed.sessionStartTimestamp;\n }\n \n return parsed;\n } catch {\n return null;\n }\n }\n\n /**\n * Create a new session (update memory first, then persistence)\n */\n private createNewSession(key: string): string {\n const sessionId = uuidv1();\n const now = Date.now();\n \n // Update memory immediately (source of truth)\n this.sessionId = sessionId;\n this._sessionActivityTimestamp = now;\n this._sessionStartTimestamp = now;\n \n // ✅ Sync idle detection timestamp with session activity timestamp\n // This ensures idle detection (5 min) and session timeout (15 min) are aligned\n this._lastActivityTimestamp = now;\n \n // Then write to persistence\n const session = {\n sessionId,\n lastActivityTimestamp: now,\n sessionStartTimestamp: now\n };\n try {\n localStorage.setItem(key, JSON.stringify(session));\n } catch (e) {\n logWarn(`Failed to save session to localStorage: ${e}`);\n }\n \n logDebug(`Created new session: ${sessionId}`);\n return sessionId;\n }\n\n /**\n * Update session activity timestamp (update memory first, then persistence)\n * Note: This is called by checkAndRefreshSession() for any event, not just user interactions\n * For user interactions, updateIdleState() also updates this, keeping them in sync\n */\n private updateSessionActivity(key: string, timestamp: number, sessionId: string, sessionStartTimestamp: number): void {\n // Update memory immediately (source of truth)\n this.sessionId = sessionId;\n this._sessionActivityTimestamp = timestamp;\n this._sessionStartTimestamp = sessionStartTimestamp;\n \n // ✅ Note: We don't update _lastActivityTimestamp here because:\n // - updateIdleState() handles it for user interactions\n // - Non-interactive events shouldn't reset idle detection (5 min threshold)\n // - Session timeout (15 min) is extended by any event via checkAndRefreshSession()\n \n // Then write to persistence\n const session = {\n sessionId,\n lastActivityTimestamp: timestamp,\n sessionStartTimestamp\n };\n try {\n localStorage.setItem(key, JSON.stringify(session));\n } catch (e) {\n logWarn(`Failed to update session in localStorage: ${e}`);\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`);\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","sdkLoggingInProgress","isSDKLogging","logError","logWarn","logInfo","logDebug","RetryQueue","sendRequest","_isPolling","_pollIntervalMs","_queue","_areWeOnline","_sendRequest","navigator","onLine","addEventListener","_flush","retriableRequest","options","retriesPerformedSoFar","url","URL","searchParams","set","toString","_shouldRetry","_enqueue","callback","statusCode","status","text","requestOptions","msToNextRetry","rawBackoffTime","minBackoff","cappedBackoffTime","Math","min","jitter","random","ceil","pickNextRetryDelay","retryAt","logMessage","round","_poll","_poller","clearTimeout","setTimeout","notToFlush","toFlush","filter","item","catch","unload","_sendBeaconRequest","sendBeacon","body","Blob","type","EventPersistence","apiKey","maxQueueSize","storageKey","getQueue","stored","queue","Array","isArray","setQueue","limitedQueue","slice","name","code","smallerQueue","floor","clearQueue","addToQueue","event","shift","removeFromQueue","count","getQueueLength","SDK_VERSION","MAX_CHUNK_SIZE_BYTES","KEEP_ALIVE_THRESHOLD","isChunkSizeExceeded","currentChunk","newEvent","sessionId","TextEncoder","encode","safeJsonStringify","events","data","_","value","splitLargeEvent","simplifiedEvent","largeProperties","forEach","prop","pathname","Object","fromEntries","entries","key","includes","HumanBehaviorAPI","ingestionUrl","monthlyLimitReached","endUserId","cspBlocked","requestTimeout","currentBatchSize","baseUrl","persistence","retryQueue","_sendRequestInternal","_loadPersistedEvents","setTrackingContext","persistedQueue","queuedEvent","sendEventsChunked","windowId","automaticProperties","controller","AbortController","timeoutId","abort","estimatedSize","useKeepalive","method","response","fetch","headers","signal","keepalive","responseText","responseJson","json","ok","checkMonthlyLimit","init","userId","entryURL","referrer","location","href","document","trackedFetch","Authorization","Referer","sdkVersion","errorText","Error","statusText","sendEvents","validEvents","results","flat","result","_sendChunkWithRetry","_persistEvents","chunk","batchSize","startIndex","bodyString","max","sendUserData","userData","payload","userAttributes","posthogName","email","sendUserAuth","authFields","sendBeaconEvents","blob","sendCustomEvent","eventName","eventProperties","sendCustomEventBatch","sendLog","logData","substring","sendNetworkError","errorData","errorType","sendIpInfo","ipDetectionMethod","requestStartTime","requestId","uuidv1","shouldSkipTracking","shouldSkipNetworkTracking","trackedFetchWithBeaconFallback","requestDuration","duration","timestampMs","classifyHttpError","errorMessage","startTimeMs","spanName","spanStatus","attributes","timeoutError","isCSPViolation","classifyNetworkError","errorName","bodyJson","encodeURIComponent","Response","Headers","parsed","toLowerCase","urlObj","baseUrlObj","origin","startsWith","RedactionManager","redactedText","unredactedFields","Set","redactedFields","redactionMode","excludeSelectors","redactionStrategy","mode","unredactFields","setFieldsToUnredact","defaultMarks","fieldsToRedact","redactFields","setFieldsToRedact","legacyRedactFields","userFields","fields","clear","field","add","size","from","applyRedactionClasses","validFields","isPasswordSelector","applyUnredactionClasses","delete","clearUnredactedFields","removeUnredactionClasses","hasUnredactedFields","getRedactionMode","getUnredactedFields","getMaskTextSelector","join","readyState","selector","elements","querySelectorAll","element","classList","remove","some","pattern","replace","getOriginalValue","HTMLInputElement","HTMLTextAreaElement","isElementUnredacted","shouldUnredactElement","matches","detectDeviceType","userAgent","screenWidth","screen","width","screenHeight","height","test","extractDomain","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","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","initialize","loadSessionProperties","getEventProperties","properties","assign","setSessionProperty","applyDenylist","getAutomaticPropertiesWithGeoIP","geoIPProperties","saveSessionProperties","setSessionProperties","getSessionProperty","removeSessionProperty","setUserProperty","setUserProperties","getUserProperty","removeUserProperty","setOnce","scope","clearSessionProperties","clearUserProperties","reset","sessionStorage","deniedKey","updateAutomaticProperties","getAllProperties","automatic","session","user","initial","HumanBehaviorTracker","isTrackerStarted","isStarted","setupDomReadyHandler","onDomReady","capture","interval","setInterval","clearInterval","isDomReady","requestQueue","request","processRequest","domReadyHandlers","handler","queueRequest","addEvent","identifyUser","trackPageView","registerDomReadyHandler","suppressConsoleErrors","originalConsoleError","apply","originalConsoleWarn","preventDefault","__humanBehaviorGlobalTracker","logLevel","configureLogging","tracker","enableConsoleTracking","enableNetworkTracking","recordCanvas","setUnredactedFields","enableAutomaticTracking","setupAutomaticTracking","automaticTrackingOptions","start","eventQueue","pendingCustomEvents","pendingLogs","pendingNetworkErrors","_sessionActivityTimestamp","_sessionStartTimestamp","isProcessing","flushInterval","FLUSH_INTERVAL_MS","initialized","initializationPromise","originalConsole","consoleTrackingEnabled","originalFetch","networkTrackingEnabled","enableConsoleTrackingFlag","enableNetworkTrackingFlag","navigationTrackingEnabled","previousUrl","originalPushState","originalReplaceState","navigationListeners","_connectionBlocked","recordInstance","sessionStartTime","rrwebRecord","fullSnapshotTimeout","minimumDurationMilliseconds","_isIdle","_lastActivityTimestamp","IDLE_THRESHOLD_MS","rageClickTracker","clicks","RAGE_CLICK_THRESHOLD_PX","RAGE_CLICK_TIMEOUT_MS","RAGE_CLICK_CLICK_COUNT","deadClickTracker","pendingClicks","Map","DEAD_CLICK_SCROLL_THRESHOLD_MS","DEAD_CLICK_SELECTION_THRESHOLD_MS","DEAD_CLICK_MUTATION_THRESHOLD_MS","DEAD_CLICK_ABSOLUTE_TIMEOUT_MS","finalIngestionUrl","api","MAX_QUEUE_SIZE","redactionManager","propertyManager","endUserIdKey","existingEndUserId","getCookie","setCookie","persistenceName","_window_id_storage_key","_primary_window_exists_storage_key","getOrCreateSessionId","getOrCreateWindowId","setupWindowUnloadListener","setupPageUnloadHandler","setupNavigationTracking","ensureInitialized","history","pushState","replaceState","trackNavigationEvent","takeFullSnapshot","popstateListener","removeEventListener","hashchangeListener","fromUrl","toUrl","navigationData","to","eventType","pageViewProperties","navigationType","customEvent","pageViewData","enhancedProperties","checkAndRefreshSession","shouldSkipDueToMinimumDuration","flushPendingCustomEvents","customEventData","fallbackError","trackButtons","trackLinks","trackForms","includeText","includeClasses","setupClickTracking","setupAutomaticButtonTracking","setupAutomaticFormTracking","setupRageClickDetection","setupDeadClickDetection","async","target","tagName","closest","tag","x","clientX","y","clientY","page","id","elementId","textContent","trim","elementText","className","elementClass","button","buttonId","buttonType","buttonText","buttonClass","keys","isRageClick","clickCount","lastClick","abs","setupDeadClickMutationObserver","setupDeadClickScrollObserver","setupDeadClickSelectionObserver","setupDeadClickNavigationTracking","isInteractiveElement","ignoreClickForDeadDetection","clickId","clickTimestamp","mutationTimeAtClick","lastMutationTime","timeoutValue","timer","handleDeadClickTimeout","originalEvent","cancelled","lastMutationTimeAtClick","mutationObserver","MutationObserver","click","timeSinceClick","isNewMutation","withinThreshold","mutationJustBeforeClick","cancelPendingClick","observe","characterData","childList","subtree","passive","lastSelectionChangedTime","lastUrl","originalTrackNavigationEvent","bind","cancelAllPendingClicks","checkUrlChange","role","getAttribute","onclick","getComputedStyle","cursor","values","absoluteDelayMs","mutationDelayMs","currentMutationTime","mutationAfterClick","selectionChangedDelayMs","hadMutation","hadSelectionChange","fireDeadClickEvent","setupAutomaticLinkTracking","form","formData","FormData","formId","formAction","action","formMethod","formClass","cleanupNavigationTracking","cleanup","none","trackConsoleEvent","input","toUpperCase","LONG_LOADING_THRESHOLD_MS","longLoadingTimeoutId","longLoadingTracked","elapsedTime","flushPendingNetworkErrors","eventsToFlush","flushPendingLogs","logsToFlush","errorsToFlush","enablePageLoadTracking","trackPageLoad","performance","perfEntry","getEntriesByType","loadDuration","loadEventEnd","fetchStart","domContentLoaded","domContentLoadedEventEnd","domComplete","timeOrigin","disableConsoleTracking","stack","isSDKStackFrame","consoleData","map","arg","String","err","sdkPatterns","stackLines","split","foundNonSDKFrame","i","line","visibilityState","flushEvents","unloadEvent","minimumDuration","sessionDuration","getSessionDuration","eventsToSend","__hb_pending_snapshots","pendingSnapshots","unshift","updateActivity","viewLogs","originalEndUserId","userResponse","actualUserId","wasExistingUser","canonicalEndUserId","cookieName","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","node","querySelector","checkDomReady","once","requestAnimationFrame","stop","disconnect","hasData","hasNode","sessionStart","eventsWithTimestamps","mostRecentEvent","reduce","latest","current","eventsToProcess","fullSnapshotsInFlush","fullSnapshots","isInteractiveEvent","source","updateIdleState","isUserInteraction","currentTime","wasIdle","timeSinceLastActivity","_canUseSessionStorage","_getWindowIdFromStorage","_setWindowIdInStorage","_removeWindowIdFromStorage","_getPrimaryWindowExists","_setPrimaryWindowExists","lastWindowId","primaryWindowExists","newWindowId","daysToExpire","setTime","getTime","expires","toUTCString","cookie","localStorageError","nameEQ","ca","c","charAt","indexOf","cookieValue","localStorageValue","deleteCookie","logout","userIdCookieName","sessionKey","createNewSession","redact","setRedactedFields","restartWithNewRedaction","getStoredSession","updateSessionActivity","sessionStartTimestamp","newSessionId","timeSinceActivity","lastActivityTimestamp","sessionAge","SESSION_IDLE_TIMEOUT_MS","SESSION_MAX_LENGTH_MS","oldSessionId","getSessionId","getCurrentUrl","getSnapshotFrequencyInfo","currentInterval","currentThreshold","phase","testConnection","success","getConnectionStatus","recommendations","blocked","isPreexistingUser","getUserInfo","identifyUserGlobally","globalTracker","globalThis","sendEventGlobally","track","isGlobalTrackerAvailable"],"mappings":"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"}
|