@tracelog/lib 0.0.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/LICENSE +21 -0
- package/README.md +217 -0
- package/dist/browser/tracelog.js +4040 -0
- package/dist/browser/web-vitals-CCnqwnC8.mjs +198 -0
- package/dist/cjs/api.d.ts +46 -0
- package/dist/cjs/api.js +224 -0
- package/dist/cjs/app.constants.d.ts +1 -0
- package/dist/cjs/app.constants.js +5 -0
- package/dist/cjs/app.d.ts +59 -0
- package/dist/cjs/app.js +272 -0
- package/dist/cjs/app.types.d.ts +6 -0
- package/dist/cjs/app.types.js +22 -0
- package/dist/cjs/constants/api.constants.d.ts +4 -0
- package/dist/cjs/constants/api.constants.js +18 -0
- package/dist/cjs/constants/browser.constants.d.ts +3 -0
- package/dist/cjs/constants/browser.constants.js +41 -0
- package/dist/cjs/constants/index.d.ts +8 -0
- package/dist/cjs/constants/index.js +24 -0
- package/dist/cjs/constants/initialization.constants.d.ts +40 -0
- package/dist/cjs/constants/initialization.constants.js +48 -0
- package/dist/cjs/constants/limits.constants.d.ts +25 -0
- package/dist/cjs/constants/limits.constants.js +40 -0
- package/dist/cjs/constants/security.constants.d.ts +1 -0
- package/dist/cjs/constants/security.constants.js +12 -0
- package/dist/cjs/constants/storage.constants.d.ts +9 -0
- package/dist/cjs/constants/storage.constants.js +22 -0
- package/dist/cjs/constants/timing.constants.d.ts +22 -0
- package/dist/cjs/constants/timing.constants.js +34 -0
- package/dist/cjs/constants/validation.constants.d.ts +13 -0
- package/dist/cjs/constants/validation.constants.js +31 -0
- package/dist/cjs/handlers/click.handler.d.ts +17 -0
- package/dist/cjs/handlers/click.handler.js +199 -0
- package/dist/cjs/handlers/error.handler.d.ts +15 -0
- package/dist/cjs/handlers/error.handler.js +97 -0
- package/dist/cjs/handlers/network.handler.d.ts +16 -0
- package/dist/cjs/handlers/network.handler.js +136 -0
- package/dist/cjs/handlers/page-view.handler.d.ts +15 -0
- package/dist/cjs/handlers/page-view.handler.js +83 -0
- package/dist/cjs/handlers/performance.handler.d.ts +19 -0
- package/dist/cjs/handlers/performance.handler.js +255 -0
- package/dist/cjs/handlers/scroll.handler.d.ts +16 -0
- package/dist/cjs/handlers/scroll.handler.js +138 -0
- package/dist/cjs/handlers/session.handler.d.ts +29 -0
- package/dist/cjs/handlers/session.handler.js +357 -0
- package/dist/cjs/integrations/google-analytics.integration.d.ts +18 -0
- package/dist/cjs/integrations/google-analytics.integration.js +159 -0
- package/dist/cjs/listeners/activity-listener-manager.d.ts +8 -0
- package/dist/cjs/listeners/activity-listener-manager.js +32 -0
- package/dist/cjs/listeners/index.d.ts +6 -0
- package/dist/cjs/listeners/index.js +14 -0
- package/dist/cjs/listeners/input-listener-managers.d.ts +15 -0
- package/dist/cjs/listeners/input-listener-managers.js +58 -0
- package/dist/cjs/listeners/listeners.types.d.ts +4 -0
- package/dist/cjs/listeners/listeners.types.js +2 -0
- package/dist/cjs/listeners/touch-listener-manager.d.ts +10 -0
- package/dist/cjs/listeners/touch-listener-manager.js +56 -0
- package/dist/cjs/listeners/unload-listener-manager.d.ts +8 -0
- package/dist/cjs/listeners/unload-listener-manager.js +30 -0
- package/dist/cjs/listeners/visibility-listener-manager.d.ts +12 -0
- package/dist/cjs/listeners/visibility-listener-manager.js +83 -0
- package/dist/cjs/managers/api.manager.d.ts +3 -0
- package/dist/cjs/managers/api.manager.js +14 -0
- package/dist/cjs/managers/config.manager.d.ts +7 -0
- package/dist/cjs/managers/config.manager.js +94 -0
- package/dist/cjs/managers/cross-tab-session.manager.d.ts +170 -0
- package/dist/cjs/managers/cross-tab-session.manager.js +730 -0
- package/dist/cjs/managers/event.manager.d.ts +61 -0
- package/dist/cjs/managers/event.manager.js +508 -0
- package/dist/cjs/managers/sampling.manager.d.ts +8 -0
- package/dist/cjs/managers/sampling.manager.js +53 -0
- package/dist/cjs/managers/sender.manager.d.ts +46 -0
- package/dist/cjs/managers/sender.manager.js +304 -0
- package/dist/cjs/managers/session-recovery.manager.d.ts +65 -0
- package/dist/cjs/managers/session-recovery.manager.js +237 -0
- package/dist/cjs/managers/session.manager.d.ts +72 -0
- package/dist/cjs/managers/session.manager.js +587 -0
- package/dist/cjs/managers/state.manager.d.ts +5 -0
- package/dist/cjs/managers/state.manager.js +23 -0
- package/dist/cjs/managers/storage.manager.d.ts +10 -0
- package/dist/cjs/managers/storage.manager.js +81 -0
- package/dist/cjs/managers/tags.manager.d.ts +12 -0
- package/dist/cjs/managers/tags.manager.js +289 -0
- package/dist/cjs/managers/user.manager.d.ts +7 -0
- package/dist/cjs/managers/user.manager.js +22 -0
- package/dist/cjs/public-api.d.ts +1 -0
- package/dist/cjs/public-api.js +37 -0
- package/dist/cjs/types/api.types.d.ts +21 -0
- package/dist/cjs/types/api.types.js +25 -0
- package/dist/cjs/types/common.types.d.ts +1 -0
- package/dist/cjs/types/common.types.js +2 -0
- package/dist/cjs/types/config.types.d.ts +104 -0
- package/dist/cjs/types/config.types.js +2 -0
- package/dist/cjs/types/device.types.d.ts +6 -0
- package/dist/cjs/types/device.types.js +10 -0
- package/dist/cjs/types/event.types.d.ts +104 -0
- package/dist/cjs/types/event.types.js +25 -0
- package/dist/cjs/types/index.d.ts +13 -0
- package/dist/cjs/types/index.js +29 -0
- package/dist/cjs/types/log.types.d.ts +4 -0
- package/dist/cjs/types/log.types.js +2 -0
- package/dist/cjs/types/mode.types.d.ts +7 -0
- package/dist/cjs/types/mode.types.js +11 -0
- package/dist/cjs/types/queue.types.d.ts +23 -0
- package/dist/cjs/types/queue.types.js +2 -0
- package/dist/cjs/types/session.types.d.ts +65 -0
- package/dist/cjs/types/session.types.js +2 -0
- package/dist/cjs/types/state.types.d.ts +12 -0
- package/dist/cjs/types/state.types.js +2 -0
- package/dist/cjs/types/tag.types.d.ts +43 -0
- package/dist/cjs/types/tag.types.js +31 -0
- package/dist/cjs/types/validation-error.types.d.ts +42 -0
- package/dist/cjs/types/validation-error.types.js +68 -0
- package/dist/cjs/types/web-vitals.types.d.ts +6 -0
- package/dist/cjs/types/web-vitals.types.js +2 -0
- package/dist/cjs/types/window.types.d.ts +17 -0
- package/dist/cjs/types/window.types.js +2 -0
- package/dist/cjs/utils/browser/device-detector.utils.d.ts +6 -0
- package/dist/cjs/utils/browser/device-detector.utils.js +71 -0
- package/dist/cjs/utils/browser/index.d.ts +2 -0
- package/dist/cjs/utils/browser/index.js +18 -0
- package/dist/cjs/utils/browser/utm-params.utils.d.ts +6 -0
- package/dist/cjs/utils/browser/utm-params.utils.js +37 -0
- package/dist/cjs/utils/data/index.d.ts +1 -0
- package/dist/cjs/utils/data/index.js +17 -0
- package/dist/cjs/utils/data/uuid.utils.d.ts +5 -0
- package/dist/cjs/utils/data/uuid.utils.js +18 -0
- package/dist/cjs/utils/index.d.ts +6 -0
- package/dist/cjs/utils/index.js +22 -0
- package/dist/cjs/utils/logging/debug-logger.utils.d.ts +56 -0
- package/dist/cjs/utils/logging/debug-logger.utils.js +139 -0
- package/dist/cjs/utils/logging/index.d.ts +1 -0
- package/dist/cjs/utils/logging/index.js +5 -0
- package/dist/cjs/utils/network/index.d.ts +1 -0
- package/dist/cjs/utils/network/index.js +17 -0
- package/dist/cjs/utils/network/url.utils.d.ts +20 -0
- package/dist/cjs/utils/network/url.utils.js +172 -0
- package/dist/cjs/utils/security/index.d.ts +1 -0
- package/dist/cjs/utils/security/index.js +17 -0
- package/dist/cjs/utils/security/sanitize.utils.d.ts +32 -0
- package/dist/cjs/utils/security/sanitize.utils.js +319 -0
- package/dist/cjs/utils/validations/config-validations.utils.d.ts +42 -0
- package/dist/cjs/utils/validations/config-validations.utils.js +297 -0
- package/dist/cjs/utils/validations/event-validations.utils.d.ts +12 -0
- package/dist/cjs/utils/validations/event-validations.utils.js +30 -0
- package/dist/cjs/utils/validations/index.d.ts +5 -0
- package/dist/cjs/utils/validations/index.js +21 -0
- package/dist/cjs/utils/validations/metadata-validations.utils.d.ts +22 -0
- package/dist/cjs/utils/validations/metadata-validations.utils.js +115 -0
- package/dist/cjs/utils/validations/type-guards.utils.d.ts +6 -0
- package/dist/cjs/utils/validations/type-guards.utils.js +31 -0
- package/dist/cjs/utils/validations/url-validations.utils.d.ts +15 -0
- package/dist/cjs/utils/validations/url-validations.utils.js +47 -0
- package/dist/esm/api.d.ts +46 -0
- package/dist/esm/api.js +183 -0
- package/dist/esm/app.constants.d.ts +1 -0
- package/dist/esm/app.constants.js +1 -0
- package/dist/esm/app.d.ts +59 -0
- package/dist/esm/app.js +268 -0
- package/dist/esm/app.types.d.ts +6 -0
- package/dist/esm/app.types.js +6 -0
- package/dist/esm/constants/api.constants.d.ts +4 -0
- package/dist/esm/constants/api.constants.js +14 -0
- package/dist/esm/constants/browser.constants.d.ts +3 -0
- package/dist/esm/constants/browser.constants.js +38 -0
- package/dist/esm/constants/index.d.ts +8 -0
- package/dist/esm/constants/index.js +8 -0
- package/dist/esm/constants/initialization.constants.d.ts +40 -0
- package/dist/esm/constants/initialization.constants.js +45 -0
- package/dist/esm/constants/limits.constants.d.ts +25 -0
- package/dist/esm/constants/limits.constants.js +37 -0
- package/dist/esm/constants/security.constants.d.ts +1 -0
- package/dist/esm/constants/security.constants.js +9 -0
- package/dist/esm/constants/storage.constants.d.ts +9 -0
- package/dist/esm/constants/storage.constants.js +11 -0
- package/dist/esm/constants/timing.constants.d.ts +22 -0
- package/dist/esm/constants/timing.constants.js +31 -0
- package/dist/esm/constants/validation.constants.d.ts +13 -0
- package/dist/esm/constants/validation.constants.js +28 -0
- package/dist/esm/handlers/click.handler.d.ts +17 -0
- package/dist/esm/handlers/click.handler.js +195 -0
- package/dist/esm/handlers/error.handler.d.ts +15 -0
- package/dist/esm/handlers/error.handler.js +93 -0
- package/dist/esm/handlers/network.handler.d.ts +16 -0
- package/dist/esm/handlers/network.handler.js +132 -0
- package/dist/esm/handlers/page-view.handler.d.ts +15 -0
- package/dist/esm/handlers/page-view.handler.js +79 -0
- package/dist/esm/handlers/performance.handler.d.ts +19 -0
- package/dist/esm/handlers/performance.handler.js +218 -0
- package/dist/esm/handlers/scroll.handler.d.ts +16 -0
- package/dist/esm/handlers/scroll.handler.js +134 -0
- package/dist/esm/handlers/session.handler.d.ts +29 -0
- package/dist/esm/handlers/session.handler.js +353 -0
- package/dist/esm/integrations/google-analytics.integration.d.ts +18 -0
- package/dist/esm/integrations/google-analytics.integration.js +155 -0
- package/dist/esm/listeners/activity-listener-manager.d.ts +8 -0
- package/dist/esm/listeners/activity-listener-manager.js +28 -0
- package/dist/esm/listeners/index.d.ts +6 -0
- package/dist/esm/listeners/index.js +5 -0
- package/dist/esm/listeners/input-listener-managers.d.ts +15 -0
- package/dist/esm/listeners/input-listener-managers.js +53 -0
- package/dist/esm/listeners/listeners.types.d.ts +4 -0
- package/dist/esm/listeners/listeners.types.js +1 -0
- package/dist/esm/listeners/touch-listener-manager.d.ts +10 -0
- package/dist/esm/listeners/touch-listener-manager.js +52 -0
- package/dist/esm/listeners/unload-listener-manager.d.ts +8 -0
- package/dist/esm/listeners/unload-listener-manager.js +26 -0
- package/dist/esm/listeners/visibility-listener-manager.d.ts +12 -0
- package/dist/esm/listeners/visibility-listener-manager.js +79 -0
- package/dist/esm/managers/api.manager.d.ts +3 -0
- package/dist/esm/managers/api.manager.js +10 -0
- package/dist/esm/managers/config.manager.d.ts +7 -0
- package/dist/esm/managers/config.manager.js +90 -0
- package/dist/esm/managers/cross-tab-session.manager.d.ts +170 -0
- package/dist/esm/managers/cross-tab-session.manager.js +726 -0
- package/dist/esm/managers/event.manager.d.ts +61 -0
- package/dist/esm/managers/event.manager.js +504 -0
- package/dist/esm/managers/sampling.manager.d.ts +8 -0
- package/dist/esm/managers/sampling.manager.js +49 -0
- package/dist/esm/managers/sender.manager.d.ts +46 -0
- package/dist/esm/managers/sender.manager.js +300 -0
- package/dist/esm/managers/session-recovery.manager.d.ts +65 -0
- package/dist/esm/managers/session-recovery.manager.js +233 -0
- package/dist/esm/managers/session.manager.d.ts +72 -0
- package/dist/esm/managers/session.manager.js +583 -0
- package/dist/esm/managers/state.manager.d.ts +5 -0
- package/dist/esm/managers/state.manager.js +19 -0
- package/dist/esm/managers/storage.manager.d.ts +10 -0
- package/dist/esm/managers/storage.manager.js +77 -0
- package/dist/esm/managers/tags.manager.d.ts +12 -0
- package/dist/esm/managers/tags.manager.js +285 -0
- package/dist/esm/managers/user.manager.d.ts +7 -0
- package/dist/esm/managers/user.manager.js +18 -0
- package/dist/esm/public-api.d.ts +1 -0
- package/dist/esm/public-api.js +1 -0
- package/dist/esm/types/api.types.d.ts +21 -0
- package/dist/esm/types/api.types.js +22 -0
- package/dist/esm/types/common.types.d.ts +1 -0
- package/dist/esm/types/common.types.js +1 -0
- package/dist/esm/types/config.types.d.ts +104 -0
- package/dist/esm/types/config.types.js +1 -0
- package/dist/esm/types/device.types.d.ts +6 -0
- package/dist/esm/types/device.types.js +7 -0
- package/dist/esm/types/event.types.d.ts +104 -0
- package/dist/esm/types/event.types.js +22 -0
- package/dist/esm/types/index.d.ts +13 -0
- package/dist/esm/types/index.js +13 -0
- package/dist/esm/types/log.types.d.ts +4 -0
- package/dist/esm/types/log.types.js +1 -0
- package/dist/esm/types/mode.types.d.ts +7 -0
- package/dist/esm/types/mode.types.js +8 -0
- package/dist/esm/types/queue.types.d.ts +23 -0
- package/dist/esm/types/queue.types.js +1 -0
- package/dist/esm/types/session.types.d.ts +65 -0
- package/dist/esm/types/session.types.js +1 -0
- package/dist/esm/types/state.types.d.ts +12 -0
- package/dist/esm/types/state.types.js +1 -0
- package/dist/esm/types/tag.types.d.ts +43 -0
- package/dist/esm/types/tag.types.js +28 -0
- package/dist/esm/types/validation-error.types.d.ts +42 -0
- package/dist/esm/types/validation-error.types.js +59 -0
- package/dist/esm/types/web-vitals.types.d.ts +6 -0
- package/dist/esm/types/web-vitals.types.js +1 -0
- package/dist/esm/types/window.types.d.ts +17 -0
- package/dist/esm/types/window.types.js +1 -0
- package/dist/esm/utils/browser/device-detector.utils.d.ts +6 -0
- package/dist/esm/utils/browser/device-detector.utils.js +67 -0
- package/dist/esm/utils/browser/index.d.ts +2 -0
- package/dist/esm/utils/browser/index.js +2 -0
- package/dist/esm/utils/browser/utm-params.utils.d.ts +6 -0
- package/dist/esm/utils/browser/utm-params.utils.js +33 -0
- package/dist/esm/utils/data/index.d.ts +1 -0
- package/dist/esm/utils/data/index.js +1 -0
- package/dist/esm/utils/data/uuid.utils.d.ts +5 -0
- package/dist/esm/utils/data/uuid.utils.js +14 -0
- package/dist/esm/utils/index.d.ts +6 -0
- package/dist/esm/utils/index.js +6 -0
- package/dist/esm/utils/logging/debug-logger.utils.d.ts +56 -0
- package/dist/esm/utils/logging/debug-logger.utils.js +136 -0
- package/dist/esm/utils/logging/index.d.ts +1 -0
- package/dist/esm/utils/logging/index.js +1 -0
- package/dist/esm/utils/network/index.d.ts +1 -0
- package/dist/esm/utils/network/index.js +1 -0
- package/dist/esm/utils/network/url.utils.d.ts +20 -0
- package/dist/esm/utils/network/url.utils.js +166 -0
- package/dist/esm/utils/security/index.d.ts +1 -0
- package/dist/esm/utils/security/index.js +1 -0
- package/dist/esm/utils/security/sanitize.utils.d.ts +32 -0
- package/dist/esm/utils/security/sanitize.utils.js +311 -0
- package/dist/esm/utils/validations/config-validations.utils.d.ts +42 -0
- package/dist/esm/utils/validations/config-validations.utils.js +289 -0
- package/dist/esm/utils/validations/event-validations.utils.d.ts +12 -0
- package/dist/esm/utils/validations/event-validations.utils.js +26 -0
- package/dist/esm/utils/validations/index.d.ts +5 -0
- package/dist/esm/utils/validations/index.js +5 -0
- package/dist/esm/utils/validations/metadata-validations.utils.d.ts +22 -0
- package/dist/esm/utils/validations/metadata-validations.utils.js +110 -0
- package/dist/esm/utils/validations/type-guards.utils.d.ts +6 -0
- package/dist/esm/utils/validations/type-guards.utils.js +27 -0
- package/dist/esm/utils/validations/url-validations.utils.d.ts +15 -0
- package/dist/esm/utils/validations/url-validations.utils.js +42 -0
- package/package.json +80 -0
|
@@ -0,0 +1,4040 @@
|
|
|
1
|
+
var E = /* @__PURE__ */ ((n) => (n.Mobile = "mobile", n.Tablet = "tablet", n.Desktop = "desktop", n.Unknown = "unknown", n))(E || {});
|
|
2
|
+
const q = {};
|
|
3
|
+
class p {
|
|
4
|
+
get(e) {
|
|
5
|
+
return q[e];
|
|
6
|
+
}
|
|
7
|
+
set(e, t) {
|
|
8
|
+
const s = q[e];
|
|
9
|
+
q[e] = t, (e === "sessionId" || e === "config" || e === "hasStartSession") && i.debug("StateManager", "Critical state updated", {
|
|
10
|
+
key: e,
|
|
11
|
+
oldValue: e === "config" ? !!s : s,
|
|
12
|
+
newValue: e === "config" ? !!t : t
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
class Re extends p {
|
|
17
|
+
/**
|
|
18
|
+
* Client-facing error - Configuration/usage errors by the client
|
|
19
|
+
* Console: qa and debug modes | Events: NODE_ENV=dev
|
|
20
|
+
*/
|
|
21
|
+
clientError(e, t, s) {
|
|
22
|
+
this.logMessage("CLIENT_ERROR", e, t, s);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Client-facing warning - Configuration/usage warnings by the client
|
|
26
|
+
* Console: qa and debug modes | Events: NODE_ENV=dev
|
|
27
|
+
*/
|
|
28
|
+
clientWarn(e, t, s) {
|
|
29
|
+
this.logMessage("CLIENT_WARN", e, t, s);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* General operational information
|
|
33
|
+
* Console: qa and debug modes | Events: NODE_ENV=dev
|
|
34
|
+
*/
|
|
35
|
+
info(e, t, s) {
|
|
36
|
+
this.logMessage("INFO", e, t, s);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Internal library errors
|
|
40
|
+
* Console: debug mode only | Events: NODE_ENV=dev
|
|
41
|
+
*/
|
|
42
|
+
error(e, t, s) {
|
|
43
|
+
this.logMessage("ERROR", e, t, s);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Internal library warnings
|
|
47
|
+
* Console: debug mode only | Events: NODE_ENV=dev
|
|
48
|
+
*/
|
|
49
|
+
warn(e, t, s) {
|
|
50
|
+
this.logMessage("WARN", e, t, s);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Strategic debug information
|
|
54
|
+
* Console: debug mode only | Events: NODE_ENV=dev
|
|
55
|
+
*/
|
|
56
|
+
debug(e, t, s) {
|
|
57
|
+
this.logMessage("DEBUG", e, t, s);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Detailed trace information
|
|
61
|
+
* Console: debug mode only | Events: NODE_ENV=dev
|
|
62
|
+
*/
|
|
63
|
+
verbose(e, t, s) {
|
|
64
|
+
this.logMessage("VERBOSE", e, t, s);
|
|
65
|
+
}
|
|
66
|
+
getCurrentMode() {
|
|
67
|
+
try {
|
|
68
|
+
return this.get("config")?.mode;
|
|
69
|
+
} catch {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
shouldShowLog(e) {
|
|
74
|
+
switch (this.getCurrentMode()) {
|
|
75
|
+
case "qa":
|
|
76
|
+
return ["INFO", "CLIENT_ERROR", "CLIENT_WARN"].includes(e);
|
|
77
|
+
case "debug":
|
|
78
|
+
return !0;
|
|
79
|
+
default:
|
|
80
|
+
return !1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
formatMessage(e, t) {
|
|
84
|
+
return `[TraceLog:${e}] ${t}`;
|
|
85
|
+
}
|
|
86
|
+
getConsoleMethod(e) {
|
|
87
|
+
switch (e) {
|
|
88
|
+
case "CLIENT_ERROR":
|
|
89
|
+
case "ERROR":
|
|
90
|
+
return "error";
|
|
91
|
+
case "CLIENT_WARN":
|
|
92
|
+
case "WARN":
|
|
93
|
+
return "warn";
|
|
94
|
+
case "INFO":
|
|
95
|
+
case "DEBUG":
|
|
96
|
+
case "VERBOSE":
|
|
97
|
+
default:
|
|
98
|
+
return "log";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
logMessage(e, t, s, r) {
|
|
102
|
+
if (!this.shouldShowLog(e))
|
|
103
|
+
return;
|
|
104
|
+
const a = this.formatMessage(t, s), o = this.getConsoleMethod(e);
|
|
105
|
+
r !== void 0 ? console[o](a, r) : console[o](a);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Dispatches tracelog:log events for E2E testing and development debugging
|
|
109
|
+
*/
|
|
110
|
+
dispatchEvent(e, t, s, r) {
|
|
111
|
+
if (!(typeof window > "u" || typeof CustomEvent > "u"))
|
|
112
|
+
try {
|
|
113
|
+
const a = new CustomEvent("tracelog:log", {
|
|
114
|
+
detail: {
|
|
115
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
116
|
+
level: e,
|
|
117
|
+
namespace: t,
|
|
118
|
+
message: s,
|
|
119
|
+
data: r
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
window.dispatchEvent(a);
|
|
123
|
+
} catch {
|
|
124
|
+
console.log(`[TraceLog:${t}] ${s}`, r);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const i = new Re();
|
|
129
|
+
let J, Ee;
|
|
130
|
+
const ke = () => {
|
|
131
|
+
typeof window < "u" && !J && (J = window.matchMedia("(pointer: coarse)"), Ee = window.matchMedia("(hover: none)"));
|
|
132
|
+
}, we = () => {
|
|
133
|
+
try {
|
|
134
|
+
i.debug("DeviceDetector", "Starting device detection");
|
|
135
|
+
const n = navigator;
|
|
136
|
+
if (n.userAgentData && typeof n.userAgentData.mobile == "boolean") {
|
|
137
|
+
if (i.debug("DeviceDetector", "Using modern User-Agent Client Hints API", {
|
|
138
|
+
mobile: n.userAgentData.mobile,
|
|
139
|
+
platform: n.userAgentData.platform
|
|
140
|
+
}), n.userAgentData.platform && /ipad|tablet/i.test(n.userAgentData.platform))
|
|
141
|
+
return i.debug("DeviceDetector", "Device detected as tablet via platform hint"), E.Tablet;
|
|
142
|
+
const d = n.userAgentData.mobile ? E.Mobile : E.Desktop;
|
|
143
|
+
return i.debug("DeviceDetector", "Device detected via User-Agent hints", { result: d }), d;
|
|
144
|
+
}
|
|
145
|
+
i.debug("DeviceDetector", "Using fallback detection methods"), ke();
|
|
146
|
+
const e = window.innerWidth, t = J?.matches ?? !1, s = Ee?.matches ?? !1, r = "ontouchstart" in window || navigator.maxTouchPoints > 0, a = navigator.userAgent.toLowerCase(), o = /mobile|android|iphone|ipod|blackberry|iemobile|opera mini/.test(a), l = /tablet|ipad|android(?!.*mobile)/.test(a), c = {
|
|
147
|
+
width: e,
|
|
148
|
+
hasCoarsePointer: t,
|
|
149
|
+
hasNoHover: s,
|
|
150
|
+
hasTouchSupport: r,
|
|
151
|
+
isMobileUA: o,
|
|
152
|
+
isTabletUA: l,
|
|
153
|
+
maxTouchPoints: navigator.maxTouchPoints
|
|
154
|
+
};
|
|
155
|
+
return e <= 767 || o && r ? (i.debug("DeviceDetector", "Device detected as mobile", c), E.Mobile) : e >= 768 && e <= 1024 || l || t && s && r ? (i.debug("DeviceDetector", "Device detected as tablet", c), E.Tablet) : (i.debug("DeviceDetector", "Device detected as desktop", c), E.Desktop);
|
|
156
|
+
} catch (n) {
|
|
157
|
+
return i.warn("DeviceDetector", "Device detection failed, defaulting to desktop", {
|
|
158
|
+
error: n instanceof Error ? n.message : n
|
|
159
|
+
}), E.Desktop;
|
|
160
|
+
}
|
|
161
|
+
}, Ne = 2, Ue = 10, Ie = 1, ne = 500, Z = 3e4, ee = 864e5, re = 120, ae = 8 * 1024, oe = 10, ce = 10, _ = 255, M = 1e3, W = 100, le = 3, k = 2, Pe = 4, He = 0.75, De = 0.2, xe = 2e3, Oe = 1e3, Fe = 10, F = 10, L = 15 * 60 * 1e3, ze = 3e4, Te = 1e3, Me = 250, Ve = 2e3, de = 1e3, $e = 1e4, je = 2500, he = 1e3, ue = 3e4, ge = 1e3, Ge = 24, Qe = 24 * 60 * 60 * 1e3, Be = Te, qe = 5e3, We = 2e3, Xe = 2, Ke = 3, X = 24 * 60 * 60 * 1e3, K = 2 * 60 * 1e3, Ye = "https://api.tracelog.io", Ae = {
|
|
162
|
+
samplingRate: Ie,
|
|
163
|
+
tags: [],
|
|
164
|
+
excludedUrlPaths: []
|
|
165
|
+
}, Je = (n) => ({
|
|
166
|
+
...Ae,
|
|
167
|
+
...n,
|
|
168
|
+
sessionTimeout: L,
|
|
169
|
+
allowHttp: !1
|
|
170
|
+
}), z = "data-tl", fe = [
|
|
171
|
+
"button",
|
|
172
|
+
"a",
|
|
173
|
+
'input[type="button"]',
|
|
174
|
+
'input[type="submit"]',
|
|
175
|
+
'input[type="reset"]',
|
|
176
|
+
'input[type="checkbox"]',
|
|
177
|
+
'input[type="radio"]',
|
|
178
|
+
"select",
|
|
179
|
+
"textarea",
|
|
180
|
+
'[role="button"]',
|
|
181
|
+
'[role="link"]',
|
|
182
|
+
'[role="tab"]',
|
|
183
|
+
'[role="menuitem"]',
|
|
184
|
+
'[role="option"]',
|
|
185
|
+
'[role="checkbox"]',
|
|
186
|
+
'[role="radio"]',
|
|
187
|
+
'[role="switch"]',
|
|
188
|
+
"[routerLink]",
|
|
189
|
+
"[ng-click]",
|
|
190
|
+
"[data-action]",
|
|
191
|
+
"[data-click]",
|
|
192
|
+
"[data-navigate]",
|
|
193
|
+
"[data-toggle]",
|
|
194
|
+
"[onclick]",
|
|
195
|
+
".btn",
|
|
196
|
+
".button",
|
|
197
|
+
".clickable",
|
|
198
|
+
".nav-link",
|
|
199
|
+
".menu-item",
|
|
200
|
+
"[data-testid]",
|
|
201
|
+
'[tabindex="0"]'
|
|
202
|
+
], Ze = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"], me = {
|
|
203
|
+
/** Maximum number of retries when waiting for concurrent initialization */
|
|
204
|
+
MAX_CONCURRENT_RETRIES: 20,
|
|
205
|
+
/** Delay between retries when waiting for concurrent initialization (ms) */
|
|
206
|
+
CONCURRENT_RETRY_DELAY_MS: 50,
|
|
207
|
+
/** Timeout for overall initialization process (ms) */
|
|
208
|
+
INITIALIZATION_TIMEOUT_MS: 1e4
|
|
209
|
+
}, b = {
|
|
210
|
+
/** Maximum number of consecutive failures before opening circuit */
|
|
211
|
+
MAX_FAILURES: 10,
|
|
212
|
+
/** Initial backoff delay when circuit opens (ms) */
|
|
213
|
+
INITIAL_BACKOFF_DELAY_MS: 1e3,
|
|
214
|
+
/** Maximum backoff delay (ms) */
|
|
215
|
+
MAX_BACKOFF_DELAY_MS: 3e4,
|
|
216
|
+
/** Backoff multiplier for exponential backoff */
|
|
217
|
+
BACKOFF_MULTIPLIER: 2,
|
|
218
|
+
/** Time-based recovery period for circuit breaker (ms) */
|
|
219
|
+
RECOVERY_TIME_MS: 3e4
|
|
220
|
+
// 30 seconds
|
|
221
|
+
}, pe = {
|
|
222
|
+
/** Timeout for session synchronization operations (ms) */
|
|
223
|
+
SYNC_TIMEOUT_MS: 2e3,
|
|
224
|
+
/** Maximum retry attempts for session operations */
|
|
225
|
+
MAX_RETRY_ATTEMPTS: 3
|
|
226
|
+
}, et = {
|
|
227
|
+
/** Multiplier for scroll debounce time when suppressing scroll events */
|
|
228
|
+
SUPPRESS_MULTIPLIER: 2
|
|
229
|
+
}, _e = [
|
|
230
|
+
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
|
|
231
|
+
/javascript:/gi,
|
|
232
|
+
/on\w+\s*=/gi,
|
|
233
|
+
/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi,
|
|
234
|
+
/<embed\b[^>]*>/gi,
|
|
235
|
+
/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi
|
|
236
|
+
], S = "tl", ve = (n) => n ? `${S}:${n}:uid` : `${S}:uid`, tt = (n) => n ? `${S}:${n}:queue` : `${S}:queue`, st = (n) => n ? `${S}:${n}:session` : `${S}:session`, Y = (n) => n ? `${S}:${n}:cross_tab_session` : `${S}:cross_tab_session`, Se = (n, e) => `${S}:${n}:tab:${e}:info`, V = (n) => n ? `${S}:${n}:recovery` : `${S}:recovery`, it = (n) => n ? `${S}:${n}:broadcast` : `${S}:broadcast`, nt = /* @__PURE__ */ new Set([
|
|
237
|
+
"mode",
|
|
238
|
+
"tags",
|
|
239
|
+
"samplingRate",
|
|
240
|
+
"excludedUrlPaths",
|
|
241
|
+
"ipExcluded"
|
|
242
|
+
]), w = {
|
|
243
|
+
// Project ID validation - consistent message across all layers
|
|
244
|
+
MISSING_PROJECT_ID: "Project ID is required",
|
|
245
|
+
PROJECT_ID_EMPTY_AFTER_TRIM: "Project ID is required",
|
|
246
|
+
// Session timeout validation
|
|
247
|
+
INVALID_SESSION_TIMEOUT: `Session timeout must be between ${Z}ms (30 seconds) and ${ee}ms (24 hours)`,
|
|
248
|
+
INVALID_ERROR_SAMPLING_RATE: "Error sampling must be between 0 and 1",
|
|
249
|
+
// Integration validation
|
|
250
|
+
INVALID_GOOGLE_ANALYTICS_ID: "Google Analytics measurement ID is required when integration is enabled",
|
|
251
|
+
// UI validation
|
|
252
|
+
INVALID_SCROLL_CONTAINER_SELECTORS: "Scroll container selectors must be valid CSS selectors",
|
|
253
|
+
// Global metadata validation
|
|
254
|
+
INVALID_GLOBAL_METADATA: "Global metadata must be an object",
|
|
255
|
+
// Array validation
|
|
256
|
+
INVALID_SENSITIVE_QUERY_PARAMS: "Sensitive query params must be an array of strings"
|
|
257
|
+
}, rt = () => {
|
|
258
|
+
i.debug("UTMParams", "Extracting UTM parameters from URL", {
|
|
259
|
+
url: window.location.href,
|
|
260
|
+
search: window.location.search
|
|
261
|
+
});
|
|
262
|
+
const n = new URLSearchParams(window.location.search), e = {};
|
|
263
|
+
Ze.forEach((s) => {
|
|
264
|
+
const r = n.get(s);
|
|
265
|
+
if (r) {
|
|
266
|
+
const a = s.split("utm_")[1];
|
|
267
|
+
e[a] = r, i.debug("UTMParams", "Found UTM parameter", { param: s, key: a, value: r });
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
const t = Object.keys(e).length ? e : void 0;
|
|
271
|
+
return t ? i.debug("UTMParams", "UTM parameters extracted successfully", {
|
|
272
|
+
parameterCount: Object.keys(t).length,
|
|
273
|
+
parameters: Object.keys(t)
|
|
274
|
+
}) : i.debug("UTMParams", "No UTM parameters found in URL"), t;
|
|
275
|
+
}, $ = () => {
|
|
276
|
+
const n = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (e) => {
|
|
277
|
+
const t = Math.random() * 16 | 0;
|
|
278
|
+
return (e === "x" ? t : t & 3 | 8).toString(16);
|
|
279
|
+
});
|
|
280
|
+
return i.verbose("UUIDUtils", "Generated new UUID", { uuid: n }), n;
|
|
281
|
+
};
|
|
282
|
+
var C = /* @__PURE__ */ ((n) => (n.HttpSkip = "http-skip", n.HttpLocal = "http-local", n))(C || {}), h = /* @__PURE__ */ ((n) => (n.PAGE_VIEW = "page_view", n.CLICK = "click", n.SCROLL = "scroll", n.SESSION_START = "session_start", n.SESSION_END = "session_end", n.CUSTOM = "custom", n.WEB_VITALS = "web_vitals", n.ERROR = "error", n))(h || {}), j = /* @__PURE__ */ ((n) => (n.UP = "up", n.DOWN = "down", n))(j || {}), D = /* @__PURE__ */ ((n) => (n.JS_ERROR = "js_error", n.PROMISE_REJECTION = "promise_rejection", n.NETWORK_ERROR = "network_error", n))(D || {}), N = /* @__PURE__ */ ((n) => (n.QA = "qa", n.DEBUG = "debug", n))(N || {}), G = /* @__PURE__ */ ((n) => (n.AND = "AND", n.OR = "OR", n))(G || {}), m = /* @__PURE__ */ ((n) => (n.URL_MATCHES = "url_matches", n.ELEMENT_MATCHES = "element_matches", n.DEVICE_TYPE = "device_type", n.ELEMENT_TEXT = "element_text", n.ELEMENT_ATTRIBUTE = "element_attribute", n.UTM_SOURCE = "utm_source", n.UTM_MEDIUM = "utm_medium", n.UTM_CAMPAIGN = "utm_campaign", n))(m || {}), f = /* @__PURE__ */ ((n) => (n.EQUALS = "equals", n.CONTAINS = "contains", n.STARTS_WITH = "starts_with", n.ENDS_WITH = "ends_with", n.REGEX = "regex", n.GREATER_THAN = "greater_than", n.LESS_THAN = "less_than", n.EXISTS = "exists", n.NOT_EXISTS = "not_exists", n))(f || {});
|
|
283
|
+
class x extends Error {
|
|
284
|
+
constructor(e, t, s) {
|
|
285
|
+
super(e), this.errorCode = t, this.layer = s, this.name = this.constructor.name, Error.captureStackTrace && Error.captureStackTrace(this, this.constructor);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
class Q extends x {
|
|
289
|
+
constructor(e = "Project ID is required", t = "config") {
|
|
290
|
+
super(e, "PROJECT_ID_INVALID", t);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
class H extends x {
|
|
294
|
+
constructor(e, t = "config") {
|
|
295
|
+
super(e, "APP_CONFIG_INVALID", t);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
class at extends x {
|
|
299
|
+
constructor(e, t = "config") {
|
|
300
|
+
super(e, "SESSION_TIMEOUT_INVALID", t);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
class ot extends x {
|
|
304
|
+
constructor(e, t = "config") {
|
|
305
|
+
super(e, "SAMPLING_RATE_INVALID", t);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
class ye extends x {
|
|
309
|
+
constructor(e, t = "config") {
|
|
310
|
+
super(e, "INTEGRATION_INVALID", t);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const ct = (n) => {
|
|
314
|
+
if (!n || typeof n != "object")
|
|
315
|
+
throw i.clientError("ConfigValidation", "Configuration must be an object", { config: n }), new H("Configuration must be an object", "config");
|
|
316
|
+
if (!("id" in n))
|
|
317
|
+
throw i.clientError("ConfigValidation", "Project ID is missing from configuration"), new Q(w.MISSING_PROJECT_ID, "config");
|
|
318
|
+
if (n.id === null || n.id === void 0 || typeof n.id != "string")
|
|
319
|
+
throw i.clientError("ConfigValidation", "Project ID must be a non-empty string", {
|
|
320
|
+
providedId: n.id,
|
|
321
|
+
type: typeof n.id
|
|
322
|
+
}), new Q(w.MISSING_PROJECT_ID, "config");
|
|
323
|
+
if (n.sessionTimeout !== void 0 && (typeof n.sessionTimeout != "number" || n.sessionTimeout < Z || n.sessionTimeout > ee))
|
|
324
|
+
throw i.clientError("ConfigValidation", "Invalid session timeout", {
|
|
325
|
+
provided: n.sessionTimeout,
|
|
326
|
+
min: Z,
|
|
327
|
+
max: ee
|
|
328
|
+
}), new at(w.INVALID_SESSION_TIMEOUT, "config");
|
|
329
|
+
if (n.globalMetadata !== void 0 && (typeof n.globalMetadata != "object" || n.globalMetadata === null))
|
|
330
|
+
throw i.clientError("ConfigValidation", "Global metadata must be an object", {
|
|
331
|
+
provided: n.globalMetadata,
|
|
332
|
+
type: typeof n.globalMetadata
|
|
333
|
+
}), new H(w.INVALID_GLOBAL_METADATA, "config");
|
|
334
|
+
if (n.scrollContainerSelectors !== void 0 && lt(n.scrollContainerSelectors), n.integrations && dt(n.integrations), n.sensitiveQueryParams !== void 0) {
|
|
335
|
+
if (!Array.isArray(n.sensitiveQueryParams))
|
|
336
|
+
throw i.clientError("ConfigValidation", "Sensitive query params must be an array", {
|
|
337
|
+
provided: n.sensitiveQueryParams,
|
|
338
|
+
type: typeof n.sensitiveQueryParams
|
|
339
|
+
}), new H(w.INVALID_SENSITIVE_QUERY_PARAMS, "config");
|
|
340
|
+
for (const e of n.sensitiveQueryParams)
|
|
341
|
+
if (typeof e != "string")
|
|
342
|
+
throw i.clientError("ConfigValidation", "All sensitive query params must be strings", {
|
|
343
|
+
param: e,
|
|
344
|
+
type: typeof e
|
|
345
|
+
}), new H("All sensitive query params must be strings", "config");
|
|
346
|
+
}
|
|
347
|
+
if (n.errorSampling !== void 0 && (typeof n.errorSampling != "number" || n.errorSampling < 0 || n.errorSampling > 1))
|
|
348
|
+
throw i.clientError("ConfigValidation", "Invalid error sampling rate", {
|
|
349
|
+
provided: n.errorSampling,
|
|
350
|
+
expected: "0-1"
|
|
351
|
+
}), new ot(w.INVALID_ERROR_SAMPLING_RATE, "config");
|
|
352
|
+
}, lt = (n) => {
|
|
353
|
+
const e = Array.isArray(n) ? n : [n];
|
|
354
|
+
for (const t of e) {
|
|
355
|
+
if (typeof t != "string" || t.trim() === "")
|
|
356
|
+
throw i.clientError("ConfigValidation", "Invalid scroll container selector", {
|
|
357
|
+
selector: t,
|
|
358
|
+
type: typeof t,
|
|
359
|
+
isEmpty: t === "" || typeof t == "string" && t.trim() === ""
|
|
360
|
+
}), new H(w.INVALID_SCROLL_CONTAINER_SELECTORS, "config");
|
|
361
|
+
if (typeof document < "u")
|
|
362
|
+
try {
|
|
363
|
+
document.querySelector(t);
|
|
364
|
+
} catch {
|
|
365
|
+
i.clientWarn("ConfigValidation", `Invalid CSS selector will be ignored: "${t}"`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}, dt = (n) => {
|
|
369
|
+
if (n && n.googleAnalytics) {
|
|
370
|
+
if (!n.googleAnalytics.measurementId || typeof n.googleAnalytics.measurementId != "string" || n.googleAnalytics.measurementId.trim() === "")
|
|
371
|
+
throw i.clientError("ConfigValidation", "Invalid Google Analytics measurement ID", {
|
|
372
|
+
provided: n.googleAnalytics.measurementId,
|
|
373
|
+
type: typeof n.googleAnalytics.measurementId
|
|
374
|
+
}), new ye(w.INVALID_GOOGLE_ANALYTICS_ID, "config");
|
|
375
|
+
const e = n.googleAnalytics.measurementId.trim();
|
|
376
|
+
if (!e.match(/^(G-|UA-)/))
|
|
377
|
+
throw i.clientError("ConfigValidation", 'Google Analytics measurement ID must start with "G-" or "UA-"', {
|
|
378
|
+
provided: e
|
|
379
|
+
}), new ye('Google Analytics measurement ID must start with "G-" or "UA-"', "config");
|
|
380
|
+
}
|
|
381
|
+
}, ht = (n) => {
|
|
382
|
+
ct(n);
|
|
383
|
+
const e = {
|
|
384
|
+
...n,
|
|
385
|
+
id: n.id.trim(),
|
|
386
|
+
globalMetadata: n.globalMetadata ?? {},
|
|
387
|
+
sensitiveQueryParams: n.sensitiveQueryParams ?? []
|
|
388
|
+
};
|
|
389
|
+
if (!e.id)
|
|
390
|
+
throw i.clientError("ConfigValidation", "Project ID is empty after trimming whitespace", {
|
|
391
|
+
originalId: n.id,
|
|
392
|
+
normalizedId: e.id
|
|
393
|
+
}), new Q(w.PROJECT_ID_EMPTY_AFTER_TRIM, "config");
|
|
394
|
+
return e;
|
|
395
|
+
}, be = (n) => {
|
|
396
|
+
if (!n || typeof n != "string" || n.trim().length === 0)
|
|
397
|
+
return i.debug("Sanitize", "String sanitization skipped - empty or invalid input", { value: n, type: typeof n }), "";
|
|
398
|
+
const e = n.length;
|
|
399
|
+
let t = n;
|
|
400
|
+
n.length > M && (t = n.slice(0, Math.max(0, M)), i.warn("Sanitize", "String truncated due to length limit", {
|
|
401
|
+
originalLength: e,
|
|
402
|
+
maxLength: M,
|
|
403
|
+
truncatedLength: t.length
|
|
404
|
+
}));
|
|
405
|
+
let s = 0;
|
|
406
|
+
for (const a of _e) {
|
|
407
|
+
const o = t;
|
|
408
|
+
t = t.replace(a, ""), o !== t && s++;
|
|
409
|
+
}
|
|
410
|
+
s > 0 && i.warn("Sanitize", "XSS patterns detected and removed", {
|
|
411
|
+
patternMatches: s,
|
|
412
|
+
originalValue: n.slice(0, 100)
|
|
413
|
+
// Log first 100 chars for debugging
|
|
414
|
+
}), t = t.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'").replaceAll("/", "/");
|
|
415
|
+
const r = t.trim();
|
|
416
|
+
return (e > 50 || s > 0) && i.debug("Sanitize", "String sanitization completed", {
|
|
417
|
+
originalLength: e,
|
|
418
|
+
sanitizedLength: r.length,
|
|
419
|
+
xssPatternMatches: s,
|
|
420
|
+
wasTruncated: e > M
|
|
421
|
+
}), r;
|
|
422
|
+
}, ut = (n) => {
|
|
423
|
+
if (typeof n != "string")
|
|
424
|
+
return "";
|
|
425
|
+
n.length > M && (n = n.slice(0, Math.max(0, M)));
|
|
426
|
+
let e = n;
|
|
427
|
+
for (const t of _e)
|
|
428
|
+
e = e.replace(t, "");
|
|
429
|
+
return e = e.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'"), e.trim();
|
|
430
|
+
}, B = (n, e = 0) => {
|
|
431
|
+
if (e > le)
|
|
432
|
+
return i.warn("Sanitize", "Maximum object depth exceeded during sanitization", {
|
|
433
|
+
depth: e,
|
|
434
|
+
maxDepth: le
|
|
435
|
+
}), null;
|
|
436
|
+
if (n == null)
|
|
437
|
+
return null;
|
|
438
|
+
if (typeof n == "string")
|
|
439
|
+
return be(n);
|
|
440
|
+
if (typeof n == "number")
|
|
441
|
+
return !Number.isFinite(n) || n < -Number.MAX_SAFE_INTEGER || n > Number.MAX_SAFE_INTEGER ? (i.warn("Sanitize", "Invalid number sanitized to 0", { value: n, isFinite: Number.isFinite(n) }), 0) : n;
|
|
442
|
+
if (typeof n == "boolean")
|
|
443
|
+
return n;
|
|
444
|
+
if (Array.isArray(n)) {
|
|
445
|
+
const t = n.length, s = n.slice(0, W);
|
|
446
|
+
t > W && i.warn("Sanitize", "Array truncated due to length limit", {
|
|
447
|
+
originalLength: t,
|
|
448
|
+
maxLength: W,
|
|
449
|
+
depth: e
|
|
450
|
+
});
|
|
451
|
+
const r = s.map((a) => B(a, e + 1)).filter((a) => a !== null);
|
|
452
|
+
return t > 0 && r.length === 0 && i.warn("Sanitize", "All array items were filtered out during sanitization", { originalLength: t, depth: e }), r;
|
|
453
|
+
}
|
|
454
|
+
if (typeof n == "object") {
|
|
455
|
+
const t = {}, s = Object.entries(n), r = s.length, a = s.slice(0, 20);
|
|
456
|
+
r > 20 && i.warn("Sanitize", "Object keys truncated due to limit", {
|
|
457
|
+
originalKeys: r,
|
|
458
|
+
maxKeys: 20,
|
|
459
|
+
depth: e
|
|
460
|
+
});
|
|
461
|
+
let o = 0;
|
|
462
|
+
for (const [l, c] of a) {
|
|
463
|
+
const d = be(l);
|
|
464
|
+
if (d) {
|
|
465
|
+
const u = B(c, e + 1);
|
|
466
|
+
u !== null ? t[d] = u : o++;
|
|
467
|
+
} else
|
|
468
|
+
o++;
|
|
469
|
+
}
|
|
470
|
+
return o > 0 && i.debug("Sanitize", "Object properties filtered during sanitization", {
|
|
471
|
+
filteredKeysCount: o,
|
|
472
|
+
remainingKeys: Object.keys(t).length,
|
|
473
|
+
depth: e
|
|
474
|
+
}), t;
|
|
475
|
+
}
|
|
476
|
+
return i.debug("Sanitize", "Unknown value type sanitized to null", { type: typeof n, depth: e }), null;
|
|
477
|
+
}, gt = (n) => {
|
|
478
|
+
i.debug("Sanitize", "Starting API config sanitization");
|
|
479
|
+
const e = {};
|
|
480
|
+
if (typeof n != "object" || n === null)
|
|
481
|
+
return i.warn("Sanitize", "API config data is not an object", { data: n, type: typeof n }), e;
|
|
482
|
+
try {
|
|
483
|
+
const t = Object.keys(n);
|
|
484
|
+
let s = 0, r = 0;
|
|
485
|
+
for (const a of t)
|
|
486
|
+
if (nt.has(a)) {
|
|
487
|
+
const o = n[a];
|
|
488
|
+
if (a === "excludedUrlPaths") {
|
|
489
|
+
const l = Array.isArray(o) ? o : typeof o == "string" ? [o] : [], c = l.length;
|
|
490
|
+
e.excludedUrlPaths = l.map((u) => ut(String(u))).filter(Boolean);
|
|
491
|
+
const d = c - e.excludedUrlPaths.length;
|
|
492
|
+
d > 0 && i.warn("Sanitize", "Some excluded URL paths were filtered during sanitization", {
|
|
493
|
+
originalCount: c,
|
|
494
|
+
filteredCount: d
|
|
495
|
+
});
|
|
496
|
+
} else if (a === "tags")
|
|
497
|
+
Array.isArray(o) ? (e.tags = o, i.debug("Sanitize", "Tags processed", { count: o.length })) : i.warn("Sanitize", "Tags value is not an array", { value: o, type: typeof o });
|
|
498
|
+
else {
|
|
499
|
+
const l = B(o);
|
|
500
|
+
l !== null ? e[a] = l : i.warn("Sanitize", "API config value sanitized to null", { key: a, originalValue: o });
|
|
501
|
+
}
|
|
502
|
+
s++;
|
|
503
|
+
} else
|
|
504
|
+
r++, i.debug("Sanitize", "API config key not allowed", { key: a });
|
|
505
|
+
i.info("Sanitize", "API config sanitization completed", {
|
|
506
|
+
originalKeys: t.length,
|
|
507
|
+
processedKeys: s,
|
|
508
|
+
filteredKeys: r,
|
|
509
|
+
finalKeys: Object.keys(e).length
|
|
510
|
+
});
|
|
511
|
+
} catch (t) {
|
|
512
|
+
throw i.error("Sanitize", "API config sanitization failed", {
|
|
513
|
+
error: t instanceof Error ? t.message : t
|
|
514
|
+
}), new Error(`API config sanitization failed: ${t instanceof Error ? t.message : "Unknown error"}`);
|
|
515
|
+
}
|
|
516
|
+
return e;
|
|
517
|
+
}, ft = (n) => {
|
|
518
|
+
if (i.debug("Sanitize", "Starting metadata sanitization", { hasMetadata: n != null }), typeof n != "object" || n === null)
|
|
519
|
+
return i.debug("Sanitize", "Metadata is not an object, returning empty object", {
|
|
520
|
+
metadata: n,
|
|
521
|
+
type: typeof n
|
|
522
|
+
}), {};
|
|
523
|
+
try {
|
|
524
|
+
const e = Object.keys(n).length, t = B(n), s = typeof t == "object" && t !== null ? t : {}, r = Object.keys(s).length;
|
|
525
|
+
return i.debug("Sanitize", "Metadata sanitization completed", {
|
|
526
|
+
originalKeys: e,
|
|
527
|
+
finalKeys: r,
|
|
528
|
+
keysFiltered: e - r
|
|
529
|
+
}), s;
|
|
530
|
+
} catch (e) {
|
|
531
|
+
throw i.error("Sanitize", "Metadata sanitization failed", {
|
|
532
|
+
error: e instanceof Error ? e.message : e
|
|
533
|
+
}), new Error(`Metadata sanitization failed: ${e instanceof Error ? e.message : "Unknown error"}`);
|
|
534
|
+
}
|
|
535
|
+
}, mt = (n) => {
|
|
536
|
+
if (typeof n != "object" || n === null)
|
|
537
|
+
return !1;
|
|
538
|
+
for (const e of Object.values(n)) {
|
|
539
|
+
if (e == null)
|
|
540
|
+
continue;
|
|
541
|
+
const t = typeof e;
|
|
542
|
+
if (!(t === "string" || t === "number" || t === "boolean")) {
|
|
543
|
+
if (Array.isArray(e)) {
|
|
544
|
+
if (!e.every((s) => typeof s == "string"))
|
|
545
|
+
return !1;
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
return !1;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return !0;
|
|
552
|
+
}, pt = (n) => typeof n != "string" ? {
|
|
553
|
+
valid: !1,
|
|
554
|
+
error: "Event name must be a string"
|
|
555
|
+
} : n.length === 0 ? {
|
|
556
|
+
valid: !1,
|
|
557
|
+
error: "Event name cannot be empty"
|
|
558
|
+
} : n.length > re ? {
|
|
559
|
+
valid: !1,
|
|
560
|
+
error: `Event name is too long (max ${re} characters)`
|
|
561
|
+
} : n.includes("<") || n.includes(">") || n.includes("&") ? {
|
|
562
|
+
valid: !1,
|
|
563
|
+
error: "Event name contains invalid characters"
|
|
564
|
+
} : ["constructor", "prototype", "__proto__", "eval", "function", "var", "let", "const"].includes(n.toLowerCase()) ? {
|
|
565
|
+
valid: !1,
|
|
566
|
+
error: "Event name cannot be a reserved word"
|
|
567
|
+
} : { valid: !0 }, vt = (n, e, t) => {
|
|
568
|
+
const s = ft(e), r = `${t} "${n}" metadata error`;
|
|
569
|
+
if (!mt(s))
|
|
570
|
+
return {
|
|
571
|
+
valid: !1,
|
|
572
|
+
error: `${r}: object has invalid types. Valid types are string, number, boolean or string arrays.`
|
|
573
|
+
};
|
|
574
|
+
let a;
|
|
575
|
+
try {
|
|
576
|
+
a = JSON.stringify(s);
|
|
577
|
+
} catch {
|
|
578
|
+
return {
|
|
579
|
+
valid: !1,
|
|
580
|
+
error: `${r}: object contains circular references or cannot be serialized.`
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
if (a.length > ae)
|
|
584
|
+
return {
|
|
585
|
+
valid: !1,
|
|
586
|
+
error: `${r}: object is too large (max ${ae / 1024} KB).`
|
|
587
|
+
};
|
|
588
|
+
if (Object.keys(s).length > oe)
|
|
589
|
+
return {
|
|
590
|
+
valid: !1,
|
|
591
|
+
error: `${r}: object has too many keys (max ${oe} keys).`
|
|
592
|
+
};
|
|
593
|
+
for (const [l, c] of Object.entries(s)) {
|
|
594
|
+
if (Array.isArray(c)) {
|
|
595
|
+
if (c.length > ce)
|
|
596
|
+
return {
|
|
597
|
+
valid: !1,
|
|
598
|
+
error: `${r}: array property "${l}" is too large (max ${ce} items).`
|
|
599
|
+
};
|
|
600
|
+
for (const d of c)
|
|
601
|
+
if (typeof d == "string" && d.length > 500)
|
|
602
|
+
return {
|
|
603
|
+
valid: !1,
|
|
604
|
+
error: `${r}: array property "${l}" contains strings that are too long (max 500 characters).`
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
if (typeof c == "string" && c.length > M)
|
|
608
|
+
return {
|
|
609
|
+
valid: !1,
|
|
610
|
+
error: `${r}: property "${l}" is too long (max ${M} characters).`
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
valid: !0,
|
|
615
|
+
sanitizedMetadata: s
|
|
616
|
+
};
|
|
617
|
+
}, St = (n, e) => {
|
|
618
|
+
const t = pt(n);
|
|
619
|
+
if (!t.valid)
|
|
620
|
+
return i.clientError("EventValidation", "Event name validation failed", { eventName: n, error: t.error }), t;
|
|
621
|
+
if (!e)
|
|
622
|
+
return { valid: !0 };
|
|
623
|
+
const s = vt(n, e, "customEvent");
|
|
624
|
+
return s.valid || i.clientError("EventValidation", "Event metadata validation failed", {
|
|
625
|
+
eventName: n,
|
|
626
|
+
error: s.error
|
|
627
|
+
}), s;
|
|
628
|
+
}, te = (n, e = !1) => {
|
|
629
|
+
try {
|
|
630
|
+
const t = new URL(n), s = t.protocol === "https:", r = t.protocol === "http:";
|
|
631
|
+
return s || e && r;
|
|
632
|
+
} catch {
|
|
633
|
+
return !1;
|
|
634
|
+
}
|
|
635
|
+
}, yt = (n, e = !1) => {
|
|
636
|
+
i.debug("URLUtils", "Generating API URL", { projectId: n, allowHttp: e });
|
|
637
|
+
const t = new URL(window.location.href), s = t.hostname, r = s.split(".");
|
|
638
|
+
if (r.length === 0)
|
|
639
|
+
throw i.clientError("URLUtils", "Invalid hostname - no domain parts found", { hostname: s }), new Error("Invalid URL");
|
|
640
|
+
const a = r.slice(-2).join("."), o = e && t.protocol === "http:" ? "http" : "https", l = `${o}://${n}.${a}`;
|
|
641
|
+
if (i.debug("URLUtils", "Generated API URL", {
|
|
642
|
+
originalUrl: window.location.href,
|
|
643
|
+
hostname: s,
|
|
644
|
+
domainParts: r.length,
|
|
645
|
+
cleanDomain: a,
|
|
646
|
+
protocol: o,
|
|
647
|
+
generatedUrl: l
|
|
648
|
+
}), !te(l, e))
|
|
649
|
+
throw i.clientError("URLUtils", "Generated API URL failed validation", {
|
|
650
|
+
apiUrl: l,
|
|
651
|
+
allowHttp: e
|
|
652
|
+
}), new Error("Invalid URL");
|
|
653
|
+
return i.debug("URLUtils", "API URL generation completed successfully", { apiUrl: l }), l;
|
|
654
|
+
}, se = (n, e = []) => {
|
|
655
|
+
i.debug("URLUtils", "Normalizing URL", {
|
|
656
|
+
urlLength: n.length,
|
|
657
|
+
sensitiveParamsCount: e.length
|
|
658
|
+
});
|
|
659
|
+
try {
|
|
660
|
+
const t = new URL(n), s = t.searchParams, r = Array.from(s.keys()).length;
|
|
661
|
+
let a = !1;
|
|
662
|
+
const o = [];
|
|
663
|
+
if (e.forEach((c) => {
|
|
664
|
+
s.has(c) && (s.delete(c), a = !0, o.push(c));
|
|
665
|
+
}), a && i.debug("URLUtils", "Sensitive parameters removed from URL", {
|
|
666
|
+
removedParams: o,
|
|
667
|
+
originalParamCount: r,
|
|
668
|
+
finalParamCount: Array.from(s.keys()).length
|
|
669
|
+
}), !a && n.includes("?"))
|
|
670
|
+
return i.debug("URLUtils", "URL normalization - no changes needed"), n;
|
|
671
|
+
t.search = s.toString();
|
|
672
|
+
const l = t.toString();
|
|
673
|
+
return i.debug("URLUtils", "URL normalization completed", {
|
|
674
|
+
hasChanged: a,
|
|
675
|
+
originalLength: n.length,
|
|
676
|
+
normalizedLength: l.length
|
|
677
|
+
}), l;
|
|
678
|
+
} catch (t) {
|
|
679
|
+
return i.warn("URLUtils", "URL normalization failed, returning original", {
|
|
680
|
+
url: n.slice(0, 100),
|
|
681
|
+
error: t instanceof Error ? t.message : t
|
|
682
|
+
}), n;
|
|
683
|
+
}
|
|
684
|
+
}, bt = (n, e = []) => {
|
|
685
|
+
if (i.debug("URLUtils", "Checking if URL path is excluded", {
|
|
686
|
+
urlLength: n.length,
|
|
687
|
+
excludedPathsCount: e.length
|
|
688
|
+
}), e.length === 0)
|
|
689
|
+
return i.debug("URLUtils", "No excluded paths configured"), !1;
|
|
690
|
+
let t;
|
|
691
|
+
try {
|
|
692
|
+
t = new URL(n, window.location.origin).pathname, i.debug("URLUtils", "Extracted path from URL", { path: t });
|
|
693
|
+
} catch (c) {
|
|
694
|
+
return i.warn("URLUtils", "Failed to parse URL for path exclusion check", {
|
|
695
|
+
url: n.slice(0, 100),
|
|
696
|
+
error: c instanceof Error ? c.message : c
|
|
697
|
+
}), !1;
|
|
698
|
+
}
|
|
699
|
+
const s = (c) => typeof c == "object" && c !== void 0 && typeof c.test == "function", r = (c) => c.replaceAll(/[$()*+.?[\\\]^{|}]/g, "\\$&"), a = (c) => new RegExp(
|
|
700
|
+
"^" + c.split("*").map((d) => r(d)).join(".+") + "$"
|
|
701
|
+
), o = e.find((c) => {
|
|
702
|
+
try {
|
|
703
|
+
if (s(c)) {
|
|
704
|
+
const u = c.test(t);
|
|
705
|
+
return u && i.debug("URLUtils", "Path matched regex pattern", { path: t, pattern: c.toString() }), u;
|
|
706
|
+
}
|
|
707
|
+
if (c.includes("*")) {
|
|
708
|
+
const u = a(c), g = u.test(t);
|
|
709
|
+
return g && i.debug("URLUtils", "Path matched wildcard pattern", { path: t, pattern: c, regex: u.toString() }), g;
|
|
710
|
+
}
|
|
711
|
+
const d = c === t;
|
|
712
|
+
return d && i.debug("URLUtils", "Path matched exact pattern", { path: t, pattern: c }), d;
|
|
713
|
+
} catch (d) {
|
|
714
|
+
return i.warn("URLUtils", "Error testing exclusion pattern", {
|
|
715
|
+
pattern: c,
|
|
716
|
+
path: t,
|
|
717
|
+
error: d instanceof Error ? d.message : d
|
|
718
|
+
}), !1;
|
|
719
|
+
}
|
|
720
|
+
}), l = !!o;
|
|
721
|
+
return i.debug("URLUtils", "URL path exclusion check completed", {
|
|
722
|
+
path: t,
|
|
723
|
+
isExcluded: l,
|
|
724
|
+
matchedPattern: o ?? null,
|
|
725
|
+
totalPatternsChecked: e.length
|
|
726
|
+
}), l;
|
|
727
|
+
};
|
|
728
|
+
class Et {
|
|
729
|
+
getUrl(e, t = !1) {
|
|
730
|
+
const s = yt(e, t);
|
|
731
|
+
if (!te(s, t))
|
|
732
|
+
throw new Error("Invalid URL");
|
|
733
|
+
return s;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
class wt {
|
|
737
|
+
async get(e, t) {
|
|
738
|
+
if (t.id === C.HttpSkip)
|
|
739
|
+
return i.debug("ConfigManager", "Using special project id"), this.getDefaultConfig(t);
|
|
740
|
+
i.debug("ConfigManager", "Loading config from API", { apiUrl: e, projectId: t.id });
|
|
741
|
+
const s = await this.load(e, t, t.id === C.HttpLocal);
|
|
742
|
+
return i.info("ConfigManager", "Config loaded successfully", {
|
|
743
|
+
projectId: t.id,
|
|
744
|
+
mode: s.mode,
|
|
745
|
+
hasExcludedPaths: !!s.excludedUrlPaths?.length,
|
|
746
|
+
hasGlobalMetadata: !!s.globalMetadata
|
|
747
|
+
}), s;
|
|
748
|
+
}
|
|
749
|
+
async load(e, t, s) {
|
|
750
|
+
try {
|
|
751
|
+
const r = s ? `${window.location.origin}/config` : this.getUrl(e);
|
|
752
|
+
if (!r)
|
|
753
|
+
throw new Error("Config URL is not valid or not allowed");
|
|
754
|
+
const a = await fetch(r, {
|
|
755
|
+
method: "GET",
|
|
756
|
+
headers: { "Content-Type": "application/json" }
|
|
757
|
+
});
|
|
758
|
+
if (!a.ok) {
|
|
759
|
+
const A = `HTTP ${a.status}: ${a.statusText}`;
|
|
760
|
+
throw i.error("ConfigManager", "Config API request failed", {
|
|
761
|
+
status: a.status,
|
|
762
|
+
statusText: a.statusText,
|
|
763
|
+
configUrl: r
|
|
764
|
+
}), new Error(A);
|
|
765
|
+
}
|
|
766
|
+
const o = await a.json();
|
|
767
|
+
if (o == null || typeof o != "object" || Array.isArray(o))
|
|
768
|
+
throw i.error("ConfigManager", "Invalid config API response format", {
|
|
769
|
+
responseType: typeof o,
|
|
770
|
+
isArray: Array.isArray(o)
|
|
771
|
+
}), new Error("Invalid config API response: expected object");
|
|
772
|
+
const l = gt(o), d = { ...{ ...Ae, ...l }, ...t };
|
|
773
|
+
new URLSearchParams(window.location.search).get("qaMode") === "true" && !d.mode && (d.mode = N.QA, i.info("ConfigManager", "QA mode enabled via URL parameter"));
|
|
774
|
+
const y = Object.values(N).includes(d.mode) ? 1 : d.errorSampling ?? 0.1;
|
|
775
|
+
return { ...d, errorSampling: y };
|
|
776
|
+
} catch (r) {
|
|
777
|
+
const a = r instanceof Error ? r.message : "Unknown error";
|
|
778
|
+
throw i.error("ConfigManager", "Failed to load config", { error: a, apiUrl: e }), new Error(`Failed to load config: ${a}`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
getUrl(e) {
|
|
782
|
+
const s = new URLSearchParams(window.location.search).get("qaMode") === "true";
|
|
783
|
+
let r = `${e}/config`;
|
|
784
|
+
if (s && (r += "?qaMode=true"), !te(r))
|
|
785
|
+
throw i.clientError("ConfigManager", "Invalid config URL provided", { configUrl: r }), new Error("Config URL is not valid or not allowed");
|
|
786
|
+
return r;
|
|
787
|
+
}
|
|
788
|
+
getDefaultConfig(e) {
|
|
789
|
+
return Je({
|
|
790
|
+
...e,
|
|
791
|
+
errorSampling: 1,
|
|
792
|
+
...Object.values(C).includes(e.id) && { mode: N.DEBUG }
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
class It extends p {
|
|
797
|
+
storeManager;
|
|
798
|
+
queueStorageKey;
|
|
799
|
+
retryDelay = he;
|
|
800
|
+
retryTimeoutId = null;
|
|
801
|
+
lastAsyncSend = 0;
|
|
802
|
+
lastSyncSend = 0;
|
|
803
|
+
constructor(e) {
|
|
804
|
+
super(), this.storeManager = e, this.queueStorageKey = `${tt(this.get("config")?.id)}:${this.get("userId")}`, this.recoverPersistedEvents();
|
|
805
|
+
}
|
|
806
|
+
async sendEventsQueueAsync(e) {
|
|
807
|
+
return this.executeSend(e, () => this.sendQueueAsync(e));
|
|
808
|
+
}
|
|
809
|
+
sendEventsQueueSync(e) {
|
|
810
|
+
return this.executeSendSync(e, () => this.sendQueueSync(e));
|
|
811
|
+
}
|
|
812
|
+
sendEventsQueue(e) {
|
|
813
|
+
return this.executeSendSync(e, () => this.sendQueue(e));
|
|
814
|
+
}
|
|
815
|
+
recoverPersistedEvents() {
|
|
816
|
+
try {
|
|
817
|
+
const e = this.getPersistedData();
|
|
818
|
+
if (!e || !this.isDataRecent(e) || e.events.length === 0) {
|
|
819
|
+
this.clearPersistedEvents();
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
const t = this.createRecoveryBody(e);
|
|
823
|
+
this.sendRecoveredEvents(t) ? (i.info("SenderManager", "Persisted events recovered successfully", {
|
|
824
|
+
eventsCount: e.events.length,
|
|
825
|
+
sessionId: e.sessionId
|
|
826
|
+
}), this.clearPersistedEvents()) : (i.warn("SenderManager", "Failed to recover persisted events, scheduling retry", {
|
|
827
|
+
eventsCount: e.events.length
|
|
828
|
+
}), this.scheduleRetryForRecoveredEvents(t));
|
|
829
|
+
} catch (e) {
|
|
830
|
+
i.error("SenderManager", "Failed to recover persisted events", { error: e });
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
stop() {
|
|
834
|
+
this.clearRetryTimeout(), this.resetRetryState();
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Sends recovered events without re-deduplication since they were already processed
|
|
838
|
+
*/
|
|
839
|
+
sendRecoveredEvents(e) {
|
|
840
|
+
return this.executeSendSync(e, () => this.sendQueue(e));
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Schedules retry for recovered events using the specific recovery method
|
|
844
|
+
*/
|
|
845
|
+
scheduleRetryForRecoveredEvents(e) {
|
|
846
|
+
this.retryTimeoutId === null && (this.retryTimeoutId = window.setTimeout(() => {
|
|
847
|
+
this.retryTimeoutId = null, this.sendRecoveredEvents(e);
|
|
848
|
+
}, this.retryDelay), this.retryDelay = Math.min(this.retryDelay * 2, ue));
|
|
849
|
+
}
|
|
850
|
+
canSendAsync() {
|
|
851
|
+
return Date.now() - this.lastAsyncSend >= ge;
|
|
852
|
+
}
|
|
853
|
+
canSendSync() {
|
|
854
|
+
return Date.now() - this.lastSyncSend >= ge;
|
|
855
|
+
}
|
|
856
|
+
async sendQueueAsync(e) {
|
|
857
|
+
const { url: t, payload: s } = this.prepareRequest(e);
|
|
858
|
+
try {
|
|
859
|
+
return (await fetch(t, {
|
|
860
|
+
method: "POST",
|
|
861
|
+
headers: {
|
|
862
|
+
"Content-Type": "application/json"
|
|
863
|
+
},
|
|
864
|
+
body: s
|
|
865
|
+
})).ok;
|
|
866
|
+
} catch (r) {
|
|
867
|
+
return i.error("SenderManager", "Failed to send events async", { error: r }), !1;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
sendQueueSync(e) {
|
|
871
|
+
const { url: t, payload: s } = this.prepareRequest(e);
|
|
872
|
+
return this.isSendBeaconAvailable() && navigator.sendBeacon(t, s) ? !0 : this.sendSyncXHR(t, s);
|
|
873
|
+
}
|
|
874
|
+
sendQueue(e) {
|
|
875
|
+
if (!this.isSendBeaconAvailable())
|
|
876
|
+
return !1;
|
|
877
|
+
const { url: t, payload: s } = this.prepareRequest(e);
|
|
878
|
+
return navigator.sendBeacon(t, s);
|
|
879
|
+
}
|
|
880
|
+
sendSyncXHR(e, t) {
|
|
881
|
+
try {
|
|
882
|
+
const s = new XMLHttpRequest();
|
|
883
|
+
return s.open("POST", e, !1), s.setRequestHeader("Content-Type", "application/json"), s.timeout = xe, s.send(t), s.status >= 200 && s.status < 300;
|
|
884
|
+
} catch (s) {
|
|
885
|
+
return i.error("SenderManager", "Sync XHR failed", { error: s }), !1;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
prepareRequest(e) {
|
|
889
|
+
return {
|
|
890
|
+
url: `${this.get("config").id === C.HttpLocal ? window.location.origin : this.get("apiUrl") ?? Ye}/events`,
|
|
891
|
+
payload: JSON.stringify(e)
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
getPersistedData() {
|
|
895
|
+
const e = this.storeManager.getItem(this.queueStorageKey);
|
|
896
|
+
return e ? JSON.parse(e) : null;
|
|
897
|
+
}
|
|
898
|
+
isDataRecent(e) {
|
|
899
|
+
return (Date.now() - e.timestamp) / 36e5 < Ge;
|
|
900
|
+
}
|
|
901
|
+
createRecoveryBody(e) {
|
|
902
|
+
return {
|
|
903
|
+
user_id: e.userId,
|
|
904
|
+
session_id: e.sessionId,
|
|
905
|
+
device: e.device,
|
|
906
|
+
events: e.events,
|
|
907
|
+
...e.global_metadata && { global_metadata: e.global_metadata }
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
logQueue(e) {
|
|
911
|
+
i.info("SenderManager", " ⏩ Queue snapshot", e);
|
|
912
|
+
}
|
|
913
|
+
handleSendFailure(e) {
|
|
914
|
+
this.persistFailedEvents(e), this.scheduleRetry(e);
|
|
915
|
+
}
|
|
916
|
+
persistFailedEvents(e) {
|
|
917
|
+
try {
|
|
918
|
+
const t = {
|
|
919
|
+
userId: e.user_id,
|
|
920
|
+
sessionId: e.session_id,
|
|
921
|
+
device: e.device,
|
|
922
|
+
events: e.events,
|
|
923
|
+
timestamp: Date.now(),
|
|
924
|
+
...e.global_metadata && { global_metadata: e.global_metadata }
|
|
925
|
+
};
|
|
926
|
+
this.storeManager.setItem(this.queueStorageKey, JSON.stringify(t));
|
|
927
|
+
} catch (t) {
|
|
928
|
+
i.error("SenderManager", "Failed to persist events", { error: t });
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
clearPersistedEvents() {
|
|
932
|
+
this.storeManager.removeItem(this.queueStorageKey);
|
|
933
|
+
}
|
|
934
|
+
resetRetryState() {
|
|
935
|
+
this.retryDelay = he, this.clearRetryTimeout();
|
|
936
|
+
}
|
|
937
|
+
scheduleRetry(e) {
|
|
938
|
+
this.retryTimeoutId === null && (this.retryTimeoutId = window.setTimeout(() => {
|
|
939
|
+
this.retryTimeoutId = null, this.sendEventsQueue(e);
|
|
940
|
+
}, this.retryDelay), this.retryDelay = Math.min(this.retryDelay * 2, ue));
|
|
941
|
+
}
|
|
942
|
+
async executeSend(e, t) {
|
|
943
|
+
if (this.shouldSkipSend())
|
|
944
|
+
return this.logQueue(e), !0;
|
|
945
|
+
if (!this.canSendAsync())
|
|
946
|
+
return i.info("SenderManager", "⏱️ Rate limited - skipping async send", {
|
|
947
|
+
eventsCount: e.events.length,
|
|
948
|
+
timeSinceLastSend: Date.now() - this.lastAsyncSend
|
|
949
|
+
}), !1;
|
|
950
|
+
i.info("SenderManager", "🌐 Sending events to server (async)", {
|
|
951
|
+
eventsCount: e.events.length,
|
|
952
|
+
sessionId: e.session_id,
|
|
953
|
+
userId: e.user_id
|
|
954
|
+
}), this.lastAsyncSend = Date.now();
|
|
955
|
+
try {
|
|
956
|
+
const s = await t();
|
|
957
|
+
return s ? (i.info("SenderManager", "✅ Successfully sent events to server", {
|
|
958
|
+
eventsCount: e.events.length,
|
|
959
|
+
method: "async"
|
|
960
|
+
}), this.resetRetryState(), this.clearPersistedEvents()) : (i.warn("SenderManager", "Failed to send events", {
|
|
961
|
+
eventsCount: e.events.length,
|
|
962
|
+
method: "async"
|
|
963
|
+
}), this.handleSendFailure(e)), s;
|
|
964
|
+
} catch {
|
|
965
|
+
return this.handleSendFailure(e), !1;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
executeSendSync(e, t) {
|
|
969
|
+
if (this.shouldSkipSend())
|
|
970
|
+
return this.logQueue(e), !0;
|
|
971
|
+
if (!this.canSendSync())
|
|
972
|
+
return i.info("SenderManager", "⏱️ Rate limited - skipping sync send", {
|
|
973
|
+
eventsCount: e.events.length,
|
|
974
|
+
timeSinceLastSend: Date.now() - this.lastSyncSend
|
|
975
|
+
}), !1;
|
|
976
|
+
i.info("SenderManager", "🌐 Sending events to server (sync)", {
|
|
977
|
+
eventsCount: e.events.length,
|
|
978
|
+
sessionId: e.session_id,
|
|
979
|
+
userId: e.user_id,
|
|
980
|
+
method: "sendBeacon/XHR"
|
|
981
|
+
}), this.lastSyncSend = Date.now();
|
|
982
|
+
try {
|
|
983
|
+
const s = t();
|
|
984
|
+
return s ? (i.info("SenderManager", "✅ Successfully sent events to server", {
|
|
985
|
+
eventsCount: e.events.length,
|
|
986
|
+
method: "sync"
|
|
987
|
+
}), this.resetRetryState(), this.clearPersistedEvents()) : (i.warn("SenderManager", "Failed to send events", {
|
|
988
|
+
eventsCount: e.events.length,
|
|
989
|
+
method: "sync"
|
|
990
|
+
}), this.handleSendFailure(e)), s;
|
|
991
|
+
} catch {
|
|
992
|
+
return i.info("SenderManager", "💥 Exception during event sending", {
|
|
993
|
+
eventsCount: e.events.length,
|
|
994
|
+
method: "sync"
|
|
995
|
+
}), this.handleSendFailure(e), !1;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
shouldSkipSend() {
|
|
999
|
+
const { id: e, mode: t } = this.get("config"), s = [N.QA, N.DEBUG];
|
|
1000
|
+
return e === C.HttpSkip ? !0 : !!t && s.includes(t) && e !== C.HttpLocal;
|
|
1001
|
+
}
|
|
1002
|
+
isSendBeaconAvailable() {
|
|
1003
|
+
return typeof navigator.sendBeacon == "function";
|
|
1004
|
+
}
|
|
1005
|
+
clearRetryTimeout() {
|
|
1006
|
+
this.retryTimeoutId !== null && (clearTimeout(this.retryTimeoutId), this.retryTimeoutId = null);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
class Tt extends p {
|
|
1010
|
+
shouldSampleEvent(e, t) {
|
|
1011
|
+
return this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug" ? !0 : e === h.WEB_VITALS ? this.isWebVitalEventSampledIn(t?.type) : this.isSampledIn();
|
|
1012
|
+
}
|
|
1013
|
+
isSampledIn() {
|
|
1014
|
+
const e = this.get("config").samplingRate ?? Ie;
|
|
1015
|
+
return e >= 1 ? !0 : e <= 0 ? !1 : this.getHash(this.get("userId")) % 100 / 100 < e;
|
|
1016
|
+
}
|
|
1017
|
+
isWebVitalEventSampledIn(e) {
|
|
1018
|
+
const t = e === "LONG_TASK", s = t ? De : He;
|
|
1019
|
+
if (s >= 1) return !0;
|
|
1020
|
+
if (s <= 0) return !1;
|
|
1021
|
+
const r = `${this.get("userId")}|${t ? "long_task" : "web_vitals"}`;
|
|
1022
|
+
return this.getHash(r) % 100 / 100 < s;
|
|
1023
|
+
}
|
|
1024
|
+
getHash(e) {
|
|
1025
|
+
let t = 0;
|
|
1026
|
+
for (let s = 0; s < e.length; s++) {
|
|
1027
|
+
const r = e.charCodeAt(s);
|
|
1028
|
+
t = (t << 5) - t + r, t |= 0;
|
|
1029
|
+
}
|
|
1030
|
+
return Math.abs(t);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
class Mt extends p {
|
|
1034
|
+
getEventTagsIds(e, t) {
|
|
1035
|
+
switch (e.type) {
|
|
1036
|
+
case h.PAGE_VIEW:
|
|
1037
|
+
return this.checkEventTypePageView(e, t);
|
|
1038
|
+
case h.CLICK:
|
|
1039
|
+
return this.checkEventTypeClick(e, t);
|
|
1040
|
+
default:
|
|
1041
|
+
return [];
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
checkEventTypePageView(e, t) {
|
|
1045
|
+
const s = this.get("config")?.tags?.filter((a) => a.triggerType === h.PAGE_VIEW) ?? [];
|
|
1046
|
+
if (s.length === 0)
|
|
1047
|
+
return [];
|
|
1048
|
+
const r = [];
|
|
1049
|
+
for (const a of s) {
|
|
1050
|
+
const { id: o, logicalOperator: l, conditions: c } = a, d = [];
|
|
1051
|
+
for (const g of c)
|
|
1052
|
+
switch (g.type) {
|
|
1053
|
+
case m.URL_MATCHES: {
|
|
1054
|
+
d.push(this.matchUrlMatches(g, e.page_url));
|
|
1055
|
+
break;
|
|
1056
|
+
}
|
|
1057
|
+
case m.DEVICE_TYPE: {
|
|
1058
|
+
d.push(this.matchDeviceType(g, t));
|
|
1059
|
+
break;
|
|
1060
|
+
}
|
|
1061
|
+
case m.UTM_SOURCE: {
|
|
1062
|
+
d.push(this.matchUtmCondition(g, e.utm?.source));
|
|
1063
|
+
break;
|
|
1064
|
+
}
|
|
1065
|
+
case m.UTM_MEDIUM: {
|
|
1066
|
+
d.push(this.matchUtmCondition(g, e.utm?.medium));
|
|
1067
|
+
break;
|
|
1068
|
+
}
|
|
1069
|
+
case m.UTM_CAMPAIGN: {
|
|
1070
|
+
d.push(this.matchUtmCondition(g, e.utm?.campaign));
|
|
1071
|
+
break;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
let u = !1;
|
|
1075
|
+
u = l === G.AND ? d.every(Boolean) : d.some(Boolean), u && r.push(o);
|
|
1076
|
+
}
|
|
1077
|
+
return r;
|
|
1078
|
+
}
|
|
1079
|
+
checkEventTypeClick(e, t) {
|
|
1080
|
+
const s = this.get("config")?.tags?.filter((a) => a.triggerType === h.CLICK) ?? [];
|
|
1081
|
+
if (s.length === 0)
|
|
1082
|
+
return [];
|
|
1083
|
+
const r = [];
|
|
1084
|
+
for (const a of s) {
|
|
1085
|
+
const { id: o, logicalOperator: l, conditions: c } = a, d = [];
|
|
1086
|
+
for (const g of c) {
|
|
1087
|
+
if (!e.click_data) {
|
|
1088
|
+
d.push(!1);
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
const y = e.click_data;
|
|
1092
|
+
switch (g.type) {
|
|
1093
|
+
case m.ELEMENT_MATCHES: {
|
|
1094
|
+
d.push(this.matchElementSelector(g, y));
|
|
1095
|
+
break;
|
|
1096
|
+
}
|
|
1097
|
+
case m.DEVICE_TYPE: {
|
|
1098
|
+
d.push(this.matchDeviceType(g, t));
|
|
1099
|
+
break;
|
|
1100
|
+
}
|
|
1101
|
+
case m.URL_MATCHES: {
|
|
1102
|
+
d.push(this.matchUrlMatches(g, e.page_url));
|
|
1103
|
+
break;
|
|
1104
|
+
}
|
|
1105
|
+
case m.UTM_SOURCE: {
|
|
1106
|
+
d.push(this.matchUtmCondition(g, e.utm?.source));
|
|
1107
|
+
break;
|
|
1108
|
+
}
|
|
1109
|
+
case m.UTM_MEDIUM: {
|
|
1110
|
+
d.push(this.matchUtmCondition(g, e.utm?.medium));
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
case m.UTM_CAMPAIGN: {
|
|
1114
|
+
d.push(this.matchUtmCondition(g, e.utm?.campaign));
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
let u = !1;
|
|
1120
|
+
u = l === G.AND ? d.every(Boolean) : d.some(Boolean), u && r.push(o);
|
|
1121
|
+
}
|
|
1122
|
+
return r;
|
|
1123
|
+
}
|
|
1124
|
+
matchUrlMatches(e, t) {
|
|
1125
|
+
if (e.type !== m.URL_MATCHES)
|
|
1126
|
+
return !1;
|
|
1127
|
+
const s = e.value.toLowerCase(), r = t.toLowerCase();
|
|
1128
|
+
switch (e.operator) {
|
|
1129
|
+
case f.EQUALS:
|
|
1130
|
+
return r === s;
|
|
1131
|
+
case f.CONTAINS:
|
|
1132
|
+
return r.includes(s);
|
|
1133
|
+
case f.STARTS_WITH:
|
|
1134
|
+
return r.startsWith(s);
|
|
1135
|
+
case f.ENDS_WITH:
|
|
1136
|
+
return r.endsWith(s);
|
|
1137
|
+
case f.REGEX:
|
|
1138
|
+
try {
|
|
1139
|
+
return new RegExp(s, "gi").test(r);
|
|
1140
|
+
} catch {
|
|
1141
|
+
return !1;
|
|
1142
|
+
}
|
|
1143
|
+
default:
|
|
1144
|
+
return !1;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
matchDeviceType(e, t) {
|
|
1148
|
+
if (e.type !== m.DEVICE_TYPE)
|
|
1149
|
+
return !1;
|
|
1150
|
+
const s = e.value.toLowerCase(), r = t.toLowerCase();
|
|
1151
|
+
switch (e.operator) {
|
|
1152
|
+
case f.EQUALS:
|
|
1153
|
+
return r === s;
|
|
1154
|
+
case f.CONTAINS:
|
|
1155
|
+
return r.includes(s);
|
|
1156
|
+
case f.STARTS_WITH:
|
|
1157
|
+
return r.startsWith(s);
|
|
1158
|
+
case f.ENDS_WITH:
|
|
1159
|
+
return r.endsWith(s);
|
|
1160
|
+
case f.REGEX:
|
|
1161
|
+
try {
|
|
1162
|
+
return new RegExp(s, "gi").test(r);
|
|
1163
|
+
} catch {
|
|
1164
|
+
return !1;
|
|
1165
|
+
}
|
|
1166
|
+
default:
|
|
1167
|
+
return !1;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
matchElementSelector(e, t) {
|
|
1171
|
+
if (e.type !== m.ELEMENT_MATCHES)
|
|
1172
|
+
return !1;
|
|
1173
|
+
const s = [
|
|
1174
|
+
t.id ?? "",
|
|
1175
|
+
t.class ?? "",
|
|
1176
|
+
t.tag ?? "",
|
|
1177
|
+
t.text ?? "",
|
|
1178
|
+
t.href ?? "",
|
|
1179
|
+
t.title ?? "",
|
|
1180
|
+
t.alt ?? "",
|
|
1181
|
+
t.role ?? "",
|
|
1182
|
+
t.ariaLabel ?? "",
|
|
1183
|
+
...Object.values(t.dataAttributes ?? {})
|
|
1184
|
+
].join(" "), r = e.value.toLowerCase(), a = s.toLowerCase();
|
|
1185
|
+
switch (e.operator) {
|
|
1186
|
+
case f.EQUALS:
|
|
1187
|
+
return this.checkElementFieldEquals(t, r);
|
|
1188
|
+
case f.CONTAINS:
|
|
1189
|
+
return a.includes(r);
|
|
1190
|
+
case f.STARTS_WITH:
|
|
1191
|
+
return a.startsWith(r);
|
|
1192
|
+
case f.ENDS_WITH:
|
|
1193
|
+
return a.endsWith(r);
|
|
1194
|
+
case f.REGEX:
|
|
1195
|
+
try {
|
|
1196
|
+
return new RegExp(r, "gi").test(a);
|
|
1197
|
+
} catch {
|
|
1198
|
+
return !1;
|
|
1199
|
+
}
|
|
1200
|
+
default:
|
|
1201
|
+
return !1;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
matchUtmCondition(e, t) {
|
|
1205
|
+
if (![m.UTM_SOURCE, m.UTM_MEDIUM, m.UTM_CAMPAIGN].includes(
|
|
1206
|
+
e.type
|
|
1207
|
+
))
|
|
1208
|
+
return !1;
|
|
1209
|
+
const s = t ?? "", r = e.value.toLowerCase(), a = s.toLowerCase();
|
|
1210
|
+
switch (e.operator) {
|
|
1211
|
+
case f.EQUALS:
|
|
1212
|
+
return a === r;
|
|
1213
|
+
case f.CONTAINS:
|
|
1214
|
+
return a.includes(r);
|
|
1215
|
+
case f.STARTS_WITH:
|
|
1216
|
+
return a.startsWith(r);
|
|
1217
|
+
case f.ENDS_WITH:
|
|
1218
|
+
return a.endsWith(r);
|
|
1219
|
+
case f.REGEX:
|
|
1220
|
+
try {
|
|
1221
|
+
return new RegExp(r, "gi").test(a);
|
|
1222
|
+
} catch {
|
|
1223
|
+
return !1;
|
|
1224
|
+
}
|
|
1225
|
+
default:
|
|
1226
|
+
return !1;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
checkElementFieldEquals(e, t) {
|
|
1230
|
+
const s = [
|
|
1231
|
+
e.id,
|
|
1232
|
+
e.class,
|
|
1233
|
+
e.tag,
|
|
1234
|
+
e.text,
|
|
1235
|
+
e.href,
|
|
1236
|
+
e.title,
|
|
1237
|
+
e.alt,
|
|
1238
|
+
e.role,
|
|
1239
|
+
e.ariaLabel
|
|
1240
|
+
];
|
|
1241
|
+
for (const r of s)
|
|
1242
|
+
if (r) {
|
|
1243
|
+
const a = r.toLowerCase(), o = t.toLowerCase();
|
|
1244
|
+
if (a === o)
|
|
1245
|
+
return !0;
|
|
1246
|
+
}
|
|
1247
|
+
if (e.dataAttributes)
|
|
1248
|
+
for (const r of Object.values(e.dataAttributes)) {
|
|
1249
|
+
const a = r.toLowerCase(), o = t.toLowerCase();
|
|
1250
|
+
if (a === o)
|
|
1251
|
+
return !0;
|
|
1252
|
+
}
|
|
1253
|
+
return !1;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
class At extends p {
|
|
1257
|
+
googleAnalytics;
|
|
1258
|
+
samplingManager;
|
|
1259
|
+
tagsManager;
|
|
1260
|
+
dataSender;
|
|
1261
|
+
storageManager;
|
|
1262
|
+
eventsQueue = [];
|
|
1263
|
+
lastEvent = null;
|
|
1264
|
+
eventsQueueIntervalId = null;
|
|
1265
|
+
intervalActive = !1;
|
|
1266
|
+
// Circuit breaker properties
|
|
1267
|
+
failureCount = 0;
|
|
1268
|
+
MAX_FAILURES = b.MAX_FAILURES;
|
|
1269
|
+
circuitOpen = !1;
|
|
1270
|
+
circuitOpenTime = 0;
|
|
1271
|
+
backoffDelay = b.INITIAL_BACKOFF_DELAY_MS;
|
|
1272
|
+
circuitResetTimeoutId = null;
|
|
1273
|
+
// Event deduplication properties
|
|
1274
|
+
eventFingerprints = /* @__PURE__ */ new Map();
|
|
1275
|
+
// Persistence storage key
|
|
1276
|
+
PERSISTENCE_KEY = "tl:circuit_breaker_events";
|
|
1277
|
+
constructor(e, t = null) {
|
|
1278
|
+
super(), this.storageManager = e, this.googleAnalytics = t, this.samplingManager = new Tt(), this.tagsManager = new Mt(), this.dataSender = new It(e), this.restoreEventsFromStorage(), i.debug("EventManager", "EventManager initialized", {
|
|
1279
|
+
hasGoogleAnalytics: !!t,
|
|
1280
|
+
restoredEventsCount: this.eventsQueue.length
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
track({
|
|
1284
|
+
type: e,
|
|
1285
|
+
page_url: t,
|
|
1286
|
+
from_page_url: s,
|
|
1287
|
+
scroll_data: r,
|
|
1288
|
+
click_data: a,
|
|
1289
|
+
custom_event: o,
|
|
1290
|
+
web_vitals: l,
|
|
1291
|
+
session_end_reason: c,
|
|
1292
|
+
session_start_recovered: d
|
|
1293
|
+
}) {
|
|
1294
|
+
if (i.info("EventManager", `📥 Event captured: ${e}`, {
|
|
1295
|
+
type: e,
|
|
1296
|
+
page_url: t,
|
|
1297
|
+
hasCustomEvent: !!o,
|
|
1298
|
+
hasClickData: !!a,
|
|
1299
|
+
hasScrollData: !!r,
|
|
1300
|
+
hasWebVitals: !!l
|
|
1301
|
+
}), !this.samplingManager.shouldSampleEvent(e, l)) {
|
|
1302
|
+
i.debug("EventManager", "Event filtered by sampling", { type: e, samplingActive: !0 });
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
if (this.isDuplicatedEvent({
|
|
1306
|
+
type: e,
|
|
1307
|
+
page_url: t,
|
|
1308
|
+
scroll_data: r,
|
|
1309
|
+
click_data: a,
|
|
1310
|
+
custom_event: o,
|
|
1311
|
+
web_vitals: l,
|
|
1312
|
+
session_end_reason: c,
|
|
1313
|
+
session_start_recovered: d
|
|
1314
|
+
})) {
|
|
1315
|
+
const R = Date.now();
|
|
1316
|
+
if (this.eventsQueue && this.eventsQueue.length > 0) {
|
|
1317
|
+
const P = this.eventsQueue.at(-1);
|
|
1318
|
+
P && (P.timestamp = R);
|
|
1319
|
+
}
|
|
1320
|
+
this.lastEvent && (this.lastEvent.timestamp = R), i.debug("EventManager", "Duplicate event detected, timestamp updated", {
|
|
1321
|
+
type: e,
|
|
1322
|
+
queueLength: this.eventsQueue.length
|
|
1323
|
+
});
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
const g = t || this.get("pageUrl"), y = bt(g, this.get("config").excludedUrlPaths), I = this.get("hasStartSession"), A = e == h.SESSION_END;
|
|
1327
|
+
if (y && (!A || A && !I)) {
|
|
1328
|
+
(this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug") && i.debug("EventManager", `Event ${e} on excluded route: ${t}`);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
const U = e === h.SESSION_START;
|
|
1332
|
+
U && this.set("hasStartSession", !0);
|
|
1333
|
+
const ie = U ? rt() : void 0, O = {
|
|
1334
|
+
type: e,
|
|
1335
|
+
page_url: y ? "excluded" : g,
|
|
1336
|
+
timestamp: Date.now(),
|
|
1337
|
+
...U && { referrer: document.referrer || "Direct" },
|
|
1338
|
+
...s && !y ? { from_page_url: s } : {},
|
|
1339
|
+
...r && { scroll_data: r },
|
|
1340
|
+
...a && { click_data: a },
|
|
1341
|
+
...o && { custom_event: o },
|
|
1342
|
+
...ie && { utm: ie },
|
|
1343
|
+
...l && { web_vitals: l },
|
|
1344
|
+
...c && { session_end_reason: c },
|
|
1345
|
+
...d && { session_start_recovered: d }
|
|
1346
|
+
};
|
|
1347
|
+
if (this.get("config")?.tags?.length) {
|
|
1348
|
+
const R = this.tagsManager.getEventTagsIds(O, this.get("device"));
|
|
1349
|
+
R?.length && (O.tags = this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug" ? R.map((P) => ({
|
|
1350
|
+
id: P,
|
|
1351
|
+
key: this.get("config")?.tags?.find((Le) => Le.id === P)?.key ?? ""
|
|
1352
|
+
})) : R);
|
|
1353
|
+
}
|
|
1354
|
+
this.lastEvent = O, this.processAndSend(O);
|
|
1355
|
+
}
|
|
1356
|
+
stop() {
|
|
1357
|
+
this.eventsQueueIntervalId && (clearInterval(this.eventsQueueIntervalId), this.eventsQueueIntervalId = null, this.intervalActive = !1), this.circuitResetTimeoutId && (clearTimeout(this.circuitResetTimeoutId), this.circuitResetTimeoutId = null), this.eventsQueue.length > 0 && this.persistEventsToStorage(), this.eventFingerprints.clear(), this.circuitOpen = !1, this.circuitOpenTime = 0, this.failureCount = 0, this.backoffDelay = b.INITIAL_BACKOFF_DELAY_MS, this.lastEvent = null, this.dataSender.stop();
|
|
1358
|
+
}
|
|
1359
|
+
processAndSend(e) {
|
|
1360
|
+
if (i.info("EventManager", `🔄 Event processed and queued: ${e.type}`, {
|
|
1361
|
+
type: e.type,
|
|
1362
|
+
timestamp: e.timestamp,
|
|
1363
|
+
page_url: e.page_url,
|
|
1364
|
+
queueLengthBefore: this.eventsQueue.length
|
|
1365
|
+
}), this.get("config").ipExcluded) {
|
|
1366
|
+
i.info("EventManager", "❌ Event blocked: IP excluded");
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
if (this.eventsQueue.push(e), this.eventsQueue.length > ne) {
|
|
1370
|
+
const t = this.eventsQueue.shift();
|
|
1371
|
+
i.warn("EventManager", "Event queue overflow, oldest event removed", {
|
|
1372
|
+
maxLength: ne,
|
|
1373
|
+
currentLength: this.eventsQueue.length,
|
|
1374
|
+
removedEventType: t?.type
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
if (this.eventsQueueIntervalId || (this.initEventsQueueInterval(), i.info("EventManager", "⏰ Event sender initialized - queue will be sent periodically", {
|
|
1378
|
+
queueLength: this.eventsQueue.length
|
|
1379
|
+
})), this.googleAnalytics && e.type === h.CUSTOM) {
|
|
1380
|
+
const t = e.custom_event;
|
|
1381
|
+
this.trackGoogleAnalyticsEvent(t);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
trackGoogleAnalyticsEvent(e) {
|
|
1385
|
+
this.get("config").mode === "qa" || this.get("config").mode === "debug" ? i.debug("EventManager", `Google Analytics event: ${JSON.stringify(e)}`) : this.googleAnalytics && this.googleAnalytics.trackEvent(e.name, e.metadata ?? {});
|
|
1386
|
+
}
|
|
1387
|
+
initEventsQueueInterval() {
|
|
1388
|
+
if (this.eventsQueueIntervalId || this.intervalActive)
|
|
1389
|
+
return;
|
|
1390
|
+
const e = this.get("config")?.id === "test" ? je : $e;
|
|
1391
|
+
this.eventsQueueIntervalId = window.setInterval(() => {
|
|
1392
|
+
this.eventsQueue.length > 0 && this.sendEventsQueue();
|
|
1393
|
+
}, e), this.intervalActive = !0;
|
|
1394
|
+
}
|
|
1395
|
+
async flushImmediately() {
|
|
1396
|
+
if (this.eventsQueue.length === 0)
|
|
1397
|
+
return !0;
|
|
1398
|
+
const e = this.buildEventsPayload(), t = await this.dataSender.sendEventsQueueAsync(e);
|
|
1399
|
+
return t && (this.eventsQueue = [], this.clearQueueInterval()), t;
|
|
1400
|
+
}
|
|
1401
|
+
flushImmediatelySync() {
|
|
1402
|
+
if (this.eventsQueue.length === 0)
|
|
1403
|
+
return !0;
|
|
1404
|
+
const e = this.buildEventsPayload(), t = this.dataSender.sendEventsQueueSync(e);
|
|
1405
|
+
return t && (this.eventsQueue = [], this.clearQueueInterval()), t;
|
|
1406
|
+
}
|
|
1407
|
+
getQueueLength() {
|
|
1408
|
+
return this.eventsQueue.length;
|
|
1409
|
+
}
|
|
1410
|
+
sendEventsQueue() {
|
|
1411
|
+
if (this.eventsQueue.length === 0)
|
|
1412
|
+
return;
|
|
1413
|
+
if (i.info("EventManager", "📤 Preparing to send event queue", {
|
|
1414
|
+
queueLength: this.eventsQueue.length,
|
|
1415
|
+
hasSessionId: !!this.get("sessionId"),
|
|
1416
|
+
circuitOpen: this.circuitOpen
|
|
1417
|
+
}), this.circuitOpen) {
|
|
1418
|
+
const s = Date.now() - this.circuitOpenTime;
|
|
1419
|
+
if (s >= b.RECOVERY_TIME_MS)
|
|
1420
|
+
this.resetCircuitBreaker(), i.info("EventManager", "Circuit breaker reset after timeout", {
|
|
1421
|
+
timeSinceOpen: s,
|
|
1422
|
+
recoveryTime: b.RECOVERY_TIME_MS
|
|
1423
|
+
});
|
|
1424
|
+
else {
|
|
1425
|
+
i.debug("EventManager", "Circuit breaker is open - skipping event sending", {
|
|
1426
|
+
queueLength: this.eventsQueue.length,
|
|
1427
|
+
failureCount: this.failureCount,
|
|
1428
|
+
timeSinceOpen: s,
|
|
1429
|
+
recoveryTime: b.RECOVERY_TIME_MS
|
|
1430
|
+
});
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
if (!this.get("sessionId")) {
|
|
1435
|
+
i.info("EventManager", `⏳ Queue waiting: ${this.eventsQueue.length} events waiting for active session`);
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
const e = this.buildEventsPayload();
|
|
1439
|
+
this.dataSender.sendEventsQueue(e) ? (i.info("EventManager", "✅ Event queue sent successfully", {
|
|
1440
|
+
eventsCount: e.events.length,
|
|
1441
|
+
sessionId: e.session_id,
|
|
1442
|
+
uniqueEventsAfterDedup: e.events.length
|
|
1443
|
+
}), this.eventsQueue = [], this.failureCount = 0, this.backoffDelay = b.INITIAL_BACKOFF_DELAY_MS, this.clearPersistedEvents()) : (i.info("EventManager", "❌ Failed to send event queue", {
|
|
1444
|
+
eventsCount: e.events.length,
|
|
1445
|
+
failureCount: this.failureCount + 1,
|
|
1446
|
+
willOpenCircuit: this.failureCount + 1 >= this.MAX_FAILURES
|
|
1447
|
+
}), this.persistEventsToStorage(), this.eventsQueue = [], this.failureCount++, this.failureCount >= this.MAX_FAILURES && this.openCircuitBreaker());
|
|
1448
|
+
}
|
|
1449
|
+
buildEventsPayload() {
|
|
1450
|
+
const e = /* @__PURE__ */ new Map();
|
|
1451
|
+
for (const s of this.eventsQueue) {
|
|
1452
|
+
let r = `${s.type}_${s.page_url}`;
|
|
1453
|
+
s.click_data && (r += `_${s.click_data.x}_${s.click_data.y}`), s.scroll_data && (r += `_${s.scroll_data.depth}_${s.scroll_data.direction}`), s.custom_event && (r += `_${s.custom_event.name}`), s.web_vitals && (r += `_${s.web_vitals.type}`), e.has(r) || e.set(r, s);
|
|
1454
|
+
}
|
|
1455
|
+
const t = [...e.values()];
|
|
1456
|
+
return t.sort((s, r) => s.timestamp - r.timestamp), {
|
|
1457
|
+
user_id: this.get("userId"),
|
|
1458
|
+
session_id: this.get("sessionId"),
|
|
1459
|
+
device: this.get("device"),
|
|
1460
|
+
events: t,
|
|
1461
|
+
...this.get("config")?.globalMetadata && { global_metadata: this.get("config")?.globalMetadata }
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
clearQueueInterval() {
|
|
1465
|
+
this.eventsQueueIntervalId && (clearInterval(this.eventsQueueIntervalId), this.eventsQueueIntervalId = null, this.intervalActive = !1);
|
|
1466
|
+
}
|
|
1467
|
+
getEventFingerprint(e) {
|
|
1468
|
+
const t = `${e.type}_${e.page_url}`;
|
|
1469
|
+
if (e.click_data) {
|
|
1470
|
+
const s = Math.round((e.click_data.x || 0) / F) * F, r = Math.round((e.click_data.y || 0) / F) * F;
|
|
1471
|
+
return `${t}_${s}_${r}_${e.click_data.tag}_${e.click_data.id}`;
|
|
1472
|
+
}
|
|
1473
|
+
return e.scroll_data ? `${t}_${e.scroll_data.depth}_${e.scroll_data.direction}` : e.custom_event ? `${t}_${e.custom_event.name}` : e.web_vitals ? `${t}_${e.web_vitals.type}` : e.session_end_reason ? `${t}_${e.session_end_reason}` : e.session_start_recovered !== void 0 ? `${t}_${e.session_start_recovered}` : t;
|
|
1474
|
+
}
|
|
1475
|
+
isDuplicatedEvent(e) {
|
|
1476
|
+
const t = this.getEventFingerprint(e), s = this.eventFingerprints.get(t) ?? 0, r = Date.now();
|
|
1477
|
+
return r - s < de ? !0 : (this.eventFingerprints.set(t, r), this.cleanupOldFingerprints(), !1);
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Cleans up old fingerprints to prevent memory leaks
|
|
1481
|
+
*/
|
|
1482
|
+
cleanupOldFingerprints() {
|
|
1483
|
+
if (this.eventFingerprints.size <= Oe)
|
|
1484
|
+
return;
|
|
1485
|
+
const e = Date.now(), t = de * Fe, s = [];
|
|
1486
|
+
for (const [r, a] of this.eventFingerprints)
|
|
1487
|
+
e - a > t && s.push(r);
|
|
1488
|
+
for (const r of s)
|
|
1489
|
+
this.eventFingerprints.delete(r);
|
|
1490
|
+
i.debug("EventManager", "Cleaned up old event fingerprints", {
|
|
1491
|
+
totalFingerprints: this.eventFingerprints.size + s.length,
|
|
1492
|
+
cleanedCount: s.length,
|
|
1493
|
+
remainingCount: this.eventFingerprints.size,
|
|
1494
|
+
cleanupThreshold: t
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Opens the circuit breaker with time-based recovery and event persistence
|
|
1499
|
+
*/
|
|
1500
|
+
openCircuitBreaker() {
|
|
1501
|
+
this.circuitOpen = !0, this.circuitOpenTime = Date.now(), this.persistEventsToStorage();
|
|
1502
|
+
const e = this.eventsQueue.length;
|
|
1503
|
+
this.eventsQueue = [], i.warn("EventManager", "Circuit breaker opened with time-based recovery", {
|
|
1504
|
+
maxFailures: this.MAX_FAILURES,
|
|
1505
|
+
persistedEvents: e,
|
|
1506
|
+
failureCount: this.failureCount,
|
|
1507
|
+
recoveryTime: b.RECOVERY_TIME_MS,
|
|
1508
|
+
openTime: this.circuitOpenTime
|
|
1509
|
+
}), this.backoffDelay = Math.min(
|
|
1510
|
+
this.backoffDelay * b.BACKOFF_MULTIPLIER,
|
|
1511
|
+
b.MAX_BACKOFF_DELAY_MS
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Resets the circuit breaker and attempts to restore persisted events
|
|
1516
|
+
*/
|
|
1517
|
+
resetCircuitBreaker() {
|
|
1518
|
+
this.circuitOpen = !1, this.circuitOpenTime = 0, this.failureCount = 0, this.circuitResetTimeoutId = null, i.info("EventManager", "Circuit breaker reset - attempting to restore events", {
|
|
1519
|
+
currentQueueLength: this.eventsQueue.length
|
|
1520
|
+
}), this.restoreEventsFromStorage(), i.info("EventManager", "Circuit breaker reset completed", {
|
|
1521
|
+
restoredQueueLength: this.eventsQueue.length,
|
|
1522
|
+
backoffDelay: this.backoffDelay
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Persists current events queue to localStorage for recovery
|
|
1527
|
+
*/
|
|
1528
|
+
persistEventsToStorage() {
|
|
1529
|
+
try {
|
|
1530
|
+
if (this.eventsQueue.length === 0)
|
|
1531
|
+
return;
|
|
1532
|
+
const e = {
|
|
1533
|
+
events: this.eventsQueue,
|
|
1534
|
+
timestamp: Date.now(),
|
|
1535
|
+
failureCount: this.failureCount
|
|
1536
|
+
};
|
|
1537
|
+
this.storageManager.setItem(this.PERSISTENCE_KEY, JSON.stringify(e)), i.debug("EventManager", "Events persisted to storage for recovery", {
|
|
1538
|
+
eventsCount: this.eventsQueue.length,
|
|
1539
|
+
failureCount: this.failureCount
|
|
1540
|
+
});
|
|
1541
|
+
} catch (e) {
|
|
1542
|
+
i.warn("EventManager", "Failed to persist events to storage", {
|
|
1543
|
+
error: e instanceof Error ? e.message : "Unknown error",
|
|
1544
|
+
eventsCount: this.eventsQueue.length
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* Restores events from localStorage if available and not expired
|
|
1550
|
+
*/
|
|
1551
|
+
restoreEventsFromStorage() {
|
|
1552
|
+
try {
|
|
1553
|
+
const e = this.storageManager.getItem(this.PERSISTENCE_KEY);
|
|
1554
|
+
if (!e)
|
|
1555
|
+
return;
|
|
1556
|
+
const t = JSON.parse(e), s = Date.now(), r = Qe;
|
|
1557
|
+
if (s - t.timestamp > r) {
|
|
1558
|
+
this.clearPersistedEvents(), i.debug("EventManager", "Cleared expired persisted events", {
|
|
1559
|
+
age: s - t.timestamp,
|
|
1560
|
+
maxAge: r
|
|
1561
|
+
});
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
Array.isArray(t.events) && t.events.length > 0 && this.eventsQueue.length === 0 && (this.eventsQueue = t.events, i.info("EventManager", "Restored events from storage", {
|
|
1565
|
+
restoredCount: t.events.length,
|
|
1566
|
+
originalFailureCount: t.failureCount ?? 0
|
|
1567
|
+
}));
|
|
1568
|
+
} catch (e) {
|
|
1569
|
+
i.warn("EventManager", "Failed to restore events from storage", {
|
|
1570
|
+
error: e instanceof Error ? e.message : "Unknown error"
|
|
1571
|
+
}), this.clearPersistedEvents();
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* Clears persisted events from localStorage
|
|
1576
|
+
*/
|
|
1577
|
+
clearPersistedEvents() {
|
|
1578
|
+
try {
|
|
1579
|
+
this.storageManager.removeItem(this.PERSISTENCE_KEY), i.debug("EventManager", "Cleared persisted events from storage");
|
|
1580
|
+
} catch (e) {
|
|
1581
|
+
i.warn("EventManager", "Failed to clear persisted events", {
|
|
1582
|
+
error: e instanceof Error ? e.message : "Unknown error"
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
class _t extends p {
|
|
1588
|
+
storageManager;
|
|
1589
|
+
constructor(e) {
|
|
1590
|
+
super(), this.storageManager = e;
|
|
1591
|
+
}
|
|
1592
|
+
getId() {
|
|
1593
|
+
const e = this.storageManager.getItem(ve(this.get("config")?.id));
|
|
1594
|
+
if (e)
|
|
1595
|
+
return e;
|
|
1596
|
+
const t = $();
|
|
1597
|
+
return this.storageManager.setItem(ve(this.get("config")?.id), t), t;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
class Ct {
|
|
1601
|
+
onActivity;
|
|
1602
|
+
options = { passive: !0 };
|
|
1603
|
+
constructor(e) {
|
|
1604
|
+
this.onActivity = e;
|
|
1605
|
+
}
|
|
1606
|
+
setup() {
|
|
1607
|
+
try {
|
|
1608
|
+
window.addEventListener("scroll", this.onActivity, this.options), window.addEventListener("resize", this.onActivity, this.options), window.addEventListener("focus", this.onActivity, this.options);
|
|
1609
|
+
} catch (e) {
|
|
1610
|
+
throw i.error("ActivityListenerManager", "Failed to setup activity listeners", { error: e }), e;
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
cleanup() {
|
|
1614
|
+
try {
|
|
1615
|
+
window.removeEventListener("scroll", this.onActivity), window.removeEventListener("resize", this.onActivity), window.removeEventListener("focus", this.onActivity);
|
|
1616
|
+
} catch (e) {
|
|
1617
|
+
i.warn("ActivityListenerManager", "Error during activity listeners cleanup", { error: e });
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
class Lt {
|
|
1622
|
+
onActivity;
|
|
1623
|
+
options = { passive: !0 };
|
|
1624
|
+
motionThreshold;
|
|
1625
|
+
constructor(e, t) {
|
|
1626
|
+
this.onActivity = e, this.motionThreshold = t;
|
|
1627
|
+
}
|
|
1628
|
+
setup() {
|
|
1629
|
+
try {
|
|
1630
|
+
window.addEventListener("touchstart", this.onActivity, this.options), window.addEventListener("touchmove", this.onActivity, this.options), window.addEventListener("touchend", this.onActivity, this.options), window.addEventListener("orientationchange", this.onActivity, this.options), "DeviceMotionEvent" in window && window.addEventListener("devicemotion", this.handleDeviceMotion, this.options);
|
|
1631
|
+
} catch (e) {
|
|
1632
|
+
throw i.error("TouchListenerManager", "Failed to setup touch listeners", { error: e }), e;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
cleanup() {
|
|
1636
|
+
try {
|
|
1637
|
+
window.removeEventListener("touchstart", this.onActivity), window.removeEventListener("touchmove", this.onActivity), window.removeEventListener("touchend", this.onActivity), window.removeEventListener("orientationchange", this.onActivity), "DeviceMotionEvent" in window && window.removeEventListener("devicemotion", this.handleDeviceMotion);
|
|
1638
|
+
} catch (e) {
|
|
1639
|
+
i.warn("TouchListenerManager", "Error during touch listeners cleanup", { error: e });
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
handleDeviceMotion = (e) => {
|
|
1643
|
+
try {
|
|
1644
|
+
const t = e.acceleration;
|
|
1645
|
+
t && Math.abs(t.x ?? 0) + Math.abs(t.y ?? 0) + Math.abs(t.z ?? 0) > this.motionThreshold && this.onActivity();
|
|
1646
|
+
} catch (t) {
|
|
1647
|
+
i.warn("TouchListenerManager", "Error handling device motion event", { error: t });
|
|
1648
|
+
}
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
class Rt {
|
|
1652
|
+
onActivity;
|
|
1653
|
+
options = { passive: !0 };
|
|
1654
|
+
constructor(e) {
|
|
1655
|
+
this.onActivity = e;
|
|
1656
|
+
}
|
|
1657
|
+
setup() {
|
|
1658
|
+
try {
|
|
1659
|
+
window.addEventListener("mousemove", this.onActivity, this.options), window.addEventListener("mousedown", this.onActivity, this.options), window.addEventListener("wheel", this.onActivity, this.options);
|
|
1660
|
+
} catch (e) {
|
|
1661
|
+
throw i.error("MouseListenerManager", "Failed to setup mouse listeners", { error: e }), e;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
cleanup() {
|
|
1665
|
+
try {
|
|
1666
|
+
window.removeEventListener("mousemove", this.onActivity), window.removeEventListener("mousedown", this.onActivity), window.removeEventListener("wheel", this.onActivity);
|
|
1667
|
+
} catch (e) {
|
|
1668
|
+
i.warn("MouseListenerManager", "Error during mouse listeners cleanup", { error: e });
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
class kt {
|
|
1673
|
+
onActivity;
|
|
1674
|
+
options = { passive: !0 };
|
|
1675
|
+
constructor(e) {
|
|
1676
|
+
this.onActivity = e;
|
|
1677
|
+
}
|
|
1678
|
+
setup() {
|
|
1679
|
+
try {
|
|
1680
|
+
window.addEventListener("keydown", this.onActivity, this.options), window.addEventListener("keypress", this.onActivity, this.options);
|
|
1681
|
+
} catch (e) {
|
|
1682
|
+
throw i.error("KeyboardListenerManager", "Failed to setup keyboard listeners", { error: e }), e;
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
cleanup() {
|
|
1686
|
+
try {
|
|
1687
|
+
window.removeEventListener("keydown", this.onActivity), window.removeEventListener("keypress", this.onActivity);
|
|
1688
|
+
} catch (e) {
|
|
1689
|
+
i.warn("KeyboardListenerManager", "Error during keyboard listeners cleanup", { error: e });
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
class Nt {
|
|
1694
|
+
onActivity;
|
|
1695
|
+
onVisibilityChange;
|
|
1696
|
+
isMobile;
|
|
1697
|
+
options = { passive: !0 };
|
|
1698
|
+
constructor(e, t, s) {
|
|
1699
|
+
this.onActivity = e, this.onVisibilityChange = t, this.isMobile = s;
|
|
1700
|
+
}
|
|
1701
|
+
setup() {
|
|
1702
|
+
try {
|
|
1703
|
+
"visibilityState" in document && document.addEventListener("visibilitychange", this.onVisibilityChange, this.options), window.addEventListener("blur", this.onVisibilityChange, this.options), window.addEventListener("focus", this.onActivity, this.options), "onLine" in navigator && (window.addEventListener("online", this.onActivity, this.options), window.addEventListener("offline", this.onVisibilityChange, this.options)), this.isMobile && this.setupMobileEvents();
|
|
1704
|
+
} catch (e) {
|
|
1705
|
+
throw i.error("VisibilityListenerManager", "Failed to setup visibility listeners", { error: e }), e;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
cleanup() {
|
|
1709
|
+
try {
|
|
1710
|
+
"visibilityState" in document && document.removeEventListener("visibilitychange", this.onVisibilityChange), window.removeEventListener("blur", this.onVisibilityChange), window.removeEventListener("focus", this.onActivity), "onLine" in navigator && (window.removeEventListener("online", this.onActivity), window.removeEventListener("offline", this.onVisibilityChange)), this.isMobile && this.cleanupMobileEvents();
|
|
1711
|
+
} catch (e) {
|
|
1712
|
+
i.warn("VisibilityListenerManager", "Error during visibility listeners cleanup", { error: e });
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
setupMobileEvents() {
|
|
1716
|
+
try {
|
|
1717
|
+
document.addEventListener("pause", this.onVisibilityChange, this.options), document.addEventListener("resume", this.onActivity, this.options), "orientation" in screen && screen.orientation.addEventListener("change", this.onActivity, this.options), window.addEventListener("pageshow", this.onActivity, this.options), window.addEventListener("pagehide", this.onActivity, this.options);
|
|
1718
|
+
} catch (e) {
|
|
1719
|
+
i.warn("VisibilityListenerManager", "Failed to setup mobile listeners", { error: e });
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
cleanupMobileEvents() {
|
|
1723
|
+
try {
|
|
1724
|
+
document.removeEventListener("pause", this.onVisibilityChange), document.removeEventListener("resume", this.onActivity), "orientation" in screen && screen.orientation.removeEventListener("change", this.onActivity), window.removeEventListener("pageshow", this.onActivity), window.removeEventListener("pagehide", this.onActivity);
|
|
1725
|
+
} catch (e) {
|
|
1726
|
+
i.warn("VisibilityListenerManager", "Error during mobile listeners cleanup", { error: e });
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
class Ut {
|
|
1731
|
+
onInactivity;
|
|
1732
|
+
options = { passive: !0 };
|
|
1733
|
+
constructor(e) {
|
|
1734
|
+
this.onInactivity = e;
|
|
1735
|
+
}
|
|
1736
|
+
setup() {
|
|
1737
|
+
try {
|
|
1738
|
+
window.addEventListener("beforeunload", this.onInactivity, this.options), window.addEventListener("pagehide", this.onInactivity, this.options);
|
|
1739
|
+
} catch (e) {
|
|
1740
|
+
throw i.error("UnloadListenerManager", "Failed to setup unload listeners", { error: e }), e;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
cleanup() {
|
|
1744
|
+
try {
|
|
1745
|
+
window.removeEventListener("beforeunload", this.onInactivity), window.removeEventListener("pagehide", this.onInactivity);
|
|
1746
|
+
} catch (e) {
|
|
1747
|
+
i.warn("UnloadListenerManager", "Error during unload listeners cleanup", { error: e });
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
class Ce extends p {
|
|
1752
|
+
config;
|
|
1753
|
+
storageManager;
|
|
1754
|
+
eventManager;
|
|
1755
|
+
projectId;
|
|
1756
|
+
debugMode;
|
|
1757
|
+
constructor(e, t, s, r) {
|
|
1758
|
+
super(), this.storageManager = e, this.eventManager = s ?? null, this.projectId = t, this.debugMode = (this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug") ?? !1, this.config = {
|
|
1759
|
+
recoveryWindowMs: this.calculateRecoveryWindow(),
|
|
1760
|
+
maxRecoveryAttempts: Ke,
|
|
1761
|
+
contextPreservation: !0,
|
|
1762
|
+
...r
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Attempt to recover a session
|
|
1767
|
+
*/
|
|
1768
|
+
attemptSessionRecovery(e) {
|
|
1769
|
+
this.debugMode && i.debug("SessionRecovery", "Attempting session recovery");
|
|
1770
|
+
const t = this.getStoredRecoveryAttempts(), s = this.getLastRecoveryAttempt();
|
|
1771
|
+
if (!this.canAttemptRecovery(s))
|
|
1772
|
+
return this.debugMode && i.debug(
|
|
1773
|
+
"SessionRecovery",
|
|
1774
|
+
"Session recovery not possible - outside recovery window or max attempts reached"
|
|
1775
|
+
), {
|
|
1776
|
+
recovered: !1
|
|
1777
|
+
};
|
|
1778
|
+
const r = s?.context;
|
|
1779
|
+
if (!r)
|
|
1780
|
+
return this.debugMode && i.debug("SessionRecovery", "No session context available for recovery"), {
|
|
1781
|
+
recovered: !1
|
|
1782
|
+
};
|
|
1783
|
+
const a = Date.now();
|
|
1784
|
+
if (a - r.lastActivity > this.config.recoveryWindowMs)
|
|
1785
|
+
return this.debugMode && i.debug("SessionRecovery", "Session recovery failed - outside recovery window"), {
|
|
1786
|
+
recovered: !1
|
|
1787
|
+
};
|
|
1788
|
+
const l = r.sessionId, c = (s?.attempt ?? 0) + 1, d = {
|
|
1789
|
+
sessionId: e ?? l,
|
|
1790
|
+
timestamp: a,
|
|
1791
|
+
attempt: c,
|
|
1792
|
+
context: {
|
|
1793
|
+
...r,
|
|
1794
|
+
recoveryAttempts: c,
|
|
1795
|
+
lastActivity: a
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
return t.push(d), this.storeRecoveryAttempts(t), this.debugMode && i.debug("SessionRecovery", `Session recovery successful: recovery of session ${l}`), {
|
|
1799
|
+
recovered: !0,
|
|
1800
|
+
recoveredSessionId: l,
|
|
1801
|
+
context: d.context
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Calculate the recovery window with bounds checking
|
|
1806
|
+
*/
|
|
1807
|
+
calculateRecoveryWindow() {
|
|
1808
|
+
const t = (this.get("config")?.sessionTimeout ?? L) * Xe, s = Math.max(
|
|
1809
|
+
Math.min(t, X),
|
|
1810
|
+
K
|
|
1811
|
+
);
|
|
1812
|
+
return this.debugMode && (t > X ? i.warn(
|
|
1813
|
+
"SessionRecovery",
|
|
1814
|
+
`Recovery window capped at ${X}ms (24h). Calculated: ${t}ms`
|
|
1815
|
+
) : t < K && i.warn(
|
|
1816
|
+
"SessionRecovery",
|
|
1817
|
+
`Recovery window increased to minimum ${K}ms (2min). Calculated: ${t}ms`
|
|
1818
|
+
)), s;
|
|
1819
|
+
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Check if session recovery can be attempted
|
|
1822
|
+
*/
|
|
1823
|
+
canAttemptRecovery(e) {
|
|
1824
|
+
return e ? !(Date.now() - e.context.lastActivity > this.config.recoveryWindowMs || e.attempt >= this.config.maxRecoveryAttempts) : !0;
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Store session context for potential recovery
|
|
1828
|
+
*/
|
|
1829
|
+
storeSessionContextForRecovery(e) {
|
|
1830
|
+
try {
|
|
1831
|
+
const t = this.getStoredRecoveryAttempts(), s = {
|
|
1832
|
+
sessionId: e.sessionId,
|
|
1833
|
+
timestamp: Date.now(),
|
|
1834
|
+
attempt: 0,
|
|
1835
|
+
context: e
|
|
1836
|
+
};
|
|
1837
|
+
t.push(s);
|
|
1838
|
+
const r = 5;
|
|
1839
|
+
t.length > r && t.splice(0, t.length - r), this.storeRecoveryAttempts(t), this.debugMode && i.debug("SessionRecovery", `Stored session context for recovery: ${e.sessionId}`);
|
|
1840
|
+
} catch (t) {
|
|
1841
|
+
this.debugMode && i.warn("SessionRecovery", "Failed to store session context for recovery", { error: t });
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* Get stored recovery attempts
|
|
1846
|
+
*/
|
|
1847
|
+
getStoredRecoveryAttempts() {
|
|
1848
|
+
try {
|
|
1849
|
+
const e = this.storageManager.getItem(V(this.projectId));
|
|
1850
|
+
return e ? JSON.parse(e) : [];
|
|
1851
|
+
} catch (e) {
|
|
1852
|
+
if (this.debugMode) {
|
|
1853
|
+
const t = this.storageManager.getItem(V(this.projectId));
|
|
1854
|
+
i.warn(
|
|
1855
|
+
"SessionRecovery",
|
|
1856
|
+
`Failed to parse stored recovery attempts for projectId ${this.projectId}. Data: ${t}`,
|
|
1857
|
+
{ error: e }
|
|
1858
|
+
);
|
|
1859
|
+
}
|
|
1860
|
+
return [];
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
/**
|
|
1864
|
+
* Store recovery attempts
|
|
1865
|
+
*/
|
|
1866
|
+
storeRecoveryAttempts(e) {
|
|
1867
|
+
try {
|
|
1868
|
+
this.storageManager.setItem(V(this.projectId), JSON.stringify(e));
|
|
1869
|
+
} catch (t) {
|
|
1870
|
+
this.debugMode && i.warn("SessionRecovery", "Failed to store recovery attempts", { error: t });
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
/**
|
|
1874
|
+
* Get the last recovery attempt
|
|
1875
|
+
*/
|
|
1876
|
+
getLastRecoveryAttempt() {
|
|
1877
|
+
const e = this.getStoredRecoveryAttempts();
|
|
1878
|
+
return e.length > 0 ? e[e.length - 1] : null;
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Clean up old recovery attempts
|
|
1882
|
+
*/
|
|
1883
|
+
cleanupOldRecoveryAttempts() {
|
|
1884
|
+
const e = this.getStoredRecoveryAttempts(), t = Date.now(), s = e.filter((r) => t - r.timestamp <= this.config.recoveryWindowMs);
|
|
1885
|
+
s.length !== e.length && (this.storeRecoveryAttempts(s), this.debugMode && i.debug("SessionRecovery", `Cleaned up ${e.length - s.length} old recovery attempts`));
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* Check if there's a recoverable session.
|
|
1889
|
+
* Returns false when no recovery attempts are stored.
|
|
1890
|
+
*/
|
|
1891
|
+
hasRecoverableSession() {
|
|
1892
|
+
const e = this.getLastRecoveryAttempt();
|
|
1893
|
+
return e ? this.canAttemptRecovery(e) : !1;
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Get recovery window in milliseconds
|
|
1897
|
+
*/
|
|
1898
|
+
getRecoveryWindowMs() {
|
|
1899
|
+
return this.config.recoveryWindowMs;
|
|
1900
|
+
}
|
|
1901
|
+
/**
|
|
1902
|
+
* Get max recovery attempts
|
|
1903
|
+
*/
|
|
1904
|
+
getMaxRecoveryAttempts() {
|
|
1905
|
+
return this.config.maxRecoveryAttempts;
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Clear all stored recovery data
|
|
1909
|
+
*/
|
|
1910
|
+
clearRecoveryData() {
|
|
1911
|
+
this.storageManager.removeItem(V(this.projectId)), this.debugMode && i.debug("SessionRecovery", "Cleared all recovery data");
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
class Pt extends p {
|
|
1915
|
+
config;
|
|
1916
|
+
eventManager = null;
|
|
1917
|
+
storageManager = null;
|
|
1918
|
+
listenerManagers = [];
|
|
1919
|
+
deviceCapabilities;
|
|
1920
|
+
onActivity;
|
|
1921
|
+
onInactivity;
|
|
1922
|
+
// Recovery manager
|
|
1923
|
+
recoveryManager = null;
|
|
1924
|
+
isSessionActive = !1;
|
|
1925
|
+
lastActivityTime = 0;
|
|
1926
|
+
inactivityTimer = null;
|
|
1927
|
+
sessionStartTime = 0;
|
|
1928
|
+
throttleTimeout = null;
|
|
1929
|
+
// Track visibility change timeout for proper cleanup
|
|
1930
|
+
visibilityChangeTimeout = null;
|
|
1931
|
+
// Session End Management
|
|
1932
|
+
pendingSessionEnd = !1;
|
|
1933
|
+
sessionEndPromise = null;
|
|
1934
|
+
sessionEndLock = Promise.resolve({
|
|
1935
|
+
success: !0,
|
|
1936
|
+
reason: "manual_stop",
|
|
1937
|
+
timestamp: Date.now(),
|
|
1938
|
+
eventsFlushed: 0,
|
|
1939
|
+
method: "async"
|
|
1940
|
+
});
|
|
1941
|
+
cleanupHandlers = [];
|
|
1942
|
+
sessionEndConfig;
|
|
1943
|
+
sessionEndReason = null;
|
|
1944
|
+
sessionEndPriority = {
|
|
1945
|
+
page_unload: 4,
|
|
1946
|
+
manual_stop: 3,
|
|
1947
|
+
orphaned_cleanup: 2,
|
|
1948
|
+
inactivity: 1,
|
|
1949
|
+
tab_closed: 0
|
|
1950
|
+
};
|
|
1951
|
+
sessionEndStats = {
|
|
1952
|
+
totalSessionEnds: 0,
|
|
1953
|
+
successfulEnds: 0,
|
|
1954
|
+
failedEnds: 0,
|
|
1955
|
+
duplicatePrevented: 0,
|
|
1956
|
+
reasonCounts: {
|
|
1957
|
+
inactivity: 0,
|
|
1958
|
+
page_unload: 0,
|
|
1959
|
+
manual_stop: 0,
|
|
1960
|
+
orphaned_cleanup: 0,
|
|
1961
|
+
tab_closed: 0
|
|
1962
|
+
}
|
|
1963
|
+
};
|
|
1964
|
+
// Session health monitoring
|
|
1965
|
+
sessionHealth = {
|
|
1966
|
+
recoveryAttempts: 0,
|
|
1967
|
+
sessionTimeouts: 0,
|
|
1968
|
+
crossTabConflicts: 0,
|
|
1969
|
+
lastHealthCheck: Date.now()
|
|
1970
|
+
};
|
|
1971
|
+
constructor(e, t, s, r, a) {
|
|
1972
|
+
super(), this.config = {
|
|
1973
|
+
throttleDelay: Te,
|
|
1974
|
+
visibilityTimeout: Ve,
|
|
1975
|
+
motionThreshold: Ne,
|
|
1976
|
+
timeout: this.get("config")?.sessionTimeout ?? L
|
|
1977
|
+
}, this.sessionEndConfig = {
|
|
1978
|
+
enablePageUnloadHandlers: !0,
|
|
1979
|
+
syncTimeoutMs: 1e3,
|
|
1980
|
+
maxRetries: 2,
|
|
1981
|
+
debugMode: !1,
|
|
1982
|
+
...a
|
|
1983
|
+
}, this.onActivity = e, this.onInactivity = t, this.eventManager = s ?? null, this.storageManager = r ?? null, this.deviceCapabilities = this.detectDeviceCapabilities(), this.initializeRecoveryManager(), this.initializeListenerManagers(), this.setupAllListeners(), this.sessionEndConfig.enablePageUnloadHandlers && this.setupPageUnloadHandlers(), i.debug("SessionManager", "SessionManager initialized", {
|
|
1984
|
+
sessionTimeout: this.config.timeout,
|
|
1985
|
+
deviceCapabilities: this.deviceCapabilities,
|
|
1986
|
+
unloadHandlersEnabled: this.sessionEndConfig.enablePageUnloadHandlers
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
* Initialize recovery manager
|
|
1991
|
+
*/
|
|
1992
|
+
initializeRecoveryManager() {
|
|
1993
|
+
if (!this.storageManager) return;
|
|
1994
|
+
const e = this.get("config")?.id;
|
|
1995
|
+
if (e)
|
|
1996
|
+
try {
|
|
1997
|
+
this.recoveryManager = new Ce(this.storageManager, e, this.eventManager ?? void 0), i.debug("SessionManager", "Recovery manager initialized", { projectId: e });
|
|
1998
|
+
} catch (t) {
|
|
1999
|
+
i.error("SessionManager", "Failed to initialize recovery manager", { error: t, projectId: e });
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Store session context for recovery
|
|
2004
|
+
*/
|
|
2005
|
+
storeSessionContextForRecovery() {
|
|
2006
|
+
if (!this.recoveryManager) return;
|
|
2007
|
+
const e = this.get("sessionId");
|
|
2008
|
+
if (!e) return;
|
|
2009
|
+
const t = {
|
|
2010
|
+
sessionId: e,
|
|
2011
|
+
startTime: this.sessionStartTime,
|
|
2012
|
+
lastActivity: this.lastActivityTime,
|
|
2013
|
+
tabCount: 1,
|
|
2014
|
+
// This will be updated by cross-tab manager
|
|
2015
|
+
recoveryAttempts: 0,
|
|
2016
|
+
metadata: {
|
|
2017
|
+
userAgent: navigator.userAgent,
|
|
2018
|
+
pageUrl: this.get("pageUrl")
|
|
2019
|
+
}
|
|
2020
|
+
};
|
|
2021
|
+
this.recoveryManager.storeSessionContextForRecovery(t);
|
|
2022
|
+
}
|
|
2023
|
+
startSession() {
|
|
2024
|
+
const e = Date.now();
|
|
2025
|
+
let t = "", s = !1;
|
|
2026
|
+
if (this.recoveryManager?.hasRecoverableSession()) {
|
|
2027
|
+
const r = this.recoveryManager.attemptSessionRecovery();
|
|
2028
|
+
r.recovered && r.recoveredSessionId && (t = r.recoveredSessionId, s = !0, this.trackSessionHealth("recovery"), r.context ? (this.sessionStartTime = r.context.startTime, this.lastActivityTime = e) : (this.sessionStartTime = e, this.lastActivityTime = e), i.info("SessionManager", "Session successfully recovered", {
|
|
2029
|
+
sessionId: t,
|
|
2030
|
+
recoveryAttempts: this.sessionHealth.recoveryAttempts
|
|
2031
|
+
}));
|
|
2032
|
+
}
|
|
2033
|
+
return s || (t = $(), this.sessionStartTime = e, this.lastActivityTime = e, i.info("SessionManager", "New session started", { sessionId: t })), this.isSessionActive = !0, this.resetInactivityTimer(), this.storeSessionContextForRecovery(), { sessionId: t, recovered: s };
|
|
2034
|
+
}
|
|
2035
|
+
endSession() {
|
|
2036
|
+
if (this.sessionStartTime === 0)
|
|
2037
|
+
return 0;
|
|
2038
|
+
const e = Date.now() - this.sessionStartTime;
|
|
2039
|
+
return this.sessionStartTime = 0, this.isSessionActive = !1, this.inactivityTimer && (clearTimeout(this.inactivityTimer), this.inactivityTimer = null), e;
|
|
2040
|
+
}
|
|
2041
|
+
destroy() {
|
|
2042
|
+
this.clearTimers(), this.cleanupAllListeners(), this.resetState(), this.cleanupHandlers.forEach((e) => e()), this.cleanupHandlers = [], this.pendingSessionEnd = !1, this.sessionEndPromise = null, this.sessionEndLock = Promise.resolve({
|
|
2043
|
+
success: !0,
|
|
2044
|
+
reason: "manual_stop",
|
|
2045
|
+
timestamp: Date.now(),
|
|
2046
|
+
eventsFlushed: 0,
|
|
2047
|
+
method: "async"
|
|
2048
|
+
}), this.recoveryManager && (this.recoveryManager.cleanupOldRecoveryAttempts(), this.recoveryManager = null);
|
|
2049
|
+
}
|
|
2050
|
+
detectDeviceCapabilities() {
|
|
2051
|
+
const e = "ontouchstart" in window || navigator.maxTouchPoints > 0, t = window.matchMedia("(pointer: fine)").matches, s = !window.matchMedia("(pointer: coarse)").matches, r = we() === E.Mobile;
|
|
2052
|
+
return { hasTouch: e, hasMouse: t, hasKeyboard: s, isMobile: r };
|
|
2053
|
+
}
|
|
2054
|
+
initializeListenerManagers() {
|
|
2055
|
+
this.listenerManagers.push(new Ct(this.handleActivity)), this.deviceCapabilities.hasTouch && this.listenerManagers.push(new Lt(this.handleActivity, this.config.motionThreshold)), this.deviceCapabilities.hasMouse && this.listenerManagers.push(new Rt(this.handleActivity)), this.deviceCapabilities.hasKeyboard && this.listenerManagers.push(new kt(this.handleActivity)), this.listenerManagers.push(
|
|
2056
|
+
new Nt(this.handleActivity, this.handleVisibilityChange, this.deviceCapabilities.isMobile)
|
|
2057
|
+
), this.listenerManagers.push(new Ut(this.handleInactivity));
|
|
2058
|
+
}
|
|
2059
|
+
setupAllListeners() {
|
|
2060
|
+
this.listenerManagers.forEach((e) => e.setup());
|
|
2061
|
+
}
|
|
2062
|
+
cleanupAllListeners() {
|
|
2063
|
+
this.listenerManagers.forEach((e) => e.cleanup());
|
|
2064
|
+
}
|
|
2065
|
+
clearTimers() {
|
|
2066
|
+
this.inactivityTimer && (clearTimeout(this.inactivityTimer), this.inactivityTimer = null), this.throttleTimeout && (clearTimeout(this.throttleTimeout), this.throttleTimeout = null);
|
|
2067
|
+
}
|
|
2068
|
+
resetState() {
|
|
2069
|
+
this.isSessionActive = !1, this.lastActivityTime = 0, this.sessionStartTime = 0;
|
|
2070
|
+
}
|
|
2071
|
+
handleActivity = () => {
|
|
2072
|
+
const e = Date.now();
|
|
2073
|
+
e - this.lastActivityTime < this.config.throttleDelay || (this.lastActivityTime = e, this.isSessionActive ? (this.onActivity(), this.resetInactivityTimer()) : (this.throttleTimeout && (clearTimeout(this.throttleTimeout), this.throttleTimeout = null), this.throttleTimeout = window.setTimeout(() => {
|
|
2074
|
+
this.onActivity(), this.throttleTimeout = null;
|
|
2075
|
+
}, 100)));
|
|
2076
|
+
};
|
|
2077
|
+
handleInactivity = () => {
|
|
2078
|
+
this.trackSessionHealth("timeout"), this.onInactivity();
|
|
2079
|
+
};
|
|
2080
|
+
handleVisibilityChange = () => {
|
|
2081
|
+
document.hidden ? this.isSessionActive && (this.inactivityTimer && (clearTimeout(this.inactivityTimer), this.inactivityTimer = null), this.inactivityTimer = window.setTimeout(this.handleInactivity, this.config.visibilityTimeout)) : this.handleActivity();
|
|
2082
|
+
};
|
|
2083
|
+
resetInactivityTimer = () => {
|
|
2084
|
+
this.inactivityTimer && (clearTimeout(this.inactivityTimer), this.inactivityTimer = null), this.isSessionActive && (this.inactivityTimer = window.setTimeout(() => {
|
|
2085
|
+
this.handleInactivity();
|
|
2086
|
+
}, this.config.timeout));
|
|
2087
|
+
};
|
|
2088
|
+
clearInactivityTimer() {
|
|
2089
|
+
this.inactivityTimer && (clearTimeout(this.inactivityTimer), this.inactivityTimer = null);
|
|
2090
|
+
}
|
|
2091
|
+
shouldProceedWithSessionEnd(e) {
|
|
2092
|
+
return !this.sessionEndReason || this.sessionEndPriority[e] > this.sessionEndPriority[this.sessionEndReason];
|
|
2093
|
+
}
|
|
2094
|
+
async waitForCompletion() {
|
|
2095
|
+
return this.sessionEndPromise ? await this.sessionEndPromise : {
|
|
2096
|
+
success: !1,
|
|
2097
|
+
reason: "inactivity",
|
|
2098
|
+
timestamp: Date.now(),
|
|
2099
|
+
eventsFlushed: 0,
|
|
2100
|
+
method: "async"
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
async endSessionManaged(e) {
|
|
2104
|
+
return this.sessionEndLock = this.sessionEndLock.then(async () => {
|
|
2105
|
+
if (this.sessionEndStats.totalSessionEnds++, this.sessionEndStats.reasonCounts[e]++, this.pendingSessionEnd)
|
|
2106
|
+
return this.sessionEndStats.duplicatePrevented++, i.debug("SessionManager", "Session end already pending, waiting for completion", { reason: e }), this.waitForCompletion();
|
|
2107
|
+
if (!this.shouldProceedWithSessionEnd(e))
|
|
2108
|
+
return this.sessionEndConfig.debugMode && i.debug(
|
|
2109
|
+
"SessionManager",
|
|
2110
|
+
`Session end skipped due to lower priority. Current: ${this.sessionEndReason}, Requested: ${e}`
|
|
2111
|
+
), {
|
|
2112
|
+
success: !1,
|
|
2113
|
+
reason: e,
|
|
2114
|
+
timestamp: Date.now(),
|
|
2115
|
+
eventsFlushed: 0,
|
|
2116
|
+
method: "async"
|
|
2117
|
+
};
|
|
2118
|
+
this.sessionEndReason = e, this.pendingSessionEnd = !0, this.sessionEndPromise = this.performSessionEnd(e, "async");
|
|
2119
|
+
try {
|
|
2120
|
+
return await this.sessionEndPromise;
|
|
2121
|
+
} finally {
|
|
2122
|
+
this.pendingSessionEnd = !1, this.sessionEndPromise = null, this.sessionEndReason = null;
|
|
2123
|
+
}
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
2126
|
+
endSessionSafely(e, t) {
|
|
2127
|
+
return t?.forceSync ?? (t?.allowSync && ["page_unload", "tab_closed"].includes(e)) ? this.endSessionManagedSync(e) : this.endSessionManaged(e);
|
|
2128
|
+
}
|
|
2129
|
+
isPendingSessionEnd() {
|
|
2130
|
+
return this.pendingSessionEnd;
|
|
2131
|
+
}
|
|
2132
|
+
/**
|
|
2133
|
+
* Track session health events for monitoring and diagnostics
|
|
2134
|
+
*/
|
|
2135
|
+
trackSessionHealth(e) {
|
|
2136
|
+
const t = Date.now();
|
|
2137
|
+
switch (e) {
|
|
2138
|
+
case "recovery":
|
|
2139
|
+
this.sessionHealth.recoveryAttempts++;
|
|
2140
|
+
break;
|
|
2141
|
+
case "timeout":
|
|
2142
|
+
this.sessionHealth.sessionTimeouts++;
|
|
2143
|
+
break;
|
|
2144
|
+
case "conflict":
|
|
2145
|
+
this.sessionHealth.crossTabConflicts++;
|
|
2146
|
+
break;
|
|
2147
|
+
}
|
|
2148
|
+
this.sessionHealth.lastHealthCheck = t, this.sessionHealth.recoveryAttempts > 3 && this.eventManager && (this.eventManager.track({
|
|
2149
|
+
type: h.CUSTOM,
|
|
2150
|
+
custom_event: {
|
|
2151
|
+
name: "session_health_degraded",
|
|
2152
|
+
metadata: {
|
|
2153
|
+
...this.sessionHealth,
|
|
2154
|
+
event_trigger: e
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
}), this.sessionEndConfig.debugMode && i.warn(
|
|
2158
|
+
"SessionManager",
|
|
2159
|
+
`Session health degraded: ${this.sessionHealth.recoveryAttempts} recovery attempts`
|
|
2160
|
+
)), this.sessionEndConfig.debugMode && i.debug("SessionManager", `Session health event tracked: ${e}`);
|
|
2161
|
+
}
|
|
2162
|
+
async performSessionEnd(e, t) {
|
|
2163
|
+
const s = Date.now();
|
|
2164
|
+
let r = 0;
|
|
2165
|
+
try {
|
|
2166
|
+
if (i.info("SessionManager", "Starting session end", { method: t, reason: e, timestamp: s }), this.eventManager) {
|
|
2167
|
+
this.eventManager.track({
|
|
2168
|
+
type: h.SESSION_END,
|
|
2169
|
+
session_end_reason: e
|
|
2170
|
+
}), r = this.eventManager.getQueueLength();
|
|
2171
|
+
const o = await this.eventManager.flushImmediately();
|
|
2172
|
+
this.cleanupSession();
|
|
2173
|
+
const l = {
|
|
2174
|
+
success: o,
|
|
2175
|
+
reason: e,
|
|
2176
|
+
timestamp: s,
|
|
2177
|
+
eventsFlushed: r,
|
|
2178
|
+
method: t
|
|
2179
|
+
};
|
|
2180
|
+
return o ? this.sessionEndStats.successfulEnds++ : this.sessionEndStats.failedEnds++, l;
|
|
2181
|
+
}
|
|
2182
|
+
this.cleanupSession();
|
|
2183
|
+
const a = {
|
|
2184
|
+
success: !0,
|
|
2185
|
+
reason: e,
|
|
2186
|
+
timestamp: s,
|
|
2187
|
+
eventsFlushed: 0,
|
|
2188
|
+
method: t
|
|
2189
|
+
};
|
|
2190
|
+
return this.sessionEndStats.successfulEnds++, a;
|
|
2191
|
+
} catch (a) {
|
|
2192
|
+
return this.sessionEndStats.failedEnds++, i.error("SessionManager", "Session end failed", { error: a, reason: e, method: t }), this.cleanupSession(), {
|
|
2193
|
+
success: !1,
|
|
2194
|
+
reason: e,
|
|
2195
|
+
timestamp: s,
|
|
2196
|
+
eventsFlushed: r,
|
|
2197
|
+
method: t
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
cleanupSession() {
|
|
2202
|
+
this.endSession(), this.clearTimers(), this.set("sessionId", null), this.set("hasStartSession", !1);
|
|
2203
|
+
}
|
|
2204
|
+
endSessionManagedSync(e) {
|
|
2205
|
+
if (this.sessionEndStats.totalSessionEnds++, this.sessionEndStats.reasonCounts[e]++, this.pendingSessionEnd && (this.sessionEndStats.duplicatePrevented++, i.warn("SessionManager", "Sync session end called while async end pending", { reason: e })), !this.shouldProceedWithSessionEnd(e))
|
|
2206
|
+
return this.sessionEndConfig.debugMode && i.debug(
|
|
2207
|
+
"SessionManager",
|
|
2208
|
+
`Sync session end skipped due to lower priority. Current: ${this.sessionEndReason}, Requested: ${e}`
|
|
2209
|
+
), {
|
|
2210
|
+
success: !1,
|
|
2211
|
+
reason: e,
|
|
2212
|
+
timestamp: Date.now(),
|
|
2213
|
+
eventsFlushed: 0,
|
|
2214
|
+
method: "sync"
|
|
2215
|
+
};
|
|
2216
|
+
this.sessionEndReason = e, this.pendingSessionEnd = !0;
|
|
2217
|
+
try {
|
|
2218
|
+
return this.performSessionEndSync(e);
|
|
2219
|
+
} finally {
|
|
2220
|
+
this.pendingSessionEnd = !1, this.sessionEndPromise = null, this.sessionEndReason = null;
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
performSessionEndSync(e) {
|
|
2224
|
+
const t = Date.now();
|
|
2225
|
+
let s = 0;
|
|
2226
|
+
try {
|
|
2227
|
+
if (this.eventManager) {
|
|
2228
|
+
this.eventManager.track({
|
|
2229
|
+
type: h.SESSION_END,
|
|
2230
|
+
session_end_reason: e
|
|
2231
|
+
}), s = this.eventManager.getQueueLength();
|
|
2232
|
+
const a = this.eventManager.flushImmediatelySync();
|
|
2233
|
+
this.cleanupSession();
|
|
2234
|
+
const o = {
|
|
2235
|
+
success: a,
|
|
2236
|
+
reason: e,
|
|
2237
|
+
timestamp: t,
|
|
2238
|
+
eventsFlushed: s,
|
|
2239
|
+
method: "sync"
|
|
2240
|
+
};
|
|
2241
|
+
return a ? this.sessionEndStats.successfulEnds++ : this.sessionEndStats.failedEnds++, o;
|
|
2242
|
+
}
|
|
2243
|
+
this.cleanupSession();
|
|
2244
|
+
const r = {
|
|
2245
|
+
success: !0,
|
|
2246
|
+
reason: e,
|
|
2247
|
+
timestamp: t,
|
|
2248
|
+
eventsFlushed: 0,
|
|
2249
|
+
method: "sync"
|
|
2250
|
+
};
|
|
2251
|
+
return this.sessionEndStats.successfulEnds++, r;
|
|
2252
|
+
} catch (r) {
|
|
2253
|
+
return this.sessionEndStats.failedEnds++, this.cleanupSession(), i.error("SessionManager", "Sync session end failed", { error: r, reason: e }), {
|
|
2254
|
+
success: !1,
|
|
2255
|
+
reason: e,
|
|
2256
|
+
timestamp: t,
|
|
2257
|
+
eventsFlushed: s,
|
|
2258
|
+
method: "sync"
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
setupPageUnloadHandlers() {
|
|
2263
|
+
let e = !1;
|
|
2264
|
+
const t = () => {
|
|
2265
|
+
e || !this.get("sessionId") || (e = !0, this.clearInactivityTimer(), this.endSessionSafely("page_unload", { forceSync: !0 }));
|
|
2266
|
+
}, s = () => {
|
|
2267
|
+
t();
|
|
2268
|
+
}, r = (o) => {
|
|
2269
|
+
o.persisted || t();
|
|
2270
|
+
}, a = () => {
|
|
2271
|
+
document.visibilityState === "hidden" && this.get("sessionId") && !e && (this.visibilityChangeTimeout = window.setTimeout(() => {
|
|
2272
|
+
document.visibilityState === "hidden" && this.get("sessionId") && !e && t(), this.visibilityChangeTimeout = null;
|
|
2273
|
+
}, 1e3));
|
|
2274
|
+
};
|
|
2275
|
+
window.addEventListener("beforeunload", s), window.addEventListener("pagehide", r), document.addEventListener("visibilitychange", a), this.cleanupHandlers.push(
|
|
2276
|
+
() => window.removeEventListener("beforeunload", s),
|
|
2277
|
+
() => window.removeEventListener("pagehide", r),
|
|
2278
|
+
() => document.removeEventListener("visibilitychange", a),
|
|
2279
|
+
() => {
|
|
2280
|
+
this.visibilityChangeTimeout && (clearTimeout(this.visibilityChangeTimeout), this.visibilityChangeTimeout = null);
|
|
2281
|
+
}
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
class Ht extends p {
|
|
2286
|
+
constructor(e, t, s, r) {
|
|
2287
|
+
super(), this.callbacks = r, this.storageManager = e, this.projectId = t, this.tabId = $(), this.config = {
|
|
2288
|
+
tabHeartbeatIntervalMs: qe,
|
|
2289
|
+
tabElectionTimeoutMs: We,
|
|
2290
|
+
debugMode: (this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug") ?? !1,
|
|
2291
|
+
...s
|
|
2292
|
+
}, this.tabInfo = {
|
|
2293
|
+
id: this.tabId,
|
|
2294
|
+
lastHeartbeat: Date.now(),
|
|
2295
|
+
isLeader: !1,
|
|
2296
|
+
sessionId: "",
|
|
2297
|
+
startTime: Date.now()
|
|
2298
|
+
}, this.broadcastChannel = this.initializeBroadcastChannel(), this.initialize();
|
|
2299
|
+
}
|
|
2300
|
+
config;
|
|
2301
|
+
storageManager;
|
|
2302
|
+
broadcastChannel;
|
|
2303
|
+
tabId;
|
|
2304
|
+
tabInfo;
|
|
2305
|
+
projectId;
|
|
2306
|
+
leaderTabId = null;
|
|
2307
|
+
isTabLeader = !1;
|
|
2308
|
+
heartbeatInterval = null;
|
|
2309
|
+
electionTimeout = null;
|
|
2310
|
+
cleanupTimeout = null;
|
|
2311
|
+
sessionEnded = !1;
|
|
2312
|
+
// Additional timeout tracking for proper cleanup
|
|
2313
|
+
fallbackLeadershipTimeout = null;
|
|
2314
|
+
electionDelayTimeout = null;
|
|
2315
|
+
tabInfoCleanupTimeout = null;
|
|
2316
|
+
closingAnnouncementTimeout = null;
|
|
2317
|
+
leaderHealthCheckInterval = null;
|
|
2318
|
+
lastHeartbeatSent = 0;
|
|
2319
|
+
/**
|
|
2320
|
+
* Initialize BroadcastChannel if supported
|
|
2321
|
+
*/
|
|
2322
|
+
initializeBroadcastChannel() {
|
|
2323
|
+
if (!this.isBroadcastChannelSupported())
|
|
2324
|
+
return null;
|
|
2325
|
+
try {
|
|
2326
|
+
const e = new BroadcastChannel(it(this.projectId));
|
|
2327
|
+
return this.setupBroadcastListeners(e), e;
|
|
2328
|
+
} catch (e) {
|
|
2329
|
+
return this.config.debugMode && i.warn("CrossTabSession", "Failed to initialize BroadcastChannel", { error: e }), null;
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
/**
|
|
2333
|
+
* Initialize the cross-tab session manager
|
|
2334
|
+
*/
|
|
2335
|
+
initialize() {
|
|
2336
|
+
if (!this.broadcastChannel) {
|
|
2337
|
+
this.becomeLeader();
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2340
|
+
const e = this.getStoredSessionContext();
|
|
2341
|
+
e ? this.tryJoinExistingSession(e) : this.startLeaderElection(), this.startHeartbeat(), this.broadcastChannel && this.setupLeadershipFallback();
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* Check if this tab should be the session leader
|
|
2345
|
+
*/
|
|
2346
|
+
tryJoinExistingSession(e) {
|
|
2347
|
+
this.config.debugMode && i.debug("CrossTabSession", `Attempting to join existing session: ${e.sessionId}`), this.tabInfo.sessionId = e.sessionId, this.requestLeadershipStatus(), e.tabCount += 1, e.lastActivity = Date.now(), this.storeSessionContext(e), this.callbacks?.onTabActivity && this.callbacks.onTabActivity();
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* Request leadership status from other tabs
|
|
2351
|
+
*/
|
|
2352
|
+
requestLeadershipStatus() {
|
|
2353
|
+
if (!this.broadcastChannel) return;
|
|
2354
|
+
this.electionTimeout && (clearTimeout(this.electionTimeout), this.electionTimeout = null);
|
|
2355
|
+
const e = {
|
|
2356
|
+
type: "election_request",
|
|
2357
|
+
tabId: this.tabId,
|
|
2358
|
+
sessionId: this.tabInfo.sessionId,
|
|
2359
|
+
timestamp: Date.now()
|
|
2360
|
+
};
|
|
2361
|
+
this.broadcastChannel.postMessage(e);
|
|
2362
|
+
const t = Math.floor(Math.random() * 500);
|
|
2363
|
+
this.electionTimeout = window.setTimeout(() => {
|
|
2364
|
+
this.isTabLeader || this.becomeLeader();
|
|
2365
|
+
}, this.config.tabElectionTimeoutMs + t);
|
|
2366
|
+
}
|
|
2367
|
+
/**
|
|
2368
|
+
* Start leader election process with debouncing to prevent excessive elections
|
|
2369
|
+
*/
|
|
2370
|
+
startLeaderElection() {
|
|
2371
|
+
if (this.electionTimeout) {
|
|
2372
|
+
this.config.debugMode && i.debug("CrossTabSession", "Leader election already in progress, skipping");
|
|
2373
|
+
return;
|
|
2374
|
+
}
|
|
2375
|
+
this.config.debugMode && i.debug("CrossTabSession", "Starting leader election");
|
|
2376
|
+
const e = Math.floor(Math.random() * 50) + 10;
|
|
2377
|
+
this.electionTimeout = window.setTimeout(() => {
|
|
2378
|
+
this.electionTimeout = null, this.requestLeadershipStatus();
|
|
2379
|
+
}, e);
|
|
2380
|
+
}
|
|
2381
|
+
/**
|
|
2382
|
+
* Become the session leader
|
|
2383
|
+
*/
|
|
2384
|
+
becomeLeader() {
|
|
2385
|
+
if (!this.isTabLeader) {
|
|
2386
|
+
if (this.isTabLeader = !0, this.tabInfo.isLeader = !0, this.leaderTabId = this.tabId, this.config.debugMode && i.debug("CrossTabSession", `Tab ${this.tabId} became session leader`), this.electionTimeout && (clearTimeout(this.electionTimeout), this.electionTimeout = null), this.tabInfo.sessionId) {
|
|
2387
|
+
const e = this.getStoredSessionContext();
|
|
2388
|
+
e && (e.lastActivity = Date.now(), this.storeSessionContext(e));
|
|
2389
|
+
} else {
|
|
2390
|
+
const e = $();
|
|
2391
|
+
this.tabInfo.sessionId = e;
|
|
2392
|
+
const t = {
|
|
2393
|
+
sessionId: e,
|
|
2394
|
+
startTime: Date.now(),
|
|
2395
|
+
lastActivity: Date.now(),
|
|
2396
|
+
tabCount: 1,
|
|
2397
|
+
recoveryAttempts: 0
|
|
2398
|
+
};
|
|
2399
|
+
this.storeSessionContext(t), this.callbacks?.onSessionStart && this.callbacks.onSessionStart(e), this.announceSessionStart(e);
|
|
2400
|
+
}
|
|
2401
|
+
this.storeTabInfo(), this.announceLeadership();
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
/**
|
|
2405
|
+
* Announce session start to other tabs
|
|
2406
|
+
*/
|
|
2407
|
+
announceSessionStart(e) {
|
|
2408
|
+
if (!this.broadcastChannel) return;
|
|
2409
|
+
const t = {
|
|
2410
|
+
type: "session_start",
|
|
2411
|
+
tabId: this.tabId,
|
|
2412
|
+
sessionId: e,
|
|
2413
|
+
timestamp: Date.now()
|
|
2414
|
+
};
|
|
2415
|
+
this.broadcastChannel.postMessage(t);
|
|
2416
|
+
}
|
|
2417
|
+
/**
|
|
2418
|
+
* Announce leadership to other tabs
|
|
2419
|
+
*/
|
|
2420
|
+
announceLeadership() {
|
|
2421
|
+
if (!this.broadcastChannel || !this.tabInfo.sessionId) return;
|
|
2422
|
+
const e = {
|
|
2423
|
+
type: "election_response",
|
|
2424
|
+
tabId: this.tabId,
|
|
2425
|
+
sessionId: this.tabInfo.sessionId,
|
|
2426
|
+
timestamp: Date.now(),
|
|
2427
|
+
data: { isLeader: !0 }
|
|
2428
|
+
};
|
|
2429
|
+
this.broadcastChannel.postMessage(e);
|
|
2430
|
+
}
|
|
2431
|
+
/**
|
|
2432
|
+
* Setup fallback mechanism to ensure a leader is always elected
|
|
2433
|
+
*/
|
|
2434
|
+
setupLeadershipFallback() {
|
|
2435
|
+
const e = this.config.tabElectionTimeoutMs + 1500;
|
|
2436
|
+
this.fallbackLeadershipTimeout = window.setTimeout(() => {
|
|
2437
|
+
!this.isTabLeader && !this.leaderTabId && (this.tabInfo.sessionId ? (this.config.debugMode && i.warn(
|
|
2438
|
+
"CrossTabSession",
|
|
2439
|
+
`No leader detected after ${e}ms, forcing leadership for tab ${this.tabId}`
|
|
2440
|
+
), this.becomeLeader()) : (this.config.debugMode && i.warn(
|
|
2441
|
+
"CrossTabSession",
|
|
2442
|
+
`No session or leader detected after ${e}ms, starting new session for tab ${this.tabId}`
|
|
2443
|
+
), this.becomeLeader())), this.fallbackLeadershipTimeout = null;
|
|
2444
|
+
}, e), this.leaderHealthCheckInterval = window.setInterval(() => {
|
|
2445
|
+
if (!this.sessionEnded && this.leaderTabId && !this.isTabLeader) {
|
|
2446
|
+
const s = this.getStoredSessionContext();
|
|
2447
|
+
if (s) {
|
|
2448
|
+
const r = Date.now() - s.lastActivity, a = this.config.tabHeartbeatIntervalMs * 3;
|
|
2449
|
+
r > a && (this.config.debugMode && i.warn(
|
|
2450
|
+
"CrossTabSession",
|
|
2451
|
+
`Leader tab appears inactive (${r}ms), attempting to become leader`
|
|
2452
|
+
), this.leaderTabId = null, this.startLeaderElection());
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
}, this.config.tabHeartbeatIntervalMs * 2);
|
|
2456
|
+
const t = this.endSession.bind(this);
|
|
2457
|
+
this.endSession = (s) => {
|
|
2458
|
+
this.leaderHealthCheckInterval && (clearInterval(this.leaderHealthCheckInterval), this.leaderHealthCheckInterval = null), t(s);
|
|
2459
|
+
};
|
|
2460
|
+
}
|
|
2461
|
+
/**
|
|
2462
|
+
* Setup BroadcastChannel event listeners
|
|
2463
|
+
*/
|
|
2464
|
+
setupBroadcastListeners(e) {
|
|
2465
|
+
e.addEventListener("message", (t) => {
|
|
2466
|
+
const s = t.data;
|
|
2467
|
+
s.tabId !== this.tabId && this.handleCrossTabMessage(s);
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Handle cross-tab messages
|
|
2472
|
+
*/
|
|
2473
|
+
handleCrossTabMessage(e) {
|
|
2474
|
+
switch (this.config.debugMode && i.debug("CrossTabSession", `Received cross-tab message: ${e.type} from ${e.tabId}`), e.type) {
|
|
2475
|
+
case "heartbeat":
|
|
2476
|
+
this.handleHeartbeatMessage(e);
|
|
2477
|
+
break;
|
|
2478
|
+
case "session_start":
|
|
2479
|
+
this.handleSessionStartMessage(e);
|
|
2480
|
+
break;
|
|
2481
|
+
case "session_end":
|
|
2482
|
+
this.handleSessionEndMessage(e);
|
|
2483
|
+
break;
|
|
2484
|
+
case "tab_closing":
|
|
2485
|
+
this.handleTabClosingMessage(e);
|
|
2486
|
+
break;
|
|
2487
|
+
case "election_request":
|
|
2488
|
+
this.handleElectionRequest(e);
|
|
2489
|
+
break;
|
|
2490
|
+
case "election_response":
|
|
2491
|
+
this.handleElectionResponse(e);
|
|
2492
|
+
break;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
/**
|
|
2496
|
+
* Handle heartbeat message from another tab
|
|
2497
|
+
*/
|
|
2498
|
+
handleHeartbeatMessage(e) {
|
|
2499
|
+
if (e.sessionId === this.tabInfo.sessionId) {
|
|
2500
|
+
const t = this.getStoredSessionContext();
|
|
2501
|
+
t && (t.lastActivity = Date.now(), this.storeSessionContext(t), this.callbacks?.onTabActivity && this.callbacks.onTabActivity());
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
/**
|
|
2505
|
+
* Handle session start message from another tab
|
|
2506
|
+
*/
|
|
2507
|
+
handleSessionStartMessage(e) {
|
|
2508
|
+
if (e.sessionId && !this.tabInfo.sessionId) {
|
|
2509
|
+
this.tabInfo.sessionId = e.sessionId, this.storeTabInfo();
|
|
2510
|
+
const t = this.getStoredSessionContext();
|
|
2511
|
+
t && (t.tabCount += 1, this.storeSessionContext(t));
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
/**
|
|
2515
|
+
* Handle session end message from another tab
|
|
2516
|
+
*/
|
|
2517
|
+
handleSessionEndMessage(e) {
|
|
2518
|
+
if (this.isTabLeader) {
|
|
2519
|
+
this.config.debugMode && i.debug("CrossTabSession", `Ignoring session end message from ${e.tabId} (this tab is leader)`);
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2522
|
+
if (!this.leaderTabId || e.tabId !== this.leaderTabId) {
|
|
2523
|
+
if (this.config.debugMode) {
|
|
2524
|
+
const s = this.leaderTabId ? `; leader is ${this.leaderTabId}` : "";
|
|
2525
|
+
i.debug("CrossTabSession", `Ignoring session end message from ${e.tabId}${s}`);
|
|
2526
|
+
}
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
this.tabInfo.sessionId = "", this.storeTabInfo(), this.leaderTabId = null, this.getStoredSessionContext() || (this.broadcastChannel ? this.startLeaderElection() : this.becomeLeader());
|
|
2530
|
+
}
|
|
2531
|
+
/**
|
|
2532
|
+
* Handle tab closing message from another tab
|
|
2533
|
+
*/
|
|
2534
|
+
handleTabClosingMessage(e) {
|
|
2535
|
+
const t = this.getStoredSessionContext();
|
|
2536
|
+
if (t && e.sessionId === t.sessionId) {
|
|
2537
|
+
const s = t.tabCount;
|
|
2538
|
+
t.tabCount = Math.max(1, t.tabCount - 1), t.lastActivity = Date.now(), this.storeSessionContext(t), this.config.debugMode && i.debug(
|
|
2539
|
+
"CrossTabSession",
|
|
2540
|
+
`Tab count updated from ${s} to ${t.tabCount} after tab ${e.tabId} closed`
|
|
2541
|
+
), (e.data?.isLeader ?? e.tabId === this.leaderTabId) && !this.isTabLeader && (this.config.debugMode && i.debug("CrossTabSession", `Leader tab ${e.tabId} closed, starting leader election`), this.leaderTabId = null, this.electionDelayTimeout = window.setTimeout(() => {
|
|
2542
|
+
this.startLeaderElection(), this.electionDelayTimeout = null;
|
|
2543
|
+
}, 200));
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
/**
|
|
2547
|
+
* Handle election request from another tab
|
|
2548
|
+
*/
|
|
2549
|
+
handleElectionRequest(e) {
|
|
2550
|
+
if (this.isTabLeader) {
|
|
2551
|
+
const t = {
|
|
2552
|
+
type: "election_response",
|
|
2553
|
+
tabId: this.tabId,
|
|
2554
|
+
sessionId: this.tabInfo.sessionId,
|
|
2555
|
+
timestamp: Date.now(),
|
|
2556
|
+
data: { isLeader: !0 }
|
|
2557
|
+
};
|
|
2558
|
+
this.broadcastChannel && this.broadcastChannel.postMessage(t);
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
/**
|
|
2562
|
+
* Handle election response from another tab
|
|
2563
|
+
*/
|
|
2564
|
+
handleElectionResponse(e) {
|
|
2565
|
+
e.data?.isLeader && (this.isTabLeader ? this.config.debugMode && (i.warn(
|
|
2566
|
+
"CrossTabSession",
|
|
2567
|
+
`Received leadership claim from ${e.tabId} but this tab is already leader`
|
|
2568
|
+
), this.callbacks?.onCrossTabConflict && this.callbacks.onCrossTabConflict()) : (this.isTabLeader = !1, this.tabInfo.isLeader = !1, this.leaderTabId = e.tabId, this.config.debugMode && i.debug("CrossTabSession", `Acknowledging tab ${e.tabId} as leader`), this.electionTimeout && (clearTimeout(this.electionTimeout), this.electionTimeout = null), e.sessionId && (this.tabInfo.sessionId = e.sessionId, this.storeTabInfo())));
|
|
2569
|
+
}
|
|
2570
|
+
/**
|
|
2571
|
+
* Start heartbeat to keep session active
|
|
2572
|
+
*/
|
|
2573
|
+
startHeartbeat() {
|
|
2574
|
+
this.heartbeatInterval && clearInterval(this.heartbeatInterval), this.heartbeatInterval = window.setInterval(() => {
|
|
2575
|
+
this.sendHeartbeat(), this.updateTabInfo();
|
|
2576
|
+
}, this.config.tabHeartbeatIntervalMs);
|
|
2577
|
+
}
|
|
2578
|
+
/**
|
|
2579
|
+
* Send heartbeat to other tabs with rate limiting to prevent flooding
|
|
2580
|
+
*/
|
|
2581
|
+
sendHeartbeat() {
|
|
2582
|
+
if (!this.broadcastChannel || !this.tabInfo.sessionId) return;
|
|
2583
|
+
const e = Date.now(), t = this.lastHeartbeatSent ?? 0, s = this.config.tabHeartbeatIntervalMs * 0.8;
|
|
2584
|
+
if (!this.isTabLeader && e - t < s)
|
|
2585
|
+
return;
|
|
2586
|
+
const r = {
|
|
2587
|
+
type: "heartbeat",
|
|
2588
|
+
tabId: this.tabId,
|
|
2589
|
+
sessionId: this.tabInfo.sessionId,
|
|
2590
|
+
timestamp: e
|
|
2591
|
+
};
|
|
2592
|
+
this.broadcastChannel.postMessage(r), this.lastHeartbeatSent = e;
|
|
2593
|
+
}
|
|
2594
|
+
/**
|
|
2595
|
+
* Update tab info with current timestamp
|
|
2596
|
+
*/
|
|
2597
|
+
updateTabInfo() {
|
|
2598
|
+
this.tabInfo.lastHeartbeat = Date.now(), this.storeTabInfo();
|
|
2599
|
+
}
|
|
2600
|
+
/**
|
|
2601
|
+
* End session and notify other tabs
|
|
2602
|
+
*/
|
|
2603
|
+
endSession(e) {
|
|
2604
|
+
this.sessionEnded || (this.sessionEnded = !0, this.config.debugMode && i.debug(
|
|
2605
|
+
"CrossTabSession",
|
|
2606
|
+
`Ending cross-tab session: ${e} (tab: ${this.tabId}, isLeader: ${this.isTabLeader})`
|
|
2607
|
+
), this.announceTabClosing(), this.isTabLeader && e !== "manual_stop" && this.announceSessionEnd(e), this.tabInfoCleanupTimeout = window.setTimeout(() => {
|
|
2608
|
+
this.clearTabInfo(), this.tabInfoCleanupTimeout = null;
|
|
2609
|
+
}, 150));
|
|
2610
|
+
}
|
|
2611
|
+
/**
|
|
2612
|
+
* Announce tab is closing to other tabs
|
|
2613
|
+
*/
|
|
2614
|
+
announceTabClosing() {
|
|
2615
|
+
if (!this.broadcastChannel || !this.tabInfo.sessionId) return;
|
|
2616
|
+
const e = {
|
|
2617
|
+
type: "tab_closing",
|
|
2618
|
+
tabId: this.tabId,
|
|
2619
|
+
sessionId: this.tabInfo.sessionId,
|
|
2620
|
+
timestamp: Date.now(),
|
|
2621
|
+
data: { isLeader: this.isTabLeader }
|
|
2622
|
+
};
|
|
2623
|
+
this.broadcastChannel.postMessage(e), this.closingAnnouncementTimeout = window.setTimeout(() => {
|
|
2624
|
+
this.config.debugMode && i.debug("CrossTabSession", `Tab ${this.tabId} closing announcement sent`), this.closingAnnouncementTimeout = null;
|
|
2625
|
+
}, 100);
|
|
2626
|
+
}
|
|
2627
|
+
/**
|
|
2628
|
+
* Announce session end to other tabs
|
|
2629
|
+
*/
|
|
2630
|
+
announceSessionEnd(e) {
|
|
2631
|
+
if (!this.broadcastChannel) return;
|
|
2632
|
+
const t = {
|
|
2633
|
+
type: "session_end",
|
|
2634
|
+
tabId: this.tabId,
|
|
2635
|
+
sessionId: this.tabInfo.sessionId,
|
|
2636
|
+
timestamp: Date.now(),
|
|
2637
|
+
data: { reason: e }
|
|
2638
|
+
};
|
|
2639
|
+
this.broadcastChannel.postMessage(t);
|
|
2640
|
+
}
|
|
2641
|
+
/**
|
|
2642
|
+
* Get current session ID
|
|
2643
|
+
*/
|
|
2644
|
+
getSessionId() {
|
|
2645
|
+
return this.tabInfo.sessionId;
|
|
2646
|
+
}
|
|
2647
|
+
/**
|
|
2648
|
+
* Get current tab ID
|
|
2649
|
+
*/
|
|
2650
|
+
getTabId() {
|
|
2651
|
+
return this.tabId;
|
|
2652
|
+
}
|
|
2653
|
+
/**
|
|
2654
|
+
* Check if this tab is the session leader
|
|
2655
|
+
*/
|
|
2656
|
+
isLeader() {
|
|
2657
|
+
return this.isTabLeader;
|
|
2658
|
+
}
|
|
2659
|
+
/**
|
|
2660
|
+
* Get current session context from storage
|
|
2661
|
+
*/
|
|
2662
|
+
getStoredSessionContext() {
|
|
2663
|
+
try {
|
|
2664
|
+
const e = this.storageManager.getItem(Y(this.projectId));
|
|
2665
|
+
return e ? JSON.parse(e) : null;
|
|
2666
|
+
} catch (e) {
|
|
2667
|
+
return this.config.debugMode && i.warn("CrossTabSession", "Failed to parse stored session context", { error: e }), null;
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
/**
|
|
2671
|
+
* Store session context to localStorage
|
|
2672
|
+
*/
|
|
2673
|
+
storeSessionContext(e) {
|
|
2674
|
+
try {
|
|
2675
|
+
this.storageManager.setItem(Y(this.projectId), JSON.stringify(e));
|
|
2676
|
+
} catch (t) {
|
|
2677
|
+
this.config.debugMode && i.warn("CrossTabSession", "Failed to store session context", { error: t });
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
/**
|
|
2681
|
+
* Clear stored session context
|
|
2682
|
+
*/
|
|
2683
|
+
clearStoredSessionContext() {
|
|
2684
|
+
this.storageManager.removeItem(Y(this.projectId));
|
|
2685
|
+
}
|
|
2686
|
+
/**
|
|
2687
|
+
* Store tab info to localStorage
|
|
2688
|
+
*/
|
|
2689
|
+
storeTabInfo() {
|
|
2690
|
+
try {
|
|
2691
|
+
this.storageManager.setItem(Se(this.projectId, this.tabId), JSON.stringify(this.tabInfo));
|
|
2692
|
+
} catch (e) {
|
|
2693
|
+
this.config.debugMode && i.warn("CrossTabSession", "Failed to store tab info", { error: e });
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
/**
|
|
2697
|
+
* Clear tab info from localStorage
|
|
2698
|
+
*/
|
|
2699
|
+
clearTabInfo() {
|
|
2700
|
+
this.storageManager.removeItem(Se(this.projectId, this.tabId));
|
|
2701
|
+
}
|
|
2702
|
+
/**
|
|
2703
|
+
* Check if BroadcastChannel is supported
|
|
2704
|
+
*/
|
|
2705
|
+
isBroadcastChannelSupported() {
|
|
2706
|
+
return typeof window < "u" && "BroadcastChannel" in window;
|
|
2707
|
+
}
|
|
2708
|
+
/**
|
|
2709
|
+
* Get session timeout considering cross-tab activity
|
|
2710
|
+
*/
|
|
2711
|
+
getEffectiveSessionTimeout() {
|
|
2712
|
+
const e = this.getStoredSessionContext();
|
|
2713
|
+
if (!e)
|
|
2714
|
+
return this.get("config")?.sessionTimeout ?? L;
|
|
2715
|
+
const s = Date.now() - e.lastActivity, r = this.get("config")?.sessionTimeout ?? L;
|
|
2716
|
+
return Math.max(0, r - s);
|
|
2717
|
+
}
|
|
2718
|
+
/**
|
|
2719
|
+
* Update session activity from any tab
|
|
2720
|
+
*/
|
|
2721
|
+
updateSessionActivity() {
|
|
2722
|
+
const e = this.getStoredSessionContext();
|
|
2723
|
+
e && (e.lastActivity = Date.now(), this.storeSessionContext(e)), this.sendHeartbeat();
|
|
2724
|
+
}
|
|
2725
|
+
/**
|
|
2726
|
+
* Cleanup resources
|
|
2727
|
+
*/
|
|
2728
|
+
destroy() {
|
|
2729
|
+
this.heartbeatInterval && (clearInterval(this.heartbeatInterval), this.heartbeatInterval = null), this.electionTimeout && (clearTimeout(this.electionTimeout), this.electionTimeout = null), this.cleanupTimeout && (clearTimeout(this.cleanupTimeout), this.cleanupTimeout = null), this.fallbackLeadershipTimeout && (clearTimeout(this.fallbackLeadershipTimeout), this.fallbackLeadershipTimeout = null), this.electionDelayTimeout && (clearTimeout(this.electionDelayTimeout), this.electionDelayTimeout = null), this.tabInfoCleanupTimeout && (clearTimeout(this.tabInfoCleanupTimeout), this.tabInfoCleanupTimeout = null), this.closingAnnouncementTimeout && (clearTimeout(this.closingAnnouncementTimeout), this.closingAnnouncementTimeout = null), this.leaderHealthCheckInterval && (clearInterval(this.leaderHealthCheckInterval), this.leaderHealthCheckInterval = null), this.endSession("manual_stop"), this.broadcastChannel && this.broadcastChannel.close();
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
class Dt extends p {
|
|
2733
|
+
eventManager;
|
|
2734
|
+
storageManager;
|
|
2735
|
+
sessionStorageKey;
|
|
2736
|
+
sessionManager = null;
|
|
2737
|
+
recoveryManager = null;
|
|
2738
|
+
_crossTabSessionManager = null;
|
|
2739
|
+
heartbeatInterval = null;
|
|
2740
|
+
_isInitializingCrossTab = !1;
|
|
2741
|
+
get crossTabSessionManager() {
|
|
2742
|
+
if (!this._crossTabSessionManager && !this._isInitializingCrossTab && this.shouldUseCrossTabs()) {
|
|
2743
|
+
this._isInitializingCrossTab = !0;
|
|
2744
|
+
try {
|
|
2745
|
+
const e = this.get("config")?.id;
|
|
2746
|
+
e && this.initializeCrossTabSessionManager(e);
|
|
2747
|
+
} catch (e) {
|
|
2748
|
+
i.error("SessionHandler", "Failed to initialize cross-tab session manager", {
|
|
2749
|
+
error: e instanceof Error ? e.message : "Unknown error"
|
|
2750
|
+
});
|
|
2751
|
+
} finally {
|
|
2752
|
+
this._isInitializingCrossTab = !1;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
return this._crossTabSessionManager;
|
|
2756
|
+
}
|
|
2757
|
+
shouldUseCrossTabs() {
|
|
2758
|
+
return typeof BroadcastChannel < "u" && typeof navigator < "u" && "serviceWorker" in navigator;
|
|
2759
|
+
}
|
|
2760
|
+
constructor(e, t) {
|
|
2761
|
+
super(), this.eventManager = t, this.storageManager = e, this.sessionStorageKey = st(this.get("config")?.id);
|
|
2762
|
+
const s = this.get("config")?.id;
|
|
2763
|
+
s && this.initializeSessionRecoveryManager(s);
|
|
2764
|
+
}
|
|
2765
|
+
startTracking() {
|
|
2766
|
+
if (this.sessionManager) {
|
|
2767
|
+
i.debug("SessionHandler", "Session tracking already active");
|
|
2768
|
+
return;
|
|
2769
|
+
}
|
|
2770
|
+
i.debug("SessionHandler", "Starting session tracking"), this.checkOrphanedSessions();
|
|
2771
|
+
const e = async () => {
|
|
2772
|
+
if (this.crossTabSessionManager && this.crossTabSessionManager.updateSessionActivity(), !this.get("sessionId"))
|
|
2773
|
+
try {
|
|
2774
|
+
const r = await this.createOrJoinSession();
|
|
2775
|
+
this.set("sessionId", r.sessionId), i.info("SessionHandler", "🏁 Session started", {
|
|
2776
|
+
sessionId: r.sessionId,
|
|
2777
|
+
recovered: r.recovered,
|
|
2778
|
+
crossTabActive: !!this.crossTabSessionManager
|
|
2779
|
+
}), this.trackSession(h.SESSION_START, r.recovered), this.persistSession(r.sessionId), this.startHeartbeat();
|
|
2780
|
+
} catch (r) {
|
|
2781
|
+
i.error(
|
|
2782
|
+
"SessionHandler",
|
|
2783
|
+
`Session creation failed: ${r instanceof Error ? r.message : "Unknown error"}`
|
|
2784
|
+
), this.forceCleanupSession();
|
|
2785
|
+
}
|
|
2786
|
+
}, t = () => {
|
|
2787
|
+
if (this.get("sessionId")) {
|
|
2788
|
+
if (this.crossTabSessionManager && this.crossTabSessionManager.getEffectiveSessionTimeout() > 0) {
|
|
2789
|
+
(this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug") && i.debug("SessionHandler", "Session kept alive by cross-tab activity");
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
this.sessionManager.endSessionManaged("inactivity").then((r) => {
|
|
2793
|
+
i.info("SessionHandler", "🛑 Session ended by inactivity", {
|
|
2794
|
+
sessionId: this.get("sessionId"),
|
|
2795
|
+
reason: r.reason,
|
|
2796
|
+
success: r.success,
|
|
2797
|
+
eventsFlushed: r.eventsFlushed
|
|
2798
|
+
}), this.crossTabSessionManager && this.crossTabSessionManager.endSession("inactivity"), this.clearPersistedSession(), this.stopHeartbeat();
|
|
2799
|
+
}).catch((r) => {
|
|
2800
|
+
i.error(
|
|
2801
|
+
"SessionHandler",
|
|
2802
|
+
`Session end failed: ${r instanceof Error ? r.message : "Unknown error"}`
|
|
2803
|
+
), this.forceCleanupSession();
|
|
2804
|
+
});
|
|
2805
|
+
}
|
|
2806
|
+
}, s = {
|
|
2807
|
+
enablePageUnloadHandlers: !0,
|
|
2808
|
+
debugMode: (this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug") ?? !1,
|
|
2809
|
+
syncTimeoutMs: pe.SYNC_TIMEOUT_MS,
|
|
2810
|
+
maxRetries: pe.MAX_RETRY_ATTEMPTS
|
|
2811
|
+
};
|
|
2812
|
+
this.sessionManager = new Pt(
|
|
2813
|
+
e,
|
|
2814
|
+
t,
|
|
2815
|
+
this.eventManager,
|
|
2816
|
+
this.storageManager,
|
|
2817
|
+
s
|
|
2818
|
+
), i.debug("SessionHandler", "Session manager initialized"), this.startInitialSession();
|
|
2819
|
+
}
|
|
2820
|
+
stopTracking() {
|
|
2821
|
+
if (i.info("SessionHandler", "Stopping session tracking"), this.sessionManager) {
|
|
2822
|
+
if (this.get("sessionId"))
|
|
2823
|
+
try {
|
|
2824
|
+
this.sessionManager.endSessionSafely("manual_stop", { forceSync: !0 }), this.clearPersistedSession(), this.stopHeartbeat();
|
|
2825
|
+
} catch (e) {
|
|
2826
|
+
i.error(
|
|
2827
|
+
"SessionHandler",
|
|
2828
|
+
`Manual session stop failed: ${e instanceof Error ? e.message : "Unknown error"}`
|
|
2829
|
+
), this.forceCleanupSession();
|
|
2830
|
+
}
|
|
2831
|
+
this.sessionManager.destroy(), this.sessionManager = null;
|
|
2832
|
+
}
|
|
2833
|
+
this._crossTabSessionManager && (this._crossTabSessionManager.destroy(), this._crossTabSessionManager = null), this._isInitializingCrossTab = !1, this.recoveryManager && (this.recoveryManager.cleanupOldRecoveryAttempts(), this.recoveryManager = null);
|
|
2834
|
+
}
|
|
2835
|
+
initializeSessionRecoveryManager(e) {
|
|
2836
|
+
this.recoveryManager = new Ce(this.storageManager, e, this.eventManager), i.debug("SessionHandler", "Session recovery manager initialized", { projectId: e });
|
|
2837
|
+
}
|
|
2838
|
+
initializeCrossTabSessionManager(e) {
|
|
2839
|
+
const t = {
|
|
2840
|
+
debugMode: (this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug") ?? !1
|
|
2841
|
+
}, l = {
|
|
2842
|
+
onSessionStart: (c) => {
|
|
2843
|
+
(this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug") && i.debug("SessionHandler", `Cross-tab session started: ${c}`), this.set("sessionId", c), this.trackSession(h.SESSION_START, !1), this.persistSession(c), this.startHeartbeat();
|
|
2844
|
+
},
|
|
2845
|
+
onSessionEnd: (c) => {
|
|
2846
|
+
(this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug") && i.debug("SessionHandler", `Cross-tab session ended: ${c}`), this.clearPersistedSession(), this.trackSession(h.SESSION_END, !1, c);
|
|
2847
|
+
},
|
|
2848
|
+
onTabActivity: () => {
|
|
2849
|
+
(this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug") && i.debug("SessionHandler", "Cross-tab activity detected");
|
|
2850
|
+
},
|
|
2851
|
+
onCrossTabConflict: () => {
|
|
2852
|
+
(this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug") && i.warn("SessionHandler", "Cross-tab conflict detected"), this.sessionManager && this.sessionManager.trackSessionHealth("conflict");
|
|
2853
|
+
}
|
|
2854
|
+
};
|
|
2855
|
+
this._crossTabSessionManager = new Ht(this.storageManager, e, t, l), i.debug("SessionHandler", "Cross-tab session manager initialized", { projectId: e });
|
|
2856
|
+
}
|
|
2857
|
+
async createOrJoinSession() {
|
|
2858
|
+
if (this.crossTabSessionManager) {
|
|
2859
|
+
const t = this.crossTabSessionManager.getSessionId();
|
|
2860
|
+
if (t)
|
|
2861
|
+
return { sessionId: t, recovered: !1 };
|
|
2862
|
+
const s = this.sessionManager.startSession();
|
|
2863
|
+
return { sessionId: s.sessionId, recovered: s.recovered ?? !1 };
|
|
2864
|
+
}
|
|
2865
|
+
const e = this.sessionManager.startSession();
|
|
2866
|
+
return { sessionId: e.sessionId, recovered: e.recovered ?? !1 };
|
|
2867
|
+
}
|
|
2868
|
+
forceCleanupSession() {
|
|
2869
|
+
if (this.set("sessionId", null), this.clearPersistedSession(), this.stopHeartbeat(), this.crossTabSessionManager)
|
|
2870
|
+
try {
|
|
2871
|
+
this.crossTabSessionManager.endSession("orphaned_cleanup");
|
|
2872
|
+
} catch (e) {
|
|
2873
|
+
(this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug") && i.warn(
|
|
2874
|
+
"SessionHandler",
|
|
2875
|
+
`Cross-tab cleanup failed during force cleanup: ${e instanceof Error ? e.message : "Unknown error"}`
|
|
2876
|
+
);
|
|
2877
|
+
}
|
|
2878
|
+
try {
|
|
2879
|
+
this.trackSession(h.SESSION_END, !1, "orphaned_cleanup");
|
|
2880
|
+
} catch (e) {
|
|
2881
|
+
(this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug") && i.warn(
|
|
2882
|
+
"SessionHandler",
|
|
2883
|
+
`Session tracking failed during force cleanup: ${e instanceof Error ? e.message : "Unknown error"}`
|
|
2884
|
+
);
|
|
2885
|
+
}
|
|
2886
|
+
(this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug") && i.debug("SessionHandler", "Forced session cleanup completed");
|
|
2887
|
+
}
|
|
2888
|
+
trackSession(e, t = !1, s) {
|
|
2889
|
+
this.eventManager.track({
|
|
2890
|
+
type: e,
|
|
2891
|
+
...e === h.SESSION_START && t && { session_start_recovered: t },
|
|
2892
|
+
...e === h.SESSION_END && { session_end_reason: s ?? "orphaned_cleanup" }
|
|
2893
|
+
});
|
|
2894
|
+
}
|
|
2895
|
+
startInitialSession() {
|
|
2896
|
+
if (this.get("sessionId")) {
|
|
2897
|
+
i.debug("SessionHandler", "Session already exists, skipping initial session creation");
|
|
2898
|
+
return;
|
|
2899
|
+
}
|
|
2900
|
+
if (i.debug("SessionHandler", "Starting initial session"), this.crossTabSessionManager) {
|
|
2901
|
+
const t = this.crossTabSessionManager.getSessionId();
|
|
2902
|
+
if (t) {
|
|
2903
|
+
this.set("sessionId", t), this.persistSession(t), this.startHeartbeat();
|
|
2904
|
+
return;
|
|
2905
|
+
}
|
|
2906
|
+
return;
|
|
2907
|
+
}
|
|
2908
|
+
i.debug("SessionHandler", "Starting regular session (no cross-tab)");
|
|
2909
|
+
const e = this.sessionManager.startSession();
|
|
2910
|
+
this.set("sessionId", e.sessionId), this.trackSession(h.SESSION_START, e.recovered), this.persistSession(e.sessionId), this.startHeartbeat();
|
|
2911
|
+
}
|
|
2912
|
+
checkOrphanedSessions() {
|
|
2913
|
+
const e = this.storageManager.getItem(this.sessionStorageKey);
|
|
2914
|
+
if (e)
|
|
2915
|
+
try {
|
|
2916
|
+
const t = JSON.parse(e), r = Date.now() - t.lastHeartbeat, a = this.get("config")?.sessionTimeout ?? L;
|
|
2917
|
+
if (r > a) {
|
|
2918
|
+
const o = this.recoveryManager?.hasRecoverableSession();
|
|
2919
|
+
if (o && this.recoveryManager) {
|
|
2920
|
+
const l = {
|
|
2921
|
+
sessionId: t.sessionId,
|
|
2922
|
+
startTime: t.startTime,
|
|
2923
|
+
lastActivity: t.lastHeartbeat,
|
|
2924
|
+
tabCount: 1,
|
|
2925
|
+
recoveryAttempts: 0,
|
|
2926
|
+
metadata: {
|
|
2927
|
+
userAgent: navigator.userAgent,
|
|
2928
|
+
pageUrl: this.get("pageUrl")
|
|
2929
|
+
}
|
|
2930
|
+
};
|
|
2931
|
+
this.recoveryManager.storeSessionContextForRecovery(l), (this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug") && i.debug("SessionHandler", `Orphaned session stored for recovery: ${t.sessionId}`);
|
|
2932
|
+
}
|
|
2933
|
+
this.trackSession(h.SESSION_END), this.clearPersistedSession(), (this.get("config")?.mode === "qa" || this.get("config")?.mode === "debug") && i.debug(
|
|
2934
|
+
"SessionHandler",
|
|
2935
|
+
`Orphaned session ended: ${t.sessionId}, recovery available: ${o}`
|
|
2936
|
+
);
|
|
2937
|
+
}
|
|
2938
|
+
} catch {
|
|
2939
|
+
this.clearPersistedSession();
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
persistSession(e) {
|
|
2943
|
+
const t = {
|
|
2944
|
+
sessionId: e,
|
|
2945
|
+
startTime: Date.now(),
|
|
2946
|
+
lastHeartbeat: Date.now()
|
|
2947
|
+
};
|
|
2948
|
+
this.storageManager.setItem(this.sessionStorageKey, JSON.stringify(t));
|
|
2949
|
+
}
|
|
2950
|
+
clearPersistedSession() {
|
|
2951
|
+
this.storageManager.removeItem(this.sessionStorageKey);
|
|
2952
|
+
}
|
|
2953
|
+
startHeartbeat() {
|
|
2954
|
+
this.stopHeartbeat(), this.heartbeatInterval = setInterval(() => {
|
|
2955
|
+
const e = this.storageManager.getItem(this.sessionStorageKey);
|
|
2956
|
+
if (e)
|
|
2957
|
+
try {
|
|
2958
|
+
const t = JSON.parse(e);
|
|
2959
|
+
t.lastHeartbeat = Date.now(), this.storageManager.setItem(this.sessionStorageKey, JSON.stringify(t));
|
|
2960
|
+
} catch {
|
|
2961
|
+
this.clearPersistedSession();
|
|
2962
|
+
}
|
|
2963
|
+
}, ze);
|
|
2964
|
+
}
|
|
2965
|
+
stopHeartbeat() {
|
|
2966
|
+
this.heartbeatInterval && (clearInterval(this.heartbeatInterval), this.heartbeatInterval = null);
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
class xt extends p {
|
|
2970
|
+
eventManager;
|
|
2971
|
+
onTrack;
|
|
2972
|
+
originalPushState;
|
|
2973
|
+
originalReplaceState;
|
|
2974
|
+
constructor(e, t) {
|
|
2975
|
+
super(), this.eventManager = e, this.onTrack = t;
|
|
2976
|
+
}
|
|
2977
|
+
startTracking() {
|
|
2978
|
+
i.debug("PageViewHandler", "Starting page view tracking"), this.trackInitialPageView(), this.trackCurrentPage(), window.addEventListener("popstate", this.trackCurrentPage), window.addEventListener("hashchange", this.trackCurrentPage), this.patchHistory("pushState"), this.patchHistory("replaceState");
|
|
2979
|
+
}
|
|
2980
|
+
stopTracking() {
|
|
2981
|
+
i.debug("PageViewHandler", "Stopping page view tracking"), window.removeEventListener("popstate", this.trackCurrentPage), window.removeEventListener("hashchange", this.trackCurrentPage), this.originalPushState && (window.history.pushState = this.originalPushState), this.originalReplaceState && (window.history.replaceState = this.originalReplaceState);
|
|
2982
|
+
}
|
|
2983
|
+
patchHistory(e) {
|
|
2984
|
+
e === "pushState" && !this.originalPushState ? this.originalPushState = window.history.pushState : e === "replaceState" && !this.originalReplaceState && (this.originalReplaceState = window.history.replaceState);
|
|
2985
|
+
const t = window.history[e];
|
|
2986
|
+
window.history[e] = (...s) => {
|
|
2987
|
+
t.apply(window.history, s), this.trackCurrentPage();
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2990
|
+
trackCurrentPage = () => {
|
|
2991
|
+
const e = window.location.href, t = se(e, this.get("config").sensitiveQueryParams);
|
|
2992
|
+
if (this.get("pageUrl") !== t) {
|
|
2993
|
+
const s = this.get("pageUrl");
|
|
2994
|
+
i.debug("PageViewHandler", "Page navigation detected", { from: s, to: t }), this.set("pageUrl", t), this.eventManager.track({
|
|
2995
|
+
type: h.PAGE_VIEW,
|
|
2996
|
+
page_url: this.get("pageUrl"),
|
|
2997
|
+
from_page_url: s,
|
|
2998
|
+
...this.extractPageViewData() && { page_view: this.extractPageViewData() }
|
|
2999
|
+
}), this.onTrack();
|
|
3000
|
+
}
|
|
3001
|
+
};
|
|
3002
|
+
trackInitialPageView() {
|
|
3003
|
+
this.eventManager.track({
|
|
3004
|
+
type: h.PAGE_VIEW,
|
|
3005
|
+
page_url: this.get("pageUrl"),
|
|
3006
|
+
...this.extractPageViewData() && { page_view: this.extractPageViewData() }
|
|
3007
|
+
}), this.onTrack();
|
|
3008
|
+
}
|
|
3009
|
+
extractPageViewData() {
|
|
3010
|
+
const e = window.location, t = {
|
|
3011
|
+
...document.referrer && { referrer: document.referrer },
|
|
3012
|
+
...document.title && { title: document.title },
|
|
3013
|
+
...e.pathname && { pathname: e.pathname },
|
|
3014
|
+
...e.search && { search: e.search },
|
|
3015
|
+
...e.hash && { hash: e.hash }
|
|
3016
|
+
};
|
|
3017
|
+
return Object.values(t).some((s) => !!s) ? t : void 0;
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
class Ot extends p {
|
|
3021
|
+
eventManager;
|
|
3022
|
+
clickHandler;
|
|
3023
|
+
constructor(e) {
|
|
3024
|
+
super(), this.eventManager = e;
|
|
3025
|
+
}
|
|
3026
|
+
startTracking() {
|
|
3027
|
+
if (this.clickHandler) {
|
|
3028
|
+
i.debug("ClickHandler", "Click tracking already active");
|
|
3029
|
+
return;
|
|
3030
|
+
}
|
|
3031
|
+
i.debug("ClickHandler", "Starting click tracking"), this.clickHandler = (e) => {
|
|
3032
|
+
const t = e, s = t.target, r = s instanceof HTMLElement ? s : s instanceof Node && s.parentElement instanceof HTMLElement ? s.parentElement : null;
|
|
3033
|
+
if (!r) {
|
|
3034
|
+
i.warn("ClickHandler", "Click target not found or not an element");
|
|
3035
|
+
return;
|
|
3036
|
+
}
|
|
3037
|
+
i.info("ClickHandler", "🖱️ Click detected on element", {
|
|
3038
|
+
tagName: r.tagName,
|
|
3039
|
+
className: r.className || "none",
|
|
3040
|
+
textContent: r.textContent?.slice(0, 50) ?? "empty"
|
|
3041
|
+
});
|
|
3042
|
+
const a = this.findTrackingElement(r), o = this.getRelevantClickElement(r), l = this.calculateClickCoordinates(t, r);
|
|
3043
|
+
if (a) {
|
|
3044
|
+
const d = this.extractTrackingData(a);
|
|
3045
|
+
if (d) {
|
|
3046
|
+
const u = this.createCustomEventData(d);
|
|
3047
|
+
this.eventManager.track({
|
|
3048
|
+
type: h.CUSTOM,
|
|
3049
|
+
custom_event: {
|
|
3050
|
+
name: u.name,
|
|
3051
|
+
...u.value && { metadata: { value: u.value } }
|
|
3052
|
+
}
|
|
3053
|
+
});
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
const c = this.generateClickData(r, o, l);
|
|
3057
|
+
this.eventManager.track({
|
|
3058
|
+
type: h.CLICK,
|
|
3059
|
+
click_data: c
|
|
3060
|
+
});
|
|
3061
|
+
}, window.addEventListener("click", this.clickHandler, !0);
|
|
3062
|
+
}
|
|
3063
|
+
stopTracking() {
|
|
3064
|
+
this.clickHandler && (window.removeEventListener("click", this.clickHandler, !0), this.clickHandler = void 0);
|
|
3065
|
+
}
|
|
3066
|
+
findTrackingElement(e) {
|
|
3067
|
+
return e.hasAttribute(`${z}-name`) ? e : e.closest(`[${z}-name]`) || void 0;
|
|
3068
|
+
}
|
|
3069
|
+
getRelevantClickElement(e) {
|
|
3070
|
+
for (const t of fe)
|
|
3071
|
+
try {
|
|
3072
|
+
if (e.matches(t))
|
|
3073
|
+
return e;
|
|
3074
|
+
} catch (s) {
|
|
3075
|
+
i.warn("ClickHandler", "Invalid selector in interactive elements check", {
|
|
3076
|
+
selector: t,
|
|
3077
|
+
error: s instanceof Error ? s.message : "Unknown error"
|
|
3078
|
+
});
|
|
3079
|
+
continue;
|
|
3080
|
+
}
|
|
3081
|
+
for (const t of fe)
|
|
3082
|
+
try {
|
|
3083
|
+
const s = e.closest(t);
|
|
3084
|
+
if (s)
|
|
3085
|
+
return s;
|
|
3086
|
+
} catch (s) {
|
|
3087
|
+
i.warn("ClickHandler", "Invalid selector in parent element search", {
|
|
3088
|
+
selector: t,
|
|
3089
|
+
error: s instanceof Error ? s.message : "Unknown error"
|
|
3090
|
+
});
|
|
3091
|
+
continue;
|
|
3092
|
+
}
|
|
3093
|
+
return e;
|
|
3094
|
+
}
|
|
3095
|
+
calculateClickCoordinates(e, t) {
|
|
3096
|
+
const s = t.getBoundingClientRect(), r = e.clientX, a = e.clientY, o = s.width > 0 ? Math.max(0, Math.min(1, Number(((r - s.left) / s.width).toFixed(3)))) : 0, l = s.height > 0 ? Math.max(0, Math.min(1, Number(((a - s.top) / s.height).toFixed(3)))) : 0;
|
|
3097
|
+
return { x: r, y: a, relativeX: o, relativeY: l };
|
|
3098
|
+
}
|
|
3099
|
+
extractTrackingData(e) {
|
|
3100
|
+
const t = e.getAttribute(`${z}-name`), s = e.getAttribute(`${z}-value`);
|
|
3101
|
+
if (t)
|
|
3102
|
+
return {
|
|
3103
|
+
element: e,
|
|
3104
|
+
name: t,
|
|
3105
|
+
...s && { value: s }
|
|
3106
|
+
};
|
|
3107
|
+
}
|
|
3108
|
+
generateClickData(e, t, s) {
|
|
3109
|
+
const { x: r, y: a, relativeX: o, relativeY: l } = s, c = this.getRelevantText(e, t), d = this.extractElementAttributes(t), u = t.getAttribute("href"), g = t.getAttribute("title"), y = t.getAttribute("alt"), I = t.getAttribute("role"), A = t.getAttribute("aria-label"), U = typeof t.className == "string" ? t.className : String(t.className);
|
|
3110
|
+
return {
|
|
3111
|
+
x: r,
|
|
3112
|
+
y: a,
|
|
3113
|
+
relativeX: o,
|
|
3114
|
+
relativeY: l,
|
|
3115
|
+
tag: t.tagName.toLowerCase(),
|
|
3116
|
+
...t.id && { id: t.id },
|
|
3117
|
+
...t.className && { class: U },
|
|
3118
|
+
...c && { text: c },
|
|
3119
|
+
...u && { href: u },
|
|
3120
|
+
...g && { title: g },
|
|
3121
|
+
...y && { alt: y },
|
|
3122
|
+
...I && { role: I },
|
|
3123
|
+
...A && { ariaLabel: A },
|
|
3124
|
+
...Object.keys(d).length > 0 && { dataAttributes: d }
|
|
3125
|
+
};
|
|
3126
|
+
}
|
|
3127
|
+
getRelevantText(e, t) {
|
|
3128
|
+
const s = ["main", "section", "article", "body", "html", "header", "footer", "aside", "nav"], r = e.textContent?.trim() ?? "", a = t.textContent?.trim() ?? "";
|
|
3129
|
+
if (!r && !a)
|
|
3130
|
+
return "";
|
|
3131
|
+
if (r && r.length <= _)
|
|
3132
|
+
return r;
|
|
3133
|
+
const o = s.includes(t.tagName.toLowerCase()), l = a.length > _ * 2;
|
|
3134
|
+
return o && l ? r && r.length <= _ ? r : "" : a.length <= _ ? a : r && r.length < a.length * 0.1 ? r.length <= _ ? r : r.slice(0, _ - 3) + "..." : a.slice(0, _ - 3) + "...";
|
|
3135
|
+
}
|
|
3136
|
+
extractElementAttributes(e) {
|
|
3137
|
+
const t = ["id", "class", "data-testid", "aria-label", "title", "href", "type", "name"], s = {};
|
|
3138
|
+
for (const r of t) {
|
|
3139
|
+
const a = e.getAttribute(r);
|
|
3140
|
+
a && (s[r] = a);
|
|
3141
|
+
}
|
|
3142
|
+
return s;
|
|
3143
|
+
}
|
|
3144
|
+
createCustomEventData(e) {
|
|
3145
|
+
return {
|
|
3146
|
+
name: e.name,
|
|
3147
|
+
...e.value && { value: e.value }
|
|
3148
|
+
};
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
class Ft extends p {
|
|
3152
|
+
eventManager;
|
|
3153
|
+
containers = [];
|
|
3154
|
+
constructor(e) {
|
|
3155
|
+
super(), this.eventManager = e;
|
|
3156
|
+
}
|
|
3157
|
+
startTracking() {
|
|
3158
|
+
const e = this.get("config").scrollContainerSelectors, t = Array.isArray(e) ? e : typeof e == "string" ? [e] : [];
|
|
3159
|
+
i.debug("ScrollHandler", "Starting scroll tracking", { selectorsCount: t.length });
|
|
3160
|
+
const s = t.map((r) => this.safeQuerySelector(r)).filter((r) => r instanceof HTMLElement);
|
|
3161
|
+
s.length === 0 && s.push(window);
|
|
3162
|
+
for (const r of s)
|
|
3163
|
+
this.setupScrollContainer(r);
|
|
3164
|
+
}
|
|
3165
|
+
stopTracking() {
|
|
3166
|
+
i.debug("ScrollHandler", "Stopping scroll tracking", { containersCount: this.containers.length });
|
|
3167
|
+
for (const e of this.containers)
|
|
3168
|
+
e.debounceTimer && clearTimeout(e.debounceTimer), e.element instanceof Window ? window.removeEventListener("scroll", e.listener) : e.element.removeEventListener("scroll", e.listener);
|
|
3169
|
+
this.containers.length = 0;
|
|
3170
|
+
}
|
|
3171
|
+
setupScrollContainer(e) {
|
|
3172
|
+
if (e !== window && !this.isElementScrollable(e))
|
|
3173
|
+
return;
|
|
3174
|
+
const t = {
|
|
3175
|
+
element: e,
|
|
3176
|
+
lastScrollPos: this.getScrollTop(e),
|
|
3177
|
+
debounceTimer: null,
|
|
3178
|
+
listener: () => {
|
|
3179
|
+
}
|
|
3180
|
+
}, s = () => {
|
|
3181
|
+
if (this.get("suppressNextScroll")) {
|
|
3182
|
+
this.set("suppressNextScroll", !1);
|
|
3183
|
+
return;
|
|
3184
|
+
}
|
|
3185
|
+
t.debounceTimer && clearTimeout(t.debounceTimer), t.debounceTimer = window.setTimeout(() => {
|
|
3186
|
+
const r = this.calculateScrollData(t);
|
|
3187
|
+
r && this.eventManager.track({
|
|
3188
|
+
type: h.SCROLL,
|
|
3189
|
+
scroll_data: r
|
|
3190
|
+
}), t.debounceTimer = null;
|
|
3191
|
+
}, Me);
|
|
3192
|
+
};
|
|
3193
|
+
t.listener = s, this.containers.push(t), e instanceof Window ? window.addEventListener("scroll", s, { passive: !0 }) : e.addEventListener("scroll", s, { passive: !0 });
|
|
3194
|
+
}
|
|
3195
|
+
calculateScrollData(e) {
|
|
3196
|
+
const { element: t, lastScrollPos: s } = e, r = this.getScrollTop(t), a = this.getViewportHeight(t), o = this.getScrollHeight(t);
|
|
3197
|
+
if (t === window && o <= a)
|
|
3198
|
+
return null;
|
|
3199
|
+
const l = r > s ? j.DOWN : j.UP, c = o > a ? Math.min(100, Math.max(0, Math.floor(r / (o - a) * 100))) : 0;
|
|
3200
|
+
return Math.abs(r - s) < Ue ? null : (e.lastScrollPos = r, { depth: c, direction: l });
|
|
3201
|
+
}
|
|
3202
|
+
getScrollTop(e) {
|
|
3203
|
+
return e instanceof Window ? window.scrollY : e.scrollTop;
|
|
3204
|
+
}
|
|
3205
|
+
getViewportHeight(e) {
|
|
3206
|
+
return e instanceof Window ? window.innerHeight : e.clientHeight;
|
|
3207
|
+
}
|
|
3208
|
+
getScrollHeight(e) {
|
|
3209
|
+
return e instanceof Window ? document.documentElement.scrollHeight : e.scrollHeight;
|
|
3210
|
+
}
|
|
3211
|
+
isElementScrollable(e) {
|
|
3212
|
+
const t = getComputedStyle(e), s = t.overflowY === "auto" || t.overflowY === "scroll" || t.overflowX === "auto" || t.overflowX === "scroll" || t.overflow === "auto" || t.overflow === "scroll", r = e.scrollHeight > e.clientHeight || e.scrollWidth > e.clientWidth;
|
|
3213
|
+
return s && r;
|
|
3214
|
+
}
|
|
3215
|
+
safeQuerySelector(e) {
|
|
3216
|
+
try {
|
|
3217
|
+
return document.querySelector(e);
|
|
3218
|
+
} catch (t) {
|
|
3219
|
+
return i.clientWarn("ScrollHandler", "Invalid CSS selector", {
|
|
3220
|
+
selector: e,
|
|
3221
|
+
error: t instanceof Error ? t.message : "Unknown error"
|
|
3222
|
+
}), null;
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
class zt extends p {
|
|
3227
|
+
isInitialized = !1;
|
|
3228
|
+
constructor() {
|
|
3229
|
+
super();
|
|
3230
|
+
}
|
|
3231
|
+
async initialize() {
|
|
3232
|
+
if (this.isInitialized)
|
|
3233
|
+
return;
|
|
3234
|
+
const e = this.get("config").integrations?.googleAnalytics?.measurementId;
|
|
3235
|
+
if (!e?.trim()) {
|
|
3236
|
+
i.clientWarn("GoogleAnalytics", "Google Analytics integration disabled - measurementId not configured", {
|
|
3237
|
+
hasIntegrations: !!this.get("config").integrations,
|
|
3238
|
+
hasGoogleAnalytics: !!this.get("config").integrations?.googleAnalytics
|
|
3239
|
+
});
|
|
3240
|
+
return;
|
|
3241
|
+
}
|
|
3242
|
+
const t = this.get("userId");
|
|
3243
|
+
if (!t?.trim()) {
|
|
3244
|
+
i.warn("GoogleAnalytics", "Google Analytics initialization delayed - userId not available", {
|
|
3245
|
+
measurementId: e.substring(0, 8) + "..."
|
|
3246
|
+
});
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
try {
|
|
3250
|
+
if (this.isScriptAlreadyLoaded()) {
|
|
3251
|
+
i.info("GoogleAnalytics", "Google Analytics script already loaded", { measurementId: e }), this.isInitialized = !0;
|
|
3252
|
+
return;
|
|
3253
|
+
}
|
|
3254
|
+
await this.loadScript(e), this.configureGtag(e, t), this.isInitialized = !0, i.info("GoogleAnalytics", "Google Analytics integration initialized successfully", {
|
|
3255
|
+
measurementId: e,
|
|
3256
|
+
userId: t
|
|
3257
|
+
});
|
|
3258
|
+
} catch (s) {
|
|
3259
|
+
i.error("GoogleAnalytics", "Google Analytics initialization failed", {
|
|
3260
|
+
error: s instanceof Error ? s.message : "Unknown error",
|
|
3261
|
+
measurementId: e,
|
|
3262
|
+
userId: t
|
|
3263
|
+
});
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
trackEvent(e, t) {
|
|
3267
|
+
if (!e?.trim()) {
|
|
3268
|
+
i.clientWarn("GoogleAnalytics", "Event tracking skipped - invalid event name provided", {
|
|
3269
|
+
eventName: e,
|
|
3270
|
+
hasMetadata: !!t && Object.keys(t).length > 0
|
|
3271
|
+
});
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
if (this.isInitialized) {
|
|
3275
|
+
if (typeof window.gtag != "function") {
|
|
3276
|
+
i.warn("GoogleAnalytics", "Event tracking failed - gtag function not available", {
|
|
3277
|
+
eventName: e,
|
|
3278
|
+
hasGtag: typeof window.gtag,
|
|
3279
|
+
hasDataLayer: Array.isArray(window.dataLayer)
|
|
3280
|
+
});
|
|
3281
|
+
return;
|
|
3282
|
+
}
|
|
3283
|
+
try {
|
|
3284
|
+
window.gtag("event", e, t);
|
|
3285
|
+
} catch (s) {
|
|
3286
|
+
i.error("GoogleAnalytics", "Event tracking failed", {
|
|
3287
|
+
eventName: e,
|
|
3288
|
+
error: s instanceof Error ? s.message : "Unknown error",
|
|
3289
|
+
metadataKeys: Object.keys(t || {})
|
|
3290
|
+
});
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
cleanup() {
|
|
3295
|
+
this.isInitialized = !1;
|
|
3296
|
+
const e = document.getElementById("tracelog-ga-script");
|
|
3297
|
+
e && e.remove(), i.info("GoogleAnalytics", "Google Analytics integration cleanup completed");
|
|
3298
|
+
}
|
|
3299
|
+
isScriptAlreadyLoaded() {
|
|
3300
|
+
if (document.getElementById("tracelog-ga-script"))
|
|
3301
|
+
return !0;
|
|
3302
|
+
const t = document.querySelector('script[src*="googletagmanager.com/gtag/js"]');
|
|
3303
|
+
return t ? (i.clientWarn("GoogleAnalytics", "Google Analytics script already loaded from external source", {
|
|
3304
|
+
scriptSrc: t.getAttribute("src"),
|
|
3305
|
+
hasGtag: typeof window.gtag == "function"
|
|
3306
|
+
}), !0) : !1;
|
|
3307
|
+
}
|
|
3308
|
+
async loadScript(e) {
|
|
3309
|
+
return new Promise((t, s) => {
|
|
3310
|
+
try {
|
|
3311
|
+
const r = document.createElement("script");
|
|
3312
|
+
r.id = "tracelog-ga-script", r.async = !0, r.src = `https://www.googletagmanager.com/gtag/js?id=${e}`, r.onload = () => {
|
|
3313
|
+
t();
|
|
3314
|
+
}, r.onerror = () => {
|
|
3315
|
+
const a = new Error("Failed to load Google Analytics script");
|
|
3316
|
+
i.error("GoogleAnalytics", "Google Analytics script load failed", {
|
|
3317
|
+
measurementId: e,
|
|
3318
|
+
error: a.message,
|
|
3319
|
+
scriptSrc: r.src
|
|
3320
|
+
}), s(a);
|
|
3321
|
+
}, document.head.appendChild(r);
|
|
3322
|
+
} catch (r) {
|
|
3323
|
+
const a = r instanceof Error ? r : new Error(String(r));
|
|
3324
|
+
i.error("GoogleAnalytics", "Error creating Google Analytics script", {
|
|
3325
|
+
measurementId: e,
|
|
3326
|
+
error: a.message
|
|
3327
|
+
}), s(a);
|
|
3328
|
+
}
|
|
3329
|
+
});
|
|
3330
|
+
}
|
|
3331
|
+
configureGtag(e, t) {
|
|
3332
|
+
try {
|
|
3333
|
+
const s = document.createElement("script");
|
|
3334
|
+
s.innerHTML = `
|
|
3335
|
+
window.dataLayer = window.dataLayer || [];
|
|
3336
|
+
function gtag(){dataLayer.push(arguments);}
|
|
3337
|
+
gtag('js', new Date());
|
|
3338
|
+
gtag('config', '${e}', {
|
|
3339
|
+
'user_id': '${t}'
|
|
3340
|
+
});
|
|
3341
|
+
`, document.head.appendChild(s);
|
|
3342
|
+
} catch (s) {
|
|
3343
|
+
throw i.error("GoogleAnalytics", "Failed to configure Google Analytics", {
|
|
3344
|
+
measurementId: e,
|
|
3345
|
+
userId: t,
|
|
3346
|
+
error: s instanceof Error ? s.message : "Unknown error"
|
|
3347
|
+
}), s;
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
class Vt {
|
|
3352
|
+
storage = null;
|
|
3353
|
+
fallbackStorage = /* @__PURE__ */ new Map();
|
|
3354
|
+
storageAvailable = !1;
|
|
3355
|
+
constructor() {
|
|
3356
|
+
this.storage = this.init(), this.storageAvailable = this.storage !== null, this.storageAvailable || i.warn("StorageManager", "localStorage not available, using memory fallback");
|
|
3357
|
+
}
|
|
3358
|
+
getItem(e) {
|
|
3359
|
+
if (!this.storageAvailable)
|
|
3360
|
+
return this.fallbackStorage.get(e) ?? null;
|
|
3361
|
+
try {
|
|
3362
|
+
return this.storage ? this.storage.getItem(e) : this.fallbackStorage.get(e) ?? null;
|
|
3363
|
+
} catch (t) {
|
|
3364
|
+
return i.warn("StorageManager", "Storage getItem failed, using memory fallback", { key: e, error: t }), this.storageAvailable = !1, this.fallbackStorage.get(e) ?? null;
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
setItem(e, t) {
|
|
3368
|
+
if (!this.storageAvailable) {
|
|
3369
|
+
this.fallbackStorage.set(e, t);
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
try {
|
|
3373
|
+
if (this.storage) {
|
|
3374
|
+
this.storage.setItem(e, t);
|
|
3375
|
+
return;
|
|
3376
|
+
}
|
|
3377
|
+
this.fallbackStorage.set(e, t);
|
|
3378
|
+
} catch (s) {
|
|
3379
|
+
i.warn("StorageManager", "Storage setItem failed, using memory fallback", { key: e, error: s }), this.storageAvailable = !1, this.fallbackStorage.set(e, t);
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
removeItem(e) {
|
|
3383
|
+
if (!this.storageAvailable) {
|
|
3384
|
+
this.fallbackStorage.delete(e);
|
|
3385
|
+
return;
|
|
3386
|
+
}
|
|
3387
|
+
try {
|
|
3388
|
+
if (this.storage) {
|
|
3389
|
+
this.storage.removeItem(e);
|
|
3390
|
+
return;
|
|
3391
|
+
}
|
|
3392
|
+
this.fallbackStorage.delete(e);
|
|
3393
|
+
} catch (t) {
|
|
3394
|
+
i.warn("StorageManager", "Storage removeItem failed, using memory fallback", { key: e, error: t }), this.storageAvailable = !1, this.fallbackStorage.delete(e);
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
init() {
|
|
3398
|
+
try {
|
|
3399
|
+
const e = "__storage_test__", t = window.localStorage;
|
|
3400
|
+
return t.setItem(e, e), t.removeItem(e), t;
|
|
3401
|
+
} catch {
|
|
3402
|
+
return null;
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
class $t extends p {
|
|
3407
|
+
eventManager;
|
|
3408
|
+
reportedByNav = /* @__PURE__ */ new Map();
|
|
3409
|
+
observers = [];
|
|
3410
|
+
lastLongTaskSentAt = 0;
|
|
3411
|
+
constructor(e) {
|
|
3412
|
+
super(), this.eventManager = e;
|
|
3413
|
+
}
|
|
3414
|
+
async startTracking() {
|
|
3415
|
+
i.debug("PerformanceHandler", "Starting performance tracking"), await this.initWebVitals(), this.observeLongTasks(), this.reportTTFB();
|
|
3416
|
+
}
|
|
3417
|
+
stopTracking() {
|
|
3418
|
+
i.debug("PerformanceHandler", "Stopping performance tracking", { observersCount: this.observers.length }), this.observers.forEach((e, t) => {
|
|
3419
|
+
try {
|
|
3420
|
+
e.disconnect();
|
|
3421
|
+
} catch (s) {
|
|
3422
|
+
i.warn("PerformanceHandler", "Failed to disconnect performance observer", {
|
|
3423
|
+
error: s instanceof Error ? s.message : "Unknown error",
|
|
3424
|
+
observerIndex: t
|
|
3425
|
+
});
|
|
3426
|
+
}
|
|
3427
|
+
}), this.observers.length = 0, this.reportedByNav.clear(), i.debug("PerformanceHandler", "Performance tracking cleanup completed", {
|
|
3428
|
+
remainingObservers: this.observers.length,
|
|
3429
|
+
clearedNavReports: !0
|
|
3430
|
+
});
|
|
3431
|
+
}
|
|
3432
|
+
observeWebVitalsFallback() {
|
|
3433
|
+
this.reportTTFB(), this.safeObserve(
|
|
3434
|
+
"largest-contentful-paint",
|
|
3435
|
+
(t) => {
|
|
3436
|
+
const s = t.getEntries(), r = s[s.length - 1];
|
|
3437
|
+
r && this.sendVital({ type: "LCP", value: Number(r.startTime.toFixed(k)) });
|
|
3438
|
+
},
|
|
3439
|
+
{ type: "largest-contentful-paint", buffered: !0 },
|
|
3440
|
+
!0
|
|
3441
|
+
);
|
|
3442
|
+
let e = 0;
|
|
3443
|
+
this.safeObserve(
|
|
3444
|
+
"layout-shift",
|
|
3445
|
+
(t) => {
|
|
3446
|
+
const s = t.getEntries();
|
|
3447
|
+
for (const r of s) {
|
|
3448
|
+
if (r.hadRecentInput === !0)
|
|
3449
|
+
continue;
|
|
3450
|
+
const a = typeof r.value == "number" ? r.value : 0;
|
|
3451
|
+
e += a;
|
|
3452
|
+
}
|
|
3453
|
+
this.sendVital({ type: "CLS", value: Number(e.toFixed(Pe)) });
|
|
3454
|
+
},
|
|
3455
|
+
{ type: "layout-shift", buffered: !0 }
|
|
3456
|
+
), this.safeObserve(
|
|
3457
|
+
"paint",
|
|
3458
|
+
(t) => {
|
|
3459
|
+
for (const s of t.getEntries())
|
|
3460
|
+
s.name === "first-contentful-paint" && this.sendVital({ type: "FCP", value: Number(s.startTime.toFixed(k)) });
|
|
3461
|
+
},
|
|
3462
|
+
{ type: "paint", buffered: !0 },
|
|
3463
|
+
!0
|
|
3464
|
+
), this.safeObserve(
|
|
3465
|
+
"event",
|
|
3466
|
+
(t) => {
|
|
3467
|
+
let s = 0;
|
|
3468
|
+
const r = t.getEntries();
|
|
3469
|
+
for (const a of r) {
|
|
3470
|
+
const o = (a.processingEnd ?? 0) - (a.startTime ?? 0);
|
|
3471
|
+
s = Math.max(s, o);
|
|
3472
|
+
}
|
|
3473
|
+
s > 0 && this.sendVital({ type: "INP", value: Number(s.toFixed(k)) });
|
|
3474
|
+
},
|
|
3475
|
+
{ type: "event", buffered: !0 }
|
|
3476
|
+
);
|
|
3477
|
+
}
|
|
3478
|
+
async initWebVitals() {
|
|
3479
|
+
try {
|
|
3480
|
+
const { onLCP: e, onCLS: t, onFCP: s, onTTFB: r, onINP: a } = await import("./web-vitals-CCnqwnC8.mjs"), o = (l) => (c) => {
|
|
3481
|
+
const d = Number(c.value.toFixed(k));
|
|
3482
|
+
this.sendVital({ type: l, value: d });
|
|
3483
|
+
};
|
|
3484
|
+
e(o("LCP")), t(o("CLS")), s(o("FCP")), r(o("TTFB")), a(o("INP"));
|
|
3485
|
+
} catch (e) {
|
|
3486
|
+
i.warn("PerformanceHandler", "Failed to load web-vitals library, using fallback", {
|
|
3487
|
+
error: e instanceof Error ? e.message : "Unknown error"
|
|
3488
|
+
}), this.observeWebVitalsFallback();
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
reportTTFB() {
|
|
3492
|
+
try {
|
|
3493
|
+
const e = performance.getEntriesByType("navigation")[0];
|
|
3494
|
+
if (!e) {
|
|
3495
|
+
i.debug("PerformanceHandler", "Navigation timing not available for TTFB");
|
|
3496
|
+
return;
|
|
3497
|
+
}
|
|
3498
|
+
const t = e.responseStart;
|
|
3499
|
+
typeof t == "number" && Number.isFinite(t) ? this.sendVital({ type: "TTFB", value: Number(t.toFixed(k)) }) : i.debug("PerformanceHandler", "TTFB value is not a valid number", { ttfb: t });
|
|
3500
|
+
} catch (e) {
|
|
3501
|
+
i.warn("PerformanceHandler", "Failed to report TTFB", {
|
|
3502
|
+
error: e instanceof Error ? e.message : "Unknown error"
|
|
3503
|
+
});
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
observeLongTasks() {
|
|
3507
|
+
this.safeObserve(
|
|
3508
|
+
"longtask",
|
|
3509
|
+
(e) => {
|
|
3510
|
+
const t = e.getEntries();
|
|
3511
|
+
for (const s of t) {
|
|
3512
|
+
const r = Number(s.duration.toFixed(k)), a = Date.now();
|
|
3513
|
+
a - this.lastLongTaskSentAt >= Be && (this.trackWebVital("LONG_TASK", r), this.lastLongTaskSentAt = a);
|
|
3514
|
+
}
|
|
3515
|
+
},
|
|
3516
|
+
{ type: "longtask", buffered: !0 }
|
|
3517
|
+
);
|
|
3518
|
+
}
|
|
3519
|
+
sendVital(e) {
|
|
3520
|
+
const t = this.getNavigationId(), s = `${e.type}`;
|
|
3521
|
+
if (t) {
|
|
3522
|
+
this.reportedByNav.has(t) || this.reportedByNav.set(t, /* @__PURE__ */ new Set());
|
|
3523
|
+
const r = this.reportedByNav.get(t);
|
|
3524
|
+
if (r.has(s))
|
|
3525
|
+
return;
|
|
3526
|
+
r.add(s);
|
|
3527
|
+
}
|
|
3528
|
+
this.trackWebVital(e.type, e.value);
|
|
3529
|
+
}
|
|
3530
|
+
trackWebVital(e, t) {
|
|
3531
|
+
if (typeof t != "number" || !Number.isFinite(t)) {
|
|
3532
|
+
i.warn("PerformanceHandler", "Invalid web vital value", { type: e, value: t });
|
|
3533
|
+
return;
|
|
3534
|
+
}
|
|
3535
|
+
this.eventManager.track({
|
|
3536
|
+
type: h.WEB_VITALS,
|
|
3537
|
+
web_vitals: {
|
|
3538
|
+
type: e,
|
|
3539
|
+
value: t
|
|
3540
|
+
}
|
|
3541
|
+
});
|
|
3542
|
+
}
|
|
3543
|
+
getNavigationId() {
|
|
3544
|
+
try {
|
|
3545
|
+
const e = performance.getEntriesByType("navigation")[0];
|
|
3546
|
+
return e ? `${Math.round(e.startTime)}_${window.location.pathname}` : null;
|
|
3547
|
+
} catch (e) {
|
|
3548
|
+
return i.warn("PerformanceHandler", "Failed to get navigation ID", {
|
|
3549
|
+
error: e instanceof Error ? e.message : "Unknown error"
|
|
3550
|
+
}), null;
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
safeObserve(e, t, s, r = !1) {
|
|
3554
|
+
try {
|
|
3555
|
+
if (typeof PerformanceObserver > "u") return;
|
|
3556
|
+
const a = PerformanceObserver.supportedEntryTypes;
|
|
3557
|
+
if (a && !a.includes(e)) return;
|
|
3558
|
+
const o = new PerformanceObserver((l, c) => {
|
|
3559
|
+
if (t(l, c), r)
|
|
3560
|
+
try {
|
|
3561
|
+
c.disconnect();
|
|
3562
|
+
} catch {
|
|
3563
|
+
}
|
|
3564
|
+
});
|
|
3565
|
+
o.observe(s ?? { type: e, buffered: !0 }), r || this.observers.push(o);
|
|
3566
|
+
} catch (a) {
|
|
3567
|
+
i.warn("PerformanceHandler", "Failed to create performance observer", {
|
|
3568
|
+
type: e,
|
|
3569
|
+
error: a instanceof Error ? a.message : "Unknown error"
|
|
3570
|
+
});
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
class jt extends p {
|
|
3575
|
+
eventManager;
|
|
3576
|
+
piiPatterns = [
|
|
3577
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
|
|
3578
|
+
/\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g,
|
|
3579
|
+
/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g,
|
|
3580
|
+
/\b[A-Z]{2}\d{2}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g
|
|
3581
|
+
];
|
|
3582
|
+
constructor(e) {
|
|
3583
|
+
super(), this.eventManager = e;
|
|
3584
|
+
}
|
|
3585
|
+
startTracking() {
|
|
3586
|
+
i.debug("ErrorHandler", "Starting error tracking"), this.setupErrorListener(), this.setupUnhandledRejectionListener();
|
|
3587
|
+
}
|
|
3588
|
+
stopTracking() {
|
|
3589
|
+
i.debug("ErrorHandler", "Stopping error tracking"), window.removeEventListener("error", this.handleError), window.removeEventListener("unhandledrejection", this.handleUnhandledRejection);
|
|
3590
|
+
}
|
|
3591
|
+
setupErrorListener() {
|
|
3592
|
+
window.addEventListener("error", this.handleError);
|
|
3593
|
+
}
|
|
3594
|
+
setupUnhandledRejectionListener() {
|
|
3595
|
+
window.addEventListener("unhandledrejection", this.handleUnhandledRejection);
|
|
3596
|
+
}
|
|
3597
|
+
handleError = (e) => {
|
|
3598
|
+
const t = this.get("config");
|
|
3599
|
+
if (!this.shouldSample(t?.errorSampling ?? 0.1)) {
|
|
3600
|
+
i.debug("ErrorHandler", `Error not sampled, skipping (errorSampling: ${t?.errorSampling})`, {
|
|
3601
|
+
errorSampling: t?.errorSampling
|
|
3602
|
+
});
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
i.warn(
|
|
3606
|
+
"ErrorHandler",
|
|
3607
|
+
`JavaScript error captured: ${e.message} (filename: ${e.filename}, lineno: ${e.lineno})`,
|
|
3608
|
+
{
|
|
3609
|
+
message: e.message,
|
|
3610
|
+
filename: e.filename,
|
|
3611
|
+
lineno: e.lineno
|
|
3612
|
+
}
|
|
3613
|
+
), this.eventManager.track({
|
|
3614
|
+
type: h.ERROR,
|
|
3615
|
+
error_data: {
|
|
3616
|
+
type: D.JS_ERROR,
|
|
3617
|
+
message: this.sanitizeText(e.message || "Unknown error")
|
|
3618
|
+
}
|
|
3619
|
+
});
|
|
3620
|
+
};
|
|
3621
|
+
handleUnhandledRejection = (e) => {
|
|
3622
|
+
const t = this.get("config");
|
|
3623
|
+
if (!this.shouldSample(t?.errorSampling ?? 0.1)) {
|
|
3624
|
+
i.debug("ErrorHandler", "Promise rejection not sampled, skipping", {
|
|
3625
|
+
errorSampling: t?.errorSampling
|
|
3626
|
+
});
|
|
3627
|
+
return;
|
|
3628
|
+
}
|
|
3629
|
+
i.warn("ErrorHandler", `Unhandled promise rejection captured (reason: ${typeof e.reason})`, {
|
|
3630
|
+
reason: typeof e.reason
|
|
3631
|
+
});
|
|
3632
|
+
let s = "Unknown rejection";
|
|
3633
|
+
e.reason && (typeof e.reason == "string" ? s = e.reason : e.reason instanceof Error ? s = e.reason.message || e.reason.toString() : s = String(e.reason)), this.eventManager.track({
|
|
3634
|
+
type: h.ERROR,
|
|
3635
|
+
error_data: {
|
|
3636
|
+
type: D.PROMISE_REJECTION,
|
|
3637
|
+
message: this.sanitizeText(s)
|
|
3638
|
+
}
|
|
3639
|
+
});
|
|
3640
|
+
};
|
|
3641
|
+
sanitizeText(e) {
|
|
3642
|
+
let t = e;
|
|
3643
|
+
for (const s of this.piiPatterns)
|
|
3644
|
+
t = t.replace(s, "[REDACTED]");
|
|
3645
|
+
return t;
|
|
3646
|
+
}
|
|
3647
|
+
shouldSample(e) {
|
|
3648
|
+
return Math.random() < e;
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3651
|
+
class Gt extends p {
|
|
3652
|
+
eventManager;
|
|
3653
|
+
originalFetch;
|
|
3654
|
+
originalXHROpen;
|
|
3655
|
+
originalXHRSend;
|
|
3656
|
+
constructor(e) {
|
|
3657
|
+
super(), this.eventManager = e, this.originalFetch = window.fetch, this.originalXHROpen = XMLHttpRequest.prototype.open, this.originalXHRSend = XMLHttpRequest.prototype.send;
|
|
3658
|
+
}
|
|
3659
|
+
startTracking() {
|
|
3660
|
+
i.debug("NetworkHandler", "Starting network error tracking"), this.interceptFetch(), this.interceptXHR();
|
|
3661
|
+
}
|
|
3662
|
+
stopTracking() {
|
|
3663
|
+
i.debug("NetworkHandler", "Stopping network error tracking"), window.fetch = this.originalFetch, XMLHttpRequest.prototype.open = this.originalXHROpen, XMLHttpRequest.prototype.send = this.originalXHRSend;
|
|
3664
|
+
}
|
|
3665
|
+
interceptFetch() {
|
|
3666
|
+
window.fetch = async (e, t) => {
|
|
3667
|
+
const s = Date.now(), r = typeof e == "string" ? e : e.toString(), a = t?.method ?? "GET";
|
|
3668
|
+
try {
|
|
3669
|
+
const o = await this.originalFetch(e, t), l = Date.now() - s;
|
|
3670
|
+
return o.ok || (i.debug("NetworkHandler", "Fetch error detected", {
|
|
3671
|
+
method: a,
|
|
3672
|
+
url: this.normalizeUrlForTracking(r),
|
|
3673
|
+
status: o.status,
|
|
3674
|
+
statusText: o.statusText
|
|
3675
|
+
}), this.trackNetworkError(
|
|
3676
|
+
a.toUpperCase(),
|
|
3677
|
+
this.normalizeUrlForTracking(r),
|
|
3678
|
+
o.status,
|
|
3679
|
+
o.statusText,
|
|
3680
|
+
l
|
|
3681
|
+
)), o;
|
|
3682
|
+
} catch (o) {
|
|
3683
|
+
const l = Date.now() - s, c = o instanceof Error ? o.message : "Network Error";
|
|
3684
|
+
throw i.debug("NetworkHandler", "Fetch exception caught", {
|
|
3685
|
+
method: a,
|
|
3686
|
+
url: this.normalizeUrlForTracking(r),
|
|
3687
|
+
error: c
|
|
3688
|
+
}), this.trackNetworkError(
|
|
3689
|
+
a.toUpperCase(),
|
|
3690
|
+
this.normalizeUrlForTracking(r),
|
|
3691
|
+
void 0,
|
|
3692
|
+
c,
|
|
3693
|
+
l
|
|
3694
|
+
), o;
|
|
3695
|
+
}
|
|
3696
|
+
};
|
|
3697
|
+
}
|
|
3698
|
+
interceptXHR() {
|
|
3699
|
+
const e = this.trackNetworkError.bind(this), t = this.normalizeUrlForTracking.bind(this), s = this.originalXHROpen, r = this.originalXHRSend;
|
|
3700
|
+
XMLHttpRequest.prototype.open = function(a, o, l, c, d) {
|
|
3701
|
+
const u = l ?? !0, g = this;
|
|
3702
|
+
return g._tracelogStartTime = Date.now(), g._tracelogMethod = a.toUpperCase(), g._tracelogUrl = o.toString(), s.call(this, a, o, u, c, d);
|
|
3703
|
+
}, XMLHttpRequest.prototype.send = function(a) {
|
|
3704
|
+
const o = this, l = o._tracelogStartTime ?? Date.now(), c = o._tracelogMethod ?? "GET", d = o._tracelogUrl ?? "", u = o.onreadystatechange;
|
|
3705
|
+
return o.onreadystatechange = (g) => {
|
|
3706
|
+
if (o.readyState === XMLHttpRequest.DONE) {
|
|
3707
|
+
const y = Date.now() - l;
|
|
3708
|
+
if (o.status === 0 || o.status >= 400) {
|
|
3709
|
+
const I = o.statusText || "Request Failed";
|
|
3710
|
+
i.debug("NetworkHandler", "XHR error detected", {
|
|
3711
|
+
method: c,
|
|
3712
|
+
url: t(d),
|
|
3713
|
+
status: o.status,
|
|
3714
|
+
statusText: I
|
|
3715
|
+
}), e(c, t(d), o.status, I, y);
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
if (u)
|
|
3719
|
+
return u.call(o, g);
|
|
3720
|
+
}, r.call(this, a);
|
|
3721
|
+
};
|
|
3722
|
+
}
|
|
3723
|
+
trackNetworkError(e, t, s, r, a) {
|
|
3724
|
+
const o = this.get("config");
|
|
3725
|
+
if (!this.shouldSample(o?.errorSampling ?? 0.1)) {
|
|
3726
|
+
i.debug(
|
|
3727
|
+
"NetworkHandler",
|
|
3728
|
+
`Network error not sampled, skipping (errorSampling: ${o?.errorSampling}, method: ${e}, url: ${t})`,
|
|
3729
|
+
{
|
|
3730
|
+
errorSampling: o?.errorSampling,
|
|
3731
|
+
method: e,
|
|
3732
|
+
url: t
|
|
3733
|
+
}
|
|
3734
|
+
);
|
|
3735
|
+
return;
|
|
3736
|
+
}
|
|
3737
|
+
i.warn(
|
|
3738
|
+
"NetworkHandler",
|
|
3739
|
+
`Network error tracked: ${e} ${t} (status: ${s}, statusText: ${r}, duration: ${a}ms)`,
|
|
3740
|
+
{ method: e, url: t, status: s, statusText: r, duration: a }
|
|
3741
|
+
), this.eventManager.track({
|
|
3742
|
+
type: h.ERROR,
|
|
3743
|
+
error_data: {
|
|
3744
|
+
type: D.NETWORK_ERROR,
|
|
3745
|
+
message: r,
|
|
3746
|
+
method: e,
|
|
3747
|
+
url: t,
|
|
3748
|
+
status: s,
|
|
3749
|
+
statusText: r,
|
|
3750
|
+
duration: a
|
|
3751
|
+
}
|
|
3752
|
+
});
|
|
3753
|
+
}
|
|
3754
|
+
normalizeUrlForTracking(e) {
|
|
3755
|
+
try {
|
|
3756
|
+
const t = this.get("config");
|
|
3757
|
+
return se(e, t?.sensitiveQueryParams);
|
|
3758
|
+
} catch {
|
|
3759
|
+
return e;
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
shouldSample(e) {
|
|
3763
|
+
return Math.random() < e;
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
class Qt extends p {
|
|
3767
|
+
isInitialized = !1;
|
|
3768
|
+
googleAnalytics = null;
|
|
3769
|
+
storageManager;
|
|
3770
|
+
eventManager;
|
|
3771
|
+
sessionHandler;
|
|
3772
|
+
pageViewHandler;
|
|
3773
|
+
clickHandler;
|
|
3774
|
+
scrollHandler;
|
|
3775
|
+
performanceHandler;
|
|
3776
|
+
errorHandler;
|
|
3777
|
+
networkHandler;
|
|
3778
|
+
suppressNextScrollTimer = null;
|
|
3779
|
+
/**
|
|
3780
|
+
* Returns the initialization status of the app
|
|
3781
|
+
* @returns true if the app is fully initialized, false otherwise
|
|
3782
|
+
*/
|
|
3783
|
+
get initialized() {
|
|
3784
|
+
return this.isInitialized;
|
|
3785
|
+
}
|
|
3786
|
+
async init(e) {
|
|
3787
|
+
if (this.isInitialized) {
|
|
3788
|
+
i.debug("App", "App already initialized, skipping re-initialization", { projectId: e.id });
|
|
3789
|
+
return;
|
|
3790
|
+
}
|
|
3791
|
+
i.info("App", "App initialization started", { projectId: e.id }), this.validateAppReadiness(e);
|
|
3792
|
+
try {
|
|
3793
|
+
this.initStorage(), await this.setState(e), await this.setIntegrations(), this.setEventManager(), await this.initHandlers(), this.isInitialized = !0, i.info("App", "App initialization completed successfully", {
|
|
3794
|
+
projectId: e.id
|
|
3795
|
+
});
|
|
3796
|
+
} catch (t) {
|
|
3797
|
+
throw this.isInitialized = !1, i.error("App", "App initialization failed", { projectId: e.id, error: t }), t;
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
/**
|
|
3801
|
+
* Validates that the app is ready to initialize with the provided config
|
|
3802
|
+
* This is a lightweight runtime validation layer that ensures the app receives proper config
|
|
3803
|
+
* @param appConfig - The validated and normalized configuration
|
|
3804
|
+
* @throws {ProjectIdValidationError} If project ID is invalid at runtime
|
|
3805
|
+
*/
|
|
3806
|
+
validateAppReadiness(e) {
|
|
3807
|
+
if (!e?.id)
|
|
3808
|
+
throw i.clientError("App", "Configuration integrity check failed - missing project ID", {
|
|
3809
|
+
hasConfig: !!e,
|
|
3810
|
+
hasId: !!e?.id
|
|
3811
|
+
}), new Q("Configuration integrity check failed", "app");
|
|
3812
|
+
}
|
|
3813
|
+
sendCustomEvent(e, t) {
|
|
3814
|
+
if (!this.eventManager) {
|
|
3815
|
+
i.warn("App", "Custom event attempted before eventManager initialization", { eventName: e });
|
|
3816
|
+
return;
|
|
3817
|
+
}
|
|
3818
|
+
const { valid: s, error: r, sanitizedMetadata: a } = St(e, t);
|
|
3819
|
+
if (s)
|
|
3820
|
+
i.debug("App", "Custom event validated and queued", { eventName: e, hasMetadata: !!a }), this.eventManager.track({
|
|
3821
|
+
type: h.CUSTOM,
|
|
3822
|
+
custom_event: {
|
|
3823
|
+
name: e,
|
|
3824
|
+
...a && { metadata: a }
|
|
3825
|
+
}
|
|
3826
|
+
});
|
|
3827
|
+
else {
|
|
3828
|
+
const o = this.get("config")?.mode;
|
|
3829
|
+
if (i.clientError("App", `Custom event validation failed: ${r ?? "unknown error"}`, {
|
|
3830
|
+
eventName: e,
|
|
3831
|
+
validationError: r,
|
|
3832
|
+
hasMetadata: !!t,
|
|
3833
|
+
mode: o
|
|
3834
|
+
}), o === "qa" || o === "debug")
|
|
3835
|
+
throw new Error(
|
|
3836
|
+
`custom event "${e}" validation failed (${r ?? "unknown error"}). Please, review your event data and try again.`
|
|
3837
|
+
);
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
destroy() {
|
|
3841
|
+
if (!this.isInitialized) {
|
|
3842
|
+
i.warn("App", "Destroy called but app was not initialized");
|
|
3843
|
+
return;
|
|
3844
|
+
}
|
|
3845
|
+
i.info("App", "App cleanup started"), this.googleAnalytics && this.googleAnalytics.cleanup(), this.sessionHandler && this.sessionHandler.stopTracking(), this.pageViewHandler && this.pageViewHandler.stopTracking(), this.clickHandler && this.clickHandler.stopTracking(), this.scrollHandler && this.scrollHandler.stopTracking(), this.performanceHandler && this.performanceHandler.stopTracking(), this.errorHandler && this.errorHandler.stopTracking(), this.networkHandler && this.networkHandler.stopTracking(), this.suppressNextScrollTimer && (clearTimeout(this.suppressNextScrollTimer), this.suppressNextScrollTimer = null), this.eventManager && this.eventManager.stop(), this.set("hasStartSession", !1), this.set("suppressNextScroll", !1), this.set("sessionId", null), this.isInitialized = !1, i.info("App", "App cleanup completed successfully");
|
|
3846
|
+
}
|
|
3847
|
+
async setState(e) {
|
|
3848
|
+
this.setApiUrl(e.id, e.allowHttp), await this.setConfig(e), this.setUserId(), this.setDevice(), this.setPageUrl();
|
|
3849
|
+
}
|
|
3850
|
+
setApiUrl(e, t = !1) {
|
|
3851
|
+
const s = new Et();
|
|
3852
|
+
this.set("apiUrl", s.getUrl(e, t));
|
|
3853
|
+
}
|
|
3854
|
+
async setConfig(e) {
|
|
3855
|
+
const s = await new wt().get(this.get("apiUrl"), e);
|
|
3856
|
+
this.set("config", s);
|
|
3857
|
+
}
|
|
3858
|
+
setUserId() {
|
|
3859
|
+
const t = new _t(this.storageManager).getId();
|
|
3860
|
+
this.set("userId", t);
|
|
3861
|
+
}
|
|
3862
|
+
setDevice() {
|
|
3863
|
+
const e = we();
|
|
3864
|
+
this.set("device", e);
|
|
3865
|
+
}
|
|
3866
|
+
setPageUrl() {
|
|
3867
|
+
const e = se(window.location.href, this.get("config").sensitiveQueryParams);
|
|
3868
|
+
this.set("pageUrl", e);
|
|
3869
|
+
}
|
|
3870
|
+
async setIntegrations() {
|
|
3871
|
+
const e = this.get("config").ipExcluded, t = this.get("config").integrations?.googleAnalytics?.measurementId;
|
|
3872
|
+
!e && t?.trim() && (this.googleAnalytics = new zt(), await this.googleAnalytics.initialize());
|
|
3873
|
+
}
|
|
3874
|
+
async initHandlers() {
|
|
3875
|
+
if (!this.eventManager)
|
|
3876
|
+
throw new Error("EventManager must be initialized before handlers");
|
|
3877
|
+
if (!this.storageManager)
|
|
3878
|
+
throw new Error("StorageManager must be initialized before handlers");
|
|
3879
|
+
this.initSessionHandler(), this.initPageViewHandler(), this.initClickHandler(), this.initScrollHandler(), await this.initPerformanceHandler(), this.initErrorHandler(), this.initNetworkHandler();
|
|
3880
|
+
}
|
|
3881
|
+
initStorage() {
|
|
3882
|
+
this.storageManager = new Vt();
|
|
3883
|
+
}
|
|
3884
|
+
setEventManager() {
|
|
3885
|
+
if (!this.storageManager)
|
|
3886
|
+
throw new Error("StorageManager must be initialized before EventManager");
|
|
3887
|
+
this.eventManager = new At(this.storageManager, this.googleAnalytics);
|
|
3888
|
+
}
|
|
3889
|
+
initSessionHandler() {
|
|
3890
|
+
if (!this.storageManager || !this.eventManager)
|
|
3891
|
+
throw new Error("StorageManager and EventManager must be initialized before SessionHandler");
|
|
3892
|
+
this.sessionHandler = new Dt(this.storageManager, this.eventManager), this.sessionHandler.startTracking();
|
|
3893
|
+
}
|
|
3894
|
+
initPageViewHandler() {
|
|
3895
|
+
if (!this.eventManager)
|
|
3896
|
+
throw new Error("EventManager must be initialized before PageViewHandler");
|
|
3897
|
+
const e = () => this.onPageViewTrack();
|
|
3898
|
+
this.pageViewHandler = new xt(this.eventManager, e), this.pageViewHandler.startTracking();
|
|
3899
|
+
}
|
|
3900
|
+
onPageViewTrack() {
|
|
3901
|
+
this.set("suppressNextScroll", !0), this.suppressNextScrollTimer && (clearTimeout(this.suppressNextScrollTimer), this.suppressNextScrollTimer = null), this.suppressNextScrollTimer = window.setTimeout(() => {
|
|
3902
|
+
this.set("suppressNextScroll", !1);
|
|
3903
|
+
}, Me * et.SUPPRESS_MULTIPLIER);
|
|
3904
|
+
}
|
|
3905
|
+
initClickHandler() {
|
|
3906
|
+
if (!this.eventManager)
|
|
3907
|
+
throw new Error("EventManager must be initialized before ClickHandler");
|
|
3908
|
+
this.clickHandler = new Ot(this.eventManager), this.clickHandler.startTracking();
|
|
3909
|
+
}
|
|
3910
|
+
initScrollHandler() {
|
|
3911
|
+
if (!this.eventManager)
|
|
3912
|
+
throw new Error("EventManager must be initialized before ScrollHandler");
|
|
3913
|
+
this.scrollHandler = new Ft(this.eventManager), this.scrollHandler.startTracking();
|
|
3914
|
+
}
|
|
3915
|
+
async initPerformanceHandler() {
|
|
3916
|
+
if (!this.eventManager)
|
|
3917
|
+
throw new Error("EventManager must be initialized before PerformanceHandler");
|
|
3918
|
+
this.performanceHandler = new $t(this.eventManager), await this.performanceHandler.startTracking();
|
|
3919
|
+
}
|
|
3920
|
+
initErrorHandler() {
|
|
3921
|
+
if (!this.eventManager)
|
|
3922
|
+
throw new Error("EventManager must be initialized before ErrorHandler");
|
|
3923
|
+
this.errorHandler = new jt(this.eventManager), this.errorHandler.startTracking();
|
|
3924
|
+
}
|
|
3925
|
+
initNetworkHandler() {
|
|
3926
|
+
if (!this.eventManager)
|
|
3927
|
+
throw new Error("EventManager must be initialized before NetworkHandler");
|
|
3928
|
+
this.networkHandler = new Gt(this.eventManager), this.networkHandler.startTracking();
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
const Bt = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
3932
|
+
__proto__: null,
|
|
3933
|
+
DeviceType: E,
|
|
3934
|
+
ErrorType: D,
|
|
3935
|
+
EventType: h,
|
|
3936
|
+
ScrollDirection: j,
|
|
3937
|
+
TagConditionOperator: f,
|
|
3938
|
+
TagConditionType: m,
|
|
3939
|
+
TagLogicalOperator: G
|
|
3940
|
+
}, Symbol.toStringTag, { value: "Module" })), qt = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
3941
|
+
__proto__: null,
|
|
3942
|
+
DEFAULT_SESSION_TIMEOUT_MS: L
|
|
3943
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
3944
|
+
let v = null, T = !1;
|
|
3945
|
+
const Wt = async (n) => {
|
|
3946
|
+
try {
|
|
3947
|
+
if (i.info("API", "Library initialization started", { id: n.id }), typeof window > "u" || typeof document > "u")
|
|
3948
|
+
throw i.clientError(
|
|
3949
|
+
"API",
|
|
3950
|
+
"Browser environment required - this library can only be used in a browser environment",
|
|
3951
|
+
{
|
|
3952
|
+
hasWindow: typeof window < "u",
|
|
3953
|
+
hasDocument: typeof document < "u"
|
|
3954
|
+
}
|
|
3955
|
+
), new Error("This library can only be used in a browser environment");
|
|
3956
|
+
if (v) {
|
|
3957
|
+
i.debug("API", "Library already initialized, skipping duplicate initialization", {
|
|
3958
|
+
projectId: n.id
|
|
3959
|
+
});
|
|
3960
|
+
return;
|
|
3961
|
+
}
|
|
3962
|
+
if (T) {
|
|
3963
|
+
i.debug("API", "Concurrent initialization detected, waiting for completion", { projectId: n.id });
|
|
3964
|
+
let s = 0;
|
|
3965
|
+
const r = me.MAX_CONCURRENT_RETRIES, a = me.CONCURRENT_RETRY_DELAY_MS;
|
|
3966
|
+
for (; T && s < r; )
|
|
3967
|
+
await new Promise((o) => setTimeout(o, a)), s++;
|
|
3968
|
+
if (v) {
|
|
3969
|
+
i.debug("API", "Concurrent initialization completed successfully", {
|
|
3970
|
+
projectId: n.id,
|
|
3971
|
+
retriesUsed: s
|
|
3972
|
+
});
|
|
3973
|
+
return;
|
|
3974
|
+
}
|
|
3975
|
+
if (T)
|
|
3976
|
+
throw i.error("API", "Initialization timeout - concurrent initialization took too long", {
|
|
3977
|
+
projectId: n.id,
|
|
3978
|
+
retriesUsed: s,
|
|
3979
|
+
maxRetries: r
|
|
3980
|
+
}), new Error("App initialization timeout - concurrent initialization took too long");
|
|
3981
|
+
}
|
|
3982
|
+
T = !0, i.debug("API", "Validating and normalizing configuration", { projectId: n.id });
|
|
3983
|
+
const e = ht(n);
|
|
3984
|
+
i.debug("API", "Creating App instance", { projectId: e.id });
|
|
3985
|
+
const t = new Qt();
|
|
3986
|
+
await t.init(e), v = t, i.info("API", "Library initialization completed successfully", {
|
|
3987
|
+
projectId: e.id
|
|
3988
|
+
});
|
|
3989
|
+
} catch (e) {
|
|
3990
|
+
if (v && !v.initialized)
|
|
3991
|
+
try {
|
|
3992
|
+
v.destroy();
|
|
3993
|
+
} catch (t) {
|
|
3994
|
+
i.warn("API", "Failed to cleanup partially initialized app", { cleanupError: t });
|
|
3995
|
+
}
|
|
3996
|
+
throw v = null, i.error("API", "Initialization failed", { error: e }), e;
|
|
3997
|
+
} finally {
|
|
3998
|
+
T = !1;
|
|
3999
|
+
}
|
|
4000
|
+
}, Xt = (n, e) => {
|
|
4001
|
+
try {
|
|
4002
|
+
if (!v)
|
|
4003
|
+
throw i.clientError("API", "Custom event failed - Library not initialized. Please call TraceLog.init() first", {
|
|
4004
|
+
eventName: n,
|
|
4005
|
+
hasMetadata: !!e
|
|
4006
|
+
}), new Error("App not initialized");
|
|
4007
|
+
i.debug("API", "Sending custom event", {
|
|
4008
|
+
eventName: n,
|
|
4009
|
+
hasMetadata: !!e,
|
|
4010
|
+
metadataKeys: e ? Object.keys(e) : []
|
|
4011
|
+
}), v.sendCustomEvent(n, e);
|
|
4012
|
+
} catch (t) {
|
|
4013
|
+
if (i.error("API", "Event tracking failed", { eventName: n, error: t, hasMetadata: !!e }), t instanceof Error && (t.message === "App not initialized" || t.message.includes("validation failed")))
|
|
4014
|
+
throw t;
|
|
4015
|
+
}
|
|
4016
|
+
}, Kt = () => v !== null, Yt = () => ({
|
|
4017
|
+
isInitialized: v !== null,
|
|
4018
|
+
isInitializing: T,
|
|
4019
|
+
hasInstance: v !== null
|
|
4020
|
+
}), Jt = () => {
|
|
4021
|
+
try {
|
|
4022
|
+
if (i.info("API", "Library cleanup initiated"), !v)
|
|
4023
|
+
throw i.warn("API", "Cleanup called but Library was not initialized"), new Error("App not initialized");
|
|
4024
|
+
v.destroy(), v = null, T = !1, i.info("API", "Library cleanup completed successfully");
|
|
4025
|
+
} catch (n) {
|
|
4026
|
+
i.error("API", "Cleanup failed", { error: n, hadApp: !!v, wasInitializing: T });
|
|
4027
|
+
}
|
|
4028
|
+
}, Zt = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
4029
|
+
__proto__: null,
|
|
4030
|
+
Constants: qt,
|
|
4031
|
+
Types: Bt,
|
|
4032
|
+
destroy: Jt,
|
|
4033
|
+
event: Xt,
|
|
4034
|
+
getInitializationStatus: Yt,
|
|
4035
|
+
init: Wt,
|
|
4036
|
+
isInitialized: Kt
|
|
4037
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
4038
|
+
export {
|
|
4039
|
+
Zt as TraceLog
|
|
4040
|
+
};
|