@tracelog/lib 2.10.0-rc.113.17 → 2.10.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.
@@ -78,10 +78,66 @@ interface InitResult {
78
78
  sessionId: string;
79
79
  }
80
80
 
81
+ /**
82
+ * Element configuration for viewport tracking with optional identifiers
83
+ */
84
+ interface ViewportElement {
85
+ /**
86
+ * CSS selector for the element
87
+ * @example '.hero' | '.cta-button' | '#pricing'
88
+ */
89
+ selector: string;
90
+ /**
91
+ * Optional unique identifier for analytics
92
+ * Used to distinguish between multiple elements with the same selector
93
+ * @example 'homepage-hero' | 'pricing-cta' | 'customer-testimonials'
94
+ */
95
+ id?: string;
96
+ /**
97
+ * Optional human-readable name for dashboards and reports
98
+ * @example 'Homepage Hero Banner' | 'Pricing Page CTA' | 'Customer Testimonials Section'
99
+ */
100
+ name?: string;
101
+ }
102
+ /**
103
+ * Configuration for viewport visibility tracking
104
+ */
105
+ interface ViewportConfig {
106
+ /**
107
+ * Elements to track with optional identifiers
108
+ * Provides analytics support with business identifiers
109
+ * @example [{ selector: '.hero', id: 'homepage-hero', name: 'Homepage Hero Banner' }]
110
+ */
111
+ elements: ViewportElement[];
112
+ /**
113
+ * Minimum percentage of element that must be visible (0-1)
114
+ * @default 0.5 (50%)
115
+ */
116
+ threshold?: number;
117
+ /**
118
+ * Minimum time (ms) element must be visible to count as a view
119
+ * @default 1000 (1 second)
120
+ */
121
+ minDwellTime?: number;
122
+ /**
123
+ * Cooldown period (ms) before same element can fire visibility event again
124
+ * Prevents repeated events from carousels, sticky headers, and scrolling patterns
125
+ * @default 60000 (60 seconds)
126
+ */
127
+ cooldownPeriod?: number;
128
+ /**
129
+ * Maximum number of elements to track simultaneously (Phase 3)
130
+ * Prevents memory/server issues with broad selectors
131
+ * @default 100
132
+ */
133
+ maxTrackedElements?: number;
134
+ }
135
+
81
136
  /**
82
137
  * Coordinate information from a click event
138
+ * Includes absolute and relative positioning
83
139
  */
84
- type ClickCoordinates = Pick<ClickData, 'x' | 'y'>;
140
+ type ClickCoordinates = Pick<ClickData, 'x' | 'y' | 'relativeX' | 'relativeY'>;
85
141
  /**
86
142
  * Web performance metric types tracked by the library
87
143
  * - LCP: Largest Contentful Paint
@@ -89,8 +145,9 @@ type ClickCoordinates = Pick<ClickData, 'x' | 'y'>;
89
145
  * - INP: Interaction to Next Paint
90
146
  * - FCP: First Contentful Paint
91
147
  * - TTFB: Time to First Byte
148
+ * - LONG_TASK: Tasks exceeding 50ms
92
149
  */
93
- type WebVitalType = 'LCP' | 'CLS' | 'INP' | 'FCP' | 'TTFB';
150
+ type WebVitalType = 'LCP' | 'CLS' | 'INP' | 'FCP' | 'TTFB' | 'LONG_TASK';
94
151
  /**
95
152
  * Event type name
96
153
  */
@@ -112,13 +169,32 @@ declare enum EventType {
112
169
  /** Performance metrics */
113
170
  WEB_VITALS = "web_vitals",
114
171
  /** JavaScript errors and rejections */
115
- ERROR = "error"
172
+ ERROR = "error",
173
+ /** Element visibility tracking */
174
+ VIEWPORT_VISIBLE = "viewport_visible"
116
175
  }
117
176
  /**
118
- * Per-session event counts structure for rate limiting.
119
- *
120
- * Persisted to localStorage: `tlog:{userId}:session_counts:{sessionId}`
121
- * Restored on page reload to maintain limits across navigations.
177
+ * Per-session event counts structure for rate limiting
178
+ *
179
+ * **Purpose**: Tracks how many events of each type have been generated
180
+ * during the current session to enforce per-session limits and prevent
181
+ * runaway event generation.
182
+ *
183
+ * **Usage**:
184
+ * - Persisted to localStorage: `tlog:{userId}:session_counts:{sessionId}`
185
+ * - Restored on page reload to maintain limits across navigations
186
+ * - Debounced writes for performance (500ms delay)
187
+ *
188
+ * **Limits** (from config.constants.ts):
189
+ * - Total: 1000 events per session
190
+ * - Clicks: 500 per session
191
+ * - Page Views: 100 per session
192
+ * - Custom: 500 per session
193
+ * - Viewport: 200 per session
194
+ * - Scroll: 120 per session
195
+ *
196
+ * @see src/managers/event.manager.ts for implementation
197
+ * @see src/constants/config.constants.ts for limit values
122
198
  */
123
199
  interface SessionEventCounts {
124
200
  /** Total events across all types */
@@ -129,6 +205,8 @@ interface SessionEventCounts {
129
205
  [EventType.PAGE_VIEW]: number;
130
206
  /** Custom events count */
131
207
  [EventType.CUSTOM]: number;
208
+ /** Viewport visibility events count */
209
+ [EventType.VIEWPORT_VISIBLE]: number;
132
210
  /** Scroll events count */
133
211
  [EventType.SCROLL]: number;
134
212
  /** Index signature for dynamic event type access */
@@ -162,6 +240,12 @@ interface ScrollData {
162
240
  direction: ScrollDirection;
163
241
  /** CSS selector of the scrolled container */
164
242
  container_selector: string;
243
+ /** Whether this is the primary viewport scroll */
244
+ is_primary: boolean;
245
+ /** Scroll velocity in pixels per second */
246
+ velocity: number;
247
+ /** Maximum scroll depth reached during session (0-100) */
248
+ max_depth_reached: number;
165
249
  }
166
250
  /**
167
251
  * Click event data capturing user interaction details
@@ -171,6 +255,10 @@ interface ClickData {
171
255
  x: number;
172
256
  /** Absolute Y coordinate in viewport (pixels) */
173
257
  y: number;
258
+ /** Relative X position within element (0-1) */
259
+ relativeX: number;
260
+ /** Relative Y position within element (0-1) */
261
+ relativeY: number;
174
262
  /** Element ID attribute */
175
263
  id?: string;
176
264
  /** Element class attribute */
@@ -181,6 +269,16 @@ interface ClickData {
181
269
  text?: string;
182
270
  /** Link href for anchor elements */
183
271
  href?: string;
272
+ /** Element title attribute */
273
+ title?: string;
274
+ /** Image alt text for img elements */
275
+ alt?: string;
276
+ /** ARIA role attribute */
277
+ role?: string;
278
+ /** ARIA label attribute */
279
+ ariaLabel?: string;
280
+ /** Custom data attributes (data-*) */
281
+ dataAttributes?: Record<string, string>;
184
282
  }
185
283
  /**
186
284
  * Element data for specialized click tracking
@@ -210,10 +308,21 @@ interface EventOptions {
210
308
  /**
211
309
  * If `true`, the event queue is flushed via `navigator.sendBeacon()`
212
310
  * immediately after this event is tracked. The browser guarantees the
213
- * request is queued for delivery even if the page is about to unload.
311
+ * request is queued for delivery even if the page is about to unload
312
+ * (e.g., `window.location.href = '/thanks'` right after tracking a
313
+ * purchase). Async fetch would be cancelled by the navigation;
314
+ * sendBeacon survives.
214
315
  *
215
316
  * Use for high-value events where loss is unacceptable (Purchase, Signup,
216
- * AddPaymentInfo).
317
+ * AddPaymentInfo). The critical event plus any previously queued events
318
+ * are sent in a single request — this does not bypass the queue.
319
+ *
320
+ * **Limitations** (inherited from `sendBeacon`):
321
+ * - 64KB payload cap. If the combined queue + critical event exceeds it,
322
+ * the failed batch is persisted to localStorage and recovered on next
323
+ * `init()` via its idempotency token.
324
+ * - No retry on failure (fire-and-forget).
325
+ * - Custom headers are not applied (browser API limitation).
217
326
  *
218
327
  * @default false
219
328
  */
@@ -270,6 +379,27 @@ interface PageViewData {
270
379
  referrer?: string;
271
380
  /** Page title from document */
272
381
  title?: string;
382
+ /** URL pathname */
383
+ pathname?: string;
384
+ /** URL query string */
385
+ search?: string;
386
+ /** URL hash fragment */
387
+ hash?: string;
388
+ }
389
+ /**
390
+ * Data captured when element becomes visible
391
+ */
392
+ interface ViewportEventData {
393
+ /** CSS selector that matched the element */
394
+ selector: string;
395
+ /** Optional unique identifier for analytics (if configured) */
396
+ id?: string;
397
+ /** Optional human-readable name (if configured) */
398
+ name?: string;
399
+ /** Actual time (ms) element was visible before event fired */
400
+ dwellTime: number;
401
+ /** Actual visibility ratio when event fired (0-1) */
402
+ visibilityRatio: number;
273
403
  }
274
404
  /**
275
405
  * Complete event data structure
@@ -300,6 +430,8 @@ interface EventData {
300
430
  page_view?: PageViewData;
301
431
  /** Error details (when type is ERROR) */
302
432
  error_data?: ErrorData;
433
+ /** Viewport visibility details (when type is VIEWPORT_VISIBLE) */
434
+ viewport_data?: ViewportEventData;
303
435
  /** Campaign tracking parameters */
304
436
  utm?: UTM;
305
437
  }
@@ -310,6 +442,11 @@ interface EventData {
310
442
  * their original `_session_id`, so `EventManager.buildBatchesWithIds()` can
311
443
  * still attribute them correctly instead of emitting `session_id: null` to the
312
444
  * wire.
445
+ *
446
+ * **Internal only.** The leading underscore mirrors `EventsQueue._metadata` —
447
+ * this field is stripped from `events[]` at batch construction time, since the
448
+ * backend's `EventDto` uses `forbidNonWhitelisted: true` and the wrapper
449
+ * `EventsQueue.session_id` is the contract.
313
450
  */
314
451
  interface QueuedEvent extends EventData {
315
452
  _session_id: string;
@@ -333,6 +470,10 @@ interface Config {
333
470
  errorSampling?: number;
334
471
  /** Event sampling rate between 0 and 1. @default 1 */
335
472
  samplingRate?: number;
473
+ /** CSS selector to manually override primary scroll container detection. */
474
+ primaryScrollSelector?: string;
475
+ /** Viewport visibility tracking configuration. */
476
+ viewport?: ViewportConfig;
336
477
  /** Page view throttle duration in milliseconds to prevent rapid navigation spam. @default 1000 */
337
478
  pageViewThrottleMs?: number;
338
479
  /** Click throttle duration in milliseconds to prevent double-clicks and rapid spam. @default 300 */
@@ -356,7 +497,11 @@ interface Config {
356
497
  /**
357
498
  * Opt-in: when `true`, the event queue is flushed after every SPA navigation
358
499
  * (`pushState`, `replaceState`, `popstate`, `hashchange`). Defaults to `false`
359
- * because per-route flushing can multiply request volume on SPA-heavy apps.
500
+ * because per-route flushing can multiply request volume on SPA-heavy apps
501
+ * (one request per route change vs. one per `sendIntervalMs`). Enable only
502
+ * if you need delivery between route changes that's faster than
503
+ * `sendIntervalMs`; `flushOnPageHidden` (default `true`) already covers the
504
+ * common tab-close / app-background case on every stack. No-op for MPAs.
360
505
  * @default false
361
506
  */
362
507
  flushOnSpaNavigation?: boolean;
@@ -367,14 +512,36 @@ interface Config {
367
512
  * @default true
368
513
  */
369
514
  flushOnPageHidden?: boolean;
370
- /** TraceLog SaaS integration. */
515
+ /** Optional configuration for third-party integrations. */
371
516
  integrations?: {
517
+ /** TraceLog integration options. */
372
518
  tracelog?: {
373
- /** Required project ID for TraceLog SaaS integration. */
519
+ /** Required project ID TraceLog SaaS integration. */
374
520
  projectId: string;
375
521
  /** Enable Shopify cart attribute linking for webhook revenue attribution. */
376
522
  shopify?: boolean;
377
523
  };
524
+ /** Custom integration options. */
525
+ custom?: {
526
+ /** Endpoint for collecting events. */
527
+ collectApiUrl: string;
528
+ /** Allow HTTP URLs (not recommended for production). @default false */
529
+ allowHttp?: boolean;
530
+ /**
531
+ * Static HTTP headers to include in every request.
532
+ * For dynamic headers, use `setCustomHeaders()` instead.
533
+ * @example { 'X-Brand': 'my-brand', 'X-Tenant-Id': 'tenant-123' }
534
+ */
535
+ headers?: Record<string, string>;
536
+ /**
537
+ * Controls whether cookies and credentials are sent with fetch requests.
538
+ * - `'include'`: Always send cookies (even cross-origin) — required for cookie-based auth
539
+ * - `'same-origin'`: Only send cookies for same-origin requests
540
+ * - `'omit'`: Never send cookies
541
+ * @default 'include'
542
+ */
543
+ fetchCredentials?: RequestCredentials;
544
+ };
378
545
  };
379
546
  }
380
547
  declare enum SpecialApiUrl {
@@ -581,15 +748,90 @@ declare enum Mode {
581
748
  QA = "qa"
582
749
  }
583
750
 
751
+ /**
752
+ * Primary scroll event type (main viewport scroll)
753
+ *
754
+ * **Purpose**: Type-safe representation of primary viewport scroll events
755
+ *
756
+ * Primary scroll events track the main page/viewport scrolling,
757
+ * which is the most important scroll metric for engagement analysis.
758
+ */
759
+ type PrimaryScrollEvent = EventData & {
760
+ type: EventType.SCROLL;
761
+ scroll_data: ScrollData & {
762
+ is_primary: true;
763
+ };
764
+ };
765
+ /**
766
+ * Secondary scroll event type (scrollable container scroll)
767
+ *
768
+ * **Purpose**: Type-safe representation of container-specific scroll events
769
+ *
770
+ * Secondary scroll events track scrolling within specific elements
771
+ * (e.g., modals, sidebars, embedded content) for granular engagement analysis.
772
+ */
773
+ type SecondaryScrollEvent = EventData & {
774
+ type: EventType.SCROLL;
775
+ scroll_data: ScrollData & {
776
+ is_primary: false;
777
+ };
778
+ };
779
+ /**
780
+ * Type guard to check if an event is a primary scroll event
781
+ *
782
+ * **Purpose**: Runtime type narrowing for primary viewport scrolls
783
+ *
784
+ * **Use Cases**:
785
+ * - Filter events to process only main viewport scrolling
786
+ * - Separate primary from secondary scroll analytics
787
+ * - Type-safe event processing in transformers
788
+ *
789
+ * @param event - Event to check
790
+ * @returns `true` if event is a primary scroll event
791
+ *
792
+ * @example
793
+ * ```typescript
794
+ * if (isPrimaryScrollEvent(event)) {
795
+ * // event.scroll_data.is_primary is guaranteed to be true
796
+ * console.log('Main viewport scrolled to', event.scroll_data.depth, '%');
797
+ * }
798
+ * ```
799
+ */
800
+ declare const isPrimaryScrollEvent: (event: EventData) => event is PrimaryScrollEvent;
801
+ /**
802
+ * Type guard to check if an event is a secondary scroll event
803
+ *
804
+ * **Purpose**: Runtime type narrowing for container-specific scrolls
805
+ *
806
+ * **Use Cases**:
807
+ * - Filter events to process only container scrolling
808
+ * - Analyze engagement with specific page sections
809
+ * - Type-safe event processing in transformers
810
+ *
811
+ * @param event - Event to check
812
+ * @returns `true` if event is a secondary scroll event
813
+ *
814
+ * @example
815
+ * ```typescript
816
+ * if (isSecondaryScrollEvent(event)) {
817
+ * // event.scroll_data.is_primary is guaranteed to be false
818
+ * console.log('Container scrolled:', event.scroll_data.container_selector);
819
+ * }
820
+ * ```
821
+ */
822
+ declare const isSecondaryScrollEvent: (event: EventData) => event is SecondaryScrollEvent;
823
+
584
824
  interface State {
585
825
  /** QA mode flag - when true, custom events are logged to console */
586
826
  mode?: Mode;
587
827
  /**
588
828
  * Collection of API URLs for different integrations.
589
829
  * - saas: TraceLog SaaS endpoint (if projectId configured)
830
+ * - custom: Custom backend endpoint (if collectApiUrl configured)
590
831
  */
591
832
  collectApiUrls: {
592
833
  saas?: string;
834
+ custom?: string;
593
835
  };
594
836
  config: Config;
595
837
  sessionId: string | null;
@@ -605,15 +847,6 @@ interface State {
605
847
  identity?: IdentifyData;
606
848
  }
607
849
 
608
- /**
609
- * Regular expressions used to detect and redact common PII patterns from
610
- * free-form text (click text, error messages, stack traces, etc.).
611
- *
612
- * Mirrors the patterns relied on by `ClickHandler` and `ErrorHandler`. Adding
613
- * a pattern here automatically widens coverage for both handlers.
614
- */
615
- declare const PII_PATTERNS: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
616
-
617
850
  /**
618
851
  * Type-safe event emitter for TraceLog internal events
619
852
  *
@@ -774,15 +1007,35 @@ declare class Emitter {
774
1007
  /**
775
1008
  * Abstract base class providing centralized, type-safe state management.
776
1009
  *
777
- * All managers/handlers extend this class. The global state is shared
778
- * across every subclass instance.
1010
+ * **Purpose**: Foundation for all TraceLog managers and handlers, providing
1011
+ * synchronized access to shared application state.
1012
+ *
1013
+ * **Architecture**:
1014
+ * - All managers/handlers extend this class
1015
+ * - Single global state instance shared across all subclasses
1016
+ * - Type-safe operations via generic methods
1017
+ * - In-memory only (no automatic persistence)
1018
+ *
1019
+ * **Supported State Properties**:
1020
+ * - **Core State**: `collectApiUrls`, `config`, `sessionId`, `userId`, `device`, `pageUrl`
1021
+ * - **Control Flags**: `mode` (QA/production), `hasStartSession`, `suppressNextScroll`
1022
+ * - **Runtime Counters**: `scrollEventCount` (optional)
1023
+ *
1024
+ * **Implementation Details**:
1025
+ * - Synchronous operations (no async overhead)
1026
+ * - Memory-efficient (minimal object creation)
1027
+ * - No built-in logging (consumers handle their own state logging)
1028
+ * - Read-only snapshots via `getState()` prevent accidental mutations
1029
+ *
1030
+ * @see src/managers/README.md (lines 170-201) for detailed documentation
779
1031
  *
780
1032
  * @example
781
1033
  * ```typescript
782
1034
  * class MyManager extends StateManager {
783
1035
  * initialize() {
784
- * const userId = this.get('userId');
785
- * this.set('mode', 'qa');
1036
+ * const userId = this.get('userId'); // Read state
1037
+ * this.set('mode', 'qa'); // Write state
1038
+ * const snapshot = this.getState(); // Readonly copy
786
1039
  * }
787
1040
  * }
788
1041
  * ```
@@ -790,14 +1043,52 @@ declare class Emitter {
790
1043
  declare abstract class StateManager {
791
1044
  /**
792
1045
  * Retrieves a value from global state.
1046
+ *
1047
+ * Type-safe getter with compile-time key validation.
1048
+ *
1049
+ * @template T - State key type (compile-time validated)
1050
+ * @param key - State property key
1051
+ * @returns Current value for the given key (may be undefined)
1052
+ *
1053
+ * @example
1054
+ * ```typescript
1055
+ * const userId = this.get('userId');
1056
+ * const config = this.get('config');
1057
+ * const sessionId = this.get('sessionId');
1058
+ * ```
793
1059
  */
794
1060
  protected get<T extends keyof State>(key: T): State[T];
795
1061
  /**
796
1062
  * Sets a value in global state.
1063
+ *
1064
+ * Type-safe setter with compile-time type checking.
1065
+ * Changes are immediately visible to all StateManager subclasses.
1066
+ *
1067
+ * @template T - State key type (compile-time validated)
1068
+ * @param key - State property key
1069
+ * @param value - New value (type must match State[T])
1070
+ *
1071
+ * @example
1072
+ * ```typescript
1073
+ * this.set('sessionId', 'session-123');
1074
+ * this.set('mode', Mode.QA);
1075
+ * this.set('hasStartSession', true);
1076
+ * ```
797
1077
  */
798
1078
  protected set<T extends keyof State>(key: T, value: State[T]): void;
799
1079
  /**
800
1080
  * Returns an immutable snapshot of the entire global state.
1081
+ *
1082
+ * Creates a shallow copy to prevent accidental mutations.
1083
+ * Use for debugging or when multiple state properties are needed.
1084
+ *
1085
+ * @returns Readonly shallow copy of global state
1086
+ *
1087
+ * @example
1088
+ * ```typescript
1089
+ * const snapshot = this.getState();
1090
+ * console.log(snapshot.userId, snapshot.sessionId);
1091
+ * ```
801
1092
  */
802
1093
  protected getState(): Readonly<State>;
803
1094
  }
@@ -805,29 +1096,212 @@ declare abstract class StateManager {
805
1096
  /**
806
1097
  * Robust localStorage and sessionStorage wrapper with automatic fallback to in-memory storage.
807
1098
  *
808
- * - Dual storage: localStorage (persistent) + sessionStorage (tab-scoped).
809
- * - Automatic in-memory fallback when browser storage unavailable (privacy modes, SSR).
810
- * - On `QuotaExceededError`: single-pass cleanup — purges `tracelog_persisted_events_*`
811
- * first (largest, recoverable), retries once. Preserves session/user/device/config keys.
1099
+ * **Purpose**: Provides consistent storage APIs across all browser environments,
1100
+ * handling quota limits, privacy modes, and SSR scenarios gracefully.
1101
+ *
1102
+ * **Core Functionality**:
1103
+ * - **Dual Storage**: localStorage (persistent) and sessionStorage (tab-scoped)
1104
+ * - **Automatic Fallback**: In-memory Maps when browser storage unavailable
1105
+ * - **Quota Handling**: Intelligent cleanup on QuotaExceededError
1106
+ * - **SSR-Safe**: No-op in Node.js environments
1107
+ *
1108
+ * **Key Features**:
1109
+ * - Separate fallback Maps for each storage type
1110
+ * - Storage quota error handling with automatic cleanup and retry
1111
+ * - Intelligent cleanup: Prioritizes removing persisted events over critical data
1112
+ * - Preserves: session, user, device, and config keys during cleanup
1113
+ * - Test key validation during initialization (`__tracelog_test__`)
1114
+ * - Public `isAvailable()` and `hasQuotaError()` for conditional logic
1115
+ * - Explicit `clear()` method for TraceLog-namespaced data only (`tracelog_*` prefix)
1116
+ *
1117
+ * **Cleanup Strategy** (on QuotaExceededError):
1118
+ * 1. Remove persisted events (`tracelog_persisted_events_*`) - usually largest data
1119
+ * 2. If no persisted events, remove up to 5 non-critical keys
1120
+ * 3. Preserve critical keys: `tracelog_session_*`, `tracelog_user_id`, `tracelog_device_id`, `tracelog_config`
1121
+ *
1122
+ * @see src/managers/README.md (lines 203-226) for detailed documentation
1123
+ *
1124
+ * @example
1125
+ * ```typescript
1126
+ * const storage = new StorageManager();
1127
+ *
1128
+ * // localStorage operations
1129
+ * storage.setItem('key', 'value');
1130
+ * const value = storage.getItem('key');
1131
+ * storage.removeItem('key');
1132
+ *
1133
+ * // sessionStorage operations
1134
+ * storage.setSessionItem('session_key', 'data');
1135
+ * const sessionValue = storage.getSessionItem('session_key');
1136
+ *
1137
+ * // Check availability
1138
+ * if (storage.isAvailable()) {
1139
+ * // localStorage is working
1140
+ * }
1141
+ *
1142
+ * // Check for quota issues
1143
+ * if (storage.hasQuotaError()) {
1144
+ * // Data may not persist
1145
+ * }
1146
+ * ```
812
1147
  */
813
1148
  declare class StorageManager {
814
1149
  private readonly storage;
815
1150
  private readonly sessionStorageRef;
816
1151
  private readonly fallbackStorage;
817
1152
  private readonly fallbackSessionStorage;
1153
+ private hasQuotaExceededError;
818
1154
  constructor();
1155
+ /**
1156
+ * Retrieves an item from localStorage.
1157
+ *
1158
+ * Automatically falls back to in-memory storage if localStorage unavailable.
1159
+ *
1160
+ * @param key - Storage key
1161
+ * @returns Stored value or null if not found
1162
+ */
819
1163
  getItem(key: string): string | null;
1164
+ /**
1165
+ * Stores an item in localStorage with automatic quota handling.
1166
+ *
1167
+ * **Behavior**:
1168
+ * 1. Updates fallback storage first (ensures consistency)
1169
+ * 2. Attempts to store in localStorage
1170
+ * 3. On QuotaExceededError: Triggers cleanup and retries once
1171
+ * 4. Falls back to in-memory storage if retry fails
1172
+ *
1173
+ * **Cleanup on Quota Error**:
1174
+ * - Removes persisted events (largest data)
1175
+ * - Removes up to 5 non-critical keys
1176
+ * - Preserves session, user, device, and config keys
1177
+ *
1178
+ * @param key - Storage key
1179
+ * @param value - String value to store
1180
+ */
820
1181
  setItem(key: string, value: string): void;
1182
+ /**
1183
+ * Removes an item from localStorage and fallback storage.
1184
+ *
1185
+ * Safe to call even if key doesn't exist (idempotent).
1186
+ *
1187
+ * @param key - Storage key to remove
1188
+ */
821
1189
  removeItem(key: string): void;
822
1190
  /**
823
- * Single-pass cleanup for QuotaExceededError. Purges persisted-events keys
824
- * (largest, safe to discard — recoverable) and up to 5 other non-critical
825
- * tracelog_* keys in one pass. Preserves session/user/device/config keys.
1191
+ * Clears all TraceLog-related items from storage.
1192
+ *
1193
+ * Only removes keys with `tracelog_` prefix (safe for shared storage).
1194
+ * Clears both localStorage and fallback storage.
1195
+ *
1196
+ * **Use Cases**:
1197
+ * - User logout/privacy actions
1198
+ * - Development/testing cleanup
1199
+ * - Reset analytics state
1200
+ */
1201
+ clear(): void;
1202
+ /**
1203
+ * Checks if localStorage is available.
1204
+ *
1205
+ * @returns true if localStorage is working, false if using fallback
1206
+ */
1207
+ isAvailable(): boolean;
1208
+ /**
1209
+ * Checks if a QuotaExceededError has occurred during this session.
1210
+ *
1211
+ * **Purpose**: Detect when localStorage is full and data may not persist.
1212
+ * Allows application to show warnings or adjust behavior.
1213
+ *
1214
+ * **Note**: Flag is set on first QuotaExceededError and never reset.
1215
+ *
1216
+ * @returns true if quota exceeded at any point during this session
1217
+ */
1218
+ hasQuotaError(): boolean;
1219
+ /**
1220
+ * Implements two-phase cleanup strategy to free storage space when quota exceeded.
1221
+ *
1222
+ * **Purpose**: Removes TraceLog data intelligently to make room for new writes
1223
+ * while preserving critical user state (session, user ID, device ID, config).
1224
+ *
1225
+ * **Two-Phase Cleanup Strategy**:
1226
+ * 1. **Phase 1 (Priority)**: Remove all persisted events (`tracelog_persisted_events_*`)
1227
+ * - These are typically the largest data items (batches of events)
1228
+ * - Safe to remove as they represent recoverable failed sends
1229
+ * - Returns immediately if any persisted events found and removed
1230
+ *
1231
+ * 2. **Phase 2 (Fallback)**: Remove up to 5 non-critical keys
1232
+ * - Only executed if no persisted events found
1233
+ * - Preserves critical keys: session data, user ID, device ID, config
1234
+ * - Limits to 5 keys to avoid excessive cleanup time
1235
+ *
1236
+ * **Critical Keys (Never Removed)**:
1237
+ * - `tracelog_session_*` - Active session data
1238
+ * - `tracelog_user_id` - User identification
1239
+ * - `tracelog_device_id` - Device fingerprint
1240
+ * - `tracelog_config` - Configuration cache
1241
+ *
1242
+ * **Error Handling**:
1243
+ * - Individual key removal failures silently ignored (continue cleanup)
1244
+ * - Overall cleanup errors logged and return false
1245
+ *
1246
+ * @returns true if any data was successfully removed, false if nothing cleaned up
826
1247
  */
827
1248
  private cleanupOldData;
1249
+ /**
1250
+ * Initializes storage with feature detection and write-test validation.
1251
+ *
1252
+ * **Purpose**: Validates storage availability by performing actual write/remove test,
1253
+ * preventing false positives in privacy modes where storage API exists but throws on write.
1254
+ *
1255
+ * **Validation Strategy**:
1256
+ * 1. SSR Safety: Returns null in Node.js environments (`typeof window === 'undefined'`)
1257
+ * 2. API Check: Verifies storage object exists on window
1258
+ * 3. Write Test: Attempts to write test key (`__tracelog_test__`)
1259
+ * 4. Cleanup: Removes test key immediately after validation
1260
+ *
1261
+ * **Why Write Test is Critical**:
1262
+ * - Safari private browsing: storage API exists but throws QuotaExceededError on write
1263
+ * - iOS private mode: storage appears available but operations fail
1264
+ * - Incognito modes: API exists but writes are silently ignored or throw
1265
+ *
1266
+ * **Fallback Behavior**:
1267
+ * - Returns null if storage unavailable or test fails
1268
+ * - Caller automatically falls back to in-memory Map storage
1269
+ *
1270
+ * @param type - Storage type to initialize ('localStorage' | 'sessionStorage')
1271
+ * @returns Storage instance if available and writable, null otherwise
1272
+ */
828
1273
  private initializeStorage;
1274
+ /**
1275
+ * Retrieves an item from sessionStorage.
1276
+ *
1277
+ * Automatically falls back to in-memory storage if sessionStorage unavailable.
1278
+ *
1279
+ * @param key - Storage key
1280
+ * @returns Stored value or null if not found
1281
+ */
829
1282
  getSessionItem(key: string): string | null;
1283
+ /**
1284
+ * Stores an item in sessionStorage with quota error detection.
1285
+ *
1286
+ * **Behavior**:
1287
+ * 1. Updates fallback storage first (ensures consistency)
1288
+ * 2. Attempts to store in sessionStorage
1289
+ * 3. On QuotaExceededError: Logs error and uses fallback (no retry/cleanup)
1290
+ *
1291
+ * **Note**: sessionStorage quota errors are rare (typically 5-10MB per tab).
1292
+ * No automatic cleanup unlike localStorage.
1293
+ *
1294
+ * @param key - Storage key
1295
+ * @param value - String value to store
1296
+ */
830
1297
  setSessionItem(key: string, value: string): void;
1298
+ /**
1299
+ * Removes an item from sessionStorage and fallback storage.
1300
+ *
1301
+ * Safe to call even if key doesn't exist (idempotent).
1302
+ *
1303
+ * @param key - Storage key to remove
1304
+ */
831
1305
  removeSessionItem(key: string): void;
832
1306
  }
833
1307
 
@@ -897,6 +1371,7 @@ declare class StorageManager {
897
1371
  declare class EventManager extends StateManager {
898
1372
  private readonly dataSenders;
899
1373
  private readonly emitter;
1374
+ private readonly transformers;
900
1375
  private readonly timeManager;
901
1376
  private readonly recentEventFingerprints;
902
1377
  private readonly perEventRateLimits;
@@ -914,10 +1389,18 @@ declare class EventManager extends StateManager {
914
1389
  /**
915
1390
  * Creates an EventManager instance.
916
1391
  *
1392
+ * **Initialization**:
1393
+ * - Creates SenderManager instances for configured integrations (SaaS/Custom)
1394
+ * - Initializes event emitter for local consumption
1395
+ *
917
1396
  * @param storeManager - Storage manager for persistence
918
1397
  * @param emitter - Optional event emitter for local event consumption
1398
+ * @param transformers - Optional event transformation hooks
1399
+ * @param staticHeaders - Optional static HTTP headers for custom backend (from config)
1400
+ * @param customHeadersProvider - Optional callback for dynamic headers
1401
+ * @param fetchCredentials - Fetch credentials mode for custom backend. @default 'include'
919
1402
  */
920
- constructor(storeManager: StorageManager, emitter?: Emitter | null);
1403
+ constructor(storeManager: StorageManager, emitter?: Emitter | null, transformers?: TransformerMap, staticHeaders?: Record<string, string>, customHeadersProvider?: CustomHeadersProvider, fetchCredentials?: RequestCredentials);
921
1404
  /**
922
1405
  * Recovers persisted events from localStorage after a crash or page reload.
923
1406
  *
@@ -1002,7 +1485,7 @@ declare class EventManager extends StateManager {
1002
1485
  *
1003
1486
  * @see src/managers/README.md (lines 5-75) for detailed tracking logic
1004
1487
  */
1005
- track({ type, page_url, from_page_url, scroll_data, click_data, custom_event, web_vitals, error_data, page_view, }: Partial<EventData>): void;
1488
+ track({ type, page_url, from_page_url, scroll_data, click_data, custom_event, web_vitals, error_data, viewport_data, page_view, }: Partial<EventData>): void;
1006
1489
  /**
1007
1490
  * Stops event tracking and clears all queues and buffers.
1008
1491
  *
@@ -1118,6 +1601,17 @@ declare class EventManager extends StateManager {
1118
1601
  * @see src/managers/README.md (lines 5-75) for flush details
1119
1602
  */
1120
1603
  flushImmediatelySync(): boolean;
1604
+ /**
1605
+ * Sets the custom headers provider callback for the custom integration.
1606
+ * Only affects requests to custom backend (not TraceLog SaaS).
1607
+ *
1608
+ * @param provider - Callback function that returns custom headers
1609
+ */
1610
+ setCustomHeadersProvider(provider: CustomHeadersProvider): void;
1611
+ /**
1612
+ * Removes the custom headers provider callback from the custom integration.
1613
+ */
1614
+ removeCustomHeadersProvider(): void;
1121
1615
  /**
1122
1616
  * Returns the current number of events in the main queue.
1123
1617
  *
@@ -1415,6 +1909,7 @@ declare class EventManager extends StateManager {
1415
1909
  * - Custom threshold overrides via webVitalsThresholds config
1416
1910
  * - Navigation-based deduplication with 50-navigation FIFO history
1417
1911
  * - CLS accumulation with reset on navigation change
1912
+ * - Long task throttling (maximum 1 event per second)
1418
1913
  * - Automatic fallback to Performance Observer if web-vitals library fails
1419
1914
  * - Final values only (reportAllChanges: false for all metrics)
1420
1915
  *
@@ -1426,6 +1921,7 @@ declare class EventManager extends StateManager {
1426
1921
  * - FCP (First Contentful Paint): Initial rendering time
1427
1922
  * - TTFB (Time to First Byte): Server response time
1428
1923
  * - INP (Interaction to Next Paint): Responsiveness measure
1924
+ * - LONG_TASK: Tasks blocking main thread (>50ms, throttled to 1/second)
1429
1925
  *
1430
1926
  * **Filtering Modes**:
1431
1927
  * - 'all': Track all positive metric values (threshold = 0)
@@ -1446,6 +1942,7 @@ declare class PerformanceHandler extends StateManager {
1446
1942
  private readonly navigationHistory;
1447
1943
  private readonly observers;
1448
1944
  private vitalThresholds;
1945
+ private lastLongTaskSentAt;
1449
1946
  private navigationCounter;
1450
1947
  constructor(eventManager: EventManager);
1451
1948
  /**
@@ -1458,6 +1955,7 @@ declare class PerformanceHandler extends StateManager {
1458
1955
  * - Reads webVitalsMode from config ('all', 'needs-improvement', 'poor')
1459
1956
  * - Merges webVitalsThresholds with mode defaults for custom thresholds
1460
1957
  * - Initializes web-vitals library observers (LCP, CLS, FCP, TTFB, INP)
1958
+ * - Starts long task observation with 1/second throttling
1461
1959
  *
1462
1960
  * @returns Promise that resolves when tracking is initialized
1463
1961
  */
@@ -1475,6 +1973,7 @@ declare class PerformanceHandler extends StateManager {
1475
1973
  private observeWebVitalsFallback;
1476
1974
  private initWebVitals;
1477
1975
  private reportTTFB;
1976
+ private observeLongTasks;
1478
1977
  private sendVital;
1479
1978
  private trackWebVital;
1480
1979
  /**
@@ -1516,70 +2015,45 @@ declare class PerformanceHandler extends StateManager {
1516
2015
  * - Message truncation (500 character limit)
1517
2016
  * - Burst detection (>10 errors/second triggers 5-second cooldown)
1518
2017
  * - Deduplication within 5-second window per error type+message
1519
- * - Per-pageview signature cap (`MAX_ERRORS_PER_SIGNATURE_PER_PAGEVIEW`): after the
1520
- * 5s dedup window expires, the same `(normalizedMessage, filename, line)` may only
1521
- * recur up to N times per pageview. Counter resets on `pagehide`, `SESSION_START`,
1522
- * and `PAGE_VIEW` (covers SPA navigation via patched History API + popstate/hashchange).
1523
2018
  *
1524
2019
  * **Privacy Protection**:
1525
2020
  * - Automatically redacts PII from error messages before storage
1526
2021
  * - Sanitizes emails, phone numbers, credit cards, IBAN, API keys, Bearer tokens
1527
2022
  *
1528
- * @see src/handlers/README.md (`## ErrorHandler` section) for detailed documentation
2023
+ * @see src/handlers/README.md (lines 167-218) for detailed documentation
1529
2024
  */
1530
2025
  declare class ErrorHandler extends StateManager {
1531
2026
  private readonly eventManager;
1532
- private readonly emitter?;
1533
2027
  private readonly recentErrors;
1534
- private readonly pageviewSignatureCounts;
1535
2028
  private errorBurstCounter;
1536
2029
  private burstWindowStart;
1537
2030
  private burstBackoffUntil;
1538
- private pagehideHandler;
1539
- private pageviewResetListener;
1540
- constructor(eventManager: EventManager, emitter?: Emitter);
2031
+ constructor(eventManager: EventManager);
1541
2032
  /**
1542
2033
  * Starts tracking JavaScript errors and promise rejections.
1543
2034
  *
1544
2035
  * - Registers global error event listener
1545
2036
  * - Registers unhandledrejection event listener
1546
- * - Registers pagehide listener to reset the per-pageview signature counter
1547
- * - Subscribes to emitter SESSION_START + PAGE_VIEW to reset the counter on new
1548
- * sessions and SPA route changes (the only signal `pagehide` does not cover)
1549
2037
  */
1550
2038
  startTracking(): void;
1551
2039
  /**
1552
2040
  * Stops tracking errors and cleans up resources.
1553
2041
  *
1554
2042
  * - Removes error event listeners
1555
- * - Removes pagehide listener and unsubscribes from emitter
1556
- * - Clears recent errors and pageview signature counters
2043
+ * - Clears recent errors map
1557
2044
  * - Resets burst detection counters
1558
2045
  */
1559
2046
  stopTracking(): void;
1560
- /**
1561
- * Clears the per-pageview signature counter.
1562
- *
1563
- * Public so `App` or tests can drive a reset explicitly; the handler itself wires
1564
- * `pagehide` and emitter `SESSION_START` / `PAGE_VIEW` in `startTracking()`.
1565
- */
1566
- resetPageviewCounter(): void;
1567
2047
  /**
1568
2048
  * Checks sampling rate and burst detection
1569
2049
  * Returns false if in cooldown period after burst detection
1570
2050
  */
1571
2051
  private shouldSample;
1572
- /**
1573
- * Returns true when the per-pageview signature cap has been hit for this error.
1574
- * Dropped errors do not increment the counter — the 5s suppression window already
1575
- * silences identical repeats, and double-counting here would skew the cap for any
1576
- * later signature that recycles the same map key after a counter reset.
1577
- */
1578
- private shouldThrottleBySignature;
1579
2052
  private readonly handleError;
1580
2053
  private readonly handleRejection;
1581
2054
  private extractRejectionMessage;
1582
2055
  private sanitize;
2056
+ private sanitizePii;
1583
2057
  private shouldSuppressError;
1584
2058
  private static readonly TRUNCATION_SUFFIX;
1585
2059
  private truncateStack;
@@ -1806,36 +2280,128 @@ declare class ClickHandler extends StateManager {
1806
2280
  private getElementPath;
1807
2281
  private findTrackingElement;
1808
2282
  private getRelevantClickElement;
2283
+ /**
2284
+ * Clamps relative coordinate values to [0, 1] range with 3 decimal precision.
2285
+ *
2286
+ * @param value - Raw relative coordinate value
2287
+ * @returns Clamped value between 0 and 1 with 3 decimal places (e.g., 0.123)
2288
+ *
2289
+ * @example
2290
+ * clamp(1.234) // returns 1.000
2291
+ * clamp(0.12345) // returns 0.123
2292
+ * clamp(-0.5) // returns 0.000
2293
+ */
2294
+ private clamp;
1809
2295
  private calculateClickCoordinates;
1810
2296
  private extractTrackingData;
1811
2297
  private generateClickData;
2298
+ /**
2299
+ * Sanitizes text by replacing PII patterns with [REDACTED].
2300
+ *
2301
+ * Protects against:
2302
+ * - Email addresses
2303
+ * - Phone numbers (US format)
2304
+ * - Credit card numbers
2305
+ * - IBAN numbers
2306
+ * - API keys/tokens
2307
+ * - Bearer tokens
2308
+ *
2309
+ * @param text - Raw text content from element
2310
+ * @returns Sanitized text with PII replaced by [REDACTED]
2311
+ *
2312
+ * @example
2313
+ * sanitizeText('Email: user@example.com') // returns 'Email: [REDACTED]'
2314
+ * sanitizeText('Card: 1234-5678-9012-3456') // returns 'Card: [REDACTED]'
2315
+ * sanitizeText('Bearer token123') // returns '[REDACTED]'
2316
+ */
2317
+ private sanitizeText;
1812
2318
  private getRelevantText;
2319
+ private extractElementAttributes;
1813
2320
  private createCustomEventData;
1814
2321
  }
1815
2322
 
1816
2323
  /**
1817
- * Tracks scroll depth and direction across the window and any detected scrollable containers.
2324
+ * Tracks scroll depth, direction, velocity, and container identification across multiple scrollable elements.
1818
2325
  *
1819
- * **Captured fields**: `depth` (0-100), `direction` (up/down), `container_selector`.
2326
+ * **Features**:
2327
+ * - Automatic container detection with intelligent retry (5 attempts @ 200ms intervals)
2328
+ * - Manual override via primaryScrollSelector config
2329
+ * - Smart filtering with multiple guardrails:
2330
+ * - Visibility check (element must be connected to DOM with dimensions)
2331
+ * - Scrollability check (content must overflow container)
2332
+ * - Significant movement (minimum 10px position delta)
2333
+ * - Depth change (minimum 5% depth change between events)
2334
+ * - Rate limiting (minimum 500ms interval between events per container)
2335
+ * - Session cap (maximum 120 events per session with single warning)
2336
+ * - Multi-container support with per-container debouncing (250ms)
2337
+ * - Velocity calculation for engagement analysis
2338
+ * - Max depth tracking per session
2339
+ * - Primary vs secondary container classification
2340
+ *
2341
+ * **Events Generated**: `scroll`
2342
+ *
2343
+ * **Analytics Fields**:
2344
+ * - depth: Current scroll depth (0-100%)
2345
+ * - direction: 'up' | 'down'
2346
+ * - container_selector: CSS selector or 'window'
2347
+ * - is_primary: Boolean indicating main scroll container
2348
+ * - velocity: Scroll speed in px/s
2349
+ * - max_depth_reached: Peak engagement per container
2350
+ *
2351
+ * **Container Detection**:
2352
+ * - Uses TreeWalker for performance
2353
+ * - Pre-filters elements with overflow: auto/scroll CSS properties
2354
+ * - Validates visibility and scrollability
2355
+ * - Retries up to 5 times for dynamically loaded content (SPAs)
2356
+ * - Falls back to window-only if no containers found
1820
2357
  *
1821
- * **Guardrails**:
1822
- * - Significant movement (minimum 10px position delta)
1823
- * - Depth change (minimum 5% delta between events)
1824
- * - Rate limiting (minimum 500ms interval between events per container)
1825
- * - Session cap (maximum 120 events per session)
1826
- * - Multi-container support with 250ms per-container debouncing
2358
+ * @example
2359
+ * ```typescript
2360
+ * const handler = new ScrollHandler(eventManager);
2361
+ * handler.startTracking();
2362
+ * // Automatically detects and tracks scrollable containers
2363
+ * handler.stopTracking();
2364
+ * ```
1827
2365
  */
1828
2366
  declare class ScrollHandler extends StateManager {
1829
2367
  private readonly eventManager;
1830
2368
  private readonly containers;
1831
2369
  private limitWarningLogged;
2370
+ private minDepthChange;
2371
+ private minIntervalMs;
2372
+ private maxEventsPerSession;
1832
2373
  private containerDiscoveryTimeoutId;
1833
2374
  constructor(eventManager: EventManager);
2375
+ /**
2376
+ * Starts tracking scroll events across all detected scrollable containers.
2377
+ *
2378
+ * Automatically detects scrollable containers using TreeWalker with retry logic:
2379
+ * - Searches DOM for elements with overflow: auto/scroll
2380
+ * - Validates visibility and scrollability
2381
+ * - Retries up to 5 times with 200ms intervals for dynamic content
2382
+ * - Falls back to window-only tracking if no containers found
2383
+ * - Applies primaryScrollSelector config override if provided
2384
+ *
2385
+ * Attaches debounced scroll listeners (250ms per container) with smart filtering:
2386
+ * - Significant movement (10px minimum)
2387
+ * - Depth change (5% minimum)
2388
+ * - Rate limiting (500ms minimum interval)
2389
+ * - Session cap (120 events maximum)
2390
+ */
1834
2391
  startTracking(): void;
2392
+ /**
2393
+ * Stops tracking scroll events and cleans up resources.
2394
+ *
2395
+ * Removes all scroll event listeners, clears debounce timers, cancels retry attempts,
2396
+ * and resets session state (event counter, warning flags). Prevents memory leaks by
2397
+ * properly cleaning up all containers and timers.
2398
+ */
1835
2399
  stopTracking(): void;
1836
2400
  private tryDetectScrollContainers;
2401
+ private applyPrimaryScrollSelectorIfConfigured;
1837
2402
  private findScrollableElements;
1838
2403
  private getElementSelector;
2404
+ private determineIfPrimary;
1839
2405
  private setupScrollContainer;
1840
2406
  private processScrollEvent;
1841
2407
  private shouldEmitScrollEvent;
@@ -1843,6 +2409,7 @@ declare class ScrollHandler extends StateManager {
1843
2409
  private hasElapsedMinimumInterval;
1844
2410
  private hasSignificantDepthChange;
1845
2411
  private logLimitOnce;
2412
+ private applyConfigOverrides;
1846
2413
  private isWindowScrollable;
1847
2414
  private clearContainerTimer;
1848
2415
  private getScrollDirection;
@@ -1852,7 +2419,106 @@ declare class ScrollHandler extends StateManager {
1852
2419
  private getViewportHeight;
1853
2420
  private getScrollHeight;
1854
2421
  private isElementScrollable;
2422
+ private applyPrimaryScrollSelector;
2423
+ private updateContainerPrimary;
2424
+ }
2425
+
2426
+ /**
2427
+ * Handles viewport visibility tracking using IntersectionObserver API.
2428
+ * Fires events when elements become visible for a minimum dwell time.
2429
+ */
2430
+ declare class ViewportHandler extends StateManager {
2431
+ private readonly eventManager;
2432
+ private readonly trackedElements;
2433
+ private observer;
2434
+ private mutationObserver;
2435
+ private mutationDebounceTimer;
2436
+ private config;
2437
+ constructor(eventManager: EventManager);
2438
+ /**
2439
+ * Starts tracking viewport visibility for configured elements
2440
+ */
2441
+ startTracking(): void;
2442
+ /**
2443
+ * Stops tracking and cleans up resources
2444
+ */
2445
+ stopTracking(): void;
2446
+ /**
2447
+ * Query and observe all elements matching configured elements
2448
+ */
2449
+ private observeElements;
2450
+ /**
2451
+ * Handles intersection events from IntersectionObserver
2452
+ */
2453
+ private readonly handleIntersection;
2454
+ /**
2455
+ * Fires a viewport visible event
2456
+ */
2457
+ private fireViewportEvent;
2458
+ /**
2459
+ * Sets up MutationObserver to detect dynamically added elements
2460
+ */
2461
+ private setupMutationObserver;
2462
+ /**
2463
+ * Cleans up tracking for removed DOM nodes
2464
+ */
2465
+ private cleanupRemovedNodes;
2466
+ }
2467
+
2468
+ /**
2469
+ * Transform individual events before sending to backend.
2470
+ * Applied per-event, can filter by returning null.
2471
+ *
2472
+ * @param event - The event to transform
2473
+ * @returns Transformed event or null to filter
2474
+ */
2475
+ type BeforeSendTransformer = (event: EventData) => EventData | null;
2476
+ /**
2477
+ * Transform entire batch before sending to backend.
2478
+ * Applied per-batch (10s interval or 50 events), can filter by returning null.
2479
+ *
2480
+ * **Multi-batch flushes**: when the queue spans more than one session (e.g.
2481
+ * after an idle-timeout renewal), one flush produces one batch per session,
2482
+ * and this transformer is invoked **once per session-batch**. Avoid stateful
2483
+ * transformers that assume a single invocation per flush.
2484
+ *
2485
+ * @param batch - The batch to transform
2486
+ * @returns Transformed batch or null to filter
2487
+ */
2488
+ type BeforeBatchTransformer = (batch: EventsQueue) => EventsQueue | null;
2489
+ /**
2490
+ * Runtime transformer hooks that can be updated after initialization
2491
+ * - 'beforeSend': Transform per-event before sending to backend
2492
+ * - 'beforeBatch': Transform entire batch before sending to backend
2493
+ */
2494
+ type TransformerHook = 'beforeSend' | 'beforeBatch';
2495
+ /**
2496
+ * Strongly-typed transformer map for internal storage
2497
+ */
2498
+ interface TransformerMap {
2499
+ beforeSend?: BeforeSendTransformer;
2500
+ beforeBatch?: BeforeBatchTransformer;
1855
2501
  }
2502
+ /**
2503
+ * Callback function for providing dynamic HTTP headers.
2504
+ *
2505
+ * Called synchronously before each fetch request to custom backends.
2506
+ * Return empty object {} to send no custom headers.
2507
+ *
2508
+ * **Note**: Only applies to `custom` integration (not TraceLog SaaS).
2509
+ * Headers are NOT applied to sendBeacon requests (page unload).
2510
+ *
2511
+ * @returns Record of header names to values
2512
+ *
2513
+ * @example
2514
+ * ```typescript
2515
+ * tracelog.setCustomHeaders(() => ({
2516
+ * 'Authorization': `Bearer ${getAuthToken()}`,
2517
+ * 'X-Request-ID': crypto.randomUUID()
2518
+ * }));
2519
+ * ```
2520
+ */
2521
+ type CustomHeadersProvider = () => Record<string, string>;
1856
2522
 
1857
2523
  /**
1858
2524
  * Testing bridge interface for E2E and integration tests
@@ -1874,9 +2540,18 @@ interface TraceLogTestBridge {
1874
2540
  get<T extends keyof State>(key: T): State[T];
1875
2541
  getFullState(): Readonly<State>;
1876
2542
  getState(): Readonly<State>;
2543
+ updateGlobalMetadata(metadata: Record<string, unknown>): void;
2544
+ mergeGlobalMetadata(metadata: Record<string, unknown>): void;
1877
2545
  getSessionData(): Record<string, unknown> | null;
1878
2546
  getQueueLength(): number;
1879
2547
  getQueueEvents(): EventData[];
2548
+ setQaMode(enabled: boolean): void;
2549
+ setTransformer(hook: 'beforeSend', fn: BeforeSendTransformer): void;
2550
+ setTransformer(hook: 'beforeBatch', fn: BeforeBatchTransformer): void;
2551
+ setTransformer(hook: TransformerHook, fn: BeforeSendTransformer | BeforeBatchTransformer): void;
2552
+ removeTransformer(hook: TransformerHook): void;
2553
+ setCustomHeaders(provider: CustomHeadersProvider): void;
2554
+ removeCustomHeaders(): void;
1880
2555
  getEventManager(): EventManager | undefined;
1881
2556
  getStorageManager(): StorageManager | null;
1882
2557
  getPerformanceHandler(): PerformanceHandler | null;
@@ -1885,6 +2560,7 @@ interface TraceLogTestBridge {
1885
2560
  getPageViewHandler(): PageViewHandler | null;
1886
2561
  getClickHandler(): ClickHandler | null;
1887
2562
  getScrollHandler(): ScrollHandler | null;
2563
+ getViewportHandler(): ViewportHandler | null;
1888
2564
  getHandlers(): {
1889
2565
  performance: PerformanceHandler | null;
1890
2566
  error: ErrorHandler | null;
@@ -1892,6 +2568,7 @@ interface TraceLogTestBridge {
1892
2568
  pageView: PageViewHandler | null;
1893
2569
  click: ClickHandler | null;
1894
2570
  scroll: ScrollHandler | null;
2571
+ viewport: ViewportHandler | null;
1895
2572
  };
1896
2573
  identify(userId: string, traits?: Record<string, string>): void;
1897
2574
  resetIdentity(): Promise<void>;
@@ -1959,6 +2636,31 @@ declare global {
1959
2636
  }
1960
2637
  }
1961
2638
 
2639
+ /**
2640
+ * Transforms events at runtime before sending to custom backend.
2641
+ *
2642
+ * Note: Only applies to custom backend integration. TraceLog SaaS ignores transformers.
2643
+ *
2644
+ * @param hook - 'beforeSend' (per-event) or 'beforeBatch' (batch-level)
2645
+ * @param fn - Transformer function. Return null to filter event/batch.
2646
+ * @throws {Error} If called during destroy()
2647
+ * @throws {Error} If fn is not a function
2648
+ *
2649
+ * @example
2650
+ * ```typescript
2651
+ * tracelog.setTransformer('beforeSend', (data) => {
2652
+ * if ('type' in data) {
2653
+ * return { ...data, appVersion: '1.0.0' };
2654
+ * }
2655
+ * return data;
2656
+ * });
2657
+ * ```
2658
+ *
2659
+ * @see {@link https://github.com/tracelog/tracelog-lib/blob/main/API_REFERENCE.md#settransformer} for integration behavior
2660
+ */
2661
+ declare function setTransformer(hook: 'beforeSend', fn: BeforeSendTransformer): void;
2662
+ declare function setTransformer(hook: 'beforeBatch', fn: BeforeBatchTransformer): void;
2663
+
1962
2664
  /**
1963
2665
  * Consolidated configuration constants for TraceLog
1964
2666
  * This file centralizes all timing, limits, browser, and initialization constants
@@ -1973,6 +2675,16 @@ declare const MAX_STRING_LENGTH = 1000;
1973
2675
  declare const MAX_STRING_LENGTH_IN_ARRAY = 500;
1974
2676
  declare const MAX_ARRAY_LENGTH = 1000;
1975
2677
 
2678
+ /**
2679
+ * Error handling and PII sanitization constants for TraceLog
2680
+ * Centralizes patterns and limits for error tracking and data protection
2681
+ */
2682
+ /**
2683
+ * Regular expressions for detecting and sanitizing Personally Identifiable Information (PII)
2684
+ * These patterns are used to replace sensitive information with [REDACTED] in error messages
2685
+ */
2686
+ declare const PII_PATTERNS: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
2687
+
1976
2688
  /**
1977
2689
  * Performance monitoring and web vitals constants for TraceLog
1978
2690
  * Centralizes thresholds and configuration for performance tracking
@@ -1980,15 +2692,20 @@ declare const MAX_ARRAY_LENGTH = 1000;
1980
2692
 
1981
2693
  /**
1982
2694
  * Web Vitals "good" thresholds (75th percentile boundaries)
2695
+ * Metrics below or equal to these values are considered good performance.
1983
2696
  * Reference: https://web.dev/articles/vitals
1984
2697
  */
1985
2698
  declare const WEB_VITALS_GOOD_THRESHOLDS: Record<WebVitalType, number>;
1986
2699
  /**
1987
2700
  * Web Vitals "needs improvement" thresholds
2701
+ * Metrics exceeding these values need attention but aren't critically poor.
2702
+ * Reference: https://web.dev/articles/vitals
1988
2703
  */
1989
2704
  declare const WEB_VITALS_NEEDS_IMPROVEMENT_THRESHOLDS: Record<WebVitalType, number>;
1990
2705
  /**
1991
2706
  * Web Vitals "poor" thresholds
2707
+ * Metrics exceeding these values indicate poor performance requiring immediate attention.
2708
+ * Reference: https://web.dev/articles/vitals
1992
2709
  */
1993
2710
  declare const WEB_VITALS_POOR_THRESHOLDS: Record<WebVitalType, number>;
1994
2711
  /**
@@ -2007,12 +2724,21 @@ declare const tracelog: {
2007
2724
  event: (name: string, metadata?: Record<string, MetadataType> | Record<string, MetadataType>[], options?: EventOptions) => void;
2008
2725
  on: <K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>) => void;
2009
2726
  off: <K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>) => void;
2727
+ setTransformer: typeof setTransformer;
2728
+ removeTransformer: (hook: TransformerHook) => void;
2729
+ setCustomHeaders: (provider: CustomHeadersProvider) => void;
2730
+ removeCustomHeaders: () => void;
2010
2731
  isInitialized: () => boolean;
2011
2732
  getSessionId: () => string | null;
2012
2733
  getUserId: () => string | null;
2013
2734
  destroy: () => void;
2735
+ setQaMode: (enabled: boolean) => void;
2736
+ updateGlobalMetadata: (metadata: Record<string, MetadataType>) => void;
2737
+ mergeGlobalMetadata: (metadata: Record<string, MetadataType>) => void;
2014
2738
  identify: (userId: string, traits?: Record<string, string>) => void;
2015
2739
  resetIdentity: () => Promise<void>;
2740
+ flushImmediately: () => Promise<boolean>;
2741
+ flushImmediatelySync: () => boolean;
2016
2742
  };
2017
2743
 
2018
- export { AppConfigValidationError, type ClickCoordinates, type ClickData, type ClickTrackingElementData, type Config, type CustomEventData, DEFAULT_SESSION_TIMEOUT, DEFAULT_WEB_VITALS_MODE, type DeviceInfo, DeviceType, type EmitterCallback, EmitterEvent, type EmitterMap, type ErrorData, ErrorType, type EventData, type EventOptions, EventType, type EventTypeName, type EventsQueue, type IdentifyData, type InitResult, InitializationTimeoutError, IntegrationValidationError, MAX_ARRAY_LENGTH, MAX_CUSTOM_EVENT_ARRAY_SIZE, MAX_CUSTOM_EVENT_KEYS, MAX_CUSTOM_EVENT_NAME_LENGTH, MAX_CUSTOM_EVENT_STRING_SIZE, MAX_NESTED_OBJECT_KEYS, MAX_STRING_LENGTH, MAX_STRING_LENGTH_IN_ARRAY, type MetadataType, Mode, PII_PATTERNS, type PageViewData, PermanentError, type PersistedEventsQueue, type QueueMetadata, type QueuedEvent, RateLimitError, SamplingRateValidationError, type ScrollData, ScrollDirection, type SessionEventCounts, SessionTimeoutValidationError, SpecialApiUrl, type State, TimeoutError, type TraceLogTestBridge, TraceLogValidationError, type UTM, WEB_VITALS_GOOD_THRESHOLDS, WEB_VITALS_NEEDS_IMPROVEMENT_THRESHOLDS, WEB_VITALS_POOR_THRESHOLDS, type WebVitalType, type WebVitalsData, type WebVitalsMode, getWebVitalsThresholds, tracelog };
2744
+ export { AppConfigValidationError, type BeforeBatchTransformer, type BeforeSendTransformer, type ClickCoordinates, type ClickData, type ClickTrackingElementData, type Config, type CustomEventData, type CustomHeadersProvider, DEFAULT_SESSION_TIMEOUT, DEFAULT_WEB_VITALS_MODE, type DeviceInfo, DeviceType, type EmitterCallback, EmitterEvent, type EmitterMap, type ErrorData, ErrorType, type EventData, type EventOptions, EventType, type EventTypeName, type EventsQueue, type IdentifyData, type InitResult, InitializationTimeoutError, IntegrationValidationError, MAX_ARRAY_LENGTH, MAX_CUSTOM_EVENT_ARRAY_SIZE, MAX_CUSTOM_EVENT_KEYS, MAX_CUSTOM_EVENT_NAME_LENGTH, MAX_CUSTOM_EVENT_STRING_SIZE, MAX_NESTED_OBJECT_KEYS, MAX_STRING_LENGTH, MAX_STRING_LENGTH_IN_ARRAY, type MetadataType, Mode, PII_PATTERNS, type PageViewData, PermanentError, type PersistedEventsQueue, type PrimaryScrollEvent, type QueueMetadata, type QueuedEvent, RateLimitError, SamplingRateValidationError, type ScrollData, ScrollDirection, type SecondaryScrollEvent, type SessionEventCounts, SessionTimeoutValidationError, SpecialApiUrl, type State, TimeoutError, type TraceLogTestBridge, TraceLogValidationError, type TransformerHook, type TransformerMap, type UTM, type ViewportConfig, type ViewportElement, type ViewportEventData, WEB_VITALS_GOOD_THRESHOLDS, WEB_VITALS_NEEDS_IMPROVEMENT_THRESHOLDS, WEB_VITALS_POOR_THRESHOLDS, type WebVitalType, type WebVitalsData, type WebVitalsMode, getWebVitalsThresholds, isPrimaryScrollEvent, isSecondaryScrollEvent, tracelog };