@tracelog/lib 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +157 -180
  2. package/dist/browser/tracelog.esm.js +1007 -1357
  3. package/dist/browser/tracelog.js +2 -2
  4. package/dist/cjs/api.d.ts +12 -2
  5. package/dist/cjs/api.js +63 -27
  6. package/dist/cjs/app.d.ts +2 -2
  7. package/dist/cjs/app.js +26 -32
  8. package/dist/cjs/constants/config.constants.d.ts +4 -2
  9. package/dist/cjs/constants/config.constants.js +6 -18
  10. package/dist/cjs/constants/index.d.ts +0 -1
  11. package/dist/cjs/constants/index.js +0 -1
  12. package/dist/cjs/constants/storage.constants.d.ts +3 -2
  13. package/dist/cjs/constants/storage.constants.js +4 -4
  14. package/dist/cjs/handlers/click.handler.js +3 -6
  15. package/dist/cjs/handlers/error.handler.js +1 -11
  16. package/dist/cjs/handlers/page-view.handler.js +0 -4
  17. package/dist/cjs/handlers/performance.handler.js +14 -29
  18. package/dist/cjs/handlers/scroll.handler.js +7 -6
  19. package/dist/cjs/handlers/session.handler.js +7 -6
  20. package/dist/cjs/integrations/google-analytics.integration.js +2 -6
  21. package/dist/cjs/listeners/activity-listener-manager.js +3 -3
  22. package/dist/cjs/listeners/input-listener-managers.js +3 -3
  23. package/dist/cjs/listeners/touch-listener-manager.js +3 -3
  24. package/dist/cjs/listeners/unload-listener-manager.js +3 -3
  25. package/dist/cjs/listeners/visibility-listener-manager.js +3 -3
  26. package/dist/cjs/managers/event.manager.d.ts +2 -1
  27. package/dist/cjs/managers/event.manager.js +60 -38
  28. package/dist/cjs/managers/sender.manager.js +29 -36
  29. package/dist/cjs/managers/session.manager.js +5 -13
  30. package/dist/cjs/managers/state.manager.d.ts +0 -3
  31. package/dist/cjs/managers/state.manager.js +1 -43
  32. package/dist/cjs/managers/storage.manager.d.ts +16 -2
  33. package/dist/cjs/managers/storage.manager.js +73 -19
  34. package/dist/cjs/managers/user.manager.d.ts +1 -1
  35. package/dist/cjs/managers/user.manager.js +2 -2
  36. package/dist/cjs/public-api.d.ts +3 -3
  37. package/dist/cjs/public-api.js +1 -1
  38. package/dist/cjs/test-bridge.d.ts +1 -0
  39. package/dist/cjs/test-bridge.js +37 -2
  40. package/dist/cjs/types/config.types.d.ts +15 -18
  41. package/dist/cjs/types/config.types.js +6 -0
  42. package/dist/cjs/types/event.types.d.ts +1 -13
  43. package/dist/cjs/types/index.d.ts +0 -2
  44. package/dist/cjs/types/index.js +0 -2
  45. package/dist/cjs/types/mode.types.d.ts +1 -2
  46. package/dist/cjs/types/mode.types.js +0 -1
  47. package/dist/cjs/types/queue.types.d.ts +0 -6
  48. package/dist/cjs/types/state.types.d.ts +2 -0
  49. package/dist/cjs/types/test-bridge.types.d.ts +2 -2
  50. package/dist/cjs/types/validation-error.types.d.ts +0 -6
  51. package/dist/cjs/types/validation-error.types.js +1 -10
  52. package/dist/cjs/utils/browser/device-detector.utils.js +2 -24
  53. package/dist/cjs/utils/browser/index.d.ts +1 -0
  54. package/dist/cjs/utils/browser/index.js +1 -0
  55. package/dist/cjs/utils/browser/qa-mode.utils.d.ts +13 -0
  56. package/dist/cjs/utils/browser/qa-mode.utils.js +43 -0
  57. package/dist/cjs/utils/browser/utm-params.utils.js +0 -15
  58. package/dist/cjs/utils/data/uuid.utils.d.ts +13 -0
  59. package/dist/cjs/utils/data/uuid.utils.js +37 -1
  60. package/dist/cjs/utils/index.d.ts +1 -1
  61. package/dist/cjs/utils/index.js +1 -1
  62. package/dist/cjs/utils/logging.utils.d.ts +6 -0
  63. package/dist/cjs/utils/logging.utils.js +25 -0
  64. package/dist/cjs/utils/network/index.d.ts +0 -1
  65. package/dist/cjs/utils/network/index.js +0 -1
  66. package/dist/cjs/utils/network/url.utils.d.ts +2 -8
  67. package/dist/cjs/utils/network/url.utils.js +46 -90
  68. package/dist/cjs/utils/security/sanitize.utils.d.ts +1 -13
  69. package/dist/cjs/utils/security/sanitize.utils.js +15 -178
  70. package/dist/cjs/utils/validations/config-validations.utils.d.ts +3 -9
  71. package/dist/cjs/utils/validations/config-validations.utils.js +48 -94
  72. package/dist/cjs/utils/validations/event-validations.utils.js +11 -5
  73. package/dist/cjs/utils/validations/index.d.ts +0 -1
  74. package/dist/cjs/utils/validations/index.js +0 -1
  75. package/dist/cjs/utils/validations/metadata-validations.utils.js +0 -1
  76. package/dist/cjs/utils/validations/type-guards.utils.d.ts +2 -2
  77. package/dist/cjs/utils/validations/type-guards.utils.js +50 -4
  78. package/dist/esm/api.d.ts +12 -2
  79. package/dist/esm/api.js +62 -27
  80. package/dist/esm/app.d.ts +2 -2
  81. package/dist/esm/app.js +28 -34
  82. package/dist/esm/constants/config.constants.d.ts +4 -2
  83. package/dist/esm/constants/config.constants.js +4 -16
  84. package/dist/esm/constants/index.d.ts +0 -1
  85. package/dist/esm/constants/index.js +0 -1
  86. package/dist/esm/constants/storage.constants.d.ts +3 -2
  87. package/dist/esm/constants/storage.constants.js +3 -2
  88. package/dist/esm/handlers/click.handler.js +3 -6
  89. package/dist/esm/handlers/error.handler.js +1 -11
  90. package/dist/esm/handlers/page-view.handler.js +0 -4
  91. package/dist/esm/handlers/performance.handler.js +14 -29
  92. package/dist/esm/handlers/scroll.handler.js +7 -6
  93. package/dist/esm/handlers/session.handler.js +7 -6
  94. package/dist/esm/integrations/google-analytics.integration.js +3 -7
  95. package/dist/esm/listeners/activity-listener-manager.js +3 -3
  96. package/dist/esm/listeners/input-listener-managers.js +3 -3
  97. package/dist/esm/listeners/touch-listener-manager.js +3 -3
  98. package/dist/esm/listeners/unload-listener-manager.js +3 -3
  99. package/dist/esm/listeners/visibility-listener-manager.js +3 -3
  100. package/dist/esm/managers/event.manager.d.ts +2 -1
  101. package/dist/esm/managers/event.manager.js +62 -40
  102. package/dist/esm/managers/sender.manager.js +31 -38
  103. package/dist/esm/managers/session.manager.js +5 -13
  104. package/dist/esm/managers/state.manager.d.ts +0 -3
  105. package/dist/esm/managers/state.manager.js +1 -43
  106. package/dist/esm/managers/storage.manager.d.ts +16 -2
  107. package/dist/esm/managers/storage.manager.js +73 -19
  108. package/dist/esm/managers/user.manager.d.ts +1 -1
  109. package/dist/esm/managers/user.manager.js +2 -2
  110. package/dist/esm/public-api.d.ts +3 -3
  111. package/dist/esm/public-api.js +1 -1
  112. package/dist/esm/test-bridge.d.ts +1 -0
  113. package/dist/esm/test-bridge.js +37 -2
  114. package/dist/esm/types/config.types.d.ts +15 -18
  115. package/dist/esm/types/config.types.js +5 -1
  116. package/dist/esm/types/event.types.d.ts +1 -13
  117. package/dist/esm/types/index.d.ts +0 -2
  118. package/dist/esm/types/index.js +0 -2
  119. package/dist/esm/types/mode.types.d.ts +1 -2
  120. package/dist/esm/types/mode.types.js +0 -1
  121. package/dist/esm/types/queue.types.d.ts +0 -6
  122. package/dist/esm/types/state.types.d.ts +2 -0
  123. package/dist/esm/types/test-bridge.types.d.ts +2 -2
  124. package/dist/esm/types/validation-error.types.d.ts +0 -6
  125. package/dist/esm/types/validation-error.types.js +0 -8
  126. package/dist/esm/utils/browser/device-detector.utils.js +2 -24
  127. package/dist/esm/utils/browser/index.d.ts +1 -0
  128. package/dist/esm/utils/browser/index.js +1 -0
  129. package/dist/esm/utils/browser/qa-mode.utils.d.ts +13 -0
  130. package/dist/esm/utils/browser/qa-mode.utils.js +39 -0
  131. package/dist/esm/utils/browser/utm-params.utils.js +0 -15
  132. package/dist/esm/utils/data/uuid.utils.d.ts +13 -0
  133. package/dist/esm/utils/data/uuid.utils.js +35 -0
  134. package/dist/esm/utils/index.d.ts +1 -1
  135. package/dist/esm/utils/index.js +1 -1
  136. package/dist/esm/utils/logging.utils.d.ts +6 -0
  137. package/dist/esm/utils/logging.utils.js +20 -0
  138. package/dist/esm/utils/network/index.d.ts +0 -1
  139. package/dist/esm/utils/network/index.js +0 -1
  140. package/dist/esm/utils/network/url.utils.d.ts +2 -8
  141. package/dist/esm/utils/network/url.utils.js +45 -88
  142. package/dist/esm/utils/security/sanitize.utils.d.ts +1 -13
  143. package/dist/esm/utils/security/sanitize.utils.js +15 -176
  144. package/dist/esm/utils/validations/config-validations.utils.d.ts +3 -9
  145. package/dist/esm/utils/validations/config-validations.utils.js +49 -94
  146. package/dist/esm/utils/validations/event-validations.utils.js +11 -5
  147. package/dist/esm/utils/validations/index.d.ts +0 -1
  148. package/dist/esm/utils/validations/index.js +0 -1
  149. package/dist/esm/utils/validations/metadata-validations.utils.js +0 -1
  150. package/dist/esm/utils/validations/type-guards.utils.d.ts +2 -2
  151. package/dist/esm/utils/validations/type-guards.utils.js +50 -4
  152. package/package.json +1 -1
  153. package/dist/cjs/app.types.d.ts +0 -2
  154. package/dist/cjs/app.types.js +0 -12
  155. package/dist/cjs/constants/api.constants.d.ts +0 -6
  156. package/dist/cjs/constants/api.constants.js +0 -14
  157. package/dist/cjs/managers/api.manager.d.ts +0 -13
  158. package/dist/cjs/managers/api.manager.js +0 -44
  159. package/dist/cjs/managers/config.builder.d.ts +0 -33
  160. package/dist/cjs/managers/config.builder.js +0 -116
  161. package/dist/cjs/managers/config.manager.d.ts +0 -56
  162. package/dist/cjs/managers/config.manager.js +0 -157
  163. package/dist/cjs/managers/tags.manager.d.ts +0 -36
  164. package/dist/cjs/managers/tags.manager.js +0 -171
  165. package/dist/cjs/types/api.types.d.ts +0 -52
  166. package/dist/cjs/types/api.types.js +0 -56
  167. package/dist/cjs/types/tag.types.d.ts +0 -43
  168. package/dist/cjs/types/tag.types.js +0 -31
  169. package/dist/cjs/utils/logging/debug-logger.utils.d.ts +0 -14
  170. package/dist/cjs/utils/logging/debug-logger.utils.js +0 -47
  171. package/dist/cjs/utils/logging/index.d.ts +0 -1
  172. package/dist/cjs/utils/logging/index.js +0 -5
  173. package/dist/cjs/utils/network/fetch-with-timeout.utils.d.ts +0 -4
  174. package/dist/cjs/utils/network/fetch-with-timeout.utils.js +0 -25
  175. package/dist/cjs/utils/validations/url-validations.utils.d.ts +0 -15
  176. package/dist/cjs/utils/validations/url-validations.utils.js +0 -47
  177. package/dist/esm/app.types.d.ts +0 -2
  178. package/dist/esm/app.types.js +0 -1
  179. package/dist/esm/constants/api.constants.d.ts +0 -6
  180. package/dist/esm/constants/api.constants.js +0 -11
  181. package/dist/esm/managers/api.manager.d.ts +0 -13
  182. package/dist/esm/managers/api.manager.js +0 -41
  183. package/dist/esm/managers/config.builder.d.ts +0 -33
  184. package/dist/esm/managers/config.builder.js +0 -112
  185. package/dist/esm/managers/config.manager.d.ts +0 -56
  186. package/dist/esm/managers/config.manager.js +0 -153
  187. package/dist/esm/managers/tags.manager.d.ts +0 -36
  188. package/dist/esm/managers/tags.manager.js +0 -167
  189. package/dist/esm/types/api.types.d.ts +0 -52
  190. package/dist/esm/types/api.types.js +0 -53
  191. package/dist/esm/types/tag.types.d.ts +0 -43
  192. package/dist/esm/types/tag.types.js +0 -28
  193. package/dist/esm/utils/logging/debug-logger.utils.d.ts +0 -14
  194. package/dist/esm/utils/logging/debug-logger.utils.js +0 -44
  195. package/dist/esm/utils/logging/index.d.ts +0 -1
  196. package/dist/esm/utils/logging/index.js +0 -1
  197. package/dist/esm/utils/network/fetch-with-timeout.utils.d.ts +0 -4
  198. package/dist/esm/utils/network/fetch-with-timeout.utils.js +0 -22
  199. package/dist/esm/utils/validations/url-validations.utils.d.ts +0 -15
  200. package/dist/esm/utils/validations/url-validations.utils.js +0 -42
@@ -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,50 @@
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
4
  import './types/window.types';
5
+ // Buffer for listeners registered before init()
6
+ const pendingListeners = [];
5
7
  let app = null;
6
8
  let isInitializing = false;
7
9
  let isDestroying = false;
8
- export const init = async (appConfig) => {
10
+ export const init = async (config) => {
9
11
  if (typeof window === 'undefined' || typeof document === 'undefined') {
10
- throw new Error('This library can only be used in a browser environment');
12
+ throw new Error('[TraceLog] This library can only be used in a browser environment');
11
13
  }
12
14
  if (window.__traceLogDisabled) {
13
15
  return;
14
16
  }
15
17
  if (app) {
16
- debugLog.debug('API', 'Library already initialized, skipping duplicate initialization');
17
18
  return;
18
19
  }
19
20
  if (isInitializing) {
20
- debugLog.warn('API', 'Initialization already in progress');
21
- throw new Error('Initialization already in progress');
21
+ return;
22
22
  }
23
23
  isInitializing = true;
24
24
  try {
25
- debugLog.info('API', 'Initializing TraceLog', { projectId: appConfig.id });
26
- const validatedConfig = validateAndNormalizeConfig(appConfig);
25
+ const validatedConfig = validateAndNormalizeConfig(config);
27
26
  const instance = new App();
28
27
  try {
28
+ // Attach buffered listeners BEFORE init() so they capture initial events
29
+ pendingListeners.forEach(({ event, callback }) => {
30
+ instance.on(event, callback);
31
+ });
32
+ pendingListeners.length = 0;
29
33
  await instance.init(validatedConfig);
30
34
  app = instance;
31
- debugLog.info('API', 'TraceLog initialized successfully', { projectId: validatedConfig.id });
32
35
  }
33
36
  catch (error) {
34
37
  try {
35
38
  await instance.destroy(true);
36
39
  }
37
40
  catch (cleanupError) {
38
- debugLog.warn('API', 'Failed to cleanup partially initialized app', { cleanupError });
41
+ log('error', 'Failed to cleanup partially initialized app', { error: cleanupError });
39
42
  }
40
43
  throw error;
41
44
  }
42
45
  }
43
46
  catch (error) {
44
47
  app = null;
45
- debugLog.error('API', 'Initialization failed', { error });
46
48
  throw error;
47
49
  }
48
50
  finally {
@@ -51,25 +53,29 @@ export const init = async (appConfig) => {
51
53
  };
52
54
  export const event = (name, metadata) => {
53
55
  if (!app) {
54
- throw new Error('TraceLog not initialized. Please call init() first.');
55
- }
56
- try {
57
- app.sendCustomEvent(name, metadata);
56
+ throw new Error('[TraceLog] TraceLog not initialized. Please call init() first.');
58
57
  }
59
- catch (error) {
60
- debugLog.error('API', 'Failed to send custom event', { eventName: name, error });
61
- throw error;
58
+ if (isDestroying) {
59
+ throw new Error('[TraceLog] Cannot send events while TraceLog is being destroyed');
62
60
  }
61
+ app.sendCustomEvent(name, metadata);
63
62
  };
64
63
  export const on = (event, callback) => {
65
- if (!app) {
66
- throw new Error('TraceLog not initialized. Please call init() first.');
64
+ if (!app || isInitializing) {
65
+ // Buffer listeners registered before or during init()
66
+ pendingListeners.push({ event, callback });
67
+ return;
67
68
  }
68
69
  app.on(event, callback);
69
70
  };
70
71
  export const off = (event, callback) => {
71
72
  if (!app) {
72
- throw new Error('TraceLog not initialized. Please call init() first.');
73
+ // Remove from pending listeners if not yet initialized
74
+ const index = pendingListeners.findIndex((l) => l.event === event && l.callback === callback);
75
+ if (index !== -1) {
76
+ pendingListeners.splice(index, 1);
77
+ }
78
+ return;
73
79
  }
74
80
  app.off(event, callback);
75
81
  };
@@ -78,24 +84,28 @@ export const isInitialized = () => {
78
84
  };
79
85
  export const destroy = async () => {
80
86
  if (!app) {
81
- throw new Error('App not initialized');
87
+ throw new Error('[TraceLog] App not initialized');
82
88
  }
83
89
  if (isDestroying) {
84
- throw new Error('Destroy operation already in progress');
90
+ throw new Error('[TraceLog] Destroy operation already in progress');
85
91
  }
86
92
  isDestroying = true;
87
93
  try {
88
- debugLog.info('API', 'Destroying TraceLog instance');
89
94
  await app.destroy();
90
95
  app = null;
91
96
  isInitializing = false;
92
- debugLog.info('API', 'TraceLog destroyed successfully');
97
+ pendingListeners.length = 0;
98
+ // Clear TestBridge reference in dev mode to prevent stale references
99
+ if (process.env.NODE_ENV === 'dev' && typeof window !== 'undefined' && window.__traceLogBridge) {
100
+ // Don't call destroy on bridge (would cause recursion), just clear reference
101
+ window.__traceLogBridge = undefined;
102
+ }
93
103
  }
94
104
  catch (error) {
95
- // Force cleanup even if destroy fails
96
105
  app = null;
97
106
  isInitializing = false;
98
- debugLog.error('API', 'Error during destroy, forced cleanup', { error });
107
+ pendingListeners.length = 0;
108
+ log('error', 'Error during destroy, forced cleanup', { error });
99
109
  throw error;
100
110
  }
101
111
  finally {
@@ -113,3 +123,28 @@ if (process.env.NODE_ENV === 'dev' && typeof window !== 'undefined') {
113
123
  injectTestingBridge();
114
124
  }
115
125
  }
126
+ /**
127
+ * Internal sync function - ONLY for TestBridge in development
128
+ *
129
+ * WARNING: This function is internal and should NEVER be called directly.
130
+ * It's only exported for TestBridge synchronization in dev mode.
131
+ *
132
+ * @internal
133
+ */
134
+ export const __setAppInstance = (instance) => {
135
+ if (instance !== null) {
136
+ const hasRequiredMethods = typeof instance === 'object' &&
137
+ 'init' in instance &&
138
+ 'destroy' in instance &&
139
+ 'on' in instance &&
140
+ 'off' in instance;
141
+ if (!hasRequiredMethods) {
142
+ throw new Error('[TraceLog] Invalid app instance type');
143
+ }
144
+ }
145
+ // Prevent overwriting an already initialized app (except when clearing)
146
+ if (app !== null && instance !== null && app !== instance) {
147
+ throw new Error('[TraceLog] Cannot overwrite existing app instance. Call destroy() first.');
148
+ }
149
+ app = instance;
150
+ };
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;
package/dist/esm/app.js CHANGED
@@ -1,5 +1,3 @@
1
- import { getApiUrlForProject } from './managers/api.manager';
2
- import { ConfigManager } from './managers/config.manager';
3
1
  import { EventManager } from './managers/event.manager';
4
2
  import { UserManager } from './managers/user.manager';
5
3
  import { StateManager } from './managers/state.manager';
@@ -7,9 +5,9 @@ import { SessionHandler } from './handlers/session.handler';
7
5
  import { PageViewHandler } from './handlers/page-view.handler';
8
6
  import { ClickHandler } from './handlers/click.handler';
9
7
  import { ScrollHandler } from './handlers/scroll.handler';
10
- import { EventType } from './types';
8
+ import { EventType, Mode } from './types';
11
9
  import { GoogleAnalyticsIntegration } from './integrations/google-analytics.integration';
12
- import { isEventValid, getDeviceType, normalizeUrl, debugLog, Emitter } from './utils';
10
+ import { isEventValid, getDeviceType, normalizeUrl, Emitter, getApiUrl, detectQaMode, log } from './utils';
13
11
  import { StorageManager } from './managers/storage.manager';
14
12
  import { SCROLL_DEBOUNCE_TIME_MS, SCROLL_SUPPRESS_MULTIPLIER } from './constants/config.constants';
15
13
  import { PerformanceHandler } from './handlers/performance.handler';
@@ -27,27 +25,25 @@ export class App extends StateManager {
27
25
  get initialized() {
28
26
  return this.isInitialized;
29
27
  }
30
- async init(appConfig) {
28
+ async init(config) {
31
29
  if (this.isInitialized) {
32
30
  return;
33
31
  }
34
- if (!appConfig.id?.trim()) {
35
- throw new Error('Project ID is required');
36
- }
37
32
  this.managers.storage = new StorageManager();
38
33
  try {
39
- await this.setupState(appConfig);
34
+ this.setupState(config);
40
35
  await this.setupIntegrations();
41
36
  this.managers.event = new EventManager(this.managers.storage, this.integrations.googleAnalytics, this.emitter);
42
- this.initializeHandlers();
43
- await this.managers.event.recoverPersistedEvents().catch(() => {
44
- debugLog.warn('App', 'Failed to recover persisted events');
37
+ await this.initializeHandlers();
38
+ await this.managers.event.recoverPersistedEvents().catch((error) => {
39
+ log('warn', 'Failed to recover persisted events', { error });
45
40
  });
46
41
  this.isInitialized = true;
47
42
  }
48
43
  catch (error) {
49
44
  await this.destroy(true);
50
- throw new Error(`TraceLog initialization failed: ${error}`);
45
+ const errorMessage = error instanceof Error ? error.message : String(error);
46
+ throw new Error(`[TraceLog] TraceLog initialization failed: ${errorMessage}`);
51
47
  }
52
48
  }
53
49
  sendCustomEvent(name, metadata) {
@@ -56,9 +52,8 @@ export class App extends StateManager {
56
52
  }
57
53
  const { valid, error, sanitizedMetadata } = isEventValid(name, metadata);
58
54
  if (!valid) {
59
- const config = this.get('config');
60
- if (config?.mode === 'qa' || config?.mode === 'debug') {
61
- throw new Error(`Custom event "${name}" validation failed: ${error}`);
55
+ if (this.get('mode') === Mode.QA) {
56
+ throw new Error(`[TraceLog] Custom event "${name}" validation failed: ${error}`);
62
57
  }
63
58
  return;
64
59
  }
@@ -87,8 +82,8 @@ export class App extends StateManager {
87
82
  try {
88
83
  await handler.stopTracking();
89
84
  }
90
- catch {
91
- debugLog.warn('App', 'Failed to stop tracking');
85
+ catch (error) {
86
+ log('warn', 'Failed to stop tracking', { error });
92
87
  }
93
88
  });
94
89
  await Promise.allSettled(handlerCleanups);
@@ -105,22 +100,25 @@ export class App extends StateManager {
105
100
  this.isInitialized = false;
106
101
  this.handlers = {};
107
102
  }
108
- async setupState(appConfig) {
109
- const apiUrl = getApiUrlForProject(appConfig.id, appConfig.allowHttp);
110
- this.set('apiUrl', apiUrl);
111
- const configManager = new ConfigManager();
112
- const config = await configManager.get(apiUrl, appConfig);
103
+ setupState(config) {
113
104
  this.set('config', config);
114
- const userId = UserManager.getId(this.managers.storage, config.id);
105
+ const userId = UserManager.getId(this.managers.storage);
115
106
  this.set('userId', userId);
116
- this.set('device', getDeviceType());
107
+ const apiUrl = getApiUrl(config);
108
+ this.set('apiUrl', apiUrl);
109
+ const device = getDeviceType();
110
+ this.set('device', device);
117
111
  const pageUrl = normalizeUrl(window.location.href, config.sensitiveQueryParams);
118
112
  this.set('pageUrl', pageUrl);
113
+ const mode = detectQaMode() ? Mode.QA : undefined;
114
+ if (mode) {
115
+ this.set('mode', mode);
116
+ }
119
117
  }
120
118
  async setupIntegrations() {
121
119
  const config = this.get('config');
122
120
  const measurementId = config.integrations?.googleAnalytics?.measurementId;
123
- if (!config.ipExcluded && measurementId?.trim()) {
121
+ if (measurementId?.trim()) {
124
122
  try {
125
123
  this.integrations.googleAnalytics = new GoogleAnalyticsIntegration();
126
124
  await this.integrations.googleAnalytics.initialize();
@@ -130,13 +128,9 @@ export class App extends StateManager {
130
128
  }
131
129
  }
132
130
  }
133
- initializeHandlers() {
131
+ async initializeHandlers() {
134
132
  this.handlers.session = new SessionHandler(this.managers.storage, this.managers.event);
135
- this.handlers.session.startTracking().catch((error) => {
136
- debugLog.error('App', 'Session handler failed to start', {
137
- message: error instanceof Error ? error.message : 'Unknown error',
138
- });
139
- });
133
+ await this.handlers.session.startTracking();
140
134
  const onPageView = () => {
141
135
  this.set('suppressNextScroll', true);
142
136
  if (this.suppressNextScrollTimer) {
@@ -153,8 +147,8 @@ export class App extends StateManager {
153
147
  this.handlers.scroll = new ScrollHandler(this.managers.event);
154
148
  this.handlers.scroll.startTracking();
155
149
  this.handlers.performance = new PerformanceHandler(this.managers.event);
156
- this.handlers.performance.startTracking().catch(() => {
157
- debugLog.warn('App', 'Failed to start performance tracking');
150
+ this.handlers.performance.startTracking().catch((error) => {
151
+ log('warn', 'Failed to start performance tracking', { error });
158
152
  });
159
153
  this.handlers.error = new ErrorHandler(this.managers.event);
160
154
  this.handlers.error.startTracking();
@@ -29,6 +29,7 @@ export declare const MAX_CUSTOM_EVENT_NAME_LENGTH = 120;
29
29
  export declare const MAX_CUSTOM_EVENT_STRING_SIZE: number;
30
30
  export declare const MAX_CUSTOM_EVENT_KEYS = 10;
31
31
  export declare const MAX_CUSTOM_EVENT_ARRAY_SIZE = 10;
32
+ export declare const MAX_NESTED_OBJECT_KEYS = 20;
32
33
  export declare const MAX_TEXT_LENGTH = 255;
33
34
  export declare const MAX_STRING_LENGTH = 1000;
34
35
  export declare const MAX_ARRAY_LENGTH = 100;
@@ -38,7 +39,7 @@ export declare const SYNC_XHR_TIMEOUT_MS = 2000;
38
39
  export declare const MAX_FINGERPRINTS = 1000;
39
40
  export declare const FINGERPRINT_CLEANUP_MULTIPLIER = 10;
40
41
  export declare const MAX_FINGERPRINTS_HARD_LIMIT = 2000;
41
- export declare const HTML_DATA_ATTR_PREFIX = "data-tl";
42
+ export declare const HTML_DATA_ATTR_PREFIX = "data-tlog";
42
43
  export declare const INTERACTIVE_SELECTORS: readonly ["button", "a", "input[type=\"button\"]", "input[type=\"submit\"]", "input[type=\"reset\"]", "input[type=\"checkbox\"]", "input[type=\"radio\"]", "select", "textarea", "[role=\"button\"]", "[role=\"link\"]", "[role=\"tab\"]", "[role=\"menuitem\"]", "[role=\"option\"]", "[role=\"checkbox\"]", "[role=\"radio\"]", "[role=\"switch\"]", "[routerLink]", "[ng-click]", "[data-action]", "[data-click]", "[data-navigate]", "[data-toggle]", "[onclick]", ".btn", ".button", ".clickable", ".nav-link", ".menu-item", "[data-testid]", "[tabindex=\"0\"]"];
43
44
  export declare const UTM_PARAMS: string[];
44
45
  export declare const INITIALIZATION_MAX_CONCURRENT_RETRIES = 20;
@@ -60,13 +61,14 @@ export declare const RETRY_BACKOFF_INITIAL = 1000;
60
61
  export declare const RETRY_BACKOFF_MAX = 30000;
61
62
  export declare const RATE_LIMIT_INTERVAL = 1000;
62
63
  export declare const MAX_RETRY_ATTEMPTS = 10;
63
- export declare const ALLOWED_API_CONFIG_KEYS: Set<"mode" | "samplingRate" | "excludedUrlPaths" | keyof import("../types").ExclusiveApiConfig>;
64
64
  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
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
+ readonly INVALID_TRACELOG_PROJECT_ID: "TraceLog project ID is required when integration is enabled";
71
+ readonly INVALID_CUSTOM_API_URL: "Custom API URL is required when integration is enabled";
70
72
  readonly INVALID_GOOGLE_ANALYTICS_ID: "Google Analytics measurement ID is required when integration is enabled";
71
73
  readonly INVALID_SCROLL_CONTAINER_SELECTORS: "Scroll container selectors must be valid CSS selectors";
72
74
  readonly INVALID_GLOBAL_METADATA: "Global metadata must be an object";
@@ -42,6 +42,7 @@ export const MAX_CUSTOM_EVENT_NAME_LENGTH = 120;
42
42
  export const MAX_CUSTOM_EVENT_STRING_SIZE = 8 * 1024; // 8KB
43
43
  export const MAX_CUSTOM_EVENT_KEYS = 10;
44
44
  export const MAX_CUSTOM_EVENT_ARRAY_SIZE = 10;
45
+ export const MAX_NESTED_OBJECT_KEYS = 20; // Maximum keys in nested objects within arrays
45
46
  // Text content limits
46
47
  export const MAX_TEXT_LENGTH = 255; // For click tracking text content
47
48
  // Data sanitization limits
@@ -59,7 +60,7 @@ export const MAX_FINGERPRINTS_HARD_LIMIT = 2000; // Hard limit for aggressive cl
59
60
  // ============================================================================
60
61
  // BROWSER & HTML
61
62
  // ============================================================================
62
- export const HTML_DATA_ATTR_PREFIX = 'data-tl';
63
+ export const HTML_DATA_ATTR_PREFIX = 'data-tlog';
63
64
  // Interactive element selectors for click tracking
64
65
  export const INTERACTIVE_SELECTORS = [
65
66
  'button',
@@ -133,31 +134,18 @@ export const MAX_RETRY_ATTEMPTS = 10;
133
134
  // ============================================================================
134
135
  // VALIDATION
135
136
  // ============================================================================
136
- // Allowed API config keys for runtime validation
137
- export const ALLOWED_API_CONFIG_KEYS = new Set([
138
- 'mode',
139
- 'tags',
140
- 'samplingRate',
141
- 'excludedUrlPaths',
142
- 'ipExcluded',
143
- ]);
144
137
  // Validation error messages - standardized across all layers
145
138
  export const VALIDATION_MESSAGES = {
146
- // Project ID validation - consistent message across all layers
147
139
  MISSING_PROJECT_ID: 'Project ID is required',
148
140
  PROJECT_ID_EMPTY_AFTER_TRIM: 'Project ID is required',
149
- // Session timeout validation
150
141
  INVALID_SESSION_TIMEOUT: `Session timeout must be between ${MIN_SESSION_TIMEOUT_MS}ms (30 seconds) and ${MAX_SESSION_TIMEOUT_MS}ms (24 hours)`,
151
- // Sampling rate validation
152
142
  INVALID_SAMPLING_RATE: 'Sampling rate must be between 0 and 1',
153
143
  INVALID_ERROR_SAMPLING_RATE: 'Error sampling must be between 0 and 1',
154
- // Integration validation
144
+ INVALID_TRACELOG_PROJECT_ID: 'TraceLog project ID is required when integration is enabled',
145
+ INVALID_CUSTOM_API_URL: 'Custom API URL is required when integration is enabled',
155
146
  INVALID_GOOGLE_ANALYTICS_ID: 'Google Analytics measurement ID is required when integration is enabled',
156
- // UI validation
157
147
  INVALID_SCROLL_CONTAINER_SELECTORS: 'Scroll container selectors must be valid CSS selectors',
158
- // Global metadata validation
159
148
  INVALID_GLOBAL_METADATA: 'Global metadata must be an object',
160
- // Array validation
161
149
  INVALID_SENSITIVE_QUERY_PARAMS: 'Sensitive query params must be an array of strings',
162
150
  };
163
151
  // ============================================================================
@@ -1,4 +1,3 @@
1
- export * from './api.constants';
2
1
  export * from './config.constants';
3
2
  export * from './storage.constants';
4
3
  export * from './performance.constants';
@@ -1,4 +1,3 @@
1
- export * from './api.constants';
2
1
  export * from './config.constants';
3
2
  export * from './storage.constants';
4
3
  export * from './performance.constants';
@@ -1,5 +1,6 @@
1
- export declare const STORAGE_BASE_KEY = "tl";
2
- export declare const USER_ID_KEY: (id: string) => string;
1
+ export declare const STORAGE_BASE_KEY = "tlog";
2
+ export declare const QA_MODE_KEY = "tlog:qa_mode";
3
+ export declare const USER_ID_KEY = "tlog:uid";
3
4
  export declare const QUEUE_KEY: (id: string) => string;
4
5
  export declare const SESSION_STORAGE_KEY: (id: string) => string;
5
6
  export declare const CROSS_TAB_SESSION_KEY: (id: string) => string;
@@ -1,5 +1,6 @@
1
- export const STORAGE_BASE_KEY = 'tl';
2
- export const USER_ID_KEY = (id) => (id ? `${STORAGE_BASE_KEY}:${id}:uid` : `${STORAGE_BASE_KEY}:uid`);
1
+ export const STORAGE_BASE_KEY = 'tlog';
2
+ export const QA_MODE_KEY = `${STORAGE_BASE_KEY}:qa_mode`;
3
+ export const USER_ID_KEY = `${STORAGE_BASE_KEY}:uid`;
3
4
  export const QUEUE_KEY = (id) => (id ? `${STORAGE_BASE_KEY}:${id}:queue` : `${STORAGE_BASE_KEY}:queue`);
4
5
  export const SESSION_STORAGE_KEY = (id) => id ? `${STORAGE_BASE_KEY}:${id}:session` : `${STORAGE_BASE_KEY}:session`;
5
6
  // Cross-tab session management storage keys
@@ -1,7 +1,7 @@
1
1
  import { HTML_DATA_ATTR_PREFIX, MAX_TEXT_LENGTH, INTERACTIVE_SELECTORS } from '../constants';
2
2
  import { EventType } from '../types';
3
3
  import { StateManager } from '../managers/state.manager';
4
- import { debugLog } from '../utils/logging';
4
+ import { log } from '@/utils';
5
5
  export class ClickHandler extends StateManager {
6
6
  constructor(eventManager) {
7
7
  super();
@@ -20,7 +20,7 @@ export class ClickHandler extends StateManager {
20
20
  ? target.parentElement
21
21
  : null;
22
22
  if (!clickedElement) {
23
- debugLog.warn('ClickHandler', 'Click target not found or not an element');
23
+ log('warn', 'Click target not found or not an element');
24
24
  return;
25
25
  }
26
26
  const trackingElement = this.findTrackingElement(clickedElement);
@@ -72,10 +72,7 @@ export class ClickHandler extends StateManager {
72
72
  }
73
73
  }
74
74
  catch (error) {
75
- debugLog.warn('ClickHandler', 'Invalid selector in element search', {
76
- selector,
77
- error: error instanceof Error ? error.message : 'Unknown error',
78
- });
75
+ log('warn', 'Invalid selector in element search', { error, data: { selector } });
79
76
  continue;
80
77
  }
81
78
  }
@@ -1,7 +1,6 @@
1
1
  import { StateManager } from '../managers/state.manager';
2
2
  import { ErrorType, EventType } from '../types';
3
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
- import { debugLog } from '../utils/logging';
5
4
  /**
6
5
  * Simplified error handler for tracking JavaScript errors and unhandled promise rejections
7
6
  * Includes PII sanitization and sampling support
@@ -18,11 +17,6 @@ export class ErrorHandler extends StateManager {
18
17
  if (this.shouldSuppressError(ErrorType.JS_ERROR, sanitizedMessage)) {
19
18
  return;
20
19
  }
21
- debugLog.warn('ErrorHandler', 'JS error captured', {
22
- message: sanitizedMessage,
23
- filename: event.filename,
24
- line: event.lineno,
25
- });
26
20
  this.eventManager.track({
27
21
  type: EventType.ERROR,
28
22
  error_data: {
@@ -43,7 +37,6 @@ export class ErrorHandler extends StateManager {
43
37
  if (this.shouldSuppressError(ErrorType.PROMISE_REJECTION, sanitizedMessage)) {
44
38
  return;
45
39
  }
46
- debugLog.warn('ErrorHandler', 'Promise rejection captured', { message: sanitizedMessage });
47
40
  this.eventManager.track({
48
41
  type: EventType.ERROR,
49
42
  error_data: {
@@ -107,10 +100,7 @@ export class ErrorHandler extends StateManager {
107
100
  }
108
101
  this.recentErrors.set(key, now);
109
102
  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
- });
103
+ // Hard limit exceeded, clearing all tracked errors
114
104
  this.recentErrors.clear();
115
105
  this.recentErrors.set(key, now);
116
106
  return false;
@@ -1,7 +1,6 @@
1
1
  import { EventType } from '../types';
2
2
  import { normalizeUrl } from '../utils';
3
3
  import { StateManager } from '../managers/state.manager';
4
- import { debugLog } from '../utils/logging';
5
4
  export class PageViewHandler extends StateManager {
6
5
  constructor(eventManager, onTrack) {
7
6
  super();
@@ -13,7 +12,6 @@ export class PageViewHandler extends StateManager {
13
12
  }
14
13
  this.onTrack();
15
14
  const fromUrl = this.get('pageUrl');
16
- debugLog.debug('PageViewHandler', 'Page navigation detected', { from: fromUrl, to: normalizedUrl });
17
15
  this.set('pageUrl', normalizedUrl);
18
16
  const pageViewData = this.extractPageViewData();
19
17
  this.eventManager.track({
@@ -27,7 +25,6 @@ export class PageViewHandler extends StateManager {
27
25
  this.onTrack = onTrack;
28
26
  }
29
27
  startTracking() {
30
- debugLog.debug('PageViewHandler', 'Starting page view tracking');
31
28
  this.trackInitialPageView();
32
29
  window.addEventListener('popstate', this.trackCurrentPage, true);
33
30
  window.addEventListener('hashchange', this.trackCurrentPage, true);
@@ -35,7 +32,6 @@ export class PageViewHandler extends StateManager {
35
32
  this.patchHistory('replaceState');
36
33
  }
37
34
  stopTracking() {
38
- debugLog.debug('PageViewHandler', 'Stopping page view tracking');
39
35
  window.removeEventListener('popstate', this.trackCurrentPage, true);
40
36
  window.removeEventListener('hashchange', this.trackCurrentPage, true);
41
37
  if (this.originalPushState) {