@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.
- package/README.md +157 -180
- package/dist/browser/tracelog.esm.js +1007 -1357
- package/dist/browser/tracelog.js +2 -2
- package/dist/cjs/api.d.ts +12 -2
- package/dist/cjs/api.js +63 -27
- package/dist/cjs/app.d.ts +2 -2
- package/dist/cjs/app.js +26 -32
- package/dist/cjs/constants/config.constants.d.ts +4 -2
- package/dist/cjs/constants/config.constants.js +6 -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 +2 -1
- package/dist/cjs/managers/event.manager.js +60 -38
- 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 +16 -2
- package/dist/cjs/managers/storage.manager.js +73 -19
- 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 +15 -18
- 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 +6 -0
- package/dist/cjs/utils/logging.utils.js +25 -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 +46 -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 +48 -94
- 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 +62 -27
- package/dist/esm/app.d.ts +2 -2
- package/dist/esm/app.js +28 -34
- package/dist/esm/constants/config.constants.d.ts +4 -2
- package/dist/esm/constants/config.constants.js +4 -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 +2 -1
- package/dist/esm/managers/event.manager.js +62 -40
- 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 +16 -2
- package/dist/esm/managers/storage.manager.js +73 -19
- 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 +15 -18
- 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 +6 -0
- package/dist/esm/utils/logging.utils.js +20 -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 +45 -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 +49 -94
- 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 +1 -1
- 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,7 +1,6 @@
|
|
|
1
|
-
import { MAX_SESSION_TIMEOUT_MS, MIN_SESSION_TIMEOUT_MS, VALIDATION_MESSAGES } from '../../constants';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { debugLog } from '../logging';
|
|
1
|
+
import { MAX_SESSION_TIMEOUT_MS, MIN_SESSION_TIMEOUT_MS, DEFAULT_SESSION_TIMEOUT, VALIDATION_MESSAGES, } from '../../constants';
|
|
2
|
+
import { AppConfigValidationError, SessionTimeoutValidationError, SamplingRateValidationError, IntegrationValidationError, } from '../../types/validation-error.types';
|
|
3
|
+
import { log } from '../logging.utils';
|
|
5
4
|
/**
|
|
6
5
|
* Validates the app configuration object (before normalization)
|
|
7
6
|
* This validates the structure and basic types but allows for normalization afterward
|
|
@@ -10,42 +9,18 @@ import { debugLog } from '../logging';
|
|
|
10
9
|
* @throws {AppConfigValidationError} If other configuration validation fails
|
|
11
10
|
*/
|
|
12
11
|
export const validateAppConfig = (config) => {
|
|
13
|
-
// Validate config exists and has id property
|
|
14
12
|
if (!config || typeof config !== 'object') {
|
|
15
|
-
debugLog.clientError('ConfigValidation', 'Configuration must be an object', { config });
|
|
16
13
|
throw new AppConfigValidationError('Configuration must be an object', 'config');
|
|
17
14
|
}
|
|
18
|
-
// Check if id property exists (allow falsy values to be handled by normalization)
|
|
19
|
-
if (!('id' in config)) {
|
|
20
|
-
debugLog.clientError('ConfigValidation', 'Project ID is missing from configuration');
|
|
21
|
-
throw new ProjectIdValidationError(VALIDATION_MESSAGES.MISSING_PROJECT_ID, 'config');
|
|
22
|
-
}
|
|
23
|
-
// Check basic type - null, undefined, or non-string values should fail here
|
|
24
|
-
if (config.id === null || config.id === undefined || typeof config.id !== 'string') {
|
|
25
|
-
debugLog.clientError('ConfigValidation', 'Project ID must be a non-empty string', {
|
|
26
|
-
providedId: config.id,
|
|
27
|
-
type: typeof config.id,
|
|
28
|
-
});
|
|
29
|
-
throw new ProjectIdValidationError(VALIDATION_MESSAGES.MISSING_PROJECT_ID, 'config');
|
|
30
|
-
}
|
|
31
15
|
if (config.sessionTimeout !== undefined) {
|
|
32
16
|
if (typeof config.sessionTimeout !== 'number' ||
|
|
33
17
|
config.sessionTimeout < MIN_SESSION_TIMEOUT_MS ||
|
|
34
18
|
config.sessionTimeout > MAX_SESSION_TIMEOUT_MS) {
|
|
35
|
-
debugLog.clientError('ConfigValidation', 'Invalid session timeout', {
|
|
36
|
-
provided: config.sessionTimeout,
|
|
37
|
-
min: MIN_SESSION_TIMEOUT_MS,
|
|
38
|
-
max: MAX_SESSION_TIMEOUT_MS,
|
|
39
|
-
});
|
|
40
19
|
throw new SessionTimeoutValidationError(VALIDATION_MESSAGES.INVALID_SESSION_TIMEOUT, 'config');
|
|
41
20
|
}
|
|
42
21
|
}
|
|
43
22
|
if (config.globalMetadata !== undefined) {
|
|
44
23
|
if (typeof config.globalMetadata !== 'object' || config.globalMetadata === null) {
|
|
45
|
-
debugLog.clientError('ConfigValidation', 'Global metadata must be an object', {
|
|
46
|
-
provided: config.globalMetadata,
|
|
47
|
-
type: typeof config.globalMetadata,
|
|
48
|
-
});
|
|
49
24
|
throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_GLOBAL_METADATA, 'config');
|
|
50
25
|
}
|
|
51
26
|
}
|
|
@@ -57,31 +32,29 @@ export const validateAppConfig = (config) => {
|
|
|
57
32
|
}
|
|
58
33
|
if (config.sensitiveQueryParams !== undefined) {
|
|
59
34
|
if (!Array.isArray(config.sensitiveQueryParams)) {
|
|
60
|
-
debugLog.clientError('ConfigValidation', 'Sensitive query params must be an array', {
|
|
61
|
-
provided: config.sensitiveQueryParams,
|
|
62
|
-
type: typeof config.sensitiveQueryParams,
|
|
63
|
-
});
|
|
64
35
|
throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_SENSITIVE_QUERY_PARAMS, 'config');
|
|
65
36
|
}
|
|
66
37
|
for (const param of config.sensitiveQueryParams) {
|
|
67
38
|
if (typeof param !== 'string') {
|
|
68
|
-
debugLog.clientError('ConfigValidation', 'All sensitive query params must be strings', {
|
|
69
|
-
param,
|
|
70
|
-
type: typeof param,
|
|
71
|
-
});
|
|
72
39
|
throw new AppConfigValidationError('All sensitive query params must be strings', 'config');
|
|
73
40
|
}
|
|
74
41
|
}
|
|
75
42
|
}
|
|
76
43
|
if (config.errorSampling !== undefined) {
|
|
77
44
|
if (typeof config.errorSampling !== 'number' || config.errorSampling < 0 || config.errorSampling > 1) {
|
|
78
|
-
debugLog.clientError('ConfigValidation', 'Invalid error sampling rate', {
|
|
79
|
-
provided: config.errorSampling,
|
|
80
|
-
expected: '0-1',
|
|
81
|
-
});
|
|
82
45
|
throw new SamplingRateValidationError(VALIDATION_MESSAGES.INVALID_ERROR_SAMPLING_RATE, 'config');
|
|
83
46
|
}
|
|
84
47
|
}
|
|
48
|
+
if (config.samplingRate !== undefined) {
|
|
49
|
+
if (typeof config.samplingRate !== 'number' || config.samplingRate < 0 || config.samplingRate > 1) {
|
|
50
|
+
throw new SamplingRateValidationError(VALIDATION_MESSAGES.INVALID_SAMPLING_RATE, 'config');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (config.allowHttp !== undefined) {
|
|
54
|
+
if (typeof config.allowHttp !== 'boolean') {
|
|
55
|
+
throw new AppConfigValidationError('allowHttp must be a boolean', 'config');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
85
58
|
};
|
|
86
59
|
/**
|
|
87
60
|
* Validates CSS selector syntax without executing querySelector (XSS prevention)
|
|
@@ -132,19 +105,25 @@ const validateScrollContainerSelectors = (selectors) => {
|
|
|
132
105
|
const selectorsArray = Array.isArray(selectors) ? selectors : [selectors];
|
|
133
106
|
for (const selector of selectorsArray) {
|
|
134
107
|
if (typeof selector !== 'string' || selector.trim() === '') {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
108
|
+
log('error', 'Invalid scroll container selector', {
|
|
109
|
+
showToClient: true,
|
|
110
|
+
data: {
|
|
111
|
+
selector,
|
|
112
|
+
type: typeof selector,
|
|
113
|
+
isEmpty: selector === '' || (typeof selector === 'string' && selector.trim() === ''),
|
|
114
|
+
},
|
|
139
115
|
});
|
|
140
116
|
throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_SCROLL_CONTAINER_SELECTORS, 'config');
|
|
141
117
|
}
|
|
142
118
|
// Validate CSS selector syntax using regex-based validation (XSS prevention)
|
|
143
119
|
// This validates syntax WITHOUT executing document.querySelector()
|
|
144
120
|
if (!isValidCssSelectorSyntax(selector)) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
121
|
+
log('error', 'Invalid or potentially unsafe CSS selector', {
|
|
122
|
+
showToClient: true,
|
|
123
|
+
data: {
|
|
124
|
+
selector,
|
|
125
|
+
reason: 'Failed security validation',
|
|
126
|
+
},
|
|
148
127
|
});
|
|
149
128
|
throw new AppConfigValidationError('Invalid or potentially unsafe CSS selector', 'config');
|
|
150
129
|
}
|
|
@@ -155,23 +134,34 @@ const validateScrollContainerSelectors = (selectors) => {
|
|
|
155
134
|
* @param integrations - Integrations configuration to validate
|
|
156
135
|
*/
|
|
157
136
|
const validateIntegrations = (integrations) => {
|
|
158
|
-
if (!integrations)
|
|
137
|
+
if (!integrations) {
|
|
159
138
|
return;
|
|
139
|
+
}
|
|
140
|
+
if (integrations.tracelog) {
|
|
141
|
+
if (!integrations.tracelog.projectId ||
|
|
142
|
+
typeof integrations.tracelog.projectId !== 'string' ||
|
|
143
|
+
integrations.tracelog.projectId.trim() === '') {
|
|
144
|
+
throw new IntegrationValidationError(VALIDATION_MESSAGES.INVALID_TRACELOG_PROJECT_ID, 'config');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (integrations.custom) {
|
|
148
|
+
if (!integrations.custom.apiUrl ||
|
|
149
|
+
typeof integrations.custom.apiUrl !== 'string' ||
|
|
150
|
+
integrations.custom.apiUrl.trim() === '') {
|
|
151
|
+
throw new IntegrationValidationError(VALIDATION_MESSAGES.INVALID_CUSTOM_API_URL, 'config');
|
|
152
|
+
}
|
|
153
|
+
if (!integrations.custom.apiUrl.startsWith('http')) {
|
|
154
|
+
throw new IntegrationValidationError('Custom API URL must start with "http"', 'config');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
160
157
|
if (integrations.googleAnalytics) {
|
|
161
158
|
if (!integrations.googleAnalytics.measurementId ||
|
|
162
159
|
typeof integrations.googleAnalytics.measurementId !== 'string' ||
|
|
163
160
|
integrations.googleAnalytics.measurementId.trim() === '') {
|
|
164
|
-
debugLog.clientError('ConfigValidation', 'Invalid Google Analytics measurement ID', {
|
|
165
|
-
provided: integrations.googleAnalytics.measurementId,
|
|
166
|
-
type: typeof integrations.googleAnalytics.measurementId,
|
|
167
|
-
});
|
|
168
161
|
throw new IntegrationValidationError(VALIDATION_MESSAGES.INVALID_GOOGLE_ANALYTICS_ID, 'config');
|
|
169
162
|
}
|
|
170
163
|
const measurementId = integrations.googleAnalytics.measurementId.trim();
|
|
171
164
|
if (!measurementId.match(/^(G-|UA-)/)) {
|
|
172
|
-
debugLog.clientError('ConfigValidation', 'Google Analytics measurement ID must start with "G-" or "UA-"', {
|
|
173
|
-
provided: measurementId,
|
|
174
|
-
});
|
|
175
165
|
throw new IntegrationValidationError('Google Analytics measurement ID must start with "G-" or "UA-"', 'config');
|
|
176
166
|
}
|
|
177
167
|
}
|
|
@@ -185,50 +175,15 @@ const validateIntegrations = (integrations) => {
|
|
|
185
175
|
* @throws {AppConfigValidationError} If other configuration validation fails
|
|
186
176
|
*/
|
|
187
177
|
export const validateAndNormalizeConfig = (config) => {
|
|
188
|
-
// First validate the structure and basic types
|
|
189
178
|
validateAppConfig(config);
|
|
190
|
-
// Normalize string values
|
|
191
179
|
const normalizedConfig = {
|
|
192
180
|
...config,
|
|
193
|
-
|
|
181
|
+
sessionTimeout: config.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT,
|
|
194
182
|
globalMetadata: config.globalMetadata ?? {},
|
|
195
183
|
sensitiveQueryParams: config.sensitiveQueryParams ?? [],
|
|
184
|
+
errorSampling: config.errorSampling ?? 1,
|
|
185
|
+
samplingRate: config.samplingRate ?? 1,
|
|
186
|
+
allowHttp: config.allowHttp ?? false,
|
|
196
187
|
};
|
|
197
|
-
// Validate normalized values - this catches whitespace-only IDs
|
|
198
|
-
if (!normalizedConfig.id) {
|
|
199
|
-
debugLog.clientError('ConfigValidation', 'Project ID is empty after trimming whitespace', {
|
|
200
|
-
originalId: config.id,
|
|
201
|
-
normalizedId: normalizedConfig.id,
|
|
202
|
-
});
|
|
203
|
-
throw new ProjectIdValidationError(VALIDATION_MESSAGES.PROJECT_ID_EMPTY_AFTER_TRIM, 'config');
|
|
204
|
-
}
|
|
205
188
|
return normalizedConfig;
|
|
206
189
|
};
|
|
207
|
-
/**
|
|
208
|
-
* Type guard to check if a JSON response is a valid API config
|
|
209
|
-
* @param json - The JSON to validate
|
|
210
|
-
* @returns True if the JSON is a valid API config
|
|
211
|
-
*/
|
|
212
|
-
export const isValidConfigApiResponse = (json) => {
|
|
213
|
-
try {
|
|
214
|
-
if (typeof json !== 'object' || !json) {
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
const response = json;
|
|
218
|
-
const result = {
|
|
219
|
-
mode: response['mode'] === undefined || [Mode.QA, Mode.DEBUG].includes(response['mode']),
|
|
220
|
-
// Zero is valid for samplingRate (means "sample nothing")
|
|
221
|
-
samplingRate: response['samplingRate'] === undefined ||
|
|
222
|
-
(typeof response['samplingRate'] === 'number' &&
|
|
223
|
-
response['samplingRate'] >= 0 &&
|
|
224
|
-
response['samplingRate'] <= 1),
|
|
225
|
-
tags: response['tags'] === undefined || Array.isArray(response['tags']),
|
|
226
|
-
excludedUrlPaths: response['excludedUrlPaths'] === undefined || Array.isArray(response['excludedUrlPaths']),
|
|
227
|
-
ipExcluded: response['ipExcluded'] === undefined || typeof response['ipExcluded'] === 'boolean',
|
|
228
|
-
};
|
|
229
|
-
return Object.values(result).every(Boolean);
|
|
230
|
-
}
|
|
231
|
-
catch {
|
|
232
|
-
return false;
|
|
233
|
-
}
|
|
234
|
-
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { log } from '../logging.utils';
|
|
1
2
|
import { isValidEventName, isValidMetadata } from './metadata-validations.utils';
|
|
2
|
-
import { debugLog } from '../logging';
|
|
3
3
|
/**
|
|
4
4
|
* Validates a complete event with name and optional metadata
|
|
5
5
|
* @param eventName - The event name to validate
|
|
@@ -9,7 +9,10 @@ import { debugLog } from '../logging';
|
|
|
9
9
|
export const isEventValid = (eventName, metadata) => {
|
|
10
10
|
const nameValidation = isValidEventName(eventName);
|
|
11
11
|
if (!nameValidation.valid) {
|
|
12
|
-
|
|
12
|
+
log('error', 'Event name validation failed', {
|
|
13
|
+
showToClient: true,
|
|
14
|
+
data: { eventName, error: nameValidation.error },
|
|
15
|
+
});
|
|
13
16
|
return nameValidation;
|
|
14
17
|
}
|
|
15
18
|
if (!metadata) {
|
|
@@ -17,9 +20,12 @@ export const isEventValid = (eventName, metadata) => {
|
|
|
17
20
|
}
|
|
18
21
|
const metadataValidation = isValidMetadata(eventName, metadata, 'customEvent');
|
|
19
22
|
if (!metadataValidation.valid) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
log('error', 'Event metadata validation failed', {
|
|
24
|
+
showToClient: true,
|
|
25
|
+
data: {
|
|
26
|
+
eventName,
|
|
27
|
+
error: metadataValidation.error,
|
|
28
|
+
},
|
|
23
29
|
});
|
|
24
30
|
}
|
|
25
31
|
return metadataValidation;
|
|
@@ -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,7 +1,38 @@
|
|
|
1
|
+
import { MAX_NESTED_OBJECT_KEYS } from '../../constants';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
+
* Validates if an item in an array is a valid nested object
|
|
4
|
+
* @param item - The item to validate
|
|
5
|
+
* @returns True if the item is a valid nested object
|
|
6
|
+
*/
|
|
7
|
+
const isValidArrayItem = (item) => {
|
|
8
|
+
if (typeof item === 'string') {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
// Allow objects with primitive fields only (one level deep)
|
|
12
|
+
if (typeof item === 'object' && item !== null && !Array.isArray(item)) {
|
|
13
|
+
const entries = Object.entries(item);
|
|
14
|
+
// Check key count limit
|
|
15
|
+
if (entries.length > MAX_NESTED_OBJECT_KEYS) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
// All values must be primitives (no nested objects or arrays)
|
|
19
|
+
for (const [, value] of entries) {
|
|
20
|
+
if (value === null || value === undefined) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const type = typeof value;
|
|
24
|
+
if (type !== 'string' && type !== 'number' && type !== 'boolean') {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Checks if an object contains only primitive fields, string arrays, or arrays of flat objects
|
|
3
34
|
* @param object - The object to check
|
|
4
|
-
* @returns True if the object contains only
|
|
35
|
+
* @returns True if the object contains only valid fields
|
|
5
36
|
*/
|
|
6
37
|
export const isOnlyPrimitiveFields = (object) => {
|
|
7
38
|
if (typeof object !== 'object' || object === null) {
|
|
@@ -16,8 +47,23 @@ export const isOnlyPrimitiveFields = (object) => {
|
|
|
16
47
|
continue;
|
|
17
48
|
}
|
|
18
49
|
if (Array.isArray(value)) {
|
|
19
|
-
if (
|
|
20
|
-
|
|
50
|
+
if (value.length === 0) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// Determine array type from first item
|
|
54
|
+
const firstItem = value[0];
|
|
55
|
+
const isStringArray = typeof firstItem === 'string';
|
|
56
|
+
// All items must be of the same type (all strings OR all objects)
|
|
57
|
+
if (isStringArray) {
|
|
58
|
+
if (!value.every((item) => typeof item === 'string')) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Must be all objects
|
|
64
|
+
if (!value.every((item) => isValidArrayItem(item))) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
21
67
|
}
|
|
22
68
|
continue;
|
|
23
69
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@tracelog/lib",
|
|
3
3
|
"description": "JavaScript library for web analytics and real-time event tracking",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.6.0",
|
|
6
6
|
"main": "./dist/cjs/public-api.js",
|
|
7
7
|
"module": "./dist/esm/public-api.js",
|
|
8
8
|
"types": "./dist/esm/public-api.d.ts",
|
package/dist/cjs/app.types.d.ts
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
export { Mode, EventType, DeviceType, ScrollDirection, ErrorType, TagConditionOperator, TagLogicalOperator, TagConditionType, } from './types';
|
|
2
|
-
export type { ScrollData, ClickData, CustomEventData, MetadataType, WebVitalsData, ErrorData, PageViewData, UTM, EventLocation, EventData, AppConfig, ApiConfig, ExtendedEventsQueueDto, TagConfig, BaseEventsQueueDto, WebVitalType, SessionEndReason, } from './types';
|
package/dist/cjs/app.types.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TagConditionType = exports.TagLogicalOperator = exports.TagConditionOperator = exports.ErrorType = exports.ScrollDirection = exports.DeviceType = exports.EventType = exports.Mode = void 0;
|
|
4
|
-
var types_1 = require("./types");
|
|
5
|
-
Object.defineProperty(exports, "Mode", { enumerable: true, get: function () { return types_1.Mode; } });
|
|
6
|
-
Object.defineProperty(exports, "EventType", { enumerable: true, get: function () { return types_1.EventType; } });
|
|
7
|
-
Object.defineProperty(exports, "DeviceType", { enumerable: true, get: function () { return types_1.DeviceType; } });
|
|
8
|
-
Object.defineProperty(exports, "ScrollDirection", { enumerable: true, get: function () { return types_1.ScrollDirection; } });
|
|
9
|
-
Object.defineProperty(exports, "ErrorType", { enumerable: true, get: function () { return types_1.ErrorType; } });
|
|
10
|
-
Object.defineProperty(exports, "TagConditionOperator", { enumerable: true, get: function () { return types_1.TagConditionOperator; } });
|
|
11
|
-
Object.defineProperty(exports, "TagLogicalOperator", { enumerable: true, get: function () { return types_1.TagLogicalOperator; } });
|
|
12
|
-
Object.defineProperty(exports, "TagConditionType", { enumerable: true, get: function () { return types_1.TagConditionType; } });
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_API_CONFIG = void 0;
|
|
4
|
-
const config_constants_1 = require("./config.constants");
|
|
5
|
-
/**
|
|
6
|
-
* Default API configuration values
|
|
7
|
-
* Used as fallback when API config is not available or incomplete
|
|
8
|
-
*/
|
|
9
|
-
exports.DEFAULT_API_CONFIG = {
|
|
10
|
-
samplingRate: config_constants_1.DEFAULT_SAMPLING_RATE,
|
|
11
|
-
excludedUrlPaths: [],
|
|
12
|
-
tags: [],
|
|
13
|
-
ipExcluded: false,
|
|
14
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generates API URL for TraceLog service based on project ID
|
|
3
|
-
*
|
|
4
|
-
* Handles two special cases:
|
|
5
|
-
* - 'localhost:8080' or 'localhost:9999' - for local development (generates http://localhost:PORT)
|
|
6
|
-
* - Regular project IDs - generates subdomain URLs via getApiUrl utility
|
|
7
|
-
*
|
|
8
|
-
* @param id Project ID or localhost address
|
|
9
|
-
* @param allowHttp Whether to allow HTTP protocol (default: false)
|
|
10
|
-
* @returns Generated API URL
|
|
11
|
-
* @throws Error if URL generation or validation fails
|
|
12
|
-
*/
|
|
13
|
-
export declare function getApiUrlForProject(id: string, allowHttp?: boolean): string;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getApiUrlForProject = getApiUrlForProject;
|
|
4
|
-
const utils_1 = require("../utils");
|
|
5
|
-
const types_1 = require("../types");
|
|
6
|
-
const logging_1 = require("../utils/logging");
|
|
7
|
-
/**
|
|
8
|
-
* Generates API URL for TraceLog service based on project ID
|
|
9
|
-
*
|
|
10
|
-
* Handles two special cases:
|
|
11
|
-
* - 'localhost:8080' or 'localhost:9999' - for local development (generates http://localhost:PORT)
|
|
12
|
-
* - Regular project IDs - generates subdomain URLs via getApiUrl utility
|
|
13
|
-
*
|
|
14
|
-
* @param id Project ID or localhost address
|
|
15
|
-
* @param allowHttp Whether to allow HTTP protocol (default: false)
|
|
16
|
-
* @returns Generated API URL
|
|
17
|
-
* @throws Error if URL generation or validation fails
|
|
18
|
-
*/
|
|
19
|
-
function getApiUrlForProject(id, allowHttp = false) {
|
|
20
|
-
try {
|
|
21
|
-
// Handle localhost development case (localhost:8080 or localhost:9999)
|
|
22
|
-
if (id === types_1.SpecialProjectId.Localhost || id === types_1.SpecialProjectId.Fail) {
|
|
23
|
-
const url = `http://${id}`;
|
|
24
|
-
if (!(0, utils_1.isValidUrl)(url, true)) {
|
|
25
|
-
throw new Error(`Invalid localhost URL format: ${id}`);
|
|
26
|
-
}
|
|
27
|
-
return url;
|
|
28
|
-
}
|
|
29
|
-
// Handle regular project ID case
|
|
30
|
-
const url = (0, utils_1.getApiUrl)(id, allowHttp);
|
|
31
|
-
if (!(0, utils_1.isValidUrl)(url, allowHttp)) {
|
|
32
|
-
throw new Error(`Generated API URL failed validation: ${url}`);
|
|
33
|
-
}
|
|
34
|
-
return url;
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
logging_1.debugLog.error('ApiManager', 'API URL generation failed', {
|
|
38
|
-
projectId: id,
|
|
39
|
-
allowHttp,
|
|
40
|
-
error: error instanceof Error ? error.message : error,
|
|
41
|
-
});
|
|
42
|
-
throw error;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { AppConfig, ApiConfig, Config } from '../types';
|
|
2
|
-
/**
|
|
3
|
-
* Centralized configuration builder
|
|
4
|
-
* Single source of truth for merging and building final configuration
|
|
5
|
-
*/
|
|
6
|
-
export declare class ConfigBuilder {
|
|
7
|
-
/**
|
|
8
|
-
* Builds final configuration from app config and API config
|
|
9
|
-
* Applies clear precedence: API overrides client, with defaults as fallback
|
|
10
|
-
*/
|
|
11
|
-
static build(appConfig: AppConfig, apiConfig?: ApiConfig): Config;
|
|
12
|
-
/**
|
|
13
|
-
* Resolves session timeout with validation
|
|
14
|
-
* Returns default if undefined or out of valid range
|
|
15
|
-
*/
|
|
16
|
-
private static resolveSessionTimeout;
|
|
17
|
-
/**
|
|
18
|
-
* Resolves sampling rate with validation
|
|
19
|
-
* Priority: API config > app config > default
|
|
20
|
-
*/
|
|
21
|
-
private static resolveSamplingRate;
|
|
22
|
-
/**
|
|
23
|
-
* Resolves error sampling rate based on mode
|
|
24
|
-
* In debug/qa modes: uses provided value or defaults to full sampling (1.0)
|
|
25
|
-
* In production: uses provided value or defaults to 10% sampling (0.1)
|
|
26
|
-
*/
|
|
27
|
-
private static resolveErrorSampling;
|
|
28
|
-
/**
|
|
29
|
-
* Resolves mode with special project ID handling
|
|
30
|
-
* Priority: Special project ID > API mode > app mode
|
|
31
|
-
*/
|
|
32
|
-
private static resolveMode;
|
|
33
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ConfigBuilder = void 0;
|
|
4
|
-
const config_constants_1 = require("../constants/config.constants");
|
|
5
|
-
const types_1 = require("../types");
|
|
6
|
-
const logging_1 = require("../utils/logging");
|
|
7
|
-
/**
|
|
8
|
-
* Centralized configuration builder
|
|
9
|
-
* Single source of truth for merging and building final configuration
|
|
10
|
-
*/
|
|
11
|
-
class ConfigBuilder {
|
|
12
|
-
/**
|
|
13
|
-
* Builds final configuration from app config and API config
|
|
14
|
-
* Applies clear precedence: API overrides client, with defaults as fallback
|
|
15
|
-
*/
|
|
16
|
-
static build(appConfig, apiConfig = {}) {
|
|
17
|
-
// Resolve mode first as it affects other settings (like errorSampling)
|
|
18
|
-
const finalMode = this.resolveMode(appConfig, apiConfig.mode);
|
|
19
|
-
const config = {
|
|
20
|
-
// Core identifiers
|
|
21
|
-
id: appConfig.id,
|
|
22
|
-
// Session configuration
|
|
23
|
-
sessionTimeout: this.resolveSessionTimeout(appConfig.sessionTimeout),
|
|
24
|
-
// Mode configuration (resolved first)
|
|
25
|
-
mode: finalMode,
|
|
26
|
-
// Sampling configuration (depends on mode)
|
|
27
|
-
samplingRate: this.resolveSamplingRate(apiConfig.samplingRate, appConfig.samplingRate),
|
|
28
|
-
errorSampling: this.resolveErrorSampling(appConfig.errorSampling, finalMode),
|
|
29
|
-
// Filtering configuration
|
|
30
|
-
excludedUrlPaths: apiConfig.excludedUrlPaths ?? appConfig.excludedUrlPaths ?? [],
|
|
31
|
-
tags: apiConfig.tags ?? [],
|
|
32
|
-
ipExcluded: apiConfig.ipExcluded ?? false,
|
|
33
|
-
// Client-only configuration
|
|
34
|
-
globalMetadata: appConfig.globalMetadata ?? {},
|
|
35
|
-
scrollContainerSelectors: appConfig.scrollContainerSelectors,
|
|
36
|
-
sensitiveQueryParams: appConfig.sensitiveQueryParams ?? [],
|
|
37
|
-
integrations: appConfig.integrations,
|
|
38
|
-
// Security configuration
|
|
39
|
-
allowHttp: appConfig.allowHttp ?? false,
|
|
40
|
-
};
|
|
41
|
-
logging_1.debugLog.debug('ConfigBuilder', 'Configuration built', {
|
|
42
|
-
projectId: config.id,
|
|
43
|
-
mode: config.mode,
|
|
44
|
-
samplingRate: config.samplingRate,
|
|
45
|
-
errorSampling: config.errorSampling,
|
|
46
|
-
hasTags: !!config.tags?.length,
|
|
47
|
-
hasExclusions: !!config.excludedUrlPaths?.length,
|
|
48
|
-
});
|
|
49
|
-
return config;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Resolves session timeout with validation
|
|
53
|
-
* Returns default if undefined or out of valid range
|
|
54
|
-
*/
|
|
55
|
-
static resolveSessionTimeout(timeout) {
|
|
56
|
-
if (timeout === undefined) {
|
|
57
|
-
return config_constants_1.DEFAULT_SESSION_TIMEOUT;
|
|
58
|
-
}
|
|
59
|
-
if (timeout < config_constants_1.MIN_SESSION_TIMEOUT_MS || timeout > config_constants_1.MAX_SESSION_TIMEOUT_MS) {
|
|
60
|
-
logging_1.debugLog.warn('ConfigBuilder', 'Invalid session timeout, using default', {
|
|
61
|
-
provided: timeout,
|
|
62
|
-
min: config_constants_1.MIN_SESSION_TIMEOUT_MS,
|
|
63
|
-
max: config_constants_1.MAX_SESSION_TIMEOUT_MS,
|
|
64
|
-
default: config_constants_1.DEFAULT_SESSION_TIMEOUT,
|
|
65
|
-
});
|
|
66
|
-
return config_constants_1.DEFAULT_SESSION_TIMEOUT;
|
|
67
|
-
}
|
|
68
|
-
return timeout;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Resolves sampling rate with validation
|
|
72
|
-
* Priority: API config > app config > default
|
|
73
|
-
*/
|
|
74
|
-
static resolveSamplingRate(apiRate, appRate) {
|
|
75
|
-
const rate = apiRate ?? appRate;
|
|
76
|
-
if (rate === undefined) {
|
|
77
|
-
return config_constants_1.DEFAULT_SAMPLING_RATE;
|
|
78
|
-
}
|
|
79
|
-
if (rate < config_constants_1.MIN_SAMPLING_RATE || rate > config_constants_1.MAX_SAMPLING_RATE) {
|
|
80
|
-
logging_1.debugLog.warn('ConfigBuilder', 'Invalid sampling rate, using default', {
|
|
81
|
-
provided: rate,
|
|
82
|
-
default: config_constants_1.DEFAULT_SAMPLING_RATE,
|
|
83
|
-
});
|
|
84
|
-
return config_constants_1.DEFAULT_SAMPLING_RATE;
|
|
85
|
-
}
|
|
86
|
-
return rate;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Resolves error sampling rate based on mode
|
|
90
|
-
* In debug/qa modes: uses provided value or defaults to full sampling (1.0)
|
|
91
|
-
* In production: uses provided value or defaults to 10% sampling (0.1)
|
|
92
|
-
*/
|
|
93
|
-
static resolveErrorSampling(appErrorSampling, apiMode) {
|
|
94
|
-
const isDebugMode = apiMode === types_1.Mode.DEBUG || apiMode === types_1.Mode.QA;
|
|
95
|
-
if (isDebugMode) {
|
|
96
|
-
// In debug mode, respect explicit value or default to full sampling
|
|
97
|
-
return appErrorSampling ?? 1;
|
|
98
|
-
}
|
|
99
|
-
return appErrorSampling ?? 0.1; // Default to 10% sampling in production
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Resolves mode with special project ID handling
|
|
103
|
-
* Priority: Special project ID > API mode > app mode
|
|
104
|
-
*/
|
|
105
|
-
static resolveMode(appConfig, apiMode) {
|
|
106
|
-
// Force DEBUG mode for special project IDs
|
|
107
|
-
if (appConfig.id === types_1.SpecialProjectId.Skip ||
|
|
108
|
-
appConfig.id === types_1.SpecialProjectId.Fail ||
|
|
109
|
-
appConfig.id.toLowerCase().startsWith('skip-')) {
|
|
110
|
-
return types_1.Mode.DEBUG;
|
|
111
|
-
}
|
|
112
|
-
// API mode takes precedence over app mode
|
|
113
|
-
return apiMode ?? appConfig.mode;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
exports.ConfigBuilder = ConfigBuilder;
|