@dotcms/analytics 1.2.3-next.3 → 1.2.3-next.4
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/lib/core/shared/constants/dot-analytics.constants.d.ts +19 -0
- package/lib/core/shared/constants/dot-analytics.constants.js +18 -15
- package/lib/core/shared/http/dot-analytics.http.d.ts +1 -1
- package/lib/core/shared/http/dot-analytics.http.js +19 -17
- package/lib/core/shared/models/event.model.d.ts +12 -0
- package/lib/core/shared/queue/dot-analytics.queue.utils.d.ts +4 -0
- package/lib/core/shared/queue/dot-analytics.queue.utils.js +122 -41
- package/lib/core/shared/utils/dot-analytics.utils.d.ts +1 -0
- package/lib/core/shared/utils/dot-analytics.utils.js +113 -76
- package/package.json +1 -1
|
@@ -156,3 +156,22 @@ export declare const ANALYTICS_WINDOWS_CLEANUP_KEY = "__dotAnalyticsCleanup__";
|
|
|
156
156
|
* @see core-web/libs/sdk/react/src/lib/next/components/Contentlet/Contentlet.tsx
|
|
157
157
|
*/
|
|
158
158
|
export declare const CONTENTLET_CLASS = "dotcms-contentlet";
|
|
159
|
+
/**
|
|
160
|
+
* Queue persistence configuration constants
|
|
161
|
+
* Used for storing events in sessionStorage for traditional page navigations
|
|
162
|
+
*/
|
|
163
|
+
/**
|
|
164
|
+
* Session storage key for persistent tab ID
|
|
165
|
+
* This ID remains constant across page navigations within the same browser tab
|
|
166
|
+
*/
|
|
167
|
+
export declare const TAB_ID_STORAGE_KEY = "dot_analytics_tab_id";
|
|
168
|
+
/**
|
|
169
|
+
* Prefix for queue storage key in sessionStorage
|
|
170
|
+
* Full key format: dot_analytics_queue_{tabId}
|
|
171
|
+
*/
|
|
172
|
+
export declare const QUEUE_STORAGE_KEY_PREFIX = "dot_analytics_queue";
|
|
173
|
+
/**
|
|
174
|
+
* Maximum age in milliseconds for persisted events
|
|
175
|
+
* Events older than this will be discarded (24 hours)
|
|
176
|
+
*/
|
|
177
|
+
export declare const MAX_EVENT_AGE_MS: number;
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
const
|
|
1
|
+
const c = "/api/v1/analytics/content/event", o = {
|
|
2
2
|
PAGEVIEW: "pageview",
|
|
3
3
|
CONTENT_IMPRESSION: "content_impression",
|
|
4
4
|
CONTENT_CLICK: "content_click",
|
|
5
5
|
CONVERSION: "conversion"
|
|
6
|
-
},
|
|
6
|
+
}, s = [
|
|
7
7
|
"utm_source",
|
|
8
8
|
"utm_medium",
|
|
9
9
|
"utm_campaign",
|
|
10
10
|
"utm_term",
|
|
11
11
|
"utm_content"
|
|
12
|
-
],
|
|
12
|
+
], I = 30, e = "dot_analytics_session_id", N = "dot_analytics_user_id", _ = 15, E = 5e3, A = ["click"], O = {
|
|
13
13
|
eventBatchSize: _,
|
|
14
14
|
// Max events per batch - auto-sends when reached
|
|
15
15
|
flushInterval: E
|
|
16
16
|
// Time between flushes - sends whatever is queued
|
|
17
|
-
},
|
|
17
|
+
}, L = [
|
|
18
18
|
"title",
|
|
19
19
|
"url",
|
|
20
20
|
"path",
|
|
@@ -28,28 +28,31 @@ const I = "/api/v1/analytics/content/event", c = {
|
|
|
28
28
|
dwellMs: n,
|
|
29
29
|
maxNodes: T,
|
|
30
30
|
throttleMs: S
|
|
31
|
-
}, U = "content_impression",
|
|
31
|
+
}, U = "content_impression", a = "content_click", D = 300, M = "a, button", R = "__dotAnalyticsActive__", l = "__dotAnalyticsCleanup__", F = "dotcms-contentlet", P = "dot_analytics_tab_id", d = "dot_analytics_queue", r = 1440 * 60 * 1e3;
|
|
32
32
|
export {
|
|
33
33
|
A as ACTIVITY_EVENTS,
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
c as ANALYTICS_ENDPOINT,
|
|
35
|
+
L as ANALYTICS_JS_DEFAULT_PROPERTIES,
|
|
36
36
|
R as ANALYTICS_WINDOWS_ACTIVE_KEY,
|
|
37
37
|
l as ANALYTICS_WINDOWS_CLEANUP_KEY,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
M as CLICKABLE_ELEMENTS_SELECTOR,
|
|
39
|
+
a as CLICK_EVENT_TYPE,
|
|
40
|
+
F as CONTENTLET_CLASS,
|
|
41
|
+
D as DEFAULT_CLICK_THROTTLE_MS,
|
|
42
42
|
C as DEFAULT_IMPRESSION_CONFIG,
|
|
43
43
|
n as DEFAULT_IMPRESSION_DWELL_MS,
|
|
44
44
|
T as DEFAULT_IMPRESSION_MAX_NODES,
|
|
45
45
|
i as DEFAULT_IMPRESSION_MUTATION_OBSERVER_DEBOUNCE_MS,
|
|
46
46
|
S as DEFAULT_IMPRESSION_THROTTLE_MS,
|
|
47
47
|
t as DEFAULT_IMPRESSION_VISIBILITY_THRESHOLD,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
O as DEFAULT_QUEUE_CONFIG,
|
|
49
|
+
I as DEFAULT_SESSION_TIMEOUT_MINUTES,
|
|
50
|
+
o as DotCMSPredefinedEventType,
|
|
51
|
+
s as EXPECTED_UTM_KEYS,
|
|
52
52
|
U as IMPRESSION_EVENT_TYPE,
|
|
53
|
+
r as MAX_EVENT_AGE_MS,
|
|
54
|
+
d as QUEUE_STORAGE_KEY_PREFIX,
|
|
53
55
|
e as SESSION_STORAGE_KEY,
|
|
56
|
+
P as TAB_ID_STORAGE_KEY,
|
|
54
57
|
N as USER_ID_KEY
|
|
55
58
|
};
|
|
@@ -6,4 +6,4 @@ import { DotCMSAnalyticsConfig, DotCMSAnalyticsRequestBody } from '../models';
|
|
|
6
6
|
* @param keepalive - Use keepalive mode for page unload scenarios (default: false)
|
|
7
7
|
* @returns A promise that resolves when the request is complete
|
|
8
8
|
*/
|
|
9
|
-
export declare const sendAnalyticsEvent: (payload: DotCMSAnalyticsRequestBody, config: DotCMSAnalyticsConfig, keepalive?: boolean) => Promise<
|
|
9
|
+
export declare const sendAnalyticsEvent: (payload: DotCMSAnalyticsRequestBody, config: DotCMSAnalyticsConfig, keepalive?: boolean) => Promise<boolean>;
|
|
@@ -1,34 +1,36 @@
|
|
|
1
|
-
import { ANALYTICS_ENDPOINT as
|
|
2
|
-
import { createPluginLogger as
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
1
|
+
import { ANALYTICS_ENDPOINT as p } from "../constants/dot-analytics.constants.js";
|
|
2
|
+
import { createPluginLogger as l } from "../utils/dot-analytics.utils.js";
|
|
3
|
+
const m = async (n, i, c = !1) => {
|
|
4
|
+
const e = l("HTTP", i), f = `${i.server}${p}`, g = JSON.stringify(n);
|
|
5
|
+
e.info(`Sending ${n.events.length} event(s)${c ? " (keepalive)" : ""}`, {
|
|
6
6
|
payload: n
|
|
7
7
|
});
|
|
8
8
|
try {
|
|
9
|
-
const
|
|
9
|
+
const r = {
|
|
10
10
|
method: "POST",
|
|
11
11
|
headers: { "Content-Type": "application/json" },
|
|
12
12
|
body: g
|
|
13
13
|
};
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const t = await fetch(
|
|
14
|
+
if (c)
|
|
15
|
+
return r.keepalive = !0, r.credentials = "omit", fetch(f, r).catch((o) => {
|
|
16
|
+
e.error("Keepalive request failed (browser may have ignored it):", o);
|
|
17
|
+
}), !0;
|
|
18
|
+
const t = await fetch(f, r);
|
|
19
19
|
if (!t.ok) {
|
|
20
|
-
const
|
|
20
|
+
const o = t.statusText || "Unknown Error", a = `HTTP ${t.status}: ${o}`;
|
|
21
21
|
try {
|
|
22
22
|
const s = await t.json();
|
|
23
|
-
s.message ?
|
|
23
|
+
s.message ? e.warn(`${s.message} (${a})`) : e.warn(`${a} - No error message in response`);
|
|
24
24
|
} catch (s) {
|
|
25
|
-
|
|
25
|
+
e.warn(`${a} - Failed to parse error response:`, s);
|
|
26
26
|
}
|
|
27
|
+
return !1;
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
return !0;
|
|
30
|
+
} catch (r) {
|
|
31
|
+
return e.error("Error sending event:", r), !1;
|
|
30
32
|
}
|
|
31
33
|
};
|
|
32
34
|
export {
|
|
33
|
-
|
|
35
|
+
m as sendAnalyticsEvent
|
|
34
36
|
};
|
|
@@ -167,3 +167,15 @@ export type DotCMSCustomEvent = DotCMSEventBase<DotCMSCustomEventType, DotCMSCus
|
|
|
167
167
|
* Used primarily for type documentation and validation.
|
|
168
168
|
*/
|
|
169
169
|
export type DotCMSEvent = DotCMSPageViewEvent | DotCMSContentImpressionEvent | DotCMSContentClickEvent | DotCMSConversionEvent | DotCMSCustomEvent;
|
|
170
|
+
/**
|
|
171
|
+
* Structure for persisted queue in sessionStorage.
|
|
172
|
+
* Used to preserve events across traditional page navigations.
|
|
173
|
+
*/
|
|
174
|
+
export interface PersistedQueue {
|
|
175
|
+
/** Unique identifier for this browser tab */
|
|
176
|
+
tabId: string;
|
|
177
|
+
/** Timestamp when the queue was last persisted */
|
|
178
|
+
timestamp: number;
|
|
179
|
+
/** Array of events waiting to be sent */
|
|
180
|
+
events: DotCMSEvent[];
|
|
181
|
+
}
|
|
@@ -23,6 +23,10 @@ export declare const createAnalyticsQueue: (config: DotCMSAnalyticsConfig) => {
|
|
|
23
23
|
/**
|
|
24
24
|
* Clean up queue resources
|
|
25
25
|
* Flushes remaining events and cleans up listeners
|
|
26
|
+
*
|
|
27
|
+
* IMPORTANT: Does NOT clear sessionStorage
|
|
28
|
+
* - Storage is cleared only after sendBatch succeeds or in initialize()
|
|
29
|
+
* - This allows events to persist across traditional page navigations
|
|
26
30
|
*/
|
|
27
31
|
cleanup: () => void;
|
|
28
32
|
};
|
|
@@ -1,49 +1,125 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import { DEFAULT_QUEUE_CONFIG as
|
|
4
|
-
import { sendAnalyticsEvent as
|
|
5
|
-
import { createPluginLogger as
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
let
|
|
9
|
-
const
|
|
10
|
-
...
|
|
11
|
-
...typeof
|
|
12
|
-
}, g = (e
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import q from "@analytics/queue-utils";
|
|
2
|
+
import F from "@analytics/router-utils";
|
|
3
|
+
import { DEFAULT_QUEUE_CONFIG as x, TAB_ID_STORAGE_KEY as I, MAX_EVENT_AGE_MS as z, QUEUE_STORAGE_KEY_PREFIX as P } from "../constants/dot-analytics.constants.js";
|
|
4
|
+
import { sendAnalyticsEvent as w } from "../http/dot-analytics.http.js";
|
|
5
|
+
import { createPluginLogger as T, safeSessionStorage as d, generateSecureId as U, getAnalyticsContext as D } from "../utils/dot-analytics.utils.js";
|
|
6
|
+
const R = (l) => {
|
|
7
|
+
const n = T("Queue", l);
|
|
8
|
+
let i = null, c = null, f = !1, m = !1, v = typeof window < "u" ? window.location.pathname : "", o = "", a = [];
|
|
9
|
+
const y = {
|
|
10
|
+
...x,
|
|
11
|
+
...typeof l.queue == "object" ? l.queue : {}
|
|
12
|
+
}, g = () => `${P}_${o}`, E = (e) => {
|
|
13
|
+
const t = e;
|
|
14
|
+
if (!t || typeof t != "object" || Array.isArray(t))
|
|
15
|
+
return null;
|
|
16
|
+
if (typeof t.tabId != "string" || typeof t.timestamp != "number" || !Number.isFinite(t.timestamp) || !Array.isArray(t.events))
|
|
17
|
+
return n.warn("Invalid persisted queue: structural mismatch"), null;
|
|
18
|
+
const s = t.events.filter(
|
|
19
|
+
(r) => r && typeof r == "object" && "event_type" in r
|
|
20
|
+
);
|
|
21
|
+
return {
|
|
22
|
+
tabId: t.tabId,
|
|
23
|
+
timestamp: t.timestamp,
|
|
24
|
+
events: s
|
|
25
|
+
};
|
|
26
|
+
}, A = () => {
|
|
27
|
+
try {
|
|
28
|
+
const e = g(), t = d.getItem(e);
|
|
29
|
+
if (!t)
|
|
30
|
+
return n.debug("No persisted queue found"), null;
|
|
31
|
+
const s = JSON.parse(t), r = E(s);
|
|
32
|
+
if (!r)
|
|
33
|
+
return d.removeItem(e), null;
|
|
34
|
+
const u = Date.now() - r.timestamp;
|
|
35
|
+
return u > z ? (n.warn(
|
|
36
|
+
`Persisted events too old (${Math.round(u / 1e3 / 60 / 60)}h), discarding`
|
|
37
|
+
), d.removeItem(e), null) : (n.info(
|
|
38
|
+
`Loaded ${r.events.length} persisted event(s) from storage (age: ${Math.round(u / 1e3)}s)`
|
|
39
|
+
), r);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return n.error("Failed to load persisted queue", e), d.removeItem(g()), null;
|
|
42
|
+
}
|
|
43
|
+
}, h = () => {
|
|
44
|
+
if (a.length !== 0)
|
|
45
|
+
try {
|
|
46
|
+
const e = g(), t = {
|
|
47
|
+
tabId: o,
|
|
48
|
+
timestamp: Date.now(),
|
|
49
|
+
events: a
|
|
50
|
+
};
|
|
51
|
+
d.setItem(e, JSON.stringify(t)), n.debug(`Persisted ${a.length} event(s) to storage`);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
e instanceof Error && e.name === "QuotaExceededError" ? n.warn("sessionStorage quota exceeded, continuing without persistence") : n.error("Failed to persist queue", e);
|
|
54
|
+
}
|
|
55
|
+
}, b = () => {
|
|
56
|
+
try {
|
|
57
|
+
const e = g();
|
|
58
|
+
d.removeItem(e), n.debug("Persisted queue cleared from storage");
|
|
59
|
+
} catch (e) {
|
|
60
|
+
n.error("Failed to clear persisted queue", e);
|
|
61
|
+
}
|
|
62
|
+
}, _ = async (e, t = !0) => {
|
|
63
|
+
if (e.length === 0)
|
|
64
|
+
return !0;
|
|
65
|
+
n.info(`Sending ${e.length} persisted event(s) immediately`);
|
|
66
|
+
const r = { context: D(l), events: e };
|
|
67
|
+
return w(r, l, t);
|
|
68
|
+
}, $ = (e, t) => {
|
|
69
|
+
if (!c) return;
|
|
70
|
+
n.debug(`Sending batch of ${e.length} event(s)`, {
|
|
15
71
|
events: e,
|
|
16
|
-
keepalive:
|
|
17
|
-
}),
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
72
|
+
keepalive: f
|
|
73
|
+
}), w({ context: c, events: e }, l, f), a = a.filter(
|
|
74
|
+
(r) => !e.some((u) => u === r)
|
|
75
|
+
), f || (a.length === 0 ? b() : h());
|
|
76
|
+
}, p = () => {
|
|
77
|
+
!i || i.size() === 0 || !c || (n.info(`Flushing ${i.size()} events (page hidden/unload)`), f = !0, i.flush(!0));
|
|
78
|
+
}, S = () => {
|
|
79
|
+
if (n.debug("handleVisibilityChange", document.visibilityState), document.visibilityState === "hidden") {
|
|
80
|
+
if (m) {
|
|
81
|
+
n.debug("Skipping flush (SPA navigation detected), persisting to storage"), h();
|
|
24
82
|
return;
|
|
25
83
|
}
|
|
26
|
-
|
|
27
|
-
} else document.visibilityState === "visible" && (
|
|
84
|
+
p();
|
|
85
|
+
} else document.visibilityState === "visible" && (m = !1);
|
|
28
86
|
};
|
|
29
87
|
return {
|
|
30
88
|
/**
|
|
31
89
|
* Initialize the queue with smart batching
|
|
32
90
|
*/
|
|
33
91
|
initialize: () => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
92
|
+
if (typeof window < "u") {
|
|
93
|
+
const t = d.getItem(I);
|
|
94
|
+
if (t)
|
|
95
|
+
o = t, n.debug(`Reusing Tab ID: ${o}`);
|
|
96
|
+
else {
|
|
97
|
+
typeof crypto < "u" && typeof crypto.randomUUID == "function" ? o = crypto.randomUUID() : (o = U("tab"), n.debug("crypto.randomUUID not available, using fallback generator"));
|
|
98
|
+
try {
|
|
99
|
+
d.setItem(I, o);
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
n.debug(`Generated new Tab ID: ${o}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const e = A();
|
|
106
|
+
e && e.events.length > 0 && _(e.events, !1).then((t) => {
|
|
107
|
+
t ? b() : n.warn(
|
|
108
|
+
"Failed to send persisted events, keeping in storage for next retry"
|
|
109
|
+
);
|
|
110
|
+
}), i = q(
|
|
111
|
+
(t, s) => {
|
|
112
|
+
$(t);
|
|
37
113
|
},
|
|
38
114
|
{
|
|
39
|
-
max:
|
|
40
|
-
interval:
|
|
115
|
+
max: y.eventBatchSize,
|
|
116
|
+
interval: y.flushInterval,
|
|
41
117
|
throttle: !1
|
|
42
118
|
// Always false - enables both batch size and interval triggers
|
|
43
119
|
}
|
|
44
|
-
), typeof window < "u" && typeof document < "u" && (document.addEventListener("visibilitychange",
|
|
45
|
-
|
|
46
|
-
|
|
120
|
+
), typeof window < "u" && typeof document < "u" && (document.addEventListener("visibilitychange", S), window.addEventListener("pagehide", p), F((t) => {
|
|
121
|
+
m = !0, v = t, n.debug(`SPA navigation detected (${v})`), setTimeout(() => {
|
|
122
|
+
m = !1;
|
|
47
123
|
}, 100);
|
|
48
124
|
}));
|
|
49
125
|
},
|
|
@@ -53,28 +129,33 @@ const L = (u) => {
|
|
|
53
129
|
* - Sends immediately when eventBatchSize reached (with throttle: false)
|
|
54
130
|
* - Sends pending events every flushInterval
|
|
55
131
|
*/
|
|
56
|
-
enqueue: (e,
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
i.
|
|
60
|
-
|
|
132
|
+
enqueue: (e, t) => {
|
|
133
|
+
if (c = t, !i) return;
|
|
134
|
+
a.push(e);
|
|
135
|
+
const s = i.size() + 1, r = y.eventBatchSize, u = s >= r;
|
|
136
|
+
n.debug(
|
|
137
|
+
`Event added. Queue size: ${s}/${r}${u ? " (full, sending...)" : ""}`,
|
|
61
138
|
{ eventType: e.event_type, event: e }
|
|
62
|
-
),
|
|
139
|
+
), i.push(e), h();
|
|
63
140
|
},
|
|
64
141
|
/**
|
|
65
142
|
* Get queue size for debugging
|
|
66
143
|
* Returns the number of events in smartQueue
|
|
67
144
|
*/
|
|
68
|
-
size: () =>
|
|
145
|
+
size: () => i?.size() ?? 0,
|
|
69
146
|
/**
|
|
70
147
|
* Clean up queue resources
|
|
71
148
|
* Flushes remaining events and cleans up listeners
|
|
149
|
+
*
|
|
150
|
+
* IMPORTANT: Does NOT clear sessionStorage
|
|
151
|
+
* - Storage is cleared only after sendBatch succeeds or in initialize()
|
|
152
|
+
* - This allows events to persist across traditional page navigations
|
|
72
153
|
*/
|
|
73
154
|
cleanup: () => {
|
|
74
|
-
|
|
155
|
+
p(), typeof window < "u" && typeof document < "u" && (document.removeEventListener("visibilitychange", S), window.removeEventListener("pagehide", p)), i = null, c = null, f = !1, a = [];
|
|
75
156
|
}
|
|
76
157
|
};
|
|
77
158
|
};
|
|
78
159
|
export {
|
|
79
|
-
|
|
160
|
+
R as createAnalyticsQueue
|
|
80
161
|
};
|
|
@@ -50,6 +50,7 @@ export declare const generateSecureId: (prefix: string) => string;
|
|
|
50
50
|
export declare const safeSessionStorage: {
|
|
51
51
|
getItem: (key: string) => string | null;
|
|
52
52
|
setItem: (key: string, value: string) => void;
|
|
53
|
+
removeItem: (key: string) => void;
|
|
53
54
|
};
|
|
54
55
|
/**
|
|
55
56
|
* Gets or generates a user ID from localStorage.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ANALYTICS_JS_DEFAULT_PROPERTIES as
|
|
2
|
-
import { DotLogger as
|
|
3
|
-
function
|
|
1
|
+
import { ANALYTICS_JS_DEFAULT_PROPERTIES as p, SESSION_STORAGE_KEY as g, DEFAULT_SESSION_TIMEOUT_MINUTES as T, USER_ID_KEY as f, DEFAULT_IMPRESSION_MUTATION_OBSERVER_DEBOUNCE_MS as I, EXPECTED_UTM_KEYS as D, CONTENTLET_CLASS as h } from "../constants/dot-analytics.constants.js";
|
|
2
|
+
import { DotLogger as y } from "../dot-analytics.logger.js";
|
|
3
|
+
function P(t) {
|
|
4
4
|
const e = [];
|
|
5
5
|
return t.siteAuth?.trim() || e.push('"siteAuth"'), t.server?.trim() || e.push('"server"'), e.length > 0 ? e : null;
|
|
6
6
|
}
|
|
@@ -8,7 +8,7 @@ let d = null, u = null;
|
|
|
8
8
|
const l = (t) => {
|
|
9
9
|
const e = Date.now(), n = Math.random().toString(36).substr(2, 9), s = Math.random().toString(36).substr(2, 9);
|
|
10
10
|
return `${t}_${e}_${n}${s}`;
|
|
11
|
-
},
|
|
11
|
+
}, w = {
|
|
12
12
|
getItem: (t) => {
|
|
13
13
|
try {
|
|
14
14
|
return localStorage.getItem(t);
|
|
@@ -19,20 +19,49 @@ const l = (t) => {
|
|
|
19
19
|
setItem: (t, e) => {
|
|
20
20
|
try {
|
|
21
21
|
localStorage.setItem(t, e);
|
|
22
|
+
} catch (n) {
|
|
23
|
+
throw console.warn(`DotCMS Analytics [Core]: Could not save ${t} to localStorage`), n;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}, R = {
|
|
27
|
+
getItem: (t) => {
|
|
28
|
+
try {
|
|
29
|
+
return sessionStorage.getItem(t);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
setItem: (t, e) => {
|
|
35
|
+
try {
|
|
36
|
+
sessionStorage.setItem(t, e);
|
|
37
|
+
} catch (n) {
|
|
38
|
+
throw console.warn(`DotCMS Analytics [Core]: Could not save ${t} to sessionStorage`), n;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
removeItem: (t) => {
|
|
42
|
+
try {
|
|
43
|
+
sessionStorage.removeItem(t);
|
|
22
44
|
} catch {
|
|
23
|
-
console.warn(`DotCMS Analytics [Core]: Could not
|
|
45
|
+
console.warn(`DotCMS Analytics [Core]: Could not remove ${t} from sessionStorage`);
|
|
24
46
|
}
|
|
25
47
|
}
|
|
26
|
-
},
|
|
27
|
-
let t =
|
|
28
|
-
|
|
29
|
-
|
|
48
|
+
}, E = () => {
|
|
49
|
+
let t = w.getItem(f);
|
|
50
|
+
if (!t) {
|
|
51
|
+
t = l("user");
|
|
52
|
+
try {
|
|
53
|
+
w.setItem(f, t);
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return t;
|
|
58
|
+
}, C = (t) => {
|
|
30
59
|
const e = new Date(t), n = /* @__PURE__ */ new Date(), s = new Date(
|
|
31
60
|
e.getUTCFullYear(),
|
|
32
61
|
e.getUTCMonth(),
|
|
33
62
|
e.getUTCDate()
|
|
34
|
-
),
|
|
35
|
-
return s.getTime() !==
|
|
63
|
+
), o = new Date(n.getUTCFullYear(), n.getUTCMonth(), n.getUTCDate());
|
|
64
|
+
return s.getTime() !== o.getTime();
|
|
36
65
|
}, v = () => {
|
|
37
66
|
const t = Date.now();
|
|
38
67
|
if (typeof window > "u")
|
|
@@ -40,16 +69,16 @@ const l = (t) => {
|
|
|
40
69
|
try {
|
|
41
70
|
const e = sessionStorage.getItem(g);
|
|
42
71
|
if (e) {
|
|
43
|
-
const { sessionId:
|
|
44
|
-
if (
|
|
72
|
+
const { sessionId: o, startTime: r, lastActivity: c } = JSON.parse(e), a = !C(r), i = t - c < T * 60 * 1e3;
|
|
73
|
+
if (a && i)
|
|
45
74
|
return sessionStorage.setItem(
|
|
46
75
|
g,
|
|
47
76
|
JSON.stringify({
|
|
48
|
-
sessionId:
|
|
49
|
-
startTime:
|
|
77
|
+
sessionId: o,
|
|
78
|
+
startTime: r,
|
|
50
79
|
lastActivity: t
|
|
51
80
|
})
|
|
52
|
-
),
|
|
81
|
+
), o;
|
|
53
82
|
}
|
|
54
83
|
const n = l("session"), s = {
|
|
55
84
|
sessionId: n,
|
|
@@ -60,58 +89,58 @@ const l = (t) => {
|
|
|
60
89
|
} catch {
|
|
61
90
|
return l("session_fallback");
|
|
62
91
|
}
|
|
63
|
-
},
|
|
64
|
-
const e = v(), n =
|
|
92
|
+
}, x = (t) => {
|
|
93
|
+
const e = v(), n = E(), s = A();
|
|
65
94
|
return {
|
|
66
95
|
site_auth: t.siteAuth,
|
|
67
96
|
session_id: e,
|
|
68
97
|
user_id: n,
|
|
69
98
|
device: s
|
|
70
99
|
};
|
|
71
|
-
},
|
|
100
|
+
}, _ = () => d || (d = {
|
|
72
101
|
user_language: navigator.language,
|
|
73
102
|
doc_encoding: document.characterSet || document.charset,
|
|
74
103
|
screen_resolution: typeof screen < "u" && screen.width && screen.height ? `${screen.width}x${screen.height}` : ""
|
|
75
|
-
}, d),
|
|
76
|
-
const t =
|
|
104
|
+
}, d), A = () => {
|
|
105
|
+
const t = _(), e = window.innerWidth || document.documentElement.clientWidth || 0, n = window.innerHeight || document.documentElement.clientHeight || 0;
|
|
77
106
|
return {
|
|
78
107
|
screen_resolution: t.screen_resolution ?? "",
|
|
79
108
|
language: t.user_language ?? "",
|
|
80
109
|
viewport_width: String(e),
|
|
81
110
|
viewport_height: String(n)
|
|
82
111
|
};
|
|
83
|
-
},
|
|
112
|
+
}, b = (t) => {
|
|
84
113
|
const e = t.search;
|
|
85
114
|
if (u && u.search === e)
|
|
86
115
|
return u.params;
|
|
87
116
|
const n = new URLSearchParams(e), s = {};
|
|
88
|
-
return D.forEach((
|
|
89
|
-
const
|
|
90
|
-
if (
|
|
91
|
-
const
|
|
92
|
-
s[
|
|
117
|
+
return D.forEach((o) => {
|
|
118
|
+
const r = n.get(o);
|
|
119
|
+
if (r) {
|
|
120
|
+
const c = o.replace("utm_", "");
|
|
121
|
+
s[c] = r;
|
|
93
122
|
}
|
|
94
123
|
}), u = { search: e, params: s }, s;
|
|
95
124
|
}, O = () => {
|
|
96
125
|
try {
|
|
97
|
-
const t = (/* @__PURE__ */ new Date()).getTimezoneOffset(), e = t > 0 ? "-" : "+", n = Math.abs(t), s = Math.floor(n / 60),
|
|
98
|
-
return `${e}${s.toString().padStart(2, "0")}:${
|
|
126
|
+
const t = (/* @__PURE__ */ new Date()).getTimezoneOffset(), e = t > 0 ? "-" : "+", n = Math.abs(t), s = Math.floor(n / 60), o = n % 60;
|
|
127
|
+
return `${e}${s.toString().padStart(2, "0")}:${o.toString().padStart(2, "0")}`;
|
|
99
128
|
} catch {
|
|
100
129
|
return "+00:00";
|
|
101
130
|
}
|
|
102
|
-
},
|
|
131
|
+
}, M = () => {
|
|
103
132
|
try {
|
|
104
|
-
const t = /* @__PURE__ */ new Date(), e = O(), n = t.getFullYear(), s = (t.getMonth() + 1).toString().padStart(2, "0"),
|
|
105
|
-
return `${n}-${s}-${
|
|
133
|
+
const t = /* @__PURE__ */ new Date(), e = O(), n = t.getFullYear(), s = (t.getMonth() + 1).toString().padStart(2, "0"), o = t.getDate().toString().padStart(2, "0"), r = t.getHours().toString().padStart(2, "0"), c = t.getMinutes().toString().padStart(2, "0"), a = t.getSeconds().toString().padStart(2, "0");
|
|
134
|
+
return `${n}-${s}-${o}T${r}:${c}:${a}${e}`;
|
|
106
135
|
} catch {
|
|
107
136
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
108
137
|
}
|
|
109
|
-
},
|
|
110
|
-
const n =
|
|
111
|
-
Object.keys(
|
|
112
|
-
|
|
138
|
+
}, Y = (t, e = typeof window < "u" ? window.location : {}) => {
|
|
139
|
+
const n = M(), s = _(), { properties: o } = t, r = {};
|
|
140
|
+
Object.keys(o).forEach((i) => {
|
|
141
|
+
p.includes(i) || (r[i] = o[i]);
|
|
113
142
|
});
|
|
114
|
-
const
|
|
143
|
+
const c = {
|
|
115
144
|
url: e.href,
|
|
116
145
|
doc_encoding: s.doc_encoding,
|
|
117
146
|
doc_hash: e.hash,
|
|
@@ -119,28 +148,28 @@ const l = (t) => {
|
|
|
119
148
|
doc_search: e.search,
|
|
120
149
|
doc_host: e.hostname,
|
|
121
150
|
doc_path: e.pathname,
|
|
122
|
-
title:
|
|
123
|
-
},
|
|
151
|
+
title: o.title ?? document?.title
|
|
152
|
+
}, a = b(e);
|
|
124
153
|
return {
|
|
125
154
|
...t,
|
|
126
|
-
page:
|
|
127
|
-
...Object.keys(
|
|
155
|
+
page: c,
|
|
156
|
+
...Object.keys(a).length > 0 && { utm: a },
|
|
128
157
|
// Only include custom if there are user-provided properties
|
|
129
|
-
...Object.keys(
|
|
158
|
+
...Object.keys(r).length > 0 && { custom: r },
|
|
130
159
|
local_time: n
|
|
131
160
|
};
|
|
132
161
|
};
|
|
133
|
-
function
|
|
162
|
+
function L(t, e) {
|
|
134
163
|
let n = 0;
|
|
135
164
|
return (...s) => {
|
|
136
|
-
const
|
|
137
|
-
|
|
165
|
+
const o = Date.now();
|
|
166
|
+
o - n >= e && (t(...s), n = o);
|
|
138
167
|
};
|
|
139
168
|
}
|
|
140
|
-
function
|
|
169
|
+
function B(t) {
|
|
141
170
|
return t.dataset.dotIdentifier || null;
|
|
142
171
|
}
|
|
143
|
-
function
|
|
172
|
+
function F(t) {
|
|
144
173
|
return {
|
|
145
174
|
identifier: t.dataset.dotIdentifier || "",
|
|
146
175
|
inode: t.dataset.dotInode || "",
|
|
@@ -149,51 +178,59 @@ function Y(t) {
|
|
|
149
178
|
baseType: t.dataset.dotBasetype || ""
|
|
150
179
|
};
|
|
151
180
|
}
|
|
152
|
-
const
|
|
153
|
-
const n =
|
|
181
|
+
const z = 100, N = () => typeof window < "u" && typeof document < "u", H = () => Array.from(document.querySelectorAll(`.${h}`)), J = (t, e = I) => {
|
|
182
|
+
const n = L(t, e), s = new MutationObserver((r) => {
|
|
154
183
|
r.some((a) => a.addedNodes.length === 0 && a.removedNodes.length === 0 ? !1 : [
|
|
155
184
|
...Array.from(a.addedNodes),
|
|
156
185
|
...Array.from(a.removedNodes)
|
|
157
|
-
].some((
|
|
158
|
-
if (
|
|
186
|
+
].some((m) => {
|
|
187
|
+
if (m.nodeType !== Node.ELEMENT_NODE)
|
|
159
188
|
return !1;
|
|
160
|
-
const S =
|
|
189
|
+
const S = m;
|
|
161
190
|
return S.classList?.contains(h) ? !0 : S.querySelector?.(`.${h}`) !== null;
|
|
162
191
|
})) && n();
|
|
163
|
-
});
|
|
164
|
-
return s.observe(
|
|
192
|
+
}), o = document.body || document.documentElement;
|
|
193
|
+
return o ? s.observe(o, {
|
|
165
194
|
childList: !0,
|
|
166
195
|
subtree: !0,
|
|
167
196
|
attributes: !1,
|
|
168
197
|
characterData: !1
|
|
198
|
+
}) : window.addEventListener("DOMContentLoaded", () => {
|
|
199
|
+
document.body && s.observe(document.body, {
|
|
200
|
+
childList: !0,
|
|
201
|
+
subtree: !0,
|
|
202
|
+
attributes: !1,
|
|
203
|
+
characterData: !1
|
|
204
|
+
});
|
|
169
205
|
}), s;
|
|
170
|
-
},
|
|
171
|
-
|
|
172
|
-
},
|
|
206
|
+
}, K = (t) => {
|
|
207
|
+
N() && (window.addEventListener("beforeunload", t), window.addEventListener("pagehide", t));
|
|
208
|
+
}, j = (t, e) => {
|
|
173
209
|
const n = e.logLevel ?? (e.debug ? "debug" : "warn");
|
|
174
|
-
return new
|
|
175
|
-
},
|
|
210
|
+
return new y("Analytics", t, n);
|
|
211
|
+
}, W = (t, e, n) => [
|
|
176
212
|
t.impressions && e(t),
|
|
177
213
|
t.clicks && n(t)
|
|
178
214
|
].filter(Boolean);
|
|
179
215
|
export {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
216
|
+
z as INITIAL_SCAN_DELAY_MS,
|
|
217
|
+
J as createContentletObserver,
|
|
218
|
+
j as createPluginLogger,
|
|
219
|
+
L as createThrottle,
|
|
220
|
+
Y as enrichPagePayloadOptimized,
|
|
221
|
+
F as extractContentletData,
|
|
222
|
+
B as extractContentletIdentifier,
|
|
223
|
+
b as extractUTMParameters,
|
|
224
|
+
H as findContentlets,
|
|
189
225
|
l as generateSecureId,
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
226
|
+
x as getAnalyticsContext,
|
|
227
|
+
A as getDeviceDataForContext,
|
|
228
|
+
W as getEnhancedTrackingPlugins,
|
|
229
|
+
M as getLocalTime,
|
|
194
230
|
v as getSessionId,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
231
|
+
E as getUserId,
|
|
232
|
+
N as isBrowser,
|
|
233
|
+
R as safeSessionStorage,
|
|
234
|
+
K as setupPluginCleanup,
|
|
235
|
+
P as validateAnalyticsConfig
|
|
199
236
|
};
|