@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.
- package/README.md +157 -180
- package/dist/browser/tracelog.esm.js +1124 -1377
- package/dist/browser/tracelog.esm.js.map +1 -0
- package/dist/browser/tracelog.js +2 -2
- package/dist/browser/tracelog.js.map +1 -0
- package/dist/cjs/api.d.ts +12 -2
- package/dist/cjs/api.js +74 -29
- package/dist/cjs/app.d.ts +2 -2
- package/dist/cjs/app.js +26 -32
- package/dist/cjs/constants/config.constants.d.ts +7 -2
- package/dist/cjs/constants/config.constants.js +9 -18
- package/dist/cjs/constants/index.d.ts +0 -1
- package/dist/cjs/constants/index.js +0 -1
- package/dist/cjs/constants/storage.constants.d.ts +3 -2
- package/dist/cjs/constants/storage.constants.js +4 -4
- package/dist/cjs/handlers/click.handler.js +3 -6
- package/dist/cjs/handlers/error.handler.js +1 -11
- package/dist/cjs/handlers/page-view.handler.js +0 -4
- package/dist/cjs/handlers/performance.handler.js +14 -29
- package/dist/cjs/handlers/scroll.handler.js +7 -6
- package/dist/cjs/handlers/session.handler.js +7 -6
- package/dist/cjs/integrations/google-analytics.integration.js +2 -6
- package/dist/cjs/listeners/activity-listener-manager.js +3 -3
- package/dist/cjs/listeners/input-listener-managers.js +3 -3
- package/dist/cjs/listeners/touch-listener-manager.js +3 -3
- package/dist/cjs/listeners/unload-listener-manager.js +3 -3
- package/dist/cjs/listeners/visibility-listener-manager.js +3 -3
- package/dist/cjs/managers/event.manager.d.ts +5 -1
- package/dist/cjs/managers/event.manager.js +103 -40
- package/dist/cjs/managers/sender.manager.js +29 -36
- package/dist/cjs/managers/session.manager.js +5 -13
- package/dist/cjs/managers/state.manager.d.ts +0 -3
- package/dist/cjs/managers/state.manager.js +1 -43
- package/dist/cjs/managers/storage.manager.d.ts +21 -2
- package/dist/cjs/managers/storage.manager.js +164 -21
- package/dist/cjs/managers/user.manager.d.ts +1 -1
- package/dist/cjs/managers/user.manager.js +2 -2
- package/dist/cjs/public-api.d.ts +3 -3
- package/dist/cjs/public-api.js +1 -1
- package/dist/cjs/test-bridge.d.ts +1 -0
- package/dist/cjs/test-bridge.js +37 -2
- package/dist/cjs/types/config.types.d.ts +17 -20
- package/dist/cjs/types/config.types.js +6 -0
- package/dist/cjs/types/event.types.d.ts +1 -13
- package/dist/cjs/types/index.d.ts +0 -2
- package/dist/cjs/types/index.js +0 -2
- package/dist/cjs/types/mode.types.d.ts +1 -2
- package/dist/cjs/types/mode.types.js +0 -1
- package/dist/cjs/types/queue.types.d.ts +0 -6
- package/dist/cjs/types/state.types.d.ts +2 -0
- package/dist/cjs/types/test-bridge.types.d.ts +2 -2
- package/dist/cjs/types/validation-error.types.d.ts +0 -6
- package/dist/cjs/types/validation-error.types.js +1 -10
- package/dist/cjs/utils/browser/device-detector.utils.js +2 -24
- package/dist/cjs/utils/browser/index.d.ts +1 -0
- package/dist/cjs/utils/browser/index.js +1 -0
- package/dist/cjs/utils/browser/qa-mode.utils.d.ts +13 -0
- package/dist/cjs/utils/browser/qa-mode.utils.js +43 -0
- package/dist/cjs/utils/browser/utm-params.utils.js +0 -15
- package/dist/cjs/utils/data/uuid.utils.d.ts +13 -0
- package/dist/cjs/utils/data/uuid.utils.js +37 -1
- package/dist/cjs/utils/index.d.ts +1 -1
- package/dist/cjs/utils/index.js +1 -1
- package/dist/cjs/utils/logging.utils.d.ts +21 -0
- package/dist/cjs/utils/logging.utils.js +86 -0
- package/dist/cjs/utils/network/index.d.ts +0 -1
- package/dist/cjs/utils/network/index.js +0 -1
- package/dist/cjs/utils/network/url.utils.d.ts +2 -8
- package/dist/cjs/utils/network/url.utils.js +45 -90
- package/dist/cjs/utils/security/sanitize.utils.d.ts +1 -13
- package/dist/cjs/utils/security/sanitize.utils.js +15 -178
- package/dist/cjs/utils/validations/config-validations.utils.d.ts +3 -9
- package/dist/cjs/utils/validations/config-validations.utils.js +56 -93
- package/dist/cjs/utils/validations/event-validations.utils.js +11 -5
- package/dist/cjs/utils/validations/index.d.ts +0 -1
- package/dist/cjs/utils/validations/index.js +0 -1
- package/dist/cjs/utils/validations/metadata-validations.utils.js +0 -1
- package/dist/cjs/utils/validations/type-guards.utils.d.ts +2 -2
- package/dist/cjs/utils/validations/type-guards.utils.js +50 -4
- package/dist/esm/api.d.ts +12 -2
- package/dist/esm/api.js +73 -29
- package/dist/esm/app.d.ts +2 -2
- package/dist/esm/app.js +28 -34
- package/dist/esm/constants/config.constants.d.ts +7 -2
- package/dist/esm/constants/config.constants.js +7 -16
- package/dist/esm/constants/index.d.ts +0 -1
- package/dist/esm/constants/index.js +0 -1
- package/dist/esm/constants/storage.constants.d.ts +3 -2
- package/dist/esm/constants/storage.constants.js +3 -2
- package/dist/esm/handlers/click.handler.js +3 -6
- package/dist/esm/handlers/error.handler.js +1 -11
- package/dist/esm/handlers/page-view.handler.js +0 -4
- package/dist/esm/handlers/performance.handler.js +14 -29
- package/dist/esm/handlers/scroll.handler.js +7 -6
- package/dist/esm/handlers/session.handler.js +7 -6
- package/dist/esm/integrations/google-analytics.integration.js +3 -7
- package/dist/esm/listeners/activity-listener-manager.js +3 -3
- package/dist/esm/listeners/input-listener-managers.js +3 -3
- package/dist/esm/listeners/touch-listener-manager.js +3 -3
- package/dist/esm/listeners/unload-listener-manager.js +3 -3
- package/dist/esm/listeners/visibility-listener-manager.js +3 -3
- package/dist/esm/managers/event.manager.d.ts +5 -1
- package/dist/esm/managers/event.manager.js +106 -43
- package/dist/esm/managers/sender.manager.js +31 -38
- package/dist/esm/managers/session.manager.js +5 -13
- package/dist/esm/managers/state.manager.d.ts +0 -3
- package/dist/esm/managers/state.manager.js +1 -43
- package/dist/esm/managers/storage.manager.d.ts +21 -2
- package/dist/esm/managers/storage.manager.js +164 -21
- package/dist/esm/managers/user.manager.d.ts +1 -1
- package/dist/esm/managers/user.manager.js +2 -2
- package/dist/esm/public-api.d.ts +3 -3
- package/dist/esm/public-api.js +1 -1
- package/dist/esm/test-bridge.d.ts +1 -0
- package/dist/esm/test-bridge.js +37 -2
- package/dist/esm/types/config.types.d.ts +17 -20
- package/dist/esm/types/config.types.js +5 -1
- package/dist/esm/types/event.types.d.ts +1 -13
- package/dist/esm/types/index.d.ts +0 -2
- package/dist/esm/types/index.js +0 -2
- package/dist/esm/types/mode.types.d.ts +1 -2
- package/dist/esm/types/mode.types.js +0 -1
- package/dist/esm/types/queue.types.d.ts +0 -6
- package/dist/esm/types/state.types.d.ts +2 -0
- package/dist/esm/types/test-bridge.types.d.ts +2 -2
- package/dist/esm/types/validation-error.types.d.ts +0 -6
- package/dist/esm/types/validation-error.types.js +0 -8
- package/dist/esm/utils/browser/device-detector.utils.js +2 -24
- package/dist/esm/utils/browser/index.d.ts +1 -0
- package/dist/esm/utils/browser/index.js +1 -0
- package/dist/esm/utils/browser/qa-mode.utils.d.ts +13 -0
- package/dist/esm/utils/browser/qa-mode.utils.js +39 -0
- package/dist/esm/utils/browser/utm-params.utils.js +0 -15
- package/dist/esm/utils/data/uuid.utils.d.ts +13 -0
- package/dist/esm/utils/data/uuid.utils.js +35 -0
- package/dist/esm/utils/index.d.ts +1 -1
- package/dist/esm/utils/index.js +1 -1
- package/dist/esm/utils/logging.utils.d.ts +21 -0
- package/dist/esm/utils/logging.utils.js +81 -0
- package/dist/esm/utils/network/index.d.ts +0 -1
- package/dist/esm/utils/network/index.js +0 -1
- package/dist/esm/utils/network/url.utils.d.ts +2 -8
- package/dist/esm/utils/network/url.utils.js +44 -88
- package/dist/esm/utils/security/sanitize.utils.d.ts +1 -13
- package/dist/esm/utils/security/sanitize.utils.js +15 -176
- package/dist/esm/utils/validations/config-validations.utils.d.ts +3 -9
- package/dist/esm/utils/validations/config-validations.utils.js +57 -93
- package/dist/esm/utils/validations/event-validations.utils.js +11 -5
- package/dist/esm/utils/validations/index.d.ts +0 -1
- package/dist/esm/utils/validations/index.js +0 -1
- package/dist/esm/utils/validations/metadata-validations.utils.js +0 -1
- package/dist/esm/utils/validations/type-guards.utils.d.ts +2 -2
- package/dist/esm/utils/validations/type-guards.utils.js +50 -4
- package/package.json +3 -2
- package/dist/cjs/app.types.d.ts +0 -2
- package/dist/cjs/app.types.js +0 -12
- package/dist/cjs/constants/api.constants.d.ts +0 -6
- package/dist/cjs/constants/api.constants.js +0 -14
- package/dist/cjs/managers/api.manager.d.ts +0 -13
- package/dist/cjs/managers/api.manager.js +0 -44
- package/dist/cjs/managers/config.builder.d.ts +0 -33
- package/dist/cjs/managers/config.builder.js +0 -116
- package/dist/cjs/managers/config.manager.d.ts +0 -56
- package/dist/cjs/managers/config.manager.js +0 -157
- package/dist/cjs/managers/tags.manager.d.ts +0 -36
- package/dist/cjs/managers/tags.manager.js +0 -171
- package/dist/cjs/types/api.types.d.ts +0 -52
- package/dist/cjs/types/api.types.js +0 -56
- package/dist/cjs/types/tag.types.d.ts +0 -43
- package/dist/cjs/types/tag.types.js +0 -31
- package/dist/cjs/utils/logging/debug-logger.utils.d.ts +0 -14
- package/dist/cjs/utils/logging/debug-logger.utils.js +0 -47
- package/dist/cjs/utils/logging/index.d.ts +0 -1
- package/dist/cjs/utils/logging/index.js +0 -5
- package/dist/cjs/utils/network/fetch-with-timeout.utils.d.ts +0 -4
- package/dist/cjs/utils/network/fetch-with-timeout.utils.js +0 -25
- package/dist/cjs/utils/validations/url-validations.utils.d.ts +0 -15
- package/dist/cjs/utils/validations/url-validations.utils.js +0 -47
- package/dist/esm/app.types.d.ts +0 -2
- package/dist/esm/app.types.js +0 -1
- package/dist/esm/constants/api.constants.d.ts +0 -6
- package/dist/esm/constants/api.constants.js +0 -11
- package/dist/esm/managers/api.manager.d.ts +0 -13
- package/dist/esm/managers/api.manager.js +0 -41
- package/dist/esm/managers/config.builder.d.ts +0 -33
- package/dist/esm/managers/config.builder.js +0 -112
- package/dist/esm/managers/config.manager.d.ts +0 -56
- package/dist/esm/managers/config.manager.js +0 -153
- package/dist/esm/managers/tags.manager.d.ts +0 -36
- package/dist/esm/managers/tags.manager.js +0 -167
- package/dist/esm/types/api.types.d.ts +0 -52
- package/dist/esm/types/api.types.js +0 -53
- package/dist/esm/types/tag.types.d.ts +0 -43
- package/dist/esm/types/tag.types.js +0 -28
- package/dist/esm/utils/logging/debug-logger.utils.d.ts +0 -14
- package/dist/esm/utils/logging/debug-logger.utils.js +0 -44
- package/dist/esm/utils/logging/index.d.ts +0 -1
- package/dist/esm/utils/logging/index.js +0 -1
- package/dist/esm/utils/network/fetch-with-timeout.utils.d.ts +0 -4
- package/dist/esm/utils/network/fetch-with-timeout.utils.js +0 -22
- package/dist/esm/utils/validations/url-validations.utils.d.ts +0 -15
- 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.
|
|
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
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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;
|
|
@@ -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);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Checks if an object contains only primitive fields
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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 (
|
|
23
|
-
|
|
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 {
|
|
1
|
+
import { App } from './app';
|
|
2
|
+
import { MetadataType, Config, EmitterCallback, EmitterMap } from './types';
|
|
2
3
|
import './types/window.types';
|
|
3
|
-
export declare const init: (
|
|
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 {
|
|
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 (
|
|
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
|
-
|
|
21
|
-
throw new Error('Initialization already in progress');
|
|
22
|
+
return;
|
|
22
23
|
}
|
|
23
24
|
isInitializing = true;
|
|
24
25
|
try {
|
|
25
|
-
|
|
26
|
-
const validatedConfig = validateAndNormalizeConfig(appConfig);
|
|
26
|
+
const validatedConfig = validateAndNormalizeConfig(config);
|
|
27
27
|
const instance = new App();
|
|
28
28
|
try {
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
throw
|
|
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 {
|
|
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(
|
|
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;
|