@tracelog/lib 0.4.0 → 0.5.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.
Files changed (98) hide show
  1. package/dist/browser/tracelog.js +620 -658
  2. package/dist/cjs/api.d.ts +1 -53
  3. package/dist/cjs/api.js +0 -59
  4. package/dist/cjs/app.constants.d.ts +1 -1
  5. package/dist/cjs/app.d.ts +1 -5
  6. package/dist/cjs/app.js +4 -12
  7. package/dist/cjs/constants/api.constants.d.ts +5 -2
  8. package/dist/cjs/constants/api.constants.js +5 -14
  9. package/dist/cjs/constants/config.constants.d.ts +3 -3
  10. package/dist/cjs/constants/config.constants.js +3 -3
  11. package/dist/cjs/constants/error.constants.d.ts +7 -2
  12. package/dist/cjs/constants/error.constants.js +13 -2
  13. package/dist/cjs/handlers/click.handler.js +0 -6
  14. package/dist/cjs/handlers/error.handler.js +9 -0
  15. package/dist/cjs/handlers/scroll.handler.js +0 -5
  16. package/dist/cjs/handlers/session.handler.js +5 -2
  17. package/dist/cjs/integrations/google-analytics.integration.d.ts +1 -1
  18. package/dist/cjs/integrations/google-analytics.integration.js +2 -1
  19. package/dist/cjs/managers/api.manager.d.ts +1 -1
  20. package/dist/cjs/managers/api.manager.js +3 -3
  21. package/dist/cjs/managers/config.builder.d.ts +33 -0
  22. package/dist/cjs/managers/config.builder.js +116 -0
  23. package/dist/cjs/managers/config.manager.d.ts +13 -14
  24. package/dist/cjs/managers/config.manager.js +52 -58
  25. package/dist/cjs/managers/event.manager.d.ts +1 -46
  26. package/dist/cjs/managers/event.manager.js +15 -70
  27. package/dist/cjs/managers/sender.manager.d.ts +1 -28
  28. package/dist/cjs/managers/sender.manager.js +43 -73
  29. package/dist/cjs/managers/session.manager.d.ts +2 -49
  30. package/dist/cjs/managers/session.manager.js +42 -83
  31. package/dist/cjs/managers/state.manager.d.ts +1 -28
  32. package/dist/cjs/managers/state.manager.js +5 -33
  33. package/dist/cjs/managers/storage.manager.d.ts +6 -0
  34. package/dist/cjs/managers/storage.manager.js +18 -1
  35. package/dist/cjs/public-api.d.ts +1 -1
  36. package/dist/cjs/test-bridge.d.ts +3 -2
  37. package/dist/cjs/test-bridge.js +34 -7
  38. package/dist/cjs/types/api.types.d.ts +24 -8
  39. package/dist/cjs/types/api.types.js +24 -8
  40. package/dist/cjs/types/event.types.d.ts +2 -4
  41. package/dist/cjs/types/event.types.js +0 -1
  42. package/dist/cjs/types/test-bridge.types.d.ts +2 -1
  43. package/dist/cjs/utils/logging/debug-logger.utils.d.ts +1 -2
  44. package/dist/cjs/utils/logging/debug-logger.utils.js +2 -3
  45. package/dist/cjs/utils/validations/config-validations.utils.d.ts +1 -26
  46. package/dist/cjs/utils/validations/config-validations.utils.js +5 -117
  47. package/dist/cjs/utils/validations/event-validations.utils.d.ts +2 -2
  48. package/dist/cjs/utils/validations/metadata-validations.utils.d.ts +3 -3
  49. package/dist/cjs/utils/validations/metadata-validations.utils.js +41 -3
  50. package/dist/esm/api.d.ts +1 -53
  51. package/dist/esm/api.js +0 -59
  52. package/dist/esm/app.constants.d.ts +1 -1
  53. package/dist/esm/app.d.ts +1 -5
  54. package/dist/esm/app.js +5 -13
  55. package/dist/esm/constants/api.constants.d.ts +5 -2
  56. package/dist/esm/constants/api.constants.js +5 -13
  57. package/dist/esm/constants/config.constants.d.ts +3 -3
  58. package/dist/esm/constants/config.constants.js +3 -3
  59. package/dist/esm/constants/error.constants.d.ts +7 -2
  60. package/dist/esm/constants/error.constants.js +12 -1
  61. package/dist/esm/handlers/click.handler.js +0 -6
  62. package/dist/esm/handlers/error.handler.js +10 -1
  63. package/dist/esm/handlers/scroll.handler.js +0 -5
  64. package/dist/esm/handlers/session.handler.js +5 -2
  65. package/dist/esm/integrations/google-analytics.integration.d.ts +1 -1
  66. package/dist/esm/integrations/google-analytics.integration.js +2 -1
  67. package/dist/esm/managers/api.manager.d.ts +1 -1
  68. package/dist/esm/managers/api.manager.js +3 -3
  69. package/dist/esm/managers/config.builder.d.ts +33 -0
  70. package/dist/esm/managers/config.builder.js +112 -0
  71. package/dist/esm/managers/config.manager.d.ts +13 -14
  72. package/dist/esm/managers/config.manager.js +54 -60
  73. package/dist/esm/managers/event.manager.d.ts +1 -46
  74. package/dist/esm/managers/event.manager.js +15 -70
  75. package/dist/esm/managers/sender.manager.d.ts +1 -28
  76. package/dist/esm/managers/sender.manager.js +44 -74
  77. package/dist/esm/managers/session.manager.d.ts +2 -49
  78. package/dist/esm/managers/session.manager.js +42 -83
  79. package/dist/esm/managers/state.manager.d.ts +1 -28
  80. package/dist/esm/managers/state.manager.js +4 -33
  81. package/dist/esm/managers/storage.manager.d.ts +6 -0
  82. package/dist/esm/managers/storage.manager.js +18 -1
  83. package/dist/esm/public-api.d.ts +1 -1
  84. package/dist/esm/test-bridge.d.ts +3 -2
  85. package/dist/esm/test-bridge.js +34 -7
  86. package/dist/esm/types/api.types.d.ts +24 -8
  87. package/dist/esm/types/api.types.js +24 -8
  88. package/dist/esm/types/event.types.d.ts +2 -4
  89. package/dist/esm/types/event.types.js +0 -1
  90. package/dist/esm/types/test-bridge.types.d.ts +2 -1
  91. package/dist/esm/utils/logging/debug-logger.utils.d.ts +1 -2
  92. package/dist/esm/utils/logging/debug-logger.utils.js +3 -4
  93. package/dist/esm/utils/validations/config-validations.utils.d.ts +1 -26
  94. package/dist/esm/utils/validations/config-validations.utils.js +5 -114
  95. package/dist/esm/utils/validations/event-validations.utils.d.ts +2 -2
  96. package/dist/esm/utils/validations/metadata-validations.utils.d.ts +3 -3
  97. package/dist/esm/utils/validations/metadata-validations.utils.js +41 -3
  98. package/package.json +1 -1
@@ -3,13 +3,13 @@
3
3
  * This file centralizes all timing, limits, browser, and initialization constants
4
4
  */
5
5
  export declare const DEFAULT_SESSION_TIMEOUT: number;
6
- export declare const DUPLICATE_EVENT_THRESHOLD_MS = 1000;
6
+ export declare const DUPLICATE_EVENT_THRESHOLD_MS = 500;
7
7
  export declare const EVENT_SENT_INTERVAL_MS = 10000;
8
8
  export declare const SCROLL_DEBOUNCE_TIME_MS = 250;
9
9
  export declare const DEFAULT_VISIBILITY_TIMEOUT_MS = 2000;
10
10
  export declare const EVENT_EXPIRY_HOURS = 24;
11
11
  export declare const EVENT_PERSISTENCE_MAX_AGE_MS: number;
12
- export declare const MAX_EVENTS_QUEUE_LENGTH = 500;
12
+ export declare const MAX_EVENTS_QUEUE_LENGTH = 100;
13
13
  export declare const MAX_RETRIES = 3;
14
14
  export declare const RETRY_DELAY_MS = 5000;
15
15
  export declare const REQUEST_TIMEOUT_MS = 10000;
@@ -65,7 +65,7 @@ export declare const VALIDATION_MESSAGES: {
65
65
  readonly MISSING_PROJECT_ID: "Project ID is required";
66
66
  readonly PROJECT_ID_EMPTY_AFTER_TRIM: "Project ID is required";
67
67
  readonly INVALID_SESSION_TIMEOUT: "Session timeout must be between 30000ms (30 seconds) and 86400000ms (24 hours)";
68
- readonly INVALID_SAMPLING_RATE: "Sampling rate must be greater than 0 and less than or equal to 1";
68
+ readonly INVALID_SAMPLING_RATE: "Sampling rate must be between 0 and 1";
69
69
  readonly INVALID_ERROR_SAMPLING_RATE: "Error sampling must be between 0 and 1";
70
70
  readonly INVALID_GOOGLE_ANALYTICS_ID: "Google Analytics measurement ID is required when integration is enabled";
71
71
  readonly INVALID_SCROLL_CONTAINER_SELECTORS: "Scroll container selectors must be valid CSS selectors";
@@ -6,7 +6,7 @@
6
6
  // SESSION & TIMING
7
7
  // ============================================================================
8
8
  export const DEFAULT_SESSION_TIMEOUT = 15 * 60 * 1000; // 15 minutes
9
- export const DUPLICATE_EVENT_THRESHOLD_MS = 1000; // 1 second
9
+ export const DUPLICATE_EVENT_THRESHOLD_MS = 500; // 500ms
10
10
  export const EVENT_SENT_INTERVAL_MS = 10000; // 10 seconds
11
11
  // Throttling and debouncing
12
12
  export const SCROLL_DEBOUNCE_TIME_MS = 250;
@@ -17,7 +17,7 @@ export const EVENT_PERSISTENCE_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
17
17
  // ============================================================================
18
18
  // LIMITS & RETRIES
19
19
  // ============================================================================
20
- export const MAX_EVENTS_QUEUE_LENGTH = 500;
20
+ export const MAX_EVENTS_QUEUE_LENGTH = 100;
21
21
  export const MAX_RETRIES = 3;
22
22
  export const RETRY_DELAY_MS = 5000;
23
23
  export const REQUEST_TIMEOUT_MS = 10000;
@@ -149,7 +149,7 @@ export const VALIDATION_MESSAGES = {
149
149
  // Session timeout validation
150
150
  INVALID_SESSION_TIMEOUT: `Session timeout must be between ${MIN_SESSION_TIMEOUT_MS}ms (30 seconds) and ${MAX_SESSION_TIMEOUT_MS}ms (24 hours)`,
151
151
  // Sampling rate validation
152
- INVALID_SAMPLING_RATE: 'Sampling rate must be greater than 0 and less than or equal to 1',
152
+ INVALID_SAMPLING_RATE: 'Sampling rate must be between 0 and 1',
153
153
  INVALID_ERROR_SAMPLING_RATE: 'Error sampling must be between 0 and 1',
154
154
  // Integration validation
155
155
  INVALID_GOOGLE_ANALYTICS_ID: 'Google Analytics measurement ID is required when integration is enabled',
@@ -6,7 +6,7 @@
6
6
  * Regular expressions for detecting and sanitizing Personally Identifiable Information (PII)
7
7
  * These patterns are used to replace sensitive information with [REDACTED] in error messages
8
8
  */
9
- export declare const PII_PATTERNS: readonly [RegExp, RegExp, RegExp, RegExp];
9
+ export declare const PII_PATTERNS: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
10
10
  /**
11
11
  * Maximum length for error messages before truncation
12
12
  * Prevents extremely long error messages from consuming excessive storage
@@ -16,12 +16,17 @@ export declare const MAX_ERROR_MESSAGE_LENGTH = 500;
16
16
  * Time window for error suppression in milliseconds
17
17
  * Prevents duplicate errors from flooding the system within this timeframe
18
18
  */
19
- export declare const ERROR_SUPPRESSION_WINDOW_MS = 60000;
19
+ export declare const ERROR_SUPPRESSION_WINDOW_MS = 5000;
20
20
  /**
21
21
  * Maximum number of unique errors to track for suppression
22
22
  * Prevents memory usage from growing indefinitely
23
23
  */
24
24
  export declare const MAX_TRACKED_ERRORS = 50;
25
+ /**
26
+ * Hard limit for error tracking before aggressive cleanup
27
+ * If this limit is exceeded, the entire error map is cleared
28
+ */
29
+ export declare const MAX_TRACKED_ERRORS_HARD_LIMIT: number;
25
30
  /**
26
31
  * Default error sampling rate
27
32
  * Controls what percentage of errors are actually reported
@@ -18,6 +18,12 @@ export const PII_PATTERNS = [
18
18
  /\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g,
19
19
  // IBAN (International Bank Account Number)
20
20
  /\b[A-Z]{2}\d{2}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/gi,
21
+ // API keys/tokens (sk_test_, sk_live_, pk_test_, pk_live_, etc.)
22
+ /\b[sp]k_(test|live)_[a-zA-Z0-9]{10,}\b/gi,
23
+ // Bearer tokens (JWT-like patterns - matches complete and partial tokens)
24
+ /Bearer\s+[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)?(?:\.[A-Za-z0-9_-]+)?/gi,
25
+ // Passwords in connection strings (protocol://user:password@host)
26
+ /:\/\/[^:/]+:([^@]+)@/gi,
21
27
  ];
22
28
  // ============================================================================
23
29
  // ERROR TRACKING LIMITS
@@ -31,12 +37,17 @@ export const MAX_ERROR_MESSAGE_LENGTH = 500;
31
37
  * Time window for error suppression in milliseconds
32
38
  * Prevents duplicate errors from flooding the system within this timeframe
33
39
  */
34
- export const ERROR_SUPPRESSION_WINDOW_MS = 60000; // 1 minute
40
+ export const ERROR_SUPPRESSION_WINDOW_MS = 5000; // 5 seconds
35
41
  /**
36
42
  * Maximum number of unique errors to track for suppression
37
43
  * Prevents memory usage from growing indefinitely
38
44
  */
39
45
  export const MAX_TRACKED_ERRORS = 50;
46
+ /**
47
+ * Hard limit for error tracking before aggressive cleanup
48
+ * If this limit is exceeded, the entire error map is cleared
49
+ */
50
+ export const MAX_TRACKED_ERRORS_HARD_LIMIT = MAX_TRACKED_ERRORS * 2;
40
51
  // ============================================================================
41
52
  // ERROR SAMPLING
42
53
  // ============================================================================
@@ -63,11 +63,9 @@ export class ClickHandler extends StateManager {
63
63
  getRelevantClickElement(element) {
64
64
  for (const selector of INTERACTIVE_SELECTORS) {
65
65
  try {
66
- // First check if the element itself matches
67
66
  if (element.matches(selector)) {
68
67
  return element;
69
68
  }
70
- // If not, search for matching ancestors
71
69
  const parent = element.closest(selector);
72
70
  if (parent) {
73
71
  return parent;
@@ -130,19 +128,15 @@ export class ClickHandler extends StateManager {
130
128
  getRelevantText(clickedElement, relevantElement) {
131
129
  const clickedText = clickedElement.textContent?.trim() ?? '';
132
130
  const relevantText = relevantElement.textContent?.trim() ?? '';
133
- // No text available
134
131
  if (!clickedText && !relevantText) {
135
132
  return '';
136
133
  }
137
- // Prefer clicked element text if it's reasonable length
138
134
  if (clickedText && clickedText.length <= MAX_TEXT_LENGTH) {
139
135
  return clickedText;
140
136
  }
141
- // Use relevant element text if it fits
142
137
  if (relevantText.length <= MAX_TEXT_LENGTH) {
143
138
  return relevantText;
144
139
  }
145
- // Truncate relevant text if too long
146
140
  return relevantText.slice(0, MAX_TEXT_LENGTH - 3) + '...';
147
141
  }
148
142
  extractElementAttributes(element) {
@@ -1,6 +1,6 @@
1
1
  import { StateManager } from '../managers/state.manager';
2
2
  import { ErrorType, EventType } from '../types';
3
- import { PII_PATTERNS, MAX_ERROR_MESSAGE_LENGTH, ERROR_SUPPRESSION_WINDOW_MS, MAX_TRACKED_ERRORS, } from '../constants/error.constants';
3
+ import { PII_PATTERNS, MAX_ERROR_MESSAGE_LENGTH, ERROR_SUPPRESSION_WINDOW_MS, MAX_TRACKED_ERRORS, MAX_TRACKED_ERRORS_HARD_LIMIT, } from '../constants/error.constants';
4
4
  import { debugLog } from '../utils/logging';
5
5
  /**
6
6
  * Simplified error handler for tracking JavaScript errors and unhandled promise rejections
@@ -106,6 +106,15 @@ export class ErrorHandler extends StateManager {
106
106
  return true;
107
107
  }
108
108
  this.recentErrors.set(key, now);
109
+ if (this.recentErrors.size > MAX_TRACKED_ERRORS_HARD_LIMIT) {
110
+ debugLog.warn('ErrorHandler', 'Hard limit exceeded, clearing all tracked errors', {
111
+ size: this.recentErrors.size,
112
+ limit: MAX_TRACKED_ERRORS_HARD_LIMIT,
113
+ });
114
+ this.recentErrors.clear();
115
+ this.recentErrors.set(key, now);
116
+ return false;
117
+ }
109
118
  if (this.recentErrors.size > MAX_TRACKED_ERRORS) {
110
119
  this.pruneOldErrors();
111
120
  }
@@ -61,7 +61,6 @@ export class ScrollHandler extends StateManager {
61
61
  }
62
62
  }
63
63
  setupScrollContainer(element) {
64
- // Skip setup for non-scrollable elements
65
64
  if (element !== window && !this.isElementScrollable(element)) {
66
65
  return;
67
66
  }
@@ -173,12 +172,10 @@ export class ScrollHandler extends StateManager {
173
172
  calculateScrollData(container) {
174
173
  const { element, lastScrollPos } = container;
175
174
  const scrollTop = this.getScrollTop(element);
176
- // Early return: check significant movement first (cheapest check)
177
175
  const positionDelta = Math.abs(scrollTop - lastScrollPos);
178
176
  if (positionDelta < SIGNIFICANT_SCROLL_DELTA) {
179
177
  return null;
180
178
  }
181
- // Early return: check if window is scrollable
182
179
  if (element === window && !this.isWindowScrollable()) {
183
180
  return null;
184
181
  }
@@ -206,7 +203,6 @@ export class ScrollHandler extends StateManager {
206
203
  style.overflowX === 'scroll' ||
207
204
  style.overflow === 'auto' ||
208
205
  style.overflow === 'scroll';
209
- // Element must have scrollable overflow AND content that exceeds the container
210
206
  const hasOverflowContent = element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
211
207
  return hasScrollableOverflow && hasOverflowContent;
212
208
  }
@@ -215,7 +211,6 @@ export class ScrollHandler extends StateManager {
215
211
  return document.querySelector(selector);
216
212
  }
217
213
  catch (error) {
218
- // Invalid CSS selector - log warning and continue
219
214
  debugLog.clientWarn('ScrollHandler', 'Invalid CSS selector', {
220
215
  selector,
221
216
  error: error instanceof Error ? error.message : 'Unknown error',
@@ -17,12 +17,15 @@ export class SessionHandler extends StateManager {
17
17
  debugLog.warn('SessionHandler', 'Cannot start tracking on destroyed handler');
18
18
  return;
19
19
  }
20
+ const projectId = this.get('config')?.id;
21
+ if (!projectId) {
22
+ throw new Error('Cannot start session tracking: config not available');
23
+ }
20
24
  try {
21
- this.sessionManager = new SessionManager(this.storageManager, this.eventManager);
25
+ this.sessionManager = new SessionManager(this.storageManager, this.eventManager, projectId);
22
26
  await this.sessionManager.startTracking();
23
27
  }
24
28
  catch (error) {
25
- // Cleanup on failure
26
29
  if (this.sessionManager) {
27
30
  try {
28
31
  this.sessionManager.destroy();
@@ -9,7 +9,7 @@ declare global {
9
9
  export declare class GoogleAnalyticsIntegration extends StateManager {
10
10
  private isInitialized;
11
11
  initialize(): Promise<void>;
12
- trackEvent(eventName: string, metadata: Record<string, MetadataType>): void;
12
+ trackEvent(eventName: string, metadata: Record<string, MetadataType> | Record<string, MetadataType>[]): void;
13
13
  cleanup(): void;
14
14
  private isScriptAlreadyLoaded;
15
15
  private loadScript;
@@ -34,7 +34,8 @@ export class GoogleAnalyticsIntegration extends StateManager {
34
34
  return;
35
35
  }
36
36
  try {
37
- window.gtag('event', eventName, metadata);
37
+ const normalizedMetadata = Array.isArray(metadata) ? { items: metadata } : metadata;
38
+ window.gtag('event', eventName, normalizedMetadata);
38
39
  }
39
40
  catch (error) {
40
41
  debugLog.error('GoogleAnalyticsIntegration', 'Event tracking failed', {
@@ -2,7 +2,7 @@
2
2
  * Generates API URL for TraceLog service based on project ID
3
3
  *
4
4
  * Handles two special cases:
5
- * - 'localhost:PORT' - for local development (generates http://localhost:PORT)
5
+ * - 'localhost:8080' or 'localhost:9999' - for local development (generates http://localhost:PORT)
6
6
  * - Regular project IDs - generates subdomain URLs via getApiUrl utility
7
7
  *
8
8
  * @param id Project ID or localhost address
@@ -5,7 +5,7 @@ import { debugLog } from '../utils/logging';
5
5
  * Generates API URL for TraceLog service based on project ID
6
6
  *
7
7
  * Handles two special cases:
8
- * - 'localhost:PORT' - for local development (generates http://localhost:PORT)
8
+ * - 'localhost:8080' or 'localhost:9999' - for local development (generates http://localhost:PORT)
9
9
  * - Regular project IDs - generates subdomain URLs via getApiUrl utility
10
10
  *
11
11
  * @param id Project ID or localhost address
@@ -15,8 +15,8 @@ import { debugLog } from '../utils/logging';
15
15
  */
16
16
  export function getApiUrlForProject(id, allowHttp = false) {
17
17
  try {
18
- // Handle localhost development case
19
- if (id.startsWith(SpecialProjectId.Localhost)) {
18
+ // Handle localhost development case (localhost:8080 or localhost:9999)
19
+ if (id === SpecialProjectId.Localhost || id === SpecialProjectId.Fail) {
20
20
  const url = `http://${id}`;
21
21
  if (!isValidUrl(url, true)) {
22
22
  throw new Error(`Invalid localhost URL format: ${id}`);
@@ -0,0 +1,33 @@
1
+ import { AppConfig, ApiConfig, Config } from '../types';
2
+ /**
3
+ * Centralized configuration builder
4
+ * Single source of truth for merging and building final configuration
5
+ */
6
+ export declare class ConfigBuilder {
7
+ /**
8
+ * Builds final configuration from app config and API config
9
+ * Applies clear precedence: API overrides client, with defaults as fallback
10
+ */
11
+ static build(appConfig: AppConfig, apiConfig?: ApiConfig): Config;
12
+ /**
13
+ * Resolves session timeout with validation
14
+ * Returns default if undefined or out of valid range
15
+ */
16
+ private static resolveSessionTimeout;
17
+ /**
18
+ * Resolves sampling rate with validation
19
+ * Priority: API config > app config > default
20
+ */
21
+ private static resolveSamplingRate;
22
+ /**
23
+ * Resolves error sampling rate based on mode
24
+ * In debug/qa modes: uses provided value or defaults to full sampling (1.0)
25
+ * In production: uses provided value or defaults to 10% sampling (0.1)
26
+ */
27
+ private static resolveErrorSampling;
28
+ /**
29
+ * Resolves mode with special project ID handling
30
+ * Priority: Special project ID > API mode > app mode
31
+ */
32
+ private static resolveMode;
33
+ }
@@ -0,0 +1,112 @@
1
+ import { DEFAULT_SESSION_TIMEOUT, DEFAULT_SAMPLING_RATE, MIN_SAMPLING_RATE, MAX_SAMPLING_RATE, MIN_SESSION_TIMEOUT_MS, MAX_SESSION_TIMEOUT_MS, } from '../constants/config.constants';
2
+ import { Mode, SpecialProjectId } from '../types';
3
+ import { debugLog } from '../utils/logging';
4
+ /**
5
+ * Centralized configuration builder
6
+ * Single source of truth for merging and building final configuration
7
+ */
8
+ export class ConfigBuilder {
9
+ /**
10
+ * Builds final configuration from app config and API config
11
+ * Applies clear precedence: API overrides client, with defaults as fallback
12
+ */
13
+ static build(appConfig, apiConfig = {}) {
14
+ // Resolve mode first as it affects other settings (like errorSampling)
15
+ const finalMode = this.resolveMode(appConfig, apiConfig.mode);
16
+ const config = {
17
+ // Core identifiers
18
+ id: appConfig.id,
19
+ // Session configuration
20
+ sessionTimeout: this.resolveSessionTimeout(appConfig.sessionTimeout),
21
+ // Mode configuration (resolved first)
22
+ mode: finalMode,
23
+ // Sampling configuration (depends on mode)
24
+ samplingRate: this.resolveSamplingRate(apiConfig.samplingRate, appConfig.samplingRate),
25
+ errorSampling: this.resolveErrorSampling(appConfig.errorSampling, finalMode),
26
+ // Filtering configuration
27
+ excludedUrlPaths: apiConfig.excludedUrlPaths ?? appConfig.excludedUrlPaths ?? [],
28
+ tags: apiConfig.tags ?? [],
29
+ ipExcluded: apiConfig.ipExcluded ?? false,
30
+ // Client-only configuration
31
+ globalMetadata: appConfig.globalMetadata ?? {},
32
+ scrollContainerSelectors: appConfig.scrollContainerSelectors,
33
+ sensitiveQueryParams: appConfig.sensitiveQueryParams ?? [],
34
+ integrations: appConfig.integrations,
35
+ // Security configuration
36
+ allowHttp: appConfig.allowHttp ?? false,
37
+ };
38
+ debugLog.debug('ConfigBuilder', 'Configuration built', {
39
+ projectId: config.id,
40
+ mode: config.mode,
41
+ samplingRate: config.samplingRate,
42
+ errorSampling: config.errorSampling,
43
+ hasTags: !!config.tags?.length,
44
+ hasExclusions: !!config.excludedUrlPaths?.length,
45
+ });
46
+ return config;
47
+ }
48
+ /**
49
+ * Resolves session timeout with validation
50
+ * Returns default if undefined or out of valid range
51
+ */
52
+ static resolveSessionTimeout(timeout) {
53
+ if (timeout === undefined) {
54
+ return DEFAULT_SESSION_TIMEOUT;
55
+ }
56
+ if (timeout < MIN_SESSION_TIMEOUT_MS || timeout > MAX_SESSION_TIMEOUT_MS) {
57
+ debugLog.warn('ConfigBuilder', 'Invalid session timeout, using default', {
58
+ provided: timeout,
59
+ min: MIN_SESSION_TIMEOUT_MS,
60
+ max: MAX_SESSION_TIMEOUT_MS,
61
+ default: DEFAULT_SESSION_TIMEOUT,
62
+ });
63
+ return DEFAULT_SESSION_TIMEOUT;
64
+ }
65
+ return timeout;
66
+ }
67
+ /**
68
+ * Resolves sampling rate with validation
69
+ * Priority: API config > app config > default
70
+ */
71
+ static resolveSamplingRate(apiRate, appRate) {
72
+ const rate = apiRate ?? appRate;
73
+ if (rate === undefined) {
74
+ return DEFAULT_SAMPLING_RATE;
75
+ }
76
+ if (rate < MIN_SAMPLING_RATE || rate > MAX_SAMPLING_RATE) {
77
+ debugLog.warn('ConfigBuilder', 'Invalid sampling rate, using default', {
78
+ provided: rate,
79
+ default: DEFAULT_SAMPLING_RATE,
80
+ });
81
+ return DEFAULT_SAMPLING_RATE;
82
+ }
83
+ return rate;
84
+ }
85
+ /**
86
+ * Resolves error sampling rate based on mode
87
+ * In debug/qa modes: uses provided value or defaults to full sampling (1.0)
88
+ * In production: uses provided value or defaults to 10% sampling (0.1)
89
+ */
90
+ static resolveErrorSampling(appErrorSampling, apiMode) {
91
+ const isDebugMode = apiMode === Mode.DEBUG || apiMode === Mode.QA;
92
+ if (isDebugMode) {
93
+ // In debug mode, respect explicit value or default to full sampling
94
+ return appErrorSampling ?? 1;
95
+ }
96
+ return appErrorSampling ?? 0.1; // Default to 10% sampling in production
97
+ }
98
+ /**
99
+ * Resolves mode with special project ID handling
100
+ * Priority: Special project ID > API mode > app mode
101
+ */
102
+ static resolveMode(appConfig, apiMode) {
103
+ // Force DEBUG mode for special project IDs
104
+ if (appConfig.id === SpecialProjectId.Skip ||
105
+ appConfig.id === SpecialProjectId.Fail ||
106
+ appConfig.id.toLowerCase().startsWith('skip-')) {
107
+ return Mode.DEBUG;
108
+ }
109
+ // API mode takes precedence over app mode
110
+ return apiMode ?? appConfig.mode;
111
+ }
112
+ }
@@ -2,20 +2,20 @@ import { AppConfig, Config } from '../types';
2
2
  /**
3
3
  * Configuration manager responsible for loading and merging application configuration.
4
4
  *
5
- * Handles three configuration sources:
6
- * 1. Default configuration (fallback values)
7
- * 2. API configuration (server-side settings)
8
- * 3. App configuration (client initialization settings)
5
+ * Handles configuration from two sources:
6
+ * 1. API configuration (server-side settings)
7
+ * 2. App configuration (client initialization settings)
8
+ *
9
+ * Uses ConfigBuilder for centralized merge logic.
9
10
  *
10
11
  * Supports special project IDs for development and testing:
11
12
  * - 'skip': Bypasses all network calls, uses defaults
12
- * - 'localhost:PORT': Loads config from local development server
13
+ * - 'localhost:8080': Loads config from local development server
13
14
  */
14
15
  export declare class ConfigManager {
15
- private static readonly LOCALHOST_PATTERN;
16
16
  private static readonly PRODUCTION_DOMAINS;
17
17
  /**
18
- * Gets complete configuration by merging default, API, and app configurations.
18
+ * Gets complete configuration by loading API config and building final config.
19
19
  *
20
20
  * @param apiUrl - Base URL for the configuration API
21
21
  * @param appConfig - Client-side configuration from init()
@@ -23,7 +23,8 @@ export declare class ConfigManager {
23
23
  */
24
24
  get(apiUrl: string, appConfig: AppConfig): Promise<Config>;
25
25
  /**
26
- * Loads configuration from API and merges with app config.
26
+ * Loads configuration from API and returns sanitized API config.
27
+ * Only returns values explicitly provided by the API.
27
28
  */
28
29
  private loadFromApi;
29
30
  /**
@@ -32,26 +33,24 @@ export declare class ConfigManager {
32
33
  private buildConfigUrl;
33
34
  /**
34
35
  * Builds request headers based on project configuration.
36
+ * Always includes X-TraceLog-Project header for consistent identification.
35
37
  */
36
38
  private buildHeaders;
37
39
  /**
38
40
  * Parses and validates JSON response from config API.
39
41
  */
40
42
  private parseJsonResponse;
41
- /**
42
- * Validates localhost project ID format and port range.
43
- */
44
- private validateLocalhostProjectId;
45
43
  /**
46
44
  * Checks if QA mode is enabled via URL parameter.
47
45
  */
48
46
  private isQaModeEnabled;
49
47
  /**
50
- * Merges API configuration with app configuration and applies mode-specific settings.
48
+ * Applies QA mode to API config if enabled via URL parameter.
51
49
  */
52
- private mergeConfigurations;
50
+ private applyQaModeIfEnabled;
53
51
  /**
54
52
  * Creates default configuration for skip mode and fallback scenarios.
53
+ * Only uses API defaults for fields not provided by the app config.
55
54
  */
56
55
  private createDefaultConfig;
57
56
  }