@tracelog/lib 2.1.2 → 2.2.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.
@@ -1 +1 @@
1
- {"version":3,"file":"tracelog.esm.js","sources":["../../src/constants/config.constants.ts","../../src/constants/storage.constants.ts","../../src/types/config.types.ts","../../src/types/device.types.ts","../../src/types/emitter.types.ts","../../src/types/error.types.ts","../../src/types/event.types.ts","../../src/types/mode.types.ts","../../src/types/scroll.types.ts","../../src/types/validation-error.types.ts","../../src/constants/app.constants.ts","../../src/utils/logging.utils.ts","../../src/utils/browser/device-detector.utils.ts","../../src/constants/error.constants.ts","../../src/constants/performance.constants.ts","../../src/constants/version.constants.ts","../../src/utils/browser/mode.utils.ts","../../src/utils/browser/referrer.utils.ts","../../src/utils/browser/utm-params.utils.ts","../../src/utils/data/uuid.utils.ts","../../src/utils/network/url.utils.ts","../../src/utils/security/sanitize.utils.ts","../../src/utils/validations/config-validations.utils.ts","../../src/utils/validations/type-guards.utils.ts","../../src/utils/validations/metadata-validations.utils.ts","../../src/utils/validations/event-validations.utils.ts","../../src/utils/emitter.utils.ts","../../src/utils/transformer.utils.ts","../../src/managers/state.manager.ts","../../src/managers/sender.manager.ts","../../src/managers/time.manager.ts","../../src/managers/event.manager.ts","../../src/managers/user.manager.ts","../../src/managers/session.manager.ts","../../src/handlers/session.handler.ts","../../src/handlers/page-view.handler.ts","../../src/handlers/click.handler.ts","../../src/handlers/scroll.handler.ts","../../src/handlers/viewport.handler.ts","../../src/managers/storage.manager.ts","../../src/handlers/performance.handler.ts","../../src/handlers/error.handler.ts","../../src/app.ts","../../src/api.ts","../../src/public-api.ts","../../node_modules/web-vitals/dist/web-vitals.js"],"sourcesContent":["/**\n * Consolidated configuration constants for TraceLog\n * This file centralizes all timing, limits, browser, and initialization constants\n */\n\n// ============================================================================\n// SESSION & TIMING\n// ============================================================================\n\nexport const DEFAULT_SESSION_TIMEOUT = 15 * 60 * 1000; // 15 minutes\nexport const DUPLICATE_EVENT_THRESHOLD_MS = 1000; // 1 second (increased from 500ms to reduce duplicate events)\nexport const EVENT_SENT_INTERVAL_MS = 10000; // 10 seconds\n\n// Throttling and debouncing\nexport const SCROLL_DEBOUNCE_TIME_MS = 250;\nexport const DEFAULT_VISIBILITY_TIMEOUT_MS = 2000;\nexport const DEFAULT_PAGE_VIEW_THROTTLE_MS = 1000; // 1 second throttle for page views\nexport const DEFAULT_CLICK_THROTTLE_MS = 300; // 300ms throttle for clicks per element\nexport const DEFAULT_VIEWPORT_COOLDOWN_PERIOD = 60000; // 60 seconds cooldown for viewport events\nexport const DEFAULT_VIEWPORT_MAX_TRACKED_ELEMENTS = 100; // Maximum elements to track (Phase 3)\nexport const VIEWPORT_MUTATION_DEBOUNCE_MS = 100; // Debounce for mutation observer re-scanning\n\n// Click throttle cache limits\nexport const MAX_THROTTLE_CACHE_ENTRIES = 1000; // Maximum element signatures to track\nexport const THROTTLE_ENTRY_TTL_MS = 300000; // 5 minutes TTL for throttle entries\nexport const THROTTLE_PRUNE_INTERVAL_MS = 30000; // 30 seconds interval for cache pruning\n\n// Event expiry\nexport const EVENT_EXPIRY_HOURS = 2;\nexport const EVENT_PERSISTENCE_MAX_AGE_MS = 2 * 60 * 60 * 1000; // 2 hours\nexport const PERSISTENCE_THROTTLE_MS = 1000; // 1 second throttle for cross-tab persistence coordination\n\n// ============================================================================\n// LIMITS & REQUESTS\n// ============================================================================\n\nexport const MAX_EVENTS_QUEUE_LENGTH = 100;\nexport const REQUEST_TIMEOUT_MS = 15000; // 15 seconds (to ensure requests complete before tab close/navigation)\nexport const MAX_METADATA_SIZE = 5000;\n\n// Motion and interaction thresholds\nexport const DEFAULT_MOTION_THRESHOLD = 2;\nexport const SIGNIFICANT_SCROLL_DELTA = 10;\nexport const MIN_SCROLL_DEPTH_CHANGE = 5;\nexport const SCROLL_MIN_EVENT_INTERVAL_MS = 500;\nexport const MAX_SCROLL_EVENTS_PER_SESSION = 120;\n\n// Sampling and rate limits\nexport const DEFAULT_SAMPLING_RATE = 1;\nexport const MIN_SAMPLING_RATE = 0;\nexport const MAX_SAMPLING_RATE = 1;\nexport const RATE_LIMIT_WINDOW_MS = 1000; // 1 second window\nexport const MAX_EVENTS_PER_SECOND = 50; // Maximum 50 events per second (Phase 3: reduced from 200)\nexport const MAX_SAME_EVENT_PER_MINUTE = 60; // Maximum same custom event name per minute (prevents infinite loops)\nexport const PER_EVENT_RATE_LIMIT_WINDOW_MS = 60000; // 60 second window for per-event-name rate limiting\n\n// Per-session event caps (Phase 3)\nexport const MAX_EVENTS_PER_SESSION = 1000;\nexport const MAX_CLICKS_PER_SESSION = 500;\nexport const MAX_PAGE_VIEWS_PER_SESSION = 100;\nexport const MAX_CUSTOM_EVENTS_PER_SESSION = 500;\nexport const MAX_VIEWPORT_EVENTS_PER_SESSION = 200;\n\n// Queue and batch limits\nexport const BATCH_SIZE_THRESHOLD = 50;\nexport const MAX_PENDING_EVENTS_BUFFER = 100; // Maximum events to buffer before session init\n\n// Session timeout validation limits\nexport const MIN_SESSION_TIMEOUT_MS = 30000; // 30 seconds minimum\nexport const MAX_SESSION_TIMEOUT_MS = 86400000; // 24 hours maximum\n\n// Custom event validation limits\nexport const MAX_CUSTOM_EVENT_NAME_LENGTH = 120;\nexport const MAX_CUSTOM_EVENT_STRING_SIZE = 8 * 1024; // 8KB\nexport const MAX_CUSTOM_EVENT_KEYS = 10;\nexport const MAX_CUSTOM_EVENT_ARRAY_SIZE = 10;\nexport const MAX_NESTED_OBJECT_KEYS = 20; // Maximum keys in nested objects within arrays\nexport const MAX_METADATA_NESTING_DEPTH = 1; // Maximum nesting depth for metadata objects\n\n// Text content limits\nexport const MAX_TEXT_LENGTH = 255; // For click tracking text content\n\n// Data sanitization limits\nexport const MAX_STRING_LENGTH = 1000;\nexport const MAX_STRING_LENGTH_IN_ARRAY = 500; // Strings within arrays are more limited\nexport const MAX_ARRAY_LENGTH = 100;\nexport const MAX_OBJECT_DEPTH = 3;\n\n// Precision for numeric metrics\nexport const PRECISION_TWO_DECIMALS = 2 as const;\n\n// Sync XHR timeout\nexport const SYNC_XHR_TIMEOUT_MS = 2000; // 2 seconds\n\n// sendBeacon payload size limit (Phase 3)\nexport const MAX_BEACON_PAYLOAD_SIZE = 64 * 1024; // 64KB browser limit\n\n// Event fingerprint management (moderately increased for high-frequency sessions)\nexport const MAX_FINGERPRINTS = 1500; // Maximum fingerprints stored before cleanup (increased from 1000)\nexport const FINGERPRINT_CLEANUP_MULTIPLIER = 10; // Cleanup fingerprints older than 10x threshold (10 seconds)\nexport const MAX_FINGERPRINTS_HARD_LIMIT = 3000; // Hard limit for aggressive cleanup (increased from 2000)\n\n// ============================================================================\n// BROWSER & HTML\n// ============================================================================\n\n/**\n * Prefix for all HTML data attributes used by TraceLog\n * Used for features like data-tlog-ignore, data-tlog-event, etc.\n */\nexport const HTML_DATA_ATTR_PREFIX = 'data-tlog';\n\n/**\n * CSS selectors for interactive elements to track in click events\n *\n * Covers:\n * - Standard HTML interactive elements (button, a, input, select, textarea)\n * - ARIA roles (button, link, tab, menuitem, option, checkbox, radio, switch)\n * - Framework-specific attributes (routerLink, ng-click)\n * - Data attributes for actions (data-action, data-click, data-navigate, data-toggle)\n * - Common CSS classes (.btn, .button, .clickable, .nav-link, .menu-item)\n * - Testing attributes (data-testid)\n * - Accessibility (tabindex=\"0\")\n */\nexport const INTERACTIVE_SELECTORS = [\n 'button',\n 'a',\n 'input[type=\"button\"]',\n 'input[type=\"submit\"]',\n 'input[type=\"reset\"]',\n 'input[type=\"checkbox\"]',\n 'input[type=\"radio\"]',\n 'select',\n 'textarea',\n '[role=\"button\"]',\n '[role=\"link\"]',\n '[role=\"tab\"]',\n '[role=\"menuitem\"]',\n '[role=\"option\"]',\n '[role=\"checkbox\"]',\n '[role=\"radio\"]',\n '[role=\"switch\"]',\n '[routerLink]',\n '[ng-click]',\n '[data-action]',\n '[data-click]',\n '[data-navigate]',\n '[data-toggle]',\n '[onclick]',\n '.btn',\n '.button',\n '.clickable',\n '.nav-link',\n '.menu-item',\n '[data-testid]',\n '[tabindex=\"0\"]',\n] as const;\n\n/**\n * Standard UTM (Urchin Tracking Module) parameters for marketing attribution\n * These parameters are preserved in URLs for campaign tracking\n */\nexport const UTM_PARAMS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];\n\n/**\n * Default list of sensitive URL query parameters to filter out for privacy protection\n *\n * Includes:\n * - Authentication tokens (token, auth, key, session, access_token, refresh_token)\n * - Password reset links (reset, password, verification, code, otp)\n * - API keys (api_key, apikey, secret)\n *\n * These parameters are removed from tracked URLs to prevent PII leakage\n */\nexport const DEFAULT_SENSITIVE_QUERY_PARAMS = [\n 'token',\n 'auth',\n 'key',\n 'session',\n 'reset',\n 'password',\n 'api_key',\n 'apikey',\n 'secret',\n 'access_token',\n 'refresh_token',\n 'verification',\n 'code',\n 'otp',\n] as const;\n\n// ============================================================================\n// ============================================================================\n// INITIALIZATION\n// ============================================================================\n\nexport const INITIALIZATION_MAX_CONCURRENT_RETRIES = 20;\nexport const INITIALIZATION_CONCURRENT_RETRY_DELAY_MS = 50;\nexport const INITIALIZATION_TIMEOUT_MS = 10000;\n\n// ============================================================================\n// SESSION MANAGEMENT\n// ============================================================================\n\nexport const SESSION_SYNC_TIMEOUT_MS = 2000;\nexport const SESSION_MAX_RETRY_ATTEMPTS = 3;\nexport const SESSION_CLEANUP_DELAY_MS = 100;\n\n// Cross-tab coordination\nexport const CROSS_TAB_INITIALIZATION_LOCK_TIMEOUT_MS = 5000;\nexport const TAB_HEARTBEAT_INTERVAL_MS = 5000; // 5 seconds\nexport const TAB_ELECTION_TIMEOUT_MS = 2000; // 2 seconds\nexport const TAB_CLEANUP_DELAY_MS = 1000; // 1 second\n\n// Session recovery\nexport const SESSION_RECOVERY_WINDOW_MULTIPLIER = 2; // 2x session timeout\nexport const MAX_SESSION_RECOVERY_ATTEMPTS = 3;\nexport const MAX_SESSION_RECOVERY_WINDOW_MS = 24 * 60 * 60 * 1000; // 24 hours max\nexport const MIN_SESSION_RECOVERY_WINDOW_MS = 2 * 60 * 1000; // 2 minutes minimum\n\n// ============================================================================\n// SCROLL SUPPRESSION\n// ============================================================================\n\nexport const SCROLL_SUPPRESS_MULTIPLIER = 2;\n\n// ============================================================================\n// NETWORK TIMING\n// ============================================================================\n\nexport const RATE_LIMIT_INTERVAL = 1000; // 1 second\n\n// ============================================================================\n// RETRY CONFIGURATION\n// ============================================================================\n\n/**\n * Maximum number of retry attempts for failed event transmissions\n * Applied to 5xx errors and network timeouts (transient failures)\n * 4xx errors (permanent) are not retried\n */\nexport const MAX_SEND_RETRIES = 2;\n\n/**\n * Base delay for exponential backoff retry strategy (in milliseconds)\n * Formula: RETRY_BACKOFF_BASE_MS * (2 ^ attempt) + jitter\n * Example: attempt 1 = 100ms + jitter, attempt 2 = 200ms + jitter\n */\nexport const RETRY_BACKOFF_BASE_MS = 100;\n\n/**\n * Maximum random jitter added to retry backoff delay (in milliseconds)\n * Prevents thundering herd problem when multiple clients retry simultaneously\n * Jitter range: 0 to RETRY_BACKOFF_JITTER_MS\n */\nexport const RETRY_BACKOFF_JITTER_MS = 100;\n\n// ============================================================================\n// VALIDATION\n// ============================================================================\n\n/**\n * Standardized validation error messages for TraceLog configuration\n *\n * Centralizes all validation messages to ensure consistency across:\n * - API layer validation\n * - Configuration validation\n * - Runtime validation\n *\n * Messages include contextual information (e.g., min/max values) to help developers\n * quickly identify and fix configuration issues.\n */\nexport const VALIDATION_MESSAGES = {\n MISSING_PROJECT_ID: 'Project ID is required',\n PROJECT_ID_EMPTY_AFTER_TRIM: 'Project ID is required',\n INVALID_SESSION_TIMEOUT: `Session timeout must be between ${MIN_SESSION_TIMEOUT_MS}ms (30 seconds) and ${MAX_SESSION_TIMEOUT_MS}ms (24 hours)`,\n INVALID_SAMPLING_RATE: 'Sampling rate must be between 0 and 1',\n INVALID_ERROR_SAMPLING_RATE: 'Error sampling must be between 0 and 1',\n INVALID_TRACELOG_PROJECT_ID: 'TraceLog project ID is required when integration is enabled',\n INVALID_CUSTOM_API_URL: 'Custom API URL is required when integration is enabled',\n INVALID_SCROLL_CONTAINER_SELECTORS: 'Scroll container selectors must be valid CSS selectors',\n INVALID_GLOBAL_METADATA: 'Global metadata must be an object',\n INVALID_SENSITIVE_QUERY_PARAMS: 'Sensitive query params must be an array of strings',\n INVALID_PRIMARY_SCROLL_SELECTOR: 'Primary scroll selector must be a non-empty string',\n INVALID_PRIMARY_SCROLL_SELECTOR_SYNTAX: 'Invalid CSS selector syntax for primaryScrollSelector',\n INVALID_PAGE_VIEW_THROTTLE: 'Page view throttle must be a non-negative number',\n INVALID_CLICK_THROTTLE: 'Click throttle must be a non-negative number',\n INVALID_MAX_SAME_EVENT_PER_MINUTE: 'Max same event per minute must be a positive number',\n INVALID_VIEWPORT_CONFIG: 'Viewport config must be an object',\n INVALID_VIEWPORT_ELEMENTS: 'Viewport elements must be a non-empty array',\n INVALID_VIEWPORT_ELEMENT: 'Each viewport element must have a valid selector string',\n INVALID_VIEWPORT_ELEMENT_ID: 'Viewport element id must be a non-empty string',\n INVALID_VIEWPORT_ELEMENT_NAME: 'Viewport element name must be a non-empty string',\n INVALID_VIEWPORT_THRESHOLD: 'Viewport threshold must be a number between 0 and 1',\n INVALID_VIEWPORT_MIN_DWELL_TIME: 'Viewport minDwellTime must be a non-negative number',\n INVALID_VIEWPORT_COOLDOWN_PERIOD: 'Viewport cooldownPeriod must be a non-negative number',\n INVALID_VIEWPORT_MAX_TRACKED_ELEMENTS: 'Viewport maxTrackedElements must be a positive number',\n} as const;\n\n// ============================================================================\n// SECURITY\n// ============================================================================\n\n/**\n * Regular expressions for detecting and sanitizing XSS (Cross-Site Scripting) attacks\n *\n * Patterns detect:\n * - Script tags (<script>)\n * - JavaScript protocol URLs (javascript:)\n * - Inline event handlers (onclick=, onload=, etc.)\n * - Embedded content (<iframe>, <embed>, <object>)\n *\n * Used to sanitize user-provided data before storage or transmission\n */\nexport const XSS_PATTERNS = [\n /<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi,\n /javascript:/gi,\n /on\\w+\\s*=/gi,\n /<iframe\\b[^<]*(?:(?!<\\/iframe>)<[^<]*)*<\\/iframe>/gi,\n /<embed\\b[^>]*>/gi,\n /<object\\b[^<]*(?:(?!<\\/object>)<[^<]*)*<\\/object>/gi,\n] as const;\n","/**\n * Storage key management constants for TraceLog\n * All keys are namespaced with 'tlog' prefix to avoid conflicts\n */\n\n/**\n * Base key prefix for all TraceLog localStorage items\n * Used as namespace to prevent conflicts with other libraries\n */\nexport const STORAGE_BASE_KEY = 'tlog';\n\n/**\n * Storage key for QA mode flag in sessionStorage\n * Format: 'tlog:qa_mode'\n */\nexport const QA_MODE_KEY = `${STORAGE_BASE_KEY}:qa_mode`;\n\n/**\n * Storage key for user ID in localStorage\n * Format: 'tlog:uid'\n */\nexport const USER_ID_KEY = `${STORAGE_BASE_KEY}:uid`;\n\n/**\n * URL parameter name for activating/deactivating QA mode\n * Example: ?tlog_mode=qa or ?tlog_mode=qa_off\n */\nexport const QA_MODE_URL_PARAM = 'tlog_mode';\n\n/**\n * URL parameter value to enable QA mode\n */\nexport const QA_MODE_ENABLE_VALUE = 'qa';\n\n/**\n * URL parameter value to disable QA mode\n */\nexport const QA_MODE_DISABLE_VALUE = 'qa_off';\n\n/**\n * Generates storage key for event queue\n *\n * @param id - User ID or project identifier\n * @returns localStorage key for event queue (e.g., 'tlog:user123:queue')\n */\nexport const QUEUE_KEY = (id: string): string => (id ? `${STORAGE_BASE_KEY}:${id}:queue` : `${STORAGE_BASE_KEY}:queue`);\n\n/**\n * Generates storage key for session data\n *\n * @param id - Project identifier\n * @returns localStorage key for session (e.g., 'tlog:project123:session')\n */\nexport const SESSION_STORAGE_KEY = (id: string): string =>\n id ? `${STORAGE_BASE_KEY}:${id}:session` : `${STORAGE_BASE_KEY}:session`;\n\n/**\n * Generates storage key for cross-tab session synchronization\n *\n * @param id - Project identifier\n * @returns localStorage key for cross-tab session (e.g., 'tlog:project123:cross_tab_session')\n */\nexport const CROSS_TAB_SESSION_KEY = (id: string): string =>\n id ? `${STORAGE_BASE_KEY}:${id}:cross_tab_session` : `${STORAGE_BASE_KEY}:cross_tab_session`;\n\n/**\n * Generates storage key for tab information registry\n *\n * @param id - Project identifier\n * @returns localStorage key for tab info (e.g., 'tlog:project123:tab_info')\n */\nexport const TAB_INFO_KEY = (id: string): string =>\n id ? `${STORAGE_BASE_KEY}:${id}:tab_info` : `${STORAGE_BASE_KEY}:tab_info`;\n\n/**\n * Generates storage key for specific tab information\n *\n * @param projectId - Project identifier\n * @param tabId - Unique tab identifier\n * @returns localStorage key for tab-specific info (e.g., 'tlog:project123:tab:abc456:info')\n */\nexport const TAB_SPECIFIC_INFO_KEY = (projectId: string, tabId: string): string =>\n `${STORAGE_BASE_KEY}:${projectId}:tab:${tabId}:info`;\n\n/**\n * Generates storage key for session recovery data\n *\n * @param id - Project identifier\n * @returns localStorage key for session recovery (e.g., 'tlog:project123:recovery')\n */\nexport const SESSION_RECOVERY_KEY = (id: string): string =>\n id ? `${STORAGE_BASE_KEY}:${id}:recovery` : `${STORAGE_BASE_KEY}:recovery`;\n\n/**\n * Generates BroadcastChannel name for cross-tab communication\n *\n * @param id - Project identifier\n * @returns BroadcastChannel name (e.g., 'tlog:project123:broadcast')\n */\nexport const BROADCAST_CHANNEL_NAME = (id: string): string =>\n id ? `${STORAGE_BASE_KEY}:${id}:broadcast` : `${STORAGE_BASE_KEY}:broadcast`;\n\n/**\n * Generates storage key for per-session event counts\n *\n * Used to persist rate limiting counters across page reloads within the same session.\n * This prevents users from bypassing per-session event limits by refreshing the page.\n *\n * @param userId - User identifier\n * @param sessionId - Session identifier\n * @returns localStorage key for session counts (e.g., 'tlog:user123:session_counts:session456')\n */\nexport const SESSION_COUNTS_KEY = (userId: string, sessionId: string): string =>\n `${STORAGE_BASE_KEY}:${userId}:session_counts:${sessionId}`;\n\n/**\n * Session counts expiry duration (7 days in milliseconds).\n *\n * Session counts are automatically cleaned up after this duration to prevent\n * localStorage pollution. Counts older than 7 days are considered stale and\n * are removed on next page load.\n *\n * **Rationale**: 7 days provides sufficient buffer for:\n * - Long-running sessions (rare but possible)\n * - Users returning after extended inactivity\n * - While preventing indefinite accumulation (~100 bytes per session)\n */\nexport const SESSION_COUNTS_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000; // 7 days\n\n/**\n * Storage key for tracking last session counts cleanup timestamp.\n *\n * Used to throttle cleanup operations and prevent performance impact\n * from scanning localStorage on every EventManager initialization.\n *\n * Format: 'tlog:session_counts_last_cleanup'\n */\nexport const SESSION_COUNTS_LAST_CLEANUP_KEY = `${STORAGE_BASE_KEY}:session_counts_last_cleanup`;\n\n/**\n * Minimum interval between session counts cleanup runs (1 hour in milliseconds).\n *\n * Cleanup will only run if at least this much time has elapsed since the\n * last cleanup. This prevents performance degradation from frequent localStorage\n * scans while still ensuring regular cleanup of stale data.\n *\n * **Rationale**: 1 hour provides a good balance between:\n * - Preventing frequent scans on rapid page reloads\n * - Ensuring cleanup runs at least once per typical browsing session\n * - Minimal localStorage overhead (~100 entries typical, <1ms scan time)\n */\nexport const SESSION_COUNTS_CLEANUP_THROTTLE_MS = 60 * 60 * 1000; // 1 hour\n","import { MetadataType } from './common.types';\nimport { ViewportConfig } from './viewport.types';\nimport { WebVitalType } from './event.types';\n\n/**\n * Web Vitals filtering mode\n * - 'all': Track all Web Vitals metrics (full analytics)\n * - 'needs-improvement': Track metrics that need improvement or are poor (default, balanced)\n * - 'poor': Track only poor metrics (minimal data)\n */\nexport type WebVitalsMode = 'all' | 'needs-improvement' | 'poor';\n\nexport interface Config {\n /** Session inactivity timeout in milliseconds. @default 900000 */\n sessionTimeout?: number;\n /** Metadata appended to every tracked event. */\n globalMetadata?: Record<string, MetadataType>;\n /** Query parameters to remove before tracking URLs. */\n sensitiveQueryParams?: string[];\n /** Error event sampling rate between 0 and 1. @default 1 */\n errorSampling?: number;\n /** Event sampling rate between 0 and 1. @default 1 */\n samplingRate?: number;\n /** CSS selector to manually override primary scroll container detection. */\n primaryScrollSelector?: string;\n /** Viewport visibility tracking configuration. */\n viewport?: ViewportConfig;\n /** Page view throttle duration in milliseconds to prevent rapid navigation spam. @default 1000 */\n pageViewThrottleMs?: number;\n /** Click throttle duration in milliseconds to prevent double-clicks and rapid spam. @default 300 */\n clickThrottleMs?: number;\n /** Maximum number of same custom event name allowed per minute to prevent infinite loops. @default 60 */\n maxSameEventPerMinute?: number;\n /**\n * Web Vitals filtering mode. @default 'needs-improvement'\n * - 'all': Track all metrics (good, needs-improvement, poor) - full trend analysis\n * - 'needs-improvement': Track metrics that need improvement or are poor - balanced approach\n * - 'poor': Track only poor metrics - minimal data, focus on problems\n */\n webVitalsMode?: WebVitalsMode;\n /**\n * Custom Web Vitals thresholds in milliseconds (except CLS which is unitless).\n * Only applies when webVitalsMode is set. Overrides default thresholds for the selected mode.\n */\n webVitalsThresholds?: Partial<Record<WebVitalType, number>>;\n /** Optional configuration for third-party integrations. */\n integrations?: {\n /** TraceLog integration options. */\n tracelog?: {\n /** Required project ID TraceLog SaaS integration. */\n projectId: string;\n };\n /** Custom integration options. */\n custom?: {\n /** Endpoint for collecting events. */\n collectApiUrl: string;\n /** Allow HTTP URLs (not recommended for production). @default false */\n allowHttp?: boolean;\n };\n };\n}\n\nexport enum SpecialApiUrl {\n Localhost = 'localhost:8080',\n Fail = 'localhost:9999',\n}\n","/**\n * Device type classification for analytics segmentation\n *\n * **Detection Logic**:\n * - Mobile: User agent contains mobile keywords (iPhone, Android phone, etc.)\n * - Tablet: User agent contains tablet keywords (iPad, Android tablet, etc.)\n * - Desktop: Default fallback for non-mobile/tablet devices\n * - Unknown: User agent not detectable or SSR environment\n *\n * **Use Cases**:\n * - Device-specific analytics and dashboards\n * - User experience optimization by device type\n * - Performance monitoring segmented by device\n *\n * @see src/utils/browser/device-detector.utils.ts for detection implementation\n */\nexport enum DeviceType {\n /** Mobile phones (iPhone, Android phones) */\n Mobile = 'mobile',\n /** Tablet devices (iPad, Android tablets) */\n Tablet = 'tablet',\n /** Desktop computers and laptops */\n Desktop = 'desktop',\n /** Unable to determine device type */\n Unknown = 'unknown',\n}\n\n/**\n * Comprehensive device and environment information\n *\n * **Purpose**: Provides rich device context for analytics segmentation,\n * debugging, and user experience optimization.\n *\n * **Detection**: Uses navigator.userAgentData (modern) with UA string fallback.\n *\n * @see src/utils/browser/device-detector.utils.ts for detection implementation\n */\nexport interface DeviceInfo {\n /** Device form factor classification */\n type: DeviceType;\n /** OS name: \"Windows\", \"macOS\", \"iOS\", \"Android\", \"Linux\", \"ChromeOS\", \"Unknown\" */\n os: string;\n /** Browser name: \"Chrome\", \"Firefox\", \"Safari\", \"Edge\", \"Opera\", \"Unknown\" */\n browser: string;\n}\n","import { EventData } from './event.types';\nimport { EventsQueue } from './queue.types';\n\n/**\n * Generic callback function for event emitter subscriptions\n *\n * @template T - Type of data passed to the callback\n * @param data - Event data passed to the callback\n */\nexport type EmitterCallback<T = any> = (data: T) => void;\n\n/**\n * Available event emitter channels for TraceLog\n *\n * **Purpose**: Type-safe event subscription system for external integrations\n *\n * **Event Channels**:\n * - `event`: Individual events as they are tracked (real-time)\n * - `queue`: Complete event batches before network transmission (every 10s or 50 events)\n *\n * **Use Cases**:\n * - Real-time event processing\n * - Custom analytics integrations\n * - Debugging and monitoring\n *\n * @example\n * ```typescript\n * // Subscribe to individual events\n * tracelog.on('event', (event) => {\n * console.log('Event tracked:', event.type, event);\n * });\n *\n * // Subscribe to event batches\n * tracelog.on('queue', (batch) => {\n * console.log('Sending batch:', batch.events.length, 'events');\n * });\n * ```\n */\nexport enum EmitterEvent {\n /** Individual events as they are tracked */\n EVENT = 'event',\n /** Complete event batches before transmission */\n QUEUE = 'queue',\n}\n\n/**\n * Type mapping for event emitter channels\n *\n * **Purpose**: Ensures type safety when subscribing to events\n *\n * Maps each EmitterEvent to its corresponding payload type:\n * - `event` → `EventData`: Single event data\n * - `queue` → `EventsQueue`: Batch of events with metadata\n */\nexport interface EmitterMap {\n [EmitterEvent.EVENT]: EventData;\n [EmitterEvent.QUEUE]: EventsQueue;\n}\n","/**\n * Custom error types for TraceLog\n */\n\n/**\n * Represents a permanent HTTP error (4xx) that should not be retried\n * Examples: 400 Bad Request, 403 Forbidden, 404 Not Found\n */\nexport class PermanentError extends Error {\n constructor(\n message: string,\n public readonly statusCode?: number,\n ) {\n super(message);\n this.name = 'PermanentError';\n\n // Maintain proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, PermanentError);\n }\n }\n}\n","import { MetadataType } from './common.types';\n\n/**\n * Coordinate information from a click event\n * Includes absolute and relative positioning\n */\nexport type ClickCoordinates = Pick<ClickData, 'x' | 'y' | 'relativeX' | 'relativeY'>;\n\n/**\n * Web performance metric types tracked by the library\n * - LCP: Largest Contentful Paint\n * - CLS: Cumulative Layout Shift\n * - INP: Interaction to Next Paint\n * - FCP: First Contentful Paint\n * - TTFB: Time to First Byte\n * - LONG_TASK: Tasks exceeding 50ms\n */\nexport type WebVitalType = 'LCP' | 'CLS' | 'INP' | 'FCP' | 'TTFB' | 'LONG_TASK';\n\n/**\n * Event type name\n */\nexport type EventTypeName = (typeof EventType)[keyof typeof EventType];\n\n/**\n * Event type enum\n */\nexport enum EventType {\n /** Page navigation and view tracking */\n PAGE_VIEW = 'page_view',\n /** User click interactions */\n CLICK = 'click',\n /** Scroll depth and behavior */\n SCROLL = 'scroll',\n /** Session initialization */\n SESSION_START = 'session_start',\n /** Custom business events */\n CUSTOM = 'custom',\n /** Performance metrics */\n WEB_VITALS = 'web_vitals',\n /** JavaScript errors and rejections */\n ERROR = 'error',\n /** Element visibility tracking */\n VIEWPORT_VISIBLE = 'viewport_visible',\n}\n\n/**\n * Per-session event counts structure for rate limiting\n *\n * **Purpose**: Tracks how many events of each type have been generated\n * during the current session to enforce per-session limits and prevent\n * runaway event generation.\n *\n * **Usage**:\n * - Persisted to localStorage: `tlog:{userId}:session_counts:{sessionId}`\n * - Restored on page reload to maintain limits across navigations\n * - Debounced writes for performance (500ms delay)\n *\n * **Limits** (from config.constants.ts):\n * - Total: 1000 events per session\n * - Clicks: 500 per session\n * - Page Views: 100 per session\n * - Custom: 500 per session\n * - Viewport: 200 per session\n * - Scroll: 120 per session\n *\n * @see src/managers/event.manager.ts for implementation\n * @see src/constants/config.constants.ts for limit values\n */\nexport interface SessionEventCounts {\n /** Total events across all types */\n total: number;\n /** Click events count */\n [EventType.CLICK]: number;\n /** Page view events count */\n [EventType.PAGE_VIEW]: number;\n /** Custom events count */\n [EventType.CUSTOM]: number;\n /** Viewport visibility events count */\n [EventType.VIEWPORT_VISIBLE]: number;\n /** Scroll events count */\n [EventType.SCROLL]: number;\n /** Index signature for dynamic event type access */\n [key: string]: number;\n}\n\n/**\n * Scroll direction indicators\n */\nexport enum ScrollDirection {\n /** Scrolling upward */\n UP = 'up',\n /** Scrolling downward */\n DOWN = 'down',\n}\n\n/**\n * JavaScript error classification\n */\nexport enum ErrorType {\n /** Runtime JavaScript errors */\n JS_ERROR = 'js_error',\n /** Unhandled promise rejections */\n PROMISE_REJECTION = 'promise_rejection',\n}\n\n/**\n * Scroll event data captured during user scrolling\n */\nexport interface ScrollData {\n /** Current scroll depth as percentage (0-100) */\n depth: number;\n /** Direction of scroll movement */\n direction: ScrollDirection;\n /** CSS selector of the scrolled container */\n container_selector: string;\n /** Whether this is the primary viewport scroll */\n is_primary: boolean;\n /** Scroll velocity in pixels per second */\n velocity: number;\n /** Maximum scroll depth reached during session (0-100) */\n max_depth_reached: number;\n}\n\n/**\n * Click event data capturing user interaction details\n */\nexport interface ClickData {\n /** Absolute X coordinate in viewport (pixels) */\n x: number;\n /** Absolute Y coordinate in viewport (pixels) */\n y: number;\n /** Relative X position within element (0-1) */\n relativeX: number;\n /** Relative Y position within element (0-1) */\n relativeY: number;\n /** Element ID attribute */\n id?: string;\n /** Element class attribute */\n class?: string;\n /** HTML tag name */\n tag?: string;\n /** Element text content (truncated) */\n text?: string;\n /** Link href for anchor elements */\n href?: string;\n /** Element title attribute */\n title?: string;\n /** Image alt text for img elements */\n alt?: string;\n /** ARIA role attribute */\n role?: string;\n /** ARIA label attribute */\n ariaLabel?: string;\n /** Custom data attributes (data-*) */\n dataAttributes?: Record<string, string>;\n}\n\n/**\n * Element data for specialized click tracking\n * Used for form inputs and interactive elements\n */\nexport interface ClickTrackingElementData {\n /** DOM element being tracked */\n element: HTMLElement;\n /** Descriptive name for the element */\n name: string;\n /** Element value (for inputs) */\n value?: string;\n}\n\n/**\n * Custom event data for business-specific tracking\n */\nexport interface CustomEventData {\n /** Event name identifier */\n name: string;\n /** Additional event metadata */\n metadata?: Record<string, MetadataType> | Record<string, MetadataType>[];\n}\n\n/**\n * Web performance metrics data\n */\nexport interface WebVitalsData {\n /** Type of performance metric */\n type: WebVitalType;\n /** Metric value (varies by type) */\n value: number;\n}\n\n/**\n * JavaScript error details\n */\nexport interface ErrorData {\n /** Error classification */\n type: ErrorType;\n /** Error message text */\n message: string;\n /** Source file where error occurred */\n filename?: string;\n /** Line number in source file */\n line?: number;\n /** Column number in source file */\n column?: number;\n}\n\n/**\n * UTM campaign tracking parameters\n */\nexport interface UTM {\n /** Campaign source (e.g., google, newsletter) */\n source?: string;\n /** Campaign medium (e.g., cpc, email) */\n medium?: string;\n /** Campaign name identifier */\n campaign?: string;\n /** Campaign search term */\n term?: string;\n /** Campaign content variation */\n content?: string;\n}\n\n/**\n * Page view navigation data\n */\nexport interface PageViewData {\n /** Previous page URL */\n referrer?: string;\n /** Page title from document */\n title?: string;\n /** URL pathname */\n pathname?: string;\n /** URL query string */\n search?: string;\n /** URL hash fragment */\n hash?: string;\n}\n\n/**\n * Data captured when element becomes visible\n */\nexport interface ViewportEventData {\n /** CSS selector that matched the element */\n selector: string;\n /** Optional unique identifier for analytics (if configured) */\n id?: string;\n /** Optional human-readable name (if configured) */\n name?: string;\n /** Actual time (ms) element was visible before event fired */\n dwellTime: number;\n /** Actual visibility ratio when event fired (0-1) */\n visibilityRatio: number;\n}\n\n/**\n * Complete event data structure\n * All events share base properties with type-specific data\n */\nexport interface EventData {\n /** Unique event identifier */\n id: string;\n /** Event type classification */\n type: EventType;\n /** Current page URL where event occurred */\n page_url: string;\n /** Unix timestamp (milliseconds) */\n timestamp: number;\n /** HTTP referrer header */\n referrer?: string;\n /** Previous page URL for navigation events */\n from_page_url?: string;\n /** Scroll event details (when type is SCROLL) */\n scroll_data?: ScrollData;\n /** Click event details (when type is CLICK) */\n click_data?: ClickData;\n /** Custom event details (when type is CUSTOM) */\n custom_event?: CustomEventData;\n /** Performance metrics (when type is WEB_VITALS) */\n web_vitals?: WebVitalsData;\n /** Page view details (when type is PAGE_VIEW) */\n page_view?: PageViewData;\n /** Error details (when type is ERROR) */\n error_data?: ErrorData;\n /** Viewport visibility details (when type is VIEWPORT_VISIBLE) */\n viewport_data?: ViewportEventData;\n /** Campaign tracking parameters */\n utm?: UTM;\n}\n","/**\n * App modes for the TraceLog Library\n *\n * - QA: Quality Assurance mode - logs custom events to console for verification\n */\nexport enum Mode {\n QA = 'qa',\n}\n","import { EventData, EventType, ScrollData } from './event.types';\n\n/**\n * Primary scroll event type (main viewport scroll)\n *\n * **Purpose**: Type-safe representation of primary viewport scroll events\n *\n * Primary scroll events track the main page/viewport scrolling,\n * which is the most important scroll metric for engagement analysis.\n */\nexport type PrimaryScrollEvent = EventData & {\n type: EventType.SCROLL;\n scroll_data: ScrollData & { is_primary: true };\n};\n\n/**\n * Secondary scroll event type (scrollable container scroll)\n *\n * **Purpose**: Type-safe representation of container-specific scroll events\n *\n * Secondary scroll events track scrolling within specific elements\n * (e.g., modals, sidebars, embedded content) for granular engagement analysis.\n */\nexport type SecondaryScrollEvent = EventData & {\n type: EventType.SCROLL;\n scroll_data: ScrollData & { is_primary: false };\n};\n\n/**\n * Type guard to check if an event is a primary scroll event\n *\n * **Purpose**: Runtime type narrowing for primary viewport scrolls\n *\n * **Use Cases**:\n * - Filter events to process only main viewport scrolling\n * - Separate primary from secondary scroll analytics\n * - Type-safe event processing in transformers\n *\n * @param event - Event to check\n * @returns `true` if event is a primary scroll event\n *\n * @example\n * ```typescript\n * if (isPrimaryScrollEvent(event)) {\n * // event.scroll_data.is_primary is guaranteed to be true\n * console.log('Main viewport scrolled to', event.scroll_data.depth, '%');\n * }\n * ```\n */\nexport const isPrimaryScrollEvent = (event: EventData): event is PrimaryScrollEvent => {\n return (\n event.type === EventType.SCROLL && 'scroll_data' in event && (event.scroll_data as ScrollData).is_primary === true\n );\n};\n\n/**\n * Type guard to check if an event is a secondary scroll event\n *\n * **Purpose**: Runtime type narrowing for container-specific scrolls\n *\n * **Use Cases**:\n * - Filter events to process only container scrolling\n * - Analyze engagement with specific page sections\n * - Type-safe event processing in transformers\n *\n * @param event - Event to check\n * @returns `true` if event is a secondary scroll event\n *\n * @example\n * ```typescript\n * if (isSecondaryScrollEvent(event)) {\n * // event.scroll_data.is_primary is guaranteed to be false\n * console.log('Container scrolled:', event.scroll_data.container_selector);\n * }\n * ```\n */\nexport const isSecondaryScrollEvent = (event: EventData): event is SecondaryScrollEvent => {\n return (\n event.type === EventType.SCROLL && 'scroll_data' in event && (event.scroll_data as ScrollData).is_primary === false\n );\n};\n","/**\n * Custom error classes for TraceLog validation errors\n * Provides better error handling and consistency across validation layers\n */\n\n/**\n * Base class for all TraceLog validation errors\n */\nexport abstract class TraceLogValidationError extends Error {\n constructor(\n message: string,\n public readonly errorCode: string,\n public readonly layer: 'config' | 'app' | 'runtime',\n ) {\n super(message);\n this.name = this.constructor.name;\n\n // Maintains proper stack trace for where error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/**\n * Thrown when app configuration validation fails\n */\nexport class AppConfigValidationError extends TraceLogValidationError {\n constructor(message: string, layer: 'config' | 'app' | 'runtime' = 'config') {\n super(message, 'APP_CONFIG_INVALID', layer);\n }\n}\n\n/**\n * Thrown when session timeout validation fails\n */\nexport class SessionTimeoutValidationError extends TraceLogValidationError {\n constructor(message: string, layer: 'config' | 'app' | 'runtime' = 'config') {\n super(message, 'SESSION_TIMEOUT_INVALID', layer);\n }\n}\n\n/**\n * Thrown when sampling rate validation fails\n */\nexport class SamplingRateValidationError extends TraceLogValidationError {\n constructor(message: string, layer: 'config' | 'app' | 'runtime' = 'config') {\n super(message, 'SAMPLING_RATE_INVALID', layer);\n }\n}\n\n/**\n * Thrown when integrations validation fails\n */\nexport class IntegrationValidationError extends TraceLogValidationError {\n constructor(message: string, layer: 'config' | 'app' | 'runtime' = 'config') {\n super(message, 'INTEGRATION_INVALID', layer);\n }\n}\n\n/**\n * Thrown when initialization exceeds the maximum allowed timeout\n */\nexport class InitializationTimeoutError extends TraceLogValidationError {\n constructor(\n message: string,\n public readonly timeoutMs: number,\n layer: 'config' | 'app' | 'runtime' = 'runtime',\n ) {\n super(message, 'INITIALIZATION_TIMEOUT', layer);\n }\n}\n","/**\n * Console log style for active TraceLog operations\n * Used for visual highlighting in browser console during QA mode\n */\nexport const LOG_STYLE_ACTIVE =\n 'background: #ff9800; color: white; font-weight: bold; padding: 2px 8px; border-radius: 3px;';\n\n/**\n * Console log style for disabled TraceLog operations\n * Used for visual indication when features are disabled\n */\nexport const LOG_STYLE_DISABLED =\n 'background: #9e9e9e; color: white; font-weight: bold; padding: 2px 8px; border-radius: 3px;';\n\n/**\n * Console log style for critical errors (always visible)\n * Used for errors that must reach monitoring platforms like Sentry\n */\nexport const LOG_STYLE_CRITICAL =\n 'background: #d32f2f; color: white; font-weight: bold; padding: 2px 8px; border-radius: 3px;';\n","import { QA_MODE_KEY } from '../constants/storage.constants';\nimport { LOG_STYLE_CRITICAL } from '../constants/app.constants';\n\n/**\n * Log visibility level determining when logs are shown\n *\n * - 'critical': Always visible (production included) - for Sentry/monitoring\n * - 'qa': Only visible when QA mode is active (equivalent to showToClient: true)\n * - undefined: Only visible in NODE_ENV=development\n */\nexport type LogVisibility = 'critical' | 'qa';\n\n/**\n * Formats log messages with optional error information and environment-specific sanitization\n *\n * **Purpose**: Creates consistent log message format across the library with automatic\n * error handling and production-safe output.\n *\n * **Behavior**:\n * - **Production**: Sanitizes stack traces and file paths from error messages\n * - **Development**: Preserves full error messages for debugging\n * - **Fallback**: Handles non-Error objects gracefully\n *\n * **Format**: `[TraceLog] {message}: {error}` or `[TraceLog] {message}` if no error\n *\n * **Stack Trace Sanitization** (production only):\n * - Removes \"at function (...)\" stack lines\n * - Removes file paths with line/column numbers\n * - Prevents leaking internal file structure\n *\n * @param msg - Base log message\n * @param error - Optional error object (Error, string, object, or unknown type)\n * @returns Formatted log message string\n *\n * @example\n * ```typescript\n * // Basic message\n * formatLogMsg('Session started');\n * // → \"[TraceLog] Session started\"\n *\n * // With Error object (development)\n * formatLogMsg('Failed to init', new Error('Network timeout'));\n * // → \"[TraceLog] Failed to init: Network timeout\"\n *\n * // With Error object (production - sanitized)\n * formatLogMsg('Failed to init', new Error('Network timeout at handleRequest (app.ts:42:10)'));\n * // → \"[TraceLog] Failed to init: Network timeout\"\n *\n * // With string error\n * formatLogMsg('Config invalid', 'Missing API key');\n * // → \"[TraceLog] Config invalid: Missing API key\"\n * ```\n */\nexport const formatLogMsg = (msg: string, error?: unknown): string => {\n if (error) {\n if (process.env.NODE_ENV !== 'development' && error instanceof Error) {\n const sanitizedMessage = error.message.replace(/\\s+at\\s+.*$/gm, '').replace(/\\s*\\([^()]+:\\d+:\\d+\\)/g, '');\n return `[TraceLog] ${msg}: ${sanitizedMessage}`;\n }\n\n if (error instanceof Error) {\n return `[TraceLog] ${msg}: ${error.message}`;\n }\n\n if (typeof error === 'string') {\n return `[TraceLog] ${msg}: ${error}`;\n }\n\n if (typeof error === 'object') {\n try {\n return `[TraceLog] ${msg}: ${JSON.stringify(error)}`;\n } catch {\n return `[TraceLog] ${msg}: [Unable to serialize error]`;\n }\n }\n\n return `[TraceLog] ${msg}: ${String(error)}`;\n }\n\n return `[TraceLog] ${msg}`;\n};\n\n/**\n * Check if QA mode is active by reading sessionStorage directly\n *\n * NOTE: Intentional duplication of mode.utils.ts:isQaModeActive() to avoid\n * circular dependency (mode.utils.ts imports log() from this file).\n * Changes to QA mode detection logic must be synchronized in both files.\n *\n * @see src/utils/browser/mode.utils.ts - Canonical public implementation\n */\nconst isQaModeActive = (): boolean => {\n if (typeof window === 'undefined' || typeof sessionStorage === 'undefined') {\n return false;\n }\n try {\n return sessionStorage.getItem(QA_MODE_KEY) === 'true';\n } catch {\n return false;\n }\n};\n\n/**\n * Safe logging utility that enforces zero logs in production\n *\n * @param type - Log level (info, warn, error, debug)\n * @param msg - Message to log\n * @param extra - Optional extra data\n * @param extra.error - Error object to include in the log message\n * @param extra.data - Additional data object to log (will be sanitized in production)\n * @param extra.showToClient - If true, log will be shown in production (QA mode only) - DEPRECATED: use visibility: 'qa'\n * @param extra.style - CSS styles to apply to the console message (browser only, uses %c formatting)\n * @param extra.visibility - Controls when log is visible:\n * - 'critical': Always visible (production included) - for monitoring/Sentry\n * - 'qa': Only visible when QA mode is active\n * - undefined: Only visible in NODE_ENV=development\n *\n * Visibility hierarchy (production):\n * - CRITICAL: Always shown - errors that must reach monitoring platforms\n * - QA: Shown with ?tlog_mode=qa - custom event verification\n * - Default: Never shown in production\n *\n * Development behavior (NODE_ENV=development):\n * - All logs visible regardless of visibility level\n * - Full error messages and stack traces preserved\n */\nexport const log = (\n type: 'info' | 'warn' | 'error' | 'debug',\n msg: string,\n extra?: {\n error?: unknown;\n data?: Record<string, unknown>;\n showToClient?: boolean;\n style?: string;\n visibility?: LogVisibility;\n },\n): void => {\n const { error, data, showToClient = false, style, visibility } = extra ?? {};\n const formattedMsg = error ? formatLogMsg(msg, error) : `[TraceLog] ${msg}`;\n const method = type === 'error' ? 'error' : type === 'warn' ? 'warn' : 'log';\n const isProduction = process.env.NODE_ENV !== 'development';\n\n // Development: All logs visible\n if (!isProduction) {\n outputLog(method, formattedMsg, style, data);\n return;\n }\n\n // Production: Check visibility level\n const shouldShow = shouldShowLog(visibility, showToClient);\n\n if (!shouldShow) {\n return;\n }\n\n // Apply appropriate style for visibility level\n const effectiveStyle = getEffectiveStyle(visibility, style);\n const sanitizedData = data !== undefined ? sanitizeLogData(data) : undefined;\n\n outputLog(method, formattedMsg, effectiveStyle, sanitizedData);\n};\n\n/**\n * Determines if a log should be shown based on visibility level\n */\nconst shouldShowLog = (visibility: LogVisibility | undefined, showToClient: boolean): boolean => {\n // Critical logs are always shown\n if (visibility === 'critical') {\n return true;\n }\n\n // QA mode logs (including legacy showToClient)\n if (visibility === 'qa' || showToClient) {\n return isQaModeActive();\n }\n\n // Default: not shown in production\n return false;\n};\n\n/**\n * Gets the appropriate style for the visibility level\n */\nconst getEffectiveStyle = (visibility: LogVisibility | undefined, providedStyle: string | undefined): string => {\n if (providedStyle !== undefined && providedStyle !== '') {\n return providedStyle;\n }\n\n if (visibility === 'critical') {\n return LOG_STYLE_CRITICAL;\n }\n\n return '';\n};\n\n/**\n * Outputs the log message to console\n */\nconst outputLog = (\n method: 'log' | 'warn' | 'error',\n formattedMsg: string,\n style: string | undefined,\n data: Record<string, unknown> | undefined,\n): void => {\n const hasStyle = style !== undefined && style !== '';\n const styledMsg = hasStyle ? `%c${formattedMsg}` : formattedMsg;\n\n if (data !== undefined) {\n if (hasStyle) {\n console[method](styledMsg, style, data);\n } else {\n console[method](styledMsg, data);\n }\n } else {\n if (hasStyle) {\n console[method](styledMsg, style);\n } else {\n console[method](styledMsg);\n }\n }\n};\n\n/**\n * Sanitizes log data in production to prevent sensitive information leakage\n *\n * **Purpose**: Recursively redacts sensitive keys while preserving object expandability\n * in browser console for debugging.\n *\n * **Sensitive Keys Detected** (case-insensitive substring matching):\n * - `token`, `password`, `secret`, `key`, `apikey`, `api_key`, `sessionid`, `session_id`\n *\n * **Behavior**:\n * - **Redaction**: Replaces values with `'[REDACTED]'` string\n * - **Deep Cloning**: Creates new objects to avoid mutating originals\n * - **Recursive**: Handles nested objects and arrays\n * - **Preserves Structure**: Maintains object hierarchy for console inspection\n *\n * **Use Cases**:\n * - Production logging where sensitive data might be present\n * - Debugging with potentially sensitive configuration objects\n * - Error logging with user or session data\n *\n * @param data - Object to sanitize\n * @returns New object with sensitive keys redacted\n *\n * @example\n * ```typescript\n * const data = {\n * userId: '123',\n * apiKey: 'secret-key-123',\n * config: {\n * endpoint: 'https://api.com',\n * token: 'bearer-xyz'\n * }\n * };\n *\n * const sanitized = sanitizeLogData(data);\n * // {\n * // userId: '123',\n * // apiKey: '[REDACTED]',\n * // config: {\n * // endpoint: 'https://api.com',\n * // token: '[REDACTED]'\n * // }\n * // }\n * ```\n */\nconst sanitizeLogData = (data: Record<string, unknown>): Record<string, unknown> => {\n const sanitized: Record<string, unknown> = {};\n const sensitiveKeys = ['token', 'password', 'secret', 'key', 'apikey', 'api_key', 'sessionid', 'session_id'];\n\n for (const [key, value] of Object.entries(data)) {\n const lowerKey = key.toLowerCase();\n\n if (sensitiveKeys.some((sensitiveKey) => lowerKey.includes(sensitiveKey))) {\n sanitized[key] = '[REDACTED]';\n continue;\n }\n\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n sanitized[key] = sanitizeLogData(value as Record<string, unknown>);\n } else if (Array.isArray(value)) {\n sanitized[key] = value.map((item) =>\n item !== null && typeof item === 'object' && !Array.isArray(item)\n ? sanitizeLogData(item as Record<string, unknown>)\n : item,\n );\n } else {\n sanitized[key] = value;\n }\n }\n\n return sanitized;\n};\n","import { DeviceInfo, DeviceType } from '../../types/device.types';\nimport { log } from '../logging.utils';\n\nlet coarsePointerQuery: MediaQueryList | undefined;\nlet noHoverQuery: MediaQueryList | undefined;\n\nconst initMediaQueries = (): void => {\n if (typeof window !== 'undefined' && !coarsePointerQuery) {\n coarsePointerQuery = window.matchMedia('(pointer: coarse)');\n noHoverQuery = window.matchMedia('(hover: none)');\n }\n};\n\ninterface UserAgentBrand {\n brand: string;\n version: string;\n}\n\ninterface NavigatorWithUserAgentData extends Navigator {\n userAgentData?: {\n mobile: boolean;\n platform?: string;\n brands?: UserAgentBrand[];\n };\n}\n\nconst UNKNOWN = 'Unknown';\n\n/**\n * Detects OS name from navigator.userAgentData (modern) or userAgent string (fallback)\n */\nconst detectOS = (nav: NavigatorWithUserAgentData): string => {\n // Modern API (Chromium 90+)\n const platform = nav.userAgentData?.platform;\n if (platform != null && platform !== '') {\n if (/windows/i.test(platform)) return 'Windows';\n if (/macos/i.test(platform)) return 'macOS';\n if (/android/i.test(platform)) return 'Android';\n if (/linux/i.test(platform)) return 'Linux';\n if (/chromeos/i.test(platform)) return 'ChromeOS';\n if (/ios/i.test(platform)) return 'iOS';\n }\n\n // Fallback: Parse userAgent string\n const ua = navigator.userAgent;\n if (/Windows/i.test(ua)) return 'Windows';\n if (/iPhone|iPad|iPod/i.test(ua)) return 'iOS';\n if (/Mac OS X|Macintosh/i.test(ua)) return 'macOS';\n if (/Android/i.test(ua)) return 'Android';\n if (/CrOS/i.test(ua)) return 'ChromeOS';\n if (/Linux/i.test(ua)) return 'Linux';\n\n return UNKNOWN;\n};\n\n/**\n * Detects browser name from navigator.userAgentData.brands (modern) or userAgent string (fallback)\n */\nconst detectBrowser = (nav: NavigatorWithUserAgentData): string => {\n // Modern API (Chromium 90+)\n const brands = nav.userAgentData?.brands;\n if (brands != null && brands.length > 0) {\n // Filter out generic brands and find the actual browser\n const validBrands = brands.filter((b) => !/not.?a.?brand|chromium/i.test(b.brand));\n const firstBrand = validBrands[0];\n if (firstBrand != null) {\n const brand = firstBrand.brand;\n // Normalize brand names\n if (/google chrome/i.test(brand)) return 'Chrome';\n if (/microsoft edge/i.test(brand)) return 'Edge';\n if (/opera/i.test(brand)) return 'Opera';\n return brand;\n }\n }\n\n // Fallback: Parse userAgent string (order matters!)\n const ua = navigator.userAgent;\n if (/Edg\\//i.test(ua)) return 'Edge';\n if (/OPR\\//i.test(ua)) return 'Opera';\n if (/Chrome/i.test(ua)) return 'Chrome';\n if (/Firefox/i.test(ua)) return 'Firefox';\n if (/Safari/i.test(ua) && !/Chrome/i.test(ua)) return 'Safari';\n\n return UNKNOWN;\n};\n\n/**\n * Detects the device type based on screen size, user agent, and browser capabilities\n * @returns The detected device type\n */\nexport const getDeviceType = (): DeviceType => {\n try {\n const nav = navigator as NavigatorWithUserAgentData;\n\n if (nav.userAgentData != null && typeof nav.userAgentData.mobile === 'boolean') {\n const uaPlatform = nav.userAgentData.platform;\n if (uaPlatform != null && uaPlatform !== '' && /ipad|tablet/i.test(uaPlatform)) {\n return DeviceType.Tablet;\n }\n\n const result = nav.userAgentData.mobile ? DeviceType.Mobile : DeviceType.Desktop;\n return result;\n }\n\n initMediaQueries();\n\n const width = window.innerWidth;\n const hasCoarsePointer = coarsePointerQuery?.matches ?? false;\n const hasNoHover = noHoverQuery?.matches ?? false;\n const hasTouchSupport = 'ontouchstart' in window || navigator.maxTouchPoints > 0;\n const ua = navigator.userAgent.toLowerCase();\n const isMobileUA = /mobile|android|iphone|ipod|blackberry|iemobile|opera mini/.test(ua);\n const isTabletUA = /tablet|ipad|android(?!.*mobile)/.test(ua);\n\n if (width <= 767 || (isMobileUA && hasTouchSupport)) {\n return DeviceType.Mobile;\n }\n\n if ((width >= 768 && width <= 1024) || isTabletUA || (hasCoarsePointer && hasNoHover && hasTouchSupport)) {\n return DeviceType.Tablet;\n }\n\n return DeviceType.Desktop;\n } catch (error) {\n log('debug', 'Device detection failed, defaulting to desktop', { error });\n\n return DeviceType.Desktop;\n }\n};\n\n/**\n * Detects comprehensive device information including type, OS, and browser.\n *\n * Uses navigator.userAgentData (modern Chromium 90+) with userAgent string fallback.\n *\n * @returns DeviceInfo object with type, os, and browser fields\n */\nexport const getDeviceInfo = (): DeviceInfo => {\n try {\n const nav = navigator as NavigatorWithUserAgentData;\n\n return {\n type: getDeviceType(),\n os: detectOS(nav),\n browser: detectBrowser(nav),\n };\n } catch (error) {\n log('debug', 'Device info detection failed, using defaults', { error });\n\n return {\n type: DeviceType.Desktop,\n os: UNKNOWN,\n browser: UNKNOWN,\n };\n }\n};\n","/**\n * Error handling and PII sanitization constants for TraceLog\n * Centralizes patterns and limits for error tracking and data protection\n */\n\n// ============================================================================\n// PII SANITIZATION PATTERNS\n// ============================================================================\n\n/**\n * Regular expressions for detecting and sanitizing Personally Identifiable Information (PII)\n * These patterns are used to replace sensitive information with [REDACTED] in error messages\n */\nexport const PII_PATTERNS = [\n // Email addresses\n /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b/gi,\n\n // US Phone numbers (various formats)\n /\\b\\d{3}[-.]?\\d{3}[-.]?\\d{4}\\b/g,\n\n // Credit card numbers (16 digits with optional separators)\n /\\b\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b/g,\n\n // IBAN (International Bank Account Number)\n /\\b[A-Z]{2}\\d{2}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b/gi,\n\n // API keys/tokens (sk_test_, sk_live_, pk_test_, pk_live_, etc.)\n /\\b[sp]k_(test|live)_[a-zA-Z0-9]{10,}\\b/gi,\n\n // Bearer tokens (JWT-like patterns - matches complete and partial tokens)\n /Bearer\\s+[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)?(?:\\.[A-Za-z0-9_-]+)?/gi,\n\n // Passwords in connection strings (protocol://user:password@host)\n /:\\/\\/[^:/]+:([^@]+)@/gi,\n] as const;\n\n// ============================================================================\n// ERROR TRACKING LIMITS\n// ============================================================================\n\n/**\n * Maximum length for error messages before truncation\n * Prevents extremely long error messages from consuming excessive storage\n */\nexport const MAX_ERROR_MESSAGE_LENGTH = 500;\n\n/**\n * Time window for error suppression in milliseconds\n * Prevents duplicate errors from flooding the system within this timeframe\n */\nexport const ERROR_SUPPRESSION_WINDOW_MS = 5_000; // 5 seconds\n\n/**\n * Maximum number of unique errors to track for suppression\n * Prevents memory usage from growing indefinitely\n */\nexport const MAX_TRACKED_ERRORS = 50;\n\n/**\n * Hard limit for error tracking before aggressive cleanup\n * If this limit is exceeded, the entire error map is cleared\n */\nexport const MAX_TRACKED_ERRORS_HARD_LIMIT = MAX_TRACKED_ERRORS * 2;\n\n// ============================================================================\n// ERROR SAMPLING\n// ============================================================================\n\n/**\n * Default error sampling rate\n * Controls what percentage of errors are actually reported\n */\nexport const DEFAULT_ERROR_SAMPLING_RATE = 1; // 100% of errors\n\n// ============================================================================\n// ERROR BURST DETECTION (Phase 3)\n// ============================================================================\n\n/**\n * Time window for error burst detection in milliseconds\n * Tracks unique errors within this window\n */\nexport const ERROR_BURST_WINDOW_MS = 1000; // 1 second\n\n/**\n * Maximum number of unique errors allowed in burst window\n * Exceeding this triggers a cooldown period\n */\nexport const ERROR_BURST_THRESHOLD = 10; // 10 unique errors\n\n/**\n * Backoff period after burst detection in milliseconds\n * No errors will be tracked during this cooldown\n */\nexport const ERROR_BURST_BACKOFF_MS = 5000; // 5 seconds\n\n// ============================================================================\n// PERMANENT ERROR LOGGING\n// ============================================================================\n\n/**\n * Time window for throttling permanent error logs in milliseconds\n * Same error status codes are logged at most once per this window\n * Prevents console spam when backend repeatedly returns 4xx errors\n */\nexport const PERMANENT_ERROR_LOG_THROTTLE_MS = 60_000; // 1 minute\n","/**\n * Performance monitoring and web vitals constants for TraceLog\n * Centralizes thresholds and configuration for performance tracking\n */\n\nimport { WebVitalType } from '../types';\nimport type { WebVitalsMode } from '../types/config.types';\n\n// ============================================================================\n// WEB VITALS THRESHOLDS\n// ============================================================================\n\n/**\n * Web Vitals \"good\" thresholds (75th percentile boundaries)\n * Metrics below or equal to these values are considered good performance.\n * Reference: https://web.dev/articles/vitals\n */\nexport const WEB_VITALS_GOOD_THRESHOLDS: Record<WebVitalType, number> = {\n LCP: 2500, // Good: ≤ 2.5s\n FCP: 1800, // Good: ≤ 1.8s\n CLS: 0.1, // Good: ≤ 0.1\n INP: 200, // Good: ≤ 200ms\n TTFB: 800, // Good: ≤ 800ms\n LONG_TASK: 50,\n} as const;\n\n/**\n * Web Vitals \"needs improvement\" thresholds\n * Metrics exceeding these values need attention but aren't critically poor.\n * Reference: https://web.dev/articles/vitals\n */\nexport const WEB_VITALS_NEEDS_IMPROVEMENT_THRESHOLDS: Record<WebVitalType, number> = {\n LCP: 2500, // Needs improvement: > 2.5s (same as good boundary)\n FCP: 1800, // Needs improvement: > 1.8s\n CLS: 0.1, // Needs improvement: > 0.1\n INP: 200, // Needs improvement: > 200ms\n TTFB: 800, // Needs improvement: > 800ms\n LONG_TASK: 50,\n} as const;\n\n/**\n * Web Vitals \"poor\" thresholds\n * Metrics exceeding these values indicate poor performance requiring immediate attention.\n * Reference: https://web.dev/articles/vitals\n */\nexport const WEB_VITALS_POOR_THRESHOLDS: Record<WebVitalType, number> = {\n LCP: 4000, // Poor: > 4s\n FCP: 3000, // Poor: > 3s\n CLS: 0.25, // Poor: > 0.25\n INP: 500, // Poor: > 500ms\n TTFB: 1800, // Poor: > 1800ms\n LONG_TASK: 50,\n} as const;\n\n/**\n * Default Web Vitals mode\n * 'needs-improvement' provides balanced approach - captures metrics that need attention\n * while filtering out good performance (reduces noise and costs)\n */\nexport const DEFAULT_WEB_VITALS_MODE: WebVitalsMode = 'needs-improvement';\n\n/**\n * Get Web Vitals thresholds for the specified mode\n */\nexport const getWebVitalsThresholds = (mode: WebVitalsMode = DEFAULT_WEB_VITALS_MODE): Record<WebVitalType, number> => {\n switch (mode) {\n case 'all':\n return { LCP: 0, FCP: 0, CLS: 0, INP: 0, TTFB: 0, LONG_TASK: 0 }; // Track everything\n case 'needs-improvement':\n return WEB_VITALS_NEEDS_IMPROVEMENT_THRESHOLDS;\n case 'poor':\n return WEB_VITALS_POOR_THRESHOLDS;\n default:\n return WEB_VITALS_NEEDS_IMPROVEMENT_THRESHOLDS;\n }\n};\n\n// ============================================================================\n// PERFORMANCE MONITORING LIMITS\n// ============================================================================\n\n/**\n * Long task throttling interval in milliseconds\n * Prevents excessive long task events from being sent\n */\nexport const LONG_TASK_THROTTLE_MS = 1000;\n\n/**\n * Maximum number of navigation history entries to keep in memory\n * Prevents unbounded growth of reportedByNav Map in long-running SPAs\n * Uses FIFO eviction when limit is exceeded\n */\nexport const MAX_NAVIGATION_HISTORY = 50;\n\n/**\n * Precision for performance metric values\n * All performance metrics are rounded to 2 decimal places\n */\nexport const PERFORMANCE_PRECISION_DECIMALS = 2 as const;\n","import { version } from '../../package.json';\n\nexport const LIB_VERSION = version;\n","import {\n QA_MODE_KEY,\n QA_MODE_URL_PARAM,\n QA_MODE_ENABLE_VALUE,\n QA_MODE_DISABLE_VALUE,\n LOG_STYLE_ACTIVE,\n LOG_STYLE_DISABLED,\n} from '../../constants';\nimport { log } from '../logging.utils';\n\n// ============================================================================\n// Internal Helpers\n// ============================================================================\n\n/**\n * Check if browser environment is available\n */\nconst isBrowserEnvironment = (): boolean => {\n return typeof window !== 'undefined' && typeof sessionStorage !== 'undefined';\n};\n\n/**\n * Clean URL parameter from the current URL\n */\nconst cleanUrlParameter = (): void => {\n try {\n const params = new URLSearchParams(window.location.search);\n params.delete(QA_MODE_URL_PARAM);\n\n const search = params.toString();\n const url = window.location.pathname + (search ? '?' + search : '') + window.location.hash;\n\n window.history.replaceState({}, '', url);\n } catch {\n // Continue without cleaning URL\n }\n};\n\n// ============================================================================\n// QA Mode Public API\n// ============================================================================\n\n/**\n * Detects QA mode from URL parameter or sessionStorage\n *\n * QA mode shows custom event logs to help verify tracking implementation.\n *\n * Activation:\n * - URL: `?tlog_mode=qa` to enable, `?tlog_mode=qa_off` to disable\n * - Programmatic: `tracelog.setQaMode(true/false)`\n *\n * @returns True if QA mode is active, false otherwise\n */\nexport const detectQaMode = (): boolean => {\n if (!isBrowserEnvironment()) {\n return false;\n }\n\n try {\n const params = new URLSearchParams(window.location.search);\n const urlParam = params.get(QA_MODE_URL_PARAM);\n const storedState = sessionStorage.getItem(QA_MODE_KEY);\n\n let newState: boolean | null = null;\n\n if (urlParam === QA_MODE_ENABLE_VALUE) {\n newState = true;\n sessionStorage.setItem(QA_MODE_KEY, 'true');\n\n log('info', 'QA Mode ACTIVE', {\n visibility: 'qa',\n style: LOG_STYLE_ACTIVE,\n });\n } else if (urlParam === QA_MODE_DISABLE_VALUE) {\n newState = false;\n sessionStorage.setItem(QA_MODE_KEY, 'false');\n\n log('info', 'QA Mode DISABLED', {\n visibility: 'qa',\n style: LOG_STYLE_DISABLED,\n });\n }\n\n if (urlParam === QA_MODE_ENABLE_VALUE || urlParam === QA_MODE_DISABLE_VALUE) {\n cleanUrlParameter();\n }\n\n return newState ?? storedState === 'true';\n } catch {\n return false;\n }\n};\n\n/**\n * Set QA mode state programmatically\n *\n * @param enabled - True to enable, false to disable\n */\nexport const setQaMode = (enabled: boolean): void => {\n if (!isBrowserEnvironment()) {\n return;\n }\n\n try {\n sessionStorage.setItem(QA_MODE_KEY, enabled ? 'true' : 'false');\n\n log('info', enabled ? 'QA Mode ACTIVE' : 'QA Mode DISABLED', {\n visibility: 'qa',\n style: enabled ? LOG_STYLE_ACTIVE : LOG_STYLE_DISABLED,\n });\n } catch {\n log('debug', 'Cannot set QA mode: sessionStorage unavailable');\n }\n};\n\n/**\n * Check if QA mode is currently active\n *\n * @returns True if QA mode is active, false otherwise\n */\nexport const isQaModeActive = (): boolean => {\n if (!isBrowserEnvironment()) {\n return false;\n }\n\n try {\n return sessionStorage.getItem(QA_MODE_KEY) === 'true';\n } catch {\n return false;\n }\n};\n","import { log } from '../logging.utils';\n\n/**\n * List of compound TLDs that require special handling for root domain extraction.\n * Keep in sync with tracelog-api/src/utils/common/utils.ts\n */\nconst COMPOUND_TLDS = [\n 'co.uk',\n 'org.uk',\n 'com.au',\n 'net.au',\n 'com.br',\n 'co.nz',\n 'co.jp',\n 'com.mx',\n 'co.in',\n 'com.cn',\n 'co.za',\n];\n\n/**\n * Extracts the root (registrable) domain from a hostname.\n * Handles both standard TLDs (.com, .org) and compound TLDs (.co.uk, .com.br).\n *\n * @example\n * getRootDomain('www.example.com') // 'example.com'\n * getRootDomain('app.blog.example.com') // 'example.com'\n * getRootDomain('shop.example.co.uk') // 'example.co.uk'\n */\nconst getRootDomain = (hostname: string): string => {\n const parts = hostname.toLowerCase().split('.');\n if (parts.length <= 2) {\n return hostname.toLowerCase();\n }\n const lastTwo = parts.slice(-2).join('.');\n if (COMPOUND_TLDS.includes(lastTwo)) {\n return parts.slice(-3).join('.');\n }\n return parts.slice(-2).join('.');\n};\n\n/**\n * Checks if two hostnames belong to the same domain (including subdomains).\n * Extracts root domain and compares to handle cross-subdomain navigation.\n *\n * @example\n * isSameDomain('www.example.com', 'example.com') // true\n * isSameDomain('app.example.com', 'www.example.com') // true\n * isSameDomain('example.co.uk', 'app.example.co.uk') // true\n *\n * @param hostname1 - First hostname (e.g., 'www.example.com')\n * @param hostname2 - Second hostname (e.g., 'app.example.com')\n * @returns true if same root domain\n */\nconst isSameDomain = (hostname1: string, hostname2: string): boolean => {\n if (hostname1 === hostname2) {\n return true;\n }\n return getRootDomain(hostname1) === getRootDomain(hostname2);\n};\n\n/**\n * Returns the referrer if it's external, or 'Direct' if internal/empty.\n *\n * **Purpose**: Filter out internal referrers (same domain) to ensure\n * accurate traffic source attribution. Internal referrers occur when:\n * - Session expires and user navigates within the same site\n * - User opens new tab from an internal link\n * - Page refresh after session timeout\n *\n * **Logic**:\n * - Empty referrer → 'Direct'\n * - Referrer from same domain or subdomain → 'Direct' (internal navigation)\n * - External referrer → Returns original referrer\n *\n * **Subdomain Detection**:\n * - `www.example.com` → `example.com` ✓ (internal)\n * - `blog.example.com` → `example.com` ✓ (internal)\n * - `example.com` → `www.example.com` ✓ (internal)\n *\n * @returns External referrer URL or 'Direct'\n */\nexport const getExternalReferrer = (): string => {\n const referrer = document.referrer;\n if (!referrer) {\n return 'Direct';\n }\n try {\n const referrerHostname = new URL(referrer).hostname.toLowerCase();\n const currentHostname = window.location.hostname.toLowerCase();\n if (isSameDomain(referrerHostname, currentHostname)) {\n return 'Direct';\n }\n return referrer;\n } catch (error) {\n log('debug', 'Failed to parse referrer URL, using raw value', { error, data: { referrer } });\n return referrer;\n }\n};\n","import { UTM_PARAMS } from '../../constants';\nimport { UTM } from '../../types/event.types';\n\n/**\n * Extracts UTM parameters from the current URL\n * @returns UTM parameters object or undefined if none found\n */\nexport const getUTMParameters = (): UTM | undefined => {\n const urlParams = new URLSearchParams(window.location.search);\n const utmParams: Partial<Record<keyof UTM, string>> = {};\n\n UTM_PARAMS.forEach((param) => {\n const value = urlParams.get(param);\n\n if (value) {\n const key = param.split('utm_')[1] as keyof UTM;\n utmParams[key] = value;\n }\n });\n\n const result = Object.keys(utmParams).length ? utmParams : undefined;\n\n return result;\n};\n","/**\n * Generates a RFC4122 compliant UUID v4 using native crypto API with fallback\n * @returns A UUID string\n */\nexport const generateUUID = (): string => {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n};\n\n/**\n * Sequence counter for generating unique event IDs within the same millisecond.\n * Resets when timestamp changes, preventing collisions in high-frequency event bursts.\n */\nlet eventSequence = 0;\nlet lastEventTimestamp = 0;\n\n/**\n * Generates a unique event ID optimized for high-frequency event tracking.\n *\n * **Collision Prevention Strategy:**\n * - Timestamp: Millisecond precision for temporal ordering\n * - Sequence: Auto-incrementing counter (0-999) for same-millisecond events\n * - Random: Cryptographically secure random (3 bytes) for cross-tab/process uniqueness\n *\n * **Format:** `{timestamp}-{sequence}-{random}`\n * **Example:** `1704067200000-001-a3f9c2`\n *\n * **Guarantees:**\n * - ✅ No collisions within same millisecond (sequence counter)\n * - ✅ No collisions across tabs (crypto random)\n * - ✅ Temporal ordering preserved (timestamp first)\n * - ✅ Works in high-frequency bursts (1000 events/ms capacity)\n * - ✅ Clock skew protection (monotonic timestamp guarantee)\n *\n * @returns Unique event ID string\n */\nexport const generateEventId = (): string => {\n let timestamp = Date.now();\n\n // Protect against clock skew (NTP sync, manual time adjustments, timezone changes)\n // If clock moves backward, use last valid timestamp to prevent ID collisions\n if (timestamp < lastEventTimestamp) {\n timestamp = lastEventTimestamp;\n }\n\n // Increment sequence counter for events in same millisecond\n if (timestamp === lastEventTimestamp) {\n eventSequence = (eventSequence + 1) % 1000; // Reset at 1000 to keep 3 digits\n } else {\n eventSequence = 0;\n }\n\n // Always update lastEventTimestamp to track current (possibly adjusted) timestamp\n lastEventTimestamp = timestamp;\n\n const sequence = eventSequence.toString().padStart(3, '0');\n\n // Cryptographically secure random (3 bytes = 6 hex chars)\n // Reduced from 4 bytes since sequence provides uniqueness within millisecond\n let random = '';\n try {\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n const bytes = crypto.getRandomValues(new Uint8Array(3));\n if (bytes) {\n random = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n }\n }\n } catch {\n /* empty */\n }\n\n // Fallback to Math.random() if crypto unavailable\n if (!random) {\n random = Math.floor(Math.random() * 0xffffff)\n .toString(16)\n .padStart(6, '0');\n }\n\n return `${timestamp}-${sequence}-${random}`;\n};\n","import { Config } from '../../types';\nimport { DEFAULT_SENSITIVE_QUERY_PARAMS } from '../../constants';\nimport { log } from '../logging.utils';\n\n/**\n * Validates if a URL is valid and optionally allows HTTP URLs\n * @param url - The URL to validate\n * @param allowHttp - Whether to allow HTTP URLs (default: false)\n * @returns True if the URL is valid, false otherwise\n */\nconst isValidUrl = (url: string, allowHttp = false): boolean => {\n try {\n const parsed = new URL(url);\n const isHttps = parsed.protocol === 'https:';\n const isHttp = parsed.protocol === 'http:';\n\n return isHttps || (allowHttp && isHttp);\n } catch {\n return false;\n }\n};\n\n/**\n * Generates a SaaS API URL based on the given project ID and the current browser domain.\n * @param projectId - The project ID to use as a subdomain.\n * @returns The generated SaaS API URL.\n */\nconst generateSaasApiUrl = (projectId: string): string => {\n try {\n const url = new URL(window.location.href);\n const host = url.hostname;\n\n if (!host || typeof host !== 'string') {\n throw new Error('Invalid hostname');\n }\n\n if (host === 'localhost' || host === '127.0.0.1' || /^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(host)) {\n throw new Error(\n 'SaaS integration not supported on localhost or IP addresses. Use custom backend integration instead.',\n );\n }\n\n const parts = host.split('.');\n\n if (!parts || !Array.isArray(parts) || parts.length === 0 || (parts.length === 1 && parts[0] === '')) {\n throw new Error('Invalid hostname structure');\n }\n\n if (parts.length === 1) {\n throw new Error('Single-part domain not supported for SaaS integration');\n }\n\n let cleanDomain: string;\n\n if (parts.length === 2) {\n cleanDomain = parts.join('.');\n } else {\n cleanDomain = parts.slice(-2).join('.');\n }\n\n if (!cleanDomain || cleanDomain.split('.').length < 2) {\n throw new Error('Invalid domain structure for SaaS');\n }\n\n const collectApiUrl = `https://${projectId}.${cleanDomain}/collect`;\n const isValid = isValidUrl(collectApiUrl);\n\n if (!isValid) {\n throw new Error('Generated URL failed validation');\n }\n\n return collectApiUrl;\n } catch (error) {\n throw new Error(`Invalid SaaS URL configuration: ${error instanceof Error ? error.message : String(error)}`);\n }\n};\n\n/**\n * Generates collection API URLs for all configured integrations\n * @param config - The TraceLog configuration\n * @returns Object containing API URLs for each configured integration\n */\nexport const getCollectApiUrls = (config: Config): { saas?: string; custom?: string } => {\n const urls: { saas?: string; custom?: string } = {};\n\n // TraceLog SaaS integration\n if (config.integrations?.tracelog?.projectId) {\n urls.saas = generateSaasApiUrl(config.integrations.tracelog.projectId);\n }\n\n // Custom backend integration\n const customApiUrl = config.integrations?.custom?.collectApiUrl;\n if (customApiUrl) {\n const allowHttp = config.integrations?.custom?.allowHttp ?? false;\n const isValid = isValidUrl(customApiUrl, allowHttp);\n\n if (!isValid) {\n throw new Error('Invalid custom API URL');\n }\n\n urls.custom = customApiUrl;\n }\n\n return urls;\n};\n\n/**\n * Normalizes a URL by removing sensitive query parameters\n * Combines default sensitive parameters with custom ones provided by user\n * @param url - The URL to normalize\n * @param sensitiveQueryParams - Array of parameter names to remove (merged with defaults)\n * @returns The normalized URL\n */\nexport const normalizeUrl = (url: string, sensitiveQueryParams: string[] = []): string => {\n if (!url || typeof url !== 'string') {\n log('warn', 'Invalid URL provided to normalizeUrl', { data: { type: typeof url } });\n return url || '';\n }\n\n try {\n const urlObject = new URL(url);\n const searchParams = urlObject.searchParams;\n\n const allSensitiveParams = [...new Set([...DEFAULT_SENSITIVE_QUERY_PARAMS, ...sensitiveQueryParams])];\n\n let hasChanged = false;\n const removedParams: string[] = [];\n\n allSensitiveParams.forEach((param) => {\n if (searchParams.has(param)) {\n searchParams.delete(param);\n hasChanged = true;\n removedParams.push(param);\n }\n });\n\n if (!hasChanged && url.includes('?')) {\n return url;\n }\n\n urlObject.search = searchParams.toString();\n const result = urlObject.toString();\n\n return result;\n } catch (error) {\n log('warn', 'URL normalization failed, returning original', { error, data: { urlLength: url?.length } });\n\n return url;\n }\n};\n","import {\n MAX_ARRAY_LENGTH,\n MAX_OBJECT_DEPTH,\n MAX_STRING_LENGTH,\n MAX_NESTED_OBJECT_KEYS,\n XSS_PATTERNS,\n} from '../../constants';\nimport { MetadataType } from '../../types';\nimport { log } from '../logging.utils';\n\n/**\n * Sanitizes a string value to prevent XSS attacks\n * @param value - The string to sanitize\n * @returns The sanitized string\n */\nexport const sanitizeString = (value: string): string => {\n if (!value || typeof value !== 'string' || value.trim().length === 0) {\n return '';\n }\n\n let sanitized = value;\n\n if (value.length > MAX_STRING_LENGTH) {\n sanitized = value.slice(0, Math.max(0, MAX_STRING_LENGTH));\n }\n\n let xssPatternMatches = 0;\n for (const pattern of XSS_PATTERNS) {\n const beforeReplace = sanitized;\n sanitized = sanitized.replace(pattern, '');\n if (beforeReplace !== sanitized) {\n xssPatternMatches++;\n }\n }\n\n if (xssPatternMatches > 0) {\n log('warn', 'XSS patterns detected and removed', {\n data: {\n patternMatches: xssPatternMatches,\n valueLength: value.length,\n },\n });\n }\n\n sanitized = sanitized\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&#x27;')\n .replaceAll('/', '&#x2F;');\n\n const result = sanitized.trim();\n\n return result;\n};\n\n/**\n * Sanitizes any value recursively with depth protection\n * @param value - The value to sanitize\n * @param depth - Current recursion depth\n * @returns The sanitized value\n */\nconst sanitizeValue = (value: unknown, depth = 0): unknown => {\n if (depth > MAX_OBJECT_DEPTH) {\n return null;\n }\n\n if (value === null || value === undefined) {\n return null;\n }\n\n if (typeof value === 'string') {\n return sanitizeString(value);\n }\n\n if (typeof value === 'number') {\n if (!Number.isFinite(value) || value < -Number.MAX_SAFE_INTEGER || value > Number.MAX_SAFE_INTEGER) {\n return 0;\n }\n\n return value;\n }\n\n if (typeof value === 'boolean') {\n return value;\n }\n\n if (Array.isArray(value)) {\n const limitedArray = value.slice(0, MAX_ARRAY_LENGTH);\n const sanitizedArray = limitedArray.map((item) => sanitizeValue(item, depth + 1)).filter((item) => item !== null);\n\n return sanitizedArray;\n }\n\n if (typeof value === 'object') {\n const sanitizedObject: Record<string, unknown> = {};\n const entries = Object.entries(value);\n const limitedEntries = entries.slice(0, MAX_NESTED_OBJECT_KEYS);\n\n for (const [key, value_] of limitedEntries) {\n const sanitizedKey = sanitizeString(key);\n\n if (sanitizedKey) {\n const sanitizedValue = sanitizeValue(value_, depth + 1);\n\n if (sanitizedValue !== null) {\n sanitizedObject[sanitizedKey] = sanitizedValue;\n }\n }\n }\n\n return sanitizedObject;\n }\n\n return null;\n};\n\n/**\n * Sanitizes user metadata for custom events\n * @param metadata - The metadata to sanitize\n * @returns The sanitized metadata\n */\nexport const sanitizeMetadata = (metadata: unknown): Record<string, MetadataType> => {\n if (typeof metadata !== 'object' || metadata === null) {\n return {};\n }\n\n try {\n const sanitized = sanitizeValue(metadata);\n const result =\n typeof sanitized === 'object' && sanitized !== null ? (sanitized as Record<string, MetadataType>) : {};\n\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new Error(`[TraceLog] Metadata sanitization failed: ${errorMessage}`);\n }\n};\n","import {\n MAX_SESSION_TIMEOUT_MS,\n MIN_SESSION_TIMEOUT_MS,\n DEFAULT_SESSION_TIMEOUT,\n DEFAULT_SAMPLING_RATE,\n VALIDATION_MESSAGES,\n DEFAULT_PAGE_VIEW_THROTTLE_MS,\n DEFAULT_CLICK_THROTTLE_MS,\n MAX_SAME_EVENT_PER_MINUTE,\n DEFAULT_VIEWPORT_COOLDOWN_PERIOD,\n DEFAULT_VIEWPORT_MAX_TRACKED_ELEMENTS,\n DEFAULT_VISIBILITY_TIMEOUT_MS,\n DEFAULT_ERROR_SAMPLING_RATE,\n} from '../../constants';\nimport {\n Config,\n AppConfigValidationError,\n SessionTimeoutValidationError,\n SamplingRateValidationError,\n IntegrationValidationError,\n} from '../../types';\n\n/**\n * Validates the app configuration object (before normalization)\n * This validates the structure and basic types but allows for normalization afterward\n * @param config - The app configuration to validate\n * @throws {ProjectIdValidationError} If project ID validation fails\n * @throws {AppConfigValidationError} If other configuration validation fails\n */\nexport const validateAppConfig = (config?: Config): void => {\n if (config !== undefined && (config === null || typeof config !== 'object')) {\n throw new AppConfigValidationError('Configuration must be an object', 'config');\n }\n\n if (!config) {\n return;\n }\n\n if (config.sessionTimeout !== undefined) {\n if (\n typeof config.sessionTimeout !== 'number' ||\n config.sessionTimeout < MIN_SESSION_TIMEOUT_MS ||\n config.sessionTimeout > MAX_SESSION_TIMEOUT_MS\n ) {\n throw new SessionTimeoutValidationError(VALIDATION_MESSAGES.INVALID_SESSION_TIMEOUT, 'config');\n }\n }\n\n if (config.globalMetadata !== undefined) {\n if (typeof config.globalMetadata !== 'object' || config.globalMetadata === null) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_GLOBAL_METADATA, 'config');\n }\n }\n\n if (config.integrations) {\n validateIntegrations(config.integrations);\n }\n\n if (config.sensitiveQueryParams !== undefined) {\n if (!Array.isArray(config.sensitiveQueryParams)) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_SENSITIVE_QUERY_PARAMS, 'config');\n }\n\n for (const param of config.sensitiveQueryParams) {\n if (typeof param !== 'string') {\n throw new AppConfigValidationError('All sensitive query params must be strings', 'config');\n }\n }\n }\n\n if (config.errorSampling !== undefined) {\n if (typeof config.errorSampling !== 'number' || config.errorSampling < 0 || config.errorSampling > 1) {\n throw new SamplingRateValidationError(VALIDATION_MESSAGES.INVALID_ERROR_SAMPLING_RATE, 'config');\n }\n }\n\n if (config.samplingRate !== undefined) {\n if (typeof config.samplingRate !== 'number' || config.samplingRate < 0 || config.samplingRate > 1) {\n throw new SamplingRateValidationError(VALIDATION_MESSAGES.INVALID_SAMPLING_RATE, 'config');\n }\n }\n\n if (config.primaryScrollSelector !== undefined) {\n if (typeof config.primaryScrollSelector !== 'string' || !config.primaryScrollSelector.trim()) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_PRIMARY_SCROLL_SELECTOR, 'config');\n }\n\n // Validate CSS selector syntax (skip for 'window' special value)\n if (config.primaryScrollSelector !== 'window') {\n try {\n document.querySelector(config.primaryScrollSelector);\n } catch {\n throw new AppConfigValidationError(\n `${VALIDATION_MESSAGES.INVALID_PRIMARY_SCROLL_SELECTOR_SYNTAX}: \"${config.primaryScrollSelector}\"`,\n 'config',\n );\n }\n }\n }\n\n if (config.pageViewThrottleMs !== undefined) {\n if (typeof config.pageViewThrottleMs !== 'number' || config.pageViewThrottleMs < 0) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_PAGE_VIEW_THROTTLE, 'config');\n }\n }\n\n if (config.clickThrottleMs !== undefined) {\n if (typeof config.clickThrottleMs !== 'number' || config.clickThrottleMs < 0) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_CLICK_THROTTLE, 'config');\n }\n }\n\n if (config.maxSameEventPerMinute !== undefined) {\n if (typeof config.maxSameEventPerMinute !== 'number' || config.maxSameEventPerMinute <= 0) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_MAX_SAME_EVENT_PER_MINUTE, 'config');\n }\n }\n\n if (config.viewport !== undefined) {\n validateViewportConfig(config.viewport);\n }\n\n if (config.webVitalsMode !== undefined) {\n // Type check first\n if (typeof config.webVitalsMode !== 'string') {\n throw new AppConfigValidationError(\n `Invalid webVitalsMode type: ${typeof config.webVitalsMode}. Must be a string`,\n 'config',\n );\n }\n\n const validModes = ['all', 'needs-improvement', 'poor'];\n if (!validModes.includes(config.webVitalsMode)) {\n throw new AppConfigValidationError(\n `Invalid webVitalsMode: \"${config.webVitalsMode}\". Must be one of: ${validModes.join(', ')}`,\n 'config',\n );\n }\n }\n\n if (config.webVitalsThresholds !== undefined) {\n // Type check: must be object and not null or array\n if (\n typeof config.webVitalsThresholds !== 'object' ||\n config.webVitalsThresholds === null ||\n Array.isArray(config.webVitalsThresholds)\n ) {\n throw new AppConfigValidationError('webVitalsThresholds must be an object', 'config');\n }\n\n const validKeys = ['LCP', 'FCP', 'CLS', 'INP', 'TTFB', 'LONG_TASK'];\n for (const [key, value] of Object.entries(config.webVitalsThresholds)) {\n if (!validKeys.includes(key)) {\n throw new AppConfigValidationError(\n `Invalid Web Vitals threshold key: \"${key}\". Must be one of: ${validKeys.join(', ')}`,\n 'config',\n );\n }\n\n // Validate value is a valid number (not NaN, Infinity, negative, or non-number)\n if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {\n throw new AppConfigValidationError(\n `Invalid Web Vitals threshold value for ${key}: ${value}. Must be a non-negative finite number`,\n 'config',\n );\n }\n }\n }\n};\n\n/**\n * Validates viewport configuration\n * @param viewport - Viewport configuration to validate\n */\nconst validateViewportConfig = (viewport: Config['viewport']): void => {\n if (typeof viewport !== 'object' || viewport === null) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_CONFIG, 'config');\n }\n\n // Validate elements array\n if (!viewport.elements || !Array.isArray(viewport.elements)) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_ELEMENTS, 'config');\n }\n\n if (viewport.elements.length === 0) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_ELEMENTS, 'config');\n }\n\n // Track unique selectors to detect duplicates\n const uniqueSelectors = new Set<string>();\n\n // Validate each element\n for (const element of viewport.elements) {\n if (!element.selector || typeof element.selector !== 'string' || !element.selector.trim()) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_ELEMENT, 'config');\n }\n\n // Check for duplicate selectors\n const normalizedSelector = element.selector.trim();\n if (uniqueSelectors.has(normalizedSelector)) {\n throw new AppConfigValidationError(\n `Duplicate viewport selector found: \"${normalizedSelector}\". Each selector should appear only once.`,\n 'config',\n );\n }\n uniqueSelectors.add(normalizedSelector);\n\n if (element.id !== undefined && (typeof element.id !== 'string' || !element.id.trim())) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_ELEMENT_ID, 'config');\n }\n\n if (element.name !== undefined && (typeof element.name !== 'string' || !element.name.trim())) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_ELEMENT_NAME, 'config');\n }\n }\n\n // Validate threshold\n if (viewport.threshold !== undefined) {\n if (typeof viewport.threshold !== 'number' || viewport.threshold < 0 || viewport.threshold > 1) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_THRESHOLD, 'config');\n }\n }\n\n // Validate minDwellTime\n if (viewport.minDwellTime !== undefined) {\n if (typeof viewport.minDwellTime !== 'number' || viewport.minDwellTime < 0) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_MIN_DWELL_TIME, 'config');\n }\n }\n\n // Validate cooldownPeriod\n if (viewport.cooldownPeriod !== undefined) {\n if (typeof viewport.cooldownPeriod !== 'number' || viewport.cooldownPeriod < 0) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_COOLDOWN_PERIOD, 'config');\n }\n }\n\n // Validate maxTrackedElements\n if (viewport.maxTrackedElements !== undefined) {\n if (typeof viewport.maxTrackedElements !== 'number' || viewport.maxTrackedElements <= 0) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_MAX_TRACKED_ELEMENTS, 'config');\n }\n }\n};\n\n/**\n * Validates integrations configuration\n * @param integrations - Integrations configuration to validate\n */\nconst validateIntegrations = (integrations: Config['integrations']): void => {\n if (!integrations) {\n return;\n }\n\n if (integrations.tracelog) {\n if (\n !integrations.tracelog.projectId ||\n typeof integrations.tracelog.projectId !== 'string' ||\n integrations.tracelog.projectId.trim() === ''\n ) {\n throw new IntegrationValidationError(VALIDATION_MESSAGES.INVALID_TRACELOG_PROJECT_ID, 'config');\n }\n }\n\n if (integrations.custom) {\n if (\n !integrations.custom.collectApiUrl ||\n typeof integrations.custom.collectApiUrl !== 'string' ||\n integrations.custom.collectApiUrl.trim() === ''\n ) {\n throw new IntegrationValidationError(VALIDATION_MESSAGES.INVALID_CUSTOM_API_URL, 'config');\n }\n\n if (integrations.custom.allowHttp !== undefined && typeof integrations.custom.allowHttp !== 'boolean') {\n throw new IntegrationValidationError('allowHttp must be a boolean', 'config');\n }\n\n const collectApiUrl = integrations.custom.collectApiUrl.trim();\n\n if (!collectApiUrl.startsWith('http://') && !collectApiUrl.startsWith('https://')) {\n throw new IntegrationValidationError('Custom API URL must start with \"http://\" or \"https://\"', 'config');\n }\n\n const allowHttp = integrations.custom.allowHttp ?? false;\n\n if (!allowHttp && collectApiUrl.startsWith('http://')) {\n throw new IntegrationValidationError(\n 'Custom API URL must use HTTPS in production. Set allowHttp: true in integration config to allow HTTP (not recommended)',\n 'config',\n );\n }\n }\n};\n\n/**\n * Validates and normalizes the app configuration\n * This is the primary validation entry point that ensures consistent behavior\n * @param config - The app configuration to validate and normalize\n * @returns The normalized configuration\n * @throws {ProjectIdValidationError} If project ID validation fails after normalization\n * @throws {AppConfigValidationError} If other configuration validation fails\n */\nexport const validateAndNormalizeConfig = (config?: Config): Config => {\n validateAppConfig(config);\n\n const normalizedConfig: Config = {\n ...(config ?? {}),\n sessionTimeout: config?.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT,\n globalMetadata: config?.globalMetadata ?? {},\n sensitiveQueryParams: config?.sensitiveQueryParams ?? [],\n errorSampling: config?.errorSampling ?? DEFAULT_ERROR_SAMPLING_RATE,\n samplingRate: config?.samplingRate ?? DEFAULT_SAMPLING_RATE,\n pageViewThrottleMs: config?.pageViewThrottleMs ?? DEFAULT_PAGE_VIEW_THROTTLE_MS,\n clickThrottleMs: config?.clickThrottleMs ?? DEFAULT_CLICK_THROTTLE_MS,\n maxSameEventPerMinute: config?.maxSameEventPerMinute ?? MAX_SAME_EVENT_PER_MINUTE,\n };\n\n if (normalizedConfig.integrations?.custom) {\n normalizedConfig.integrations.custom = {\n ...normalizedConfig.integrations.custom,\n allowHttp: normalizedConfig.integrations.custom.allowHttp ?? false,\n };\n }\n\n if (normalizedConfig.viewport) {\n normalizedConfig.viewport = {\n ...normalizedConfig.viewport,\n threshold: normalizedConfig.viewport.threshold ?? 0.5,\n minDwellTime: normalizedConfig.viewport.minDwellTime ?? DEFAULT_VISIBILITY_TIMEOUT_MS,\n cooldownPeriod: normalizedConfig.viewport.cooldownPeriod ?? DEFAULT_VIEWPORT_COOLDOWN_PERIOD,\n maxTrackedElements: normalizedConfig.viewport.maxTrackedElements ?? DEFAULT_VIEWPORT_MAX_TRACKED_ELEMENTS,\n };\n }\n\n return normalizedConfig;\n};\n","import { MAX_NESTED_OBJECT_KEYS, MAX_METADATA_NESTING_DEPTH } from '../../constants';\n\n/**\n * Validates if an item in an array is a valid nested object\n * @param item - The item to validate\n * @returns True if the item is a valid nested object\n */\nconst isValidArrayItem = (item: unknown): boolean => {\n if (typeof item === 'string') {\n return true;\n }\n\n // Allow objects with primitive fields only (one level deep)\n if (typeof item === 'object' && item !== null && !Array.isArray(item)) {\n const entries = Object.entries(item);\n\n // Check key count limit\n if (entries.length > MAX_NESTED_OBJECT_KEYS) {\n return false;\n }\n\n // All values must be primitives (no nested objects or arrays)\n for (const [, value] of entries) {\n if (value === null || value === undefined) {\n continue;\n }\n\n const type = typeof value;\n if (type !== 'string' && type !== 'number' && type !== 'boolean') {\n return false;\n }\n }\n\n return true;\n }\n\n return false;\n};\n\n/**\n * Checks if an object contains only primitive fields, string arrays, arrays of flat objects,\n * or nested objects with primitive fields\n * @param object - The object to check\n * @param depth - Current nesting depth (default: 0, max: MAX_METADATA_NESTING_DEPTH)\n * @returns True if the object contains only valid fields\n */\nexport const isOnlyPrimitiveFields = (object: Record<string, unknown>, depth = 0): boolean => {\n if (typeof object !== 'object' || object === null) {\n return false;\n }\n\n if (depth > MAX_METADATA_NESTING_DEPTH) {\n return false;\n }\n\n for (const value of Object.values(object)) {\n if (value === null || value === undefined) {\n continue;\n }\n\n const type = typeof value;\n if (type === 'string' || type === 'number' || type === 'boolean') {\n continue;\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) {\n continue;\n }\n\n // Determine array type from first item\n const firstItem = value[0];\n const isStringArray = typeof firstItem === 'string';\n\n // All items must be of the same type (all strings OR all objects)\n if (isStringArray) {\n if (!value.every((item) => typeof item === 'string')) {\n return false;\n }\n } else {\n // Must be all objects\n if (!value.every((item) => isValidArrayItem(item))) {\n return false;\n }\n }\n\n continue;\n }\n\n // Allow nested objects at depth 0 only (one level deep)\n if (type === 'object' && depth === 0) {\n if (!isOnlyPrimitiveFields(value as Record<string, unknown>, depth + 1)) {\n return false;\n }\n continue;\n }\n\n return false;\n }\n\n return true;\n};\n","import {\n MAX_CUSTOM_EVENT_ARRAY_SIZE,\n MAX_CUSTOM_EVENT_KEYS,\n MAX_CUSTOM_EVENT_NAME_LENGTH,\n MAX_CUSTOM_EVENT_STRING_SIZE,\n MAX_STRING_LENGTH,\n MAX_STRING_LENGTH_IN_ARRAY,\n} from '../../constants';\nimport { MetadataType } from '../../types';\nimport { sanitizeMetadata } from '../security/sanitize.utils';\nimport { isOnlyPrimitiveFields } from './type-guards.utils';\n\n/**\n * Validates an event name\n * @param eventName - The event name to validate\n * @returns Validation result with error message if invalid\n */\nexport const isValidEventName = (eventName: string): { valid: boolean; error?: string } => {\n if (typeof eventName !== 'string') {\n return {\n valid: false,\n error: 'Event name must be a string',\n };\n }\n\n if (eventName.length === 0) {\n return {\n valid: false,\n error: 'Event name cannot be empty',\n };\n }\n\n if (eventName.length > MAX_CUSTOM_EVENT_NAME_LENGTH) {\n return {\n valid: false,\n error: `Event name is too long (max ${MAX_CUSTOM_EVENT_NAME_LENGTH} characters)`,\n };\n }\n\n if (eventName.includes('<') || eventName.includes('>') || eventName.includes('&')) {\n return {\n valid: false,\n error: 'Event name contains invalid characters',\n };\n }\n\n const reservedWords = ['constructor', 'prototype', '__proto__', 'eval', 'function', 'var', 'let', 'const'];\n\n if (reservedWords.includes(eventName.toLowerCase())) {\n return {\n valid: false,\n error: 'Event name cannot be a reserved word',\n };\n }\n\n return { valid: true };\n};\n\n/**\n * Validates a single metadata object\n * @param eventName - The event name (for error messages)\n * @param metadata - The metadata object to validate\n * @param type - Type of metadata (globalMetadata or customEvent)\n * @returns Validation result with sanitized metadata if valid\n */\nconst validateSingleMetadata = (\n eventName: string,\n metadata: Record<string, unknown>,\n type?: 'globalMetadata' | 'customEvent',\n): { valid: boolean; error?: string; sanitizedMetadata?: Record<string, MetadataType> } => {\n const sanitizedMetadata = sanitizeMetadata(metadata);\n const intro =\n type && type === 'customEvent' ? `${type} \"${eventName}\" metadata error` : `${eventName} metadata error`;\n\n if (!isOnlyPrimitiveFields(sanitizedMetadata)) {\n return {\n valid: false,\n error: `${intro}: object has invalid types. Valid types are string, number, boolean or string arrays.`,\n };\n }\n\n let jsonString: string;\n\n try {\n jsonString = JSON.stringify(sanitizedMetadata);\n } catch {\n return {\n valid: false,\n error: `${intro}: object contains circular references or cannot be serialized.`,\n };\n }\n\n if (jsonString.length > MAX_CUSTOM_EVENT_STRING_SIZE) {\n return {\n valid: false,\n error: `${intro}: object is too large (max ${MAX_CUSTOM_EVENT_STRING_SIZE / 1024} KB).`,\n };\n }\n\n const keyCount = Object.keys(sanitizedMetadata).length;\n\n if (keyCount > MAX_CUSTOM_EVENT_KEYS) {\n return {\n valid: false,\n error: `${intro}: object has too many keys (max ${MAX_CUSTOM_EVENT_KEYS} keys).`,\n };\n }\n\n for (const [key, value] of Object.entries(sanitizedMetadata)) {\n if (Array.isArray(value)) {\n if (value.length > MAX_CUSTOM_EVENT_ARRAY_SIZE) {\n return {\n valid: false,\n error: `${intro}: array property \"${key}\" is too large (max ${MAX_CUSTOM_EVENT_ARRAY_SIZE} items).`,\n };\n }\n\n for (const item of value) {\n if (typeof item === 'string' && item.length > MAX_STRING_LENGTH_IN_ARRAY) {\n return {\n valid: false,\n error: `${intro}: array property \"${key}\" contains strings that are too long (max ${MAX_STRING_LENGTH_IN_ARRAY} characters).`,\n };\n }\n }\n }\n\n if (typeof value === 'string' && value.length > MAX_STRING_LENGTH) {\n return {\n valid: false,\n error: `${intro}: property \"${key}\" is too long (max ${MAX_STRING_LENGTH} characters).`,\n };\n }\n }\n\n return {\n valid: true,\n sanitizedMetadata,\n };\n};\n\n/**\n * Validates metadata for events (supports both objects and arrays of objects)\n * @param eventName - The event name (for error messages)\n * @param metadata - The metadata to validate\n * @param type - Type of metadata (globalMetadata or customEvent)\n * @returns Validation result with sanitized metadata if valid\n */\nexport const isValidMetadata = (\n eventName: string,\n metadata: Record<string, unknown> | Record<string, unknown>[],\n type?: 'globalMetadata' | 'customEvent',\n): {\n valid: boolean;\n error?: string;\n sanitizedMetadata?: Record<string, MetadataType> | Record<string, MetadataType>[];\n} => {\n if (Array.isArray(metadata)) {\n const sanitizedArray: Record<string, MetadataType>[] = [];\n const intro =\n type && type === 'customEvent' ? `${type} \"${eventName}\" metadata error` : `${eventName} metadata error`;\n\n for (let i = 0; i < metadata.length; i++) {\n const item = metadata[i];\n\n if (typeof item !== 'object' || item === null || Array.isArray(item)) {\n return {\n valid: false,\n error: `${intro}: array item at index ${i} must be an object.`,\n };\n }\n\n const itemValidation = validateSingleMetadata(eventName, item, type);\n\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: `${intro}: array item at index ${i} is invalid: ${itemValidation.error}`,\n };\n }\n\n if (itemValidation.sanitizedMetadata) {\n sanitizedArray.push(itemValidation.sanitizedMetadata);\n }\n }\n\n return {\n valid: true,\n sanitizedMetadata: sanitizedArray,\n };\n }\n\n return validateSingleMetadata(eventName, metadata, type);\n};\n","import { MetadataType } from '../../types';\nimport { log } from '../logging.utils';\nimport { isValidEventName, isValidMetadata } from './metadata-validations.utils';\n\n/**\n * Validates a complete event with name and optional metadata\n * @param eventName - The event name to validate\n * @param metadata - Optional metadata to validate\n * @returns Validation result with sanitized metadata if valid\n */\nexport const isEventValid = (\n eventName: string,\n metadata?: Record<string, unknown> | Record<string, unknown>[],\n): {\n valid: boolean;\n error?: string;\n sanitizedMetadata?: Record<string, MetadataType> | Record<string, MetadataType>[];\n} => {\n const nameValidation = isValidEventName(eventName);\n\n if (!nameValidation.valid) {\n log('error', 'Event name validation failed', {\n data: { eventName, error: nameValidation.error },\n });\n\n return nameValidation;\n }\n\n if (!metadata) {\n return { valid: true };\n }\n\n const metadataValidation = isValidMetadata(eventName, metadata, 'customEvent');\n\n if (!metadataValidation.valid) {\n log('error', 'Event metadata validation failed', {\n data: {\n eventName,\n error: metadataValidation.error,\n },\n });\n }\n\n return metadataValidation;\n};\n","import { EmitterCallback, EmitterMap } from '../types';\n\n/**\n * Type-safe event emitter for TraceLog internal events\n *\n * **Purpose**: Provides pub/sub mechanism for internal library events with full TypeScript\n * type safety through `EmitterMap` interface.\n *\n * **Supported Events** (defined in `EmitterMap`):\n * - `event`: Individual events as they are tracked (EventData)\n * - `queue`: Complete event batches before transmission (EventsQueue)\n *\n * **Note**: Developers are responsible for implementing consent logic\n * before calling `init()` and filtering events as needed.\n *\n * **Key Features**:\n * - **Type Safety**: Callbacks receive correctly typed data based on event name\n * - **Memory Management**: Listeners stored in Map for efficient lookup and cleanup\n * - **Synchronous**: All callbacks execute immediately when event is emitted\n * - **No Error Isolation**: Errors in callbacks propagate to caller (by design)\n *\n * **Use Cases**:\n * - External event consumption via `tracelog.on('event', callback)`\n * - Integration testing via `window.__traceLogBridge.on('event', callback)`\n * - Custom analytics integrations\n * - Real-time event monitoring\n *\n * @example\n * ```typescript\n * const emitter = new Emitter();\n *\n * // Subscribe to events\n * const callback = (event: EventData) => {\n * console.log('Event tracked:', event.type);\n * };\n * emitter.on('event', callback);\n *\n * // Emit event (type-safe)\n * emitter.emit('event', {\n * id: '123',\n * type: EventType.CLICK,\n * page_url: 'https://example.com',\n * timestamp: Date.now(),\n * click_data: { x: 100, y: 200, tag: 'button' }\n * });\n *\n * // Unsubscribe\n * emitter.off('event', callback);\n *\n * // Clear all listeners (destroy/cleanup)\n * emitter.removeAllListeners();\n * ```\n *\n * @see EmitterMap for event type definitions\n * @see src/api.ts for public on/off API\n */\nexport class Emitter {\n private readonly listeners: Map<string, EmitterCallback[]> = new Map();\n\n /**\n * Subscribes to an event channel\n *\n * **Behavior**:\n * - Creates event channel if it doesn't exist\n * - Appends callback to list of listeners for this event\n * - Same callback can be registered multiple times (will fire multiple times)\n *\n * **Type Safety**: Callback receives data type matching the event name\n *\n * @param event - Event name to subscribe to\n * @param callback - Function to call when event is emitted\n *\n * @example\n * ```typescript\n * emitter.on('event', (eventData) => {\n * // eventData is typed as EventData\n * console.log(eventData.type);\n * });\n * ```\n */\n on<K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, []);\n }\n\n this.listeners.get(event)!.push(callback);\n }\n\n /**\n * Unsubscribes from an event channel\n *\n * **Behavior**:\n * - Removes first occurrence of callback from listener list\n * - If callback not found, no error is thrown\n * - If callback was registered multiple times, only one instance is removed\n *\n * **Important**: Must use same function reference passed to `on()`\n *\n * @param event - Event name to unsubscribe from\n * @param callback - Function reference to remove (must match `on()` reference)\n *\n * @example\n * ```typescript\n * const callback = (data) => console.log(data);\n * emitter.on('event', callback);\n * emitter.off('event', callback); // Unsubscribes successfully\n *\n * // BAD: Won't work (different function reference)\n * emitter.on('event', (data) => console.log(data));\n * emitter.off('event', (data) => console.log(data)); // No effect\n * ```\n */\n off<K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>): void {\n const callbacks = this.listeners.get(event);\n\n if (callbacks) {\n const index = callbacks.indexOf(callback);\n\n if (index > -1) {\n callbacks.splice(index, 1);\n }\n }\n }\n\n /**\n * Emits an event with data to all subscribed listeners\n *\n * **Behavior**:\n * - Calls all registered callbacks for this event synchronously\n * - Callbacks execute in registration order\n * - If no listeners, no-op (no error thrown)\n * - Errors in callbacks are NOT caught (propagate to caller)\n *\n * **Type Safety**: Data type must match event name's expected type\n *\n * @param event - Event name to emit\n * @param data - Event data (type must match EmitterMap[event])\n *\n * @example\n * ```typescript\n * // Emit event data\n * emitter.emit('event', eventData);\n *\n * // Emit queue data\n * emitter.emit('queue', {\n * user_id: 'user-123',\n * session_id: 'session-456',\n * device: DeviceType.Desktop,\n * events: [event1, event2]\n * });\n * ```\n */\n emit<K extends keyof EmitterMap>(event: K, data: EmitterMap[K]): void {\n const callbacks = this.listeners.get(event);\n\n if (callbacks) {\n callbacks.forEach((callback) => {\n callback(data);\n });\n }\n }\n\n /**\n * Removes all listeners for all events\n *\n * **Purpose**: Cleanup method called during `App.destroy()` to prevent memory leaks\n *\n * **Behavior**:\n * - Clears all event channels\n * - Listeners cannot be restored (new subscriptions required)\n * - Called automatically during library teardown\n *\n * **Use Cases**:\n * - Application teardown\n * - Component unmounting in SPA frameworks\n * - Test cleanup\n *\n * @example\n * ```typescript\n * // During destroy\n * emitter.removeAllListeners();\n * // All subscriptions cleared\n * ```\n */\n removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","import { EventData, EventsQueue, BeforeSendTransformer, BeforeBatchTransformer } from '../types';\nimport { log } from './logging.utils';\n\n/**\n * Applies beforeSend transformer to a single event with error handling\n *\n * @param event - Event to transform\n * @param transformer - Transformer function\n * @param context - Context string for logging (e.g., 'EventManager', 'custom')\n * @returns Transformed event or null if filtered\n */\nexport function transformEvent(\n event: EventData,\n transformer: BeforeSendTransformer,\n context: string,\n): EventData | null {\n try {\n const result = transformer(event);\n\n if (result === null) {\n return null; // Event filtered\n }\n\n if (typeof result === 'object' && result !== null && 'type' in result) {\n return result;\n }\n\n log('warn', `beforeSend transformer returned invalid data, using original [${context}]`);\n\n return event;\n } catch (error) {\n log('error', `beforeSend transformer threw error, using original event [${context}]`, {\n error,\n visibility: 'critical',\n });\n\n return event;\n }\n}\n\n/**\n * Applies beforeSend transformer to an array of events (optimized functional approach)\n *\n * @param events - Array of events to transform\n * @param transformer - Transformer function\n * @param context - Context string for logging\n * @returns Array of transformed events (filtered events removed)\n */\nexport function transformEvents(events: EventData[], transformer: BeforeSendTransformer, context: string): EventData[] {\n return events\n .map((event) => transformEvent(event, transformer, context))\n .filter((event): event is EventData => event !== null);\n}\n\n/**\n * Applies beforeBatch transformer to entire batch with error handling\n *\n * @param batch - Batch to transform\n * @param transformer - Transformer function\n * @param context - Context string for logging\n * @returns Transformed batch or null if filtered\n */\nexport function transformBatch(\n batch: EventsQueue,\n transformer: BeforeBatchTransformer,\n context: string,\n): EventsQueue | null {\n try {\n const result = transformer(batch);\n\n if (result === null) {\n log('debug', `Batch filtered by beforeBatch transformer [${context}]`, {\n data: { eventCount: batch.events.length },\n });\n\n return null;\n }\n\n if (typeof result === 'object' && result !== null && Array.isArray(result.events)) {\n return result;\n }\n\n log('warn', `beforeBatch transformer returned invalid data, using original [${context}]`, {\n data: { eventCount: batch.events.length },\n });\n\n return batch;\n } catch (error) {\n log('error', `beforeBatch transformer threw error, using original batch [${context}]`, {\n error,\n data: { eventCount: batch.events.length },\n visibility: 'critical',\n });\n\n return batch;\n }\n}\n","import { State } from '../types';\n\n/**\n * Global in-memory state store shared across all TraceLog components.\n * Single source of truth for runtime application state.\n */\nconst globalState: State = {} as State;\n\n/**\n * Returns an immutable snapshot of the global state.\n *\n * **Purpose**: Testing and debugging - provides read-only access to internal state.\n *\n * **Usage**:\n * - Test assertions: Verify state changes after operations\n * - Debugging: Inspect current application state\n * - Non-intrusive: Returns readonly reference (no mutations)\n *\n * @returns Readonly reference to global state\n *\n * @example\n * ```typescript\n * const state = getGlobalState();\n * console.log(state.userId); // Read-only access\n * ```\n *\n * @see src/managers/README.md (lines 198-201) for StateManager documentation\n */\nexport const getGlobalState = (): Readonly<State> => {\n return globalState;\n};\n\n/**\n * Clears all properties from the global state.\n *\n * **Purpose**: Test isolation - ensures clean state between test runs.\n *\n * **Warning**: Should only be called in test environments.\n * Calling in production will break the application.\n *\n * **Usage**:\n * - afterEach/beforeEach hooks in test suites\n * - Cleanup after integration tests\n * - Resetting application state for fresh initialization\n *\n * @example\n * ```typescript\n * afterEach(() => {\n * resetGlobalState(); // Clean slate for next test\n * });\n * ```\n */\nexport const resetGlobalState = (): void => {\n Object.keys(globalState).forEach((key) => {\n delete globalState[key as keyof State];\n });\n};\n\n/**\n * Abstract base class providing centralized, type-safe state management.\n *\n * **Purpose**: Foundation for all TraceLog managers and handlers, providing\n * synchronized access to shared application state.\n *\n * **Architecture**:\n * - All managers/handlers extend this class\n * - Single global state instance shared across all subclasses\n * - Type-safe operations via generic methods\n * - In-memory only (no automatic persistence)\n *\n * **Supported State Properties**:\n * - **Core State**: `collectApiUrls`, `config`, `sessionId`, `userId`, `device`, `pageUrl`\n * - **Control Flags**: `mode` (QA/production), `hasStartSession`, `suppressNextScroll`\n * - **Runtime Counters**: `scrollEventCount` (optional)\n *\n * **Implementation Details**:\n * - Synchronous operations (no async overhead)\n * - Memory-efficient (minimal object creation)\n * - No built-in logging (consumers handle their own state logging)\n * - Read-only snapshots via `getState()` prevent accidental mutations\n *\n * @see src/managers/README.md (lines 170-201) for detailed documentation\n *\n * @example\n * ```typescript\n * class MyManager extends StateManager {\n * initialize() {\n * const userId = this.get('userId'); // Read state\n * this.set('mode', 'qa'); // Write state\n * const snapshot = this.getState(); // Readonly copy\n * }\n * }\n * ```\n */\nexport abstract class StateManager {\n /**\n * Retrieves a value from global state.\n *\n * Type-safe getter with compile-time key validation.\n *\n * @template T - State key type (compile-time validated)\n * @param key - State property key\n * @returns Current value for the given key (may be undefined)\n *\n * @example\n * ```typescript\n * const userId = this.get('userId');\n * const config = this.get('config');\n * const sessionId = this.get('sessionId');\n * ```\n */\n protected get<T extends keyof State>(key: T): State[T] {\n return globalState[key];\n }\n\n /**\n * Sets a value in global state.\n *\n * Type-safe setter with compile-time type checking.\n * Changes are immediately visible to all StateManager subclasses.\n *\n * @template T - State key type (compile-time validated)\n * @param key - State property key\n * @param value - New value (type must match State[T])\n *\n * @example\n * ```typescript\n * this.set('sessionId', 'session-123');\n * this.set('mode', Mode.QA);\n * this.set('hasStartSession', true);\n * ```\n */\n protected set<T extends keyof State>(key: T, value: State[T]): void {\n globalState[key] = value;\n }\n\n /**\n * Returns an immutable snapshot of the entire global state.\n *\n * Creates a shallow copy to prevent accidental mutations.\n * Use for debugging or when multiple state properties are needed.\n *\n * @returns Readonly shallow copy of global state\n *\n * @example\n * ```typescript\n * const snapshot = this.getState();\n * console.log(snapshot.userId, snapshot.sessionId);\n * ```\n */\n protected getState(): Readonly<State> {\n return { ...globalState };\n }\n}\n","import {\n QUEUE_KEY,\n EVENT_EXPIRY_HOURS,\n REQUEST_TIMEOUT_MS,\n PERMANENT_ERROR_LOG_THROTTLE_MS,\n MAX_BEACON_PAYLOAD_SIZE,\n PERSISTENCE_THROTTLE_MS,\n MAX_SEND_RETRIES,\n RETRY_BACKOFF_BASE_MS,\n RETRY_BACKOFF_JITTER_MS,\n LIB_VERSION,\n} from '../constants';\nimport { PersistedEventsQueue, EventsQueue, SpecialApiUrl, PermanentError, TransformerMap } from '../types';\nimport { log, transformEvents, transformBatch } from '../utils';\nimport { StorageManager } from './storage.manager';\nimport { StateManager } from './state.manager';\n\ninterface SendCallbacks {\n onSuccess?: (eventCount?: number, events?: any[], body?: EventsQueue) => void;\n onFailure?: () => void;\n}\n\n/**\n * Manages sending event queues to configured API endpoints with persistence,\n * recovery, and multi-integration support.\n *\n * **Purpose**: Handles reliable event transmission to backend APIs with automatic\n * persistence for crash recovery and support for multiple integration backends.\n *\n * **Core Functionality**:\n * - **Event Transmission**: Sends event batches via `fetch()` (async) or `sendBeacon()` (sync)\n * - **Automatic Retries**: Up to 2 in-session retry attempts for transient failures (5xx, timeout)\n * - **Persistence & Recovery**: Stores failed events in localStorage, recovers on next page load\n * - **Multi-Integration**: Separate queues for SaaS (`tracelog`) and Custom backends\n * - **Multi-Tab Protection**: 1-second window prevents duplicate sends across tabs\n * - **Event Expiry**: Discards events older than 2 hours (prevents stale data accumulation)\n * - **Transformer Support**: Applies `beforeBatch` transformer before network transmission\n *\n * **Key Features**:\n * - **In-Session Retries**: Up to 2 retry attempts for 5xx/timeout with exponential backoff\n * - **Recovery on Next Page**: Failed events persisted to localStorage, recovered on init\n * - **Payload Size Validation**: 64KB limit for `sendBeacon()` to prevent truncation\n * - **Permanent Error Detection**: 4xx status codes (except 408, 429) marked as permanent\n * - **Independent Multi-Integration**: Separate SenderManager instances for each backend\n *\n * **Error Handling**:\n * - **4xx Errors** (permanent): Logged once per minute (throttled), events discarded, no retries\n * - **5xx Errors** (transient): Retried up to 2 times with exponential backoff, then persisted\n * - **Network Errors** (transient): Retried up to 2 times with exponential backoff, then persisted\n * - **Timeout** (transient): Retried up to 2 times with exponential backoff, then persisted\n *\n * **Storage Keys**:\n * - **Standalone**: `tlog:{userId}:queue` (single queue)\n * - **SaaS Integration**: `tlog:{userId}:queue:saas`\n * - **Custom Integration**: `tlog:{userId}:queue:custom`\n *\n * **Multi-Tab Protection**:\n * - Persisted events include `lastPersistTime` timestamp\n * - Recovery skips events persisted within last 1 second (active tab may retry)\n *\n * @see src/managers/README.md (lines 82-139) for detailed documentation\n *\n * @example\n * ```typescript\n * // Standalone mode (no backend)\n * const sender = new SenderManager(storage);\n * const success = await sender.send(eventsQueue); // No-op, returns true\n *\n * // SaaS integration\n * const saasSender = new SenderManager(storage, 'saas', 'https://api.tracelog.io/collect', {});\n * const success = await saasSender.send(eventsQueue);\n *\n * // Custom backend\n * const customSender = new SenderManager(storage, 'custom', 'https://myapi.com/events', {});\n * const success = await customSender.send(eventsQueue);\n *\n * // Synchronous send (page unload)\n * const success = sender.sendQueueSync(eventsQueue); // Uses sendBeacon()\n * ```\n */\nexport class SenderManager extends StateManager {\n private readonly storeManager: StorageManager;\n private readonly integrationId?: 'saas' | 'custom';\n private readonly apiUrl?: string;\n private readonly transformers: TransformerMap;\n private lastPermanentErrorLog: { statusCode?: number; timestamp: number } | null = null;\n private recoveryInProgress = false;\n private lastMetadataTimestamp = 0;\n private readonly pendingControllers = new Set<AbortController>();\n\n /**\n * Creates a SenderManager instance.\n *\n * **Validation**: `integrationId` and `apiUrl` must both be provided or both be undefined.\n * Throws error if only one is provided.\n *\n * @param storeManager - Storage manager for event persistence\n * @param integrationId - Optional integration identifier ('saas' or 'custom')\n * @param apiUrl - Optional API endpoint URL\n * @param transformers - Optional event transformation hooks\n * @throws Error if integrationId and apiUrl are not both provided or both undefined\n */\n constructor(\n storeManager: StorageManager,\n integrationId?: 'saas' | 'custom',\n apiUrl?: string,\n transformers: TransformerMap = {},\n ) {\n super();\n\n if ((integrationId && !apiUrl) || (!integrationId && apiUrl)) {\n throw new Error('SenderManager: integrationId and apiUrl must either both be provided or both be undefined');\n }\n\n this.storeManager = storeManager;\n this.integrationId = integrationId;\n this.apiUrl = apiUrl;\n this.transformers = transformers;\n }\n\n /**\n * Get the integration ID for this sender\n * @returns The integration ID ('saas' or 'custom') or undefined if not set\n */\n public getIntegrationId(): 'saas' | 'custom' | undefined {\n return this.integrationId;\n }\n\n private getQueueStorageKey(): string {\n const userId = this.get('userId') || 'anonymous';\n const baseKey = QUEUE_KEY(userId);\n // Separate storage keys prevent cross-integration interference in multi-integration mode\n return this.integrationId ? `${baseKey}:${this.integrationId}` : baseKey;\n }\n\n /**\n * Sends events synchronously using `navigator.sendBeacon()`.\n *\n * **Purpose**: Guarantees event delivery before page unload even if network is slow.\n *\n * **Use Cases**:\n * - Page unload (`beforeunload`, `pagehide` events)\n * - Tab close scenarios\n * - Any case where async send might be interrupted\n *\n * **Behavior**:\n * - Uses `navigator.sendBeacon()` (browser-queued, synchronous API)\n * - Payload size limited to 64KB (enforced by browser)\n * - Browser guarantees delivery attempt (survives page close)\n * - NO persistence on failure (fire-and-forget)\n *\n * **Return Values**:\n * - `true`: Send succeeded OR skipped (standalone mode)\n * - `false`: Send failed (network error, browser rejected beacon)\n *\n * **Important**: No retry mechanism for failures. Events are NOT persisted.\n *\n * @param body - Event queue to send\n * @returns `true` if send succeeded or was skipped, `false` if failed\n *\n * @see sendEventsQueue for async send with persistence\n * @see src/managers/README.md (lines 82-139) for send details\n */\n sendEventsQueueSync(body: EventsQueue): boolean {\n if (this.shouldSkipSend()) {\n return true;\n }\n\n if (this.apiUrl?.includes(SpecialApiUrl.Fail)) {\n log(\n 'warn',\n `Fail mode: simulating network failure (sync)${this.integrationId ? ` [${this.integrationId}]` : ''}`,\n {\n data: { events: body.events.length },\n },\n );\n\n return false;\n }\n\n if (this.apiUrl?.includes(SpecialApiUrl.Localhost)) {\n log(\n 'debug',\n `Success mode: simulating successful send (sync)${this.integrationId ? ` [${this.integrationId}]` : ''}`,\n {\n data: { events: body.events.length },\n },\n );\n\n return true;\n }\n\n return this.sendQueueSyncInternal(body);\n }\n\n /**\n * Sends events asynchronously using `fetch()` API with automatic persistence on failure.\n *\n * **Purpose**: Reliable event transmission with localStorage fallback for failed sends.\n *\n * **Flow**:\n * 1. Calls internal `send()` method (applies transformers, consent checks)\n * 2. On success: Clears persisted events, invokes `onSuccess` callback\n * 3. On failure: Persists events to localStorage, invokes `onFailure` callback\n * 4. On permanent error (4xx): Clears persisted events (no retry)\n *\n * **Callbacks**:\n * - `onSuccess(eventCount, events, body)`: Called after successful transmission\n * - `onFailure()`: Called after failed transmission or permanent error\n *\n * **Error Handling**:\n * - **Permanent errors** (4xx except 408, 429): Events discarded, not persisted\n * - **Transient errors** (5xx, network, timeout): Events persisted for recovery\n *\n * **Important**: Events are NOT retried in-session. Persistence is for\n * recovery on next page load via `recoverPersistedEvents()`.\n *\n * @param body - Event queue to send\n * @param callbacks - Optional success/failure callbacks\n * @returns Promise resolving to `true` if send succeeded, `false` if failed\n *\n * @see recoverPersistedEvents for recovery flow\n * @see src/managers/README.md (lines 82-139) for send details\n */\n async sendEventsQueue(body: EventsQueue, callbacks?: SendCallbacks): Promise<boolean> {\n try {\n const success = await this.send(body);\n\n if (success) {\n this.clearPersistedEvents();\n callbacks?.onSuccess?.(body.events.length, body.events, body);\n } else {\n this.persistEvents(body);\n callbacks?.onFailure?.();\n }\n\n return success;\n } catch (error) {\n if (error instanceof PermanentError) {\n this.logPermanentError('Permanent error, not retrying', error);\n this.clearPersistedEvents();\n callbacks?.onFailure?.();\n return false;\n }\n\n this.persistEvents(body);\n callbacks?.onFailure?.();\n return false;\n }\n }\n\n /**\n * Recovers and attempts to resend events persisted from previous session.\n *\n * **Purpose**: Zero data loss guarantee - recovers events that failed to send\n * in previous session due to network errors or crashes.\n *\n * **Flow**:\n * 1. Checks if recovery already in progress (prevents duplicate attempts)\n * 2. Loads persisted events from localStorage\n * 3. Validates freshness (discards events older than 2 hours)\n * 4. Applies multi-tab protection (skips events persisted within 1 second)\n * 5. Attempts to resend via `send()` method\n * 6. On success: Clears persisted events, invokes `onSuccess` callback\n * 7. On failure: Keeps events in localStorage, invokes `onFailure` callback\n * 8. On permanent error (4xx): Clears persisted events (no further retry)\n *\n * **Multi-Tab Protection**:\n * - Events persisted within last 1 second are skipped (active tab may retry)\n * - Prevents duplicate sends when multiple tabs recover simultaneously\n *\n * **Event Expiry**:\n * - Events older than 2 hours are discarded (prevents stale data accumulation)\n * - Expiry check uses event timestamps, not persistence time\n *\n * **Callbacks**:\n * - `onSuccess(eventCount, events, body)`: Called after successful transmission\n * - `onFailure()`: Called on send failure or permanent error\n *\n * **Called by**: `EventManager.recoverPersistedEvents()` during `App.init()`\n *\n * **Important**: This method is idempotent and safe to call multiple times.\n * Recovery flag prevents concurrent attempts.\n *\n * @param callbacks - Optional success/failure callbacks\n *\n * @example\n * ```typescript\n * await senderManager.recoverPersistedEvents({\n * onSuccess: (count, events, body) => {\n * console.log(`Recovered ${count} events`);\n * },\n * onFailure: () => {\n * console.warn('Recovery failed, will retry on next init');\n * }\n * });\n * ```\n *\n * @see src/managers/README.md (lines 82-139) for recovery details\n */\n async recoverPersistedEvents(callbacks?: SendCallbacks): Promise<void> {\n if (this.recoveryInProgress) {\n log('debug', 'Recovery already in progress, skipping duplicate attempt');\n return;\n }\n\n this.recoveryInProgress = true;\n\n try {\n const persistedData = this.getPersistedData();\n\n if (!persistedData || !this.isDataRecent(persistedData) || persistedData.events.length === 0) {\n this.clearPersistedEvents();\n return;\n }\n\n const body = this.createRecoveryBody(persistedData);\n const success = await this.send(body);\n\n if (success) {\n this.clearPersistedEvents();\n callbacks?.onSuccess?.(persistedData.events.length, persistedData.events, body);\n } else {\n callbacks?.onFailure?.();\n }\n } catch (error) {\n if (error instanceof PermanentError) {\n this.logPermanentError('Permanent error during recovery, clearing persisted events', error);\n this.clearPersistedEvents();\n callbacks?.onFailure?.();\n return;\n }\n\n log('error', 'Failed to recover persisted events', { error });\n } finally {\n this.recoveryInProgress = false;\n }\n }\n\n /**\n * Cleanup method called during `App.destroy()`.\n *\n * **Purpose**: Reserved for future cleanup logic (currently no-op).\n *\n * **Note**: This method is intentionally empty. SenderManager has no\n * cleanup requirements (no timers, no event listeners, no active connections).\n * Persisted events are intentionally kept in localStorage for recovery.\n *\n * **Called by**: `EventManager.stop()` during application teardown\n */\n stop(): void {}\n\n /**\n * Applies beforeSend transformer to event array for custom backend integrations.\n *\n * **Purpose**: Per-event transformation in multi-integration mode for custom backends only.\n * Bypassed for TraceLog SaaS to maintain schema integrity.\n *\n * **Application Context**:\n * - Only applied in multi-integration mode (SaaS + Custom)\n * - EventManager applies beforeSend for standalone/custom-only modes\n * - This method handles the multi-integration scenario\n *\n * **Transformation Flow**:\n * 1. Skip for TraceLog SaaS integration (returns untransformed body)\n * 2. Check if beforeSend transformer exists\n * 3. Apply transformer to each event via transformEvents() utility\n * 4. Filter out events (empty array = filter entire batch)\n * 5. Return transformed queue or null\n *\n * **Error Handling**:\n * - transformEvents() utility catches and logs transformer errors\n * - Failed transformations fall back to original event\n * - Empty result array treated as filter signal (returns null)\n *\n * @param body - Event queue to transform\n * @returns Transformed queue with modified events, or null to filter entire batch\n */\n private applyBeforeSendTransformer(body: EventsQueue): EventsQueue | null {\n // TraceLog SaaS requires untransformed events to maintain schema integrity\n if (this.integrationId === 'saas') {\n return body;\n }\n\n const beforeSendTransformer = this.transformers.beforeSend;\n\n if (!beforeSendTransformer) {\n return body;\n }\n\n const transformedEvents = transformEvents(\n body.events,\n beforeSendTransformer,\n this.integrationId || 'SenderManager',\n );\n\n if (transformedEvents.length === 0) {\n return null;\n }\n\n return {\n ...body,\n events: transformedEvents,\n };\n }\n\n /**\n * Applies beforeBatch transformer to entire event queue for custom backend integrations.\n *\n * **Purpose**: Batch-level transformation before network transmission for custom backends only.\n * Bypassed for TraceLog SaaS to maintain schema integrity.\n *\n * **Application Context**:\n * - Applied in both sync (`sendQueueSyncInternal`) and async (`send`) methods\n * - Operates on entire queue after beforeSend transformations\n * - Final transformation step before network transmission\n *\n * **Transformation Flow**:\n * 1. Skip for TraceLog SaaS integration (returns untransformed body)\n * 2. Check if beforeBatch transformer exists\n * 3. Apply transformer to entire queue via transformBatch() utility\n * 4. Return transformed queue or null to filter\n *\n * **Use Cases**:\n * - Add batch-level metadata (timestamps, signatures)\n * - Compress or encrypt entire payload\n * - Apply custom formatting to queue structure\n * - Filter entire batch based on conditions\n *\n * **Error Handling**:\n * - transformBatch() utility catches and logs transformer errors\n * - Failed transformations fall back to original batch\n * - Returning null filters entire batch (prevents send)\n *\n * @param body - Event queue to transform\n * @returns Transformed queue, or null to filter entire batch\n */\n private applyBeforeBatchTransformer(body: EventsQueue): EventsQueue | null {\n // TraceLog SaaS requires untransformed batches to maintain schema integrity\n if (this.integrationId === 'saas') {\n return body;\n }\n\n const beforeBatchTransformer = this.transformers.beforeBatch;\n\n if (!beforeBatchTransformer) {\n return body;\n }\n\n const transformed = transformBatch(body, beforeBatchTransformer, this.integrationId || 'SenderManager');\n\n return transformed;\n }\n\n /**\n * Calculates exponential backoff delay with jitter for retry attempts.\n *\n * **Purpose**: Prevents thundering herd problem when multiple clients retry simultaneously.\n *\n * **Formula**: `RETRY_BACKOFF_BASE_MS * (2 ^ attempt) + random(0, RETRY_BACKOFF_JITTER_MS)`\n *\n * **Examples**:\n * - Attempt 1: 100ms * 2^1 + jitter = 200ms + 0-100ms = 200-300ms\n * - Attempt 2: 100ms * 2^2 + jitter = 400ms + 0-100ms = 400-500ms\n *\n * **Why Jitter?**\n * - Distributes retry timing across clients\n * - Reduces server load spikes from synchronized retries\n * - Industry standard pattern (AWS, Google, Netflix use similar approaches)\n *\n * @param attempt - Current retry attempt number (1-based)\n * @returns Promise that resolves after calculated delay\n */\n private async backoffDelay(attempt: number): Promise<void> {\n const exponentialDelay = RETRY_BACKOFF_BASE_MS * Math.pow(2, attempt);\n const jitter = Math.random() * RETRY_BACKOFF_JITTER_MS;\n const totalDelay = exponentialDelay + jitter;\n\n return new Promise((resolve) => setTimeout(resolve, totalDelay));\n }\n\n /**\n * Sends event queue with automatic retry logic for transient failures.\n *\n * **Purpose**: Reliable event transmission with intelligent retry mechanism\n * for transient network/server errors while avoiding unnecessary retries for\n * permanent client errors.\n *\n * **Retry Strategy**:\n * - **Maximum Attempts**: Up to `MAX_SEND_RETRIES` (2) retry attempts\n * - **Backoff**: Exponential backoff with jitter (200-300ms, 400-500ms)\n * - **Transient Errors**: 5xx status codes, network failures, timeouts\n * - **Permanent Errors**: 4xx status codes (except 408, 429) - no retries\n *\n * **Retry Flow**:\n * 1. Attempt send with `sendWithTimeout()`\n * 2. If success (2xx) → return true immediately\n * 3. If permanent error (4xx) → throw PermanentError immediately\n * 4. If transient error (5xx/timeout/network):\n * - If attempts remaining → wait backoff delay → retry\n * - If no attempts remaining → return false (caller persists)\n *\n * **Important Behaviors**:\n * - Transformers applied once before retry loop (not re-applied per attempt)\n * - Each retry uses same transformed payload\n * - Permanent errors bypass retries immediately\n *\n * **Error Classification**:\n * - **Permanent** (4xx except 408, 429): Schema errors, auth failures, invalid data\n * - **Transient** (5xx, timeout, network): Server overload, network hiccups, DNS issues\n *\n * @param body - Event queue to send\n * @returns Promise resolving to true if send succeeded, false if all retries exhausted\n * @throws PermanentError for 4xx errors (caller should not retry)\n */\n private async send(body: EventsQueue): Promise<boolean> {\n if (this.shouldSkipSend()) {\n return this.simulateSuccessfulSend();\n }\n\n const afterBeforeSend = this.applyBeforeSendTransformer(body);\n\n if (!afterBeforeSend) {\n return true;\n }\n\n const transformedBody = this.applyBeforeBatchTransformer(afterBeforeSend);\n\n if (!transformedBody) {\n return true;\n }\n\n if (this.apiUrl?.includes(SpecialApiUrl.Fail)) {\n log('debug', `Fail mode: simulating network failure${this.integrationId ? ` [${this.integrationId}]` : ''}`, {\n data: { events: transformedBody.events.length },\n });\n\n return false;\n }\n\n if (this.apiUrl?.includes(SpecialApiUrl.Localhost)) {\n log('debug', `Success mode: simulating successful send${this.integrationId ? ` [${this.integrationId}]` : ''}`, {\n data: { events: transformedBody.events.length },\n });\n\n return true;\n }\n\n const { url, payload } = this.prepareRequest(transformedBody);\n\n // Retry loop: initial attempt + MAX_SEND_RETRIES additional attempts\n for (let attempt = 1; attempt <= MAX_SEND_RETRIES + 1; attempt++) {\n try {\n const response = await this.sendWithTimeout(url, payload);\n\n if (response.ok) {\n if (attempt > 1) {\n log(\n 'info',\n `Send succeeded after ${attempt - 1} retry attempt(s)${this.integrationId ? ` [${this.integrationId}]` : ''}`,\n {\n data: { events: transformedBody.events.length, attempt },\n },\n );\n }\n\n return true;\n }\n\n // Response received but not OK - should be handled by sendWithTimeout throwing error\n // This branch should not be reached, but included for defensive programming\n return false;\n } catch (error) {\n const isLastAttempt = attempt === MAX_SEND_RETRIES + 1;\n\n // Permanent errors bypass retries immediately\n if (error instanceof PermanentError) {\n throw error;\n }\n\n // Log error with retry context\n log(\n isLastAttempt ? 'error' : 'warn',\n `Send attempt ${attempt} failed${this.integrationId ? ` [${this.integrationId}]` : ''}${isLastAttempt ? ' (all retries exhausted)' : ', will retry'}`,\n {\n error,\n data: {\n events: body.events.length,\n url: url.replace(/\\/\\/[^/]+/, '//[DOMAIN]'),\n attempt,\n maxAttempts: MAX_SEND_RETRIES + 1,\n },\n },\n );\n\n // If not last attempt, wait backoff delay and retry\n if (!isLastAttempt) {\n await this.backoffDelay(attempt);\n continue;\n }\n\n // All retries exhausted\n return false;\n }\n }\n\n // Should never reach here due to loop structure, but included for type safety\n return false;\n }\n\n /**\n * Sends HTTP POST request with 10-second timeout and AbortController.\n *\n * **Purpose**: Wraps fetch() with timeout protection to prevent hanging requests.\n * Throws PermanentError for 4xx status codes (except 408, 429) to bypass retries.\n *\n * **Timeout Behavior**:\n * - 10-second timeout via AbortController (REQUEST_TIMEOUT_MS constant)\n * - Aborted requests throw network error (triggers retry in caller)\n *\n * **Error Classification**:\n * - 4xx (except 408, 429): PermanentError thrown → no retries\n * - 408, 429, 5xx, network: Standard Error thrown → triggers retry\n *\n * @param url - API endpoint URL\n * @param payload - JSON-stringified EventsQueue body\n * @returns Response object if successful\n * @throws PermanentError for unrecoverable 4xx errors\n * @throws Error for transient errors (5xx, timeout, network)\n * @private\n */\n private async sendWithTimeout(url: string, payload: string): Promise<Response> {\n const controller = new AbortController();\n this.pendingControllers.add(controller);\n\n const timeoutId = setTimeout(() => {\n controller.abort();\n }, REQUEST_TIMEOUT_MS);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n body: payload,\n keepalive: true,\n credentials: 'include',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n // 4xx errors are permanent (unrecoverable) except:\n // - 408 Request Timeout (transient - network/server issue)\n // - 429 Too Many Requests (transient - rate limiting)\n const isPermanentError =\n response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429;\n\n if (isPermanentError) {\n throw new PermanentError(`HTTP ${response.status}: ${response.statusText}`, response.status);\n }\n\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n return response;\n } finally {\n clearTimeout(timeoutId);\n this.pendingControllers.delete(controller);\n }\n }\n\n /**\n * Internal synchronous send logic using navigator.sendBeacon() for page unload scenarios.\n *\n * **Purpose**: Sends events synchronously during page unload when async fetch() is unreliable.\n * Uses sendBeacon() browser API which queues request even after page closes.\n *\n * **Flow**:\n * 1. Apply beforeSend transformer (per-event transformation)\n * 2. Apply beforeBatch transformer (batch-level transformation)\n * 3. Validate payload size (64KB browser limit for sendBeacon)\n * 4. Send via sendBeacon() or fallback to persistence if unavailable\n * 5. Persist events on failure for next-page-load recovery\n *\n * **Payload Size Limit**: 64KB enforced by browser for sendBeacon()\n * - Oversized payloads persisted instead of silently failing\n *\n * @param body - EventsQueue to send\n * @returns `true` on success or when events persisted for recovery, `false` on failure\n * @private\n */\n private sendQueueSyncInternal(body: EventsQueue): boolean {\n const afterBeforeSend = this.applyBeforeSendTransformer(body);\n\n if (!afterBeforeSend) {\n return true;\n }\n\n const transformedBody = this.applyBeforeBatchTransformer(afterBeforeSend);\n\n if (!transformedBody) {\n return true;\n }\n\n const { url, payload } = this.prepareRequest(transformedBody);\n\n if (payload.length > MAX_BEACON_PAYLOAD_SIZE) {\n log(\n 'warn',\n `Payload exceeds sendBeacon limit, persisting for recovery${this.integrationId ? ` [${this.integrationId}]` : ''}`,\n {\n data: {\n size: payload.length,\n limit: MAX_BEACON_PAYLOAD_SIZE,\n events: transformedBody.events.length,\n },\n },\n );\n\n this.persistEvents(transformedBody);\n\n return false;\n }\n\n const blob = new Blob([payload], { type: 'application/json' });\n\n if (!this.isSendBeaconAvailable()) {\n log(\n 'warn',\n `sendBeacon not available, persisting events for recovery${this.integrationId ? ` [${this.integrationId}]` : ''}`,\n );\n\n this.persistEvents(transformedBody);\n return false;\n }\n\n const accepted = navigator.sendBeacon(url, blob);\n\n if (!accepted) {\n log(\n 'warn',\n `sendBeacon rejected request, persisting events for recovery${this.integrationId ? ` [${this.integrationId}]` : ''}`,\n );\n\n this.persistEvents(transformedBody);\n }\n\n return accepted;\n }\n\n /**\n * Prepares request by enriching payload with metadata and serializing to JSON.\n *\n * **Purpose**: Adds request metadata (referer, timestamp) before transmission.\n *\n * **Metadata Enrichment**:\n * - `referer`: Current page URL (browser only, undefined in Node.js)\n * - `timestamp`: Request generation time in milliseconds\n *\n * **Idempotency Token**:\n * - Generated in this method using generateEventId()\n * - Same token persists across all retry attempts of the same batch (same payload string)\n * - Backend can use this to distinguish retries from genuine duplicates\n *\n * @param body - EventsQueue to send\n * @returns Object with `url` (API endpoint) and `payload` (JSON string)\n * @private\n */\n private prepareRequest(body: EventsQueue): { url: string; payload: string } {\n let timestamp = Date.now();\n\n // Protect against clock skew (same pattern as generateEventId)\n // Ensures _metadata.timestamp is monotonically increasing\n if (timestamp < this.lastMetadataTimestamp) {\n timestamp = this.lastMetadataTimestamp;\n }\n this.lastMetadataTimestamp = timestamp;\n\n const enrichedBody = {\n ...body,\n _metadata: {\n referer: typeof window !== 'undefined' ? window.location.href : undefined,\n timestamp,\n client_version: LIB_VERSION,\n },\n };\n\n return {\n url: this.apiUrl || '',\n payload: JSON.stringify(enrichedBody),\n };\n }\n\n /**\n * Retrieves persisted events from localStorage with error recovery.\n *\n * **Purpose**: Loads previously failed events from storage for recovery attempt.\n *\n * **Error Handling**:\n * - JSON parse failures logged and storage cleared (corrupted data)\n * - Missing data returns null (no recovery needed)\n *\n * @returns Persisted events object or null if none exist/invalid\n * @private\n */\n private getPersistedData(): PersistedEventsQueue | null {\n try {\n const storageKey = this.getQueueStorageKey();\n const persistedDataString = this.storeManager.getItem(storageKey);\n\n if (persistedDataString) {\n return JSON.parse(persistedDataString);\n }\n } catch (error) {\n log('debug', `Failed to parse persisted data${this.integrationId ? ` [${this.integrationId}]` : ''}`, { error });\n this.clearPersistedEvents();\n }\n\n return null;\n }\n\n /**\n * Checks if persisted events are within the 2-hour expiry window.\n *\n * **Purpose**: Prevents recovery of stale events that are too old to be relevant.\n *\n * **Expiry Logic**:\n * - Events older than 2 hours (EVENT_EXPIRY_HOURS) are considered expired\n * - Invalid/missing timestamps treated as expired\n *\n * @param data - Persisted events object with timestamp\n * @returns `true` if events are recent (< 2 hours old), `false` otherwise\n * @private\n */\n private isDataRecent(data: PersistedEventsQueue): boolean {\n if (!data.timestamp || typeof data.timestamp !== 'number') {\n return false;\n }\n\n const ageInHours = (Date.now() - data.timestamp) / (1000 * 60 * 60);\n return ageInHours < EVENT_EXPIRY_HOURS;\n }\n\n /**\n * Creates EventsQueue from persisted data by removing storage-specific timestamp field.\n *\n * **Purpose**: Converts PersistedEventsQueue (with timestamp) to EventsQueue for sending.\n *\n * @param data - Persisted events with timestamp\n * @returns EventsQueue ready for transmission (timestamp removed)\n * @private\n */\n private createRecoveryBody(data: PersistedEventsQueue): EventsQueue {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { timestamp, ...queue } = data;\n return queue;\n }\n\n /**\n * Persists failed events to localStorage for next-page-load recovery.\n *\n * **Purpose**: Saves events that couldn't be sent due to network/server errors.\n * Implements multi-tab protection to prevent data loss during simultaneous failures.\n *\n * **Multi-Tab Protection**:\n * - Throttles persistence (1-second window via PERSISTENCE_THROTTLE_MS)\n * - If another tab persisted within 1 second, skips write (last-write-wins)\n * - Prevents redundant storage writes when multiple tabs fail together\n *\n * **Storage Format**: PersistedEventsQueue (EventsQueue + timestamp)\n *\n * @param body - EventsQueue to persist\n * @returns `true` on successful persistence or throttled write, `false` on error\n * @private\n */\n private persistEvents(body: EventsQueue): boolean {\n try {\n const existing = this.getPersistedData();\n\n if (existing && existing.timestamp) {\n const timeSinceExisting = Date.now() - existing.timestamp;\n\n if (timeSinceExisting < PERSISTENCE_THROTTLE_MS) {\n log(\n 'debug',\n `Skipping persistence, another tab recently persisted events${this.integrationId ? ` [${this.integrationId}]` : ''}`,\n {\n data: { timeSinceExisting },\n },\n );\n\n return true;\n }\n }\n\n const persistedData: PersistedEventsQueue = {\n ...body,\n timestamp: Date.now(),\n };\n\n const storageKey = this.getQueueStorageKey();\n\n this.storeManager.setItem(storageKey, JSON.stringify(persistedData));\n\n return !!this.storeManager.getItem(storageKey);\n } catch (error) {\n log('debug', `Failed to persist events${this.integrationId ? ` [${this.integrationId}]` : ''}`, { error });\n return false;\n }\n }\n\n private clearPersistedEvents(): void {\n try {\n const key = this.getQueueStorageKey();\n this.storeManager.removeItem(key);\n } catch (error) {\n log('debug', `Failed to clear persisted events${this.integrationId ? ` [${this.integrationId}]` : ''}`, {\n error,\n });\n }\n }\n\n private shouldSkipSend(): boolean {\n return !this.apiUrl;\n }\n\n private async simulateSuccessfulSend(): Promise<boolean> {\n const delay = Math.random() * 400 + 100;\n\n await new Promise((resolve) => setTimeout(resolve, delay));\n\n return true;\n }\n\n private isSendBeaconAvailable(): boolean {\n return typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function';\n }\n\n private logPermanentError(context: string, error: PermanentError): void {\n const now = Date.now();\n const shouldLog =\n !this.lastPermanentErrorLog ||\n this.lastPermanentErrorLog.statusCode !== error.statusCode ||\n now - this.lastPermanentErrorLog.timestamp >= PERMANENT_ERROR_LOG_THROTTLE_MS;\n\n if (shouldLog) {\n log('error', `${context}${this.integrationId ? ` [${this.integrationId}]` : ''}`, {\n data: { status: error.statusCode, message: error.message },\n });\n\n this.lastPermanentErrorLog = { statusCode: error.statusCode, timestamp: now };\n }\n }\n}\n","import { log } from '../utils';\nimport { StateManager } from './state.manager';\n\n/**\n * Manages accurate timestamp generation using monotonic clock (performance.now())\n * to prevent issues from system clock changes during the session.\n *\n * **Purpose**: Provides reliable timestamps immune to system clock adjustments\n * that could cause future timestamp rejections or incorrect event ordering.\n *\n * **Core Functionality**:\n * - **Boot Time Reference**: Captures `performance.now()` and `Date.now()` at initialization\n * - **Monotonic Clock**: Uses `performance.now()` for elapsed time (immune to clock changes)\n * - **Timestamp Generation**: `bootTimestamp + (performance.now() - bootTime)`\n * - **Graceful Degradation**: Falls back to `Date.now()` if `performance.now()` unavailable\n *\n * **Key Features**:\n * - **Clock Skew Protection**: Timestamps stay accurate even if system clock changes during session\n * - **No Server Dependency**: Works in standalone mode without backend communication\n * - **High Precision**: Uses performance.now() with microsecond precision\n * - **Detect Clock Skew**: Warns when significant clock drift detected (>30 seconds)\n *\n * **Use Cases**:\n * - Generate event timestamps that won't be rejected for being in the future\n * - Maintain correct event ordering even when system clock jumps\n * - Detect and warn about clock synchronization issues\n *\n * **Limitations**:\n * - Only protects against clock changes DURING the session\n * - If clock is wrong at boot time, timestamps will reflect that initial offset\n * - Server-side validation should still allow some tolerance (e.g., 3 minutes)\n *\n * @example\n * ```typescript\n * const timeManager = new TimeManager();\n *\n * // Get accurate timestamp (immune to clock changes)\n * const timestamp = timeManager.now(); // milliseconds since epoch\n *\n * // Check if clock skew detected\n * const skew = timeManager.getClockSkew(); // milliseconds of drift\n * if (Math.abs(skew) > 30000) {\n * console.warn('System clock drifted by', skew, 'ms');\n * }\n * ```\n */\nexport class TimeManager extends StateManager {\n private readonly bootTime: number;\n private readonly bootTimestamp: number;\n private readonly hasPerformanceNow: boolean;\n private lastClockSkewCheck = 0;\n private detectedSkew = 0;\n\n /**\n * Creates a TimeManager instance and establishes boot time reference.\n *\n * **Initialization**:\n * 1. Captures `performance.now()` as boot time (monotonic clock)\n * 2. Captures `Date.now()` as boot timestamp (wall clock)\n * 3. Detects if `performance.now()` is available (for fallback)\n *\n * **Boot Time**: Reference point for all subsequent timestamp calculations\n * - All timestamps are relative to this boot time\n * - Immune to system clock changes after initialization\n *\n * **SSR Safety**: In non-browser environments (Node.js, SSR), falls back to Date.now()\n */\n constructor() {\n super();\n\n // SSR safety: TimeManager is no-op in non-browser environments\n if (typeof window === 'undefined') {\n this.hasPerformanceNow = false;\n this.bootTime = 0;\n this.bootTimestamp = 0;\n return;\n }\n\n this.hasPerformanceNow = typeof performance !== 'undefined' && typeof performance.now === 'function';\n\n if (this.hasPerformanceNow) {\n this.bootTime = performance.now();\n this.bootTimestamp = Date.now();\n\n log('debug', 'TimeManager initialized with monotonic clock', {\n data: {\n bootTime: this.bootTime.toFixed(3),\n bootTimestamp: this.bootTimestamp,\n },\n });\n } else {\n // Fallback for environments without performance.now()\n this.bootTime = 0;\n this.bootTimestamp = Date.now();\n\n log('debug', 'performance.now() not available, falling back to Date.now()');\n }\n }\n\n /**\n * Returns current timestamp in milliseconds since epoch.\n *\n * **Calculation**:\n * - If `performance.now()` available: `bootTimestamp + (performance.now() - bootTime)`\n * - Otherwise: `Date.now()` (fallback)\n *\n * **Advantages over Date.now()**:\n * - Immune to system clock changes during session\n * - More accurate (microsecond precision)\n * - Prevents future timestamp errors from clock adjustments\n *\n * @returns Timestamp in milliseconds since Unix epoch\n *\n * @example\n * ```typescript\n * const eventTimestamp = timeManager.now();\n * // Always accurate relative to boot time, even if system clock changes\n * ```\n */\n now(): number {\n if (!this.hasPerformanceNow) {\n return Date.now();\n }\n\n const elapsed = performance.now() - this.bootTime;\n return Math.round(this.bootTimestamp + elapsed);\n }\n\n /**\n * Detects clock skew by comparing monotonic time vs system time.\n *\n * **Purpose**: Identifies when the system clock has changed during the session.\n *\n * **Detection Method**:\n * 1. Calculate expected timestamp using monotonic clock: `now()`\n * 2. Compare with actual system time: `Date.now()`\n * 3. Difference is the clock skew\n *\n * **Clock Skew Scenarios**:\n * - Positive skew: System clock jumped forward (e.g., NTP correction)\n * - Negative skew: System clock jumped backward (rare, usually manual adjustment)\n * - Near zero: No significant clock drift\n *\n * **Throttling**: Only checks every 5 seconds to avoid performance impact\n *\n * @returns Clock skew in milliseconds (positive = clock ahead, negative = clock behind)\n *\n * @example\n * ```typescript\n * const skew = timeManager.getClockSkew();\n * if (Math.abs(skew) > 30000) {\n * console.warn(`System clock drifted by ${skew}ms`);\n * }\n * ```\n */\n getClockSkew(): number {\n if (!this.hasPerformanceNow) {\n return 0;\n }\n\n const now = Date.now();\n\n // Throttle skew checks to every 5 seconds\n if (now - this.lastClockSkewCheck < 5000) {\n return this.detectedSkew;\n }\n\n this.lastClockSkewCheck = now;\n\n const monotonicTimestamp = this.now();\n const systemTimestamp = Date.now();\n this.detectedSkew = systemTimestamp - monotonicTimestamp;\n\n if (Math.abs(this.detectedSkew) > 30000) {\n log('warn', 'Significant clock skew detected', {\n data: {\n skewMs: this.detectedSkew,\n skewMinutes: (this.detectedSkew / 1000 / 60).toFixed(2),\n monotonicTime: new Date(monotonicTimestamp).toISOString(),\n systemTime: new Date(systemTimestamp).toISOString(),\n },\n });\n }\n\n return this.detectedSkew;\n }\n\n /**\n * Validates if a timestamp is reasonable (not too far in the future).\n *\n * **Purpose**: Client-side validation to catch obviously wrong timestamps\n * before sending to backend.\n *\n * **Validation Rules**:\n * - Timestamp must not be >2 minutes in the future (relative to monotonic clock)\n * - Prevents backend rejections due to clock skew\n * - More lenient than backend (allows up to 2 min vs backend's 3 min)\n *\n * **Use Case**: Validate event timestamps before adding to queue\n *\n * @param timestamp - Timestamp to validate (milliseconds since epoch)\n * @returns Object with `valid` boolean and optional `error` message\n *\n * @example\n * ```typescript\n * const validation = timeManager.validateTimestamp(eventTimestamp);\n * if (!validation.valid) {\n * console.error('Invalid timestamp:', validation.error);\n * }\n * ```\n */\n validateTimestamp(timestamp: number): { valid: boolean; error?: string } {\n const maxFutureOffset = 2 * 60 * 1000; // 2 minutes\n const currentTime = this.now();\n const offset = timestamp - currentTime;\n\n if (offset > maxFutureOffset) {\n return {\n valid: false,\n error: `Timestamp is ${(offset / 1000 / 60).toFixed(2)} minutes in the future (max allowed: 2 minutes)`,\n };\n }\n\n return { valid: true };\n }\n\n /**\n * Returns boot time information for debugging.\n *\n * **Purpose**: Diagnostic utility for troubleshooting timestamp issues.\n *\n * @returns Object with boot time details\n */\n getBootInfo(): {\n bootTime: number;\n bootTimestamp: number;\n hasPerformanceNow: boolean;\n clockSkew: number;\n } {\n return {\n bootTime: this.bootTime,\n bootTimestamp: this.bootTimestamp,\n hasPerformanceNow: this.hasPerformanceNow,\n clockSkew: this.getClockSkew(),\n };\n }\n}\n","import {\n EVENT_SENT_INTERVAL_MS,\n MAX_EVENTS_QUEUE_LENGTH,\n DUPLICATE_EVENT_THRESHOLD_MS,\n RATE_LIMIT_WINDOW_MS,\n MAX_EVENTS_PER_SECOND,\n MAX_PENDING_EVENTS_BUFFER,\n BATCH_SIZE_THRESHOLD,\n MAX_SAME_EVENT_PER_MINUTE,\n PER_EVENT_RATE_LIMIT_WINDOW_MS,\n MAX_EVENTS_PER_SESSION,\n MAX_CLICKS_PER_SESSION,\n MAX_PAGE_VIEWS_PER_SESSION,\n MAX_CUSTOM_EVENTS_PER_SESSION,\n MAX_VIEWPORT_EVENTS_PER_SESSION,\n MAX_SCROLL_EVENTS_PER_SESSION,\n MAX_FINGERPRINTS,\n FINGERPRINT_CLEANUP_MULTIPLIER,\n MAX_FINGERPRINTS_HARD_LIMIT,\n} from '../constants/config.constants';\nimport {\n SESSION_COUNTS_KEY,\n SESSION_COUNTS_EXPIRY_MS,\n SESSION_COUNTS_LAST_CLEANUP_KEY,\n SESSION_COUNTS_CLEANUP_THROTTLE_MS,\n STORAGE_BASE_KEY,\n} from '../constants/storage.constants';\nimport { EventsQueue, EmitterEvent, EventData, EventType, Mode, TransformerMap, SessionEventCounts } from '../types';\nimport { log, Emitter, generateEventId, transformEvent, transformBatch } from '../utils';\nimport { SenderManager } from './sender.manager';\nimport { StateManager } from './state.manager';\nimport { StorageManager } from './storage.manager';\nimport { TimeManager } from './time.manager';\n\n/**\n * Extended session counts structure with metadata for expiry tracking.\n *\n * Adds timestamp and version fields to SessionEventCounts for cleanup management.\n */\ninterface StoredSessionCounts extends SessionEventCounts {\n /** Timestamp when counts were last saved (for 7-day expiry) */\n _timestamp: number;\n /** Schema version for future migrations */\n _version: number;\n}\n\n/**\n * Core component responsible for event tracking, queue management, deduplication,\n * rate limiting, and multi-integration API communication coordination.\n *\n * **Purpose**: Central hub for all analytics events in TraceLog, managing the complete\n * event lifecycle from capture to transmission.\n *\n * **Core Functionality**:\n * - **Event Tracking**: Captures all user interactions (clicks, scrolls, page views, custom events, web vitals, errors)\n * - **Queue Management**: Batches events with 10-second intervals to optimize network requests\n * - **Deduplication**: LRU cache with 1000-entry fingerprint storage prevents duplicate events\n * - **Rate Limiting**: Client-side limits (50 events/second global, 60/minute per event name)\n * - **Per-Session Caps**: Configurable limits prevent runaway generation (1000 total, type-specific limits)\n * - **Dynamic Queue Flush**: Immediate send when 50-event batch threshold reached\n * - **Pending Events Buffer**: Buffers up to 100 events before session initialization\n * - **Event Recovery**: Recovers persisted events from localStorage after crashes (independent per integration)\n * - **Multi-Integration**: Manages 0-2 SenderManager instances (SaaS + Custom) with parallel async sending\n * - **Standalone Mode**: Emits queue events without network requests when no integrations configured\n *\n * **Key Features**:\n * - **LRU Deduplication**: 1000 fingerprints, 10px coordinate precision for clicks, 500ms time threshold\n * - **Smart Queue Overflow**: Fixed 100-event limit with priority preservation for SESSION_START/END\n * - **Rate Limiting**: 50 events/sec sliding window, critical events exempted\n * - **Per-Event-Name Limits**: 60 same event name per minute (configurable via `maxSameEventPerMinute`)\n * - **Per-Session Caps**: Total 1000/session, Clicks 500, Page views 100, Custom 500, Viewport 200, Scroll 120\n * - **Dynamic Flush**: Immediate send when 50-event batch threshold reached\n * - **Multi-Integration Sending**: Parallel async with `Promise.allSettled()`, independent error handling per integration\n * - **QA Mode**: Console logging for custom events without backend transmission\n *\n * **State Management**:\n * - **`hasStartSession` Flag**: Prevents duplicate SESSION_START events across init cycles\n * - Set to `true` when SESSION_START is tracked via `track()` method\n * - Reset to `false` in `stop()` method to allow subsequent init() → destroy() → init() cycles\n * - NOT set by SessionManager's BroadcastChannel message handler (secondary tabs don't track SESSION_START)\n *\n * **Transformer Support**:\n * - **`beforeSend`**: Applied conditionally based on integration mode\n * - Custom-only mode: Applied in EventManager before dedup/sampling/queueing\n * - Multi-integration mode: Skipped in EventManager, applied in SenderManager per-integration\n * - **`beforeBatch`**: Applied in SenderManager before network transmission\n *\n * @see src/managers/README.md for detailed documentation\n *\n * @example\n * ```typescript\n * const eventManager = new EventManager(storage, emitter, transformers);\n *\n * // Track event\n * eventManager.track({\n * type: 'click',\n * click_data: { x: 100, y: 200, tag: 'button' }\n * });\n *\n * // Flush immediately (async)\n * await eventManager.flushImmediately();\n *\n * // Synchronous flush (page unload)\n * eventManager.flushImmediatelySync();\n *\n * // Stop and cleanup\n * eventManager.stop();\n * ```\n */\nexport class EventManager extends StateManager {\n private readonly dataSenders: SenderManager[];\n private readonly emitter: Emitter | null;\n private readonly transformers: TransformerMap;\n private readonly timeManager: TimeManager;\n private readonly recentEventFingerprints = new Map<string, number>();\n private readonly perEventRateLimits: Map<string, number[]> = new Map();\n\n private eventsQueue: EventData[] = [];\n private pendingEventsBuffer: Partial<EventData>[] = [];\n private sendIntervalId: number | null = null;\n private rateLimitCounter = 0;\n private rateLimitWindowStart = 0;\n private lastSessionId: string | null = null;\n\n private sessionEventCounts: SessionEventCounts = {\n total: 0,\n [EventType.CLICK]: 0,\n [EventType.PAGE_VIEW]: 0,\n [EventType.CUSTOM]: 0,\n [EventType.VIEWPORT_VISIBLE]: 0,\n [EventType.SCROLL]: 0,\n };\n\n private readonly saveSessionCountsDebounced: ((sessionId: string) => void) | null = null;\n\n /**\n * Creates an EventManager instance.\n *\n * **Initialization**:\n * - Creates SenderManager instances for configured integrations (SaaS/Custom)\n * - Initializes event emitter for local consumption\n *\n * @param storeManager - Storage manager for persistence\n * @param emitter - Optional event emitter for local event consumption\n * @param transformers - Optional event transformation hooks\n */\n constructor(storeManager: StorageManager, emitter: Emitter | null = null, transformers: TransformerMap = {}) {\n super();\n\n this.emitter = emitter;\n this.transformers = transformers;\n this.timeManager = new TimeManager();\n\n this.dataSenders = [];\n const collectApiUrls = this.get('collectApiUrls');\n\n if (collectApiUrls?.saas) {\n this.dataSenders.push(new SenderManager(storeManager, 'saas', collectApiUrls.saas, transformers));\n }\n\n if (collectApiUrls?.custom) {\n this.dataSenders.push(new SenderManager(storeManager, 'custom', collectApiUrls.custom, transformers));\n }\n\n // Initialize debounced session counts saver (500ms delay, trailing edge)\n // Reduces localStorage writes from ~1000 per session to ~20-30 (96-97% reduction)\n this.saveSessionCountsDebounced = this.debounce((sessionId: string) => {\n this.saveSessionCounts(sessionId);\n }, 500);\n\n // Cleanup expired session counts on init (7-day TTL)\n this.cleanupExpiredSessionCounts();\n }\n\n /**\n * Recovers persisted events from localStorage after a crash or page reload.\n *\n * **Purpose**: Ensures zero data loss by recovering events that failed to send\n * in the previous session due to network errors or crashes.\n *\n * **Flow**:\n * 1. Calls `recoverPersistedEvents()` on all SenderManager instances in parallel\n * 2. Each SenderManager attempts to resend its persisted events to backend\n * 3. On success: Removes recovered events from consent/pending buffers\n * 4. On failure: Logs warning (events remain in localStorage for next attempt)\n *\n * **Multi-Integration**:\n * - Independent recovery per integration (SaaS + Custom backends)\n * - Parallel recovery via `Promise.allSettled()` (one failure doesn't block others)\n * - No cross-contamination (SaaS events don't go to Custom API)\n *\n * **Called by**: `App.init()` after initialization\n *\n * **Important**: Events are NOT removed from pending/consent buffers until\n * successful network transmission.\n *\n * @see src/managers/README.md (lines 5-75) for recovery details\n */\n async recoverPersistedEvents(): Promise<void> {\n const recoveryPromises = this.dataSenders.map(async (sender) =>\n sender.recoverPersistedEvents({\n onSuccess: (_eventCount, recoveredEvents, body) => {\n if (recoveredEvents && recoveredEvents.length > 0) {\n const eventIds = recoveredEvents.map((e) => e.id);\n this.removeProcessedEvents(eventIds);\n\n if (body) {\n this.emitEventsQueue(body);\n }\n }\n },\n onFailure: () => {\n log('debug', 'Failed to recover persisted events');\n },\n }),\n );\n\n await Promise.allSettled(recoveryPromises);\n }\n\n /**\n * Tracks a user interaction event and adds it to the event queue.\n *\n * **Purpose**: Central tracking method for all analytics events (clicks, page views,\n * custom events, web vitals, errors, scroll, viewport visibility, session start/end).\n *\n * **Validation & Buffering**:\n * - Validates `type` is provided (required)\n * - If session not initialized: Buffers in `pendingEventsBuffer` (max 100 events, FIFO)\n *\n * **Rate Limiting** (non-critical events only):\n * - Global: 50 events/second sliding window (critical events exempted)\n * - Per-event-name: 60/minute for custom events (configurable via `maxSameEventPerMinute`)\n * - Per-session total: 1000 events max\n * - Per-session by type: Clicks 500, Page views 100, Custom 500, Viewport 200, Scroll 120\n *\n * **Deduplication**:\n * - LRU cache with 1000 fingerprints (10px coordinate precision for clicks, 500ms time threshold)\n * - Prevents duplicate events within 500ms window\n * - SESSION_START protected by `hasStartSession` flag\n *\n * **Sampling**:\n * - Applied after validation and rate limiting\n * - Critical events (SESSION_START/END) always included\n * - Configurable via `samplingRate` (0-1)\n *\n * **Transformation**:\n * - `beforeSend` applied (if custom-only mode) before dedup/sampling/queueing\n * - Returning `null` from `beforeSend` filters out the event\n *\n * **Queue Management**:\n * - Events added to `eventsQueue` (max 100 events, FIFO with priority for session events)\n * - Dynamic flush: Immediate send when 50-event batch threshold reached\n * - Periodic flush: Every 10 seconds\n *\n * **Multi-Integration**:\n * - Backend integrations: Handled by SenderManager instances\n *\n * **QA Mode**:\n * - Custom events logged to console with styling\n * - Events NOT sent to backend (emitted locally only)\n *\n * @param eventData - Event data to track\n *\n * @example\n * ```typescript\n * eventManager.track({\n * type: EventType.CLICK,\n * click_data: { x: 0.5, y: 0.3, tag: 'button', text: 'Submit' }\n * });\n *\n * eventManager.track({\n * type: EventType.CUSTOM,\n * custom_event: { name: 'checkout_completed', metadata: { total: 99.99 } }\n * });\n * ```\n *\n * @see src/managers/README.md (lines 5-75) for detailed tracking logic\n */\n track({\n type,\n page_url,\n from_page_url,\n scroll_data,\n click_data,\n custom_event,\n web_vitals,\n error_data,\n viewport_data,\n page_view,\n }: Partial<EventData>): void {\n if (!type) {\n log('error', 'Event type is required - event will be ignored');\n return;\n }\n\n const currentSessionId = this.get('sessionId');\n\n if (!currentSessionId) {\n if (this.pendingEventsBuffer.length >= MAX_PENDING_EVENTS_BUFFER) {\n this.pendingEventsBuffer.shift();\n log('debug', 'Pending events buffer full - dropping oldest event', {\n data: { maxBufferSize: MAX_PENDING_EVENTS_BUFFER },\n });\n }\n\n this.pendingEventsBuffer.push({\n type,\n page_url,\n from_page_url,\n scroll_data,\n click_data,\n custom_event,\n web_vitals,\n error_data,\n viewport_data,\n page_view,\n });\n\n return;\n }\n\n if (this.lastSessionId !== currentSessionId) {\n this.lastSessionId = currentSessionId;\n // Load persisted counts from localStorage instead of resetting to zero\n // This prevents count reset on page reload and allows proper enforcement of per-session limits\n this.sessionEventCounts = this.loadSessionCounts(currentSessionId);\n }\n\n const isCriticalEvent = type === EventType.SESSION_START;\n\n if (isCriticalEvent) {\n log('debug', 'Processing SESSION_START event', {\n data: { sessionId: currentSessionId },\n });\n }\n\n if (!isCriticalEvent && !this.checkRateLimit()) {\n return;\n }\n\n const eventType = type as EventType;\n\n if (!isCriticalEvent) {\n if (this.sessionEventCounts.total >= MAX_EVENTS_PER_SESSION) {\n log('warn', 'Session event limit reached', {\n data: {\n type: eventType,\n total: this.sessionEventCounts.total,\n limit: MAX_EVENTS_PER_SESSION,\n },\n });\n\n return;\n }\n\n const typeLimit = this.getTypeLimitForEvent(eventType);\n\n if (typeLimit) {\n const currentCount = this.sessionEventCounts[eventType];\n\n if (currentCount !== undefined && currentCount >= typeLimit) {\n log('warn', 'Session event type limit reached', {\n data: {\n type: eventType,\n count: currentCount,\n limit: typeLimit,\n },\n });\n\n return;\n }\n }\n }\n\n if (eventType === EventType.CUSTOM && custom_event?.name) {\n const maxSameEventPerMinute = this.get('config')?.maxSameEventPerMinute ?? MAX_SAME_EVENT_PER_MINUTE;\n\n if (!this.checkPerEventRateLimit(custom_event.name, maxSameEventPerMinute)) {\n return;\n }\n }\n const isSessionStart = eventType === EventType.SESSION_START;\n\n const currentPageUrl = (page_url as string) || this.get('pageUrl');\n const payload = this.buildEventPayload({\n type: eventType,\n page_url: currentPageUrl,\n from_page_url,\n scroll_data,\n click_data,\n custom_event,\n web_vitals,\n error_data,\n viewport_data,\n page_view,\n });\n\n // Handle event filtered by beforeSend transformer\n if (!payload) {\n return;\n }\n\n if (!isCriticalEvent && !this.shouldSample()) {\n return;\n }\n\n if (isSessionStart) {\n const currentSessionId = this.get('sessionId');\n\n if (!currentSessionId) {\n log('error', 'Session start event requires sessionId - event will be ignored');\n return;\n }\n\n if (this.get('hasStartSession')) {\n log('debug', 'Duplicate session_start detected', {\n data: { sessionId: currentSessionId },\n });\n\n return;\n }\n\n this.set('hasStartSession', true);\n }\n\n if (this.isDuplicateEvent(payload)) {\n return;\n }\n\n if (this.get('mode') === Mode.QA) {\n if (eventType === EventType.CUSTOM && custom_event) {\n log('info', `Custom Event: ${custom_event.name}`, {\n visibility: 'qa',\n data: {\n name: custom_event.name,\n ...(custom_event.metadata && { metadata: custom_event.metadata }),\n },\n });\n\n this.emitEvent(payload);\n\n return;\n }\n\n if (eventType === EventType.VIEWPORT_VISIBLE && viewport_data) {\n const displayName = viewport_data.name || viewport_data.id || viewport_data.selector;\n\n log('info', `Viewport Visible: ${displayName}`, {\n visibility: 'qa',\n data: {\n selector: viewport_data.selector,\n ...(viewport_data.name && { name: viewport_data.name }),\n ...(viewport_data.id && { id: viewport_data.id }),\n visibilityRatio: viewport_data.visibilityRatio,\n dwellTime: viewport_data.dwellTime,\n },\n });\n\n this.emitEvent(payload);\n\n return;\n }\n }\n\n this.addToQueue(payload);\n\n if (!isCriticalEvent) {\n this.sessionEventCounts.total++;\n\n if (this.sessionEventCounts[eventType] !== undefined) {\n this.sessionEventCounts[eventType]++;\n }\n\n // Persist updated counts to localStorage (debounced for performance)\n // Debouncing reduces localStorage writes from ~1000 to ~20-30 per session (96-97% reduction)\n // Trailing edge ensures no data loss - final counts always saved after 500ms silence\n const currentSessionId = this.get('sessionId');\n if (currentSessionId && this.saveSessionCountsDebounced) {\n this.saveSessionCountsDebounced(currentSessionId);\n }\n }\n }\n\n /**\n * Stops event tracking and clears all queues and buffers.\n *\n * **Purpose**: Cleanup method called during `App.destroy()` to reset EventManager state\n * and allow subsequent init() → destroy() → init() cycles.\n *\n * **Cleanup Actions**:\n * 1. **Clear send interval**: Stops periodic 10-second queue flush timer\n * 2. **Clear all queues and buffers**:\n * - `eventsQueue`: Discarded (not sent)\n * - `pendingEventsBuffer`: Discarded (events before session init)\n * 3. **Reset rate limiting state**: Clears rate limit counters and per-event limits\n * 4. **Reset session counters**: Clears per-session event counts\n * 5. **Reset `hasStartSession` flag**: Allows SESSION_START in next init cycle\n * 6. **Stop SenderManagers**: Calls `stop()` on all SenderManager instances\n *\n * **Important Behavior**:\n * - **No final flush**: Events in queue are NOT sent before stopping\n * - For flush before destroy, call `flushImmediatelySync()` first\n *\n * **Multi-Integration**:\n * - Stops all SenderManager instances (SaaS + Custom)\n *\n * **Called by**: `App.destroy()` during application teardown\n *\n * @example\n * ```typescript\n * // Proper cleanup with final flush\n * eventManager.flushImmediatelySync(); // Send pending events\n * eventManager.stop(); // Stop and clear\n * ```\n *\n * @see src/managers/README.md (lines 5-75) for cleanup details\n */\n stop(): void {\n if (this.sendIntervalId) {\n clearInterval(this.sendIntervalId);\n this.sendIntervalId = null;\n }\n\n // Save session counts immediately before cleanup (bypass debounce)\n // This ensures final counts are persisted before destroying the EventManager\n const currentSessionId = this.get('sessionId');\n if (currentSessionId) {\n this.saveSessionCounts(currentSessionId);\n }\n\n this.eventsQueue = [];\n this.pendingEventsBuffer = [];\n this.recentEventFingerprints.clear();\n this.rateLimitCounter = 0;\n this.rateLimitWindowStart = 0;\n this.perEventRateLimits.clear();\n this.sessionEventCounts = {\n total: 0,\n [EventType.CLICK]: 0,\n [EventType.PAGE_VIEW]: 0,\n [EventType.CUSTOM]: 0,\n [EventType.VIEWPORT_VISIBLE]: 0,\n [EventType.SCROLL]: 0,\n };\n this.lastSessionId = null;\n this.set('hasStartSession', false);\n\n this.dataSenders.forEach((sender) => {\n sender.stop();\n });\n }\n\n /**\n * Flushes all events in the queue asynchronously.\n *\n * **Purpose**: Force immediate sending of queued events without waiting for\n * the 10-second periodic flush timer.\n *\n * **Use Cases**:\n * - Manual flush triggered by user action\n * - Before page unload (prefer `flushImmediatelySync()` for unload scenarios)\n * - Testing/debugging\n *\n * **Behavior**:\n * - Sends events via `fetch()` API (async, reliable, allows retries)\n * - Multi-integration: Sends to all configured backends in parallel\n * - Does NOT block (returns Promise that resolves when all sends complete)\n * - Clears queue only after successful transmission\n *\n * **Note**: For page unload, use `flushImmediatelySync()` instead,\n * which uses `sendBeacon()` for guaranteed delivery.\n *\n * @returns Promise resolving to `true` if all sends succeeded, `false` if any failed\n *\n * @example\n * ```typescript\n * // Before critical user action\n * await eventManager.flushImmediately();\n * ```\n *\n * @see flushImmediatelySync for synchronous page unload flush\n * @see src/managers/README.md (lines 5-75) for flush details\n */\n async flushImmediately(): Promise<boolean> {\n return this.flushEvents(false);\n }\n\n /**\n * Flushes all events in the queue synchronously using `sendBeacon()`.\n *\n * **Purpose**: Ensure events are sent before page unload, even if network is slow.\n *\n * **Use Cases**:\n * - Page unload (`beforeunload`, `pagehide` events)\n * - Tab close detection\n * - Any scenario where async flush might be interrupted\n *\n * **Behavior**:\n * - Uses `navigator.sendBeacon()` API (synchronous, queued by browser)\n * - Payload size limited to 64KB per beacon\n * - Browser guarantees delivery attempt (queued even if page closes)\n * - Clears queue immediately (no retry mechanism)\n *\n * **Multi-Integration**:\n * - Sends to all configured backends (SaaS + Custom) in parallel\n * - Independent success tracking per integration\n *\n * **Limitations**:\n * - No retry on failure (sendBeacon is fire-and-forget)\n * - 64KB payload limit (large batches may be truncated)\n *\n * @returns `true` if all sends succeeded, `false` if any failed\n *\n * @example\n * ```typescript\n * // Page unload handler\n * window.addEventListener('beforeunload', () => {\n * eventManager.flushImmediatelySync();\n * });\n * ```\n *\n * @see flushImmediately for async flush with retries\n * @see src/managers/README.md (lines 5-75) for flush details\n */\n flushImmediatelySync(): boolean {\n return this.flushEvents(true) as boolean;\n }\n\n /**\n * Returns the current number of events in the main queue.\n *\n * **Purpose**: Debugging and monitoring utility to check queue length.\n *\n * **Note**: This does NOT include:\n * - Pending events buffer (events before session init)\n * - Consent events buffer (events awaiting consent)\n * - Persisted events (events in localStorage from previous sessions)\n *\n * @returns Number of events currently in the main queue\n *\n * @example\n * ```typescript\n * const queueSize = eventManager.getQueueLength();\n * console.log(`Queue has ${queueSize} events`);\n * ```\n */\n getQueueLength(): number {\n return this.eventsQueue.length;\n }\n\n /**\n * Returns a copy of current events in the queue.\n *\n * **Purpose**: Test utility to inspect queued events for validation.\n *\n * **Note**: Only available in development mode via TestBridge.\n *\n * @returns Shallow copy of events queue\n * @internal Used by test-bridge.ts for test inspection\n */\n getQueueEvents(): EventData[] {\n return [...this.eventsQueue];\n }\n\n /**\n * Triggers immediate queue flush (test utility).\n *\n * **Purpose**: Test utility to manually flush event queue for validation.\n *\n * **Note**: Only available in development mode via TestBridge.\n *\n * @returns Promise that resolves when flush completes\n * @internal Used by test-bridge.ts for test control\n */\n async flushQueue(): Promise<void> {\n await this.flushImmediately();\n }\n\n /**\n * Clears the event queue (test utility - use with caution).\n *\n * **Purpose**: Test utility to reset queue state between tests.\n *\n * **Warning**: This will discard all queued events without sending them.\n * Only use in test cleanup or when explicitly required.\n *\n * **Note**: Only available in development mode via TestBridge.\n *\n * @internal Used by test-bridge.ts for test cleanup\n */\n clearQueue(): void {\n this.eventsQueue = [];\n }\n\n /**\n * Flushes buffered events to the main queue after session initialization.\n *\n * **Purpose**: Re-tracks events that were captured before session initialization\n * (e.g., events fired during `App.init()` before SessionManager completes).\n *\n * **Pending Events Buffer**:\n * - Holds up to 100 events captured before `sessionId` is available\n * - FIFO eviction when buffer full (oldest events dropped with warning)\n * - Cleared and re-tracked when session becomes available\n *\n * **Flow**:\n * 1. Check if session is initialized (`sessionId` exists in global state)\n * 2. If not initialized: Log warning and keep events in buffer\n * 3. If initialized: Copy buffer, clear it, and re-track each event via `track()`\n * 4. Each event goes through full validation/dedup/rate limiting pipeline\n *\n * **Called by**:\n * - `SessionManager.startTracking()` after session initialization\n * - Ensures no events are lost during initialization phase\n *\n * **Important**: Events are re-tracked through `track()` method, so they go\n * through all validation, deduplication, rate limiting, and consent checks again.\n *\n * @example\n * ```typescript\n * // In SessionManager after session creation\n * this.set('sessionId', newSessionId);\n * eventManager.flushPendingEvents(); // Re-track buffered events\n * ```\n *\n * @see src/managers/README.md (lines 5-75) for pending buffer details\n */\n flushPendingEvents(): void {\n if (this.pendingEventsBuffer.length === 0) {\n return;\n }\n\n const currentSessionId = this.get('sessionId');\n if (!currentSessionId) {\n log('debug', 'Cannot flush pending events: session not initialized - keeping in buffer', {\n data: { bufferedEventCount: this.pendingEventsBuffer.length },\n });\n\n return;\n }\n\n const bufferedEvents = [...this.pendingEventsBuffer];\n this.pendingEventsBuffer = [];\n\n bufferedEvents.forEach((event) => {\n this.track(event);\n });\n }\n\n private clearSendInterval(): void {\n if (this.sendIntervalId) {\n clearInterval(this.sendIntervalId);\n this.sendIntervalId = null;\n }\n }\n\n private isSuccessfulResult(result: PromiseSettledResult<boolean>): boolean {\n return result.status === 'fulfilled' && result.value === true;\n }\n\n private flushEvents(isSync: boolean): boolean | Promise<boolean> {\n if (this.eventsQueue.length === 0) {\n return isSync ? true : Promise.resolve(true);\n }\n\n const body = this.buildEventsPayload();\n const eventsToSend = [...this.eventsQueue];\n const eventIds = eventsToSend.map((e) => e.id);\n\n if (this.dataSenders.length === 0) {\n this.removeProcessedEvents(eventIds);\n this.clearSendInterval();\n this.emitEventsQueue(body);\n\n return isSync ? true : Promise.resolve(true);\n }\n\n if (isSync) {\n const results = this.dataSenders.map((sender) => sender.sendEventsQueueSync(body));\n const anySucceeded = results.some((success) => success);\n\n // Optimistic removal: Remove events if ANY integration succeeded\n // Each SenderManager independently persists failures in its own localStorage\n // This prevents duplicate sends to successful integrations on retry\n if (anySucceeded) {\n this.removeProcessedEvents(eventIds);\n this.clearSendInterval();\n this.emitEventsQueue(body);\n } else {\n // All integrations failed - keep events in queue for retry on next page load\n this.clearSendInterval();\n log('debug', 'Sync flush complete failure, events kept in queue for retry', {\n data: { eventCount: eventIds.length },\n });\n }\n\n return anySucceeded;\n } else {\n const sendPromises = this.dataSenders.map(async (sender) =>\n sender.sendEventsQueue(body, {\n onSuccess: () => {},\n onFailure: () => {},\n }),\n );\n\n return Promise.allSettled(sendPromises).then((results) => {\n const anySucceeded = results.some((result) => this.isSuccessfulResult(result));\n\n // Optimistic removal: Remove events if ANY integration succeeded\n // Each SenderManager independently persists failures in its own localStorage\n // This prevents duplicate sends to successful integrations on retry\n if (anySucceeded) {\n this.removeProcessedEvents(eventIds);\n this.clearSendInterval();\n this.emitEventsQueue(body);\n } else {\n // All integrations failed - keep events in queue for retry on next page load\n log('debug', 'Async flush complete failure, events kept in queue for retry', {\n data: { eventCount: eventsToSend.length },\n });\n }\n\n return anySucceeded;\n });\n }\n }\n\n private async sendEventsQueue(): Promise<void> {\n if (!this.get('sessionId') || this.eventsQueue.length === 0) {\n return;\n }\n\n const body = this.buildEventsPayload();\n\n if (this.dataSenders.length === 0) {\n this.emitEventsQueue(body);\n return;\n }\n\n const eventsToSend = [...this.eventsQueue];\n const eventIds = eventsToSend.map((e) => e.id);\n\n const sendPromises = this.dataSenders.map(async (sender) =>\n sender.sendEventsQueue(body, {\n onSuccess: () => {},\n onFailure: () => {},\n }),\n );\n\n const results = await Promise.allSettled(sendPromises);\n\n const anySucceeded = results.some((result) => this.isSuccessfulResult(result));\n\n // Optimistic removal: Remove events if ANY integration succeeded\n // Each SenderManager independently persists failures in its own localStorage\n if (anySucceeded) {\n this.removeProcessedEvents(eventIds);\n this.emitEventsQueue(body);\n\n const failedCount = results.filter((result) => !this.isSuccessfulResult(result)).length;\n if (failedCount > 0) {\n log('debug', 'Periodic send completed with some failures, removed from queue and persisted per-integration', {\n data: { eventCount: eventsToSend.length, failedCount },\n });\n }\n } else {\n // All integrations failed - keep events in queue for retry\n log('debug', 'Periodic send complete failure, events kept in queue for retry', {\n data: { eventCount: eventsToSend.length },\n });\n }\n\n if (this.eventsQueue.length === 0) {\n this.clearSendInterval();\n }\n }\n\n private buildEventsPayload(): EventsQueue {\n const eventMap = new Map<string, EventData>();\n const order: string[] = [];\n\n for (const event of this.eventsQueue) {\n const signature = this.createEventSignature(event);\n\n if (!eventMap.has(signature)) {\n order.push(signature);\n }\n\n eventMap.set(signature, event);\n }\n\n const events = order\n .map((signature) => eventMap.get(signature))\n .filter((event): event is EventData => Boolean(event))\n .sort((a, b) => {\n // SESSION_START always goes first\n if (a.type === EventType.SESSION_START && b.type !== EventType.SESSION_START) return -1;\n if (b.type === EventType.SESSION_START && a.type !== EventType.SESSION_START) return 1;\n // Otherwise sort by timestamp\n return a.timestamp - b.timestamp;\n });\n\n let queue: EventsQueue = {\n user_id: this.get('userId'),\n session_id: this.get('sessionId') as string,\n device: this.get('device'),\n events,\n ...(this.get('config')?.globalMetadata && { global_metadata: this.get('config')?.globalMetadata }),\n };\n\n const collectApiUrls = this.get('collectApiUrls');\n const hasAnyBackend = Boolean(collectApiUrls?.custom || collectApiUrls?.saas);\n const beforeBatchTransformer = this.transformers.beforeBatch;\n\n if (!hasAnyBackend && beforeBatchTransformer) {\n const transformed = transformBatch(queue, beforeBatchTransformer, 'EventManager');\n\n if (transformed !== null) {\n queue = transformed;\n }\n }\n\n return queue;\n }\n\n private buildEventPayload(data: Partial<EventData>): EventData | null {\n const currentPageUrl = data.page_url ?? this.get('pageUrl');\n\n // Use TimeManager for accurate timestamps (immune to clock skew during session)\n const timestamp = this.timeManager.now();\n\n // Validate timestamp before creating event\n const validation = this.timeManager.validateTimestamp(timestamp);\n if (!validation.valid) {\n log('warn', 'Event timestamp validation failed', {\n data: { type: data.type, error: validation.error },\n });\n // Continue anyway with adjusted timestamp to avoid data loss\n // Backend has 3-minute tolerance as safety net\n }\n\n // Get session-level attribution from global state (captured once at SESSION_START)\n const sessionReferrer = this.get('sessionReferrer');\n const sessionUtm = this.get('sessionUtm');\n\n let payload: EventData = {\n id: generateEventId(),\n type: data.type as EventType,\n page_url: currentPageUrl,\n timestamp,\n ...(sessionReferrer && { referrer: sessionReferrer }),\n ...(data.from_page_url && { from_page_url: data.from_page_url }),\n ...(data.scroll_data && { scroll_data: data.scroll_data }),\n ...(data.click_data && { click_data: data.click_data }),\n ...(data.custom_event && { custom_event: data.custom_event }),\n ...(data.web_vitals && { web_vitals: data.web_vitals }),\n ...(data.error_data && { error_data: data.error_data }),\n ...(data.viewport_data && { viewport_data: data.viewport_data }),\n ...(data.page_view && { page_view: data.page_view }),\n ...(sessionUtm && { utm: sessionUtm }),\n };\n\n const collectApiUrls = this.get('collectApiUrls');\n const hasCustomBackend = Boolean(collectApiUrls?.custom);\n const hasSaasBackend = Boolean(collectApiUrls?.saas);\n const hasAnyBackend = hasCustomBackend || hasSaasBackend;\n const isMultiIntegration = hasCustomBackend && hasSaasBackend;\n const beforeSendTransformer = this.transformers.beforeSend;\n\n const shouldApplyBeforeSend =\n beforeSendTransformer && (!hasAnyBackend || (hasCustomBackend && !isMultiIntegration));\n\n if (shouldApplyBeforeSend) {\n const transformed = transformEvent(payload, beforeSendTransformer, 'EventManager');\n\n if (transformed === null) {\n return null;\n }\n\n payload = transformed;\n }\n\n return payload;\n }\n\n private isDuplicateEvent(event: EventData): boolean {\n const now = Date.now();\n const fingerprint = this.createEventFingerprint(event);\n\n const lastSeen = this.recentEventFingerprints.get(fingerprint);\n\n if (lastSeen && now - lastSeen < DUPLICATE_EVENT_THRESHOLD_MS) {\n this.recentEventFingerprints.set(fingerprint, now);\n return true;\n }\n\n this.recentEventFingerprints.set(fingerprint, now);\n\n if (this.recentEventFingerprints.size > MAX_FINGERPRINTS) {\n this.pruneOldFingerprints();\n }\n\n if (this.recentEventFingerprints.size > MAX_FINGERPRINTS_HARD_LIMIT) {\n this.recentEventFingerprints.clear();\n this.recentEventFingerprints.set(fingerprint, now);\n\n log('debug', 'Event fingerprint cache exceeded hard limit, cleared', {\n data: { hardLimit: MAX_FINGERPRINTS_HARD_LIMIT },\n });\n }\n\n return false;\n }\n\n private pruneOldFingerprints(): void {\n const now = Date.now();\n const cutoff = DUPLICATE_EVENT_THRESHOLD_MS * FINGERPRINT_CLEANUP_MULTIPLIER;\n\n for (const [fingerprint, timestamp] of this.recentEventFingerprints.entries()) {\n if (now - timestamp > cutoff) {\n this.recentEventFingerprints.delete(fingerprint);\n }\n }\n\n log('debug', 'Pruned old event fingerprints', {\n data: {\n remaining: this.recentEventFingerprints.size,\n cutoffMs: cutoff,\n },\n });\n }\n\n private createEventFingerprint(event: EventData): string {\n let fingerprint = `${event.type}_${event.page_url}`;\n\n if (event.click_data) {\n const x = Math.round((event.click_data.x || 0) / 10) * 10;\n const y = Math.round((event.click_data.y || 0) / 10) * 10;\n fingerprint += `_click_${x}_${y}`;\n }\n\n if (event.scroll_data) {\n fingerprint += `_scroll_${event.scroll_data.depth}_${event.scroll_data.direction}`;\n }\n\n if (event.custom_event) {\n fingerprint += `_custom_${event.custom_event.name}`;\n }\n\n if (event.web_vitals) {\n fingerprint += `_vitals_${event.web_vitals.type}`;\n }\n\n if (event.error_data) {\n fingerprint += `_error_${event.error_data.type}_${event.error_data.message}`;\n }\n\n return fingerprint;\n }\n\n private createEventSignature(event: EventData): string {\n return this.createEventFingerprint(event);\n }\n\n private addToQueue(event: EventData): void {\n this.emitEvent(event);\n\n this.eventsQueue.push(event);\n\n if (this.eventsQueue.length > MAX_EVENTS_QUEUE_LENGTH) {\n const nonCriticalIndex = this.eventsQueue.findIndex((e) => e.type !== EventType.SESSION_START);\n\n const removedEvent =\n nonCriticalIndex >= 0 ? this.eventsQueue.splice(nonCriticalIndex, 1)[0] : this.eventsQueue.shift();\n\n log('warn', 'Event queue overflow, oldest non-critical event removed', {\n data: {\n maxLength: MAX_EVENTS_QUEUE_LENGTH,\n currentLength: this.eventsQueue.length,\n removedEventType: removedEvent?.type,\n wasCritical: removedEvent?.type === EventType.SESSION_START,\n },\n });\n }\n\n if (!this.sendIntervalId) {\n this.startSendInterval();\n }\n\n if (this.eventsQueue.length >= BATCH_SIZE_THRESHOLD) {\n void this.sendEventsQueue();\n }\n }\n\n private startSendInterval(): void {\n this.sendIntervalId = window.setInterval(() => {\n if (this.eventsQueue.length > 0) {\n void this.sendEventsQueue();\n }\n }, EVENT_SENT_INTERVAL_MS);\n }\n\n private shouldSample(): boolean {\n const samplingRate = this.get('config')?.samplingRate ?? 1;\n return Math.random() < samplingRate;\n }\n\n private checkRateLimit(): boolean {\n const now = Date.now();\n\n if (now - this.rateLimitWindowStart > RATE_LIMIT_WINDOW_MS) {\n this.rateLimitCounter = 0;\n this.rateLimitWindowStart = now;\n }\n\n if (this.rateLimitCounter >= MAX_EVENTS_PER_SECOND) {\n return false;\n }\n\n this.rateLimitCounter++;\n return true;\n }\n\n private checkPerEventRateLimit(eventName: string, maxSameEventPerMinute: number): boolean {\n const now = Date.now();\n const timestamps = this.perEventRateLimits.get(eventName) ?? [];\n\n const validTimestamps = timestamps.filter((ts) => now - ts < PER_EVENT_RATE_LIMIT_WINDOW_MS);\n\n if (validTimestamps.length >= maxSameEventPerMinute) {\n log('warn', 'Per-event rate limit exceeded for custom event', {\n data: {\n eventName,\n limit: maxSameEventPerMinute,\n window: `${PER_EVENT_RATE_LIMIT_WINDOW_MS / 1000}s`,\n },\n });\n return false;\n }\n\n validTimestamps.push(now);\n this.perEventRateLimits.set(eventName, validTimestamps);\n\n return true;\n }\n\n private getTypeLimitForEvent(type: EventType): number | null {\n const limits: Partial<Record<EventType, number>> = {\n [EventType.CLICK]: MAX_CLICKS_PER_SESSION,\n [EventType.PAGE_VIEW]: MAX_PAGE_VIEWS_PER_SESSION,\n [EventType.CUSTOM]: MAX_CUSTOM_EVENTS_PER_SESSION,\n [EventType.VIEWPORT_VISIBLE]: MAX_VIEWPORT_EVENTS_PER_SESSION,\n [EventType.SCROLL]: MAX_SCROLL_EVENTS_PER_SESSION,\n };\n return limits[type] ?? null;\n }\n\n private removeProcessedEvents(eventIds: string[]): void {\n const eventIdSet = new Set(eventIds);\n\n this.eventsQueue = this.eventsQueue.filter((event) => {\n return !eventIdSet.has(event.id);\n });\n }\n\n private emitEvent(eventData: EventData): void {\n if (this.emitter) {\n this.emitter.emit(EmitterEvent.EVENT, eventData);\n }\n }\n\n private emitEventsQueue(queue: EventsQueue): void {\n if (this.emitter) {\n this.emitter.emit(EmitterEvent.QUEUE, queue);\n }\n }\n\n /**\n * Creates a debounced version of a function that delays execution until after\n * a specified wait time has elapsed since the last invocation.\n *\n * **Purpose**: Reduces frequency of expensive operations (localStorage writes)\n * while ensuring no data is lost (trailing edge execution).\n *\n * **Behavior**:\n * - Each call resets the timer\n * - Function executes only after `delay` ms of silence\n * - Last invocation always executes (trailing edge)\n *\n * **Use Case**: Batches rapid successive calls into a single execution\n *\n * @param fn - Function to debounce\n * @param delay - Delay in milliseconds\n * @returns Debounced version of the function\n *\n * @internal\n */\n private debounce<T extends (...args: any[]) => void>(fn: T, delay: number): T {\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n return ((...args: Parameters<T>) => {\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n }\n\n timeoutId = setTimeout(() => {\n fn(...args);\n timeoutId = null;\n }, delay);\n }) as T;\n }\n\n /**\n * Returns initial zero counts for session event tracking.\n *\n * **Purpose**: DRY helper to avoid duplicating initial counts structure\n * across multiple methods (loadSessionCounts, validation fallbacks).\n *\n * @returns Fresh SessionEventCounts object with all counters at zero\n *\n * @internal\n */\n private getInitialCounts(): SessionEventCounts {\n return {\n total: 0,\n [EventType.CLICK]: 0,\n [EventType.PAGE_VIEW]: 0,\n [EventType.CUSTOM]: 0,\n [EventType.VIEWPORT_VISIBLE]: 0,\n [EventType.SCROLL]: 0,\n };\n }\n\n /**\n * Loads persisted session event counts from localStorage.\n *\n * **Purpose**: Restore per-session event counts after page reload to maintain\n * accurate rate limiting across page navigations within the same session.\n *\n * **Behavior**:\n * - Attempts to load counts from localStorage using session ID as key\n * - If no persisted data found: Returns initial zero counts\n * - If corrupted data found: Returns initial zero counts with warning\n *\n * **Storage Key**: `tlog:{userId}:session_counts:{sessionId}`\n *\n * **Why This Matters**:\n * - Without persistence, counts reset on every page reload\n * - This allows users to bypass per-session limits by refreshing the page\n * - Example: 100 PAGE_VIEW limit could be bypassed by reloading after 99 events\n *\n * @param sessionId - Current session identifier\n * @returns Session event counts object (either persisted or initial state)\n *\n * @internal\n */\n private loadSessionCounts(sessionId: string): SessionEventCounts {\n if (typeof window === 'undefined' || typeof localStorage === 'undefined') {\n return this.getInitialCounts();\n }\n\n const userId = this.get('userId') || 'anonymous';\n const storageKey = SESSION_COUNTS_KEY(userId, sessionId);\n\n try {\n const stored = localStorage.getItem(storageKey);\n\n if (!stored) {\n // No persisted data - this is a new session or first page load\n return this.getInitialCounts();\n }\n\n const parsed = JSON.parse(stored) as Partial<StoredSessionCounts>;\n\n // Check for expiry (7 days)\n if (parsed._timestamp && Date.now() - parsed._timestamp > SESSION_COUNTS_EXPIRY_MS) {\n log('debug', 'Session counts expired, clearing', {\n data: { sessionId, age: Date.now() - parsed._timestamp },\n });\n localStorage.removeItem(storageKey);\n return this.getInitialCounts();\n }\n\n // Robust validation: check all required fields (not just 'total')\n // Prevents partial/corrupted data from causing runtime errors\n if (\n typeof parsed.total === 'number' &&\n typeof parsed[EventType.CLICK] === 'number' &&\n typeof parsed[EventType.PAGE_VIEW] === 'number' &&\n typeof parsed[EventType.CUSTOM] === 'number' &&\n typeof parsed[EventType.VIEWPORT_VISIBLE] === 'number' &&\n typeof parsed[EventType.SCROLL] === 'number'\n ) {\n // Return only the SessionEventCounts fields (exclude _timestamp, _version)\n return {\n total: parsed.total,\n [EventType.CLICK]: parsed[EventType.CLICK],\n [EventType.PAGE_VIEW]: parsed[EventType.PAGE_VIEW],\n [EventType.CUSTOM]: parsed[EventType.CUSTOM],\n [EventType.VIEWPORT_VISIBLE]: parsed[EventType.VIEWPORT_VISIBLE],\n [EventType.SCROLL]: parsed[EventType.SCROLL],\n };\n }\n\n log('warn', 'Invalid session counts structure in localStorage, resetting', {\n data: { sessionId, parsed },\n });\n localStorage.removeItem(storageKey);\n log('debug', 'Session counts removed due to invalid/corrupted data', {\n data: { sessionId, parsed },\n });\n\n return this.getInitialCounts();\n } catch (error) {\n log('warn', 'Failed to load session counts from localStorage', {\n error,\n data: { sessionId },\n });\n\n return this.getInitialCounts();\n }\n }\n\n /**\n * Cleans up expired session counts from localStorage.\n *\n * **Purpose**: Prevents localStorage pollution from abandoned sessions by removing\n * counts older than 7 days.\n *\n * **Behavior**:\n * - Checks if cleanup was run recently (within last hour) and skips if so\n * - Iterates all localStorage keys matching the session counts prefix pattern\n * - Parses each entry and checks `_timestamp` field\n * - Removes entries where age exceeds SESSION_COUNTS_EXPIRY_MS (7 days)\n * - Silently ignores parse errors (corrupted entries cleaned on next load)\n * - Updates last cleanup timestamp after successful run\n *\n * **When Called**: Automatically on EventManager constructor initialization\n *\n * **Performance**: O(n) scan where n = localStorage keys (typically <100), but throttled\n * to run at most once per hour to prevent impact on rapid page reloads\n *\n * @internal\n */\n private cleanupExpiredSessionCounts(): void {\n if (typeof window === 'undefined' || typeof localStorage === 'undefined') {\n return;\n }\n\n try {\n // Throttling: Skip if cleanup ran recently (within last hour)\n const lastCleanup = localStorage.getItem(SESSION_COUNTS_LAST_CLEANUP_KEY);\n\n if (lastCleanup) {\n const timeSinceLastCleanup = Date.now() - parseInt(lastCleanup, 10);\n\n if (timeSinceLastCleanup < SESSION_COUNTS_CLEANUP_THROTTLE_MS) {\n log('debug', 'Skipping session counts cleanup (throttled)', {\n data: { timeSinceLastCleanup, throttleMs: SESSION_COUNTS_CLEANUP_THROTTLE_MS },\n });\n\n return;\n }\n }\n\n const userId = this.get('userId') || 'anonymous';\n const prefix = `${STORAGE_BASE_KEY}:${userId}:session_counts:`;\n\n // Collect keys to remove (can't modify during iteration)\n const keysToRemove: string[] = [];\n\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n\n if (key?.startsWith(prefix)) {\n try {\n const stored = localStorage.getItem(key);\n\n if (stored) {\n const parsed = JSON.parse(stored) as Partial<StoredSessionCounts>;\n\n // Check expiry\n if (parsed._timestamp && Date.now() - parsed._timestamp > SESSION_COUNTS_EXPIRY_MS) {\n keysToRemove.push(key);\n }\n }\n } catch {\n // Ignore parse errors, will be cleaned on next load\n }\n }\n }\n\n // Remove expired entries\n keysToRemove.forEach((key) => {\n localStorage.removeItem(key);\n log('debug', 'Cleaned up expired session counts', { data: { key } });\n });\n\n if (keysToRemove.length > 0) {\n log('info', `Cleaned up ${keysToRemove.length} expired session counts entries`);\n }\n\n // Update last cleanup timestamp\n localStorage.setItem(SESSION_COUNTS_LAST_CLEANUP_KEY, Date.now().toString());\n } catch (error) {\n log('warn', 'Failed to cleanup expired session counts', { error });\n }\n }\n\n /**\n * Persists current session event counts to localStorage (debounced).\n *\n * **Purpose**: Save event counts to ensure they survive page reloads and\n * maintain accurate per-session rate limiting across navigations.\n *\n * **Behavior**:\n * - Saves current `sessionEventCounts` to localStorage using session ID as key\n * - Overwrites previous counts (always reflects latest state)\n * - Fails silently if localStorage quota exceeded or unavailable\n *\n * **Storage Key**: `tlog:{userId}:session_counts:{sessionId}`\n *\n * **Debouncing**: This method is called via `saveSessionCountsDebounced()`\n * with 500ms debounce delay. Direct calls are for immediate saves (e.g., stop()).\n *\n * **Performance**: Debouncing reduces localStorage writes from ~1000 per session\n * to ~20-30 (96-97% reduction) while maintaining data integrity.\n *\n * **Cleanup**: Counts persist across page reloads for rate limiting enforcement.\n * Automatic cleanup on page reload removes expired counts (older than 7 days).\n * Note: SESSION_COUNTS_KEY entries are intentionally persistent to maintain\n * rate limits across sessions (~100 bytes per session).\n *\n * @param sessionId - Current session identifier\n *\n * @internal\n */\n private saveSessionCounts(sessionId: string): void {\n const userId = this.get('userId') || 'anonymous';\n const storageKey = SESSION_COUNTS_KEY(userId, sessionId);\n\n try {\n const dataToStore: StoredSessionCounts = {\n ...this.sessionEventCounts,\n _timestamp: Date.now(),\n _version: 1,\n };\n\n localStorage.setItem(storageKey, JSON.stringify(dataToStore));\n } catch (error) {\n log('warn', 'Failed to persist session counts to localStorage', {\n error,\n data: { sessionId },\n });\n }\n }\n}\n","import { USER_ID_KEY } from '../constants';\nimport { generateUUID } from '../utils';\nimport { StorageManager } from './storage.manager';\n\n/**\n * Simple utility for managing unique user identification for analytics tracking.\n *\n * **Purpose**: Creates and persists RFC4122-compliant UUID v4 identifiers\n * for tracking users across browser sessions.\n *\n * **Core Functionality**:\n * - **User ID Generation**: Creates UUID v4 identifiers\n * - **Persistence**: Stores user IDs in localStorage with automatic fallback\n * - **Session Continuity**: Reuses existing user IDs across browser sessions\n * - **Global User Identity**: Single user ID shared across all TraceLog projects in the same browser\n *\n * **Key Features**:\n * - Static utility method pattern (no object instantiation required)\n * - UUID v4 generation for globally unique identifiers\n * - Fixed storage key (`tlog:uid`) for consistent identification across projects\n * - Automatic fallback to memory storage when localStorage unavailable\n * - Minimal dependencies and zero allocation approach\n *\n * **Storage**: `tlog:uid` (fixed, not project-scoped)\n *\n * @see src/managers/README.md (lines 227-252) for detailed documentation\n *\n * @example\n * ```typescript\n * const userId = UserManager.getId(storageManager);\n * // Returns: '550e8400-e29b-41d4-a716-446655440000' (UUID v4)\n *\n * // Subsequent calls return the same ID\n * const sameUserId = UserManager.getId(storageManager);\n * // Returns: '550e8400-e29b-41d4-a716-446655440000' (persisted)\n * ```\n */\nexport class UserManager {\n /**\n * Gets or creates a unique user ID.\n *\n * **Behavior**:\n * 1. Checks localStorage for existing user ID\n * 2. Returns existing ID if found\n * 3. Generates new RFC4122-compliant UUID v4 if not found\n * 4. Persists new ID to localStorage\n *\n * **Storage Key**: `tlog:uid` (fixed, shared across all TraceLog projects)\n *\n * **ID Format**: UUID v4 (e.g., `550e8400-e29b-41d4-a716-446655440000`)\n *\n * @param storageManager - Storage manager instance for persistence\n * @returns Persistent unique user ID (UUID v4 format)\n */\n static getId(storageManager: StorageManager): string {\n const storedUserId = storageManager.getItem(USER_ID_KEY);\n\n if (storedUserId) {\n return storedUserId;\n }\n\n const newUserId = generateUUID();\n storageManager.setItem(USER_ID_KEY, newUserId);\n\n return newUserId;\n }\n}\n","import { BROADCAST_CHANNEL_NAME, DEFAULT_SESSION_TIMEOUT, SESSION_STORAGE_KEY } from '../constants';\nimport { EventType, UTM } from '../types';\nimport { getExternalReferrer, getUTMParameters, log } from '../utils';\nimport { StateManager } from './state.manager';\nimport { StorageManager } from './storage.manager';\nimport { EventManager } from './event.manager';\n\ninterface StoredSessionData {\n id: string;\n lastActivity: number;\n referrer?: string;\n utm?: UTM;\n}\n\n/**\n * Session ID validation pattern: {timestamp}-{9-char-base36}\n *\n * Validates recovered session IDs to prevent data corruption from:\n * - Manual localStorage manipulation by users or browser extensions\n * - Race conditions in cross-tab scenarios (partial/truncated writes)\n * - Storage corruption or quota-based truncation\n * - Malformed IDs from older library versions or external interference\n *\n * Format: 13-digit timestamp + hyphen + 9 lowercase alphanumeric characters\n * Example: 1704067200000-a3f9c2b5d\n */\nconst SESSION_ID_PATTERN = /^\\d{13}-[a-z0-9]{9}$/;\n\n/**\n * Manages user sessions with cross-tab synchronization, inactivity detection,\n * and automatic lifecycle tracking.\n *\n * **Purpose**: Creates and manages user sessions, tracking session start\n * with automatic persistence, recovery, and multi-tab synchronization.\n * Does not track session end events (removed in v2.0.0).\n *\n * **Core Functionality**:\n * - **Session Generation**: Creates unique session IDs (`{timestamp}-{9-char-base36}`)\n * - **Activity Tracking**: Monitors user interactions to extend session timeout\n * - **Cross-Tab Sync**: BroadcastChannel synchronization across browser tabs\n * - **Persistence**: Stores session data in localStorage for recovery\n * - **Inactivity Detection**: Automatic timeout after inactivity (default 15 minutes)\n * - **Lifecycle Events**: Emits SESSION_START event only (SESSION_END removed in v2.0.0)\n *\n * **Key Features**:\n * - **Session ID Format**: `{timestamp}-{9-char-base36}` (e.g., `1704896400000-a3b4c5d6e`)\n * - **Default Timeout**: 15 minutes (900,000 ms), configurable via `sessionTimeout`\n * - **Cross-Tab Sharing**: Primary tab creates session, shares via BroadcastChannel\n * - **Secondary Tab Behavior**: Receives session from primary tab, no SESSION_START event\n * - **No Session End Events**: Library only tracks SESSION_START (v2.0.0+)\n *\n * **BroadcastChannel Integration**:\n * - **Initialized BEFORE SESSION_START**: Prevents race condition with secondary tabs\n * - **Messages**: Only `session_start` action (share session across tabs)\n * - **Fallback**: Logs warning if BroadcastChannel not supported (no cross-tab sync)\n *\n * **Activity Detection**:\n * - Tracks user interactions via listener managers (mouse, keyboard, touch, scroll, etc.)\n * - Resets inactivity timeout on each activity\n * - Updates `lastActivity` timestamp in localStorage\n *\n * **State Management**:\n * - **`sessionId`**: Current session ID stored in global state\n *\n * @see src/managers/README.md (lines 140-169) for detailed documentation\n *\n * @example\n * ```typescript\n * const sessionManager = new SessionManager(storage, eventManager, 'project123');\n *\n * // Start session tracking\n * sessionManager.startTracking();\n * // → Creates session ID: '1704896400000-a3b4c5d6e'\n * // → Emits SESSION_START event\n * // → Sets up activity listeners\n * // → Initializes cross-tab sync\n *\n * // User activity extends session\n * // (automatic via activity listeners)\n *\n * // Stop tracking (cleanup only)\n * sessionManager.stopTracking();\n * // → Cleans up listeners and timers\n * // → No SESSION_END event emitted\n * ```\n */\nexport class SessionManager extends StateManager {\n private readonly storageManager: StorageManager;\n private readonly eventManager: EventManager;\n private readonly projectId: string;\n\n private activityHandler: (() => void) | null = null;\n private visibilityChangeHandler: (() => void) | null = null;\n private sessionTimeoutId: ReturnType<typeof setTimeout> | null = null;\n private broadcastChannel: BroadcastChannel | null = null;\n private isTracking = false;\n private needsRenewal = false;\n\n /**\n * Creates a SessionManager instance.\n *\n * @param storageManager - Storage manager for session persistence\n * @param eventManager - Event manager for SESSION_START events\n * @param projectId - Project identifier for namespacing session storage\n */\n constructor(storageManager: StorageManager, eventManager: EventManager, projectId: string) {\n super();\n this.storageManager = storageManager;\n this.eventManager = eventManager;\n this.projectId = projectId;\n }\n\n private initCrossTabSync(): void {\n if (typeof BroadcastChannel === 'undefined') {\n log('debug', 'BroadcastChannel not supported');\n return;\n }\n\n const projectId = this.getProjectId();\n this.broadcastChannel = new BroadcastChannel(BROADCAST_CHANNEL_NAME(projectId));\n\n this.broadcastChannel.onmessage = (event): void => {\n const { action, sessionId, timestamp, projectId: messageProjectId } = event.data ?? {};\n\n if (messageProjectId !== projectId) {\n return;\n }\n\n if (action === 'session_start' && sessionId && typeof timestamp === 'number' && timestamp > Date.now() - 5000) {\n this.set('sessionId', sessionId);\n this.persistSession(sessionId, timestamp);\n if (this.isTracking) {\n this.setupSessionTimeout();\n }\n } else if (action && action !== 'session_start') {\n // Log unknown action types to help with debugging cross-tab synchronization issues\n log('debug', 'Ignored BroadcastChannel message with unknown action', { data: { action } });\n }\n };\n }\n\n private shareSession(sessionId: string): void {\n if (this.broadcastChannel && typeof this.broadcastChannel.postMessage === 'function') {\n this.broadcastChannel.postMessage({\n action: 'session_start',\n projectId: this.getProjectId(),\n sessionId,\n timestamp: Date.now(),\n });\n }\n }\n\n private cleanupCrossTabSync(): void {\n if (this.broadcastChannel) {\n if (typeof this.broadcastChannel.close === 'function') {\n this.broadcastChannel.close();\n }\n this.broadcastChannel = null;\n }\n }\n\n private recoverSession(): string | null {\n const storedSession = this.loadStoredSession();\n\n if (!storedSession) {\n return null;\n }\n\n // Validate session ID format: {timestamp}-{9-char-base36}\n if (!SESSION_ID_PATTERN.test(storedSession.id)) {\n log('warn', 'Invalid session ID format recovered from storage, clearing', {\n data: { sessionId: storedSession.id },\n });\n this.clearStoredSession();\n return null;\n }\n\n const sessionTimeout = this.get('config')?.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT;\n\n if (Date.now() - storedSession.lastActivity > sessionTimeout) {\n this.clearStoredSession();\n return null;\n }\n\n return storedSession.id;\n }\n\n private persistSession(sessionId: string, lastActivity: number = Date.now(), referrer?: string, utm?: UTM): void {\n this.saveStoredSession({\n id: sessionId,\n lastActivity,\n ...(referrer && { referrer }),\n ...(utm && { utm }),\n });\n }\n\n private clearStoredSession(): void {\n const storageKey = this.getSessionStorageKey();\n this.storageManager.removeItem(storageKey);\n }\n\n private loadStoredSession(): StoredSessionData | null {\n const storageKey = this.getSessionStorageKey();\n const storedData = this.storageManager.getItem(storageKey);\n\n if (!storedData) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(storedData) as StoredSessionData;\n if (!parsed.id || typeof parsed.lastActivity !== 'number') {\n return null;\n }\n return parsed;\n } catch {\n this.storageManager.removeItem(storageKey);\n return null;\n }\n }\n\n private saveStoredSession(session: StoredSessionData): void {\n const storageKey = this.getSessionStorageKey();\n this.storageManager.setItem(storageKey, JSON.stringify(session));\n }\n\n private getSessionStorageKey(): string {\n return SESSION_STORAGE_KEY(this.getProjectId());\n }\n\n private getProjectId(): string {\n return this.projectId;\n }\n\n /**\n * Starts session tracking with lifecycle management and cross-tab synchronization.\n *\n * **Purpose**: Initializes session tracking, creating or recovering a session ID,\n * setting up activity listeners, and enabling cross-tab synchronization.\n *\n * **Flow**:\n * 1. Checks if tracking already active (idempotent)\n * 2. Attempts to recover session from localStorage\n * 3. If no recovery: Generates new session ID (`{timestamp}-{9-char-base36}`)\n * 4. Sets `sessionId` in global state\n * 5. Persists session to localStorage\n * 6. Initializes BroadcastChannel for cross-tab sync (BEFORE SESSION_START)\n * 7. Shares session via BroadcastChannel (notifies other tabs)\n * 8. If NOT recovered: Tracks SESSION_START event\n * 9. Sets up inactivity timeout (default 15 minutes)\n * 10. Sets up activity listeners (click, keydown, scroll)\n * 11. Sets up lifecycle listeners (visibilitychange, beforeunload)\n *\n * **Session Recovery**:\n * - Checks localStorage for existing session\n * - Recovers if session exists and is recent (within timeout window)\n * - NO SESSION_START event if session recovered\n *\n * **Error Handling**:\n * - On error: Rolls back all setup (cleanup listeners, timers, state)\n * - Re-throws error to caller (App.init() handles failure)\n *\n * **BroadcastChannel Initialization Order**:\n * - CRITICAL: BroadcastChannel initialized BEFORE SESSION_START event\n * - Prevents race condition with secondary tabs\n * - Ensures secondary tabs can receive session_start message\n *\n * **Called by**: `SessionHandler.startTracking()` during `App.init()`\n *\n * **Important**: After successful call, `sessionId` is available in global state\n * and EventManager can flush pending events via `flushPendingEvents()`.\n *\n * @throws Error if initialization fails (rolled back automatically)\n *\n * @example\n * ```typescript\n * sessionManager.startTracking();\n * // → Session created: '1704896400000-a3b4c5d6e'\n * // → SESSION_START event tracked\n * // → Activity listeners active\n * // → Cross-tab sync enabled\n * ```\n *\n * @see src/managers/README.md (lines 140-169) for session management details\n */\n startTracking(): void {\n if (this.isTracking) {\n log('debug', 'Session tracking already active');\n return;\n }\n\n const recoveredSessionId = this.recoverSession();\n const sessionId = recoveredSessionId ?? this.generateSessionId();\n\n // Capture or recover attribution data (referrer/UTM)\n let sessionReferrer: string;\n let sessionUtm: UTM | undefined;\n\n if (recoveredSessionId) {\n // Session recovered: load attribution from storage (with fallback to current context)\n const storedSession = this.loadStoredSession();\n sessionReferrer = storedSession?.referrer ?? getExternalReferrer();\n sessionUtm = storedSession?.utm ?? getUTMParameters();\n } else {\n // New session: capture from current page context\n sessionReferrer = getExternalReferrer();\n sessionUtm = getUTMParameters();\n }\n\n log('debug', 'Session tracking initialized', {\n data: {\n sessionId,\n wasRecovered: !!recoveredSessionId,\n willEmitSessionStart: !recoveredSessionId,\n sessionReferrer,\n hasUtm: !!sessionUtm,\n },\n });\n\n this.isTracking = true;\n\n try {\n this.set('sessionId', sessionId);\n this.set('sessionReferrer', sessionReferrer);\n this.set('sessionUtm', sessionUtm);\n this.persistSession(sessionId, Date.now(), sessionReferrer, sessionUtm);\n this.initCrossTabSync();\n this.shareSession(sessionId);\n\n // Only emit SESSION_START for NEW sessions (not recovered)\n // Recovered sessions already had their SESSION_START tracked on initial creation\n // Server infers session end from last event timestamp (v2.0+)\n if (!recoveredSessionId) {\n log('debug', 'Emitting SESSION_START event', {\n data: { sessionId },\n });\n\n this.eventManager.track({\n type: EventType.SESSION_START,\n });\n } else {\n log('debug', 'Session recovered, skipping SESSION_START', {\n data: { sessionId },\n });\n }\n\n this.setupSessionTimeout();\n this.setupActivityListeners();\n this.setupLifecycleListeners();\n } catch (error) {\n this.isTracking = false;\n this.clearSessionTimeout();\n this.cleanupActivityListeners();\n this.cleanupLifecycleListeners();\n this.cleanupCrossTabSync();\n this.set('sessionId', null);\n\n throw error;\n }\n }\n\n private generateSessionId(): string {\n return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;\n }\n\n private setupSessionTimeout(): void {\n this.clearSessionTimeout();\n\n const sessionTimeout = this.get('config')?.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT;\n\n this.sessionTimeoutId = setTimeout(() => {\n this.enterRenewalMode();\n }, sessionTimeout);\n }\n\n private resetSessionTimeout(): void {\n this.setupSessionTimeout();\n const sessionId = this.get('sessionId') as string;\n if (sessionId) {\n this.persistSession(sessionId, Date.now(), this.get('sessionReferrer'), this.get('sessionUtm'));\n }\n }\n\n private clearSessionTimeout(): void {\n if (this.sessionTimeoutId) {\n clearTimeout(this.sessionTimeoutId);\n this.sessionTimeoutId = null;\n }\n }\n\n private setupActivityListeners(): void {\n this.activityHandler = (): void => {\n if (this.needsRenewal) {\n this.renewSession();\n } else {\n this.resetSessionTimeout();\n }\n };\n\n document.addEventListener('click', this.activityHandler, { passive: true });\n document.addEventListener('keydown', this.activityHandler, { passive: true });\n document.addEventListener('scroll', this.activityHandler, { passive: true });\n }\n\n /**\n * Renews the session after timeout when user returns.\n * Creates a new session ID and emits SESSION_START.\n */\n private renewSession(): void {\n this.needsRenewal = false;\n\n const newSessionId = this.generateSessionId();\n const sessionReferrer = getExternalReferrer();\n const sessionUtm = getUTMParameters();\n\n log('debug', 'Renewing session after timeout', {\n data: { newSessionId },\n });\n\n this.set('sessionId', newSessionId);\n this.set('sessionReferrer', sessionReferrer);\n this.set('sessionUtm', sessionUtm);\n this.persistSession(newSessionId, Date.now(), sessionReferrer, sessionUtm);\n\n // Re-initialize cross-tab sync with new session\n this.cleanupCrossTabSync();\n this.initCrossTabSync();\n this.shareSession(newSessionId);\n\n this.eventManager.track({\n type: EventType.SESSION_START,\n });\n\n // Flush any events that were buffered during renewal mode\n this.eventManager.flushPendingEvents();\n\n this.setupSessionTimeout();\n }\n\n private cleanupActivityListeners(): void {\n if (this.activityHandler) {\n document.removeEventListener('click', this.activityHandler);\n document.removeEventListener('keydown', this.activityHandler);\n document.removeEventListener('scroll', this.activityHandler);\n this.activityHandler = null;\n }\n }\n\n private setupLifecycleListeners(): void {\n if (this.visibilityChangeHandler) {\n return;\n }\n\n this.visibilityChangeHandler = (): void => {\n if (document.hidden) {\n this.clearSessionTimeout();\n } else {\n // Check if session expired during browser suspend/hibernate\n if (this.isSessionStale()) {\n log('debug', 'Session expired during suspend, entering renewal mode');\n this.enterRenewalMode();\n return;\n }\n\n const sessionId = this.get('sessionId');\n if (sessionId) {\n this.setupSessionTimeout();\n }\n }\n };\n\n document.addEventListener('visibilitychange', this.visibilityChangeHandler);\n }\n\n /**\n * Checks if the current session has become stale (expired during browser suspend).\n * This handles the case where JavaScript timers are paused during suspend/hibernate.\n */\n private isSessionStale(): boolean {\n // If already in renewal mode, no need to check\n if (this.needsRenewal) {\n return false;\n }\n\n const sessionId = this.get('sessionId');\n if (!sessionId) {\n return false;\n }\n\n const storedSession = this.loadStoredSession();\n if (!storedSession) {\n return false;\n }\n\n const sessionTimeout = this.get('config')?.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT;\n return Date.now() - storedSession.lastActivity > sessionTimeout;\n }\n\n private cleanupLifecycleListeners(): void {\n if (this.visibilityChangeHandler) {\n document.removeEventListener('visibilitychange', this.visibilityChangeHandler);\n this.visibilityChangeHandler = null;\n }\n }\n\n /**\n * Enters renewal mode after session timeout.\n * Keeps activity listeners active to detect when user returns.\n * Called by session timeout timer.\n */\n private enterRenewalMode(): void {\n this.clearSessionTimeout();\n this.cleanupCrossTabSync();\n this.clearStoredSession();\n\n this.set('sessionId', null);\n this.set('hasStartSession', false);\n this.set('sessionReferrer', undefined);\n this.set('sessionUtm', undefined);\n\n // Keep activity listeners active but switch to renewal mode\n this.needsRenewal = true;\n\n log('debug', 'Session timed out, entering renewal mode');\n }\n\n /**\n * Fully resets session state and cleans up all resources.\n * Called by stopTracking() for explicit session termination.\n */\n private resetSessionState(): void {\n this.clearSessionTimeout();\n this.cleanupActivityListeners();\n this.cleanupLifecycleListeners();\n this.cleanupCrossTabSync();\n this.clearStoredSession();\n\n this.set('sessionId', null);\n this.set('hasStartSession', false);\n this.set('sessionReferrer', undefined);\n this.set('sessionUtm', undefined);\n\n this.needsRenewal = false;\n this.isTracking = false;\n }\n\n /**\n * Stops session tracking and cleans up all resources.\n *\n * **Purpose**: Manually stops the current session tracking and cleans up\n * all listeners and timers. Does not emit SESSION_END event (removed in v2.0.0).\n *\n * **Flow**:\n * 1. Clears inactivity timeout\n * 2. Removes activity listeners (click, keydown, scroll)\n * 3. Removes lifecycle listeners (visibilitychange)\n * 4. Closes BroadcastChannel\n * 5. Clears session from localStorage\n * 6. Resets `sessionId` and `hasStartSession` in global state\n * 7. Sets `isTracking` to false\n *\n * **Called by**: `App.destroy()` during application teardown or when session times out\n *\n * **Important**: After calling, session tracking is terminated and cannot be resumed.\n * A new session will be created on next `startTracking()` call.\n *\n * @example\n * ```typescript\n * // Stop session tracking\n * sessionManager.stopTracking();\n * // → All listeners cleaned up\n * // → Session cleared from localStorage\n * // → BroadcastChannel closed\n * // → No SESSION_END event emitted\n * ```\n *\n * @see src/managers/README.md (lines 140-169) for session management details\n */\n stopTracking(): void {\n this.resetSessionState();\n }\n\n /**\n * Destroys the session manager and cleans up all resources.\n *\n * **Purpose**: Performs deep cleanup of session manager resources during\n * application teardown. Preserves session in localStorage for recovery.\n *\n * **Differences from stopTracking()**:\n * - Does NOT clear localStorage (preserves session for recovery)\n * - Used for internal cleanup during teardown\n *\n * **Cleanup Flow**:\n * 1. Clears inactivity timeout\n * 2. Removes activity listeners (click, keydown, scroll)\n * 3. Closes BroadcastChannel\n * 4. Removes lifecycle listeners (visibilitychange)\n * 5. Resets tracking flag (`isTracking`)\n *\n * **Called by**: `App.destroy()` during application teardown\n *\n * @returns void\n *\n * @example\n * ```typescript\n * sessionManager.destroy();\n * // → All resources cleaned up\n * // → Session preserved in localStorage for recovery\n * ```\n */\n destroy(): void {\n this.clearSessionTimeout();\n this.cleanupActivityListeners();\n this.cleanupCrossTabSync();\n this.cleanupLifecycleListeners();\n this.isTracking = false;\n this.needsRenewal = false;\n this.set('hasStartSession', false);\n }\n}\n","import { EventManager } from '../managers/event.manager';\nimport { SessionManager } from '../managers/session.manager';\nimport { StateManager } from '../managers/state.manager';\nimport { StorageManager } from '../managers/storage.manager';\nimport { log } from '../utils';\n\n/**\n * Wrapper around SessionManager providing consistent handler interface with robust error handling.\n *\n * **Purpose**: Manages user session lifecycle through delegation to SessionManager,\n * adding error recovery, state validation, and event buffer flushing.\n *\n * **Core Functionality**:\n * - Extracts projectId from config (tracelog/custom integrations or 'default')\n * - Creates SessionManager with storage, event manager, and projectId\n * - Flushes pending events after successful session initialization\n * - Automatic cleanup on initialization failures with nested try-catch\n *\n * **Key Features**:\n * - Idempotent operations (safe to call startTracking() multiple times)\n * - Double-destroy protection via destroyed flag\n * - State validation prevents operations on destroyed instances\n * - Centralized cleanup via cleanupSessionManager()\n *\n * **Lifecycle**:\n * - startTracking(): Creates session, sends SESSION_START event\n * - stopTracking(): Cleans up session tracking (no events emitted)\n * - destroy(): Same as stopTracking() (no events emitted in v2.0.0+)\n *\n * @example\n * ```typescript\n * const handler = new SessionHandler(storage, eventManager);\n * handler.startTracking(); // Creates session\n * handler.stopTracking(); // Ends session + cleanup\n * handler.destroy(); // Cleanup only\n * ```\n */\nexport class SessionHandler extends StateManager {\n private readonly eventManager: EventManager;\n private readonly storageManager: StorageManager;\n private sessionManager: SessionManager | null = null;\n private destroyed = false;\n\n constructor(storageManager: StorageManager, eventManager: EventManager) {\n super();\n this.eventManager = eventManager;\n this.storageManager = storageManager;\n }\n\n /**\n * Starts session tracking by creating SessionManager and initializing session.\n *\n * **Behavior**:\n * - Extracts projectId from config (tracelog projectId or custom collectApiUrl or 'default')\n * - Creates SessionManager instance with storage, event manager, and projectId\n * - Calls SessionManager.startTracking() to begin session lifecycle\n * - Flushes pending events buffered during initialization\n * - Idempotent: Early return if session already active\n * - Validates state: Warns and returns if handler destroyed\n *\n * **Error Handling**:\n * - On failure: Automatically cleans up SessionManager via nested try-catch\n * - Leaves handler in clean, reusable state after error\n * - Re-throws error after logging\n *\n * @throws {Error} If SessionManager initialization fails\n */\n startTracking(): void {\n if (this.isActive()) {\n return;\n }\n\n if (this.destroyed) {\n log('debug', 'Cannot start tracking on destroyed handler');\n return;\n }\n\n const config = this.get('config');\n const projectId = config?.integrations?.tracelog?.projectId ?? 'custom';\n\n try {\n this.sessionManager = new SessionManager(this.storageManager, this.eventManager, projectId);\n this.sessionManager.startTracking();\n\n this.eventManager.flushPendingEvents();\n } catch (error) {\n if (this.sessionManager) {\n try {\n this.sessionManager.destroy();\n } catch {\n /* empty */\n }\n this.sessionManager = null;\n }\n\n log('error', 'Failed to start session tracking', { error });\n throw error;\n }\n }\n\n private isActive(): boolean {\n return this.sessionManager !== null && !this.destroyed;\n }\n\n private cleanupSessionManager(): void {\n if (this.sessionManager) {\n this.sessionManager.stopTracking();\n this.sessionManager.destroy();\n this.sessionManager = null;\n }\n }\n\n /**\n * Stops session tracking by cleaning up resources.\n *\n * **Purpose**: Terminates session tracking and removes all listeners and timers.\n * No events are emitted (SESSION_END removed in v2.0.0).\n *\n * **Behavior**:\n * - Calls SessionManager.stopTracking() to clean up listeners\n * - Calls SessionManager.destroy() to finalize cleanup\n * - Safe to call multiple times (idempotent via cleanupSessionManager)\n *\n * **Note**: In v2.0.0+, this method only performs cleanup without emitting events.\n * Session end time is inferred server-side from last event timestamp.\n */\n stopTracking(): void {\n this.cleanupSessionManager();\n }\n\n /**\n * Destroys handler and cleans up SessionManager.\n *\n * **Purpose**: Same as stopTracking() in v2.0.0+. Both methods perform cleanup\n * without emitting events.\n *\n * **Behavior**:\n * - Idempotent: Early return if already destroyed\n * - Calls SessionManager.destroy() to clean up listeners and timers\n * - Sets sessionManager to null and destroyed flag to true\n *\n * **Note**: In v2.0.0+, there is no functional difference between stopTracking()\n * and destroy(). Both perform cleanup without emitting SESSION_END events.\n */\n destroy(): void {\n if (this.destroyed) {\n return;\n }\n\n if (this.sessionManager) {\n this.sessionManager.destroy();\n this.sessionManager = null;\n }\n\n this.destroyed = true;\n }\n}\n","import { EventType, PageViewData } from '../types';\nimport { normalizeUrl } from '../utils';\nimport { StateManager } from '../managers/state.manager';\nimport { EventManager } from '../managers/event.manager';\nimport { DEFAULT_PAGE_VIEW_THROTTLE_MS } from '../constants/config.constants';\n\n/**\n * Tracks page navigation and route changes in single-page applications.\n *\n * **Events Generated**: `page_view`\n *\n * **Features**:\n * - Tracks initial page load, browser navigation, hash changes, and History API calls\n * - URL normalization with automatic filtering of sensitive query parameters\n * - Deduplication to prevent consecutive duplicate events\n * - Configurable throttling (default: 1 second)\n * - SPA navigation detection via History API patching\n *\n * **Privacy Protection**:\n * - Automatically removes 15 common sensitive params (token, auth, key, password, etc.)\n * - User-configurable additional sensitive parameters via config\n *\n * @see src/handlers/README.md (lines 5-63) for detailed documentation\n */\nexport class PageViewHandler extends StateManager {\n private readonly eventManager: EventManager;\n private readonly onTrack: () => void;\n\n private originalPushState?: typeof window.history.pushState;\n private originalReplaceState?: typeof window.history.replaceState;\n private lastPageViewTime = 0;\n\n constructor(eventManager: EventManager, onTrack: () => void) {\n super();\n\n this.eventManager = eventManager;\n this.onTrack = onTrack;\n }\n\n /**\n * Starts tracking page views.\n *\n * - Tracks initial page load first (via trackInitialPageView)\n * - Attaches popstate and hashchange event listeners\n * - Patches History API methods (pushState, replaceState) for SPA navigation\n * - All setup happens synchronously\n *\n * **Note**: onTrack() callback is invoked AFTER initial page view but BEFORE\n * subsequent navigation events for session management coordination.\n */\n startTracking(): void {\n this.trackInitialPageView();\n\n window.addEventListener('popstate', this.trackCurrentPage, true);\n window.addEventListener('hashchange', this.trackCurrentPage, true);\n\n this.patchHistory('pushState');\n this.patchHistory('replaceState');\n }\n\n /**\n * Stops tracking page views and restores original History API methods.\n *\n * - Removes event listeners (popstate, hashchange)\n * - Restores original pushState and replaceState methods\n * - Resets throttling state\n */\n stopTracking(): void {\n window.removeEventListener('popstate', this.trackCurrentPage, true);\n window.removeEventListener('hashchange', this.trackCurrentPage, true);\n\n if (this.originalPushState) {\n window.history.pushState = this.originalPushState;\n }\n\n if (this.originalReplaceState) {\n window.history.replaceState = this.originalReplaceState;\n }\n\n this.lastPageViewTime = 0;\n }\n\n private patchHistory(method: 'pushState' | 'replaceState'): void {\n const original = window.history[method];\n\n if (method === 'pushState' && !this.originalPushState) {\n this.originalPushState = original;\n } else if (method === 'replaceState' && !this.originalReplaceState) {\n this.originalReplaceState = original;\n }\n\n window.history[method] = (...args: [unknown, string, string | URL | null | undefined]): void => {\n original.apply(window.history, args);\n this.trackCurrentPage();\n };\n }\n\n private readonly trackCurrentPage = (): void => {\n const rawUrl = window.location.href;\n const normalizedUrl = normalizeUrl(rawUrl, this.get('config').sensitiveQueryParams);\n\n if (this.get('pageUrl') === normalizedUrl) {\n return;\n }\n\n const now = Date.now();\n const throttleMs = this.get('config').pageViewThrottleMs ?? DEFAULT_PAGE_VIEW_THROTTLE_MS;\n\n if (now - this.lastPageViewTime < throttleMs) {\n return;\n }\n\n this.lastPageViewTime = now;\n\n this.onTrack();\n\n const fromUrl = this.get('pageUrl');\n\n this.set('pageUrl', normalizedUrl);\n\n const pageViewData = this.extractPageViewData();\n this.eventManager.track({\n type: EventType.PAGE_VIEW,\n page_url: this.get('pageUrl'),\n from_page_url: fromUrl,\n ...(pageViewData && { page_view: pageViewData }),\n });\n };\n\n private trackInitialPageView(): void {\n const normalizedUrl = normalizeUrl(window.location.href, this.get('config').sensitiveQueryParams);\n const pageViewData = this.extractPageViewData();\n\n this.lastPageViewTime = Date.now();\n\n this.eventManager.track({\n type: EventType.PAGE_VIEW,\n page_url: normalizedUrl,\n ...(pageViewData && { page_view: pageViewData }),\n });\n\n this.onTrack();\n }\n\n private extractPageViewData(): PageViewData | undefined {\n const { pathname, search, hash } = window.location;\n const { referrer } = document;\n const { title } = document;\n\n if (!referrer && !title && !pathname && !search && !hash) {\n return undefined;\n }\n\n const data: PageViewData = {\n ...(referrer && { referrer }),\n ...(title && { title }),\n ...(pathname && { pathname }),\n ...(search && { search }),\n ...(hash && { hash }),\n };\n\n return data;\n }\n}\n","import {\n HTML_DATA_ATTR_PREFIX,\n MAX_TEXT_LENGTH,\n INTERACTIVE_SELECTORS,\n PII_PATTERNS,\n DEFAULT_CLICK_THROTTLE_MS,\n MAX_THROTTLE_CACHE_ENTRIES,\n THROTTLE_ENTRY_TTL_MS,\n THROTTLE_PRUNE_INTERVAL_MS,\n} from '../constants';\nimport { ClickCoordinates, ClickData, ClickTrackingElementData, EventType } from '../types';\nimport { EventManager } from '../managers/event.manager';\nimport { StateManager } from '../managers/state.manager';\nimport { log } from '../utils';\n\n/**\n * Captures mouse clicks and converts them into analytics events with element context and coordinates.\n *\n * **Features**:\n * - Smart element detection via INTERACTIVE_SELECTORS (29 selectors including buttons, links, form elements, ARIA roles)\n * - Relative coordinates calculation (0-1 scale, 3 decimal precision, clamped)\n * - Custom event tracking via `data-tlog-name` attributes\n * - Text extraction with length limits (255 chars max) and priority logic\n * - PII sanitization (emails, phone numbers, credit cards, API keys, tokens)\n * - Privacy controls via `data-tlog-ignore` attribute\n * - Per-element click throttling (default 300ms) with memory management (TTL + LRU)\n *\n * **Events Generated**: `click`, `custom` (for elements with data-tlog-name)\n *\n * **Triggers**: Capture-phase click events on document\n *\n * **Memory Management**:\n * - TTL-based pruning: 5-minute TTL with automatic cleanup\n * - LRU eviction: Maintains maximum 1000 throttle entries\n * - Rate-limited pruning: Runs every 30 seconds\n *\n * @example\n * ```typescript\n * const handler = new ClickHandler(eventManager);\n * handler.startTracking();\n * // Clicks are now tracked automatically\n * handler.stopTracking();\n * ```\n */\nexport class ClickHandler extends StateManager {\n private readonly eventManager: EventManager;\n private readonly lastClickTimes: Map<string, number> = new Map();\n private clickHandler?: (event: Event) => void;\n private lastPruneTime = 0;\n\n constructor(eventManager: EventManager) {\n super();\n\n this.eventManager = eventManager;\n }\n\n /**\n * Starts tracking click events on the document.\n *\n * Attaches a single capture-phase click listener to window that:\n * - Detects interactive elements or falls back to clicked element\n * - Applies click throttling per element (configurable, default 300ms)\n * - Extracts custom tracking data from data-tlog-name attributes\n * - Generates both custom events (for tracked elements) and click events\n * - Respects data-tlog-ignore privacy controls\n * - Sanitizes text content for PII protection\n *\n * Idempotent: Safe to call multiple times (early return if already tracking).\n */\n startTracking(): void {\n if (this.clickHandler) {\n return;\n }\n\n this.clickHandler = (event: Event): void => {\n const mouseEvent = event as MouseEvent;\n const target = mouseEvent.target;\n const clickedElement =\n typeof HTMLElement !== 'undefined' && target instanceof HTMLElement\n ? target\n : typeof HTMLElement !== 'undefined' && target instanceof Node && target.parentElement instanceof HTMLElement\n ? target.parentElement\n : null;\n\n if (!clickedElement) {\n log('debug', 'Click target not found or not an element');\n return;\n }\n\n if (this.shouldIgnoreElement(clickedElement)) {\n return;\n }\n\n // Throttle clicks per element to prevent double-clicks and spam\n const clickThrottleMs = this.get('config')?.clickThrottleMs ?? DEFAULT_CLICK_THROTTLE_MS;\n if (clickThrottleMs > 0 && !this.checkClickThrottle(clickedElement, clickThrottleMs)) {\n return;\n }\n\n const trackingElement = this.findTrackingElement(clickedElement);\n const relevantClickElement = this.getRelevantClickElement(clickedElement);\n const coordinates = this.calculateClickCoordinates(mouseEvent, clickedElement);\n\n if (trackingElement) {\n const trackingData = this.extractTrackingData(trackingElement);\n\n if (trackingData) {\n const attributeData = this.createCustomEventData(trackingData);\n\n this.eventManager.track({\n type: EventType.CUSTOM,\n custom_event: {\n name: attributeData.name,\n ...(attributeData.value && { metadata: { value: attributeData.value } }),\n },\n });\n }\n }\n\n const clickData = this.generateClickData(clickedElement, relevantClickElement, coordinates);\n\n this.eventManager.track({\n type: EventType.CLICK,\n click_data: clickData,\n });\n };\n\n window.addEventListener('click', this.clickHandler, true);\n }\n\n /**\n * Stops tracking click events and cleans up resources.\n *\n * Removes the click event listener, clears throttle cache, and resets prune timer.\n * Prevents memory leaks by properly cleaning up all state.\n */\n stopTracking(): void {\n if (this.clickHandler) {\n window.removeEventListener('click', this.clickHandler, true);\n this.clickHandler = undefined;\n }\n this.lastClickTimes.clear();\n this.lastPruneTime = 0;\n }\n\n private shouldIgnoreElement(element: HTMLElement): boolean {\n if (element.hasAttribute(`${HTML_DATA_ATTR_PREFIX}-ignore`)) {\n return true;\n }\n\n const parent = element.closest(`[${HTML_DATA_ATTR_PREFIX}-ignore]`);\n\n return parent !== null;\n }\n\n /**\n * Checks per-element click throttling to prevent double-clicks and rapid spam\n * Returns true if the click should be tracked, false if throttled\n */\n private checkClickThrottle(element: HTMLElement, throttleMs: number): boolean {\n const signature = this.getElementSignature(element);\n const now = Date.now();\n\n this.pruneThrottleCache(now);\n\n const lastClickTime = this.lastClickTimes.get(signature);\n\n if (lastClickTime !== undefined && now - lastClickTime < throttleMs) {\n log('debug', 'ClickHandler: Click suppressed by throttle', {\n data: {\n signature,\n throttleRemaining: throttleMs - (now - lastClickTime),\n },\n });\n return false;\n }\n\n this.lastClickTimes.set(signature, now);\n return true;\n }\n\n /**\n * Prunes stale entries from the throttle cache to prevent memory leaks\n * Uses TTL-based eviction (5 minutes) and enforces max size limit\n * Called during checkClickThrottle with built-in rate limiting (every 30 seconds)\n */\n private pruneThrottleCache(now: number): void {\n if (now - this.lastPruneTime < THROTTLE_PRUNE_INTERVAL_MS) {\n return;\n }\n\n this.lastPruneTime = now;\n const cutoff = now - THROTTLE_ENTRY_TTL_MS;\n\n for (const [key, timestamp] of this.lastClickTimes.entries()) {\n if (timestamp < cutoff) {\n this.lastClickTimes.delete(key);\n }\n }\n\n if (this.lastClickTimes.size > MAX_THROTTLE_CACHE_ENTRIES) {\n const entries = Array.from(this.lastClickTimes.entries()).sort((a, b) => a[1] - b[1]);\n\n const excessCount = this.lastClickTimes.size - MAX_THROTTLE_CACHE_ENTRIES;\n const toDelete = entries.slice(0, excessCount);\n\n for (const [key] of toDelete) {\n this.lastClickTimes.delete(key);\n }\n\n log('debug', 'ClickHandler: Pruned throttle cache', {\n data: {\n removed: toDelete.length,\n remaining: this.lastClickTimes.size,\n },\n });\n }\n }\n\n /**\n * Creates a stable signature for an element to track throttling\n * Priority: id > data-testid > data-tlog-name > DOM path\n */\n private getElementSignature(element: HTMLElement): string {\n if (element.id) {\n return `#${element.id}`;\n }\n\n const testId = element.getAttribute('data-testid');\n if (testId) {\n return `[data-testid=\"${testId}\"]`;\n }\n\n const tlogName = element.getAttribute(`${HTML_DATA_ATTR_PREFIX}-name`);\n if (tlogName) {\n return `[${HTML_DATA_ATTR_PREFIX}-name=\"${tlogName}\"]`;\n }\n\n return this.getElementPath(element);\n }\n\n /**\n * Generates a DOM path for an element (e.g., \"body>div>button\")\n */\n private getElementPath(element: HTMLElement): string {\n const path: string[] = [];\n let current: HTMLElement | null = element;\n\n while (current && current !== document.body) {\n let selector = current.tagName.toLowerCase();\n\n if (current.className) {\n const firstClass = current.className.split(' ')[0];\n if (firstClass) {\n selector += `.${firstClass}`;\n }\n }\n\n path.unshift(selector);\n current = current.parentElement;\n }\n\n return path.join('>') || 'unknown';\n }\n\n private findTrackingElement(element: HTMLElement): HTMLElement | undefined {\n if (element.hasAttribute(`${HTML_DATA_ATTR_PREFIX}-name`)) {\n return element;\n }\n\n const closest = element.closest(`[${HTML_DATA_ATTR_PREFIX}-name]`) as HTMLElement;\n\n return closest;\n }\n\n private getRelevantClickElement(element: HTMLElement): HTMLElement {\n for (const selector of INTERACTIVE_SELECTORS) {\n try {\n if (element.matches(selector)) {\n return element;\n }\n\n const parent = element.closest(selector) as HTMLElement;\n\n if (parent) {\n return parent;\n }\n } catch (error) {\n log('debug', 'Invalid selector in element search', { error, data: { selector } });\n continue;\n }\n }\n\n return element;\n }\n\n /**\n * Clamps relative coordinate values to [0, 1] range with 3 decimal precision.\n *\n * @param value - Raw relative coordinate value\n * @returns Clamped value between 0 and 1 with 3 decimal places (e.g., 0.123)\n *\n * @example\n * clamp(1.234) // returns 1.000\n * clamp(0.12345) // returns 0.123\n * clamp(-0.5) // returns 0.000\n */\n private clamp(value: number): number {\n return Math.max(0, Math.min(1, Number(value.toFixed(3))));\n }\n\n private calculateClickCoordinates(event: MouseEvent, element: HTMLElement): ClickCoordinates {\n const rect = element.getBoundingClientRect();\n const x = event.clientX;\n const y = event.clientY;\n const relativeX = rect.width > 0 ? this.clamp((x - rect.left) / rect.width) : 0;\n const relativeY = rect.height > 0 ? this.clamp((y - rect.top) / rect.height) : 0;\n\n return { x, y, relativeX, relativeY };\n }\n\n private extractTrackingData(trackingElement: HTMLElement): ClickTrackingElementData | undefined {\n const name = trackingElement.getAttribute(`${HTML_DATA_ATTR_PREFIX}-name`);\n const value = trackingElement.getAttribute(`${HTML_DATA_ATTR_PREFIX}-value`);\n\n if (!name) {\n return undefined;\n }\n\n return {\n element: trackingElement,\n name,\n ...(value && { value }),\n };\n }\n\n private generateClickData(\n clickedElement: HTMLElement,\n relevantElement: HTMLElement,\n coordinates: ClickCoordinates,\n ): ClickData {\n const { x, y, relativeX, relativeY } = coordinates;\n const text = this.getRelevantText(clickedElement, relevantElement);\n const attributes = this.extractElementAttributes(relevantElement);\n\n return {\n x,\n y,\n relativeX,\n relativeY,\n tag: relevantElement.tagName.toLowerCase(),\n ...(relevantElement.id && { id: relevantElement.id }),\n ...(relevantElement.className && { class: relevantElement.className }),\n ...(text && { text }),\n ...(attributes.href && { href: attributes.href }),\n ...(attributes.title && { title: attributes.title }),\n ...(attributes.alt && { alt: attributes.alt }),\n ...(attributes.role && { role: attributes.role }),\n ...(attributes['aria-label'] && { ariaLabel: attributes['aria-label'] }),\n ...(Object.keys(attributes).length > 0 && { dataAttributes: attributes }),\n };\n }\n\n /**\n * Sanitizes text by replacing PII patterns with [REDACTED].\n *\n * Protects against:\n * - Email addresses\n * - Phone numbers (US format)\n * - Credit card numbers\n * - IBAN numbers\n * - API keys/tokens\n * - Bearer tokens\n *\n * @param text - Raw text content from element\n * @returns Sanitized text with PII replaced by [REDACTED]\n *\n * @example\n * sanitizeText('Email: user@example.com') // returns 'Email: [REDACTED]'\n * sanitizeText('Card: 1234-5678-9012-3456') // returns 'Card: [REDACTED]'\n * sanitizeText('Bearer token123') // returns '[REDACTED]'\n */\n private sanitizeText(text: string): string {\n let sanitized = text;\n\n for (const pattern of PII_PATTERNS) {\n const regex = new RegExp(pattern.source, pattern.flags);\n sanitized = sanitized.replace(regex, '[REDACTED]');\n }\n\n return sanitized;\n }\n\n private getRelevantText(clickedElement: HTMLElement, relevantElement: HTMLElement): string {\n const clickedText = clickedElement.textContent?.trim() ?? '';\n const relevantText = relevantElement.textContent?.trim() ?? '';\n\n if (!clickedText && !relevantText) {\n return '';\n }\n\n let finalText = '';\n\n if (clickedText && clickedText.length <= MAX_TEXT_LENGTH) {\n finalText = clickedText;\n } else if (relevantText.length <= MAX_TEXT_LENGTH) {\n finalText = relevantText;\n } else {\n finalText = relevantText.slice(0, MAX_TEXT_LENGTH - 3) + '...';\n }\n\n return this.sanitizeText(finalText);\n }\n\n private extractElementAttributes(element: HTMLElement): Record<string, string> {\n const commonAttributes = [\n 'id',\n 'class',\n 'data-testid',\n 'aria-label',\n 'title',\n 'href',\n 'type',\n 'name',\n 'alt',\n 'role',\n ];\n const result: Record<string, string> = {};\n\n for (const attributeName of commonAttributes) {\n const value = element.getAttribute(attributeName);\n\n if (value) {\n result[attributeName] = value;\n }\n }\n\n return result;\n }\n\n private createCustomEventData(trackingData: ClickTrackingElementData): { name: string; value?: string } {\n return {\n name: trackingData.name,\n ...(trackingData.value && { value: trackingData.value }),\n };\n }\n}\n","import {\n MAX_SCROLL_EVENTS_PER_SESSION,\n MIN_SCROLL_DEPTH_CHANGE,\n SCROLL_DEBOUNCE_TIME_MS,\n SCROLL_MIN_EVENT_INTERVAL_MS,\n SIGNIFICANT_SCROLL_DELTA,\n} from '../constants';\nimport { EventType, ScrollData, ScrollDirection } from '../types';\nimport { EventManager } from '../managers/event.manager';\nimport { StateManager } from '../managers/state.manager';\nimport { log } from '../utils';\n\ninterface ScrollContainer {\n element: Window | HTMLElement;\n selector: string;\n isPrimary: boolean;\n lastScrollPos: number;\n lastDepth: number;\n lastDirection: ScrollDirection;\n lastEventTime: number;\n firstScrollEventTime: number | null;\n maxDepthReached: number;\n debounceTimer: number | null;\n listener: EventListener;\n}\n\n/**\n * Tracks scroll depth, direction, velocity, and container identification across multiple scrollable elements.\n *\n * **Features**:\n * - Automatic container detection with intelligent retry (5 attempts @ 200ms intervals)\n * - Manual override via primaryScrollSelector config\n * - Smart filtering with multiple guardrails:\n * - Visibility check (element must be connected to DOM with dimensions)\n * - Scrollability check (content must overflow container)\n * - Significant movement (minimum 10px position delta)\n * - Depth change (minimum 5% depth change between events)\n * - Rate limiting (minimum 500ms interval between events per container)\n * - Session cap (maximum 120 events per session with single warning)\n * - Multi-container support with per-container debouncing (250ms)\n * - Velocity calculation for engagement analysis\n * - Max depth tracking per session\n * - Primary vs secondary container classification\n *\n * **Events Generated**: `scroll`\n *\n * **Analytics Fields**:\n * - depth: Current scroll depth (0-100%)\n * - direction: 'up' | 'down'\n * - container_selector: CSS selector or 'window'\n * - is_primary: Boolean indicating main scroll container\n * - velocity: Scroll speed in px/s\n * - max_depth_reached: Peak engagement per container\n *\n * **Container Detection**:\n * - Uses TreeWalker for performance\n * - Pre-filters elements with overflow: auto/scroll CSS properties\n * - Validates visibility and scrollability\n * - Retries up to 5 times for dynamically loaded content (SPAs)\n * - Falls back to window-only if no containers found\n *\n * @example\n * ```typescript\n * const handler = new ScrollHandler(eventManager);\n * handler.startTracking();\n * // Automatically detects and tracks scrollable containers\n * handler.stopTracking();\n * ```\n */\nexport class ScrollHandler extends StateManager {\n private readonly eventManager: EventManager;\n private readonly containers: ScrollContainer[] = [];\n private limitWarningLogged = false;\n private minDepthChange = MIN_SCROLL_DEPTH_CHANGE;\n private minIntervalMs = SCROLL_MIN_EVENT_INTERVAL_MS;\n private maxEventsPerSession = MAX_SCROLL_EVENTS_PER_SESSION;\n private containerDiscoveryTimeoutId: number | null = null;\n\n constructor(eventManager: EventManager) {\n super();\n\n this.eventManager = eventManager;\n }\n\n /**\n * Starts tracking scroll events across all detected scrollable containers.\n *\n * Automatically detects scrollable containers using TreeWalker with retry logic:\n * - Searches DOM for elements with overflow: auto/scroll\n * - Validates visibility and scrollability\n * - Retries up to 5 times with 200ms intervals for dynamic content\n * - Falls back to window-only tracking if no containers found\n * - Applies primaryScrollSelector config override if provided\n *\n * Attaches debounced scroll listeners (250ms per container) with smart filtering:\n * - Significant movement (10px minimum)\n * - Depth change (5% minimum)\n * - Rate limiting (500ms minimum interval)\n * - Session cap (120 events maximum)\n */\n startTracking(): void {\n this.limitWarningLogged = false;\n this.applyConfigOverrides();\n this.set('scrollEventCount', 0);\n this.tryDetectScrollContainers(0);\n }\n\n /**\n * Stops tracking scroll events and cleans up resources.\n *\n * Removes all scroll event listeners, clears debounce timers, cancels retry attempts,\n * and resets session state (event counter, warning flags). Prevents memory leaks by\n * properly cleaning up all containers and timers.\n */\n stopTracking(): void {\n if (this.containerDiscoveryTimeoutId !== null) {\n clearTimeout(this.containerDiscoveryTimeoutId);\n this.containerDiscoveryTimeoutId = null;\n }\n\n for (const container of this.containers) {\n this.clearContainerTimer(container);\n\n if (container.element === window) {\n window.removeEventListener('scroll', container.listener);\n } else {\n (container.element as HTMLElement).removeEventListener('scroll', container.listener);\n }\n }\n\n this.containers.length = 0;\n this.set('scrollEventCount', 0);\n this.limitWarningLogged = false;\n }\n\n private tryDetectScrollContainers(attempt: number): void {\n const elements = this.findScrollableElements();\n\n if (this.isWindowScrollable()) {\n this.setupScrollContainer(window, 'window');\n }\n\n if (elements.length > 0) {\n for (const element of elements) {\n const selector = this.getElementSelector(element);\n this.setupScrollContainer(element, selector);\n }\n\n this.applyPrimaryScrollSelectorIfConfigured();\n\n return;\n }\n\n if (attempt < 5) {\n this.containerDiscoveryTimeoutId = window.setTimeout(() => {\n this.containerDiscoveryTimeoutId = null;\n this.tryDetectScrollContainers(attempt + 1);\n }, 200);\n\n return;\n }\n\n if (this.containers.length === 0) {\n this.setupScrollContainer(window, 'window');\n }\n\n this.applyPrimaryScrollSelectorIfConfigured();\n }\n\n private applyPrimaryScrollSelectorIfConfigured(): void {\n const config = this.get('config');\n\n if (config?.primaryScrollSelector) {\n this.applyPrimaryScrollSelector(config.primaryScrollSelector);\n }\n }\n\n private findScrollableElements(): HTMLElement[] {\n if (!document.body) {\n return [];\n }\n\n const elements: HTMLElement[] = [];\n\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, {\n acceptNode: (node) => {\n const element = node as HTMLElement;\n\n if (!element.isConnected || !element.offsetParent) {\n return NodeFilter.FILTER_SKIP;\n }\n\n const style = getComputedStyle(element);\n\n const hasVerticalScrollableStyle =\n style.overflowY === 'auto' ||\n style.overflowY === 'scroll' ||\n style.overflow === 'auto' ||\n style.overflow === 'scroll';\n\n return hasVerticalScrollableStyle ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;\n },\n });\n\n let node: Node | null;\n\n while ((node = walker.nextNode()) && elements.length < 10) {\n const element = node as HTMLElement;\n\n if (this.isElementScrollable(element)) {\n elements.push(element);\n }\n }\n\n return elements;\n }\n\n private getElementSelector(element: Window | HTMLElement): string {\n if (element === window) {\n return 'window';\n }\n\n const htmlElement = element as HTMLElement;\n\n if (htmlElement.id) {\n return `#${htmlElement.id}`;\n }\n\n if (htmlElement.className && typeof htmlElement.className === 'string') {\n const firstClass = htmlElement.className.split(' ').filter((c) => c.trim())[0];\n\n if (firstClass) {\n return `.${firstClass}`;\n }\n }\n\n return htmlElement.tagName.toLowerCase();\n }\n\n private determineIfPrimary(element: Window | HTMLElement): boolean {\n // Window scrollable → window is primary\n if (this.isWindowScrollable()) {\n return element === window;\n }\n\n // Window not scrollable → first detected container is primary\n return this.containers.length === 0;\n }\n\n private setupScrollContainer(element: Window | HTMLElement, selector: string): void {\n const alreadyTracking = this.containers.some((c) => c.element === element);\n\n if (alreadyTracking) {\n return;\n }\n\n if (element !== window && !this.isElementScrollable(element as HTMLElement)) {\n return;\n }\n\n const initialScrollTop = this.getScrollTop(element);\n\n const initialDepth = this.calculateScrollDepth(\n initialScrollTop,\n this.getScrollHeight(element),\n this.getViewportHeight(element),\n );\n\n const isPrimary = this.determineIfPrimary(element);\n\n const container: ScrollContainer = {\n element,\n selector,\n isPrimary,\n lastScrollPos: initialScrollTop,\n lastDepth: initialDepth,\n lastDirection: ScrollDirection.DOWN,\n lastEventTime: 0,\n firstScrollEventTime: null,\n maxDepthReached: initialDepth,\n debounceTimer: null,\n listener: null as unknown as EventListener,\n };\n\n const handleScroll = (): void => {\n if (this.get('suppressNextScroll')) {\n return;\n }\n\n if (container.firstScrollEventTime === null) {\n container.firstScrollEventTime = Date.now();\n }\n\n this.clearContainerTimer(container);\n\n container.debounceTimer = window.setTimeout(() => {\n const scrollData = this.calculateScrollData(container);\n\n if (scrollData) {\n const now = Date.now();\n\n this.processScrollEvent(container, scrollData, now);\n }\n\n container.debounceTimer = null;\n }, SCROLL_DEBOUNCE_TIME_MS);\n };\n\n container.listener = handleScroll;\n\n this.containers.push(container);\n\n if (element === window) {\n window.addEventListener('scroll', handleScroll, { passive: true });\n } else {\n (element as HTMLElement).addEventListener('scroll', handleScroll, { passive: true });\n }\n }\n\n private processScrollEvent(\n container: ScrollContainer,\n scrollData: Omit<ScrollData, 'container_selector' | 'is_primary'>,\n timestamp: number,\n ): void {\n if (!this.shouldEmitScrollEvent(container, scrollData, timestamp)) {\n return;\n }\n\n container.lastEventTime = timestamp;\n container.lastDepth = scrollData.depth;\n container.lastDirection = scrollData.direction;\n\n const currentCount = this.get('scrollEventCount') ?? 0;\n this.set('scrollEventCount', currentCount + 1);\n\n this.eventManager.track({\n type: EventType.SCROLL,\n scroll_data: {\n ...scrollData,\n container_selector: container.selector,\n is_primary: container.isPrimary,\n },\n });\n }\n\n private shouldEmitScrollEvent(\n container: ScrollContainer,\n scrollData: Omit<ScrollData, 'container_selector' | 'is_primary'>,\n timestamp: number,\n ): boolean {\n if (this.hasReachedSessionLimit()) {\n this.logLimitOnce();\n return false;\n }\n\n if (!this.hasElapsedMinimumInterval(container, timestamp)) {\n return false;\n }\n\n if (!this.hasSignificantDepthChange(container, scrollData.depth)) {\n return false;\n }\n\n return true;\n }\n\n private hasReachedSessionLimit(): boolean {\n const currentCount = this.get('scrollEventCount') ?? 0;\n return currentCount >= this.maxEventsPerSession;\n }\n\n private hasElapsedMinimumInterval(container: ScrollContainer, timestamp: number): boolean {\n if (container.lastEventTime === 0) {\n return true;\n }\n return timestamp - container.lastEventTime >= this.minIntervalMs;\n }\n\n private hasSignificantDepthChange(container: ScrollContainer, newDepth: number): boolean {\n return Math.abs(newDepth - container.lastDepth) >= this.minDepthChange;\n }\n\n private logLimitOnce(): void {\n if (this.limitWarningLogged) {\n return;\n }\n\n this.limitWarningLogged = true;\n\n log('debug', 'Max scroll events per session reached', {\n data: { limit: this.maxEventsPerSession },\n });\n }\n\n private applyConfigOverrides(): void {\n this.minDepthChange = MIN_SCROLL_DEPTH_CHANGE;\n this.minIntervalMs = SCROLL_MIN_EVENT_INTERVAL_MS;\n this.maxEventsPerSession = MAX_SCROLL_EVENTS_PER_SESSION;\n }\n\n private isWindowScrollable(): boolean {\n return document.documentElement.scrollHeight > window.innerHeight;\n }\n\n private clearContainerTimer(container: ScrollContainer): void {\n if (container.debounceTimer !== null) {\n clearTimeout(container.debounceTimer);\n container.debounceTimer = null;\n }\n }\n\n private getScrollDirection(current: number, previous: number): ScrollDirection {\n return current > previous ? ScrollDirection.DOWN : ScrollDirection.UP;\n }\n\n private calculateScrollDepth(scrollTop: number, scrollHeight: number, viewportHeight: number): number {\n if (scrollHeight <= viewportHeight) {\n return 0;\n }\n\n const maxScrollTop = scrollHeight - viewportHeight;\n return Math.min(100, Math.max(0, Math.floor((scrollTop / maxScrollTop) * 100)));\n }\n\n private calculateScrollData(\n container: ScrollContainer,\n ): Omit<ScrollData, 'container_selector' | 'is_primary'> | null {\n const { element, lastScrollPos, lastEventTime } = container;\n const scrollTop = this.getScrollTop(element);\n const now = Date.now();\n\n const positionDelta = Math.abs(scrollTop - lastScrollPos);\n if (positionDelta < SIGNIFICANT_SCROLL_DELTA) {\n return null;\n }\n\n if (element === window && !this.isWindowScrollable()) {\n return null;\n }\n\n const viewportHeight = this.getViewportHeight(element);\n const scrollHeight = this.getScrollHeight(element);\n const direction = this.getScrollDirection(scrollTop, lastScrollPos);\n const depth = this.calculateScrollDepth(scrollTop, scrollHeight, viewportHeight);\n\n let timeDelta: number;\n\n if (lastEventTime > 0) {\n timeDelta = now - lastEventTime;\n } else if (container.firstScrollEventTime !== null) {\n timeDelta = now - container.firstScrollEventTime;\n } else {\n timeDelta = SCROLL_DEBOUNCE_TIME_MS;\n }\n\n const velocity = Math.round((positionDelta / timeDelta) * 1000);\n\n if (depth > container.maxDepthReached) {\n container.maxDepthReached = depth;\n }\n\n container.lastScrollPos = scrollTop;\n\n return {\n depth,\n direction,\n velocity,\n max_depth_reached: container.maxDepthReached,\n };\n }\n\n private getScrollTop(element: Window | HTMLElement): number {\n return element === window ? window.scrollY : (element as HTMLElement).scrollTop;\n }\n\n private getViewportHeight(element: Window | HTMLElement): number {\n return element === window ? window.innerHeight : (element as HTMLElement).clientHeight;\n }\n\n private getScrollHeight(element: Window | HTMLElement): number {\n return element === window ? document.documentElement.scrollHeight : (element as HTMLElement).scrollHeight;\n }\n\n private isElementScrollable(element: HTMLElement): boolean {\n const style = getComputedStyle(element);\n\n const hasVerticalScrollableOverflow =\n style.overflowY === 'auto' ||\n style.overflowY === 'scroll' ||\n style.overflow === 'auto' ||\n style.overflow === 'scroll';\n\n const hasVerticalOverflowContent = element.scrollHeight > element.clientHeight;\n\n return hasVerticalScrollableOverflow && hasVerticalOverflowContent;\n }\n\n private applyPrimaryScrollSelector(selector: string): void {\n let targetElement: Window | HTMLElement;\n\n if (selector === 'window') {\n targetElement = window;\n } else {\n const element = document.querySelector(selector);\n if (!(element instanceof HTMLElement)) {\n log('debug', `Selector \"${selector}\" did not match an HTMLElement`);\n return;\n }\n targetElement = element;\n }\n\n this.containers.forEach((container) => {\n this.updateContainerPrimary(container, container.element === targetElement);\n });\n\n const targetAlreadyTracked = this.containers.some((c) => c.element === targetElement);\n if (!targetAlreadyTracked && targetElement instanceof HTMLElement) {\n if (this.isElementScrollable(targetElement)) {\n this.setupScrollContainer(targetElement, selector);\n }\n }\n }\n\n private updateContainerPrimary(container: ScrollContainer, isPrimary: boolean): void {\n container.isPrimary = isPrimary;\n }\n}\n","import {\n HTML_DATA_ATTR_PREFIX,\n DEFAULT_VIEWPORT_COOLDOWN_PERIOD,\n DEFAULT_VIEWPORT_MAX_TRACKED_ELEMENTS,\n VIEWPORT_MUTATION_DEBOUNCE_MS,\n} from '../constants';\nimport { EventManager } from '../managers/event.manager';\nimport { StateManager } from '../managers/state.manager';\nimport { EventType, ViewportConfig, ViewportEventData } from '../types';\nimport { log } from '../utils';\n\ninterface TrackedElement {\n element: Element;\n selector: string;\n id?: string;\n name?: string;\n startTime: number | null;\n timeoutId: number | null;\n lastFiredTime: number | null;\n}\n\n/**\n * Handles viewport visibility tracking using IntersectionObserver API.\n * Fires events when elements become visible for a minimum dwell time.\n */\nexport class ViewportHandler extends StateManager {\n private readonly eventManager: EventManager;\n private readonly trackedElements = new Map<Element, TrackedElement>();\n private observer: IntersectionObserver | null = null;\n private mutationObserver: MutationObserver | null = null;\n private mutationDebounceTimer: number | null = null;\n private config: ViewportConfig | null = null;\n\n constructor(eventManager: EventManager) {\n super();\n this.eventManager = eventManager;\n }\n\n /**\n * Starts tracking viewport visibility for configured elements\n */\n startTracking(): void {\n // Get viewport config from state\n const config = this.get('config');\n this.config = config.viewport ?? null;\n\n if (!this.config?.elements || this.config.elements.length === 0) {\n return;\n }\n\n const threshold = this.config.threshold ?? 0.5;\n const minDwellTime = this.config.minDwellTime ?? 1000;\n\n if (threshold < 0 || threshold > 1) {\n log('debug', 'ViewportHandler: Invalid threshold, must be between 0 and 1');\n return;\n }\n\n if (minDwellTime < 0) {\n log('debug', 'ViewportHandler: Invalid minDwellTime, must be non-negative');\n return;\n }\n\n if (typeof IntersectionObserver === 'undefined') {\n log('debug', 'ViewportHandler: IntersectionObserver not supported in this browser');\n return;\n }\n\n this.observer = new IntersectionObserver(this.handleIntersection, {\n threshold,\n });\n\n this.observeElements();\n\n this.setupMutationObserver();\n }\n\n /**\n * Stops tracking and cleans up resources\n */\n stopTracking(): void {\n if (this.observer) {\n this.observer.disconnect();\n this.observer = null;\n }\n\n if (this.mutationObserver) {\n this.mutationObserver.disconnect();\n this.mutationObserver = null;\n }\n\n if (this.mutationDebounceTimer !== null) {\n window.clearTimeout(this.mutationDebounceTimer);\n this.mutationDebounceTimer = null;\n }\n\n for (const tracked of this.trackedElements.values()) {\n if (tracked.timeoutId !== null) {\n window.clearTimeout(tracked.timeoutId);\n }\n }\n\n this.trackedElements.clear();\n }\n\n /**\n * Query and observe all elements matching configured elements\n */\n private observeElements(): void {\n if (!this.config || !this.observer) return;\n\n const maxTrackedElements = this.config.maxTrackedElements ?? DEFAULT_VIEWPORT_MAX_TRACKED_ELEMENTS;\n let totalTracked = this.trackedElements.size;\n\n for (const elementConfig of this.config.elements) {\n try {\n const elements = document.querySelectorAll(elementConfig.selector);\n\n for (const element of Array.from(elements)) {\n if (totalTracked >= maxTrackedElements) {\n log('debug', 'ViewportHandler: Maximum tracked elements reached', {\n data: {\n limit: maxTrackedElements,\n selector: elementConfig.selector,\n message: 'Some elements will not be tracked. Consider more specific selectors.',\n },\n });\n return;\n }\n\n if (element.hasAttribute(`${HTML_DATA_ATTR_PREFIX}-ignore`)) {\n continue;\n }\n\n if (this.trackedElements.has(element)) {\n continue;\n }\n\n this.trackedElements.set(element, {\n element,\n selector: elementConfig.selector,\n id: elementConfig.id,\n name: elementConfig.name,\n startTime: null,\n timeoutId: null,\n lastFiredTime: null,\n });\n\n this.observer?.observe(element);\n totalTracked++;\n }\n } catch (error) {\n log('debug', `ViewportHandler: Invalid selector \"${elementConfig.selector}\"`, { error });\n }\n }\n\n log('debug', 'ViewportHandler: Elements tracked', {\n data: { count: totalTracked, limit: maxTrackedElements },\n });\n }\n\n /**\n * Handles intersection events from IntersectionObserver\n */\n private readonly handleIntersection = (entries: IntersectionObserverEntry[]): void => {\n if (!this.config) return;\n\n const minDwellTime = this.config.minDwellTime ?? 1000;\n\n for (const entry of entries) {\n const tracked = this.trackedElements.get(entry.target);\n if (!tracked) continue;\n\n if (entry.isIntersecting) {\n if (tracked.startTime === null) {\n tracked.startTime = performance.now();\n\n tracked.timeoutId = window.setTimeout(() => {\n const visibilityRatio = Math.round(entry.intersectionRatio * 100) / 100;\n this.fireViewportEvent(tracked, visibilityRatio);\n }, minDwellTime);\n }\n } else {\n if (tracked.startTime !== null) {\n if (tracked.timeoutId !== null) {\n window.clearTimeout(tracked.timeoutId);\n tracked.timeoutId = null;\n }\n tracked.startTime = null;\n }\n }\n }\n };\n\n /**\n * Fires a viewport visible event\n */\n private fireViewportEvent(tracked: TrackedElement, visibilityRatio: number): void {\n if (tracked.startTime === null) return;\n\n const dwellTime = Math.round(performance.now() - tracked.startTime);\n\n if (tracked.element.hasAttribute(`${HTML_DATA_ATTR_PREFIX}-ignore`)) {\n return;\n }\n\n const cooldownPeriod = this.config?.cooldownPeriod ?? DEFAULT_VIEWPORT_COOLDOWN_PERIOD;\n const now = Date.now();\n if (tracked.lastFiredTime !== null && now - tracked.lastFiredTime < cooldownPeriod) {\n log('debug', 'ViewportHandler: Event suppressed by cooldown period', {\n data: {\n selector: tracked.selector,\n cooldownRemaining: cooldownPeriod - (now - tracked.lastFiredTime),\n },\n });\n tracked.startTime = null;\n tracked.timeoutId = null;\n return;\n }\n\n const eventData: ViewportEventData = {\n selector: tracked.selector,\n dwellTime,\n visibilityRatio,\n ...(tracked.id !== undefined && { id: tracked.id }),\n ...(tracked.name !== undefined && { name: tracked.name }),\n };\n\n this.eventManager.track({\n type: EventType.VIEWPORT_VISIBLE,\n viewport_data: eventData,\n });\n\n tracked.startTime = null;\n tracked.timeoutId = null;\n tracked.lastFiredTime = now;\n }\n\n /**\n * Sets up MutationObserver to detect dynamically added elements\n */\n private setupMutationObserver(): void {\n if (!this.config || typeof MutationObserver === 'undefined') {\n return;\n }\n\n if (!document.body) {\n log('debug', 'ViewportHandler: document.body not available, skipping MutationObserver setup');\n return;\n }\n\n this.mutationObserver = new MutationObserver((mutations) => {\n let hasAddedNodes = false;\n\n for (const mutation of mutations) {\n if (mutation.type === 'childList') {\n if (mutation.addedNodes.length > 0) {\n hasAddedNodes = true;\n }\n if (mutation.removedNodes.length > 0) {\n this.cleanupRemovedNodes(mutation.removedNodes);\n }\n }\n }\n\n if (hasAddedNodes) {\n if (this.mutationDebounceTimer !== null) {\n window.clearTimeout(this.mutationDebounceTimer);\n }\n this.mutationDebounceTimer = window.setTimeout(() => {\n this.observeElements();\n this.mutationDebounceTimer = null;\n }, VIEWPORT_MUTATION_DEBOUNCE_MS);\n }\n });\n\n this.mutationObserver.observe(document.body, {\n childList: true,\n subtree: true,\n });\n }\n\n /**\n * Cleans up tracking for removed DOM nodes\n */\n private cleanupRemovedNodes(removedNodes: NodeList): void {\n removedNodes.forEach((node) => {\n if (node.nodeType !== 1) return; // 1 = ELEMENT_NODE\n\n const element = node as Element;\n const tracked = this.trackedElements.get(element);\n\n if (tracked) {\n if (tracked.timeoutId !== null) {\n window.clearTimeout(tracked.timeoutId);\n }\n\n this.observer?.unobserve(element);\n this.trackedElements.delete(element);\n }\n\n const descendants = Array.from(this.trackedElements.keys()).filter((el) => element.contains(el));\n descendants.forEach((el) => {\n const descendantTracked = this.trackedElements.get(el);\n if (descendantTracked && descendantTracked.timeoutId !== null) {\n window.clearTimeout(descendantTracked.timeoutId);\n }\n this.observer?.unobserve(el);\n this.trackedElements.delete(el);\n });\n });\n }\n}\n","import { log } from '../utils';\n\n/**\n * Robust localStorage and sessionStorage wrapper with automatic fallback to in-memory storage.\n *\n * **Purpose**: Provides consistent storage APIs across all browser environments,\n * handling quota limits, privacy modes, and SSR scenarios gracefully.\n *\n * **Core Functionality**:\n * - **Dual Storage**: localStorage (persistent) and sessionStorage (tab-scoped)\n * - **Automatic Fallback**: In-memory Maps when browser storage unavailable\n * - **Quota Handling**: Intelligent cleanup on QuotaExceededError\n * - **SSR-Safe**: No-op in Node.js environments\n *\n * **Key Features**:\n * - Separate fallback Maps for each storage type\n * - Storage quota error handling with automatic cleanup and retry\n * - Intelligent cleanup: Prioritizes removing persisted events over critical data\n * - Preserves: session, user, device, and config keys during cleanup\n * - Test key validation during initialization (`__tracelog_test__`)\n * - Public `isAvailable()` and `hasQuotaError()` for conditional logic\n * - Explicit `clear()` method for TraceLog-namespaced data only (`tracelog_*` prefix)\n *\n * **Cleanup Strategy** (on QuotaExceededError):\n * 1. Remove persisted events (`tracelog_persisted_events_*`) - usually largest data\n * 2. If no persisted events, remove up to 5 non-critical keys\n * 3. Preserve critical keys: `tracelog_session_*`, `tracelog_user_id`, `tracelog_device_id`, `tracelog_config`\n *\n * @see src/managers/README.md (lines 203-226) for detailed documentation\n *\n * @example\n * ```typescript\n * const storage = new StorageManager();\n *\n * // localStorage operations\n * storage.setItem('key', 'value');\n * const value = storage.getItem('key');\n * storage.removeItem('key');\n *\n * // sessionStorage operations\n * storage.setSessionItem('session_key', 'data');\n * const sessionValue = storage.getSessionItem('session_key');\n *\n * // Check availability\n * if (storage.isAvailable()) {\n * // localStorage is working\n * }\n *\n * // Check for quota issues\n * if (storage.hasQuotaError()) {\n * // Data may not persist\n * }\n * ```\n */\nexport class StorageManager {\n private readonly storage: Storage | null;\n private readonly sessionStorageRef: Storage | null;\n private readonly fallbackStorage = new Map<string, string>();\n private readonly fallbackSessionStorage = new Map<string, string>();\n\n private hasQuotaExceededError = false;\n\n constructor() {\n this.storage = this.initializeStorage('localStorage');\n this.sessionStorageRef = this.initializeStorage('sessionStorage');\n\n if (!this.storage) {\n log('debug', 'localStorage not available, using memory fallback');\n }\n if (!this.sessionStorageRef) {\n log('debug', 'sessionStorage not available, using memory fallback');\n }\n }\n\n /**\n * Retrieves an item from localStorage.\n *\n * Automatically falls back to in-memory storage if localStorage unavailable.\n *\n * @param key - Storage key\n * @returns Stored value or null if not found\n */\n getItem(key: string): string | null {\n try {\n if (this.storage) {\n return this.storage.getItem(key);\n }\n return this.fallbackStorage.get(key) ?? null;\n } catch {\n return this.fallbackStorage.get(key) ?? null;\n }\n }\n\n /**\n * Stores an item in localStorage with automatic quota handling.\n *\n * **Behavior**:\n * 1. Updates fallback storage first (ensures consistency)\n * 2. Attempts to store in localStorage\n * 3. On QuotaExceededError: Triggers cleanup and retries once\n * 4. Falls back to in-memory storage if retry fails\n *\n * **Cleanup on Quota Error**:\n * - Removes persisted events (largest data)\n * - Removes up to 5 non-critical keys\n * - Preserves session, user, device, and config keys\n *\n * @param key - Storage key\n * @param value - String value to store\n */\n setItem(key: string, value: string): void {\n this.fallbackStorage.set(key, value);\n\n try {\n if (this.storage) {\n this.storage.setItem(key, value);\n return;\n }\n } catch (error) {\n const isQuotaError =\n (error instanceof DOMException && error.name === 'QuotaExceededError') ||\n (error instanceof Error && error.name === 'QuotaExceededError');\n\n if (isQuotaError) {\n this.hasQuotaExceededError = true;\n\n log('warn', 'localStorage quota exceeded, attempting cleanup', {\n data: { key, valueSize: value.length },\n });\n\n const cleanedUp = this.cleanupOldData();\n\n if (cleanedUp) {\n try {\n if (this.storage) {\n this.storage.setItem(key, value);\n return;\n }\n } catch (retryError) {\n log('error', 'localStorage quota exceeded even after cleanup - data will not persist', {\n error: retryError,\n data: { key, valueSize: value.length },\n });\n }\n } else {\n log('error', 'localStorage quota exceeded and no data to cleanup - data will not persist', {\n error,\n data: { key, valueSize: value.length },\n });\n }\n }\n }\n }\n\n /**\n * Removes an item from localStorage and fallback storage.\n *\n * Safe to call even if key doesn't exist (idempotent).\n *\n * @param key - Storage key to remove\n */\n removeItem(key: string): void {\n try {\n if (this.storage) {\n this.storage.removeItem(key);\n }\n } catch {\n /* empty */\n }\n\n this.fallbackStorage.delete(key);\n }\n\n /**\n * Clears all TraceLog-related items from storage.\n *\n * Only removes keys with `tracelog_` prefix (safe for shared storage).\n * Clears both localStorage and fallback storage.\n *\n * **Use Cases**:\n * - User logout/privacy actions\n * - Development/testing cleanup\n * - Reset analytics state\n */\n clear(): void {\n if (!this.storage) {\n this.fallbackStorage.clear();\n return;\n }\n\n try {\n const keysToRemove: string[] = [];\n\n for (let i = 0; i < this.storage.length; i++) {\n const key = this.storage.key(i);\n if (key?.startsWith('tracelog_')) {\n keysToRemove.push(key);\n }\n }\n\n keysToRemove.forEach((key) => {\n this.storage!.removeItem(key);\n });\n this.fallbackStorage.clear();\n } catch (error) {\n log('error', 'Failed to clear storage', { error });\n this.fallbackStorage.clear();\n }\n }\n\n /**\n * Checks if localStorage is available.\n *\n * @returns true if localStorage is working, false if using fallback\n */\n isAvailable(): boolean {\n return this.storage !== null;\n }\n\n /**\n * Checks if a QuotaExceededError has occurred during this session.\n *\n * **Purpose**: Detect when localStorage is full and data may not persist.\n * Allows application to show warnings or adjust behavior.\n *\n * **Note**: Flag is set on first QuotaExceededError and never reset.\n *\n * @returns true if quota exceeded at any point during this session\n */\n hasQuotaError(): boolean {\n return this.hasQuotaExceededError;\n }\n\n /**\n * Implements two-phase cleanup strategy to free storage space when quota exceeded.\n *\n * **Purpose**: Removes TraceLog data intelligently to make room for new writes\n * while preserving critical user state (session, user ID, device ID, config).\n *\n * **Two-Phase Cleanup Strategy**:\n * 1. **Phase 1 (Priority)**: Remove all persisted events (`tracelog_persisted_events_*`)\n * - These are typically the largest data items (batches of events)\n * - Safe to remove as they represent recoverable failed sends\n * - Returns immediately if any persisted events found and removed\n *\n * 2. **Phase 2 (Fallback)**: Remove up to 5 non-critical keys\n * - Only executed if no persisted events found\n * - Preserves critical keys: session data, user ID, device ID, config\n * - Limits to 5 keys to avoid excessive cleanup time\n *\n * **Critical Keys (Never Removed)**:\n * - `tracelog_session_*` - Active session data\n * - `tracelog_user_id` - User identification\n * - `tracelog_device_id` - Device fingerprint\n * - `tracelog_config` - Configuration cache\n *\n * **Error Handling**:\n * - Individual key removal failures silently ignored (continue cleanup)\n * - Overall cleanup errors logged and return false\n *\n * @returns true if any data was successfully removed, false if nothing cleaned up\n */\n private cleanupOldData(): boolean {\n if (!this.storage) {\n return false;\n }\n\n try {\n const tracelogKeys: string[] = [];\n const persistedEventsKeys: string[] = [];\n\n for (let i = 0; i < this.storage.length; i++) {\n const key = this.storage.key(i);\n if (key?.startsWith('tracelog_')) {\n tracelogKeys.push(key);\n\n if (key.startsWith('tracelog_persisted_events_')) {\n persistedEventsKeys.push(key);\n }\n }\n }\n\n if (persistedEventsKeys.length > 0) {\n persistedEventsKeys.forEach((key) => {\n try {\n this.storage!.removeItem(key);\n } catch {\n /* empty */\n }\n });\n\n return true;\n }\n\n const criticalPrefixes = ['tracelog_session_', 'tracelog_user_id', 'tracelog_device_id', 'tracelog_config'];\n\n const nonCriticalKeys = tracelogKeys.filter((key) => {\n return !criticalPrefixes.some((prefix) => key.startsWith(prefix));\n });\n\n if (nonCriticalKeys.length > 0) {\n const keysToRemove = nonCriticalKeys.slice(0, 5);\n keysToRemove.forEach((key) => {\n try {\n this.storage!.removeItem(key);\n } catch {\n /* empty */\n }\n });\n\n return true;\n }\n\n return false;\n } catch (error) {\n log('error', 'Failed to cleanup old data', { error });\n return false;\n }\n }\n\n /**\n * Initializes storage with feature detection and write-test validation.\n *\n * **Purpose**: Validates storage availability by performing actual write/remove test,\n * preventing false positives in privacy modes where storage API exists but throws on write.\n *\n * **Validation Strategy**:\n * 1. SSR Safety: Returns null in Node.js environments (`typeof window === 'undefined'`)\n * 2. API Check: Verifies storage object exists on window\n * 3. Write Test: Attempts to write test key (`__tracelog_test__`)\n * 4. Cleanup: Removes test key immediately after validation\n *\n * **Why Write Test is Critical**:\n * - Safari private browsing: storage API exists but throws QuotaExceededError on write\n * - iOS private mode: storage appears available but operations fail\n * - Incognito modes: API exists but writes are silently ignored or throw\n *\n * **Fallback Behavior**:\n * - Returns null if storage unavailable or test fails\n * - Caller automatically falls back to in-memory Map storage\n *\n * @param type - Storage type to initialize ('localStorage' | 'sessionStorage')\n * @returns Storage instance if available and writable, null otherwise\n */\n private initializeStorage(type: 'localStorage' | 'sessionStorage'): Storage | null {\n if (typeof window === 'undefined') {\n return null;\n }\n\n try {\n const storage = type === 'localStorage' ? window.localStorage : window.sessionStorage;\n const testKey = '__tracelog_test__';\n\n storage.setItem(testKey, 'test');\n storage.removeItem(testKey);\n\n return storage;\n } catch {\n return null;\n }\n }\n\n /**\n * Retrieves an item from sessionStorage.\n *\n * Automatically falls back to in-memory storage if sessionStorage unavailable.\n *\n * @param key - Storage key\n * @returns Stored value or null if not found\n */\n getSessionItem(key: string): string | null {\n try {\n if (this.sessionStorageRef) {\n return this.sessionStorageRef.getItem(key);\n }\n return this.fallbackSessionStorage.get(key) ?? null;\n } catch {\n return this.fallbackSessionStorage.get(key) ?? null;\n }\n }\n\n /**\n * Stores an item in sessionStorage with quota error detection.\n *\n * **Behavior**:\n * 1. Updates fallback storage first (ensures consistency)\n * 2. Attempts to store in sessionStorage\n * 3. On QuotaExceededError: Logs error and uses fallback (no retry/cleanup)\n *\n * **Note**: sessionStorage quota errors are rare (typically 5-10MB per tab).\n * No automatic cleanup unlike localStorage.\n *\n * @param key - Storage key\n * @param value - String value to store\n */\n setSessionItem(key: string, value: string): void {\n this.fallbackSessionStorage.set(key, value);\n\n try {\n if (this.sessionStorageRef) {\n this.sessionStorageRef.setItem(key, value);\n return;\n }\n } catch (error) {\n const isQuotaError =\n (error instanceof DOMException && error.name === 'QuotaExceededError') ||\n (error instanceof Error && error.name === 'QuotaExceededError');\n\n if (isQuotaError) {\n log('error', 'sessionStorage quota exceeded - data will not persist', {\n error,\n data: { key, valueSize: value.length },\n });\n }\n }\n }\n\n /**\n * Removes an item from sessionStorage and fallback storage.\n *\n * Safe to call even if key doesn't exist (idempotent).\n *\n * @param key - Storage key to remove\n */\n removeSessionItem(key: string): void {\n try {\n if (this.sessionStorageRef) {\n this.sessionStorageRef.removeItem(key);\n }\n } catch {\n /* empty */\n }\n\n this.fallbackSessionStorage.delete(key);\n }\n}\n","import { EventManager } from '../managers/event.manager';\nimport { StateManager } from '../managers/state.manager';\nimport { EventType, WebVitalType } from '../types';\nimport {\n LONG_TASK_THROTTLE_MS,\n MAX_NAVIGATION_HISTORY,\n PRECISION_TWO_DECIMALS,\n getWebVitalsThresholds,\n DEFAULT_WEB_VITALS_MODE,\n} from '../constants';\nimport { log } from '../utils';\n\ntype LayoutShiftEntry = PerformanceEntry & { value?: number; hadRecentInput?: boolean };\n\n/**\n * Captures Web Vitals and performance metrics using the web-vitals library with fallback to native Performance Observer API.\n *\n * **Features**:\n * - Configurable filtering modes: 'all', 'needs-improvement' (default), 'poor'\n * - Custom threshold overrides via webVitalsThresholds config\n * - Navigation-based deduplication with 50-navigation FIFO history\n * - CLS accumulation with reset on navigation change\n * - Long task throttling (maximum 1 event per second)\n * - Automatic fallback to Performance Observer if web-vitals library fails\n * - Final values only (reportAllChanges: false for all metrics)\n *\n * **Events Generated**: `web_vitals`\n *\n * **Metrics Captured**:\n * - LCP (Largest Contentful Paint): Main content loading time\n * - CLS (Cumulative Layout Shift): Visual stability score\n * - FCP (First Contentful Paint): Initial rendering time\n * - TTFB (Time to First Byte): Server response time\n * - INP (Interaction to Next Paint): Responsiveness measure\n * - LONG_TASK: Tasks blocking main thread (>50ms, throttled to 1/second)\n *\n * **Filtering Modes**:\n * - 'all': Track all positive metric values (threshold = 0)\n * - 'needs-improvement': Track metrics exceeding good thresholds (default)\n * - 'poor': Track only critical performance issues\n *\n * @example\n * ```typescript\n * const handler = new PerformanceHandler(eventManager);\n * await handler.startTracking();\n * // Web Vitals are now being tracked with default 'needs-improvement' mode\n * handler.stopTracking();\n * ```\n */\nexport class PerformanceHandler extends StateManager {\n private readonly eventManager: EventManager;\n private readonly reportedByNav: Map<string, Set<string>> = new Map();\n private readonly navigationHistory: string[] = []; // FIFO queue for tracking navigation order\n private readonly observers: PerformanceObserver[] = [];\n private vitalThresholds: Record<WebVitalType, number>;\n private lastLongTaskSentAt = 0;\n private navigationCounter = 0; // Counter for handling simultaneous navigations edge case\n\n constructor(eventManager: EventManager) {\n super();\n this.eventManager = eventManager;\n this.vitalThresholds = getWebVitalsThresholds(DEFAULT_WEB_VITALS_MODE);\n }\n\n /**\n * Starts tracking Web Vitals and performance metrics.\n *\n * Asynchronously loads the web-vitals library and initializes performance tracking.\n * Falls back to native Performance Observer API if web-vitals fails to load.\n *\n * **Configuration**:\n * - Reads webVitalsMode from config ('all', 'needs-improvement', 'poor')\n * - Merges webVitalsThresholds with mode defaults for custom thresholds\n * - Initializes web-vitals library observers (LCP, CLS, FCP, TTFB, INP)\n * - Starts long task observation with 1/second throttling\n *\n * @returns Promise that resolves when tracking is initialized\n */\n async startTracking(): Promise<void> {\n const config = this.get('config');\n const mode = config?.webVitalsMode ?? DEFAULT_WEB_VITALS_MODE;\n\n this.vitalThresholds = getWebVitalsThresholds(mode);\n\n if (config?.webVitalsThresholds) {\n this.vitalThresholds = { ...this.vitalThresholds, ...config.webVitalsThresholds };\n }\n\n await this.initWebVitals();\n this.observeLongTasks();\n }\n\n /**\n * Stops tracking Web Vitals and cleans up resources.\n *\n * Disconnects all Performance Observers and clears internal state:\n * - Disconnects all active observers (web-vitals and long task)\n * - Clears navigation-based deduplication map\n * - Clears navigation history array\n * - Prevents memory leaks in long-running applications\n */\n stopTracking(): void {\n this.observers.forEach((obs, index) => {\n try {\n obs.disconnect();\n } catch (error) {\n log('debug', 'Failed to disconnect performance observer', { error, data: { observerIndex: index } });\n }\n });\n\n this.observers.length = 0;\n this.reportedByNav.clear();\n this.navigationHistory.length = 0;\n }\n\n private observeWebVitalsFallback(): void {\n this.reportTTFB();\n\n this.safeObserve(\n 'largest-contentful-paint',\n (list) => {\n const entries = list.getEntries();\n const last = entries[entries.length - 1] as (PerformanceEntry & { startTime: number }) | undefined;\n\n if (!last) {\n return;\n }\n\n this.sendVital({ type: 'LCP', value: Number(last.startTime.toFixed(PRECISION_TWO_DECIMALS)) });\n },\n { type: 'largest-contentful-paint', buffered: true },\n true,\n );\n\n let clsValue = 0;\n let currentNavId = this.getNavigationId();\n\n this.safeObserve(\n 'layout-shift',\n (list) => {\n const navId = this.getNavigationId();\n\n if (navId !== currentNavId) {\n clsValue = 0;\n currentNavId = navId;\n }\n\n const entries = list.getEntries() as LayoutShiftEntry[];\n\n for (const entry of entries) {\n if (entry.hadRecentInput === true) {\n continue;\n }\n\n const value = typeof entry.value === 'number' ? entry.value : 0;\n clsValue += value;\n }\n\n this.sendVital({ type: 'CLS', value: Number(clsValue.toFixed(PRECISION_TWO_DECIMALS)) });\n },\n { type: 'layout-shift', buffered: true },\n );\n\n this.safeObserve(\n 'paint',\n (list) => {\n for (const entry of list.getEntries()) {\n if (entry.name === 'first-contentful-paint') {\n this.sendVital({ type: 'FCP', value: Number(entry.startTime.toFixed(PRECISION_TWO_DECIMALS)) });\n }\n }\n },\n { type: 'paint', buffered: true },\n true,\n );\n\n this.safeObserve(\n 'event',\n (list) => {\n let worst = 0;\n const entries = list.getEntries() as Array<{ startTime: number; processingEnd?: number }>;\n\n for (const entry of entries) {\n const dur = (entry.processingEnd ?? 0) - (entry.startTime ?? 0);\n worst = Math.max(worst, dur);\n }\n\n if (worst > 0) {\n this.sendVital({ type: 'INP', value: Number(worst.toFixed(PRECISION_TWO_DECIMALS)) });\n }\n },\n { type: 'event', buffered: true },\n );\n }\n\n private async initWebVitals(): Promise<void> {\n try {\n const { onLCP, onCLS, onFCP, onTTFB, onINP } = await import('web-vitals');\n\n const report =\n (type: WebVitalType) =>\n (metric: { value: number }): void => {\n const value = Number(metric.value.toFixed(PRECISION_TWO_DECIMALS));\n this.sendVital({ type, value });\n };\n\n onLCP(report('LCP'), { reportAllChanges: false });\n onCLS(report('CLS'), { reportAllChanges: false });\n onFCP(report('FCP'), { reportAllChanges: false });\n onTTFB(report('TTFB'), { reportAllChanges: false });\n onINP(report('INP'), { reportAllChanges: false });\n } catch (error) {\n log('debug', 'Failed to load web-vitals library, using fallback', { error });\n this.observeWebVitalsFallback();\n }\n }\n\n private reportTTFB(): void {\n try {\n const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined;\n\n if (!nav) {\n return;\n }\n\n const ttfb = nav.responseStart;\n\n // TTFB can be 0 in Mobile Safari when response is served from cache\n if (typeof ttfb === 'number' && Number.isFinite(ttfb)) {\n this.sendVital({ type: 'TTFB', value: Number(ttfb.toFixed(PRECISION_TWO_DECIMALS)) });\n }\n } catch (error) {\n log('debug', 'Failed to report TTFB', { error });\n }\n }\n\n private observeLongTasks(): void {\n this.safeObserve(\n 'longtask',\n (list) => {\n const entries = list.getEntries() as Array<{ duration: number }>;\n\n for (const entry of entries) {\n const duration = Number(entry.duration.toFixed(PRECISION_TWO_DECIMALS));\n const now = Date.now();\n\n if (now - this.lastLongTaskSentAt >= LONG_TASK_THROTTLE_MS) {\n if (this.shouldSendVital('LONG_TASK', duration)) {\n this.trackWebVital('LONG_TASK', duration);\n }\n this.lastLongTaskSentAt = now;\n }\n }\n },\n { type: 'longtask', buffered: true },\n );\n }\n\n private sendVital(sample: { type: WebVitalType; value: number }): void {\n if (!this.shouldSendVital(sample.type, sample.value)) {\n return;\n }\n\n const navId = this.getNavigationId();\n\n if (navId) {\n const reportedForNav = this.reportedByNav.get(navId);\n const isDuplicate = reportedForNav?.has(sample.type);\n\n if (isDuplicate) {\n return;\n }\n\n if (!reportedForNav) {\n this.reportedByNav.set(navId, new Set([sample.type]));\n this.navigationHistory.push(navId);\n\n if (this.navigationHistory.length > MAX_NAVIGATION_HISTORY) {\n const oldestNav = this.navigationHistory.shift();\n if (oldestNav) {\n this.reportedByNav.delete(oldestNav);\n }\n }\n } else {\n reportedForNav.add(sample.type);\n }\n }\n\n this.trackWebVital(sample.type, sample.value);\n }\n\n private trackWebVital(type: WebVitalType, value: number): void {\n if (!Number.isFinite(value)) {\n log('debug', 'Invalid web vital value', { data: { type, value } });\n return;\n }\n\n this.eventManager.track({\n type: EventType.WEB_VITALS,\n web_vitals: {\n type,\n value,\n },\n });\n }\n\n /**\n * Generates a unique navigation identifier for deduplication.\n *\n * **Purpose**: Creates deterministic IDs to prevent duplicate Web Vitals reporting\n * across multiple metrics for the same navigation event.\n *\n * **ID Format**: `{timestamp}_{pathname}` or `{timestamp}_{pathname}_{counter}`\n *\n * **Edge Case Handling**:\n * - If multiple navigations occur to the same pathname in the same millisecond,\n * a counter suffix is appended (e.g., `1234.56_/home_2`)\n * - Counter only added when > 1 to minimize ID length for common case\n *\n * **Why Deterministic**:\n * - Previous implementation used random string → duplicate metrics on page reload\n * - Now: Same navigation = same ID = proper deduplication via reportedByNav Map\n *\n * @returns Navigation ID string or null if navigation timing unavailable\n *\n * @internal\n */\n private getNavigationId(): string | null {\n try {\n const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined;\n\n if (!nav) {\n return null;\n }\n\n const timestamp = nav.startTime || performance.now();\n const counter = ++this.navigationCounter;\n\n // Base ID: timestamp + pathname (deterministic for deduplication)\n const baseId = `${timestamp.toFixed(2)}_${window.location.pathname}`;\n\n // Append counter only if > 1 (edge case: simultaneous navigations)\n // This prevents collisions if two navigations occur in the same millisecond to the same path\n return counter > 1 ? `${baseId}_${counter}` : baseId;\n } catch (error) {\n log('debug', 'Failed to get navigation ID', { error });\n return null;\n }\n }\n\n private isObserverSupported(type: string): boolean {\n if (typeof PerformanceObserver === 'undefined') return false;\n const supported = PerformanceObserver.supportedEntryTypes;\n return !supported || supported.includes(type);\n }\n\n private safeObserve(\n type: string,\n cb: PerformanceObserverCallback,\n options?: PerformanceObserverInit,\n once = false,\n ): boolean {\n try {\n if (!this.isObserverSupported(type)) {\n return false;\n }\n\n const obs = new PerformanceObserver((list, observer) => {\n try {\n cb(list, observer);\n } catch (callbackError) {\n log('debug', 'Observer callback failed', {\n error: callbackError,\n data: { type },\n });\n }\n\n if (once) {\n try {\n observer.disconnect();\n } catch {\n /* empty */\n }\n }\n });\n\n obs.observe(options ?? { type, buffered: true });\n\n if (!once) {\n this.observers.push(obs);\n }\n\n return true;\n } catch (error) {\n log('debug', 'Failed to create performance observer', {\n error,\n data: { type },\n });\n return false;\n }\n }\n\n private shouldSendVital(type: WebVitalType, value?: number): boolean {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n log('debug', 'Invalid web vital value', { data: { type, value } });\n return false;\n }\n\n const threshold = this.vitalThresholds[type];\n\n if (typeof threshold === 'number' && value <= threshold) {\n return false;\n }\n\n return true;\n }\n}\n","import { EventManager } from '../managers/event.manager';\nimport { StateManager } from '../managers/state.manager';\nimport { ErrorType, EventType } from '../types';\nimport { log } from '../utils';\nimport {\n PII_PATTERNS,\n MAX_ERROR_MESSAGE_LENGTH,\n ERROR_SUPPRESSION_WINDOW_MS,\n MAX_TRACKED_ERRORS,\n MAX_TRACKED_ERRORS_HARD_LIMIT,\n DEFAULT_ERROR_SAMPLING_RATE,\n ERROR_BURST_WINDOW_MS,\n ERROR_BURST_THRESHOLD,\n ERROR_BURST_BACKOFF_MS,\n} from '../constants/error.constants';\n\n/**\n * Captures JavaScript errors and unhandled promise rejections for debugging and monitoring.\n *\n * **Events Generated**: `error`\n *\n * **Features**:\n * - Tracks JavaScript runtime errors and unhandled promise rejections\n * - Configurable error sampling rate (default: 100%)\n * - PII sanitization (emails, phone numbers, credit cards, API keys, tokens)\n * - Message truncation (500 character limit)\n * - Burst detection (>10 errors/second triggers 5-second cooldown)\n * - Deduplication within 5-second window per error type+message\n *\n * **Privacy Protection**:\n * - Automatically redacts PII from error messages before storage\n * - Sanitizes emails, phone numbers, credit cards, IBAN, API keys, Bearer tokens\n *\n * @see src/handlers/README.md (lines 167-218) for detailed documentation\n */\nexport class ErrorHandler extends StateManager {\n private readonly eventManager: EventManager;\n private readonly recentErrors = new Map<string, number>();\n private errorBurstCounter = 0;\n private burstWindowStart = 0;\n private burstBackoffUntil = 0;\n\n constructor(eventManager: EventManager) {\n super();\n this.eventManager = eventManager;\n }\n\n /**\n * Starts tracking JavaScript errors and promise rejections.\n *\n * - Registers global error event listener\n * - Registers unhandledrejection event listener\n */\n startTracking(): void {\n window.addEventListener('error', this.handleError);\n window.addEventListener('unhandledrejection', this.handleRejection);\n }\n\n /**\n * Stops tracking errors and cleans up resources.\n *\n * - Removes error event listeners\n * - Clears recent errors map\n * - Resets burst detection counters\n */\n stopTracking(): void {\n window.removeEventListener('error', this.handleError);\n window.removeEventListener('unhandledrejection', this.handleRejection);\n this.recentErrors.clear();\n this.errorBurstCounter = 0;\n this.burstWindowStart = 0;\n this.burstBackoffUntil = 0;\n }\n\n /**\n * Checks sampling rate and burst detection\n * Returns false if in cooldown period after burst detection\n */\n private shouldSample(): boolean {\n const now = Date.now();\n\n if (now < this.burstBackoffUntil) {\n return false;\n }\n\n if (now - this.burstWindowStart > ERROR_BURST_WINDOW_MS) {\n this.errorBurstCounter = 0;\n this.burstWindowStart = now;\n }\n\n this.errorBurstCounter++;\n\n if (this.errorBurstCounter > ERROR_BURST_THRESHOLD) {\n this.burstBackoffUntil = now + ERROR_BURST_BACKOFF_MS;\n log('debug', 'Error burst detected - entering cooldown', {\n data: {\n errorsInWindow: this.errorBurstCounter,\n cooldownMs: ERROR_BURST_BACKOFF_MS,\n },\n });\n return false;\n }\n\n const config = this.get('config');\n const samplingRate = config?.errorSampling ?? DEFAULT_ERROR_SAMPLING_RATE;\n return Math.random() < samplingRate;\n }\n\n private readonly handleError = (event: ErrorEvent): void => {\n if (!this.shouldSample()) {\n return;\n }\n\n const sanitizedMessage = this.sanitize(event.message || 'Unknown error');\n\n if (this.shouldSuppressError(ErrorType.JS_ERROR, sanitizedMessage)) {\n return;\n }\n\n this.eventManager.track({\n type: EventType.ERROR,\n error_data: {\n type: ErrorType.JS_ERROR,\n message: sanitizedMessage,\n ...(event.filename && { filename: event.filename }),\n ...(event.lineno && { line: event.lineno }),\n ...(event.colno && { column: event.colno }),\n },\n });\n };\n\n private readonly handleRejection = (event: PromiseRejectionEvent): void => {\n if (!this.shouldSample()) {\n return;\n }\n\n const message = this.extractRejectionMessage(event.reason);\n const sanitizedMessage = this.sanitize(message);\n\n if (this.shouldSuppressError(ErrorType.PROMISE_REJECTION, sanitizedMessage)) {\n return;\n }\n\n this.eventManager.track({\n type: EventType.ERROR,\n error_data: {\n type: ErrorType.PROMISE_REJECTION,\n message: sanitizedMessage,\n },\n });\n };\n\n private extractRejectionMessage(reason: unknown): string {\n if (!reason) return 'Unknown rejection';\n\n if (typeof reason === 'string') return reason;\n\n if (reason instanceof Error) {\n return reason.stack ?? reason.message ?? reason.toString();\n }\n\n if (typeof reason === 'object' && 'message' in reason) {\n return String(reason.message);\n }\n\n try {\n return JSON.stringify(reason);\n } catch {\n return String(reason);\n }\n }\n\n private sanitize(text: string): string {\n let sanitized = text.length > MAX_ERROR_MESSAGE_LENGTH ? text.slice(0, MAX_ERROR_MESSAGE_LENGTH) + '...' : text;\n\n for (const pattern of PII_PATTERNS) {\n const regex = new RegExp(pattern.source, pattern.flags);\n sanitized = sanitized.replace(regex, '[REDACTED]');\n }\n\n return sanitized;\n }\n\n private shouldSuppressError(type: ErrorType, message: string): boolean {\n const now = Date.now();\n const key = `${type}:${message}`;\n const lastSeenAt = this.recentErrors.get(key);\n\n if (lastSeenAt && now - lastSeenAt < ERROR_SUPPRESSION_WINDOW_MS) {\n this.recentErrors.set(key, now);\n return true;\n }\n\n this.recentErrors.set(key, now);\n\n if (this.recentErrors.size > MAX_TRACKED_ERRORS_HARD_LIMIT) {\n this.recentErrors.clear();\n this.recentErrors.set(key, now);\n\n return false;\n }\n\n if (this.recentErrors.size > MAX_TRACKED_ERRORS) {\n this.pruneOldErrors();\n }\n\n return false;\n }\n\n private pruneOldErrors(): void {\n const now = Date.now();\n for (const [key, timestamp] of this.recentErrors.entries()) {\n if (now - timestamp > ERROR_SUPPRESSION_WINDOW_MS) {\n this.recentErrors.delete(key);\n }\n }\n\n if (this.recentErrors.size <= MAX_TRACKED_ERRORS) {\n return;\n }\n\n const entries = Array.from(this.recentErrors.entries()).sort((a, b) => a[1] - b[1]);\n const excess = this.recentErrors.size - MAX_TRACKED_ERRORS;\n\n for (let index = 0; index < excess; index += 1) {\n const entry = entries[index];\n if (entry) {\n this.recentErrors.delete(entry[0]);\n }\n }\n }\n}\n","import { EventManager } from './managers/event.manager';\nimport { UserManager } from './managers/user.manager';\nimport { StateManager } from './managers/state.manager';\nimport { SessionHandler } from './handlers/session.handler';\nimport { PageViewHandler } from './handlers/page-view.handler';\nimport { ClickHandler } from './handlers/click.handler';\nimport { ScrollHandler } from './handlers/scroll.handler';\nimport { ViewportHandler } from './handlers/viewport.handler';\nimport {\n Config,\n EventType,\n EmitterCallback,\n EmitterMap,\n Mode,\n TransformerHook,\n TransformerMap,\n BeforeSendTransformer,\n BeforeBatchTransformer,\n MetadataType,\n} from './types';\nimport {\n isEventValid,\n getDeviceInfo,\n normalizeUrl,\n Emitter,\n getCollectApiUrls,\n detectQaMode,\n log,\n isValidMetadata,\n} from './utils';\nimport { StorageManager } from './managers/storage.manager';\nimport { SCROLL_DEBOUNCE_TIME_MS, SCROLL_SUPPRESS_MULTIPLIER } from './constants/config.constants';\nimport { PerformanceHandler } from './handlers/performance.handler';\nimport { ErrorHandler } from './handlers/error.handler';\n\nexport class App extends StateManager {\n private isInitialized = false;\n private suppressNextScrollTimer: number | null = null;\n\n private readonly emitter = new Emitter();\n private readonly transformers: TransformerMap = {};\n\n protected managers: {\n storage?: StorageManager;\n event?: EventManager;\n } = {};\n\n protected handlers: {\n session?: SessionHandler;\n pageView?: PageViewHandler;\n click?: ClickHandler;\n scroll?: ScrollHandler;\n performance?: PerformanceHandler;\n error?: ErrorHandler;\n viewport?: ViewportHandler;\n } = {};\n\n get initialized(): boolean {\n return this.isInitialized;\n }\n\n /**\n * Initializes TraceLog with configuration.\n *\n * @param config - Configuration object\n * @throws {Error} If initialization fails\n * @internal Called from api.init()\n */\n async init(config: Config = {}): Promise<void> {\n if (this.isInitialized) {\n return;\n }\n\n this.managers.storage = new StorageManager();\n\n try {\n this.setupState(config);\n\n this.managers.event = new EventManager(this.managers.storage, this.emitter, this.transformers);\n\n this.initializeHandlers();\n\n await this.managers.event.recoverPersistedEvents().catch((error) => {\n log('warn', 'Failed to recover persisted events', { error });\n });\n\n this.isInitialized = true;\n } catch (error) {\n this.destroy(true);\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new Error(`[TraceLog] TraceLog initialization failed: ${errorMessage}`);\n }\n }\n\n /**\n * Sends a custom event with optional metadata.\n *\n * @param name - Event name\n * @param metadata - Optional metadata\n * @internal Called from api.event()\n */\n sendCustomEvent(name: string, metadata?: Record<string, unknown> | Record<string, unknown>[]): void {\n if (!this.managers.event) {\n log('warn', 'Cannot send custom event: TraceLog not initialized', { data: { name } });\n return;\n }\n\n let normalizedMetadata = metadata;\n\n if (metadata && typeof metadata === 'object' && !Array.isArray(metadata)) {\n if (Object.getPrototypeOf(metadata) !== Object.prototype) {\n normalizedMetadata = Object.assign({}, metadata);\n }\n }\n\n const { valid, error, sanitizedMetadata } = isEventValid(name, normalizedMetadata);\n\n if (!valid) {\n if (this.get('mode') === Mode.QA) {\n throw new Error(`[TraceLog] Custom event \"${name}\" validation failed: ${error}`);\n }\n\n return;\n }\n\n this.managers.event.track({\n type: EventType.CUSTOM,\n custom_event: {\n name,\n ...(sanitizedMetadata && { metadata: sanitizedMetadata }),\n },\n });\n }\n\n on<K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>): void {\n this.emitter.on(event, callback);\n }\n\n off<K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>): void {\n this.emitter.off(event, callback);\n }\n\n setTransformer(hook: 'beforeSend', fn: BeforeSendTransformer): void;\n setTransformer(hook: 'beforeBatch', fn: BeforeBatchTransformer): void;\n setTransformer(hook: TransformerHook, fn: BeforeSendTransformer | BeforeBatchTransformer): void {\n if (typeof fn !== 'function') {\n throw new Error(`[TraceLog] Transformer must be a function, received: ${typeof fn}`);\n }\n\n this.transformers[hook] = fn as BeforeSendTransformer & BeforeBatchTransformer;\n }\n\n removeTransformer(hook: TransformerHook): void {\n delete this.transformers[hook];\n }\n\n getTransformer(hook: 'beforeSend'): BeforeSendTransformer | undefined;\n getTransformer(hook: 'beforeBatch'): BeforeBatchTransformer | undefined;\n getTransformer(hook: TransformerHook): BeforeSendTransformer | BeforeBatchTransformer | undefined {\n return this.transformers[hook];\n }\n\n /**\n * Destroys the TraceLog instance and cleans up all resources.\n *\n * @param force - If true, forces cleanup even if not initialized (used during init failure)\n * @internal Called from api.destroy()\n */\n destroy(force = false): void {\n if (!this.isInitialized && !force) {\n return;\n }\n\n Object.values(this.handlers)\n .filter(Boolean)\n .forEach((handler) => {\n try {\n handler.stopTracking();\n } catch (error) {\n log('warn', 'Failed to stop tracking', { error });\n }\n });\n\n if (this.suppressNextScrollTimer) {\n clearTimeout(this.suppressNextScrollTimer);\n this.suppressNextScrollTimer = null;\n }\n\n this.managers.event?.stop();\n\n this.emitter.removeAllListeners();\n this.transformers.beforeSend = undefined;\n this.transformers.beforeBatch = undefined;\n\n this.set('suppressNextScroll', false);\n this.set('sessionId', null);\n\n this.isInitialized = false;\n this.handlers = {};\n this.managers = {};\n }\n\n private setupState(config: Config = {}): void {\n this.set('config', config);\n\n const userId = UserManager.getId(this.managers.storage as StorageManager);\n this.set('userId', userId);\n\n const collectApiUrls = getCollectApiUrls(config);\n this.set('collectApiUrls', collectApiUrls);\n\n const device = getDeviceInfo();\n this.set('device', device);\n\n const pageUrl = normalizeUrl(window.location.href, config.sensitiveQueryParams);\n this.set('pageUrl', pageUrl);\n\n const isQaMode = detectQaMode();\n\n if (isQaMode) {\n this.set('mode', Mode.QA);\n }\n }\n\n /**\n * Returns the current configuration object.\n *\n * @returns The Config object passed to init()\n * @internal Used by api.ts for configuration access\n */\n public getConfig(): Config {\n return this.get('config');\n }\n\n /**\n * Returns the configured backend API URLs for event collection.\n *\n * @returns Object containing optional saas and custom API URLs\n * @internal Used by api.ts for backend URL access\n */\n public getCollectApiUrls(): { saas?: string; custom?: string } {\n return this.get('collectApiUrls');\n }\n\n /**\n * Returns the EventManager instance for event tracking operations.\n *\n * @returns The EventManager instance, or undefined if not initialized\n * @internal Used by api.ts for event operations\n */\n public getEventManager(): EventManager | undefined {\n return this.managers.event;\n }\n\n /**\n * Validates metadata object structure and values.\n *\n * @param metadata - The metadata object to validate\n * @returns Validation result with error message if invalid\n * @internal Helper for updateGlobalMetadata and mergeGlobalMetadata\n */\n private validateGlobalMetadata(metadata: Record<string, unknown>): { valid: boolean; error?: string } {\n if (typeof metadata !== 'object' || metadata === null || Array.isArray(metadata)) {\n return {\n valid: false,\n error: 'Global metadata must be a plain object',\n };\n }\n\n const validation = isValidMetadata('Global', metadata, 'globalMetadata');\n\n if (!validation.valid) {\n return {\n valid: false,\n error: validation.error,\n };\n }\n\n return { valid: true };\n }\n\n /**\n * Replaces global metadata with new values.\n *\n * @param metadata - New global metadata object\n * @throws {Error} If metadata validation fails\n * @internal Called from api.updateGlobalMetadata()\n */\n public updateGlobalMetadata(metadata: Record<string, unknown>): void {\n const validation = this.validateGlobalMetadata(metadata);\n\n if (!validation.valid) {\n throw new Error(`[TraceLog] Invalid global metadata: ${validation.error}`);\n }\n\n const currentConfig = this.get('config');\n\n const updatedConfig: Config = {\n ...currentConfig,\n globalMetadata: metadata as Record<string, MetadataType>,\n };\n\n this.set('config', updatedConfig);\n\n log('debug', 'Global metadata updated (replaced)', { data: { keys: Object.keys(metadata) } });\n }\n\n /**\n * Merges new metadata with existing global metadata.\n *\n * @param metadata - Metadata to merge with existing values\n * @throws {Error} If metadata validation fails\n * @internal Called from api.mergeGlobalMetadata()\n */\n public mergeGlobalMetadata(metadata: Record<string, unknown>): void {\n const validation = this.validateGlobalMetadata(metadata);\n\n if (!validation.valid) {\n throw new Error(`[TraceLog] Invalid global metadata: ${validation.error}`);\n }\n\n const currentConfig = this.get('config');\n const existingMetadata = currentConfig.globalMetadata ?? {};\n\n const mergedMetadata: Record<string, MetadataType> = {\n ...existingMetadata,\n ...(metadata as Record<string, MetadataType>),\n };\n\n const updatedConfig: Config = {\n ...currentConfig,\n globalMetadata: mergedMetadata,\n };\n\n this.set('config', updatedConfig);\n\n log('debug', 'Global metadata updated (merged)', { data: { keys: Object.keys(metadata) } });\n }\n\n private initializeHandlers(): void {\n const config = this.get('config');\n\n this.handlers.session = new SessionHandler(\n this.managers.storage as StorageManager,\n this.managers.event as EventManager,\n );\n\n this.handlers.session.startTracking();\n\n const onPageView = (): void => {\n this.set('suppressNextScroll', true);\n\n if (this.suppressNextScrollTimer) {\n clearTimeout(this.suppressNextScrollTimer);\n }\n\n this.suppressNextScrollTimer = window.setTimeout(() => {\n this.set('suppressNextScroll', false);\n }, SCROLL_DEBOUNCE_TIME_MS * SCROLL_SUPPRESS_MULTIPLIER);\n };\n\n this.handlers.pageView = new PageViewHandler(this.managers.event as EventManager, onPageView);\n this.handlers.pageView.startTracking();\n\n this.handlers.click = new ClickHandler(this.managers.event as EventManager);\n this.handlers.click.startTracking();\n\n this.handlers.scroll = new ScrollHandler(this.managers.event as EventManager);\n this.handlers.scroll.startTracking();\n\n this.handlers.performance = new PerformanceHandler(this.managers.event as EventManager);\n this.handlers.performance.startTracking().catch((error) => {\n log('warn', 'Failed to start performance tracking', { error });\n });\n\n this.handlers.error = new ErrorHandler(this.managers.event as EventManager);\n this.handlers.error.startTracking();\n\n if (config.viewport) {\n this.handlers.viewport = new ViewportHandler(this.managers.event as EventManager);\n this.handlers.viewport.startTracking();\n }\n }\n}\n","import { App } from './app';\nimport {\n MetadataType,\n Config,\n EmitterCallback,\n EmitterMap,\n TransformerHook,\n BeforeSendTransformer,\n BeforeBatchTransformer,\n} from './types';\nimport { log, validateAndNormalizeConfig, setQaMode as setQaModeUtil } from './utils';\nimport { INITIALIZATION_TIMEOUT_MS } from './constants';\nimport './types/window.types';\n\ninterface PendingListener {\n event: keyof EmitterMap;\n callback: EmitterCallback<EmitterMap[keyof EmitterMap]>;\n}\n\ninterface PendingTransformer {\n hook: TransformerHook;\n fn: BeforeSendTransformer | BeforeBatchTransformer;\n}\n\nconst pendingListeners: PendingListener[] = [];\nconst pendingTransformers: PendingTransformer[] = [];\n\nlet app: App | null = null;\nlet isInitializing = false;\nlet isDestroying = false;\n\n/**\n * Initializes TraceLog and begins tracking user interactions.\n *\n * Important: Register listeners with on() before calling init() to capture initial events.\n *\n * @param config - Optional configuration object\n * @returns Promise that resolves when initialization completes (5s timeout)\n * @throws {Error} If initialization fails or times out\n *\n * @example\n * ```typescript\n * await tracelog.init({\n * integrations: {\n * tracelog: { projectId: 'your-project-id' }\n * }\n * });\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#init} for configuration options\n */\nexport const init = async (config?: Config): Promise<void> => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n isDestroying = false;\n\n if (window.__traceLogDisabled === true) {\n return;\n }\n\n if (app) {\n return;\n }\n\n if (isInitializing) {\n return;\n }\n\n isInitializing = true;\n\n try {\n const validatedConfig = validateAndNormalizeConfig(config ?? {});\n const instance = new App();\n\n try {\n pendingListeners.forEach(({ event, callback }) => {\n instance.on(event, callback);\n });\n\n pendingListeners.length = 0;\n\n pendingTransformers.forEach(({ hook, fn }) => {\n if (hook === 'beforeSend') {\n instance.setTransformer('beforeSend', fn as BeforeSendTransformer);\n } else {\n instance.setTransformer('beforeBatch', fn as BeforeBatchTransformer);\n }\n });\n\n pendingTransformers.length = 0;\n\n const initPromise = instance.init(validatedConfig);\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => {\n reject(new Error(`[TraceLog] Initialization timeout after ${INITIALIZATION_TIMEOUT_MS}ms`));\n }, INITIALIZATION_TIMEOUT_MS);\n });\n\n await Promise.race([initPromise, timeoutPromise]);\n\n app = instance;\n } catch (error) {\n try {\n instance.destroy(true);\n } catch (cleanupError) {\n log('error', 'Failed to cleanup partially initialized app', { error: cleanupError });\n }\n\n throw error;\n }\n } catch (error) {\n app = null;\n throw error;\n } finally {\n isInitializing = false;\n }\n};\n\n/**\n * Tracks a custom analytics event with optional metadata.\n *\n * @param name - Event identifier (e.g., 'checkout_completed')\n * @param metadata - Optional event data (object or array of objects)\n * @throws {Error} If called before init() or during destroy()\n *\n * @example\n * ```typescript\n * tracelog.event('product_viewed', {\n * productId: 'abc-123',\n * price: 299.99\n * });\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#event} for rate limiting details\n */\nexport const event = (name: string, metadata?: Record<string, MetadataType> | Record<string, MetadataType>[]): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (!app) {\n throw new Error('[TraceLog] TraceLog not initialized. Please call init() first.');\n }\n\n if (isDestroying) {\n throw new Error('[TraceLog] Cannot send events while TraceLog is being destroyed');\n }\n\n app.sendCustomEvent(name, metadata);\n};\n\n/**\n * Subscribes to TraceLog events for real-time consumption.\n *\n * Important: Register listeners BEFORE calling init() to capture SESSION_START and PAGE_VIEW.\n *\n * @param event - Event type ('event' or 'queue')\n * @param callback - Handler function called when event fires\n *\n * @example\n * ```typescript\n * tracelog.on('event', (event) => console.log(event.type));\n * await tracelog.init();\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib#event-listeners} for timing details\n */\nexport const on = <K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (!app || isInitializing) {\n pendingListeners.push({ event, callback } as PendingListener);\n return;\n }\n\n app.on(event, callback);\n};\n\n/**\n * Unsubscribes from TraceLog events.\n *\n * @param event - Event type to unsubscribe from\n * @param callback - Exact callback function reference used in on()\n *\n * @example\n * ```typescript\n * const handler = (event) => console.log(event.type);\n * tracelog.on('event', handler);\n * tracelog.off('event', handler);\n * ```\n */\nexport const off = <K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (!app) {\n const index = pendingListeners.findIndex((l) => l.event === event && l.callback === callback);\n if (index !== -1) {\n pendingListeners.splice(index, 1);\n }\n return;\n }\n\n app.off(event, callback);\n};\n\n/**\n * Transforms events at runtime before sending to custom backend.\n *\n * Note: Only applies to custom backend integration. TraceLog SaaS ignores transformers.\n *\n * @param hook - 'beforeSend' (per-event) or 'beforeBatch' (batch-level)\n * @param fn - Transformer function. Return null to filter event/batch.\n * @throws {Error} If called during destroy()\n * @throws {Error} If fn is not a function\n *\n * @example\n * ```typescript\n * tracelog.setTransformer('beforeSend', (data) => {\n * if ('type' in data) {\n * return { ...data, appVersion: '1.0.0' };\n * }\n * return data;\n * });\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#settransformer} for integration behavior\n */\nexport function setTransformer(hook: 'beforeSend', fn: BeforeSendTransformer): void;\nexport function setTransformer(hook: 'beforeBatch', fn: BeforeBatchTransformer): void;\nexport function setTransformer(hook: TransformerHook, fn: BeforeSendTransformer | BeforeBatchTransformer): void {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (typeof fn !== 'function') {\n throw new Error(`[TraceLog] Transformer must be a function, received: ${typeof fn}`);\n }\n\n if (!app || isInitializing) {\n const existingIndex = pendingTransformers.findIndex((t) => t.hook === hook);\n\n if (existingIndex !== -1) {\n pendingTransformers.splice(existingIndex, 1);\n }\n\n pendingTransformers.push({ hook, fn });\n\n return;\n }\n\n if (isDestroying) {\n throw new Error('[TraceLog] Cannot set transformers while TraceLog is being destroyed');\n }\n\n if (hook === 'beforeSend') {\n app.setTransformer('beforeSend', fn as BeforeSendTransformer);\n } else {\n app.setTransformer('beforeBatch', fn as BeforeBatchTransformer);\n }\n}\n\n/**\n * Removes a previously set transformer function.\n *\n * @param hook - Transformer hook type to remove ('beforeSend' or 'beforeBatch')\n * @throws {Error} If called during destroy()\n *\n * @example\n * ```typescript\n * tracelog.removeTransformer('beforeSend');\n * ```\n */\nexport const removeTransformer = (hook: TransformerHook): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (!app) {\n const index = pendingTransformers.findIndex((t) => t.hook === hook);\n\n if (index !== -1) {\n pendingTransformers.splice(index, 1);\n }\n\n return;\n }\n\n if (isDestroying) {\n throw new Error('[TraceLog] Cannot remove transformers while TraceLog is being destroyed');\n }\n\n app.removeTransformer(hook);\n};\n\n/**\n * Checks if TraceLog is currently initialized.\n *\n * @returns true if initialized, false otherwise\n *\n * @example\n * ```typescript\n * if (tracelog.isInitialized()) {\n * tracelog.event('app_ready');\n * }\n * ```\n */\nexport const isInitialized = (): boolean => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return false;\n }\n\n return app !== null;\n};\n\n/**\n * Stops all tracking, cleans up listeners, and flushes pending events.\n *\n * Sends remaining events with sendBeacon before cleanup.\n *\n * @throws {Error} If destroy operation is already in progress\n *\n * @example\n * ```typescript\n * tracelog.destroy();\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#destroy} for usage patterns\n */\nexport const destroy = (): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (isDestroying) {\n throw new Error('[TraceLog] Destroy operation already in progress');\n }\n\n if (!app) {\n isDestroying = false;\n\n return;\n }\n\n isDestroying = true;\n\n try {\n app.destroy();\n app = null;\n isInitializing = false;\n pendingListeners.length = 0;\n pendingTransformers.length = 0;\n\n if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined' && window.__traceLogBridge) {\n window.__traceLogBridge = undefined;\n }\n\n isDestroying = false;\n } catch (error) {\n app = null;\n isInitializing = false;\n\n pendingListeners.length = 0;\n pendingTransformers.length = 0;\n\n isDestroying = false;\n\n log('warn', 'Error during destroy, forced cleanup completed', { error });\n }\n};\n\n/**\n * Enables or disables QA (Quality Assurance) mode for debugging.\n *\n * QA mode logs custom events to console instead of sending to backend.\n *\n * @param enabled - true to enable, false to disable\n *\n * @example\n * ```typescript\n * tracelog.setQaMode(true);\n * tracelog.event('test', { key: 'value' }); // Logged to console\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#setqamode} for URL activation\n */\nexport const setQaMode = (enabled: boolean): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n setQaModeUtil(enabled);\n};\n\n/**\n * Replaces all global metadata with new values.\n *\n * Global metadata is automatically appended to every event.\n *\n * @param metadata - New metadata object (replaces existing)\n * @throws {Error} If TraceLog not initialized\n * @throws {Error} If called during destroy()\n * @throws {Error} If validation fails (max 100 keys, 10KB limit)\n *\n * @example\n * ```typescript\n * tracelog.updateGlobalMetadata({ userId: 'user-123', plan: 'pro' });\n * // Replaces all existing metadata\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#updateglobalmetadata} for validation rules\n */\nexport const updateGlobalMetadata = (metadata: Record<string, MetadataType>): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (!app) {\n throw new Error('[TraceLog] TraceLog not initialized. Please call init() first.');\n }\n\n if (isDestroying) {\n throw new Error('[TraceLog] Cannot update metadata while TraceLog is being destroyed');\n }\n\n app.updateGlobalMetadata(metadata);\n};\n\n/**\n * Merges new metadata with existing global metadata (shallow merge).\n *\n * Global metadata is automatically appended to every event.\n *\n * @param metadata - Metadata to merge with existing values\n * @throws {Error} If TraceLog not initialized\n * @throws {Error} If called during destroy()\n * @throws {Error} If validation fails (max 100 keys, 10KB limit)\n *\n * @example\n * ```typescript\n * tracelog.mergeGlobalMetadata({ userId: 'user-123' });\n * // Preserves existing keys, adds/overwrites specified keys\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#mergeglobalmetadata} for validation rules\n */\nexport const mergeGlobalMetadata = (metadata: Record<string, MetadataType>): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (!app) {\n throw new Error('[TraceLog] TraceLog not initialized. Please call init() first.');\n }\n\n if (isDestroying) {\n throw new Error('[TraceLog] Cannot update metadata while TraceLog is being destroyed');\n }\n\n app.mergeGlobalMetadata(metadata);\n};\n\n/**\n * @internal TestBridge API - development only\n */\nexport const __setAppInstance = (instance: App | null): void => {\n if (process.env.NODE_ENV !== 'development') {\n return;\n }\n\n if (instance !== null) {\n const hasRequiredMethods =\n typeof instance === 'object' &&\n 'init' in instance &&\n 'destroy' in instance &&\n 'on' in instance &&\n 'off' in instance;\n\n if (!hasRequiredMethods) {\n throw new Error('[TraceLog] Invalid app instance type');\n }\n }\n\n if (app !== null && instance !== null && app !== instance) {\n throw new Error('[TraceLog] Cannot overwrite existing app instance. Call destroy() first.');\n }\n\n app = instance;\n};\n\n/**\n * @internal TestBridge state accessors - development only\n */\nexport const __getInitState = (): { isInitializing: boolean; isDestroying: boolean } => {\n if (process.env.NODE_ENV !== 'development') {\n return { isInitializing: false, isDestroying: false };\n }\n return { isInitializing, isDestroying };\n};\n\nif (process.env.NODE_ENV === 'development' && typeof window !== 'undefined' && typeof document !== 'undefined') {\n void import('./test-bridge')\n .then((module) => {\n if (typeof module.injectTestBridge === 'function') {\n module.injectTestBridge();\n }\n })\n .catch(() => {\n // Silent fail - TestBridge is optional in test environments\n });\n\n void import('./utils/browser/mode.utils')\n .then((module) => {\n if (typeof module.detectQaMode === 'function') {\n module.detectQaMode();\n }\n })\n .catch(() => {\n // Silent fail - mode detection is optional\n });\n}\n","import {\n init,\n event,\n on,\n off,\n isInitialized,\n destroy,\n setQaMode,\n setTransformer,\n removeTransformer,\n updateGlobalMetadata,\n mergeGlobalMetadata,\n} from './api';\n\n// Constants\nexport * from './app.constants';\n\n// Types\nexport * from './types';\n\n// TraceLog namespace containing all API methods\nexport const tracelog = {\n init,\n event,\n on,\n off,\n setTransformer,\n removeTransformer,\n isInitialized,\n destroy,\n setQaMode,\n updateGlobalMetadata,\n mergeGlobalMetadata,\n};\n","var e,n,t,r,i,o=-1,a=function(e){addEventListener(\"pageshow\",(function(n){n.persisted&&(o=n.timeStamp,e(n))}),!0)},c=function(){var e=self.performance&&performance.getEntriesByType&&performance.getEntriesByType(\"navigation\")[0];if(e&&e.responseStart>0&&e.responseStart<performance.now())return e},u=function(){var e=c();return e&&e.activationStart||0},f=function(e,n){var t=c(),r=\"navigate\";o>=0?r=\"back-forward-cache\":t&&(document.prerendering||u()>0?r=\"prerender\":document.wasDiscarded?r=\"restore\":t.type&&(r=t.type.replace(/_/g,\"-\")));return{name:e,value:void 0===n?-1:n,rating:\"good\",delta:0,entries:[],id:\"v4-\".concat(Date.now(),\"-\").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:r}},s=function(e,n,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){var r=new PerformanceObserver((function(e){Promise.resolve().then((function(){n(e.getEntries())}))}));return r.observe(Object.assign({type:e,buffered:!0},t||{})),r}}catch(e){}},d=function(e,n,t,r){var i,o;return function(a){n.value>=0&&(a||r)&&((o=n.value-(i||0))||void 0===i)&&(i=n.value,n.delta=o,n.rating=function(e,n){return e>n[1]?\"poor\":e>n[0]?\"needs-improvement\":\"good\"}(n.value,t),e(n))}},l=function(e){requestAnimationFrame((function(){return requestAnimationFrame((function(){return e()}))}))},p=function(e){document.addEventListener(\"visibilitychange\",(function(){\"hidden\"===document.visibilityState&&e()}))},v=function(e){var n=!1;return function(){n||(e(),n=!0)}},m=-1,h=function(){return\"hidden\"!==document.visibilityState||document.prerendering?1/0:0},g=function(e){\"hidden\"===document.visibilityState&&m>-1&&(m=\"visibilitychange\"===e.type?e.timeStamp:0,T())},y=function(){addEventListener(\"visibilitychange\",g,!0),addEventListener(\"prerenderingchange\",g,!0)},T=function(){removeEventListener(\"visibilitychange\",g,!0),removeEventListener(\"prerenderingchange\",g,!0)},E=function(){return m<0&&(m=h(),y(),a((function(){setTimeout((function(){m=h(),y()}),0)}))),{get firstHiddenTime(){return m}}},C=function(e){document.prerendering?addEventListener(\"prerenderingchange\",(function(){return e()}),!0):e()},b=[1800,3e3],S=function(e,n){n=n||{},C((function(){var t,r=E(),i=f(\"FCP\"),o=s(\"paint\",(function(e){e.forEach((function(e){\"first-contentful-paint\"===e.name&&(o.disconnect(),e.startTime<r.firstHiddenTime&&(i.value=Math.max(e.startTime-u(),0),i.entries.push(e),t(!0)))}))}));o&&(t=d(e,i,b,n.reportAllChanges),a((function(r){i=f(\"FCP\"),t=d(e,i,b,n.reportAllChanges),l((function(){i.value=performance.now()-r.timeStamp,t(!0)}))})))}))},L=[.1,.25],w=function(e,n){n=n||{},S(v((function(){var t,r=f(\"CLS\",0),i=0,o=[],c=function(e){e.forEach((function(e){if(!e.hadRecentInput){var n=o[0],t=o[o.length-1];i&&e.startTime-t.startTime<1e3&&e.startTime-n.startTime<5e3?(i+=e.value,o.push(e)):(i=e.value,o=[e])}})),i>r.value&&(r.value=i,r.entries=o,t())},u=s(\"layout-shift\",c);u&&(t=d(e,r,L,n.reportAllChanges),p((function(){c(u.takeRecords()),t(!0)})),a((function(){i=0,r=f(\"CLS\",0),t=d(e,r,L,n.reportAllChanges),l((function(){return t()}))})),setTimeout(t,0))})))},A=0,I=1/0,P=0,M=function(e){e.forEach((function(e){e.interactionId&&(I=Math.min(I,e.interactionId),P=Math.max(P,e.interactionId),A=P?(P-I)/7+1:0)}))},k=function(){return e?A:performance.interactionCount||0},F=function(){\"interactionCount\"in performance||e||(e=s(\"event\",M,{type:\"event\",buffered:!0,durationThreshold:0}))},D=[],x=new Map,R=0,B=function(){var e=Math.min(D.length-1,Math.floor((k()-R)/50));return D[e]},H=[],q=function(e){if(H.forEach((function(n){return n(e)})),e.interactionId||\"first-input\"===e.entryType){var n=D[D.length-1],t=x.get(e.interactionId);if(t||D.length<10||e.duration>n.latency){if(t)e.duration>t.latency?(t.entries=[e],t.latency=e.duration):e.duration===t.latency&&e.startTime===t.entries[0].startTime&&t.entries.push(e);else{var r={id:e.interactionId,latency:e.duration,entries:[e]};x.set(r.id,r),D.push(r)}D.sort((function(e,n){return n.latency-e.latency})),D.length>10&&D.splice(10).forEach((function(e){return x.delete(e.id)}))}}},O=function(e){var n=self.requestIdleCallback||self.setTimeout,t=-1;return e=v(e),\"hidden\"===document.visibilityState?e():(t=n(e),p(e)),t},N=[200,500],j=function(e,n){\"PerformanceEventTiming\"in self&&\"interactionId\"in PerformanceEventTiming.prototype&&(n=n||{},C((function(){var t;F();var r,i=f(\"INP\"),o=function(e){O((function(){e.forEach(q);var n=B();n&&n.latency!==i.value&&(i.value=n.latency,i.entries=n.entries,r())}))},c=s(\"event\",o,{durationThreshold:null!==(t=n.durationThreshold)&&void 0!==t?t:40});r=d(e,i,N,n.reportAllChanges),c&&(c.observe({type:\"first-input\",buffered:!0}),p((function(){o(c.takeRecords()),r(!0)})),a((function(){R=k(),D.length=0,x.clear(),i=f(\"INP\"),r=d(e,i,N,n.reportAllChanges)})))})))},_=[2500,4e3],z={},G=function(e,n){n=n||{},C((function(){var t,r=E(),i=f(\"LCP\"),o=function(e){n.reportAllChanges||(e=e.slice(-1)),e.forEach((function(e){e.startTime<r.firstHiddenTime&&(i.value=Math.max(e.startTime-u(),0),i.entries=[e],t())}))},c=s(\"largest-contentful-paint\",o);if(c){t=d(e,i,_,n.reportAllChanges);var m=v((function(){z[i.id]||(o(c.takeRecords()),c.disconnect(),z[i.id]=!0,t(!0))}));[\"keydown\",\"click\"].forEach((function(e){addEventListener(e,(function(){return O(m)}),{once:!0,capture:!0})})),p(m),a((function(r){i=f(\"LCP\"),t=d(e,i,_,n.reportAllChanges),l((function(){i.value=performance.now()-r.timeStamp,z[i.id]=!0,t(!0)}))}))}}))},J=[800,1800],K=function e(n){document.prerendering?C((function(){return e(n)})):\"complete\"!==document.readyState?addEventListener(\"load\",(function(){return e(n)}),!0):setTimeout(n,0)},Q=function(e,n){n=n||{};var t=f(\"TTFB\"),r=d(e,t,J,n.reportAllChanges);K((function(){var i=c();i&&(t.value=Math.max(i.responseStart-u(),0),t.entries=[i],r(!0),a((function(){t=f(\"TTFB\",0),(r=d(e,t,J,n.reportAllChanges))(!0)})))}))},U={passive:!0,capture:!0},V=new Date,W=function(e,i){n||(n=i,t=e,r=new Date,Z(removeEventListener),X())},X=function(){if(t>=0&&t<r-V){var e={entryType:\"first-input\",name:n.type,target:n.target,cancelable:n.cancelable,startTime:n.timeStamp,processingStart:n.timeStamp+t};i.forEach((function(n){n(e)})),i=[]}},Y=function(e){if(e.cancelable){var n=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;\"pointerdown\"==e.type?function(e,n){var t=function(){W(e,n),i()},r=function(){i()},i=function(){removeEventListener(\"pointerup\",t,U),removeEventListener(\"pointercancel\",r,U)};addEventListener(\"pointerup\",t,U),addEventListener(\"pointercancel\",r,U)}(n,e):W(n,e)}},Z=function(e){[\"mousedown\",\"keydown\",\"touchstart\",\"pointerdown\"].forEach((function(n){return e(n,Y,U)}))},$=[100,300],ee=function(e,r){r=r||{},C((function(){var o,c=E(),u=f(\"FID\"),l=function(e){e.startTime<c.firstHiddenTime&&(u.value=e.processingStart-e.startTime,u.entries.push(e),o(!0))},m=function(e){e.forEach(l)},h=s(\"first-input\",m);o=d(e,u,$,r.reportAllChanges),h&&(p(v((function(){m(h.takeRecords()),h.disconnect()}))),a((function(){var a;u=f(\"FID\"),o=d(e,u,$,r.reportAllChanges),i=[],t=-1,n=null,Z(addEventListener),a=l,i.push(a),X()})))}))};export{L as CLSThresholds,b as FCPThresholds,$ as FIDThresholds,N as INPThresholds,_ as LCPThresholds,J as TTFBThresholds,w as onCLS,S as onFCP,ee as onFID,j as onINP,G as onLCP,Q as onTTFB};\n"],"names":["DEFAULT_SESSION_TIMEOUT","MAX_CUSTOM_EVENT_NAME_LENGTH","MAX_CUSTOM_EVENT_STRING_SIZE","MAX_CUSTOM_EVENT_KEYS","MAX_CUSTOM_EVENT_ARRAY_SIZE","MAX_NESTED_OBJECT_KEYS","MAX_METADATA_NESTING_DEPTH","MAX_STRING_LENGTH","MAX_STRING_LENGTH_IN_ARRAY","MAX_ARRAY_LENGTH","HTML_DATA_ATTR_PREFIX","INTERACTIVE_SELECTORS","UTM_PARAMS","DEFAULT_SENSITIVE_QUERY_PARAMS","VALIDATION_MESSAGES","XSS_PATTERNS","STORAGE_BASE_KEY","QA_MODE_KEY","USER_ID_KEY","QA_MODE_URL_PARAM","QA_MODE_ENABLE_VALUE","QA_MODE_DISABLE_VALUE","QUEUE_KEY","id","SESSION_STORAGE_KEY","BROADCAST_CHANNEL_NAME","SESSION_COUNTS_KEY","userId","sessionId","SESSION_COUNTS_EXPIRY_MS","SESSION_COUNTS_LAST_CLEANUP_KEY","SESSION_COUNTS_CLEANUP_THROTTLE_MS","SpecialApiUrl","DeviceType","EmitterEvent","PermanentError","message","statusCode","EventType","ScrollDirection","ErrorType","Mode","isPrimaryScrollEvent","event","isSecondaryScrollEvent","TraceLogValidationError","errorCode","layer","AppConfigValidationError","SessionTimeoutValidationError","SamplingRateValidationError","IntegrationValidationError","InitializationTimeoutError","timeoutMs","LOG_STYLE_ACTIVE","LOG_STYLE_DISABLED","LOG_STYLE_CRITICAL","formatLogMsg","msg","error","sanitizedMessage","isQaModeActive","log","type","extra","data","showToClient","style","visibility","formattedMsg","method","shouldShowLog","effectiveStyle","getEffectiveStyle","sanitizedData","sanitizeLogData","outputLog","providedStyle","hasStyle","styledMsg","sanitized","sensitiveKeys","key","value","lowerKey","sensitiveKey","item","coarsePointerQuery","noHoverQuery","initMediaQueries","UNKNOWN","detectOS","nav","platform","ua","detectBrowser","brands","firstBrand","b","brand","getDeviceType","uaPlatform","width","hasCoarsePointer","hasNoHover","hasTouchSupport","isMobileUA","isTabletUA","getDeviceInfo","PII_PATTERNS","MAX_ERROR_MESSAGE_LENGTH","ERROR_SUPPRESSION_WINDOW_MS","MAX_TRACKED_ERRORS","MAX_TRACKED_ERRORS_HARD_LIMIT","DEFAULT_ERROR_SAMPLING_RATE","ERROR_BURST_WINDOW_MS","ERROR_BURST_THRESHOLD","ERROR_BURST_BACKOFF_MS","PERMANENT_ERROR_LOG_THROTTLE_MS","WEB_VITALS_GOOD_THRESHOLDS","WEB_VITALS_NEEDS_IMPROVEMENT_THRESHOLDS","WEB_VITALS_POOR_THRESHOLDS","DEFAULT_WEB_VITALS_MODE","getWebVitalsThresholds","mode","LONG_TASK_THROTTLE_MS","MAX_NAVIGATION_HISTORY","LIB_VERSION","version","isBrowserEnvironment","cleanUrlParameter","params","search","url","detectQaMode","urlParam","storedState","newState","setQaMode","enabled","COMPOUND_TLDS","getRootDomain","hostname","parts","lastTwo","isSameDomain","hostname1","hostname2","getExternalReferrer","referrer","referrerHostname","currentHostname","getUTMParameters","urlParams","utmParams","param","generateUUID","c","r","eventSequence","lastEventTimestamp","generateEventId","timestamp","sequence","random","bytes","isValidUrl","allowHttp","parsed","isHttps","isHttp","generateSaasApiUrl","projectId","host","cleanDomain","collectApiUrl","getCollectApiUrls","config","urls","customApiUrl","normalizeUrl","sensitiveQueryParams","urlObject","searchParams","allSensitiveParams","hasChanged","removedParams","sanitizeString","xssPatternMatches","pattern","beforeReplace","sanitizeValue","depth","sanitizedObject","limitedEntries","value_","sanitizedKey","sanitizedValue","sanitizeMetadata","metadata","errorMessage","validateAppConfig","validateIntegrations","validateViewportConfig","validModes","validKeys","viewport","uniqueSelectors","element","normalizedSelector","integrations","validateAndNormalizeConfig","normalizedConfig","isValidArrayItem","entries","isOnlyPrimitiveFields","object","isValidEventName","eventName","validateSingleMetadata","sanitizedMetadata","intro","jsonString","isValidMetadata","sanitizedArray","itemValidation","isEventValid","nameValidation","metadataValidation","Emitter","callback","callbacks","index","transformEvent","transformer","context","result","transformEvents","events","transformBatch","batch","globalState","StateManager","SenderManager","storeManager","integrationId","apiUrl","transformers","baseKey","body","success","persistedData","beforeSendTransformer","transformedEvents","beforeBatchTransformer","attempt","exponentialDelay","jitter","totalDelay","resolve","afterBeforeSend","transformedBody","payload","isLastAttempt","controller","timeoutId","response","blob","accepted","enrichedBody","storageKey","persistedDataString","queue","existing","timeSinceExisting","delay","now","TimeManager","elapsed","monotonicTimestamp","systemTimestamp","currentTime","offset","EventManager","emitter","collectApiUrls","recoveryPromises","sender","_eventCount","recoveredEvents","eventIds","e","page_url","from_page_url","scroll_data","click_data","custom_event","web_vitals","error_data","viewport_data","page_view","currentSessionId","isCriticalEvent","eventType","typeLimit","currentCount","maxSameEventPerMinute","isSessionStart","currentPageUrl","displayName","bufferedEvents","isSync","eventsToSend","anySucceeded","sendPromises","results","failedCount","eventMap","order","signature","a","hasAnyBackend","transformed","validation","sessionReferrer","sessionUtm","hasCustomBackend","hasSaasBackend","isMultiIntegration","fingerprint","lastSeen","cutoff","x","y","nonCriticalIndex","removedEvent","samplingRate","validTimestamps","ts","eventIdSet","eventData","fn","args","stored","lastCleanup","timeSinceLastCleanup","prefix","keysToRemove","dataToStore","UserManager","storageManager","storedUserId","newUserId","SESSION_ID_PATTERN","SessionManager","eventManager","action","messageProjectId","storedSession","sessionTimeout","lastActivity","utm","storedData","session","recoveredSessionId","newSessionId","SessionHandler","PageViewHandler","onTrack","original","rawUrl","normalizedUrl","throttleMs","fromUrl","pageViewData","pathname","hash","title","ClickHandler","mouseEvent","target","clickedElement","clickThrottleMs","trackingElement","relevantClickElement","coordinates","trackingData","attributeData","clickData","lastClickTime","excessCount","toDelete","testId","tlogName","path","current","selector","firstClass","parent","rect","relativeX","relativeY","name","relevantElement","text","attributes","regex","clickedText","relevantText","finalText","commonAttributes","attributeName","ScrollHandler","container","elements","walker","node","htmlElement","initialScrollTop","initialDepth","isPrimary","handleScroll","scrollData","newDepth","previous","scrollTop","scrollHeight","viewportHeight","maxScrollTop","lastScrollPos","lastEventTime","positionDelta","direction","timeDelta","velocity","hasVerticalScrollableOverflow","hasVerticalOverflowContent","targetElement","ViewportHandler","threshold","minDwellTime","tracked","maxTrackedElements","totalTracked","elementConfig","entry","visibilityRatio","dwellTime","cooldownPeriod","mutations","hasAddedNodes","mutation","removedNodes","el","descendantTracked","StorageManager","retryError","i","tracelogKeys","persistedEventsKeys","criticalPrefixes","nonCriticalKeys","storage","testKey","PerformanceHandler","obs","list","last","clsValue","currentNavId","navId","worst","dur","onLCP","onCLS","onFCP","onTTFB","onINP","webVitals","report","metric","ttfb","duration","sample","reportedForNav","oldestNav","counter","baseId","supported","cb","options","once","observer","callbackError","ErrorHandler","reason","lastSeenAt","excess","App","normalizedMetadata","valid","hook","force","handler","device","pageUrl","updatedConfig","currentConfig","mergedMetadata","onPageView","pendingListeners","pendingTransformers","app","isInitializing","isDestroying","init","validatedConfig","instance","initPromise","timeoutPromise","_","reject","cleanupError","on","off","l","setTransformer","existingIndex","t","removeTransformer","isInitialized","destroy","setQaModeUtil","updateGlobalMetadata","mergeGlobalMetadata","tracelog","o","n","u","f","s","d","p","v","m","h","g","T","E","C","S","L","w","A","I","P","M","k","F","D","R","B","H","q","O","N","j","z","G","J","K","Q"],"mappings":"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","x_google_ignoreList":[45]}
1
+ {"version":3,"file":"tracelog.esm.js","sources":["../../src/constants/config.constants.ts","../../src/constants/storage.constants.ts","../../src/types/config.types.ts","../../src/types/device.types.ts","../../src/types/emitter.types.ts","../../src/types/error.types.ts","../../src/types/event.types.ts","../../src/types/mode.types.ts","../../src/types/scroll.types.ts","../../src/types/validation-error.types.ts","../../src/constants/app.constants.ts","../../src/utils/logging.utils.ts","../../src/utils/browser/device-detector.utils.ts","../../src/constants/error.constants.ts","../../src/constants/performance.constants.ts","../../src/constants/version.constants.ts","../../src/utils/browser/mode.utils.ts","../../src/utils/browser/referrer.utils.ts","../../src/utils/browser/utm-params.utils.ts","../../src/utils/data/uuid.utils.ts","../../src/utils/network/url.utils.ts","../../src/utils/security/sanitize.utils.ts","../../src/utils/validations/config-validations.utils.ts","../../src/utils/validations/type-guards.utils.ts","../../src/utils/validations/metadata-validations.utils.ts","../../src/utils/validations/event-validations.utils.ts","../../src/utils/emitter.utils.ts","../../src/utils/transformer.utils.ts","../../src/managers/state.manager.ts","../../src/managers/sender.manager.ts","../../src/managers/time.manager.ts","../../src/managers/event.manager.ts","../../src/managers/user.manager.ts","../../src/managers/session.manager.ts","../../src/handlers/session.handler.ts","../../src/handlers/page-view.handler.ts","../../src/handlers/click.handler.ts","../../src/handlers/scroll.handler.ts","../../src/handlers/viewport.handler.ts","../../src/managers/storage.manager.ts","../../src/handlers/performance.handler.ts","../../src/handlers/error.handler.ts","../../src/app.ts","../../src/api.ts","../../src/public-api.ts","../../node_modules/web-vitals/dist/web-vitals.js"],"sourcesContent":["/**\n * Consolidated configuration constants for TraceLog\n * This file centralizes all timing, limits, browser, and initialization constants\n */\n\n// ============================================================================\n// SESSION & TIMING\n// ============================================================================\n\nexport const DEFAULT_SESSION_TIMEOUT = 15 * 60 * 1000; // 15 minutes\nexport const DUPLICATE_EVENT_THRESHOLD_MS = 1000; // 1 second (increased from 500ms to reduce duplicate events)\nexport const EVENT_SENT_INTERVAL_MS = 10000; // 10 seconds\n\n// Throttling and debouncing\nexport const SCROLL_DEBOUNCE_TIME_MS = 250;\nexport const DEFAULT_VISIBILITY_TIMEOUT_MS = 2000;\nexport const DEFAULT_PAGE_VIEW_THROTTLE_MS = 1000; // 1 second throttle for page views\nexport const DEFAULT_CLICK_THROTTLE_MS = 300; // 300ms throttle for clicks per element\nexport const DEFAULT_VIEWPORT_COOLDOWN_PERIOD = 60000; // 60 seconds cooldown for viewport events\nexport const DEFAULT_VIEWPORT_MAX_TRACKED_ELEMENTS = 100; // Maximum elements to track (Phase 3)\nexport const VIEWPORT_MUTATION_DEBOUNCE_MS = 100; // Debounce for mutation observer re-scanning\n\n// Click throttle cache limits\nexport const MAX_THROTTLE_CACHE_ENTRIES = 1000; // Maximum element signatures to track\nexport const THROTTLE_ENTRY_TTL_MS = 300000; // 5 minutes TTL for throttle entries\nexport const THROTTLE_PRUNE_INTERVAL_MS = 30000; // 30 seconds interval for cache pruning\n\n// Event expiry\nexport const EVENT_EXPIRY_HOURS = 2;\nexport const EVENT_PERSISTENCE_MAX_AGE_MS = 2 * 60 * 60 * 1000; // 2 hours\nexport const PERSISTENCE_THROTTLE_MS = 1000; // 1 second throttle for cross-tab persistence coordination\n\n// ============================================================================\n// LIMITS & REQUESTS\n// ============================================================================\n\nexport const MAX_EVENTS_QUEUE_LENGTH = 100;\nexport const REQUEST_TIMEOUT_MS = 15000; // 15 seconds (to ensure requests complete before tab close/navigation)\nexport const MAX_METADATA_SIZE = 5000;\n\n// Motion and interaction thresholds\nexport const DEFAULT_MOTION_THRESHOLD = 2;\nexport const SIGNIFICANT_SCROLL_DELTA = 10;\nexport const MIN_SCROLL_DEPTH_CHANGE = 5;\nexport const SCROLL_MIN_EVENT_INTERVAL_MS = 500;\nexport const MAX_SCROLL_EVENTS_PER_SESSION = 120;\n\n// Sampling and rate limits\nexport const DEFAULT_SAMPLING_RATE = 1;\nexport const MIN_SAMPLING_RATE = 0;\nexport const MAX_SAMPLING_RATE = 1;\nexport const RATE_LIMIT_WINDOW_MS = 1000; // 1 second window\nexport const MAX_EVENTS_PER_SECOND = 50; // Maximum 50 events per second (Phase 3: reduced from 200)\nexport const MAX_SAME_EVENT_PER_MINUTE = 60; // Maximum same custom event name per minute (prevents infinite loops)\nexport const PER_EVENT_RATE_LIMIT_WINDOW_MS = 60000; // 60 second window for per-event-name rate limiting\n\n// Per-session event caps (Phase 3)\nexport const MAX_EVENTS_PER_SESSION = 1000;\nexport const MAX_CLICKS_PER_SESSION = 500;\nexport const MAX_PAGE_VIEWS_PER_SESSION = 100;\nexport const MAX_CUSTOM_EVENTS_PER_SESSION = 500;\nexport const MAX_VIEWPORT_EVENTS_PER_SESSION = 200;\n\n// Queue and batch limits\nexport const BATCH_SIZE_THRESHOLD = 50;\nexport const MAX_PENDING_EVENTS_BUFFER = 100; // Maximum events to buffer before session init\n\n// Session timeout validation limits\nexport const MIN_SESSION_TIMEOUT_MS = 30000; // 30 seconds minimum\nexport const MAX_SESSION_TIMEOUT_MS = 86400000; // 24 hours maximum\n\n// Custom event validation limits\nexport const MAX_CUSTOM_EVENT_NAME_LENGTH = 120;\nexport const MAX_CUSTOM_EVENT_STRING_SIZE = 8 * 1024; // 8KB\nexport const MAX_CUSTOM_EVENT_KEYS = 10;\nexport const MAX_CUSTOM_EVENT_ARRAY_SIZE = 10;\nexport const MAX_NESTED_OBJECT_KEYS = 20; // Maximum keys in nested objects within arrays\nexport const MAX_METADATA_NESTING_DEPTH = 1; // Maximum nesting depth for metadata objects\n\n// Text content limits\nexport const MAX_TEXT_LENGTH = 255; // For click tracking text content\n\n// Data sanitization limits\nexport const MAX_STRING_LENGTH = 1000;\nexport const MAX_STRING_LENGTH_IN_ARRAY = 500; // Strings within arrays are more limited\nexport const MAX_ARRAY_LENGTH = 100;\nexport const MAX_OBJECT_DEPTH = 3;\n\n// Precision for numeric metrics\nexport const PRECISION_TWO_DECIMALS = 2 as const;\n\n// Sync XHR timeout\nexport const SYNC_XHR_TIMEOUT_MS = 2000; // 2 seconds\n\n// sendBeacon payload size limit (Phase 3)\nexport const MAX_BEACON_PAYLOAD_SIZE = 64 * 1024; // 64KB browser limit\n\n// Event fingerprint management (moderately increased for high-frequency sessions)\nexport const MAX_FINGERPRINTS = 1500; // Maximum fingerprints stored before cleanup (increased from 1000)\nexport const FINGERPRINT_CLEANUP_MULTIPLIER = 10; // Cleanup fingerprints older than 10x threshold (10 seconds)\nexport const MAX_FINGERPRINTS_HARD_LIMIT = 3000; // Hard limit for aggressive cleanup (increased from 2000)\n\n// ============================================================================\n// BROWSER & HTML\n// ============================================================================\n\n/**\n * Prefix for all HTML data attributes used by TraceLog\n * Used for features like data-tlog-ignore, data-tlog-event, etc.\n */\nexport const HTML_DATA_ATTR_PREFIX = 'data-tlog';\n\n/**\n * CSS selectors for interactive elements to track in click events\n *\n * Covers:\n * - Standard HTML interactive elements (button, a, input, select, textarea)\n * - ARIA roles (button, link, tab, menuitem, option, checkbox, radio, switch)\n * - Framework-specific attributes (routerLink, ng-click)\n * - Data attributes for actions (data-action, data-click, data-navigate, data-toggle)\n * - Common CSS classes (.btn, .button, .clickable, .nav-link, .menu-item)\n * - Testing attributes (data-testid)\n * - Accessibility (tabindex=\"0\")\n */\nexport const INTERACTIVE_SELECTORS = [\n 'button',\n 'a',\n 'input[type=\"button\"]',\n 'input[type=\"submit\"]',\n 'input[type=\"reset\"]',\n 'input[type=\"checkbox\"]',\n 'input[type=\"radio\"]',\n 'select',\n 'textarea',\n '[role=\"button\"]',\n '[role=\"link\"]',\n '[role=\"tab\"]',\n '[role=\"menuitem\"]',\n '[role=\"option\"]',\n '[role=\"checkbox\"]',\n '[role=\"radio\"]',\n '[role=\"switch\"]',\n '[routerLink]',\n '[ng-click]',\n '[data-action]',\n '[data-click]',\n '[data-navigate]',\n '[data-toggle]',\n '[onclick]',\n '.btn',\n '.button',\n '.clickable',\n '.nav-link',\n '.menu-item',\n '[data-testid]',\n '[tabindex=\"0\"]',\n] as const;\n\n/**\n * Standard UTM (Urchin Tracking Module) parameters for marketing attribution\n * These parameters are preserved in URLs for campaign tracking\n */\nexport const UTM_PARAMS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];\n\n/**\n * Default list of sensitive URL query parameters to filter out for privacy protection\n *\n * Includes:\n * - Authentication tokens (token, auth, key, session, access_token, refresh_token)\n * - Password reset links (reset, password, verification, code, otp)\n * - API keys (api_key, apikey, secret)\n *\n * These parameters are removed from tracked URLs to prevent PII leakage\n */\nexport const DEFAULT_SENSITIVE_QUERY_PARAMS = [\n 'token',\n 'auth',\n 'key',\n 'session',\n 'reset',\n 'password',\n 'api_key',\n 'apikey',\n 'secret',\n 'access_token',\n 'refresh_token',\n 'verification',\n 'code',\n 'otp',\n] as const;\n\n// ============================================================================\n// ============================================================================\n// INITIALIZATION\n// ============================================================================\n\nexport const INITIALIZATION_MAX_CONCURRENT_RETRIES = 20;\nexport const INITIALIZATION_CONCURRENT_RETRY_DELAY_MS = 50;\nexport const INITIALIZATION_TIMEOUT_MS = 10000;\n\n// ============================================================================\n// SESSION MANAGEMENT\n// ============================================================================\n\nexport const SESSION_SYNC_TIMEOUT_MS = 2000;\nexport const SESSION_MAX_RETRY_ATTEMPTS = 3;\nexport const SESSION_CLEANUP_DELAY_MS = 100;\n\n// Cross-tab coordination\nexport const CROSS_TAB_INITIALIZATION_LOCK_TIMEOUT_MS = 5000;\nexport const TAB_HEARTBEAT_INTERVAL_MS = 5000; // 5 seconds\nexport const TAB_ELECTION_TIMEOUT_MS = 2000; // 2 seconds\nexport const TAB_CLEANUP_DELAY_MS = 1000; // 1 second\n\n// Session recovery\nexport const SESSION_RECOVERY_WINDOW_MULTIPLIER = 2; // 2x session timeout\nexport const MAX_SESSION_RECOVERY_ATTEMPTS = 3;\nexport const MAX_SESSION_RECOVERY_WINDOW_MS = 24 * 60 * 60 * 1000; // 24 hours max\nexport const MIN_SESSION_RECOVERY_WINDOW_MS = 2 * 60 * 1000; // 2 minutes minimum\n\n// ============================================================================\n// SCROLL SUPPRESSION\n// ============================================================================\n\nexport const SCROLL_SUPPRESS_MULTIPLIER = 2;\n\n// ============================================================================\n// NETWORK TIMING\n// ============================================================================\n\nexport const RATE_LIMIT_INTERVAL = 1000; // 1 second\n\n// ============================================================================\n// RETRY CONFIGURATION\n// ============================================================================\n\n/**\n * Maximum number of retry attempts for failed event transmissions\n * Applied to 5xx errors and network timeouts (transient failures)\n * 4xx errors (permanent) are not retried\n */\nexport const MAX_SEND_RETRIES = 2;\n\n/**\n * Base delay for exponential backoff retry strategy (in milliseconds)\n * Formula: RETRY_BACKOFF_BASE_MS * (2 ^ attempt) + jitter\n * Example: attempt 1 = 100ms + jitter, attempt 2 = 200ms + jitter\n */\nexport const RETRY_BACKOFF_BASE_MS = 100;\n\n/**\n * Maximum random jitter added to retry backoff delay (in milliseconds)\n * Prevents thundering herd problem when multiple clients retry simultaneously\n * Jitter range: 0 to RETRY_BACKOFF_JITTER_MS\n */\nexport const RETRY_BACKOFF_JITTER_MS = 100;\n\n// ============================================================================\n// VALIDATION\n// ============================================================================\n\n/**\n * Standardized validation error messages for TraceLog configuration\n *\n * Centralizes all validation messages to ensure consistency across:\n * - API layer validation\n * - Configuration validation\n * - Runtime validation\n *\n * Messages include contextual information (e.g., min/max values) to help developers\n * quickly identify and fix configuration issues.\n */\nexport const VALIDATION_MESSAGES = {\n MISSING_PROJECT_ID: 'Project ID is required',\n PROJECT_ID_EMPTY_AFTER_TRIM: 'Project ID is required',\n INVALID_SESSION_TIMEOUT: `Session timeout must be between ${MIN_SESSION_TIMEOUT_MS}ms (30 seconds) and ${MAX_SESSION_TIMEOUT_MS}ms (24 hours)`,\n INVALID_SAMPLING_RATE: 'Sampling rate must be between 0 and 1',\n INVALID_ERROR_SAMPLING_RATE: 'Error sampling must be between 0 and 1',\n INVALID_TRACELOG_PROJECT_ID: 'TraceLog project ID is required when integration is enabled',\n INVALID_CUSTOM_API_URL: 'Custom API URL is required when integration is enabled',\n INVALID_SCROLL_CONTAINER_SELECTORS: 'Scroll container selectors must be valid CSS selectors',\n INVALID_GLOBAL_METADATA: 'Global metadata must be an object',\n INVALID_SENSITIVE_QUERY_PARAMS: 'Sensitive query params must be an array of strings',\n INVALID_PRIMARY_SCROLL_SELECTOR: 'Primary scroll selector must be a non-empty string',\n INVALID_PRIMARY_SCROLL_SELECTOR_SYNTAX: 'Invalid CSS selector syntax for primaryScrollSelector',\n INVALID_PAGE_VIEW_THROTTLE: 'Page view throttle must be a non-negative number',\n INVALID_CLICK_THROTTLE: 'Click throttle must be a non-negative number',\n INVALID_MAX_SAME_EVENT_PER_MINUTE: 'Max same event per minute must be a positive number',\n INVALID_VIEWPORT_CONFIG: 'Viewport config must be an object',\n INVALID_VIEWPORT_ELEMENTS: 'Viewport elements must be a non-empty array',\n INVALID_VIEWPORT_ELEMENT: 'Each viewport element must have a valid selector string',\n INVALID_VIEWPORT_ELEMENT_ID: 'Viewport element id must be a non-empty string',\n INVALID_VIEWPORT_ELEMENT_NAME: 'Viewport element name must be a non-empty string',\n INVALID_VIEWPORT_THRESHOLD: 'Viewport threshold must be a number between 0 and 1',\n INVALID_VIEWPORT_MIN_DWELL_TIME: 'Viewport minDwellTime must be a non-negative number',\n INVALID_VIEWPORT_COOLDOWN_PERIOD: 'Viewport cooldownPeriod must be a non-negative number',\n INVALID_VIEWPORT_MAX_TRACKED_ELEMENTS: 'Viewport maxTrackedElements must be a positive number',\n} as const;\n\n// ============================================================================\n// SECURITY\n// ============================================================================\n\n/**\n * Regular expressions for detecting and sanitizing XSS (Cross-Site Scripting) attacks\n *\n * Patterns detect:\n * - Script tags (<script>)\n * - JavaScript protocol URLs (javascript:)\n * - Inline event handlers (onclick=, onload=, etc.)\n * - Embedded content (<iframe>, <embed>, <object>)\n *\n * Used to sanitize user-provided data before storage or transmission\n */\nexport const XSS_PATTERNS = [\n /<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi,\n /javascript:/gi,\n /on\\w+\\s*=/gi,\n /<iframe\\b[^<]*(?:(?!<\\/iframe>)<[^<]*)*<\\/iframe>/gi,\n /<embed\\b[^>]*>/gi,\n /<object\\b[^<]*(?:(?!<\\/object>)<[^<]*)*<\\/object>/gi,\n] as const;\n","/**\n * Storage key management constants for TraceLog\n * All keys are namespaced with 'tlog' prefix to avoid conflicts\n */\n\n/**\n * Base key prefix for all TraceLog localStorage items\n * Used as namespace to prevent conflicts with other libraries\n */\nexport const STORAGE_BASE_KEY = 'tlog';\n\n/**\n * Storage key for QA mode flag in sessionStorage\n * Format: 'tlog:qa_mode'\n */\nexport const QA_MODE_KEY = `${STORAGE_BASE_KEY}:qa_mode`;\n\n/**\n * Storage key for user ID in localStorage\n * Format: 'tlog:uid'\n */\nexport const USER_ID_KEY = `${STORAGE_BASE_KEY}:uid`;\n\n/**\n * URL parameter name for activating/deactivating QA mode\n * Example: ?tlog_mode=qa or ?tlog_mode=qa_off\n */\nexport const QA_MODE_URL_PARAM = 'tlog_mode';\n\n/**\n * URL parameter value to enable QA mode\n */\nexport const QA_MODE_ENABLE_VALUE = 'qa';\n\n/**\n * URL parameter value to disable QA mode\n */\nexport const QA_MODE_DISABLE_VALUE = 'qa_off';\n\n/**\n * Generates storage key for event queue\n *\n * @param id - User ID or project identifier\n * @returns localStorage key for event queue (e.g., 'tlog:user123:queue')\n */\nexport const QUEUE_KEY = (id: string): string => (id ? `${STORAGE_BASE_KEY}:${id}:queue` : `${STORAGE_BASE_KEY}:queue`);\n\n/**\n * Generates storage key for session data\n *\n * @param id - Project identifier\n * @returns localStorage key for session (e.g., 'tlog:project123:session')\n */\nexport const SESSION_STORAGE_KEY = (id: string): string =>\n id ? `${STORAGE_BASE_KEY}:${id}:session` : `${STORAGE_BASE_KEY}:session`;\n\n/**\n * Generates storage key for cross-tab session synchronization\n *\n * @param id - Project identifier\n * @returns localStorage key for cross-tab session (e.g., 'tlog:project123:cross_tab_session')\n */\nexport const CROSS_TAB_SESSION_KEY = (id: string): string =>\n id ? `${STORAGE_BASE_KEY}:${id}:cross_tab_session` : `${STORAGE_BASE_KEY}:cross_tab_session`;\n\n/**\n * Generates storage key for tab information registry\n *\n * @param id - Project identifier\n * @returns localStorage key for tab info (e.g., 'tlog:project123:tab_info')\n */\nexport const TAB_INFO_KEY = (id: string): string =>\n id ? `${STORAGE_BASE_KEY}:${id}:tab_info` : `${STORAGE_BASE_KEY}:tab_info`;\n\n/**\n * Generates storage key for specific tab information\n *\n * @param projectId - Project identifier\n * @param tabId - Unique tab identifier\n * @returns localStorage key for tab-specific info (e.g., 'tlog:project123:tab:abc456:info')\n */\nexport const TAB_SPECIFIC_INFO_KEY = (projectId: string, tabId: string): string =>\n `${STORAGE_BASE_KEY}:${projectId}:tab:${tabId}:info`;\n\n/**\n * Generates storage key for session recovery data\n *\n * @param id - Project identifier\n * @returns localStorage key for session recovery (e.g., 'tlog:project123:recovery')\n */\nexport const SESSION_RECOVERY_KEY = (id: string): string =>\n id ? `${STORAGE_BASE_KEY}:${id}:recovery` : `${STORAGE_BASE_KEY}:recovery`;\n\n/**\n * Generates BroadcastChannel name for cross-tab communication\n *\n * @param id - Project identifier\n * @returns BroadcastChannel name (e.g., 'tlog:project123:broadcast')\n */\nexport const BROADCAST_CHANNEL_NAME = (id: string): string =>\n id ? `${STORAGE_BASE_KEY}:${id}:broadcast` : `${STORAGE_BASE_KEY}:broadcast`;\n\n/**\n * Generates storage key for per-session event counts\n *\n * Used to persist rate limiting counters across page reloads within the same session.\n * This prevents users from bypassing per-session event limits by refreshing the page.\n *\n * @param userId - User identifier\n * @param sessionId - Session identifier\n * @returns localStorage key for session counts (e.g., 'tlog:user123:session_counts:session456')\n */\nexport const SESSION_COUNTS_KEY = (userId: string, sessionId: string): string =>\n `${STORAGE_BASE_KEY}:${userId}:session_counts:${sessionId}`;\n\n/**\n * Session counts expiry duration (7 days in milliseconds).\n *\n * Session counts are automatically cleaned up after this duration to prevent\n * localStorage pollution. Counts older than 7 days are considered stale and\n * are removed on next page load.\n *\n * **Rationale**: 7 days provides sufficient buffer for:\n * - Long-running sessions (rare but possible)\n * - Users returning after extended inactivity\n * - While preventing indefinite accumulation (~100 bytes per session)\n */\nexport const SESSION_COUNTS_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000; // 7 days\n\n/**\n * Storage key for tracking last session counts cleanup timestamp.\n *\n * Used to throttle cleanup operations and prevent performance impact\n * from scanning localStorage on every EventManager initialization.\n *\n * Format: 'tlog:session_counts_last_cleanup'\n */\nexport const SESSION_COUNTS_LAST_CLEANUP_KEY = `${STORAGE_BASE_KEY}:session_counts_last_cleanup`;\n\n/**\n * Minimum interval between session counts cleanup runs (1 hour in milliseconds).\n *\n * Cleanup will only run if at least this much time has elapsed since the\n * last cleanup. This prevents performance degradation from frequent localStorage\n * scans while still ensuring regular cleanup of stale data.\n *\n * **Rationale**: 1 hour provides a good balance between:\n * - Preventing frequent scans on rapid page reloads\n * - Ensuring cleanup runs at least once per typical browsing session\n * - Minimal localStorage overhead (~100 entries typical, <1ms scan time)\n */\nexport const SESSION_COUNTS_CLEANUP_THROTTLE_MS = 60 * 60 * 1000; // 1 hour\n","import { MetadataType } from './common.types';\nimport { ViewportConfig } from './viewport.types';\nimport { WebVitalType } from './event.types';\n\n/**\n * Web Vitals filtering mode\n * - 'all': Track all Web Vitals metrics (full analytics)\n * - 'needs-improvement': Track metrics that need improvement or are poor (default, balanced)\n * - 'poor': Track only poor metrics (minimal data)\n */\nexport type WebVitalsMode = 'all' | 'needs-improvement' | 'poor';\n\nexport interface Config {\n /** Session inactivity timeout in milliseconds. @default 900000 */\n sessionTimeout?: number;\n /** Metadata appended to every tracked event. */\n globalMetadata?: Record<string, MetadataType>;\n /** Query parameters to remove before tracking URLs. */\n sensitiveQueryParams?: string[];\n /** Error event sampling rate between 0 and 1. @default 1 */\n errorSampling?: number;\n /** Event sampling rate between 0 and 1. @default 1 */\n samplingRate?: number;\n /** CSS selector to manually override primary scroll container detection. */\n primaryScrollSelector?: string;\n /** Viewport visibility tracking configuration. */\n viewport?: ViewportConfig;\n /** Page view throttle duration in milliseconds to prevent rapid navigation spam. @default 1000 */\n pageViewThrottleMs?: number;\n /** Click throttle duration in milliseconds to prevent double-clicks and rapid spam. @default 300 */\n clickThrottleMs?: number;\n /** Maximum number of same custom event name allowed per minute to prevent infinite loops. @default 60 */\n maxSameEventPerMinute?: number;\n /**\n * Web Vitals filtering mode. @default 'needs-improvement'\n * - 'all': Track all metrics (good, needs-improvement, poor) - full trend analysis\n * - 'needs-improvement': Track metrics that need improvement or are poor - balanced approach\n * - 'poor': Track only poor metrics - minimal data, focus on problems\n */\n webVitalsMode?: WebVitalsMode;\n /**\n * Custom Web Vitals thresholds in milliseconds (except CLS which is unitless).\n * Only applies when webVitalsMode is set. Overrides default thresholds for the selected mode.\n */\n webVitalsThresholds?: Partial<Record<WebVitalType, number>>;\n /** Optional configuration for third-party integrations. */\n integrations?: {\n /** TraceLog integration options. */\n tracelog?: {\n /** Required project ID TraceLog SaaS integration. */\n projectId: string;\n };\n /** Custom integration options. */\n custom?: {\n /** Endpoint for collecting events. */\n collectApiUrl: string;\n /** Allow HTTP URLs (not recommended for production). @default false */\n allowHttp?: boolean;\n /**\n * Static HTTP headers to include in every request.\n * For dynamic headers, use `setCustomHeaders()` instead.\n * @example { 'X-Brand': 'my-brand', 'X-Tenant-Id': 'tenant-123' }\n */\n headers?: Record<string, string>;\n };\n };\n}\n\nexport enum SpecialApiUrl {\n Localhost = 'localhost:8080',\n Fail = 'localhost:9999',\n}\n","/**\n * Device type classification for analytics segmentation\n *\n * **Detection Logic**:\n * - Mobile: User agent contains mobile keywords (iPhone, Android phone, etc.)\n * - Tablet: User agent contains tablet keywords (iPad, Android tablet, etc.)\n * - Desktop: Default fallback for non-mobile/tablet devices\n * - Unknown: User agent not detectable or SSR environment\n *\n * **Use Cases**:\n * - Device-specific analytics and dashboards\n * - User experience optimization by device type\n * - Performance monitoring segmented by device\n *\n * @see src/utils/browser/device-detector.utils.ts for detection implementation\n */\nexport enum DeviceType {\n /** Mobile phones (iPhone, Android phones) */\n Mobile = 'mobile',\n /** Tablet devices (iPad, Android tablets) */\n Tablet = 'tablet',\n /** Desktop computers and laptops */\n Desktop = 'desktop',\n /** Unable to determine device type */\n Unknown = 'unknown',\n}\n\n/**\n * Comprehensive device and environment information\n *\n * **Purpose**: Provides rich device context for analytics segmentation,\n * debugging, and user experience optimization.\n *\n * **Detection**: Uses navigator.userAgentData (modern) with UA string fallback.\n *\n * @see src/utils/browser/device-detector.utils.ts for detection implementation\n */\nexport interface DeviceInfo {\n /** Device form factor classification */\n type: DeviceType;\n /** OS name: \"Windows\", \"macOS\", \"iOS\", \"Android\", \"Linux\", \"ChromeOS\", \"Unknown\" */\n os: string;\n /** Browser name: \"Chrome\", \"Firefox\", \"Safari\", \"Edge\", \"Opera\", \"Unknown\" */\n browser: string;\n}\n","import { EventData } from './event.types';\nimport { EventsQueue } from './queue.types';\n\n/**\n * Generic callback function for event emitter subscriptions\n *\n * @template T - Type of data passed to the callback\n * @param data - Event data passed to the callback\n */\nexport type EmitterCallback<T = any> = (data: T) => void;\n\n/**\n * Available event emitter channels for TraceLog\n *\n * **Purpose**: Type-safe event subscription system for external integrations\n *\n * **Event Channels**:\n * - `event`: Individual events as they are tracked (real-time)\n * - `queue`: Complete event batches before network transmission (every 10s or 50 events)\n *\n * **Use Cases**:\n * - Real-time event processing\n * - Custom analytics integrations\n * - Debugging and monitoring\n *\n * @example\n * ```typescript\n * // Subscribe to individual events\n * tracelog.on('event', (event) => {\n * console.log('Event tracked:', event.type, event);\n * });\n *\n * // Subscribe to event batches\n * tracelog.on('queue', (batch) => {\n * console.log('Sending batch:', batch.events.length, 'events');\n * });\n * ```\n */\nexport enum EmitterEvent {\n /** Individual events as they are tracked */\n EVENT = 'event',\n /** Complete event batches before transmission */\n QUEUE = 'queue',\n}\n\n/**\n * Type mapping for event emitter channels\n *\n * **Purpose**: Ensures type safety when subscribing to events\n *\n * Maps each EmitterEvent to its corresponding payload type:\n * - `event` → `EventData`: Single event data\n * - `queue` → `EventsQueue`: Batch of events with metadata\n */\nexport interface EmitterMap {\n [EmitterEvent.EVENT]: EventData;\n [EmitterEvent.QUEUE]: EventsQueue;\n}\n","/**\n * Custom error types for TraceLog\n */\n\n/**\n * Represents a permanent HTTP error (4xx) that should not be retried\n * Examples: 400 Bad Request, 403 Forbidden, 404 Not Found\n */\nexport class PermanentError extends Error {\n constructor(\n message: string,\n public readonly statusCode?: number,\n ) {\n super(message);\n this.name = 'PermanentError';\n\n // Maintain proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, PermanentError);\n }\n }\n}\n","import { MetadataType } from './common.types';\n\n/**\n * Coordinate information from a click event\n * Includes absolute and relative positioning\n */\nexport type ClickCoordinates = Pick<ClickData, 'x' | 'y' | 'relativeX' | 'relativeY'>;\n\n/**\n * Web performance metric types tracked by the library\n * - LCP: Largest Contentful Paint\n * - CLS: Cumulative Layout Shift\n * - INP: Interaction to Next Paint\n * - FCP: First Contentful Paint\n * - TTFB: Time to First Byte\n * - LONG_TASK: Tasks exceeding 50ms\n */\nexport type WebVitalType = 'LCP' | 'CLS' | 'INP' | 'FCP' | 'TTFB' | 'LONG_TASK';\n\n/**\n * Event type name\n */\nexport type EventTypeName = (typeof EventType)[keyof typeof EventType];\n\n/**\n * Event type enum\n */\nexport enum EventType {\n /** Page navigation and view tracking */\n PAGE_VIEW = 'page_view',\n /** User click interactions */\n CLICK = 'click',\n /** Scroll depth and behavior */\n SCROLL = 'scroll',\n /** Session initialization */\n SESSION_START = 'session_start',\n /** Custom business events */\n CUSTOM = 'custom',\n /** Performance metrics */\n WEB_VITALS = 'web_vitals',\n /** JavaScript errors and rejections */\n ERROR = 'error',\n /** Element visibility tracking */\n VIEWPORT_VISIBLE = 'viewport_visible',\n}\n\n/**\n * Per-session event counts structure for rate limiting\n *\n * **Purpose**: Tracks how many events of each type have been generated\n * during the current session to enforce per-session limits and prevent\n * runaway event generation.\n *\n * **Usage**:\n * - Persisted to localStorage: `tlog:{userId}:session_counts:{sessionId}`\n * - Restored on page reload to maintain limits across navigations\n * - Debounced writes for performance (500ms delay)\n *\n * **Limits** (from config.constants.ts):\n * - Total: 1000 events per session\n * - Clicks: 500 per session\n * - Page Views: 100 per session\n * - Custom: 500 per session\n * - Viewport: 200 per session\n * - Scroll: 120 per session\n *\n * @see src/managers/event.manager.ts for implementation\n * @see src/constants/config.constants.ts for limit values\n */\nexport interface SessionEventCounts {\n /** Total events across all types */\n total: number;\n /** Click events count */\n [EventType.CLICK]: number;\n /** Page view events count */\n [EventType.PAGE_VIEW]: number;\n /** Custom events count */\n [EventType.CUSTOM]: number;\n /** Viewport visibility events count */\n [EventType.VIEWPORT_VISIBLE]: number;\n /** Scroll events count */\n [EventType.SCROLL]: number;\n /** Index signature for dynamic event type access */\n [key: string]: number;\n}\n\n/**\n * Scroll direction indicators\n */\nexport enum ScrollDirection {\n /** Scrolling upward */\n UP = 'up',\n /** Scrolling downward */\n DOWN = 'down',\n}\n\n/**\n * JavaScript error classification\n */\nexport enum ErrorType {\n /** Runtime JavaScript errors */\n JS_ERROR = 'js_error',\n /** Unhandled promise rejections */\n PROMISE_REJECTION = 'promise_rejection',\n}\n\n/**\n * Scroll event data captured during user scrolling\n */\nexport interface ScrollData {\n /** Current scroll depth as percentage (0-100) */\n depth: number;\n /** Direction of scroll movement */\n direction: ScrollDirection;\n /** CSS selector of the scrolled container */\n container_selector: string;\n /** Whether this is the primary viewport scroll */\n is_primary: boolean;\n /** Scroll velocity in pixels per second */\n velocity: number;\n /** Maximum scroll depth reached during session (0-100) */\n max_depth_reached: number;\n}\n\n/**\n * Click event data capturing user interaction details\n */\nexport interface ClickData {\n /** Absolute X coordinate in viewport (pixels) */\n x: number;\n /** Absolute Y coordinate in viewport (pixels) */\n y: number;\n /** Relative X position within element (0-1) */\n relativeX: number;\n /** Relative Y position within element (0-1) */\n relativeY: number;\n /** Element ID attribute */\n id?: string;\n /** Element class attribute */\n class?: string;\n /** HTML tag name */\n tag?: string;\n /** Element text content (truncated) */\n text?: string;\n /** Link href for anchor elements */\n href?: string;\n /** Element title attribute */\n title?: string;\n /** Image alt text for img elements */\n alt?: string;\n /** ARIA role attribute */\n role?: string;\n /** ARIA label attribute */\n ariaLabel?: string;\n /** Custom data attributes (data-*) */\n dataAttributes?: Record<string, string>;\n}\n\n/**\n * Element data for specialized click tracking\n * Used for form inputs and interactive elements\n */\nexport interface ClickTrackingElementData {\n /** DOM element being tracked */\n element: HTMLElement;\n /** Descriptive name for the element */\n name: string;\n /** Element value (for inputs) */\n value?: string;\n}\n\n/**\n * Custom event data for business-specific tracking\n */\nexport interface CustomEventData {\n /** Event name identifier */\n name: string;\n /** Additional event metadata */\n metadata?: Record<string, MetadataType> | Record<string, MetadataType>[];\n}\n\n/**\n * Web performance metrics data\n */\nexport interface WebVitalsData {\n /** Type of performance metric */\n type: WebVitalType;\n /** Metric value (varies by type) */\n value: number;\n}\n\n/**\n * JavaScript error details\n */\nexport interface ErrorData {\n /** Error classification */\n type: ErrorType;\n /** Error message text */\n message: string;\n /** Source file where error occurred */\n filename?: string;\n /** Line number in source file */\n line?: number;\n /** Column number in source file */\n column?: number;\n}\n\n/**\n * UTM campaign tracking parameters\n */\nexport interface UTM {\n /** Campaign source (e.g., google, newsletter) */\n source?: string;\n /** Campaign medium (e.g., cpc, email) */\n medium?: string;\n /** Campaign name identifier */\n campaign?: string;\n /** Campaign search term */\n term?: string;\n /** Campaign content variation */\n content?: string;\n}\n\n/**\n * Page view navigation data\n */\nexport interface PageViewData {\n /** Previous page URL */\n referrer?: string;\n /** Page title from document */\n title?: string;\n /** URL pathname */\n pathname?: string;\n /** URL query string */\n search?: string;\n /** URL hash fragment */\n hash?: string;\n}\n\n/**\n * Data captured when element becomes visible\n */\nexport interface ViewportEventData {\n /** CSS selector that matched the element */\n selector: string;\n /** Optional unique identifier for analytics (if configured) */\n id?: string;\n /** Optional human-readable name (if configured) */\n name?: string;\n /** Actual time (ms) element was visible before event fired */\n dwellTime: number;\n /** Actual visibility ratio when event fired (0-1) */\n visibilityRatio: number;\n}\n\n/**\n * Complete event data structure\n * All events share base properties with type-specific data\n */\nexport interface EventData {\n /** Unique event identifier */\n id: string;\n /** Event type classification */\n type: EventType;\n /** Current page URL where event occurred */\n page_url: string;\n /** Unix timestamp (milliseconds) */\n timestamp: number;\n /** HTTP referrer header */\n referrer?: string;\n /** Previous page URL for navigation events */\n from_page_url?: string;\n /** Scroll event details (when type is SCROLL) */\n scroll_data?: ScrollData;\n /** Click event details (when type is CLICK) */\n click_data?: ClickData;\n /** Custom event details (when type is CUSTOM) */\n custom_event?: CustomEventData;\n /** Performance metrics (when type is WEB_VITALS) */\n web_vitals?: WebVitalsData;\n /** Page view details (when type is PAGE_VIEW) */\n page_view?: PageViewData;\n /** Error details (when type is ERROR) */\n error_data?: ErrorData;\n /** Viewport visibility details (when type is VIEWPORT_VISIBLE) */\n viewport_data?: ViewportEventData;\n /** Campaign tracking parameters */\n utm?: UTM;\n}\n","/**\n * App modes for the TraceLog Library\n *\n * - QA: Quality Assurance mode - logs custom events to console for verification\n */\nexport enum Mode {\n QA = 'qa',\n}\n","import { EventData, EventType, ScrollData } from './event.types';\n\n/**\n * Primary scroll event type (main viewport scroll)\n *\n * **Purpose**: Type-safe representation of primary viewport scroll events\n *\n * Primary scroll events track the main page/viewport scrolling,\n * which is the most important scroll metric for engagement analysis.\n */\nexport type PrimaryScrollEvent = EventData & {\n type: EventType.SCROLL;\n scroll_data: ScrollData & { is_primary: true };\n};\n\n/**\n * Secondary scroll event type (scrollable container scroll)\n *\n * **Purpose**: Type-safe representation of container-specific scroll events\n *\n * Secondary scroll events track scrolling within specific elements\n * (e.g., modals, sidebars, embedded content) for granular engagement analysis.\n */\nexport type SecondaryScrollEvent = EventData & {\n type: EventType.SCROLL;\n scroll_data: ScrollData & { is_primary: false };\n};\n\n/**\n * Type guard to check if an event is a primary scroll event\n *\n * **Purpose**: Runtime type narrowing for primary viewport scrolls\n *\n * **Use Cases**:\n * - Filter events to process only main viewport scrolling\n * - Separate primary from secondary scroll analytics\n * - Type-safe event processing in transformers\n *\n * @param event - Event to check\n * @returns `true` if event is a primary scroll event\n *\n * @example\n * ```typescript\n * if (isPrimaryScrollEvent(event)) {\n * // event.scroll_data.is_primary is guaranteed to be true\n * console.log('Main viewport scrolled to', event.scroll_data.depth, '%');\n * }\n * ```\n */\nexport const isPrimaryScrollEvent = (event: EventData): event is PrimaryScrollEvent => {\n return (\n event.type === EventType.SCROLL && 'scroll_data' in event && (event.scroll_data as ScrollData).is_primary === true\n );\n};\n\n/**\n * Type guard to check if an event is a secondary scroll event\n *\n * **Purpose**: Runtime type narrowing for container-specific scrolls\n *\n * **Use Cases**:\n * - Filter events to process only container scrolling\n * - Analyze engagement with specific page sections\n * - Type-safe event processing in transformers\n *\n * @param event - Event to check\n * @returns `true` if event is a secondary scroll event\n *\n * @example\n * ```typescript\n * if (isSecondaryScrollEvent(event)) {\n * // event.scroll_data.is_primary is guaranteed to be false\n * console.log('Container scrolled:', event.scroll_data.container_selector);\n * }\n * ```\n */\nexport const isSecondaryScrollEvent = (event: EventData): event is SecondaryScrollEvent => {\n return (\n event.type === EventType.SCROLL && 'scroll_data' in event && (event.scroll_data as ScrollData).is_primary === false\n );\n};\n","/**\n * Custom error classes for TraceLog validation errors\n * Provides better error handling and consistency across validation layers\n */\n\n/**\n * Base class for all TraceLog validation errors\n */\nexport abstract class TraceLogValidationError extends Error {\n constructor(\n message: string,\n public readonly errorCode: string,\n public readonly layer: 'config' | 'app' | 'runtime',\n ) {\n super(message);\n this.name = this.constructor.name;\n\n // Maintains proper stack trace for where error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/**\n * Thrown when app configuration validation fails\n */\nexport class AppConfigValidationError extends TraceLogValidationError {\n constructor(message: string, layer: 'config' | 'app' | 'runtime' = 'config') {\n super(message, 'APP_CONFIG_INVALID', layer);\n }\n}\n\n/**\n * Thrown when session timeout validation fails\n */\nexport class SessionTimeoutValidationError extends TraceLogValidationError {\n constructor(message: string, layer: 'config' | 'app' | 'runtime' = 'config') {\n super(message, 'SESSION_TIMEOUT_INVALID', layer);\n }\n}\n\n/**\n * Thrown when sampling rate validation fails\n */\nexport class SamplingRateValidationError extends TraceLogValidationError {\n constructor(message: string, layer: 'config' | 'app' | 'runtime' = 'config') {\n super(message, 'SAMPLING_RATE_INVALID', layer);\n }\n}\n\n/**\n * Thrown when integrations validation fails\n */\nexport class IntegrationValidationError extends TraceLogValidationError {\n constructor(message: string, layer: 'config' | 'app' | 'runtime' = 'config') {\n super(message, 'INTEGRATION_INVALID', layer);\n }\n}\n\n/**\n * Thrown when initialization exceeds the maximum allowed timeout\n */\nexport class InitializationTimeoutError extends TraceLogValidationError {\n constructor(\n message: string,\n public readonly timeoutMs: number,\n layer: 'config' | 'app' | 'runtime' = 'runtime',\n ) {\n super(message, 'INITIALIZATION_TIMEOUT', layer);\n }\n}\n","/**\n * Console log style for active TraceLog operations\n * Used for visual highlighting in browser console during QA mode\n */\nexport const LOG_STYLE_ACTIVE =\n 'background: #ff9800; color: white; font-weight: bold; padding: 2px 8px; border-radius: 3px;';\n\n/**\n * Console log style for disabled TraceLog operations\n * Used for visual indication when features are disabled\n */\nexport const LOG_STYLE_DISABLED =\n 'background: #9e9e9e; color: white; font-weight: bold; padding: 2px 8px; border-radius: 3px;';\n\n/**\n * Console log style for critical errors (always visible)\n * Used for errors that must reach monitoring platforms like Sentry\n */\nexport const LOG_STYLE_CRITICAL =\n 'background: #d32f2f; color: white; font-weight: bold; padding: 2px 8px; border-radius: 3px;';\n","import { QA_MODE_KEY } from '../constants/storage.constants';\nimport { LOG_STYLE_CRITICAL } from '../constants/app.constants';\n\n/**\n * Log visibility level determining when logs are shown\n *\n * - 'critical': Always visible (production included) - for Sentry/monitoring\n * - 'qa': Only visible when QA mode is active (equivalent to showToClient: true)\n * - undefined: Only visible in NODE_ENV=development\n */\nexport type LogVisibility = 'critical' | 'qa';\n\n/**\n * Formats log messages with optional error information and environment-specific sanitization\n *\n * **Purpose**: Creates consistent log message format across the library with automatic\n * error handling and production-safe output.\n *\n * **Behavior**:\n * - **Production**: Sanitizes stack traces and file paths from error messages\n * - **Development**: Preserves full error messages for debugging\n * - **Fallback**: Handles non-Error objects gracefully\n *\n * **Format**: `[TraceLog] {message}: {error}` or `[TraceLog] {message}` if no error\n *\n * **Stack Trace Sanitization** (production only):\n * - Removes \"at function (...)\" stack lines\n * - Removes file paths with line/column numbers\n * - Prevents leaking internal file structure\n *\n * @param msg - Base log message\n * @param error - Optional error object (Error, string, object, or unknown type)\n * @returns Formatted log message string\n *\n * @example\n * ```typescript\n * // Basic message\n * formatLogMsg('Session started');\n * // → \"[TraceLog] Session started\"\n *\n * // With Error object (development)\n * formatLogMsg('Failed to init', new Error('Network timeout'));\n * // → \"[TraceLog] Failed to init: Network timeout\"\n *\n * // With Error object (production - sanitized)\n * formatLogMsg('Failed to init', new Error('Network timeout at handleRequest (app.ts:42:10)'));\n * // → \"[TraceLog] Failed to init: Network timeout\"\n *\n * // With string error\n * formatLogMsg('Config invalid', 'Missing API key');\n * // → \"[TraceLog] Config invalid: Missing API key\"\n * ```\n */\nexport const formatLogMsg = (msg: string, error?: unknown): string => {\n if (error) {\n if (process.env.NODE_ENV !== 'development' && error instanceof Error) {\n const sanitizedMessage = error.message.replace(/\\s+at\\s+.*$/gm, '').replace(/\\s*\\([^()]+:\\d+:\\d+\\)/g, '');\n return `[TraceLog] ${msg}: ${sanitizedMessage}`;\n }\n\n if (error instanceof Error) {\n return `[TraceLog] ${msg}: ${error.message}`;\n }\n\n if (typeof error === 'string') {\n return `[TraceLog] ${msg}: ${error}`;\n }\n\n if (typeof error === 'object') {\n try {\n return `[TraceLog] ${msg}: ${JSON.stringify(error)}`;\n } catch {\n return `[TraceLog] ${msg}: [Unable to serialize error]`;\n }\n }\n\n return `[TraceLog] ${msg}: ${String(error)}`;\n }\n\n return `[TraceLog] ${msg}`;\n};\n\n/**\n * Check if QA mode is active by reading sessionStorage directly\n *\n * NOTE: Intentional duplication of mode.utils.ts:isQaModeActive() to avoid\n * circular dependency (mode.utils.ts imports log() from this file).\n * Changes to QA mode detection logic must be synchronized in both files.\n *\n * @see src/utils/browser/mode.utils.ts - Canonical public implementation\n */\nconst isQaModeActive = (): boolean => {\n if (typeof window === 'undefined' || typeof sessionStorage === 'undefined') {\n return false;\n }\n try {\n return sessionStorage.getItem(QA_MODE_KEY) === 'true';\n } catch {\n return false;\n }\n};\n\n/**\n * Safe logging utility that enforces zero logs in production\n *\n * @param type - Log level (info, warn, error, debug)\n * @param msg - Message to log\n * @param extra - Optional extra data\n * @param extra.error - Error object to include in the log message\n * @param extra.data - Additional data object to log (will be sanitized in production)\n * @param extra.showToClient - If true, log will be shown in production (QA mode only) - DEPRECATED: use visibility: 'qa'\n * @param extra.style - CSS styles to apply to the console message (browser only, uses %c formatting)\n * @param extra.visibility - Controls when log is visible:\n * - 'critical': Always visible (production included) - for monitoring/Sentry\n * - 'qa': Only visible when QA mode is active\n * - undefined: Only visible in NODE_ENV=development\n *\n * Visibility hierarchy (production):\n * - CRITICAL: Always shown - errors that must reach monitoring platforms\n * - QA: Shown with ?tlog_mode=qa - custom event verification\n * - Default: Never shown in production\n *\n * Development behavior (NODE_ENV=development):\n * - All logs visible regardless of visibility level\n * - Full error messages and stack traces preserved\n */\nexport const log = (\n type: 'info' | 'warn' | 'error' | 'debug',\n msg: string,\n extra?: {\n error?: unknown;\n data?: Record<string, unknown>;\n showToClient?: boolean;\n style?: string;\n visibility?: LogVisibility;\n },\n): void => {\n const { error, data, showToClient = false, style, visibility } = extra ?? {};\n const formattedMsg = error ? formatLogMsg(msg, error) : `[TraceLog] ${msg}`;\n const method = type === 'error' ? 'error' : type === 'warn' ? 'warn' : 'log';\n const isProduction = process.env.NODE_ENV !== 'development';\n\n // Development: All logs visible\n if (!isProduction) {\n outputLog(method, formattedMsg, style, data);\n return;\n }\n\n // Production: Check visibility level\n const shouldShow = shouldShowLog(visibility, showToClient);\n\n if (!shouldShow) {\n return;\n }\n\n // Apply appropriate style for visibility level\n const effectiveStyle = getEffectiveStyle(visibility, style);\n const sanitizedData = data !== undefined ? sanitizeLogData(data) : undefined;\n\n outputLog(method, formattedMsg, effectiveStyle, sanitizedData);\n};\n\n/**\n * Determines if a log should be shown based on visibility level\n */\nconst shouldShowLog = (visibility: LogVisibility | undefined, showToClient: boolean): boolean => {\n // Critical logs are always shown\n if (visibility === 'critical') {\n return true;\n }\n\n // QA mode logs (including legacy showToClient)\n if (visibility === 'qa' || showToClient) {\n return isQaModeActive();\n }\n\n // Default: not shown in production\n return false;\n};\n\n/**\n * Gets the appropriate style for the visibility level\n */\nconst getEffectiveStyle = (visibility: LogVisibility | undefined, providedStyle: string | undefined): string => {\n if (providedStyle !== undefined && providedStyle !== '') {\n return providedStyle;\n }\n\n if (visibility === 'critical') {\n return LOG_STYLE_CRITICAL;\n }\n\n return '';\n};\n\n/**\n * Outputs the log message to console\n */\nconst outputLog = (\n method: 'log' | 'warn' | 'error',\n formattedMsg: string,\n style: string | undefined,\n data: Record<string, unknown> | undefined,\n): void => {\n const hasStyle = style !== undefined && style !== '';\n const styledMsg = hasStyle ? `%c${formattedMsg}` : formattedMsg;\n\n if (data !== undefined) {\n if (hasStyle) {\n console[method](styledMsg, style, data);\n } else {\n console[method](styledMsg, data);\n }\n } else {\n if (hasStyle) {\n console[method](styledMsg, style);\n } else {\n console[method](styledMsg);\n }\n }\n};\n\n/**\n * Sanitizes log data in production to prevent sensitive information leakage\n *\n * **Purpose**: Recursively redacts sensitive keys while preserving object expandability\n * in browser console for debugging.\n *\n * **Sensitive Keys Detected** (case-insensitive substring matching):\n * - `token`, `password`, `secret`, `key`, `apikey`, `api_key`, `sessionid`, `session_id`\n *\n * **Behavior**:\n * - **Redaction**: Replaces values with `'[REDACTED]'` string\n * - **Deep Cloning**: Creates new objects to avoid mutating originals\n * - **Recursive**: Handles nested objects and arrays\n * - **Preserves Structure**: Maintains object hierarchy for console inspection\n *\n * **Use Cases**:\n * - Production logging where sensitive data might be present\n * - Debugging with potentially sensitive configuration objects\n * - Error logging with user or session data\n *\n * @param data - Object to sanitize\n * @returns New object with sensitive keys redacted\n *\n * @example\n * ```typescript\n * const data = {\n * userId: '123',\n * apiKey: 'secret-key-123',\n * config: {\n * endpoint: 'https://api.com',\n * token: 'bearer-xyz'\n * }\n * };\n *\n * const sanitized = sanitizeLogData(data);\n * // {\n * // userId: '123',\n * // apiKey: '[REDACTED]',\n * // config: {\n * // endpoint: 'https://api.com',\n * // token: '[REDACTED]'\n * // }\n * // }\n * ```\n */\nconst sanitizeLogData = (data: Record<string, unknown>): Record<string, unknown> => {\n const sanitized: Record<string, unknown> = {};\n const sensitiveKeys = ['token', 'password', 'secret', 'key', 'apikey', 'api_key', 'sessionid', 'session_id'];\n\n for (const [key, value] of Object.entries(data)) {\n const lowerKey = key.toLowerCase();\n\n if (sensitiveKeys.some((sensitiveKey) => lowerKey.includes(sensitiveKey))) {\n sanitized[key] = '[REDACTED]';\n continue;\n }\n\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n sanitized[key] = sanitizeLogData(value as Record<string, unknown>);\n } else if (Array.isArray(value)) {\n sanitized[key] = value.map((item) =>\n item !== null && typeof item === 'object' && !Array.isArray(item)\n ? sanitizeLogData(item as Record<string, unknown>)\n : item,\n );\n } else {\n sanitized[key] = value;\n }\n }\n\n return sanitized;\n};\n","import { DeviceInfo, DeviceType } from '../../types/device.types';\nimport { log } from '../logging.utils';\n\nlet coarsePointerQuery: MediaQueryList | undefined;\nlet noHoverQuery: MediaQueryList | undefined;\n\nconst initMediaQueries = (): void => {\n if (typeof window !== 'undefined' && !coarsePointerQuery) {\n coarsePointerQuery = window.matchMedia('(pointer: coarse)');\n noHoverQuery = window.matchMedia('(hover: none)');\n }\n};\n\ninterface UserAgentBrand {\n brand: string;\n version: string;\n}\n\ninterface NavigatorWithUserAgentData extends Navigator {\n userAgentData?: {\n mobile: boolean;\n platform?: string;\n brands?: UserAgentBrand[];\n };\n}\n\nconst UNKNOWN = 'Unknown';\n\n/**\n * Detects OS name from navigator.userAgentData (modern) or userAgent string (fallback)\n */\nconst detectOS = (nav: NavigatorWithUserAgentData): string => {\n // Modern API (Chromium 90+)\n const platform = nav.userAgentData?.platform;\n if (platform != null && platform !== '') {\n if (/windows/i.test(platform)) return 'Windows';\n if (/macos/i.test(platform)) return 'macOS';\n if (/android/i.test(platform)) return 'Android';\n if (/linux/i.test(platform)) return 'Linux';\n if (/chromeos/i.test(platform)) return 'ChromeOS';\n if (/ios/i.test(platform)) return 'iOS';\n }\n\n // Fallback: Parse userAgent string\n const ua = navigator.userAgent;\n if (/Windows/i.test(ua)) return 'Windows';\n if (/iPhone|iPad|iPod/i.test(ua)) return 'iOS';\n if (/Mac OS X|Macintosh/i.test(ua)) return 'macOS';\n if (/Android/i.test(ua)) return 'Android';\n if (/CrOS/i.test(ua)) return 'ChromeOS';\n if (/Linux/i.test(ua)) return 'Linux';\n\n return UNKNOWN;\n};\n\n/**\n * Detects browser name from navigator.userAgentData.brands (modern) or userAgent string (fallback)\n */\nconst detectBrowser = (nav: NavigatorWithUserAgentData): string => {\n // Modern API (Chromium 90+)\n const brands = nav.userAgentData?.brands;\n if (brands != null && brands.length > 0) {\n // Filter out generic brands and find the actual browser\n const validBrands = brands.filter((b) => !/not.?a.?brand|chromium/i.test(b.brand));\n const firstBrand = validBrands[0];\n if (firstBrand != null) {\n const brand = firstBrand.brand;\n // Normalize brand names\n if (/google chrome/i.test(brand)) return 'Chrome';\n if (/microsoft edge/i.test(brand)) return 'Edge';\n if (/opera/i.test(brand)) return 'Opera';\n return brand;\n }\n }\n\n // Fallback: Parse userAgent string (order matters!)\n const ua = navigator.userAgent;\n if (/Edg\\//i.test(ua)) return 'Edge';\n if (/OPR\\//i.test(ua)) return 'Opera';\n if (/Chrome/i.test(ua)) return 'Chrome';\n if (/Firefox/i.test(ua)) return 'Firefox';\n if (/Safari/i.test(ua) && !/Chrome/i.test(ua)) return 'Safari';\n\n return UNKNOWN;\n};\n\n/**\n * Detects the device type based on screen size, user agent, and browser capabilities\n * @returns The detected device type\n */\nexport const getDeviceType = (): DeviceType => {\n try {\n const nav = navigator as NavigatorWithUserAgentData;\n\n if (nav.userAgentData != null && typeof nav.userAgentData.mobile === 'boolean') {\n const uaPlatform = nav.userAgentData.platform;\n if (uaPlatform != null && uaPlatform !== '' && /ipad|tablet/i.test(uaPlatform)) {\n return DeviceType.Tablet;\n }\n\n const result = nav.userAgentData.mobile ? DeviceType.Mobile : DeviceType.Desktop;\n return result;\n }\n\n initMediaQueries();\n\n const width = window.innerWidth;\n const hasCoarsePointer = coarsePointerQuery?.matches ?? false;\n const hasNoHover = noHoverQuery?.matches ?? false;\n const hasTouchSupport = 'ontouchstart' in window || navigator.maxTouchPoints > 0;\n const ua = navigator.userAgent.toLowerCase();\n const isMobileUA = /mobile|android|iphone|ipod|blackberry|iemobile|opera mini/.test(ua);\n const isTabletUA = /tablet|ipad|android(?!.*mobile)/.test(ua);\n\n if (width <= 767 || (isMobileUA && hasTouchSupport)) {\n return DeviceType.Mobile;\n }\n\n if ((width >= 768 && width <= 1024) || isTabletUA || (hasCoarsePointer && hasNoHover && hasTouchSupport)) {\n return DeviceType.Tablet;\n }\n\n return DeviceType.Desktop;\n } catch (error) {\n log('debug', 'Device detection failed, defaulting to desktop', { error });\n\n return DeviceType.Desktop;\n }\n};\n\n/**\n * Detects comprehensive device information including type, OS, and browser.\n *\n * Uses navigator.userAgentData (modern Chromium 90+) with userAgent string fallback.\n *\n * @returns DeviceInfo object with type, os, and browser fields\n */\nexport const getDeviceInfo = (): DeviceInfo => {\n try {\n const nav = navigator as NavigatorWithUserAgentData;\n\n return {\n type: getDeviceType(),\n os: detectOS(nav),\n browser: detectBrowser(nav),\n };\n } catch (error) {\n log('debug', 'Device info detection failed, using defaults', { error });\n\n return {\n type: DeviceType.Desktop,\n os: UNKNOWN,\n browser: UNKNOWN,\n };\n }\n};\n","/**\n * Error handling and PII sanitization constants for TraceLog\n * Centralizes patterns and limits for error tracking and data protection\n */\n\n// ============================================================================\n// PII SANITIZATION PATTERNS\n// ============================================================================\n\n/**\n * Regular expressions for detecting and sanitizing Personally Identifiable Information (PII)\n * These patterns are used to replace sensitive information with [REDACTED] in error messages\n */\nexport const PII_PATTERNS = [\n // Email addresses\n /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b/gi,\n\n // US Phone numbers (various formats)\n /\\b\\d{3}[-.]?\\d{3}[-.]?\\d{4}\\b/g,\n\n // Credit card numbers (16 digits with optional separators)\n /\\b\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b/g,\n\n // IBAN (International Bank Account Number)\n /\\b[A-Z]{2}\\d{2}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b/gi,\n\n // API keys/tokens (sk_test_, sk_live_, pk_test_, pk_live_, etc.)\n /\\b[sp]k_(test|live)_[a-zA-Z0-9]{10,}\\b/gi,\n\n // Bearer tokens (JWT-like patterns - matches complete and partial tokens)\n /Bearer\\s+[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)?(?:\\.[A-Za-z0-9_-]+)?/gi,\n\n // Passwords in connection strings (protocol://user:password@host)\n /:\\/\\/[^:/]+:([^@]+)@/gi,\n] as const;\n\n// ============================================================================\n// ERROR TRACKING LIMITS\n// ============================================================================\n\n/**\n * Maximum length for error messages before truncation\n * Prevents extremely long error messages from consuming excessive storage\n */\nexport const MAX_ERROR_MESSAGE_LENGTH = 500;\n\n/**\n * Time window for error suppression in milliseconds\n * Prevents duplicate errors from flooding the system within this timeframe\n */\nexport const ERROR_SUPPRESSION_WINDOW_MS = 5_000; // 5 seconds\n\n/**\n * Maximum number of unique errors to track for suppression\n * Prevents memory usage from growing indefinitely\n */\nexport const MAX_TRACKED_ERRORS = 50;\n\n/**\n * Hard limit for error tracking before aggressive cleanup\n * If this limit is exceeded, the entire error map is cleared\n */\nexport const MAX_TRACKED_ERRORS_HARD_LIMIT = MAX_TRACKED_ERRORS * 2;\n\n// ============================================================================\n// ERROR SAMPLING\n// ============================================================================\n\n/**\n * Default error sampling rate\n * Controls what percentage of errors are actually reported\n */\nexport const DEFAULT_ERROR_SAMPLING_RATE = 1; // 100% of errors\n\n// ============================================================================\n// ERROR BURST DETECTION (Phase 3)\n// ============================================================================\n\n/**\n * Time window for error burst detection in milliseconds\n * Tracks unique errors within this window\n */\nexport const ERROR_BURST_WINDOW_MS = 1000; // 1 second\n\n/**\n * Maximum number of unique errors allowed in burst window\n * Exceeding this triggers a cooldown period\n */\nexport const ERROR_BURST_THRESHOLD = 10; // 10 unique errors\n\n/**\n * Backoff period after burst detection in milliseconds\n * No errors will be tracked during this cooldown\n */\nexport const ERROR_BURST_BACKOFF_MS = 5000; // 5 seconds\n\n// ============================================================================\n// PERMANENT ERROR LOGGING\n// ============================================================================\n\n/**\n * Time window for throttling permanent error logs in milliseconds\n * Same error status codes are logged at most once per this window\n * Prevents console spam when backend repeatedly returns 4xx errors\n */\nexport const PERMANENT_ERROR_LOG_THROTTLE_MS = 60_000; // 1 minute\n","/**\n * Performance monitoring and web vitals constants for TraceLog\n * Centralizes thresholds and configuration for performance tracking\n */\n\nimport { WebVitalType } from '../types';\nimport type { WebVitalsMode } from '../types/config.types';\n\n// ============================================================================\n// WEB VITALS THRESHOLDS\n// ============================================================================\n\n/**\n * Web Vitals \"good\" thresholds (75th percentile boundaries)\n * Metrics below or equal to these values are considered good performance.\n * Reference: https://web.dev/articles/vitals\n */\nexport const WEB_VITALS_GOOD_THRESHOLDS: Record<WebVitalType, number> = {\n LCP: 2500, // Good: ≤ 2.5s\n FCP: 1800, // Good: ≤ 1.8s\n CLS: 0.1, // Good: ≤ 0.1\n INP: 200, // Good: ≤ 200ms\n TTFB: 800, // Good: ≤ 800ms\n LONG_TASK: 50,\n} as const;\n\n/**\n * Web Vitals \"needs improvement\" thresholds\n * Metrics exceeding these values need attention but aren't critically poor.\n * Reference: https://web.dev/articles/vitals\n */\nexport const WEB_VITALS_NEEDS_IMPROVEMENT_THRESHOLDS: Record<WebVitalType, number> = {\n LCP: 2500, // Needs improvement: > 2.5s (same as good boundary)\n FCP: 1800, // Needs improvement: > 1.8s\n CLS: 0.1, // Needs improvement: > 0.1\n INP: 200, // Needs improvement: > 200ms\n TTFB: 800, // Needs improvement: > 800ms\n LONG_TASK: 50,\n} as const;\n\n/**\n * Web Vitals \"poor\" thresholds\n * Metrics exceeding these values indicate poor performance requiring immediate attention.\n * Reference: https://web.dev/articles/vitals\n */\nexport const WEB_VITALS_POOR_THRESHOLDS: Record<WebVitalType, number> = {\n LCP: 4000, // Poor: > 4s\n FCP: 3000, // Poor: > 3s\n CLS: 0.25, // Poor: > 0.25\n INP: 500, // Poor: > 500ms\n TTFB: 1800, // Poor: > 1800ms\n LONG_TASK: 50,\n} as const;\n\n/**\n * Default Web Vitals mode\n * 'needs-improvement' provides balanced approach - captures metrics that need attention\n * while filtering out good performance (reduces noise and costs)\n */\nexport const DEFAULT_WEB_VITALS_MODE: WebVitalsMode = 'needs-improvement';\n\n/**\n * Get Web Vitals thresholds for the specified mode\n */\nexport const getWebVitalsThresholds = (mode: WebVitalsMode = DEFAULT_WEB_VITALS_MODE): Record<WebVitalType, number> => {\n switch (mode) {\n case 'all':\n return { LCP: 0, FCP: 0, CLS: 0, INP: 0, TTFB: 0, LONG_TASK: 0 }; // Track everything\n case 'needs-improvement':\n return WEB_VITALS_NEEDS_IMPROVEMENT_THRESHOLDS;\n case 'poor':\n return WEB_VITALS_POOR_THRESHOLDS;\n default:\n return WEB_VITALS_NEEDS_IMPROVEMENT_THRESHOLDS;\n }\n};\n\n// ============================================================================\n// PERFORMANCE MONITORING LIMITS\n// ============================================================================\n\n/**\n * Long task throttling interval in milliseconds\n * Prevents excessive long task events from being sent\n */\nexport const LONG_TASK_THROTTLE_MS = 1000;\n\n/**\n * Maximum number of navigation history entries to keep in memory\n * Prevents unbounded growth of reportedByNav Map in long-running SPAs\n * Uses FIFO eviction when limit is exceeded\n */\nexport const MAX_NAVIGATION_HISTORY = 50;\n\n/**\n * Precision for performance metric values\n * All performance metrics are rounded to 2 decimal places\n */\nexport const PERFORMANCE_PRECISION_DECIMALS = 2 as const;\n","import { version } from '../../package.json';\n\nexport const LIB_VERSION = version;\n","import {\n QA_MODE_KEY,\n QA_MODE_URL_PARAM,\n QA_MODE_ENABLE_VALUE,\n QA_MODE_DISABLE_VALUE,\n LOG_STYLE_ACTIVE,\n LOG_STYLE_DISABLED,\n} from '../../constants';\nimport { log } from '../logging.utils';\n\n// ============================================================================\n// Internal Helpers\n// ============================================================================\n\n/**\n * Check if browser environment is available\n */\nconst isBrowserEnvironment = (): boolean => {\n return typeof window !== 'undefined' && typeof sessionStorage !== 'undefined';\n};\n\n/**\n * Clean URL parameter from the current URL\n */\nconst cleanUrlParameter = (): void => {\n try {\n const params = new URLSearchParams(window.location.search);\n params.delete(QA_MODE_URL_PARAM);\n\n const search = params.toString();\n const url = window.location.pathname + (search ? '?' + search : '') + window.location.hash;\n\n window.history.replaceState({}, '', url);\n } catch {\n // Continue without cleaning URL\n }\n};\n\n// ============================================================================\n// QA Mode Public API\n// ============================================================================\n\n/**\n * Detects QA mode from URL parameter or sessionStorage\n *\n * QA mode shows custom event logs to help verify tracking implementation.\n *\n * Activation:\n * - URL: `?tlog_mode=qa` to enable, `?tlog_mode=qa_off` to disable\n * - Programmatic: `tracelog.setQaMode(true/false)`\n *\n * @returns True if QA mode is active, false otherwise\n */\nexport const detectQaMode = (): boolean => {\n if (!isBrowserEnvironment()) {\n return false;\n }\n\n try {\n const params = new URLSearchParams(window.location.search);\n const urlParam = params.get(QA_MODE_URL_PARAM);\n const storedState = sessionStorage.getItem(QA_MODE_KEY);\n\n let newState: boolean | null = null;\n\n if (urlParam === QA_MODE_ENABLE_VALUE) {\n newState = true;\n sessionStorage.setItem(QA_MODE_KEY, 'true');\n\n log('info', 'QA Mode ACTIVE', {\n visibility: 'qa',\n style: LOG_STYLE_ACTIVE,\n });\n } else if (urlParam === QA_MODE_DISABLE_VALUE) {\n newState = false;\n sessionStorage.setItem(QA_MODE_KEY, 'false');\n\n log('info', 'QA Mode DISABLED', {\n visibility: 'qa',\n style: LOG_STYLE_DISABLED,\n });\n }\n\n if (urlParam === QA_MODE_ENABLE_VALUE || urlParam === QA_MODE_DISABLE_VALUE) {\n cleanUrlParameter();\n }\n\n return newState ?? storedState === 'true';\n } catch {\n return false;\n }\n};\n\n/**\n * Set QA mode state programmatically\n *\n * @param enabled - True to enable, false to disable\n */\nexport const setQaMode = (enabled: boolean): void => {\n if (!isBrowserEnvironment()) {\n return;\n }\n\n try {\n sessionStorage.setItem(QA_MODE_KEY, enabled ? 'true' : 'false');\n\n log('info', enabled ? 'QA Mode ACTIVE' : 'QA Mode DISABLED', {\n visibility: 'qa',\n style: enabled ? LOG_STYLE_ACTIVE : LOG_STYLE_DISABLED,\n });\n } catch {\n log('debug', 'Cannot set QA mode: sessionStorage unavailable');\n }\n};\n\n/**\n * Check if QA mode is currently active\n *\n * @returns True if QA mode is active, false otherwise\n */\nexport const isQaModeActive = (): boolean => {\n if (!isBrowserEnvironment()) {\n return false;\n }\n\n try {\n return sessionStorage.getItem(QA_MODE_KEY) === 'true';\n } catch {\n return false;\n }\n};\n","import { log } from '../logging.utils';\n\n/**\n * List of compound TLDs that require special handling for root domain extraction.\n * Keep in sync with tracelog-api/src/utils/common/utils.ts\n */\nconst COMPOUND_TLDS = [\n 'co.uk',\n 'org.uk',\n 'com.au',\n 'net.au',\n 'com.br',\n 'co.nz',\n 'co.jp',\n 'com.mx',\n 'co.in',\n 'com.cn',\n 'co.za',\n];\n\n/**\n * Extracts the root (registrable) domain from a hostname.\n * Handles both standard TLDs (.com, .org) and compound TLDs (.co.uk, .com.br).\n *\n * @example\n * getRootDomain('www.example.com') // 'example.com'\n * getRootDomain('app.blog.example.com') // 'example.com'\n * getRootDomain('shop.example.co.uk') // 'example.co.uk'\n */\nconst getRootDomain = (hostname: string): string => {\n const parts = hostname.toLowerCase().split('.');\n if (parts.length <= 2) {\n return hostname.toLowerCase();\n }\n const lastTwo = parts.slice(-2).join('.');\n if (COMPOUND_TLDS.includes(lastTwo)) {\n return parts.slice(-3).join('.');\n }\n return parts.slice(-2).join('.');\n};\n\n/**\n * Checks if two hostnames belong to the same domain (including subdomains).\n * Extracts root domain and compares to handle cross-subdomain navigation.\n *\n * @example\n * isSameDomain('www.example.com', 'example.com') // true\n * isSameDomain('app.example.com', 'www.example.com') // true\n * isSameDomain('example.co.uk', 'app.example.co.uk') // true\n *\n * @param hostname1 - First hostname (e.g., 'www.example.com')\n * @param hostname2 - Second hostname (e.g., 'app.example.com')\n * @returns true if same root domain\n */\nconst isSameDomain = (hostname1: string, hostname2: string): boolean => {\n if (hostname1 === hostname2) {\n return true;\n }\n return getRootDomain(hostname1) === getRootDomain(hostname2);\n};\n\n/**\n * Returns the referrer if it's external, or 'Direct' if internal/empty.\n *\n * **Purpose**: Filter out internal referrers (same domain) to ensure\n * accurate traffic source attribution. Internal referrers occur when:\n * - Session expires and user navigates within the same site\n * - User opens new tab from an internal link\n * - Page refresh after session timeout\n *\n * **Logic**:\n * - Empty referrer → 'Direct'\n * - Referrer from same domain or subdomain → 'Direct' (internal navigation)\n * - External referrer → Returns original referrer\n *\n * **Subdomain Detection**:\n * - `www.example.com` → `example.com` ✓ (internal)\n * - `blog.example.com` → `example.com` ✓ (internal)\n * - `example.com` → `www.example.com` ✓ (internal)\n *\n * @returns External referrer URL or 'Direct'\n */\nexport const getExternalReferrer = (): string => {\n const referrer = document.referrer;\n if (!referrer) {\n return 'Direct';\n }\n try {\n const referrerHostname = new URL(referrer).hostname.toLowerCase();\n const currentHostname = window.location.hostname.toLowerCase();\n if (isSameDomain(referrerHostname, currentHostname)) {\n return 'Direct';\n }\n return referrer;\n } catch (error) {\n log('debug', 'Failed to parse referrer URL, using raw value', { error, data: { referrer } });\n return referrer;\n }\n};\n","import { UTM_PARAMS } from '../../constants';\nimport { UTM } from '../../types/event.types';\n\n/**\n * Extracts UTM parameters from the current URL\n * @returns UTM parameters object or undefined if none found\n */\nexport const getUTMParameters = (): UTM | undefined => {\n const urlParams = new URLSearchParams(window.location.search);\n const utmParams: Partial<Record<keyof UTM, string>> = {};\n\n UTM_PARAMS.forEach((param) => {\n const value = urlParams.get(param);\n\n if (value) {\n const key = param.split('utm_')[1] as keyof UTM;\n utmParams[key] = value;\n }\n });\n\n const result = Object.keys(utmParams).length ? utmParams : undefined;\n\n return result;\n};\n","/**\n * Generates a RFC4122 compliant UUID v4 using native crypto API with fallback\n * @returns A UUID string\n */\nexport const generateUUID = (): string => {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n};\n\n/**\n * Sequence counter for generating unique event IDs within the same millisecond.\n * Resets when timestamp changes, preventing collisions in high-frequency event bursts.\n */\nlet eventSequence = 0;\nlet lastEventTimestamp = 0;\n\n/**\n * Generates a unique event ID optimized for high-frequency event tracking.\n *\n * **Collision Prevention Strategy:**\n * - Timestamp: Millisecond precision for temporal ordering\n * - Sequence: Auto-incrementing counter (0-999) for same-millisecond events\n * - Random: Cryptographically secure random (3 bytes) for cross-tab/process uniqueness\n *\n * **Format:** `{timestamp}-{sequence}-{random}`\n * **Example:** `1704067200000-001-a3f9c2`\n *\n * **Guarantees:**\n * - ✅ No collisions within same millisecond (sequence counter)\n * - ✅ No collisions across tabs (crypto random)\n * - ✅ Temporal ordering preserved (timestamp first)\n * - ✅ Works in high-frequency bursts (1000 events/ms capacity)\n * - ✅ Clock skew protection (monotonic timestamp guarantee)\n *\n * @returns Unique event ID string\n */\nexport const generateEventId = (): string => {\n let timestamp = Date.now();\n\n // Protect against clock skew (NTP sync, manual time adjustments, timezone changes)\n // If clock moves backward, use last valid timestamp to prevent ID collisions\n if (timestamp < lastEventTimestamp) {\n timestamp = lastEventTimestamp;\n }\n\n // Increment sequence counter for events in same millisecond\n if (timestamp === lastEventTimestamp) {\n eventSequence = (eventSequence + 1) % 1000; // Reset at 1000 to keep 3 digits\n } else {\n eventSequence = 0;\n }\n\n // Always update lastEventTimestamp to track current (possibly adjusted) timestamp\n lastEventTimestamp = timestamp;\n\n const sequence = eventSequence.toString().padStart(3, '0');\n\n // Cryptographically secure random (3 bytes = 6 hex chars)\n // Reduced from 4 bytes since sequence provides uniqueness within millisecond\n let random = '';\n try {\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n const bytes = crypto.getRandomValues(new Uint8Array(3));\n if (bytes) {\n random = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n }\n }\n } catch {\n /* empty */\n }\n\n // Fallback to Math.random() if crypto unavailable\n if (!random) {\n random = Math.floor(Math.random() * 0xffffff)\n .toString(16)\n .padStart(6, '0');\n }\n\n return `${timestamp}-${sequence}-${random}`;\n};\n","import { Config } from '../../types';\nimport { DEFAULT_SENSITIVE_QUERY_PARAMS } from '../../constants';\nimport { log } from '../logging.utils';\n\n/**\n * Validates if a URL is valid and optionally allows HTTP URLs\n * @param url - The URL to validate\n * @param allowHttp - Whether to allow HTTP URLs (default: false)\n * @returns True if the URL is valid, false otherwise\n */\nconst isValidUrl = (url: string, allowHttp = false): boolean => {\n try {\n const parsed = new URL(url);\n const isHttps = parsed.protocol === 'https:';\n const isHttp = parsed.protocol === 'http:';\n\n return isHttps || (allowHttp && isHttp);\n } catch {\n return false;\n }\n};\n\n/**\n * Generates a SaaS API URL based on the given project ID and the current browser domain.\n * @param projectId - The project ID to use as a subdomain.\n * @returns The generated SaaS API URL.\n */\nconst generateSaasApiUrl = (projectId: string): string => {\n try {\n const url = new URL(window.location.href);\n const host = url.hostname;\n\n if (!host || typeof host !== 'string') {\n throw new Error('Invalid hostname');\n }\n\n if (host === 'localhost' || host === '127.0.0.1' || /^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(host)) {\n throw new Error(\n 'SaaS integration not supported on localhost or IP addresses. Use custom backend integration instead.',\n );\n }\n\n const parts = host.split('.');\n\n if (!parts || !Array.isArray(parts) || parts.length === 0 || (parts.length === 1 && parts[0] === '')) {\n throw new Error('Invalid hostname structure');\n }\n\n if (parts.length === 1) {\n throw new Error('Single-part domain not supported for SaaS integration');\n }\n\n let cleanDomain: string;\n\n if (parts.length === 2) {\n cleanDomain = parts.join('.');\n } else {\n cleanDomain = parts.slice(-2).join('.');\n }\n\n if (!cleanDomain || cleanDomain.split('.').length < 2) {\n throw new Error('Invalid domain structure for SaaS');\n }\n\n const collectApiUrl = `https://${projectId}.${cleanDomain}/collect`;\n const isValid = isValidUrl(collectApiUrl);\n\n if (!isValid) {\n throw new Error('Generated URL failed validation');\n }\n\n return collectApiUrl;\n } catch (error) {\n throw new Error(`Invalid SaaS URL configuration: ${error instanceof Error ? error.message : String(error)}`);\n }\n};\n\n/**\n * Generates collection API URLs for all configured integrations\n * @param config - The TraceLog configuration\n * @returns Object containing API URLs for each configured integration\n */\nexport const getCollectApiUrls = (config: Config): { saas?: string; custom?: string } => {\n const urls: { saas?: string; custom?: string } = {};\n\n // TraceLog SaaS integration\n if (config.integrations?.tracelog?.projectId) {\n urls.saas = generateSaasApiUrl(config.integrations.tracelog.projectId);\n }\n\n // Custom backend integration\n const customApiUrl = config.integrations?.custom?.collectApiUrl;\n if (customApiUrl) {\n const allowHttp = config.integrations?.custom?.allowHttp ?? false;\n const isValid = isValidUrl(customApiUrl, allowHttp);\n\n if (!isValid) {\n throw new Error('Invalid custom API URL');\n }\n\n urls.custom = customApiUrl;\n }\n\n return urls;\n};\n\n/**\n * Normalizes a URL by removing sensitive query parameters\n * Combines default sensitive parameters with custom ones provided by user\n * @param url - The URL to normalize\n * @param sensitiveQueryParams - Array of parameter names to remove (merged with defaults)\n * @returns The normalized URL\n */\nexport const normalizeUrl = (url: string, sensitiveQueryParams: string[] = []): string => {\n if (!url || typeof url !== 'string') {\n log('warn', 'Invalid URL provided to normalizeUrl', { data: { type: typeof url } });\n return url || '';\n }\n\n try {\n const urlObject = new URL(url);\n const searchParams = urlObject.searchParams;\n\n const allSensitiveParams = [...new Set([...DEFAULT_SENSITIVE_QUERY_PARAMS, ...sensitiveQueryParams])];\n\n let hasChanged = false;\n const removedParams: string[] = [];\n\n allSensitiveParams.forEach((param) => {\n if (searchParams.has(param)) {\n searchParams.delete(param);\n hasChanged = true;\n removedParams.push(param);\n }\n });\n\n if (!hasChanged && url.includes('?')) {\n return url;\n }\n\n urlObject.search = searchParams.toString();\n const result = urlObject.toString();\n\n return result;\n } catch (error) {\n log('warn', 'URL normalization failed, returning original', { error, data: { urlLength: url?.length } });\n\n return url;\n }\n};\n","import {\n MAX_ARRAY_LENGTH,\n MAX_OBJECT_DEPTH,\n MAX_STRING_LENGTH,\n MAX_NESTED_OBJECT_KEYS,\n XSS_PATTERNS,\n} from '../../constants';\nimport { MetadataType } from '../../types';\nimport { log } from '../logging.utils';\n\n/**\n * Sanitizes a string value to prevent XSS attacks\n * @param value - The string to sanitize\n * @returns The sanitized string\n */\nexport const sanitizeString = (value: string): string => {\n if (!value || typeof value !== 'string' || value.trim().length === 0) {\n return '';\n }\n\n let sanitized = value;\n\n if (value.length > MAX_STRING_LENGTH) {\n sanitized = value.slice(0, Math.max(0, MAX_STRING_LENGTH));\n }\n\n let xssPatternMatches = 0;\n for (const pattern of XSS_PATTERNS) {\n const beforeReplace = sanitized;\n sanitized = sanitized.replace(pattern, '');\n if (beforeReplace !== sanitized) {\n xssPatternMatches++;\n }\n }\n\n if (xssPatternMatches > 0) {\n log('warn', 'XSS patterns detected and removed', {\n data: {\n patternMatches: xssPatternMatches,\n valueLength: value.length,\n },\n });\n }\n\n sanitized = sanitized\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&#x27;')\n .replaceAll('/', '&#x2F;');\n\n const result = sanitized.trim();\n\n return result;\n};\n\n/**\n * Sanitizes any value recursively with depth protection\n * @param value - The value to sanitize\n * @param depth - Current recursion depth\n * @returns The sanitized value\n */\nconst sanitizeValue = (value: unknown, depth = 0): unknown => {\n if (depth > MAX_OBJECT_DEPTH) {\n return null;\n }\n\n if (value === null || value === undefined) {\n return null;\n }\n\n if (typeof value === 'string') {\n return sanitizeString(value);\n }\n\n if (typeof value === 'number') {\n if (!Number.isFinite(value) || value < -Number.MAX_SAFE_INTEGER || value > Number.MAX_SAFE_INTEGER) {\n return 0;\n }\n\n return value;\n }\n\n if (typeof value === 'boolean') {\n return value;\n }\n\n if (Array.isArray(value)) {\n const limitedArray = value.slice(0, MAX_ARRAY_LENGTH);\n const sanitizedArray = limitedArray.map((item) => sanitizeValue(item, depth + 1)).filter((item) => item !== null);\n\n return sanitizedArray;\n }\n\n if (typeof value === 'object') {\n const sanitizedObject: Record<string, unknown> = {};\n const entries = Object.entries(value);\n const limitedEntries = entries.slice(0, MAX_NESTED_OBJECT_KEYS);\n\n for (const [key, value_] of limitedEntries) {\n const sanitizedKey = sanitizeString(key);\n\n if (sanitizedKey) {\n const sanitizedValue = sanitizeValue(value_, depth + 1);\n\n if (sanitizedValue !== null) {\n sanitizedObject[sanitizedKey] = sanitizedValue;\n }\n }\n }\n\n return sanitizedObject;\n }\n\n return null;\n};\n\n/**\n * Sanitizes user metadata for custom events\n * @param metadata - The metadata to sanitize\n * @returns The sanitized metadata\n */\nexport const sanitizeMetadata = (metadata: unknown): Record<string, MetadataType> => {\n if (typeof metadata !== 'object' || metadata === null) {\n return {};\n }\n\n try {\n const sanitized = sanitizeValue(metadata);\n const result =\n typeof sanitized === 'object' && sanitized !== null ? (sanitized as Record<string, MetadataType>) : {};\n\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new Error(`[TraceLog] Metadata sanitization failed: ${errorMessage}`);\n }\n};\n","import {\n MAX_SESSION_TIMEOUT_MS,\n MIN_SESSION_TIMEOUT_MS,\n DEFAULT_SESSION_TIMEOUT,\n DEFAULT_SAMPLING_RATE,\n VALIDATION_MESSAGES,\n DEFAULT_PAGE_VIEW_THROTTLE_MS,\n DEFAULT_CLICK_THROTTLE_MS,\n MAX_SAME_EVENT_PER_MINUTE,\n DEFAULT_VIEWPORT_COOLDOWN_PERIOD,\n DEFAULT_VIEWPORT_MAX_TRACKED_ELEMENTS,\n DEFAULT_VISIBILITY_TIMEOUT_MS,\n DEFAULT_ERROR_SAMPLING_RATE,\n} from '../../constants';\nimport {\n Config,\n AppConfigValidationError,\n SessionTimeoutValidationError,\n SamplingRateValidationError,\n IntegrationValidationError,\n} from '../../types';\n\n/**\n * Validates the app configuration object (before normalization)\n * This validates the structure and basic types but allows for normalization afterward\n * @param config - The app configuration to validate\n * @throws {ProjectIdValidationError} If project ID validation fails\n * @throws {AppConfigValidationError} If other configuration validation fails\n */\nexport const validateAppConfig = (config?: Config): void => {\n if (config !== undefined && (config === null || typeof config !== 'object')) {\n throw new AppConfigValidationError('Configuration must be an object', 'config');\n }\n\n if (!config) {\n return;\n }\n\n if (config.sessionTimeout !== undefined) {\n if (\n typeof config.sessionTimeout !== 'number' ||\n config.sessionTimeout < MIN_SESSION_TIMEOUT_MS ||\n config.sessionTimeout > MAX_SESSION_TIMEOUT_MS\n ) {\n throw new SessionTimeoutValidationError(VALIDATION_MESSAGES.INVALID_SESSION_TIMEOUT, 'config');\n }\n }\n\n if (config.globalMetadata !== undefined) {\n if (typeof config.globalMetadata !== 'object' || config.globalMetadata === null) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_GLOBAL_METADATA, 'config');\n }\n }\n\n if (config.integrations) {\n validateIntegrations(config.integrations);\n }\n\n if (config.sensitiveQueryParams !== undefined) {\n if (!Array.isArray(config.sensitiveQueryParams)) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_SENSITIVE_QUERY_PARAMS, 'config');\n }\n\n for (const param of config.sensitiveQueryParams) {\n if (typeof param !== 'string') {\n throw new AppConfigValidationError('All sensitive query params must be strings', 'config');\n }\n }\n }\n\n if (config.errorSampling !== undefined) {\n if (typeof config.errorSampling !== 'number' || config.errorSampling < 0 || config.errorSampling > 1) {\n throw new SamplingRateValidationError(VALIDATION_MESSAGES.INVALID_ERROR_SAMPLING_RATE, 'config');\n }\n }\n\n if (config.samplingRate !== undefined) {\n if (typeof config.samplingRate !== 'number' || config.samplingRate < 0 || config.samplingRate > 1) {\n throw new SamplingRateValidationError(VALIDATION_MESSAGES.INVALID_SAMPLING_RATE, 'config');\n }\n }\n\n if (config.primaryScrollSelector !== undefined) {\n if (typeof config.primaryScrollSelector !== 'string' || !config.primaryScrollSelector.trim()) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_PRIMARY_SCROLL_SELECTOR, 'config');\n }\n\n // Validate CSS selector syntax (skip for 'window' special value)\n if (config.primaryScrollSelector !== 'window') {\n try {\n document.querySelector(config.primaryScrollSelector);\n } catch {\n throw new AppConfigValidationError(\n `${VALIDATION_MESSAGES.INVALID_PRIMARY_SCROLL_SELECTOR_SYNTAX}: \"${config.primaryScrollSelector}\"`,\n 'config',\n );\n }\n }\n }\n\n if (config.pageViewThrottleMs !== undefined) {\n if (typeof config.pageViewThrottleMs !== 'number' || config.pageViewThrottleMs < 0) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_PAGE_VIEW_THROTTLE, 'config');\n }\n }\n\n if (config.clickThrottleMs !== undefined) {\n if (typeof config.clickThrottleMs !== 'number' || config.clickThrottleMs < 0) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_CLICK_THROTTLE, 'config');\n }\n }\n\n if (config.maxSameEventPerMinute !== undefined) {\n if (typeof config.maxSameEventPerMinute !== 'number' || config.maxSameEventPerMinute <= 0) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_MAX_SAME_EVENT_PER_MINUTE, 'config');\n }\n }\n\n if (config.viewport !== undefined) {\n validateViewportConfig(config.viewport);\n }\n\n if (config.webVitalsMode !== undefined) {\n // Type check first\n if (typeof config.webVitalsMode !== 'string') {\n throw new AppConfigValidationError(\n `Invalid webVitalsMode type: ${typeof config.webVitalsMode}. Must be a string`,\n 'config',\n );\n }\n\n const validModes = ['all', 'needs-improvement', 'poor'];\n if (!validModes.includes(config.webVitalsMode)) {\n throw new AppConfigValidationError(\n `Invalid webVitalsMode: \"${config.webVitalsMode}\". Must be one of: ${validModes.join(', ')}`,\n 'config',\n );\n }\n }\n\n if (config.webVitalsThresholds !== undefined) {\n // Type check: must be object and not null or array\n if (\n typeof config.webVitalsThresholds !== 'object' ||\n config.webVitalsThresholds === null ||\n Array.isArray(config.webVitalsThresholds)\n ) {\n throw new AppConfigValidationError('webVitalsThresholds must be an object', 'config');\n }\n\n const validKeys = ['LCP', 'FCP', 'CLS', 'INP', 'TTFB', 'LONG_TASK'];\n for (const [key, value] of Object.entries(config.webVitalsThresholds)) {\n if (!validKeys.includes(key)) {\n throw new AppConfigValidationError(\n `Invalid Web Vitals threshold key: \"${key}\". Must be one of: ${validKeys.join(', ')}`,\n 'config',\n );\n }\n\n // Validate value is a valid number (not NaN, Infinity, negative, or non-number)\n if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {\n throw new AppConfigValidationError(\n `Invalid Web Vitals threshold value for ${key}: ${value}. Must be a non-negative finite number`,\n 'config',\n );\n }\n }\n }\n};\n\n/**\n * Validates viewport configuration\n * @param viewport - Viewport configuration to validate\n */\nconst validateViewportConfig = (viewport: Config['viewport']): void => {\n if (typeof viewport !== 'object' || viewport === null) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_CONFIG, 'config');\n }\n\n // Validate elements array\n if (!viewport.elements || !Array.isArray(viewport.elements)) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_ELEMENTS, 'config');\n }\n\n if (viewport.elements.length === 0) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_ELEMENTS, 'config');\n }\n\n // Track unique selectors to detect duplicates\n const uniqueSelectors = new Set<string>();\n\n // Validate each element\n for (const element of viewport.elements) {\n if (!element.selector || typeof element.selector !== 'string' || !element.selector.trim()) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_ELEMENT, 'config');\n }\n\n // Check for duplicate selectors\n const normalizedSelector = element.selector.trim();\n if (uniqueSelectors.has(normalizedSelector)) {\n throw new AppConfigValidationError(\n `Duplicate viewport selector found: \"${normalizedSelector}\". Each selector should appear only once.`,\n 'config',\n );\n }\n uniqueSelectors.add(normalizedSelector);\n\n if (element.id !== undefined && (typeof element.id !== 'string' || !element.id.trim())) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_ELEMENT_ID, 'config');\n }\n\n if (element.name !== undefined && (typeof element.name !== 'string' || !element.name.trim())) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_ELEMENT_NAME, 'config');\n }\n }\n\n // Validate threshold\n if (viewport.threshold !== undefined) {\n if (typeof viewport.threshold !== 'number' || viewport.threshold < 0 || viewport.threshold > 1) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_THRESHOLD, 'config');\n }\n }\n\n // Validate minDwellTime\n if (viewport.minDwellTime !== undefined) {\n if (typeof viewport.minDwellTime !== 'number' || viewport.minDwellTime < 0) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_MIN_DWELL_TIME, 'config');\n }\n }\n\n // Validate cooldownPeriod\n if (viewport.cooldownPeriod !== undefined) {\n if (typeof viewport.cooldownPeriod !== 'number' || viewport.cooldownPeriod < 0) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_COOLDOWN_PERIOD, 'config');\n }\n }\n\n // Validate maxTrackedElements\n if (viewport.maxTrackedElements !== undefined) {\n if (typeof viewport.maxTrackedElements !== 'number' || viewport.maxTrackedElements <= 0) {\n throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_MAX_TRACKED_ELEMENTS, 'config');\n }\n }\n};\n\n/**\n * Validates integrations configuration\n * @param integrations - Integrations configuration to validate\n */\nconst validateIntegrations = (integrations: Config['integrations']): void => {\n if (!integrations) {\n return;\n }\n\n if (integrations.tracelog) {\n if (\n !integrations.tracelog.projectId ||\n typeof integrations.tracelog.projectId !== 'string' ||\n integrations.tracelog.projectId.trim() === ''\n ) {\n throw new IntegrationValidationError(VALIDATION_MESSAGES.INVALID_TRACELOG_PROJECT_ID, 'config');\n }\n }\n\n if (integrations.custom) {\n if (\n !integrations.custom.collectApiUrl ||\n typeof integrations.custom.collectApiUrl !== 'string' ||\n integrations.custom.collectApiUrl.trim() === ''\n ) {\n throw new IntegrationValidationError(VALIDATION_MESSAGES.INVALID_CUSTOM_API_URL, 'config');\n }\n\n if (integrations.custom.allowHttp !== undefined && typeof integrations.custom.allowHttp !== 'boolean') {\n throw new IntegrationValidationError('allowHttp must be a boolean', 'config');\n }\n\n const collectApiUrl = integrations.custom.collectApiUrl.trim();\n\n if (!collectApiUrl.startsWith('http://') && !collectApiUrl.startsWith('https://')) {\n throw new IntegrationValidationError('Custom API URL must start with \"http://\" or \"https://\"', 'config');\n }\n\n const allowHttp = integrations.custom.allowHttp ?? false;\n\n if (!allowHttp && collectApiUrl.startsWith('http://')) {\n throw new IntegrationValidationError(\n 'Custom API URL must use HTTPS in production. Set allowHttp: true in integration config to allow HTTP (not recommended)',\n 'config',\n );\n }\n }\n};\n\n/**\n * Validates and normalizes the app configuration\n * This is the primary validation entry point that ensures consistent behavior\n * @param config - The app configuration to validate and normalize\n * @returns The normalized configuration\n * @throws {ProjectIdValidationError} If project ID validation fails after normalization\n * @throws {AppConfigValidationError} If other configuration validation fails\n */\nexport const validateAndNormalizeConfig = (config?: Config): Config => {\n validateAppConfig(config);\n\n const normalizedConfig: Config = {\n ...(config ?? {}),\n sessionTimeout: config?.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT,\n globalMetadata: config?.globalMetadata ?? {},\n sensitiveQueryParams: config?.sensitiveQueryParams ?? [],\n errorSampling: config?.errorSampling ?? DEFAULT_ERROR_SAMPLING_RATE,\n samplingRate: config?.samplingRate ?? DEFAULT_SAMPLING_RATE,\n pageViewThrottleMs: config?.pageViewThrottleMs ?? DEFAULT_PAGE_VIEW_THROTTLE_MS,\n clickThrottleMs: config?.clickThrottleMs ?? DEFAULT_CLICK_THROTTLE_MS,\n maxSameEventPerMinute: config?.maxSameEventPerMinute ?? MAX_SAME_EVENT_PER_MINUTE,\n };\n\n if (normalizedConfig.integrations?.custom) {\n normalizedConfig.integrations.custom = {\n ...normalizedConfig.integrations.custom,\n allowHttp: normalizedConfig.integrations.custom.allowHttp ?? false,\n };\n }\n\n if (normalizedConfig.viewport) {\n normalizedConfig.viewport = {\n ...normalizedConfig.viewport,\n threshold: normalizedConfig.viewport.threshold ?? 0.5,\n minDwellTime: normalizedConfig.viewport.minDwellTime ?? DEFAULT_VISIBILITY_TIMEOUT_MS,\n cooldownPeriod: normalizedConfig.viewport.cooldownPeriod ?? DEFAULT_VIEWPORT_COOLDOWN_PERIOD,\n maxTrackedElements: normalizedConfig.viewport.maxTrackedElements ?? DEFAULT_VIEWPORT_MAX_TRACKED_ELEMENTS,\n };\n }\n\n return normalizedConfig;\n};\n","import { MAX_NESTED_OBJECT_KEYS, MAX_METADATA_NESTING_DEPTH } from '../../constants';\n\n/**\n * Validates if an item in an array is a valid nested object\n * @param item - The item to validate\n * @returns True if the item is a valid nested object\n */\nconst isValidArrayItem = (item: unknown): boolean => {\n if (typeof item === 'string') {\n return true;\n }\n\n // Allow objects with primitive fields only (one level deep)\n if (typeof item === 'object' && item !== null && !Array.isArray(item)) {\n const entries = Object.entries(item);\n\n // Check key count limit\n if (entries.length > MAX_NESTED_OBJECT_KEYS) {\n return false;\n }\n\n // All values must be primitives (no nested objects or arrays)\n for (const [, value] of entries) {\n if (value === null || value === undefined) {\n continue;\n }\n\n const type = typeof value;\n if (type !== 'string' && type !== 'number' && type !== 'boolean') {\n return false;\n }\n }\n\n return true;\n }\n\n return false;\n};\n\n/**\n * Checks if an object contains only primitive fields, string arrays, arrays of flat objects,\n * or nested objects with primitive fields\n * @param object - The object to check\n * @param depth - Current nesting depth (default: 0, max: MAX_METADATA_NESTING_DEPTH)\n * @returns True if the object contains only valid fields\n */\nexport const isOnlyPrimitiveFields = (object: Record<string, unknown>, depth = 0): boolean => {\n if (typeof object !== 'object' || object === null) {\n return false;\n }\n\n if (depth > MAX_METADATA_NESTING_DEPTH) {\n return false;\n }\n\n for (const value of Object.values(object)) {\n if (value === null || value === undefined) {\n continue;\n }\n\n const type = typeof value;\n if (type === 'string' || type === 'number' || type === 'boolean') {\n continue;\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) {\n continue;\n }\n\n // Determine array type from first item\n const firstItem = value[0];\n const isStringArray = typeof firstItem === 'string';\n\n // All items must be of the same type (all strings OR all objects)\n if (isStringArray) {\n if (!value.every((item) => typeof item === 'string')) {\n return false;\n }\n } else {\n // Must be all objects\n if (!value.every((item) => isValidArrayItem(item))) {\n return false;\n }\n }\n\n continue;\n }\n\n // Allow nested objects at depth 0 only (one level deep)\n if (type === 'object' && depth === 0) {\n if (!isOnlyPrimitiveFields(value as Record<string, unknown>, depth + 1)) {\n return false;\n }\n continue;\n }\n\n return false;\n }\n\n return true;\n};\n","import {\n MAX_CUSTOM_EVENT_ARRAY_SIZE,\n MAX_CUSTOM_EVENT_KEYS,\n MAX_CUSTOM_EVENT_NAME_LENGTH,\n MAX_CUSTOM_EVENT_STRING_SIZE,\n MAX_STRING_LENGTH,\n MAX_STRING_LENGTH_IN_ARRAY,\n} from '../../constants';\nimport { MetadataType } from '../../types';\nimport { sanitizeMetadata } from '../security/sanitize.utils';\nimport { isOnlyPrimitiveFields } from './type-guards.utils';\n\n/**\n * Validates an event name\n * @param eventName - The event name to validate\n * @returns Validation result with error message if invalid\n */\nexport const isValidEventName = (eventName: string): { valid: boolean; error?: string } => {\n if (typeof eventName !== 'string') {\n return {\n valid: false,\n error: 'Event name must be a string',\n };\n }\n\n if (eventName.length === 0) {\n return {\n valid: false,\n error: 'Event name cannot be empty',\n };\n }\n\n if (eventName.length > MAX_CUSTOM_EVENT_NAME_LENGTH) {\n return {\n valid: false,\n error: `Event name is too long (max ${MAX_CUSTOM_EVENT_NAME_LENGTH} characters)`,\n };\n }\n\n if (eventName.includes('<') || eventName.includes('>') || eventName.includes('&')) {\n return {\n valid: false,\n error: 'Event name contains invalid characters',\n };\n }\n\n const reservedWords = ['constructor', 'prototype', '__proto__', 'eval', 'function', 'var', 'let', 'const'];\n\n if (reservedWords.includes(eventName.toLowerCase())) {\n return {\n valid: false,\n error: 'Event name cannot be a reserved word',\n };\n }\n\n return { valid: true };\n};\n\n/**\n * Validates a single metadata object\n * @param eventName - The event name (for error messages)\n * @param metadata - The metadata object to validate\n * @param type - Type of metadata (globalMetadata or customEvent)\n * @returns Validation result with sanitized metadata if valid\n */\nconst validateSingleMetadata = (\n eventName: string,\n metadata: Record<string, unknown>,\n type?: 'globalMetadata' | 'customEvent',\n): { valid: boolean; error?: string; sanitizedMetadata?: Record<string, MetadataType> } => {\n const sanitizedMetadata = sanitizeMetadata(metadata);\n const intro =\n type && type === 'customEvent' ? `${type} \"${eventName}\" metadata error` : `${eventName} metadata error`;\n\n if (!isOnlyPrimitiveFields(sanitizedMetadata)) {\n return {\n valid: false,\n error: `${intro}: object has invalid types. Valid types are string, number, boolean or string arrays.`,\n };\n }\n\n let jsonString: string;\n\n try {\n jsonString = JSON.stringify(sanitizedMetadata);\n } catch {\n return {\n valid: false,\n error: `${intro}: object contains circular references or cannot be serialized.`,\n };\n }\n\n if (jsonString.length > MAX_CUSTOM_EVENT_STRING_SIZE) {\n return {\n valid: false,\n error: `${intro}: object is too large (max ${MAX_CUSTOM_EVENT_STRING_SIZE / 1024} KB).`,\n };\n }\n\n const keyCount = Object.keys(sanitizedMetadata).length;\n\n if (keyCount > MAX_CUSTOM_EVENT_KEYS) {\n return {\n valid: false,\n error: `${intro}: object has too many keys (max ${MAX_CUSTOM_EVENT_KEYS} keys).`,\n };\n }\n\n for (const [key, value] of Object.entries(sanitizedMetadata)) {\n if (Array.isArray(value)) {\n if (value.length > MAX_CUSTOM_EVENT_ARRAY_SIZE) {\n return {\n valid: false,\n error: `${intro}: array property \"${key}\" is too large (max ${MAX_CUSTOM_EVENT_ARRAY_SIZE} items).`,\n };\n }\n\n for (const item of value) {\n if (typeof item === 'string' && item.length > MAX_STRING_LENGTH_IN_ARRAY) {\n return {\n valid: false,\n error: `${intro}: array property \"${key}\" contains strings that are too long (max ${MAX_STRING_LENGTH_IN_ARRAY} characters).`,\n };\n }\n }\n }\n\n if (typeof value === 'string' && value.length > MAX_STRING_LENGTH) {\n return {\n valid: false,\n error: `${intro}: property \"${key}\" is too long (max ${MAX_STRING_LENGTH} characters).`,\n };\n }\n }\n\n return {\n valid: true,\n sanitizedMetadata,\n };\n};\n\n/**\n * Validates metadata for events (supports both objects and arrays of objects)\n * @param eventName - The event name (for error messages)\n * @param metadata - The metadata to validate\n * @param type - Type of metadata (globalMetadata or customEvent)\n * @returns Validation result with sanitized metadata if valid\n */\nexport const isValidMetadata = (\n eventName: string,\n metadata: Record<string, unknown> | Record<string, unknown>[],\n type?: 'globalMetadata' | 'customEvent',\n): {\n valid: boolean;\n error?: string;\n sanitizedMetadata?: Record<string, MetadataType> | Record<string, MetadataType>[];\n} => {\n if (Array.isArray(metadata)) {\n const sanitizedArray: Record<string, MetadataType>[] = [];\n const intro =\n type && type === 'customEvent' ? `${type} \"${eventName}\" metadata error` : `${eventName} metadata error`;\n\n for (let i = 0; i < metadata.length; i++) {\n const item = metadata[i];\n\n if (typeof item !== 'object' || item === null || Array.isArray(item)) {\n return {\n valid: false,\n error: `${intro}: array item at index ${i} must be an object.`,\n };\n }\n\n const itemValidation = validateSingleMetadata(eventName, item, type);\n\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: `${intro}: array item at index ${i} is invalid: ${itemValidation.error}`,\n };\n }\n\n if (itemValidation.sanitizedMetadata) {\n sanitizedArray.push(itemValidation.sanitizedMetadata);\n }\n }\n\n return {\n valid: true,\n sanitizedMetadata: sanitizedArray,\n };\n }\n\n return validateSingleMetadata(eventName, metadata, type);\n};\n","import { MetadataType } from '../../types';\nimport { log } from '../logging.utils';\nimport { isValidEventName, isValidMetadata } from './metadata-validations.utils';\n\n/**\n * Validates a complete event with name and optional metadata\n * @param eventName - The event name to validate\n * @param metadata - Optional metadata to validate\n * @returns Validation result with sanitized metadata if valid\n */\nexport const isEventValid = (\n eventName: string,\n metadata?: Record<string, unknown> | Record<string, unknown>[],\n): {\n valid: boolean;\n error?: string;\n sanitizedMetadata?: Record<string, MetadataType> | Record<string, MetadataType>[];\n} => {\n const nameValidation = isValidEventName(eventName);\n\n if (!nameValidation.valid) {\n log('error', 'Event name validation failed', {\n data: { eventName, error: nameValidation.error },\n });\n\n return nameValidation;\n }\n\n if (!metadata) {\n return { valid: true };\n }\n\n const metadataValidation = isValidMetadata(eventName, metadata, 'customEvent');\n\n if (!metadataValidation.valid) {\n log('error', 'Event metadata validation failed', {\n data: {\n eventName,\n error: metadataValidation.error,\n },\n });\n }\n\n return metadataValidation;\n};\n","import { EmitterCallback, EmitterMap } from '../types';\n\n/**\n * Type-safe event emitter for TraceLog internal events\n *\n * **Purpose**: Provides pub/sub mechanism for internal library events with full TypeScript\n * type safety through `EmitterMap` interface.\n *\n * **Supported Events** (defined in `EmitterMap`):\n * - `event`: Individual events as they are tracked (EventData)\n * - `queue`: Complete event batches before transmission (EventsQueue)\n *\n * **Note**: Developers are responsible for implementing consent logic\n * before calling `init()` and filtering events as needed.\n *\n * **Key Features**:\n * - **Type Safety**: Callbacks receive correctly typed data based on event name\n * - **Memory Management**: Listeners stored in Map for efficient lookup and cleanup\n * - **Synchronous**: All callbacks execute immediately when event is emitted\n * - **No Error Isolation**: Errors in callbacks propagate to caller (by design)\n *\n * **Use Cases**:\n * - External event consumption via `tracelog.on('event', callback)`\n * - Integration testing via `window.__traceLogBridge.on('event', callback)`\n * - Custom analytics integrations\n * - Real-time event monitoring\n *\n * @example\n * ```typescript\n * const emitter = new Emitter();\n *\n * // Subscribe to events\n * const callback = (event: EventData) => {\n * console.log('Event tracked:', event.type);\n * };\n * emitter.on('event', callback);\n *\n * // Emit event (type-safe)\n * emitter.emit('event', {\n * id: '123',\n * type: EventType.CLICK,\n * page_url: 'https://example.com',\n * timestamp: Date.now(),\n * click_data: { x: 100, y: 200, tag: 'button' }\n * });\n *\n * // Unsubscribe\n * emitter.off('event', callback);\n *\n * // Clear all listeners (destroy/cleanup)\n * emitter.removeAllListeners();\n * ```\n *\n * @see EmitterMap for event type definitions\n * @see src/api.ts for public on/off API\n */\nexport class Emitter {\n private readonly listeners: Map<string, EmitterCallback[]> = new Map();\n\n /**\n * Subscribes to an event channel\n *\n * **Behavior**:\n * - Creates event channel if it doesn't exist\n * - Appends callback to list of listeners for this event\n * - Same callback can be registered multiple times (will fire multiple times)\n *\n * **Type Safety**: Callback receives data type matching the event name\n *\n * @param event - Event name to subscribe to\n * @param callback - Function to call when event is emitted\n *\n * @example\n * ```typescript\n * emitter.on('event', (eventData) => {\n * // eventData is typed as EventData\n * console.log(eventData.type);\n * });\n * ```\n */\n on<K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, []);\n }\n\n this.listeners.get(event)!.push(callback);\n }\n\n /**\n * Unsubscribes from an event channel\n *\n * **Behavior**:\n * - Removes first occurrence of callback from listener list\n * - If callback not found, no error is thrown\n * - If callback was registered multiple times, only one instance is removed\n *\n * **Important**: Must use same function reference passed to `on()`\n *\n * @param event - Event name to unsubscribe from\n * @param callback - Function reference to remove (must match `on()` reference)\n *\n * @example\n * ```typescript\n * const callback = (data) => console.log(data);\n * emitter.on('event', callback);\n * emitter.off('event', callback); // Unsubscribes successfully\n *\n * // BAD: Won't work (different function reference)\n * emitter.on('event', (data) => console.log(data));\n * emitter.off('event', (data) => console.log(data)); // No effect\n * ```\n */\n off<K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>): void {\n const callbacks = this.listeners.get(event);\n\n if (callbacks) {\n const index = callbacks.indexOf(callback);\n\n if (index > -1) {\n callbacks.splice(index, 1);\n }\n }\n }\n\n /**\n * Emits an event with data to all subscribed listeners\n *\n * **Behavior**:\n * - Calls all registered callbacks for this event synchronously\n * - Callbacks execute in registration order\n * - If no listeners, no-op (no error thrown)\n * - Errors in callbacks are NOT caught (propagate to caller)\n *\n * **Type Safety**: Data type must match event name's expected type\n *\n * @param event - Event name to emit\n * @param data - Event data (type must match EmitterMap[event])\n *\n * @example\n * ```typescript\n * // Emit event data\n * emitter.emit('event', eventData);\n *\n * // Emit queue data\n * emitter.emit('queue', {\n * user_id: 'user-123',\n * session_id: 'session-456',\n * device: DeviceType.Desktop,\n * events: [event1, event2]\n * });\n * ```\n */\n emit<K extends keyof EmitterMap>(event: K, data: EmitterMap[K]): void {\n const callbacks = this.listeners.get(event);\n\n if (callbacks) {\n callbacks.forEach((callback) => {\n callback(data);\n });\n }\n }\n\n /**\n * Removes all listeners for all events\n *\n * **Purpose**: Cleanup method called during `App.destroy()` to prevent memory leaks\n *\n * **Behavior**:\n * - Clears all event channels\n * - Listeners cannot be restored (new subscriptions required)\n * - Called automatically during library teardown\n *\n * **Use Cases**:\n * - Application teardown\n * - Component unmounting in SPA frameworks\n * - Test cleanup\n *\n * @example\n * ```typescript\n * // During destroy\n * emitter.removeAllListeners();\n * // All subscriptions cleared\n * ```\n */\n removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","import { EventData, EventsQueue, BeforeSendTransformer, BeforeBatchTransformer } from '../types';\nimport { log } from './logging.utils';\n\n/**\n * Applies beforeSend transformer to a single event with error handling\n *\n * @param event - Event to transform\n * @param transformer - Transformer function\n * @param context - Context string for logging (e.g., 'EventManager', 'custom')\n * @returns Transformed event or null if filtered\n */\nexport function transformEvent(\n event: EventData,\n transformer: BeforeSendTransformer,\n context: string,\n): EventData | null {\n try {\n const result = transformer(event);\n\n if (result === null) {\n return null; // Event filtered\n }\n\n if (typeof result === 'object' && result !== null && 'type' in result) {\n return result;\n }\n\n log('warn', `beforeSend transformer returned invalid data, using original [${context}]`);\n\n return event;\n } catch (error) {\n log('error', `beforeSend transformer threw error, using original event [${context}]`, {\n error,\n visibility: 'critical',\n });\n\n return event;\n }\n}\n\n/**\n * Applies beforeSend transformer to an array of events (optimized functional approach)\n *\n * @param events - Array of events to transform\n * @param transformer - Transformer function\n * @param context - Context string for logging\n * @returns Array of transformed events (filtered events removed)\n */\nexport function transformEvents(events: EventData[], transformer: BeforeSendTransformer, context: string): EventData[] {\n return events\n .map((event) => transformEvent(event, transformer, context))\n .filter((event): event is EventData => event !== null);\n}\n\n/**\n * Applies beforeBatch transformer to entire batch with error handling\n *\n * @param batch - Batch to transform\n * @param transformer - Transformer function\n * @param context - Context string for logging\n * @returns Transformed batch or null if filtered\n */\nexport function transformBatch(\n batch: EventsQueue,\n transformer: BeforeBatchTransformer,\n context: string,\n): EventsQueue | null {\n try {\n const result = transformer(batch);\n\n if (result === null) {\n log('debug', `Batch filtered by beforeBatch transformer [${context}]`, {\n data: { eventCount: batch.events.length },\n });\n\n return null;\n }\n\n if (typeof result === 'object' && result !== null && Array.isArray(result.events)) {\n return result;\n }\n\n log('warn', `beforeBatch transformer returned invalid data, using original [${context}]`, {\n data: { eventCount: batch.events.length },\n });\n\n return batch;\n } catch (error) {\n log('error', `beforeBatch transformer threw error, using original batch [${context}]`, {\n error,\n data: { eventCount: batch.events.length },\n visibility: 'critical',\n });\n\n return batch;\n }\n}\n","import { State } from '../types';\n\n/**\n * Global in-memory state store shared across all TraceLog components.\n * Single source of truth for runtime application state.\n */\nconst globalState: State = {} as State;\n\n/**\n * Returns an immutable snapshot of the global state.\n *\n * **Purpose**: Testing and debugging - provides read-only access to internal state.\n *\n * **Usage**:\n * - Test assertions: Verify state changes after operations\n * - Debugging: Inspect current application state\n * - Non-intrusive: Returns readonly reference (no mutations)\n *\n * @returns Readonly reference to global state\n *\n * @example\n * ```typescript\n * const state = getGlobalState();\n * console.log(state.userId); // Read-only access\n * ```\n *\n * @see src/managers/README.md (lines 198-201) for StateManager documentation\n */\nexport const getGlobalState = (): Readonly<State> => {\n return globalState;\n};\n\n/**\n * Clears all properties from the global state.\n *\n * **Purpose**: Test isolation - ensures clean state between test runs.\n *\n * **Warning**: Should only be called in test environments.\n * Calling in production will break the application.\n *\n * **Usage**:\n * - afterEach/beforeEach hooks in test suites\n * - Cleanup after integration tests\n * - Resetting application state for fresh initialization\n *\n * @example\n * ```typescript\n * afterEach(() => {\n * resetGlobalState(); // Clean slate for next test\n * });\n * ```\n */\nexport const resetGlobalState = (): void => {\n Object.keys(globalState).forEach((key) => {\n delete globalState[key as keyof State];\n });\n};\n\n/**\n * Abstract base class providing centralized, type-safe state management.\n *\n * **Purpose**: Foundation for all TraceLog managers and handlers, providing\n * synchronized access to shared application state.\n *\n * **Architecture**:\n * - All managers/handlers extend this class\n * - Single global state instance shared across all subclasses\n * - Type-safe operations via generic methods\n * - In-memory only (no automatic persistence)\n *\n * **Supported State Properties**:\n * - **Core State**: `collectApiUrls`, `config`, `sessionId`, `userId`, `device`, `pageUrl`\n * - **Control Flags**: `mode` (QA/production), `hasStartSession`, `suppressNextScroll`\n * - **Runtime Counters**: `scrollEventCount` (optional)\n *\n * **Implementation Details**:\n * - Synchronous operations (no async overhead)\n * - Memory-efficient (minimal object creation)\n * - No built-in logging (consumers handle their own state logging)\n * - Read-only snapshots via `getState()` prevent accidental mutations\n *\n * @see src/managers/README.md (lines 170-201) for detailed documentation\n *\n * @example\n * ```typescript\n * class MyManager extends StateManager {\n * initialize() {\n * const userId = this.get('userId'); // Read state\n * this.set('mode', 'qa'); // Write state\n * const snapshot = this.getState(); // Readonly copy\n * }\n * }\n * ```\n */\nexport abstract class StateManager {\n /**\n * Retrieves a value from global state.\n *\n * Type-safe getter with compile-time key validation.\n *\n * @template T - State key type (compile-time validated)\n * @param key - State property key\n * @returns Current value for the given key (may be undefined)\n *\n * @example\n * ```typescript\n * const userId = this.get('userId');\n * const config = this.get('config');\n * const sessionId = this.get('sessionId');\n * ```\n */\n protected get<T extends keyof State>(key: T): State[T] {\n return globalState[key];\n }\n\n /**\n * Sets a value in global state.\n *\n * Type-safe setter with compile-time type checking.\n * Changes are immediately visible to all StateManager subclasses.\n *\n * @template T - State key type (compile-time validated)\n * @param key - State property key\n * @param value - New value (type must match State[T])\n *\n * @example\n * ```typescript\n * this.set('sessionId', 'session-123');\n * this.set('mode', Mode.QA);\n * this.set('hasStartSession', true);\n * ```\n */\n protected set<T extends keyof State>(key: T, value: State[T]): void {\n globalState[key] = value;\n }\n\n /**\n * Returns an immutable snapshot of the entire global state.\n *\n * Creates a shallow copy to prevent accidental mutations.\n * Use for debugging or when multiple state properties are needed.\n *\n * @returns Readonly shallow copy of global state\n *\n * @example\n * ```typescript\n * const snapshot = this.getState();\n * console.log(snapshot.userId, snapshot.sessionId);\n * ```\n */\n protected getState(): Readonly<State> {\n return { ...globalState };\n }\n}\n","import {\n QUEUE_KEY,\n EVENT_EXPIRY_HOURS,\n REQUEST_TIMEOUT_MS,\n PERMANENT_ERROR_LOG_THROTTLE_MS,\n MAX_BEACON_PAYLOAD_SIZE,\n PERSISTENCE_THROTTLE_MS,\n MAX_SEND_RETRIES,\n RETRY_BACKOFF_BASE_MS,\n RETRY_BACKOFF_JITTER_MS,\n LIB_VERSION,\n} from '../constants';\nimport {\n PersistedEventsQueue,\n EventsQueue,\n SpecialApiUrl,\n PermanentError,\n TransformerMap,\n CustomHeadersProvider,\n} from '../types';\nimport { log, transformEvents, transformBatch } from '../utils';\nimport { StorageManager } from './storage.manager';\nimport { StateManager } from './state.manager';\n\ninterface SendCallbacks {\n onSuccess?: (eventCount?: number, events?: any[], body?: EventsQueue) => void;\n onFailure?: () => void;\n}\n\n/**\n * Manages sending event queues to configured API endpoints with persistence,\n * recovery, and multi-integration support.\n *\n * **Purpose**: Handles reliable event transmission to backend APIs with automatic\n * persistence for crash recovery and support for multiple integration backends.\n *\n * **Core Functionality**:\n * - **Event Transmission**: Sends event batches via `fetch()` (async) or `sendBeacon()` (sync)\n * - **Automatic Retries**: Up to 2 in-session retry attempts for transient failures (5xx, timeout)\n * - **Persistence & Recovery**: Stores failed events in localStorage, recovers on next page load\n * - **Multi-Integration**: Separate queues for SaaS (`tracelog`) and Custom backends\n * - **Multi-Tab Protection**: 1-second window prevents duplicate sends across tabs\n * - **Event Expiry**: Discards events older than 2 hours (prevents stale data accumulation)\n * - **Transformer Support**: Applies `beforeBatch` transformer before network transmission\n *\n * **Key Features**:\n * - **In-Session Retries**: Up to 2 retry attempts for 5xx/timeout with exponential backoff\n * - **Recovery on Next Page**: Failed events persisted to localStorage, recovered on init\n * - **Payload Size Validation**: 64KB limit for `sendBeacon()` to prevent truncation\n * - **Permanent Error Detection**: 4xx status codes (except 408, 429) marked as permanent\n * - **Independent Multi-Integration**: Separate SenderManager instances for each backend\n *\n * **Error Handling**:\n * - **4xx Errors** (permanent): Logged once per minute (throttled), events discarded, no retries\n * - **5xx Errors** (transient): Retried up to 2 times with exponential backoff, then persisted\n * - **Network Errors** (transient): Retried up to 2 times with exponential backoff, then persisted\n * - **Timeout** (transient): Retried up to 2 times with exponential backoff, then persisted\n *\n * **Storage Keys**:\n * - **Standalone**: `tlog:{userId}:queue` (single queue)\n * - **SaaS Integration**: `tlog:{userId}:queue:saas`\n * - **Custom Integration**: `tlog:{userId}:queue:custom`\n *\n * **Multi-Tab Protection**:\n * - Persisted events include `lastPersistTime` timestamp\n * - Recovery skips events persisted within last 1 second (active tab may retry)\n *\n * @see src/managers/README.md (lines 82-139) for detailed documentation\n *\n * @example\n * ```typescript\n * // Standalone mode (no backend)\n * const sender = new SenderManager(storage);\n * const success = await sender.send(eventsQueue); // No-op, returns true\n *\n * // SaaS integration\n * const saasSender = new SenderManager(storage, 'saas', 'https://api.tracelog.io/collect', {});\n * const success = await saasSender.send(eventsQueue);\n *\n * // Custom backend\n * const customSender = new SenderManager(storage, 'custom', 'https://myapi.com/events', {});\n * const success = await customSender.send(eventsQueue);\n *\n * // Synchronous send (page unload)\n * const success = sender.sendQueueSync(eventsQueue); // Uses sendBeacon()\n * ```\n */\nexport class SenderManager extends StateManager {\n private readonly storeManager: StorageManager;\n private readonly integrationId?: 'saas' | 'custom';\n private readonly apiUrl?: string;\n private readonly transformers: TransformerMap;\n private readonly staticHeaders: Record<string, string>;\n private customHeadersProvider?: CustomHeadersProvider;\n private lastPermanentErrorLog: { statusCode?: number; timestamp: number } | null = null;\n private recoveryInProgress = false;\n private lastMetadataTimestamp = 0;\n private readonly pendingControllers = new Set<AbortController>();\n\n /**\n * Creates a SenderManager instance.\n *\n * **Validation**: `integrationId` and `apiUrl` must both be provided or both be undefined.\n * Throws error if only one is provided.\n *\n * @param storeManager - Storage manager for event persistence\n * @param integrationId - Optional integration identifier ('saas' or 'custom')\n * @param apiUrl - Optional API endpoint URL\n * @param transformers - Optional event transformation hooks\n * @param staticHeaders - Optional static HTTP headers (from config)\n * @param customHeadersProvider - Optional callback for dynamic headers\n * @throws Error if integrationId and apiUrl are not both provided or both undefined\n */\n constructor(\n storeManager: StorageManager,\n integrationId?: 'saas' | 'custom',\n apiUrl?: string,\n transformers: TransformerMap = {},\n staticHeaders: Record<string, string> = {},\n customHeadersProvider?: CustomHeadersProvider,\n ) {\n super();\n\n if ((integrationId && !apiUrl) || (!integrationId && apiUrl)) {\n throw new Error('SenderManager: integrationId and apiUrl must either both be provided or both be undefined');\n }\n\n this.storeManager = storeManager;\n this.integrationId = integrationId;\n this.apiUrl = apiUrl;\n this.transformers = transformers;\n this.staticHeaders = staticHeaders;\n this.customHeadersProvider = customHeadersProvider;\n }\n\n /**\n * Get the integration ID for this sender\n * @returns The integration ID ('saas' or 'custom') or undefined if not set\n */\n public getIntegrationId(): 'saas' | 'custom' | undefined {\n return this.integrationId;\n }\n\n /**\n * Sets the custom headers provider callback.\n * Only applies to 'custom' integration (ignored for 'saas').\n *\n * @param provider - Callback function that returns custom headers\n */\n public setCustomHeadersProvider(provider: CustomHeadersProvider): void {\n this.customHeadersProvider = provider;\n }\n\n /**\n * Removes the custom headers provider callback.\n */\n public removeCustomHeadersProvider(): void {\n this.customHeadersProvider = undefined;\n }\n\n /**\n * Builds custom headers by merging static headers with dynamic headers from provider.\n * Only applies to 'custom' integration (returns empty object for 'saas').\n *\n * @returns Merged custom headers object (dynamic headers override static)\n * @private\n */\n private getCustomHeaders(): Record<string, string> {\n // Only apply custom headers to 'custom' integration\n if (this.integrationId !== 'custom') {\n return {};\n }\n\n let dynamicHeaders: Record<string, string> = {};\n\n if (this.customHeadersProvider) {\n try {\n const result = this.customHeadersProvider();\n\n // Validate return value\n if (typeof result === 'object' && result !== null && !Array.isArray(result)) {\n dynamicHeaders = result;\n } else {\n log('warn', 'Custom headers provider returned invalid value, expected object', {\n data: { received: typeof result },\n });\n }\n } catch (error) {\n log('warn', 'Custom headers provider threw an error, using static headers only', { error });\n }\n }\n\n // Merge: dynamic headers override static headers\n return { ...this.staticHeaders, ...dynamicHeaders };\n }\n\n private getQueueStorageKey(): string {\n const userId = this.get('userId') || 'anonymous';\n const baseKey = QUEUE_KEY(userId);\n // Separate storage keys prevent cross-integration interference in multi-integration mode\n return this.integrationId ? `${baseKey}:${this.integrationId}` : baseKey;\n }\n\n /**\n * Sends events synchronously using `navigator.sendBeacon()`.\n *\n * **Purpose**: Guarantees event delivery before page unload even if network is slow.\n *\n * **Use Cases**:\n * - Page unload (`beforeunload`, `pagehide` events)\n * - Tab close scenarios\n * - Any case where async send might be interrupted\n *\n * **Behavior**:\n * - Uses `navigator.sendBeacon()` (browser-queued, synchronous API)\n * - Payload size limited to 64KB (enforced by browser)\n * - Browser guarantees delivery attempt (survives page close)\n * - NO persistence on failure (fire-and-forget)\n *\n * **Return Values**:\n * - `true`: Send succeeded OR skipped (standalone mode)\n * - `false`: Send failed (network error, browser rejected beacon)\n *\n * **Important**: No retry mechanism for failures. Events are NOT persisted.\n *\n * **Custom Headers Limitation**: Custom headers set via `setCustomHeaders()` are NOT applied\n * to sendBeacon requests due to browser API limitations. The sendBeacon API only supports\n * Content-Type header via Blob. For scenarios requiring custom headers, ensure async\n * sends complete before page unload.\n *\n * @param body - Event queue to send\n * @returns `true` if send succeeded or was skipped, `false` if failed\n *\n * @see sendEventsQueue for async send with persistence\n * @see src/managers/README.md (lines 82-139) for send details\n */\n sendEventsQueueSync(body: EventsQueue): boolean {\n if (this.shouldSkipSend()) {\n return true;\n }\n\n if (this.apiUrl?.includes(SpecialApiUrl.Fail)) {\n log(\n 'warn',\n `Fail mode: simulating network failure (sync)${this.integrationId ? ` [${this.integrationId}]` : ''}`,\n {\n data: { events: body.events.length },\n },\n );\n\n return false;\n }\n\n if (this.apiUrl?.includes(SpecialApiUrl.Localhost)) {\n log(\n 'debug',\n `Success mode: simulating successful send (sync)${this.integrationId ? ` [${this.integrationId}]` : ''}`,\n {\n data: { events: body.events.length },\n },\n );\n\n return true;\n }\n\n return this.sendQueueSyncInternal(body);\n }\n\n /**\n * Sends events asynchronously using `fetch()` API with automatic persistence on failure.\n *\n * **Purpose**: Reliable event transmission with localStorage fallback for failed sends.\n *\n * **Flow**:\n * 1. Calls internal `send()` method (applies transformers, consent checks)\n * 2. On success: Clears persisted events, invokes `onSuccess` callback\n * 3. On failure: Persists events to localStorage, invokes `onFailure` callback\n * 4. On permanent error (4xx): Clears persisted events (no retry)\n *\n * **Callbacks**:\n * - `onSuccess(eventCount, events, body)`: Called after successful transmission\n * - `onFailure()`: Called after failed transmission or permanent error\n *\n * **Error Handling**:\n * - **Permanent errors** (4xx except 408, 429): Events discarded, not persisted\n * - **Transient errors** (5xx, network, timeout): Events persisted for recovery\n *\n * **Important**: Events are NOT retried in-session. Persistence is for\n * recovery on next page load via `recoverPersistedEvents()`.\n *\n * @param body - Event queue to send\n * @param callbacks - Optional success/failure callbacks\n * @returns Promise resolving to `true` if send succeeded, `false` if failed\n *\n * @see recoverPersistedEvents for recovery flow\n * @see src/managers/README.md (lines 82-139) for send details\n */\n async sendEventsQueue(body: EventsQueue, callbacks?: SendCallbacks): Promise<boolean> {\n try {\n const success = await this.send(body);\n\n if (success) {\n this.clearPersistedEvents();\n callbacks?.onSuccess?.(body.events.length, body.events, body);\n } else {\n this.persistEvents(body);\n callbacks?.onFailure?.();\n }\n\n return success;\n } catch (error) {\n if (error instanceof PermanentError) {\n this.logPermanentError('Permanent error, not retrying', error);\n this.clearPersistedEvents();\n callbacks?.onFailure?.();\n return false;\n }\n\n this.persistEvents(body);\n callbacks?.onFailure?.();\n return false;\n }\n }\n\n /**\n * Recovers and attempts to resend events persisted from previous session.\n *\n * **Purpose**: Zero data loss guarantee - recovers events that failed to send\n * in previous session due to network errors or crashes.\n *\n * **Flow**:\n * 1. Checks if recovery already in progress (prevents duplicate attempts)\n * 2. Loads persisted events from localStorage\n * 3. Validates freshness (discards events older than 2 hours)\n * 4. Applies multi-tab protection (skips events persisted within 1 second)\n * 5. Attempts to resend via `send()` method\n * 6. On success: Clears persisted events, invokes `onSuccess` callback\n * 7. On failure: Keeps events in localStorage, invokes `onFailure` callback\n * 8. On permanent error (4xx): Clears persisted events (no further retry)\n *\n * **Multi-Tab Protection**:\n * - Events persisted within last 1 second are skipped (active tab may retry)\n * - Prevents duplicate sends when multiple tabs recover simultaneously\n *\n * **Event Expiry**:\n * - Events older than 2 hours are discarded (prevents stale data accumulation)\n * - Expiry check uses event timestamps, not persistence time\n *\n * **Callbacks**:\n * - `onSuccess(eventCount, events, body)`: Called after successful transmission\n * - `onFailure()`: Called on send failure or permanent error\n *\n * **Called by**: `EventManager.recoverPersistedEvents()` during `App.init()`\n *\n * **Important**: This method is idempotent and safe to call multiple times.\n * Recovery flag prevents concurrent attempts.\n *\n * @param callbacks - Optional success/failure callbacks\n *\n * @example\n * ```typescript\n * await senderManager.recoverPersistedEvents({\n * onSuccess: (count, events, body) => {\n * console.log(`Recovered ${count} events`);\n * },\n * onFailure: () => {\n * console.warn('Recovery failed, will retry on next init');\n * }\n * });\n * ```\n *\n * @see src/managers/README.md (lines 82-139) for recovery details\n */\n async recoverPersistedEvents(callbacks?: SendCallbacks): Promise<void> {\n if (this.recoveryInProgress) {\n log('debug', 'Recovery already in progress, skipping duplicate attempt');\n return;\n }\n\n this.recoveryInProgress = true;\n\n try {\n const persistedData = this.getPersistedData();\n\n if (!persistedData || !this.isDataRecent(persistedData) || persistedData.events.length === 0) {\n this.clearPersistedEvents();\n return;\n }\n\n const body = this.createRecoveryBody(persistedData);\n const success = await this.send(body);\n\n if (success) {\n this.clearPersistedEvents();\n callbacks?.onSuccess?.(persistedData.events.length, persistedData.events, body);\n } else {\n callbacks?.onFailure?.();\n }\n } catch (error) {\n if (error instanceof PermanentError) {\n this.logPermanentError('Permanent error during recovery, clearing persisted events', error);\n this.clearPersistedEvents();\n callbacks?.onFailure?.();\n return;\n }\n\n log('error', 'Failed to recover persisted events', { error });\n } finally {\n this.recoveryInProgress = false;\n }\n }\n\n /**\n * Cleanup method called during `App.destroy()`.\n *\n * **Purpose**: Reserved for future cleanup logic (currently no-op).\n *\n * **Note**: This method is intentionally empty. SenderManager has no\n * cleanup requirements (no timers, no event listeners, no active connections).\n * Persisted events are intentionally kept in localStorage for recovery.\n *\n * **Called by**: `EventManager.stop()` during application teardown\n */\n stop(): void {}\n\n /**\n * Applies beforeSend transformer to event array for custom backend integrations.\n *\n * **Purpose**: Per-event transformation in multi-integration mode for custom backends only.\n * Bypassed for TraceLog SaaS to maintain schema integrity.\n *\n * **Application Context**:\n * - Only applied in multi-integration mode (SaaS + Custom)\n * - EventManager applies beforeSend for standalone/custom-only modes\n * - This method handles the multi-integration scenario\n *\n * **Transformation Flow**:\n * 1. Skip for TraceLog SaaS integration (returns untransformed body)\n * 2. Check if beforeSend transformer exists\n * 3. Apply transformer to each event via transformEvents() utility\n * 4. Filter out events (empty array = filter entire batch)\n * 5. Return transformed queue or null\n *\n * **Error Handling**:\n * - transformEvents() utility catches and logs transformer errors\n * - Failed transformations fall back to original event\n * - Empty result array treated as filter signal (returns null)\n *\n * @param body - Event queue to transform\n * @returns Transformed queue with modified events, or null to filter entire batch\n */\n private applyBeforeSendTransformer(body: EventsQueue): EventsQueue | null {\n // TraceLog SaaS requires untransformed events to maintain schema integrity\n if (this.integrationId === 'saas') {\n return body;\n }\n\n const beforeSendTransformer = this.transformers.beforeSend;\n\n if (!beforeSendTransformer) {\n return body;\n }\n\n const transformedEvents = transformEvents(\n body.events,\n beforeSendTransformer,\n this.integrationId || 'SenderManager',\n );\n\n if (transformedEvents.length === 0) {\n return null;\n }\n\n return {\n ...body,\n events: transformedEvents,\n };\n }\n\n /**\n * Applies beforeBatch transformer to entire event queue for custom backend integrations.\n *\n * **Purpose**: Batch-level transformation before network transmission for custom backends only.\n * Bypassed for TraceLog SaaS to maintain schema integrity.\n *\n * **Application Context**:\n * - Applied in both sync (`sendQueueSyncInternal`) and async (`send`) methods\n * - Operates on entire queue after beforeSend transformations\n * - Final transformation step before network transmission\n *\n * **Transformation Flow**:\n * 1. Skip for TraceLog SaaS integration (returns untransformed body)\n * 2. Check if beforeBatch transformer exists\n * 3. Apply transformer to entire queue via transformBatch() utility\n * 4. Return transformed queue or null to filter\n *\n * **Use Cases**:\n * - Add batch-level metadata (timestamps, signatures)\n * - Compress or encrypt entire payload\n * - Apply custom formatting to queue structure\n * - Filter entire batch based on conditions\n *\n * **Error Handling**:\n * - transformBatch() utility catches and logs transformer errors\n * - Failed transformations fall back to original batch\n * - Returning null filters entire batch (prevents send)\n *\n * @param body - Event queue to transform\n * @returns Transformed queue, or null to filter entire batch\n */\n private applyBeforeBatchTransformer(body: EventsQueue): EventsQueue | null {\n // TraceLog SaaS requires untransformed batches to maintain schema integrity\n if (this.integrationId === 'saas') {\n return body;\n }\n\n const beforeBatchTransformer = this.transformers.beforeBatch;\n\n if (!beforeBatchTransformer) {\n return body;\n }\n\n const transformed = transformBatch(body, beforeBatchTransformer, this.integrationId || 'SenderManager');\n\n return transformed;\n }\n\n /**\n * Calculates exponential backoff delay with jitter for retry attempts.\n *\n * **Purpose**: Prevents thundering herd problem when multiple clients retry simultaneously.\n *\n * **Formula**: `RETRY_BACKOFF_BASE_MS * (2 ^ attempt) + random(0, RETRY_BACKOFF_JITTER_MS)`\n *\n * **Examples**:\n * - Attempt 1: 100ms * 2^1 + jitter = 200ms + 0-100ms = 200-300ms\n * - Attempt 2: 100ms * 2^2 + jitter = 400ms + 0-100ms = 400-500ms\n *\n * **Why Jitter?**\n * - Distributes retry timing across clients\n * - Reduces server load spikes from synchronized retries\n * - Industry standard pattern (AWS, Google, Netflix use similar approaches)\n *\n * @param attempt - Current retry attempt number (1-based)\n * @returns Promise that resolves after calculated delay\n */\n private async backoffDelay(attempt: number): Promise<void> {\n const exponentialDelay = RETRY_BACKOFF_BASE_MS * Math.pow(2, attempt);\n const jitter = Math.random() * RETRY_BACKOFF_JITTER_MS;\n const totalDelay = exponentialDelay + jitter;\n\n return new Promise((resolve) => setTimeout(resolve, totalDelay));\n }\n\n /**\n * Sends event queue with automatic retry logic for transient failures.\n *\n * **Purpose**: Reliable event transmission with intelligent retry mechanism\n * for transient network/server errors while avoiding unnecessary retries for\n * permanent client errors.\n *\n * **Retry Strategy**:\n * - **Maximum Attempts**: Up to `MAX_SEND_RETRIES` (2) retry attempts\n * - **Backoff**: Exponential backoff with jitter (200-300ms, 400-500ms)\n * - **Transient Errors**: 5xx status codes, network failures, timeouts\n * - **Permanent Errors**: 4xx status codes (except 408, 429) - no retries\n *\n * **Retry Flow**:\n * 1. Attempt send with `sendWithTimeout()`\n * 2. If success (2xx) → return true immediately\n * 3. If permanent error (4xx) → throw PermanentError immediately\n * 4. If transient error (5xx/timeout/network):\n * - If attempts remaining → wait backoff delay → retry\n * - If no attempts remaining → return false (caller persists)\n *\n * **Important Behaviors**:\n * - Transformers applied once before retry loop (not re-applied per attempt)\n * - Each retry uses same transformed payload\n * - Permanent errors bypass retries immediately\n *\n * **Error Classification**:\n * - **Permanent** (4xx except 408, 429): Schema errors, auth failures, invalid data\n * - **Transient** (5xx, timeout, network): Server overload, network hiccups, DNS issues\n *\n * @param body - Event queue to send\n * @returns Promise resolving to true if send succeeded, false if all retries exhausted\n * @throws PermanentError for 4xx errors (caller should not retry)\n */\n private async send(body: EventsQueue): Promise<boolean> {\n if (this.shouldSkipSend()) {\n return this.simulateSuccessfulSend();\n }\n\n const afterBeforeSend = this.applyBeforeSendTransformer(body);\n\n if (!afterBeforeSend) {\n return true;\n }\n\n const transformedBody = this.applyBeforeBatchTransformer(afterBeforeSend);\n\n if (!transformedBody) {\n return true;\n }\n\n if (this.apiUrl?.includes(SpecialApiUrl.Fail)) {\n log('debug', `Fail mode: simulating network failure${this.integrationId ? ` [${this.integrationId}]` : ''}`, {\n data: { events: transformedBody.events.length },\n });\n\n return false;\n }\n\n if (this.apiUrl?.includes(SpecialApiUrl.Localhost)) {\n log('debug', `Success mode: simulating successful send${this.integrationId ? ` [${this.integrationId}]` : ''}`, {\n data: { events: transformedBody.events.length },\n });\n\n return true;\n }\n\n const { url, payload } = this.prepareRequest(transformedBody);\n\n // Retry loop: initial attempt + MAX_SEND_RETRIES additional attempts\n for (let attempt = 1; attempt <= MAX_SEND_RETRIES + 1; attempt++) {\n try {\n const response = await this.sendWithTimeout(url, payload);\n\n if (response.ok) {\n if (attempt > 1) {\n log(\n 'info',\n `Send succeeded after ${attempt - 1} retry attempt(s)${this.integrationId ? ` [${this.integrationId}]` : ''}`,\n {\n data: { events: transformedBody.events.length, attempt },\n },\n );\n }\n\n return true;\n }\n\n // Response received but not OK - should be handled by sendWithTimeout throwing error\n // This branch should not be reached, but included for defensive programming\n return false;\n } catch (error) {\n const isLastAttempt = attempt === MAX_SEND_RETRIES + 1;\n\n // Permanent errors bypass retries immediately\n if (error instanceof PermanentError) {\n throw error;\n }\n\n // Log error with retry context\n log(\n isLastAttempt ? 'error' : 'warn',\n `Send attempt ${attempt} failed${this.integrationId ? ` [${this.integrationId}]` : ''}${isLastAttempt ? ' (all retries exhausted)' : ', will retry'}`,\n {\n error,\n data: {\n events: body.events.length,\n url: url.replace(/\\/\\/[^/]+/, '//[DOMAIN]'),\n attempt,\n maxAttempts: MAX_SEND_RETRIES + 1,\n },\n },\n );\n\n // If not last attempt, wait backoff delay and retry\n if (!isLastAttempt) {\n await this.backoffDelay(attempt);\n continue;\n }\n\n // All retries exhausted\n return false;\n }\n }\n\n // Should never reach here due to loop structure, but included for type safety\n return false;\n }\n\n /**\n * Sends HTTP POST request with 10-second timeout and AbortController.\n *\n * **Purpose**: Wraps fetch() with timeout protection to prevent hanging requests.\n * Throws PermanentError for 4xx status codes (except 408, 429) to bypass retries.\n *\n * **Timeout Behavior**:\n * - 10-second timeout via AbortController (REQUEST_TIMEOUT_MS constant)\n * - Aborted requests throw network error (triggers retry in caller)\n *\n * **Error Classification**:\n * - 4xx (except 408, 429): PermanentError thrown → no retries\n * - 408, 429, 5xx, network: Standard Error thrown → triggers retry\n *\n * @param url - API endpoint URL\n * @param payload - JSON-stringified EventsQueue body\n * @returns Response object if successful\n * @throws PermanentError for unrecoverable 4xx errors\n * @throws Error for transient errors (5xx, timeout, network)\n * @private\n */\n private async sendWithTimeout(url: string, payload: string): Promise<Response> {\n const controller = new AbortController();\n this.pendingControllers.add(controller);\n\n const timeoutId = setTimeout(() => {\n controller.abort();\n }, REQUEST_TIMEOUT_MS);\n\n try {\n // Get custom headers (only applies to 'custom' integration)\n const customHeaders = this.getCustomHeaders();\n\n const response = await fetch(url, {\n method: 'POST',\n body: payload,\n keepalive: true,\n credentials: 'include',\n signal: controller.signal,\n headers: {\n ...customHeaders,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n // 4xx errors are permanent (unrecoverable) except:\n // - 408 Request Timeout (transient - network/server issue)\n // - 429 Too Many Requests (transient - rate limiting)\n const isPermanentError =\n response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429;\n\n if (isPermanentError) {\n throw new PermanentError(`HTTP ${response.status}: ${response.statusText}`, response.status);\n }\n\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n return response;\n } finally {\n clearTimeout(timeoutId);\n this.pendingControllers.delete(controller);\n }\n }\n\n /**\n * Internal synchronous send logic using navigator.sendBeacon() for page unload scenarios.\n *\n * **Purpose**: Sends events synchronously during page unload when async fetch() is unreliable.\n * Uses sendBeacon() browser API which queues request even after page closes.\n *\n * **Flow**:\n * 1. Apply beforeSend transformer (per-event transformation)\n * 2. Apply beforeBatch transformer (batch-level transformation)\n * 3. Validate payload size (64KB browser limit for sendBeacon)\n * 4. Send via sendBeacon() or fallback to persistence if unavailable\n * 5. Persist events on failure for next-page-load recovery\n *\n * **Payload Size Limit**: 64KB enforced by browser for sendBeacon()\n * - Oversized payloads persisted instead of silently failing\n *\n * @param body - EventsQueue to send\n * @returns `true` on success or when events persisted for recovery, `false` on failure\n * @private\n */\n private sendQueueSyncInternal(body: EventsQueue): boolean {\n const afterBeforeSend = this.applyBeforeSendTransformer(body);\n\n if (!afterBeforeSend) {\n return true;\n }\n\n const transformedBody = this.applyBeforeBatchTransformer(afterBeforeSend);\n\n if (!transformedBody) {\n return true;\n }\n\n const { url, payload } = this.prepareRequest(transformedBody);\n\n if (payload.length > MAX_BEACON_PAYLOAD_SIZE) {\n log(\n 'warn',\n `Payload exceeds sendBeacon limit, persisting for recovery${this.integrationId ? ` [${this.integrationId}]` : ''}`,\n {\n data: {\n size: payload.length,\n limit: MAX_BEACON_PAYLOAD_SIZE,\n events: transformedBody.events.length,\n },\n },\n );\n\n this.persistEvents(transformedBody);\n\n return false;\n }\n\n const blob = new Blob([payload], { type: 'application/json' });\n\n if (!this.isSendBeaconAvailable()) {\n log(\n 'warn',\n `sendBeacon not available, persisting events for recovery${this.integrationId ? ` [${this.integrationId}]` : ''}`,\n );\n\n this.persistEvents(transformedBody);\n return false;\n }\n\n const accepted = navigator.sendBeacon(url, blob);\n\n if (!accepted) {\n log(\n 'warn',\n `sendBeacon rejected request, persisting events for recovery${this.integrationId ? ` [${this.integrationId}]` : ''}`,\n );\n\n this.persistEvents(transformedBody);\n }\n\n return accepted;\n }\n\n /**\n * Prepares request by enriching payload with metadata and serializing to JSON.\n *\n * **Purpose**: Adds request metadata (referer, timestamp) before transmission.\n *\n * **Metadata Enrichment**:\n * - `referer`: Current page URL (browser only, undefined in Node.js)\n * - `timestamp`: Request generation time in milliseconds\n *\n * **Idempotency Token**:\n * - Generated in this method using generateEventId()\n * - Same token persists across all retry attempts of the same batch (same payload string)\n * - Backend can use this to distinguish retries from genuine duplicates\n *\n * @param body - EventsQueue to send\n * @returns Object with `url` (API endpoint) and `payload` (JSON string)\n * @private\n */\n private prepareRequest(body: EventsQueue): { url: string; payload: string } {\n let timestamp = Date.now();\n\n // Protect against clock skew (same pattern as generateEventId)\n // Ensures _metadata.timestamp is monotonically increasing\n if (timestamp < this.lastMetadataTimestamp) {\n timestamp = this.lastMetadataTimestamp;\n }\n this.lastMetadataTimestamp = timestamp;\n\n const enrichedBody = {\n ...body,\n _metadata: {\n referer: typeof window !== 'undefined' ? window.location.href : undefined,\n timestamp,\n client_version: LIB_VERSION,\n },\n };\n\n return {\n url: this.apiUrl || '',\n payload: JSON.stringify(enrichedBody),\n };\n }\n\n /**\n * Retrieves persisted events from localStorage with error recovery.\n *\n * **Purpose**: Loads previously failed events from storage for recovery attempt.\n *\n * **Error Handling**:\n * - JSON parse failures logged and storage cleared (corrupted data)\n * - Missing data returns null (no recovery needed)\n *\n * @returns Persisted events object or null if none exist/invalid\n * @private\n */\n private getPersistedData(): PersistedEventsQueue | null {\n try {\n const storageKey = this.getQueueStorageKey();\n const persistedDataString = this.storeManager.getItem(storageKey);\n\n if (persistedDataString) {\n return JSON.parse(persistedDataString);\n }\n } catch (error) {\n log('debug', `Failed to parse persisted data${this.integrationId ? ` [${this.integrationId}]` : ''}`, { error });\n this.clearPersistedEvents();\n }\n\n return null;\n }\n\n /**\n * Checks if persisted events are within the 2-hour expiry window.\n *\n * **Purpose**: Prevents recovery of stale events that are too old to be relevant.\n *\n * **Expiry Logic**:\n * - Events older than 2 hours (EVENT_EXPIRY_HOURS) are considered expired\n * - Invalid/missing timestamps treated as expired\n *\n * @param data - Persisted events object with timestamp\n * @returns `true` if events are recent (< 2 hours old), `false` otherwise\n * @private\n */\n private isDataRecent(data: PersistedEventsQueue): boolean {\n if (!data.timestamp || typeof data.timestamp !== 'number') {\n return false;\n }\n\n const ageInHours = (Date.now() - data.timestamp) / (1000 * 60 * 60);\n return ageInHours < EVENT_EXPIRY_HOURS;\n }\n\n /**\n * Creates EventsQueue from persisted data by removing storage-specific timestamp field.\n *\n * **Purpose**: Converts PersistedEventsQueue (with timestamp) to EventsQueue for sending.\n *\n * @param data - Persisted events with timestamp\n * @returns EventsQueue ready for transmission (timestamp removed)\n * @private\n */\n private createRecoveryBody(data: PersistedEventsQueue): EventsQueue {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { timestamp, ...queue } = data;\n return queue;\n }\n\n /**\n * Persists failed events to localStorage for next-page-load recovery.\n *\n * **Purpose**: Saves events that couldn't be sent due to network/server errors.\n * Implements multi-tab protection to prevent data loss during simultaneous failures.\n *\n * **Multi-Tab Protection**:\n * - Throttles persistence (1-second window via PERSISTENCE_THROTTLE_MS)\n * - If another tab persisted within 1 second, skips write (last-write-wins)\n * - Prevents redundant storage writes when multiple tabs fail together\n *\n * **Storage Format**: PersistedEventsQueue (EventsQueue + timestamp)\n *\n * @param body - EventsQueue to persist\n * @returns `true` on successful persistence or throttled write, `false` on error\n * @private\n */\n private persistEvents(body: EventsQueue): boolean {\n try {\n const existing = this.getPersistedData();\n\n if (existing && existing.timestamp) {\n const timeSinceExisting = Date.now() - existing.timestamp;\n\n if (timeSinceExisting < PERSISTENCE_THROTTLE_MS) {\n log(\n 'debug',\n `Skipping persistence, another tab recently persisted events${this.integrationId ? ` [${this.integrationId}]` : ''}`,\n {\n data: { timeSinceExisting },\n },\n );\n\n return true;\n }\n }\n\n const persistedData: PersistedEventsQueue = {\n ...body,\n timestamp: Date.now(),\n };\n\n const storageKey = this.getQueueStorageKey();\n\n this.storeManager.setItem(storageKey, JSON.stringify(persistedData));\n\n return !!this.storeManager.getItem(storageKey);\n } catch (error) {\n log('debug', `Failed to persist events${this.integrationId ? ` [${this.integrationId}]` : ''}`, { error });\n return false;\n }\n }\n\n private clearPersistedEvents(): void {\n try {\n const key = this.getQueueStorageKey();\n this.storeManager.removeItem(key);\n } catch (error) {\n log('debug', `Failed to clear persisted events${this.integrationId ? ` [${this.integrationId}]` : ''}`, {\n error,\n });\n }\n }\n\n private shouldSkipSend(): boolean {\n return !this.apiUrl;\n }\n\n private async simulateSuccessfulSend(): Promise<boolean> {\n const delay = Math.random() * 400 + 100;\n\n await new Promise((resolve) => setTimeout(resolve, delay));\n\n return true;\n }\n\n private isSendBeaconAvailable(): boolean {\n return typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function';\n }\n\n private logPermanentError(context: string, error: PermanentError): void {\n const now = Date.now();\n const shouldLog =\n !this.lastPermanentErrorLog ||\n this.lastPermanentErrorLog.statusCode !== error.statusCode ||\n now - this.lastPermanentErrorLog.timestamp >= PERMANENT_ERROR_LOG_THROTTLE_MS;\n\n if (shouldLog) {\n log('error', `${context}${this.integrationId ? ` [${this.integrationId}]` : ''}`, {\n data: { status: error.statusCode, message: error.message },\n });\n\n this.lastPermanentErrorLog = { statusCode: error.statusCode, timestamp: now };\n }\n }\n}\n","import { log } from '../utils';\nimport { StateManager } from './state.manager';\n\n/**\n * Manages accurate timestamp generation using monotonic clock (performance.now())\n * to prevent issues from system clock changes during the session.\n *\n * **Purpose**: Provides reliable timestamps immune to system clock adjustments\n * that could cause future timestamp rejections or incorrect event ordering.\n *\n * **Core Functionality**:\n * - **Boot Time Reference**: Captures `performance.now()` and `Date.now()` at initialization\n * - **Monotonic Clock**: Uses `performance.now()` for elapsed time (immune to clock changes)\n * - **Timestamp Generation**: `bootTimestamp + (performance.now() - bootTime)`\n * - **Graceful Degradation**: Falls back to `Date.now()` if `performance.now()` unavailable\n *\n * **Key Features**:\n * - **Clock Skew Protection**: Timestamps stay accurate even if system clock changes during session\n * - **No Server Dependency**: Works in standalone mode without backend communication\n * - **High Precision**: Uses performance.now() with microsecond precision\n * - **Detect Clock Skew**: Warns when significant clock drift detected (>30 seconds)\n *\n * **Use Cases**:\n * - Generate event timestamps that won't be rejected for being in the future\n * - Maintain correct event ordering even when system clock jumps\n * - Detect and warn about clock synchronization issues\n *\n * **Limitations**:\n * - Only protects against clock changes DURING the session\n * - If clock is wrong at boot time, timestamps will reflect that initial offset\n * - Server-side validation should still allow some tolerance (e.g., 3 minutes)\n *\n * @example\n * ```typescript\n * const timeManager = new TimeManager();\n *\n * // Get accurate timestamp (immune to clock changes)\n * const timestamp = timeManager.now(); // milliseconds since epoch\n *\n * // Check if clock skew detected\n * const skew = timeManager.getClockSkew(); // milliseconds of drift\n * if (Math.abs(skew) > 30000) {\n * console.warn('System clock drifted by', skew, 'ms');\n * }\n * ```\n */\nexport class TimeManager extends StateManager {\n private readonly bootTime: number;\n private readonly bootTimestamp: number;\n private readonly hasPerformanceNow: boolean;\n private lastClockSkewCheck = 0;\n private detectedSkew = 0;\n\n /**\n * Creates a TimeManager instance and establishes boot time reference.\n *\n * **Initialization**:\n * 1. Captures `performance.now()` as boot time (monotonic clock)\n * 2. Captures `Date.now()` as boot timestamp (wall clock)\n * 3. Detects if `performance.now()` is available (for fallback)\n *\n * **Boot Time**: Reference point for all subsequent timestamp calculations\n * - All timestamps are relative to this boot time\n * - Immune to system clock changes after initialization\n *\n * **SSR Safety**: In non-browser environments (Node.js, SSR), falls back to Date.now()\n */\n constructor() {\n super();\n\n // SSR safety: TimeManager is no-op in non-browser environments\n if (typeof window === 'undefined') {\n this.hasPerformanceNow = false;\n this.bootTime = 0;\n this.bootTimestamp = 0;\n return;\n }\n\n this.hasPerformanceNow = typeof performance !== 'undefined' && typeof performance.now === 'function';\n\n if (this.hasPerformanceNow) {\n this.bootTime = performance.now();\n this.bootTimestamp = Date.now();\n\n log('debug', 'TimeManager initialized with monotonic clock', {\n data: {\n bootTime: this.bootTime.toFixed(3),\n bootTimestamp: this.bootTimestamp,\n },\n });\n } else {\n // Fallback for environments without performance.now()\n this.bootTime = 0;\n this.bootTimestamp = Date.now();\n\n log('debug', 'performance.now() not available, falling back to Date.now()');\n }\n }\n\n /**\n * Returns current timestamp in milliseconds since epoch.\n *\n * **Calculation**:\n * - If `performance.now()` available: `bootTimestamp + (performance.now() - bootTime)`\n * - Otherwise: `Date.now()` (fallback)\n *\n * **Advantages over Date.now()**:\n * - Immune to system clock changes during session\n * - More accurate (microsecond precision)\n * - Prevents future timestamp errors from clock adjustments\n *\n * @returns Timestamp in milliseconds since Unix epoch\n *\n * @example\n * ```typescript\n * const eventTimestamp = timeManager.now();\n * // Always accurate relative to boot time, even if system clock changes\n * ```\n */\n now(): number {\n if (!this.hasPerformanceNow) {\n return Date.now();\n }\n\n const elapsed = performance.now() - this.bootTime;\n return Math.round(this.bootTimestamp + elapsed);\n }\n\n /**\n * Detects clock skew by comparing monotonic time vs system time.\n *\n * **Purpose**: Identifies when the system clock has changed during the session.\n *\n * **Detection Method**:\n * 1. Calculate expected timestamp using monotonic clock: `now()`\n * 2. Compare with actual system time: `Date.now()`\n * 3. Difference is the clock skew\n *\n * **Clock Skew Scenarios**:\n * - Positive skew: System clock jumped forward (e.g., NTP correction)\n * - Negative skew: System clock jumped backward (rare, usually manual adjustment)\n * - Near zero: No significant clock drift\n *\n * **Throttling**: Only checks every 5 seconds to avoid performance impact\n *\n * @returns Clock skew in milliseconds (positive = clock ahead, negative = clock behind)\n *\n * @example\n * ```typescript\n * const skew = timeManager.getClockSkew();\n * if (Math.abs(skew) > 30000) {\n * console.warn(`System clock drifted by ${skew}ms`);\n * }\n * ```\n */\n getClockSkew(): number {\n if (!this.hasPerformanceNow) {\n return 0;\n }\n\n const now = Date.now();\n\n // Throttle skew checks to every 5 seconds\n if (now - this.lastClockSkewCheck < 5000) {\n return this.detectedSkew;\n }\n\n this.lastClockSkewCheck = now;\n\n const monotonicTimestamp = this.now();\n const systemTimestamp = Date.now();\n this.detectedSkew = systemTimestamp - monotonicTimestamp;\n\n if (Math.abs(this.detectedSkew) > 30000) {\n log('warn', 'Significant clock skew detected', {\n data: {\n skewMs: this.detectedSkew,\n skewMinutes: (this.detectedSkew / 1000 / 60).toFixed(2),\n monotonicTime: new Date(monotonicTimestamp).toISOString(),\n systemTime: new Date(systemTimestamp).toISOString(),\n },\n });\n }\n\n return this.detectedSkew;\n }\n\n /**\n * Validates if a timestamp is reasonable (not too far in the future).\n *\n * **Purpose**: Client-side validation to catch obviously wrong timestamps\n * before sending to backend.\n *\n * **Validation Rules**:\n * - Timestamp must not be >2 minutes in the future (relative to monotonic clock)\n * - Prevents backend rejections due to clock skew\n * - More lenient than backend (allows up to 2 min vs backend's 3 min)\n *\n * **Use Case**: Validate event timestamps before adding to queue\n *\n * @param timestamp - Timestamp to validate (milliseconds since epoch)\n * @returns Object with `valid` boolean and optional `error` message\n *\n * @example\n * ```typescript\n * const validation = timeManager.validateTimestamp(eventTimestamp);\n * if (!validation.valid) {\n * console.error('Invalid timestamp:', validation.error);\n * }\n * ```\n */\n validateTimestamp(timestamp: number): { valid: boolean; error?: string } {\n const maxFutureOffset = 2 * 60 * 1000; // 2 minutes\n const currentTime = this.now();\n const offset = timestamp - currentTime;\n\n if (offset > maxFutureOffset) {\n return {\n valid: false,\n error: `Timestamp is ${(offset / 1000 / 60).toFixed(2)} minutes in the future (max allowed: 2 minutes)`,\n };\n }\n\n return { valid: true };\n }\n\n /**\n * Returns boot time information for debugging.\n *\n * **Purpose**: Diagnostic utility for troubleshooting timestamp issues.\n *\n * @returns Object with boot time details\n */\n getBootInfo(): {\n bootTime: number;\n bootTimestamp: number;\n hasPerformanceNow: boolean;\n clockSkew: number;\n } {\n return {\n bootTime: this.bootTime,\n bootTimestamp: this.bootTimestamp,\n hasPerformanceNow: this.hasPerformanceNow,\n clockSkew: this.getClockSkew(),\n };\n }\n}\n","import {\n EVENT_SENT_INTERVAL_MS,\n MAX_EVENTS_QUEUE_LENGTH,\n DUPLICATE_EVENT_THRESHOLD_MS,\n RATE_LIMIT_WINDOW_MS,\n MAX_EVENTS_PER_SECOND,\n MAX_PENDING_EVENTS_BUFFER,\n BATCH_SIZE_THRESHOLD,\n MAX_SAME_EVENT_PER_MINUTE,\n PER_EVENT_RATE_LIMIT_WINDOW_MS,\n MAX_EVENTS_PER_SESSION,\n MAX_CLICKS_PER_SESSION,\n MAX_PAGE_VIEWS_PER_SESSION,\n MAX_CUSTOM_EVENTS_PER_SESSION,\n MAX_VIEWPORT_EVENTS_PER_SESSION,\n MAX_SCROLL_EVENTS_PER_SESSION,\n MAX_FINGERPRINTS,\n FINGERPRINT_CLEANUP_MULTIPLIER,\n MAX_FINGERPRINTS_HARD_LIMIT,\n} from '../constants/config.constants';\nimport {\n SESSION_COUNTS_KEY,\n SESSION_COUNTS_EXPIRY_MS,\n SESSION_COUNTS_LAST_CLEANUP_KEY,\n SESSION_COUNTS_CLEANUP_THROTTLE_MS,\n STORAGE_BASE_KEY,\n} from '../constants/storage.constants';\nimport {\n EventsQueue,\n EmitterEvent,\n EventData,\n EventType,\n Mode,\n TransformerMap,\n SessionEventCounts,\n CustomHeadersProvider,\n} from '../types';\nimport { log, Emitter, generateEventId, transformEvent, transformBatch } from '../utils';\nimport { SenderManager } from './sender.manager';\nimport { StateManager } from './state.manager';\nimport { StorageManager } from './storage.manager';\nimport { TimeManager } from './time.manager';\n\n/**\n * Extended session counts structure with metadata for expiry tracking.\n *\n * Adds timestamp and version fields to SessionEventCounts for cleanup management.\n */\ninterface StoredSessionCounts extends SessionEventCounts {\n /** Timestamp when counts were last saved (for 7-day expiry) */\n _timestamp: number;\n /** Schema version for future migrations */\n _version: number;\n}\n\n/**\n * Core component responsible for event tracking, queue management, deduplication,\n * rate limiting, and multi-integration API communication coordination.\n *\n * **Purpose**: Central hub for all analytics events in TraceLog, managing the complete\n * event lifecycle from capture to transmission.\n *\n * **Core Functionality**:\n * - **Event Tracking**: Captures all user interactions (clicks, scrolls, page views, custom events, web vitals, errors)\n * - **Queue Management**: Batches events with 10-second intervals to optimize network requests\n * - **Deduplication**: LRU cache with 1000-entry fingerprint storage prevents duplicate events\n * - **Rate Limiting**: Client-side limits (50 events/second global, 60/minute per event name)\n * - **Per-Session Caps**: Configurable limits prevent runaway generation (1000 total, type-specific limits)\n * - **Dynamic Queue Flush**: Immediate send when 50-event batch threshold reached\n * - **Pending Events Buffer**: Buffers up to 100 events before session initialization\n * - **Event Recovery**: Recovers persisted events from localStorage after crashes (independent per integration)\n * - **Multi-Integration**: Manages 0-2 SenderManager instances (SaaS + Custom) with parallel async sending\n * - **Standalone Mode**: Emits queue events without network requests when no integrations configured\n *\n * **Key Features**:\n * - **LRU Deduplication**: 1000 fingerprints, 10px coordinate precision for clicks, 500ms time threshold\n * - **Smart Queue Overflow**: Fixed 100-event limit with priority preservation for SESSION_START/END\n * - **Rate Limiting**: 50 events/sec sliding window, critical events exempted\n * - **Per-Event-Name Limits**: 60 same event name per minute (configurable via `maxSameEventPerMinute`)\n * - **Per-Session Caps**: Total 1000/session, Clicks 500, Page views 100, Custom 500, Viewport 200, Scroll 120\n * - **Dynamic Flush**: Immediate send when 50-event batch threshold reached\n * - **Multi-Integration Sending**: Parallel async with `Promise.allSettled()`, independent error handling per integration\n * - **QA Mode**: Console logging for custom events without backend transmission\n *\n * **State Management**:\n * - **`hasStartSession` Flag**: Prevents duplicate SESSION_START events across init cycles\n * - Set to `true` when SESSION_START is tracked via `track()` method\n * - Reset to `false` in `stop()` method to allow subsequent init() → destroy() → init() cycles\n * - NOT set by SessionManager's BroadcastChannel message handler (secondary tabs don't track SESSION_START)\n *\n * **Transformer Support**:\n * - **`beforeSend`**: Applied conditionally based on integration mode\n * - Custom-only mode: Applied in EventManager before dedup/sampling/queueing\n * - Multi-integration mode: Skipped in EventManager, applied in SenderManager per-integration\n * - **`beforeBatch`**: Applied in SenderManager before network transmission\n *\n * @see src/managers/README.md for detailed documentation\n *\n * @example\n * ```typescript\n * const eventManager = new EventManager(storage, emitter, transformers);\n *\n * // Track event\n * eventManager.track({\n * type: 'click',\n * click_data: { x: 100, y: 200, tag: 'button' }\n * });\n *\n * // Flush immediately (async)\n * await eventManager.flushImmediately();\n *\n * // Synchronous flush (page unload)\n * eventManager.flushImmediatelySync();\n *\n * // Stop and cleanup\n * eventManager.stop();\n * ```\n */\nexport class EventManager extends StateManager {\n private readonly dataSenders: SenderManager[];\n private readonly emitter: Emitter | null;\n private readonly transformers: TransformerMap;\n private readonly timeManager: TimeManager;\n private readonly recentEventFingerprints = new Map<string, number>();\n private readonly perEventRateLimits: Map<string, number[]> = new Map();\n\n private eventsQueue: EventData[] = [];\n private pendingEventsBuffer: Partial<EventData>[] = [];\n private sendIntervalId: number | null = null;\n private rateLimitCounter = 0;\n private rateLimitWindowStart = 0;\n private lastSessionId: string | null = null;\n\n private sessionEventCounts: SessionEventCounts = {\n total: 0,\n [EventType.CLICK]: 0,\n [EventType.PAGE_VIEW]: 0,\n [EventType.CUSTOM]: 0,\n [EventType.VIEWPORT_VISIBLE]: 0,\n [EventType.SCROLL]: 0,\n };\n\n private readonly saveSessionCountsDebounced: ((sessionId: string) => void) | null = null;\n\n /**\n * Creates an EventManager instance.\n *\n * **Initialization**:\n * - Creates SenderManager instances for configured integrations (SaaS/Custom)\n * - Initializes event emitter for local consumption\n *\n * @param storeManager - Storage manager for persistence\n * @param emitter - Optional event emitter for local event consumption\n * @param transformers - Optional event transformation hooks\n * @param staticHeaders - Optional static HTTP headers for custom backend (from config)\n * @param customHeadersProvider - Optional callback for dynamic headers\n */\n constructor(\n storeManager: StorageManager,\n emitter: Emitter | null = null,\n transformers: TransformerMap = {},\n staticHeaders: Record<string, string> = {},\n customHeadersProvider?: CustomHeadersProvider,\n ) {\n super();\n\n this.emitter = emitter;\n this.transformers = transformers;\n this.timeManager = new TimeManager();\n\n this.dataSenders = [];\n const collectApiUrls = this.get('collectApiUrls');\n\n // SaaS integration: no custom headers (schema protection)\n if (collectApiUrls?.saas) {\n this.dataSenders.push(new SenderManager(storeManager, 'saas', collectApiUrls.saas, transformers));\n }\n\n // Custom integration: receives static headers and dynamic provider\n if (collectApiUrls?.custom) {\n this.dataSenders.push(\n new SenderManager(\n storeManager,\n 'custom',\n collectApiUrls.custom,\n transformers,\n staticHeaders,\n customHeadersProvider,\n ),\n );\n }\n\n // Initialize debounced session counts saver (500ms delay, trailing edge)\n // Reduces localStorage writes from ~1000 per session to ~20-30 (96-97% reduction)\n this.saveSessionCountsDebounced = this.debounce((sessionId: string) => {\n this.saveSessionCounts(sessionId);\n }, 500);\n\n // Cleanup expired session counts on init (7-day TTL)\n this.cleanupExpiredSessionCounts();\n }\n\n /**\n * Recovers persisted events from localStorage after a crash or page reload.\n *\n * **Purpose**: Ensures zero data loss by recovering events that failed to send\n * in the previous session due to network errors or crashes.\n *\n * **Flow**:\n * 1. Calls `recoverPersistedEvents()` on all SenderManager instances in parallel\n * 2. Each SenderManager attempts to resend its persisted events to backend\n * 3. On success: Removes recovered events from consent/pending buffers\n * 4. On failure: Logs warning (events remain in localStorage for next attempt)\n *\n * **Multi-Integration**:\n * - Independent recovery per integration (SaaS + Custom backends)\n * - Parallel recovery via `Promise.allSettled()` (one failure doesn't block others)\n * - No cross-contamination (SaaS events don't go to Custom API)\n *\n * **Called by**: `App.init()` after initialization\n *\n * **Important**: Events are NOT removed from pending/consent buffers until\n * successful network transmission.\n *\n * @see src/managers/README.md (lines 5-75) for recovery details\n */\n async recoverPersistedEvents(): Promise<void> {\n const recoveryPromises = this.dataSenders.map(async (sender) =>\n sender.recoverPersistedEvents({\n onSuccess: (_eventCount, recoveredEvents, body) => {\n if (recoveredEvents && recoveredEvents.length > 0) {\n const eventIds = recoveredEvents.map((e) => e.id);\n this.removeProcessedEvents(eventIds);\n\n if (body) {\n this.emitEventsQueue(body);\n }\n }\n },\n onFailure: () => {\n log('debug', 'Failed to recover persisted events');\n },\n }),\n );\n\n await Promise.allSettled(recoveryPromises);\n }\n\n /**\n * Tracks a user interaction event and adds it to the event queue.\n *\n * **Purpose**: Central tracking method for all analytics events (clicks, page views,\n * custom events, web vitals, errors, scroll, viewport visibility, session start/end).\n *\n * **Validation & Buffering**:\n * - Validates `type` is provided (required)\n * - If session not initialized: Buffers in `pendingEventsBuffer` (max 100 events, FIFO)\n *\n * **Rate Limiting** (non-critical events only):\n * - Global: 50 events/second sliding window (critical events exempted)\n * - Per-event-name: 60/minute for custom events (configurable via `maxSameEventPerMinute`)\n * - Per-session total: 1000 events max\n * - Per-session by type: Clicks 500, Page views 100, Custom 500, Viewport 200, Scroll 120\n *\n * **Deduplication**:\n * - LRU cache with 1000 fingerprints (10px coordinate precision for clicks, 500ms time threshold)\n * - Prevents duplicate events within 500ms window\n * - SESSION_START protected by `hasStartSession` flag\n *\n * **Sampling**:\n * - Applied after validation and rate limiting\n * - Critical events (SESSION_START/END) always included\n * - Configurable via `samplingRate` (0-1)\n *\n * **Transformation**:\n * - `beforeSend` applied (if custom-only mode) before dedup/sampling/queueing\n * - Returning `null` from `beforeSend` filters out the event\n *\n * **Queue Management**:\n * - Events added to `eventsQueue` (max 100 events, FIFO with priority for session events)\n * - Dynamic flush: Immediate send when 50-event batch threshold reached\n * - Periodic flush: Every 10 seconds\n *\n * **Multi-Integration**:\n * - Backend integrations: Handled by SenderManager instances\n *\n * **QA Mode**:\n * - Custom events logged to console with styling\n * - Events NOT sent to backend (emitted locally only)\n *\n * @param eventData - Event data to track\n *\n * @example\n * ```typescript\n * eventManager.track({\n * type: EventType.CLICK,\n * click_data: { x: 0.5, y: 0.3, tag: 'button', text: 'Submit' }\n * });\n *\n * eventManager.track({\n * type: EventType.CUSTOM,\n * custom_event: { name: 'checkout_completed', metadata: { total: 99.99 } }\n * });\n * ```\n *\n * @see src/managers/README.md (lines 5-75) for detailed tracking logic\n */\n track({\n type,\n page_url,\n from_page_url,\n scroll_data,\n click_data,\n custom_event,\n web_vitals,\n error_data,\n viewport_data,\n page_view,\n }: Partial<EventData>): void {\n if (!type) {\n log('error', 'Event type is required - event will be ignored');\n return;\n }\n\n const currentSessionId = this.get('sessionId');\n\n if (!currentSessionId) {\n if (this.pendingEventsBuffer.length >= MAX_PENDING_EVENTS_BUFFER) {\n this.pendingEventsBuffer.shift();\n log('debug', 'Pending events buffer full - dropping oldest event', {\n data: { maxBufferSize: MAX_PENDING_EVENTS_BUFFER },\n });\n }\n\n this.pendingEventsBuffer.push({\n type,\n page_url,\n from_page_url,\n scroll_data,\n click_data,\n custom_event,\n web_vitals,\n error_data,\n viewport_data,\n page_view,\n });\n\n return;\n }\n\n if (this.lastSessionId !== currentSessionId) {\n this.lastSessionId = currentSessionId;\n // Load persisted counts from localStorage instead of resetting to zero\n // This prevents count reset on page reload and allows proper enforcement of per-session limits\n this.sessionEventCounts = this.loadSessionCounts(currentSessionId);\n }\n\n const isCriticalEvent = type === EventType.SESSION_START;\n\n if (isCriticalEvent) {\n log('debug', 'Processing SESSION_START event', {\n data: { sessionId: currentSessionId },\n });\n }\n\n if (!isCriticalEvent && !this.checkRateLimit()) {\n return;\n }\n\n const eventType = type as EventType;\n\n if (!isCriticalEvent) {\n if (this.sessionEventCounts.total >= MAX_EVENTS_PER_SESSION) {\n log('warn', 'Session event limit reached', {\n data: {\n type: eventType,\n total: this.sessionEventCounts.total,\n limit: MAX_EVENTS_PER_SESSION,\n },\n });\n\n return;\n }\n\n const typeLimit = this.getTypeLimitForEvent(eventType);\n\n if (typeLimit) {\n const currentCount = this.sessionEventCounts[eventType];\n\n if (currentCount !== undefined && currentCount >= typeLimit) {\n log('warn', 'Session event type limit reached', {\n data: {\n type: eventType,\n count: currentCount,\n limit: typeLimit,\n },\n });\n\n return;\n }\n }\n }\n\n if (eventType === EventType.CUSTOM && custom_event?.name) {\n const maxSameEventPerMinute = this.get('config')?.maxSameEventPerMinute ?? MAX_SAME_EVENT_PER_MINUTE;\n\n if (!this.checkPerEventRateLimit(custom_event.name, maxSameEventPerMinute)) {\n return;\n }\n }\n const isSessionStart = eventType === EventType.SESSION_START;\n\n const currentPageUrl = (page_url as string) || this.get('pageUrl');\n const payload = this.buildEventPayload({\n type: eventType,\n page_url: currentPageUrl,\n from_page_url,\n scroll_data,\n click_data,\n custom_event,\n web_vitals,\n error_data,\n viewport_data,\n page_view,\n });\n\n // Handle event filtered by beforeSend transformer\n if (!payload) {\n return;\n }\n\n if (!isCriticalEvent && !this.shouldSample()) {\n return;\n }\n\n if (isSessionStart) {\n const currentSessionId = this.get('sessionId');\n\n if (!currentSessionId) {\n log('error', 'Session start event requires sessionId - event will be ignored');\n return;\n }\n\n if (this.get('hasStartSession')) {\n log('debug', 'Duplicate session_start detected', {\n data: { sessionId: currentSessionId },\n });\n\n return;\n }\n\n this.set('hasStartSession', true);\n }\n\n if (this.isDuplicateEvent(payload)) {\n return;\n }\n\n if (this.get('mode') === Mode.QA) {\n if (eventType === EventType.CUSTOM && custom_event) {\n log('info', `Custom Event: ${custom_event.name}`, {\n visibility: 'qa',\n data: {\n name: custom_event.name,\n ...(custom_event.metadata && { metadata: custom_event.metadata }),\n },\n });\n\n this.emitEvent(payload);\n\n return;\n }\n\n if (eventType === EventType.VIEWPORT_VISIBLE && viewport_data) {\n const displayName = viewport_data.name || viewport_data.id || viewport_data.selector;\n\n log('info', `Viewport Visible: ${displayName}`, {\n visibility: 'qa',\n data: {\n selector: viewport_data.selector,\n ...(viewport_data.name && { name: viewport_data.name }),\n ...(viewport_data.id && { id: viewport_data.id }),\n visibilityRatio: viewport_data.visibilityRatio,\n dwellTime: viewport_data.dwellTime,\n },\n });\n\n this.emitEvent(payload);\n\n return;\n }\n }\n\n this.addToQueue(payload);\n\n if (!isCriticalEvent) {\n this.sessionEventCounts.total++;\n\n if (this.sessionEventCounts[eventType] !== undefined) {\n this.sessionEventCounts[eventType]++;\n }\n\n // Persist updated counts to localStorage (debounced for performance)\n // Debouncing reduces localStorage writes from ~1000 to ~20-30 per session (96-97% reduction)\n // Trailing edge ensures no data loss - final counts always saved after 500ms silence\n const currentSessionId = this.get('sessionId');\n if (currentSessionId && this.saveSessionCountsDebounced) {\n this.saveSessionCountsDebounced(currentSessionId);\n }\n }\n }\n\n /**\n * Stops event tracking and clears all queues and buffers.\n *\n * **Purpose**: Cleanup method called during `App.destroy()` to reset EventManager state\n * and allow subsequent init() → destroy() → init() cycles.\n *\n * **Cleanup Actions**:\n * 1. **Clear send interval**: Stops periodic 10-second queue flush timer\n * 2. **Clear all queues and buffers**:\n * - `eventsQueue`: Discarded (not sent)\n * - `pendingEventsBuffer`: Discarded (events before session init)\n * 3. **Reset rate limiting state**: Clears rate limit counters and per-event limits\n * 4. **Reset session counters**: Clears per-session event counts\n * 5. **Reset `hasStartSession` flag**: Allows SESSION_START in next init cycle\n * 6. **Stop SenderManagers**: Calls `stop()` on all SenderManager instances\n *\n * **Important Behavior**:\n * - **No final flush**: Events in queue are NOT sent before stopping\n * - For flush before destroy, call `flushImmediatelySync()` first\n *\n * **Multi-Integration**:\n * - Stops all SenderManager instances (SaaS + Custom)\n *\n * **Called by**: `App.destroy()` during application teardown\n *\n * @example\n * ```typescript\n * // Proper cleanup with final flush\n * eventManager.flushImmediatelySync(); // Send pending events\n * eventManager.stop(); // Stop and clear\n * ```\n *\n * @see src/managers/README.md (lines 5-75) for cleanup details\n */\n stop(): void {\n // Note: customHeadersProvider is NOT cleared here since it's stored in SenderManagers\n // and those are destroyed during this stop() call anyway\n if (this.sendIntervalId) {\n clearInterval(this.sendIntervalId);\n this.sendIntervalId = null;\n }\n\n // Save session counts immediately before cleanup (bypass debounce)\n // This ensures final counts are persisted before destroying the EventManager\n const currentSessionId = this.get('sessionId');\n if (currentSessionId) {\n this.saveSessionCounts(currentSessionId);\n }\n\n this.eventsQueue = [];\n this.pendingEventsBuffer = [];\n this.recentEventFingerprints.clear();\n this.rateLimitCounter = 0;\n this.rateLimitWindowStart = 0;\n this.perEventRateLimits.clear();\n this.sessionEventCounts = {\n total: 0,\n [EventType.CLICK]: 0,\n [EventType.PAGE_VIEW]: 0,\n [EventType.CUSTOM]: 0,\n [EventType.VIEWPORT_VISIBLE]: 0,\n [EventType.SCROLL]: 0,\n };\n this.lastSessionId = null;\n this.set('hasStartSession', false);\n\n this.dataSenders.forEach((sender) => {\n sender.stop();\n });\n }\n\n /**\n * Flushes all events in the queue asynchronously.\n *\n * **Purpose**: Force immediate sending of queued events without waiting for\n * the 10-second periodic flush timer.\n *\n * **Use Cases**:\n * - Manual flush triggered by user action\n * - Before page unload (prefer `flushImmediatelySync()` for unload scenarios)\n * - Testing/debugging\n *\n * **Behavior**:\n * - Sends events via `fetch()` API (async, reliable, allows retries)\n * - Multi-integration: Sends to all configured backends in parallel\n * - Does NOT block (returns Promise that resolves when all sends complete)\n * - Clears queue only after successful transmission\n *\n * **Note**: For page unload, use `flushImmediatelySync()` instead,\n * which uses `sendBeacon()` for guaranteed delivery.\n *\n * @returns Promise resolving to `true` if all sends succeeded, `false` if any failed\n *\n * @example\n * ```typescript\n * // Before critical user action\n * await eventManager.flushImmediately();\n * ```\n *\n * @see flushImmediatelySync for synchronous page unload flush\n * @see src/managers/README.md (lines 5-75) for flush details\n */\n async flushImmediately(): Promise<boolean> {\n return this.flushEvents(false);\n }\n\n /**\n * Flushes all events in the queue synchronously using `sendBeacon()`.\n *\n * **Purpose**: Ensure events are sent before page unload, even if network is slow.\n *\n * **Use Cases**:\n * - Page unload (`beforeunload`, `pagehide` events)\n * - Tab close detection\n * - Any scenario where async flush might be interrupted\n *\n * **Behavior**:\n * - Uses `navigator.sendBeacon()` API (synchronous, queued by browser)\n * - Payload size limited to 64KB per beacon\n * - Browser guarantees delivery attempt (queued even if page closes)\n * - Clears queue immediately (no retry mechanism)\n *\n * **Multi-Integration**:\n * - Sends to all configured backends (SaaS + Custom) in parallel\n * - Independent success tracking per integration\n *\n * **Limitations**:\n * - No retry on failure (sendBeacon is fire-and-forget)\n * - 64KB payload limit (large batches may be truncated)\n *\n * @returns `true` if all sends succeeded, `false` if any failed\n *\n * @example\n * ```typescript\n * // Page unload handler\n * window.addEventListener('beforeunload', () => {\n * eventManager.flushImmediatelySync();\n * });\n * ```\n *\n * @see flushImmediately for async flush with retries\n * @see src/managers/README.md (lines 5-75) for flush details\n */\n flushImmediatelySync(): boolean {\n return this.flushEvents(true) as boolean;\n }\n\n /**\n * Sets the custom headers provider callback for the custom integration.\n * Only affects requests to custom backend (not TraceLog SaaS).\n *\n * @param provider - Callback function that returns custom headers\n */\n setCustomHeadersProvider(provider: CustomHeadersProvider): void {\n for (const sender of this.dataSenders) {\n if (sender.getIntegrationId() === 'custom') {\n sender.setCustomHeadersProvider(provider);\n }\n }\n }\n\n /**\n * Removes the custom headers provider callback from the custom integration.\n */\n removeCustomHeadersProvider(): void {\n for (const sender of this.dataSenders) {\n if (sender.getIntegrationId() === 'custom') {\n sender.removeCustomHeadersProvider();\n }\n }\n }\n\n /**\n * Returns the current number of events in the main queue.\n *\n * **Purpose**: Debugging and monitoring utility to check queue length.\n *\n * **Note**: This does NOT include:\n * - Pending events buffer (events before session init)\n * - Consent events buffer (events awaiting consent)\n * - Persisted events (events in localStorage from previous sessions)\n *\n * @returns Number of events currently in the main queue\n *\n * @example\n * ```typescript\n * const queueSize = eventManager.getQueueLength();\n * console.log(`Queue has ${queueSize} events`);\n * ```\n */\n getQueueLength(): number {\n return this.eventsQueue.length;\n }\n\n /**\n * Returns a copy of current events in the queue.\n *\n * **Purpose**: Test utility to inspect queued events for validation.\n *\n * **Note**: Only available in development mode via TestBridge.\n *\n * @returns Shallow copy of events queue\n * @internal Used by test-bridge.ts for test inspection\n */\n getQueueEvents(): EventData[] {\n return [...this.eventsQueue];\n }\n\n /**\n * Triggers immediate queue flush (test utility).\n *\n * **Purpose**: Test utility to manually flush event queue for validation.\n *\n * **Note**: Only available in development mode via TestBridge.\n *\n * @returns Promise that resolves when flush completes\n * @internal Used by test-bridge.ts for test control\n */\n async flushQueue(): Promise<void> {\n await this.flushImmediately();\n }\n\n /**\n * Clears the event queue (test utility - use with caution).\n *\n * **Purpose**: Test utility to reset queue state between tests.\n *\n * **Warning**: This will discard all queued events without sending them.\n * Only use in test cleanup or when explicitly required.\n *\n * **Note**: Only available in development mode via TestBridge.\n *\n * @internal Used by test-bridge.ts for test cleanup\n */\n clearQueue(): void {\n this.eventsQueue = [];\n }\n\n /**\n * Flushes buffered events to the main queue after session initialization.\n *\n * **Purpose**: Re-tracks events that were captured before session initialization\n * (e.g., events fired during `App.init()` before SessionManager completes).\n *\n * **Pending Events Buffer**:\n * - Holds up to 100 events captured before `sessionId` is available\n * - FIFO eviction when buffer full (oldest events dropped with warning)\n * - Cleared and re-tracked when session becomes available\n *\n * **Flow**:\n * 1. Check if session is initialized (`sessionId` exists in global state)\n * 2. If not initialized: Log warning and keep events in buffer\n * 3. If initialized: Copy buffer, clear it, and re-track each event via `track()`\n * 4. Each event goes through full validation/dedup/rate limiting pipeline\n *\n * **Called by**:\n * - `SessionManager.startTracking()` after session initialization\n * - Ensures no events are lost during initialization phase\n *\n * **Important**: Events are re-tracked through `track()` method, so they go\n * through all validation, deduplication, rate limiting, and consent checks again.\n *\n * @example\n * ```typescript\n * // In SessionManager after session creation\n * this.set('sessionId', newSessionId);\n * eventManager.flushPendingEvents(); // Re-track buffered events\n * ```\n *\n * @see src/managers/README.md (lines 5-75) for pending buffer details\n */\n flushPendingEvents(): void {\n if (this.pendingEventsBuffer.length === 0) {\n return;\n }\n\n const currentSessionId = this.get('sessionId');\n if (!currentSessionId) {\n log('debug', 'Cannot flush pending events: session not initialized - keeping in buffer', {\n data: { bufferedEventCount: this.pendingEventsBuffer.length },\n });\n\n return;\n }\n\n const bufferedEvents = [...this.pendingEventsBuffer];\n this.pendingEventsBuffer = [];\n\n bufferedEvents.forEach((event) => {\n this.track(event);\n });\n }\n\n private clearSendInterval(): void {\n if (this.sendIntervalId) {\n clearInterval(this.sendIntervalId);\n this.sendIntervalId = null;\n }\n }\n\n private isSuccessfulResult(result: PromiseSettledResult<boolean>): boolean {\n return result.status === 'fulfilled' && result.value === true;\n }\n\n private flushEvents(isSync: boolean): boolean | Promise<boolean> {\n if (this.eventsQueue.length === 0) {\n return isSync ? true : Promise.resolve(true);\n }\n\n const body = this.buildEventsPayload();\n const eventsToSend = [...this.eventsQueue];\n const eventIds = eventsToSend.map((e) => e.id);\n\n if (this.dataSenders.length === 0) {\n this.removeProcessedEvents(eventIds);\n this.clearSendInterval();\n this.emitEventsQueue(body);\n\n return isSync ? true : Promise.resolve(true);\n }\n\n if (isSync) {\n const results = this.dataSenders.map((sender) => sender.sendEventsQueueSync(body));\n const anySucceeded = results.some((success) => success);\n\n // Optimistic removal: Remove events if ANY integration succeeded\n // Each SenderManager independently persists failures in its own localStorage\n // This prevents duplicate sends to successful integrations on retry\n if (anySucceeded) {\n this.removeProcessedEvents(eventIds);\n this.clearSendInterval();\n this.emitEventsQueue(body);\n } else {\n // All integrations failed - keep events in queue for retry on next page load\n this.clearSendInterval();\n log('debug', 'Sync flush complete failure, events kept in queue for retry', {\n data: { eventCount: eventIds.length },\n });\n }\n\n return anySucceeded;\n } else {\n const sendPromises = this.dataSenders.map(async (sender) =>\n sender.sendEventsQueue(body, {\n onSuccess: () => {},\n onFailure: () => {},\n }),\n );\n\n return Promise.allSettled(sendPromises).then((results) => {\n const anySucceeded = results.some((result) => this.isSuccessfulResult(result));\n\n // Optimistic removal: Remove events if ANY integration succeeded\n // Each SenderManager independently persists failures in its own localStorage\n // This prevents duplicate sends to successful integrations on retry\n if (anySucceeded) {\n this.removeProcessedEvents(eventIds);\n this.clearSendInterval();\n this.emitEventsQueue(body);\n } else {\n // All integrations failed - keep events in queue for retry on next page load\n log('debug', 'Async flush complete failure, events kept in queue for retry', {\n data: { eventCount: eventsToSend.length },\n });\n }\n\n return anySucceeded;\n });\n }\n }\n\n private async sendEventsQueue(): Promise<void> {\n if (!this.get('sessionId') || this.eventsQueue.length === 0) {\n return;\n }\n\n const body = this.buildEventsPayload();\n\n if (this.dataSenders.length === 0) {\n this.emitEventsQueue(body);\n return;\n }\n\n const eventsToSend = [...this.eventsQueue];\n const eventIds = eventsToSend.map((e) => e.id);\n\n const sendPromises = this.dataSenders.map(async (sender) =>\n sender.sendEventsQueue(body, {\n onSuccess: () => {},\n onFailure: () => {},\n }),\n );\n\n const results = await Promise.allSettled(sendPromises);\n\n const anySucceeded = results.some((result) => this.isSuccessfulResult(result));\n\n // Optimistic removal: Remove events if ANY integration succeeded\n // Each SenderManager independently persists failures in its own localStorage\n if (anySucceeded) {\n this.removeProcessedEvents(eventIds);\n this.emitEventsQueue(body);\n\n const failedCount = results.filter((result) => !this.isSuccessfulResult(result)).length;\n if (failedCount > 0) {\n log('debug', 'Periodic send completed with some failures, removed from queue and persisted per-integration', {\n data: { eventCount: eventsToSend.length, failedCount },\n });\n }\n } else {\n // All integrations failed - keep events in queue for retry\n log('debug', 'Periodic send complete failure, events kept in queue for retry', {\n data: { eventCount: eventsToSend.length },\n });\n }\n\n if (this.eventsQueue.length === 0) {\n this.clearSendInterval();\n }\n }\n\n private buildEventsPayload(): EventsQueue {\n const eventMap = new Map<string, EventData>();\n const order: string[] = [];\n\n for (const event of this.eventsQueue) {\n const signature = this.createEventSignature(event);\n\n if (!eventMap.has(signature)) {\n order.push(signature);\n }\n\n eventMap.set(signature, event);\n }\n\n const events = order\n .map((signature) => eventMap.get(signature))\n .filter((event): event is EventData => Boolean(event))\n .sort((a, b) => {\n // SESSION_START always goes first\n if (a.type === EventType.SESSION_START && b.type !== EventType.SESSION_START) return -1;\n if (b.type === EventType.SESSION_START && a.type !== EventType.SESSION_START) return 1;\n // Otherwise sort by timestamp\n return a.timestamp - b.timestamp;\n });\n\n let queue: EventsQueue = {\n user_id: this.get('userId'),\n session_id: this.get('sessionId') as string,\n device: this.get('device'),\n events,\n ...(this.get('config')?.globalMetadata && { global_metadata: this.get('config')?.globalMetadata }),\n };\n\n const collectApiUrls = this.get('collectApiUrls');\n const hasAnyBackend = Boolean(collectApiUrls?.custom || collectApiUrls?.saas);\n const beforeBatchTransformer = this.transformers.beforeBatch;\n\n if (!hasAnyBackend && beforeBatchTransformer) {\n const transformed = transformBatch(queue, beforeBatchTransformer, 'EventManager');\n\n if (transformed !== null) {\n queue = transformed;\n }\n }\n\n return queue;\n }\n\n private buildEventPayload(data: Partial<EventData>): EventData | null {\n const currentPageUrl = data.page_url ?? this.get('pageUrl');\n\n // Use TimeManager for accurate timestamps (immune to clock skew during session)\n const timestamp = this.timeManager.now();\n\n // Validate timestamp before creating event\n const validation = this.timeManager.validateTimestamp(timestamp);\n if (!validation.valid) {\n log('warn', 'Event timestamp validation failed', {\n data: { type: data.type, error: validation.error },\n });\n // Continue anyway with adjusted timestamp to avoid data loss\n // Backend has 3-minute tolerance as safety net\n }\n\n // Get session-level attribution from global state (captured once at SESSION_START)\n const sessionReferrer = this.get('sessionReferrer');\n const sessionUtm = this.get('sessionUtm');\n\n let payload: EventData = {\n id: generateEventId(),\n type: data.type as EventType,\n page_url: currentPageUrl,\n timestamp,\n ...(sessionReferrer && { referrer: sessionReferrer }),\n ...(data.from_page_url && { from_page_url: data.from_page_url }),\n ...(data.scroll_data && { scroll_data: data.scroll_data }),\n ...(data.click_data && { click_data: data.click_data }),\n ...(data.custom_event && { custom_event: data.custom_event }),\n ...(data.web_vitals && { web_vitals: data.web_vitals }),\n ...(data.error_data && { error_data: data.error_data }),\n ...(data.viewport_data && { viewport_data: data.viewport_data }),\n ...(data.page_view && { page_view: data.page_view }),\n ...(sessionUtm && { utm: sessionUtm }),\n };\n\n const collectApiUrls = this.get('collectApiUrls');\n const hasCustomBackend = Boolean(collectApiUrls?.custom);\n const hasSaasBackend = Boolean(collectApiUrls?.saas);\n const hasAnyBackend = hasCustomBackend || hasSaasBackend;\n const isMultiIntegration = hasCustomBackend && hasSaasBackend;\n const beforeSendTransformer = this.transformers.beforeSend;\n\n const shouldApplyBeforeSend =\n beforeSendTransformer && (!hasAnyBackend || (hasCustomBackend && !isMultiIntegration));\n\n if (shouldApplyBeforeSend) {\n const transformed = transformEvent(payload, beforeSendTransformer, 'EventManager');\n\n if (transformed === null) {\n return null;\n }\n\n payload = transformed;\n }\n\n return payload;\n }\n\n private isDuplicateEvent(event: EventData): boolean {\n const now = Date.now();\n const fingerprint = this.createEventFingerprint(event);\n\n const lastSeen = this.recentEventFingerprints.get(fingerprint);\n\n if (lastSeen && now - lastSeen < DUPLICATE_EVENT_THRESHOLD_MS) {\n this.recentEventFingerprints.set(fingerprint, now);\n return true;\n }\n\n this.recentEventFingerprints.set(fingerprint, now);\n\n if (this.recentEventFingerprints.size > MAX_FINGERPRINTS) {\n this.pruneOldFingerprints();\n }\n\n if (this.recentEventFingerprints.size > MAX_FINGERPRINTS_HARD_LIMIT) {\n this.recentEventFingerprints.clear();\n this.recentEventFingerprints.set(fingerprint, now);\n\n log('debug', 'Event fingerprint cache exceeded hard limit, cleared', {\n data: { hardLimit: MAX_FINGERPRINTS_HARD_LIMIT },\n });\n }\n\n return false;\n }\n\n private pruneOldFingerprints(): void {\n const now = Date.now();\n const cutoff = DUPLICATE_EVENT_THRESHOLD_MS * FINGERPRINT_CLEANUP_MULTIPLIER;\n\n for (const [fingerprint, timestamp] of this.recentEventFingerprints.entries()) {\n if (now - timestamp > cutoff) {\n this.recentEventFingerprints.delete(fingerprint);\n }\n }\n\n log('debug', 'Pruned old event fingerprints', {\n data: {\n remaining: this.recentEventFingerprints.size,\n cutoffMs: cutoff,\n },\n });\n }\n\n private createEventFingerprint(event: EventData): string {\n let fingerprint = `${event.type}_${event.page_url}`;\n\n if (event.click_data) {\n const x = Math.round((event.click_data.x || 0) / 10) * 10;\n const y = Math.round((event.click_data.y || 0) / 10) * 10;\n fingerprint += `_click_${x}_${y}`;\n }\n\n if (event.scroll_data) {\n fingerprint += `_scroll_${event.scroll_data.depth}_${event.scroll_data.direction}`;\n }\n\n if (event.custom_event) {\n fingerprint += `_custom_${event.custom_event.name}`;\n }\n\n if (event.web_vitals) {\n fingerprint += `_vitals_${event.web_vitals.type}`;\n }\n\n if (event.error_data) {\n fingerprint += `_error_${event.error_data.type}_${event.error_data.message}`;\n }\n\n return fingerprint;\n }\n\n private createEventSignature(event: EventData): string {\n return this.createEventFingerprint(event);\n }\n\n private addToQueue(event: EventData): void {\n this.emitEvent(event);\n\n this.eventsQueue.push(event);\n\n if (this.eventsQueue.length > MAX_EVENTS_QUEUE_LENGTH) {\n const nonCriticalIndex = this.eventsQueue.findIndex((e) => e.type !== EventType.SESSION_START);\n\n const removedEvent =\n nonCriticalIndex >= 0 ? this.eventsQueue.splice(nonCriticalIndex, 1)[0] : this.eventsQueue.shift();\n\n log('warn', 'Event queue overflow, oldest non-critical event removed', {\n data: {\n maxLength: MAX_EVENTS_QUEUE_LENGTH,\n currentLength: this.eventsQueue.length,\n removedEventType: removedEvent?.type,\n wasCritical: removedEvent?.type === EventType.SESSION_START,\n },\n });\n }\n\n if (!this.sendIntervalId) {\n this.startSendInterval();\n }\n\n if (this.eventsQueue.length >= BATCH_SIZE_THRESHOLD) {\n void this.sendEventsQueue();\n }\n }\n\n private startSendInterval(): void {\n this.sendIntervalId = window.setInterval(() => {\n if (this.eventsQueue.length > 0) {\n void this.sendEventsQueue();\n }\n }, EVENT_SENT_INTERVAL_MS);\n }\n\n private shouldSample(): boolean {\n const samplingRate = this.get('config')?.samplingRate ?? 1;\n return Math.random() < samplingRate;\n }\n\n private checkRateLimit(): boolean {\n const now = Date.now();\n\n if (now - this.rateLimitWindowStart > RATE_LIMIT_WINDOW_MS) {\n this.rateLimitCounter = 0;\n this.rateLimitWindowStart = now;\n }\n\n if (this.rateLimitCounter >= MAX_EVENTS_PER_SECOND) {\n return false;\n }\n\n this.rateLimitCounter++;\n return true;\n }\n\n private checkPerEventRateLimit(eventName: string, maxSameEventPerMinute: number): boolean {\n const now = Date.now();\n const timestamps = this.perEventRateLimits.get(eventName) ?? [];\n\n const validTimestamps = timestamps.filter((ts) => now - ts < PER_EVENT_RATE_LIMIT_WINDOW_MS);\n\n if (validTimestamps.length >= maxSameEventPerMinute) {\n log('warn', 'Per-event rate limit exceeded for custom event', {\n data: {\n eventName,\n limit: maxSameEventPerMinute,\n window: `${PER_EVENT_RATE_LIMIT_WINDOW_MS / 1000}s`,\n },\n });\n return false;\n }\n\n validTimestamps.push(now);\n this.perEventRateLimits.set(eventName, validTimestamps);\n\n return true;\n }\n\n private getTypeLimitForEvent(type: EventType): number | null {\n const limits: Partial<Record<EventType, number>> = {\n [EventType.CLICK]: MAX_CLICKS_PER_SESSION,\n [EventType.PAGE_VIEW]: MAX_PAGE_VIEWS_PER_SESSION,\n [EventType.CUSTOM]: MAX_CUSTOM_EVENTS_PER_SESSION,\n [EventType.VIEWPORT_VISIBLE]: MAX_VIEWPORT_EVENTS_PER_SESSION,\n [EventType.SCROLL]: MAX_SCROLL_EVENTS_PER_SESSION,\n };\n return limits[type] ?? null;\n }\n\n private removeProcessedEvents(eventIds: string[]): void {\n const eventIdSet = new Set(eventIds);\n\n this.eventsQueue = this.eventsQueue.filter((event) => {\n return !eventIdSet.has(event.id);\n });\n }\n\n private emitEvent(eventData: EventData): void {\n if (this.emitter) {\n this.emitter.emit(EmitterEvent.EVENT, eventData);\n }\n }\n\n private emitEventsQueue(queue: EventsQueue): void {\n if (this.emitter) {\n this.emitter.emit(EmitterEvent.QUEUE, queue);\n }\n }\n\n /**\n * Creates a debounced version of a function that delays execution until after\n * a specified wait time has elapsed since the last invocation.\n *\n * **Purpose**: Reduces frequency of expensive operations (localStorage writes)\n * while ensuring no data is lost (trailing edge execution).\n *\n * **Behavior**:\n * - Each call resets the timer\n * - Function executes only after `delay` ms of silence\n * - Last invocation always executes (trailing edge)\n *\n * **Use Case**: Batches rapid successive calls into a single execution\n *\n * @param fn - Function to debounce\n * @param delay - Delay in milliseconds\n * @returns Debounced version of the function\n *\n * @internal\n */\n private debounce<T extends (...args: any[]) => void>(fn: T, delay: number): T {\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n return ((...args: Parameters<T>) => {\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n }\n\n timeoutId = setTimeout(() => {\n fn(...args);\n timeoutId = null;\n }, delay);\n }) as T;\n }\n\n /**\n * Returns initial zero counts for session event tracking.\n *\n * **Purpose**: DRY helper to avoid duplicating initial counts structure\n * across multiple methods (loadSessionCounts, validation fallbacks).\n *\n * @returns Fresh SessionEventCounts object with all counters at zero\n *\n * @internal\n */\n private getInitialCounts(): SessionEventCounts {\n return {\n total: 0,\n [EventType.CLICK]: 0,\n [EventType.PAGE_VIEW]: 0,\n [EventType.CUSTOM]: 0,\n [EventType.VIEWPORT_VISIBLE]: 0,\n [EventType.SCROLL]: 0,\n };\n }\n\n /**\n * Loads persisted session event counts from localStorage.\n *\n * **Purpose**: Restore per-session event counts after page reload to maintain\n * accurate rate limiting across page navigations within the same session.\n *\n * **Behavior**:\n * - Attempts to load counts from localStorage using session ID as key\n * - If no persisted data found: Returns initial zero counts\n * - If corrupted data found: Returns initial zero counts with warning\n *\n * **Storage Key**: `tlog:{userId}:session_counts:{sessionId}`\n *\n * **Why This Matters**:\n * - Without persistence, counts reset on every page reload\n * - This allows users to bypass per-session limits by refreshing the page\n * - Example: 100 PAGE_VIEW limit could be bypassed by reloading after 99 events\n *\n * @param sessionId - Current session identifier\n * @returns Session event counts object (either persisted or initial state)\n *\n * @internal\n */\n private loadSessionCounts(sessionId: string): SessionEventCounts {\n if (typeof window === 'undefined' || typeof localStorage === 'undefined') {\n return this.getInitialCounts();\n }\n\n const userId = this.get('userId') || 'anonymous';\n const storageKey = SESSION_COUNTS_KEY(userId, sessionId);\n\n try {\n const stored = localStorage.getItem(storageKey);\n\n if (!stored) {\n // No persisted data - this is a new session or first page load\n return this.getInitialCounts();\n }\n\n const parsed = JSON.parse(stored) as Partial<StoredSessionCounts>;\n\n // Check for expiry (7 days)\n if (parsed._timestamp && Date.now() - parsed._timestamp > SESSION_COUNTS_EXPIRY_MS) {\n log('debug', 'Session counts expired, clearing', {\n data: { sessionId, age: Date.now() - parsed._timestamp },\n });\n localStorage.removeItem(storageKey);\n return this.getInitialCounts();\n }\n\n // Robust validation: check all required fields (not just 'total')\n // Prevents partial/corrupted data from causing runtime errors\n if (\n typeof parsed.total === 'number' &&\n typeof parsed[EventType.CLICK] === 'number' &&\n typeof parsed[EventType.PAGE_VIEW] === 'number' &&\n typeof parsed[EventType.CUSTOM] === 'number' &&\n typeof parsed[EventType.VIEWPORT_VISIBLE] === 'number' &&\n typeof parsed[EventType.SCROLL] === 'number'\n ) {\n // Return only the SessionEventCounts fields (exclude _timestamp, _version)\n return {\n total: parsed.total,\n [EventType.CLICK]: parsed[EventType.CLICK],\n [EventType.PAGE_VIEW]: parsed[EventType.PAGE_VIEW],\n [EventType.CUSTOM]: parsed[EventType.CUSTOM],\n [EventType.VIEWPORT_VISIBLE]: parsed[EventType.VIEWPORT_VISIBLE],\n [EventType.SCROLL]: parsed[EventType.SCROLL],\n };\n }\n\n log('warn', 'Invalid session counts structure in localStorage, resetting', {\n data: { sessionId, parsed },\n });\n localStorage.removeItem(storageKey);\n log('debug', 'Session counts removed due to invalid/corrupted data', {\n data: { sessionId, parsed },\n });\n\n return this.getInitialCounts();\n } catch (error) {\n log('warn', 'Failed to load session counts from localStorage', {\n error,\n data: { sessionId },\n });\n\n return this.getInitialCounts();\n }\n }\n\n /**\n * Cleans up expired session counts from localStorage.\n *\n * **Purpose**: Prevents localStorage pollution from abandoned sessions by removing\n * counts older than 7 days.\n *\n * **Behavior**:\n * - Checks if cleanup was run recently (within last hour) and skips if so\n * - Iterates all localStorage keys matching the session counts prefix pattern\n * - Parses each entry and checks `_timestamp` field\n * - Removes entries where age exceeds SESSION_COUNTS_EXPIRY_MS (7 days)\n * - Silently ignores parse errors (corrupted entries cleaned on next load)\n * - Updates last cleanup timestamp after successful run\n *\n * **When Called**: Automatically on EventManager constructor initialization\n *\n * **Performance**: O(n) scan where n = localStorage keys (typically <100), but throttled\n * to run at most once per hour to prevent impact on rapid page reloads\n *\n * @internal\n */\n private cleanupExpiredSessionCounts(): void {\n if (typeof window === 'undefined' || typeof localStorage === 'undefined') {\n return;\n }\n\n try {\n // Throttling: Skip if cleanup ran recently (within last hour)\n const lastCleanup = localStorage.getItem(SESSION_COUNTS_LAST_CLEANUP_KEY);\n\n if (lastCleanup) {\n const timeSinceLastCleanup = Date.now() - parseInt(lastCleanup, 10);\n\n if (timeSinceLastCleanup < SESSION_COUNTS_CLEANUP_THROTTLE_MS) {\n log('debug', 'Skipping session counts cleanup (throttled)', {\n data: { timeSinceLastCleanup, throttleMs: SESSION_COUNTS_CLEANUP_THROTTLE_MS },\n });\n\n return;\n }\n }\n\n const userId = this.get('userId') || 'anonymous';\n const prefix = `${STORAGE_BASE_KEY}:${userId}:session_counts:`;\n\n // Collect keys to remove (can't modify during iteration)\n const keysToRemove: string[] = [];\n\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n\n if (key?.startsWith(prefix)) {\n try {\n const stored = localStorage.getItem(key);\n\n if (stored) {\n const parsed = JSON.parse(stored) as Partial<StoredSessionCounts>;\n\n // Check expiry\n if (parsed._timestamp && Date.now() - parsed._timestamp > SESSION_COUNTS_EXPIRY_MS) {\n keysToRemove.push(key);\n }\n }\n } catch {\n // Ignore parse errors, will be cleaned on next load\n }\n }\n }\n\n // Remove expired entries\n keysToRemove.forEach((key) => {\n localStorage.removeItem(key);\n log('debug', 'Cleaned up expired session counts', { data: { key } });\n });\n\n if (keysToRemove.length > 0) {\n log('info', `Cleaned up ${keysToRemove.length} expired session counts entries`);\n }\n\n // Update last cleanup timestamp\n localStorage.setItem(SESSION_COUNTS_LAST_CLEANUP_KEY, Date.now().toString());\n } catch (error) {\n log('warn', 'Failed to cleanup expired session counts', { error });\n }\n }\n\n /**\n * Persists current session event counts to localStorage (debounced).\n *\n * **Purpose**: Save event counts to ensure they survive page reloads and\n * maintain accurate per-session rate limiting across navigations.\n *\n * **Behavior**:\n * - Saves current `sessionEventCounts` to localStorage using session ID as key\n * - Overwrites previous counts (always reflects latest state)\n * - Fails silently if localStorage quota exceeded or unavailable\n *\n * **Storage Key**: `tlog:{userId}:session_counts:{sessionId}`\n *\n * **Debouncing**: This method is called via `saveSessionCountsDebounced()`\n * with 500ms debounce delay. Direct calls are for immediate saves (e.g., stop()).\n *\n * **Performance**: Debouncing reduces localStorage writes from ~1000 per session\n * to ~20-30 (96-97% reduction) while maintaining data integrity.\n *\n * **Cleanup**: Counts persist across page reloads for rate limiting enforcement.\n * Automatic cleanup on page reload removes expired counts (older than 7 days).\n * Note: SESSION_COUNTS_KEY entries are intentionally persistent to maintain\n * rate limits across sessions (~100 bytes per session).\n *\n * @param sessionId - Current session identifier\n *\n * @internal\n */\n private saveSessionCounts(sessionId: string): void {\n const userId = this.get('userId') || 'anonymous';\n const storageKey = SESSION_COUNTS_KEY(userId, sessionId);\n\n try {\n const dataToStore: StoredSessionCounts = {\n ...this.sessionEventCounts,\n _timestamp: Date.now(),\n _version: 1,\n };\n\n localStorage.setItem(storageKey, JSON.stringify(dataToStore));\n } catch (error) {\n log('warn', 'Failed to persist session counts to localStorage', {\n error,\n data: { sessionId },\n });\n }\n }\n}\n","import { USER_ID_KEY } from '../constants';\nimport { generateUUID } from '../utils';\nimport { StorageManager } from './storage.manager';\n\n/**\n * Simple utility for managing unique user identification for analytics tracking.\n *\n * **Purpose**: Creates and persists RFC4122-compliant UUID v4 identifiers\n * for tracking users across browser sessions.\n *\n * **Core Functionality**:\n * - **User ID Generation**: Creates UUID v4 identifiers\n * - **Persistence**: Stores user IDs in localStorage with automatic fallback\n * - **Session Continuity**: Reuses existing user IDs across browser sessions\n * - **Global User Identity**: Single user ID shared across all TraceLog projects in the same browser\n *\n * **Key Features**:\n * - Static utility method pattern (no object instantiation required)\n * - UUID v4 generation for globally unique identifiers\n * - Fixed storage key (`tlog:uid`) for consistent identification across projects\n * - Automatic fallback to memory storage when localStorage unavailable\n * - Minimal dependencies and zero allocation approach\n *\n * **Storage**: `tlog:uid` (fixed, not project-scoped)\n *\n * @see src/managers/README.md (lines 227-252) for detailed documentation\n *\n * @example\n * ```typescript\n * const userId = UserManager.getId(storageManager);\n * // Returns: '550e8400-e29b-41d4-a716-446655440000' (UUID v4)\n *\n * // Subsequent calls return the same ID\n * const sameUserId = UserManager.getId(storageManager);\n * // Returns: '550e8400-e29b-41d4-a716-446655440000' (persisted)\n * ```\n */\nexport class UserManager {\n /**\n * Gets or creates a unique user ID.\n *\n * **Behavior**:\n * 1. Checks localStorage for existing user ID\n * 2. Returns existing ID if found\n * 3. Generates new RFC4122-compliant UUID v4 if not found\n * 4. Persists new ID to localStorage\n *\n * **Storage Key**: `tlog:uid` (fixed, shared across all TraceLog projects)\n *\n * **ID Format**: UUID v4 (e.g., `550e8400-e29b-41d4-a716-446655440000`)\n *\n * @param storageManager - Storage manager instance for persistence\n * @returns Persistent unique user ID (UUID v4 format)\n */\n static getId(storageManager: StorageManager): string {\n const storedUserId = storageManager.getItem(USER_ID_KEY);\n\n if (storedUserId) {\n return storedUserId;\n }\n\n const newUserId = generateUUID();\n storageManager.setItem(USER_ID_KEY, newUserId);\n\n return newUserId;\n }\n}\n","import { BROADCAST_CHANNEL_NAME, DEFAULT_SESSION_TIMEOUT, SESSION_STORAGE_KEY } from '../constants';\nimport { EventType, UTM } from '../types';\nimport { getExternalReferrer, getUTMParameters, log } from '../utils';\nimport { StateManager } from './state.manager';\nimport { StorageManager } from './storage.manager';\nimport { EventManager } from './event.manager';\n\ninterface StoredSessionData {\n id: string;\n lastActivity: number;\n referrer?: string;\n utm?: UTM;\n}\n\n/**\n * Session ID validation pattern: {timestamp}-{9-char-base36}\n *\n * Validates recovered session IDs to prevent data corruption from:\n * - Manual localStorage manipulation by users or browser extensions\n * - Race conditions in cross-tab scenarios (partial/truncated writes)\n * - Storage corruption or quota-based truncation\n * - Malformed IDs from older library versions or external interference\n *\n * Format: 13-digit timestamp + hyphen + 9 lowercase alphanumeric characters\n * Example: 1704067200000-a3f9c2b5d\n */\nconst SESSION_ID_PATTERN = /^\\d{13}-[a-z0-9]{9}$/;\n\n/**\n * Manages user sessions with cross-tab synchronization, inactivity detection,\n * and automatic lifecycle tracking.\n *\n * **Purpose**: Creates and manages user sessions, tracking session start\n * with automatic persistence, recovery, and multi-tab synchronization.\n * Does not track session end events (removed in v2.0.0).\n *\n * **Core Functionality**:\n * - **Session Generation**: Creates unique session IDs (`{timestamp}-{9-char-base36}`)\n * - **Activity Tracking**: Monitors user interactions to extend session timeout\n * - **Cross-Tab Sync**: BroadcastChannel synchronization across browser tabs\n * - **Persistence**: Stores session data in localStorage for recovery\n * - **Inactivity Detection**: Automatic timeout after inactivity (default 15 minutes)\n * - **Lifecycle Events**: Emits SESSION_START event only (SESSION_END removed in v2.0.0)\n *\n * **Key Features**:\n * - **Session ID Format**: `{timestamp}-{9-char-base36}` (e.g., `1704896400000-a3b4c5d6e`)\n * - **Default Timeout**: 15 minutes (900,000 ms), configurable via `sessionTimeout`\n * - **Cross-Tab Sharing**: Primary tab creates session, shares via BroadcastChannel\n * - **Secondary Tab Behavior**: Receives session from primary tab, no SESSION_START event\n * - **No Session End Events**: Library only tracks SESSION_START (v2.0.0+)\n *\n * **BroadcastChannel Integration**:\n * - **Initialized BEFORE SESSION_START**: Prevents race condition with secondary tabs\n * - **Messages**: Only `session_start` action (share session across tabs)\n * - **Fallback**: Logs warning if BroadcastChannel not supported (no cross-tab sync)\n *\n * **Activity Detection**:\n * - Tracks user interactions via listener managers (mouse, keyboard, touch, scroll, etc.)\n * - Resets inactivity timeout on each activity\n * - Updates `lastActivity` timestamp in localStorage\n *\n * **State Management**:\n * - **`sessionId`**: Current session ID stored in global state\n *\n * @see src/managers/README.md (lines 140-169) for detailed documentation\n *\n * @example\n * ```typescript\n * const sessionManager = new SessionManager(storage, eventManager, 'project123');\n *\n * // Start session tracking\n * sessionManager.startTracking();\n * // → Creates session ID: '1704896400000-a3b4c5d6e'\n * // → Emits SESSION_START event\n * // → Sets up activity listeners\n * // → Initializes cross-tab sync\n *\n * // User activity extends session\n * // (automatic via activity listeners)\n *\n * // Stop tracking (cleanup only)\n * sessionManager.stopTracking();\n * // → Cleans up listeners and timers\n * // → No SESSION_END event emitted\n * ```\n */\nexport class SessionManager extends StateManager {\n private readonly storageManager: StorageManager;\n private readonly eventManager: EventManager;\n private readonly projectId: string;\n\n private activityHandler: (() => void) | null = null;\n private visibilityChangeHandler: (() => void) | null = null;\n private sessionTimeoutId: ReturnType<typeof setTimeout> | null = null;\n private broadcastChannel: BroadcastChannel | null = null;\n private isTracking = false;\n private needsRenewal = false;\n\n /**\n * Creates a SessionManager instance.\n *\n * @param storageManager - Storage manager for session persistence\n * @param eventManager - Event manager for SESSION_START events\n * @param projectId - Project identifier for namespacing session storage\n */\n constructor(storageManager: StorageManager, eventManager: EventManager, projectId: string) {\n super();\n this.storageManager = storageManager;\n this.eventManager = eventManager;\n this.projectId = projectId;\n }\n\n private initCrossTabSync(): void {\n if (typeof BroadcastChannel === 'undefined') {\n log('debug', 'BroadcastChannel not supported');\n return;\n }\n\n const projectId = this.getProjectId();\n this.broadcastChannel = new BroadcastChannel(BROADCAST_CHANNEL_NAME(projectId));\n\n this.broadcastChannel.onmessage = (event): void => {\n const { action, sessionId, timestamp, projectId: messageProjectId } = event.data ?? {};\n\n if (messageProjectId !== projectId) {\n return;\n }\n\n if (action === 'session_start' && sessionId && typeof timestamp === 'number' && timestamp > Date.now() - 5000) {\n this.set('sessionId', sessionId);\n this.persistSession(sessionId, timestamp);\n if (this.isTracking) {\n this.setupSessionTimeout();\n }\n } else if (action && action !== 'session_start') {\n // Log unknown action types to help with debugging cross-tab synchronization issues\n log('debug', 'Ignored BroadcastChannel message with unknown action', { data: { action } });\n }\n };\n }\n\n private shareSession(sessionId: string): void {\n if (this.broadcastChannel && typeof this.broadcastChannel.postMessage === 'function') {\n this.broadcastChannel.postMessage({\n action: 'session_start',\n projectId: this.getProjectId(),\n sessionId,\n timestamp: Date.now(),\n });\n }\n }\n\n private cleanupCrossTabSync(): void {\n if (this.broadcastChannel) {\n if (typeof this.broadcastChannel.close === 'function') {\n this.broadcastChannel.close();\n }\n this.broadcastChannel = null;\n }\n }\n\n private recoverSession(): string | null {\n const storedSession = this.loadStoredSession();\n\n if (!storedSession) {\n return null;\n }\n\n // Validate session ID format: {timestamp}-{9-char-base36}\n if (!SESSION_ID_PATTERN.test(storedSession.id)) {\n log('warn', 'Invalid session ID format recovered from storage, clearing', {\n data: { sessionId: storedSession.id },\n });\n this.clearStoredSession();\n return null;\n }\n\n const sessionTimeout = this.get('config')?.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT;\n\n if (Date.now() - storedSession.lastActivity > sessionTimeout) {\n this.clearStoredSession();\n return null;\n }\n\n return storedSession.id;\n }\n\n private persistSession(sessionId: string, lastActivity: number = Date.now(), referrer?: string, utm?: UTM): void {\n this.saveStoredSession({\n id: sessionId,\n lastActivity,\n ...(referrer && { referrer }),\n ...(utm && { utm }),\n });\n }\n\n private clearStoredSession(): void {\n const storageKey = this.getSessionStorageKey();\n this.storageManager.removeItem(storageKey);\n }\n\n private loadStoredSession(): StoredSessionData | null {\n const storageKey = this.getSessionStorageKey();\n const storedData = this.storageManager.getItem(storageKey);\n\n if (!storedData) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(storedData) as StoredSessionData;\n if (!parsed.id || typeof parsed.lastActivity !== 'number') {\n return null;\n }\n return parsed;\n } catch {\n this.storageManager.removeItem(storageKey);\n return null;\n }\n }\n\n private saveStoredSession(session: StoredSessionData): void {\n const storageKey = this.getSessionStorageKey();\n this.storageManager.setItem(storageKey, JSON.stringify(session));\n }\n\n private getSessionStorageKey(): string {\n return SESSION_STORAGE_KEY(this.getProjectId());\n }\n\n private getProjectId(): string {\n return this.projectId;\n }\n\n /**\n * Starts session tracking with lifecycle management and cross-tab synchronization.\n *\n * **Purpose**: Initializes session tracking, creating or recovering a session ID,\n * setting up activity listeners, and enabling cross-tab synchronization.\n *\n * **Flow**:\n * 1. Checks if tracking already active (idempotent)\n * 2. Attempts to recover session from localStorage\n * 3. If no recovery: Generates new session ID (`{timestamp}-{9-char-base36}`)\n * 4. Sets `sessionId` in global state\n * 5. Persists session to localStorage\n * 6. Initializes BroadcastChannel for cross-tab sync (BEFORE SESSION_START)\n * 7. Shares session via BroadcastChannel (notifies other tabs)\n * 8. If NOT recovered: Tracks SESSION_START event\n * 9. Sets up inactivity timeout (default 15 minutes)\n * 10. Sets up activity listeners (click, keydown, scroll)\n * 11. Sets up lifecycle listeners (visibilitychange, beforeunload)\n *\n * **Session Recovery**:\n * - Checks localStorage for existing session\n * - Recovers if session exists and is recent (within timeout window)\n * - NO SESSION_START event if session recovered\n *\n * **Error Handling**:\n * - On error: Rolls back all setup (cleanup listeners, timers, state)\n * - Re-throws error to caller (App.init() handles failure)\n *\n * **BroadcastChannel Initialization Order**:\n * - CRITICAL: BroadcastChannel initialized BEFORE SESSION_START event\n * - Prevents race condition with secondary tabs\n * - Ensures secondary tabs can receive session_start message\n *\n * **Called by**: `SessionHandler.startTracking()` during `App.init()`\n *\n * **Important**: After successful call, `sessionId` is available in global state\n * and EventManager can flush pending events via `flushPendingEvents()`.\n *\n * @throws Error if initialization fails (rolled back automatically)\n *\n * @example\n * ```typescript\n * sessionManager.startTracking();\n * // → Session created: '1704896400000-a3b4c5d6e'\n * // → SESSION_START event tracked\n * // → Activity listeners active\n * // → Cross-tab sync enabled\n * ```\n *\n * @see src/managers/README.md (lines 140-169) for session management details\n */\n startTracking(): void {\n if (this.isTracking) {\n log('debug', 'Session tracking already active');\n return;\n }\n\n const recoveredSessionId = this.recoverSession();\n const sessionId = recoveredSessionId ?? this.generateSessionId();\n\n // Capture or recover attribution data (referrer/UTM)\n let sessionReferrer: string;\n let sessionUtm: UTM | undefined;\n\n if (recoveredSessionId) {\n // Session recovered: load attribution from storage (with fallback to current context)\n const storedSession = this.loadStoredSession();\n sessionReferrer = storedSession?.referrer ?? getExternalReferrer();\n sessionUtm = storedSession?.utm ?? getUTMParameters();\n } else {\n // New session: capture from current page context\n sessionReferrer = getExternalReferrer();\n sessionUtm = getUTMParameters();\n }\n\n log('debug', 'Session tracking initialized', {\n data: {\n sessionId,\n wasRecovered: !!recoveredSessionId,\n willEmitSessionStart: !recoveredSessionId,\n sessionReferrer,\n hasUtm: !!sessionUtm,\n },\n });\n\n this.isTracking = true;\n\n try {\n this.set('sessionId', sessionId);\n this.set('sessionReferrer', sessionReferrer);\n this.set('sessionUtm', sessionUtm);\n this.persistSession(sessionId, Date.now(), sessionReferrer, sessionUtm);\n this.initCrossTabSync();\n this.shareSession(sessionId);\n\n // Only emit SESSION_START for NEW sessions (not recovered)\n // Recovered sessions already had their SESSION_START tracked on initial creation\n // Server infers session end from last event timestamp (v2.0+)\n if (!recoveredSessionId) {\n log('debug', 'Emitting SESSION_START event', {\n data: { sessionId },\n });\n\n this.eventManager.track({\n type: EventType.SESSION_START,\n });\n } else {\n log('debug', 'Session recovered, skipping SESSION_START', {\n data: { sessionId },\n });\n }\n\n this.setupSessionTimeout();\n this.setupActivityListeners();\n this.setupLifecycleListeners();\n } catch (error) {\n this.isTracking = false;\n this.clearSessionTimeout();\n this.cleanupActivityListeners();\n this.cleanupLifecycleListeners();\n this.cleanupCrossTabSync();\n this.set('sessionId', null);\n\n throw error;\n }\n }\n\n private generateSessionId(): string {\n return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;\n }\n\n private setupSessionTimeout(): void {\n this.clearSessionTimeout();\n\n const sessionTimeout = this.get('config')?.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT;\n\n this.sessionTimeoutId = setTimeout(() => {\n this.enterRenewalMode();\n }, sessionTimeout);\n }\n\n private resetSessionTimeout(): void {\n this.setupSessionTimeout();\n const sessionId = this.get('sessionId') as string;\n if (sessionId) {\n this.persistSession(sessionId, Date.now(), this.get('sessionReferrer'), this.get('sessionUtm'));\n }\n }\n\n private clearSessionTimeout(): void {\n if (this.sessionTimeoutId) {\n clearTimeout(this.sessionTimeoutId);\n this.sessionTimeoutId = null;\n }\n }\n\n private setupActivityListeners(): void {\n this.activityHandler = (): void => {\n if (this.needsRenewal) {\n this.renewSession();\n } else {\n this.resetSessionTimeout();\n }\n };\n\n document.addEventListener('click', this.activityHandler, { passive: true });\n document.addEventListener('keydown', this.activityHandler, { passive: true });\n document.addEventListener('scroll', this.activityHandler, { passive: true });\n }\n\n /**\n * Renews the session after timeout when user returns.\n * Creates a new session ID and emits SESSION_START.\n */\n private renewSession(): void {\n this.needsRenewal = false;\n\n const newSessionId = this.generateSessionId();\n const sessionReferrer = getExternalReferrer();\n const sessionUtm = getUTMParameters();\n\n log('debug', 'Renewing session after timeout', {\n data: { newSessionId },\n });\n\n this.set('sessionId', newSessionId);\n this.set('sessionReferrer', sessionReferrer);\n this.set('sessionUtm', sessionUtm);\n this.persistSession(newSessionId, Date.now(), sessionReferrer, sessionUtm);\n\n // Re-initialize cross-tab sync with new session\n this.cleanupCrossTabSync();\n this.initCrossTabSync();\n this.shareSession(newSessionId);\n\n this.eventManager.track({\n type: EventType.SESSION_START,\n });\n\n // Flush any events that were buffered during renewal mode\n this.eventManager.flushPendingEvents();\n\n this.setupSessionTimeout();\n }\n\n private cleanupActivityListeners(): void {\n if (this.activityHandler) {\n document.removeEventListener('click', this.activityHandler);\n document.removeEventListener('keydown', this.activityHandler);\n document.removeEventListener('scroll', this.activityHandler);\n this.activityHandler = null;\n }\n }\n\n private setupLifecycleListeners(): void {\n if (this.visibilityChangeHandler) {\n return;\n }\n\n this.visibilityChangeHandler = (): void => {\n if (document.hidden) {\n this.clearSessionTimeout();\n } else {\n // Check if session expired during browser suspend/hibernate\n if (this.isSessionStale()) {\n log('debug', 'Session expired during suspend, entering renewal mode');\n this.enterRenewalMode();\n return;\n }\n\n const sessionId = this.get('sessionId');\n if (sessionId) {\n this.setupSessionTimeout();\n }\n }\n };\n\n document.addEventListener('visibilitychange', this.visibilityChangeHandler);\n }\n\n /**\n * Checks if the current session has become stale (expired during browser suspend).\n * This handles the case where JavaScript timers are paused during suspend/hibernate.\n */\n private isSessionStale(): boolean {\n // If already in renewal mode, no need to check\n if (this.needsRenewal) {\n return false;\n }\n\n const sessionId = this.get('sessionId');\n if (!sessionId) {\n return false;\n }\n\n const storedSession = this.loadStoredSession();\n if (!storedSession) {\n return false;\n }\n\n const sessionTimeout = this.get('config')?.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT;\n return Date.now() - storedSession.lastActivity > sessionTimeout;\n }\n\n private cleanupLifecycleListeners(): void {\n if (this.visibilityChangeHandler) {\n document.removeEventListener('visibilitychange', this.visibilityChangeHandler);\n this.visibilityChangeHandler = null;\n }\n }\n\n /**\n * Enters renewal mode after session timeout.\n * Keeps activity listeners active to detect when user returns.\n * Called by session timeout timer.\n */\n private enterRenewalMode(): void {\n this.clearSessionTimeout();\n this.cleanupCrossTabSync();\n this.clearStoredSession();\n\n this.set('sessionId', null);\n this.set('hasStartSession', false);\n this.set('sessionReferrer', undefined);\n this.set('sessionUtm', undefined);\n\n // Keep activity listeners active but switch to renewal mode\n this.needsRenewal = true;\n\n log('debug', 'Session timed out, entering renewal mode');\n }\n\n /**\n * Fully resets session state and cleans up all resources.\n * Called by stopTracking() for explicit session termination.\n */\n private resetSessionState(): void {\n this.clearSessionTimeout();\n this.cleanupActivityListeners();\n this.cleanupLifecycleListeners();\n this.cleanupCrossTabSync();\n this.clearStoredSession();\n\n this.set('sessionId', null);\n this.set('hasStartSession', false);\n this.set('sessionReferrer', undefined);\n this.set('sessionUtm', undefined);\n\n this.needsRenewal = false;\n this.isTracking = false;\n }\n\n /**\n * Stops session tracking and cleans up all resources.\n *\n * **Purpose**: Manually stops the current session tracking and cleans up\n * all listeners and timers. Does not emit SESSION_END event (removed in v2.0.0).\n *\n * **Flow**:\n * 1. Clears inactivity timeout\n * 2. Removes activity listeners (click, keydown, scroll)\n * 3. Removes lifecycle listeners (visibilitychange)\n * 4. Closes BroadcastChannel\n * 5. Clears session from localStorage\n * 6. Resets `sessionId` and `hasStartSession` in global state\n * 7. Sets `isTracking` to false\n *\n * **Called by**: `App.destroy()` during application teardown or when session times out\n *\n * **Important**: After calling, session tracking is terminated and cannot be resumed.\n * A new session will be created on next `startTracking()` call.\n *\n * @example\n * ```typescript\n * // Stop session tracking\n * sessionManager.stopTracking();\n * // → All listeners cleaned up\n * // → Session cleared from localStorage\n * // → BroadcastChannel closed\n * // → No SESSION_END event emitted\n * ```\n *\n * @see src/managers/README.md (lines 140-169) for session management details\n */\n stopTracking(): void {\n this.resetSessionState();\n }\n\n /**\n * Destroys the session manager and cleans up all resources.\n *\n * **Purpose**: Performs deep cleanup of session manager resources during\n * application teardown. Preserves session in localStorage for recovery.\n *\n * **Differences from stopTracking()**:\n * - Does NOT clear localStorage (preserves session for recovery)\n * - Used for internal cleanup during teardown\n *\n * **Cleanup Flow**:\n * 1. Clears inactivity timeout\n * 2. Removes activity listeners (click, keydown, scroll)\n * 3. Closes BroadcastChannel\n * 4. Removes lifecycle listeners (visibilitychange)\n * 5. Resets tracking flag (`isTracking`)\n *\n * **Called by**: `App.destroy()` during application teardown\n *\n * @returns void\n *\n * @example\n * ```typescript\n * sessionManager.destroy();\n * // → All resources cleaned up\n * // → Session preserved in localStorage for recovery\n * ```\n */\n destroy(): void {\n this.clearSessionTimeout();\n this.cleanupActivityListeners();\n this.cleanupCrossTabSync();\n this.cleanupLifecycleListeners();\n this.isTracking = false;\n this.needsRenewal = false;\n this.set('hasStartSession', false);\n }\n}\n","import { EventManager } from '../managers/event.manager';\nimport { SessionManager } from '../managers/session.manager';\nimport { StateManager } from '../managers/state.manager';\nimport { StorageManager } from '../managers/storage.manager';\nimport { log } from '../utils';\n\n/**\n * Wrapper around SessionManager providing consistent handler interface with robust error handling.\n *\n * **Purpose**: Manages user session lifecycle through delegation to SessionManager,\n * adding error recovery, state validation, and event buffer flushing.\n *\n * **Core Functionality**:\n * - Extracts projectId from config (tracelog/custom integrations or 'default')\n * - Creates SessionManager with storage, event manager, and projectId\n * - Flushes pending events after successful session initialization\n * - Automatic cleanup on initialization failures with nested try-catch\n *\n * **Key Features**:\n * - Idempotent operations (safe to call startTracking() multiple times)\n * - Double-destroy protection via destroyed flag\n * - State validation prevents operations on destroyed instances\n * - Centralized cleanup via cleanupSessionManager()\n *\n * **Lifecycle**:\n * - startTracking(): Creates session, sends SESSION_START event\n * - stopTracking(): Cleans up session tracking (no events emitted)\n * - destroy(): Same as stopTracking() (no events emitted in v2.0.0+)\n *\n * @example\n * ```typescript\n * const handler = new SessionHandler(storage, eventManager);\n * handler.startTracking(); // Creates session\n * handler.stopTracking(); // Ends session + cleanup\n * handler.destroy(); // Cleanup only\n * ```\n */\nexport class SessionHandler extends StateManager {\n private readonly eventManager: EventManager;\n private readonly storageManager: StorageManager;\n private sessionManager: SessionManager | null = null;\n private destroyed = false;\n\n constructor(storageManager: StorageManager, eventManager: EventManager) {\n super();\n this.eventManager = eventManager;\n this.storageManager = storageManager;\n }\n\n /**\n * Starts session tracking by creating SessionManager and initializing session.\n *\n * **Behavior**:\n * - Extracts projectId from config (tracelog projectId or custom collectApiUrl or 'default')\n * - Creates SessionManager instance with storage, event manager, and projectId\n * - Calls SessionManager.startTracking() to begin session lifecycle\n * - Flushes pending events buffered during initialization\n * - Idempotent: Early return if session already active\n * - Validates state: Warns and returns if handler destroyed\n *\n * **Error Handling**:\n * - On failure: Automatically cleans up SessionManager via nested try-catch\n * - Leaves handler in clean, reusable state after error\n * - Re-throws error after logging\n *\n * @throws {Error} If SessionManager initialization fails\n */\n startTracking(): void {\n if (this.isActive()) {\n return;\n }\n\n if (this.destroyed) {\n log('debug', 'Cannot start tracking on destroyed handler');\n return;\n }\n\n const config = this.get('config');\n const projectId = config?.integrations?.tracelog?.projectId ?? 'custom';\n\n try {\n this.sessionManager = new SessionManager(this.storageManager, this.eventManager, projectId);\n this.sessionManager.startTracking();\n\n this.eventManager.flushPendingEvents();\n } catch (error) {\n if (this.sessionManager) {\n try {\n this.sessionManager.destroy();\n } catch {\n /* empty */\n }\n this.sessionManager = null;\n }\n\n log('error', 'Failed to start session tracking', { error });\n throw error;\n }\n }\n\n private isActive(): boolean {\n return this.sessionManager !== null && !this.destroyed;\n }\n\n private cleanupSessionManager(): void {\n if (this.sessionManager) {\n this.sessionManager.stopTracking();\n this.sessionManager.destroy();\n this.sessionManager = null;\n }\n }\n\n /**\n * Stops session tracking by cleaning up resources.\n *\n * **Purpose**: Terminates session tracking and removes all listeners and timers.\n * No events are emitted (SESSION_END removed in v2.0.0).\n *\n * **Behavior**:\n * - Calls SessionManager.stopTracking() to clean up listeners\n * - Calls SessionManager.destroy() to finalize cleanup\n * - Safe to call multiple times (idempotent via cleanupSessionManager)\n *\n * **Note**: In v2.0.0+, this method only performs cleanup without emitting events.\n * Session end time is inferred server-side from last event timestamp.\n */\n stopTracking(): void {\n this.cleanupSessionManager();\n }\n\n /**\n * Destroys handler and cleans up SessionManager.\n *\n * **Purpose**: Same as stopTracking() in v2.0.0+. Both methods perform cleanup\n * without emitting events.\n *\n * **Behavior**:\n * - Idempotent: Early return if already destroyed\n * - Calls SessionManager.destroy() to clean up listeners and timers\n * - Sets sessionManager to null and destroyed flag to true\n *\n * **Note**: In v2.0.0+, there is no functional difference between stopTracking()\n * and destroy(). Both perform cleanup without emitting SESSION_END events.\n */\n destroy(): void {\n if (this.destroyed) {\n return;\n }\n\n if (this.sessionManager) {\n this.sessionManager.destroy();\n this.sessionManager = null;\n }\n\n this.destroyed = true;\n }\n}\n","import { EventType, PageViewData } from '../types';\nimport { normalizeUrl } from '../utils';\nimport { StateManager } from '../managers/state.manager';\nimport { EventManager } from '../managers/event.manager';\nimport { DEFAULT_PAGE_VIEW_THROTTLE_MS } from '../constants/config.constants';\n\n/**\n * Tracks page navigation and route changes in single-page applications.\n *\n * **Events Generated**: `page_view`\n *\n * **Features**:\n * - Tracks initial page load, browser navigation, hash changes, and History API calls\n * - URL normalization with automatic filtering of sensitive query parameters\n * - Deduplication to prevent consecutive duplicate events\n * - Configurable throttling (default: 1 second)\n * - SPA navigation detection via History API patching\n *\n * **Privacy Protection**:\n * - Automatically removes 15 common sensitive params (token, auth, key, password, etc.)\n * - User-configurable additional sensitive parameters via config\n *\n * @see src/handlers/README.md (lines 5-63) for detailed documentation\n */\nexport class PageViewHandler extends StateManager {\n private readonly eventManager: EventManager;\n private readonly onTrack: () => void;\n\n private originalPushState?: typeof window.history.pushState;\n private originalReplaceState?: typeof window.history.replaceState;\n private lastPageViewTime = 0;\n\n constructor(eventManager: EventManager, onTrack: () => void) {\n super();\n\n this.eventManager = eventManager;\n this.onTrack = onTrack;\n }\n\n /**\n * Starts tracking page views.\n *\n * - Tracks initial page load first (via trackInitialPageView)\n * - Attaches popstate and hashchange event listeners\n * - Patches History API methods (pushState, replaceState) for SPA navigation\n * - All setup happens synchronously\n *\n * **Note**: onTrack() callback is invoked AFTER initial page view but BEFORE\n * subsequent navigation events for session management coordination.\n */\n startTracking(): void {\n this.trackInitialPageView();\n\n window.addEventListener('popstate', this.trackCurrentPage, true);\n window.addEventListener('hashchange', this.trackCurrentPage, true);\n\n this.patchHistory('pushState');\n this.patchHistory('replaceState');\n }\n\n /**\n * Stops tracking page views and restores original History API methods.\n *\n * - Removes event listeners (popstate, hashchange)\n * - Restores original pushState and replaceState methods\n * - Resets throttling state\n */\n stopTracking(): void {\n window.removeEventListener('popstate', this.trackCurrentPage, true);\n window.removeEventListener('hashchange', this.trackCurrentPage, true);\n\n if (this.originalPushState) {\n window.history.pushState = this.originalPushState;\n }\n\n if (this.originalReplaceState) {\n window.history.replaceState = this.originalReplaceState;\n }\n\n this.lastPageViewTime = 0;\n }\n\n private patchHistory(method: 'pushState' | 'replaceState'): void {\n const original = window.history[method];\n\n if (method === 'pushState' && !this.originalPushState) {\n this.originalPushState = original;\n } else if (method === 'replaceState' && !this.originalReplaceState) {\n this.originalReplaceState = original;\n }\n\n window.history[method] = (...args: [unknown, string, string | URL | null | undefined]): void => {\n original.apply(window.history, args);\n this.trackCurrentPage();\n };\n }\n\n private readonly trackCurrentPage = (): void => {\n const rawUrl = window.location.href;\n const normalizedUrl = normalizeUrl(rawUrl, this.get('config').sensitiveQueryParams);\n\n if (this.get('pageUrl') === normalizedUrl) {\n return;\n }\n\n const now = Date.now();\n const throttleMs = this.get('config').pageViewThrottleMs ?? DEFAULT_PAGE_VIEW_THROTTLE_MS;\n\n if (now - this.lastPageViewTime < throttleMs) {\n return;\n }\n\n this.lastPageViewTime = now;\n\n this.onTrack();\n\n const fromUrl = this.get('pageUrl');\n\n this.set('pageUrl', normalizedUrl);\n\n const pageViewData = this.extractPageViewData();\n this.eventManager.track({\n type: EventType.PAGE_VIEW,\n page_url: this.get('pageUrl'),\n from_page_url: fromUrl,\n ...(pageViewData && { page_view: pageViewData }),\n });\n };\n\n private trackInitialPageView(): void {\n const normalizedUrl = normalizeUrl(window.location.href, this.get('config').sensitiveQueryParams);\n const pageViewData = this.extractPageViewData();\n\n this.lastPageViewTime = Date.now();\n\n this.eventManager.track({\n type: EventType.PAGE_VIEW,\n page_url: normalizedUrl,\n ...(pageViewData && { page_view: pageViewData }),\n });\n\n this.onTrack();\n }\n\n private extractPageViewData(): PageViewData | undefined {\n const { pathname, search, hash } = window.location;\n const { referrer } = document;\n const { title } = document;\n\n if (!referrer && !title && !pathname && !search && !hash) {\n return undefined;\n }\n\n const data: PageViewData = {\n ...(referrer && { referrer }),\n ...(title && { title }),\n ...(pathname && { pathname }),\n ...(search && { search }),\n ...(hash && { hash }),\n };\n\n return data;\n }\n}\n","import {\n HTML_DATA_ATTR_PREFIX,\n MAX_TEXT_LENGTH,\n INTERACTIVE_SELECTORS,\n PII_PATTERNS,\n DEFAULT_CLICK_THROTTLE_MS,\n MAX_THROTTLE_CACHE_ENTRIES,\n THROTTLE_ENTRY_TTL_MS,\n THROTTLE_PRUNE_INTERVAL_MS,\n} from '../constants';\nimport { ClickCoordinates, ClickData, ClickTrackingElementData, EventType } from '../types';\nimport { EventManager } from '../managers/event.manager';\nimport { StateManager } from '../managers/state.manager';\nimport { log } from '../utils';\n\n/**\n * Captures mouse clicks and converts them into analytics events with element context and coordinates.\n *\n * **Features**:\n * - Smart element detection via INTERACTIVE_SELECTORS (29 selectors including buttons, links, form elements, ARIA roles)\n * - Relative coordinates calculation (0-1 scale, 3 decimal precision, clamped)\n * - Custom event tracking via `data-tlog-name` attributes\n * - Text extraction with length limits (255 chars max) and priority logic\n * - PII sanitization (emails, phone numbers, credit cards, API keys, tokens)\n * - Privacy controls via `data-tlog-ignore` attribute\n * - Per-element click throttling (default 300ms) with memory management (TTL + LRU)\n *\n * **Events Generated**: `click`, `custom` (for elements with data-tlog-name)\n *\n * **Triggers**: Capture-phase click events on document\n *\n * **Memory Management**:\n * - TTL-based pruning: 5-minute TTL with automatic cleanup\n * - LRU eviction: Maintains maximum 1000 throttle entries\n * - Rate-limited pruning: Runs every 30 seconds\n *\n * @example\n * ```typescript\n * const handler = new ClickHandler(eventManager);\n * handler.startTracking();\n * // Clicks are now tracked automatically\n * handler.stopTracking();\n * ```\n */\nexport class ClickHandler extends StateManager {\n private readonly eventManager: EventManager;\n private readonly lastClickTimes: Map<string, number> = new Map();\n private clickHandler?: (event: Event) => void;\n private lastPruneTime = 0;\n\n constructor(eventManager: EventManager) {\n super();\n\n this.eventManager = eventManager;\n }\n\n /**\n * Starts tracking click events on the document.\n *\n * Attaches a single capture-phase click listener to window that:\n * - Detects interactive elements or falls back to clicked element\n * - Applies click throttling per element (configurable, default 300ms)\n * - Extracts custom tracking data from data-tlog-name attributes\n * - Generates both custom events (for tracked elements) and click events\n * - Respects data-tlog-ignore privacy controls\n * - Sanitizes text content for PII protection\n *\n * Idempotent: Safe to call multiple times (early return if already tracking).\n */\n startTracking(): void {\n if (this.clickHandler) {\n return;\n }\n\n this.clickHandler = (event: Event): void => {\n const mouseEvent = event as MouseEvent;\n const target = mouseEvent.target;\n const clickedElement =\n typeof HTMLElement !== 'undefined' && target instanceof HTMLElement\n ? target\n : typeof HTMLElement !== 'undefined' && target instanceof Node && target.parentElement instanceof HTMLElement\n ? target.parentElement\n : null;\n\n if (!clickedElement) {\n log('debug', 'Click target not found or not an element');\n return;\n }\n\n if (this.shouldIgnoreElement(clickedElement)) {\n return;\n }\n\n // Throttle clicks per element to prevent double-clicks and spam\n const clickThrottleMs = this.get('config')?.clickThrottleMs ?? DEFAULT_CLICK_THROTTLE_MS;\n if (clickThrottleMs > 0 && !this.checkClickThrottle(clickedElement, clickThrottleMs)) {\n return;\n }\n\n const trackingElement = this.findTrackingElement(clickedElement);\n const relevantClickElement = this.getRelevantClickElement(clickedElement);\n const coordinates = this.calculateClickCoordinates(mouseEvent, clickedElement);\n\n if (trackingElement) {\n const trackingData = this.extractTrackingData(trackingElement);\n\n if (trackingData) {\n const attributeData = this.createCustomEventData(trackingData);\n\n this.eventManager.track({\n type: EventType.CUSTOM,\n custom_event: {\n name: attributeData.name,\n ...(attributeData.value && { metadata: { value: attributeData.value } }),\n },\n });\n }\n }\n\n const clickData = this.generateClickData(clickedElement, relevantClickElement, coordinates);\n\n this.eventManager.track({\n type: EventType.CLICK,\n click_data: clickData,\n });\n };\n\n window.addEventListener('click', this.clickHandler, true);\n }\n\n /**\n * Stops tracking click events and cleans up resources.\n *\n * Removes the click event listener, clears throttle cache, and resets prune timer.\n * Prevents memory leaks by properly cleaning up all state.\n */\n stopTracking(): void {\n if (this.clickHandler) {\n window.removeEventListener('click', this.clickHandler, true);\n this.clickHandler = undefined;\n }\n this.lastClickTimes.clear();\n this.lastPruneTime = 0;\n }\n\n private shouldIgnoreElement(element: HTMLElement): boolean {\n if (element.hasAttribute(`${HTML_DATA_ATTR_PREFIX}-ignore`)) {\n return true;\n }\n\n const parent = element.closest(`[${HTML_DATA_ATTR_PREFIX}-ignore]`);\n\n return parent !== null;\n }\n\n /**\n * Checks per-element click throttling to prevent double-clicks and rapid spam\n * Returns true if the click should be tracked, false if throttled\n */\n private checkClickThrottle(element: HTMLElement, throttleMs: number): boolean {\n const signature = this.getElementSignature(element);\n const now = Date.now();\n\n this.pruneThrottleCache(now);\n\n const lastClickTime = this.lastClickTimes.get(signature);\n\n if (lastClickTime !== undefined && now - lastClickTime < throttleMs) {\n log('debug', 'ClickHandler: Click suppressed by throttle', {\n data: {\n signature,\n throttleRemaining: throttleMs - (now - lastClickTime),\n },\n });\n return false;\n }\n\n this.lastClickTimes.set(signature, now);\n return true;\n }\n\n /**\n * Prunes stale entries from the throttle cache to prevent memory leaks\n * Uses TTL-based eviction (5 minutes) and enforces max size limit\n * Called during checkClickThrottle with built-in rate limiting (every 30 seconds)\n */\n private pruneThrottleCache(now: number): void {\n if (now - this.lastPruneTime < THROTTLE_PRUNE_INTERVAL_MS) {\n return;\n }\n\n this.lastPruneTime = now;\n const cutoff = now - THROTTLE_ENTRY_TTL_MS;\n\n for (const [key, timestamp] of this.lastClickTimes.entries()) {\n if (timestamp < cutoff) {\n this.lastClickTimes.delete(key);\n }\n }\n\n if (this.lastClickTimes.size > MAX_THROTTLE_CACHE_ENTRIES) {\n const entries = Array.from(this.lastClickTimes.entries()).sort((a, b) => a[1] - b[1]);\n\n const excessCount = this.lastClickTimes.size - MAX_THROTTLE_CACHE_ENTRIES;\n const toDelete = entries.slice(0, excessCount);\n\n for (const [key] of toDelete) {\n this.lastClickTimes.delete(key);\n }\n\n log('debug', 'ClickHandler: Pruned throttle cache', {\n data: {\n removed: toDelete.length,\n remaining: this.lastClickTimes.size,\n },\n });\n }\n }\n\n /**\n * Creates a stable signature for an element to track throttling\n * Priority: id > data-testid > data-tlog-name > DOM path\n */\n private getElementSignature(element: HTMLElement): string {\n if (element.id) {\n return `#${element.id}`;\n }\n\n const testId = element.getAttribute('data-testid');\n if (testId) {\n return `[data-testid=\"${testId}\"]`;\n }\n\n const tlogName = element.getAttribute(`${HTML_DATA_ATTR_PREFIX}-name`);\n if (tlogName) {\n return `[${HTML_DATA_ATTR_PREFIX}-name=\"${tlogName}\"]`;\n }\n\n return this.getElementPath(element);\n }\n\n /**\n * Generates a DOM path for an element (e.g., \"body>div>button\")\n */\n private getElementPath(element: HTMLElement): string {\n const path: string[] = [];\n let current: HTMLElement | null = element;\n\n while (current && current !== document.body) {\n let selector = current.tagName.toLowerCase();\n\n if (current.className) {\n const firstClass = current.className.split(' ')[0];\n if (firstClass) {\n selector += `.${firstClass}`;\n }\n }\n\n path.unshift(selector);\n current = current.parentElement;\n }\n\n return path.join('>') || 'unknown';\n }\n\n private findTrackingElement(element: HTMLElement): HTMLElement | undefined {\n if (element.hasAttribute(`${HTML_DATA_ATTR_PREFIX}-name`)) {\n return element;\n }\n\n const closest = element.closest(`[${HTML_DATA_ATTR_PREFIX}-name]`) as HTMLElement;\n\n return closest;\n }\n\n private getRelevantClickElement(element: HTMLElement): HTMLElement {\n for (const selector of INTERACTIVE_SELECTORS) {\n try {\n if (element.matches(selector)) {\n return element;\n }\n\n const parent = element.closest(selector) as HTMLElement;\n\n if (parent) {\n return parent;\n }\n } catch (error) {\n log('debug', 'Invalid selector in element search', { error, data: { selector } });\n continue;\n }\n }\n\n return element;\n }\n\n /**\n * Clamps relative coordinate values to [0, 1] range with 3 decimal precision.\n *\n * @param value - Raw relative coordinate value\n * @returns Clamped value between 0 and 1 with 3 decimal places (e.g., 0.123)\n *\n * @example\n * clamp(1.234) // returns 1.000\n * clamp(0.12345) // returns 0.123\n * clamp(-0.5) // returns 0.000\n */\n private clamp(value: number): number {\n return Math.max(0, Math.min(1, Number(value.toFixed(3))));\n }\n\n private calculateClickCoordinates(event: MouseEvent, element: HTMLElement): ClickCoordinates {\n const rect = element.getBoundingClientRect();\n const x = event.clientX;\n const y = event.clientY;\n const relativeX = rect.width > 0 ? this.clamp((x - rect.left) / rect.width) : 0;\n const relativeY = rect.height > 0 ? this.clamp((y - rect.top) / rect.height) : 0;\n\n return { x, y, relativeX, relativeY };\n }\n\n private extractTrackingData(trackingElement: HTMLElement): ClickTrackingElementData | undefined {\n const name = trackingElement.getAttribute(`${HTML_DATA_ATTR_PREFIX}-name`);\n const value = trackingElement.getAttribute(`${HTML_DATA_ATTR_PREFIX}-value`);\n\n if (!name) {\n return undefined;\n }\n\n return {\n element: trackingElement,\n name,\n ...(value && { value }),\n };\n }\n\n private generateClickData(\n clickedElement: HTMLElement,\n relevantElement: HTMLElement,\n coordinates: ClickCoordinates,\n ): ClickData {\n const { x, y, relativeX, relativeY } = coordinates;\n const text = this.getRelevantText(clickedElement, relevantElement);\n const attributes = this.extractElementAttributes(relevantElement);\n\n return {\n x,\n y,\n relativeX,\n relativeY,\n tag: relevantElement.tagName.toLowerCase(),\n ...(relevantElement.id && { id: relevantElement.id }),\n ...(relevantElement.className && { class: relevantElement.className }),\n ...(text && { text }),\n ...(attributes.href && { href: attributes.href }),\n ...(attributes.title && { title: attributes.title }),\n ...(attributes.alt && { alt: attributes.alt }),\n ...(attributes.role && { role: attributes.role }),\n ...(attributes['aria-label'] && { ariaLabel: attributes['aria-label'] }),\n ...(Object.keys(attributes).length > 0 && { dataAttributes: attributes }),\n };\n }\n\n /**\n * Sanitizes text by replacing PII patterns with [REDACTED].\n *\n * Protects against:\n * - Email addresses\n * - Phone numbers (US format)\n * - Credit card numbers\n * - IBAN numbers\n * - API keys/tokens\n * - Bearer tokens\n *\n * @param text - Raw text content from element\n * @returns Sanitized text with PII replaced by [REDACTED]\n *\n * @example\n * sanitizeText('Email: user@example.com') // returns 'Email: [REDACTED]'\n * sanitizeText('Card: 1234-5678-9012-3456') // returns 'Card: [REDACTED]'\n * sanitizeText('Bearer token123') // returns '[REDACTED]'\n */\n private sanitizeText(text: string): string {\n let sanitized = text;\n\n for (const pattern of PII_PATTERNS) {\n const regex = new RegExp(pattern.source, pattern.flags);\n sanitized = sanitized.replace(regex, '[REDACTED]');\n }\n\n return sanitized;\n }\n\n private getRelevantText(clickedElement: HTMLElement, relevantElement: HTMLElement): string {\n const clickedText = clickedElement.textContent?.trim() ?? '';\n const relevantText = relevantElement.textContent?.trim() ?? '';\n\n if (!clickedText && !relevantText) {\n return '';\n }\n\n let finalText = '';\n\n if (clickedText && clickedText.length <= MAX_TEXT_LENGTH) {\n finalText = clickedText;\n } else if (relevantText.length <= MAX_TEXT_LENGTH) {\n finalText = relevantText;\n } else {\n finalText = relevantText.slice(0, MAX_TEXT_LENGTH - 3) + '...';\n }\n\n return this.sanitizeText(finalText);\n }\n\n private extractElementAttributes(element: HTMLElement): Record<string, string> {\n const commonAttributes = [\n 'id',\n 'class',\n 'data-testid',\n 'aria-label',\n 'title',\n 'href',\n 'type',\n 'name',\n 'alt',\n 'role',\n ];\n const result: Record<string, string> = {};\n\n for (const attributeName of commonAttributes) {\n const value = element.getAttribute(attributeName);\n\n if (value) {\n result[attributeName] = value;\n }\n }\n\n return result;\n }\n\n private createCustomEventData(trackingData: ClickTrackingElementData): { name: string; value?: string } {\n return {\n name: trackingData.name,\n ...(trackingData.value && { value: trackingData.value }),\n };\n }\n}\n","import {\n MAX_SCROLL_EVENTS_PER_SESSION,\n MIN_SCROLL_DEPTH_CHANGE,\n SCROLL_DEBOUNCE_TIME_MS,\n SCROLL_MIN_EVENT_INTERVAL_MS,\n SIGNIFICANT_SCROLL_DELTA,\n} from '../constants';\nimport { EventType, ScrollData, ScrollDirection } from '../types';\nimport { EventManager } from '../managers/event.manager';\nimport { StateManager } from '../managers/state.manager';\nimport { log } from '../utils';\n\ninterface ScrollContainer {\n element: Window | HTMLElement;\n selector: string;\n isPrimary: boolean;\n lastScrollPos: number;\n lastDepth: number;\n lastDirection: ScrollDirection;\n lastEventTime: number;\n firstScrollEventTime: number | null;\n maxDepthReached: number;\n debounceTimer: number | null;\n listener: EventListener;\n}\n\n/**\n * Tracks scroll depth, direction, velocity, and container identification across multiple scrollable elements.\n *\n * **Features**:\n * - Automatic container detection with intelligent retry (5 attempts @ 200ms intervals)\n * - Manual override via primaryScrollSelector config\n * - Smart filtering with multiple guardrails:\n * - Visibility check (element must be connected to DOM with dimensions)\n * - Scrollability check (content must overflow container)\n * - Significant movement (minimum 10px position delta)\n * - Depth change (minimum 5% depth change between events)\n * - Rate limiting (minimum 500ms interval between events per container)\n * - Session cap (maximum 120 events per session with single warning)\n * - Multi-container support with per-container debouncing (250ms)\n * - Velocity calculation for engagement analysis\n * - Max depth tracking per session\n * - Primary vs secondary container classification\n *\n * **Events Generated**: `scroll`\n *\n * **Analytics Fields**:\n * - depth: Current scroll depth (0-100%)\n * - direction: 'up' | 'down'\n * - container_selector: CSS selector or 'window'\n * - is_primary: Boolean indicating main scroll container\n * - velocity: Scroll speed in px/s\n * - max_depth_reached: Peak engagement per container\n *\n * **Container Detection**:\n * - Uses TreeWalker for performance\n * - Pre-filters elements with overflow: auto/scroll CSS properties\n * - Validates visibility and scrollability\n * - Retries up to 5 times for dynamically loaded content (SPAs)\n * - Falls back to window-only if no containers found\n *\n * @example\n * ```typescript\n * const handler = new ScrollHandler(eventManager);\n * handler.startTracking();\n * // Automatically detects and tracks scrollable containers\n * handler.stopTracking();\n * ```\n */\nexport class ScrollHandler extends StateManager {\n private readonly eventManager: EventManager;\n private readonly containers: ScrollContainer[] = [];\n private limitWarningLogged = false;\n private minDepthChange = MIN_SCROLL_DEPTH_CHANGE;\n private minIntervalMs = SCROLL_MIN_EVENT_INTERVAL_MS;\n private maxEventsPerSession = MAX_SCROLL_EVENTS_PER_SESSION;\n private containerDiscoveryTimeoutId: number | null = null;\n\n constructor(eventManager: EventManager) {\n super();\n\n this.eventManager = eventManager;\n }\n\n /**\n * Starts tracking scroll events across all detected scrollable containers.\n *\n * Automatically detects scrollable containers using TreeWalker with retry logic:\n * - Searches DOM for elements with overflow: auto/scroll\n * - Validates visibility and scrollability\n * - Retries up to 5 times with 200ms intervals for dynamic content\n * - Falls back to window-only tracking if no containers found\n * - Applies primaryScrollSelector config override if provided\n *\n * Attaches debounced scroll listeners (250ms per container) with smart filtering:\n * - Significant movement (10px minimum)\n * - Depth change (5% minimum)\n * - Rate limiting (500ms minimum interval)\n * - Session cap (120 events maximum)\n */\n startTracking(): void {\n this.limitWarningLogged = false;\n this.applyConfigOverrides();\n this.set('scrollEventCount', 0);\n this.tryDetectScrollContainers(0);\n }\n\n /**\n * Stops tracking scroll events and cleans up resources.\n *\n * Removes all scroll event listeners, clears debounce timers, cancels retry attempts,\n * and resets session state (event counter, warning flags). Prevents memory leaks by\n * properly cleaning up all containers and timers.\n */\n stopTracking(): void {\n if (this.containerDiscoveryTimeoutId !== null) {\n clearTimeout(this.containerDiscoveryTimeoutId);\n this.containerDiscoveryTimeoutId = null;\n }\n\n for (const container of this.containers) {\n this.clearContainerTimer(container);\n\n if (container.element === window) {\n window.removeEventListener('scroll', container.listener);\n } else {\n (container.element as HTMLElement).removeEventListener('scroll', container.listener);\n }\n }\n\n this.containers.length = 0;\n this.set('scrollEventCount', 0);\n this.limitWarningLogged = false;\n }\n\n private tryDetectScrollContainers(attempt: number): void {\n const elements = this.findScrollableElements();\n\n if (this.isWindowScrollable()) {\n this.setupScrollContainer(window, 'window');\n }\n\n if (elements.length > 0) {\n for (const element of elements) {\n const selector = this.getElementSelector(element);\n this.setupScrollContainer(element, selector);\n }\n\n this.applyPrimaryScrollSelectorIfConfigured();\n\n return;\n }\n\n if (attempt < 5) {\n this.containerDiscoveryTimeoutId = window.setTimeout(() => {\n this.containerDiscoveryTimeoutId = null;\n this.tryDetectScrollContainers(attempt + 1);\n }, 200);\n\n return;\n }\n\n if (this.containers.length === 0) {\n this.setupScrollContainer(window, 'window');\n }\n\n this.applyPrimaryScrollSelectorIfConfigured();\n }\n\n private applyPrimaryScrollSelectorIfConfigured(): void {\n const config = this.get('config');\n\n if (config?.primaryScrollSelector) {\n this.applyPrimaryScrollSelector(config.primaryScrollSelector);\n }\n }\n\n private findScrollableElements(): HTMLElement[] {\n if (!document.body) {\n return [];\n }\n\n const elements: HTMLElement[] = [];\n\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, {\n acceptNode: (node) => {\n const element = node as HTMLElement;\n\n if (!element.isConnected || !element.offsetParent) {\n return NodeFilter.FILTER_SKIP;\n }\n\n const style = getComputedStyle(element);\n\n const hasVerticalScrollableStyle =\n style.overflowY === 'auto' ||\n style.overflowY === 'scroll' ||\n style.overflow === 'auto' ||\n style.overflow === 'scroll';\n\n return hasVerticalScrollableStyle ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;\n },\n });\n\n let node: Node | null;\n\n while ((node = walker.nextNode()) && elements.length < 10) {\n const element = node as HTMLElement;\n\n if (this.isElementScrollable(element)) {\n elements.push(element);\n }\n }\n\n return elements;\n }\n\n private getElementSelector(element: Window | HTMLElement): string {\n if (element === window) {\n return 'window';\n }\n\n const htmlElement = element as HTMLElement;\n\n if (htmlElement.id) {\n return `#${htmlElement.id}`;\n }\n\n if (htmlElement.className && typeof htmlElement.className === 'string') {\n const firstClass = htmlElement.className.split(' ').filter((c) => c.trim())[0];\n\n if (firstClass) {\n return `.${firstClass}`;\n }\n }\n\n return htmlElement.tagName.toLowerCase();\n }\n\n private determineIfPrimary(element: Window | HTMLElement): boolean {\n // Window scrollable → window is primary\n if (this.isWindowScrollable()) {\n return element === window;\n }\n\n // Window not scrollable → first detected container is primary\n return this.containers.length === 0;\n }\n\n private setupScrollContainer(element: Window | HTMLElement, selector: string): void {\n const alreadyTracking = this.containers.some((c) => c.element === element);\n\n if (alreadyTracking) {\n return;\n }\n\n if (element !== window && !this.isElementScrollable(element as HTMLElement)) {\n return;\n }\n\n const initialScrollTop = this.getScrollTop(element);\n\n const initialDepth = this.calculateScrollDepth(\n initialScrollTop,\n this.getScrollHeight(element),\n this.getViewportHeight(element),\n );\n\n const isPrimary = this.determineIfPrimary(element);\n\n const container: ScrollContainer = {\n element,\n selector,\n isPrimary,\n lastScrollPos: initialScrollTop,\n lastDepth: initialDepth,\n lastDirection: ScrollDirection.DOWN,\n lastEventTime: 0,\n firstScrollEventTime: null,\n maxDepthReached: initialDepth,\n debounceTimer: null,\n listener: null as unknown as EventListener,\n };\n\n const handleScroll = (): void => {\n if (this.get('suppressNextScroll')) {\n return;\n }\n\n if (container.firstScrollEventTime === null) {\n container.firstScrollEventTime = Date.now();\n }\n\n this.clearContainerTimer(container);\n\n container.debounceTimer = window.setTimeout(() => {\n const scrollData = this.calculateScrollData(container);\n\n if (scrollData) {\n const now = Date.now();\n\n this.processScrollEvent(container, scrollData, now);\n }\n\n container.debounceTimer = null;\n }, SCROLL_DEBOUNCE_TIME_MS);\n };\n\n container.listener = handleScroll;\n\n this.containers.push(container);\n\n if (element === window) {\n window.addEventListener('scroll', handleScroll, { passive: true });\n } else {\n (element as HTMLElement).addEventListener('scroll', handleScroll, { passive: true });\n }\n }\n\n private processScrollEvent(\n container: ScrollContainer,\n scrollData: Omit<ScrollData, 'container_selector' | 'is_primary'>,\n timestamp: number,\n ): void {\n if (!this.shouldEmitScrollEvent(container, scrollData, timestamp)) {\n return;\n }\n\n container.lastEventTime = timestamp;\n container.lastDepth = scrollData.depth;\n container.lastDirection = scrollData.direction;\n\n const currentCount = this.get('scrollEventCount') ?? 0;\n this.set('scrollEventCount', currentCount + 1);\n\n this.eventManager.track({\n type: EventType.SCROLL,\n scroll_data: {\n ...scrollData,\n container_selector: container.selector,\n is_primary: container.isPrimary,\n },\n });\n }\n\n private shouldEmitScrollEvent(\n container: ScrollContainer,\n scrollData: Omit<ScrollData, 'container_selector' | 'is_primary'>,\n timestamp: number,\n ): boolean {\n if (this.hasReachedSessionLimit()) {\n this.logLimitOnce();\n return false;\n }\n\n if (!this.hasElapsedMinimumInterval(container, timestamp)) {\n return false;\n }\n\n if (!this.hasSignificantDepthChange(container, scrollData.depth)) {\n return false;\n }\n\n return true;\n }\n\n private hasReachedSessionLimit(): boolean {\n const currentCount = this.get('scrollEventCount') ?? 0;\n return currentCount >= this.maxEventsPerSession;\n }\n\n private hasElapsedMinimumInterval(container: ScrollContainer, timestamp: number): boolean {\n if (container.lastEventTime === 0) {\n return true;\n }\n return timestamp - container.lastEventTime >= this.minIntervalMs;\n }\n\n private hasSignificantDepthChange(container: ScrollContainer, newDepth: number): boolean {\n return Math.abs(newDepth - container.lastDepth) >= this.minDepthChange;\n }\n\n private logLimitOnce(): void {\n if (this.limitWarningLogged) {\n return;\n }\n\n this.limitWarningLogged = true;\n\n log('debug', 'Max scroll events per session reached', {\n data: { limit: this.maxEventsPerSession },\n });\n }\n\n private applyConfigOverrides(): void {\n this.minDepthChange = MIN_SCROLL_DEPTH_CHANGE;\n this.minIntervalMs = SCROLL_MIN_EVENT_INTERVAL_MS;\n this.maxEventsPerSession = MAX_SCROLL_EVENTS_PER_SESSION;\n }\n\n private isWindowScrollable(): boolean {\n return document.documentElement.scrollHeight > window.innerHeight;\n }\n\n private clearContainerTimer(container: ScrollContainer): void {\n if (container.debounceTimer !== null) {\n clearTimeout(container.debounceTimer);\n container.debounceTimer = null;\n }\n }\n\n private getScrollDirection(current: number, previous: number): ScrollDirection {\n return current > previous ? ScrollDirection.DOWN : ScrollDirection.UP;\n }\n\n private calculateScrollDepth(scrollTop: number, scrollHeight: number, viewportHeight: number): number {\n if (scrollHeight <= viewportHeight) {\n return 0;\n }\n\n const maxScrollTop = scrollHeight - viewportHeight;\n return Math.min(100, Math.max(0, Math.floor((scrollTop / maxScrollTop) * 100)));\n }\n\n private calculateScrollData(\n container: ScrollContainer,\n ): Omit<ScrollData, 'container_selector' | 'is_primary'> | null {\n const { element, lastScrollPos, lastEventTime } = container;\n const scrollTop = this.getScrollTop(element);\n const now = Date.now();\n\n const positionDelta = Math.abs(scrollTop - lastScrollPos);\n if (positionDelta < SIGNIFICANT_SCROLL_DELTA) {\n return null;\n }\n\n if (element === window && !this.isWindowScrollable()) {\n return null;\n }\n\n const viewportHeight = this.getViewportHeight(element);\n const scrollHeight = this.getScrollHeight(element);\n const direction = this.getScrollDirection(scrollTop, lastScrollPos);\n const depth = this.calculateScrollDepth(scrollTop, scrollHeight, viewportHeight);\n\n let timeDelta: number;\n\n if (lastEventTime > 0) {\n timeDelta = now - lastEventTime;\n } else if (container.firstScrollEventTime !== null) {\n timeDelta = now - container.firstScrollEventTime;\n } else {\n timeDelta = SCROLL_DEBOUNCE_TIME_MS;\n }\n\n const velocity = Math.round((positionDelta / timeDelta) * 1000);\n\n if (depth > container.maxDepthReached) {\n container.maxDepthReached = depth;\n }\n\n container.lastScrollPos = scrollTop;\n\n return {\n depth,\n direction,\n velocity,\n max_depth_reached: container.maxDepthReached,\n };\n }\n\n private getScrollTop(element: Window | HTMLElement): number {\n return element === window ? window.scrollY : (element as HTMLElement).scrollTop;\n }\n\n private getViewportHeight(element: Window | HTMLElement): number {\n return element === window ? window.innerHeight : (element as HTMLElement).clientHeight;\n }\n\n private getScrollHeight(element: Window | HTMLElement): number {\n return element === window ? document.documentElement.scrollHeight : (element as HTMLElement).scrollHeight;\n }\n\n private isElementScrollable(element: HTMLElement): boolean {\n const style = getComputedStyle(element);\n\n const hasVerticalScrollableOverflow =\n style.overflowY === 'auto' ||\n style.overflowY === 'scroll' ||\n style.overflow === 'auto' ||\n style.overflow === 'scroll';\n\n const hasVerticalOverflowContent = element.scrollHeight > element.clientHeight;\n\n return hasVerticalScrollableOverflow && hasVerticalOverflowContent;\n }\n\n private applyPrimaryScrollSelector(selector: string): void {\n let targetElement: Window | HTMLElement;\n\n if (selector === 'window') {\n targetElement = window;\n } else {\n const element = document.querySelector(selector);\n if (!(element instanceof HTMLElement)) {\n log('debug', `Selector \"${selector}\" did not match an HTMLElement`);\n return;\n }\n targetElement = element;\n }\n\n this.containers.forEach((container) => {\n this.updateContainerPrimary(container, container.element === targetElement);\n });\n\n const targetAlreadyTracked = this.containers.some((c) => c.element === targetElement);\n if (!targetAlreadyTracked && targetElement instanceof HTMLElement) {\n if (this.isElementScrollable(targetElement)) {\n this.setupScrollContainer(targetElement, selector);\n }\n }\n }\n\n private updateContainerPrimary(container: ScrollContainer, isPrimary: boolean): void {\n container.isPrimary = isPrimary;\n }\n}\n","import {\n HTML_DATA_ATTR_PREFIX,\n DEFAULT_VIEWPORT_COOLDOWN_PERIOD,\n DEFAULT_VIEWPORT_MAX_TRACKED_ELEMENTS,\n VIEWPORT_MUTATION_DEBOUNCE_MS,\n} from '../constants';\nimport { EventManager } from '../managers/event.manager';\nimport { StateManager } from '../managers/state.manager';\nimport { EventType, ViewportConfig, ViewportEventData } from '../types';\nimport { log } from '../utils';\n\ninterface TrackedElement {\n element: Element;\n selector: string;\n id?: string;\n name?: string;\n startTime: number | null;\n timeoutId: number | null;\n lastFiredTime: number | null;\n}\n\n/**\n * Handles viewport visibility tracking using IntersectionObserver API.\n * Fires events when elements become visible for a minimum dwell time.\n */\nexport class ViewportHandler extends StateManager {\n private readonly eventManager: EventManager;\n private readonly trackedElements = new Map<Element, TrackedElement>();\n private observer: IntersectionObserver | null = null;\n private mutationObserver: MutationObserver | null = null;\n private mutationDebounceTimer: number | null = null;\n private config: ViewportConfig | null = null;\n\n constructor(eventManager: EventManager) {\n super();\n this.eventManager = eventManager;\n }\n\n /**\n * Starts tracking viewport visibility for configured elements\n */\n startTracking(): void {\n // Get viewport config from state\n const config = this.get('config');\n this.config = config.viewport ?? null;\n\n if (!this.config?.elements || this.config.elements.length === 0) {\n return;\n }\n\n const threshold = this.config.threshold ?? 0.5;\n const minDwellTime = this.config.minDwellTime ?? 1000;\n\n if (threshold < 0 || threshold > 1) {\n log('debug', 'ViewportHandler: Invalid threshold, must be between 0 and 1');\n return;\n }\n\n if (minDwellTime < 0) {\n log('debug', 'ViewportHandler: Invalid minDwellTime, must be non-negative');\n return;\n }\n\n if (typeof IntersectionObserver === 'undefined') {\n log('debug', 'ViewportHandler: IntersectionObserver not supported in this browser');\n return;\n }\n\n this.observer = new IntersectionObserver(this.handleIntersection, {\n threshold,\n });\n\n this.observeElements();\n\n this.setupMutationObserver();\n }\n\n /**\n * Stops tracking and cleans up resources\n */\n stopTracking(): void {\n if (this.observer) {\n this.observer.disconnect();\n this.observer = null;\n }\n\n if (this.mutationObserver) {\n this.mutationObserver.disconnect();\n this.mutationObserver = null;\n }\n\n if (this.mutationDebounceTimer !== null) {\n window.clearTimeout(this.mutationDebounceTimer);\n this.mutationDebounceTimer = null;\n }\n\n for (const tracked of this.trackedElements.values()) {\n if (tracked.timeoutId !== null) {\n window.clearTimeout(tracked.timeoutId);\n }\n }\n\n this.trackedElements.clear();\n }\n\n /**\n * Query and observe all elements matching configured elements\n */\n private observeElements(): void {\n if (!this.config || !this.observer) return;\n\n const maxTrackedElements = this.config.maxTrackedElements ?? DEFAULT_VIEWPORT_MAX_TRACKED_ELEMENTS;\n let totalTracked = this.trackedElements.size;\n\n for (const elementConfig of this.config.elements) {\n try {\n const elements = document.querySelectorAll(elementConfig.selector);\n\n for (const element of Array.from(elements)) {\n if (totalTracked >= maxTrackedElements) {\n log('debug', 'ViewportHandler: Maximum tracked elements reached', {\n data: {\n limit: maxTrackedElements,\n selector: elementConfig.selector,\n message: 'Some elements will not be tracked. Consider more specific selectors.',\n },\n });\n return;\n }\n\n if (element.hasAttribute(`${HTML_DATA_ATTR_PREFIX}-ignore`)) {\n continue;\n }\n\n if (this.trackedElements.has(element)) {\n continue;\n }\n\n this.trackedElements.set(element, {\n element,\n selector: elementConfig.selector,\n id: elementConfig.id,\n name: elementConfig.name,\n startTime: null,\n timeoutId: null,\n lastFiredTime: null,\n });\n\n this.observer?.observe(element);\n totalTracked++;\n }\n } catch (error) {\n log('debug', `ViewportHandler: Invalid selector \"${elementConfig.selector}\"`, { error });\n }\n }\n\n log('debug', 'ViewportHandler: Elements tracked', {\n data: { count: totalTracked, limit: maxTrackedElements },\n });\n }\n\n /**\n * Handles intersection events from IntersectionObserver\n */\n private readonly handleIntersection = (entries: IntersectionObserverEntry[]): void => {\n if (!this.config) return;\n\n const minDwellTime = this.config.minDwellTime ?? 1000;\n\n for (const entry of entries) {\n const tracked = this.trackedElements.get(entry.target);\n if (!tracked) continue;\n\n if (entry.isIntersecting) {\n if (tracked.startTime === null) {\n tracked.startTime = performance.now();\n\n tracked.timeoutId = window.setTimeout(() => {\n const visibilityRatio = Math.round(entry.intersectionRatio * 100) / 100;\n this.fireViewportEvent(tracked, visibilityRatio);\n }, minDwellTime);\n }\n } else {\n if (tracked.startTime !== null) {\n if (tracked.timeoutId !== null) {\n window.clearTimeout(tracked.timeoutId);\n tracked.timeoutId = null;\n }\n tracked.startTime = null;\n }\n }\n }\n };\n\n /**\n * Fires a viewport visible event\n */\n private fireViewportEvent(tracked: TrackedElement, visibilityRatio: number): void {\n if (tracked.startTime === null) return;\n\n const dwellTime = Math.round(performance.now() - tracked.startTime);\n\n if (tracked.element.hasAttribute(`${HTML_DATA_ATTR_PREFIX}-ignore`)) {\n return;\n }\n\n const cooldownPeriod = this.config?.cooldownPeriod ?? DEFAULT_VIEWPORT_COOLDOWN_PERIOD;\n const now = Date.now();\n if (tracked.lastFiredTime !== null && now - tracked.lastFiredTime < cooldownPeriod) {\n log('debug', 'ViewportHandler: Event suppressed by cooldown period', {\n data: {\n selector: tracked.selector,\n cooldownRemaining: cooldownPeriod - (now - tracked.lastFiredTime),\n },\n });\n tracked.startTime = null;\n tracked.timeoutId = null;\n return;\n }\n\n const eventData: ViewportEventData = {\n selector: tracked.selector,\n dwellTime,\n visibilityRatio,\n ...(tracked.id !== undefined && { id: tracked.id }),\n ...(tracked.name !== undefined && { name: tracked.name }),\n };\n\n this.eventManager.track({\n type: EventType.VIEWPORT_VISIBLE,\n viewport_data: eventData,\n });\n\n tracked.startTime = null;\n tracked.timeoutId = null;\n tracked.lastFiredTime = now;\n }\n\n /**\n * Sets up MutationObserver to detect dynamically added elements\n */\n private setupMutationObserver(): void {\n if (!this.config || typeof MutationObserver === 'undefined') {\n return;\n }\n\n if (!document.body) {\n log('debug', 'ViewportHandler: document.body not available, skipping MutationObserver setup');\n return;\n }\n\n this.mutationObserver = new MutationObserver((mutations) => {\n let hasAddedNodes = false;\n\n for (const mutation of mutations) {\n if (mutation.type === 'childList') {\n if (mutation.addedNodes.length > 0) {\n hasAddedNodes = true;\n }\n if (mutation.removedNodes.length > 0) {\n this.cleanupRemovedNodes(mutation.removedNodes);\n }\n }\n }\n\n if (hasAddedNodes) {\n if (this.mutationDebounceTimer !== null) {\n window.clearTimeout(this.mutationDebounceTimer);\n }\n this.mutationDebounceTimer = window.setTimeout(() => {\n this.observeElements();\n this.mutationDebounceTimer = null;\n }, VIEWPORT_MUTATION_DEBOUNCE_MS);\n }\n });\n\n this.mutationObserver.observe(document.body, {\n childList: true,\n subtree: true,\n });\n }\n\n /**\n * Cleans up tracking for removed DOM nodes\n */\n private cleanupRemovedNodes(removedNodes: NodeList): void {\n removedNodes.forEach((node) => {\n if (node.nodeType !== 1) return; // 1 = ELEMENT_NODE\n\n const element = node as Element;\n const tracked = this.trackedElements.get(element);\n\n if (tracked) {\n if (tracked.timeoutId !== null) {\n window.clearTimeout(tracked.timeoutId);\n }\n\n this.observer?.unobserve(element);\n this.trackedElements.delete(element);\n }\n\n const descendants = Array.from(this.trackedElements.keys()).filter((el) => element.contains(el));\n descendants.forEach((el) => {\n const descendantTracked = this.trackedElements.get(el);\n if (descendantTracked && descendantTracked.timeoutId !== null) {\n window.clearTimeout(descendantTracked.timeoutId);\n }\n this.observer?.unobserve(el);\n this.trackedElements.delete(el);\n });\n });\n }\n}\n","import { log } from '../utils';\n\n/**\n * Robust localStorage and sessionStorage wrapper with automatic fallback to in-memory storage.\n *\n * **Purpose**: Provides consistent storage APIs across all browser environments,\n * handling quota limits, privacy modes, and SSR scenarios gracefully.\n *\n * **Core Functionality**:\n * - **Dual Storage**: localStorage (persistent) and sessionStorage (tab-scoped)\n * - **Automatic Fallback**: In-memory Maps when browser storage unavailable\n * - **Quota Handling**: Intelligent cleanup on QuotaExceededError\n * - **SSR-Safe**: No-op in Node.js environments\n *\n * **Key Features**:\n * - Separate fallback Maps for each storage type\n * - Storage quota error handling with automatic cleanup and retry\n * - Intelligent cleanup: Prioritizes removing persisted events over critical data\n * - Preserves: session, user, device, and config keys during cleanup\n * - Test key validation during initialization (`__tracelog_test__`)\n * - Public `isAvailable()` and `hasQuotaError()` for conditional logic\n * - Explicit `clear()` method for TraceLog-namespaced data only (`tracelog_*` prefix)\n *\n * **Cleanup Strategy** (on QuotaExceededError):\n * 1. Remove persisted events (`tracelog_persisted_events_*`) - usually largest data\n * 2. If no persisted events, remove up to 5 non-critical keys\n * 3. Preserve critical keys: `tracelog_session_*`, `tracelog_user_id`, `tracelog_device_id`, `tracelog_config`\n *\n * @see src/managers/README.md (lines 203-226) for detailed documentation\n *\n * @example\n * ```typescript\n * const storage = new StorageManager();\n *\n * // localStorage operations\n * storage.setItem('key', 'value');\n * const value = storage.getItem('key');\n * storage.removeItem('key');\n *\n * // sessionStorage operations\n * storage.setSessionItem('session_key', 'data');\n * const sessionValue = storage.getSessionItem('session_key');\n *\n * // Check availability\n * if (storage.isAvailable()) {\n * // localStorage is working\n * }\n *\n * // Check for quota issues\n * if (storage.hasQuotaError()) {\n * // Data may not persist\n * }\n * ```\n */\nexport class StorageManager {\n private readonly storage: Storage | null;\n private readonly sessionStorageRef: Storage | null;\n private readonly fallbackStorage = new Map<string, string>();\n private readonly fallbackSessionStorage = new Map<string, string>();\n\n private hasQuotaExceededError = false;\n\n constructor() {\n this.storage = this.initializeStorage('localStorage');\n this.sessionStorageRef = this.initializeStorage('sessionStorage');\n\n if (!this.storage) {\n log('debug', 'localStorage not available, using memory fallback');\n }\n if (!this.sessionStorageRef) {\n log('debug', 'sessionStorage not available, using memory fallback');\n }\n }\n\n /**\n * Retrieves an item from localStorage.\n *\n * Automatically falls back to in-memory storage if localStorage unavailable.\n *\n * @param key - Storage key\n * @returns Stored value or null if not found\n */\n getItem(key: string): string | null {\n try {\n if (this.storage) {\n return this.storage.getItem(key);\n }\n return this.fallbackStorage.get(key) ?? null;\n } catch {\n return this.fallbackStorage.get(key) ?? null;\n }\n }\n\n /**\n * Stores an item in localStorage with automatic quota handling.\n *\n * **Behavior**:\n * 1. Updates fallback storage first (ensures consistency)\n * 2. Attempts to store in localStorage\n * 3. On QuotaExceededError: Triggers cleanup and retries once\n * 4. Falls back to in-memory storage if retry fails\n *\n * **Cleanup on Quota Error**:\n * - Removes persisted events (largest data)\n * - Removes up to 5 non-critical keys\n * - Preserves session, user, device, and config keys\n *\n * @param key - Storage key\n * @param value - String value to store\n */\n setItem(key: string, value: string): void {\n this.fallbackStorage.set(key, value);\n\n try {\n if (this.storage) {\n this.storage.setItem(key, value);\n return;\n }\n } catch (error) {\n const isQuotaError =\n (error instanceof DOMException && error.name === 'QuotaExceededError') ||\n (error instanceof Error && error.name === 'QuotaExceededError');\n\n if (isQuotaError) {\n this.hasQuotaExceededError = true;\n\n log('warn', 'localStorage quota exceeded, attempting cleanup', {\n data: { key, valueSize: value.length },\n });\n\n const cleanedUp = this.cleanupOldData();\n\n if (cleanedUp) {\n try {\n if (this.storage) {\n this.storage.setItem(key, value);\n return;\n }\n } catch (retryError) {\n log('error', 'localStorage quota exceeded even after cleanup - data will not persist', {\n error: retryError,\n data: { key, valueSize: value.length },\n });\n }\n } else {\n log('error', 'localStorage quota exceeded and no data to cleanup - data will not persist', {\n error,\n data: { key, valueSize: value.length },\n });\n }\n }\n }\n }\n\n /**\n * Removes an item from localStorage and fallback storage.\n *\n * Safe to call even if key doesn't exist (idempotent).\n *\n * @param key - Storage key to remove\n */\n removeItem(key: string): void {\n try {\n if (this.storage) {\n this.storage.removeItem(key);\n }\n } catch {\n /* empty */\n }\n\n this.fallbackStorage.delete(key);\n }\n\n /**\n * Clears all TraceLog-related items from storage.\n *\n * Only removes keys with `tracelog_` prefix (safe for shared storage).\n * Clears both localStorage and fallback storage.\n *\n * **Use Cases**:\n * - User logout/privacy actions\n * - Development/testing cleanup\n * - Reset analytics state\n */\n clear(): void {\n if (!this.storage) {\n this.fallbackStorage.clear();\n return;\n }\n\n try {\n const keysToRemove: string[] = [];\n\n for (let i = 0; i < this.storage.length; i++) {\n const key = this.storage.key(i);\n if (key?.startsWith('tracelog_')) {\n keysToRemove.push(key);\n }\n }\n\n keysToRemove.forEach((key) => {\n this.storage!.removeItem(key);\n });\n this.fallbackStorage.clear();\n } catch (error) {\n log('error', 'Failed to clear storage', { error });\n this.fallbackStorage.clear();\n }\n }\n\n /**\n * Checks if localStorage is available.\n *\n * @returns true if localStorage is working, false if using fallback\n */\n isAvailable(): boolean {\n return this.storage !== null;\n }\n\n /**\n * Checks if a QuotaExceededError has occurred during this session.\n *\n * **Purpose**: Detect when localStorage is full and data may not persist.\n * Allows application to show warnings or adjust behavior.\n *\n * **Note**: Flag is set on first QuotaExceededError and never reset.\n *\n * @returns true if quota exceeded at any point during this session\n */\n hasQuotaError(): boolean {\n return this.hasQuotaExceededError;\n }\n\n /**\n * Implements two-phase cleanup strategy to free storage space when quota exceeded.\n *\n * **Purpose**: Removes TraceLog data intelligently to make room for new writes\n * while preserving critical user state (session, user ID, device ID, config).\n *\n * **Two-Phase Cleanup Strategy**:\n * 1. **Phase 1 (Priority)**: Remove all persisted events (`tracelog_persisted_events_*`)\n * - These are typically the largest data items (batches of events)\n * - Safe to remove as they represent recoverable failed sends\n * - Returns immediately if any persisted events found and removed\n *\n * 2. **Phase 2 (Fallback)**: Remove up to 5 non-critical keys\n * - Only executed if no persisted events found\n * - Preserves critical keys: session data, user ID, device ID, config\n * - Limits to 5 keys to avoid excessive cleanup time\n *\n * **Critical Keys (Never Removed)**:\n * - `tracelog_session_*` - Active session data\n * - `tracelog_user_id` - User identification\n * - `tracelog_device_id` - Device fingerprint\n * - `tracelog_config` - Configuration cache\n *\n * **Error Handling**:\n * - Individual key removal failures silently ignored (continue cleanup)\n * - Overall cleanup errors logged and return false\n *\n * @returns true if any data was successfully removed, false if nothing cleaned up\n */\n private cleanupOldData(): boolean {\n if (!this.storage) {\n return false;\n }\n\n try {\n const tracelogKeys: string[] = [];\n const persistedEventsKeys: string[] = [];\n\n for (let i = 0; i < this.storage.length; i++) {\n const key = this.storage.key(i);\n if (key?.startsWith('tracelog_')) {\n tracelogKeys.push(key);\n\n if (key.startsWith('tracelog_persisted_events_')) {\n persistedEventsKeys.push(key);\n }\n }\n }\n\n if (persistedEventsKeys.length > 0) {\n persistedEventsKeys.forEach((key) => {\n try {\n this.storage!.removeItem(key);\n } catch {\n /* empty */\n }\n });\n\n return true;\n }\n\n const criticalPrefixes = ['tracelog_session_', 'tracelog_user_id', 'tracelog_device_id', 'tracelog_config'];\n\n const nonCriticalKeys = tracelogKeys.filter((key) => {\n return !criticalPrefixes.some((prefix) => key.startsWith(prefix));\n });\n\n if (nonCriticalKeys.length > 0) {\n const keysToRemove = nonCriticalKeys.slice(0, 5);\n keysToRemove.forEach((key) => {\n try {\n this.storage!.removeItem(key);\n } catch {\n /* empty */\n }\n });\n\n return true;\n }\n\n return false;\n } catch (error) {\n log('error', 'Failed to cleanup old data', { error });\n return false;\n }\n }\n\n /**\n * Initializes storage with feature detection and write-test validation.\n *\n * **Purpose**: Validates storage availability by performing actual write/remove test,\n * preventing false positives in privacy modes where storage API exists but throws on write.\n *\n * **Validation Strategy**:\n * 1. SSR Safety: Returns null in Node.js environments (`typeof window === 'undefined'`)\n * 2. API Check: Verifies storage object exists on window\n * 3. Write Test: Attempts to write test key (`__tracelog_test__`)\n * 4. Cleanup: Removes test key immediately after validation\n *\n * **Why Write Test is Critical**:\n * - Safari private browsing: storage API exists but throws QuotaExceededError on write\n * - iOS private mode: storage appears available but operations fail\n * - Incognito modes: API exists but writes are silently ignored or throw\n *\n * **Fallback Behavior**:\n * - Returns null if storage unavailable or test fails\n * - Caller automatically falls back to in-memory Map storage\n *\n * @param type - Storage type to initialize ('localStorage' | 'sessionStorage')\n * @returns Storage instance if available and writable, null otherwise\n */\n private initializeStorage(type: 'localStorage' | 'sessionStorage'): Storage | null {\n if (typeof window === 'undefined') {\n return null;\n }\n\n try {\n const storage = type === 'localStorage' ? window.localStorage : window.sessionStorage;\n const testKey = '__tracelog_test__';\n\n storage.setItem(testKey, 'test');\n storage.removeItem(testKey);\n\n return storage;\n } catch {\n return null;\n }\n }\n\n /**\n * Retrieves an item from sessionStorage.\n *\n * Automatically falls back to in-memory storage if sessionStorage unavailable.\n *\n * @param key - Storage key\n * @returns Stored value or null if not found\n */\n getSessionItem(key: string): string | null {\n try {\n if (this.sessionStorageRef) {\n return this.sessionStorageRef.getItem(key);\n }\n return this.fallbackSessionStorage.get(key) ?? null;\n } catch {\n return this.fallbackSessionStorage.get(key) ?? null;\n }\n }\n\n /**\n * Stores an item in sessionStorage with quota error detection.\n *\n * **Behavior**:\n * 1. Updates fallback storage first (ensures consistency)\n * 2. Attempts to store in sessionStorage\n * 3. On QuotaExceededError: Logs error and uses fallback (no retry/cleanup)\n *\n * **Note**: sessionStorage quota errors are rare (typically 5-10MB per tab).\n * No automatic cleanup unlike localStorage.\n *\n * @param key - Storage key\n * @param value - String value to store\n */\n setSessionItem(key: string, value: string): void {\n this.fallbackSessionStorage.set(key, value);\n\n try {\n if (this.sessionStorageRef) {\n this.sessionStorageRef.setItem(key, value);\n return;\n }\n } catch (error) {\n const isQuotaError =\n (error instanceof DOMException && error.name === 'QuotaExceededError') ||\n (error instanceof Error && error.name === 'QuotaExceededError');\n\n if (isQuotaError) {\n log('error', 'sessionStorage quota exceeded - data will not persist', {\n error,\n data: { key, valueSize: value.length },\n });\n }\n }\n }\n\n /**\n * Removes an item from sessionStorage and fallback storage.\n *\n * Safe to call even if key doesn't exist (idempotent).\n *\n * @param key - Storage key to remove\n */\n removeSessionItem(key: string): void {\n try {\n if (this.sessionStorageRef) {\n this.sessionStorageRef.removeItem(key);\n }\n } catch {\n /* empty */\n }\n\n this.fallbackSessionStorage.delete(key);\n }\n}\n","import { EventManager } from '../managers/event.manager';\nimport { StateManager } from '../managers/state.manager';\nimport { EventType, WebVitalType } from '../types';\nimport {\n LONG_TASK_THROTTLE_MS,\n MAX_NAVIGATION_HISTORY,\n PRECISION_TWO_DECIMALS,\n getWebVitalsThresholds,\n DEFAULT_WEB_VITALS_MODE,\n} from '../constants';\nimport { log } from '../utils';\n\ntype LayoutShiftEntry = PerformanceEntry & { value?: number; hadRecentInput?: boolean };\n\n/**\n * Captures Web Vitals and performance metrics using the web-vitals library with fallback to native Performance Observer API.\n *\n * **Features**:\n * - Configurable filtering modes: 'all', 'needs-improvement' (default), 'poor'\n * - Custom threshold overrides via webVitalsThresholds config\n * - Navigation-based deduplication with 50-navigation FIFO history\n * - CLS accumulation with reset on navigation change\n * - Long task throttling (maximum 1 event per second)\n * - Automatic fallback to Performance Observer if web-vitals library fails\n * - Final values only (reportAllChanges: false for all metrics)\n *\n * **Events Generated**: `web_vitals`\n *\n * **Metrics Captured**:\n * - LCP (Largest Contentful Paint): Main content loading time\n * - CLS (Cumulative Layout Shift): Visual stability score\n * - FCP (First Contentful Paint): Initial rendering time\n * - TTFB (Time to First Byte): Server response time\n * - INP (Interaction to Next Paint): Responsiveness measure\n * - LONG_TASK: Tasks blocking main thread (>50ms, throttled to 1/second)\n *\n * **Filtering Modes**:\n * - 'all': Track all positive metric values (threshold = 0)\n * - 'needs-improvement': Track metrics exceeding good thresholds (default)\n * - 'poor': Track only critical performance issues\n *\n * @example\n * ```typescript\n * const handler = new PerformanceHandler(eventManager);\n * await handler.startTracking();\n * // Web Vitals are now being tracked with default 'needs-improvement' mode\n * handler.stopTracking();\n * ```\n */\nexport class PerformanceHandler extends StateManager {\n private readonly eventManager: EventManager;\n private readonly reportedByNav: Map<string, Set<string>> = new Map();\n private readonly navigationHistory: string[] = []; // FIFO queue for tracking navigation order\n private readonly observers: PerformanceObserver[] = [];\n private vitalThresholds: Record<WebVitalType, number>;\n private lastLongTaskSentAt = 0;\n private navigationCounter = 0; // Counter for handling simultaneous navigations edge case\n\n constructor(eventManager: EventManager) {\n super();\n this.eventManager = eventManager;\n this.vitalThresholds = getWebVitalsThresholds(DEFAULT_WEB_VITALS_MODE);\n }\n\n /**\n * Starts tracking Web Vitals and performance metrics.\n *\n * Asynchronously loads the web-vitals library and initializes performance tracking.\n * Falls back to native Performance Observer API if web-vitals fails to load.\n *\n * **Configuration**:\n * - Reads webVitalsMode from config ('all', 'needs-improvement', 'poor')\n * - Merges webVitalsThresholds with mode defaults for custom thresholds\n * - Initializes web-vitals library observers (LCP, CLS, FCP, TTFB, INP)\n * - Starts long task observation with 1/second throttling\n *\n * @returns Promise that resolves when tracking is initialized\n */\n async startTracking(): Promise<void> {\n const config = this.get('config');\n const mode = config?.webVitalsMode ?? DEFAULT_WEB_VITALS_MODE;\n\n this.vitalThresholds = getWebVitalsThresholds(mode);\n\n if (config?.webVitalsThresholds) {\n this.vitalThresholds = { ...this.vitalThresholds, ...config.webVitalsThresholds };\n }\n\n await this.initWebVitals();\n this.observeLongTasks();\n }\n\n /**\n * Stops tracking Web Vitals and cleans up resources.\n *\n * Disconnects all Performance Observers and clears internal state:\n * - Disconnects all active observers (web-vitals and long task)\n * - Clears navigation-based deduplication map\n * - Clears navigation history array\n * - Prevents memory leaks in long-running applications\n */\n stopTracking(): void {\n this.observers.forEach((obs, index) => {\n try {\n obs.disconnect();\n } catch (error) {\n log('debug', 'Failed to disconnect performance observer', { error, data: { observerIndex: index } });\n }\n });\n\n this.observers.length = 0;\n this.reportedByNav.clear();\n this.navigationHistory.length = 0;\n }\n\n private observeWebVitalsFallback(): void {\n this.reportTTFB();\n\n this.safeObserve(\n 'largest-contentful-paint',\n (list) => {\n const entries = list.getEntries();\n const last = entries[entries.length - 1] as (PerformanceEntry & { startTime: number }) | undefined;\n\n if (!last) {\n return;\n }\n\n this.sendVital({ type: 'LCP', value: Number(last.startTime.toFixed(PRECISION_TWO_DECIMALS)) });\n },\n { type: 'largest-contentful-paint', buffered: true },\n true,\n );\n\n let clsValue = 0;\n let currentNavId = this.getNavigationId();\n\n this.safeObserve(\n 'layout-shift',\n (list) => {\n const navId = this.getNavigationId();\n\n if (navId !== currentNavId) {\n clsValue = 0;\n currentNavId = navId;\n }\n\n const entries = list.getEntries() as LayoutShiftEntry[];\n\n for (const entry of entries) {\n if (entry.hadRecentInput === true) {\n continue;\n }\n\n const value = typeof entry.value === 'number' ? entry.value : 0;\n clsValue += value;\n }\n\n this.sendVital({ type: 'CLS', value: Number(clsValue.toFixed(PRECISION_TWO_DECIMALS)) });\n },\n { type: 'layout-shift', buffered: true },\n );\n\n this.safeObserve(\n 'paint',\n (list) => {\n for (const entry of list.getEntries()) {\n if (entry.name === 'first-contentful-paint') {\n this.sendVital({ type: 'FCP', value: Number(entry.startTime.toFixed(PRECISION_TWO_DECIMALS)) });\n }\n }\n },\n { type: 'paint', buffered: true },\n true,\n );\n\n this.safeObserve(\n 'event',\n (list) => {\n let worst = 0;\n const entries = list.getEntries() as Array<{ startTime: number; processingEnd?: number }>;\n\n for (const entry of entries) {\n const dur = (entry.processingEnd ?? 0) - (entry.startTime ?? 0);\n worst = Math.max(worst, dur);\n }\n\n if (worst > 0) {\n this.sendVital({ type: 'INP', value: Number(worst.toFixed(PRECISION_TWO_DECIMALS)) });\n }\n },\n { type: 'event', buffered: true },\n );\n }\n\n private async initWebVitals(): Promise<void> {\n try {\n const { onLCP, onCLS, onFCP, onTTFB, onINP } = await import('web-vitals');\n\n const report =\n (type: WebVitalType) =>\n (metric: { value: number }): void => {\n const value = Number(metric.value.toFixed(PRECISION_TWO_DECIMALS));\n this.sendVital({ type, value });\n };\n\n onLCP(report('LCP'), { reportAllChanges: false });\n onCLS(report('CLS'), { reportAllChanges: false });\n onFCP(report('FCP'), { reportAllChanges: false });\n onTTFB(report('TTFB'), { reportAllChanges: false });\n onINP(report('INP'), { reportAllChanges: false });\n } catch (error) {\n log('debug', 'Failed to load web-vitals library, using fallback', { error });\n this.observeWebVitalsFallback();\n }\n }\n\n private reportTTFB(): void {\n try {\n const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined;\n\n if (!nav) {\n return;\n }\n\n const ttfb = nav.responseStart;\n\n // TTFB can be 0 in Mobile Safari when response is served from cache\n if (typeof ttfb === 'number' && Number.isFinite(ttfb)) {\n this.sendVital({ type: 'TTFB', value: Number(ttfb.toFixed(PRECISION_TWO_DECIMALS)) });\n }\n } catch (error) {\n log('debug', 'Failed to report TTFB', { error });\n }\n }\n\n private observeLongTasks(): void {\n this.safeObserve(\n 'longtask',\n (list) => {\n const entries = list.getEntries() as Array<{ duration: number }>;\n\n for (const entry of entries) {\n const duration = Number(entry.duration.toFixed(PRECISION_TWO_DECIMALS));\n const now = Date.now();\n\n if (now - this.lastLongTaskSentAt >= LONG_TASK_THROTTLE_MS) {\n if (this.shouldSendVital('LONG_TASK', duration)) {\n this.trackWebVital('LONG_TASK', duration);\n }\n this.lastLongTaskSentAt = now;\n }\n }\n },\n { type: 'longtask', buffered: true },\n );\n }\n\n private sendVital(sample: { type: WebVitalType; value: number }): void {\n if (!this.shouldSendVital(sample.type, sample.value)) {\n return;\n }\n\n const navId = this.getNavigationId();\n\n if (navId) {\n const reportedForNav = this.reportedByNav.get(navId);\n const isDuplicate = reportedForNav?.has(sample.type);\n\n if (isDuplicate) {\n return;\n }\n\n if (!reportedForNav) {\n this.reportedByNav.set(navId, new Set([sample.type]));\n this.navigationHistory.push(navId);\n\n if (this.navigationHistory.length > MAX_NAVIGATION_HISTORY) {\n const oldestNav = this.navigationHistory.shift();\n if (oldestNav) {\n this.reportedByNav.delete(oldestNav);\n }\n }\n } else {\n reportedForNav.add(sample.type);\n }\n }\n\n this.trackWebVital(sample.type, sample.value);\n }\n\n private trackWebVital(type: WebVitalType, value: number): void {\n if (!Number.isFinite(value)) {\n log('debug', 'Invalid web vital value', { data: { type, value } });\n return;\n }\n\n this.eventManager.track({\n type: EventType.WEB_VITALS,\n web_vitals: {\n type,\n value,\n },\n });\n }\n\n /**\n * Generates a unique navigation identifier for deduplication.\n *\n * **Purpose**: Creates deterministic IDs to prevent duplicate Web Vitals reporting\n * across multiple metrics for the same navigation event.\n *\n * **ID Format**: `{timestamp}_{pathname}` or `{timestamp}_{pathname}_{counter}`\n *\n * **Edge Case Handling**:\n * - If multiple navigations occur to the same pathname in the same millisecond,\n * a counter suffix is appended (e.g., `1234.56_/home_2`)\n * - Counter only added when > 1 to minimize ID length for common case\n *\n * **Why Deterministic**:\n * - Previous implementation used random string → duplicate metrics on page reload\n * - Now: Same navigation = same ID = proper deduplication via reportedByNav Map\n *\n * @returns Navigation ID string or null if navigation timing unavailable\n *\n * @internal\n */\n private getNavigationId(): string | null {\n try {\n const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined;\n\n if (!nav) {\n return null;\n }\n\n const timestamp = nav.startTime || performance.now();\n const counter = ++this.navigationCounter;\n\n // Base ID: timestamp + pathname (deterministic for deduplication)\n const baseId = `${timestamp.toFixed(2)}_${window.location.pathname}`;\n\n // Append counter only if > 1 (edge case: simultaneous navigations)\n // This prevents collisions if two navigations occur in the same millisecond to the same path\n return counter > 1 ? `${baseId}_${counter}` : baseId;\n } catch (error) {\n log('debug', 'Failed to get navigation ID', { error });\n return null;\n }\n }\n\n private isObserverSupported(type: string): boolean {\n if (typeof PerformanceObserver === 'undefined') return false;\n const supported = PerformanceObserver.supportedEntryTypes;\n return !supported || supported.includes(type);\n }\n\n private safeObserve(\n type: string,\n cb: PerformanceObserverCallback,\n options?: PerformanceObserverInit,\n once = false,\n ): boolean {\n try {\n if (!this.isObserverSupported(type)) {\n return false;\n }\n\n const obs = new PerformanceObserver((list, observer) => {\n try {\n cb(list, observer);\n } catch (callbackError) {\n log('debug', 'Observer callback failed', {\n error: callbackError,\n data: { type },\n });\n }\n\n if (once) {\n try {\n observer.disconnect();\n } catch {\n /* empty */\n }\n }\n });\n\n obs.observe(options ?? { type, buffered: true });\n\n if (!once) {\n this.observers.push(obs);\n }\n\n return true;\n } catch (error) {\n log('debug', 'Failed to create performance observer', {\n error,\n data: { type },\n });\n return false;\n }\n }\n\n private shouldSendVital(type: WebVitalType, value?: number): boolean {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n log('debug', 'Invalid web vital value', { data: { type, value } });\n return false;\n }\n\n const threshold = this.vitalThresholds[type];\n\n if (typeof threshold === 'number' && value <= threshold) {\n return false;\n }\n\n return true;\n }\n}\n","import { EventManager } from '../managers/event.manager';\nimport { StateManager } from '../managers/state.manager';\nimport { ErrorType, EventType } from '../types';\nimport { log } from '../utils';\nimport {\n PII_PATTERNS,\n MAX_ERROR_MESSAGE_LENGTH,\n ERROR_SUPPRESSION_WINDOW_MS,\n MAX_TRACKED_ERRORS,\n MAX_TRACKED_ERRORS_HARD_LIMIT,\n DEFAULT_ERROR_SAMPLING_RATE,\n ERROR_BURST_WINDOW_MS,\n ERROR_BURST_THRESHOLD,\n ERROR_BURST_BACKOFF_MS,\n} from '../constants/error.constants';\n\n/**\n * Captures JavaScript errors and unhandled promise rejections for debugging and monitoring.\n *\n * **Events Generated**: `error`\n *\n * **Features**:\n * - Tracks JavaScript runtime errors and unhandled promise rejections\n * - Configurable error sampling rate (default: 100%)\n * - PII sanitization (emails, phone numbers, credit cards, API keys, tokens)\n * - Message truncation (500 character limit)\n * - Burst detection (>10 errors/second triggers 5-second cooldown)\n * - Deduplication within 5-second window per error type+message\n *\n * **Privacy Protection**:\n * - Automatically redacts PII from error messages before storage\n * - Sanitizes emails, phone numbers, credit cards, IBAN, API keys, Bearer tokens\n *\n * @see src/handlers/README.md (lines 167-218) for detailed documentation\n */\nexport class ErrorHandler extends StateManager {\n private readonly eventManager: EventManager;\n private readonly recentErrors = new Map<string, number>();\n private errorBurstCounter = 0;\n private burstWindowStart = 0;\n private burstBackoffUntil = 0;\n\n constructor(eventManager: EventManager) {\n super();\n this.eventManager = eventManager;\n }\n\n /**\n * Starts tracking JavaScript errors and promise rejections.\n *\n * - Registers global error event listener\n * - Registers unhandledrejection event listener\n */\n startTracking(): void {\n window.addEventListener('error', this.handleError);\n window.addEventListener('unhandledrejection', this.handleRejection);\n }\n\n /**\n * Stops tracking errors and cleans up resources.\n *\n * - Removes error event listeners\n * - Clears recent errors map\n * - Resets burst detection counters\n */\n stopTracking(): void {\n window.removeEventListener('error', this.handleError);\n window.removeEventListener('unhandledrejection', this.handleRejection);\n this.recentErrors.clear();\n this.errorBurstCounter = 0;\n this.burstWindowStart = 0;\n this.burstBackoffUntil = 0;\n }\n\n /**\n * Checks sampling rate and burst detection\n * Returns false if in cooldown period after burst detection\n */\n private shouldSample(): boolean {\n const now = Date.now();\n\n if (now < this.burstBackoffUntil) {\n return false;\n }\n\n if (now - this.burstWindowStart > ERROR_BURST_WINDOW_MS) {\n this.errorBurstCounter = 0;\n this.burstWindowStart = now;\n }\n\n this.errorBurstCounter++;\n\n if (this.errorBurstCounter > ERROR_BURST_THRESHOLD) {\n this.burstBackoffUntil = now + ERROR_BURST_BACKOFF_MS;\n log('debug', 'Error burst detected - entering cooldown', {\n data: {\n errorsInWindow: this.errorBurstCounter,\n cooldownMs: ERROR_BURST_BACKOFF_MS,\n },\n });\n return false;\n }\n\n const config = this.get('config');\n const samplingRate = config?.errorSampling ?? DEFAULT_ERROR_SAMPLING_RATE;\n return Math.random() < samplingRate;\n }\n\n private readonly handleError = (event: ErrorEvent): void => {\n if (!this.shouldSample()) {\n return;\n }\n\n const sanitizedMessage = this.sanitize(event.message || 'Unknown error');\n\n if (this.shouldSuppressError(ErrorType.JS_ERROR, sanitizedMessage)) {\n return;\n }\n\n this.eventManager.track({\n type: EventType.ERROR,\n error_data: {\n type: ErrorType.JS_ERROR,\n message: sanitizedMessage,\n ...(event.filename && { filename: event.filename }),\n ...(event.lineno && { line: event.lineno }),\n ...(event.colno && { column: event.colno }),\n },\n });\n };\n\n private readonly handleRejection = (event: PromiseRejectionEvent): void => {\n if (!this.shouldSample()) {\n return;\n }\n\n const message = this.extractRejectionMessage(event.reason);\n const sanitizedMessage = this.sanitize(message);\n\n if (this.shouldSuppressError(ErrorType.PROMISE_REJECTION, sanitizedMessage)) {\n return;\n }\n\n this.eventManager.track({\n type: EventType.ERROR,\n error_data: {\n type: ErrorType.PROMISE_REJECTION,\n message: sanitizedMessage,\n },\n });\n };\n\n private extractRejectionMessage(reason: unknown): string {\n if (!reason) return 'Unknown rejection';\n\n if (typeof reason === 'string') return reason;\n\n if (reason instanceof Error) {\n return reason.stack ?? reason.message ?? reason.toString();\n }\n\n if (typeof reason === 'object' && 'message' in reason) {\n return String(reason.message);\n }\n\n try {\n return JSON.stringify(reason);\n } catch {\n return String(reason);\n }\n }\n\n private sanitize(text: string): string {\n let sanitized = text.length > MAX_ERROR_MESSAGE_LENGTH ? text.slice(0, MAX_ERROR_MESSAGE_LENGTH) + '...' : text;\n\n for (const pattern of PII_PATTERNS) {\n const regex = new RegExp(pattern.source, pattern.flags);\n sanitized = sanitized.replace(regex, '[REDACTED]');\n }\n\n return sanitized;\n }\n\n private shouldSuppressError(type: ErrorType, message: string): boolean {\n const now = Date.now();\n const key = `${type}:${message}`;\n const lastSeenAt = this.recentErrors.get(key);\n\n if (lastSeenAt && now - lastSeenAt < ERROR_SUPPRESSION_WINDOW_MS) {\n this.recentErrors.set(key, now);\n return true;\n }\n\n this.recentErrors.set(key, now);\n\n if (this.recentErrors.size > MAX_TRACKED_ERRORS_HARD_LIMIT) {\n this.recentErrors.clear();\n this.recentErrors.set(key, now);\n\n return false;\n }\n\n if (this.recentErrors.size > MAX_TRACKED_ERRORS) {\n this.pruneOldErrors();\n }\n\n return false;\n }\n\n private pruneOldErrors(): void {\n const now = Date.now();\n for (const [key, timestamp] of this.recentErrors.entries()) {\n if (now - timestamp > ERROR_SUPPRESSION_WINDOW_MS) {\n this.recentErrors.delete(key);\n }\n }\n\n if (this.recentErrors.size <= MAX_TRACKED_ERRORS) {\n return;\n }\n\n const entries = Array.from(this.recentErrors.entries()).sort((a, b) => a[1] - b[1]);\n const excess = this.recentErrors.size - MAX_TRACKED_ERRORS;\n\n for (let index = 0; index < excess; index += 1) {\n const entry = entries[index];\n if (entry) {\n this.recentErrors.delete(entry[0]);\n }\n }\n }\n}\n","import { EventManager } from './managers/event.manager';\nimport { UserManager } from './managers/user.manager';\nimport { StateManager } from './managers/state.manager';\nimport { SessionHandler } from './handlers/session.handler';\nimport { PageViewHandler } from './handlers/page-view.handler';\nimport { ClickHandler } from './handlers/click.handler';\nimport { ScrollHandler } from './handlers/scroll.handler';\nimport { ViewportHandler } from './handlers/viewport.handler';\nimport {\n Config,\n EventType,\n EmitterCallback,\n EmitterMap,\n Mode,\n TransformerHook,\n TransformerMap,\n BeforeSendTransformer,\n BeforeBatchTransformer,\n MetadataType,\n CustomHeadersProvider,\n} from './types';\nimport {\n isEventValid,\n getDeviceInfo,\n normalizeUrl,\n Emitter,\n getCollectApiUrls,\n detectQaMode,\n log,\n isValidMetadata,\n} from './utils';\nimport { StorageManager } from './managers/storage.manager';\nimport { SCROLL_DEBOUNCE_TIME_MS, SCROLL_SUPPRESS_MULTIPLIER } from './constants/config.constants';\nimport { PerformanceHandler } from './handlers/performance.handler';\nimport { ErrorHandler } from './handlers/error.handler';\n\nexport class App extends StateManager {\n private isInitialized = false;\n private suppressNextScrollTimer: number | null = null;\n\n private readonly emitter = new Emitter();\n private readonly transformers: TransformerMap = {};\n private customHeadersProvider?: CustomHeadersProvider;\n\n protected managers: {\n storage?: StorageManager;\n event?: EventManager;\n } = {};\n\n protected handlers: {\n session?: SessionHandler;\n pageView?: PageViewHandler;\n click?: ClickHandler;\n scroll?: ScrollHandler;\n performance?: PerformanceHandler;\n error?: ErrorHandler;\n viewport?: ViewportHandler;\n } = {};\n\n get initialized(): boolean {\n return this.isInitialized;\n }\n\n /**\n * Initializes TraceLog with configuration.\n *\n * @param config - Configuration object\n * @throws {Error} If initialization fails\n * @internal Called from api.init()\n */\n async init(config: Config = {}): Promise<void> {\n if (this.isInitialized) {\n return;\n }\n\n this.managers.storage = new StorageManager();\n\n try {\n this.setupState(config);\n\n // Extract static headers from custom integration config\n const staticHeaders = config.integrations?.custom?.headers ?? {};\n\n this.managers.event = new EventManager(\n this.managers.storage,\n this.emitter,\n this.transformers,\n staticHeaders,\n this.customHeadersProvider,\n );\n\n this.initializeHandlers();\n\n await this.managers.event.recoverPersistedEvents().catch((error) => {\n log('warn', 'Failed to recover persisted events', { error });\n });\n\n this.isInitialized = true;\n } catch (error) {\n this.destroy(true);\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new Error(`[TraceLog] TraceLog initialization failed: ${errorMessage}`);\n }\n }\n\n /**\n * Sends a custom event with optional metadata.\n *\n * @param name - Event name\n * @param metadata - Optional metadata\n * @internal Called from api.event()\n */\n sendCustomEvent(name: string, metadata?: Record<string, unknown> | Record<string, unknown>[]): void {\n if (!this.managers.event) {\n log('warn', 'Cannot send custom event: TraceLog not initialized', { data: { name } });\n return;\n }\n\n let normalizedMetadata = metadata;\n\n if (metadata && typeof metadata === 'object' && !Array.isArray(metadata)) {\n if (Object.getPrototypeOf(metadata) !== Object.prototype) {\n normalizedMetadata = Object.assign({}, metadata);\n }\n }\n\n const { valid, error, sanitizedMetadata } = isEventValid(name, normalizedMetadata);\n\n if (!valid) {\n if (this.get('mode') === Mode.QA) {\n throw new Error(`[TraceLog] Custom event \"${name}\" validation failed: ${error}`);\n }\n\n return;\n }\n\n this.managers.event.track({\n type: EventType.CUSTOM,\n custom_event: {\n name,\n ...(sanitizedMetadata && { metadata: sanitizedMetadata }),\n },\n });\n }\n\n on<K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>): void {\n this.emitter.on(event, callback);\n }\n\n off<K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>): void {\n this.emitter.off(event, callback);\n }\n\n setTransformer(hook: 'beforeSend', fn: BeforeSendTransformer): void;\n setTransformer(hook: 'beforeBatch', fn: BeforeBatchTransformer): void;\n setTransformer(hook: TransformerHook, fn: BeforeSendTransformer | BeforeBatchTransformer): void {\n if (typeof fn !== 'function') {\n throw new Error(`[TraceLog] Transformer must be a function, received: ${typeof fn}`);\n }\n\n this.transformers[hook] = fn as BeforeSendTransformer & BeforeBatchTransformer;\n }\n\n removeTransformer(hook: TransformerHook): void {\n delete this.transformers[hook];\n }\n\n getTransformer(hook: 'beforeSend'): BeforeSendTransformer | undefined;\n getTransformer(hook: 'beforeBatch'): BeforeBatchTransformer | undefined;\n getTransformer(hook: TransformerHook): BeforeSendTransformer | BeforeBatchTransformer | undefined {\n return this.transformers[hook];\n }\n\n /**\n * Sets a callback to provide custom HTTP headers for requests to custom backends.\n * Only applies to custom backend integration (not TraceLog SaaS).\n *\n * @param provider - Callback function that returns custom headers\n * @throws {Error} If provider is not a function\n * @internal Called from api.setCustomHeaders()\n */\n setCustomHeaders(provider: CustomHeadersProvider): void {\n if (typeof provider !== 'function') {\n throw new Error(`[TraceLog] Custom headers provider must be a function, received: ${typeof provider}`);\n }\n\n this.customHeadersProvider = provider;\n\n // Update EventManager if already initialized\n if (this.managers.event) {\n this.managers.event.setCustomHeadersProvider(provider);\n }\n }\n\n /**\n * Removes the custom headers provider callback.\n *\n * @internal Called from api.removeCustomHeaders()\n */\n removeCustomHeaders(): void {\n this.customHeadersProvider = undefined;\n\n // Update EventManager if already initialized\n if (this.managers.event) {\n this.managers.event.removeCustomHeadersProvider();\n }\n }\n\n /**\n * Destroys the TraceLog instance and cleans up all resources.\n *\n * @param force - If true, forces cleanup even if not initialized (used during init failure)\n * @internal Called from api.destroy()\n */\n destroy(force = false): void {\n if (!this.isInitialized && !force) {\n return;\n }\n\n Object.values(this.handlers)\n .filter(Boolean)\n .forEach((handler) => {\n try {\n handler.stopTracking();\n } catch (error) {\n log('warn', 'Failed to stop tracking', { error });\n }\n });\n\n if (this.suppressNextScrollTimer) {\n clearTimeout(this.suppressNextScrollTimer);\n this.suppressNextScrollTimer = null;\n }\n\n this.managers.event?.stop();\n\n this.emitter.removeAllListeners();\n this.transformers.beforeSend = undefined;\n this.transformers.beforeBatch = undefined;\n this.customHeadersProvider = undefined;\n\n this.set('suppressNextScroll', false);\n this.set('sessionId', null);\n\n this.isInitialized = false;\n this.handlers = {};\n this.managers = {};\n }\n\n private setupState(config: Config = {}): void {\n this.set('config', config);\n\n const userId = UserManager.getId(this.managers.storage as StorageManager);\n this.set('userId', userId);\n\n const collectApiUrls = getCollectApiUrls(config);\n this.set('collectApiUrls', collectApiUrls);\n\n const device = getDeviceInfo();\n this.set('device', device);\n\n const pageUrl = normalizeUrl(window.location.href, config.sensitiveQueryParams);\n this.set('pageUrl', pageUrl);\n\n const isQaMode = detectQaMode();\n\n if (isQaMode) {\n this.set('mode', Mode.QA);\n }\n }\n\n /**\n * Returns the current configuration object.\n *\n * @returns The Config object passed to init()\n * @internal Used by api.ts for configuration access\n */\n public getConfig(): Config {\n return this.get('config');\n }\n\n /**\n * Returns the configured backend API URLs for event collection.\n *\n * @returns Object containing optional saas and custom API URLs\n * @internal Used by api.ts for backend URL access\n */\n public getCollectApiUrls(): { saas?: string; custom?: string } {\n return this.get('collectApiUrls');\n }\n\n /**\n * Returns the EventManager instance for event tracking operations.\n *\n * @returns The EventManager instance, or undefined if not initialized\n * @internal Used by api.ts for event operations\n */\n public getEventManager(): EventManager | undefined {\n return this.managers.event;\n }\n\n /**\n * Validates metadata object structure and values.\n *\n * @param metadata - The metadata object to validate\n * @returns Validation result with error message if invalid\n * @internal Helper for updateGlobalMetadata and mergeGlobalMetadata\n */\n private validateGlobalMetadata(metadata: Record<string, unknown>): { valid: boolean; error?: string } {\n if (typeof metadata !== 'object' || metadata === null || Array.isArray(metadata)) {\n return {\n valid: false,\n error: 'Global metadata must be a plain object',\n };\n }\n\n const validation = isValidMetadata('Global', metadata, 'globalMetadata');\n\n if (!validation.valid) {\n return {\n valid: false,\n error: validation.error,\n };\n }\n\n return { valid: true };\n }\n\n /**\n * Replaces global metadata with new values.\n *\n * @param metadata - New global metadata object\n * @throws {Error} If metadata validation fails\n * @internal Called from api.updateGlobalMetadata()\n */\n public updateGlobalMetadata(metadata: Record<string, unknown>): void {\n const validation = this.validateGlobalMetadata(metadata);\n\n if (!validation.valid) {\n throw new Error(`[TraceLog] Invalid global metadata: ${validation.error}`);\n }\n\n const currentConfig = this.get('config');\n\n const updatedConfig: Config = {\n ...currentConfig,\n globalMetadata: metadata as Record<string, MetadataType>,\n };\n\n this.set('config', updatedConfig);\n\n log('debug', 'Global metadata updated (replaced)', { data: { keys: Object.keys(metadata) } });\n }\n\n /**\n * Merges new metadata with existing global metadata.\n *\n * @param metadata - Metadata to merge with existing values\n * @throws {Error} If metadata validation fails\n * @internal Called from api.mergeGlobalMetadata()\n */\n public mergeGlobalMetadata(metadata: Record<string, unknown>): void {\n const validation = this.validateGlobalMetadata(metadata);\n\n if (!validation.valid) {\n throw new Error(`[TraceLog] Invalid global metadata: ${validation.error}`);\n }\n\n const currentConfig = this.get('config');\n const existingMetadata = currentConfig.globalMetadata ?? {};\n\n const mergedMetadata: Record<string, MetadataType> = {\n ...existingMetadata,\n ...(metadata as Record<string, MetadataType>),\n };\n\n const updatedConfig: Config = {\n ...currentConfig,\n globalMetadata: mergedMetadata,\n };\n\n this.set('config', updatedConfig);\n\n log('debug', 'Global metadata updated (merged)', { data: { keys: Object.keys(metadata) } });\n }\n\n private initializeHandlers(): void {\n const config = this.get('config');\n\n this.handlers.session = new SessionHandler(\n this.managers.storage as StorageManager,\n this.managers.event as EventManager,\n );\n\n this.handlers.session.startTracking();\n\n const onPageView = (): void => {\n this.set('suppressNextScroll', true);\n\n if (this.suppressNextScrollTimer) {\n clearTimeout(this.suppressNextScrollTimer);\n }\n\n this.suppressNextScrollTimer = window.setTimeout(() => {\n this.set('suppressNextScroll', false);\n }, SCROLL_DEBOUNCE_TIME_MS * SCROLL_SUPPRESS_MULTIPLIER);\n };\n\n this.handlers.pageView = new PageViewHandler(this.managers.event as EventManager, onPageView);\n this.handlers.pageView.startTracking();\n\n this.handlers.click = new ClickHandler(this.managers.event as EventManager);\n this.handlers.click.startTracking();\n\n this.handlers.scroll = new ScrollHandler(this.managers.event as EventManager);\n this.handlers.scroll.startTracking();\n\n this.handlers.performance = new PerformanceHandler(this.managers.event as EventManager);\n this.handlers.performance.startTracking().catch((error) => {\n log('warn', 'Failed to start performance tracking', { error });\n });\n\n this.handlers.error = new ErrorHandler(this.managers.event as EventManager);\n this.handlers.error.startTracking();\n\n if (config.viewport) {\n this.handlers.viewport = new ViewportHandler(this.managers.event as EventManager);\n this.handlers.viewport.startTracking();\n }\n }\n}\n","import { App } from './app';\nimport {\n MetadataType,\n Config,\n EmitterCallback,\n EmitterMap,\n TransformerHook,\n BeforeSendTransformer,\n BeforeBatchTransformer,\n CustomHeadersProvider,\n} from './types';\nimport { log, validateAndNormalizeConfig, setQaMode as setQaModeUtil } from './utils';\nimport { INITIALIZATION_TIMEOUT_MS } from './constants';\nimport './types/window.types';\n\ninterface PendingListener {\n event: keyof EmitterMap;\n callback: EmitterCallback<EmitterMap[keyof EmitterMap]>;\n}\n\ninterface PendingTransformer {\n hook: TransformerHook;\n fn: BeforeSendTransformer | BeforeBatchTransformer;\n}\n\nconst pendingListeners: PendingListener[] = [];\nconst pendingTransformers: PendingTransformer[] = [];\nlet pendingCustomHeadersProvider: CustomHeadersProvider | null = null;\n\nlet app: App | null = null;\nlet isInitializing = false;\nlet isDestroying = false;\n\n/**\n * Initializes TraceLog and begins tracking user interactions.\n *\n * Important: Register listeners with on() before calling init() to capture initial events.\n *\n * @param config - Optional configuration object\n * @returns Promise that resolves when initialization completes (5s timeout)\n * @throws {Error} If initialization fails or times out\n *\n * @example\n * ```typescript\n * await tracelog.init({\n * integrations: {\n * tracelog: { projectId: 'your-project-id' }\n * }\n * });\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#init} for configuration options\n */\nexport const init = async (config?: Config): Promise<void> => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n isDestroying = false;\n\n if (window.__traceLogDisabled === true) {\n return;\n }\n\n if (app) {\n return;\n }\n\n if (isInitializing) {\n return;\n }\n\n isInitializing = true;\n\n try {\n const validatedConfig = validateAndNormalizeConfig(config ?? {});\n const instance = new App();\n\n try {\n pendingListeners.forEach(({ event, callback }) => {\n instance.on(event, callback);\n });\n\n pendingListeners.length = 0;\n\n pendingTransformers.forEach(({ hook, fn }) => {\n if (hook === 'beforeSend') {\n instance.setTransformer('beforeSend', fn as BeforeSendTransformer);\n } else {\n instance.setTransformer('beforeBatch', fn as BeforeBatchTransformer);\n }\n });\n\n pendingTransformers.length = 0;\n\n // Apply pending custom headers provider\n if (pendingCustomHeadersProvider) {\n instance.setCustomHeaders(pendingCustomHeadersProvider);\n pendingCustomHeadersProvider = null;\n }\n\n const initPromise = instance.init(validatedConfig);\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => {\n reject(new Error(`[TraceLog] Initialization timeout after ${INITIALIZATION_TIMEOUT_MS}ms`));\n }, INITIALIZATION_TIMEOUT_MS);\n });\n\n await Promise.race([initPromise, timeoutPromise]);\n\n app = instance;\n } catch (error) {\n try {\n instance.destroy(true);\n } catch (cleanupError) {\n log('error', 'Failed to cleanup partially initialized app', { error: cleanupError });\n }\n\n throw error;\n }\n } catch (error) {\n app = null;\n throw error;\n } finally {\n isInitializing = false;\n }\n};\n\n/**\n * Tracks a custom analytics event with optional metadata.\n *\n * @param name - Event identifier (e.g., 'checkout_completed')\n * @param metadata - Optional event data (object or array of objects)\n * @throws {Error} If called before init() or during destroy()\n *\n * @example\n * ```typescript\n * tracelog.event('product_viewed', {\n * productId: 'abc-123',\n * price: 299.99\n * });\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#event} for rate limiting details\n */\nexport const event = (name: string, metadata?: Record<string, MetadataType> | Record<string, MetadataType>[]): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (!app) {\n throw new Error('[TraceLog] TraceLog not initialized. Please call init() first.');\n }\n\n if (isDestroying) {\n throw new Error('[TraceLog] Cannot send events while TraceLog is being destroyed');\n }\n\n app.sendCustomEvent(name, metadata);\n};\n\n/**\n * Subscribes to TraceLog events for real-time consumption.\n *\n * Important: Register listeners BEFORE calling init() to capture SESSION_START and PAGE_VIEW.\n *\n * @param event - Event type ('event' or 'queue')\n * @param callback - Handler function called when event fires\n *\n * @example\n * ```typescript\n * tracelog.on('event', (event) => console.log(event.type));\n * await tracelog.init();\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib#event-listeners} for timing details\n */\nexport const on = <K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (!app || isInitializing) {\n pendingListeners.push({ event, callback } as PendingListener);\n return;\n }\n\n app.on(event, callback);\n};\n\n/**\n * Unsubscribes from TraceLog events.\n *\n * @param event - Event type to unsubscribe from\n * @param callback - Exact callback function reference used in on()\n *\n * @example\n * ```typescript\n * const handler = (event) => console.log(event.type);\n * tracelog.on('event', handler);\n * tracelog.off('event', handler);\n * ```\n */\nexport const off = <K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (!app) {\n const index = pendingListeners.findIndex((l) => l.event === event && l.callback === callback);\n if (index !== -1) {\n pendingListeners.splice(index, 1);\n }\n return;\n }\n\n app.off(event, callback);\n};\n\n/**\n * Transforms events at runtime before sending to custom backend.\n *\n * Note: Only applies to custom backend integration. TraceLog SaaS ignores transformers.\n *\n * @param hook - 'beforeSend' (per-event) or 'beforeBatch' (batch-level)\n * @param fn - Transformer function. Return null to filter event/batch.\n * @throws {Error} If called during destroy()\n * @throws {Error} If fn is not a function\n *\n * @example\n * ```typescript\n * tracelog.setTransformer('beforeSend', (data) => {\n * if ('type' in data) {\n * return { ...data, appVersion: '1.0.0' };\n * }\n * return data;\n * });\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#settransformer} for integration behavior\n */\nexport function setTransformer(hook: 'beforeSend', fn: BeforeSendTransformer): void;\nexport function setTransformer(hook: 'beforeBatch', fn: BeforeBatchTransformer): void;\nexport function setTransformer(hook: TransformerHook, fn: BeforeSendTransformer | BeforeBatchTransformer): void {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (typeof fn !== 'function') {\n throw new Error(`[TraceLog] Transformer must be a function, received: ${typeof fn}`);\n }\n\n if (!app || isInitializing) {\n const existingIndex = pendingTransformers.findIndex((t) => t.hook === hook);\n\n if (existingIndex !== -1) {\n pendingTransformers.splice(existingIndex, 1);\n }\n\n pendingTransformers.push({ hook, fn });\n\n return;\n }\n\n if (isDestroying) {\n throw new Error('[TraceLog] Cannot set transformers while TraceLog is being destroyed');\n }\n\n if (hook === 'beforeSend') {\n app.setTransformer('beforeSend', fn as BeforeSendTransformer);\n } else {\n app.setTransformer('beforeBatch', fn as BeforeBatchTransformer);\n }\n}\n\n/**\n * Removes a previously set transformer function.\n *\n * @param hook - Transformer hook type to remove ('beforeSend' or 'beforeBatch')\n * @throws {Error} If called during destroy()\n *\n * @example\n * ```typescript\n * tracelog.removeTransformer('beforeSend');\n * ```\n */\nexport const removeTransformer = (hook: TransformerHook): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (!app) {\n const index = pendingTransformers.findIndex((t) => t.hook === hook);\n\n if (index !== -1) {\n pendingTransformers.splice(index, 1);\n }\n\n return;\n }\n\n if (isDestroying) {\n throw new Error('[TraceLog] Cannot remove transformers while TraceLog is being destroyed');\n }\n\n app.removeTransformer(hook);\n};\n\n/**\n * Sets a callback to provide custom HTTP headers for requests to custom backends.\n *\n * Can be called before or after init(). Only applies to custom backend integration\n * (not TraceLog SaaS). Headers are NOT applied to sendBeacon requests (page unload).\n *\n * @param provider - Callback function that returns custom headers object\n * @throws {Error} If provider is not a function\n * @throws {Error} If called during destroy()\n *\n * @example\n * ```typescript\n * // Set before or after init\n * tracelog.setCustomHeaders(() => ({\n * 'Authorization': `Bearer ${getAuthToken()}`,\n * 'X-Request-ID': crypto.randomUUID()\n * }));\n *\n * await tracelog.init({ integrations: { custom: { collectApiUrl: '...' } } });\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#setcustomheaders} for full documentation\n */\nexport const setCustomHeaders = (provider: CustomHeadersProvider): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (typeof provider !== 'function') {\n throw new Error(`[TraceLog] Custom headers provider must be a function, received: ${typeof provider}`);\n }\n\n if (!app || isInitializing) {\n pendingCustomHeadersProvider = provider;\n return;\n }\n\n if (isDestroying) {\n throw new Error('[TraceLog] Cannot set custom headers while TraceLog is being destroyed');\n }\n\n app.setCustomHeaders(provider);\n};\n\n/**\n * Removes the custom headers provider callback.\n *\n * @throws {Error} If called during destroy()\n *\n * @example\n * ```typescript\n * tracelog.removeCustomHeaders();\n * ```\n */\nexport const removeCustomHeaders = (): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (!app) {\n pendingCustomHeadersProvider = null;\n return;\n }\n\n if (isDestroying) {\n throw new Error('[TraceLog] Cannot remove custom headers while TraceLog is being destroyed');\n }\n\n app.removeCustomHeaders();\n};\n\n/**\n * Checks if TraceLog is currently initialized.\n *\n * @returns true if initialized, false otherwise\n *\n * @example\n * ```typescript\n * if (tracelog.isInitialized()) {\n * tracelog.event('app_ready');\n * }\n * ```\n */\nexport const isInitialized = (): boolean => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return false;\n }\n\n return app !== null;\n};\n\n/**\n * Stops all tracking, cleans up listeners, and flushes pending events.\n *\n * Sends remaining events with sendBeacon before cleanup.\n *\n * @throws {Error} If destroy operation is already in progress\n *\n * @example\n * ```typescript\n * tracelog.destroy();\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#destroy} for usage patterns\n */\nexport const destroy = (): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (isDestroying) {\n throw new Error('[TraceLog] Destroy operation already in progress');\n }\n\n if (!app) {\n isDestroying = false;\n\n return;\n }\n\n isDestroying = true;\n\n try {\n app.destroy();\n app = null;\n isInitializing = false;\n pendingListeners.length = 0;\n pendingTransformers.length = 0;\n pendingCustomHeadersProvider = null;\n\n if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined' && window.__traceLogBridge) {\n window.__traceLogBridge = undefined;\n }\n\n isDestroying = false;\n } catch (error) {\n app = null;\n isInitializing = false;\n\n pendingListeners.length = 0;\n pendingTransformers.length = 0;\n pendingCustomHeadersProvider = null;\n\n isDestroying = false;\n\n log('warn', 'Error during destroy, forced cleanup completed', { error });\n }\n};\n\n/**\n * Enables or disables QA (Quality Assurance) mode for debugging.\n *\n * QA mode logs custom events to console instead of sending to backend.\n *\n * @param enabled - true to enable, false to disable\n *\n * @example\n * ```typescript\n * tracelog.setQaMode(true);\n * tracelog.event('test', { key: 'value' }); // Logged to console\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#setqamode} for URL activation\n */\nexport const setQaMode = (enabled: boolean): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n setQaModeUtil(enabled);\n};\n\n/**\n * Replaces all global metadata with new values.\n *\n * Global metadata is automatically appended to every event.\n *\n * @param metadata - New metadata object (replaces existing)\n * @throws {Error} If TraceLog not initialized\n * @throws {Error} If called during destroy()\n * @throws {Error} If validation fails (max 100 keys, 10KB limit)\n *\n * @example\n * ```typescript\n * tracelog.updateGlobalMetadata({ userId: 'user-123', plan: 'pro' });\n * // Replaces all existing metadata\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#updateglobalmetadata} for validation rules\n */\nexport const updateGlobalMetadata = (metadata: Record<string, MetadataType>): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (!app) {\n throw new Error('[TraceLog] TraceLog not initialized. Please call init() first.');\n }\n\n if (isDestroying) {\n throw new Error('[TraceLog] Cannot update metadata while TraceLog is being destroyed');\n }\n\n app.updateGlobalMetadata(metadata);\n};\n\n/**\n * Merges new metadata with existing global metadata (shallow merge).\n *\n * Global metadata is automatically appended to every event.\n *\n * @param metadata - Metadata to merge with existing values\n * @throws {Error} If TraceLog not initialized\n * @throws {Error} If called during destroy()\n * @throws {Error} If validation fails (max 100 keys, 10KB limit)\n *\n * @example\n * ```typescript\n * tracelog.mergeGlobalMetadata({ userId: 'user-123' });\n * // Preserves existing keys, adds/overwrites specified keys\n * ```\n *\n * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#mergeglobalmetadata} for validation rules\n */\nexport const mergeGlobalMetadata = (metadata: Record<string, MetadataType>): void => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n if (!app) {\n throw new Error('[TraceLog] TraceLog not initialized. Please call init() first.');\n }\n\n if (isDestroying) {\n throw new Error('[TraceLog] Cannot update metadata while TraceLog is being destroyed');\n }\n\n app.mergeGlobalMetadata(metadata);\n};\n\n/**\n * @internal TestBridge API - development only\n */\nexport const __setAppInstance = (instance: App | null): void => {\n if (process.env.NODE_ENV !== 'development') {\n return;\n }\n\n if (instance !== null) {\n const hasRequiredMethods =\n typeof instance === 'object' &&\n 'init' in instance &&\n 'destroy' in instance &&\n 'on' in instance &&\n 'off' in instance;\n\n if (!hasRequiredMethods) {\n throw new Error('[TraceLog] Invalid app instance type');\n }\n }\n\n if (app !== null && instance !== null && app !== instance) {\n throw new Error('[TraceLog] Cannot overwrite existing app instance. Call destroy() first.');\n }\n\n app = instance;\n};\n\n/**\n * @internal TestBridge state accessors - development only\n */\nexport const __getInitState = (): { isInitializing: boolean; isDestroying: boolean } => {\n if (process.env.NODE_ENV !== 'development') {\n return { isInitializing: false, isDestroying: false };\n }\n return { isInitializing, isDestroying };\n};\n\nif (process.env.NODE_ENV === 'development' && typeof window !== 'undefined' && typeof document !== 'undefined') {\n void import('./test-bridge')\n .then((module) => {\n if (typeof module.injectTestBridge === 'function') {\n module.injectTestBridge();\n }\n })\n .catch(() => {\n // Silent fail - TestBridge is optional in test environments\n });\n\n void import('./utils/browser/mode.utils')\n .then((module) => {\n if (typeof module.detectQaMode === 'function') {\n module.detectQaMode();\n }\n })\n .catch(() => {\n // Silent fail - mode detection is optional\n });\n}\n","import {\n init,\n event,\n on,\n off,\n isInitialized,\n destroy,\n setQaMode,\n setTransformer,\n removeTransformer,\n setCustomHeaders,\n removeCustomHeaders,\n updateGlobalMetadata,\n mergeGlobalMetadata,\n} from './api';\n\n// Constants\nexport * from './app.constants';\n\n// Types\nexport * from './types';\n\n// TraceLog namespace containing all API methods\nexport const tracelog = {\n init,\n event,\n on,\n off,\n setTransformer,\n removeTransformer,\n setCustomHeaders,\n removeCustomHeaders,\n isInitialized,\n destroy,\n setQaMode,\n updateGlobalMetadata,\n mergeGlobalMetadata,\n};\n","var e,n,t,r,i,o=-1,a=function(e){addEventListener(\"pageshow\",(function(n){n.persisted&&(o=n.timeStamp,e(n))}),!0)},c=function(){var e=self.performance&&performance.getEntriesByType&&performance.getEntriesByType(\"navigation\")[0];if(e&&e.responseStart>0&&e.responseStart<performance.now())return e},u=function(){var e=c();return e&&e.activationStart||0},f=function(e,n){var t=c(),r=\"navigate\";o>=0?r=\"back-forward-cache\":t&&(document.prerendering||u()>0?r=\"prerender\":document.wasDiscarded?r=\"restore\":t.type&&(r=t.type.replace(/_/g,\"-\")));return{name:e,value:void 0===n?-1:n,rating:\"good\",delta:0,entries:[],id:\"v4-\".concat(Date.now(),\"-\").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:r}},s=function(e,n,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){var r=new PerformanceObserver((function(e){Promise.resolve().then((function(){n(e.getEntries())}))}));return r.observe(Object.assign({type:e,buffered:!0},t||{})),r}}catch(e){}},d=function(e,n,t,r){var i,o;return function(a){n.value>=0&&(a||r)&&((o=n.value-(i||0))||void 0===i)&&(i=n.value,n.delta=o,n.rating=function(e,n){return e>n[1]?\"poor\":e>n[0]?\"needs-improvement\":\"good\"}(n.value,t),e(n))}},l=function(e){requestAnimationFrame((function(){return requestAnimationFrame((function(){return e()}))}))},p=function(e){document.addEventListener(\"visibilitychange\",(function(){\"hidden\"===document.visibilityState&&e()}))},v=function(e){var n=!1;return function(){n||(e(),n=!0)}},m=-1,h=function(){return\"hidden\"!==document.visibilityState||document.prerendering?1/0:0},g=function(e){\"hidden\"===document.visibilityState&&m>-1&&(m=\"visibilitychange\"===e.type?e.timeStamp:0,T())},y=function(){addEventListener(\"visibilitychange\",g,!0),addEventListener(\"prerenderingchange\",g,!0)},T=function(){removeEventListener(\"visibilitychange\",g,!0),removeEventListener(\"prerenderingchange\",g,!0)},E=function(){return m<0&&(m=h(),y(),a((function(){setTimeout((function(){m=h(),y()}),0)}))),{get firstHiddenTime(){return m}}},C=function(e){document.prerendering?addEventListener(\"prerenderingchange\",(function(){return e()}),!0):e()},b=[1800,3e3],S=function(e,n){n=n||{},C((function(){var t,r=E(),i=f(\"FCP\"),o=s(\"paint\",(function(e){e.forEach((function(e){\"first-contentful-paint\"===e.name&&(o.disconnect(),e.startTime<r.firstHiddenTime&&(i.value=Math.max(e.startTime-u(),0),i.entries.push(e),t(!0)))}))}));o&&(t=d(e,i,b,n.reportAllChanges),a((function(r){i=f(\"FCP\"),t=d(e,i,b,n.reportAllChanges),l((function(){i.value=performance.now()-r.timeStamp,t(!0)}))})))}))},L=[.1,.25],w=function(e,n){n=n||{},S(v((function(){var t,r=f(\"CLS\",0),i=0,o=[],c=function(e){e.forEach((function(e){if(!e.hadRecentInput){var n=o[0],t=o[o.length-1];i&&e.startTime-t.startTime<1e3&&e.startTime-n.startTime<5e3?(i+=e.value,o.push(e)):(i=e.value,o=[e])}})),i>r.value&&(r.value=i,r.entries=o,t())},u=s(\"layout-shift\",c);u&&(t=d(e,r,L,n.reportAllChanges),p((function(){c(u.takeRecords()),t(!0)})),a((function(){i=0,r=f(\"CLS\",0),t=d(e,r,L,n.reportAllChanges),l((function(){return t()}))})),setTimeout(t,0))})))},A=0,I=1/0,P=0,M=function(e){e.forEach((function(e){e.interactionId&&(I=Math.min(I,e.interactionId),P=Math.max(P,e.interactionId),A=P?(P-I)/7+1:0)}))},k=function(){return e?A:performance.interactionCount||0},F=function(){\"interactionCount\"in performance||e||(e=s(\"event\",M,{type:\"event\",buffered:!0,durationThreshold:0}))},D=[],x=new Map,R=0,B=function(){var e=Math.min(D.length-1,Math.floor((k()-R)/50));return D[e]},H=[],q=function(e){if(H.forEach((function(n){return n(e)})),e.interactionId||\"first-input\"===e.entryType){var n=D[D.length-1],t=x.get(e.interactionId);if(t||D.length<10||e.duration>n.latency){if(t)e.duration>t.latency?(t.entries=[e],t.latency=e.duration):e.duration===t.latency&&e.startTime===t.entries[0].startTime&&t.entries.push(e);else{var r={id:e.interactionId,latency:e.duration,entries:[e]};x.set(r.id,r),D.push(r)}D.sort((function(e,n){return n.latency-e.latency})),D.length>10&&D.splice(10).forEach((function(e){return x.delete(e.id)}))}}},O=function(e){var n=self.requestIdleCallback||self.setTimeout,t=-1;return e=v(e),\"hidden\"===document.visibilityState?e():(t=n(e),p(e)),t},N=[200,500],j=function(e,n){\"PerformanceEventTiming\"in self&&\"interactionId\"in PerformanceEventTiming.prototype&&(n=n||{},C((function(){var t;F();var r,i=f(\"INP\"),o=function(e){O((function(){e.forEach(q);var n=B();n&&n.latency!==i.value&&(i.value=n.latency,i.entries=n.entries,r())}))},c=s(\"event\",o,{durationThreshold:null!==(t=n.durationThreshold)&&void 0!==t?t:40});r=d(e,i,N,n.reportAllChanges),c&&(c.observe({type:\"first-input\",buffered:!0}),p((function(){o(c.takeRecords()),r(!0)})),a((function(){R=k(),D.length=0,x.clear(),i=f(\"INP\"),r=d(e,i,N,n.reportAllChanges)})))})))},_=[2500,4e3],z={},G=function(e,n){n=n||{},C((function(){var t,r=E(),i=f(\"LCP\"),o=function(e){n.reportAllChanges||(e=e.slice(-1)),e.forEach((function(e){e.startTime<r.firstHiddenTime&&(i.value=Math.max(e.startTime-u(),0),i.entries=[e],t())}))},c=s(\"largest-contentful-paint\",o);if(c){t=d(e,i,_,n.reportAllChanges);var m=v((function(){z[i.id]||(o(c.takeRecords()),c.disconnect(),z[i.id]=!0,t(!0))}));[\"keydown\",\"click\"].forEach((function(e){addEventListener(e,(function(){return O(m)}),{once:!0,capture:!0})})),p(m),a((function(r){i=f(\"LCP\"),t=d(e,i,_,n.reportAllChanges),l((function(){i.value=performance.now()-r.timeStamp,z[i.id]=!0,t(!0)}))}))}}))},J=[800,1800],K=function e(n){document.prerendering?C((function(){return e(n)})):\"complete\"!==document.readyState?addEventListener(\"load\",(function(){return e(n)}),!0):setTimeout(n,0)},Q=function(e,n){n=n||{};var t=f(\"TTFB\"),r=d(e,t,J,n.reportAllChanges);K((function(){var i=c();i&&(t.value=Math.max(i.responseStart-u(),0),t.entries=[i],r(!0),a((function(){t=f(\"TTFB\",0),(r=d(e,t,J,n.reportAllChanges))(!0)})))}))},U={passive:!0,capture:!0},V=new Date,W=function(e,i){n||(n=i,t=e,r=new Date,Z(removeEventListener),X())},X=function(){if(t>=0&&t<r-V){var e={entryType:\"first-input\",name:n.type,target:n.target,cancelable:n.cancelable,startTime:n.timeStamp,processingStart:n.timeStamp+t};i.forEach((function(n){n(e)})),i=[]}},Y=function(e){if(e.cancelable){var n=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;\"pointerdown\"==e.type?function(e,n){var t=function(){W(e,n),i()},r=function(){i()},i=function(){removeEventListener(\"pointerup\",t,U),removeEventListener(\"pointercancel\",r,U)};addEventListener(\"pointerup\",t,U),addEventListener(\"pointercancel\",r,U)}(n,e):W(n,e)}},Z=function(e){[\"mousedown\",\"keydown\",\"touchstart\",\"pointerdown\"].forEach((function(n){return e(n,Y,U)}))},$=[100,300],ee=function(e,r){r=r||{},C((function(){var o,c=E(),u=f(\"FID\"),l=function(e){e.startTime<c.firstHiddenTime&&(u.value=e.processingStart-e.startTime,u.entries.push(e),o(!0))},m=function(e){e.forEach(l)},h=s(\"first-input\",m);o=d(e,u,$,r.reportAllChanges),h&&(p(v((function(){m(h.takeRecords()),h.disconnect()}))),a((function(){var a;u=f(\"FID\"),o=d(e,u,$,r.reportAllChanges),i=[],t=-1,n=null,Z(addEventListener),a=l,i.push(a),X()})))}))};export{L as CLSThresholds,b as FCPThresholds,$ as FIDThresholds,N as INPThresholds,_ as LCPThresholds,J as TTFBThresholds,w as onCLS,S as onFCP,ee as onFID,j as onINP,G as onLCP,Q as onTTFB};\n"],"names":["DEFAULT_SESSION_TIMEOUT","MAX_CUSTOM_EVENT_NAME_LENGTH","MAX_CUSTOM_EVENT_STRING_SIZE","MAX_CUSTOM_EVENT_KEYS","MAX_CUSTOM_EVENT_ARRAY_SIZE","MAX_NESTED_OBJECT_KEYS","MAX_METADATA_NESTING_DEPTH","MAX_STRING_LENGTH","MAX_STRING_LENGTH_IN_ARRAY","MAX_ARRAY_LENGTH","HTML_DATA_ATTR_PREFIX","INTERACTIVE_SELECTORS","UTM_PARAMS","DEFAULT_SENSITIVE_QUERY_PARAMS","VALIDATION_MESSAGES","XSS_PATTERNS","STORAGE_BASE_KEY","QA_MODE_KEY","USER_ID_KEY","QA_MODE_URL_PARAM","QA_MODE_ENABLE_VALUE","QA_MODE_DISABLE_VALUE","QUEUE_KEY","id","SESSION_STORAGE_KEY","BROADCAST_CHANNEL_NAME","SESSION_COUNTS_KEY","userId","sessionId","SESSION_COUNTS_EXPIRY_MS","SESSION_COUNTS_LAST_CLEANUP_KEY","SESSION_COUNTS_CLEANUP_THROTTLE_MS","SpecialApiUrl","DeviceType","EmitterEvent","PermanentError","message","statusCode","EventType","ScrollDirection","ErrorType","Mode","isPrimaryScrollEvent","event","isSecondaryScrollEvent","TraceLogValidationError","errorCode","layer","AppConfigValidationError","SessionTimeoutValidationError","SamplingRateValidationError","IntegrationValidationError","InitializationTimeoutError","timeoutMs","LOG_STYLE_ACTIVE","LOG_STYLE_DISABLED","LOG_STYLE_CRITICAL","formatLogMsg","msg","error","sanitizedMessage","isQaModeActive","log","type","extra","data","showToClient","style","visibility","formattedMsg","method","shouldShowLog","effectiveStyle","getEffectiveStyle","sanitizedData","sanitizeLogData","outputLog","providedStyle","hasStyle","styledMsg","sanitized","sensitiveKeys","key","value","lowerKey","sensitiveKey","item","coarsePointerQuery","noHoverQuery","initMediaQueries","UNKNOWN","detectOS","nav","platform","ua","detectBrowser","brands","firstBrand","b","brand","getDeviceType","uaPlatform","width","hasCoarsePointer","hasNoHover","hasTouchSupport","isMobileUA","isTabletUA","getDeviceInfo","PII_PATTERNS","MAX_ERROR_MESSAGE_LENGTH","ERROR_SUPPRESSION_WINDOW_MS","MAX_TRACKED_ERRORS","MAX_TRACKED_ERRORS_HARD_LIMIT","DEFAULT_ERROR_SAMPLING_RATE","ERROR_BURST_WINDOW_MS","ERROR_BURST_THRESHOLD","ERROR_BURST_BACKOFF_MS","PERMANENT_ERROR_LOG_THROTTLE_MS","WEB_VITALS_GOOD_THRESHOLDS","WEB_VITALS_NEEDS_IMPROVEMENT_THRESHOLDS","WEB_VITALS_POOR_THRESHOLDS","DEFAULT_WEB_VITALS_MODE","getWebVitalsThresholds","mode","LONG_TASK_THROTTLE_MS","MAX_NAVIGATION_HISTORY","LIB_VERSION","version","isBrowserEnvironment","cleanUrlParameter","params","search","url","detectQaMode","urlParam","storedState","newState","setQaMode","enabled","COMPOUND_TLDS","getRootDomain","hostname","parts","lastTwo","isSameDomain","hostname1","hostname2","getExternalReferrer","referrer","referrerHostname","currentHostname","getUTMParameters","urlParams","utmParams","param","generateUUID","c","r","eventSequence","lastEventTimestamp","generateEventId","timestamp","sequence","random","bytes","isValidUrl","allowHttp","parsed","isHttps","isHttp","generateSaasApiUrl","projectId","host","cleanDomain","collectApiUrl","getCollectApiUrls","config","urls","customApiUrl","normalizeUrl","sensitiveQueryParams","urlObject","searchParams","allSensitiveParams","hasChanged","removedParams","sanitizeString","xssPatternMatches","pattern","beforeReplace","sanitizeValue","depth","sanitizedObject","limitedEntries","value_","sanitizedKey","sanitizedValue","sanitizeMetadata","metadata","errorMessage","validateAppConfig","validateIntegrations","validateViewportConfig","validModes","validKeys","viewport","uniqueSelectors","element","normalizedSelector","integrations","validateAndNormalizeConfig","normalizedConfig","isValidArrayItem","entries","isOnlyPrimitiveFields","object","isValidEventName","eventName","validateSingleMetadata","sanitizedMetadata","intro","jsonString","isValidMetadata","sanitizedArray","itemValidation","isEventValid","nameValidation","metadataValidation","Emitter","callback","callbacks","index","transformEvent","transformer","context","result","transformEvents","events","transformBatch","batch","globalState","StateManager","SenderManager","storeManager","integrationId","apiUrl","transformers","staticHeaders","customHeadersProvider","provider","dynamicHeaders","baseKey","body","success","persistedData","beforeSendTransformer","transformedEvents","beforeBatchTransformer","attempt","exponentialDelay","jitter","totalDelay","resolve","afterBeforeSend","transformedBody","payload","isLastAttempt","controller","timeoutId","customHeaders","response","blob","accepted","enrichedBody","storageKey","persistedDataString","queue","existing","timeSinceExisting","delay","now","TimeManager","elapsed","monotonicTimestamp","systemTimestamp","currentTime","offset","EventManager","emitter","collectApiUrls","recoveryPromises","sender","_eventCount","recoveredEvents","eventIds","e","page_url","from_page_url","scroll_data","click_data","custom_event","web_vitals","error_data","viewport_data","page_view","currentSessionId","isCriticalEvent","eventType","typeLimit","currentCount","maxSameEventPerMinute","isSessionStart","currentPageUrl","displayName","bufferedEvents","isSync","eventsToSend","anySucceeded","sendPromises","results","failedCount","eventMap","order","signature","a","hasAnyBackend","transformed","validation","sessionReferrer","sessionUtm","hasCustomBackend","hasSaasBackend","isMultiIntegration","fingerprint","lastSeen","cutoff","x","y","nonCriticalIndex","removedEvent","samplingRate","validTimestamps","ts","eventIdSet","eventData","fn","args","stored","lastCleanup","timeSinceLastCleanup","prefix","keysToRemove","dataToStore","UserManager","storageManager","storedUserId","newUserId","SESSION_ID_PATTERN","SessionManager","eventManager","action","messageProjectId","storedSession","sessionTimeout","lastActivity","utm","storedData","session","recoveredSessionId","newSessionId","SessionHandler","PageViewHandler","onTrack","original","rawUrl","normalizedUrl","throttleMs","fromUrl","pageViewData","pathname","hash","title","ClickHandler","mouseEvent","target","clickedElement","clickThrottleMs","trackingElement","relevantClickElement","coordinates","trackingData","attributeData","clickData","lastClickTime","excessCount","toDelete","testId","tlogName","path","current","selector","firstClass","parent","rect","relativeX","relativeY","name","relevantElement","text","attributes","regex","clickedText","relevantText","finalText","commonAttributes","attributeName","ScrollHandler","container","elements","walker","node","htmlElement","initialScrollTop","initialDepth","isPrimary","handleScroll","scrollData","newDepth","previous","scrollTop","scrollHeight","viewportHeight","maxScrollTop","lastScrollPos","lastEventTime","positionDelta","direction","timeDelta","velocity","hasVerticalScrollableOverflow","hasVerticalOverflowContent","targetElement","ViewportHandler","threshold","minDwellTime","tracked","maxTrackedElements","totalTracked","elementConfig","entry","visibilityRatio","dwellTime","cooldownPeriod","mutations","hasAddedNodes","mutation","removedNodes","el","descendantTracked","StorageManager","retryError","i","tracelogKeys","persistedEventsKeys","criticalPrefixes","nonCriticalKeys","storage","testKey","PerformanceHandler","obs","list","last","clsValue","currentNavId","navId","worst","dur","onLCP","onCLS","onFCP","onTTFB","onINP","webVitals","report","metric","ttfb","duration","sample","reportedForNav","oldestNav","counter","baseId","supported","cb","options","once","observer","callbackError","ErrorHandler","reason","lastSeenAt","excess","App","normalizedMetadata","valid","hook","force","handler","device","pageUrl","updatedConfig","currentConfig","mergedMetadata","onPageView","pendingListeners","pendingTransformers","pendingCustomHeadersProvider","app","isInitializing","isDestroying","init","validatedConfig","instance","initPromise","timeoutPromise","_","reject","cleanupError","on","off","l","setTransformer","existingIndex","t","removeTransformer","setCustomHeaders","removeCustomHeaders","isInitialized","destroy","setQaModeUtil","updateGlobalMetadata","mergeGlobalMetadata","tracelog","o","n","u","f","s","d","p","v","m","h","g","T","E","C","S","L","w","A","I","P","M","k","F","D","R","B","H","q","O","N","j","z","G","J","K","Q"],"mappings":"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","x_google_ignoreList":[45]}