@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
|
@@ -10,12 +10,6 @@ export declare abstract class TraceLogValidationError extends Error {
|
|
|
10
10
|
readonly layer: 'config' | 'app' | 'runtime';
|
|
11
11
|
constructor(message: string, errorCode: string, layer: 'config' | 'app' | 'runtime');
|
|
12
12
|
}
|
|
13
|
-
/**
|
|
14
|
-
* Thrown when project ID validation fails
|
|
15
|
-
*/
|
|
16
|
-
export declare class ProjectIdValidationError extends TraceLogValidationError {
|
|
17
|
-
constructor(message?: string, layer?: 'config' | 'app' | 'runtime');
|
|
18
|
-
}
|
|
19
13
|
/**
|
|
20
14
|
* Thrown when app configuration validation fails
|
|
21
15
|
*/
|
|
@@ -17,14 +17,6 @@ export class TraceLogValidationError extends Error {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Thrown when project ID validation fails
|
|
22
|
-
*/
|
|
23
|
-
export class ProjectIdValidationError extends TraceLogValidationError {
|
|
24
|
-
constructor(message = 'Project ID is required', layer = 'config') {
|
|
25
|
-
super(message, 'PROJECT_ID_INVALID', layer);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
20
|
/**
|
|
29
21
|
* Thrown when app configuration validation fails
|
|
30
22
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DeviceType } from '../../types/device.types';
|
|
2
|
-
import {
|
|
2
|
+
import { log } from '../logging.utils';
|
|
3
3
|
let coarsePointerQuery;
|
|
4
4
|
let noHoverQuery;
|
|
5
5
|
const initMediaQueries = () => {
|
|
@@ -14,22 +14,14 @@ const initMediaQueries = () => {
|
|
|
14
14
|
*/
|
|
15
15
|
export const getDeviceType = () => {
|
|
16
16
|
try {
|
|
17
|
-
debugLog.debug('DeviceDetector', 'Starting device detection');
|
|
18
17
|
const nav = navigator;
|
|
19
18
|
if (nav.userAgentData && typeof nav.userAgentData.mobile === 'boolean') {
|
|
20
|
-
debugLog.debug('DeviceDetector', 'Using modern User-Agent Client Hints API', {
|
|
21
|
-
mobile: nav.userAgentData.mobile,
|
|
22
|
-
platform: nav.userAgentData.platform,
|
|
23
|
-
});
|
|
24
19
|
if (nav.userAgentData.platform && /ipad|tablet/i.test(nav.userAgentData.platform)) {
|
|
25
|
-
debugLog.debug('DeviceDetector', 'Device detected as tablet via platform hint');
|
|
26
20
|
return DeviceType.Tablet;
|
|
27
21
|
}
|
|
28
22
|
const result = nav.userAgentData.mobile ? DeviceType.Mobile : DeviceType.Desktop;
|
|
29
|
-
debugLog.debug('DeviceDetector', 'Device detected via User-Agent hints', { result });
|
|
30
23
|
return result;
|
|
31
24
|
}
|
|
32
|
-
debugLog.debug('DeviceDetector', 'Using fallback detection methods');
|
|
33
25
|
initMediaQueries();
|
|
34
26
|
const width = window.innerWidth;
|
|
35
27
|
const hasCoarsePointer = coarsePointerQuery?.matches ?? false;
|
|
@@ -38,30 +30,16 @@ export const getDeviceType = () => {
|
|
|
38
30
|
const ua = navigator.userAgent.toLowerCase();
|
|
39
31
|
const isMobileUA = /mobile|android|iphone|ipod|blackberry|iemobile|opera mini/.test(ua);
|
|
40
32
|
const isTabletUA = /tablet|ipad|android(?!.*mobile)/.test(ua);
|
|
41
|
-
const detectionData = {
|
|
42
|
-
width,
|
|
43
|
-
hasCoarsePointer,
|
|
44
|
-
hasNoHover,
|
|
45
|
-
hasTouchSupport,
|
|
46
|
-
isMobileUA,
|
|
47
|
-
isTabletUA,
|
|
48
|
-
maxTouchPoints: navigator.maxTouchPoints,
|
|
49
|
-
};
|
|
50
33
|
if (width <= 767 || (isMobileUA && hasTouchSupport)) {
|
|
51
|
-
debugLog.debug('DeviceDetector', 'Device detected as mobile', detectionData);
|
|
52
34
|
return DeviceType.Mobile;
|
|
53
35
|
}
|
|
54
36
|
if ((width >= 768 && width <= 1024) || isTabletUA || (hasCoarsePointer && hasNoHover && hasTouchSupport)) {
|
|
55
|
-
debugLog.debug('DeviceDetector', 'Device detected as tablet', detectionData);
|
|
56
37
|
return DeviceType.Tablet;
|
|
57
38
|
}
|
|
58
|
-
debugLog.debug('DeviceDetector', 'Device detected as desktop', detectionData);
|
|
59
39
|
return DeviceType.Desktop;
|
|
60
40
|
}
|
|
61
41
|
catch (error) {
|
|
62
|
-
|
|
63
|
-
error: error instanceof Error ? error.message : error,
|
|
64
|
-
});
|
|
42
|
+
log('warn', 'Device detection failed, defaulting to desktop', { error });
|
|
65
43
|
return DeviceType.Desktop;
|
|
66
44
|
}
|
|
67
45
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects if QA mode should be active based on URL query parameter or sessionStorage
|
|
3
|
+
*
|
|
4
|
+
* Detection flow:
|
|
5
|
+
* 1. Check if already active in sessionStorage
|
|
6
|
+
* 2. Check for ?tlog_mode=qa query parameter
|
|
7
|
+
* 3. If found in URL:
|
|
8
|
+
* - Persist to sessionStorage
|
|
9
|
+
* - Clean param from URL
|
|
10
|
+
*
|
|
11
|
+
* @returns True if QA mode is active, false otherwise
|
|
12
|
+
*/
|
|
13
|
+
export declare const detectQaMode: () => boolean;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { QA_MODE_KEY } from '@/constants';
|
|
2
|
+
import { log } from '../logging.utils';
|
|
3
|
+
const QA_MODE_PARAM = 'tlog_mode';
|
|
4
|
+
const QA_MODE_VALUE = 'qa';
|
|
5
|
+
/**
|
|
6
|
+
* Detects if QA mode should be active based on URL query parameter or sessionStorage
|
|
7
|
+
*
|
|
8
|
+
* Detection flow:
|
|
9
|
+
* 1. Check if already active in sessionStorage
|
|
10
|
+
* 2. Check for ?tlog_mode=qa query parameter
|
|
11
|
+
* 3. If found in URL:
|
|
12
|
+
* - Persist to sessionStorage
|
|
13
|
+
* - Clean param from URL
|
|
14
|
+
*
|
|
15
|
+
* @returns True if QA mode is active, false otherwise
|
|
16
|
+
*/
|
|
17
|
+
export const detectQaMode = () => {
|
|
18
|
+
const stored = sessionStorage.getItem(QA_MODE_KEY);
|
|
19
|
+
if (stored === 'true') {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
const params = new URLSearchParams(window.location.search);
|
|
23
|
+
const modeParam = params.get(QA_MODE_PARAM);
|
|
24
|
+
const isQaMode = modeParam === QA_MODE_VALUE;
|
|
25
|
+
if (isQaMode) {
|
|
26
|
+
sessionStorage.setItem(QA_MODE_KEY, 'true');
|
|
27
|
+
params.delete(QA_MODE_PARAM);
|
|
28
|
+
const newSearch = params.toString();
|
|
29
|
+
const newUrl = `${window.location.pathname}${newSearch ? '?' + newSearch : ''}${window.location.hash}`;
|
|
30
|
+
try {
|
|
31
|
+
window.history.replaceState({}, '', newUrl);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
log('warn', 'History API not available, cannot replace URL', { error });
|
|
35
|
+
}
|
|
36
|
+
console.log('%c[TraceLog] QA Mode ACTIVE', 'background: #ff9800; color: white; font-weight: bold; padding: 2px 8px; border-radius: 3px;');
|
|
37
|
+
}
|
|
38
|
+
return isQaMode;
|
|
39
|
+
};
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import { UTM_PARAMS } from '../../constants';
|
|
2
|
-
import { debugLog } from '../logging';
|
|
3
2
|
/**
|
|
4
3
|
* Extracts UTM parameters from the current URL
|
|
5
4
|
* @returns UTM parameters object or undefined if none found
|
|
6
5
|
*/
|
|
7
6
|
export const getUTMParameters = () => {
|
|
8
|
-
debugLog.debug('UTMParams', 'Extracting UTM parameters from URL', {
|
|
9
|
-
url: window.location.href,
|
|
10
|
-
search: window.location.search,
|
|
11
|
-
});
|
|
12
7
|
const urlParams = new URLSearchParams(window.location.search);
|
|
13
8
|
const utmParams = {};
|
|
14
9
|
UTM_PARAMS.forEach((param) => {
|
|
@@ -16,18 +11,8 @@ export const getUTMParameters = () => {
|
|
|
16
11
|
if (value) {
|
|
17
12
|
const key = param.split('utm_')[1];
|
|
18
13
|
utmParams[key] = value;
|
|
19
|
-
debugLog.debug('UTMParams', 'Found UTM parameter', { param, key, value });
|
|
20
14
|
}
|
|
21
15
|
});
|
|
22
16
|
const result = Object.keys(utmParams).length ? utmParams : undefined;
|
|
23
|
-
if (result) {
|
|
24
|
-
debugLog.debug('UTMParams', 'UTM parameters extracted successfully', {
|
|
25
|
-
parameterCount: Object.keys(result).length,
|
|
26
|
-
parameters: Object.keys(result),
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
debugLog.debug('UTMParams', 'No UTM parameters found in URL');
|
|
31
|
-
}
|
|
32
17
|
return result;
|
|
33
18
|
};
|
|
@@ -3,3 +3,16 @@
|
|
|
3
3
|
* @returns A UUID string
|
|
4
4
|
*/
|
|
5
5
|
export declare const generateUUID: () => string;
|
|
6
|
+
/**
|
|
7
|
+
* Generates a unique event ID optimized for high-frequency event tracking
|
|
8
|
+
*
|
|
9
|
+
* Uses a simple hybrid approach:
|
|
10
|
+
* - Timestamp for temporal ordering
|
|
11
|
+
* - Random component for uniqueness across tabs/processes
|
|
12
|
+
*
|
|
13
|
+
* Format: {timestamp}-{random}
|
|
14
|
+
* Example: "1704067200000-a3f9c2b1"
|
|
15
|
+
*
|
|
16
|
+
* @returns Unique event ID string
|
|
17
|
+
*/
|
|
18
|
+
export declare const generateEventId: () => string;
|
|
@@ -14,3 +14,38 @@ export const generateUUID = () => {
|
|
|
14
14
|
return v.toString(16);
|
|
15
15
|
});
|
|
16
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* Generates a unique event ID optimized for high-frequency event tracking
|
|
19
|
+
*
|
|
20
|
+
* Uses a simple hybrid approach:
|
|
21
|
+
* - Timestamp for temporal ordering
|
|
22
|
+
* - Random component for uniqueness across tabs/processes
|
|
23
|
+
*
|
|
24
|
+
* Format: {timestamp}-{random}
|
|
25
|
+
* Example: "1704067200000-a3f9c2b1"
|
|
26
|
+
*
|
|
27
|
+
* @returns Unique event ID string
|
|
28
|
+
*/
|
|
29
|
+
export const generateEventId = () => {
|
|
30
|
+
const timestamp = Date.now();
|
|
31
|
+
// Generate 8 random hex chars (32 bits entropy)
|
|
32
|
+
let random = '';
|
|
33
|
+
try {
|
|
34
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
35
|
+
const bytes = crypto.getRandomValues(new Uint8Array(4));
|
|
36
|
+
if (bytes) {
|
|
37
|
+
random = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// crypto failed, use fallback
|
|
43
|
+
}
|
|
44
|
+
// Fallback to Math.random if crypto unavailable
|
|
45
|
+
if (!random) {
|
|
46
|
+
random = Math.floor(Math.random() * 0xffffffff)
|
|
47
|
+
.toString(16)
|
|
48
|
+
.padStart(8, '0');
|
|
49
|
+
}
|
|
50
|
+
return `${timestamp}-${random}`;
|
|
51
|
+
};
|
package/dist/esm/utils/index.js
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const formatLogMsg: (msg: string, error?: unknown) => string;
|
|
2
|
+
/**
|
|
3
|
+
* Safe logging utility that respects production environment
|
|
4
|
+
*
|
|
5
|
+
* @param type - Log level (info, warn, error, debug)
|
|
6
|
+
* @param msg - Message to log
|
|
7
|
+
* @param extra - Optional extra data
|
|
8
|
+
*
|
|
9
|
+
* Production behavior:
|
|
10
|
+
* - debug: Never logged in production
|
|
11
|
+
* - info: Only logged if showToClient=true
|
|
12
|
+
* - warn: Always logged (important for debugging production issues)
|
|
13
|
+
* - error: Always logged
|
|
14
|
+
* - Stack traces are sanitized
|
|
15
|
+
* - Data objects are sanitized
|
|
16
|
+
*/
|
|
17
|
+
export declare const log: (type: "info" | "warn" | "error" | "debug", msg: string, extra?: {
|
|
18
|
+
error?: unknown;
|
|
19
|
+
data?: Record<string, unknown>;
|
|
20
|
+
showToClient?: boolean;
|
|
21
|
+
}) => void;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export const formatLogMsg = (msg, error) => {
|
|
2
|
+
if (error) {
|
|
3
|
+
// In production, sanitize error messages to avoid exposing sensitive paths
|
|
4
|
+
if (process.env.NODE_ENV !== 'dev' && error instanceof Error) {
|
|
5
|
+
// Remove file paths and line numbers from error messages
|
|
6
|
+
const sanitizedMessage = error.message.replace(/\s+at\s+.*$/gm, '').replace(/\(.*?:\d+:\d+\)/g, '');
|
|
7
|
+
return `[TraceLog] ${msg}: ${sanitizedMessage}`;
|
|
8
|
+
}
|
|
9
|
+
return `[TraceLog] ${msg}: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
10
|
+
}
|
|
11
|
+
return `[TraceLog] ${msg}`;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Safe logging utility that respects production environment
|
|
15
|
+
*
|
|
16
|
+
* @param type - Log level (info, warn, error, debug)
|
|
17
|
+
* @param msg - Message to log
|
|
18
|
+
* @param extra - Optional extra data
|
|
19
|
+
*
|
|
20
|
+
* Production behavior:
|
|
21
|
+
* - debug: Never logged in production
|
|
22
|
+
* - info: Only logged if showToClient=true
|
|
23
|
+
* - warn: Always logged (important for debugging production issues)
|
|
24
|
+
* - error: Always logged
|
|
25
|
+
* - Stack traces are sanitized
|
|
26
|
+
* - Data objects are sanitized
|
|
27
|
+
*/
|
|
28
|
+
export const log = (type, msg, extra) => {
|
|
29
|
+
const { error, data, showToClient = false } = extra ?? {};
|
|
30
|
+
const formattedMsg = error ? formatLogMsg(msg, error) : `[TraceLog] ${msg}`;
|
|
31
|
+
const method = type === 'error' ? 'error' : type === 'warn' ? 'warn' : 'log';
|
|
32
|
+
// Production logging strategy:
|
|
33
|
+
// - Development: Log everything
|
|
34
|
+
// - Production:
|
|
35
|
+
// - debug: never logged
|
|
36
|
+
// - info: only if showToClient=true
|
|
37
|
+
// - warn: always logged (critical for debugging)
|
|
38
|
+
// - error: always logged
|
|
39
|
+
const isProduction = process.env.NODE_ENV !== 'dev';
|
|
40
|
+
if (isProduction) {
|
|
41
|
+
// Never log debug in production
|
|
42
|
+
if (type === 'debug') {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Log info only if explicitly flagged
|
|
46
|
+
if (type === 'info' && !showToClient) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// warn and error always logged in production
|
|
50
|
+
}
|
|
51
|
+
// In production, sanitize data to avoid exposing sensitive information
|
|
52
|
+
if (isProduction && data !== undefined) {
|
|
53
|
+
const sanitizedData = sanitizeLogData(data);
|
|
54
|
+
console[method](formattedMsg, sanitizedData);
|
|
55
|
+
}
|
|
56
|
+
else if (data !== undefined) {
|
|
57
|
+
console[method](formattedMsg, data);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console[method](formattedMsg);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Sanitizes log data in production to prevent sensitive information leakage
|
|
65
|
+
* Simple approach: redact sensitive keys only
|
|
66
|
+
*/
|
|
67
|
+
const sanitizeLogData = (data) => {
|
|
68
|
+
const sanitized = {};
|
|
69
|
+
const sensitiveKeys = ['token', 'password', 'secret', 'key', 'apikey', 'api_key', 'sessionid', 'session_id'];
|
|
70
|
+
for (const [key, value] of Object.entries(data)) {
|
|
71
|
+
const lowerKey = key.toLowerCase();
|
|
72
|
+
// Redact sensitive keys
|
|
73
|
+
if (sensitiveKeys.some((sensitiveKey) => lowerKey.includes(sensitiveKey))) {
|
|
74
|
+
sanitized[key] = '[REDACTED]';
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
sanitized[key] = value;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return sanitized;
|
|
81
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { Config } from '@/types';
|
|
1
2
|
/**
|
|
2
3
|
* Generates an API URL based on project ID and current domain
|
|
3
4
|
* @param id - The project ID
|
|
4
5
|
* @returns The generated API URL
|
|
5
6
|
*/
|
|
6
|
-
export declare const getApiUrl: (
|
|
7
|
+
export declare const getApiUrl: (config: Config) => string;
|
|
7
8
|
/**
|
|
8
9
|
* Normalizes a URL by removing sensitive query parameters
|
|
9
10
|
* @param url - The URL to normalize
|
|
@@ -11,10 +12,3 @@ export declare const getApiUrl: (id: string, allowHttp?: boolean) => string;
|
|
|
11
12
|
* @returns The normalized URL
|
|
12
13
|
*/
|
|
13
14
|
export declare const normalizeUrl: (url: string, sensitiveQueryParams?: string[]) => string;
|
|
14
|
-
/**
|
|
15
|
-
* Checks if a URL path should be excluded from tracking
|
|
16
|
-
* @param url - The URL to check
|
|
17
|
-
* @param excludedPaths - Array of patterns to match against
|
|
18
|
-
* @returns True if the URL should be excluded
|
|
19
|
-
*/
|
|
20
|
-
export declare const isUrlPathExcluded: (url: string, excludedPaths?: string[]) => boolean;
|
|
@@ -1,30 +1,53 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { log } from '../logging.utils';
|
|
2
|
+
/**
|
|
3
|
+
* Validates if a URL is valid and optionally allows HTTP URLs
|
|
4
|
+
* @param url - The URL to validate
|
|
5
|
+
* @param allowHttp - Whether to allow HTTP URLs (default: false)
|
|
6
|
+
* @returns True if the URL is valid, false otherwise
|
|
7
|
+
*/
|
|
8
|
+
const isValidUrl = (url, allowHttp = false) => {
|
|
9
|
+
try {
|
|
10
|
+
const parsed = new URL(url);
|
|
11
|
+
const isHttps = parsed.protocol === 'https:';
|
|
12
|
+
const isHttp = parsed.protocol === 'http:';
|
|
13
|
+
return isHttps || (allowHttp && isHttp);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
3
19
|
/**
|
|
4
20
|
* Generates an API URL based on project ID and current domain
|
|
5
21
|
* @param id - The project ID
|
|
6
22
|
* @returns The generated API URL
|
|
7
23
|
*/
|
|
8
|
-
export const getApiUrl = (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
24
|
+
export const getApiUrl = (config) => {
|
|
25
|
+
if (config.integrations?.tracelog?.projectId) {
|
|
26
|
+
const url = new URL(window.location.href);
|
|
27
|
+
const host = url.hostname;
|
|
28
|
+
const parts = host.split('.');
|
|
29
|
+
if (parts.length === 0) {
|
|
30
|
+
throw new Error('Invalid URL');
|
|
31
|
+
}
|
|
32
|
+
const projectId = config.integrations.tracelog.projectId;
|
|
33
|
+
const cleanDomain = parts.slice(-2).join('.');
|
|
34
|
+
const apiUrl = `https://${projectId}.${cleanDomain}`;
|
|
35
|
+
const isValid = isValidUrl(apiUrl);
|
|
36
|
+
if (!isValid) {
|
|
37
|
+
throw new Error('Invalid URL');
|
|
38
|
+
}
|
|
39
|
+
return apiUrl;
|
|
15
40
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
25
|
-
throw new Error('Invalid URL');
|
|
41
|
+
if (config.integrations?.custom?.apiUrl) {
|
|
42
|
+
const apiUrl = config.integrations.custom.apiUrl;
|
|
43
|
+
const allowHttp = config.integrations?.custom?.allowHttp ?? false;
|
|
44
|
+
const isValid = isValidUrl(apiUrl, allowHttp);
|
|
45
|
+
if (!isValid) {
|
|
46
|
+
throw new Error('Invalid URL');
|
|
47
|
+
}
|
|
48
|
+
return apiUrl;
|
|
26
49
|
}
|
|
27
|
-
return
|
|
50
|
+
return '';
|
|
28
51
|
};
|
|
29
52
|
/**
|
|
30
53
|
* Normalizes a URL by removing sensitive query parameters
|
|
@@ -36,7 +59,6 @@ export const normalizeUrl = (url, sensitiveQueryParams = []) => {
|
|
|
36
59
|
try {
|
|
37
60
|
const urlObject = new URL(url);
|
|
38
61
|
const searchParams = urlObject.searchParams;
|
|
39
|
-
const originalParamCount = Array.from(searchParams.keys()).length;
|
|
40
62
|
let hasChanged = false;
|
|
41
63
|
const removedParams = [];
|
|
42
64
|
sensitiveQueryParams.forEach((param) => {
|
|
@@ -46,13 +68,6 @@ export const normalizeUrl = (url, sensitiveQueryParams = []) => {
|
|
|
46
68
|
removedParams.push(param);
|
|
47
69
|
}
|
|
48
70
|
});
|
|
49
|
-
if (hasChanged) {
|
|
50
|
-
debugLog.debug('URLUtils', 'Sensitive parameters removed from URL', {
|
|
51
|
-
removedParams,
|
|
52
|
-
originalParamCount,
|
|
53
|
-
finalParamCount: Array.from(searchParams.keys()).length,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
71
|
if (!hasChanged && url.includes('?')) {
|
|
57
72
|
return url;
|
|
58
73
|
}
|
|
@@ -61,66 +76,7 @@ export const normalizeUrl = (url, sensitiveQueryParams = []) => {
|
|
|
61
76
|
return result;
|
|
62
77
|
}
|
|
63
78
|
catch (error) {
|
|
64
|
-
|
|
65
|
-
url: url.slice(0, 100),
|
|
66
|
-
error: error instanceof Error ? error.message : error,
|
|
67
|
-
});
|
|
79
|
+
log('warn', 'URL normalization failed, returning original', { error, data: { url: url.slice(0, 100) } });
|
|
68
80
|
return url;
|
|
69
81
|
}
|
|
70
82
|
};
|
|
71
|
-
/**
|
|
72
|
-
* Checks if a URL path should be excluded from tracking
|
|
73
|
-
* @param url - The URL to check
|
|
74
|
-
* @param excludedPaths - Array of patterns to match against
|
|
75
|
-
* @returns True if the URL should be excluded
|
|
76
|
-
*/
|
|
77
|
-
export const isUrlPathExcluded = (url, excludedPaths = []) => {
|
|
78
|
-
if (excludedPaths.length === 0) {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
let path;
|
|
82
|
-
try {
|
|
83
|
-
const parsedUrl = new URL(url, window.location.origin);
|
|
84
|
-
path = parsedUrl.pathname + (parsedUrl.hash ?? '');
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
debugLog.warn('URLUtils', 'Failed to parse URL for path exclusion check', {
|
|
88
|
-
url: url.slice(0, 100),
|
|
89
|
-
error: error instanceof Error ? error.message : error,
|
|
90
|
-
});
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
const isRegularExpression = (value) => typeof value === 'object' && value !== undefined && typeof value.test === 'function';
|
|
94
|
-
const escapeRegexString = (string_) => string_.replaceAll(/[$()*+.?[\\\]^{|}]/g, '\\$&');
|
|
95
|
-
const wildcardToRegex = (string_) => new RegExp('^' +
|
|
96
|
-
string_
|
|
97
|
-
.split('*')
|
|
98
|
-
.map((element) => escapeRegexString(element))
|
|
99
|
-
.join('.+') +
|
|
100
|
-
'$');
|
|
101
|
-
const matchedPattern = excludedPaths.find((pattern) => {
|
|
102
|
-
try {
|
|
103
|
-
if (isRegularExpression(pattern)) {
|
|
104
|
-
const matches = pattern.test(path);
|
|
105
|
-
return matches;
|
|
106
|
-
}
|
|
107
|
-
if (pattern.includes('*')) {
|
|
108
|
-
const regex = wildcardToRegex(pattern);
|
|
109
|
-
const matches = regex.test(path);
|
|
110
|
-
return matches;
|
|
111
|
-
}
|
|
112
|
-
const matches = pattern === path;
|
|
113
|
-
return matches;
|
|
114
|
-
}
|
|
115
|
-
catch (error) {
|
|
116
|
-
debugLog.warn('URLUtils', 'Error testing exclusion pattern', {
|
|
117
|
-
pattern,
|
|
118
|
-
path,
|
|
119
|
-
error: error instanceof Error ? error.message : error,
|
|
120
|
-
});
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
const isExcluded = !!matchedPattern;
|
|
125
|
-
return isExcluded;
|
|
126
|
-
};
|
|
@@ -1,22 +1,10 @@
|
|
|
1
|
-
import { MetadataType
|
|
1
|
+
import { MetadataType } from '../../types';
|
|
2
2
|
/**
|
|
3
3
|
* Sanitizes a string value to prevent XSS attacks
|
|
4
4
|
* @param value - The string to sanitize
|
|
5
5
|
* @returns The sanitized string
|
|
6
6
|
*/
|
|
7
7
|
export declare const sanitizeString: (value: string) => string;
|
|
8
|
-
/**
|
|
9
|
-
* Sanitizes a path string for route exclusion checks
|
|
10
|
-
* @param value - The path string to sanitize
|
|
11
|
-
* @returns The sanitized path string
|
|
12
|
-
*/
|
|
13
|
-
export declare const sanitizePathString: (value: string) => string;
|
|
14
|
-
/**
|
|
15
|
-
* Sanitizes API configuration data with strict validation
|
|
16
|
-
* @param data - The API config data to sanitize
|
|
17
|
-
* @returns The sanitized API config
|
|
18
|
-
*/
|
|
19
|
-
export declare const sanitizeApiConfig: (data: unknown) => ApiConfig;
|
|
20
8
|
/**
|
|
21
9
|
* Sanitizes user metadata for custom events
|
|
22
10
|
* @param metadata - The metadata to sanitize
|