@tracelog/lib 0.5.5 → 0.6.1

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 (202) hide show
  1. package/README.md +157 -180
  2. package/dist/browser/tracelog.esm.js +1124 -1377
  3. package/dist/browser/tracelog.esm.js.map +1 -0
  4. package/dist/browser/tracelog.js +2 -2
  5. package/dist/browser/tracelog.js.map +1 -0
  6. package/dist/cjs/api.d.ts +12 -2
  7. package/dist/cjs/api.js +74 -29
  8. package/dist/cjs/app.d.ts +2 -2
  9. package/dist/cjs/app.js +26 -32
  10. package/dist/cjs/constants/config.constants.d.ts +7 -2
  11. package/dist/cjs/constants/config.constants.js +9 -18
  12. package/dist/cjs/constants/index.d.ts +0 -1
  13. package/dist/cjs/constants/index.js +0 -1
  14. package/dist/cjs/constants/storage.constants.d.ts +3 -2
  15. package/dist/cjs/constants/storage.constants.js +4 -4
  16. package/dist/cjs/handlers/click.handler.js +3 -6
  17. package/dist/cjs/handlers/error.handler.js +1 -11
  18. package/dist/cjs/handlers/page-view.handler.js +0 -4
  19. package/dist/cjs/handlers/performance.handler.js +14 -29
  20. package/dist/cjs/handlers/scroll.handler.js +7 -6
  21. package/dist/cjs/handlers/session.handler.js +7 -6
  22. package/dist/cjs/integrations/google-analytics.integration.js +2 -6
  23. package/dist/cjs/listeners/activity-listener-manager.js +3 -3
  24. package/dist/cjs/listeners/input-listener-managers.js +3 -3
  25. package/dist/cjs/listeners/touch-listener-manager.js +3 -3
  26. package/dist/cjs/listeners/unload-listener-manager.js +3 -3
  27. package/dist/cjs/listeners/visibility-listener-manager.js +3 -3
  28. package/dist/cjs/managers/event.manager.d.ts +5 -1
  29. package/dist/cjs/managers/event.manager.js +103 -40
  30. package/dist/cjs/managers/sender.manager.js +29 -36
  31. package/dist/cjs/managers/session.manager.js +5 -13
  32. package/dist/cjs/managers/state.manager.d.ts +0 -3
  33. package/dist/cjs/managers/state.manager.js +1 -43
  34. package/dist/cjs/managers/storage.manager.d.ts +21 -2
  35. package/dist/cjs/managers/storage.manager.js +164 -21
  36. package/dist/cjs/managers/user.manager.d.ts +1 -1
  37. package/dist/cjs/managers/user.manager.js +2 -2
  38. package/dist/cjs/public-api.d.ts +3 -3
  39. package/dist/cjs/public-api.js +1 -1
  40. package/dist/cjs/test-bridge.d.ts +1 -0
  41. package/dist/cjs/test-bridge.js +37 -2
  42. package/dist/cjs/types/config.types.d.ts +17 -20
  43. package/dist/cjs/types/config.types.js +6 -0
  44. package/dist/cjs/types/event.types.d.ts +1 -13
  45. package/dist/cjs/types/index.d.ts +0 -2
  46. package/dist/cjs/types/index.js +0 -2
  47. package/dist/cjs/types/mode.types.d.ts +1 -2
  48. package/dist/cjs/types/mode.types.js +0 -1
  49. package/dist/cjs/types/queue.types.d.ts +0 -6
  50. package/dist/cjs/types/state.types.d.ts +2 -0
  51. package/dist/cjs/types/test-bridge.types.d.ts +2 -2
  52. package/dist/cjs/types/validation-error.types.d.ts +0 -6
  53. package/dist/cjs/types/validation-error.types.js +1 -10
  54. package/dist/cjs/utils/browser/device-detector.utils.js +2 -24
  55. package/dist/cjs/utils/browser/index.d.ts +1 -0
  56. package/dist/cjs/utils/browser/index.js +1 -0
  57. package/dist/cjs/utils/browser/qa-mode.utils.d.ts +13 -0
  58. package/dist/cjs/utils/browser/qa-mode.utils.js +43 -0
  59. package/dist/cjs/utils/browser/utm-params.utils.js +0 -15
  60. package/dist/cjs/utils/data/uuid.utils.d.ts +13 -0
  61. package/dist/cjs/utils/data/uuid.utils.js +37 -1
  62. package/dist/cjs/utils/index.d.ts +1 -1
  63. package/dist/cjs/utils/index.js +1 -1
  64. package/dist/cjs/utils/logging.utils.d.ts +21 -0
  65. package/dist/cjs/utils/logging.utils.js +86 -0
  66. package/dist/cjs/utils/network/index.d.ts +0 -1
  67. package/dist/cjs/utils/network/index.js +0 -1
  68. package/dist/cjs/utils/network/url.utils.d.ts +2 -8
  69. package/dist/cjs/utils/network/url.utils.js +45 -90
  70. package/dist/cjs/utils/security/sanitize.utils.d.ts +1 -13
  71. package/dist/cjs/utils/security/sanitize.utils.js +15 -178
  72. package/dist/cjs/utils/validations/config-validations.utils.d.ts +3 -9
  73. package/dist/cjs/utils/validations/config-validations.utils.js +56 -93
  74. package/dist/cjs/utils/validations/event-validations.utils.js +11 -5
  75. package/dist/cjs/utils/validations/index.d.ts +0 -1
  76. package/dist/cjs/utils/validations/index.js +0 -1
  77. package/dist/cjs/utils/validations/metadata-validations.utils.js +0 -1
  78. package/dist/cjs/utils/validations/type-guards.utils.d.ts +2 -2
  79. package/dist/cjs/utils/validations/type-guards.utils.js +50 -4
  80. package/dist/esm/api.d.ts +12 -2
  81. package/dist/esm/api.js +73 -29
  82. package/dist/esm/app.d.ts +2 -2
  83. package/dist/esm/app.js +28 -34
  84. package/dist/esm/constants/config.constants.d.ts +7 -2
  85. package/dist/esm/constants/config.constants.js +7 -16
  86. package/dist/esm/constants/index.d.ts +0 -1
  87. package/dist/esm/constants/index.js +0 -1
  88. package/dist/esm/constants/storage.constants.d.ts +3 -2
  89. package/dist/esm/constants/storage.constants.js +3 -2
  90. package/dist/esm/handlers/click.handler.js +3 -6
  91. package/dist/esm/handlers/error.handler.js +1 -11
  92. package/dist/esm/handlers/page-view.handler.js +0 -4
  93. package/dist/esm/handlers/performance.handler.js +14 -29
  94. package/dist/esm/handlers/scroll.handler.js +7 -6
  95. package/dist/esm/handlers/session.handler.js +7 -6
  96. package/dist/esm/integrations/google-analytics.integration.js +3 -7
  97. package/dist/esm/listeners/activity-listener-manager.js +3 -3
  98. package/dist/esm/listeners/input-listener-managers.js +3 -3
  99. package/dist/esm/listeners/touch-listener-manager.js +3 -3
  100. package/dist/esm/listeners/unload-listener-manager.js +3 -3
  101. package/dist/esm/listeners/visibility-listener-manager.js +3 -3
  102. package/dist/esm/managers/event.manager.d.ts +5 -1
  103. package/dist/esm/managers/event.manager.js +106 -43
  104. package/dist/esm/managers/sender.manager.js +31 -38
  105. package/dist/esm/managers/session.manager.js +5 -13
  106. package/dist/esm/managers/state.manager.d.ts +0 -3
  107. package/dist/esm/managers/state.manager.js +1 -43
  108. package/dist/esm/managers/storage.manager.d.ts +21 -2
  109. package/dist/esm/managers/storage.manager.js +164 -21
  110. package/dist/esm/managers/user.manager.d.ts +1 -1
  111. package/dist/esm/managers/user.manager.js +2 -2
  112. package/dist/esm/public-api.d.ts +3 -3
  113. package/dist/esm/public-api.js +1 -1
  114. package/dist/esm/test-bridge.d.ts +1 -0
  115. package/dist/esm/test-bridge.js +37 -2
  116. package/dist/esm/types/config.types.d.ts +17 -20
  117. package/dist/esm/types/config.types.js +5 -1
  118. package/dist/esm/types/event.types.d.ts +1 -13
  119. package/dist/esm/types/index.d.ts +0 -2
  120. package/dist/esm/types/index.js +0 -2
  121. package/dist/esm/types/mode.types.d.ts +1 -2
  122. package/dist/esm/types/mode.types.js +0 -1
  123. package/dist/esm/types/queue.types.d.ts +0 -6
  124. package/dist/esm/types/state.types.d.ts +2 -0
  125. package/dist/esm/types/test-bridge.types.d.ts +2 -2
  126. package/dist/esm/types/validation-error.types.d.ts +0 -6
  127. package/dist/esm/types/validation-error.types.js +0 -8
  128. package/dist/esm/utils/browser/device-detector.utils.js +2 -24
  129. package/dist/esm/utils/browser/index.d.ts +1 -0
  130. package/dist/esm/utils/browser/index.js +1 -0
  131. package/dist/esm/utils/browser/qa-mode.utils.d.ts +13 -0
  132. package/dist/esm/utils/browser/qa-mode.utils.js +39 -0
  133. package/dist/esm/utils/browser/utm-params.utils.js +0 -15
  134. package/dist/esm/utils/data/uuid.utils.d.ts +13 -0
  135. package/dist/esm/utils/data/uuid.utils.js +35 -0
  136. package/dist/esm/utils/index.d.ts +1 -1
  137. package/dist/esm/utils/index.js +1 -1
  138. package/dist/esm/utils/logging.utils.d.ts +21 -0
  139. package/dist/esm/utils/logging.utils.js +81 -0
  140. package/dist/esm/utils/network/index.d.ts +0 -1
  141. package/dist/esm/utils/network/index.js +0 -1
  142. package/dist/esm/utils/network/url.utils.d.ts +2 -8
  143. package/dist/esm/utils/network/url.utils.js +44 -88
  144. package/dist/esm/utils/security/sanitize.utils.d.ts +1 -13
  145. package/dist/esm/utils/security/sanitize.utils.js +15 -176
  146. package/dist/esm/utils/validations/config-validations.utils.d.ts +3 -9
  147. package/dist/esm/utils/validations/config-validations.utils.js +57 -93
  148. package/dist/esm/utils/validations/event-validations.utils.js +11 -5
  149. package/dist/esm/utils/validations/index.d.ts +0 -1
  150. package/dist/esm/utils/validations/index.js +0 -1
  151. package/dist/esm/utils/validations/metadata-validations.utils.js +0 -1
  152. package/dist/esm/utils/validations/type-guards.utils.d.ts +2 -2
  153. package/dist/esm/utils/validations/type-guards.utils.js +50 -4
  154. package/package.json +3 -2
  155. package/dist/cjs/app.types.d.ts +0 -2
  156. package/dist/cjs/app.types.js +0 -12
  157. package/dist/cjs/constants/api.constants.d.ts +0 -6
  158. package/dist/cjs/constants/api.constants.js +0 -14
  159. package/dist/cjs/managers/api.manager.d.ts +0 -13
  160. package/dist/cjs/managers/api.manager.js +0 -44
  161. package/dist/cjs/managers/config.builder.d.ts +0 -33
  162. package/dist/cjs/managers/config.builder.js +0 -116
  163. package/dist/cjs/managers/config.manager.d.ts +0 -56
  164. package/dist/cjs/managers/config.manager.js +0 -157
  165. package/dist/cjs/managers/tags.manager.d.ts +0 -36
  166. package/dist/cjs/managers/tags.manager.js +0 -171
  167. package/dist/cjs/types/api.types.d.ts +0 -52
  168. package/dist/cjs/types/api.types.js +0 -56
  169. package/dist/cjs/types/tag.types.d.ts +0 -43
  170. package/dist/cjs/types/tag.types.js +0 -31
  171. package/dist/cjs/utils/logging/debug-logger.utils.d.ts +0 -14
  172. package/dist/cjs/utils/logging/debug-logger.utils.js +0 -47
  173. package/dist/cjs/utils/logging/index.d.ts +0 -1
  174. package/dist/cjs/utils/logging/index.js +0 -5
  175. package/dist/cjs/utils/network/fetch-with-timeout.utils.d.ts +0 -4
  176. package/dist/cjs/utils/network/fetch-with-timeout.utils.js +0 -25
  177. package/dist/cjs/utils/validations/url-validations.utils.d.ts +0 -15
  178. package/dist/cjs/utils/validations/url-validations.utils.js +0 -47
  179. package/dist/esm/app.types.d.ts +0 -2
  180. package/dist/esm/app.types.js +0 -1
  181. package/dist/esm/constants/api.constants.d.ts +0 -6
  182. package/dist/esm/constants/api.constants.js +0 -11
  183. package/dist/esm/managers/api.manager.d.ts +0 -13
  184. package/dist/esm/managers/api.manager.js +0 -41
  185. package/dist/esm/managers/config.builder.d.ts +0 -33
  186. package/dist/esm/managers/config.builder.js +0 -112
  187. package/dist/esm/managers/config.manager.d.ts +0 -56
  188. package/dist/esm/managers/config.manager.js +0 -153
  189. package/dist/esm/managers/tags.manager.d.ts +0 -36
  190. package/dist/esm/managers/tags.manager.js +0 -167
  191. package/dist/esm/types/api.types.d.ts +0 -52
  192. package/dist/esm/types/api.types.js +0 -53
  193. package/dist/esm/types/tag.types.d.ts +0 -43
  194. package/dist/esm/types/tag.types.js +0 -28
  195. package/dist/esm/utils/logging/debug-logger.utils.d.ts +0 -14
  196. package/dist/esm/utils/logging/debug-logger.utils.js +0 -44
  197. package/dist/esm/utils/logging/index.d.ts +0 -1
  198. package/dist/esm/utils/logging/index.js +0 -1
  199. package/dist/esm/utils/network/fetch-with-timeout.utils.d.ts +0 -4
  200. package/dist/esm/utils/network/fetch-with-timeout.utils.js +0 -22
  201. package/dist/esm/utils/validations/url-validations.utils.d.ts +0 -15
  202. package/dist/esm/utils/validations/url-validations.utils.js +0 -42
@@ -1,10 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isValidConfigApiResponse = exports.validateAndNormalizeConfig = exports.validateAppConfig = void 0;
3
+ exports.validateAndNormalizeConfig = exports.validateAppConfig = void 0;
4
4
  const constants_1 = require("../../constants");
5
- const types_1 = require("../../types");
6
5
  const validation_error_types_1 = require("../../types/validation-error.types");
7
- const logging_1 = require("../logging");
6
+ const logging_utils_1 = require("../logging.utils");
8
7
  /**
9
8
  * Validates the app configuration object (before normalization)
10
9
  * This validates the structure and basic types but allows for normalization afterward
@@ -13,42 +12,18 @@ const logging_1 = require("../logging");
13
12
  * @throws {AppConfigValidationError} If other configuration validation fails
14
13
  */
15
14
  const validateAppConfig = (config) => {
16
- // Validate config exists and has id property
17
15
  if (!config || typeof config !== 'object') {
18
- logging_1.debugLog.clientError('ConfigValidation', 'Configuration must be an object', { config });
19
16
  throw new validation_error_types_1.AppConfigValidationError('Configuration must be an object', 'config');
20
17
  }
21
- // Check if id property exists (allow falsy values to be handled by normalization)
22
- if (!('id' in config)) {
23
- logging_1.debugLog.clientError('ConfigValidation', 'Project ID is missing from configuration');
24
- throw new validation_error_types_1.ProjectIdValidationError(constants_1.VALIDATION_MESSAGES.MISSING_PROJECT_ID, 'config');
25
- }
26
- // Check basic type - null, undefined, or non-string values should fail here
27
- if (config.id === null || config.id === undefined || typeof config.id !== 'string') {
28
- logging_1.debugLog.clientError('ConfigValidation', 'Project ID must be a non-empty string', {
29
- providedId: config.id,
30
- type: typeof config.id,
31
- });
32
- throw new validation_error_types_1.ProjectIdValidationError(constants_1.VALIDATION_MESSAGES.MISSING_PROJECT_ID, 'config');
33
- }
34
18
  if (config.sessionTimeout !== undefined) {
35
19
  if (typeof config.sessionTimeout !== 'number' ||
36
20
  config.sessionTimeout < constants_1.MIN_SESSION_TIMEOUT_MS ||
37
21
  config.sessionTimeout > constants_1.MAX_SESSION_TIMEOUT_MS) {
38
- logging_1.debugLog.clientError('ConfigValidation', 'Invalid session timeout', {
39
- provided: config.sessionTimeout,
40
- min: constants_1.MIN_SESSION_TIMEOUT_MS,
41
- max: constants_1.MAX_SESSION_TIMEOUT_MS,
42
- });
43
22
  throw new validation_error_types_1.SessionTimeoutValidationError(constants_1.VALIDATION_MESSAGES.INVALID_SESSION_TIMEOUT, 'config');
44
23
  }
45
24
  }
46
25
  if (config.globalMetadata !== undefined) {
47
26
  if (typeof config.globalMetadata !== 'object' || config.globalMetadata === null) {
48
- logging_1.debugLog.clientError('ConfigValidation', 'Global metadata must be an object', {
49
- provided: config.globalMetadata,
50
- type: typeof config.globalMetadata,
51
- });
52
27
  throw new validation_error_types_1.AppConfigValidationError(constants_1.VALIDATION_MESSAGES.INVALID_GLOBAL_METADATA, 'config');
53
28
  }
54
29
  }
@@ -60,31 +35,24 @@ const validateAppConfig = (config) => {
60
35
  }
61
36
  if (config.sensitiveQueryParams !== undefined) {
62
37
  if (!Array.isArray(config.sensitiveQueryParams)) {
63
- logging_1.debugLog.clientError('ConfigValidation', 'Sensitive query params must be an array', {
64
- provided: config.sensitiveQueryParams,
65
- type: typeof config.sensitiveQueryParams,
66
- });
67
38
  throw new validation_error_types_1.AppConfigValidationError(constants_1.VALIDATION_MESSAGES.INVALID_SENSITIVE_QUERY_PARAMS, 'config');
68
39
  }
69
40
  for (const param of config.sensitiveQueryParams) {
70
41
  if (typeof param !== 'string') {
71
- logging_1.debugLog.clientError('ConfigValidation', 'All sensitive query params must be strings', {
72
- param,
73
- type: typeof param,
74
- });
75
42
  throw new validation_error_types_1.AppConfigValidationError('All sensitive query params must be strings', 'config');
76
43
  }
77
44
  }
78
45
  }
79
46
  if (config.errorSampling !== undefined) {
80
47
  if (typeof config.errorSampling !== 'number' || config.errorSampling < 0 || config.errorSampling > 1) {
81
- logging_1.debugLog.clientError('ConfigValidation', 'Invalid error sampling rate', {
82
- provided: config.errorSampling,
83
- expected: '0-1',
84
- });
85
48
  throw new validation_error_types_1.SamplingRateValidationError(constants_1.VALIDATION_MESSAGES.INVALID_ERROR_SAMPLING_RATE, 'config');
86
49
  }
87
50
  }
51
+ if (config.samplingRate !== undefined) {
52
+ if (typeof config.samplingRate !== 'number' || config.samplingRate < 0 || config.samplingRate > 1) {
53
+ throw new validation_error_types_1.SamplingRateValidationError(constants_1.VALIDATION_MESSAGES.INVALID_SAMPLING_RATE, 'config');
54
+ }
55
+ }
88
56
  };
89
57
  exports.validateAppConfig = validateAppConfig;
90
58
  /**
@@ -136,19 +104,25 @@ const validateScrollContainerSelectors = (selectors) => {
136
104
  const selectorsArray = Array.isArray(selectors) ? selectors : [selectors];
137
105
  for (const selector of selectorsArray) {
138
106
  if (typeof selector !== 'string' || selector.trim() === '') {
139
- logging_1.debugLog.clientError('ConfigValidation', 'Invalid scroll container selector', {
140
- selector,
141
- type: typeof selector,
142
- isEmpty: selector === '' || (typeof selector === 'string' && selector.trim() === ''),
107
+ (0, logging_utils_1.log)('error', 'Invalid scroll container selector', {
108
+ showToClient: true,
109
+ data: {
110
+ selector,
111
+ type: typeof selector,
112
+ isEmpty: selector === '' || (typeof selector === 'string' && selector.trim() === ''),
113
+ },
143
114
  });
144
115
  throw new validation_error_types_1.AppConfigValidationError(constants_1.VALIDATION_MESSAGES.INVALID_SCROLL_CONTAINER_SELECTORS, 'config');
145
116
  }
146
117
  // Validate CSS selector syntax using regex-based validation (XSS prevention)
147
118
  // This validates syntax WITHOUT executing document.querySelector()
148
119
  if (!isValidCssSelectorSyntax(selector)) {
149
- logging_1.debugLog.clientError('ConfigValidation', 'Invalid or potentially unsafe CSS selector', {
150
- selector,
151
- reason: 'Failed security validation',
120
+ (0, logging_utils_1.log)('error', 'Invalid or potentially unsafe CSS selector', {
121
+ showToClient: true,
122
+ data: {
123
+ selector,
124
+ reason: 'Failed security validation',
125
+ },
152
126
  });
153
127
  throw new validation_error_types_1.AppConfigValidationError('Invalid or potentially unsafe CSS selector', 'config');
154
128
  }
@@ -159,23 +133,42 @@ const validateScrollContainerSelectors = (selectors) => {
159
133
  * @param integrations - Integrations configuration to validate
160
134
  */
161
135
  const validateIntegrations = (integrations) => {
162
- if (!integrations)
136
+ if (!integrations) {
163
137
  return;
138
+ }
139
+ if (integrations.tracelog) {
140
+ if (!integrations.tracelog.projectId ||
141
+ typeof integrations.tracelog.projectId !== 'string' ||
142
+ integrations.tracelog.projectId.trim() === '') {
143
+ throw new validation_error_types_1.IntegrationValidationError(constants_1.VALIDATION_MESSAGES.INVALID_TRACELOG_PROJECT_ID, 'config');
144
+ }
145
+ }
146
+ if (integrations.custom) {
147
+ if (!integrations.custom.apiUrl ||
148
+ typeof integrations.custom.apiUrl !== 'string' ||
149
+ integrations.custom.apiUrl.trim() === '') {
150
+ throw new validation_error_types_1.IntegrationValidationError(constants_1.VALIDATION_MESSAGES.INVALID_CUSTOM_API_URL, 'config');
151
+ }
152
+ if (integrations.custom.allowHttp !== undefined && typeof integrations.custom.allowHttp !== 'boolean') {
153
+ throw new validation_error_types_1.IntegrationValidationError('allowHttp must be a boolean', 'config');
154
+ }
155
+ const apiUrl = integrations.custom.apiUrl.trim();
156
+ if (!apiUrl.startsWith('http://') && !apiUrl.startsWith('https://')) {
157
+ throw new validation_error_types_1.IntegrationValidationError('Custom API URL must start with "http://" or "https://"', 'config');
158
+ }
159
+ const allowHttp = integrations.custom.allowHttp ?? false;
160
+ if (!allowHttp && apiUrl.startsWith('http://')) {
161
+ throw new validation_error_types_1.IntegrationValidationError('Custom API URL must use HTTPS in production. Set allowHttp: true in integration config to allow HTTP (not recommended)', 'config');
162
+ }
163
+ }
164
164
  if (integrations.googleAnalytics) {
165
165
  if (!integrations.googleAnalytics.measurementId ||
166
166
  typeof integrations.googleAnalytics.measurementId !== 'string' ||
167
167
  integrations.googleAnalytics.measurementId.trim() === '') {
168
- logging_1.debugLog.clientError('ConfigValidation', 'Invalid Google Analytics measurement ID', {
169
- provided: integrations.googleAnalytics.measurementId,
170
- type: typeof integrations.googleAnalytics.measurementId,
171
- });
172
168
  throw new validation_error_types_1.IntegrationValidationError(constants_1.VALIDATION_MESSAGES.INVALID_GOOGLE_ANALYTICS_ID, 'config');
173
169
  }
174
170
  const measurementId = integrations.googleAnalytics.measurementId.trim();
175
171
  if (!measurementId.match(/^(G-|UA-)/)) {
176
- logging_1.debugLog.clientError('ConfigValidation', 'Google Analytics measurement ID must start with "G-" or "UA-"', {
177
- provided: measurementId,
178
- });
179
172
  throw new validation_error_types_1.IntegrationValidationError('Google Analytics measurement ID must start with "G-" or "UA-"', 'config');
180
173
  }
181
174
  }
@@ -189,52 +182,22 @@ const validateIntegrations = (integrations) => {
189
182
  * @throws {AppConfigValidationError} If other configuration validation fails
190
183
  */
191
184
  const validateAndNormalizeConfig = (config) => {
192
- // First validate the structure and basic types
193
185
  (0, exports.validateAppConfig)(config);
194
- // Normalize string values
195
186
  const normalizedConfig = {
196
187
  ...config,
197
- id: config.id.trim(),
188
+ sessionTimeout: config.sessionTimeout ?? constants_1.DEFAULT_SESSION_TIMEOUT,
198
189
  globalMetadata: config.globalMetadata ?? {},
199
190
  sensitiveQueryParams: config.sensitiveQueryParams ?? [],
191
+ errorSampling: config.errorSampling ?? 1,
192
+ samplingRate: config.samplingRate ?? 1,
200
193
  };
201
- // Validate normalized values - this catches whitespace-only IDs
202
- if (!normalizedConfig.id) {
203
- logging_1.debugLog.clientError('ConfigValidation', 'Project ID is empty after trimming whitespace', {
204
- originalId: config.id,
205
- normalizedId: normalizedConfig.id,
206
- });
207
- throw new validation_error_types_1.ProjectIdValidationError(constants_1.VALIDATION_MESSAGES.PROJECT_ID_EMPTY_AFTER_TRIM, 'config');
194
+ // Normalize integrations
195
+ if (normalizedConfig.integrations?.custom) {
196
+ normalizedConfig.integrations.custom = {
197
+ ...normalizedConfig.integrations.custom,
198
+ allowHttp: normalizedConfig.integrations.custom.allowHttp ?? false,
199
+ };
208
200
  }
209
201
  return normalizedConfig;
210
202
  };
211
203
  exports.validateAndNormalizeConfig = validateAndNormalizeConfig;
212
- /**
213
- * Type guard to check if a JSON response is a valid API config
214
- * @param json - The JSON to validate
215
- * @returns True if the JSON is a valid API config
216
- */
217
- const isValidConfigApiResponse = (json) => {
218
- try {
219
- if (typeof json !== 'object' || !json) {
220
- return false;
221
- }
222
- const response = json;
223
- const result = {
224
- mode: response['mode'] === undefined || [types_1.Mode.QA, types_1.Mode.DEBUG].includes(response['mode']),
225
- // Zero is valid for samplingRate (means "sample nothing")
226
- samplingRate: response['samplingRate'] === undefined ||
227
- (typeof response['samplingRate'] === 'number' &&
228
- response['samplingRate'] >= 0 &&
229
- response['samplingRate'] <= 1),
230
- tags: response['tags'] === undefined || Array.isArray(response['tags']),
231
- excludedUrlPaths: response['excludedUrlPaths'] === undefined || Array.isArray(response['excludedUrlPaths']),
232
- ipExcluded: response['ipExcluded'] === undefined || typeof response['ipExcluded'] === 'boolean',
233
- };
234
- return Object.values(result).every(Boolean);
235
- }
236
- catch {
237
- return false;
238
- }
239
- };
240
- exports.isValidConfigApiResponse = isValidConfigApiResponse;
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isEventValid = void 0;
4
+ const logging_utils_1 = require("../logging.utils");
4
5
  const metadata_validations_utils_1 = require("./metadata-validations.utils");
5
- const logging_1 = require("../logging");
6
6
  /**
7
7
  * Validates a complete event with name and optional metadata
8
8
  * @param eventName - The event name to validate
@@ -12,7 +12,10 @@ const logging_1 = require("../logging");
12
12
  const isEventValid = (eventName, metadata) => {
13
13
  const nameValidation = (0, metadata_validations_utils_1.isValidEventName)(eventName);
14
14
  if (!nameValidation.valid) {
15
- logging_1.debugLog.clientError('EventValidation', 'Event name validation failed', { eventName, error: nameValidation.error });
15
+ (0, logging_utils_1.log)('error', 'Event name validation failed', {
16
+ showToClient: true,
17
+ data: { eventName, error: nameValidation.error },
18
+ });
16
19
  return nameValidation;
17
20
  }
18
21
  if (!metadata) {
@@ -20,9 +23,12 @@ const isEventValid = (eventName, metadata) => {
20
23
  }
21
24
  const metadataValidation = (0, metadata_validations_utils_1.isValidMetadata)(eventName, metadata, 'customEvent');
22
25
  if (!metadataValidation.valid) {
23
- logging_1.debugLog.clientError('EventValidation', 'Event metadata validation failed', {
24
- eventName,
25
- error: metadataValidation.error,
26
+ (0, logging_utils_1.log)('error', 'Event metadata validation failed', {
27
+ showToClient: true,
28
+ data: {
29
+ eventName,
30
+ error: metadataValidation.error,
31
+ },
26
32
  });
27
33
  }
28
34
  return metadataValidation;
@@ -2,4 +2,3 @@ export * from './config-validations.utils';
2
2
  export * from './event-validations.utils';
3
3
  export * from './metadata-validations.utils';
4
4
  export * from './type-guards.utils';
5
- export * from './url-validations.utils';
@@ -18,4 +18,3 @@ __exportStar(require("./config-validations.utils"), exports);
18
18
  __exportStar(require("./event-validations.utils"), exports);
19
19
  __exportStar(require("./metadata-validations.utils"), exports);
20
20
  __exportStar(require("./type-guards.utils"), exports);
21
- __exportStar(require("./url-validations.utils"), exports);
@@ -142,7 +142,6 @@ const isValidMetadata = (eventName, metadata, type) => {
142
142
  sanitizedArray.push(itemValidation.sanitizedMetadata);
143
143
  }
144
144
  }
145
- // Allow empty arrays after sanitization
146
145
  return {
147
146
  valid: true,
148
147
  sanitizedMetadata: sanitizedArray,
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Checks if an object contains only primitive fields (string, number, boolean, or string arrays)
2
+ * Checks if an object contains only primitive fields, string arrays, or arrays of flat objects
3
3
  * @param object - The object to check
4
- * @returns True if the object contains only primitive fields
4
+ * @returns True if the object contains only valid fields
5
5
  */
6
6
  export declare const isOnlyPrimitiveFields: (object: Record<string, unknown>) => boolean;
@@ -1,10 +1,41 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isOnlyPrimitiveFields = void 0;
4
+ const constants_1 = require("../../constants");
4
5
  /**
5
- * Checks if an object contains only primitive fields (string, number, boolean, or string arrays)
6
+ * Validates if an item in an array is a valid nested object
7
+ * @param item - The item to validate
8
+ * @returns True if the item is a valid nested object
9
+ */
10
+ const isValidArrayItem = (item) => {
11
+ if (typeof item === 'string') {
12
+ return true;
13
+ }
14
+ // Allow objects with primitive fields only (one level deep)
15
+ if (typeof item === 'object' && item !== null && !Array.isArray(item)) {
16
+ const entries = Object.entries(item);
17
+ // Check key count limit
18
+ if (entries.length > constants_1.MAX_NESTED_OBJECT_KEYS) {
19
+ return false;
20
+ }
21
+ // All values must be primitives (no nested objects or arrays)
22
+ for (const [, value] of entries) {
23
+ if (value === null || value === undefined) {
24
+ continue;
25
+ }
26
+ const type = typeof value;
27
+ if (type !== 'string' && type !== 'number' && type !== 'boolean') {
28
+ return false;
29
+ }
30
+ }
31
+ return true;
32
+ }
33
+ return false;
34
+ };
35
+ /**
36
+ * Checks if an object contains only primitive fields, string arrays, or arrays of flat objects
6
37
  * @param object - The object to check
7
- * @returns True if the object contains only primitive fields
38
+ * @returns True if the object contains only valid fields
8
39
  */
9
40
  const isOnlyPrimitiveFields = (object) => {
10
41
  if (typeof object !== 'object' || object === null) {
@@ -19,8 +50,23 @@ const isOnlyPrimitiveFields = (object) => {
19
50
  continue;
20
51
  }
21
52
  if (Array.isArray(value)) {
22
- if (!value.every((item) => typeof item === 'string')) {
23
- return false;
53
+ if (value.length === 0) {
54
+ continue;
55
+ }
56
+ // Determine array type from first item
57
+ const firstItem = value[0];
58
+ const isStringArray = typeof firstItem === 'string';
59
+ // All items must be of the same type (all strings OR all objects)
60
+ if (isStringArray) {
61
+ if (!value.every((item) => typeof item === 'string')) {
62
+ return false;
63
+ }
64
+ }
65
+ else {
66
+ // Must be all objects
67
+ if (!value.every((item) => isValidArrayItem(item))) {
68
+ return false;
69
+ }
24
70
  }
25
71
  continue;
26
72
  }
package/dist/esm/api.d.ts CHANGED
@@ -1,8 +1,18 @@
1
- import { MetadataType, AppConfig, EmitterCallback, EmitterMap } from './types';
1
+ import { App } from './app';
2
+ import { MetadataType, Config, EmitterCallback, EmitterMap } from './types';
2
3
  import './types/window.types';
3
- export declare const init: (appConfig: AppConfig) => Promise<void>;
4
+ export declare const init: (config: Config) => Promise<void>;
4
5
  export declare const event: (name: string, metadata?: Record<string, MetadataType> | Record<string, MetadataType>[]) => void;
5
6
  export declare const on: <K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>) => void;
6
7
  export declare const off: <K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>) => void;
7
8
  export declare const isInitialized: () => boolean;
8
9
  export declare const destroy: () => Promise<void>;
10
+ /**
11
+ * Internal sync function - ONLY for TestBridge in development
12
+ *
13
+ * WARNING: This function is internal and should NEVER be called directly.
14
+ * It's only exported for TestBridge synchronization in dev mode.
15
+ *
16
+ * @internal
17
+ */
18
+ export declare const __setAppInstance: (instance: App | null) => void;
package/dist/esm/api.js CHANGED
@@ -1,48 +1,58 @@
1
1
  import { App } from './app';
2
- import { debugLog, validateAndNormalizeConfig } from './utils';
2
+ import { log, validateAndNormalizeConfig } from './utils';
3
3
  import { TestBridge } from './test-bridge';
4
+ import { INITIALIZATION_TIMEOUT_MS } from './constants';
4
5
  import './types/window.types';
6
+ // Buffer for listeners registered before init()
7
+ const pendingListeners = [];
5
8
  let app = null;
6
9
  let isInitializing = false;
7
10
  let isDestroying = false;
8
- export const init = async (appConfig) => {
11
+ export const init = async (config) => {
9
12
  if (typeof window === 'undefined' || typeof document === 'undefined') {
10
- throw new Error('This library can only be used in a browser environment');
13
+ throw new Error('[TraceLog] This library can only be used in a browser environment');
11
14
  }
12
15
  if (window.__traceLogDisabled) {
13
16
  return;
14
17
  }
15
18
  if (app) {
16
- debugLog.debug('API', 'Library already initialized, skipping duplicate initialization');
17
19
  return;
18
20
  }
19
21
  if (isInitializing) {
20
- debugLog.warn('API', 'Initialization already in progress');
21
- throw new Error('Initialization already in progress');
22
+ return;
22
23
  }
23
24
  isInitializing = true;
24
25
  try {
25
- debugLog.info('API', 'Initializing TraceLog', { projectId: appConfig.id });
26
- const validatedConfig = validateAndNormalizeConfig(appConfig);
26
+ const validatedConfig = validateAndNormalizeConfig(config);
27
27
  const instance = new App();
28
28
  try {
29
- await instance.init(validatedConfig);
29
+ // Attach buffered listeners BEFORE init() so they capture initial events
30
+ pendingListeners.forEach(({ event, callback }) => {
31
+ instance.on(event, callback);
32
+ });
33
+ pendingListeners.length = 0;
34
+ // Wrap initialization with timeout using Promise.race
35
+ const initPromise = instance.init(validatedConfig);
36
+ const timeoutPromise = new Promise((_, reject) => {
37
+ setTimeout(() => {
38
+ reject(new Error(`[TraceLog] Initialization timeout after ${INITIALIZATION_TIMEOUT_MS}ms`));
39
+ }, INITIALIZATION_TIMEOUT_MS);
40
+ });
41
+ await Promise.race([initPromise, timeoutPromise]);
30
42
  app = instance;
31
- debugLog.info('API', 'TraceLog initialized successfully', { projectId: validatedConfig.id });
32
43
  }
33
44
  catch (error) {
34
45
  try {
35
46
  await instance.destroy(true);
36
47
  }
37
48
  catch (cleanupError) {
38
- debugLog.warn('API', 'Failed to cleanup partially initialized app', { cleanupError });
49
+ log('error', 'Failed to cleanup partially initialized app', { error: cleanupError });
39
50
  }
40
51
  throw error;
41
52
  }
42
53
  }
43
54
  catch (error) {
44
55
  app = null;
45
- debugLog.error('API', 'Initialization failed', { error });
46
56
  throw error;
47
57
  }
48
58
  finally {
@@ -51,25 +61,29 @@ export const init = async (appConfig) => {
51
61
  };
52
62
  export const event = (name, metadata) => {
53
63
  if (!app) {
54
- throw new Error('TraceLog not initialized. Please call init() first.');
55
- }
56
- try {
57
- app.sendCustomEvent(name, metadata);
64
+ throw new Error('[TraceLog] TraceLog not initialized. Please call init() first.');
58
65
  }
59
- catch (error) {
60
- debugLog.error('API', 'Failed to send custom event', { eventName: name, error });
61
- throw error;
66
+ if (isDestroying) {
67
+ throw new Error('[TraceLog] Cannot send events while TraceLog is being destroyed');
62
68
  }
69
+ app.sendCustomEvent(name, metadata);
63
70
  };
64
71
  export const on = (event, callback) => {
65
- if (!app) {
66
- throw new Error('TraceLog not initialized. Please call init() first.');
72
+ if (!app || isInitializing) {
73
+ // Buffer listeners registered before or during init()
74
+ pendingListeners.push({ event, callback });
75
+ return;
67
76
  }
68
77
  app.on(event, callback);
69
78
  };
70
79
  export const off = (event, callback) => {
71
80
  if (!app) {
72
- throw new Error('TraceLog not initialized. Please call init() first.');
81
+ // Remove from pending listeners if not yet initialized
82
+ const index = pendingListeners.findIndex((l) => l.event === event && l.callback === callback);
83
+ if (index !== -1) {
84
+ pendingListeners.splice(index, 1);
85
+ }
86
+ return;
73
87
  }
74
88
  app.off(event, callback);
75
89
  };
@@ -78,25 +92,30 @@ export const isInitialized = () => {
78
92
  };
79
93
  export const destroy = async () => {
80
94
  if (!app) {
81
- throw new Error('App not initialized');
95
+ throw new Error('[TraceLog] App not initialized');
82
96
  }
83
97
  if (isDestroying) {
84
- throw new Error('Destroy operation already in progress');
98
+ throw new Error('[TraceLog] Destroy operation already in progress');
85
99
  }
86
100
  isDestroying = true;
87
101
  try {
88
- debugLog.info('API', 'Destroying TraceLog instance');
89
102
  await app.destroy();
90
103
  app = null;
91
104
  isInitializing = false;
92
- debugLog.info('API', 'TraceLog destroyed successfully');
105
+ pendingListeners.length = 0;
106
+ // Clear TestBridge reference in dev mode to prevent stale references
107
+ if (process.env.NODE_ENV === 'dev' && typeof window !== 'undefined' && window.__traceLogBridge) {
108
+ // Don't call destroy on bridge (would cause recursion), just clear reference
109
+ window.__traceLogBridge = undefined;
110
+ }
93
111
  }
94
112
  catch (error) {
95
- // Force cleanup even if destroy fails
96
113
  app = null;
97
114
  isInitializing = false;
98
- debugLog.error('API', 'Error during destroy, forced cleanup', { error });
99
- throw error;
115
+ pendingListeners.length = 0;
116
+ // Log error but don't re-throw - destroy should always complete successfully
117
+ // Applications should be able to tear down TraceLog even if internal cleanup fails
118
+ log('warn', 'Error during destroy, forced cleanup completed', { error });
100
119
  }
101
120
  finally {
102
121
  isDestroying = false;
@@ -113,3 +132,28 @@ if (process.env.NODE_ENV === 'dev' && typeof window !== 'undefined') {
113
132
  injectTestingBridge();
114
133
  }
115
134
  }
135
+ /**
136
+ * Internal sync function - ONLY for TestBridge in development
137
+ *
138
+ * WARNING: This function is internal and should NEVER be called directly.
139
+ * It's only exported for TestBridge synchronization in dev mode.
140
+ *
141
+ * @internal
142
+ */
143
+ export const __setAppInstance = (instance) => {
144
+ if (instance !== null) {
145
+ const hasRequiredMethods = typeof instance === 'object' &&
146
+ 'init' in instance &&
147
+ 'destroy' in instance &&
148
+ 'on' in instance &&
149
+ 'off' in instance;
150
+ if (!hasRequiredMethods) {
151
+ throw new Error('[TraceLog] Invalid app instance type');
152
+ }
153
+ }
154
+ // Prevent overwriting an already initialized app (except when clearing)
155
+ if (app !== null && instance !== null && app !== instance) {
156
+ throw new Error('[TraceLog] Cannot overwrite existing app instance. Call destroy() first.');
157
+ }
158
+ app = instance;
159
+ };
package/dist/esm/app.d.ts CHANGED
@@ -4,7 +4,7 @@ import { SessionHandler } from './handlers/session.handler';
4
4
  import { PageViewHandler } from './handlers/page-view.handler';
5
5
  import { ClickHandler } from './handlers/click.handler';
6
6
  import { ScrollHandler } from './handlers/scroll.handler';
7
- import { AppConfig, EmitterCallback, EmitterMap } from './types';
7
+ import { Config, EmitterCallback, EmitterMap } from './types';
8
8
  import { GoogleAnalyticsIntegration } from './integrations/google-analytics.integration';
9
9
  import { StorageManager } from './managers/storage.manager';
10
10
  import { PerformanceHandler } from './handlers/performance.handler';
@@ -29,7 +29,7 @@ export declare class App extends StateManager {
29
29
  googleAnalytics?: GoogleAnalyticsIntegration;
30
30
  };
31
31
  get initialized(): boolean;
32
- init(appConfig: AppConfig): Promise<void>;
32
+ init(config: Config): Promise<void>;
33
33
  sendCustomEvent(name: string, metadata?: Record<string, unknown> | Record<string, unknown>[]): void;
34
34
  on<K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>): void;
35
35
  off<K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>): void;